@quillmark/quillify 0.3.0 → 0.4.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/NOTICE +1 -1
- package/README.md +2 -1
- package/assets/qpdf.worker.js +9 -2
- package/dist/api.d.ts +1 -0
- package/dist/assemble.d.ts +1 -1
- package/dist/assemble.js +2 -13
- package/dist/display-list.d.ts +17 -14
- package/dist/display-list.js +159 -120
- package/dist/index.d.ts +1 -1
- package/dist/index.js +1 -0
- package/dist/inventory.js +6 -2
- package/dist/naming.js +2 -0
- package/dist/normalize.js +158 -14
- package/dist/pdfjs.js +3 -2
- package/dist/quillify.js +2 -2
- package/dist/strip.d.ts +11 -6
- package/dist/strip.js +30 -21
- package/dist/types.d.ts +20 -6
- package/dist/types.js +18 -3
- package/dist/xfa-layer.d.ts +4 -4
- package/dist/xfa-layer.js +8 -4
- package/dist/xfa-widgets.js +3 -0
- package/dist/xfa.js +87 -60
- package/package.json +3 -2
package/dist/xfa.js
CHANGED
|
@@ -44,8 +44,9 @@ export async function xfaToAcroForm(bytes, signal) {
|
|
|
44
44
|
for (const span of laid.layer.querySelectorAll('.xfaPageCount'))
|
|
45
45
|
span.textContent = String(pdf.numPages);
|
|
46
46
|
const list = displayList(laid, number);
|
|
47
|
-
for (const
|
|
48
|
-
|
|
47
|
+
for (const item of list.drawn)
|
|
48
|
+
if ('image' in item)
|
|
49
|
+
pixels += item.image.naturalWidth * item.image.naturalHeight;
|
|
49
50
|
if (pixels > MAX_PICTURE_PIXELS)
|
|
50
51
|
throw new Refused({
|
|
51
52
|
code: 'unpaintable',
|
|
@@ -73,7 +74,36 @@ export async function xfaToAcroForm(bytes, signal) {
|
|
|
73
74
|
/** PDF's origin is bottom-left; every primitive arrives top-left. */
|
|
74
75
|
async function paint(page, pageNumber, list, font) {
|
|
75
76
|
const H = list.height;
|
|
76
|
-
for (
|
|
77
|
+
for (const item of list.drawn) {
|
|
78
|
+
if ('text' in item) {
|
|
79
|
+
await drawRun(page, pageNumber, item, H, font);
|
|
80
|
+
continue;
|
|
81
|
+
}
|
|
82
|
+
if ('image' in item) {
|
|
83
|
+
const { x, y, w, h, image } = item;
|
|
84
|
+
// The pixels are read off a canvas of the host page's. The frame's policy
|
|
85
|
+
// governs what its document loads, and the picture is of the page's origin.
|
|
86
|
+
const canvas = document.createElement('canvas');
|
|
87
|
+
canvas.width = image.naturalWidth;
|
|
88
|
+
canvas.height = image.naturalHeight;
|
|
89
|
+
canvas.getContext('2d').drawImage(image, 0, 0);
|
|
90
|
+
const png = canvas.toDataURL('image/png');
|
|
91
|
+
// What a canvas with a side the browser will not make gives.
|
|
92
|
+
if (png === 'data:,')
|
|
93
|
+
throw new Refused({ code: 'unpaintable', page: pageNumber - 1, construct: 'image' });
|
|
94
|
+
page.drawImage(await page.doc.embedPng(png), { x, y: H - y - h, width: w, height: h });
|
|
95
|
+
continue;
|
|
96
|
+
}
|
|
97
|
+
if ('x1' in item) {
|
|
98
|
+
page.drawLine({
|
|
99
|
+
start: { x: item.x1, y: H - item.y1 },
|
|
100
|
+
end: { x: item.x2, y: H - item.y2 },
|
|
101
|
+
thickness: item.width,
|
|
102
|
+
color: rgb(...item.colour)
|
|
103
|
+
});
|
|
104
|
+
continue;
|
|
105
|
+
}
|
|
106
|
+
let box = item;
|
|
77
107
|
if (box.radius) {
|
|
78
108
|
// An SVG rect's outline runs along the middle of its stroke, and each
|
|
79
109
|
// radius stops at half the side it lies along.
|
|
@@ -125,27 +155,6 @@ async function paint(page, pageNumber, list, font) {
|
|
|
125
155
|
if (right)
|
|
126
156
|
seg(box.x + box.w - right.width / 2, box.y, box.x + box.w - right.width / 2, box.y + box.h, right);
|
|
127
157
|
}
|
|
128
|
-
for (const { x, y, w, h, image } of list.pictures) {
|
|
129
|
-
// The pixels are read off a canvas of the host page's. The frame's policy
|
|
130
|
-
// governs what its document loads, and the picture is of the page's origin.
|
|
131
|
-
const canvas = document.createElement('canvas');
|
|
132
|
-
canvas.width = image.naturalWidth;
|
|
133
|
-
canvas.height = image.naturalHeight;
|
|
134
|
-
canvas.getContext('2d').drawImage(image, 0, 0);
|
|
135
|
-
const png = canvas.toDataURL('image/png');
|
|
136
|
-
// What a canvas with a side the browser will not make gives.
|
|
137
|
-
if (png === 'data:,')
|
|
138
|
-
throw new Refused({ code: 'unpaintable', page: pageNumber - 1, construct: 'image' });
|
|
139
|
-
page.drawImage(await page.doc.embedPng(png), { x, y: H - y - h, width: w, height: h });
|
|
140
|
-
}
|
|
141
|
-
for (const line of list.lines)
|
|
142
|
-
page.drawLine({
|
|
143
|
-
start: { x: line.x1, y: H - line.y1 },
|
|
144
|
-
end: { x: line.x2, y: H - line.y2 },
|
|
145
|
-
thickness: line.width,
|
|
146
|
-
color: rgb(...line.colour)
|
|
147
|
-
});
|
|
148
|
-
return drawRuns(page, pageNumber, list.runs, H, font);
|
|
149
158
|
}
|
|
150
159
|
/**
|
|
151
160
|
* The dash and the gap the browser paints along a dashed edge, or nothing for
|
|
@@ -168,53 +177,71 @@ function dashes(width, length) {
|
|
|
168
177
|
const [wide, narrow] = [fit(count), fit(count + 1)];
|
|
169
178
|
return [dash, narrow <= 0 || Math.abs(wide - gap) < Math.abs(narrow - gap) ? wide : narrow];
|
|
170
179
|
}
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
180
|
+
/** The standard fonts that hold what the text fonts lack, in the order a character is looked for. */
|
|
181
|
+
const SYMBOLS = [StandardFonts.Symbol, StandardFonts.ZapfDingbats];
|
|
182
|
+
async function drawRun(page, pageNumber, run, H, get) {
|
|
183
|
+
const font = await get(standard14(run));
|
|
184
|
+
// The standard-14 text fonts encode WinAnsi alone. U+2010 and U+2011 are
|
|
185
|
+
// drawn as its hyphen, and any other character outside it in the first of
|
|
186
|
+
// SYMBOLS that holds it. A soft hyphen is left out: WinAnsi has it as a
|
|
187
|
+
// hyphen, and the browser lays it out at no width and shows nothing. The
|
|
188
|
+
// one it breaks a line at is a hyphen in the run already.
|
|
189
|
+
// Dropping the run would lose a line of the form without anything
|
|
190
|
+
// reporting it.
|
|
191
|
+
// pdf-lib kerns the width it measures and writes no kerning, so the width
|
|
192
|
+
// it draws a run at is the sum of the widths of its characters.
|
|
193
|
+
const parts = [];
|
|
194
|
+
let natural = 0;
|
|
195
|
+
for (const char of run.text.replaceAll('\u00ad', '').replace(/[\u2010\u2011]/g, '-')) {
|
|
196
|
+
const code = char.codePointAt(0);
|
|
197
|
+
let face = font;
|
|
198
|
+
for (const name of SYMBOLS)
|
|
199
|
+
if (!face.getCharacterSet().includes(code))
|
|
200
|
+
face = await get(name);
|
|
201
|
+
if (!face.getCharacterSet().includes(code))
|
|
178
202
|
throw new Refused({
|
|
179
203
|
code: 'unpaintable',
|
|
180
204
|
page: pageNumber - 1,
|
|
181
205
|
construct: 'glyph',
|
|
182
206
|
detail: run.text.slice(0, 40)
|
|
183
207
|
});
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
+
const last = parts.at(-1);
|
|
209
|
+
if (last?.font === face)
|
|
210
|
+
last.text += char;
|
|
211
|
+
else
|
|
212
|
+
parts.push({ font: face, text: char, at: natural });
|
|
213
|
+
natural += face.widthOfTextAtSize(char, run.size) + run.letterSpacing;
|
|
214
|
+
}
|
|
215
|
+
// The standard-14 substitute's advances differ from the embedded face's.
|
|
216
|
+
// Letter spacing goes in first, since it is a real property of the run;
|
|
217
|
+
// horizontal scaling then pins the whole run to the width the browser
|
|
218
|
+
// measured, so the error stays inside the run instead of drifting across
|
|
219
|
+
// the line.
|
|
220
|
+
const spaced = natural - run.letterSpacing;
|
|
221
|
+
const scale = spaced > 0 && run.width > 0 ? Math.max(50, Math.min(200, (100 * run.width) / spaced)) : 100;
|
|
222
|
+
const scaled = Math.abs(scale - 100) > 3;
|
|
223
|
+
// Both are text-state parameters, so they persist across drawText's own
|
|
224
|
+
// BT/ET and have to be put back.
|
|
225
|
+
if (run.letterSpacing)
|
|
226
|
+
page.pushOperators(PDFOperator.of(PDFOperatorNames.SetCharacterSpacing, [String(+run.letterSpacing.toFixed(4))]));
|
|
227
|
+
if (scaled)
|
|
228
|
+
page.pushOperators(PDFOperator.of(PDFOperatorNames.SetTextHorizontalScaling, [String(+scale.toFixed(3))]));
|
|
229
|
+
const angle = (Math.PI / 2) * (run.turn ?? 0);
|
|
230
|
+
for (const part of parts) {
|
|
231
|
+
const along = scaled ? (part.at * scale) / 100 : part.at;
|
|
232
|
+
page.drawText(part.text, {
|
|
233
|
+
x: run.x + along * Math.cos(angle),
|
|
234
|
+
y: H - run.baseline + along * Math.sin(angle),
|
|
208
235
|
size: run.size,
|
|
209
|
-
font,
|
|
236
|
+
font: part.font,
|
|
210
237
|
color: rgb(...run.colour),
|
|
211
238
|
rotate: degrees(90 * (run.turn ?? 0))
|
|
212
239
|
});
|
|
213
|
-
if (scaled)
|
|
214
|
-
page.pushOperators(PDFOperator.of(PDFOperatorNames.SetTextHorizontalScaling, ['100']));
|
|
215
|
-
if (run.letterSpacing)
|
|
216
|
-
page.pushOperators(PDFOperator.of(PDFOperatorNames.SetCharacterSpacing, ['0']));
|
|
217
240
|
}
|
|
241
|
+
if (scaled)
|
|
242
|
+
page.pushOperators(PDFOperator.of(PDFOperatorNames.SetTextHorizontalScaling, ['100']));
|
|
243
|
+
if (run.letterSpacing)
|
|
244
|
+
page.pushOperators(PDFOperator.of(PDFOperatorNames.SetCharacterSpacing, ['0']));
|
|
218
245
|
}
|
|
219
246
|
function standard14(run) {
|
|
220
247
|
const family = run.family.toLowerCase();
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@quillmark/quillify",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.4.0",
|
|
4
4
|
"description": "Turn a published PDF form (AcroForm or XFA) into a Quillmark quill, in the browser.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"license": "Apache-2.0",
|
|
@@ -48,9 +48,10 @@
|
|
|
48
48
|
},
|
|
49
49
|
"devDependencies": {
|
|
50
50
|
"@neslinesli93/qpdf-wasm": "0.3.0",
|
|
51
|
-
"@quillmark/wasm": "0.
|
|
51
|
+
"@quillmark/wasm": "0.125.0",
|
|
52
52
|
"@types/node": "24.19.0",
|
|
53
53
|
"@vitest/browser-playwright": "5.0.2",
|
|
54
|
+
"fast-check": "4.10.2",
|
|
54
55
|
"playwright": "1.63.0",
|
|
55
56
|
"prettier": "3.9.9",
|
|
56
57
|
"typescript": "6.0.3",
|