diff --git a/job.sums b/job.sums index 2a9b1bda..a3116e44 100644 --- a/job.sums +++ b/job.sums @@ -6,12 +6,12 @@ include/qpdf/auto_job_c_enc.hh 72e138c7b96ed5aacdce78c1dec04b1c20d361faec4f8faf5 include/qpdf/auto_job_c_main.hh 516adb23cc7e44e614e436880be870d0574e4ebbc706cd855a1360000eed31bb include/qpdf/auto_job_c_pages.hh 931840b329a36ca0e41401190e04537b47f2867671a6643bfd8da74014202671 include/qpdf/auto_job_c_uo.hh 0585b7de459fa479d9e51a45fa92de0ff6dee748efc9ec1cedd0dde6cee1ad50 -job.yml 7433861fa281197aa275d624a334aa324ec63839c5e56a24448ab64d0b75587c +job.yml 6389b89c25f0f07fa54bfc3d9f24f814aac5566ace43666f81476781db616ea1 libqpdf/qpdf/auto_job_decl.hh 9f79396ec459f191be4c5fe34cf88c265cf47355a1a945fa39169d1c94cf04f6 libqpdf/qpdf/auto_job_help.hh 23c79f1d2c02bda28f64aace17f69487205c797e7ae2234892cbbabab49d6d47 libqpdf/qpdf/auto_job_init.hh 8e9e31b6099a662497339b27f6e2d7f779f35011e88a834bee8811c33405a0fe -libqpdf/qpdf/auto_job_json_decl.hh d315f920a32d7a5a2272807e6813d463c3e1877a9d735e44e8417b5f1461b87a -libqpdf/qpdf/auto_job_json_init.hh 08e6ee8a509bc592e3aa6f7a1d3a6d18cdf4803e6220147855caf675e0a299ce -libqpdf/qpdf/auto_job_schema.hh 6ec5b9dd3b4709b49fb3b928c4d9cde8b35ad938a0945f81c9a3da6c3bf9a3c1 +libqpdf/qpdf/auto_job_json_decl.hh 8a6e3b25e01969f1c8e2c9ec781f7f2e89b57e67e65d5dc7445ad9124b694b9a +libqpdf/qpdf/auto_job_json_init.hh 48888c602de4cc13040cff26569e162d6ebb5aec6ab959164d432cfe1058eb23 +libqpdf/qpdf/auto_job_schema.hh 9e19fb0b8ddd6fe13da12f1f98c27f6d558fc4706a56a63697e529b3140a457c manual/_ext/qpdf.py e9ac9d6c70642a3d29281ee5ad92ae2422dee8be9306fb8a0bc9dba0ed5e28f3 manual/cli.rst 79140e023faa0cb77afe0b1dc512dd120ee5617f4db82f842596e4f239f93882 diff --git a/job.yml b/job.yml index 90c918e0..0d755fb2 100644 --- a/job.yml +++ b/job.yml @@ -318,19 +318,6 @@ json: Enc256.print: allow-insecure: force-R5: - _options: - allow-weak-crypto: - deterministic-id: - keep-files-open: - keep-files-open-threshold: - no-warn: - verbose: - warning-exit-0: - ignore-xref-streams: - password-is-hex-key: - password-mode: - suppress-password-recovery: - suppress-recovery: _inspect: check: check-linearization: @@ -353,13 +340,24 @@ json: - null json-object: - null - _transform: + _options: + allow-weak-crypto: + deterministic-id: + keep-files-open: + keep-files-open-threshold: + no-warn: + verbose: + warning-exit-0: + ignore-xref-streams: + password-is-hex-key: + password-mode: + suppress-password-recovery: + suppress-recovery: coalesce-contents: compression-level: externalize-inline-images: ii-min-bytes: remove-unreferenced-resources: - _modify: add-attachment: - path: "attachment to add" creationdate: diff --git a/libqpdf/QPDFJob_json.cc b/libqpdf/QPDFJob_json.cc index a9d68d49..8ebbb926 100644 --- a/libqpdf/QPDFJob_json.cc +++ b/libqpdf/QPDFJob_json.cc @@ -229,7 +229,9 @@ Handlers::setupInputFilename(std::string const& key) void Handlers::setupInputPassword(std::string const& key) { - // QXXXQ + addParameter(key, [this](char const* p) { + c_main->password(p); + }); } void @@ -281,21 +283,53 @@ Handlers::endOutputOptions() } void -Handlers::beginOutputOptionsEncrypt(JSON) +Handlers::beginOutputOptionsEncrypt(JSON j) { - // QXXXQ -// if (this->keylen_seen == 0) -// { -// usage("exactly one of 40bit, 128bit, or 256bit must be given;" -// " an empty dictionary may be supplied for one of them" -// " to set the key length without imposing any restrictions"); -// } + // This method is only called if the overall JSON structure + // matches the schema, so we already know that keys that are + // present have the right types. + int key_len = 0; + std::string user_password; + std::string owner_password; + bool user_password_seen = false; + bool owner_password_seen = false; + j.forEachDictItem([&](std::string const& key, JSON value){ + if ((key == "40bit") || (key == "128bit") || (key == "256bit")) + { + if (key_len != 0) + { + usage("exactly one of 40bit, 128bit, or 256bit must be given"); + } + key_len = QUtil::string_to_int(key.c_str()); + } + else if (key == "userPassword") + { + user_password_seen = value.getString(user_password); + } + else if (key == "ownerPassword") + { + owner_password_seen = value.getString(owner_password); + } + }); + if (key_len == 0) + { + usage("exactly one of 40bit, 128bit, or 256bit must be given;" + " an empty dictionary may be supplied for one of them" + " to set the key length without imposing any restrictions"); + } + if (! (user_password_seen && owner_password_seen)) + { + usage("the user and owner password are both required; use the empty" + " string for the user password if you don't want a password"); + } + this->c_enc = c_main->encrypt(key_len, user_password, owner_password); } void Handlers::endOutputOptionsEncrypt() { - // QXXXQ + this->c_enc->endEncrypt(); + this->c_enc = nullptr; } void @@ -371,147 +405,141 @@ Handlers::endInspect() } void -Handlers::beginTransform(JSON) +Handlers::beginOptionsAddAttachment(JSON) { - // nothing needed + this->c_att = c_main->addAttachment(); } void -Handlers::endTransform() +Handlers::endOptionsAddAttachment() { - // nothing needed + this->c_att->endAddAttachment(); + this->c_att = nullptr; } void -Handlers::beginModify(JSON) +Handlers::setupOptionsAddAttachmentPath(std::string const& key) { - // nothing needed + addParameter(key, [this](char const* p) { + c_att->path(p); + }); } void -Handlers::endModify() +Handlers::beginOptionsCopyAttachmentsFrom(JSON) { - // nothing needed + this->c_copy_att = c_main->copyAttachmentsFrom(); } void -Handlers::beginModifyAddAttachment(JSON) +Handlers::endOptionsCopyAttachmentsFrom() +{ + this->c_copy_att->endCopyAttachmentsFrom(); + this->c_copy_att = nullptr; +} + +void +Handlers::setupOptionsCopyAttachmentsFromPath(std::string const& key) +{ + addParameter(key, [this](char const* p) { + c_copy_att->path(p); + }); +} + +void +Handlers::setupOptionsCopyAttachmentsFromPassword(std::string const& key) +{ + addParameter(key, [this](char const* p) { + c_copy_att->password(p); + }); +} + +void +Handlers::beginOptionsPages(JSON) { // QXXXQ } void -Handlers::endModifyAddAttachment() +Handlers::endOptionsPages() { // QXXXQ } void -Handlers::setupModifyAddAttachmentPath(std::string const& key) +Handlers::setupOptionsPagesFile(std::string const& key) { - // QXXXQ setup + // handled in beginOptionsPages } void -Handlers::beginModifyCopyAttachmentsFrom(JSON) +Handlers::setupOptionsPagesPassword(std::string const& key) { - // QXXXQ + // handled in beginOptionsPages } void -Handlers::endModifyCopyAttachmentsFrom() +Handlers::setupOptionsPagesRange(std::string const& key) { - // QXXXQ + // handled in beginOptionsPages } void -Handlers::setupModifyCopyAttachmentsFromPath(std::string const& key) +Handlers::beginOptionsOverlay(JSON) { - // QXXXQ setup + this->c_uo = c_main->overlay(); } void -Handlers::setupModifyCopyAttachmentsFromPassword(std::string const& key) +Handlers::endOptionsOverlay() { - // QXXXQ setup + c_uo->endUnderlayOverlay(); + c_uo = nullptr; } void -Handlers::beginModifyPages(JSON) +Handlers::setupOptionsOverlayFile(std::string const& key) { - // QXXXQ + addParameter(key, [this](char const* p) { + c_uo->path(p); + }); } void -Handlers::endModifyPages() +Handlers::setupOptionsOverlayPassword(std::string const& key) { - // QXXXQ + addParameter(key, [this](char const* p) { + c_uo->password(p); + }); } void -Handlers::setupModifyPagesFile(std::string const& key) +Handlers::beginOptionsUnderlay(JSON) { - // QXXXQ setup + this->c_uo = c_main->underlay(); } void -Handlers::setupModifyPagesPassword(std::string const& key) +Handlers::endOptionsUnderlay() { - // QXXXQ setup + c_uo->endUnderlayOverlay(); + c_uo = nullptr; } void -Handlers::setupModifyPagesRange(std::string const& key) +Handlers::setupOptionsUnderlayFile(std::string const& key) { - // QXXXQ setup + addParameter(key, [this](char const* p) { + c_uo->path(p); + }); } void -Handlers::beginModifyOverlay(JSON) +Handlers::setupOptionsUnderlayPassword(std::string const& key) { - // QXXXQ -} - -void -Handlers::endModifyOverlay() -{ - // QXXXQ -} - -void -Handlers::setupModifyOverlayFile(std::string const& key) -{ - // QXXXQ setup -} - -void -Handlers::setupModifyOverlayPassword(std::string const& key) -{ - // QXXXQ setup -} - -void -Handlers::beginModifyUnderlay(JSON) -{ - // QXXXQ -} - -void -Handlers::endModifyUnderlay() -{ - // QXXXQ -} - -void -Handlers::setupModifyUnderlayFile(std::string const& key) -{ - // QXXXQ setup -} - -void -Handlers::setupModifyUnderlayPassword(std::string const& key) -{ - // QXXXQ setup + addParameter(key, [this](char const* p) { + c_uo->password(p); + }); } void diff --git a/libqpdf/qpdf/auto_job_json_decl.hh b/libqpdf/qpdf/auto_job_json_decl.hh index d8bf1ef4..446838e6 100644 --- a/libqpdf/qpdf/auto_job_json_decl.hh +++ b/libqpdf/qpdf/auto_job_json_decl.hh @@ -24,31 +24,27 @@ void beginOutputOptionsEncrypt128bit(JSON); void endOutputOptionsEncrypt128bit(); void beginOutputOptionsEncrypt256bit(JSON); void endOutputOptionsEncrypt256bit(); -void beginOptions(JSON); -void endOptions(); void beginInspect(JSON); void endInspect(); -void beginTransform(JSON); -void endTransform(); -void beginModify(JSON); -void endModify(); -void beginModifyAddAttachment(JSON); -void endModifyAddAttachment(); -void setupModifyAddAttachmentPath(std::string const&); -void beginModifyCopyAttachmentsFrom(JSON); -void endModifyCopyAttachmentsFrom(); -void setupModifyCopyAttachmentsFromPath(std::string const&); -void setupModifyCopyAttachmentsFromPassword(std::string const&); -void beginModifyPages(JSON); -void endModifyPages(); -void setupModifyPagesFile(std::string const&); -void setupModifyPagesPassword(std::string const&); -void setupModifyPagesRange(std::string const&); -void beginModifyOverlay(JSON); -void endModifyOverlay(); -void setupModifyOverlayFile(std::string const&); -void setupModifyOverlayPassword(std::string const&); -void beginModifyUnderlay(JSON); -void endModifyUnderlay(); -void setupModifyUnderlayFile(std::string const&); -void setupModifyUnderlayPassword(std::string const&); +void beginOptions(JSON); +void endOptions(); +void beginOptionsAddAttachment(JSON); +void endOptionsAddAttachment(); +void setupOptionsAddAttachmentPath(std::string const&); +void beginOptionsCopyAttachmentsFrom(JSON); +void endOptionsCopyAttachmentsFrom(); +void setupOptionsCopyAttachmentsFromPath(std::string const&); +void setupOptionsCopyAttachmentsFromPassword(std::string const&); +void beginOptionsPages(JSON); +void endOptionsPages(); +void setupOptionsPagesFile(std::string const&); +void setupOptionsPagesPassword(std::string const&); +void setupOptionsPagesRange(std::string const&); +void beginOptionsOverlay(JSON); +void endOptionsOverlay(); +void setupOptionsOverlayFile(std::string const&); +void setupOptionsOverlayPassword(std::string const&); +void beginOptionsUnderlay(JSON); +void endOptionsUnderlay(); +void setupOptionsUnderlayFile(std::string const&); +void setupOptionsUnderlayPassword(std::string const&); diff --git a/libqpdf/qpdf/auto_job_json_init.hh b/libqpdf/qpdf/auto_job_json_init.hh index b3a7ced5..7570a492 100644 --- a/libqpdf/qpdf/auto_job_json_init.hh +++ b/libqpdf/qpdf/auto_job_json_init.hh @@ -83,20 +83,6 @@ endDict(); // .output.options.encrypt.256bit endDict(); // .output.options.encrypt endDict(); // .output.options endDict(); // .output -beginDict("options", bindJSON(&Handlers::beginOptions), bindBare(&Handlers::endOptions)); // .options -addBare("allowWeakCrypto", [this]() { c_main->allowWeakCrypto(); }); -addBare("deterministicId", [this]() { c_main->deterministicId(); }); -addChoices("keepFilesOpen", yn_choices, [this](char const* p) { c_main->keepFilesOpen(p); }); -addParameter("keepFilesOpenThreshold", [this](char const* p) { c_main->keepFilesOpenThreshold(p); }); -addBare("noWarn", [this]() { c_main->noWarn(); }); -addBare("verbose", [this]() { c_main->verbose(); }); -addBare("warningExit0", [this]() { c_main->warningExit0(); }); -addBare("ignoreXrefStreams", [this]() { c_main->ignoreXrefStreams(); }); -addBare("passwordIsHexKey", [this]() { c_main->passwordIsHexKey(); }); -addChoices("passwordMode", password_mode_choices, [this](char const* p) { c_main->passwordMode(p); }); -addBare("suppressPasswordRecovery", [this]() { c_main->suppressPasswordRecovery(); }); -addBare("suppressRecovery", [this]() { c_main->suppressRecovery(); }); -endDict(); // .options beginDict("inspect", bindJSON(&Handlers::beginInspect), bindBare(&Handlers::endInspect)); // .inspect addBare("check", [this]() { c_main->check(); }); addBare("checkLinearization", [this]() { c_main->checkLinearization(); }); @@ -118,16 +104,26 @@ addBare("json", [this]() { c_main->json(); }); addChoices("jsonKey", json_key_choices, [this](char const* p) { c_main->jsonKey(p); }); addParameter("jsonObject", [this](char const* p) { c_main->jsonObject(p); }); endDict(); // .inspect -beginDict("transform", bindJSON(&Handlers::beginTransform), bindBare(&Handlers::endTransform)); // .transform +beginDict("options", bindJSON(&Handlers::beginOptions), bindBare(&Handlers::endOptions)); // .options +addBare("allowWeakCrypto", [this]() { c_main->allowWeakCrypto(); }); +addBare("deterministicId", [this]() { c_main->deterministicId(); }); +addChoices("keepFilesOpen", yn_choices, [this](char const* p) { c_main->keepFilesOpen(p); }); +addParameter("keepFilesOpenThreshold", [this](char const* p) { c_main->keepFilesOpenThreshold(p); }); +addBare("noWarn", [this]() { c_main->noWarn(); }); +addBare("verbose", [this]() { c_main->verbose(); }); +addBare("warningExit0", [this]() { c_main->warningExit0(); }); +addBare("ignoreXrefStreams", [this]() { c_main->ignoreXrefStreams(); }); +addBare("passwordIsHexKey", [this]() { c_main->passwordIsHexKey(); }); +addChoices("passwordMode", password_mode_choices, [this](char const* p) { c_main->passwordMode(p); }); +addBare("suppressPasswordRecovery", [this]() { c_main->suppressPasswordRecovery(); }); +addBare("suppressRecovery", [this]() { c_main->suppressRecovery(); }); addBare("coalesceContents", [this]() { c_main->coalesceContents(); }); addParameter("compressionLevel", [this](char const* p) { c_main->compressionLevel(p); }); addBare("externalizeInlineImages", [this]() { c_main->externalizeInlineImages(); }); addParameter("iiMinBytes", [this](char const* p) { c_main->iiMinBytes(p); }); addChoices("removeUnreferencedResources", remove_unref_choices, [this](char const* p) { c_main->removeUnreferencedResources(p); }); -endDict(); // .transform -beginDict("modify", bindJSON(&Handlers::beginModify), bindBare(&Handlers::endModify)); // .modify -beginDict("addAttachment", bindJSON(&Handlers::beginModifyAddAttachment), bindBare(&Handlers::endModifyAddAttachment)); // .modify.addAttachment -doSetup("path", bindSetup(&Handlers::setupModifyAddAttachmentPath)); +beginDict("addAttachment", bindJSON(&Handlers::beginOptionsAddAttachment), bindBare(&Handlers::endOptionsAddAttachment)); // .options.addAttachment +doSetup("path", bindSetup(&Handlers::setupOptionsAddAttachmentPath)); addParameter("creationdate", [this](char const* p) { c_att->creationdate(p); }); addParameter("description", [this](char const* p) { c_att->description(p); }); addParameter("filename", [this](char const* p) { c_att->filename(p); }); @@ -135,13 +131,13 @@ addParameter("key", [this](char const* p) { c_att->key(p); }); addParameter("mimetype", [this](char const* p) { c_att->mimetype(p); }); addParameter("moddate", [this](char const* p) { c_att->moddate(p); }); addBare("replace", [this]() { c_att->replace(); }); -endDict(); // .modify.addAttachment +endDict(); // .options.addAttachment addParameter("removeAttachment", [this](char const* p) { c_main->removeAttachment(p); }); -beginDict("copyAttachmentsFrom", bindJSON(&Handlers::beginModifyCopyAttachmentsFrom), bindBare(&Handlers::endModifyCopyAttachmentsFrom)); // .modify.copyAttachmentsFrom -doSetup("path", bindSetup(&Handlers::setupModifyCopyAttachmentsFromPath)); -doSetup("password", bindSetup(&Handlers::setupModifyCopyAttachmentsFromPassword)); +beginDict("copyAttachmentsFrom", bindJSON(&Handlers::beginOptionsCopyAttachmentsFrom), bindBare(&Handlers::endOptionsCopyAttachmentsFrom)); // .options.copyAttachmentsFrom +doSetup("path", bindSetup(&Handlers::setupOptionsCopyAttachmentsFromPath)); +doSetup("password", bindSetup(&Handlers::setupOptionsCopyAttachmentsFromPassword)); addParameter("prefix", [this](char const* p) { c_copy_att->prefix(p); }); -endDict(); // .modify.copyAttachmentsFrom +endDict(); // .options.copyAttachmentsFrom addParameter("collate", [this](char const* p) { c_main->collate(p); }); addChoices("flattenAnnotations", flatten_choices, [this](char const* p) { c_main->flattenAnnotations(p); }); addBare("flattenRotation", [this]() { c_main->flattenRotation(); }); @@ -151,25 +147,25 @@ addParameter("oiMinArea", [this](char const* p) { c_main->oiMinArea(p); }); addParameter("oiMinHeight", [this](char const* p) { c_main->oiMinHeight(p); }); addParameter("oiMinWidth", [this](char const* p) { c_main->oiMinWidth(p); }); addBare("optimizeImages", [this]() { c_main->optimizeImages(); }); -beginDict("pages", bindJSON(&Handlers::beginModifyPages), bindBare(&Handlers::endModifyPages)); // .modify.pages -doSetup("file", bindSetup(&Handlers::setupModifyPagesFile)); -doSetup("password", bindSetup(&Handlers::setupModifyPagesPassword)); -doSetup("range", bindSetup(&Handlers::setupModifyPagesRange)); -endDict(); // .modify.pages +beginDict("pages", bindJSON(&Handlers::beginOptionsPages), bindBare(&Handlers::endOptionsPages)); // .options.pages +doSetup("file", bindSetup(&Handlers::setupOptionsPagesFile)); +doSetup("password", bindSetup(&Handlers::setupOptionsPagesPassword)); +doSetup("range", bindSetup(&Handlers::setupOptionsPagesRange)); +endDict(); // .options.pages addBare("removePageLabels", [this]() { c_main->removePageLabels(); }); addParameter("rotate", [this](char const* p) { c_main->rotate(p); }); -beginDict("overlay", bindJSON(&Handlers::beginModifyOverlay), bindBare(&Handlers::endModifyOverlay)); // .modify.overlay -doSetup("file", bindSetup(&Handlers::setupModifyOverlayFile)); -doSetup("password", bindSetup(&Handlers::setupModifyOverlayPassword)); +beginDict("overlay", bindJSON(&Handlers::beginOptionsOverlay), bindBare(&Handlers::endOptionsOverlay)); // .options.overlay +doSetup("file", bindSetup(&Handlers::setupOptionsOverlayFile)); +doSetup("password", bindSetup(&Handlers::setupOptionsOverlayPassword)); addParameter("from", [this](char const* p) { c_uo->from(p); }); addParameter("repeat", [this](char const* p) { c_uo->repeat(p); }); addParameter("to", [this](char const* p) { c_uo->to(p); }); -endDict(); // .modify.overlay -beginDict("underlay", bindJSON(&Handlers::beginModifyUnderlay), bindBare(&Handlers::endModifyUnderlay)); // .modify.underlay -doSetup("file", bindSetup(&Handlers::setupModifyUnderlayFile)); -doSetup("password", bindSetup(&Handlers::setupModifyUnderlayPassword)); +endDict(); // .options.overlay +beginDict("underlay", bindJSON(&Handlers::beginOptionsUnderlay), bindBare(&Handlers::endOptionsUnderlay)); // .options.underlay +doSetup("file", bindSetup(&Handlers::setupOptionsUnderlayFile)); +doSetup("password", bindSetup(&Handlers::setupOptionsUnderlayPassword)); addParameter("from", [this](char const* p) { c_uo->from(p); }); addParameter("repeat", [this](char const* p) { c_uo->repeat(p); }); addParameter("to", [this](char const* p) { c_uo->to(p); }); -endDict(); // .modify.underlay -endDict(); // .modify +endDict(); // .options.underlay +endDict(); // .options diff --git a/libqpdf/qpdf/auto_job_schema.hh b/libqpdf/qpdf/auto_job_schema.hh index d590c951..94391932 100644 --- a/libqpdf/qpdf/auto_job_schema.hh +++ b/libqpdf/qpdf/auto_job_schema.hh @@ -68,20 +68,6 @@ static constexpr char const* JOB_SCHEMA_DATA = R"({ } } }, - "options": { - "allowWeakCrypto": "allow insecure cryptographic algorithms", - "deterministicId": "generate ID deterministically", - "keepFilesOpen": "manage keeping multiple files open", - "keepFilesOpenThreshold": "set threshold for keepFilesOpen", - "noWarn": "suppress printing of warning messages", - "verbose": "print additional information", - "warningExit0": "exit 0 even with warnings", - "ignoreXrefStreams": "use xref tables rather than streams", - "passwordIsHexKey": "provide hex-encoded encryption key", - "passwordMode": "tweak how qpdf encodes passwords", - "suppressPasswordRecovery": "don't try different password encodings", - "suppressRecovery": "suppress error recovery" - }, "inspect": { "check": "partially check whether PDF is valid", "checkLinearization": "check linearization tables", @@ -107,14 +93,24 @@ static constexpr char const* JOB_SCHEMA_DATA = R"({ null ] }, - "transform": { + "options": { + "allowWeakCrypto": "allow insecure cryptographic algorithms", + "deterministicId": "generate ID deterministically", + "keepFilesOpen": "manage keeping multiple files open", + "keepFilesOpenThreshold": "set threshold for keepFilesOpen", + "noWarn": "suppress printing of warning messages", + "verbose": "print additional information", + "warningExit0": "exit 0 even with warnings", + "ignoreXrefStreams": "use xref tables rather than streams", + "passwordIsHexKey": "provide hex-encoded encryption key", + "passwordMode": "tweak how qpdf encodes passwords", + "suppressPasswordRecovery": "don't try different password encodings", + "suppressRecovery": "suppress error recovery", "coalesceContents": "combine content streams", "compressionLevel": "set compression level for flate", "externalizeInlineImages": "convert inline to regular images", "iiMinBytes": "set minimum size for externalizeInlineImages", - "removeUnreferencedResources": "remove unreferenced page resources" - }, - "modify": { + "removeUnreferencedResources": "remove unreferenced page resources", "addAttachment": [ { "path": "attachment to add",