poppler/Annot.cc | 213 +++++++++++++++++++++++++----------------------------- poppler/Annot.h | 3 poppler/Form.cc | 25 ++++-- poppler/Form.h | 6 - poppler/PDFDoc.cc | 10 ++ 5 files changed, 131 insertions(+), 126 deletions(-)
New commits: commit db7865c6a38dd9d26d8b16ab2727fcec311efad8 Author: Albert Astals Cid <aa...@kde.org> Date: Wed Mar 23 13:35:01 2022 +0100 Signatures: Make sure we embed the needed fonts diff --git a/poppler/Annot.cc b/poppler/Annot.cc index 8312cf97..5959a993 100644 --- a/poppler/Annot.cc +++ b/poppler/Annot.cc @@ -3101,6 +3101,90 @@ private: std::vector<Data> data; }; +struct DrawFreeTextResult +{ + std::string text; + int nLines = 0; +}; + +static DrawFreeTextResult drawFreeTextText(const GooString &text, double availableWidth, const Form *form, const GfxFont &font, const std::string &fontName, double fontSize, VariableTextQuadding quadding) +{ + DrawFreeTextResult result; + int i = 0; + double xposPrev = 0; + const bool isUnicode = text.hasUnicodeMarker(); + while (i < text.getLength()) { + HorizontalTextLayouter textLayouter; + + GooString out; + double availableTextWidthInFontPtSize = availableWidth / fontSize; + double blockWidth; + bool newFontNeeded; + Annot::layoutText(&text, &out, &i, font, &blockWidth, availableTextWidthInFontPtSize, nullptr, false, &newFontNeeded); + availableTextWidthInFontPtSize -= blockWidth; + textLayouter.add(out.toStr(), fontName, blockWidth * fontSize); + + 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)(text.getChar(i)) << 8; + uChar += (unsigned char)(text.getChar(i + 1)); + } else { + uChar = pdfDocEncoding[text.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(text.toStr().substr(i, isUnicode ? 2 : 1)); + if (isUnicode) { + auxContents.prependUnicodeMarker(); + } + int auxI = 0; + Annot::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 * fontSize); + } + + } else { + error(errSyntaxError, -1, "AnnotFreeText::generateFreeTextAppearance, couldn't find a font for character U+{0:04uX}", uChar); + newFontNeeded = false; + i += text.hasUnicodeMarker() ? 2 : 1; + } + } + + // Now layout the rest of the text with the original font if we still have space + if (availableTextWidthInFontPtSize >= 0) { + Annot::layoutText(&text, &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(), fontName, blockWidth * fontSize); + } else { + i -= isUnicode ? 2 : 1; + } + } + } + + result.text += textLayouter.layout(quadding, availableWidth, fontSize, xposPrev); + result.nLines += 1; + } + return result; +} + void AnnotFreeText::generateFreeTextAppearance() { double borderWidth, ca = opacity; @@ -3192,80 +3276,8 @@ 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()); - - int i = 0; - double xposPrev = 0; - const bool isUnicode = contents->hasUnicodeMarker(); - while (i < contents->getLength()) { - HorizontalTextLayouter textLayouter; - - GooString out; - 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; - } - } - } - - const std::string layoutedLine = textLayouter.layout(quadding, textwidth, da.getFontPtSize(), xposPrev); - appearBuilder.append(layoutedLine.c_str()); - } - + const DrawFreeTextResult textCommands = drawFreeTextText(*contents, textwidth, form, *font, da.getFontName().getName(), da.getFontPtSize(), quadding); + appearBuilder.append(textCommands.text.c_str()); appearBuilder.append("ET Q\n"); double bbox[4]; @@ -5195,23 +5207,23 @@ bool AnnotAppearanceBuilder::drawSignatureFieldText(const FormFieldSignature *fi const GooString &leftText = field->getCustomAppearanceLeftContent(); if (leftText.toStr().empty()) { - drawSignatureFieldText(contents, DefaultAppearance(_da), border, rect, xref, resourcesDict, 0, false /* don't center vertically */, false /* don't center horizontally */); + drawSignatureFieldText(contents, form, DefaultAppearance(_da), border, rect, xref, resourcesDict, 0, false /* don't center vertically */, false /* don't center horizontally */); } else { DefaultAppearance daLeft(_da); daLeft.setFontPtSize(field->getCustomAppearanceLeftFontSize()); const double halfWidth = (rect->x2 - rect->x1) / 2; PDFRectangle rectLeft(rect->x1, rect->y1, rect->x1 + halfWidth, rect->y2); - drawSignatureFieldText(leftText, daLeft, border, &rectLeft, xref, resourcesDict, 0, true /* center vertically */, true /* center horizontally */); + drawSignatureFieldText(leftText, form, daLeft, border, &rectLeft, xref, resourcesDict, 0, true /* center vertically */, true /* center horizontally */); PDFRectangle rectRight(rectLeft.x2, rect->y1, rect->x2, rect->y2); - drawSignatureFieldText(contents, DefaultAppearance(_da), border, &rectRight, xref, resourcesDict, halfWidth, true /* center vertically */, false /* don't center horizontally */); + drawSignatureFieldText(contents, form, DefaultAppearance(_da), border, &rectRight, xref, resourcesDict, halfWidth, true /* center vertically */, false /* don't center horizontally */); } return true; } -void AnnotAppearanceBuilder::drawSignatureFieldText(const GooString &text, const DefaultAppearance &da, const AnnotBorder *border, const PDFRectangle *rect, XRef *xref, Dict *resourcesDict, double leftMargin, bool centerVertically, - bool centerHorizontally) +void AnnotAppearanceBuilder::drawSignatureFieldText(const GooString &text, const Form *form, const DefaultAppearance &da, const AnnotBorder *border, const PDFRectangle *rect, XRef *xref, Dict *resourcesDict, double leftMargin, + bool centerVertically, bool centerHorizontally) { double borderWidth = 0; append("q\n"); @@ -5230,52 +5242,25 @@ void AnnotAppearanceBuilder::drawSignatureFieldText(const GooString &text, const const double textwidth = width - 2 * textmargin; // create a Helvetica fake font - std::unique_ptr<const GfxFont> font = createAnnotDrawFont(xref, resourcesDict, da.getFontName().getName()); - - // calculate the string tokenization - int i = 0; - std::vector<std::pair<std::string, double>> outTexts; - while (i < text.getLength()) { - GooString out; - double textWidth; - Annot::layoutText(&text, &out, &i, *font, &textWidth, textwidth / da.getFontPtSize(), nullptr, false); - outTexts.emplace_back(out.toStr(), textWidth * da.getFontPtSize()); + std::shared_ptr<const GfxFont> font = form->getDefaultResources()->lookupFont(da.getFontName().getName()); + if (!font) { + font = createAnnotDrawFont(xref, resourcesDict, da.getFontName().getName()); } // Setup text clipping appendf("{0:.2f} {1:.2f} {2:.2f} {3:.2f} re W n\n", leftMargin + textmargin, textmargin, textwidth, height - 2 * textmargin); - - // Set font state setDrawColor(da.getFontColor(), true); - appendf("BT 1 0 0 1 {0:.2f} {1:.2f} Tm\n", textmargin, height - textmargin - da.getFontPtSize() * font->getDescent()); - setTextFont(da.getFontName(), da.getFontPtSize()); + const DrawFreeTextResult textCommands = drawFreeTextText(text, textwidth, form, *font, da.getFontName().getName(), da.getFontPtSize(), centerHorizontally ? VariableTextQuadding::centered : VariableTextQuadding::leftJustified); - double xDelta = centerHorizontally ? 0 : leftMargin; - double currentX = 0; - double yDelta = -da.getFontPtSize(); + double yDelta = height - textmargin - da.getFontPtSize() * font->getDescent(); if (centerVertically) { - const double outTextHeight = outTexts.size() * da.getFontPtSize(); + const double outTextHeight = textCommands.nLines * da.getFontPtSize(); if (outTextHeight < height) { yDelta -= (height - outTextHeight) / 2; } } - for (const std::pair<std::string, double> &outText : outTexts) { - if (centerHorizontally) { - const double lineX = (width - outText.second) / 2; - xDelta = (lineX - currentX); - currentX += xDelta; - } - - appendf("{0:.2f} {1:.2f} Td\n", xDelta, yDelta); - writeString(outText.first); - append("Tj\n"); - - if (!centerHorizontally) { - xDelta = 0; - } - yDelta = -da.getFontPtSize(); - } - + appendf("BT 1 0 0 1 {0:.2f} {1:.2f} Tm\n", leftMargin + textmargin, yDelta); + append(textCommands.text.c_str()); append("ET Q\n"); } diff --git a/poppler/Annot.h b/poppler/Annot.h index 29e8f6ea..f9c7816c 100644 --- a/poppler/Annot.h +++ b/poppler/Annot.h @@ -623,7 +623,8 @@ private: XRef *xref, Dict *resourcesDict); bool drawSignatureFieldText(const FormFieldSignature *field, const Form *form, const GfxResources *resources, const GooString *da, const AnnotBorder *border, const AnnotAppearanceCharacs *appearCharacs, const PDFRectangle *rect, XRef *xref, Dict *resourcesDict); - void drawSignatureFieldText(const GooString &text, const DefaultAppearance &da, const AnnotBorder *border, const PDFRectangle *rect, XRef *xref, Dict *resourcesDict, double leftMargin, bool centerVertically, bool centerHorizontally); + void drawSignatureFieldText(const GooString &text, const Form *form, const DefaultAppearance &da, const AnnotBorder *border, const PDFRectangle *rect, XRef *xref, Dict *resourcesDict, double leftMargin, bool centerVertically, + bool centerHorizontally); bool drawText(const GooString *text, const GooString *da, const GfxResources *resources, const AnnotBorder *border, const AnnotAppearanceCharacs *appearCharacs, const PDFRectangle *rect, const VariableTextQuadding quadding, XRef *xref, Dict *resourcesDict, const int flags = NoDrawTextFlags, const int nCombs = 0); void drawArrowPath(double x, double y, const Matrix &m, int orientation = 1); diff --git a/poppler/Form.cc b/poppler/Form.cc index 165e7288..41f823dc 100644 --- a/poppler/Form.cc +++ b/poppler/Form.cc @@ -693,7 +693,13 @@ bool FormWidgetSignature::signDocumentWithAppearance(const char *saveFilename, c GooString *aux = getField()->getDefaultAppearance(); std::string originalDefaultAppearance = aux ? aux->toStr() : std::string(); - const DefaultAppearance da { { objName, "SigFont" }, fontSize, std::move(fontColor) }; + Form *form = doc->getCatalog()->getForm(); + std::string pdfFontName = form->findFontInDefaultResources("Helvetica", ""); + if (pdfFontName.empty()) { + pdfFontName = form->addFontToDefaultResources("Helvetica", ""); + } + + const DefaultAppearance da { { objName, pdfFontName.c_str() }, fontSize, std::move(fontColor) }; getField()->setDefaultAppearance(da.toAppearanceString()); std::unique_ptr<AnnotAppearanceCharacs> origAppearCharacs = getWidgetAnnotation()->getAppearCharacs() ? getWidgetAnnotation()->getAppearCharacs()->copy() : nullptr; @@ -710,6 +716,9 @@ bool FormWidgetSignature::signDocumentWithAppearance(const char *saveFilename, c getWidgetAnnotation()->generateFieldAppearance(); getWidgetAnnotation()->updateAppearanceStream(); + form->ensureFontsForAllCharacters(&signatureText, pdfFontName); + form->ensureFontsForAllCharacters(&signatureTextLeft, pdfFontName); + ::FormFieldSignature *ffs = static_cast<::FormFieldSignature *>(getField()); ffs->setCustomAppearanceContent(signatureText); ffs->setCustomAppearanceLeftContent(signatureTextLeft); @@ -2656,7 +2665,7 @@ FormField *Form::createFieldFromDict(Object &&obj, PDFDoc *docA, const Ref aref, static const std::string kOurDictFontNamePrefix = "popplerfont"; -std::string Form::findFontInDefaultResources(const std::string &fontFamily, const std::string &fontStyle) +std::string Form::findFontInDefaultResources(const std::string &fontFamily, const std::string &fontStyle) const { if (!resDict.isDict()) { return {}; @@ -2899,9 +2908,11 @@ std::string Form::addFontToDefaultResources(const std::string &filepath, int fac return dictFontName; } -std::string Form::getFallbackFontForChar(Unicode uChar, const GfxFont &fontToEmulate) +std::string Form::getFallbackFontForChar(Unicode uChar, const GfxFont &fontToEmulate) const { - return doGetAddFontToDefaultResources(uChar, fontToEmulate, false /*addIfNotFound*/); + const UCharFontSearchResult res = globalParams->findSystemFontFileForUChar(uChar, fontToEmulate); + + return findFontInDefaultResources(res.family, res.style); } void Form::ensureFontsForAllCharacters(const GooString *unicodeText, const std::string &pdfFontNameToEmulate) @@ -2927,18 +2938,18 @@ void Form::ensureFontsForAllCharacters(const GooString *unicodeText, const std:: if (c < cidFont->getCIDToGIDLen() && c != 0 && c != '\r' && c != '\n') { const int glyph = cidFont->getCIDToGID()[c]; if (glyph == 0) { - doGetAddFontToDefaultResources(uChar, *f, true /*addIfNotFound*/); + doGetAddFontToDefaultResources(uChar, *f); } } } } -std::string Form::doGetAddFontToDefaultResources(Unicode uChar, const GfxFont &fontToEmulate, bool addIfNotFound) +std::string Form::doGetAddFontToDefaultResources(Unicode uChar, const GfxFont &fontToEmulate) { const UCharFontSearchResult res = globalParams->findSystemFontFileForUChar(uChar, fontToEmulate); std::string pdfFontName = findFontInDefaultResources(res.family, res.style); - if (addIfNotFound && pdfFontName.empty()) { + if (pdfFontName.empty()) { pdfFontName = addFontToDefaultResources(res.filepath, res.faceIndex, res.family, res.style); } return pdfFontName; diff --git a/poppler/Form.h b/poppler/Form.h index 98421c9a..903482a0 100644 --- a/poppler/Form.h +++ b/poppler/Form.h @@ -679,7 +679,7 @@ public: // 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); + std::string findFontInDefaultResources(const std::string &fontFamily, const std::string &fontStyle) const; // 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 @@ -687,7 +687,7 @@ public: // 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); + std::string getFallbackFontForChar(Unicode uChar, const GfxFont &fontToEmulate) const; // 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 @@ -716,7 +716,7 @@ private: // 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); + std::string doGetAddFontToDefaultResources(Unicode uChar, const GfxFont &fontToEmulate); FormField **rootFields; int numFields; diff --git a/poppler/PDFDoc.cc b/poppler/PDFDoc.cc index 2b0139a4..25d0b56a 100644 --- a/poppler/PDFDoc.cc +++ b/poppler/PDFDoc.cc @@ -2123,7 +2123,13 @@ bool PDFDoc::sign(const char *saveFilename, const char *certNickname, const char } } - const DefaultAppearance da { { objName, "SigFont" }, fontSize, std::move(fontColor) }; + Form *form = catalog->getCreateForm(); + std::string pdfFontName = form->findFontInDefaultResources("Helvetica", ""); + if (pdfFontName.empty()) { + pdfFontName = form->addFontToDefaultResources("Helvetica", ""); + } + + const DefaultAppearance da { { objName, pdfFontName.c_str() }, fontSize, std::move(fontColor) }; Object annotObj = Object(new Dict(getXRef())); annotObj.dictSet("Type", Object(objName, "Annot")); @@ -2144,6 +2150,8 @@ bool PDFDoc::sign(const char *saveFilename, const char *certNickname, const char catalog->addFormToAcroForm(ref); // say that there a now signatures and that we should append only catalog->getAcroForm()->dictSet("SigFlags", Object(3)); + form->ensureFontsForAllCharacters(&signatureText, pdfFontName); + form->ensureFontsForAllCharacters(&signatureTextLeft, pdfFontName); std::unique_ptr<::FormFieldSignature> field = std::make_unique<::FormFieldSignature>(this, Object(annotObj.getDict()), ref, nullptr, nullptr); field->setCustomAppearanceContent(signatureText);