Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
159 changes: 121 additions & 38 deletions src/core/annotation.js
Original file line number Diff line number Diff line change
Expand Up @@ -2390,26 +2390,13 @@ class WidgetAnnotation extends Annotation {
dict.set("AP", AP);
AP.set("N", newRef);

const resources = this._getSaveFieldResources(xref),
appearanceDict = new Dict(xref);
appearanceDict.setIfName("Subtype", "Form");
appearanceDict.set("Resources", resources);
const bbox =
rotation % 180 === 0
? [0, 0, this.width, this.height]
: [0, 0, this.height, this.width];
appearanceDict.set("BBox", bbox);

const appearanceStream = new StringStream(appearance, appearanceDict);

const rotationMatrix = this.getRotationMatrix(annotationStorage);
if (rotationMatrix !== IDENTITY_MATRIX) {
// The matrix isn't the identity one.
appearanceDict.set("Matrix", rotationMatrix);
}

changes.put(newRef, {
data: appearanceStream,
data: this.#createAppearanceStream(
appearance,
xref,
annotationStorage,
rotation
),
xfa: null,
needAppearances: false,
});
Expand All @@ -2418,6 +2405,57 @@ class WidgetAnnotation extends Annotation {
dict.set("M", `D:${getModificationDate()}`);
}

#createAppearanceStream(appearance, xref, annotationStorage, rotation) {
const resources = this._getSaveFieldResources(xref),
appearanceDict = new Dict(xref);
appearanceDict.setIfName("Subtype", "Form");
appearanceDict.set("Resources", resources);
const bbox =
rotation % 180 === 0
? [0, 0, this.width, this.height]
: [0, 0, this.height, this.width];
appearanceDict.set("BBox", bbox);

const appearanceStream = new StringStream(appearance, appearanceDict);

const rotationMatrix = this.getRotationMatrix(annotationStorage);
if (rotationMatrix !== IDENTITY_MATRIX) {
appearanceDict.set("Matrix", rotationMatrix);
}
return appearanceStream;
}

/**
* Generate a field appearance for flattening.
* @returns {Promise<BaseStream|null|false>} The generated appearance, `null`
* to use the AP dictionary, or `false` if the font can't encode the value.
*/
async getFlattenedAppearance(evaluator, task, annotationStorage, intent) {
if (!this._hasText) {
return null;
}
const appearance = await this._getAppearance(
evaluator,
task,
intent | RenderingIntentFlag.SAVE,
annotationStorage
);
if (appearance === null) {
return null;
}
if (typeof appearance !== "string") {
return false;
}
const rotation =
annotationStorage?.get(this.data.id)?.rotation ?? this.rotation;
return this.#createAppearanceStream(
appearance,
evaluator.xref,
annotationStorage,
rotation
);
}

