/** * @licstart The following is the entire license notice for the * JavaScript code in this page * * Copyright 2023 Mozilla Foundation * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. * * @licend The above is the entire license notice for the * JavaScript code in this page */ /******/ // The require scope /******/ var __webpack_require__ = {}; /******/ /************************************************************************/ /******/ /* webpack/runtime/define property getters */ /******/ (() => { /******/ // define getter functions for harmony exports /******/ __webpack_require__.d = (exports, definition) => { /******/ for(var key in definition) { /******/ if(__webpack_require__.o(definition, key) && !__webpack_require__.o(exports, key)) { /******/ Object.defineProperty(exports, key, { enumerable: true, get: definition[key] }); /******/ } /******/ } /******/ }; /******/ })(); /******/ /******/ /* webpack/runtime/hasOwnProperty shorthand */ /******/ (() => { /******/ __webpack_require__.o = (obj, prop) => (Object.prototype.hasOwnProperty.call(obj, prop)) /******/ })(); /******/ /************************************************************************/ var __webpack_exports__ = globalThis.pdfjsLib = {}; // EXPORTS __webpack_require__.d(__webpack_exports__, { AbortException: () => (/* reexport */ AbortException), AnnotationEditorLayer: () => (/* reexport */ AnnotationEditorLayer), AnnotationEditorParamsType: () => (/* reexport */ AnnotationEditorParamsType), AnnotationEditorType: () => (/* reexport */ AnnotationEditorType), AnnotationEditorUIManager: () => (/* reexport */ AnnotationEditorUIManager), AnnotationLayer: () => (/* reexport */ AnnotationLayer), AnnotationMode: () => (/* reexport */ AnnotationMode), CMapCompressionType: () => (/* reexport */ CMapCompressionType), ColorPicker: () => (/* reexport */ ColorPicker), DOMSVGFactory: () => (/* reexport */ DOMSVGFactory), DrawLayer: () => (/* reexport */ DrawLayer), FeatureTest: () => (/* reexport */ util_FeatureTest), GlobalWorkerOptions: () => (/* reexport */ GlobalWorkerOptions), ImageKind: () => (/* reexport */ util_ImageKind), InvalidPDFException: () => (/* reexport */ InvalidPDFException), MissingPDFException: () => (/* reexport */ MissingPDFException), OPS: () => (/* reexport */ OPS), Outliner: () => (/* reexport */ Outliner), PDFDataRangeTransport: () => (/* reexport */ PDFDataRangeTransport), PDFDateString: () => (/* reexport */ PDFDateString), PDFWorker: () => (/* reexport */ PDFWorker), PasswordResponses: () => (/* reexport */ PasswordResponses), PermissionFlag: () => (/* reexport */ PermissionFlag), PixelsPerInch: () => (/* reexport */ PixelsPerInch), RenderingCancelledException: () => (/* reexport */ RenderingCancelledException), UnexpectedResponseException: () => (/* reexport */ UnexpectedResponseException), Util: () => (/* reexport */ Util), VerbosityLevel: () => (/* reexport */ VerbosityLevel), XfaLayer: () => (/* reexport */ XfaLayer), build: () => (/* reexport */ build), createValidAbsoluteUrl: () => (/* reexport */ createValidAbsoluteUrl), fetchData: () => (/* reexport */ fetchData), getDocument: () => (/* reexport */ getDocument), getFilenameFromUrl: () => (/* reexport */ getFilenameFromUrl), getPdfFilenameFromUrl: () => (/* reexport */ getPdfFilenameFromUrl), getXfaPageViewport: () => (/* reexport */ getXfaPageViewport), isDataScheme: () => (/* reexport */ isDataScheme), isPdfFile: () => (/* reexport */ isPdfFile), noContextMenu: () => (/* reexport */ noContextMenu), normalizeUnicode: () => (/* reexport */ normalizeUnicode), renderTextLayer: () => (/* reexport */ renderTextLayer), setLayerDimensions: () => (/* reexport */ setLayerDimensions), shadow: () => (/* reexport */ shadow), updateTextLayer: () => (/* reexport */ updateTextLayer), version: () => (/* reexport */ version) }); ;// CONCATENATED MODULE: ./src/shared/util.js const isNodeJS = false; const IDENTITY_MATRIX = [1, 0, 0, 1, 0, 0]; const FONT_IDENTITY_MATRIX = [0.001, 0, 0, 0.001, 0, 0]; const MAX_IMAGE_SIZE_TO_CACHE = 10e6; const LINE_FACTOR = 1.35; const LINE_DESCENT_FACTOR = 0.35; const BASELINE_FACTOR = LINE_DESCENT_FACTOR / LINE_FACTOR; const RenderingIntentFlag = { ANY: 0x01, DISPLAY: 0x02, PRINT: 0x04, SAVE: 0x08, ANNOTATIONS_FORMS: 0x10, ANNOTATIONS_STORAGE: 0x20, ANNOTATIONS_DISABLE: 0x40, OPLIST: 0x100 }; const AnnotationMode = { DISABLE: 0, ENABLE: 1, ENABLE_FORMS: 2, ENABLE_STORAGE: 3 }; const AnnotationEditorPrefix = "pdfjs_internal_editor_"; const AnnotationEditorType = { DISABLE: -1, NONE: 0, FREETEXT: 3, HIGHLIGHT: 9, STAMP: 13, INK: 15 }; const AnnotationEditorParamsType = { RESIZE: 1, CREATE: 2, FREETEXT_SIZE: 11, FREETEXT_COLOR: 12, FREETEXT_OPACITY: 13, INK_COLOR: 21, INK_THICKNESS: 22, INK_OPACITY: 23, HIGHLIGHT_COLOR: 31, HIGHLIGHT_DEFAULT_COLOR: 32, HIGHLIGHT_THICKNESS: 33, HIGHLIGHT_FREE: 34, HIGHLIGHT_SHOW_ALL: 35 }; const PermissionFlag = { PRINT: 0x04, MODIFY_CONTENTS: 0x08, COPY: 0x10, MODIFY_ANNOTATIONS: 0x20, FILL_INTERACTIVE_FORMS: 0x100, COPY_FOR_ACCESSIBILITY: 0x200, ASSEMBLE: 0x400, PRINT_HIGH_QUALITY: 0x800 }; const TextRenderingMode = { FILL: 0, STROKE: 1, FILL_STROKE: 2, INVISIBLE: 3, FILL_ADD_TO_PATH: 4, STROKE_ADD_TO_PATH: 5, FILL_STROKE_ADD_TO_PATH: 6, ADD_TO_PATH: 7, FILL_STROKE_MASK: 3, ADD_TO_PATH_FLAG: 4 }; const util_ImageKind = { GRAYSCALE_1BPP: 1, RGB_24BPP: 2, RGBA_32BPP: 3 }; const AnnotationType = { TEXT: 1, LINK: 2, FREETEXT: 3, LINE: 4, SQUARE: 5, CIRCLE: 6, POLYGON: 7, POLYLINE: 8, HIGHLIGHT: 9, UNDERLINE: 10, SQUIGGLY: 11, STRIKEOUT: 12, STAMP: 13, CARET: 14, INK: 15, POPUP: 16, FILEATTACHMENT: 17, SOUND: 18, MOVIE: 19, WIDGET: 20, SCREEN: 21, PRINTERMARK: 22, TRAPNET: 23, WATERMARK: 24, THREED: 25, REDACT: 26 }; const AnnotationReplyType = { GROUP: "Group", REPLY: "R" }; const AnnotationFlag = { INVISIBLE: 0x01, HIDDEN: 0x02, PRINT: 0x04, NOZOOM: 0x08, NOROTATE: 0x10, NOVIEW: 0x20, READONLY: 0x40, LOCKED: 0x80, TOGGLENOVIEW: 0x100, LOCKEDCONTENTS: 0x200 }; const AnnotationFieldFlag = { READONLY: 0x0000001, REQUIRED: 0x0000002, NOEXPORT: 0x0000004, MULTILINE: 0x0001000, PASSWORD: 0x0002000, NOTOGGLETOOFF: 0x0004000, RADIO: 0x0008000, PUSHBUTTON: 0x0010000, COMBO: 0x0020000, EDIT: 0x0040000, SORT: 0x0080000, FILESELECT: 0x0100000, MULTISELECT: 0x0200000, DONOTSPELLCHECK: 0x0400000, DONOTSCROLL: 0x0800000, COMB: 0x1000000, RICHTEXT: 0x2000000, RADIOSINUNISON: 0x2000000, COMMITONSELCHANGE: 0x4000000 }; const AnnotationBorderStyleType = { SOLID: 1, DASHED: 2, BEVELED: 3, INSET: 4, UNDERLINE: 5 }; const AnnotationActionEventType = { E: "Mouse Enter", X: "Mouse Exit", D: "Mouse Down", U: "Mouse Up", Fo: "Focus", Bl: "Blur", PO: "PageOpen", PC: "PageClose", PV: "PageVisible", PI: "PageInvisible", K: "Keystroke", F: "Format", V: "Validate", C: "Calculate" }; const DocumentActionEventType = { WC: "WillClose", WS: "WillSave", DS: "DidSave", WP: "WillPrint", DP: "DidPrint" }; const PageActionEventType = { O: "PageOpen", C: "PageClose" }; const VerbosityLevel = { ERRORS: 0, WARNINGS: 1, INFOS: 5 }; const CMapCompressionType = { NONE: 0, BINARY: 1 }; const OPS = { dependency: 1, setLineWidth: 2, setLineCap: 3, setLineJoin: 4, setMiterLimit: 5, setDash: 6, setRenderingIntent: 7, setFlatness: 8, setGState: 9, save: 10, restore: 11, transform: 12, moveTo: 13, lineTo: 14, curveTo: 15, curveTo2: 16, curveTo3: 17, closePath: 18, rectangle: 19, stroke: 20, closeStroke: 21, fill: 22, eoFill: 23, fillStroke: 24, eoFillStroke: 25, closeFillStroke: 26, closeEOFillStroke: 27, endPath: 28, clip: 29, eoClip: 30, beginText: 31, endText: 32, setCharSpacing: 33, setWordSpacing: 34, setHScale: 35, setLeading: 36, setFont: 37, setTextRenderingMode: 38, setTextRise: 39, moveText: 40, setLeadingMoveText: 41, setTextMatrix: 42, nextLine: 43, showText: 44, showSpacedText: 45, nextLineShowText: 46, nextLineSetSpacingShowText: 47, setCharWidth: 48, setCharWidthAndBounds: 49, setStrokeColorSpace: 50, setFillColorSpace: 51, setStrokeColor: 52, setStrokeColorN: 53, setFillColor: 54, setFillColorN: 55, setStrokeGray: 56, setFillGray: 57, setStrokeRGBColor: 58, setFillRGBColor: 59, setStrokeCMYKColor: 60, setFillCMYKColor: 61, shadingFill: 62, beginInlineImage: 63, beginImageData: 64, endInlineImage: 65, paintXObject: 66, markPoint: 67, markPointProps: 68, beginMarkedContent: 69, beginMarkedContentProps: 70, endMarkedContent: 71, beginCompat: 72, endCompat: 73, paintFormXObjectBegin: 74, paintFormXObjectEnd: 75, beginGroup: 76, endGroup: 77, beginAnnotation: 80, endAnnotation: 81, paintImageMaskXObject: 83, paintImageMaskXObjectGroup: 84, paintImageXObject: 85, paintInlineImageXObject: 86, paintInlineImageXObjectGroup: 87, paintImageXObjectRepeat: 88, paintImageMaskXObjectRepeat: 89, paintSolidColorImageMask: 90, constructPath: 91 }; const PasswordResponses = { NEED_PASSWORD: 1, INCORRECT_PASSWORD: 2 }; let verbosity = VerbosityLevel.WARNINGS; function setVerbosityLevel(level) { if (Number.isInteger(level)) { verbosity = level; } } function getVerbosityLevel() { return verbosity; } function info(msg) { if (verbosity >= VerbosityLevel.INFOS) { console.log(`Info: ${msg}`); } } function warn(msg) { if (verbosity >= VerbosityLevel.WARNINGS) { console.log(`Warning: ${msg}`); } } function unreachable(msg) { throw new Error(msg); } function assert(cond, msg) { if (!cond) { unreachable(msg); } } function _isValidProtocol(url) { switch (url?.protocol) { case "http:": case "https:": case "ftp:": case "mailto:": case "tel:": return true; default: return false; } } function createValidAbsoluteUrl(url, baseUrl = null, options = null) { if (!url) { return null; } try { if (options && typeof url === "string") { if (options.addDefaultProtocol && url.startsWith("www.")) { const dots = url.match(/\./g); if (dots?.length >= 2) { url = `http://${url}`; } } if (options.tryConvertEncoding) { try { url = stringToUTF8String(url); } catch {} } } const absoluteUrl = baseUrl ? new URL(url, baseUrl) : new URL(url); if (_isValidProtocol(absoluteUrl)) { return absoluteUrl; } } catch {} return null; } function shadow(obj, prop, value, nonSerializable = false) { Object.defineProperty(obj, prop, { value, enumerable: !nonSerializable, configurable: true, writable: false }); return value; } const BaseException = function BaseExceptionClosure() { function BaseException(message, name) { if (this.constructor === BaseException) { unreachable("Cannot initialize BaseException."); } this.message = message; this.name = name; } BaseException.prototype = new Error(); BaseException.constructor = BaseException; return BaseException; }(); class PasswordException extends BaseException { constructor(msg, code) { super(msg, "PasswordException"); this.code = code; } } class UnknownErrorException extends BaseException { constructor(msg, details) { super(msg, "UnknownErrorException"); this.details = details; } } class InvalidPDFException extends BaseException { constructor(msg) { super(msg, "InvalidPDFException"); } } class MissingPDFException extends BaseException { constructor(msg) { super(msg, "MissingPDFException"); } } class UnexpectedResponseException extends BaseException { constructor(msg, status) { super(msg, "UnexpectedResponseException"); this.status = status; } } class FormatError extends BaseException { constructor(msg) { super(msg, "FormatError"); } } class AbortException extends BaseException { constructor(msg) { super(msg, "AbortException"); } } function bytesToString(bytes) { if (typeof bytes !== "object" || bytes?.length === undefined) { unreachable("Invalid argument for bytesToString"); } const length = bytes.length; const MAX_ARGUMENT_COUNT = 8192; if (length < MAX_ARGUMENT_COUNT) { return String.fromCharCode.apply(null, bytes); } const strBuf = []; for (let i = 0; i < length; i += MAX_ARGUMENT_COUNT) { const chunkEnd = Math.min(i + MAX_ARGUMENT_COUNT, length); const chunk = bytes.subarray(i, chunkEnd); strBuf.push(String.fromCharCode.apply(null, chunk)); } return strBuf.join(""); } function stringToBytes(str) { if (typeof str !== "string") { unreachable("Invalid argument for stringToBytes"); } const length = str.length; const bytes = new Uint8Array(length); for (let i = 0; i < length; ++i) { bytes[i] = str.charCodeAt(i) & 0xff; } return bytes; } function string32(value) { return String.fromCharCode(value >> 24 & 0xff, value >> 16 & 0xff, value >> 8 & 0xff, value & 0xff); } function objectSize(obj) { return Object.keys(obj).length; } function objectFromMap(map) { const obj = Object.create(null); for (const [key, value] of map) { obj[key] = value; } return obj; } function isLittleEndian() { const buffer8 = new Uint8Array(4); buffer8[0] = 1; const view32 = new Uint32Array(buffer8.buffer, 0, 1); return view32[0] === 1; } function isEvalSupported() { try { new Function(""); return true; } catch { return false; } } class util_FeatureTest { static get isLittleEndian() { return shadow(this, "isLittleEndian", isLittleEndian()); } static get isEvalSupported() { return shadow(this, "isEvalSupported", isEvalSupported()); } static get isOffscreenCanvasSupported() { return shadow(this, "isOffscreenCanvasSupported", typeof OffscreenCanvas !== "undefined"); } static get platform() { return shadow(this, "platform", { isMac: navigator.platform.includes("Mac") }); } static get isCSSRoundSupported() { return shadow(this, "isCSSRoundSupported", globalThis.CSS?.supports?.("width: round(1.5px, 1px)")); } } const hexNumbers = Array.from(Array(256).keys(), n => n.toString(16).padStart(2, "0")); class Util { static makeHexColor(r, g, b) { return `#${hexNumbers[r]}${hexNumbers[g]}${hexNumbers[b]}`; } static scaleMinMax(transform, minMax) { let temp; if (transform[0]) { if (transform[0] < 0) { temp = minMax[0]; minMax[0] = minMax[2]; minMax[2] = temp; } minMax[0] *= transform[0]; minMax[2] *= transform[0]; if (transform[3] < 0) { temp = minMax[1]; minMax[1] = minMax[3]; minMax[3] = temp; } minMax[1] *= transform[3]; minMax[3] *= transform[3]; } else { temp = minMax[0]; minMax[0] = minMax[1]; minMax[1] = temp; temp = minMax[2]; minMax[2] = minMax[3]; minMax[3] = temp; if (transform[1] < 0) { temp = minMax[1]; minMax[1] = minMax[3]; minMax[3] = temp; } minMax[1] *= transform[1]; minMax[3] *= transform[1]; if (transform[2] < 0) { temp = minMax[0]; minMax[0] = minMax[2]; minMax[2] = temp; } minMax[0] *= transform[2]; minMax[2] *= transform[2]; } minMax[0] += transform[4]; minMax[1] += transform[5]; minMax[2] += transform[4]; minMax[3] += transform[5]; } static transform(m1, m2) { 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]]; } static applyTransform(p, m) { const xt = p[0] * m[0] + p[1] * m[2] + m[4]; const yt = p[0] * m[1] + p[1] * m[3] + m[5]; return [xt, yt]; } static applyInverseTransform(p, m) { const d = m[0] * m[3] - m[1] * m[2]; const xt = (p[0] * m[3] - p[1] * m[2] + m[2] * m[5] - m[4] * m[3]) / d; const yt = (-p[0] * m[1] + p[1] * m[0] + m[4] * m[1] - m[5] * m[0]) / d; return [xt, yt]; } static getAxialAlignedBoundingBox(r, m) { const p1 = this.applyTransform(r, m); const p2 = this.applyTransform(r.slice(2, 4), m); const p3 = this.applyTransform([r[0], r[3]], m); const p4 = this.applyTransform([r[2], r[1]], m); 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])]; } static inverseTransform(m) { const d = m[0] * m[3] - m[1] * m[2]; 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]; } static singularValueDecompose2dScale(m) { const transpose = [m[0], m[2], m[1], m[3]]; const a = m[0] * transpose[0] + m[1] * transpose[2]; const b = m[0] * transpose[1] + m[1] * transpose[3]; const c = m[2] * transpose[0] + m[3] * transpose[2]; const d = m[2] * transpose[1] + m[3] * transpose[3]; const first = (a + d) / 2; const second = Math.sqrt((a + d) ** 2 - 4 * (a * d - c * b)) / 2; const sx = first + second || 1; const sy = first - second || 1; return [Math.sqrt(sx), Math.sqrt(sy)]; } static normalizeRect(rect) { const r = rect.slice(0); if (rect[0] > rect[2]) { r[0] = rect[2]; r[2] = rect[0]; } if (rect[1] > rect[3]) { r[1] = rect[3]; r[3] = rect[1]; } return r; } static intersect(rect1, rect2) { const xLow = Math.max(Math.min(rect1[0], rect1[2]), Math.min(rect2[0], rect2[2])); const xHigh = Math.min(Math.max(rect1[0], rect1[2]), Math.max(rect2[0], rect2[2])); if (xLow > xHigh) { return null; } const yLow = Math.max(Math.min(rect1[1], rect1[3]), Math.min(rect2[1], rect2[3])); const yHigh = Math.min(Math.max(rect1[1], rect1[3]), Math.max(rect2[1], rect2[3])); if (yLow > yHigh) { return null; } return [xLow, yLow, xHigh, yHigh]; } static #getExtremumOnCurve(x0, x1, x2, x3, y0, y1, y2, y3, t, minMax) { if (t <= 0 || t >= 1) { return; } const mt = 1 - t; const tt = t * t; const ttt = tt * t; const x = mt * (mt * (mt * x0 + 3 * t * x1) + 3 * tt * x2) + ttt * x3; const y = mt * (mt * (mt * y0 + 3 * t * y1) + 3 * tt * y2) + ttt * y3; minMax[0] = Math.min(minMax[0], x); minMax[1] = Math.min(minMax[1], y); minMax[2] = Math.max(minMax[2], x); minMax[3] = Math.max(minMax[3], y); } static #getExtremum(x0, x1, x2, x3, y0, y1, y2, y3, a, b, c, minMax) { if (Math.abs(a) < 1e-12) { if (Math.abs(b) >= 1e-12) { this.#getExtremumOnCurve(x0, x1, x2, x3, y0, y1, y2, y3, -c / b, minMax); } return; } const delta = b ** 2 - 4 * c * a; if (delta < 0) { return; } const sqrtDelta = Math.sqrt(delta); const a2 = 2 * a; this.#getExtremumOnCurve(x0, x1, x2, x3, y0, y1, y2, y3, (-b + sqrtDelta) / a2, minMax); this.#getExtremumOnCurve(x0, x1, x2, x3, y0, y1, y2, y3, (-b - sqrtDelta) / a2, minMax); } static bezierBoundingBox(x0, y0, x1, y1, x2, y2, x3, y3, minMax) { if (minMax) { minMax[0] = Math.min(minMax[0], x0, x3); minMax[1] = Math.min(minMax[1], y0, y3); minMax[2] = Math.max(minMax[2], x0, x3); minMax[3] = Math.max(minMax[3], y0, y3); } else { minMax = [Math.min(x0, x3), Math.min(y0, y3), Math.max(x0, x3), Math.max(y0, y3)]; } 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); 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); return minMax; } } 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])); function stringToPDFString(str) { if (str[0] >= "\xEF") { let encoding; if (str[0] === "\xFE" && str[1] === "\xFF") { encoding = "utf-16be"; if (str.length % 2 === 1) { str = str.slice(0, -1); } } else if (str[0] === "\xFF" && str[1] === "\xFE") { encoding = "utf-16le"; if (str.length % 2 === 1) { str = str.slice(0, -1); } } else if (str[0] === "\xEF" && str[1] === "\xBB" && str[2] === "\xBF") { encoding = "utf-8"; } if (encoding) { try { const decoder = new TextDecoder(encoding, { fatal: true }); const buffer = stringToBytes(str); const decoded = decoder.decode(buffer); if (!decoded.includes("\x1b")) { return decoded; } return decoded.replaceAll(/\x1b[^\x1b]*(?:\x1b|$)/g, ""); } catch (ex) { warn(`stringToPDFString: "${ex}".`); } } } const strBuf = []; for (let i = 0, ii = str.length; i < ii; i++) { const charCode = str.charCodeAt(i); if (charCode === 0x1b) { while (++i < ii && str.charCodeAt(i) !== 0x1b) {} continue; } const code = PDFStringTranslateTable[charCode]; strBuf.push(code ? String.fromCharCode(code) : str.charAt(i)); } return strBuf.join(""); } function stringToUTF8String(str) { return decodeURIComponent(escape(str)); } function utf8StringToString(str) { return unescape(encodeURIComponent(str)); } function isArrayEqual(arr1, arr2) { if (arr1.length !== arr2.length) { return false; } for (let i = 0, ii = arr1.length; i < ii; i++) { if (arr1[i] !== arr2[i]) { return false; } } return true; } function getModificationDate(date = new Date()) { 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")]; return buffer.join(""); } let NormalizeRegex = null; let NormalizationMap = null; function normalizeUnicode(str) { if (!NormalizeRegex) { 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; NormalizationMap = new Map([["ſt", "ſt"]]); } return str.replaceAll(NormalizeRegex, (_, p1, p2) => p1 ? p1.normalize("NFKC") : NormalizationMap.get(p2)); } function getUuid() { return crypto.randomUUID(); } const AnnotationPrefix = "pdfjs_internal_id_"; const FontRenderOps = { BEZIER_CURVE_TO: 0, MOVE_TO: 1, LINE_TO: 2, QUADRATIC_CURVE_TO: 3, RESTORE: 4, SAVE: 5, SCALE: 6, TRANSFORM: 7, TRANSLATE: 8 }; ;// CONCATENATED MODULE: ./src/display/base_factory.js class BaseFilterFactory { constructor() { if (this.constructor === BaseFilterFactory) { unreachable("Cannot initialize BaseFilterFactory."); } } addFilter(maps) { return "none"; } addHCMFilter(fgColor, bgColor) { return "none"; } addAlphaFilter(map) { return "none"; } addLuminosityFilter(map) { return "none"; } addHighlightHCMFilter(filterName, fgColor, bgColor, newFgColor, newBgColor) { return "none"; } destroy(keepHCM = false) {} } class BaseCanvasFactory { constructor() { if (this.constructor === BaseCanvasFactory) { unreachable("Cannot initialize BaseCanvasFactory."); } } create(width, height) { if (width <= 0 || height <= 0) { throw new Error("Invalid canvas size"); } const canvas = this._createCanvas(width, height); return { canvas, context: canvas.getContext("2d") }; } reset(canvasAndContext, width, height) { if (!canvasAndContext.canvas) { throw new Error("Canvas is not specified"); } if (width <= 0 || height <= 0) { throw new Error("Invalid canvas size"); } canvasAndContext.canvas.width = width; canvasAndContext.canvas.height = height; } destroy(canvasAndContext) { if (!canvasAndContext.canvas) { throw new Error("Canvas is not specified"); } canvasAndContext.canvas.width = 0; canvasAndContext.canvas.height = 0; canvasAndContext.canvas = null; canvasAndContext.context = null; } _createCanvas(width, height) { unreachable("Abstract method `_createCanvas` called."); } } class BaseCMapReaderFactory { constructor({ baseUrl = null, isCompressed = true }) { if (this.constructor === BaseCMapReaderFactory) { unreachable("Cannot initialize BaseCMapReaderFactory."); } this.baseUrl = baseUrl; this.isCompressed = isCompressed; } async fetch({ name }) { if (!this.baseUrl) { throw new Error('The CMap "baseUrl" parameter must be specified, ensure that ' + 'the "cMapUrl" and "cMapPacked" API parameters are provided.'); } if (!name) { throw new Error("CMap name must be specified."); } const url = this.baseUrl + name + (this.isCompressed ? ".bcmap" : ""); const compressionType = this.isCompressed ? CMapCompressionType.BINARY : CMapCompressionType.NONE; return this._fetchData(url, compressionType).catch(reason => { throw new Error(`Unable to load ${this.isCompressed ? "binary " : ""}CMap at: ${url}`); }); } _fetchData(url, compressionType) { unreachable("Abstract method `_fetchData` called."); } } class BaseStandardFontDataFactory { constructor({ baseUrl = null }) { if (this.constructor === BaseStandardFontDataFactory) { unreachable("Cannot initialize BaseStandardFontDataFactory."); } this.baseUrl = baseUrl; } async fetch({ filename }) { if (!this.baseUrl) { throw new Error('The standard font "baseUrl" parameter must be specified, ensure that ' + 'the "standardFontDataUrl" API parameter is provided.'); } if (!filename) { throw new Error("Font filename must be specified."); } const url = `${this.baseUrl}${filename}`; return this._fetchData(url).catch(reason => { throw new Error(`Unable to load font data at: ${url}`); }); } _fetchData(url) { unreachable("Abstract method `_fetchData` called."); } } class BaseSVGFactory { constructor() { if (this.constructor === BaseSVGFactory) { unreachable("Cannot initialize BaseSVGFactory."); } } create(width, height, skipDimensions = false) { if (width <= 0 || height <= 0) { throw new Error("Invalid SVG dimensions"); } const svg = this._createSVG("svg:svg"); svg.setAttribute("version", "1.1"); if (!skipDimensions) { svg.setAttribute("width", `${width}px`); svg.setAttribute("height", `${height}px`); } svg.setAttribute("preserveAspectRatio", "none"); svg.setAttribute("viewBox", `0 0 ${width} ${height}`); return svg; } createElement(type) { if (typeof type !== "string") { throw new Error("Invalid SVG element type"); } return this._createSVG(type); } _createSVG(type) { unreachable("Abstract method `_createSVG` called."); } } ;// CONCATENATED MODULE: ./src/display/display_utils.js const SVG_NS = "http://www.w3.org/2000/svg"; class PixelsPerInch { static CSS = 96.0; static PDF = 72.0; static PDF_TO_CSS_UNITS = this.CSS / this.PDF; } class DOMFilterFactory extends BaseFilterFactory { #_cache; #_defs; #docId; #document; #_hcmCache; #id = 0; constructor({ docId, ownerDocument = globalThis.document } = {}) { super(); this.#docId = docId; this.#document = ownerDocument; } get #cache() { return this.#_cache ||= new Map(); } get #hcmCache() { return this.#_hcmCache ||= new Map(); } get #defs() { if (!this.#_defs) { const div = this.#document.createElement("div"); const { style } = div; style.visibility = "hidden"; style.contain = "strict"; style.width = style.height = 0; style.position = "absolute"; style.top = style.left = 0; style.zIndex = -1; const svg = this.#document.createElementNS(SVG_NS, "svg"); svg.setAttribute("width", 0); svg.setAttribute("height", 0); this.#_defs = this.#document.createElementNS(SVG_NS, "defs"); div.append(svg); svg.append(this.#_defs); this.#document.body.append(div); } return this.#_defs; } #createTables(maps) { if (maps.length === 1) { const mapR = maps[0]; const buffer = new Array(256); for (let i = 0; i < 256; i++) { buffer[i] = mapR[i] / 255; } const table = buffer.join(","); return [table, table, table]; } const [mapR, mapG, mapB] = maps; const bufferR = new Array(256); const bufferG = new Array(256); const bufferB = new Array(256); for (let i = 0; i < 256; i++) { bufferR[i] = mapR[i] / 255; bufferG[i] = mapG[i] / 255; bufferB[i] = mapB[i] / 255; } return [bufferR.join(","), bufferG.join(","), bufferB.join(",")]; } addFilter(maps) { if (!maps) { return "none"; } let value = this.#cache.get(maps); if (value) { return value; } const [tableR, tableG, tableB] = this.#createTables(maps); const key = maps.length === 1 ? tableR : `${tableR}${tableG}${tableB}`; value = this.#cache.get(key); if (value) { this.#cache.set(maps, value); return value; } const id = `g_${this.#docId}_transfer_map_${this.#id++}`; const url = `url(#${id})`; this.#cache.set(maps, url); this.#cache.set(key, url); const filter = this.#createFilter(id); this.#addTransferMapConversion(tableR, tableG, tableB, filter); return url; } addHCMFilter(fgColor, bgColor) { const key = `${fgColor}-${bgColor}`; const filterName = "base"; let info = this.#hcmCache.get(filterName); if (info?.key === key) { return info.url; } if (info) { info.filter?.remove(); info.key = key; info.url = "none"; info.filter = null; } else { info = { key, url: "none", filter: null }; this.#hcmCache.set(filterName, info); } if (!fgColor || !bgColor) { return info.url; } const fgRGB = this.#getRGB(fgColor); fgColor = Util.makeHexColor(...fgRGB); const bgRGB = this.#getRGB(bgColor); bgColor = Util.makeHexColor(...bgRGB); this.#defs.style.color = ""; if (fgColor === "#000000" && bgColor === "#ffffff" || fgColor === bgColor) { return info.url; } const map = new Array(256); for (let i = 0; i <= 255; i++) { const x = i / 255; map[i] = x <= 0.03928 ? x / 12.92 : ((x + 0.055) / 1.055) ** 2.4; } const table = map.join(","); const id = `g_${this.#docId}_hcm_filter`; const filter = info.filter = this.#createFilter(id); this.#addTransferMapConversion(table, table, table, filter); this.#addGrayConversion(filter); const getSteps = (c, n) => { const start = fgRGB[c] / 255; const end = bgRGB[c] / 255; const arr = new Array(n + 1); for (let i = 0; i <= n; i++) { arr[i] = start + i / n * (end - start); } return arr.join(","); }; this.#addTransferMapConversion(getSteps(0, 5), getSteps(1, 5), getSteps(2, 5), filter); info.url = `url(#${id})`; return info.url; } addAlphaFilter(map) { let value = this.#cache.get(map); if (value) { return value; } const [tableA] = this.#createTables([map]); const key = `alpha_${tableA}`; value = this.#cache.get(key); if (value) { this.#cache.set(map, value); return value; } const id = `g_${this.#docId}_alpha_map_${this.#id++}`; const url = `url(#${id})`; this.#cache.set(map, url); this.#cache.set(key, url); const filter = this.#createFilter(id); this.#addTransferMapAlphaConversion(tableA, filter); return url; } addLuminosityFilter(map) { let value = this.#cache.get(map || "luminosity"); if (value) { return value; } let tableA, key; if (map) { [tableA] = this.#createTables([map]); key = `luminosity_${tableA}`; } else { key = "luminosity"; } value = this.#cache.get(key); if (value) { this.#cache.set(map, value); return value; } const id = `g_${this.#docId}_luminosity_map_${this.#id++}`; const url = `url(#${id})`; this.#cache.set(map, url); this.#cache.set(key, url); const filter = this.#createFilter(id); this.#addLuminosityConversion(filter); if (map) { this.#addTransferMapAlphaConversion(tableA, filter); } return url; } addHighlightHCMFilter(filterName, fgColor, bgColor, newFgColor, newBgColor) { const key = `${fgColor}-${bgColor}-${newFgColor}-${newBgColor}`; let info = this.#hcmCache.get(filterName); if (info?.key === key) { return info.url; } if (info) { info.filter?.remove(); info.key = key; info.url = "none"; info.filter = null; } else { info = { key, url: "none", filter: null }; this.#hcmCache.set(filterName, info); } if (!fgColor || !bgColor) { return info.url; } const [fgRGB, bgRGB] = [fgColor, bgColor].map(this.#getRGB.bind(this)); let fgGray = Math.round(0.2126 * fgRGB[0] + 0.7152 * fgRGB[1] + 0.0722 * fgRGB[2]); let bgGray = Math.round(0.2126 * bgRGB[0] + 0.7152 * bgRGB[1] + 0.0722 * bgRGB[2]); let [newFgRGB, newBgRGB] = [newFgColor, newBgColor].map(this.#getRGB.bind(this)); if (bgGray < fgGray) { [fgGray, bgGray, newFgRGB, newBgRGB] = [bgGray, fgGray, newBgRGB, newFgRGB]; } this.#defs.style.color = ""; const getSteps = (fg, bg, n) => { const arr = new Array(256); const step = (bgGray - fgGray) / n; const newStart = fg / 255; const newStep = (bg - fg) / (255 * n); let prev = 0; for (let i = 0; i <= n; i++) { const k = Math.round(fgGray + i * step); const value = newStart + i * newStep; for (let j = prev; j <= k; j++) { arr[j] = value; } prev = k + 1; } for (let i = prev; i < 256; i++) { arr[i] = arr[prev - 1]; } return arr.join(","); }; const id = `g_${this.#docId}_hcm_${filterName}_filter`; const filter = info.filter = this.#createFilter(id); this.#addGrayConversion(filter); this.#addTransferMapConversion(getSteps(newFgRGB[0], newBgRGB[0], 5), getSteps(newFgRGB[1], newBgRGB[1], 5), getSteps(newFgRGB[2], newBgRGB[2], 5), filter); info.url = `url(#${id})`; return info.url; } destroy(keepHCM = false) { if (keepHCM && this.#hcmCache.size !== 0) { return; } if (this.#_defs) { this.#_defs.parentNode.parentNode.remove(); this.#_defs = null; } if (this.#_cache) { this.#_cache.clear(); this.#_cache = null; } this.#id = 0; } #addLuminosityConversion(filter) { const feColorMatrix = this.#document.createElementNS(SVG_NS, "feColorMatrix"); feColorMatrix.setAttribute("type", "matrix"); 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"); filter.append(feColorMatrix); } #addGrayConversion(filter) { const feColorMatrix = this.#document.createElementNS(SVG_NS, "feColorMatrix"); feColorMatrix.setAttribute("type", "matrix"); 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"); filter.append(feColorMatrix); } #createFilter(id) { const filter = this.#document.createElementNS(SVG_NS, "filter"); filter.setAttribute("color-interpolation-filters", "sRGB"); filter.setAttribute("id", id); this.#defs.append(filter); return filter; } #appendFeFunc(feComponentTransfer, func, table) { const feFunc = this.#document.createElementNS(SVG_NS, func); feFunc.setAttribute("type", "discrete"); feFunc.setAttribute("tableValues", table); feComponentTransfer.append(feFunc); } #addTransferMapConversion(rTable, gTable, bTable, filter) { const feComponentTransfer = this.#document.createElementNS(SVG_NS, "feComponentTransfer"); filter.append(feComponentTransfer); this.#appendFeFunc(feComponentTransfer, "feFuncR", rTable); this.#appendFeFunc(feComponentTransfer, "feFuncG", gTable); this.#appendFeFunc(feComponentTransfer, "feFuncB", bTable); } #addTransferMapAlphaConversion(aTable, filter) { const feComponentTransfer = this.#document.createElementNS(SVG_NS, "feComponentTransfer"); filter.append(feComponentTransfer); this.#appendFeFunc(feComponentTransfer, "feFuncA", aTable); } #getRGB(color) { this.#defs.style.color = color; return getRGB(getComputedStyle(this.#defs).getPropertyValue("color")); } } class DOMCanvasFactory extends BaseCanvasFactory { constructor({ ownerDocument = globalThis.document } = {}) { super(); this._document = ownerDocument; } _createCanvas(width, height) { const canvas = this._document.createElement("canvas"); canvas.width = width; canvas.height = height; return canvas; } } async function fetchData(url, type = "text") { const response = await fetch(url); if (!response.ok) { throw new Error(response.statusText); } switch (type) { case "arraybuffer": return response.arrayBuffer(); case "blob": return response.blob(); case "json": return response.json(); } return response.text(); } class DOMCMapReaderFactory extends BaseCMapReaderFactory { _fetchData(url, compressionType) { return fetchData(url, this.isCompressed ? "arraybuffer" : "text").then(data => ({ cMapData: data instanceof ArrayBuffer ? new Uint8Array(data) : stringToBytes(data), compressionType })); } } class DOMStandardFontDataFactory extends BaseStandardFontDataFactory { _fetchData(url) { return fetchData(url, "arraybuffer").then(data => new Uint8Array(data)); } } class DOMSVGFactory extends BaseSVGFactory { _createSVG(type) { return document.createElementNS(SVG_NS, type); } } class PageViewport { constructor({ viewBox, scale, rotation, offsetX = 0, offsetY = 0, dontFlip = false }) { this.viewBox = viewBox; this.scale = scale; this.rotation = rotation; this.offsetX = offsetX; this.offsetY = offsetY; const centerX = (viewBox[2] + viewBox[0]) / 2; const centerY = (viewBox[3] + viewBox[1]) / 2; let rotateA, rotateB, rotateC, rotateD; rotation %= 360; if (rotation < 0) { rotation += 360; } switch (rotation) { case 180: rotateA = -1; rotateB = 0; rotateC = 0; rotateD = 1; break; case 90: rotateA = 0; rotateB = 1; rotateC = 1; rotateD = 0; break; case 270: rotateA = 0; rotateB = -1; rotateC = -1; rotateD = 0; break; case 0: rotateA = 1; rotateB = 0; rotateC = 0; rotateD = -1; break; default: throw new Error("PageViewport: Invalid rotation, must be a multiple of 90 degrees."); } if (dontFlip) { rotateC = -rotateC; rotateD = -rotateD; } let offsetCanvasX, offsetCanvasY; let width, height; if (rotateA === 0) { offsetCanvasX = Math.abs(centerY - viewBox[1]) * scale + offsetX; offsetCanvasY = Math.abs(centerX - viewBox[0]) * scale + offsetY; width = (viewBox[3] - viewBox[1]) * scale; height = (viewBox[2] - viewBox[0]) * scale; } else { offsetCanvasX = Math.abs(centerX - viewBox[0]) * scale + offsetX; offsetCanvasY = Math.abs(centerY - viewBox[1]) * scale + offsetY; width = (viewBox[2] - viewBox[0]) * scale; height = (viewBox[3] - viewBox[1]) * scale; } this.transform = [rotateA * scale, rotateB * scale, rotateC * scale, rotateD * scale, offsetCanvasX - rotateA * scale * centerX - rotateC * scale * centerY, offsetCanvasY - rotateB * scale * centerX - rotateD * scale * centerY]; this.width = width; this.height = height; } get rawDims() { const { viewBox } = this; return shadow(this, "rawDims", { pageWidth: viewBox[2] - viewBox[0], pageHeight: viewBox[3] - viewBox[1], pageX: viewBox[0], pageY: viewBox[1] }); } clone({ scale = this.scale, rotation = this.rotation, offsetX = this.offsetX, offsetY = this.offsetY, dontFlip = false } = {}) { return new PageViewport({ viewBox: this.viewBox.slice(), scale, rotation, offsetX, offsetY, dontFlip }); } convertToViewportPoint(x, y) { return Util.applyTransform([x, y], this.transform); } convertToViewportRectangle(rect) { const topLeft = Util.applyTransform([rect[0], rect[1]], this.transform); const bottomRight = Util.applyTransform([rect[2], rect[3]], this.transform); return [topLeft[0], topLeft[1], bottomRight[0], bottomRight[1]]; } convertToPdfPoint(x, y) { return Util.applyInverseTransform([x, y], this.transform); } } class RenderingCancelledException extends BaseException { constructor(msg, extraDelay = 0) { super(msg, "RenderingCancelledException"); this.extraDelay = extraDelay; } } function isDataScheme(url) { const ii = url.length; let i = 0; while (i < ii && url[i].trim() === "") { i++; } return url.substring(i, i + 5).toLowerCase() === "data:"; } function isPdfFile(filename) { return typeof filename === "string" && /\.pdf$/i.test(filename); } function getFilenameFromUrl(url, onlyStripPath = false) { if (!onlyStripPath) { [url] = url.split(/[#?]/, 1); } return url.substring(url.lastIndexOf("/") + 1); } function getPdfFilenameFromUrl(url, defaultFilename = "document.pdf") { if (typeof url !== "string") { return defaultFilename; } if (isDataScheme(url)) { warn('getPdfFilenameFromUrl: ignore "data:"-URL for performance reasons.'); return defaultFilename; } const reURI = /^(?:(?:[^:]+:)?\/\/[^/]+)?([^?#]*)(\?[^#]*)?(#.*)?$/; const reFilename = /[^/?#=]+\.pdf\b(?!.*\.pdf\b)/i; const splitURI = reURI.exec(url); let suggestedFilename = reFilename.exec(splitURI[1]) || reFilename.exec(splitURI[2]) || reFilename.exec(splitURI[3]); if (suggestedFilename) { suggestedFilename = suggestedFilename[0]; if (suggestedFilename.includes("%")) { try { suggestedFilename = reFilename.exec(decodeURIComponent(suggestedFilename))[0]; } catch {} } } return suggestedFilename || defaultFilename; } class StatTimer { started = Object.create(null); times = []; time(name) { if (name in this.started) { warn(`Timer is already running for ${name}`); } this.started[name] = Date.now(); } timeEnd(name) { if (!(name in this.started)) { warn(`Timer has not been started for ${name}`); } this.times.push({ name, start: this.started[name], end: Date.now() }); delete this.started[name]; } toString() { const outBuf = []; let longest = 0; for (const { name } of this.times) { longest = Math.max(name.length, longest); } for (const { name, start, end } of this.times) { outBuf.push(`${name.padEnd(longest)} ${end - start}ms\n`); } return outBuf.join(""); } } function isValidFetchUrl(url, baseUrl) { throw new Error("Not implemented: isValidFetchUrl"); } function noContextMenu(e) { e.preventDefault(); } function deprecated(details) { console.log("Deprecated API usage: " + details); } let pdfDateStringRegex; class PDFDateString { static toDateObject(input) { if (!input || typeof input !== "string") { return null; } pdfDateStringRegex ||= new RegExp("^D:" + "(\\d{4})" + "(\\d{2})?" + "(\\d{2})?" + "(\\d{2})?" + "(\\d{2})?" + "(\\d{2})?" + "([Z|+|-])?" + "(\\d{2})?" + "'?" + "(\\d{2})?" + "'?"); const matches = pdfDateStringRegex.exec(input); if (!matches) { return null; } const year = parseInt(matches[1], 10); let month = parseInt(matches[2], 10); month = month >= 1 && month <= 12 ? month - 1 : 0; let day = parseInt(matches[3], 10); day = day >= 1 && day <= 31 ? day : 1; let hour = parseInt(matches[4], 10); hour = hour >= 0 && hour <= 23 ? hour : 0; let minute = parseInt(matches[5], 10); minute = minute >= 0 && minute <= 59 ? minute : 0; let second = parseInt(matches[6], 10); second = second >= 0 && second <= 59 ? second : 0; const universalTimeRelation = matches[7] || "Z"; let offsetHour = parseInt(matches[8], 10); offsetHour = offsetHour >= 0 && offsetHour <= 23 ? offsetHour : 0; let offsetMinute = parseInt(matches[9], 10) || 0; offsetMinute = offsetMinute >= 0 && offsetMinute <= 59 ? offsetMinute : 0; if (universalTimeRelation === "-") { hour += offsetHour; minute += offsetMinute; } else if (universalTimeRelation === "+") { hour -= offsetHour; minute -= offsetMinute; } return new Date(Date.UTC(year, month, day, hour, minute, second)); } } function getXfaPageViewport(xfaPage, { scale = 1, rotation = 0 }) { const { width, height } = xfaPage.attributes.style; const viewBox = [0, 0, parseInt(width), parseInt(height)]; return new PageViewport({ viewBox, scale, rotation }); } function getRGB(color) { if (color.startsWith("#")) { const colorRGB = parseInt(color.slice(1), 16); return [(colorRGB & 0xff0000) >> 16, (colorRGB & 0x00ff00) >> 8, colorRGB & 0x0000ff]; } if (color.startsWith("rgb(")) { return color.slice(4, -1).split(",").map(x => parseInt(x)); } if (color.startsWith("rgba(")) { return color.slice(5, -1).split(",").map(x => parseInt(x)).slice(0, 3); } warn(`Not a valid color format: "${color}"`); return [0, 0, 0]; } function getColorValues(colors) { const span = document.createElement("span"); span.style.visibility = "hidden"; document.body.append(span); for (const name of colors.keys()) { span.style.color = name; const computedColor = window.getComputedStyle(span).color; colors.set(name, getRGB(computedColor)); } span.remove(); } function getCurrentTransform(ctx) { const { a, b, c, d, e, f } = ctx.getTransform(); return [a, b, c, d, e, f]; } function getCurrentTransformInverse(ctx) { const { a, b, c, d, e, f } = ctx.getTransform().invertSelf(); return [a, b, c, d, e, f]; } function setLayerDimensions(div, viewport, mustFlip = false, mustRotate = true) { if (viewport instanceof PageViewport) { const { pageWidth, pageHeight } = viewport.rawDims; const { style } = div; const useRound = util_FeatureTest.isCSSRoundSupported; const w = `var(--scale-factor) * ${pageWidth}px`, h = `var(--scale-factor) * ${pageHeight}px`; const widthStr = useRound ? `round(${w}, 1px)` : `calc(${w})`, heightStr = useRound ? `round(${h}, 1px)` : `calc(${h})`; if (!mustFlip || viewport.rotation % 180 === 0) { style.width = widthStr; style.height = heightStr; } else { style.width = heightStr; style.height = widthStr; } } if (mustRotate) { div.setAttribute("data-main-rotation", viewport.rotation); } } ;// CONCATENATED MODULE: ./src/display/editor/toolbar.js class EditorToolbar { #toolbar = null; #colorPicker = null; #editor; #buttons = null; constructor(editor) { this.#editor = editor; } render() { const editToolbar = this.#toolbar = document.createElement("div"); editToolbar.className = "editToolbar"; editToolbar.setAttribute("role", "toolbar"); editToolbar.addEventListener("contextmenu", noContextMenu); editToolbar.addEventListener("pointerdown", EditorToolbar.#pointerDown); const buttons = this.#buttons = document.createElement("div"); buttons.className = "buttons"; editToolbar.append(buttons); const position = this.#editor.toolbarPosition; if (position) { const { style } = editToolbar; const x = this.#editor._uiManager.direction === "ltr" ? 1 - position[0] : position[0]; style.insetInlineEnd = `${100 * x}%`; style.top = `calc(${100 * position[1]}% + var(--editor-toolbar-vert-offset))`; } this.#addDeleteButton(); return editToolbar; } static #pointerDown(e) { e.stopPropagation(); } #focusIn(e) { this.#editor._focusEventsAllowed = false; e.preventDefault(); e.stopPropagation(); } #focusOut(e) { this.#editor._focusEventsAllowed = true; e.preventDefault(); e.stopPropagation(); } #addListenersToElement(element) { element.addEventListener("focusin", this.#focusIn.bind(this), { capture: true }); element.addEventListener("focusout", this.#focusOut.bind(this), { capture: true }); element.addEventListener("contextmenu", noContextMenu); } hide() { this.#toolbar.classList.add("hidden"); this.#colorPicker?.hideDropdown(); } show() { this.#toolbar.classList.remove("hidden"); } #addDeleteButton() { const button = document.createElement("button"); button.className = "delete"; button.tabIndex = 0; button.setAttribute("data-l10n-id", `pdfjs-editor-remove-${this.#editor.editorType}-button`); this.#addListenersToElement(button); button.addEventListener("click", e => { this.#editor._uiManager.delete(); }); this.#buttons.append(button); } get #divider() { const divider = document.createElement("div"); divider.className = "divider"; return divider; } addAltTextButton(button) { this.#addListenersToElement(button); this.#buttons.prepend(button, this.#divider); } addColorPicker(colorPicker) { this.#colorPicker = colorPicker; const button = colorPicker.renderButton(); this.#addListenersToElement(button); this.#buttons.prepend(button, this.#divider); } remove() { this.#toolbar.remove(); this.#colorPicker?.destroy(); this.#colorPicker = null; } } class HighlightToolbar { #buttons = null; #toolbar = null; #uiManager; constructor(uiManager) { this.#uiManager = uiManager; } #render() { const editToolbar = this.#toolbar = document.createElement("div"); editToolbar.className = "editToolbar"; editToolbar.setAttribute("role", "toolbar"); editToolbar.addEventListener("contextmenu", noContextMenu); const buttons = this.#buttons = document.createElement("div"); buttons.className = "buttons"; editToolbar.append(buttons); this.#addHighlightButton(); return editToolbar; } #getLastPoint(boxes, isLTR) { let lastY = 0; let lastX = 0; for (const box of boxes) { const y = box.y + box.height; if (y < lastY) { continue; } const x = box.x + (isLTR ? box.width : 0); if (y > lastY) { lastX = x; lastY = y; continue; } if (isLTR) { if (x > lastX) { lastX = x; } } else if (x < lastX) { lastX = x; } } return [isLTR ? 1 - lastX : lastX, lastY]; } show(parent, boxes, isLTR) { const [x, y] = this.#getLastPoint(boxes, isLTR); const { style } = this.#toolbar ||= this.#render(); parent.append(this.#toolbar); style.insetInlineEnd = `${100 * x}%`; style.top = `calc(${100 * y}% + var(--editor-toolbar-vert-offset))`; } hide() { this.#toolbar.remove(); } #addHighlightButton() { const button = document.createElement("button"); button.className = "highlightButton"; button.tabIndex = 0; button.setAttribute("data-l10n-id", `pdfjs-highlight-floating-button1`); const span = document.createElement("span"); button.append(span); span.className = "visuallyHidden"; span.setAttribute("data-l10n-id", "pdfjs-highlight-floating-button-label"); button.addEventListener("contextmenu", noContextMenu); button.addEventListener("click", () => { this.#uiManager.highlightSelection("floating_button"); }); this.#buttons.append(button); } } ;// CONCATENATED MODULE: ./src/display/editor/tools.js function bindEvents(obj, element, names) { for (const name of names) { element.addEventListener(name, obj[name].bind(obj)); } } function opacityToHex(opacity) { return Math.round(Math.min(255, Math.max(1, 255 * opacity))).toString(16).padStart(2, "0"); } class IdManager { #id = 0; constructor() {} get id() { return `${AnnotationEditorPrefix}${this.#id++}`; } } class ImageManager { #baseId = getUuid(); #id = 0; #cache = null; static get _isSVGFittingCanvas() { const svg = `data:image/svg+xml;charset=UTF-8,`; const canvas = new OffscreenCanvas(1, 3); const ctx = canvas.getContext("2d"); const image = new Image(); image.src = svg; const promise = image.decode().then(() => { ctx.drawImage(image, 0, 0, 1, 1, 0, 0, 1, 3); return new Uint32Array(ctx.getImageData(0, 0, 1, 1).data.buffer)[0] === 0; }); return shadow(this, "_isSVGFittingCanvas", promise); } async #get(key, rawData) { this.#cache ||= new Map(); let data = this.#cache.get(key); if (data === null) { return null; } if (data?.bitmap) { data.refCounter += 1; return data; } try { data ||= { bitmap: null, id: `image_${this.#baseId}_${this.#id++}`, refCounter: 0, isSvg: false }; let image; if (typeof rawData === "string") { data.url = rawData; image = await fetchData(rawData, "blob"); } else { image = data.file = rawData; } if (image.type === "image/svg+xml") { const mustRemoveAspectRatioPromise = ImageManager._isSVGFittingCanvas; const fileReader = new FileReader(); const imageElement = new Image(); const imagePromise = new Promise((resolve, reject) => { imageElement.onload = () => { data.bitmap = imageElement; data.isSvg = true; resolve(); }; fileReader.onload = async () => { const url = data.svgUrl = fileReader.result; imageElement.src = (await mustRemoveAspectRatioPromise) ? `${url}#svgView(preserveAspectRatio(none))` : url; }; imageElement.onerror = fileReader.onerror = reject; }); fileReader.readAsDataURL(image); await imagePromise; } else { data.bitmap = await createImageBitmap(image); } data.refCounter = 1; } catch (e) { console.error(e); data = null; } this.#cache.set(key, data); if (data) { this.#cache.set(data.id, data); } return data; } async getFromFile(file) { const { lastModified, name, size, type } = file; return this.#get(`${lastModified}_${name}_${size}_${type}`, file); } async getFromUrl(url) { return this.#get(url, url); } async getFromId(id) { this.#cache ||= new Map(); const data = this.#cache.get(id); if (!data) { return null; } if (data.bitmap) { data.refCounter += 1; return data; } if (data.file) { return this.getFromFile(data.file); } return this.getFromUrl(data.url); } getSvgUrl(id) { const data = this.#cache.get(id); if (!data?.isSvg) { return null; } return data.svgUrl; } deleteId(id) { this.#cache ||= new Map(); const data = this.#cache.get(id); if (!data) { return; } data.refCounter -= 1; if (data.refCounter !== 0) { return; } data.bitmap = null; } isValidId(id) { return id.startsWith(`image_${this.#baseId}_`); } } class CommandManager { #commands = []; #locked = false; #maxSize; #position = -1; constructor(maxSize = 128) { this.#maxSize = maxSize; } add({ cmd, undo, post, mustExec, type = NaN, overwriteIfSameType = false, keepUndo = false }) { if (mustExec) { cmd(); } if (this.#locked) { return; } const save = { cmd, undo, post, type }; if (this.#position === -1) { if (this.#commands.length > 0) { this.#commands.length = 0; } this.#position = 0; this.#commands.push(save); return; } if (overwriteIfSameType && this.#commands[this.#position].type === type) { if (keepUndo) { save.undo = this.#commands[this.#position].undo; } this.#commands[this.#position] = save; return; } const next = this.#position + 1; if (next === this.#maxSize) { this.#commands.splice(0, 1); } else { this.#position = next; if (next < this.#commands.length) { this.#commands.splice(next); } } this.#commands.push(save); } undo() { if (this.#position === -1) { return; } this.#locked = true; const { undo, post } = this.#commands[this.#position]; undo(); post?.(); this.#locked = false; this.#position -= 1; } redo() { if (this.#position < this.#commands.length - 1) { this.#position += 1; this.#locked = true; const { cmd, post } = this.#commands[this.#position]; cmd(); post?.(); this.#locked = false; } } hasSomethingToUndo() { return this.#position !== -1; } hasSomethingToRedo() { return this.#position < this.#commands.length - 1; } destroy() { this.#commands = null; } } class KeyboardManager { constructor(callbacks) { this.buffer = []; this.callbacks = new Map(); this.allKeys = new Set(); const { isMac } = util_FeatureTest.platform; for (const [keys, callback, options = {}] of callbacks) { for (const key of keys) { const isMacKey = key.startsWith("mac+"); if (isMac && isMacKey) { this.callbacks.set(key.slice(4), { callback, options }); this.allKeys.add(key.split("+").at(-1)); } else if (!isMac && !isMacKey) { this.callbacks.set(key, { callback, options }); this.allKeys.add(key.split("+").at(-1)); } } } } #serialize(event) { if (event.altKey) { this.buffer.push("alt"); } if (event.ctrlKey) { this.buffer.push("ctrl"); } if (event.metaKey) { this.buffer.push("meta"); } if (event.shiftKey) { this.buffer.push("shift"); } this.buffer.push(event.key); const str = this.buffer.join("+"); this.buffer.length = 0; return str; } exec(self, event) { if (!this.allKeys.has(event.key)) { return; } const info = this.callbacks.get(this.#serialize(event)); if (!info) { return; } const { callback, options: { bubbles = false, args = [], checker = null } } = info; if (checker && !checker(self, event)) { return; } callback.bind(self, ...args, event)(); if (!bubbles) { event.stopPropagation(); event.preventDefault(); } } } class ColorManager { static _colorsMapping = new Map([["CanvasText", [0, 0, 0]], ["Canvas", [255, 255, 255]]]); get _colors() { const colors = new Map([["CanvasText", null], ["Canvas", null]]); getColorValues(colors); return shadow(this, "_colors", colors); } convert(color) { const rgb = getRGB(color); if (!window.matchMedia("(forced-colors: active)").matches) { return rgb; } for (const [name, RGB] of this._colors) { if (RGB.every((x, i) => x === rgb[i])) { return ColorManager._colorsMapping.get(name); } } return rgb; } getHexCode(name) { const rgb = this._colors.get(name); if (!rgb) { return name; } return Util.makeHexColor(...rgb); } } class AnnotationEditorUIManager { #activeEditor = null; #allEditors = new Map(); #allLayers = new Map(); #altTextManager = null; #annotationStorage = null; #changedExistingAnnotations = null; #commandManager = new CommandManager(); #currentPageIndex = 0; #deletedAnnotationsElementIds = new Set(); #draggingEditors = null; #editorTypes = null; #editorsToRescale = new Set(); #enableHighlightFloatingButton = false; #filterFactory = null; #focusMainContainerTimeoutId = null; #highlightColors = null; #highlightWhenShiftUp = false; #highlightToolbar = null; #idManager = new IdManager(); #isEnabled = false; #isWaiting = false; #lastActiveElement = null; #mainHighlightColorPicker = null; #mlManager = null; #mode = AnnotationEditorType.NONE; #selectedEditors = new Set(); #selectedTextNode = null; #pageColors = null; #showAllStates = null; #boundBlur = this.blur.bind(this); #boundFocus = this.focus.bind(this); #boundCopy = this.copy.bind(this); #boundCut = this.cut.bind(this); #boundPaste = this.paste.bind(this); #boundKeydown = this.keydown.bind(this); #boundKeyup = this.keyup.bind(this); #boundOnEditingAction = this.onEditingAction.bind(this); #boundOnPageChanging = this.onPageChanging.bind(this); #boundOnScaleChanging = this.onScaleChanging.bind(this); #boundSelectionChange = this.#selectionChange.bind(this); #boundOnRotationChanging = this.onRotationChanging.bind(this); #previousStates = { isEditing: false, isEmpty: true, hasSomethingToUndo: false, hasSomethingToRedo: false, hasSelectedEditor: false, hasSelectedText: false }; #translation = [0, 0]; #translationTimeoutId = null; #container = null; #viewer = null; static TRANSLATE_SMALL = 1; static TRANSLATE_BIG = 10; static get _keyboardManager() { const proto = AnnotationEditorUIManager.prototype; const arrowChecker = self => self.#container.contains(document.activeElement) && document.activeElement.tagName !== "BUTTON" && self.hasSomethingToControl(); const textInputChecker = (_self, { target: el }) => { if (el instanceof HTMLInputElement) { const { type } = el; return type !== "text" && type !== "number"; } return true; }; const small = this.TRANSLATE_SMALL; const big = this.TRANSLATE_BIG; return shadow(this, "_keyboardManager", new KeyboardManager([[["ctrl+a", "mac+meta+a"], proto.selectAll, { checker: textInputChecker }], [["ctrl+z", "mac+meta+z"], proto.undo, { checker: textInputChecker }], [["ctrl+y", "ctrl+shift+z", "mac+meta+shift+z", "ctrl+shift+Z", "mac+meta+shift+Z"], proto.redo, { checker: textInputChecker }], [["Backspace", "alt+Backspace", "ctrl+Backspace", "shift+Backspace", "mac+Backspace", "mac+alt+Backspace", "mac+ctrl+Backspace", "Delete", "ctrl+Delete", "shift+Delete", "mac+Delete"], proto.delete, { checker: textInputChecker }], [["Enter", "mac+Enter"], proto.addNewEditorFromKeyboard, { checker: (self, { target: el }) => !(el instanceof HTMLButtonElement) && self.#container.contains(el) && !self.isEnterHandled }], [[" ", "mac+ "], proto.addNewEditorFromKeyboard, { checker: (self, { target: el }) => !(el instanceof HTMLButtonElement) && self.#container.contains(document.activeElement) }], [["Escape", "mac+Escape"], proto.unselectAll], [["ArrowLeft", "mac+ArrowLeft"], proto.translateSelectedEditors, { args: [-small, 0], checker: arrowChecker }], [["ctrl+ArrowLeft", "mac+shift+ArrowLeft"], proto.translateSelectedEditors, { args: [-big, 0], checker: arrowChecker }], [["ArrowRight", "mac+ArrowRight"], proto.translateSelectedEditors, { args: [small, 0], checker: arrowChecker }], [["ctrl+ArrowRight", "mac+shift+ArrowRight"], proto.translateSelectedEditors, { args: [big, 0], checker: arrowChecker }], [["ArrowUp", "mac+ArrowUp"], proto.translateSelectedEditors, { args: [0, -small], checker: arrowChecker }], [["ctrl+ArrowUp", "mac+shift+ArrowUp"], proto.translateSelectedEditors, { args: [0, -big], checker: arrowChecker }], [["ArrowDown", "mac+ArrowDown"], proto.translateSelectedEditors, { args: [0, small], checker: arrowChecker }], [["ctrl+ArrowDown", "mac+shift+ArrowDown"], proto.translateSelectedEditors, { args: [0, big], checker: arrowChecker }]])); } constructor(container, viewer, altTextManager, eventBus, pdfDocument, pageColors, highlightColors, enableHighlightFloatingButton, mlManager) { this.#container = container; this.#viewer = viewer; this.#altTextManager = altTextManager; this._eventBus = eventBus; this._eventBus._on("editingaction", this.#boundOnEditingAction); this._eventBus._on("pagechanging", this.#boundOnPageChanging); this._eventBus._on("scalechanging", this.#boundOnScaleChanging); this._eventBus._on("rotationchanging", this.#boundOnRotationChanging); this.#addSelectionListener(); this.#addKeyboardManager(); this.#annotationStorage = pdfDocument.annotationStorage; this.#filterFactory = pdfDocument.filterFactory; this.#pageColors = pageColors; this.#highlightColors = highlightColors || null; this.#enableHighlightFloatingButton = enableHighlightFloatingButton; this.#mlManager = mlManager || null; this.viewParameters = { realScale: PixelsPerInch.PDF_TO_CSS_UNITS, rotation: 0 }; this.isShiftKeyDown = false; } destroy() { this.#removeKeyboardManager(); this.#removeFocusManager(); this._eventBus._off("editingaction", this.#boundOnEditingAction); this._eventBus._off("pagechanging", this.#boundOnPageChanging); this._eventBus._off("scalechanging", this.#boundOnScaleChanging); this._eventBus._off("rotationchanging", this.#boundOnRotationChanging); for (const layer of this.#allLayers.values()) { layer.destroy(); } this.#allLayers.clear(); this.#allEditors.clear(); this.#editorsToRescale.clear(); this.#activeEditor = null; this.#selectedEditors.clear(); this.#commandManager.destroy(); this.#altTextManager?.destroy(); this.#highlightToolbar?.hide(); this.#highlightToolbar = null; if (this.#focusMainContainerTimeoutId) { clearTimeout(this.#focusMainContainerTimeoutId); this.#focusMainContainerTimeoutId = null; } if (this.#translationTimeoutId) { clearTimeout(this.#translationTimeoutId); this.#translationTimeoutId = null; } this.#removeSelectionListener(); } async mlGuess(data) { return this.#mlManager?.guess(data) || null; } get hasMLManager() { return !!this.#mlManager; } get hcmFilter() { return shadow(this, "hcmFilter", this.#pageColors ? this.#filterFactory.addHCMFilter(this.#pageColors.foreground, this.#pageColors.background) : "none"); } get direction() { return shadow(this, "direction", getComputedStyle(this.#container).direction); } get highlightColors() { return shadow(this, "highlightColors", this.#highlightColors ? new Map(this.#highlightColors.split(",").map(pair => pair.split("=").map(x => x.trim()))) : null); } get highlightColorNames() { return shadow(this, "highlightColorNames", this.highlightColors ? new Map(Array.from(this.highlightColors, e => e.reverse())) : null); } setMainHighlightColorPicker(colorPicker) { this.#mainHighlightColorPicker = colorPicker; } editAltText(editor) { this.#altTextManager?.editAltText(this, editor); } onPageChanging({ pageNumber }) { this.#currentPageIndex = pageNumber - 1; } focusMainContainer() { this.#container.focus(); } findParent(x, y) { for (const layer of this.#allLayers.values()) { const { x: layerX, y: layerY, width, height } = layer.div.getBoundingClientRect(); if (x >= layerX && x <= layerX + width && y >= layerY && y <= layerY + height) { return layer; } } return null; } disableUserSelect(value = false) { this.#viewer.classList.toggle("noUserSelect", value); } addShouldRescale(editor) { this.#editorsToRescale.add(editor); } removeShouldRescale(editor) { this.#editorsToRescale.delete(editor); } onScaleChanging({ scale }) { this.commitOrRemove(); this.viewParameters.realScale = scale * PixelsPerInch.PDF_TO_CSS_UNITS; for (const editor of this.#editorsToRescale) { editor.onScaleChanging(); } } onRotationChanging({ pagesRotation }) { this.commitOrRemove(); this.viewParameters.rotation = pagesRotation; } #getAnchorElementForSelection({ anchorNode }) { return anchorNode.nodeType === Node.TEXT_NODE ? anchorNode.parentElement : anchorNode; } highlightSelection(methodOfCreation = "") { const selection = document.getSelection(); if (!selection || selection.isCollapsed) { return; } const { anchorNode, anchorOffset, focusNode, focusOffset } = selection; const text = selection.toString(); const anchorElement = this.#getAnchorElementForSelection(selection); const textLayer = anchorElement.closest(".textLayer"); const boxes = this.getSelectionBoxes(textLayer); if (!boxes) { return; } selection.empty(); if (this.#mode === AnnotationEditorType.NONE) { this._eventBus.dispatch("showannotationeditorui", { source: this, mode: AnnotationEditorType.HIGHLIGHT }); this.showAllEditors("highlight", true, true); } for (const layer of this.#allLayers.values()) { if (layer.hasTextLayer(textLayer)) { layer.createAndAddNewEditor({ x: 0, y: 0 }, false, { methodOfCreation, boxes, anchorNode, anchorOffset, focusNode, focusOffset, text }); break; } } } #displayHighlightToolbar() { const selection = document.getSelection(); if (!selection || selection.isCollapsed) { return; } const anchorElement = this.#getAnchorElementForSelection(selection); const textLayer = anchorElement.closest(".textLayer"); const boxes = this.getSelectionBoxes(textLayer); if (!boxes) { return; } this.#highlightToolbar ||= new HighlightToolbar(this); this.#highlightToolbar.show(textLayer, boxes, this.direction === "ltr"); } addToAnnotationStorage(editor) { if (!editor.isEmpty() && this.#annotationStorage && !this.#annotationStorage.has(editor.id)) { this.#annotationStorage.setValue(editor.id, editor); } } #selectionChange() { const selection = document.getSelection(); if (!selection || selection.isCollapsed) { if (this.#selectedTextNode) { this.#highlightToolbar?.hide(); this.#selectedTextNode = null; this.#dispatchUpdateStates({ hasSelectedText: false }); } return; } const { anchorNode } = selection; if (anchorNode === this.#selectedTextNode) { return; } const anchorElement = this.#getAnchorElementForSelection(selection); const textLayer = anchorElement.closest(".textLayer"); if (!textLayer) { if (this.#selectedTextNode) { this.#highlightToolbar?.hide(); this.#selectedTextNode = null; this.#dispatchUpdateStates({ hasSelectedText: false }); } return; } this.#highlightToolbar?.hide(); this.#selectedTextNode = anchorNode; this.#dispatchUpdateStates({ hasSelectedText: true }); if (this.#mode !== AnnotationEditorType.HIGHLIGHT && this.#mode !== AnnotationEditorType.NONE) { return; } if (this.#mode === AnnotationEditorType.HIGHLIGHT) { this.showAllEditors("highlight", true, true); } this.#highlightWhenShiftUp = this.isShiftKeyDown; if (!this.isShiftKeyDown) { const pointerup = e => { if (e.type === "pointerup" && e.button !== 0) { return; } window.removeEventListener("pointerup", pointerup); window.removeEventListener("blur", pointerup); if (e.type === "pointerup") { this.#onSelectEnd("main_toolbar"); } }; window.addEventListener("pointerup", pointerup); window.addEventListener("blur", pointerup); } } #onSelectEnd(methodOfCreation = "") { if (this.#mode === AnnotationEditorType.HIGHLIGHT) { this.highlightSelection(methodOfCreation); } else if (this.#enableHighlightFloatingButton) { this.#displayHighlightToolbar(); } } #addSelectionListener() { document.addEventListener("selectionchange", this.#boundSelectionChange); } #removeSelectionListener() { document.removeEventListener("selectionchange", this.#boundSelectionChange); } #addFocusManager() { window.addEventListener("focus", this.#boundFocus); window.addEventListener("blur", this.#boundBlur); } #removeFocusManager() { window.removeEventListener("focus", this.#boundFocus); window.removeEventListener("blur", this.#boundBlur); } blur() { this.isShiftKeyDown = false; if (this.#highlightWhenShiftUp) { this.#highlightWhenShiftUp = false; this.#onSelectEnd("main_toolbar"); } if (!this.hasSelection) { return; } const { activeElement } = document; for (const editor of this.#selectedEditors) { if (editor.div.contains(activeElement)) { this.#lastActiveElement = [editor, activeElement]; editor._focusEventsAllowed = false; break; } } } focus() { if (!this.#lastActiveElement) { return; } const [lastEditor, lastActiveElement] = this.#lastActiveElement; this.#lastActiveElement = null; lastActiveElement.addEventListener("focusin", () => { lastEditor._focusEventsAllowed = true; }, { once: true }); lastActiveElement.focus(); } #addKeyboardManager() { window.addEventListener("keydown", this.#boundKeydown); window.addEventListener("keyup", this.#boundKeyup); } #removeKeyboardManager() { window.removeEventListener("keydown", this.#boundKeydown); window.removeEventListener("keyup", this.#boundKeyup); } #addCopyPasteListeners() { document.addEventListener("copy", this.#boundCopy); document.addEventListener("cut", this.#boundCut); document.addEventListener("paste", this.#boundPaste); } #removeCopyPasteListeners() { document.removeEventListener("copy", this.#boundCopy); document.removeEventListener("cut", this.#boundCut); document.removeEventListener("paste", this.#boundPaste); } addEditListeners() { this.#addKeyboardManager(); this.#addCopyPasteListeners(); } removeEditListeners() { this.#removeKeyboardManager(); this.#removeCopyPasteListeners(); } copy(event) { event.preventDefault(); this.#activeEditor?.commitOrRemove(); if (!this.hasSelection) { return; } const editors = []; for (const editor of this.#selectedEditors) { const serialized = editor.serialize(true); if (serialized) { editors.push(serialized); } } if (editors.length === 0) { return; } event.clipboardData.setData("application/pdfjs", JSON.stringify(editors)); } cut(event) { this.copy(event); this.delete(); } paste(event) { event.preventDefault(); const { clipboardData } = event; for (const item of clipboardData.items) { for (const editorType of this.#editorTypes) { if (editorType.isHandlingMimeForPasting(item.type)) { editorType.paste(item, this.currentLayer); return; } } } let data = clipboardData.getData("application/pdfjs"); if (!data) { return; } try { data = JSON.parse(data); } catch (ex) { warn(`paste: "${ex.message}".`); return; } if (!Array.isArray(data)) { return; } this.unselectAll(); const layer = this.currentLayer; try { const newEditors = []; for (const editor of data) { const deserializedEditor = layer.deserialize(editor); if (!deserializedEditor) { return; } newEditors.push(deserializedEditor); } const cmd = () => { for (const editor of newEditors) { this.#addEditorToLayer(editor); } this.#selectEditors(newEditors); }; const undo = () => { for (const editor of newEditors) { editor.remove(); } }; this.addCommands({ cmd, undo, mustExec: true }); } catch (ex) { warn(`paste: "${ex.message}".`); } } keydown(event) { if (!this.isShiftKeyDown && event.key === "Shift") { this.isShiftKeyDown = true; } if (this.#mode !== AnnotationEditorType.NONE && !this.isEditorHandlingKeyboard) { AnnotationEditorUIManager._keyboardManager.exec(this, event); } } keyup(event) { if (this.isShiftKeyDown && event.key === "Shift") { this.isShiftKeyDown = false; if (this.#highlightWhenShiftUp) { this.#highlightWhenShiftUp = false; this.#onSelectEnd("main_toolbar"); } } } onEditingAction({ name }) { switch (name) { case "undo": case "redo": case "delete": case "selectAll": this[name](); break; case "highlightSelection": this.highlightSelection("context_menu"); break; } } #dispatchUpdateStates(details) { const hasChanged = Object.entries(details).some(([key, value]) => this.#previousStates[key] !== value); if (hasChanged) { this._eventBus.dispatch("annotationeditorstateschanged", { source: this, details: Object.assign(this.#previousStates, details) }); if (this.#mode === AnnotationEditorType.HIGHLIGHT && details.hasSelectedEditor === false) { this.#dispatchUpdateUI([[AnnotationEditorParamsType.HIGHLIGHT_FREE, true]]); } } } #dispatchUpdateUI(details) { this._eventBus.dispatch("annotationeditorparamschanged", { source: this, details }); } setEditingState(isEditing) { if (isEditing) { this.#addFocusManager(); this.#addCopyPasteListeners(); this.#dispatchUpdateStates({ isEditing: this.#mode !== AnnotationEditorType.NONE, isEmpty: this.#isEmpty(), hasSomethingToUndo: this.#commandManager.hasSomethingToUndo(), hasSomethingToRedo: this.#commandManager.hasSomethingToRedo(), hasSelectedEditor: false }); } else { this.#removeFocusManager(); this.#removeCopyPasteListeners(); this.#dispatchUpdateStates({ isEditing: false }); this.disableUserSelect(false); } } registerEditorTypes(types) { if (this.#editorTypes) { return; } this.#editorTypes = types; for (const editorType of this.#editorTypes) { this.#dispatchUpdateUI(editorType.defaultPropertiesToUpdate); } } getId() { return this.#idManager.id; } get currentLayer() { return this.#allLayers.get(this.#currentPageIndex); } getLayer(pageIndex) { return this.#allLayers.get(pageIndex); } get currentPageIndex() { return this.#currentPageIndex; } addLayer(layer) { this.#allLayers.set(layer.pageIndex, layer); if (this.#isEnabled) { layer.enable(); } else { layer.disable(); } } removeLayer(layer) { this.#allLayers.delete(layer.pageIndex); } updateMode(mode, editId = null, isFromKeyboard = false) { if (this.#mode === mode) { return; } this.#mode = mode; if (mode === AnnotationEditorType.NONE) { this.setEditingState(false); this.#disableAll(); return; } this.setEditingState(true); this.#enableAll(); this.unselectAll(); for (const layer of this.#allLayers.values()) { layer.updateMode(mode); } if (!editId && isFromKeyboard) { this.addNewEditorFromKeyboard(); return; } if (!editId) { return; } for (const editor of this.#allEditors.values()) { if (editor.annotationElementId === editId) { this.setSelected(editor); editor.enterInEditMode(); break; } } } addNewEditorFromKeyboard() { if (this.currentLayer.canCreateNewEmptyEditor()) { this.currentLayer.addNewEditor(); } } updateToolbar(mode) { if (mode === this.#mode) { return; } this._eventBus.dispatch("switchannotationeditormode", { source: this, mode }); } updateParams(type, value) { if (!this.#editorTypes) { return; } switch (type) { case AnnotationEditorParamsType.CREATE: this.currentLayer.addNewEditor(); return; case AnnotationEditorParamsType.HIGHLIGHT_DEFAULT_COLOR: this.#mainHighlightColorPicker?.updateColor(value); break; case AnnotationEditorParamsType.HIGHLIGHT_SHOW_ALL: this._eventBus.dispatch("reporttelemetry", { source: this, details: { type: "editing", data: { type: "highlight", action: "toggle_visibility" } } }); (this.#showAllStates ||= new Map()).set(type, value); this.showAllEditors("highlight", value); break; } for (const editor of this.#selectedEditors) { editor.updateParams(type, value); } for (const editorType of this.#editorTypes) { editorType.updateDefaultParams(type, value); } } showAllEditors(type, visible, updateButton = false) { for (const editor of this.#allEditors.values()) { if (editor.editorType === type) { editor.show(visible); } } const state = this.#showAllStates?.get(AnnotationEditorParamsType.HIGHLIGHT_SHOW_ALL) ?? true; if (state !== visible) { this.#dispatchUpdateUI([[AnnotationEditorParamsType.HIGHLIGHT_SHOW_ALL, visible]]); } } enableWaiting(mustWait = false) { if (this.#isWaiting === mustWait) { return; } this.#isWaiting = mustWait; for (const layer of this.#allLayers.values()) { if (mustWait) { layer.disableClick(); } else { layer.enableClick(); } layer.div.classList.toggle("waiting", mustWait); } } #enableAll() { if (!this.#isEnabled) { this.#isEnabled = true; for (const layer of this.#allLayers.values()) { layer.enable(); } for (const editor of this.#allEditors.values()) { editor.enable(); } } } #disableAll() { this.unselectAll(); if (this.#isEnabled) { this.#isEnabled = false; for (const layer of this.#allLayers.values()) { layer.disable(); } for (const editor of this.#allEditors.values()) { editor.disable(); } } } getEditors(pageIndex) { const editors = []; for (const editor of this.#allEditors.values()) { if (editor.pageIndex === pageIndex) { editors.push(editor); } } return editors; } getEditor(id) { return this.#allEditors.get(id); } addEditor(editor) { this.#allEditors.set(editor.id, editor); } removeEditor(editor) { if (editor.div.contains(document.activeElement)) { if (this.#focusMainContainerTimeoutId) { clearTimeout(this.#focusMainContainerTimeoutId); } this.#focusMainContainerTimeoutId = setTimeout(() => { this.focusMainContainer(); this.#focusMainContainerTimeoutId = null; }, 0); } this.#allEditors.delete(editor.id); this.unselect(editor); if (!editor.annotationElementId || !this.#deletedAnnotationsElementIds.has(editor.annotationElementId)) { this.#annotationStorage?.remove(editor.id); } } addDeletedAnnotationElement(editor) { this.#deletedAnnotationsElementIds.add(editor.annotationElementId); this.addChangedExistingAnnotation(editor); editor.deleted = true; } isDeletedAnnotationElement(annotationElementId) { return this.#deletedAnnotationsElementIds.has(annotationElementId); } removeDeletedAnnotationElement(editor) { this.#deletedAnnotationsElementIds.delete(editor.annotationElementId); this.removeChangedExistingAnnotation(editor); editor.deleted = false; } #addEditorToLayer(editor) { const layer = this.#allLayers.get(editor.pageIndex); if (layer) { layer.addOrRebuild(editor); } else { this.addEditor(editor); this.addToAnnotationStorage(editor); } } setActiveEditor(editor) { if (this.#activeEditor === editor) { return; } this.#activeEditor = editor; if (editor) { this.#dispatchUpdateUI(editor.propertiesToUpdate); } } get #lastSelectedEditor() { let ed = null; for (ed of this.#selectedEditors) {} return ed; } updateUI(editor) { if (this.#lastSelectedEditor === editor) { this.#dispatchUpdateUI(editor.propertiesToUpdate); } } toggleSelected(editor) { if (this.#selectedEditors.has(editor)) { this.#selectedEditors.delete(editor); editor.unselect(); this.#dispatchUpdateStates({ hasSelectedEditor: this.hasSelection }); return; } this.#selectedEditors.add(editor); editor.select(); this.#dispatchUpdateUI(editor.propertiesToUpdate); this.#dispatchUpdateStates({ hasSelectedEditor: true }); } setSelected(editor) { for (const ed of this.#selectedEditors) { if (ed !== editor) { ed.unselect(); } } this.#selectedEditors.clear(); this.#selectedEditors.add(editor); editor.select(); this.#dispatchUpdateUI(editor.propertiesToUpdate); this.#dispatchUpdateStates({ hasSelectedEditor: true }); } isSelected(editor) { return this.#selectedEditors.has(editor); } get firstSelectedEditor() { return this.#selectedEditors.values().next().value; } unselect(editor) { editor.unselect(); this.#selectedEditors.delete(editor); this.#dispatchUpdateStates({ hasSelectedEditor: this.hasSelection }); } get hasSelection() { return this.#selectedEditors.size !== 0; } get isEnterHandled() { return this.#selectedEditors.size === 1 && this.firstSelectedEditor.isEnterHandled; } undo() { this.#commandManager.undo(); this.#dispatchUpdateStates({ hasSomethingToUndo: this.#commandManager.hasSomethingToUndo(), hasSomethingToRedo: true, isEmpty: this.#isEmpty() }); } redo() { this.#commandManager.redo(); this.#dispatchUpdateStates({ hasSomethingToUndo: true, hasSomethingToRedo: this.#commandManager.hasSomethingToRedo(), isEmpty: this.#isEmpty() }); } addCommands(params) { this.#commandManager.add(params); this.#dispatchUpdateStates({ hasSomethingToUndo: true, hasSomethingToRedo: false, isEmpty: this.#isEmpty() }); } #isEmpty() { if (this.#allEditors.size === 0) { return true; } if (this.#allEditors.size === 1) { for (const editor of this.#allEditors.values()) { return editor.isEmpty(); } } return false; } delete() { this.commitOrRemove(); if (!this.hasSelection) { return; } const editors = [...this.#selectedEditors]; const cmd = () => { for (const editor of editors) { editor.remove(); } }; const undo = () => { for (const editor of editors) { this.#addEditorToLayer(editor); } }; this.addCommands({ cmd, undo, mustExec: true }); } commitOrRemove() { this.#activeEditor?.commitOrRemove(); } hasSomethingToControl() { return this.#activeEditor || this.hasSelection; } #selectEditors(editors) { for (const editor of this.#selectedEditors) { editor.unselect(); } this.#selectedEditors.clear(); for (const editor of editors) { if (editor.isEmpty()) { continue; } this.#selectedEditors.add(editor); editor.select(); } this.#dispatchUpdateStates({ hasSelectedEditor: this.hasSelection }); } selectAll() { for (const editor of this.#selectedEditors) { editor.commit(); } this.#selectEditors(this.#allEditors.values()); } unselectAll() { if (this.#activeEditor) { this.#activeEditor.commitOrRemove(); if (this.#mode !== AnnotationEditorType.NONE) { return; } } if (!this.hasSelection) { return; } for (const editor of this.#selectedEditors) { editor.unselect(); } this.#selectedEditors.clear(); this.#dispatchUpdateStates({ hasSelectedEditor: false }); } translateSelectedEditors(x, y, noCommit = false) { if (!noCommit) { this.commitOrRemove(); } if (!this.hasSelection) { return; } this.#translation[0] += x; this.#translation[1] += y; const [totalX, totalY] = this.#translation; const editors = [...this.#selectedEditors]; const TIME_TO_WAIT = 1000; if (this.#translationTimeoutId) { clearTimeout(this.#translationTimeoutId); } this.#translationTimeoutId = setTimeout(() => { this.#translationTimeoutId = null; this.#translation[0] = this.#translation[1] = 0; this.addCommands({ cmd: () => { for (const editor of editors) { if (this.#allEditors.has(editor.id)) { editor.translateInPage(totalX, totalY); } } }, undo: () => { for (const editor of editors) { if (this.#allEditors.has(editor.id)) { editor.translateInPage(-totalX, -totalY); } } }, mustExec: false }); }, TIME_TO_WAIT); for (const editor of editors) { editor.translateInPage(x, y); } } setUpDragSession() { if (!this.hasSelection) { return; } this.disableUserSelect(true); this.#draggingEditors = new Map(); for (const editor of this.#selectedEditors) { this.#draggingEditors.set(editor, { savedX: editor.x, savedY: editor.y, savedPageIndex: editor.pageIndex, newX: 0, newY: 0, newPageIndex: -1 }); } } endDragSession() { if (!this.#draggingEditors) { return false; } this.disableUserSelect(false); const map = this.#draggingEditors; this.#draggingEditors = null; let mustBeAddedInUndoStack = false; for (const [{ x, y, pageIndex }, value] of map) { value.newX = x; value.newY = y; value.newPageIndex = pageIndex; mustBeAddedInUndoStack ||= x !== value.savedX || y !== value.savedY || pageIndex !== value.savedPageIndex; } if (!mustBeAddedInUndoStack) { return false; } const move = (editor, x, y, pageIndex) => { if (this.#allEditors.has(editor.id)) { const parent = this.#allLayers.get(pageIndex); if (parent) { editor._setParentAndPosition(parent, x, y); } else { editor.pageIndex = pageIndex; editor.x = x; editor.y = y; } } }; this.addCommands({ cmd: () => { for (const [editor, { newX, newY, newPageIndex }] of map) { move(editor, newX, newY, newPageIndex); } }, undo: () => { for (const [editor, { savedX, savedY, savedPageIndex }] of map) { move(editor, savedX, savedY, savedPageIndex); } }, mustExec: true }); return true; } dragSelectedEditors(tx, ty) { if (!this.#draggingEditors) { return; } for (const editor of this.#draggingEditors.keys()) { editor.drag(tx, ty); } } rebuild(editor) { if (editor.parent === null) { const parent = this.getLayer(editor.pageIndex); if (parent) { parent.changeParent(editor); parent.addOrRebuild(editor); } else { this.addEditor(editor); this.addToAnnotationStorage(editor); editor.rebuild(); } } else { editor.parent.addOrRebuild(editor); } } get isEditorHandlingKeyboard() { return this.getActive()?.shouldGetKeyboardEvents() || this.#selectedEditors.size === 1 && this.firstSelectedEditor.shouldGetKeyboardEvents(); } isActive(editor) { return this.#activeEditor === editor; } getActive() { return this.#activeEditor; } getMode() { return this.#mode; } get imageManager() { return shadow(this, "imageManager", new ImageManager()); } getSelectionBoxes(textLayer) { if (!textLayer) { return null; } const selection = document.getSelection(); for (let i = 0, ii = selection.rangeCount; i < ii; i++) { if (!textLayer.contains(selection.getRangeAt(i).commonAncestorContainer)) { return null; } } const { x: layerX, y: layerY, width: parentWidth, height: parentHeight } = textLayer.getBoundingClientRect(); let rotator; switch (textLayer.getAttribute("data-main-rotation")) { case "90": rotator = (x, y, w, h) => ({ x: (y - layerY) / parentHeight, y: 1 - (x + w - layerX) / parentWidth, width: h / parentHeight, height: w / parentWidth }); break; case "180": rotator = (x, y, w, h) => ({ x: 1 - (x + w - layerX) / parentWidth, y: 1 - (y + h - layerY) / parentHeight, width: w / parentWidth, height: h / parentHeight }); break; case "270": rotator = (x, y, w, h) => ({ x: 1 - (y + h - layerY) / parentHeight, y: (x - layerX) / parentWidth, width: h / parentHeight, height: w / parentWidth }); break; default: rotator = (x, y, w, h) => ({ x: (x - layerX) / parentWidth, y: (y - layerY) / parentHeight, width: w / parentWidth, height: h / parentHeight }); break; } const boxes = []; for (let i = 0, ii = selection.rangeCount; i < ii; i++) { const range = selection.getRangeAt(i); if (range.collapsed) { continue; } for (const { x, y, width, height } of range.getClientRects()) { if (width === 0 || height === 0) { continue; } boxes.push(rotator(x, y, width, height)); } } return boxes.length === 0 ? null : boxes; } addChangedExistingAnnotation({ annotationElementId, id }) { (this.#changedExistingAnnotations ||= new Map()).set(annotationElementId, id); } removeChangedExistingAnnotation({ annotationElementId }) { this.#changedExistingAnnotations?.delete(annotationElementId); } renderAnnotationElement(annotation) { const editorId = this.#changedExistingAnnotations?.get(annotation.data.id); if (!editorId) { return; } const editor = this.#annotationStorage.getRawValue(editorId); if (!editor) { return; } if (this.#mode === AnnotationEditorType.NONE && !editor.hasBeenModified) { return; } editor.renderAnnotationElement(annotation); } } ;// CONCATENATED MODULE: ./src/display/editor/alt_text.js class AltText { #altText = ""; #altTextDecorative = false; #altTextButton = null; #altTextTooltip = null; #altTextTooltipTimeout = null; #altTextWasFromKeyBoard = false; #editor = null; static _l10nPromise = null; constructor(editor) { this.#editor = editor; } static initialize(l10nPromise) { AltText._l10nPromise ||= l10nPromise; } async render() { const altText = this.#altTextButton = document.createElement("button"); altText.className = "altText"; const msg = await AltText._l10nPromise.get("pdfjs-editor-alt-text-button-label"); altText.textContent = msg; altText.setAttribute("aria-label", msg); altText.tabIndex = "0"; altText.addEventListener("contextmenu", noContextMenu); altText.addEventListener("pointerdown", event => event.stopPropagation()); const onClick = event => { event.preventDefault(); this.#editor._uiManager.editAltText(this.#editor); }; altText.addEventListener("click", onClick, { capture: true }); altText.addEventListener("keydown", event => { if (event.target === altText && event.key === "Enter") { this.#altTextWasFromKeyBoard = true; onClick(event); } }); await this.#setState(); return altText; } finish() { if (!this.#altTextButton) { return; } this.#altTextButton.focus({ focusVisible: this.#altTextWasFromKeyBoard }); this.#altTextWasFromKeyBoard = false; } isEmpty() { return !this.#altText && !this.#altTextDecorative; } get data() { return { altText: this.#altText, decorative: this.#altTextDecorative }; } set data({ altText, decorative }) { if (this.#altText === altText && this.#altTextDecorative === decorative) { return; } this.#altText = altText; this.#altTextDecorative = decorative; this.#setState(); } toggle(enabled = false) { if (!this.#altTextButton) { return; } if (!enabled && this.#altTextTooltipTimeout) { clearTimeout(this.#altTextTooltipTimeout); this.#altTextTooltipTimeout = null; } this.#altTextButton.disabled = !enabled; } destroy() { this.#altTextButton?.remove(); this.#altTextButton = null; this.#altTextTooltip = null; } async #setState() { const button = this.#altTextButton; if (!button) { return; } if (!this.#altText && !this.#altTextDecorative) { button.classList.remove("done"); this.#altTextTooltip?.remove(); return; } button.classList.add("done"); AltText._l10nPromise.get("pdfjs-editor-alt-text-edit-button-label").then(msg => { button.setAttribute("aria-label", msg); }); let tooltip = this.#altTextTooltip; if (!tooltip) { this.#altTextTooltip = tooltip = document.createElement("span"); tooltip.className = "tooltip"; tooltip.setAttribute("role", "tooltip"); const id = tooltip.id = `alt-text-tooltip-${this.#editor.id}`; button.setAttribute("aria-describedby", id); const DELAY_TO_SHOW_TOOLTIP = 100; button.addEventListener("mouseenter", () => { this.#altTextTooltipTimeout = setTimeout(() => { this.#altTextTooltipTimeout = null; this.#altTextTooltip.classList.add("show"); this.#editor._reportTelemetry({ action: "alt_text_tooltip" }); }, DELAY_TO_SHOW_TOOLTIP); }); button.addEventListener("mouseleave", () => { if (this.#altTextTooltipTimeout) { clearTimeout(this.#altTextTooltipTimeout); this.#altTextTooltipTimeout = null; } this.#altTextTooltip?.classList.remove("show"); }); } tooltip.innerText = this.#altTextDecorative ? await AltText._l10nPromise.get("pdfjs-editor-alt-text-decorative-tooltip") : this.#altText; if (!tooltip.parentNode) { button.append(tooltip); } const element = this.#editor.getImageForAltText(); element?.setAttribute("aria-describedby", tooltip.id); } } ;// CONCATENATED MODULE: ./src/display/editor/editor.js class AnnotationEditor { #allResizerDivs = null; #altText = null; #disabled = false; #keepAspectRatio = false; #resizersDiv = null; #savedDimensions = null; #boundFocusin = this.focusin.bind(this); #boundFocusout = this.focusout.bind(this); #editToolbar = null; #focusedResizerName = ""; #hasBeenClicked = false; #initialPosition = null; #isEditing = false; #isInEditMode = false; #isResizerEnabledForKeyboard = false; #moveInDOMTimeout = null; #prevDragX = 0; #prevDragY = 0; #telemetryTimeouts = null; _initialOptions = Object.create(null); _isVisible = true; _uiManager = null; _focusEventsAllowed = true; _l10nPromise = null; #isDraggable = false; #zIndex = AnnotationEditor._zIndex++; static _borderLineWidth = -1; static _colorManager = new ColorManager(); static _zIndex = 1; static _telemetryTimeout = 1000; static get _resizerKeyboardManager() { const resize = AnnotationEditor.prototype._resizeWithKeyboard; const small = AnnotationEditorUIManager.TRANSLATE_SMALL; const big = AnnotationEditorUIManager.TRANSLATE_BIG; return shadow(this, "_resizerKeyboardManager", new KeyboardManager([[["ArrowLeft", "mac+ArrowLeft"], resize, { args: [-small, 0] }], [["ctrl+ArrowLeft", "mac+shift+ArrowLeft"], resize, { args: [-big, 0] }], [["ArrowRight", "mac+ArrowRight"], resize, { args: [small, 0] }], [["ctrl+ArrowRight", "mac+shift+ArrowRight"], resize, { args: [big, 0] }], [["ArrowUp", "mac+ArrowUp"], resize, { args: [0, -small] }], [["ctrl+ArrowUp", "mac+shift+ArrowUp"], resize, { args: [0, -big] }], [["ArrowDown", "mac+ArrowDown"], resize, { args: [0, small] }], [["ctrl+ArrowDown", "mac+shift+ArrowDown"], resize, { args: [0, big] }], [["Escape", "mac+Escape"], AnnotationEditor.prototype._stopResizingWithKeyboard]])); } constructor(parameters) { if (this.constructor === AnnotationEditor) { unreachable("Cannot initialize AnnotationEditor."); } this.parent = parameters.parent; this.id = parameters.id; this.width = this.height = null; this.pageIndex = parameters.parent.pageIndex; this.name = parameters.name; this.div = null; this._uiManager = parameters.uiManager; this.annotationElementId = null; this._willKeepAspectRatio = false; this._initialOptions.isCentered = parameters.isCentered; this._structTreeParentId = null; const { rotation, rawDims: { pageWidth, pageHeight, pageX, pageY } } = this.parent.viewport; this.rotation = rotation; this.pageRotation = (360 + rotation - this._uiManager.viewParameters.rotation) % 360; this.pageDimensions = [pageWidth, pageHeight]; this.pageTranslation = [pageX, pageY]; const [width, height] = this.parentDimensions; this.x = parameters.x / width; this.y = parameters.y / height; this.isAttachedToDOM = false; this.deleted = false; } get editorType() { return Object.getPrototypeOf(this).constructor._type; } static get _defaultLineColor() { return shadow(this, "_defaultLineColor", this._colorManager.getHexCode("CanvasText")); } static deleteAnnotationElement(editor) { const fakeEditor = new FakeEditor({ id: editor.parent.getNextId(), parent: editor.parent, uiManager: editor._uiManager }); fakeEditor.annotationElementId = editor.annotationElementId; fakeEditor.deleted = true; fakeEditor._uiManager.addToAnnotationStorage(fakeEditor); } static initialize(l10n, _uiManager, options) { AnnotationEditor._l10nPromise ||= new Map(["pdfjs-editor-alt-text-button-label", "pdfjs-editor-alt-text-edit-button-label", "pdfjs-editor-alt-text-decorative-tooltip", "pdfjs-editor-resizer-label-topLeft", "pdfjs-editor-resizer-label-topMiddle", "pdfjs-editor-resizer-label-topRight", "pdfjs-editor-resizer-label-middleRight", "pdfjs-editor-resizer-label-bottomRight", "pdfjs-editor-resizer-label-bottomMiddle", "pdfjs-editor-resizer-label-bottomLeft", "pdfjs-editor-resizer-label-middleLeft"].map(str => [str, l10n.get(str.replaceAll(/([A-Z])/g, c => `-${c.toLowerCase()}`))])); if (options?.strings) { for (const str of options.strings) { AnnotationEditor._l10nPromise.set(str, l10n.get(str)); } } if (AnnotationEditor._borderLineWidth !== -1) { return; } const style = getComputedStyle(document.documentElement); AnnotationEditor._borderLineWidth = parseFloat(style.getPropertyValue("--outline-width")) || 0; } static updateDefaultParams(_type, _value) {} static get defaultPropertiesToUpdate() { return []; } static isHandlingMimeForPasting(mime) { return false; } static paste(item, parent) { unreachable("Not implemented"); } get propertiesToUpdate() { return []; } get _isDraggable() { return this.#isDraggable; } set _isDraggable(value) { this.#isDraggable = value; this.div?.classList.toggle("draggable", value); } get isEnterHandled() { return true; } center() { const [pageWidth, pageHeight] = this.pageDimensions; switch (this.parentRotation) { case 90: this.x -= this.height * pageHeight / (pageWidth * 2); this.y += this.width * pageWidth / (pageHeight * 2); break; case 180: this.x += this.width / 2; this.y += this.height / 2; break; case 270: this.x += this.height * pageHeight / (pageWidth * 2); this.y -= this.width * pageWidth / (pageHeight * 2); break; default: this.x -= this.width / 2; this.y -= this.height / 2; break; } this.fixAndSetPosition(); } addCommands(params) { this._uiManager.addCommands(params); } get currentLayer() { return this._uiManager.currentLayer; } setInBackground() { this.div.style.zIndex = 0; } setInForeground() { this.div.style.zIndex = this.#zIndex; } setParent(parent) { if (parent !== null) { this.pageIndex = parent.pageIndex; this.pageDimensions = parent.pageDimensions; } else { this.#stopResizing(); } this.parent = parent; } focusin(event) { if (!this._focusEventsAllowed) { return; } if (!this.#hasBeenClicked) { this.parent.setSelected(this); } else { this.#hasBeenClicked = false; } } focusout(event) { if (!this._focusEventsAllowed) { return; } if (!this.isAttachedToDOM) { return; } const target = event.relatedTarget; if (target?.closest(`#${this.id}`)) { return; } event.preventDefault(); if (!this.parent?.isMultipleSelection) { this.commitOrRemove(); } } commitOrRemove() { if (this.isEmpty()) { this.remove(); } else { this.commit(); } } commit() { this.addToAnnotationStorage(); } addToAnnotationStorage() { this._uiManager.addToAnnotationStorage(this); } setAt(x, y, tx, ty) { const [width, height] = this.parentDimensions; [tx, ty] = this.screenToPageTranslation(tx, ty); this.x = (x + tx) / width; this.y = (y + ty) / height; this.fixAndSetPosition(); } #translate([width, height], x, y) { [x, y] = this.screenToPageTranslation(x, y); this.x += x / width; this.y += y / height; this.fixAndSetPosition(); } translate(x, y) { this.#translate(this.parentDimensions, x, y); } translateInPage(x, y) { this.#initialPosition ||= [this.x, this.y]; this.#translate(this.pageDimensions, x, y); this.div.scrollIntoView({ block: "nearest" }); } drag(tx, ty) { this.#initialPosition ||= [this.x, this.y]; const [parentWidth, parentHeight] = this.parentDimensions; this.x += tx / parentWidth; this.y += ty / parentHeight; if (this.parent && (this.x < 0 || this.x > 1 || this.y < 0 || this.y > 1)) { const { x, y } = this.div.getBoundingClientRect(); if (this.parent.findNewParent(this, x, y)) { this.x -= Math.floor(this.x); this.y -= Math.floor(this.y); } } let { x, y } = this; const [bx, by] = this.getBaseTranslation(); x += bx; y += by; this.div.style.left = `${(100 * x).toFixed(2)}%`; this.div.style.top = `${(100 * y).toFixed(2)}%`; this.div.scrollIntoView({ block: "nearest" }); } get _hasBeenMoved() { return !!this.#initialPosition && (this.#initialPosition[0] !== this.x || this.#initialPosition[1] !== this.y); } getBaseTranslation() { const [parentWidth, parentHeight] = this.parentDimensions; const { _borderLineWidth } = AnnotationEditor; const x = _borderLineWidth / parentWidth; const y = _borderLineWidth / parentHeight; switch (this.rotation) { case 90: return [-x, y]; case 180: return [x, y]; case 270: return [x, -y]; default: return [-x, -y]; } } get _mustFixPosition() { return true; } fixAndSetPosition(rotation = this.rotation) { const [pageWidth, pageHeight] = this.pageDimensions; let { x, y, width, height } = this; width *= pageWidth; height *= pageHeight; x *= pageWidth; y *= pageHeight; if (this._mustFixPosition) { switch (rotation) { case 0: x = Math.max(0, Math.min(pageWidth - width, x)); y = Math.max(0, Math.min(pageHeight - height, y)); break; case 90: x = Math.max(0, Math.min(pageWidth - height, x)); y = Math.min(pageHeight, Math.max(width, y)); break; case 180: x = Math.min(pageWidth, Math.max(width, x)); y = Math.min(pageHeight, Math.max(height, y)); break; case 270: x = Math.min(pageWidth, Math.max(height, x)); y = Math.max(0, Math.min(pageHeight - width, y)); break; } } this.x = x /= pageWidth; this.y = y /= pageHeight; const [bx, by] = this.getBaseTranslation(); x += bx; y += by; const { style } = this.div; style.left = `${(100 * x).toFixed(2)}%`; style.top = `${(100 * y).toFixed(2)}%`; this.moveInDOM(); } static #rotatePoint(x, y, angle) { switch (angle) { case 90: return [y, -x]; case 180: return [-x, -y]; case 270: return [-y, x]; default: return [x, y]; } } screenToPageTranslation(x, y) { return AnnotationEditor.#rotatePoint(x, y, this.parentRotation); } pageTranslationToScreen(x, y) { return AnnotationEditor.#rotatePoint(x, y, 360 - this.parentRotation); } #getRotationMatrix(rotation) { switch (rotation) { case 90: { const [pageWidth, pageHeight] = this.pageDimensions; return [0, -pageWidth / pageHeight, pageHeight / pageWidth, 0]; } case 180: return [-1, 0, 0, -1]; case 270: { const [pageWidth, pageHeight] = this.pageDimensions; return [0, pageWidth / pageHeight, -pageHeight / pageWidth, 0]; } default: return [1, 0, 0, 1]; } } get parentScale() { return this._uiManager.viewParameters.realScale; } get parentRotation() { return (this._uiManager.viewParameters.rotation + this.pageRotation) % 360; } get parentDimensions() { const { parentScale, pageDimensions: [pageWidth, pageHeight] } = this; const scaledWidth = pageWidth * parentScale; const scaledHeight = pageHeight * parentScale; return util_FeatureTest.isCSSRoundSupported ? [Math.round(scaledWidth), Math.round(scaledHeight)] : [scaledWidth, scaledHeight]; } setDims(width, height) { const [parentWidth, parentHeight] = this.parentDimensions; this.div.style.width = `${(100 * width / parentWidth).toFixed(2)}%`; if (!this.#keepAspectRatio) { this.div.style.height = `${(100 * height / parentHeight).toFixed(2)}%`; } } fixDims() { const { style } = this.div; const { height, width } = style; const widthPercent = width.endsWith("%"); const heightPercent = !this.#keepAspectRatio && height.endsWith("%"); if (widthPercent && heightPercent) { return; } const [parentWidth, parentHeight] = this.parentDimensions; if (!widthPercent) { style.width = `${(100 * parseFloat(width) / parentWidth).toFixed(2)}%`; } if (!this.#keepAspectRatio && !heightPercent) { style.height = `${(100 * parseFloat(height) / parentHeight).toFixed(2)}%`; } } getInitialTranslation() { return [0, 0]; } #createResizers() { if (this.#resizersDiv) { return; } this.#resizersDiv = document.createElement("div"); this.#resizersDiv.classList.add("resizers"); const classes = this._willKeepAspectRatio ? ["topLeft", "topRight", "bottomRight", "bottomLeft"] : ["topLeft", "topMiddle", "topRight", "middleRight", "bottomRight", "bottomMiddle", "bottomLeft", "middleLeft"]; for (const name of classes) { const div = document.createElement("div"); this.#resizersDiv.append(div); div.classList.add("resizer", name); div.setAttribute("data-resizer-name", name); div.addEventListener("pointerdown", this.#resizerPointerdown.bind(this, name)); div.addEventListener("contextmenu", noContextMenu); div.tabIndex = -1; } this.div.prepend(this.#resizersDiv); } #resizerPointerdown(name, event) { event.preventDefault(); const { isMac } = util_FeatureTest.platform; if (event.button !== 0 || event.ctrlKey && isMac) { return; } this.#altText?.toggle(false); const boundResizerPointermove = this.#resizerPointermove.bind(this, name); const savedDraggable = this._isDraggable; this._isDraggable = false; const pointerMoveOptions = { passive: true, capture: true }; this.parent.togglePointerEvents(false); window.addEventListener("pointermove", boundResizerPointermove, pointerMoveOptions); window.addEventListener("contextmenu", noContextMenu); const savedX = this.x; const savedY = this.y; const savedWidth = this.width; const savedHeight = this.height; const savedParentCursor = this.parent.div.style.cursor; const savedCursor = this.div.style.cursor; this.div.style.cursor = this.parent.div.style.cursor = window.getComputedStyle(event.target).cursor; const pointerUpCallback = () => { this.parent.togglePointerEvents(true); this.#altText?.toggle(true); this._isDraggable = savedDraggable; window.removeEventListener("pointerup", pointerUpCallback); window.removeEventListener("blur", pointerUpCallback); window.removeEventListener("pointermove", boundResizerPointermove, pointerMoveOptions); window.removeEventListener("contextmenu", noContextMenu); this.parent.div.style.cursor = savedParentCursor; this.div.style.cursor = savedCursor; this.#addResizeToUndoStack(savedX, savedY, savedWidth, savedHeight); }; window.addEventListener("pointerup", pointerUpCallback); window.addEventListener("blur", pointerUpCallback); } #addResizeToUndoStack(savedX, savedY, savedWidth, savedHeight) { const newX = this.x; const newY = this.y; const newWidth = this.width; const newHeight = this.height; if (newX === savedX && newY === savedY && newWidth === savedWidth && newHeight === savedHeight) { return; } this.addCommands({ cmd: () => { this.width = newWidth; this.height = newHeight; this.x = newX; this.y = newY; const [parentWidth, parentHeight] = this.parentDimensions; this.setDims(parentWidth * newWidth, parentHeight * newHeight); this.fixAndSetPosition(); }, undo: () => { this.width = savedWidth; this.height = savedHeight; this.x = savedX; this.y = savedY; const [parentWidth, parentHeight] = this.parentDimensions; this.setDims(parentWidth * savedWidth, parentHeight * savedHeight); this.fixAndSetPosition(); }, mustExec: true }); } #resizerPointermove(name, event) { const [parentWidth, parentHeight] = this.parentDimensions; const savedX = this.x; const savedY = this.y; const savedWidth = this.width; const savedHeight = this.height; const minWidth = AnnotationEditor.MIN_SIZE / parentWidth; const minHeight = AnnotationEditor.MIN_SIZE / parentHeight; const round = x => Math.round(x * 10000) / 10000; const rotationMatrix = this.#getRotationMatrix(this.rotation); const transf = (x, y) => [rotationMatrix[0] * x + rotationMatrix[2] * y, rotationMatrix[1] * x + rotationMatrix[3] * y]; const invRotationMatrix = this.#getRotationMatrix(360 - this.rotation); const invTransf = (x, y) => [invRotationMatrix[0] * x + invRotationMatrix[2] * y, invRotationMatrix[1] * x + invRotationMatrix[3] * y]; let getPoint; let getOpposite; let isDiagonal = false; let isHorizontal = false; switch (name) { case "topLeft": isDiagonal = true; getPoint = (w, h) => [0, 0]; getOpposite = (w, h) => [w, h]; break; case "topMiddle": getPoint = (w, h) => [w / 2, 0]; getOpposite = (w, h) => [w / 2, h]; break; case "topRight": isDiagonal = true; getPoint = (w, h) => [w, 0]; getOpposite = (w, h) => [0, h]; break; case "middleRight": isHorizontal = true; getPoint = (w, h) => [w, h / 2]; getOpposite = (w, h) => [0, h / 2]; break; case "bottomRight": isDiagonal = true; getPoint = (w, h) => [w, h]; getOpposite = (w, h) => [0, 0]; break; case "bottomMiddle": getPoint = (w, h) => [w / 2, h]; getOpposite = (w, h) => [w / 2, 0]; break; case "bottomLeft": isDiagonal = true; getPoint = (w, h) => [0, h]; getOpposite = (w, h) => [w, 0]; break; case "middleLeft": isHorizontal = true; getPoint = (w, h) => [0, h / 2]; getOpposite = (w, h) => [w, h / 2]; break; } const point = getPoint(savedWidth, savedHeight); const oppositePoint = getOpposite(savedWidth, savedHeight); let transfOppositePoint = transf(...oppositePoint); const oppositeX = round(savedX + transfOppositePoint[0]); const oppositeY = round(savedY + transfOppositePoint[1]); let ratioX = 1; let ratioY = 1; let [deltaX, deltaY] = this.screenToPageTranslation(event.movementX, event.movementY); [deltaX, deltaY] = invTransf(deltaX / parentWidth, deltaY / parentHeight); if (isDiagonal) { const oldDiag = Math.hypot(savedWidth, savedHeight); 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); } else if (isHorizontal) { ratioX = Math.max(minWidth, Math.min(1, Math.abs(oppositePoint[0] - point[0] - deltaX))) / savedWidth; } else { ratioY = Math.max(minHeight, Math.min(1, Math.abs(oppositePoint[1] - point[1] - deltaY))) / savedHeight; } const newWidth = round(savedWidth * ratioX); const newHeight = round(savedHeight * ratioY); transfOppositePoint = transf(...getOpposite(newWidth, newHeight)); const newX = oppositeX - transfOppositePoint[0]; const newY = oppositeY - transfOppositePoint[1]; this.width = newWidth; this.height = newHeight; this.x = newX; this.y = newY; this.setDims(parentWidth * newWidth, parentHeight * newHeight); this.fixAndSetPosition(); } altTextFinish() { this.#altText?.finish(); } async addEditToolbar() { if (this.#editToolbar || this.#isInEditMode) { return this.#editToolbar; } this.#editToolbar = new EditorToolbar(this); this.div.append(this.#editToolbar.render()); if (this.#altText) { this.#editToolbar.addAltTextButton(await this.#altText.render()); } return this.#editToolbar; } removeEditToolbar() { if (!this.#editToolbar) { return; } this.#editToolbar.remove(); this.#editToolbar = null; this.#altText?.destroy(); } getClientDimensions() { return this.div.getBoundingClientRect(); } async addAltTextButton() { if (this.#altText) { return; } AltText.initialize(AnnotationEditor._l10nPromise); this.#altText = new AltText(this); await this.addEditToolbar(); } get altTextData() { return this.#altText?.data; } set altTextData(data) { if (!this.#altText) { return; } this.#altText.data = data; } hasAltText() { return !this.#altText?.isEmpty(); } render() { this.div = document.createElement("div"); this.div.setAttribute("data-editor-rotation", (360 - this.rotation) % 360); this.div.className = this.name; this.div.setAttribute("id", this.id); this.div.tabIndex = this.#disabled ? -1 : 0; if (!this._isVisible) { this.div.classList.add("hidden"); } this.setInForeground(); this.div.addEventListener("focusin", this.#boundFocusin); this.div.addEventListener("focusout", this.#boundFocusout); const [parentWidth, parentHeight] = this.parentDimensions; if (this.parentRotation % 180 !== 0) { this.div.style.maxWidth = `${(100 * parentHeight / parentWidth).toFixed(2)}%`; this.div.style.maxHeight = `${(100 * parentWidth / parentHeight).toFixed(2)}%`; } const [tx, ty] = this.getInitialTranslation(); this.translate(tx, ty); bindEvents(this, this.div, ["pointerdown"]); return this.div; } pointerdown(event) { const { isMac } = util_FeatureTest.platform; if (event.button !== 0 || event.ctrlKey && isMac) { event.preventDefault(); return; } this.#hasBeenClicked = true; if (this._isDraggable) { this.#setUpDragSession(event); return; } this.#selectOnPointerEvent(event); } #selectOnPointerEvent(event) { const { isMac } = util_FeatureTest.platform; if (event.ctrlKey && !isMac || event.shiftKey || event.metaKey && isMac) { this.parent.toggleSelected(this); } else { this.parent.setSelected(this); } } #setUpDragSession(event) { const isSelected = this._uiManager.isSelected(this); this._uiManager.setUpDragSession(); let pointerMoveOptions, pointerMoveCallback; if (isSelected) { this.div.classList.add("moving"); pointerMoveOptions = { passive: true, capture: true }; this.#prevDragX = event.clientX; this.#prevDragY = event.clientY; pointerMoveCallback = e => { const { clientX: x, clientY: y } = e; const [tx, ty] = this.screenToPageTranslation(x - this.#prevDragX, y - this.#prevDragY); this.#prevDragX = x; this.#prevDragY = y; this._uiManager.dragSelectedEditors(tx, ty); }; window.addEventListener("pointermove", pointerMoveCallback, pointerMoveOptions); } const pointerUpCallback = () => { window.removeEventListener("pointerup", pointerUpCallback); window.removeEventListener("blur", pointerUpCallback); if (isSelected) { this.div.classList.remove("moving"); window.removeEventListener("pointermove", pointerMoveCallback, pointerMoveOptions); } this.#hasBeenClicked = false; if (!this._uiManager.endDragSession()) { this.#selectOnPointerEvent(event); } }; window.addEventListener("pointerup", pointerUpCallback); window.addEventListener("blur", pointerUpCallback); } moveInDOM() { if (this.#moveInDOMTimeout) { clearTimeout(this.#moveInDOMTimeout); } this.#moveInDOMTimeout = setTimeout(() => { this.#moveInDOMTimeout = null; this.parent?.moveEditorInDOM(this); }, 0); } _setParentAndPosition(parent, x, y) { parent.changeParent(this); this.x = x; this.y = y; this.fixAndSetPosition(); } getRect(tx, ty, rotation = this.rotation) { const scale = this.parentScale; const [pageWidth, pageHeight] = this.pageDimensions; const [pageX, pageY] = this.pageTranslation; const shiftX = tx / scale; const shiftY = ty / scale; const x = this.x * pageWidth; const y = this.y * pageHeight; const width = this.width * pageWidth; const height = this.height * pageHeight; switch (rotation) { case 0: return [x + shiftX + pageX, pageHeight - y - shiftY - height + pageY, x + shiftX + width + pageX, pageHeight - y - shiftY + pageY]; case 90: return [x + shiftY + pageX, pageHeight - y + shiftX + pageY, x + shiftY + height + pageX, pageHeight - y + shiftX + width + pageY]; case 180: return [x - shiftX - width + pageX, pageHeight - y + shiftY + pageY, x - shiftX + pageX, pageHeight - y + shiftY + height + pageY]; case 270: return [x - shiftY - height + pageX, pageHeight - y - shiftX - width + pageY, x - shiftY + pageX, pageHeight - y - shiftX + pageY]; default: throw new Error("Invalid rotation"); } } getRectInCurrentCoords(rect, pageHeight) { const [x1, y1, x2, y2] = rect; const width = x2 - x1; const height = y2 - y1; switch (this.rotation) { case 0: return [x1, pageHeight - y2, width, height]; case 90: return [x1, pageHeight - y1, height, width]; case 180: return [x2, pageHeight - y1, width, height]; case 270: return [x2, pageHeight - y2, height, width]; default: throw new Error("Invalid rotation"); } } onceAdded() {} isEmpty() { return false; } enableEditMode() { this.#isInEditMode = true; } disableEditMode() { this.#isInEditMode = false; } isInEditMode() { return this.#isInEditMode; } shouldGetKeyboardEvents() { return this.#isResizerEnabledForKeyboard; } needsToBeRebuilt() { return this.div && !this.isAttachedToDOM; } rebuild() { this.div?.addEventListener("focusin", this.#boundFocusin); this.div?.addEventListener("focusout", this.#boundFocusout); } rotate(_angle) {} serialize(isForCopying = false, context = null) { unreachable("An editor must be serializable"); } static deserialize(data, parent, uiManager) { const editor = new this.prototype.constructor({ parent, id: parent.getNextId(), uiManager }); editor.rotation = data.rotation; const [pageWidth, pageHeight] = editor.pageDimensions; const [x, y, width, height] = editor.getRectInCurrentCoords(data.rect, pageHeight); editor.x = x / pageWidth; editor.y = y / pageHeight; editor.width = width / pageWidth; editor.height = height / pageHeight; return editor; } get hasBeenModified() { return !!this.annotationElementId && (this.deleted || this.serialize() !== null); } remove() { this.div.removeEventListener("focusin", this.#boundFocusin); this.div.removeEventListener("focusout", this.#boundFocusout); if (!this.isEmpty()) { this.commit(); } if (this.parent) { this.parent.remove(this); } else { this._uiManager.removeEditor(this); } if (this.#moveInDOMTimeout) { clearTimeout(this.#moveInDOMTimeout); this.#moveInDOMTimeout = null; } this.#stopResizing(); this.removeEditToolbar(); if (this.#telemetryTimeouts) { for (const timeout of this.#telemetryTimeouts.values()) { clearTimeout(timeout); } this.#telemetryTimeouts = null; } this.parent = null; } get isResizable() { return false; } makeResizable() { if (this.isResizable) { this.#createResizers(); this.#resizersDiv.classList.remove("hidden"); bindEvents(this, this.div, ["keydown"]); } } get toolbarPosition() { return null; } keydown(event) { if (!this.isResizable || event.target !== this.div || event.key !== "Enter") { return; } this._uiManager.setSelected(this); this.#savedDimensions = { savedX: this.x, savedY: this.y, savedWidth: this.width, savedHeight: this.height }; const children = this.#resizersDiv.children; if (!this.#allResizerDivs) { this.#allResizerDivs = Array.from(children); const boundResizerKeydown = this.#resizerKeydown.bind(this); const boundResizerBlur = this.#resizerBlur.bind(this); for (const div of this.#allResizerDivs) { const name = div.getAttribute("data-resizer-name"); div.setAttribute("role", "spinbutton"); div.addEventListener("keydown", boundResizerKeydown); div.addEventListener("blur", boundResizerBlur); div.addEventListener("focus", this.#resizerFocus.bind(this, name)); AnnotationEditor._l10nPromise.get(`pdfjs-editor-resizer-label-${name}`).then(msg => div.setAttribute("aria-label", msg)); } } const first = this.#allResizerDivs[0]; let firstPosition = 0; for (const div of children) { if (div === first) { break; } firstPosition++; } const nextFirstPosition = (360 - this.rotation + this.parentRotation) % 360 / 90 * (this.#allResizerDivs.length / 4); if (nextFirstPosition !== firstPosition) { if (nextFirstPosition < firstPosition) { for (let i = 0; i < firstPosition - nextFirstPosition; i++) { this.#resizersDiv.append(this.#resizersDiv.firstChild); } } else if (nextFirstPosition > firstPosition) { for (let i = 0; i < nextFirstPosition - firstPosition; i++) { this.#resizersDiv.firstChild.before(this.#resizersDiv.lastChild); } } let i = 0; for (const child of children) { const div = this.#allResizerDivs[i++]; const name = div.getAttribute("data-resizer-name"); AnnotationEditor._l10nPromise.get(`pdfjs-editor-resizer-label-${name}`).then(msg => child.setAttribute("aria-label", msg)); } } this.#setResizerTabIndex(0); this.#isResizerEnabledForKeyboard = true; this.#resizersDiv.firstChild.focus({ focusVisible: true }); event.preventDefault(); event.stopImmediatePropagation(); } #resizerKeydown(event) { AnnotationEditor._resizerKeyboardManager.exec(this, event); } #resizerBlur(event) { if (this.#isResizerEnabledForKeyboard && event.relatedTarget?.parentNode !== this.#resizersDiv) { this.#stopResizing(); } } #resizerFocus(name) { this.#focusedResizerName = this.#isResizerEnabledForKeyboard ? name : ""; } #setResizerTabIndex(value) { if (!this.#allResizerDivs) { return; } for (const div of this.#allResizerDivs) { div.tabIndex = value; } } _resizeWithKeyboard(x, y) { if (!this.#isResizerEnabledForKeyboard) { return; } this.#resizerPointermove(this.#focusedResizerName, { movementX: x, movementY: y }); } #stopResizing() { this.#isResizerEnabledForKeyboard = false; this.#setResizerTabIndex(-1); if (this.#savedDimensions) { const { savedX, savedY, savedWidth, savedHeight } = this.#savedDimensions; this.#addResizeToUndoStack(savedX, savedY, savedWidth, savedHeight); this.#savedDimensions = null; } } _stopResizingWithKeyboard() { this.#stopResizing(); this.div.focus(); } select() { this.makeResizable(); this.div?.classList.add("selectedEditor"); if (!this.#editToolbar) { this.addEditToolbar().then(() => { if (this.div?.classList.contains("selectedEditor")) { this.#editToolbar?.show(); } }); return; } this.#editToolbar?.show(); } unselect() { this.#resizersDiv?.classList.add("hidden"); this.div?.classList.remove("selectedEditor"); if (this.div?.contains(document.activeElement)) { this._uiManager.currentLayer.div.focus({ preventScroll: true }); } this.#editToolbar?.hide(); } updateParams(type, value) {} disableEditing() {} enableEditing() {} enterInEditMode() {} getImageForAltText() { return null; } get contentDiv() { return this.div; } get isEditing() { return this.#isEditing; } set isEditing(value) { this.#isEditing = value; if (!this.parent) { return; } if (value) { this.parent.setSelected(this); this.parent.setActiveEditor(this); } else { this.parent.setActiveEditor(null); } } setAspectRatio(width, height) { this.#keepAspectRatio = true; const aspectRatio = width / height; const { style } = this.div; style.aspectRatio = aspectRatio; style.height = "auto"; } static get MIN_SIZE() { return 16; } static canCreateNewEmptyEditor() { return true; } get telemetryInitialData() { return { action: "added" }; } get telemetryFinalData() { return null; } _reportTelemetry(data, mustWait = false) { if (mustWait) { this.#telemetryTimeouts ||= new Map(); const { action } = data; let timeout = this.#telemetryTimeouts.get(action); if (timeout) { clearTimeout(timeout); } timeout = setTimeout(() => { this._reportTelemetry(data); this.#telemetryTimeouts.delete(action); if (this.#telemetryTimeouts.size === 0) { this.#telemetryTimeouts = null; } }, AnnotationEditor._telemetryTimeout); this.#telemetryTimeouts.set(action, timeout); return; } data.type ||= this.editorType; this._uiManager._eventBus.dispatch("reporttelemetry", { source: this, details: { type: "editing", data } }); } show(visible = this._isVisible) { this.div.classList.toggle("hidden", !visible); this._isVisible = visible; } enable() { if (this.div) { this.div.tabIndex = 0; } this.#disabled = false; } disable() { if (this.div) { this.div.tabIndex = -1; } this.#disabled = true; } renderAnnotationElement(annotation) { let content = annotation.container.querySelector(".annotationContent"); if (!content) { content = document.createElement("div"); content.classList.add("annotationContent", this.editorType); annotation.container.prepend(content); } else if (content.nodeName === "CANVAS") { const canvas = content; content = document.createElement("div"); content.classList.add("annotationContent", this.editorType); canvas.before(content); } return content; } resetAnnotationElement(annotation) { const { firstChild } = annotation.container; if (firstChild.nodeName === "DIV" && firstChild.classList.contains("annotationContent")) { firstChild.remove(); } } } class FakeEditor extends AnnotationEditor { constructor(params) { super(params); this.annotationElementId = params.annotationElementId; this.deleted = true; } serialize() { return { id: this.annotationElementId, deleted: true, pageIndex: this.pageIndex }; } } ;// CONCATENATED MODULE: ./src/shared/murmurhash3.js const SEED = 0xc3d2e1f0; const MASK_HIGH = 0xffff0000; const MASK_LOW = 0xffff; class MurmurHash3_64 { constructor(seed) { this.h1 = seed ? seed & 0xffffffff : SEED; this.h2 = seed ? seed & 0xffffffff : SEED; } update(input) { let data, length; if (typeof input === "string") { data = new Uint8Array(input.length * 2); length = 0; for (let i = 0, ii = input.length; i < ii; i++) { const code = input.charCodeAt(i); if (code <= 0xff) { data[length++] = code; } else { data[length++] = code >>> 8; data[length++] = code & 0xff; } } } else if (ArrayBuffer.isView(input)) { data = input.slice(); length = data.byteLength; } else { throw new Error("Invalid data format, must be a string or TypedArray."); } const blockCounts = length >> 2; const tailLength = length - blockCounts * 4; const dataUint32 = new Uint32Array(data.buffer, 0, blockCounts); let k1 = 0, k2 = 0; let h1 = this.h1, h2 = this.h2; const C1 = 0xcc9e2d51, C2 = 0x1b873593; const C1_LOW = C1 & MASK_LOW, C2_LOW = C2 & MASK_LOW; for (let i = 0; i < blockCounts; i++) { if (i & 1) { k1 = dataUint32[i]; k1 = k1 * C1 & MASK_HIGH | k1 * C1_LOW & MASK_LOW; k1 = k1 << 15 | k1 >>> 17; k1 = k1 * C2 & MASK_HIGH | k1 * C2_LOW & MASK_LOW; h1 ^= k1; h1 = h1 << 13 | h1 >>> 19; h1 = h1 * 5 + 0xe6546b64; } else { k2 = dataUint32[i]; k2 = k2 * C1 & MASK_HIGH | k2 * C1_LOW & MASK_LOW; k2 = k2 << 15 | k2 >>> 17; k2 = k2 * C2 & MASK_HIGH | k2 * C2_LOW & MASK_LOW; h2 ^= k2; h2 = h2 << 13 | h2 >>> 19; h2 = h2 * 5 + 0xe6546b64; } } k1 = 0; switch (tailLength) { case 3: k1 ^= data[blockCounts * 4 + 2] << 16; case 2: k1 ^= data[blockCounts * 4 + 1] << 8; case 1: k1 ^= data[blockCounts * 4]; k1 = k1 * C1 & MASK_HIGH | k1 * C1_LOW & MASK_LOW; k1 = k1 << 15 | k1 >>> 17; k1 = k1 * C2 & MASK_HIGH | k1 * C2_LOW & MASK_LOW; if (blockCounts & 1) { h1 ^= k1; } else { h2 ^= k1; } } this.h1 = h1; this.h2 = h2; } hexdigest() { let h1 = this.h1, h2 = this.h2; h1 ^= h2 >>> 1; h1 = h1 * 0xed558ccd & MASK_HIGH | h1 * 0x8ccd & MASK_LOW; h2 = h2 * 0xff51afd7 & MASK_HIGH | ((h2 << 16 | h1 >>> 16) * 0xafd7ed55 & MASK_HIGH) >>> 16; h1 ^= h2 >>> 1; h1 = h1 * 0x1a85ec53 & MASK_HIGH | h1 * 0xec53 & MASK_LOW; h2 = h2 * 0xc4ceb9fe & MASK_HIGH | ((h2 << 16 | h1 >>> 16) * 0xb9fe1a85 & MASK_HIGH) >>> 16; h1 ^= h2 >>> 1; return (h1 >>> 0).toString(16).padStart(8, "0") + (h2 >>> 0).toString(16).padStart(8, "0"); } } ;// CONCATENATED MODULE: ./src/display/annotation_storage.js const SerializableEmpty = Object.freeze({ map: null, hash: "", transfer: undefined }); class AnnotationStorage { #modified = false; #storage = new Map(); constructor() { this.onSetModified = null; this.onResetModified = null; this.onAnnotationEditor = null; } getValue(key, defaultValue) { const value = this.#storage.get(key); if (value === undefined) { return defaultValue; } return Object.assign(defaultValue, value); } getRawValue(key) { return this.#storage.get(key); } remove(key) { this.#storage.delete(key); if (this.#storage.size === 0) { this.resetModified(); } if (typeof this.onAnnotationEditor === "function") { for (const value of this.#storage.values()) { if (value instanceof AnnotationEditor) { return; } } this.onAnnotationEditor(null); } } setValue(key, value) { const obj = this.#storage.get(key); let modified = false; if (obj !== undefined) { for (const [entry, val] of Object.entries(value)) { if (obj[entry] !== val) { modified = true; obj[entry] = val; } } } else { modified = true; this.#storage.set(key, value); } if (modified) { this.#setModified(); } if (value instanceof AnnotationEditor && typeof this.onAnnotationEditor === "function") { this.onAnnotationEditor(value.constructor._type); } } has(key) { return this.#storage.has(key); } getAll() { return this.#storage.size > 0 ? objectFromMap(this.#storage) : null; } setAll(obj) { for (const [key, val] of Object.entries(obj)) { this.setValue(key, val); } } get size() { return this.#storage.size; } #setModified() { if (!this.#modified) { this.#modified = true; if (typeof this.onSetModified === "function") { this.onSetModified(); } } } resetModified() { if (this.#modified) { this.#modified = false; if (typeof this.onResetModified === "function") { this.onResetModified(); } } } get print() { return new PrintAnnotationStorage(this); } get serializable() { if (this.#storage.size === 0) { return SerializableEmpty; } const map = new Map(), hash = new MurmurHash3_64(), transfer = []; const context = Object.create(null); let hasBitmap = false; for (const [key, val] of this.#storage) { const serialized = val instanceof AnnotationEditor ? val.serialize(false, context) : val; if (serialized) { map.set(key, serialized); hash.update(`${key}:${JSON.stringify(serialized)}`); hasBitmap ||= !!serialized.bitmap; } } if (hasBitmap) { for (const value of map.values()) { if (value.bitmap) { transfer.push(value.bitmap); } } } return map.size > 0 ? { map, hash: hash.hexdigest(), transfer } : SerializableEmpty; } get editorStats() { let stats = null; const typeToEditor = new Map(); for (const value of this.#storage.values()) { if (!(value instanceof AnnotationEditor)) { continue; } const editorStats = value.telemetryFinalData; if (!editorStats) { continue; } const { type } = editorStats; if (!typeToEditor.has(type)) { typeToEditor.set(type, Object.getPrototypeOf(value).constructor); } stats ||= Object.create(null); const map = stats[type] ||= new Map(); for (const [key, val] of Object.entries(editorStats)) { if (key === "type") { continue; } let counters = map.get(key); if (!counters) { counters = new Map(); map.set(key, counters); } const count = counters.get(val) ?? 0; counters.set(val, count + 1); } } for (const [type, editor] of typeToEditor) { stats[type] = editor.computeTelemetryFinalData(stats[type]); } return stats; } } class PrintAnnotationStorage extends AnnotationStorage { #serializable; constructor(parent) { super(); const { map, hash, transfer } = parent.serializable; const clone = structuredClone(map, transfer ? { transfer } : null); this.#serializable = { map: clone, hash, transfer }; } get print() { unreachable("Should not call PrintAnnotationStorage.print"); } get serializable() { return this.#serializable; } } ;// CONCATENATED MODULE: ./src/display/font_loader.js class FontLoader { #systemFonts = new Set(); constructor({ ownerDocument = globalThis.document, styleElement = null }) { this._document = ownerDocument; this.nativeFontFaces = new Set(); this.styleElement = null; } addNativeFontFace(nativeFontFace) { this.nativeFontFaces.add(nativeFontFace); this._document.fonts.add(nativeFontFace); } removeNativeFontFace(nativeFontFace) { this.nativeFontFaces.delete(nativeFontFace); this._document.fonts.delete(nativeFontFace); } insertRule(rule) { if (!this.styleElement) { this.styleElement = this._document.createElement("style"); this._document.documentElement.getElementsByTagName("head")[0].append(this.styleElement); } const styleSheet = this.styleElement.sheet; styleSheet.insertRule(rule, styleSheet.cssRules.length); } clear() { for (const nativeFontFace of this.nativeFontFaces) { this._document.fonts.delete(nativeFontFace); } this.nativeFontFaces.clear(); this.#systemFonts.clear(); if (this.styleElement) { this.styleElement.remove(); this.styleElement = null; } } async loadSystemFont({ systemFontInfo: info, _inspectFont }) { if (!info || this.#systemFonts.has(info.loadedName)) { return; } assert(!this.disableFontFace, "loadSystemFont shouldn't be called when `disableFontFace` is set."); if (this.isFontLoadingAPISupported) { const { loadedName, src, style } = info; const fontFace = new FontFace(loadedName, src, style); this.addNativeFontFace(fontFace); try { await fontFace.load(); this.#systemFonts.add(loadedName); _inspectFont?.(info); } catch { warn(`Cannot load system font: ${info.baseFontName}, installing it could help to improve PDF rendering.`); this.removeNativeFontFace(fontFace); } return; } unreachable("Not implemented: loadSystemFont without the Font Loading API."); } async bind(font) { if (font.attached || font.missingFile && !font.systemFontInfo) { return; } font.attached = true; if (font.systemFontInfo) { await this.loadSystemFont(font); return; } if (this.isFontLoadingAPISupported) { const nativeFontFace = font.createNativeFontFace(); if (nativeFontFace) { this.addNativeFontFace(nativeFontFace); try { await nativeFontFace.loaded; } catch (ex) { warn(`Failed to load font '${nativeFontFace.family}': '${ex}'.`); font.disableFontFace = true; throw ex; } } return; } const rule = font.createFontFaceRule(); if (rule) { this.insertRule(rule); if (this.isSyncFontLoadingSupported) { return; } throw new Error("Not implemented: async font loading"); } } get isFontLoadingAPISupported() { const hasFonts = !!this._document?.fonts; return shadow(this, "isFontLoadingAPISupported", hasFonts); } get isSyncFontLoadingSupported() { return shadow(this, "isSyncFontLoadingSupported", true); } _queueLoadingCallback(callback) { throw new Error("Not implemented: _queueLoadingCallback"); } get _loadTestFont() { throw new Error("Not implemented: _loadTestFont"); } _prepareFontLoadEvent(font, request) { throw new Error("Not implemented: _prepareFontLoadEvent"); } } class FontFaceObject { constructor(translatedData, { disableFontFace = false, ignoreErrors = false, inspectFont = null }) { this.compiledGlyphs = Object.create(null); for (const i in translatedData) { this[i] = translatedData[i]; } this.disableFontFace = disableFontFace === true; this.ignoreErrors = ignoreErrors === true; this._inspectFont = inspectFont; } createNativeFontFace() { if (!this.data || this.disableFontFace) { return null; } let nativeFontFace; if (!this.cssFontInfo) { nativeFontFace = new FontFace(this.loadedName, this.data, {}); } else { const css = { weight: this.cssFontInfo.fontWeight }; if (this.cssFontInfo.italicAngle) { css.style = `oblique ${this.cssFontInfo.italicAngle}deg`; } nativeFontFace = new FontFace(this.cssFontInfo.fontFamily, this.data, css); } this._inspectFont?.(this); return nativeFontFace; } createFontFaceRule() { if (!this.data || this.disableFontFace) { return null; } const data = bytesToString(this.data); const url = `url(data:${this.mimetype};base64,${btoa(data)});`; let rule; if (!this.cssFontInfo) { rule = `@font-face {font-family:"${this.loadedName}";src:${url}}`; } else { let css = `font-weight: ${this.cssFontInfo.fontWeight};`; if (this.cssFontInfo.italicAngle) { css += `font-style: oblique ${this.cssFontInfo.italicAngle}deg;`; } rule = `@font-face {font-family:"${this.cssFontInfo.fontFamily}";${css}src:${url}}`; } this._inspectFont?.(this, url); return rule; } getPathGenerator(objs, character) { if (this.compiledGlyphs[character] !== undefined) { return this.compiledGlyphs[character]; } let cmds; try { cmds = objs.get(this.loadedName + "_path_" + character); } catch (ex) { if (!this.ignoreErrors) { throw ex; } warn(`getPathGenerator - ignoring character: "${ex}".`); } if (!Array.isArray(cmds) || cmds.length === 0) { return this.compiledGlyphs[character] = function (c, size) {}; } const commands = []; for (let i = 0, ii = cmds.length; i < ii;) { switch (cmds[i++]) { case FontRenderOps.BEZIER_CURVE_TO: { const [a, b, c, d, e, f] = cmds.slice(i, i + 6); commands.push(ctx => ctx.bezierCurveTo(a, b, c, d, e, f)); i += 6; } break; case FontRenderOps.MOVE_TO: { const [a, b] = cmds.slice(i, i + 2); commands.push(ctx => ctx.moveTo(a, b)); i += 2; } break; case FontRenderOps.LINE_TO: { const [a, b] = cmds.slice(i, i + 2); commands.push(ctx => ctx.lineTo(a, b)); i += 2; } break; case FontRenderOps.QUADRATIC_CURVE_TO: { const [a, b, c, d] = cmds.slice(i, i + 4); commands.push(ctx => ctx.quadraticCurveTo(a, b, c, d)); i += 4; } break; case FontRenderOps.RESTORE: commands.push(ctx => ctx.restore()); break; case FontRenderOps.SAVE: commands.push(ctx => ctx.save()); break; case FontRenderOps.SCALE: assert(commands.length === 2, "Scale command is only valid at the third position."); break; case FontRenderOps.TRANSFORM: { const [a, b, c, d, e, f] = cmds.slice(i, i + 6); commands.push(ctx => ctx.transform(a, b, c, d, e, f)); i += 6; } break; case FontRenderOps.TRANSLATE: { const [a, b] = cmds.slice(i, i + 2); commands.push(ctx => ctx.translate(a, b)); i += 2; } break; } } return this.compiledGlyphs[character] = function glyphDrawer(ctx, size) { commands[0](ctx); commands[1](ctx); ctx.scale(size, -size); for (let i = 2, ii = commands.length; i < ii; i++) { commands[i](ctx); } }; } } ;// CONCATENATED MODULE: ./src/display/pattern_helper.js const PathType = { FILL: "Fill", STROKE: "Stroke", SHADING: "Shading" }; function applyBoundingBox(ctx, bbox) { if (!bbox) { return; } const width = bbox[2] - bbox[0]; const height = bbox[3] - bbox[1]; const region = new Path2D(); region.rect(bbox[0], bbox[1], width, height); ctx.clip(region); } class BaseShadingPattern { constructor() { if (this.constructor === BaseShadingPattern) { unreachable("Cannot initialize BaseShadingPattern."); } } getPattern() { unreachable("Abstract method `getPattern` called."); } } class RadialAxialShadingPattern extends BaseShadingPattern { constructor(IR) { super(); this._type = IR[1]; this._bbox = IR[2]; this._colorStops = IR[3]; this._p0 = IR[4]; this._p1 = IR[5]; this._r0 = IR[6]; this._r1 = IR[7]; this.matrix = null; } _createGradient(ctx) { let grad; if (this._type === "axial") { grad = ctx.createLinearGradient(this._p0[0], this._p0[1], this._p1[0], this._p1[1]); } else if (this._type === "radial") { grad = ctx.createRadialGradient(this._p0[0], this._p0[1], this._r0, this._p1[0], this._p1[1], this._r1); } for (const colorStop of this._colorStops) { grad.addColorStop(colorStop[0], colorStop[1]); } return grad; } getPattern(ctx, owner, inverse, pathType) { let pattern; if (pathType === PathType.STROKE || pathType === PathType.FILL) { const ownerBBox = owner.current.getClippedPathBoundingBox(pathType, getCurrentTransform(ctx)) || [0, 0, 0, 0]; const width = Math.ceil(ownerBBox[2] - ownerBBox[0]) || 1; const height = Math.ceil(ownerBBox[3] - ownerBBox[1]) || 1; const tmpCanvas = owner.cachedCanvases.getCanvas("pattern", width, height, true); const tmpCtx = tmpCanvas.context; tmpCtx.clearRect(0, 0, tmpCtx.canvas.width, tmpCtx.canvas.height); tmpCtx.beginPath(); tmpCtx.rect(0, 0, tmpCtx.canvas.width, tmpCtx.canvas.height); tmpCtx.translate(-ownerBBox[0], -ownerBBox[1]); inverse = Util.transform(inverse, [1, 0, 0, 1, ownerBBox[0], ownerBBox[1]]); tmpCtx.transform(...owner.baseTransform); if (this.matrix) { tmpCtx.transform(...this.matrix); } applyBoundingBox(tmpCtx, this._bbox); tmpCtx.fillStyle = this._createGradient(tmpCtx); tmpCtx.fill(); pattern = ctx.createPattern(tmpCanvas.canvas, "no-repeat"); const domMatrix = new DOMMatrix(inverse); pattern.setTransform(domMatrix); } else { applyBoundingBox(ctx, this._bbox); pattern = this._createGradient(ctx); } return pattern; } } function drawTriangle(data, context, p1, p2, p3, c1, c2, c3) { const coords = context.coords, colors = context.colors; const bytes = data.data, rowSize = data.width * 4; let tmp; if (coords[p1 + 1] > coords[p2 + 1]) { tmp = p1; p1 = p2; p2 = tmp; tmp = c1; c1 = c2; c2 = tmp; } if (coords[p2 + 1] > coords[p3 + 1]) { tmp = p2; p2 = p3; p3 = tmp; tmp = c2; c2 = c3; c3 = tmp; } if (coords[p1 + 1] > coords[p2 + 1]) { tmp = p1; p1 = p2; p2 = tmp; tmp = c1; c1 = c2; c2 = tmp; } const x1 = (coords[p1] + context.offsetX) * context.scaleX; const y1 = (coords[p1 + 1] + context.offsetY) * context.scaleY; const x2 = (coords[p2] + context.offsetX) * context.scaleX; const y2 = (coords[p2 + 1] + context.offsetY) * context.scaleY; const x3 = (coords[p3] + context.offsetX) * context.scaleX; const y3 = (coords[p3 + 1] + context.offsetY) * context.scaleY; if (y1 >= y3) { return; } const c1r = colors[c1], c1g = colors[c1 + 1], c1b = colors[c1 + 2]; const c2r = colors[c2], c2g = colors[c2 + 1], c2b = colors[c2 + 2]; const c3r = colors[c3], c3g = colors[c3 + 1], c3b = colors[c3 + 2]; const minY = Math.round(y1), maxY = Math.round(y3); let xa, car, cag, cab; let xb, cbr, cbg, cbb; for (let y = minY; y <= maxY; y++) { if (y < y2) { const k = y < y1 ? 0 : (y1 - y) / (y1 - y2); xa = x1 - (x1 - x2) * k; car = c1r - (c1r - c2r) * k; cag = c1g - (c1g - c2g) * k; cab = c1b - (c1b - c2b) * k; } else { let k; if (y > y3) { k = 1; } else if (y2 === y3) { k = 0; } else { k = (y2 - y) / (y2 - y3); } xa = x2 - (x2 - x3) * k; car = c2r - (c2r - c3r) * k; cag = c2g - (c2g - c3g) * k; cab = c2b - (c2b - c3b) * k; } let k; if (y < y1) { k = 0; } else if (y > y3) { k = 1; } else { k = (y1 - y) / (y1 - y3); } xb = x1 - (x1 - x3) * k; cbr = c1r - (c1r - c3r) * k; cbg = c1g - (c1g - c3g) * k; cbb = c1b - (c1b - c3b) * k; const x1_ = Math.round(Math.min(xa, xb)); const x2_ = Math.round(Math.max(xa, xb)); let j = rowSize * y + x1_ * 4; for (let x = x1_; x <= x2_; x++) { k = (xa - x) / (xa - xb); if (k < 0) { k = 0; } else if (k > 1) { k = 1; } bytes[j++] = car - (car - cbr) * k | 0; bytes[j++] = cag - (cag - cbg) * k | 0; bytes[j++] = cab - (cab - cbb) * k | 0; bytes[j++] = 255; } } } function drawFigure(data, figure, context) { const ps = figure.coords; const cs = figure.colors; let i, ii; switch (figure.type) { case "lattice": const verticesPerRow = figure.verticesPerRow; const rows = Math.floor(ps.length / verticesPerRow) - 1; const cols = verticesPerRow - 1; for (i = 0; i < rows; i++) { let q = i * verticesPerRow; for (let j = 0; j < cols; j++, q++) { drawTriangle(data, context, ps[q], ps[q + 1], ps[q + verticesPerRow], cs[q], cs[q + 1], cs[q + verticesPerRow]); drawTriangle(data, context, ps[q + verticesPerRow + 1], ps[q + 1], ps[q + verticesPerRow], cs[q + verticesPerRow + 1], cs[q + 1], cs[q + verticesPerRow]); } } break; case "triangles": for (i = 0, ii = ps.length; i < ii; i += 3) { drawTriangle(data, context, ps[i], ps[i + 1], ps[i + 2], cs[i], cs[i + 1], cs[i + 2]); } break; default: throw new Error("illegal figure"); } } class MeshShadingPattern extends BaseShadingPattern { constructor(IR) { super(); this._coords = IR[2]; this._colors = IR[3]; this._figures = IR[4]; this._bounds = IR[5]; this._bbox = IR[7]; this._background = IR[8]; this.matrix = null; } _createMeshCanvas(combinedScale, backgroundColor, cachedCanvases) { const EXPECTED_SCALE = 1.1; const MAX_PATTERN_SIZE = 3000; const BORDER_SIZE = 2; const offsetX = Math.floor(this._bounds[0]); const offsetY = Math.floor(this._bounds[1]); const boundsWidth = Math.ceil(this._bounds[2]) - offsetX; const boundsHeight = Math.ceil(this._bounds[3]) - offsetY; const width = Math.min(Math.ceil(Math.abs(boundsWidth * combinedScale[0] * EXPECTED_SCALE)), MAX_PATTERN_SIZE); const height = Math.min(Math.ceil(Math.abs(boundsHeight * combinedScale[1] * EXPECTED_SCALE)), MAX_PATTERN_SIZE); const scaleX = boundsWidth / width; const scaleY = boundsHeight / height; const context = { coords: this._coords, colors: this._colors, offsetX: -offsetX, offsetY: -offsetY, scaleX: 1 / scaleX, scaleY: 1 / scaleY }; const paddedWidth = width + BORDER_SIZE * 2; const paddedHeight = height + BORDER_SIZE * 2; const tmpCanvas = cachedCanvases.getCanvas("mesh", paddedWidth, paddedHeight, false); const tmpCtx = tmpCanvas.context; const data = tmpCtx.createImageData(width, height); if (backgroundColor) { const bytes = data.data; for (let i = 0, ii = bytes.length; i < ii; i += 4) { bytes[i] = backgroundColor[0]; bytes[i + 1] = backgroundColor[1]; bytes[i + 2] = backgroundColor[2]; bytes[i + 3] = 255; } } for (const figure of this._figures) { drawFigure(data, figure, context); } tmpCtx.putImageData(data, BORDER_SIZE, BORDER_SIZE); const canvas = tmpCanvas.canvas; return { canvas, offsetX: offsetX - BORDER_SIZE * scaleX, offsetY: offsetY - BORDER_SIZE * scaleY, scaleX, scaleY }; } getPattern(ctx, owner, inverse, pathType) { applyBoundingBox(ctx, this._bbox); let scale; if (pathType === PathType.SHADING) { scale = Util.singularValueDecompose2dScale(getCurrentTransform(ctx)); } else { scale = Util.singularValueDecompose2dScale(owner.baseTransform); if (this.matrix) { const matrixScale = Util.singularValueDecompose2dScale(this.matrix); scale = [scale[0] * matrixScale[0], scale[1] * matrixScale[1]]; } } const temporaryPatternCanvas = this._createMeshCanvas(scale, pathType === PathType.SHADING ? null : this._background, owner.cachedCanvases); if (pathType !== PathType.SHADING) { ctx.setTransform(...owner.baseTransform); if (this.matrix) { ctx.transform(...this.matrix); } } ctx.translate(temporaryPatternCanvas.offsetX, temporaryPatternCanvas.offsetY); ctx.scale(temporaryPatternCanvas.scaleX, temporaryPatternCanvas.scaleY); return ctx.createPattern(temporaryPatternCanvas.canvas, "no-repeat"); } } class DummyShadingPattern extends BaseShadingPattern { getPattern() { return "hotpink"; } } function getShadingPattern(IR) { switch (IR[0]) { case "RadialAxial": return new RadialAxialShadingPattern(IR); case "Mesh": return new MeshShadingPattern(IR); case "Dummy": return new DummyShadingPattern(); } throw new Error(`Unknown IR type: ${IR[0]}`); } const PaintType = { COLORED: 1, UNCOLORED: 2 }; class TilingPattern { static MAX_PATTERN_SIZE = 3000; constructor(IR, color, ctx, canvasGraphicsFactory, baseTransform) { this.operatorList = IR[2]; this.matrix = IR[3] || [1, 0, 0, 1, 0, 0]; this.bbox = IR[4]; this.xstep = IR[5]; this.ystep = IR[6]; this.paintType = IR[7]; this.tilingType = IR[8]; this.color = color; this.ctx = ctx; this.canvasGraphicsFactory = canvasGraphicsFactory; this.baseTransform = baseTransform; } createPatternCanvas(owner) { const operatorList = this.operatorList; const bbox = this.bbox; const xstep = this.xstep; const ystep = this.ystep; const paintType = this.paintType; const tilingType = this.tilingType; const color = this.color; const canvasGraphicsFactory = this.canvasGraphicsFactory; info("TilingType: " + tilingType); const x0 = bbox[0], y0 = bbox[1], x1 = bbox[2], y1 = bbox[3]; const matrixScale = Util.singularValueDecompose2dScale(this.matrix); const curMatrixScale = Util.singularValueDecompose2dScale(this.baseTransform); const combinedScale = [matrixScale[0] * curMatrixScale[0], matrixScale[1] * curMatrixScale[1]]; const dimx = this.getSizeAndScale(xstep, this.ctx.canvas.width, combinedScale[0]); const dimy = this.getSizeAndScale(ystep, this.ctx.canvas.height, combinedScale[1]); const tmpCanvas = owner.cachedCanvases.getCanvas("pattern", dimx.size, dimy.size, true); const tmpCtx = tmpCanvas.context; const graphics = canvasGraphicsFactory.createCanvasGraphics(tmpCtx); graphics.groupLevel = owner.groupLevel; this.setFillAndStrokeStyleToContext(graphics, paintType, color); let adjustedX0 = x0; let adjustedY0 = y0; let adjustedX1 = x1; let adjustedY1 = y1; if (x0 < 0) { adjustedX0 = 0; adjustedX1 += Math.abs(x0); } if (y0 < 0) { adjustedY0 = 0; adjustedY1 += Math.abs(y0); } tmpCtx.translate(-(dimx.scale * adjustedX0), -(dimy.scale * adjustedY0)); graphics.transform(dimx.scale, 0, 0, dimy.scale, 0, 0); tmpCtx.save(); this.clipBbox(graphics, adjustedX0, adjustedY0, adjustedX1, adjustedY1); graphics.baseTransform = getCurrentTransform(graphics.ctx); graphics.executeOperatorList(operatorList); graphics.endDrawing(); return { canvas: tmpCanvas.canvas, scaleX: dimx.scale, scaleY: dimy.scale, offsetX: adjustedX0, offsetY: adjustedY0 }; } getSizeAndScale(step, realOutputSize, scale) { step = Math.abs(step); const maxSize = Math.max(TilingPattern.MAX_PATTERN_SIZE, realOutputSize); let size = Math.ceil(step * scale); if (size >= maxSize) { size = maxSize; } else { scale = size / step; } return { scale, size }; } clipBbox(graphics, x0, y0, x1, y1) { const bboxWidth = x1 - x0; const bboxHeight = y1 - y0; graphics.ctx.rect(x0, y0, bboxWidth, bboxHeight); graphics.current.updateRectMinMax(getCurrentTransform(graphics.ctx), [x0, y0, x1, y1]); graphics.clip(); graphics.endPath(); } setFillAndStrokeStyleToContext(graphics, paintType, color) { const context = graphics.ctx, current = graphics.current; switch (paintType) { case PaintType.COLORED: const ctx = this.ctx; context.fillStyle = ctx.fillStyle; context.strokeStyle = ctx.strokeStyle; current.fillColor = ctx.fillStyle; current.strokeColor = ctx.strokeStyle; break; case PaintType.UNCOLORED: const cssColor = Util.makeHexColor(color[0], color[1], color[2]); context.fillStyle = cssColor; context.strokeStyle = cssColor; current.fillColor = cssColor; current.strokeColor = cssColor; break; default: throw new FormatError(`Unsupported paint type: ${paintType}`); } } getPattern(ctx, owner, inverse, pathType) { let matrix = inverse; if (pathType !== PathType.SHADING) { matrix = Util.transform(matrix, owner.baseTransform); if (this.matrix) { matrix = Util.transform(matrix, this.matrix); } } const temporaryPatternCanvas = this.createPatternCanvas(owner); let domMatrix = new DOMMatrix(matrix); domMatrix = domMatrix.translate(temporaryPatternCanvas.offsetX, temporaryPatternCanvas.offsetY); domMatrix = domMatrix.scale(1 / temporaryPatternCanvas.scaleX, 1 / temporaryPatternCanvas.scaleY); const pattern = ctx.createPattern(temporaryPatternCanvas.canvas, "repeat"); pattern.setTransform(domMatrix); return pattern; } } ;// CONCATENATED MODULE: ./src/shared/image_utils.js function convertToRGBA(params) { switch (params.kind) { case ImageKind.GRAYSCALE_1BPP: return convertBlackAndWhiteToRGBA(params); case ImageKind.RGB_24BPP: return convertRGBToRGBA(params); } return null; } function convertBlackAndWhiteToRGBA({ src, srcPos = 0, dest, width, height, nonBlackColor = 0xffffffff, inverseDecode = false }) { const black = util_FeatureTest.isLittleEndian ? 0xff000000 : 0x000000ff; const [zeroMapping, oneMapping] = inverseDecode ? [nonBlackColor, black] : [black, nonBlackColor]; const widthInSource = width >> 3; const widthRemainder = width & 7; const srcLength = src.length; dest = new Uint32Array(dest.buffer); let destPos = 0; for (let i = 0; i < height; i++) { for (const max = srcPos + widthInSource; srcPos < max; srcPos++) { const elem = srcPos < srcLength ? src[srcPos] : 255; dest[destPos++] = elem & 0b10000000 ? oneMapping : zeroMapping; dest[destPos++] = elem & 0b1000000 ? oneMapping : zeroMapping; dest[destPos++] = elem & 0b100000 ? oneMapping : zeroMapping; dest[destPos++] = elem & 0b10000 ? oneMapping : zeroMapping; dest[destPos++] = elem & 0b1000 ? oneMapping : zeroMapping; dest[destPos++] = elem & 0b100 ? oneMapping : zeroMapping; dest[destPos++] = elem & 0b10 ? oneMapping : zeroMapping; dest[destPos++] = elem & 0b1 ? oneMapping : zeroMapping; } if (widthRemainder === 0) { continue; } const elem = srcPos < srcLength ? src[srcPos++] : 255; for (let j = 0; j < widthRemainder; j++) { dest[destPos++] = elem & 1 << 7 - j ? oneMapping : zeroMapping; } } return { srcPos, destPos }; } function convertRGBToRGBA({ src, srcPos = 0, dest, destPos = 0, width, height }) { let i = 0; const len32 = src.length >> 2; const src32 = new Uint32Array(src.buffer, srcPos, len32); if (FeatureTest.isLittleEndian) { for (; i < len32 - 2; i += 3, destPos += 4) { const s1 = src32[i]; const s2 = src32[i + 1]; const s3 = src32[i + 2]; dest[destPos] = s1 | 0xff000000; dest[destPos + 1] = s1 >>> 24 | s2 << 8 | 0xff000000; dest[destPos + 2] = s2 >>> 16 | s3 << 16 | 0xff000000; dest[destPos + 3] = s3 >>> 8 | 0xff000000; } for (let j = i * 4, jj = src.length; j < jj; j += 3) { dest[destPos++] = src[j] | src[j + 1] << 8 | src[j + 2] << 16 | 0xff000000; } } else { for (; i < len32 - 2; i += 3, destPos += 4) { const s1 = src32[i]; const s2 = src32[i + 1]; const s3 = src32[i + 2]; dest[destPos] = s1 | 0xff; dest[destPos + 1] = s1 << 24 | s2 >>> 8 | 0xff; dest[destPos + 2] = s2 << 16 | s3 >>> 16 | 0xff; dest[destPos + 3] = s3 << 8 | 0xff; } for (let j = i * 4, jj = src.length; j < jj; j += 3) { dest[destPos++] = src[j] << 24 | src[j + 1] << 16 | src[j + 2] << 8 | 0xff; } } return { srcPos, destPos }; } function grayToRGBA(src, dest) { if (FeatureTest.isLittleEndian) { for (let i = 0, ii = src.length; i < ii; i++) { dest[i] = src[i] * 0x10101 | 0xff000000; } } else { for (let i = 0, ii = src.length; i < ii; i++) { dest[i] = src[i] * 0x1010100 | 0x000000ff; } } } ;// CONCATENATED MODULE: ./src/display/canvas.js const MIN_FONT_SIZE = 16; const MAX_FONT_SIZE = 100; const EXECUTION_TIME = 15; const EXECUTION_STEPS = 10; const MAX_SIZE_TO_COMPILE = 1000; const FULL_CHUNK_HEIGHT = 16; function mirrorContextOperations(ctx, destCtx) { if (ctx._removeMirroring) { throw new Error("Context is already forwarding operations."); } ctx.__originalSave = ctx.save; ctx.__originalRestore = ctx.restore; ctx.__originalRotate = ctx.rotate; ctx.__originalScale = ctx.scale; ctx.__originalTranslate = ctx.translate; ctx.__originalTransform = ctx.transform; ctx.__originalSetTransform = ctx.setTransform; ctx.__originalResetTransform = ctx.resetTransform; ctx.__originalClip = ctx.clip; ctx.__originalMoveTo = ctx.moveTo; ctx.__originalLineTo = ctx.lineTo; ctx.__originalBezierCurveTo = ctx.bezierCurveTo; ctx.__originalRect = ctx.rect; ctx.__originalClosePath = ctx.closePath; ctx.__originalBeginPath = ctx.beginPath; ctx._removeMirroring = () => { ctx.save = ctx.__originalSave; ctx.restore = ctx.__originalRestore; ctx.rotate = ctx.__originalRotate; ctx.scale = ctx.__originalScale; ctx.translate = ctx.__originalTranslate; ctx.transform = ctx.__originalTransform; ctx.setTransform = ctx.__originalSetTransform; ctx.resetTransform = ctx.__originalResetTransform; ctx.clip = ctx.__originalClip; ctx.moveTo = ctx.__originalMoveTo; ctx.lineTo = ctx.__originalLineTo; ctx.bezierCurveTo = ctx.__originalBezierCurveTo; ctx.rect = ctx.__originalRect; ctx.closePath = ctx.__originalClosePath; ctx.beginPath = ctx.__originalBeginPath; delete ctx._removeMirroring; }; ctx.save = function ctxSave() { destCtx.save(); this.__originalSave(); }; ctx.restore = function ctxRestore() { destCtx.restore(); this.__originalRestore(); }; ctx.translate = function ctxTranslate(x, y) { destCtx.translate(x, y); this.__originalTranslate(x, y); }; ctx.scale = function ctxScale(x, y) { destCtx.scale(x, y); this.__originalScale(x, y); }; ctx.transform = function ctxTransform(a, b, c, d, e, f) { destCtx.transform(a, b, c, d, e, f); this.__originalTransform(a, b, c, d, e, f); }; ctx.setTransform = function ctxSetTransform(a, b, c, d, e, f) { destCtx.setTransform(a, b, c, d, e, f); this.__originalSetTransform(a, b, c, d, e, f); }; ctx.resetTransform = function ctxResetTransform() { destCtx.resetTransform(); this.__originalResetTransform(); }; ctx.rotate = function ctxRotate(angle) { destCtx.rotate(angle); this.__originalRotate(angle); }; ctx.clip = function ctxRotate(rule) { destCtx.clip(rule); this.__originalClip(rule); }; ctx.moveTo = function (x, y) { destCtx.moveTo(x, y); this.__originalMoveTo(x, y); }; ctx.lineTo = function (x, y) { destCtx.lineTo(x, y); this.__originalLineTo(x, y); }; ctx.bezierCurveTo = function (cp1x, cp1y, cp2x, cp2y, x, y) { destCtx.bezierCurveTo(cp1x, cp1y, cp2x, cp2y, x, y); this.__originalBezierCurveTo(cp1x, cp1y, cp2x, cp2y, x, y); }; ctx.rect = function (x, y, width, height) { destCtx.rect(x, y, width, height); this.__originalRect(x, y, width, height); }; ctx.closePath = function () { destCtx.closePath(); this.__originalClosePath(); }; ctx.beginPath = function () { destCtx.beginPath(); this.__originalBeginPath(); }; } class CachedCanvases { constructor(canvasFactory) { this.canvasFactory = canvasFactory; this.cache = Object.create(null); } getCanvas(id, width, height) { let canvasEntry; if (this.cache[id] !== undefined) { canvasEntry = this.cache[id]; this.canvasFactory.reset(canvasEntry, width, height); } else { canvasEntry = this.canvasFactory.create(width, height); this.cache[id] = canvasEntry; } return canvasEntry; } delete(id) { delete this.cache[id]; } clear() { for (const id in this.cache) { const canvasEntry = this.cache[id]; this.canvasFactory.destroy(canvasEntry); delete this.cache[id]; } } } function drawImageAtIntegerCoords(ctx, srcImg, srcX, srcY, srcW, srcH, destX, destY, destW, destH) { const [a, b, c, d, tx, ty] = getCurrentTransform(ctx); if (b === 0 && c === 0) { const tlX = destX * a + tx; const rTlX = Math.round(tlX); const tlY = destY * d + ty; const rTlY = Math.round(tlY); const brX = (destX + destW) * a + tx; const rWidth = Math.abs(Math.round(brX) - rTlX) || 1; const brY = (destY + destH) * d + ty; const rHeight = Math.abs(Math.round(brY) - rTlY) || 1; ctx.setTransform(Math.sign(a), 0, 0, Math.sign(d), rTlX, rTlY); ctx.drawImage(srcImg, srcX, srcY, srcW, srcH, 0, 0, rWidth, rHeight); ctx.setTransform(a, b, c, d, tx, ty); return [rWidth, rHeight]; } if (a === 0 && d === 0) { const tlX = destY * c + tx; const rTlX = Math.round(tlX); const tlY = destX * b + ty; const rTlY = Math.round(tlY); const brX = (destY + destH) * c + tx; const rWidth = Math.abs(Math.round(brX) - rTlX) || 1; const brY = (destX + destW) * b + ty; const rHeight = Math.abs(Math.round(brY) - rTlY) || 1; ctx.setTransform(0, Math.sign(b), Math.sign(c), 0, rTlX, rTlY); ctx.drawImage(srcImg, srcX, srcY, srcW, srcH, 0, 0, rHeight, rWidth); ctx.setTransform(a, b, c, d, tx, ty); return [rHeight, rWidth]; } ctx.drawImage(srcImg, srcX, srcY, srcW, srcH, destX, destY, destW, destH); const scaleX = Math.hypot(a, b); const scaleY = Math.hypot(c, d); return [scaleX * destW, scaleY * destH]; } function compileType3Glyph(imgData) { const { width, height } = imgData; if (width > MAX_SIZE_TO_COMPILE || height > MAX_SIZE_TO_COMPILE) { return null; } const POINT_TO_PROCESS_LIMIT = 1000; const POINT_TYPES = new Uint8Array([0, 2, 4, 0, 1, 0, 5, 4, 8, 10, 0, 8, 0, 2, 1, 0]); const width1 = width + 1; let points = new Uint8Array(width1 * (height + 1)); let i, j, j0; const lineSize = width + 7 & ~7; let data = new Uint8Array(lineSize * height), pos = 0; for (const elem of imgData.data) { let mask = 128; while (mask > 0) { data[pos++] = elem & mask ? 0 : 255; mask >>= 1; } } let count = 0; pos = 0; if (data[pos] !== 0) { points[0] = 1; ++count; } for (j = 1; j < width; j++) { if (data[pos] !== data[pos + 1]) { points[j] = data[pos] ? 2 : 1; ++count; } pos++; } if (data[pos] !== 0) { points[j] = 2; ++count; } for (i = 1; i < height; i++) { pos = i * lineSize; j0 = i * width1; if (data[pos - lineSize] !== data[pos]) { points[j0] = data[pos] ? 1 : 8; ++count; } let sum = (data[pos] ? 4 : 0) + (data[pos - lineSize] ? 8 : 0); for (j = 1; j < width; j++) { sum = (sum >> 2) + (data[pos + 1] ? 4 : 0) + (data[pos - lineSize + 1] ? 8 : 0); if (POINT_TYPES[sum]) { points[j0 + j] = POINT_TYPES[sum]; ++count; } pos++; } if (data[pos - lineSize] !== data[pos]) { points[j0 + j] = data[pos] ? 2 : 4; ++count; } if (count > POINT_TO_PROCESS_LIMIT) { return null; } } pos = lineSize * (height - 1); j0 = i * width1; if (data[pos] !== 0) { points[j0] = 8; ++count; } for (j = 1; j < width; j++) { if (data[pos] !== data[pos + 1]) { points[j0 + j] = data[pos] ? 4 : 8; ++count; } pos++; } if (data[pos] !== 0) { points[j0 + j] = 4; ++count; } if (count > POINT_TO_PROCESS_LIMIT) { return null; } const steps = new Int32Array([0, width1, -1, 0, -width1, 0, 0, 0, 1]); const path = new Path2D(); for (i = 0; count && i <= height; i++) { let p = i * width1; const end = p + width; while (p < end && !points[p]) { p++; } if (p === end) { continue; } path.moveTo(p % width1, i); const p0 = p; let type = points[p]; do { const step = steps[type]; do { p += step; } while (!points[p]); const pp = points[p]; if (pp !== 5 && pp !== 10) { type = pp; points[p] = 0; } else { type = pp & 0x33 * type >> 4; points[p] &= type >> 2 | type << 2; } path.lineTo(p % width1, p / width1 | 0); if (!points[p]) { --count; } } while (p0 !== p); --i; } data = null; points = null; const drawOutline = function (c) { c.save(); c.scale(1 / width, -1 / height); c.translate(0, -height); c.fill(path); c.beginPath(); c.restore(); }; return drawOutline; } class CanvasExtraState { constructor(width, height) { this.alphaIsShape = false; this.fontSize = 0; this.fontSizeScale = 1; this.textMatrix = IDENTITY_MATRIX; this.textMatrixScale = 1; this.fontMatrix = FONT_IDENTITY_MATRIX; this.leading = 0; this.x = 0; this.y = 0; this.lineX = 0; this.lineY = 0; this.charSpacing = 0; this.wordSpacing = 0; this.textHScale = 1; this.textRenderingMode = TextRenderingMode.FILL; this.textRise = 0; this.fillColor = "#000000"; this.strokeColor = "#000000"; this.patternFill = false; this.fillAlpha = 1; this.strokeAlpha = 1; this.lineWidth = 1; this.activeSMask = null; this.transferMaps = "none"; this.startNewPathAndClipBox([0, 0, width, height]); } clone() { const clone = Object.create(this); clone.clipBox = this.clipBox.slice(); return clone; } setCurrentPoint(x, y) { this.x = x; this.y = y; } updatePathMinMax(transform, x, y) { [x, y] = Util.applyTransform([x, y], transform); this.minX = Math.min(this.minX, x); this.minY = Math.min(this.minY, y); this.maxX = Math.max(this.maxX, x); this.maxY = Math.max(this.maxY, y); } updateRectMinMax(transform, rect) { const p1 = Util.applyTransform(rect, transform); const p2 = Util.applyTransform(rect.slice(2), transform); const p3 = Util.applyTransform([rect[0], rect[3]], transform); const p4 = Util.applyTransform([rect[2], rect[1]], transform); this.minX = Math.min(this.minX, p1[0], p2[0], p3[0], p4[0]); this.minY = Math.min(this.minY, p1[1], p2[1], p3[1], p4[1]); this.maxX = Math.max(this.maxX, p1[0], p2[0], p3[0], p4[0]); this.maxY = Math.max(this.maxY, p1[1], p2[1], p3[1], p4[1]); } updateScalingPathMinMax(transform, minMax) { Util.scaleMinMax(transform, minMax); this.minX = Math.min(this.minX, minMax[0]); this.minY = Math.min(this.minY, minMax[1]); this.maxX = Math.max(this.maxX, minMax[2]); this.maxY = Math.max(this.maxY, minMax[3]); } updateCurvePathMinMax(transform, x0, y0, x1, y1, x2, y2, x3, y3, minMax) { const box = Util.bezierBoundingBox(x0, y0, x1, y1, x2, y2, x3, y3, minMax); if (minMax) { return; } this.updateRectMinMax(transform, box); } getPathBoundingBox(pathType = PathType.FILL, transform = null) { const box = [this.minX, this.minY, this.maxX, this.maxY]; if (pathType === PathType.STROKE) { if (!transform) { unreachable("Stroke bounding box must include transform."); } const scale = Util.singularValueDecompose2dScale(transform); const xStrokePad = scale[0] * this.lineWidth / 2; const yStrokePad = scale[1] * this.lineWidth / 2; box[0] -= xStrokePad; box[1] -= yStrokePad; box[2] += xStrokePad; box[3] += yStrokePad; } return box; } updateClipFromPath() { const intersect = Util.intersect(this.clipBox, this.getPathBoundingBox()); this.startNewPathAndClipBox(intersect || [0, 0, 0, 0]); } isEmptyClip() { return this.minX === Infinity; } startNewPathAndClipBox(box) { this.clipBox = box; this.minX = Infinity; this.minY = Infinity; this.maxX = 0; this.maxY = 0; } getClippedPathBoundingBox(pathType = PathType.FILL, transform = null) { return Util.intersect(this.clipBox, this.getPathBoundingBox(pathType, transform)); } } function putBinaryImageData(ctx, imgData) { if (typeof ImageData !== "undefined" && imgData instanceof ImageData) { ctx.putImageData(imgData, 0, 0); return; } const height = imgData.height, width = imgData.width; const partialChunkHeight = height % FULL_CHUNK_HEIGHT; const fullChunks = (height - partialChunkHeight) / FULL_CHUNK_HEIGHT; const totalChunks = partialChunkHeight === 0 ? fullChunks : fullChunks + 1; const chunkImgData = ctx.createImageData(width, FULL_CHUNK_HEIGHT); let srcPos = 0, destPos; const src = imgData.data; const dest = chunkImgData.data; let i, j, thisChunkHeight, elemsInThisChunk; if (imgData.kind === util_ImageKind.GRAYSCALE_1BPP) { const srcLength = src.byteLength; const dest32 = new Uint32Array(dest.buffer, 0, dest.byteLength >> 2); const dest32DataLength = dest32.length; const fullSrcDiff = width + 7 >> 3; const white = 0xffffffff; const black = util_FeatureTest.isLittleEndian ? 0xff000000 : 0x000000ff; for (i = 0; i < totalChunks; i++) { thisChunkHeight = i < fullChunks ? FULL_CHUNK_HEIGHT : partialChunkHeight; destPos = 0; for (j = 0; j < thisChunkHeight; j++) { const srcDiff = srcLength - srcPos; let k = 0; const kEnd = srcDiff > fullSrcDiff ? width : srcDiff * 8 - 7; const kEndUnrolled = kEnd & ~7; let mask = 0; let srcByte = 0; for (; k < kEndUnrolled; k += 8) { srcByte = src[srcPos++]; dest32[destPos++] = srcByte & 128 ? white : black; dest32[destPos++] = srcByte & 64 ? white : black; dest32[destPos++] = srcByte & 32 ? white : black; dest32[destPos++] = srcByte & 16 ? white : black; dest32[destPos++] = srcByte & 8 ? white : black; dest32[destPos++] = srcByte & 4 ? white : black; dest32[destPos++] = srcByte & 2 ? white : black; dest32[destPos++] = srcByte & 1 ? white : black; } for (; k < kEnd; k++) { if (mask === 0) { srcByte = src[srcPos++]; mask = 128; } dest32[destPos++] = srcByte & mask ? white : black; mask >>= 1; } } while (destPos < dest32DataLength) { dest32[destPos++] = 0; } ctx.putImageData(chunkImgData, 0, i * FULL_CHUNK_HEIGHT); } } else if (imgData.kind === util_ImageKind.RGBA_32BPP) { j = 0; elemsInThisChunk = width * FULL_CHUNK_HEIGHT * 4; for (i = 0; i < fullChunks; i++) { dest.set(src.subarray(srcPos, srcPos + elemsInThisChunk)); srcPos += elemsInThisChunk; ctx.putImageData(chunkImgData, 0, j); j += FULL_CHUNK_HEIGHT; } if (i < totalChunks) { elemsInThisChunk = width * partialChunkHeight * 4; dest.set(src.subarray(srcPos, srcPos + elemsInThisChunk)); ctx.putImageData(chunkImgData, 0, j); } } else if (imgData.kind === util_ImageKind.RGB_24BPP) { thisChunkHeight = FULL_CHUNK_HEIGHT; elemsInThisChunk = width * thisChunkHeight; for (i = 0; i < totalChunks; i++) { if (i >= fullChunks) { thisChunkHeight = partialChunkHeight; elemsInThisChunk = width * thisChunkHeight; } destPos = 0; for (j = elemsInThisChunk; j--;) { dest[destPos++] = src[srcPos++]; dest[destPos++] = src[srcPos++]; dest[destPos++] = src[srcPos++]; dest[destPos++] = 255; } ctx.putImageData(chunkImgData, 0, i * FULL_CHUNK_HEIGHT); } } else { throw new Error(`bad image kind: ${imgData.kind}`); } } function putBinaryImageMask(ctx, imgData) { if (imgData.bitmap) { ctx.drawImage(imgData.bitmap, 0, 0); return; } const height = imgData.height, width = imgData.width; const partialChunkHeight = height % FULL_CHUNK_HEIGHT; const fullChunks = (height - partialChunkHeight) / FULL_CHUNK_HEIGHT; const totalChunks = partialChunkHeight === 0 ? fullChunks : fullChunks + 1; const chunkImgData = ctx.createImageData(width, FULL_CHUNK_HEIGHT); let srcPos = 0; const src = imgData.data; const dest = chunkImgData.data; for (let i = 0; i < totalChunks; i++) { const thisChunkHeight = i < fullChunks ? FULL_CHUNK_HEIGHT : partialChunkHeight; ({ srcPos } = convertBlackAndWhiteToRGBA({ src, srcPos, dest, width, height: thisChunkHeight, nonBlackColor: 0 })); ctx.putImageData(chunkImgData, 0, i * FULL_CHUNK_HEIGHT); } } function copyCtxState(sourceCtx, destCtx) { const properties = ["strokeStyle", "fillStyle", "fillRule", "globalAlpha", "lineWidth", "lineCap", "lineJoin", "miterLimit", "globalCompositeOperation", "font", "filter"]; for (const property of properties) { if (sourceCtx[property] !== undefined) { destCtx[property] = sourceCtx[property]; } } if (sourceCtx.setLineDash !== undefined) { destCtx.setLineDash(sourceCtx.getLineDash()); destCtx.lineDashOffset = sourceCtx.lineDashOffset; } } function resetCtxToDefault(ctx) { ctx.strokeStyle = ctx.fillStyle = "#000000"; ctx.fillRule = "nonzero"; ctx.globalAlpha = 1; ctx.lineWidth = 1; ctx.lineCap = "butt"; ctx.lineJoin = "miter"; ctx.miterLimit = 10; ctx.globalCompositeOperation = "source-over"; ctx.font = "10px sans-serif"; if (ctx.setLineDash !== undefined) { ctx.setLineDash([]); ctx.lineDashOffset = 0; } const { filter } = ctx; if (filter !== "none" && filter !== "") { ctx.filter = "none"; } } function getImageSmoothingEnabled(transform, interpolate) { if (interpolate) { return true; } const scale = Util.singularValueDecompose2dScale(transform); scale[0] = Math.fround(scale[0]); scale[1] = Math.fround(scale[1]); const actualScale = Math.fround((globalThis.devicePixelRatio || 1) * PixelsPerInch.PDF_TO_CSS_UNITS); return scale[0] <= actualScale && scale[1] <= actualScale; } const LINE_CAP_STYLES = ["butt", "round", "square"]; const LINE_JOIN_STYLES = ["miter", "round", "bevel"]; const NORMAL_CLIP = {}; const EO_CLIP = {}; class CanvasGraphics { constructor(canvasCtx, commonObjs, objs, canvasFactory, filterFactory, { optionalContentConfig, markedContentStack = null }, annotationCanvasMap, pageColors) { this.ctx = canvasCtx; this.current = new CanvasExtraState(this.ctx.canvas.width, this.ctx.canvas.height); this.stateStack = []; this.pendingClip = null; this.pendingEOFill = false; this.res = null; this.xobjs = null; this.commonObjs = commonObjs; this.objs = objs; this.canvasFactory = canvasFactory; this.filterFactory = filterFactory; this.groupStack = []; this.processingType3 = null; this.baseTransform = null; this.baseTransformStack = []; this.groupLevel = 0; this.smaskStack = []; this.smaskCounter = 0; this.tempSMask = null; this.suspendedCtx = null; this.contentVisible = true; this.markedContentStack = markedContentStack || []; this.optionalContentConfig = optionalContentConfig; this.cachedCanvases = new CachedCanvases(this.canvasFactory); this.cachedPatterns = new Map(); this.annotationCanvasMap = annotationCanvasMap; this.viewportScale = 1; this.outputScaleX = 1; this.outputScaleY = 1; this.pageColors = pageColors; this._cachedScaleForStroking = [-1, 0]; this._cachedGetSinglePixelWidth = null; this._cachedBitmapsMap = new Map(); } getObject(data, fallback = null) { if (typeof data === "string") { return data.startsWith("g_") ? this.commonObjs.get(data) : this.objs.get(data); } return fallback; } beginDrawing({ transform, viewport, transparency = false, background = null }) { const width = this.ctx.canvas.width; const height = this.ctx.canvas.height; const savedFillStyle = this.ctx.fillStyle; this.ctx.fillStyle = background || "#ffffff"; this.ctx.fillRect(0, 0, width, height); this.ctx.fillStyle = savedFillStyle; if (transparency) { const transparentCanvas = this.cachedCanvases.getCanvas("transparent", width, height); this.compositeCtx = this.ctx; this.transparentCanvas = transparentCanvas.canvas; this.ctx = transparentCanvas.context; this.ctx.save(); this.ctx.transform(...getCurrentTransform(this.compositeCtx)); } this.ctx.save(); resetCtxToDefault(this.ctx); if (transform) { this.ctx.transform(...transform); this.outputScaleX = transform[0]; this.outputScaleY = transform[0]; } this.ctx.transform(...viewport.transform); this.viewportScale = viewport.scale; this.baseTransform = getCurrentTransform(this.ctx); } executeOperatorList(operatorList, executionStartIdx, continueCallback, stepper) { const argsArray = operatorList.argsArray; const fnArray = operatorList.fnArray; let i = executionStartIdx || 0; const argsArrayLen = argsArray.length; if (argsArrayLen === i) { return i; } const chunkOperations = argsArrayLen - i > EXECUTION_STEPS && typeof continueCallback === "function"; const endTime = chunkOperations ? Date.now() + EXECUTION_TIME : 0; let steps = 0; const commonObjs = this.commonObjs; const objs = this.objs; let fnId; while (true) { if (stepper !== undefined && i === stepper.nextBreakPoint) { stepper.breakIt(i, continueCallback); return i; } fnId = fnArray[i]; if (fnId !== OPS.dependency) { this[fnId].apply(this, argsArray[i]); } else { for (const depObjId of argsArray[i]) { const objsPool = depObjId.startsWith("g_") ? commonObjs : objs; if (!objsPool.has(depObjId)) { objsPool.get(depObjId, continueCallback); return i; } } } i++; if (i === argsArrayLen) { return i; } if (chunkOperations && ++steps > EXECUTION_STEPS) { if (Date.now() > endTime) { continueCallback(); return i; } steps = 0; } } } #restoreInitialState() { while (this.stateStack.length || this.inSMaskMode) { this.restore(); } this.ctx.restore(); if (this.transparentCanvas) { this.ctx = this.compositeCtx; this.ctx.save(); this.ctx.setTransform(1, 0, 0, 1, 0, 0); this.ctx.drawImage(this.transparentCanvas, 0, 0); this.ctx.restore(); this.transparentCanvas = null; } } endDrawing() { this.#restoreInitialState(); this.cachedCanvases.clear(); this.cachedPatterns.clear(); for (const cache of this._cachedBitmapsMap.values()) { for (const canvas of cache.values()) { if (typeof HTMLCanvasElement !== "undefined" && canvas instanceof HTMLCanvasElement) { canvas.width = canvas.height = 0; } } cache.clear(); } this._cachedBitmapsMap.clear(); this.#drawFilter(); } #drawFilter() { if (this.pageColors) { const hcmFilterId = this.filterFactory.addHCMFilter(this.pageColors.foreground, this.pageColors.background); if (hcmFilterId !== "none") { const savedFilter = this.ctx.filter; this.ctx.filter = hcmFilterId; this.ctx.drawImage(this.ctx.canvas, 0, 0); this.ctx.filter = savedFilter; } } } _scaleImage(img, inverseTransform) { const width = img.width; const height = img.height; let widthScale = Math.max(Math.hypot(inverseTransform[0], inverseTransform[1]), 1); let heightScale = Math.max(Math.hypot(inverseTransform[2], inverseTransform[3]), 1); let paintWidth = width, paintHeight = height; let tmpCanvasId = "prescale1"; let tmpCanvas, tmpCtx; while (widthScale > 2 && paintWidth > 1 || heightScale > 2 && paintHeight > 1) { let newWidth = paintWidth, newHeight = paintHeight; if (widthScale > 2 && paintWidth > 1) { newWidth = paintWidth >= 16384 ? Math.floor(paintWidth / 2) - 1 || 1 : Math.ceil(paintWidth / 2); widthScale /= paintWidth / newWidth; } if (heightScale > 2 && paintHeight > 1) { newHeight = paintHeight >= 16384 ? Math.floor(paintHeight / 2) - 1 || 1 : Math.ceil(paintHeight) / 2; heightScale /= paintHeight / newHeight; } tmpCanvas = this.cachedCanvases.getCanvas(tmpCanvasId, newWidth, newHeight); tmpCtx = tmpCanvas.context; tmpCtx.clearRect(0, 0, newWidth, newHeight); tmpCtx.drawImage(img, 0, 0, paintWidth, paintHeight, 0, 0, newWidth, newHeight); img = tmpCanvas.canvas; paintWidth = newWidth; paintHeight = newHeight; tmpCanvasId = tmpCanvasId === "prescale1" ? "prescale2" : "prescale1"; } return { img, paintWidth, paintHeight }; } _createMaskCanvas(img) { const ctx = this.ctx; const { width, height } = img; const fillColor = this.current.fillColor; const isPatternFill = this.current.patternFill; const currentTransform = getCurrentTransform(ctx); let cache, cacheKey, scaled, maskCanvas; if ((img.bitmap || img.data) && img.count > 1) { const mainKey = img.bitmap || img.data.buffer; cacheKey = JSON.stringify(isPatternFill ? currentTransform : [currentTransform.slice(0, 4), fillColor]); cache = this._cachedBitmapsMap.get(mainKey); if (!cache) { cache = new Map(); this._cachedBitmapsMap.set(mainKey, cache); } const cachedImage = cache.get(cacheKey); if (cachedImage && !isPatternFill) { const offsetX = Math.round(Math.min(currentTransform[0], currentTransform[2]) + currentTransform[4]); const offsetY = Math.round(Math.min(currentTransform[1], currentTransform[3]) + currentTransform[5]); return { canvas: cachedImage, offsetX, offsetY }; } scaled = cachedImage; } if (!scaled) { maskCanvas = this.cachedCanvases.getCanvas("maskCanvas", width, height); putBinaryImageMask(maskCanvas.context, img); } let maskToCanvas = Util.transform(currentTransform, [1 / width, 0, 0, -1 / height, 0, 0]); maskToCanvas = Util.transform(maskToCanvas, [1, 0, 0, 1, 0, -height]); const [minX, minY, maxX, maxY] = Util.getAxialAlignedBoundingBox([0, 0, width, height], maskToCanvas); const drawnWidth = Math.round(maxX - minX) || 1; const drawnHeight = Math.round(maxY - minY) || 1; const fillCanvas = this.cachedCanvases.getCanvas("fillCanvas", drawnWidth, drawnHeight); const fillCtx = fillCanvas.context; const offsetX = minX; const offsetY = minY; fillCtx.translate(-offsetX, -offsetY); fillCtx.transform(...maskToCanvas); if (!scaled) { scaled = this._scaleImage(maskCanvas.canvas, getCurrentTransformInverse(fillCtx)); scaled = scaled.img; if (cache && isPatternFill) { cache.set(cacheKey, scaled); } } fillCtx.imageSmoothingEnabled = getImageSmoothingEnabled(getCurrentTransform(fillCtx), img.interpolate); drawImageAtIntegerCoords(fillCtx, scaled, 0, 0, scaled.width, scaled.height, 0, 0, width, height); fillCtx.globalCompositeOperation = "source-in"; const inverse = Util.transform(getCurrentTransformInverse(fillCtx), [1, 0, 0, 1, -offsetX, -offsetY]); fillCtx.fillStyle = isPatternFill ? fillColor.getPattern(ctx, this, inverse, PathType.FILL) : fillColor; fillCtx.fillRect(0, 0, width, height); if (cache && !isPatternFill) { this.cachedCanvases.delete("fillCanvas"); cache.set(cacheKey, fillCanvas.canvas); } return { canvas: fillCanvas.canvas, offsetX: Math.round(offsetX), offsetY: Math.round(offsetY) }; } setLineWidth(width) { if (width !== this.current.lineWidth) { this._cachedScaleForStroking[0] = -1; } this.current.lineWidth = width; this.ctx.lineWidth = width; } setLineCap(style) { this.ctx.lineCap = LINE_CAP_STYLES[style]; } setLineJoin(style) { this.ctx.lineJoin = LINE_JOIN_STYLES[style]; } setMiterLimit(limit) { this.ctx.miterLimit = limit; } setDash(dashArray, dashPhase) { const ctx = this.ctx; if (ctx.setLineDash !== undefined) { ctx.setLineDash(dashArray); ctx.lineDashOffset = dashPhase; } } setRenderingIntent(intent) {} setFlatness(flatness) {} setGState(states) { for (const [key, value] of states) { switch (key) { case "LW": this.setLineWidth(value); break; case "LC": this.setLineCap(value); break; case "LJ": this.setLineJoin(value); break; case "ML": this.setMiterLimit(value); break; case "D": this.setDash(value[0], value[1]); break; case "RI": this.setRenderingIntent(value); break; case "FL": this.setFlatness(value); break; case "Font": this.setFont(value[0], value[1]); break; case "CA": this.current.strokeAlpha = value; break; case "ca": this.current.fillAlpha = value; this.ctx.globalAlpha = value; break; case "BM": this.ctx.globalCompositeOperation = value; break; case "SMask": this.current.activeSMask = value ? this.tempSMask : null; this.tempSMask = null; this.checkSMaskState(); break; case "TR": this.ctx.filter = this.current.transferMaps = this.filterFactory.addFilter(value); break; } } } get inSMaskMode() { return !!this.suspendedCtx; } checkSMaskState() { const inSMaskMode = this.inSMaskMode; if (this.current.activeSMask && !inSMaskMode) { this.beginSMaskMode(); } else if (!this.current.activeSMask && inSMaskMode) { this.endSMaskMode(); } } beginSMaskMode() { if (this.inSMaskMode) { throw new Error("beginSMaskMode called while already in smask mode"); } const drawnWidth = this.ctx.canvas.width; const drawnHeight = this.ctx.canvas.height; const cacheId = "smaskGroupAt" + this.groupLevel; const scratchCanvas = this.cachedCanvases.getCanvas(cacheId, drawnWidth, drawnHeight); this.suspendedCtx = this.ctx; this.ctx = scratchCanvas.context; const ctx = this.ctx; ctx.setTransform(...getCurrentTransform(this.suspendedCtx)); copyCtxState(this.suspendedCtx, ctx); mirrorContextOperations(ctx, this.suspendedCtx); this.setGState([["BM", "source-over"], ["ca", 1], ["CA", 1]]); } endSMaskMode() { if (!this.inSMaskMode) { throw new Error("endSMaskMode called while not in smask mode"); } this.ctx._removeMirroring(); copyCtxState(this.ctx, this.suspendedCtx); this.ctx = this.suspendedCtx; this.suspendedCtx = null; } compose(dirtyBox) { if (!this.current.activeSMask) { return; } if (!dirtyBox) { dirtyBox = [0, 0, this.ctx.canvas.width, this.ctx.canvas.height]; } else { dirtyBox[0] = Math.floor(dirtyBox[0]); dirtyBox[1] = Math.floor(dirtyBox[1]); dirtyBox[2] = Math.ceil(dirtyBox[2]); dirtyBox[3] = Math.ceil(dirtyBox[3]); } const smask = this.current.activeSMask; const suspendedCtx = this.suspendedCtx; this.composeSMask(suspendedCtx, smask, this.ctx, dirtyBox); this.ctx.save(); this.ctx.setTransform(1, 0, 0, 1, 0, 0); this.ctx.clearRect(0, 0, this.ctx.canvas.width, this.ctx.canvas.height); this.ctx.restore(); } composeSMask(ctx, smask, layerCtx, layerBox) { const layerOffsetX = layerBox[0]; const layerOffsetY = layerBox[1]; const layerWidth = layerBox[2] - layerOffsetX; const layerHeight = layerBox[3] - layerOffsetY; if (layerWidth === 0 || layerHeight === 0) { return; } this.genericComposeSMask(smask.context, layerCtx, layerWidth, layerHeight, smask.subtype, smask.backdrop, smask.transferMap, layerOffsetX, layerOffsetY, smask.offsetX, smask.offsetY); ctx.save(); ctx.globalAlpha = 1; ctx.globalCompositeOperation = "source-over"; ctx.setTransform(1, 0, 0, 1, 0, 0); ctx.drawImage(layerCtx.canvas, 0, 0); ctx.restore(); } genericComposeSMask(maskCtx, layerCtx, width, height, subtype, backdrop, transferMap, layerOffsetX, layerOffsetY, maskOffsetX, maskOffsetY) { let maskCanvas = maskCtx.canvas; let maskX = layerOffsetX - maskOffsetX; let maskY = layerOffsetY - maskOffsetY; if (backdrop) { if (maskX < 0 || maskY < 0 || maskX + width > maskCanvas.width || maskY + height > maskCanvas.height) { const canvas = this.cachedCanvases.getCanvas("maskExtension", width, height); const ctx = canvas.context; ctx.drawImage(maskCanvas, -maskX, -maskY); if (backdrop.some(c => c !== 0)) { ctx.globalCompositeOperation = "destination-atop"; ctx.fillStyle = Util.makeHexColor(...backdrop); ctx.fillRect(0, 0, width, height); ctx.globalCompositeOperation = "source-over"; } maskCanvas = canvas.canvas; maskX = maskY = 0; } else if (backdrop.some(c => c !== 0)) { maskCtx.save(); maskCtx.globalAlpha = 1; maskCtx.setTransform(1, 0, 0, 1, 0, 0); const clip = new Path2D(); clip.rect(maskX, maskY, width, height); maskCtx.clip(clip); maskCtx.globalCompositeOperation = "destination-atop"; maskCtx.fillStyle = Util.makeHexColor(...backdrop); maskCtx.fillRect(maskX, maskY, width, height); maskCtx.restore(); } } layerCtx.save(); layerCtx.globalAlpha = 1; layerCtx.setTransform(1, 0, 0, 1, 0, 0); if (subtype === "Alpha" && transferMap) { layerCtx.filter = this.filterFactory.addAlphaFilter(transferMap); } else if (subtype === "Luminosity") { layerCtx.filter = this.filterFactory.addLuminosityFilter(transferMap); } const clip = new Path2D(); clip.rect(layerOffsetX, layerOffsetY, width, height); layerCtx.clip(clip); layerCtx.globalCompositeOperation = "destination-in"; layerCtx.drawImage(maskCanvas, maskX, maskY, width, height, layerOffsetX, layerOffsetY, width, height); layerCtx.restore(); } save() { if (this.inSMaskMode) { copyCtxState(this.ctx, this.suspendedCtx); this.suspendedCtx.save(); } else { this.ctx.save(); } const old = this.current; this.stateStack.push(old); this.current = old.clone(); } restore() { if (this.stateStack.length === 0 && this.inSMaskMode) { this.endSMaskMode(); } if (this.stateStack.length !== 0) { this.current = this.stateStack.pop(); if (this.inSMaskMode) { this.suspendedCtx.restore(); copyCtxState(this.suspendedCtx, this.ctx); } else { this.ctx.restore(); } this.checkSMaskState(); this.pendingClip = null; this._cachedScaleForStroking[0] = -1; this._cachedGetSinglePixelWidth = null; } } transform(a, b, c, d, e, f) { this.ctx.transform(a, b, c, d, e, f); this._cachedScaleForStroking[0] = -1; this._cachedGetSinglePixelWidth = null; } constructPath(ops, args, minMax) { const ctx = this.ctx; const current = this.current; let x = current.x, y = current.y; let startX, startY; const currentTransform = getCurrentTransform(ctx); const isScalingMatrix = currentTransform[0] === 0 && currentTransform[3] === 0 || currentTransform[1] === 0 && currentTransform[2] === 0; const minMaxForBezier = isScalingMatrix ? minMax.slice(0) : null; for (let i = 0, j = 0, ii = ops.length; i < ii; i++) { switch (ops[i] | 0) { case OPS.rectangle: x = args[j++]; y = args[j++]; const width = args[j++]; const height = args[j++]; const xw = x + width; const yh = y + height; ctx.moveTo(x, y); if (width === 0 || height === 0) { ctx.lineTo(xw, yh); } else { ctx.lineTo(xw, y); ctx.lineTo(xw, yh); ctx.lineTo(x, yh); } if (!isScalingMatrix) { current.updateRectMinMax(currentTransform, [x, y, xw, yh]); } ctx.closePath(); break; case OPS.moveTo: x = args[j++]; y = args[j++]; ctx.moveTo(x, y); if (!isScalingMatrix) { current.updatePathMinMax(currentTransform, x, y); } break; case OPS.lineTo: x = args[j++]; y = args[j++]; ctx.lineTo(x, y); if (!isScalingMatrix) { current.updatePathMinMax(currentTransform, x, y); } break; case OPS.curveTo: startX = x; startY = y; x = args[j + 4]; y = args[j + 5]; ctx.bezierCurveTo(args[j], args[j + 1], args[j + 2], args[j + 3], x, y); current.updateCurvePathMinMax(currentTransform, startX, startY, args[j], args[j + 1], args[j + 2], args[j + 3], x, y, minMaxForBezier); j += 6; break; case OPS.curveTo2: startX = x; startY = y; ctx.bezierCurveTo(x, y, args[j], args[j + 1], args[j + 2], args[j + 3]); current.updateCurvePathMinMax(currentTransform, startX, startY, x, y, args[j], args[j + 1], args[j + 2], args[j + 3], minMaxForBezier); x = args[j + 2]; y = args[j + 3]; j += 4; break; case OPS.curveTo3: startX = x; startY = y; x = args[j + 2]; y = args[j + 3]; ctx.bezierCurveTo(args[j], args[j + 1], x, y, x, y); current.updateCurvePathMinMax(currentTransform, startX, startY, args[j], args[j + 1], x, y, x, y, minMaxForBezier); j += 4; break; case OPS.closePath: ctx.closePath(); break; } } if (isScalingMatrix) { current.updateScalingPathMinMax(currentTransform, minMaxForBezier); } current.setCurrentPoint(x, y); } closePath() { this.ctx.closePath(); } stroke(consumePath = true) { const ctx = this.ctx; const strokeColor = this.current.strokeColor; ctx.globalAlpha = this.current.strokeAlpha; if (this.contentVisible) { if (typeof strokeColor === "object" && strokeColor?.getPattern) { ctx.save(); ctx.strokeStyle = strokeColor.getPattern(ctx, this, getCurrentTransformInverse(ctx), PathType.STROKE); this.rescaleAndStroke(false); ctx.restore(); } else { this.rescaleAndStroke(true); } } if (consumePath) { this.consumePath(this.current.getClippedPathBoundingBox()); } ctx.globalAlpha = this.current.fillAlpha; } closeStroke() { this.closePath(); this.stroke(); } fill(consumePath = true) { const ctx = this.ctx; const fillColor = this.current.fillColor; const isPatternFill = this.current.patternFill; let needRestore = false; if (isPatternFill) { ctx.save(); ctx.fillStyle = fillColor.getPattern(ctx, this, getCurrentTransformInverse(ctx), PathType.FILL); needRestore = true; } const intersect = this.current.getClippedPathBoundingBox(); if (this.contentVisible && intersect !== null) { if (this.pendingEOFill) { ctx.fill("evenodd"); this.pendingEOFill = false; } else { ctx.fill(); } } if (needRestore) { ctx.restore(); } if (consumePath) { this.consumePath(intersect); } } eoFill() { this.pendingEOFill = true; this.fill(); } fillStroke() { this.fill(false); this.stroke(false); this.consumePath(); } eoFillStroke() { this.pendingEOFill = true; this.fillStroke(); } closeFillStroke() { this.closePath(); this.fillStroke(); } closeEOFillStroke() { this.pendingEOFill = true; this.closePath(); this.fillStroke(); } endPath() { this.consumePath(); } clip() { this.pendingClip = NORMAL_CLIP; } eoClip() { this.pendingClip = EO_CLIP; } beginText() { this.current.textMatrix = IDENTITY_MATRIX; this.current.textMatrixScale = 1; this.current.x = this.current.lineX = 0; this.current.y = this.current.lineY = 0; } endText() { const paths = this.pendingTextPaths; const ctx = this.ctx; if (paths === undefined) { ctx.beginPath(); return; } ctx.save(); ctx.beginPath(); for (const path of paths) { ctx.setTransform(...path.transform); ctx.translate(path.x, path.y); path.addToPath(ctx, path.fontSize); } ctx.restore(); ctx.clip(); ctx.beginPath(); delete this.pendingTextPaths; } setCharSpacing(spacing) { this.current.charSpacing = spacing; } setWordSpacing(spacing) { this.current.wordSpacing = spacing; } setHScale(scale) { this.current.textHScale = scale / 100; } setLeading(leading) { this.current.leading = -leading; } setFont(fontRefName, size) { const fontObj = this.commonObjs.get(fontRefName); const current = this.current; if (!fontObj) { throw new Error(`Can't find font for ${fontRefName}`); } current.fontMatrix = fontObj.fontMatrix || FONT_IDENTITY_MATRIX; if (current.fontMatrix[0] === 0 || current.fontMatrix[3] === 0) { warn("Invalid font matrix for font " + fontRefName); } if (size < 0) { size = -size; current.fontDirection = -1; } else { current.fontDirection = 1; } this.current.font = fontObj; this.current.fontSize = size; if (fontObj.isType3Font) { return; } const name = fontObj.loadedName || "sans-serif"; const typeface = fontObj.systemFontInfo?.css || `"${name}", ${fontObj.fallbackName}`; let bold = "normal"; if (fontObj.black) { bold = "900"; } else if (fontObj.bold) { bold = "bold"; } const italic = fontObj.italic ? "italic" : "normal"; let browserFontSize = size; if (size < MIN_FONT_SIZE) { browserFontSize = MIN_FONT_SIZE; } else if (size > MAX_FONT_SIZE) { browserFontSize = MAX_FONT_SIZE; } this.current.fontSizeScale = size / browserFontSize; this.ctx.font = `${italic} ${bold} ${browserFontSize}px ${typeface}`; } setTextRenderingMode(mode) { this.current.textRenderingMode = mode; } setTextRise(rise) { this.current.textRise = rise; } moveText(x, y) { this.current.x = this.current.lineX += x; this.current.y = this.current.lineY += y; } setLeadingMoveText(x, y) { this.setLeading(-y); this.moveText(x, y); } setTextMatrix(a, b, c, d, e, f) { this.current.textMatrix = [a, b, c, d, e, f]; this.current.textMatrixScale = Math.hypot(a, b); this.current.x = this.current.lineX = 0; this.current.y = this.current.lineY = 0; } nextLine() { this.moveText(0, this.current.leading); } paintChar(character, x, y, patternTransform) { const ctx = this.ctx; const current = this.current; const font = current.font; const textRenderingMode = current.textRenderingMode; const fontSize = current.fontSize / current.fontSizeScale; const fillStrokeMode = textRenderingMode & TextRenderingMode.FILL_STROKE_MASK; const isAddToPathSet = !!(textRenderingMode & TextRenderingMode.ADD_TO_PATH_FLAG); const patternFill = current.patternFill && !font.missingFile; let addToPath; if (font.disableFontFace || isAddToPathSet || patternFill) { addToPath = font.getPathGenerator(this.commonObjs, character); } if (font.disableFontFace || patternFill) { ctx.save(); ctx.translate(x, y); ctx.beginPath(); addToPath(ctx, fontSize); if (patternTransform) { ctx.setTransform(...patternTransform); } if (fillStrokeMode === TextRenderingMode.FILL || fillStrokeMode === TextRenderingMode.FILL_STROKE) { ctx.fill(); } if (fillStrokeMode === TextRenderingMode.STROKE || fillStrokeMode === TextRenderingMode.FILL_STROKE) { ctx.stroke(); } ctx.restore(); } else { if (fillStrokeMode === TextRenderingMode.FILL || fillStrokeMode === TextRenderingMode.FILL_STROKE) { ctx.fillText(character, x, y); } if (fillStrokeMode === TextRenderingMode.STROKE || fillStrokeMode === TextRenderingMode.FILL_STROKE) { ctx.strokeText(character, x, y); } } if (isAddToPathSet) { const paths = this.pendingTextPaths ||= []; paths.push({ transform: getCurrentTransform(ctx), x, y, fontSize, addToPath }); } } get isFontSubpixelAAEnabled() { const { context: ctx } = this.cachedCanvases.getCanvas("isFontSubpixelAAEnabled", 10, 10); ctx.scale(1.5, 1); ctx.fillText("I", 0, 10); const data = ctx.getImageData(0, 0, 10, 10).data; let enabled = false; for (let i = 3; i < data.length; i += 4) { if (data[i] > 0 && data[i] < 255) { enabled = true; break; } } return shadow(this, "isFontSubpixelAAEnabled", enabled); } showText(glyphs) { const current = this.current; const font = current.font; if (font.isType3Font) { return this.showType3Text(glyphs); } const fontSize = current.fontSize; if (fontSize === 0) { return undefined; } const ctx = this.ctx; const fontSizeScale = current.fontSizeScale; const charSpacing = current.charSpacing; const wordSpacing = current.wordSpacing; const fontDirection = current.fontDirection; const textHScale = current.textHScale * fontDirection; const glyphsLength = glyphs.length; const vertical = font.vertical; const spacingDir = vertical ? 1 : -1; const defaultVMetrics = font.defaultVMetrics; const widthAdvanceScale = fontSize * current.fontMatrix[0]; const simpleFillText = current.textRenderingMode === TextRenderingMode.FILL && !font.disableFontFace && !current.patternFill; ctx.save(); ctx.transform(...current.textMatrix); ctx.translate(current.x, current.y + current.textRise); if (fontDirection > 0) { ctx.scale(textHScale, -1); } else { ctx.scale(textHScale, 1); } let patternTransform; if (current.patternFill) { ctx.save(); const pattern = current.fillColor.getPattern(ctx, this, getCurrentTransformInverse(ctx), PathType.FILL); patternTransform = getCurrentTransform(ctx); ctx.restore(); ctx.fillStyle = pattern; } let lineWidth = current.lineWidth; const scale = current.textMatrixScale; if (scale === 0 || lineWidth === 0) { const fillStrokeMode = current.textRenderingMode & TextRenderingMode.FILL_STROKE_MASK; if (fillStrokeMode === TextRenderingMode.STROKE || fillStrokeMode === TextRenderingMode.FILL_STROKE) { lineWidth = this.getSinglePixelWidth(); } } else { lineWidth /= scale; } if (fontSizeScale !== 1.0) { ctx.scale(fontSizeScale, fontSizeScale); lineWidth /= fontSizeScale; } ctx.lineWidth = lineWidth; if (font.isInvalidPDFjsFont) { const chars = []; let width = 0; for (const glyph of glyphs) { chars.push(glyph.unicode); width += glyph.width; } ctx.fillText(chars.join(""), 0, 0); current.x += width * widthAdvanceScale * textHScale; ctx.restore(); this.compose(); return undefined; } let x = 0, i; for (i = 0; i < glyphsLength; ++i) { const glyph = glyphs[i]; if (typeof glyph === "number") { x += spacingDir * glyph * fontSize / 1000; continue; } let restoreNeeded = false; const spacing = (glyph.isSpace ? wordSpacing : 0) + charSpacing; const character = glyph.fontChar; const accent = glyph.accent; let scaledX, scaledY; let width = glyph.width; if (vertical) { const vmetric = glyph.vmetric || defaultVMetrics; const vx = -(glyph.vmetric ? vmetric[1] : width * 0.5) * widthAdvanceScale; const vy = vmetric[2] * widthAdvanceScale; width = vmetric ? -vmetric[0] : width; scaledX = vx / fontSizeScale; scaledY = (x + vy) / fontSizeScale; } else { scaledX = x / fontSizeScale; scaledY = 0; } if (font.remeasure && width > 0) { const measuredWidth = ctx.measureText(character).width * 1000 / fontSize * fontSizeScale; if (width < measuredWidth && this.isFontSubpixelAAEnabled) { const characterScaleX = width / measuredWidth; restoreNeeded = true; ctx.save(); ctx.scale(characterScaleX, 1); scaledX /= characterScaleX; } else if (width !== measuredWidth) { scaledX += (width - measuredWidth) / 2000 * fontSize / fontSizeScale; } } if (this.contentVisible && (glyph.isInFont || font.missingFile)) { if (simpleFillText && !accent) { ctx.fillText(character, scaledX, scaledY); } else { this.paintChar(character, scaledX, scaledY, patternTransform); if (accent) { const scaledAccentX = scaledX + fontSize * accent.offset.x / fontSizeScale; const scaledAccentY = scaledY - fontSize * accent.offset.y / fontSizeScale; this.paintChar(accent.fontChar, scaledAccentX, scaledAccentY, patternTransform); } } } const charWidth = vertical ? width * widthAdvanceScale - spacing * fontDirection : width * widthAdvanceScale + spacing * fontDirection; x += charWidth; if (restoreNeeded) { ctx.restore(); } } if (vertical) { current.y -= x; } else { current.x += x * textHScale; } ctx.restore(); this.compose(); return undefined; } showType3Text(glyphs) { const ctx = this.ctx; const current = this.current; const font = current.font; const fontSize = current.fontSize; const fontDirection = current.fontDirection; const spacingDir = font.vertical ? 1 : -1; const charSpacing = current.charSpacing; const wordSpacing = current.wordSpacing; const textHScale = current.textHScale * fontDirection; const fontMatrix = current.fontMatrix || FONT_IDENTITY_MATRIX; const glyphsLength = glyphs.length; const isTextInvisible = current.textRenderingMode === TextRenderingMode.INVISIBLE; let i, glyph, width, spacingLength; if (isTextInvisible || fontSize === 0) { return; } this._cachedScaleForStroking[0] = -1; this._cachedGetSinglePixelWidth = null; ctx.save(); ctx.transform(...current.textMatrix); ctx.translate(current.x, current.y); ctx.scale(textHScale, fontDirection); for (i = 0; i < glyphsLength; ++i) { glyph = glyphs[i]; if (typeof glyph === "number") { spacingLength = spacingDir * glyph * fontSize / 1000; this.ctx.translate(spacingLength, 0); current.x += spacingLength * textHScale; continue; } const spacing = (glyph.isSpace ? wordSpacing : 0) + charSpacing; const operatorList = font.charProcOperatorList[glyph.operatorListId]; if (!operatorList) { warn(`Type3 character "${glyph.operatorListId}" is not available.`); continue; } if (this.contentVisible) { this.processingType3 = glyph; this.save(); ctx.scale(fontSize, fontSize); ctx.transform(...fontMatrix); this.executeOperatorList(operatorList); this.restore(); } const transformed = Util.applyTransform([glyph.width, 0], fontMatrix); width = transformed[0] * fontSize + spacing; ctx.translate(width, 0); current.x += width * textHScale; } ctx.restore(); this.processingType3 = null; } setCharWidth(xWidth, yWidth) {} setCharWidthAndBounds(xWidth, yWidth, llx, lly, urx, ury) { this.ctx.rect(llx, lly, urx - llx, ury - lly); this.ctx.clip(); this.endPath(); } getColorN_Pattern(IR) { let pattern; if (IR[0] === "TilingPattern") { const color = IR[1]; const baseTransform = this.baseTransform || getCurrentTransform(this.ctx); const canvasGraphicsFactory = { createCanvasGraphics: ctx => new CanvasGraphics(ctx, this.commonObjs, this.objs, this.canvasFactory, this.filterFactory, { optionalContentConfig: this.optionalContentConfig, markedContentStack: this.markedContentStack }) }; pattern = new TilingPattern(IR, color, this.ctx, canvasGraphicsFactory, baseTransform); } else { pattern = this._getPattern(IR[1], IR[2]); } return pattern; } setStrokeColorN() { this.current.strokeColor = this.getColorN_Pattern(arguments); } setFillColorN() { this.current.fillColor = this.getColorN_Pattern(arguments); this.current.patternFill = true; } setStrokeRGBColor(r, g, b) { const color = Util.makeHexColor(r, g, b); this.ctx.strokeStyle = color; this.current.strokeColor = color; } setFillRGBColor(r, g, b) { const color = Util.makeHexColor(r, g, b); this.ctx.fillStyle = color; this.current.fillColor = color; this.current.patternFill = false; } _getPattern(objId, matrix = null) { let pattern; if (this.cachedPatterns.has(objId)) { pattern = this.cachedPatterns.get(objId); } else { pattern = getShadingPattern(this.getObject(objId)); this.cachedPatterns.set(objId, pattern); } if (matrix) { pattern.matrix = matrix; } return pattern; } shadingFill(objId) { if (!this.contentVisible) { return; } const ctx = this.ctx; this.save(); const pattern = this._getPattern(objId); ctx.fillStyle = pattern.getPattern(ctx, this, getCurrentTransformInverse(ctx), PathType.SHADING); const inv = getCurrentTransformInverse(ctx); if (inv) { const { width, height } = ctx.canvas; const [x0, y0, x1, y1] = Util.getAxialAlignedBoundingBox([0, 0, width, height], inv); this.ctx.fillRect(x0, y0, x1 - x0, y1 - y0); } else { this.ctx.fillRect(-1e10, -1e10, 2e10, 2e10); } this.compose(this.current.getClippedPathBoundingBox()); this.restore(); } beginInlineImage() { unreachable("Should not call beginInlineImage"); } beginImageData() { unreachable("Should not call beginImageData"); } paintFormXObjectBegin(matrix, bbox) { if (!this.contentVisible) { return; } this.save(); this.baseTransformStack.push(this.baseTransform); if (Array.isArray(matrix) && matrix.length === 6) { this.transform(...matrix); } this.baseTransform = getCurrentTransform(this.ctx); if (bbox) { const width = bbox[2] - bbox[0]; const height = bbox[3] - bbox[1]; this.ctx.rect(bbox[0], bbox[1], width, height); this.current.updateRectMinMax(getCurrentTransform(this.ctx), bbox); this.clip(); this.endPath(); } } paintFormXObjectEnd() { if (!this.contentVisible) { return; } this.restore(); this.baseTransform = this.baseTransformStack.pop(); } beginGroup(group) { if (!this.contentVisible) { return; } this.save(); if (this.inSMaskMode) { this.endSMaskMode(); this.current.activeSMask = null; } const currentCtx = this.ctx; if (!group.isolated) { info("TODO: Support non-isolated groups."); } if (group.knockout) { warn("Knockout groups not supported."); } const currentTransform = getCurrentTransform(currentCtx); if (group.matrix) { currentCtx.transform(...group.matrix); } if (!group.bbox) { throw new Error("Bounding box is required."); } let bounds = Util.getAxialAlignedBoundingBox(group.bbox, getCurrentTransform(currentCtx)); const canvasBounds = [0, 0, currentCtx.canvas.width, currentCtx.canvas.height]; bounds = Util.intersect(bounds, canvasBounds) || [0, 0, 0, 0]; const offsetX = Math.floor(bounds[0]); const offsetY = Math.floor(bounds[1]); const drawnWidth = Math.max(Math.ceil(bounds[2]) - offsetX, 1); const drawnHeight = Math.max(Math.ceil(bounds[3]) - offsetY, 1); this.current.startNewPathAndClipBox([0, 0, drawnWidth, drawnHeight]); let cacheId = "groupAt" + this.groupLevel; if (group.smask) { cacheId += "_smask_" + this.smaskCounter++ % 2; } const scratchCanvas = this.cachedCanvases.getCanvas(cacheId, drawnWidth, drawnHeight); const groupCtx = scratchCanvas.context; groupCtx.translate(-offsetX, -offsetY); groupCtx.transform(...currentTransform); if (group.smask) { this.smaskStack.push({ canvas: scratchCanvas.canvas, context: groupCtx, offsetX, offsetY, subtype: group.smask.subtype, backdrop: group.smask.backdrop, transferMap: group.smask.transferMap || null, startTransformInverse: null }); } else { currentCtx.setTransform(1, 0, 0, 1, 0, 0); currentCtx.translate(offsetX, offsetY); currentCtx.save(); } copyCtxState(currentCtx, groupCtx); this.ctx = groupCtx; this.setGState([["BM", "source-over"], ["ca", 1], ["CA", 1]]); this.groupStack.push(currentCtx); this.groupLevel++; } endGroup(group) { if (!this.contentVisible) { return; } this.groupLevel--; const groupCtx = this.ctx; const ctx = this.groupStack.pop(); this.ctx = ctx; this.ctx.imageSmoothingEnabled = false; if (group.smask) { this.tempSMask = this.smaskStack.pop(); this.restore(); } else { this.ctx.restore(); const currentMtx = getCurrentTransform(this.ctx); this.restore(); this.ctx.save(); this.ctx.setTransform(...currentMtx); const dirtyBox = Util.getAxialAlignedBoundingBox([0, 0, groupCtx.canvas.width, groupCtx.canvas.height], currentMtx); this.ctx.drawImage(groupCtx.canvas, 0, 0); this.ctx.restore(); this.compose(dirtyBox); } } beginAnnotation(id, rect, transform, matrix, hasOwnCanvas) { this.#restoreInitialState(); resetCtxToDefault(this.ctx); this.ctx.save(); this.save(); if (this.baseTransform) { this.ctx.setTransform(...this.baseTransform); } if (Array.isArray(rect) && rect.length === 4) { const width = rect[2] - rect[0]; const height = rect[3] - rect[1]; if (hasOwnCanvas && this.annotationCanvasMap) { transform = transform.slice(); transform[4] -= rect[0]; transform[5] -= rect[1]; rect = rect.slice(); rect[0] = rect[1] = 0; rect[2] = width; rect[3] = height; const [scaleX, scaleY] = Util.singularValueDecompose2dScale(getCurrentTransform(this.ctx)); const { viewportScale } = this; const canvasWidth = Math.ceil(width * this.outputScaleX * viewportScale); const canvasHeight = Math.ceil(height * this.outputScaleY * viewportScale); this.annotationCanvas = this.canvasFactory.create(canvasWidth, canvasHeight); const { canvas, context } = this.annotationCanvas; this.annotationCanvasMap.set(id, canvas); this.annotationCanvas.savedCtx = this.ctx; this.ctx = context; this.ctx.save(); this.ctx.setTransform(scaleX, 0, 0, -scaleY, 0, height * scaleY); resetCtxToDefault(this.ctx); } else { resetCtxToDefault(this.ctx); this.ctx.rect(rect[0], rect[1], width, height); this.ctx.clip(); this.endPath(); } } this.current = new CanvasExtraState(this.ctx.canvas.width, this.ctx.canvas.height); this.transform(...transform); this.transform(...matrix); } endAnnotation() { if (this.annotationCanvas) { this.ctx.restore(); this.#drawFilter(); this.ctx = this.annotationCanvas.savedCtx; delete this.annotationCanvas.savedCtx; delete this.annotationCanvas; } } paintImageMaskXObject(img) { if (!this.contentVisible) { return; } const count = img.count; img = this.getObject(img.data, img); img.count = count; const ctx = this.ctx; const glyph = this.processingType3; if (glyph) { if (glyph.compiled === undefined) { glyph.compiled = compileType3Glyph(img); } if (glyph.compiled) { glyph.compiled(ctx); return; } } const mask = this._createMaskCanvas(img); const maskCanvas = mask.canvas; ctx.save(); ctx.setTransform(1, 0, 0, 1, 0, 0); ctx.drawImage(maskCanvas, mask.offsetX, mask.offsetY); ctx.restore(); this.compose(); } paintImageMaskXObjectRepeat(img, scaleX, skewX = 0, skewY = 0, scaleY, positions) { if (!this.contentVisible) { return; } img = this.getObject(img.data, img); const ctx = this.ctx; ctx.save(); const currentTransform = getCurrentTransform(ctx); ctx.transform(scaleX, skewX, skewY, scaleY, 0, 0); const mask = this._createMaskCanvas(img); ctx.setTransform(1, 0, 0, 1, mask.offsetX - currentTransform[4], mask.offsetY - currentTransform[5]); for (let i = 0, ii = positions.length; i < ii; i += 2) { const trans = Util.transform(currentTransform, [scaleX, skewX, skewY, scaleY, positions[i], positions[i + 1]]); const [x, y] = Util.applyTransform([0, 0], trans); ctx.drawImage(mask.canvas, x, y); } ctx.restore(); this.compose(); } paintImageMaskXObjectGroup(images) { if (!this.contentVisible) { return; } const ctx = this.ctx; const fillColor = this.current.fillColor; const isPatternFill = this.current.patternFill; for (const image of images) { const { data, width, height, transform } = image; const maskCanvas = this.cachedCanvases.getCanvas("maskCanvas", width, height); const maskCtx = maskCanvas.context; maskCtx.save(); const img = this.getObject(data, image); putBinaryImageMask(maskCtx, img); maskCtx.globalCompositeOperation = "source-in"; maskCtx.fillStyle = isPatternFill ? fillColor.getPattern(maskCtx, this, getCurrentTransformInverse(ctx), PathType.FILL) : fillColor; maskCtx.fillRect(0, 0, width, height); maskCtx.restore(); ctx.save(); ctx.transform(...transform); ctx.scale(1, -1); drawImageAtIntegerCoords(ctx, maskCanvas.canvas, 0, 0, width, height, 0, -1, 1, 1); ctx.restore(); } this.compose(); } paintImageXObject(objId) { if (!this.contentVisible) { return; } const imgData = this.getObject(objId); if (!imgData) { warn("Dependent image isn't ready yet"); return; } this.paintInlineImageXObject(imgData); } paintImageXObjectRepeat(objId, scaleX, scaleY, positions) { if (!this.contentVisible) { return; } const imgData = this.getObject(objId); if (!imgData) { warn("Dependent image isn't ready yet"); return; } const width = imgData.width; const height = imgData.height; const map = []; for (let i = 0, ii = positions.length; i < ii; i += 2) { map.push({ transform: [scaleX, 0, 0, scaleY, positions[i], positions[i + 1]], x: 0, y: 0, w: width, h: height }); } this.paintInlineImageXObjectGroup(imgData, map); } applyTransferMapsToCanvas(ctx) { if (this.current.transferMaps !== "none") { ctx.filter = this.current.transferMaps; ctx.drawImage(ctx.canvas, 0, 0); ctx.filter = "none"; } return ctx.canvas; } applyTransferMapsToBitmap(imgData) { if (this.current.transferMaps === "none") { return imgData.bitmap; } const { bitmap, width, height } = imgData; const tmpCanvas = this.cachedCanvases.getCanvas("inlineImage", width, height); const tmpCtx = tmpCanvas.context; tmpCtx.filter = this.current.transferMaps; tmpCtx.drawImage(bitmap, 0, 0); tmpCtx.filter = "none"; return tmpCanvas.canvas; } paintInlineImageXObject(imgData) { if (!this.contentVisible) { return; } const width = imgData.width; const height = imgData.height; const ctx = this.ctx; this.save(); const { filter } = ctx; if (filter !== "none" && filter !== "") { ctx.filter = "none"; } ctx.scale(1 / width, -1 / height); let imgToPaint; if (imgData.bitmap) { imgToPaint = this.applyTransferMapsToBitmap(imgData); } else if (typeof HTMLElement === "function" && imgData instanceof HTMLElement || !imgData.data) { imgToPaint = imgData; } else { const tmpCanvas = this.cachedCanvases.getCanvas("inlineImage", width, height); const tmpCtx = tmpCanvas.context; putBinaryImageData(tmpCtx, imgData); imgToPaint = this.applyTransferMapsToCanvas(tmpCtx); } const scaled = this._scaleImage(imgToPaint, getCurrentTransformInverse(ctx)); ctx.imageSmoothingEnabled = getImageSmoothingEnabled(getCurrentTransform(ctx), imgData.interpolate); drawImageAtIntegerCoords(ctx, scaled.img, 0, 0, scaled.paintWidth, scaled.paintHeight, 0, -height, width, height); this.compose(); this.restore(); } paintInlineImageXObjectGroup(imgData, map) { if (!this.contentVisible) { return; } const ctx = this.ctx; let imgToPaint; if (imgData.bitmap) { imgToPaint = imgData.bitmap; } else { const w = imgData.width; const h = imgData.height; const tmpCanvas = this.cachedCanvases.getCanvas("inlineImage", w, h); const tmpCtx = tmpCanvas.context; putBinaryImageData(tmpCtx, imgData); imgToPaint = this.applyTransferMapsToCanvas(tmpCtx); } for (const entry of map) { ctx.save(); ctx.transform(...entry.transform); ctx.scale(1, -1); drawImageAtIntegerCoords(ctx, imgToPaint, entry.x, entry.y, entry.w, entry.h, 0, -1, 1, 1); ctx.restore(); } this.compose(); } paintSolidColorImageMask() { if (!this.contentVisible) { return; } this.ctx.fillRect(0, 0, 1, 1); this.compose(); } markPoint(tag) {} markPointProps(tag, properties) {} beginMarkedContent(tag) { this.markedContentStack.push({ visible: true }); } beginMarkedContentProps(tag, properties) { if (tag === "OC") { this.markedContentStack.push({ visible: this.optionalContentConfig.isVisible(properties) }); } else { this.markedContentStack.push({ visible: true }); } this.contentVisible = this.isContentVisible(); } endMarkedContent() { this.markedContentStack.pop(); this.contentVisible = this.isContentVisible(); } beginCompat() {} endCompat() {} consumePath(clipBox) { const isEmpty = this.current.isEmptyClip(); if (this.pendingClip) { this.current.updateClipFromPath(); } if (!this.pendingClip) { this.compose(clipBox); } const ctx = this.ctx; if (this.pendingClip) { if (!isEmpty) { if (this.pendingClip === EO_CLIP) { ctx.clip("evenodd"); } else { ctx.clip(); } } this.pendingClip = null; } this.current.startNewPathAndClipBox(this.current.clipBox); ctx.beginPath(); } getSinglePixelWidth() { if (!this._cachedGetSinglePixelWidth) { const m = getCurrentTransform(this.ctx); if (m[1] === 0 && m[2] === 0) { this._cachedGetSinglePixelWidth = 1 / Math.min(Math.abs(m[0]), Math.abs(m[3])); } else { const absDet = Math.abs(m[0] * m[3] - m[2] * m[1]); const normX = Math.hypot(m[0], m[2]); const normY = Math.hypot(m[1], m[3]); this._cachedGetSinglePixelWidth = Math.max(normX, normY) / absDet; } } return this._cachedGetSinglePixelWidth; } getScaleForStroking() { if (this._cachedScaleForStroking[0] === -1) { const { lineWidth } = this.current; const { a, b, c, d } = this.ctx.getTransform(); let scaleX, scaleY; if (b === 0 && c === 0) { const normX = Math.abs(a); const normY = Math.abs(d); if (normX === normY) { if (lineWidth === 0) { scaleX = scaleY = 1 / normX; } else { const scaledLineWidth = normX * lineWidth; scaleX = scaleY = scaledLineWidth < 1 ? 1 / scaledLineWidth : 1; } } else if (lineWidth === 0) { scaleX = 1 / normX; scaleY = 1 / normY; } else { const scaledXLineWidth = normX * lineWidth; const scaledYLineWidth = normY * lineWidth; scaleX = scaledXLineWidth < 1 ? 1 / scaledXLineWidth : 1; scaleY = scaledYLineWidth < 1 ? 1 / scaledYLineWidth : 1; } } else { const absDet = Math.abs(a * d - b * c); const normX = Math.hypot(a, b); const normY = Math.hypot(c, d); if (lineWidth === 0) { scaleX = normY / absDet; scaleY = normX / absDet; } else { const baseArea = lineWidth * absDet; scaleX = normY > baseArea ? normY / baseArea : 1; scaleY = normX > baseArea ? normX / baseArea : 1; } } this._cachedScaleForStroking[0] = scaleX; this._cachedScaleForStroking[1] = scaleY; } return this._cachedScaleForStroking; } rescaleAndStroke(saveRestore) { const { ctx } = this; const { lineWidth } = this.current; const [scaleX, scaleY] = this.getScaleForStroking(); ctx.lineWidth = lineWidth || 1; if (scaleX === 1 && scaleY === 1) { ctx.stroke(); return; } const dashes = ctx.getLineDash(); if (saveRestore) { ctx.save(); } ctx.scale(scaleX, scaleY); if (dashes.length > 0) { const scale = Math.max(scaleX, scaleY); ctx.setLineDash(dashes.map(x => x / scale)); ctx.lineDashOffset /= scale; } ctx.stroke(); if (saveRestore) { ctx.restore(); } } isContentVisible() { for (let i = this.markedContentStack.length - 1; i >= 0; i--) { if (!this.markedContentStack[i].visible) { return false; } } return true; } } for (const op in OPS) { if (CanvasGraphics.prototype[op] !== undefined) { CanvasGraphics.prototype[OPS[op]] = CanvasGraphics.prototype[op]; } } ;// CONCATENATED MODULE: ./src/display/text_layer.js const MAX_TEXT_DIVS_TO_RENDER = 100000; const DEFAULT_FONT_SIZE = 30; const DEFAULT_FONT_ASCENT = 0.8; const ascentCache = new Map(); let _canvasContext = null; function getCtx() { if (!_canvasContext) { const canvas = document.createElement("canvas"); canvas.className = "hiddenCanvasElement"; document.body.append(canvas); _canvasContext = canvas.getContext("2d", { alpha: false }); } return _canvasContext; } function cleanupTextLayer() { _canvasContext?.canvas.remove(); _canvasContext = null; } function getAscent(fontFamily) { const cachedAscent = ascentCache.get(fontFamily); if (cachedAscent) { return cachedAscent; } const ctx = getCtx(); const savedFont = ctx.font; ctx.canvas.width = ctx.canvas.height = DEFAULT_FONT_SIZE; ctx.font = `${DEFAULT_FONT_SIZE}px ${fontFamily}`; const metrics = ctx.measureText(""); let ascent = metrics.fontBoundingBoxAscent; let descent = Math.abs(metrics.fontBoundingBoxDescent); if (ascent) { const ratio = ascent / (ascent + descent); ascentCache.set(fontFamily, ratio); ctx.canvas.width = ctx.canvas.height = 0; ctx.font = savedFont; return ratio; } ctx.strokeStyle = "red"; ctx.clearRect(0, 0, DEFAULT_FONT_SIZE, DEFAULT_FONT_SIZE); ctx.strokeText("g", 0, 0); let pixels = ctx.getImageData(0, 0, DEFAULT_FONT_SIZE, DEFAULT_FONT_SIZE).data; descent = 0; for (let i = pixels.length - 1 - 3; i >= 0; i -= 4) { if (pixels[i] > 0) { descent = Math.ceil(i / 4 / DEFAULT_FONT_SIZE); break; } } ctx.clearRect(0, 0, DEFAULT_FONT_SIZE, DEFAULT_FONT_SIZE); ctx.strokeText("A", 0, DEFAULT_FONT_SIZE); pixels = ctx.getImageData(0, 0, DEFAULT_FONT_SIZE, DEFAULT_FONT_SIZE).data; ascent = 0; for (let i = 0, ii = pixels.length; i < ii; i += 4) { if (pixels[i] > 0) { ascent = DEFAULT_FONT_SIZE - Math.floor(i / 4 / DEFAULT_FONT_SIZE); break; } } ctx.canvas.width = ctx.canvas.height = 0; ctx.font = savedFont; if (ascent) { const ratio = ascent / (ascent + descent); ascentCache.set(fontFamily, ratio); return ratio; } ascentCache.set(fontFamily, DEFAULT_FONT_ASCENT); return DEFAULT_FONT_ASCENT; } function appendText(task, geom, styles) { const textDiv = document.createElement("span"); const textDivProperties = { angle: 0, canvasWidth: 0, hasText: geom.str !== "", hasEOL: geom.hasEOL, fontSize: 0 }; task._textDivs.push(textDiv); const tx = Util.transform(task._transform, geom.transform); let angle = Math.atan2(tx[1], tx[0]); const style = styles[geom.fontName]; if (style.vertical) { angle += Math.PI / 2; } const fontFamily = task._fontInspectorEnabled && style.fontSubstitution || style.fontFamily; const fontHeight = Math.hypot(tx[2], tx[3]); const fontAscent = fontHeight * getAscent(fontFamily); let left, top; if (angle === 0) { left = tx[4]; top = tx[5] - fontAscent; } else { left = tx[4] + fontAscent * Math.sin(angle); top = tx[5] - fontAscent * Math.cos(angle); } const scaleFactorStr = "calc(var(--scale-factor)*"; const divStyle = textDiv.style; if (task._container === task._rootContainer) { divStyle.left = `${(100 * left / task._pageWidth).toFixed(2)}%`; divStyle.top = `${(100 * top / task._pageHeight).toFixed(2)}%`; } else { divStyle.left = `${scaleFactorStr}${left.toFixed(2)}px)`; divStyle.top = `${scaleFactorStr}${top.toFixed(2)}px)`; } divStyle.fontSize = `${scaleFactorStr}${fontHeight.toFixed(2)}px)`; divStyle.fontFamily = fontFamily; textDivProperties.fontSize = fontHeight; textDiv.setAttribute("role", "presentation"); textDiv.textContent = geom.str; textDiv.dir = geom.dir; if (task._fontInspectorEnabled) { textDiv.dataset.fontName = style.fontSubstitutionLoadedName || geom.fontName; } if (angle !== 0) { textDivProperties.angle = angle * (180 / Math.PI); } let shouldScaleText = false; if (geom.str.length > 1) { shouldScaleText = true; } else if (geom.str !== " " && geom.transform[0] !== geom.transform[3]) { const absScaleX = Math.abs(geom.transform[0]), absScaleY = Math.abs(geom.transform[3]); if (absScaleX !== absScaleY && Math.max(absScaleX, absScaleY) / Math.min(absScaleX, absScaleY) > 1.5) { shouldScaleText = true; } } if (shouldScaleText) { textDivProperties.canvasWidth = style.vertical ? geom.height : geom.width; } task._textDivProperties.set(textDiv, textDivProperties); if (task._isReadableStream) { task._layoutText(textDiv); } } function layout(params) { const { div, scale, properties, ctx, prevFontSize, prevFontFamily } = params; const { style } = div; let transform = ""; if (properties.canvasWidth !== 0 && properties.hasText) { const { fontFamily } = style; const { canvasWidth, fontSize } = properties; if (prevFontSize !== fontSize || prevFontFamily !== fontFamily) { ctx.font = `${fontSize * scale}px ${fontFamily}`; params.prevFontSize = fontSize; params.prevFontFamily = fontFamily; } const { width } = ctx.measureText(div.textContent); if (width > 0) { transform = `scaleX(${canvasWidth * scale / width})`; } } if (properties.angle !== 0) { transform = `rotate(${properties.angle}deg) ${transform}`; } if (transform.length > 0) { style.transform = transform; } } function render(task) { if (task._canceled) { return; } const textDivs = task._textDivs; const capability = task._capability; const textDivsLength = textDivs.length; if (textDivsLength > MAX_TEXT_DIVS_TO_RENDER) { capability.resolve(); return; } if (!task._isReadableStream) { for (const textDiv of textDivs) { task._layoutText(textDiv); } } capability.resolve(); } class TextLayerRenderTask { constructor({ textContentSource, container, viewport, textDivs, textDivProperties, textContentItemsStr }) { this._textContentSource = textContentSource; this._isReadableStream = textContentSource instanceof ReadableStream; this._container = this._rootContainer = container; this._textDivs = textDivs || []; this._textContentItemsStr = textContentItemsStr || []; this._fontInspectorEnabled = !!globalThis.FontInspector?.enabled; this._reader = null; this._textDivProperties = textDivProperties || new WeakMap(); this._canceled = false; this._capability = Promise.withResolvers(); this._layoutTextParams = { prevFontSize: null, prevFontFamily: null, div: null, scale: viewport.scale * (globalThis.devicePixelRatio || 1), properties: null, ctx: getCtx() }; const { pageWidth, pageHeight, pageX, pageY } = viewport.rawDims; this._transform = [1, 0, 0, -1, -pageX, pageY + pageHeight]; this._pageWidth = pageWidth; this._pageHeight = pageHeight; setLayerDimensions(container, viewport); this._capability.promise.finally(() => { this._layoutTextParams = null; }).catch(() => {}); } get promise() { return this._capability.promise; } cancel() { this._canceled = true; if (this._reader) { this._reader.cancel(new AbortException("TextLayer task cancelled.")).catch(() => {}); this._reader = null; } this._capability.reject(new AbortException("TextLayer task cancelled.")); } _processItems(items, styleCache) { for (const item of items) { if (item.str === undefined) { if (item.type === "beginMarkedContentProps" || item.type === "beginMarkedContent") { const parent = this._container; this._container = document.createElement("span"); this._container.classList.add("markedContent"); if (item.id !== null) { this._container.setAttribute("id", `${item.id}`); } parent.append(this._container); } else if (item.type === "endMarkedContent") { this._container = this._container.parentNode; } continue; } this._textContentItemsStr.push(item.str); appendText(this, item, styleCache); } } _layoutText(textDiv) { const textDivProperties = this._layoutTextParams.properties = this._textDivProperties.get(textDiv); this._layoutTextParams.div = textDiv; layout(this._layoutTextParams); if (textDivProperties.hasText) { this._container.append(textDiv); } if (textDivProperties.hasEOL) { const br = document.createElement("br"); br.setAttribute("role", "presentation"); this._container.append(br); } } _render() { const { promise, resolve, reject } = Promise.withResolvers(); let styleCache = Object.create(null); if (this._isReadableStream) { const pump = () => { this._reader.read().then(({ value, done }) => { if (done) { resolve(); return; } Object.assign(styleCache, value.styles); this._processItems(value.items, styleCache); pump(); }, reject); }; this._reader = this._textContentSource.getReader(); pump(); } else if (this._textContentSource) { const { items, styles } = this._textContentSource; this._processItems(items, styles); resolve(); } else { throw new Error('No "textContentSource" parameter specified.'); } promise.then(() => { styleCache = null; render(this); }, this._capability.reject); } } function renderTextLayer(params) { const task = new TextLayerRenderTask(params); task._render(); return task; } function updateTextLayer({ container, viewport, textDivs, textDivProperties, mustRotate = true, mustRescale = true }) { if (mustRotate) { setLayerDimensions(container, { rotation: viewport.rotation }); } if (mustRescale) { const ctx = getCtx(); const scale = viewport.scale * (globalThis.devicePixelRatio || 1); const params = { prevFontSize: null, prevFontFamily: null, div: null, scale, properties: null, ctx }; for (const div of textDivs) { params.properties = textDivProperties.get(div); params.div = div; layout(params); } } } ;// CONCATENATED MODULE: ./src/display/worker_options.js class GlobalWorkerOptions { static #port = null; static #src = ""; static get workerPort() { return this.#port; } static set workerPort(val) { if (!(typeof Worker !== "undefined" && val instanceof Worker) && val !== null) { throw new Error("Invalid `workerPort` type."); } this.#port = val; } static get workerSrc() { return this.#src; } static set workerSrc(val) { if (typeof val !== "string") { throw new Error("Invalid `workerSrc` type."); } this.#src = val; } } ;// CONCATENATED MODULE: ./src/shared/message_handler.js const CallbackKind = { UNKNOWN: 0, DATA: 1, ERROR: 2 }; const StreamKind = { UNKNOWN: 0, CANCEL: 1, CANCEL_COMPLETE: 2, CLOSE: 3, ENQUEUE: 4, ERROR: 5, PULL: 6, PULL_COMPLETE: 7, START_COMPLETE: 8 }; function wrapReason(reason) { if (!(reason instanceof Error || typeof reason === "object" && reason !== null)) { unreachable('wrapReason: Expected "reason" to be a (possibly cloned) Error.'); } switch (reason.name) { case "AbortException": return new AbortException(reason.message); case "MissingPDFException": return new MissingPDFException(reason.message); case "PasswordException": return new PasswordException(reason.message, reason.code); case "UnexpectedResponseException": return new UnexpectedResponseException(reason.message, reason.status); case "UnknownErrorException": return new UnknownErrorException(reason.message, reason.details); default: return new UnknownErrorException(reason.message, reason.toString()); } } class MessageHandler { constructor(sourceName, targetName, comObj) { this.sourceName = sourceName; this.targetName = targetName; this.comObj = comObj; this.callbackId = 1; this.streamId = 1; this.streamSinks = Object.create(null); this.streamControllers = Object.create(null); this.callbackCapabilities = Object.create(null); this.actionHandler = Object.create(null); this._onComObjOnMessage = event => { const data = event.data; if (data.targetName !== this.sourceName) { return; } if (data.stream) { this.#processStreamMessage(data); return; } if (data.callback) { const callbackId = data.callbackId; const capability = this.callbackCapabilities[callbackId]; if (!capability) { throw new Error(`Cannot resolve callback ${callbackId}`); } delete this.callbackCapabilities[callbackId]; if (data.callback === CallbackKind.DATA) { capability.resolve(data.data); } else if (data.callback === CallbackKind.ERROR) { capability.reject(wrapReason(data.reason)); } else { throw new Error("Unexpected callback case"); } return; } const action = this.actionHandler[data.action]; if (!action) { throw new Error(`Unknown action from worker: ${data.action}`); } if (data.callbackId) { const cbSourceName = this.sourceName; const cbTargetName = data.sourceName; new Promise(function (resolve) { resolve(action(data.data)); }).then(function (result) { comObj.postMessage({ sourceName: cbSourceName, targetName: cbTargetName, callback: CallbackKind.DATA, callbackId: data.callbackId, data: result }); }, function (reason) { comObj.postMessage({ sourceName: cbSourceName, targetName: cbTargetName, callback: CallbackKind.ERROR, callbackId: data.callbackId, reason: wrapReason(reason) }); }); return; } if (data.streamId) { this.#createStreamSink(data); return; } action(data.data); }; comObj.addEventListener("message", this._onComObjOnMessage); } on(actionName, handler) { const ah = this.actionHandler; if (ah[actionName]) { throw new Error(`There is already an actionName called "${actionName}"`); } ah[actionName] = handler; } send(actionName, data, transfers) { this.comObj.postMessage({ sourceName: this.sourceName, targetName: this.targetName, action: actionName, data }, transfers); } sendWithPromise(actionName, data, transfers) { const callbackId = this.callbackId++; const capability = Promise.withResolvers(); this.callbackCapabilities[callbackId] = capability; try { this.comObj.postMessage({ sourceName: this.sourceName, targetName: this.targetName, action: actionName, callbackId, data }, transfers); } catch (ex) { capability.reject(ex); } return capability.promise; } sendWithStream(actionName, data, queueingStrategy, transfers) { const streamId = this.streamId++, sourceName = this.sourceName, targetName = this.targetName, comObj = this.comObj; return new ReadableStream({ start: controller => { const startCapability = Promise.withResolvers(); this.streamControllers[streamId] = { controller, startCall: startCapability, pullCall: null, cancelCall: null, isClosed: false }; comObj.postMessage({ sourceName, targetName, action: actionName, streamId, data, desiredSize: controller.desiredSize }, transfers); return startCapability.promise; }, pull: controller => { const pullCapability = Promise.withResolvers(); this.streamControllers[streamId].pullCall = pullCapability; comObj.postMessage({ sourceName, targetName, stream: StreamKind.PULL, streamId, desiredSize: controller.desiredSize }); return pullCapability.promise; }, cancel: reason => { assert(reason instanceof Error, "cancel must have a valid reason"); const cancelCapability = Promise.withResolvers(); this.streamControllers[streamId].cancelCall = cancelCapability; this.streamControllers[streamId].isClosed = true; comObj.postMessage({ sourceName, targetName, stream: StreamKind.CANCEL, streamId, reason: wrapReason(reason) }); return cancelCapability.promise; } }, queueingStrategy); } #createStreamSink(data) { const streamId = data.streamId, sourceName = this.sourceName, targetName = data.sourceName, comObj = this.comObj; const self = this, action = this.actionHandler[data.action]; const streamSink = { enqueue(chunk, size = 1, transfers) { if (this.isCancelled) { return; } const lastDesiredSize = this.desiredSize; this.desiredSize -= size; if (lastDesiredSize > 0 && this.desiredSize <= 0) { this.sinkCapability = Promise.withResolvers(); this.ready = this.sinkCapability.promise; } comObj.postMessage({ sourceName, targetName, stream: StreamKind.ENQUEUE, streamId, chunk }, transfers); }, close() { if (this.isCancelled) { return; } this.isCancelled = true; comObj.postMessage({ sourceName, targetName, stream: StreamKind.CLOSE, streamId }); delete self.streamSinks[streamId]; }, error(reason) { assert(reason instanceof Error, "error must have a valid reason"); if (this.isCancelled) { return; } this.isCancelled = true; comObj.postMessage({ sourceName, targetName, stream: StreamKind.ERROR, streamId, reason: wrapReason(reason) }); }, sinkCapability: Promise.withResolvers(), onPull: null, onCancel: null, isCancelled: false, desiredSize: data.desiredSize, ready: null }; streamSink.sinkCapability.resolve(); streamSink.ready = streamSink.sinkCapability.promise; this.streamSinks[streamId] = streamSink; new Promise(function (resolve) { resolve(action(data.data, streamSink)); }).then(function () { comObj.postMessage({ sourceName, targetName, stream: StreamKind.START_COMPLETE, streamId, success: true }); }, function (reason) { comObj.postMessage({ sourceName, targetName, stream: StreamKind.START_COMPLETE, streamId, reason: wrapReason(reason) }); }); } #processStreamMessage(data) { const streamId = data.streamId, sourceName = this.sourceName, targetName = data.sourceName, comObj = this.comObj; const streamController = this.streamControllers[streamId], streamSink = this.streamSinks[streamId]; switch (data.stream) { case StreamKind.START_COMPLETE: if (data.success) { streamController.startCall.resolve(); } else { streamController.startCall.reject(wrapReason(data.reason)); } break; case StreamKind.PULL_COMPLETE: if (data.success) { streamController.pullCall.resolve(); } else { streamController.pullCall.reject(wrapReason(data.reason)); } break; case StreamKind.PULL: if (!streamSink) { comObj.postMessage({ sourceName, targetName, stream: StreamKind.PULL_COMPLETE, streamId, success: true }); break; } if (streamSink.desiredSize <= 0 && data.desiredSize > 0) { streamSink.sinkCapability.resolve(); } streamSink.desiredSize = data.desiredSize; new Promise(function (resolve) { resolve(streamSink.onPull?.()); }).then(function () { comObj.postMessage({ sourceName, targetName, stream: StreamKind.PULL_COMPLETE, streamId, success: true }); }, function (reason) { comObj.postMessage({ sourceName, targetName, stream: StreamKind.PULL_COMPLETE, streamId, reason: wrapReason(reason) }); }); break; case StreamKind.ENQUEUE: assert(streamController, "enqueue should have stream controller"); if (streamController.isClosed) { break; } streamController.controller.enqueue(data.chunk); break; case StreamKind.CLOSE: assert(streamController, "close should have stream controller"); if (streamController.isClosed) { break; } streamController.isClosed = true; streamController.controller.close(); this.#deleteStreamController(streamController, streamId); break; case StreamKind.ERROR: assert(streamController, "error should have stream controller"); streamController.controller.error(wrapReason(data.reason)); this.#deleteStreamController(streamController, streamId); break; case StreamKind.CANCEL_COMPLETE: if (data.success) { streamController.cancelCall.resolve(); } else { streamController.cancelCall.reject(wrapReason(data.reason)); } this.#deleteStreamController(streamController, streamId); break; case StreamKind.CANCEL: if (!streamSink) { break; } new Promise(function (resolve) { resolve(streamSink.onCancel?.(wrapReason(data.reason))); }).then(function () { comObj.postMessage({ sourceName, targetName, stream: StreamKind.CANCEL_COMPLETE, streamId, success: true }); }, function (reason) { comObj.postMessage({ sourceName, targetName, stream: StreamKind.CANCEL_COMPLETE, streamId, reason: wrapReason(reason) }); }); streamSink.sinkCapability.reject(wrapReason(data.reason)); streamSink.isCancelled = true; delete this.streamSinks[streamId]; break; default: throw new Error("Unexpected stream case"); } } async #deleteStreamController(streamController, streamId) { await Promise.allSettled([streamController.startCall?.promise, streamController.pullCall?.promise, streamController.cancelCall?.promise]); delete this.streamControllers[streamId]; } destroy() { this.comObj.removeEventListener("message", this._onComObjOnMessage); } } ;// CONCATENATED MODULE: ./src/display/metadata.js class Metadata { #metadataMap; #data; constructor({ parsedData, rawData }) { this.#metadataMap = parsedData; this.#data = rawData; } getRaw() { return this.#data; } get(name) { return this.#metadataMap.get(name) ?? null; } getAll() { return objectFromMap(this.#metadataMap); } has(name) { return this.#metadataMap.has(name); } } ;// CONCATENATED MODULE: ./src/display/optional_content_config.js const INTERNAL = Symbol("INTERNAL"); class OptionalContentGroup { #isDisplay = false; #isPrint = false; #userSet = false; #visible = true; constructor(renderingIntent, { name, intent, usage }) { this.#isDisplay = !!(renderingIntent & RenderingIntentFlag.DISPLAY); this.#isPrint = !!(renderingIntent & RenderingIntentFlag.PRINT); this.name = name; this.intent = intent; this.usage = usage; } get visible() { if (this.#userSet) { return this.#visible; } if (!this.#visible) { return false; } const { print, view } = this.usage; if (this.#isDisplay) { return view?.viewState !== "OFF"; } else if (this.#isPrint) { return print?.printState !== "OFF"; } return true; } _setVisible(internal, visible, userSet = false) { if (internal !== INTERNAL) { unreachable("Internal method `_setVisible` called."); } this.#userSet = userSet; this.#visible = visible; } } class OptionalContentConfig { #cachedGetHash = null; #groups = new Map(); #initialHash = null; #order = null; constructor(data, renderingIntent = RenderingIntentFlag.DISPLAY) { this.renderingIntent = renderingIntent; this.name = null; this.creator = null; if (data === null) { return; } this.name = data.name; this.creator = data.creator; this.#order = data.order; for (const group of data.groups) { this.#groups.set(group.id, new OptionalContentGroup(renderingIntent, group)); } if (data.baseState === "OFF") { for (const group of this.#groups.values()) { group._setVisible(INTERNAL, false); } } for (const on of data.on) { this.#groups.get(on)._setVisible(INTERNAL, true); } for (const off of data.off) { this.#groups.get(off)._setVisible(INTERNAL, false); } this.#initialHash = this.getHash(); } #evaluateVisibilityExpression(array) { const length = array.length; if (length < 2) { return true; } const operator = array[0]; for (let i = 1; i < length; i++) { const element = array[i]; let state; if (Array.isArray(element)) { state = this.#evaluateVisibilityExpression(element); } else if (this.#groups.has(element)) { state = this.#groups.get(element).visible; } else { warn(`Optional content group not found: ${element}`); return true; } switch (operator) { case "And": if (!state) { return false; } break; case "Or": if (state) { return true; } break; case "Not": return !state; default: return true; } } return operator === "And"; } isVisible(group) { if (this.#groups.size === 0) { return true; } if (!group) { info("Optional content group not defined."); return true; } if (group.type === "OCG") { if (!this.#groups.has(group.id)) { warn(`Optional content group not found: ${group.id}`); return true; } return this.#groups.get(group.id).visible; } else if (group.type === "OCMD") { if (group.expression) { return this.#evaluateVisibilityExpression(group.expression); } if (!group.policy || group.policy === "AnyOn") { for (const id of group.ids) { if (!this.#groups.has(id)) { warn(`Optional content group not found: ${id}`); return true; } if (this.#groups.get(id).visible) { return true; } } return false; } else if (group.policy === "AllOn") { for (const id of group.ids) { if (!this.#groups.has(id)) { warn(`Optional content group not found: ${id}`); return true; } if (!this.#groups.get(id).visible) { return false; } } return true; } else if (group.policy === "AnyOff") { for (const id of group.ids) { if (!this.#groups.has(id)) { warn(`Optional content group not found: ${id}`); return true; } if (!this.#groups.get(id).visible) { return true; } } return false; } else if (group.policy === "AllOff") { for (const id of group.ids) { if (!this.#groups.has(id)) { warn(`Optional content group not found: ${id}`); return true; } if (this.#groups.get(id).visible) { return false; } } return true; } warn(`Unknown optional content policy ${group.policy}.`); return true; } warn(`Unknown group type ${group.type}.`); return true; } setVisibility(id, visible = true) { const group = this.#groups.get(id); if (!group) { warn(`Optional content group not found: ${id}`); return; } group._setVisible(INTERNAL, !!visible, true); this.#cachedGetHash = null; } setOCGState({ state, preserveRB }) { let operator; for (const elem of state) { switch (elem) { case "ON": case "OFF": case "Toggle": operator = elem; continue; } const group = this.#groups.get(elem); if (!group) { continue; } switch (operator) { case "ON": group._setVisible(INTERNAL, true); break; case "OFF": group._setVisible(INTERNAL, false); break; case "Toggle": group._setVisible(INTERNAL, !group.visible); break; } } this.#cachedGetHash = null; } get hasInitialVisibility() { return this.#initialHash === null || this.getHash() === this.#initialHash; } getOrder() { if (!this.#groups.size) { return null; } if (this.#order) { return this.#order.slice(); } return [...this.#groups.keys()]; } getGroups() { return this.#groups.size > 0 ? objectFromMap(this.#groups) : null; } getGroup(id) { return this.#groups.get(id) || null; } getHash() { if (this.#cachedGetHash !== null) { return this.#cachedGetHash; } const hash = new MurmurHash3_64(); for (const [id, group] of this.#groups) { hash.update(`${id}:${group.visible}`); } return this.#cachedGetHash = hash.hexdigest(); } } ;// CONCATENATED MODULE: ./src/display/transport_stream.js class PDFDataTransportStream { constructor(pdfDataRangeTransport, { disableRange = false, disableStream = false }) { assert(pdfDataRangeTransport, 'PDFDataTransportStream - missing required "pdfDataRangeTransport" argument.'); const { length, initialData, progressiveDone, contentDispositionFilename } = pdfDataRangeTransport; this._queuedChunks = []; this._progressiveDone = progressiveDone; this._contentDispositionFilename = contentDispositionFilename; if (initialData?.length > 0) { const buffer = initialData instanceof Uint8Array && initialData.byteLength === initialData.buffer.byteLength ? initialData.buffer : new Uint8Array(initialData).buffer; this._queuedChunks.push(buffer); } this._pdfDataRangeTransport = pdfDataRangeTransport; this._isStreamingSupported = !disableStream; this._isRangeSupported = !disableRange; this._contentLength = length; this._fullRequestReader = null; this._rangeReaders = []; pdfDataRangeTransport.addRangeListener((begin, chunk) => { this._onReceiveData({ begin, chunk }); }); pdfDataRangeTransport.addProgressListener((loaded, total) => { this._onProgress({ loaded, total }); }); pdfDataRangeTransport.addProgressiveReadListener(chunk => { this._onReceiveData({ chunk }); }); pdfDataRangeTransport.addProgressiveDoneListener(() => { this._onProgressiveDone(); }); pdfDataRangeTransport.transportReady(); } _onReceiveData({ begin, chunk }) { const buffer = chunk instanceof Uint8Array && chunk.byteLength === chunk.buffer.byteLength ? chunk.buffer : new Uint8Array(chunk).buffer; if (begin === undefined) { if (this._fullRequestReader) { this._fullRequestReader._enqueue(buffer); } else { this._queuedChunks.push(buffer); } } else { const found = this._rangeReaders.some(function (rangeReader) { if (rangeReader._begin !== begin) { return false; } rangeReader._enqueue(buffer); return true; }); assert(found, "_onReceiveData - no `PDFDataTransportStreamRangeReader` instance found."); } } get _progressiveDataLength() { return this._fullRequestReader?._loaded ?? 0; } _onProgress(evt) { if (evt.total === undefined) { this._rangeReaders[0]?.onProgress?.({ loaded: evt.loaded }); } else { this._fullRequestReader?.onProgress?.({ loaded: evt.loaded, total: evt.total }); } } _onProgressiveDone() { this._fullRequestReader?.progressiveDone(); this._progressiveDone = true; } _removeRangeReader(reader) { const i = this._rangeReaders.indexOf(reader); if (i >= 0) { this._rangeReaders.splice(i, 1); } } getFullReader() { assert(!this._fullRequestReader, "PDFDataTransportStream.getFullReader can only be called once."); const queuedChunks = this._queuedChunks; this._queuedChunks = null; return new PDFDataTransportStreamReader(this, queuedChunks, this._progressiveDone, this._contentDispositionFilename); } getRangeReader(begin, end) { if (end <= this._progressiveDataLength) { return null; } const reader = new PDFDataTransportStreamRangeReader(this, begin, end); this._pdfDataRangeTransport.requestDataRange(begin, end); this._rangeReaders.push(reader); return reader; } cancelAllRequests(reason) { this._fullRequestReader?.cancel(reason); for (const reader of this._rangeReaders.slice(0)) { reader.cancel(reason); } this._pdfDataRangeTransport.abort(); } } class PDFDataTransportStreamReader { constructor(stream, queuedChunks, progressiveDone = false, contentDispositionFilename = null) { this._stream = stream; this._done = progressiveDone || false; this._filename = isPdfFile(contentDispositionFilename) ? contentDispositionFilename : null; this._queuedChunks = queuedChunks || []; this._loaded = 0; for (const chunk of this._queuedChunks) { this._loaded += chunk.byteLength; } this._requests = []; this._headersReady = Promise.resolve(); stream._fullRequestReader = this; this.onProgress = null; } _enqueue(chunk) { if (this._done) { return; } if (this._requests.length > 0) { const requestCapability = this._requests.shift(); requestCapability.resolve({ value: chunk, done: false }); } else { this._queuedChunks.push(chunk); } this._loaded += chunk.byteLength; } get headersReady() { return this._headersReady; } get filename() { return this._filename; } get isRangeSupported() { return this._stream._isRangeSupported; } get isStreamingSupported() { return this._stream._isStreamingSupported; } get contentLength() { return this._stream._contentLength; } async read() { if (this._queuedChunks.length > 0) { const chunk = this._queuedChunks.shift(); return { value: chunk, done: false }; } if (this._done) { return { value: undefined, done: true }; } const requestCapability = Promise.withResolvers(); this._requests.push(requestCapability); return requestCapability.promise; } cancel(reason) { this._done = true; for (const requestCapability of this._requests) { requestCapability.resolve({ value: undefined, done: true }); } this._requests.length = 0; } progressiveDone() { if (this._done) { return; } this._done = true; } } class PDFDataTransportStreamRangeReader { constructor(stream, begin, end) { this._stream = stream; this._begin = begin; this._end = end; this._queuedChunk = null; this._requests = []; this._done = false; this.onProgress = null; } _enqueue(chunk) { if (this._done) { return; } if (this._requests.length === 0) { this._queuedChunk = chunk; } else { const requestsCapability = this._requests.shift(); requestsCapability.resolve({ value: chunk, done: false }); for (const requestCapability of this._requests) { requestCapability.resolve({ value: undefined, done: true }); } this._requests.length = 0; } this._done = true; this._stream._removeRangeReader(this); } get isStreamingSupported() { return false; } async read() { if (this._queuedChunk) { const chunk = this._queuedChunk; this._queuedChunk = null; return { value: chunk, done: false }; } if (this._done) { return { value: undefined, done: true }; } const requestCapability = Promise.withResolvers(); this._requests.push(requestCapability); return requestCapability.promise; } cancel(reason) { this._done = true; for (const requestCapability of this._requests) { requestCapability.resolve({ value: undefined, done: true }); } this._requests.length = 0; this._stream._removeRangeReader(this); } } ;// CONCATENATED MODULE: ./src/display/xfa_text.js class XfaText { static textContent(xfa) { const items = []; const output = { items, styles: Object.create(null) }; function walk(node) { if (!node) { return; } let str = null; const name = node.name; if (name === "#text") { str = node.value; } else if (!XfaText.shouldBuildText(name)) { return; } else if (node?.attributes?.textContent) { str = node.attributes.textContent; } else if (node.value) { str = node.value; } if (str !== null) { items.push({ str }); } if (!node.children) { return; } for (const child of node.children) { walk(child); } } walk(xfa); return output; } static shouldBuildText(name) { return !(name === "textarea" || name === "input" || name === "option" || name === "select"); } } ;// CONCATENATED MODULE: ./src/display/api.js const DEFAULT_RANGE_CHUNK_SIZE = 65536; const RENDERING_CANCELLED_TIMEOUT = 100; const DELAYED_CLEANUP_TIMEOUT = 5000; const DefaultCanvasFactory = DOMCanvasFactory; const DefaultCMapReaderFactory = DOMCMapReaderFactory; const DefaultFilterFactory = DOMFilterFactory; const DefaultStandardFontDataFactory = DOMStandardFontDataFactory; function getDocument(src) { if (typeof src !== "object") { throw new Error("Invalid parameter in getDocument, need parameter object."); } if (!src.url && !src.data && !src.range) { throw new Error("Invalid parameter object: need either .data, .range or .url"); } const task = new PDFDocumentLoadingTask(); const { docId } = task; const url = src.url ? getUrlProp(src.url) : null; const data = src.data ? getDataProp(src.data) : null; const httpHeaders = src.httpHeaders || null; const withCredentials = src.withCredentials === true; const password = src.password ?? null; const rangeTransport = src.range instanceof PDFDataRangeTransport ? src.range : null; const rangeChunkSize = Number.isInteger(src.rangeChunkSize) && src.rangeChunkSize > 0 ? src.rangeChunkSize : DEFAULT_RANGE_CHUNK_SIZE; let worker = src.worker instanceof PDFWorker ? src.worker : null; const verbosity = src.verbosity; const docBaseUrl = typeof src.docBaseUrl === "string" && !isDataScheme(src.docBaseUrl) ? src.docBaseUrl : null; const cMapUrl = typeof src.cMapUrl === "string" ? src.cMapUrl : null; const cMapPacked = src.cMapPacked !== false; const CMapReaderFactory = src.CMapReaderFactory || DefaultCMapReaderFactory; const standardFontDataUrl = typeof src.standardFontDataUrl === "string" ? src.standardFontDataUrl : null; const StandardFontDataFactory = src.StandardFontDataFactory || DefaultStandardFontDataFactory; const ignoreErrors = src.stopAtErrors !== true; const maxImageSize = Number.isInteger(src.maxImageSize) && src.maxImageSize > -1 ? src.maxImageSize : -1; const isEvalSupported = src.isEvalSupported !== false; const isOffscreenCanvasSupported = typeof src.isOffscreenCanvasSupported === "boolean" ? src.isOffscreenCanvasSupported : !isNodeJS; const canvasMaxAreaInBytes = Number.isInteger(src.canvasMaxAreaInBytes) ? src.canvasMaxAreaInBytes : -1; const disableFontFace = typeof src.disableFontFace === "boolean" ? src.disableFontFace : isNodeJS; const fontExtraProperties = src.fontExtraProperties === true; const enableXfa = src.enableXfa === true; const ownerDocument = src.ownerDocument || globalThis.document; const disableRange = src.disableRange === true; const disableStream = src.disableStream === true; const disableAutoFetch = src.disableAutoFetch === true; const pdfBug = src.pdfBug === true; const length = rangeTransport ? rangeTransport.length : src.length ?? NaN; const useSystemFonts = typeof src.useSystemFonts === "boolean" ? src.useSystemFonts : !isNodeJS && !disableFontFace; const useWorkerFetch = typeof src.useWorkerFetch === "boolean" ? src.useWorkerFetch : true; const canvasFactory = src.canvasFactory || new DefaultCanvasFactory({ ownerDocument }); const filterFactory = src.filterFactory || new DefaultFilterFactory({ docId, ownerDocument }); const styleElement = null; setVerbosityLevel(verbosity); const transportFactory = { canvasFactory, filterFactory }; if (!useWorkerFetch) { transportFactory.cMapReaderFactory = new CMapReaderFactory({ baseUrl: cMapUrl, isCompressed: cMapPacked }); transportFactory.standardFontDataFactory = new StandardFontDataFactory({ baseUrl: standardFontDataUrl }); } if (!worker) { const workerParams = { verbosity, port: GlobalWorkerOptions.workerPort }; worker = workerParams.port ? PDFWorker.fromPort(workerParams) : new PDFWorker(workerParams); task._worker = worker; } const fetchDocParams = { docId, apiVersion: "4.3.8", data, password, disableAutoFetch, rangeChunkSize, length, docBaseUrl, enableXfa, evaluatorOptions: { maxImageSize, disableFontFace, ignoreErrors, isEvalSupported, isOffscreenCanvasSupported, canvasMaxAreaInBytes, fontExtraProperties, useSystemFonts, cMapUrl: useWorkerFetch ? cMapUrl : null, standardFontDataUrl: useWorkerFetch ? standardFontDataUrl : null } }; const transportParams = { ignoreErrors, disableFontFace, fontExtraProperties, enableXfa, ownerDocument, disableAutoFetch, pdfBug, styleElement }; worker.promise.then(function () { if (task.destroyed) { throw new Error("Loading aborted"); } const workerIdPromise = _fetchDocument(worker, fetchDocParams); const networkStreamPromise = new Promise(function (resolve) { let networkStream; if (rangeTransport) { networkStream = new PDFDataTransportStream(rangeTransport, { disableRange, disableStream }); } else if (!data) { throw new Error("Not implemented: createPDFNetworkStream"); } resolve(networkStream); }); return Promise.all([workerIdPromise, networkStreamPromise]).then(function ([workerId, networkStream]) { if (task.destroyed) { throw new Error("Loading aborted"); } const messageHandler = new MessageHandler(docId, workerId, worker.port); const transport = new WorkerTransport(messageHandler, task, networkStream, transportParams, transportFactory); task._transport = transport; messageHandler.send("Ready", null); }); }).catch(task._capability.reject); return task; } async function _fetchDocument(worker, source) { if (worker.destroyed) { throw new Error("Worker was destroyed"); } const workerId = await worker.messageHandler.sendWithPromise("GetDocRequest", source, source.data ? [source.data.buffer] : null); if (worker.destroyed) { throw new Error("Worker was destroyed"); } return workerId; } function getUrlProp(val) { return null; } function getDataProp(val) { if (val instanceof Uint8Array && val.byteLength === val.buffer.byteLength) { return val; } if (typeof val === "string") { return stringToBytes(val); } if (val instanceof ArrayBuffer || ArrayBuffer.isView(val) || typeof val === "object" && !isNaN(val?.length)) { return new Uint8Array(val); } throw new Error("Invalid PDF binary data: either TypedArray, " + "string, or array-like object is expected in the data property."); } function isRefProxy(ref) { return typeof ref === "object" && Number.isInteger(ref?.num) && ref.num >= 0 && Number.isInteger(ref?.gen) && ref.gen >= 0; } class PDFDocumentLoadingTask { static #docId = 0; constructor() { this._capability = Promise.withResolvers(); this._transport = null; this._worker = null; this.docId = `d${PDFDocumentLoadingTask.#docId++}`; this.destroyed = false; this.onPassword = null; this.onProgress = null; } get promise() { return this._capability.promise; } async destroy() { this.destroyed = true; try { if (this._worker?.port) { this._worker._pendingDestroy = true; } await this._transport?.destroy(); } catch (ex) { if (this._worker?.port) { delete this._worker._pendingDestroy; } throw ex; } this._transport = null; if (this._worker) { this._worker.destroy(); this._worker = null; } } } class PDFDataRangeTransport { constructor(length, initialData, progressiveDone = false, contentDispositionFilename = null) { this.length = length; this.initialData = initialData; this.progressiveDone = progressiveDone; this.contentDispositionFilename = contentDispositionFilename; this._rangeListeners = []; this._progressListeners = []; this._progressiveReadListeners = []; this._progressiveDoneListeners = []; this._readyCapability = Promise.withResolvers(); } addRangeListener(listener) { this._rangeListeners.push(listener); } addProgressListener(listener) { this._progressListeners.push(listener); } addProgressiveReadListener(listener) { this._progressiveReadListeners.push(listener); } addProgressiveDoneListener(listener) { this._progressiveDoneListeners.push(listener); } onDataRange(begin, chunk) { for (const listener of this._rangeListeners) { listener(begin, chunk); } } onDataProgress(loaded, total) { this._readyCapability.promise.then(() => { for (const listener of this._progressListeners) { listener(loaded, total); } }); } onDataProgressiveRead(chunk) { this._readyCapability.promise.then(() => { for (const listener of this._progressiveReadListeners) { listener(chunk); } }); } onDataProgressiveDone() { this._readyCapability.promise.then(() => { for (const listener of this._progressiveDoneListeners) { listener(); } }); } transportReady() { this._readyCapability.resolve(); } requestDataRange(begin, end) { unreachable("Abstract method PDFDataRangeTransport.requestDataRange"); } abort() {} } class PDFDocumentProxy { constructor(pdfInfo, transport) { this._pdfInfo = pdfInfo; this._transport = transport; } get annotationStorage() { return this._transport.annotationStorage; } get filterFactory() { return this._transport.filterFactory; } get numPages() { return this._pdfInfo.numPages; } get fingerprints() { return this._pdfInfo.fingerprints; } get isPureXfa() { return shadow(this, "isPureXfa", !!this._transport._htmlForXfa); } get allXfaHtml() { return this._transport._htmlForXfa; } getPage(pageNumber) { return this._transport.getPage(pageNumber); } getPageIndex(ref) { return this._transport.getPageIndex(ref); } getDestinations() { return this._transport.getDestinations(); } getDestination(id) { return this._transport.getDestination(id); } getPageLabels() { return this._transport.getPageLabels(); } getPageLayout() { return this._transport.getPageLayout(); } getPageMode() { return this._transport.getPageMode(); } getViewerPreferences() { return this._transport.getViewerPreferences(); } getOpenAction() { return this._transport.getOpenAction(); } getAttachments() { return this._transport.getAttachments(); } getJSActions() { return this._transport.getDocJSActions(); } getOutline() { return this._transport.getOutline(); } getOptionalContentConfig({ intent = "display" } = {}) { const { renderingIntent } = this._transport.getRenderingIntent(intent); return this._transport.getOptionalContentConfig(renderingIntent); } getPermissions() { return this._transport.getPermissions(); } getMetadata() { return this._transport.getMetadata(); } getMarkInfo() { return this._transport.getMarkInfo(); } getData() { return this._transport.getData(); } saveDocument() { return this._transport.saveDocument(); } getDownloadInfo() { return this._transport.downloadInfoCapability.promise; } cleanup(keepLoadedFonts = false) { return this._transport.startCleanup(keepLoadedFonts || this.isPureXfa); } destroy() { return this.loadingTask.destroy(); } cachedPageNumber(ref) { return this._transport.cachedPageNumber(ref); } get loadingParams() { return this._transport.loadingParams; } get loadingTask() { return this._transport.loadingTask; } getFieldObjects() { return this._transport.getFieldObjects(); } hasJSActions() { return this._transport.hasJSActions(); } getCalculationOrderIds() { return this._transport.getCalculationOrderIds(); } } class PDFPageProxy { #delayedCleanupTimeout = null; #pendingCleanup = false; constructor(pageIndex, pageInfo, transport, pdfBug = false) { this._pageIndex = pageIndex; this._pageInfo = pageInfo; this._transport = transport; this._stats = pdfBug ? new StatTimer() : null; this._pdfBug = pdfBug; this.commonObjs = transport.commonObjs; this.objs = new PDFObjects(); this._maybeCleanupAfterRender = false; this._intentStates = new Map(); this.destroyed = false; } get pageNumber() { return this._pageIndex + 1; } get rotate() { return this._pageInfo.rotate; } get ref() { return this._pageInfo.ref; } get userUnit() { return this._pageInfo.userUnit; } get view() { return this._pageInfo.view; } getViewport({ scale, rotation = this.rotate, offsetX = 0, offsetY = 0, dontFlip = false } = {}) { return new PageViewport({ viewBox: this.view, scale, rotation, offsetX, offsetY, dontFlip }); } getAnnotations({ intent = "display" } = {}) { const { renderingIntent } = this._transport.getRenderingIntent(intent); return this._transport.getAnnotations(this._pageIndex, renderingIntent); } getJSActions() { return this._transport.getPageJSActions(this._pageIndex); } get filterFactory() { return this._transport.filterFactory; } get isPureXfa() { return shadow(this, "isPureXfa", !!this._transport._htmlForXfa); } async getXfa() { return this._transport._htmlForXfa?.children[this._pageIndex] || null; } render({ canvasContext, viewport, intent = "display", annotationMode = AnnotationMode.ENABLE, transform = null, background = null, optionalContentConfigPromise = null, annotationCanvasMap = null, pageColors = null, printAnnotationStorage = null }) { this._stats?.time("Overall"); const intentArgs = this._transport.getRenderingIntent(intent, annotationMode, printAnnotationStorage); const { renderingIntent, cacheKey } = intentArgs; this.#pendingCleanup = false; this.#abortDelayedCleanup(); optionalContentConfigPromise ||= this._transport.getOptionalContentConfig(renderingIntent); let intentState = this._intentStates.get(cacheKey); if (!intentState) { intentState = Object.create(null); this._intentStates.set(cacheKey, intentState); } if (intentState.streamReaderCancelTimeout) { clearTimeout(intentState.streamReaderCancelTimeout); intentState.streamReaderCancelTimeout = null; } const intentPrint = !!(renderingIntent & RenderingIntentFlag.PRINT); if (!intentState.displayReadyCapability) { intentState.displayReadyCapability = Promise.withResolvers(); intentState.operatorList = { fnArray: [], argsArray: [], lastChunk: false, separateAnnots: null }; this._stats?.time("Page Request"); this._pumpOperatorList(intentArgs); } const complete = error => { intentState.renderTasks.delete(internalRenderTask); if (this._maybeCleanupAfterRender || intentPrint) { this.#pendingCleanup = true; } this.#tryCleanup(!intentPrint); if (error) { internalRenderTask.capability.reject(error); this._abortOperatorList({ intentState, reason: error instanceof Error ? error : new Error(error) }); } else { internalRenderTask.capability.resolve(); } this._stats?.timeEnd("Rendering"); this._stats?.timeEnd("Overall"); }; const internalRenderTask = new InternalRenderTask({ callback: complete, params: { canvasContext, viewport, transform, background }, objs: this.objs, commonObjs: this.commonObjs, annotationCanvasMap, operatorList: intentState.operatorList, pageIndex: this._pageIndex, canvasFactory: this._transport.canvasFactory, filterFactory: this._transport.filterFactory, useRequestAnimationFrame: !intentPrint, pdfBug: this._pdfBug, pageColors }); (intentState.renderTasks ||= new Set()).add(internalRenderTask); const renderTask = internalRenderTask.task; Promise.all([intentState.displayReadyCapability.promise, optionalContentConfigPromise]).then(([transparency, optionalContentConfig]) => { if (this.destroyed) { complete(); return; } this._stats?.time("Rendering"); if (!(optionalContentConfig.renderingIntent & renderingIntent)) { throw new Error("Must use the same `intent`-argument when calling the `PDFPageProxy.render` " + "and `PDFDocumentProxy.getOptionalContentConfig` methods."); } internalRenderTask.initializeGraphics({ transparency, optionalContentConfig }); internalRenderTask.operatorListChanged(); }).catch(complete); return renderTask; } getOperatorList({ intent = "display", annotationMode = AnnotationMode.ENABLE, printAnnotationStorage = null } = {}) { throw new Error("Not implemented: getOperatorList"); } streamTextContent({ includeMarkedContent = false, disableNormalization = false } = {}) { const TEXT_CONTENT_CHUNK_SIZE = 100; return this._transport.messageHandler.sendWithStream("GetTextContent", { pageIndex: this._pageIndex, includeMarkedContent: includeMarkedContent === true, disableNormalization: disableNormalization === true }, { highWaterMark: TEXT_CONTENT_CHUNK_SIZE, size(textContent) { return textContent.items.length; } }); } getTextContent(params = {}) { if (this._transport._htmlForXfa) { return this.getXfa().then(xfa => XfaText.textContent(xfa)); } const readableStream = this.streamTextContent(params); return new Promise(function (resolve, reject) { function pump() { reader.read().then(function ({ value, done }) { if (done) { resolve(textContent); return; } Object.assign(textContent.styles, value.styles); textContent.items.push(...value.items); pump(); }, reject); } const reader = readableStream.getReader(); const textContent = { items: [], styles: Object.create(null) }; pump(); }); } getStructTree() { return this._transport.getStructTree(this._pageIndex); } _destroy() { this.destroyed = true; const waitOn = []; for (const intentState of this._intentStates.values()) { this._abortOperatorList({ intentState, reason: new Error("Page was destroyed."), force: true }); if (intentState.opListReadCapability) { continue; } for (const internalRenderTask of intentState.renderTasks) { waitOn.push(internalRenderTask.completed); internalRenderTask.cancel(); } } this.objs.clear(); this.#pendingCleanup = false; this.#abortDelayedCleanup(); return Promise.all(waitOn); } cleanup(resetStats = false) { this.#pendingCleanup = true; const success = this.#tryCleanup(false); if (resetStats && success) { this._stats &&= new StatTimer(); } return success; } #tryCleanup(delayed = false) { this.#abortDelayedCleanup(); if (!this.#pendingCleanup || this.destroyed) { return false; } if (delayed) { this.#delayedCleanupTimeout = setTimeout(() => { this.#delayedCleanupTimeout = null; this.#tryCleanup(false); }, DELAYED_CLEANUP_TIMEOUT); return false; } for (const { renderTasks, operatorList } of this._intentStates.values()) { if (renderTasks.size > 0 || !operatorList.lastChunk) { return false; } } this._intentStates.clear(); this.objs.clear(); this.#pendingCleanup = false; return true; } #abortDelayedCleanup() { if (this.#delayedCleanupTimeout) { clearTimeout(this.#delayedCleanupTimeout); this.#delayedCleanupTimeout = null; } } _startRenderPage(transparency, cacheKey) { const intentState = this._intentStates.get(cacheKey); if (!intentState) { return; } this._stats?.timeEnd("Page Request"); intentState.displayReadyCapability?.resolve(transparency); } _renderPageChunk(operatorListChunk, intentState) { for (let i = 0, ii = operatorListChunk.length; i < ii; i++) { intentState.operatorList.fnArray.push(operatorListChunk.fnArray[i]); intentState.operatorList.argsArray.push(operatorListChunk.argsArray[i]); } intentState.operatorList.lastChunk = operatorListChunk.lastChunk; intentState.operatorList.separateAnnots = operatorListChunk.separateAnnots; for (const internalRenderTask of intentState.renderTasks) { internalRenderTask.operatorListChanged(); } if (operatorListChunk.lastChunk) { this.#tryCleanup(true); } } _pumpOperatorList({ renderingIntent, cacheKey, annotationStorageSerializable }) { const { map, transfer } = annotationStorageSerializable; const readableStream = this._transport.messageHandler.sendWithStream("GetOperatorList", { pageIndex: this._pageIndex, intent: renderingIntent, cacheKey, annotationStorage: map }, transfer); const reader = readableStream.getReader(); const intentState = this._intentStates.get(cacheKey); intentState.streamReader = reader; const pump = () => { reader.read().then(({ value, done }) => { if (done) { intentState.streamReader = null; return; } if (this._transport.destroyed) { return; } this._renderPageChunk(value, intentState); pump(); }, reason => { intentState.streamReader = null; if (this._transport.destroyed) { return; } if (intentState.operatorList) { intentState.operatorList.lastChunk = true; for (const internalRenderTask of intentState.renderTasks) { internalRenderTask.operatorListChanged(); } this.#tryCleanup(true); } if (intentState.displayReadyCapability) { intentState.displayReadyCapability.reject(reason); } else if (intentState.opListReadCapability) { intentState.opListReadCapability.reject(reason); } else { throw reason; } }); }; pump(); } _abortOperatorList({ intentState, reason, force = false }) { if (!intentState.streamReader) { return; } if (intentState.streamReaderCancelTimeout) { clearTimeout(intentState.streamReaderCancelTimeout); intentState.streamReaderCancelTimeout = null; } if (!force) { if (intentState.renderTasks.size > 0) { return; } if (reason instanceof RenderingCancelledException) { let delay = RENDERING_CANCELLED_TIMEOUT; if (reason.extraDelay > 0 && reason.extraDelay < 1000) { delay += reason.extraDelay; } intentState.streamReaderCancelTimeout = setTimeout(() => { intentState.streamReaderCancelTimeout = null; this._abortOperatorList({ intentState, reason, force: true }); }, delay); return; } } intentState.streamReader.cancel(new AbortException(reason.message)).catch(() => {}); intentState.streamReader = null; if (this._transport.destroyed) { return; } for (const [curCacheKey, curIntentState] of this._intentStates) { if (curIntentState === intentState) { this._intentStates.delete(curCacheKey); break; } } this.cleanup(); } get stats() { return this._stats; } } class LoopbackPort { #listeners = new Set(); #deferred = Promise.resolve(); postMessage(obj, transfer) { const event = { data: structuredClone(obj, transfer ? { transfer } : null) }; this.#deferred.then(() => { for (const listener of this.#listeners) { listener.call(this, event); } }); } addEventListener(name, listener) { this.#listeners.add(listener); } removeEventListener(name, listener) { this.#listeners.delete(listener); } terminate() { this.#listeners.clear(); } } const PDFWorkerUtil = { isWorkerDisabled: false, fakeWorkerId: 0 }; class PDFWorker { static #workerPorts; constructor({ name = null, port = null, verbosity = getVerbosityLevel() } = {}) { this.name = name; this.destroyed = false; this.verbosity = verbosity; this._readyCapability = Promise.withResolvers(); this._port = null; this._webWorker = null; this._messageHandler = null; this._initialize(); } get promise() { return this._readyCapability.promise; } get port() { return this._port; } get messageHandler() { return this._messageHandler; } _initializeFromPort(port) { throw new Error("Not implemented: _initializeFromPort"); } _initialize() { if (!PDFWorkerUtil.isWorkerDisabled && !PDFWorker.#mainThreadWorkerMessageHandler) { let { workerSrc } = PDFWorker; try { const worker = new Worker(workerSrc, { type: "module" }); const messageHandler = new MessageHandler("main", "worker", worker); const terminateEarly = () => { worker.removeEventListener("error", onWorkerError); messageHandler.destroy(); worker.terminate(); if (this.destroyed) { this._readyCapability.reject(new Error("Worker was destroyed")); } else { this._setupFakeWorker(); } }; const onWorkerError = () => { if (!this._webWorker) { terminateEarly(); } }; worker.addEventListener("error", onWorkerError); messageHandler.on("test", data => { worker.removeEventListener("error", onWorkerError); if (this.destroyed) { terminateEarly(); return; } if (data) { this._messageHandler = messageHandler; this._port = worker; this._webWorker = worker; this._readyCapability.resolve(); messageHandler.send("configure", { verbosity: this.verbosity }); } else { this._setupFakeWorker(); messageHandler.destroy(); worker.terminate(); } }); messageHandler.on("ready", data => { worker.removeEventListener("error", onWorkerError); if (this.destroyed) { terminateEarly(); return; } try { sendTest(); } catch { this._setupFakeWorker(); } }); const sendTest = () => { const testObj = new Uint8Array(); messageHandler.send("test", testObj, [testObj.buffer]); }; sendTest(); return; } catch { info("The worker has been disabled."); } } this._setupFakeWorker(); } _setupFakeWorker() { if (!PDFWorkerUtil.isWorkerDisabled) { warn("Setting up fake worker."); PDFWorkerUtil.isWorkerDisabled = true; } PDFWorker._setupFakeWorkerGlobal.then(WorkerMessageHandler => { if (this.destroyed) { this._readyCapability.reject(new Error("Worker was destroyed")); return; } const port = new LoopbackPort(); this._port = port; const id = `fake${PDFWorkerUtil.fakeWorkerId++}`; const workerHandler = new MessageHandler(id + "_worker", id, port); WorkerMessageHandler.setup(workerHandler, port); const messageHandler = new MessageHandler(id, id + "_worker", port); this._messageHandler = messageHandler; this._readyCapability.resolve(); messageHandler.send("configure", { verbosity: this.verbosity }); }).catch(reason => { this._readyCapability.reject(new Error(`Setting up fake worker failed: "${reason.message}".`)); }); } destroy() { this.destroyed = true; if (this._webWorker) { this._webWorker.terminate(); this._webWorker = null; } PDFWorker.#workerPorts?.delete(this._port); this._port = null; if (this._messageHandler) { this._messageHandler.destroy(); this._messageHandler = null; } } static fromPort(params) { throw new Error("Not implemented: fromPort"); } static get workerSrc() { if (GlobalWorkerOptions.workerSrc) { return GlobalWorkerOptions.workerSrc; } throw new Error('No "GlobalWorkerOptions.workerSrc" specified.'); } static get #mainThreadWorkerMessageHandler() { try { return globalThis.pdfjsWorker?.WorkerMessageHandler || null; } catch { return null; } } static get _setupFakeWorkerGlobal() { const loader = async () => { if (this.#mainThreadWorkerMessageHandler) { return this.#mainThreadWorkerMessageHandler; } const worker = await import( /*webpackIgnore: true*/this.workerSrc); return worker.WorkerMessageHandler; }; return shadow(this, "_setupFakeWorkerGlobal", loader()); } } class WorkerTransport { #methodPromises = new Map(); #pageCache = new Map(); #pagePromises = new Map(); #pageRefCache = new Map(); #passwordCapability = null; constructor(messageHandler, loadingTask, networkStream, params, factory) { this.messageHandler = messageHandler; this.loadingTask = loadingTask; this.commonObjs = new PDFObjects(); this.fontLoader = new FontLoader({ ownerDocument: params.ownerDocument, styleElement: params.styleElement }); this._params = params; this.canvasFactory = factory.canvasFactory; this.filterFactory = factory.filterFactory; this.cMapReaderFactory = factory.cMapReaderFactory; this.standardFontDataFactory = factory.standardFontDataFactory; this.destroyed = false; this.destroyCapability = null; this._networkStream = networkStream; this._fullReader = null; this._lastProgress = null; this.downloadInfoCapability = Promise.withResolvers(); this.setupMessageHandler(); } #cacheSimpleMethod(name, data = null) { const cachedPromise = this.#methodPromises.get(name); if (cachedPromise) { return cachedPromise; } const promise = this.messageHandler.sendWithPromise(name, data); this.#methodPromises.set(name, promise); return promise; } get annotationStorage() { return shadow(this, "annotationStorage", new AnnotationStorage()); } getRenderingIntent(intent, annotationMode = AnnotationMode.ENABLE, printAnnotationStorage = null, isOpList = false) { let renderingIntent = RenderingIntentFlag.DISPLAY; let annotationStorageSerializable = SerializableEmpty; switch (intent) { case "any": renderingIntent = RenderingIntentFlag.ANY; break; case "display": break; case "print": renderingIntent = RenderingIntentFlag.PRINT; break; default: warn(`getRenderingIntent - invalid intent: ${intent}`); } switch (annotationMode) { case AnnotationMode.DISABLE: renderingIntent += RenderingIntentFlag.ANNOTATIONS_DISABLE; break; case AnnotationMode.ENABLE: break; case AnnotationMode.ENABLE_FORMS: renderingIntent += RenderingIntentFlag.ANNOTATIONS_FORMS; break; case AnnotationMode.ENABLE_STORAGE: renderingIntent += RenderingIntentFlag.ANNOTATIONS_STORAGE; const annotationStorage = renderingIntent & RenderingIntentFlag.PRINT && printAnnotationStorage instanceof PrintAnnotationStorage ? printAnnotationStorage : this.annotationStorage; annotationStorageSerializable = annotationStorage.serializable; break; default: warn(`getRenderingIntent - invalid annotationMode: ${annotationMode}`); } if (isOpList) { renderingIntent += RenderingIntentFlag.OPLIST; } return { renderingIntent, cacheKey: `${renderingIntent}_${annotationStorageSerializable.hash}`, annotationStorageSerializable }; } destroy() { if (this.destroyCapability) { return this.destroyCapability.promise; } this.destroyed = true; this.destroyCapability = Promise.withResolvers(); this.#passwordCapability?.reject(new Error("Worker was destroyed during onPassword callback")); const waitOn = []; for (const page of this.#pageCache.values()) { waitOn.push(page._destroy()); } this.#pageCache.clear(); this.#pagePromises.clear(); this.#pageRefCache.clear(); if (this.hasOwnProperty("annotationStorage")) { this.annotationStorage.resetModified(); } const terminated = this.messageHandler.sendWithPromise("Terminate", null); waitOn.push(terminated); Promise.all(waitOn).then(() => { this.commonObjs.clear(); this.fontLoader.clear(); this.#methodPromises.clear(); this.filterFactory.destroy(); cleanupTextLayer(); this._networkStream?.cancelAllRequests(new AbortException("Worker was terminated.")); if (this.messageHandler) { this.messageHandler.destroy(); this.messageHandler = null; } this.destroyCapability.resolve(); }, this.destroyCapability.reject); return this.destroyCapability.promise; } setupMessageHandler() { const { messageHandler, loadingTask } = this; messageHandler.on("GetReader", (data, sink) => { assert(this._networkStream, "GetReader - no `IPDFStream` instance available."); this._fullReader = this._networkStream.getFullReader(); this._fullReader.onProgress = evt => { this._lastProgress = { loaded: evt.loaded, total: evt.total }; }; sink.onPull = () => { this._fullReader.read().then(function ({ value, done }) { if (done) { sink.close(); return; } assert(value instanceof ArrayBuffer, "GetReader - expected an ArrayBuffer."); sink.enqueue(new Uint8Array(value), 1, [value]); }).catch(reason => { sink.error(reason); }); }; sink.onCancel = reason => { this._fullReader.cancel(reason); sink.ready.catch(readyReason => { if (this.destroyed) { return; } throw readyReason; }); }; }); messageHandler.on("ReaderHeadersReady", data => { const headersCapability = Promise.withResolvers(); const fullReader = this._fullReader; fullReader.headersReady.then(() => { if (!fullReader.isStreamingSupported || !fullReader.isRangeSupported) { if (this._lastProgress) { loadingTask.onProgress?.(this._lastProgress); } fullReader.onProgress = evt => { loadingTask.onProgress?.({ loaded: evt.loaded, total: evt.total }); }; } headersCapability.resolve({ isStreamingSupported: fullReader.isStreamingSupported, isRangeSupported: fullReader.isRangeSupported, contentLength: fullReader.contentLength }); }, headersCapability.reject); return headersCapability.promise; }); messageHandler.on("GetRangeReader", (data, sink) => { assert(this._networkStream, "GetRangeReader - no `IPDFStream` instance available."); const rangeReader = this._networkStream.getRangeReader(data.begin, data.end); if (!rangeReader) { sink.close(); return; } sink.onPull = () => { rangeReader.read().then(function ({ value, done }) { if (done) { sink.close(); return; } assert(value instanceof ArrayBuffer, "GetRangeReader - expected an ArrayBuffer."); sink.enqueue(new Uint8Array(value), 1, [value]); }).catch(reason => { sink.error(reason); }); }; sink.onCancel = reason => { rangeReader.cancel(reason); sink.ready.catch(readyReason => { if (this.destroyed) { return; } throw readyReason; }); }; }); messageHandler.on("GetDoc", ({ pdfInfo }) => { this._numPages = pdfInfo.numPages; this._htmlForXfa = pdfInfo.htmlForXfa; delete pdfInfo.htmlForXfa; loadingTask._capability.resolve(new PDFDocumentProxy(pdfInfo, this)); }); messageHandler.on("DocException", function (ex) { let reason; switch (ex.name) { case "PasswordException": reason = new PasswordException(ex.message, ex.code); break; case "InvalidPDFException": reason = new InvalidPDFException(ex.message); break; case "MissingPDFException": reason = new MissingPDFException(ex.message); break; case "UnexpectedResponseException": reason = new UnexpectedResponseException(ex.message, ex.status); break; case "UnknownErrorException": reason = new UnknownErrorException(ex.message, ex.details); break; default: unreachable("DocException - expected a valid Error."); } loadingTask._capability.reject(reason); }); messageHandler.on("PasswordRequest", exception => { this.#passwordCapability = Promise.withResolvers(); if (loadingTask.onPassword) { const updatePassword = password => { if (password instanceof Error) { this.#passwordCapability.reject(password); } else { this.#passwordCapability.resolve({ password }); } }; try { loadingTask.onPassword(updatePassword, exception.code); } catch (ex) { this.#passwordCapability.reject(ex); } } else { this.#passwordCapability.reject(new PasswordException(exception.message, exception.code)); } return this.#passwordCapability.promise; }); messageHandler.on("DataLoaded", data => { loadingTask.onProgress?.({ loaded: data.length, total: data.length }); this.downloadInfoCapability.resolve(data); }); messageHandler.on("StartRenderPage", data => { if (this.destroyed) { return; } const page = this.#pageCache.get(data.pageIndex); page._startRenderPage(data.transparency, data.cacheKey); }); messageHandler.on("commonobj", ([id, type, exportedData]) => { if (this.destroyed) { return null; } if (this.commonObjs.has(id)) { return null; } switch (type) { case "Font": const params = this._params; if ("error" in exportedData) { const exportedError = exportedData.error; warn(`Error during font loading: ${exportedError}`); this.commonObjs.resolve(id, exportedError); break; } const inspectFont = params.pdfBug && globalThis.FontInspector?.enabled ? (font, url) => globalThis.FontInspector.fontAdded(font, url) : null; const font = new FontFaceObject(exportedData, { disableFontFace: params.disableFontFace, ignoreErrors: params.ignoreErrors, inspectFont }); this.fontLoader.bind(font).catch(() => messageHandler.sendWithPromise("FontFallback", { id })).finally(() => { if (!params.fontExtraProperties && font.data) { font.data = null; } this.commonObjs.resolve(id, font); }); break; case "CopyLocalImage": const { imageRef } = exportedData; assert(imageRef, "The imageRef must be defined."); for (const pageProxy of this.#pageCache.values()) { for (const [, data] of pageProxy.objs) { if (data.ref !== imageRef) { continue; } if (!data.dataLen) { return null; } this.commonObjs.resolve(id, structuredClone(data)); return data.dataLen; } } break; case "FontPath": case "Image": case "Pattern": this.commonObjs.resolve(id, exportedData); break; default: throw new Error(`Got unknown common object type ${type}`); } return null; }); messageHandler.on("obj", ([id, pageIndex, type, imageData]) => { if (this.destroyed) { return; } const pageProxy = this.#pageCache.get(pageIndex); if (pageProxy.objs.has(id)) { return; } if (pageProxy._intentStates.size === 0) { imageData?.bitmap?.close(); return; } switch (type) { case "Image": pageProxy.objs.resolve(id, imageData); if (imageData?.dataLen > MAX_IMAGE_SIZE_TO_CACHE) { pageProxy._maybeCleanupAfterRender = true; } break; case "Pattern": pageProxy.objs.resolve(id, imageData); break; default: throw new Error(`Got unknown object type ${type}`); } }); messageHandler.on("DocProgress", data => { if (this.destroyed) { return; } loadingTask.onProgress?.({ loaded: data.loaded, total: data.total }); }); messageHandler.on("FetchBuiltInCMap", data => { if (this.destroyed) { return Promise.reject(new Error("Worker was destroyed.")); } if (!this.cMapReaderFactory) { return Promise.reject(new Error("CMapReaderFactory not initialized, see the `useWorkerFetch` parameter.")); } return this.cMapReaderFactory.fetch(data); }); messageHandler.on("FetchStandardFontData", data => { if (this.destroyed) { return Promise.reject(new Error("Worker was destroyed.")); } if (!this.standardFontDataFactory) { return Promise.reject(new Error("StandardFontDataFactory not initialized, see the `useWorkerFetch` parameter.")); } return this.standardFontDataFactory.fetch(data); }); } getData() { return this.messageHandler.sendWithPromise("GetData", null); } saveDocument() { if (this.annotationStorage.size <= 0) { warn("saveDocument called while `annotationStorage` is empty, " + "please use the getData-method instead."); } const { map, transfer } = this.annotationStorage.serializable; return this.messageHandler.sendWithPromise("SaveDocument", { isPureXfa: !!this._htmlForXfa, numPages: this._numPages, annotationStorage: map, filename: this._fullReader?.filename ?? null }, transfer).finally(() => { this.annotationStorage.resetModified(); }); } getPage(pageNumber) { if (!Number.isInteger(pageNumber) || pageNumber <= 0 || pageNumber > this._numPages) { return Promise.reject(new Error("Invalid page request.")); } const pageIndex = pageNumber - 1, cachedPromise = this.#pagePromises.get(pageIndex); if (cachedPromise) { return cachedPromise; } const promise = this.messageHandler.sendWithPromise("GetPage", { pageIndex }).then(pageInfo => { if (this.destroyed) { throw new Error("Transport destroyed"); } if (pageInfo.refStr) { this.#pageRefCache.set(pageInfo.refStr, pageNumber); } const page = new PDFPageProxy(pageIndex, pageInfo, this, this._params.pdfBug); this.#pageCache.set(pageIndex, page); return page; }); this.#pagePromises.set(pageIndex, promise); return promise; } getPageIndex(ref) { if (!isRefProxy(ref)) { return Promise.reject(new Error("Invalid pageIndex request.")); } return this.messageHandler.sendWithPromise("GetPageIndex", { num: ref.num, gen: ref.gen }); } getAnnotations(pageIndex, intent) { return this.messageHandler.sendWithPromise("GetAnnotations", { pageIndex, intent }); } getFieldObjects() { return this.#cacheSimpleMethod("GetFieldObjects"); } hasJSActions() { return this.#cacheSimpleMethod("HasJSActions"); } getCalculationOrderIds() { return this.messageHandler.sendWithPromise("GetCalculationOrderIds", null); } getDestinations() { return this.messageHandler.sendWithPromise("GetDestinations", null); } getDestination(id) { if (typeof id !== "string") { return Promise.reject(new Error("Invalid destination request.")); } return this.messageHandler.sendWithPromise("GetDestination", { id }); } getPageLabels() { return this.messageHandler.sendWithPromise("GetPageLabels", null); } getPageLayout() { return this.messageHandler.sendWithPromise("GetPageLayout", null); } getPageMode() { return this.messageHandler.sendWithPromise("GetPageMode", null); } getViewerPreferences() { return this.messageHandler.sendWithPromise("GetViewerPreferences", null); } getOpenAction() { return this.messageHandler.sendWithPromise("GetOpenAction", null); } getAttachments() { return this.messageHandler.sendWithPromise("GetAttachments", null); } getDocJSActions() { return this.#cacheSimpleMethod("GetDocJSActions"); } getPageJSActions(pageIndex) { return this.messageHandler.sendWithPromise("GetPageJSActions", { pageIndex }); } getStructTree(pageIndex) { return this.messageHandler.sendWithPromise("GetStructTree", { pageIndex }); } getOutline() { return this.messageHandler.sendWithPromise("GetOutline", null); } getOptionalContentConfig(renderingIntent) { return this.#cacheSimpleMethod("GetOptionalContentConfig").then(data => new OptionalContentConfig(data, renderingIntent)); } getPermissions() { return this.messageHandler.sendWithPromise("GetPermissions", null); } getMetadata() { const name = "GetMetadata", cachedPromise = this.#methodPromises.get(name); if (cachedPromise) { return cachedPromise; } const promise = this.messageHandler.sendWithPromise(name, null).then(results => ({ info: results[0], metadata: results[1] ? new Metadata(results[1]) : null, contentDispositionFilename: this._fullReader?.filename ?? null, contentLength: this._fullReader?.contentLength ?? null })); this.#methodPromises.set(name, promise); return promise; } getMarkInfo() { return this.messageHandler.sendWithPromise("GetMarkInfo", null); } async startCleanup(keepLoadedFonts = false) { if (this.destroyed) { return; } await this.messageHandler.sendWithPromise("Cleanup", null); for (const page of this.#pageCache.values()) { const cleanupSuccessful = page.cleanup(); if (!cleanupSuccessful) { throw new Error(`startCleanup: Page ${page.pageNumber} is currently rendering.`); } } this.commonObjs.clear(); if (!keepLoadedFonts) { this.fontLoader.clear(); } this.#methodPromises.clear(); this.filterFactory.destroy(true); cleanupTextLayer(); } cachedPageNumber(ref) { if (!isRefProxy(ref)) { return null; } const refStr = ref.gen === 0 ? `${ref.num}R` : `${ref.num}R${ref.gen}`; return this.#pageRefCache.get(refStr) ?? null; } get loadingParams() { const { disableAutoFetch, enableXfa } = this._params; return shadow(this, "loadingParams", { disableAutoFetch, enableXfa }); } } const INITIAL_DATA = Symbol("INITIAL_DATA"); class PDFObjects { #objs = Object.create(null); #ensureObj(objId) { return this.#objs[objId] ||= { ...Promise.withResolvers(), data: INITIAL_DATA }; } get(objId, callback = null) { if (callback) { const obj = this.#ensureObj(objId); obj.promise.then(() => callback(obj.data)); return null; } const obj = this.#objs[objId]; if (!obj || obj.data === INITIAL_DATA) { throw new Error(`Requesting object that isn't resolved yet ${objId}.`); } return obj.data; } has(objId) { const obj = this.#objs[objId]; return !!obj && obj.data !== INITIAL_DATA; } resolve(objId, data = null) { const obj = this.#ensureObj(objId); obj.data = data; obj.resolve(); } clear() { for (const objId in this.#objs) { const { data } = this.#objs[objId]; data?.bitmap?.close(); } this.#objs = Object.create(null); } *[Symbol.iterator]() { for (const objId in this.#objs) { const { data } = this.#objs[objId]; if (data === INITIAL_DATA) { continue; } yield [objId, data]; } } } class RenderTask { #internalRenderTask = null; constructor(internalRenderTask) { this.#internalRenderTask = internalRenderTask; this.onContinue = null; } get promise() { return this.#internalRenderTask.capability.promise; } cancel(extraDelay = 0) { this.#internalRenderTask.cancel(null, extraDelay); } get separateAnnots() { const { separateAnnots } = this.#internalRenderTask.operatorList; if (!separateAnnots) { return false; } const { annotationCanvasMap } = this.#internalRenderTask; return separateAnnots.form || separateAnnots.canvas && annotationCanvasMap?.size > 0; } } class InternalRenderTask { static #canvasInUse = new WeakSet(); constructor({ callback, params, objs, commonObjs, annotationCanvasMap, operatorList, pageIndex, canvasFactory, filterFactory, useRequestAnimationFrame = false, pdfBug = false, pageColors = null }) { this.callback = callback; this.params = params; this.objs = objs; this.commonObjs = commonObjs; this.annotationCanvasMap = annotationCanvasMap; this.operatorListIdx = null; this.operatorList = operatorList; this._pageIndex = pageIndex; this.canvasFactory = canvasFactory; this.filterFactory = filterFactory; this._pdfBug = pdfBug; this.pageColors = pageColors; this.running = false; this.graphicsReadyCallback = null; this.graphicsReady = false; this._useRequestAnimationFrame = useRequestAnimationFrame === true && typeof window !== "undefined"; this.cancelled = false; this.capability = Promise.withResolvers(); this.task = new RenderTask(this); this._cancelBound = this.cancel.bind(this); this._continueBound = this._continue.bind(this); this._scheduleNextBound = this._scheduleNext.bind(this); this._nextBound = this._next.bind(this); this._canvas = params.canvasContext.canvas; } get completed() { return this.capability.promise.catch(function () {}); } initializeGraphics({ transparency = false, optionalContentConfig }) { if (this.cancelled) { return; } if (this._canvas) { if (InternalRenderTask.#canvasInUse.has(this._canvas)) { throw new Error("Cannot use the same canvas during multiple render() operations. " + "Use different canvas or ensure previous operations were " + "cancelled or completed."); } InternalRenderTask.#canvasInUse.add(this._canvas); } if (this._pdfBug && globalThis.StepperManager?.enabled) { this.stepper = globalThis.StepperManager.create(this._pageIndex); this.stepper.init(this.operatorList); this.stepper.nextBreakPoint = this.stepper.getNextBreakPoint(); } const { canvasContext, viewport, transform, background } = this.params; this.gfx = new CanvasGraphics(canvasContext, this.commonObjs, this.objs, this.canvasFactory, this.filterFactory, { optionalContentConfig }, this.annotationCanvasMap, this.pageColors); this.gfx.beginDrawing({ transform, viewport, transparency, background }); this.operatorListIdx = 0; this.graphicsReady = true; this.graphicsReadyCallback?.(); } cancel(error = null, extraDelay = 0) { this.running = false; this.cancelled = true; this.gfx?.endDrawing(); InternalRenderTask.#canvasInUse.delete(this._canvas); this.callback(error || new RenderingCancelledException(`Rendering cancelled, page ${this._pageIndex + 1}`, extraDelay)); } operatorListChanged() { if (!this.graphicsReady) { this.graphicsReadyCallback ||= this._continueBound; return; } this.stepper?.updateOperatorList(this.operatorList); if (this.running) { return; } this._continue(); } _continue() { this.running = true; if (this.cancelled) { return; } if (this.task.onContinue) { this.task.onContinue(this._scheduleNextBound); } else { this._scheduleNext(); } } _scheduleNext() { if (this._useRequestAnimationFrame) { window.requestAnimationFrame(() => { this._nextBound().catch(this._cancelBound); }); } else { Promise.resolve().then(this._nextBound).catch(this._cancelBound); } } async _next() { if (this.cancelled) { return; } this.operatorListIdx = this.gfx.executeOperatorList(this.operatorList, this.operatorListIdx, this._continueBound, this.stepper); if (this.operatorListIdx === this.operatorList.argsArray.length) { this.running = false; if (this.operatorList.lastChunk) { this.gfx.endDrawing(); InternalRenderTask.#canvasInUse.delete(this._canvas); this.callback(); } } } } const version = "4.3.8"; const build = "c419c8333"; ;// CONCATENATED MODULE: ./src/shared/scripting_utils.js function makeColorComp(n) { return Math.floor(Math.max(0, Math.min(1, n)) * 255).toString(16).padStart(2, "0"); } function scaleAndClamp(x) { return Math.max(0, Math.min(255, 255 * x)); } class ColorConverters { static CMYK_G([c, y, m, k]) { return ["G", 1 - Math.min(1, 0.3 * c + 0.59 * m + 0.11 * y + k)]; } static G_CMYK([g]) { return ["CMYK", 0, 0, 0, 1 - g]; } static G_RGB([g]) { return ["RGB", g, g, g]; } static G_rgb([g]) { g = scaleAndClamp(g); return [g, g, g]; } static G_HTML([g]) { const G = makeColorComp(g); return `#${G}${G}${G}`; } static RGB_G([r, g, b]) { return ["G", 0.3 * r + 0.59 * g + 0.11 * b]; } static RGB_rgb(color) { return color.map(scaleAndClamp); } static RGB_HTML(color) { return `#${color.map(makeColorComp).join("")}`; } static T_HTML() { return "#00000000"; } static T_rgb() { return [null]; } static CMYK_RGB([c, y, m, k]) { return ["RGB", 1 - Math.min(1, c + k), 1 - Math.min(1, m + k), 1 - Math.min(1, y + k)]; } static CMYK_rgb([c, y, m, k]) { return [scaleAndClamp(1 - Math.min(1, c + k)), scaleAndClamp(1 - Math.min(1, m + k)), scaleAndClamp(1 - Math.min(1, y + k))]; } static CMYK_HTML(components) { const rgb = this.CMYK_RGB(components).slice(1); return this.RGB_HTML(rgb); } static RGB_CMYK([r, g, b]) { const c = 1 - r; const m = 1 - g; const y = 1 - b; const k = Math.min(c, m, y); return ["CMYK", c, m, y, k]; } } ;// CONCATENATED MODULE: ./src/display/xfa_layer.js class XfaLayer { static setupStorage(html, id, element, storage, intent) { const storedData = storage.getValue(id, { value: null }); switch (element.name) { case "textarea": if (storedData.value !== null) { html.textContent = storedData.value; } if (intent === "print") { break; } html.addEventListener("input", event => { storage.setValue(id, { value: event.target.value }); }); break; case "input": if (element.attributes.type === "radio" || element.attributes.type === "checkbox") { if (storedData.value === element.attributes.xfaOn) { html.setAttribute("checked", true); } else if (storedData.value === element.attributes.xfaOff) { html.removeAttribute("checked"); } if (intent === "print") { break; } html.addEventListener("change", event => { storage.setValue(id, { value: event.target.checked ? event.target.getAttribute("xfaOn") : event.target.getAttribute("xfaOff") }); }); } else { if (storedData.value !== null) { html.setAttribute("value", storedData.value); } if (intent === "print") { break; } html.addEventListener("input", event => { storage.setValue(id, { value: event.target.value }); }); } break; case "select": if (storedData.value !== null) { html.setAttribute("value", storedData.value); for (const option of element.children) { if (option.attributes.value === storedData.value) { option.attributes.selected = true; } else if (option.attributes.hasOwnProperty("selected")) { delete option.attributes.selected; } } } html.addEventListener("input", event => { const options = event.target.options; const value = options.selectedIndex === -1 ? "" : options[options.selectedIndex].value; storage.setValue(id, { value }); }); break; } } static setAttributes({ html, element, storage = null, intent, linkService }) { const { attributes } = element; const isHTMLAnchorElement = html instanceof HTMLAnchorElement; if (attributes.type === "radio") { attributes.name = `${attributes.name}-${intent}`; } for (const [key, value] of Object.entries(attributes)) { if (value === null || value === undefined) { continue; } switch (key) { case "class": if (value.length) { html.setAttribute(key, value.join(" ")); } break; case "dataId": break; case "id": html.setAttribute("data-element-id", value); break; case "style": Object.assign(html.style, value); break; case "textContent": html.textContent = value; break; default: if (!isHTMLAnchorElement || key !== "href" && key !== "newWindow") { html.setAttribute(key, value); } } } if (isHTMLAnchorElement) { linkService.addLinkAttributes(html, attributes.href, attributes.newWindow); } if (storage && attributes.dataId) { this.setupStorage(html, attributes.dataId, element, storage); } } static render(parameters) { const storage = parameters.annotationStorage; const linkService = parameters.linkService; const root = parameters.xfaHtml; const intent = parameters.intent || "display"; const rootHtml = document.createElement(root.name); if (root.attributes) { this.setAttributes({ html: rootHtml, element: root, intent, linkService }); } const isNotForRichText = intent !== "richText"; const rootDiv = parameters.div; rootDiv.append(rootHtml); if (parameters.viewport) { const transform = `matrix(${parameters.viewport.transform.join(",")})`; rootDiv.style.transform = transform; } if (isNotForRichText) { rootDiv.setAttribute("class", "xfaLayer xfaFont"); } const textDivs = []; if (root.children.length === 0) { if (root.value) { const node = document.createTextNode(root.value); rootHtml.append(node); if (isNotForRichText && XfaText.shouldBuildText(root.name)) { textDivs.push(node); } } return { textDivs }; } const stack = [[root, -1, rootHtml]]; while (stack.length > 0) { const [parent, i, html] = stack.at(-1); if (i + 1 === parent.children.length) { stack.pop(); continue; } const child = parent.children[++stack.at(-1)[1]]; if (child === null) { continue; } const { name } = child; if (name === "#text") { const node = document.createTextNode(child.value); textDivs.push(node); html.append(node); continue; } const childHtml = child?.attributes?.xmlns ? document.createElementNS(child.attributes.xmlns, name) : document.createElement(name); html.append(childHtml); if (child.attributes) { this.setAttributes({ html: childHtml, element: child, storage, intent, linkService }); } if (child.children?.length > 0) { stack.push([child, -1, childHtml]); } else if (child.value) { const node = document.createTextNode(child.value); if (isNotForRichText && XfaText.shouldBuildText(name)) { textDivs.push(node); } childHtml.append(node); } } for (const el of rootDiv.querySelectorAll(".xfaNonInteractive input, .xfaNonInteractive textarea")) { el.setAttribute("readOnly", true); } return { textDivs }; } static update(parameters) { const transform = `matrix(${parameters.viewport.transform.join(",")})`; parameters.div.style.transform = transform; parameters.div.hidden = false; } } ;// CONCATENATED MODULE: ./src/display/annotation_layer.js const DEFAULT_TAB_INDEX = 1000; const annotation_layer_DEFAULT_FONT_SIZE = 9; const GetElementsByNameSet = new WeakSet(); function getRectDims(rect) { return { width: rect[2] - rect[0], height: rect[3] - rect[1] }; } class AnnotationElementFactory { static create(parameters) { const subtype = parameters.data.annotationType; switch (subtype) { case AnnotationType.LINK: return new LinkAnnotationElement(parameters); case AnnotationType.TEXT: return new TextAnnotationElement(parameters); case AnnotationType.WIDGET: const fieldType = parameters.data.fieldType; switch (fieldType) { case "Tx": return new TextWidgetAnnotationElement(parameters); case "Btn": if (parameters.data.radioButton) { return new RadioButtonWidgetAnnotationElement(parameters); } else if (parameters.data.checkBox) { return new CheckboxWidgetAnnotationElement(parameters); } return new PushButtonWidgetAnnotationElement(parameters); case "Ch": return new ChoiceWidgetAnnotationElement(parameters); case "Sig": return new SignatureWidgetAnnotationElement(parameters); } return new WidgetAnnotationElement(parameters); case AnnotationType.POPUP: return new PopupAnnotationElement(parameters); case AnnotationType.FREETEXT: return new FreeTextAnnotationElement(parameters); case AnnotationType.LINE: return new LineAnnotationElement(parameters); case AnnotationType.SQUARE: return new SquareAnnotationElement(parameters); case AnnotationType.CIRCLE: return new CircleAnnotationElement(parameters); case AnnotationType.POLYLINE: return new PolylineAnnotationElement(parameters); case AnnotationType.CARET: return new CaretAnnotationElement(parameters); case AnnotationType.INK: return new InkAnnotationElement(parameters); case AnnotationType.POLYGON: return new PolygonAnnotationElement(parameters); case AnnotationType.HIGHLIGHT: return new HighlightAnnotationElement(parameters); case AnnotationType.UNDERLINE: return new UnderlineAnnotationElement(parameters); case AnnotationType.SQUIGGLY: return new SquigglyAnnotationElement(parameters); case AnnotationType.STRIKEOUT: return new StrikeOutAnnotationElement(parameters); case AnnotationType.STAMP: return new StampAnnotationElement(parameters); case AnnotationType.FILEATTACHMENT: return new FileAttachmentAnnotationElement(parameters); default: return new AnnotationElement(parameters); } } } class AnnotationElement { #updates = null; #hasBorder = false; constructor(parameters, { isRenderable = false, ignoreBorder = false, createQuadrilaterals = false } = {}) { this.isRenderable = isRenderable; this.data = parameters.data; this.layer = parameters.layer; this.linkService = parameters.linkService; this.downloadManager = parameters.downloadManager; this.imageResourcesPath = parameters.imageResourcesPath; this.renderForms = parameters.renderForms; this.svgFactory = parameters.svgFactory; this.annotationStorage = parameters.annotationStorage; this.enableScripting = parameters.enableScripting; this.hasJSActions = parameters.hasJSActions; this._fieldObjects = parameters.fieldObjects; this.parent = parameters.parent; if (isRenderable) { this.container = this._createContainer(ignoreBorder); } if (createQuadrilaterals) { this._createQuadrilaterals(); } } static _hasPopupData({ titleObj, contentsObj, richText }) { return !!(titleObj?.str || contentsObj?.str || richText?.str); } get hasPopupData() { return AnnotationElement._hasPopupData(this.data); } updateEdited(params) { if (!this.container) { return; } this.#updates ||= { rect: this.data.rect.slice(0) }; const { rect } = params; if (rect) { this.#setRectEdited(rect); } } resetEdited() { if (!this.#updates) { return; } this.#setRectEdited(this.#updates.rect); this.#updates = null; } #setRectEdited(rect) { const { container: { style }, data: { rect: currentRect, rotation }, parent: { viewport: { rawDims: { pageWidth, pageHeight, pageX, pageY } } } } = this; currentRect?.splice(0, 4, ...rect); const { width, height } = getRectDims(rect); style.left = `${100 * (rect[0] - pageX) / pageWidth}%`; style.top = `${100 * (pageHeight - rect[3] + pageY) / pageHeight}%`; if (rotation === 0) { style.width = `${100 * width / pageWidth}%`; style.height = `${100 * height / pageHeight}%`; } else { this.setRotation(rotation); } } _createContainer(ignoreBorder) { const { data, parent: { page, viewport } } = this; const container = document.createElement("section"); container.setAttribute("data-annotation-id", data.id); if (!(this instanceof WidgetAnnotationElement)) { container.tabIndex = DEFAULT_TAB_INDEX; } const { style } = container; style.zIndex = this.parent.zIndex++; if (data.popupRef) { container.setAttribute("aria-haspopup", "dialog"); } if (data.alternativeText) { container.title = data.alternativeText; } if (data.noRotate) { container.classList.add("norotate"); } if (!data.rect || this instanceof PopupAnnotationElement) { const { rotation } = data; if (!data.hasOwnCanvas && rotation !== 0) { this.setRotation(rotation, container); } return container; } const { width, height } = getRectDims(data.rect); if (!ignoreBorder && data.borderStyle.width > 0) { style.borderWidth = `${data.borderStyle.width}px`; const horizontalRadius = data.borderStyle.horizontalCornerRadius; const verticalRadius = data.borderStyle.verticalCornerRadius; if (horizontalRadius > 0 || verticalRadius > 0) { const radius = `calc(${horizontalRadius}px * var(--scale-factor)) / calc(${verticalRadius}px * var(--scale-factor))`; style.borderRadius = radius; } else if (this instanceof RadioButtonWidgetAnnotationElement) { const radius = `calc(${width}px * var(--scale-factor)) / calc(${height}px * var(--scale-factor))`; style.borderRadius = radius; } switch (data.borderStyle.style) { case AnnotationBorderStyleType.SOLID: style.borderStyle = "solid"; break; case AnnotationBorderStyleType.DASHED: style.borderStyle = "dashed"; break; case AnnotationBorderStyleType.BEVELED: warn("Unimplemented border style: beveled"); break; case AnnotationBorderStyleType.INSET: warn("Unimplemented border style: inset"); break; case AnnotationBorderStyleType.UNDERLINE: style.borderBottomStyle = "solid"; break; default: break; } const borderColor = data.borderColor || null; if (borderColor) { this.#hasBorder = true; style.borderColor = Util.makeHexColor(borderColor[0] | 0, borderColor[1] | 0, borderColor[2] | 0); } else { style.borderWidth = 0; } } 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]]); const { pageWidth, pageHeight, pageX, pageY } = viewport.rawDims; style.left = `${100 * (rect[0] - pageX) / pageWidth}%`; style.top = `${100 * (rect[1] - pageY) / pageHeight}%`; const { rotation } = data; if (data.hasOwnCanvas || rotation === 0) { style.width = `${100 * width / pageWidth}%`; style.height = `${100 * height / pageHeight}%`; } else { this.setRotation(rotation, container); } return container; } setRotation(angle, container = this.container) { if (!this.data.rect) { return; } const { pageWidth, pageHeight } = this.parent.viewport.rawDims; const { width, height } = getRectDims(this.data.rect); let elementWidth, elementHeight; if (angle % 180 === 0) { elementWidth = 100 * width / pageWidth; elementHeight = 100 * height / pageHeight; } else { elementWidth = 100 * height / pageWidth; elementHeight = 100 * width / pageHeight; } container.style.width = `${elementWidth}%`; container.style.height = `${elementHeight}%`; container.setAttribute("data-main-rotation", (360 - angle) % 360); } get _commonActions() { const setColor = (jsName, styleName, event) => { const color = event.detail[jsName]; const colorType = color[0]; const colorArray = color.slice(1); event.target.style[styleName] = ColorConverters[`${colorType}_HTML`](colorArray); this.annotationStorage.setValue(this.data.id, { [styleName]: ColorConverters[`${colorType}_rgb`](colorArray) }); }; return shadow(this, "_commonActions", { display: event => { const { display } = event.detail; const hidden = display % 2 === 1; this.container.style.visibility = hidden ? "hidden" : "visible"; this.annotationStorage.setValue(this.data.id, { noView: hidden, noPrint: display === 1 || display === 2 }); }, print: event => { this.annotationStorage.setValue(this.data.id, { noPrint: !event.detail.print }); }, hidden: event => { const { hidden } = event.detail; this.container.style.visibility = hidden ? "hidden" : "visible"; this.annotationStorage.setValue(this.data.id, { noPrint: hidden, noView: hidden }); }, focus: event => { setTimeout(() => event.target.focus({ preventScroll: false }), 0); }, userName: event => { event.target.title = event.detail.userName; }, readonly: event => { event.target.disabled = event.detail.readonly; }, required: event => { this._setRequired(event.target, event.detail.required); }, bgColor: event => { setColor("bgColor", "backgroundColor", event); }, fillColor: event => { setColor("fillColor", "backgroundColor", event); }, fgColor: event => { setColor("fgColor", "color", event); }, textColor: event => { setColor("textColor", "color", event); }, borderColor: event => { setColor("borderColor", "borderColor", event); }, strokeColor: event => { setColor("strokeColor", "borderColor", event); }, rotation: event => { const angle = event.detail.rotation; this.setRotation(angle); this.annotationStorage.setValue(this.data.id, { rotation: angle }); } }); } _dispatchEventFromSandbox(actions, jsEvent) { const commonActions = this._commonActions; for (const name of Object.keys(jsEvent.detail)) { const action = actions[name] || commonActions[name]; action?.(jsEvent); } } _setDefaultPropertiesFromJS(element) { if (!this.enableScripting) { return; } const storedData = this.annotationStorage.getRawValue(this.data.id); if (!storedData) { return; } const commonActions = this._commonActions; for (const [actionName, detail] of Object.entries(storedData)) { const action = commonActions[actionName]; if (action) { const eventProxy = { detail: { [actionName]: detail }, target: element }; action(eventProxy); delete storedData[actionName]; } } } _createQuadrilaterals() { if (!this.container) { return; } const { quadPoints } = this.data; if (!quadPoints) { return; } const [rectBlX, rectBlY, rectTrX, rectTrY] = this.data.rect; if (quadPoints.length === 1) { const [, { x: trX, y: trY }, { x: blX, y: blY }] = quadPoints[0]; if (rectTrX === trX && rectTrY === trY && rectBlX === blX && rectBlY === blY) { return; } } const { style } = this.container; let svgBuffer; if (this.#hasBorder) { const { borderColor, borderWidth } = style; style.borderWidth = 0; svgBuffer = ["url('data:image/svg+xml;utf8,", ``, ``]; this.container.classList.add("hasBorder"); } const width = rectTrX - rectBlX; const height = rectTrY - rectBlY; const { svgFactory } = this; const svg = svgFactory.createElement("svg"); svg.classList.add("quadrilateralsContainer"); svg.setAttribute("width", 0); svg.setAttribute("height", 0); const defs = svgFactory.createElement("defs"); svg.append(defs); const clipPath = svgFactory.createElement("clipPath"); const id = `clippath_${this.data.id}`; clipPath.setAttribute("id", id); clipPath.setAttribute("clipPathUnits", "objectBoundingBox"); defs.append(clipPath); for (const [, { x: trX, y: trY }, { x: blX, y: blY }] of quadPoints) { const rect = svgFactory.createElement("rect"); const x = (blX - rectBlX) / width; const y = (rectTrY - trY) / height; const rectWidth = (trX - blX) / width; const rectHeight = (trY - blY) / height; rect.setAttribute("x", x); rect.setAttribute("y", y); rect.setAttribute("width", rectWidth); rect.setAttribute("height", rectHeight); clipPath.append(rect); svgBuffer?.push(``); } if (this.#hasBorder) { svgBuffer.push(`')`); style.backgroundImage = svgBuffer.join(""); } this.container.append(svg); this.container.style.clipPath = `url(#${id})`; } _createPopup() { const { container, data } = this; container.setAttribute("aria-haspopup", "dialog"); const popup = new PopupAnnotationElement({ data: { color: data.color, titleObj: data.titleObj, modificationDate: data.modificationDate, contentsObj: data.contentsObj, richText: data.richText, parentRect: data.rect, borderStyle: 0, id: `popup_${data.id}`, rotation: data.rotation }, parent: this.parent, elements: [this] }); this.parent.div.append(popup.render()); } render() { unreachable("Abstract method `AnnotationElement.render` called"); } _getElementsByName(name, skipId = null) { const fields = []; if (this._fieldObjects) { const fieldObj = this._fieldObjects[name]; if (fieldObj) { for (const { page, id, exportValues } of fieldObj) { if (page === -1) { continue; } if (id === skipId) { continue; } const exportValue = typeof exportValues === "string" ? exportValues : null; const domElement = document.querySelector(`[data-element-id="${id}"]`); if (domElement && !GetElementsByNameSet.has(domElement)) { warn(`_getElementsByName - element not allowed: ${id}`); continue; } fields.push({ id, exportValue, domElement }); } } return fields; } for (const domElement of document.getElementsByName(name)) { const { exportValue } = domElement; const id = domElement.getAttribute("data-element-id"); if (id === skipId) { continue; } if (!GetElementsByNameSet.has(domElement)) { continue; } fields.push({ id, exportValue, domElement }); } return fields; } show() { if (this.container) { this.container.hidden = false; } this.popup?.maybeShow(); } hide() { if (this.container) { this.container.hidden = true; } this.popup?.forceHide(); } getElementsToTriggerPopup() { return this.container; } addHighlightArea() { const triggers = this.getElementsToTriggerPopup(); if (Array.isArray(triggers)) { for (const element of triggers) { element.classList.add("highlightArea"); } } else { triggers.classList.add("highlightArea"); } } get _isEditable() { return false; } _editOnDoubleClick() { if (!this._isEditable) { return; } const { annotationEditorType: mode, data: { id: editId } } = this; this.container.addEventListener("dblclick", () => { this.linkService.eventBus?.dispatch("switchannotationeditormode", { source: this, mode, editId }); }); } } class LinkAnnotationElement extends AnnotationElement { constructor(parameters, options = null) { super(parameters, { isRenderable: true, ignoreBorder: !!options?.ignoreBorder, createQuadrilaterals: true }); this.isTooltipOnly = parameters.data.isTooltipOnly; } render() { const { data, linkService } = this; const link = document.createElement("a"); link.setAttribute("data-element-id", data.id); let isBound = false; if (data.url) { linkService.addLinkAttributes(link, data.url, data.newWindow); isBound = true; } else if (data.action) { this._bindNamedAction(link, data.action); isBound = true; } else if (data.attachment) { this.#bindAttachment(link, data.attachment, data.attachmentDest); isBound = true; } else if (data.setOCGState) { this.#bindSetOCGState(link, data.setOCGState); isBound = true; } else if (data.dest) { this._bindLink(link, data.dest); isBound = true; } else { if (data.actions && (data.actions.Action || data.actions["Mouse Up"] || data.actions["Mouse Down"]) && this.enableScripting && this.hasJSActions) { this._bindJSAction(link, data); isBound = true; } if (data.resetForm) { this._bindResetFormAction(link, data.resetForm); isBound = true; } else if (this.isTooltipOnly && !isBound) { this._bindLink(link, ""); isBound = true; } } this.container.classList.add("linkAnnotation"); if (isBound) { this.container.append(link); } return this.container; } #setInternalLink() { this.container.setAttribute("data-internal-link", ""); } _bindLink(link, destination) { link.href = this.linkService.getDestinationHash(destination); link.onclick = () => { if (destination) { this.linkService.goToDestination(destination); } return false; }; if (destination || destination === "") { this.#setInternalLink(); } } _bindNamedAction(link, action) { link.href = this.linkService.getAnchorUrl(""); link.onclick = () => { this.linkService.executeNamedAction(action); return false; }; this.#setInternalLink(); } #bindAttachment(link, attachment, dest = null) { link.href = this.linkService.getAnchorUrl(""); if (attachment.description) { link.title = attachment.description; } link.onclick = () => { this.downloadManager?.openOrDownloadData(attachment.content, attachment.filename, dest); return false; }; this.#setInternalLink(); } #bindSetOCGState(link, action) { link.href = this.linkService.getAnchorUrl(""); link.onclick = () => { this.linkService.executeSetOCGState(action); return false; }; this.#setInternalLink(); } _bindJSAction(link, data) { link.href = this.linkService.getAnchorUrl(""); const map = new Map([["Action", "onclick"], ["Mouse Up", "onmouseup"], ["Mouse Down", "onmousedown"]]); for (const name of Object.keys(data.actions)) { const jsName = map.get(name); if (!jsName) { continue; } link[jsName] = () => { this.linkService.eventBus?.dispatch("dispatcheventinsandbox", { source: this, detail: { id: data.id, name } }); return false; }; } if (!link.onclick) { link.onclick = () => false; } this.#setInternalLink(); } _bindResetFormAction(link, resetForm) { const otherClickAction = link.onclick; if (!otherClickAction) { link.href = this.linkService.getAnchorUrl(""); } this.#setInternalLink(); if (!this._fieldObjects) { warn(`_bindResetFormAction - "resetForm" action not supported, ` + "ensure that the `fieldObjects` parameter is provided."); if (!otherClickAction) { link.onclick = () => false; } return; } link.onclick = () => { otherClickAction?.(); const { fields: resetFormFields, refs: resetFormRefs, include } = resetForm; const allFields = []; if (resetFormFields.length !== 0 || resetFormRefs.length !== 0) { const fieldIds = new Set(resetFormRefs); for (const fieldName of resetFormFields) { const fields = this._fieldObjects[fieldName] || []; for (const { id } of fields) { fieldIds.add(id); } } for (const fields of Object.values(this._fieldObjects)) { for (const field of fields) { if (fieldIds.has(field.id) === include) { allFields.push(field); } } } } else { for (const fields of Object.values(this._fieldObjects)) { allFields.push(...fields); } } const storage = this.annotationStorage; const allIds = []; for (const field of allFields) { const { id } = field; allIds.push(id); switch (field.type) { case "text": { const value = field.defaultValue || ""; storage.setValue(id, { value }); break; } case "checkbox": case "radiobutton": { const value = field.defaultValue === field.exportValues; storage.setValue(id, { value }); break; } case "combobox": case "listbox": { const value = field.defaultValue || ""; storage.setValue(id, { value }); break; } default: continue; } const domElement = document.querySelector(`[data-element-id="${id}"]`); if (!domElement) { continue; } else if (!GetElementsByNameSet.has(domElement)) { warn(`_bindResetFormAction - element not allowed: ${id}`); continue; } domElement.dispatchEvent(new Event("resetform")); } if (this.enableScripting) { this.linkService.eventBus?.dispatch("dispatcheventinsandbox", { source: this, detail: { id: "app", ids: allIds, name: "ResetForm" } }); } return false; }; } } class TextAnnotationElement extends AnnotationElement { constructor(parameters) { super(parameters, { isRenderable: true }); } render() { this.container.classList.add("textAnnotation"); const image = document.createElement("img"); image.src = this.imageResourcesPath + "annotation-" + this.data.name.toLowerCase() + ".svg"; image.setAttribute("data-l10n-id", "pdfjs-text-annotation-type"); image.setAttribute("data-l10n-args", JSON.stringify({ type: this.data.name })); if (!this.data.popupRef && this.hasPopupData) { this._createPopup(); } this.container.append(image); return this.container; } } class WidgetAnnotationElement extends AnnotationElement { render() { return this.container; } showElementAndHideCanvas(element) { if (this.data.hasOwnCanvas) { if (element.previousSibling?.nodeName === "CANVAS") { element.previousSibling.hidden = true; } element.hidden = false; } } _getKeyModifier(event) { return util_FeatureTest.platform.isMac ? event.metaKey : event.ctrlKey; } _setEventListener(element, elementData, baseName, eventName, valueGetter) { if (baseName.includes("mouse")) { element.addEventListener(baseName, event => { this.linkService.eventBus?.dispatch("dispatcheventinsandbox", { source: this, detail: { id: this.data.id, name: eventName, value: valueGetter(event), shift: event.shiftKey, modifier: this._getKeyModifier(event) } }); }); } else { element.addEventListener(baseName, event => { if (baseName === "blur") { if (!elementData.focused || !event.relatedTarget) { return; } elementData.focused = false; } else if (baseName === "focus") { if (elementData.focused) { return; } elementData.focused = true; } if (!valueGetter) { return; } this.linkService.eventBus?.dispatch("dispatcheventinsandbox", { source: this, detail: { id: this.data.id, name: eventName, value: valueGetter(event) } }); }); } } _setEventListeners(element, elementData, names, getter) { for (const [baseName, eventName] of names) { if (eventName === "Action" || this.data.actions?.[eventName]) { if (eventName === "Focus" || eventName === "Blur") { elementData ||= { focused: false }; } this._setEventListener(element, elementData, baseName, eventName, getter); if (eventName === "Focus" && !this.data.actions?.Blur) { this._setEventListener(element, elementData, "blur", "Blur", null); } else if (eventName === "Blur" && !this.data.actions?.Focus) { this._setEventListener(element, elementData, "focus", "Focus", null); } } } } _setBackgroundColor(element) { const color = this.data.backgroundColor || null; element.style.backgroundColor = color === null ? "transparent" : Util.makeHexColor(color[0], color[1], color[2]); } _setTextStyle(element) { const TEXT_ALIGNMENT = ["left", "center", "right"]; const { fontColor } = this.data.defaultAppearanceData; const fontSize = this.data.defaultAppearanceData.fontSize || annotation_layer_DEFAULT_FONT_SIZE; const style = element.style; let computedFontSize; const BORDER_SIZE = 2; const roundToOneDecimal = x => Math.round(10 * x) / 10; if (this.data.multiLine) { const height = Math.abs(this.data.rect[3] - this.data.rect[1] - BORDER_SIZE); const numberOfLines = Math.round(height / (LINE_FACTOR * fontSize)) || 1; const lineHeight = height / numberOfLines; computedFontSize = Math.min(fontSize, roundToOneDecimal(lineHeight / LINE_FACTOR)); } else { const height = Math.abs(this.data.rect[3] - this.data.rect[1] - BORDER_SIZE); computedFontSize = Math.min(fontSize, roundToOneDecimal(height / LINE_FACTOR)); } style.fontSize = `calc(${computedFontSize}px * var(--scale-factor))`; style.color = Util.makeHexColor(fontColor[0], fontColor[1], fontColor[2]); if (this.data.textAlignment !== null) { style.textAlign = TEXT_ALIGNMENT[this.data.textAlignment]; } } _setRequired(element, isRequired) { if (isRequired) { element.setAttribute("required", true); } else { element.removeAttribute("required"); } element.setAttribute("aria-required", isRequired); } } class TextWidgetAnnotationElement extends WidgetAnnotationElement { constructor(parameters) { const isRenderable = parameters.renderForms || parameters.data.hasOwnCanvas || !parameters.data.hasAppearance && !!parameters.data.fieldValue; super(parameters, { isRenderable }); } setPropertyOnSiblings(base, key, value, keyInStorage) { const storage = this.annotationStorage; for (const element of this._getElementsByName(base.name, base.id)) { if (element.domElement) { element.domElement[key] = value; } storage.setValue(element.id, { [keyInStorage]: value }); } } render() { const storage = this.annotationStorage; const id = this.data.id; this.container.classList.add("textWidgetAnnotation"); let element = null; if (this.renderForms) { const storedData = storage.getValue(id, { value: this.data.fieldValue }); let textContent = storedData.value || ""; const maxLen = storage.getValue(id, { charLimit: this.data.maxLen }).charLimit; if (maxLen && textContent.length > maxLen) { textContent = textContent.slice(0, maxLen); } let fieldFormattedValues = storedData.formattedValue || this.data.textContent?.join("\n") || null; if (fieldFormattedValues && this.data.comb) { fieldFormattedValues = fieldFormattedValues.replaceAll(/\s+/g, ""); } const elementData = { userValue: textContent, formattedValue: fieldFormattedValues, lastCommittedValue: null, commitKey: 1, focused: false }; if (this.data.multiLine) { element = document.createElement("textarea"); element.textContent = fieldFormattedValues ?? textContent; if (this.data.doNotScroll) { element.style.overflowY = "hidden"; } } else { element = document.createElement("input"); element.type = "text"; element.setAttribute("value", fieldFormattedValues ?? textContent); if (this.data.doNotScroll) { element.style.overflowX = "hidden"; } } if (this.data.hasOwnCanvas) { element.hidden = true; } GetElementsByNameSet.add(element); element.setAttribute("data-element-id", id); element.disabled = this.data.readOnly; element.name = this.data.fieldName; element.tabIndex = DEFAULT_TAB_INDEX; this._setRequired(element, this.data.required); if (maxLen) { element.maxLength = maxLen; } element.addEventListener("input", event => { storage.setValue(id, { value: event.target.value }); this.setPropertyOnSiblings(element, "value", event.target.value, "value"); elementData.formattedValue = null; }); element.addEventListener("resetform", event => { const defaultValue = this.data.defaultFieldValue ?? ""; element.value = elementData.userValue = defaultValue; elementData.formattedValue = null; }); let blurListener = event => { const { formattedValue } = elementData; if (formattedValue !== null && formattedValue !== undefined) { event.target.value = formattedValue; } event.target.scrollLeft = 0; }; if (this.enableScripting && this.hasJSActions) { element.addEventListener("focus", event => { if (elementData.focused) { return; } const { target } = event; if (elementData.userValue) { target.value = elementData.userValue; } elementData.lastCommittedValue = target.value; elementData.commitKey = 1; if (!this.data.actions?.Focus) { elementData.focused = true; } }); element.addEventListener("updatefromsandbox", jsEvent => { this.showElementAndHideCanvas(jsEvent.target); const actions = { value(event) { elementData.userValue = event.detail.value ?? ""; storage.setValue(id, { value: elementData.userValue.toString() }); event.target.value = elementData.userValue; }, formattedValue(event) { const { formattedValue } = event.detail; elementData.formattedValue = formattedValue; if (formattedValue !== null && formattedValue !== undefined && event.target !== document.activeElement) { event.target.value = formattedValue; } storage.setValue(id, { formattedValue }); }, selRange(event) { event.target.setSelectionRange(...event.detail.selRange); }, charLimit: event => { const { charLimit } = event.detail; const { target } = event; if (charLimit === 0) { target.removeAttribute("maxLength"); return; } target.setAttribute("maxLength", charLimit); let value = elementData.userValue; if (!value || value.length <= charLimit) { return; } value = value.slice(0, charLimit); target.value = elementData.userValue = value; storage.setValue(id, { value }); this.linkService.eventBus?.dispatch("dispatcheventinsandbox", { source: this, detail: { id, name: "Keystroke", value, willCommit: true, commitKey: 1, selStart: target.selectionStart, selEnd: target.selectionEnd } }); } }; this._dispatchEventFromSandbox(actions, jsEvent); }); element.addEventListener("keydown", event => { elementData.commitKey = 1; let commitKey = -1; if (event.key === "Escape") { commitKey = 0; } else if (event.key === "Enter" && !this.data.multiLine) { commitKey = 2; } else if (event.key === "Tab") { elementData.commitKey = 3; } if (commitKey === -1) { return; } const { value } = event.target; if (elementData.lastCommittedValue === value) { return; } elementData.lastCommittedValue = value; elementData.userValue = value; this.linkService.eventBus?.dispatch("dispatcheventinsandbox", { source: this, detail: { id, name: "Keystroke", value, willCommit: true, commitKey, selStart: event.target.selectionStart, selEnd: event.target.selectionEnd } }); }); const _blurListener = blurListener; blurListener = null; element.addEventListener("blur", event => { if (!elementData.focused || !event.relatedTarget) { return; } if (!this.data.actions?.Blur) { elementData.focused = false; } const { value } = event.target; elementData.userValue = value; if (elementData.lastCommittedValue !== value) { this.linkService.eventBus?.dispatch("dispatcheventinsandbox", { source: this, detail: { id, name: "Keystroke", value, willCommit: true, commitKey: elementData.commitKey, selStart: event.target.selectionStart, selEnd: event.target.selectionEnd } }); } _blurListener(event); }); if (this.data.actions?.Keystroke) { element.addEventListener("beforeinput", event => { elementData.lastCommittedValue = null; const { data, target } = event; const { value, selectionStart, selectionEnd } = target; let selStart = selectionStart, selEnd = selectionEnd; switch (event.inputType) { case "deleteWordBackward": { const match = value.substring(0, selectionStart).match(/\w*[^\w]*$/); if (match) { selStart -= match[0].length; } break; } case "deleteWordForward": { const match = value.substring(selectionStart).match(/^[^\w]*\w*/); if (match) { selEnd += match[0].length; } break; } case "deleteContentBackward": if (selectionStart === selectionEnd) { selStart -= 1; } break; case "deleteContentForward": if (selectionStart === selectionEnd) { selEnd += 1; } break; } event.preventDefault(); this.linkService.eventBus?.dispatch("dispatcheventinsandbox", { source: this, detail: { id, name: "Keystroke", value, change: data || "", willCommit: false, selStart, selEnd } }); }); } this._setEventListeners(element, elementData, [["focus", "Focus"], ["blur", "Blur"], ["mousedown", "Mouse Down"], ["mouseenter", "Mouse Enter"], ["mouseleave", "Mouse Exit"], ["mouseup", "Mouse Up"]], event => event.target.value); } if (blurListener) { element.addEventListener("blur", blurListener); } if (this.data.comb) { const fieldWidth = this.data.rect[2] - this.data.rect[0]; const combWidth = fieldWidth / maxLen; element.classList.add("comb"); element.style.letterSpacing = `calc(${combWidth}px * var(--scale-factor) - 1ch)`; } } else { element = document.createElement("div"); element.textContent = this.data.fieldValue; element.style.verticalAlign = "middle"; element.style.display = "table-cell"; if (this.data.hasOwnCanvas) { element.hidden = true; } } this._setTextStyle(element); this._setBackgroundColor(element); this._setDefaultPropertiesFromJS(element); this.container.append(element); return this.container; } } class SignatureWidgetAnnotationElement extends WidgetAnnotationElement { constructor(parameters) { super(parameters, { isRenderable: !!parameters.data.hasOwnCanvas }); } } class CheckboxWidgetAnnotationElement extends WidgetAnnotationElement { constructor(parameters) { super(parameters, { isRenderable: parameters.renderForms }); } render() { const storage = this.annotationStorage; const data = this.data; const id = data.id; let value = storage.getValue(id, { value: data.exportValue === data.fieldValue }).value; if (typeof value === "string") { value = value !== "Off"; storage.setValue(id, { value }); } this.container.classList.add("buttonWidgetAnnotation", "checkBox"); const element = document.createElement("input"); GetElementsByNameSet.add(element); element.setAttribute("data-element-id", id); element.disabled = data.readOnly; this._setRequired(element, this.data.required); element.type = "checkbox"; element.name = data.fieldName; if (value) { element.setAttribute("checked", true); } element.setAttribute("exportValue", data.exportValue); element.tabIndex = DEFAULT_TAB_INDEX; element.addEventListener("change", event => { const { name, checked } = event.target; for (const checkbox of this._getElementsByName(name, id)) { const curChecked = checked && checkbox.exportValue === data.exportValue; if (checkbox.domElement) { checkbox.domElement.checked = curChecked; } storage.setValue(checkbox.id, { value: curChecked }); } storage.setValue(id, { value: checked }); }); element.addEventListener("resetform", event => { const defaultValue = data.defaultFieldValue || "Off"; event.target.checked = defaultValue === data.exportValue; }); if (this.enableScripting && this.hasJSActions) { element.addEventListener("updatefromsandbox", jsEvent => { const actions = { value(event) { event.target.checked = event.detail.value !== "Off"; storage.setValue(id, { value: event.target.checked }); } }; this._dispatchEventFromSandbox(actions, jsEvent); }); 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); } this._setBackgroundColor(element); this._setDefaultPropertiesFromJS(element); this.container.append(element); return this.container; } } class RadioButtonWidgetAnnotationElement extends WidgetAnnotationElement { constructor(parameters) { super(parameters, { isRenderable: parameters.renderForms }); } render() { this.container.classList.add("buttonWidgetAnnotation", "radioButton"); const storage = this.annotationStorage; const data = this.data; const id = data.id; let value = storage.getValue(id, { value: data.fieldValue === data.buttonValue }).value; if (typeof value === "string") { value = value !== data.buttonValue; storage.setValue(id, { value }); } if (value) { for (const radio of this._getElementsByName(data.fieldName, id)) { storage.setValue(radio.id, { value: false }); } } const element = document.createElement("input"); GetElementsByNameSet.add(element); element.setAttribute("data-element-id", id); element.disabled = data.readOnly; this._setRequired(element, this.data.required); element.type = "radio"; element.name = data.fieldName; if (value) { element.setAttribute("checked", true); } element.tabIndex = DEFAULT_TAB_INDEX; element.addEventListener("change", event => { const { name, checked } = event.target; for (const radio of this._getElementsByName(name, id)) { storage.setValue(radio.id, { value: false }); } storage.setValue(id, { value: checked }); }); element.addEventListener("resetform", event => { const defaultValue = data.defaultFieldValue; event.target.checked = defaultValue !== null && defaultValue !== undefined && defaultValue === data.buttonValue; }); if (this.enableScripting && this.hasJSActions) { const pdfButtonValue = data.buttonValue; element.addEventListener("updatefromsandbox", jsEvent => { const actions = { value: event => { const checked = pdfButtonValue === event.detail.value; for (const radio of this._getElementsByName(event.target.name)) { const curChecked = checked && radio.id === id; if (radio.domElement) { radio.domElement.checked = curChecked; } storage.setValue(radio.id, { value: curChecked }); } } }; this._dispatchEventFromSandbox(actions, jsEvent); }); 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); } this._setBackgroundColor(element); this._setDefaultPropertiesFromJS(element); this.container.append(element); return this.container; } } class PushButtonWidgetAnnotationElement extends LinkAnnotationElement { constructor(parameters) { super(parameters, { ignoreBorder: parameters.data.hasAppearance }); } render() { const container = super.render(); container.classList.add("buttonWidgetAnnotation", "pushButton"); const linkElement = container.lastChild; if (this.enableScripting && this.hasJSActions && linkElement) { this._setDefaultPropertiesFromJS(linkElement); linkElement.addEventListener("updatefromsandbox", jsEvent => { this._dispatchEventFromSandbox({}, jsEvent); }); } return container; } } class ChoiceWidgetAnnotationElement extends WidgetAnnotationElement { constructor(parameters) { super(parameters, { isRenderable: parameters.renderForms }); } render() { this.container.classList.add("choiceWidgetAnnotation"); const storage = this.annotationStorage; const id = this.data.id; const storedData = storage.getValue(id, { value: this.data.fieldValue }); const selectElement = document.createElement("select"); GetElementsByNameSet.add(selectElement); selectElement.setAttribute("data-element-id", id); selectElement.disabled = this.data.readOnly; this._setRequired(selectElement, this.data.required); selectElement.name = this.data.fieldName; selectElement.tabIndex = DEFAULT_TAB_INDEX; let addAnEmptyEntry = this.data.combo && this.data.options.length > 0; if (!this.data.combo) { selectElement.size = this.data.options.length; if (this.data.multiSelect) { selectElement.multiple = true; } } selectElement.addEventListener("resetform", event => { const defaultValue = this.data.defaultFieldValue; for (const option of selectElement.options) { option.selected = option.value === defaultValue; } }); for (const option of this.data.options) { const optionElement = document.createElement("option"); optionElement.textContent = option.displayValue; optionElement.value = option.exportValue; if (storedData.value.includes(option.exportValue)) { optionElement.setAttribute("selected", true); addAnEmptyEntry = false; } selectElement.append(optionElement); } let removeEmptyEntry = null; if (addAnEmptyEntry) { const noneOptionElement = document.createElement("option"); noneOptionElement.value = " "; noneOptionElement.setAttribute("hidden", true); noneOptionElement.setAttribute("selected", true); selectElement.prepend(noneOptionElement); removeEmptyEntry = () => { noneOptionElement.remove(); selectElement.removeEventListener("input", removeEmptyEntry); removeEmptyEntry = null; }; selectElement.addEventListener("input", removeEmptyEntry); } const getValue = isExport => { const name = isExport ? "value" : "textContent"; const { options, multiple } = selectElement; if (!multiple) { return options.selectedIndex === -1 ? null : options[options.selectedIndex][name]; } return Array.prototype.filter.call(options, option => option.selected).map(option => option[name]); }; let selectedValues = getValue(false); const getItems = event => { const options = event.target.options; return Array.prototype.map.call(options, option => ({ displayValue: option.textContent, exportValue: option.value })); }; if (this.enableScripting && this.hasJSActions) { selectElement.addEventListener("updatefromsandbox", jsEvent => { const actions = { value(event) { removeEmptyEntry?.(); const value = event.detail.value; const values = new Set(Array.isArray(value) ? value : [value]); for (const option of selectElement.options) { option.selected = values.has(option.value); } storage.setValue(id, { value: getValue(true) }); selectedValues = getValue(false); }, multipleSelection(event) { selectElement.multiple = true; }, remove(event) { const options = selectElement.options; const index = event.detail.remove; options[index].selected = false; selectElement.remove(index); if (options.length > 0) { const i = Array.prototype.findIndex.call(options, option => option.selected); if (i === -1) { options[0].selected = true; } } storage.setValue(id, { value: getValue(true), items: getItems(event) }); selectedValues = getValue(false); }, clear(event) { while (selectElement.length !== 0) { selectElement.remove(0); } storage.setValue(id, { value: null, items: [] }); selectedValues = getValue(false); }, insert(event) { const { index, displayValue, exportValue } = event.detail.insert; const selectChild = selectElement.children[index]; const optionElement = document.createElement("option"); optionElement.textContent = displayValue; optionElement.value = exportValue; if (selectChild) { selectChild.before(optionElement); } else { selectElement.append(optionElement); } storage.setValue(id, { value: getValue(true), items: getItems(event) }); selectedValues = getValue(false); }, items(event) { const { items } = event.detail; while (selectElement.length !== 0) { selectElement.remove(0); } for (const item of items) { const { displayValue, exportValue } = item; const optionElement = document.createElement("option"); optionElement.textContent = displayValue; optionElement.value = exportValue; selectElement.append(optionElement); } if (selectElement.options.length > 0) { selectElement.options[0].selected = true; } storage.setValue(id, { value: getValue(true), items: getItems(event) }); selectedValues = getValue(false); }, indices(event) { const indices = new Set(event.detail.indices); for (const option of event.target.options) { option.selected = indices.has(option.index); } storage.setValue(id, { value: getValue(true) }); selectedValues = getValue(false); }, editable(event) { event.target.disabled = !event.detail.editable; } }; this._dispatchEventFromSandbox(actions, jsEvent); }); selectElement.addEventListener("input", event => { const exportValue = getValue(true); const change = getValue(false); storage.setValue(id, { value: exportValue }); event.preventDefault(); this.linkService.eventBus?.dispatch("dispatcheventinsandbox", { source: this, detail: { id, name: "Keystroke", value: selectedValues, change, changeEx: exportValue, willCommit: false, commitKey: 1, keyDown: false } }); }); 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); } else { selectElement.addEventListener("input", function (event) { storage.setValue(id, { value: getValue(true) }); }); } if (this.data.combo) { this._setTextStyle(selectElement); } else {} this._setBackgroundColor(selectElement); this._setDefaultPropertiesFromJS(selectElement); this.container.append(selectElement); return this.container; } } class PopupAnnotationElement extends AnnotationElement { constructor(parameters) { const { data, elements } = parameters; super(parameters, { isRenderable: AnnotationElement._hasPopupData(data) }); this.elements = elements; } render() { this.container.classList.add("popupAnnotation"); const popup = new PopupElement({ container: this.container, color: this.data.color, titleObj: this.data.titleObj, modificationDate: this.data.modificationDate, contentsObj: this.data.contentsObj, richText: this.data.richText, rect: this.data.rect, parentRect: this.data.parentRect || null, parent: this.parent, elements: this.elements, open: this.data.open }); const elementIds = []; for (const element of this.elements) { element.popup = popup; elementIds.push(element.data.id); element.addHighlightArea(); } this.container.setAttribute("aria-controls", elementIds.map(id => `${AnnotationPrefix}${id}`).join(",")); return this.container; } } class PopupElement { #boundKeyDown = this.#keyDown.bind(this); #boundHide = this.#hide.bind(this); #boundShow = this.#show.bind(this); #boundToggle = this.#toggle.bind(this); #color = null; #container = null; #contentsObj = null; #dateObj = null; #elements = null; #parent = null; #parentRect = null; #pinned = false; #popup = null; #rect = null; #richText = null; #titleObj = null; #wasVisible = false; constructor({ container, color, elements, titleObj, modificationDate, contentsObj, richText, parent, rect, parentRect, open }) { this.#container = container; this.#titleObj = titleObj; this.#contentsObj = contentsObj; this.#richText = richText; this.#parent = parent; this.#color = color; this.#rect = rect; this.#parentRect = parentRect; this.#elements = elements; this.#dateObj = PDFDateString.toDateObject(modificationDate); this.trigger = elements.flatMap(e => e.getElementsToTriggerPopup()); for (const element of this.trigger) { element.addEventListener("click", this.#boundToggle); element.addEventListener("mouseenter", this.#boundShow); element.addEventListener("mouseleave", this.#boundHide); element.classList.add("popupTriggerArea"); } for (const element of elements) { element.container?.addEventListener("keydown", this.#boundKeyDown); } this.#container.hidden = true; if (open) { this.#toggle(); } } render() { if (this.#popup) { return; } const { page: { view }, viewport: { rawDims: { pageWidth, pageHeight, pageX, pageY } } } = this.#parent; const popup = this.#popup = document.createElement("div"); popup.className = "popup"; if (this.#color) { const baseColor = popup.style.outlineColor = Util.makeHexColor(...this.#color); popup.style.backgroundColor = `color-mix(in srgb, ${baseColor} 30%, white)`; } const header = document.createElement("span"); header.className = "header"; const title = document.createElement("h1"); header.append(title); ({ dir: title.dir, str: title.textContent } = this.#titleObj); popup.append(header); if (this.#dateObj) { const modificationDate = document.createElement("span"); modificationDate.classList.add("popupDate"); modificationDate.setAttribute("data-l10n-id", "pdfjs-annotation-date-string"); modificationDate.setAttribute("data-l10n-args", JSON.stringify({ date: this.#dateObj.toLocaleDateString(), time: this.#dateObj.toLocaleTimeString() })); header.append(modificationDate); } const contentsObj = this.#contentsObj; const richText = this.#richText; if (richText?.str && (!contentsObj?.str || contentsObj.str === richText.str)) { XfaLayer.render({ xfaHtml: richText.html, intent: "richText", div: popup }); popup.lastChild.classList.add("richText", "popupContent"); } else { const contents = this._formatContents(contentsObj); popup.append(contents); } let useParentRect = !!this.#parentRect; let rect = useParentRect ? this.#parentRect : this.#rect; for (const element of this.#elements) { if (!rect || Util.intersect(element.data.rect, rect) !== null) { rect = element.data.rect; useParentRect = true; break; } } const normalizedRect = Util.normalizeRect([rect[0], view[3] - rect[1] + view[1], rect[2], view[3] - rect[3] + view[1]]); const HORIZONTAL_SPACE_AFTER_ANNOTATION = 5; const parentWidth = useParentRect ? rect[2] - rect[0] + HORIZONTAL_SPACE_AFTER_ANNOTATION : 0; const popupLeft = normalizedRect[0] + parentWidth; const popupTop = normalizedRect[1]; const { style } = this.#container; style.left = `${100 * (popupLeft - pageX) / pageWidth}%`; style.top = `${100 * (popupTop - pageY) / pageHeight}%`; this.#container.append(popup); } _formatContents({ str, dir }) { const p = document.createElement("p"); p.classList.add("popupContent"); p.dir = dir; const lines = str.split(/(?:\r\n?|\n)/); for (let i = 0, ii = lines.length; i < ii; ++i) { const line = lines[i]; p.append(document.createTextNode(line)); if (i < ii - 1) { p.append(document.createElement("br")); } } return p; } #keyDown(event) { if (event.altKey || event.shiftKey || event.ctrlKey || event.metaKey) { return; } if (event.key === "Enter" || event.key === "Escape" && this.#pinned) { this.#toggle(); } } #toggle() { this.#pinned = !this.#pinned; if (this.#pinned) { this.#show(); this.#container.addEventListener("click", this.#boundToggle); this.#container.addEventListener("keydown", this.#boundKeyDown); } else { this.#hide(); this.#container.removeEventListener("click", this.#boundToggle); this.#container.removeEventListener("keydown", this.#boundKeyDown); } } #show() { if (!this.#popup) { this.render(); } if (!this.isVisible) { this.#container.hidden = false; this.#container.style.zIndex = parseInt(this.#container.style.zIndex) + 1000; } else if (this.#pinned) { this.#container.classList.add("focused"); } } #hide() { this.#container.classList.remove("focused"); if (this.#pinned || !this.isVisible) { return; } this.#container.hidden = true; this.#container.style.zIndex = parseInt(this.#container.style.zIndex) - 1000; } forceHide() { this.#wasVisible = this.isVisible; if (!this.#wasVisible) { return; } this.#container.hidden = true; } maybeShow() { if (!this.#wasVisible) { return; } this.#wasVisible = false; this.#container.hidden = false; } get isVisible() { return this.#container.hidden === false; } } class FreeTextAnnotationElement extends AnnotationElement { constructor(parameters) { super(parameters, { isRenderable: true, ignoreBorder: true }); this.textContent = parameters.data.textContent; this.textPosition = parameters.data.textPosition; this.annotationEditorType = AnnotationEditorType.FREETEXT; } render() { this.container.classList.add("freeTextAnnotation"); if (this.textContent) { const content = document.createElement("div"); content.classList.add("annotationTextContent"); content.setAttribute("role", "comment"); for (const line of this.textContent) { const lineSpan = document.createElement("span"); lineSpan.textContent = line; content.append(lineSpan); } this.container.append(content); } if (!this.data.popupRef && this.hasPopupData) { this._createPopup(); } this._editOnDoubleClick(); return this.container; } get _isEditable() { return this.data.hasOwnCanvas; } } class LineAnnotationElement extends AnnotationElement { #line = null; constructor(parameters) { super(parameters, { isRenderable: true, ignoreBorder: true }); } render() { this.container.classList.add("lineAnnotation"); const data = this.data; const { width, height } = getRectDims(data.rect); const svg = this.svgFactory.create(width, height, true); const line = this.#line = this.svgFactory.createElement("svg:line"); line.setAttribute("x1", data.rect[2] - data.lineCoordinates[0]); line.setAttribute("y1", data.rect[3] - data.lineCoordinates[1]); line.setAttribute("x2", data.rect[2] - data.lineCoordinates[2]); line.setAttribute("y2", data.rect[3] - data.lineCoordinates[3]); line.setAttribute("stroke-width", data.borderStyle.width || 1); line.setAttribute("stroke", "transparent"); line.setAttribute("fill", "transparent"); svg.append(line); this.container.append(svg); if (!data.popupRef && this.hasPopupData) { this._createPopup(); } return this.container; } getElementsToTriggerPopup() { return this.#line; } addHighlightArea() { this.container.classList.add("highlightArea"); } } class SquareAnnotationElement extends AnnotationElement { #square = null; constructor(parameters) { super(parameters, { isRenderable: true, ignoreBorder: true }); } render() { this.container.classList.add("squareAnnotation"); const data = this.data; const { width, height } = getRectDims(data.rect); const svg = this.svgFactory.create(width, height, true); const borderWidth = data.borderStyle.width; const square = this.#square = this.svgFactory.createElement("svg:rect"); square.setAttribute("x", borderWidth / 2); square.setAttribute("y", borderWidth / 2); square.setAttribute("width", width - borderWidth); square.setAttribute("height", height - borderWidth); square.setAttribute("stroke-width", borderWidth || 1); square.setAttribute("stroke", "transparent"); square.setAttribute("fill", "transparent"); svg.append(square); this.container.append(svg); if (!data.popupRef && this.hasPopupData) { this._createPopup(); } return this.container; } getElementsToTriggerPopup() { return this.#square; } addHighlightArea() { this.container.classList.add("highlightArea"); } } class CircleAnnotationElement extends AnnotationElement { #circle = null; constructor(parameters) { super(parameters, { isRenderable: true, ignoreBorder: true }); } render() { this.container.classList.add("circleAnnotation"); const data = this.data; const { width, height } = getRectDims(data.rect); const svg = this.svgFactory.create(width, height, true); const borderWidth = data.borderStyle.width; const circle = this.#circle = this.svgFactory.createElement("svg:ellipse"); circle.setAttribute("cx", width / 2); circle.setAttribute("cy", height / 2); circle.setAttribute("rx", width / 2 - borderWidth / 2); circle.setAttribute("ry", height / 2 - borderWidth / 2); circle.setAttribute("stroke-width", borderWidth || 1); circle.setAttribute("stroke", "transparent"); circle.setAttribute("fill", "transparent"); svg.append(circle); this.container.append(svg); if (!data.popupRef && this.hasPopupData) { this._createPopup(); } return this.container; } getElementsToTriggerPopup() { return this.#circle; } addHighlightArea() { this.container.classList.add("highlightArea"); } } class PolylineAnnotationElement extends AnnotationElement { #polyline = null; constructor(parameters) { super(parameters, { isRenderable: true, ignoreBorder: true }); this.containerClassName = "polylineAnnotation"; this.svgElementName = "svg:polyline"; } render() { this.container.classList.add(this.containerClassName); const data = this.data; const { width, height } = getRectDims(data.rect); const svg = this.svgFactory.create(width, height, true); let points = []; for (const coordinate of data.vertices) { const x = coordinate.x - data.rect[0]; const y = data.rect[3] - coordinate.y; points.push(x + "," + y); } points = points.join(" "); const polyline = this.#polyline = this.svgFactory.createElement(this.svgElementName); polyline.setAttribute("points", points); polyline.setAttribute("stroke-width", data.borderStyle.width || 1); polyline.setAttribute("stroke", "transparent"); polyline.setAttribute("fill", "transparent"); svg.append(polyline); this.container.append(svg); if (!data.popupRef && this.hasPopupData) { this._createPopup(); } return this.container; } getElementsToTriggerPopup() { return this.#polyline; } addHighlightArea() { this.container.classList.add("highlightArea"); } } class PolygonAnnotationElement extends PolylineAnnotationElement { constructor(parameters) { super(parameters); this.containerClassName = "polygonAnnotation"; this.svgElementName = "svg:polygon"; } } class CaretAnnotationElement extends AnnotationElement { constructor(parameters) { super(parameters, { isRenderable: true, ignoreBorder: true }); } render() { this.container.classList.add("caretAnnotation"); if (!this.data.popupRef && this.hasPopupData) { this._createPopup(); } return this.container; } } class InkAnnotationElement extends AnnotationElement { #polylines = []; constructor(parameters) { super(parameters, { isRenderable: true, ignoreBorder: true }); this.containerClassName = "inkAnnotation"; this.svgElementName = "svg:polyline"; this.annotationEditorType = AnnotationEditorType.INK; } render() { this.container.classList.add(this.containerClassName); const data = this.data; const { width, height } = getRectDims(data.rect); const svg = this.svgFactory.create(width, height, true); for (const inkList of data.inkLists) { let points = []; for (const coordinate of inkList) { const x = coordinate.x - data.rect[0]; const y = data.rect[3] - coordinate.y; points.push(`${x},${y}`); } points = points.join(" "); const polyline = this.svgFactory.createElement(this.svgElementName); this.#polylines.push(polyline); polyline.setAttribute("points", points); polyline.setAttribute("stroke-width", data.borderStyle.width || 1); polyline.setAttribute("stroke", "transparent"); polyline.setAttribute("fill", "transparent"); if (!data.popupRef && this.hasPopupData) { this._createPopup(); } svg.append(polyline); } this.container.append(svg); return this.container; } getElementsToTriggerPopup() { return this.#polylines; } addHighlightArea() { this.container.classList.add("highlightArea"); } } class HighlightAnnotationElement extends AnnotationElement { constructor(parameters) { super(parameters, { isRenderable: true, ignoreBorder: true, createQuadrilaterals: true }); } render() { if (!this.data.popupRef && this.hasPopupData) { this._createPopup(); } this.container.classList.add("highlightAnnotation"); return this.container; } } class UnderlineAnnotationElement extends AnnotationElement { constructor(parameters) { super(parameters, { isRenderable: true, ignoreBorder: true, createQuadrilaterals: true }); } render() { if (!this.data.popupRef && this.hasPopupData) { this._createPopup(); } this.container.classList.add("underlineAnnotation"); return this.container; } } class SquigglyAnnotationElement extends AnnotationElement { constructor(parameters) { super(parameters, { isRenderable: true, ignoreBorder: true, createQuadrilaterals: true }); } render() { if (!this.data.popupRef && this.hasPopupData) { this._createPopup(); } this.container.classList.add("squigglyAnnotation"); return this.container; } } class StrikeOutAnnotationElement extends AnnotationElement { constructor(parameters) { super(parameters, { isRenderable: true, ignoreBorder: true, createQuadrilaterals: true }); } render() { if (!this.data.popupRef && this.hasPopupData) { this._createPopup(); } this.container.classList.add("strikeoutAnnotation"); return this.container; } } class StampAnnotationElement extends AnnotationElement { constructor(parameters) { super(parameters, { isRenderable: true, ignoreBorder: true }); } render() { this.container.classList.add("stampAnnotation"); if (!this.data.popupRef && this.hasPopupData) { this._createPopup(); } return this.container; } } class FileAttachmentAnnotationElement extends AnnotationElement { #trigger = null; constructor(parameters) { super(parameters, { isRenderable: true }); const { filename, content, description } = this.data.file; this.filename = getFilenameFromUrl(filename, true); this.content = content; this.linkService.eventBus?.dispatch("fileattachmentannotation", { source: this, filename, content, description }); } render() { this.container.classList.add("fileAttachmentAnnotation"); const { container, data } = this; let trigger; if (data.hasAppearance || data.fillAlpha === 0) { trigger = document.createElement("div"); } else { trigger = document.createElement("img"); trigger.src = `${this.imageResourcesPath}annotation-${/paperclip/i.test(data.name) ? "paperclip" : "pushpin"}.svg`; if (data.fillAlpha && data.fillAlpha < 1) { trigger.style = `filter: opacity(${Math.round(data.fillAlpha * 100)}%);`; } } trigger.addEventListener("dblclick", this.#download.bind(this)); this.#trigger = trigger; const { isMac } = util_FeatureTest.platform; container.addEventListener("keydown", evt => { if (evt.key === "Enter" && (isMac ? evt.metaKey : evt.ctrlKey)) { this.#download(); } }); if (!data.popupRef && this.hasPopupData) { this._createPopup(); } else { trigger.classList.add("popupTriggerArea"); } container.append(trigger); return container; } getElementsToTriggerPopup() { return this.#trigger; } addHighlightArea() { this.container.classList.add("highlightArea"); } #download() { this.downloadManager?.openOrDownloadData(this.content, this.filename); } } class AnnotationLayer { #accessibilityManager = null; #annotationCanvasMap = null; #editableAnnotations = new Map(); constructor({ div, accessibilityManager, annotationCanvasMap, annotationEditorUIManager, page, viewport }) { this.div = div; this.#accessibilityManager = accessibilityManager; this.#annotationCanvasMap = annotationCanvasMap; this.page = page; this.viewport = viewport; this.zIndex = 0; this._annotationEditorUIManager = annotationEditorUIManager; } #appendElement(element, id) { const contentElement = element.firstChild || element; contentElement.id = `${AnnotationPrefix}${id}`; this.div.append(element); this.#accessibilityManager?.moveElementInDOM(this.div, element, contentElement, false); } async render(params) { const { annotations } = params; const layer = this.div; setLayerDimensions(layer, this.viewport); const popupToElements = new Map(); const elementParams = { data: null, layer, linkService: params.linkService, downloadManager: params.downloadManager, imageResourcesPath: params.imageResourcesPath || "", renderForms: params.renderForms !== false, svgFactory: new DOMSVGFactory(), annotationStorage: params.annotationStorage || new AnnotationStorage(), enableScripting: params.enableScripting === true, hasJSActions: params.hasJSActions, fieldObjects: params.fieldObjects, parent: this, elements: null }; for (const data of annotations) { if (data.noHTML) { continue; } const isPopupAnnotation = data.annotationType === AnnotationType.POPUP; if (!isPopupAnnotation) { const { width, height } = getRectDims(data.rect); if (width <= 0 || height <= 0) { continue; } } else { const elements = popupToElements.get(data.id); if (!elements) { continue; } elementParams.elements = elements; } elementParams.data = data; const element = AnnotationElementFactory.create(elementParams); if (!element.isRenderable) { continue; } if (!isPopupAnnotation && data.popupRef) { const elements = popupToElements.get(data.popupRef); if (!elements) { popupToElements.set(data.popupRef, [element]); } else { elements.push(element); } } const rendered = element.render(); if (data.hidden) { rendered.style.visibility = "hidden"; } this.#appendElement(rendered, data.id); if (element.annotationEditorType > 0) { this.#editableAnnotations.set(element.data.id, element); this._annotationEditorUIManager?.renderAnnotationElement(element); } } this.#setAnnotationCanvasMap(); } update({ viewport }) { const layer = this.div; this.viewport = viewport; setLayerDimensions(layer, { rotation: viewport.rotation }); this.#setAnnotationCanvasMap(); layer.hidden = false; } #setAnnotationCanvasMap() { if (!this.#annotationCanvasMap) { return; } const layer = this.div; for (const [id, canvas] of this.#annotationCanvasMap) { const element = layer.querySelector(`[data-annotation-id="${id}"]`); if (!element) { continue; } canvas.className = "annotationContent"; const { firstChild } = element; if (!firstChild) { element.append(canvas); } else if (firstChild.nodeName === "CANVAS") { firstChild.replaceWith(canvas); } else if (!firstChild.classList.contains("annotationContent")) { firstChild.before(canvas); } else { firstChild.after(canvas); } } this.#annotationCanvasMap.clear(); } getEditableAnnotations() { return Array.from(this.#editableAnnotations.values()); } getEditableAnnotation(id) { return this.#editableAnnotations.get(id); } } ;// CONCATENATED MODULE: ./src/display/editor/freetext.js const EOL_PATTERN = /\r\n?|\n/g; class FreeTextEditor extends AnnotationEditor { #boundEditorDivBlur = this.editorDivBlur.bind(this); #boundEditorDivFocus = this.editorDivFocus.bind(this); #boundEditorDivInput = this.editorDivInput.bind(this); #boundEditorDivKeydown = this.editorDivKeydown.bind(this); #boundEditorDivPaste = this.editorDivPaste.bind(this); #color; #content = ""; #editorDivId = `${this.id}-editor`; #fontSize; #initialData = null; static _freeTextDefaultContent = ""; static _internalPadding = 0; static _defaultColor = null; static _defaultFontSize = 10; static get _keyboardManager() { const proto = FreeTextEditor.prototype; const arrowChecker = self => self.isEmpty(); const small = AnnotationEditorUIManager.TRANSLATE_SMALL; const big = AnnotationEditorUIManager.TRANSLATE_BIG; return shadow(this, "_keyboardManager", new KeyboardManager([[["ctrl+s", "mac+meta+s", "ctrl+p", "mac+meta+p"], proto.commitOrRemove, { bubbles: true }], [["ctrl+Enter", "mac+meta+Enter", "Escape", "mac+Escape"], proto.commitOrRemove], [["ArrowLeft", "mac+ArrowLeft"], proto._translateEmpty, { args: [-small, 0], checker: arrowChecker }], [["ctrl+ArrowLeft", "mac+shift+ArrowLeft"], proto._translateEmpty, { args: [-big, 0], checker: arrowChecker }], [["ArrowRight", "mac+ArrowRight"], proto._translateEmpty, { args: [small, 0], checker: arrowChecker }], [["ctrl+ArrowRight", "mac+shift+ArrowRight"], proto._translateEmpty, { args: [big, 0], checker: arrowChecker }], [["ArrowUp", "mac+ArrowUp"], proto._translateEmpty, { args: [0, -small], checker: arrowChecker }], [["ctrl+ArrowUp", "mac+shift+ArrowUp"], proto._translateEmpty, { args: [0, -big], checker: arrowChecker }], [["ArrowDown", "mac+ArrowDown"], proto._translateEmpty, { args: [0, small], checker: arrowChecker }], [["ctrl+ArrowDown", "mac+shift+ArrowDown"], proto._translateEmpty, { args: [0, big], checker: arrowChecker }]])); } static _type = "freetext"; static _editorType = AnnotationEditorType.FREETEXT; constructor(params) { super({ ...params, name: "freeTextEditor" }); this.#color = params.color || FreeTextEditor._defaultColor || AnnotationEditor._defaultLineColor; this.#fontSize = params.fontSize || FreeTextEditor._defaultFontSize; } static initialize(l10n, uiManager) { AnnotationEditor.initialize(l10n, uiManager, { strings: ["pdfjs-free-text-default-content"] }); const style = getComputedStyle(document.documentElement); this._internalPadding = parseFloat(style.getPropertyValue("--freetext-padding")); } static updateDefaultParams(type, value) { switch (type) { case AnnotationEditorParamsType.FREETEXT_SIZE: FreeTextEditor._defaultFontSize = value; break; case AnnotationEditorParamsType.FREETEXT_COLOR: FreeTextEditor._defaultColor = value; break; } } updateParams(type, value) { switch (type) { case AnnotationEditorParamsType.FREETEXT_SIZE: this.#updateFontSize(value); break; case AnnotationEditorParamsType.FREETEXT_COLOR: this.#updateColor(value); break; } } static get defaultPropertiesToUpdate() { return [[AnnotationEditorParamsType.FREETEXT_SIZE, FreeTextEditor._defaultFontSize], [AnnotationEditorParamsType.FREETEXT_COLOR, FreeTextEditor._defaultColor || AnnotationEditor._defaultLineColor]]; } get propertiesToUpdate() { return [[AnnotationEditorParamsType.FREETEXT_SIZE, this.#fontSize], [AnnotationEditorParamsType.FREETEXT_COLOR, this.#color]]; } #updateFontSize(fontSize) { const setFontsize = size => { this.editorDiv.style.fontSize = `calc(${size}px * var(--scale-factor))`; this.translate(0, -(size - this.#fontSize) * this.parentScale); this.#fontSize = size; this.#setEditorDimensions(); }; const savedFontsize = this.#fontSize; this.addCommands({ cmd: setFontsize.bind(this, fontSize), undo: setFontsize.bind(this, savedFontsize), post: this._uiManager.updateUI.bind(this._uiManager, this), mustExec: true, type: AnnotationEditorParamsType.FREETEXT_SIZE, overwriteIfSameType: true, keepUndo: true }); } #updateColor(color) { const setColor = col => { this.#color = this.editorDiv.style.color = col; }; const savedColor = this.#color; this.addCommands({ cmd: setColor.bind(this, color), undo: setColor.bind(this, savedColor), post: this._uiManager.updateUI.bind(this._uiManager, this), mustExec: true, type: AnnotationEditorParamsType.FREETEXT_COLOR, overwriteIfSameType: true, keepUndo: true }); } _translateEmpty(x, y) { this._uiManager.translateSelectedEditors(x, y, true); } getInitialTranslation() { const scale = this.parentScale; return [-FreeTextEditor._internalPadding * scale, -(FreeTextEditor._internalPadding + this.#fontSize) * scale]; } rebuild() { if (!this.parent) { return; } super.rebuild(); if (this.div === null) { return; } if (!this.isAttachedToDOM) { this.parent.add(this); } } enableEditMode() { if (this.isInEditMode()) { return; } this.parent.setEditingState(false); this.parent.updateToolbar(AnnotationEditorType.FREETEXT); super.enableEditMode(); this.overlayDiv.classList.remove("enabled"); this.editorDiv.contentEditable = true; this._isDraggable = false; this.div.removeAttribute("aria-activedescendant"); this.editorDiv.addEventListener("keydown", this.#boundEditorDivKeydown); this.editorDiv.addEventListener("focus", this.#boundEditorDivFocus); this.editorDiv.addEventListener("blur", this.#boundEditorDivBlur); this.editorDiv.addEventListener("input", this.#boundEditorDivInput); this.editorDiv.addEventListener("paste", this.#boundEditorDivPaste); } disableEditMode() { if (!this.isInEditMode()) { return; } this.parent.setEditingState(true); super.disableEditMode(); this.overlayDiv.classList.add("enabled"); this.editorDiv.contentEditable = false; this.div.setAttribute("aria-activedescendant", this.#editorDivId); this._isDraggable = true; this.editorDiv.removeEventListener("keydown", this.#boundEditorDivKeydown); this.editorDiv.removeEventListener("focus", this.#boundEditorDivFocus); this.editorDiv.removeEventListener("blur", this.#boundEditorDivBlur); this.editorDiv.removeEventListener("input", this.#boundEditorDivInput); this.editorDiv.removeEventListener("paste", this.#boundEditorDivPaste); this.div.focus({ preventScroll: true }); this.isEditing = false; this.parent.div.classList.add("freetextEditing"); } focusin(event) { if (!this._focusEventsAllowed) { return; } super.focusin(event); if (event.target !== this.editorDiv) { this.editorDiv.focus(); } } onceAdded() { if (this.width) { return; } this.enableEditMode(); this.editorDiv.focus(); if (this._initialOptions?.isCentered) { this.center(); } this._initialOptions = null; } isEmpty() { return !this.editorDiv || this.editorDiv.innerText.trim() === ""; } remove() { this.isEditing = false; if (this.parent) { this.parent.setEditingState(true); this.parent.div.classList.add("freetextEditing"); } super.remove(); } #extractText() { const buffer = []; this.editorDiv.normalize(); for (const child of this.editorDiv.childNodes) { buffer.push(FreeTextEditor.#getNodeContent(child)); } return buffer.join("\n"); } #setEditorDimensions() { const [parentWidth, parentHeight] = this.parentDimensions; let rect; if (this.isAttachedToDOM) { rect = this.div.getBoundingClientRect(); } else { const { currentLayer, div } = this; const savedDisplay = div.style.display; const savedVisibility = div.classList.contains("hidden"); div.classList.remove("hidden"); div.style.display = "hidden"; currentLayer.div.append(this.div); rect = div.getBoundingClientRect(); div.remove(); div.style.display = savedDisplay; div.classList.toggle("hidden", savedVisibility); } if (this.rotation % 180 === this.parentRotation % 180) { this.width = rect.width / parentWidth; this.height = rect.height / parentHeight; } else { this.width = rect.height / parentWidth; this.height = rect.width / parentHeight; } this.fixAndSetPosition(); } commit() { if (!this.isInEditMode()) { return; } super.commit(); this.disableEditMode(); const savedText = this.#content; const newText = this.#content = this.#extractText().trimEnd(); if (savedText === newText) { return; } const setText = text => { this.#content = text; if (!text) { this.remove(); return; } this.#setContent(); this._uiManager.rebuild(this); this.#setEditorDimensions(); }; this.addCommands({ cmd: () => { setText(newText); }, undo: () => { setText(savedText); }, mustExec: false }); this.#setEditorDimensions(); } shouldGetKeyboardEvents() { return this.isInEditMode(); } enterInEditMode() { this.enableEditMode(); this.editorDiv.focus(); } dblclick(event) { this.enterInEditMode(); } keydown(event) { if (event.target === this.div && event.key === "Enter") { this.enterInEditMode(); event.preventDefault(); } } editorDivKeydown(event) { FreeTextEditor._keyboardManager.exec(this, event); } editorDivFocus(event) { this.isEditing = true; } editorDivBlur(event) { this.isEditing = false; } editorDivInput(event) { this.parent.div.classList.toggle("freetextEditing", this.isEmpty()); } disableEditing() { this.editorDiv.setAttribute("role", "comment"); this.editorDiv.removeAttribute("aria-multiline"); } enableEditing() { this.editorDiv.setAttribute("role", "textbox"); this.editorDiv.setAttribute("aria-multiline", true); } render() { if (this.div) { return this.div; } let baseX, baseY; if (this.width) { baseX = this.x; baseY = this.y; } super.render(); this.editorDiv = document.createElement("div"); this.editorDiv.className = "internal"; this.editorDiv.setAttribute("id", this.#editorDivId); this.editorDiv.setAttribute("data-l10n-id", "pdfjs-free-text"); this.enableEditing(); AnnotationEditor._l10nPromise.get("pdfjs-free-text-default-content").then(msg => this.editorDiv?.setAttribute("default-content", msg)); this.editorDiv.contentEditable = true; const { style } = this.editorDiv; style.fontSize = `calc(${this.#fontSize}px * var(--scale-factor))`; style.color = this.#color; this.div.append(this.editorDiv); this.overlayDiv = document.createElement("div"); this.overlayDiv.classList.add("overlay", "enabled"); this.div.append(this.overlayDiv); bindEvents(this, this.div, ["dblclick", "keydown"]); if (this.width) { const [parentWidth, parentHeight] = this.parentDimensions; if (this.annotationElementId) { const { position } = this.#initialData; let [tx, ty] = this.getInitialTranslation(); [tx, ty] = this.pageTranslationToScreen(tx, ty); const [pageWidth, pageHeight] = this.pageDimensions; const [pageX, pageY] = this.pageTranslation; let posX, posY; switch (this.rotation) { case 0: posX = baseX + (position[0] - pageX) / pageWidth; posY = baseY + this.height - (position[1] - pageY) / pageHeight; break; case 90: posX = baseX + (position[0] - pageX) / pageWidth; posY = baseY - (position[1] - pageY) / pageHeight; [tx, ty] = [ty, -tx]; break; case 180: posX = baseX - this.width + (position[0] - pageX) / pageWidth; posY = baseY - (position[1] - pageY) / pageHeight; [tx, ty] = [-tx, -ty]; break; case 270: posX = baseX + (position[0] - pageX - this.height * pageHeight) / pageWidth; posY = baseY + (position[1] - pageY - this.width * pageWidth) / pageHeight; [tx, ty] = [-ty, tx]; break; } this.setAt(posX * parentWidth, posY * parentHeight, tx, ty); } else { this.setAt(baseX * parentWidth, baseY * parentHeight, this.width * parentWidth, this.height * parentHeight); } this.#setContent(); this._isDraggable = true; this.editorDiv.contentEditable = false; } else { this._isDraggable = false; this.editorDiv.contentEditable = true; } return this.div; } static #getNodeContent(node) { return (node.nodeType === Node.TEXT_NODE ? node.nodeValue : node.innerText).replaceAll(EOL_PATTERN, ""); } editorDivPaste(event) { const clipboardData = event.clipboardData || window.clipboardData; const { types } = clipboardData; if (types.length === 1 && types[0] === "text/plain") { return; } event.preventDefault(); const paste = FreeTextEditor.#deserializeContent(clipboardData.getData("text") || "").replaceAll(EOL_PATTERN, "\n"); if (!paste) { return; } const selection = window.getSelection(); if (!selection.rangeCount) { return; } this.editorDiv.normalize(); selection.deleteFromDocument(); const range = selection.getRangeAt(0); if (!paste.includes("\n")) { range.insertNode(document.createTextNode(paste)); this.editorDiv.normalize(); selection.collapseToStart(); return; } const { startContainer, startOffset } = range; const bufferBefore = []; const bufferAfter = []; if (startContainer.nodeType === Node.TEXT_NODE) { const parent = startContainer.parentElement; bufferAfter.push(startContainer.nodeValue.slice(startOffset).replaceAll(EOL_PATTERN, "")); if (parent !== this.editorDiv) { let buffer = bufferBefore; for (const child of this.editorDiv.childNodes) { if (child === parent) { buffer = bufferAfter; continue; } buffer.push(FreeTextEditor.#getNodeContent(child)); } } bufferBefore.push(startContainer.nodeValue.slice(0, startOffset).replaceAll(EOL_PATTERN, "")); } else if (startContainer === this.editorDiv) { let buffer = bufferBefore; let i = 0; for (const child of this.editorDiv.childNodes) { if (i++ === startOffset) { buffer = bufferAfter; } buffer.push(FreeTextEditor.#getNodeContent(child)); } } this.#content = `${bufferBefore.join("\n")}${paste}${bufferAfter.join("\n")}`; this.#setContent(); const newRange = new Range(); let beforeLength = bufferBefore.reduce((acc, line) => acc + line.length, 0); for (const { firstChild } of this.editorDiv.childNodes) { if (firstChild.nodeType === Node.TEXT_NODE) { const length = firstChild.nodeValue.length; if (beforeLength <= length) { newRange.setStart(firstChild, beforeLength); newRange.setEnd(firstChild, beforeLength); break; } beforeLength -= length; } } selection.removeAllRanges(); selection.addRange(newRange); } #setContent() { this.editorDiv.replaceChildren(); if (!this.#content) { return; } for (const line of this.#content.split("\n")) { const div = document.createElement("div"); div.append(line ? document.createTextNode(line) : document.createElement("br")); this.editorDiv.append(div); } } #serializeContent() { return this.#content.replaceAll("\xa0", " "); } static #deserializeContent(content) { return content.replaceAll(" ", "\xa0"); } get contentDiv() { return this.editorDiv; } static deserialize(data, parent, uiManager) { let initialData = null; if (data instanceof FreeTextAnnotationElement) { const { data: { defaultAppearanceData: { fontSize, fontColor }, rect, rotation, id }, textContent, textPosition, parent: { page: { pageNumber } } } = data; if (!textContent || textContent.length === 0) { return null; } initialData = data = { annotationType: AnnotationEditorType.FREETEXT, color: Array.from(fontColor), fontSize, value: textContent.join("\n"), position: textPosition, pageIndex: pageNumber - 1, rect: rect.slice(0), rotation, id, deleted: false }; } const editor = super.deserialize(data, parent, uiManager); editor.#fontSize = data.fontSize; editor.#color = Util.makeHexColor(...data.color); editor.#content = FreeTextEditor.#deserializeContent(data.value); editor.annotationElementId = data.id || null; editor.#initialData = initialData; return editor; } serialize(isForCopying = false) { if (this.isEmpty()) { return null; } if (this.deleted) { return { pageIndex: this.pageIndex, id: this.annotationElementId, deleted: true }; } const padding = FreeTextEditor._internalPadding * this.parentScale; const rect = this.getRect(padding, padding); const color = AnnotationEditor._colorManager.convert(this.isAttachedToDOM ? getComputedStyle(this.editorDiv).color : this.#color); const serialized = { annotationType: AnnotationEditorType.FREETEXT, color, fontSize: this.#fontSize, value: this.#serializeContent(), pageIndex: this.pageIndex, rect, rotation: this.rotation, structTreeParentId: this._structTreeParentId }; if (isForCopying) { return serialized; } if (this.annotationElementId && !this.#hasElementChanged(serialized)) { return null; } serialized.id = this.annotationElementId; return serialized; } #hasElementChanged(serialized) { const { value, fontSize, color, pageIndex } = this.#initialData; return this._hasBeenMoved || serialized.value !== value || serialized.fontSize !== fontSize || serialized.color.some((c, i) => c !== color[i]) || serialized.pageIndex !== pageIndex; } renderAnnotationElement(annotation) { const content = super.renderAnnotationElement(annotation); if (this.deleted) { return content; } const { style } = content; style.fontSize = `calc(${this.#fontSize}px * var(--scale-factor))`; style.color = this.#color; content.replaceChildren(); for (const line of this.#content.split("\n")) { const div = document.createElement("div"); div.append(line ? document.createTextNode(line) : document.createElement("br")); content.append(div); } const padding = FreeTextEditor._internalPadding * this.parentScale; annotation.updateEdited({ rect: this.getRect(padding, padding) }); return content; } resetAnnotationElement(annotation) { super.resetAnnotationElement(annotation); annotation.resetEdited(); } } ;// CONCATENATED MODULE: ./src/display/editor/outliner.js class Outliner { #box; #verticalEdges = []; #intervals = []; constructor(boxes, borderWidth = 0, innerMargin = 0, isLTR = true) { let minX = Infinity; let maxX = -Infinity; let minY = Infinity; let maxY = -Infinity; const NUMBER_OF_DIGITS = 4; const EPSILON = 10 ** -NUMBER_OF_DIGITS; for (const { x, y, width, height } of boxes) { const x1 = Math.floor((x - borderWidth) / EPSILON) * EPSILON; const x2 = Math.ceil((x + width + borderWidth) / EPSILON) * EPSILON; const y1 = Math.floor((y - borderWidth) / EPSILON) * EPSILON; const y2 = Math.ceil((y + height + borderWidth) / EPSILON) * EPSILON; const left = [x1, y1, y2, true]; const right = [x2, y1, y2, false]; this.#verticalEdges.push(left, right); minX = Math.min(minX, x1); maxX = Math.max(maxX, x2); minY = Math.min(minY, y1); maxY = Math.max(maxY, y2); } const bboxWidth = maxX - minX + 2 * innerMargin; const bboxHeight = maxY - minY + 2 * innerMargin; const shiftedMinX = minX - innerMargin; const shiftedMinY = minY - innerMargin; const lastEdge = this.#verticalEdges.at(isLTR ? -1 : -2); const lastPoint = [lastEdge[0], lastEdge[2]]; for (const edge of this.#verticalEdges) { const [x, y1, y2] = edge; edge[0] = (x - shiftedMinX) / bboxWidth; edge[1] = (y1 - shiftedMinY) / bboxHeight; edge[2] = (y2 - shiftedMinY) / bboxHeight; } this.#box = { x: shiftedMinX, y: shiftedMinY, width: bboxWidth, height: bboxHeight, lastPoint }; } getOutlines() { this.#verticalEdges.sort((a, b) => a[0] - b[0] || a[1] - b[1] || a[2] - b[2]); const outlineVerticalEdges = []; for (const edge of this.#verticalEdges) { if (edge[3]) { outlineVerticalEdges.push(...this.#breakEdge(edge)); this.#insert(edge); } else { this.#remove(edge); outlineVerticalEdges.push(...this.#breakEdge(edge)); } } return this.#getOutlines(outlineVerticalEdges); } #getOutlines(outlineVerticalEdges) { const edges = []; const allEdges = new Set(); for (const edge of outlineVerticalEdges) { const [x, y1, y2] = edge; edges.push([x, y1, edge], [x, y2, edge]); } edges.sort((a, b) => a[1] - b[1] || a[0] - b[0]); for (let i = 0, ii = edges.length; i < ii; i += 2) { const edge1 = edges[i][2]; const edge2 = edges[i + 1][2]; edge1.push(edge2); edge2.push(edge1); allEdges.add(edge1); allEdges.add(edge2); } const outlines = []; let outline; while (allEdges.size > 0) { const edge = allEdges.values().next().value; let [x, y1, y2, edge1, edge2] = edge; allEdges.delete(edge); let lastPointX = x; let lastPointY = y1; outline = [x, y2]; outlines.push(outline); while (true) { let e; if (allEdges.has(edge1)) { e = edge1; } else if (allEdges.has(edge2)) { e = edge2; } else { break; } allEdges.delete(e); [x, y1, y2, edge1, edge2] = e; if (lastPointX !== x) { outline.push(lastPointX, lastPointY, x, lastPointY === y1 ? y1 : y2); lastPointX = x; } lastPointY = lastPointY === y1 ? y2 : y1; } outline.push(lastPointX, lastPointY); } return new HighlightOutline(outlines, this.#box); } #binarySearch(y) { const array = this.#intervals; let start = 0; let end = array.length - 1; while (start <= end) { const middle = start + end >> 1; const y1 = array[middle][0]; if (y1 === y) { return middle; } if (y1 < y) { start = middle + 1; } else { end = middle - 1; } } return end + 1; } #insert([, y1, y2]) { const index = this.#binarySearch(y1); this.#intervals.splice(index, 0, [y1, y2]); } #remove([, y1, y2]) { const index = this.#binarySearch(y1); for (let i = index; i < this.#intervals.length; i++) { const [start, end] = this.#intervals[i]; if (start !== y1) { break; } if (start === y1 && end === y2) { this.#intervals.splice(i, 1); return; } } for (let i = index - 1; i >= 0; i--) { const [start, end] = this.#intervals[i]; if (start !== y1) { break; } if (start === y1 && end === y2) { this.#intervals.splice(i, 1); return; } } } #breakEdge(edge) { const [x, y1, y2] = edge; const results = [[x, y1, y2]]; const index = this.#binarySearch(y2); for (let i = 0; i < index; i++) { const [start, end] = this.#intervals[i]; for (let j = 0, jj = results.length; j < jj; j++) { const [, y3, y4] = results[j]; if (end <= y3 || y4 <= start) { continue; } if (y3 >= start) { if (y4 > end) { results[j][1] = end; } else { if (jj === 1) { return []; } results.splice(j, 1); j--; jj--; } continue; } results[j][2] = start; if (y4 > end) { results.push([x, end, y4]); } } } return results; } } class Outline { toSVGPath() { throw new Error("Abstract method `toSVGPath` must be implemented."); } get box() { throw new Error("Abstract getter `box` must be implemented."); } serialize(_bbox, _rotation) { throw new Error("Abstract method `serialize` must be implemented."); } get free() { return this instanceof FreeHighlightOutline; } } class HighlightOutline extends Outline { #box; #outlines; constructor(outlines, box) { super(); this.#outlines = outlines; this.#box = box; } toSVGPath() { const buffer = []; for (const polygon of this.#outlines) { let [prevX, prevY] = polygon; buffer.push(`M${prevX} ${prevY}`); for (let i = 2; i < polygon.length; i += 2) { const x = polygon[i]; const y = polygon[i + 1]; if (x === prevX) { buffer.push(`V${y}`); prevY = y; } else if (y === prevY) { buffer.push(`H${x}`); prevX = x; } } buffer.push("Z"); } return buffer.join(" "); } serialize([blX, blY, trX, trY], _rotation) { const outlines = []; const width = trX - blX; const height = trY - blY; for (const outline of this.#outlines) { const points = new Array(outline.length); for (let i = 0; i < outline.length; i += 2) { points[i] = blX + outline[i] * width; points[i + 1] = trY - outline[i + 1] * height; } outlines.push(points); } return outlines; } get box() { return this.#box; } } class FreeOutliner { #box; #bottom = []; #innerMargin; #isLTR; #top = []; #last = new Float64Array(18); #lastX; #lastY; #min; #min_dist; #scaleFactor; #thickness; #points = []; static #MIN_DIST = 8; static #MIN_DIFF = 2; static #MIN = FreeOutliner.#MIN_DIST + FreeOutliner.#MIN_DIFF; constructor({ x, y }, box, scaleFactor, thickness, isLTR, innerMargin = 0) { this.#box = box; this.#thickness = thickness * scaleFactor; this.#isLTR = isLTR; this.#last.set([NaN, NaN, NaN, NaN, x, y], 6); this.#innerMargin = innerMargin; this.#min_dist = FreeOutliner.#MIN_DIST * scaleFactor; this.#min = FreeOutliner.#MIN * scaleFactor; this.#scaleFactor = scaleFactor; this.#points.push(x, y); } get free() { return true; } isEmpty() { return isNaN(this.#last[8]); } #getLastCoords() { const lastTop = this.#last.subarray(4, 6); const lastBottom = this.#last.subarray(16, 18); const [x, y, width, height] = this.#box; 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]; } add({ x, y }) { this.#lastX = x; this.#lastY = y; const [layerX, layerY, layerWidth, layerHeight] = this.#box; let [x1, y1, x2, y2] = this.#last.subarray(8, 12); const diffX = x - x2; const diffY = y - y2; const d = Math.hypot(diffX, diffY); if (d < this.#min) { return false; } const diffD = d - this.#min_dist; const K = diffD / d; const shiftX = K * diffX; const shiftY = K * diffY; let x0 = x1; let y0 = y1; x1 = x2; y1 = y2; x2 += shiftX; y2 += shiftY; this.#points?.push(x, y); const nX = -shiftY / diffD; const nY = shiftX / diffD; const thX = nX * this.#thickness; const thY = nY * this.#thickness; this.#last.set(this.#last.subarray(2, 8), 0); this.#last.set([x2 + thX, y2 + thY], 4); this.#last.set(this.#last.subarray(14, 18), 12); this.#last.set([x2 - thX, y2 - thY], 16); if (isNaN(this.#last[6])) { if (this.#top.length === 0) { this.#last.set([x1 + thX, y1 + thY], 2); this.#top.push(NaN, NaN, NaN, NaN, (x1 + thX - layerX) / layerWidth, (y1 + thY - layerY) / layerHeight); this.#last.set([x1 - thX, y1 - thY], 14); this.#bottom.push(NaN, NaN, NaN, NaN, (x1 - thX - layerX) / layerWidth, (y1 - thY - layerY) / layerHeight); } this.#last.set([x0, y0, x1, y1, x2, y2], 6); return !this.isEmpty(); } this.#last.set([x0, y0, x1, y1, x2, y2], 6); const angle = Math.abs(Math.atan2(y0 - y1, x0 - x1) - Math.atan2(shiftY, shiftX)); if (angle < Math.PI / 2) { [x1, y1, x2, y2] = this.#last.subarray(2, 6); this.#top.push(NaN, NaN, NaN, NaN, ((x1 + x2) / 2 - layerX) / layerWidth, ((y1 + y2) / 2 - layerY) / layerHeight); [x1, y1, x0, y0] = this.#last.subarray(14, 18); this.#bottom.push(NaN, NaN, NaN, NaN, ((x0 + x1) / 2 - layerX) / layerWidth, ((y0 + y1) / 2 - layerY) / layerHeight); return true; } [x0, y0, x1, y1, x2, y2] = this.#last.subarray(0, 6); 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); [x2, y2, x1, y1, x0, y0] = this.#last.subarray(12, 18); 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); return true; } toSVGPath() { if (this.isEmpty()) { return ""; } const top = this.#top; const bottom = this.#bottom; const lastTop = this.#last.subarray(4, 6); const lastBottom = this.#last.subarray(16, 18); const [x, y, width, height] = this.#box; const [lastTopX, lastTopY, lastBottomX, lastBottomY] = this.#getLastCoords(); if (isNaN(this.#last[6]) && !this.isEmpty()) { 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`; } const buffer = []; buffer.push(`M${top[4]} ${top[5]}`); for (let i = 6; i < top.length; i += 6) { if (isNaN(top[i])) { buffer.push(`L${top[i + 4]} ${top[i + 5]}`); } else { buffer.push(`C${top[i]} ${top[i + 1]} ${top[i + 2]} ${top[i + 3]} ${top[i + 4]} ${top[i + 5]}`); } } 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}`); for (let i = bottom.length - 6; i >= 6; i -= 6) { if (isNaN(bottom[i])) { buffer.push(`L${bottom[i + 4]} ${bottom[i + 5]}`); } else { buffer.push(`C${bottom[i]} ${bottom[i + 1]} ${bottom[i + 2]} ${bottom[i + 3]} ${bottom[i + 4]} ${bottom[i + 5]}`); } } buffer.push(`L${bottom[4]} ${bottom[5]} Z`); return buffer.join(" "); } getOutlines() { const top = this.#top; const bottom = this.#bottom; const last = this.#last; const lastTop = last.subarray(4, 6); const lastBottom = last.subarray(16, 18); const [layerX, layerY, layerWidth, layerHeight] = this.#box; const points = new Float64Array((this.#points?.length ?? 0) + 2); for (let i = 0, ii = points.length - 2; i < ii; i += 2) { points[i] = (this.#points[i] - layerX) / layerWidth; points[i + 1] = (this.#points[i + 1] - layerY) / layerHeight; } points[points.length - 2] = (this.#lastX - layerX) / layerWidth; points[points.length - 1] = (this.#lastY - layerY) / layerHeight; const [lastTopX, lastTopY, lastBottomX, lastBottomY] = this.#getLastCoords(); if (isNaN(last[6]) && !this.isEmpty()) { const outline = new Float64Array(36); 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); return new FreeHighlightOutline(outline, points, this.#box, this.#scaleFactor, this.#innerMargin, this.#isLTR); } const outline = new Float64Array(this.#top.length + 24 + this.#bottom.length); let N = top.length; for (let i = 0; i < N; i += 2) { if (isNaN(top[i])) { outline[i] = outline[i + 1] = NaN; continue; } outline[i] = top[i]; outline[i + 1] = top[i + 1]; } 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], N); N += 24; for (let i = bottom.length - 6; i >= 6; i -= 6) { for (let j = 0; j < 6; j += 2) { if (isNaN(bottom[i + j])) { outline[N] = outline[N + 1] = NaN; N += 2; continue; } outline[N] = bottom[i + j]; outline[N + 1] = bottom[i + j + 1]; N += 2; } } outline.set([NaN, NaN, NaN, NaN, bottom[4], bottom[5]], N); return new FreeHighlightOutline(outline, points, this.#box, this.#scaleFactor, this.#innerMargin, this.#isLTR); } } class FreeHighlightOutline extends Outline { #box; #bbox = null; #innerMargin; #isLTR; #points; #scaleFactor; #outline; constructor(outline, points, box, scaleFactor, innerMargin, isLTR) { super(); this.#outline = outline; this.#points = points; this.#box = box; this.#scaleFactor = scaleFactor; this.#innerMargin = innerMargin; this.#isLTR = isLTR; this.#computeMinMax(isLTR); const { x, y, width, height } = this.#bbox; for (let i = 0, ii = outline.length; i < ii; i += 2) { outline[i] = (outline[i] - x) / width; outline[i + 1] = (outline[i + 1] - y) / height; } for (let i = 0, ii = points.length; i < ii; i += 2) { points[i] = (points[i] - x) / width; points[i + 1] = (points[i + 1] - y) / height; } } toSVGPath() { const buffer = [`M${this.#outline[4]} ${this.#outline[5]}`]; for (let i = 6, ii = this.#outline.length; i < ii; i += 6) { if (isNaN(this.#outline[i])) { buffer.push(`L${this.#outline[i + 4]} ${this.#outline[i + 5]}`); continue; } 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]}`); } buffer.push("Z"); return buffer.join(" "); } serialize([blX, blY, trX, trY], rotation) { const width = trX - blX; const height = trY - blY; let outline; let points; switch (rotation) { case 0: outline = this.#rescale(this.#outline, blX, trY, width, -height); points = this.#rescale(this.#points, blX, trY, width, -height); break; case 90: outline = this.#rescaleAndSwap(this.#outline, blX, blY, width, height); points = this.#rescaleAndSwap(this.#points, blX, blY, width, height); break; case 180: outline = this.#rescale(this.#outline, trX, blY, -width, height); points = this.#rescale(this.#points, trX, blY, -width, height); break; case 270: outline = this.#rescaleAndSwap(this.#outline, trX, trY, -width, -height); points = this.#rescaleAndSwap(this.#points, trX, trY, -width, -height); break; } return { outline: Array.from(outline), points: [Array.from(points)] }; } #rescale(src, tx, ty, sx, sy) { const dest = new Float64Array(src.length); for (let i = 0, ii = src.length; i < ii; i += 2) { dest[i] = tx + src[i] * sx; dest[i + 1] = ty + src[i + 1] * sy; } return dest; } #rescaleAndSwap(src, tx, ty, sx, sy) { const dest = new Float64Array(src.length); for (let i = 0, ii = src.length; i < ii; i += 2) { dest[i] = tx + src[i + 1] * sx; dest[i + 1] = ty + src[i] * sy; } return dest; } #computeMinMax(isLTR) { const outline = this.#outline; let lastX = outline[4]; let lastY = outline[5]; let minX = lastX; let minY = lastY; let maxX = lastX; let maxY = lastY; let lastPointX = lastX; let lastPointY = lastY; const ltrCallback = isLTR ? Math.max : Math.min; for (let i = 6, ii = outline.length; i < ii; i += 6) { if (isNaN(outline[i])) { minX = Math.min(minX, outline[i + 4]); minY = Math.min(minY, outline[i + 5]); maxX = Math.max(maxX, outline[i + 4]); maxY = Math.max(maxY, outline[i + 5]); if (lastPointY < outline[i + 5]) { lastPointX = outline[i + 4]; lastPointY = outline[i + 5]; } else if (lastPointY === outline[i + 5]) { lastPointX = ltrCallback(lastPointX, outline[i + 4]); } } else { const bbox = Util.bezierBoundingBox(lastX, lastY, ...outline.slice(i, i + 6)); minX = Math.min(minX, bbox[0]); minY = Math.min(minY, bbox[1]); maxX = Math.max(maxX, bbox[2]); maxY = Math.max(maxY, bbox[3]); if (lastPointY < bbox[3]) { lastPointX = bbox[2]; lastPointY = bbox[3]; } else if (lastPointY === bbox[3]) { lastPointX = ltrCallback(lastPointX, bbox[2]); } } lastX = outline[i + 4]; lastY = outline[i + 5]; } const x = minX - this.#innerMargin, y = minY - this.#innerMargin, width = maxX - minX + 2 * this.#innerMargin, height = maxY - minY + 2 * this.#innerMargin; this.#bbox = { x, y, width, height, lastPoint: [lastPointX, lastPointY] }; } get box() { return this.#bbox; } getNewOutline(thickness, innerMargin) { const { x, y, width, height } = this.#bbox; const [layerX, layerY, layerWidth, layerHeight] = this.#box; const sx = width * layerWidth; const sy = height * layerHeight; const tx = x * layerWidth + layerX; const ty = y * layerHeight + layerY; const outliner = new FreeOutliner({ x: this.#points[0] * sx + tx, y: this.#points[1] * sy + ty }, this.#box, this.#scaleFactor, thickness, this.#isLTR, innerMargin ?? this.#innerMargin); for (let i = 2; i < this.#points.length; i += 2) { outliner.add({ x: this.#points[i] * sx + tx, y: this.#points[i + 1] * sy + ty }); } return outliner.getOutlines(); } } ;// CONCATENATED MODULE: ./src/display/editor/color_picker.js class ColorPicker { #boundKeyDown = this.#keyDown.bind(this); #boundPointerDown = this.#pointerDown.bind(this); #button = null; #buttonSwatch = null; #defaultColor; #dropdown = null; #dropdownWasFromKeyboard = false; #isMainColorPicker = false; #editor = null; #eventBus; #uiManager = null; #type; static get _keyboardManager() { 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]])); } constructor({ editor = null, uiManager = null }) { if (editor) { this.#isMainColorPicker = false; this.#type = AnnotationEditorParamsType.HIGHLIGHT_COLOR; this.#editor = editor; } else { this.#isMainColorPicker = true; this.#type = AnnotationEditorParamsType.HIGHLIGHT_DEFAULT_COLOR; } this.#uiManager = editor?._uiManager || uiManager; this.#eventBus = this.#uiManager._eventBus; this.#defaultColor = editor?.color || this.#uiManager?.highlightColors.values().next().value || "#FFFF98"; } renderButton() { const button = this.#button = document.createElement("button"); button.className = "colorPicker"; button.tabIndex = "0"; button.setAttribute("data-l10n-id", "pdfjs-editor-colorpicker-button"); button.setAttribute("aria-haspopup", true); button.addEventListener("click", this.#openDropdown.bind(this)); button.addEventListener("keydown", this.#boundKeyDown); const swatch = this.#buttonSwatch = document.createElement("span"); swatch.className = "swatch"; swatch.setAttribute("aria-hidden", true); swatch.style.backgroundColor = this.#defaultColor; button.append(swatch); return button; } renderMainDropdown() { const dropdown = this.#dropdown = this.#getDropdownRoot(); dropdown.setAttribute("aria-orientation", "horizontal"); dropdown.setAttribute("aria-labelledby", "highlightColorPickerLabel"); return dropdown; } #getDropdownRoot() { const div = document.createElement("div"); div.addEventListener("contextmenu", noContextMenu); div.className = "dropdown"; div.role = "listbox"; div.setAttribute("aria-multiselectable", false); div.setAttribute("aria-orientation", "vertical"); div.setAttribute("data-l10n-id", "pdfjs-editor-colorpicker-dropdown"); for (const [name, color] of this.#uiManager.highlightColors) { const button = document.createElement("button"); button.tabIndex = "0"; button.role = "option"; button.setAttribute("data-color", color); button.title = name; button.setAttribute("data-l10n-id", `pdfjs-editor-colorpicker-${name}`); const swatch = document.createElement("span"); button.append(swatch); swatch.className = "swatch"; swatch.style.backgroundColor = color; button.setAttribute("aria-selected", color === this.#defaultColor); button.addEventListener("click", this.#colorSelect.bind(this, color)); div.append(button); } div.addEventListener("keydown", this.#boundKeyDown); return div; } #colorSelect(color, event) { event.stopPropagation(); this.#eventBus.dispatch("switchannotationeditorparams", { source: this, type: this.#type, value: color }); } _colorSelectFromKeyboard(event) { if (event.target === this.#button) { this.#openDropdown(event); return; } const color = event.target.getAttribute("data-color"); if (!color) { return; } this.#colorSelect(color, event); } _moveToNext(event) { if (!this.#isDropdownVisible) { this.#openDropdown(event); return; } if (event.target === this.#button) { this.#dropdown.firstChild?.focus(); return; } event.target.nextSibling?.focus(); } _moveToPrevious(event) { if (event.target === this.#dropdown?.firstChild || event.target === this.#button) { if (this.#isDropdownVisible) { this._hideDropdownFromKeyboard(); } return; } if (!this.#isDropdownVisible) { this.#openDropdown(event); } event.target.previousSibling?.focus(); } _moveToBeginning(event) { if (!this.#isDropdownVisible) { this.#openDropdown(event); return; } this.#dropdown.firstChild?.focus(); } _moveToEnd(event) { if (!this.#isDropdownVisible) { this.#openDropdown(event); return; } this.#dropdown.lastChild?.focus(); } #keyDown(event) { ColorPicker._keyboardManager.exec(this, event); } #openDropdown(event) { if (this.#isDropdownVisible) { this.hideDropdown(); return; } this.#dropdownWasFromKeyboard = event.detail === 0; window.addEventListener("pointerdown", this.#boundPointerDown); if (this.#dropdown) { this.#dropdown.classList.remove("hidden"); return; } const root = this.#dropdown = this.#getDropdownRoot(); this.#button.append(root); } #pointerDown(event) { if (this.#dropdown?.contains(event.target)) { return; } this.hideDropdown(); } hideDropdown() { this.#dropdown?.classList.add("hidden"); window.removeEventListener("pointerdown", this.#boundPointerDown); } get #isDropdownVisible() { return this.#dropdown && !this.#dropdown.classList.contains("hidden"); } _hideDropdownFromKeyboard() { if (this.#isMainColorPicker) { return; } if (!this.#isDropdownVisible) { this.#editor?.unselect(); return; } this.hideDropdown(); this.#button.focus({ preventScroll: true, focusVisible: this.#dropdownWasFromKeyboard }); } updateColor(color) { if (this.#buttonSwatch) { this.#buttonSwatch.style.backgroundColor = color; } if (!this.#dropdown) { return; } const i = this.#uiManager.highlightColors.values(); for (const child of this.#dropdown.children) { child.setAttribute("aria-selected", i.next().value === color); } } destroy() { this.#button?.remove(); this.#button = null; this.#buttonSwatch = null; this.#dropdown?.remove(); this.#dropdown = null; } } ;// CONCATENATED MODULE: ./src/display/editor/highlight.js class HighlightEditor extends AnnotationEditor { #anchorNode = null; #anchorOffset = 0; #boxes; #clipPathId = null; #colorPicker = null; #focusOutlines = null; #focusNode = null; #focusOffset = 0; #highlightDiv = null; #highlightOutlines = null; #id = null; #isFreeHighlight = false; #boundKeydown = this.#keydown.bind(this); #lastPoint = null; #opacity; #outlineId = null; #text = ""; #thickness; #methodOfCreation = ""; static _defaultColor = null; static _defaultOpacity = 1; static _defaultThickness = 12; static _l10nPromise; static _type = "highlight"; static _editorType = AnnotationEditorType.HIGHLIGHT; static _freeHighlightId = -1; static _freeHighlight = null; static _freeHighlightClipId = ""; static get _keyboardManager() { const proto = HighlightEditor.prototype; return shadow(this, "_keyboardManager", new KeyboardManager([[["ArrowLeft", "mac+ArrowLeft"], proto._moveCaret, { args: [0] }], [["ArrowRight", "mac+ArrowRight"], proto._moveCaret, { args: [1] }], [["ArrowUp", "mac+ArrowUp"], proto._moveCaret, { args: [2] }], [["ArrowDown", "mac+ArrowDown"], proto._moveCaret, { args: [3] }]])); } constructor(params) { super({ ...params, name: "highlightEditor" }); this.color = params.color || HighlightEditor._defaultColor; this.#thickness = params.thickness || HighlightEditor._defaultThickness; this.#opacity = params.opacity || HighlightEditor._defaultOpacity; this.#boxes = params.boxes || null; this.#methodOfCreation = params.methodOfCreation || ""; this.#text = params.text || ""; this._isDraggable = false; if (params.highlightId > -1) { this.#isFreeHighlight = true; this.#createFreeOutlines(params); this.#addToDrawLayer(); } else { this.#anchorNode = params.anchorNode; this.#anchorOffset = params.anchorOffset; this.#focusNode = params.focusNode; this.#focusOffset = params.focusOffset; this.#createOutlines(); this.#addToDrawLayer(); this.rotate(this.rotation); } } get telemetryInitialData() { return { action: "added", type: this.#isFreeHighlight ? "free_highlight" : "highlight", color: this._uiManager.highlightColorNames.get(this.color), thickness: this.#thickness, methodOfCreation: this.#methodOfCreation }; } get telemetryFinalData() { return { type: "highlight", color: this._uiManager.highlightColorNames.get(this.color) }; } static computeTelemetryFinalData(data) { return { numberOfColors: data.get("color").size }; } #createOutlines() { const outliner = new Outliner(this.#boxes, 0.001); this.#highlightOutlines = outliner.getOutlines(); ({ x: this.x, y: this.y, width: this.width, height: this.height } = this.#highlightOutlines.box); const outlinerForOutline = new Outliner(this.#boxes, 0.0025, 0.001, this._uiManager.direction === "ltr"); this.#focusOutlines = outlinerForOutline.getOutlines(); const { lastPoint } = this.#focusOutlines.box; this.#lastPoint = [(lastPoint[0] - this.x) / this.width, (lastPoint[1] - this.y) / this.height]; } #createFreeOutlines({ highlightOutlines, highlightId, clipPathId }) { this.#highlightOutlines = highlightOutlines; const extraThickness = 1.5; this.#focusOutlines = highlightOutlines.getNewOutline(this.#thickness / 2 + extraThickness, 0.0025); if (highlightId >= 0) { this.#id = highlightId; this.#clipPathId = clipPathId; this.parent.drawLayer.finalizeLine(highlightId, highlightOutlines); this.#outlineId = this.parent.drawLayer.highlightOutline(this.#focusOutlines); } else if (this.parent) { const angle = this.parent.viewport.rotation; this.parent.drawLayer.updateLine(this.#id, highlightOutlines); this.parent.drawLayer.updateBox(this.#id, HighlightEditor.#rotateBbox(this.#highlightOutlines.box, (angle - this.rotation + 360) % 360)); this.parent.drawLayer.updateLine(this.#outlineId, this.#focusOutlines); this.parent.drawLayer.updateBox(this.#outlineId, HighlightEditor.#rotateBbox(this.#focusOutlines.box, angle)); } const { x, y, width, height } = highlightOutlines.box; switch (this.rotation) { case 0: this.x = x; this.y = y; this.width = width; this.height = height; break; case 90: { const [pageWidth, pageHeight] = this.parentDimensions; this.x = y; this.y = 1 - x; this.width = width * pageHeight / pageWidth; this.height = height * pageWidth / pageHeight; break; } case 180: this.x = 1 - x; this.y = 1 - y; this.width = width; this.height = height; break; case 270: { const [pageWidth, pageHeight] = this.parentDimensions; this.x = 1 - y; this.y = x; this.width = width * pageHeight / pageWidth; this.height = height * pageWidth / pageHeight; break; } } const { lastPoint } = this.#focusOutlines.box; this.#lastPoint = [(lastPoint[0] - x) / width, (lastPoint[1] - y) / height]; } static initialize(l10n, uiManager) { AnnotationEditor.initialize(l10n, uiManager); HighlightEditor._defaultColor ||= uiManager.highlightColors?.values().next().value || "#fff066"; } static updateDefaultParams(type, value) { switch (type) { case AnnotationEditorParamsType.HIGHLIGHT_DEFAULT_COLOR: HighlightEditor._defaultColor = value; break; case AnnotationEditorParamsType.HIGHLIGHT_THICKNESS: HighlightEditor._defaultThickness = value; break; } } translateInPage(x, y) {} get toolbarPosition() { return this.#lastPoint; } updateParams(type, value) { switch (type) { case AnnotationEditorParamsType.HIGHLIGHT_COLOR: this.#updateColor(value); break; case AnnotationEditorParamsType.HIGHLIGHT_THICKNESS: this.#updateThickness(value); break; } } static get defaultPropertiesToUpdate() { return [[AnnotationEditorParamsType.HIGHLIGHT_DEFAULT_COLOR, HighlightEditor._defaultColor], [AnnotationEditorParamsType.HIGHLIGHT_THICKNESS, HighlightEditor._defaultThickness]]; } get propertiesToUpdate() { return [[AnnotationEditorParamsType.HIGHLIGHT_COLOR, this.color || HighlightEditor._defaultColor], [AnnotationEditorParamsType.HIGHLIGHT_THICKNESS, this.#thickness || HighlightEditor._defaultThickness], [AnnotationEditorParamsType.HIGHLIGHT_FREE, this.#isFreeHighlight]]; } #updateColor(color) { const setColor = col => { this.color = col; this.parent?.drawLayer.changeColor(this.#id, col); this.#colorPicker?.updateColor(col); }; const savedColor = this.color; this.addCommands({ cmd: setColor.bind(this, color), undo: setColor.bind(this, savedColor), post: this._uiManager.updateUI.bind(this._uiManager, this), mustExec: true, type: AnnotationEditorParamsType.HIGHLIGHT_COLOR, overwriteIfSameType: true, keepUndo: true }); this._reportTelemetry({ action: "color_changed", color: this._uiManager.highlightColorNames.get(color) }, true); } #updateThickness(thickness) { const savedThickness = this.#thickness; const setThickness = th => { this.#thickness = th; this.#changeThickness(th); }; this.addCommands({ cmd: setThickness.bind(this, thickness), undo: setThickness.bind(this, savedThickness), post: this._uiManager.updateUI.bind(this._uiManager, this), mustExec: true, type: AnnotationEditorParamsType.INK_THICKNESS, overwriteIfSameType: true, keepUndo: true }); this._reportTelemetry({ action: "thickness_changed", thickness }, true); } async addEditToolbar() { const toolbar = await super.addEditToolbar(); if (!toolbar) { return null; } if (this._uiManager.highlightColors) { this.#colorPicker = new ColorPicker({ editor: this }); toolbar.addColorPicker(this.#colorPicker); } return toolbar; } disableEditing() { super.disableEditing(); this.div.classList.toggle("disabled", true); } enableEditing() { super.enableEditing(); this.div.classList.toggle("disabled", false); } fixAndSetPosition() { return super.fixAndSetPosition(this.#getRotation()); } getBaseTranslation() { return [0, 0]; } getRect(tx, ty) { return super.getRect(tx, ty, this.#getRotation()); } onceAdded() { this.parent.addUndoableEditor(this); this.div.focus(); } remove() { this.#cleanDrawLayer(); this._reportTelemetry({ action: "deleted" }); super.remove(); } rebuild() { if (!this.parent) { return; } super.rebuild(); if (this.div === null) { return; } this.#addToDrawLayer(); if (!this.isAttachedToDOM) { this.parent.add(this); } } setParent(parent) { let mustBeSelected = false; if (this.parent && !parent) { this.#cleanDrawLayer(); } else if (parent) { this.#addToDrawLayer(parent); mustBeSelected = !this.parent && this.div?.classList.contains("selectedEditor"); } super.setParent(parent); this.show(this._isVisible); if (mustBeSelected) { this.select(); } } #changeThickness(thickness) { if (!this.#isFreeHighlight) { return; } this.#createFreeOutlines({ highlightOutlines: this.#highlightOutlines.getNewOutline(thickness / 2) }); this.fixAndSetPosition(); const [parentWidth, parentHeight] = this.parentDimensions; this.setDims(this.width * parentWidth, this.height * parentHeight); } #cleanDrawLayer() { if (this.#id === null || !this.parent) { return; } this.parent.drawLayer.remove(this.#id); this.#id = null; this.parent.drawLayer.remove(this.#outlineId); this.#outlineId = null; } #addToDrawLayer(parent = this.parent) { if (this.#id !== null) { return; } ({ id: this.#id, clipPathId: this.#clipPathId } = parent.drawLayer.highlight(this.#highlightOutlines, this.color, this.#opacity)); this.#outlineId = parent.drawLayer.highlightOutline(this.#focusOutlines); if (this.#highlightDiv) { this.#highlightDiv.style.clipPath = this.#clipPathId; } } static #rotateBbox({ x, y, width, height }, angle) { switch (angle) { case 90: return { x: 1 - y - height, y: x, width: height, height: width }; case 180: return { x: 1 - x - width, y: 1 - y - height, width, height }; case 270: return { x: y, y: 1 - x - width, width: height, height: width }; } return { x, y, width, height }; } rotate(angle) { const { drawLayer } = this.parent; let box; if (this.#isFreeHighlight) { angle = (angle - this.rotation + 360) % 360; box = HighlightEditor.#rotateBbox(this.#highlightOutlines.box, angle); } else { box = HighlightEditor.#rotateBbox(this, angle); } drawLayer.rotate(this.#id, angle); drawLayer.rotate(this.#outlineId, angle); drawLayer.updateBox(this.#id, box); drawLayer.updateBox(this.#outlineId, HighlightEditor.#rotateBbox(this.#focusOutlines.box, angle)); } render() { if (this.div) { return this.div; } const div = super.render(); if (this.#text) { div.setAttribute("aria-label", this.#text); div.setAttribute("role", "mark"); } if (this.#isFreeHighlight) { div.classList.add("free"); } else { this.div.addEventListener("keydown", this.#boundKeydown); } const highlightDiv = this.#highlightDiv = document.createElement("div"); div.append(highlightDiv); highlightDiv.setAttribute("aria-hidden", "true"); highlightDiv.className = "internal"; highlightDiv.style.clipPath = this.#clipPathId; const [parentWidth, parentHeight] = this.parentDimensions; this.setDims(this.width * parentWidth, this.height * parentHeight); bindEvents(this, this.#highlightDiv, ["pointerover", "pointerleave"]); this.enableEditing(); return div; } pointerover() { this.parent.drawLayer.addClass(this.#outlineId, "hovered"); } pointerleave() { this.parent.drawLayer.removeClass(this.#outlineId, "hovered"); } #keydown(event) { HighlightEditor._keyboardManager.exec(this, event); } _moveCaret(direction) { this.parent.unselect(this); switch (direction) { case 0: case 2: this.#setCaret(true); break; case 1: case 3: this.#setCaret(false); break; } } #setCaret(start) { if (!this.#anchorNode) { return; } const selection = window.getSelection(); if (start) { selection.setPosition(this.#anchorNode, this.#anchorOffset); } else { selection.setPosition(this.#focusNode, this.#focusOffset); } } select() { super.select(); if (!this.#outlineId) { return; } this.parent?.drawLayer.removeClass(this.#outlineId, "hovered"); this.parent?.drawLayer.addClass(this.#outlineId, "selected"); } unselect() { super.unselect(); if (!this.#outlineId) { return; } this.parent?.drawLayer.removeClass(this.#outlineId, "selected"); if (!this.#isFreeHighlight) { this.#setCaret(false); } } get _mustFixPosition() { return !this.#isFreeHighlight; } show(visible = this._isVisible) { super.show(visible); if (this.parent) { this.parent.drawLayer.show(this.#id, visible); this.parent.drawLayer.show(this.#outlineId, visible); } } #getRotation() { return this.#isFreeHighlight ? this.rotation : 0; } #serializeBoxes() { if (this.#isFreeHighlight) { return null; } const [pageWidth, pageHeight] = this.pageDimensions; const boxes = this.#boxes; const quadPoints = new Array(boxes.length * 8); let i = 0; for (const { x, y, width, height } of boxes) { const sx = x * pageWidth; const sy = (1 - y - height) * pageHeight; quadPoints[i] = quadPoints[i + 4] = sx; quadPoints[i + 1] = quadPoints[i + 3] = sy; quadPoints[i + 2] = quadPoints[i + 6] = sx + width * pageWidth; quadPoints[i + 5] = quadPoints[i + 7] = sy + height * pageHeight; i += 8; } return quadPoints; } #serializeOutlines(rect) { return this.#highlightOutlines.serialize(rect, this.#getRotation()); } static startHighlighting(parent, isLTR, { target: textLayer, x, y }) { const { x: layerX, y: layerY, width: parentWidth, height: parentHeight } = textLayer.getBoundingClientRect(); const pointerMove = e => { this.#highlightMove(parent, e); }; const pointerDownOptions = { capture: true, passive: false }; const pointerDown = e => { e.preventDefault(); e.stopPropagation(); }; const pointerUpCallback = e => { textLayer.removeEventListener("pointermove", pointerMove); window.removeEventListener("blur", pointerUpCallback); window.removeEventListener("pointerup", pointerUpCallback); window.removeEventListener("pointerdown", pointerDown, pointerDownOptions); window.removeEventListener("contextmenu", noContextMenu); this.#endHighlight(parent, e); }; window.addEventListener("blur", pointerUpCallback); window.addEventListener("pointerup", pointerUpCallback); window.addEventListener("pointerdown", pointerDown, pointerDownOptions); window.addEventListener("contextmenu", noContextMenu); textLayer.addEventListener("pointermove", pointerMove); this._freeHighlight = new FreeOutliner({ x, y }, [layerX, layerY, parentWidth, parentHeight], parent.scale, this._defaultThickness / 2, isLTR, 0.001); ({ id: this._freeHighlightId, clipPathId: this._freeHighlightClipId } = parent.drawLayer.highlight(this._freeHighlight, this._defaultColor, this._defaultOpacity, true)); } static #highlightMove(parent, event) { if (this._freeHighlight.add(event)) { parent.drawLayer.updatePath(this._freeHighlightId, this._freeHighlight); } } static #endHighlight(parent, event) { if (!this._freeHighlight.isEmpty()) { parent.createAndAddNewEditor(event, false, { highlightId: this._freeHighlightId, highlightOutlines: this._freeHighlight.getOutlines(), clipPathId: this._freeHighlightClipId, methodOfCreation: "main_toolbar" }); } else { parent.drawLayer.removeFreeHighlight(this._freeHighlightId); } this._freeHighlightId = -1; this._freeHighlight = null; this._freeHighlightClipId = ""; } static deserialize(data, parent, uiManager) { const editor = super.deserialize(data, parent, uiManager); const { rect: [blX, blY, trX, trY], color, quadPoints } = data; editor.color = Util.makeHexColor(...color); editor.#opacity = data.opacity; const [pageWidth, pageHeight] = editor.pageDimensions; editor.width = (trX - blX) / pageWidth; editor.height = (trY - blY) / pageHeight; const boxes = editor.#boxes = []; for (let i = 0; i < quadPoints.length; i += 8) { boxes.push({ x: (quadPoints[4] - trX) / pageWidth, y: (trY - (1 - quadPoints[i + 5])) / pageHeight, width: (quadPoints[i + 2] - quadPoints[i]) / pageWidth, height: (quadPoints[i + 5] - quadPoints[i + 1]) / pageHeight }); } editor.#createOutlines(); return editor; } serialize(isForCopying = false) { if (this.isEmpty() || isForCopying) { return null; } const rect = this.getRect(0, 0); const color = AnnotationEditor._colorManager.convert(this.color); return { annotationType: AnnotationEditorType.HIGHLIGHT, color, opacity: this.#opacity, thickness: this.#thickness, quadPoints: this.#serializeBoxes(), outlines: this.#serializeOutlines(rect), pageIndex: this.pageIndex, rect, rotation: this.#getRotation(), structTreeParentId: this._structTreeParentId }; } static canCreateNewEmptyEditor() { return false; } } ;// CONCATENATED MODULE: ./src/display/editor/ink.js class InkEditor extends AnnotationEditor { #baseHeight = 0; #baseWidth = 0; #boundCanvasPointermove = this.canvasPointermove.bind(this); #boundCanvasPointerleave = this.canvasPointerleave.bind(this); #boundCanvasPointerup = this.canvasPointerup.bind(this); #boundCanvasPointerdown = this.canvasPointerdown.bind(this); #canvasContextMenuTimeoutId = null; #currentPath2D = new Path2D(); #disableEditing = false; #hasSomethingToDraw = false; #isCanvasInitialized = false; #observer = null; #realWidth = 0; #realHeight = 0; #requestFrameCallback = null; static _defaultColor = null; static _defaultOpacity = 1; static _defaultThickness = 1; static _type = "ink"; static _editorType = AnnotationEditorType.INK; constructor(params) { super({ ...params, name: "inkEditor" }); this.color = params.color || null; this.thickness = params.thickness || null; this.opacity = params.opacity || null; this.paths = []; this.bezierPath2D = []; this.allRawPaths = []; this.currentPath = []; this.scaleFactor = 1; this.translationX = this.translationY = 0; this.x = 0; this.y = 0; this._willKeepAspectRatio = true; } static initialize(l10n, uiManager) { AnnotationEditor.initialize(l10n, uiManager); } static updateDefaultParams(type, value) { switch (type) { case AnnotationEditorParamsType.INK_THICKNESS: InkEditor._defaultThickness = value; break; case AnnotationEditorParamsType.INK_COLOR: InkEditor._defaultColor = value; break; case AnnotationEditorParamsType.INK_OPACITY: InkEditor._defaultOpacity = value / 100; break; } } updateParams(type, value) { switch (type) { case AnnotationEditorParamsType.INK_THICKNESS: this.#updateThickness(value); break; case AnnotationEditorParamsType.INK_COLOR: this.#updateColor(value); break; case AnnotationEditorParamsType.INK_OPACITY: this.#updateOpacity(value); break; } } static get defaultPropertiesToUpdate() { return [[AnnotationEditorParamsType.INK_THICKNESS, InkEditor._defaultThickness], [AnnotationEditorParamsType.INK_COLOR, InkEditor._defaultColor || AnnotationEditor._defaultLineColor], [AnnotationEditorParamsType.INK_OPACITY, Math.round(InkEditor._defaultOpacity * 100)]]; } get propertiesToUpdate() { return [[AnnotationEditorParamsType.INK_THICKNESS, this.thickness || InkEditor._defaultThickness], [AnnotationEditorParamsType.INK_COLOR, this.color || InkEditor._defaultColor || AnnotationEditor._defaultLineColor], [AnnotationEditorParamsType.INK_OPACITY, Math.round(100 * (this.opacity ?? InkEditor._defaultOpacity))]]; } #updateThickness(thickness) { const setThickness = th => { this.thickness = th; this.#fitToContent(); }; const savedThickness = this.thickness; this.addCommands({ cmd: setThickness.bind(this, thickness), undo: setThickness.bind(this, savedThickness), post: this._uiManager.updateUI.bind(this._uiManager, this), mustExec: true, type: AnnotationEditorParamsType.INK_THICKNESS, overwriteIfSameType: true, keepUndo: true }); } #updateColor(color) { const setColor = col => { this.color = col; this.#redraw(); }; const savedColor = this.color; this.addCommands({ cmd: setColor.bind(this, color), undo: setColor.bind(this, savedColor), post: this._uiManager.updateUI.bind(this._uiManager, this), mustExec: true, type: AnnotationEditorParamsType.INK_COLOR, overwriteIfSameType: true, keepUndo: true }); } #updateOpacity(opacity) { const setOpacity = op => { this.opacity = op; this.#redraw(); }; opacity /= 100; const savedOpacity = this.opacity; this.addCommands({ cmd: setOpacity.bind(this, opacity), undo: setOpacity.bind(this, savedOpacity), post: this._uiManager.updateUI.bind(this._uiManager, this), mustExec: true, type: AnnotationEditorParamsType.INK_OPACITY, overwriteIfSameType: true, keepUndo: true }); } rebuild() { if (!this.parent) { return; } super.rebuild(); if (this.div === null) { return; } if (!this.canvas) { this.#createCanvas(); this.#createObserver(); } if (!this.isAttachedToDOM) { this.parent.add(this); this.#setCanvasDims(); } this.#fitToContent(); } remove() { if (this.canvas === null) { return; } if (!this.isEmpty()) { this.commit(); } this.canvas.width = this.canvas.height = 0; this.canvas.remove(); this.canvas = null; if (this.#canvasContextMenuTimeoutId) { clearTimeout(this.#canvasContextMenuTimeoutId); this.#canvasContextMenuTimeoutId = null; } this.#observer.disconnect(); this.#observer = null; super.remove(); } setParent(parent) { if (!this.parent && parent) { this._uiManager.removeShouldRescale(this); } else if (this.parent && parent === null) { this._uiManager.addShouldRescale(this); } super.setParent(parent); } onScaleChanging() { const [parentWidth, parentHeight] = this.parentDimensions; const width = this.width * parentWidth; const height = this.height * parentHeight; this.setDimensions(width, height); } enableEditMode() { if (this.#disableEditing || this.canvas === null) { return; } super.enableEditMode(); this._isDraggable = false; this.canvas.addEventListener("pointerdown", this.#boundCanvasPointerdown); } disableEditMode() { if (!this.isInEditMode() || this.canvas === null) { return; } super.disableEditMode(); this._isDraggable = !this.isEmpty(); this.div.classList.remove("editing"); this.canvas.removeEventListener("pointerdown", this.#boundCanvasPointerdown); } onceAdded() { this._isDraggable = !this.isEmpty(); } isEmpty() { return this.paths.length === 0 || this.paths.length === 1 && this.paths[0].length === 0; } #getInitialBBox() { const { parentRotation, parentDimensions: [width, height] } = this; switch (parentRotation) { case 90: return [0, height, height, width]; case 180: return [width, height, width, height]; case 270: return [width, 0, height, width]; default: return [0, 0, width, height]; } } #setStroke() { const { ctx, color, opacity, thickness, parentScale, scaleFactor } = this; ctx.lineWidth = thickness * parentScale / scaleFactor; ctx.lineCap = "round"; ctx.lineJoin = "round"; ctx.miterLimit = 10; ctx.strokeStyle = `${color}${opacityToHex(opacity)}`; } #startDrawing(x, y) { this.canvas.addEventListener("contextmenu", noContextMenu); this.canvas.addEventListener("pointerleave", this.#boundCanvasPointerleave); this.canvas.addEventListener("pointermove", this.#boundCanvasPointermove); this.canvas.addEventListener("pointerup", this.#boundCanvasPointerup); this.canvas.removeEventListener("pointerdown", this.#boundCanvasPointerdown); this.isEditing = true; if (!this.#isCanvasInitialized) { this.#isCanvasInitialized = true; this.#setCanvasDims(); this.thickness ||= InkEditor._defaultThickness; this.color ||= InkEditor._defaultColor || AnnotationEditor._defaultLineColor; this.opacity ??= InkEditor._defaultOpacity; } this.currentPath.push([x, y]); this.#hasSomethingToDraw = false; this.#setStroke(); this.#requestFrameCallback = () => { this.#drawPoints(); if (this.#requestFrameCallback) { window.requestAnimationFrame(this.#requestFrameCallback); } }; window.requestAnimationFrame(this.#requestFrameCallback); } #draw(x, y) { const [lastX, lastY] = this.currentPath.at(-1); if (this.currentPath.length > 1 && x === lastX && y === lastY) { return; } const currentPath = this.currentPath; let path2D = this.#currentPath2D; currentPath.push([x, y]); this.#hasSomethingToDraw = true; if (currentPath.length <= 2) { path2D.moveTo(...currentPath[0]); path2D.lineTo(x, y); return; } if (currentPath.length === 3) { this.#currentPath2D = path2D = new Path2D(); path2D.moveTo(...currentPath[0]); } this.#makeBezierCurve(path2D, ...currentPath.at(-3), ...currentPath.at(-2), x, y); } #endPath() { if (this.currentPath.length === 0) { return; } const lastPoint = this.currentPath.at(-1); this.#currentPath2D.lineTo(...lastPoint); } #stopDrawing(x, y) { this.#requestFrameCallback = null; x = Math.min(Math.max(x, 0), this.canvas.width); y = Math.min(Math.max(y, 0), this.canvas.height); this.#draw(x, y); this.#endPath(); let bezier; if (this.currentPath.length !== 1) { bezier = this.#generateBezierPoints(); } else { const xy = [x, y]; bezier = [[xy, xy.slice(), xy.slice(), xy]]; } const path2D = this.#currentPath2D; const currentPath = this.currentPath; this.currentPath = []; this.#currentPath2D = new Path2D(); const cmd = () => { this.allRawPaths.push(currentPath); this.paths.push(bezier); this.bezierPath2D.push(path2D); this._uiManager.rebuild(this); }; const undo = () => { this.allRawPaths.pop(); this.paths.pop(); this.bezierPath2D.pop(); if (this.paths.length === 0) { this.remove(); } else { if (!this.canvas) { this.#createCanvas(); this.#createObserver(); } this.#fitToContent(); } }; this.addCommands({ cmd, undo, mustExec: true }); } #drawPoints() { if (!this.#hasSomethingToDraw) { return; } this.#hasSomethingToDraw = false; const thickness = Math.ceil(this.thickness * this.parentScale); const lastPoints = this.currentPath.slice(-3); const x = lastPoints.map(xy => xy[0]); const y = lastPoints.map(xy => xy[1]); const xMin = Math.min(...x) - thickness; const xMax = Math.max(...x) + thickness; const yMin = Math.min(...y) - thickness; const yMax = Math.max(...y) + thickness; const { ctx } = this; ctx.save(); ctx.clearRect(xMin, yMin, xMax - xMin, yMax - yMin); ctx.beginPath(); ctx.rect(xMin, yMin, xMax - xMin, yMax - yMin); ctx.clip(); for (const path of this.bezierPath2D) { ctx.stroke(path); } ctx.stroke(this.#currentPath2D); ctx.restore(); } #makeBezierCurve(path2D, x0, y0, x1, y1, x2, y2) { const prevX = (x0 + x1) / 2; const prevY = (y0 + y1) / 2; const x3 = (x1 + x2) / 2; const y3 = (y1 + y2) / 2; path2D.bezierCurveTo(prevX + 2 * (x1 - prevX) / 3, prevY + 2 * (y1 - prevY) / 3, x3 + 2 * (x1 - x3) / 3, y3 + 2 * (y1 - y3) / 3, x3, y3); } #generateBezierPoints() { const path = this.currentPath; if (path.length <= 2) { return [[path[0], path[0], path.at(-1), path.at(-1)]]; } const bezierPoints = []; let i; let [x0, y0] = path[0]; for (i = 1; i < path.length - 2; i++) { const [x1, y1] = path[i]; const [x2, y2] = path[i + 1]; const x3 = (x1 + x2) / 2; const y3 = (y1 + y2) / 2; const control1 = [x0 + 2 * (x1 - x0) / 3, y0 + 2 * (y1 - y0) / 3]; const control2 = [x3 + 2 * (x1 - x3) / 3, y3 + 2 * (y1 - y3) / 3]; bezierPoints.push([[x0, y0], control1, control2, [x3, y3]]); [x0, y0] = [x3, y3]; } const [x1, y1] = path[i]; const [x2, y2] = path[i + 1]; const control1 = [x0 + 2 * (x1 - x0) / 3, y0 + 2 * (y1 - y0) / 3]; const control2 = [x2 + 2 * (x1 - x2) / 3, y2 + 2 * (y1 - y2) / 3]; bezierPoints.push([[x0, y0], control1, control2, [x2, y2]]); return bezierPoints; } #redraw() { if (this.isEmpty()) { this.#updateTransform(); return; } this.#setStroke(); const { canvas, ctx } = this; ctx.setTransform(1, 0, 0, 1, 0, 0); ctx.clearRect(0, 0, canvas.width, canvas.height); this.#updateTransform(); for (const path of this.bezierPath2D) { ctx.stroke(path); } } commit() { if (this.#disableEditing) { return; } super.commit(); this.isEditing = false; this.disableEditMode(); this.setInForeground(); this.#disableEditing = true; this.div.classList.add("disabled"); this.#fitToContent(true); this.select(); this.parent.addInkEditorIfNeeded(true); this.moveInDOM(); this.div.focus({ preventScroll: true }); } focusin(event) { if (!this._focusEventsAllowed) { return; } super.focusin(event); this.enableEditMode(); } canvasPointerdown(event) { if (event.button !== 0 || !this.isInEditMode() || this.#disableEditing) { return; } this.setInForeground(); event.preventDefault(); if (!this.div.contains(document.activeElement)) { this.div.focus({ preventScroll: true }); } this.#startDrawing(event.offsetX, event.offsetY); } canvasPointermove(event) { event.preventDefault(); this.#draw(event.offsetX, event.offsetY); } canvasPointerup(event) { event.preventDefault(); this.#endDrawing(event); } canvasPointerleave(event) { this.#endDrawing(event); } #endDrawing(event) { this.canvas.removeEventListener("pointerleave", this.#boundCanvasPointerleave); this.canvas.removeEventListener("pointermove", this.#boundCanvasPointermove); this.canvas.removeEventListener("pointerup", this.#boundCanvasPointerup); this.canvas.addEventListener("pointerdown", this.#boundCanvasPointerdown); if (this.#canvasContextMenuTimeoutId) { clearTimeout(this.#canvasContextMenuTimeoutId); } this.#canvasContextMenuTimeoutId = setTimeout(() => { this.#canvasContextMenuTimeoutId = null; this.canvas.removeEventListener("contextmenu", noContextMenu); }, 10); this.#stopDrawing(event.offsetX, event.offsetY); this.addToAnnotationStorage(); this.setInBackground(); } #createCanvas() { this.canvas = document.createElement("canvas"); this.canvas.width = this.canvas.height = 0; this.canvas.className = "inkEditorCanvas"; this.canvas.setAttribute("data-l10n-id", "pdfjs-ink-canvas"); this.div.append(this.canvas); this.ctx = this.canvas.getContext("2d"); } #createObserver() { this.#observer = new ResizeObserver(entries => { const rect = entries[0].contentRect; if (rect.width && rect.height) { this.setDimensions(rect.width, rect.height); } }); this.#observer.observe(this.div); } get isResizable() { return !this.isEmpty() && this.#disableEditing; } render() { if (this.div) { return this.div; } let baseX, baseY; if (this.width) { baseX = this.x; baseY = this.y; } super.render(); this.div.setAttribute("data-l10n-id", "pdfjs-ink"); const [x, y, w, h] = this.#getInitialBBox(); this.setAt(x, y, 0, 0); this.setDims(w, h); this.#createCanvas(); if (this.width) { const [parentWidth, parentHeight] = this.parentDimensions; this.setAspectRatio(this.width * parentWidth, this.height * parentHeight); this.setAt(baseX * parentWidth, baseY * parentHeight, this.width * parentWidth, this.height * parentHeight); this.#isCanvasInitialized = true; this.#setCanvasDims(); this.setDims(this.width * parentWidth, this.height * parentHeight); this.#redraw(); this.div.classList.add("disabled"); } else { this.div.classList.add("editing"); this.enableEditMode(); } this.#createObserver(); return this.div; } #setCanvasDims() { if (!this.#isCanvasInitialized) { return; } const [parentWidth, parentHeight] = this.parentDimensions; this.canvas.width = Math.ceil(this.width * parentWidth); this.canvas.height = Math.ceil(this.height * parentHeight); this.#updateTransform(); } setDimensions(width, height) { const roundedWidth = Math.round(width); const roundedHeight = Math.round(height); if (this.#realWidth === roundedWidth && this.#realHeight === roundedHeight) { return; } this.#realWidth = roundedWidth; this.#realHeight = roundedHeight; this.canvas.style.visibility = "hidden"; const [parentWidth, parentHeight] = this.parentDimensions; this.width = width / parentWidth; this.height = height / parentHeight; this.fixAndSetPosition(); if (this.#disableEditing) { this.#setScaleFactor(width, height); } this.#setCanvasDims(); this.#redraw(); this.canvas.style.visibility = "visible"; this.fixDims(); } #setScaleFactor(width, height) { const padding = this.#getPadding(); const scaleFactorW = (width - padding) / this.#baseWidth; const scaleFactorH = (height - padding) / this.#baseHeight; this.scaleFactor = Math.min(scaleFactorW, scaleFactorH); } #updateTransform() { const padding = this.#getPadding() / 2; this.ctx.setTransform(this.scaleFactor, 0, 0, this.scaleFactor, this.translationX * this.scaleFactor + padding, this.translationY * this.scaleFactor + padding); } static #buildPath2D(bezier) { const path2D = new Path2D(); for (let i = 0, ii = bezier.length; i < ii; i++) { const [first, control1, control2, second] = bezier[i]; if (i === 0) { path2D.moveTo(...first); } path2D.bezierCurveTo(control1[0], control1[1], control2[0], control2[1], second[0], second[1]); } return path2D; } static #toPDFCoordinates(points, rect, rotation) { const [blX, blY, trX, trY] = rect; switch (rotation) { case 0: for (let i = 0, ii = points.length; i < ii; i += 2) { points[i] += blX; points[i + 1] = trY - points[i + 1]; } break; case 90: for (let i = 0, ii = points.length; i < ii; i += 2) { const x = points[i]; points[i] = points[i + 1] + blX; points[i + 1] = x + blY; } break; case 180: for (let i = 0, ii = points.length; i < ii; i += 2) { points[i] = trX - points[i]; points[i + 1] += blY; } break; case 270: for (let i = 0, ii = points.length; i < ii; i += 2) { const x = points[i]; points[i] = trX - points[i + 1]; points[i + 1] = trY - x; } break; default: throw new Error("Invalid rotation"); } return points; } static #fromPDFCoordinates(points, rect, rotation) { const [blX, blY, trX, trY] = rect; switch (rotation) { case 0: for (let i = 0, ii = points.length; i < ii; i += 2) { points[i] -= blX; points[i + 1] = trY - points[i + 1]; } break; case 90: for (let i = 0, ii = points.length; i < ii; i += 2) { const x = points[i]; points[i] = points[i + 1] - blY; points[i + 1] = x - blX; } break; case 180: for (let i = 0, ii = points.length; i < ii; i += 2) { points[i] = trX - points[i]; points[i + 1] -= blY; } break; case 270: for (let i = 0, ii = points.length; i < ii; i += 2) { const x = points[i]; points[i] = trY - points[i + 1]; points[i + 1] = trX - x; } break; default: throw new Error("Invalid rotation"); } return points; } #serializePaths(s, tx, ty, rect) { const paths = []; const padding = this.thickness / 2; const shiftX = s * tx + padding; const shiftY = s * ty + padding; for (const bezier of this.paths) { const buffer = []; const points = []; for (let j = 0, jj = bezier.length; j < jj; j++) { const [first, control1, control2, second] = bezier[j]; if (first[0] === second[0] && first[1] === second[1] && jj === 1) { const p0 = s * first[0] + shiftX; const p1 = s * first[1] + shiftY; buffer.push(p0, p1); points.push(p0, p1); break; } const p10 = s * first[0] + shiftX; const p11 = s * first[1] + shiftY; const p20 = s * control1[0] + shiftX; const p21 = s * control1[1] + shiftY; const p30 = s * control2[0] + shiftX; const p31 = s * control2[1] + shiftY; const p40 = s * second[0] + shiftX; const p41 = s * second[1] + shiftY; if (j === 0) { buffer.push(p10, p11); points.push(p10, p11); } buffer.push(p20, p21, p30, p31, p40, p41); points.push(p20, p21); if (j === jj - 1) { points.push(p40, p41); } } paths.push({ bezier: InkEditor.#toPDFCoordinates(buffer, rect, this.rotation), points: InkEditor.#toPDFCoordinates(points, rect, this.rotation) }); } return paths; } #getBbox() { let xMin = Infinity; let xMax = -Infinity; let yMin = Infinity; let yMax = -Infinity; for (const path of this.paths) { for (const [first, control1, control2, second] of path) { const bbox = Util.bezierBoundingBox(...first, ...control1, ...control2, ...second); xMin = Math.min(xMin, bbox[0]); yMin = Math.min(yMin, bbox[1]); xMax = Math.max(xMax, bbox[2]); yMax = Math.max(yMax, bbox[3]); } } return [xMin, yMin, xMax, yMax]; } #getPadding() { return this.#disableEditing ? Math.ceil(this.thickness * this.parentScale) : 0; } #fitToContent(firstTime = false) { if (this.isEmpty()) { return; } if (!this.#disableEditing) { this.#redraw(); return; } const bbox = this.#getBbox(); const padding = this.#getPadding(); this.#baseWidth = Math.max(AnnotationEditor.MIN_SIZE, bbox[2] - bbox[0]); this.#baseHeight = Math.max(AnnotationEditor.MIN_SIZE, bbox[3] - bbox[1]); const width = Math.ceil(padding + this.#baseWidth * this.scaleFactor); const height = Math.ceil(padding + this.#baseHeight * this.scaleFactor); const [parentWidth, parentHeight] = this.parentDimensions; this.width = width / parentWidth; this.height = height / parentHeight; this.setAspectRatio(width, height); const prevTranslationX = this.translationX; const prevTranslationY = this.translationY; this.translationX = -bbox[0]; this.translationY = -bbox[1]; this.#setCanvasDims(); this.#redraw(); this.#realWidth = width; this.#realHeight = height; this.setDims(width, height); const unscaledPadding = firstTime ? padding / this.scaleFactor / 2 : 0; this.translate(prevTranslationX - this.translationX - unscaledPadding, prevTranslationY - this.translationY - unscaledPadding); } static deserialize(data, parent, uiManager) { if (data instanceof InkAnnotationElement) { return null; } const editor = super.deserialize(data, parent, uiManager); editor.thickness = data.thickness; editor.color = Util.makeHexColor(...data.color); editor.opacity = data.opacity; const [pageWidth, pageHeight] = editor.pageDimensions; const width = editor.width * pageWidth; const height = editor.height * pageHeight; const scaleFactor = editor.parentScale; const padding = data.thickness / 2; editor.#disableEditing = true; editor.#realWidth = Math.round(width); editor.#realHeight = Math.round(height); const { paths, rect, rotation } = data; for (let { bezier } of paths) { bezier = InkEditor.#fromPDFCoordinates(bezier, rect, rotation); const path = []; editor.paths.push(path); let p0 = scaleFactor * (bezier[0] - padding); let p1 = scaleFactor * (bezier[1] - padding); for (let i = 2, ii = bezier.length; i < ii; i += 6) { const p10 = scaleFactor * (bezier[i] - padding); const p11 = scaleFactor * (bezier[i + 1] - padding); const p20 = scaleFactor * (bezier[i + 2] - padding); const p21 = scaleFactor * (bezier[i + 3] - padding); const p30 = scaleFactor * (bezier[i + 4] - padding); const p31 = scaleFactor * (bezier[i + 5] - padding); path.push([[p0, p1], [p10, p11], [p20, p21], [p30, p31]]); p0 = p30; p1 = p31; } const path2D = this.#buildPath2D(path); editor.bezierPath2D.push(path2D); } const bbox = editor.#getBbox(); editor.#baseWidth = Math.max(AnnotationEditor.MIN_SIZE, bbox[2] - bbox[0]); editor.#baseHeight = Math.max(AnnotationEditor.MIN_SIZE, bbox[3] - bbox[1]); editor.#setScaleFactor(width, height); return editor; } serialize() { if (this.isEmpty()) { return null; } const rect = this.getRect(0, 0); const color = AnnotationEditor._colorManager.convert(this.ctx.strokeStyle); return { annotationType: AnnotationEditorType.INK, color, thickness: this.thickness, opacity: this.opacity, paths: this.#serializePaths(this.scaleFactor / this.parentScale, this.translationX, this.translationY, rect), pageIndex: this.pageIndex, rect, rotation: this.rotation, structTreeParentId: this._structTreeParentId }; } } ;// CONCATENATED MODULE: ./src/display/editor/stamp.js class StampEditor extends AnnotationEditor { #bitmap = null; #bitmapId = null; #bitmapPromise = null; #bitmapUrl = null; #bitmapFile = null; #bitmapFileName = ""; #canvas = null; #observer = null; #resizeTimeoutId = null; #isSvg = false; #hasBeenAddedInUndoStack = false; static _type = "stamp"; static _editorType = AnnotationEditorType.STAMP; constructor(params) { super({ ...params, name: "stampEditor" }); this.#bitmapUrl = params.bitmapUrl; this.#bitmapFile = params.bitmapFile; } static initialize(l10n, uiManager) { AnnotationEditor.initialize(l10n, uiManager); } static get supportedTypes() { const types = ["apng", "avif", "bmp", "gif", "jpeg", "png", "svg+xml", "webp", "x-icon"]; return shadow(this, "supportedTypes", types.map(type => `image/${type}`)); } static get supportedTypesStr() { return shadow(this, "supportedTypesStr", this.supportedTypes.join(",")); } static isHandlingMimeForPasting(mime) { return this.supportedTypes.includes(mime); } static paste(item, parent) { parent.pasteEditor(AnnotationEditorType.STAMP, { bitmapFile: item.getAsFile() }); } #getBitmapFetched(data, fromId = false) { if (!data) { this.remove(); return; } this.#bitmap = data.bitmap; if (!fromId) { this.#bitmapId = data.id; this.#isSvg = data.isSvg; } if (data.file) { this.#bitmapFileName = data.file.name; } this.#createCanvas(); } #getBitmapDone() { this.#bitmapPromise = null; this._uiManager.enableWaiting(false); if (this.#canvas) { this.div.focus(); } } #getBitmap() { if (this.#bitmapId) { this._uiManager.enableWaiting(true); this._uiManager.imageManager.getFromId(this.#bitmapId).then(data => this.#getBitmapFetched(data, true)).finally(() => this.#getBitmapDone()); return; } if (this.#bitmapUrl) { const url = this.#bitmapUrl; this.#bitmapUrl = null; this._uiManager.enableWaiting(true); this.#bitmapPromise = this._uiManager.imageManager.getFromUrl(url).then(data => this.#getBitmapFetched(data)).finally(() => this.#getBitmapDone()); return; } if (this.#bitmapFile) { const file = this.#bitmapFile; this.#bitmapFile = null; this._uiManager.enableWaiting(true); this.#bitmapPromise = this._uiManager.imageManager.getFromFile(file).then(data => this.#getBitmapFetched(data)).finally(() => this.#getBitmapDone()); return; } const input = document.createElement("input"); input.type = "file"; input.accept = StampEditor.supportedTypesStr; this.#bitmapPromise = new Promise(resolve => { input.addEventListener("change", async () => { if (!input.files || input.files.length === 0) { this.remove(); } else { this._uiManager.enableWaiting(true); const data = await this._uiManager.imageManager.getFromFile(input.files[0]); this.#getBitmapFetched(data); } resolve(); }); input.addEventListener("cancel", () => { this.remove(); resolve(); }); }).finally(() => this.#getBitmapDone()); input.click(); } remove() { if (this.#bitmapId) { this.#bitmap = null; this._uiManager.imageManager.deleteId(this.#bitmapId); this.#canvas?.remove(); this.#canvas = null; this.#observer?.disconnect(); this.#observer = null; if (this.#resizeTimeoutId) { clearTimeout(this.#resizeTimeoutId); this.#resizeTimeoutId = null; } } super.remove(); } rebuild() { if (!this.parent) { if (this.#bitmapId) { this.#getBitmap(); } return; } super.rebuild(); if (this.div === null) { return; } if (this.#bitmapId && this.#canvas === null) { this.#getBitmap(); } if (!this.isAttachedToDOM) { this.parent.add(this); } } onceAdded() { this._isDraggable = true; this.div.focus(); } isEmpty() { return !(this.#bitmapPromise || this.#bitmap || this.#bitmapUrl || this.#bitmapFile || this.#bitmapId); } get isResizable() { return true; } render() { if (this.div) { return this.div; } let baseX, baseY; if (this.width) { baseX = this.x; baseY = this.y; } super.render(); this.div.hidden = true; this.addAltTextButton(); if (this.#bitmap) { this.#createCanvas(); } else { this.#getBitmap(); } if (this.width) { const [parentWidth, parentHeight] = this.parentDimensions; this.setAt(baseX * parentWidth, baseY * parentHeight, this.width * parentWidth, this.height * parentHeight); } return this.div; } #createCanvas() { const { div } = this; let { width, height } = this.#bitmap; const [pageWidth, pageHeight] = this.pageDimensions; const MAX_RATIO = 0.75; if (this.width) { width = this.width * pageWidth; height = this.height * pageHeight; } else if (width > MAX_RATIO * pageWidth || height > MAX_RATIO * pageHeight) { const factor = Math.min(MAX_RATIO * pageWidth / width, MAX_RATIO * pageHeight / height); width *= factor; height *= factor; } const [parentWidth, parentHeight] = this.parentDimensions; this.setDims(width * parentWidth / pageWidth, height * parentHeight / pageHeight); this._uiManager.enableWaiting(false); const canvas = this.#canvas = document.createElement("canvas"); div.append(canvas); div.hidden = false; this.#drawBitmap(width, height); this.#createObserver(); if (!this.#hasBeenAddedInUndoStack) { this.parent.addUndoableEditor(this); this.#hasBeenAddedInUndoStack = true; } this._reportTelemetry({ action: "inserted_image" }); if (this.#bitmapFileName) { canvas.setAttribute("aria-label", this.#bitmapFileName); } } #setDimensions(width, height) { const [parentWidth, parentHeight] = this.parentDimensions; this.width = width / parentWidth; this.height = height / parentHeight; this.setDims(width, height); if (this._initialOptions?.isCentered) { this.center(); } else { this.fixAndSetPosition(); } this._initialOptions = null; if (this.#resizeTimeoutId !== null) { clearTimeout(this.#resizeTimeoutId); } const TIME_TO_WAIT = 200; this.#resizeTimeoutId = setTimeout(() => { this.#resizeTimeoutId = null; this.#drawBitmap(width, height); }, TIME_TO_WAIT); } #scaleBitmap(width, height) { const { width: bitmapWidth, height: bitmapHeight } = this.#bitmap; let newWidth = bitmapWidth; let newHeight = bitmapHeight; let bitmap = this.#bitmap; while (newWidth > 2 * width || newHeight > 2 * height) { const prevWidth = newWidth; const prevHeight = newHeight; if (newWidth > 2 * width) { newWidth = newWidth >= 16384 ? Math.floor(newWidth / 2) - 1 : Math.ceil(newWidth / 2); } if (newHeight > 2 * height) { newHeight = newHeight >= 16384 ? Math.floor(newHeight / 2) - 1 : Math.ceil(newHeight / 2); } const offscreen = new OffscreenCanvas(newWidth, newHeight); const ctx = offscreen.getContext("2d"); ctx.drawImage(bitmap, 0, 0, prevWidth, prevHeight, 0, 0, newWidth, newHeight); bitmap = offscreen.transferToImageBitmap(); } return bitmap; } #drawBitmap(width, height) { width = Math.ceil(width); height = Math.ceil(height); const canvas = this.#canvas; if (!canvas || canvas.width === width && canvas.height === height) { return; } canvas.width = width; canvas.height = height; const bitmap = this.#isSvg ? this.#bitmap : this.#scaleBitmap(width, height); if (this._uiManager.hasMLManager && !this.hasAltText()) { const offscreen = new OffscreenCanvas(width, height); const ctx = offscreen.getContext("2d"); ctx.drawImage(bitmap, 0, 0, bitmap.width, bitmap.height, 0, 0, width, height); offscreen.convertToBlob().then(blob => { const fileReader = new FileReader(); fileReader.onload = () => { const url = fileReader.result; this._uiManager.mlGuess({ service: "image-to-text", request: { imageData: url } }).then(response => { const altText = response?.output || ""; if (this.parent && altText && !this.hasAltText()) { this.altTextData = { altText, decorative: false }; } }); }; fileReader.readAsDataURL(blob); }); } const ctx = canvas.getContext("2d"); ctx.filter = this._uiManager.hcmFilter; ctx.drawImage(bitmap, 0, 0, bitmap.width, bitmap.height, 0, 0, width, height); } getImageForAltText() { return this.#canvas; } #serializeBitmap(toUrl) { if (toUrl) { if (this.#isSvg) { const url = this._uiManager.imageManager.getSvgUrl(this.#bitmapId); if (url) { return url; } } const canvas = document.createElement("canvas"); ({ width: canvas.width, height: canvas.height } = this.#bitmap); const ctx = canvas.getContext("2d"); ctx.drawImage(this.#bitmap, 0, 0); return canvas.toDataURL(); } if (this.#isSvg) { const [pageWidth, pageHeight] = this.pageDimensions; const width = Math.round(this.width * pageWidth * PixelsPerInch.PDF_TO_CSS_UNITS); const height = Math.round(this.height * pageHeight * PixelsPerInch.PDF_TO_CSS_UNITS); const offscreen = new OffscreenCanvas(width, height); const ctx = offscreen.getContext("2d"); ctx.drawImage(this.#bitmap, 0, 0, this.#bitmap.width, this.#bitmap.height, 0, 0, width, height); return offscreen.transferToImageBitmap(); } return structuredClone(this.#bitmap); } #createObserver() { this.#observer = new ResizeObserver(entries => { const rect = entries[0].contentRect; if (rect.width && rect.height) { this.#setDimensions(rect.width, rect.height); } }); this.#observer.observe(this.div); } static deserialize(data, parent, uiManager) { if (data instanceof StampAnnotationElement) { return null; } const editor = super.deserialize(data, parent, uiManager); const { rect, bitmapUrl, bitmapId, isSvg, accessibilityData } = data; if (bitmapId && uiManager.imageManager.isValidId(bitmapId)) { editor.#bitmapId = bitmapId; } else { editor.#bitmapUrl = bitmapUrl; } editor.#isSvg = isSvg; const [parentWidth, parentHeight] = editor.pageDimensions; editor.width = (rect[2] - rect[0]) / parentWidth; editor.height = (rect[3] - rect[1]) / parentHeight; if (accessibilityData) { editor.altTextData = accessibilityData; } return editor; } serialize(isForCopying = false, context = null) { if (this.isEmpty()) { return null; } const serialized = { annotationType: AnnotationEditorType.STAMP, bitmapId: this.#bitmapId, pageIndex: this.pageIndex, rect: this.getRect(0, 0), rotation: this.rotation, isSvg: this.#isSvg, structTreeParentId: this._structTreeParentId }; if (isForCopying) { serialized.bitmapUrl = this.#serializeBitmap(true); serialized.accessibilityData = this.altTextData; return serialized; } const { decorative, altText } = this.altTextData; if (!decorative && altText) { serialized.accessibilityData = { type: "Figure", alt: altText }; } if (context === null) { return serialized; } context.stamps ||= new Map(); const area = this.#isSvg ? (serialized.rect[2] - serialized.rect[0]) * (serialized.rect[3] - serialized.rect[1]) : null; if (!context.stamps.has(this.#bitmapId)) { context.stamps.set(this.#bitmapId, { area, serialized }); serialized.bitmap = this.#serializeBitmap(false); } else if (this.#isSvg) { const prevData = context.stamps.get(this.#bitmapId); if (area > prevData.area) { prevData.area = area; prevData.serialized.bitmap.close(); prevData.serialized.bitmap = this.#serializeBitmap(false); } } return serialized; } } ;// CONCATENATED MODULE: ./src/display/editor/annotation_editor_layer.js class AnnotationEditorLayer { #accessibilityManager; #allowClick = false; #annotationLayer = null; #boundPointerup = null; #boundPointerdown = null; #boundTextLayerPointerDown = null; #editorFocusTimeoutId = null; #editors = new Map(); #hadPointerDown = false; #isCleaningUp = false; #isDisabling = false; #textLayer = null; #uiManager; static _initialized = false; static #editorTypes = new Map([FreeTextEditor, InkEditor, StampEditor, HighlightEditor].map(type => [type._editorType, type])); constructor({ uiManager, pageIndex, div, accessibilityManager, annotationLayer, drawLayer, textLayer, viewport, l10n }) { const editorTypes = [...AnnotationEditorLayer.#editorTypes.values()]; if (!AnnotationEditorLayer._initialized) { AnnotationEditorLayer._initialized = true; for (const editorType of editorTypes) { editorType.initialize(l10n, uiManager); } } uiManager.registerEditorTypes(editorTypes); this.#uiManager = uiManager; this.pageIndex = pageIndex; this.div = div; this.#accessibilityManager = accessibilityManager; this.#annotationLayer = annotationLayer; this.viewport = viewport; this.#textLayer = textLayer; this.drawLayer = drawLayer; this.#uiManager.addLayer(this); } get isEmpty() { return this.#editors.size === 0; } get isInvisible() { return this.isEmpty && this.#uiManager.getMode() === AnnotationEditorType.NONE; } updateToolbar(mode) { this.#uiManager.updateToolbar(mode); } updateMode(mode = this.#uiManager.getMode()) { this.#cleanup(); switch (mode) { case AnnotationEditorType.NONE: this.disableTextSelection(); this.togglePointerEvents(false); this.toggleAnnotationLayerPointerEvents(true); this.disableClick(); return; case AnnotationEditorType.INK: this.addInkEditorIfNeeded(false); this.disableTextSelection(); this.togglePointerEvents(true); this.disableClick(); break; case AnnotationEditorType.HIGHLIGHT: this.enableTextSelection(); this.togglePointerEvents(false); this.disableClick(); break; default: this.disableTextSelection(); this.togglePointerEvents(true); this.enableClick(); } this.toggleAnnotationLayerPointerEvents(false); const { classList } = this.div; for (const editorType of AnnotationEditorLayer.#editorTypes.values()) { classList.toggle(`${editorType._type}Editing`, mode === editorType._editorType); } this.div.hidden = false; } hasTextLayer(textLayer) { return textLayer === this.#textLayer?.div; } addInkEditorIfNeeded(isCommitting) { if (this.#uiManager.getMode() !== AnnotationEditorType.INK) { return; } if (!isCommitting) { for (const editor of this.#editors.values()) { if (editor.isEmpty()) { editor.setInBackground(); return; } } } const editor = this.createAndAddNewEditor({ offsetX: 0, offsetY: 0 }, false); editor.setInBackground(); } setEditingState(isEditing) { this.#uiManager.setEditingState(isEditing); } addCommands(params) { this.#uiManager.addCommands(params); } togglePointerEvents(enabled = false) { this.div.classList.toggle("disabled", !enabled); } toggleAnnotationLayerPointerEvents(enabled = false) { this.#annotationLayer?.div.classList.toggle("disabled", !enabled); } enable() { this.div.tabIndex = 0; this.togglePointerEvents(true); const annotationElementIds = new Set(); for (const editor of this.#editors.values()) { editor.enableEditing(); editor.show(true); if (editor.annotationElementId) { this.#uiManager.removeChangedExistingAnnotation(editor); annotationElementIds.add(editor.annotationElementId); } } if (!this.#annotationLayer) { return; } const editables = this.#annotationLayer.getEditableAnnotations(); for (const editable of editables) { editable.hide(); if (this.#uiManager.isDeletedAnnotationElement(editable.data.id)) { continue; } if (annotationElementIds.has(editable.data.id)) { continue; } const editor = this.deserialize(editable); if (!editor) { continue; } this.addOrRebuild(editor); editor.enableEditing(); } } disable() { this.#isDisabling = true; this.div.tabIndex = -1; this.togglePointerEvents(false); const changedAnnotations = new Map(); const resetAnnotations = new Map(); for (const editor of this.#editors.values()) { editor.disableEditing(); if (!editor.annotationElementId) { continue; } if (editor.serialize() !== null) { changedAnnotations.set(editor.annotationElementId, editor); continue; } else { resetAnnotations.set(editor.annotationElementId, editor); } this.getEditableAnnotation(editor.annotationElementId)?.show(); editor.remove(); } if (this.#annotationLayer) { const editables = this.#annotationLayer.getEditableAnnotations(); for (const editable of editables) { const { id } = editable.data; if (this.#uiManager.isDeletedAnnotationElement(id)) { continue; } let editor = resetAnnotations.get(id); if (editor) { editor.resetAnnotationElement(editable); editor.show(false); editable.show(); continue; } editor = changedAnnotations.get(id); if (editor) { this.#uiManager.addChangedExistingAnnotation(editor); editor.renderAnnotationElement(editable); editor.show(false); } editable.show(); } } this.#cleanup(); if (this.isEmpty) { this.div.hidden = true; } const { classList } = this.div; for (const editorType of AnnotationEditorLayer.#editorTypes.values()) { classList.remove(`${editorType._type}Editing`); } this.disableTextSelection(); this.toggleAnnotationLayerPointerEvents(true); this.#isDisabling = false; } getEditableAnnotation(id) { return this.#annotationLayer?.getEditableAnnotation(id) || null; } setActiveEditor(editor) { const currentActive = this.#uiManager.getActive(); if (currentActive === editor) { return; } this.#uiManager.setActiveEditor(editor); } enableTextSelection() { this.div.tabIndex = -1; if (this.#textLayer?.div && !this.#boundTextLayerPointerDown) { this.#boundTextLayerPointerDown = this.#textLayerPointerDown.bind(this); this.#textLayer.div.addEventListener("pointerdown", this.#boundTextLayerPointerDown); this.#textLayer.div.classList.add("highlighting"); } } disableTextSelection() { this.div.tabIndex = 0; if (this.#textLayer?.div && this.#boundTextLayerPointerDown) { this.#textLayer.div.removeEventListener("pointerdown", this.#boundTextLayerPointerDown); this.#boundTextLayerPointerDown = null; this.#textLayer.div.classList.remove("highlighting"); } } #textLayerPointerDown(event) { this.#uiManager.unselectAll(); if (event.target === this.#textLayer.div) { const { isMac } = util_FeatureTest.platform; if (event.button !== 0 || event.ctrlKey && isMac) { return; } this.#uiManager.showAllEditors("highlight", true, true); this.#textLayer.div.classList.add("free"); HighlightEditor.startHighlighting(this, this.#uiManager.direction === "ltr", event); this.#textLayer.div.addEventListener("pointerup", () => { this.#textLayer.div.classList.remove("free"); }, { once: true }); event.preventDefault(); } } enableClick() { if (this.#boundPointerdown) { return; } this.#boundPointerdown = this.pointerdown.bind(this); this.#boundPointerup = this.pointerup.bind(this); this.div.addEventListener("pointerdown", this.#boundPointerdown); this.div.addEventListener("pointerup", this.#boundPointerup); } disableClick() { if (!this.#boundPointerdown) { return; } this.div.removeEventListener("pointerdown", this.#boundPointerdown); this.div.removeEventListener("pointerup", this.#boundPointerup); this.#boundPointerdown = null; this.#boundPointerup = null; } attach(editor) { this.#editors.set(editor.id, editor); const { annotationElementId } = editor; if (annotationElementId && this.#uiManager.isDeletedAnnotationElement(annotationElementId)) { this.#uiManager.removeDeletedAnnotationElement(editor); } } detach(editor) { this.#editors.delete(editor.id); this.#accessibilityManager?.removePointerInTextLayer(editor.contentDiv); if (!this.#isDisabling && editor.annotationElementId) { this.#uiManager.addDeletedAnnotationElement(editor); } } remove(editor) { this.detach(editor); this.#uiManager.removeEditor(editor); editor.div.remove(); editor.isAttachedToDOM = false; if (!this.#isCleaningUp) { this.addInkEditorIfNeeded(false); } } changeParent(editor) { if (editor.parent === this) { return; } if (editor.parent && editor.annotationElementId) { this.#uiManager.addDeletedAnnotationElement(editor.annotationElementId); AnnotationEditor.deleteAnnotationElement(editor); editor.annotationElementId = null; } this.attach(editor); editor.parent?.detach(editor); editor.setParent(this); if (editor.div && editor.isAttachedToDOM) { editor.div.remove(); this.div.append(editor.div); } } add(editor) { if (editor.parent === this && editor.isAttachedToDOM) { return; } this.changeParent(editor); this.#uiManager.addEditor(editor); this.attach(editor); if (!editor.isAttachedToDOM) { const div = editor.render(); this.div.append(div); editor.isAttachedToDOM = true; } editor.fixAndSetPosition(); editor.onceAdded(); this.#uiManager.addToAnnotationStorage(editor); editor._reportTelemetry(editor.telemetryInitialData); } moveEditorInDOM(editor) { if (!editor.isAttachedToDOM) { return; } const { activeElement } = document; if (editor.div.contains(activeElement) && !this.#editorFocusTimeoutId) { editor._focusEventsAllowed = false; this.#editorFocusTimeoutId = setTimeout(() => { this.#editorFocusTimeoutId = null; if (!editor.div.contains(document.activeElement)) { editor.div.addEventListener("focusin", () => { editor._focusEventsAllowed = true; }, { once: true }); activeElement.focus(); } else { editor._focusEventsAllowed = true; } }, 0); } editor._structTreeParentId = this.#accessibilityManager?.moveElementInDOM(this.div, editor.div, editor.contentDiv, true); } addOrRebuild(editor) { if (editor.needsToBeRebuilt()) { editor.parent ||= this; editor.rebuild(); editor.show(); } else { this.add(editor); } } addUndoableEditor(editor) { const cmd = () => editor._uiManager.rebuild(editor); const undo = () => { editor.remove(); }; this.addCommands({ cmd, undo, mustExec: false }); } getNextId() { return this.#uiManager.getId(); } get #currentEditorType() { return AnnotationEditorLayer.#editorTypes.get(this.#uiManager.getMode()); } #createNewEditor(params) { const editorType = this.#currentEditorType; return editorType ? new editorType.prototype.constructor(params) : null; } canCreateNewEmptyEditor() { return this.#currentEditorType?.canCreateNewEmptyEditor(); } pasteEditor(mode, params) { this.#uiManager.updateToolbar(mode); this.#uiManager.updateMode(mode); const { offsetX, offsetY } = this.#getCenterPoint(); const id = this.getNextId(); const editor = this.#createNewEditor({ parent: this, id, x: offsetX, y: offsetY, uiManager: this.#uiManager, isCentered: true, ...params }); if (editor) { this.add(editor); } } deserialize(data) { return AnnotationEditorLayer.#editorTypes.get(data.annotationType ?? data.annotationEditorType)?.deserialize(data, this, this.#uiManager) || null; } createAndAddNewEditor(event, isCentered, data = {}) { const id = this.getNextId(); const editor = this.#createNewEditor({ parent: this, id, x: event.offsetX, y: event.offsetY, uiManager: this.#uiManager, isCentered, ...data }); if (editor) { this.add(editor); } return editor; } #getCenterPoint() { const { x, y, width, height } = this.div.getBoundingClientRect(); const tlX = Math.max(0, x); const tlY = Math.max(0, y); const brX = Math.min(window.innerWidth, x + width); const brY = Math.min(window.innerHeight, y + height); const centerX = (tlX + brX) / 2 - x; const centerY = (tlY + brY) / 2 - y; const [offsetX, offsetY] = this.viewport.rotation % 180 === 0 ? [centerX, centerY] : [centerY, centerX]; return { offsetX, offsetY }; } addNewEditor() { this.createAndAddNewEditor(this.#getCenterPoint(), true); } setSelected(editor) { this.#uiManager.setSelected(editor); } toggleSelected(editor) { this.#uiManager.toggleSelected(editor); } isSelected(editor) { return this.#uiManager.isSelected(editor); } unselect(editor) { this.#uiManager.unselect(editor); } pointerup(event) { const { isMac } = util_FeatureTest.platform; if (event.button !== 0 || event.ctrlKey && isMac) { return; } if (event.target !== this.div) { return; } if (!this.#hadPointerDown) { return; } this.#hadPointerDown = false; if (!this.#allowClick) { this.#allowClick = true; return; } if (this.#uiManager.getMode() === AnnotationEditorType.STAMP) { this.#uiManager.unselectAll(); return; } this.createAndAddNewEditor(event, false); } pointerdown(event) { if (this.#uiManager.getMode() === AnnotationEditorType.HIGHLIGHT) { this.enableTextSelection(); } if (this.#hadPointerDown) { this.#hadPointerDown = false; return; } const { isMac } = util_FeatureTest.platform; if (event.button !== 0 || event.ctrlKey && isMac) { return; } if (event.target !== this.div) { return; } this.#hadPointerDown = true; const editor = this.#uiManager.getActive(); this.#allowClick = !editor || editor.isEmpty(); } findNewParent(editor, x, y) { const layer = this.#uiManager.findParent(x, y); if (layer === null || layer === this) { return false; } layer.changeParent(editor); return true; } destroy() { if (this.#uiManager.getActive()?.parent === this) { this.#uiManager.commitOrRemove(); this.#uiManager.setActiveEditor(null); } if (this.#editorFocusTimeoutId) { clearTimeout(this.#editorFocusTimeoutId); this.#editorFocusTimeoutId = null; } for (const editor of this.#editors.values()) { this.#accessibilityManager?.removePointerInTextLayer(editor.contentDiv); editor.setParent(null); editor.isAttachedToDOM = false; editor.div.remove(); } this.div = null; this.#editors.clear(); this.#uiManager.removeLayer(this); } #cleanup() { this.#isCleaningUp = true; for (const editor of this.#editors.values()) { if (editor.isEmpty()) { editor.remove(); } } this.#isCleaningUp = false; } render({ viewport }) { this.viewport = viewport; setLayerDimensions(this.div, viewport); for (const editor of this.#uiManager.getEditors(this.pageIndex)) { this.add(editor); editor.rebuild(); } this.updateMode(); } update({ viewport }) { this.#uiManager.commitOrRemove(); this.#cleanup(); const oldRotation = this.viewport.rotation; const rotation = viewport.rotation; this.viewport = viewport; setLayerDimensions(this.div, { rotation }); if (oldRotation !== rotation) { for (const editor of this.#editors.values()) { editor.rotate(rotation); } } this.addInkEditorIfNeeded(false); } get pageDimensions() { const { pageWidth, pageHeight } = this.viewport.rawDims; return [pageWidth, pageHeight]; } get scale() { return this.#uiManager.viewParameters.realScale; } } ;// CONCATENATED MODULE: ./src/display/draw_layer.js class DrawLayer { #parent = null; #id = 0; #mapping = new Map(); #toUpdate = new Map(); constructor({ pageIndex }) { this.pageIndex = pageIndex; } setParent(parent) { if (!this.#parent) { this.#parent = parent; return; } if (this.#parent !== parent) { if (this.#mapping.size > 0) { for (const root of this.#mapping.values()) { root.remove(); parent.append(root); } } this.#parent = parent; } } static get _svgFactory() { return shadow(this, "_svgFactory", new DOMSVGFactory()); } static #setBox(element, { x = 0, y = 0, width = 1, height = 1 } = {}) { const { style } = element; style.top = `${100 * y}%`; style.left = `${100 * x}%`; style.width = `${100 * width}%`; style.height = `${100 * height}%`; } #createSVG(box) { const svg = DrawLayer._svgFactory.create(1, 1, true); this.#parent.append(svg); svg.setAttribute("aria-hidden", true); DrawLayer.#setBox(svg, box); return svg; } #createClipPath(defs, pathId) { const clipPath = DrawLayer._svgFactory.createElement("clipPath"); defs.append(clipPath); const clipPathId = `clip_${pathId}`; clipPath.setAttribute("id", clipPathId); clipPath.setAttribute("clipPathUnits", "objectBoundingBox"); const clipPathUse = DrawLayer._svgFactory.createElement("use"); clipPath.append(clipPathUse); clipPathUse.setAttribute("href", `#${pathId}`); clipPathUse.classList.add("clip"); return clipPathId; } highlight(outlines, color, opacity, isPathUpdatable = false) { const id = this.#id++; const root = this.#createSVG(outlines.box); root.classList.add("highlight"); if (outlines.free) { root.classList.add("free"); } const defs = DrawLayer._svgFactory.createElement("defs"); root.append(defs); const path = DrawLayer._svgFactory.createElement("path"); defs.append(path); const pathId = `path_p${this.pageIndex}_${id}`; path.setAttribute("id", pathId); path.setAttribute("d", outlines.toSVGPath()); if (isPathUpdatable) { this.#toUpdate.set(id, path); } const clipPathId = this.#createClipPath(defs, pathId); const use = DrawLayer._svgFactory.createElement("use"); root.append(use); root.setAttribute("fill", color); root.setAttribute("fill-opacity", opacity); use.setAttribute("href", `#${pathId}`); this.#mapping.set(id, root); return { id, clipPathId: `url(#${clipPathId})` }; } highlightOutline(outlines) { const id = this.#id++; const root = this.#createSVG(outlines.box); root.classList.add("highlightOutline"); const defs = DrawLayer._svgFactory.createElement("defs"); root.append(defs); const path = DrawLayer._svgFactory.createElement("path"); defs.append(path); const pathId = `path_p${this.pageIndex}_${id}`; path.setAttribute("id", pathId); path.setAttribute("d", outlines.toSVGPath()); path.setAttribute("vector-effect", "non-scaling-stroke"); let maskId; if (outlines.free) { root.classList.add("free"); const mask = DrawLayer._svgFactory.createElement("mask"); defs.append(mask); maskId = `mask_p${this.pageIndex}_${id}`; mask.setAttribute("id", maskId); mask.setAttribute("maskUnits", "objectBoundingBox"); const rect = DrawLayer._svgFactory.createElement("rect"); mask.append(rect); rect.setAttribute("width", "1"); rect.setAttribute("height", "1"); rect.setAttribute("fill", "white"); const use = DrawLayer._svgFactory.createElement("use"); mask.append(use); use.setAttribute("href", `#${pathId}`); use.setAttribute("stroke", "none"); use.setAttribute("fill", "black"); use.setAttribute("fill-rule", "nonzero"); use.classList.add("mask"); } const use1 = DrawLayer._svgFactory.createElement("use"); root.append(use1); use1.setAttribute("href", `#${pathId}`); if (maskId) { use1.setAttribute("mask", `url(#${maskId})`); } const use2 = use1.cloneNode(); root.append(use2); use1.classList.add("mainOutline"); use2.classList.add("secondaryOutline"); this.#mapping.set(id, root); return id; } finalizeLine(id, line) { const path = this.#toUpdate.get(id); this.#toUpdate.delete(id); this.updateBox(id, line.box); path.setAttribute("d", line.toSVGPath()); } updateLine(id, line) { const root = this.#mapping.get(id); const defs = root.firstChild; const path = defs.firstChild; path.setAttribute("d", line.toSVGPath()); } removeFreeHighlight(id) { this.remove(id); this.#toUpdate.delete(id); } updatePath(id, line) { this.#toUpdate.get(id).setAttribute("d", line.toSVGPath()); } updateBox(id, box) { DrawLayer.#setBox(this.#mapping.get(id), box); } show(id, visible) { this.#mapping.get(id).classList.toggle("hidden", !visible); } rotate(id, angle) { this.#mapping.get(id).setAttribute("data-main-rotation", angle); } changeColor(id, color) { this.#mapping.get(id).setAttribute("fill", color); } changeOpacity(id, opacity) { this.#mapping.get(id).setAttribute("fill-opacity", opacity); } addClass(id, className) { this.#mapping.get(id).classList.add(className); } removeClass(id, className) { this.#mapping.get(id).classList.remove(className); } remove(id) { if (this.#parent === null) { return; } this.#mapping.get(id).remove(); this.#mapping.delete(id); } destroy() { this.#parent = null; for (const root of this.#mapping.values()) { root.remove(); } this.#mapping.clear(); } } ;// CONCATENATED MODULE: ./src/pdf.js const pdfjsVersion = "4.3.8"; const pdfjsBuild = "c419c8333"; var __webpack_exports__AbortException = __webpack_exports__.AbortException; var __webpack_exports__AnnotationEditorLayer = __webpack_exports__.AnnotationEditorLayer; var __webpack_exports__AnnotationEditorParamsType = __webpack_exports__.AnnotationEditorParamsType; var __webpack_exports__AnnotationEditorType = __webpack_exports__.AnnotationEditorType; var __webpack_exports__AnnotationEditorUIManager = __webpack_exports__.AnnotationEditorUIManager; var __webpack_exports__AnnotationLayer = __webpack_exports__.AnnotationLayer; var __webpack_exports__AnnotationMode = __webpack_exports__.AnnotationMode; var __webpack_exports__CMapCompressionType = __webpack_exports__.CMapCompressionType; var __webpack_exports__ColorPicker = __webpack_exports__.ColorPicker; var __webpack_exports__DOMSVGFactory = __webpack_exports__.DOMSVGFactory; var __webpack_exports__DrawLayer = __webpack_exports__.DrawLayer; var __webpack_exports__FeatureTest = __webpack_exports__.FeatureTest; var __webpack_exports__GlobalWorkerOptions = __webpack_exports__.GlobalWorkerOptions; var __webpack_exports__ImageKind = __webpack_exports__.ImageKind; var __webpack_exports__InvalidPDFException = __webpack_exports__.InvalidPDFException; var __webpack_exports__MissingPDFException = __webpack_exports__.MissingPDFException; var __webpack_exports__OPS = __webpack_exports__.OPS; var __webpack_exports__Outliner = __webpack_exports__.Outliner; var __webpack_exports__PDFDataRangeTransport = __webpack_exports__.PDFDataRangeTransport; var __webpack_exports__PDFDateString = __webpack_exports__.PDFDateString; var __webpack_exports__PDFWorker = __webpack_exports__.PDFWorker; var __webpack_exports__PasswordResponses = __webpack_exports__.PasswordResponses; var __webpack_exports__PermissionFlag = __webpack_exports__.PermissionFlag; var __webpack_exports__PixelsPerInch = __webpack_exports__.PixelsPerInch; var __webpack_exports__RenderingCancelledException = __webpack_exports__.RenderingCancelledException; var __webpack_exports__UnexpectedResponseException = __webpack_exports__.UnexpectedResponseException; var __webpack_exports__Util = __webpack_exports__.Util; var __webpack_exports__VerbosityLevel = __webpack_exports__.VerbosityLevel; var __webpack_exports__XfaLayer = __webpack_exports__.XfaLayer; var __webpack_exports__build = __webpack_exports__.build; var __webpack_exports__createValidAbsoluteUrl = __webpack_exports__.createValidAbsoluteUrl; var __webpack_exports__fetchData = __webpack_exports__.fetchData; var __webpack_exports__getDocument = __webpack_exports__.getDocument; var __webpack_exports__getFilenameFromUrl = __webpack_exports__.getFilenameFromUrl; var __webpack_exports__getPdfFilenameFromUrl = __webpack_exports__.getPdfFilenameFromUrl; var __webpack_exports__getXfaPageViewport = __webpack_exports__.getXfaPageViewport; var __webpack_exports__isDataScheme = __webpack_exports__.isDataScheme; var __webpack_exports__isPdfFile = __webpack_exports__.isPdfFile; var __webpack_exports__noContextMenu = __webpack_exports__.noContextMenu; var __webpack_exports__normalizeUnicode = __webpack_exports__.normalizeUnicode; var __webpack_exports__renderTextLayer = __webpack_exports__.renderTextLayer; var __webpack_exports__setLayerDimensions = __webpack_exports__.setLayerDimensions; var __webpack_exports__shadow = __webpack_exports__.shadow; var __webpack_exports__updateTextLayer = __webpack_exports__.updateTextLayer; var __webpack_exports__version = __webpack_exports__.version; export { __webpack_exports__AbortException as AbortException, __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__CMapCompressionType as CMapCompressionType, __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__MissingPDFException as MissingPDFException, __webpack_exports__OPS as OPS, __webpack_exports__Outliner as Outliner, __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__UnexpectedResponseException as UnexpectedResponseException, __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__renderTextLayer as renderTextLayer, __webpack_exports__setLayerDimensions as setLayerDimensions, __webpack_exports__shadow as shadow, __webpack_exports__updateTextLayer as updateTextLayer, __webpack_exports__version as version };