Skip to content

Commit 563d74e

Browse files
authored
refactor(pdfkit): sync acroform mixin and sRGB profile with upstream (#3481)
1 parent 6cc0ae2 commit 563d74e

9 files changed

Lines changed: 286 additions & 203 deletions

File tree

.changeset/purple-donkeys-clap.md

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
---
2+
'@react-pdf/pdfkit': patch
3+
'@react-pdf/render': patch
4+
---
5+
6+
Sync pdfkit acroform mixin with upstream. Form field dictionaries no longer leak internal options like `fontSize` into the PDF.

packages/pdfkit/src/mixins/acroform.js

Lines changed: 146 additions & 183 deletions
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,123 @@ const FORMAT_DEFAULT = {
4040
},
4141
};
4242

43+
function mapTypeAndFlags(type, userOptions, pdfObject) {
44+
let flags = userOptions.Ff ?? 0;
45+
46+
if (type === 'text') {
47+
pdfObject.FT = 'Tx';
48+
} else if (type === 'pushButton') {
49+
pdfObject.FT = 'Btn';
50+
flags |= FIELD_FLAGS.pushButton;
51+
} else if (type === 'radioButton') {
52+
pdfObject.FT = 'Btn';
53+
flags |= FIELD_FLAGS.radioButton;
54+
} else if (type === 'checkbox') {
55+
pdfObject.FT = 'Btn';
56+
} else if (type === 'combo') {
57+
pdfObject.FT = 'Ch';
58+
flags |= FIELD_FLAGS.combo;
59+
} else if (type === 'list') {
60+
pdfObject.FT = 'Ch';
61+
} else if (type) {
62+
throw new Error(`Invalid form annotation type '${type}'`);
63+
}
64+
65+
Object.keys(userOptions).forEach((key) => {
66+
if (FIELD_FLAGS[key] && userOptions[key]) {
67+
flags |= FIELD_FLAGS[key];
68+
}
69+
});
70+
71+
if (flags !== 0) {
72+
pdfObject.Ff = flags;
73+
}
74+
}
75+
76+
function mapJustify(userOptions, pdfObject) {
77+
const result = FIELD_JUSTIFY[userOptions.align];
78+
if (typeof result === 'number' && result !== 0) {
79+
pdfObject.Q = result;
80+
}
81+
}
82+
83+
function mapStrings(options, pdfObject) {
84+
if (Array.isArray(options.select) && options.select.length) {
85+
pdfObject.Opt = options.select.map((s) => {
86+
if (typeof s === 'string') {
87+
return new String(s);
88+
}
89+
return s;
90+
});
91+
}
92+
93+
Object.keys(VALUE_MAP).forEach((key) => {
94+
if (key in options) {
95+
const value = options[key] ?? '';
96+
pdfObject[VALUE_MAP[key]] =
97+
typeof value === 'string' ? new String(value) : value;
98+
}
99+
});
100+
101+
if (options.MK?.CA) {
102+
pdfObject.MK = { CA: new String(options.MK.CA) };
103+
}
104+
105+
if (options.label) {
106+
pdfObject.MK = options.MK ?? {};
107+
pdfObject.MK.CA = new String(options.label);
108+
}
109+
}
110+
111+
function mapFormat(options, pdfObject) {
112+
const f = options.format;
113+
if (f?.type) {
114+
let fnKeystroke;
115+
let fnFormat;
116+
let params = '';
117+
if (FORMAT_SPECIAL[f.type] !== undefined) {
118+
fnKeystroke = `AFSpecial_Keystroke`;
119+
fnFormat = `AFSpecial_Format`;
120+
params = FORMAT_SPECIAL[f.type];
121+
} else {
122+
let format = f.type.charAt(0).toUpperCase() + f.type.slice(1);
123+
fnKeystroke = `AF${format}_Keystroke`;
124+
fnFormat = `AF${format}_Format`;
125+
126+
if (f.type === 'date') {
127+
fnKeystroke += 'Ex';
128+
params = String(f.param);
129+
} else if (f.type === 'time') {
130+
params = String(f.param);
131+
} else if (f.type === 'number') {
132+
let p = Object.assign({}, FORMAT_DEFAULT.number, f);
133+
params = String(
134+
[
135+
String(p.nDec),
136+
p.sepComma ? '0' : '1',
137+
'"' + p.negStyle + '"',
138+
'null',
139+
'"' + p.currency + '"',
140+
String(p.currencyPrepend),
141+
].join(','),
142+
);
143+
} else if (f.type === 'percent') {
144+
let p = Object.assign({}, FORMAT_DEFAULT.percent, f);
145+
params = String([String(p.nDec), p.sepComma ? '0' : '1'].join(','));
146+
}
147+
}
148+
pdfObject.AA = options.AA ?? {};
149+
pdfObject.AA.K = {
150+
S: 'JavaScript',
151+
JS: new String(`${fnKeystroke}(${params});`),
152+
};
153+
pdfObject.AA.F = {
154+
S: 'JavaScript',
155+
JS: new String(`${fnFormat}(${params});`),
156+
};
157+
}
158+
}
159+
43160
export default {
44161
/**
45162
* Must call if adding AcroForms to a document. Must also call font() before
@@ -184,209 +301,55 @@ export default {
184301
'Call document.initForm() method before adding form elements to document',
185302
);
186303
}
187-
let opts = Object.assign({}, options);
188-
if (type !== null) {
189-
opts = this._resolveType(type, options);
190-
}
191-
opts = this._resolveFlags(opts);
192-
opts = this._resolveJustify(opts);
193-
opts = this._resolveFont(opts);
194-
opts = this._resolveStrings(opts);
195-
opts = this._resolveColors(opts);
196-
opts = this._resolveFormat(opts);
197-
opts.T = new String(name);
198-
if (opts.parent) {
199-
opts.Parent = opts.parent;
200-
delete opts.parent;
201-
}
202-
return opts;
203-
},
304+
const pdfObject = {};
204305

205-
_resolveType(type, opts) {
206-
if (type === 'text') {
207-
opts.FT = 'Tx';
208-
} else if (type === 'pushButton') {
209-
opts.FT = 'Btn';
210-
opts.pushButton = true;
211-
} else if (type === 'radioButton') {
212-
opts.FT = 'Btn';
213-
opts.radioButton = true;
214-
} else if (type === 'checkbox') {
215-
opts.FT = 'Btn';
216-
} else if (type === 'combo') {
217-
opts.FT = 'Ch';
218-
opts.combo = true;
219-
} else if (type === 'list') {
220-
opts.FT = 'Ch';
221-
} else {
222-
throw new Error(`Invalid form annotation type '${type}'`);
223-
}
224-
return opts;
225-
},
306+
mapTypeAndFlags(type, options, pdfObject);
307+
mapJustify(options, pdfObject);
308+
this._mapFont(options, pdfObject);
309+
mapStrings(options, pdfObject);
310+
this._mapColors(options, pdfObject);
311+
mapFormat(options, pdfObject);
226312

227-
_resolveFormat(opts) {
228-
const f = opts.format;
229-
if (f && f.type) {
230-
let fnKeystroke;
231-
let fnFormat;
232-
let params = '';
233-
if (FORMAT_SPECIAL[f.type] !== undefined) {
234-
fnKeystroke = `AFSpecial_Keystroke`;
235-
fnFormat = `AFSpecial_Format`;
236-
params = FORMAT_SPECIAL[f.type];
237-
} else {
238-
let format = f.type.charAt(0).toUpperCase() + f.type.slice(1);
239-
fnKeystroke = `AF${format}_Keystroke`;
240-
fnFormat = `AF${format}_Format`;
241-
242-
if (f.type === 'date') {
243-
fnKeystroke += 'Ex';
244-
params = String(f.param);
245-
} else if (f.type === 'time') {
246-
params = String(f.param);
247-
} else if (f.type === 'number') {
248-
let p = Object.assign({}, FORMAT_DEFAULT.number, f);
249-
params = String(
250-
[
251-
String(p.nDec),
252-
p.sepComma ? '0' : '1',
253-
'"' + p.negStyle + '"',
254-
'null',
255-
'"' + p.currency + '"',
256-
String(p.currencyPrepend),
257-
].join(','),
258-
);
259-
} else if (f.type === 'percent') {
260-
let p = Object.assign({}, FORMAT_DEFAULT.percent, f);
261-
params = String([String(p.nDec), p.sepComma ? '0' : '1'].join(','));
262-
}
263-
}
264-
opts.AA = opts.AA ? opts.AA : {};
265-
opts.AA.K = {
266-
S: 'JavaScript',
267-
JS: new String(`${fnKeystroke}(${params});`),
268-
};
269-
opts.AA.F = {
270-
S: 'JavaScript',
271-
JS: new String(`${fnFormat}(${params});`),
272-
};
273-
}
274-
delete opts.format;
275-
return opts;
276-
},
313+
pdfObject.T = new String(name);
277314

278-
_resolveColors(opts) {
279-
let color = this._normalizeColor(opts.backgroundColor);
280-
if (color) {
281-
if (!opts.MK) {
282-
opts.MK = {};
283-
}
284-
opts.MK.BG = color;
285-
}
286-
color = this._normalizeColor(opts.borderColor);
287-
if (color) {
288-
if (!opts.MK) {
289-
opts.MK = {};
290-
}
291-
opts.MK.BC = color;
315+
if (options.parent) {
316+
pdfObject.Parent = options.parent;
292317
}
293-
delete opts.backgroundColor;
294-
delete opts.borderColor;
295-
return opts;
318+
return pdfObject;
296319
},
297320

298-
_resolveFlags(options) {
299-
let result = 0;
300-
Object.keys(options).forEach((key) => {
301-
if (FIELD_FLAGS[key]) {
302-
if (options[key]) {
303-
result |= FIELD_FLAGS[key];
304-
}
305-
delete options[key];
306-
}
307-
});
308-
if (result !== 0) {
309-
options.Ff = options.Ff ? options.Ff : 0;
310-
options.Ff |= result;
321+
_mapColors(options, pdfObject) {
322+
let color = this._normalizeColor(options.backgroundColor);
323+
if (color) {
324+
pdfObject.MK = pdfObject.MK ? pdfObject.MK : {};
325+
pdfObject.MK.BG = color;
311326
}
312-
return options;
313-
},
314327

315-
_resolveJustify(options) {
316-
let result = 0;
317-
if (options.align !== undefined) {
318-
if (typeof FIELD_JUSTIFY[options.align] === 'number') {
319-
result = FIELD_JUSTIFY[options.align];
328+
color = this._normalizeColor(options.borderColor);
329+
if (color) {
330+
if (!pdfObject.MK) {
331+
pdfObject.MK = {};
320332
}
321-
delete options.align;
322-
}
323-
if (result !== 0) {
324-
options.Q = result; // default
333+
pdfObject.MK.BC = color;
325334
}
326-
return options;
327335
},
328336

329-
_resolveFont(options) {
337+
_mapFont(options, pdfObject) {
338+
const { _acroform, _font } = this;
330339
// add current font to document-level AcroForm dict if necessary
331-
if (this._acroform.fonts[this._font.id] == null) {
332-
this._acroform.fonts[this._font.id] = this._font.ref();
340+
if (_acroform.fonts[_font.id] == null) {
341+
_acroform.fonts[_font.id] = _font.ref();
333342
}
334343

335344
// add current font to field's resource dict (RD) if not the default acroform font
336-
if (this._acroform.defaultFont !== this._font.name) {
337-
options.DR = { Font: {} };
345+
if (_acroform.defaultFont !== _font.name) {
346+
pdfObject.DR = { Font: {} };
338347

339348
// Get the fontSize option. If not set use auto sizing
340349
const fontSize = options.fontSize || 0;
341350

342-
options.DR.Font[this._font.id] = this._font.ref();
343-
options.DA = new String(`/${this._font.id} ${fontSize} Tf 0 g`);
344-
}
345-
return options;
346-
},
347-
348-
_resolveStrings(options) {
349-
let select = [];
350-
function appendChoices(a) {
351-
if (Array.isArray(a)) {
352-
for (let idx = 0; idx < a.length; idx++) {
353-
if (typeof a[idx] === 'string') {
354-
select.push(new String(a[idx]));
355-
} else {
356-
select.push(a[idx]);
357-
}
358-
}
359-
}
360-
}
361-
appendChoices(options.Opt);
362-
if (options.select) {
363-
appendChoices(options.select);
364-
delete options.select;
365-
}
366-
if (select.length) {
367-
options.Opt = select;
368-
}
369-
370-
Object.keys(VALUE_MAP).forEach((key) => {
371-
if (options[key] !== undefined) {
372-
options[VALUE_MAP[key]] = options[key];
373-
delete options[key];
374-
}
375-
});
376-
['V', 'DV'].forEach((key) => {
377-
if (typeof options[key] === 'string') {
378-
options[key] = new String(options[key]);
379-
}
380-
});
381-
382-
if (options.MK && options.MK.CA) {
383-
options.MK.CA = new String(options.MK.CA);
384-
}
385-
if (options.label) {
386-
options.MK = options.MK ? options.MK : {};
387-
options.MK.CA = new String(options.label);
388-
delete options.label;
351+
pdfObject.DR.Font[_font.id] = _font.ref();
352+
pdfObject.DA = new String(`/${_font.id} ${fontSize} Tf 0 g`);
389353
}
390-
return options;
391354
},
392355
};
2.95 KB
Binary file not shown.

0 commit comments

Comments
 (0)