2 * @licstart The following is the entire license notice for the
3 * JavaScript code in this page
5 * Copyright 2024 Mozilla Foundation
7 * Licensed under the Apache License, Version 2.0 (the "License");
8 * you may not use this file except in compliance with the License.
9 * You may obtain a copy of the License at
11 * http://www.apache.org/licenses/LICENSE-2.0
13 * Unless required by applicable law or agreed to in writing, software
14 * distributed under the License is distributed on an "AS IS" BASIS,
15 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
16 * See the License for the specific language governing permissions and
17 * limitations under the License.
19 * @licend The above is the entire license notice for the
20 * JavaScript code in this page
23 /******/ // The require scope
24 /******/ var __webpack_require__ = {};
26 /************************************************************************/
27 /******/ /* webpack/runtime/define property getters */
29 /******/ // define getter functions for harmony exports
30 /******/ __webpack_require__.d = (exports, definition) => {
31 /******/ for(var key in definition) {
32 /******/ if(__webpack_require__.o(definition, key) && !__webpack_require__.o(exports, key)) {
33 /******/ Object.defineProperty(exports, key, { enumerable: true, get: definition[key] });
39 /******/ /* webpack/runtime/hasOwnProperty shorthand */
41 /******/ __webpack_require__.o = (obj, prop) => (Object.prototype.hasOwnProperty.call(obj, prop))
44 /************************************************************************/
45 var __webpack_exports__ = globalThis.pdfjsLib = {};
48 __webpack_require__.d(__webpack_exports__, {
49 AbortException: () => (/* reexport */ AbortException),
50 AnnotationBorderStyleType: () => (/* reexport */ AnnotationBorderStyleType),
51 AnnotationEditorLayer: () => (/* reexport */ AnnotationEditorLayer),
52 AnnotationEditorParamsType: () => (/* reexport */ AnnotationEditorParamsType),
53 AnnotationEditorType: () => (/* reexport */ AnnotationEditorType),
54 AnnotationEditorUIManager: () => (/* reexport */ AnnotationEditorUIManager),
55 AnnotationLayer: () => (/* reexport */ AnnotationLayer),
56 AnnotationMode: () => (/* reexport */ AnnotationMode),
57 AnnotationType: () => (/* reexport */ AnnotationType),
58 ColorPicker: () => (/* reexport */ ColorPicker),
59 DOMSVGFactory: () => (/* reexport */ DOMSVGFactory),
60 DrawLayer: () => (/* reexport */ DrawLayer),
61 FeatureTest: () => (/* reexport */ util_FeatureTest),
62 GlobalWorkerOptions: () => (/* reexport */ GlobalWorkerOptions),
63 ImageKind: () => (/* reexport */ util_ImageKind),
64 InvalidPDFException: () => (/* reexport */ InvalidPDFException),
65 OPS: () => (/* reexport */ OPS),
66 OutputScale: () => (/* reexport */ OutputScale),
67 PDFDataRangeTransport: () => (/* reexport */ PDFDataRangeTransport),
68 PDFDateString: () => (/* reexport */ PDFDateString),
69 PDFWorker: () => (/* reexport */ PDFWorker),
70 PasswordResponses: () => (/* reexport */ PasswordResponses),
71 PermissionFlag: () => (/* reexport */ PermissionFlag),
72 PixelsPerInch: () => (/* reexport */ PixelsPerInch),
73 RenderingCancelledException: () => (/* reexport */ RenderingCancelledException),
74 ResponseException: () => (/* reexport */ ResponseException),
75 SupportedImageMimeTypes: () => (/* reexport */ SupportedImageMimeTypes),
76 TextLayer: () => (/* reexport */ TextLayer),
77 TouchManager: () => (/* reexport */ TouchManager),
78 Util: () => (/* reexport */ Util),
79 VerbosityLevel: () => (/* reexport */ VerbosityLevel),
80 XfaLayer: () => (/* reexport */ XfaLayer),
81 build: () => (/* reexport */ build),
82 createValidAbsoluteUrl: () => (/* reexport */ createValidAbsoluteUrl),
83 fetchData: () => (/* reexport */ fetchData),
84 getDocument: () => (/* reexport */ getDocument),
85 getFilenameFromUrl: () => (/* reexport */ getFilenameFromUrl),
86 getPdfFilenameFromUrl: () => (/* reexport */ getPdfFilenameFromUrl),
87 getXfaPageViewport: () => (/* reexport */ getXfaPageViewport),
88 isDataScheme: () => (/* reexport */ isDataScheme),
89 isPdfFile: () => (/* reexport */ isPdfFile),
90 noContextMenu: () => (/* reexport */ noContextMenu),
91 normalizeUnicode: () => (/* reexport */ normalizeUnicode),
92 setLayerDimensions: () => (/* reexport */ setLayerDimensions),
93 shadow: () => (/* reexport */ shadow),
94 stopEvent: () => (/* reexport */ stopEvent),
95 version: () => (/* reexport */ version)
98 ;// ./src/shared/util.js
99 const isNodeJS = false;
100 const IDENTITY_MATRIX = [1, 0, 0, 1, 0, 0];
101 const FONT_IDENTITY_MATRIX = [0.001, 0, 0, 0.001, 0, 0];
102 const LINE_FACTOR = 1.35;
103 const LINE_DESCENT_FACTOR = 0.35;
104 const BASELINE_FACTOR = LINE_DESCENT_FACTOR / LINE_FACTOR;
105 const RenderingIntentFlag = {
110 ANNOTATIONS_FORMS: 0x10,
111 ANNOTATIONS_STORAGE: 0x20,
112 ANNOTATIONS_DISABLE: 0x40,
116 const AnnotationMode = {
122 const AnnotationEditorPrefix = "pdfjs_internal_editor_";
123 const AnnotationEditorType = {
132 const AnnotationEditorParamsType = {
137 FREETEXT_OPACITY: 13,
142 HIGHLIGHT_DEFAULT_COLOR: 32,
143 HIGHLIGHT_THICKNESS: 33,
145 HIGHLIGHT_SHOW_ALL: 35,
148 const PermissionFlag = {
150 MODIFY_CONTENTS: 0x08,
152 MODIFY_ANNOTATIONS: 0x20,
153 FILL_INTERACTIVE_FORMS: 0x100,
154 COPY_FOR_ACCESSIBILITY: 0x200,
156 PRINT_HIGH_QUALITY: 0x800
158 const TextRenderingMode = {
164 STROKE_ADD_TO_PATH: 5,
165 FILL_STROKE_ADD_TO_PATH: 6,
170 const util_ImageKind = {
175 const AnnotationType = {
203 const AnnotationReplyType = {
207 const AnnotationFlag = {
217 LOCKEDCONTENTS: 0x200
219 const AnnotationFieldFlag = {
223 MULTILINE: 0x0001000,
225 NOTOGGLETOOFF: 0x0004000,
227 PUSHBUTTON: 0x0010000,
231 FILESELECT: 0x0100000,
232 MULTISELECT: 0x0200000,
233 DONOTSPELLCHECK: 0x0400000,
234 DONOTSCROLL: 0x0800000,
237 RADIOSINUNISON: 0x2000000,
238 COMMITONSELCHANGE: 0x4000000
240 const AnnotationBorderStyleType = {
247 const AnnotationActionEventType = {
263 const DocumentActionEventType = {
270 const PageActionEventType = {
274 const VerbosityLevel = {
286 setRenderingIntent: 7,
306 closeEOFillStroke: 27,
317 setTextRenderingMode: 38,
320 setLeadingMoveText: 41,
325 nextLineShowText: 46,
326 nextLineSetSpacingShowText: 47,
328 setCharWidthAndBounds: 49,
329 setStrokeColorSpace: 50,
330 setFillColorSpace: 51,
337 setStrokeRGBColor: 58,
339 setStrokeCMYKColor: 60,
340 setFillCMYKColor: 61,
342 beginInlineImage: 63,
348 beginMarkedContent: 69,
349 beginMarkedContentProps: 70,
350 endMarkedContent: 71,
353 paintFormXObjectBegin: 74,
354 paintFormXObjectEnd: 75,
359 paintImageMaskXObject: 83,
360 paintImageMaskXObjectGroup: 84,
361 paintImageXObject: 85,
362 paintInlineImageXObject: 86,
363 paintInlineImageXObjectGroup: 87,
364 paintImageXObjectRepeat: 88,
365 paintImageMaskXObjectRepeat: 89,
366 paintSolidColorImageMask: 90,
368 setStrokeTransparent: 92,
369 setFillTransparent: 93
371 const PasswordResponses = {
373 INCORRECT_PASSWORD: 2
375 let verbosity = VerbosityLevel.WARNINGS;
376 function setVerbosityLevel(level) {
377 if (Number.isInteger(level)) {
381 function getVerbosityLevel() {
385 if (verbosity >= VerbosityLevel.INFOS) {
386 console.log(`Info: ${msg}`);
390 if (verbosity >= VerbosityLevel.WARNINGS) {
391 console.log(`Warning: ${msg}`);
394 function unreachable(msg) {
395 throw new Error(msg);
397 function assert(cond, msg) {
402 function _isValidProtocol(url) {
403 switch (url?.protocol) {
414 function createValidAbsoluteUrl(url, baseUrl = null, options = null) {
419 if (options && typeof url === "string") {
420 if (options.addDefaultProtocol && url.startsWith("www.")) {
421 const dots = url.match(/\./g);
422 if (dots?.length >= 2) {
423 url = `http://${url}`;
426 if (options.tryConvertEncoding) {
428 url = stringToUTF8String(url);
432 const absoluteUrl = baseUrl ? new URL(url, baseUrl) : new URL(url);
433 if (_isValidProtocol(absoluteUrl)) {
439 function shadow(obj, prop, value, nonSerializable = false) {
440 Object.defineProperty(obj, prop, {
442 enumerable: !nonSerializable,
448 const BaseException = function BaseExceptionClosure() {
449 function BaseException(message, name) {
450 this.message = message;
453 BaseException.prototype = new Error();
454 BaseException.constructor = BaseException;
455 return BaseException;
457 class PasswordException extends BaseException {
458 constructor(msg, code) {
459 super(msg, "PasswordException");
463 class UnknownErrorException extends BaseException {
464 constructor(msg, details) {
465 super(msg, "UnknownErrorException");
466 this.details = details;
469 class InvalidPDFException extends BaseException {
471 super(msg, "InvalidPDFException");
474 class ResponseException extends BaseException {
475 constructor(msg, status, missing) {
476 super(msg, "ResponseException");
477 this.status = status;
478 this.missing = missing;
481 class FormatError extends BaseException {
483 super(msg, "FormatError");
486 class AbortException extends BaseException {
488 super(msg, "AbortException");
491 function bytesToString(bytes) {
492 if (typeof bytes !== "object" || bytes?.length === undefined) {
493 unreachable("Invalid argument for bytesToString");
495 const length = bytes.length;
496 const MAX_ARGUMENT_COUNT = 8192;
497 if (length < MAX_ARGUMENT_COUNT) {
498 return String.fromCharCode.apply(null, bytes);
501 for (let i = 0; i < length; i += MAX_ARGUMENT_COUNT) {
502 const chunkEnd = Math.min(i + MAX_ARGUMENT_COUNT, length);
503 const chunk = bytes.subarray(i, chunkEnd);
504 strBuf.push(String.fromCharCode.apply(null, chunk));
506 return strBuf.join("");
508 function stringToBytes(str) {
509 if (typeof str !== "string") {
510 unreachable("Invalid argument for stringToBytes");
512 const length = str.length;
513 const bytes = new Uint8Array(length);
514 for (let i = 0; i < length; ++i) {
515 bytes[i] = str.charCodeAt(i) & 0xff;
519 function string32(value) {
520 return String.fromCharCode(value >> 24 & 0xff, value >> 16 & 0xff, value >> 8 & 0xff, value & 0xff);
522 function objectSize(obj) {
523 return Object.keys(obj).length;
525 function objectFromMap(map) {
526 const obj = Object.create(null);
527 for (const [key, value] of map) {
532 function isLittleEndian() {
533 const buffer8 = new Uint8Array(4);
535 const view32 = new Uint32Array(buffer8.buffer, 0, 1);
536 return view32[0] === 1;
538 function isEvalSupported() {
546 class util_FeatureTest {
547 static get isLittleEndian() {
548 return shadow(this, "isLittleEndian", isLittleEndian());
550 static get isEvalSupported() {
551 return shadow(this, "isEvalSupported", isEvalSupported());
553 static get isOffscreenCanvasSupported() {
554 return shadow(this, "isOffscreenCanvasSupported", typeof OffscreenCanvas !== "undefined");
556 static get isImageDecoderSupported() {
557 return shadow(this, "isImageDecoderSupported", typeof ImageDecoder !== "undefined");
559 static get platform() {
560 return shadow(this, "platform", {
561 isMac: navigator.platform.includes("Mac"),
562 isWindows: navigator.platform.includes("Win"),
566 static get isCSSRoundSupported() {
567 return shadow(this, "isCSSRoundSupported", globalThis.CSS?.supports?.("width: round(1.5px, 1px)"));
570 const hexNumbers = Array.from(Array(256).keys(), n => n.toString(16).padStart(2, "0"));
572 static makeHexColor(r, g, b) {
573 return `#${hexNumbers[r]}${hexNumbers[g]}${hexNumbers[b]}`;
575 static scaleMinMax(transform, minMax) {
578 if (transform[0] < 0) {
580 minMax[0] = minMax[2];
583 minMax[0] *= transform[0];
584 minMax[2] *= transform[0];
585 if (transform[3] < 0) {
587 minMax[1] = minMax[3];
590 minMax[1] *= transform[3];
591 minMax[3] *= transform[3];
594 minMax[0] = minMax[1];
597 minMax[2] = minMax[3];
599 if (transform[1] < 0) {
601 minMax[1] = minMax[3];
604 minMax[1] *= transform[1];
605 minMax[3] *= transform[1];
606 if (transform[2] < 0) {
608 minMax[0] = minMax[2];
611 minMax[0] *= transform[2];
612 minMax[2] *= transform[2];
614 minMax[0] += transform[4];
615 minMax[1] += transform[5];
616 minMax[2] += transform[4];
617 minMax[3] += transform[5];
619 static transform(m1, m2) {
620 return [m1[0] * m2[0] + m1[2] * m2[1], m1[1] * m2[0] + m1[3] * m2[1], m1[0] * m2[2] + m1[2] * m2[3], m1[1] * m2[2] + m1[3] * m2[3], m1[0] * m2[4] + m1[2] * m2[5] + m1[4], m1[1] * m2[4] + m1[3] * m2[5] + m1[5]];
622 static applyTransform(p, m) {
623 const xt = p[0] * m[0] + p[1] * m[2] + m[4];
624 const yt = p[0] * m[1] + p[1] * m[3] + m[5];
627 static applyInverseTransform(p, m) {
628 const d = m[0] * m[3] - m[1] * m[2];
629 const xt = (p[0] * m[3] - p[1] * m[2] + m[2] * m[5] - m[4] * m[3]) / d;
630 const yt = (-p[0] * m[1] + p[1] * m[0] + m[4] * m[1] - m[5] * m[0]) / d;
633 static getAxialAlignedBoundingBox(r, m) {
634 const p1 = this.applyTransform(r, m);
635 const p2 = this.applyTransform(r.slice(2, 4), m);
636 const p3 = this.applyTransform([r[0], r[3]], m);
637 const p4 = this.applyTransform([r[2], r[1]], m);
638 return [Math.min(p1[0], p2[0], p3[0], p4[0]), Math.min(p1[1], p2[1], p3[1], p4[1]), Math.max(p1[0], p2[0], p3[0], p4[0]), Math.max(p1[1], p2[1], p3[1], p4[1])];
640 static inverseTransform(m) {
641 const d = m[0] * m[3] - m[1] * m[2];
642 return [m[3] / d, -m[1] / d, -m[2] / d, m[0] / d, (m[2] * m[5] - m[4] * m[3]) / d, (m[4] * m[1] - m[5] * m[0]) / d];
644 static singularValueDecompose2dScale(m) {
645 const transpose = [m[0], m[2], m[1], m[3]];
646 const a = m[0] * transpose[0] + m[1] * transpose[2];
647 const b = m[0] * transpose[1] + m[1] * transpose[3];
648 const c = m[2] * transpose[0] + m[3] * transpose[2];
649 const d = m[2] * transpose[1] + m[3] * transpose[3];
650 const first = (a + d) / 2;
651 const second = Math.sqrt((a + d) ** 2 - 4 * (a * d - c * b)) / 2;
652 const sx = first + second || 1;
653 const sy = first - second || 1;
654 return [Math.sqrt(sx), Math.sqrt(sy)];
656 static normalizeRect(rect) {
657 const r = rect.slice(0);
658 if (rect[0] > rect[2]) {
662 if (rect[1] > rect[3]) {
668 static intersect(rect1, rect2) {
669 const xLow = Math.max(Math.min(rect1[0], rect1[2]), Math.min(rect2[0], rect2[2]));
670 const xHigh = Math.min(Math.max(rect1[0], rect1[2]), Math.max(rect2[0], rect2[2]));
674 const yLow = Math.max(Math.min(rect1[1], rect1[3]), Math.min(rect2[1], rect2[3]));
675 const yHigh = Math.min(Math.max(rect1[1], rect1[3]), Math.max(rect2[1], rect2[3]));
679 return [xLow, yLow, xHigh, yHigh];
681 static #getExtremumOnCurve(x0, x1, x2, x3, y0, y1, y2, y3, t, minMax) {
682 if (t <= 0 || t >= 1) {
688 const x = mt * (mt * (mt * x0 + 3 * t * x1) + 3 * tt * x2) + ttt * x3;
689 const y = mt * (mt * (mt * y0 + 3 * t * y1) + 3 * tt * y2) + ttt * y3;
690 minMax[0] = Math.min(minMax[0], x);
691 minMax[1] = Math.min(minMax[1], y);
692 minMax[2] = Math.max(minMax[2], x);
693 minMax[3] = Math.max(minMax[3], y);
695 static #getExtremum(x0, x1, x2, x3, y0, y1, y2, y3, a, b, c, minMax) {
696 if (Math.abs(a) < 1e-12) {
697 if (Math.abs(b) >= 1e-12) {
698 this.#getExtremumOnCurve(x0, x1, x2, x3, y0, y1, y2, y3, -c / b, minMax);
702 const delta = b ** 2 - 4 * c * a;
706 const sqrtDelta = Math.sqrt(delta);
708 this.#getExtremumOnCurve(x0, x1, x2, x3, y0, y1, y2, y3, (-b + sqrtDelta) / a2, minMax);
709 this.#getExtremumOnCurve(x0, x1, x2, x3, y0, y1, y2, y3, (-b - sqrtDelta) / a2, minMax);
711 static bezierBoundingBox(x0, y0, x1, y1, x2, y2, x3, y3, minMax) {
713 minMax[0] = Math.min(minMax[0], x0, x3);
714 minMax[1] = Math.min(minMax[1], y0, y3);
715 minMax[2] = Math.max(minMax[2], x0, x3);
716 minMax[3] = Math.max(minMax[3], y0, y3);
718 minMax = [Math.min(x0, x3), Math.min(y0, y3), Math.max(x0, x3), Math.max(y0, y3)];
720 this.#getExtremum(x0, x1, x2, x3, y0, y1, y2, y3, 3 * (-x0 + 3 * (x1 - x2) + x3), 6 * (x0 - 2 * x1 + x2), 3 * (x1 - x0), minMax);
721 this.#getExtremum(x0, x1, x2, x3, y0, y1, y2, y3, 3 * (-y0 + 3 * (y1 - y2) + y3), 6 * (y0 - 2 * y1 + y2), 3 * (y1 - y0), minMax);
725 const PDFStringTranslateTable = (/* unused pure expression or super */ null && ([0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0x2d8, 0x2c7, 0x2c6, 0x2d9, 0x2dd, 0x2db, 0x2da, 0x2dc, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0x2022, 0x2020, 0x2021, 0x2026, 0x2014, 0x2013, 0x192, 0x2044, 0x2039, 0x203a, 0x2212, 0x2030, 0x201e, 0x201c, 0x201d, 0x2018, 0x2019, 0x201a, 0x2122, 0xfb01, 0xfb02, 0x141, 0x152, 0x160, 0x178, 0x17d, 0x131, 0x142, 0x153, 0x161, 0x17e, 0, 0x20ac]));
726 function stringToPDFString(str) {
727 if (str[0] >= "\xEF") {
729 if (str[0] === "\xFE" && str[1] === "\xFF") {
730 encoding = "utf-16be";
731 if (str.length % 2 === 1) {
732 str = str.slice(0, -1);
734 } else if (str[0] === "\xFF" && str[1] === "\xFE") {
735 encoding = "utf-16le";
736 if (str.length % 2 === 1) {
737 str = str.slice(0, -1);
739 } else if (str[0] === "\xEF" && str[1] === "\xBB" && str[2] === "\xBF") {
744 const decoder = new TextDecoder(encoding, {
747 const buffer = stringToBytes(str);
748 const decoded = decoder.decode(buffer);
749 if (!decoded.includes("\x1b")) {
752 return decoded.replaceAll(/\x1b[^\x1b]*(?:\x1b|$)/g, "");
754 warn(`stringToPDFString: "${ex}".`);
759 for (let i = 0, ii = str.length; i < ii; i++) {
760 const charCode = str.charCodeAt(i);
761 if (charCode === 0x1b) {
762 while (++i < ii && str.charCodeAt(i) !== 0x1b) {}
765 const code = PDFStringTranslateTable[charCode];
766 strBuf.push(code ? String.fromCharCode(code) : str.charAt(i));
768 return strBuf.join("");
770 function stringToUTF8String(str) {
771 return decodeURIComponent(escape(str));
773 function utf8StringToString(str) {
774 return unescape(encodeURIComponent(str));
776 function isArrayEqual(arr1, arr2) {
777 if (arr1.length !== arr2.length) {
780 for (let i = 0, ii = arr1.length; i < ii; i++) {
781 if (arr1[i] !== arr2[i]) {
787 function getModificationDate(date = new Date()) {
788 const buffer = [date.getUTCFullYear().toString(), (date.getUTCMonth() + 1).toString().padStart(2, "0"), date.getUTCDate().toString().padStart(2, "0"), date.getUTCHours().toString().padStart(2, "0"), date.getUTCMinutes().toString().padStart(2, "0"), date.getUTCSeconds().toString().padStart(2, "0")];
789 return buffer.join("");
791 let NormalizeRegex = null;
792 let NormalizationMap = null;
793 function normalizeUnicode(str) {
794 if (!NormalizeRegex) {
795 NormalizeRegex = /([\u00a0\u00b5\u037e\u0eb3\u2000-\u200a\u202f\u2126\ufb00-\ufb04\ufb06\ufb20-\ufb36\ufb38-\ufb3c\ufb3e\ufb40-\ufb41\ufb43-\ufb44\ufb46-\ufba1\ufba4-\ufba9\ufbae-\ufbb1\ufbd3-\ufbdc\ufbde-\ufbe7\ufbea-\ufbf8\ufbfc-\ufbfd\ufc00-\ufc5d\ufc64-\ufcf1\ufcf5-\ufd3d\ufd88\ufdf4\ufdfa-\ufdfb\ufe71\ufe77\ufe79\ufe7b\ufe7d]+)|(\ufb05+)/gu;
796 NormalizationMap = new Map([["ſt", "ſt"]]);
798 return str.replaceAll(NormalizeRegex, (_, p1, p2) => p1 ? p1.normalize("NFKC") : NormalizationMap.get(p2));
801 return crypto.randomUUID();
803 const AnnotationPrefix = "pdfjs_internal_id_";
804 function toHexUtil(arr) {
805 if (Uint8Array.prototype.toHex) {
808 return Array.from(arr, num => hexNumbers[num]).join("");
810 function toBase64Util(arr) {
811 if (Uint8Array.prototype.toBase64) {
812 return arr.toBase64();
814 return btoa(bytesToString(arr));
816 function fromBase64Util(str) {
817 if (Uint8Array.fromBase64) {
818 return Uint8Array.fromBase64(str);
820 return stringToBytes(atob(str));
822 if (typeof Promise.try !== "function") {
823 Promise.try = function (fn, ...args) {
824 return new Promise(resolve => {
825 resolve(fn(...args));
830 ;// ./src/display/display_utils.js
832 const SVG_NS = "http://www.w3.org/2000/svg";
833 class PixelsPerInch {
836 static PDF_TO_CSS_UNITS = this.CSS / this.PDF;
838 async function fetchData(url, type = "text") {
839 const response = await fetch(url);
841 throw new Error(response.statusText);
845 return response.arrayBuffer();
847 return response.blob();
849 return response.json();
851 return response.text();
863 this.viewBox = viewBox;
864 this.userUnit = userUnit;
866 this.rotation = rotation;
867 this.offsetX = offsetX;
868 this.offsetY = offsetY;
870 const centerX = (viewBox[2] + viewBox[0]) / 2;
871 const centerY = (viewBox[3] + viewBox[1]) / 2;
872 let rotateA, rotateB, rotateC, rotateD;
903 throw new Error("PageViewport: Invalid rotation, must be a multiple of 90 degrees.");
909 let offsetCanvasX, offsetCanvasY;
912 offsetCanvasX = Math.abs(centerY - viewBox[1]) * scale + offsetX;
913 offsetCanvasY = Math.abs(centerX - viewBox[0]) * scale + offsetY;
914 width = (viewBox[3] - viewBox[1]) * scale;
915 height = (viewBox[2] - viewBox[0]) * scale;
917 offsetCanvasX = Math.abs(centerX - viewBox[0]) * scale + offsetX;
918 offsetCanvasY = Math.abs(centerY - viewBox[1]) * scale + offsetY;
919 width = (viewBox[2] - viewBox[0]) * scale;
920 height = (viewBox[3] - viewBox[1]) * scale;
922 this.transform = [rotateA * scale, rotateB * scale, rotateC * scale, rotateD * scale, offsetCanvasX - rotateA * scale * centerX - rotateC * scale * centerY, offsetCanvasY - rotateB * scale * centerX - rotateD * scale * centerY];
924 this.height = height;
931 const dims = viewBox.map(x => x * userUnit);
932 return shadow(this, "rawDims", {
933 pageWidth: dims[2] - dims[0],
934 pageHeight: dims[3] - dims[1],
941 rotation = this.rotation,
942 offsetX = this.offsetX,
943 offsetY = this.offsetY,
946 return new PageViewport({
947 viewBox: this.viewBox.slice(),
948 userUnit: this.userUnit,
956 convertToViewportPoint(x, y) {
957 return Util.applyTransform([x, y], this.transform);
959 convertToViewportRectangle(rect) {
960 const topLeft = Util.applyTransform([rect[0], rect[1]], this.transform);
961 const bottomRight = Util.applyTransform([rect[2], rect[3]], this.transform);
962 return [topLeft[0], topLeft[1], bottomRight[0], bottomRight[1]];
964 convertToPdfPoint(x, y) {
965 return Util.applyInverseTransform([x, y], this.transform);
968 class RenderingCancelledException extends BaseException {
969 constructor(msg, extraDelay = 0) {
970 super(msg, "RenderingCancelledException");
971 this.extraDelay = extraDelay;
974 function isDataScheme(url) {
975 const ii = url.length;
977 while (i < ii && url[i].trim() === "") {
980 return url.substring(i, i + 5).toLowerCase() === "data:";
982 function isPdfFile(filename) {
983 return typeof filename === "string" && /\.pdf$/i.test(filename);
985 function getFilenameFromUrl(url) {
986 [url] = url.split(/[#?]/, 1);
987 return url.substring(url.lastIndexOf("/") + 1);
989 function getPdfFilenameFromUrl(url, defaultFilename = "document.pdf") {
990 if (typeof url !== "string") {
991 return defaultFilename;
993 if (isDataScheme(url)) {
994 warn('getPdfFilenameFromUrl: ignore "data:"-URL for performance reasons.');
995 return defaultFilename;
997 const reURI = /^(?:(?:[^:]+:)?\/\/[^/]+)?([^?#]*)(\?[^#]*)?(#.*)?$/;
998 const reFilename = /[^/?#=]+\.pdf\b(?!.*\.pdf\b)/i;
999 const splitURI = reURI.exec(url);
1000 let suggestedFilename = reFilename.exec(splitURI[1]) || reFilename.exec(splitURI[2]) || reFilename.exec(splitURI[3]);
1001 if (suggestedFilename) {
1002 suggestedFilename = suggestedFilename[0];
1003 if (suggestedFilename.includes("%")) {
1005 suggestedFilename = reFilename.exec(decodeURIComponent(suggestedFilename))[0];
1009 return suggestedFilename || defaultFilename;
1012 started = Object.create(null);
1015 if (name in this.started) {
1016 warn(`Timer is already running for ${name}`);
1018 this.started[name] = Date.now();
1021 if (!(name in this.started)) {
1022 warn(`Timer has not been started for ${name}`);
1026 start: this.started[name],
1029 delete this.started[name];
1037 longest = Math.max(name.length, longest);
1044 outBuf.push(`${name.padEnd(longest)} ${end - start}ms\n`);
1046 return outBuf.join("");
1049 function isValidFetchUrl(url, baseUrl) {
1050 throw new Error("Not implemented: isValidFetchUrl");
1052 function noContextMenu(e) {
1055 function stopEvent(e) {
1057 e.stopPropagation();
1059 function deprecated(details) {
1060 console.log("Deprecated API usage: " + details);
1062 class PDFDateString {
1064 static toDateObject(input) {
1065 if (!input || typeof input !== "string") {
1068 this.#regex ||= new RegExp("^D:" + "(\\d{4})" + "(\\d{2})?" + "(\\d{2})?" + "(\\d{2})?" + "(\\d{2})?" + "(\\d{2})?" + "([Z|+|-])?" + "(\\d{2})?" + "'?" + "(\\d{2})?" + "'?");
1069 const matches = this.#regex.exec(input);
1073 const year = parseInt(matches[1], 10);
1074 let month = parseInt(matches[2], 10);
1075 month = month >= 1 && month <= 12 ? month - 1 : 0;
1076 let day = parseInt(matches[3], 10);
1077 day = day >= 1 && day <= 31 ? day : 1;
1078 let hour = parseInt(matches[4], 10);
1079 hour = hour >= 0 && hour <= 23 ? hour : 0;
1080 let minute = parseInt(matches[5], 10);
1081 minute = minute >= 0 && minute <= 59 ? minute : 0;
1082 let second = parseInt(matches[6], 10);
1083 second = second >= 0 && second <= 59 ? second : 0;
1084 const universalTimeRelation = matches[7] || "Z";
1085 let offsetHour = parseInt(matches[8], 10);
1086 offsetHour = offsetHour >= 0 && offsetHour <= 23 ? offsetHour : 0;
1087 let offsetMinute = parseInt(matches[9], 10) || 0;
1088 offsetMinute = offsetMinute >= 0 && offsetMinute <= 59 ? offsetMinute : 0;
1089 if (universalTimeRelation === "-") {
1091 minute += offsetMinute;
1092 } else if (universalTimeRelation === "+") {
1094 minute -= offsetMinute;
1096 return new Date(Date.UTC(year, month, day, hour, minute, second));
1099 function getXfaPageViewport(xfaPage, {
1106 } = xfaPage.attributes.style;
1107 const viewBox = [0, 0, parseInt(width), parseInt(height)];
1108 return new PageViewport({
1115 function getRGB(color) {
1116 if (color.startsWith("#")) {
1117 const colorRGB = parseInt(color.slice(1), 16);
1118 return [(colorRGB & 0xff0000) >> 16, (colorRGB & 0x00ff00) >> 8, colorRGB & 0x0000ff];
1120 if (color.startsWith("rgb(")) {
1121 return color.slice(4, -1).split(",").map(x => parseInt(x));
1123 if (color.startsWith("rgba(")) {
1124 return color.slice(5, -1).split(",").map(x => parseInt(x)).slice(0, 3);
1126 warn(`Not a valid color format: "${color}"`);
1129 function getColorValues(colors) {
1130 const span = document.createElement("span");
1131 span.style.visibility = "hidden";
1132 document.body.append(span);
1133 for (const name of colors.keys()) {
1134 span.style.color = name;
1135 const computedColor = window.getComputedStyle(span).color;
1136 colors.set(name, getRGB(computedColor));
1140 function getCurrentTransform(ctx) {
1148 } = ctx.getTransform();
1149 return [a, b, c, d, e, f];
1151 function getCurrentTransformInverse(ctx) {
1159 } = ctx.getTransform().invertSelf();
1160 return [a, b, c, d, e, f];
1162 function setLayerDimensions(div, viewport, mustFlip = false, mustRotate = true) {
1163 if (viewport instanceof PageViewport) {
1167 } = viewport.rawDims;
1171 const useRound = util_FeatureTest.isCSSRoundSupported;
1172 const w = `var(--scale-factor) * ${pageWidth}px`,
1173 h = `var(--scale-factor) * ${pageHeight}px`;
1174 const widthStr = useRound ? `round(down, ${w}, var(--scale-round-x, 1px))` : `calc(${w})`,
1175 heightStr = useRound ? `round(down, ${h}, var(--scale-round-y, 1px))` : `calc(${h})`;
1176 if (!mustFlip || viewport.rotation % 180 === 0) {
1177 style.width = widthStr;
1178 style.height = heightStr;
1180 style.width = heightStr;
1181 style.height = widthStr;
1185 div.setAttribute("data-main-rotation", viewport.rotation);
1190 const pixelRatio = window.devicePixelRatio || 1;
1191 this.sx = pixelRatio;
1192 this.sy = pixelRatio;
1195 return this.sx !== 1 || this.sy !== 1;
1198 return this.sx === this.sy;
1201 const SupportedImageMimeTypes = ["image/apng", "image/avif", "image/bmp", "image/gif", "image/jpeg", "image/png", "image/svg+xml", "image/webp", "image/x-icon"];
1203 ;// ./src/display/editor/toolbar.js
1205 class EditorToolbar {
1207 #colorPicker = null;
1211 static #l10nRemove = null;
1212 constructor(editor) {
1213 this.#editor = editor;
1214 EditorToolbar.#l10nRemove ||= Object.freeze({
1215 freetext: "pdfjs-editor-remove-freetext-button",
1216 highlight: "pdfjs-editor-remove-highlight-button",
1217 ink: "pdfjs-editor-remove-ink-button",
1218 stamp: "pdfjs-editor-remove-stamp-button",
1219 signature: "pdfjs-editor-remove-signature-button"
1223 const editToolbar = this.#toolbar = document.createElement("div");
1224 editToolbar.classList.add("editToolbar", "hidden");
1225 editToolbar.setAttribute("role", "toolbar");
1226 const signal = this.#editor._uiManager._signal;
1227 editToolbar.addEventListener("contextmenu", noContextMenu, {
1230 editToolbar.addEventListener("pointerdown", EditorToolbar.#pointerDown, {
1233 const buttons = this.#buttons = document.createElement("div");
1234 buttons.className = "buttons";
1235 editToolbar.append(buttons);
1236 const position = this.#editor.toolbarPosition;
1241 const x = this.#editor._uiManager.direction === "ltr" ? 1 - position[0] : position[0];
1242 style.insetInlineEnd = `${100 * x}%`;
1243 style.top = `calc(${100 * position[1]}% + var(--editor-toolbar-vert-offset))`;
1245 this.#addDeleteButton();
1249 return this.#toolbar;
1251 static #pointerDown(e) {
1252 e.stopPropagation();
1255 this.#editor._focusEventsAllowed = false;
1259 this.#editor._focusEventsAllowed = true;
1262 #addListenersToElement(element) {
1263 const signal = this.#editor._uiManager._signal;
1264 element.addEventListener("focusin", this.#focusIn.bind(this), {
1268 element.addEventListener("focusout", this.#focusOut.bind(this), {
1272 element.addEventListener("contextmenu", noContextMenu, {
1277 this.#toolbar.classList.add("hidden");
1278 this.#colorPicker?.hideDropdown();
1281 this.#toolbar.classList.remove("hidden");
1282 this.#altText?.shown();
1284 #addDeleteButton() {
1289 const button = document.createElement("button");
1290 button.className = "delete";
1291 button.tabIndex = 0;
1292 button.setAttribute("data-l10n-id", EditorToolbar.#l10nRemove[editorType]);
1293 this.#addListenersToElement(button);
1294 button.addEventListener("click", e => {
1295 _uiManager.delete();
1297 signal: _uiManager._signal
1299 this.#buttons.append(button);
1302 const divider = document.createElement("div");
1303 divider.className = "divider";
1306 async addAltText(altText) {
1307 const button = await altText.render();
1308 this.#addListenersToElement(button);
1309 this.#buttons.prepend(button, this.#divider);
1310 this.#altText = altText;
1312 addColorPicker(colorPicker) {
1313 this.#colorPicker = colorPicker;
1314 const button = colorPicker.renderButton();
1315 this.#addListenersToElement(button);
1316 this.#buttons.prepend(button, this.#divider);
1319 this.#toolbar.remove();
1320 this.#colorPicker?.destroy();
1321 this.#colorPicker = null;
1324 class HighlightToolbar {
1328 constructor(uiManager) {
1329 this.#uiManager = uiManager;
1332 const editToolbar = this.#toolbar = document.createElement("div");
1333 editToolbar.className = "editToolbar";
1334 editToolbar.setAttribute("role", "toolbar");
1335 editToolbar.addEventListener("contextmenu", noContextMenu, {
1336 signal: this.#uiManager._signal
1338 const buttons = this.#buttons = document.createElement("div");
1339 buttons.className = "buttons";
1340 editToolbar.append(buttons);
1341 this.#addHighlightButton();
1344 #getLastPoint(boxes, isLTR) {
1347 for (const box of boxes) {
1348 const y = box.y + box.height;
1352 const x = box.x + (isLTR ? box.width : 0);
1362 } else if (x < lastX) {
1366 return [isLTR ? 1 - lastX : lastX, lastY];
1368 show(parent, boxes, isLTR) {
1369 const [x, y] = this.#getLastPoint(boxes, isLTR);
1372 } = this.#toolbar ||= this.#render();
1373 parent.append(this.#toolbar);
1374 style.insetInlineEnd = `${100 * x}%`;
1375 style.top = `calc(${100 * y}% + var(--editor-toolbar-vert-offset))`;
1378 this.#toolbar.remove();
1380 #addHighlightButton() {
1381 const button = document.createElement("button");
1382 button.className = "highlightButton";
1383 button.tabIndex = 0;
1384 button.setAttribute("data-l10n-id", `pdfjs-highlight-floating-button1`);
1385 const span = document.createElement("span");
1386 button.append(span);
1387 span.className = "visuallyHidden";
1388 span.setAttribute("data-l10n-id", "pdfjs-highlight-floating-button-label");
1389 const signal = this.#uiManager._signal;
1390 button.addEventListener("contextmenu", noContextMenu, {
1393 button.addEventListener("click", () => {
1394 this.#uiManager.highlightSelection("floating_button");
1398 this.#buttons.append(button);
1402 ;// ./src/display/editor/tools.js
1406 function bindEvents(obj, element, names) {
1407 for (const name of names) {
1408 element.addEventListener(name, obj[name].bind(obj));
1411 function opacityToHex(opacity) {
1412 return Math.round(Math.min(255, Math.max(1, 255 * opacity))).toString(16).padStart(2, "0");
1417 return `${AnnotationEditorPrefix}${this.#id++}`;
1420 class ImageManager {
1421 #baseId = getUuid();
1424 static get _isSVGFittingCanvas() {
1425 const svg = `data:image/svg+xml;charset=UTF-8,<svg viewBox="0 0 1 1" width="1" height="1" xmlns="http://www.w3.org/2000/svg"><rect width="1" height="1" style="fill:red;"/></svg>`;
1426 const canvas = new OffscreenCanvas(1, 3);
1427 const ctx = canvas.getContext("2d", {
1428 willReadFrequently: true
1430 const image = new Image();
1432 const promise = image.decode().then(() => {
1433 ctx.drawImage(image, 0, 0, 1, 1, 0, 0, 1, 3);
1434 return new Uint32Array(ctx.getImageData(0, 0, 1, 1).data.buffer)[0] === 0;
1436 return shadow(this, "_isSVGFittingCanvas", promise);
1438 async #get(key, rawData) {
1439 this.#cache ||= new Map();
1440 let data = this.#cache.get(key);
1441 if (data === null) {
1445 data.refCounter += 1;
1451 id: `image_${this.#baseId}_${this.#id++}`,
1456 if (typeof rawData === "string") {
1458 image = await fetchData(rawData, "blob");
1459 } else if (rawData instanceof File) {
1460 image = data.file = rawData;
1461 } else if (rawData instanceof Blob) {
1464 if (image.type === "image/svg+xml") {
1465 const mustRemoveAspectRatioPromise = ImageManager._isSVGFittingCanvas;
1466 const fileReader = new FileReader();
1467 const imageElement = new Image();
1468 const imagePromise = new Promise((resolve, reject) => {
1469 imageElement.onload = () => {
1470 data.bitmap = imageElement;
1474 fileReader.onload = async () => {
1475 const url = data.svgUrl = fileReader.result;
1476 imageElement.src = (await mustRemoveAspectRatioPromise) ? `${url}#svgView(preserveAspectRatio(none))` : url;
1478 imageElement.onerror = fileReader.onerror = reject;
1480 fileReader.readAsDataURL(image);
1483 data.bitmap = await createImageBitmap(image);
1485 data.refCounter = 1;
1490 this.#cache.set(key, data);
1492 this.#cache.set(data.id, data);
1496 async getFromFile(file) {
1503 return this.#get(`${lastModified}_${name}_${size}_${type}`, file);
1505 async getFromUrl(url) {
1506 return this.#get(url, url);
1508 async getFromBlob(id, blobPromise) {
1509 const blob = await blobPromise;
1510 return this.#get(id, blob);
1512 async getFromId(id) {
1513 this.#cache ||= new Map();
1514 const data = this.#cache.get(id);
1519 data.refCounter += 1;
1523 return this.getFromFile(data.file);
1525 if (data.blobPromise) {
1529 delete data.blobPromise;
1530 return this.getFromBlob(data.id, blobPromise);
1532 return this.getFromUrl(data.url);
1534 getFromCanvas(id, canvas) {
1535 this.#cache ||= new Map();
1536 let data = this.#cache.get(id);
1538 data.refCounter += 1;
1541 const offscreen = new OffscreenCanvas(canvas.width, canvas.height);
1542 const ctx = offscreen.getContext("2d");
1543 ctx.drawImage(canvas, 0, 0);
1545 bitmap: offscreen.transferToImageBitmap(),
1546 id: `image_${this.#baseId}_${this.#id++}`,
1550 this.#cache.set(id, data);
1551 this.#cache.set(data.id, data);
1555 const data = this.#cache.get(id);
1562 this.#cache ||= new Map();
1563 const data = this.#cache.get(id);
1567 data.refCounter -= 1;
1568 if (data.refCounter !== 0) {
1574 if (!data.url && !data.file) {
1575 const canvas = new OffscreenCanvas(bitmap.width, bitmap.height);
1576 const ctx = canvas.getContext("bitmaprenderer");
1577 ctx.transferFromImageBitmap(bitmap);
1578 data.blobPromise = canvas.convertToBlob();
1584 return id.startsWith(`image_${this.#baseId}_`);
1587 class CommandManager {
1592 constructor(maxSize = 128) {
1593 this.#maxSize = maxSize;
1601 overwriteIfSameType = false,
1616 if (this.#position === -1) {
1617 if (this.#commands.length > 0) {
1618 this.#commands.length = 0;
1621 this.#commands.push(save);
1624 if (overwriteIfSameType && this.#commands[this.#position].type === type) {
1626 save.undo = this.#commands[this.#position].undo;
1628 this.#commands[this.#position] = save;
1631 const next = this.#position + 1;
1632 if (next === this.#maxSize) {
1633 this.#commands.splice(0, 1);
1635 this.#position = next;
1636 if (next < this.#commands.length) {
1637 this.#commands.splice(next);
1640 this.#commands.push(save);
1643 if (this.#position === -1) {
1646 this.#locked = true;
1650 } = this.#commands[this.#position];
1653 this.#locked = false;
1654 this.#position -= 1;
1657 if (this.#position < this.#commands.length - 1) {
1658 this.#position += 1;
1659 this.#locked = true;
1663 } = this.#commands[this.#position];
1666 this.#locked = false;
1669 hasSomethingToUndo() {
1670 return this.#position !== -1;
1672 hasSomethingToRedo() {
1673 return this.#position < this.#commands.length - 1;
1676 if (this.#position === -1) {
1679 for (let i = this.#position; i >= 0; i--) {
1680 if (this.#commands[i].type !== type) {
1681 this.#commands.splice(i + 1, this.#position - i);
1686 this.#commands.length = 0;
1687 this.#position = -1;
1690 this.#commands = null;
1693 class KeyboardManager {
1694 constructor(callbacks) {
1696 this.callbacks = new Map();
1697 this.allKeys = new Set();
1700 } = util_FeatureTest.platform;
1701 for (const [keys, callback, options = {}] of callbacks) {
1702 for (const key of keys) {
1703 const isMacKey = key.startsWith("mac+");
1704 if (isMac && isMacKey) {
1705 this.callbacks.set(key.slice(4), {
1709 this.allKeys.add(key.split("+").at(-1));
1710 } else if (!isMac && !isMacKey) {
1711 this.callbacks.set(key, {
1715 this.allKeys.add(key.split("+").at(-1));
1722 this.buffer.push("alt");
1724 if (event.ctrlKey) {
1725 this.buffer.push("ctrl");
1727 if (event.metaKey) {
1728 this.buffer.push("meta");
1730 if (event.shiftKey) {
1731 this.buffer.push("shift");
1733 this.buffer.push(event.key);
1734 const str = this.buffer.join("+");
1735 this.buffer.length = 0;
1739 if (!this.allKeys.has(event.key)) {
1742 const info = this.callbacks.get(this.#serialize(event));
1754 if (checker && !checker(self, event)) {
1757 callback.bind(self, ...args, event)();
1763 class ColorManager {
1764 static _colorsMapping = new Map([["CanvasText", [0, 0, 0]], ["Canvas", [255, 255, 255]]]);
1766 const colors = new Map([["CanvasText", null], ["Canvas", null]]);
1767 getColorValues(colors);
1768 return shadow(this, "_colors", colors);
1771 const rgb = getRGB(color);
1772 if (!window.matchMedia("(forced-colors: active)").matches) {
1775 for (const [name, RGB] of this._colors) {
1776 if (RGB.every((x, i) => x === rgb[i])) {
1777 return ColorManager._colorsMapping.get(name);
1783 const rgb = this._colors.get(name);
1787 return Util.makeHexColor(...rgb);
1790 class AnnotationEditorUIManager {
1791 #abortController = new AbortController();
1792 #activeEditor = null;
1793 #allEditors = new Map();
1794 #allLayers = new Map();
1795 #altTextManager = null;
1796 #annotationStorage = null;
1797 #changedExistingAnnotations = null;
1798 #commandManager = new CommandManager();
1799 #copyPasteAC = null;
1800 #currentDrawingSession = null;
1801 #currentPageIndex = 0;
1802 #deletedAnnotationsElementIds = new Set();
1803 #draggingEditors = null;
1804 #editorTypes = null;
1805 #editorsToRescale = new Set();
1806 _editorUndoBar = null;
1807 #enableHighlightFloatingButton = false;
1808 #enableUpdatedAddImage = false;
1809 #enableNewAltTextWhenAddingImage = false;
1810 #filterFactory = null;
1811 #focusMainContainerTimeoutId = null;
1812 #focusManagerAC = null;
1813 #highlightColors = null;
1814 #highlightWhenShiftUp = false;
1815 #highlightToolbar = null;
1816 #idManager = new IdManager();
1819 #keyboardManagerAC = null;
1820 #lastActiveElement = null;
1821 #mainHighlightColorPicker = null;
1822 #missingCanvases = null;
1824 #mode = AnnotationEditorType.NONE;
1825 #selectedEditors = new Set();
1826 #selectedTextNode = null;
1827 #signatureManager = null;
1829 #showAllStates = null;
1833 hasSomethingToUndo: false,
1834 hasSomethingToRedo: false,
1835 hasSelectedEditor: false,
1836 hasSelectedText: false
1838 #translation = [0, 0];
1839 #translationTimeoutId = null;
1842 #updateModeCapability = null;
1843 static TRANSLATE_SMALL = 1;
1844 static TRANSLATE_BIG = 10;
1845 static get _keyboardManager() {
1846 const proto = AnnotationEditorUIManager.prototype;
1847 const arrowChecker = self => self.#container.contains(document.activeElement) && document.activeElement.tagName !== "BUTTON" && self.hasSomethingToControl();
1848 const textInputChecker = (_self, {
1851 if (el instanceof HTMLInputElement) {
1855 return type !== "text" && type !== "number";
1859 const small = this.TRANSLATE_SMALL;
1860 const big = this.TRANSLATE_BIG;
1861 return shadow(this, "_keyboardManager", new KeyboardManager([[["ctrl+a", "mac+meta+a"], proto.selectAll, {
1862 checker: textInputChecker
1863 }], [["ctrl+z", "mac+meta+z"], proto.undo, {
1864 checker: textInputChecker
1865 }], [["ctrl+y", "ctrl+shift+z", "mac+meta+shift+z", "ctrl+shift+Z", "mac+meta+shift+Z"], proto.redo, {
1866 checker: textInputChecker
1867 }], [["Backspace", "alt+Backspace", "ctrl+Backspace", "shift+Backspace", "mac+Backspace", "mac+alt+Backspace", "mac+ctrl+Backspace", "Delete", "ctrl+Delete", "shift+Delete", "mac+Delete"], proto.delete, {
1868 checker: textInputChecker
1869 }], [["Enter", "mac+Enter"], proto.addNewEditorFromKeyboard, {
1872 }) => !(el instanceof HTMLButtonElement) && self.#container.contains(el) && !self.isEnterHandled
1873 }], [[" ", "mac+ "], proto.addNewEditorFromKeyboard, {
1876 }) => !(el instanceof HTMLButtonElement) && self.#container.contains(document.activeElement)
1877 }], [["Escape", "mac+Escape"], proto.unselectAll], [["ArrowLeft", "mac+ArrowLeft"], proto.translateSelectedEditors, {
1879 checker: arrowChecker
1880 }], [["ctrl+ArrowLeft", "mac+shift+ArrowLeft"], proto.translateSelectedEditors, {
1882 checker: arrowChecker
1883 }], [["ArrowRight", "mac+ArrowRight"], proto.translateSelectedEditors, {
1885 checker: arrowChecker
1886 }], [["ctrl+ArrowRight", "mac+shift+ArrowRight"], proto.translateSelectedEditors, {
1888 checker: arrowChecker
1889 }], [["ArrowUp", "mac+ArrowUp"], proto.translateSelectedEditors, {
1891 checker: arrowChecker
1892 }], [["ctrl+ArrowUp", "mac+shift+ArrowUp"], proto.translateSelectedEditors, {
1894 checker: arrowChecker
1895 }], [["ArrowDown", "mac+ArrowDown"], proto.translateSelectedEditors, {
1897 checker: arrowChecker
1898 }], [["ctrl+ArrowDown", "mac+shift+ArrowDown"], proto.translateSelectedEditors, {
1900 checker: arrowChecker
1903 constructor(container, viewer, altTextManager, signatureManager, eventBus, pdfDocument, pageColors, highlightColors, enableHighlightFloatingButton, enableUpdatedAddImage, enableNewAltTextWhenAddingImage, mlManager, editorUndoBar, supportsPinchToZoom) {
1904 const signal = this._signal = this.#abortController.signal;
1905 this.#container = container;
1906 this.#viewer = viewer;
1907 this.#altTextManager = altTextManager;
1908 this.#signatureManager = signatureManager;
1909 this._eventBus = eventBus;
1910 eventBus._on("editingaction", this.onEditingAction.bind(this), {
1913 eventBus._on("pagechanging", this.onPageChanging.bind(this), {
1916 eventBus._on("scalechanging", this.onScaleChanging.bind(this), {
1919 eventBus._on("rotationchanging", this.onRotationChanging.bind(this), {
1922 eventBus._on("setpreference", this.onSetPreference.bind(this), {
1925 eventBus._on("switchannotationeditorparams", evt => this.updateParams(evt.type, evt.value), {
1928 this.#addSelectionListener();
1929 this.#addDragAndDropListeners();
1930 this.#addKeyboardManager();
1931 this.#annotationStorage = pdfDocument.annotationStorage;
1932 this.#filterFactory = pdfDocument.filterFactory;
1933 this.#pageColors = pageColors;
1934 this.#highlightColors = highlightColors || null;
1935 this.#enableHighlightFloatingButton = enableHighlightFloatingButton;
1936 this.#enableUpdatedAddImage = enableUpdatedAddImage;
1937 this.#enableNewAltTextWhenAddingImage = enableNewAltTextWhenAddingImage;
1938 this.#mlManager = mlManager || null;
1939 this.viewParameters = {
1940 realScale: PixelsPerInch.PDF_TO_CSS_UNITS,
1943 this.isShiftKeyDown = false;
1944 this._editorUndoBar = editorUndoBar || null;
1945 this._supportsPinchToZoom = supportsPinchToZoom !== false;
1948 this.#updateModeCapability?.resolve();
1949 this.#updateModeCapability = null;
1950 this.#abortController?.abort();
1951 this.#abortController = null;
1952 this._signal = null;
1953 for (const layer of this.#allLayers.values()) {
1956 this.#allLayers.clear();
1957 this.#allEditors.clear();
1958 this.#editorsToRescale.clear();
1959 this.#missingCanvases?.clear();
1960 this.#activeEditor = null;
1961 this.#selectedEditors.clear();
1962 this.#commandManager.destroy();
1963 this.#altTextManager?.destroy();
1964 this.#signatureManager?.destroy();
1965 this.#highlightToolbar?.hide();
1966 this.#highlightToolbar = null;
1967 this.#mainHighlightColorPicker?.destroy();
1968 this.#mainHighlightColorPicker = null;
1969 if (this.#focusMainContainerTimeoutId) {
1970 clearTimeout(this.#focusMainContainerTimeoutId);
1971 this.#focusMainContainerTimeoutId = null;
1973 if (this.#translationTimeoutId) {
1974 clearTimeout(this.#translationTimeoutId);
1975 this.#translationTimeoutId = null;
1977 this._editorUndoBar?.destroy();
1979 combinedSignal(ac) {
1980 return AbortSignal.any([this._signal, ac.signal]);
1983 return this.#mlManager;
1985 get useNewAltTextFlow() {
1986 return this.#enableUpdatedAddImage;
1988 get useNewAltTextWhenAddingImage() {
1989 return this.#enableNewAltTextWhenAddingImage;
1992 return shadow(this, "hcmFilter", this.#pageColors ? this.#filterFactory.addHCMFilter(this.#pageColors.foreground, this.#pageColors.background) : "none");
1995 return shadow(this, "direction", getComputedStyle(this.#container).direction);
1997 get highlightColors() {
1998 return shadow(this, "highlightColors", this.#highlightColors ? new Map(this.#highlightColors.split(",").map(pair => pair.split("=").map(x => x.trim()))) : null);
2000 get highlightColorNames() {
2001 return shadow(this, "highlightColorNames", this.highlightColors ? new Map(Array.from(this.highlightColors, e => e.reverse())) : null);
2003 setCurrentDrawingSession(layer) {
2006 this.disableUserSelect(true);
2008 this.disableUserSelect(false);
2010 this.#currentDrawingSession = layer;
2012 setMainHighlightColorPicker(colorPicker) {
2013 this.#mainHighlightColorPicker = colorPicker;
2015 editAltText(editor, firstTime = false) {
2016 this.#altTextManager?.editAltText(this, editor, firstTime);
2018 getSignature(editor) {
2019 this.#signatureManager?.getSignature({
2024 switchToMode(mode, callback) {
2025 this._eventBus.on("annotationeditormodechanged", callback, {
2027 signal: this._signal
2029 this._eventBus.dispatch("showannotationeditorui", {
2034 setPreference(name, value) {
2035 this._eventBus.dispatch("setpreference", {
2046 case "enableNewAltTextWhenAddingImage":
2047 this.#enableNewAltTextWhenAddingImage = value;
2054 this.#currentPageIndex = pageNumber - 1;
2056 focusMainContainer() {
2057 this.#container.focus();
2060 for (const layer of this.#allLayers.values()) {
2066 } = layer.div.getBoundingClientRect();
2067 if (x >= layerX && x <= layerX + width && y >= layerY && y <= layerY + height) {
2073 disableUserSelect(value = false) {
2074 this.#viewer.classList.toggle("noUserSelect", value);
2076 addShouldRescale(editor) {
2077 this.#editorsToRescale.add(editor);
2079 removeShouldRescale(editor) {
2080 this.#editorsToRescale.delete(editor);
2085 this.commitOrRemove();
2086 this.viewParameters.realScale = scale * PixelsPerInch.PDF_TO_CSS_UNITS;
2087 for (const editor of this.#editorsToRescale) {
2088 editor.onScaleChanging();
2090 this.#currentDrawingSession?.onScaleChanging();
2092 onRotationChanging({
2095 this.commitOrRemove();
2096 this.viewParameters.rotation = pagesRotation;
2098 #getAnchorElementForSelection({
2101 return anchorNode.nodeType === Node.TEXT_NODE ? anchorNode.parentElement : anchorNode;
2103 #getLayerForTextLayer(textLayer) {
2107 if (currentLayer.hasTextLayer(textLayer)) {
2108 return currentLayer;
2110 for (const layer of this.#allLayers.values()) {
2111 if (layer.hasTextLayer(textLayer)) {
2117 highlightSelection(methodOfCreation = "") {
2118 const selection = document.getSelection();
2119 if (!selection || selection.isCollapsed) {
2128 const text = selection.toString();
2129 const anchorElement = this.#getAnchorElementForSelection(selection);
2130 const textLayer = anchorElement.closest(".textLayer");
2131 const boxes = this.getSelectionBoxes(textLayer);
2136 const layer = this.#getLayerForTextLayer(textLayer);
2137 const isNoneMode = this.#mode === AnnotationEditorType.NONE;
2138 const callback = () => {
2139 layer?.createAndAddNewEditor({
2152 this.showAllEditors("highlight", true, true);
2156 this.switchToMode(AnnotationEditorType.HIGHLIGHT, callback);
2161 #displayHighlightToolbar() {
2162 const selection = document.getSelection();
2163 if (!selection || selection.isCollapsed) {
2166 const anchorElement = this.#getAnchorElementForSelection(selection);
2167 const textLayer = anchorElement.closest(".textLayer");
2168 const boxes = this.getSelectionBoxes(textLayer);
2172 this.#highlightToolbar ||= new HighlightToolbar(this);
2173 this.#highlightToolbar.show(textLayer, boxes, this.direction === "ltr");
2175 addToAnnotationStorage(editor) {
2176 if (!editor.isEmpty() && this.#annotationStorage && !this.#annotationStorage.has(editor.id)) {
2177 this.#annotationStorage.setValue(editor.id, editor);
2180 #selectionChange() {
2181 const selection = document.getSelection();
2182 if (!selection || selection.isCollapsed) {
2183 if (this.#selectedTextNode) {
2184 this.#highlightToolbar?.hide();
2185 this.#selectedTextNode = null;
2186 this.#dispatchUpdateStates({
2187 hasSelectedText: false
2195 if (anchorNode === this.#selectedTextNode) {
2198 const anchorElement = this.#getAnchorElementForSelection(selection);
2199 const textLayer = anchorElement.closest(".textLayer");
2201 if (this.#selectedTextNode) {
2202 this.#highlightToolbar?.hide();
2203 this.#selectedTextNode = null;
2204 this.#dispatchUpdateStates({
2205 hasSelectedText: false
2210 this.#highlightToolbar?.hide();
2211 this.#selectedTextNode = anchorNode;
2212 this.#dispatchUpdateStates({
2213 hasSelectedText: true
2215 if (this.#mode !== AnnotationEditorType.HIGHLIGHT && this.#mode !== AnnotationEditorType.NONE) {
2218 if (this.#mode === AnnotationEditorType.HIGHLIGHT) {
2219 this.showAllEditors("highlight", true, true);
2221 this.#highlightWhenShiftUp = this.isShiftKeyDown;
2222 if (!this.isShiftKeyDown) {
2223 const activeLayer = this.#mode === AnnotationEditorType.HIGHLIGHT ? this.#getLayerForTextLayer(textLayer) : null;
2224 activeLayer?.toggleDrawing();
2225 const ac = new AbortController();
2226 const signal = this.combinedSignal(ac);
2227 const pointerup = e => {
2228 if (e.type === "pointerup" && e.button !== 0) {
2232 activeLayer?.toggleDrawing(true);
2233 if (e.type === "pointerup") {
2234 this.#onSelectEnd("main_toolbar");
2237 window.addEventListener("pointerup", pointerup, {
2240 window.addEventListener("blur", pointerup, {
2245 #onSelectEnd(methodOfCreation = "") {
2246 if (this.#mode === AnnotationEditorType.HIGHLIGHT) {
2247 this.highlightSelection(methodOfCreation);
2248 } else if (this.#enableHighlightFloatingButton) {
2249 this.#displayHighlightToolbar();
2252 #addSelectionListener() {
2253 document.addEventListener("selectionchange", this.#selectionChange.bind(this), {
2254 signal: this._signal
2257 #addFocusManager() {
2258 if (this.#focusManagerAC) {
2261 this.#focusManagerAC = new AbortController();
2262 const signal = this.combinedSignal(this.#focusManagerAC);
2263 window.addEventListener("focus", this.focus.bind(this), {
2266 window.addEventListener("blur", this.blur.bind(this), {
2270 #removeFocusManager() {
2271 this.#focusManagerAC?.abort();
2272 this.#focusManagerAC = null;
2275 this.isShiftKeyDown = false;
2276 if (this.#highlightWhenShiftUp) {
2277 this.#highlightWhenShiftUp = false;
2278 this.#onSelectEnd("main_toolbar");
2280 if (!this.hasSelection) {
2286 for (const editor of this.#selectedEditors) {
2287 if (editor.div.contains(activeElement)) {
2288 this.#lastActiveElement = [editor, activeElement];
2289 editor._focusEventsAllowed = false;
2295 if (!this.#lastActiveElement) {
2298 const [lastEditor, lastActiveElement] = this.#lastActiveElement;
2299 this.#lastActiveElement = null;
2300 lastActiveElement.addEventListener("focusin", () => {
2301 lastEditor._focusEventsAllowed = true;
2304 signal: this._signal
2306 lastActiveElement.focus();
2308 #addKeyboardManager() {
2309 if (this.#keyboardManagerAC) {
2312 this.#keyboardManagerAC = new AbortController();
2313 const signal = this.combinedSignal(this.#keyboardManagerAC);
2314 window.addEventListener("keydown", this.keydown.bind(this), {
2317 window.addEventListener("keyup", this.keyup.bind(this), {
2321 #removeKeyboardManager() {
2322 this.#keyboardManagerAC?.abort();
2323 this.#keyboardManagerAC = null;
2325 #addCopyPasteListeners() {
2326 if (this.#copyPasteAC) {
2329 this.#copyPasteAC = new AbortController();
2330 const signal = this.combinedSignal(this.#copyPasteAC);
2331 document.addEventListener("copy", this.copy.bind(this), {
2334 document.addEventListener("cut", this.cut.bind(this), {
2337 document.addEventListener("paste", this.paste.bind(this), {
2341 #removeCopyPasteListeners() {
2342 this.#copyPasteAC?.abort();
2343 this.#copyPasteAC = null;
2345 #addDragAndDropListeners() {
2346 const signal = this._signal;
2347 document.addEventListener("dragover", this.dragOver.bind(this), {
2350 document.addEventListener("drop", this.drop.bind(this), {
2354 addEditListeners() {
2355 this.#addKeyboardManager();
2356 this.#addCopyPasteListeners();
2358 removeEditListeners() {
2359 this.#removeKeyboardManager();
2360 this.#removeCopyPasteListeners();
2365 } of event.dataTransfer.items) {
2366 for (const editorType of this.#editorTypes) {
2367 if (editorType.isHandlingMimeForPasting(type)) {
2368 event.dataTransfer.dropEffect = "copy";
2369 event.preventDefault();
2376 for (const item of event.dataTransfer.items) {
2377 for (const editorType of this.#editorTypes) {
2378 if (editorType.isHandlingMimeForPasting(item.type)) {
2379 editorType.paste(item, this.currentLayer);
2380 event.preventDefault();
2387 event.preventDefault();
2388 this.#activeEditor?.commitOrRemove();
2389 if (!this.hasSelection) {
2393 for (const editor of this.#selectedEditors) {
2394 const serialized = editor.serialize(true);
2396 editors.push(serialized);
2399 if (editors.length === 0) {
2402 event.clipboardData.setData("application/pdfjs", JSON.stringify(editors));
2408 async paste(event) {
2409 event.preventDefault();
2413 for (const item of clipboardData.items) {
2414 for (const editorType of this.#editorTypes) {
2415 if (editorType.isHandlingMimeForPasting(item.type)) {
2416 editorType.paste(item, this.currentLayer);
2421 let data = clipboardData.getData("application/pdfjs");
2426 data = JSON.parse(data);
2428 warn(`paste: "${ex.message}".`);
2431 if (!Array.isArray(data)) {
2435 const layer = this.currentLayer;
2437 const newEditors = [];
2438 for (const editor of data) {
2439 const deserializedEditor = await layer.deserialize(editor);
2440 if (!deserializedEditor) {
2443 newEditors.push(deserializedEditor);
2446 for (const editor of newEditors) {
2447 this.#addEditorToLayer(editor);
2449 this.#selectEditors(newEditors);
2451 const undo = () => {
2452 for (const editor of newEditors) {
2462 warn(`paste: "${ex.message}".`);
2466 if (!this.isShiftKeyDown && event.key === "Shift") {
2467 this.isShiftKeyDown = true;
2469 if (this.#mode !== AnnotationEditorType.NONE && !this.isEditorHandlingKeyboard) {
2470 AnnotationEditorUIManager._keyboardManager.exec(this, event);
2474 if (this.isShiftKeyDown && event.key === "Shift") {
2475 this.isShiftKeyDown = false;
2476 if (this.#highlightWhenShiftUp) {
2477 this.#highlightWhenShiftUp = false;
2478 this.#onSelectEnd("main_toolbar");
2492 case "highlightSelection":
2493 this.highlightSelection("context_menu");
2497 #dispatchUpdateStates(details) {
2498 const hasChanged = Object.entries(details).some(([key, value]) => this.#previousStates[key] !== value);
2500 this._eventBus.dispatch("annotationeditorstateschanged", {
2502 details: Object.assign(this.#previousStates, details)
2504 if (this.#mode === AnnotationEditorType.HIGHLIGHT && details.hasSelectedEditor === false) {
2505 this.#dispatchUpdateUI([[AnnotationEditorParamsType.HIGHLIGHT_FREE, true]]);
2509 #dispatchUpdateUI(details) {
2510 this._eventBus.dispatch("annotationeditorparamschanged", {
2515 setEditingState(isEditing) {
2517 this.#addFocusManager();
2518 this.#addCopyPasteListeners();
2519 this.#dispatchUpdateStates({
2520 isEditing: this.#mode !== AnnotationEditorType.NONE,
2521 isEmpty: this.#isEmpty(),
2522 hasSomethingToUndo: this.#commandManager.hasSomethingToUndo(),
2523 hasSomethingToRedo: this.#commandManager.hasSomethingToRedo(),
2524 hasSelectedEditor: false
2527 this.#removeFocusManager();
2528 this.#removeCopyPasteListeners();
2529 this.#dispatchUpdateStates({
2532 this.disableUserSelect(false);
2535 registerEditorTypes(types) {
2536 if (this.#editorTypes) {
2539 this.#editorTypes = types;
2540 for (const editorType of this.#editorTypes) {
2541 this.#dispatchUpdateUI(editorType.defaultPropertiesToUpdate);
2545 return this.#idManager.id;
2547 get currentLayer() {
2548 return this.#allLayers.get(this.#currentPageIndex);
2550 getLayer(pageIndex) {
2551 return this.#allLayers.get(pageIndex);
2553 get currentPageIndex() {
2554 return this.#currentPageIndex;
2557 this.#allLayers.set(layer.pageIndex, layer);
2558 if (this.#isEnabled) {
2564 removeLayer(layer) {
2565 this.#allLayers.delete(layer.pageIndex);
2567 async updateMode(mode, editId = null, isFromKeyboard = false) {
2568 if (this.#mode === mode) {
2571 if (this.#updateModeCapability) {
2572 await this.#updateModeCapability.promise;
2573 if (!this.#updateModeCapability) {
2577 this.#updateModeCapability = Promise.withResolvers();
2579 if (mode === AnnotationEditorType.NONE) {
2580 this.setEditingState(false);
2582 this._editorUndoBar?.hide();
2583 this.#updateModeCapability.resolve();
2586 this.setEditingState(true);
2587 await this.#enableAll();
2589 for (const layer of this.#allLayers.values()) {
2590 layer.updateMode(mode);
2593 if (isFromKeyboard) {
2594 this.addNewEditorFromKeyboard();
2596 this.#updateModeCapability.resolve();
2599 for (const editor of this.#allEditors.values()) {
2600 if (editor.annotationElementId === editId) {
2601 this.setSelected(editor);
2602 editor.enterInEditMode();
2607 this.#updateModeCapability.resolve();
2609 addNewEditorFromKeyboard() {
2610 if (this.currentLayer.canCreateNewEmptyEditor()) {
2611 this.currentLayer.addNewEditor();
2614 updateToolbar(mode) {
2615 if (mode === this.#mode) {
2618 this._eventBus.dispatch("switchannotationeditormode", {
2623 updateParams(type, value) {
2624 if (!this.#editorTypes) {
2628 case AnnotationEditorParamsType.CREATE:
2629 this.currentLayer.addNewEditor();
2631 case AnnotationEditorParamsType.HIGHLIGHT_DEFAULT_COLOR:
2632 this.#mainHighlightColorPicker?.updateColor(value);
2634 case AnnotationEditorParamsType.HIGHLIGHT_SHOW_ALL:
2635 this._eventBus.dispatch("reporttelemetry", {
2641 action: "toggle_visibility"
2645 (this.#showAllStates ||= new Map()).set(type, value);
2646 this.showAllEditors("highlight", value);
2649 for (const editor of this.#selectedEditors) {
2650 editor.updateParams(type, value);
2652 for (const editorType of this.#editorTypes) {
2653 editorType.updateDefaultParams(type, value);
2656 showAllEditors(type, visible, updateButton = false) {
2657 for (const editor of this.#allEditors.values()) {
2658 if (editor.editorType === type) {
2659 editor.show(visible);
2662 const state = this.#showAllStates?.get(AnnotationEditorParamsType.HIGHLIGHT_SHOW_ALL) ?? true;
2663 if (state !== visible) {
2664 this.#dispatchUpdateUI([[AnnotationEditorParamsType.HIGHLIGHT_SHOW_ALL, visible]]);
2667 enableWaiting(mustWait = false) {
2668 if (this.#isWaiting === mustWait) {
2671 this.#isWaiting = mustWait;
2672 for (const layer of this.#allLayers.values()) {
2674 layer.disableClick();
2676 layer.enableClick();
2678 layer.div.classList.toggle("waiting", mustWait);
2681 async #enableAll() {
2682 if (!this.#isEnabled) {
2683 this.#isEnabled = true;
2684 const promises = [];
2685 for (const layer of this.#allLayers.values()) {
2686 promises.push(layer.enable());
2688 await Promise.all(promises);
2689 for (const editor of this.#allEditors.values()) {
2696 if (this.#isEnabled) {
2697 this.#isEnabled = false;
2698 for (const layer of this.#allLayers.values()) {
2701 for (const editor of this.#allEditors.values()) {
2706 getEditors(pageIndex) {
2708 for (const editor of this.#allEditors.values()) {
2709 if (editor.pageIndex === pageIndex) {
2710 editors.push(editor);
2716 return this.#allEditors.get(id);
2719 this.#allEditors.set(editor.id, editor);
2721 removeEditor(editor) {
2722 if (editor.div.contains(document.activeElement)) {
2723 if (this.#focusMainContainerTimeoutId) {
2724 clearTimeout(this.#focusMainContainerTimeoutId);
2726 this.#focusMainContainerTimeoutId = setTimeout(() => {
2727 this.focusMainContainer();
2728 this.#focusMainContainerTimeoutId = null;
2731 this.#allEditors.delete(editor.id);
2732 if (editor.annotationElementId) {
2733 this.#missingCanvases?.delete(editor.annotationElementId);
2735 this.unselect(editor);
2736 if (!editor.annotationElementId || !this.#deletedAnnotationsElementIds.has(editor.annotationElementId)) {
2737 this.#annotationStorage?.remove(editor.id);
2740 addDeletedAnnotationElement(editor) {
2741 this.#deletedAnnotationsElementIds.add(editor.annotationElementId);
2742 this.addChangedExistingAnnotation(editor);
2743 editor.deleted = true;
2745 isDeletedAnnotationElement(annotationElementId) {
2746 return this.#deletedAnnotationsElementIds.has(annotationElementId);
2748 removeDeletedAnnotationElement(editor) {
2749 this.#deletedAnnotationsElementIds.delete(editor.annotationElementId);
2750 this.removeChangedExistingAnnotation(editor);
2751 editor.deleted = false;
2753 #addEditorToLayer(editor) {
2754 const layer = this.#allLayers.get(editor.pageIndex);
2756 layer.addOrRebuild(editor);
2758 this.addEditor(editor);
2759 this.addToAnnotationStorage(editor);
2762 setActiveEditor(editor) {
2763 if (this.#activeEditor === editor) {
2766 this.#activeEditor = editor;
2768 this.#dispatchUpdateUI(editor.propertiesToUpdate);
2771 get #lastSelectedEditor() {
2773 for (ed of this.#selectedEditors) {}
2777 if (this.#lastSelectedEditor === editor) {
2778 this.#dispatchUpdateUI(editor.propertiesToUpdate);
2781 updateUIForDefaultProperties(editorType) {
2782 this.#dispatchUpdateUI(editorType.defaultPropertiesToUpdate);
2784 toggleSelected(editor) {
2785 if (this.#selectedEditors.has(editor)) {
2786 this.#selectedEditors.delete(editor);
2788 this.#dispatchUpdateStates({
2789 hasSelectedEditor: this.hasSelection
2793 this.#selectedEditors.add(editor);
2795 this.#dispatchUpdateUI(editor.propertiesToUpdate);
2796 this.#dispatchUpdateStates({
2797 hasSelectedEditor: true
2800 setSelected(editor) {
2801 this.#currentDrawingSession?.commitOrRemove();
2802 for (const ed of this.#selectedEditors) {
2803 if (ed !== editor) {
2807 this.#selectedEditors.clear();
2808 this.#selectedEditors.add(editor);
2810 this.#dispatchUpdateUI(editor.propertiesToUpdate);
2811 this.#dispatchUpdateStates({
2812 hasSelectedEditor: true
2815 isSelected(editor) {
2816 return this.#selectedEditors.has(editor);
2818 get firstSelectedEditor() {
2819 return this.#selectedEditors.values().next().value;
2823 this.#selectedEditors.delete(editor);
2824 this.#dispatchUpdateStates({
2825 hasSelectedEditor: this.hasSelection
2828 get hasSelection() {
2829 return this.#selectedEditors.size !== 0;
2831 get isEnterHandled() {
2832 return this.#selectedEditors.size === 1 && this.firstSelectedEditor.isEnterHandled;
2835 this.#commandManager.undo();
2836 this.#dispatchUpdateStates({
2837 hasSomethingToUndo: this.#commandManager.hasSomethingToUndo(),
2838 hasSomethingToRedo: true,
2839 isEmpty: this.#isEmpty()
2841 this._editorUndoBar?.hide();
2844 this.#commandManager.redo();
2845 this.#dispatchUpdateStates({
2846 hasSomethingToUndo: true,
2847 hasSomethingToRedo: this.#commandManager.hasSomethingToRedo(),
2848 isEmpty: this.#isEmpty()
2851 addCommands(params) {
2852 this.#commandManager.add(params);
2853 this.#dispatchUpdateStates({
2854 hasSomethingToUndo: true,
2855 hasSomethingToRedo: false,
2856 isEmpty: this.#isEmpty()
2859 cleanUndoStack(type) {
2860 this.#commandManager.cleanType(type);
2863 if (this.#allEditors.size === 0) {
2866 if (this.#allEditors.size === 1) {
2867 for (const editor of this.#allEditors.values()) {
2868 return editor.isEmpty();
2874 this.commitOrRemove();
2875 const drawingEditor = this.currentLayer?.endDrawingSession(true);
2876 if (!this.hasSelection && !drawingEditor) {
2879 const editors = drawingEditor ? [drawingEditor] : [...this.#selectedEditors];
2881 this._editorUndoBar?.show(undo, editors.length === 1 ? editors[0].editorType : editors.length);
2882 for (const editor of editors) {
2886 const undo = () => {
2887 for (const editor of editors) {
2888 this.#addEditorToLayer(editor);
2898 this.#activeEditor?.commitOrRemove();
2900 hasSomethingToControl() {
2901 return this.#activeEditor || this.hasSelection;
2903 #selectEditors(editors) {
2904 for (const editor of this.#selectedEditors) {
2907 this.#selectedEditors.clear();
2908 for (const editor of editors) {
2909 if (editor.isEmpty()) {
2912 this.#selectedEditors.add(editor);
2915 this.#dispatchUpdateStates({
2916 hasSelectedEditor: this.hasSelection
2920 for (const editor of this.#selectedEditors) {
2923 this.#selectEditors(this.#allEditors.values());
2926 if (this.#activeEditor) {
2927 this.#activeEditor.commitOrRemove();
2928 if (this.#mode !== AnnotationEditorType.NONE) {
2932 if (this.#currentDrawingSession?.commitOrRemove()) {
2935 if (!this.hasSelection) {
2938 for (const editor of this.#selectedEditors) {
2941 this.#selectedEditors.clear();
2942 this.#dispatchUpdateStates({
2943 hasSelectedEditor: false
2946 translateSelectedEditors(x, y, noCommit = false) {
2948 this.commitOrRemove();
2950 if (!this.hasSelection) {
2953 this.#translation[0] += x;
2954 this.#translation[1] += y;
2955 const [totalX, totalY] = this.#translation;
2956 const editors = [...this.#selectedEditors];
2957 const TIME_TO_WAIT = 1000;
2958 if (this.#translationTimeoutId) {
2959 clearTimeout(this.#translationTimeoutId);
2961 this.#translationTimeoutId = setTimeout(() => {
2962 this.#translationTimeoutId = null;
2963 this.#translation[0] = this.#translation[1] = 0;
2966 for (const editor of editors) {
2967 if (this.#allEditors.has(editor.id)) {
2968 editor.translateInPage(totalX, totalY);
2973 for (const editor of editors) {
2974 if (this.#allEditors.has(editor.id)) {
2975 editor.translateInPage(-totalX, -totalY);
2982 for (const editor of editors) {
2983 editor.translateInPage(x, y);
2986 setUpDragSession() {
2987 if (!this.hasSelection) {
2990 this.disableUserSelect(true);
2991 this.#draggingEditors = new Map();
2992 for (const editor of this.#selectedEditors) {
2993 this.#draggingEditors.set(editor, {
2996 savedPageIndex: editor.pageIndex,
3004 if (!this.#draggingEditors) {
3007 this.disableUserSelect(false);
3008 const map = this.#draggingEditors;
3009 this.#draggingEditors = null;
3010 let mustBeAddedInUndoStack = false;
3018 value.newPageIndex = pageIndex;
3019 mustBeAddedInUndoStack ||= x !== value.savedX || y !== value.savedY || pageIndex !== value.savedPageIndex;
3021 if (!mustBeAddedInUndoStack) {
3024 const move = (editor, x, y, pageIndex) => {
3025 if (this.#allEditors.has(editor.id)) {
3026 const parent = this.#allLayers.get(pageIndex);
3028 editor._setParentAndPosition(parent, x, y);
3030 editor.pageIndex = pageIndex;
3038 for (const [editor, {
3043 move(editor, newX, newY, newPageIndex);
3047 for (const [editor, {
3052 move(editor, savedX, savedY, savedPageIndex);
3059 dragSelectedEditors(tx, ty) {
3060 if (!this.#draggingEditors) {
3063 for (const editor of this.#draggingEditors.keys()) {
3064 editor.drag(tx, ty);
3068 if (editor.parent === null) {
3069 const parent = this.getLayer(editor.pageIndex);
3071 parent.changeParent(editor);
3072 parent.addOrRebuild(editor);
3074 this.addEditor(editor);
3075 this.addToAnnotationStorage(editor);
3079 editor.parent.addOrRebuild(editor);
3082 get isEditorHandlingKeyboard() {
3083 return this.getActive()?.shouldGetKeyboardEvents() || this.#selectedEditors.size === 1 && this.firstSelectedEditor.shouldGetKeyboardEvents();
3086 return this.#activeEditor === editor;
3089 return this.#activeEditor;
3094 get imageManager() {
3095 return shadow(this, "imageManager", new ImageManager());
3097 getSelectionBoxes(textLayer) {
3101 const selection = document.getSelection();
3102 for (let i = 0, ii = selection.rangeCount; i < ii; i++) {
3103 if (!textLayer.contains(selection.getRangeAt(i).commonAncestorContainer)) {
3111 height: parentHeight
3112 } = textLayer.getBoundingClientRect();
3114 switch (textLayer.getAttribute("data-main-rotation")) {
3116 rotator = (x, y, w, h) => ({
3117 x: (y - layerY) / parentHeight,
3118 y: 1 - (x + w - layerX) / parentWidth,
3119 width: h / parentHeight,
3120 height: w / parentWidth
3124 rotator = (x, y, w, h) => ({
3125 x: 1 - (x + w - layerX) / parentWidth,
3126 y: 1 - (y + h - layerY) / parentHeight,
3127 width: w / parentWidth,
3128 height: h / parentHeight
3132 rotator = (x, y, w, h) => ({
3133 x: 1 - (y + h - layerY) / parentHeight,
3134 y: (x - layerX) / parentWidth,
3135 width: h / parentHeight,
3136 height: w / parentWidth
3140 rotator = (x, y, w, h) => ({
3141 x: (x - layerX) / parentWidth,
3142 y: (y - layerY) / parentHeight,
3143 width: w / parentWidth,
3144 height: h / parentHeight
3149 for (let i = 0, ii = selection.rangeCount; i < ii; i++) {
3150 const range = selection.getRangeAt(i);
3151 if (range.collapsed) {
3159 } of range.getClientRects()) {
3160 if (width === 0 || height === 0) {
3163 boxes.push(rotator(x, y, width, height));
3166 return boxes.length === 0 ? null : boxes;
3168 addChangedExistingAnnotation({
3169 annotationElementId,
3172 (this.#changedExistingAnnotations ||= new Map()).set(annotationElementId, id);
3174 removeChangedExistingAnnotation({
3177 this.#changedExistingAnnotations?.delete(annotationElementId);
3179 renderAnnotationElement(annotation) {
3180 const editorId = this.#changedExistingAnnotations?.get(annotation.data.id);
3184 const editor = this.#annotationStorage.getRawValue(editorId);
3188 if (this.#mode === AnnotationEditorType.NONE && !editor.hasBeenModified) {
3191 editor.renderAnnotationElement(annotation);
3193 setMissingCanvas(annotationId, annotationElementId, canvas) {
3194 const editor = this.#missingCanvases?.get(annotationId);
3198 editor.setCanvas(annotationElementId, canvas);
3199 this.#missingCanvases.delete(annotationId);
3201 addMissingCanvas(annotationId, editor) {
3202 (this.#missingCanvases ||= new Map()).set(annotationId, editor);
3206 ;// ./src/display/editor/alt_text.js
3210 #altTextDecorative = false;
3211 #altTextButton = null;
3212 #altTextButtonLabel = null;
3213 #altTextTooltip = null;
3214 #altTextTooltipTimeout = null;
3215 #altTextWasFromKeyBoard = false;
3218 #guessedText = null;
3219 #textWithDisclaimer = null;
3220 #useNewAltTextFlow = false;
3221 static #l10nNewButton = null;
3222 static _l10n = null;
3223 constructor(editor) {
3224 this.#editor = editor;
3225 this.#useNewAltTextFlow = editor._uiManager.useNewAltTextFlow;
3226 AltText.#l10nNewButton ||= Object.freeze({
3227 added: "pdfjs-editor-new-alt-text-added-button",
3228 "added-label": "pdfjs-editor-new-alt-text-added-button-label",
3229 missing: "pdfjs-editor-new-alt-text-missing-button",
3230 "missing-label": "pdfjs-editor-new-alt-text-missing-button-label",
3231 review: "pdfjs-editor-new-alt-text-to-review-button",
3232 "review-label": "pdfjs-editor-new-alt-text-to-review-button-label"
3235 static initialize(l10n) {
3236 AltText._l10n ??= l10n;
3239 const altText = this.#altTextButton = document.createElement("button");
3240 altText.className = "altText";
3241 altText.tabIndex = "0";
3242 const label = this.#altTextButtonLabel = document.createElement("span");
3243 altText.append(label);
3244 if (this.#useNewAltTextFlow) {
3245 altText.classList.add("new");
3246 altText.setAttribute("data-l10n-id", AltText.#l10nNewButton.missing);
3247 label.setAttribute("data-l10n-id", AltText.#l10nNewButton["missing-label"]);
3249 altText.setAttribute("data-l10n-id", "pdfjs-editor-alt-text-button");
3250 label.setAttribute("data-l10n-id", "pdfjs-editor-alt-text-button-label");
3252 const signal = this.#editor._uiManager._signal;
3253 altText.addEventListener("contextmenu", noContextMenu, {
3256 altText.addEventListener("pointerdown", event => event.stopPropagation(), {
3259 const onClick = event => {
3260 event.preventDefault();
3261 this.#editor._uiManager.editAltText(this.#editor);
3262 if (this.#useNewAltTextFlow) {
3263 this.#editor._reportTelemetry({
3264 action: "pdfjs.image.alt_text.image_status_label_clicked",
3271 altText.addEventListener("click", onClick, {
3275 altText.addEventListener("keydown", event => {
3276 if (event.target === altText && event.key === "Enter") {
3277 this.#altTextWasFromKeyBoard = true;
3283 await this.#setState();
3287 return this.#altText && "added" || this.#altText === null && this.guessedText && "review" || "missing";
3290 if (!this.#altTextButton) {
3293 this.#altTextButton.focus({
3294 focusVisible: this.#altTextWasFromKeyBoard
3296 this.#altTextWasFromKeyBoard = false;
3299 if (this.#useNewAltTextFlow) {
3300 return this.#altText === null;
3302 return !this.#altText && !this.#altTextDecorative;
3305 if (this.#useNewAltTextFlow) {
3306 return this.#altText !== null || !!this.#guessedText;
3308 return this.isEmpty();
3311 return this.#guessedText;
3313 async setGuessedText(guessedText) {
3314 if (this.#altText !== null) {
3317 this.#guessedText = guessedText;
3318 this.#textWithDisclaimer = await AltText._l10n.get("pdfjs-editor-new-alt-text-generated-alt-text-with-disclaimer", {
3319 generatedAltText: guessedText
3323 toggleAltTextBadge(visibility = false) {
3324 if (!this.#useNewAltTextFlow || this.#altText) {
3325 this.#badge?.remove();
3330 const badge = this.#badge = document.createElement("div");
3331 badge.className = "noAltTextBadge";
3332 this.#editor.div.append(badge);
3334 this.#badge.classList.toggle("hidden", !visibility);
3336 serialize(isForCopying) {
3337 let altText = this.#altText;
3338 if (!isForCopying && this.#guessedText === altText) {
3339 altText = this.#textWithDisclaimer;
3343 decorative: this.#altTextDecorative,
3344 guessedText: this.#guessedText,
3345 textWithDisclaimer: this.#textWithDisclaimer
3350 altText: this.#altText,
3351 decorative: this.#altTextDecorative
3362 this.#guessedText = guessedText;
3363 this.#textWithDisclaimer = textWithDisclaimer;
3365 if (this.#altText === altText && this.#altTextDecorative === decorative) {
3369 this.#altText = altText;
3370 this.#altTextDecorative = decorative;
3374 toggle(enabled = false) {
3375 if (!this.#altTextButton) {
3378 if (!enabled && this.#altTextTooltipTimeout) {
3379 clearTimeout(this.#altTextTooltipTimeout);
3380 this.#altTextTooltipTimeout = null;
3382 this.#altTextButton.disabled = !enabled;
3385 this.#editor._reportTelemetry({
3386 action: "pdfjs.image.alt_text.image_status_label_displayed",
3393 this.#altTextButton?.remove();
3394 this.#altTextButton = null;
3395 this.#altTextButtonLabel = null;
3396 this.#altTextTooltip = null;
3397 this.#badge?.remove();
3401 const button = this.#altTextButton;
3405 if (this.#useNewAltTextFlow) {
3406 button.classList.toggle("done", !!this.#altText);
3407 button.setAttribute("data-l10n-id", AltText.#l10nNewButton[this.#label]);
3408 this.#altTextButtonLabel?.setAttribute("data-l10n-id", AltText.#l10nNewButton[`${this.#label}-label`]);
3409 if (!this.#altText) {
3410 this.#altTextTooltip?.remove();
3414 if (!this.#altText && !this.#altTextDecorative) {
3415 button.classList.remove("done");
3416 this.#altTextTooltip?.remove();
3419 button.classList.add("done");
3420 button.setAttribute("data-l10n-id", "pdfjs-editor-alt-text-edit-button");
3422 let tooltip = this.#altTextTooltip;
3424 this.#altTextTooltip = tooltip = document.createElement("span");
3425 tooltip.className = "tooltip";
3426 tooltip.setAttribute("role", "tooltip");
3427 tooltip.id = `alt-text-tooltip-${this.#editor.id}`;
3428 const DELAY_TO_SHOW_TOOLTIP = 100;
3429 const signal = this.#editor._uiManager._signal;
3430 signal.addEventListener("abort", () => {
3431 clearTimeout(this.#altTextTooltipTimeout);
3432 this.#altTextTooltipTimeout = null;
3436 button.addEventListener("mouseenter", () => {
3437 this.#altTextTooltipTimeout = setTimeout(() => {
3438 this.#altTextTooltipTimeout = null;
3439 this.#altTextTooltip.classList.add("show");
3440 this.#editor._reportTelemetry({
3441 action: "alt_text_tooltip"
3443 }, DELAY_TO_SHOW_TOOLTIP);
3447 button.addEventListener("mouseleave", () => {
3448 if (this.#altTextTooltipTimeout) {
3449 clearTimeout(this.#altTextTooltipTimeout);
3450 this.#altTextTooltipTimeout = null;
3452 this.#altTextTooltip?.classList.remove("show");
3457 if (this.#altTextDecorative) {
3458 tooltip.setAttribute("data-l10n-id", "pdfjs-editor-alt-text-decorative-tooltip");
3460 tooltip.removeAttribute("data-l10n-id");
3461 tooltip.textContent = this.#altText;
3463 if (!tooltip.parentNode) {
3464 button.append(tooltip);
3466 const element = this.#editor.getImageForAltText();
3467 element?.setAttribute("aria-describedby", tooltip.id);
3471 ;// ./src/display/touch_manager.js
3474 class TouchManager {
3476 #isPinching = false;
3477 #isPinchingStopped = null;
3478 #isPinchingDisabled;
3482 #pointerDownAC = null;
3486 #touchMoveAC = null;
3489 isPinchingDisabled = null,
3490 isPinchingStopped = null,
3491 onPinchStart = null,
3496 this.#container = container;
3497 this.#isPinchingStopped = isPinchingStopped;
3498 this.#isPinchingDisabled = isPinchingDisabled;
3499 this.#onPinchStart = onPinchStart;
3500 this.#onPinching = onPinching;
3501 this.#onPinchEnd = onPinchEnd;
3502 this.#touchManagerAC = new AbortController();
3503 this.#signal = AbortSignal.any([signal, this.#touchManagerAC.signal]);
3504 container.addEventListener("touchstart", this.#onTouchStart.bind(this), {
3506 signal: this.#signal
3509 get MIN_TOUCH_DISTANCE_TO_PINCH() {
3510 return shadow(this, "MIN_TOUCH_DISTANCE_TO_PINCH", 35 / (window.devicePixelRatio || 1));
3512 #onTouchStart(evt) {
3513 if (this.#isPinchingDisabled?.()) {
3516 if (evt.touches.length === 1) {
3517 if (this.#pointerDownAC) {
3520 const pointerDownAC = this.#pointerDownAC = new AbortController();
3521 const signal = AbortSignal.any([this.#signal, pointerDownAC.signal]);
3522 const container = this.#container;
3528 const cancelPointerDown = e => {
3529 if (e.pointerType === "touch") {
3530 this.#pointerDownAC?.abort();
3531 this.#pointerDownAC = null;
3534 container.addEventListener("pointerdown", e => {
3535 if (e.pointerType === "touch") {
3537 cancelPointerDown(e);
3540 container.addEventListener("pointerup", cancelPointerDown, opts);
3541 container.addEventListener("pointercancel", cancelPointerDown, opts);
3544 if (!this.#touchMoveAC) {
3545 this.#touchMoveAC = new AbortController();
3546 const signal = AbortSignal.any([this.#signal, this.#touchMoveAC.signal]);
3547 const container = this.#container;
3553 container.addEventListener("touchmove", this.#onTouchMove.bind(this), opt);
3554 const onTouchEnd = this.#onTouchEnd.bind(this);
3555 container.addEventListener("touchend", onTouchEnd, opt);
3556 container.addEventListener("touchcancel", onTouchEnd, opt);
3558 container.addEventListener("pointerdown", stopEvent, opt);
3559 container.addEventListener("pointermove", stopEvent, opt);
3560 container.addEventListener("pointercancel", stopEvent, opt);
3561 container.addEventListener("pointerup", stopEvent, opt);
3562 this.#onPinchStart?.();
3565 if (evt.touches.length !== 2 || this.#isPinchingStopped?.()) {
3566 this.#touchInfo = null;
3569 let [touch0, touch1] = evt.touches;
3570 if (touch0.identifier > touch1.identifier) {
3571 [touch0, touch1] = [touch1, touch0];
3574 touch0X: touch0.screenX,
3575 touch0Y: touch0.screenY,
3576 touch1X: touch1.screenX,
3577 touch1Y: touch1.screenY
3581 if (!this.#touchInfo || evt.touches.length !== 2) {
3585 let [touch0, touch1] = evt.touches;
3586 if (touch0.identifier > touch1.identifier) {
3587 [touch0, touch1] = [touch1, touch0];
3597 const touchInfo = this.#touchInfo;
3604 const prevGapX = pTouch1X - pTouch0X;
3605 const prevGapY = pTouch1Y - pTouch0Y;
3606 const currGapX = screen1X - screen0X;
3607 const currGapY = screen1Y - screen0Y;
3608 const distance = Math.hypot(currGapX, currGapY) || 1;
3609 const pDistance = Math.hypot(prevGapX, prevGapY) || 1;
3610 if (!this.#isPinching && Math.abs(pDistance - distance) <= TouchManager.MIN_TOUCH_DISTANCE_TO_PINCH) {
3613 touchInfo.touch0X = screen0X;
3614 touchInfo.touch0Y = screen0Y;
3615 touchInfo.touch1X = screen1X;
3616 touchInfo.touch1Y = screen1Y;
3617 if (!this.#isPinching) {
3618 this.#isPinching = true;
3621 const origin = [(screen0X + screen1X) / 2, (screen0Y + screen1Y) / 2];
3622 this.#onPinching?.(origin, pDistance, distance);
3625 if (evt.touches.length >= 2) {
3628 this.#touchMoveAC.abort();
3629 this.#touchMoveAC = null;
3630 this.#onPinchEnd?.();
3631 if (!this.#touchInfo) {
3635 this.#touchInfo = null;
3636 this.#isPinching = false;
3639 this.#touchManagerAC?.abort();
3640 this.#touchManagerAC = null;
3641 this.#pointerDownAC?.abort();
3642 this.#pointerDownAC = null;
3646 ;// ./src/display/editor/editor.js
3653 class AnnotationEditor {
3654 #accessibilityData = null;
3655 #allResizerDivs = null;
3658 #dragPointerId = null;
3659 #dragPointerType = "";
3660 #keepAspectRatio = false;
3661 #resizersDiv = null;
3662 #lastPointerCoords = null;
3663 #savedDimensions = null;
3665 #focusedResizerName = "";
3666 #hasBeenClicked = false;
3667 #initialRect = null;
3669 #isInEditMode = false;
3670 #isResizerEnabledForKeyboard = false;
3671 #moveInDOMTimeout = null;
3674 #telemetryTimeouts = null;
3675 #touchManager = null;
3676 _editToolbar = null;
3677 _initialOptions = Object.create(null);
3678 _initialData = null;
3681 _focusEventsAllowed = true;
3682 static _l10n = null;
3683 static _l10nResizer = null;
3684 #isDraggable = false;
3685 #zIndex = AnnotationEditor._zIndex++;
3686 static _borderLineWidth = -1;
3687 static _colorManager = new ColorManager();
3689 static _telemetryTimeout = 1000;
3690 static get _resizerKeyboardManager() {
3691 const resize = AnnotationEditor.prototype._resizeWithKeyboard;
3692 const small = AnnotationEditorUIManager.TRANSLATE_SMALL;
3693 const big = AnnotationEditorUIManager.TRANSLATE_BIG;
3694 return shadow(this, "_resizerKeyboardManager", new KeyboardManager([[["ArrowLeft", "mac+ArrowLeft"], resize, {
3696 }], [["ctrl+ArrowLeft", "mac+shift+ArrowLeft"], resize, {
3698 }], [["ArrowRight", "mac+ArrowRight"], resize, {
3700 }], [["ctrl+ArrowRight", "mac+shift+ArrowRight"], resize, {
3702 }], [["ArrowUp", "mac+ArrowUp"], resize, {
3704 }], [["ctrl+ArrowUp", "mac+shift+ArrowUp"], resize, {
3706 }], [["ArrowDown", "mac+ArrowDown"], resize, {
3708 }], [["ctrl+ArrowDown", "mac+shift+ArrowDown"], resize, {
3710 }], [["Escape", "mac+Escape"], AnnotationEditor.prototype._stopResizingWithKeyboard]]));
3712 constructor(parameters) {
3713 this.parent = parameters.parent;
3714 this.id = parameters.id;
3715 this.width = this.height = null;
3716 this.pageIndex = parameters.parent.pageIndex;
3717 this.name = parameters.name;
3719 this._uiManager = parameters.uiManager;
3720 this.annotationElementId = null;
3721 this._willKeepAspectRatio = false;
3722 this._initialOptions.isCentered = parameters.isCentered;
3723 this._structTreeParentId = null;
3732 } = this.parent.viewport;
3733 this.rotation = rotation;
3734 this.pageRotation = (360 + rotation - this._uiManager.viewParameters.rotation) % 360;
3735 this.pageDimensions = [pageWidth, pageHeight];
3736 this.pageTranslation = [pageX, pageY];
3737 const [width, height] = this.parentDimensions;
3738 this.x = parameters.x / width;
3739 this.y = parameters.y / height;
3740 this.isAttachedToDOM = false;
3741 this.deleted = false;
3744 return Object.getPrototypeOf(this).constructor._type;
3746 static get isDrawer() {
3749 static get _defaultLineColor() {
3750 return shadow(this, "_defaultLineColor", this._colorManager.getHexCode("CanvasText"));
3752 static deleteAnnotationElement(editor) {
3753 const fakeEditor = new FakeEditor({
3754 id: editor.parent.getNextId(),
3755 parent: editor.parent,
3756 uiManager: editor._uiManager
3758 fakeEditor.annotationElementId = editor.annotationElementId;
3759 fakeEditor.deleted = true;
3760 fakeEditor._uiManager.addToAnnotationStorage(fakeEditor);
3762 static initialize(l10n, _uiManager) {
3763 AnnotationEditor._l10n ??= l10n;
3764 AnnotationEditor._l10nResizer ||= Object.freeze({
3765 topLeft: "pdfjs-editor-resizer-top-left",
3766 topMiddle: "pdfjs-editor-resizer-top-middle",
3767 topRight: "pdfjs-editor-resizer-top-right",
3768 middleRight: "pdfjs-editor-resizer-middle-right",
3769 bottomRight: "pdfjs-editor-resizer-bottom-right",
3770 bottomMiddle: "pdfjs-editor-resizer-bottom-middle",
3771 bottomLeft: "pdfjs-editor-resizer-bottom-left",
3772 middleLeft: "pdfjs-editor-resizer-middle-left"
3774 if (AnnotationEditor._borderLineWidth !== -1) {
3777 const style = getComputedStyle(document.documentElement);
3778 AnnotationEditor._borderLineWidth = parseFloat(style.getPropertyValue("--outline-width")) || 0;
3780 static updateDefaultParams(_type, _value) {}
3781 static get defaultPropertiesToUpdate() {
3784 static isHandlingMimeForPasting(mime) {
3787 static paste(item, parent) {
3788 unreachable("Not implemented");
3790 get propertiesToUpdate() {
3793 get _isDraggable() {
3794 return this.#isDraggable;
3796 set _isDraggable(value) {
3797 this.#isDraggable = value;
3798 this.div?.classList.toggle("draggable", value);
3800 get isEnterHandled() {
3804 const [pageWidth, pageHeight] = this.pageDimensions;
3805 switch (this.parentRotation) {
3807 this.x -= this.height * pageHeight / (pageWidth * 2);
3808 this.y += this.width * pageWidth / (pageHeight * 2);
3811 this.x += this.width / 2;
3812 this.y += this.height / 2;
3815 this.x += this.height * pageHeight / (pageWidth * 2);
3816 this.y -= this.width * pageWidth / (pageHeight * 2);
3819 this.x -= this.width / 2;
3820 this.y -= this.height / 2;
3823 this.fixAndSetPosition();
3825 addCommands(params) {
3826 this._uiManager.addCommands(params);
3828 get currentLayer() {
3829 return this._uiManager.currentLayer;
3832 this.div.style.zIndex = 0;
3835 this.div.style.zIndex = this.#zIndex;
3838 if (parent !== null) {
3839 this.pageIndex = parent.pageIndex;
3840 this.pageDimensions = parent.pageDimensions;
3842 this.#stopResizing();
3844 this.parent = parent;
3847 if (!this._focusEventsAllowed) {
3850 if (!this.#hasBeenClicked) {
3851 this.parent.setSelected(this);
3853 this.#hasBeenClicked = false;
3857 if (!this._focusEventsAllowed) {
3860 if (!this.isAttachedToDOM) {
3863 const target = event.relatedTarget;
3864 if (target?.closest(`#${this.id}`)) {
3867 event.preventDefault();
3868 if (!this.parent?.isMultipleSelection) {
3869 this.commitOrRemove();
3873 if (this.isEmpty()) {
3880 this.addToAnnotationStorage();
3882 addToAnnotationStorage() {
3883 this._uiManager.addToAnnotationStorage(this);
3885 setAt(x, y, tx, ty) {
3886 const [width, height] = this.parentDimensions;
3887 [tx, ty] = this.screenToPageTranslation(tx, ty);
3888 this.x = (x + tx) / width;
3889 this.y = (y + ty) / height;
3890 this.fixAndSetPosition();
3892 #translate([width, height], x, y) {
3893 [x, y] = this.screenToPageTranslation(x, y);
3894 this.x += x / width;
3895 this.y += y / height;
3896 this._onTranslating(this.x, this.y);
3897 this.fixAndSetPosition();
3900 this.#translate(this.parentDimensions, x, y);
3902 translateInPage(x, y) {
3903 this.#initialRect ||= [this.x, this.y, this.width, this.height];
3904 this.#translate(this.pageDimensions, x, y);
3905 this.div.scrollIntoView({
3910 this.#initialRect ||= [this.x, this.y, this.width, this.height];
3913 parentDimensions: [parentWidth, parentHeight]
3915 this.x += tx / parentWidth;
3916 this.y += ty / parentHeight;
3917 if (this.parent && (this.x < 0 || this.x > 1 || this.y < 0 || this.y > 1)) {
3921 } = this.div.getBoundingClientRect();
3922 if (this.parent.findNewParent(this, x, y)) {
3923 this.x -= Math.floor(this.x);
3924 this.y -= Math.floor(this.y);
3931 const [bx, by] = this.getBaseTranslation();
3937 style.left = `${(100 * x).toFixed(2)}%`;
3938 style.top = `${(100 * y).toFixed(2)}%`;
3939 this._onTranslating(x, y);
3940 div.scrollIntoView({
3944 _onTranslating(x, y) {}
3945 _onTranslated(x, y) {}
3946 get _hasBeenMoved() {
3947 return !!this.#initialRect && (this.#initialRect[0] !== this.x || this.#initialRect[1] !== this.y);
3949 get _hasBeenResized() {
3950 return !!this.#initialRect && (this.#initialRect[2] !== this.width || this.#initialRect[3] !== this.height);
3952 getBaseTranslation() {
3953 const [parentWidth, parentHeight] = this.parentDimensions;
3956 } = AnnotationEditor;
3957 const x = _borderLineWidth / parentWidth;
3958 const y = _borderLineWidth / parentHeight;
3959 switch (this.rotation) {
3970 get _mustFixPosition() {
3973 fixAndSetPosition(rotation = this.rotation) {
3978 pageDimensions: [pageWidth, pageHeight]
3987 height *= pageHeight;
3990 if (this._mustFixPosition) {
3993 x = Math.max(0, Math.min(pageWidth - width, x));
3994 y = Math.max(0, Math.min(pageHeight - height, y));
3997 x = Math.max(0, Math.min(pageWidth - height, x));
3998 y = Math.min(pageHeight, Math.max(width, y));
4001 x = Math.min(pageWidth, Math.max(width, x));
4002 y = Math.min(pageHeight, Math.max(height, y));
4005 x = Math.min(pageWidth, Math.max(height, x));
4006 y = Math.max(0, Math.min(pageHeight - width, y));
4010 this.x = x /= pageWidth;
4011 this.y = y /= pageHeight;
4012 const [bx, by] = this.getBaseTranslation();
4015 style.left = `${(100 * x).toFixed(2)}%`;
4016 style.top = `${(100 * y).toFixed(2)}%`;
4019 static #rotatePoint(x, y, angle) {
4031 screenToPageTranslation(x, y) {
4032 return AnnotationEditor.#rotatePoint(x, y, this.parentRotation);
4034 pageTranslationToScreen(x, y) {
4035 return AnnotationEditor.#rotatePoint(x, y, 360 - this.parentRotation);
4037 #getRotationMatrix(rotation) {
4041 const [pageWidth, pageHeight] = this.pageDimensions;
4042 return [0, -pageWidth / pageHeight, pageHeight / pageWidth, 0];
4045 return [-1, 0, 0, -1];
4048 const [pageWidth, pageHeight] = this.pageDimensions;
4049 return [0, pageWidth / pageHeight, -pageHeight / pageWidth, 0];
4052 return [1, 0, 0, 1];
4056 return this._uiManager.viewParameters.realScale;
4058 get parentRotation() {
4059 return (this._uiManager.viewParameters.rotation + this.pageRotation) % 360;
4061 get parentDimensions() {
4064 pageDimensions: [pageWidth, pageHeight]
4066 return [pageWidth * parentScale, pageHeight * parentScale];
4068 setDims(width, height) {
4069 const [parentWidth, parentHeight] = this.parentDimensions;
4073 style.width = `${(100 * width / parentWidth).toFixed(2)}%`;
4074 if (!this.#keepAspectRatio) {
4075 style.height = `${(100 * height / parentHeight).toFixed(2)}%`;
4086 const widthPercent = width.endsWith("%");
4087 const heightPercent = !this.#keepAspectRatio && height.endsWith("%");
4088 if (widthPercent && heightPercent) {
4091 const [parentWidth, parentHeight] = this.parentDimensions;
4092 if (!widthPercent) {
4093 style.width = `${(100 * parseFloat(width) / parentWidth).toFixed(2)}%`;
4095 if (!this.#keepAspectRatio && !heightPercent) {
4096 style.height = `${(100 * parseFloat(height) / parentHeight).toFixed(2)}%`;
4099 getInitialTranslation() {
4103 if (this.#resizersDiv) {
4106 this.#resizersDiv = document.createElement("div");
4107 this.#resizersDiv.classList.add("resizers");
4108 const classes = this._willKeepAspectRatio ? ["topLeft", "topRight", "bottomRight", "bottomLeft"] : ["topLeft", "topMiddle", "topRight", "middleRight", "bottomRight", "bottomMiddle", "bottomLeft", "middleLeft"];
4109 const signal = this._uiManager._signal;
4110 for (const name of classes) {
4111 const div = document.createElement("div");
4112 this.#resizersDiv.append(div);
4113 div.classList.add("resizer", name);
4114 div.setAttribute("data-resizer-name", name);
4115 div.addEventListener("pointerdown", this.#resizerPointerdown.bind(this, name), {
4118 div.addEventListener("contextmenu", noContextMenu, {
4123 this.div.prepend(this.#resizersDiv);
4125 #resizerPointerdown(name, event) {
4126 event.preventDefault();
4129 } = util_FeatureTest.platform;
4130 if (event.button !== 0 || event.ctrlKey && isMac) {
4133 this.#altText?.toggle(false);
4134 const savedDraggable = this._isDraggable;
4135 this._isDraggable = false;
4136 this.#lastPointerCoords = [event.screenX, event.screenY];
4137 const ac = new AbortController();
4138 const signal = this._uiManager.combinedSignal(ac);
4139 this.parent.togglePointerEvents(false);
4140 window.addEventListener("pointermove", this.#resizerPointermove.bind(this, name), {
4145 window.addEventListener("touchmove", stopEvent, {
4149 window.addEventListener("contextmenu", noContextMenu, {
4152 this.#savedDimensions = {
4155 savedWidth: this.width,
4156 savedHeight: this.height
4158 const savedParentCursor = this.parent.div.style.cursor;
4159 const savedCursor = this.div.style.cursor;
4160 this.div.style.cursor = this.parent.div.style.cursor = window.getComputedStyle(event.target).cursor;
4161 const pointerUpCallback = () => {
4163 this.parent.togglePointerEvents(true);
4164 this.#altText?.toggle(true);
4165 this._isDraggable = savedDraggable;
4166 this.parent.div.style.cursor = savedParentCursor;
4167 this.div.style.cursor = savedCursor;
4168 this.#addResizeToUndoStack();
4170 window.addEventListener("pointerup", pointerUpCallback, {
4173 window.addEventListener("blur", pointerUpCallback, {
4177 #resize(x, y, width, height) {
4179 this.height = height;
4182 const [parentWidth, parentHeight] = this.parentDimensions;
4183 this.setDims(parentWidth * width, parentHeight * height);
4184 this.fixAndSetPosition();
4188 #addResizeToUndoStack() {
4189 if (!this.#savedDimensions) {
4197 } = this.#savedDimensions;
4198 this.#savedDimensions = null;
4199 const newX = this.x;
4200 const newY = this.y;
4201 const newWidth = this.width;
4202 const newHeight = this.height;
4203 if (newX === savedX && newY === savedY && newWidth === savedWidth && newHeight === savedHeight) {
4207 cmd: this.#resize.bind(this, newX, newY, newWidth, newHeight),
4208 undo: this.#resize.bind(this, savedX, savedY, savedWidth, savedHeight),
4213 return Math.round(x * 10000) / 10000;
4215 #resizerPointermove(name, event) {
4216 const [parentWidth, parentHeight] = this.parentDimensions;
4217 const savedX = this.x;
4218 const savedY = this.y;
4219 const savedWidth = this.width;
4220 const savedHeight = this.height;
4221 const minWidth = AnnotationEditor.MIN_SIZE / parentWidth;
4222 const minHeight = AnnotationEditor.MIN_SIZE / parentHeight;
4223 const rotationMatrix = this.#getRotationMatrix(this.rotation);
4224 const transf = (x, y) => [rotationMatrix[0] * x + rotationMatrix[2] * y, rotationMatrix[1] * x + rotationMatrix[3] * y];
4225 const invRotationMatrix = this.#getRotationMatrix(360 - this.rotation);
4226 const invTransf = (x, y) => [invRotationMatrix[0] * x + invRotationMatrix[2] * y, invRotationMatrix[1] * x + invRotationMatrix[3] * y];
4229 let isDiagonal = false;
4230 let isHorizontal = false;
4234 getPoint = (w, h) => [0, 0];
4235 getOpposite = (w, h) => [w, h];
4238 getPoint = (w, h) => [w / 2, 0];
4239 getOpposite = (w, h) => [w / 2, h];
4243 getPoint = (w, h) => [w, 0];
4244 getOpposite = (w, h) => [0, h];
4247 isHorizontal = true;
4248 getPoint = (w, h) => [w, h / 2];
4249 getOpposite = (w, h) => [0, h / 2];
4253 getPoint = (w, h) => [w, h];
4254 getOpposite = (w, h) => [0, 0];
4256 case "bottomMiddle":
4257 getPoint = (w, h) => [w / 2, h];
4258 getOpposite = (w, h) => [w / 2, 0];
4262 getPoint = (w, h) => [0, h];
4263 getOpposite = (w, h) => [w, 0];
4266 isHorizontal = true;
4267 getPoint = (w, h) => [0, h / 2];
4268 getOpposite = (w, h) => [w, h / 2];
4271 const point = getPoint(savedWidth, savedHeight);
4272 const oppositePoint = getOpposite(savedWidth, savedHeight);
4273 let transfOppositePoint = transf(...oppositePoint);
4274 const oppositeX = AnnotationEditor._round(savedX + transfOppositePoint[0]);
4275 const oppositeY = AnnotationEditor._round(savedY + transfOppositePoint[1]);
4279 if (!event.fromKeyboard) {
4284 const [lastScreenX, lastScreenY] = this.#lastPointerCoords;
4285 [deltaX, deltaY] = this.screenToPageTranslation(screenX - lastScreenX, screenY - lastScreenY);
4286 this.#lastPointerCoords[0] = screenX;
4287 this.#lastPointerCoords[1] = screenY;
4294 [deltaX, deltaY] = invTransf(deltaX / parentWidth, deltaY / parentHeight);
4296 const oldDiag = Math.hypot(savedWidth, savedHeight);
4297 ratioX = ratioY = Math.max(Math.min(Math.hypot(oppositePoint[0] - point[0] - deltaX, oppositePoint[1] - point[1] - deltaY) / oldDiag, 1 / savedWidth, 1 / savedHeight), minWidth / savedWidth, minHeight / savedHeight);
4298 } else if (isHorizontal) {
4299 ratioX = Math.max(minWidth, Math.min(1, Math.abs(oppositePoint[0] - point[0] - deltaX))) / savedWidth;
4301 ratioY = Math.max(minHeight, Math.min(1, Math.abs(oppositePoint[1] - point[1] - deltaY))) / savedHeight;
4303 const newWidth = AnnotationEditor._round(savedWidth * ratioX);
4304 const newHeight = AnnotationEditor._round(savedHeight * ratioY);
4305 transfOppositePoint = transf(...getOpposite(newWidth, newHeight));
4306 const newX = oppositeX - transfOppositePoint[0];
4307 const newY = oppositeY - transfOppositePoint[1];
4308 this.#initialRect ||= [this.x, this.y, this.width, this.height];
4309 this.width = newWidth;
4310 this.height = newHeight;
4313 this.setDims(parentWidth * newWidth, parentHeight * newHeight);
4314 this.fixAndSetPosition();
4319 this.#altText?.finish();
4321 async addEditToolbar() {
4322 if (this._editToolbar || this.#isInEditMode) {
4323 return this._editToolbar;
4325 this._editToolbar = new EditorToolbar(this);
4326 this.div.append(this._editToolbar.render());
4327 if (this.#altText) {
4328 await this._editToolbar.addAltText(this.#altText);
4330 return this._editToolbar;
4332 removeEditToolbar() {
4333 if (!this._editToolbar) {
4336 this._editToolbar.remove();
4337 this._editToolbar = null;
4338 this.#altText?.destroy();
4340 addContainer(container) {
4341 const editToolbarDiv = this._editToolbar?.div;
4342 if (editToolbarDiv) {
4343 editToolbarDiv.before(container);
4345 this.div.append(container);
4348 getClientDimensions() {
4349 return this.div.getBoundingClientRect();
4351 async addAltTextButton() {
4352 if (this.#altText) {
4355 AltText.initialize(AnnotationEditor._l10n);
4356 this.#altText = new AltText(this);
4357 if (this.#accessibilityData) {
4358 this.#altText.data = this.#accessibilityData;
4359 this.#accessibilityData = null;
4361 await this.addEditToolbar();
4364 return this.#altText?.data;
4366 set altTextData(data) {
4367 if (!this.#altText) {
4370 this.#altText.data = data;
4372 get guessedAltText() {
4373 return this.#altText?.guessedText;
4375 async setGuessedAltText(text) {
4376 await this.#altText?.setGuessedText(text);
4378 serializeAltText(isForCopying) {
4379 return this.#altText?.serialize(isForCopying);
4382 return !!this.#altText && !this.#altText.isEmpty();
4385 return this.#altText?.hasData() ?? false;
4388 this.div = document.createElement("div");
4389 this.div.setAttribute("data-editor-rotation", (360 - this.rotation) % 360);
4390 this.div.className = this.name;
4391 this.div.setAttribute("id", this.id);
4392 this.div.tabIndex = this.#disabled ? -1 : 0;
4393 if (!this._isVisible) {
4394 this.div.classList.add("hidden");
4396 this.setInForeground();
4397 this.#addFocusListeners();
4398 const [parentWidth, parentHeight] = this.parentDimensions;
4399 if (this.parentRotation % 180 !== 0) {
4400 this.div.style.maxWidth = `${(100 * parentHeight / parentWidth).toFixed(2)}%`;
4401 this.div.style.maxHeight = `${(100 * parentWidth / parentHeight).toFixed(2)}%`;
4403 const [tx, ty] = this.getInitialTranslation();
4404 this.translate(tx, ty);
4405 bindEvents(this, this.div, ["pointerdown"]);
4406 if (this.isResizable && this._uiManager._supportsPinchToZoom) {
4407 this.#touchManager ||= new TouchManager({
4408 container: this.div,
4409 isPinchingDisabled: () => !this.isSelected,
4410 onPinchStart: this.#touchPinchStartCallback.bind(this),
4411 onPinching: this.#touchPinchCallback.bind(this),
4412 onPinchEnd: this.#touchPinchEndCallback.bind(this),
4413 signal: this._uiManager._signal
4416 this._uiManager._editorUndoBar?.hide();
4419 #touchPinchStartCallback() {
4420 this.#savedDimensions = {
4423 savedWidth: this.width,
4424 savedHeight: this.height
4426 this.#altText?.toggle(false);
4427 this.parent.togglePointerEvents(false);
4429 #touchPinchCallback(_origin, prevDistance, distance) {
4430 const slowDownFactor = 0.7;
4431 let factor = slowDownFactor * (distance / prevDistance) + 1 - slowDownFactor;
4435 const rotationMatrix = this.#getRotationMatrix(this.rotation);
4436 const transf = (x, y) => [rotationMatrix[0] * x + rotationMatrix[2] * y, rotationMatrix[1] * x + rotationMatrix[3] * y];
4437 const [parentWidth, parentHeight] = this.parentDimensions;
4438 const savedX = this.x;
4439 const savedY = this.y;
4440 const savedWidth = this.width;
4441 const savedHeight = this.height;
4442 const minWidth = AnnotationEditor.MIN_SIZE / parentWidth;
4443 const minHeight = AnnotationEditor.MIN_SIZE / parentHeight;
4444 factor = Math.max(Math.min(factor, 1 / savedWidth, 1 / savedHeight), minWidth / savedWidth, minHeight / savedHeight);
4445 const newWidth = AnnotationEditor._round(savedWidth * factor);
4446 const newHeight = AnnotationEditor._round(savedHeight * factor);
4447 if (newWidth === savedWidth && newHeight === savedHeight) {
4450 this.#initialRect ||= [savedX, savedY, savedWidth, savedHeight];
4451 const transfCenterPoint = transf(savedWidth / 2, savedHeight / 2);
4452 const centerX = AnnotationEditor._round(savedX + transfCenterPoint[0]);
4453 const centerY = AnnotationEditor._round(savedY + transfCenterPoint[1]);
4454 const newTransfCenterPoint = transf(newWidth / 2, newHeight / 2);
4455 this.x = centerX - newTransfCenterPoint[0];
4456 this.y = centerY - newTransfCenterPoint[1];
4457 this.width = newWidth;
4458 this.height = newHeight;
4459 this.setDims(parentWidth * newWidth, parentHeight * newHeight);
4460 this.fixAndSetPosition();
4463 #touchPinchEndCallback() {
4464 this.#altText?.toggle(true);
4465 this.parent.togglePointerEvents(true);
4466 this.#addResizeToUndoStack();
4468 pointerdown(event) {
4471 } = util_FeatureTest.platform;
4472 if (event.button !== 0 || event.ctrlKey && isMac) {
4473 event.preventDefault();
4476 this.#hasBeenClicked = true;
4477 if (this._isDraggable) {
4478 this.#setUpDragSession(event);
4481 this.#selectOnPointerEvent(event);
4484 return this._uiManager.isSelected(this);
4486 #selectOnPointerEvent(event) {
4489 } = util_FeatureTest.platform;
4490 if (event.ctrlKey && !isMac || event.shiftKey || event.metaKey && isMac) {
4491 this.parent.toggleSelected(this);
4493 this.parent.setSelected(this);
4496 #setUpDragSession(event) {
4500 this._uiManager.setUpDragSession();
4501 let hasDraggingStarted = false;
4502 const ac = new AbortController();
4503 const signal = this._uiManager.combinedSignal(ac);
4509 const cancelDrag = e => {
4511 this.#dragPointerId = null;
4512 this.#hasBeenClicked = false;
4513 if (!this._uiManager.endDragSession()) {
4514 this.#selectOnPointerEvent(e);
4516 if (hasDraggingStarted) {
4517 this._onStopDragging();
4521 this.#prevDragX = event.clientX;
4522 this.#prevDragY = event.clientY;
4523 this.#dragPointerId = event.pointerId;
4524 this.#dragPointerType = event.pointerType;
4525 window.addEventListener("pointermove", e => {
4526 if (!hasDraggingStarted) {
4527 hasDraggingStarted = true;
4528 this._onStartDragging();
4535 if (pointerId !== this.#dragPointerId) {
4539 const [tx, ty] = this.screenToPageTranslation(x - this.#prevDragX, y - this.#prevDragY);
4540 this.#prevDragX = x;
4541 this.#prevDragY = y;
4542 this._uiManager.dragSelectedEditors(tx, ty);
4544 window.addEventListener("touchmove", stopEvent, opts);
4545 window.addEventListener("pointerdown", e => {
4546 if (e.pointerType === this.#dragPointerType) {
4547 if (this.#touchManager || e.isPrimary) {
4554 const pointerUpCallback = e => {
4555 if (!this.#dragPointerId || this.#dragPointerId === e.pointerId) {
4561 window.addEventListener("pointerup", pointerUpCallback, {
4564 window.addEventListener("blur", pointerUpCallback, {
4568 _onStartDragging() {}
4569 _onStopDragging() {}
4571 if (this.#moveInDOMTimeout) {
4572 clearTimeout(this.#moveInDOMTimeout);
4574 this.#moveInDOMTimeout = setTimeout(() => {
4575 this.#moveInDOMTimeout = null;
4576 this.parent?.moveEditorInDOM(this);
4579 _setParentAndPosition(parent, x, y) {
4580 parent.changeParent(this);
4583 this.fixAndSetPosition();
4584 this._onTranslated();
4586 getRect(tx, ty, rotation = this.rotation) {
4587 const scale = this.parentScale;
4588 const [pageWidth, pageHeight] = this.pageDimensions;
4589 const [pageX, pageY] = this.pageTranslation;
4590 const shiftX = tx / scale;
4591 const shiftY = ty / scale;
4592 const x = this.x * pageWidth;
4593 const y = this.y * pageHeight;
4594 const width = this.width * pageWidth;
4595 const height = this.height * pageHeight;
4598 return [x + shiftX + pageX, pageHeight - y - shiftY - height + pageY, x + shiftX + width + pageX, pageHeight - y - shiftY + pageY];
4600 return [x + shiftY + pageX, pageHeight - y + shiftX + pageY, x + shiftY + height + pageX, pageHeight - y + shiftX + width + pageY];
4602 return [x - shiftX - width + pageX, pageHeight - y + shiftY + pageY, x - shiftX + pageX, pageHeight - y + shiftY + height + pageY];
4604 return [x - shiftY - height + pageX, pageHeight - y - shiftX - width + pageY, x - shiftY + pageX, pageHeight - y - shiftX + pageY];
4606 throw new Error("Invalid rotation");
4609 getRectInCurrentCoords(rect, pageHeight) {
4610 const [x1, y1, x2, y2] = rect;
4611 const width = x2 - x1;
4612 const height = y2 - y1;
4613 switch (this.rotation) {
4615 return [x1, pageHeight - y2, width, height];
4617 return [x1, pageHeight - y1, height, width];
4619 return [x2, pageHeight - y1, width, height];
4621 return [x2, pageHeight - y2, height, width];
4623 throw new Error("Invalid rotation");
4631 this.#isInEditMode = true;
4634 this.#isInEditMode = false;
4637 return this.#isInEditMode;
4639 shouldGetKeyboardEvents() {
4640 return this.#isResizerEnabledForKeyboard;
4642 needsToBeRebuilt() {
4643 return this.div && !this.isAttachedToDOM;
4651 } = this.getClientDimensions();
4656 return left < innerWidth && right > 0 && top < innerHeight && bottom > 0;
4658 #addFocusListeners() {
4659 if (this.#focusAC || !this.div) {
4662 this.#focusAC = new AbortController();
4663 const signal = this._uiManager.combinedSignal(this.#focusAC);
4664 this.div.addEventListener("focusin", this.focusin.bind(this), {
4667 this.div.addEventListener("focusout", this.focusout.bind(this), {
4672 this.#addFocusListeners();
4676 serializeDeleted() {
4678 id: this.annotationElementId,
4680 pageIndex: this.pageIndex,
4681 popupRef: this._initialData?.popupRef || ""
4684 serialize(isForCopying = false, context = null) {
4685 unreachable("An editor must be serializable");
4687 static async deserialize(data, parent, uiManager) {
4688 const editor = new this.prototype.constructor({
4690 id: parent.getNextId(),
4693 editor.rotation = data.rotation;
4694 editor.#accessibilityData = data.accessibilityData;
4695 const [pageWidth, pageHeight] = editor.pageDimensions;
4696 const [x, y, width, height] = editor.getRectInCurrentCoords(data.rect, pageHeight);
4697 editor.x = x / pageWidth;
4698 editor.y = y / pageHeight;
4699 editor.width = width / pageWidth;
4700 editor.height = height / pageHeight;
4703 get hasBeenModified() {
4704 return !!this.annotationElementId && (this.deleted || this.serialize() !== null);
4707 this.#focusAC?.abort();
4708 this.#focusAC = null;
4709 if (!this.isEmpty()) {
4713 this.parent.remove(this);
4715 this._uiManager.removeEditor(this);
4717 if (this.#moveInDOMTimeout) {
4718 clearTimeout(this.#moveInDOMTimeout);
4719 this.#moveInDOMTimeout = null;
4721 this.#stopResizing();
4722 this.removeEditToolbar();
4723 if (this.#telemetryTimeouts) {
4724 for (const timeout of this.#telemetryTimeouts.values()) {
4725 clearTimeout(timeout);
4727 this.#telemetryTimeouts = null;
4730 this.#touchManager?.destroy();
4731 this.#touchManager = null;
4737 if (this.isResizable) {
4738 this.#createResizers();
4739 this.#resizersDiv.classList.remove("hidden");
4740 bindEvents(this, this.div, ["keydown"]);
4743 get toolbarPosition() {
4747 if (!this.isResizable || event.target !== this.div || event.key !== "Enter") {
4750 this._uiManager.setSelected(this);
4751 this.#savedDimensions = {
4754 savedWidth: this.width,
4755 savedHeight: this.height
4757 const children = this.#resizersDiv.children;
4758 if (!this.#allResizerDivs) {
4759 this.#allResizerDivs = Array.from(children);
4760 const boundResizerKeydown = this.#resizerKeydown.bind(this);
4761 const boundResizerBlur = this.#resizerBlur.bind(this);
4762 const signal = this._uiManager._signal;
4763 for (const div of this.#allResizerDivs) {
4764 const name = div.getAttribute("data-resizer-name");
4765 div.setAttribute("role", "spinbutton");
4766 div.addEventListener("keydown", boundResizerKeydown, {
4769 div.addEventListener("blur", boundResizerBlur, {
4772 div.addEventListener("focus", this.#resizerFocus.bind(this, name), {
4775 div.setAttribute("data-l10n-id", AnnotationEditor._l10nResizer[name]);
4778 const first = this.#allResizerDivs[0];
4779 let firstPosition = 0;
4780 for (const div of children) {
4781 if (div === first) {
4786 const nextFirstPosition = (360 - this.rotation + this.parentRotation) % 360 / 90 * (this.#allResizerDivs.length / 4);
4787 if (nextFirstPosition !== firstPosition) {
4788 if (nextFirstPosition < firstPosition) {
4789 for (let i = 0; i < firstPosition - nextFirstPosition; i++) {
4790 this.#resizersDiv.append(this.#resizersDiv.firstChild);
4792 } else if (nextFirstPosition > firstPosition) {
4793 for (let i = 0; i < nextFirstPosition - firstPosition; i++) {
4794 this.#resizersDiv.firstChild.before(this.#resizersDiv.lastChild);
4798 for (const child of children) {
4799 const div = this.#allResizerDivs[i++];
4800 const name = div.getAttribute("data-resizer-name");
4801 child.setAttribute("data-l10n-id", AnnotationEditor._l10nResizer[name]);
4804 this.#setResizerTabIndex(0);
4805 this.#isResizerEnabledForKeyboard = true;
4806 this.#resizersDiv.firstChild.focus({
4809 event.preventDefault();
4810 event.stopImmediatePropagation();
4812 #resizerKeydown(event) {
4813 AnnotationEditor._resizerKeyboardManager.exec(this, event);
4815 #resizerBlur(event) {
4816 if (this.#isResizerEnabledForKeyboard && event.relatedTarget?.parentNode !== this.#resizersDiv) {
4817 this.#stopResizing();
4820 #resizerFocus(name) {
4821 this.#focusedResizerName = this.#isResizerEnabledForKeyboard ? name : "";
4823 #setResizerTabIndex(value) {
4824 if (!this.#allResizerDivs) {
4827 for (const div of this.#allResizerDivs) {
4828 div.tabIndex = value;
4831 _resizeWithKeyboard(x, y) {
4832 if (!this.#isResizerEnabledForKeyboard) {
4835 this.#resizerPointermove(this.#focusedResizerName, {
4842 this.#isResizerEnabledForKeyboard = false;
4843 this.#setResizerTabIndex(-1);
4844 this.#addResizeToUndoStack();
4846 _stopResizingWithKeyboard() {
4847 this.#stopResizing();
4851 this.makeResizable();
4852 this.div?.classList.add("selectedEditor");
4853 if (!this._editToolbar) {
4854 this.addEditToolbar().then(() => {
4855 if (this.div?.classList.contains("selectedEditor")) {
4856 this._editToolbar?.show();
4861 this._editToolbar?.show();
4862 this.#altText?.toggleAltTextBadge(false);
4865 this.#resizersDiv?.classList.add("hidden");
4866 this.div?.classList.remove("selectedEditor");
4867 if (this.div?.contains(document.activeElement)) {
4868 this._uiManager.currentLayer.div.focus({
4872 this._editToolbar?.hide();
4873 this.#altText?.toggleAltTextBadge(true);
4875 updateParams(type, value) {}
4878 enterInEditMode() {}
4879 getImageForAltText() {
4886 return this.#isEditing;
4888 set isEditing(value) {
4889 this.#isEditing = value;
4894 this.parent.setSelected(this);
4895 this.parent.setActiveEditor(this);
4897 this.parent.setActiveEditor(null);
4900 setAspectRatio(width, height) {
4901 this.#keepAspectRatio = true;
4902 const aspectRatio = width / height;
4906 style.aspectRatio = aspectRatio;
4907 style.height = "auto";
4909 static get MIN_SIZE() {
4912 static canCreateNewEmptyEditor() {
4915 get telemetryInitialData() {
4920 get telemetryFinalData() {
4923 _reportTelemetry(data, mustWait = false) {
4925 this.#telemetryTimeouts ||= new Map();
4929 let timeout = this.#telemetryTimeouts.get(action);
4931 clearTimeout(timeout);
4933 timeout = setTimeout(() => {
4934 this._reportTelemetry(data);
4935 this.#telemetryTimeouts.delete(action);
4936 if (this.#telemetryTimeouts.size === 0) {
4937 this.#telemetryTimeouts = null;
4939 }, AnnotationEditor._telemetryTimeout);
4940 this.#telemetryTimeouts.set(action, timeout);
4943 data.type ||= this.editorType;
4944 this._uiManager._eventBus.dispatch("reporttelemetry", {
4952 show(visible = this._isVisible) {
4953 this.div.classList.toggle("hidden", !visible);
4954 this._isVisible = visible;
4958 this.div.tabIndex = 0;
4960 this.#disabled = false;
4964 this.div.tabIndex = -1;
4966 this.#disabled = true;
4968 renderAnnotationElement(annotation) {
4969 let content = annotation.container.querySelector(".annotationContent");
4971 content = document.createElement("div");
4972 content.classList.add("annotationContent", this.editorType);
4973 annotation.container.prepend(content);
4974 } else if (content.nodeName === "CANVAS") {
4975 const canvas = content;
4976 content = document.createElement("div");
4977 content.classList.add("annotationContent", this.editorType);
4978 canvas.before(content);
4982 resetAnnotationElement(annotation) {
4985 } = annotation.container;
4986 if (firstChild?.nodeName === "DIV" && firstChild.classList.contains("annotationContent")) {
4987 firstChild.remove();
4991 class FakeEditor extends AnnotationEditor {
4992 constructor(params) {
4994 this.annotationElementId = params.annotationElementId;
4995 this.deleted = true;
4998 return this.serializeDeleted();
5002 ;// ./src/shared/murmurhash3.js
5003 const SEED = 0xc3d2e1f0;
5004 const MASK_HIGH = 0xffff0000;
5005 const MASK_LOW = 0xffff;
5006 class MurmurHash3_64 {
5008 this.h1 = seed ? seed & 0xffffffff : SEED;
5009 this.h2 = seed ? seed & 0xffffffff : SEED;
5013 if (typeof input === "string") {
5014 data = new Uint8Array(input.length * 2);
5016 for (let i = 0, ii = input.length; i < ii; i++) {
5017 const code = input.charCodeAt(i);
5019 data[length++] = code;
5021 data[length++] = code >>> 8;
5022 data[length++] = code & 0xff;
5025 } else if (ArrayBuffer.isView(input)) {
5026 data = input.slice();
5027 length = data.byteLength;
5029 throw new Error("Invalid data format, must be a string or TypedArray.");
5031 const blockCounts = length >> 2;
5032 const tailLength = length - blockCounts * 4;
5033 const dataUint32 = new Uint32Array(data.buffer, 0, blockCounts);
5038 const C1 = 0xcc9e2d51,
5040 const C1_LOW = C1 & MASK_LOW,
5041 C2_LOW = C2 & MASK_LOW;
5042 for (let i = 0; i < blockCounts; i++) {
5045 k1 = k1 * C1 & MASK_HIGH | k1 * C1_LOW & MASK_LOW;
5046 k1 = k1 << 15 | k1 >>> 17;
5047 k1 = k1 * C2 & MASK_HIGH | k1 * C2_LOW & MASK_LOW;
5049 h1 = h1 << 13 | h1 >>> 19;
5050 h1 = h1 * 5 + 0xe6546b64;
5053 k2 = k2 * C1 & MASK_HIGH | k2 * C1_LOW & MASK_LOW;
5054 k2 = k2 << 15 | k2 >>> 17;
5055 k2 = k2 * C2 & MASK_HIGH | k2 * C2_LOW & MASK_LOW;
5057 h2 = h2 << 13 | h2 >>> 19;
5058 h2 = h2 * 5 + 0xe6546b64;
5062 switch (tailLength) {
5064 k1 ^= data[blockCounts * 4 + 2] << 16;
5066 k1 ^= data[blockCounts * 4 + 1] << 8;
5068 k1 ^= data[blockCounts * 4];
5069 k1 = k1 * C1 & MASK_HIGH | k1 * C1_LOW & MASK_LOW;
5070 k1 = k1 << 15 | k1 >>> 17;
5071 k1 = k1 * C2 & MASK_HIGH | k1 * C2_LOW & MASK_LOW;
5072 if (blockCounts & 1) {
5085 h1 = h1 * 0xed558ccd & MASK_HIGH | h1 * 0x8ccd & MASK_LOW;
5086 h2 = h2 * 0xff51afd7 & MASK_HIGH | ((h2 << 16 | h1 >>> 16) * 0xafd7ed55 & MASK_HIGH) >>> 16;
5088 h1 = h1 * 0x1a85ec53 & MASK_HIGH | h1 * 0xec53 & MASK_LOW;
5089 h2 = h2 * 0xc4ceb9fe & MASK_HIGH | ((h2 << 16 | h1 >>> 16) * 0xb9fe1a85 & MASK_HIGH) >>> 16;
5091 return (h1 >>> 0).toString(16).padStart(8, "0") + (h2 >>> 0).toString(16).padStart(8, "0");
5095 ;// ./src/display/annotation_storage.js
5099 const SerializableEmpty = Object.freeze({
5104 class AnnotationStorage {
5106 #modifiedIds = null;
5107 #storage = new Map();
5109 this.onSetModified = null;
5110 this.onResetModified = null;
5111 this.onAnnotationEditor = null;
5113 getValue(key, defaultValue) {
5114 const value = this.#storage.get(key);
5115 if (value === undefined) {
5116 return defaultValue;
5118 return Object.assign(defaultValue, value);
5121 return this.#storage.get(key);
5124 this.#storage.delete(key);
5125 if (this.#storage.size === 0) {
5126 this.resetModified();
5128 if (typeof this.onAnnotationEditor === "function") {
5129 for (const value of this.#storage.values()) {
5130 if (value instanceof AnnotationEditor) {
5134 this.onAnnotationEditor(null);
5137 setValue(key, value) {
5138 const obj = this.#storage.get(key);
5139 let modified = false;
5140 if (obj !== undefined) {
5141 for (const [entry, val] of Object.entries(value)) {
5142 if (obj[entry] !== val) {
5149 this.#storage.set(key, value);
5152 this.#setModified();
5154 if (value instanceof AnnotationEditor && typeof this.onAnnotationEditor === "function") {
5155 this.onAnnotationEditor(value.constructor._type);
5159 return this.#storage.has(key);
5162 return this.#storage.size > 0 ? objectFromMap(this.#storage) : null;
5165 for (const [key, val] of Object.entries(obj)) {
5166 this.setValue(key, val);
5170 return this.#storage.size;
5173 if (!this.#modified) {
5174 this.#modified = true;
5175 if (typeof this.onSetModified === "function") {
5176 this.onSetModified();
5181 if (this.#modified) {
5182 this.#modified = false;
5183 if (typeof this.onResetModified === "function") {
5184 this.onResetModified();
5189 return new PrintAnnotationStorage(this);
5191 get serializable() {
5192 if (this.#storage.size === 0) {
5193 return SerializableEmpty;
5195 const map = new Map(),
5196 hash = new MurmurHash3_64(),
5198 const context = Object.create(null);
5199 let hasBitmap = false;
5200 for (const [key, val] of this.#storage) {
5201 const serialized = val instanceof AnnotationEditor ? val.serialize(false, context) : val;
5203 map.set(key, serialized);
5204 hash.update(`${key}:${JSON.stringify(serialized)}`);
5205 hasBitmap ||= !!serialized.bitmap;
5209 for (const value of map.values()) {
5211 transfer.push(value.bitmap);
5215 return map.size > 0 ? {
5217 hash: hash.hexdigest(),
5219 } : SerializableEmpty;
5223 const typeToEditor = new Map();
5224 for (const value of this.#storage.values()) {
5225 if (!(value instanceof AnnotationEditor)) {
5228 const editorStats = value.telemetryFinalData;
5235 if (!typeToEditor.has(type)) {
5236 typeToEditor.set(type, Object.getPrototypeOf(value).constructor);
5238 stats ||= Object.create(null);
5239 const map = stats[type] ||= new Map();
5240 for (const [key, val] of Object.entries(editorStats)) {
5241 if (key === "type") {
5244 let counters = map.get(key);
5246 counters = new Map();
5247 map.set(key, counters);
5249 const count = counters.get(val) ?? 0;
5250 counters.set(val, count + 1);
5253 for (const [type, editor] of typeToEditor) {
5254 stats[type] = editor.computeTelemetryFinalData(stats[type]);
5258 resetModifiedIds() {
5259 this.#modifiedIds = null;
5262 if (this.#modifiedIds) {
5263 return this.#modifiedIds;
5266 for (const value of this.#storage.values()) {
5267 if (!(value instanceof AnnotationEditor) || !value.annotationElementId || !value.serialize()) {
5270 ids.push(value.annotationElementId);
5272 return this.#modifiedIds = {
5278 class PrintAnnotationStorage extends AnnotationStorage {
5280 constructor(parent) {
5286 } = parent.serializable;
5287 const clone = structuredClone(map, transfer ? {
5290 this.#serializable = {
5297 unreachable("Should not call PrintAnnotationStorage.print");
5299 get serializable() {
5300 return this.#serializable;
5303 return shadow(this, "modifiedIds", {
5310 ;// ./src/display/font_loader.js
5313 #systemFonts = new Set();
5315 ownerDocument = globalThis.document,
5318 this._document = ownerDocument;
5319 this.nativeFontFaces = new Set();
5320 this.styleElement = null;
5322 addNativeFontFace(nativeFontFace) {
5323 this.nativeFontFaces.add(nativeFontFace);
5324 this._document.fonts.add(nativeFontFace);
5326 removeNativeFontFace(nativeFontFace) {
5327 this.nativeFontFaces.delete(nativeFontFace);
5328 this._document.fonts.delete(nativeFontFace);
5331 if (!this.styleElement) {
5332 this.styleElement = this._document.createElement("style");
5333 this._document.documentElement.getElementsByTagName("head")[0].append(this.styleElement);
5335 const styleSheet = this.styleElement.sheet;
5336 styleSheet.insertRule(rule, styleSheet.cssRules.length);
5339 for (const nativeFontFace of this.nativeFontFaces) {
5340 this._document.fonts.delete(nativeFontFace);
5342 this.nativeFontFaces.clear();
5343 this.#systemFonts.clear();
5344 if (this.styleElement) {
5345 this.styleElement.remove();
5346 this.styleElement = null;
5349 async loadSystemFont({
5350 systemFontInfo: info,
5353 if (!info || this.#systemFonts.has(info.loadedName)) {
5356 assert(!this.disableFontFace, "loadSystemFont shouldn't be called when `disableFontFace` is set.");
5357 if (this.isFontLoadingAPISupported) {
5363 const fontFace = new FontFace(loadedName, src, style);
5364 this.addNativeFontFace(fontFace);
5366 await fontFace.load();
5367 this.#systemFonts.add(loadedName);
5368 _inspectFont?.(info);
5370 warn(`Cannot load system font: ${info.baseFontName}, installing it could help to improve PDF rendering.`);
5371 this.removeNativeFontFace(fontFace);
5375 unreachable("Not implemented: loadSystemFont without the Font Loading API.");
5378 if (font.attached || font.missingFile && !font.systemFontInfo) {
5381 font.attached = true;
5382 if (font.systemFontInfo) {
5383 await this.loadSystemFont(font);
5386 if (this.isFontLoadingAPISupported) {
5387 const nativeFontFace = font.createNativeFontFace();
5388 if (nativeFontFace) {
5389 this.addNativeFontFace(nativeFontFace);
5391 await nativeFontFace.loaded;
5393 warn(`Failed to load font '${nativeFontFace.family}': '${ex}'.`);
5394 font.disableFontFace = true;
5400 const rule = font.createFontFaceRule();
5402 this.insertRule(rule);
5403 if (this.isSyncFontLoadingSupported) {
5406 throw new Error("Not implemented: async font loading");
5409 get isFontLoadingAPISupported() {
5410 const hasFonts = !!this._document?.fonts;
5411 return shadow(this, "isFontLoadingAPISupported", hasFonts);
5413 get isSyncFontLoadingSupported() {
5414 return shadow(this, "isSyncFontLoadingSupported", true);
5416 _queueLoadingCallback(callback) {
5417 throw new Error("Not implemented: _queueLoadingCallback");
5419 get _loadTestFont() {
5420 throw new Error("Not implemented: _loadTestFont");
5422 _prepareFontLoadEvent(font, request) {
5423 throw new Error("Not implemented: _prepareFontLoadEvent");
5426 class FontFaceObject {
5427 constructor(translatedData, {
5428 disableFontFace = false,
5429 fontExtraProperties = false,
5432 this.compiledGlyphs = Object.create(null);
5433 for (const i in translatedData) {
5434 this[i] = translatedData[i];
5436 this.disableFontFace = disableFontFace === true;
5437 this.fontExtraProperties = fontExtraProperties === true;
5438 this._inspectFont = inspectFont;
5440 createNativeFontFace() {
5441 if (!this.data || this.disableFontFace) {
5445 if (!this.cssFontInfo) {
5446 nativeFontFace = new FontFace(this.loadedName, this.data, {});
5449 weight: this.cssFontInfo.fontWeight
5451 if (this.cssFontInfo.italicAngle) {
5452 css.style = `oblique ${this.cssFontInfo.italicAngle}deg`;
5454 nativeFontFace = new FontFace(this.cssFontInfo.fontFamily, this.data, css);
5456 this._inspectFont?.(this);
5457 return nativeFontFace;
5459 createFontFaceRule() {
5460 if (!this.data || this.disableFontFace) {
5463 const url = `url(data:${this.mimetype};base64,${toBase64Util(this.data)});`;
5465 if (!this.cssFontInfo) {
5466 rule = `@font-face {font-family:"${this.loadedName}";src:${url}}`;
5468 let css = `font-weight: ${this.cssFontInfo.fontWeight};`;
5469 if (this.cssFontInfo.italicAngle) {
5470 css += `font-style: oblique ${this.cssFontInfo.italicAngle}deg;`;
5472 rule = `@font-face {font-family:"${this.cssFontInfo.fontFamily}";${css}src:${url}}`;
5474 this._inspectFont?.(this, url);
5477 getPathGenerator(objs, character) {
5478 if (this.compiledGlyphs[character] !== undefined) {
5479 return this.compiledGlyphs[character];
5481 const objId = this.loadedName + "_path_" + character;
5484 cmds = objs.get(objId);
5486 warn(`getPathGenerator - ignoring character: "${ex}".`);
5488 const path = new Path2D(cmds || "");
5489 if (!this.fontExtraProperties) {
5492 return this.compiledGlyphs[character] = path;
5496 ;// ./src/shared/message_handler.js
5498 const CallbackKind = {
5502 const StreamKind = {
5513 function wrapReason(ex) {
5514 if (ex instanceof AbortException || ex instanceof InvalidPDFException || ex instanceof PasswordException || ex instanceof ResponseException || ex instanceof UnknownErrorException) {
5517 if (!(ex instanceof Error || typeof ex === "object" && ex !== null)) {
5518 unreachable('wrapReason: Expected "reason" to be a (possibly cloned) Error.');
5521 case "AbortException":
5522 return new AbortException(ex.message);
5523 case "InvalidPDFException":
5524 return new InvalidPDFException(ex.message);
5525 case "PasswordException":
5526 return new PasswordException(ex.message, ex.code);
5527 case "ResponseException":
5528 return new ResponseException(ex.message, ex.status, ex.missing);
5529 case "UnknownErrorException":
5530 return new UnknownErrorException(ex.message, ex.details);
5532 return new UnknownErrorException(ex.message, ex.toString());
5534 class MessageHandler {
5535 #messageAC = new AbortController();
5536 constructor(sourceName, targetName, comObj) {
5537 this.sourceName = sourceName;
5538 this.targetName = targetName;
5539 this.comObj = comObj;
5540 this.callbackId = 1;
5542 this.streamSinks = Object.create(null);
5543 this.streamControllers = Object.create(null);
5544 this.callbackCapabilities = Object.create(null);
5545 this.actionHandler = Object.create(null);
5546 comObj.addEventListener("message", this.#onMessage.bind(this), {
5547 signal: this.#messageAC.signal
5553 if (data.targetName !== this.sourceName) {
5557 this.#processStreamMessage(data);
5560 if (data.callback) {
5561 const callbackId = data.callbackId;
5562 const capability = this.callbackCapabilities[callbackId];
5564 throw new Error(`Cannot resolve callback ${callbackId}`);
5566 delete this.callbackCapabilities[callbackId];
5567 if (data.callback === CallbackKind.DATA) {
5568 capability.resolve(data.data);
5569 } else if (data.callback === CallbackKind.ERROR) {
5570 capability.reject(wrapReason(data.reason));
5572 throw new Error("Unexpected callback case");
5576 const action = this.actionHandler[data.action];
5578 throw new Error(`Unknown action from worker: ${data.action}`);
5580 if (data.callbackId) {
5581 const sourceName = this.sourceName,
5582 targetName = data.sourceName,
5583 comObj = this.comObj;
5584 Promise.try(action, data.data).then(function (result) {
5585 comObj.postMessage({
5588 callback: CallbackKind.DATA,
5589 callbackId: data.callbackId,
5592 }, function (reason) {
5593 comObj.postMessage({
5596 callback: CallbackKind.ERROR,
5597 callbackId: data.callbackId,
5598 reason: wrapReason(reason)
5603 if (data.streamId) {
5604 this.#createStreamSink(data);
5609 on(actionName, handler) {
5610 const ah = this.actionHandler;
5611 if (ah[actionName]) {
5612 throw new Error(`There is already an actionName called "${actionName}"`);
5614 ah[actionName] = handler;
5616 send(actionName, data, transfers) {
5617 this.comObj.postMessage({
5618 sourceName: this.sourceName,
5619 targetName: this.targetName,
5624 sendWithPromise(actionName, data, transfers) {
5625 const callbackId = this.callbackId++;
5626 const capability = Promise.withResolvers();
5627 this.callbackCapabilities[callbackId] = capability;
5629 this.comObj.postMessage({
5630 sourceName: this.sourceName,
5631 targetName: this.targetName,
5637 capability.reject(ex);
5639 return capability.promise;
5641 sendWithStream(actionName, data, queueingStrategy, transfers) {
5642 const streamId = this.streamId++,
5643 sourceName = this.sourceName,
5644 targetName = this.targetName,
5645 comObj = this.comObj;
5646 return new ReadableStream({
5647 start: controller => {
5648 const startCapability = Promise.withResolvers();
5649 this.streamControllers[streamId] = {
5651 startCall: startCapability,
5656 comObj.postMessage({
5662 desiredSize: controller.desiredSize
5664 return startCapability.promise;
5666 pull: controller => {
5667 const pullCapability = Promise.withResolvers();
5668 this.streamControllers[streamId].pullCall = pullCapability;
5669 comObj.postMessage({
5672 stream: StreamKind.PULL,
5674 desiredSize: controller.desiredSize
5676 return pullCapability.promise;
5679 assert(reason instanceof Error, "cancel must have a valid reason");
5680 const cancelCapability = Promise.withResolvers();
5681 this.streamControllers[streamId].cancelCall = cancelCapability;
5682 this.streamControllers[streamId].isClosed = true;
5683 comObj.postMessage({
5686 stream: StreamKind.CANCEL,
5688 reason: wrapReason(reason)
5690 return cancelCapability.promise;
5692 }, queueingStrategy);
5694 #createStreamSink(data) {
5695 const streamId = data.streamId,
5696 sourceName = this.sourceName,
5697 targetName = data.sourceName,
5698 comObj = this.comObj;
5700 action = this.actionHandler[data.action];
5701 const streamSink = {
5702 enqueue(chunk, size = 1, transfers) {
5703 if (this.isCancelled) {
5706 const lastDesiredSize = this.desiredSize;
5707 this.desiredSize -= size;
5708 if (lastDesiredSize > 0 && this.desiredSize <= 0) {
5709 this.sinkCapability = Promise.withResolvers();
5710 this.ready = this.sinkCapability.promise;
5712 comObj.postMessage({
5715 stream: StreamKind.ENQUEUE,
5721 if (this.isCancelled) {
5724 this.isCancelled = true;
5725 comObj.postMessage({
5728 stream: StreamKind.CLOSE,
5731 delete self.streamSinks[streamId];
5734 assert(reason instanceof Error, "error must have a valid reason");
5735 if (this.isCancelled) {
5738 this.isCancelled = true;
5739 comObj.postMessage({
5742 stream: StreamKind.ERROR,
5744 reason: wrapReason(reason)
5747 sinkCapability: Promise.withResolvers(),
5751 desiredSize: data.desiredSize,
5754 streamSink.sinkCapability.resolve();
5755 streamSink.ready = streamSink.sinkCapability.promise;
5756 this.streamSinks[streamId] = streamSink;
5757 Promise.try(action, data.data, streamSink).then(function () {
5758 comObj.postMessage({
5761 stream: StreamKind.START_COMPLETE,
5765 }, function (reason) {
5766 comObj.postMessage({
5769 stream: StreamKind.START_COMPLETE,
5771 reason: wrapReason(reason)
5775 #processStreamMessage(data) {
5776 const streamId = data.streamId,
5777 sourceName = this.sourceName,
5778 targetName = data.sourceName,
5779 comObj = this.comObj;
5780 const streamController = this.streamControllers[streamId],
5781 streamSink = this.streamSinks[streamId];
5782 switch (data.stream) {
5783 case StreamKind.START_COMPLETE:
5785 streamController.startCall.resolve();
5787 streamController.startCall.reject(wrapReason(data.reason));
5790 case StreamKind.PULL_COMPLETE:
5792 streamController.pullCall.resolve();
5794 streamController.pullCall.reject(wrapReason(data.reason));
5797 case StreamKind.PULL:
5799 comObj.postMessage({
5802 stream: StreamKind.PULL_COMPLETE,
5808 if (streamSink.desiredSize <= 0 && data.desiredSize > 0) {
5809 streamSink.sinkCapability.resolve();
5811 streamSink.desiredSize = data.desiredSize;
5812 Promise.try(streamSink.onPull || onFn).then(function () {
5813 comObj.postMessage({
5816 stream: StreamKind.PULL_COMPLETE,
5820 }, function (reason) {
5821 comObj.postMessage({
5824 stream: StreamKind.PULL_COMPLETE,
5826 reason: wrapReason(reason)
5830 case StreamKind.ENQUEUE:
5831 assert(streamController, "enqueue should have stream controller");
5832 if (streamController.isClosed) {
5835 streamController.controller.enqueue(data.chunk);
5837 case StreamKind.CLOSE:
5838 assert(streamController, "close should have stream controller");
5839 if (streamController.isClosed) {
5842 streamController.isClosed = true;
5843 streamController.controller.close();
5844 this.#deleteStreamController(streamController, streamId);
5846 case StreamKind.ERROR:
5847 assert(streamController, "error should have stream controller");
5848 streamController.controller.error(wrapReason(data.reason));
5849 this.#deleteStreamController(streamController, streamId);
5851 case StreamKind.CANCEL_COMPLETE:
5853 streamController.cancelCall.resolve();
5855 streamController.cancelCall.reject(wrapReason(data.reason));
5857 this.#deleteStreamController(streamController, streamId);
5859 case StreamKind.CANCEL:
5863 const dataReason = wrapReason(data.reason);
5864 Promise.try(streamSink.onCancel || onFn, dataReason).then(function () {
5865 comObj.postMessage({
5868 stream: StreamKind.CANCEL_COMPLETE,
5872 }, function (reason) {
5873 comObj.postMessage({
5876 stream: StreamKind.CANCEL_COMPLETE,
5878 reason: wrapReason(reason)
5881 streamSink.sinkCapability.reject(dataReason);
5882 streamSink.isCancelled = true;
5883 delete this.streamSinks[streamId];
5886 throw new Error("Unexpected stream case");
5889 async #deleteStreamController(streamController, streamId) {
5890 await Promise.allSettled([streamController.startCall?.promise, streamController.pullCall?.promise, streamController.cancelCall?.promise]);
5891 delete this.streamControllers[streamId];
5894 this.#messageAC?.abort();
5895 this.#messageAC = null;
5899 ;// ./src/display/pattern_helper.js
5907 function applyBoundingBox(ctx, bbox) {
5911 const width = bbox[2] - bbox[0];
5912 const height = bbox[3] - bbox[1];
5913 const region = new Path2D();
5914 region.rect(bbox[0], bbox[1], width, height);
5917 class BaseShadingPattern {
5919 unreachable("Abstract method `getPattern` called.");
5922 class RadialAxialShadingPattern extends BaseShadingPattern {
5927 this._colorStops = IR[3];
5934 _createGradient(ctx) {
5936 if (this._type === "axial") {
5937 grad = ctx.createLinearGradient(this._p0[0], this._p0[1], this._p1[0], this._p1[1]);
5938 } else if (this._type === "radial") {
5939 grad = ctx.createRadialGradient(this._p0[0], this._p0[1], this._r0, this._p1[0], this._p1[1], this._r1);
5941 for (const colorStop of this._colorStops) {
5942 grad.addColorStop(colorStop[0], colorStop[1]);
5946 getPattern(ctx, owner, inverse, pathType) {
5948 if (pathType === PathType.STROKE || pathType === PathType.FILL) {
5949 const ownerBBox = owner.current.getClippedPathBoundingBox(pathType, getCurrentTransform(ctx)) || [0, 0, 0, 0];
5950 const width = Math.ceil(ownerBBox[2] - ownerBBox[0]) || 1;
5951 const height = Math.ceil(ownerBBox[3] - ownerBBox[1]) || 1;
5952 const tmpCanvas = owner.cachedCanvases.getCanvas("pattern", width, height);
5953 const tmpCtx = tmpCanvas.context;
5954 tmpCtx.clearRect(0, 0, tmpCtx.canvas.width, tmpCtx.canvas.height);
5956 tmpCtx.rect(0, 0, tmpCtx.canvas.width, tmpCtx.canvas.height);
5957 tmpCtx.translate(-ownerBBox[0], -ownerBBox[1]);
5958 inverse = Util.transform(inverse, [1, 0, 0, 1, ownerBBox[0], ownerBBox[1]]);
5959 tmpCtx.transform(...owner.baseTransform);
5961 tmpCtx.transform(...this.matrix);
5963 applyBoundingBox(tmpCtx, this._bbox);
5964 tmpCtx.fillStyle = this._createGradient(tmpCtx);
5966 pattern = ctx.createPattern(tmpCanvas.canvas, "no-repeat");
5967 const domMatrix = new DOMMatrix(inverse);
5968 pattern.setTransform(domMatrix);
5970 applyBoundingBox(ctx, this._bbox);
5971 pattern = this._createGradient(ctx);
5976 function drawTriangle(data, context, p1, p2, p3, c1, c2, c3) {
5977 const coords = context.coords,
5978 colors = context.colors;
5979 const bytes = data.data,
5980 rowSize = data.width * 4;
5982 if (coords[p1 + 1] > coords[p2 + 1]) {
5990 if (coords[p2 + 1] > coords[p3 + 1]) {
5998 if (coords[p1 + 1] > coords[p2 + 1]) {
6006 const x1 = (coords[p1] + context.offsetX) * context.scaleX;
6007 const y1 = (coords[p1 + 1] + context.offsetY) * context.scaleY;
6008 const x2 = (coords[p2] + context.offsetX) * context.scaleX;
6009 const y2 = (coords[p2 + 1] + context.offsetY) * context.scaleY;
6010 const x3 = (coords[p3] + context.offsetX) * context.scaleX;
6011 const y3 = (coords[p3 + 1] + context.offsetY) * context.scaleY;
6015 const c1r = colors[c1],
6016 c1g = colors[c1 + 1],
6017 c1b = colors[c1 + 2];
6018 const c2r = colors[c2],
6019 c2g = colors[c2 + 1],
6020 c2b = colors[c2 + 2];
6021 const c3r = colors[c3],
6022 c3g = colors[c3 + 1],
6023 c3b = colors[c3 + 2];
6024 const minY = Math.round(y1),
6025 maxY = Math.round(y3);
6026 let xa, car, cag, cab;
6027 let xb, cbr, cbg, cbb;
6028 for (let y = minY; y <= maxY; y++) {
6030 const k = y < y1 ? 0 : (y1 - y) / (y1 - y2);
6031 xa = x1 - (x1 - x2) * k;
6032 car = c1r - (c1r - c2r) * k;
6033 cag = c1g - (c1g - c2g) * k;
6034 cab = c1b - (c1b - c2b) * k;
6039 } else if (y2 === y3) {
6042 k = (y2 - y) / (y2 - y3);
6044 xa = x2 - (x2 - x3) * k;
6045 car = c2r - (c2r - c3r) * k;
6046 cag = c2g - (c2g - c3g) * k;
6047 cab = c2b - (c2b - c3b) * k;
6052 } else if (y > y3) {
6055 k = (y1 - y) / (y1 - y3);
6057 xb = x1 - (x1 - x3) * k;
6058 cbr = c1r - (c1r - c3r) * k;
6059 cbg = c1g - (c1g - c3g) * k;
6060 cbb = c1b - (c1b - c3b) * k;
6061 const x1_ = Math.round(Math.min(xa, xb));
6062 const x2_ = Math.round(Math.max(xa, xb));
6063 let j = rowSize * y + x1_ * 4;
6064 for (let x = x1_; x <= x2_; x++) {
6065 k = (xa - x) / (xa - xb);
6071 bytes[j++] = car - (car - cbr) * k | 0;
6072 bytes[j++] = cag - (cag - cbg) * k | 0;
6073 bytes[j++] = cab - (cab - cbb) * k | 0;
6078 function drawFigure(data, figure, context) {
6079 const ps = figure.coords;
6080 const cs = figure.colors;
6082 switch (figure.type) {
6084 const verticesPerRow = figure.verticesPerRow;
6085 const rows = Math.floor(ps.length / verticesPerRow) - 1;
6086 const cols = verticesPerRow - 1;
6087 for (i = 0; i < rows; i++) {
6088 let q = i * verticesPerRow;
6089 for (let j = 0; j < cols; j++, q++) {
6090 drawTriangle(data, context, ps[q], ps[q + 1], ps[q + verticesPerRow], cs[q], cs[q + 1], cs[q + verticesPerRow]);
6091 drawTriangle(data, context, ps[q + verticesPerRow + 1], ps[q + 1], ps[q + verticesPerRow], cs[q + verticesPerRow + 1], cs[q + 1], cs[q + verticesPerRow]);
6096 for (i = 0, ii = ps.length; i < ii; i += 3) {
6097 drawTriangle(data, context, ps[i], ps[i + 1], ps[i + 2], cs[i], cs[i + 1], cs[i + 2]);
6101 throw new Error("illegal figure");
6104 class MeshShadingPattern extends BaseShadingPattern {
6107 this._coords = IR[2];
6108 this._colors = IR[3];
6109 this._figures = IR[4];
6110 this._bounds = IR[5];
6112 this._background = IR[7];
6115 _createMeshCanvas(combinedScale, backgroundColor, cachedCanvases) {
6116 const EXPECTED_SCALE = 1.1;
6117 const MAX_PATTERN_SIZE = 3000;
6118 const BORDER_SIZE = 2;
6119 const offsetX = Math.floor(this._bounds[0]);
6120 const offsetY = Math.floor(this._bounds[1]);
6121 const boundsWidth = Math.ceil(this._bounds[2]) - offsetX;
6122 const boundsHeight = Math.ceil(this._bounds[3]) - offsetY;
6123 const width = Math.min(Math.ceil(Math.abs(boundsWidth * combinedScale[0] * EXPECTED_SCALE)), MAX_PATTERN_SIZE);
6124 const height = Math.min(Math.ceil(Math.abs(boundsHeight * combinedScale[1] * EXPECTED_SCALE)), MAX_PATTERN_SIZE);
6125 const scaleX = boundsWidth / width;
6126 const scaleY = boundsHeight / height;
6128 coords: this._coords,
6129 colors: this._colors,
6135 const paddedWidth = width + BORDER_SIZE * 2;
6136 const paddedHeight = height + BORDER_SIZE * 2;
6137 const tmpCanvas = cachedCanvases.getCanvas("mesh", paddedWidth, paddedHeight);
6138 const tmpCtx = tmpCanvas.context;
6139 const data = tmpCtx.createImageData(width, height);
6140 if (backgroundColor) {
6141 const bytes = data.data;
6142 for (let i = 0, ii = bytes.length; i < ii; i += 4) {
6143 bytes[i] = backgroundColor[0];
6144 bytes[i + 1] = backgroundColor[1];
6145 bytes[i + 2] = backgroundColor[2];
6149 for (const figure of this._figures) {
6150 drawFigure(data, figure, context);
6152 tmpCtx.putImageData(data, BORDER_SIZE, BORDER_SIZE);
6153 const canvas = tmpCanvas.canvas;
6156 offsetX: offsetX - BORDER_SIZE * scaleX,
6157 offsetY: offsetY - BORDER_SIZE * scaleY,
6162 getPattern(ctx, owner, inverse, pathType) {
6163 applyBoundingBox(ctx, this._bbox);
6165 if (pathType === PathType.SHADING) {
6166 scale = Util.singularValueDecompose2dScale(getCurrentTransform(ctx));
6168 scale = Util.singularValueDecompose2dScale(owner.baseTransform);
6170 const matrixScale = Util.singularValueDecompose2dScale(this.matrix);
6171 scale = [scale[0] * matrixScale[0], scale[1] * matrixScale[1]];
6174 const temporaryPatternCanvas = this._createMeshCanvas(scale, pathType === PathType.SHADING ? null : this._background, owner.cachedCanvases);
6175 if (pathType !== PathType.SHADING) {
6176 ctx.setTransform(...owner.baseTransform);
6178 ctx.transform(...this.matrix);
6181 ctx.translate(temporaryPatternCanvas.offsetX, temporaryPatternCanvas.offsetY);
6182 ctx.scale(temporaryPatternCanvas.scaleX, temporaryPatternCanvas.scaleY);
6183 return ctx.createPattern(temporaryPatternCanvas.canvas, "no-repeat");
6186 class DummyShadingPattern extends BaseShadingPattern {
6191 function getShadingPattern(IR) {
6194 return new RadialAxialShadingPattern(IR);
6196 return new MeshShadingPattern(IR);
6198 return new DummyShadingPattern();
6200 throw new Error(`Unknown IR type: ${IR[0]}`);
6206 class TilingPattern {
6207 static MAX_PATTERN_SIZE = 3000;
6208 constructor(IR, color, ctx, canvasGraphicsFactory, baseTransform) {
6209 this.operatorList = IR[2];
6210 this.matrix = IR[3];
6214 this.paintType = IR[7];
6215 this.tilingType = IR[8];
6218 this.canvasGraphicsFactory = canvasGraphicsFactory;
6219 this.baseTransform = baseTransform;
6221 createPatternCanvas(owner) {
6228 canvasGraphicsFactory
6234 xstep = Math.abs(xstep);
6235 ystep = Math.abs(ystep);
6236 info("TilingType: " + tilingType);
6241 const width = x1 - x0;
6242 const height = y1 - y0;
6243 const matrixScale = Util.singularValueDecompose2dScale(this.matrix);
6244 const curMatrixScale = Util.singularValueDecompose2dScale(this.baseTransform);
6245 const combinedScaleX = matrixScale[0] * curMatrixScale[0];
6246 const combinedScaleY = matrixScale[1] * curMatrixScale[1];
6247 let canvasWidth = width,
6248 canvasHeight = height,
6249 redrawHorizontally = false,
6250 redrawVertically = false;
6251 const xScaledStep = Math.ceil(xstep * combinedScaleX);
6252 const yScaledStep = Math.ceil(ystep * combinedScaleY);
6253 const xScaledWidth = Math.ceil(width * combinedScaleX);
6254 const yScaledHeight = Math.ceil(height * combinedScaleY);
6255 if (xScaledStep >= xScaledWidth) {
6256 canvasWidth = xstep;
6258 redrawHorizontally = true;
6260 if (yScaledStep >= yScaledHeight) {
6261 canvasHeight = ystep;
6263 redrawVertically = true;
6265 const dimx = this.getSizeAndScale(canvasWidth, this.ctx.canvas.width, combinedScaleX);
6266 const dimy = this.getSizeAndScale(canvasHeight, this.ctx.canvas.height, combinedScaleY);
6267 const tmpCanvas = owner.cachedCanvases.getCanvas("pattern", dimx.size, dimy.size);
6268 const tmpCtx = tmpCanvas.context;
6269 const graphics = canvasGraphicsFactory.createCanvasGraphics(tmpCtx);
6270 graphics.groupLevel = owner.groupLevel;
6271 this.setFillAndStrokeStyleToContext(graphics, paintType, color);
6272 tmpCtx.translate(-dimx.scale * x0, -dimy.scale * y0);
6273 graphics.transform(dimx.scale, 0, 0, dimy.scale, 0, 0);
6275 this.clipBbox(graphics, x0, y0, x1, y1);
6276 graphics.baseTransform = getCurrentTransform(graphics.ctx);
6277 graphics.executeOperatorList(operatorList);
6278 graphics.endDrawing();
6280 if (redrawHorizontally || redrawVertically) {
6281 const image = tmpCanvas.canvas;
6282 if (redrawHorizontally) {
6283 canvasWidth = xstep;
6285 if (redrawVertically) {
6286 canvasHeight = ystep;
6288 const dimx2 = this.getSizeAndScale(canvasWidth, this.ctx.canvas.width, combinedScaleX);
6289 const dimy2 = this.getSizeAndScale(canvasHeight, this.ctx.canvas.height, combinedScaleY);
6290 const xSize = dimx2.size;
6291 const ySize = dimy2.size;
6292 const tmpCanvas2 = owner.cachedCanvases.getCanvas("pattern-workaround", xSize, ySize);
6293 const tmpCtx2 = tmpCanvas2.context;
6294 const ii = redrawHorizontally ? Math.floor(width / xstep) : 0;
6295 const jj = redrawVertically ? Math.floor(height / ystep) : 0;
6296 for (let i = 0; i <= ii; i++) {
6297 for (let j = 0; j <= jj; j++) {
6298 tmpCtx2.drawImage(image, xSize * i, ySize * j, xSize, ySize, 0, 0, xSize, ySize);
6302 canvas: tmpCanvas2.canvas,
6303 scaleX: dimx2.scale,
6304 scaleY: dimy2.scale,
6310 canvas: tmpCanvas.canvas,
6317 getSizeAndScale(step, realOutputSize, scale) {
6318 const maxSize = Math.max(TilingPattern.MAX_PATTERN_SIZE, realOutputSize);
6319 let size = Math.ceil(step * scale);
6320 if (size >= maxSize) {
6323 scale = size / step;
6330 clipBbox(graphics, x0, y0, x1, y1) {
6331 const bboxWidth = x1 - x0;
6332 const bboxHeight = y1 - y0;
6333 graphics.ctx.rect(x0, y0, bboxWidth, bboxHeight);
6334 graphics.current.updateRectMinMax(getCurrentTransform(graphics.ctx), [x0, y0, x1, y1]);
6338 setFillAndStrokeStyleToContext(graphics, paintType, color) {
6339 const context = graphics.ctx,
6340 current = graphics.current;
6341 switch (paintType) {
6342 case PaintType.COLORED:
6343 const ctx = this.ctx;
6344 context.fillStyle = ctx.fillStyle;
6345 context.strokeStyle = ctx.strokeStyle;
6346 current.fillColor = ctx.fillStyle;
6347 current.strokeColor = ctx.strokeStyle;
6349 case PaintType.UNCOLORED:
6350 const cssColor = Util.makeHexColor(color[0], color[1], color[2]);
6351 context.fillStyle = cssColor;
6352 context.strokeStyle = cssColor;
6353 current.fillColor = cssColor;
6354 current.strokeColor = cssColor;
6357 throw new FormatError(`Unsupported paint type: ${paintType}`);
6360 getPattern(ctx, owner, inverse, pathType) {
6361 let matrix = inverse;
6362 if (pathType !== PathType.SHADING) {
6363 matrix = Util.transform(matrix, owner.baseTransform);
6365 matrix = Util.transform(matrix, this.matrix);
6368 const temporaryPatternCanvas = this.createPatternCanvas(owner);
6369 let domMatrix = new DOMMatrix(matrix);
6370 domMatrix = domMatrix.translate(temporaryPatternCanvas.offsetX, temporaryPatternCanvas.offsetY);
6371 domMatrix = domMatrix.scale(1 / temporaryPatternCanvas.scaleX, 1 / temporaryPatternCanvas.scaleY);
6372 const pattern = ctx.createPattern(temporaryPatternCanvas.canvas, "repeat");
6373 pattern.setTransform(domMatrix);
6378 ;// ./src/shared/image_utils.js
6380 function convertToRGBA(params) {
6381 switch (params.kind) {
6382 case ImageKind.GRAYSCALE_1BPP:
6383 return convertBlackAndWhiteToRGBA(params);
6384 case ImageKind.RGB_24BPP:
6385 return convertRGBToRGBA(params);
6389 function convertBlackAndWhiteToRGBA({
6395 nonBlackColor = 0xffffffff,
6396 inverseDecode = false
6398 const black = util_FeatureTest.isLittleEndian ? 0xff000000 : 0x000000ff;
6399 const [zeroMapping, oneMapping] = inverseDecode ? [nonBlackColor, black] : [black, nonBlackColor];
6400 const widthInSource = width >> 3;
6401 const widthRemainder = width & 7;
6402 const srcLength = src.length;
6403 dest = new Uint32Array(dest.buffer);
6405 for (let i = 0; i < height; i++) {
6406 for (const max = srcPos + widthInSource; srcPos < max; srcPos++) {
6407 const elem = srcPos < srcLength ? src[srcPos] : 255;
6408 dest[destPos++] = elem & 0b10000000 ? oneMapping : zeroMapping;
6409 dest[destPos++] = elem & 0b1000000 ? oneMapping : zeroMapping;
6410 dest[destPos++] = elem & 0b100000 ? oneMapping : zeroMapping;
6411 dest[destPos++] = elem & 0b10000 ? oneMapping : zeroMapping;
6412 dest[destPos++] = elem & 0b1000 ? oneMapping : zeroMapping;
6413 dest[destPos++] = elem & 0b100 ? oneMapping : zeroMapping;
6414 dest[destPos++] = elem & 0b10 ? oneMapping : zeroMapping;
6415 dest[destPos++] = elem & 0b1 ? oneMapping : zeroMapping;
6417 if (widthRemainder === 0) {
6420 const elem = srcPos < srcLength ? src[srcPos++] : 255;
6421 for (let j = 0; j < widthRemainder; j++) {
6422 dest[destPos++] = elem & 1 << 7 - j ? oneMapping : zeroMapping;
6430 function convertRGBToRGBA({
6439 const len = width * height * 3;
6440 const len32 = len >> 2;
6441 const src32 = new Uint32Array(src.buffer, srcPos, len32);
6442 if (FeatureTest.isLittleEndian) {
6443 for (; i < len32 - 2; i += 3, destPos += 4) {
6444 const s1 = src32[i];
6445 const s2 = src32[i + 1];
6446 const s3 = src32[i + 2];
6447 dest[destPos] = s1 | 0xff000000;
6448 dest[destPos + 1] = s1 >>> 24 | s2 << 8 | 0xff000000;
6449 dest[destPos + 2] = s2 >>> 16 | s3 << 16 | 0xff000000;
6450 dest[destPos + 3] = s3 >>> 8 | 0xff000000;
6452 for (let j = i * 4, jj = srcPos + len; j < jj; j += 3) {
6453 dest[destPos++] = src[j] | src[j + 1] << 8 | src[j + 2] << 16 | 0xff000000;
6456 for (; i < len32 - 2; i += 3, destPos += 4) {
6457 const s1 = src32[i];
6458 const s2 = src32[i + 1];
6459 const s3 = src32[i + 2];
6460 dest[destPos] = s1 | 0xff;
6461 dest[destPos + 1] = s1 << 24 | s2 >>> 8 | 0xff;
6462 dest[destPos + 2] = s2 << 16 | s3 >>> 16 | 0xff;
6463 dest[destPos + 3] = s3 << 8 | 0xff;
6465 for (let j = i * 4, jj = srcPos + len; j < jj; j += 3) {
6466 dest[destPos++] = src[j] << 24 | src[j + 1] << 16 | src[j + 2] << 8 | 0xff;
6470 srcPos: srcPos + len,
6474 function grayToRGBA(src, dest) {
6475 if (FeatureTest.isLittleEndian) {
6476 for (let i = 0, ii = src.length; i < ii; i++) {
6477 dest[i] = src[i] * 0x10101 | 0xff000000;
6480 for (let i = 0, ii = src.length; i < ii; i++) {
6481 dest[i] = src[i] * 0x1010100 | 0x000000ff;
6486 ;// ./src/display/canvas.js
6491 const MIN_FONT_SIZE = 16;
6492 const MAX_FONT_SIZE = 100;
6493 const EXECUTION_TIME = 15;
6494 const EXECUTION_STEPS = 10;
6495 const MAX_SIZE_TO_COMPILE = 1000;
6496 const FULL_CHUNK_HEIGHT = 16;
6497 function mirrorContextOperations(ctx, destCtx) {
6498 if (ctx._removeMirroring) {
6499 throw new Error("Context is already forwarding operations.");
6501 ctx.__originalSave = ctx.save;
6502 ctx.__originalRestore = ctx.restore;
6503 ctx.__originalRotate = ctx.rotate;
6504 ctx.__originalScale = ctx.scale;
6505 ctx.__originalTranslate = ctx.translate;
6506 ctx.__originalTransform = ctx.transform;
6507 ctx.__originalSetTransform = ctx.setTransform;
6508 ctx.__originalResetTransform = ctx.resetTransform;
6509 ctx.__originalClip = ctx.clip;
6510 ctx.__originalMoveTo = ctx.moveTo;
6511 ctx.__originalLineTo = ctx.lineTo;
6512 ctx.__originalBezierCurveTo = ctx.bezierCurveTo;
6513 ctx.__originalRect = ctx.rect;
6514 ctx.__originalClosePath = ctx.closePath;
6515 ctx.__originalBeginPath = ctx.beginPath;
6516 ctx._removeMirroring = () => {
6517 ctx.save = ctx.__originalSave;
6518 ctx.restore = ctx.__originalRestore;
6519 ctx.rotate = ctx.__originalRotate;
6520 ctx.scale = ctx.__originalScale;
6521 ctx.translate = ctx.__originalTranslate;
6522 ctx.transform = ctx.__originalTransform;
6523 ctx.setTransform = ctx.__originalSetTransform;
6524 ctx.resetTransform = ctx.__originalResetTransform;
6525 ctx.clip = ctx.__originalClip;
6526 ctx.moveTo = ctx.__originalMoveTo;
6527 ctx.lineTo = ctx.__originalLineTo;
6528 ctx.bezierCurveTo = ctx.__originalBezierCurveTo;
6529 ctx.rect = ctx.__originalRect;
6530 ctx.closePath = ctx.__originalClosePath;
6531 ctx.beginPath = ctx.__originalBeginPath;
6532 delete ctx._removeMirroring;
6534 ctx.save = function ctxSave() {
6536 this.__originalSave();
6538 ctx.restore = function ctxRestore() {
6540 this.__originalRestore();
6542 ctx.translate = function ctxTranslate(x, y) {
6543 destCtx.translate(x, y);
6544 this.__originalTranslate(x, y);
6546 ctx.scale = function ctxScale(x, y) {
6547 destCtx.scale(x, y);
6548 this.__originalScale(x, y);
6550 ctx.transform = function ctxTransform(a, b, c, d, e, f) {
6551 destCtx.transform(a, b, c, d, e, f);
6552 this.__originalTransform(a, b, c, d, e, f);
6554 ctx.setTransform = function ctxSetTransform(a, b, c, d, e, f) {
6555 destCtx.setTransform(a, b, c, d, e, f);
6556 this.__originalSetTransform(a, b, c, d, e, f);
6558 ctx.resetTransform = function ctxResetTransform() {
6559 destCtx.resetTransform();
6560 this.__originalResetTransform();
6562 ctx.rotate = function ctxRotate(angle) {
6563 destCtx.rotate(angle);
6564 this.__originalRotate(angle);
6566 ctx.clip = function ctxRotate(rule) {
6568 this.__originalClip(rule);
6570 ctx.moveTo = function (x, y) {
6571 destCtx.moveTo(x, y);
6572 this.__originalMoveTo(x, y);
6574 ctx.lineTo = function (x, y) {
6575 destCtx.lineTo(x, y);
6576 this.__originalLineTo(x, y);
6578 ctx.bezierCurveTo = function (cp1x, cp1y, cp2x, cp2y, x, y) {
6579 destCtx.bezierCurveTo(cp1x, cp1y, cp2x, cp2y, x, y);
6580 this.__originalBezierCurveTo(cp1x, cp1y, cp2x, cp2y, x, y);
6582 ctx.rect = function (x, y, width, height) {
6583 destCtx.rect(x, y, width, height);
6584 this.__originalRect(x, y, width, height);
6586 ctx.closePath = function () {
6587 destCtx.closePath();
6588 this.__originalClosePath();
6590 ctx.beginPath = function () {
6591 destCtx.beginPath();
6592 this.__originalBeginPath();
6595 class CachedCanvases {
6596 constructor(canvasFactory) {
6597 this.canvasFactory = canvasFactory;
6598 this.cache = Object.create(null);
6600 getCanvas(id, width, height) {
6602 if (this.cache[id] !== undefined) {
6603 canvasEntry = this.cache[id];
6604 this.canvasFactory.reset(canvasEntry, width, height);
6606 canvasEntry = this.canvasFactory.create(width, height);
6607 this.cache[id] = canvasEntry;
6612 delete this.cache[id];
6615 for (const id in this.cache) {
6616 const canvasEntry = this.cache[id];
6617 this.canvasFactory.destroy(canvasEntry);
6618 delete this.cache[id];
6622 function drawImageAtIntegerCoords(ctx, srcImg, srcX, srcY, srcW, srcH, destX, destY, destW, destH) {
6623 const [a, b, c, d, tx, ty] = getCurrentTransform(ctx);
6624 if (b === 0 && c === 0) {
6625 const tlX = destX * a + tx;
6626 const rTlX = Math.round(tlX);
6627 const tlY = destY * d + ty;
6628 const rTlY = Math.round(tlY);
6629 const brX = (destX + destW) * a + tx;
6630 const rWidth = Math.abs(Math.round(brX) - rTlX) || 1;
6631 const brY = (destY + destH) * d + ty;
6632 const rHeight = Math.abs(Math.round(brY) - rTlY) || 1;
6633 ctx.setTransform(Math.sign(a), 0, 0, Math.sign(d), rTlX, rTlY);
6634 ctx.drawImage(srcImg, srcX, srcY, srcW, srcH, 0, 0, rWidth, rHeight);
6635 ctx.setTransform(a, b, c, d, tx, ty);
6636 return [rWidth, rHeight];
6638 if (a === 0 && d === 0) {
6639 const tlX = destY * c + tx;
6640 const rTlX = Math.round(tlX);
6641 const tlY = destX * b + ty;
6642 const rTlY = Math.round(tlY);
6643 const brX = (destY + destH) * c + tx;
6644 const rWidth = Math.abs(Math.round(brX) - rTlX) || 1;
6645 const brY = (destX + destW) * b + ty;
6646 const rHeight = Math.abs(Math.round(brY) - rTlY) || 1;
6647 ctx.setTransform(0, Math.sign(b), Math.sign(c), 0, rTlX, rTlY);
6648 ctx.drawImage(srcImg, srcX, srcY, srcW, srcH, 0, 0, rHeight, rWidth);
6649 ctx.setTransform(a, b, c, d, tx, ty);
6650 return [rHeight, rWidth];
6652 ctx.drawImage(srcImg, srcX, srcY, srcW, srcH, destX, destY, destW, destH);
6653 const scaleX = Math.hypot(a, b);
6654 const scaleY = Math.hypot(c, d);
6655 return [scaleX * destW, scaleY * destH];
6657 function compileType3Glyph(imgData) {
6662 if (width > MAX_SIZE_TO_COMPILE || height > MAX_SIZE_TO_COMPILE) {
6665 const POINT_TO_PROCESS_LIMIT = 1000;
6666 const POINT_TYPES = new Uint8Array([0, 2, 4, 0, 1, 0, 5, 4, 8, 10, 0, 8, 0, 2, 1, 0]);
6667 const width1 = width + 1;
6668 let points = new Uint8Array(width1 * (height + 1));
6670 const lineSize = width + 7 & ~7;
6671 let data = new Uint8Array(lineSize * height),
6673 for (const elem of imgData.data) {
6676 data[pos++] = elem & mask ? 0 : 255;
6682 if (data[pos] !== 0) {
6686 for (j = 1; j < width; j++) {
6687 if (data[pos] !== data[pos + 1]) {
6688 points[j] = data[pos] ? 2 : 1;
6693 if (data[pos] !== 0) {
6697 for (i = 1; i < height; i++) {
6700 if (data[pos - lineSize] !== data[pos]) {
6701 points[j0] = data[pos] ? 1 : 8;
6704 let sum = (data[pos] ? 4 : 0) + (data[pos - lineSize] ? 8 : 0);
6705 for (j = 1; j < width; j++) {
6706 sum = (sum >> 2) + (data[pos + 1] ? 4 : 0) + (data[pos - lineSize + 1] ? 8 : 0);
6707 if (POINT_TYPES[sum]) {
6708 points[j0 + j] = POINT_TYPES[sum];
6713 if (data[pos - lineSize] !== data[pos]) {
6714 points[j0 + j] = data[pos] ? 2 : 4;
6717 if (count > POINT_TO_PROCESS_LIMIT) {
6721 pos = lineSize * (height - 1);
6723 if (data[pos] !== 0) {
6727 for (j = 1; j < width; j++) {
6728 if (data[pos] !== data[pos + 1]) {
6729 points[j0 + j] = data[pos] ? 4 : 8;
6734 if (data[pos] !== 0) {
6738 if (count > POINT_TO_PROCESS_LIMIT) {
6741 const steps = new Int32Array([0, width1, -1, 0, -width1, 0, 0, 0, 1]);
6742 const path = new Path2D();
6743 for (i = 0; count && i <= height; i++) {
6745 const end = p + width;
6746 while (p < end && !points[p]) {
6752 path.moveTo(p % width1, i);
6754 let type = points[p];
6756 const step = steps[type];
6759 } while (!points[p]);
6760 const pp = points[p];
6761 if (pp !== 5 && pp !== 10) {
6765 type = pp & 0x33 * type >> 4;
6766 points[p] &= type >> 2 | type << 2;
6768 path.lineTo(p % width1, p / width1 | 0);
6777 const drawOutline = function (c) {
6779 c.scale(1 / width, -1 / height);
6780 c.translate(0, -height);
6787 class CanvasExtraState {
6788 constructor(width, height) {
6789 this.alphaIsShape = false;
6791 this.fontSizeScale = 1;
6792 this.textMatrix = IDENTITY_MATRIX;
6793 this.textMatrixScale = 1;
6794 this.fontMatrix = FONT_IDENTITY_MATRIX;
6800 this.charSpacing = 0;
6801 this.wordSpacing = 0;
6802 this.textHScale = 1;
6803 this.textRenderingMode = TextRenderingMode.FILL;
6805 this.fillColor = "#000000";
6806 this.strokeColor = "#000000";
6807 this.patternFill = false;
6808 this.patternStroke = false;
6810 this.strokeAlpha = 1;
6812 this.activeSMask = null;
6813 this.transferMaps = "none";
6814 this.startNewPathAndClipBox([0, 0, width, height]);
6817 const clone = Object.create(this);
6818 clone.clipBox = this.clipBox.slice();
6821 setCurrentPoint(x, y) {
6825 updatePathMinMax(transform, x, y) {
6826 [x, y] = Util.applyTransform([x, y], transform);
6827 this.minX = Math.min(this.minX, x);
6828 this.minY = Math.min(this.minY, y);
6829 this.maxX = Math.max(this.maxX, x);
6830 this.maxY = Math.max(this.maxY, y);
6832 updateRectMinMax(transform, rect) {
6833 const p1 = Util.applyTransform(rect, transform);
6834 const p2 = Util.applyTransform(rect.slice(2), transform);
6835 const p3 = Util.applyTransform([rect[0], rect[3]], transform);
6836 const p4 = Util.applyTransform([rect[2], rect[1]], transform);
6837 this.minX = Math.min(this.minX, p1[0], p2[0], p3[0], p4[0]);
6838 this.minY = Math.min(this.minY, p1[1], p2[1], p3[1], p4[1]);
6839 this.maxX = Math.max(this.maxX, p1[0], p2[0], p3[0], p4[0]);
6840 this.maxY = Math.max(this.maxY, p1[1], p2[1], p3[1], p4[1]);
6842 updateScalingPathMinMax(transform, minMax) {
6843 Util.scaleMinMax(transform, minMax);
6844 this.minX = Math.min(this.minX, minMax[0]);
6845 this.minY = Math.min(this.minY, minMax[1]);
6846 this.maxX = Math.max(this.maxX, minMax[2]);
6847 this.maxY = Math.max(this.maxY, minMax[3]);
6849 updateCurvePathMinMax(transform, x0, y0, x1, y1, x2, y2, x3, y3, minMax) {
6850 const box = Util.bezierBoundingBox(x0, y0, x1, y1, x2, y2, x3, y3, minMax);
6854 this.updateRectMinMax(transform, box);
6856 getPathBoundingBox(pathType = PathType.FILL, transform = null) {
6857 const box = [this.minX, this.minY, this.maxX, this.maxY];
6858 if (pathType === PathType.STROKE) {
6860 unreachable("Stroke bounding box must include transform.");
6862 const scale = Util.singularValueDecompose2dScale(transform);
6863 const xStrokePad = scale[0] * this.lineWidth / 2;
6864 const yStrokePad = scale[1] * this.lineWidth / 2;
6865 box[0] -= xStrokePad;
6866 box[1] -= yStrokePad;
6867 box[2] += xStrokePad;
6868 box[3] += yStrokePad;
6872 updateClipFromPath() {
6873 const intersect = Util.intersect(this.clipBox, this.getPathBoundingBox());
6874 this.startNewPathAndClipBox(intersect || [0, 0, 0, 0]);
6877 return this.minX === Infinity;
6879 startNewPathAndClipBox(box) {
6881 this.minX = Infinity;
6882 this.minY = Infinity;
6886 getClippedPathBoundingBox(pathType = PathType.FILL, transform = null) {
6887 return Util.intersect(this.clipBox, this.getPathBoundingBox(pathType, transform));
6890 function putBinaryImageData(ctx, imgData) {
6891 if (imgData instanceof ImageData) {
6892 ctx.putImageData(imgData, 0, 0);
6895 const height = imgData.height,
6896 width = imgData.width;
6897 const partialChunkHeight = height % FULL_CHUNK_HEIGHT;
6898 const fullChunks = (height - partialChunkHeight) / FULL_CHUNK_HEIGHT;
6899 const totalChunks = partialChunkHeight === 0 ? fullChunks : fullChunks + 1;
6900 const chunkImgData = ctx.createImageData(width, FULL_CHUNK_HEIGHT);
6903 const src = imgData.data;
6904 const dest = chunkImgData.data;
6905 let i, j, thisChunkHeight, elemsInThisChunk;
6906 if (imgData.kind === util_ImageKind.GRAYSCALE_1BPP) {
6907 const srcLength = src.byteLength;
6908 const dest32 = new Uint32Array(dest.buffer, 0, dest.byteLength >> 2);
6909 const dest32DataLength = dest32.length;
6910 const fullSrcDiff = width + 7 >> 3;
6911 const white = 0xffffffff;
6912 const black = util_FeatureTest.isLittleEndian ? 0xff000000 : 0x000000ff;
6913 for (i = 0; i < totalChunks; i++) {
6914 thisChunkHeight = i < fullChunks ? FULL_CHUNK_HEIGHT : partialChunkHeight;
6916 for (j = 0; j < thisChunkHeight; j++) {
6917 const srcDiff = srcLength - srcPos;
6919 const kEnd = srcDiff > fullSrcDiff ? width : srcDiff * 8 - 7;
6920 const kEndUnrolled = kEnd & ~7;
6923 for (; k < kEndUnrolled; k += 8) {
6924 srcByte = src[srcPos++];
6925 dest32[destPos++] = srcByte & 128 ? white : black;
6926 dest32[destPos++] = srcByte & 64 ? white : black;
6927 dest32[destPos++] = srcByte & 32 ? white : black;
6928 dest32[destPos++] = srcByte & 16 ? white : black;
6929 dest32[destPos++] = srcByte & 8 ? white : black;
6930 dest32[destPos++] = srcByte & 4 ? white : black;
6931 dest32[destPos++] = srcByte & 2 ? white : black;
6932 dest32[destPos++] = srcByte & 1 ? white : black;
6934 for (; k < kEnd; k++) {
6936 srcByte = src[srcPos++];
6939 dest32[destPos++] = srcByte & mask ? white : black;
6943 while (destPos < dest32DataLength) {
6944 dest32[destPos++] = 0;
6946 ctx.putImageData(chunkImgData, 0, i * FULL_CHUNK_HEIGHT);
6948 } else if (imgData.kind === util_ImageKind.RGBA_32BPP) {
6950 elemsInThisChunk = width * FULL_CHUNK_HEIGHT * 4;
6951 for (i = 0; i < fullChunks; i++) {
6952 dest.set(src.subarray(srcPos, srcPos + elemsInThisChunk));
6953 srcPos += elemsInThisChunk;
6954 ctx.putImageData(chunkImgData, 0, j);
6955 j += FULL_CHUNK_HEIGHT;
6957 if (i < totalChunks) {
6958 elemsInThisChunk = width * partialChunkHeight * 4;
6959 dest.set(src.subarray(srcPos, srcPos + elemsInThisChunk));
6960 ctx.putImageData(chunkImgData, 0, j);
6962 } else if (imgData.kind === util_ImageKind.RGB_24BPP) {
6963 thisChunkHeight = FULL_CHUNK_HEIGHT;
6964 elemsInThisChunk = width * thisChunkHeight;
6965 for (i = 0; i < totalChunks; i++) {
6966 if (i >= fullChunks) {
6967 thisChunkHeight = partialChunkHeight;
6968 elemsInThisChunk = width * thisChunkHeight;
6971 for (j = elemsInThisChunk; j--;) {
6972 dest[destPos++] = src[srcPos++];
6973 dest[destPos++] = src[srcPos++];
6974 dest[destPos++] = src[srcPos++];
6975 dest[destPos++] = 255;
6977 ctx.putImageData(chunkImgData, 0, i * FULL_CHUNK_HEIGHT);
6980 throw new Error(`bad image kind: ${imgData.kind}`);
6983 function putBinaryImageMask(ctx, imgData) {
6984 if (imgData.bitmap) {
6985 ctx.drawImage(imgData.bitmap, 0, 0);
6988 const height = imgData.height,
6989 width = imgData.width;
6990 const partialChunkHeight = height % FULL_CHUNK_HEIGHT;
6991 const fullChunks = (height - partialChunkHeight) / FULL_CHUNK_HEIGHT;
6992 const totalChunks = partialChunkHeight === 0 ? fullChunks : fullChunks + 1;
6993 const chunkImgData = ctx.createImageData(width, FULL_CHUNK_HEIGHT);
6995 const src = imgData.data;
6996 const dest = chunkImgData.data;
6997 for (let i = 0; i < totalChunks; i++) {
6998 const thisChunkHeight = i < fullChunks ? FULL_CHUNK_HEIGHT : partialChunkHeight;
7001 } = convertBlackAndWhiteToRGBA({
7006 height: thisChunkHeight,
7009 ctx.putImageData(chunkImgData, 0, i * FULL_CHUNK_HEIGHT);
7012 function copyCtxState(sourceCtx, destCtx) {
7013 const properties = ["strokeStyle", "fillStyle", "fillRule", "globalAlpha", "lineWidth", "lineCap", "lineJoin", "miterLimit", "globalCompositeOperation", "font", "filter"];
7014 for (const property of properties) {
7015 if (sourceCtx[property] !== undefined) {
7016 destCtx[property] = sourceCtx[property];
7019 if (sourceCtx.setLineDash !== undefined) {
7020 destCtx.setLineDash(sourceCtx.getLineDash());
7021 destCtx.lineDashOffset = sourceCtx.lineDashOffset;
7024 function resetCtxToDefault(ctx) {
7025 ctx.strokeStyle = ctx.fillStyle = "#000000";
7026 ctx.fillRule = "nonzero";
7027 ctx.globalAlpha = 1;
7029 ctx.lineCap = "butt";
7030 ctx.lineJoin = "miter";
7031 ctx.miterLimit = 10;
7032 ctx.globalCompositeOperation = "source-over";
7033 ctx.font = "10px sans-serif";
7034 if (ctx.setLineDash !== undefined) {
7035 ctx.setLineDash([]);
7036 ctx.lineDashOffset = 0;
7041 if (filter !== "none" && filter !== "") {
7042 ctx.filter = "none";
7045 function getImageSmoothingEnabled(transform, interpolate) {
7049 const scale = Util.singularValueDecompose2dScale(transform);
7050 scale[0] = Math.fround(scale[0]);
7051 scale[1] = Math.fround(scale[1]);
7052 const actualScale = Math.fround((globalThis.devicePixelRatio || 1) * PixelsPerInch.PDF_TO_CSS_UNITS);
7053 return scale[0] <= actualScale && scale[1] <= actualScale;
7055 const LINE_CAP_STYLES = ["butt", "round", "square"];
7056 const LINE_JOIN_STYLES = ["miter", "round", "bevel"];
7057 const NORMAL_CLIP = {};
7059 class CanvasGraphics {
7060 constructor(canvasCtx, commonObjs, objs, canvasFactory, filterFactory, {
7061 optionalContentConfig,
7062 markedContentStack = null
7063 }, annotationCanvasMap, pageColors) {
7064 this.ctx = canvasCtx;
7065 this.current = new CanvasExtraState(this.ctx.canvas.width, this.ctx.canvas.height);
7066 this.stateStack = [];
7067 this.pendingClip = null;
7068 this.pendingEOFill = false;
7071 this.commonObjs = commonObjs;
7073 this.canvasFactory = canvasFactory;
7074 this.filterFactory = filterFactory;
7075 this.groupStack = [];
7076 this.processingType3 = null;
7077 this.baseTransform = null;
7078 this.baseTransformStack = [];
7079 this.groupLevel = 0;
7080 this.smaskStack = [];
7081 this.smaskCounter = 0;
7082 this.tempSMask = null;
7083 this.suspendedCtx = null;
7084 this.contentVisible = true;
7085 this.markedContentStack = markedContentStack || [];
7086 this.optionalContentConfig = optionalContentConfig;
7087 this.cachedCanvases = new CachedCanvases(this.canvasFactory);
7088 this.cachedPatterns = new Map();
7089 this.annotationCanvasMap = annotationCanvasMap;
7090 this.viewportScale = 1;
7091 this.outputScaleX = 1;
7092 this.outputScaleY = 1;
7093 this.pageColors = pageColors;
7094 this._cachedScaleForStroking = [-1, 0];
7095 this._cachedGetSinglePixelWidth = null;
7096 this._cachedBitmapsMap = new Map();
7098 getObject(data, fallback = null) {
7099 if (typeof data === "string") {
7100 return data.startsWith("g_") ? this.commonObjs.get(data) : this.objs.get(data);
7107 transparency = false,
7110 const width = this.ctx.canvas.width;
7111 const height = this.ctx.canvas.height;
7112 const savedFillStyle = this.ctx.fillStyle;
7113 this.ctx.fillStyle = background || "#ffffff";
7114 this.ctx.fillRect(0, 0, width, height);
7115 this.ctx.fillStyle = savedFillStyle;
7117 const transparentCanvas = this.cachedCanvases.getCanvas("transparent", width, height);
7118 this.compositeCtx = this.ctx;
7119 this.transparentCanvas = transparentCanvas.canvas;
7120 this.ctx = transparentCanvas.context;
7122 this.ctx.transform(...getCurrentTransform(this.compositeCtx));
7125 resetCtxToDefault(this.ctx);
7127 this.ctx.transform(...transform);
7128 this.outputScaleX = transform[0];
7129 this.outputScaleY = transform[0];
7131 this.ctx.transform(...viewport.transform);
7132 this.viewportScale = viewport.scale;
7133 this.baseTransform = getCurrentTransform(this.ctx);
7135 executeOperatorList(operatorList, executionStartIdx, continueCallback, stepper) {
7136 const argsArray = operatorList.argsArray;
7137 const fnArray = operatorList.fnArray;
7138 let i = executionStartIdx || 0;
7139 const argsArrayLen = argsArray.length;
7140 if (argsArrayLen === i) {
7143 const chunkOperations = argsArrayLen - i > EXECUTION_STEPS && typeof continueCallback === "function";
7144 const endTime = chunkOperations ? Date.now() + EXECUTION_TIME : 0;
7146 const commonObjs = this.commonObjs;
7147 const objs = this.objs;
7150 if (stepper !== undefined && i === stepper.nextBreakPoint) {
7151 stepper.breakIt(i, continueCallback);
7155 if (fnId !== OPS.dependency) {
7156 this[fnId].apply(this, argsArray[i]);
7158 for (const depObjId of argsArray[i]) {
7159 const objsPool = depObjId.startsWith("g_") ? commonObjs : objs;
7160 if (!objsPool.has(depObjId)) {
7161 objsPool.get(depObjId, continueCallback);
7167 if (i === argsArrayLen) {
7170 if (chunkOperations && ++steps > EXECUTION_STEPS) {
7171 if (Date.now() > endTime) {
7179 #restoreInitialState() {
7180 while (this.stateStack.length || this.inSMaskMode) {
7183 this.current.activeSMask = null;
7185 if (this.transparentCanvas) {
7186 this.ctx = this.compositeCtx;
7188 this.ctx.setTransform(1, 0, 0, 1, 0, 0);
7189 this.ctx.drawImage(this.transparentCanvas, 0, 0);
7191 this.transparentCanvas = null;
7195 this.#restoreInitialState();
7196 this.cachedCanvases.clear();
7197 this.cachedPatterns.clear();
7198 for (const cache of this._cachedBitmapsMap.values()) {
7199 for (const canvas of cache.values()) {
7200 if (typeof HTMLCanvasElement !== "undefined" && canvas instanceof HTMLCanvasElement) {
7201 canvas.width = canvas.height = 0;
7206 this._cachedBitmapsMap.clear();
7210 if (this.pageColors) {
7211 const hcmFilterId = this.filterFactory.addHCMFilter(this.pageColors.foreground, this.pageColors.background);
7212 if (hcmFilterId !== "none") {
7213 const savedFilter = this.ctx.filter;
7214 this.ctx.filter = hcmFilterId;
7215 this.ctx.drawImage(this.ctx.canvas, 0, 0);
7216 this.ctx.filter = savedFilter;
7220 _scaleImage(img, inverseTransform) {
7221 const width = img.width ?? img.displayWidth;
7222 const height = img.height ?? img.displayHeight;
7223 let widthScale = Math.max(Math.hypot(inverseTransform[0], inverseTransform[1]), 1);
7224 let heightScale = Math.max(Math.hypot(inverseTransform[2], inverseTransform[3]), 1);
7225 let paintWidth = width,
7226 paintHeight = height;
7227 let tmpCanvasId = "prescale1";
7228 let tmpCanvas, tmpCtx;
7229 while (widthScale > 2 && paintWidth > 1 || heightScale > 2 && paintHeight > 1) {
7230 let newWidth = paintWidth,
7231 newHeight = paintHeight;
7232 if (widthScale > 2 && paintWidth > 1) {
7233 newWidth = paintWidth >= 16384 ? Math.floor(paintWidth / 2) - 1 || 1 : Math.ceil(paintWidth / 2);
7234 widthScale /= paintWidth / newWidth;
7236 if (heightScale > 2 && paintHeight > 1) {
7237 newHeight = paintHeight >= 16384 ? Math.floor(paintHeight / 2) - 1 || 1 : Math.ceil(paintHeight) / 2;
7238 heightScale /= paintHeight / newHeight;
7240 tmpCanvas = this.cachedCanvases.getCanvas(tmpCanvasId, newWidth, newHeight);
7241 tmpCtx = tmpCanvas.context;
7242 tmpCtx.clearRect(0, 0, newWidth, newHeight);
7243 tmpCtx.drawImage(img, 0, 0, paintWidth, paintHeight, 0, 0, newWidth, newHeight);
7244 img = tmpCanvas.canvas;
7245 paintWidth = newWidth;
7246 paintHeight = newHeight;
7247 tmpCanvasId = tmpCanvasId === "prescale1" ? "prescale2" : "prescale1";
7255 _createMaskCanvas(img) {
7256 const ctx = this.ctx;
7261 const fillColor = this.current.fillColor;
7262 const isPatternFill = this.current.patternFill;
7263 const currentTransform = getCurrentTransform(ctx);
7264 let cache, cacheKey, scaled, maskCanvas;
7265 if ((img.bitmap || img.data) && img.count > 1) {
7266 const mainKey = img.bitmap || img.data.buffer;
7267 cacheKey = JSON.stringify(isPatternFill ? currentTransform : [currentTransform.slice(0, 4), fillColor]);
7268 cache = this._cachedBitmapsMap.get(mainKey);
7271 this._cachedBitmapsMap.set(mainKey, cache);
7273 const cachedImage = cache.get(cacheKey);
7274 if (cachedImage && !isPatternFill) {
7275 const offsetX = Math.round(Math.min(currentTransform[0], currentTransform[2]) + currentTransform[4]);
7276 const offsetY = Math.round(Math.min(currentTransform[1], currentTransform[3]) + currentTransform[5]);
7278 canvas: cachedImage,
7283 scaled = cachedImage;
7286 maskCanvas = this.cachedCanvases.getCanvas("maskCanvas", width, height);
7287 putBinaryImageMask(maskCanvas.context, img);
7289 let maskToCanvas = Util.transform(currentTransform, [1 / width, 0, 0, -1 / height, 0, 0]);
7290 maskToCanvas = Util.transform(maskToCanvas, [1, 0, 0, 1, 0, -height]);
7291 const [minX, minY, maxX, maxY] = Util.getAxialAlignedBoundingBox([0, 0, width, height], maskToCanvas);
7292 const drawnWidth = Math.round(maxX - minX) || 1;
7293 const drawnHeight = Math.round(maxY - minY) || 1;
7294 const fillCanvas = this.cachedCanvases.getCanvas("fillCanvas", drawnWidth, drawnHeight);
7295 const fillCtx = fillCanvas.context;
7296 const offsetX = minX;
7297 const offsetY = minY;
7298 fillCtx.translate(-offsetX, -offsetY);
7299 fillCtx.transform(...maskToCanvas);
7301 scaled = this._scaleImage(maskCanvas.canvas, getCurrentTransformInverse(fillCtx));
7302 scaled = scaled.img;
7303 if (cache && isPatternFill) {
7304 cache.set(cacheKey, scaled);
7307 fillCtx.imageSmoothingEnabled = getImageSmoothingEnabled(getCurrentTransform(fillCtx), img.interpolate);
7308 drawImageAtIntegerCoords(fillCtx, scaled, 0, 0, scaled.width, scaled.height, 0, 0, width, height);
7309 fillCtx.globalCompositeOperation = "source-in";
7310 const inverse = Util.transform(getCurrentTransformInverse(fillCtx), [1, 0, 0, 1, -offsetX, -offsetY]);
7311 fillCtx.fillStyle = isPatternFill ? fillColor.getPattern(ctx, this, inverse, PathType.FILL) : fillColor;
7312 fillCtx.fillRect(0, 0, width, height);
7313 if (cache && !isPatternFill) {
7314 this.cachedCanvases.delete("fillCanvas");
7315 cache.set(cacheKey, fillCanvas.canvas);
7318 canvas: fillCanvas.canvas,
7319 offsetX: Math.round(offsetX),
7320 offsetY: Math.round(offsetY)
7323 setLineWidth(width) {
7324 if (width !== this.current.lineWidth) {
7325 this._cachedScaleForStroking[0] = -1;
7327 this.current.lineWidth = width;
7328 this.ctx.lineWidth = width;
7331 this.ctx.lineCap = LINE_CAP_STYLES[style];
7333 setLineJoin(style) {
7334 this.ctx.lineJoin = LINE_JOIN_STYLES[style];
7336 setMiterLimit(limit) {
7337 this.ctx.miterLimit = limit;
7339 setDash(dashArray, dashPhase) {
7340 const ctx = this.ctx;
7341 if (ctx.setLineDash !== undefined) {
7342 ctx.setLineDash(dashArray);
7343 ctx.lineDashOffset = dashPhase;
7346 setRenderingIntent(intent) {}
7347 setFlatness(flatness) {}
7349 for (const [key, value] of states) {
7352 this.setLineWidth(value);
7355 this.setLineCap(value);
7358 this.setLineJoin(value);
7361 this.setMiterLimit(value);
7364 this.setDash(value[0], value[1]);
7367 this.setRenderingIntent(value);
7370 this.setFlatness(value);
7373 this.setFont(value[0], value[1]);
7376 this.current.strokeAlpha = value;
7379 this.current.fillAlpha = value;
7380 this.ctx.globalAlpha = value;
7383 this.ctx.globalCompositeOperation = value;
7386 this.current.activeSMask = value ? this.tempSMask : null;
7387 this.tempSMask = null;
7388 this.checkSMaskState();
7391 this.ctx.filter = this.current.transferMaps = this.filterFactory.addFilter(value);
7397 return !!this.suspendedCtx;
7400 const inSMaskMode = this.inSMaskMode;
7401 if (this.current.activeSMask && !inSMaskMode) {
7402 this.beginSMaskMode();
7403 } else if (!this.current.activeSMask && inSMaskMode) {
7404 this.endSMaskMode();
7408 if (this.inSMaskMode) {
7409 throw new Error("beginSMaskMode called while already in smask mode");
7411 const drawnWidth = this.ctx.canvas.width;
7412 const drawnHeight = this.ctx.canvas.height;
7413 const cacheId = "smaskGroupAt" + this.groupLevel;
7414 const scratchCanvas = this.cachedCanvases.getCanvas(cacheId, drawnWidth, drawnHeight);
7415 this.suspendedCtx = this.ctx;
7416 this.ctx = scratchCanvas.context;
7417 const ctx = this.ctx;
7418 ctx.setTransform(...getCurrentTransform(this.suspendedCtx));
7419 copyCtxState(this.suspendedCtx, ctx);
7420 mirrorContextOperations(ctx, this.suspendedCtx);
7421 this.setGState([["BM", "source-over"], ["ca", 1], ["CA", 1]]);
7424 if (!this.inSMaskMode) {
7425 throw new Error("endSMaskMode called while not in smask mode");
7427 this.ctx._removeMirroring();
7428 copyCtxState(this.ctx, this.suspendedCtx);
7429 this.ctx = this.suspendedCtx;
7430 this.suspendedCtx = null;
7433 if (!this.current.activeSMask) {
7437 dirtyBox = [0, 0, this.ctx.canvas.width, this.ctx.canvas.height];
7439 dirtyBox[0] = Math.floor(dirtyBox[0]);
7440 dirtyBox[1] = Math.floor(dirtyBox[1]);
7441 dirtyBox[2] = Math.ceil(dirtyBox[2]);
7442 dirtyBox[3] = Math.ceil(dirtyBox[3]);
7444 const smask = this.current.activeSMask;
7445 const suspendedCtx = this.suspendedCtx;
7446 this.composeSMask(suspendedCtx, smask, this.ctx, dirtyBox);
7448 this.ctx.setTransform(1, 0, 0, 1, 0, 0);
7449 this.ctx.clearRect(0, 0, this.ctx.canvas.width, this.ctx.canvas.height);
7452 composeSMask(ctx, smask, layerCtx, layerBox) {
7453 const layerOffsetX = layerBox[0];
7454 const layerOffsetY = layerBox[1];
7455 const layerWidth = layerBox[2] - layerOffsetX;
7456 const layerHeight = layerBox[3] - layerOffsetY;
7457 if (layerWidth === 0 || layerHeight === 0) {
7460 this.genericComposeSMask(smask.context, layerCtx, layerWidth, layerHeight, smask.subtype, smask.backdrop, smask.transferMap, layerOffsetX, layerOffsetY, smask.offsetX, smask.offsetY);
7462 ctx.globalAlpha = 1;
7463 ctx.globalCompositeOperation = "source-over";
7464 ctx.setTransform(1, 0, 0, 1, 0, 0);
7465 ctx.drawImage(layerCtx.canvas, 0, 0);
7468 genericComposeSMask(maskCtx, layerCtx, width, height, subtype, backdrop, transferMap, layerOffsetX, layerOffsetY, maskOffsetX, maskOffsetY) {
7469 let maskCanvas = maskCtx.canvas;
7470 let maskX = layerOffsetX - maskOffsetX;
7471 let maskY = layerOffsetY - maskOffsetY;
7473 const backdropRGB = Util.makeHexColor(...backdrop);
7474 if (maskX < 0 || maskY < 0 || maskX + width > maskCanvas.width || maskY + height > maskCanvas.height) {
7475 const canvas = this.cachedCanvases.getCanvas("maskExtension", width, height);
7476 const ctx = canvas.context;
7477 ctx.drawImage(maskCanvas, -maskX, -maskY);
7478 ctx.globalCompositeOperation = "destination-atop";
7479 ctx.fillStyle = backdropRGB;
7480 ctx.fillRect(0, 0, width, height);
7481 ctx.globalCompositeOperation = "source-over";
7482 maskCanvas = canvas.canvas;
7486 maskCtx.globalAlpha = 1;
7487 maskCtx.setTransform(1, 0, 0, 1, 0, 0);
7488 const clip = new Path2D();
7489 clip.rect(maskX, maskY, width, height);
7491 maskCtx.globalCompositeOperation = "destination-atop";
7492 maskCtx.fillStyle = backdropRGB;
7493 maskCtx.fillRect(maskX, maskY, width, height);
7498 layerCtx.globalAlpha = 1;
7499 layerCtx.setTransform(1, 0, 0, 1, 0, 0);
7500 if (subtype === "Alpha" && transferMap) {
7501 layerCtx.filter = this.filterFactory.addAlphaFilter(transferMap);
7502 } else if (subtype === "Luminosity") {
7503 layerCtx.filter = this.filterFactory.addLuminosityFilter(transferMap);
7505 const clip = new Path2D();
7506 clip.rect(layerOffsetX, layerOffsetY, width, height);
7507 layerCtx.clip(clip);
7508 layerCtx.globalCompositeOperation = "destination-in";
7509 layerCtx.drawImage(maskCanvas, maskX, maskY, width, height, layerOffsetX, layerOffsetY, width, height);
7513 if (this.inSMaskMode) {
7514 copyCtxState(this.ctx, this.suspendedCtx);
7515 this.suspendedCtx.save();
7519 const old = this.current;
7520 this.stateStack.push(old);
7521 this.current = old.clone();
7524 if (this.stateStack.length === 0 && this.inSMaskMode) {
7525 this.endSMaskMode();
7527 if (this.stateStack.length !== 0) {
7528 this.current = this.stateStack.pop();
7529 if (this.inSMaskMode) {
7530 this.suspendedCtx.restore();
7531 copyCtxState(this.suspendedCtx, this.ctx);
7535 this.checkSMaskState();
7536 this.pendingClip = null;
7537 this._cachedScaleForStroking[0] = -1;
7538 this._cachedGetSinglePixelWidth = null;
7541 transform(a, b, c, d, e, f) {
7542 this.ctx.transform(a, b, c, d, e, f);
7543 this._cachedScaleForStroking[0] = -1;
7544 this._cachedGetSinglePixelWidth = null;
7546 constructPath(ops, args, minMax) {
7547 const ctx = this.ctx;
7548 const current = this.current;
7552 const currentTransform = getCurrentTransform(ctx);
7553 const isScalingMatrix = currentTransform[0] === 0 && currentTransform[3] === 0 || currentTransform[1] === 0 && currentTransform[2] === 0;
7554 const minMaxForBezier = isScalingMatrix ? minMax.slice(0) : null;
7555 for (let i = 0, j = 0, ii = ops.length; i < ii; i++) {
7556 switch (ops[i] | 0) {
7560 const width = args[j++];
7561 const height = args[j++];
7562 const xw = x + width;
7563 const yh = y + height;
7565 if (width === 0 || height === 0) {
7572 if (!isScalingMatrix) {
7573 current.updateRectMinMax(currentTransform, [x, y, xw, yh]);
7581 if (!isScalingMatrix) {
7582 current.updatePathMinMax(currentTransform, x, y);
7589 if (!isScalingMatrix) {
7590 current.updatePathMinMax(currentTransform, x, y);
7598 ctx.bezierCurveTo(args[j], args[j + 1], args[j + 2], args[j + 3], x, y);
7599 current.updateCurvePathMinMax(currentTransform, startX, startY, args[j], args[j + 1], args[j + 2], args[j + 3], x, y, minMaxForBezier);
7605 ctx.bezierCurveTo(x, y, args[j], args[j + 1], args[j + 2], args[j + 3]);
7606 current.updateCurvePathMinMax(currentTransform, startX, startY, x, y, args[j], args[j + 1], args[j + 2], args[j + 3], minMaxForBezier);
7616 ctx.bezierCurveTo(args[j], args[j + 1], x, y, x, y);
7617 current.updateCurvePathMinMax(currentTransform, startX, startY, args[j], args[j + 1], x, y, x, y, minMaxForBezier);
7625 if (isScalingMatrix) {
7626 current.updateScalingPathMinMax(currentTransform, minMaxForBezier);
7628 current.setCurrentPoint(x, y);
7631 this.ctx.closePath();
7633 stroke(consumePath = true) {
7634 const ctx = this.ctx;
7635 const strokeColor = this.current.strokeColor;
7636 ctx.globalAlpha = this.current.strokeAlpha;
7637 if (this.contentVisible) {
7638 if (typeof strokeColor === "object" && strokeColor?.getPattern) {
7640 ctx.strokeStyle = strokeColor.getPattern(ctx, this, getCurrentTransformInverse(ctx), PathType.STROKE);
7641 this.rescaleAndStroke(false);
7644 this.rescaleAndStroke(true);
7648 this.consumePath(this.current.getClippedPathBoundingBox());
7650 ctx.globalAlpha = this.current.fillAlpha;
7656 fill(consumePath = true) {
7657 const ctx = this.ctx;
7658 const fillColor = this.current.fillColor;
7659 const isPatternFill = this.current.patternFill;
7660 let needRestore = false;
7661 if (isPatternFill) {
7663 ctx.fillStyle = fillColor.getPattern(ctx, this, getCurrentTransformInverse(ctx), PathType.FILL);
7666 const intersect = this.current.getClippedPathBoundingBox();
7667 if (this.contentVisible && intersect !== null) {
7668 if (this.pendingEOFill) {
7669 ctx.fill("evenodd");
7670 this.pendingEOFill = false;
7679 this.consumePath(intersect);
7683 this.pendingEOFill = true;
7692 this.pendingEOFill = true;
7699 closeEOFillStroke() {
7700 this.pendingEOFill = true;
7708 this.pendingClip = NORMAL_CLIP;
7711 this.pendingClip = EO_CLIP;
7714 this.current.textMatrix = IDENTITY_MATRIX;
7715 this.current.textMatrixScale = 1;
7716 this.current.x = this.current.lineX = 0;
7717 this.current.y = this.current.lineY = 0;
7720 const paths = this.pendingTextPaths;
7721 const ctx = this.ctx;
7722 if (paths === undefined) {
7726 const newPath = new Path2D();
7727 const invTransf = ctx.getTransform().invertSelf();
7735 newPath.addPath(path, new DOMMatrix(transform).preMultiplySelf(invTransf).translate(x, y).scale(fontSize, -fontSize));
7739 delete this.pendingTextPaths;
7741 setCharSpacing(spacing) {
7742 this.current.charSpacing = spacing;
7744 setWordSpacing(spacing) {
7745 this.current.wordSpacing = spacing;
7748 this.current.textHScale = scale / 100;
7750 setLeading(leading) {
7751 this.current.leading = -leading;
7753 setFont(fontRefName, size) {
7754 const fontObj = this.commonObjs.get(fontRefName);
7755 const current = this.current;
7757 throw new Error(`Can't find font for ${fontRefName}`);
7759 current.fontMatrix = fontObj.fontMatrix || FONT_IDENTITY_MATRIX;
7760 if (current.fontMatrix[0] === 0 || current.fontMatrix[3] === 0) {
7761 warn("Invalid font matrix for font " + fontRefName);
7765 current.fontDirection = -1;
7767 current.fontDirection = 1;
7769 this.current.font = fontObj;
7770 this.current.fontSize = size;
7771 if (fontObj.isType3Font) {
7774 const name = fontObj.loadedName || "sans-serif";
7775 const typeface = fontObj.systemFontInfo?.css || `"${name}", ${fontObj.fallbackName}`;
7776 let bold = "normal";
7777 if (fontObj.black) {
7779 } else if (fontObj.bold) {
7782 const italic = fontObj.italic ? "italic" : "normal";
7783 let browserFontSize = size;
7784 if (size < MIN_FONT_SIZE) {
7785 browserFontSize = MIN_FONT_SIZE;
7786 } else if (size > MAX_FONT_SIZE) {
7787 browserFontSize = MAX_FONT_SIZE;
7789 this.current.fontSizeScale = size / browserFontSize;
7790 this.ctx.font = `${italic} ${bold} ${browserFontSize}px ${typeface}`;
7792 setTextRenderingMode(mode) {
7793 this.current.textRenderingMode = mode;
7796 this.current.textRise = rise;
7799 this.current.x = this.current.lineX += x;
7800 this.current.y = this.current.lineY += y;
7802 setLeadingMoveText(x, y) {
7803 this.setLeading(-y);
7804 this.moveText(x, y);
7806 setTextMatrix(a, b, c, d, e, f) {
7807 this.current.textMatrix = [a, b, c, d, e, f];
7808 this.current.textMatrixScale = Math.hypot(a, b);
7809 this.current.x = this.current.lineX = 0;
7810 this.current.y = this.current.lineY = 0;
7813 this.moveText(0, this.current.leading);
7815 #getScaledPath(path, currentTransform, transform) {
7816 const newPath = new Path2D();
7817 newPath.addPath(path, new DOMMatrix(transform).invertSelf().multiplySelf(currentTransform));
7820 paintChar(character, x, y, patternFillTransform, patternStrokeTransform) {
7821 const ctx = this.ctx;
7822 const current = this.current;
7823 const font = current.font;
7824 const textRenderingMode = current.textRenderingMode;
7825 const fontSize = current.fontSize / current.fontSizeScale;
7826 const fillStrokeMode = textRenderingMode & TextRenderingMode.FILL_STROKE_MASK;
7827 const isAddToPathSet = !!(textRenderingMode & TextRenderingMode.ADD_TO_PATH_FLAG);
7828 const patternFill = current.patternFill && !font.missingFile;
7829 const patternStroke = current.patternStroke && !font.missingFile;
7831 if (font.disableFontFace || isAddToPathSet || patternFill || patternStroke) {
7832 path = font.getPathGenerator(this.commonObjs, character);
7834 if (font.disableFontFace || patternFill || patternStroke) {
7836 ctx.translate(x, y);
7837 ctx.scale(fontSize, -fontSize);
7838 let currentTransform;
7839 if (fillStrokeMode === TextRenderingMode.FILL || fillStrokeMode === TextRenderingMode.FILL_STROKE) {
7840 if (patternFillTransform) {
7841 currentTransform = ctx.getTransform();
7842 ctx.setTransform(...patternFillTransform);
7843 ctx.fill(this.#getScaledPath(path, currentTransform, patternFillTransform));
7848 if (fillStrokeMode === TextRenderingMode.STROKE || fillStrokeMode === TextRenderingMode.FILL_STROKE) {
7849 if (patternStrokeTransform) {
7850 currentTransform ||= ctx.getTransform();
7851 ctx.setTransform(...patternStrokeTransform);
7857 } = currentTransform;
7858 const invPatternTransform = Util.inverseTransform(patternStrokeTransform);
7859 const transf = Util.transform([a, b, c, d, 0, 0], invPatternTransform);
7860 const [sx, sy] = Util.singularValueDecompose2dScale(transf);
7861 ctx.lineWidth *= Math.max(sx, sy) / fontSize;
7862 ctx.stroke(this.#getScaledPath(path, currentTransform, patternStrokeTransform));
7864 ctx.lineWidth /= fontSize;
7870 if (fillStrokeMode === TextRenderingMode.FILL || fillStrokeMode === TextRenderingMode.FILL_STROKE) {
7871 ctx.fillText(character, x, y);
7873 if (fillStrokeMode === TextRenderingMode.STROKE || fillStrokeMode === TextRenderingMode.FILL_STROKE) {
7874 ctx.strokeText(character, x, y);
7877 if (isAddToPathSet) {
7878 const paths = this.pendingTextPaths ||= [];
7880 transform: getCurrentTransform(ctx),
7888 get isFontSubpixelAAEnabled() {
7891 } = this.cachedCanvases.getCanvas("isFontSubpixelAAEnabled", 10, 10);
7893 ctx.fillText("I", 0, 10);
7894 const data = ctx.getImageData(0, 0, 10, 10).data;
7895 let enabled = false;
7896 for (let i = 3; i < data.length; i += 4) {
7897 if (data[i] > 0 && data[i] < 255) {
7902 return shadow(this, "isFontSubpixelAAEnabled", enabled);
7905 const current = this.current;
7906 const font = current.font;
7907 if (font.isType3Font) {
7908 return this.showType3Text(glyphs);
7910 const fontSize = current.fontSize;
7911 if (fontSize === 0) {
7914 const ctx = this.ctx;
7915 const fontSizeScale = current.fontSizeScale;
7916 const charSpacing = current.charSpacing;
7917 const wordSpacing = current.wordSpacing;
7918 const fontDirection = current.fontDirection;
7919 const textHScale = current.textHScale * fontDirection;
7920 const glyphsLength = glyphs.length;
7921 const vertical = font.vertical;
7922 const spacingDir = vertical ? 1 : -1;
7923 const defaultVMetrics = font.defaultVMetrics;
7924 const widthAdvanceScale = fontSize * current.fontMatrix[0];
7925 const simpleFillText = current.textRenderingMode === TextRenderingMode.FILL && !font.disableFontFace && !current.patternFill;
7927 ctx.transform(...current.textMatrix);
7928 ctx.translate(current.x, current.y + current.textRise);
7929 if (fontDirection > 0) {
7930 ctx.scale(textHScale, -1);
7932 ctx.scale(textHScale, 1);
7934 let patternFillTransform, patternStrokeTransform;
7935 if (current.patternFill) {
7937 const pattern = current.fillColor.getPattern(ctx, this, getCurrentTransformInverse(ctx), PathType.FILL);
7938 patternFillTransform = getCurrentTransform(ctx);
7940 ctx.fillStyle = pattern;
7942 if (current.patternStroke) {
7944 const pattern = current.strokeColor.getPattern(ctx, this, getCurrentTransformInverse(ctx), PathType.STROKE);
7945 patternStrokeTransform = getCurrentTransform(ctx);
7947 ctx.strokeStyle = pattern;
7949 let lineWidth = current.lineWidth;
7950 const scale = current.textMatrixScale;
7951 if (scale === 0 || lineWidth === 0) {
7952 const fillStrokeMode = current.textRenderingMode & TextRenderingMode.FILL_STROKE_MASK;
7953 if (fillStrokeMode === TextRenderingMode.STROKE || fillStrokeMode === TextRenderingMode.FILL_STROKE) {
7954 lineWidth = this.getSinglePixelWidth();
7959 if (fontSizeScale !== 1.0) {
7960 ctx.scale(fontSizeScale, fontSizeScale);
7961 lineWidth /= fontSizeScale;
7963 ctx.lineWidth = lineWidth;
7964 if (font.isInvalidPDFjsFont) {
7967 for (const glyph of glyphs) {
7968 chars.push(glyph.unicode);
7969 width += glyph.width;
7971 ctx.fillText(chars.join(""), 0, 0);
7972 current.x += width * widthAdvanceScale * textHScale;
7979 for (i = 0; i < glyphsLength; ++i) {
7980 const glyph = glyphs[i];
7981 if (typeof glyph === "number") {
7982 x += spacingDir * glyph * fontSize / 1000;
7985 let restoreNeeded = false;
7986 const spacing = (glyph.isSpace ? wordSpacing : 0) + charSpacing;
7987 const character = glyph.fontChar;
7988 const accent = glyph.accent;
7989 let scaledX, scaledY;
7990 let width = glyph.width;
7992 const vmetric = glyph.vmetric || defaultVMetrics;
7993 const vx = -(glyph.vmetric ? vmetric[1] : width * 0.5) * widthAdvanceScale;
7994 const vy = vmetric[2] * widthAdvanceScale;
7995 width = vmetric ? -vmetric[0] : width;
7996 scaledX = vx / fontSizeScale;
7997 scaledY = (x + vy) / fontSizeScale;
7999 scaledX = x / fontSizeScale;
8002 if (font.remeasure && width > 0) {
8003 const measuredWidth = ctx.measureText(character).width * 1000 / fontSize * fontSizeScale;
8004 if (width < measuredWidth && this.isFontSubpixelAAEnabled) {
8005 const characterScaleX = width / measuredWidth;
8006 restoreNeeded = true;
8008 ctx.scale(characterScaleX, 1);
8009 scaledX /= characterScaleX;
8010 } else if (width !== measuredWidth) {
8011 scaledX += (width - measuredWidth) / 2000 * fontSize / fontSizeScale;
8014 if (this.contentVisible && (glyph.isInFont || font.missingFile)) {
8015 if (simpleFillText && !accent) {
8016 ctx.fillText(character, scaledX, scaledY);
8018 this.paintChar(character, scaledX, scaledY, patternFillTransform, patternStrokeTransform);
8020 const scaledAccentX = scaledX + fontSize * accent.offset.x / fontSizeScale;
8021 const scaledAccentY = scaledY - fontSize * accent.offset.y / fontSizeScale;
8022 this.paintChar(accent.fontChar, scaledAccentX, scaledAccentY, patternFillTransform, patternStrokeTransform);
8026 const charWidth = vertical ? width * widthAdvanceScale - spacing * fontDirection : width * widthAdvanceScale + spacing * fontDirection;
8028 if (restoreNeeded) {
8035 current.x += x * textHScale;
8041 showType3Text(glyphs) {
8042 const ctx = this.ctx;
8043 const current = this.current;
8044 const font = current.font;
8045 const fontSize = current.fontSize;
8046 const fontDirection = current.fontDirection;
8047 const spacingDir = font.vertical ? 1 : -1;
8048 const charSpacing = current.charSpacing;
8049 const wordSpacing = current.wordSpacing;
8050 const textHScale = current.textHScale * fontDirection;
8051 const fontMatrix = current.fontMatrix || FONT_IDENTITY_MATRIX;
8052 const glyphsLength = glyphs.length;
8053 const isTextInvisible = current.textRenderingMode === TextRenderingMode.INVISIBLE;
8054 let i, glyph, width, spacingLength;
8055 if (isTextInvisible || fontSize === 0) {
8058 this._cachedScaleForStroking[0] = -1;
8059 this._cachedGetSinglePixelWidth = null;
8061 ctx.transform(...current.textMatrix);
8062 ctx.translate(current.x, current.y);
8063 ctx.scale(textHScale, fontDirection);
8064 for (i = 0; i < glyphsLength; ++i) {
8066 if (typeof glyph === "number") {
8067 spacingLength = spacingDir * glyph * fontSize / 1000;
8068 this.ctx.translate(spacingLength, 0);
8069 current.x += spacingLength * textHScale;
8072 const spacing = (glyph.isSpace ? wordSpacing : 0) + charSpacing;
8073 const operatorList = font.charProcOperatorList[glyph.operatorListId];
8074 if (!operatorList) {
8075 warn(`Type3 character "${glyph.operatorListId}" is not available.`);
8078 if (this.contentVisible) {
8079 this.processingType3 = glyph;
8081 ctx.scale(fontSize, fontSize);
8082 ctx.transform(...fontMatrix);
8083 this.executeOperatorList(operatorList);
8086 const transformed = Util.applyTransform([glyph.width, 0], fontMatrix);
8087 width = transformed[0] * fontSize + spacing;
8088 ctx.translate(width, 0);
8089 current.x += width * textHScale;
8092 this.processingType3 = null;
8094 setCharWidth(xWidth, yWidth) {}
8095 setCharWidthAndBounds(xWidth, yWidth, llx, lly, urx, ury) {
8096 this.ctx.rect(llx, lly, urx - llx, ury - lly);
8100 getColorN_Pattern(IR) {
8102 if (IR[0] === "TilingPattern") {
8103 const color = IR[1];
8104 const baseTransform = this.baseTransform || getCurrentTransform(this.ctx);
8105 const canvasGraphicsFactory = {
8106 createCanvasGraphics: ctx => new CanvasGraphics(ctx, this.commonObjs, this.objs, this.canvasFactory, this.filterFactory, {
8107 optionalContentConfig: this.optionalContentConfig,
8108 markedContentStack: this.markedContentStack
8111 pattern = new TilingPattern(IR, color, this.ctx, canvasGraphicsFactory, baseTransform);
8113 pattern = this._getPattern(IR[1], IR[2]);
8118 this.current.strokeColor = this.getColorN_Pattern(arguments);
8119 this.current.patternStroke = true;
8122 this.current.fillColor = this.getColorN_Pattern(arguments);
8123 this.current.patternFill = true;
8125 setStrokeRGBColor(r, g, b) {
8126 this.ctx.strokeStyle = this.current.strokeColor = Util.makeHexColor(r, g, b);
8127 this.current.patternStroke = false;
8129 setStrokeTransparent() {
8130 this.ctx.strokeStyle = this.current.strokeColor = "transparent";
8131 this.current.patternStroke = false;
8133 setFillRGBColor(r, g, b) {
8134 this.ctx.fillStyle = this.current.fillColor = Util.makeHexColor(r, g, b);
8135 this.current.patternFill = false;
8137 setFillTransparent() {
8138 this.ctx.fillStyle = this.current.fillColor = "transparent";
8139 this.current.patternFill = false;
8141 _getPattern(objId, matrix = null) {
8143 if (this.cachedPatterns.has(objId)) {
8144 pattern = this.cachedPatterns.get(objId);
8146 pattern = getShadingPattern(this.getObject(objId));
8147 this.cachedPatterns.set(objId, pattern);
8150 pattern.matrix = matrix;
8154 shadingFill(objId) {
8155 if (!this.contentVisible) {
8158 const ctx = this.ctx;
8160 const pattern = this._getPattern(objId);
8161 ctx.fillStyle = pattern.getPattern(ctx, this, getCurrentTransformInverse(ctx), PathType.SHADING);
8162 const inv = getCurrentTransformInverse(ctx);
8168 const [x0, y0, x1, y1] = Util.getAxialAlignedBoundingBox([0, 0, width, height], inv);
8169 this.ctx.fillRect(x0, y0, x1 - x0, y1 - y0);
8171 this.ctx.fillRect(-1e10, -1e10, 2e10, 2e10);
8173 this.compose(this.current.getClippedPathBoundingBox());
8176 beginInlineImage() {
8177 unreachable("Should not call beginInlineImage");
8180 unreachable("Should not call beginImageData");
8182 paintFormXObjectBegin(matrix, bbox) {
8183 if (!this.contentVisible) {
8187 this.baseTransformStack.push(this.baseTransform);
8189 this.transform(...matrix);
8191 this.baseTransform = getCurrentTransform(this.ctx);
8193 const width = bbox[2] - bbox[0];
8194 const height = bbox[3] - bbox[1];
8195 this.ctx.rect(bbox[0], bbox[1], width, height);
8196 this.current.updateRectMinMax(getCurrentTransform(this.ctx), bbox);
8201 paintFormXObjectEnd() {
8202 if (!this.contentVisible) {
8206 this.baseTransform = this.baseTransformStack.pop();
8209 if (!this.contentVisible) {
8213 if (this.inSMaskMode) {
8214 this.endSMaskMode();
8215 this.current.activeSMask = null;
8217 const currentCtx = this.ctx;
8218 if (!group.isolated) {
8219 info("TODO: Support non-isolated groups.");
8221 if (group.knockout) {
8222 warn("Knockout groups not supported.");
8224 const currentTransform = getCurrentTransform(currentCtx);
8226 currentCtx.transform(...group.matrix);
8229 throw new Error("Bounding box is required.");
8231 let bounds = Util.getAxialAlignedBoundingBox(group.bbox, getCurrentTransform(currentCtx));
8232 const canvasBounds = [0, 0, currentCtx.canvas.width, currentCtx.canvas.height];
8233 bounds = Util.intersect(bounds, canvasBounds) || [0, 0, 0, 0];
8234 const offsetX = Math.floor(bounds[0]);
8235 const offsetY = Math.floor(bounds[1]);
8236 const drawnWidth = Math.max(Math.ceil(bounds[2]) - offsetX, 1);
8237 const drawnHeight = Math.max(Math.ceil(bounds[3]) - offsetY, 1);
8238 this.current.startNewPathAndClipBox([0, 0, drawnWidth, drawnHeight]);
8239 let cacheId = "groupAt" + this.groupLevel;
8241 cacheId += "_smask_" + this.smaskCounter++ % 2;
8243 const scratchCanvas = this.cachedCanvases.getCanvas(cacheId, drawnWidth, drawnHeight);
8244 const groupCtx = scratchCanvas.context;
8245 groupCtx.translate(-offsetX, -offsetY);
8246 groupCtx.transform(...currentTransform);
8248 this.smaskStack.push({
8249 canvas: scratchCanvas.canvas,
8253 subtype: group.smask.subtype,
8254 backdrop: group.smask.backdrop,
8255 transferMap: group.smask.transferMap || null,
8256 startTransformInverse: null
8259 currentCtx.setTransform(1, 0, 0, 1, 0, 0);
8260 currentCtx.translate(offsetX, offsetY);
8263 copyCtxState(currentCtx, groupCtx);
8264 this.ctx = groupCtx;
8265 this.setGState([["BM", "source-over"], ["ca", 1], ["CA", 1]]);
8266 this.groupStack.push(currentCtx);
8270 if (!this.contentVisible) {
8274 const groupCtx = this.ctx;
8275 const ctx = this.groupStack.pop();
8277 this.ctx.imageSmoothingEnabled = false;
8279 this.tempSMask = this.smaskStack.pop();
8283 const currentMtx = getCurrentTransform(this.ctx);
8286 this.ctx.setTransform(...currentMtx);
8287 const dirtyBox = Util.getAxialAlignedBoundingBox([0, 0, groupCtx.canvas.width, groupCtx.canvas.height], currentMtx);
8288 this.ctx.drawImage(groupCtx.canvas, 0, 0);
8290 this.compose(dirtyBox);
8293 beginAnnotation(id, rect, transform, matrix, hasOwnCanvas) {
8294 this.#restoreInitialState();
8295 resetCtxToDefault(this.ctx);
8298 if (this.baseTransform) {
8299 this.ctx.setTransform(...this.baseTransform);
8302 const width = rect[2] - rect[0];
8303 const height = rect[3] - rect[1];
8304 if (hasOwnCanvas && this.annotationCanvasMap) {
8305 transform = transform.slice();
8306 transform[4] -= rect[0];
8307 transform[5] -= rect[1];
8308 rect = rect.slice();
8309 rect[0] = rect[1] = 0;
8312 const [scaleX, scaleY] = Util.singularValueDecompose2dScale(getCurrentTransform(this.ctx));
8316 const canvasWidth = Math.ceil(width * this.outputScaleX * viewportScale);
8317 const canvasHeight = Math.ceil(height * this.outputScaleY * viewportScale);
8318 this.annotationCanvas = this.canvasFactory.create(canvasWidth, canvasHeight);
8322 } = this.annotationCanvas;
8323 this.annotationCanvasMap.set(id, canvas);
8324 this.annotationCanvas.savedCtx = this.ctx;
8327 this.ctx.setTransform(scaleX, 0, 0, -scaleY, 0, height * scaleY);
8328 resetCtxToDefault(this.ctx);
8330 resetCtxToDefault(this.ctx);
8332 this.ctx.rect(rect[0], rect[1], width, height);
8334 this.ctx.beginPath();
8337 this.current = new CanvasExtraState(this.ctx.canvas.width, this.ctx.canvas.height);
8338 this.transform(...transform);
8339 this.transform(...matrix);
8342 if (this.annotationCanvas) {
8345 this.ctx = this.annotationCanvas.savedCtx;
8346 delete this.annotationCanvas.savedCtx;
8347 delete this.annotationCanvas;
8350 paintImageMaskXObject(img) {
8351 if (!this.contentVisible) {
8354 const count = img.count;
8355 img = this.getObject(img.data, img);
8357 const ctx = this.ctx;
8358 const glyph = this.processingType3;
8360 if (glyph.compiled === undefined) {
8361 glyph.compiled = compileType3Glyph(img);
8363 if (glyph.compiled) {
8364 glyph.compiled(ctx);
8368 const mask = this._createMaskCanvas(img);
8369 const maskCanvas = mask.canvas;
8371 ctx.setTransform(1, 0, 0, 1, 0, 0);
8372 ctx.drawImage(maskCanvas, mask.offsetX, mask.offsetY);
8376 paintImageMaskXObjectRepeat(img, scaleX, skewX = 0, skewY = 0, scaleY, positions) {
8377 if (!this.contentVisible) {
8380 img = this.getObject(img.data, img);
8381 const ctx = this.ctx;
8383 const currentTransform = getCurrentTransform(ctx);
8384 ctx.transform(scaleX, skewX, skewY, scaleY, 0, 0);
8385 const mask = this._createMaskCanvas(img);
8386 ctx.setTransform(1, 0, 0, 1, mask.offsetX - currentTransform[4], mask.offsetY - currentTransform[5]);
8387 for (let i = 0, ii = positions.length; i < ii; i += 2) {
8388 const trans = Util.transform(currentTransform, [scaleX, skewX, skewY, scaleY, positions[i], positions[i + 1]]);
8389 const [x, y] = Util.applyTransform([0, 0], trans);
8390 ctx.drawImage(mask.canvas, x, y);
8395 paintImageMaskXObjectGroup(images) {
8396 if (!this.contentVisible) {
8399 const ctx = this.ctx;
8400 const fillColor = this.current.fillColor;
8401 const isPatternFill = this.current.patternFill;
8402 for (const image of images) {
8409 const maskCanvas = this.cachedCanvases.getCanvas("maskCanvas", width, height);
8410 const maskCtx = maskCanvas.context;
8412 const img = this.getObject(data, image);
8413 putBinaryImageMask(maskCtx, img);
8414 maskCtx.globalCompositeOperation = "source-in";
8415 maskCtx.fillStyle = isPatternFill ? fillColor.getPattern(maskCtx, this, getCurrentTransformInverse(ctx), PathType.FILL) : fillColor;
8416 maskCtx.fillRect(0, 0, width, height);
8419 ctx.transform(...transform);
8421 drawImageAtIntegerCoords(ctx, maskCanvas.canvas, 0, 0, width, height, 0, -1, 1, 1);
8426 paintImageXObject(objId) {
8427 if (!this.contentVisible) {
8430 const imgData = this.getObject(objId);
8432 warn("Dependent image isn't ready yet");
8435 this.paintInlineImageXObject(imgData);
8437 paintImageXObjectRepeat(objId, scaleX, scaleY, positions) {
8438 if (!this.contentVisible) {
8441 const imgData = this.getObject(objId);
8443 warn("Dependent image isn't ready yet");
8446 const width = imgData.width;
8447 const height = imgData.height;
8449 for (let i = 0, ii = positions.length; i < ii; i += 2) {
8451 transform: [scaleX, 0, 0, scaleY, positions[i], positions[i + 1]],
8458 this.paintInlineImageXObjectGroup(imgData, map);
8460 applyTransferMapsToCanvas(ctx) {
8461 if (this.current.transferMaps !== "none") {
8462 ctx.filter = this.current.transferMaps;
8463 ctx.drawImage(ctx.canvas, 0, 0);
8464 ctx.filter = "none";
8468 applyTransferMapsToBitmap(imgData) {
8469 if (this.current.transferMaps === "none") {
8470 return imgData.bitmap;
8477 const tmpCanvas = this.cachedCanvases.getCanvas("inlineImage", width, height);
8478 const tmpCtx = tmpCanvas.context;
8479 tmpCtx.filter = this.current.transferMaps;
8480 tmpCtx.drawImage(bitmap, 0, 0);
8481 tmpCtx.filter = "none";
8482 return tmpCanvas.canvas;
8484 paintInlineImageXObject(imgData) {
8485 if (!this.contentVisible) {
8488 const width = imgData.width;
8489 const height = imgData.height;
8490 const ctx = this.ctx;
8495 if (filter !== "none" && filter !== "") {
8496 ctx.filter = "none";
8498 ctx.scale(1 / width, -1 / height);
8500 if (imgData.bitmap) {
8501 imgToPaint = this.applyTransferMapsToBitmap(imgData);
8502 } else if (typeof HTMLElement === "function" && imgData instanceof HTMLElement || !imgData.data) {
8503 imgToPaint = imgData;
8505 const tmpCanvas = this.cachedCanvases.getCanvas("inlineImage", width, height);
8506 const tmpCtx = tmpCanvas.context;
8507 putBinaryImageData(tmpCtx, imgData);
8508 imgToPaint = this.applyTransferMapsToCanvas(tmpCtx);
8510 const scaled = this._scaleImage(imgToPaint, getCurrentTransformInverse(ctx));
8511 ctx.imageSmoothingEnabled = getImageSmoothingEnabled(getCurrentTransform(ctx), imgData.interpolate);
8512 drawImageAtIntegerCoords(ctx, scaled.img, 0, 0, scaled.paintWidth, scaled.paintHeight, 0, -height, width, height);
8516 paintInlineImageXObjectGroup(imgData, map) {
8517 if (!this.contentVisible) {
8520 const ctx = this.ctx;
8522 if (imgData.bitmap) {
8523 imgToPaint = imgData.bitmap;
8525 const w = imgData.width;
8526 const h = imgData.height;
8527 const tmpCanvas = this.cachedCanvases.getCanvas("inlineImage", w, h);
8528 const tmpCtx = tmpCanvas.context;
8529 putBinaryImageData(tmpCtx, imgData);
8530 imgToPaint = this.applyTransferMapsToCanvas(tmpCtx);
8532 for (const entry of map) {
8534 ctx.transform(...entry.transform);
8536 drawImageAtIntegerCoords(ctx, imgToPaint, entry.x, entry.y, entry.w, entry.h, 0, -1, 1, 1);
8541 paintSolidColorImageMask() {
8542 if (!this.contentVisible) {
8545 this.ctx.fillRect(0, 0, 1, 1);
8549 markPointProps(tag, properties) {}
8550 beginMarkedContent(tag) {
8551 this.markedContentStack.push({
8555 beginMarkedContentProps(tag, properties) {
8557 this.markedContentStack.push({
8558 visible: this.optionalContentConfig.isVisible(properties)
8561 this.markedContentStack.push({
8565 this.contentVisible = this.isContentVisible();
8567 endMarkedContent() {
8568 this.markedContentStack.pop();
8569 this.contentVisible = this.isContentVisible();
8573 consumePath(clipBox) {
8574 const isEmpty = this.current.isEmptyClip();
8575 if (this.pendingClip) {
8576 this.current.updateClipFromPath();
8578 if (!this.pendingClip) {
8579 this.compose(clipBox);
8581 const ctx = this.ctx;
8582 if (this.pendingClip) {
8584 if (this.pendingClip === EO_CLIP) {
8585 ctx.clip("evenodd");
8590 this.pendingClip = null;
8592 this.current.startNewPathAndClipBox(this.current.clipBox);
8595 getSinglePixelWidth() {
8596 if (!this._cachedGetSinglePixelWidth) {
8597 const m = getCurrentTransform(this.ctx);
8598 if (m[1] === 0 && m[2] === 0) {
8599 this._cachedGetSinglePixelWidth = 1 / Math.min(Math.abs(m[0]), Math.abs(m[3]));
8601 const absDet = Math.abs(m[0] * m[3] - m[2] * m[1]);
8602 const normX = Math.hypot(m[0], m[2]);
8603 const normY = Math.hypot(m[1], m[3]);
8604 this._cachedGetSinglePixelWidth = Math.max(normX, normY) / absDet;
8607 return this._cachedGetSinglePixelWidth;
8609 getScaleForStroking() {
8610 if (this._cachedScaleForStroking[0] === -1) {
8619 } = this.ctx.getTransform();
8621 if (b === 0 && c === 0) {
8622 const normX = Math.abs(a);
8623 const normY = Math.abs(d);
8624 if (normX === normY) {
8625 if (lineWidth === 0) {
8626 scaleX = scaleY = 1 / normX;
8628 const scaledLineWidth = normX * lineWidth;
8629 scaleX = scaleY = scaledLineWidth < 1 ? 1 / scaledLineWidth : 1;
8631 } else if (lineWidth === 0) {
8635 const scaledXLineWidth = normX * lineWidth;
8636 const scaledYLineWidth = normY * lineWidth;
8637 scaleX = scaledXLineWidth < 1 ? 1 / scaledXLineWidth : 1;
8638 scaleY = scaledYLineWidth < 1 ? 1 / scaledYLineWidth : 1;
8641 const absDet = Math.abs(a * d - b * c);
8642 const normX = Math.hypot(a, b);
8643 const normY = Math.hypot(c, d);
8644 if (lineWidth === 0) {
8645 scaleX = normY / absDet;
8646 scaleY = normX / absDet;
8648 const baseArea = lineWidth * absDet;
8649 scaleX = normY > baseArea ? normY / baseArea : 1;
8650 scaleY = normX > baseArea ? normX / baseArea : 1;
8653 this._cachedScaleForStroking[0] = scaleX;
8654 this._cachedScaleForStroking[1] = scaleY;
8656 return this._cachedScaleForStroking;
8658 rescaleAndStroke(saveRestore) {
8665 const [scaleX, scaleY] = this.getScaleForStroking();
8666 ctx.lineWidth = lineWidth || 1;
8667 if (scaleX === 1 && scaleY === 1) {
8671 const dashes = ctx.getLineDash();
8675 ctx.scale(scaleX, scaleY);
8676 if (dashes.length > 0) {
8677 const scale = Math.max(scaleX, scaleY);
8678 ctx.setLineDash(dashes.map(x => x / scale));
8679 ctx.lineDashOffset /= scale;
8686 isContentVisible() {
8687 for (let i = this.markedContentStack.length - 1; i >= 0; i--) {
8688 if (!this.markedContentStack[i].visible) {
8695 for (const op in OPS) {
8696 if (CanvasGraphics.prototype[op] !== undefined) {
8697 CanvasGraphics.prototype[OPS[op]] = CanvasGraphics.prototype[op];
8701 ;// ./src/display/canvas_factory.js
8703 class BaseCanvasFactory {
8708 this.#enableHWA = enableHWA;
8710 create(width, height) {
8711 if (width <= 0 || height <= 0) {
8712 throw new Error("Invalid canvas size");
8714 const canvas = this._createCanvas(width, height);
8717 context: canvas.getContext("2d", {
8718 willReadFrequently: !this.#enableHWA
8722 reset(canvasAndContext, width, height) {
8723 if (!canvasAndContext.canvas) {
8724 throw new Error("Canvas is not specified");
8726 if (width <= 0 || height <= 0) {
8727 throw new Error("Invalid canvas size");
8729 canvasAndContext.canvas.width = width;
8730 canvasAndContext.canvas.height = height;
8732 destroy(canvasAndContext) {
8733 if (!canvasAndContext.canvas) {
8734 throw new Error("Canvas is not specified");
8736 canvasAndContext.canvas.width = 0;
8737 canvasAndContext.canvas.height = 0;
8738 canvasAndContext.canvas = null;
8739 canvasAndContext.context = null;
8741 _createCanvas(width, height) {
8742 unreachable("Abstract method `_createCanvas` called.");
8745 class DOMCanvasFactory extends BaseCanvasFactory {
8747 ownerDocument = globalThis.document,
8753 this._document = ownerDocument;
8755 _createCanvas(width, height) {
8756 const canvas = this._document.createElement("canvas");
8757 canvas.width = width;
8758 canvas.height = height;
8763 ;// ./src/display/stubs.js
8764 const DOMCMapReaderFactory = null;
8765 const DOMWasmFactory = null;
8766 const DOMStandardFontDataFactory = null;
8767 const NodeCanvasFactory = null;
8768 const NodeCMapReaderFactory = null;
8769 const NodeFilterFactory = null;
8770 const NodeWasmFactory = null;
8771 const NodeStandardFontDataFactory = null;
8772 const PDFFetchStream = null;
8773 const PDFNetworkStream = null;
8774 const PDFNodeStream = null;
8776 ;// ./src/display/filter_factory.js
8779 class BaseFilterFactory {
8783 addHCMFilter(fgColor, bgColor) {
8786 addAlphaFilter(map) {
8789 addLuminosityFilter(map) {
8792 addHighlightHCMFilter(filterName, fgColor, bgColor, newFgColor, newBgColor) {
8795 destroy(keepHCM = false) {}
8797 class DOMFilterFactory extends BaseFilterFactory {
8807 ownerDocument = globalThis.document
8810 this.#docId = docId;
8811 this.#document = ownerDocument;
8814 return this.#_cache ||= new Map();
8817 return this.#_hcmCache ||= new Map();
8821 const div = this.#document.createElement("div");
8825 style.visibility = "hidden";
8826 style.contain = "strict";
8827 style.width = style.height = 0;
8828 style.position = "absolute";
8829 style.top = style.left = 0;
8831 const svg = this.#document.createElementNS(SVG_NS, "svg");
8832 svg.setAttribute("width", 0);
8833 svg.setAttribute("height", 0);
8834 this.#_defs = this.#document.createElementNS(SVG_NS, "defs");
8836 svg.append(this.#_defs);
8837 this.#document.body.append(div);
8841 #createTables(maps) {
8842 if (maps.length === 1) {
8843 const mapR = maps[0];
8844 const buffer = new Array(256);
8845 for (let i = 0; i < 256; i++) {
8846 buffer[i] = mapR[i] / 255;
8848 const table = buffer.join(",");
8849 return [table, table, table];
8851 const [mapR, mapG, mapB] = maps;
8852 const bufferR = new Array(256);
8853 const bufferG = new Array(256);
8854 const bufferB = new Array(256);
8855 for (let i = 0; i < 256; i++) {
8856 bufferR[i] = mapR[i] / 255;
8857 bufferG[i] = mapG[i] / 255;
8858 bufferB[i] = mapB[i] / 255;
8860 return [bufferR.join(","), bufferG.join(","), bufferB.join(",")];
8863 if (this.#baseUrl === undefined) {
8865 const url = this.#document.URL;
8866 if (url !== this.#document.baseURI) {
8867 if (isDataScheme(url)) {
8868 warn('#createUrl: ignore "data:"-URL for performance reasons.');
8870 this.#baseUrl = url.split("#", 1)[0];
8874 return `url(${this.#baseUrl}#${id})`;
8880 let value = this.#cache.get(maps);
8884 const [tableR, tableG, tableB] = this.#createTables(maps);
8885 const key = maps.length === 1 ? tableR : `${tableR}${tableG}${tableB}`;
8886 value = this.#cache.get(key);
8888 this.#cache.set(maps, value);
8891 const id = `g_${this.#docId}_transfer_map_${this.#id++}`;
8892 const url = this.#createUrl(id);
8893 this.#cache.set(maps, url);
8894 this.#cache.set(key, url);
8895 const filter = this.#createFilter(id);
8896 this.#addTransferMapConversion(tableR, tableG, tableB, filter);
8899 addHCMFilter(fgColor, bgColor) {
8900 const key = `${fgColor}-${bgColor}`;
8901 const filterName = "base";
8902 let info = this.#hcmCache.get(filterName);
8903 if (info?.key === key) {
8907 info.filter?.remove();
8917 this.#hcmCache.set(filterName, info);
8919 if (!fgColor || !bgColor) {
8922 const fgRGB = this.#getRGB(fgColor);
8923 fgColor = Util.makeHexColor(...fgRGB);
8924 const bgRGB = this.#getRGB(bgColor);
8925 bgColor = Util.makeHexColor(...bgRGB);
8926 this.#defs.style.color = "";
8927 if (fgColor === "#000000" && bgColor === "#ffffff" || fgColor === bgColor) {
8930 const map = new Array(256);
8931 for (let i = 0; i <= 255; i++) {
8933 map[i] = x <= 0.03928 ? x / 12.92 : ((x + 0.055) / 1.055) ** 2.4;
8935 const table = map.join(",");
8936 const id = `g_${this.#docId}_hcm_filter`;
8937 const filter = info.filter = this.#createFilter(id);
8938 this.#addTransferMapConversion(table, table, table, filter);
8939 this.#addGrayConversion(filter);
8940 const getSteps = (c, n) => {
8941 const start = fgRGB[c] / 255;
8942 const end = bgRGB[c] / 255;
8943 const arr = new Array(n + 1);
8944 for (let i = 0; i <= n; i++) {
8945 arr[i] = start + i / n * (end - start);
8947 return arr.join(",");
8949 this.#addTransferMapConversion(getSteps(0, 5), getSteps(1, 5), getSteps(2, 5), filter);
8950 info.url = this.#createUrl(id);
8953 addAlphaFilter(map) {
8954 let value = this.#cache.get(map);
8958 const [tableA] = this.#createTables([map]);
8959 const key = `alpha_${tableA}`;
8960 value = this.#cache.get(key);
8962 this.#cache.set(map, value);
8965 const id = `g_${this.#docId}_alpha_map_${this.#id++}`;
8966 const url = this.#createUrl(id);
8967 this.#cache.set(map, url);
8968 this.#cache.set(key, url);
8969 const filter = this.#createFilter(id);
8970 this.#addTransferMapAlphaConversion(tableA, filter);
8973 addLuminosityFilter(map) {
8974 let value = this.#cache.get(map || "luminosity");
8980 [tableA] = this.#createTables([map]);
8981 key = `luminosity_${tableA}`;
8985 value = this.#cache.get(key);
8987 this.#cache.set(map, value);
8990 const id = `g_${this.#docId}_luminosity_map_${this.#id++}`;
8991 const url = this.#createUrl(id);
8992 this.#cache.set(map, url);
8993 this.#cache.set(key, url);
8994 const filter = this.#createFilter(id);
8995 this.#addLuminosityConversion(filter);
8997 this.#addTransferMapAlphaConversion(tableA, filter);
9001 addHighlightHCMFilter(filterName, fgColor, bgColor, newFgColor, newBgColor) {
9002 const key = `${fgColor}-${bgColor}-${newFgColor}-${newBgColor}`;
9003 let info = this.#hcmCache.get(filterName);
9004 if (info?.key === key) {
9008 info.filter?.remove();
9018 this.#hcmCache.set(filterName, info);
9020 if (!fgColor || !bgColor) {
9023 const [fgRGB, bgRGB] = [fgColor, bgColor].map(this.#getRGB.bind(this));
9024 let fgGray = Math.round(0.2126 * fgRGB[0] + 0.7152 * fgRGB[1] + 0.0722 * fgRGB[2]);
9025 let bgGray = Math.round(0.2126 * bgRGB[0] + 0.7152 * bgRGB[1] + 0.0722 * bgRGB[2]);
9026 let [newFgRGB, newBgRGB] = [newFgColor, newBgColor].map(this.#getRGB.bind(this));
9027 if (bgGray < fgGray) {
9028 [fgGray, bgGray, newFgRGB, newBgRGB] = [bgGray, fgGray, newBgRGB, newFgRGB];
9030 this.#defs.style.color = "";
9031 const getSteps = (fg, bg, n) => {
9032 const arr = new Array(256);
9033 const step = (bgGray - fgGray) / n;
9034 const newStart = fg / 255;
9035 const newStep = (bg - fg) / (255 * n);
9037 for (let i = 0; i <= n; i++) {
9038 const k = Math.round(fgGray + i * step);
9039 const value = newStart + i * newStep;
9040 for (let j = prev; j <= k; j++) {
9045 for (let i = prev; i < 256; i++) {
9046 arr[i] = arr[prev - 1];
9048 return arr.join(",");
9050 const id = `g_${this.#docId}_hcm_${filterName}_filter`;
9051 const filter = info.filter = this.#createFilter(id);
9052 this.#addGrayConversion(filter);
9053 this.#addTransferMapConversion(getSteps(newFgRGB[0], newBgRGB[0], 5), getSteps(newFgRGB[1], newBgRGB[1], 5), getSteps(newFgRGB[2], newBgRGB[2], 5), filter);
9054 info.url = this.#createUrl(id);
9057 destroy(keepHCM = false) {
9058 if (keepHCM && this.#_hcmCache?.size) {
9061 this.#_defs?.parentNode.parentNode.remove();
9063 this.#_cache?.clear();
9064 this.#_cache = null;
9065 this.#_hcmCache?.clear();
9066 this.#_hcmCache = null;
9069 #addLuminosityConversion(filter) {
9070 const feColorMatrix = this.#document.createElementNS(SVG_NS, "feColorMatrix");
9071 feColorMatrix.setAttribute("type", "matrix");
9072 feColorMatrix.setAttribute("values", "0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0.3 0.59 0.11 0 0");
9073 filter.append(feColorMatrix);
9075 #addGrayConversion(filter) {
9076 const feColorMatrix = this.#document.createElementNS(SVG_NS, "feColorMatrix");
9077 feColorMatrix.setAttribute("type", "matrix");
9078 feColorMatrix.setAttribute("values", "0.2126 0.7152 0.0722 0 0 0.2126 0.7152 0.0722 0 0 0.2126 0.7152 0.0722 0 0 0 0 0 1 0");
9079 filter.append(feColorMatrix);
9082 const filter = this.#document.createElementNS(SVG_NS, "filter");
9083 filter.setAttribute("color-interpolation-filters", "sRGB");
9084 filter.setAttribute("id", id);
9085 this.#defs.append(filter);
9088 #appendFeFunc(feComponentTransfer, func, table) {
9089 const feFunc = this.#document.createElementNS(SVG_NS, func);
9090 feFunc.setAttribute("type", "discrete");
9091 feFunc.setAttribute("tableValues", table);
9092 feComponentTransfer.append(feFunc);
9094 #addTransferMapConversion(rTable, gTable, bTable, filter) {
9095 const feComponentTransfer = this.#document.createElementNS(SVG_NS, "feComponentTransfer");
9096 filter.append(feComponentTransfer);
9097 this.#appendFeFunc(feComponentTransfer, "feFuncR", rTable);
9098 this.#appendFeFunc(feComponentTransfer, "feFuncG", gTable);
9099 this.#appendFeFunc(feComponentTransfer, "feFuncB", bTable);
9101 #addTransferMapAlphaConversion(aTable, filter) {
9102 const feComponentTransfer = this.#document.createElementNS(SVG_NS, "feComponentTransfer");
9103 filter.append(feComponentTransfer);
9104 this.#appendFeFunc(feComponentTransfer, "feFuncA", aTable);
9107 this.#defs.style.color = color;
9108 return getRGB(getComputedStyle(this.#defs).getPropertyValue("color"));
9112 ;// ./src/display/worker_options.js
9113 class GlobalWorkerOptions {
9114 static #port = null;
9116 static get workerPort() {
9119 static set workerPort(val) {
9120 if (!(typeof Worker !== "undefined" && val instanceof Worker) && val !== null) {
9121 throw new Error("Invalid `workerPort` type.");
9125 static get workerSrc() {
9128 static set workerSrc(val) {
9129 if (typeof val !== "string") {
9130 throw new Error("Invalid `workerSrc` type.");
9136 ;// ./src/display/metadata.js
9145 this.#metadataMap = parsedData;
9146 this.#data = rawData;
9152 return this.#metadataMap.get(name) ?? null;
9155 return objectFromMap(this.#metadataMap);
9158 return this.#metadataMap.has(name);
9162 ;// ./src/display/optional_content_config.js
9165 const INTERNAL = Symbol("INTERNAL");
9166 class OptionalContentGroup {
9171 constructor(renderingIntent, {
9177 this.#isDisplay = !!(renderingIntent & RenderingIntentFlag.DISPLAY);
9178 this.#isPrint = !!(renderingIntent & RenderingIntentFlag.PRINT);
9180 this.intent = intent;
9182 this.rbGroups = rbGroups;
9185 if (this.#userSet) {
9186 return this.#visible;
9188 if (!this.#visible) {
9195 if (this.#isDisplay) {
9196 return view?.viewState !== "OFF";
9197 } else if (this.#isPrint) {
9198 return print?.printState !== "OFF";
9202 _setVisible(internal, visible, userSet = false) {
9203 if (internal !== INTERNAL) {
9204 unreachable("Internal method `_setVisible` called.");
9206 this.#userSet = userSet;
9207 this.#visible = visible;
9210 class OptionalContentConfig {
9211 #cachedGetHash = null;
9212 #groups = new Map();
9213 #initialHash = null;
9215 constructor(data, renderingIntent = RenderingIntentFlag.DISPLAY) {
9216 this.renderingIntent = renderingIntent;
9218 this.creator = null;
9219 if (data === null) {
9222 this.name = data.name;
9223 this.creator = data.creator;
9224 this.#order = data.order;
9225 for (const group of data.groups) {
9226 this.#groups.set(group.id, new OptionalContentGroup(renderingIntent, group));
9228 if (data.baseState === "OFF") {
9229 for (const group of this.#groups.values()) {
9230 group._setVisible(INTERNAL, false);
9233 for (const on of data.on) {
9234 this.#groups.get(on)._setVisible(INTERNAL, true);
9236 for (const off of data.off) {
9237 this.#groups.get(off)._setVisible(INTERNAL, false);
9239 this.#initialHash = this.getHash();
9241 #evaluateVisibilityExpression(array) {
9242 const length = array.length;
9246 const operator = array[0];
9247 for (let i = 1; i < length; i++) {
9248 const element = array[i];
9250 if (Array.isArray(element)) {
9251 state = this.#evaluateVisibilityExpression(element);
9252 } else if (this.#groups.has(element)) {
9253 state = this.#groups.get(element).visible;
9255 warn(`Optional content group not found: ${element}`);
9275 return operator === "And";
9278 if (this.#groups.size === 0) {
9282 info("Optional content group not defined.");
9285 if (group.type === "OCG") {
9286 if (!this.#groups.has(group.id)) {
9287 warn(`Optional content group not found: ${group.id}`);
9290 return this.#groups.get(group.id).visible;
9291 } else if (group.type === "OCMD") {
9292 if (group.expression) {
9293 return this.#evaluateVisibilityExpression(group.expression);
9295 if (!group.policy || group.policy === "AnyOn") {
9296 for (const id of group.ids) {
9297 if (!this.#groups.has(id)) {
9298 warn(`Optional content group not found: ${id}`);
9301 if (this.#groups.get(id).visible) {
9306 } else if (group.policy === "AllOn") {
9307 for (const id of group.ids) {
9308 if (!this.#groups.has(id)) {
9309 warn(`Optional content group not found: ${id}`);
9312 if (!this.#groups.get(id).visible) {
9317 } else if (group.policy === "AnyOff") {
9318 for (const id of group.ids) {
9319 if (!this.#groups.has(id)) {
9320 warn(`Optional content group not found: ${id}`);
9323 if (!this.#groups.get(id).visible) {
9328 } else if (group.policy === "AllOff") {
9329 for (const id of group.ids) {
9330 if (!this.#groups.has(id)) {
9331 warn(`Optional content group not found: ${id}`);
9334 if (this.#groups.get(id).visible) {
9340 warn(`Unknown optional content policy ${group.policy}.`);
9343 warn(`Unknown group type ${group.type}.`);
9346 setVisibility(id, visible = true, preserveRB = true) {
9347 const group = this.#groups.get(id);
9349 warn(`Optional content group not found: ${id}`);
9352 if (preserveRB && visible && group.rbGroups.length) {
9353 for (const rbGroup of group.rbGroups) {
9354 for (const otherId of rbGroup) {
9355 if (otherId !== id) {
9356 this.#groups.get(otherId)?._setVisible(INTERNAL, false, true);
9361 group._setVisible(INTERNAL, !!visible, true);
9362 this.#cachedGetHash = null;
9369 for (const elem of state) {
9377 const group = this.#groups.get(elem);
9383 this.setVisibility(elem, true, preserveRB);
9386 this.setVisibility(elem, false, preserveRB);
9389 this.setVisibility(elem, !group.visible, preserveRB);
9393 this.#cachedGetHash = null;
9395 get hasInitialVisibility() {
9396 return this.#initialHash === null || this.getHash() === this.#initialHash;
9399 if (!this.#groups.size) {
9403 return this.#order.slice();
9405 return [...this.#groups.keys()];
9408 return this.#groups.size > 0 ? objectFromMap(this.#groups) : null;
9411 return this.#groups.get(id) || null;
9414 if (this.#cachedGetHash !== null) {
9415 return this.#cachedGetHash;
9417 const hash = new MurmurHash3_64();
9418 for (const [id, group] of this.#groups) {
9419 hash.update(`${id}:${group.visible}`);
9421 return this.#cachedGetHash = hash.hexdigest();
9425 ;// ./src/display/transport_stream.js
9428 class PDFDataTransportStream {
9429 constructor(pdfDataRangeTransport, {
9430 disableRange = false,
9431 disableStream = false
9433 assert(pdfDataRangeTransport, 'PDFDataTransportStream - missing required "pdfDataRangeTransport" argument.');
9438 contentDispositionFilename
9439 } = pdfDataRangeTransport;
9440 this._queuedChunks = [];
9441 this._progressiveDone = progressiveDone;
9442 this._contentDispositionFilename = contentDispositionFilename;
9443 if (initialData?.length > 0) {
9444 const buffer = initialData instanceof Uint8Array && initialData.byteLength === initialData.buffer.byteLength ? initialData.buffer : new Uint8Array(initialData).buffer;
9445 this._queuedChunks.push(buffer);
9447 this._pdfDataRangeTransport = pdfDataRangeTransport;
9448 this._isStreamingSupported = !disableStream;
9449 this._isRangeSupported = !disableRange;
9450 this._contentLength = length;
9451 this._fullRequestReader = null;
9452 this._rangeReaders = [];
9453 pdfDataRangeTransport.addRangeListener((begin, chunk) => {
9454 this._onReceiveData({
9459 pdfDataRangeTransport.addProgressListener((loaded, total) => {
9465 pdfDataRangeTransport.addProgressiveReadListener(chunk => {
9466 this._onReceiveData({
9470 pdfDataRangeTransport.addProgressiveDoneListener(() => {
9471 this._onProgressiveDone();
9473 pdfDataRangeTransport.transportReady();
9479 const buffer = chunk instanceof Uint8Array && chunk.byteLength === chunk.buffer.byteLength ? chunk.buffer : new Uint8Array(chunk).buffer;
9480 if (begin === undefined) {
9481 if (this._fullRequestReader) {
9482 this._fullRequestReader._enqueue(buffer);
9484 this._queuedChunks.push(buffer);
9487 const found = this._rangeReaders.some(function (rangeReader) {
9488 if (rangeReader._begin !== begin) {
9491 rangeReader._enqueue(buffer);
9494 assert(found, "_onReceiveData - no `PDFDataTransportStreamRangeReader` instance found.");
9497 get _progressiveDataLength() {
9498 return this._fullRequestReader?._loaded ?? 0;
9501 if (evt.total === undefined) {
9502 this._rangeReaders[0]?.onProgress?.({
9506 this._fullRequestReader?.onProgress?.({
9512 _onProgressiveDone() {
9513 this._fullRequestReader?.progressiveDone();
9514 this._progressiveDone = true;
9516 _removeRangeReader(reader) {
9517 const i = this._rangeReaders.indexOf(reader);
9519 this._rangeReaders.splice(i, 1);
9523 assert(!this._fullRequestReader, "PDFDataTransportStream.getFullReader can only be called once.");
9524 const queuedChunks = this._queuedChunks;
9525 this._queuedChunks = null;
9526 return new PDFDataTransportStreamReader(this, queuedChunks, this._progressiveDone, this._contentDispositionFilename);
9528 getRangeReader(begin, end) {
9529 if (end <= this._progressiveDataLength) {
9532 const reader = new PDFDataTransportStreamRangeReader(this, begin, end);
9533 this._pdfDataRangeTransport.requestDataRange(begin, end);
9534 this._rangeReaders.push(reader);
9537 cancelAllRequests(reason) {
9538 this._fullRequestReader?.cancel(reason);
9539 for (const reader of this._rangeReaders.slice(0)) {
9540 reader.cancel(reason);
9542 this._pdfDataRangeTransport.abort();
9545 class PDFDataTransportStreamReader {
9546 constructor(stream, queuedChunks, progressiveDone = false, contentDispositionFilename = null) {
9547 this._stream = stream;
9548 this._done = progressiveDone || false;
9549 this._filename = isPdfFile(contentDispositionFilename) ? contentDispositionFilename : null;
9550 this._queuedChunks = queuedChunks || [];
9552 for (const chunk of this._queuedChunks) {
9553 this._loaded += chunk.byteLength;
9555 this._requests = [];
9556 this._headersReady = Promise.resolve();
9557 stream._fullRequestReader = this;
9558 this.onProgress = null;
9564 if (this._requests.length > 0) {
9565 const requestCapability = this._requests.shift();
9566 requestCapability.resolve({
9571 this._queuedChunks.push(chunk);
9573 this._loaded += chunk.byteLength;
9575 get headersReady() {
9576 return this._headersReady;
9579 return this._filename;
9581 get isRangeSupported() {
9582 return this._stream._isRangeSupported;
9584 get isStreamingSupported() {
9585 return this._stream._isStreamingSupported;
9587 get contentLength() {
9588 return this._stream._contentLength;
9591 if (this._queuedChunks.length > 0) {
9592 const chunk = this._queuedChunks.shift();
9604 const requestCapability = Promise.withResolvers();
9605 this._requests.push(requestCapability);
9606 return requestCapability.promise;
9610 for (const requestCapability of this._requests) {
9611 requestCapability.resolve({
9616 this._requests.length = 0;
9625 class PDFDataTransportStreamRangeReader {
9626 constructor(stream, begin, end) {
9627 this._stream = stream;
9628 this._begin = begin;
9630 this._queuedChunk = null;
9631 this._requests = [];
9633 this.onProgress = null;
9639 if (this._requests.length === 0) {
9640 this._queuedChunk = chunk;
9642 const requestsCapability = this._requests.shift();
9643 requestsCapability.resolve({
9647 for (const requestCapability of this._requests) {
9648 requestCapability.resolve({
9653 this._requests.length = 0;
9656 this._stream._removeRangeReader(this);
9658 get isStreamingSupported() {
9662 if (this._queuedChunk) {
9663 const chunk = this._queuedChunk;
9664 this._queuedChunk = null;
9676 const requestCapability = Promise.withResolvers();
9677 this._requests.push(requestCapability);
9678 return requestCapability.promise;
9682 for (const requestCapability of this._requests) {
9683 requestCapability.resolve({
9688 this._requests.length = 0;
9689 this._stream._removeRangeReader(this);
9693 ;// ./src/display/text_layer.js
9696 const MAX_TEXT_DIVS_TO_RENDER = 100000;
9697 const DEFAULT_FONT_SIZE = 30;
9698 const DEFAULT_FONT_ASCENT = 0.8;
9700 #capability = Promise.withResolvers();
9702 #disableProcessItems = false;
9703 #fontInspectorEnabled = !!globalThis.FontInspector?.enabled;
9705 #layoutTextParams = null;
9709 #rootContainer = null;
9712 #styleCache = Object.create(null);
9713 #textContentItemsStr = [];
9714 #textContentSource = null;
9716 #textDivProperties = new WeakMap();
9718 static #ascentCache = new Map();
9719 static #canvasContexts = new Map();
9720 static #canvasCtxFonts = new WeakMap();
9721 static #minFontSize = null;
9722 static #pendingTextLayers = new Set();
9728 if (textContentSource instanceof ReadableStream) {
9729 this.#textContentSource = textContentSource;
9731 throw new Error('No "textContentSource" parameter specified.');
9733 this.#container = this.#rootContainer = container;
9734 this.#scale = viewport.scale * (globalThis.devicePixelRatio || 1);
9735 this.#rotation = viewport.rotation;
9736 this.#layoutTextParams = {
9746 } = viewport.rawDims;
9747 this.#transform = [1, 0, 0, -1, -pageX, pageY + pageHeight];
9748 this.#pageWidth = pageWidth;
9749 this.#pageHeight = pageHeight;
9750 TextLayer.#ensureMinFontSizeComputed();
9751 setLayerDimensions(container, viewport);
9752 this.#capability.promise.finally(() => {
9753 TextLayer.#pendingTextLayers.delete(this);
9754 this.#layoutTextParams = null;
9755 this.#styleCache = null;
9758 static get fontFamilyMap() {
9762 } = util_FeatureTest.platform;
9763 return shadow(this, "fontFamilyMap", new Map([["sans-serif", `${isWindows && isFirefox ? "Calibri, " : ""}sans-serif`], ["monospace", `${isWindows && isFirefox ? "Lucida Console, " : ""}monospace`]]));
9766 const pump = () => {
9767 this.#reader.read().then(({
9772 this.#capability.resolve();
9775 this.#lang ??= value.lang;
9776 Object.assign(this.#styleCache, value.styles);
9777 this.#processItems(value.items);
9779 }, this.#capability.reject);
9781 this.#reader = this.#textContentSource.getReader();
9782 TextLayer.#pendingTextLayers.add(this);
9784 return this.#capability.promise;
9790 const scale = viewport.scale * (globalThis.devicePixelRatio || 1);
9791 const rotation = viewport.rotation;
9792 if (rotation !== this.#rotation) {
9794 this.#rotation = rotation;
9795 setLayerDimensions(this.#rootContainer, {
9799 if (scale !== this.#scale) {
9801 this.#scale = scale;
9805 ctx: TextLayer.#getCtx(this.#lang)
9807 for (const div of this.#textDivs) {
9808 params.properties = this.#textDivProperties.get(div);
9810 this.#layout(params);
9815 const abortEx = new AbortException("TextLayer task cancelled.");
9816 this.#reader?.cancel(abortEx).catch(() => {});
9817 this.#reader = null;
9818 this.#capability.reject(abortEx);
9821 return this.#textDivs;
9823 get textContentItemsStr() {
9824 return this.#textContentItemsStr;
9826 #processItems(items) {
9827 if (this.#disableProcessItems) {
9830 this.#layoutTextParams.ctx ??= TextLayer.#getCtx(this.#lang);
9831 const textDivs = this.#textDivs,
9832 textContentItemsStr = this.#textContentItemsStr;
9833 for (const item of items) {
9834 if (textDivs.length > MAX_TEXT_DIVS_TO_RENDER) {
9835 warn("Ignoring additional textDivs for performance reasons.");
9836 this.#disableProcessItems = true;
9839 if (item.str === undefined) {
9840 if (item.type === "beginMarkedContentProps" || item.type === "beginMarkedContent") {
9841 const parent = this.#container;
9842 this.#container = document.createElement("span");
9843 this.#container.classList.add("markedContent");
9844 if (item.id !== null) {
9845 this.#container.setAttribute("id", `${item.id}`);
9847 parent.append(this.#container);
9848 } else if (item.type === "endMarkedContent") {
9849 this.#container = this.#container.parentNode;
9853 textContentItemsStr.push(item.str);
9854 this.#appendText(item);
9858 const textDiv = document.createElement("span");
9859 const textDivProperties = {
9862 hasText: geom.str !== "",
9863 hasEOL: geom.hasEOL,
9866 this.#textDivs.push(textDiv);
9867 const tx = Util.transform(this.#transform, geom.transform);
9868 let angle = Math.atan2(tx[1], tx[0]);
9869 const style = this.#styleCache[geom.fontName];
9870 if (style.vertical) {
9871 angle += Math.PI / 2;
9873 let fontFamily = this.#fontInspectorEnabled && style.fontSubstitution || style.fontFamily;
9874 fontFamily = TextLayer.fontFamilyMap.get(fontFamily) || fontFamily;
9875 const fontHeight = Math.hypot(tx[2], tx[3]);
9876 const fontAscent = fontHeight * TextLayer.#getAscent(fontFamily, this.#lang);
9880 top = tx[5] - fontAscent;
9882 left = tx[4] + fontAscent * Math.sin(angle);
9883 top = tx[5] - fontAscent * Math.cos(angle);
9885 const scaleFactorStr = "calc(var(--scale-factor)*";
9886 const divStyle = textDiv.style;
9887 if (this.#container === this.#rootContainer) {
9888 divStyle.left = `${(100 * left / this.#pageWidth).toFixed(2)}%`;
9889 divStyle.top = `${(100 * top / this.#pageHeight).toFixed(2)}%`;
9891 divStyle.left = `${scaleFactorStr}${left.toFixed(2)}px)`;
9892 divStyle.top = `${scaleFactorStr}${top.toFixed(2)}px)`;
9894 divStyle.fontSize = `${scaleFactorStr}${(TextLayer.#minFontSize * fontHeight).toFixed(2)}px)`;
9895 divStyle.fontFamily = fontFamily;
9896 textDivProperties.fontSize = fontHeight;
9897 textDiv.setAttribute("role", "presentation");
9898 textDiv.textContent = geom.str;
9899 textDiv.dir = geom.dir;
9900 if (this.#fontInspectorEnabled) {
9901 textDiv.dataset.fontName = style.fontSubstitutionLoadedName || geom.fontName;
9904 textDivProperties.angle = angle * (180 / Math.PI);
9906 let shouldScaleText = false;
9907 if (geom.str.length > 1) {
9908 shouldScaleText = true;
9909 } else if (geom.str !== " " && geom.transform[0] !== geom.transform[3]) {
9910 const absScaleX = Math.abs(geom.transform[0]),
9911 absScaleY = Math.abs(geom.transform[3]);
9912 if (absScaleX !== absScaleY && Math.max(absScaleX, absScaleY) / Math.min(absScaleX, absScaleY) > 1.5) {
9913 shouldScaleText = true;
9916 if (shouldScaleText) {
9917 textDivProperties.canvasWidth = style.vertical ? geom.height : geom.width;
9919 this.#textDivProperties.set(textDiv, textDivProperties);
9920 this.#layoutTextParams.div = textDiv;
9921 this.#layoutTextParams.properties = textDivProperties;
9922 this.#layout(this.#layoutTextParams);
9923 if (textDivProperties.hasText) {
9924 this.#container.append(textDiv);
9926 if (textDivProperties.hasEOL) {
9927 const br = document.createElement("br");
9928 br.setAttribute("role", "presentation");
9929 this.#container.append(br);
9942 if (TextLayer.#minFontSize > 1) {
9943 transform = `scale(${1 / TextLayer.#minFontSize})`;
9945 if (properties.canvasWidth !== 0 && properties.hasText) {
9953 TextLayer.#ensureCtxFont(ctx, fontSize * this.#scale, fontFamily);
9956 } = ctx.measureText(div.textContent);
9958 transform = `scaleX(${canvasWidth * this.#scale / width}) ${transform}`;
9961 if (properties.angle !== 0) {
9962 transform = `rotate(${properties.angle}deg) ${transform}`;
9964 if (transform.length > 0) {
9965 style.transform = transform;
9969 if (this.#pendingTextLayers.size > 0) {
9972 this.#ascentCache.clear();
9975 } of this.#canvasContexts.values()) {
9978 this.#canvasContexts.clear();
9980 static #getCtx(lang = null) {
9981 let ctx = this.#canvasContexts.get(lang ||= "");
9983 const canvas = document.createElement("canvas");
9984 canvas.className = "hiddenCanvasElement";
9986 document.body.append(canvas);
9987 ctx = canvas.getContext("2d", {
9989 willReadFrequently: true
9991 this.#canvasContexts.set(lang, ctx);
9992 this.#canvasCtxFonts.set(ctx, {
9999 static #ensureCtxFont(ctx, size, family) {
10000 const cached = this.#canvasCtxFonts.get(ctx);
10001 if (size === cached.size && family === cached.family) {
10004 ctx.font = `${size}px ${family}`;
10005 cached.size = size;
10006 cached.family = family;
10008 static #ensureMinFontSizeComputed() {
10009 if (this.#minFontSize !== null) {
10012 const div = document.createElement("div");
10013 div.style.opacity = 0;
10014 div.style.lineHeight = 1;
10015 div.style.fontSize = "1px";
10016 div.style.position = "absolute";
10017 div.textContent = "X";
10018 document.body.append(div);
10019 this.#minFontSize = div.getBoundingClientRect().height;
10022 static #getAscent(fontFamily, lang) {
10023 const cachedAscent = this.#ascentCache.get(fontFamily);
10024 if (cachedAscent) {
10025 return cachedAscent;
10027 const ctx = this.#getCtx(lang);
10028 ctx.canvas.width = ctx.canvas.height = DEFAULT_FONT_SIZE;
10029 this.#ensureCtxFont(ctx, DEFAULT_FONT_SIZE, fontFamily);
10030 const metrics = ctx.measureText("");
10031 let ascent = metrics.fontBoundingBoxAscent;
10032 let descent = Math.abs(metrics.fontBoundingBoxDescent);
10034 const ratio = ascent / (ascent + descent);
10035 this.#ascentCache.set(fontFamily, ratio);
10036 ctx.canvas.width = ctx.canvas.height = 0;
10039 ctx.strokeStyle = "red";
10040 ctx.clearRect(0, 0, DEFAULT_FONT_SIZE, DEFAULT_FONT_SIZE);
10041 ctx.strokeText("g", 0, 0);
10042 let pixels = ctx.getImageData(0, 0, DEFAULT_FONT_SIZE, DEFAULT_FONT_SIZE).data;
10044 for (let i = pixels.length - 1 - 3; i >= 0; i -= 4) {
10045 if (pixels[i] > 0) {
10046 descent = Math.ceil(i / 4 / DEFAULT_FONT_SIZE);
10050 ctx.clearRect(0, 0, DEFAULT_FONT_SIZE, DEFAULT_FONT_SIZE);
10051 ctx.strokeText("A", 0, DEFAULT_FONT_SIZE);
10052 pixels = ctx.getImageData(0, 0, DEFAULT_FONT_SIZE, DEFAULT_FONT_SIZE).data;
10054 for (let i = 0, ii = pixels.length; i < ii; i += 4) {
10055 if (pixels[i] > 0) {
10056 ascent = DEFAULT_FONT_SIZE - Math.floor(i / 4 / DEFAULT_FONT_SIZE);
10060 ctx.canvas.width = ctx.canvas.height = 0;
10061 const ratio = ascent ? ascent / (ascent + descent) : DEFAULT_FONT_ASCENT;
10062 this.#ascentCache.set(fontFamily, ratio);
10067 ;// ./src/display/xfa_text.js
10069 static textContent(xfa) {
10073 styles: Object.create(null)
10075 function walk(node) {
10080 const name = node.name;
10081 if (name === "#text") {
10083 } else if (!XfaText.shouldBuildText(name)) {
10085 } else if (node?.attributes?.textContent) {
10086 str = node.attributes.textContent;
10087 } else if (node.value) {
10090 if (str !== null) {
10095 if (!node.children) {
10098 for (const child of node.children) {
10105 static shouldBuildText(name) {
10106 return !(name === "textarea" || name === "input" || name === "option" || name === "select");
10110 ;// ./src/display/api.js
10132 const DEFAULT_RANGE_CHUNK_SIZE = 65536;
10133 const RENDERING_CANCELLED_TIMEOUT = 100;
10134 function getDocument(src = {}) {
10135 const task = new PDFDocumentLoadingTask();
10139 const url = src.url ? getUrlProp(src.url) : null;
10140 const data = src.data ? getDataProp(src.data) : null;
10141 const httpHeaders = src.httpHeaders || null;
10142 const withCredentials = src.withCredentials === true;
10143 const password = src.password ?? null;
10144 const rangeTransport = src.range instanceof PDFDataRangeTransport ? src.range : null;
10145 const rangeChunkSize = Number.isInteger(src.rangeChunkSize) && src.rangeChunkSize > 0 ? src.rangeChunkSize : DEFAULT_RANGE_CHUNK_SIZE;
10146 let worker = src.worker instanceof PDFWorker ? src.worker : null;
10147 const verbosity = src.verbosity;
10148 const docBaseUrl = typeof src.docBaseUrl === "string" && !isDataScheme(src.docBaseUrl) ? src.docBaseUrl : null;
10149 const cMapUrl = getFactoryUrlProp(src.cMapUrl);
10150 const cMapPacked = src.cMapPacked !== false;
10151 const CMapReaderFactory = src.CMapReaderFactory || DOMCMapReaderFactory;
10152 const standardFontDataUrl = getFactoryUrlProp(src.standardFontDataUrl);
10153 const StandardFontDataFactory = src.StandardFontDataFactory || DOMStandardFontDataFactory;
10154 const wasmUrl = getFactoryUrlProp(src.wasmUrl);
10155 const WasmFactory = src.WasmFactory || DOMWasmFactory;
10156 const ignoreErrors = src.stopAtErrors !== true;
10157 const maxImageSize = Number.isInteger(src.maxImageSize) && src.maxImageSize > -1 ? src.maxImageSize : -1;
10158 const isEvalSupported = src.isEvalSupported !== false;
10159 const isOffscreenCanvasSupported = typeof src.isOffscreenCanvasSupported === "boolean" ? src.isOffscreenCanvasSupported : !isNodeJS;
10160 const isImageDecoderSupported = typeof src.isImageDecoderSupported === "boolean" ? src.isImageDecoderSupported : true;
10161 const canvasMaxAreaInBytes = Number.isInteger(src.canvasMaxAreaInBytes) ? src.canvasMaxAreaInBytes : -1;
10162 const disableFontFace = typeof src.disableFontFace === "boolean" ? src.disableFontFace : isNodeJS;
10163 const fontExtraProperties = src.fontExtraProperties === true;
10164 const enableXfa = src.enableXfa === true;
10165 const ownerDocument = src.ownerDocument || globalThis.document;
10166 const disableRange = src.disableRange === true;
10167 const disableStream = src.disableStream === true;
10168 const disableAutoFetch = src.disableAutoFetch === true;
10169 const pdfBug = src.pdfBug === true;
10170 const CanvasFactory = src.CanvasFactory || DOMCanvasFactory;
10171 const FilterFactory = src.FilterFactory || DOMFilterFactory;
10172 const enableHWA = src.enableHWA === true;
10173 const length = rangeTransport ? rangeTransport.length : src.length ?? NaN;
10174 const useSystemFonts = typeof src.useSystemFonts === "boolean" ? src.useSystemFonts : !isNodeJS && !disableFontFace;
10175 const useWorkerFetch = typeof src.useWorkerFetch === "boolean" ? src.useWorkerFetch : true;
10176 const styleElement = null;
10177 setVerbosityLevel(verbosity);
10178 const transportFactory = {
10179 canvasFactory: new CanvasFactory({
10183 filterFactory: new FilterFactory({
10187 cMapReaderFactory: null,
10188 standardFontDataFactory: null,
10192 const workerParams = {
10194 port: GlobalWorkerOptions.workerPort
10196 worker = workerParams.port ? PDFWorker.fromPort(workerParams) : new PDFWorker(workerParams);
10197 task._worker = worker;
10199 const docParams = {
10201 apiVersion: "5.0.98",
10209 evaluatorOptions: {
10214 isOffscreenCanvasSupported,
10215 isImageDecoderSupported,
10216 canvasMaxAreaInBytes,
10217 fontExtraProperties,
10219 cMapUrl: useWorkerFetch ? cMapUrl : null,
10220 standardFontDataUrl: useWorkerFetch ? standardFontDataUrl : null,
10221 wasmUrl: useWorkerFetch ? wasmUrl : null
10224 const transportParams = {
10226 fontExtraProperties,
10235 worker.promise.then(function () {
10236 if (task.destroyed) {
10237 throw new Error("Loading aborted");
10239 if (worker.destroyed) {
10240 throw new Error("Worker was destroyed");
10242 const workerIdPromise = worker.messageHandler.sendWithPromise("GetDocRequest", docParams, data ? [data.buffer] : null);
10244 if (rangeTransport) {
10245 networkStream = new PDFDataTransportStream(rangeTransport, {
10249 } else if (!data) {
10250 throw new Error("Not implemented: NetworkStream");
10252 return workerIdPromise.then(workerId => {
10253 if (task.destroyed) {
10254 throw new Error("Loading aborted");
10256 if (worker.destroyed) {
10257 throw new Error("Worker was destroyed");
10259 const messageHandler = new MessageHandler(docId, workerId, worker.port);
10260 const transport = new WorkerTransport(messageHandler, task, networkStream, transportParams, transportFactory);
10261 task._transport = transport;
10262 messageHandler.send("Ready", null);
10264 }).catch(task._capability.reject);
10267 function getUrlProp(val) {
10270 function getDataProp(val) {
10271 if (val instanceof Uint8Array && val.byteLength === val.buffer.byteLength) {
10274 if (typeof val === "string") {
10275 return stringToBytes(val);
10277 if (val instanceof ArrayBuffer || ArrayBuffer.isView(val) || typeof val === "object" && !isNaN(val?.length)) {
10278 return new Uint8Array(val);
10280 throw new Error("Invalid PDF binary data: either TypedArray, " + "string, or array-like object is expected in the data property.");
10282 function getFactoryUrlProp(val) {
10283 if (typeof val !== "string") {
10286 if (val.endsWith("/")) {
10289 throw new Error(`Invalid factory url: "${val}" must include trailing slash.`);
10291 function isRefProxy(ref) {
10292 return typeof ref === "object" && Number.isInteger(ref?.num) && ref.num >= 0 && Number.isInteger(ref?.gen) && ref.gen >= 0;
10294 class PDFDocumentLoadingTask {
10297 this._capability = Promise.withResolvers();
10298 this._transport = null;
10299 this._worker = null;
10300 this.docId = `d${PDFDocumentLoadingTask.#docId++}`;
10301 this.destroyed = false;
10302 this.onPassword = null;
10303 this.onProgress = null;
10306 return this._capability.promise;
10309 this.destroyed = true;
10310 await this._transport?.destroy();
10311 this._transport = null;
10312 this._worker?.destroy();
10313 this._worker = null;
10316 class PDFDataRangeTransport {
10317 constructor(length, initialData, progressiveDone = false, contentDispositionFilename = null) {
10318 this.length = length;
10319 this.initialData = initialData;
10320 this.progressiveDone = progressiveDone;
10321 this.contentDispositionFilename = contentDispositionFilename;
10322 this._rangeListeners = [];
10323 this._progressListeners = [];
10324 this._progressiveReadListeners = [];
10325 this._progressiveDoneListeners = [];
10326 this._readyCapability = Promise.withResolvers();
10328 addRangeListener(listener) {
10329 this._rangeListeners.push(listener);
10331 addProgressListener(listener) {
10332 this._progressListeners.push(listener);
10334 addProgressiveReadListener(listener) {
10335 this._progressiveReadListeners.push(listener);
10337 addProgressiveDoneListener(listener) {
10338 this._progressiveDoneListeners.push(listener);
10340 onDataRange(begin, chunk) {
10341 for (const listener of this._rangeListeners) {
10342 listener(begin, chunk);
10345 onDataProgress(loaded, total) {
10346 this._readyCapability.promise.then(() => {
10347 for (const listener of this._progressListeners) {
10348 listener(loaded, total);
10352 onDataProgressiveRead(chunk) {
10353 this._readyCapability.promise.then(() => {
10354 for (const listener of this._progressiveReadListeners) {
10359 onDataProgressiveDone() {
10360 this._readyCapability.promise.then(() => {
10361 for (const listener of this._progressiveDoneListeners) {
10367 this._readyCapability.resolve();
10369 requestDataRange(begin, end) {
10370 unreachable("Abstract method PDFDataRangeTransport.requestDataRange");
10374 class PDFDocumentProxy {
10375 constructor(pdfInfo, transport) {
10376 this._pdfInfo = pdfInfo;
10377 this._transport = transport;
10379 get annotationStorage() {
10380 return this._transport.annotationStorage;
10382 get canvasFactory() {
10383 return this._transport.canvasFactory;
10385 get filterFactory() {
10386 return this._transport.filterFactory;
10389 return this._pdfInfo.numPages;
10391 get fingerprints() {
10392 return this._pdfInfo.fingerprints;
10395 return shadow(this, "isPureXfa", !!this._transport._htmlForXfa);
10398 return this._transport._htmlForXfa;
10400 getPage(pageNumber) {
10401 return this._transport.getPage(pageNumber);
10403 getPageIndex(ref) {
10404 return this._transport.getPageIndex(ref);
10406 getDestinations() {
10407 return this._transport.getDestinations();
10409 getDestination(id) {
10410 return this._transport.getDestination(id);
10413 return this._transport.getPageLabels();
10416 return this._transport.getPageLayout();
10419 return this._transport.getPageMode();
10421 getViewerPreferences() {
10422 return this._transport.getViewerPreferences();
10425 return this._transport.getOpenAction();
10428 return this._transport.getAttachments();
10431 return this._transport.getDocJSActions();
10434 return this._transport.getOutline();
10436 getOptionalContentConfig({
10441 } = this._transport.getRenderingIntent(intent);
10442 return this._transport.getOptionalContentConfig(renderingIntent);
10445 return this._transport.getPermissions();
10448 return this._transport.getMetadata();
10451 return this._transport.getMarkInfo();
10454 return this._transport.getData();
10457 return this._transport.saveDocument();
10459 getDownloadInfo() {
10460 return this._transport.downloadInfoCapability.promise;
10462 cleanup(keepLoadedFonts = false) {
10463 return this._transport.startCleanup(keepLoadedFonts || this.isPureXfa);
10466 return this.loadingTask.destroy();
10468 cachedPageNumber(ref) {
10469 return this._transport.cachedPageNumber(ref);
10471 get loadingParams() {
10472 return this._transport.loadingParams;
10474 get loadingTask() {
10475 return this._transport.loadingTask;
10477 getFieldObjects() {
10478 return this._transport.getFieldObjects();
10481 return this._transport.hasJSActions();
10483 getCalculationOrderIds() {
10484 return this._transport.getCalculationOrderIds();
10487 class PDFPageProxy {
10488 #pendingCleanup = false;
10489 constructor(pageIndex, pageInfo, transport, pdfBug = false) {
10490 this._pageIndex = pageIndex;
10491 this._pageInfo = pageInfo;
10492 this._transport = transport;
10493 this._stats = pdfBug ? new StatTimer() : null;
10494 this._pdfBug = pdfBug;
10495 this.commonObjs = transport.commonObjs;
10496 this.objs = new PDFObjects();
10497 this._intentStates = new Map();
10498 this.destroyed = false;
10501 return this._pageIndex + 1;
10504 return this._pageInfo.rotate;
10507 return this._pageInfo.ref;
10510 return this._pageInfo.userUnit;
10513 return this._pageInfo.view;
10517 rotation = this.rotate,
10522 return new PageViewport({
10523 viewBox: this.view,
10524 userUnit: this.userUnit,
10537 } = this._transport.getRenderingIntent(intent);
10538 return this._transport.getAnnotations(this._pageIndex, renderingIntent);
10541 return this._transport.getPageJSActions(this._pageIndex);
10543 get filterFactory() {
10544 return this._transport.filterFactory;
10547 return shadow(this, "isPureXfa", !!this._transport._htmlForXfa);
10550 return this._transport._htmlForXfa?.children[this._pageIndex] || null;
10555 intent = "display",
10556 annotationMode = AnnotationMode.ENABLE,
10559 optionalContentConfigPromise = null,
10560 annotationCanvasMap = null,
10562 printAnnotationStorage = null,
10565 this._stats?.time("Overall");
10566 const intentArgs = this._transport.getRenderingIntent(intent, annotationMode, printAnnotationStorage, isEditing);
10571 this.#pendingCleanup = false;
10572 optionalContentConfigPromise ||= this._transport.getOptionalContentConfig(renderingIntent);
10573 let intentState = this._intentStates.get(cacheKey);
10574 if (!intentState) {
10575 intentState = Object.create(null);
10576 this._intentStates.set(cacheKey, intentState);
10578 if (intentState.streamReaderCancelTimeout) {
10579 clearTimeout(intentState.streamReaderCancelTimeout);
10580 intentState.streamReaderCancelTimeout = null;
10582 const intentPrint = !!(renderingIntent & RenderingIntentFlag.PRINT);
10583 if (!intentState.displayReadyCapability) {
10584 intentState.displayReadyCapability = Promise.withResolvers();
10585 intentState.operatorList = {
10589 separateAnnots: null
10591 this._stats?.time("Page Request");
10592 this._pumpOperatorList(intentArgs);
10594 const complete = error => {
10595 intentState.renderTasks.delete(internalRenderTask);
10597 this.#pendingCleanup = true;
10599 this.#tryCleanup();
10601 internalRenderTask.capability.reject(error);
10602 this._abortOperatorList({
10604 reason: error instanceof Error ? error : new Error(error)
10607 internalRenderTask.capability.resolve();
10610 this._stats.timeEnd("Rendering");
10611 this._stats.timeEnd("Overall");
10612 if (globalThis.Stats?.enabled) {
10613 globalThis.Stats.add(this.pageNumber, this._stats);
10617 const internalRenderTask = new InternalRenderTask({
10618 callback: complete,
10626 commonObjs: this.commonObjs,
10627 annotationCanvasMap,
10628 operatorList: intentState.operatorList,
10629 pageIndex: this._pageIndex,
10630 canvasFactory: this._transport.canvasFactory,
10631 filterFactory: this._transport.filterFactory,
10632 useRequestAnimationFrame: !intentPrint,
10633 pdfBug: this._pdfBug,
10636 (intentState.renderTasks ||= new Set()).add(internalRenderTask);
10637 const renderTask = internalRenderTask.task;
10638 Promise.all([intentState.displayReadyCapability.promise, optionalContentConfigPromise]).then(([transparency, optionalContentConfig]) => {
10639 if (this.destroyed) {
10643 this._stats?.time("Rendering");
10644 if (!(optionalContentConfig.renderingIntent & renderingIntent)) {
10645 throw new Error("Must use the same `intent`-argument when calling the `PDFPageProxy.render` " + "and `PDFDocumentProxy.getOptionalContentConfig` methods.");
10647 internalRenderTask.initializeGraphics({
10649 optionalContentConfig
10651 internalRenderTask.operatorListChanged();
10652 }).catch(complete);
10656 intent = "display",
10657 annotationMode = AnnotationMode.ENABLE,
10658 printAnnotationStorage = null,
10661 throw new Error("Not implemented: getOperatorList");
10663 streamTextContent({
10664 includeMarkedContent = false,
10665 disableNormalization = false
10667 const TEXT_CONTENT_CHUNK_SIZE = 100;
10668 return this._transport.messageHandler.sendWithStream("GetTextContent", {
10669 pageIndex: this._pageIndex,
10670 includeMarkedContent: includeMarkedContent === true,
10671 disableNormalization: disableNormalization === true
10673 highWaterMark: TEXT_CONTENT_CHUNK_SIZE,
10674 size(textContent) {
10675 return textContent.items.length;
10679 getTextContent(params = {}) {
10680 if (this._transport._htmlForXfa) {
10681 return this.getXfa().then(xfa => XfaText.textContent(xfa));
10683 const readableStream = this.streamTextContent(params);
10684 return new Promise(function (resolve, reject) {
10686 reader.read().then(function ({
10691 resolve(textContent);
10694 textContent.lang ??= value.lang;
10695 Object.assign(textContent.styles, value.styles);
10696 textContent.items.push(...value.items);
10700 const reader = readableStream.getReader();
10701 const textContent = {
10703 styles: Object.create(null),
10710 return this._transport.getStructTree(this._pageIndex);
10713 this.destroyed = true;
10715 for (const intentState of this._intentStates.values()) {
10716 this._abortOperatorList({
10718 reason: new Error("Page was destroyed."),
10721 if (intentState.opListReadCapability) {
10724 for (const internalRenderTask of intentState.renderTasks) {
10725 waitOn.push(internalRenderTask.completed);
10726 internalRenderTask.cancel();
10730 this.#pendingCleanup = false;
10731 return Promise.all(waitOn);
10733 cleanup(resetStats = false) {
10734 this.#pendingCleanup = true;
10735 const success = this.#tryCleanup();
10736 if (resetStats && success) {
10737 this._stats &&= new StatTimer();
10742 if (!this.#pendingCleanup || this.destroyed) {
10748 } of this._intentStates.values()) {
10749 if (renderTasks.size > 0 || !operatorList.lastChunk) {
10753 this._intentStates.clear();
10755 this.#pendingCleanup = false;
10758 _startRenderPage(transparency, cacheKey) {
10759 const intentState = this._intentStates.get(cacheKey);
10760 if (!intentState) {
10763 this._stats?.timeEnd("Page Request");
10764 intentState.displayReadyCapability?.resolve(transparency);
10766 _renderPageChunk(operatorListChunk, intentState) {
10767 for (let i = 0, ii = operatorListChunk.length; i < ii; i++) {
10768 intentState.operatorList.fnArray.push(operatorListChunk.fnArray[i]);
10769 intentState.operatorList.argsArray.push(operatorListChunk.argsArray[i]);
10771 intentState.operatorList.lastChunk = operatorListChunk.lastChunk;
10772 intentState.operatorList.separateAnnots = operatorListChunk.separateAnnots;
10773 for (const internalRenderTask of intentState.renderTasks) {
10774 internalRenderTask.operatorListChanged();
10776 if (operatorListChunk.lastChunk) {
10777 this.#tryCleanup();
10780 _pumpOperatorList({
10783 annotationStorageSerializable,
10789 } = annotationStorageSerializable;
10790 const readableStream = this._transport.messageHandler.sendWithStream("GetOperatorList", {
10791 pageIndex: this._pageIndex,
10792 intent: renderingIntent,
10794 annotationStorage: map,
10797 const reader = readableStream.getReader();
10798 const intentState = this._intentStates.get(cacheKey);
10799 intentState.streamReader = reader;
10800 const pump = () => {
10801 reader.read().then(({
10806 intentState.streamReader = null;
10809 if (this._transport.destroyed) {
10812 this._renderPageChunk(value, intentState);
10815 intentState.streamReader = null;
10816 if (this._transport.destroyed) {
10819 if (intentState.operatorList) {
10820 intentState.operatorList.lastChunk = true;
10821 for (const internalRenderTask of intentState.renderTasks) {
10822 internalRenderTask.operatorListChanged();
10824 this.#tryCleanup();
10826 if (intentState.displayReadyCapability) {
10827 intentState.displayReadyCapability.reject(reason);
10828 } else if (intentState.opListReadCapability) {
10829 intentState.opListReadCapability.reject(reason);
10837 _abortOperatorList({
10842 if (!intentState.streamReader) {
10845 if (intentState.streamReaderCancelTimeout) {
10846 clearTimeout(intentState.streamReaderCancelTimeout);
10847 intentState.streamReaderCancelTimeout = null;
10850 if (intentState.renderTasks.size > 0) {
10853 if (reason instanceof RenderingCancelledException) {
10854 let delay = RENDERING_CANCELLED_TIMEOUT;
10855 if (reason.extraDelay > 0 && reason.extraDelay < 1000) {
10856 delay += reason.extraDelay;
10858 intentState.streamReaderCancelTimeout = setTimeout(() => {
10859 intentState.streamReaderCancelTimeout = null;
10860 this._abortOperatorList({
10869 intentState.streamReader.cancel(new AbortException(reason.message)).catch(() => {});
10870 intentState.streamReader = null;
10871 if (this._transport.destroyed) {
10874 for (const [curCacheKey, curIntentState] of this._intentStates) {
10875 if (curIntentState === intentState) {
10876 this._intentStates.delete(curCacheKey);
10883 return this._stats;
10886 class LoopbackPort {
10887 #listeners = new Map();
10888 #deferred = Promise.resolve();
10889 postMessage(obj, transfer) {
10891 data: structuredClone(obj, transfer ? {
10895 this.#deferred.then(() => {
10896 for (const [listener] of this.#listeners) {
10897 listener.call(this, event);
10901 addEventListener(name, listener, options = null) {
10902 let rmAbort = null;
10903 if (options?.signal instanceof AbortSignal) {
10907 if (signal.aborted) {
10908 warn("LoopbackPort - cannot use an `aborted` signal.");
10911 const onAbort = () => this.removeEventListener(name, listener);
10912 rmAbort = () => signal.removeEventListener("abort", onAbort);
10913 signal.addEventListener("abort", onAbort);
10915 this.#listeners.set(listener, rmAbort);
10917 removeEventListener(name, listener) {
10918 const rmAbort = this.#listeners.get(listener);
10920 this.#listeners.delete(listener);
10923 for (const [, rmAbort] of this.#listeners) {
10926 this.#listeners.clear();
10930 static #fakeWorkerId = 0;
10931 static #isWorkerDisabled = false;
10932 static #workerPorts;
10936 verbosity = getVerbosityLevel()
10939 this.destroyed = false;
10940 this.verbosity = verbosity;
10941 this._readyCapability = Promise.withResolvers();
10943 this._webWorker = null;
10944 this._messageHandler = null;
10945 this._initialize();
10948 return this._readyCapability.promise;
10951 this._readyCapability.resolve();
10952 this._messageHandler.send("configure", {
10953 verbosity: this.verbosity
10959 get messageHandler() {
10960 return this._messageHandler;
10962 _initializeFromPort(port) {
10963 throw new Error("Not implemented: _initializeFromPort");
10966 if (PDFWorker.#isWorkerDisabled || PDFWorker.#mainThreadWorkerMessageHandler) {
10967 this._setupFakeWorker();
10974 const worker = new Worker(workerSrc, {
10977 const messageHandler = new MessageHandler("main", "worker", worker);
10978 const terminateEarly = () => {
10980 messageHandler.destroy();
10981 worker.terminate();
10982 if (this.destroyed) {
10983 this._readyCapability.reject(new Error("Worker was destroyed"));
10985 this._setupFakeWorker();
10988 const ac = new AbortController();
10989 worker.addEventListener("error", () => {
10990 if (!this._webWorker) {
10996 messageHandler.on("test", data => {
10998 if (this.destroyed || !data) {
11002 this._messageHandler = messageHandler;
11003 this._port = worker;
11004 this._webWorker = worker;
11007 messageHandler.on("ready", data => {
11009 if (this.destroyed) {
11016 this._setupFakeWorker();
11019 const sendTest = () => {
11020 const testObj = new Uint8Array();
11021 messageHandler.send("test", testObj, [testObj.buffer]);
11026 info("The worker has been disabled.");
11028 this._setupFakeWorker();
11030 _setupFakeWorker() {
11031 if (!PDFWorker.#isWorkerDisabled) {
11032 warn("Setting up fake worker.");
11033 PDFWorker.#isWorkerDisabled = true;
11035 PDFWorker._setupFakeWorkerGlobal.then(WorkerMessageHandler => {
11036 if (this.destroyed) {
11037 this._readyCapability.reject(new Error("Worker was destroyed"));
11040 const port = new LoopbackPort();
11042 const id = `fake${PDFWorker.#fakeWorkerId++}`;
11043 const workerHandler = new MessageHandler(id + "_worker", id, port);
11044 WorkerMessageHandler.setup(workerHandler, port);
11045 this._messageHandler = new MessageHandler(id, id + "_worker", port);
11047 }).catch(reason => {
11048 this._readyCapability.reject(new Error(`Setting up fake worker failed: "${reason.message}".`));
11052 this.destroyed = true;
11053 this._webWorker?.terminate();
11054 this._webWorker = null;
11055 PDFWorker.#workerPorts?.delete(this._port);
11057 this._messageHandler?.destroy();
11058 this._messageHandler = null;
11060 static fromPort(params) {
11061 throw new Error("Not implemented: fromPort");
11063 static get workerSrc() {
11064 if (GlobalWorkerOptions.workerSrc) {
11065 return GlobalWorkerOptions.workerSrc;
11067 throw new Error('No "GlobalWorkerOptions.workerSrc" specified.');
11069 static get #mainThreadWorkerMessageHandler() {
11071 return globalThis.pdfjsWorker?.WorkerMessageHandler || null;
11076 static get _setupFakeWorkerGlobal() {
11077 const loader = async () => {
11078 if (this.#mainThreadWorkerMessageHandler) {
11079 return this.#mainThreadWorkerMessageHandler;
11081 const worker = await import(/*webpackIgnore: true*/this.workerSrc);
11082 return worker.WorkerMessageHandler;
11084 return shadow(this, "_setupFakeWorkerGlobal", loader());
11087 class WorkerTransport {
11088 #methodPromises = new Map();
11089 #pageCache = new Map();
11090 #pagePromises = new Map();
11091 #pageRefCache = new Map();
11092 #passwordCapability = null;
11093 constructor(messageHandler, loadingTask, networkStream, params, factory) {
11094 this.messageHandler = messageHandler;
11095 this.loadingTask = loadingTask;
11096 this.commonObjs = new PDFObjects();
11097 this.fontLoader = new FontLoader({
11098 ownerDocument: params.ownerDocument,
11099 styleElement: params.styleElement
11101 this.loadingParams = params.loadingParams;
11102 this._params = params;
11103 this.canvasFactory = factory.canvasFactory;
11104 this.filterFactory = factory.filterFactory;
11105 this.cMapReaderFactory = factory.cMapReaderFactory;
11106 this.standardFontDataFactory = factory.standardFontDataFactory;
11107 this.wasmFactory = factory.wasmFactory;
11108 this.destroyed = false;
11109 this.destroyCapability = null;
11110 this._networkStream = networkStream;
11111 this._fullReader = null;
11112 this._lastProgress = null;
11113 this.downloadInfoCapability = Promise.withResolvers();
11114 this.setupMessageHandler();
11116 #cacheSimpleMethod(name, data = null) {
11117 const cachedPromise = this.#methodPromises.get(name);
11118 if (cachedPromise) {
11119 return cachedPromise;
11121 const promise = this.messageHandler.sendWithPromise(name, data);
11122 this.#methodPromises.set(name, promise);
11125 get annotationStorage() {
11126 return shadow(this, "annotationStorage", new AnnotationStorage());
11128 getRenderingIntent(intent, annotationMode = AnnotationMode.ENABLE, printAnnotationStorage = null, isEditing = false, isOpList = false) {
11129 let renderingIntent = RenderingIntentFlag.DISPLAY;
11130 let annotationStorageSerializable = SerializableEmpty;
11133 renderingIntent = RenderingIntentFlag.ANY;
11138 renderingIntent = RenderingIntentFlag.PRINT;
11141 warn(`getRenderingIntent - invalid intent: ${intent}`);
11143 const annotationStorage = renderingIntent & RenderingIntentFlag.PRINT && printAnnotationStorage instanceof PrintAnnotationStorage ? printAnnotationStorage : this.annotationStorage;
11144 switch (annotationMode) {
11145 case AnnotationMode.DISABLE:
11146 renderingIntent += RenderingIntentFlag.ANNOTATIONS_DISABLE;
11148 case AnnotationMode.ENABLE:
11150 case AnnotationMode.ENABLE_FORMS:
11151 renderingIntent += RenderingIntentFlag.ANNOTATIONS_FORMS;
11153 case AnnotationMode.ENABLE_STORAGE:
11154 renderingIntent += RenderingIntentFlag.ANNOTATIONS_STORAGE;
11155 annotationStorageSerializable = annotationStorage.serializable;
11158 warn(`getRenderingIntent - invalid annotationMode: ${annotationMode}`);
11161 renderingIntent += RenderingIntentFlag.IS_EDITING;
11164 renderingIntent += RenderingIntentFlag.OPLIST;
11168 hash: modifiedIdsHash
11169 } = annotationStorage.modifiedIds;
11170 const cacheKeyBuf = [renderingIntent, annotationStorageSerializable.hash, modifiedIdsHash];
11173 cacheKey: cacheKeyBuf.join("_"),
11174 annotationStorageSerializable,
11179 if (this.destroyCapability) {
11180 return this.destroyCapability.promise;
11182 this.destroyed = true;
11183 this.destroyCapability = Promise.withResolvers();
11184 this.#passwordCapability?.reject(new Error("Worker was destroyed during onPassword callback"));
11186 for (const page of this.#pageCache.values()) {
11187 waitOn.push(page._destroy());
11189 this.#pageCache.clear();
11190 this.#pagePromises.clear();
11191 this.#pageRefCache.clear();
11192 if (this.hasOwnProperty("annotationStorage")) {
11193 this.annotationStorage.resetModified();
11195 const terminated = this.messageHandler.sendWithPromise("Terminate", null);
11196 waitOn.push(terminated);
11197 Promise.all(waitOn).then(() => {
11198 this.commonObjs.clear();
11199 this.fontLoader.clear();
11200 this.#methodPromises.clear();
11201 this.filterFactory.destroy();
11202 TextLayer.cleanup();
11203 this._networkStream?.cancelAllRequests(new AbortException("Worker was terminated."));
11204 this.messageHandler?.destroy();
11205 this.messageHandler = null;
11206 this.destroyCapability.resolve();
11207 }, this.destroyCapability.reject);
11208 return this.destroyCapability.promise;
11210 setupMessageHandler() {
11215 messageHandler.on("GetReader", (data, sink) => {
11216 assert(this._networkStream, "GetReader - no `IPDFStream` instance available.");
11217 this._fullReader = this._networkStream.getFullReader();
11218 this._fullReader.onProgress = evt => {
11219 this._lastProgress = {
11220 loaded: evt.loaded,
11224 sink.onPull = () => {
11225 this._fullReader.read().then(function ({
11233 assert(value instanceof ArrayBuffer, "GetReader - expected an ArrayBuffer.");
11234 sink.enqueue(new Uint8Array(value), 1, [value]);
11235 }).catch(reason => {
11236 sink.error(reason);
11239 sink.onCancel = reason => {
11240 this._fullReader.cancel(reason);
11241 sink.ready.catch(readyReason => {
11242 if (this.destroyed) {
11249 messageHandler.on("ReaderHeadersReady", async data => {
11250 await this._fullReader.headersReady;
11252 isStreamingSupported,
11255 } = this._fullReader;
11256 if (!isStreamingSupported || !isRangeSupported) {
11257 if (this._lastProgress) {
11258 loadingTask.onProgress?.(this._lastProgress);
11260 this._fullReader.onProgress = evt => {
11261 loadingTask.onProgress?.({
11262 loaded: evt.loaded,
11268 isStreamingSupported,
11273 messageHandler.on("GetRangeReader", (data, sink) => {
11274 assert(this._networkStream, "GetRangeReader - no `IPDFStream` instance available.");
11275 const rangeReader = this._networkStream.getRangeReader(data.begin, data.end);
11276 if (!rangeReader) {
11280 sink.onPull = () => {
11281 rangeReader.read().then(function ({
11289 assert(value instanceof ArrayBuffer, "GetRangeReader - expected an ArrayBuffer.");
11290 sink.enqueue(new Uint8Array(value), 1, [value]);
11291 }).catch(reason => {
11292 sink.error(reason);
11295 sink.onCancel = reason => {
11296 rangeReader.cancel(reason);
11297 sink.ready.catch(readyReason => {
11298 if (this.destroyed) {
11305 messageHandler.on("GetDoc", ({
11308 this._numPages = pdfInfo.numPages;
11309 this._htmlForXfa = pdfInfo.htmlForXfa;
11310 delete pdfInfo.htmlForXfa;
11311 loadingTask._capability.resolve(new PDFDocumentProxy(pdfInfo, this));
11313 messageHandler.on("DocException", ex => {
11314 loadingTask._capability.reject(wrapReason(ex));
11316 messageHandler.on("PasswordRequest", ex => {
11317 this.#passwordCapability = Promise.withResolvers();
11319 if (!loadingTask.onPassword) {
11320 throw wrapReason(ex);
11322 const updatePassword = password => {
11323 if (password instanceof Error) {
11324 this.#passwordCapability.reject(password);
11326 this.#passwordCapability.resolve({
11331 loadingTask.onPassword(updatePassword, ex.code);
11333 this.#passwordCapability.reject(err);
11335 return this.#passwordCapability.promise;
11337 messageHandler.on("DataLoaded", data => {
11338 loadingTask.onProgress?.({
11339 loaded: data.length,
11342 this.downloadInfoCapability.resolve(data);
11344 messageHandler.on("StartRenderPage", data => {
11345 if (this.destroyed) {
11348 const page = this.#pageCache.get(data.pageIndex);
11349 page._startRenderPage(data.transparency, data.cacheKey);
11351 messageHandler.on("commonobj", ([id, type, exportedData]) => {
11352 if (this.destroyed) {
11355 if (this.commonObjs.has(id)) {
11362 fontExtraProperties,
11365 if ("error" in exportedData) {
11366 const exportedError = exportedData.error;
11367 warn(`Error during font loading: ${exportedError}`);
11368 this.commonObjs.resolve(id, exportedError);
11371 const inspectFont = pdfBug && globalThis.FontInspector?.enabled ? (font, url) => globalThis.FontInspector.fontAdded(font, url) : null;
11372 const font = new FontFaceObject(exportedData, {
11374 fontExtraProperties,
11377 this.fontLoader.bind(font).catch(() => messageHandler.sendWithPromise("FontFallback", {
11379 })).finally(() => {
11380 if (!fontExtraProperties && font.data) {
11383 this.commonObjs.resolve(id, font);
11386 case "CopyLocalImage":
11390 assert(imageRef, "The imageRef must be defined.");
11391 for (const pageProxy of this.#pageCache.values()) {
11392 for (const [, data] of pageProxy.objs) {
11393 if (data?.ref !== imageRef) {
11396 if (!data.dataLen) {
11399 this.commonObjs.resolve(id, structuredClone(data));
11400 return data.dataLen;
11407 this.commonObjs.resolve(id, exportedData);
11410 throw new Error(`Got unknown common object type ${type}`);
11414 messageHandler.on("obj", ([id, pageIndex, type, imageData]) => {
11415 if (this.destroyed) {
11418 const pageProxy = this.#pageCache.get(pageIndex);
11419 if (pageProxy.objs.has(id)) {
11422 if (pageProxy._intentStates.size === 0) {
11423 imageData?.bitmap?.close();
11429 pageProxy.objs.resolve(id, imageData);
11432 throw new Error(`Got unknown object type ${type}`);
11435 messageHandler.on("DocProgress", data => {
11436 if (this.destroyed) {
11439 loadingTask.onProgress?.({
11440 loaded: data.loaded,
11444 messageHandler.on("FetchBuiltInCMap", async data => {
11445 throw new Error("Not implemented: FetchBuiltInCMap");
11447 messageHandler.on("FetchStandardFontData", async data => {
11448 throw new Error("Not implemented: FetchStandardFontData");
11450 messageHandler.on("FetchWasm", async data => {
11451 throw new Error("Not implemented: FetchWasm");
11455 return this.messageHandler.sendWithPromise("GetData", null);
11458 if (this.annotationStorage.size <= 0) {
11459 warn("saveDocument called while `annotationStorage` is empty, " + "please use the getData-method instead.");
11464 } = this.annotationStorage.serializable;
11465 return this.messageHandler.sendWithPromise("SaveDocument", {
11466 isPureXfa: !!this._htmlForXfa,
11467 numPages: this._numPages,
11468 annotationStorage: map,
11469 filename: this._fullReader?.filename ?? null
11470 }, transfer).finally(() => {
11471 this.annotationStorage.resetModified();
11474 getPage(pageNumber) {
11475 if (!Number.isInteger(pageNumber) || pageNumber <= 0 || pageNumber > this._numPages) {
11476 return Promise.reject(new Error("Invalid page request."));
11478 const pageIndex = pageNumber - 1,
11479 cachedPromise = this.#pagePromises.get(pageIndex);
11480 if (cachedPromise) {
11481 return cachedPromise;
11483 const promise = this.messageHandler.sendWithPromise("GetPage", {
11485 }).then(pageInfo => {
11486 if (this.destroyed) {
11487 throw new Error("Transport destroyed");
11489 if (pageInfo.refStr) {
11490 this.#pageRefCache.set(pageInfo.refStr, pageNumber);
11492 const page = new PDFPageProxy(pageIndex, pageInfo, this, this._params.pdfBug);
11493 this.#pageCache.set(pageIndex, page);
11496 this.#pagePromises.set(pageIndex, promise);
11499 getPageIndex(ref) {
11500 if (!isRefProxy(ref)) {
11501 return Promise.reject(new Error("Invalid pageIndex request."));
11503 return this.messageHandler.sendWithPromise("GetPageIndex", {
11508 getAnnotations(pageIndex, intent) {
11509 return this.messageHandler.sendWithPromise("GetAnnotations", {
11514 getFieldObjects() {
11515 return this.#cacheSimpleMethod("GetFieldObjects");
11518 return this.#cacheSimpleMethod("HasJSActions");
11520 getCalculationOrderIds() {
11521 return this.messageHandler.sendWithPromise("GetCalculationOrderIds", null);
11523 getDestinations() {
11524 return this.messageHandler.sendWithPromise("GetDestinations", null);
11526 getDestination(id) {
11527 if (typeof id !== "string") {
11528 return Promise.reject(new Error("Invalid destination request."));
11530 return this.messageHandler.sendWithPromise("GetDestination", {
11535 return this.messageHandler.sendWithPromise("GetPageLabels", null);
11538 return this.messageHandler.sendWithPromise("GetPageLayout", null);
11541 return this.messageHandler.sendWithPromise("GetPageMode", null);
11543 getViewerPreferences() {
11544 return this.messageHandler.sendWithPromise("GetViewerPreferences", null);
11547 return this.messageHandler.sendWithPromise("GetOpenAction", null);
11550 return this.messageHandler.sendWithPromise("GetAttachments", null);
11552 getDocJSActions() {
11553 return this.#cacheSimpleMethod("GetDocJSActions");
11555 getPageJSActions(pageIndex) {
11556 return this.messageHandler.sendWithPromise("GetPageJSActions", {
11560 getStructTree(pageIndex) {
11561 return this.messageHandler.sendWithPromise("GetStructTree", {
11566 return this.messageHandler.sendWithPromise("GetOutline", null);
11568 getOptionalContentConfig(renderingIntent) {
11569 return this.#cacheSimpleMethod("GetOptionalContentConfig").then(data => new OptionalContentConfig(data, renderingIntent));
11572 return this.messageHandler.sendWithPromise("GetPermissions", null);
11575 const name = "GetMetadata",
11576 cachedPromise = this.#methodPromises.get(name);
11577 if (cachedPromise) {
11578 return cachedPromise;
11580 const promise = this.messageHandler.sendWithPromise(name, null).then(results => ({
11582 metadata: results[1] ? new Metadata(results[1]) : null,
11583 contentDispositionFilename: this._fullReader?.filename ?? null,
11584 contentLength: this._fullReader?.contentLength ?? null
11586 this.#methodPromises.set(name, promise);
11590 return this.messageHandler.sendWithPromise("GetMarkInfo", null);
11592 async startCleanup(keepLoadedFonts = false) {
11593 if (this.destroyed) {
11596 await this.messageHandler.sendWithPromise("Cleanup", null);
11597 for (const page of this.#pageCache.values()) {
11598 const cleanupSuccessful = page.cleanup();
11599 if (!cleanupSuccessful) {
11600 throw new Error(`startCleanup: Page ${page.pageNumber} is currently rendering.`);
11603 this.commonObjs.clear();
11604 if (!keepLoadedFonts) {
11605 this.fontLoader.clear();
11607 this.#methodPromises.clear();
11608 this.filterFactory.destroy(true);
11609 TextLayer.cleanup();
11611 cachedPageNumber(ref) {
11612 if (!isRefProxy(ref)) {
11615 const refStr = ref.gen === 0 ? `${ref.num}R` : `${ref.num}R${ref.gen}`;
11616 return this.#pageRefCache.get(refStr) ?? null;
11619 const INITIAL_DATA = Symbol("INITIAL_DATA");
11621 #objs = Object.create(null);
11622 #ensureObj(objId) {
11623 return this.#objs[objId] ||= {
11624 ...Promise.withResolvers(),
11628 get(objId, callback = null) {
11630 const obj = this.#ensureObj(objId);
11631 obj.promise.then(() => callback(obj.data));
11634 const obj = this.#objs[objId];
11635 if (!obj || obj.data === INITIAL_DATA) {
11636 throw new Error(`Requesting object that isn't resolved yet ${objId}.`);
11641 const obj = this.#objs[objId];
11642 return !!obj && obj.data !== INITIAL_DATA;
11645 const obj = this.#objs[objId];
11646 if (!obj || obj.data === INITIAL_DATA) {
11649 delete this.#objs[objId];
11652 resolve(objId, data = null) {
11653 const obj = this.#ensureObj(objId);
11658 for (const objId in this.#objs) {
11661 } = this.#objs[objId];
11662 data?.bitmap?.close();
11664 this.#objs = Object.create(null);
11666 *[Symbol.iterator]() {
11667 for (const objId in this.#objs) {
11670 } = this.#objs[objId];
11671 if (data === INITIAL_DATA) {
11674 yield [objId, data];
11679 #internalRenderTask = null;
11680 constructor(internalRenderTask) {
11681 this.#internalRenderTask = internalRenderTask;
11682 this.onContinue = null;
11685 return this.#internalRenderTask.capability.promise;
11687 cancel(extraDelay = 0) {
11688 this.#internalRenderTask.cancel(null, extraDelay);
11690 get separateAnnots() {
11693 } = this.#internalRenderTask.operatorList;
11694 if (!separateAnnots) {
11698 annotationCanvasMap
11699 } = this.#internalRenderTask;
11700 return separateAnnots.form || separateAnnots.canvas && annotationCanvasMap?.size > 0;
11703 class InternalRenderTask {
11705 static #canvasInUse = new WeakSet();
11711 annotationCanvasMap,
11716 useRequestAnimationFrame = false,
11720 this.callback = callback;
11721 this.params = params;
11723 this.commonObjs = commonObjs;
11724 this.annotationCanvasMap = annotationCanvasMap;
11725 this.operatorListIdx = null;
11726 this.operatorList = operatorList;
11727 this._pageIndex = pageIndex;
11728 this.canvasFactory = canvasFactory;
11729 this.filterFactory = filterFactory;
11730 this._pdfBug = pdfBug;
11731 this.pageColors = pageColors;
11732 this.running = false;
11733 this.graphicsReadyCallback = null;
11734 this.graphicsReady = false;
11735 this._useRequestAnimationFrame = useRequestAnimationFrame === true && typeof window !== "undefined";
11736 this.cancelled = false;
11737 this.capability = Promise.withResolvers();
11738 this.task = new RenderTask(this);
11739 this._cancelBound = this.cancel.bind(this);
11740 this._continueBound = this._continue.bind(this);
11741 this._scheduleNextBound = this._scheduleNext.bind(this);
11742 this._nextBound = this._next.bind(this);
11743 this._canvas = params.canvasContext.canvas;
11746 return this.capability.promise.catch(function () {});
11748 initializeGraphics({
11749 transparency = false,
11750 optionalContentConfig
11752 if (this.cancelled) {
11755 if (this._canvas) {
11756 if (InternalRenderTask.#canvasInUse.has(this._canvas)) {
11757 throw new Error("Cannot use the same canvas during multiple render() operations. " + "Use different canvas or ensure previous operations were " + "cancelled or completed.");
11759 InternalRenderTask.#canvasInUse.add(this._canvas);
11761 if (this._pdfBug && globalThis.StepperManager?.enabled) {
11762 this.stepper = globalThis.StepperManager.create(this._pageIndex);
11763 this.stepper.init(this.operatorList);
11764 this.stepper.nextBreakPoint = this.stepper.getNextBreakPoint();
11772 this.gfx = new CanvasGraphics(canvasContext, this.commonObjs, this.objs, this.canvasFactory, this.filterFactory, {
11773 optionalContentConfig
11774 }, this.annotationCanvasMap, this.pageColors);
11775 this.gfx.beginDrawing({
11781 this.operatorListIdx = 0;
11782 this.graphicsReady = true;
11783 this.graphicsReadyCallback?.();
11785 cancel(error = null, extraDelay = 0) {
11786 this.running = false;
11787 this.cancelled = true;
11788 this.gfx?.endDrawing();
11790 window.cancelAnimationFrame(this.#rAF);
11793 InternalRenderTask.#canvasInUse.delete(this._canvas);
11794 this.callback(error || new RenderingCancelledException(`Rendering cancelled, page ${this._pageIndex + 1}`, extraDelay));
11796 operatorListChanged() {
11797 if (!this.graphicsReady) {
11798 this.graphicsReadyCallback ||= this._continueBound;
11801 this.stepper?.updateOperatorList(this.operatorList);
11802 if (this.running) {
11808 this.running = true;
11809 if (this.cancelled) {
11812 if (this.task.onContinue) {
11813 this.task.onContinue(this._scheduleNextBound);
11815 this._scheduleNext();
11819 if (this._useRequestAnimationFrame) {
11820 this.#rAF = window.requestAnimationFrame(() => {
11822 this._nextBound().catch(this._cancelBound);
11825 Promise.resolve().then(this._nextBound).catch(this._cancelBound);
11829 if (this.cancelled) {
11832 this.operatorListIdx = this.gfx.executeOperatorList(this.operatorList, this.operatorListIdx, this._continueBound, this.stepper);
11833 if (this.operatorListIdx === this.operatorList.argsArray.length) {
11834 this.running = false;
11835 if (this.operatorList.lastChunk) {
11836 this.gfx.endDrawing();
11837 InternalRenderTask.#canvasInUse.delete(this._canvas);
11843 const version = "5.0.98";
11844 const build = "16155fd80";
11846 ;// ./src/shared/scripting_utils.js
11847 function makeColorComp(n) {
11848 return Math.floor(Math.max(0, Math.min(1, n)) * 255).toString(16).padStart(2, "0");
11850 function scaleAndClamp(x) {
11851 return Math.max(0, Math.min(255, 255 * x));
11853 class ColorConverters {
11854 static CMYK_G([c, y, m, k]) {
11855 return ["G", 1 - Math.min(1, 0.3 * c + 0.59 * m + 0.11 * y + k)];
11857 static G_CMYK([g]) {
11858 return ["CMYK", 0, 0, 0, 1 - g];
11860 static G_RGB([g]) {
11861 return ["RGB", g, g, g];
11863 static G_rgb([g]) {
11864 g = scaleAndClamp(g);
11867 static G_HTML([g]) {
11868 const G = makeColorComp(g);
11869 return `#${G}${G}${G}`;
11871 static RGB_G([r, g, b]) {
11872 return ["G", 0.3 * r + 0.59 * g + 0.11 * b];
11874 static RGB_rgb(color) {
11875 return color.map(scaleAndClamp);
11877 static RGB_HTML(color) {
11878 return `#${color.map(makeColorComp).join("")}`;
11881 return "#00000000";
11886 static CMYK_RGB([c, y, m, k]) {
11887 return ["RGB", 1 - Math.min(1, c + k), 1 - Math.min(1, m + k), 1 - Math.min(1, y + k)];
11889 static CMYK_rgb([c, y, m, k]) {
11890 return [scaleAndClamp(1 - Math.min(1, c + k)), scaleAndClamp(1 - Math.min(1, m + k)), scaleAndClamp(1 - Math.min(1, y + k))];
11892 static CMYK_HTML(components) {
11893 const rgb = this.CMYK_RGB(components).slice(1);
11894 return this.RGB_HTML(rgb);
11896 static RGB_CMYK([r, g, b]) {
11900 const k = Math.min(c, m, y);
11901 return ["CMYK", c, m, y, k];
11905 ;// ./src/display/svg_factory.js
11908 class BaseSVGFactory {
11909 create(width, height, skipDimensions = false) {
11910 if (width <= 0 || height <= 0) {
11911 throw new Error("Invalid SVG dimensions");
11913 const svg = this._createSVG("svg:svg");
11914 svg.setAttribute("version", "1.1");
11915 if (!skipDimensions) {
11916 svg.setAttribute("width", `${width}px`);
11917 svg.setAttribute("height", `${height}px`);
11919 svg.setAttribute("preserveAspectRatio", "none");
11920 svg.setAttribute("viewBox", `0 0 ${width} ${height}`);
11923 createElement(type) {
11924 if (typeof type !== "string") {
11925 throw new Error("Invalid SVG element type");
11927 return this._createSVG(type);
11930 unreachable("Abstract method `_createSVG` called.");
11933 class DOMSVGFactory extends BaseSVGFactory {
11935 return document.createElementNS(SVG_NS, type);
11939 ;// ./src/display/xfa_layer.js
11942 static setupStorage(html, id, element, storage, intent) {
11943 const storedData = storage.getValue(id, {
11946 switch (element.name) {
11948 if (storedData.value !== null) {
11949 html.textContent = storedData.value;
11951 if (intent === "print") {
11954 html.addEventListener("input", event => {
11955 storage.setValue(id, {
11956 value: event.target.value
11961 if (element.attributes.type === "radio" || element.attributes.type === "checkbox") {
11962 if (storedData.value === element.attributes.xfaOn) {
11963 html.setAttribute("checked", true);
11964 } else if (storedData.value === element.attributes.xfaOff) {
11965 html.removeAttribute("checked");
11967 if (intent === "print") {
11970 html.addEventListener("change", event => {
11971 storage.setValue(id, {
11972 value: event.target.checked ? event.target.getAttribute("xfaOn") : event.target.getAttribute("xfaOff")
11976 if (storedData.value !== null) {
11977 html.setAttribute("value", storedData.value);
11979 if (intent === "print") {
11982 html.addEventListener("input", event => {
11983 storage.setValue(id, {
11984 value: event.target.value
11990 if (storedData.value !== null) {
11991 html.setAttribute("value", storedData.value);
11992 for (const option of element.children) {
11993 if (option.attributes.value === storedData.value) {
11994 option.attributes.selected = true;
11995 } else if (option.attributes.hasOwnProperty("selected")) {
11996 delete option.attributes.selected;
12000 html.addEventListener("input", event => {
12001 const options = event.target.options;
12002 const value = options.selectedIndex === -1 ? "" : options[options.selectedIndex].value;
12003 storage.setValue(id, {
12010 static setAttributes({
12020 const isHTMLAnchorElement = html instanceof HTMLAnchorElement;
12021 if (attributes.type === "radio") {
12022 attributes.name = `${attributes.name}-${intent}`;
12024 for (const [key, value] of Object.entries(attributes)) {
12025 if (value === null || value === undefined) {
12030 if (value.length) {
12031 html.setAttribute(key, value.join(" "));
12037 html.setAttribute("data-element-id", value);
12040 Object.assign(html.style, value);
12042 case "textContent":
12043 html.textContent = value;
12046 if (!isHTMLAnchorElement || key !== "href" && key !== "newWindow") {
12047 html.setAttribute(key, value);
12051 if (isHTMLAnchorElement) {
12052 linkService.addLinkAttributes(html, attributes.href, attributes.newWindow);
12054 if (storage && attributes.dataId) {
12055 this.setupStorage(html, attributes.dataId, element, storage);
12058 static render(parameters) {
12059 const storage = parameters.annotationStorage;
12060 const linkService = parameters.linkService;
12061 const root = parameters.xfaHtml;
12062 const intent = parameters.intent || "display";
12063 const rootHtml = document.createElement(root.name);
12064 if (root.attributes) {
12065 this.setAttributes({
12072 const isNotForRichText = intent !== "richText";
12073 const rootDiv = parameters.div;
12074 rootDiv.append(rootHtml);
12075 if (parameters.viewport) {
12076 const transform = `matrix(${parameters.viewport.transform.join(",")})`;
12077 rootDiv.style.transform = transform;
12079 if (isNotForRichText) {
12080 rootDiv.setAttribute("class", "xfaLayer xfaFont");
12082 const textDivs = [];
12083 if (root.children.length === 0) {
12085 const node = document.createTextNode(root.value);
12086 rootHtml.append(node);
12087 if (isNotForRichText && XfaText.shouldBuildText(root.name)) {
12088 textDivs.push(node);
12095 const stack = [[root, -1, rootHtml]];
12096 while (stack.length > 0) {
12097 const [parent, i, html] = stack.at(-1);
12098 if (i + 1 === parent.children.length) {
12102 const child = parent.children[++stack.at(-1)[1]];
12103 if (child === null) {
12109 if (name === "#text") {
12110 const node = document.createTextNode(child.value);
12111 textDivs.push(node);
12115 const childHtml = child?.attributes?.xmlns ? document.createElementNS(child.attributes.xmlns, name) : document.createElement(name);
12116 html.append(childHtml);
12117 if (child.attributes) {
12118 this.setAttributes({
12126 if (child.children?.length > 0) {
12127 stack.push([child, -1, childHtml]);
12128 } else if (child.value) {
12129 const node = document.createTextNode(child.value);
12130 if (isNotForRichText && XfaText.shouldBuildText(name)) {
12131 textDivs.push(node);
12133 childHtml.append(node);
12136 for (const el of rootDiv.querySelectorAll(".xfaNonInteractive input, .xfaNonInteractive textarea")) {
12137 el.setAttribute("readOnly", true);
12143 static update(parameters) {
12144 const transform = `matrix(${parameters.viewport.transform.join(",")})`;
12145 parameters.div.style.transform = transform;
12146 parameters.div.hidden = false;
12150 ;// ./src/display/annotation_layer.js
12157 const DEFAULT_TAB_INDEX = 1000;
12158 const annotation_layer_DEFAULT_FONT_SIZE = 9;
12159 const GetElementsByNameSet = new WeakSet();
12160 class AnnotationElementFactory {
12161 static create(parameters) {
12162 const subtype = parameters.data.annotationType;
12164 case AnnotationType.LINK:
12165 return new LinkAnnotationElement(parameters);
12166 case AnnotationType.TEXT:
12167 return new TextAnnotationElement(parameters);
12168 case AnnotationType.WIDGET:
12169 const fieldType = parameters.data.fieldType;
12170 switch (fieldType) {
12172 return new TextWidgetAnnotationElement(parameters);
12174 if (parameters.data.radioButton) {
12175 return new RadioButtonWidgetAnnotationElement(parameters);
12176 } else if (parameters.data.checkBox) {
12177 return new CheckboxWidgetAnnotationElement(parameters);
12179 return new PushButtonWidgetAnnotationElement(parameters);
12181 return new ChoiceWidgetAnnotationElement(parameters);
12183 return new SignatureWidgetAnnotationElement(parameters);
12185 return new WidgetAnnotationElement(parameters);
12186 case AnnotationType.POPUP:
12187 return new PopupAnnotationElement(parameters);
12188 case AnnotationType.FREETEXT:
12189 return new FreeTextAnnotationElement(parameters);
12190 case AnnotationType.LINE:
12191 return new LineAnnotationElement(parameters);
12192 case AnnotationType.SQUARE:
12193 return new SquareAnnotationElement(parameters);
12194 case AnnotationType.CIRCLE:
12195 return new CircleAnnotationElement(parameters);
12196 case AnnotationType.POLYLINE:
12197 return new PolylineAnnotationElement(parameters);
12198 case AnnotationType.CARET:
12199 return new CaretAnnotationElement(parameters);
12200 case AnnotationType.INK:
12201 return new InkAnnotationElement(parameters);
12202 case AnnotationType.POLYGON:
12203 return new PolygonAnnotationElement(parameters);
12204 case AnnotationType.HIGHLIGHT:
12205 return new HighlightAnnotationElement(parameters);
12206 case AnnotationType.UNDERLINE:
12207 return new UnderlineAnnotationElement(parameters);
12208 case AnnotationType.SQUIGGLY:
12209 return new SquigglyAnnotationElement(parameters);
12210 case AnnotationType.STRIKEOUT:
12211 return new StrikeOutAnnotationElement(parameters);
12212 case AnnotationType.STAMP:
12213 return new StampAnnotationElement(parameters);
12214 case AnnotationType.FILEATTACHMENT:
12215 return new FileAttachmentAnnotationElement(parameters);
12217 return new AnnotationElement(parameters);
12221 class AnnotationElement {
12223 #hasBorder = false;
12224 #popupElement = null;
12225 constructor(parameters, {
12226 isRenderable = false,
12227 ignoreBorder = false,
12228 createQuadrilaterals = false
12230 this.isRenderable = isRenderable;
12231 this.data = parameters.data;
12232 this.layer = parameters.layer;
12233 this.linkService = parameters.linkService;
12234 this.downloadManager = parameters.downloadManager;
12235 this.imageResourcesPath = parameters.imageResourcesPath;
12236 this.renderForms = parameters.renderForms;
12237 this.svgFactory = parameters.svgFactory;
12238 this.annotationStorage = parameters.annotationStorage;
12239 this.enableScripting = parameters.enableScripting;
12240 this.hasJSActions = parameters.hasJSActions;
12241 this._fieldObjects = parameters.fieldObjects;
12242 this.parent = parameters.parent;
12243 if (isRenderable) {
12244 this.container = this._createContainer(ignoreBorder);
12246 if (createQuadrilaterals) {
12247 this._createQuadrilaterals();
12250 static _hasPopupData({
12255 return !!(titleObj?.str || contentsObj?.str || richText?.str);
12257 get _isEditable() {
12258 return this.data.isEditable;
12260 get hasPopupData() {
12261 return AnnotationElement._hasPopupData(this.data);
12263 updateEdited(params) {
12264 if (!this.container) {
12267 this.#updates ||= {
12268 rect: this.data.rect.slice(0)
12274 this.#setRectEdited(rect);
12276 this.#popupElement?.popup.updateEdited(params);
12279 if (!this.#updates) {
12282 this.#setRectEdited(this.#updates.rect);
12283 this.#popupElement?.popup.resetEdited();
12284 this.#updates = null;
12286 #setRectEdited(rect) {
12306 currentRect?.splice(0, 4, ...rect);
12307 style.left = `${100 * (rect[0] - pageX) / pageWidth}%`;
12308 style.top = `${100 * (pageHeight - rect[3] + pageY) / pageHeight}%`;
12309 if (rotation === 0) {
12310 style.width = `${100 * (rect[2] - rect[0]) / pageWidth}%`;
12311 style.height = `${100 * (rect[3] - rect[1]) / pageHeight}%`;
12313 this.setRotation(rotation);
12316 _createContainer(ignoreBorder) {
12324 const container = document.createElement("section");
12325 container.setAttribute("data-annotation-id", data.id);
12326 if (!(this instanceof WidgetAnnotationElement)) {
12327 container.tabIndex = DEFAULT_TAB_INDEX;
12332 style.zIndex = this.parent.zIndex++;
12333 if (data.alternativeText) {
12334 container.title = data.alternativeText;
12336 if (data.noRotate) {
12337 container.classList.add("norotate");
12339 if (!data.rect || this instanceof PopupAnnotationElement) {
12343 if (!data.hasOwnCanvas && rotation !== 0) {
12344 this.setRotation(rotation, container);
12352 if (!ignoreBorder && data.borderStyle.width > 0) {
12353 style.borderWidth = `${data.borderStyle.width}px`;
12354 const horizontalRadius = data.borderStyle.horizontalCornerRadius;
12355 const verticalRadius = data.borderStyle.verticalCornerRadius;
12356 if (horizontalRadius > 0 || verticalRadius > 0) {
12357 const radius = `calc(${horizontalRadius}px * var(--scale-factor)) / calc(${verticalRadius}px * var(--scale-factor))`;
12358 style.borderRadius = radius;
12359 } else if (this instanceof RadioButtonWidgetAnnotationElement) {
12360 const radius = `calc(${width}px * var(--scale-factor)) / calc(${height}px * var(--scale-factor))`;
12361 style.borderRadius = radius;
12363 switch (data.borderStyle.style) {
12364 case AnnotationBorderStyleType.SOLID:
12365 style.borderStyle = "solid";
12367 case AnnotationBorderStyleType.DASHED:
12368 style.borderStyle = "dashed";
12370 case AnnotationBorderStyleType.BEVELED:
12371 warn("Unimplemented border style: beveled");
12373 case AnnotationBorderStyleType.INSET:
12374 warn("Unimplemented border style: inset");
12376 case AnnotationBorderStyleType.UNDERLINE:
12377 style.borderBottomStyle = "solid";
12382 const borderColor = data.borderColor || null;
12384 this.#hasBorder = true;
12385 style.borderColor = Util.makeHexColor(borderColor[0] | 0, borderColor[1] | 0, borderColor[2] | 0);
12387 style.borderWidth = 0;
12390 const rect = Util.normalizeRect([data.rect[0], page.view[3] - data.rect[1] + page.view[1], data.rect[2], page.view[3] - data.rect[3] + page.view[1]]);
12396 } = viewport.rawDims;
12397 style.left = `${100 * (rect[0] - pageX) / pageWidth}%`;
12398 style.top = `${100 * (rect[1] - pageY) / pageHeight}%`;
12402 if (data.hasOwnCanvas || rotation === 0) {
12403 style.width = `${100 * width / pageWidth}%`;
12404 style.height = `${100 * height / pageHeight}%`;
12406 this.setRotation(rotation, container);
12410 setRotation(angle, container = this.container) {
12411 if (!this.data.rect) {
12417 } = this.parent.viewport.rawDims;
12422 if (angle % 180 !== 0) {
12423 [width, height] = [height, width];
12425 container.style.width = `${100 * width / pageWidth}%`;
12426 container.style.height = `${100 * height / pageHeight}%`;
12427 container.setAttribute("data-main-rotation", (360 - angle) % 360);
12429 get _commonActions() {
12430 const setColor = (jsName, styleName, event) => {
12431 const color = event.detail[jsName];
12432 const colorType = color[0];
12433 const colorArray = color.slice(1);
12434 event.target.style[styleName] = ColorConverters[`${colorType}_HTML`](colorArray);
12435 this.annotationStorage.setValue(this.data.id, {
12436 [styleName]: ColorConverters[`${colorType}_rgb`](colorArray)
12439 return shadow(this, "_commonActions", {
12440 display: event => {
12444 const hidden = display % 2 === 1;
12445 this.container.style.visibility = hidden ? "hidden" : "visible";
12446 this.annotationStorage.setValue(this.data.id, {
12448 noPrint: display === 1 || display === 2
12452 this.annotationStorage.setValue(this.data.id, {
12453 noPrint: !event.detail.print
12460 this.container.style.visibility = hidden ? "hidden" : "visible";
12461 this.annotationStorage.setValue(this.data.id, {
12467 setTimeout(() => event.target.focus({
12468 preventScroll: false
12471 userName: event => {
12472 event.target.title = event.detail.userName;
12474 readonly: event => {
12475 event.target.disabled = event.detail.readonly;
12477 required: event => {
12478 this._setRequired(event.target, event.detail.required);
12480 bgColor: event => {
12481 setColor("bgColor", "backgroundColor", event);
12483 fillColor: event => {
12484 setColor("fillColor", "backgroundColor", event);
12486 fgColor: event => {
12487 setColor("fgColor", "color", event);
12489 textColor: event => {
12490 setColor("textColor", "color", event);
12492 borderColor: event => {
12493 setColor("borderColor", "borderColor", event);
12495 strokeColor: event => {
12496 setColor("strokeColor", "borderColor", event);
12498 rotation: event => {
12499 const angle = event.detail.rotation;
12500 this.setRotation(angle);
12501 this.annotationStorage.setValue(this.data.id, {
12507 _dispatchEventFromSandbox(actions, jsEvent) {
12508 const commonActions = this._commonActions;
12509 for (const name of Object.keys(jsEvent.detail)) {
12510 const action = actions[name] || commonActions[name];
12514 _setDefaultPropertiesFromJS(element) {
12515 if (!this.enableScripting) {
12518 const storedData = this.annotationStorage.getRawValue(this.data.id);
12522 const commonActions = this._commonActions;
12523 for (const [actionName, detail] of Object.entries(storedData)) {
12524 const action = commonActions[actionName];
12526 const eventProxy = {
12528 [actionName]: detail
12532 action(eventProxy);
12533 delete storedData[actionName];
12537 _createQuadrilaterals() {
12538 if (!this.container) {
12547 const [rectBlX, rectBlY, rectTrX, rectTrY] = this.data.rect.map(x => Math.fround(x));
12548 if (quadPoints.length === 8) {
12549 const [trX, trY, blX, blY] = quadPoints.subarray(2, 6);
12550 if (rectTrX === trX && rectTrY === trY && rectBlX === blX && rectBlY === blY) {
12556 } = this.container;
12558 if (this.#hasBorder) {
12563 style.borderWidth = 0;
12564 svgBuffer = ["url('data:image/svg+xml;utf8,", `<svg xmlns="http://www.w3.org/2000/svg"`, ` preserveAspectRatio="none" viewBox="0 0 1 1">`, `<g fill="transparent" stroke="${borderColor}" stroke-width="${borderWidth}">`];
12565 this.container.classList.add("hasBorder");
12567 const width = rectTrX - rectBlX;
12568 const height = rectTrY - rectBlY;
12572 const svg = svgFactory.createElement("svg");
12573 svg.classList.add("quadrilateralsContainer");
12574 svg.setAttribute("width", 0);
12575 svg.setAttribute("height", 0);
12576 const defs = svgFactory.createElement("defs");
12578 const clipPath = svgFactory.createElement("clipPath");
12579 const id = `clippath_${this.data.id}`;
12580 clipPath.setAttribute("id", id);
12581 clipPath.setAttribute("clipPathUnits", "objectBoundingBox");
12582 defs.append(clipPath);
12583 for (let i = 2, ii = quadPoints.length; i < ii; i += 8) {
12584 const trX = quadPoints[i];
12585 const trY = quadPoints[i + 1];
12586 const blX = quadPoints[i + 2];
12587 const blY = quadPoints[i + 3];
12588 const rect = svgFactory.createElement("rect");
12589 const x = (blX - rectBlX) / width;
12590 const y = (rectTrY - trY) / height;
12591 const rectWidth = (trX - blX) / width;
12592 const rectHeight = (trY - blY) / height;
12593 rect.setAttribute("x", x);
12594 rect.setAttribute("y", y);
12595 rect.setAttribute("width", rectWidth);
12596 rect.setAttribute("height", rectHeight);
12597 clipPath.append(rect);
12598 svgBuffer?.push(`<rect vector-effect="non-scaling-stroke" x="${x}" y="${y}" width="${rectWidth}" height="${rectHeight}"/>`);
12600 if (this.#hasBorder) {
12601 svgBuffer.push(`</g></svg>')`);
12602 style.backgroundImage = svgBuffer.join("");
12604 this.container.append(svg);
12605 this.container.style.clipPath = `url(#${id})`;
12611 const popup = this.#popupElement = new PopupAnnotationElement({
12614 titleObj: data.titleObj,
12615 modificationDate: data.modificationDate,
12616 contentsObj: data.contentsObj,
12617 richText: data.richText,
12618 parentRect: data.rect,
12620 id: `popup_${data.id}`,
12621 rotation: data.rotation
12623 parent: this.parent,
12626 this.parent.div.append(popup.render());
12629 unreachable("Abstract method `AnnotationElement.render` called");
12631 _getElementsByName(name, skipId = null) {
12633 if (this._fieldObjects) {
12634 const fieldObj = this._fieldObjects[name];
12644 if (id === skipId) {
12647 const exportValue = typeof exportValues === "string" ? exportValues : null;
12648 const domElement = document.querySelector(`[data-element-id="${id}"]`);
12649 if (domElement && !GetElementsByNameSet.has(domElement)) {
12650 warn(`_getElementsByName - element not allowed: ${id}`);
12662 for (const domElement of document.getElementsByName(name)) {
12666 const id = domElement.getAttribute("data-element-id");
12667 if (id === skipId) {
12670 if (!GetElementsByNameSet.has(domElement)) {
12682 if (this.container) {
12683 this.container.hidden = false;
12685 this.popup?.maybeShow();
12688 if (this.container) {
12689 this.container.hidden = true;
12691 this.popup?.forceHide();
12693 getElementsToTriggerPopup() {
12694 return this.container;
12696 addHighlightArea() {
12697 const triggers = this.getElementsToTriggerPopup();
12698 if (Array.isArray(triggers)) {
12699 for (const element of triggers) {
12700 element.classList.add("highlightArea");
12703 triggers.classList.add("highlightArea");
12706 _editOnDoubleClick() {
12707 if (!this._isEditable) {
12711 annotationEditorType: mode,
12716 this.container.addEventListener("dblclick", () => {
12717 this.linkService.eventBus?.dispatch("switchannotationeditormode", {
12725 return this.data.rect[2] - this.data.rect[0];
12728 return this.data.rect[3] - this.data.rect[1];
12731 class LinkAnnotationElement extends AnnotationElement {
12732 constructor(parameters, options = null) {
12733 super(parameters, {
12734 isRenderable: true,
12735 ignoreBorder: !!options?.ignoreBorder,
12736 createQuadrilaterals: true
12738 this.isTooltipOnly = parameters.data.isTooltipOnly;
12745 const link = document.createElement("a");
12746 link.setAttribute("data-element-id", data.id);
12747 let isBound = false;
12749 linkService.addLinkAttributes(link, data.url, data.newWindow);
12751 } else if (data.action) {
12752 this._bindNamedAction(link, data.action);
12754 } else if (data.attachment) {
12755 this.#bindAttachment(link, data.attachment, data.attachmentDest);
12757 } else if (data.setOCGState) {
12758 this.#bindSetOCGState(link, data.setOCGState);
12760 } else if (data.dest) {
12761 this._bindLink(link, data.dest);
12764 if (data.actions && (data.actions.Action || data.actions["Mouse Up"] || data.actions["Mouse Down"]) && this.enableScripting && this.hasJSActions) {
12765 this._bindJSAction(link, data);
12768 if (data.resetForm) {
12769 this._bindResetFormAction(link, data.resetForm);
12771 } else if (this.isTooltipOnly && !isBound) {
12772 this._bindLink(link, "");
12776 this.container.classList.add("linkAnnotation");
12778 this.container.append(link);
12780 return this.container;
12782 #setInternalLink() {
12783 this.container.setAttribute("data-internal-link", "");
12785 _bindLink(link, destination) {
12786 link.href = this.linkService.getDestinationHash(destination);
12787 link.onclick = () => {
12789 this.linkService.goToDestination(destination);
12793 if (destination || destination === "") {
12794 this.#setInternalLink();
12797 _bindNamedAction(link, action) {
12798 link.href = this.linkService.getAnchorUrl("");
12799 link.onclick = () => {
12800 this.linkService.executeNamedAction(action);
12803 this.#setInternalLink();
12805 #bindAttachment(link, attachment, dest = null) {
12806 link.href = this.linkService.getAnchorUrl("");
12807 if (attachment.description) {
12808 link.title = attachment.description;
12810 link.onclick = () => {
12811 this.downloadManager?.openOrDownloadData(attachment.content, attachment.filename, dest);
12814 this.#setInternalLink();
12816 #bindSetOCGState(link, action) {
12817 link.href = this.linkService.getAnchorUrl("");
12818 link.onclick = () => {
12819 this.linkService.executeSetOCGState(action);
12822 this.#setInternalLink();
12824 _bindJSAction(link, data) {
12825 link.href = this.linkService.getAnchorUrl("");
12826 const map = new Map([["Action", "onclick"], ["Mouse Up", "onmouseup"], ["Mouse Down", "onmousedown"]]);
12827 for (const name of Object.keys(data.actions)) {
12828 const jsName = map.get(name);
12832 link[jsName] = () => {
12833 this.linkService.eventBus?.dispatch("dispatcheventinsandbox", {
12843 if (!link.onclick) {
12844 link.onclick = () => false;
12846 this.#setInternalLink();
12848 _bindResetFormAction(link, resetForm) {
12849 const otherClickAction = link.onclick;
12850 if (!otherClickAction) {
12851 link.href = this.linkService.getAnchorUrl("");
12853 this.#setInternalLink();
12854 if (!this._fieldObjects) {
12855 warn(`_bindResetFormAction - "resetForm" action not supported, ` + "ensure that the `fieldObjects` parameter is provided.");
12856 if (!otherClickAction) {
12857 link.onclick = () => false;
12861 link.onclick = () => {
12862 otherClickAction?.();
12864 fields: resetFormFields,
12865 refs: resetFormRefs,
12868 const allFields = [];
12869 if (resetFormFields.length !== 0 || resetFormRefs.length !== 0) {
12870 const fieldIds = new Set(resetFormRefs);
12871 for (const fieldName of resetFormFields) {
12872 const fields = this._fieldObjects[fieldName] || [];
12879 for (const fields of Object.values(this._fieldObjects)) {
12880 for (const field of fields) {
12881 if (fieldIds.has(field.id) === include) {
12882 allFields.push(field);
12887 for (const fields of Object.values(this._fieldObjects)) {
12888 allFields.push(...fields);
12891 const storage = this.annotationStorage;
12893 for (const field of allFields) {
12898 switch (field.type) {
12901 const value = field.defaultValue || "";
12902 storage.setValue(id, {
12908 case "radiobutton":
12910 const value = field.defaultValue === field.exportValues;
12911 storage.setValue(id, {
12919 const value = field.defaultValue || "";
12920 storage.setValue(id, {
12928 const domElement = document.querySelector(`[data-element-id="${id}"]`);
12931 } else if (!GetElementsByNameSet.has(domElement)) {
12932 warn(`_bindResetFormAction - element not allowed: ${id}`);
12935 domElement.dispatchEvent(new Event("resetform"));
12937 if (this.enableScripting) {
12938 this.linkService.eventBus?.dispatch("dispatcheventinsandbox", {
12951 class TextAnnotationElement extends AnnotationElement {
12952 constructor(parameters) {
12953 super(parameters, {
12958 this.container.classList.add("textAnnotation");
12959 const image = document.createElement("img");
12960 image.src = this.imageResourcesPath + "annotation-" + this.data.name.toLowerCase() + ".svg";
12961 image.setAttribute("data-l10n-id", "pdfjs-text-annotation-type");
12962 image.setAttribute("data-l10n-args", JSON.stringify({
12963 type: this.data.name
12965 if (!this.data.popupRef && this.hasPopupData) {
12966 this._createPopup();
12968 this.container.append(image);
12969 return this.container;
12972 class WidgetAnnotationElement extends AnnotationElement {
12974 return this.container;
12976 showElementAndHideCanvas(element) {
12977 if (this.data.hasOwnCanvas) {
12978 if (element.previousSibling?.nodeName === "CANVAS") {
12979 element.previousSibling.hidden = true;
12981 element.hidden = false;
12984 _getKeyModifier(event) {
12985 return util_FeatureTest.platform.isMac ? event.metaKey : event.ctrlKey;
12987 _setEventListener(element, elementData, baseName, eventName, valueGetter) {
12988 if (baseName.includes("mouse")) {
12989 element.addEventListener(baseName, event => {
12990 this.linkService.eventBus?.dispatch("dispatcheventinsandbox", {
12995 value: valueGetter(event),
12996 shift: event.shiftKey,
12997 modifier: this._getKeyModifier(event)
13002 element.addEventListener(baseName, event => {
13003 if (baseName === "blur") {
13004 if (!elementData.focused || !event.relatedTarget) {
13007 elementData.focused = false;
13008 } else if (baseName === "focus") {
13009 if (elementData.focused) {
13012 elementData.focused = true;
13014 if (!valueGetter) {
13017 this.linkService.eventBus?.dispatch("dispatcheventinsandbox", {
13022 value: valueGetter(event)
13028 _setEventListeners(element, elementData, names, getter) {
13029 for (const [baseName, eventName] of names) {
13030 if (eventName === "Action" || this.data.actions?.[eventName]) {
13031 if (eventName === "Focus" || eventName === "Blur") {
13036 this._setEventListener(element, elementData, baseName, eventName, getter);
13037 if (eventName === "Focus" && !this.data.actions?.Blur) {
13038 this._setEventListener(element, elementData, "blur", "Blur", null);
13039 } else if (eventName === "Blur" && !this.data.actions?.Focus) {
13040 this._setEventListener(element, elementData, "focus", "Focus", null);
13045 _setBackgroundColor(element) {
13046 const color = this.data.backgroundColor || null;
13047 element.style.backgroundColor = color === null ? "transparent" : Util.makeHexColor(color[0], color[1], color[2]);
13049 _setTextStyle(element) {
13050 const TEXT_ALIGNMENT = ["left", "center", "right"];
13053 } = this.data.defaultAppearanceData;
13054 const fontSize = this.data.defaultAppearanceData.fontSize || annotation_layer_DEFAULT_FONT_SIZE;
13055 const style = element.style;
13056 let computedFontSize;
13057 const BORDER_SIZE = 2;
13058 const roundToOneDecimal = x => Math.round(10 * x) / 10;
13059 if (this.data.multiLine) {
13060 const height = Math.abs(this.data.rect[3] - this.data.rect[1] - BORDER_SIZE);
13061 const numberOfLines = Math.round(height / (LINE_FACTOR * fontSize)) || 1;
13062 const lineHeight = height / numberOfLines;
13063 computedFontSize = Math.min(fontSize, roundToOneDecimal(lineHeight / LINE_FACTOR));
13065 const height = Math.abs(this.data.rect[3] - this.data.rect[1] - BORDER_SIZE);
13066 computedFontSize = Math.min(fontSize, roundToOneDecimal(height / LINE_FACTOR));
13068 style.fontSize = `calc(${computedFontSize}px * var(--scale-factor))`;
13069 style.color = Util.makeHexColor(fontColor[0], fontColor[1], fontColor[2]);
13070 if (this.data.textAlignment !== null) {
13071 style.textAlign = TEXT_ALIGNMENT[this.data.textAlignment];
13074 _setRequired(element, isRequired) {
13076 element.setAttribute("required", true);
13078 element.removeAttribute("required");
13080 element.setAttribute("aria-required", isRequired);
13083 class TextWidgetAnnotationElement extends WidgetAnnotationElement {
13084 constructor(parameters) {
13085 const isRenderable = parameters.renderForms || parameters.data.hasOwnCanvas || !parameters.data.hasAppearance && !!parameters.data.fieldValue;
13086 super(parameters, {
13090 setPropertyOnSiblings(base, key, value, keyInStorage) {
13091 const storage = this.annotationStorage;
13092 for (const element of this._getElementsByName(base.name, base.id)) {
13093 if (element.domElement) {
13094 element.domElement[key] = value;
13096 storage.setValue(element.id, {
13097 [keyInStorage]: value
13102 const storage = this.annotationStorage;
13103 const id = this.data.id;
13104 this.container.classList.add("textWidgetAnnotation");
13105 let element = null;
13106 if (this.renderForms) {
13107 const storedData = storage.getValue(id, {
13108 value: this.data.fieldValue
13110 let textContent = storedData.value || "";
13111 const maxLen = storage.getValue(id, {
13112 charLimit: this.data.maxLen
13114 if (maxLen && textContent.length > maxLen) {
13115 textContent = textContent.slice(0, maxLen);
13117 let fieldFormattedValues = storedData.formattedValue || this.data.textContent?.join("\n") || null;
13118 if (fieldFormattedValues && this.data.comb) {
13119 fieldFormattedValues = fieldFormattedValues.replaceAll(/\s+/g, "");
13121 const elementData = {
13122 userValue: textContent,
13123 formattedValue: fieldFormattedValues,
13124 lastCommittedValue: null,
13128 if (this.data.multiLine) {
13129 element = document.createElement("textarea");
13130 element.textContent = fieldFormattedValues ?? textContent;
13131 if (this.data.doNotScroll) {
13132 element.style.overflowY = "hidden";
13135 element = document.createElement("input");
13136 element.type = this.data.password ? "password" : "text";
13137 element.setAttribute("value", fieldFormattedValues ?? textContent);
13138 if (this.data.doNotScroll) {
13139 element.style.overflowX = "hidden";
13142 if (this.data.hasOwnCanvas) {
13143 element.hidden = true;
13145 GetElementsByNameSet.add(element);
13146 element.setAttribute("data-element-id", id);
13147 element.disabled = this.data.readOnly;
13148 element.name = this.data.fieldName;
13149 element.tabIndex = DEFAULT_TAB_INDEX;
13150 this._setRequired(element, this.data.required);
13152 element.maxLength = maxLen;
13154 element.addEventListener("input", event => {
13155 storage.setValue(id, {
13156 value: event.target.value
13158 this.setPropertyOnSiblings(element, "value", event.target.value, "value");
13159 elementData.formattedValue = null;
13161 element.addEventListener("resetform", event => {
13162 const defaultValue = this.data.defaultFieldValue ?? "";
13163 element.value = elementData.userValue = defaultValue;
13164 elementData.formattedValue = null;
13166 let blurListener = event => {
13170 if (formattedValue !== null && formattedValue !== undefined) {
13171 event.target.value = formattedValue;
13173 event.target.scrollLeft = 0;
13175 if (this.enableScripting && this.hasJSActions) {
13176 element.addEventListener("focus", event => {
13177 if (elementData.focused) {
13183 if (elementData.userValue) {
13184 target.value = elementData.userValue;
13186 elementData.lastCommittedValue = target.value;
13187 elementData.commitKey = 1;
13188 if (!this.data.actions?.Focus) {
13189 elementData.focused = true;
13192 element.addEventListener("updatefromsandbox", jsEvent => {
13193 this.showElementAndHideCanvas(jsEvent.target);
13196 elementData.userValue = event.detail.value ?? "";
13197 storage.setValue(id, {
13198 value: elementData.userValue.toString()
13200 event.target.value = elementData.userValue;
13202 formattedValue(event) {
13206 elementData.formattedValue = formattedValue;
13207 if (formattedValue !== null && formattedValue !== undefined && event.target !== document.activeElement) {
13208 event.target.value = formattedValue;
13210 storage.setValue(id, {
13215 event.target.setSelectionRange(...event.detail.selRange);
13217 charLimit: event => {
13224 if (charLimit === 0) {
13225 target.removeAttribute("maxLength");
13228 target.setAttribute("maxLength", charLimit);
13229 let value = elementData.userValue;
13230 if (!value || value.length <= charLimit) {
13233 value = value.slice(0, charLimit);
13234 target.value = elementData.userValue = value;
13235 storage.setValue(id, {
13238 this.linkService.eventBus?.dispatch("dispatcheventinsandbox", {
13246 selStart: target.selectionStart,
13247 selEnd: target.selectionEnd
13252 this._dispatchEventFromSandbox(actions, jsEvent);
13254 element.addEventListener("keydown", event => {
13255 elementData.commitKey = 1;
13256 let commitKey = -1;
13257 if (event.key === "Escape") {
13259 } else if (event.key === "Enter" && !this.data.multiLine) {
13261 } else if (event.key === "Tab") {
13262 elementData.commitKey = 3;
13264 if (commitKey === -1) {
13270 if (elementData.lastCommittedValue === value) {
13273 elementData.lastCommittedValue = value;
13274 elementData.userValue = value;
13275 this.linkService.eventBus?.dispatch("dispatcheventinsandbox", {
13283 selStart: event.target.selectionStart,
13284 selEnd: event.target.selectionEnd
13288 const _blurListener = blurListener;
13289 blurListener = null;
13290 element.addEventListener("blur", event => {
13291 if (!elementData.focused || !event.relatedTarget) {
13294 if (!this.data.actions?.Blur) {
13295 elementData.focused = false;
13300 elementData.userValue = value;
13301 if (elementData.lastCommittedValue !== value) {
13302 this.linkService.eventBus?.dispatch("dispatcheventinsandbox", {
13309 commitKey: elementData.commitKey,
13310 selStart: event.target.selectionStart,
13311 selEnd: event.target.selectionEnd
13315 _blurListener(event);
13317 if (this.data.actions?.Keystroke) {
13318 element.addEventListener("beforeinput", event => {
13319 elementData.lastCommittedValue = null;
13329 let selStart = selectionStart,
13330 selEnd = selectionEnd;
13331 switch (event.inputType) {
13332 case "deleteWordBackward":
13334 const match = value.substring(0, selectionStart).match(/\w*[^\w]*$/);
13336 selStart -= match[0].length;
13340 case "deleteWordForward":
13342 const match = value.substring(selectionStart).match(/^[^\w]*\w*/);
13344 selEnd += match[0].length;
13348 case "deleteContentBackward":
13349 if (selectionStart === selectionEnd) {
13353 case "deleteContentForward":
13354 if (selectionStart === selectionEnd) {
13359 event.preventDefault();
13360 this.linkService.eventBus?.dispatch("dispatcheventinsandbox", {
13366 change: data || "",
13374 this._setEventListeners(element, elementData, [["focus", "Focus"], ["blur", "Blur"], ["mousedown", "Mouse Down"], ["mouseenter", "Mouse Enter"], ["mouseleave", "Mouse Exit"], ["mouseup", "Mouse Up"]], event => event.target.value);
13376 if (blurListener) {
13377 element.addEventListener("blur", blurListener);
13379 if (this.data.comb) {
13380 const fieldWidth = this.data.rect[2] - this.data.rect[0];
13381 const combWidth = fieldWidth / maxLen;
13382 element.classList.add("comb");
13383 element.style.letterSpacing = `calc(${combWidth}px * var(--scale-factor) - 1ch)`;
13386 element = document.createElement("div");
13387 element.textContent = this.data.fieldValue;
13388 element.style.verticalAlign = "middle";
13389 element.style.display = "table-cell";
13390 if (this.data.hasOwnCanvas) {
13391 element.hidden = true;
13394 this._setTextStyle(element);
13395 this._setBackgroundColor(element);
13396 this._setDefaultPropertiesFromJS(element);
13397 this.container.append(element);
13398 return this.container;
13401 class SignatureWidgetAnnotationElement extends WidgetAnnotationElement {
13402 constructor(parameters) {
13403 super(parameters, {
13404 isRenderable: !!parameters.data.hasOwnCanvas
13408 class CheckboxWidgetAnnotationElement extends WidgetAnnotationElement {
13409 constructor(parameters) {
13410 super(parameters, {
13411 isRenderable: parameters.renderForms
13415 const storage = this.annotationStorage;
13416 const data = this.data;
13417 const id = data.id;
13418 let value = storage.getValue(id, {
13419 value: data.exportValue === data.fieldValue
13421 if (typeof value === "string") {
13422 value = value !== "Off";
13423 storage.setValue(id, {
13427 this.container.classList.add("buttonWidgetAnnotation", "checkBox");
13428 const element = document.createElement("input");
13429 GetElementsByNameSet.add(element);
13430 element.setAttribute("data-element-id", id);
13431 element.disabled = data.readOnly;
13432 this._setRequired(element, this.data.required);
13433 element.type = "checkbox";
13434 element.name = data.fieldName;
13436 element.setAttribute("checked", true);
13438 element.setAttribute("exportValue", data.exportValue);
13439 element.tabIndex = DEFAULT_TAB_INDEX;
13440 element.addEventListener("change", event => {
13445 for (const checkbox of this._getElementsByName(name, id)) {
13446 const curChecked = checked && checkbox.exportValue === data.exportValue;
13447 if (checkbox.domElement) {
13448 checkbox.domElement.checked = curChecked;
13450 storage.setValue(checkbox.id, {
13454 storage.setValue(id, {
13458 element.addEventListener("resetform", event => {
13459 const defaultValue = data.defaultFieldValue || "Off";
13460 event.target.checked = defaultValue === data.exportValue;
13462 if (this.enableScripting && this.hasJSActions) {
13463 element.addEventListener("updatefromsandbox", jsEvent => {
13466 event.target.checked = event.detail.value !== "Off";
13467 storage.setValue(id, {
13468 value: event.target.checked
13472 this._dispatchEventFromSandbox(actions, jsEvent);
13474 this._setEventListeners(element, null, [["change", "Validate"], ["change", "Action"], ["focus", "Focus"], ["blur", "Blur"], ["mousedown", "Mouse Down"], ["mouseenter", "Mouse Enter"], ["mouseleave", "Mouse Exit"], ["mouseup", "Mouse Up"]], event => event.target.checked);
13476 this._setBackgroundColor(element);
13477 this._setDefaultPropertiesFromJS(element);
13478 this.container.append(element);
13479 return this.container;
13482 class RadioButtonWidgetAnnotationElement extends WidgetAnnotationElement {
13483 constructor(parameters) {
13484 super(parameters, {
13485 isRenderable: parameters.renderForms
13489 this.container.classList.add("buttonWidgetAnnotation", "radioButton");
13490 const storage = this.annotationStorage;
13491 const data = this.data;
13492 const id = data.id;
13493 let value = storage.getValue(id, {
13494 value: data.fieldValue === data.buttonValue
13496 if (typeof value === "string") {
13497 value = value !== data.buttonValue;
13498 storage.setValue(id, {
13503 for (const radio of this._getElementsByName(data.fieldName, id)) {
13504 storage.setValue(radio.id, {
13509 const element = document.createElement("input");
13510 GetElementsByNameSet.add(element);
13511 element.setAttribute("data-element-id", id);
13512 element.disabled = data.readOnly;
13513 this._setRequired(element, this.data.required);
13514 element.type = "radio";
13515 element.name = data.fieldName;
13517 element.setAttribute("checked", true);
13519 element.tabIndex = DEFAULT_TAB_INDEX;
13520 element.addEventListener("change", event => {
13525 for (const radio of this._getElementsByName(name, id)) {
13526 storage.setValue(radio.id, {
13530 storage.setValue(id, {
13534 element.addEventListener("resetform", event => {
13535 const defaultValue = data.defaultFieldValue;
13536 event.target.checked = defaultValue !== null && defaultValue !== undefined && defaultValue === data.buttonValue;
13538 if (this.enableScripting && this.hasJSActions) {
13539 const pdfButtonValue = data.buttonValue;
13540 element.addEventListener("updatefromsandbox", jsEvent => {
13543 const checked = pdfButtonValue === event.detail.value;
13544 for (const radio of this._getElementsByName(event.target.name)) {
13545 const curChecked = checked && radio.id === id;
13546 if (radio.domElement) {
13547 radio.domElement.checked = curChecked;
13549 storage.setValue(radio.id, {
13555 this._dispatchEventFromSandbox(actions, jsEvent);
13557 this._setEventListeners(element, null, [["change", "Validate"], ["change", "Action"], ["focus", "Focus"], ["blur", "Blur"], ["mousedown", "Mouse Down"], ["mouseenter", "Mouse Enter"], ["mouseleave", "Mouse Exit"], ["mouseup", "Mouse Up"]], event => event.target.checked);
13559 this._setBackgroundColor(element);
13560 this._setDefaultPropertiesFromJS(element);
13561 this.container.append(element);
13562 return this.container;
13565 class PushButtonWidgetAnnotationElement extends LinkAnnotationElement {
13566 constructor(parameters) {
13567 super(parameters, {
13568 ignoreBorder: parameters.data.hasAppearance
13572 const container = super.render();
13573 container.classList.add("buttonWidgetAnnotation", "pushButton");
13574 const linkElement = container.lastChild;
13575 if (this.enableScripting && this.hasJSActions && linkElement) {
13576 this._setDefaultPropertiesFromJS(linkElement);
13577 linkElement.addEventListener("updatefromsandbox", jsEvent => {
13578 this._dispatchEventFromSandbox({}, jsEvent);
13584 class ChoiceWidgetAnnotationElement extends WidgetAnnotationElement {
13585 constructor(parameters) {
13586 super(parameters, {
13587 isRenderable: parameters.renderForms
13591 this.container.classList.add("choiceWidgetAnnotation");
13592 const storage = this.annotationStorage;
13593 const id = this.data.id;
13594 const storedData = storage.getValue(id, {
13595 value: this.data.fieldValue
13597 const selectElement = document.createElement("select");
13598 GetElementsByNameSet.add(selectElement);
13599 selectElement.setAttribute("data-element-id", id);
13600 selectElement.disabled = this.data.readOnly;
13601 this._setRequired(selectElement, this.data.required);
13602 selectElement.name = this.data.fieldName;
13603 selectElement.tabIndex = DEFAULT_TAB_INDEX;
13604 let addAnEmptyEntry = this.data.combo && this.data.options.length > 0;
13605 if (!this.data.combo) {
13606 selectElement.size = this.data.options.length;
13607 if (this.data.multiSelect) {
13608 selectElement.multiple = true;
13611 selectElement.addEventListener("resetform", event => {
13612 const defaultValue = this.data.defaultFieldValue;
13613 for (const option of selectElement.options) {
13614 option.selected = option.value === defaultValue;
13617 for (const option of this.data.options) {
13618 const optionElement = document.createElement("option");
13619 optionElement.textContent = option.displayValue;
13620 optionElement.value = option.exportValue;
13621 if (storedData.value.includes(option.exportValue)) {
13622 optionElement.setAttribute("selected", true);
13623 addAnEmptyEntry = false;
13625 selectElement.append(optionElement);
13627 let removeEmptyEntry = null;
13628 if (addAnEmptyEntry) {
13629 const noneOptionElement = document.createElement("option");
13630 noneOptionElement.value = " ";
13631 noneOptionElement.setAttribute("hidden", true);
13632 noneOptionElement.setAttribute("selected", true);
13633 selectElement.prepend(noneOptionElement);
13634 removeEmptyEntry = () => {
13635 noneOptionElement.remove();
13636 selectElement.removeEventListener("input", removeEmptyEntry);
13637 removeEmptyEntry = null;
13639 selectElement.addEventListener("input", removeEmptyEntry);
13641 const getValue = isExport => {
13642 const name = isExport ? "value" : "textContent";
13648 return options.selectedIndex === -1 ? null : options[options.selectedIndex][name];
13650 return Array.prototype.filter.call(options, option => option.selected).map(option => option[name]);
13652 let selectedValues = getValue(false);
13653 const getItems = event => {
13654 const options = event.target.options;
13655 return Array.prototype.map.call(options, option => ({
13656 displayValue: option.textContent,
13657 exportValue: option.value
13660 if (this.enableScripting && this.hasJSActions) {
13661 selectElement.addEventListener("updatefromsandbox", jsEvent => {
13664 removeEmptyEntry?.();
13665 const value = event.detail.value;
13666 const values = new Set(Array.isArray(value) ? value : [value]);
13667 for (const option of selectElement.options) {
13668 option.selected = values.has(option.value);
13670 storage.setValue(id, {
13671 value: getValue(true)
13673 selectedValues = getValue(false);
13675 multipleSelection(event) {
13676 selectElement.multiple = true;
13679 const options = selectElement.options;
13680 const index = event.detail.remove;
13681 options[index].selected = false;
13682 selectElement.remove(index);
13683 if (options.length > 0) {
13684 const i = Array.prototype.findIndex.call(options, option => option.selected);
13686 options[0].selected = true;
13689 storage.setValue(id, {
13690 value: getValue(true),
13691 items: getItems(event)
13693 selectedValues = getValue(false);
13696 while (selectElement.length !== 0) {
13697 selectElement.remove(0);
13699 storage.setValue(id, {
13703 selectedValues = getValue(false);
13710 } = event.detail.insert;
13711 const selectChild = selectElement.children[index];
13712 const optionElement = document.createElement("option");
13713 optionElement.textContent = displayValue;
13714 optionElement.value = exportValue;
13716 selectChild.before(optionElement);
13718 selectElement.append(optionElement);
13720 storage.setValue(id, {
13721 value: getValue(true),
13722 items: getItems(event)
13724 selectedValues = getValue(false);
13730 while (selectElement.length !== 0) {
13731 selectElement.remove(0);
13733 for (const item of items) {
13738 const optionElement = document.createElement("option");
13739 optionElement.textContent = displayValue;
13740 optionElement.value = exportValue;
13741 selectElement.append(optionElement);
13743 if (selectElement.options.length > 0) {
13744 selectElement.options[0].selected = true;
13746 storage.setValue(id, {
13747 value: getValue(true),
13748 items: getItems(event)
13750 selectedValues = getValue(false);
13753 const indices = new Set(event.detail.indices);
13754 for (const option of event.target.options) {
13755 option.selected = indices.has(option.index);
13757 storage.setValue(id, {
13758 value: getValue(true)
13760 selectedValues = getValue(false);
13763 event.target.disabled = !event.detail.editable;
13766 this._dispatchEventFromSandbox(actions, jsEvent);
13768 selectElement.addEventListener("input", event => {
13769 const exportValue = getValue(true);
13770 const change = getValue(false);
13771 storage.setValue(id, {
13774 event.preventDefault();
13775 this.linkService.eventBus?.dispatch("dispatcheventinsandbox", {
13780 value: selectedValues,
13782 changeEx: exportValue,
13789 this._setEventListeners(selectElement, null, [["focus", "Focus"], ["blur", "Blur"], ["mousedown", "Mouse Down"], ["mouseenter", "Mouse Enter"], ["mouseleave", "Mouse Exit"], ["mouseup", "Mouse Up"], ["input", "Action"], ["input", "Validate"]], event => event.target.value);
13791 selectElement.addEventListener("input", function (event) {
13792 storage.setValue(id, {
13793 value: getValue(true)
13797 if (this.data.combo) {
13798 this._setTextStyle(selectElement);
13800 this._setBackgroundColor(selectElement);
13801 this._setDefaultPropertiesFromJS(selectElement);
13802 this.container.append(selectElement);
13803 return this.container;
13806 class PopupAnnotationElement extends AnnotationElement {
13807 constructor(parameters) {
13812 super(parameters, {
13813 isRenderable: AnnotationElement._hasPopupData(data)
13815 this.elements = elements;
13819 this.container.classList.add("popupAnnotation");
13820 const popup = this.popup = new PopupElement({
13821 container: this.container,
13822 color: this.data.color,
13823 titleObj: this.data.titleObj,
13824 modificationDate: this.data.modificationDate,
13825 contentsObj: this.data.contentsObj,
13826 richText: this.data.richText,
13827 rect: this.data.rect,
13828 parentRect: this.data.parentRect || null,
13829 parent: this.parent,
13830 elements: this.elements,
13831 open: this.data.open
13833 const elementIds = [];
13834 for (const element of this.elements) {
13835 element.popup = popup;
13836 element.container.ariaHasPopup = "dialog";
13837 elementIds.push(element.data.id);
13838 element.addHighlightArea();
13840 this.container.setAttribute("aria-controls", elementIds.map(id => `${AnnotationPrefix}${id}`).join(","));
13841 return this.container;
13844 class PopupElement {
13845 #boundKeyDown = this.#keyDown.bind(this);
13846 #boundHide = this.#hide.bind(this);
13847 #boundShow = this.#show.bind(this);
13848 #boundToggle = this.#toggle.bind(this);
13851 #contentsObj = null;
13855 #parentRect = null;
13863 #wasVisible = false;
13877 this.#container = container;
13878 this.#titleObj = titleObj;
13879 this.#contentsObj = contentsObj;
13880 this.#richText = richText;
13881 this.#parent = parent;
13882 this.#color = color;
13884 this.#parentRect = parentRect;
13885 this.#elements = elements;
13886 this.#dateObj = PDFDateString.toDateObject(modificationDate);
13887 this.trigger = elements.flatMap(e => e.getElementsToTriggerPopup());
13888 for (const element of this.trigger) {
13889 element.addEventListener("click", this.#boundToggle);
13890 element.addEventListener("mouseenter", this.#boundShow);
13891 element.addEventListener("mouseleave", this.#boundHide);
13892 element.classList.add("popupTriggerArea");
13894 for (const element of elements) {
13895 element.container?.addEventListener("keydown", this.#boundKeyDown);
13897 this.#container.hidden = true;
13906 const popup = this.#popup = document.createElement("div");
13907 popup.className = "popup";
13909 const baseColor = popup.style.outlineColor = Util.makeHexColor(...this.#color);
13910 popup.style.backgroundColor = `color-mix(in srgb, ${baseColor} 30%, white)`;
13912 const header = document.createElement("span");
13913 header.className = "header";
13914 const title = document.createElement("h1");
13915 header.append(title);
13918 str: title.textContent
13919 } = this.#titleObj);
13920 popup.append(header);
13921 if (this.#dateObj) {
13922 const modificationDate = document.createElement("span");
13923 modificationDate.classList.add("popupDate");
13924 modificationDate.setAttribute("data-l10n-id", "pdfjs-annotation-date-time-string");
13925 modificationDate.setAttribute("data-l10n-args", JSON.stringify({
13926 dateObj: this.#dateObj.valueOf()
13928 header.append(modificationDate);
13930 const html = this.#html;
13934 intent: "richText",
13937 popup.lastChild.classList.add("richText", "popupContent");
13939 const contents = this._formatContents(this.#contentsObj);
13940 popup.append(contents);
13942 this.#container.append(popup);
13945 const richText = this.#richText;
13946 const contentsObj = this.#contentsObj;
13947 if (richText?.str && (!contentsObj?.str || contentsObj.str === richText.str)) {
13948 return this.#richText.html || null;
13953 return this.#html?.attributes?.style?.fontSize || 0;
13956 return this.#html?.attributes?.style?.color || null;
13958 #makePopupContent(text) {
13959 const popupLines = [];
13960 const popupContent = {
13969 children: popupLines
13973 const lineAttributes = {
13975 color: this.#fontColor,
13976 fontSize: this.#fontSize ? `calc(${this.#fontSize}px * var(--scale-factor))` : ""
13979 for (const line of text.split("\n")) {
13983 attributes: lineAttributes
13986 return popupContent;
13992 const p = document.createElement("p");
13993 p.classList.add("popupContent");
13995 const lines = str.split(/(?:\r\n?|\n)/);
13996 for (let i = 0, ii = lines.length; i < ii; ++i) {
13997 const line = lines[i];
13998 p.append(document.createTextNode(line));
14000 p.append(document.createElement("br"));
14006 if (event.altKey || event.shiftKey || event.ctrlKey || event.metaKey) {
14009 if (event.key === "Enter" || event.key === "Escape" && this.#pinned) {
14017 this.#updates ||= {
14018 contentsObj: this.#contentsObj,
14019 richText: this.#richText
14022 this.#position = null;
14024 if (popupContent) {
14025 this.#richText = this.#makePopupContent(popupContent);
14026 this.#contentsObj = null;
14028 this.#popup?.remove();
14029 this.#popup = null;
14032 if (!this.#updates) {
14036 contentsObj: this.#contentsObj,
14037 richText: this.#richText
14038 } = this.#updates);
14039 this.#updates = null;
14040 this.#popup?.remove();
14041 this.#popup = null;
14042 this.#position = null;
14045 if (this.#position !== null) {
14061 let useParentRect = !!this.#parentRect;
14062 let rect = useParentRect ? this.#parentRect : this.#rect;
14063 for (const element of this.#elements) {
14064 if (!rect || Util.intersect(element.data.rect, rect) !== null) {
14065 rect = element.data.rect;
14066 useParentRect = true;
14070 const normalizedRect = Util.normalizeRect([rect[0], view[3] - rect[1] + view[1], rect[2], view[3] - rect[3] + view[1]]);
14071 const HORIZONTAL_SPACE_AFTER_ANNOTATION = 5;
14072 const parentWidth = useParentRect ? rect[2] - rect[0] + HORIZONTAL_SPACE_AFTER_ANNOTATION : 0;
14073 const popupLeft = normalizedRect[0] + parentWidth;
14074 const popupTop = normalizedRect[1];
14075 this.#position = [100 * (popupLeft - pageX) / pageWidth, 100 * (popupTop - pageY) / pageHeight];
14078 } = this.#container;
14079 style.left = `${this.#position[0]}%`;
14080 style.top = `${this.#position[1]}%`;
14083 this.#pinned = !this.#pinned;
14084 if (this.#pinned) {
14086 this.#container.addEventListener("click", this.#boundToggle);
14087 this.#container.addEventListener("keydown", this.#boundKeyDown);
14090 this.#container.removeEventListener("click", this.#boundToggle);
14091 this.#container.removeEventListener("keydown", this.#boundKeyDown);
14095 if (!this.#popup) {
14098 if (!this.isVisible) {
14099 this.#setPosition();
14100 this.#container.hidden = false;
14101 this.#container.style.zIndex = parseInt(this.#container.style.zIndex) + 1000;
14102 } else if (this.#pinned) {
14103 this.#container.classList.add("focused");
14107 this.#container.classList.remove("focused");
14108 if (this.#pinned || !this.isVisible) {
14111 this.#container.hidden = true;
14112 this.#container.style.zIndex = parseInt(this.#container.style.zIndex) - 1000;
14115 this.#wasVisible = this.isVisible;
14116 if (!this.#wasVisible) {
14119 this.#container.hidden = true;
14122 if (!this.#wasVisible) {
14125 if (!this.#popup) {
14128 this.#wasVisible = false;
14129 this.#container.hidden = false;
14132 return this.#container.hidden === false;
14135 class FreeTextAnnotationElement extends AnnotationElement {
14136 constructor(parameters) {
14137 super(parameters, {
14138 isRenderable: true,
14141 this.textContent = parameters.data.textContent;
14142 this.textPosition = parameters.data.textPosition;
14143 this.annotationEditorType = AnnotationEditorType.FREETEXT;
14146 this.container.classList.add("freeTextAnnotation");
14147 if (this.textContent) {
14148 const content = document.createElement("div");
14149 content.classList.add("annotationTextContent");
14150 content.setAttribute("role", "comment");
14151 for (const line of this.textContent) {
14152 const lineSpan = document.createElement("span");
14153 lineSpan.textContent = line;
14154 content.append(lineSpan);
14156 this.container.append(content);
14158 if (!this.data.popupRef && this.hasPopupData) {
14159 this._createPopup();
14161 this._editOnDoubleClick();
14162 return this.container;
14165 class LineAnnotationElement extends AnnotationElement {
14167 constructor(parameters) {
14168 super(parameters, {
14169 isRenderable: true,
14174 this.container.classList.add("lineAnnotation");
14180 const svg = this.svgFactory.create(width, height, true);
14181 const line = this.#line = this.svgFactory.createElement("svg:line");
14182 line.setAttribute("x1", data.rect[2] - data.lineCoordinates[0]);
14183 line.setAttribute("y1", data.rect[3] - data.lineCoordinates[1]);
14184 line.setAttribute("x2", data.rect[2] - data.lineCoordinates[2]);
14185 line.setAttribute("y2", data.rect[3] - data.lineCoordinates[3]);
14186 line.setAttribute("stroke-width", data.borderStyle.width || 1);
14187 line.setAttribute("stroke", "transparent");
14188 line.setAttribute("fill", "transparent");
14190 this.container.append(svg);
14191 if (!data.popupRef && this.hasPopupData) {
14192 this._createPopup();
14194 return this.container;
14196 getElementsToTriggerPopup() {
14199 addHighlightArea() {
14200 this.container.classList.add("highlightArea");
14203 class SquareAnnotationElement extends AnnotationElement {
14205 constructor(parameters) {
14206 super(parameters, {
14207 isRenderable: true,
14212 this.container.classList.add("squareAnnotation");
14218 const svg = this.svgFactory.create(width, height, true);
14219 const borderWidth = data.borderStyle.width;
14220 const square = this.#square = this.svgFactory.createElement("svg:rect");
14221 square.setAttribute("x", borderWidth / 2);
14222 square.setAttribute("y", borderWidth / 2);
14223 square.setAttribute("width", width - borderWidth);
14224 square.setAttribute("height", height - borderWidth);
14225 square.setAttribute("stroke-width", borderWidth || 1);
14226 square.setAttribute("stroke", "transparent");
14227 square.setAttribute("fill", "transparent");
14228 svg.append(square);
14229 this.container.append(svg);
14230 if (!data.popupRef && this.hasPopupData) {
14231 this._createPopup();
14233 return this.container;
14235 getElementsToTriggerPopup() {
14236 return this.#square;
14238 addHighlightArea() {
14239 this.container.classList.add("highlightArea");
14242 class CircleAnnotationElement extends AnnotationElement {
14244 constructor(parameters) {
14245 super(parameters, {
14246 isRenderable: true,
14251 this.container.classList.add("circleAnnotation");
14257 const svg = this.svgFactory.create(width, height, true);
14258 const borderWidth = data.borderStyle.width;
14259 const circle = this.#circle = this.svgFactory.createElement("svg:ellipse");
14260 circle.setAttribute("cx", width / 2);
14261 circle.setAttribute("cy", height / 2);
14262 circle.setAttribute("rx", width / 2 - borderWidth / 2);
14263 circle.setAttribute("ry", height / 2 - borderWidth / 2);
14264 circle.setAttribute("stroke-width", borderWidth || 1);
14265 circle.setAttribute("stroke", "transparent");
14266 circle.setAttribute("fill", "transparent");
14267 svg.append(circle);
14268 this.container.append(svg);
14269 if (!data.popupRef && this.hasPopupData) {
14270 this._createPopup();
14272 return this.container;
14274 getElementsToTriggerPopup() {
14275 return this.#circle;
14277 addHighlightArea() {
14278 this.container.classList.add("highlightArea");
14281 class PolylineAnnotationElement extends AnnotationElement {
14283 constructor(parameters) {
14284 super(parameters, {
14285 isRenderable: true,
14288 this.containerClassName = "polylineAnnotation";
14289 this.svgElementName = "svg:polyline";
14292 this.container.classList.add(this.containerClassName);
14304 return this.container;
14306 const svg = this.svgFactory.create(width, height, true);
14308 for (let i = 0, ii = vertices.length; i < ii; i += 2) {
14309 const x = vertices[i] - rect[0];
14310 const y = rect[3] - vertices[i + 1];
14311 points.push(`${x},${y}`);
14313 points = points.join(" ");
14314 const polyline = this.#polyline = this.svgFactory.createElement(this.svgElementName);
14315 polyline.setAttribute("points", points);
14316 polyline.setAttribute("stroke-width", borderStyle.width || 1);
14317 polyline.setAttribute("stroke", "transparent");
14318 polyline.setAttribute("fill", "transparent");
14319 svg.append(polyline);
14320 this.container.append(svg);
14321 if (!popupRef && this.hasPopupData) {
14322 this._createPopup();
14324 return this.container;
14326 getElementsToTriggerPopup() {
14327 return this.#polyline;
14329 addHighlightArea() {
14330 this.container.classList.add("highlightArea");
14333 class PolygonAnnotationElement extends PolylineAnnotationElement {
14334 constructor(parameters) {
14336 this.containerClassName = "polygonAnnotation";
14337 this.svgElementName = "svg:polygon";
14340 class CaretAnnotationElement extends AnnotationElement {
14341 constructor(parameters) {
14342 super(parameters, {
14343 isRenderable: true,
14348 this.container.classList.add("caretAnnotation");
14349 if (!this.data.popupRef && this.hasPopupData) {
14350 this._createPopup();
14352 return this.container;
14355 class InkAnnotationElement extends AnnotationElement {
14356 #polylinesGroupElement = null;
14358 constructor(parameters) {
14359 super(parameters, {
14360 isRenderable: true,
14363 this.containerClassName = "inkAnnotation";
14364 this.svgElementName = "svg:polyline";
14365 this.annotationEditorType = this.data.it === "InkHighlight" ? AnnotationEditorType.HIGHLIGHT : AnnotationEditorType.INK;
14367 #getTransform(rotation, rect) {
14368 switch (rotation) {
14371 transform: `rotate(90) translate(${-rect[0]},${rect[1]}) scale(1,-1)`,
14372 width: rect[3] - rect[1],
14373 height: rect[2] - rect[0]
14377 transform: `rotate(180) translate(${-rect[2]},${rect[1]}) scale(1,-1)`,
14378 width: rect[2] - rect[0],
14379 height: rect[3] - rect[1]
14383 transform: `rotate(270) translate(${-rect[2]},${rect[3]}) scale(1,-1)`,
14384 width: rect[3] - rect[1],
14385 height: rect[2] - rect[0]
14389 transform: `translate(${-rect[0]},${rect[3]}) scale(1,-1)`,
14390 width: rect[2] - rect[0],
14391 height: rect[3] - rect[1]
14396 this.container.classList.add(this.containerClassName);
14410 } = this.#getTransform(rotation, rect);
14411 const svg = this.svgFactory.create(width, height, true);
14412 const g = this.#polylinesGroupElement = this.svgFactory.createElement("svg:g");
14414 g.setAttribute("stroke-width", borderStyle.width || 1);
14415 g.setAttribute("stroke-linecap", "round");
14416 g.setAttribute("stroke-linejoin", "round");
14417 g.setAttribute("stroke-miterlimit", 10);
14418 g.setAttribute("stroke", "transparent");
14419 g.setAttribute("fill", "transparent");
14420 g.setAttribute("transform", transform);
14421 for (let i = 0, ii = inkLists.length; i < ii; i++) {
14422 const polyline = this.svgFactory.createElement(this.svgElementName);
14423 this.#polylines.push(polyline);
14424 polyline.setAttribute("points", inkLists[i].join(","));
14425 g.append(polyline);
14427 if (!popupRef && this.hasPopupData) {
14428 this._createPopup();
14430 this.container.append(svg);
14431 this._editOnDoubleClick();
14432 return this.container;
14434 updateEdited(params) {
14435 super.updateEdited(params);
14441 const g = this.#polylinesGroupElement;
14442 if (thickness >= 0) {
14443 g.setAttribute("stroke-width", thickness || 1);
14446 for (let i = 0, ii = this.#polylines.length; i < ii; i++) {
14447 this.#polylines[i].setAttribute("points", points[i].join(","));
14455 } = this.#getTransform(this.data.rotation, rect);
14456 const root = g.parentElement;
14457 root.setAttribute("viewBox", `0 0 ${width} ${height}`);
14458 g.setAttribute("transform", transform);
14461 getElementsToTriggerPopup() {
14462 return this.#polylines;
14464 addHighlightArea() {
14465 this.container.classList.add("highlightArea");
14468 class HighlightAnnotationElement extends AnnotationElement {
14469 constructor(parameters) {
14470 super(parameters, {
14471 isRenderable: true,
14472 ignoreBorder: true,
14473 createQuadrilaterals: true
14475 this.annotationEditorType = AnnotationEditorType.HIGHLIGHT;
14478 if (!this.data.popupRef && this.hasPopupData) {
14479 this._createPopup();
14481 this.container.classList.add("highlightAnnotation");
14482 this._editOnDoubleClick();
14483 return this.container;
14486 class UnderlineAnnotationElement extends AnnotationElement {
14487 constructor(parameters) {
14488 super(parameters, {
14489 isRenderable: true,
14490 ignoreBorder: true,
14491 createQuadrilaterals: true
14495 if (!this.data.popupRef && this.hasPopupData) {
14496 this._createPopup();
14498 this.container.classList.add("underlineAnnotation");
14499 return this.container;
14502 class SquigglyAnnotationElement extends AnnotationElement {
14503 constructor(parameters) {
14504 super(parameters, {
14505 isRenderable: true,
14506 ignoreBorder: true,
14507 createQuadrilaterals: true
14511 if (!this.data.popupRef && this.hasPopupData) {
14512 this._createPopup();
14514 this.container.classList.add("squigglyAnnotation");
14515 return this.container;
14518 class StrikeOutAnnotationElement extends AnnotationElement {
14519 constructor(parameters) {
14520 super(parameters, {
14521 isRenderable: true,
14522 ignoreBorder: true,
14523 createQuadrilaterals: true
14527 if (!this.data.popupRef && this.hasPopupData) {
14528 this._createPopup();
14530 this.container.classList.add("strikeoutAnnotation");
14531 return this.container;
14534 class StampAnnotationElement extends AnnotationElement {
14535 constructor(parameters) {
14536 super(parameters, {
14537 isRenderable: true,
14540 this.annotationEditorType = AnnotationEditorType.STAMP;
14543 this.container.classList.add("stampAnnotation");
14544 this.container.setAttribute("role", "img");
14545 if (!this.data.popupRef && this.hasPopupData) {
14546 this._createPopup();
14548 this._editOnDoubleClick();
14549 return this.container;
14552 class FileAttachmentAnnotationElement extends AnnotationElement {
14554 constructor(parameters) {
14555 super(parameters, {
14561 this.filename = file.filename;
14562 this.content = file.content;
14563 this.linkService.eventBus?.dispatch("fileattachmentannotation", {
14569 this.container.classList.add("fileAttachmentAnnotation");
14575 if (data.hasAppearance || data.fillAlpha === 0) {
14576 trigger = document.createElement("div");
14578 trigger = document.createElement("img");
14579 trigger.src = `${this.imageResourcesPath}annotation-${/paperclip/i.test(data.name) ? "paperclip" : "pushpin"}.svg`;
14580 if (data.fillAlpha && data.fillAlpha < 1) {
14581 trigger.style = `filter: opacity(${Math.round(data.fillAlpha * 100)}%);`;
14584 trigger.addEventListener("dblclick", this.#download.bind(this));
14585 this.#trigger = trigger;
14588 } = util_FeatureTest.platform;
14589 container.addEventListener("keydown", evt => {
14590 if (evt.key === "Enter" && (isMac ? evt.metaKey : evt.ctrlKey)) {
14594 if (!data.popupRef && this.hasPopupData) {
14595 this._createPopup();
14597 trigger.classList.add("popupTriggerArea");
14599 container.append(trigger);
14602 getElementsToTriggerPopup() {
14603 return this.#trigger;
14605 addHighlightArea() {
14606 this.container.classList.add("highlightArea");
14609 this.downloadManager?.openOrDownloadData(this.content, this.filename);
14612 class AnnotationLayer {
14613 #accessibilityManager = null;
14614 #annotationCanvasMap = null;
14615 #editableAnnotations = new Map();
14616 #structTreeLayer = null;
14619 accessibilityManager,
14620 annotationCanvasMap,
14621 annotationEditorUIManager,
14627 this.#accessibilityManager = accessibilityManager;
14628 this.#annotationCanvasMap = annotationCanvasMap;
14629 this.#structTreeLayer = structTreeLayer || null;
14631 this.viewport = viewport;
14633 this._annotationEditorUIManager = annotationEditorUIManager;
14635 hasEditableAnnotations() {
14636 return this.#editableAnnotations.size > 0;
14638 async #appendElement(element, id) {
14639 const contentElement = element.firstChild || element;
14640 const annotationId = contentElement.id = `${AnnotationPrefix}${id}`;
14641 const ariaAttributes = await this.#structTreeLayer?.getAriaAttributes(annotationId);
14642 if (ariaAttributes) {
14643 for (const [key, value] of ariaAttributes) {
14644 contentElement.setAttribute(key, value);
14647 this.div.append(element);
14648 this.#accessibilityManager?.moveElementInDOM(this.div, element, contentElement, false);
14650 async render(params) {
14654 const layer = this.div;
14655 setLayerDimensions(layer, this.viewport);
14656 const popupToElements = new Map();
14657 const elementParams = {
14660 linkService: params.linkService,
14661 downloadManager: params.downloadManager,
14662 imageResourcesPath: params.imageResourcesPath || "",
14663 renderForms: params.renderForms !== false,
14664 svgFactory: new DOMSVGFactory(),
14665 annotationStorage: params.annotationStorage || new AnnotationStorage(),
14666 enableScripting: params.enableScripting === true,
14667 hasJSActions: params.hasJSActions,
14668 fieldObjects: params.fieldObjects,
14672 for (const data of annotations) {
14676 const isPopupAnnotation = data.annotationType === AnnotationType.POPUP;
14677 if (!isPopupAnnotation) {
14678 if (data.rect[2] === data.rect[0] || data.rect[3] === data.rect[1]) {
14682 const elements = popupToElements.get(data.id);
14686 elementParams.elements = elements;
14688 elementParams.data = data;
14689 const element = AnnotationElementFactory.create(elementParams);
14690 if (!element.isRenderable) {
14693 if (!isPopupAnnotation && data.popupRef) {
14694 const elements = popupToElements.get(data.popupRef);
14696 popupToElements.set(data.popupRef, [element]);
14698 elements.push(element);
14701 const rendered = element.render();
14703 rendered.style.visibility = "hidden";
14705 await this.#appendElement(rendered, data.id);
14706 if (element._isEditable) {
14707 this.#editableAnnotations.set(element.data.id, element);
14708 this._annotationEditorUIManager?.renderAnnotationElement(element);
14711 this.#setAnnotationCanvasMap();
14713 async addLinkAnnotations(annotations, linkService) {
14714 const elementParams = {
14718 svgFactory: new DOMSVGFactory(),
14721 for (const data of annotations) {
14722 elementParams.data = data;
14723 const element = AnnotationElementFactory.create(elementParams);
14724 if (!element.isRenderable) {
14727 const rendered = element.render();
14728 await this.#appendElement(rendered, data.id);
14734 const layer = this.div;
14735 this.viewport = viewport;
14736 setLayerDimensions(layer, {
14737 rotation: viewport.rotation
14739 this.#setAnnotationCanvasMap();
14740 layer.hidden = false;
14742 #setAnnotationCanvasMap() {
14743 if (!this.#annotationCanvasMap) {
14746 const layer = this.div;
14747 for (const [id, canvas] of this.#annotationCanvasMap) {
14748 const element = layer.querySelector(`[data-annotation-id="${id}"]`);
14752 canvas.className = "annotationContent";
14757 element.append(canvas);
14758 } else if (firstChild.nodeName === "CANVAS") {
14759 firstChild.replaceWith(canvas);
14760 } else if (!firstChild.classList.contains("annotationContent")) {
14761 firstChild.before(canvas);
14763 firstChild.after(canvas);
14765 const editableAnnotation = this.#editableAnnotations.get(id);
14766 if (!editableAnnotation) {
14769 if (editableAnnotation._hasNoCanvas) {
14770 this._annotationEditorUIManager?.setMissingCanvas(id, element.id, canvas);
14771 editableAnnotation._hasNoCanvas = false;
14773 editableAnnotation.canvas = canvas;
14776 this.#annotationCanvasMap.clear();
14778 getEditableAnnotations() {
14779 return Array.from(this.#editableAnnotations.values());
14781 getEditableAnnotation(id) {
14782 return this.#editableAnnotations.get(id);
14786 ;// ./src/display/editor/freetext.js
14791 const EOL_PATTERN = /\r\n?|\n/g;
14792 class FreeTextEditor extends AnnotationEditor {
14795 #editorDivId = `${this.id}-editor`;
14796 #editModeAC = null;
14798 static _freeTextDefaultContent = "";
14799 static _internalPadding = 0;
14800 static _defaultColor = null;
14801 static _defaultFontSize = 10;
14802 static get _keyboardManager() {
14803 const proto = FreeTextEditor.prototype;
14804 const arrowChecker = self => self.isEmpty();
14805 const small = AnnotationEditorUIManager.TRANSLATE_SMALL;
14806 const big = AnnotationEditorUIManager.TRANSLATE_BIG;
14807 return shadow(this, "_keyboardManager", new KeyboardManager([[["ctrl+s", "mac+meta+s", "ctrl+p", "mac+meta+p"], proto.commitOrRemove, {
14809 }], [["ctrl+Enter", "mac+meta+Enter", "Escape", "mac+Escape"], proto.commitOrRemove], [["ArrowLeft", "mac+ArrowLeft"], proto._translateEmpty, {
14811 checker: arrowChecker
14812 }], [["ctrl+ArrowLeft", "mac+shift+ArrowLeft"], proto._translateEmpty, {
14814 checker: arrowChecker
14815 }], [["ArrowRight", "mac+ArrowRight"], proto._translateEmpty, {
14817 checker: arrowChecker
14818 }], [["ctrl+ArrowRight", "mac+shift+ArrowRight"], proto._translateEmpty, {
14820 checker: arrowChecker
14821 }], [["ArrowUp", "mac+ArrowUp"], proto._translateEmpty, {
14823 checker: arrowChecker
14824 }], [["ctrl+ArrowUp", "mac+shift+ArrowUp"], proto._translateEmpty, {
14826 checker: arrowChecker
14827 }], [["ArrowDown", "mac+ArrowDown"], proto._translateEmpty, {
14829 checker: arrowChecker
14830 }], [["ctrl+ArrowDown", "mac+shift+ArrowDown"], proto._translateEmpty, {
14832 checker: arrowChecker
14835 static _type = "freetext";
14836 static _editorType = AnnotationEditorType.FREETEXT;
14837 constructor(params) {
14840 name: "freeTextEditor"
14842 this.#color = params.color || FreeTextEditor._defaultColor || AnnotationEditor._defaultLineColor;
14843 this.#fontSize = params.fontSize || FreeTextEditor._defaultFontSize;
14845 static initialize(l10n, uiManager) {
14846 AnnotationEditor.initialize(l10n, uiManager);
14847 const style = getComputedStyle(document.documentElement);
14848 this._internalPadding = parseFloat(style.getPropertyValue("--freetext-padding"));
14850 static updateDefaultParams(type, value) {
14852 case AnnotationEditorParamsType.FREETEXT_SIZE:
14853 FreeTextEditor._defaultFontSize = value;
14855 case AnnotationEditorParamsType.FREETEXT_COLOR:
14856 FreeTextEditor._defaultColor = value;
14860 updateParams(type, value) {
14862 case AnnotationEditorParamsType.FREETEXT_SIZE:
14863 this.#updateFontSize(value);
14865 case AnnotationEditorParamsType.FREETEXT_COLOR:
14866 this.#updateColor(value);
14870 static get defaultPropertiesToUpdate() {
14871 return [[AnnotationEditorParamsType.FREETEXT_SIZE, FreeTextEditor._defaultFontSize], [AnnotationEditorParamsType.FREETEXT_COLOR, FreeTextEditor._defaultColor || AnnotationEditor._defaultLineColor]];
14873 get propertiesToUpdate() {
14874 return [[AnnotationEditorParamsType.FREETEXT_SIZE, this.#fontSize], [AnnotationEditorParamsType.FREETEXT_COLOR, this.#color]];
14876 #updateFontSize(fontSize) {
14877 const setFontsize = size => {
14878 this.editorDiv.style.fontSize = `calc(${size}px * var(--scale-factor))`;
14879 this.translate(0, -(size - this.#fontSize) * this.parentScale);
14880 this.#fontSize = size;
14881 this.#setEditorDimensions();
14883 const savedFontsize = this.#fontSize;
14885 cmd: setFontsize.bind(this, fontSize),
14886 undo: setFontsize.bind(this, savedFontsize),
14887 post: this._uiManager.updateUI.bind(this._uiManager, this),
14889 type: AnnotationEditorParamsType.FREETEXT_SIZE,
14890 overwriteIfSameType: true,
14894 #updateColor(color) {
14895 const setColor = col => {
14896 this.#color = this.editorDiv.style.color = col;
14898 const savedColor = this.#color;
14900 cmd: setColor.bind(this, color),
14901 undo: setColor.bind(this, savedColor),
14902 post: this._uiManager.updateUI.bind(this._uiManager, this),
14904 type: AnnotationEditorParamsType.FREETEXT_COLOR,
14905 overwriteIfSameType: true,
14909 _translateEmpty(x, y) {
14910 this._uiManager.translateSelectedEditors(x, y, true);
14912 getInitialTranslation() {
14913 const scale = this.parentScale;
14914 return [-FreeTextEditor._internalPadding * scale, -(FreeTextEditor._internalPadding + this.#fontSize) * scale];
14917 if (!this.parent) {
14921 if (this.div === null) {
14924 if (!this.isAttachedToDOM) {
14925 this.parent.add(this);
14929 if (this.isInEditMode()) {
14932 this.parent.setEditingState(false);
14933 this.parent.updateToolbar(AnnotationEditorType.FREETEXT);
14934 super.enableEditMode();
14935 this.overlayDiv.classList.remove("enabled");
14936 this.editorDiv.contentEditable = true;
14937 this._isDraggable = false;
14938 this.div.removeAttribute("aria-activedescendant");
14939 this.#editModeAC = new AbortController();
14940 const signal = this._uiManager.combinedSignal(this.#editModeAC);
14941 this.editorDiv.addEventListener("keydown", this.editorDivKeydown.bind(this), {
14944 this.editorDiv.addEventListener("focus", this.editorDivFocus.bind(this), {
14947 this.editorDiv.addEventListener("blur", this.editorDivBlur.bind(this), {
14950 this.editorDiv.addEventListener("input", this.editorDivInput.bind(this), {
14953 this.editorDiv.addEventListener("paste", this.editorDivPaste.bind(this), {
14957 disableEditMode() {
14958 if (!this.isInEditMode()) {
14961 this.parent.setEditingState(true);
14962 super.disableEditMode();
14963 this.overlayDiv.classList.add("enabled");
14964 this.editorDiv.contentEditable = false;
14965 this.div.setAttribute("aria-activedescendant", this.#editorDivId);
14966 this._isDraggable = true;
14967 this.#editModeAC?.abort();
14968 this.#editModeAC = null;
14970 preventScroll: true
14972 this.isEditing = false;
14973 this.parent.div.classList.add("freetextEditing");
14976 if (!this._focusEventsAllowed) {
14979 super.focusin(event);
14980 if (event.target !== this.editorDiv) {
14981 this.editorDiv.focus();
14988 this.enableEditMode();
14990 this.editorDiv.focus();
14992 if (this._initialOptions?.isCentered) {
14995 this._initialOptions = null;
14998 return !this.editorDiv || this.editorDiv.innerText.trim() === "";
15001 this.isEditing = false;
15003 this.parent.setEditingState(true);
15004 this.parent.div.classList.add("freetextEditing");
15010 this.editorDiv.normalize();
15011 let prevChild = null;
15012 for (const child of this.editorDiv.childNodes) {
15013 if (prevChild?.nodeType === Node.TEXT_NODE && child.nodeName === "BR") {
15016 buffer.push(FreeTextEditor.#getNodeContent(child));
15019 return buffer.join("\n");
15021 #setEditorDimensions() {
15022 const [parentWidth, parentHeight] = this.parentDimensions;
15024 if (this.isAttachedToDOM) {
15025 rect = this.div.getBoundingClientRect();
15031 const savedDisplay = div.style.display;
15032 const savedVisibility = div.classList.contains("hidden");
15033 div.classList.remove("hidden");
15034 div.style.display = "hidden";
15035 currentLayer.div.append(this.div);
15036 rect = div.getBoundingClientRect();
15038 div.style.display = savedDisplay;
15039 div.classList.toggle("hidden", savedVisibility);
15041 if (this.rotation % 180 === this.parentRotation % 180) {
15042 this.width = rect.width / parentWidth;
15043 this.height = rect.height / parentHeight;
15045 this.width = rect.height / parentWidth;
15046 this.height = rect.width / parentHeight;
15048 this.fixAndSetPosition();
15051 if (!this.isInEditMode()) {
15055 this.disableEditMode();
15056 const savedText = this.#content;
15057 const newText = this.#content = this.#extractText().trimEnd();
15058 if (savedText === newText) {
15061 const setText = text => {
15062 this.#content = text;
15067 this.#setContent();
15068 this._uiManager.rebuild(this);
15069 this.#setEditorDimensions();
15076 setText(savedText);
15080 this.#setEditorDimensions();
15082 shouldGetKeyboardEvents() {
15083 return this.isInEditMode();
15085 enterInEditMode() {
15086 this.enableEditMode();
15087 this.editorDiv.focus();
15090 this.enterInEditMode();
15093 if (event.target === this.div && event.key === "Enter") {
15094 this.enterInEditMode();
15095 event.preventDefault();
15098 editorDivKeydown(event) {
15099 FreeTextEditor._keyboardManager.exec(this, event);
15101 editorDivFocus(event) {
15102 this.isEditing = true;
15104 editorDivBlur(event) {
15105 this.isEditing = false;
15107 editorDivInput(event) {
15108 this.parent.div.classList.toggle("freetextEditing", this.isEmpty());
15111 this.editorDiv.setAttribute("role", "comment");
15112 this.editorDiv.removeAttribute("aria-multiline");
15115 this.editorDiv.setAttribute("role", "textbox");
15116 this.editorDiv.setAttribute("aria-multiline", true);
15128 this.editorDiv = document.createElement("div");
15129 this.editorDiv.className = "internal";
15130 this.editorDiv.setAttribute("id", this.#editorDivId);
15131 this.editorDiv.setAttribute("data-l10n-id", "pdfjs-free-text2");
15132 this.editorDiv.setAttribute("data-l10n-attrs", "default-content");
15133 this.enableEditing();
15134 this.editorDiv.contentEditable = true;
15137 } = this.editorDiv;
15138 style.fontSize = `calc(${this.#fontSize}px * var(--scale-factor))`;
15139 style.color = this.#color;
15140 this.div.append(this.editorDiv);
15141 this.overlayDiv = document.createElement("div");
15142 this.overlayDiv.classList.add("overlay", "enabled");
15143 this.div.append(this.overlayDiv);
15144 bindEvents(this, this.div, ["dblclick", "keydown"]);
15146 const [parentWidth, parentHeight] = this.parentDimensions;
15147 if (this.annotationElementId) {
15150 } = this._initialData;
15151 let [tx, ty] = this.getInitialTranslation();
15152 [tx, ty] = this.pageTranslationToScreen(tx, ty);
15153 const [pageWidth, pageHeight] = this.pageDimensions;
15154 const [pageX, pageY] = this.pageTranslation;
15156 switch (this.rotation) {
15158 posX = baseX + (position[0] - pageX) / pageWidth;
15159 posY = baseY + this.height - (position[1] - pageY) / pageHeight;
15162 posX = baseX + (position[0] - pageX) / pageWidth;
15163 posY = baseY - (position[1] - pageY) / pageHeight;
15164 [tx, ty] = [ty, -tx];
15167 posX = baseX - this.width + (position[0] - pageX) / pageWidth;
15168 posY = baseY - (position[1] - pageY) / pageHeight;
15169 [tx, ty] = [-tx, -ty];
15172 posX = baseX + (position[0] - pageX - this.height * pageHeight) / pageWidth;
15173 posY = baseY + (position[1] - pageY - this.width * pageWidth) / pageHeight;
15174 [tx, ty] = [-ty, tx];
15177 this.setAt(posX * parentWidth, posY * parentHeight, tx, ty);
15179 this.setAt(baseX * parentWidth, baseY * parentHeight, this.width * parentWidth, this.height * parentHeight);
15181 this.#setContent();
15182 this._isDraggable = true;
15183 this.editorDiv.contentEditable = false;
15185 this._isDraggable = false;
15186 this.editorDiv.contentEditable = true;
15190 static #getNodeContent(node) {
15191 return (node.nodeType === Node.TEXT_NODE ? node.nodeValue : node.innerText).replaceAll(EOL_PATTERN, "");
15193 editorDivPaste(event) {
15194 const clipboardData = event.clipboardData || window.clipboardData;
15198 if (types.length === 1 && types[0] === "text/plain") {
15201 event.preventDefault();
15202 const paste = FreeTextEditor.#deserializeContent(clipboardData.getData("text") || "").replaceAll(EOL_PATTERN, "\n");
15206 const selection = window.getSelection();
15207 if (!selection.rangeCount) {
15210 this.editorDiv.normalize();
15211 selection.deleteFromDocument();
15212 const range = selection.getRangeAt(0);
15213 if (!paste.includes("\n")) {
15214 range.insertNode(document.createTextNode(paste));
15215 this.editorDiv.normalize();
15216 selection.collapseToStart();
15223 const bufferBefore = [];
15224 const bufferAfter = [];
15225 if (startContainer.nodeType === Node.TEXT_NODE) {
15226 const parent = startContainer.parentElement;
15227 bufferAfter.push(startContainer.nodeValue.slice(startOffset).replaceAll(EOL_PATTERN, ""));
15228 if (parent !== this.editorDiv) {
15229 let buffer = bufferBefore;
15230 for (const child of this.editorDiv.childNodes) {
15231 if (child === parent) {
15232 buffer = bufferAfter;
15235 buffer.push(FreeTextEditor.#getNodeContent(child));
15238 bufferBefore.push(startContainer.nodeValue.slice(0, startOffset).replaceAll(EOL_PATTERN, ""));
15239 } else if (startContainer === this.editorDiv) {
15240 let buffer = bufferBefore;
15242 for (const child of this.editorDiv.childNodes) {
15243 if (i++ === startOffset) {
15244 buffer = bufferAfter;
15246 buffer.push(FreeTextEditor.#getNodeContent(child));
15249 this.#content = `${bufferBefore.join("\n")}${paste}${bufferAfter.join("\n")}`;
15250 this.#setContent();
15251 const newRange = new Range();
15252 let beforeLength = bufferBefore.reduce((acc, line) => acc + line.length, 0);
15255 } of this.editorDiv.childNodes) {
15256 if (firstChild.nodeType === Node.TEXT_NODE) {
15257 const length = firstChild.nodeValue.length;
15258 if (beforeLength <= length) {
15259 newRange.setStart(firstChild, beforeLength);
15260 newRange.setEnd(firstChild, beforeLength);
15263 beforeLength -= length;
15266 selection.removeAllRanges();
15267 selection.addRange(newRange);
15270 this.editorDiv.replaceChildren();
15271 if (!this.#content) {
15274 for (const line of this.#content.split("\n")) {
15275 const div = document.createElement("div");
15276 div.append(line ? document.createTextNode(line) : document.createElement("br"));
15277 this.editorDiv.append(div);
15280 #serializeContent() {
15281 return this.#content.replaceAll("\xa0", " ");
15283 static #deserializeContent(content) {
15284 return content.replaceAll(" ", "\xa0");
15287 return this.editorDiv;
15289 static async deserialize(data, parent, uiManager) {
15290 let initialData = null;
15291 if (data instanceof FreeTextAnnotationElement) {
15294 defaultAppearanceData: {
15311 if (!textContent || textContent.length === 0) {
15314 initialData = data = {
15315 annotationType: AnnotationEditorType.FREETEXT,
15316 color: Array.from(fontColor),
15318 value: textContent.join("\n"),
15319 position: textPosition,
15320 pageIndex: pageNumber - 1,
15321 rect: rect.slice(0),
15328 const editor = await super.deserialize(data, parent, uiManager);
15329 editor.#fontSize = data.fontSize;
15330 editor.#color = Util.makeHexColor(...data.color);
15331 editor.#content = FreeTextEditor.#deserializeContent(data.value);
15332 editor.annotationElementId = data.id || null;
15333 editor._initialData = initialData;
15336 serialize(isForCopying = false) {
15337 if (this.isEmpty()) {
15340 if (this.deleted) {
15341 return this.serializeDeleted();
15343 const padding = FreeTextEditor._internalPadding * this.parentScale;
15344 const rect = this.getRect(padding, padding);
15345 const color = AnnotationEditor._colorManager.convert(this.isAttachedToDOM ? getComputedStyle(this.editorDiv).color : this.#color);
15346 const serialized = {
15347 annotationType: AnnotationEditorType.FREETEXT,
15349 fontSize: this.#fontSize,
15350 value: this.#serializeContent(),
15351 pageIndex: this.pageIndex,
15353 rotation: this.rotation,
15354 structTreeParentId: this._structTreeParentId
15356 if (isForCopying) {
15359 if (this.annotationElementId && !this.#hasElementChanged(serialized)) {
15362 serialized.id = this.annotationElementId;
15365 #hasElementChanged(serialized) {
15371 } = this._initialData;
15372 return this._hasBeenMoved || serialized.value !== value || serialized.fontSize !== fontSize || serialized.color.some((c, i) => c !== color[i]) || serialized.pageIndex !== pageIndex;
15374 renderAnnotationElement(annotation) {
15375 const content = super.renderAnnotationElement(annotation);
15376 if (this.deleted) {
15382 style.fontSize = `calc(${this.#fontSize}px * var(--scale-factor))`;
15383 style.color = this.#color;
15384 content.replaceChildren();
15385 for (const line of this.#content.split("\n")) {
15386 const div = document.createElement("div");
15387 div.append(line ? document.createTextNode(line) : document.createElement("br"));
15388 content.append(div);
15390 const padding = FreeTextEditor._internalPadding * this.parentScale;
15391 annotation.updateEdited({
15392 rect: this.getRect(padding, padding),
15393 popupContent: this.#content
15397 resetAnnotationElement(annotation) {
15398 super.resetAnnotationElement(annotation);
15399 annotation.resetEdited();
15403 ;// ./src/display/editor/drawers/outline.js
15406 static PRECISION = 1e-4;
15408 unreachable("Abstract method `toSVGPath` must be implemented.");
15411 unreachable("Abstract getter `box` must be implemented.");
15413 serialize(_bbox, _rotation) {
15414 unreachable("Abstract method `serialize` must be implemented.");
15416 static _rescale(src, tx, ty, sx, sy, dest) {
15417 dest ||= new Float32Array(src.length);
15418 for (let i = 0, ii = src.length; i < ii; i += 2) {
15419 dest[i] = tx + src[i] * sx;
15420 dest[i + 1] = ty + src[i + 1] * sy;
15424 static _rescaleAndSwap(src, tx, ty, sx, sy, dest) {
15425 dest ||= new Float32Array(src.length);
15426 for (let i = 0, ii = src.length; i < ii; i += 2) {
15427 dest[i] = tx + src[i + 1] * sx;
15428 dest[i + 1] = ty + src[i] * sy;
15432 static _translate(src, tx, ty, dest) {
15433 dest ||= new Float32Array(src.length);
15434 for (let i = 0, ii = src.length; i < ii; i += 2) {
15435 dest[i] = tx + src[i];
15436 dest[i + 1] = ty + src[i + 1];
15440 static svgRound(x) {
15441 return Math.round(x * 10000);
15443 static _normalizePoint(x, y, parentWidth, parentHeight, rotation) {
15444 switch (rotation) {
15446 return [1 - y / parentWidth, x / parentHeight];
15448 return [1 - x / parentWidth, 1 - y / parentHeight];
15450 return [y / parentWidth, 1 - x / parentHeight];
15452 return [x / parentWidth, y / parentHeight];
15455 static _normalizePagePoint(x, y, rotation) {
15456 switch (rotation) {
15460 return [1 - x, 1 - y];
15467 static createBezierPoints(x1, y1, x2, y2, x3, y3) {
15468 return [(x1 + 5 * x2) / 6, (y1 + 5 * y2) / 6, (5 * x2 + x3) / 6, (5 * y2 + y3) / 6, (x2 + x3) / 2, (y2 + y3) / 2];
15472 ;// ./src/display/editor/drawers/freedraw.js
15475 class FreeDrawOutliner {
15481 #last = new Float32Array(18);
15489 static #MIN_DIST = 8;
15490 static #MIN_DIFF = 2;
15491 static #MIN = FreeDrawOutliner.#MIN_DIST + FreeDrawOutliner.#MIN_DIFF;
15495 }, box, scaleFactor, thickness, isLTR, innerMargin = 0) {
15497 this.#thickness = thickness * scaleFactor;
15498 this.#isLTR = isLTR;
15499 this.#last.set([NaN, NaN, NaN, NaN, x, y], 6);
15500 this.#innerMargin = innerMargin;
15501 this.#min_dist = FreeDrawOutliner.#MIN_DIST * scaleFactor;
15502 this.#min = FreeDrawOutliner.#MIN * scaleFactor;
15503 this.#scaleFactor = scaleFactor;
15504 this.#points.push(x, y);
15507 return isNaN(this.#last[8]);
15510 const lastTop = this.#last.subarray(4, 6);
15511 const lastBottom = this.#last.subarray(16, 18);
15512 const [x, y, width, height] = this.#box;
15513 return [(this.#lastX + (lastTop[0] - lastBottom[0]) / 2 - x) / width, (this.#lastY + (lastTop[1] - lastBottom[1]) / 2 - y) / height, (this.#lastX + (lastBottom[0] - lastTop[0]) / 2 - x) / width, (this.#lastY + (lastBottom[1] - lastTop[1]) / 2 - y) / height];
15521 const [layerX, layerY, layerWidth, layerHeight] = this.#box;
15522 let [x1, y1, x2, y2] = this.#last.subarray(8, 12);
15523 const diffX = x - x2;
15524 const diffY = y - y2;
15525 const d = Math.hypot(diffX, diffY);
15526 if (d < this.#min) {
15529 const diffD = d - this.#min_dist;
15530 const K = diffD / d;
15531 const shiftX = K * diffX;
15532 const shiftY = K * diffY;
15539 this.#points?.push(x, y);
15540 const nX = -shiftY / diffD;
15541 const nY = shiftX / diffD;
15542 const thX = nX * this.#thickness;
15543 const thY = nY * this.#thickness;
15544 this.#last.set(this.#last.subarray(2, 8), 0);
15545 this.#last.set([x2 + thX, y2 + thY], 4);
15546 this.#last.set(this.#last.subarray(14, 18), 12);
15547 this.#last.set([x2 - thX, y2 - thY], 16);
15548 if (isNaN(this.#last[6])) {
15549 if (this.#top.length === 0) {
15550 this.#last.set([x1 + thX, y1 + thY], 2);
15551 this.#top.push(NaN, NaN, NaN, NaN, (x1 + thX - layerX) / layerWidth, (y1 + thY - layerY) / layerHeight);
15552 this.#last.set([x1 - thX, y1 - thY], 14);
15553 this.#bottom.push(NaN, NaN, NaN, NaN, (x1 - thX - layerX) / layerWidth, (y1 - thY - layerY) / layerHeight);
15555 this.#last.set([x0, y0, x1, y1, x2, y2], 6);
15556 return !this.isEmpty();
15558 this.#last.set([x0, y0, x1, y1, x2, y2], 6);
15559 const angle = Math.abs(Math.atan2(y0 - y1, x0 - x1) - Math.atan2(shiftY, shiftX));
15560 if (angle < Math.PI / 2) {
15561 [x1, y1, x2, y2] = this.#last.subarray(2, 6);
15562 this.#top.push(NaN, NaN, NaN, NaN, ((x1 + x2) / 2 - layerX) / layerWidth, ((y1 + y2) / 2 - layerY) / layerHeight);
15563 [x1, y1, x0, y0] = this.#last.subarray(14, 18);
15564 this.#bottom.push(NaN, NaN, NaN, NaN, ((x0 + x1) / 2 - layerX) / layerWidth, ((y0 + y1) / 2 - layerY) / layerHeight);
15567 [x0, y0, x1, y1, x2, y2] = this.#last.subarray(0, 6);
15568 this.#top.push(((x0 + 5 * x1) / 6 - layerX) / layerWidth, ((y0 + 5 * y1) / 6 - layerY) / layerHeight, ((5 * x1 + x2) / 6 - layerX) / layerWidth, ((5 * y1 + y2) / 6 - layerY) / layerHeight, ((x1 + x2) / 2 - layerX) / layerWidth, ((y1 + y2) / 2 - layerY) / layerHeight);
15569 [x2, y2, x1, y1, x0, y0] = this.#last.subarray(12, 18);
15570 this.#bottom.push(((x0 + 5 * x1) / 6 - layerX) / layerWidth, ((y0 + 5 * y1) / 6 - layerY) / layerHeight, ((5 * x1 + x2) / 6 - layerX) / layerWidth, ((5 * y1 + y2) / 6 - layerY) / layerHeight, ((x1 + x2) / 2 - layerX) / layerWidth, ((y1 + y2) / 2 - layerY) / layerHeight);
15574 if (this.isEmpty()) {
15577 const top = this.#top;
15578 const bottom = this.#bottom;
15579 if (isNaN(this.#last[6]) && !this.isEmpty()) {
15580 return this.#toSVGPathTwoPoints();
15583 buffer.push(`M${top[4]} ${top[5]}`);
15584 for (let i = 6; i < top.length; i += 6) {
15585 if (isNaN(top[i])) {
15586 buffer.push(`L${top[i + 4]} ${top[i + 5]}`);
15588 buffer.push(`C${top[i]} ${top[i + 1]} ${top[i + 2]} ${top[i + 3]} ${top[i + 4]} ${top[i + 5]}`);
15591 this.#toSVGPathEnd(buffer);
15592 for (let i = bottom.length - 6; i >= 6; i -= 6) {
15593 if (isNaN(bottom[i])) {
15594 buffer.push(`L${bottom[i + 4]} ${bottom[i + 5]}`);
15596 buffer.push(`C${bottom[i]} ${bottom[i + 1]} ${bottom[i + 2]} ${bottom[i + 3]} ${bottom[i + 4]} ${bottom[i + 5]}`);
15599 this.#toSVGPathStart(buffer);
15600 return buffer.join(" ");
15602 #toSVGPathTwoPoints() {
15603 const [x, y, width, height] = this.#box;
15604 const [lastTopX, lastTopY, lastBottomX, lastBottomY] = this.#getLastCoords();
15605 return `M${(this.#last[2] - x) / width} ${(this.#last[3] - y) / height} L${(this.#last[4] - x) / width} ${(this.#last[5] - y) / height} L${lastTopX} ${lastTopY} L${lastBottomX} ${lastBottomY} L${(this.#last[16] - x) / width} ${(this.#last[17] - y) / height} L${(this.#last[14] - x) / width} ${(this.#last[15] - y) / height} Z`;
15607 #toSVGPathStart(buffer) {
15608 const bottom = this.#bottom;
15609 buffer.push(`L${bottom[4]} ${bottom[5]} Z`);
15611 #toSVGPathEnd(buffer) {
15612 const [x, y, width, height] = this.#box;
15613 const lastTop = this.#last.subarray(4, 6);
15614 const lastBottom = this.#last.subarray(16, 18);
15615 const [lastTopX, lastTopY, lastBottomX, lastBottomY] = this.#getLastCoords();
15616 buffer.push(`L${(lastTop[0] - x) / width} ${(lastTop[1] - y) / height} L${lastTopX} ${lastTopY} L${lastBottomX} ${lastBottomY} L${(lastBottom[0] - x) / width} ${(lastBottom[1] - y) / height}`);
15618 newFreeDrawOutline(outline, points, box, scaleFactor, innerMargin, isLTR) {
15619 return new FreeDrawOutline(outline, points, box, scaleFactor, innerMargin, isLTR);
15622 const top = this.#top;
15623 const bottom = this.#bottom;
15624 const last = this.#last;
15625 const [layerX, layerY, layerWidth, layerHeight] = this.#box;
15626 const points = new Float32Array((this.#points?.length ?? 0) + 2);
15627 for (let i = 0, ii = points.length - 2; i < ii; i += 2) {
15628 points[i] = (this.#points[i] - layerX) / layerWidth;
15629 points[i + 1] = (this.#points[i + 1] - layerY) / layerHeight;
15631 points[points.length - 2] = (this.#lastX - layerX) / layerWidth;
15632 points[points.length - 1] = (this.#lastY - layerY) / layerHeight;
15633 if (isNaN(last[6]) && !this.isEmpty()) {
15634 return this.#getOutlineTwoPoints(points);
15636 const outline = new Float32Array(this.#top.length + 24 + this.#bottom.length);
15637 let N = top.length;
15638 for (let i = 0; i < N; i += 2) {
15639 if (isNaN(top[i])) {
15640 outline[i] = outline[i + 1] = NaN;
15643 outline[i] = top[i];
15644 outline[i + 1] = top[i + 1];
15646 N = this.#getOutlineEnd(outline, N);
15647 for (let i = bottom.length - 6; i >= 6; i -= 6) {
15648 for (let j = 0; j < 6; j += 2) {
15649 if (isNaN(bottom[i + j])) {
15650 outline[N] = outline[N + 1] = NaN;
15654 outline[N] = bottom[i + j];
15655 outline[N + 1] = bottom[i + j + 1];
15659 this.#getOutlineStart(outline, N);
15660 return this.newFreeDrawOutline(outline, points, this.#box, this.#scaleFactor, this.#innerMargin, this.#isLTR);
15662 #getOutlineTwoPoints(points) {
15663 const last = this.#last;
15664 const [layerX, layerY, layerWidth, layerHeight] = this.#box;
15665 const [lastTopX, lastTopY, lastBottomX, lastBottomY] = this.#getLastCoords();
15666 const outline = new Float32Array(36);
15667 outline.set([NaN, NaN, NaN, NaN, (last[2] - layerX) / layerWidth, (last[3] - layerY) / layerHeight, NaN, NaN, NaN, NaN, (last[4] - layerX) / layerWidth, (last[5] - layerY) / layerHeight, NaN, NaN, NaN, NaN, lastTopX, lastTopY, NaN, NaN, NaN, NaN, lastBottomX, lastBottomY, NaN, NaN, NaN, NaN, (last[16] - layerX) / layerWidth, (last[17] - layerY) / layerHeight, NaN, NaN, NaN, NaN, (last[14] - layerX) / layerWidth, (last[15] - layerY) / layerHeight], 0);
15668 return this.newFreeDrawOutline(outline, points, this.#box, this.#scaleFactor, this.#innerMargin, this.#isLTR);
15670 #getOutlineStart(outline, pos) {
15671 const bottom = this.#bottom;
15672 outline.set([NaN, NaN, NaN, NaN, bottom[4], bottom[5]], pos);
15675 #getOutlineEnd(outline, pos) {
15676 const lastTop = this.#last.subarray(4, 6);
15677 const lastBottom = this.#last.subarray(16, 18);
15678 const [layerX, layerY, layerWidth, layerHeight] = this.#box;
15679 const [lastTopX, lastTopY, lastBottomX, lastBottomY] = this.#getLastCoords();
15680 outline.set([NaN, NaN, NaN, NaN, (lastTop[0] - layerX) / layerWidth, (lastTop[1] - layerY) / layerHeight, NaN, NaN, NaN, NaN, lastTopX, lastTopY, NaN, NaN, NaN, NaN, lastBottomX, lastBottomY, NaN, NaN, NaN, NaN, (lastBottom[0] - layerX) / layerWidth, (lastBottom[1] - layerY) / layerHeight], pos);
15684 class FreeDrawOutline extends Outline {
15686 #bbox = new Float32Array(4);
15692 constructor(outline, points, box, scaleFactor, innerMargin, isLTR) {
15694 this.#outline = outline;
15695 this.#points = points;
15697 this.#scaleFactor = scaleFactor;
15698 this.#innerMargin = innerMargin;
15699 this.#isLTR = isLTR;
15700 this.lastPoint = [NaN, NaN];
15701 this.#computeMinMax(isLTR);
15702 const [x, y, width, height] = this.#bbox;
15703 for (let i = 0, ii = outline.length; i < ii; i += 2) {
15704 outline[i] = (outline[i] - x) / width;
15705 outline[i + 1] = (outline[i + 1] - y) / height;
15707 for (let i = 0, ii = points.length; i < ii; i += 2) {
15708 points[i] = (points[i] - x) / width;
15709 points[i + 1] = (points[i + 1] - y) / height;
15713 const buffer = [`M${this.#outline[4]} ${this.#outline[5]}`];
15714 for (let i = 6, ii = this.#outline.length; i < ii; i += 6) {
15715 if (isNaN(this.#outline[i])) {
15716 buffer.push(`L${this.#outline[i + 4]} ${this.#outline[i + 5]}`);
15719 buffer.push(`C${this.#outline[i]} ${this.#outline[i + 1]} ${this.#outline[i + 2]} ${this.#outline[i + 3]} ${this.#outline[i + 4]} ${this.#outline[i + 5]}`);
15722 return buffer.join(" ");
15724 serialize([blX, blY, trX, trY], rotation) {
15725 const width = trX - blX;
15726 const height = trY - blY;
15729 switch (rotation) {
15731 outline = Outline._rescale(this.#outline, blX, trY, width, -height);
15732 points = Outline._rescale(this.#points, blX, trY, width, -height);
15735 outline = Outline._rescaleAndSwap(this.#outline, blX, blY, width, height);
15736 points = Outline._rescaleAndSwap(this.#points, blX, blY, width, height);
15739 outline = Outline._rescale(this.#outline, trX, blY, -width, height);
15740 points = Outline._rescale(this.#points, trX, blY, -width, height);
15743 outline = Outline._rescaleAndSwap(this.#outline, trX, trY, -width, -height);
15744 points = Outline._rescaleAndSwap(this.#points, trX, trY, -width, -height);
15748 outline: Array.from(outline),
15749 points: [Array.from(points)]
15752 #computeMinMax(isLTR) {
15753 const outline = this.#outline;
15754 let lastX = outline[4];
15755 let lastY = outline[5];
15760 let lastPointX = lastX;
15761 let lastPointY = lastY;
15762 const ltrCallback = isLTR ? Math.max : Math.min;
15763 for (let i = 6, ii = outline.length; i < ii; i += 6) {
15764 if (isNaN(outline[i])) {
15765 minX = Math.min(minX, outline[i + 4]);
15766 minY = Math.min(minY, outline[i + 5]);
15767 maxX = Math.max(maxX, outline[i + 4]);
15768 maxY = Math.max(maxY, outline[i + 5]);
15769 if (lastPointY < outline[i + 5]) {
15770 lastPointX = outline[i + 4];
15771 lastPointY = outline[i + 5];
15772 } else if (lastPointY === outline[i + 5]) {
15773 lastPointX = ltrCallback(lastPointX, outline[i + 4]);
15776 const bbox = Util.bezierBoundingBox(lastX, lastY, ...outline.slice(i, i + 6));
15777 minX = Math.min(minX, bbox[0]);
15778 minY = Math.min(minY, bbox[1]);
15779 maxX = Math.max(maxX, bbox[2]);
15780 maxY = Math.max(maxY, bbox[3]);
15781 if (lastPointY < bbox[3]) {
15782 lastPointX = bbox[2];
15783 lastPointY = bbox[3];
15784 } else if (lastPointY === bbox[3]) {
15785 lastPointX = ltrCallback(lastPointX, bbox[2]);
15788 lastX = outline[i + 4];
15789 lastY = outline[i + 5];
15791 const bbox = this.#bbox;
15792 bbox[0] = minX - this.#innerMargin;
15793 bbox[1] = minY - this.#innerMargin;
15794 bbox[2] = maxX - minX + 2 * this.#innerMargin;
15795 bbox[3] = maxY - minY + 2 * this.#innerMargin;
15796 this.lastPoint = [lastPointX, lastPointY];
15801 newOutliner(point, box, scaleFactor, thickness, isLTR, innerMargin = 0) {
15802 return new FreeDrawOutliner(point, box, scaleFactor, thickness, isLTR, innerMargin);
15804 getNewOutline(thickness, innerMargin) {
15805 const [x, y, width, height] = this.#bbox;
15806 const [layerX, layerY, layerWidth, layerHeight] = this.#box;
15807 const sx = width * layerWidth;
15808 const sy = height * layerHeight;
15809 const tx = x * layerWidth + layerX;
15810 const ty = y * layerHeight + layerY;
15811 const outliner = this.newOutliner({
15812 x: this.#points[0] * sx + tx,
15813 y: this.#points[1] * sy + ty
15814 }, this.#box, this.#scaleFactor, thickness, this.#isLTR, innerMargin ?? this.#innerMargin);
15815 for (let i = 2; i < this.#points.length; i += 2) {
15817 x: this.#points[i] * sx + tx,
15818 y: this.#points[i + 1] * sy + ty
15821 return outliner.getOutlines();
15825 ;// ./src/display/editor/drawers/highlight.js
15828 class HighlightOutliner {
15831 #verticalEdges = [];
15833 constructor(boxes, borderWidth = 0, innerMargin = 0, isLTR = true) {
15834 let minX = Infinity;
15835 let maxX = -Infinity;
15836 let minY = Infinity;
15837 let maxY = -Infinity;
15838 const NUMBER_OF_DIGITS = 4;
15839 const EPSILON = 10 ** -NUMBER_OF_DIGITS;
15846 const x1 = Math.floor((x - borderWidth) / EPSILON) * EPSILON;
15847 const x2 = Math.ceil((x + width + borderWidth) / EPSILON) * EPSILON;
15848 const y1 = Math.floor((y - borderWidth) / EPSILON) * EPSILON;
15849 const y2 = Math.ceil((y + height + borderWidth) / EPSILON) * EPSILON;
15850 const left = [x1, y1, y2, true];
15851 const right = [x2, y1, y2, false];
15852 this.#verticalEdges.push(left, right);
15853 minX = Math.min(minX, x1);
15854 maxX = Math.max(maxX, x2);
15855 minY = Math.min(minY, y1);
15856 maxY = Math.max(maxY, y2);
15858 const bboxWidth = maxX - minX + 2 * innerMargin;
15859 const bboxHeight = maxY - minY + 2 * innerMargin;
15860 const shiftedMinX = minX - innerMargin;
15861 const shiftedMinY = minY - innerMargin;
15862 const lastEdge = this.#verticalEdges.at(isLTR ? -1 : -2);
15863 const lastPoint = [lastEdge[0], lastEdge[2]];
15864 for (const edge of this.#verticalEdges) {
15865 const [x, y1, y2] = edge;
15866 edge[0] = (x - shiftedMinX) / bboxWidth;
15867 edge[1] = (y1 - shiftedMinY) / bboxHeight;
15868 edge[2] = (y2 - shiftedMinY) / bboxHeight;
15870 this.#box = new Float32Array([shiftedMinX, shiftedMinY, bboxWidth, bboxHeight]);
15871 this.#lastPoint = lastPoint;
15874 this.#verticalEdges.sort((a, b) => a[0] - b[0] || a[1] - b[1] || a[2] - b[2]);
15875 const outlineVerticalEdges = [];
15876 for (const edge of this.#verticalEdges) {
15878 outlineVerticalEdges.push(...this.#breakEdge(edge));
15879 this.#insert(edge);
15881 this.#remove(edge);
15882 outlineVerticalEdges.push(...this.#breakEdge(edge));
15885 return this.#getOutlines(outlineVerticalEdges);
15887 #getOutlines(outlineVerticalEdges) {
15889 const allEdges = new Set();
15890 for (const edge of outlineVerticalEdges) {
15891 const [x, y1, y2] = edge;
15892 edges.push([x, y1, edge], [x, y2, edge]);
15894 edges.sort((a, b) => a[1] - b[1] || a[0] - b[0]);
15895 for (let i = 0, ii = edges.length; i < ii; i += 2) {
15896 const edge1 = edges[i][2];
15897 const edge2 = edges[i + 1][2];
15900 allEdges.add(edge1);
15901 allEdges.add(edge2);
15903 const outlines = [];
15905 while (allEdges.size > 0) {
15906 const edge = allEdges.values().next().value;
15907 let [x, y1, y2, edge1, edge2] = edge;
15908 allEdges.delete(edge);
15909 let lastPointX = x;
15910 let lastPointY = y1;
15912 outlines.push(outline);
15915 if (allEdges.has(edge1)) {
15917 } else if (allEdges.has(edge2)) {
15922 allEdges.delete(e);
15923 [x, y1, y2, edge1, edge2] = e;
15924 if (lastPointX !== x) {
15925 outline.push(lastPointX, lastPointY, x, lastPointY === y1 ? y1 : y2);
15928 lastPointY = lastPointY === y1 ? y2 : y1;
15930 outline.push(lastPointX, lastPointY);
15932 return new HighlightOutline(outlines, this.#box, this.#lastPoint);
15935 const array = this.#intervals;
15937 let end = array.length - 1;
15938 while (start <= end) {
15939 const middle = start + end >> 1;
15940 const y1 = array[middle][0];
15945 start = middle + 1;
15952 #insert([, y1, y2]) {
15953 const index = this.#binarySearch(y1);
15954 this.#intervals.splice(index, 0, [y1, y2]);
15956 #remove([, y1, y2]) {
15957 const index = this.#binarySearch(y1);
15958 for (let i = index; i < this.#intervals.length; i++) {
15959 const [start, end] = this.#intervals[i];
15960 if (start !== y1) {
15963 if (start === y1 && end === y2) {
15964 this.#intervals.splice(i, 1);
15968 for (let i = index - 1; i >= 0; i--) {
15969 const [start, end] = this.#intervals[i];
15970 if (start !== y1) {
15973 if (start === y1 && end === y2) {
15974 this.#intervals.splice(i, 1);
15980 const [x, y1, y2] = edge;
15981 const results = [[x, y1, y2]];
15982 const index = this.#binarySearch(y2);
15983 for (let i = 0; i < index; i++) {
15984 const [start, end] = this.#intervals[i];
15985 for (let j = 0, jj = results.length; j < jj; j++) {
15986 const [, y3, y4] = results[j];
15987 if (end <= y3 || y4 <= start) {
15992 results[j][1] = end;
15997 results.splice(j, 1);
16003 results[j][2] = start;
16005 results.push([x, end, y4]);
16012 class HighlightOutline extends Outline {
16015 constructor(outlines, box, lastPoint) {
16017 this.#outlines = outlines;
16019 this.lastPoint = lastPoint;
16023 for (const polygon of this.#outlines) {
16024 let [prevX, prevY] = polygon;
16025 buffer.push(`M${prevX} ${prevY}`);
16026 for (let i = 2; i < polygon.length; i += 2) {
16027 const x = polygon[i];
16028 const y = polygon[i + 1];
16030 buffer.push(`V${y}`);
16032 } else if (y === prevY) {
16033 buffer.push(`H${x}`);
16039 return buffer.join(" ");
16041 serialize([blX, blY, trX, trY], _rotation) {
16042 const outlines = [];
16043 const width = trX - blX;
16044 const height = trY - blY;
16045 for (const outline of this.#outlines) {
16046 const points = new Array(outline.length);
16047 for (let i = 0; i < outline.length; i += 2) {
16048 points[i] = blX + outline[i] * width;
16049 points[i + 1] = trY - outline[i + 1] * height;
16051 outlines.push(points);
16058 get classNamesForOutlining() {
16059 return ["highlightOutline"];
16062 class FreeHighlightOutliner extends FreeDrawOutliner {
16063 newFreeDrawOutline(outline, points, box, scaleFactor, innerMargin, isLTR) {
16064 return new FreeHighlightOutline(outline, points, box, scaleFactor, innerMargin, isLTR);
16067 class FreeHighlightOutline extends FreeDrawOutline {
16068 newOutliner(point, box, scaleFactor, thickness, isLTR, innerMargin = 0) {
16069 return new FreeHighlightOutliner(point, box, scaleFactor, thickness, isLTR, innerMargin);
16073 ;// ./src/display/editor/color_picker.js
16077 class ColorPicker {
16079 #buttonSwatch = null;
16082 #dropdownWasFromKeyboard = false;
16083 #isMainColorPicker = false;
16086 #openDropdownAC = null;
16089 static #l10nColor = null;
16090 static get _keyboardManager() {
16091 return shadow(this, "_keyboardManager", new KeyboardManager([[["Escape", "mac+Escape"], ColorPicker.prototype._hideDropdownFromKeyboard], [[" ", "mac+ "], ColorPicker.prototype._colorSelectFromKeyboard], [["ArrowDown", "ArrowRight", "mac+ArrowDown", "mac+ArrowRight"], ColorPicker.prototype._moveToNext], [["ArrowUp", "ArrowLeft", "mac+ArrowUp", "mac+ArrowLeft"], ColorPicker.prototype._moveToPrevious], [["Home", "mac+Home"], ColorPicker.prototype._moveToBeginning], [["End", "mac+End"], ColorPicker.prototype._moveToEnd]]));
16098 this.#isMainColorPicker = false;
16099 this.#type = AnnotationEditorParamsType.HIGHLIGHT_COLOR;
16100 this.#editor = editor;
16102 this.#isMainColorPicker = true;
16103 this.#type = AnnotationEditorParamsType.HIGHLIGHT_DEFAULT_COLOR;
16105 this.#uiManager = editor?._uiManager || uiManager;
16106 this.#eventBus = this.#uiManager._eventBus;
16107 this.#defaultColor = editor?.color || this.#uiManager?.highlightColors.values().next().value || "#FFFF98";
16108 ColorPicker.#l10nColor ||= Object.freeze({
16109 blue: "pdfjs-editor-colorpicker-blue",
16110 green: "pdfjs-editor-colorpicker-green",
16111 pink: "pdfjs-editor-colorpicker-pink",
16112 red: "pdfjs-editor-colorpicker-red",
16113 yellow: "pdfjs-editor-colorpicker-yellow"
16117 const button = this.#button = document.createElement("button");
16118 button.className = "colorPicker";
16119 button.tabIndex = "0";
16120 button.setAttribute("data-l10n-id", "pdfjs-editor-colorpicker-button");
16121 button.setAttribute("aria-haspopup", true);
16122 const signal = this.#uiManager._signal;
16123 button.addEventListener("click", this.#openDropdown.bind(this), {
16126 button.addEventListener("keydown", this.#keyDown.bind(this), {
16129 const swatch = this.#buttonSwatch = document.createElement("span");
16130 swatch.className = "swatch";
16131 swatch.setAttribute("aria-hidden", true);
16132 swatch.style.backgroundColor = this.#defaultColor;
16133 button.append(swatch);
16136 renderMainDropdown() {
16137 const dropdown = this.#dropdown = this.#getDropdownRoot();
16138 dropdown.setAttribute("aria-orientation", "horizontal");
16139 dropdown.setAttribute("aria-labelledby", "highlightColorPickerLabel");
16142 #getDropdownRoot() {
16143 const div = document.createElement("div");
16144 const signal = this.#uiManager._signal;
16145 div.addEventListener("contextmenu", noContextMenu, {
16148 div.className = "dropdown";
16149 div.role = "listbox";
16150 div.setAttribute("aria-multiselectable", false);
16151 div.setAttribute("aria-orientation", "vertical");
16152 div.setAttribute("data-l10n-id", "pdfjs-editor-colorpicker-dropdown");
16153 for (const [name, color] of this.#uiManager.highlightColors) {
16154 const button = document.createElement("button");
16155 button.tabIndex = "0";
16156 button.role = "option";
16157 button.setAttribute("data-color", color);
16158 button.title = name;
16159 button.setAttribute("data-l10n-id", ColorPicker.#l10nColor[name]);
16160 const swatch = document.createElement("span");
16161 button.append(swatch);
16162 swatch.className = "swatch";
16163 swatch.style.backgroundColor = color;
16164 button.setAttribute("aria-selected", color === this.#defaultColor);
16165 button.addEventListener("click", this.#colorSelect.bind(this, color), {
16168 div.append(button);
16170 div.addEventListener("keydown", this.#keyDown.bind(this), {
16175 #colorSelect(color, event) {
16176 event.stopPropagation();
16177 this.#eventBus.dispatch("switchannotationeditorparams", {
16183 _colorSelectFromKeyboard(event) {
16184 if (event.target === this.#button) {
16185 this.#openDropdown(event);
16188 const color = event.target.getAttribute("data-color");
16192 this.#colorSelect(color, event);
16194 _moveToNext(event) {
16195 if (!this.#isDropdownVisible) {
16196 this.#openDropdown(event);
16199 if (event.target === this.#button) {
16200 this.#dropdown.firstChild?.focus();
16203 event.target.nextSibling?.focus();
16205 _moveToPrevious(event) {
16206 if (event.target === this.#dropdown?.firstChild || event.target === this.#button) {
16207 if (this.#isDropdownVisible) {
16208 this._hideDropdownFromKeyboard();
16212 if (!this.#isDropdownVisible) {
16213 this.#openDropdown(event);
16215 event.target.previousSibling?.focus();
16217 _moveToBeginning(event) {
16218 if (!this.#isDropdownVisible) {
16219 this.#openDropdown(event);
16222 this.#dropdown.firstChild?.focus();
16224 _moveToEnd(event) {
16225 if (!this.#isDropdownVisible) {
16226 this.#openDropdown(event);
16229 this.#dropdown.lastChild?.focus();
16232 ColorPicker._keyboardManager.exec(this, event);
16234 #openDropdown(event) {
16235 if (this.#isDropdownVisible) {
16236 this.hideDropdown();
16239 this.#dropdownWasFromKeyboard = event.detail === 0;
16240 if (!this.#openDropdownAC) {
16241 this.#openDropdownAC = new AbortController();
16242 window.addEventListener("pointerdown", this.#pointerDown.bind(this), {
16243 signal: this.#uiManager.combinedSignal(this.#openDropdownAC)
16246 if (this.#dropdown) {
16247 this.#dropdown.classList.remove("hidden");
16250 const root = this.#dropdown = this.#getDropdownRoot();
16251 this.#button.append(root);
16253 #pointerDown(event) {
16254 if (this.#dropdown?.contains(event.target)) {
16257 this.hideDropdown();
16260 this.#dropdown?.classList.add("hidden");
16261 this.#openDropdownAC?.abort();
16262 this.#openDropdownAC = null;
16264 get #isDropdownVisible() {
16265 return this.#dropdown && !this.#dropdown.classList.contains("hidden");
16267 _hideDropdownFromKeyboard() {
16268 if (this.#isMainColorPicker) {
16271 if (!this.#isDropdownVisible) {
16272 this.#editor?.unselect();
16275 this.hideDropdown();
16276 this.#button.focus({
16277 preventScroll: true,
16278 focusVisible: this.#dropdownWasFromKeyboard
16281 updateColor(color) {
16282 if (this.#buttonSwatch) {
16283 this.#buttonSwatch.style.backgroundColor = color;
16285 if (!this.#dropdown) {
16288 const i = this.#uiManager.highlightColors.values();
16289 for (const child of this.#dropdown.children) {
16290 child.setAttribute("aria-selected", i.next().value === color);
16294 this.#button?.remove();
16295 this.#button = null;
16296 this.#buttonSwatch = null;
16297 this.#dropdown?.remove();
16298 this.#dropdown = null;
16302 ;// ./src/display/editor/highlight.js
16310 class HighlightEditor extends AnnotationEditor {
16311 #anchorNode = null;
16314 #clipPathId = null;
16315 #colorPicker = null;
16316 #focusOutlines = null;
16319 #highlightDiv = null;
16320 #highlightOutlines = null;
16322 #isFreeHighlight = false;
16328 #methodOfCreation = "";
16329 static _defaultColor = null;
16330 static _defaultOpacity = 1;
16331 static _defaultThickness = 12;
16332 static _type = "highlight";
16333 static _editorType = AnnotationEditorType.HIGHLIGHT;
16334 static _freeHighlightId = -1;
16335 static _freeHighlight = null;
16336 static _freeHighlightClipId = "";
16337 static get _keyboardManager() {
16338 const proto = HighlightEditor.prototype;
16339 return shadow(this, "_keyboardManager", new KeyboardManager([[["ArrowLeft", "mac+ArrowLeft"], proto._moveCaret, {
16341 }], [["ArrowRight", "mac+ArrowRight"], proto._moveCaret, {
16343 }], [["ArrowUp", "mac+ArrowUp"], proto._moveCaret, {
16345 }], [["ArrowDown", "mac+ArrowDown"], proto._moveCaret, {
16349 constructor(params) {
16352 name: "highlightEditor"
16354 this.color = params.color || HighlightEditor._defaultColor;
16355 this.#thickness = params.thickness || HighlightEditor._defaultThickness;
16356 this.#opacity = params.opacity || HighlightEditor._defaultOpacity;
16357 this.#boxes = params.boxes || null;
16358 this.#methodOfCreation = params.methodOfCreation || "";
16359 this.#text = params.text || "";
16360 this._isDraggable = false;
16361 if (params.highlightId > -1) {
16362 this.#isFreeHighlight = true;
16363 this.#createFreeOutlines(params);
16364 this.#addToDrawLayer();
16365 } else if (this.#boxes) {
16366 this.#anchorNode = params.anchorNode;
16367 this.#anchorOffset = params.anchorOffset;
16368 this.#focusNode = params.focusNode;
16369 this.#focusOffset = params.focusOffset;
16370 this.#createOutlines();
16371 this.#addToDrawLayer();
16372 this.rotate(this.rotation);
16375 get telemetryInitialData() {
16378 type: this.#isFreeHighlight ? "free_highlight" : "highlight",
16379 color: this._uiManager.highlightColorNames.get(this.color),
16380 thickness: this.#thickness,
16381 methodOfCreation: this.#methodOfCreation
16384 get telemetryFinalData() {
16387 color: this._uiManager.highlightColorNames.get(this.color)
16390 static computeTelemetryFinalData(data) {
16392 numberOfColors: data.get("color").size
16395 #createOutlines() {
16396 const outliner = new HighlightOutliner(this.#boxes, 0.001);
16397 this.#highlightOutlines = outliner.getOutlines();
16398 [this.x, this.y, this.width, this.height] = this.#highlightOutlines.box;
16399 const outlinerForOutline = new HighlightOutliner(this.#boxes, 0.0025, 0.001, this._uiManager.direction === "ltr");
16400 this.#focusOutlines = outlinerForOutline.getOutlines();
16403 } = this.#focusOutlines;
16404 this.#lastPoint = [(lastPoint[0] - this.x) / this.width, (lastPoint[1] - this.y) / this.height];
16406 #createFreeOutlines({
16411 this.#highlightOutlines = highlightOutlines;
16412 const extraThickness = 1.5;
16413 this.#focusOutlines = highlightOutlines.getNewOutline(this.#thickness / 2 + extraThickness, 0.0025);
16414 if (highlightId >= 0) {
16415 this.#id = highlightId;
16416 this.#clipPathId = clipPathId;
16417 this.parent.drawLayer.finalizeDraw(highlightId, {
16418 bbox: highlightOutlines.box,
16420 d: highlightOutlines.toSVGPath()
16423 this.#outlineId = this.parent.drawLayer.drawOutline({
16425 highlightOutline: true,
16428 bbox: this.#focusOutlines.box,
16430 d: this.#focusOutlines.toSVGPath()
16433 } else if (this.parent) {
16434 const angle = this.parent.viewport.rotation;
16435 this.parent.drawLayer.updateProperties(this.#id, {
16436 bbox: HighlightEditor.#rotateBbox(this.#highlightOutlines.box, (angle - this.rotation + 360) % 360),
16438 d: highlightOutlines.toSVGPath()
16441 this.parent.drawLayer.updateProperties(this.#outlineId, {
16442 bbox: HighlightEditor.#rotateBbox(this.#focusOutlines.box, angle),
16444 d: this.#focusOutlines.toSVGPath()
16448 const [x, y, width, height] = highlightOutlines.box;
16449 switch (this.rotation) {
16453 this.width = width;
16454 this.height = height;
16458 const [pageWidth, pageHeight] = this.parentDimensions;
16461 this.width = width * pageHeight / pageWidth;
16462 this.height = height * pageWidth / pageHeight;
16468 this.width = width;
16469 this.height = height;
16473 const [pageWidth, pageHeight] = this.parentDimensions;
16476 this.width = width * pageHeight / pageWidth;
16477 this.height = height * pageWidth / pageHeight;
16483 } = this.#focusOutlines;
16484 this.#lastPoint = [(lastPoint[0] - x) / width, (lastPoint[1] - y) / height];
16486 static initialize(l10n, uiManager) {
16487 AnnotationEditor.initialize(l10n, uiManager);
16488 HighlightEditor._defaultColor ||= uiManager.highlightColors?.values().next().value || "#fff066";
16490 static updateDefaultParams(type, value) {
16492 case AnnotationEditorParamsType.HIGHLIGHT_DEFAULT_COLOR:
16493 HighlightEditor._defaultColor = value;
16495 case AnnotationEditorParamsType.HIGHLIGHT_THICKNESS:
16496 HighlightEditor._defaultThickness = value;
16500 translateInPage(x, y) {}
16501 get toolbarPosition() {
16502 return this.#lastPoint;
16504 updateParams(type, value) {
16506 case AnnotationEditorParamsType.HIGHLIGHT_COLOR:
16507 this.#updateColor(value);
16509 case AnnotationEditorParamsType.HIGHLIGHT_THICKNESS:
16510 this.#updateThickness(value);
16514 static get defaultPropertiesToUpdate() {
16515 return [[AnnotationEditorParamsType.HIGHLIGHT_DEFAULT_COLOR, HighlightEditor._defaultColor], [AnnotationEditorParamsType.HIGHLIGHT_THICKNESS, HighlightEditor._defaultThickness]];
16517 get propertiesToUpdate() {
16518 return [[AnnotationEditorParamsType.HIGHLIGHT_COLOR, this.color || HighlightEditor._defaultColor], [AnnotationEditorParamsType.HIGHLIGHT_THICKNESS, this.#thickness || HighlightEditor._defaultThickness], [AnnotationEditorParamsType.HIGHLIGHT_FREE, this.#isFreeHighlight]];
16520 #updateColor(color) {
16521 const setColorAndOpacity = (col, opa) => {
16523 this.#opacity = opa;
16524 this.parent?.drawLayer.updateProperties(this.#id, {
16527 "fill-opacity": opa
16530 this.#colorPicker?.updateColor(col);
16532 const savedColor = this.color;
16533 const savedOpacity = this.#opacity;
16535 cmd: setColorAndOpacity.bind(this, color, HighlightEditor._defaultOpacity),
16536 undo: setColorAndOpacity.bind(this, savedColor, savedOpacity),
16537 post: this._uiManager.updateUI.bind(this._uiManager, this),
16539 type: AnnotationEditorParamsType.HIGHLIGHT_COLOR,
16540 overwriteIfSameType: true,
16543 this._reportTelemetry({
16544 action: "color_changed",
16545 color: this._uiManager.highlightColorNames.get(color)
16548 #updateThickness(thickness) {
16549 const savedThickness = this.#thickness;
16550 const setThickness = th => {
16551 this.#thickness = th;
16552 this.#changeThickness(th);
16555 cmd: setThickness.bind(this, thickness),
16556 undo: setThickness.bind(this, savedThickness),
16557 post: this._uiManager.updateUI.bind(this._uiManager, this),
16559 type: AnnotationEditorParamsType.INK_THICKNESS,
16560 overwriteIfSameType: true,
16563 this._reportTelemetry({
16564 action: "thickness_changed",
16568 async addEditToolbar() {
16569 const toolbar = await super.addEditToolbar();
16573 if (this._uiManager.highlightColors) {
16574 this.#colorPicker = new ColorPicker({
16577 toolbar.addColorPicker(this.#colorPicker);
16582 super.disableEditing();
16583 this.div.classList.toggle("disabled", true);
16586 super.enableEditing();
16587 this.div.classList.toggle("disabled", false);
16589 fixAndSetPosition() {
16590 return super.fixAndSetPosition(this.#getRotation());
16592 getBaseTranslation() {
16596 return super.getRect(tx, ty, this.#getRotation());
16599 if (!this.annotationElementId) {
16600 this.parent.addUndoableEditor(this);
16607 this.#cleanDrawLayer();
16608 this._reportTelemetry({
16614 if (!this.parent) {
16618 if (this.div === null) {
16621 this.#addToDrawLayer();
16622 if (!this.isAttachedToDOM) {
16623 this.parent.add(this);
16626 setParent(parent) {
16627 let mustBeSelected = false;
16628 if (this.parent && !parent) {
16629 this.#cleanDrawLayer();
16630 } else if (parent) {
16631 this.#addToDrawLayer(parent);
16632 mustBeSelected = !this.parent && this.div?.classList.contains("selectedEditor");
16634 super.setParent(parent);
16635 this.show(this._isVisible);
16636 if (mustBeSelected) {
16640 #changeThickness(thickness) {
16641 if (!this.#isFreeHighlight) {
16644 this.#createFreeOutlines({
16645 highlightOutlines: this.#highlightOutlines.getNewOutline(thickness / 2)
16647 this.fixAndSetPosition();
16648 const [parentWidth, parentHeight] = this.parentDimensions;
16649 this.setDims(this.width * parentWidth, this.height * parentHeight);
16651 #cleanDrawLayer() {
16652 if (this.#id === null || !this.parent) {
16655 this.parent.drawLayer.remove(this.#id);
16657 this.parent.drawLayer.remove(this.#outlineId);
16658 this.#outlineId = null;
16660 #addToDrawLayer(parent = this.parent) {
16661 if (this.#id !== null) {
16666 clipPathId: this.#clipPathId
16667 } = parent.drawLayer.draw({
16668 bbox: this.#highlightOutlines.box,
16670 viewBox: "0 0 1 1",
16672 "fill-opacity": this.#opacity
16676 free: this.#isFreeHighlight
16679 d: this.#highlightOutlines.toSVGPath()
16682 this.#outlineId = parent.drawLayer.drawOutline({
16684 highlightOutline: true,
16685 free: this.#isFreeHighlight
16687 bbox: this.#focusOutlines.box,
16689 d: this.#focusOutlines.toSVGPath()
16691 }, this.#isFreeHighlight);
16692 if (this.#highlightDiv) {
16693 this.#highlightDiv.style.clipPath = this.#clipPathId;
16696 static #rotateBbox([x, y, width, height], angle) {
16699 return [1 - y - height, x, height, width];
16701 return [1 - x - width, 1 - y - height, width, height];
16703 return [y, 1 - x - width, height, width];
16705 return [x, y, width, height];
16712 if (this.#isFreeHighlight) {
16713 angle = (angle - this.rotation + 360) % 360;
16714 box = HighlightEditor.#rotateBbox(this.#highlightOutlines.box, angle);
16716 box = HighlightEditor.#rotateBbox([this.x, this.y, this.width, this.height], angle);
16718 drawLayer.updateProperties(this.#id, {
16721 "data-main-rotation": angle
16724 drawLayer.updateProperties(this.#outlineId, {
16725 bbox: HighlightEditor.#rotateBbox(this.#focusOutlines.box, angle),
16727 "data-main-rotation": angle
16735 const div = super.render();
16737 div.setAttribute("aria-label", this.#text);
16738 div.setAttribute("role", "mark");
16740 if (this.#isFreeHighlight) {
16741 div.classList.add("free");
16743 this.div.addEventListener("keydown", this.#keydown.bind(this), {
16744 signal: this._uiManager._signal
16747 const highlightDiv = this.#highlightDiv = document.createElement("div");
16748 div.append(highlightDiv);
16749 highlightDiv.setAttribute("aria-hidden", "true");
16750 highlightDiv.className = "internal";
16751 highlightDiv.style.clipPath = this.#clipPathId;
16752 const [parentWidth, parentHeight] = this.parentDimensions;
16753 this.setDims(this.width * parentWidth, this.height * parentHeight);
16754 bindEvents(this, this.#highlightDiv, ["pointerover", "pointerleave"]);
16755 this.enableEditing();
16759 if (!this.isSelected) {
16760 this.parent?.drawLayer.updateProperties(this.#outlineId, {
16768 if (!this.isSelected) {
16769 this.parent?.drawLayer.updateProperties(this.#outlineId, {
16777 HighlightEditor._keyboardManager.exec(this, event);
16779 _moveCaret(direction) {
16780 this.parent.unselect(this);
16781 switch (direction) {
16784 this.#setCaret(true);
16788 this.#setCaret(false);
16793 if (!this.#anchorNode) {
16796 const selection = window.getSelection();
16798 selection.setPosition(this.#anchorNode, this.#anchorOffset);
16800 selection.setPosition(this.#focusNode, this.#focusOffset);
16805 if (!this.#outlineId) {
16808 this.parent?.drawLayer.updateProperties(this.#outlineId, {
16817 if (!this.#outlineId) {
16820 this.parent?.drawLayer.updateProperties(this.#outlineId, {
16825 if (!this.#isFreeHighlight) {
16826 this.#setCaret(false);
16829 get _mustFixPosition() {
16830 return !this.#isFreeHighlight;
16832 show(visible = this._isVisible) {
16833 super.show(visible);
16835 this.parent.drawLayer.updateProperties(this.#id, {
16840 this.parent.drawLayer.updateProperties(this.#outlineId, {
16848 return this.#isFreeHighlight ? this.rotation : 0;
16850 #serializeBoxes() {
16851 if (this.#isFreeHighlight) {
16854 const [pageWidth, pageHeight] = this.pageDimensions;
16855 const [pageX, pageY] = this.pageTranslation;
16856 const boxes = this.#boxes;
16857 const quadPoints = new Float32Array(boxes.length * 8);
16865 const sx = x * pageWidth + pageX;
16866 const sy = (1 - y) * pageHeight + pageY;
16867 quadPoints[i] = quadPoints[i + 4] = sx;
16868 quadPoints[i + 1] = quadPoints[i + 3] = sy;
16869 quadPoints[i + 2] = quadPoints[i + 6] = sx + width * pageWidth;
16870 quadPoints[i + 5] = quadPoints[i + 7] = sy - height * pageHeight;
16875 #serializeOutlines(rect) {
16876 return this.#highlightOutlines.serialize(rect, this.#getRotation());
16878 static startHighlighting(parent, isLTR, {
16886 width: parentWidth,
16887 height: parentHeight
16888 } = textLayer.getBoundingClientRect();
16889 const ac = new AbortController();
16890 const signal = parent.combinedSignal(ac);
16891 const pointerUpCallback = e => {
16893 this.#endHighlight(parent, e);
16895 window.addEventListener("blur", pointerUpCallback, {
16898 window.addEventListener("pointerup", pointerUpCallback, {
16901 window.addEventListener("pointerdown", stopEvent, {
16906 window.addEventListener("contextmenu", noContextMenu, {
16909 textLayer.addEventListener("pointermove", this.#highlightMove.bind(this, parent), {
16912 this._freeHighlight = new FreeHighlightOutliner({
16915 }, [layerX, layerY, parentWidth, parentHeight], parent.scale, this._defaultThickness / 2, isLTR, 0.001);
16917 id: this._freeHighlightId,
16918 clipPathId: this._freeHighlightClipId
16919 } = parent.drawLayer.draw({
16920 bbox: [0, 0, 1, 1],
16922 viewBox: "0 0 1 1",
16923 fill: this._defaultColor,
16924 "fill-opacity": this._defaultOpacity
16931 d: this._freeHighlight.toSVGPath()
16935 static #highlightMove(parent, event) {
16936 if (this._freeHighlight.add(event)) {
16937 parent.drawLayer.updateProperties(this._freeHighlightId, {
16939 d: this._freeHighlight.toSVGPath()
16944 static #endHighlight(parent, event) {
16945 if (!this._freeHighlight.isEmpty()) {
16946 parent.createAndAddNewEditor(event, false, {
16947 highlightId: this._freeHighlightId,
16948 highlightOutlines: this._freeHighlight.getOutlines(),
16949 clipPathId: this._freeHighlightClipId,
16950 methodOfCreation: "main_toolbar"
16953 parent.drawLayer.remove(this._freeHighlightId);
16955 this._freeHighlightId = -1;
16956 this._freeHighlight = null;
16957 this._freeHighlightClipId = "";
16959 static async deserialize(data, parent, uiManager) {
16960 let initialData = null;
16961 if (data instanceof HighlightAnnotationElement) {
16978 initialData = data = {
16979 annotationType: AnnotationEditorType.HIGHLIGHT,
16980 color: Array.from(color),
16984 pageIndex: pageNumber - 1,
16985 rect: rect.slice(0),
16991 } else if (data instanceof InkAnnotationElement) {
17000 rawWidth: thickness
17010 initialData = data = {
17011 annotationType: AnnotationEditorType.HIGHLIGHT,
17012 color: Array.from(color),
17016 pageIndex: pageNumber - 1,
17017 rect: rect.slice(0),
17030 const editor = await super.deserialize(data, parent, uiManager);
17031 editor.color = Util.makeHexColor(...color);
17032 editor.#opacity = opacity || 1;
17034 editor.#thickness = data.thickness;
17036 editor.annotationElementId = data.id || null;
17037 editor._initialData = initialData;
17038 const [pageWidth, pageHeight] = editor.pageDimensions;
17039 const [pageX, pageY] = editor.pageTranslation;
17041 const boxes = editor.#boxes = [];
17042 for (let i = 0; i < quadPoints.length; i += 8) {
17044 x: (quadPoints[i] - pageX) / pageWidth,
17045 y: 1 - (quadPoints[i + 1] - pageY) / pageHeight,
17046 width: (quadPoints[i + 2] - quadPoints[i]) / pageWidth,
17047 height: (quadPoints[i + 1] - quadPoints[i + 5]) / pageHeight
17050 editor.#createOutlines();
17051 editor.#addToDrawLayer();
17052 editor.rotate(editor.rotation);
17053 } else if (inkLists) {
17054 editor.#isFreeHighlight = true;
17055 const points = inkLists[0];
17057 x: points[0] - pageX,
17058 y: pageHeight - (points[1] - pageY)
17060 const outliner = new FreeHighlightOutliner(point, [0, 0, pageWidth, pageHeight], 1, editor.#thickness / 2, true, 0.001);
17061 for (let i = 0, ii = points.length; i < ii; i += 2) {
17062 point.x = points[i] - pageX;
17063 point.y = pageHeight - (points[i + 1] - pageY);
17064 outliner.add(point);
17069 } = parent.drawLayer.draw({
17070 bbox: [0, 0, 1, 1],
17072 viewBox: "0 0 1 1",
17073 fill: editor.color,
17074 "fill-opacity": editor._defaultOpacity
17081 d: outliner.toSVGPath()
17084 editor.#createFreeOutlines({
17085 highlightOutlines: outliner.getOutlines(),
17089 editor.#addToDrawLayer();
17093 serialize(isForCopying = false) {
17094 if (this.isEmpty() || isForCopying) {
17097 if (this.deleted) {
17098 return this.serializeDeleted();
17100 const rect = this.getRect(0, 0);
17101 const color = AnnotationEditor._colorManager.convert(this.color);
17102 const serialized = {
17103 annotationType: AnnotationEditorType.HIGHLIGHT,
17105 opacity: this.#opacity,
17106 thickness: this.#thickness,
17107 quadPoints: this.#serializeBoxes(),
17108 outlines: this.#serializeOutlines(rect),
17109 pageIndex: this.pageIndex,
17111 rotation: this.#getRotation(),
17112 structTreeParentId: this._structTreeParentId
17114 if (this.annotationElementId && !this.#hasElementChanged(serialized)) {
17117 serialized.id = this.annotationElementId;
17120 #hasElementChanged(serialized) {
17123 } = this._initialData;
17124 return serialized.color.some((c, i) => c !== color[i]);
17126 renderAnnotationElement(annotation) {
17127 annotation.updateEdited({
17128 rect: this.getRect(0, 0)
17132 static canCreateNewEmptyEditor() {
17137 ;// ./src/display/editor/draw.js
17141 class DrawingOptions {
17142 #svgProperties = Object.create(null);
17143 updateProperty(name, value) {
17144 this[name] = value;
17145 this.updateSVGProperty(name, value);
17147 updateProperties(properties) {
17151 for (const [name, value] of Object.entries(properties)) {
17152 this.updateProperty(name, value);
17155 updateSVGProperty(name, value) {
17156 this.#svgProperties[name] = value;
17158 toSVGProperties() {
17159 const root = this.#svgProperties;
17160 this.#svgProperties = Object.create(null);
17166 this.#svgProperties = Object.create(null);
17168 updateAll(options = this) {
17169 this.updateProperties(options);
17172 unreachable("Not implemented");
17175 class DrawingEditor extends AnnotationEditor {
17176 #drawOutlines = null;
17179 static _currentDrawId = -1;
17180 static _currentParent = null;
17181 static #currentDraw = null;
17182 static #currentDrawingAC = null;
17183 static #currentDrawingOptions = null;
17184 static #currentPointerId = NaN;
17185 static #currentPointerType = null;
17186 static #currentPointerIds = null;
17187 static #currentMoveTimestamp = NaN;
17188 static _INNER_MARGIN = 3;
17189 constructor(params) {
17191 this.#mustBeCommitted = params.mustBeCommitted || false;
17192 this._addOutlines(params);
17194 _addOutlines(params) {
17195 if (params.drawOutlines) {
17196 this.#createDrawOutlines(params);
17197 this.#addToDrawLayer();
17200 #createDrawOutlines({
17205 this.#drawOutlines = drawOutlines;
17206 this._drawingOptions ||= drawingOptions;
17208 this._drawId = drawId;
17209 this.parent.drawLayer.finalizeDraw(drawId, drawOutlines.defaultProperties);
17211 this._drawId = this.#createDrawing(drawOutlines, this.parent);
17213 this.#updateBbox(drawOutlines.box);
17215 #createDrawing(drawOutlines, parent) {
17218 } = parent.drawLayer.draw(DrawingEditor._mergeSVGProperties(this._drawingOptions.toSVGProperties(), drawOutlines.defaultSVGProperties), false, false);
17221 static _mergeSVGProperties(p1, p2) {
17222 const p1Keys = new Set(Object.keys(p1));
17223 for (const [key, value] of Object.entries(p2)) {
17224 if (p1Keys.has(key)) {
17225 Object.assign(p1[key], value);
17232 static getDefaultDrawingOptions(_options) {
17233 unreachable("Not implemented");
17235 static get typesMap() {
17236 unreachable("Not implemented");
17238 static get isDrawer() {
17241 static get supportMultipleDrawings() {
17244 static updateDefaultParams(type, value) {
17245 const propertyName = this.typesMap.get(type);
17246 if (propertyName) {
17247 this._defaultDrawingOptions.updateProperty(propertyName, value);
17249 if (this._currentParent) {
17250 DrawingEditor.#currentDraw.updateProperty(propertyName, value);
17251 this._currentParent.drawLayer.updateProperties(this._currentDrawId, this._defaultDrawingOptions.toSVGProperties());
17254 updateParams(type, value) {
17255 const propertyName = this.constructor.typesMap.get(type);
17256 if (propertyName) {
17257 this._updateProperty(type, propertyName, value);
17260 static get defaultPropertiesToUpdate() {
17261 const properties = [];
17262 const options = this._defaultDrawingOptions;
17263 for (const [type, name] of this.typesMap) {
17264 properties.push([type, options[name]]);
17268 get propertiesToUpdate() {
17269 const properties = [];
17273 for (const [type, name] of this.constructor.typesMap) {
17274 properties.push([type, _drawingOptions[name]]);
17278 _updateProperty(type, name, value) {
17279 const options = this._drawingOptions;
17280 const savedValue = options[name];
17281 const setter = val => {
17282 options.updateProperty(name, val);
17283 const bbox = this.#drawOutlines.updateProperty(name, val);
17285 this.#updateBbox(bbox);
17287 this.parent?.drawLayer.updateProperties(this._drawId, options.toSVGProperties());
17290 cmd: setter.bind(this, value),
17291 undo: setter.bind(this, savedValue),
17292 post: this._uiManager.updateUI.bind(this._uiManager, this),
17295 overwriteIfSameType: true,
17300 this.parent?.drawLayer.updateProperties(this._drawId, DrawingEditor._mergeSVGProperties(this.#drawOutlines.getPathResizingSVGProperties(this.#convertToDrawSpace()), {
17301 bbox: this.#rotateBox()
17305 this.parent?.drawLayer.updateProperties(this._drawId, DrawingEditor._mergeSVGProperties(this.#drawOutlines.getPathResizedSVGProperties(this.#convertToDrawSpace()), {
17306 bbox: this.#rotateBox()
17309 _onTranslating(x, y) {
17310 this.parent?.drawLayer.updateProperties(this._drawId, {
17311 bbox: this.#rotateBox(x, y)
17315 this.parent?.drawLayer.updateProperties(this._drawId, DrawingEditor._mergeSVGProperties(this.#drawOutlines.getPathTranslatedSVGProperties(this.#convertToDrawSpace(), this.parentDimensions), {
17316 bbox: this.#rotateBox()
17319 _onStartDragging() {
17320 this.parent?.drawLayer.updateProperties(this._drawId, {
17326 _onStopDragging() {
17327 this.parent?.drawLayer.updateProperties(this._drawId, {
17335 this.disableEditMode();
17336 this.disableEditing();
17339 super.disableEditing();
17340 this.div.classList.toggle("disabled", true);
17343 super.enableEditing();
17344 this.div.classList.toggle("disabled", false);
17346 getBaseTranslation() {
17349 get isResizable() {
17353 if (!this.annotationElementId) {
17354 this.parent.addUndoableEditor(this);
17356 this._isDraggable = true;
17357 if (this.#mustBeCommitted) {
17358 this.#mustBeCommitted = false;
17360 this.parent.setSelected(this);
17361 if (focus && this.isOnScreen) {
17367 this.#cleanDrawLayer();
17371 if (!this.parent) {
17375 if (this.div === null) {
17378 this.#addToDrawLayer();
17379 this.#updateBbox(this.#drawOutlines.box);
17380 if (!this.isAttachedToDOM) {
17381 this.parent.add(this);
17384 setParent(parent) {
17385 let mustBeSelected = false;
17386 if (this.parent && !parent) {
17387 this._uiManager.removeShouldRescale(this);
17388 this.#cleanDrawLayer();
17389 } else if (parent) {
17390 this._uiManager.addShouldRescale(this);
17391 this.#addToDrawLayer(parent);
17392 mustBeSelected = !this.parent && this.div?.classList.contains("selectedEditor");
17394 super.setParent(parent);
17395 if (mustBeSelected) {
17399 #cleanDrawLayer() {
17400 if (this._drawId === null || !this.parent) {
17403 this.parent.drawLayer.remove(this._drawId);
17404 this._drawId = null;
17405 this._drawingOptions.reset();
17407 #addToDrawLayer(parent = this.parent) {
17408 if (this._drawId !== null && this.parent === parent) {
17411 if (this._drawId !== null) {
17412 this.parent.drawLayer.updateParent(this._drawId, parent.drawLayer);
17415 this._drawingOptions.updateAll();
17416 this._drawId = this.#createDrawing(this.#drawOutlines, parent);
17418 #convertToParentSpace([x, y, width, height]) {
17420 parentDimensions: [pW, pH],
17423 switch (rotation) {
17425 return [y, 1 - x, width * (pH / pW), height * (pW / pH)];
17427 return [1 - x, 1 - y, width, height];
17429 return [1 - y, x, width * (pH / pW), height * (pW / pH)];
17431 return [x, y, width, height];
17434 #convertToDrawSpace() {
17440 parentDimensions: [pW, pH],
17443 switch (rotation) {
17445 return [1 - y, x, width * (pW / pH), height * (pH / pW)];
17447 return [1 - x, 1 - y, width, height];
17449 return [y, 1 - x, width * (pW / pH), height * (pH / pW)];
17451 return [x, y, width, height];
17454 #updateBbox(bbox) {
17455 [this.x, this.y, this.width, this.height] = this.#convertToParentSpace(bbox);
17457 this.fixAndSetPosition();
17458 const [parentWidth, parentHeight] = this.parentDimensions;
17459 this.setDims(this.width * parentWidth, this.height * parentHeight);
17471 parentDimensions: [pW, pH]
17473 switch ((rotation * 4 + parentRotation) / 90) {
17475 return [1 - y - height, x, height, width];
17477 return [1 - x - width, 1 - y - height, width, height];
17479 return [y, 1 - x - width, height, width];
17481 return [x, y - width * (pW / pH), height * (pH / pW), width * (pW / pH)];
17483 return [1 - y, x, width * (pW / pH), height * (pH / pW)];
17485 return [1 - x - height * (pH / pW), 1 - y, height * (pH / pW), width * (pW / pH)];
17487 return [y - width * (pW / pH), 1 - x - height * (pH / pW), width * (pW / pH), height * (pH / pW)];
17489 return [x - width, y - height, width, height];
17491 return [1 - y, x - width, height, width];
17493 return [1 - x, 1 - y, width, height];
17495 return [y - height, 1 - x, height, width];
17497 return [x - height * (pH / pW), y, height * (pH / pW), width * (pW / pH)];
17499 return [1 - y - width * (pW / pH), x - height * (pH / pW), width * (pW / pH), height * (pH / pW)];
17501 return [1 - x, 1 - y - width * (pW / pH), height * (pH / pW), width * (pW / pH)];
17503 return [y, 1 - x, width * (pW / pH), height * (pH / pW)];
17505 return [x, y, width, height];
17509 if (!this.parent) {
17512 this.parent.drawLayer.updateProperties(this._drawId, DrawingEditor._mergeSVGProperties({
17513 bbox: this.#rotateBox()
17514 }, this.#drawOutlines.updateRotation((this.parentRotation - this.rotation + 360) % 360)));
17516 onScaleChanging() {
17517 if (!this.parent) {
17520 this.#updateBbox(this.#drawOutlines.updateParentDimensions(this.parentDimensions, this.parent.scale));
17522 static onScaleChangingWhenDrawing() {}
17527 const div = super.render();
17528 div.classList.add("draw");
17529 const drawDiv = document.createElement("div");
17530 div.append(drawDiv);
17531 drawDiv.setAttribute("aria-hidden", "true");
17532 drawDiv.className = "internal";
17533 const [parentWidth, parentHeight] = this.parentDimensions;
17534 this.setDims(this.width * parentWidth, this.height * parentHeight);
17535 this._uiManager.addShouldRescale(this);
17536 this.disableEditing();
17539 static createDrawerInstance(_x, _y, _parentWidth, _parentHeight, _rotation) {
17540 unreachable("Not implemented");
17542 static startDrawing(parent, uiManager, _isLTR, event) {
17550 if (DrawingEditor.#currentPointerType && DrawingEditor.#currentPointerType !== pointerType) {
17559 width: parentWidth,
17560 height: parentHeight
17561 } = target.getBoundingClientRect();
17562 const ac = DrawingEditor.#currentDrawingAC = new AbortController();
17563 const signal = parent.combinedSignal(ac);
17564 DrawingEditor.#currentPointerId ||= pointerId;
17565 DrawingEditor.#currentPointerType ??= pointerType;
17566 window.addEventListener("pointerup", e => {
17567 if (DrawingEditor.#currentPointerId === e.pointerId) {
17570 DrawingEditor.#currentPointerIds?.delete(e.pointerId);
17575 window.addEventListener("pointercancel", e => {
17576 if (DrawingEditor.#currentPointerId === e.pointerId) {
17577 this._currentParent.endDrawingSession();
17579 DrawingEditor.#currentPointerIds?.delete(e.pointerId);
17584 window.addEventListener("pointerdown", e => {
17585 if (DrawingEditor.#currentPointerType !== e.pointerType) {
17588 (DrawingEditor.#currentPointerIds ||= new Set()).add(e.pointerId);
17589 if (DrawingEditor.#currentDraw.isCancellable()) {
17590 DrawingEditor.#currentDraw.removeLastElement();
17591 if (DrawingEditor.#currentDraw.isEmpty()) {
17592 this._currentParent.endDrawingSession(true);
17594 this._endDraw(null);
17602 window.addEventListener("contextmenu", noContextMenu, {
17605 target.addEventListener("pointermove", this._drawMove.bind(this), {
17608 target.addEventListener("touchmove", e => {
17609 if (e.timeStamp === DrawingEditor.#currentMoveTimestamp) {
17615 parent.toggleDrawing();
17616 uiManager._editorUndoBar?.hide();
17617 if (DrawingEditor.#currentDraw) {
17618 parent.drawLayer.updateProperties(this._currentDrawId, DrawingEditor.#currentDraw.startNew(x, y, parentWidth, parentHeight, rotation));
17621 uiManager.updateUIForDefaultProperties(this);
17622 DrawingEditor.#currentDraw = this.createDrawerInstance(x, y, parentWidth, parentHeight, rotation);
17623 DrawingEditor.#currentDrawingOptions = this.getDefaultDrawingOptions();
17624 this._currentParent = parent;
17626 id: this._currentDrawId
17627 } = parent.drawLayer.draw(this._mergeSVGProperties(DrawingEditor.#currentDrawingOptions.toSVGProperties(), DrawingEditor.#currentDraw.defaultSVGProperties), true, false));
17629 static _drawMove(event) {
17630 DrawingEditor.#currentMoveTimestamp = -1;
17631 if (!DrawingEditor.#currentDraw) {
17639 if (DrawingEditor.#currentPointerId !== pointerId) {
17642 if (DrawingEditor.#currentPointerIds?.size >= 1) {
17643 this._endDraw(event);
17646 this._currentParent.drawLayer.updateProperties(this._currentDrawId, DrawingEditor.#currentDraw.add(offsetX, offsetY));
17647 DrawingEditor.#currentMoveTimestamp = event.timeStamp;
17650 static _cleanup(all) {
17652 this._currentDrawId = -1;
17653 this._currentParent = null;
17654 DrawingEditor.#currentDraw = null;
17655 DrawingEditor.#currentDrawingOptions = null;
17656 DrawingEditor.#currentPointerType = null;
17657 DrawingEditor.#currentMoveTimestamp = NaN;
17659 if (DrawingEditor.#currentDrawingAC) {
17660 DrawingEditor.#currentDrawingAC.abort();
17661 DrawingEditor.#currentDrawingAC = null;
17662 DrawingEditor.#currentPointerId = NaN;
17663 DrawingEditor.#currentPointerIds = null;
17666 static _endDraw(event) {
17667 const parent = this._currentParent;
17671 parent.toggleDrawing(true);
17672 this._cleanup(false);
17673 if (event?.target === parent.div) {
17674 parent.drawLayer.updateProperties(this._currentDrawId, DrawingEditor.#currentDraw.end(event.offsetX, event.offsetY));
17676 if (this.supportMultipleDrawings) {
17677 const draw = DrawingEditor.#currentDraw;
17678 const drawId = this._currentDrawId;
17679 const lastElement = draw.getLastElement();
17680 parent.addCommands({
17682 parent.drawLayer.updateProperties(drawId, draw.setLastElement(lastElement));
17685 parent.drawLayer.updateProperties(drawId, draw.removeLastElement());
17688 type: AnnotationEditorParamsType.DRAW_STEP
17692 this.endDrawing(false);
17694 static endDrawing(isAborted) {
17695 const parent = this._currentParent;
17699 parent.toggleDrawing(true);
17700 parent.cleanUndoStack(AnnotationEditorParamsType.DRAW_STEP);
17701 if (!DrawingEditor.#currentDraw.isEmpty()) {
17703 pageDimensions: [pageWidth, pageHeight],
17706 const editor = parent.createAndAddNewEditor({
17710 drawId: this._currentDrawId,
17711 drawOutlines: DrawingEditor.#currentDraw.getOutlines(pageWidth * scale, pageHeight * scale, scale, this._INNER_MARGIN),
17712 drawingOptions: DrawingEditor.#currentDrawingOptions,
17713 mustBeCommitted: !isAborted
17715 this._cleanup(true);
17718 parent.drawLayer.remove(this._currentDrawId);
17719 this._cleanup(true);
17722 createDrawingOptions(_data) {}
17723 static deserializeDraw(_pageX, _pageY, _pageWidth, _pageHeight, _innerWidth, _data) {
17724 unreachable("Not implemented");
17726 static async deserialize(data, parent, uiManager) {
17734 } = parent.viewport;
17735 const drawOutlines = this.deserializeDraw(pageX, pageY, pageWidth, pageHeight, this._INNER_MARGIN, data);
17736 const editor = await super.deserialize(data, parent, uiManager);
17737 editor.createDrawingOptions(data);
17738 editor.#createDrawOutlines({
17741 editor.#addToDrawLayer();
17742 editor.onScaleChanging();
17746 serializeDraw(isForCopying) {
17747 const [pageX, pageY] = this.pageTranslation;
17748 const [pageWidth, pageHeight] = this.pageDimensions;
17749 return this.#drawOutlines.serialize([pageX, pageY, pageWidth, pageHeight], isForCopying);
17751 renderAnnotationElement(annotation) {
17752 annotation.updateEdited({
17753 rect: this.getRect(0, 0)
17757 static canCreateNewEmptyEditor() {
17762 ;// ./src/display/editor/drawers/inkdraw.js
17765 class InkDrawOutliner {
17766 #last = new Float64Array(6);
17774 #outlines = new InkDrawOutline();
17777 constructor(x, y, parentWidth, parentHeight, rotation, thickness) {
17778 this.#parentWidth = parentWidth;
17779 this.#parentHeight = parentHeight;
17780 this.#rotation = rotation;
17781 this.#thickness = thickness;
17782 [x, y] = this.#normalizePoint(x, y);
17783 const line = this.#line = [NaN, NaN, NaN, NaN, x, y];
17784 this.#points = [x, y];
17787 points: this.#points
17789 this.#last.set(line, 0);
17791 updateProperty(name, value) {
17792 if (name === "stroke-width") {
17793 this.#thickness = value;
17796 #normalizePoint(x, y) {
17797 return Outline._normalizePoint(x, y, this.#parentWidth, this.#parentHeight, this.#rotation);
17800 return !this.#lines || this.#lines.length === 0;
17803 return this.#points.length <= 10;
17806 [x, y] = this.#normalizePoint(x, y);
17807 const [x1, y1, x2, y2] = this.#last.subarray(2, 6);
17808 const diffX = x - x2;
17809 const diffY = y - y2;
17810 const d = Math.hypot(this.#parentWidth * diffX, this.#parentHeight * diffY);
17814 this.#points.push(x, y);
17816 this.#last.set([x2, y2, x, y], 2);
17817 this.#line.push(NaN, NaN, NaN, NaN, x, y);
17820 d: this.toSVGPath()
17824 if (isNaN(this.#last[0])) {
17825 this.#line.splice(6, 6);
17827 this.#last.set([x1, y1, x2, y2, x, y], 0);
17828 this.#line.push(...Outline.createBezierPoints(x1, y1, x2, y2, x, y));
17831 d: this.toSVGPath()
17836 const change = this.add(x, y);
17840 if (this.#points.length === 2) {
17843 d: this.toSVGPath()
17849 startNew(x, y, parentWidth, parentHeight, rotation) {
17850 this.#parentWidth = parentWidth;
17851 this.#parentHeight = parentHeight;
17852 this.#rotation = rotation;
17853 [x, y] = this.#normalizePoint(x, y);
17854 const line = this.#line = [NaN, NaN, NaN, NaN, x, y];
17855 this.#points = [x, y];
17856 const last = this.#lines.at(-1);
17858 last.line = new Float32Array(last.line);
17859 last.points = new Float32Array(last.points);
17863 points: this.#points
17865 this.#last.set(line, 0);
17866 this.#lastIndex = 0;
17871 return this.#lines.at(-1);
17873 setLastElement(element) {
17874 if (!this.#lines) {
17875 return this.#outlines.setLastElement(element);
17877 this.#lines.push(element);
17878 this.#line = element.line;
17879 this.#points = element.points;
17880 this.#lastIndex = 0;
17883 d: this.toSVGPath()
17887 removeLastElement() {
17888 if (!this.#lines) {
17889 return this.#outlines.removeLastElement();
17892 this.#lastSVGPath = "";
17893 for (let i = 0, ii = this.#lines.length; i < ii; i++) {
17897 } = this.#lines[i];
17899 this.#points = points;
17900 this.#lastIndex = 0;
17905 d: this.#lastSVGPath
17910 const firstX = Outline.svgRound(this.#line[4]);
17911 const firstY = Outline.svgRound(this.#line[5]);
17912 if (this.#points.length === 2) {
17913 this.#lastSVGPath = `${this.#lastSVGPath} M ${firstX} ${firstY} Z`;
17914 return this.#lastSVGPath;
17916 if (this.#points.length <= 6) {
17917 const i = this.#lastSVGPath.lastIndexOf("M");
17918 this.#lastSVGPath = `${this.#lastSVGPath.slice(0, i)} M ${firstX} ${firstY}`;
17919 this.#lastIndex = 6;
17921 if (this.#points.length === 4) {
17922 const secondX = Outline.svgRound(this.#line[10]);
17923 const secondY = Outline.svgRound(this.#line[11]);
17924 this.#lastSVGPath = `${this.#lastSVGPath} L ${secondX} ${secondY}`;
17925 this.#lastIndex = 12;
17926 return this.#lastSVGPath;
17929 if (this.#lastIndex === 0) {
17930 buffer.push(`M ${firstX} ${firstY}`);
17931 this.#lastIndex = 6;
17933 for (let i = this.#lastIndex, ii = this.#line.length; i < ii; i += 6) {
17934 const [c1x, c1y, c2x, c2y, x, y] = this.#line.slice(i, i + 6).map(Outline.svgRound);
17935 buffer.push(`C${c1x} ${c1y} ${c2x} ${c2y} ${x} ${y}`);
17937 this.#lastSVGPath += buffer.join(" ");
17938 this.#lastIndex = this.#line.length;
17939 return this.#lastSVGPath;
17941 getOutlines(parentWidth, parentHeight, scale, innerMargin) {
17942 const last = this.#lines.at(-1);
17943 last.line = new Float32Array(last.line);
17944 last.points = new Float32Array(last.points);
17945 this.#outlines.build(this.#lines, parentWidth, parentHeight, scale, this.#rotation, this.#thickness, innerMargin);
17948 this.#lines = null;
17949 this.#lastSVGPath = null;
17950 return this.#outlines;
17952 get defaultSVGProperties() {
17955 viewBox: "0 0 10000 10000"
17964 class InkDrawOutline extends Outline {
17966 #currentRotation = 0;
17974 build(lines, parentWidth, parentHeight, parentScale, rotation, thickness, innerMargin) {
17975 this.#parentWidth = parentWidth;
17976 this.#parentHeight = parentHeight;
17977 this.#parentScale = parentScale;
17978 this.#rotation = rotation;
17979 this.#thickness = thickness;
17980 this.#innerMargin = innerMargin ?? 0;
17981 this.#lines = lines;
17982 this.#computeBbox();
17985 return this.#thickness;
17987 setLastElement(element) {
17988 this.#lines.push(element);
17991 d: this.toSVGPath()
17995 removeLastElement() {
17999 d: this.toSVGPath()
18007 } of this.#lines) {
18008 buffer.push(`M${Outline.svgRound(line[4])} ${Outline.svgRound(line[5])}`);
18009 if (line.length === 6) {
18013 if (line.length === 12 && isNaN(line[6])) {
18014 buffer.push(`L${Outline.svgRound(line[10])} ${Outline.svgRound(line[11])}`);
18017 for (let i = 6, ii = line.length; i < ii; i += 6) {
18018 const [c1x, c1y, c2x, c2y, x, y] = line.subarray(i, i + 6).map(Outline.svgRound);
18019 buffer.push(`C${c1x} ${c1y} ${c2x} ${c2y} ${x} ${y}`);
18022 return buffer.join("");
18024 serialize([pageX, pageY, pageWidth, pageHeight], isForCopying) {
18025 const serializedLines = [];
18026 const serializedPoints = [];
18027 const [x, y, width, height] = this.#getBBoxWithNoMargin();
18028 let tx, ty, sx, sy, x1, y1, x2, y2, rescaleFn;
18029 switch (this.#rotation) {
18031 rescaleFn = Outline._rescale;
18033 ty = pageY + pageHeight;
18036 x1 = pageX + x * pageWidth;
18037 y1 = pageY + (1 - y - height) * pageHeight;
18038 x2 = pageX + (x + width) * pageWidth;
18039 y2 = pageY + (1 - y) * pageHeight;
18042 rescaleFn = Outline._rescaleAndSwap;
18047 x1 = pageX + y * pageWidth;
18048 y1 = pageY + x * pageHeight;
18049 x2 = pageX + (y + height) * pageWidth;
18050 y2 = pageY + (x + width) * pageHeight;
18053 rescaleFn = Outline._rescale;
18054 tx = pageX + pageWidth;
18058 x1 = pageX + (1 - x - width) * pageWidth;
18059 y1 = pageY + y * pageHeight;
18060 x2 = pageX + (1 - x) * pageWidth;
18061 y2 = pageY + (y + height) * pageHeight;
18064 rescaleFn = Outline._rescaleAndSwap;
18065 tx = pageX + pageWidth;
18066 ty = pageY + pageHeight;
18069 x1 = pageX + (1 - y - height) * pageWidth;
18070 y1 = pageY + (1 - x - width) * pageHeight;
18071 x2 = pageX + (1 - y) * pageWidth;
18072 y2 = pageY + (1 - x) * pageHeight;
18078 } of this.#lines) {
18079 serializedLines.push(rescaleFn(line, tx, ty, sx, sy, isForCopying ? new Array(line.length) : null));
18080 serializedPoints.push(rescaleFn(points, tx, ty, sx, sy, isForCopying ? new Array(points.length) : null));
18083 lines: serializedLines,
18084 points: serializedPoints,
18085 rect: [x1, y1, x2, y2]
18088 static deserialize(pageX, pageY, pageWidth, pageHeight, innerMargin, {
18096 const newLines = [];
18097 let tx, ty, sx, sy, rescaleFn;
18098 switch (rotation) {
18100 rescaleFn = Outline._rescale;
18101 tx = -pageX / pageWidth;
18102 ty = pageY / pageHeight + 1;
18103 sx = 1 / pageWidth;
18104 sy = -1 / pageHeight;
18107 rescaleFn = Outline._rescaleAndSwap;
18108 tx = -pageY / pageHeight;
18109 ty = -pageX / pageWidth;
18110 sx = 1 / pageHeight;
18111 sy = 1 / pageWidth;
18114 rescaleFn = Outline._rescale;
18115 tx = pageX / pageWidth + 1;
18116 ty = -pageY / pageHeight;
18117 sx = -1 / pageWidth;
18118 sy = 1 / pageHeight;
18121 rescaleFn = Outline._rescaleAndSwap;
18122 tx = pageY / pageHeight + 1;
18123 ty = pageX / pageWidth + 1;
18124 sx = -1 / pageHeight;
18125 sy = -1 / pageWidth;
18130 for (const point of points) {
18131 const len = point.length;
18133 lines.push(new Float32Array([NaN, NaN, NaN, NaN, point[0], point[1]]));
18137 lines.push(new Float32Array([NaN, NaN, NaN, NaN, point[0], point[1], NaN, NaN, NaN, NaN, point[2], point[3]]));
18140 const line = new Float32Array(3 * (len - 2));
18142 let [x1, y1, x2, y2] = point.subarray(0, 4);
18143 line.set([NaN, NaN, NaN, NaN, x1, y1], 0);
18144 for (let i = 4; i < len; i += 2) {
18145 const x = point[i];
18146 const y = point[i + 1];
18147 line.set(Outline.createBezierPoints(x1, y1, x2, y2, x, y), (i - 2) * 3);
18148 [x1, y1, x2, y2] = [x2, y2, x, y];
18152 for (let i = 0, ii = lines.length; i < ii; i++) {
18154 line: rescaleFn(lines[i].map(x => x ?? NaN), tx, ty, sx, sy),
18155 points: rescaleFn(points[i].map(x => x ?? NaN), tx, ty, sx, sy)
18158 const outlines = new InkDrawOutline();
18159 outlines.build(newLines, pageWidth, pageHeight, 1, rotation, thickness, innerMargin);
18162 #getMarginComponents(thickness = this.#thickness) {
18163 const margin = this.#innerMargin + thickness / 2 * this.#parentScale;
18164 return this.#rotation % 180 === 0 ? [margin / this.#parentWidth, margin / this.#parentHeight] : [margin / this.#parentHeight, margin / this.#parentWidth];
18166 #getBBoxWithNoMargin() {
18167 const [x, y, width, height] = this.#bbox;
18168 const [marginX, marginY] = this.#getMarginComponents(0);
18169 return [x + marginX, y + marginY, width - 2 * marginX, height - 2 * marginY];
18172 const bbox = this.#bbox = new Float32Array([Infinity, Infinity, -Infinity, -Infinity]);
18175 } of this.#lines) {
18176 if (line.length <= 12) {
18177 for (let i = 4, ii = line.length; i < ii; i += 6) {
18178 const [x, y] = line.subarray(i, i + 2);
18179 bbox[0] = Math.min(bbox[0], x);
18180 bbox[1] = Math.min(bbox[1], y);
18181 bbox[2] = Math.max(bbox[2], x);
18182 bbox[3] = Math.max(bbox[3], y);
18186 let lastX = line[4],
18188 for (let i = 6, ii = line.length; i < ii; i += 6) {
18189 const [c1x, c1y, c2x, c2y, x, y] = line.subarray(i, i + 6);
18190 Util.bezierBoundingBox(lastX, lastY, c1x, c1y, c2x, c2y, x, y, bbox);
18195 const [marginX, marginY] = this.#getMarginComponents();
18196 bbox[0] = Math.min(1, Math.max(0, bbox[0] - marginX));
18197 bbox[1] = Math.min(1, Math.max(0, bbox[1] - marginY));
18198 bbox[2] = Math.min(1, Math.max(0, bbox[2] + marginX));
18199 bbox[3] = Math.min(1, Math.max(0, bbox[3] + marginY));
18200 bbox[2] -= bbox[0];
18201 bbox[3] -= bbox[1];
18206 updateProperty(name, value) {
18207 if (name === "stroke-width") {
18208 return this.#updateThickness(value);
18212 #updateThickness(thickness) {
18213 const [oldMarginX, oldMarginY] = this.#getMarginComponents();
18214 this.#thickness = thickness;
18215 const [newMarginX, newMarginY] = this.#getMarginComponents();
18216 const [diffMarginX, diffMarginY] = [newMarginX - oldMarginX, newMarginY - oldMarginY];
18217 const bbox = this.#bbox;
18218 bbox[0] -= diffMarginX;
18219 bbox[1] -= diffMarginY;
18220 bbox[2] += 2 * diffMarginX;
18221 bbox[3] += 2 * diffMarginY;
18224 updateParentDimensions([width, height], scale) {
18225 const [oldMarginX, oldMarginY] = this.#getMarginComponents();
18226 this.#parentWidth = width;
18227 this.#parentHeight = height;
18228 this.#parentScale = scale;
18229 const [newMarginX, newMarginY] = this.#getMarginComponents();
18230 const diffMarginX = newMarginX - oldMarginX;
18231 const diffMarginY = newMarginY - oldMarginY;
18232 const bbox = this.#bbox;
18233 bbox[0] -= diffMarginX;
18234 bbox[1] -= diffMarginY;
18235 bbox[2] += 2 * diffMarginX;
18236 bbox[3] += 2 * diffMarginY;
18239 updateRotation(rotation) {
18240 this.#currentRotation = rotation;
18243 transform: this.rotationTransform
18248 return this.#bbox.map(Outline.svgRound).join(" ");
18250 get defaultProperties() {
18251 const [x, y] = this.#bbox;
18254 viewBox: this.viewBox
18257 "transform-origin": `${Outline.svgRound(x)} ${Outline.svgRound(y)}`
18261 get rotationTransform() {
18262 const [,, width, height] = this.#bbox;
18269 switch (this.#currentRotation) {
18271 b = height / width;
18272 c = -width / height;
18282 b = -height / width;
18283 c = width / height;
18289 return `matrix(${a} ${b} ${c} ${d} ${Outline.svgRound(e)} ${Outline.svgRound(f)})`;
18291 getPathResizingSVGProperties([newX, newY, newWidth, newHeight]) {
18292 const [marginX, marginY] = this.#getMarginComponents();
18293 const [x, y, width, height] = this.#bbox;
18294 if (Math.abs(width - marginX) <= Outline.PRECISION || Math.abs(height - marginY) <= Outline.PRECISION) {
18295 const tx = newX + newWidth / 2 - (x + width / 2);
18296 const ty = newY + newHeight / 2 - (y + height / 2);
18299 "transform-origin": `${Outline.svgRound(newX)} ${Outline.svgRound(newY)}`,
18300 transform: `${this.rotationTransform} translate(${tx} ${ty})`
18304 const s1x = (newWidth - 2 * marginX) / (width - 2 * marginX);
18305 const s1y = (newHeight - 2 * marginY) / (height - 2 * marginY);
18306 const s2x = width / newWidth;
18307 const s2y = height / newHeight;
18310 "transform-origin": `${Outline.svgRound(x)} ${Outline.svgRound(y)}`,
18311 transform: `${this.rotationTransform} scale(${s2x} ${s2y}) ` + `translate(${Outline.svgRound(marginX)} ${Outline.svgRound(marginY)}) scale(${s1x} ${s1y}) ` + `translate(${Outline.svgRound(-marginX)} ${Outline.svgRound(-marginY)})`
18315 getPathResizedSVGProperties([newX, newY, newWidth, newHeight]) {
18316 const [marginX, marginY] = this.#getMarginComponents();
18317 const bbox = this.#bbox;
18318 const [x, y, width, height] = bbox;
18321 bbox[2] = newWidth;
18322 bbox[3] = newHeight;
18323 if (Math.abs(width - marginX) <= Outline.PRECISION || Math.abs(height - marginY) <= Outline.PRECISION) {
18324 const tx = newX + newWidth / 2 - (x + width / 2);
18325 const ty = newY + newHeight / 2 - (y + height / 2);
18329 } of this.#lines) {
18330 Outline._translate(line, tx, ty, line);
18331 Outline._translate(points, tx, ty, points);
18335 viewBox: this.viewBox
18338 "transform-origin": `${Outline.svgRound(newX)} ${Outline.svgRound(newY)}`,
18339 transform: this.rotationTransform || null,
18340 d: this.toSVGPath()
18344 const s1x = (newWidth - 2 * marginX) / (width - 2 * marginX);
18345 const s1y = (newHeight - 2 * marginY) / (height - 2 * marginY);
18346 const tx = -s1x * (x + marginX) + newX + marginX;
18347 const ty = -s1y * (y + marginY) + newY + marginY;
18348 if (s1x !== 1 || s1y !== 1 || tx !== 0 || ty !== 0) {
18352 } of this.#lines) {
18353 Outline._rescale(line, tx, ty, s1x, s1y, line);
18354 Outline._rescale(points, tx, ty, s1x, s1y, points);
18359 viewBox: this.viewBox
18362 "transform-origin": `${Outline.svgRound(newX)} ${Outline.svgRound(newY)}`,
18363 transform: this.rotationTransform || null,
18364 d: this.toSVGPath()
18368 getPathTranslatedSVGProperties([newX, newY], parentDimensions) {
18369 const [newParentWidth, newParentHeight] = parentDimensions;
18370 const bbox = this.#bbox;
18371 const tx = newX - bbox[0];
18372 const ty = newY - bbox[1];
18373 if (this.#parentWidth === newParentWidth && this.#parentHeight === newParentHeight) {
18377 } of this.#lines) {
18378 Outline._translate(line, tx, ty, line);
18379 Outline._translate(points, tx, ty, points);
18382 const sx = this.#parentWidth / newParentWidth;
18383 const sy = this.#parentHeight / newParentHeight;
18384 this.#parentWidth = newParentWidth;
18385 this.#parentHeight = newParentHeight;
18389 } of this.#lines) {
18390 Outline._rescale(line, tx, ty, sx, sy, line);
18391 Outline._rescale(points, tx, ty, sx, sy, points);
18400 viewBox: this.viewBox
18403 d: this.toSVGPath(),
18404 "transform-origin": `${Outline.svgRound(newX)} ${Outline.svgRound(newY)}`
18408 get defaultSVGProperties() {
18409 const bbox = this.#bbox;
18412 viewBox: this.viewBox
18418 d: this.toSVGPath(),
18419 "transform-origin": `${Outline.svgRound(bbox[0])} ${Outline.svgRound(bbox[1])}`,
18420 transform: this.rotationTransform || null
18427 ;// ./src/display/editor/ink.js
18433 class InkDrawingOptions extends DrawingOptions {
18434 constructor(viewerParameters) {
18436 this._viewParameters = viewerParameters;
18437 super.updateProperties({
18439 stroke: AnnotationEditor._defaultLineColor,
18440 "stroke-opacity": 1,
18442 "stroke-linecap": "round",
18443 "stroke-linejoin": "round",
18444 "stroke-miterlimit": 10
18447 updateSVGProperty(name, value) {
18448 if (name === "stroke-width") {
18449 value ??= this["stroke-width"];
18450 value *= this._viewParameters.realScale;
18452 super.updateSVGProperty(name, value);
18455 const clone = new InkDrawingOptions(this._viewParameters);
18456 clone.updateAll(this);
18460 class InkEditor extends DrawingEditor {
18461 static _type = "ink";
18462 static _editorType = AnnotationEditorType.INK;
18463 static _defaultDrawingOptions = null;
18464 constructor(params) {
18469 this._willKeepAspectRatio = true;
18471 static initialize(l10n, uiManager) {
18472 AnnotationEditor.initialize(l10n, uiManager);
18473 this._defaultDrawingOptions = new InkDrawingOptions(uiManager.viewParameters);
18475 static getDefaultDrawingOptions(options) {
18476 const clone = this._defaultDrawingOptions.clone();
18477 clone.updateProperties(options);
18480 static get supportMultipleDrawings() {
18483 static get typesMap() {
18484 return shadow(this, "typesMap", new Map([[AnnotationEditorParamsType.INK_THICKNESS, "stroke-width"], [AnnotationEditorParamsType.INK_COLOR, "stroke"], [AnnotationEditorParamsType.INK_OPACITY, "stroke-opacity"]]));
18486 static createDrawerInstance(x, y, parentWidth, parentHeight, rotation) {
18487 return new InkDrawOutliner(x, y, parentWidth, parentHeight, rotation, this._defaultDrawingOptions["stroke-width"]);
18489 static deserializeDraw(pageX, pageY, pageWidth, pageHeight, innerMargin, data) {
18490 return InkDrawOutline.deserialize(pageX, pageY, pageWidth, pageHeight, innerMargin, data);
18492 static async deserialize(data, parent, uiManager) {
18493 let initialData = null;
18494 if (data instanceof InkAnnotationElement) {
18504 rawWidth: thickness
18514 initialData = data = {
18515 annotationType: AnnotationEditorType.INK,
18516 color: Array.from(color),
18523 pageIndex: pageNumber - 1,
18524 rect: rect.slice(0),
18531 const editor = await super.deserialize(data, parent, uiManager);
18532 editor.annotationElementId = data.id || null;
18533 editor._initialData = initialData;
18536 onScaleChanging() {
18537 if (!this.parent) {
18540 super.onScaleChanging();
18546 _drawingOptions.updateSVGProperty("stroke-width");
18547 parent.drawLayer.updateProperties(_drawId, _drawingOptions.toSVGProperties());
18549 static onScaleChangingWhenDrawing() {
18550 const parent = this._currentParent;
18554 super.onScaleChangingWhenDrawing();
18555 this._defaultDrawingOptions.updateSVGProperty("stroke-width");
18556 parent.drawLayer.updateProperties(this._currentDrawId, this._defaultDrawingOptions.toSVGProperties());
18558 createDrawingOptions({
18563 this._drawingOptions = InkEditor.getDefaultDrawingOptions({
18564 stroke: Util.makeHexColor(...color),
18565 "stroke-width": thickness,
18566 "stroke-opacity": opacity
18569 serialize(isForCopying = false) {
18570 if (this.isEmpty()) {
18573 if (this.deleted) {
18574 return this.serializeDeleted();
18580 } = this.serializeDraw(isForCopying);
18584 "stroke-opacity": opacity,
18585 "stroke-width": thickness
18588 const serialized = {
18589 annotationType: AnnotationEditorType.INK,
18590 color: AnnotationEditor._colorManager.convert(stroke),
18597 pageIndex: this.pageIndex,
18599 rotation: this.rotation,
18600 structTreeParentId: this._structTreeParentId
18602 if (isForCopying) {
18605 if (this.annotationElementId && !this.#hasElementChanged(serialized)) {
18608 serialized.id = this.annotationElementId;
18611 #hasElementChanged(serialized) {
18617 } = this._initialData;
18618 return this._hasBeenMoved || this._hasBeenResized || serialized.color.some((c, i) => c !== color[i]) || serialized.thickness !== thickness || serialized.opacity !== opacity || serialized.pageIndex !== pageIndex;
18620 renderAnnotationElement(annotation) {
18624 } = this.serializeDraw(false);
18625 annotation.updateEdited({
18627 thickness: this._drawingOptions["stroke-width"],
18634 ;// ./src/display/editor/drawers/contour.js
18636 class ContourDrawOutline extends InkDrawOutline {
18638 let path = super.toSVGPath();
18639 if (!path.endsWith("Z")) {
18646 ;// ./src/display/editor/drawers/signaturedraw.js
18650 class SignatureExtractor {
18651 static #PARAMETERS = {
18653 sigmaSFactor: 0.02,
18657 static #neighborIndexToId(i0, j0, i, j) {
18661 return j > 0 ? 0 : 4;
18668 static #neighborIdToIndex = new Int32Array([0, 1, -1, 1, -1, 0, -1, -1, 0, -1, 1, -1, 1, 0, 1, 1]);
18669 static #clockwiseNonZero(buf, width, i0, j0, i, j, offset) {
18670 const id = this.#neighborIndexToId(i0, j0, i, j);
18671 for (let k = 0; k < 8; k++) {
18672 const kk = (-k + id - offset + 16) % 8;
18673 const shiftI = this.#neighborIdToIndex[2 * kk];
18674 const shiftJ = this.#neighborIdToIndex[2 * kk + 1];
18675 if (buf[(i0 + shiftI) * width + (j0 + shiftJ)] !== 0) {
18681 static #counterClockwiseNonZero(buf, width, i0, j0, i, j, offset) {
18682 const id = this.#neighborIndexToId(i0, j0, i, j);
18683 for (let k = 0; k < 8; k++) {
18684 const kk = (k + id + offset + 16) % 8;
18685 const shiftI = this.#neighborIdToIndex[2 * kk];
18686 const shiftJ = this.#neighborIdToIndex[2 * kk + 1];
18687 if (buf[(i0 + shiftI) * width + (j0 + shiftJ)] !== 0) {
18693 static #findContours(buf, width, height, threshold) {
18694 const N = buf.length;
18695 const types = new Int32Array(N);
18696 for (let i = 0; i < N; i++) {
18697 types[i] = buf[i] <= threshold ? 1 : 0;
18699 for (let i = 1; i < height - 1; i++) {
18700 types[i * width] = types[i * width + width - 1] = 0;
18702 for (let i = 0; i < width; i++) {
18703 types[i] = types[width * height - 1 - i] = 0;
18707 const contours = [];
18708 for (let i = 1; i < height - 1; i++) {
18710 for (let j = 1; j < width - 1; j++) {
18711 const ij = i * width + j;
18712 const pix = types[ij];
18718 if (pix === 1 && types[ij - 1] === 0) {
18721 } else if (pix >= 1 && types[ij + 1] === 0) {
18729 lnbd = Math.abs(pix);
18733 const points = [j, i];
18734 const isHole = j2 === j + 1;
18741 contours.push(contour);
18743 for (const c of contours) {
18744 if (c.id === lnbd) {
18750 contour.parent = isHole ? lnbd : 0;
18751 } else if (contour0.isHole) {
18752 contour.parent = isHole ? contour0.parent : lnbd;
18754 contour.parent = isHole ? lnbd : contour0.parent;
18756 const k = this.#clockwiseNonZero(types, width, i, j, i2, j2, 0);
18759 if (types[ij] !== 1) {
18760 lnbd = Math.abs(types[ij]);
18764 let shiftI = this.#neighborIdToIndex[2 * k];
18765 let shiftJ = this.#neighborIdToIndex[2 * k + 1];
18766 const i1 = i + shiftI;
18767 const j1 = j + shiftJ;
18773 const kk = this.#counterClockwiseNonZero(types, width, i3, j3, i2, j2, 1);
18774 shiftI = this.#neighborIdToIndex[2 * kk];
18775 shiftJ = this.#neighborIdToIndex[2 * kk + 1];
18776 const i4 = i3 + shiftI;
18777 const j4 = j3 + shiftJ;
18778 points.push(j4, i4);
18779 const ij3 = i3 * width + j3;
18780 if (types[ij3 + 1] === 0) {
18782 } else if (types[ij3] === 1) {
18785 if (i4 === i && j4 === j && i3 === i1 && j3 === j1) {
18786 if (types[ij] !== 1) {
18787 lnbd = Math.abs(types[ij]);
18801 static #douglasPeuckerHelper(points, start, end, output) {
18802 if (end - start <= 4) {
18803 for (let i = start; i < end - 2; i += 2) {
18804 output.push(points[i], points[i + 1]);
18808 const ax = points[start];
18809 const ay = points[start + 1];
18810 const abx = points[end - 4] - ax;
18811 const aby = points[end - 3] - ay;
18812 const dist = Math.hypot(abx, aby);
18813 const nabx = abx / dist;
18814 const naby = aby / dist;
18815 const aa = nabx * ay - naby * ax;
18816 const m = aby / abx;
18817 const invS = 1 / dist;
18818 const phi = Math.atan(m);
18819 const cosPhi = Math.cos(phi);
18820 const sinPhi = Math.sin(phi);
18821 const tmax = invS * (Math.abs(cosPhi) + Math.abs(sinPhi));
18822 const poly = invS * (1 - tmax + tmax ** 2);
18823 const partialPhi = Math.max(Math.atan(Math.abs(sinPhi + cosPhi) * poly), Math.atan(Math.abs(sinPhi - cosPhi) * poly));
18826 for (let i = start + 2; i < end - 2; i += 2) {
18827 const d = Math.abs(aa - nabx * points[i + 1] + naby * points[i]);
18833 if (dmax > (dist * partialPhi) ** 2) {
18834 this.#douglasPeuckerHelper(points, start, index + 2, output);
18835 this.#douglasPeuckerHelper(points, index, end, output);
18837 output.push(ax, ay);
18840 static #douglasPeucker(points) {
18842 const len = points.length;
18843 this.#douglasPeuckerHelper(points, 0, len, output);
18844 output.push(points[len - 2], points[len - 1]);
18845 return output.length <= 4 ? null : output;
18847 static #bilateralFilter(buf, width, height, sigmaS, sigmaR, kernelSize) {
18848 const kernel = new Float32Array(kernelSize ** 2);
18849 const sigmaS2 = -2 * sigmaS ** 2;
18850 const halfSize = kernelSize >> 1;
18851 for (let i = 0; i < kernelSize; i++) {
18852 const x = (i - halfSize) ** 2;
18853 for (let j = 0; j < kernelSize; j++) {
18854 kernel[i * kernelSize + j] = Math.exp((x + (j - halfSize) ** 2) / sigmaS2);
18857 const rangeValues = new Float32Array(256);
18858 const sigmaR2 = -2 * sigmaR ** 2;
18859 for (let i = 0; i < 256; i++) {
18860 rangeValues[i] = Math.exp(i ** 2 / sigmaR2);
18862 const N = buf.length;
18863 const out = new Uint8Array(N);
18864 const histogram = new Uint32Array(256);
18865 for (let i = 0; i < height; i++) {
18866 for (let j = 0; j < width; j++) {
18867 const ij = i * width + j;
18868 const center = buf[ij];
18871 for (let k = 0; k < kernelSize; k++) {
18872 const y = i + k - halfSize;
18873 if (y < 0 || y >= height) {
18876 for (let l = 0; l < kernelSize; l++) {
18877 const x = j + l - halfSize;
18878 if (x < 0 || x >= width) {
18881 const neighbour = buf[y * width + x];
18882 const w = kernel[k * kernelSize + l] * rangeValues[Math.abs(neighbour - center)];
18883 sum += neighbour * w;
18887 const pix = out[ij] = Math.round(sum / norm);
18891 return [out, histogram];
18893 static #getHistogram(buf) {
18894 const histogram = new Uint32Array(256);
18895 for (const g of buf) {
18900 static #toUint8(buf) {
18901 const N = buf.length;
18902 const out = new Uint8ClampedArray(N >> 2);
18903 let max = -Infinity;
18904 let min = Infinity;
18905 for (let i = 0, ii = out.length; i < ii; i++) {
18906 const A = buf[(i << 2) + 3];
18908 max = out[i] = 0xff;
18911 const pix = out[i] = buf[i << 2];
18919 const ratio = 255 / (max - min);
18920 for (let i = 0; i < N; i++) {
18921 out[i] = (out[i] - min) * ratio;
18925 static #guessThreshold(histogram) {
18929 const min = histogram.findIndex(v => v !== 0);
18932 for (i = min; i < 256; i++) {
18933 const v = histogram[i];
18943 for (i = spos - 1; i >= 0; i--) {
18944 if (histogram[i] > histogram[i + 1]) {
18950 static #getGrayPixels(bitmap) {
18951 const originalBitmap = bitmap;
18958 } = this.#PARAMETERS;
18959 let newWidth = width;
18960 let newHeight = height;
18961 if (width > maxDim || height > maxDim) {
18962 let prevWidth = width;
18963 let prevHeight = height;
18964 let steps = Math.log2(Math.max(width, height) / maxDim);
18965 const isteps = Math.floor(steps);
18966 steps = steps === isteps ? isteps - 1 : isteps;
18967 for (let i = 0; i < steps; i++) {
18968 newWidth = prevWidth;
18969 newHeight = prevHeight;
18970 if (newWidth > maxDim) {
18971 newWidth = Math.ceil(newWidth / 2);
18973 if (newHeight > maxDim) {
18974 newHeight = Math.ceil(newHeight / 2);
18976 const offscreen = new OffscreenCanvas(newWidth, newHeight);
18977 const ctx = offscreen.getContext("2d");
18978 ctx.drawImage(bitmap, 0, 0, prevWidth, prevHeight, 0, 0, newWidth, newHeight);
18979 prevWidth = newWidth;
18980 prevHeight = newHeight;
18981 if (bitmap !== originalBitmap) {
18984 bitmap = offscreen.transferToImageBitmap();
18986 const ratio = Math.min(maxDim / newWidth, maxDim / newHeight);
18987 newWidth = Math.round(newWidth * ratio);
18988 newHeight = Math.round(newHeight * ratio);
18990 const offscreen = new OffscreenCanvas(newWidth, newHeight);
18991 const ctx = offscreen.getContext("2d", {
18992 willReadFrequently: true
18994 ctx.filter = "grayscale(1)";
18995 ctx.drawImage(bitmap, 0, 0, bitmap.width, bitmap.height, 0, 0, newWidth, newHeight);
18996 const grayImage = ctx.getImageData(0, 0, newWidth, newHeight).data;
18997 const uint8Buf = this.#toUint8(grayImage);
18998 return [uint8Buf, newWidth, newHeight];
19000 static extractContoursFromText(text, {
19004 }, pageWidth, pageHeight, rotation, innerMargin) {
19005 let canvas = new OffscreenCanvas(1, 1);
19006 let ctx = canvas.getContext("2d", {
19009 const fontSize = 200;
19010 const font = ctx.font = `${fontStyle} ${fontWeight} ${fontSize}px ${fontFamily}`;
19012 actualBoundingBoxLeft,
19013 actualBoundingBoxRight,
19014 actualBoundingBoxAscent,
19015 actualBoundingBoxDescent,
19016 fontBoundingBoxAscent,
19017 fontBoundingBoxDescent,
19019 } = ctx.measureText(text);
19021 const canvasWidth = Math.ceil(Math.max(Math.abs(actualBoundingBoxLeft) + Math.abs(actualBoundingBoxRight) || 0, width) * SCALE);
19022 const canvasHeight = Math.ceil(Math.max(Math.abs(actualBoundingBoxAscent) + Math.abs(actualBoundingBoxDescent) || fontSize, Math.abs(fontBoundingBoxAscent) + Math.abs(fontBoundingBoxDescent) || fontSize) * SCALE);
19023 canvas = new OffscreenCanvas(canvasWidth, canvasHeight);
19024 ctx = canvas.getContext("2d", {
19026 willReadFrequently: true
19029 ctx.filter = "grayscale(1)";
19030 ctx.fillStyle = "white";
19031 ctx.fillRect(0, 0, canvasWidth, canvasHeight);
19032 ctx.fillStyle = "black";
19033 ctx.fillText(text, canvasWidth * (SCALE - 1) / 2, canvasHeight * (3 - SCALE) / 2);
19034 const uint8Buf = this.#toUint8(ctx.getImageData(0, 0, canvasWidth, canvasHeight).data);
19035 const histogram = this.#getHistogram(uint8Buf);
19036 const threshold = this.#guessThreshold(histogram);
19037 const contourList = this.#findContours(uint8Buf, canvasWidth, canvasHeight, threshold);
19038 return this.processDrawnLines({
19040 curves: contourList,
19041 width: canvasWidth,
19042 height: canvasHeight
19052 static process(bitmap, pageWidth, pageHeight, rotation, innerMargin) {
19053 const [uint8Buf, width, height] = this.#getGrayPixels(bitmap);
19054 const [buffer, histogram] = this.#bilateralFilter(uint8Buf, width, height, Math.hypot(width, height) * this.#PARAMETERS.sigmaSFactor, this.#PARAMETERS.sigmaR, this.#PARAMETERS.kernelSize);
19055 const threshold = this.#guessThreshold(histogram);
19056 const contourList = this.#findContours(buffer, width, height, threshold);
19057 return this.processDrawnLines({
19059 curves: contourList,
19071 static processDrawnLines({
19080 if (rotation % 180 !== 0) {
19081 [pageWidth, pageHeight] = [pageHeight, pageWidth];
19089 const linesAndPoints = [];
19090 const ratio = Math.min(pageWidth / width, pageHeight / height);
19091 const xScale = ratio / pageWidth;
19092 const yScale = ratio / pageHeight;
19096 const reducedPoints = mustSmooth ? this.#douglasPeucker(points) : points;
19097 if (!reducedPoints) {
19100 const len = reducedPoints.length;
19101 const newPoints = new Float32Array(len);
19102 const line = new Float32Array(3 * (len === 2 ? 2 : len - 2));
19103 linesAndPoints.push({
19108 newPoints[0] = reducedPoints[0] * xScale;
19109 newPoints[1] = reducedPoints[1] * yScale;
19110 line.set([NaN, NaN, NaN, NaN, newPoints[0], newPoints[1]], 0);
19113 let [x1, y1, x2, y2] = reducedPoints;
19118 newPoints.set([x1, y1, x2, y2], 0);
19119 line.set([NaN, NaN, NaN, NaN, x1, y1], 0);
19120 for (let i = 4; i < len; i += 2) {
19121 const x = newPoints[i] = reducedPoints[i] * xScale;
19122 const y = newPoints[i + 1] = reducedPoints[i + 1] * yScale;
19123 line.set(Outline.createBezierPoints(x1, y1, x2, y2, x, y), (i - 2) * 3);
19124 [x1, y1, x2, y2] = [x2, y2, x, y];
19127 if (linesAndPoints.length === 0) {
19130 const outline = areContours ? new ContourDrawOutline() : new InkDrawOutline();
19131 outline.build(linesAndPoints, pageWidth, pageHeight, 1, rotation, areContours ? 0 : thickness, innerMargin);
19136 ;// ./src/display/editor/signature.js
19143 class SignatureOptions extends DrawingOptions {
19146 super.updateProperties({
19152 const clone = new SignatureOptions();
19153 clone.updateAll(this);
19157 class DrawnSignatureOptions extends InkDrawingOptions {
19158 constructor(viewerParameters) {
19159 super(viewerParameters);
19160 super.updateProperties({
19166 const clone = new DrawnSignatureOptions(this._viewParameters);
19167 clone.updateAll(this);
19171 class SignatureEditor extends DrawingEditor {
19172 #isExtracted = false;
19173 static _type = "signature";
19174 static _editorType = AnnotationEditorType.SIGNATURE;
19175 static _defaultDrawingOptions = null;
19176 constructor(params) {
19179 mustBeCommitted: true,
19180 name: "signatureEditor"
19182 this._willKeepAspectRatio = true;
19184 static initialize(l10n, uiManager) {
19185 AnnotationEditor.initialize(l10n, uiManager);
19186 this._defaultDrawingOptions = new SignatureOptions();
19187 this._defaultDrawnSignatureOptions = new DrawnSignatureOptions(uiManager.viewParameters);
19189 static getDefaultDrawingOptions(options) {
19190 const clone = this._defaultDrawingOptions.clone();
19191 clone.updateProperties(options);
19194 static get supportMultipleDrawings() {
19197 static get typesMap() {
19198 return shadow(this, "typesMap", new Map());
19200 static get isDrawer() {
19203 get isResizable() {
19206 onScaleChanging() {
19207 if (this._drawId === null) {
19210 super.onScaleChanging();
19217 this.div.hidden = true;
19218 this.div.setAttribute("role", "figure");
19219 this._uiManager.getSignature(this);
19222 addSignature(outline, heightInPage) {
19227 this.#isExtracted = outline instanceof ContourDrawOutline;
19228 let drawingOptions;
19229 if (this.#isExtracted) {
19230 drawingOptions = SignatureEditor.getDefaultDrawingOptions();
19232 drawingOptions = SignatureEditor._defaultDrawnSignatureOptions.clone();
19233 drawingOptions.updateProperties({
19234 "stroke-width": outline.thickness
19237 this._addOutlines({
19238 drawOutlines: outline,
19241 const [parentWidth, parentHeight] = this.parentDimensions;
19242 const [, pageHeight] = this.pageDimensions;
19243 let newHeight = heightInPage / pageHeight;
19244 newHeight = newHeight >= 1 ? 0.5 : newHeight;
19245 this.width *= newHeight / this.height;
19246 this.height = newHeight;
19247 this.setDims(parentWidth * this.width, parentHeight * this.height);
19252 this.onScaleChanging();
19254 this._uiManager.addToAnnotationStorage(this);
19255 this.div.hidden = false;
19257 getFromImage(bitmap) {
19264 } = this.parent.viewport;
19265 return SignatureExtractor.process(bitmap, pageWidth, pageHeight, rotation, SignatureEditor._INNER_MARGIN);
19267 getFromText(text, fontInfo) {
19274 } = this.parent.viewport;
19275 return SignatureExtractor.extractContoursFromText(text, fontInfo, pageWidth, pageHeight, rotation, SignatureEditor._INNER_MARGIN);
19277 getDrawnSignature(curves) {
19284 } = this.parent.viewport;
19285 return SignatureExtractor.processDrawnLines({
19290 innerMargin: SignatureEditor._INNER_MARGIN,
19297 ;// ./src/display/editor/stamp.js
19302 class StampEditor extends AnnotationEditor {
19305 #bitmapPromise = null;
19307 #bitmapFile = null;
19308 #bitmapFileName = "";
19310 #missingCanvas = false;
19311 #resizeTimeoutId = null;
19313 #hasBeenAddedInUndoStack = false;
19314 static _type = "stamp";
19315 static _editorType = AnnotationEditorType.STAMP;
19316 constructor(params) {
19319 name: "stampEditor"
19321 this.#bitmapUrl = params.bitmapUrl;
19322 this.#bitmapFile = params.bitmapFile;
19324 static initialize(l10n, uiManager) {
19325 AnnotationEditor.initialize(l10n, uiManager);
19327 static isHandlingMimeForPasting(mime) {
19328 return SupportedImageMimeTypes.includes(mime);
19330 static paste(item, parent) {
19331 parent.pasteEditor(AnnotationEditorType.STAMP, {
19332 bitmapFile: item.getAsFile()
19336 if (this._uiManager.useNewAltTextFlow) {
19337 this.div.hidden = false;
19339 super.altTextFinish();
19341 get telemetryFinalData() {
19344 hasAltText: !!this.altTextData?.altText
19347 static computeTelemetryFinalData(data) {
19348 const hasAltTextStats = data.get("hasAltText");
19350 hasAltText: hasAltTextStats.get(true) ?? 0,
19351 hasNoAltText: hasAltTextStats.get(false) ?? 0
19354 #getBitmapFetched(data, fromId = false) {
19359 this.#bitmap = data.bitmap;
19361 this.#bitmapId = data.id;
19362 this.#isSvg = data.isSvg;
19365 this.#bitmapFileName = data.file.name;
19367 this.#createCanvas();
19370 this.#bitmapPromise = null;
19371 this._uiManager.enableWaiting(false);
19372 if (!this.#canvas) {
19375 if (this._uiManager.useNewAltTextWhenAddingImage && this._uiManager.useNewAltTextFlow && this.#bitmap) {
19376 this._editToolbar.hide();
19377 this._uiManager.editAltText(this, true);
19380 if (!this._uiManager.useNewAltTextWhenAddingImage && this._uiManager.useNewAltTextFlow && this.#bitmap) {
19381 this._reportTelemetry({
19382 action: "pdfjs.image.image_added",
19384 alt_text_modal: false,
19385 alt_text_type: "empty"
19389 this.mlGuessAltText();
19394 async mlGuessAltText(imageData = null, updateAltTextData = true) {
19395 if (this.hasAltTextData()) {
19400 } = this._uiManager;
19402 throw new Error("No ML.");
19404 if (!(await mlManager.isEnabledFor("altText"))) {
19405 throw new Error("ML isn't enabled for alt text.");
19411 } = imageData || this.copyCanvas(null, null, true).imageData;
19412 const response = await mlManager.guess({
19418 channels: data.length / (width * height)
19422 throw new Error("No response from the AI service.");
19424 if (response.error) {
19425 throw new Error("Error from the AI service.");
19427 if (response.cancel) {
19430 if (!response.output) {
19431 throw new Error("No valid response from the AI service.");
19433 const altText = response.output;
19434 await this.setGuessedAltText(altText);
19435 if (updateAltTextData && !this.hasAltTextData()) {
19436 this.altTextData = {
19444 if (this.#bitmapId) {
19445 this._uiManager.enableWaiting(true);
19446 this._uiManager.imageManager.getFromId(this.#bitmapId).then(data => this.#getBitmapFetched(data, true)).finally(() => this.#getBitmapDone());
19449 if (this.#bitmapUrl) {
19450 const url = this.#bitmapUrl;
19451 this.#bitmapUrl = null;
19452 this._uiManager.enableWaiting(true);
19453 this.#bitmapPromise = this._uiManager.imageManager.getFromUrl(url).then(data => this.#getBitmapFetched(data)).finally(() => this.#getBitmapDone());
19456 if (this.#bitmapFile) {
19457 const file = this.#bitmapFile;
19458 this.#bitmapFile = null;
19459 this._uiManager.enableWaiting(true);
19460 this.#bitmapPromise = this._uiManager.imageManager.getFromFile(file).then(data => this.#getBitmapFetched(data)).finally(() => this.#getBitmapDone());
19463 const input = document.createElement("input");
19464 input.type = "file";
19465 input.accept = SupportedImageMimeTypes.join(",");
19466 const signal = this._uiManager._signal;
19467 this.#bitmapPromise = new Promise(resolve => {
19468 input.addEventListener("change", async () => {
19469 if (!input.files || input.files.length === 0) {
19472 this._uiManager.enableWaiting(true);
19473 const data = await this._uiManager.imageManager.getFromFile(input.files[0]);
19474 this._reportTelemetry({
19475 action: "pdfjs.image.image_selected",
19477 alt_text_modal: this._uiManager.useNewAltTextFlow
19480 this.#getBitmapFetched(data);
19486 input.addEventListener("cancel", () => {
19492 }).finally(() => this.#getBitmapDone());
19496 if (this.#bitmapId) {
19497 this.#bitmap = null;
19498 this._uiManager.imageManager.deleteId(this.#bitmapId);
19499 this.#canvas?.remove();
19500 this.#canvas = null;
19501 if (this.#resizeTimeoutId) {
19502 clearTimeout(this.#resizeTimeoutId);
19503 this.#resizeTimeoutId = null;
19509 if (!this.parent) {
19510 if (this.#bitmapId) {
19516 if (this.div === null) {
19519 if (this.#bitmapId && this.#canvas === null) {
19522 if (!this.isAttachedToDOM) {
19523 this.parent.add(this);
19527 this._isDraggable = true;
19533 return !(this.#bitmapPromise || this.#bitmap || this.#bitmapUrl || this.#bitmapFile || this.#bitmapId || this.#missingCanvas);
19535 get isResizable() {
19548 this.div.hidden = true;
19549 this.div.setAttribute("role", "figure");
19550 this.addAltTextButton();
19551 if (!this.#missingCanvas) {
19552 if (this.#bitmap) {
19553 this.#createCanvas();
19558 if (this.width && !this.annotationElementId) {
19559 const [parentWidth, parentHeight] = this.parentDimensions;
19560 this.setAt(baseX * parentWidth, baseY * parentHeight, this.width * parentWidth, this.height * parentHeight);
19562 this._uiManager.addShouldRescale(this);
19565 setCanvas(annotationElementId, canvas) {
19569 } = this._uiManager.imageManager.getFromCanvas(annotationElementId, canvas);
19571 if (bitmapId && this._uiManager.imageManager.isValidId(bitmapId)) {
19572 this.#bitmapId = bitmapId;
19574 this.#bitmap = bitmap;
19576 this.#missingCanvas = false;
19577 this.#createCanvas();
19581 this.onScaleChanging();
19583 onScaleChanging() {
19584 if (!this.parent) {
19587 if (this.#resizeTimeoutId !== null) {
19588 clearTimeout(this.#resizeTimeoutId);
19590 const TIME_TO_WAIT = 200;
19591 this.#resizeTimeoutId = setTimeout(() => {
19592 this.#resizeTimeoutId = null;
19593 this.#drawBitmap();
19604 const [pageWidth, pageHeight] = this.pageDimensions;
19605 const MAX_RATIO = 0.75;
19607 width = this.width * pageWidth;
19608 height = this.height * pageHeight;
19609 } else if (width > MAX_RATIO * pageWidth || height > MAX_RATIO * pageHeight) {
19610 const factor = Math.min(MAX_RATIO * pageWidth / width, MAX_RATIO * pageHeight / height);
19614 const [parentWidth, parentHeight] = this.parentDimensions;
19615 this.setDims(width * parentWidth / pageWidth, height * parentHeight / pageHeight);
19616 this._uiManager.enableWaiting(false);
19617 const canvas = this.#canvas = document.createElement("canvas");
19618 canvas.setAttribute("role", "img");
19619 this.addContainer(canvas);
19620 this.width = width / pageWidth;
19621 this.height = height / pageHeight;
19622 if (this._initialOptions?.isCentered) {
19625 this.fixAndSetPosition();
19627 this._initialOptions = null;
19628 if (!this._uiManager.useNewAltTextWhenAddingImage || !this._uiManager.useNewAltTextFlow || this.annotationElementId) {
19629 div.hidden = false;
19631 this.#drawBitmap();
19632 if (!this.#hasBeenAddedInUndoStack) {
19633 this.parent.addUndoableEditor(this);
19634 this.#hasBeenAddedInUndoStack = true;
19636 this._reportTelemetry({
19637 action: "inserted_image"
19639 if (this.#bitmapFileName) {
19640 canvas.setAttribute("aria-label", this.#bitmapFileName);
19643 copyCanvas(maxDataDimension, maxPreviewDimension, createImageData = false) {
19644 if (!maxDataDimension) {
19645 maxDataDimension = 224;
19648 width: bitmapWidth,
19649 height: bitmapHeight
19651 const outputScale = new OutputScale();
19652 let bitmap = this.#bitmap;
19653 let width = bitmapWidth,
19654 height = bitmapHeight;
19656 if (maxPreviewDimension) {
19657 if (bitmapWidth > maxPreviewDimension || bitmapHeight > maxPreviewDimension) {
19658 const ratio = Math.min(maxPreviewDimension / bitmapWidth, maxPreviewDimension / bitmapHeight);
19659 width = Math.floor(bitmapWidth * ratio);
19660 height = Math.floor(bitmapHeight * ratio);
19662 canvas = document.createElement("canvas");
19663 const scaledWidth = canvas.width = Math.ceil(width * outputScale.sx);
19664 const scaledHeight = canvas.height = Math.ceil(height * outputScale.sy);
19665 if (!this.#isSvg) {
19666 bitmap = this.#scaleBitmap(scaledWidth, scaledHeight);
19668 const ctx = canvas.getContext("2d");
19669 ctx.filter = this._uiManager.hcmFilter;
19670 let white = "white",
19672 if (this._uiManager.hcmFilter !== "none") {
19674 } else if (window.matchMedia?.("(prefers-color-scheme: dark)").matches) {
19679 const boxDimWidth = boxDim * outputScale.sx;
19680 const boxDimHeight = boxDim * outputScale.sy;
19681 const pattern = new OffscreenCanvas(boxDimWidth * 2, boxDimHeight * 2);
19682 const patternCtx = pattern.getContext("2d");
19683 patternCtx.fillStyle = white;
19684 patternCtx.fillRect(0, 0, boxDimWidth * 2, boxDimHeight * 2);
19685 patternCtx.fillStyle = black;
19686 patternCtx.fillRect(0, 0, boxDimWidth, boxDimHeight);
19687 patternCtx.fillRect(boxDimWidth, boxDimHeight, boxDimWidth, boxDimHeight);
19688 ctx.fillStyle = ctx.createPattern(pattern, "repeat");
19689 ctx.fillRect(0, 0, scaledWidth, scaledHeight);
19690 ctx.drawImage(bitmap, 0, 0, bitmap.width, bitmap.height, 0, 0, scaledWidth, scaledHeight);
19692 let imageData = null;
19693 if (createImageData) {
19694 let dataWidth, dataHeight;
19695 if (outputScale.symmetric && bitmap.width < maxDataDimension && bitmap.height < maxDataDimension) {
19696 dataWidth = bitmap.width;
19697 dataHeight = bitmap.height;
19699 bitmap = this.#bitmap;
19700 if (bitmapWidth > maxDataDimension || bitmapHeight > maxDataDimension) {
19701 const ratio = Math.min(maxDataDimension / bitmapWidth, maxDataDimension / bitmapHeight);
19702 dataWidth = Math.floor(bitmapWidth * ratio);
19703 dataHeight = Math.floor(bitmapHeight * ratio);
19704 if (!this.#isSvg) {
19705 bitmap = this.#scaleBitmap(dataWidth, dataHeight);
19709 const offscreen = new OffscreenCanvas(dataWidth, dataHeight);
19710 const offscreenCtx = offscreen.getContext("2d", {
19711 willReadFrequently: true
19713 offscreenCtx.drawImage(bitmap, 0, 0, bitmap.width, bitmap.height, 0, 0, dataWidth, dataHeight);
19716 height: dataHeight,
19717 data: offscreenCtx.getImageData(0, 0, dataWidth, dataHeight).data
19727 #scaleBitmap(width, height) {
19729 width: bitmapWidth,
19730 height: bitmapHeight
19732 let newWidth = bitmapWidth;
19733 let newHeight = bitmapHeight;
19734 let bitmap = this.#bitmap;
19735 while (newWidth > 2 * width || newHeight > 2 * height) {
19736 const prevWidth = newWidth;
19737 const prevHeight = newHeight;
19738 if (newWidth > 2 * width) {
19739 newWidth = newWidth >= 16384 ? Math.floor(newWidth / 2) - 1 : Math.ceil(newWidth / 2);
19741 if (newHeight > 2 * height) {
19742 newHeight = newHeight >= 16384 ? Math.floor(newHeight / 2) - 1 : Math.ceil(newHeight / 2);
19744 const offscreen = new OffscreenCanvas(newWidth, newHeight);
19745 const ctx = offscreen.getContext("2d");
19746 ctx.drawImage(bitmap, 0, 0, prevWidth, prevHeight, 0, 0, newWidth, newHeight);
19747 bitmap = offscreen.transferToImageBitmap();
19752 const [parentWidth, parentHeight] = this.parentDimensions;
19757 const outputScale = new OutputScale();
19758 const scaledWidth = Math.ceil(width * parentWidth * outputScale.sx);
19759 const scaledHeight = Math.ceil(height * parentHeight * outputScale.sy);
19760 const canvas = this.#canvas;
19761 if (!canvas || canvas.width === scaledWidth && canvas.height === scaledHeight) {
19764 canvas.width = scaledWidth;
19765 canvas.height = scaledHeight;
19766 const bitmap = this.#isSvg ? this.#bitmap : this.#scaleBitmap(scaledWidth, scaledHeight);
19767 const ctx = canvas.getContext("2d");
19768 ctx.filter = this._uiManager.hcmFilter;
19769 ctx.drawImage(bitmap, 0, 0, bitmap.width, bitmap.height, 0, 0, scaledWidth, scaledHeight);
19771 getImageForAltText() {
19772 return this.#canvas;
19774 #serializeBitmap(toUrl) {
19777 const url = this._uiManager.imageManager.getSvgUrl(this.#bitmapId);
19782 const canvas = document.createElement("canvas");
19784 width: canvas.width,
19785 height: canvas.height
19787 const ctx = canvas.getContext("2d");
19788 ctx.drawImage(this.#bitmap, 0, 0);
19789 return canvas.toDataURL();
19792 const [pageWidth, pageHeight] = this.pageDimensions;
19793 const width = Math.round(this.width * pageWidth * PixelsPerInch.PDF_TO_CSS_UNITS);
19794 const height = Math.round(this.height * pageHeight * PixelsPerInch.PDF_TO_CSS_UNITS);
19795 const offscreen = new OffscreenCanvas(width, height);
19796 const ctx = offscreen.getContext("2d");
19797 ctx.drawImage(this.#bitmap, 0, 0, this.#bitmap.width, this.#bitmap.height, 0, 0, width, height);
19798 return offscreen.transferToImageBitmap();
19800 return structuredClone(this.#bitmap);
19802 static async deserialize(data, parent, uiManager) {
19803 let initialData = null;
19804 let missingCanvas = false;
19805 if (data instanceof StampAnnotationElement) {
19822 let bitmapId, bitmap;
19824 delete data.canvas;
19828 } = uiManager.imageManager.getFromCanvas(container.id, canvas));
19831 missingCanvas = true;
19832 data._hasNoCanvas = true;
19834 const altText = (await parent._structTree.getAriaAttributes(`${AnnotationPrefix}${id}`))?.get("aria-label") || "";
19835 initialData = data = {
19836 annotationType: AnnotationEditorType.STAMP,
19839 pageIndex: pageNumber - 1,
19840 rect: rect.slice(0),
19844 accessibilityData: {
19853 const editor = await super.deserialize(data, parent, uiManager);
19862 if (missingCanvas) {
19863 uiManager.addMissingCanvas(data.id, editor);
19864 editor.#missingCanvas = true;
19865 } else if (bitmapId && uiManager.imageManager.isValidId(bitmapId)) {
19866 editor.#bitmapId = bitmapId;
19868 editor.#bitmap = bitmap;
19871 editor.#bitmapUrl = bitmapUrl;
19873 editor.#isSvg = isSvg;
19874 const [parentWidth, parentHeight] = editor.pageDimensions;
19875 editor.width = (rect[2] - rect[0]) / parentWidth;
19876 editor.height = (rect[3] - rect[1]) / parentHeight;
19877 editor.annotationElementId = data.id || null;
19878 if (accessibilityData) {
19879 editor.altTextData = accessibilityData;
19881 editor._initialData = initialData;
19882 editor.#hasBeenAddedInUndoStack = !!initialData;
19885 serialize(isForCopying = false, context = null) {
19886 if (this.isEmpty()) {
19889 if (this.deleted) {
19890 return this.serializeDeleted();
19892 const serialized = {
19893 annotationType: AnnotationEditorType.STAMP,
19894 bitmapId: this.#bitmapId,
19895 pageIndex: this.pageIndex,
19896 rect: this.getRect(0, 0),
19897 rotation: this.rotation,
19898 isSvg: this.#isSvg,
19899 structTreeParentId: this._structTreeParentId
19901 if (isForCopying) {
19902 serialized.bitmapUrl = this.#serializeBitmap(true);
19903 serialized.accessibilityData = this.serializeAltText(true);
19909 } = this.serializeAltText(false);
19910 if (!decorative && altText) {
19911 serialized.accessibilityData = {
19916 if (this.annotationElementId) {
19917 const changes = this.#hasElementChanged(serialized);
19918 if (changes.isSame) {
19921 if (changes.isSameAltText) {
19922 delete serialized.accessibilityData;
19924 serialized.accessibilityData.structParent = this._initialData.structParent ?? -1;
19927 serialized.id = this.annotationElementId;
19928 if (context === null) {
19931 context.stamps ||= new Map();
19932 const area = this.#isSvg ? (serialized.rect[2] - serialized.rect[0]) * (serialized.rect[3] - serialized.rect[1]) : null;
19933 if (!context.stamps.has(this.#bitmapId)) {
19934 context.stamps.set(this.#bitmapId, {
19938 serialized.bitmap = this.#serializeBitmap(false);
19939 } else if (this.#isSvg) {
19940 const prevData = context.stamps.get(this.#bitmapId);
19941 if (area > prevData.area) {
19942 prevData.area = area;
19943 prevData.serialized.bitmap.close();
19944 prevData.serialized.bitmap = this.#serializeBitmap(false);
19949 #hasElementChanged(serialized) {
19952 accessibilityData: {
19955 } = this._initialData;
19956 const isSamePageIndex = serialized.pageIndex === pageIndex;
19957 const isSameAltText = (serialized.accessibilityData?.alt || "") === altText;
19959 isSame: !this._hasBeenMoved && !this._hasBeenResized && isSamePageIndex && isSameAltText,
19963 renderAnnotationElement(annotation) {
19964 annotation.updateEdited({
19965 rect: this.getRect(0, 0)
19971 ;// ./src/display/editor/annotation_editor_layer.js
19980 class AnnotationEditorLayer {
19981 #accessibilityManager;
19982 #allowClick = false;
19983 #annotationLayer = null;
19985 #editorFocusTimeoutId = null;
19986 #editors = new Map();
19987 #hadPointerDown = false;
19988 #isDisabling = false;
19989 #isEnabling = false;
19991 #focusedElement = null;
19993 #textSelectionAC = null;
19995 static _initialized = false;
19996 static #editorTypes = new Map([FreeTextEditor, InkEditor, StampEditor, HighlightEditor, SignatureEditor].map(type => [type._editorType, type]));
20002 accessibilityManager,
20009 const editorTypes = [...AnnotationEditorLayer.#editorTypes.values()];
20010 if (!AnnotationEditorLayer._initialized) {
20011 AnnotationEditorLayer._initialized = true;
20012 for (const editorType of editorTypes) {
20013 editorType.initialize(l10n, uiManager);
20016 uiManager.registerEditorTypes(editorTypes);
20017 this.#uiManager = uiManager;
20018 this.pageIndex = pageIndex;
20020 this.#accessibilityManager = accessibilityManager;
20021 this.#annotationLayer = annotationLayer;
20022 this.viewport = viewport;
20023 this.#textLayer = textLayer;
20024 this.drawLayer = drawLayer;
20025 this._structTree = structTreeLayer;
20026 this.#uiManager.addLayer(this);
20029 return this.#editors.size === 0;
20031 get isInvisible() {
20032 return this.isEmpty && this.#uiManager.getMode() === AnnotationEditorType.NONE;
20034 updateToolbar(mode) {
20035 this.#uiManager.updateToolbar(mode);
20037 updateMode(mode = this.#uiManager.getMode()) {
20040 case AnnotationEditorType.NONE:
20041 this.disableTextSelection();
20042 this.togglePointerEvents(false);
20043 this.toggleAnnotationLayerPointerEvents(true);
20044 this.disableClick();
20046 case AnnotationEditorType.INK:
20047 this.disableTextSelection();
20048 this.togglePointerEvents(true);
20049 this.enableClick();
20051 case AnnotationEditorType.HIGHLIGHT:
20052 this.enableTextSelection();
20053 this.togglePointerEvents(false);
20054 this.disableClick();
20057 this.disableTextSelection();
20058 this.togglePointerEvents(true);
20059 this.enableClick();
20061 this.toggleAnnotationLayerPointerEvents(false);
20065 for (const editorType of AnnotationEditorLayer.#editorTypes.values()) {
20066 classList.toggle(`${editorType._type}Editing`, mode === editorType._editorType);
20068 this.div.hidden = false;
20070 hasTextLayer(textLayer) {
20071 return textLayer === this.#textLayer?.div;
20073 setEditingState(isEditing) {
20074 this.#uiManager.setEditingState(isEditing);
20076 addCommands(params) {
20077 this.#uiManager.addCommands(params);
20079 cleanUndoStack(type) {
20080 this.#uiManager.cleanUndoStack(type);
20082 toggleDrawing(enabled = false) {
20083 this.div.classList.toggle("drawing", !enabled);
20085 togglePointerEvents(enabled = false) {
20086 this.div.classList.toggle("disabled", !enabled);
20088 toggleAnnotationLayerPointerEvents(enabled = false) {
20089 this.#annotationLayer?.div.classList.toggle("disabled", !enabled);
20092 this.#isEnabling = true;
20093 this.div.tabIndex = 0;
20094 this.togglePointerEvents(true);
20095 const annotationElementIds = new Set();
20096 for (const editor of this.#editors.values()) {
20097 editor.enableEditing();
20099 if (editor.annotationElementId) {
20100 this.#uiManager.removeChangedExistingAnnotation(editor);
20101 annotationElementIds.add(editor.annotationElementId);
20104 if (!this.#annotationLayer) {
20105 this.#isEnabling = false;
20108 const editables = this.#annotationLayer.getEditableAnnotations();
20109 for (const editable of editables) {
20111 if (this.#uiManager.isDeletedAnnotationElement(editable.data.id)) {
20114 if (annotationElementIds.has(editable.data.id)) {
20117 const editor = await this.deserialize(editable);
20121 this.addOrRebuild(editor);
20122 editor.enableEditing();
20124 this.#isEnabling = false;
20127 this.#isDisabling = true;
20128 this.div.tabIndex = -1;
20129 this.togglePointerEvents(false);
20130 const changedAnnotations = new Map();
20131 const resetAnnotations = new Map();
20132 for (const editor of this.#editors.values()) {
20133 editor.disableEditing();
20134 if (!editor.annotationElementId) {
20137 if (editor.serialize() !== null) {
20138 changedAnnotations.set(editor.annotationElementId, editor);
20141 resetAnnotations.set(editor.annotationElementId, editor);
20143 this.getEditableAnnotation(editor.annotationElementId)?.show();
20146 if (this.#annotationLayer) {
20147 const editables = this.#annotationLayer.getEditableAnnotations();
20148 for (const editable of editables) {
20152 if (this.#uiManager.isDeletedAnnotationElement(id)) {
20155 let editor = resetAnnotations.get(id);
20157 editor.resetAnnotationElement(editable);
20158 editor.show(false);
20162 editor = changedAnnotations.get(id);
20164 this.#uiManager.addChangedExistingAnnotation(editor);
20165 if (editor.renderAnnotationElement(editable)) {
20166 editor.show(false);
20173 if (this.isEmpty) {
20174 this.div.hidden = true;
20179 for (const editorType of AnnotationEditorLayer.#editorTypes.values()) {
20180 classList.remove(`${editorType._type}Editing`);
20182 this.disableTextSelection();
20183 this.toggleAnnotationLayerPointerEvents(true);
20184 this.#isDisabling = false;
20186 getEditableAnnotation(id) {
20187 return this.#annotationLayer?.getEditableAnnotation(id) || null;
20189 setActiveEditor(editor) {
20190 const currentActive = this.#uiManager.getActive();
20191 if (currentActive === editor) {
20194 this.#uiManager.setActiveEditor(editor);
20196 enableTextSelection() {
20197 this.div.tabIndex = -1;
20198 if (this.#textLayer?.div && !this.#textSelectionAC) {
20199 this.#textSelectionAC = new AbortController();
20200 const signal = this.#uiManager.combinedSignal(this.#textSelectionAC);
20201 this.#textLayer.div.addEventListener("pointerdown", this.#textLayerPointerDown.bind(this), {
20204 this.#textLayer.div.classList.add("highlighting");
20207 disableTextSelection() {
20208 this.div.tabIndex = 0;
20209 if (this.#textLayer?.div && this.#textSelectionAC) {
20210 this.#textSelectionAC.abort();
20211 this.#textSelectionAC = null;
20212 this.#textLayer.div.classList.remove("highlighting");
20215 #textLayerPointerDown(event) {
20216 this.#uiManager.unselectAll();
20220 if (target === this.#textLayer.div || (target.getAttribute("role") === "img" || target.classList.contains("endOfContent")) && this.#textLayer.div.contains(target)) {
20223 } = util_FeatureTest.platform;
20224 if (event.button !== 0 || event.ctrlKey && isMac) {
20227 this.#uiManager.showAllEditors("highlight", true, true);
20228 this.#textLayer.div.classList.add("free");
20229 this.toggleDrawing();
20230 HighlightEditor.startHighlighting(this, this.#uiManager.direction === "ltr", {
20231 target: this.#textLayer.div,
20235 this.#textLayer.div.addEventListener("pointerup", () => {
20236 this.#textLayer.div.classList.remove("free");
20237 this.toggleDrawing(true);
20240 signal: this.#uiManager._signal
20242 event.preventDefault();
20246 if (this.#clickAC) {
20249 this.#clickAC = new AbortController();
20250 const signal = this.#uiManager.combinedSignal(this.#clickAC);
20251 this.div.addEventListener("pointerdown", this.pointerdown.bind(this), {
20254 const pointerup = this.pointerup.bind(this);
20255 this.div.addEventListener("pointerup", pointerup, {
20258 this.div.addEventListener("pointercancel", pointerup, {
20263 this.#clickAC?.abort();
20264 this.#clickAC = null;
20267 this.#editors.set(editor.id, editor);
20269 annotationElementId
20271 if (annotationElementId && this.#uiManager.isDeletedAnnotationElement(annotationElementId)) {
20272 this.#uiManager.removeDeletedAnnotationElement(editor);
20276 this.#editors.delete(editor.id);
20277 this.#accessibilityManager?.removePointerInTextLayer(editor.contentDiv);
20278 if (!this.#isDisabling && editor.annotationElementId) {
20279 this.#uiManager.addDeletedAnnotationElement(editor);
20283 this.detach(editor);
20284 this.#uiManager.removeEditor(editor);
20285 editor.div.remove();
20286 editor.isAttachedToDOM = false;
20288 changeParent(editor) {
20289 if (editor.parent === this) {
20292 if (editor.parent && editor.annotationElementId) {
20293 this.#uiManager.addDeletedAnnotationElement(editor.annotationElementId);
20294 AnnotationEditor.deleteAnnotationElement(editor);
20295 editor.annotationElementId = null;
20297 this.attach(editor);
20298 editor.parent?.detach(editor);
20299 editor.setParent(this);
20300 if (editor.div && editor.isAttachedToDOM) {
20301 editor.div.remove();
20302 this.div.append(editor.div);
20306 if (editor.parent === this && editor.isAttachedToDOM) {
20309 this.changeParent(editor);
20310 this.#uiManager.addEditor(editor);
20311 this.attach(editor);
20312 if (!editor.isAttachedToDOM) {
20313 const div = editor.render();
20314 this.div.append(div);
20315 editor.isAttachedToDOM = true;
20317 editor.fixAndSetPosition();
20318 editor.onceAdded(!this.#isEnabling);
20319 this.#uiManager.addToAnnotationStorage(editor);
20320 editor._reportTelemetry(editor.telemetryInitialData);
20322 moveEditorInDOM(editor) {
20323 if (!editor.isAttachedToDOM) {
20329 if (editor.div.contains(activeElement) && !this.#editorFocusTimeoutId) {
20330 editor._focusEventsAllowed = false;
20331 this.#editorFocusTimeoutId = setTimeout(() => {
20332 this.#editorFocusTimeoutId = null;
20333 if (!editor.div.contains(document.activeElement)) {
20334 editor.div.addEventListener("focusin", () => {
20335 editor._focusEventsAllowed = true;
20338 signal: this.#uiManager._signal
20340 activeElement.focus();
20342 editor._focusEventsAllowed = true;
20346 editor._structTreeParentId = this.#accessibilityManager?.moveElementInDOM(this.div, editor.div, editor.contentDiv, true);
20348 addOrRebuild(editor) {
20349 if (editor.needsToBeRebuilt()) {
20350 editor.parent ||= this;
20357 addUndoableEditor(editor) {
20358 const cmd = () => editor._uiManager.rebuild(editor);
20359 const undo = () => {
20369 return this.#uiManager.getId();
20371 get #currentEditorType() {
20372 return AnnotationEditorLayer.#editorTypes.get(this.#uiManager.getMode());
20374 combinedSignal(ac) {
20375 return this.#uiManager.combinedSignal(ac);
20377 #createNewEditor(params) {
20378 const editorType = this.#currentEditorType;
20379 return editorType ? new editorType.prototype.constructor(params) : null;
20381 canCreateNewEmptyEditor() {
20382 return this.#currentEditorType?.canCreateNewEmptyEditor();
20384 pasteEditor(mode, params) {
20385 this.#uiManager.updateToolbar(mode);
20386 this.#uiManager.updateMode(mode);
20390 } = this.#getCenterPoint();
20391 const id = this.getNextId();
20392 const editor = this.#createNewEditor({
20397 uiManager: this.#uiManager,
20405 async deserialize(data) {
20406 return (await AnnotationEditorLayer.#editorTypes.get(data.annotationType ?? data.annotationEditorType)?.deserialize(data, this, this.#uiManager)) || null;
20408 createAndAddNewEditor(event, isCentered, data = {}) {
20409 const id = this.getNextId();
20410 const editor = this.#createNewEditor({
20415 uiManager: this.#uiManager,
20424 #getCenterPoint() {
20430 } = this.div.getBoundingClientRect();
20431 const tlX = Math.max(0, x);
20432 const tlY = Math.max(0, y);
20433 const brX = Math.min(window.innerWidth, x + width);
20434 const brY = Math.min(window.innerHeight, y + height);
20435 const centerX = (tlX + brX) / 2 - x;
20436 const centerY = (tlY + brY) / 2 - y;
20437 const [offsetX, offsetY] = this.viewport.rotation % 180 === 0 ? [centerX, centerY] : [centerY, centerX];
20444 this.createAndAddNewEditor(this.#getCenterPoint(), true);
20446 setSelected(editor) {
20447 this.#uiManager.setSelected(editor);
20449 toggleSelected(editor) {
20450 this.#uiManager.toggleSelected(editor);
20453 this.#uiManager.unselect(editor);
20458 } = util_FeatureTest.platform;
20459 if (event.button !== 0 || event.ctrlKey && isMac) {
20462 if (event.target !== this.div) {
20465 if (!this.#hadPointerDown) {
20468 this.#hadPointerDown = false;
20469 if (this.#currentEditorType?.isDrawer && this.#currentEditorType.supportMultipleDrawings) {
20472 if (!this.#allowClick) {
20473 this.#allowClick = true;
20476 const currentMode = this.#uiManager.getMode();
20477 if (currentMode === AnnotationEditorType.STAMP || currentMode === AnnotationEditorType.SIGNATURE) {
20478 this.#uiManager.unselectAll();
20481 this.createAndAddNewEditor(event, false);
20483 pointerdown(event) {
20484 if (this.#uiManager.getMode() === AnnotationEditorType.HIGHLIGHT) {
20485 this.enableTextSelection();
20487 if (this.#hadPointerDown) {
20488 this.#hadPointerDown = false;
20493 } = util_FeatureTest.platform;
20494 if (event.button !== 0 || event.ctrlKey && isMac) {
20497 if (event.target !== this.div) {
20500 this.#hadPointerDown = true;
20501 if (this.#currentEditorType?.isDrawer) {
20502 this.startDrawingSession(event);
20505 const editor = this.#uiManager.getActive();
20506 this.#allowClick = !editor || editor.isEmpty();
20508 startDrawingSession(event) {
20510 preventScroll: true
20512 if (this.#drawingAC) {
20513 this.#currentEditorType.startDrawing(this, this.#uiManager, false, event);
20516 this.#uiManager.setCurrentDrawingSession(this);
20517 this.#drawingAC = new AbortController();
20518 const signal = this.#uiManager.combinedSignal(this.#drawingAC);
20519 this.div.addEventListener("blur", ({
20522 if (relatedTarget && !this.div.contains(relatedTarget)) {
20523 this.#focusedElement = null;
20524 this.commitOrRemove();
20529 this.#currentEditorType.startDrawing(this, this.#uiManager, false, event);
20536 if (this.div.contains(activeElement)) {
20537 this.#focusedElement = activeElement;
20541 if (this.#focusedElement) {
20543 this.#focusedElement?.focus();
20544 this.#focusedElement = null;
20548 endDrawingSession(isAborted = false) {
20549 if (!this.#drawingAC) {
20552 this.#uiManager.setCurrentDrawingSession(null);
20553 this.#drawingAC.abort();
20554 this.#drawingAC = null;
20555 this.#focusedElement = null;
20556 return this.#currentEditorType.endDrawing(isAborted);
20558 findNewParent(editor, x, y) {
20559 const layer = this.#uiManager.findParent(x, y);
20560 if (layer === null || layer === this) {
20563 layer.changeParent(editor);
20567 if (this.#drawingAC) {
20568 this.endDrawingSession();
20573 onScaleChanging() {
20574 if (!this.#drawingAC) {
20577 this.#currentEditorType.onScaleChangingWhenDrawing(this);
20580 this.commitOrRemove();
20581 if (this.#uiManager.getActive()?.parent === this) {
20582 this.#uiManager.commitOrRemove();
20583 this.#uiManager.setActiveEditor(null);
20585 if (this.#editorFocusTimeoutId) {
20586 clearTimeout(this.#editorFocusTimeoutId);
20587 this.#editorFocusTimeoutId = null;
20589 for (const editor of this.#editors.values()) {
20590 this.#accessibilityManager?.removePointerInTextLayer(editor.contentDiv);
20591 editor.setParent(null);
20592 editor.isAttachedToDOM = false;
20593 editor.div.remove();
20596 this.#editors.clear();
20597 this.#uiManager.removeLayer(this);
20600 for (const editor of this.#editors.values()) {
20601 if (editor.isEmpty()) {
20609 this.viewport = viewport;
20610 setLayerDimensions(this.div, viewport);
20611 for (const editor of this.#uiManager.getEditors(this.pageIndex)) {
20620 this.#uiManager.commitOrRemove();
20622 const oldRotation = this.viewport.rotation;
20623 const rotation = viewport.rotation;
20624 this.viewport = viewport;
20625 setLayerDimensions(this.div, {
20628 if (oldRotation !== rotation) {
20629 for (const editor of this.#editors.values()) {
20630 editor.rotate(rotation);
20634 get pageDimensions() {
20638 } = this.viewport.rawDims;
20639 return [pageWidth, pageHeight];
20642 return this.#uiManager.viewParameters.realScale;
20646 ;// ./src/display/draw_layer.js
20651 #mapping = new Map();
20652 #toUpdate = new Map();
20657 this.pageIndex = pageIndex;
20659 setParent(parent) {
20660 if (!this.#parent) {
20661 this.#parent = parent;
20664 if (this.#parent !== parent) {
20665 if (this.#mapping.size > 0) {
20666 for (const root of this.#mapping.values()) {
20668 parent.append(root);
20671 this.#parent = parent;
20674 static get _svgFactory() {
20675 return shadow(this, "_svgFactory", new DOMSVGFactory());
20677 static #setBox(element, [x, y, width, height]) {
20681 style.top = `${100 * y}%`;
20682 style.left = `${100 * x}%`;
20683 style.width = `${100 * width}%`;
20684 style.height = `${100 * height}%`;
20687 const svg = DrawLayer._svgFactory.create(1, 1, true);
20688 this.#parent.append(svg);
20689 svg.setAttribute("aria-hidden", true);
20692 #createClipPath(defs, pathId) {
20693 const clipPath = DrawLayer._svgFactory.createElement("clipPath");
20694 defs.append(clipPath);
20695 const clipPathId = `clip_${pathId}`;
20696 clipPath.setAttribute("id", clipPathId);
20697 clipPath.setAttribute("clipPathUnits", "objectBoundingBox");
20698 const clipPathUse = DrawLayer._svgFactory.createElement("use");
20699 clipPath.append(clipPathUse);
20700 clipPathUse.setAttribute("href", `#${pathId}`);
20701 clipPathUse.classList.add("clip");
20704 #updateProperties(element, properties) {
20705 for (const [key, value] of Object.entries(properties)) {
20706 if (value === null) {
20707 element.removeAttribute(key);
20709 element.setAttribute(key, value);
20713 draw(properties, isPathUpdatable = false, hasClip = false) {
20714 const id = DrawLayer.#id++;
20715 const root = this.#createSVG();
20716 const defs = DrawLayer._svgFactory.createElement("defs");
20718 const path = DrawLayer._svgFactory.createElement("path");
20720 const pathId = `path_p${this.pageIndex}_${id}`;
20721 path.setAttribute("id", pathId);
20722 path.setAttribute("vector-effect", "non-scaling-stroke");
20723 if (isPathUpdatable) {
20724 this.#toUpdate.set(id, path);
20726 const clipPathId = hasClip ? this.#createClipPath(defs, pathId) : null;
20727 const use = DrawLayer._svgFactory.createElement("use");
20729 use.setAttribute("href", `#${pathId}`);
20730 this.updateProperties(root, properties);
20731 this.#mapping.set(id, root);
20734 clipPathId: `url(#${clipPathId})`
20737 drawOutline(properties, mustRemoveSelfIntersections) {
20738 const id = DrawLayer.#id++;
20739 const root = this.#createSVG();
20740 const defs = DrawLayer._svgFactory.createElement("defs");
20742 const path = DrawLayer._svgFactory.createElement("path");
20744 const pathId = `path_p${this.pageIndex}_${id}`;
20745 path.setAttribute("id", pathId);
20746 path.setAttribute("vector-effect", "non-scaling-stroke");
20748 if (mustRemoveSelfIntersections) {
20749 const mask = DrawLayer._svgFactory.createElement("mask");
20751 maskId = `mask_p${this.pageIndex}_${id}`;
20752 mask.setAttribute("id", maskId);
20753 mask.setAttribute("maskUnits", "objectBoundingBox");
20754 const rect = DrawLayer._svgFactory.createElement("rect");
20756 rect.setAttribute("width", "1");
20757 rect.setAttribute("height", "1");
20758 rect.setAttribute("fill", "white");
20759 const use = DrawLayer._svgFactory.createElement("use");
20761 use.setAttribute("href", `#${pathId}`);
20762 use.setAttribute("stroke", "none");
20763 use.setAttribute("fill", "black");
20764 use.setAttribute("fill-rule", "nonzero");
20765 use.classList.add("mask");
20767 const use1 = DrawLayer._svgFactory.createElement("use");
20769 use1.setAttribute("href", `#${pathId}`);
20771 use1.setAttribute("mask", `url(#${maskId})`);
20773 const use2 = use1.cloneNode();
20775 use1.classList.add("mainOutline");
20776 use2.classList.add("secondaryOutline");
20777 this.updateProperties(root, properties);
20778 this.#mapping.set(id, root);
20781 finalizeDraw(id, properties) {
20782 this.#toUpdate.delete(id);
20783 this.updateProperties(id, properties);
20785 updateProperties(elementOrId, properties) {
20795 const element = typeof elementOrId === "number" ? this.#mapping.get(elementOrId) : elementOrId;
20800 this.#updateProperties(element, root);
20803 DrawLayer.#setBox(element, bbox);
20809 for (const [className, value] of Object.entries(rootClass)) {
20810 classList.toggle(className, value);
20814 const defs = element.firstChild;
20815 const pathElement = defs.firstChild;
20816 this.#updateProperties(pathElement, path);
20819 updateParent(id, layer) {
20820 if (layer === this) {
20823 const root = this.#mapping.get(id);
20827 layer.#parent.append(root);
20828 this.#mapping.delete(id);
20829 layer.#mapping.set(id, root);
20832 this.#toUpdate.delete(id);
20833 if (this.#parent === null) {
20836 this.#mapping.get(id).remove();
20837 this.#mapping.delete(id);
20840 this.#parent = null;
20841 for (const root of this.#mapping.values()) {
20844 this.#mapping.clear();
20845 this.#toUpdate.clear();
20864 const pdfjsVersion = "5.0.98";
20865 const pdfjsBuild = "16155fd80";
20867 var __webpack_exports__AbortException = __webpack_exports__.AbortException;
20868 var __webpack_exports__AnnotationBorderStyleType = __webpack_exports__.AnnotationBorderStyleType;
20869 var __webpack_exports__AnnotationEditorLayer = __webpack_exports__.AnnotationEditorLayer;
20870 var __webpack_exports__AnnotationEditorParamsType = __webpack_exports__.AnnotationEditorParamsType;
20871 var __webpack_exports__AnnotationEditorType = __webpack_exports__.AnnotationEditorType;
20872 var __webpack_exports__AnnotationEditorUIManager = __webpack_exports__.AnnotationEditorUIManager;
20873 var __webpack_exports__AnnotationLayer = __webpack_exports__.AnnotationLayer;
20874 var __webpack_exports__AnnotationMode = __webpack_exports__.AnnotationMode;
20875 var __webpack_exports__AnnotationType = __webpack_exports__.AnnotationType;
20876 var __webpack_exports__ColorPicker = __webpack_exports__.ColorPicker;
20877 var __webpack_exports__DOMSVGFactory = __webpack_exports__.DOMSVGFactory;
20878 var __webpack_exports__DrawLayer = __webpack_exports__.DrawLayer;
20879 var __webpack_exports__FeatureTest = __webpack_exports__.FeatureTest;
20880 var __webpack_exports__GlobalWorkerOptions = __webpack_exports__.GlobalWorkerOptions;
20881 var __webpack_exports__ImageKind = __webpack_exports__.ImageKind;
20882 var __webpack_exports__InvalidPDFException = __webpack_exports__.InvalidPDFException;
20883 var __webpack_exports__OPS = __webpack_exports__.OPS;
20884 var __webpack_exports__OutputScale = __webpack_exports__.OutputScale;
20885 var __webpack_exports__PDFDataRangeTransport = __webpack_exports__.PDFDataRangeTransport;
20886 var __webpack_exports__PDFDateString = __webpack_exports__.PDFDateString;
20887 var __webpack_exports__PDFWorker = __webpack_exports__.PDFWorker;
20888 var __webpack_exports__PasswordResponses = __webpack_exports__.PasswordResponses;
20889 var __webpack_exports__PermissionFlag = __webpack_exports__.PermissionFlag;
20890 var __webpack_exports__PixelsPerInch = __webpack_exports__.PixelsPerInch;
20891 var __webpack_exports__RenderingCancelledException = __webpack_exports__.RenderingCancelledException;
20892 var __webpack_exports__ResponseException = __webpack_exports__.ResponseException;
20893 var __webpack_exports__SupportedImageMimeTypes = __webpack_exports__.SupportedImageMimeTypes;
20894 var __webpack_exports__TextLayer = __webpack_exports__.TextLayer;
20895 var __webpack_exports__TouchManager = __webpack_exports__.TouchManager;
20896 var __webpack_exports__Util = __webpack_exports__.Util;
20897 var __webpack_exports__VerbosityLevel = __webpack_exports__.VerbosityLevel;
20898 var __webpack_exports__XfaLayer = __webpack_exports__.XfaLayer;
20899 var __webpack_exports__build = __webpack_exports__.build;
20900 var __webpack_exports__createValidAbsoluteUrl = __webpack_exports__.createValidAbsoluteUrl;
20901 var __webpack_exports__fetchData = __webpack_exports__.fetchData;
20902 var __webpack_exports__getDocument = __webpack_exports__.getDocument;
20903 var __webpack_exports__getFilenameFromUrl = __webpack_exports__.getFilenameFromUrl;
20904 var __webpack_exports__getPdfFilenameFromUrl = __webpack_exports__.getPdfFilenameFromUrl;
20905 var __webpack_exports__getXfaPageViewport = __webpack_exports__.getXfaPageViewport;
20906 var __webpack_exports__isDataScheme = __webpack_exports__.isDataScheme;
20907 var __webpack_exports__isPdfFile = __webpack_exports__.isPdfFile;
20908 var __webpack_exports__noContextMenu = __webpack_exports__.noContextMenu;
20909 var __webpack_exports__normalizeUnicode = __webpack_exports__.normalizeUnicode;
20910 var __webpack_exports__setLayerDimensions = __webpack_exports__.setLayerDimensions;
20911 var __webpack_exports__shadow = __webpack_exports__.shadow;
20912 var __webpack_exports__stopEvent = __webpack_exports__.stopEvent;
20913 var __webpack_exports__version = __webpack_exports__.version;
20914 export { __webpack_exports__AbortException as AbortException, __webpack_exports__AnnotationBorderStyleType as AnnotationBorderStyleType, __webpack_exports__AnnotationEditorLayer as AnnotationEditorLayer, __webpack_exports__AnnotationEditorParamsType as AnnotationEditorParamsType, __webpack_exports__AnnotationEditorType as AnnotationEditorType, __webpack_exports__AnnotationEditorUIManager as AnnotationEditorUIManager, __webpack_exports__AnnotationLayer as AnnotationLayer, __webpack_exports__AnnotationMode as AnnotationMode, __webpack_exports__AnnotationType as AnnotationType, __webpack_exports__ColorPicker as ColorPicker, __webpack_exports__DOMSVGFactory as DOMSVGFactory, __webpack_exports__DrawLayer as DrawLayer, __webpack_exports__FeatureTest as FeatureTest, __webpack_exports__GlobalWorkerOptions as GlobalWorkerOptions, __webpack_exports__ImageKind as ImageKind, __webpack_exports__InvalidPDFException as InvalidPDFException, __webpack_exports__OPS as OPS, __webpack_exports__OutputScale as OutputScale, __webpack_exports__PDFDataRangeTransport as PDFDataRangeTransport, __webpack_exports__PDFDateString as PDFDateString, __webpack_exports__PDFWorker as PDFWorker, __webpack_exports__PasswordResponses as PasswordResponses, __webpack_exports__PermissionFlag as PermissionFlag, __webpack_exports__PixelsPerInch as PixelsPerInch, __webpack_exports__RenderingCancelledException as RenderingCancelledException, __webpack_exports__ResponseException as ResponseException, __webpack_exports__SupportedImageMimeTypes as SupportedImageMimeTypes, __webpack_exports__TextLayer as TextLayer, __webpack_exports__TouchManager as TouchManager, __webpack_exports__Util as Util, __webpack_exports__VerbosityLevel as VerbosityLevel, __webpack_exports__XfaLayer as XfaLayer, __webpack_exports__build as build, __webpack_exports__createValidAbsoluteUrl as createValidAbsoluteUrl, __webpack_exports__fetchData as fetchData, __webpack_exports__getDocument as getDocument, __webpack_exports__getFilenameFromUrl as getFilenameFromUrl, __webpack_exports__getPdfFilenameFromUrl as getPdfFilenameFromUrl, __webpack_exports__getXfaPageViewport as getXfaPageViewport, __webpack_exports__isDataScheme as isDataScheme, __webpack_exports__isPdfFile as isPdfFile, __webpack_exports__noContextMenu as noContextMenu, __webpack_exports__normalizeUnicode as normalizeUnicode, __webpack_exports__setLayerDimensions as setLayerDimensions, __webpack_exports__shadow as shadow, __webpack_exports__stopEvent as stopEvent, __webpack_exports__version as version };