poppler/Annot.cc | 433 +++++++++++++++++++++++++++++++------------------------ poppler/Annot.h | 8 - poppler/Form.cc | 33 ++-- 3 files changed, 275 insertions(+), 199 deletions(-)
New commits: commit 0b474ffb990a8285a6d3c2bf157b32b2264fb140 Author: Albert Astals Cid <aa...@kde.org> Date: Wed Mar 23 13:35:01 2022 +0100 Forms: Make sure we embedd fonts as needed And a bit of refactoring :) diff --git a/poppler/Annot.cc b/poppler/Annot.cc index 5959a993..db84027f 100644 --- a/poppler/Annot.cc +++ b/poppler/Annot.cc @@ -3046,141 +3046,190 @@ static std::unique_ptr<GfxFont> createAnnotDrawFont(XRef *xref, Dict *fontParent class HorizontalTextLayouter { public: - void add(const std::string &text, const std::string &fontName, const double width) { data.emplace_back(text, fontName, width); } + HorizontalTextLayouter() = default; - std::string layout(const VariableTextQuadding quadding, const double availableWidth, const double fontSize, double &xposPrev) const + HorizontalTextLayouter(const GooString *text, const Form *form, const GfxFont *font, std::optional<double> availableWidth, const bool noReencode) { - double totalWidth = 0; - for (const Data &d : data) { - totalWidth += d.width; - } + int i = 0; + double blockWidth; + bool newFontNeeded = false; + GooString outputText; + const bool isUnicode = text->hasUnicodeMarker(); + int charCount; - 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; + Annot::layoutText(text, &outputText, &i, *font, &blockWidth, availableWidth ? *availableWidth : 0.0, &charCount, noReencode, !noReencode ? &newFontNeeded : nullptr); + data.emplace_back(outputText.toStr(), std::string(), blockWidth, charCount); + if (availableWidth) { + *availableWidth -= blockWidth; } - 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); + while (newFontNeeded && (!availableWidth || *availableWidth > 0)) { + if (!form) { + // There's no fonts to look for, so just skip the characters + i += isUnicode ? 2 : 1; + error(errSyntaxError, -1, "HorizontalTextLayouter, 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()); - const double yDiff = first ? -fontSize : 0; - const double xDiff = first ? newXpos - xposPrev : prevBlockWidth; + // 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, &outputText, &auxI, *auxFont, &blockWidth, availableWidth ? *availableWidth : 0.0, &charCount, false, &newFontNeeded); + assert(!newFontNeeded); + if (availableWidth) { + *availableWidth -= 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 (!availableWidth || *availableWidth > 0) { + i += isUnicode ? 2 : 1; + data.emplace_back(outputText.toStr(), auxFontName, blockWidth, charCount); + } + } else { + error(errSyntaxError, -1, "HorizontalTextLayouter, couldn't find a font for character U+{0:04uX}", uChar); + newFontNeeded = false; + i += isUnicode ? 2 : 1; + } + } + // Now layout the rest of the text with the original font + if (!availableWidth || *availableWidth > 0) { + Annot::layoutText(text, &outputText, &i, *font, &blockWidth, availableWidth ? *availableWidth : 0.0, &charCount, false, &newFontNeeded); + if (availableWidth) { + *availableWidth -= 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 (!availableWidth || *availableWidth > 0) { + data.emplace_back(outputText.toStr(), std::string(), blockWidth, charCount); + } else { + i -= isUnicode ? 2 : 1; + } + } + } + consumedText = i; + } - builder.appendf("{0:.2f} {1:.2f} Td\n", xDiff, yDiff); - builder.writeString(d.text); - builder.append("Tj\n"); - first = false; - prevBlockWidth = d.width; + HorizontalTextLayouter(const HorizontalTextLayouter &) = delete; + HorizontalTextLayouter &operator=(const HorizontalTextLayouter &) = delete; + + double totalWidth() const + { + double totalWidth = 0; + for (const Data &d : data) { + totalWidth += d.width; } - xposPrev = newXpos + totalWidth - prevBlockWidth; + return totalWidth; + } - return builder.buffer()->toStr(); + int totalCharCount() const + { + int total = 0; + for (const Data &d : data) { + total += d.charCount; + } + return total; } -private: struct Data { - Data(const std::string &t, const std::string &fName, double w) : text(t), fontName(fName), width(w) { } + Data(const std::string &t, const std::string &fName, double w, int cc) : text(t), fontName(fName), width(w), charCount(cc) { } const std::string text; const std::string fontName; const double width; + const int charCount; }; std::vector<Data> data; + int consumedText; }; -struct DrawFreeTextResult +struct DrawMultiLineTextResult { 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) +// if fontName is empty it is assumed it is sent from the outside +// so for text that is in font no Tf is added and for text that is in the aux fonts +// a pair of q/Q is added +static DrawMultiLineTextResult drawMultiLineText(const GooString &text, double availableWidth, const Form *form, const GfxFont &font, const std::string &fontName, double fontSize, VariableTextQuadding quadding, double borderWidth) { - DrawFreeTextResult result; + DrawMultiLineTextResult result; int i = 0; - double xposPrev = 0; - const bool isUnicode = text.hasUnicodeMarker(); + double xPosPrev = 0; + const double availableTextWidthInFontPtSize = availableWidth / fontSize; 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); + GooString lineText(text.toStr().substr(i)); + if (!lineText.hasUnicodeMarker() && text.hasUnicodeMarker()) { + lineText.prependUnicodeMarker(); + } + const HorizontalTextLayouter textLayouter(&lineText, form, &font, availableTextWidthInFontPtSize, false); - 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()); + const double totalWidth = textLayouter.totalWidth() * fontSize; - // 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); - } + auto calculateX = [quadding, availableWidth, totalWidth, borderWidth] { + switch (quadding) { + case VariableTextQuadding::centered: + return (availableWidth - totalWidth) / 2; + break; + case VariableTextQuadding::rightJustified: + return availableWidth - totalWidth - borderWidth; + break; + default: // VariableTextQuadding::lLeftJustified: + return borderWidth; + break; + } + }; + const double xPos = calculateX(); - } else { - error(errSyntaxError, -1, "AnnotFreeText::generateFreeTextAppearance, couldn't find a font for character U+{0:04uX}", uChar); - newFontNeeded = false; - i += text.hasUnicodeMarker() ? 2 : 1; + AnnotAppearanceBuilder builder; + bool first = true; + double prevBlockWidth = 0; + for (const HorizontalTextLayouter::Data &d : textLayouter.data) { + const std::string &fName = d.fontName.empty() ? fontName : d.fontName; + if (!fName.empty()) { + if (fontName.empty()) { + builder.append(" q\n"); } + builder.appendf("/{0:s} {1:.2f} Tf\n", fName.c_str(), fontSize); } - // 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; - } + const double yDiff = first ? -fontSize : 0; + const double xDiff = first ? xPos - 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 * fontSize; + + if (!fName.empty() && fontName.empty()) { + builder.append(" Q\n"); } } + xPosPrev = xPos + totalWidth - prevBlockWidth; - result.text += textLayouter.layout(quadding, availableWidth, fontSize, xposPrev); + result.text += builder.buffer()->toStr(); result.nLines += 1; + if (i == 0) { + i += textLayouter.consumedText; + } else { + i += textLayouter.consumedText - (text.hasUnicodeMarker() ? 2 : 0); + } } return result; } @@ -3276,7 +3325,7 @@ 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()); - const DrawFreeTextResult textCommands = drawFreeTextText(*contents, textwidth, form, *font, da.getFontName().getName(), da.getFontPtSize(), quadding); + const DrawMultiLineTextResult textCommands = drawMultiLineText(*contents, textwidth, form, *font, da.getFontName().getName(), da.getFontPtSize(), quadding, 0 /*borderWidth*/); appearBuilder.append(textCommands.text.c_str()); appearBuilder.append("ET Q\n"); @@ -4330,7 +4379,13 @@ void Annot::layoutText(const GooString *text, GooString *outBuf, int *i, const G outBuf->append(c); } } else { - error(errSyntaxError, -1, "AnnotWidget::layoutText, cannot convert U+{0:04uX}", uChar); + if (newFontNeeded) { + *newFontNeeded = true; + *i -= unicode ? 2 : 1; + break; + } else { + error(errSyntaxError, -1, "AnnotWidget::layoutText, cannot convert U+{0:04uX}", uChar); + } } } @@ -4449,13 +4504,12 @@ void AnnotAppearanceBuilder::writeString(const std::string &str) } // Draw the variable text or caption for a field. -bool AnnotAppearanceBuilder::drawText(const GooString *text, const GooString *da, const GfxResources *resources, const AnnotBorder *border, const AnnotAppearanceCharacs *appearCharacs, const PDFRectangle *rect, +bool AnnotAppearanceBuilder::drawText(const GooString *text, const Form *form, 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, const int nCombs) { const bool forceZapfDingbats = flags & ForceZapfDingbatsDrawTextFlag; std::vector<std::string> daToks; - GooString convertedText; const GfxFont *font; double dx, dy; double fontSize; @@ -4575,15 +4629,26 @@ bool AnnotAppearanceBuilder::drawText(const GooString *text, const GooString *da // note: comb is ignored in multiline mode as mentioned in the spec const double wMax = dx - 2 * borderWidth - 4; + const bool isUnicode = text->hasUnicodeMarker(); // compute font autosize if (fontSize == 0) { for (fontSize = 20; fontSize > 1; --fontSize) { + const double availableWidthInFontSize = wMax / fontSize; double y = dy - 3; int i = 0; while (i < text->getLength()) { - Annot::layoutText(text, &convertedText, &i, *font, nullptr, wMax / fontSize, nullptr, forceZapfDingbats); + GooString lineText(text->toStr().substr(i)); + if (!lineText.hasUnicodeMarker() && isUnicode) { + lineText.prependUnicodeMarker(); + } + const HorizontalTextLayouter textLayouter(&lineText, form, font, availableWidthInFontSize, forceZapfDingbats); y -= fontSize; + if (i == 0) { + i += textLayouter.consumedText; + } else { + i += textLayouter.consumedText - (isUnicode ? 2 : 0); + } } // approximate the descender for the last line if (y >= 0.33 * fontSize) { @@ -4607,36 +4672,8 @@ bool AnnotAppearanceBuilder::drawText(const GooString *text, const GooString *da appearBuf->append(daTok)->append(' '); } - // write a series of lines of text - int i = 0; - double xPrev = 0; - while (i < text->getLength()) { - double w; - Annot::layoutText(text, &convertedText, &i, *font, &w, wMax / fontSize, nullptr, forceZapfDingbats); - w *= fontSize; - - // compute text start position - auto calculateX = [quadding, borderWidth, dx, w] { - switch (quadding) { - case VariableTextQuadding::leftJustified: - default: - return borderWidth + 2; - case VariableTextQuadding::centered: - return (dx - w) / 2; - case VariableTextQuadding::rightJustified: - return dx - borderWidth - 2 - w; - } - }; - const double x = calculateX(); - - // draw the line - appearBuf->appendf("{0:.2f} {1:.2f} Td\n", x - xPrev, -fontSize); - writeString(convertedText.toStr()); - appearBuf->append(" Tj\n"); - - // next line - xPrev = x; - } + const DrawMultiLineTextResult textCommands = drawMultiLineText(*text, dx, form, *font, std::string(), fontSize, quadding, borderWidth + 2); + appearBuf->append(textCommands.text); // single-line text } else { @@ -4644,8 +4681,6 @@ bool AnnotAppearanceBuilder::drawText(const GooString *text, const GooString *da // comb formatting if (nCombs > 0) { - int charCount; - // compute comb spacing const double w = (dx - 2 * borderWidth) / nCombs; @@ -4659,11 +4694,9 @@ bool AnnotAppearanceBuilder::drawText(const GooString *text, const GooString *da daToks[tfPos + 1] = GooString().appendf("{0:.2f}", fontSize)->toStr(); } - int dummy = 0; - Annot::layoutText(text, &convertedText, &dummy, *font, nullptr, 0.0, &charCount, forceZapfDingbats); - if (charCount > nCombs) { - charCount = nCombs; - } + const HorizontalTextLayouter textLayouter(text, form, font, {}, forceZapfDingbats); + + const int charCount = std::min(textLayouter.totalCharCount(), nCombs); // compute starting text cell auto calculateX = [quadding, borderWidth, nCombs, charCount, w] { @@ -4690,47 +4723,60 @@ bool AnnotAppearanceBuilder::drawText(const GooString *text, const GooString *da } // write the text string - const char *s = convertedText.c_str(); - int len = convertedText.getLength(); int i = 0; double xPrev = w; // so that first character is placed properly - while (i < nCombs && len > 0) { - CharCode code; - const Unicode *uAux; - int uLen, n; - double char_dx, char_dy, ox, oy; - - char_dx = 0.0; - n = font->getNextChar(s, len, &code, &uAux, &uLen, &char_dx, &char_dy, &ox, &oy); - char_dx *= fontSize; - - // center each character within its cell, by advancing the text - // position the appropriate amount relative to the start of the - // previous character - const double combX = 0.5 * (w - char_dx); - appearBuf->appendf("{0:.2f} 0 Td\n", combX - xPrev + w); - - GooString charBuf(s, n); - writeString(charBuf.toStr()); - appearBuf->append(" Tj\n"); + for (const HorizontalTextLayouter::Data &d : textLayouter.data) { + const char *s = d.text.c_str(); + int len = d.text.size(); + while (i < nCombs && len > 0) { + CharCode code; + const Unicode *uAux; + int uLen, n; + double char_dx, char_dy, ox, oy; + + const GfxFont *currentFont = font; + if (!d.fontName.empty()) { + appearBuf->append(" q\n"); + appearBuf->appendf("/{0:s} {1:.2f} Tf\n", d.fontName.c_str(), fontSize); + currentFont = form->getDefaultResources()->lookupFont(d.fontName.c_str()).get(); + } + + char_dx = 0.0; + n = currentFont->getNextChar(s, len, &code, &uAux, &uLen, &char_dx, &char_dy, &ox, &oy); + char_dx *= fontSize; + + // center each character within its cell, by advancing the text + // position the appropriate amount relative to the start of the + // previous character + const double combX = 0.5 * (w - char_dx); + appearBuf->appendf("{0:.2f} 0 Td\n", combX - xPrev + w); + + GooString charBuf(s, n); + writeString(charBuf.toStr()); + appearBuf->append(" Tj\n"); + + if (!d.fontName.empty()) { + appearBuf->append(" Q\n"); + } - i++; - s += n; - len -= n; - xPrev = combX; + i++; + s += n; + len -= n; + xPrev = combX; + } } // regular (non-comb) formatting } else { - int ii = 0; - double w; - Annot::layoutText(text, &convertedText, &ii, *font, &w, 0.0, nullptr, forceZapfDingbats); + const HorizontalTextLayouter textLayouter(text, form, font, {}, forceZapfDingbats); + + const double usedWidthUnscaled = textLayouter.totalWidth(); // compute font autosize if (fontSize == 0) { fontSize = dy - 2 * borderWidth; - if (w > 0) { - const double fontSize2 = (dx - 4 - 2 * borderWidth) / w; + if (usedWidthUnscaled > 0) { + const double fontSize2 = (dx - 4 - 2 * borderWidth) / usedWidthUnscaled; if (fontSize2 < fontSize) { fontSize = fontSize2; } @@ -4740,16 +4786,16 @@ bool AnnotAppearanceBuilder::drawText(const GooString *text, const GooString *da } // compute text start position - w *= fontSize; - auto calculateX = [quadding, borderWidth, dx, w] { + const double usedWidth = usedWidthUnscaled * fontSize; + auto calculateX = [quadding, borderWidth, dx, usedWidth] { switch (quadding) { case VariableTextQuadding::leftJustified: default: return borderWidth + 2; case VariableTextQuadding::centered: - return (dx - w) / 2; + return (dx - usedWidth) / 2; case VariableTextQuadding::rightJustified: - return dx - borderWidth - 2 - w; + return dx - borderWidth - 2 - usedWidth; } }; const double x = calculateX(); @@ -4767,9 +4813,18 @@ bool AnnotAppearanceBuilder::drawText(const GooString *text, const GooString *da // and our auto tests "wrongly" assume it will be there, so add it anyway appearBuf->append("\n"); - // write the text string - writeString(convertedText.toStr()); - appearBuf->append(" Tj\n"); + // write the text strings + for (const HorizontalTextLayouter::Data &d : textLayouter.data) { + if (!d.fontName.empty()) { + appearBuf->append(" q\n"); + appearBuf->appendf("/{0:s} {1:.2f} Tf\n", d.fontName.c_str(), fontSize); + } + writeString(d.text); + appearBuf->append(" Tj\n"); + if (!d.fontName.empty()) { + appearBuf->append(" Q\n"); + } + } } } // cleanup @@ -5080,7 +5135,7 @@ bool AnnotAppearanceBuilder::drawFormField(const FormField *field, const Form *f // draw the field contents switch (field->getType()) { case formButton: - return drawFormFieldButton(static_cast<const FormFieldButton *>(field), resources, da, border, appearCharacs, rect, appearState, xref, resourcesDict); + return drawFormFieldButton(static_cast<const FormFieldButton *>(field), form, resources, da, border, appearCharacs, rect, appearState, xref, resourcesDict); break; case formText: return drawFormFieldText(static_cast<const FormFieldText *>(field), form, resources, da, border, appearCharacs, rect, xref, resourcesDict); @@ -5098,8 +5153,8 @@ bool AnnotAppearanceBuilder::drawFormField(const FormField *field, const Form *f return false; } -bool AnnotAppearanceBuilder::drawFormFieldButton(const FormFieldButton *field, const GfxResources *resources, const GooString *da, const AnnotBorder *border, const AnnotAppearanceCharacs *appearCharacs, const PDFRectangle *rect, - const GooString *appearState, XRef *xref, Dict *resourcesDict) +bool AnnotAppearanceBuilder::drawFormFieldButton(const FormFieldButton *field, const Form *form, const GfxResources *resources, const GooString *da, const AnnotBorder *border, const AnnotAppearanceCharacs *appearCharacs, + const PDFRectangle *rect, const GooString *appearState, XRef *xref, Dict *resourcesDict) { const GooString *caption = nullptr; if (appearCharacs) { @@ -5111,7 +5166,7 @@ bool AnnotAppearanceBuilder::drawFormFieldButton(const FormFieldButton *field, c //~ Acrobat doesn't draw a caption if there is no AP dict (?) if (appearState && appearState->cmp("Off") != 0 && field->getState(appearState->c_str())) { if (caption) { - return drawText(caption, da, resources, border, appearCharacs, rect, VariableTextQuadding::centered, xref, resourcesDict, ForceZapfDingbatsDrawTextFlag); + return drawText(caption, form, da, resources, border, appearCharacs, rect, VariableTextQuadding::centered, xref, resourcesDict, ForceZapfDingbatsDrawTextFlag); } else if (appearCharacs) { const AnnotColor *aColor = appearCharacs->getBorderColor(); if (aColor) { @@ -5126,16 +5181,16 @@ bool AnnotAppearanceBuilder::drawFormFieldButton(const FormFieldButton *field, c } break; case formButtonPush: if (caption) { - return drawText(caption, da, resources, border, appearCharacs, rect, VariableTextQuadding::centered, xref, resourcesDict); + return drawText(caption, form, da, resources, border, appearCharacs, rect, VariableTextQuadding::centered, xref, resourcesDict); } break; case formButtonCheck: if (appearState && appearState->cmp("Off") != 0) { if (!caption) { GooString checkMark("3"); - return drawText(&checkMark, da, resources, border, appearCharacs, rect, VariableTextQuadding::centered, xref, resourcesDict, ForceZapfDingbatsDrawTextFlag); + return drawText(&checkMark, form, da, resources, border, appearCharacs, rect, VariableTextQuadding::centered, xref, resourcesDict, ForceZapfDingbatsDrawTextFlag); } else { - return drawText(caption, da, resources, border, appearCharacs, rect, VariableTextQuadding::centered, xref, resourcesDict, ForceZapfDingbatsDrawTextFlag); + return drawText(caption, form, da, resources, border, appearCharacs, rect, VariableTextQuadding::centered, xref, resourcesDict, ForceZapfDingbatsDrawTextFlag); } } break; @@ -5169,7 +5224,7 @@ bool AnnotAppearanceBuilder::drawFormFieldText(const FormFieldText *fieldText, c if (fieldText->isPassword()) { flags = flags | TurnTextToStarsDrawTextFlag; } - return drawText(contents, da, resources, border, appearCharacs, rect, quadding, xref, resourcesDict, flags, nCombs); + return drawText(contents, form, da, resources, border, appearCharacs, rect, quadding, xref, resourcesDict, flags, nCombs); } return true; @@ -5250,7 +5305,8 @@ void AnnotAppearanceBuilder::drawSignatureFieldText(const GooString &text, const // Setup text clipping appendf("{0:.2f} {1:.2f} {2:.2f} {3:.2f} re W n\n", leftMargin + textmargin, textmargin, textwidth, height - 2 * textmargin); setDrawColor(da.getFontColor(), true); - const DrawFreeTextResult textCommands = drawFreeTextText(text, textwidth, form, *font, da.getFontName().getName(), da.getFontPtSize(), centerHorizontally ? VariableTextQuadding::centered : VariableTextQuadding::leftJustified); + const DrawMultiLineTextResult textCommands = + drawMultiLineText(text, textwidth, form, *font, da.getFontName().getName(), da.getFontPtSize(), centerHorizontally ? VariableTextQuadding::centered : VariableTextQuadding::leftJustified, 0 /*borderWidth*/); double yDelta = height - textmargin - da.getFontPtSize() * font->getDescent(); if (centerVertically) { @@ -5281,7 +5337,7 @@ bool AnnotAppearanceBuilder::drawFormFieldChoice(const FormFieldChoice *fieldCho if (fieldChoice->isCombo()) { selected = fieldChoice->getSelectedChoice(); if (selected) { - return drawText(selected, da, resources, border, appearCharacs, rect, quadding, xref, resourcesDict, EmitMarkedContentDrawTextFlag); + return drawText(selected, form, da, resources, border, appearCharacs, rect, quadding, xref, resourcesDict, EmitMarkedContentDrawTextFlag); //~ Acrobat draws a popup icon on the right side } // list box @@ -5369,7 +5425,14 @@ void AnnotWidget::generateFieldAppearance() // build the appearance stream Stream *appearStream = new AutoFreeMemStream(copyString(appearBuf->c_str()), 0, appearBuf->getLength(), Object(appearDict)); - appearance = Object(appearStream); + if (hasBeenUpdated) { + // We should technically do this for all annots but AnnotFreeText + // forms are 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(Object(appearStream)); + } else { + appearance = Object(appearStream); + } if (resourcesToFree) { delete resourcesToFree; diff --git a/poppler/Annot.h b/poppler/Annot.h index f9c7816c..15dbfe81 100644 --- a/poppler/Annot.h +++ b/poppler/Annot.h @@ -615,8 +615,8 @@ private: }; bool drawListBox(const FormFieldChoice *fieldChoice, const AnnotBorder *border, const PDFRectangle *rect, const GooString *da, const GfxResources *resources, VariableTextQuadding quadding, XRef *xref, Dict *resourcesDict); - bool drawFormFieldButton(const FormFieldButton *field, const GfxResources *resources, const GooString *da, const AnnotBorder *border, const AnnotAppearanceCharacs *appearCharacs, const PDFRectangle *rect, const GooString *appearState, - XRef *xref, Dict *resourcesDict); + bool drawFormFieldButton(const FormFieldButton *field, const Form *form, const GfxResources *resources, const GooString *da, const AnnotBorder *border, const AnnotAppearanceCharacs *appearCharacs, const PDFRectangle *rect, + const GooString *appearState, XRef *xref, Dict *resourcesDict); bool drawFormFieldText(const FormFieldText *fieldText, const Form *form, const GfxResources *resources, const GooString *da, const AnnotBorder *border, const AnnotAppearanceCharacs *appearCharacs, const PDFRectangle *rect, XRef *xref, Dict *resourcesDict); bool drawFormFieldChoice(const FormFieldChoice *fieldChoice, const Form *form, const GfxResources *resources, const GooString *da, const AnnotBorder *border, const AnnotAppearanceCharacs *appearCharacs, const PDFRectangle *rect, @@ -625,8 +625,8 @@ private: XRef *xref, Dict *resourcesDict); 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); + bool drawText(const GooString *text, const Form *form, 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); GooString *appearBuf; diff --git a/poppler/Form.cc b/poppler/Form.cc index 41f823dc..a7bf9aba 100644 --- a/poppler/Form.cc +++ b/poppler/Form.cc @@ -1660,6 +1660,18 @@ void FormFieldText::setContentCopy(const GooString *new_content) if (!content->hasUnicodeMarker()) { content->prependUnicodeMarker(); } + Form *form = doc->getCatalog()->getForm(); + if (form) { + DefaultAppearance da(defaultAppearance); + if (da.getFontName().isName()) { + const std::string fontName = da.getFontName().getName(); + if (!fontName.empty()) { + form->ensureFontsForAllCharacters(new_content, fontName); + } + } else { + // This is wrong, there has to be a Tf in DA + } + } } obj.getDict()->set("V", Object(content ? content->copy() : new GooString(""))); @@ -2922,24 +2934,25 @@ void Form::ensureFontsForAllCharacters(const GooString *unicodeText, const std:: 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); + if (ccToUnicode->mapToCharCode(&uChar, &c, 1)) { + if (f->isCIDFont()) { + auto cidFont = static_cast<const GfxCIDFont *>(f.get()); + if (c < cidFont->getCIDToGIDLen() && c != 0 && c != '\r' && c != '\n') { + const int glyph = cidFont->getCIDToGID()[c]; + if (glyph == 0) { + doGetAddFontToDefaultResources(uChar, *f); + } + } } + } else { + doGetAddFontToDefaultResources(uChar, *f); } } }