Font subsetting for form fields
diff --git a/poppler/Annot.cc b/poppler/Annot.cc index f40643a..faf523d 100644 --- a/poppler/Annot.cc +++ b/poppler/Annot.cc
@@ -3302,7 +3302,7 @@ return fontDict; } -std::vector<std::shared_ptr<const GfxFont>> AnnotFreeText::subsetFonts(const FontSubsetter *fontSubsetter) +AnnotFreeText::SubsetFontsResult AnnotFreeText::subsetFonts(const FontSubsetter *fontSubsetter) { #if ENABLE_HARFBUZZ if (appearance.isNull()) { @@ -3388,7 +3388,7 @@ Dict *apDict = appearance.fetch(doc->getXRef()).getStream()->getDict(); apDict->set("Resources", std::move(resourcesCopy)); - return result.fontsToRemove; + return { .success = true, .fontsToRemove = result.fontsToRemove }; #else (void)fontSubsetter; return {}; @@ -4733,11 +4733,9 @@ const bool forceZapfDingbats = flags & ForceZapfDingbatsDrawTextFlag; std::vector<std::string> daToks; - const GfxFont *font; double fontSize; int tfPos, tmPos; std::unique_ptr<std::string> textToFree; - std::unique_ptr<const GfxFont> fontToFree = nullptr; //~ if there is no MK entry, this should use the existing content stream, //~ and only replace the marked content portion of it @@ -4761,39 +4759,11 @@ } // get the font and font size - font = nullptr; - fontSize = 0; - if (tfPos >= 0) { - std::string &tok = daToks[tfPos]; - if (forceZapfDingbats) { - if (tok != "/ZaDb") { - tok = "/ZaDb"; - } - } - if (!tok.empty() && tok[0] == '/') { - const auto fontName = std::string_view(tok).substr(1); - if (!resources || !(font = resources->lookupFont(fontName).get())) { - const char *fallback = determineFallbackFont(tok, forceZapfDingbats ? "ZapfDingbats" : "Helvetica"); - // The font variable sometimes points to an object that needs to be deleted - // and sometimes not, depending on whether the call to lookupFont above fails. - // When the code path right here is taken, the destructor of fontToFree - // (which is a std::unique_ptr) will delete the font object at the end of this method. - fontToFree = createAnnotDrawFont(&xref, &resourcesDict, fontName, fallback); - font = fontToFree.get(); - if (font && forceZapfDingbats) { - addedDingbatsResource = true; - } - } - } else { - error(errSyntaxError, -1, "Invalid font name in 'Tf' operator in field's DA string"); - } - fontSize = gatof(daToks[tfPos + 1].c_str()); - } else { - error(errSyntaxError, -1, "Missing 'Tf' operator in field's DA string"); - } + std::shared_ptr<const GfxFont> font = calculateFont(daToks, resources, resourcesDict, xref, tfPos, flags); if (!font) { return false; } + fontSize = gatof(daToks[tfPos + 1].c_str()); if (tmPos < 0) { // Add fake Tm to the DA tokens @@ -4938,7 +4908,7 @@ int uLen, n; double char_dx, char_dy, ox, oy; - const GfxFont *currentFont = font; + const GfxFont *currentFont = font.get(); if (!d.fontName.empty()) { appearBuf.append(" q\n"); GooString::appendf(appearBuf, "/{0:s} {1:.2f} Tf\n", d.fontName.c_str(), fontSize); @@ -5583,6 +5553,196 @@ recursiveMergeDicts(primary, secondary, &alreadySeenDicts); } +AnnotWidget::SubsetFontsResult AnnotWidget::subsetFonts(const FontSubsetter *fontSubsetter, const std::string &content) +{ +#if ENABLE_HARFBUZZ + const auto *textField = dynamic_cast<FormFieldText *>(field); + if (!textField) { + error(errInternal, -1, "AnnotWidget::subsetFonts, subsetFonts() called on a non-text form field. This should never happen. Abort subsetting."); + return {}; + } + + // TODO: Implement subsetting when appearance is null + if (appearance.isNull() || form->getNeedAppearances()) { + return {}; + } + + Object resourcesDictObj; + const GfxResources *resources = nullptr; + std::unique_ptr<GfxResources> resourcesToFree = nullptr; + if (field->getObj() && field->getObj()->isDict()) { + // Let's use a field's resource dictionary. + resourcesDictObj = field->getObj()->dictLookup("DR"); + if (resourcesDictObj.isDict()) { + if (form && form->getDefaultResourcesObj()->isDict()) { + resourcesDictObj = resourcesDictObj.deepCopy(); + recursiveMergeDicts(resourcesDictObj.getDict(), form->getDefaultResourcesObj()->getDict()); + } + resourcesToFree = std::make_unique<GfxResources>(doc->getXRef(), resourcesDictObj.getDict(), nullptr); + resources = resourcesToFree.get(); + } + } + if (!resourcesDictObj.isDict()) { + // No luck with a field's resource dictionary. Let's use an AcroForm's resource dictionary. + if (form && form->getDefaultResourcesObj()->isDict()) { + resourcesDictObj = form->getDefaultResourcesObj()->deepCopy(); + resources = form->getDefaultResources(); + } + } + if (!resourcesDictObj.isDict()) { + resourcesDictObj = Object(std::make_unique<Dict>(doc->getXRef())); + } + + const std::string &fieldDA = field->getDefaultAppearance(); + const std::string &daToUse = fieldDA.empty() && form ? form->getDefaultAppearance() : fieldDA; + + std::vector<std::string> daToks; + int tfPos = -1; + + // parse the default appearance string + FormFieldText::tokenizeDA(daToUse, &daToks, nullptr /*searchTok*/); + for (size_t i = 2; i < daToks.size(); ++i) { + if (daToks[i] == "Tf") { + tfPos = i - 2; + } + } + + AnnotAppearanceBuilder appearBuilderA; + std::shared_ptr<const GfxFont> primaryFont = appearBuilderA.calculateFont(daToks, resources, *resourcesDictObj.getDict(), *doc->getXRef(), tfPos, 0 /* No flags required when our purpose is to get the primary font for a form field*/); + + if (!primaryFont) { + error(errInternal, -1, "AnnotWidget::subsetFonts, Could not determine primary font"); + return {}; + } + + HorizontalTextLayouter textLayouter(content, form, *primaryFont, std::nullopt, false); + + FontSubsetter::FontStringMap fontStringMap; + bool isPassword = textField->isPassword(); + + for (const auto &d : textLayouter.data) { + const std::string &text = d.text; + + std::shared_ptr<const GfxFont> font = nullptr; + + if (d.fontName.empty()) { + font = primaryFont; + } else { + font = form->getDefaultResources()->lookupFont(d.fontName); + if (!font) { + error(errInternal, -1, "AnnotWidget::subsetFonts, Could not find font in default resources"); + return {}; + } + } + + // If it's a password field, subset only '*' in all the fonts + if (isPassword) { + Unicode uChar = pdfDocEncoding['*' & 0xff]; + fontStringMap[font] = { uChar }; + continue; + } + + bool isUnicode; + if (d.charCount == text.size()) { + isUnicode = false; + } else if (d.charCount * 2 == text.size()) { + isUnicode = true; + } else { + error(errInternal, -1, "AnnotWidget::subsetFonts, Invalid result from HorizontalTextLayouter"); + return {}; + } + + if (text.empty()) { + if (!fontStringMap.contains(font)) { + fontStringMap[font] = {}; + } + } + + size_t i = 0; + while (i < text.size()) { + if (isUnicode) { + Unicode uChar = ((text[i] & 0xff) << 8) | (text[i + 1] & 0xff); + fontStringMap[font].emplace_back(uChar); + } else { + Unicode uChar = pdfDocEncoding[text[i] & 0xff]; + fontStringMap[font].emplace_back(uChar); + } + i += (isUnicode ? 2 : 1); + } + } + + FontSubsetter::GetSubsetFontsResult result = fontSubsetter->getSubsetFonts(fontStringMap); + + /* + - Deep copy the Resources present in appearance dictionary + - Get a pointer to the font dictionary inside the resources copy + - Modify the font dictionary + - Set "Resources" in the appearance dictionary to our resources copy + */ + + Object resourcesCopy = getAppearanceResDict().deepCopy(); + Dict *fontDict = getFontDictFromResourcesDict(resourcesCopy, doc->getXRef(), false); + + if (!fontDict) { + error(errInternal, -1, "AnnotWidget::subsetFonts, Unable to find font dictionary inside appearance resource dictionary"); + return {}; + } + + for (const auto &[tag, newFontRef] : result.tagRefMappings) { + if (fontDict->hasKey(tag)) { + fontDict->set(tag, Object(newFontRef)); + } else { + // This should never happen. We are just being extra safe here. + error(errInternal, -1, "AnnotWidget::subsetFonts, a tag was not found in font dictionary. Aborting subsetting."); + return {}; + } + } + + Dict *apDict = appearance.fetch(doc->getXRef()).getStream()->getDict(); + apDict->set("Resources", std::move(resourcesCopy)); + + return { .success = true, .fontsToRemove = result.fontsToRemove }; +#else + (void)fontSubsetter; + (void)content; + return {}; +#endif +} + +std::shared_ptr<const GfxFont> AnnotAppearanceBuilder::calculateFont(std::vector<std::string> &daToks, const GfxResources *resources, Dict &resourcesDict, XRef &xref, int tfPos, const int flags) +{ + const bool forceZapfDingbats = flags & ForceZapfDingbatsDrawTextFlag; + + std::shared_ptr<const GfxFont> font; + + // get the font + font = nullptr; + if (tfPos >= 0) { + std::string &tok = daToks[tfPos]; + if (forceZapfDingbats) { + if (tok != "/ZaDb") { + tok = "/ZaDb"; + } + } + if (!tok.empty() && tok[0] == '/') { + const auto fontName = std::string_view(tok).substr(1); + if (!resources || !(font = resources->lookupFont(fontName))) { + const char *fallback = determineFallbackFont(tok, forceZapfDingbats ? "ZapfDingbats" : "Helvetica"); + font = createAnnotDrawFont(&xref, &resourcesDict, fontName, fallback); + if (font && forceZapfDingbats) { + addedDingbatsResource = true; + } + } + } else { + error(errSyntaxError, -1, "Invalid font name in 'Tf' operator in field's DA string"); + } + } else { + error(errSyntaxError, -1, "Missing 'Tf' operator in field's DA string"); + } + + return font; +} + void AnnotWidget::generateFieldAppearance(bool *addedDingbatsResource) { AnnotAppearanceBuilder appearBuilder;
diff --git a/poppler/Annot.h b/poppler/Annot.h index 8eea9f1..4764d42 100644 --- a/poppler/Annot.h +++ b/poppler/Annot.h
@@ -609,6 +609,8 @@ const std::string &buffer() const; bool getAddedDingbatsResource() const { return addedDingbatsResource; } + std::shared_ptr<const GfxFont> calculateFont(std::vector<std::string> &daToks, const GfxResources *resources, Dict &resourcesDict, XRef &xref, int tfPos, int flags); + private: enum DrawTextFlags { @@ -1090,7 +1092,12 @@ AnnotLineEndingStyle getEndStyle() const { return endStyle; } // Performs the subsetting and returns the original fonts to delete - std::vector<std::shared_ptr<const GfxFont>> subsetFonts(const FontSubsetter *fontSubsetter); + struct SubsetFontsResult + { + bool success = false; + std::vector<std::shared_ptr<const GfxFont>> fontsToRemove; + }; + SubsetFontsResult subsetFonts(const FontSubsetter *fontSubsetter); protected: void initialize(Dict *dict); @@ -1486,6 +1493,14 @@ void setField(FormField *f) { field = f; }; + // Performs the subsetting and returns the original fonts to delete + struct SubsetFontsResult + { + bool success = false; + std::vector<std::shared_ptr<const GfxFont>> fontsToRemove; + }; + SubsetFontsResult subsetFonts(const FontSubsetter *fontSubsetter, const std::string &content); + private: void initialize(Dict *dict);
diff --git a/poppler/FontSubsetter.cc b/poppler/FontSubsetter.cc index e2c6b3f..0db2687 100644 --- a/poppler/FontSubsetter.cc +++ b/poppler/FontSubsetter.cc
@@ -96,6 +96,38 @@ xref->removeIndirectObject(fontStreamRef); } +void FontSubsetter::subsetFormFieldText(const std::unique_ptr<FormPageWidgets> &form, std::vector<std::shared_ptr<const GfxFont>> &fontsToRemove, std::unordered_set<Ref> &refSet, XRef *xref, bool &subsettingSuccessful) const +{ + int numWidgets = form->getNumWidgets(); + for (int i = 0; i < numWidgets; ++i) { + FormWidget *widget = form->getWidget(i); + if (widget->getType() != FormFieldType::formText) { + continue; + } + + auto *widgetText = static_cast<FormWidgetText *>(widget); + + if (auto wdgAnnot = widgetText->getWidgetAnnotation(); wdgAnnot != nullptr) { + unsigned int id = wdgAnnot->getId(); + if (!xref->getEntry(id)->getFlag(XRefEntry::Updated) || !widgetText->getContent()) { + continue; + } + + auto result = wdgAnnot->subsetFonts(this, widgetText->getContent()->toStr()); + if (!result.success) { + subsettingSuccessful = false; + } + + for (const auto &font : result.fontsToRemove) { + if (!refSet.contains(*font->getID())) { + fontsToRemove.emplace_back(font); + refSet.insert(*font->getID()); + } + } + } + } +} + static void removeFontsFromFontDict(Dict *fontDict, std::unordered_set<Ref> &refSet) { std::vector<std::string> keysToRemove; @@ -119,40 +151,29 @@ } } -static bool formFieldModified(const FormField *field, XRef *xref) +static void removeFontsFromFieldRes(const std::unique_ptr<FormPageWidgets> &form, std::unordered_set<Ref> &refSet, XRef *xref) { - int numChildren = field->getNumChildren(); - if (numChildren == 0) { - int numWidgets = field->getNumWidgets(); + int numWidgets = form->getNumWidgets(); + for (int i = 0; i < numWidgets; ++i) { + FormWidget *widget = form->getWidget(i); + if (widget->getType() != FormFieldType::formText || !widget->getField()) { + continue; + } - for (int i = 0; i < numWidgets; ++i) { - if (FormWidget *wdg = field->getWidget(i); wdg != nullptr) { - if (std::shared_ptr<AnnotWidget> wdgAnnot = wdg->getWidgetAnnotation(); wdgAnnot != nullptr) { - unsigned int id = wdgAnnot->getId(); - if (xref->getEntry(id)->getFlag(XRefEntry::Updated)) { - return true; - } + FormField *field = widget->getField(); + if (Object *fieldObj = field->getObj(); fieldObj && fieldObj->isDict()) { + if (Object dr = fieldObj->dictLookup("DR"); dr.isDict()) { + if (Dict *fontDict = getFontDictFromResourcesDict(dr, xref, false); fontDict != nullptr) { + removeFontsFromFontDict(fontDict, refSet); } } } - return false; } - - for (int i = 0; i < numChildren; ++i) { - if (const FormField *childField = field->getChildren(i); childField != nullptr) { - if (formFieldModified(childField, xref)) { - return true; - } - } - } - - return false; } void FontSubsetter::subsetAll() const { XRef *xref = doc->getXRef(); - Form *form = doc->getCatalog()->getCreateForm(); int numPages = doc->getNumPages(); Object *acroForm = doc->getCatalog()->getAcroForm(); @@ -162,25 +183,13 @@ std::unordered_set<Ref> refSet; std::vector<std::shared_ptr<const GfxFont>> fontsToRemove; - /* - * Do not perform font subsetting if a form field is also modified - * This is because we might remove a font that is already getting used in a form field - * TODO: Remove this code when we have font subsetting ready for form fields - */ - bool anyFormFieldModified = false; - for (int i = 0; i < form->getNumFields(); i++) { - if (FormField *field = form->getRootField(i); field != nullptr) { - if (formFieldModified(field, xref)) { - anyFormFieldModified = true; - break; - } - } - } - if (anyFormFieldModified) { - return; + bool subsettingSuccessful = true; + + for (int i = 1; i <= numPages; i++) { + Page *page = doc->getPage(i); + subsetFormFieldText(page->getFormWidgets(), fontsToRemove, refSet, xref, subsettingSuccessful); } - bool subsettingSuccessful = true; for (int i = 1; i <= numPages; i++) { Page *page = doc->getPage(i); @@ -200,12 +209,12 @@ if (annot->getType() == Annot::typeFreeText) { auto *ftAnnot = static_cast<AnnotFreeText *>(annot.get()); - auto fontsToRemoveLocal = ftAnnot->subsetFonts(this); - if (fontsToRemoveLocal.empty()) { + auto result = ftAnnot->subsetFonts(this); + if (!result.success) { subsettingSuccessful = false; } - for (const auto &font : fontsToRemoveLocal) { + for (const auto &font : result.fontsToRemove) { if (!refSet.contains(*font->getID())) { fontsToRemove.emplace_back(font); refSet.insert(*font->getID()); @@ -219,6 +228,7 @@ * If subsetting fails for even one entity (annotation or form), it may not be safe to remove the original fonts since the entity might be using the old fonts */ if (!subsettingSuccessful) { + error(errInternal, -1, "FontSubsetter::subsetAll, subsetting was not fully successful so skipping removal of original fonts"); return; } @@ -232,7 +242,7 @@ } } - // Scan local resources of all modified annotations and remove the old font refs + // Scan local resources of all modified freetext annotations and widget annotations (forms) and remove the old font refs for (int i = 1; i <= numPages; i++) { Page *page = doc->getPage(i); @@ -249,15 +259,18 @@ continue; } - if (annot->getType() == Annot::typeFreeText) { - auto *ftAnnot = static_cast<AnnotFreeText *>(annot.get()); - - Object resourcesCopy = ftAnnot->getAppearanceResDict().deepCopy(); + if (annot->getType() == Annot::typeFreeText || annot->getType() == Annot::typeWidget) { + Object resourcesCopy = annot->getAppearanceResDict().deepCopy(); if (!resourcesCopy.isDict()) { continue; } - Dict *fontDict = getFontDictFromResourcesDict(resourcesCopy, xref, (ftAnnot->getOpacity() != 1)); + bool transparency = false; + if (annot->getType() == Annot::typeFreeText) { + auto *ftAnnot = static_cast<AnnotFreeText *>(annot.get()); + transparency = (ftAnnot->getOpacity() != 1); + } + Dict *fontDict = getFontDictFromResourcesDict(resourcesCopy, xref, transparency); if (!fontDict) { continue; } @@ -275,6 +288,12 @@ } } + // Remove old font refs from modified field resources + for (int i = 1; i <= numPages; i++) { + Page *page = doc->getPage(i); + removeFontsFromFieldRes(page->getFormWidgets(), refSet, xref); + } + // Remove the actual font and it's associated objects for (const auto &font : fontsToRemove) { removeOldFont(font, xref);
diff --git a/poppler/FontSubsetter.h b/poppler/FontSubsetter.h index 47f4ef8..addeabe 100644 --- a/poppler/FontSubsetter.h +++ b/poppler/FontSubsetter.h
@@ -14,10 +14,12 @@ #include "GfxFont.h" #include "Object.h" #include <vector> +#include <unordered_set> struct hb_blob_t; class PDFDoc; +class FormPageWidgets; class FontSubsetter { @@ -49,6 +51,8 @@ Object createNewSubsetFont(const GfxFont *oldFont, SubsetFontResult &&subsettingResult, const std::vector<Unicode> &unicodeValues, Ref &newFontRef) const; + void subsetFormFieldText(const std::unique_ptr<FormPageWidgets> &form, std::vector<std::shared_ptr<const GfxFont>> &fontsToRemove, std::unordered_set<Ref> &refSet, XRef *xref, bool &subsettingSuccessful) const; + // Harfbuzz related APIs static SubsetFontResult hbSubsetFont(std::string &fontStream, const std::vector<Unicode> &unicodeValues); };
diff --git a/poppler/Form.h b/poppler/Form.h index 0fd8677..98a3c84 100644 --- a/poppler/Form.h +++ b/poppler/Form.h
@@ -491,7 +491,7 @@ // FormFieldText //------------------------------------------------------------------------ -class FormFieldText : public FormField +class POPPLER_PRIVATE_EXPORT FormFieldText : public FormField { public: FormFieldText(PDFDoc *docA, Object &&dictObj, Ref ref, FormField *parent, std::set<int> *usedParents);
diff --git a/test/CMakeLists.txt b/test/CMakeLists.txt index 933edba..60779d1 100644 --- a/test/CMakeLists.txt +++ b/test/CMakeLists.txt
@@ -302,11 +302,11 @@ endif() if (ENABLE_HARFBUZZ) - set(fontsubsetting_basic_test_SRCS font-subsetting/fontsubsetting-basic-test.cc) - add_executable(fontsubsetting-basic-test ${fontsubsetting_basic_test_SRCS}) - target_link_libraries(fontsubsetting-basic-test Freetype::Freetype poppler) - add_test( - NAME fontsubsetting-basic-test - COMMAND fontsubsetting-basic-test ${TESTDATADIR}/unittestcases/UseNone.pdf - ) -endif() \ No newline at end of file + set(fontsubsetting_basic_test_SRCS font-subsetting/fontsubsetting-basic-test.cc) + add_executable(fontsubsetting-basic-test ${fontsubsetting_basic_test_SRCS}) + target_link_libraries(fontsubsetting-basic-test Freetype::Freetype poppler) + add_test( + NAME fontsubsetting-basic-test + COMMAND fontsubsetting-basic-test ${TESTDATADIR}/unittestcases/UseNone.pdf ${TESTDATADIR}/unittestcases/pdf_with_text_form_field.pdf + ) +endif()
diff --git a/test/font-subsetting/fontsubsetting-basic-test.cc b/test/font-subsetting/fontsubsetting-basic-test.cc index f263825..73f2f53 100644 --- a/test/font-subsetting/fontsubsetting-basic-test.cc +++ b/test/font-subsetting/fontsubsetting-basic-test.cc
@@ -167,7 +167,7 @@ * Goes over all the fonts used by the annotation and collects all the glyphs * Checks if the glyphs are only what we need */ -static bool subsettingWorksInAnnot(Annot *annot, XRef *xref, bool transparency) +static bool subsettingWorksInAnnot(Annot *annot, const std::string &text, XRef *xref, bool transparency) { Object resources = annot->getAppearanceResDict(); const Dict *fontDict = getFontDictFromResourcesDict(resources, xref, transparency); @@ -180,7 +180,6 @@ std::vector<unsigned long> allChars; for (auto &font : allFonts) { std::string data = getFontDataFromFont(font, xref); - if (data.empty()) { continue; } @@ -195,8 +194,7 @@ } } - std::vector<unsigned long> neededChars = convertUnicodetoNormalStr(annot->getContents().toStr()); - + std::vector<unsigned long> neededChars = convertUnicodetoNormalStr(text); return match(allChars, neededChars); } @@ -264,7 +262,7 @@ return false; } - return subsettingWorksInAnnot(annot.get(), doc->getXRef(), false); + return subsettingWorksInAnnot(annot.get(), annot->getContents().toStr(), doc->getXRef(), false); } /* @@ -279,7 +277,6 @@ } auto doc = PDFDocFactory().createPDFDoc(*filename); - if (!doc->isOk() || doc->getNumPages() < 1) { return false; } @@ -299,7 +296,6 @@ // Re-open the file and check if the fonts only contain the needed glyphs doc = PDFDocFactory().createPDFDoc(*tempFilePath); - if (!doc->isOk() || doc->getNumPages() < 1) { return false; } @@ -321,24 +317,100 @@ return false; } - return subsettingWorksInAnnot(annot.get(), doc->getXRef(), true); + return subsettingWorksInAnnot(annot.get(), annot->getContents().toStr(), doc->getXRef(), true); +} + +static bool testFormFieldSubsetting(std::shared_ptr<GooString> &filename) +{ + std::string UNICODE_TEXT; + int unicodeVals[] = { -2, -1, 101, -27, 103, 44, -118, -98 }; // "日本語" + for (int val : unicodeVals) { + UNICODE_TEXT += static_cast<char>(val); + } + + auto doc = PDFDocFactory().createPDFDoc(*filename); + if (!doc->isOk() || doc->getNumPages() < 1) { + return false; + } + + Form *form = doc->getCatalog()->getCreateForm(); + + int numFields = form->getNumFields(); + + if (numFields != 1) { + return false; + } + + FormField *field = form->getRootField(0); + if (field->getNumChildren() != 0 || field->getType() != FormFieldType::formText) { + return false; + } + + auto *textField = dynamic_cast<FormFieldText *>(field); + + textField->setContent(std::make_unique<GooString>(UNICODE_TEXT)); + + auto tempFilePath = std::make_unique<GooString>(std::filesystem::current_path() / "temp.pdf"); + + doc->saveAs(tempFilePath->toStr()); + + doc = PDFDocFactory().createPDFDoc(*tempFilePath); + if (!doc->isOk() || doc->getNumPages() < 1) { + return false; + } + + form = doc->getCatalog()->getCreateForm(); + + numFields = form->getNumFields(); + if (numFields != 1) { + return false; + } + + field = form->getRootField(0); + if (field->getNumChildren() != 0 || field->getType() != FormFieldType::formText) { + return false; + } + + textField = dynamic_cast<FormFieldText *>(field); + std::string newText = textField->getContent()->toStr(); + + if (newText != UNICODE_TEXT) { + return false; + } + + int numWidgets = textField->getNumWidgets(); + if (numWidgets != 1) { + return false; + } + + FormWidget *widget = textField->getWidget(0); + + std::shared_ptr<AnnotWidget> wdgAnnot = widget->getWidgetAnnotation(); + + return subsettingWorksInAnnot(wdgAnnot.get(), newText, doc->getXRef(), false); } int main(int argc, char *argv[]) { - if (argc != 2) { + if (argc != 3) { return 1; } globalParams = std::make_unique<GlobalParams>(); - // For test1 and test2 - auto filename = std::make_shared<GooString>(argv[1]); + auto filename1 = std::make_shared<GooString>(argv[1]); + auto filename2 = std::make_shared<GooString>(argv[2]); - auto test1 = testSingleAnnotationSubsetting(filename); - auto test2 = testSingleAnnotationSubsettingWithOpacity(filename); + auto test1 = testSingleAnnotationSubsetting(filename1); + auto test2 = testSingleAnnotationSubsettingWithOpacity(filename1); + auto test3 = testFormFieldSubsetting(filename2); - if (!(test1 && test2)) { + // Check and remove the temporary pdf file we save for the tests + if (std::filesystem::exists(std::filesystem::current_path() / "temp.pdf")) { + std::filesystem::remove(std::filesystem::current_path() / "temp.pdf"); + } + + if (!(test1 && test2 && test3)) { return 1; }