poppler/Annot.cc | 194 +++++++++++++++++++++++---- poppler/Annot.h | 7 poppler/Catalog.cc | 66 +++++---- poppler/Catalog.h | 2 poppler/Dict.cc | 10 + poppler/Dict.h | 7 poppler/Form.cc | 301 +++++++++++++++++++++++++++++++++++++++++- poppler/Form.h | 26 +++ poppler/GlobalParams.cc | 117 ++++++++++++++++ poppler/GlobalParams.h | 28 +++ poppler/GlobalParamsWin.cc | 47 ++++++ poppler/PDFDoc.cc | 2 qt5/src/poppler-annotation.cc | 68 ++++++--- qt6/src/poppler-annotation.cc | 69 ++++++--- 14 files changed, 842 insertions(+), 102 deletions(-)
New commits: commit 5f915d46c99ecbc0c026b86de50f9e0243391a01 Author: Albert Astals Cid <aa...@kde.org> Date: Tue Feb 22 16:01:19 2022 +0100 Annotations: Make sure we embed fonts for the FreeText annots diff --git a/poppler/Annot.cc b/poppler/Annot.cc index 366e7de1..8312cf97 100644 --- a/poppler/Annot.cc +++ b/poppler/Annot.cc @@ -1513,6 +1513,8 @@ void Annot::update(const char *key, Object &&value) annotObj.dictSet(const_cast<char *>(key), std::move(value)); doc->getXRef()->setModifiedObject(&annotObj, ref); + + hasBeenUpdated = true; } void Annot::setContents(std::unique_ptr<GooString> &&new_content) @@ -2094,6 +2096,7 @@ void Annot::setNewAppearance(Object &&newAppearance) Object obj1 = Object(new Dict(doc->getXRef())); obj1.dictAdd("N", Object(updatedAppearanceStream)); update("AP", std::move(obj1)); + update("AS", Object(objName, "N")); Object updatedAP = annotObj.dictLookup("AP"); appearStreams = std::make_unique<AnnotAppearance>(doc, &updatedAP); @@ -2831,13 +2834,12 @@ void AnnotLink::draw(Gfx *gfx, bool printing) //------------------------------------------------------------------------ const double AnnotFreeText::undefinedFontPtSize = 10.; -AnnotFreeText::AnnotFreeText(PDFDoc *docA, PDFRectangle *rectA, const DefaultAppearance &da) : AnnotMarkup(docA, rectA) +AnnotFreeText::AnnotFreeText(PDFDoc *docA, PDFRectangle *rectA) : AnnotMarkup(docA, rectA) { type = typeFreeText; - const std::string daStr = da.toAppearanceString(); annotObj.dictSet("Subtype", Object(objName, "FreeText")); - annotObj.dictSet("DA", Object(new GooString(daStr))); + annotObj.dictSet("DA", Object(new GooString())); initialize(docA, annotObj.getDict()); } @@ -3041,6 +3043,64 @@ static std::unique_ptr<GfxFont> createAnnotDrawFont(XRef *xref, Dict *fontParent return GfxFont::makeFont(xref, resourceName, dummyRef, fontDict); } +class HorizontalTextLayouter +{ +public: + void add(const std::string &text, const std::string &fontName, const double width) { data.emplace_back(text, fontName, width); } + + std::string layout(const VariableTextQuadding quadding, const double availableWidth, const double fontSize, double &xposPrev) const + { + double totalWidth = 0; + for (const Data &d : data) { + totalWidth += d.width; + } + + double newXpos; + switch (quadding) { + case VariableTextQuadding::centered: + newXpos = (availableWidth - totalWidth) / 2; + break; + case VariableTextQuadding::rightJustified: + newXpos = availableWidth - totalWidth; + break; + default: // VariableTextQuadding::lLeftJustified: + newXpos = 0; + break; + } + + AnnotAppearanceBuilder builder; + bool first = true; + double prevBlockWidth = 0; + for (const Data &d : data) { + builder.appendf("/{0:s} {1:.2f} Tf\n", d.fontName.c_str(), fontSize); + + const double yDiff = first ? -fontSize : 0; + const double xDiff = first ? newXpos - xposPrev : prevBlockWidth; + + builder.appendf("{0:.2f} {1:.2f} Td\n", xDiff, yDiff); + builder.writeString(d.text); + builder.append("Tj\n"); + first = false; + prevBlockWidth = d.width; + } + xposPrev = newXpos + totalWidth - prevBlockWidth; + + return builder.buffer()->toStr(); + } + +private: + struct Data + { + Data(const std::string &t, const std::string &fName, double w) : text(t), fontName(fName), width(w) { } + + const std::string text; + const std::string fontName; + const double width; + }; + + std::vector<Data> data; +}; + void AnnotFreeText::generateFreeTextAppearance() { double borderWidth, ca = opacity; @@ -3132,30 +3192,78 @@ void AnnotFreeText::generateFreeTextAppearance() // Set font state appearBuilder.setDrawColor(da.getFontColor(), true); appearBuilder.appendf("BT 1 0 0 1 {0:.2f} {1:.2f} Tm\n", textmargin, height - textmargin - da.getFontPtSize() * font->getDescent()); - appearBuilder.setTextFont(da.getFontName(), da.getFontPtSize()); int i = 0; double xposPrev = 0; + const bool isUnicode = contents->hasUnicodeMarker(); while (i < contents->getLength()) { + HorizontalTextLayouter textLayouter; + GooString out; - double linewidth, xpos; - layoutText(contents.get(), &out, &i, *font, &linewidth, textwidth / da.getFontPtSize(), nullptr, false); - linewidth *= da.getFontPtSize(); - switch (quadding) { - case VariableTextQuadding::centered: - xpos = (textwidth - linewidth) / 2; - break; - case VariableTextQuadding::rightJustified: - xpos = textwidth - linewidth; - break; - default: // VariableTextQuadding::leftJustified: - xpos = 0; - break; + double availableTextWidthInFontPtSize = textwidth / da.getFontPtSize(); + double blockWidth; + bool newFontNeeded; + layoutText(contents.get(), &out, &i, *font, &blockWidth, availableTextWidthInFontPtSize, nullptr, false, &newFontNeeded); + availableTextWidthInFontPtSize -= blockWidth; + textLayouter.add(out.toStr(), da.getFontName().getName(), blockWidth * da.getFontPtSize()); + + while (availableTextWidthInFontPtSize >= 0 && newFontNeeded) { + if (!form) { + // There's no fonts to look for, so just skip the characters + i += isUnicode ? 2 : 1; + error(errSyntaxError, -1, "AnnotFreeText::generateFreeTextAppearance, found character that the font can't represent"); + newFontNeeded = false; + } else { + Unicode uChar; + if (isUnicode) { + uChar = (unsigned char)(contents->getChar(i)) << 8; + uChar += (unsigned char)(contents->getChar(i + 1)); + } else { + uChar = pdfDocEncoding[contents->getChar(i) & 0xff]; + } + const std::string auxFontName = form->getFallbackFontForChar(uChar, *font); + if (!auxFontName.empty()) { + std::shared_ptr<GfxFont> auxFont = form->getDefaultResources()->lookupFont(auxFontName.c_str()); + + // Here we just layout one char, we don't know if the one afterwards can be layouted with the original font + GooString auxContents = GooString(contents->toStr().substr(i, isUnicode ? 2 : 1)); + if (isUnicode) { + auxContents.prependUnicodeMarker(); + } + int auxI = 0; + layoutText(&auxContents, &out, &auxI, *auxFont, &blockWidth, availableTextWidthInFontPtSize, nullptr, false, &newFontNeeded); + assert(!newFontNeeded); + // layoutText will always at least layout one character even if it doesn't fit in + // the given space which makes sense (except in the case of switching fonts, so we control if we ran out of space here manually) + availableTextWidthInFontPtSize -= blockWidth; + if (availableTextWidthInFontPtSize >= 0) { + i += auxI - (isUnicode ? 2 : 0); // the second term is the unicode marker + textLayouter.add(out.toStr(), auxFontName, blockWidth * da.getFontPtSize()); + } + + } else { + error(errSyntaxError, -1, "AnnotFreeText::generateFreeTextAppearance, couldn't find a font for character U+{0:04uX}", uChar); + newFontNeeded = false; + i += contents->hasUnicodeMarker() ? 2 : 1; + } + } + + // Now layout the rest of the text with the original font if we still have space + if (availableTextWidthInFontPtSize >= 0) { + layoutText(contents.get(), &out, &i, *font, &blockWidth, availableTextWidthInFontPtSize, nullptr, false, &newFontNeeded); + availableTextWidthInFontPtSize -= blockWidth; + // layoutText will always at least layout one character even if it doesn't fit in + // the given space which makes sense (except in the case of switching fonts, so we control if we ran out of space here manually) + if (availableTextWidthInFontPtSize >= 0) { + textLayouter.add(out.toStr(), da.getFontName().getName(), blockWidth * da.getFontPtSize()); + } else { + i -= isUnicode ? 2 : 1; + } + } } - appearBuilder.appendf("{0:.2f} {1:.2f} Td\n", xpos - xposPrev, -da.getFontPtSize()); - appearBuilder.writeString(out.toStr()); - appearBuilder.append("Tj\n"); - xposPrev = xpos; + + const std::string layoutedLine = textLayouter.layout(quadding, textwidth, da.getFontPtSize(), xposPrev); + appearBuilder.append(layoutedLine.c_str()); } appearBuilder.append("ET Q\n"); @@ -3165,14 +3273,23 @@ void AnnotFreeText::generateFreeTextAppearance() bbox[2] = rect->x2 - rect->x1; bbox[3] = rect->y2 - rect->y1; + Object newAppearance; if (ca == 1) { - appearance = createForm(appearBuilder.buffer(), bbox, false, std::move(resourceObj)); + newAppearance = createForm(appearBuilder.buffer(), bbox, false, std::move(resourceObj)); } else { Object aStream = createForm(appearBuilder.buffer(), bbox, true, std::move(resourceObj)); GooString appearBuf("/GS0 gs\n/Fm0 Do"); Dict *resDict = createResourcesDict("Fm0", std::move(aStream), "GS0", ca, nullptr); - appearance = createForm(&appearBuf, bbox, false, resDict); + newAppearance = createForm(&appearBuf, bbox, false, resDict); + } + if (hasBeenUpdated) { + // We should technically do this for all annots but AnnotFreeText + // is particularly special since we're potentially embeddeing a font so we really need + // to set the AP and not let other renderers guess it from the contents + setNewAppearance(std::move(newAppearance)); + } else { + appearance = std::move(newAppearance); } } @@ -4078,7 +4195,7 @@ bool AnnotWidget::setFormAdditionalAction(FormAdditionalActionsType formAddition // TODO: Handle surrogate pairs in UTF-16. // Should be able to generate output for any CID-keyed font. // Doesn't handle vertical fonts--should it? -void Annot::layoutText(const GooString *text, GooString *outBuf, int *i, const GfxFont &font, double *width, double widthLimit, int *charCount, bool noReencode) +void Annot::layoutText(const GooString *text, GooString *outBuf, int *i, const GfxFont &font, double *width, double widthLimit, int *charCount, bool noReencode, bool *newFontNeeded) { CharCode c; Unicode uChar; @@ -4087,6 +4204,9 @@ void Annot::layoutText(const GooString *text, GooString *outBuf, int *i, const G int uLen, n; double dx, dy, ox, oy; + if (newFontNeeded) { + *newFontNeeded = false; + } if (width != nullptr) { *width = 0.0; } @@ -4171,10 +4291,28 @@ void Annot::layoutText(const GooString *text, GooString *outBuf, int *i, const G outBuf->append(uChar & 0xff); } else if (ccToUnicode->mapToCharCode(&uChar, &c, 1)) { if (font.isCIDFont()) { - // TODO: This assumes an identity CMap. It should be extended to - // handle the general case. - outBuf->append((c >> 8) & 0xff); - outBuf->append(c & 0xff); + auto cidFont = static_cast<const GfxCIDFont *>(&font); + if (c < cidFont->getCIDToGIDLen()) { + const int glyph = cidFont->getCIDToGID()[c]; + if (glyph > 0 || c == 0) { + outBuf->append((c >> 8) & 0xff); + outBuf->append(c & 0xff); + } else { + if (newFontNeeded) { + *newFontNeeded = true; + *i -= unicode ? 2 : 1; + break; + } + outBuf->append((c >> 8) & 0xff); + outBuf->append(c & 0xff); + error(errSyntaxError, -1, "AnnotWidget::layoutText, font doesn't have glyph for charcode U+{0:04uX}", c); + } + } else { + // TODO: This assumes an identity CMap. It should be extended to + // handle the general case. + outBuf->append((c >> 8) & 0xff); + outBuf->append(c & 0xff); + } } else { // 8-bit font outBuf->append(c); diff --git a/poppler/Annot.h b/poppler/Annot.h index 19688712..29e8f6ea 100644 --- a/poppler/Annot.h +++ b/poppler/Annot.h @@ -772,7 +772,8 @@ public: // Check if point is inside the annot rectangle. bool inRect(double x, double y) const; - static void layoutText(const GooString *text, GooString *outBuf, int *i, const GfxFont &font, double *width, double widthLimit, int *charCount, bool noReencode); + // If newFontNeeded is not null, it will contain whether the given font has glyphs to represent the needed text + static void layoutText(const GooString *text, GooString *outBuf, int *i, const GfxFont &font, double *width, double widthLimit, int *charCount, bool noReencode, bool *newFontNeeded = nullptr); private: void readArrayNum(Object *pdfArray, int key, double *value); @@ -827,6 +828,8 @@ protected: bool hasRef; mutable std::recursive_mutex mutex; + + bool hasBeenUpdated = false; }; //------------------------------------------------------------------------ @@ -1055,7 +1058,7 @@ public: static const double undefinedFontPtSize; - AnnotFreeText(PDFDoc *docA, PDFRectangle *rect, const DefaultAppearance &da); + AnnotFreeText(PDFDoc *docA, PDFRectangle *rect); AnnotFreeText(PDFDoc *docA, Object &&dictObject, const Object *obj); ~AnnotFreeText() override; diff --git a/poppler/Catalog.cc b/poppler/Catalog.cc index 986577a7..add93530 100644 --- a/poppler/Catalog.cc +++ b/poppler/Catalog.cc @@ -103,7 +103,13 @@ Catalog::Catalog(PDFDoc *docA) return; } // get the AcroForm dictionary - acroForm = catDict.dictLookup("AcroForm"); + acroForm = catDict.getDict()->lookup("AcroForm"); + if (acroForm.isDict()) { + // We later assume the Fields Array exists, so check it does + if (!acroForm.dictLookup("Fields").isArray()) { + acroForm.setToNull(); + } + } // read base URI Object obj = catDict.getDict()->lookupEnsureEncryptedIfNeeded("URI"); @@ -1055,6 +1061,26 @@ Catalog::FormType Catalog::getFormType() return res; } +Form *Catalog::getCreateForm() +{ + catalogLocker(); + if (!form) { + if (!acroForm.isDict()) { + acroForm = Object(new Dict(xref)); + acroForm.dictSet("Fields", Object(new Array(xref))); + + const Ref newFormRef = xref->addIndirectObject(acroForm); + + Object catDict = xref->getCatalog(); + catDict.dictSet("AcroForm", Object(newFormRef)); + + xref->setModifiedObject(&catDict, { xref->getRootNum(), xref->getRootGen() }); + } + } + + return getForm(); +} + Form *Catalog::getForm() { catalogLocker(); @@ -1073,28 +1099,23 @@ void Catalog::addFormToAcroForm(const Ref formRef) { catalogLocker(); - Object catDict = xref->getCatalog(); - Ref acroFormRef; - acroForm = catDict.getDict()->lookup("AcroForm", &acroFormRef); - if (!acroForm.isDict()) { - // none there yet, need to create a new fields dict - Object newForm = Object(new Dict(xref)); - newForm.dictSet("SigFlags", Object(3)); + getCreateForm(); + } - Array *fieldArray = new Array(xref); - fieldArray->add(Object(formRef)); - newForm.dictSet("Fields", Object(fieldArray)); + // append to field array + Ref fieldRef; + Object fieldArray = acroForm.getDict()->lookup("Fields", &fieldRef); + fieldArray.getArray()->add(Object(formRef)); - Ref newRef = xref->addIndirectObject(newForm); - catDict.dictSet("AcroForm", Object(newRef)); - acroForm = catDict.getDict()->lookup("AcroForm"); - } else { - // append to field array - Ref fieldRef; - Object fieldArray = acroForm.getDict()->lookup("Fields", &fieldRef); - fieldArray.getArray()->add(Object(formRef)); - } + setAcroFormModified(); +} + +void Catalog::setAcroFormModified() +{ + Object catDict = xref->getCatalog(); + Ref acroFormRef; + catDict.getDict()->lookup("AcroForm", &acroFormRef); if (acroFormRef != Ref::INVALID()) { xref->setModifiedObject(&acroForm, acroFormRef); @@ -1108,9 +1129,6 @@ void Catalog::removeFormFromAcroForm(const Ref formRef) catalogLocker(); Object catDict = xref->getCatalog(); - Ref acroFormRef; - acroForm = catDict.getDict()->lookup("AcroForm", &acroFormRef); - if (acroForm.isDict()) { // remove from field array Ref fieldRef; @@ -1124,7 +1142,7 @@ void Catalog::removeFormFromAcroForm(const Ref formRef) } } - xref->setModifiedObject(&acroForm, acroFormRef); + setAcroFormModified(); } } diff --git a/poppler/Catalog.h b/poppler/Catalog.h index 70ca99ca..03c7314c 100644 --- a/poppler/Catalog.h +++ b/poppler/Catalog.h @@ -212,6 +212,7 @@ public: Object *getAcroForm() { return &acroForm; } void addFormToAcroForm(const Ref formRef); void removeFormFromAcroForm(const Ref formRef); + void setAcroFormModified(); OCGs *getOptContentConfig() { return optContent; } @@ -226,6 +227,7 @@ public: }; FormType getFormType(); + Form *getCreateForm(); Form *getForm(); ViewerPreferences *getViewerPreferences(); diff --git a/poppler/Dict.cc b/poppler/Dict.cc index 59936c9f..789f90ed 100644 --- a/poppler/Dict.cc +++ b/poppler/Dict.cc @@ -247,3 +247,13 @@ bool Dict::hasKey(const char *key) const { return find(key) != nullptr; } + +std::string Dict::findAvailableKey(const std::string &suggestedKey) +{ + int i = 0; + std::string res = suggestedKey; + while (find(res.c_str())) { + res = suggestedKey + std::to_string(i++); + } + return res; +} diff --git a/poppler/Dict.h b/poppler/Dict.h index 1639ab44..621b76a2 100644 --- a/poppler/Dict.h +++ b/poppler/Dict.h @@ -16,7 +16,7 @@ // Copyright (C) 2005 Kristian Høgsberg <k...@redhat.com> // Copyright (C) 2006 Krzysztof Kowalczyk <kkowalc...@gmail.com> // Copyright (C) 2007-2008 Julien Rebetez <juli...@svn.gnome.org> -// Copyright (C) 2010, 2017-2021 Albert Astals Cid <aa...@kde.org> +// Copyright (C) 2010, 2017-2022 Albert Astals Cid <aa...@kde.org> // Copyright (C) 2010 Paweł Wiejacha <pawel.wieja...@gmail.com> // Copyright (C) 2013 Thomas Freitag <thomas.frei...@alfa.de> // Copyright (C) 2017 Adrian Johnson <ajohn...@redneon.com> @@ -103,6 +103,11 @@ public: bool hasKey(const char *key) const; + // Returns a key name that is not in the dictionary + // It will be suggestedKey itself if available + // otherwise it will start adding 0, 1, 2, 3, etc. to suggestedKey until there's one available + std::string findAvailableKey(const std::string &suggestedKey); + private: friend class Object; // for incRef/decRef diff --git a/poppler/Form.cc b/poppler/Form.cc index 43ae67ba..165e7288 100644 --- a/poppler/Form.cc +++ b/poppler/Form.cc @@ -49,10 +49,13 @@ #include "goo/GooString.h" #include "Error.h" #include "ErrorCodes.h" +#include "CharCodeToUnicode.h" #include "Object.h" #include "Array.h" #include "Dict.h" #include "Gfx.h" +#include "GfxFont.h" +#include "GlobalParams.h" #include "Form.h" #include "PDFDoc.h" #include "DateInfo.h" @@ -68,6 +71,12 @@ #include "Lexer.h" #include "Parser.h" +#include "fofi/FoFiTrueType.h" +#include "fofi/FoFiIdentifier.h" + +#include <ft2build.h> +#include FT_FREETYPE_H + // return a newly allocated char* containing an UTF16BE string of size length char *pdfDocEncodingToUTF16(const std::string &orig, int *length) { @@ -2489,7 +2498,7 @@ void FormFieldSignature::print(int indent) // Form //------------------------------------------------------------------------ -Form::Form(PDFDoc *doc) +Form::Form(PDFDoc *docA) : doc(docA) { Object obj1; @@ -2645,6 +2654,296 @@ FormField *Form::createFieldFromDict(Object &&obj, PDFDoc *docA, const Ref aref, return field; } +static const std::string kOurDictFontNamePrefix = "popplerfont"; + +std::string Form::findFontInDefaultResources(const std::string &fontFamily, const std::string &fontStyle) +{ + if (!resDict.isDict()) { + return {}; + } + + const std::string fontFamilyAndStyle = fontFamily + " " + fontStyle; + + Object fontDictObj = resDict.dictLookup("Font"); + assert(fontDictObj.isDict()); + + const Dict *fontDict = fontDictObj.getDict(); + for (int i = 0; i < fontDict->getLength(); ++i) { + const char *key = fontDict->getKey(i); + if (GooString::startsWith(key, kOurDictFontNamePrefix)) { + const Object fontObj = fontDict->getVal(i); + if (fontObj.isDict() && fontObj.dictIs("Font")) { + const Object fontBaseFontObj = fontObj.dictLookup("BaseFont"); + if (fontBaseFontObj.isName(fontFamilyAndStyle.c_str())) { + return key; + } + } + } + } + + return {}; +} + +std::string Form::addFontToDefaultResources(const std::string &fontFamily, const std::string &fontStyle) +{ + const FamilyStyleFontSearchResult res = globalParams->findSystemFontFileForFamilyAndStyle(fontFamily, fontStyle); + + return addFontToDefaultResources(res.filepath, res.faceIndex, fontFamily, fontStyle); +} + +std::string Form::addFontToDefaultResources(const std::string &filepath, int faceIndex, const std::string &fontFamily, const std::string &fontStyle) +{ + if (!GooString::endsWith(filepath, ".ttf") && !GooString::endsWith(filepath, ".ttc") && !GooString::endsWith(filepath, ".otf")) { + error(errIO, -1, "We only support embedding ttf/ttc/otf fonts for now. The font file for %s %s was %s", fontFamily.c_str(), fontStyle.c_str(), filepath.c_str()); + return {}; + } + + const FoFiIdentifierType fontFoFiType = FoFiIdentifier::identifyFile(filepath.c_str()); + if (fontFoFiType != fofiIdTrueType && fontFoFiType != fofiIdTrueTypeCollection && fontFoFiType != fofiIdOpenTypeCFF8Bit && fontFoFiType != fofiIdOpenTypeCFFCID) { + error(errIO, -1, "We only support embedding ttf/ttc/otf fonts for now. The font file for %s %s was %s of type %d", fontFamily.c_str(), fontStyle.c_str(), filepath.c_str(), fontFoFiType); + return {}; + } + + const std::string fontFamilyAndStyle = fontFamily + " " + fontStyle; + + XRef *xref = doc->getXRef(); + Object fontDict(new Dict(xref)); + fontDict.dictSet("Type", Object(objName, "Font")); + fontDict.dictSet("Subtype", Object(objName, "Type0")); + fontDict.dictSet("BaseFont", Object(objName, fontFamilyAndStyle.c_str())); + + fontDict.dictSet("Encoding", Object(objName, "Identity-H")); + + { + std::unique_ptr<Array> descendantFonts = std::make_unique<Array>(xref); + + const bool isTrueType = (fontFoFiType == fofiIdTrueType || fontFoFiType == fofiIdTrueTypeCollection); + std::unique_ptr<Dict> descendantFont = std::make_unique<Dict>(xref); + descendantFont->set("Type", Object(objName, "Font")); + descendantFont->set("Subtype", Object(objName, isTrueType ? "CIDFontType2" : "CIDFontType0")); + descendantFont->set("BaseFont", Object(objName, fontFamilyAndStyle.c_str())); + + { + // We only support fonts with identity cmaps for now + Dict *cidSystemInfo = new Dict(xref); + cidSystemInfo->set("Registry", Object(new GooString("Adobe"))); + cidSystemInfo->set("Ordering", Object(new GooString("Identity"))); + cidSystemInfo->set("Supplement", Object(0)); + descendantFont->set("CIDSystemInfo", Object(cidSystemInfo)); + } + + FT_Library freetypeLib; + if (FT_Init_FreeType(&freetypeLib)) { + error(errIO, -1, "FT_Init_FreeType failed"); + return {}; + } + const std::unique_ptr<FT_Library, void (*)(FT_Library *)> freetypeLibDeleter(&freetypeLib, [](FT_Library *l) { FT_Done_FreeType(*l); }); + + FT_Face face; + if (FT_New_Face(freetypeLib, filepath.c_str(), faceIndex, &face)) { + error(errIO, -1, "FT_New_Face failed for %s", filepath.c_str()); + return {}; + } + const std::unique_ptr<FT_Face, void (*)(FT_Face *)> faceDeleter(&face, [](FT_Face *f) { FT_Done_Face(*f); }); + + if (FT_Set_Char_Size(face, 1000, 1000, 0, 0)) { + error(errIO, -1, "FT_Set_Char_Size failed for %s", filepath.c_str()); + return {}; + } + + { + std::unique_ptr<Dict> fontDescriptor = std::make_unique<Dict>(xref); + fontDescriptor->set("Type", Object(objName, "FontDescriptor")); + fontDescriptor->set("FontName", Object(objName, "Noto Sans")); + + // a bit arbirary but the Flags field is mandatory... + const std::string lowerCaseFontFamily = GooString::toLowerCase(fontFamily); + if (lowerCaseFontFamily.find("serif") != std::string::npos && lowerCaseFontFamily.find("sans") == std::string::npos) { + fontDescriptor->set("Flags", Object(2)); // Serif + } else { + fontDescriptor->set("Flags", Object(0)); // Sans Serif + } + + Array *fontBBox = new Array(xref); + fontBBox->add(Object(static_cast<int>(face->bbox.xMin))); + fontBBox->add(Object(static_cast<int>(face->bbox.yMin))); + fontBBox->add(Object(static_cast<int>(face->bbox.xMax))); + fontBBox->add(Object(static_cast<int>(face->bbox.yMax))); + fontDescriptor->set("FontBBox", Object(fontBBox)); + + fontDescriptor->set("Ascent", Object(static_cast<int>(face->ascender))); + + fontDescriptor->set("Descent", Object(static_cast<int>(face->descender))); + + { + const std::unique_ptr<GooFile> file(GooFile::open(filepath)); + if (!file) { + error(errIO, -1, "Failed to open %s", filepath.c_str()); + return {}; + } + const Goffset fileSize = file->size(); + if (fileSize < 0) { + error(errIO, -1, "Failed to get file size for %s", filepath.c_str()); + return {}; + } + char *dataPtr = static_cast<char *>(gmalloc(fileSize)); + const Goffset bytesRead = file->read(dataPtr, fileSize, 0); + if (bytesRead != fileSize) { + error(errIO, -1, "Failed to read contents of %s", filepath.c_str()); + gfree(dataPtr); + return {}; + } + + if (isTrueType) { + const Ref fontFile2Ref = xref->addStreamObject(new Dict(xref), dataPtr, fileSize); + fontDescriptor->set("FontFile2", Object(fontFile2Ref)); + } else { + Dict *fontFileStreamDict = new Dict(xref); + fontFileStreamDict->set("Subtype", Object(objName, "OpenType")); + const Ref fontFile3Ref = xref->addStreamObject(fontFileStreamDict, dataPtr, fileSize); + fontDescriptor->set("FontFile3", Object(fontFile3Ref)); + } + } + + const Ref fontDescriptorRef = xref->addIndirectObject(Object(fontDescriptor.release())); + descendantFont->set("FontDescriptor", Object(fontDescriptorRef)); + } + + static const int basicMultilingualMaxCode = 65535; + + const std::unique_ptr<FoFiTrueType> fft = FoFiTrueType::load(filepath.c_str()); + if (fft) { + + // Look for the Unicode BMP cmaps, which are 0/3 or 3/1 + int unicodeBMPCMap = fft->findCmap(0, 3); + if (unicodeBMPCMap < 0) { + unicodeBMPCMap = fft->findCmap(3, 1); + } + if (unicodeBMPCMap < 0) { + error(errIO, -1, "Font does not have an unicode BMP cmap %s", filepath.c_str()); + return {}; + } + + Array *widthsInner = new Array(xref); + for (int code = 0; code <= basicMultilingualMaxCode; ++code) { + const int glyph = fft->mapCodeToGID(unicodeBMPCMap, code); + if (FT_Load_Glyph(face, glyph, FT_LOAD_DEFAULT | FT_LOAD_NO_HINTING)) { + widthsInner->add(Object(0)); + } else { + widthsInner->add(Object(static_cast<int>(face->glyph->metrics.horiAdvance))); + } + } + Array *widths = new Array(xref); + widths->add(Object(0)); + widths->add(Object(widthsInner)); + descendantFont->set("W", Object(widths)); + + char *dataPtr = static_cast<char *>(gmalloc(2 * (basicMultilingualMaxCode + 1))); + int i = 0; + + for (int code = 0; code <= basicMultilingualMaxCode; ++code) { + const int glyph = fft->mapCodeToGID(unicodeBMPCMap, code); + dataPtr[i++] = (unsigned char)(glyph >> 8); + dataPtr[i++] = (unsigned char)(glyph & 0xff); + } + const Ref cidToGidMapStream = xref->addStreamObject(new Dict(xref), dataPtr, basicMultilingualMaxCode * 2); + descendantFont->set("CIDToGIDMap", Object(cidToGidMapStream)); + } + + descendantFonts->add(Object(descendantFont.release())); + + fontDict.dictSet("DescendantFonts", Object(descendantFonts.release())); + } + + const Ref fontDictRef = xref->addIndirectObject(fontDict); + + std::string dictFontName = kOurDictFontNamePrefix; + Object *acroForm = doc->getCatalog()->getAcroForm(); + if (resDict.isDict()) { + Ref fontDictObjRef; + Object fontDictObj = resDict.getDict()->lookup("Font", &fontDictObjRef); + assert(fontDictObj.isDict()); + dictFontName = fontDictObj.getDict()->findAvailableKey(dictFontName); + fontDictObj.dictSet(dictFontName.c_str(), Object(fontDictRef)); + + if (fontDictObjRef != Ref::INVALID()) { + xref->setModifiedObject(&fontDictObj, fontDictObjRef); + } else { + Ref resDictRef; + acroForm->getDict()->lookup("DR", &resDictRef); + if (resDictRef != Ref::INVALID()) { + xref->setModifiedObject(&resDict, resDictRef); + } else { + doc->getCatalog()->setAcroFormModified(); + } + } + + // maybe we can do something to reuse the existing data instead of recreating from scratch? + delete defaultResources; + defaultResources = new GfxResources(xref, resDict.getDict(), nullptr); + } else { + Dict *fontsDict = new Dict(xref); + fontsDict->set(dictFontName.c_str(), Object(fontDictRef)); + + Dict *defaultResourcesDict = new Dict(xref); + defaultResourcesDict->set("Font", Object(fontsDict)); + + assert(!defaultResources); + defaultResources = new GfxResources(xref, defaultResourcesDict, nullptr); + resDict = Object(defaultResourcesDict); + + acroForm->dictSet("DR", resDict.copy()); + doc->getCatalog()->setAcroFormModified(); + } + + return dictFontName; +} + +std::string Form::getFallbackFontForChar(Unicode uChar, const GfxFont &fontToEmulate) +{ + return doGetAddFontToDefaultResources(uChar, fontToEmulate, false /*addIfNotFound*/); +} + +void Form::ensureFontsForAllCharacters(const GooString *unicodeText, const std::string &pdfFontNameToEmulate) +{ + std::shared_ptr<GfxFont> f = defaultResources->lookupFont(pdfFontNameToEmulate.c_str()); + const CharCodeToUnicode *ccToUnicode = f->getToUnicode(); + if (!ccToUnicode) { + return; // will never happen with current code + } + if (!f->isCIDFont()) { + return; // will never happen with current code + } + + auto cidFont = static_cast<const GfxCIDFont *>(f.get()); + // If the text has some characters that are not available in the font, try adding a font for those + for (int i = 2; i < unicodeText->getLength(); i += 2) { + Unicode uChar = (unsigned char)(unicodeText->getChar(i)) << 8; + uChar += (unsigned char)(unicodeText->getChar(i + 1)); + + CharCode c; + ccToUnicode->mapToCharCode(&uChar, &c, 1); + + if (c < cidFont->getCIDToGIDLen() && c != 0 && c != '\r' && c != '\n') { + const int glyph = cidFont->getCIDToGID()[c]; + if (glyph == 0) { + doGetAddFontToDefaultResources(uChar, *f, true /*addIfNotFound*/); + } + } + } +} + +std::string Form::doGetAddFontToDefaultResources(Unicode uChar, const GfxFont &fontToEmulate, bool addIfNotFound) +{ + const UCharFontSearchResult res = globalParams->findSystemFontFileForUChar(uChar, fontToEmulate); + + std::string pdfFontName = findFontInDefaultResources(res.family, res.style); + if (addIfNotFound && pdfFontName.empty()) { + pdfFontName = addFontToDefaultResources(res.filepath, res.faceIndex, res.family, res.style); + } + return pdfFontName; +} + void Form::postWidgetsLoad() { // We create the widget annotations associated to diff --git a/poppler/Form.h b/poppler/Form.h index 43a59f95..98421c9a 100644 --- a/poppler/Form.h +++ b/poppler/Form.h @@ -32,8 +32,9 @@ #ifndef FORM_H #define FORM_H -#include "Object.h" #include "Annot.h" +#include "CharTypes.h" +#include "Object.h" #include "poppler_private_export.h" #include <ctime> @@ -676,6 +677,22 @@ public: Page::loadStandaloneFields */ static FormField *createFieldFromDict(Object &&obj, PDFDoc *docA, const Ref aref, FormField *parent, std::set<int> *usedParents); + // Finds in the default resources dictionary a font named popplerfontXXX that + // has the given fontFamily and fontStyle. This makes us relatively sure that we added that font ourselves + std::string findFontInDefaultResources(const std::string &fontFamily, const std::string &fontStyle); + + // Finds in the system a font name matching the given fontFamily and fontStyle + // And adds it to the default resources dictionary, font name there will be popplerfontXXX + std::string addFontToDefaultResources(const std::string &fontFamily, const std::string &fontStyle); + + // Finds in the default resources dictionary a font named popplerfontXXX that + // emulates fontToEmulate and can draw the given char + std::string getFallbackFontForChar(Unicode uChar, const GfxFont &fontToEmulate); + + // Makes sure the default resources has fonts to draw all the given chars and as close as possible to the given pdfFontNameToEmulate + // If needed adds fonts to the default resources dictionary, font names will be popplerfontXXX + void ensureFontsForAllCharacters(const GooString *unicodeText, const std::string &pdfFontNameToEmulate); + bool getNeedAppearances() const { return needAppearances; } int getNumFields() const { return numFields; } FormField *getRootField(int i) const { return rootFields[i]; } @@ -695,9 +712,16 @@ public: void reset(const std::vector<std::string> &fields, bool excludeFields); private: + // Finds in the system a font name matching the given fontFamily and fontStyle + // And adds it to the default resources dictionary, font name there will be popplerfontXXX + std::string addFontToDefaultResources(const std::string &filepath, int faceIndex, const std::string &fontFamily, const std::string &fontStyle); + + std::string doGetAddFontToDefaultResources(Unicode uChar, const GfxFont &fontToEmulate, bool addIfNotFound); + FormField **rootFields; int numFields; int size; + PDFDoc *const doc; bool needAppearances; GfxResources *defaultResources; Object resDict; diff --git a/poppler/GlobalParams.cc b/poppler/GlobalParams.cc index a95d940e..570b592b 100644 --- a/poppler/GlobalParams.cc +++ b/poppler/GlobalParams.cc @@ -90,6 +90,9 @@ #include "UnicodeMapTables.h" #include "UnicodeMapFuncs.h" +#include "fofi/FoFiTrueType.h" +#include "fofi/FoFiIdentifier.h" + //------------------------------------------------------------------------ #define cidToUnicodeCacheSize 4 @@ -251,6 +254,8 @@ public: SysFontList &operator=(const SysFontList &) = delete; const SysFontInfo *find(const std::string &name, bool isFixedWidth, bool exact); + const std::vector<SysFontInfo *> &getFonts() const { return fonts; } + #ifdef _WIN32 void scanWindowsFonts(GooString *winFontDir); #endif @@ -888,6 +893,39 @@ GooString *GlobalParams::findFontFile(const std::string &fontName) return path; } +static bool supportedFontForEmbedding(Unicode uChar, const char *filepath, int faceIndex) +{ + if (!GooString::endsWith(filepath, ".ttf") && !GooString::endsWith(filepath, ".ttc") && !GooString::endsWith(filepath, ".otf")) { + // for now we only support ttf, ttc, otf fonts + return false; + } + + const FoFiIdentifierType fontFoFiType = FoFiIdentifier::identifyFile(filepath); + if (fontFoFiType != fofiIdTrueType && fontFoFiType != fofiIdTrueTypeCollection && fontFoFiType != fofiIdOpenTypeCFF8Bit && fontFoFiType != fofiIdOpenTypeCFFCID) { + // for now we only support ttf, ttc, otf fonts + return false; + } + + const std::unique_ptr<FoFiTrueType> fft = FoFiTrueType::load(filepath, faceIndex); + if (!fft) { + error(errIO, -1, "Form::addFontToDefaultResources. Failed to FoFiTrueType::load %s", filepath); + return false; + } + + // Look for the Unicode BMP cmaps, which are 0/3 or 3/1 + int unicodeBMPCMap = fft->findCmap(0, 3); + if (unicodeBMPCMap < 0) { + unicodeBMPCMap = fft->findCmap(3, 1); + } + if (unicodeBMPCMap < 0) { + // for now we only support files with unicode bmp cmaps + return false; + } + + const int glyph = fft->mapCodeToGID(unicodeBMPCMap, uChar); + return glyph > 0; +} + /* if you can't or don't want to use Fontconfig, you need to implement this function for your platform. For Windows, it's in GlobalParamsWin.cc */ @@ -1067,6 +1105,71 @@ fin: return path; } +FamilyStyleFontSearchResult GlobalParams::findSystemFontFileForFamilyAndStyle(const std::string &fontFamily, const std::string &fontStyle) +{ + FcChar8 *fcFilePath = nullptr; + int faceIndex = 0; + FcPattern *p = FcPatternBuild(nullptr, FC_FAMILY, FcTypeString, fontFamily.c_str(), FC_STYLE, FcTypeString, fontStyle.c_str(), nullptr); + FcConfigSubstitute(nullptr, p, FcMatchPattern); + FcDefaultSubstitute(p); + if (p) { + FcResult res; + FcFontSet *set = FcFontSort(nullptr, p, FcFalse, nullptr, &res); + if (set) { + if (res == FcResultMatch && set->nfont > 0) { + FcPatternGetString(set->fonts[0], FC_FILE, 0, &fcFilePath); + FcPatternGetInteger(set->fonts[0], FC_INDEX, 0, &faceIndex); + } + FcFontSetDestroy(set); + } + FcPatternDestroy(p); + } + + if (!fcFilePath) { + error(errIO, -1, "Couldn't find font file for %s %s", fontFamily.c_str(), fontStyle.c_str()); + return {}; + } + + return FamilyStyleFontSearchResult(reinterpret_cast<char *>(fcFilePath), faceIndex); +} + +UCharFontSearchResult GlobalParams::findSystemFontFileForUChar(Unicode uChar, const GfxFont &fontToEmulate) +{ + FcPattern *pattern = buildFcPattern(&fontToEmulate, nullptr); + + FcConfigSubstitute(nullptr, pattern, FcMatchPattern); + FcDefaultSubstitute(pattern); + + FcResult result = FcResultMatch; + FcFontSet *fontSet = FcFontSort(nullptr, pattern, FcFalse, nullptr, &result); + FcPatternDestroy(pattern); + + if (fontSet) { + const std::unique_ptr<FcFontSet, void (*)(FcFontSet *)> fontSetDeleter(fontSet, [](FcFontSet *fSet) { FcFontSetDestroy(fSet); }); + for (int i = 0; i < fontSet->nfont; i++) { + FcChar8 *fcFilePath = nullptr; + int faceIndex = 0; + FcChar8 *fcFamily = nullptr; + FcChar8 *fcStyle = nullptr; + FcPatternGetString(fontSet->fonts[i], FC_FILE, 0, &fcFilePath); + FcPatternGetInteger(fontSet->fonts[i], FC_INDEX, 0, &faceIndex); + FcPatternGetString(fontSet->fonts[i], FC_FAMILY, 0, &fcFamily); + FcPatternGetString(fontSet->fonts[i], FC_STYLE, 0, &fcStyle); + if (!fcFilePath || !fcFamily || !fcStyle) { + continue; + } + + const char *filepath = reinterpret_cast<char *>(fcFilePath); + + if (supportedFontForEmbedding(uChar, filepath, faceIndex)) { + return UCharFontSearchResult(filepath, faceIndex, reinterpret_cast<char *>(fcFamily), reinterpret_cast<char *>(fcStyle)); + } + } + } + + return {}; +} + #elif defined(WITH_FONTCONFIGURATION_WIN32) # include "GlobalParamsWin.cc" @@ -1074,7 +1177,21 @@ GooString *GlobalParams::findBase14FontFile(const GooString *base14Name, const G { return findFontFile(base14Name->toStr()); } + #else + +FamilyStyleFontSearchResult GlobalParams::findSystemFontFileForFamilyAndStyle(const std::string &fontFamily, const std::string &fontStyle) +{ + error(errUnimplemented, -1, "GlobalParams::findSystemFontFileForFamilyAndStyle not implemented for this platform"); + return {}; +} + +UCharFontSearchResult GlobalParams::findSystemFontFileForUChar(Unicode uChar, const GfxFont &fontToEmulate) +{ + error(errUnimplemented, -1, "GlobalParams::findSystemFontFileForUChar not implemented for this platform"); + return {}; +} + GooString *GlobalParams::findBase14FontFile(const GooString *base14Name, const GfxFont *font) { return findFontFile(base14Name->toStr()); diff --git a/poppler/GlobalParams.h b/poppler/GlobalParams.h index 967268ca..af3e53dc 100644 --- a/poppler/GlobalParams.h +++ b/poppler/GlobalParams.h @@ -79,6 +79,32 @@ enum SysFontType //------------------------------------------------------------------------ +struct FamilyStyleFontSearchResult +{ + FamilyStyleFontSearchResult() = default; + + FamilyStyleFontSearchResult(const std::string &filepathA, int faceIndexA) : filepath(filepathA), faceIndex(faceIndexA) { } + + const std::string filepath; + const int faceIndex = 0; +}; + +//------------------------------------------------------------------------ + +struct UCharFontSearchResult +{ + UCharFontSearchResult() = default; + + UCharFontSearchResult(const std::string &filepathA, int faceIndexA, const std::string &familyA, const std::string &styleA) : filepath(filepathA), faceIndex(faceIndexA), family(familyA), style(styleA) { } + + const std::string filepath; + const int faceIndex = 0; + const std::string family; + const std::string style; +}; + +//------------------------------------------------------------------------ + class POPPLER_PRIVATE_EXPORT GlobalParams { public: @@ -111,6 +137,8 @@ public: GooString *findFontFile(const std::string &fontName); GooString *findBase14FontFile(const GooString *base14Name, const GfxFont *font); GooString *findSystemFontFile(const GfxFont *font, SysFontType *type, int *fontNum, GooString *substituteFontName = nullptr, const GooString *base14Name = nullptr); + FamilyStyleFontSearchResult findSystemFontFileForFamilyAndStyle(const std::string &fontFamily, const std::string &fontStyle); + UCharFontSearchResult findSystemFontFileForUChar(Unicode uChar, const GfxFont &fontToEmulate); std::string getTextEncodingName() const; bool getPrintCommands(); bool getProfileCommands(); diff --git a/poppler/GlobalParamsWin.cc b/poppler/GlobalParamsWin.cc index bd453862..63ee229b 100644 --- a/poppler/GlobalParamsWin.cc +++ b/poppler/GlobalParamsWin.cc @@ -535,3 +535,50 @@ GooString *GlobalParams::findSystemFontFile(const GfxFont *font, SysFontType *ty return path; } + +FamilyStyleFontSearchResult GlobalParams::findSystemFontFileForFamilyAndStyle(const std::string &fontFamily, const std::string &fontStyle) +{ + const std::scoped_lock locker(mutex); + setupBaseFonts(POPPLER_FONTSDIR); + + const std::string familyAndStyle = fontFamily + " " + fontStyle; + + const SysFontInfo *fi = sysFonts->find(familyAndStyle, false, false); + if (fi) { + return FamilyStyleFontSearchResult(fi->path->toStr(), fi->fontNum); + } + + return {}; +} + +UCharFontSearchResult GlobalParams::findSystemFontFileForUChar(Unicode uChar, const GfxFont &fontToEmulate) +{ + const std::scoped_lock locker(mutex); + setupBaseFonts(POPPLER_FONTSDIR); + + const std::vector<SysFontInfo *> &fonts = sysFonts->getFonts(); + for (SysFontInfo *f : fonts) { + // This is not super great given that it ignores fontToEmulate, but will do for now + if (supportedFontForEmbedding(uChar, f->path->c_str(), f->fontNum)) { + std::string style; + if (f->italic) { + style = "Italic"; + } + if (f->oblique) { + if (!style.empty()) { + style += " "; + } + style += "Oblique"; + } + if (f->bold) { + if (!style.empty()) { + style += " "; + } + style += "Bold"; + } + return UCharFontSearchResult(f->path->toStr(), f->fontNum, f->name->toStr(), style); + } + } + + return {}; +} diff --git a/poppler/PDFDoc.cc b/poppler/PDFDoc.cc index dede5d8c..2b0139a4 100644 --- a/poppler/PDFDoc.cc +++ b/poppler/PDFDoc.cc @@ -2142,6 +2142,8 @@ bool PDFDoc::sign(const char *saveFilename, const char *certNickname, const char const Ref ref = getXRef()->addIndirectObject(annotObj); catalog->addFormToAcroForm(ref); + // say that there a now signatures and that we should append only + catalog->getAcroForm()->dictSet("SigFlags", Object(3)); std::unique_ptr<::FormFieldSignature> field = std::make_unique<::FormFieldSignature>(this, Object(annotObj.getDict()), ref, nullptr, nullptr); field->setCustomAppearanceContent(signatureText); diff --git a/qt5/src/poppler-annotation.cc b/qt5/src/poppler-annotation.cc index cdf25da1..851f78a8 100644 --- a/qt5/src/poppler-annotation.cc +++ b/qt5/src/poppler-annotation.cc @@ -904,6 +904,25 @@ void AnnotationPrivate::removeAnnotationFromPage(::Page *pdfPage, const Annotati delete ann; } +class TextAnnotationPrivate : public AnnotationPrivate +{ +public: + TextAnnotationPrivate(); + Annotation *makeAlias() override; + Annot *createNativeAnnot(::Page *destPage, DocumentData *doc) override; + void setDefaultAppearanceToNative(); + std::unique_ptr<DefaultAppearance> getDefaultAppearanceFromNative() const; + + // data fields + TextAnnotation::TextType textType; + QString textIcon; + std::optional<QFont> textFont; + QColor textColor = Qt::black; + int inplaceAlign; // 0:left, 1:center, 2:right + QVector<QPointF> inplaceCallout; + TextAnnotation::InplaceIntent inplaceIntent; +}; + class Annotation::Style::Private : public QSharedData { public: @@ -1440,6 +1459,11 @@ void Annotation::setContents(const QString &contents) } d->pdfAnnot->setContents(std::unique_ptr<GooString>(QStringToUnicodeGooString(contents))); + + TextAnnotationPrivate *textAnnotD = dynamic_cast<TextAnnotationPrivate *>(d); + if (textAnnotD) { + textAnnotD->setDefaultAppearanceToNative(); + } } QString Annotation::uniqueName() const @@ -1644,7 +1668,10 @@ void Annotation::setBoundary(const QRectF &boundary) return; } - PDFRectangle rect = d->boundaryToPdfRectangle(boundary, flags()); + const PDFRectangle rect = d->boundaryToPdfRectangle(boundary, flags()); + if (rect == d->pdfAnnot->getRect()) { + return; + } d->pdfAnnot->setRect(&rect); } @@ -1905,24 +1932,6 @@ void Annotation::setAnnotationAppearance(const AnnotationAppearance &annotationA // END Annotation implementation /** TextAnnotation [Annotation] */ -class TextAnnotationPrivate : public AnnotationPrivate -{ -public: - TextAnnotationPrivate(); - Annotation *makeAlias() override; - Annot *createNativeAnnot(::Page *destPage, DocumentData *doc) override; - void setDefaultAppearanceToNative(); - std::unique_ptr<DefaultAppearance> getDefaultAppearanceFromNative() const; - - // data fields - TextAnnotation::TextType textType; - QString textIcon; - std::optional<QFont> textFont; - QColor textColor = Qt::black; - int inplaceAlign; // 0:left, 1:center, 2:right - QVector<QPointF> inplaceCallout; - TextAnnotation::InplaceIntent inplaceIntent; -}; TextAnnotationPrivate::TextAnnotationPrivate() : AnnotationPrivate(), textType(TextAnnotation::Linked), textIcon(QStringLiteral("Note")), inplaceAlign(0), inplaceIntent(TextAnnotation::Unknown) { } @@ -1949,8 +1958,7 @@ Annot *TextAnnotationPrivate::createNativeAnnot(::Page *destPage, DocumentData * if (pointSize < 0) { qWarning() << "TextAnnotationPrivate::createNativeAnnot: font pointSize < 0"; } - DefaultAppearance da { { objName, "Invalid_font" }, pointSize, convertQColor(textColor) }; - pdfAnnot = new AnnotFreeText { destPage->getDoc(), &rect, da }; + pdfAnnot = new AnnotFreeText { destPage->getDoc(), &rect }; } // Set properties @@ -1964,6 +1972,8 @@ Annot *TextAnnotationPrivate::createNativeAnnot(::Page *destPage, DocumentData * inplaceCallout.clear(); // Free up memory + setDefaultAppearanceToNative(); + return pdfAnnot; } @@ -1975,7 +1985,21 @@ void TextAnnotationPrivate::setDefaultAppearanceToNative() if (pointSize < 0) { qWarning() << "TextAnnotationPrivate::createNativeAnnot: font pointSize < 0"; } - DefaultAppearance da { { objName, "Invalid_font" }, pointSize, convertQColor(textColor) }; + std::string fontName = "Invalid_font"; + if (textFont) { + Form *form = pdfPage->getDoc()->getCatalog()->getCreateForm(); + fontName = form->findFontInDefaultResources(textFont->family().toStdString(), textFont->styleName().toStdString()); + if (fontName.empty()) { + fontName = form->addFontToDefaultResources(textFont->family().toStdString(), textFont->styleName().toStdString()); + } + + if (!fontName.empty()) { + form->ensureFontsForAllCharacters(pdfAnnot->getContents(), fontName); + } else { + fontName = "Invalid_font"; + } + } + DefaultAppearance da { { objName, fontName.c_str() }, pointSize, convertQColor(textColor) }; ftextann->setDefaultAppearance(da); } } diff --git a/qt6/src/poppler-annotation.cc b/qt6/src/poppler-annotation.cc index 5770be6f..4472f62f 100644 --- a/qt6/src/poppler-annotation.cc +++ b/qt6/src/poppler-annotation.cc @@ -824,6 +824,25 @@ void AnnotationPrivate::removeAnnotationFromPage(::Page *pdfPage, const Annotati delete ann; } +class TextAnnotationPrivate : public AnnotationPrivate +{ +public: + TextAnnotationPrivate(); + Annotation *makeAlias() override; + Annot *createNativeAnnot(::Page *destPage, DocumentData *doc) override; + void setDefaultAppearanceToNative(); + std::unique_ptr<DefaultAppearance> getDefaultAppearanceFromNative() const; + + // data fields + TextAnnotation::TextType textType; + QString textIcon; + std::optional<QFont> textFont; + QColor textColor = Qt::black; + TextAnnotation::InplaceAlignPosition inplaceAlign; + QVector<QPointF> inplaceCallout; + TextAnnotation::InplaceIntent inplaceIntent; +}; + class Annotation::Style::Private : public QSharedData { public: @@ -1078,6 +1097,11 @@ void Annotation::setContents(const QString &contents) } d->pdfAnnot->setContents(std::unique_ptr<GooString>(QStringToUnicodeGooString(contents))); + + TextAnnotationPrivate *textAnnotD = dynamic_cast<TextAnnotationPrivate *>(d); + if (textAnnotD) { + textAnnotD->setDefaultAppearanceToNative(); + } } QString Annotation::uniqueName() const @@ -1283,7 +1307,10 @@ void Annotation::setBoundary(const QRectF &boundary) return; } - PDFRectangle rect = d->boundaryToPdfRectangle(boundary, flags()); + const PDFRectangle rect = d->boundaryToPdfRectangle(boundary, flags()); + if (rect == d->pdfAnnot->getRect()) { + return; + } d->pdfAnnot->setRect(&rect); } @@ -1544,25 +1571,6 @@ void Annotation::setAnnotationAppearance(const AnnotationAppearance &annotationA // END Annotation implementation /** TextAnnotation [Annotation] */ -class TextAnnotationPrivate : public AnnotationPrivate -{ -public: - TextAnnotationPrivate(); - Annotation *makeAlias() override; - Annot *createNativeAnnot(::Page *destPage, DocumentData *doc) override; - void setDefaultAppearanceToNative(); - std::unique_ptr<DefaultAppearance> getDefaultAppearanceFromNative() const; - - // data fields - TextAnnotation::TextType textType; - QString textIcon; - std::optional<QFont> textFont; - QColor textColor = Qt::black; - TextAnnotation::InplaceAlignPosition inplaceAlign; - QVector<QPointF> inplaceCallout; - TextAnnotation::InplaceIntent inplaceIntent; -}; - TextAnnotationPrivate::TextAnnotationPrivate() : AnnotationPrivate(), textType(TextAnnotation::Linked), textIcon(QStringLiteral("Note")), inplaceAlign(TextAnnotation::InplaceAlignLeft), inplaceIntent(TextAnnotation::Unknown) { } Annotation *TextAnnotationPrivate::makeAlias() @@ -1588,8 +1596,7 @@ Annot *TextAnnotationPrivate::createNativeAnnot(::Page *destPage, DocumentData * if (pointSize < 0) { qWarning() << "TextAnnotationPrivate::createNativeAnnot: font pointSize < 0"; } - DefaultAppearance da { { objName, "Invalid_font" }, pointSize, convertQColor(textColor) }; - pdfAnnot = new AnnotFreeText { destPage->getDoc(), &rect, da }; + pdfAnnot = new AnnotFreeText { destPage->getDoc(), &rect }; } // Set properties @@ -1603,6 +1610,8 @@ Annot *TextAnnotationPrivate::createNativeAnnot(::Page *destPage, DocumentData * inplaceCallout.clear(); // Free up memory + setDefaultAppearanceToNative(); + return pdfAnnot; } @@ -1614,7 +1623,21 @@ void TextAnnotationPrivate::setDefaultAppearanceToNative() if (pointSize < 0) { qWarning() << "TextAnnotationPrivate::createNativeAnnot: font pointSize < 0"; } - DefaultAppearance da { { objName, "Invalid_font" }, pointSize, convertQColor(textColor) }; + std::string fontName = "Invalid_font"; + if (textFont) { + Form *form = pdfPage->getDoc()->getCatalog()->getCreateForm(); + fontName = form->findFontInDefaultResources(textFont->family().toStdString(), textFont->styleName().toStdString()); + if (fontName.empty()) { + fontName = form->addFontToDefaultResources(textFont->family().toStdString(), textFont->styleName().toStdString()); + } + + if (!fontName.empty()) { + form->ensureFontsForAllCharacters(pdfAnnot->getContents(), fontName); + } else { + fontName = "Invalid_font"; + } + } + DefaultAppearance da { { objName, fontName.c_str() }, pointSize, convertQColor(textColor) }; ftextann->setDefaultAppearance(da); } }