diff --git a/PdfForQtLib/sources/pdfexception.h b/PdfForQtLib/sources/pdfexception.h index 32d38f0..71fbd2a 100644 --- a/PdfForQtLib/sources/pdfexception.h +++ b/PdfForQtLib/sources/pdfexception.h @@ -42,6 +42,7 @@ private: enum RenderErrorType { Error, + Warning, NotImplemented }; @@ -74,6 +75,19 @@ private: PDFRenderError m_error; }; +/// Abstract class for reporting render errors +class PDFRenderErrorReporter +{ +public: + explicit PDFRenderErrorReporter() = default; + virtual ~PDFRenderErrorReporter() = default; + + /// Reports render errors + /// \param type Error type + /// \param message Error message + virtual void reportRenderError(RenderErrorType type, QString message) = 0; +}; + } // namespace pdf #endif // PDFEXCEPTION_H diff --git a/PdfForQtLib/sources/pdffont.cpp b/PdfForQtLib/sources/pdffont.cpp index f646a8f..5cf47d5 100644 --- a/PdfForQtLib/sources/pdffont.cpp +++ b/PdfForQtLib/sources/pdffont.cpp @@ -324,12 +324,16 @@ public: /// 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); + void fillTextSequence(const QByteArray& byteArray, TextSequence& textSequence, PDFRenderErrorReporter* reporter); + + static constexpr const PDFReal PIXEL_SIZE_MULTIPLIER = 100.0; private: friend class PDFRealizedFont; + static constexpr const PDFReal FONT_WIDTH_MULTIPLIER = 1.0 / 1000.0; static constexpr const PDFReal FORMAT_26_6_MULTIPLIER = 1 / 64.0; + static constexpr const PDFReal FONT_MULTIPLIER = FORMAT_26_6_MULTIPLIER / PIXEL_SIZE_MULTIPLIER; struct Glyph { @@ -405,7 +409,7 @@ PDFRealizedFontImpl::~PDFRealizedFontImpl() } } -void PDFRealizedFontImpl::fillTextSequence(const QByteArray& byteArray, TextSequence& textSequence) +void PDFRealizedFontImpl::fillTextSequence(const QByteArray& byteArray, TextSequence& textSequence, PDFRenderErrorReporter* reporter) { switch (m_parentFont->getFontType()) { @@ -432,13 +436,21 @@ void PDFRealizedFontImpl::fillTextSequence(const QByteArray& byteArray, TextSequ } } - if (!glyphIndex) - { - throw PDFParserException(PDFTranslationContext::tr("Glyph for simple font character code '%1' not found.").arg(static_cast(byteArray[i]))); - } + const PDFReal glyphWidth = font->getGlyphWidth(static_cast(byteArray[i])); - const Glyph& glyph = getGlyph(glyphIndex); - textSequence.items.emplace_back(&glyph.glyph, (*encoding)[static_cast(byteArray[i])], glyph.advance); + if (glyphIndex) + { + const Glyph& glyph = getGlyph(glyphIndex); + textSequence.items.emplace_back(&glyph.glyph, (*encoding)[static_cast(byteArray[i])], glyph.advance); + } + else + { + reporter->reportRenderError(RenderErrorType::Warning, PDFTranslationContext::tr("Glyph for simple font character code '%1' not found.").arg(static_cast(byteArray[i]))); + if (glyphWidth > 0) + { + textSequence.items.emplace_back(nullptr, QChar(), glyphWidth * m_pixelSize * FONT_WIDTH_MULTIPLIER); + } + } } break; } @@ -482,28 +494,28 @@ void PDFRealizedFontImpl::fillTextSequence(const QByteArray& byteArray, TextSequ int PDFRealizedFontImpl::outlineMoveTo(const FT_Vector* to, void* user) { Glyph* glyph = reinterpret_cast(user); - glyph->glyph.moveTo(to->x * FORMAT_26_6_MULTIPLIER, to->y * FORMAT_26_6_MULTIPLIER); + glyph->glyph.moveTo(to->x * FONT_MULTIPLIER, to->y * FONT_MULTIPLIER); return 0; } int PDFRealizedFontImpl::outlineLineTo(const FT_Vector* to, void* user) { Glyph* glyph = reinterpret_cast(user); - glyph->glyph.lineTo(to->x * FORMAT_26_6_MULTIPLIER, to->y * FORMAT_26_6_MULTIPLIER); + glyph->glyph.lineTo(to->x * FONT_MULTIPLIER, to->y * FONT_MULTIPLIER); return 0; } int PDFRealizedFontImpl::outlineConicTo(const FT_Vector* control, const FT_Vector* to, void* user) { Glyph* glyph = reinterpret_cast(user); - glyph->glyph.quadTo(control->x * FORMAT_26_6_MULTIPLIER, control->y * FORMAT_26_6_MULTIPLIER, to->x * FORMAT_26_6_MULTIPLIER, to->y * FORMAT_26_6_MULTIPLIER); + glyph->glyph.quadTo(control->x * FONT_MULTIPLIER, control->y * FONT_MULTIPLIER, to->x * FONT_MULTIPLIER, to->y * FONT_MULTIPLIER); return 0; } int PDFRealizedFontImpl::outlineCubicTo(const FT_Vector* control1, const FT_Vector* control2, const FT_Vector* to, void* user) { Glyph* glyph = reinterpret_cast(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); + glyph->glyph.cubicTo(control1->x * FONT_MULTIPLIER, control1->y * FONT_MULTIPLIER, control2->x * FONT_MULTIPLIER, control2->y * FONT_MULTIPLIER, to->x * FONT_MULTIPLIER, to->y * FONT_MULTIPLIER); return 0; } @@ -534,7 +546,7 @@ const PDFRealizedFontImpl::Glyph& PDFRealizedFontImpl::getGlyph(unsigned int gly checkFreeTypeError(FT_Outline_Decompose(&m_face->glyph->outline, &glyphOutlineInterface, &glyph)); glyph.glyph.closeSubpath(); glyph.advance = !m_isVertical ? m_face->glyph->advance.x : m_face->glyph->advance.y; - glyph.advance *= FORMAT_26_6_MULTIPLIER; + glyph.advance *= FONT_MULTIPLIER; m_glyphCache[glyphIndex] = qMove(glyph); return m_glyphCache[glyphIndex]; @@ -563,9 +575,9 @@ PDFRealizedFont::~PDFRealizedFont() delete m_impl; } -void PDFRealizedFont::fillTextSequence(const QByteArray& byteArray, TextSequence& textSequence) +void PDFRealizedFont::fillTextSequence(const QByteArray& byteArray, TextSequence& textSequence, PDFRenderErrorReporter* reporter) { - m_impl->fillTextSequence(byteArray, textSequence); + m_impl->fillTextSequence(byteArray, textSequence, reporter); } bool PDFRealizedFont::isHorizontalWritingSystem() const @@ -580,6 +592,7 @@ PDFRealizedFontPointer PDFRealizedFont::createRealizedFont(PDFFontPointer font, PDFRealizedFontImpl* impl = implPtr.get(); impl->m_parentFont = font; + impl->m_pixelSize = pixelSize; const FontDescriptor* descriptor = font->getFontDescriptor(); if (descriptor->isEmbedded()) @@ -594,7 +607,7 @@ PDFRealizedFontPointer PDFRealizedFont::createRealizedFont(PDFFontPointer font, PDFRealizedFontImpl::checkFreeTypeError(FT_New_Memory_Face(impl->m_library, reinterpret_cast(impl->m_embeddedFontData.constData()), impl->m_embeddedFontData.size(), 0, &impl->m_face)); FT_Select_Charmap(impl->m_face, FT_ENCODING_UNICODE); // We try to select unicode encoding, but if it fails, we don't do anything (use glyph indices instead) - PDFRealizedFontImpl::checkFreeTypeError(FT_Set_Pixel_Sizes(impl->m_face, 0, qRound(pixelSize))); + PDFRealizedFontImpl::checkFreeTypeError(FT_Set_Pixel_Sizes(impl->m_face, 0, qRound(pixelSize * PDFRealizedFontImpl::PIXEL_SIZE_MULTIPLIER))); impl->m_isVertical = impl->m_face->face_flags & FT_FACE_FLAG_VERTICAL; impl->m_isEmbedded = true; result.reset(new PDFRealizedFont(implPtr.release())); @@ -620,7 +633,7 @@ PDFRealizedFontPointer PDFRealizedFont::createRealizedFont(PDFFontPointer font, PDFRealizedFontImpl::checkFreeTypeError(FT_Init_FreeType(&impl->m_library)); PDFRealizedFontImpl::checkFreeTypeError(FT_New_Memory_Face(impl->m_library, reinterpret_cast(impl->m_systemFontData.constData()), impl->m_systemFontData.size(), 0, &impl->m_face)); FT_Select_Charmap(impl->m_face, FT_ENCODING_UNICODE); // We try to select unicode encoding, but if it fails, we don't do anything (use glyph indices instead) - PDFRealizedFontImpl::checkFreeTypeError(FT_Set_Pixel_Sizes(impl->m_face, 0, qRound(pixelSize))); + PDFRealizedFontImpl::checkFreeTypeError(FT_Set_Pixel_Sizes(impl->m_face, 0, qRound(pixelSize * PDFRealizedFontImpl::PIXEL_SIZE_MULTIPLIER))); impl->m_isVertical = impl->m_face->face_flags & FT_FACE_FLAG_VERTICAL; impl->m_isEmbedded = false; result.reset(new PDFRealizedFont(implPtr.release())); @@ -815,13 +828,7 @@ PDFFontPointer PDFFont::createFont(const PDFObject& object, const PDFDocument* d throw PDFParserException(PDFTranslationContext::tr("Invalid differences in encoding entry of the font.")); } - QChar character = PDFNameToUnicode::getUnicodeForName(item.getString()); - - // Try ZapfDingbats, if this fails - if (character.isNull()) - { - character = PDFNameToUnicode::getUnicodeForNameZapfDingbats(item.getString()); - } + QChar character = PDFNameToUnicode::getUnicodeUsingResolvedName(item.getString()); differences[currentOffset] = character; ++currentOffset; @@ -1106,6 +1113,23 @@ PDFSimpleFont::PDFSimpleFont(FontDescriptor fontDescriptor, } +PDFInteger PDFSimpleFont::getGlyphWidth(size_t index) const +{ + const size_t min = m_firstChar; + const size_t max = m_lastChar; + + if (index >= min && index <= max) + { + const size_t adjustedIndex = index - min; + if (adjustedIndex < m_widths.size()) + { + return m_widths[adjustedIndex]; + } + } + + return 0; +} + PDFType1Font::PDFType1Font(FontDescriptor fontDescriptor, QByteArray name, QByteArray baseFont, diff --git a/PdfForQtLib/sources/pdffont.h b/PdfForQtLib/sources/pdffont.h index 7a84b97..4fdb99c 100644 --- a/PdfForQtLib/sources/pdffont.h +++ b/PdfForQtLib/sources/pdffont.h @@ -30,6 +30,7 @@ class QPainterPath; namespace pdf { class PDFDocument; +class PDFRenderErrorReporter; using CID = unsigned int; using GID = unsigned int; @@ -217,7 +218,8 @@ public: /// 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); + /// \param reporter Error reporter + void fillTextSequence(const QByteArray& byteArray, TextSequence& textSequence, PDFRenderErrorReporter* reporter); /// Return true, if we have horizontal writing system bool isHorizontalWritingSystem() const; @@ -280,6 +282,9 @@ public: const encoding::EncodingTable* getEncoding() const { return &m_encoding; } const GlyphIndices* getGlyphIndices() const { return &m_glyphIndices; } + /// Returns the glyph width (or zero, if glyph width is invalid) + PDFInteger getGlyphWidth(size_t index) const; + protected: QByteArray m_name; QByteArray m_baseFont; diff --git a/PdfForQtLib/sources/pdfnametounicode.cpp b/PdfForQtLib/sources/pdfnametounicode.cpp index 4d2b729..83cdb33 100644 --- a/PdfForQtLib/sources/pdfnametounicode.cpp +++ b/PdfForQtLib/sources/pdfnametounicode.cpp @@ -4540,4 +4540,27 @@ QChar PDFNameToUnicode::getUnicodeForNameZapfDingbats(const QByteArray& name) } } +QChar PDFNameToUnicode::getUnicodeUsingResolvedName(const QByteArray& name) +{ + QChar character = getUnicodeForName(name); + + // Try ZapfDingbats, if this fails + if (character.isNull()) + { + character = getUnicodeForNameZapfDingbats(name); + } + + if (character.isNull() && name.startsWith("uni")) + { + QByteArray hexValue = QByteArray::fromHex(name.mid(3, -1)); + if (hexValue.size() == 2) + { + unsigned short value = (static_cast(hexValue[0]) << 8) + static_cast(hexValue[1]); + character = QChar(value); + } + } + + return character; +} + } // namespace pdf diff --git a/PdfForQtLib/sources/pdfnametounicode.h b/PdfForQtLib/sources/pdfnametounicode.h index 1fc35f1..f9d3793 100644 --- a/PdfForQtLib/sources/pdfnametounicode.h +++ b/PdfForQtLib/sources/pdfnametounicode.h @@ -38,6 +38,9 @@ public: /// Returns unicode character for name (for ZapfDingbats). If name is not found, then null character is returned. static QChar getUnicodeForNameZapfDingbats(const QByteArray& name); + /// Tries to resolve unicode name + static QChar getUnicodeUsingResolvedName(const QByteArray& name); + private: struct Comparator { diff --git a/PdfForQtLib/sources/pdfpagecontentprocessor.cpp b/PdfForQtLib/sources/pdfpagecontentprocessor.cpp index 36ed05f..06e9ac2 100644 --- a/PdfForQtLib/sources/pdfpagecontentprocessor.cpp +++ b/PdfForQtLib/sources/pdfpagecontentprocessor.cpp @@ -257,6 +257,11 @@ QList PDFPageContentProcessor::processContents() return m_errorList; } +void PDFPageContentProcessor::reportRenderError(RenderErrorType type, QString message) +{ + m_errorList.append(PDFRenderError(type, qMove(message))); +} + void PDFPageContentProcessor::performPathPainting(const QPainterPath& path, bool stroke, bool fill, Qt::FillRule fillRule) { Q_UNUSED(path); @@ -1654,7 +1659,7 @@ void PDFPageContentProcessor::operatorTextShowTextString(PDFOperandString text) // We use simple heuristic to ensure reallocation doesn't occur too often textSequence.items.reserve(m_operands.size()); - realizedFont->fillTextSequence(text.string, textSequence); + realizedFont->fillTextSequence(text.string, textSequence, this); drawText(textSequence); } else @@ -1711,7 +1716,7 @@ void PDFPageContentProcessor::operatorTextShowTextIndividualSpacing() case PDFLexicalAnalyzer::TokenType::String: { - realizedFont->fillTextSequence(m_operands[i].data.toByteArray(), textSequence); + realizedFont->fillTextSequence(m_operands[i].data.toByteArray(), textSequence, this); break; } diff --git a/PdfForQtLib/sources/pdfpagecontentprocessor.h b/PdfForQtLib/sources/pdfpagecontentprocessor.h index 596ec52..0e8516f 100644 --- a/PdfForQtLib/sources/pdfpagecontentprocessor.h +++ b/PdfForQtLib/sources/pdfpagecontentprocessor.h @@ -37,7 +37,7 @@ namespace pdf static constexpr const char* PDF_RESOURCE_EXTGSTATE = "ExtGState"; /// Process the contents of the page. -class PDFPageContentProcessor +class PDFPageContentProcessor : public PDFRenderErrorReporter { public: explicit PDFPageContentProcessor(const PDFPage* page, const PDFDocument* document, const PDFFontCache* fontCache); @@ -155,6 +155,8 @@ public: /// Process the contents of the page QList processContents(); + virtual void reportRenderError(RenderErrorType type, QString message) override; + protected: class PDFLineDashPattern diff --git a/PdfForQtLib/sources/pdfrenderingerrorswidget.cpp b/PdfForQtLib/sources/pdfrenderingerrorswidget.cpp index 7f57f77..fc7d39e 100644 --- a/PdfForQtLib/sources/pdfrenderingerrorswidget.cpp +++ b/PdfForQtLib/sources/pdfrenderingerrorswidget.cpp @@ -56,6 +56,12 @@ PDFRenderingErrorsWidget::PDFRenderingErrorsWidget(QWidget* parent, PDFWidget* p break; } + case RenderErrorType::Warning: + { + typeString = tr("Warning"); + break; + } + case RenderErrorType::NotImplemented: { typeString = tr("Not implemented");