mirror of
https://github.com/JakubMelka/PDF4QT.git
synced 2025-06-05 21:59:17 +02:00
Text drawing using FreeType library
This commit is contained in:
@@ -91,6 +91,20 @@ unix {
|
||||
}
|
||||
|
||||
|
||||
# Link to freetype library
|
||||
|
||||
win32:CONFIG(release, debug|release): LIBS += -L$$PWD/../FreeType/ -lfreetype
|
||||
else:win32:CONFIG(debug, debug|release): LIBS += -L$$PWD/../FreeType/ -lfreetype
|
||||
else:unix: LIBS += -L$$PWD/FreeType/ -lfreetype
|
||||
|
||||
INCLUDEPATH += $$PWD/../FreeType/include
|
||||
DEPENDPATH += $$PWD/../FreeType/include
|
||||
|
||||
freetype_lib.files = $$PWD/../FreeType/freetype.dll
|
||||
freetype_lib.path = $$OUT_PWD
|
||||
|
||||
INSTALLS += freetype_lib
|
||||
|
||||
CONFIG += force_debug_info
|
||||
|
||||
|
||||
|
@@ -20,6 +20,14 @@
|
||||
#include "pdfparser.h"
|
||||
#include "pdfnametounicode.h"
|
||||
|
||||
#include <ft2build.h>
|
||||
#include <freetype/freetype.h>
|
||||
#include <freetype/ftglyph.h>
|
||||
#include <freetype/fterrors.h>
|
||||
#include <freetype/ftoutln.h>
|
||||
|
||||
#include <QMutex>
|
||||
|
||||
namespace pdf
|
||||
{
|
||||
|
||||
@@ -29,8 +37,271 @@ PDFFont::PDFFont(FontDescriptor fontDescriptor) :
|
||||
|
||||
}
|
||||
|
||||
/// Implementation of the PDFRealizedFont class using PIMPL pattern
|
||||
class PDFRealizedFontImpl
|
||||
{
|
||||
public:
|
||||
explicit PDFRealizedFontImpl();
|
||||
~PDFRealizedFontImpl();
|
||||
|
||||
/// Fills the text sequence by interpreting byte array according font data and
|
||||
/// produces glyphs for the font.
|
||||
/// \param byteArray Array of bytes to be interpreted
|
||||
/// \param textSequence Text sequence to be filled
|
||||
void fillTextSequence(const QByteArray& byteArray, TextSequence& textSequence);
|
||||
|
||||
private:
|
||||
friend class PDFRealizedFont;
|
||||
|
||||
static constexpr const PDFReal FORMAT_26_6_MULTIPLIER = 1 / 64.0;
|
||||
|
||||
struct Glyph
|
||||
{
|
||||
QPainterPath glyph;
|
||||
PDFReal advance;
|
||||
};
|
||||
|
||||
static int outlineMoveTo(const FT_Vector* to, void* user);
|
||||
static int outlineLineTo(const FT_Vector* to, void* user);
|
||||
static int outlineConicTo(const FT_Vector* control, const FT_Vector* to, void* user);
|
||||
static int outlineCubicTo(const FT_Vector* control1, const FT_Vector* control2, const FT_Vector* to, void* user);
|
||||
|
||||
/// Get glyph for unicode character. Throws exception, if glyph can't be found.
|
||||
const Glyph& getGlyphForUnicode(QChar character);
|
||||
|
||||
/// Function checks, if error occured, and if yes, then exception is thrown
|
||||
static void checkFreeTypeError(FT_Error error);
|
||||
|
||||
/// Mutex for accessing the glyph data
|
||||
QMutex m_mutex;
|
||||
|
||||
/// Glyph cache, must be protected by the mutex above
|
||||
std::map<QChar, Glyph> m_glyphCache;
|
||||
|
||||
/// For embedded fonts, this byte array contains embedded font data
|
||||
QByteArray m_embeddedFontData;
|
||||
|
||||
/// Instance of FreeType library assigned to this font
|
||||
FT_Library m_library;
|
||||
|
||||
/// Face of the font
|
||||
FT_Face m_face;
|
||||
|
||||
/// Pixel size of the font
|
||||
PDFReal m_pixelSize;
|
||||
|
||||
/// Parent font
|
||||
const PDFFont* m_parentFont;
|
||||
|
||||
/// True, if font is embedded
|
||||
bool m_isEmbedded;
|
||||
|
||||
/// True, if font has vertical writing system
|
||||
bool m_isVertical;
|
||||
};
|
||||
|
||||
PDFRealizedFontImpl::PDFRealizedFontImpl() :
|
||||
m_library(nullptr),
|
||||
m_face(nullptr),
|
||||
m_pixelSize(0.0),
|
||||
m_parentFont(nullptr),
|
||||
m_isEmbedded(false),
|
||||
m_isVertical(false)
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
PDFRealizedFontImpl::~PDFRealizedFontImpl()
|
||||
{
|
||||
if (m_face)
|
||||
{
|
||||
FT_Done_Face(m_face);
|
||||
m_face = nullptr;
|
||||
}
|
||||
|
||||
if (m_library)
|
||||
{
|
||||
FT_Done_FreeType(m_library);
|
||||
m_library = nullptr;
|
||||
}
|
||||
}
|
||||
|
||||
void PDFRealizedFontImpl::fillTextSequence(const QByteArray& byteArray, TextSequence& textSequence)
|
||||
{
|
||||
switch (m_parentFont->getFontType())
|
||||
{
|
||||
case FontType::Type1:
|
||||
case FontType::TrueType:
|
||||
{
|
||||
// We can use encoding
|
||||
QString text = m_parentFont->getTextUsingEncoding(byteArray);
|
||||
|
||||
textSequence.items.reserve(textSequence.items.size() + text.size());
|
||||
for (const QChar& character : text)
|
||||
{
|
||||
const Glyph& glyph = getGlyphForUnicode(character);
|
||||
textSequence.items.emplace_back(&glyph.glyph, character, glyph.advance);
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
default:
|
||||
{
|
||||
// Unhandled font type
|
||||
Q_ASSERT(false);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
int PDFRealizedFontImpl::outlineMoveTo(const FT_Vector* to, void* user)
|
||||
{
|
||||
Glyph* glyph = reinterpret_cast<Glyph*>(user);
|
||||
glyph->glyph.moveTo(to->x * FORMAT_26_6_MULTIPLIER, to->y * FORMAT_26_6_MULTIPLIER);
|
||||
return 0;
|
||||
}
|
||||
|
||||
int PDFRealizedFontImpl::outlineLineTo(const FT_Vector* to, void* user)
|
||||
{
|
||||
Glyph* glyph = reinterpret_cast<Glyph*>(user);
|
||||
glyph->glyph.lineTo(to->x * FORMAT_26_6_MULTIPLIER, to->y * FORMAT_26_6_MULTIPLIER);
|
||||
return 0;
|
||||
}
|
||||
|
||||
int PDFRealizedFontImpl::outlineConicTo(const FT_Vector* control, const FT_Vector* to, void* user)
|
||||
{
|
||||
Glyph* glyph = reinterpret_cast<Glyph*>(user);
|
||||
glyph->glyph.cubicTo(control->x * FORMAT_26_6_MULTIPLIER, control->y * FORMAT_26_6_MULTIPLIER, control->x * FORMAT_26_6_MULTIPLIER, control->y * FORMAT_26_6_MULTIPLIER, to->x * FORMAT_26_6_MULTIPLIER, to->y * FORMAT_26_6_MULTIPLIER);
|
||||
return 0;
|
||||
}
|
||||
|
||||
int PDFRealizedFontImpl::outlineCubicTo(const FT_Vector* control1, const FT_Vector* control2, const FT_Vector* to, void* user)
|
||||
{
|
||||
Glyph* glyph = reinterpret_cast<Glyph*>(user);
|
||||
glyph->glyph.cubicTo(control1->x * FORMAT_26_6_MULTIPLIER, control1->y * FORMAT_26_6_MULTIPLIER, control2->x * FORMAT_26_6_MULTIPLIER, control2->y * FORMAT_26_6_MULTIPLIER, to->x * FORMAT_26_6_MULTIPLIER, to->y * FORMAT_26_6_MULTIPLIER);
|
||||
return 0;
|
||||
}
|
||||
|
||||
const PDFRealizedFontImpl::Glyph& PDFRealizedFontImpl::getGlyphForUnicode(QChar character)
|
||||
{
|
||||
QMutexLocker lock(&m_mutex);
|
||||
|
||||
// First look into cache
|
||||
auto it = m_glyphCache.find(character);
|
||||
if (it != m_glyphCache.cend())
|
||||
{
|
||||
return it->second;
|
||||
}
|
||||
|
||||
FT_UInt glyphIndex = FT_Get_Char_Index(m_face, character.unicode());
|
||||
if (glyphIndex)
|
||||
{
|
||||
Glyph glyph;
|
||||
|
||||
FT_Outline_Funcs interface;
|
||||
interface.delta = 0;
|
||||
interface.shift = 0;
|
||||
interface.move_to = PDFRealizedFontImpl::outlineMoveTo;
|
||||
interface.line_to = PDFRealizedFontImpl::outlineLineTo;
|
||||
interface.conic_to = PDFRealizedFontImpl::outlineConicTo;
|
||||
interface.cubic_to = PDFRealizedFontImpl::outlineCubicTo;
|
||||
|
||||
checkFreeTypeError(FT_Load_Glyph(m_face, glyphIndex, FT_LOAD_NO_BITMAP | FT_LOAD_NO_HINTING));
|
||||
checkFreeTypeError(FT_Outline_Decompose(&m_face->glyph->outline, &interface, &glyph));
|
||||
glyph.glyph.closeSubpath();
|
||||
glyph.advance = !m_isVertical ? m_face->glyph->advance.x : m_face->glyph->advance.y;
|
||||
glyph.advance *= FORMAT_26_6_MULTIPLIER;
|
||||
|
||||
m_glyphCache[character] = qMove(glyph);
|
||||
return m_glyphCache[character];
|
||||
}
|
||||
else
|
||||
{
|
||||
throw PDFParserException(PDFTranslationContext::tr("Glyph for unicode character '%1' not found.").arg(character));
|
||||
}
|
||||
|
||||
static Glyph dummy;
|
||||
return dummy;
|
||||
}
|
||||
|
||||
void PDFRealizedFontImpl::checkFreeTypeError(FT_Error error)
|
||||
{
|
||||
if (error)
|
||||
{
|
||||
QString message;
|
||||
if (const char* errorString = FT_Error_String(error))
|
||||
{
|
||||
message = QString::fromLatin1(errorString);
|
||||
}
|
||||
|
||||
throw PDFParserException(PDFTranslationContext::tr("FreeType error code %1: message").arg(error).arg(message));
|
||||
}
|
||||
}
|
||||
|
||||
PDFRealizedFont::~PDFRealizedFont()
|
||||
{
|
||||
delete m_impl;
|
||||
}
|
||||
|
||||
void PDFRealizedFont::fillTextSequence(const QByteArray& byteArray, TextSequence& textSequence)
|
||||
{
|
||||
m_impl->fillTextSequence(byteArray, textSequence);
|
||||
}
|
||||
|
||||
bool PDFRealizedFont::isHorizontalWritingSystem() const
|
||||
{
|
||||
return !m_impl->m_isVertical;
|
||||
}
|
||||
|
||||
PDFRealizedFontPointer PDFRealizedFont::createRealizedFont(const PDFFont* font, PDFReal pixelSize)
|
||||
{
|
||||
PDFRealizedFontPointer result;
|
||||
std::unique_ptr<PDFRealizedFontImpl> implPtr(new PDFRealizedFontImpl());
|
||||
|
||||
PDFRealizedFontImpl* impl = implPtr.get();
|
||||
impl->m_parentFont = font;
|
||||
|
||||
const FontDescriptor* descriptor = font->getFontDescriptor();
|
||||
if (descriptor->isEmbedded())
|
||||
{
|
||||
|
||||
PDFRealizedFontImpl::checkFreeTypeError(FT_Init_FreeType(&impl->m_library));
|
||||
|
||||
if (!descriptor->fontFile.isEmpty())
|
||||
{
|
||||
impl->m_embeddedFontData = descriptor->fontFile;
|
||||
}
|
||||
else if (!descriptor->fontFile2.isEmpty())
|
||||
{
|
||||
impl->m_embeddedFontData = descriptor->fontFile2;
|
||||
}
|
||||
else if (!descriptor->fontFile3.isEmpty())
|
||||
{
|
||||
impl->m_embeddedFontData = descriptor->fontFile3;
|
||||
}
|
||||
|
||||
// At this time, embedded font data should not be empty!
|
||||
Q_ASSERT(!impl->m_embeddedFontData.isEmpty());
|
||||
|
||||
PDFRealizedFontImpl::checkFreeTypeError(FT_New_Memory_Face(impl->m_library, reinterpret_cast<const FT_Byte*>(impl->m_embeddedFontData.constData()), impl->m_embeddedFontData.size(), 0, &impl->m_face));
|
||||
PDFRealizedFontImpl::checkFreeTypeError(FT_Select_Charmap(impl->m_face, FT_ENCODING_UNICODE));
|
||||
PDFRealizedFontImpl::checkFreeTypeError(FT_Set_Pixel_Sizes(impl->m_face, 0, qRound(pixelSize)));
|
||||
impl->m_isVertical = impl->m_face->face_flags & FT_FACE_FLAG_VERTICAL;
|
||||
impl->m_isEmbedded = true;
|
||||
result.reset(new PDFRealizedFont(implPtr.release()));
|
||||
}
|
||||
else
|
||||
{
|
||||
// TODO: Support non-embedded fonts
|
||||
throw PDFParserException(PDFTranslationContext::tr("Only embedded fonts are supported."));
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
PDFFontPointer PDFFont::createFont(const PDFObject& object, const PDFDocument* document)
|
||||
{
|
||||
// TODO: Create font cache for realized fonts
|
||||
const PDFObject& dereferencedFontDictionary = document->getObject(object);
|
||||
if (!dereferencedFontDictionary.isDictionary())
|
||||
{
|
||||
@@ -326,9 +597,13 @@ PDFSimpleFont::PDFSimpleFont(FontDescriptor fontDescriptor,
|
||||
|
||||
}
|
||||
|
||||
QRawFont PDFSimpleFont::getRealizedFont(PDFReal fontSize) const
|
||||
PDFRealizedFontPointer PDFSimpleFont::getRealizedFont(PDFReal fontSize) const
|
||||
{
|
||||
// TODO: Fix font creation to use also embedded fonts, font descriptor, etc.
|
||||
// TODO: Remove QRawFont
|
||||
|
||||
return PDFRealizedFont::createRealizedFont(this, fontSize);
|
||||
/*
|
||||
QRawFont rawFont;
|
||||
|
||||
if (m_fontDescriptor.isEmbedded())
|
||||
@@ -362,7 +637,7 @@ QRawFont PDFSimpleFont::getRealizedFont(PDFReal fontSize) const
|
||||
rawFont = QRawFont::fromFont(font, QFontDatabase::Any);
|
||||
}
|
||||
|
||||
return rawFont;
|
||||
return rawFont;*/
|
||||
}
|
||||
|
||||
QString PDFSimpleFont::getTextUsingEncoding(const QByteArray& byteArray) const
|
||||
|
@@ -41,6 +41,27 @@ enum class TextRenderingMode
|
||||
Clip = 7
|
||||
};
|
||||
|
||||
/// Item of the text sequence (either single character, or advance)
|
||||
struct TextSequenceItem
|
||||
{
|
||||
inline explicit TextSequenceItem() = default;
|
||||
inline explicit TextSequenceItem(const QPainterPath* glyph, QChar character, PDFReal advance) : glyph(glyph), character(character), advance(advance) { }
|
||||
inline explicit TextSequenceItem(PDFReal advance) : character(), advance(advance) { }
|
||||
|
||||
inline bool isCharacter() const { return !character.isNull(); }
|
||||
inline bool isAdvance() const { return advance != 0.0; }
|
||||
inline bool isNull() const { return !isCharacter() && !isAdvance(); }
|
||||
|
||||
const QPainterPath* glyph = nullptr;
|
||||
QChar character;
|
||||
PDFReal advance = 0;
|
||||
};
|
||||
|
||||
struct TextSequence
|
||||
{
|
||||
std::vector<TextSequenceItem> items;
|
||||
};
|
||||
|
||||
constexpr bool isTextRenderingModeFilled(TextRenderingMode mode)
|
||||
{
|
||||
switch (mode)
|
||||
@@ -169,6 +190,38 @@ class PDFFont;
|
||||
|
||||
using PDFFontPointer = QSharedPointer<PDFFont>;
|
||||
|
||||
class PDFRealizedFont;
|
||||
class PDFRealizedFontImpl;
|
||||
|
||||
using PDFRealizedFontPointer = QSharedPointer<PDFRealizedFont>;
|
||||
|
||||
/// Font, which has fixed pixel size. It is programmed as PIMPL, because we need
|
||||
/// to remove FreeType types from the interface (so we do not include FreeType in the interface).
|
||||
class PDFRealizedFont
|
||||
{
|
||||
public:
|
||||
~PDFRealizedFont();
|
||||
|
||||
/// Fills the text sequence by interpreting byte array according font data and
|
||||
/// produces glyphs for the font.
|
||||
/// \param byteArray Array of bytes to be interpreted
|
||||
/// \param textSequence Text sequence to be filled
|
||||
void fillTextSequence(const QByteArray& byteArray, TextSequence& textSequence);
|
||||
|
||||
/// Return true, if we have horizontal writing system
|
||||
bool isHorizontalWritingSystem() const;
|
||||
|
||||
/// Creates new realized font from the standard font. If font can't be created,
|
||||
/// then exception is thrown.
|
||||
static PDFRealizedFontPointer createRealizedFont(const PDFFont* font, PDFReal pixelSize);
|
||||
|
||||
private:
|
||||
/// Constructs new realized font
|
||||
explicit PDFRealizedFont(PDFRealizedFontImpl* impl) : m_impl(impl) { }
|
||||
|
||||
PDFRealizedFontImpl* m_impl;
|
||||
};
|
||||
|
||||
/// Base class representing font in the PDF file
|
||||
class PDFFont
|
||||
{
|
||||
@@ -182,7 +235,7 @@ public:
|
||||
/// Realizes the font (physical materialization of the font using pixel size,
|
||||
/// if font can't be realized, then empty QRawFont is returned).
|
||||
/// \param fontSize Size of the font
|
||||
virtual QRawFont getRealizedFont(PDFReal fontSize) const = 0;
|
||||
virtual PDFRealizedFontPointer getRealizedFont(PDFReal fontSize) const = 0;
|
||||
|
||||
/// Returns text using the font encoding
|
||||
/// \param byteArray Byte array with encoded string
|
||||
@@ -215,7 +268,7 @@ public:
|
||||
encoding::EncodingTable encoding);
|
||||
virtual ~PDFSimpleFont() override = default;
|
||||
|
||||
virtual QRawFont getRealizedFont(PDFReal fontSize) const override;
|
||||
virtual PDFRealizedFontPointer getRealizedFont(PDFReal fontSize) const override;
|
||||
virtual QString getTextUsingEncoding(const QByteArray& byteArray) const override;
|
||||
|
||||
protected:
|
||||
|
@@ -1626,8 +1626,19 @@ void PDFPageContentProcessor::operatorTextShowTextString(PDFOperandString text)
|
||||
{
|
||||
if (m_graphicState.getTextFont())
|
||||
{
|
||||
QString textDecoded = m_graphicState.getTextFont()->getTextUsingEncoding(text.string);
|
||||
drawText(TextSequence::fromString(textDecoded));
|
||||
// Get the realized font
|
||||
const PDFRealizedFontPointer& realizedFont = getRealizedFont();
|
||||
if (!realizedFont)
|
||||
{
|
||||
throw PDFRendererException(RenderErrorType::Error, PDFTranslationContext::tr("Invalid font, text can't be printed."));
|
||||
}
|
||||
|
||||
TextSequence textSequence;
|
||||
|
||||
// We use simple heuristic to ensure reallocation doesn't occur too often
|
||||
textSequence.items.reserve(m_operands.size());
|
||||
realizedFont->fillTextSequence(text.string, textSequence);
|
||||
drawText(textSequence);
|
||||
}
|
||||
else
|
||||
{
|
||||
@@ -1658,6 +1669,13 @@ void PDFPageContentProcessor::operatorTextShowTextIndividualSpacing()
|
||||
// We use simple heuristic to ensure reallocation doesn't occur too often
|
||||
textSequence.items.reserve(m_operands.size() * 4);
|
||||
|
||||
// Get the realized font
|
||||
const PDFRealizedFontPointer& realizedFont = getRealizedFont();
|
||||
if (!realizedFont)
|
||||
{
|
||||
throw PDFRendererException(RenderErrorType::Error, PDFTranslationContext::tr("Invalid font, text can't be printed."));
|
||||
}
|
||||
|
||||
for (size_t i = 1, lastIndex = m_operands.size() - 1; i < lastIndex; ++i)
|
||||
{
|
||||
switch (m_operands[i].type)
|
||||
@@ -1676,8 +1694,7 @@ void PDFPageContentProcessor::operatorTextShowTextIndividualSpacing()
|
||||
|
||||
case PDFLexicalAnalyzer::TokenType::String:
|
||||
{
|
||||
QString string = m_graphicState.getTextFont()->getTextUsingEncoding(m_operands[i].data.toByteArray());
|
||||
std::transform(string.cbegin(), string.cend(), std::back_inserter(textSequence.items), [](const QChar character) { return TextSequenceItem(character); });
|
||||
realizedFont->fillTextSequence(m_operands[i].data.toByteArray(), textSequence);
|
||||
break;
|
||||
}
|
||||
|
||||
@@ -1721,10 +1738,89 @@ void PDFPageContentProcessor::drawText(const TextSequence& textSequence)
|
||||
return;
|
||||
}
|
||||
|
||||
const QRawFont& font = getRealizedFont();
|
||||
if (font.isValid())
|
||||
const PDFRealizedFontPointer& font = getRealizedFont();
|
||||
if (font)
|
||||
{
|
||||
std::vector<QChar> chars;
|
||||
const PDFReal fontSize = m_graphicState.getTextFontSize();
|
||||
const PDFReal horizontalScaling = m_graphicState.getTextHorizontalScaling() * 0.01; // Horizontal scaling is in percents
|
||||
const PDFReal characterSpacing = m_graphicState.getTextCharacterSpacing();
|
||||
const PDFReal wordSpacing = m_graphicState.getTextWordSpacing();
|
||||
const PDFReal textRise = m_graphicState.getTextRise();
|
||||
const TextRenderingMode textRenderingMode = m_graphicState.getTextRenderingMode();
|
||||
const bool fill = isTextRenderingModeFilled(textRenderingMode);
|
||||
const bool stroke = isTextRenderingModeStroked(textRenderingMode);
|
||||
const bool clipped = isTextRenderingModeClipped(textRenderingMode);
|
||||
// TODO: Add Text Clipping
|
||||
// TODO: Pouzit pravdepodobne sirky z widths array?
|
||||
|
||||
// Detect horizontal writing system
|
||||
const bool isHorizontalWritingSystem = font->isHorizontalWritingSystem();
|
||||
|
||||
// Calculate text rendering matrix
|
||||
QMatrix adjustMatrix(horizontalScaling, 0.0, 0.0, 1.0, 0.0, textRise);
|
||||
QMatrix textMatrix = m_graphicState.getTextMatrix();
|
||||
QMatrix fontMatrix(1.0, 0.0, 0.0, 1.0, 0.0, 0.0);
|
||||
|
||||
size_t characterIndex = 0;
|
||||
for (const TextSequenceItem& item : textSequence.items)
|
||||
{
|
||||
PDFReal displacementX = 0.0;
|
||||
PDFReal displacementY = 0.0;
|
||||
|
||||
if (item.isCharacter())
|
||||
{
|
||||
QChar character = item.character;
|
||||
QPointF advance = isHorizontalWritingSystem ? QPointF(item.advance, 0) : QPointF(0, item.advance);
|
||||
|
||||
// First, compute the advance
|
||||
const PDFReal additionalAdvance = (character == QChar(QChar::Space)) ? wordSpacing : characterSpacing;
|
||||
if (isHorizontalWritingSystem)
|
||||
{
|
||||
advance.rx() += additionalAdvance;
|
||||
}
|
||||
else
|
||||
{
|
||||
advance.ry() += additionalAdvance;
|
||||
}
|
||||
advance.rx() *= horizontalScaling;
|
||||
|
||||
// Then get the glyph path and paint it
|
||||
if (item.glyph)
|
||||
{
|
||||
QPainterPath glyphPath = fontMatrix.map(*item.glyph);
|
||||
if (!glyphPath.isEmpty())
|
||||
{
|
||||
QMatrix textRenderingMatrix = textMatrix * adjustMatrix;
|
||||
QPainterPath transformedGlyph = textRenderingMatrix.map(glyphPath);
|
||||
performPathPainting(transformedGlyph, stroke, fill, transformedGlyph.fillRule());
|
||||
}
|
||||
}
|
||||
|
||||
displacementX = advance.x();
|
||||
displacementY = advance.y();
|
||||
|
||||
++characterIndex;
|
||||
}
|
||||
else if (item.isAdvance())
|
||||
{
|
||||
if (horizontalScaling)
|
||||
{
|
||||
displacementX = -item.advance * 0.001 * fontSize * horizontalScaling;
|
||||
}
|
||||
else
|
||||
{
|
||||
displacementY = -item.advance * 0.001 * fontSize;
|
||||
}
|
||||
}
|
||||
|
||||
textMatrix.translate(displacementX, displacementY);
|
||||
}
|
||||
|
||||
m_graphicState.setTextMatrix(textMatrix);
|
||||
updateGraphicState();
|
||||
|
||||
|
||||
/*std::vector<QChar> chars;
|
||||
chars.reserve(textSequence.items.size());
|
||||
for (const TextSequenceItem& item : textSequence.items)
|
||||
{
|
||||
@@ -1748,80 +1844,7 @@ void PDFPageContentProcessor::drawText(const TextSequence& textSequence)
|
||||
advances.resize(numGlyphs, QPointF());
|
||||
if (font.advancesForGlyphIndexes(glyphIndices.data(), advances.data(), numGlyphs, QRawFont::SeparateAdvances | QRawFont::UseDesignMetrics))
|
||||
{
|
||||
const PDFReal fontSize = m_graphicState.getTextFontSize();
|
||||
const PDFReal horizontalScaling = m_graphicState.getTextHorizontalScaling() * 0.01; // Horizontal scaling is in percents
|
||||
const PDFReal characterSpacing = m_graphicState.getTextCharacterSpacing();
|
||||
const PDFReal wordSpacing = m_graphicState.getTextWordSpacing();
|
||||
const PDFReal textRise = m_graphicState.getTextRise();
|
||||
const TextRenderingMode textRenderingMode = m_graphicState.getTextRenderingMode();
|
||||
const bool fill = isTextRenderingModeFilled(textRenderingMode);
|
||||
const bool stroke = isTextRenderingModeStroked(textRenderingMode);
|
||||
const bool clipped = isTextRenderingModeClipped(textRenderingMode);
|
||||
// TODO: Add Text Clipping
|
||||
// TODO: Pouzit pravdepodobne sirky z widths array?
|
||||
|
||||
// Detect horizontal writing system
|
||||
const bool isHorizontalWritingSystem = std::any_of(advances.cbegin(), advances.cend(), [](const QPointF& point) { return !qFuzzyIsNull(point.x()); });
|
||||
|
||||
// Calculate text rendering matrix
|
||||
QMatrix adjustMatrix(horizontalScaling, 0.0, 0.0, 1.0, 0.0, textRise);
|
||||
QMatrix textMatrix = m_graphicState.getTextMatrix();
|
||||
QMatrix fontMatrix(1.0, 0.0, 0.0, -1.0, 0.0, 0.0);
|
||||
|
||||
size_t characterIndex = 0;
|
||||
for (const TextSequenceItem& item : textSequence.items)
|
||||
{
|
||||
PDFReal displacementX = 0.0;
|
||||
PDFReal displacementY = 0.0;
|
||||
|
||||
if (item.isCharacter())
|
||||
{
|
||||
QChar character = item.character;
|
||||
QPointF advance = advances[characterIndex];
|
||||
|
||||
// First, compute the advance
|
||||
const PDFReal additionalAdvance = (character == QChar(QChar::Space)) ? wordSpacing : characterSpacing;
|
||||
if (isHorizontalWritingSystem)
|
||||
{
|
||||
advance.rx() += additionalAdvance;
|
||||
}
|
||||
else
|
||||
{
|
||||
advance.ry() += additionalAdvance;
|
||||
}
|
||||
advance.rx() *= horizontalScaling;
|
||||
|
||||
// Then get the glyph path and paint it
|
||||
QPainterPath glyphPath = fontMatrix.map(font.pathForGlyph(glyphIndices[characterIndex]));
|
||||
if (!glyphPath.isEmpty())
|
||||
{
|
||||
QMatrix textRenderingMatrix = textMatrix * adjustMatrix;
|
||||
QPainterPath transformedGlyph = textRenderingMatrix.map(glyphPath);
|
||||
performPathPainting(transformedGlyph, stroke, fill, transformedGlyph.fillRule());
|
||||
}
|
||||
|
||||
displacementX = advance.x();
|
||||
displacementY = advance.y();
|
||||
|
||||
++characterIndex;
|
||||
}
|
||||
else if (item.isAdvance())
|
||||
{
|
||||
if (horizontalScaling)
|
||||
{
|
||||
displacementX = -item.advance * 0.001 * fontSize * horizontalScaling;
|
||||
}
|
||||
else
|
||||
{
|
||||
displacementY = -item.advance * 0.001 * fontSize;
|
||||
}
|
||||
}
|
||||
|
||||
textMatrix.translate(displacementX, displacementY);
|
||||
}
|
||||
|
||||
m_graphicState.setTextMatrix(textMatrix);
|
||||
updateGraphicState();
|
||||
}
|
||||
else
|
||||
{
|
||||
@@ -1831,7 +1854,7 @@ void PDFPageContentProcessor::drawText(const TextSequence& textSequence)
|
||||
else
|
||||
{
|
||||
throw PDFRendererException(RenderErrorType::Error, PDFTranslationContext::tr("Cant convert unicode to glyph indices, text can't be printed."));
|
||||
}
|
||||
}*/
|
||||
}
|
||||
else
|
||||
{
|
||||
@@ -1839,14 +1862,14 @@ void PDFPageContentProcessor::drawText(const TextSequence& textSequence)
|
||||
}
|
||||
}
|
||||
|
||||
QRawFont PDFPageContentProcessor::getRealizedFontImpl() const
|
||||
PDFRealizedFontPointer PDFPageContentProcessor::getRealizedFontImpl() const
|
||||
{
|
||||
if (m_graphicState.getTextFont())
|
||||
{
|
||||
return m_graphicState.getTextFont()->getRealizedFont(m_graphicState.getTextFontSize());
|
||||
}
|
||||
|
||||
return QRawFont();
|
||||
return PDFRealizedFontPointer();
|
||||
}
|
||||
|
||||
PDFPageContentProcessor::PDFPageContentProcessorState::PDFPageContentProcessorState() :
|
||||
@@ -2126,12 +2149,4 @@ void PDFPageContentProcessor::PDFPageContentProcessorState::setTextCharacterSpac
|
||||
}
|
||||
}
|
||||
|
||||
PDFPageContentProcessor::TextSequence PDFPageContentProcessor::TextSequence::fromString(const QString& string)
|
||||
{
|
||||
TextSequence result;
|
||||
result.items.reserve(string.size());
|
||||
std::transform(string.cbegin(), string.cend(), std::back_inserter(result.items), [](const QChar character) { return TextSequenceItem(character); });
|
||||
return result;
|
||||
}
|
||||
|
||||
} // namespace pdf
|
||||
|
@@ -350,28 +350,6 @@ protected:
|
||||
StateFlags m_stateFlags;
|
||||
};
|
||||
|
||||
/// Item of the text sequence (either single character, or advance)
|
||||
struct TextSequenceItem
|
||||
{
|
||||
inline explicit TextSequenceItem() = default;
|
||||
inline explicit TextSequenceItem(QChar character) : character(character), advance(0) { }
|
||||
inline explicit TextSequenceItem(PDFReal advance) : character(), advance(advance) { }
|
||||
|
||||
inline bool isCharacter() const { return !character.isNull(); }
|
||||
inline bool isAdvance() const { return advance != 0.0; }
|
||||
inline bool isNull() const { return !isCharacter() && !isAdvance(); }
|
||||
|
||||
QChar character;
|
||||
PDFReal advance = 0;
|
||||
};
|
||||
|
||||
struct TextSequence
|
||||
{
|
||||
static TextSequence fromString(const QString& string);
|
||||
|
||||
std::vector<TextSequenceItem> items;
|
||||
};
|
||||
|
||||
enum class ProcessOrder
|
||||
{
|
||||
BeforeOperation,
|
||||
@@ -593,10 +571,10 @@ private:
|
||||
void drawText(const TextSequence& textSequence);
|
||||
|
||||
/// Returns realized font
|
||||
const QRawFont& getRealizedFont() { return m_realizedFont.get(this, &PDFPageContentProcessor::getRealizedFontImpl); }
|
||||
const PDFRealizedFontPointer& getRealizedFont() { return m_realizedFont.get(this, &PDFPageContentProcessor::getRealizedFontImpl); }
|
||||
|
||||
/// Returns realized font (or empty font, if font can't be realized)
|
||||
QRawFont getRealizedFontImpl() const;
|
||||
PDFRealizedFontPointer getRealizedFontImpl() const;
|
||||
|
||||
const PDFPage* m_page;
|
||||
const PDFDocument* m_document;
|
||||
@@ -627,7 +605,7 @@ private:
|
||||
int m_textBeginEndState;
|
||||
|
||||
/// Actually realized physical font
|
||||
PDFCachedItem<QRawFont> m_realizedFont;
|
||||
PDFCachedItem<PDFRealizedFontPointer> m_realizedFont;
|
||||
};
|
||||
|
||||
} // namespace pdf
|
||||
|
Reference in New Issue
Block a user