@embedpdf/web 3.0.0-next.15 → 3.0.0-next.16
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/dist/index.cjs +404 -32
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +172 -36
- package/dist/index.d.ts +172 -36
- package/dist/index.js +398 -31
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
package/dist/index.cjs
CHANGED
|
@@ -105,6 +105,263 @@ function browserClock() {
|
|
|
105
105
|
return clock;
|
|
106
106
|
}
|
|
107
107
|
//#endregion
|
|
108
|
+
//#region src/printer.ts
|
|
109
|
+
/** The attribute that marks the sheets printed from the page itself. */
|
|
110
|
+
const PRINT_ROOT = "data-epdf-print";
|
|
111
|
+
/** The browser's printer: it prints PDFs where the browser's own PDF viewer does. */
|
|
112
|
+
function browserPrinter() {
|
|
113
|
+
const printer = {
|
|
114
|
+
needsTap: printsOnlyFromATap(),
|
|
115
|
+
stage: stageSheets
|
|
116
|
+
};
|
|
117
|
+
if (!printsPdfFiles()) return printer;
|
|
118
|
+
let removePrinted = null;
|
|
119
|
+
return {
|
|
120
|
+
...printer,
|
|
121
|
+
async stagePdf(pdf, { name, signal }) {
|
|
122
|
+
removePrinted?.();
|
|
123
|
+
removePrinted = null;
|
|
124
|
+
const frame = await loadPdfFrame(pdf, name, signal);
|
|
125
|
+
let printed = false;
|
|
126
|
+
return {
|
|
127
|
+
async print() {
|
|
128
|
+
frame.view.focus();
|
|
129
|
+
frame.view.print();
|
|
130
|
+
printed = true;
|
|
131
|
+
},
|
|
132
|
+
dispose() {
|
|
133
|
+
if (printed) removePrinted = frame.remove;
|
|
134
|
+
else frame.remove();
|
|
135
|
+
}
|
|
136
|
+
};
|
|
137
|
+
}
|
|
138
|
+
};
|
|
139
|
+
}
|
|
140
|
+
/** The sheets staged where this browser prints them: from the page on iOS, else a frame. */
|
|
141
|
+
async function stageSheets(sheets, { name, signal }) {
|
|
142
|
+
const urls = sheets.map((sheet) => URL.createObjectURL(sheet.image));
|
|
143
|
+
const revoke = () => urls.forEach((url) => URL.revokeObjectURL(url));
|
|
144
|
+
try {
|
|
145
|
+
return printsFramesAsPictures() ? await stageInPage(sheets, urls, name, revoke, signal) : await stageInFrame(sheets, urls, name, revoke, signal);
|
|
146
|
+
} catch (error) {
|
|
147
|
+
revoke();
|
|
148
|
+
throw error;
|
|
149
|
+
}
|
|
150
|
+
}
|
|
151
|
+
/** `pdf` loaded in a hidden frame of its own, where the browser's PDF viewer shows it. */
|
|
152
|
+
async function loadPdfFrame(pdf, name, signal) {
|
|
153
|
+
const url = URL.createObjectURL(new Blob([pdf], { type: "application/pdf" }));
|
|
154
|
+
const frame = hiddenFrame();
|
|
155
|
+
frame.title = name;
|
|
156
|
+
frame.src = url;
|
|
157
|
+
const remove = () => {
|
|
158
|
+
frame.remove();
|
|
159
|
+
URL.revokeObjectURL(url);
|
|
160
|
+
};
|
|
161
|
+
const loaded = untilEvent(frame, "load", signal);
|
|
162
|
+
document.body.appendChild(frame);
|
|
163
|
+
try {
|
|
164
|
+
await loaded;
|
|
165
|
+
const view = frame.contentWindow;
|
|
166
|
+
if (!view) throw new Error("the print frame did not load");
|
|
167
|
+
return {
|
|
168
|
+
view,
|
|
169
|
+
remove
|
|
170
|
+
};
|
|
171
|
+
} catch (error) {
|
|
172
|
+
remove();
|
|
173
|
+
throw error;
|
|
174
|
+
}
|
|
175
|
+
}
|
|
176
|
+
/**
|
|
177
|
+
* How the sheets print: one named `@page` per page size, and a class per size that puts a sheet
|
|
178
|
+
* on that page, its picture fitted inside. A browser that prints on other paper keeps each sheet
|
|
179
|
+
* on one page: a sheet is never taller or wider than the page it prints on. That cap is a
|
|
180
|
+
* percentage of the page (`html` and `body` are the page's height): Safari resolves `vw` and
|
|
181
|
+
* `vh` against the screen even when it prints, so on a phone they would shrink every sheet to
|
|
182
|
+
* the phone's width. `sizes` is each sheet's class.
|
|
183
|
+
*/
|
|
184
|
+
function printSheetsStyle(sheets) {
|
|
185
|
+
const named = [];
|
|
186
|
+
const sizes = sheets.map((sheet) => {
|
|
187
|
+
const index = named.findIndex((other) => other.widthPt === sheet.widthPt && other.heightPt === sheet.heightPt);
|
|
188
|
+
return `epdf-sheet-${index >= 0 ? index : named.push(sheet) - 1}`;
|
|
189
|
+
});
|
|
190
|
+
return {
|
|
191
|
+
css: [
|
|
192
|
+
"@page { margin: 0; }",
|
|
193
|
+
...named.flatMap(({ widthPt, heightPt }, index) => [`@page epdf-sheet-${index} { size: ${widthPt}pt ${heightPt}pt; margin: 0; }`, `.epdf-sheet-${index} { page: epdf-sheet-${index}; width: ${widthPt}pt; height: ${heightPt}pt; }`]),
|
|
194
|
+
".epdf-print-sheet { break-after: page; break-inside: avoid; max-width: 100%; max-height: 100%; overflow: hidden; }",
|
|
195
|
+
".epdf-print-sheet:last-child { break-after: auto; }",
|
|
196
|
+
".epdf-print-sheet img { display: block; width: 100%; height: 100%; object-fit: contain; }"
|
|
197
|
+
].join("\n"),
|
|
198
|
+
sizes
|
|
199
|
+
};
|
|
200
|
+
}
|
|
201
|
+
/** The sheets, made in `owner` (the frame's document or the page's): one per page, its picture inside. */
|
|
202
|
+
function sheetElements(owner, urls, sizes) {
|
|
203
|
+
return urls.map((url, index) => {
|
|
204
|
+
const sheet = owner.createElement("div");
|
|
205
|
+
sheet.className = `epdf-print-sheet ${sizes[index]}`;
|
|
206
|
+
const image = owner.createElement("img");
|
|
207
|
+
image.alt = "";
|
|
208
|
+
image.src = url;
|
|
209
|
+
sheet.append(image);
|
|
210
|
+
return sheet;
|
|
211
|
+
});
|
|
212
|
+
}
|
|
213
|
+
/** A frame's document before the sheets go in: nothing of the print job's own is in it. */
|
|
214
|
+
const FRAME_DOCUMENT = "<!doctype html><html><head><meta charset=\"utf-8\"></head><body></body></html>";
|
|
215
|
+
/** A frame laid out but out of sight: a frame that is not laid out may never load. */
|
|
216
|
+
function hiddenFrame() {
|
|
217
|
+
const frame = document.createElement("iframe");
|
|
218
|
+
frame.setAttribute("aria-hidden", "true");
|
|
219
|
+
frame.tabIndex = -1;
|
|
220
|
+
Object.assign(frame.style, {
|
|
221
|
+
position: "fixed",
|
|
222
|
+
right: "0",
|
|
223
|
+
bottom: "0",
|
|
224
|
+
width: "1px",
|
|
225
|
+
height: "1px",
|
|
226
|
+
border: "0",
|
|
227
|
+
opacity: "0",
|
|
228
|
+
pointerEvents: "none"
|
|
229
|
+
});
|
|
230
|
+
return frame;
|
|
231
|
+
}
|
|
232
|
+
/** The sheets in a hidden frame of their own, so nothing of the host page prints with them. */
|
|
233
|
+
async function stageInFrame(sheets, urls, name, revoke, signal) {
|
|
234
|
+
const { css, sizes } = printSheetsStyle(sheets);
|
|
235
|
+
const frame = hiddenFrame();
|
|
236
|
+
frame.srcdoc = FRAME_DOCUMENT;
|
|
237
|
+
const loaded = untilEvent(frame, "load", signal);
|
|
238
|
+
document.body.appendChild(frame);
|
|
239
|
+
try {
|
|
240
|
+
await loaded;
|
|
241
|
+
const view = frame.contentWindow;
|
|
242
|
+
const frameDocument = frame.contentDocument;
|
|
243
|
+
if (!view || !frameDocument) throw new Error("the print frame did not load");
|
|
244
|
+
frameDocument.title = name;
|
|
245
|
+
const style = frameDocument.createElement("style");
|
|
246
|
+
style.textContent = `html, body { margin: 0; padding: 0; height: 100%; }\n${css}`;
|
|
247
|
+
frameDocument.head.append(style);
|
|
248
|
+
frameDocument.body.append(...sheetElements(frameDocument, urls, sizes));
|
|
249
|
+
await decodeAll(Array.from(frameDocument.querySelectorAll("img")), signal);
|
|
250
|
+
return {
|
|
251
|
+
print: () => printFrom(view),
|
|
252
|
+
dispose: () => {
|
|
253
|
+
frame.remove();
|
|
254
|
+
revoke();
|
|
255
|
+
}
|
|
256
|
+
};
|
|
257
|
+
} catch (error) {
|
|
258
|
+
frame.remove();
|
|
259
|
+
throw error;
|
|
260
|
+
}
|
|
261
|
+
}
|
|
262
|
+
/**
|
|
263
|
+
* The sheets in the page itself, shown only when it prints, and everything else hidden then.
|
|
264
|
+
* While it prints, `html` and `body` are plain blocks the page's height, whatever the host page
|
|
265
|
+
* makes them (an app often keeps its body to the window, which would clip the sheets after the
|
|
266
|
+
* first). The page's title is the print job's name while it prints.
|
|
267
|
+
*/
|
|
268
|
+
async function stageInPage(sheets, urls, name, revoke, signal) {
|
|
269
|
+
const { css, sizes } = printSheetsStyle(sheets);
|
|
270
|
+
const style = document.createElement("style");
|
|
271
|
+
style.textContent = `${css}\n@media screen { [${PRINT_ROOT}] { display: none !important; } }\n@media print { html, body { display: block !important; height: 100% !important; margin: 0 !important; padding: 0 !important; overflow: visible !important; } body > :not([${PRINT_ROOT}]) { display: none !important; } [${PRINT_ROOT}] { height: 100%; } }`;
|
|
272
|
+
const root = document.createElement("div");
|
|
273
|
+
root.setAttribute(PRINT_ROOT, "");
|
|
274
|
+
root.append(...sheetElements(document, urls, sizes));
|
|
275
|
+
document.head.appendChild(style);
|
|
276
|
+
document.body.appendChild(root);
|
|
277
|
+
const remove = () => {
|
|
278
|
+
root.remove();
|
|
279
|
+
style.remove();
|
|
280
|
+
};
|
|
281
|
+
try {
|
|
282
|
+
await decodeAll(Array.from(root.querySelectorAll("img")), signal);
|
|
283
|
+
} catch (error) {
|
|
284
|
+
remove();
|
|
285
|
+
throw error;
|
|
286
|
+
}
|
|
287
|
+
return {
|
|
288
|
+
async print() {
|
|
289
|
+
const title = document.title;
|
|
290
|
+
document.title = name;
|
|
291
|
+
try {
|
|
292
|
+
await printFrom(window);
|
|
293
|
+
} finally {
|
|
294
|
+
document.title = title;
|
|
295
|
+
}
|
|
296
|
+
},
|
|
297
|
+
dispose: () => {
|
|
298
|
+
remove();
|
|
299
|
+
revoke();
|
|
300
|
+
}
|
|
301
|
+
};
|
|
302
|
+
}
|
|
303
|
+
/** Open `view`'s print dialog; resolves when the browser says printing finished. */
|
|
304
|
+
function printFrom(view) {
|
|
305
|
+
return new Promise((resolve, reject) => {
|
|
306
|
+
view.addEventListener("afterprint", () => resolve(), { once: true });
|
|
307
|
+
try {
|
|
308
|
+
view.focus();
|
|
309
|
+
view.print();
|
|
310
|
+
} catch (error) {
|
|
311
|
+
reject(error);
|
|
312
|
+
}
|
|
313
|
+
});
|
|
314
|
+
}
|
|
315
|
+
/** Wait until every picture is decoded, so the dialog never shows an empty sheet. */
|
|
316
|
+
async function decodeAll(images, signal) {
|
|
317
|
+
await untilSettled(Promise.all(images.map((image) => image.decode())), signal);
|
|
318
|
+
}
|
|
319
|
+
function untilEvent(target, type, signal) {
|
|
320
|
+
return untilSettled(new Promise((resolve) => target.addEventListener(type, () => resolve(), { once: true })), signal);
|
|
321
|
+
}
|
|
322
|
+
/** `work`, or the signal's reason as soon as it aborts. */
|
|
323
|
+
function untilSettled(work, signal) {
|
|
324
|
+
if (!signal) return work;
|
|
325
|
+
if (signal.aborted) return Promise.reject(signal.reason);
|
|
326
|
+
return new Promise((resolve, reject) => {
|
|
327
|
+
const onAbort = () => reject(signal.reason);
|
|
328
|
+
signal.addEventListener("abort", onAbort, { once: true });
|
|
329
|
+
work.then((value) => {
|
|
330
|
+
signal.removeEventListener("abort", onAbort);
|
|
331
|
+
resolve(value);
|
|
332
|
+
}, (error) => {
|
|
333
|
+
signal.removeEventListener("abort", onAbort);
|
|
334
|
+
reject(error);
|
|
335
|
+
});
|
|
336
|
+
});
|
|
337
|
+
}
|
|
338
|
+
/**
|
|
339
|
+
* Whether this browser prints a PDF file from a frame: its own PDF viewer is on (Chrome set to
|
|
340
|
+
* download PDFs has none, and would download the copy instead), and it is not iOS, which reports
|
|
341
|
+
* a viewer but prints a framed PDF as a picture of its first page, or Android.
|
|
342
|
+
*/
|
|
343
|
+
function printsPdfFiles() {
|
|
344
|
+
if (typeof navigator === "undefined" || navigator.pdfViewerEnabled !== true) return false;
|
|
345
|
+
return !printsFramesAsPictures() && !/Android/i.test(navigator.userAgent);
|
|
346
|
+
}
|
|
347
|
+
/**
|
|
348
|
+
* Whether this browser prints only while a tap or click is being handled: Safari's engine, whose
|
|
349
|
+
* vendor is Apple. On iOS a print that waited shows "This website has been blocked from
|
|
350
|
+
* automatically printing"; Safari on the Mac opens its print dialog, but with blank pages.
|
|
351
|
+
*/
|
|
352
|
+
function printsOnlyFromATap() {
|
|
353
|
+
if (typeof navigator === "undefined") return false;
|
|
354
|
+
return navigator.vendor === "Apple Computer, Inc." || printsFramesAsPictures();
|
|
355
|
+
}
|
|
356
|
+
/**
|
|
357
|
+
* Whether this browser prints a frame as a picture of the page around it: every browser on
|
|
358
|
+
* iOS and iPadOS (all use Safari's engine; iPadOS presents itself as a Mac with touch).
|
|
359
|
+
*/
|
|
360
|
+
function printsFramesAsPictures() {
|
|
361
|
+
if (typeof navigator === "undefined") return false;
|
|
362
|
+
return /iPad|iPhone|iPod/.test(navigator.userAgent) || navigator.userAgent.includes("Macintosh") && navigator.maxTouchPoints > 1;
|
|
363
|
+
}
|
|
364
|
+
//#endregion
|
|
108
365
|
//#region src/css-text.ts
|
|
109
366
|
/**
|
|
110
367
|
* Style records as CSS text. This package hands styles out as records with
|
|
@@ -3213,8 +3470,54 @@ function chromeInPixels(nodes, page) {
|
|
|
3213
3470
|
});
|
|
3214
3471
|
}
|
|
3215
3472
|
//#endregion
|
|
3473
|
+
//#region src/legible-text.ts
|
|
3474
|
+
/**
|
|
3475
|
+
* Editors laid out at a legible size and drawn at the page's. iOS Safari zooms the page in when
|
|
3476
|
+
* a field takes focus with text under 16 CSS pixels, to 16 over that size, and it reads the
|
|
3477
|
+
* field's own computed font size: a transform doesn't change that size (CSS `zoom` does). Text
|
|
3478
|
+
* on a page is drawn at the page's scale, so a form field or a text box on a phone often has
|
|
3479
|
+
* smaller text. Such an editor is laid out {@link legibleScaleOf} times bigger (its text, lines
|
|
3480
|
+
* and box alike), and a `scale()` from its top-left corner draws it back at the page's size: the
|
|
3481
|
+
* same picture, with text iOS reads as legible. Every other browser draws it the same way.
|
|
3482
|
+
*/
|
|
3483
|
+
/** The smallest text, in CSS pixels, a field takes focus with on iOS without the page zooming in. */
|
|
3484
|
+
const LEGIBLE_FONT_PX = 16;
|
|
3485
|
+
/**
|
|
3486
|
+
* How many times bigger to lay out an editor whose text is `fontPx` CSS pixels, so its text is
|
|
3487
|
+
* at least {@link LEGIBLE_FONT_PX}: 1 when it already is.
|
|
3488
|
+
*/
|
|
3489
|
+
function legibleScaleOf(fontPx) {
|
|
3490
|
+
return fontPx > 0 && fontPx < 16 ? 16 / fontPx : 1;
|
|
3491
|
+
}
|
|
3492
|
+
/**
|
|
3493
|
+
* The transform that draws an element laid out `legible` times bigger back at its size, from its
|
|
3494
|
+
* top-left corner (`transformOrigin: '0 0'`), or null when it is laid out at its size.
|
|
3495
|
+
*/
|
|
3496
|
+
function legibleShrinkOf(legible) {
|
|
3497
|
+
return legible === 1 ? null : `scale(${1 / legible})`;
|
|
3498
|
+
}
|
|
3499
|
+
//#endregion
|
|
3216
3500
|
//#region src/text-box-editor.ts
|
|
3217
3501
|
/**
|
|
3502
|
+
* A free text box's editor on an element: the controller behind the
|
|
3503
|
+
* annotation layer's own text boxes and behind a renderer's element that
|
|
3504
|
+
* draws one itself. The shared rich-text binding ({@link attachRichTextEditor})
|
|
3505
|
+
* renders the runs as inline-styled spans, turns typing back into runs, maps
|
|
3506
|
+
* the selection to flat offsets and keeps the caret through a restyle; the
|
|
3507
|
+
* browser owns layout, the caret, input methods and the clipboard; the
|
|
3508
|
+
* annotation plugin owns the text, the range routing and the write after a
|
|
3509
|
+
* pause. This module adds what makes the element a text box of the layer:
|
|
3510
|
+
* focus and `contentEditable` follow the plugin's `editing`, never the other
|
|
3511
|
+
* way, and a press inside the box stays inside it. A framework binding keeps
|
|
3512
|
+
* one {@link createTextBoxEditorFollower} per element and hands it what
|
|
3513
|
+
* changed. {@link freeTextBoxOf} is the layer's own editor element and
|
|
3514
|
+
* {@link ownTextBoxOf} one a renderer draws itself, both laid out at a legible
|
|
3515
|
+
* size (`legible-text.ts`).
|
|
3516
|
+
*
|
|
3517
|
+
* The plugin's types are mirrored structurally, so this package stays free of
|
|
3518
|
+
* EmbedPDF imports.
|
|
3519
|
+
*/
|
|
3520
|
+
/**
|
|
3218
3521
|
* Make `element` the editor of a text box. Typing shows at once and reaches
|
|
3219
3522
|
* the engine in one write after a pause (or when the edit ends), not one
|
|
3220
3523
|
* write per keystroke.
|
|
@@ -3324,22 +3627,67 @@ function textBoxStyleOf(css, scale) {
|
|
|
3324
3627
|
};
|
|
3325
3628
|
}
|
|
3326
3629
|
/**
|
|
3327
|
-
* The editor element
|
|
3328
|
-
*
|
|
3329
|
-
*
|
|
3330
|
-
*
|
|
3331
|
-
*
|
|
3630
|
+
* The layer's own editor element for a free text box: the engine's text
|
|
3631
|
+
* plate (`SetPlateRect` and its `re W n` clip), the box inset by its padding,
|
|
3632
|
+
* with no CSS padding, so its edge is the plate's: CSS padding doesn't clip
|
|
3633
|
+
* overflow, and scrolled lines would paint over the border band. The box
|
|
3634
|
+
* never grows with its text. It is laid out at a legible size and drawn back
|
|
3635
|
+
* at the plate's (`legible-text.ts`), and a turned box turns about its
|
|
3636
|
+
* centre, as the baked appearance does. While someone types it takes the
|
|
3637
|
+
* pointer and shows an `outline` of that color.
|
|
3332
3638
|
*/
|
|
3333
|
-
function
|
|
3639
|
+
function freeTextBoxOf(item, page, outline) {
|
|
3334
3640
|
const box = rectInPixels(item.box, page);
|
|
3335
|
-
const
|
|
3336
|
-
const inset = item.css.padding *
|
|
3641
|
+
const onPage = item.box.width > 0 ? box.width / item.box.width : 1;
|
|
3642
|
+
const inset = item.css.padding * onPage;
|
|
3643
|
+
const width = Math.max(0, box.width - 2 * inset);
|
|
3644
|
+
const height = Math.max(0, box.height - 2 * inset);
|
|
3645
|
+
const legible = legibleScaleOf(item.css.fontSize * onPage);
|
|
3646
|
+
const scale = onPage * legible;
|
|
3647
|
+
const turn = item.rot ? `translate(${width / 2}px, ${height / 2}px) rotate(${item.rot}deg) translate(${-width / 2}px, ${-height / 2}px)` : null;
|
|
3648
|
+
return {
|
|
3649
|
+
scale,
|
|
3650
|
+
style: {
|
|
3651
|
+
position: "absolute",
|
|
3652
|
+
left: `${box.left + inset}px`,
|
|
3653
|
+
top: `${box.top + inset}px`,
|
|
3654
|
+
width: `${width * legible}px`,
|
|
3655
|
+
height: `${height * legible}px`,
|
|
3656
|
+
...textBoxStyleOf(item.css, scale),
|
|
3657
|
+
boxSizing: "border-box",
|
|
3658
|
+
background: "transparent",
|
|
3659
|
+
whiteSpace: "pre-wrap",
|
|
3660
|
+
overflowWrap: "break-word",
|
|
3661
|
+
overflowX: "hidden",
|
|
3662
|
+
overflowY: item.editing ? "auto" : "hidden",
|
|
3663
|
+
outline: item.editing ? `${legible}px solid ${outline}` : "none",
|
|
3664
|
+
cursor: item.editing ? "text" : "default",
|
|
3665
|
+
transform: [turn, legibleShrinkOf(legible)].filter(Boolean).join(" ") || "none",
|
|
3666
|
+
transformOrigin: "0 0",
|
|
3667
|
+
pointerEvents: item.editing ? "auto" : "none"
|
|
3668
|
+
}
|
|
3669
|
+
};
|
|
3670
|
+
}
|
|
3671
|
+
/**
|
|
3672
|
+
* Your own element as the editor of a text box you draw yourself, at `scale`
|
|
3673
|
+
* pixels per point where it is ({@link textBoxEditorScaleOf}): the box's body
|
|
3674
|
+
* style, filling the element's parent, laid out at a legible size and drawn
|
|
3675
|
+
* back at the parent's (`legible-text.ts`). Draw the box's frame (its fill,
|
|
3676
|
+
* border and padding) on the parent: what's on the element scales with it.
|
|
3677
|
+
*/
|
|
3678
|
+
function ownTextBoxOf(item, scale) {
|
|
3679
|
+
const legible = legibleScaleOf(item.css.fontSize * scale);
|
|
3337
3680
|
return {
|
|
3338
|
-
|
|
3339
|
-
|
|
3340
|
-
|
|
3341
|
-
|
|
3342
|
-
|
|
3681
|
+
scale: scale * legible,
|
|
3682
|
+
style: {
|
|
3683
|
+
...textBoxStyleOf(item.css, scale * legible),
|
|
3684
|
+
width: `${100 * legible}%`,
|
|
3685
|
+
height: `${100 * legible}%`,
|
|
3686
|
+
boxSizing: "border-box",
|
|
3687
|
+
transform: legibleShrinkOf(legible) ?? "none",
|
|
3688
|
+
transformOrigin: "0 0",
|
|
3689
|
+
...item.editing ? { pointerEvents: "auto" } : {}
|
|
3690
|
+
}
|
|
3343
3691
|
};
|
|
3344
3692
|
}
|
|
3345
3693
|
//#endregion
|
|
@@ -3626,19 +3974,25 @@ function cssFontOf(pdfFont) {
|
|
|
3626
3974
|
/** Pixels per page point where the widget is drawn. */
|
|
3627
3975
|
const scaleOf = (field, frame) => field.box.width > 0 ? frame.width / field.box.width : 1;
|
|
3628
3976
|
/**
|
|
3629
|
-
*
|
|
3630
|
-
* the box height for one line, Acrobat's 12 pt for several.
|
|
3977
|
+
* The size of a text field's text in pixels at `frame`. A font size of 0 means "fit the box" in
|
|
3978
|
+
* PDF: the box height for one line, Acrobat's 12 pt for several.
|
|
3631
3979
|
*/
|
|
3632
|
-
function
|
|
3980
|
+
function textFieldFontPxOf(field, frame) {
|
|
3633
3981
|
const scale = scaleOf(field, frame);
|
|
3634
|
-
|
|
3982
|
+
return field.look.fontSize ? field.look.fontSize * scale : field.multiline ? 12 * scale : Math.max(6, frame.height * .72);
|
|
3983
|
+
}
|
|
3984
|
+
/**
|
|
3985
|
+
* A text field's editor style at `frame`, with its lengths `legible` times bigger for an editor
|
|
3986
|
+
* laid out at a legible size (`legible-text.ts`).
|
|
3987
|
+
*/
|
|
3988
|
+
function textFieldStyleOf(field, frame, colors, legible = 1) {
|
|
3635
3989
|
return {
|
|
3636
3990
|
...cssFontOf(field.look.fontFamily),
|
|
3637
|
-
fontSize: `${
|
|
3991
|
+
fontSize: `${textFieldFontPxOf(field, frame) * legible}px`,
|
|
3638
3992
|
textAlign: field.look.textAlign,
|
|
3639
3993
|
color: field.look.color ?? colors.text,
|
|
3640
3994
|
background: field.look.background ?? colors.background,
|
|
3641
|
-
...field.comb && field.maxLength ? { letterSpacing: `${frame.width / field.maxLength / 2}px` } : {}
|
|
3995
|
+
...field.comb && field.maxLength ? { letterSpacing: `${frame.width / field.maxLength / 2 * legible}px` } : {}
|
|
3642
3996
|
};
|
|
3643
3997
|
}
|
|
3644
3998
|
/** A list box's style: the field's own look, the settings where it has none (12 pt for a fitted size). */
|
|
@@ -3858,6 +4212,9 @@ function widgetBoxStyleOf(widget, frame, colors, { edge = true } = {}) {
|
|
|
3858
4212
|
* control would swallow clicks, and the box below runs the widget's action
|
|
3859
4213
|
* (the read-only "button" pattern), so a disabled one lets them through.
|
|
3860
4214
|
*
|
|
4215
|
+
* It fills the widget's box, laid out at a legible size and drawn back at the
|
|
4216
|
+
* box's (`legible-text.ts`), so iOS doesn't zoom the page in when it takes focus.
|
|
4217
|
+
*
|
|
3861
4218
|
* With the engine's `layout` of the text being typed, the editor writes it
|
|
3862
4219
|
* where the engine will draw it: in the engine's face (mounted), at the size
|
|
3863
4220
|
* it draws (a fitted size resolved), in the box inside the border, a line of
|
|
@@ -3865,14 +4222,24 @@ function widgetBoxStyleOf(widget, frame, colors, { edge = true } = {}) {
|
|
|
3865
4222
|
*/
|
|
3866
4223
|
function textFieldEditorStyleOf(field, frame, colors, focused, layout = null) {
|
|
3867
4224
|
const scale = scaleOf(field, frame);
|
|
3868
|
-
const
|
|
4225
|
+
const legible = legibleScaleOf(layout ? layout.fontSize * scale : textFieldFontPxOf(field, frame));
|
|
4226
|
+
const px = (pixels) => `${pixels * legible}px`;
|
|
4227
|
+
const pixels = (points) => px(points * scale);
|
|
3869
4228
|
return {
|
|
3870
|
-
|
|
4229
|
+
position: "absolute",
|
|
4230
|
+
left: "0",
|
|
4231
|
+
top: "0",
|
|
4232
|
+
width: `${100 * legible}%`,
|
|
4233
|
+
height: `${100 * legible}%`,
|
|
4234
|
+
boxSizing: "border-box",
|
|
4235
|
+
margin: "0",
|
|
4236
|
+
transform: legibleShrinkOf(legible) ?? "none",
|
|
4237
|
+
transformOrigin: "0 0",
|
|
3871
4238
|
border: "none",
|
|
3872
|
-
outline: focused ?
|
|
3873
|
-
outlineOffset:
|
|
3874
|
-
padding:
|
|
3875
|
-
...textFieldStyleOf(field, frame, colors),
|
|
4239
|
+
outline: focused ? `${px(2)} solid ${colors.focus}` : "none",
|
|
4240
|
+
outlineOffset: px(-2),
|
|
4241
|
+
padding: `0 ${px(2)}`,
|
|
4242
|
+
...textFieldStyleOf(field, frame, colors, legible),
|
|
3876
4243
|
...layout ? {
|
|
3877
4244
|
fontFamily: layout.fontFamily,
|
|
3878
4245
|
fontWeight: layout.fontWeight,
|
|
@@ -5086,18 +5453,19 @@ function createSurfaceFocusFollower() {
|
|
|
5086
5453
|
//#region src/standard-commands-platform.ts
|
|
5087
5454
|
/**
|
|
5088
5455
|
* The browser's part of the standard commands (`createStandardCommands` in
|
|
5089
|
-
* `@embedpdf/plugin-commands/standard`): copying the selected text,
|
|
5090
|
-
* the document to the user as a download
|
|
5091
|
-
*
|
|
5456
|
+
* `@embedpdf/plugin-commands/standard`): copying the selected text, and
|
|
5457
|
+
* handing the document to the user as a download. Every framework adapter
|
|
5458
|
+
* builds its `standardCommands` from this one object. Printing is the
|
|
5459
|
+
* kernel's, with `browserPrinter()`.
|
|
5092
5460
|
*/
|
|
5093
5461
|
const standardCommandsBrowser = {
|
|
5094
5462
|
copySelection,
|
|
5095
|
-
saveFile: (bytes, fileName) => saveFile(bytes, fileName, "application/pdf")
|
|
5096
|
-
print: () => window.print()
|
|
5463
|
+
saveFile: (bytes, fileName) => saveFile(bytes, fileName, "application/pdf")
|
|
5097
5464
|
};
|
|
5098
5465
|
//#endregion
|
|
5099
5466
|
exports.EPDF_VARIABLES = EPDF_VARIABLES;
|
|
5100
5467
|
exports.FORM_CONTROL_FILL = FORM_CONTROL_FILL;
|
|
5468
|
+
exports.LEGIBLE_FONT_PX = LEGIBLE_FONT_PX;
|
|
5101
5469
|
exports.TOOLBAR_ITEM_ATTRIBUTE = TOOLBAR_ITEM_ATTRIBUTE;
|
|
5102
5470
|
exports.anchoredViewOf = anchoredViewOf;
|
|
5103
5471
|
exports.annotationChromePaint = annotationChromePaint;
|
|
@@ -5116,6 +5484,7 @@ exports.bindCommandShortcuts = bindCommandShortcuts;
|
|
|
5116
5484
|
exports.bindPaintedImage = bindPaintedImage;
|
|
5117
5485
|
exports.bindWidgetEvents = bindWidgetEvents;
|
|
5118
5486
|
exports.browserClock = browserClock;
|
|
5487
|
+
exports.browserPrinter = browserPrinter;
|
|
5119
5488
|
exports.canTakeFocus = canTakeFocus;
|
|
5120
5489
|
exports.chromeInPixels = chromeInPixels;
|
|
5121
5490
|
exports.clientPageProjector = clientPageProjector;
|
|
@@ -5149,6 +5518,7 @@ exports.fontProgramsOf = fontProgramsOf;
|
|
|
5149
5518
|
exports.formColorsOf = formColorsOf;
|
|
5150
5519
|
exports.formFocusRingStyleOf = formFocusRingStyleOf;
|
|
5151
5520
|
exports.frameInPixels = frameInPixels;
|
|
5521
|
+
exports.freeTextBoxOf = freeTextBoxOf;
|
|
5152
5522
|
exports.ghostOpacity = ghostOpacity;
|
|
5153
5523
|
exports.hoverLink = hoverLink;
|
|
5154
5524
|
exports.indexedDbByteStore = indexedDbByteStore;
|
|
@@ -5161,6 +5531,8 @@ exports.isUprightQuad = isUprightQuad;
|
|
|
5161
5531
|
exports.isolatePointerDown = isolatePointerDown;
|
|
5162
5532
|
exports.isolateWheel = isolateWheel;
|
|
5163
5533
|
exports.layerTextBoxesOf = layerTextBoxesOf;
|
|
5534
|
+
exports.legibleScaleOf = legibleScaleOf;
|
|
5535
|
+
exports.legibleShrinkOf = legibleShrinkOf;
|
|
5164
5536
|
exports.lineModelFor = lineModelFor;
|
|
5165
5537
|
exports.linkActivateContextOf = linkActivateContextOf;
|
|
5166
5538
|
exports.linkAnchorOf = linkAnchorOf;
|
|
@@ -5191,6 +5563,7 @@ exports.observeOutsidePress = observeOutsidePress;
|
|
|
5191
5563
|
exports.observeWidth = observeWidth;
|
|
5192
5564
|
exports.offsetOfPosition = offsetOfPosition;
|
|
5193
5565
|
exports.openExternalUri = openExternalUri;
|
|
5566
|
+
exports.ownTextBoxOf = ownTextBoxOf;
|
|
5194
5567
|
exports.pageClientSpace = pageClientSpace;
|
|
5195
5568
|
exports.pageLayersOf = pageLayersOf;
|
|
5196
5569
|
exports.pageSurfaceLayout = pageSurfaceLayout;
|
|
@@ -5240,10 +5613,9 @@ exports.svgPoints = svgPoints;
|
|
|
5240
5613
|
exports.svgShapesOf = svgShapesOf;
|
|
5241
5614
|
exports.tabbablesIn = tabbablesIn;
|
|
5242
5615
|
exports.textBoxEditorScaleOf = textBoxEditorScaleOf;
|
|
5243
|
-
exports.textBoxStyleOf = textBoxStyleOf;
|
|
5244
5616
|
exports.textFieldEditorStyleOf = textFieldEditorStyleOf;
|
|
5617
|
+
exports.textFieldFontPxOf = textFieldFontPxOf;
|
|
5245
5618
|
exports.textFieldStyleOf = textFieldStyleOf;
|
|
5246
|
-
exports.textPlateInPixels = textPlateInPixels;
|
|
5247
5619
|
exports.toolCursorsOf = toolCursorsOf;
|
|
5248
5620
|
exports.toolbarMeasureKey = toolbarMeasureKey;
|
|
5249
5621
|
exports.toolbarStopsOf = toolbarStopsOf;
|