diff --git a/PdfForQtLib/sources/pdfpagecontentprocessor.cpp b/PdfForQtLib/sources/pdfpagecontentprocessor.cpp index 69ac380..d08f343 100644 --- a/PdfForQtLib/sources/pdfpagecontentprocessor.cpp +++ b/PdfForQtLib/sources/pdfpagecontentprocessor.cpp @@ -1659,6 +1659,12 @@ void PDFPageContentProcessor::operatorTextShowTextIndividualSpacing() break; } + case PDFLexicalAnalyzer::TokenType::Real: + { + textSequence.items.push_back(TextSequenceItem(m_operands[i].data.value())); + break; + } + case PDFLexicalAnalyzer::TokenType::String: { QString string = m_graphicState.getTextFont()->getTextUsingEncoding(m_operands[i].data.toByteArray()); @@ -1699,7 +1705,127 @@ void PDFPageContentProcessor::operatorTextSetSpacingAndShowText(PDFReal t_w, PDF void PDFPageContentProcessor::drawText(const TextSequence& textSequence) { + if (textSequence.items.empty()) + { + // Do not display empty text + return; + } + const QRawFont& font = getRealizedFont(); + if (font.isValid()) + { + std::vector chars; + chars.reserve(textSequence.items.size()); + for (const TextSequenceItem& item : textSequence.items) + { + if (item.isCharacter()) + { + chars.push_back(item.character); + } + } + + int numGlyphs = static_cast(chars.size()); + std::vector glyphIndices; + glyphIndices.resize(chars.size(), 0); + if (font.glyphIndexesForChars(chars.data(), static_cast(chars.size()), glyphIndices.data(), &numGlyphs)) + { + if (chars.size() != static_cast(numGlyphs)) + { + throw PDFRendererException(RenderErrorType::Error, PDFTranslationContext::tr("Cant convert unicode to glyph indices, text can't be printed.")); + } + + std::vector advances; + 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 + + // 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 + { + throw PDFRendererException(RenderErrorType::Error, PDFTranslationContext::tr("Cant convert unicode to glyph indices, text can't be printed.")); + } + } + else + { + throw PDFRendererException(RenderErrorType::Error, PDFTranslationContext::tr("Cant convert unicode to glyph indices, text can't be printed.")); + } + } + else + { + throw PDFRendererException(RenderErrorType::Error, PDFTranslationContext::tr("Invalid font, text can't be printed.")); + } } QRawFont PDFPageContentProcessor::getRealizedFontImpl() const diff --git a/PdfForQtLib/sources/pdfpagecontentprocessor.h b/PdfForQtLib/sources/pdfpagecontentprocessor.h index 931d12b..d1081b4 100644 --- a/PdfForQtLib/sources/pdfpagecontentprocessor.h +++ b/PdfForQtLib/sources/pdfpagecontentprocessor.h @@ -355,14 +355,14 @@ protected: { inline explicit TextSequenceItem() = default; inline explicit TextSequenceItem(QChar character) : character(character), advance(0) { } - inline explicit TextSequenceItem(PDFInteger advance) : 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; } + inline bool isAdvance() const { return advance != 0.0; } inline bool isNull() const { return !isCharacter() && !isAdvance(); } QChar character; - PDFInteger advance = 0; + PDFReal advance = 0; }; struct TextSequence