* Internal save that returns full result including xref offset. * Used by signing to chain incremental updates.
(options: SaveOptions = {})
| 3195 | * Used by signing to chain incremental updates. |
| 3196 | */ |
| 3197 | private saveInternal(options: SaveOptions = {}): { bytes: Uint8Array; xrefOffset: number } { |
| 3198 | // Finalize embedded fonts (creates PDF objects, optionally subsets) |
| 3199 | this.fonts.finalize(options.subsetFonts ?? false); |
| 3200 | |
| 3201 | const wantsIncremental = options.incremental ?? false; |
| 3202 | const blocker = this.canSaveIncrementally(); |
| 3203 | |
| 3204 | // Check if incremental is requested but not possible |
| 3205 | if (wantsIncremental && blocker !== null) { |
| 3206 | this.ctx.registry.addWarning( |
| 3207 | `Incremental save not possible (${blocker}), performing full save`, |
| 3208 | ); |
| 3209 | } |
| 3210 | |
| 3211 | const useIncremental = wantsIncremental && blocker === null; |
| 3212 | |
| 3213 | // If no changes and no security changes, return original bytes |
| 3214 | const hasSecurityChanges = this._pendingSecurity.action !== "none"; |
| 3215 | |
| 3216 | if (!this.hasChanges() && !this.fonts.hasEmbeddedFonts && !hasSecurityChanges) { |
| 3217 | return { |
| 3218 | bytes: this.originalBytes, |
| 3219 | xrefOffset: this.originalXRefOffset, |
| 3220 | }; |
| 3221 | } |
| 3222 | |
| 3223 | // Get root reference from trailer |
| 3224 | const root = this.ctx.info.trailer.getRef("Root"); |
| 3225 | |
| 3226 | if (!root) { |
| 3227 | throw new Error("Document has no catalog (missing /Root in trailer)"); |
| 3228 | } |
| 3229 | |
| 3230 | // Get optional info reference |
| 3231 | const infoRef = this.ctx.info.trailer.getRef("Info"); |
| 3232 | |
| 3233 | // Handle encryption based on pending security state |
| 3234 | let encryptRef: PdfRef | undefined; |
| 3235 | let fileId: [Uint8Array, Uint8Array] | undefined; |
| 3236 | let securityHandler: StandardSecurityHandler | undefined; |
| 3237 | |
| 3238 | if (this._pendingSecurity.action === "encrypt" && this._pendingSecurity.options) { |
| 3239 | // Generate new encryption |
| 3240 | const encryption = generateEncryption(this._pendingSecurity.options); |
| 3241 | |
| 3242 | // Register the encrypt dictionary |
| 3243 | encryptRef = this.ctx.registry.register(encryption.encryptDict); |
| 3244 | fileId = encryption.fileId; |
| 3245 | securityHandler = encryption.securityHandler; |
| 3246 | } else if (this._pendingSecurity.action === "none" && this.isEncrypted) { |
| 3247 | // Re-encrypt with original security - preserve encryption when saving encrypted documents |
| 3248 | const handler = this.ctx.info.securityHandler; |
| 3249 | |
| 3250 | if (handler) { |
| 3251 | // Reconstruct the encrypt dict from the parsed encryption parameters |
| 3252 | // We can't resolve the original encrypt ref because it would try to decrypt it |
| 3253 | // (the encrypt dict is never encrypted in the PDF) |
| 3254 | const reconstructedDict = reconstructEncryptDict(handler.encryption); |
no test coverage detected