async _getAppearance(evaluator, task, intent, annotationStorage) {
if (this.data.password) {
return null;
Expand Down Expand Up @@ -3274,6 +3312,9 @@ class TextWidgetAnnotation extends WidgetAnnotation {
}

class ButtonWidgetAnnotation extends WidgetAnnotation {
// Appearances using the in-memory fallback font.
#generatedAppearances = new Set();

constructor(params) {
super(params);

Expand Down Expand Up @@ -3305,6 +3346,21 @@ class ButtonWidgetAnnotation extends WidgetAnnotation {
return this.data.checkBox || this.data.radioButton;
}

// Return null to use this.appearance, otherwise the checked state.
#getAppearanceValue(annotationStorage) {
const storageEntry = annotationStorage?.get(this.data.id);
const value = storageEntry ? storageEntry.value : null;
if (value === null && this.appearance) {
return null;
}
return (
value ??
(this.data.checkBox
? this.data.fieldValue === this.data.exportValue
: this.data.fieldValue === this.data.buttonValue)
);
}

#getOperatorListForAppearance(
evaluator,
task,
Expand Down Expand Up @@ -3389,36 +3445,60 @@ class ButtonWidgetAnnotation extends WidgetAnnotation {
return checked;
}

let value = null;
let rotation = null;
if (annotationStorage) {
const storageEntry = annotationStorage.get(this.data.id);
value = storageEntry ? storageEntry.value : null;
rotation = storageEntry ? storageEntry.rotation : null;
}

if (value === null && this.appearance) {
// Nothing in the annotationStorage.
// But we've a default appearance so use it.
const value = this.#getAppearanceValue(annotationStorage);
if (value === null) {
return super.getOperatorList(evaluator, task, intent, annotationStorage);
}

// There is no default appearance, `value === null || value === undefined`,
// so use the one derived from the field value.
value ??= this.data.checkBox
? this.data.fieldValue === this.data.exportValue
: this.data.fieldValue === this.data.buttonValue;

return this.#getOperatorListForAppearance(
evaluator,
task,
intent,
annotationStorage,
rotation,
annotationStorage?.get(this.data.id)?.rotation,
value ? this.checkedAppearance : this.uncheckedAppearance
);
}

async getFlattenedAppearance(evaluator, task, annotationStorage) {
if (this.data.pushButton) {
return null;
}
const value = this.#getAppearanceValue(annotationStorage);
if (value === null) {
return null;
}
const appearance = value
? this.checkedAppearance
: this.uncheckedAppearance;
const { xref } = evaluator;
if (!appearance) {
const dict = new Dict(xref);
dict.setIfName("Subtype", "Form");
dict.set("BBox", [0, 0, this.width, this.height]);
return new StringStream("", dict);
}
if (!this.#generatedAppearances.has(appearance)) {
return appearance;
}
// Serialize ZapfDingbats in place of the in-memory fallback font.
const dict = appearance.dict.clone();
const font = new Dict(xref);
font.setIfName("Type", "Font");
font.setIfName("Subtype", "Type1");
font.setIfName("BaseFont", "ZapfDingbats");
const fonts = new Dict(xref);
fonts.set("PdfJsZaDb", font);
const resources = new Dict(xref);
resources.set("Font", fonts);
dict.set("Resources", resources);

appearance.reset();
const content = appearance.getString();
appearance.reset();
return new StringStream(content, dict);
}

async save(evaluator, task, annotationStorage, changes) {
if (this.data.checkBox) {
this._saveCheckbox(evaluator, task, annotationStorage, changes);
Expand Down Expand Up @@ -3634,7 +3714,9 @@ class ButtonWidgetAnnotation extends WidgetAnnotation {

appearanceStreamDict.set("Resources", resources);

return new StringStream(appearance, appearanceStreamDict);
const stream = new StringStream(appearance, appearanceStreamDict);
this.#generatedAppearances.add(stream);
return stream;
}

_getOnStateName(dict) {
Expand Down Expand Up @@ -5978,6 +6060,7 @@ export {
AnnotationBorderStyle,
AnnotationFactory,
getQuadPoints,
getTransformMatrix,
MarkupAnnotation,
PopupAnnotation,
WidgetAnnotation,
Expand Down
37 changes: 37 additions & 0 deletions src/core/core_utils.js
Original file line number Diff line number Diff line change
Expand Up @@ -739,8 +739,45 @@ function getSizeInBytes(x) {
return Math.ceil(Math.ceil(Math.log2(1 + x)) / 8);
}

/** Resolve references from `changes` before falling back to the XRef. */
class ChangesXRefWrapper {
#changes;

#xref;

constructor(changes, xref) {
this.#changes = changes;
this.#xref = xref;
}

getNewTemporaryRef() {
return this.#xref.getNewTemporaryRef();
}

fetch(ref) {
return this.#changes.has(ref)
? this.#changes.get(ref).data
: this.#xref.fetch(ref);
}

fetchIfRef(obj) {
return obj instanceof Ref ? this.fetch(obj) : obj;
}

async fetchAsync(ref) {
return this.#changes.has(ref)
? this.#changes.get(ref).data
: this.#xref.fetchAsync(ref);
}

async fetchIfRefAsync(obj) {
return obj instanceof Ref ? this.fetchAsync(obj) : obj;
}
}

export {
arrayBuffersToBytes,
ChangesXRefWrapper,
codePointIter,
collectActions,
deepCompare,
Expand Down
12 changes: 11 additions & 1 deletion src/core/document.js
Original file line number Diff line number Diff line change
Expand Up @@ -72,6 +72,7 @@ import { Linearization } from "./parser.js";
import { LocalColorSpaceCache } from "./image_utils.js";
import { ObjectLoader } from "./object_loader.js";
import { OperatorList } from "./operator_list.js";
import { PageAnnotationFlattener } from "./flattener.js";
import { PartialEvaluator } from "./evaluator.js";
import { PDFImage } from "./image.js";
import { StreamsSequenceStream } from "./decode_stream.js";
Expand Down Expand Up @@ -445,6 +446,15 @@ class Page {
return Promise.all(promises);
}

async flattenAnnotations(handler, task, options) {
return new PageAnnotationFlattener({
...options,
page: this,
getResources: streamDict =>
this.#getMergedResources(streamDict, RESOURCES_KEYS_OPERATOR_LIST),
}).flatten(handler, task);
}

async loadResources(keys) {
// TODO: add async `#getInheritableProperty` and remove this.
await (this.#resourcesPromise ??= this.pdfManager.ensure(
Expand Down Expand Up @@ -1510,7 +1520,7 @@ class PDFDocument {
}

serializeXfaData(annotationStorage) {
return this.xfaFactory
return this.xfaFactory && annotationStorage
? this.xfaFactory.serializeData(annotationStorage)
: null;
}
Expand Down
35 changes: 1 addition & 34 deletions src/core/editor/print_appearances.js
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@
import { Cmd, Dict, EOF, Ref, RefMap } from "../primitives.js";
import { stringToBytes, warn } from "../../shared/util.js";
import { BaseStream } from "../base_stream.js";
import { ChangesXRefWrapper } from "../core_utils.js";
import { EvaluatorPreprocessor } from "../evaluator.js";
import { Lexer } from "../parser.js";
import { LocalPdfManager } from "../pdf_manager.js";
Expand All @@ -24,40 +25,6 @@ import { Stream } from "../stream.js";
// Drop marked-content operators; their metadata belongs to the generated PDF.
const MARKED_CONTENT_OPS = new Set(["BDC", "BMC", "DP", "EMC", "MP"]);

/** Resolve copied temporary references from `changes`. */
class ChangesXRefWrapper {
#changes;

#xref;

constructor(changes, xref) {
this.#changes = changes;
this.#xref = xref;
}

getNewTemporaryRef() {
return this.#xref.getNewTemporaryRef();
}

fetch(ref) {
return this.#changes.has(ref)
? this.#changes.get(ref).data
: this.#xref.fetch(ref);
}

fetchIfRef(obj) {
return obj instanceof Ref ? this.fetch(obj) : obj;
}

async fetchAsync(ref) {
return this.fetch(ref);
}

async fetchIfRefAsync(obj) {
return this.fetchIfRef(obj);
}
}

/**
* Copy `obj` and its indirect dependencies into the target document.
* @param {*} obj
Expand Down
Loading
Loading