1/** 2 * @licstart The following is the entire license notice for the 3 * JavaScript code in this page 4 * 5 * Copyright 2024 Mozilla Foundation 6 * 7 * Licensed under the Apache License, Version 2.0 (the "License"); 8 * you may not use this file except in compliance with the License. 9 * You may obtain a copy of the License at 10 * 11 * http://www.apache.org/licenses/LICENSE-2.0 12 * 13 * Unless required by applicable law or agreed to in writing, software 14 * distributed under the License is distributed on an "AS IS" BASIS, 15 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 16 * See the License for the specific language governing permissions and 17 * limitations under the License. 18 * 19 * @licend The above is the entire license notice for the 20 * JavaScript code in this page 21 */ 22 23/** 24 * pdfjsVersion = 6.1.200 25 * pdfjsBuild = 6353acefe 26 */ 27 28;// ./src/shared/util.js 29const isNodeJS = typeof process === "object" && process + "" === "[object process]" && !process.versions.nw && !(process.versions.electron && process.type && process.type !== "browser"); 30const BBOX_INIT = [Infinity, Infinity, -Infinity, -Infinity]; 31const F32_BBOX_INIT = new Float32Array(BBOX_INIT); 32const FONT_IDENTITY_MATRIX = [0.001, 0, 0, 0.001, 0, 0]; 33const LINE_FACTOR = 1.35; 34const LINE_DESCENT_FACTOR = 0.35; 35const BASELINE_FACTOR = LINE_DESCENT_FACTOR / LINE_FACTOR
vendor: 9,864 bytes, lines 35-483
35; 36const SVG_NS = "http://www.w3.org/2000/svg"; 37const RenderingIntentFlag = { 38 ANY: 0x01, 39 DISPLAY: 0x02, 40 PRINT: 0x04, 41 SAVE: 0x08, 42 ANNOTATIONS_FORMS: 0x10, 43 ANNOTATIONS_STORAGE: 0x20, 44 ANNOTATIONS_DISABLE: 0x40, 45 IS_EDITING: 0x80, 46 OPLIST: 0x100 47}; 48const AnnotationMode = { 49 DISABLE: 0, 50 ENABLE: 1, 51 ENABLE_FORMS: 2, 52 ENABLE_STORAGE: 3 53}; 54const AnnotationPrefix = "pdfjs_internal_id_"; 55const AnnotationEditorPrefix = "pdfjs_internal_editor_"; 56const AnnotationEditorType = { 57 DISABLE: -1, 58 NONE: 0, 59 FREETEXT: 3, 60 HIGHLIGHT: 9, 61 STAMP: 13, 62 INK: 15, 63 POPUP: 16, 64 SIGNATURE: 101, 65 COMMENT: 102 66}; 67const AnnotationEditorParamsType = { 68 RESIZE: 1, 69 CREATE: 2, 70 FREETEXT_SIZE: 11, 71 FREETEXT_COLOR: 12, 72 FREETEXT_OPACITY: 13, 73 INK_COLOR: 21, 74 INK_THICKNESS: 22, 75 INK_OPACITY: 23, 76 INK_COLOR_AND_OPACITY: 24, 77 HIGHLIGHT_COLOR: 31, 78 HIGHLIGHT_THICKNESS: 32, 79 HIGHLIGHT_FREE: 33, 80 HIGHLIGHT_SHOW_ALL: 34, 81 DRAW_STEP: 41 82}; 83const PermissionFlag = { 84 PRINT: 0x04, 85 MODIFY_CONTENTS: 0x08, 86 COPY: 0x10, 87 MODIFY_ANNOTATIONS: 0x20, 88 FILL_INTERACTIVE_FORMS: 0x100, 89 COPY_FOR_ACCESSIBILITY: 0x200, 90 ASSEMBLE: 0x400, 91 PRINT_HIGH_QUALITY: 0x800 92}; 93const MeshFigureType = (/* unused pure expression or super */ null && ({ 94 TRIANGLES: 1, 95 LATTICE: 2, 96 PATCH: 3 97})); 98const TextRenderingMode = { 99 FILL: 0, 100 STROKE: 1, 101 FILL_STROKE: 2, 102 INVISIBLE: 3, 103 FILL_ADD_TO_PATH: 4, 104 STROKE_ADD_TO_PATH: 5, 105 FILL_STROKE_ADD_TO_PATH: 6, 106 ADD_TO_PATH: 7, 107 FILL_STROKE_MASK: 3, 108 ADD_TO_PATH_FLAG: 4 109}; 110const ImageKind = { 111 GRAYSCALE_1BPP: 1, 112 RGB_24BPP: 2, 113 RGBA_32BPP: 3 114}; 115const AnnotationType = { 116 TEXT: 1, 117 LINK: 2, 118 FREETEXT: 3, 119 LINE: 4, 120 SQUARE: 5, 121 CIRCLE: 6, 122 POLYGON: 7, 123 POLYLINE: 8, 124 HIGHLIGHT: 9, 125 UNDERLINE: 10, 126 SQUIGGLY: 11, 127 STRIKEOUT: 12, 128 STAMP: 13, 129 CARET: 14, 130 INK: 15, 131 POPUP: 16, 132 FILEATTACHMENT: 17, 133 SOUND: 18, 134 MOVIE: 19, 135 WIDGET: 20, 136 SCREEN: 21, 137 PRINTERMARK: 22, 138 TRAPNET: 23, 139 WATERMARK: 24, 140 THREED: 25, 141 REDACT: 26, 142 RICHMEDIA: 27 143}; 144const AnnotationReplyType = (/* unused pure expression or super */ null && ({ 145 GROUP: "Group", 146 REPLY: "R" 147})); 148const AnnotationRenditionOperation = (/* unused pure expression or super */ null && ({ 149 PLAY_OR_RESUME: 0, 150 STOP: 1, 151 PAUSE: 2, 152 RESUME: 3, 153 PLAY: 4 154})); 155const AnnotationFlag = (/* unused pure expression or super */ null && ({ 156 INVISIBLE: 0x01, 157 HIDDEN: 0x02, 158 PRINT: 0x04, 159 NOZOOM: 0x08, 160 NOROTATE: 0x10, 161 NOVIEW: 0x20, 162 READONLY: 0x40, 163 LOCKED: 0x80, 164 TOGGLENOVIEW: 0x100, 165 LOCKEDCONTENTS: 0x200 166})); 167const AnnotationFieldFlag = (/* unused pure expression or super */ null && ({ 168 READONLY: 0x0000001, 169 REQUIRED: 0x0000002, 170 NOEXPORT: 0x0000004, 171 MULTILINE: 0x0001000, 172 PASSWORD: 0x0002000, 173 NOTOGGLETOOFF: 0x0004000, 174 RADIO: 0x0008000, 175 PUSHBUTTON: 0x0010000, 176 COMBO: 0x0020000, 177 EDIT: 0x0040000, 178 SORT: 0x0080000, 179 FILESELECT: 0x0100000, 180 MULTISELECT: 0x0200000, 181 DONOTSPELLCHECK: 0x0400000, 182 DONOTSCROLL: 0x0800000, 183 COMB: 0x1000000, 184 RICHTEXT: 0x2000000, 185 RADIOSINUNISON: 0x2000000, 186 COMMITONSELCHANGE: 0x4000000 187})); 188const AnnotationBorderStyleType = { 189 SOLID: 1, 190 DASHED: 2, 191 BEVELED: 3, 192 INSET: 4, 193 UNDERLINE: 5 194}; 195const AnnotationActionEventType = (/* unused pure expression or super */ null && ({ 196 E: "Mouse Enter", 197 X: "Mouse Exit", 198 D: "Mouse Down", 199 U: "Mouse Up", 200 Fo: "Focus", 201 Bl: "Blur", 202 PO: "PageOpen", 203 PC: "PageClose", 204 PV: "PageVisible", 205 PI: "PageInvisible", 206 K: "Keystroke", 207 F: "Format", 208 V: "Validate", 209 C: "Calculate" 210})); 211const DocumentActionEventType = (/* unused pure expression or super */ null && ({ 212 WC: "WillClose", 213 WS: "WillSave", 214 DS: "DidSave", 215 WP: "WillPrint", 216 DP: "DidPrint" 217})); 218const PageActionEventType = (/* unused pure expression or super */ null && ({ 219 O: "PageOpen", 220 C: "PageClose" 221})); 222const VerbosityLevel = { 223 ERRORS: 0, 224 WARNINGS: 1, 225 INFOS: 5 226}; 227const OPS = { 228 dependency: 1, 229 setLineWidth: 2, 230 setLineCap: 3, 231 setLineJoin: 4, 232 setMiterLimit: 5, 233 setDash: 6, 234 setRenderingIntent: 7, 235 setFlatness: 8, 236 setGState: 9, 237 save: 10, 238 restore: 11, 239 transform: 12, 240 moveTo: 13, 241 lineTo: 14, 242 curveTo: 15, 243 curveTo2: 16, 244 curveTo3: 17, 245 closePath: 18, 246 rectangle: 19, 247 stroke: 20, 248 closeStroke: 21, 249 fill: 22, 250 eoFill: 23, 251 fillStroke: 24, 252 eoFillStroke: 25, 253 closeFillStroke: 26, 254 closeEOFillStroke: 27, 255 endPath: 28, 256 clip: 29, 257 eoClip: 30, 258 beginText: 31, 259 endText: 32, 260 setCharSpacing: 33, 261 setWordSpacing: 34, 262 setHScale: 35, 263 setLeading: 36, 264 setFont: 37, 265 setTextRenderingMode: 38, 266 setTextRise: 39, 267 moveText: 40, 268 setLeadingMoveText: 41, 269 setTextMatrix: 42, 270 nextLine: 43, 271 showText: 44, 272 showSpacedText: 45, 273 nextLineShowText: 46, 274 nextLineSetSpacingShowText: 47, 275 setCharWidth: 48, 276 setCharWidthAndBounds: 49, 277 setStrokeColorSpace: 50, 278 setFillColorSpace: 51, 279 setStrokeColor: 52, 280 setStrokeColorN: 53, 281 setFillColor: 54, 282 setFillColorN: 55, 283 setStrokeGray: 56, 284 setFillGray: 57, 285 setStrokeRGBColor: 58, 286 setFillRGBColor: 59, 287 setStrokeCMYKColor: 60, 288 setFillCMYKColor: 61, 289 shadingFill: 62, 290 beginInlineImage: 63, 291 beginImageData: 64, 292 endInlineImage: 65, 293 paintXObject: 66, 294 markPoint: 67, 295 markPointProps: 68, 296 beginMarkedContent: 69, 297 beginMarkedContentProps: 70, 298 endMarkedContent: 71, 299 beginCompat: 72, 300 endCompat: 73, 301 paintFormXObjectBegin: 74, 302 paintFormXObjectEnd: 75, 303 beginGroup: 76, 304 endGroup: 77, 305 beginAnnotation: 80, 306 endAnnotation: 81, 307 paintImageMaskXObject: 83, 308 paintImageMaskXObjectGroup: 84, 309 paintImageXObject: 85, 310 paintInlineImageXObject: 86, 311 paintInlineImageXObjectGroup: 87, 312 paintImageXObjectRepeat: 88, 313 paintImageMaskXObjectRepeat: 89, 314 paintSolidColorImageMask: 90, 315 constructPath: 91, 316 setStrokeTransparent: 92, 317 setFillTransparent: 93, 318 rawFillPath: 94 319}; 320const DrawOPS = { 321 moveTo: 0, 322 lineTo: 1, 323 curveTo: 2, 324 quadraticCurveTo: 3, 325 closePath: 4 326}; 327const PasswordResponses = { 328 NEED_PASSWORD: 1, 329 INCORRECT_PASSWORD: 2 330}; 331let verbosity = VerbosityLevel.WARNINGS; 332function setVerbosityLevel(level) { 333 if (Number.isInteger(level)) { 334 verbosity = level; 335 } 336} 337function getVerbosityLevel() { 338 return verbosity; 339} 340function info(msg) { 341 if (verbosity >= VerbosityLevel.INFOS) { 342 console.info(`Info: ${msg}`); 343 } 344} 345function warn(msg) { 346 if (verbosity >= VerbosityLevel.WARNINGS) { 347 console.warn(`Warning: ${msg}`); 348 } 349} 350function unreachable(msg) { 351 throw new Error(msg); 352} 353function assert(cond, msg) { 354 if (!cond) { 355 unreachable(msg); 356 } 357} 358function _isValidProtocol(url) { 359 switch (url?.protocol) { 360 case "http:": 361 case "https:": 362 case "ftp:": 363 case "mailto:": 364 case "tel:": 365 return true; 366 default: 367 return false; 368 } 369} 370function createValidAbsoluteUrl(url, baseUrl = null, options = null) { 371 if (!url) { 372 return null; 373 } 374 if (options && typeof url === "string") { 375 if (options.addDefaultProtocol && url.startsWith("www.")) { 376 const dots = url.match(/\./g); 377 if (dots?.length >= 2) { 378 url = `http://${url}`; 379 } 380 } 381 if (options.tryConvertEncoding) { 382 try { 383 url = stringToUTF8String(url); 384 } catch {} 385 } 386 } 387 const absoluteUrl = baseUrl ? URL.parse(url, baseUrl) : URL.parse(url); 388 return _isValidProtocol(absoluteUrl) ? absoluteUrl : null; 389} 390function updateUrlHash(url, hash, allowRel = false) { 391 const res = URL.parse(url); 392 if (res) { 393 res.hash = hash; 394 return res.href; 395 } 396 if (allowRel && createValidAbsoluteUrl(url, "http://example.com")) { 397 return url.split("#", 1)[0] + `${hash ? `#${hash}` : ""}`; 398 } 399 return ""; 400} 401function stripPath(str) { 402 return str.substring(str.lastIndexOf("/") + 1); 403} 404function shadow(obj, prop, value, nonSerializable = false) { 405 Object.defineProperty(obj, prop, { 406 value, 407 enumerable: !nonSerializable, 408 configurable: true, 409 writable: false 410 }); 411 return value; 412} 413const BaseException = function BaseExceptionClosure() { 414 function BaseException(message, name) { 415 this.message = message; 416 this.name = name; 417 } 418 BaseException.prototype = new Error(); 419 BaseException.constructor = BaseException; 420 return BaseException; 421}(); 422class PasswordException extends BaseException { 423 constructor(msg, code) { 424 super(msg, "PasswordException"); 425 this.code = code; 426 } 427} 428class UnknownErrorException extends BaseException { 429 constructor(msg, details) { 430 super(msg, "UnknownErrorException"); 431 this.details = details; 432 } 433} 434class InvalidPDFException extends BaseException { 435 constructor(msg) { 436 super(msg, "InvalidPDFException"); 437 } 438} 439class ResponseException extends BaseException { 440 constructor(msg, status, missing) { 441 super(msg, "ResponseException"); 442 this.status = status; 443 this.missing = missing; 444 } 445} 446class FormatError extends BaseException { 447 constructor(msg) { 448 super(msg, "FormatError"); 449 } 450} 451class AbortException extends BaseException { 452 constructor(msg) { 453 super(msg, "AbortException"); 454 } 455} 456function bytesToString(bytes) { 457 if (typeof bytes !== "object" || bytes?.length === undefined) { 458 unreachable("Invalid argument for bytesToString"); 459 } 460 const length = bytes.length; 461 const MAX_ARGUMENT_COUNT = 8192; 462 if (length < MAX_ARGUMENT_COUNT) { 463 return String.fromCharCode.apply(null, bytes); 464 } 465 const strBuf = []; 466 for (let i = 0; i < length; i += MAX_ARGUMENT_COUNT) { 467 const chunkEnd = Math.min(i + MAX_ARGUMENT_COUNT, length); 468 const chunk = bytes.subarray(i, chunkEnd); 469 strBuf.push(String.fromCharCode.apply(null, chunk)); 470 } 471 return strBuf.join(""); 472} 473function stringToBytes(str) { 474 if (typeof str !== "string") { 475 unreachable("Invalid argument for stringToBytes"); 476 } 477 const length = str.length; 478 const bytes = new Uint8Array(length); 479 for (let i = 0; i < length; ++i) { 480 bytes[i] = str.charCodeAt(i) & 0xff; 481 } 482 return bytes; 483}
vendor: 13,109 bytes, lines 484-893
484function objectSize(obj) { 485 return Object.keys(obj).length; 486} 487class FeatureTest { 488 static get isLittleEndian() { 489 const buffer8 = new Uint8Array(4); 490 buffer8[0] = 1; 491 const view32 = new Uint32Array(buffer8.buffer, 0, 1); 492 return shadow(this, "isLittleEndian", view32[0] === 1); 493 } 494 static get isOffscreenCanvasSupported() { 495 return shadow(this, "isOffscreenCanvasSupported", typeof OffscreenCanvas !== "undefined"); 496 } 497 static get isImageDecoderSupported() { 498 return shadow(this, "isImageDecoderSupported", typeof ImageDecoder !== "undefined"); 499 } 500 static get isFloat16ArraySupported() { 501 return shadow(this, "isFloat16ArraySupported", typeof Float16Array !== "undefined"); 502 } 503 static get isSanitizerSupported() { 504 return shadow(this, "isSanitizerSupported", typeof Sanitizer !== "undefined"); 505 } 506 static get platform() { 507 const { 508 platform, 509 userAgent 510 } = navigator; 511 return shadow(this, "platform", { 512 isAndroid: userAgent.includes("Android"), 513 isLinux: platform.includes("Linux"), 514 isMac: platform.includes("Mac"), 515 isWindows: platform.includes("Win"), 516 isFirefox: userAgent.includes("Firefox") 517 }); 518 } 519 static get isCanvasFilterSupported() { 520 let ctx; 521 if (this.isOffscreenCanvasSupported) { 522 ctx = new OffscreenCanvas(1, 1).getContext("2d"); 523 } else if (typeof document !== "undefined") { 524 ctx = document.createElement("canvas").getContext("2d"); 525 } 526 return shadow(this, "isCanvasFilterSupported", ctx?.filter !== undefined); 527 } 528 static get isAlphaColorInputSupported() { 529 if (typeof document === "undefined") { 530 return shadow(this, "isAlphaColorInputSupported", false); 531 } 532 const input = document.createElement("input"); 533 input.type = "color"; 534 input.setAttribute("alpha", ""); 535 input.value = "#ff000080"; 536 return shadow(this, "isAlphaColorInputSupported", input.value !== "#ff0000"); 537 } 538 static get isBackdropFilterSupported() { 539 return shadow(this, "isBackdropFilterSupported", typeof CSS !== "undefined" && CSS.supports("backdrop-filter", "blur(1px)")); 540 } 541} 542class Util { 543 static get hexNums() { 544 return shadow(this, "hexNums", Array.from(Array(256).keys(), n => n.toString(16).padStart(2, "0"))); 545 } 546 static makeHexColor(r, g, b) { 547 return `#${this.hexNums[r]}${this.hexNums[g]}${this.hexNums[b]}`; 548 } 549 static transform(m1, m2) { 550 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]]; 551 } 552 static multiplyByDOMMatrix(m, md) { 553 return [m[0] * md.a + m[2] * md.b, m[1] * md.a + m[3] * md.b, m[0] * md.c + m[2] * md.d, m[1] * md.c + m[3] * md.d, m[0] * md.e + m[2] * md.f + m[4], m[1] * md.e + m[3] * md.f + m[5]]; 554 } 555 static applyTransform(p, m, pos = 0) { 556 const p0 = p[pos]; 557 const p1 = p[pos + 1]; 558 p[pos] = p0 * m[0] + p1 * m[2] + m[4]; 559 p[pos + 1] = p0 * m[1] + p1 * m[3] + m[5]; 560 } 561 static applyTransformToBezier(p, transform, pos = 0) { 562 const m0 = transform[0]; 563 const m1 = transform[1]; 564 const m2 = transform[2]; 565 const m3 = transform[3]; 566 const m4 = transform[4]; 567 const m5 = transform[5]; 568 for (let i = 0; i < 6; i += 2) { 569 const pI = p[pos + i]; 570 const pI1 = p[pos + i + 1]; 571 p[pos + i] = pI * m0 + pI1 * m2 + m4; 572 p[pos + i + 1] = pI * m1 + pI1 * m3 + m5; 573 } 574 } 575 static applyInverseTransform(p, m) { 576 const p0 = p[0]; 577 const p1 = p[1]; 578 const d = m[0] * m[3] - m[1] * m[2]; 579 p[0] = (p0 * m[3] - p1 * m[2] + m[2] * m[5] - m[4] * m[3]) / d; 580 p[1] = (-p0 * m[1] + p1 * m[0] + m[4] * m[1] - m[5] * m[0]) / d; 581 } 582 static axialAlignedBoundingBox(rect, transform, output) { 583 const m0 = transform[0]; 584 const m1 = transform[1]; 585 const m2 = transform[2]; 586 const m3 = transform[3]; 587 const m4 = transform[4]; 588 const m5 = transform[5]; 589 const r0 = rect[0]; 590 const r1 = rect[1]; 591 const r2 = rect[2]; 592 const r3 = rect[3]; 593 let a0 = m0 * r0 + m4; 594 let a2 = a0; 595 let a1 = m0 * r2 + m4; 596 let a3 = a1; 597 let b0 = m3 * r1 + m5; 598 let b2 = b0; 599 let b1 = m3 * r3 + m5; 600 let b3 = b1; 601 if (m1 !== 0 || m2 !== 0) { 602 const m1r0 = m1 * r0; 603 const m1r2 = m1 * r2; 604 const m2r1 = m2 * r1; 605 const m2r3 = m2 * r3; 606 a0 += m2r1; 607 a3 += m2r1; 608 a1 += m2r3; 609 a2 += m2r3; 610 b0 += m1r0; 611 b3 += m1r0; 612 b1 += m1r2; 613 b2 += m1r2; 614 } 615 output[0] = Math.min(output[0], a0, a1, a2, a3); 616 output[1] = Math.min(output[1], b0, b1, b2, b3); 617 output[2] = Math.max(output[2], a0, a1, a2, a3); 618 output[3] = Math.max(output[3], b0, b1, b2, b3); 619 } 620 static inverseTransform(m) { 621 const d = m[0] * m[3] - m[1] * m[2]; 622 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]; 623 } 624 static singularValueDecompose2dScale(matrix, output) { 625 const m0 = matrix[0]; 626 const m1 = matrix[1]; 627 const m2 = matrix[2]; 628 const m3 = matrix[3]; 629 const a = m0 ** 2 + m1 ** 2; 630 const b = m0 * m2 + m1 * m3; 631 const c = m2 ** 2 + m3 ** 2; 632 const first = (a + c) / 2; 633 const second = Math.sqrt(first ** 2 - (a * c - b ** 2)); 634 output[0] = Math.sqrt(first + second || 1); 635 output[1] = Math.sqrt(first - second || 1); 636 } 637 static normalizeRect(rect) { 638 const r = rect.slice(0); 639 if (rect[0] > rect[2]) { 640 r[0] = rect[2]; 641 r[2] = rect[0]; 642 } 643 if (rect[1] > rect[3]) { 644 r[1] = rect[3]; 645 r[3] = rect[1]; 646 } 647 return r; 648 } 649 static intersect(rect1, rect2) { 650 const xLow = Math.max(Math.min(rect1[0], rect1[2]), Math.min(rect2[0], rect2[2])); 651 const xHigh = Math.min(Math.max(rect1[0], rect1[2]), Math.max(rect2[0], rect2[2])); 652 if (xLow > xHigh) { 653 return null; 654 } 655 const yLow = Math.max(Math.min(rect1[1], rect1[3]), Math.min(rect2[1], rect2[3])); 656 const yHigh = Math.min(Math.max(rect1[1], rect1[3]), Math.max(rect2[1], rect2[3])); 657 if (yLow > yHigh) { 658 return null; 659 } 660 return [xLow, yLow, xHigh, yHigh]; 661 } 662 static pointBoundingBox(x, y, minMax) { 663 minMax[0] = Math.min(minMax[0], x); 664 minMax[1] = Math.min(minMax[1], y); 665 minMax[2] = Math.max(minMax[2], x); 666 minMax[3] = Math.max(minMax[3], y); 667 } 668 static rectBoundingBox(x0, y0, x1, y1, minMax) { 669 minMax[0] = Math.min(minMax[0], x0, x1); 670 minMax[1] = Math.min(minMax[1], y0, y1); 671 minMax[2] = Math.max(minMax[2], x0, x1); 672 minMax[3] = Math.max(minMax[3], y0, y1); 673 } 674 static #getExtremumOnCurve(x0, x1, x2, x3, y0, y1, y2, y3, t, minMax) { 675 if (t <= 0 || t >= 1) { 676 return; 677 } 678 const mt = 1 - t; 679 const tt = t * t; 680 const ttt = tt * t; 681 const x = mt * (mt * (mt * x0 + 3 * t * x1) + 3 * tt * x2) + ttt * x3; 682 const y = mt * (mt * (mt * y0 + 3 * t * y1) + 3 * tt * y2) + ttt * y3; 683 minMax[0] = Math.min(minMax[0], x); 684 minMax[1] = Math.min(minMax[1], y); 685 minMax[2] = Math.max(minMax[2], x); 686 minMax[3] = Math.max(minMax[3], y); 687 } 688 static #getExtremum(x0, x1, x2, x3, y0, y1, y2, y3, a, b, c, minMax) { 689 if (Math.abs(a) < 1e-12) { 690 if (Math.abs(b) >= 1e-12) { 691 this.#getExtremumOnCurve(x0, x1, x2, x3, y0, y1, y2, y3, -c / b, minMax); 692 } 693 return; 694 } 695 const delta = b ** 2 - 4 * c * a; 696 if (delta < 0) { 697 return; 698 } 699 const sqrtDelta = Math.sqrt(delta); 700 const a2 = 2 * a; 701 this.#getExtremumOnCurve(x0, x1, x2, x3, y0, y1, y2, y3, (-b + sqrtDelta) / a2, minMax); 702 this.#getExtremumOnCurve(x0, x1, x2, x3, y0, y1, y2, y3, (-b - sqrtDelta) / a2, minMax); 703 } 704 static bezierBoundingBox(x0, y0, x1, y1, x2, y2, x3, y3, minMax) { 705 minMax[0] = Math.min(minMax[0], x0, x3); 706 minMax[1] = Math.min(minMax[1], y0, y3); 707 minMax[2] = Math.max(minMax[2], x0, x3); 708 minMax[3] = Math.max(minMax[3], y0, y3); 709 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); 710 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); 711 } 712} 713function stringToUTF8String(str) { 714 return decodeURIComponent(escape(str)); 715} 716function utf8StringToString(str) { 717 return unescape(encodeURIComponent(str)); 718} 719function isArrayEqual(arr1, arr2) { 720 if (arr1.length !== arr2.length) { 721 return false; 722 } 723 for (let i = 0, ii = arr1.length; i < ii; i++) { 724 if (arr1[i] !== arr2[i]) { 725 return false; 726 } 727 } 728 return true; 729} 730let NormalizeRegex = null; 731let NormalizationMap = null; 732function normalizeUnicode(str) { 733 if (!NormalizeRegex) { 734 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; 735 NormalizationMap = new Map([["ſt", "ſt"]]); 736 } 737 return str.replaceAll(NormalizeRegex, (_, p1, p2) => p1 ? p1.normalize("NFKC") : NormalizationMap.get(p2)); 738} 739function getUuid() { 740 if (typeof crypto.randomUUID === "function") { 741 return crypto.randomUUID(); 742 } 743 const buf = new Uint8Array(32); 744 crypto.getRandomValues(buf); 745 return bytesToString(buf); 746} 747function _isValidExplicitDest(validRef, validName, dest) { 748 if (!Array.isArray(dest) || dest.length < 2) { 749 return false; 750 } 751 const [page, zoom, ...args] = dest; 752 if (!validRef(page) && !Number.isInteger(page)) { 753 return false; 754 } 755 if (!validName(zoom)) { 756 return false; 757 } 758 const argsLen = args.length; 759 let allowNull = true; 760 switch (zoom.name) { 761 case "XYZ": 762 if (argsLen < 2 || argsLen > 3) { 763 return false; 764 } 765 break; 766 case "Fit": 767 case "FitB": 768 return argsLen === 0; 769 case "FitH": 770 case "FitBH": 771 case "FitV": 772 case "FitBV": 773 if (argsLen > 1) { 774 return false; 775 } 776 break; 777 case "FitR": 778 if (argsLen !== 4) { 779 return false; 780 } 781 allowNull = false; 782 break; 783 default: 784 return false; 785 } 786 for (const arg of args) { 787 if (typeof arg === "number" || allowNull && arg === null) { 788 continue; 789 } 790 return false; 791 } 792 return true; 793} 794const makeArr = () => []; 795const makeMap = () => new Map(); 796const makeObj = () => Object.create(null); 797 798;// ./src/shared/math_clamp.js 799function MathClamp(v, min, max) { 800 return Math.min(Math.max(v, min), max); 801} 802 803;// ./src/display/page_viewport.js 804 805class PageViewport { 806 constructor({ 807 viewBox, 808 userUnit, 809 scale, 810 rotation, 811 offsetX = 0, 812 offsetY = 0, 813 dontFlip = false 814 }) { 815 this.viewBox = viewBox; 816 this.userUnit = userUnit; 817 this.scale = scale; 818 this.rotation = rotation; 819 this.offsetX = offsetX; 820 this.offsetY = offsetY; 821 scale *= userUnit; 822 const centerX = (viewBox[2] + viewBox[0]) / 2; 823 const centerY = (viewBox[3] + viewBox[1]) / 2; 824 let rotateA, rotateB, rotateC, rotateD; 825 rotation %= 360; 826 if (rotation < 0) { 827 rotation += 360; 828 } 829 switch (rotation) { 830 case 180: 831 rotateA = -1; 832 rotateB = 0; 833 rotateC = 0; 834 rotateD = 1; 835 break; 836 case 90: 837 rotateA = 0; 838 rotateB = 1; 839 rotateC = 1; 840 rotateD = 0; 841 break; 842 case 270: 843 rotateA = 0; 844 rotateB = -1; 845 rotateC = -1; 846 rotateD = 0; 847 break; 848 case 0: 849 rotateA = 1; 850 rotateB = 0; 851 rotateC = 0; 852 rotateD = -1; 853 break; 854 default: 855 throw new Error("PageViewport: Invalid rotation, must be a multiple of 90 degrees."); 856 } 857 if (dontFlip) { 858 rotateC = -rotateC; 859 rotateD = -rotateD; 860 } 861 let offsetCanvasX, offsetCanvasY; 862 let width, height; 863 if (rotateA === 0) { 864 offsetCanvasX = Math.abs(centerY - viewBox[1]) * scale + offsetX; 865 offsetCanvasY = Math.abs(centerX - viewBox[0]) * scale + offsetY; 866 width = (viewBox[3] - viewBox[1]) * scale; 867 height = (viewBox[2] - viewBox[0]) * scale; 868 } else { 869 offsetCanvasX = Math.abs(centerX - viewBox[0]) * scale + offsetX; 870 offsetCanvasY = Math.abs(centerY - viewBox[1]) * scale + offsetY; 871 width = (viewBox[2] - viewBox[0]) * scale; 872 height = (viewBox[3] - viewBox[1]) * scale; 873 } 874 this.transform = [rotateA * scale, rotateB * scale, rotateC * scale, rotateD * scale, offsetCanvasX - rotateA * scale * centerX - rotateC * scale * centerY, offsetCanvasY - rotateB * scale * centerX - rotateD * scale * centerY]; 875 this.width = width; 876 this.height = height; 877 } 878 get rawDims() { 879 const dims = this.viewBox; 880 return shadow(this, "rawDims", { 881 pageWidth: dims[2] - dims[0], 882 pageHeight: dims[3] - dims[1], 883 pageX: dims[0], 884 pageY: dims[1] 885 }); 886 } 887 clone({ 888 scale = this.scale, 889 rotation = this.rotation, 890 offsetX = this.offsetX, 891 offsetY = this.offsetY, 892 dontFlip = false 893 }
vendor: 7,759 bytes, lines 893-1167
893 = {}) { 894 return new PageViewport({ 895 viewBox: this.viewBox.slice(), 896 userUnit: this.userUnit, 897 scale, 898 rotation, 899 offsetX, 900 offsetY, 901 dontFlip 902 }); 903 } 904 convertToViewportPoint(x, y) { 905 const p = [x, y]; 906 Util.applyTransform(p, this.transform); 907 return p; 908 } 909 convertToPdfPoint(x, y) { 910 const p = [x, y]; 911 Util.applyInverseTransform(p, this.transform); 912 return p; 913 } 914} 915 916;// ./src/display/xfa_text.js 917class XfaText { 918 static textContent(xfa) { 919 const items = []; 920 const output = { 921 items, 922 styles: Object.create(null) 923 }; 924 function walk(node) { 925 if (!node) { 926 return; 927 } 928 let str = null; 929 const name = node.name; 930 if (name === "#text") { 931 str = node.value; 932 } else if (!XfaText.shouldBuildText(name)) { 933 return; 934 } else if (node?.attributes?.textContent) { 935 str = node.attributes.textContent; 936 } else if (node.value) { 937 str = node.value; 938 } 939 if (str !== null) { 940 items.push({ 941 str 942 }); 943 } 944 if (!node.children) { 945 return; 946 } 947 for (const child of node.children) { 948 walk(child); 949 } 950 } 951 walk(xfa); 952 return output; 953 } 954 static shouldBuildText(name) { 955 return !(name === "textarea" || name === "input" || name === "option" || name === "select"); 956 } 957} 958 959;// ./src/display/xfa_layer.js 960 961 962class XfaLayer { 963 static setupStorage(html, id, element, storage, intent) { 964 const storedData = storage.getValue(id, { 965 value: null 966 }); 967 switch (element.name) { 968 case "textarea": 969 if (storedData.value !== null) { 970 html.textContent = storedData.value; 971 } 972 if (intent === "print") { 973 break; 974 } 975 html.addEventListener("input", event => { 976 storage.setValue(id, { 977 value: event.target.value 978 }); 979 }); 980 break; 981 case "input": 982 if (element.attributes.type === "radio" || element.attributes.type === "checkbox") { 983 if (storedData.value === element.attributes.xfaOn) { 984 html.setAttribute("checked", true); 985 } else if (storedData.value === element.attributes.xfaOff) { 986 html.removeAttribute("checked"); 987 } 988 if (intent === "print") { 989 break; 990 } 991 html.addEventListener("change", event => { 992 storage.setValue(id, { 993 value: event.target.checked ? event.target.getAttribute("xfaOn") : event.target.getAttribute("xfaOff") 994 }); 995 }); 996 } else { 997 if (storedData.value !== null) { 998 html.setAttribute("value", storedData.value); 999 } 1000 if (intent === "print") { 1001 break; 1002 } 1003 html.addEventListener("input", event => { 1004 storage.setValue(id, { 1005 value: event.target.value 1006 }); 1007 }); 1008 } 1009 break; 1010 case "select": 1011 if (storedData.value !== null) { 1012 html.setAttribute("value", storedData.value); 1013 for (const option of element.children) { 1014 if (option.attributes.value === storedData.value) { 1015 option.attributes.selected = true; 1016 } else if (Object.hasOwn(option.attributes, "selected")) { 1017 delete option.attributes.selected; 1018 } 1019 } 1020 } 1021 html.addEventListener("input", event => { 1022 const options = event.target.options; 1023 const value = options.selectedIndex === -1 ? "" : options[options.selectedIndex].value; 1024 storage.setValue(id, { 1025 value 1026 }); 1027 }); 1028 break; 1029 } 1030 } 1031 static setAttributes({ 1032 html, 1033 element, 1034 storage = null, 1035 intent, 1036 linkService 1037 }) { 1038 const { 1039 attributes 1040 } = element; 1041 const isHTMLAnchorElement = html instanceof HTMLAnchorElement; 1042 if (attributes.type === "radio") { 1043 attributes.name = `${attributes.name}-${intent}`; 1044 } 1045 for (const [key, value] of Object.entries(attributes)) { 1046 if (value === null || value === undefined) { 1047 continue; 1048 } 1049 switch (key) { 1050 case "class": 1051 if (value.length) { 1052 html.setAttribute(key, value.join(" ")); 1053 } 1054 break; 1055 case "dataId": 1056 break; 1057 case "id": 1058 html.setAttribute("data-element-id", value); 1059 break; 1060 case "style": 1061 Object.assign(html.style, value); 1062 break; 1063 case "textContent": 1064 html.textContent = value; 1065 break; 1066 default: 1067 if (!isHTMLAnchorElement || key !== "href" && key !== "newWindow") { 1068 html.setAttribute(key, value); 1069 } 1070 } 1071 } 1072 if (isHTMLAnchorElement) { 1073 linkService.addLinkAttributes(html, attributes.href, attributes.newWindow); 1074 } 1075 if (storage && attributes.dataId) { 1076 this.setupStorage(html, attributes.dataId, element, storage); 1077 } 1078 } 1079 static render(parameters) { 1080 const storage = parameters.annotationStorage; 1081 const linkService = parameters.linkService; 1082 const root = parameters.xfaHtml; 1083 const intent = parameters.intent || "display"; 1084 const rootHtml = document.createElement(root.name); 1085 if (root.attributes) { 1086 this.setAttributes({ 1087 html: rootHtml, 1088 element: root, 1089 intent, 1090 linkService 1091 }); 1092 } 1093 const isNotForRichText = intent !== "richText"; 1094 const rootDiv = parameters.div; 1095 rootDiv.append(rootHtml); 1096 if (parameters.viewport) { 1097 const transform = `matrix(${parameters.viewport.transform.join(",")})`; 1098 rootDiv.style.transform = transform; 1099 } 1100 if (isNotForRichText) { 1101 rootDiv.setAttribute("class", "xfaLayer xfaFont"); 1102 } 1103 const textDivs = []; 1104 if (root.children.length === 0) { 1105 if (root.value) { 1106 const node = document.createTextNode(root.value); 1107 rootHtml.append(node); 1108 if (isNotForRichText && XfaText.shouldBuildText(root.name)) { 1109 textDivs.push(node); 1110 } 1111 } 1112 return { 1113 textDivs 1114 }; 1115 } 1116 const stack = [[root, -1, rootHtml]]; 1117 while (stack.length > 0) { 1118 const [parent, i, html] = stack.at(-1); 1119 if (i + 1 === parent.children.length) { 1120 stack.pop(); 1121 continue; 1122 } 1123 const child = parent.children[++stack.at(-1)[1]]; 1124 if (child === null) { 1125 continue; 1126 } 1127 const { 1128 name 1129 } = child; 1130 if (name === "#text") { 1131 const node = document.createTextNode(child.value); 1132 textDivs.push(node); 1133 html.append(node); 1134 continue; 1135 } 1136 const childHtml = child?.attributes?.xmlns ? document.createElementNS(child.attributes.xmlns, name) : document.createElement(name); 1137 html.append(childHtml); 1138 if (child.attributes) { 1139 this.setAttributes({ 1140 html: childHtml, 1141 element: child, 1142 storage, 1143 intent, 1144 linkService 1145 }); 1146 } 1147 if (child.children?.length > 0) { 1148 stack.push([child, -1, childHtml]); 1149 } else if (child.value) { 1150 const node = document.createTextNode(child.value); 1151 if (isNotForRichText && XfaText.shouldBuildText(name)) { 1152 textDivs.push(node); 1153 } 1154 childHtml.append(node); 1155 } 1156 } 1157 for (const el of rootDiv.querySelectorAll(".xfaNonInteractive input, .xfaNonInteractive textarea")) { 1158 el.setAttribute("readOnly", true); 1159 } 1160 return { 1161 textDivs 1162 }; 1163 } 1164 static update(parameters) { 1165 const transform = `matrix(${parameters.viewport.transform.join(",")})`; 1166 parameters.div.style.transform = transform; 1167 parameters.div.hidden = false;
vendor: 3,846 bytes, lines 1168-1316
1168 } 1169 static getPageViewport(xfaPage, { 1170 scale = 1, 1171 rotation = 0 1172 }) { 1173 const { 1174 width, 1175 height 1176 } = xfaPage.attributes.style; 1177 return new PageViewport({ 1178 viewBox: [0, 0, parseInt(width, 10), parseInt(height, 10)], 1179 userUnit: 1, 1180 scale, 1181 rotation 1182 }); 1183 } 1184} 1185 1186;// ./src/display/display_utils.js 1187 1188 1189 1190 1191class PixelsPerInch { 1192 static CSS = 96.0; 1193 static PDF = 72.0; 1194 static PDF_TO_CSS_UNITS = this.CSS / this.PDF; 1195} 1196async function fetchData(url, type = "text") { 1197 if (isValidFetchUrl(url, document.baseURI)) { 1198 const response = await fetch(url); 1199 if (!response.ok) { 1200 throw new Error(response.statusText); 1201 } 1202 switch (type) { 1203 case "blob": 1204 return response.blob(); 1205 case "bytes": 1206 return response.bytes(); 1207 case "json": 1208 return response.json(); 1209 } 1210 return response.text(); 1211 } 1212 return new Promise((resolve, reject) => { 1213 const request = new XMLHttpRequest(); 1214 request.open("GET", url, true); 1215 request.responseType = type === "bytes" ? "arraybuffer" : type; 1216 request.onreadystatechange = () => { 1217 if (request.readyState !== XMLHttpRequest.DONE) { 1218 return; 1219 } 1220 if (request.status === 200 || request.status === 0) { 1221 switch (type) { 1222 case "bytes": 1223 resolve(new Uint8Array(request.response)); 1224 return; 1225 case "blob": 1226 case "json": 1227 resolve(request.response); 1228 return; 1229 } 1230 resolve(request.responseText); 1231 return; 1232 } 1233 reject(new Error(request.statusText)); 1234 }; 1235 request.send(null); 1236 }); 1237} 1238class RenderingCancelledException extends BaseException { 1239 constructor(msg, extraDelay = 0) { 1240 super(msg, "RenderingCancelledException"); 1241 this.extraDelay = extraDelay; 1242 } 1243} 1244function isDataScheme(url) { 1245 const ii = url.length; 1246 let i = 0; 1247 while (i < ii && url[i].trim() === "") { 1248 i++; 1249 } 1250 return url.substring(i, i + 5).toLowerCase() === "data:"; 1251} 1252function isPdfFile(filename) { 1253 return typeof filename === "string" && /\.pdf$/i.test(filename); 1254} 1255function getFilenameFromUrl(url) { 1256 [url] = url.split(/[#?]/, 1); 1257 return stripPath(url); 1258} 1259function getPdfFilenameFromUrl(url, defaultFilename = "document.pdf") { 1260 if (typeof url !== "string") { 1261 return defaultFilename; 1262 } 1263 if (isDataScheme(url)) { 1264 warn('getPdfFilenameFromUrl: ignore "data:"-URL for performance reasons.'); 1265 return defaultFilename; 1266 } 1267 const getURL = urlString => { 1268 try { 1269 return new URL(urlString); 1270 } catch {} 1271 try { 1272 return new URL(decodeURIComponent(urlString)); 1273 } catch {} 1274 try { 1275 return new URL(urlString, "https://foo.bar"); 1276 } catch {} 1277 try { 1278 return new URL(decodeURIComponent(urlString), "https://foo.bar"); 1279 } catch {} 1280 return null; 1281 }; 1282 const newURL = getURL(url); 1283 if (!newURL) { 1284 return defaultFilename; 1285 } 1286 const decode = name => { 1287 try { 1288 let decoded = decodeURIComponent(name); 1289 if (decoded.includes("/")) { 1290 decoded = stripPath(decoded); 1291 if (decoded.length === 4 && pdfRegex.test(decoded)) { 1292 return name; 1293 } 1294 } 1295 return decoded; 1296 } catch { 1297 return name; 1298 } 1299 }; 1300 const pdfRegex = /\.pdf$/i; 1301 const filename = stripPath(newURL.pathname); 1302 if (pdfRegex.test(filename)) { 1303 return decode(filename); 1304 } 1305 if (newURL.searchParams.size > 0) { 1306 const getLast = iterator => [...iterator].findLast(v => pdfRegex.test(v)); 1307 const name = getLast(newURL.searchParams.values()) ?? getLast(newURL.searchParams.keys()); 1308 if (name) { 1309 return decode(name); 1310 } 1311 } 1312 if (newURL.hash) { 1313 const reFilename = /[^/?#=]+\.pdf\b(?!.*\.pdf\b)/i; 1314 const hashFilename = reFilename.exec(newURL.hash); 1315 if (hashFilename) { 1316 return decode(hashFilename[0]);
vendor: 18,746 bytes, lines 1316-1959
1316 1317 } 1318 } 1319 return defaultFilename; 1320} 1321class StatTimer { 1322 #started = new Map(); 1323 times = []; 1324 time(name) { 1325 if (this.#started.has(name)) { 1326 warn(`Timer is already running for ${name}`); 1327 } 1328 this.#started.set(name, Date.now()); 1329 } 1330 timeEnd(name) { 1331 if (!this.#started.has(name)) { 1332 warn(`Timer has not been started for ${name}`); 1333 } 1334 this.times.push({ 1335 name, 1336 start: this.#started.get(name), 1337 end: Date.now() 1338 }); 1339 this.#started.delete(name); 1340 } 1341 toString() { 1342 const longest = Math.max(...this.times.map(t => t.name.length)); 1343 return this.times.map(t => `${t.name.padEnd(longest)} ${t.end - t.start}ms\n`).join(""); 1344 } 1345} 1346function isValidFetchUrl(url, baseUrl) { 1347 const res = baseUrl ? URL.parse(url, baseUrl) : URL.parse(url); 1348 return /https?:/.test(res?.protocol ?? ""); 1349} 1350function noContextMenu(e) { 1351 e.preventDefault(); 1352} 1353function stopEvent(e) { 1354 e.preventDefault(); 1355 e.stopPropagation(); 1356} 1357function deprecated(details) { 1358 console.log("Deprecated API usage: " + details); 1359} 1360class PDFDateString { 1361 static #regex; 1362 static toDateObject(input) { 1363 if (input instanceof Date) { 1364 return input; 1365 } 1366 if (!input || typeof input !== "string") { 1367 return null; 1368 } 1369 this.#regex ||= new RegExp("^D:" + "(\\d{4})" + "(\\d{2})?" + "(\\d{2})?" + "(\\d{2})?" + "(\\d{2})?" + "(\\d{2})?" + "([Z|+\\-])?" + "(\\d{2})?" + "'?" + "(\\d{2})?" + "'?"); 1370 const matches = this.#regex.exec(input); 1371 if (!matches) { 1372 return null; 1373 } 1374 const year = parseInt(matches[1], 10); 1375 let month = parseInt(matches[2], 10); 1376 month = month >= 1 && month <= 12 ? month - 1 : 0; 1377 let day = parseInt(matches[3], 10); 1378 day = day >= 1 && day <= 31 ? day : 1; 1379 let hour = parseInt(matches[4], 10); 1380 hour = hour >= 0 && hour <= 23 ? hour : 0; 1381 let minute = parseInt(matches[5], 10); 1382 minute = minute >= 0 && minute <= 59 ? minute : 0; 1383 let second = parseInt(matches[6], 10); 1384 second = second >= 0 && second <= 59 ? second : 0; 1385 const universalTimeRelation = matches[7] || "Z"; 1386 let offsetHour = parseInt(matches[8], 10); 1387 offsetHour = offsetHour >= 0 && offsetHour <= 23 ? offsetHour : 0; 1388 let offsetMinute = parseInt(matches[9], 10) || 0; 1389 offsetMinute = offsetMinute >= 0 && offsetMinute <= 59 ? offsetMinute : 0; 1390 if (universalTimeRelation === "-") { 1391 hour += offsetHour; 1392 minute += offsetMinute; 1393 } else if (universalTimeRelation === "+") { 1394 hour -= offsetHour; 1395 minute -= offsetMinute; 1396 } 1397 return new Date(Date.UTC(year, month, day, hour, minute, second)); 1398 } 1399} 1400function getRGBA(color) { 1401 if (color.startsWith("#")) { 1402 const hex = color.slice(1); 1403 return [parseInt(hex.slice(0, 2), 16), parseInt(hex.slice(2, 4), 16), parseInt(hex.slice(4, 6), 16), hex.length >= 8 ? parseInt(hex.slice(6, 8), 16) / 255 : 1]; 1404 } 1405 if (color.startsWith("rgb(")) { 1406 const [r, g, b] = color.slice(4, -1).split(",").map(x => parseInt(x, 10)); 1407 return [r, g, b, 1]; 1408 } 1409 if (color.startsWith("rgba(")) { 1410 const parts = color.slice(5, -1).split(","); 1411 return [parseInt(parts[0], 10), parseInt(parts[1], 10), parseInt(parts[2], 10), parseFloat(parts[3])]; 1412 } 1413 const m = color.match(/^color\(srgb\s+([\d.]+)\s+([\d.]+)\s+([\d.]+)(?:\s*\/\s*([\d.]+|none))?\)$/); 1414 if (m) { 1415 return [Math.round(parseFloat(m[1]) * 255), Math.round(parseFloat(m[2]) * 255), Math.round(parseFloat(m[3]) * 255), m[4] !== undefined && m[4] !== "none" ? parseFloat(m[4]) : 1]; 1416 } 1417 return null; 1418} 1419function getRGB(color) { 1420 const rgba = getRGBA(color); 1421 if (!rgba) { 1422 warn(`Not a valid color format: "${color}"`); 1423 return [0, 0, 0]; 1424 } 1425 return rgba.slice(0, 3); 1426} 1427function getColorValues(colors) { 1428 const span = document.createElement("span"); 1429 span.style.visibility = "hidden"; 1430 span.style.colorScheme = "only light"; 1431 document.body.append(span); 1432 for (const name of colors.keys()) { 1433 span.style.color = name; 1434 const computedColor = window.getComputedStyle(span).color; 1435 colors.set(name, getRGB(computedColor)); 1436 } 1437 span.remove(); 1438} 1439function getCurrentTransform(ctx) { 1440 const { 1441 a, 1442 b, 1443 c, 1444 d, 1445 e, 1446 f 1447 } = ctx.getTransform(); 1448 return [a, b, c, d, e, f]; 1449} 1450function getCurrentTransformInverse(ctx) { 1451 const { 1452 a, 1453 b, 1454 c, 1455 d, 1456 e, 1457 f 1458 } = ctx.getTransform().invertSelf(); 1459 return [a, b, c, d, e, f]; 1460} 1461function setLayerDimensions(div, viewport, mustFlip = false, mustRotate = true) { 1462 if (viewport instanceof PageViewport) { 1463 const { 1464 pageWidth, 1465 pageHeight 1466 } = viewport.rawDims; 1467 const { 1468 style 1469 } = div; 1470 const widthStr = `round(down, var(--total-scale-factor) * ${pageWidth}px, var(--scale-round-x))`, 1471 heightStr = `round(down, var(--total-scale-factor) * ${pageHeight}px, var(--scale-round-y))`; 1472 if (!mustFlip || viewport.rotation % 180 === 0) { 1473 style.width = widthStr; 1474 style.height = heightStr; 1475 } else { 1476 style.width = heightStr; 1477 style.height = widthStr; 1478 } 1479 } 1480 if (mustRotate) { 1481 div.setAttribute("data-main-rotation", viewport.rotation); 1482 } 1483} 1484class OutputScale { 1485 constructor() { 1486 const { 1487 pixelRatio 1488 } = OutputScale; 1489 this.sx = pixelRatio; 1490 this.sy = pixelRatio; 1491 } 1492 get scaled() { 1493 return this.sx !== 1 || this.sy !== 1; 1494 } 1495 get symmetric() { 1496 return this.sx === this.sy; 1497 } 1498 limitCanvas(width, height, maxPixels, maxDim, capAreaFactor = -1) { 1499 let maxAreaScale = Infinity, 1500 maxWidthScale = Infinity, 1501 maxHeightScale = Infinity; 1502 maxPixels = OutputScale.capPixels(maxPixels, capAreaFactor); 1503 if (maxPixels > 0) { 1504 maxAreaScale = Math.sqrt(maxPixels / (width * height)); 1505 } 1506 if (maxDim !== -1) { 1507 maxWidthScale = maxDim / width; 1508 maxHeightScale = maxDim / height; 1509 } 1510 const maxScale = Math.min(maxAreaScale, maxWidthScale, maxHeightScale); 1511 if (this.sx > maxScale || this.sy > maxScale) { 1512 this.sx = maxScale; 1513 this.sy = maxScale; 1514 return true; 1515 } 1516 return false; 1517 } 1518 static get pixelRatio() { 1519 return globalThis.devicePixelRatio || 1; 1520 } 1521 static capPixels(maxPixels, capAreaFactor) { 1522 if (capAreaFactor >= 0) { 1523 const winPixels = Math.ceil(window.screen.availWidth * window.screen.availHeight * this.pixelRatio ** 2 * (1 + capAreaFactor / 100)); 1524 return maxPixels > 0 ? Math.min(maxPixels, winPixels) : winPixels; 1525 } 1526 return maxPixels; 1527 } 1528} 1529const SupportedImageMimeTypes = ["image/apng", "image/avif", "image/bmp", "image/gif", "image/jpeg", "image/png", "image/svg+xml", "image/webp", "image/x-icon"]; 1530class ColorScheme { 1531 static get isDarkMode() { 1532 return shadow(this, "isDarkMode", !!window?.matchMedia?.("(prefers-color-scheme: dark)").matches); 1533 } 1534} 1535class CSSConstants { 1536 static get commentForegroundColor() { 1537 const element = document.createElement("span"); 1538 element.classList.add("comment", "sidebar"); 1539 const { 1540 style 1541 } = element; 1542 style.width = style.height = "0"; 1543 style.display = "none"; 1544 style.color = "var(--comment-fg-color)"; 1545 document.body.append(element); 1546 const { 1547 color 1548 } = window.getComputedStyle(element); 1549 element.remove(); 1550 return shadow(this, "commentForegroundColor", getRGB(color)); 1551 } 1552} 1553function applyOpacity(color, opacity) { 1554 opacity = MathClamp(opacity ?? 1, 0, 1); 1555 const white = 255 * (1 - opacity); 1556 return color.map(c => Math.round(c * opacity + white)); 1557} 1558function RGBToHSL(rgb, output) { 1559 const r = rgb[0] / 255; 1560 const g = rgb[1] / 255; 1561 const b = rgb[2] / 255; 1562 const max = Math.max(r, g, b); 1563 const min = Math.min(r, g, b); 1564 const l = (max + min) / 2; 1565 if (max === min) { 1566 output[0] = output[1] = 0; 1567 } else { 1568 const d = max - min; 1569 output[1] = l < 0.5 ? d / (max + min) : d / (2 - max - min); 1570 switch (max) { 1571 case r: 1572 output[0] = ((g - b) / d + (g < b ? 6 : 0)) * 60; 1573 break; 1574 case g: 1575 output[0] = ((b - r) / d + 2) * 60; 1576 break; 1577 case b: 1578 output[0] = ((r - g) / d + 4) * 60; 1579 break; 1580 } 1581 } 1582 output[2] = l; 1583} 1584function HSLToRGB(hsl, output) { 1585 const h = hsl[0]; 1586 const s = hsl[1]; 1587 const l = hsl[2]; 1588 const c = (1 - Math.abs(2 * l - 1)) * s; 1589 const x = c * (1 - Math.abs(h / 60 % 2 - 1)); 1590 const m = l - c / 2; 1591 switch (Math.floor(h / 60)) { 1592 case 0: 1593 output[0] = c + m; 1594 output[1] = x + m; 1595 output[2] = m; 1596 break; 1597 case 1: 1598 output[0] = x + m; 1599 output[1] = c + m; 1600 output[2] = m; 1601 break; 1602 case 2: 1603 output[0] = m; 1604 output[1] = c + m; 1605 output[2] = x + m; 1606 break; 1607 case 3: 1608 output[0] = m; 1609 output[1] = x + m; 1610 output[2] = c + m; 1611 break; 1612 case 4: 1613 output[0] = x + m; 1614 output[1] = m; 1615 output[2] = c + m; 1616 break; 1617 case 5: 1618 case 6: 1619 output[0] = c + m; 1620 output[1] = m; 1621 output[2] = x + m; 1622 break; 1623 } 1624} 1625function computeLuminance(x) { 1626 return x <= 0.03928 ? x / 12.92 : ((x + 0.055) / 1.055) ** 2.4; 1627} 1628function contrastRatio(hsl1, hsl2, output) { 1629 HSLToRGB(hsl1, output); 1630 output.map(computeLuminance); 1631 const lum1 = 0.2126 * output[0] + 0.7152 * output[1] + 0.0722 * output[2]; 1632 HSLToRGB(hsl2, output); 1633 output.map(computeLuminance); 1634 const lum2 = 0.2126 * output[0] + 0.7152 * output[1] + 0.0722 * output[2]; 1635 return lum1 > lum2 ? (lum1 + 0.05) / (lum2 + 0.05) : (lum2 + 0.05) / (lum1 + 0.05); 1636} 1637const contrastCache = new Map(); 1638function findContrastColor(baseColor, fixedColor) { 1639 const key = baseColor[0] + baseColor[1] * 0x100 + baseColor[2] * 0x10000 + fixedColor[0] * 0x1000000 + fixedColor[1] * 0x100000000 + fixedColor[2] * 0x10000000000; 1640 let cachedValue = contrastCache.get(key); 1641 if (cachedValue) { 1642 return cachedValue; 1643 } 1644 const array = new Float32Array(9); 1645 const output = array.subarray(0, 3); 1646 const baseHSL = array.subarray(3, 6); 1647 RGBToHSL(baseColor, baseHSL); 1648 const fixedHSL = array.subarray(6, 9); 1649 RGBToHSL(fixedColor, fixedHSL); 1650 const isFixedColorDark = fixedHSL[2] < 0.5; 1651 const minContrast = isFixedColorDark ? 12 : 4.5; 1652 baseHSL[2] = isFixedColorDark ? Math.sqrt(baseHSL[2]) : 1 - Math.sqrt(1 - baseHSL[2]); 1653 if (contrastRatio(baseHSL, fixedHSL, output) < minContrast) { 1654 let start, end; 1655 if (isFixedColorDark) { 1656 start = baseHSL[2]; 1657 end = 1; 1658 } else { 1659 start = 0; 1660 end = baseHSL[2]; 1661 } 1662 const PRECISION = 0.005; 1663 while (end - start > PRECISION) { 1664 const mid = baseHSL[2] = (start + end) / 2; 1665 if (isFixedColorDark === contrastRatio(baseHSL, fixedHSL, output) < minContrast) { 1666 start = mid; 1667 } else { 1668 end = mid; 1669 } 1670 } 1671 baseHSL[2] = isFixedColorDark ? end : start; 1672 } 1673 HSLToRGB(baseHSL, output); 1674 cachedValue = Util.makeHexColor(Math.round(output[0] * 255), Math.round(output[1] * 255), Math.round(output[2] * 255)); 1675 contrastCache.set(key, cachedValue); 1676 return cachedValue; 1677} 1678function renderRichText({ 1679 html, 1680 dir, 1681 className 1682}, container) { 1683 const fragment = document.createDocumentFragment(); 1684 if (typeof html === "string") { 1685 const p = document.createElement("p"); 1686 p.dir = dir || "auto"; 1687 const lines = html.split(/\r\n?|\n/); 1688 for (let i = 0, ii = lines.length; i < ii; ++i) { 1689 const line = lines[i]; 1690 p.append(document.createTextNode(line)); 1691 if (i < ii - 1) { 1692 p.append(document.createElement("br")); 1693 } 1694 } 1695 fragment.append(p); 1696 } else { 1697 XfaLayer.render({ 1698 xfaHtml: html, 1699 div: fragment, 1700 intent: "richText" 1701 }); 1702 } 1703 fragment.firstElementChild.classList.add("richText", className); 1704 container.append(fragment); 1705} 1706function makePathFromDrawOPS(data) { 1707 const path = new Path2D(); 1708 if (!data) { 1709 return path; 1710 } 1711 for (let i = 0, ii = data.length; i < ii;) { 1712 switch (data[i++]) { 1713 case DrawOPS.moveTo: 1714 path.moveTo(data[i++], data[i++]); 1715 break; 1716 case DrawOPS.lineTo: 1717 path.lineTo(data[i++], data[i++]); 1718 break; 1719 case DrawOPS.curveTo: 1720 path.bezierCurveTo(data[i++], data[i++], data[i++], data[i++], data[i++], data[i++]); 1721 break; 1722 case DrawOPS.quadraticCurveTo: 1723 path.quadraticCurveTo(data[i++], data[i++], data[i++], data[i++]); 1724 break; 1725 case DrawOPS.closePath: 1726 path.closePath(); 1727 break; 1728 default: 1729 warn(`Unrecognized drawing path operator: ${data[i - 1]}`); 1730 break; 1731 } 1732 } 1733 return path; 1734} 1735 1736;// ./src/display/editor/toolbar.js 1737 1738class EditorToolbar { 1739 #toolbar = null; 1740 #colorPicker = null; 1741 #editor; 1742 #buttons = null; 1743 #altText = null; 1744 #comment = null; 1745 #commentButtonDivider = null; 1746 #signatureDescriptionButton = null; 1747 static #l10nRemove = null; 1748 constructor(editor) { 1749 this.#editor = editor; 1750 EditorToolbar.#l10nRemove ||= Object.freeze({ 1751 freetext: "pdfjs-editor-remove-freetext-button", 1752 highlight: "pdfjs-editor-remove-highlight-button", 1753 ink: "pdfjs-editor-remove-ink-button", 1754 stamp: "pdfjs-editor-remove-stamp-button", 1755 signature: "pdfjs-editor-remove-signature-button" 1756 }); 1757 } 1758 render() { 1759 const editToolbar = this.#toolbar = document.createElement("div"); 1760 editToolbar.classList.add("editToolbar", "hidden"); 1761 editToolbar.setAttribute("role", "toolbar"); 1762 const signal = this.#editor._uiManager._signal; 1763 if (signal instanceof AbortSignal && !signal.aborted) { 1764 editToolbar.addEventListener("contextmenu", noContextMenu, { 1765 signal 1766 }); 1767 editToolbar.addEventListener("pointerdown", EditorToolbar.#pointerDown, { 1768 signal 1769 }); 1770 } 1771 const buttons = this.#buttons = document.createElement("div"); 1772 buttons.className = "buttons"; 1773 editToolbar.append(buttons); 1774 const position = this.#editor.toolbarPosition; 1775 if (position) { 1776 const { 1777 style 1778 } = editToolbar; 1779 const x = this.#editor._uiManager.direction === "ltr" ? 1 - position[0] : position[0]; 1780 style.insetInlineEnd = `${100 * x}%`; 1781 style.top = `calc(${100 * position[1]}% + var(--editor-toolbar-vert-offset))`; 1782 } 1783 return editToolbar; 1784 } 1785 get div() { 1786 return this.#toolbar; 1787 } 1788 static #pointerDown(e) { 1789 e.stopPropagation(); 1790 } 1791 #focusIn(e) { 1792 this.#editor._focusEventsAllowed = false; 1793 stopEvent(e); 1794 } 1795 #focusOut(e) { 1796 this.#editor._focusEventsAllowed = true; 1797 stopEvent(e); 1798 } 1799 #addListenersToElement(element) { 1800 const signal = this.#editor._uiManager._signal; 1801 if (!(signal instanceof AbortSignal) || signal.aborted) { 1802 return false; 1803 } 1804 element.addEventListener("focusin", this.#focusIn.bind(this), { 1805 capture: true, 1806 signal 1807 }); 1808 element.addEventListener("focusout", this.#focusOut.bind(this), { 1809 capture: true, 1810 signal 1811 }); 1812 element.addEventListener("contextmenu", noContextMenu, { 1813 signal 1814 }); 1815 return true; 1816 } 1817 hide() { 1818 this.#toolbar.classList.add("hidden"); 1819 this.#colorPicker?.hideDropdown(); 1820 } 1821 show() { 1822 this.#toolbar.classList.remove("hidden"); 1823 this.#altText?.shown(); 1824 this.#comment?.shown(); 1825 } 1826 addDeleteButton() { 1827 const { 1828 editorType, 1829 _uiManager 1830 } = this.#editor; 1831 const button = document.createElement("button"); 1832 button.classList.add("basic", "deleteButton"); 1833 button.tabIndex = 0; 1834 button.setAttribute("data-l10n-id", EditorToolbar.#l10nRemove[editorType]); 1835 if (this.#addListenersToElement(button)) { 1836 button.addEventListener("click", e => { 1837 _uiManager.delete(); 1838 }, { 1839 signal: _uiManager._signal 1840 }); 1841 } 1842 this.#buttons.append(button); 1843 } 1844 get #divider() { 1845 const divider = document.createElement("div"); 1846 divider.className = "divider"; 1847 return divider; 1848 } 1849 async addAltText(altText) { 1850 const button = await altText.render(); 1851 this.#addListenersToElement(button); 1852 this.#buttons.append(button, this.#divider); 1853 this.#altText = altText; 1854 } 1855 addComment(comment, beforeElement = null) { 1856 if (this.#comment) { 1857 return; 1858 } 1859 const button = comment.renderForToolbar(); 1860 if (!button) { 1861 return; 1862 } 1863 this.#addListenersToElement(button); 1864 const divider = this.#commentButtonDivider = this.#divider; 1865 if (!beforeElement) { 1866 this.#buttons.append(button, divider); 1867 } else { 1868 this.#buttons.insertBefore(button, beforeElement); 1869 this.#buttons.insertBefore(divider, beforeElement); 1870 } 1871 this.#comment = comment; 1872 comment.toolbar = this; 1873 } 1874 addColorPicker(colorPicker) { 1875 if (this.#colorPicker) { 1876 return; 1877 } 1878 this.#colorPicker = colorPicker; 1879 const button = colorPicker.renderButton(); 1880 this.#addListenersToElement(button); 1881 this.#buttons.append(button, this.#divider); 1882 } 1883 async addEditSignatureButton(signatureManager) { 1884 const button = this.#signatureDescriptionButton = await signatureManager.renderEditButton(this.#editor); 1885 this.#addListenersToElement(button); 1886 this.#buttons.append(button, this.#divider); 1887 } 1888 removeButton(name) { 1889 switch (name) { 1890 case "comment": 1891 this.#comment?.removeToolbarCommentButton(); 1892 this.#comment = null; 1893 this.#commentButtonDivider?.remove(); 1894 this.#commentButtonDivider = null; 1895 break; 1896 } 1897 } 1898 async addButton(name, tool) { 1899 switch (name) { 1900 case "colorPicker": 1901 if (tool) { 1902 this.addColorPicker(tool); 1903 } 1904 break; 1905 case "altText": 1906 if (tool) { 1907 await this.addAltText(tool); 1908 } 1909 break; 1910 case "editSignature": 1911 if (tool) { 1912 await this.addEditSignatureButton(tool); 1913 } 1914 break; 1915 case "delete": 1916 this.addDeleteButton(); 1917 break; 1918 case "comment": 1919 if (tool) { 1920 this.addComment(tool); 1921 } 1922 break; 1923 } 1924 } 1925 async addButtonBefore(name, tool, beforeSelector) { 1926 if (!tool && name === "comment") { 1927 return; 1928 } 1929 const beforeElement = this.#buttons.querySelector(beforeSelector); 1930 if (!beforeElement) { 1931 return; 1932 } 1933 if (name === "comment") { 1934 this.addComment(tool, beforeElement); 1935 } 1936 } 1937 updateEditSignatureButton(description) { 1938 if (this.#signatureDescriptionButton) { 1939 this.#signatureDescriptionButton.title = description; 1940 } 1941 } 1942 remove() { 1943 this.#toolbar.remove(); 1944 this.#colorPicker?.destroy(); 1945 this.#colorPicker = null; 1946 } 1947} 1948class FloatingToolbar { 1949 #buttons = null; 1950 #toolbar = null; 1951 #uiManager; 1952 constructor(uiManager) { 1953 this.#uiManager = uiManager; 1954 } 1955 #render() { 1956 const editToolbar = this.#toolbar = document.createElement("div"); 1957 editToolbar.className = "editToolbar"; 1958 editToolbar.setAttribute("role", "toolbar"); 1959
vendor: 2,469 bytes, lines 1959-2037
1959const signal = this.#uiManager._signal; 1960 if (signal instanceof AbortSignal && !signal.aborted) { 1961 editToolbar.addEventListener("contextmenu", noContextMenu, { 1962 signal 1963 }); 1964 } 1965 const buttons = this.#buttons = document.createElement("div"); 1966 buttons.className = "buttons"; 1967 editToolbar.append(buttons); 1968 if (this.#uiManager.hasCommentManager()) { 1969 this.#makeButton("commentButton", `pdfjs-comment-floating-button`, "pdfjs-comment-floating-button-label", () => { 1970 this.#uiManager.commentSelection("floating_button"); 1971 }); 1972 } 1973 this.#makeButton("highlightButton", `pdfjs-highlight-floating-button1`, "pdfjs-highlight-floating-button-label", () => { 1974 this.#uiManager.highlightSelection("floating_button"); 1975 }); 1976 return editToolbar; 1977 } 1978 #getLastPoint(boxes, isLTR) { 1979 let lastY = 0; 1980 let lastX = 0; 1981 for (const box of boxes) { 1982 const y = box.y + box.height; 1983 if (y < lastY) { 1984 continue; 1985 } 1986 const x = box.x + (isLTR ? box.width : 0); 1987 if (y > lastY) { 1988 lastX = x; 1989 lastY = y; 1990 continue; 1991 } 1992 if (isLTR) { 1993 if (x > lastX) { 1994 lastX = x; 1995 } 1996 } else if (x < lastX) { 1997 lastX = x; 1998 } 1999 } 2000 return [isLTR ? 1 - lastX : lastX, lastY]; 2001 } 2002 show(parent, boxes, isLTR) { 2003 const [x, y] = this.#getLastPoint(boxes, isLTR); 2004 const { 2005 style 2006 } = this.#toolbar ||= this.#render(); 2007 parent.append(this.#toolbar); 2008 style.insetInlineEnd = `${100 * x}%`; 2009 style.top = `calc(${100 * y}% + var(--editor-toolbar-vert-offset))`; 2010 } 2011 hide() { 2012 this.#toolbar.remove(); 2013 } 2014 #makeButton(buttonClass, l10nId, labelL10nId, clickHandler) { 2015 const button = document.createElement("button"); 2016 button.classList.add("basic", buttonClass); 2017 button.tabIndex = 0; 2018 button.setAttribute("data-l10n-id", l10nId); 2019 const span = document.createElement("span"); 2020 button.append(span); 2021 span.className = "visuallyHidden"; 2022 span.setAttribute("data-l10n-id", labelL10nId); 2023 const signal = this.#uiManager._signal; 2024 if (signal instanceof AbortSignal && !signal.aborted) { 2025 button.addEventListener("contextmenu", noContextMenu, { 2026 signal 2027 }); 2028 button.addEventListener("click", clickHandler, { 2029 signal 2030 }); 2031 } 2032 this.#buttons.append(button); 2033 } 2034} 2035 2036;// ./src/shared/internal_evt.js 2037const INTERNAL_EVT = "068f09d6-5b37-4f77-a97a-f8ff4f253b68
vendor: 12,463 bytes, lines 2037-2517
2037"; 2038const internalOpt = Object.freeze({ 2039 internal: INTERNAL_EVT 2040}); 2041 2042;// ./src/display/editor/tools.js 2043 2044 2045 2046 2047function bindEvents(obj, element, names) { 2048 for (const name of names) { 2049 element.addEventListener(name, obj[name].bind(obj)); 2050 } 2051} 2052class CurrentPointers { 2053 static #pointerId = NaN; 2054 static #pointerIds = null; 2055 static #moveTimestamp = NaN; 2056 static #pointerType = null; 2057 static initializeAndAddPointerId(pointerId) { 2058 (CurrentPointers.#pointerIds ||= new Set()).add(pointerId); 2059 } 2060 static setPointer(pointerType, pointerId) { 2061 CurrentPointers.#pointerId ||= pointerId; 2062 CurrentPointers.#pointerType ??= pointerType; 2063 } 2064 static setTimeStamp(timeStamp) { 2065 CurrentPointers.#moveTimestamp = timeStamp; 2066 } 2067 static isSamePointerId(pointerId) { 2068 return CurrentPointers.#pointerId === pointerId; 2069 } 2070 static isSamePointerIdOrRemove(pointerId) { 2071 if (CurrentPointers.#pointerId === pointerId) { 2072 return true; 2073 } 2074 CurrentPointers.#pointerIds?.delete(pointerId); 2075 return false; 2076 } 2077 static isSamePointerType(pointerType) { 2078 return CurrentPointers.#pointerType === pointerType; 2079 } 2080 static isInitializedAndDifferentPointerType(pointerType) { 2081 return CurrentPointers.#pointerType !== null && !CurrentPointers.isSamePointerType(pointerType); 2082 } 2083 static isSameTimeStamp(timeStamp) { 2084 return CurrentPointers.#moveTimestamp === timeStamp; 2085 } 2086 static isUsingMultiplePointers() { 2087 return CurrentPointers.#pointerIds?.size >= 1; 2088 } 2089 static clearPointerType() { 2090 CurrentPointers.#pointerType = null; 2091 } 2092 static clearPointerIds() { 2093 CurrentPointers.#pointerId = NaN; 2094 CurrentPointers.#pointerIds = null; 2095 } 2096 static clearTimeStamp() { 2097 CurrentPointers.#moveTimestamp = NaN; 2098 } 2099} 2100class IdManager { 2101 #id = 0; 2102 get id() { 2103 return `${AnnotationEditorPrefix}${this.#id++}`; 2104 } 2105} 2106class ImageManager { 2107 #baseId = getUuid(); 2108 #id = 0; 2109 #cache = null; 2110 static get _isSVGFittingCanvas() { 2111 const svg = `data:image/svg+xml;charset=UTF-8,<svg viewBox="0 0 1 1" width="1" height="1" xmlns="${SVG_NS}"><rect width="1" height="1" style="fill:red;"/></svg>`; 2112 const canvas = new OffscreenCanvas(1, 3); 2113 const ctx = canvas.getContext("2d", { 2114 willReadFrequently: true 2115 }); 2116 const image = new Image(); 2117 image.src = svg; 2118 const promise = image.decode().then(() => { 2119 ctx.drawImage(image, 0, 0, 1, 1, 0, 0, 1, 3); 2120 return new Uint32Array(ctx.getImageData(0, 0, 1, 1).data.buffer)[0] === 0; 2121 }); 2122 return shadow(this, "_isSVGFittingCanvas", promise); 2123 } 2124 async #get(key, rawData) { 2125 this.#cache ||= new Map(); 2126 let data = this.#cache.get(key); 2127 if (data === null) { 2128 return null; 2129 } 2130 if (data?.bitmap) { 2131 data.refCounter += 1; 2132 return data; 2133 } 2134 try { 2135 data ||= { 2136 bitmap: null, 2137 id: `image_${this.#baseId}_${this.#id++}`, 2138 refCounter: 0, 2139 isSvg: false 2140 }; 2141 let image; 2142 if (typeof rawData === "string") { 2143 data.url = rawData; 2144 image = await fetchData(rawData, "blob"); 2145 } else if (rawData instanceof File) { 2146 image = data.file = rawData; 2147 } else if (rawData instanceof Blob) { 2148 image = rawData; 2149 } 2150 if (image.type === "image/svg+xml") { 2151 const mustRemoveAspectRatioPromise = ImageManager._isSVGFittingCanvas; 2152 const fileReader = new FileReader(); 2153 const imageElement = new Image(); 2154 const imagePromise = new Promise((resolve, reject) => { 2155 imageElement.onload = () => { 2156 data.bitmap = imageElement; 2157 data.isSvg = true; 2158 resolve(); 2159 }; 2160 fileReader.onload = async () => { 2161 const url = data.svgUrl = fileReader.result; 2162 imageElement.src = (await mustRemoveAspectRatioPromise) ? `${url}#svgView(preserveAspectRatio(none))` : url; 2163 }; 2164 imageElement.onerror = fileReader.onerror = reject; 2165 }); 2166 fileReader.readAsDataURL(image); 2167 await imagePromise; 2168 } else { 2169 data.bitmap = await createImageBitmap(image); 2170 } 2171 data.refCounter = 1; 2172 } catch (e) { 2173 warn(e); 2174 data = null; 2175 } 2176 this.#cache.set(key, data); 2177 if (data) { 2178 this.#cache.set(data.id, data); 2179 } 2180 return data; 2181 } 2182 async getFromFile(file) { 2183 const { 2184 lastModified, 2185 name, 2186 size, 2187 type 2188 } = file; 2189 return this.#get(`${lastModified}_${name}_${size}_${type}`, file); 2190 } 2191 async getFromUrl(url) { 2192 return this.#get(url, url); 2193 } 2194 async getFromBlob(id, blobPromise) { 2195 const blob = await blobPromise; 2196 return this.#get(id, blob); 2197 } 2198 async getFromId(id) { 2199 this.#cache ||= new Map(); 2200 const data = this.#cache.get(id); 2201 if (!data) { 2202 return null; 2203 } 2204 if (data.bitmap) { 2205 data.refCounter += 1; 2206 return data; 2207 } 2208 if (data.file) { 2209 return this.getFromFile(data.file); 2210 } 2211 if (data.blobPromise) { 2212 const { 2213 blobPromise 2214 } = data; 2215 delete data.blobPromise; 2216 return this.getFromBlob(data.id, blobPromise); 2217 } 2218 return this.getFromUrl(data.url); 2219 } 2220 getFromCanvas(id, canvas) { 2221 this.#cache ||= new Map(); 2222 let data = this.#cache.get(id); 2223 if (data?.bitmap) { 2224 data.refCounter += 1; 2225 return data; 2226 } 2227 const offscreen = new OffscreenCanvas(canvas.width, canvas.height); 2228 const ctx = offscreen.getContext("2d"); 2229 ctx.drawImage(canvas, 0, 0); 2230 data = { 2231 bitmap: offscreen.transferToImageBitmap(), 2232 id: `image_${this.#baseId}_${this.#id++}`, 2233 refCounter: 1, 2234 isSvg: false 2235 }; 2236 this.#cache.set(id, data); 2237 this.#cache.set(data.id, data); 2238 return data; 2239 } 2240 getSvgUrl(id) { 2241 const data = this.#cache.get(id); 2242 if (!data?.isSvg) { 2243 return null; 2244 } 2245 return data.svgUrl; 2246 } 2247 deleteId(id) { 2248 this.#cache ||= new Map(); 2249 const data = this.#cache.get(id); 2250 if (!data) { 2251 return; 2252 } 2253 data.refCounter -= 1; 2254 if (data.refCounter !== 0) { 2255 return; 2256 } 2257 const { 2258 bitmap 2259 } = data; 2260 if (!data.url && !data.file) { 2261 const canvas = new OffscreenCanvas(bitmap.width, bitmap.height); 2262 const ctx = canvas.getContext("bitmaprenderer"); 2263 ctx.transferFromImageBitmap(bitmap); 2264 data.blobPromise = canvas.convertToBlob(); 2265 } 2266 bitmap.close?.(); 2267 data.bitmap = null; 2268 } 2269 isValidId(id) { 2270 return id.startsWith(`image_${this.#baseId}_`); 2271 } 2272} 2273class CommandManager { 2274 #commands = []; 2275 #locked = false; 2276 #maxSize; 2277 #position = -1; 2278 constructor(maxSize = 128) { 2279 this.#maxSize = maxSize; 2280 } 2281 add({ 2282 cmd, 2283 undo, 2284 post, 2285 mustExec, 2286 type = NaN, 2287 overwriteIfSameType = false, 2288 keepUndo = false 2289 }) { 2290 if (mustExec) { 2291 cmd(); 2292 } 2293 if (this.#locked) { 2294 return; 2295 } 2296 const save = { 2297 cmd, 2298 undo, 2299 post, 2300 type 2301 }; 2302 if (this.#position === -1) { 2303 if (this.#commands.length > 0) { 2304 this.#commands.length = 0; 2305 } 2306 this.#position = 0; 2307 this.#commands.push(save); 2308 return; 2309 } 2310 if (overwriteIfSameType && this.#commands[this.#position].type === type) { 2311 if (keepUndo) { 2312 save.undo = this.#commands[this.#position].undo; 2313 } 2314 this.#commands[this.#position] = save; 2315 return; 2316 } 2317 const next = this.#position + 1; 2318 if (next === this.#maxSize) { 2319 this.#commands.splice(0, 1); 2320 } else { 2321 this.#position = next; 2322 if (next < this.#commands.length) { 2323 this.#commands.splice(next); 2324 } 2325 } 2326 this.#commands.push(save); 2327 } 2328 undo() { 2329 if (this.#position === -1) { 2330 return; 2331 } 2332 this.#locked = true; 2333 const { 2334 undo, 2335 post 2336 } = this.#commands[this.#position]; 2337 undo(); 2338 post?.(); 2339 this.#locked = false; 2340 this.#position -= 1; 2341 } 2342 redo() { 2343 if (this.#position < this.#commands.length - 1) { 2344 this.#position += 1; 2345 this.#locked = true; 2346 const { 2347 cmd, 2348 post 2349 } = this.#commands[this.#position]; 2350 cmd(); 2351 post?.(); 2352 this.#locked = false; 2353 } 2354 } 2355 hasSomethingToUndo() { 2356 return this.#position !== -1; 2357 } 2358 hasSomethingToRedo() { 2359 return this.#position < this.#commands.length - 1; 2360 } 2361 cleanType(type) { 2362 if (this.#position === -1) { 2363 return; 2364 } 2365 for (let i = this.#position; i >= 0; i--) { 2366 if (this.#commands[i].type !== type) { 2367 this.#commands.splice(i + 1, this.#position - i); 2368 this.#position = i; 2369 return; 2370 } 2371 } 2372 this.#commands.length = 0; 2373 this.#position = -1; 2374 } 2375 destroy() { 2376 this.#commands = null; 2377 } 2378} 2379class KeyboardManager { 2380 static ALT = 0x1; 2381 static CTRL = 0x2; 2382 static META = 0x4; 2383 static SHIFT = 0x8; 2384 constructor(callbacks) { 2385 this.callbacks = new Map(); 2386 const { 2387 isMac 2388 } = FeatureTest.platform; 2389 for (const [keys, callback, options = {}] of callbacks) { 2390 const hasMacOverride = keys.some(k => k.startsWith("mac+")); 2391 for (const key of keys) { 2392 let shortcut = key; 2393 if (hasMacOverride) { 2394 const isMacKey = key.startsWith("mac+"); 2395 if (isMac !== isMacKey) { 2396 continue; 2397 } 2398 if (isMacKey) { 2399 shortcut = key.slice(4); 2400 } 2401 } 2402 const [keyName, modifiers] = KeyboardManager.#parseShortcut(shortcut); 2403 if (keyName === null) { 2404 continue; 2405 } 2406 this.callbacks.getOrInsertComputed(keyName, makeArr).push({ 2407 callback, 2408 options, 2409 modifiers 2410 }); 2411 } 2412 } 2413 } 2414 static #parseShortcut(value) { 2415 let keyPart = null; 2416 let modifiers = 0; 2417 for (let part of value.split("+")) { 2418 part = part.trim(); 2419 if (!part) { 2420 continue; 2421 } 2422 const upper = part.toUpperCase(); 2423 const modifier = KeyboardManager[upper]; 2424 if (modifier) { 2425 modifiers |= modifier; 2426 continue; 2427 } 2428 if (keyPart !== null) { 2429 warn(`KeyboardManager: multiple keys in shortcut "${value}"`); 2430 break; 2431 } 2432 keyPart = upper === "SPACE" ? " " : part; 2433 } 2434 if (keyPart === null) { 2435 warn(`KeyboardManager: no key found in shortcut "${value}"`); 2436 } 2437 return [keyPart, modifiers]; 2438 } 2439 static #codeToKey(code) { 2440 const match = /^(?:Key([A-Z])|(?:Digit|Numpad)(\d))$/.exec(code); 2441 if (!match) { 2442 return null; 2443 } 2444 return match[1]?.toLowerCase() ?? match[2]; 2445 } 2446 exec(self, event) { 2447 let shortcuts = this.callbacks.get(event.key); 2448 if (!shortcuts) { 2449 if (/^[a-z]$/i.test(event.key)) { 2450 return; 2451 } 2452 const fallback = KeyboardManager.#codeToKey(event.code); 2453 if (fallback === null || fallback === event.key) { 2454 return; 2455 } 2456 shortcuts = this.callbacks.get(fallback); 2457 if (!shortcuts) { 2458 return; 2459 } 2460 } 2461 const eventModifiers = (event.altKey ? KeyboardManager.ALT : 0) | (event.ctrlKey ? KeyboardManager.CTRL : 0) | (event.metaKey ? KeyboardManager.META : 0) | (event.shiftKey ? KeyboardManager.SHIFT : 0); 2462 const info = shortcuts.find(shortcut => shortcut.modifiers === eventModifiers); 2463 if (!info) { 2464 return; 2465 } 2466 const { 2467 callback, 2468 options: { 2469 bubbles = false, 2470 args = [], 2471 checker = null 2472 } 2473 } = info; 2474 if (checker && !checker(self, event)) { 2475 return; 2476 } 2477 callback.bind(self, ...args, event)(); 2478 if (!bubbles) { 2479 stopEvent(event); 2480 } 2481 } 2482} 2483class ColorManager { 2484 static _colorsMapping = new Map([["CanvasText", [0, 0, 0]], ["Canvas", [255, 255, 255]]]); 2485 get _colors() { 2486 const colors = new Map([["CanvasText", null], ["Canvas", null]]); 2487 getColorValues(colors); 2488 return shadow(this, "_colors", colors); 2489 } 2490 convert(color) { 2491 const rgb = getRGB(color); 2492 if (!window.matchMedia("(forced-colors: active)").matches) { 2493 return rgb; 2494 } 2495 for (const [name, RGB] of this._colors) { 2496 if (RGB.every((x, i) => x === rgb[i])) { 2497 return ColorManager._colorsMapping.get(name); 2498 } 2499 } 2500 return rgb; 2501 } 2502 getHexCode(name) { 2503 const rgb = this._colors.get(name); 2504 if (!rgb) { 2505 return name; 2506 } 2507 return Util.makeHexColor(...rgb); 2508 } 2509} 2510class AnnotationEditorUIManager { 2511 #abortController = new AbortController(); 2512 #activeEditor = null; 2513 #allEditableAnnotations = null; 2514 #allEditors = new Map(); 2515 #allLayers = new Map(); 2516 #savedAllLayers = null; 2517 #
vendor: 15,830 bytes, lines 2517-3017
2517altTextManager = null; 2518 #annotationStorage = null; 2519 #changedExistingAnnotations = null; 2520 #commandManager = new CommandManager(); 2521 #commentManager = null; 2522 #copyPasteAC = null; 2523 #currentDrawingSession = null; 2524 #currentPageIndex = 0; 2525 #deletedAnnotationsElementIds = new Set(); 2526 #draggingEditors = null; 2527 #editorTypes = null; 2528 #editorsToRescale = new Set(); 2529 _editorUndoBar = null; 2530 #enableHighlightFloatingButton = false; 2531 #enableUpdatedAddImage = false; 2532 #enableNewAltTextWhenAddingImage = false; 2533 #filterFactory = null; 2534 #focusMainContainerTimeoutId = null; 2535 #focusManagerAC = null; 2536 #highlightColors = null; 2537 #highlightWhenShiftUp = false; 2538 #floatingToolbar = null; 2539 #idManager = new IdManager(); 2540 #isEnabled = false; 2541 #isPointerDown = false; 2542 #isWaiting = false; 2543 #keyboardManagerAC = null; 2544 #lastActiveElement = null; 2545 #mainHighlightColorPicker = null; 2546 #missingCanvases = null; 2547 #mlManager = null; 2548 #mode = AnnotationEditorType.NONE; 2549 #selectedEditors = new Set(); 2550 #selectedTextNode = null; 2551 #signatureManager = null; 2552 #pageColors = null; 2553 #showAllStates = null; 2554 #pdfDocument = null; 2555 #previousStates = { 2556 isEditing: false, 2557 isEmpty: true, 2558 hasSomethingToUndo: false, 2559 hasSomethingToRedo: false, 2560 hasSelectedEditor: false, 2561 hasSelectedText: false 2562 }; 2563 #translation = [0, 0]; 2564 #translationTimeoutId = null; 2565 #container = null; 2566 #viewer = null; 2567 #viewerAlert = null; 2568 #updateModeCapability = null; 2569 static TRANSLATE_SMALL = 1; 2570 static TRANSLATE_BIG = 10; 2571 static get _keyboardManager() { 2572 const proto = AnnotationEditorUIManager.prototype; 2573 const arrowChecker = self => self.#container.contains(document.activeElement) && document.activeElement.tagName !== "BUTTON" && self.hasSomethingToControl(); 2574 const textInputChecker = (_self, { 2575 target: el 2576 }) => { 2577 if (el instanceof HTMLInputElement) { 2578 const { 2579 type 2580 } = el; 2581 return type !== "text" && type !== "number"; 2582 } 2583 return true; 2584 }; 2585 const small = this.TRANSLATE_SMALL; 2586 const big = this.TRANSLATE_BIG; 2587 return shadow(this, "_keyboardManager", new KeyboardManager([[["ctrl+a", "mac+meta+a"], proto.selectAll, { 2588 checker: textInputChecker 2589 }], [["ctrl+z", "mac+meta+z"], proto.undo, { 2590 checker: textInputChecker 2591 }], [["ctrl+y", "ctrl+shift+z", "mac+meta+shift+z", "ctrl+shift+Z", "mac+meta+shift+Z"], proto.redo, { 2592 checker: textInputChecker 2593 }], [["Backspace", "alt+Backspace", "ctrl+Backspace", "shift+Backspace", "mac+Backspace", "mac+alt+Backspace", "mac+ctrl+Backspace", "Delete", "ctrl+Delete", "shift+Delete", "mac+Delete"], proto.delete, { 2594 checker: textInputChecker 2595 }], [["Enter"], proto.addNewEditorFromKeyboard, { 2596 checker: (self, { 2597 target: el 2598 }) => !(el instanceof HTMLButtonElement) && self.#container.contains(el) && !self.isEnterHandled 2599 }], [["Space"], proto.addNewEditorFromKeyboard, { 2600 checker: (self, { 2601 target: el 2602 }) => !(el instanceof HTMLButtonElement) && self.#container.contains(document.activeElement) 2603 }], [["Escape"], proto.unselectAll], [["ArrowLeft"], proto.translateSelectedEditors, { 2604 args: [-small, 0], 2605 checker: arrowChecker 2606 }], [["ctrl+ArrowLeft", "mac+shift+ArrowLeft"], proto.translateSelectedEditors, { 2607 args: [-big, 0], 2608 checker: arrowChecker 2609 }], [["ArrowRight"], proto.translateSelectedEditors, { 2610 args: [small, 0], 2611 checker: arrowChecker 2612 }], [["ctrl+ArrowRight", "mac+shift+ArrowRight"], proto.translateSelectedEditors, { 2613 args: [big, 0], 2614 checker: arrowChecker 2615 }], [["ArrowUp"], proto.translateSelectedEditors, { 2616 args: [0, -small], 2617 checker: arrowChecker 2618 }], [["ctrl+ArrowUp", "mac+shift+ArrowUp"], proto.translateSelectedEditors, { 2619 args: [0, -big], 2620 checker: arrowChecker 2621 }], [["ArrowDown"], proto.translateSelectedEditors, { 2622 args: [0, small], 2623 checker: arrowChecker 2624 }], [["ctrl+ArrowDown", "mac+shift+ArrowDown"], proto.translateSelectedEditors, { 2625 args: [0, big], 2626 checker: arrowChecker 2627 }]])); 2628 } 2629 constructor(container, viewer, viewerAlert, altTextManager, commentManager, signatureManager, eventBus, pdfDocument, pageColors, highlightColors, enableHighlightFloatingButton, enableUpdatedAddImage, enableNewAltTextWhenAddingImage, mlManager, editorUndoBar, supportsPinchToZoom) { 2630 const signal = this._signal = this.#abortController.signal; 2631 this.#container = container; 2632 this.#viewer = viewer; 2633 this.#viewerAlert = viewerAlert; 2634 this.#altTextManager = altTextManager; 2635 this.#commentManager = commentManager; 2636 this.#signatureManager = signatureManager; 2637 this.#pdfDocument = pdfDocument; 2638 this._eventBus = eventBus; 2639 const evtOpts = { 2640 signal, 2641 ...internalOpt 2642 }; 2643 eventBus.on("editingaction", this.onEditingAction.bind(this), evtOpts); 2644 eventBus.on("pagechanging", this.onPageChanging.bind(this), evtOpts); 2645 eventBus.on("scalechanging", this.onScaleChanging.bind(this), evtOpts); 2646 eventBus.on("rotationchanging", this.onRotationChanging.bind(this), evtOpts); 2647 eventBus.on("setpreference", this.onSetPreference.bind(this), evtOpts); 2648 eventBus.on("switchannotationeditorparams", evt => this.updateParams(evt.type, evt.value), evtOpts); 2649 window.addEventListener("pointerdown", () => { 2650 this.#isPointerDown = true; 2651 }, { 2652 capture: true, 2653 signal 2654 }); 2655 window.addEventListener("pointerup", () => { 2656 this.#isPointerDown = false; 2657 }, { 2658 capture: true, 2659 signal 2660 }); 2661 window.addEventListener("beforeunload", this.#beforeUnload.bind(this), { 2662 capture: true, 2663 signal 2664 }); 2665 this.#addSelectionListener(); 2666 this.#addDragAndDropListeners(); 2667 this.#addKeyboardManager(); 2668 this.#annotationStorage = pdfDocument.annotationStorage; 2669 this.#filterFactory = pdfDocument.filterFactory; 2670 this.#pageColors = pageColors; 2671 this.#highlightColors = highlightColors || null; 2672 this.#enableHighlightFloatingButton = enableHighlightFloatingButton; 2673 this.#enableUpdatedAddImage = enableUpdatedAddImage; 2674 this.#enableNewAltTextWhenAddingImage = enableNewAltTextWhenAddingImage; 2675 this.#mlManager = mlManager || null; 2676 this.viewParameters = { 2677 realScale: PixelsPerInch.PDF_TO_CSS_UNITS, 2678 rotation: 0 2679 }; 2680 this.isShiftKeyDown = false; 2681 this._editorUndoBar = editorUndoBar || null; 2682 this._supportsPinchToZoom = supportsPinchToZoom !== false; 2683 commentManager?.setSidebarUiManager(this); 2684 } 2685 destroy() { 2686 this.#updateModeCapability?.resolve(); 2687 this.#updateModeCapability = null; 2688 this.#abortController?.abort(); 2689 this.#abortController = null; 2690 this._signal = null; 2691 for (const layer of this.#allLayers.values()) { 2692 layer.destroy(); 2693 } 2694 this.#allLayers.clear(); 2695 this.#allEditors.clear(); 2696 this.#editorsToRescale.clear(); 2697 this.#missingCanvases?.clear(); 2698 this.#activeEditor = null; 2699 this.#selectedEditors.clear(); 2700 this.#commandManager.destroy(); 2701 this.#altTextManager?.destroy(); 2702 this.#commentManager?.destroy(); 2703 this.#signatureManager?.destroy(); 2704 this.#floatingToolbar?.hide(); 2705 this.#floatingToolbar = null; 2706 this.#mainHighlightColorPicker?.destroy(); 2707 this.#mainHighlightColorPicker = null; 2708 this.#allEditableAnnotations = null; 2709 if (this.#focusMainContainerTimeoutId) { 2710 clearTimeout(this.#focusMainContainerTimeoutId); 2711 this.#focusMainContainerTimeoutId = null; 2712 } 2713 if (this.#translationTimeoutId) { 2714 clearTimeout(this.#translationTimeoutId); 2715 this.#translationTimeoutId = null; 2716 } 2717 this._editorUndoBar?.destroy(); 2718 this.#pdfDocument = null; 2719 } 2720 combinedSignal(ac) { 2721 return AbortSignal.any([this._signal, ac.signal]); 2722 } 2723 get mlManager() { 2724 return this.#mlManager; 2725 } 2726 get useNewAltTextFlow() { 2727 return this.#enableUpdatedAddImage; 2728 } 2729 get useNewAltTextWhenAddingImage() { 2730 return this.#enableNewAltTextWhenAddingImage; 2731 } 2732 get hcmFilter() { 2733 return shadow(this, "hcmFilter", this.#pageColors ? this.#filterFactory.addHCMFilter(this.#pageColors.foreground, this.#pageColors.background) : "none"); 2734 } 2735 get direction() { 2736 return shadow(this, "direction", getComputedStyle(this.#container).direction); 2737 } 2738 get _highlightColors() { 2739 return shadow(this, "_highlightColors", this.#highlightColors ? new Map(this.#highlightColors.split(",").map(pair => { 2740 pair = pair.split("=").map(x => x.trim()); 2741 pair[1] = pair[1].toUpperCase(); 2742 return pair; 2743 })) : null); 2744 } 2745 get highlightColors() { 2746 const { 2747 _highlightColors 2748 } = this; 2749 if (!_highlightColors) { 2750 return shadow(this, "highlightColors", null); 2751 } 2752 const map = new Map(); 2753 const hasHCM = !!this.#pageColors; 2754 for (const [name, color] of _highlightColors) { 2755 const isNameForHCM = name.endsWith("_HCM"); 2756 if (hasHCM && isNameForHCM) { 2757 map.set(name.replace("_HCM", ""), color); 2758 continue; 2759 } 2760 if (!hasHCM && !isNameForHCM) { 2761 map.set(name, color); 2762 } 2763 } 2764 return shadow(this, "highlightColors", map); 2765 } 2766 get highlightColorNames() { 2767 return shadow(this, "highlightColorNames", this.highlightColors ? new Map(Array.from(this.highlightColors, e => e.reverse())) : null); 2768 } 2769 getNonHCMColor(color) { 2770 if (!this._highlightColors) { 2771 return color; 2772 } 2773 const colorName = this.highlightColorNames.get(color); 2774 return this._highlightColors.get(colorName) || color; 2775 } 2776 getNonHCMColorName(color) { 2777 return this.highlightColorNames.get(color) || color; 2778 } 2779 setCurrentDrawingSession(layer) { 2780 if (layer) { 2781 this.unselectAll(); 2782 this.disableUserSelect(true); 2783 } else { 2784 this.disableUserSelect(false); 2785 } 2786 this.#currentDrawingSession = layer; 2787 } 2788 setMainHighlightColorPicker(colorPicker) { 2789 this.#mainHighlightColorPicker = colorPicker; 2790 } 2791 editAltText(editor, firstTime = false) { 2792 this.#altTextManager?.editAltText(this, editor, firstTime); 2793 } 2794 hasCommentManager() { 2795 return !!this.#commentManager; 2796 } 2797 editComment(editor, posX, posY, options) { 2798 this.#commentManager?.showDialog(this, editor, posX, posY, options); 2799 } 2800 selectComment(pageIndex, uid) { 2801 const layer = this.#allLayers.get(pageIndex); 2802 const editor = layer?.getEditorByUID(uid); 2803 editor?.toggleComment(true, true); 2804 } 2805 updateComment(editor) { 2806 this.#commentManager?.updateComment(editor.getData()); 2807 } 2808 updatePopupColor(editor) { 2809 this.#commentManager?.updatePopupColor(editor); 2810 } 2811 removeComment(editor) { 2812 this.#commentManager?.removeComments([editor.uid]); 2813 } 2814 deleteComment(editor, savedData) { 2815 const undo = () => { 2816 editor.comment = savedData; 2817 }; 2818 const cmd = () => { 2819 this._editorUndoBar?.show(undo, "comment"); 2820 this.toggleComment(null); 2821 editor.comment = null; 2822 }; 2823 this.addCommands({ 2824 cmd, 2825 undo, 2826 mustExec: true 2827 }); 2828 } 2829 toggleComment(editor, isSelected, visibility = undefined) { 2830 this.#commentManager?.toggleCommentPopup(editor, isSelected, visibility); 2831 } 2832 makeCommentColor(color, opacity) { 2833 return color && this.#commentManager?.makeCommentColor(color, opacity) || null; 2834 } 2835 getCommentDialogElement() { 2836 return this.#commentManager?.dialogElement || null; 2837 } 2838 async waitForEditorsRendered(pageNumber) { 2839 if (this.#allLayers.has(pageNumber - 1)) { 2840 return; 2841 } 2842 const { 2843 resolve, 2844 promise 2845 } = Promise.withResolvers(); 2846 const onEditorsRendered = evt => { 2847 if (evt.pageNumber === pageNumber) { 2848 this._eventBus.off("editorsrendered", onEditorsRendered); 2849 resolve(); 2850 } 2851 }; 2852 this._eventBus.on("editorsrendered", onEditorsRendered, internalOpt); 2853 await promise; 2854 } 2855 getSignature(editor) { 2856 this.#signatureManager?.getSignature({ 2857 uiManager: this, 2858 editor 2859 }); 2860 } 2861 get signatureManager() { 2862 return this.#signatureManager; 2863 } 2864 switchToMode(mode, callback) { 2865 this._eventBus.on("annotationeditormodechanged", callback, { 2866 once: true, 2867 signal: this._signal, 2868 ...internalOpt 2869 }); 2870 this._eventBus.dispatch("showannotationeditorui", { 2871 source: this, 2872 mode 2873 }); 2874 } 2875 setPreference(name, value) { 2876 this._eventBus.dispatch("setpreference", { 2877 source: this, 2878 name, 2879 value 2880 }); 2881 } 2882 onSetPreference({ 2883 name, 2884 value 2885 }) { 2886 switch (name) { 2887 case "enableNewAltTextWhenAddingImage": 2888 this.#enableNewAltTextWhenAddingImage = value; 2889 break; 2890 } 2891 } 2892 onPageChanging({ 2893 pageNumber 2894 }) { 2895 this.#currentPageIndex = pageNumber - 1; 2896 } 2897 deletePage(id) { 2898 for (const editor of this.getEditors(id)) { 2899 editor.remove(); 2900 } 2901 this.#allLayers.delete(id); 2902 if (this.#currentPageIndex === id) { 2903 this.#currentPageIndex = 0; 2904 } 2905 } 2906 focusMainContainer() { 2907 this.#container.focus(); 2908 } 2909 findParent(x, y) { 2910 for (const layer of this.#allLayers.values()) { 2911 const { 2912 x: layerX, 2913 y: layerY, 2914 width, 2915 height 2916 } = layer.div.getBoundingClientRect(); 2917 if (x >= layerX && x <= layerX + width && y >= layerY && y <= layerY + height) { 2918 return layer; 2919 } 2920 } 2921 return null; 2922 } 2923 disableUserSelect(value = false) { 2924 this.#viewer.classList.toggle("noUserSelect", value); 2925 } 2926 addShouldRescale(editor) { 2927 this.#editorsToRescale.add(editor); 2928 } 2929 removeShouldRescale(editor) { 2930 this.#editorsToRescale.delete(editor); 2931 } 2932 onScaleChanging({ 2933 scale 2934 }) { 2935 this.commitOrRemove(); 2936 this.viewParameters.realScale = scale * PixelsPerInch.PDF_TO_CSS_UNITS; 2937 for (const editor of this.#editorsToRescale) { 2938 editor.onScaleChanging(); 2939 } 2940 this.#currentDrawingSession?.onScaleChanging(); 2941 } 2942 onRotationChanging({ 2943 pagesRotation 2944 }) { 2945 this.commitOrRemove(); 2946 this.viewParameters.rotation = pagesRotation; 2947 } 2948 #getAnchorElementForSelection({ 2949 anchorNode 2950 }) { 2951 return anchorNode.nodeType === Node.TEXT_NODE ? anchorNode.parentElement : anchorNode; 2952 } 2953 #getLayerForTextLayer(textLayer) { 2954 const { 2955 currentLayer 2956 } = this; 2957 if (currentLayer.hasTextLayer(textLayer)) { 2958 return currentLayer; 2959 } 2960 for (const layer of this.#allLayers.values()) { 2961 if (layer.hasTextLayer(textLayer)) { 2962 return layer; 2963 } 2964 } 2965 return null; 2966 } 2967 highlightSelection(methodOfCreation = "", comment = false) { 2968 const selection = document.getSelection(); 2969 if (!selection || selection.isCollapsed) { 2970 return; 2971 } 2972 const { 2973 anchorNode, 2974 anchorOffset, 2975 focusNode, 2976 focusOffset 2977 } = selection; 2978 const text = selection.toString(); 2979 const anchorElement = this.#getAnchorElementForSelection(selection); 2980 const textLayer = anchorElement.closest(".textLayer"); 2981 const boxes = this.getSelectionBoxes(textLayer); 2982 if (!boxes) { 2983 return; 2984 } 2985 selection.empty(); 2986 const layer = this.#getLayerForTextLayer(textLayer); 2987 const isNoneMode = this.#mode === AnnotationEditorType.NONE; 2988 const callback = () => { 2989 const editor = layer?.createAndAddNewEditor({ 2990 x: 0, 2991 y: 0 2992 }, false, { 2993 methodOfCreation, 2994 boxes, 2995 anchorNode, 2996 anchorOffset, 2997 focusNode, 2998 focusOffset, 2999 text 3000 }); 3001 if (isNoneMode) { 3002 this.showAllEditors("highlight", true, true); 3003 } 3004 if (comment) { 3005 editor?.editComment(); 3006 } 3007 }; 3008 if (isNoneMode) { 3009 this.switchToMode(AnnotationEditorType.HIGHLIGHT, callback); 3010 return; 3011 } 3012 callback(); 3013 } 3014 commentSelection(methodOfCreation = "") { 3015 this.highlightSelection(methodOfCreation, true); 3016 } 3017 #beforeUnload(e
vendor: 10,355 bytes, lines 3017-3389
3017) { 3018 this.commitOrRemove(); 3019 this.currentLayer?.endDrawingSession(false); 3020 } 3021 #displayFloatingToolbar() { 3022 const selection = document.getSelection(); 3023 if (!selection || selection.isCollapsed) { 3024 return; 3025 } 3026 const anchorElement = this.#getAnchorElementForSelection(selection); 3027 const textLayer = anchorElement.closest(".textLayer"); 3028 const boxes = this.getSelectionBoxes(textLayer); 3029 if (!boxes) { 3030 return; 3031 } 3032 this.#floatingToolbar ||= new FloatingToolbar(this); 3033 this.#floatingToolbar.show(textLayer, boxes, this.direction === "ltr"); 3034 } 3035 getAndRemoveDataFromAnnotationStorage(annotationId) { 3036 if (!this.#annotationStorage) { 3037 return null; 3038 } 3039 const key = `${AnnotationEditorPrefix}${annotationId}`; 3040 const storedValue = this.#annotationStorage.getRawValue(key); 3041 if (storedValue) { 3042 this.#annotationStorage.remove(key); 3043 } 3044 return storedValue; 3045 } 3046 addToAnnotationStorage(editor) { 3047 if (!editor.isEmpty() && this.#annotationStorage && !this.#annotationStorage.has(editor.id)) { 3048 this.#annotationStorage.setValue(editor.id, editor); 3049 } 3050 } 3051 a11yAlert(messageId, args = null) { 3052 const viewerAlert = this.#viewerAlert; 3053 if (!viewerAlert) { 3054 return; 3055 } 3056 viewerAlert.setAttribute("data-l10n-id", messageId); 3057 if (args) { 3058 viewerAlert.setAttribute("data-l10n-args", JSON.stringify(args)); 3059 } else { 3060 viewerAlert.removeAttribute("data-l10n-args"); 3061 } 3062 } 3063 #selectionChange() { 3064 const selection = document.getSelection(); 3065 if (!selection || selection.isCollapsed) { 3066 if (this.#selectedTextNode) { 3067 this.#floatingToolbar?.hide(); 3068 this.#selectedTextNode = null; 3069 this.#dispatchUpdateStates({ 3070 hasSelectedText: false 3071 }); 3072 } 3073 return; 3074 } 3075 const { 3076 anchorNode 3077 } = selection; 3078 if (anchorNode === this.#selectedTextNode) { 3079 return; 3080 } 3081 const anchorElement = this.#getAnchorElementForSelection(selection); 3082 const textLayer = anchorElement.closest(".textLayer"); 3083 if (!textLayer) { 3084 if (this.#selectedTextNode) { 3085 this.#floatingToolbar?.hide(); 3086 this.#selectedTextNode = null; 3087 this.#dispatchUpdateStates({ 3088 hasSelectedText: false 3089 }); 3090 } 3091 return; 3092 } 3093 this.#floatingToolbar?.hide(); 3094 this.#selectedTextNode = anchorNode; 3095 this.#dispatchUpdateStates({ 3096 hasSelectedText: true 3097 }); 3098 if (this.#mode !== AnnotationEditorType.HIGHLIGHT && this.#mode !== AnnotationEditorType.NONE) { 3099 return; 3100 } 3101 if (this.#mode === AnnotationEditorType.HIGHLIGHT) { 3102 this.showAllEditors("highlight", true, true); 3103 } 3104 this.#highlightWhenShiftUp = this.isShiftKeyDown; 3105 if (!this.isShiftKeyDown) { 3106 const activeLayer = this.#mode === AnnotationEditorType.HIGHLIGHT ? this.#getLayerForTextLayer(textLayer) : null; 3107 activeLayer?.toggleDrawing(); 3108 if (this.#isPointerDown) { 3109 const ac = new AbortController(); 3110 const signal = this.combinedSignal(ac); 3111 const pointerup = e => { 3112 if (e.type === "pointerup" && e.button !== 0) { 3113 return; 3114 } 3115 ac.abort(); 3116 activeLayer?.toggleDrawing(true); 3117 if (e.type === "pointerup") { 3118 this.#onSelectEnd("main_toolbar"); 3119 } 3120 }; 3121 window.addEventListener("pointerup", pointerup, { 3122 signal 3123 }); 3124 window.addEventListener("blur", pointerup, { 3125 signal 3126 }); 3127 } else { 3128 activeLayer?.toggleDrawing(true); 3129 this.#onSelectEnd("main_toolbar"); 3130 } 3131 } 3132 } 3133 #onSelectEnd(methodOfCreation = "") { 3134 if (this.#mode === AnnotationEditorType.HIGHLIGHT) { 3135 this.highlightSelection(methodOfCreation); 3136 } else if (this.#enableHighlightFloatingButton) { 3137 this.#displayFloatingToolbar(); 3138 } 3139 } 3140 #addSelectionListener() { 3141 document.addEventListener("selectionchange", this.#selectionChange.bind(this), { 3142 signal: this._signal 3143 }); 3144 } 3145 #addFocusManager() { 3146 if (this.#focusManagerAC) { 3147 return; 3148 } 3149 this.#focusManagerAC = new AbortController(); 3150 const signal = this.combinedSignal(this.#focusManagerAC); 3151 window.addEventListener("focus", this.focus.bind(this), { 3152 signal 3153 }); 3154 window.addEventListener("blur", this.blur.bind(this), { 3155 signal 3156 }); 3157 } 3158 #removeFocusManager() { 3159 this.#focusManagerAC?.abort(); 3160 this.#focusManagerAC = null; 3161 } 3162 blur() { 3163 this.isShiftKeyDown = false; 3164 if (this.#highlightWhenShiftUp) { 3165 this.#highlightWhenShiftUp = false; 3166 this.#onSelectEnd("main_toolbar"); 3167 } 3168 if (!this.hasSelection) { 3169 return; 3170 } 3171 const { 3172 activeElement 3173 } = document; 3174 for (const editor of this.#selectedEditors) { 3175 if (editor.div.contains(activeElement)) { 3176 this.#lastActiveElement = [editor, activeElement]; 3177 editor._focusEventsAllowed = false; 3178 break; 3179 } 3180 } 3181 } 3182 focus() { 3183 if (!this.#lastActiveElement) { 3184 return; 3185 } 3186 const [lastEditor, lastActiveElement] = this.#lastActiveElement; 3187 this.#lastActiveElement = null; 3188 lastActiveElement.addEventListener("focusin", () => { 3189 lastEditor._focusEventsAllowed = true; 3190 }, { 3191 once: true, 3192 signal: this._signal 3193 }); 3194 lastActiveElement.focus(); 3195 } 3196 #addKeyboardManager() { 3197 if (this.#keyboardManagerAC) { 3198 return; 3199 } 3200 this.#keyboardManagerAC = new AbortController(); 3201 const signal = this.combinedSignal(this.#keyboardManagerAC); 3202 window.addEventListener("keydown", this.keydown.bind(this), { 3203 signal 3204 }); 3205 window.addEventListener("keyup", this.keyup.bind(this), { 3206 signal 3207 }); 3208 } 3209 #removeKeyboardManager() { 3210 this.#keyboardManagerAC?.abort(); 3211 this.#keyboardManagerAC = null; 3212 } 3213 #addCopyPasteListeners() { 3214 if (this.#copyPasteAC) { 3215 return; 3216 } 3217 this.#copyPasteAC = new AbortController(); 3218 const signal = this.combinedSignal(this.#copyPasteAC); 3219 document.addEventListener("copy", this.copy.bind(this), { 3220 signal 3221 }); 3222 document.addEventListener("cut", this.cut.bind(this), { 3223 signal 3224 }); 3225 document.addEventListener("paste", this.paste.bind(this), { 3226 signal 3227 }); 3228 } 3229 #removeCopyPasteListeners() { 3230 this.#copyPasteAC?.abort(); 3231 this.#copyPasteAC = null; 3232 } 3233 #addDragAndDropListeners() { 3234 const signal = this._signal; 3235 document.addEventListener("dragover", this.dragOver.bind(this), { 3236 signal 3237 }); 3238 document.addEventListener("drop", this.drop.bind(this), { 3239 signal 3240 }); 3241 } 3242 addEditListeners() { 3243 this.#addKeyboardManager(); 3244 this.setEditingState(true); 3245 } 3246 removeEditListeners() { 3247 this.#removeKeyboardManager(); 3248 this.setEditingState(false); 3249 } 3250 dragOver(event) { 3251 for (const { 3252 type 3253 } of event.dataTransfer.items) { 3254 for (const editorType of this.#editorTypes) { 3255 if (editorType.isHandlingMimeForPasting(type)) { 3256 event.dataTransfer.dropEffect = "copy"; 3257 event.preventDefault(); 3258 return; 3259 } 3260 } 3261 } 3262 } 3263 drop(event) { 3264 for (const item of event.dataTransfer.items) { 3265 for (const editorType of this.#editorTypes) { 3266 if (editorType.isHandlingMimeForPasting(item.type)) { 3267 editorType.paste(item, this.currentLayer); 3268 event.preventDefault(); 3269 return; 3270 } 3271 } 3272 } 3273 } 3274 copy(event) { 3275 event.preventDefault(); 3276 this.#activeEditor?.commitOrRemove(); 3277 if (!this.hasSelection) { 3278 return; 3279 } 3280 const editors = []; 3281 for (const editor of this.#selectedEditors) { 3282 const serialized = editor.serialize(true); 3283 if (serialized) { 3284 editors.push(serialized); 3285 } 3286 } 3287 if (editors.length === 0) { 3288 return; 3289 } 3290 event.clipboardData.setData("application/pdfjs", JSON.stringify(editors)); 3291 } 3292 cut(event) { 3293 this.copy(event); 3294 this.delete(); 3295 } 3296 async paste(event) { 3297 event.preventDefault(); 3298 const { 3299 clipboardData 3300 } = event; 3301 for (const item of clipboardData.items) { 3302 for (const editorType of this.#editorTypes) { 3303 if (editorType.isHandlingMimeForPasting(item.type)) { 3304 editorType.paste(item, this.currentLayer); 3305 return; 3306 } 3307 } 3308 } 3309 let data = clipboardData.getData("application/pdfjs"); 3310 if (!data) { 3311 return; 3312 } 3313 try { 3314 data = JSON.parse(data); 3315 } catch (ex) { 3316 warn(`paste: "${ex.message}".`); 3317 return; 3318 } 3319 if (!Array.isArray(data)) { 3320 return; 3321 } 3322 this.unselectAll(); 3323 const layer = this.currentLayer; 3324 try { 3325 const newEditors = []; 3326 for (const editor of data) { 3327 const deserializedEditor = await layer.deserialize(editor); 3328 if (!deserializedEditor) { 3329 return; 3330 } 3331 newEditors.push(deserializedEditor); 3332 } 3333 const cmd = () => { 3334 for (const editor of newEditors) { 3335 this.#addEditorToLayer(editor); 3336 } 3337 this.#selectEditors(newEditors); 3338 }; 3339 const undo = () => { 3340 for (const editor of newEditors) { 3341 editor.remove(); 3342 } 3343 }; 3344 this.addCommands({ 3345 cmd, 3346 undo, 3347 mustExec: true 3348 }); 3349 } catch (ex) { 3350 warn(`paste: "${ex.message}".`); 3351 } 3352 } 3353 keydown(event) { 3354 if (!this.isShiftKeyDown && event.key === "Shift") { 3355 this.isShiftKeyDown = true; 3356 } 3357 if (this.#mode !== AnnotationEditorType.NONE && !this.isEditorHandlingKeyboard) { 3358 AnnotationEditorUIManager._keyboardManager.exec(this, event); 3359 } 3360 } 3361 keyup(event) { 3362 if (this.isShiftKeyDown && event.key === "Shift") { 3363 this.isShiftKeyDown = false; 3364 if (this.#highlightWhenShiftUp) { 3365 this.#highlightWhenShiftUp = false; 3366 this.#onSelectEnd("main_toolbar"); 3367 } 3368 } 3369 } 3370 onEditingAction({ 3371 name 3372 }) { 3373 switch (name) { 3374 case "undo": 3375 case "redo": 3376 case "delete": 3377 case "selectAll": 3378 this[name](); 3379 break; 3380 case "highlightSelection": 3381 this.highlightSelection("context_menu"); 3382 break; 3383 case "commentSelection": 3384 this.commentSelection("context_menu"); 3385 break; 3386 } 3387 } 3388 updatePageIndex(oldPageIndex, newPageIndex) { 3389 for (const editor of this.
3389getEditors(oldPageIndex)) { 3390 editor.pageIndex = newPageIndex; 3391 } 3392 const layer = this.#savedAllLayers.get(oldPageIndex); 3393 if (layer) { 3394 layer.pageIndex = newPageIndex; 3395 this.#allLayers.set(newPageIndex, layer); 3396 if (this.#isEnabled) { 3397 layer.enable(); 3398 } else { 3399 layer.disable(); 3400 } 3401 } 3402 } 3403 startUpdatePages() { 3404 this.#savedAllLayers = new Map(this.#allLayers); 3405 this.#allLayers.clear(); 3406 } 3407 endUpdatePages() { 3408 this.#savedAllLayers
vendor: 25,619 bytes, lines 3408-4303
3408 = null; 3409 } 3410 clonePage(pageIndex, newPageIndex) { 3411 for (const editor of this.getEditors(pageIndex)) { 3412 const serialized = editor.serialize(editor.mode !== AnnotationEditorType.HIGHLIGHT); 3413 if (!serialized) { 3414 continue; 3415 } 3416 serialized.pageIndex = newPageIndex; 3417 serialized.id = this.getId(); 3418 serialized.isClone = true; 3419 delete serialized.popupRef; 3420 this.#annotationStorage.setValue(serialized.id, serialized); 3421 } 3422 } 3423 findClonesForPage(layer) { 3424 const promises = []; 3425 const { 3426 pageIndex 3427 } = layer; 3428 for (const [id, editor] of this.#annotationStorage) { 3429 if (editor.pageIndex === pageIndex && editor.isClone) { 3430 this.#annotationStorage.remove(id); 3431 promises.push(layer.deserialize(editor).then(deserializedEditor => { 3432 if (deserializedEditor) { 3433 deserializedEditor.isClone = true; 3434 layer.addOrRebuild(deserializedEditor); 3435 } 3436 })); 3437 } 3438 } 3439 return Promise.all(promises); 3440 } 3441 #dispatchUpdateStates(details) { 3442 const hasChanged = Object.entries(details).some(([key, value]) => this.#previousStates[key] !== value); 3443 if (hasChanged) { 3444 this._eventBus.dispatch("editingstateschanged", { 3445 source: this, 3446 details: Object.assign(this.#previousStates, details) 3447 }); 3448 if (this.#mode === AnnotationEditorType.HIGHLIGHT && details.hasSelectedEditor === false) { 3449 this.#dispatchUpdateUI([[AnnotationEditorParamsType.HIGHLIGHT_FREE, true]]); 3450 } 3451 } 3452 } 3453 #dispatchUpdateUI(details) { 3454 this._eventBus.dispatch("annotationeditorparamschanged", { 3455 source: this, 3456 details 3457 }); 3458 } 3459 setEditingState(isEditing) { 3460 if (isEditing) { 3461 this.#addFocusManager(); 3462 this.#addCopyPasteListeners(); 3463 this.#dispatchUpdateStates({ 3464 isEditing: this.#mode !== AnnotationEditorType.NONE, 3465 isEmpty: this.#isEmpty(), 3466 hasSomethingToUndo: this.#commandManager.hasSomethingToUndo(), 3467 hasSomethingToRedo: this.#commandManager.hasSomethingToRedo(), 3468 hasSelectedEditor: false 3469 }); 3470 } else { 3471 this.#removeFocusManager(); 3472 this.#removeCopyPasteListeners(); 3473 this.#dispatchUpdateStates({ 3474 isEditing: false 3475 }); 3476 this.disableUserSelect(false); 3477 } 3478 } 3479 registerEditorTypes(types) { 3480 if (this.#editorTypes) { 3481 return; 3482 } 3483 this.#editorTypes = types; 3484 for (const editorType of this.#editorTypes) { 3485 this.#dispatchUpdateUI(editorType.defaultPropertiesToUpdate); 3486 } 3487 } 3488 getId() { 3489 return this.#idManager.id; 3490 } 3491 get currentLayer() { 3492 return this.#allLayers.get(this.#currentPageIndex); 3493 } 3494 getLayer(pageIndex) { 3495 return this.#allLayers.get(pageIndex); 3496 } 3497 get currentPageIndex() { 3498 return this.#currentPageIndex; 3499 } 3500 addLayer(layer) { 3501 this.#allLayers.set(layer.pageIndex, layer); 3502 if (this.#isEnabled) { 3503 layer.enable(); 3504 } else { 3505 layer.disable(); 3506 } 3507 } 3508 removeLayer(layer) { 3509 this.#allLayers.delete(layer.pageIndex); 3510 } 3511 async updateMode(mode, editId = null, isFromUser = false, isFromKeyboard = false, mustEnterInEditMode = false, editComment = false) { 3512 if (this.#mode === mode) { 3513 return; 3514 } 3515 if (this.#updateModeCapability) { 3516 await this.#updateModeCapability.promise; 3517 if (!this.#updateModeCapability) { 3518 return; 3519 } 3520 } 3521 this.#updateModeCapability = Promise.withResolvers(); 3522 this.#currentDrawingSession?.commitOrRemove(); 3523 if (this.#mode === AnnotationEditorType.POPUP) { 3524 this.#commentManager?.hideSidebar(); 3525 } 3526 this.#commentManager?.destroyPopup(); 3527 this.#mode = mode; 3528 if (mode === AnnotationEditorType.NONE) { 3529 this.setEditingState(false); 3530 this.#disableAll(); 3531 for (const editor of this.#allEditors.values()) { 3532 editor.hideStandaloneCommentButton(); 3533 } 3534 this._editorUndoBar?.hide(); 3535 this.toggleComment(null); 3536 this.#updateModeCapability.resolve(); 3537 return; 3538 } 3539 for (const editor of this.#allEditors.values()) { 3540 editor.addStandaloneCommentButton(); 3541 } 3542 if (mode === AnnotationEditorType.SIGNATURE) { 3543 await this.#signatureManager?.loadSignatures(); 3544 } 3545 if (isFromUser) { 3546 CurrentPointers.clearPointerType(); 3547 } 3548 this.setEditingState(true); 3549 await this.#enableAll(); 3550 this.unselectAll(); 3551 for (const layer of this.#allLayers.values()) { 3552 layer.updateMode(mode); 3553 } 3554 if (mode === AnnotationEditorType.POPUP) { 3555 this.#allEditableAnnotations ||= await this.#pdfDocument.getAnnotationsByType(new Set(this.#editorTypes.map(editorClass => editorClass._editorType))); 3556 const elementIds = new Set(); 3557 const allComments = []; 3558 for (const editor of this.#allEditors.values()) { 3559 const { 3560 annotationElementId, 3561 hasComment, 3562 deleted 3563 } = editor; 3564 if (annotationElementId) { 3565 elementIds.add(annotationElementId); 3566 } 3567 if (hasComment && !deleted) { 3568 allComments.push(editor.getData()); 3569 } 3570 } 3571 for (const annotation of this.#allEditableAnnotations) { 3572 const { 3573 id, 3574 popupRef, 3575 contentsObj 3576 } = annotation; 3577 if (popupRef && contentsObj?.str && !elementIds.has(id) && !this.#deletedAnnotationsElementIds.has(id)) { 3578 allComments.push(annotation); 3579 } 3580 } 3581 this.#commentManager?.showSidebar(allComments); 3582 } 3583 if (!editId) { 3584 if (isFromKeyboard) { 3585 this.addNewEditorFromKeyboard(); 3586 } 3587 this.#updateModeCapability.resolve(); 3588 return; 3589 } 3590 for (const editor of this.#allEditors.values()) { 3591 if (editor.uid === editId) { 3592 this.setSelected(editor); 3593 if (editComment) { 3594 editor.editComment(); 3595 } else if (mustEnterInEditMode) { 3596 editor.enterInEditMode(); 3597 } else { 3598 editor.focus(); 3599 } 3600 } else { 3601 editor.unselect(); 3602 } 3603 } 3604 this.#updateModeCapability.resolve(); 3605 } 3606 addNewEditorFromKeyboard() { 3607 if (this.currentLayer.canCreateNewEmptyEditor()) { 3608 this.currentLayer.addNewEditor(); 3609 } 3610 } 3611 updateToolbar(options) { 3612 if (options.mode === this.#mode) { 3613 return; 3614 } 3615 this._eventBus.dispatch("switchannotationeditormode", { 3616 source: this, 3617 ...options 3618 }); 3619 } 3620 updateParams(type, value) { 3621 if (!this.#editorTypes) { 3622 return; 3623 } 3624 switch (type) { 3625 case AnnotationEditorParamsType.CREATE: 3626 this.currentLayer.addNewEditor(value); 3627 return; 3628 case AnnotationEditorParamsType.HIGHLIGHT_SHOW_ALL: 3629 this._eventBus.dispatch("reporttelemetry", { 3630 source: this, 3631 details: { 3632 type: "editing", 3633 data: { 3634 type: "highlight", 3635 action: "toggle_visibility" 3636 } 3637 } 3638 }); 3639 (this.#showAllStates ||= new Map()).set(type, value); 3640 this.showAllEditors("highlight", value); 3641 break; 3642 } 3643 if (this.hasSelection) { 3644 for (const editor of this.#selectedEditors) { 3645 editor.updateParams(type, value); 3646 } 3647 } else { 3648 for (const editorType of this.#editorTypes) { 3649 editorType.updateDefaultParams(type, value); 3650 } 3651 } 3652 } 3653 showAllEditors(type, visible, updateButton = false) { 3654 for (const editor of this.#allEditors.values()) { 3655 if (editor.editorType === type) { 3656 editor.show(visible); 3657 } 3658 } 3659 const state = this.#showAllStates?.get(AnnotationEditorParamsType.HIGHLIGHT_SHOW_ALL) ?? true; 3660 if (state !== visible) { 3661 this.#dispatchUpdateUI([[AnnotationEditorParamsType.HIGHLIGHT_SHOW_ALL, visible]]); 3662 } 3663 } 3664 enableWaiting(mustWait = false) { 3665 if (this.#isWaiting === mustWait) { 3666 return; 3667 } 3668 this.#isWaiting = mustWait; 3669 for (const layer of this.#allLayers.values()) { 3670 if (mustWait) { 3671 layer.disableClick(); 3672 } else { 3673 layer.enableClick(); 3674 } 3675 layer.div.classList.toggle("waiting", mustWait); 3676 } 3677 } 3678 async #enableAll() { 3679 if (!this.#isEnabled) { 3680 this.#isEnabled = true; 3681 const promises = []; 3682 for (const layer of this.#allLayers.values()) { 3683 promises.push(layer.enable()); 3684 } 3685 await Promise.all(promises); 3686 for (const editor of this.#allEditors.values()) { 3687 editor.enable(); 3688 } 3689 } 3690 } 3691 #disableAll() { 3692 this.unselectAll(); 3693 if (this.#isEnabled) { 3694 this.#isEnabled = false; 3695 for (const layer of this.#allLayers.values()) { 3696 layer.disable(); 3697 } 3698 for (const editor of this.#allEditors.values()) { 3699 editor.disable(); 3700 } 3701 } 3702 } 3703 *getEditors(pageIndex) { 3704 for (const editor of this.#allEditors.values()) { 3705 if (editor.pageIndex === pageIndex) { 3706 yield editor; 3707 } 3708 } 3709 } 3710 getEditor(id) { 3711 return this.#allEditors.get(id); 3712 } 3713 addEditor(editor) { 3714 this.#allEditors.set(editor.id, editor); 3715 } 3716 removeEditor(editor) { 3717 if (editor.div.contains(document.activeElement)) { 3718 if (this.#focusMainContainerTimeoutId) { 3719 clearTimeout(this.#focusMainContainerTimeoutId); 3720 } 3721 this.#focusMainContainerTimeoutId = setTimeout(() => { 3722 this.focusMainContainer(); 3723 this.#focusMainContainerTimeoutId = null; 3724 }, 0); 3725 } 3726 this.#allEditors.delete(editor.id); 3727 if (editor.annotationElementId) { 3728 this.#missingCanvases?.delete(editor.annotationElementId); 3729 } 3730 this.unselect(editor); 3731 if (!editor.annotationElementId || !this.#deletedAnnotationsElementIds.has(editor.annotationElementId)) { 3732 this.#annotationStorage?.remove(editor.id); 3733 } 3734 } 3735 addDeletedAnnotationElement(editor) { 3736 this.#deletedAnnotationsElementIds.add(editor.annotationElementId); 3737 this.addChangedExistingAnnotation(editor); 3738 editor.deleted = true; 3739 } 3740 isDeletedAnnotationElement(annotationElementId) { 3741 return this.#deletedAnnotationsElementIds.has(annotationElementId); 3742 } 3743 removeDeletedAnnotationElement(editor) { 3744 this.#deletedAnnotationsElementIds.delete(editor.annotationElementId); 3745 this.removeChangedExistingAnnotation(editor); 3746 editor.deleted = false; 3747 } 3748 #addEditorToLayer(editor) { 3749 const layer = this.#allLayers.get(editor.pageIndex); 3750 if (layer) { 3751 layer.addOrRebuild(editor); 3752 } else { 3753 this.addEditor(editor); 3754 this.addToAnnotationStorage(editor); 3755 } 3756 } 3757 setActiveEditor(editor) { 3758 if (this.#activeEditor === editor) { 3759 return; 3760 } 3761 this.#activeEditor = editor; 3762 if (editor) { 3763 this.#dispatchUpdateUI(editor.propertiesToUpdate); 3764 } 3765 } 3766 get #lastSelectedEditor() { 3767 let ed = null; 3768 for (ed of this.#selectedEditors) {} 3769 return ed; 3770 } 3771 updateUI(editor) { 3772 if (this.#lastSelectedEditor === editor) { 3773 this.#dispatchUpdateUI(editor.propertiesToUpdate); 3774 } 3775 } 3776 updateUIForDefaultProperties(editorType) { 3777 this.#dispatchUpdateUI(editorType.defaultPropertiesToUpdate); 3778 } 3779 toggleSelected(editor) { 3780 if (this.#selectedEditors.has(editor)) { 3781 this.#selectedEditors.delete(editor); 3782 editor.unselect(); 3783 this.#dispatchUpdateStates({ 3784 hasSelectedEditor: this.hasSelection 3785 }); 3786 return; 3787 } 3788 this.#selectedEditors.add(editor); 3789 editor.select(); 3790 this.#dispatchUpdateUI(editor.propertiesToUpdate); 3791 this.#dispatchUpdateStates({ 3792 hasSelectedEditor: true 3793 }); 3794 } 3795 setSelected(editor) { 3796 this.updateToolbar({ 3797 mode: editor.mode, 3798 editId: editor.uid 3799 }); 3800 this.#currentDrawingSession?.commitOrRemove(); 3801 for (const ed of this.#selectedEditors) { 3802 if (ed !== editor) { 3803 ed.unselect(); 3804 } 3805 } 3806 this.#commentManager?.destroyPopup(); 3807 this.#selectedEditors.clear(); 3808 this.#selectedEditors.add(editor); 3809 editor.select(); 3810 this.#dispatchUpdateUI(editor.propertiesToUpdate); 3811 this.#dispatchUpdateStates({ 3812 hasSelectedEditor: true 3813 }); 3814 } 3815 get firstSelectedEditor() { 3816 return this.#selectedEditors.values().next().value; 3817 } 3818 unselect(editor) { 3819 editor.unselect(); 3820 this.#selectedEditors.delete(editor); 3821 this.#dispatchUpdateStates({ 3822 hasSelectedEditor: this.hasSelection 3823 }); 3824 } 3825 get hasSelection() { 3826 return this.#selectedEditors.size !== 0; 3827 } 3828 get isEnterHandled() { 3829 return this.#selectedEditors.size === 1 && this.firstSelectedEditor.isEnterHandled; 3830 } 3831 undo() { 3832 this.#commandManager.undo(); 3833 this.#dispatchUpdateStates({ 3834 hasSomethingToUndo: this.#commandManager.hasSomethingToUndo(), 3835 hasSomethingToRedo: true, 3836 isEmpty: this.#isEmpty() 3837 }); 3838 this._editorUndoBar?.hide(); 3839 } 3840 redo() { 3841 this.#commandManager.redo(); 3842 this.#dispatchUpdateStates({ 3843 hasSomethingToUndo: true, 3844 hasSomethingToRedo: this.#commandManager.hasSomethingToRedo(), 3845 isEmpty: this.#isEmpty() 3846 }); 3847 } 3848 addCommands(params) { 3849 this.#commandManager.add(params); 3850 this.#dispatchUpdateStates({ 3851 hasSomethingToUndo: true, 3852 hasSomethingToRedo: false, 3853 isEmpty: this.#isEmpty() 3854 }); 3855 } 3856 cleanUndoStack(type) { 3857 this.#commandManager.cleanType(type); 3858 } 3859 #isEmpty() { 3860 if (this.#allEditors.size === 0) { 3861 return true; 3862 } 3863 if (this.#allEditors.size === 1) { 3864 for (const editor of this.#allEditors.values()) { 3865 return editor.isEmpty(); 3866 } 3867 } 3868 return false; 3869 } 3870 delete() { 3871 this.commitOrRemove(); 3872 const drawingEditor = this.currentLayer?.endDrawingSession(true); 3873 if (!this.hasSelection && !drawingEditor) { 3874 return; 3875 } 3876 const editors = drawingEditor ? [drawingEditor] : [...this.#selectedEditors]; 3877 const cmd = () => { 3878 this._editorUndoBar?.show(undo, editors.length === 1 ? editors[0].editorType : editors.length); 3879 for (const editor of editors) { 3880 editor.remove(); 3881 } 3882 }; 3883 const undo = () => { 3884 for (const editor of editors) { 3885 this.#addEditorToLayer(editor); 3886 } 3887 }; 3888 this.addCommands({ 3889 cmd, 3890 undo, 3891 mustExec: true 3892 }); 3893 } 3894 commitOrRemove() { 3895 this.#activeEditor?.commitOrRemove(); 3896 } 3897 hasSomethingToControl() { 3898 return this.#activeEditor || this.hasSelection; 3899 } 3900 #selectEditors(editors) { 3901 for (const editor of this.#selectedEditors) { 3902 editor.unselect(); 3903 } 3904 this.#selectedEditors.clear(); 3905 for (const editor of editors) { 3906 if (editor.isEmpty()) { 3907 continue; 3908 } 3909 this.#selectedEditors.add(editor); 3910 editor.select(); 3911 } 3912 this.#dispatchUpdateStates({ 3913 hasSelectedEditor: this.hasSelection 3914 }); 3915 } 3916 selectAll() { 3917 for (const editor of this.#selectedEditors) { 3918 editor.commit(); 3919 } 3920 this.#selectEditors(this.#allEditors.values()); 3921 } 3922 unselectAll() { 3923 if (this.#activeEditor) { 3924 this.#activeEditor.commitOrRemove(); 3925 if (this.#mode !== AnnotationEditorType.NONE) { 3926 return; 3927 } 3928 } 3929 if (this.#currentDrawingSession?.commitOrRemove()) { 3930 return; 3931 } 3932 this.#commentManager?.destroyPopup(); 3933 if (!this.hasSelection) { 3934 return; 3935 } 3936 for (const editor of this.#selectedEditors) { 3937 editor.unselect(); 3938 } 3939 this.#selectedEditors.clear(); 3940 this.#dispatchUpdateStates({ 3941 hasSelectedEditor: false 3942 }); 3943 } 3944 translateSelectedEditors(x, y, noCommit = false) { 3945 if (!noCommit) { 3946 this.commitOrRemove(); 3947 } 3948 if (!this.hasSelection) { 3949 return; 3950 } 3951 this.#translation[0] += x; 3952 this.#translation[1] += y; 3953 const [totalX, totalY] = this.#translation; 3954 const editors = [...this.#selectedEditors]; 3955 const TIME_TO_WAIT = 1000; 3956 if (this.#translationTimeoutId) { 3957 clearTimeout(this.#translationTimeoutId); 3958 } 3959 this.#translationTimeoutId = setTimeout(() => { 3960 this.#translationTimeoutId = null; 3961 this.#translation[0] = this.#translation[1] = 0; 3962 this.addCommands({ 3963 cmd: () => { 3964 for (const editor of editors) { 3965 if (this.#allEditors.has(editor.id)) { 3966 editor.translateInPage(totalX, totalY); 3967 editor.translationDone(); 3968 } 3969 } 3970 }, 3971 undo: () => { 3972 for (const editor of editors) { 3973 if (this.#allEditors.has(editor.id)) { 3974 editor.translateInPage(-totalX, -totalY); 3975 editor.translationDone(); 3976 } 3977 } 3978 }, 3979 mustExec: false 3980 }); 3981 }, TIME_TO_WAIT); 3982 for (const editor of editors) { 3983 editor.translateInPage(x, y); 3984 editor.translationDone(); 3985 } 3986 } 3987 setUpDragSession() { 3988 if (!this.hasSelection) { 3989 return; 3990 } 3991 this.disableUserSelect(true); 3992 this.#draggingEditors = new Map(); 3993 for (const editor of this.#selectedEditors) { 3994 this.#draggingEditors.set(editor, { 3995 savedX: editor.x, 3996 savedY: editor.y, 3997 savedPageIndex: editor.pageIndex, 3998 newX: 0, 3999 newY: 0, 4000 newPageIndex: -1 4001 }); 4002 } 4003 } 4004 endDragSession() { 4005 if (!this.#draggingEditors) { 4006 return false; 4007 } 4008 this.disableUserSelect(false); 4009 const map = this.#draggingEditors; 4010 this.#draggingEditors = null; 4011 let mustBeAddedInUndoStack = false; 4012 for (const [{ 4013 x, 4014 y, 4015 pageIndex 4016 }, value] of map) { 4017 value.newX = x; 4018 value.newY = y; 4019 value.newPageIndex = pageIndex; 4020 mustBeAddedInUndoStack ||= x !== value.savedX || y !== value.savedY || pageIndex !== value.savedPageIndex; 4021 } 4022 if (!mustBeAddedInUndoStack) { 4023 return false; 4024 } 4025 const move = (editor, x, y, pageIndex) => { 4026 if (this.#allEditors.has(editor.id)) { 4027 const parent = this.#allLayers.get(pageIndex); 4028 if (parent) { 4029 editor._setParentAndPosition(parent, x, y); 4030 } else { 4031 editor.pageIndex = pageIndex; 4032 editor.x = x; 4033 editor.y = y; 4034 } 4035 } 4036 }; 4037 this.addCommands({ 4038 cmd: () => { 4039 for (const [editor, { 4040 newX, 4041 newY, 4042 newPageIndex 4043 }] of map) { 4044 move(editor, newX, newY, newPageIndex); 4045 } 4046 }, 4047 undo: () => { 4048 for (const [editor, { 4049 savedX, 4050 savedY, 4051 savedPageIndex 4052 }] of map) { 4053 move(editor, savedX, savedY, savedPageIndex); 4054 } 4055 }, 4056 mustExec: true 4057 }); 4058 return true; 4059 } 4060 dragSelectedEditors(tx, ty) { 4061 if (!this.#draggingEditors) { 4062 return; 4063 } 4064 for (const editor of this.#draggingEditors.keys()) { 4065 editor.drag(tx, ty); 4066 } 4067 } 4068 rebuild(editor) { 4069 if (editor.parent === null) { 4070 const parent = this.getLayer(editor.pageIndex); 4071 if (parent) { 4072 parent.changeParent(editor); 4073 parent.addOrRebuild(editor); 4074 } else { 4075 this.addEditor(editor); 4076 this.addToAnnotationStorage(editor); 4077 editor.rebuild(); 4078 } 4079 } else { 4080 editor.parent.addOrRebuild(editor); 4081 } 4082 } 4083 get isEditorHandlingKeyboard() { 4084 return this.getActive()?.shouldGetKeyboardEvents() || this.#selectedEditors.size === 1 && this.firstSelectedEditor.shouldGetKeyboardEvents(); 4085 } 4086 isActive(editor) { 4087 return this.#activeEditor === editor; 4088 } 4089 getActive() { 4090 return this.#activeEditor; 4091 } 4092 getMode() { 4093 return this.#mode; 4094 } 4095 isEditingMode() { 4096 return this.#mode !== AnnotationEditorType.NONE; 4097 } 4098 get imageManager() { 4099 return shadow(this, "imageManager", new ImageManager()); 4100 } 4101 getSelectionBoxes(textLayer) { 4102 if (!textLayer) { 4103 return null; 4104 } 4105 const selection = document.getSelection(); 4106 for (let i = 0, ii = selection.rangeCount; i < ii; i++) { 4107 if (!textLayer.contains(selection.getRangeAt(i).commonAncestorContainer)) { 4108 return null; 4109 } 4110 } 4111 const { 4112 x: layerX, 4113 y: layerY, 4114 width: parentWidth, 4115 height: parentHeight 4116 } = textLayer.getBoundingClientRect(); 4117 let rotator; 4118 switch (textLayer.getAttribute("data-main-rotation")) { 4119 case "90": 4120 rotator = (x, y, w, h) => ({ 4121 x: (y - layerY) / parentHeight, 4122 y: 1 - (x + w - layerX) / parentWidth, 4123 width: h / parentHeight, 4124 height: w / parentWidth 4125 }); 4126 break; 4127 case "180": 4128 rotator = (x, y, w, h) => ({ 4129 x: 1 - (x + w - layerX) / parentWidth, 4130 y: 1 - (y + h - layerY) / parentHeight, 4131 width: w / parentWidth, 4132 height: h / parentHeight 4133 }); 4134 break; 4135 case "270": 4136 rotator = (x, y, w, h) => ({ 4137 x: 1 - (y + h - layerY) / parentHeight, 4138 y: (x - layerX) / parentWidth, 4139 width: h / parentHeight, 4140 height: w / parentWidth 4141 }); 4142 break; 4143 default: 4144 rotator = (x, y, w, h) => ({ 4145 x: (x - layerX) / parentWidth, 4146 y: (y - layerY) / parentHeight, 4147 width: w / parentWidth, 4148 height: h / parentHeight 4149 }); 4150 break; 4151 } 4152 const boxes = []; 4153 for (let i = 0, ii = selection.rangeCount; i < ii; i++) { 4154 const range = selection.getRangeAt(i); 4155 if (range.collapsed) { 4156 continue; 4157 } 4158 for (const { 4159 x, 4160 y, 4161 width, 4162 height 4163 } of range.getClientRects()) { 4164 if (width === 0 || height === 0) { 4165 continue; 4166 } 4167 boxes.push(rotator(x, y, width, height)); 4168 } 4169 } 4170 return boxes.length === 0 ? null : boxes; 4171 } 4172 addChangedExistingAnnotation({ 4173 annotationElementId, 4174 id 4175 }) { 4176 (this.#changedExistingAnnotations ||= new Map()).set(annotationElementId, id); 4177 } 4178 removeChangedExistingAnnotation({ 4179 annotationElementId 4180 }) { 4181 this.#changedExistingAnnotations?.delete(annotationElementId); 4182 } 4183 renderAnnotationElement(annotation) { 4184 const editorId = this.#changedExistingAnnotations?.get(annotation.data.id); 4185 if (!editorId) { 4186 return; 4187 } 4188 const editor = this.#annotationStorage.getRawValue(editorId); 4189 if (!editor) { 4190 return; 4191 } 4192 if (this.#mode === AnnotationEditorType.NONE && !editor.hasBeenModified) { 4193 return; 4194 } 4195 editor.renderAnnotationElement(annotation); 4196 } 4197 setMissingCanvas(annotationId, annotationElementId, canvas) { 4198 const editor = this.#missingCanvases?.get(annotationId); 4199 if (!editor) { 4200 return; 4201 } 4202 editor.setCanvas(annotationElementId, canvas); 4203 this.#missingCanvases.delete(annotationId); 4204 } 4205 addMissingCanvas(annotationId, editor) { 4206 (this.#missingCanvases ||= new Map()).set(annotationId, editor); 4207 } 4208} 4209 4210;// ./src/display/editor/alt_text.js 4211 4212class AltText { 4213 #altText = null; 4214 #altTextDecorative = false; 4215 #altTextButton = null; 4216 #altTextButtonLabel = null; 4217 #altTextTooltip = null; 4218 #altTextTooltipTimeout = null; 4219 #altTextWasFromKeyBoard = false; 4220 #badge = null; 4221 #editor = null; 4222 #guessedText = null; 4223 #textWithDisclaimer = null; 4224 #useNewAltTextFlow = false; 4225 static #l10nNewButton = null; 4226 static _l10n = null; 4227 constructor(editor) { 4228 this.#editor = editor; 4229 this.#useNewAltTextFlow = editor._uiManager.useNewAltTextFlow; 4230 AltText.#l10nNewButton ||= Object.freeze({ 4231 added: "pdfjs-editor-new-alt-text-added-button", 4232 "added-label": "pdfjs-editor-new-alt-text-added-button-label", 4233 missing: "pdfjs-editor-new-alt-text-missing-button", 4234 "missing-label": "pdfjs-editor-new-alt-text-missing-button-label", 4235 review: "pdfjs-editor-new-alt-text-to-review-button", 4236 "review-label": "pdfjs-editor-new-alt-text-to-review-button-label" 4237 }); 4238 } 4239 static initialize(l10n) { 4240 AltText._l10n ??= l10n; 4241 } 4242 async render() { 4243 const altText = this.#altTextButton = document.createElement("button"); 4244 altText.className = "altText"; 4245 altText.tabIndex = "0"; 4246 const label = this.#altTextButtonLabel = document.createElement("span"); 4247 altText.append(label); 4248 if (this.#useNewAltTextFlow) { 4249 altText.classList.add("new"); 4250 altText.setAttribute("data-l10n-id", AltText.#l10nNewButton.missing); 4251 label.setAttribute("data-l10n-id", AltText.#l10nNewButton["missing-label"]); 4252 } else { 4253 altText.setAttribute("data-l10n-id", "pdfjs-editor-alt-text-button"); 4254 label.setAttribute("data-l10n-id", "pdfjs-editor-alt-text-button-label"); 4255 } 4256 const signal = this.#editor._uiManager._signal; 4257 altText.addEventListener("contextmenu", noContextMenu, { 4258 signal 4259 }); 4260 altText.addEventListener("pointerdown", event => event.stopPropagation(), { 4261 signal 4262 }); 4263 const onClick = event => { 4264 event.preventDefault(); 4265 this.#editor._uiManager.editAltText(this.#editor); 4266 if (this.#useNewAltTextFlow) { 4267 this.#editor._reportTelemetry({ 4268 action: "pdfjs.image.alt_text.image_status_label_clicked", 4269 data: { 4270 label: this.#label 4271 } 4272 }); 4273 } 4274 }; 4275 altText.addEventListener("click", onClick, { 4276 capture: true, 4277 signal 4278 }); 4279 altText.addEventListener("keydown", event => { 4280 if (event.target === altText && event.key === "Enter") { 4281 this.#altTextWasFromKeyBoard = true; 4282 onClick(event); 4283 } 4284 }, { 4285 signal 4286 }); 4287 await this.#setState(); 4288 return altText; 4289 } 4290 get #label() { 4291 return this.#altText && "added" || this.#altText === null && this.guessedText && "review" || "missing"; 4292 } 4293 finish() { 4294 if (!this.#altTextButton) { 4295 return; 4296 } 4297 this.#altTextButton.focus({ 4298 focusVisible: this.#altTextWasFromKeyBoard 4299 }); 4300 this.#altTextWasFromKeyBoard = false; 4301 } 4302 isEmpty() { 4303
4303if (this.#useNewAltTextFlow) { 4304 return this.#altText === null; 4305 } 4306 return !this.#altText && !this.#altTextDecorative; 4307 } 4308 hasData() { 4309 if (this.#useNewAltTextFlow) { 4310 return this.#altText !== null || !!this.#guessedText; 4311 } 4312 return
vendor: 12,118 bytes, lines 4312-4748
4312 this.isEmpty(); 4313 } 4314 get guessedText() { 4315 return this.#guessedText; 4316 } 4317 async setGuessedText(guessedText) { 4318 if (this.#altText !== null) { 4319 return; 4320 } 4321 this.#guessedText = guessedText; 4322 this.#textWithDisclaimer = await AltText._l10n.get("pdfjs-editor-new-alt-text-generated-alt-text-with-disclaimer", { 4323 generatedAltText: guessedText 4324 }); 4325 this.#setState(); 4326 } 4327 toggleAltTextBadge(visibility = false) { 4328 if (!this.#useNewAltTextFlow || this.#altText) { 4329 this.#badge?.remove(); 4330 this.#badge = null; 4331 return; 4332 } 4333 if (!this.#badge) { 4334 const badge = this.#badge = document.createElement("div"); 4335 badge.className = "noAltTextBadge"; 4336 this.#editor.div.append(badge); 4337 } 4338 this.#badge.classList.toggle("hidden", !visibility); 4339 } 4340 serialize(isForCopying) { 4341 let altText = this.#altText; 4342 if (!isForCopying && this.#guessedText === altText) { 4343 altText = this.#textWithDisclaimer; 4344 } 4345 return { 4346 altText, 4347 decorative: this.#altTextDecorative, 4348 guessedText: this.#guessedText, 4349 textWithDisclaimer: this.#textWithDisclaimer 4350 }; 4351 } 4352 get data() { 4353 return { 4354 altText: this.#altText, 4355 decorative: this.#altTextDecorative 4356 }; 4357 } 4358 set data({ 4359 altText, 4360 decorative, 4361 guessedText, 4362 textWithDisclaimer, 4363 cancel = false 4364 }) { 4365 if (guessedText) { 4366 this.#guessedText = guessedText; 4367 this.#textWithDisclaimer = textWithDisclaimer; 4368 } 4369 if (this.#altText === altText && this.#altTextDecorative === decorative) { 4370 return; 4371 } 4372 if (!cancel) { 4373 this.#altText = altText; 4374 this.#altTextDecorative = decorative; 4375 } 4376 this.#setState(); 4377 } 4378 toggle(enabled = false) { 4379 if (!this.#altTextButton) { 4380 return; 4381 } 4382 if (!enabled && this.#altTextTooltipTimeout) { 4383 clearTimeout(this.#altTextTooltipTimeout); 4384 this.#altTextTooltipTimeout = null; 4385 } 4386 this.#altTextButton.disabled = !enabled; 4387 } 4388 shown() { 4389 this.#editor._reportTelemetry({ 4390 action: "pdfjs.image.alt_text.image_status_label_displayed", 4391 data: { 4392 label: this.#label 4393 } 4394 }); 4395 } 4396 destroy() { 4397 this.#altTextButton?.remove(); 4398 this.#altTextButton = null; 4399 this.#altTextButtonLabel = null; 4400 this.#altTextTooltip = null; 4401 this.#badge?.remove(); 4402 this.#badge = null; 4403 } 4404 async #setState() { 4405 const button = this.#altTextButton; 4406 if (!button) { 4407 return; 4408 } 4409 if (this.#useNewAltTextFlow) { 4410 button.classList.toggle("done", !!this.#altText); 4411 button.setAttribute("data-l10n-id", AltText.#l10nNewButton[this.#label]); 4412 this.#altTextButtonLabel?.setAttribute("data-l10n-id", AltText.#l10nNewButton[`${this.#label}-label`]); 4413 if (!this.#altText) { 4414 this.#altTextTooltip?.remove(); 4415 return; 4416 } 4417 } else { 4418 if (!this.#altText && !this.#altTextDecorative) { 4419 button.classList.remove("done"); 4420 this.#altTextTooltip?.remove(); 4421 return; 4422 } 4423 button.classList.add("done"); 4424 button.setAttribute("data-l10n-id", "pdfjs-editor-alt-text-edit-button"); 4425 } 4426 let tooltip = this.#altTextTooltip; 4427 if (!tooltip) { 4428 this.#altTextTooltip = tooltip = document.createElement("span"); 4429 tooltip.className = "tooltip"; 4430 tooltip.setAttribute("role", "tooltip"); 4431 tooltip.id = `alt-text-tooltip-${this.#editor.id}`; 4432 const DELAY_TO_SHOW_TOOLTIP = 100; 4433 const signal = this.#editor._uiManager._signal; 4434 signal.addEventListener("abort", () => { 4435 clearTimeout(this.#altTextTooltipTimeout); 4436 this.#altTextTooltipTimeout = null; 4437 }, { 4438 once: true 4439 }); 4440 button.addEventListener("mouseenter", () => { 4441 this.#altTextTooltipTimeout = setTimeout(() => { 4442 this.#altTextTooltipTimeout = null; 4443 this.#altTextTooltip.classList.add("show"); 4444 this.#editor._reportTelemetry({ 4445 action: "alt_text_tooltip" 4446 }); 4447 }, DELAY_TO_SHOW_TOOLTIP); 4448 }, { 4449 signal 4450 }); 4451 button.addEventListener("mouseleave", () => { 4452 if (this.#altTextTooltipTimeout) { 4453 clearTimeout(this.#altTextTooltipTimeout); 4454 this.#altTextTooltipTimeout = null; 4455 } 4456 this.#altTextTooltip?.classList.remove("show"); 4457 }, { 4458 signal 4459 }); 4460 } 4461 if (this.#altTextDecorative) { 4462 tooltip.setAttribute("data-l10n-id", "pdfjs-editor-alt-text-decorative-tooltip"); 4463 } else { 4464 tooltip.removeAttribute("data-l10n-id"); 4465 tooltip.textContent = this.#altText; 4466 } 4467 if (!tooltip.parentNode) { 4468 button.append(tooltip); 4469 } 4470 const element = this.#editor.getElementForAltText(); 4471 element?.setAttribute("aria-describedby", tooltip.id); 4472 } 4473} 4474 4475;// ./src/display/editor/comment.js 4476 4477class Comment { 4478 #commentStandaloneButton = null; 4479 #commentToolbarButton = null; 4480 #commentWasFromKeyBoard = false; 4481 #editor = null; 4482 #initialText = null; 4483 #richText = null; 4484 #text = null; 4485 #date = null; 4486 #deleted = false; 4487 #popupPosition = null; 4488 constructor(editor) { 4489 this.#editor = editor; 4490 } 4491 renderForToolbar() { 4492 const button = this.#commentToolbarButton = document.createElement("button"); 4493 button.className = "comment"; 4494 return this.#render(button, false); 4495 } 4496 renderForStandalone() { 4497 const button = this.#commentStandaloneButton = document.createElement("button"); 4498 button.className = "annotationCommentButton"; 4499 const position = this.#editor.commentButtonPosition; 4500 if (position) { 4501 const { 4502 style 4503 } = button; 4504 style.insetInlineEnd = `calc(${100 * (this.#editor._uiManager.direction === "ltr" ? 1 - position[0] : position[0])}% - var(--comment-button-dim))`; 4505 style.top = `calc(${100 * position[1]}% - var(--comment-button-dim))`; 4506 const color = this.#editor.commentButtonColor; 4507 if (color) { 4508 style.backgroundColor = color; 4509 } 4510 } 4511 return this.#render(button, true); 4512 } 4513 focusButton() { 4514 setTimeout(() => { 4515 (this.#commentStandaloneButton ?? this.#commentToolbarButton)?.focus(); 4516 }, 0); 4517 } 4518 onUpdatedColor() { 4519 if (!this.#commentStandaloneButton) { 4520 return; 4521 } 4522 const color = this.#editor.commentButtonColor; 4523 if (color) { 4524 this.#commentStandaloneButton.style.backgroundColor = color; 4525 } 4526 this.#editor._uiManager.updatePopupColor(this.#editor); 4527 } 4528 get commentButtonWidth() { 4529 return (this.#commentStandaloneButton?.getBoundingClientRect().width ?? 0) / this.#editor.parent.boundingClientRect.width; 4530 } 4531 get commentPopupPositionInLayer() { 4532 if (this.#popupPosition) { 4533 return this.#popupPosition; 4534 } 4535 if (!this.#commentStandaloneButton) { 4536 return null; 4537 } 4538 const { 4539 x, 4540 y, 4541 height 4542 } = this.#commentStandaloneButton.getBoundingClientRect(); 4543 const { 4544 x: parentX, 4545 y: parentY, 4546 width: parentWidth, 4547 height: parentHeight 4548 } = this.#editor.parent.boundingClientRect; 4549 return [(x - parentX) / parentWidth, (y + height - parentY) / parentHeight]; 4550 } 4551 set commentPopupPositionInLayer(pos) { 4552 this.#popupPosition = pos; 4553 } 4554 hasDefaultPopupPosition() { 4555 return this.#popupPosition === null; 4556 } 4557 removeStandaloneCommentButton() { 4558 this.#commentStandaloneButton?.remove(); 4559 this.#commentStandaloneButton = null; 4560 } 4561 removeToolbarCommentButton() { 4562 this.#commentToolbarButton?.remove(); 4563 this.#commentToolbarButton = null; 4564 } 4565 setCommentButtonStates({ 4566 selected, 4567 hasPopup 4568 }) { 4569 if (!this.#commentStandaloneButton) { 4570 return; 4571 } 4572 this.#commentStandaloneButton.classList.toggle("selected", selected); 4573 this.#commentStandaloneButton.ariaExpanded = hasPopup; 4574 } 4575 #render(comment, isStandalone) { 4576 if (!this.#editor._uiManager.hasCommentManager()) { 4577 return null; 4578 } 4579 comment.tabIndex = "0"; 4580 comment.ariaHasPopup = "dialog"; 4581 if (isStandalone) { 4582 comment.ariaControls = "commentPopup"; 4583 comment.setAttribute("data-l10n-id", "pdfjs-show-comment-button"); 4584 } else { 4585 comment.ariaControlsElements = [this.#editor._uiManager.getCommentDialogElement()]; 4586 comment.setAttribute("data-l10n-id", "pdfjs-editor-add-comment-button"); 4587 } 4588 const signal = this.#editor._uiManager._signal; 4589 if (!(signal instanceof AbortSignal) || signal.aborted) { 4590 return comment; 4591 } 4592 comment.addEventListener("contextmenu", noContextMenu, { 4593 signal 4594 }); 4595 if (isStandalone) { 4596 comment.addEventListener("focusin", e => { 4597 this.#editor._focusEventsAllowed = false; 4598 stopEvent(e); 4599 }, { 4600 capture: true, 4601 signal 4602 }); 4603 comment.addEventListener("focusout", e => { 4604 this.#editor._focusEventsAllowed = true; 4605 stopEvent(e); 4606 }, { 4607 capture: true, 4608 signal 4609 }); 4610 } 4611 comment.addEventListener("pointerdown", event => event.stopPropagation(), { 4612 signal 4613 }); 4614 const onClick = event => { 4615 event.preventDefault(); 4616 if (comment === this.#commentToolbarButton) { 4617 this.edit(); 4618 } else { 4619 this.#editor.toggleComment(true); 4620 } 4621 }; 4622 comment.addEventListener("click", onClick, { 4623 capture: true, 4624 signal 4625 }); 4626 comment.addEventListener("keydown", event => { 4627 if (event.target === comment && event.key === "Enter") { 4628 this.#commentWasFromKeyBoard = true; 4629 onClick(event); 4630 } 4631 }, { 4632 signal 4633 }); 4634 comment.addEventListener("pointerenter", () => { 4635 this.#editor.toggleComment(false, true); 4636 }, { 4637 signal 4638 }); 4639 comment.addEventListener("pointerleave", () => { 4640 this.#editor.toggleComment(false, false); 4641 }, { 4642 signal 4643 }); 4644 return comment; 4645 } 4646 edit(options) { 4647 const position = this.commentPopupPositionInLayer; 4648 let posX, posY; 4649 if (position) { 4650 [posX, posY] = position; 4651 } else { 4652 [posX, posY] = this.#editor.commentButtonPosition; 4653 const { 4654 width, 4655 height, 4656 x, 4657 y 4658 } = this.#editor; 4659 posX = x + posX * width; 4660 posY = y + posY * height; 4661 } 4662 const parentDimensions = this.#editor.parent.boundingClientRect; 4663 const { 4664 x: parentX, 4665 y: parentY, 4666 width: parentWidth, 4667 height: parentHeight 4668 } = parentDimensions; 4669 this.#editor._uiManager.editComment(this.#editor, parentX + posX * parentWidth, parentY + posY * parentHeight, { 4670 ...options, 4671 parentDimensions 4672 }); 4673 } 4674 finish() { 4675 if (!this.#commentToolbarButton) { 4676 return; 4677 } 4678 this.#commentToolbarButton.focus({ 4679 focusVisible: this.#commentWasFromKeyBoard 4680 }); 4681 this.#commentWasFromKeyBoard = false; 4682 } 4683 isDeleted() { 4684 return this.#deleted || this.#text === ""; 4685 } 4686 isEmpty() { 4687 return this.#text === null; 4688 } 4689 hasBeenEdited() { 4690 return this.isDeleted() || this.#text !== this.#initialText; 4691 } 4692 serialize() { 4693 return this.data; 4694 } 4695 get data() { 4696 return { 4697 text: this.#text, 4698 richText: this.#richText, 4699 date: this.#date, 4700 deleted: this.isDeleted() 4701 }; 4702 } 4703 set data(text) { 4704 if (text !== this.#text) { 4705 this.#richText = null; 4706 } 4707 if (text === null) { 4708 this.#text = ""; 4709 this.#deleted = true; 4710 return; 4711 } 4712 this.#text = text; 4713 this.#date = new Date(); 4714 this.#deleted = false; 4715 } 4716 restoreData({ 4717 text, 4718 richText, 4719 date 4720 }) { 4721 this.#text = text; 4722 this.#richText = richText; 4723 this.#date = date; 4724 this.#deleted = false; 4725 } 4726 setInitialText(text, richText = null) { 4727 this.#initialText = text; 4728 this.data = text; 4729 this.#date = null; 4730 this.#richText = richText; 4731 } 4732 shown() {} 4733 destroy() { 4734 this.#commentToolbarButton?.remove(); 4735 this.#commentToolbarButton = null; 4736 this.#commentStandaloneButton?.remove(); 4737 this.#commentStandaloneButton = null; 4738 this.#text = ""; 4739 this.#richText = null; 4740 this.#date = null; 4741 this.#editor = null; 4742 this.#commentWasFromKeyBoard = false; 4743 this.#deleted = false; 4744 } 4745} 4746 4747;// ./src/display/touch_manager.js 4748
vendor: 8,436 bytes, lines 4749-5028
4749class TouchManager { 4750 #container; 4751 #isPinching = false; 4752 #isPinchingStopped = null; 4753 #isPinchingDisabled; 4754 #onPinchStart; 4755 #onPinching; 4756 #onPinchEnd; 4757 #pointerDownAC = null; 4758 #signal; 4759 #touchInfo = null; 4760 #touchManagerAC; 4761 #touchMoveAC = null; 4762 constructor({ 4763 container, 4764 isPinchingDisabled = null, 4765 isPinchingStopped = null, 4766 onPinchStart = null, 4767 onPinching = null, 4768 onPinchEnd = null, 4769 signal 4770 }) { 4771 this.#container = container; 4772 this.#isPinchingStopped = isPinchingStopped; 4773 this.#isPinchingDisabled = isPinchingDisabled; 4774 this.#onPinchStart = onPinchStart; 4775 this.#onPinching = onPinching; 4776 this.#onPinchEnd = onPinchEnd; 4777 this.#touchManagerAC = new AbortController(); 4778 this.#signal = AbortSignal.any([signal, this.#touchManagerAC.signal]); 4779 container.addEventListener("touchstart", this.#onTouchStart.bind(this), { 4780 passive: false, 4781 signal: this.#signal 4782 }); 4783 } 4784 get MIN_TOUCH_DISTANCE_TO_PINCH() { 4785 return 35 / OutputScale.pixelRatio; 4786 } 4787 #onTouchStart(evt) { 4788 if (this.#isPinchingDisabled?.()) { 4789 return; 4790 } 4791 if (evt.touches.length === 1) { 4792 if (this.#pointerDownAC) { 4793 return; 4794 } 4795 const pointerDownAC = this.#pointerDownAC = new AbortController(); 4796 const signal = AbortSignal.any([this.#signal, pointerDownAC.signal]); 4797 const container = this.#container; 4798 const opts = { 4799 capture: true, 4800 signal, 4801 passive: false 4802 }; 4803 const cancelPointerDown = e => { 4804 if (e.pointerType === "touch") { 4805 this.#pointerDownAC?.abort(); 4806 this.#pointerDownAC = null; 4807 } 4808 }; 4809 container.addEventListener("pointerdown", e => { 4810 if (e.pointerType === "touch") { 4811 stopEvent(e); 4812 cancelPointerDown(e); 4813 } 4814 }, opts); 4815 container.addEventListener("pointerup", cancelPointerDown, opts); 4816 container.addEventListener("pointercancel", cancelPointerDown, opts); 4817 return; 4818 } 4819 if (!this.#touchMoveAC) { 4820 this.#touchMoveAC = new AbortController(); 4821 const signal = AbortSignal.any([this.#signal, this.#touchMoveAC.signal]); 4822 const container = this.#container; 4823 const opt = { 4824 signal, 4825 capture: false, 4826 passive: false 4827 }; 4828 container.addEventListener("touchmove", this.#onTouchMove.bind(this), opt); 4829 const onTouchEnd = this.#onTouchEnd.bind(this); 4830 container.addEventListener("touchend", onTouchEnd, opt); 4831 container.addEventListener("touchcancel", onTouchEnd, opt); 4832 opt.capture = true; 4833 container.addEventListener("pointerdown", stopEvent, opt); 4834 container.addEventListener("pointermove", stopEvent, opt); 4835 container.addEventListener("pointercancel", stopEvent, opt); 4836 container.addEventListener("pointerup", stopEvent, opt); 4837 this.#onPinchStart?.(); 4838 } 4839 stopEvent(evt); 4840 if (evt.touches.length !== 2 || this.#isPinchingStopped?.()) { 4841 this.#touchInfo = null; 4842 return; 4843 } 4844 let [touch0, touch1] = evt.touches; 4845 if (touch0.identifier > touch1.identifier) { 4846 [touch0, touch1] = [touch1, touch0]; 4847 } 4848 this.#touchInfo = { 4849 touch0X: touch0.screenX, 4850 touch0Y: touch0.screenY, 4851 touch1X: touch1.screenX, 4852 touch1Y: touch1.screenY 4853 }; 4854 } 4855 #onTouchMove(evt) { 4856 if (!this.#touchInfo || evt.touches.length !== 2) { 4857 return; 4858 } 4859 stopEvent(evt); 4860 let [touch0, touch1] = evt.touches; 4861 if (touch0.identifier > touch1.identifier) { 4862 [touch0, touch1] = [touch1, touch0]; 4863 } 4864 const { 4865 screenX: screen0X, 4866 screenY: screen0Y 4867 } = touch0; 4868 const { 4869 screenX: screen1X, 4870 screenY: screen1Y 4871 } = touch1; 4872 const touchInfo = this.#touchInfo; 4873 const { 4874 touch0X: pTouch0X, 4875 touch0Y: pTouch0Y, 4876 touch1X: pTouch1X, 4877 touch1Y: pTouch1Y 4878 } = touchInfo; 4879 const prevGapX = pTouch1X - pTouch0X; 4880 const prevGapY = pTouch1Y - pTouch0Y; 4881 const currGapX = screen1X - screen0X; 4882 const currGapY = screen1Y - screen0Y; 4883 const distance = Math.hypot(currGapX, currGapY) || 1; 4884 const pDistance = Math.hypot(prevGapX, prevGapY) || 1; 4885 if (!this.#isPinching && Math.abs(pDistance - distance) <= TouchManager.MIN_TOUCH_DISTANCE_TO_PINCH) { 4886 return; 4887 } 4888 touchInfo.touch0X = screen0X; 4889 touchInfo.touch0Y = screen0Y; 4890 touchInfo.touch1X = screen1X; 4891 touchInfo.touch1Y = screen1Y; 4892 if (!this.#isPinching) { 4893 this.#isPinching = true; 4894 return; 4895 } 4896 const origin = [(screen0X + screen1X) / 2, (screen0Y + screen1Y) / 2]; 4897 this.#onPinching?.(origin, pDistance, distance); 4898 } 4899 #onTouchEnd(evt) { 4900 if (evt.touches.length >= 2) { 4901 return; 4902 } 4903 if (this.#touchMoveAC) { 4904 this.#touchMoveAC.abort(); 4905 this.#touchMoveAC = null; 4906 this.#onPinchEnd?.(); 4907 } 4908 if (!this.#touchInfo) { 4909 return; 4910 } 4911 stopEvent(evt); 4912 this.#touchInfo = null; 4913 this.#isPinching = false; 4914 } 4915 destroy() { 4916 this.#touchManagerAC?.abort(); 4917 this.#touchManagerAC = null; 4918 this.#pointerDownAC?.abort(); 4919 this.#pointerDownAC = null; 4920 } 4921} 4922 4923;// ./src/display/editor/editor.js 4924 4925 4926 4927 4928 4929 4930 4931 4932class AnnotationEditor { 4933 #accessibilityData = null; 4934 #allResizerDivs = null; 4935 #altText = null; 4936 #comment = null; 4937 #commentStandaloneButton = null; 4938 #disabled = false; 4939 #dragPointerId = null; 4940 #dragPointerType = ""; 4941 #resizersDiv = null; 4942 #lastPointerCoords = null; 4943 #savedDimensions = null; 4944 #fakeAnnotation = null; 4945 #focusAC = null; 4946 #focusedResizerName = ""; 4947 #hasBeenClicked = false; 4948 #initialRect = null; 4949 #isEditing = false; 4950 #isInEditMode = false; 4951 #isResizerEnabledForKeyboard = false; 4952 #moveInDOMTimeout = null; 4953 #prevDragX = 0; 4954 #prevDragY = 0; 4955 #telemetryTimeouts = null; 4956 #touchManager = null; 4957 isSelected = false; 4958 _isCopy = false; 4959 _editToolbar = null; 4960 _initialOptions = Object.create(null); 4961 _initialData = null; 4962 _isVisible = true; 4963 _uiManager = null; 4964 _focusEventsAllowed = true; 4965 static _l10n = null; 4966 static _l10nAlert = null; 4967 static _l10nResizer = null; 4968 #isDraggable = false; 4969 #zIndex = AnnotationEditor._zIndex++; 4970 static _borderLineWidth = -1; 4971 static _colorManager = new ColorManager(); 4972 static _zIndex = 1; 4973 static _telemetryTimeout = 1000; 4974 static get _resizerKeyboardManager() { 4975 const resize = AnnotationEditor.prototype._resizeWithKeyboard; 4976 const small = AnnotationEditorUIManager.TRANSLATE_SMALL; 4977 const big = AnnotationEditorUIManager.TRANSLATE_BIG; 4978 return shadow(this, "_resizerKeyboardManager", new KeyboardManager([[["ArrowLeft"], resize, { 4979 args: [-small, 0] 4980 }], [["ctrl+ArrowLeft", "mac+shift+ArrowLeft"], resize, { 4981 args: [-big, 0] 4982 }], [["ArrowRight"], resize, { 4983 args: [small, 0] 4984 }], [["ctrl+ArrowRight", "mac+shift+ArrowRight"], resize, { 4985 args: [big, 0] 4986 }], [["ArrowUp"], resize, { 4987 args: [0, -small] 4988 }], [["ctrl+ArrowUp", "mac+shift+ArrowUp"], resize, { 4989 args: [0, -big] 4990 }], [["ArrowDown"], resize, { 4991 args: [0, small] 4992 }], [["ctrl+ArrowDown", "mac+shift+ArrowDown"], resize, { 4993 args: [0, big] 4994 }], [["Escape"], AnnotationEditor.prototype._stopResizingWithKeyboard]])); 4995 } 4996 constructor(parameters) { 4997 this.parent = parameters.parent; 4998 this.id = parameters.id; 4999 this.width = this.height = null; 5000 this.pageIndex = parameters.parent.pageIndex; 5001 this.name = parameters.name; 5002 this.div = null; 5003 this._uiManager = parameters.uiManager; 5004 this.annotationElementId = null; 5005 this._willKeepAspectRatio = false; 5006 this._initialOptions.isCentered = parameters.isCentered; 5007 this._structTreeParentId = null; 5008 this.annotationElementId = parameters.annotationElementId || null; 5009 this.creationDate = parameters.creationDate || new Date(); 5010 this.modificationDate = parameters.modificationDate || null; 5011 this.canAddComment = true; 5012 const { 5013 rotation, 5014 rawDims: { 5015 pageWidth, 5016 pageHeight, 5017 pageX, 5018 pageY 5019 } 5020 } = this.parent.viewport; 5021 this.rotation = rotation; 5022 this.pageRotation = (360 + rotation - this._uiManager.viewParameters.rotation) % 360; 5023 this.pageDimensions = [pageWidth, pageHeight]; 5024 this.pageTranslation = [pageX, pageY]; 5025 const [width, height] = this.parentDimensions; 5026 this.x = parameters.x / width; 5027 this.y = parameters.y / height; 5028 this.isAttachedToDOM = false;
vendor: 6,508 bytes, lines 5029-5263
5029 this.deleted = false; 5030 } 5031 updatePageIndex(newPageIndex) { 5032 this.pageIndex = newPageIndex; 5033 } 5034 get editorType() { 5035 return Object.getPrototypeOf(this).constructor._type; 5036 } 5037 get mode() { 5038 return Object.getPrototypeOf(this).constructor._editorType; 5039 } 5040 static get isDrawer() { 5041 return false; 5042 } 5043 static get _defaultLineColor() { 5044 return shadow(this, "_defaultLineColor", this._colorManager.getHexCode("CanvasText")); 5045 } 5046 static deleteAnnotationElement(editor) { 5047 const fakeEditor = new FakeEditor({ 5048 id: editor._uiManager.getId(), 5049 parent: editor.parent, 5050 uiManager: editor._uiManager 5051 }); 5052 fakeEditor.annotationElementId = editor.annotationElementId; 5053 fakeEditor.deleted = true; 5054 fakeEditor._uiManager.addToAnnotationStorage(fakeEditor); 5055 } 5056 static initialize(l10n, _uiManager) { 5057 AnnotationEditor._l10n ??= l10n; 5058 AnnotationEditor._l10nAlert ??= Object.freeze({ 5059 highlight: "pdfjs-editor-highlight-added-alert", 5060 freetext: "pdfjs-editor-freetext-added-alert", 5061 ink: "pdfjs-editor-ink-added-alert", 5062 stamp: "pdfjs-editor-stamp-added-alert", 5063 signature: "pdfjs-editor-signature-added-alert" 5064 }); 5065 AnnotationEditor._l10nResizer ??= Object.freeze({ 5066 topLeft: "pdfjs-editor-resizer-top-left", 5067 topMiddle: "pdfjs-editor-resizer-top-middle", 5068 topRight: "pdfjs-editor-resizer-top-right", 5069 middleRight: "pdfjs-editor-resizer-middle-right", 5070 bottomRight: "pdfjs-editor-resizer-bottom-right", 5071 bottomMiddle: "pdfjs-editor-resizer-bottom-middle", 5072 bottomLeft: "pdfjs-editor-resizer-bottom-left", 5073 middleLeft: "pdfjs-editor-resizer-middle-left" 5074 }); 5075 if (AnnotationEditor._borderLineWidth !== -1) { 5076 return; 5077 } 5078 const style = getComputedStyle(document.documentElement); 5079 AnnotationEditor._borderLineWidth = parseFloat(style.getPropertyValue("--outline-width")) || 0; 5080 } 5081 static updateDefaultParams(_type, _value) {} 5082 static get defaultPropertiesToUpdate() { 5083 return []; 5084 } 5085 static isHandlingMimeForPasting(mime) { 5086 return false; 5087 } 5088 static paste(item, parent) { 5089 unreachable("Not implemented"); 5090 } 5091 get propertiesToUpdate() { 5092 return []; 5093 } 5094 get _isDraggable() { 5095 return this.#isDraggable; 5096 } 5097 set _isDraggable(value) { 5098 this.#isDraggable = value; 5099 this.div?.classList.toggle("draggable", value); 5100 } 5101 get uid() { 5102 return this.annotationElementId || this.id; 5103 } 5104 get isEnterHandled() { 5105 return true; 5106 } 5107 center() { 5108 const [pageWidth, pageHeight] = this.pageDimensions; 5109 switch (this.parentRotation) { 5110 case 90: 5111 this.x -= this.height * pageHeight / (pageWidth * 2); 5112 this.y += this.width * pageWidth / (pageHeight * 2); 5113 break; 5114 case 180: 5115 this.x += this.width / 2; 5116 this.y += this.height / 2; 5117 break; 5118 case 270: 5119 this.x += this.height * pageHeight / (pageWidth * 2); 5120 this.y -= this.width * pageWidth / (pageHeight * 2); 5121 break; 5122 default: 5123 this.x -= this.width / 2; 5124 this.y -= this.height / 2; 5125 break; 5126 } 5127 this.fixAndSetPosition(); 5128 } 5129 addCommands(params) { 5130 this._uiManager.addCommands(params); 5131 } 5132 get currentLayer() { 5133 return this._uiManager.currentLayer; 5134 } 5135 setInBackground() { 5136 this.div.style.zIndex = 0; 5137 } 5138 setInForeground() { 5139 this.div.style.zIndex = this.#zIndex; 5140 } 5141 setParent(parent) { 5142 if (parent !== null) { 5143 this.pageIndex = parent.pageIndex; 5144 this.pageDimensions = parent.pageDimensions; 5145 } else { 5146 this.#stopResizing(); 5147 this.#fakeAnnotation?.remove(); 5148 this.#fakeAnnotation = null; 5149 } 5150 this.parent = parent; 5151 } 5152 focusin(event) { 5153 if (!this._focusEventsAllowed) { 5154 return; 5155 } 5156 if (!this.#hasBeenClicked) { 5157 this.parent.setSelected(this); 5158 } else { 5159 this.#hasBeenClicked = false; 5160 } 5161 } 5162 focusout(event) { 5163 if (!this._focusEventsAllowed) { 5164 return; 5165 } 5166 if (!this.isAttachedToDOM) { 5167 return; 5168 } 5169 const target = event.relatedTarget; 5170 if (target?.closest(`#${this.id}`)) { 5171 return; 5172 } 5173 event.preventDefault(); 5174 if (!this.parent?.isMultipleSelection) { 5175 this.commitOrRemove(); 5176 } 5177 } 5178 commitOrRemove() { 5179 if (this.isEmpty()) { 5180 this.remove(); 5181 } else { 5182 this.commit(); 5183 } 5184 } 5185 commit() { 5186 if (!this.isInEditMode()) { 5187 return; 5188 } 5189 this.addToAnnotationStorage(); 5190 } 5191 addToAnnotationStorage() { 5192 this._uiManager.addToAnnotationStorage(this); 5193 } 5194 setAt(x, y, tx, ty) { 5195 const [width, height] = this.parentDimensions; 5196 [tx, ty] = this.screenToPageTranslation(tx, ty); 5197 this.x = (x + tx) / width; 5198 this.y = (y + ty) / height; 5199 this.fixAndSetPosition(); 5200 } 5201 _moveAfterPaste(baseX, baseY) { 5202 if (this.isClone) { 5203 delete this.isClone; 5204 return; 5205 } 5206 const [parentWidth, parentHeight] = this.parentDimensions; 5207 this.setAt(baseX * parentWidth, baseY * parentHeight, this.width * parentWidth, this.height * parentHeight); 5208 this._onTranslated(); 5209 } 5210 #translate([width, height], x, y) { 5211 [x, y] = this.screenToPageTranslation(x, y); 5212 this.x += x / width; 5213 this.y += y / height; 5214 this._onTranslating(this.x, this.y); 5215 this.fixAndSetPosition(); 5216 } 5217 translate(x, y) { 5218 this.#translate(this.parentDimensions, x, y); 5219 } 5220 translateInPage(x, y) { 5221 this.#initialRect ||= [this.x, this.y, this.width, this.height]; 5222 this.#translate(this.pageDimensions, x, y); 5223 this.div.scrollIntoView({ 5224 block: "nearest" 5225 }); 5226 } 5227 translationDone() { 5228 this._onTranslated(this.x, this.y); 5229 } 5230 drag(tx, ty) { 5231 this.#initialRect ||= [this.x, this.y, this.width, this.height]; 5232 const { 5233 div, 5234 parentDimensions: [parentWidth, parentHeight] 5235 } = this; 5236 this.x += tx / parentWidth; 5237 this.y += ty / parentHeight; 5238 if (this.parent && (this.x < 0 || this.x > 1 || this.y < 0 || this.y > 1)) { 5239 const { 5240 x, 5241 y 5242 } = this.div.getBoundingClientRect(); 5243 if (this.parent.findNewParent(this, x, y)) { 5244 this.x -= Math.floor(this.x); 5245 this.y -= Math.floor(this.y); 5246 } 5247 } 5248 let { 5249 x, 5250 y 5251 } = this; 5252 const [bx, by] = this.getBaseTranslation(); 5253 x += bx; 5254 y += by; 5255 const { 5256 style 5257 } = div; 5258 style.left = `${(100 * x).toFixed(2)}%`; 5259 style.top = `${(100 * y).toFixed(2)}%`; 5260 this._onTranslating(x, y); 5261 div.scrollIntoView({ 5262 block: "nearest" 5263 }
vendor: 17,821 bytes, lines 5263-5858
5263); 5264 } 5265 _onTranslating(x, y) {} 5266 _onTranslated(x, y) {} 5267 get _hasBeenMoved() { 5268 return !!this.#initialRect && (this.#initialRect[0] !== this.x || this.#initialRect[1] !== this.y); 5269 } 5270 get _hasBeenResized() { 5271 return !!this.#initialRect && (this.#initialRect[2] !== this.width || this.#initialRect[3] !== this.height); 5272 } 5273 getBaseTranslation() { 5274 const [parentWidth, parentHeight] = this.parentDimensions; 5275 const { 5276 _borderLineWidth 5277 } = AnnotationEditor; 5278 const x = _borderLineWidth / parentWidth; 5279 const y = _borderLineWidth / parentHeight; 5280 switch (this.rotation) { 5281 case 90: 5282 return [-x, y]; 5283 case 180: 5284 return [x, y]; 5285 case 270: 5286 return [x, -y]; 5287 default: 5288 return [-x, -y]; 5289 } 5290 } 5291 get _mustFixPosition() { 5292 return true; 5293 } 5294 fixAndSetPosition(rotation = this.rotation) { 5295 const { 5296 div: { 5297 style 5298 }, 5299 pageDimensions: [pageWidth, pageHeight] 5300 } = this; 5301 let { 5302 x, 5303 y, 5304 width, 5305 height 5306 } = this; 5307 width *= pageWidth; 5308 height *= pageHeight; 5309 x *= pageWidth; 5310 y *= pageHeight; 5311 if (this._mustFixPosition) { 5312 switch (rotation) { 5313 case 0: 5314 x = MathClamp(x, 0, pageWidth - width); 5315 y = MathClamp(y, 0, pageHeight - height); 5316 break; 5317 case 90: 5318 x = MathClamp(x, 0, pageWidth - height); 5319 y = MathClamp(y, width, pageHeight); 5320 break; 5321 case 180: 5322 x = MathClamp(x, width, pageWidth); 5323 y = MathClamp(y, height, pageHeight); 5324 break; 5325 case 270: 5326 x = MathClamp(x, height, pageWidth); 5327 y = MathClamp(y, 0, pageHeight - width); 5328 break; 5329 } 5330 } 5331 this.x = x /= pageWidth; 5332 this.y = y /= pageHeight; 5333 const [bx, by] = this.getBaseTranslation(); 5334 x += bx; 5335 y += by; 5336 style.left = `${(100 * x).toFixed(2)}%`; 5337 style.top = `${(100 * y).toFixed(2)}%`; 5338 this.moveInDOM(); 5339 } 5340 static #rotatePoint(x, y, angle) { 5341 switch (angle) { 5342 case 90: 5343 return [y, -x]; 5344 case 180: 5345 return [-x, -y]; 5346 case 270: 5347 return [-y, x]; 5348 default: 5349 return [x, y]; 5350 } 5351 } 5352 screenToPageTranslation(x, y) { 5353 return AnnotationEditor.#rotatePoint(x, y, this.parentRotation); 5354 } 5355 pageTranslationToScreen(x, y) { 5356 return AnnotationEditor.#rotatePoint(x, y, 360 - this.parentRotation); 5357 } 5358 #getRotationMatrix(rotation) { 5359 switch (rotation) { 5360 case 90: 5361 { 5362 const [pageWidth, pageHeight] = this.pageDimensions; 5363 return [0, -pageWidth / pageHeight, pageHeight / pageWidth, 0]; 5364 } 5365 case 180: 5366 return [-1, 0, 0, -1]; 5367 case 270: 5368 { 5369 const [pageWidth, pageHeight] = this.pageDimensions; 5370 return [0, pageWidth / pageHeight, -pageHeight / pageWidth, 0]; 5371 } 5372 default: 5373 return [1, 0, 0, 1]; 5374 } 5375 } 5376 get parentScale() { 5377 return this._uiManager.viewParameters.realScale; 5378 } 5379 get parentRotation() { 5380 return (this._uiManager.viewParameters.rotation + this.pageRotation) % 360; 5381 } 5382 get parentDimensions() { 5383 const { 5384 parentScale, 5385 pageDimensions: [pageWidth, pageHeight] 5386 } = this; 5387 return [pageWidth * parentScale, pageHeight * parentScale]; 5388 } 5389 setDims() { 5390 const { 5391 div: { 5392 style 5393 }, 5394 width, 5395 height 5396 } = this; 5397 style.width = `${(100 * width).toFixed(2)}%`; 5398 style.height = `${(100 * height).toFixed(2)}%`; 5399 } 5400 getInitialTranslation() { 5401 return [0, 0]; 5402 } 5403 #createResizers() { 5404 if (this.#resizersDiv) { 5405 return; 5406 } 5407 this.#resizersDiv = document.createElement("div"); 5408 this.#resizersDiv.classList.add("resizers"); 5409 const classes = this._willKeepAspectRatio ? ["topLeft", "topRight", "bottomRight", "bottomLeft"] : ["topLeft", "topMiddle", "topRight", "middleRight", "bottomRight", "bottomMiddle", "bottomLeft", "middleLeft"]; 5410 const signal = this._uiManager._signal; 5411 for (const name of classes) { 5412 const div = document.createElement("div"); 5413 this.#resizersDiv.append(div); 5414 div.classList.add("resizer", name); 5415 div.setAttribute("data-resizer-name", name); 5416 div.addEventListener("pointerdown", this.#resizerPointerdown.bind(this, name), { 5417 signal 5418 }); 5419 div.addEventListener("contextmenu", noContextMenu, { 5420 signal 5421 }); 5422 div.tabIndex = -1; 5423 } 5424 this.div.prepend(this.#resizersDiv); 5425 } 5426 #resizerPointerdown(name, event) { 5427 event.preventDefault(); 5428 const { 5429 isMac 5430 } = FeatureTest.platform; 5431 if (event.button !== 0 || event.ctrlKey && isMac) { 5432 return; 5433 } 5434 this.#altText?.toggle(false); 5435 const savedDraggable = this._isDraggable; 5436 this._isDraggable = false; 5437 this.#lastPointerCoords = [event.screenX, event.screenY]; 5438 const ac = new AbortController(); 5439 const signal = this._uiManager.combinedSignal(ac); 5440 this.parent.togglePointerEvents(false); 5441 window.addEventListener("pointermove", this.#resizerPointermove.bind(this, name), { 5442 passive: true, 5443 capture: true, 5444 signal 5445 }); 5446 window.addEventListener("touchmove", stopEvent, { 5447 passive: false, 5448 signal 5449 }); 5450 window.addEventListener("contextmenu", noContextMenu, { 5451 signal 5452 }); 5453 this.#savedDimensions = { 5454 savedX: this.x, 5455 savedY: this.y, 5456 savedWidth: this.width, 5457 savedHeight: this.height 5458 }; 5459 const savedParentCursor = this.parent.div.style.cursor; 5460 const savedCursor = this.div.style.cursor; 5461 this.div.style.cursor = this.parent.div.style.cursor = window.getComputedStyle(event.target).cursor; 5462 const pointerUpCallback = () => { 5463 ac.abort(); 5464 this.parent.togglePointerEvents(true); 5465 this.#altText?.toggle(true); 5466 this._isDraggable = savedDraggable; 5467 this.parent.div.style.cursor = savedParentCursor; 5468 this.div.style.cursor = savedCursor; 5469 this.#addResizeToUndoStack(); 5470 }; 5471 window.addEventListener("pointerup", pointerUpCallback, { 5472 signal 5473 }); 5474 window.addEventListener("blur", pointerUpCallback, { 5475 signal 5476 }); 5477 } 5478 #resize(x, y, width, height) { 5479 this.width = width; 5480 this.height = height; 5481 this.x = x; 5482 this.y = y; 5483 this.setDims(); 5484 this.fixAndSetPosition(); 5485 this._onResized(); 5486 } 5487 _onResized() {} 5488 #addResizeToUndoStack() { 5489 if (!this.#savedDimensions) { 5490 return; 5491 } 5492 const { 5493 savedX, 5494 savedY, 5495 savedWidth, 5496 savedHeight 5497 } = this.#savedDimensions; 5498 this.#savedDimensions = null; 5499 const newX = this.x; 5500 const newY = this.y; 5501 const newWidth = this.width; 5502 const newHeight = this.height; 5503 if (newX === savedX && newY === savedY && newWidth === savedWidth && newHeight === savedHeight) { 5504 return; 5505 } 5506 this.addCommands({ 5507 cmd: this.#resize.bind(this, newX, newY, newWidth, newHeight), 5508 undo: this.#resize.bind(this, savedX, savedY, savedWidth, savedHeight), 5509 mustExec: true 5510 }); 5511 } 5512 static _round(x) { 5513 return Math.round(x * 10000) / 10000; 5514 } 5515 #resizerPointermove(name, event) { 5516 const [parentWidth, parentHeight] = this.parentDimensions; 5517 const savedX = this.x; 5518 const savedY = this.y; 5519 const savedWidth = this.width; 5520 const savedHeight = this.height; 5521 const minWidth = AnnotationEditor.MIN_SIZE / parentWidth; 5522 const minHeight = AnnotationEditor.MIN_SIZE / parentHeight; 5523 const rotationMatrix = this.#getRotationMatrix(this.rotation); 5524 const transf = (x, y) => [rotationMatrix[0] * x + rotationMatrix[2] * y, rotationMatrix[1] * x + rotationMatrix[3] * y]; 5525 const invRotationMatrix = this.#getRotationMatrix(360 - this.rotation); 5526 const invTransf = (x, y) => [invRotationMatrix[0] * x + invRotationMatrix[2] * y, invRotationMatrix[1] * x + invRotationMatrix[3] * y]; 5527 let getPoint; 5528 let getOpposite; 5529 let isDiagonal = false; 5530 let isHorizontal = false; 5531 switch (name) { 5532 case "topLeft": 5533 isDiagonal = true; 5534 getPoint = (w, h) => [0, 0]; 5535 getOpposite = (w, h) => [w, h]; 5536 break; 5537 case "topMiddle": 5538 getPoint = (w, h) => [w / 2, 0]; 5539 getOpposite = (w, h) => [w / 2, h]; 5540 break; 5541 case "topRight": 5542 isDiagonal = true; 5543 getPoint = (w, h) => [w, 0]; 5544 getOpposite = (w, h) => [0, h]; 5545 break; 5546 case "middleRight": 5547 isHorizontal = true; 5548 getPoint = (w, h) => [w, h / 2]; 5549 getOpposite = (w, h) => [0, h / 2]; 5550 break; 5551 case "bottomRight": 5552 isDiagonal = true; 5553 getPoint = (w, h) => [w, h]; 5554 getOpposite = (w, h) => [0, 0]; 5555 break; 5556 case "bottomMiddle": 5557 getPoint = (w, h) => [w / 2, h]; 5558 getOpposite = (w, h) => [w / 2, 0]; 5559 break; 5560 case "bottomLeft": 5561 isDiagonal = true; 5562 getPoint = (w, h) => [0, h]; 5563 getOpposite = (w, h) => [w, 0]; 5564 break; 5565 case "middleLeft": 5566 isHorizontal = true; 5567 getPoint = (w, h) => [0, h / 2]; 5568 getOpposite = (w, h) => [w, h / 2]; 5569 break; 5570 } 5571 const point = getPoint(savedWidth, savedHeight); 5572 const oppositePoint = getOpposite(savedWidth, savedHeight); 5573 let transfOppositePoint = transf(...oppositePoint); 5574 const oppositeX = AnnotationEditor._round(savedX + transfOppositePoint[0]); 5575 const oppositeY = AnnotationEditor._round(savedY + transfOppositePoint[1]); 5576 let ratioX = 1; 5577 let ratioY = 1; 5578 let deltaX, deltaY; 5579 if (!event.fromKeyboard) { 5580 const { 5581 screenX, 5582 screenY 5583 } = event; 5584 const [lastScreenX, lastScreenY] = this.#lastPointerCoords; 5585 [deltaX, deltaY] = this.screenToPageTranslation(screenX - lastScreenX, screenY - lastScreenY); 5586 this.#lastPointerCoords[0] = screenX; 5587 this.#lastPointerCoords[1] = screenY; 5588 } else { 5589 ({ 5590 deltaX, 5591 deltaY 5592 } = event); 5593 } 5594 [deltaX, deltaY] = invTransf(deltaX / parentWidth, deltaY / parentHeight); 5595 if (isDiagonal) { 5596 const oldDiag = Math.hypot(savedWidth, savedHeight); 5597 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); 5598 } else if (isHorizontal) { 5599 ratioX = MathClamp(Math.abs(oppositePoint[0] - point[0] - deltaX), minWidth, 1) / savedWidth; 5600 } else { 5601 ratioY = MathClamp(Math.abs(oppositePoint[1] - point[1] - deltaY), minHeight, 1) / savedHeight; 5602 } 5603 const newWidth = AnnotationEditor._round(savedWidth * ratioX); 5604 const newHeight = AnnotationEditor._round(savedHeight * ratioY); 5605 transfOppositePoint = transf(...getOpposite(newWidth, newHeight)); 5606 const newX = oppositeX - transfOppositePoint[0]; 5607 const newY = oppositeY - transfOppositePoint[1]; 5608 this.#initialRect ||= [this.x, this.y, this.width, this.height]; 5609 this.width = newWidth; 5610 this.height = newHeight; 5611 this.x = newX; 5612 this.y = newY; 5613 this.setDims(); 5614 this.fixAndSetPosition(); 5615 this._onResizing(); 5616 } 5617 _onResizing() {} 5618 altTextFinish() { 5619 this.#altText?.finish(); 5620 } 5621 get toolbarButtons() { 5622 return null; 5623 } 5624 async addEditToolbar() { 5625 if (this._editToolbar || this.#isInEditMode) { 5626 return this._editToolbar; 5627 } 5628 this._editToolbar = new EditorToolbar(this); 5629 this.div.append(this._editToolbar.render()); 5630 const { 5631 toolbarButtons 5632 } = this; 5633 if (toolbarButtons) { 5634 for (const [name, tool] of toolbarButtons) { 5635 await this._editToolbar.addButton(name, tool); 5636 } 5637 } 5638 if (!this.hasComment) { 5639 this._editToolbar.addButton("comment", this.addCommentButton()); 5640 } 5641 this._editToolbar.addButton("delete"); 5642 return this._editToolbar; 5643 } 5644 addCommentButtonInToolbar() { 5645 this._editToolbar?.addButtonBefore("comment", this.addCommentButton(), ".deleteButton"); 5646 } 5647 removeCommentButtonFromToolbar() { 5648 this._editToolbar?.removeButton("comment"); 5649 } 5650 removeEditToolbar() { 5651 this._editToolbar?.remove(); 5652 this._editToolbar = null; 5653 this.#altText?.destroy(); 5654 } 5655 addContainer(container) { 5656 const editToolbarDiv = this._editToolbar?.div; 5657 if (editToolbarDiv) { 5658 editToolbarDiv.before(container); 5659 } else { 5660 this.div.append(container); 5661 } 5662 } 5663 getClientDimensions() { 5664 return this.div.getBoundingClientRect(); 5665 } 5666 createAltText() { 5667 if (!this.#altText) { 5668 AltText.initialize(AnnotationEditor._l10n); 5669 this.#altText = new AltText(this); 5670 if (this.#accessibilityData) { 5671 this.#altText.data = this.#accessibilityData; 5672 this.#accessibilityData = null; 5673 } 5674 } 5675 return this.#altText; 5676 } 5677 get altTextData() { 5678 return this.#altText?.data; 5679 } 5680 set altTextData(data) { 5681 if (!this.#altText) { 5682 return; 5683 } 5684 this.#altText.data = data; 5685 } 5686 get guessedAltText() { 5687 return this.#altText?.guessedText; 5688 } 5689 async setGuessedAltText(text) { 5690 await this.#altText?.setGuessedText(text); 5691 } 5692 serializeAltText(isForCopying) { 5693 return this.#altText?.serialize(isForCopying); 5694 } 5695 hasAltText() { 5696 return !!this.#altText && !this.#altText.isEmpty(); 5697 } 5698 hasAltTextData() { 5699 return this.#altText?.hasData() ?? false; 5700 } 5701 focusCommentButton() { 5702 this.#comment?.focusButton(); 5703 } 5704 addCommentButton() { 5705 return this.canAddComment ? this.#comment ||= new Comment(this) : null; 5706 } 5707 addStandaloneCommentButton() { 5708 if (!this._uiManager.hasCommentManager()) { 5709 return; 5710 } 5711 if (this.#commentStandaloneButton) { 5712 if (this._uiManager.isEditingMode()) { 5713 this.#commentStandaloneButton.classList.remove("hidden"); 5714 } 5715 return; 5716 } 5717 if (!this.hasComment) { 5718 return; 5719 } 5720 this.#commentStandaloneButton = this.#comment.renderForStandalone(); 5721 this.div.append(this.#commentStandaloneButton); 5722 } 5723 removeStandaloneCommentButton() { 5724 this.#comment.removeStandaloneCommentButton(); 5725 this.#commentStandaloneButton = null; 5726 } 5727 hideStandaloneCommentButton() { 5728 this.#commentStandaloneButton?.classList.add("hidden"); 5729 } 5730 get comment() { 5731 if (!this.#comment) { 5732 return null; 5733 } 5734 const { 5735 data: { 5736 richText, 5737 text, 5738 date, 5739 deleted 5740 } 5741 } = this.#comment; 5742 return { 5743 text, 5744 richText, 5745 date, 5746 deleted, 5747 color: this.getNonHCMColor(), 5748 opacity: this.opacity ?? 1 5749 }; 5750 } 5751 set comment(value) { 5752 this.#comment ||= new Comment(this); 5753 if (typeof value === "object" && value !== null) { 5754 this.#comment.restoreData(value); 5755 } else { 5756 this.#comment.data = value; 5757 } 5758 if (this.hasComment) { 5759 this.removeCommentButtonFromToolbar(); 5760 this.addStandaloneCommentButton(); 5761 this._uiManager.updateComment(this); 5762 } else { 5763 this.addCommentButtonInToolbar(); 5764 this.removeStandaloneCommentButton(); 5765 this._uiManager.removeComment(this); 5766 } 5767 } 5768 setCommentData({ 5769 comment, 5770 popupRef, 5771 richText 5772 }) { 5773 if (!popupRef) { 5774 return; 5775 } 5776 this.#comment ||= new Comment(this); 5777 this.#comment.setInitialText(comment, richText); 5778 if (!this.annotationElementId) { 5779 return; 5780 } 5781 const storedData = this._uiManager.getAndRemoveDataFromAnnotationStorage(this.annotationElementId); 5782 if (storedData) { 5783 this.updateFromAnnotationLayer(storedData); 5784 } 5785 } 5786 get hasEditedComment() { 5787 return this.#comment?.hasBeenEdited(); 5788 } 5789 get hasDeletedComment() { 5790 return this.#comment?.isDeleted(); 5791 } 5792 get hasComment() { 5793 return !!this.#comment && !this.#comment.isEmpty() && !this.#comment.isDeleted(); 5794 } 5795 async editComment(options) { 5796 this.#comment ||= new Comment(this); 5797 this.#comment.edit(options); 5798 } 5799 toggleComment(isSelected, visibility = undefined) { 5800 if (this.hasComment) { 5801 this._uiManager.toggleComment(this, isSelected, visibility); 5802 } 5803 } 5804 setSelectedCommentButton(selected) { 5805 this.#comment.setSelectedButton(selected); 5806 } 5807 addComment(serialized) { 5808 if (this.hasEditedComment) { 5809 const DEFAULT_POPUP_WIDTH = 180; 5810 const DEFAULT_POPUP_HEIGHT = 100; 5811 const [,,, trY] = serialized.rect; 5812 const [pageWidth] = this.pageDimensions; 5813 const [pageX] = this.pageTranslation; 5814 const blX = pageX + pageWidth + 1; 5815 const blY = trY - DEFAULT_POPUP_HEIGHT; 5816 const trX = blX + DEFAULT_POPUP_WIDTH; 5817 serialized.popup = { 5818 contents: this.comment.text, 5819 deleted: this.comment.deleted, 5820 rect: [blX, blY, trX, trY] 5821 }; 5822 } 5823 } 5824 updateFromAnnotationLayer({ 5825 popup: { 5826 contents, 5827 deleted 5828 } 5829 }) { 5830 this.#comment.data = deleted ? null : contents; 5831 } 5832 get parentBoundingClientRect() { 5833 return this.parent.boundingClientRect; 5834 } 5835 render() { 5836 const div = this.div = document.createElement("div"); 5837 div.setAttribute("data-editor-rotation", (360 - this.rotation) % 360); 5838 div.className = this.name; 5839 div.setAttribute("id", this.id); 5840 div.tabIndex = this.#disabled ? -1 : 0; 5841 div.setAttribute("role", "application"); 5842 if (this.defaultL10nId) { 5843 div.setAttribute("data-l10n-id", this.defaultL10nId); 5844 } 5845 if (!this._isVisible) { 5846 div.classList.add("hidden"); 5847 } 5848 this.setInForeground(); 5849 this.#addFocusListeners(); 5850 const [parentWidth, parentHeight] = this.parentDimensions; 5851 if (this.parentRotation % 180 !== 0) { 5852 div.style.maxWidth = `${(100 * parentHeight / parentWidth).toFixed(2)}%`; 5853 div.style.maxHeight = `${(100 * parentWidth / parentHeight).toFixed(2)}%`; 5854 } 5855 const [tx, ty] = this.getInitialTranslation(); 5856 this.translate(tx, ty); 5857 bindEvents(this, div, ["keydown", "pointerdown", "dblclick"]); 5858
vendor: 3,341 bytes, lines 5858-5951
5858if (this.isResizable && this._uiManager._supportsPinchToZoom) { 5859 this.#touchManager ||= new TouchManager({ 5860 container: div, 5861 isPinchingDisabled: () => !this.isSelected, 5862 onPinchStart: this.#touchPinchStartCallback.bind(this), 5863 onPinching: this.#touchPinchCallback.bind(this), 5864 onPinchEnd: this.#touchPinchEndCallback.bind(this), 5865 signal: this._uiManager._signal 5866 }); 5867 } 5868 this.addStandaloneCommentButton(); 5869 this._uiManager._editorUndoBar?.hide(); 5870 return div; 5871 } 5872 #touchPinchStartCallback() { 5873 this.#savedDimensions = { 5874 savedX: this.x, 5875 savedY: this.y, 5876 savedWidth: this.width, 5877 savedHeight: this.height 5878 }; 5879 this.#altText?.toggle(false); 5880 this.parent.togglePointerEvents(false); 5881 } 5882 #touchPinchCallback(_origin, prevDistance, distance) { 5883 const slowDownFactor = 0.7; 5884 let factor = slowDownFactor * (distance / prevDistance) + 1 - slowDownFactor; 5885 if (factor === 1) { 5886 return; 5887 } 5888 const rotationMatrix = this.#getRotationMatrix(this.rotation); 5889 const transf = (x, y) => [rotationMatrix[0] * x + rotationMatrix[2] * y, rotationMatrix[1] * x + rotationMatrix[3] * y]; 5890 const [parentWidth, parentHeight] = this.parentDimensions; 5891 const savedX = this.x; 5892 const savedY = this.y; 5893 const savedWidth = this.width; 5894 const savedHeight = this.height; 5895 const minWidth = AnnotationEditor.MIN_SIZE / parentWidth; 5896 const minHeight = AnnotationEditor.MIN_SIZE / parentHeight; 5897 factor = Math.max(Math.min(factor, 1 / savedWidth, 1 / savedHeight), minWidth / savedWidth, minHeight / savedHeight); 5898 const newWidth = AnnotationEditor._round(savedWidth * factor); 5899 const newHeight = AnnotationEditor._round(savedHeight * factor); 5900 if (newWidth === savedWidth && newHeight === savedHeight) { 5901 return; 5902 } 5903 this.#initialRect ||= [savedX, savedY, savedWidth, savedHeight]; 5904 const transfCenterPoint = transf(savedWidth / 2, savedHeight / 2); 5905 const centerX = AnnotationEditor._round(savedX + transfCenterPoint[0]); 5906 const centerY = AnnotationEditor._round(savedY + transfCenterPoint[1]); 5907 const newTransfCenterPoint = transf(newWidth / 2, newHeight / 2); 5908 this.x = centerX - newTransfCenterPoint[0]; 5909 this.y = centerY - newTransfCenterPoint[1]; 5910 this.width = newWidth; 5911 this.height = newHeight; 5912 this.setDims(); 5913 this.fixAndSetPosition(); 5914 this._onResizing(); 5915 } 5916 #touchPinchEndCallback() { 5917 this.#altText?.toggle(true); 5918 this.parent.togglePointerEvents(true); 5919 this.#addResizeToUndoStack(); 5920 } 5921 pointerdown(event) { 5922 const { 5923 isMac 5924 } = FeatureTest.platform; 5925 if (event.button !== 0 || event.ctrlKey && isMac) { 5926 event.preventDefault(); 5927 return; 5928 } 5929 this.#hasBeenClicked = true; 5930 if (this._isDraggable) { 5931 this.#setUpDragSession(event); 5932 return; 5933 } 5934 this.#selectOnPointerEvent(event); 5935 } 5936 #selectOnPointerEvent(event) { 5937 const { 5938 isMac 5939 } = FeatureTest.platform; 5940 if (event.ctrlKey && !isMac || event.shiftKey || event.metaKey && isMac) { 5941 this.parent.toggleSelected(this); 5942 } else { 5943 this.parent.setSelected(this); 5944 } 5945 } 5946 #setUpDragSession(event) { 5947 const { 5948 isSelected 5949 } = this; 5950 this._uiManager.setUpDragSession(); 5951 let hasDraggingStarted = false;
vendor: 8,204 bytes, lines 5952-6243
5952 const ac = new AbortController(); 5953 const signal = this._uiManager.combinedSignal(ac); 5954 const opts = { 5955 capture: true, 5956 passive: false, 5957 signal 5958 }; 5959 const cancelDrag = e => { 5960 ac.abort(); 5961 this.#dragPointerId = null; 5962 this.#hasBeenClicked = false; 5963 if (!this._uiManager.endDragSession()) { 5964 this.#selectOnPointerEvent(e); 5965 } 5966 if (hasDraggingStarted) { 5967 this._onStopDragging(); 5968 } 5969 }; 5970 if (isSelected) { 5971 this.#prevDragX = event.clientX; 5972 this.#prevDragY = event.clientY; 5973 this.#dragPointerId = event.pointerId; 5974 this.#dragPointerType = event.pointerType; 5975 window.addEventListener("pointermove", e => { 5976 if (!hasDraggingStarted) { 5977 hasDraggingStarted = true; 5978 this._uiManager.toggleComment(this, true, false); 5979 this._onStartDragging(); 5980 } 5981 const { 5982 clientX: x, 5983 clientY: y, 5984 pointerId 5985 } = e; 5986 if (pointerId !== this.#dragPointerId) { 5987 stopEvent(e); 5988 return; 5989 } 5990 const [tx, ty] = this.screenToPageTranslation(x - this.#prevDragX, y - this.#prevDragY); 5991 this.#prevDragX = x; 5992 this.#prevDragY = y; 5993 this._uiManager.dragSelectedEditors(tx, ty); 5994 }, opts); 5995 window.addEventListener("touchmove", stopEvent, opts); 5996 window.addEventListener("pointerdown", e => { 5997 if (e.pointerType === this.#dragPointerType) { 5998 if (this.#touchManager || e.isPrimary) { 5999 cancelDrag(e); 6000 } 6001 } 6002 stopEvent(e); 6003 }, opts); 6004 } 6005 const pointerUpCallback = e => { 6006 if (!this.#dragPointerId || this.#dragPointerId === e.pointerId) { 6007 cancelDrag(e); 6008 return; 6009 } 6010 stopEvent(e); 6011 }; 6012 window.addEventListener("pointerup", pointerUpCallback, { 6013 signal 6014 }); 6015 window.addEventListener("blur", pointerUpCallback, { 6016 signal 6017 }); 6018 } 6019 _onStartDragging() {} 6020 _onStopDragging() {} 6021 moveInDOM() { 6022 if (this.#moveInDOMTimeout) { 6023 clearTimeout(this.#moveInDOMTimeout); 6024 } 6025 this.#moveInDOMTimeout = setTimeout(() => { 6026 this.#moveInDOMTimeout = null; 6027 this.parent?.moveEditorInDOM(this); 6028 }, 0); 6029 } 6030 _setParentAndPosition(parent, x, y) { 6031 parent.changeParent(this); 6032 this.x = x; 6033 this.y = y; 6034 this.fixAndSetPosition(); 6035 this._onTranslated(); 6036 } 6037 getRect(tx, ty, rotation = this.rotation) { 6038 const scale = this.parentScale; 6039 const [pageWidth, pageHeight] = this.pageDimensions; 6040 const [pageX, pageY] = this.pageTranslation; 6041 const shiftX = tx / scale; 6042 const shiftY = ty / scale; 6043 const x = this.x * pageWidth; 6044 const y = this.y * pageHeight; 6045 const width = this.width * pageWidth; 6046 const height = this.height * pageHeight; 6047 switch (rotation) { 6048 case 0: 6049 return [x + shiftX + pageX, pageHeight - y - shiftY - height + pageY, x + shiftX + width + pageX, pageHeight - y - shiftY + pageY]; 6050 case 90: 6051 return [x + shiftY + pageX, pageHeight - y + shiftX + pageY, x + shiftY + height + pageX, pageHeight - y + shiftX + width + pageY]; 6052 case 180: 6053 return [x - shiftX - width + pageX, pageHeight - y + shiftY + pageY, x - shiftX + pageX, pageHeight - y + shiftY + height + pageY]; 6054 case 270: 6055 return [x - shiftY - height + pageX, pageHeight - y - shiftX - width + pageY, x - shiftY + pageX, pageHeight - y - shiftX + pageY]; 6056 default: 6057 throw new Error("Invalid rotation"); 6058 } 6059 } 6060 getRectInCurrentCoords(rect, pageHeight) { 6061 const [x1, y1, x2, y2] = rect; 6062 const width = x2 - x1; 6063 const height = y2 - y1; 6064 switch (this.rotation) { 6065 case 0: 6066 return [x1, pageHeight - y2, width, height]; 6067 case 90: 6068 return [x1, pageHeight - y1, height, width]; 6069 case 180: 6070 return [x2, pageHeight - y1, width, height]; 6071 case 270: 6072 return [x2, pageHeight - y2, height, width]; 6073 default: 6074 throw new Error("Invalid rotation"); 6075 } 6076 } 6077 getPDFRect() { 6078 return this.getRect(0, 0); 6079 } 6080 getNonHCMColor() { 6081 return this.color && AnnotationEditor._colorManager.convert(this._uiManager.getNonHCMColor(this.color)); 6082 } 6083 onUpdatedColor() { 6084 this.#comment?.onUpdatedColor(); 6085 } 6086 getData() { 6087 const { 6088 comment: { 6089 text: str, 6090 color, 6091 date, 6092 opacity, 6093 deleted, 6094 richText 6095 }, 6096 uid: id, 6097 pageIndex, 6098 creationDate, 6099 modificationDate 6100 } = this; 6101 return { 6102 id, 6103 pageIndex, 6104 rect: this.getPDFRect(), 6105 richText, 6106 contentsObj: { 6107 str 6108 }, 6109 creationDate, 6110 modificationDate: date || modificationDate, 6111 popupRef: !deleted, 6112 color, 6113 opacity 6114 }; 6115 } 6116 onceAdded(focus) {} 6117 isEmpty() { 6118 return false; 6119 } 6120 enableEditMode() { 6121 if (this.isInEditMode()) { 6122 return false; 6123 } 6124 this.parent.setEditingState(false); 6125 this.#isInEditMode = true; 6126 return true; 6127 } 6128 disableEditMode() { 6129 if (!this.isInEditMode()) { 6130 return false; 6131 } 6132 this.parent.setEditingState(true); 6133 this.#isInEditMode = false; 6134 return true; 6135 } 6136 isInEditMode() { 6137 return this.#isInEditMode; 6138 } 6139 shouldGetKeyboardEvents() { 6140 return this.#isResizerEnabledForKeyboard; 6141 } 6142 needsToBeRebuilt() { 6143 return this.div && !this.isAttachedToDOM; 6144 } 6145 get isOnScreen() { 6146 const { 6147 top, 6148 left, 6149 bottom, 6150 right 6151 } = this.getClientDimensions(); 6152 const { 6153 innerHeight, 6154 innerWidth 6155 } = window; 6156 return left < innerWidth && right > 0 && top < innerHeight && bottom > 0; 6157 } 6158 #addFocusListeners() { 6159 if (this.#focusAC || !this.div) { 6160 return; 6161 } 6162 this.#focusAC = new AbortController(); 6163 const signal = this._uiManager.combinedSignal(this.#focusAC); 6164 this.div.addEventListener("focusin", this.focusin.bind(this), { 6165 signal 6166 }); 6167 this.div.addEventListener("focusout", this.focusout.bind(this), { 6168 signal 6169 }); 6170 } 6171 rebuild() { 6172 this.#addFocusListeners(); 6173 } 6174 rotate(_angle) {} 6175 resize() {} 6176 serializeDeleted() { 6177 return { 6178 id: this.annotationElementId, 6179 deleted: true, 6180 pageIndex: this.pageIndex, 6181 popupRef: this._initialData?.popupRef || "" 6182 }; 6183 } 6184 serialize(isForCopying = false, context = null) { 6185 return { 6186 annotationType: this.mode, 6187 pageIndex: this.pageIndex, 6188 rect: this.getPDFRect(), 6189 rotation: this.rotation, 6190 structTreeParentId: this._structTreeParentId, 6191 popupRef: this._initialData?.popupRef || "" 6192 }; 6193 } 6194 static async deserialize(data, parent, uiManager) { 6195 const editor = new this.prototype.constructor({ 6196 parent, 6197 id: uiManager.getId(), 6198 uiManager, 6199 annotationElementId: data.annotationElementId, 6200 creationDate: data.creationDate, 6201 modificationDate: data.modificationDate 6202 }); 6203 editor.rotation = data.rotation; 6204 editor.#accessibilityData = data.accessibilityData; 6205 editor._isCopy = data.isCopy || false; 6206 const [pageWidth, pageHeight] = editor.pageDimensions; 6207 const [x, y, width, height] = editor.getRectInCurrentCoords(data.rect, pageHeight); 6208 editor.x = x / pageWidth; 6209 editor.y = y / pageHeight; 6210 editor.width = width / pageWidth; 6211 editor.height = height / pageHeight; 6212 return editor; 6213 } 6214 get hasBeenModified() { 6215 return !!this.annotationElementId && (this.deleted || this.serialize() !== null); 6216 } 6217 remove() { 6218 this.#focusAC?.abort(); 6219 this.#focusAC = null; 6220 if (!this.isEmpty()) { 6221 this.commit(); 6222 } 6223 if (this.parent) { 6224 this.parent.remove(this); 6225 } else { 6226 this._uiManager.removeEditor(this); 6227 } 6228 this.hideCommentPopup(); 6229 if (this.#moveInDOMTimeout) { 6230 clearTimeout(this.#moveInDOMTimeout); 6231 this.#moveInDOMTimeout = null; 6232 } 6233 this.#stopResizing(); 6234 this.removeEditToolbar(); 6235 if (this.#telemetryTimeouts) { 6236 for (const timeout of this.#telemetryTimeouts.values()) { 6237 clearTimeout(timeout); 6238 } 6239 this.#telemetryTimeouts = null; 6240 } 6241 this.parent = null; 6242 this.#touchManager?.destroy(); 6243 this.#touchManager
vendor: 22,323 bytes, lines 6243-7078
6243 = null; 6244 this.#fakeAnnotation?.remove(); 6245 this.#fakeAnnotation = null; 6246 } 6247 get isResizable() { 6248 return false; 6249 } 6250 makeResizable() { 6251 if (this.isResizable) { 6252 this.#createResizers(); 6253 this.#resizersDiv.classList.remove("hidden"); 6254 } 6255 } 6256 get toolbarPosition() { 6257 return null; 6258 } 6259 get commentButtonPosition() { 6260 return this._uiManager.direction === "ltr" ? [1, 0] : [0, 0]; 6261 } 6262 get commentButtonPositionInPage() { 6263 const { 6264 commentButtonPosition: [posX, posY] 6265 } = this; 6266 const [blX, blY, trX, trY] = this.getPDFRect(); 6267 return [AnnotationEditor._round(blX + (trX - blX) * posX), AnnotationEditor._round(blY + (trY - blY) * (1 - posY))]; 6268 } 6269 get commentButtonColor() { 6270 return this._uiManager.makeCommentColor(this.getNonHCMColor(), this.opacity); 6271 } 6272 get commentPopupPosition() { 6273 return this.#comment.commentPopupPositionInLayer; 6274 } 6275 set commentPopupPosition(pos) { 6276 this.#comment.commentPopupPositionInLayer = pos; 6277 } 6278 hasDefaultPopupPosition() { 6279 return this.#comment.hasDefaultPopupPosition(); 6280 } 6281 get commentButtonWidth() { 6282 return this.#comment.commentButtonWidth; 6283 } 6284 get elementBeforePopup() { 6285 return this.div; 6286 } 6287 setCommentButtonStates(options) { 6288 this.#comment?.setCommentButtonStates(options); 6289 } 6290 keydown(event) { 6291 if (!this.isResizable || event.target !== this.div || event.key !== "Enter") { 6292 return; 6293 } 6294 this._uiManager.setSelected(this); 6295 this.#savedDimensions = { 6296 savedX: this.x, 6297 savedY: this.y, 6298 savedWidth: this.width, 6299 savedHeight: this.height 6300 }; 6301 const children = this.#resizersDiv.children; 6302 if (!this.#allResizerDivs) { 6303 this.#allResizerDivs = Array.from(children); 6304 const boundResizerKeydown = this.#resizerKeydown.bind(this); 6305 const boundResizerBlur = this.#resizerBlur.bind(this); 6306 const signal = this._uiManager._signal; 6307 for (const div of this.#allResizerDivs) { 6308 const name = div.getAttribute("data-resizer-name"); 6309 div.setAttribute("role", "spinbutton"); 6310 div.addEventListener("keydown", boundResizerKeydown, { 6311 signal 6312 }); 6313 div.addEventListener("blur", boundResizerBlur, { 6314 signal 6315 }); 6316 div.addEventListener("focus", this.#resizerFocus.bind(this, name), { 6317 signal 6318 }); 6319 div.setAttribute("data-l10n-id", AnnotationEditor._l10nResizer[name]); 6320 } 6321 } 6322 const first = this.#allResizerDivs[0]; 6323 let firstPosition = 0; 6324 for (const div of children) { 6325 if (div === first) { 6326 break; 6327 } 6328 firstPosition++; 6329 } 6330 const nextFirstPosition = (360 - this.rotation + this.parentRotation) % 360 / 90 * (this.#allResizerDivs.length / 4); 6331 if (nextFirstPosition !== firstPosition) { 6332 if (nextFirstPosition < firstPosition) { 6333 for (let i = 0; i < firstPosition - nextFirstPosition; i++) { 6334 this.#resizersDiv.append(this.#resizersDiv.firstElementChild); 6335 } 6336 } else if (nextFirstPosition > firstPosition) { 6337 for (let i = 0; i < nextFirstPosition - firstPosition; i++) { 6338 this.#resizersDiv.firstElementChild.before(this.#resizersDiv.lastElementChild); 6339 } 6340 } 6341 let i = 0; 6342 for (const child of children) { 6343 const div = this.#allResizerDivs[i++]; 6344 const name = div.getAttribute("data-resizer-name"); 6345 child.setAttribute("data-l10n-id", AnnotationEditor._l10nResizer[name]); 6346 } 6347 } 6348 this.#setResizerTabIndex(0); 6349 this.#isResizerEnabledForKeyboard = true; 6350 this.#resizersDiv.firstElementChild.focus({ 6351 focusVisible: true 6352 }); 6353 event.preventDefault(); 6354 event.stopImmediatePropagation(); 6355 } 6356 #resizerKeydown(event) { 6357 AnnotationEditor._resizerKeyboardManager.exec(this, event); 6358 } 6359 #resizerBlur(event) { 6360 if (this.#isResizerEnabledForKeyboard && event.relatedTarget?.parentNode !== this.#resizersDiv) { 6361 this.#stopResizing(); 6362 } 6363 } 6364 #resizerFocus(name) { 6365 this.#focusedResizerName = this.#isResizerEnabledForKeyboard ? name : ""; 6366 } 6367 #setResizerTabIndex(value) { 6368 if (!this.#allResizerDivs) { 6369 return; 6370 } 6371 for (const div of this.#allResizerDivs) { 6372 div.tabIndex = value; 6373 } 6374 } 6375 _resizeWithKeyboard(x, y) { 6376 if (!this.#isResizerEnabledForKeyboard) { 6377 return; 6378 } 6379 this.#resizerPointermove(this.#focusedResizerName, { 6380 deltaX: x, 6381 deltaY: y, 6382 fromKeyboard: true 6383 }); 6384 } 6385 #stopResizing() { 6386 this.#isResizerEnabledForKeyboard = false; 6387 this.#setResizerTabIndex(-1); 6388 this.#addResizeToUndoStack(); 6389 } 6390 _stopResizingWithKeyboard() { 6391 this.#stopResizing(); 6392 this.div.focus(); 6393 } 6394 select() { 6395 if (this.isSelected && this._editToolbar) { 6396 this._editToolbar.show(); 6397 return; 6398 } 6399 this.isSelected = true; 6400 this.makeResizable(); 6401 this.div?.classList.add("selectedEditor"); 6402 if (!this._editToolbar) { 6403 this.addEditToolbar().then(() => { 6404 if (this.div?.classList.contains("selectedEditor")) { 6405 this._editToolbar?.show(); 6406 } 6407 }); 6408 return; 6409 } 6410 this._editToolbar?.show(); 6411 this.#altText?.toggleAltTextBadge(false); 6412 } 6413 focus() { 6414 if (this.div && !this.div.contains(document.activeElement)) { 6415 setTimeout(() => this.div?.focus({ 6416 preventScroll: true 6417 }), 0); 6418 } 6419 } 6420 unselect() { 6421 if (!this.isSelected) { 6422 return; 6423 } 6424 this.isSelected = false; 6425 this.#resizersDiv?.classList.add("hidden"); 6426 this.div?.classList.remove("selectedEditor"); 6427 if (this.div?.contains(document.activeElement)) { 6428 this._uiManager.currentLayer.div.focus({ 6429 preventScroll: true 6430 }); 6431 } 6432 this._editToolbar?.hide(); 6433 this.#altText?.toggleAltTextBadge(true); 6434 this.hideCommentPopup(); 6435 } 6436 hideCommentPopup() { 6437 if (this.hasComment) { 6438 this._uiManager.toggleComment(null); 6439 } 6440 } 6441 updateParams(type, value) {} 6442 disableEditing() {} 6443 enableEditing() {} 6444 get canChangeContent() { 6445 return false; 6446 } 6447 enterInEditMode() { 6448 if (!this.canChangeContent) { 6449 return; 6450 } 6451 this.enableEditMode(); 6452 this.div.focus(); 6453 } 6454 dblclick(event) { 6455 if (event.target.nodeName === "BUTTON") { 6456 return; 6457 } 6458 this.enterInEditMode(); 6459 this.parent.updateToolbar({ 6460 mode: this.constructor._editorType, 6461 editId: this.uid 6462 }); 6463 } 6464 getElementForAltText() { 6465 return this.div; 6466 } 6467 get contentDiv() { 6468 return this.div; 6469 } 6470 get isEditing() { 6471 return this.#isEditing; 6472 } 6473 set isEditing(value) { 6474 this.#isEditing = value; 6475 if (!this.parent) { 6476 return; 6477 } 6478 if (value) { 6479 this.parent.setSelected(this); 6480 this.parent.setActiveEditor(this); 6481 } else { 6482 this.parent.setActiveEditor(null); 6483 } 6484 } 6485 static get MIN_SIZE() { 6486 return 16; 6487 } 6488 static canCreateNewEmptyEditor() { 6489 return true; 6490 } 6491 get telemetryInitialData() { 6492 return { 6493 action: "added" 6494 }; 6495 } 6496 get telemetryFinalData() { 6497 return null; 6498 } 6499 _reportTelemetry(data, mustWait = false) { 6500 if (mustWait) { 6501 this.#telemetryTimeouts ||= new Map(); 6502 const { 6503 action 6504 } = data; 6505 let timeout = this.#telemetryTimeouts.get(action); 6506 if (timeout) { 6507 clearTimeout(timeout); 6508 } 6509 timeout = setTimeout(() => { 6510 this._reportTelemetry(data); 6511 this.#telemetryTimeouts.delete(action); 6512 if (this.#telemetryTimeouts.size === 0) { 6513 this.#telemetryTimeouts = null; 6514 } 6515 }, AnnotationEditor._telemetryTimeout); 6516 this.#telemetryTimeouts.set(action, timeout); 6517 return; 6518 } 6519 data.type ||= this.editorType; 6520 this._uiManager._eventBus.dispatch("reporttelemetry", { 6521 source: this, 6522 details: { 6523 type: "editing", 6524 data 6525 } 6526 }); 6527 } 6528 show(visible = this._isVisible) { 6529 this.div.classList.toggle("hidden", !visible); 6530 this._isVisible = visible; 6531 } 6532 enable() { 6533 if (this.div) { 6534 this.div.tabIndex = 0; 6535 } 6536 this.#disabled = false; 6537 } 6538 disable() { 6539 if (this.div) { 6540 this.div.tabIndex = -1; 6541 } 6542 this.#disabled = true; 6543 } 6544 updateFakeAnnotationElement(annotationLayer) { 6545 if (!this.#fakeAnnotation && !this.deleted) { 6546 this.#fakeAnnotation = annotationLayer.addFakeAnnotation(this); 6547 return; 6548 } 6549 if (this.deleted) { 6550 this.#fakeAnnotation.remove(); 6551 this.#fakeAnnotation = null; 6552 return; 6553 } 6554 if (this.hasEditedComment || this._hasBeenMoved || this._hasBeenResized) { 6555 this.#fakeAnnotation.updateEdited({ 6556 rect: this.getPDFRect(), 6557 popup: this.comment 6558 }); 6559 } 6560 } 6561 renderAnnotationElement(annotation) { 6562 if (this.deleted) { 6563 annotation.hide(); 6564 return null; 6565 } 6566 let content = annotation.container.querySelector(".annotationContent"); 6567 if (!content) { 6568 content = document.createElement("div"); 6569 content.classList.add("annotationContent", this.editorType); 6570 annotation.container.prepend(content); 6571 } else if (content.nodeName === "CANVAS") { 6572 const canvas = content; 6573 content = document.createElement("div"); 6574 content.classList.add("annotationContent", this.editorType); 6575 canvas.before(content); 6576 } 6577 return content; 6578 } 6579 resetAnnotationElement(annotation) { 6580 const { 6581 firstElementChild 6582 } = annotation.container; 6583 if (firstElementChild?.nodeName === "DIV" && firstElementChild.classList.contains("annotationContent")) { 6584 firstElementChild.remove(); 6585 } 6586 } 6587} 6588class FakeEditor extends AnnotationEditor { 6589 constructor(params) { 6590 super(params); 6591 this.annotationElementId = params.annotationElementId; 6592 this.deleted = true; 6593 } 6594 serialize() { 6595 return this.serializeDeleted(); 6596 } 6597} 6598 6599;// ./src/shared/murmurhash3.js 6600const SEED = 0xc3d2e1f0; 6601const MASK_HIGH = 0xffff0000; 6602const MASK_LOW = 0xffff; 6603class MurmurHash3_64 { 6604 constructor(seed) { 6605 this.h1 = seed ? seed & 0xffffffff : SEED; 6606 this.h2 = seed ? seed & 0xffffffff : SEED; 6607 } 6608 update(input) { 6609 let data, length; 6610 if (typeof input === "string") { 6611 data = new Uint8Array(input.length * 2); 6612 length = 0; 6613 for (let i = 0, ii = input.length; i < ii; i++) { 6614 const code = input.charCodeAt(i); 6615 if (code <= 0xff) { 6616 data[length++] = code; 6617 } else { 6618 data[length++] = code >>> 8; 6619 data[length++] = code & 0xff; 6620 } 6621 } 6622 } else if (ArrayBuffer.isView(input)) { 6623 data = input.slice(); 6624 length = data.byteLength; 6625 } else { 6626 throw new Error("Invalid data format, must be a string or TypedArray."); 6627 } 6628 const blockCounts = length >> 2; 6629 const tailLength = length - blockCounts * 4; 6630 const dataUint32 = new Uint32Array(data.buffer, 0, blockCounts); 6631 let k1 = 0, 6632 k2 = 0; 6633 let h1 = this.h1, 6634 h2 = this.h2; 6635 const C1 = 0xcc9e2d51, 6636 C2 = 0x1b873593; 6637 const C1_LOW = C1 & MASK_LOW, 6638 C2_LOW = C2 & MASK_LOW; 6639 for (let i = 0; i < blockCounts; i++) { 6640 if (i & 1) { 6641 k1 = dataUint32[i]; 6642 k1 = k1 * C1 & MASK_HIGH | k1 * C1_LOW & MASK_LOW; 6643 k1 = k1 << 15 | k1 >>> 17; 6644 k1 = k1 * C2 & MASK_HIGH | k1 * C2_LOW & MASK_LOW; 6645 h1 ^= k1; 6646 h1 = h1 << 13 | h1 >>> 19; 6647 h1 = h1 * 5 + 0xe6546b64; 6648 } else { 6649 k2 = dataUint32[i]; 6650 k2 = k2 * C1 & MASK_HIGH | k2 * C1_LOW & MASK_LOW; 6651 k2 = k2 << 15 | k2 >>> 17; 6652 k2 = k2 * C2 & MASK_HIGH | k2 * C2_LOW & MASK_LOW; 6653 h2 ^= k2; 6654 h2 = h2 << 13 | h2 >>> 19; 6655 h2 = h2 * 5 + 0xe6546b64; 6656 } 6657 } 6658 k1 = 0; 6659 switch (tailLength) { 6660 case 3: 6661 k1 ^= data[blockCounts * 4 + 2] << 16; 6662 case 2: 6663 k1 ^= data[blockCounts * 4 + 1] << 8; 6664 case 1: 6665 k1 ^= data[blockCounts * 4]; 6666 k1 = k1 * C1 & MASK_HIGH | k1 * C1_LOW & MASK_LOW; 6667 k1 = k1 << 15 | k1 >>> 17; 6668 k1 = k1 * C2 & MASK_HIGH | k1 * C2_LOW & MASK_LOW; 6669 if (blockCounts & 1) { 6670 h1 ^= k1; 6671 } else { 6672 h2 ^= k1; 6673 } 6674 } 6675 this.h1 = h1; 6676 this.h2 = h2; 6677 } 6678 hexdigest() { 6679 let h1 = this.h1, 6680 h2 = this.h2; 6681 h1 ^= h2 >>> 1; 6682 h1 = h1 * 0xed558ccd & MASK_HIGH | h1 * 0x8ccd & MASK_LOW; 6683 h2 = h2 * 0xff51afd7 & MASK_HIGH | ((h2 << 16 | h1 >>> 16) * 0xafd7ed55 & MASK_HIGH) >>> 16; 6684 h1 ^= h2 >>> 1; 6685 h1 = h1 * 0x1a85ec53 & MASK_HIGH | h1 * 0xec53 & MASK_LOW; 6686 h2 = h2 * 0xc4ceb9fe & MASK_HIGH | ((h2 << 16 | h1 >>> 16) * 0xb9fe1a85 & MASK_HIGH) >>> 16; 6687 h1 ^= h2 >>> 1; 6688 return (h1 >>> 0).toString(16).padStart(8, "0") + (h2 >>> 0).toString(16).padStart(8, "0"); 6689 } 6690} 6691 6692;// ./src/display/annotation_storage.js 6693 6694 6695 6696const SerializableEmpty = Object.freeze({ 6697 map: null, 6698 hash: "", 6699 transfer: undefined 6700}); 6701class AnnotationStorage { 6702 #modified = false; 6703 #modifiedIds = null; 6704 #editorsMap = null; 6705 #storage = new Map(); 6706 onSetModified = null; 6707 onResetModified = null; 6708 onAnnotationEditor = null; 6709 getValue(key, defaultValue) { 6710 const value = this.#storage.get(key); 6711 if (value === undefined) { 6712 return defaultValue; 6713 } 6714 return Object.assign(defaultValue, value); 6715 } 6716 getRawValue(key) { 6717 return this.#storage.get(key); 6718 } 6719 remove(key) { 6720 const storedValue = this.#storage.get(key); 6721 if (storedValue === undefined) { 6722 return; 6723 } 6724 if (storedValue instanceof AnnotationEditor) { 6725 this.#editorsMap.delete(storedValue.annotationElementId); 6726 } 6727 this.#storage.delete(key); 6728 if (this.#storage.size === 0) { 6729 this.resetModified(); 6730 } 6731 if (this.#storage.values().some(v => v instanceof AnnotationEditor)) { 6732 return; 6733 } 6734 this.onAnnotationEditor?.(null); 6735 } 6736 setValue(key, value) { 6737 const obj = this.#storage.get(key); 6738 let modified = false; 6739 if (obj !== undefined) { 6740 for (const [entry, val] of Object.entries(value)) { 6741 if (obj[entry] !== val) { 6742 modified = true; 6743 obj[entry] = val; 6744 } 6745 } 6746 } else { 6747 modified = true; 6748 this.#storage.set(key, value); 6749 } 6750 if (modified) { 6751 this.#setModified(); 6752 } 6753 if (value instanceof AnnotationEditor) { 6754 (this.#editorsMap ||= new Map()).set(value.annotationElementId, value); 6755 this.onAnnotationEditor?.(value.constructor._type); 6756 } 6757 } 6758 has(key) { 6759 return this.#storage.has(key); 6760 } 6761 get size() { 6762 return this.#storage.size; 6763 } 6764 #setModified() { 6765 if (!this.#modified) { 6766 this.#modified = true; 6767 this.onSetModified?.(); 6768 } 6769 } 6770 resetModified() { 6771 if (this.#modified) { 6772 this.#modified = false; 6773 this.onResetModified?.(); 6774 } 6775 } 6776 get print() { 6777 return new PrintAnnotationStorage(this); 6778 } 6779 get serializable() { 6780 if (this.#storage.size === 0) { 6781 return SerializableEmpty; 6782 } 6783 const map = new Map(), 6784 hash = new MurmurHash3_64(), 6785 transfer = []; 6786 const context = Object.create(null); 6787 let hasBitmap = false; 6788 for (const [key, val] of this.#storage) { 6789 const serialized = val instanceof AnnotationEditor ? val.serialize(false, context) : val; 6790 if (val.page) { 6791 val.pageIndex = val.page._pageIndex; 6792 delete val.page; 6793 } 6794 if (serialized) { 6795 map.set(key, serialized); 6796 hash.update(`${key}:${JSON.stringify(serialized)}`); 6797 hasBitmap ||= !!serialized.bitmap; 6798 } 6799 } 6800 if (hasBitmap) { 6801 for (const value of map.values()) { 6802 if (value.bitmap) { 6803 transfer.push(value.bitmap); 6804 } 6805 } 6806 } 6807 return map.size > 0 ? { 6808 map, 6809 hash: hash.hexdigest(), 6810 transfer 6811 } : SerializableEmpty; 6812 } 6813 get editorStats() { 6814 let stats = null; 6815 const typeToEditor = new Map(); 6816 let numberOfEditedComments = 0; 6817 let numberOfDeletedComments = 0; 6818 for (const value of this.#storage.values()) { 6819 if (!(value instanceof AnnotationEditor)) { 6820 if (value.popup) { 6821 if (value.popup.deleted) { 6822 numberOfDeletedComments += 1; 6823 } else { 6824 numberOfEditedComments += 1; 6825 } 6826 } 6827 continue; 6828 } 6829 if (value.isCommentDeleted) { 6830 numberOfDeletedComments += 1; 6831 } else if (value.hasEditedComment) { 6832 numberOfEditedComments += 1; 6833 } 6834 const editorStats = value.telemetryFinalData; 6835 if (!editorStats) { 6836 continue; 6837 } 6838 const { 6839 type 6840 } = editorStats; 6841 typeToEditor.getOrInsertComputed(type, () => Object.getPrototypeOf(value).constructor); 6842 stats ||= Object.create(null); 6843 const map = stats[type] ||= new Map(); 6844 for (const [key, val] of Object.entries(editorStats)) { 6845 if (key === "type") { 6846 continue; 6847 } 6848 const counters = map.getOrInsertComputed(key, makeMap); 6849 counters.set(val, (counters.get(val) ?? 0) + 1); 6850 } 6851 } 6852 if (numberOfDeletedComments > 0 || numberOfEditedComments > 0) { 6853 stats ||= Object.create(null); 6854 stats.comments = { 6855 deleted: numberOfDeletedComments, 6856 edited: numberOfEditedComments 6857 }; 6858 } 6859 if (!stats) { 6860 return null; 6861 } 6862 for (const [type, editor] of typeToEditor) { 6863 stats[type] = editor.computeTelemetryFinalData(stats[type]); 6864 } 6865 return stats; 6866 } 6867 resetModifiedIds() { 6868 this.#modifiedIds = null; 6869 } 6870 updateEditor(annotationId, data) { 6871 const value = this.#editorsMap?.get(annotationId); 6872 if (value) { 6873 value.updateFromAnnotationLayer(data); 6874 return true; 6875 } 6876 return false; 6877 } 6878 getEditor(annotationId) { 6879 return this.#editorsMap?.get(annotationId) || null; 6880 } 6881 get modifiedIds() { 6882 if (this.#modifiedIds) { 6883 return this.#modifiedIds; 6884 } 6885 const ids = []; 6886 if (this.#editorsMap) { 6887 for (const value of this.#editorsMap.values()) { 6888 if (!value.serialize()) { 6889 continue; 6890 } 6891 ids.push(value.annotationElementId); 6892 } 6893 } 6894 let hash = ""; 6895 if (ids.length) { 6896 const h = new MurmurHash3_64(); 6897 h.update(ids.join(",")); 6898 hash = h.hexdigest(); 6899 } 6900 return this.#modifiedIds = { 6901 ids: new Set(ids), 6902 hash 6903 }; 6904 } 6905 [Symbol.iterator]() { 6906 return this.#storage.entries(); 6907 } 6908} 6909class PrintAnnotationStorage extends AnnotationStorage { 6910 #serializable = SerializableEmpty; 6911 constructor(parent) { 6912 super(); 6913 const { 6914 serializable 6915 } = parent; 6916 if (serializable === SerializableEmpty) { 6917 return; 6918 } 6919 const { 6920 map, 6921 hash, 6922 transfer 6923 } = serializable; 6924 const clone = structuredClone(map, transfer ? { 6925 transfer 6926 } : null); 6927 this.#serializable = { 6928 map: clone, 6929 hash, 6930 transfer: [] 6931 }; 6932 } 6933 get print() { 6934 unreachable("Should not call PrintAnnotationStorage.print"); 6935 } 6936 get serializable() { 6937 return this.#serializable; 6938 } 6939 get modifiedIds() { 6940 return shadow(this, "modifiedIds", { 6941 ids: new Set(), 6942 hash: "" 6943 }); 6944 } 6945} 6946 6947;// ./src/display/canvas_dependency_tracker.js 6948 6949 6950 6951const FORCED_DEPENDENCY_LABEL = "__forcedDependency"; 6952const { 6953 floor, 6954 ceil 6955} = Math; 6956function expandBBox(array, index, minX, minY, maxX, maxY) { 6957 array[index * 4 + 0] = Math.min(array[index * 4 + 0], minX); 6958 array[index * 4 + 1] = Math.min(array[index * 4 + 1], minY); 6959 array[index * 4 + 2] = Math.max(array[index * 4 + 2], maxX); 6960 array[index * 4 + 3] = Math.max(array[index * 4 + 3], maxY); 6961} 6962function scaleCharBBox(scaleX, scaleY, x, y, bbox) { 6963 let temp; 6964 if (scaleX) { 6965 if (scaleX < 0) { 6966 temp = bbox[0]; 6967 bbox[0] = bbox[2]; 6968 bbox[2] = temp; 6969 } 6970 bbox[0] *= scaleX; 6971 bbox[2] *= scaleX; 6972 if (scaleY < 0) { 6973 temp = bbox[1]; 6974 bbox[1] = bbox[3]; 6975 bbox[3] = temp; 6976 } 6977 bbox[1] *= scaleY; 6978 bbox[3] *= scaleY; 6979 } else { 6980 bbox.fill(0); 6981 } 6982 bbox[0] += x; 6983 bbox[1] += y; 6984 bbox[2] += x; 6985 bbox[3] += y; 6986} 6987const EMPTY_BBOX = new Uint32Array(new Uint8Array([255, 255, 0, 0]).buffer)[0]; 6988class BBoxReader { 6989 #bboxes; 6990 #coords; 6991 constructor(bboxes, coords) { 6992 this.#bboxes = bboxes; 6993 this.#coords = coords; 6994 } 6995 get length() { 6996 return this.#bboxes.length; 6997 } 6998 isEmpty(i) { 6999 return this.#bboxes[i] === EMPTY_BBOX; 7000 } 7001 minX(i) { 7002 return this.#coords[i * 4 + 0] / 256; 7003 } 7004 minY(i) { 7005 return this.#coords[i * 4 + 1] / 256; 7006 } 7007 maxX(i) { 7008 return (this.#coords[i * 4 + 2] + 1) / 256; 7009 } 7010 maxY(i) { 7011 return (this.#coords[i * 4 + 3] + 1) / 256; 7012 } 7013} 7014const ensureDebugMetadata = (map, key) => map?.getOrInsertComputed(key, () => ({ 7015 dependencies: new Set(), 7016 isRenderingOperation: false 7017})); 7018class CanvasBBoxTracker { 7019 #baseTransformStack = [[1, 0, 0, 1, 0, 0]]; 7020 #clipBox = [-Infinity, -Infinity, Infinity, Infinity]; 7021 #pendingBBox = new Float64Array(BBOX_INIT); 7022 _pendingBBoxIdx = -1; 7023 #canvasWidth; 7024 #canvasHeight; 7025 #bboxesCoords; 7026 #bboxes; 7027 _savesStack = []; 7028 _markedContentStack = []; 7029 constructor(canvas, operationsCount) { 7030 this.#canvasWidth = canvas.width; 7031 this.#canvasHeight = canvas.height; 7032 this.#initializeBBoxes(operationsCount); 7033 } 7034 growOperationsCount(operationsCount) { 7035 if (operationsCount >= this.#bboxes.length) { 7036 this.#initializeBBoxes(operationsCount, this.#bboxes); 7037 } 7038 } 7039 #initializeBBoxes(operationsCount, oldBBoxes) { 7040 const buffer = new ArrayBuffer(operationsCount * 4); 7041 this.#bboxesCoords = new Uint8ClampedArray(buffer); 7042 this.#bboxes = new Uint32Array(buffer); 7043 if (oldBBoxes && oldBBoxes.length > 0) { 7044 this.#bboxes.set(oldBBoxes); 7045 this.#bboxes.fill(EMPTY_BBOX, oldBBoxes.length); 7046 } else { 7047 this.#bboxes.fill(EMPTY_BBOX); 7048 } 7049 } 7050 get clipBox() { 7051 return this.#clipBox; 7052 } 7053 save(opIdx) { 7054 this.#clipBox = { 7055 __proto__: this.#clipBox 7056 }; 7057 this._savesStack.push(opIdx); 7058 return this; 7059 } 7060 restore(opIdx, onSavePopped) { 7061 const previous = Object.getPrototypeOf(this.#clipBox); 7062 if (previous === null) { 7063 return this; 7064 } 7065 this.#clipBox = previous; 7066 const lastSave = this._savesStack.pop(); 7067 if (lastSave !== undefined) { 7068 onSavePopped?.(lastSave, opIdx); 7069 this.#bboxes[opIdx] = this.#bboxes[lastSave]; 7070 } 7071 return this; 7072 } 7073 recordOpenMarker(idx) { 7074 this._savesStack.push(idx); 7075 return this; 7076 } 7077 getOpenMarker() { 7078
7078if (this._savesStack.length === 0) { 7079 return null; 7080 } 7081 return
vendor: 20,732 bytes, lines 7081-7707
7081 this._savesStack.at(-1); 7082 } 7083 recordCloseMarker(opIdx, onSavePopped) { 7084 const lastSave = this._savesStack.pop(); 7085 if (lastSave !== undefined) { 7086 onSavePopped?.(lastSave, opIdx); 7087 this.#bboxes[opIdx] = this.#bboxes[lastSave]; 7088 } 7089 return this; 7090 } 7091 beginMarkedContent(opIdx) { 7092 this._markedContentStack.push(opIdx); 7093 return this; 7094 } 7095 endMarkedContent(opIdx, onSavePopped) { 7096 const lastSave = this._markedContentStack.pop(); 7097 if (lastSave !== undefined) { 7098 onSavePopped?.(lastSave, opIdx); 7099 this.#bboxes[opIdx] = this.#bboxes[lastSave]; 7100 } 7101 return this; 7102 } 7103 pushBaseTransform(ctx) { 7104 this.#baseTransformStack.push(Util.multiplyByDOMMatrix(this.#baseTransformStack.at(-1), ctx.getTransform())); 7105 return this; 7106 } 7107 popBaseTransform() { 7108 if (this.#baseTransformStack.length > 1) { 7109 this.#baseTransformStack.pop(); 7110 } 7111 return this; 7112 } 7113 resetBBox(idx) { 7114 if (this._pendingBBoxIdx !== idx) { 7115 this._pendingBBoxIdx = idx; 7116 this.#pendingBBox.set(BBOX_INIT, 0); 7117 } 7118 return this; 7119 } 7120 recordClipBox(idx, ctx, minX, maxX, minY, maxY) { 7121 const transform = Util.multiplyByDOMMatrix(this.#baseTransformStack.at(-1), ctx.getTransform()); 7122 const clipBox = BBOX_INIT.slice(); 7123 Util.axialAlignedBoundingBox([minX, minY, maxX, maxY], transform, clipBox); 7124 const intersection = Util.intersect(this.#clipBox, clipBox); 7125 if (intersection) { 7126 this.#clipBox[0] = intersection[0]; 7127 this.#clipBox[1] = intersection[1]; 7128 this.#clipBox[2] = intersection[2]; 7129 this.#clipBox[3] = intersection[3]; 7130 } else { 7131 this.#clipBox[0] = this.#clipBox[1] = Infinity; 7132 this.#clipBox[2] = this.#clipBox[3] = -Infinity; 7133 } 7134 return this; 7135 } 7136 recordBBox(idx, ctx, minX, maxX, minY, maxY) { 7137 const clipBox = this.#clipBox; 7138 if (clipBox[0] === Infinity) { 7139 return this; 7140 } 7141 const transform = Util.multiplyByDOMMatrix(this.#baseTransformStack.at(-1), ctx.getTransform()); 7142 if (clipBox[0] === -Infinity) { 7143 Util.axialAlignedBoundingBox([minX, minY, maxX, maxY], transform, this.#pendingBBox); 7144 return this; 7145 } 7146 const bbox = BBOX_INIT.slice(); 7147 Util.axialAlignedBoundingBox([minX, minY, maxX, maxY], transform, bbox); 7148 this.#pendingBBox[0] = MathClamp(bbox[0], clipBox[0], this.#pendingBBox[0]); 7149 this.#pendingBBox[1] = MathClamp(bbox[1], clipBox[1], this.#pendingBBox[1]); 7150 this.#pendingBBox[2] = MathClamp(bbox[2], this.#pendingBBox[2], clipBox[2]); 7151 this.#pendingBBox[3] = MathClamp(bbox[3], this.#pendingBBox[3], clipBox[3]); 7152 return this; 7153 } 7154 recordFullPageBBox(idx) { 7155 this.#pendingBBox[0] = Math.max(0, this.#clipBox[0]); 7156 this.#pendingBBox[1] = Math.max(0, this.#clipBox[1]); 7157 this.#pendingBBox[2] = Math.min(this.#canvasWidth, this.#clipBox[2]); 7158 this.#pendingBBox[3] = Math.min(this.#canvasHeight, this.#clipBox[3]); 7159 return this; 7160 } 7161 recordOperation(idx, preserve = false, dependencyLists) { 7162 if (this._pendingBBoxIdx !== idx) { 7163 return this; 7164 } 7165 const minX = floor(this.#pendingBBox[0] * 256 / this.#canvasWidth); 7166 const minY = floor(this.#pendingBBox[1] * 256 / this.#canvasHeight); 7167 const maxX = ceil(this.#pendingBBox[2] * 256 / this.#canvasWidth); 7168 const maxY = ceil(this.#pendingBBox[3] * 256 / this.#canvasHeight); 7169 expandBBox(this.#bboxesCoords, idx, minX, minY, maxX, maxY); 7170 if (dependencyLists) { 7171 for (const dependencies of dependencyLists) { 7172 for (const depIdx of dependencies) { 7173 if (depIdx !== idx) { 7174 expandBBox(this.#bboxesCoords, depIdx, minX, minY, maxX, maxY); 7175 } 7176 } 7177 } 7178 } 7179 if (!preserve) { 7180 this._pendingBBoxIdx = -1; 7181 } 7182 return this; 7183 } 7184 bboxToClipBoxDropOperation(idx) { 7185 if (this._pendingBBoxIdx === idx) { 7186 this._pendingBBoxIdx = -1; 7187 this.#clipBox[0] = Math.max(this.#clipBox[0], this.#pendingBBox[0]); 7188 this.#clipBox[1] = Math.max(this.#clipBox[1], this.#pendingBBox[1]); 7189 this.#clipBox[2] = Math.min(this.#clipBox[2], this.#pendingBBox[2]); 7190 this.#clipBox[3] = Math.min(this.#clipBox[3], this.#pendingBBox[3]); 7191 } 7192 return this; 7193 } 7194 take() { 7195 return new BBoxReader(this.#bboxes, this.#bboxesCoords); 7196 } 7197 takeDebugMetadata() { 7198 throw new Error("Unreachable"); 7199 } 7200 recordSimpleData(name, idx) { 7201 return this; 7202 } 7203 recordIncrementalData(name, idx) { 7204 return this; 7205 } 7206 resetIncrementalData(name, idx) { 7207 return this; 7208 } 7209 recordNamedData(name, idx) { 7210 return this; 7211 } 7212 recordSimpleDataFromNamed(name, depName, fallbackIdx) { 7213 return this; 7214 } 7215 recordFutureForcedDependency(name, idx) { 7216 return this; 7217 } 7218 inheritSimpleDataAsFutureForcedDependencies(names) { 7219 return this; 7220 } 7221 inheritPendingDependenciesAsFutureForcedDependencies() { 7222 return this; 7223 } 7224 recordCharacterBBox(idx, ctx, font, scale = 1, x = 0, y = 0, getMeasure) { 7225 return this; 7226 } 7227 getSimpleIndex(dependencyName) { 7228 return undefined; 7229 } 7230 recordDependencies(idx, dependencyNames) { 7231 return this; 7232 } 7233 recordNamedDependency(idx, name) { 7234 return this; 7235 } 7236 recordShowTextOperation(idx, preserve = false) { 7237 return this; 7238 } 7239} 7240class CanvasDependencyTracker { 7241 #simple = { 7242 __proto__: null 7243 }; 7244 #incremental = { 7245 __proto__: null, 7246 transform: [], 7247 moveText: [], 7248 sameLineText: [], 7249 [FORCED_DEPENDENCY_LABEL]: [] 7250 }; 7251 #namedDependencies = new Map(); 7252 #pendingDependencies = new Set(); 7253 #fontBBoxTrustworthy = new Map(); 7254 #debugMetadata; 7255 #recordDebugMetadataDepenencyAfterRestore; 7256 #bboxTracker; 7257 constructor(bboxTracker, recordDebugMetadata = false) { 7258 this.#bboxTracker = bboxTracker; 7259 if (recordDebugMetadata) { 7260 this.#debugMetadata = new Map(); 7261 this.#recordDebugMetadataDepenencyAfterRestore = (lastSave, opIdx) => { 7262 ensureDebugMetadata(this.#debugMetadata, opIdx).dependencies.add(lastSave); 7263 }; 7264 } 7265 } 7266 get clipBox() { 7267 return this.#bboxTracker.clipBox; 7268 } 7269 growOperationsCount(operationsCount) { 7270 this.#bboxTracker.growOperationsCount(operationsCount); 7271 } 7272 save(opIdx) { 7273 this.#simple = { 7274 __proto__: this.#simple 7275 }; 7276 this.#incremental = { 7277 __proto__: this.#incremental, 7278 transform: { 7279 __proto__: this.#incremental.transform 7280 }, 7281 moveText: { 7282 __proto__: this.#incremental.moveText 7283 }, 7284 sameLineText: { 7285 __proto__: this.#incremental.sameLineText 7286 }, 7287 [FORCED_DEPENDENCY_LABEL]: { 7288 __proto__: this.#incremental[FORCED_DEPENDENCY_LABEL] 7289 } 7290 }; 7291 this.#bboxTracker.save(opIdx); 7292 return this; 7293 } 7294 restore(opIdx) { 7295 this.#bboxTracker.restore(opIdx, this.#recordDebugMetadataDepenencyAfterRestore); 7296 const previous = Object.getPrototypeOf(this.#simple); 7297 if (previous === null) { 7298 return this; 7299 } 7300 this.#simple = previous; 7301 this.#incremental = Object.getPrototypeOf(this.#incremental); 7302 return this; 7303 } 7304 recordOpenMarker(opIdx) { 7305 this.#bboxTracker.recordOpenMarker(opIdx, this.#recordDebugMetadataDepenencyAfterRestore); 7306 return this; 7307 } 7308 getOpenMarker() { 7309 return this.#bboxTracker.getOpenMarker(); 7310 } 7311 recordCloseMarker(opIdx) { 7312 this.#bboxTracker.recordCloseMarker(opIdx, this.#recordDebugMetadataDepenencyAfterRestore); 7313 return this; 7314 } 7315 beginMarkedContent(opIdx) { 7316 this.#bboxTracker.beginMarkedContent(opIdx); 7317 return this; 7318 } 7319 endMarkedContent(opIdx) { 7320 this.#bboxTracker.endMarkedContent(opIdx, this.#recordDebugMetadataDepenencyAfterRestore); 7321 return this; 7322 } 7323 pushBaseTransform(ctx) { 7324 this.#bboxTracker.pushBaseTransform(ctx); 7325 return this; 7326 } 7327 popBaseTransform() { 7328 this.#bboxTracker.popBaseTransform(); 7329 return this; 7330 } 7331 recordSimpleData(name, idx) { 7332 this.#simple[name] = idx; 7333 return this; 7334 } 7335 recordIncrementalData(name, idx) { 7336 this.#incremental[name].push(idx); 7337 return this; 7338 } 7339 resetIncrementalData(name, idx) { 7340 this.#incremental[name].length = 0; 7341 return this; 7342 } 7343 recordNamedData(name, idx) { 7344 this.#namedDependencies.set(name, idx); 7345 return this; 7346 } 7347 recordSimpleDataFromNamed(name, depName, fallbackIdx) { 7348 this.#simple[name] = this.#namedDependencies.get(depName) ?? fallbackIdx; 7349 } 7350 recordFutureForcedDependency(name, idx) { 7351 this.recordIncrementalData(FORCED_DEPENDENCY_LABEL, idx); 7352 return this; 7353 } 7354 inheritSimpleDataAsFutureForcedDependencies(names) { 7355 for (const name of names) { 7356 if (name in this.#simple) { 7357 this.recordFutureForcedDependency(name, this.#simple[name]); 7358 } 7359 } 7360 return this; 7361 } 7362 inheritPendingDependenciesAsFutureForcedDependencies() { 7363 for (const dep of this.#pendingDependencies) { 7364 this.recordFutureForcedDependency(FORCED_DEPENDENCY_LABEL, dep); 7365 } 7366 return this; 7367 } 7368 resetBBox(idx) { 7369 this.#bboxTracker.resetBBox(idx); 7370 return this; 7371 } 7372 recordClipBox(idx, ctx, minX, maxX, minY, maxY) { 7373 this.#bboxTracker.recordClipBox(idx, ctx, minX, maxX, minY, maxY); 7374 return this; 7375 } 7376 recordBBox(idx, ctx, minX, maxX, minY, maxY) { 7377 this.#bboxTracker.recordBBox(idx, ctx, minX, maxX, minY, maxY); 7378 return this; 7379 } 7380 recordCharacterBBox(idx, ctx, font, scale = 1, x = 0, y = 0, getMeasure) { 7381 const fontBBox = font.bbox; 7382 let isBBoxTrustworthy; 7383 let computedBBox; 7384 if (fontBBox) { 7385 isBBoxTrustworthy = fontBBox[2] !== fontBBox[0] && fontBBox[3] !== fontBBox[1] && this.#fontBBoxTrustworthy.get(font); 7386 if (isBBoxTrustworthy !== false) { 7387 computedBBox = [0, 0, 0, 0]; 7388 Util.axialAlignedBoundingBox(fontBBox, font.fontMatrix, computedBBox); 7389 if (scale !== 1 || x !== 0 || y !== 0) { 7390 scaleCharBBox(scale, -scale, x, y, computedBBox); 7391 } 7392 if (isBBoxTrustworthy) { 7393 return this.recordBBox(idx, ctx, computedBBox[0], computedBBox[2], computedBBox[1], computedBBox[3]); 7394 } 7395 } 7396 } 7397 if (!getMeasure) { 7398 return this.recordFullPageBBox(idx); 7399 } 7400 const measure = getMeasure(); 7401 if (fontBBox && computedBBox && isBBoxTrustworthy === undefined) { 7402 isBBoxTrustworthy = computedBBox[0] <= x - measure.actualBoundingBoxLeft && computedBBox[2] >= x + measure.actualBoundingBoxRight && computedBBox[1] <= y - measure.actualBoundingBoxAscent && computedBBox[3] >= y + measure.actualBoundingBoxDescent; 7403 this.#fontBBoxTrustworthy.set(font, isBBoxTrustworthy); 7404 if (isBBoxTrustworthy) { 7405 return this.recordBBox(idx, ctx, computedBBox[0], computedBBox[2], computedBBox[1], computedBBox[3]); 7406 } 7407 } 7408 return this.recordBBox(idx, ctx, x - measure.actualBoundingBoxLeft, x + measure.actualBoundingBoxRight, y - measure.actualBoundingBoxAscent, y + measure.actualBoundingBoxDescent); 7409 } 7410 recordFullPageBBox(idx) { 7411 this.#bboxTracker.recordFullPageBBox(idx); 7412 return this; 7413 } 7414 getSimpleIndex(dependencyName) { 7415 return this.#simple[dependencyName]; 7416 } 7417 recordDependencies(idx, dependencyNames) { 7418 const pendingDependencies = this.#pendingDependencies; 7419 const simple = this.#simple; 7420 const incremental = this.#incremental; 7421 for (const name of dependencyNames) { 7422 if (name in this.#simple) { 7423 pendingDependencies.add(simple[name]); 7424 } else if (name in incremental) { 7425 incremental[name].forEach(pendingDependencies.add, pendingDependencies); 7426 } 7427 } 7428 return this; 7429 } 7430 recordNamedDependency(idx, name) { 7431 if (this.#namedDependencies.has(name)) { 7432 this.#pendingDependencies.add(this.#namedDependencies.get(name)); 7433 } 7434 return this; 7435 } 7436 recordOperation(idx, preserve = false) { 7437 this.recordDependencies(idx, [FORCED_DEPENDENCY_LABEL]); 7438 if (this.#debugMetadata) { 7439 const metadata = ensureDebugMetadata(this.#debugMetadata, idx); 7440 const { 7441 dependencies 7442 } = metadata; 7443 this.#pendingDependencies.forEach(dependencies.add, dependencies); 7444 this.#bboxTracker._savesStack.forEach(dependencies.add, dependencies); 7445 this.#bboxTracker._markedContentStack.forEach(dependencies.add, dependencies); 7446 dependencies.delete(idx); 7447 metadata.isRenderingOperation = true; 7448 } 7449 const needsCleanup = !preserve && idx === this.#bboxTracker._pendingBBoxIdx; 7450 this.#bboxTracker.recordOperation(idx, preserve, [this.#pendingDependencies, this.#bboxTracker._savesStack, this.#bboxTracker._markedContentStack]); 7451 if (needsCleanup) { 7452 this.#pendingDependencies.clear(); 7453 } 7454 return this; 7455 } 7456 recordShowTextOperation(idx, preserve = false) { 7457 const deps = Array.from(this.#pendingDependencies); 7458 this.recordOperation(idx, preserve); 7459 this.recordIncrementalData("sameLineText", idx); 7460 for (const dep of deps) { 7461 this.recordIncrementalData("sameLineText", dep); 7462 } 7463 return this; 7464 } 7465 bboxToClipBoxDropOperation(idx, preserve = false) { 7466 const needsCleanup = !preserve && idx === this.#bboxTracker._pendingBBoxIdx; 7467 this.#bboxTracker.bboxToClipBoxDropOperation(idx); 7468 if (needsCleanup) { 7469 this.#pendingDependencies.clear(); 7470 } 7471 return this; 7472 } 7473 take() { 7474 this.#fontBBoxTrustworthy.clear(); 7475 return this.#bboxTracker.take(); 7476 } 7477 takeDebugMetadata() { 7478 return this.#debugMetadata; 7479 } 7480} 7481class CanvasNestedDependencyTracker { 7482 #dependencyTracker; 7483 #opIdx; 7484 #ignoreBBoxes; 7485 #nestingLevel = 0; 7486 #savesLevel = 0; 7487 constructor(dependencyTracker, opIdx, ignoreBBoxes) { 7488 if (dependencyTracker instanceof CanvasNestedDependencyTracker && dependencyTracker.#ignoreBBoxes === !!ignoreBBoxes) { 7489 return dependencyTracker; 7490 } 7491 this.#dependencyTracker = dependencyTracker; 7492 this.#opIdx = opIdx; 7493 this.#ignoreBBoxes = !!ignoreBBoxes; 7494 } 7495 get clipBox() { 7496 return this.#dependencyTracker.clipBox; 7497 } 7498 growOperationsCount() { 7499 throw new Error("Unreachable"); 7500 } 7501 save(opIdx) { 7502 this.#savesLevel++; 7503 this.#dependencyTracker.save(this.#opIdx); 7504 return this; 7505 } 7506 restore(opIdx) { 7507 if (this.#savesLevel > 0) { 7508 this.#dependencyTracker.restore(this.#opIdx); 7509 this.#savesLevel--; 7510 } 7511 return this; 7512 } 7513 recordOpenMarker(idx) { 7514 this.#nestingLevel++; 7515 return this; 7516 } 7517 getOpenMarker() { 7518 return this.#nestingLevel > 0 ? this.#opIdx : this.#dependencyTracker.getOpenMarker(); 7519 } 7520 recordCloseMarker(idx) { 7521 this.#nestingLevel--; 7522 return this; 7523 } 7524 beginMarkedContent(opIdx) { 7525 return this; 7526 } 7527 endMarkedContent(opIdx) { 7528 return this; 7529 } 7530 pushBaseTransform(ctx) { 7531 this.#dependencyTracker.pushBaseTransform(ctx); 7532 return this; 7533 } 7534 popBaseTransform() { 7535 this.#dependencyTracker.popBaseTransform(); 7536 return this; 7537 } 7538 recordSimpleData(name, idx) { 7539 this.#dependencyTracker.recordSimpleData(name, this.#opIdx); 7540 return this; 7541 } 7542 recordIncrementalData(name, idx) { 7543 this.#dependencyTracker.recordIncrementalData(name, this.#opIdx); 7544 return this; 7545 } 7546 resetIncrementalData(name, idx) { 7547 this.#dependencyTracker.resetIncrementalData(name, this.#opIdx); 7548 return this; 7549 } 7550 recordNamedData(name, idx) { 7551 return this; 7552 } 7553 recordSimpleDataFromNamed(name, depName, fallbackIdx) { 7554 this.#dependencyTracker.recordSimpleDataFromNamed(name, depName, this.#opIdx); 7555 return this; 7556 } 7557 recordFutureForcedDependency(name, idx) { 7558 this.#dependencyTracker.recordFutureForcedDependency(name, this.#opIdx); 7559 return this; 7560 } 7561 inheritSimpleDataAsFutureForcedDependencies(names) { 7562 this.#dependencyTracker.inheritSimpleDataAsFutureForcedDependencies(names); 7563 return this; 7564 } 7565 inheritPendingDependenciesAsFutureForcedDependencies() { 7566 this.#dependencyTracker.inheritPendingDependenciesAsFutureForcedDependencies(); 7567 return this; 7568 } 7569 resetBBox(idx) { 7570 if (!this.#ignoreBBoxes) { 7571 this.#dependencyTracker.resetBBox(this.#opIdx); 7572 } 7573 return this; 7574 } 7575 recordClipBox(idx, ctx, minX, maxX, minY, maxY) { 7576 if (!this.#ignoreBBoxes) { 7577 this.#dependencyTracker.recordClipBox(this.#opIdx, ctx, minX, maxX, minY, maxY); 7578 } 7579 return this; 7580 } 7581 recordBBox(idx, ctx, minX, maxX, minY, maxY) { 7582 if (!this.#ignoreBBoxes) { 7583 this.#dependencyTracker.recordBBox(this.#opIdx, ctx, minX, maxX, minY, maxY); 7584 } 7585 return this; 7586 } 7587 recordCharacterBBox(idx, ctx, font, scale, x, y, getMeasure) { 7588 if (!this.#ignoreBBoxes) { 7589 this.#dependencyTracker.recordCharacterBBox(this.#opIdx, ctx, font, scale, x, y, getMeasure); 7590 } 7591 return this; 7592 } 7593 recordFullPageBBox(idx) { 7594 if (!this.#ignoreBBoxes) { 7595 this.#dependencyTracker.recordFullPageBBox(this.#opIdx); 7596 } 7597 return this; 7598 } 7599 getSimpleIndex(dependencyName) { 7600 return this.#dependencyTracker.getSimpleIndex(dependencyName); 7601 } 7602 recordDependencies(idx, dependencyNames) { 7603 this.#dependencyTracker.recordDependencies(this.#opIdx, dependencyNames); 7604 return this; 7605 } 7606 recordNamedDependency(idx, name) { 7607 this.#dependencyTracker.recordNamedDependency(this.#opIdx, name); 7608 return this; 7609 } 7610 recordOperation(idx) { 7611 this.#dependencyTracker.recordOperation(this.#opIdx, true); 7612 return this; 7613 } 7614 recordShowTextOperation(idx) { 7615 this.#dependencyTracker.recordShowTextOperation(this.#opIdx, true); 7616 return this; 7617 } 7618 bboxToClipBoxDropOperation(idx) { 7619 if (!this.#ignoreBBoxes) { 7620 this.#dependencyTracker.bboxToClipBoxDropOperation(this.#opIdx, true); 7621 } 7622 return this; 7623 } 7624 take() { 7625 throw new Error("Unreachable"); 7626 } 7627 takeDebugMetadata() { 7628 throw new Error("Unreachable"); 7629 } 7630} 7631const Dependencies = { 7632 stroke: ["path", "transform", "filter", "strokeColor", "strokeAlpha", "lineWidth", "lineCap", "lineJoin", "miterLimit", "dash"], 7633 fill: ["path", "transform", "filter", "fillColor", "fillAlpha", "globalCompositeOperation", "SMask"], 7634 imageXObject: ["transform", "SMask", "filter", "fillAlpha", "strokeAlpha", "globalCompositeOperation"], 7635 rawFillPath: ["filter", "fillColor", "fillAlpha"], 7636 showText: ["transform", "leading", "charSpacing", "wordSpacing", "hScale", "textRise", "moveText", "textMatrix", "font", "fontObj", "filter", "fillColor", "textRenderingMode", "SMask", "fillAlpha", "strokeAlpha", "globalCompositeOperation", "sameLineText"], 7637 transform: ["transform"], 7638 transformAndFill: ["transform", "fillColor"] 7639}; 7640class CanvasImagesTracker { 7641 #canvasWidth; 7642 #canvasHeight; 7643 #capacity = 4; 7644 #count = 0; 7645 #coords = new CanvasImagesTracker.#CoordsArray(this.#capacity * 6); 7646 static #CoordsArray = FeatureTest.isFloat16ArraySupported ? Float16Array : Float32Array; 7647 constructor(canvas) { 7648 this.#canvasWidth = canvas.width; 7649 this.#canvasHeight = canvas.height; 7650 } 7651 record(ctx, width, height, clipBox) { 7652 if (this.#count === this.#capacity) { 7653 this.#capacity *= 2; 7654 const newCoords = new CanvasImagesTracker.#CoordsArray(this.#capacity * 6); 7655 newCoords.set(this.#coords); 7656 this.#coords = newCoords; 7657 } 7658 const transform = getCurrentTransform(ctx); 7659 let coords; 7660 if (clipBox[0] !== Infinity) { 7661 const bbox = BBOX_INIT.slice(); 7662 Util.axialAlignedBoundingBox([0, -height, width, 0], transform, bbox); 7663 const finalBBox = Util.intersect(clipBox, bbox); 7664 if (!finalBBox) { 7665 return; 7666 } 7667 const [minX, minY, maxX, maxY] = finalBBox; 7668 if (minX !== bbox[0] || minY !== bbox[1] || maxX !== bbox[2] || maxY !== bbox[3]) { 7669 const rotationAngle = Math.atan2(transform[1], transform[0]); 7670 const sin = Math.abs(Math.sin(rotationAngle)); 7671 const cos = Math.abs(Math.cos(rotationAngle)); 7672 if (sin < 1e-6 || cos < 1e-6 || Math.abs(sin - cos) < 1e-6) { 7673 coords = [minX, minY, minX, maxY, maxX, minY]; 7674 } else { 7675 const finalBBoxWidth = maxX - minX; 7676 const finalBBoxHeight = maxY - minY; 7677 const sin2 = sin * sin; 7678 const cos2 = cos * cos; 7679 const cosSin = cos * sin; 7680 const denom = cos2 - sin2; 7681 const a = (finalBBoxHeight * cos2 - finalBBoxWidth * cosSin) / denom; 7682 const b = (finalBBoxHeight * cosSin - finalBBoxWidth * sin2) / denom; 7683 coords = [minX + b, minY, minX, minY + a, maxX, maxY - a]; 7684 } 7685 } 7686 } 7687 if (!coords) { 7688 coords = [0, -height, 0, 0, width, -height]; 7689 Util.applyTransform(coords, transform, 0); 7690 Util.applyTransform(coords, transform, 2); 7691 Util.applyTransform(coords, transform, 4); 7692 } 7693 coords[0] /= this.#canvasWidth; 7694 coords[1] /= this.#canvasHeight; 7695 coords[2] /= this.#canvasWidth; 7696 coords[3] /= this.#canvasHeight; 7697 coords[4] /= this.#canvasWidth; 7698 coords[5] /= this.#canvasHeight; 7699 this.#coords.set(coords, this.#count * 6); 7700 this.#count++; 7701 } 7702 take() { 7703 return this.#coords.subarray(0, this.#count * 6); 7704 } 7705} 7706 7707;// ./src/
7707display/font_loader.js
vendor: 9,963 bytes, lines 7707-7966
7707 7708 7709 7710class FontLoader { 7711 #systemFonts = new Set(); 7712 #styleSheet = null; 7713 constructor({ 7714 ownerDocument = globalThis.document, 7715 styleElement = null 7716 }) { 7717 this._document = ownerDocument; 7718 this.nativeFontFaces = new Set(); 7719 this.styleElement = null; 7720 this.loadingRequests = []; 7721 this.loadTestFontId = 0; 7722 } 7723 addNativeFontFace(nativeFontFace) { 7724 this.nativeFontFaces.add(nativeFontFace); 7725 this._document.fonts.add(nativeFontFace); 7726 } 7727 removeNativeFontFace(nativeFontFace) { 7728 this.nativeFontFaces.delete(nativeFontFace); 7729 this._document.fonts.delete(nativeFontFace); 7730 } 7731 insertRule(rule) { 7732 const styleSheet = this.#getStyleSheet(); 7733 styleSheet.insertRule(rule, styleSheet.cssRules.length); 7734 } 7735 #getStyleSheet() { 7736 if (this.#styleSheet) { 7737 return this.#styleSheet; 7738 } 7739 const StyleSheet = this._document.defaultView?.CSSStyleSheet || globalThis.CSSStyleSheet; 7740 if (!this.styleElement && StyleSheet) { 7741 const { 7742 adoptedStyleSheets 7743 } = this._document; 7744 if (adoptedStyleSheets) { 7745 const styleSheet = new StyleSheet(); 7746 adoptedStyleSheets.push(styleSheet); 7747 return this.#styleSheet = styleSheet; 7748 } 7749 } 7750 if (!this.styleElement) { 7751 this.styleElement = this._document.createElement("style"); 7752 this._document.documentElement.getElementsByTagName("head")[0].append(this.styleElement); 7753 } 7754 return this.#styleSheet = this.styleElement.sheet; 7755 } 7756 clear() { 7757 for (const nativeFontFace of this.nativeFontFaces) { 7758 this._document.fonts.delete(nativeFontFace); 7759 } 7760 this.nativeFontFaces.clear(); 7761 this.#systemFonts.clear(); 7762 if (this.#styleSheet) { 7763 const { 7764 adoptedStyleSheets 7765 } = this._document; 7766 if (adoptedStyleSheets?.includes(this.#styleSheet)) { 7767 this._document.adoptedStyleSheets = adoptedStyleSheets.filter(styleSheet => styleSheet !== this.#styleSheet); 7768 } 7769 this.#styleSheet = null; 7770 } 7771 if (this.styleElement) { 7772 this.styleElement.remove(); 7773 this.styleElement = null; 7774 } 7775 } 7776 async loadSystemFont({ 7777 systemFontInfo: info, 7778 disableFontFace, 7779 _inspectFont 7780 }) { 7781 if (!info || this.#systemFonts.has(info.loadedName)) { 7782 return; 7783 } 7784 assert(!disableFontFace, "loadSystemFont shouldn't be called when `disableFontFace` is set."); 7785 if (this.isFontLoadingAPISupported) { 7786 const { 7787 loadedName, 7788 src, 7789 style 7790 } = info; 7791 const fontFace = new FontFace(loadedName, src, style); 7792 this.addNativeFontFace(fontFace); 7793 try { 7794 await fontFace.load(); 7795 this.#systemFonts.add(loadedName); 7796 _inspectFont?.(info); 7797 } catch { 7798 warn(`Cannot load system font: ${info.baseFontName}, installing it could help to improve PDF rendering.`); 7799 this.removeNativeFontFace(fontFace); 7800 } 7801 return; 7802 } 7803 unreachable("Not implemented: loadSystemFont without the Font Loading API."); 7804 } 7805 async bind(font) { 7806 if (font.attached || font.missingFile && !font.systemFontInfo) { 7807 return; 7808 } 7809 font.attached = true; 7810 if (font.systemFontInfo) { 7811 await this.loadSystemFont(font); 7812 return; 7813 } 7814 if (this.isFontLoadingAPISupported) { 7815 const nativeFontFace = font.createNativeFontFace(); 7816 if (nativeFontFace) { 7817 this.addNativeFontFace(nativeFontFace); 7818 try { 7819 await nativeFontFace.loaded; 7820 } catch (ex) { 7821 warn(`Failed to load font '${nativeFontFace.family}': '${ex}'.`); 7822 font.disableFontFace = true; 7823 throw ex; 7824 } 7825 } 7826 return; 7827 } 7828 const rule = font.createFontFaceRule(); 7829 if (rule) { 7830 this.insertRule(rule); 7831 if (this.isSyncFontLoadingSupported) { 7832 return; 7833 } 7834 await new Promise(resolve => { 7835 const request = this._queueLoadingCallback(resolve); 7836 this._prepareFontLoadEvent(font, request); 7837 }); 7838 } 7839 } 7840 get isFontLoadingAPISupported() { 7841 const hasFonts = !!this._document?.fonts; 7842 return shadow(this, "isFontLoadingAPISupported", hasFonts); 7843 } 7844 get isSyncFontLoadingSupported() { 7845 return shadow(this, "isSyncFontLoadingSupported", isNodeJS || FeatureTest.platform.isFirefox); 7846 } 7847 _queueLoadingCallback(callback) { 7848 function completeRequest() { 7849 assert(!request.done, "completeRequest() cannot be called twice."); 7850 request.done = true; 7851 while (loadingRequests.length > 0 && loadingRequests[0].done) { 7852 const otherRequest = loadingRequests.shift(); 7853 setTimeout(otherRequest.callback, 0); 7854 } 7855 } 7856 const { 7857 loadingRequests 7858 } = this; 7859 const request = { 7860 done: false, 7861 complete: completeRequest, 7862 callback 7863 }; 7864 loadingRequests.push(request); 7865 return request; 7866 } 7867 get _loadTestFont() { 7868 const testFont = atob("T1RUTwALAIAAAwAwQ0ZGIDHtZg4AAAOYAAAAgUZGVE1lkzZwAAAEHAAAABxHREVGABQA" + "FQAABDgAAAAeT1MvMlYNYwkAAAEgAAAAYGNtYXABDQLUAAACNAAAAUJoZWFk/xVFDQAA" + "ALwAAAA2aGhlYQdkA+oAAAD0AAAAJGhtdHgD6AAAAAAEWAAAAAZtYXhwAAJQAAAAARgA" + "AAAGbmFtZVjmdH4AAAGAAAAAsXBvc3T/hgAzAAADeAAAACAAAQAAAAEAALZRFsRfDzz1" + "AAsD6AAAAADOBOTLAAAAAM4KHDwAAAAAA+gDIQAAAAgAAgAAAAAAAAABAAADIQAAAFoD" + "6AAAAAAD6AABAAAAAAAAAAAAAAAAAAAAAQAAUAAAAgAAAAQD6AH0AAUAAAKKArwAAACM" + "AooCvAAAAeAAMQECAAACAAYJAAAAAAAAAAAAAQAAAAAAAAAAAAAAAFBmRWQAwAAuAC4D" + "IP84AFoDIQAAAAAAAQAAAAAAAAAAACAAIAABAAAADgCuAAEAAAAAAAAAAQAAAAEAAAAA" + "AAEAAQAAAAEAAAAAAAIAAQAAAAEAAAAAAAMAAQAAAAEAAAAAAAQAAQAAAAEAAAAAAAUA" + "AQAAAAEAAAAAAAYAAQAAAAMAAQQJAAAAAgABAAMAAQQJAAEAAgABAAMAAQQJAAIAAgAB" + "AAMAAQQJAAMAAgABAAMAAQQJAAQAAgABAAMAAQQJAAUAAgABAAMAAQQJAAYAAgABWABY" + "AAAAAAAAAwAAAAMAAAAcAAEAAAAAADwAAwABAAAAHAAEACAAAAAEAAQAAQAAAC7//wAA" + "AC7////TAAEAAAAAAAABBgAAAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA" + "AAAAAAAAAAAAAAAAAAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA" + "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA" + "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA" + "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA" + "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAMAAAAAAAD/gwAyAAAAAQAAAAAAAAAAAAAAAAAA" + "AAABAAQEAAEBAQJYAAEBASH4DwD4GwHEAvgcA/gXBIwMAYuL+nz5tQXkD5j3CBLnEQAC" + "AQEBIVhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYAAABAQAADwACAQEEE/t3" + "Dov6fAH6fAT+fPp8+nwHDosMCvm1Cvm1DAz6fBQAAAAAAAABAAAAAMmJbzEAAAAAzgTj" + "FQAAAADOBOQpAAEAAAAAAAAADAAUAAQAAAABAAAAAgABAAAAAAAAAAAD6AAAAAAAAA=="); 7869 return shadow(this, "_loadTestFont", testFont); 7870 } 7871 _prepareFontLoadEvent(font, request) { 7872 function int32(data, offset) { 7873 return data.charCodeAt(offset) << 24 | data.charCodeAt(offset + 1) << 16 | data.charCodeAt(offset + 2) << 8 | data.charCodeAt(offset + 3) & 0xff; 7874 } 7875 function string32(value) { 7876 return String.fromCharCode(value >> 24 & 0xff, value >> 16 & 0xff, value >> 8 & 0xff, value & 0xff); 7877 } 7878 function spliceString(s, offset, remove, insert) { 7879 const chunk1 = s.substring(0, offset); 7880 const chunk2 = s.substring(offset + remove); 7881 return chunk1 + insert + chunk2; 7882 } 7883 let i, ii; 7884 const canvas = this._document.createElement("canvas"); 7885 canvas.width = 1; 7886 canvas.height = 1; 7887 const ctx = canvas.getContext("2d"); 7888 let called = 0; 7889 function isFontReady(name, callback) { 7890 if (++called > 30) { 7891 warn("Load test font never loaded."); 7892 callback(); 7893 return; 7894 } 7895 ctx.font = "30px " + name; 7896 ctx.fillText(".", 0, 20); 7897 const imageData = ctx.getImageData(0, 0, 1, 1); 7898 if (imageData.data[3] > 0) { 7899 callback(); 7900 return; 7901 } 7902 setTimeout(isFontReady.bind(null, name, callback)); 7903 } 7904 const loadTestFontId = `lt${Date.now()}${this.loadTestFontId++}`; 7905 let data = this._loadTestFont; 7906 const COMMENT_OFFSET = 976; 7907 data = spliceString(data, COMMENT_OFFSET, loadTestFontId.length, loadTestFontId); 7908 const CFF_CHECKSUM_OFFSET = 16; 7909 const XXXX_VALUE = 0x58585858; 7910 let checksum = int32(data, CFF_CHECKSUM_OFFSET); 7911 for (i = 0, ii = loadTestFontId.length - 3; i < ii; i += 4) { 7912 checksum = checksum - XXXX_VALUE + int32(loadTestFontId, i) | 0; 7913 } 7914 if (i < loadTestFontId.length) { 7915 checksum = checksum - XXXX_VALUE + int32(loadTestFontId + "XXX", i) | 0; 7916 } 7917 data = spliceString(data, CFF_CHECKSUM_OFFSET, 4, string32(checksum)); 7918 const url = `url(data:font/opentype;base64,${btoa(data)});`; 7919 const rule = `@font-face {font-family:"${loadTestFontId}";src:${url}}`; 7920 this.insertRule(rule); 7921 const div = this._document.createElement("div"); 7922 div.style.visibility = "hidden"; 7923 div.style.width = div.style.height = "10px"; 7924 div.style.position = "absolute"; 7925 div.style.top = div.style.left = "0px"; 7926 for (const name of [font.loadedName, loadTestFontId]) { 7927 const span = this._document.createElement("span"); 7928 span.textContent = "Hi"; 7929 span.style.fontFamily = name; 7930 div.append(span); 7931 } 7932 this._document.body.append(div); 7933 isFontReady(loadTestFontId, () => { 7934 div.remove(); 7935 request.complete(); 7936 }); 7937 } 7938} 7939class FontFaceObject { 7940 compiledGlyphs = Object.create(null); 7941 #fontData; 7942 constructor(translatedData, inspectFont = null, charProcOperatorList, extra) { 7943 this.#fontData = translatedData; 7944 this._inspectFont = inspectFont; 7945 if (charProcOperatorList) { 7946 this.charProcOperatorList = charProcOperatorList; 7947 } 7948 if (extra) { 7949 Object.assign(this, extra); 7950 } 7951 } 7952 createNativeFontFace() { 7953 if (!this.data || this.disableFontFace) { 7954 return null; 7955 } 7956 let nativeFontFace; 7957 if (!this.cssFontInfo) { 7958 nativeFontFace = new FontFace(this.loadedName, this.data, {}); 7959 } else { 7960 const css = { 7961 weight: this.cssFontInfo.fontWeight 7962 }; 7963 if (this.cssFontInfo.italicAngle) { 7964 css.style = `oblique ${this.cssFontInfo.italicAngle}deg`; 7965 } 7966 nativeFontFace = new FontFace(
vendor: 8,406 bytes, lines 7966-8281
7966this.cssFontInfo.fontFamily, this.data, css); 7967 } 7968 this._inspectFont?.(this); 7969 return nativeFontFace; 7970 } 7971 createFontFaceRule() { 7972 if (!this.data || this.disableFontFace) { 7973 return null; 7974 } 7975 const url = `url(data:${this.mimetype};base64,${this.data.toBase64()});`; 7976 let rule; 7977 if (!this.cssFontInfo) { 7978 rule = `@font-face {font-family:"${this.loadedName}";src:${url}}`; 7979 } else { 7980 let css = `font-weight: ${this.cssFontInfo.fontWeight};`; 7981 if (this.cssFontInfo.italicAngle) { 7982 css += `font-style: oblique ${this.cssFontInfo.italicAngle}deg;`; 7983 } 7984 rule = `@font-face {font-family:"${this.cssFontInfo.fontFamily}";${css}src:${url}}`; 7985 } 7986 this._inspectFont?.(this, url); 7987 return rule; 7988 } 7989 getPathGenerator(objs, character) { 7990 if (this.compiledGlyphs[character] !== undefined) { 7991 return this.compiledGlyphs[character]; 7992 } 7993 const objId = this.loadedName + "_path_" + character; 7994 let cmds; 7995 try { 7996 cmds = objs.get(objId); 7997 } catch (ex) { 7998 warn(`getPathGenerator - ignoring character: "${ex}".`); 7999 } 8000 const path = makePathFromDrawOPS(cmds?.path); 8001 if (!this.fontExtraProperties) { 8002 objs.delete(objId); 8003 } 8004 return this.compiledGlyphs[character] = path; 8005 } 8006 get black() { 8007 return this.#fontData.black; 8008 } 8009 get bold() { 8010 return this.#fontData.bold; 8011 } 8012 get disableFontFace() { 8013 return this.#fontData.disableFontFace; 8014 } 8015 set disableFontFace(value) { 8016 shadow(this, "disableFontFace", !!value); 8017 } 8018 get fontExtraProperties() { 8019 return this.#fontData.fontExtraProperties; 8020 } 8021 get isInvalidPDFjsFont() { 8022 return this.#fontData.isInvalidPDFjsFont; 8023 } 8024 get isType3Font() { 8025 return this.#fontData.isType3Font; 8026 } 8027 get italic() { 8028 return this.#fontData.italic; 8029 } 8030 get missingFile() { 8031 return this.#fontData.missingFile; 8032 } 8033 get remeasure() { 8034 return this.#fontData.remeasure; 8035 } 8036 get vertical() { 8037 return this.#fontData.vertical; 8038 } 8039 get ascent() { 8040 return this.#fontData.ascent; 8041 } 8042 get defaultWidth() { 8043 return this.#fontData.defaultWidth; 8044 } 8045 get descent() { 8046 return this.#fontData.descent; 8047 } 8048 get bbox() { 8049 return this.#fontData.bbox; 8050 } 8051 get fontMatrix() { 8052 return this.#fontData.fontMatrix; 8053 } 8054 get fallbackName() { 8055 return this.#fontData.fallbackName; 8056 } 8057 get loadedName() { 8058 return this.#fontData.loadedName; 8059 } 8060 get mimetype() { 8061 return this.#fontData.mimetype; 8062 } 8063 get name() { 8064 return this.#fontData.name; 8065 } 8066 get data() { 8067 return this.#fontData.data; 8068 } 8069 clearData() { 8070 this.#fontData.clearData(); 8071 } 8072 get cssFontInfo() { 8073 return this.#fontData.cssFontInfo; 8074 } 8075 get systemFontInfo() { 8076 return this.#fontData.systemFontInfo; 8077 } 8078 get defaultVMetrics() { 8079 return this.#fontData.defaultVMetrics; 8080 } 8081} 8082 8083;// ./src/shared/obj_bin_transform_utils.js 8084class CSS_FONT_INFO { 8085 static strings = ["fontFamily", "fontWeight", "italicAngle"]; 8086} 8087class SYSTEM_FONT_INFO { 8088 static strings = ["css", "loadedName", "baseFontName", "src"]; 8089} 8090class FONT_INFO { 8091 static bools = ["black", "bold", "disableFontFace", "fontExtraProperties", "isInvalidPDFjsFont", "isType3Font", "italic", "missingFile", "remeasure", "vertical"]; 8092 static numbers = ["ascent", "defaultWidth", "descent"]; 8093 static strings = ["fallbackName", "loadedName", "mimetype", "name"]; 8094 static OFFSET_NUMBERS = Math.ceil(this.bools.length * 2 / 8); 8095 static OFFSET_BBOX = this.OFFSET_NUMBERS + this.numbers.length * 8; 8096 static OFFSET_FONT_MATRIX = this.OFFSET_BBOX + 1 + 2 * 4; 8097 static OFFSET_DEFAULT_VMETRICS = this.OFFSET_FONT_MATRIX + 1 + 8 * 6; 8098 static OFFSET_STRINGS = this.OFFSET_DEFAULT_VMETRICS + 1 + 2 * 3; 8099} 8100class PATTERN_INFO { 8101 static KIND = 0; 8102 static HAS_BBOX = 1; 8103 static HAS_BACKGROUND = 2; 8104 static SHADING_TYPE = 3; 8105 static N_COORD = 4; 8106 static N_COLOR = 8; 8107 static N_STOP = 12; 8108 static N_FIGURES = 16; 8109} 8110 8111;// ./src/display/obj_bin_transform_display.js 8112 8113 8114class CssFontInfo { 8115 #buffer; 8116 #decoder = new TextDecoder(); 8117 #view; 8118 constructor(buffer) { 8119 this.#buffer = buffer; 8120 this.#view = new DataView(buffer); 8121 } 8122 #readString(index) { 8123 assert(index < CSS_FONT_INFO.strings.length, "Invalid string index"); 8124 let offset = 0; 8125 for (let i = 0; i < index; i++) { 8126 offset += this.#view.getUint32(offset) + 4; 8127 } 8128 const length = this.#view.getUint32(offset); 8129 return this.#decoder.decode(new Uint8Array(this.#buffer, offset + 4, length)); 8130 } 8131 get fontFamily() { 8132 return this.#readString(0); 8133 } 8134 get fontWeight() { 8135 return this.#readString(1); 8136 } 8137 get italicAngle() { 8138 return this.#readString(2); 8139 } 8140} 8141class SystemFontInfo { 8142 #buffer; 8143 #decoder = new TextDecoder(); 8144 #view; 8145 constructor(buffer) { 8146 this.#buffer = buffer; 8147 this.#view = new DataView(buffer); 8148 } 8149 get guessFallback() { 8150 return this.#view.getUint8(0) !== 0; 8151 } 8152 #readString(index) { 8153 assert(index < SYSTEM_FONT_INFO.strings.length, "Invalid string index"); 8154 let offset = 5; 8155 for (let i = 0; i < index; i++) { 8156 offset += this.#view.getUint32(offset) + 4; 8157 } 8158 const length = this.#view.getUint32(offset); 8159 return this.#decoder.decode(new Uint8Array(this.#buffer, offset + 4, length)); 8160 } 8161 get css() { 8162 return this.#readString(0); 8163 } 8164 get loadedName() { 8165 return this.#readString(1); 8166 } 8167 get baseFontName() { 8168 return this.#readString(2); 8169 } 8170 get src() { 8171 return this.#readString(3); 8172 } 8173 get style() { 8174 let offset = 1; 8175 offset += 4 + this.#view.getUint32(offset); 8176 const styleLength = this.#view.getUint32(offset); 8177 const style = this.#decoder.decode(new Uint8Array(this.#buffer, offset + 4, styleLength)); 8178 offset += 4 + styleLength; 8179 const weightLength = this.#view.getUint32(offset); 8180 const weight = this.#decoder.decode(new Uint8Array(this.#buffer, offset + 4, weightLength)); 8181 return { 8182 style, 8183 weight 8184 }; 8185 } 8186} 8187class FontInfo { 8188 #buffer; 8189 #decoder = new TextDecoder(); 8190 #view; 8191 constructor({ 8192 buffer, 8193 extra 8194 }) { 8195 this.#buffer = buffer; 8196 this.#view = new DataView(buffer); 8197 if (extra) { 8198 Object.assign(this, extra); 8199 } 8200 } 8201 #readBoolean(index) { 8202 assert(index < FONT_INFO.bools.length, "Invalid boolean index"); 8203 const byteOffset = Math.floor(index / 4); 8204 const bitOffset = index * 2 % 8; 8205 const value = this.#view.getUint8(byteOffset) >> bitOffset & 0x03; 8206 return value === 0x00 ? undefined : value === 0x02; 8207 } 8208 get black() { 8209 return this.#readBoolean(0); 8210 } 8211 get bold() { 8212 return this.#readBoolean(1); 8213 } 8214 get disableFontFace() { 8215 return this.#readBoolean(2); 8216 } 8217 get fontExtraProperties() { 8218 return this.#readBoolean(3); 8219 } 8220 get isInvalidPDFjsFont() { 8221 return this.#readBoolean(4); 8222 } 8223 get isType3Font() { 8224 return this.#readBoolean(5); 8225 } 8226 get italic() { 8227 return this.#readBoolean(6); 8228 } 8229 get missingFile() { 8230 return this.#readBoolean(7); 8231 } 8232 get remeasure() { 8233 return this.#readBoolean(8); 8234 } 8235 get vertical() { 8236 return this.#readBoolean(9); 8237 } 8238 #readNumber(index) { 8239 assert(index < FONT_INFO.numbers.length, "Invalid number index"); 8240 return this.#view.getFloat64(FONT_INFO.OFFSET_NUMBERS + index * 8); 8241 } 8242 get ascent() { 8243 return this.#readNumber(0); 8244 } 8245 get defaultWidth() { 8246 return this.#readNumber(1); 8247 } 8248 get descent() { 8249 return this.#readNumber(2); 8250 } 8251 #readArray(offset, arrLen, lookupName, increment) { 8252 const len = this.#view.getUint8(offset); 8253 if (len === 0) { 8254 return undefined; 8255 } 8256 assert(len === arrLen, "Invalid array length."); 8257 offset += 1; 8258 const arr = new Array(len); 8259 for (let i = 0; i < len; i++) { 8260 arr[i] = this.#view[lookupName](offset, true); 8261 offset += increment; 8262 } 8263 return arr; 8264 } 8265 get bbox() { 8266 return this.#readArray(FONT_INFO.OFFSET_BBOX, 4, "getInt16", 2); 8267 } 8268 get fontMatrix() { 8269 return this.#readArray(FONT_INFO.OFFSET_FONT_MATRIX, 6, "getFloat64", 8); 8270 } 8271 get defaultVMetrics() { 8272 return this.#readArray(FONT_INFO.OFFSET_DEFAULT_VMETRICS, 3, "getInt16", 2); 8273 } 8274 #readString(index) { 8275 assert(index < FONT_INFO.strings.length, "Invalid string index"); 8276 let offset = FONT_INFO.OFFSET_STRINGS + 4; 8277 for (let i = 0; i < index; i++) { 8278 offset += this.#view.getUint32(offset) + 4; 8279 } 8280 const length = this.#view.getUint32(offset); 8281 return this.#
vendor: 26,889 bytes, lines 8281-9203
8281decoder.decode(new Uint8Array(this.#buffer, offset + 4, length)); 8282 } 8283 get fallbackName() { 8284 return this.#readString(0); 8285 } 8286 get loadedName() { 8287 return this.#readString(1); 8288 } 8289 get mimetype() { 8290 return this.#readString(2); 8291 } 8292 get name() { 8293 return this.#readString(3); 8294 } 8295 #getDataOffsets() { 8296 let offset = FONT_INFO.OFFSET_STRINGS; 8297 const stringsLength = this.#view.getUint32(offset); 8298 offset += 4 + stringsLength; 8299 const systemFontInfoLength = this.#view.getUint32(offset); 8300 offset += 4 + systemFontInfoLength; 8301 const cssFontInfoLength = this.#view.getUint32(offset); 8302 offset += 4 + cssFontInfoLength; 8303 const length = this.#view.getUint32(offset); 8304 return { 8305 offset, 8306 length 8307 }; 8308 } 8309 get data() { 8310 const { 8311 offset, 8312 length 8313 } = this.#getDataOffsets(); 8314 return length === 0 ? undefined : new Uint8Array(this.#buffer, offset + 4, length); 8315 } 8316 clearData() { 8317 const { 8318 offset, 8319 length 8320 } = this.#getDataOffsets(); 8321 if (length === 0) { 8322 return; 8323 } 8324 this.#view.setUint32(offset, 0); 8325 this.#buffer = new Uint8Array(this.#buffer, 0, offset + 4).slice().buffer; 8326 this.#view = new DataView(this.#buffer); 8327 } 8328 get cssFontInfo() { 8329 let offset = FONT_INFO.OFFSET_STRINGS; 8330 const stringsLength = this.#view.getUint32(offset); 8331 offset += 4 + stringsLength; 8332 const systemFontInfoLength = this.#view.getUint32(offset); 8333 offset += 4 + systemFontInfoLength; 8334 const cssFontInfoLength = this.#view.getUint32(offset); 8335 if (cssFontInfoLength === 0) { 8336 return null; 8337 } 8338 const cssFontInfoData = new Uint8Array(cssFontInfoLength); 8339 cssFontInfoData.set(new Uint8Array(this.#buffer, offset + 4, cssFontInfoLength)); 8340 return new CssFontInfo(cssFontInfoData.buffer); 8341 } 8342 get systemFontInfo() { 8343 let offset = FONT_INFO.OFFSET_STRINGS; 8344 const stringsLength = this.#view.getUint32(offset); 8345 offset += 4 + stringsLength; 8346 const systemFontInfoLength = this.#view.getUint32(offset); 8347 if (systemFontInfoLength === 0) { 8348 return null; 8349 } 8350 const systemFontInfoData = new Uint8Array(systemFontInfoLength); 8351 systemFontInfoData.set(new Uint8Array(this.#buffer, offset + 4, systemFontInfoLength)); 8352 return new SystemFontInfo(systemFontInfoData.buffer); 8353 } 8354} 8355class PatternInfo { 8356 constructor(buffer) { 8357 this.buffer = buffer; 8358 this.view = new DataView(buffer); 8359 this.data = new Uint8Array(buffer); 8360 } 8361 getIR() { 8362 const dataView = this.view; 8363 const kind = this.data[PATTERN_INFO.KIND]; 8364 const hasBBox = !!this.data[PATTERN_INFO.HAS_BBOX]; 8365 const hasBackground = !!this.data[PATTERN_INFO.HAS_BACKGROUND]; 8366 const nCoord = dataView.getUint32(PATTERN_INFO.N_COORD, true); 8367 const nColor = dataView.getUint32(PATTERN_INFO.N_COLOR, true); 8368 const nStop = dataView.getUint32(PATTERN_INFO.N_STOP, true); 8369 let offset = 20; 8370 const coords = new Float32Array(this.buffer, offset, nCoord * 2); 8371 offset += nCoord * 8; 8372 const colors = new Uint8Array(this.buffer, offset, nColor * 4); 8373 offset += nColor * 4; 8374 const stops = []; 8375 for (let i = 0; i < nStop; ++i) { 8376 const p = dataView.getFloat32(offset, true); 8377 offset += 4; 8378 const rgb = dataView.getUint32(offset, true); 8379 offset += 4; 8380 stops.push([p, `#${rgb.toString(16).padStart(6, "0")}`]); 8381 } 8382 let bbox = null; 8383 if (hasBBox) { 8384 bbox = []; 8385 for (let i = 0; i < 4; ++i) { 8386 bbox.push(dataView.getFloat32(offset, true)); 8387 offset += 4; 8388 } 8389 } 8390 let background = null; 8391 if (hasBackground) { 8392 background = new Uint8Array(this.buffer, offset, 3); 8393 offset += 3; 8394 } 8395 if (kind === 1) { 8396 return ["RadialAxial", "axial", bbox, stops, Array.from(coords.slice(0, 2)), Array.from(coords.slice(2, 4)), null, null]; 8397 } 8398 if (kind === 2) { 8399 return ["RadialAxial", "radial", bbox, stops, [coords[0], coords[1]], [coords[3], coords[4]], coords[2], coords[5]]; 8400 } 8401 if (kind === 3) { 8402 const shadingType = this.data[PATTERN_INFO.SHADING_TYPE]; 8403 let bounds = null; 8404 if (coords.length > 0) { 8405 bounds = BBOX_INIT.slice(); 8406 for (let i = 0, ii = coords.length; i < ii; i += 2) { 8407 Util.pointBoundingBox(coords[i], coords[i + 1], bounds); 8408 } 8409 } 8410 return ["Mesh", shadingType, coords, colors, nCoord, bounds, bbox, background]; 8411 } 8412 throw new Error(`Unsupported pattern kind: ${kind}`); 8413 } 8414} 8415class FontPathInfo { 8416 #buffer; 8417 constructor(buffer) { 8418 this.#buffer = buffer; 8419 } 8420 get path() { 8421 if (FeatureTest.isFloat16ArraySupported) { 8422 return new Float16Array(this.#buffer); 8423 } 8424 return new Float32Array(this.#buffer); 8425 } 8426} 8427 8428;// ./src/display/api_utils.js 8429 8430function getUrlProp(val) { 8431 if (val instanceof URL) { 8432 return val; 8433 } 8434 if (typeof val === "string") { 8435 if (isNodeJS) { 8436 if (/^[a-z][a-z0-9\-+.]+:/i.test(val)) { 8437 return new URL(val); 8438 } 8439 const url = process.getBuiltinModule("url"); 8440 return new URL(url.pathToFileURL(val)); 8441 } 8442 const url = URL.parse(val, window.location); 8443 if (url) { 8444 return url; 8445 } 8446 } 8447 throw new Error("Invalid PDF url data: " + "either string or URL-object is expected in the url property."); 8448} 8449function getDataProp(val) { 8450 if (isNodeJS && typeof Buffer !== "undefined" && val instanceof Buffer) { 8451 throw new Error("Please provide binary data as `Uint8Array`, rather than `Buffer`."); 8452 } 8453 if (val instanceof Uint8Array && val.byteLength === val.buffer.byteLength) { 8454 return val; 8455 } 8456 if (typeof val === "string") { 8457 return stringToBytes(val); 8458 } 8459 if (val instanceof ArrayBuffer || ArrayBuffer.isView(val) || typeof val === "object" && !isNaN(val?.length)) { 8460 return new Uint8Array(val); 8461 } 8462 throw new Error("Invalid PDF binary data: either TypedArray, " + "string, or array-like object is expected in the data property."); 8463} 8464function getFactoryUrlProp(val) { 8465 if (typeof val !== "string") { 8466 return null; 8467 } 8468 if (val.endsWith("/")) { 8469 return val; 8470 } 8471 throw new Error(`Invalid factory url: "${val}" must include trailing slash.`); 8472} 8473const isRefProxy = v => typeof v === "object" && Number.isInteger(v?.num) && v.num >= 0 && Number.isInteger(v?.gen) && v.gen >= 0; 8474const isNameProxy = v => typeof v === "object" && typeof v?.name === "string"; 8475const isValidExplicitDest = _isValidExplicitDest.bind(null, isRefProxy, isNameProxy); 8476class LoopbackPort { 8477 #listeners = new Map(); 8478 #deferred = Promise.resolve(); 8479 postMessage(obj, transfer) { 8480 const event = { 8481 data: structuredClone(obj, transfer ? { 8482 transfer 8483 } : null) 8484 }; 8485 this.#deferred.then(() => { 8486 for (const [listener] of this.#listeners) { 8487 listener.call(this, event); 8488 } 8489 }); 8490 } 8491 addEventListener(name, listener, options = null) { 8492 let rmAbort = null; 8493 if (options?.signal instanceof AbortSignal) { 8494 const { 8495 signal 8496 } = options; 8497 if (signal.aborted) { 8498 warn("LoopbackPort - cannot use an `aborted` signal."); 8499 return; 8500 } 8501 const onAbort = () => this.removeEventListener(name, listener); 8502 rmAbort = () => signal.removeEventListener("abort", onAbort); 8503 signal.addEventListener("abort", onAbort); 8504 } 8505 this.#listeners.set(listener, rmAbort); 8506 } 8507 removeEventListener(name, listener) { 8508 const rmAbort = this.#listeners.get(listener); 8509 rmAbort?.(); 8510 this.#listeners.delete(listener); 8511 } 8512 terminate() { 8513 for (const [, rmAbort] of this.#listeners) { 8514 rmAbort?.(); 8515 } 8516 this.#listeners.clear(); 8517 } 8518} 8519 8520;// ./src/shared/message_handler.js 8521 8522const CallbackKind = { 8523 DATA: 1, 8524 ERROR: 2 8525}; 8526const StreamKind = { 8527 CANCEL: 1, 8528 CANCEL_COMPLETE: 2, 8529 CLOSE: 3, 8530 ENQUEUE: 4, 8531 ERROR: 5, 8532 PULL: 6, 8533 PULL_COMPLETE: 7, 8534 START_COMPLETE: 8 8535}; 8536function onFn() {} 8537function wrapReason(ex) { 8538 if (ex instanceof AbortException || ex instanceof InvalidPDFException || ex instanceof PasswordException || ex instanceof ResponseException || ex instanceof UnknownErrorException) { 8539 return ex; 8540 } 8541 if (!(ex instanceof Error || typeof ex === "object" && ex !== null)) { 8542 unreachable('wrapReason: Expected "reason" to be a (possibly cloned) Error.'); 8543 } 8544 switch (ex.name) { 8545 case "AbortException": 8546 return new AbortException(ex.message); 8547 case "InvalidPDFException": 8548 return new InvalidPDFException(ex.message); 8549 case "PasswordException": 8550 return new PasswordException(ex.message, ex.code); 8551 case "ResponseException": 8552 return new ResponseException(ex.message, ex.status, ex.missing); 8553 case "UnknownErrorException": 8554 return new UnknownErrorException(ex.message, ex.details); 8555 } 8556 return new UnknownErrorException(ex.message, ex.toString()); 8557} 8558class MessageHandler { 8559 #messageAC = new AbortController(); 8560 constructor(sourceName, targetName, comObj) { 8561 this.sourceName = sourceName; 8562 this.targetName = targetName; 8563 this.comObj = comObj; 8564 this.callbackId = 1; 8565 this.streamId = 1; 8566 this.streamSinks = Object.create(null); 8567 this.streamControllers = Object.create(null); 8568 this.callbackCapabilities = Object.create(null); 8569 this.actionHandler = Object.create(null); 8570 comObj.addEventListener("message", this.#onMessage.bind(this), { 8571 signal: this.#messageAC.signal 8572 }); 8573 } 8574 #onMessage({ 8575 data 8576 }) { 8577 if (data.targetName !== this.sourceName) { 8578 return; 8579 } 8580 if (data.stream) { 8581 this.#processStreamMessage(data); 8582 return; 8583 } 8584 if (data.callback) { 8585 const callbackId = data.callbackId; 8586 const capability = this.callbackCapabilities[callbackId]; 8587 if (!capability) { 8588 throw new Error(`Cannot resolve callback ${callbackId}`); 8589 } 8590 delete this.callbackCapabilities[callbackId]; 8591 if (data.callback === CallbackKind.DATA) { 8592 capability.resolve(data.data); 8593 } else if (data.callback === CallbackKind.ERROR) { 8594 capability.reject(wrapReason(data.reason)); 8595 } else { 8596 throw new Error("Unexpected callback case"); 8597 } 8598 return; 8599 } 8600 const action = this.actionHandler[data.action]; 8601 if (!action) { 8602 throw new Error(`Unknown action from worker: ${data.action}`); 8603 } 8604 if (data.callbackId) { 8605 const sourceName = this.sourceName, 8606 targetName = data.sourceName, 8607 comObj = this.comObj; 8608 Promise.try(action, data.data).then(function (result) { 8609 comObj.postMessage({ 8610 sourceName, 8611 targetName, 8612 callback: CallbackKind.DATA, 8613 callbackId: data.callbackId, 8614 data: result 8615 }); 8616 }, function (reason) { 8617 comObj.postMessage({ 8618 sourceName, 8619 targetName, 8620 callback: CallbackKind.ERROR, 8621 callbackId: data.callbackId, 8622 reason: wrapReason(reason) 8623 }); 8624 }); 8625 return; 8626 } 8627 if (data.streamId) { 8628 this.#createStreamSink(data); 8629 return; 8630 } 8631 action(data.data); 8632 } 8633 on(actionName, handler) { 8634 const ah = this.actionHandler; 8635 if (ah[actionName]) { 8636 throw new Error(`There is already an actionName called "${actionName}"`); 8637 } 8638 ah[actionName] = handler; 8639 } 8640 send(actionName, data, transfers) { 8641 this.comObj.postMessage({ 8642 sourceName: this.sourceName, 8643 targetName: this.targetName, 8644 action: actionName, 8645 data 8646 }, transfers); 8647 } 8648 sendWithPromise(actionName, data, transfers) { 8649 const callbackId = this.callbackId++; 8650 const capability = Promise.withResolvers(); 8651 this.callbackCapabilities[callbackId] = capability; 8652 try { 8653 this.comObj.postMessage({ 8654 sourceName: this.sourceName, 8655 targetName: this.targetName, 8656 action: actionName, 8657 callbackId, 8658 data 8659 }, transfers); 8660 } catch (ex) { 8661 capability.reject(ex); 8662 } 8663 return capability.promise; 8664 } 8665 sendWithStream(actionName, data, queueingStrategy, transfers) { 8666 const streamId = this.streamId++, 8667 sourceName = this.sourceName, 8668 targetName = this.targetName, 8669 comObj = this.comObj; 8670 return new ReadableStream({ 8671 start: controller => { 8672 const startCapability = Promise.withResolvers(); 8673 this.streamControllers[streamId] = { 8674 controller, 8675 startCall: startCapability, 8676 pullCall: null, 8677 cancelCall: null, 8678 isClosed: false 8679 }; 8680 comObj.postMessage({ 8681 sourceName, 8682 targetName, 8683 action: actionName, 8684 streamId, 8685 data, 8686 desiredSize: controller.desiredSize 8687 }, transfers); 8688 return startCapability.promise; 8689 }, 8690 pull: controller => { 8691 const pullCapability = Promise.withResolvers(); 8692 this.streamControllers[streamId].pullCall = pullCapability; 8693 comObj.postMessage({ 8694 sourceName, 8695 targetName, 8696 stream: StreamKind.PULL, 8697 streamId, 8698 desiredSize: controller.desiredSize 8699 }); 8700 return pullCapability.promise; 8701 }, 8702 cancel: reason => { 8703 assert(reason instanceof Error, "cancel must have a valid reason"); 8704 const cancelCapability = Promise.withResolvers(); 8705 this.streamControllers[streamId].cancelCall = cancelCapability; 8706 this.streamControllers[streamId].isClosed = true; 8707 comObj.postMessage({ 8708 sourceName, 8709 targetName, 8710 stream: StreamKind.CANCEL, 8711 streamId, 8712 reason: wrapReason(reason) 8713 }); 8714 return cancelCapability.promise; 8715 } 8716 }, queueingStrategy); 8717 } 8718 #createStreamSink(data) { 8719 const streamId = data.streamId, 8720 sourceName = this.sourceName, 8721 targetName = data.sourceName, 8722 comObj = this.comObj; 8723 const self = this, 8724 action = this.actionHandler[data.action]; 8725 const streamSink = { 8726 enqueue(chunk, size = 1, transfers) { 8727 if (this.isCancelled) { 8728 return; 8729 } 8730 const lastDesiredSize = this.desiredSize; 8731 this.desiredSize -= size; 8732 if (lastDesiredSize > 0 && this.desiredSize <= 0) { 8733 this.sinkCapability = Promise.withResolvers(); 8734 this.ready = this.sinkCapability.promise; 8735 } 8736 comObj.postMessage({ 8737 sourceName, 8738 targetName, 8739 stream: StreamKind.ENQUEUE, 8740 streamId, 8741 chunk 8742 }, transfers); 8743 }, 8744 close() { 8745 if (this.isCancelled) { 8746 return; 8747 } 8748 this.isCancelled = true; 8749 comObj.postMessage({ 8750 sourceName, 8751 targetName, 8752 stream: StreamKind.CLOSE, 8753 streamId 8754 }); 8755 delete self.streamSinks[streamId]; 8756 }, 8757 error(reason) { 8758 assert(reason instanceof Error, "error must have a valid reason"); 8759 if (this.isCancelled) { 8760 return; 8761 } 8762 this.isCancelled = true; 8763 comObj.postMessage({ 8764 sourceName, 8765 targetName, 8766 stream: StreamKind.ERROR, 8767 streamId, 8768 reason: wrapReason(reason) 8769 }); 8770 }, 8771 sinkCapability: Promise.withResolvers(), 8772 onPull: null, 8773 onCancel: null, 8774 isCancelled: false, 8775 desiredSize: data.desiredSize, 8776 ready: null 8777 }; 8778 streamSink.sinkCapability.resolve(); 8779 streamSink.ready = streamSink.sinkCapability.promise; 8780 this.streamSinks[streamId] = streamSink; 8781 Promise.try(action, data.data, streamSink).then(function () { 8782 comObj.postMessage({ 8783 sourceName, 8784 targetName, 8785 stream: StreamKind.START_COMPLETE, 8786 streamId, 8787 success: true 8788 }); 8789 }, function (reason) { 8790 comObj.postMessage({ 8791 sourceName, 8792 targetName, 8793 stream: StreamKind.START_COMPLETE, 8794 streamId, 8795 reason: wrapReason(reason) 8796 }); 8797 }); 8798 } 8799 #processStreamMessage(data) { 8800 const streamId = data.streamId, 8801 sourceName = this.sourceName, 8802 targetName = data.sourceName, 8803 comObj = this.comObj; 8804 const streamController = this.streamControllers[streamId], 8805 streamSink = this.streamSinks[streamId]; 8806 switch (data.stream) { 8807 case StreamKind.START_COMPLETE: 8808 if (data.success) { 8809 streamController.startCall.resolve(); 8810 } else { 8811 streamController.startCall.reject(wrapReason(data.reason)); 8812 } 8813 break; 8814 case StreamKind.PULL_COMPLETE: 8815 if (data.success) { 8816 streamController.pullCall.resolve(); 8817 } else { 8818 streamController.pullCall.reject(wrapReason(data.reason)); 8819 } 8820 break; 8821 case StreamKind.PULL: 8822 if (!streamSink) { 8823 comObj.postMessage({ 8824 sourceName, 8825 targetName, 8826 stream: StreamKind.PULL_COMPLETE, 8827 streamId, 8828 success: true 8829 }); 8830 break; 8831 } 8832 if (streamSink.desiredSize <= 0 && data.desiredSize > 0) { 8833 streamSink.sinkCapability.resolve(); 8834 } 8835 streamSink.desiredSize = data.desiredSize; 8836 Promise.try(streamSink.onPull || onFn).then(function () { 8837 comObj.postMessage({ 8838 sourceName, 8839 targetName, 8840 stream: StreamKind.PULL_COMPLETE, 8841 streamId, 8842 success: true 8843 }); 8844 }, function (reason) { 8845 comObj.postMessage({ 8846 sourceName, 8847 targetName, 8848 stream: StreamKind.PULL_COMPLETE, 8849 streamId, 8850 reason: wrapReason(reason) 8851 }); 8852 }); 8853 break; 8854 case StreamKind.ENQUEUE: 8855 assert(streamController, "enqueue should have stream controller"); 8856 if (streamController.isClosed) { 8857 break; 8858 } 8859 streamController.controller.enqueue(data.chunk); 8860 break; 8861 case StreamKind.CLOSE: 8862 assert(streamController, "close should have stream controller"); 8863 if (streamController.isClosed) { 8864 break; 8865 } 8866 streamController.isClosed = true; 8867 streamController.controller.close(); 8868 this.#deleteStreamController(streamController, streamId); 8869 break; 8870 case StreamKind.ERROR: 8871 assert(streamController, "error should have stream controller"); 8872 streamController.controller.error(wrapReason(data.reason)); 8873 this.#deleteStreamController(streamController, streamId); 8874 break; 8875 case StreamKind.CANCEL_COMPLETE: 8876 if (data.success) { 8877 streamController.cancelCall.resolve(); 8878 } else { 8879 streamController.cancelCall.reject(wrapReason(data.reason)); 8880 } 8881 this.#deleteStreamController(streamController, streamId); 8882 break; 8883 case StreamKind.CANCEL: 8884 if (!streamSink) { 8885 break; 8886 } 8887 const dataReason = wrapReason(data.reason); 8888 Promise.try(streamSink.onCancel || onFn, dataReason).then(function () { 8889 comObj.postMessage({ 8890 sourceName, 8891 targetName, 8892 stream: StreamKind.CANCEL_COMPLETE, 8893 streamId, 8894 success: true 8895 }); 8896 }, function (reason) { 8897 comObj.postMessage({ 8898 sourceName, 8899 targetName, 8900 stream: StreamKind.CANCEL_COMPLETE, 8901 streamId, 8902 reason: wrapReason(reason) 8903 }); 8904 }); 8905 streamSink.sinkCapability.reject(dataReason); 8906 streamSink.isCancelled = true; 8907 delete this.streamSinks[streamId]; 8908 break; 8909 default: 8910 throw new Error("Unexpected stream case"); 8911 } 8912 } 8913 async #deleteStreamController(streamController, streamId) { 8914 await Promise.allSettled([streamController.startCall?.promise, streamController.pullCall?.promise, streamController.cancelCall?.promise]); 8915 delete this.streamControllers[streamId]; 8916 } 8917 destroy() { 8918 this.#messageAC?.abort(); 8919 this.#messageAC = null; 8920 } 8921} 8922 8923;// ./src/display/binary_data_factory.js 8924 8925 8926class BaseBinaryDataFactory { 8927 #errorStr = Object.freeze({ 8928 cMapUrl: "CMap", 8929 standardFontDataUrl: "font", 8930 wasmUrl: "wasm" 8931 }); 8932 constructor({ 8933 cMapUrl = null, 8934 standardFontDataUrl = null, 8935 wasmUrl = null 8936 }) { 8937 this.cMapUrl = cMapUrl; 8938 this.standardFontDataUrl = standardFontDataUrl; 8939 this.wasmUrl = wasmUrl; 8940 } 8941 async fetch({ 8942 kind, 8943 filename 8944 }) { 8945 switch (kind) { 8946 case "cMapUrl": 8947 case "standardFontDataUrl": 8948 case "wasmUrl": 8949 break; 8950 default: 8951 unreachable(`Not implemented: ${kind}`); 8952 } 8953 const baseUrl = this[kind]; 8954 if (!baseUrl) { 8955 throw new Error(`Ensure that the \`${kind}\` API parameter is provided.`); 8956 } 8957 const url = `${baseUrl}${filename}`; 8958 return this._fetch(url, kind).catch(reason => { 8959 throw new Error(`Unable to load ${this.#errorStr[kind]} data at: ${url}`); 8960 }); 8961 } 8962 async _fetch(url, kind) { 8963 unreachable("Abstract method `_fetch` called."); 8964 } 8965} 8966class DOMBinaryDataFactory extends BaseBinaryDataFactory { 8967 async _fetch(url, kind) { 8968 const type = kind === "cMapUrl" && !url.endsWith(".bcmap") ? "text" : "bytes"; 8969 const data = await fetchData(url, type); 8970 return data instanceof Uint8Array ? data : stringToBytes(data); 8971 } 8972} 8973 8974;// ./src/display/canvas_factory.js 8975 8976class BaseCanvasFactory { 8977 #enableHWA = false; 8978 constructor({ 8979 enableHWA = false 8980 }) { 8981 this.#enableHWA = enableHWA; 8982 } 8983 create(width, height) { 8984 if (width <= 0 || height <= 0) { 8985 throw new Error("Invalid canvas size"); 8986 } 8987 const canvas = this._createCanvas(width, height); 8988 return { 8989 canvas, 8990 context: canvas.getContext("2d", { 8991 willReadFrequently: !this.#enableHWA 8992 }) 8993 }; 8994 } 8995 reset({ 8996 canvas 8997 }, width, height) { 8998 if (!canvas) { 8999 throw new Error("Canvas is not specified"); 9000 } 9001 if (width <= 0 || height <= 0) { 9002 throw new Error("Invalid canvas size"); 9003 } 9004 canvas.width = width; 9005 canvas.height = height; 9006 } 9007 destroy(canvasAndContext) { 9008 const { 9009 canvas 9010 } = canvasAndContext; 9011 if (!canvas) { 9012 throw new Error("Canvas is not specified"); 9013 } 9014 canvas.width = canvas.height = 0; 9015 canvasAndContext.canvas = null; 9016 canvasAndContext.context = null; 9017 } 9018 _createCanvas(width, height) { 9019 unreachable("Abstract method `_createCanvas` called."); 9020 } 9021} 9022class DOMCanvasFactory extends BaseCanvasFactory { 9023 constructor({ 9024 ownerDocument = globalThis.document, 9025 enableHWA = false 9026 }) { 9027 super({ 9028 enableHWA 9029 }); 9030 this._document = ownerDocument; 9031 } 9032 _createCanvas(width, height) { 9033 const canvas = this._document.createElement("canvas"); 9034 canvas.width = width; 9035 canvas.height = height; 9036 return canvas; 9037 } 9038} 9039 9040;// ./src/display/filter_factory.js 9041 9042 9043class BaseFilterFactory { 9044 addFilter(maps) { 9045 return "none"; 9046 } 9047 addHCMFilter(fgColor, bgColor) { 9048 return "none"; 9049 } 9050 addAlphaFilter(map) { 9051 return "none"; 9052 } 9053 addLuminosityFilter(map) { 9054 return "none"; 9055 } 9056 addKnockoutFilter(alpha = 0) { 9057 return "none"; 9058 } 9059 addHighlightHCMFilter(filterName, fgColor, bgColor, newFgColor, newBgColor) { 9060 return "none"; 9061 } 9062 addSelectionHCMFilter(fgColor, bgColor) { 9063 return "none"; 9064 } 9065 addSelectionFilter() { 9066 return "none"; 9067 } 9068 createSelectionStyle(pageColors = null) { 9069 return null; 9070 } 9071 destroy(keepHCM = false) {} 9072} 9073class DOMFilterFactory extends BaseFilterFactory { 9074 #baseUrl; 9075 #_cache; 9076 #_defs; 9077 #docId; 9078 #document; 9079 #_hcmCache; 9080 #id = 0; 9081 constructor({ 9082 docId, 9083 ownerDocument = globalThis.document 9084 }) { 9085 super(); 9086 this.#docId = docId; 9087 this.#document = ownerDocument; 9088 } 9089 get #cache() { 9090 return this.#_cache ||= new Map(); 9091 } 9092 get #hcmCache() { 9093 return this.#_hcmCache ||= new Map(); 9094 } 9095 get #defs() { 9096 if (!this.#_defs) { 9097 const div = this.#document.createElement("div"); 9098 const { 9099 style 9100 } = div; 9101 style.colorScheme = "only light"; 9102 style.visibility = "hidden"; 9103 style.contain = "strict"; 9104 style.width = style.height = 0; 9105 style.position = "absolute"; 9106 style.top = style.left = 0; 9107 style.zIndex = -1; 9108 const svg = this.#document.createElementNS(SVG_NS, "svg"); 9109 svg.setAttribute("width", 0); 9110 svg.setAttribute("height", 0); 9111 this.#_defs = this.#document.createElementNS(SVG_NS, "defs"); 9112 div.append(svg); 9113 svg.append(this.#_defs); 9114 this.#document.body.append(div); 9115 } 9116 return this.#_defs; 9117 } 9118 #createTables(maps) { 9119 if (maps.length === 1) { 9120 const mapR = maps[0]; 9121 const buffer = new Array(256); 9122 for (let i = 0; i < 256; i++) { 9123 buffer[i] = mapR[i] / 255; 9124 } 9125 const table = buffer.join(","); 9126 return [table, table, table]; 9127 } 9128 const [mapR, mapG, mapB] = maps; 9129 const bufferR = new Array(256); 9130 const bufferG = new Array(256); 9131 const bufferB = new Array(256); 9132 for (let i = 0; i < 256; i++) { 9133 bufferR[i] = mapR[i] / 255; 9134 bufferG[i] = mapG[i] / 255; 9135 bufferB[i] = mapB[i] / 255; 9136 } 9137 return [bufferR.join(","), bufferG.join(","), bufferB.join(",")]; 9138 } 9139 #createUrl(id) { 9140 if (this.#baseUrl === undefined) { 9141 this.#baseUrl = ""; 9142 const url = this.#document.URL; 9143 if (url !== this.#document.baseURI) { 9144 if (isDataScheme(url)) { 9145 warn('#createUrl: ignore "data:"-URL for performance reasons.'); 9146 } else { 9147 this.#baseUrl = updateUrlHash(url, ""); 9148 } 9149 } 9150 } 9151 return `url(${this.#baseUrl}#${id})`; 9152 } 9153 addFilter(maps) { 9154 if (!maps) { 9155 return "none"; 9156 } 9157 let value = this.#cache.get(maps); 9158 if (value) { 9159 return value; 9160 } 9161 const [tableR, tableG, tableB] = this.#createTables(maps); 9162 const key = maps.length === 1 ? tableR : `${tableR}${tableG}${tableB}`; 9163 value = this.#cache.get(key); 9164 if (value) { 9165 this.#cache.set(maps, value); 9166 return value; 9167 } 9168 const id = `g_${this.#docId}_transfer_map_${this.#id++}`; 9169 const url = this.#createUrl(id); 9170 this.#cache.set(maps, url); 9171 this.#cache.set(key, url); 9172 const filter = this.#createFilter(id); 9173 this.#addTransferMapConversion(tableR, tableG, tableB, filter); 9174 return url; 9175 } 9176 addHCMFilter(fgColor, bgColor) { 9177 const key = `${fgColor}-${bgColor}`; 9178 const filterName = "base"; 9179 let info = this.#hcmCache.get(filterName); 9180 if (info?.key === key) { 9181 return info.url; 9182 } 9183 if (info) { 9184 info.filter?.remove(); 9185 info.key = key; 9186 info.url = "none"; 9187 info.filter = null; 9188 } else { 9189 info = { 9190 key, 9191 url: "none", 9192 filter: null 9193 }; 9194 this.#hcmCache.set(filterName, info); 9195 } 9196 if (!fgColor || !bgColor) { 9197 return info.url; 9198 } 9199 const fgRGB = this.#getRGB(fgColor); 9200 fgColor = Util.makeHexColor(...fgRGB); 9201 const bgRGB = this.#getRGB(bgColor); 9202 bgColor = Util.makeHexColor(...bgRGB); 9203 this.#
vendor: 8,088 bytes, lines 9203-9425
9203defs.style.color = ""; 9204 if (fgColor === "#000000" && bgColor === "#ffffff" || fgColor === bgColor) { 9205 return info.url; 9206 } 9207 const map = new Array(256); 9208 for (let i = 0; i <= 255; i++) { 9209 const x = i / 255; 9210 map[i] = x <= 0.03928 ? x / 12.92 : ((x + 0.055) / 1.055) ** 2.4; 9211 } 9212 const table = map.join(","); 9213 const id = `g_${this.#docId}_hcm_filter`; 9214 const filter = info.filter = this.#createFilter(id); 9215 this.#addTransferMapConversion(table, table, table, filter); 9216 this.#addGrayConversion(filter); 9217 const getSteps = (c, n) => { 9218 const start = fgRGB[c] / 255; 9219 const end = bgRGB[c] / 255; 9220 const arr = new Array(n + 1); 9221 for (let i = 0; i <= n; i++) { 9222 arr[i] = start + i / n * (end - start); 9223 } 9224 return arr.join(","); 9225 }; 9226 this.#addTransferMapConversion(getSteps(0, 5), getSteps(1, 5), getSteps(2, 5), filter); 9227 info.url = this.#createUrl(id); 9228 return info.url; 9229 } 9230 addSelectionHCMFilter(fgColor, bgColor) { 9231 return this.addHighlightHCMFilter("selection", fgColor, bgColor, "HighlightText", "Highlight"); 9232 } 9233 addSelectionFilter() { 9234 return this.addHighlightHCMFilter("selection_default", "black", "white", "HighlightText", "Highlight"); 9235 } 9236 createSelectionStyle(pageColors = null) { 9237 const filter = pageColors ? this.addSelectionHCMFilter(pageColors.foreground, pageColors.background) : this.addSelectionFilter(); 9238 if (filter === "none" || !FeatureTest.platform.isFirefox) { 9239 return null; 9240 } 9241 return { 9242 "backdrop-filter": filter, 9243 "background-color": "transparent" 9244 }; 9245 } 9246 addAlphaFilter(map) { 9247 let value = this.#cache.get(map); 9248 if (value) { 9249 return value; 9250 } 9251 const [tableA] = this.#createTables([map]); 9252 const key = `alpha_${tableA}`; 9253 value = this.#cache.get(key); 9254 if (value) { 9255 this.#cache.set(map, value); 9256 return value; 9257 } 9258 const id = `g_${this.#docId}_alpha_map_${this.#id++}`; 9259 const url = this.#createUrl(id); 9260 this.#cache.set(map, url); 9261 this.#cache.set(key, url); 9262 const filter = this.#createFilter(id); 9263 this.#addTransferMapAlphaConversion(tableA, filter); 9264 return url; 9265 } 9266 addLuminosityFilter(map) { 9267 let value = this.#cache.get(map || "luminosity"); 9268 if (value) { 9269 return value; 9270 } 9271 let tableA, key; 9272 if (map) { 9273 [tableA] = this.#createTables([map]); 9274 key = `luminosity_${tableA}`; 9275 } else { 9276 key = "luminosity"; 9277 } 9278 value = this.#cache.get(key); 9279 if (value) { 9280 this.#cache.set(map, value); 9281 return value; 9282 } 9283 const id = `g_${this.#docId}_luminosity_map_${this.#id++}`; 9284 const url = this.#createUrl(id); 9285 this.#cache.set(map, url); 9286 this.#cache.set(key, url); 9287 const filter = this.#createFilter(id); 9288 this.#addLuminosityConversion(filter); 9289 if (map) { 9290 this.#addTransferMapAlphaConversion(tableA, filter); 9291 } 9292 return url; 9293 } 9294 addKnockoutFilter(alpha = 0) { 9295 const slope = alpha > 0 ? Math.min(1 / alpha, 1e6) : 1e6; 9296 const key = `knockout_${slope}`; 9297 const value = this.#cache.get(key); 9298 if (value) { 9299 return value; 9300 } 9301 const id = `g_${this.#docId}_knockout_filter_${this.#id++}`; 9302 const url = this.#createUrl(id); 9303 this.#cache.set(key, url); 9304 const filter = this.#createFilter(id); 9305 const feComponentTransfer = this.#document.createElementNS(SVG_NS, "feComponentTransfer"); 9306 filter.append(feComponentTransfer); 9307 const feFuncA = this.#document.createElementNS(SVG_NS, "feFuncA"); 9308 feFuncA.setAttribute("type", "linear"); 9309 feFuncA.setAttribute("slope", `${slope}`); 9310 feFuncA.setAttribute("intercept", "0"); 9311 feComponentTransfer.append(feFuncA); 9312 return url; 9313 } 9314 addHighlightHCMFilter(filterName, fgColor, bgColor, newFgColor, newBgColor) { 9315 const key = `${fgColor}-${bgColor}-${newFgColor}-${newBgColor}`; 9316 let info = this.#hcmCache.get(filterName); 9317 if (info?.key === key) { 9318 return info.url; 9319 } 9320 if (info) { 9321 info.filter?.remove(); 9322 info.key = key; 9323 info.url = "none"; 9324 info.filter = null; 9325 } else { 9326 info = { 9327 key, 9328 url: "none", 9329 filter: null 9330 }; 9331 this.#hcmCache.set(filterName, info); 9332 } 9333 if (!fgColor || !bgColor) { 9334 return info.url; 9335 } 9336 const [fgRGB, bgRGB] = [fgColor, bgColor].map(this.#getRGB.bind(this)); 9337 let fgGray = Math.round(0.2126 * fgRGB[0] + 0.7152 * fgRGB[1] + 0.0722 * fgRGB[2]); 9338 let bgGray = Math.round(0.2126 * bgRGB[0] + 0.7152 * bgRGB[1] + 0.0722 * bgRGB[2]); 9339 let [newFgRGB, newBgRGB] = [newFgColor, newBgColor].map(this.#getOpaqueTextColor.bind(this)); 9340 if (bgGray < fgGray) { 9341 [fgGray, bgGray, newFgRGB, newBgRGB] = [bgGray, fgGray, newBgRGB, newFgRGB]; 9342 } 9343 this.#defs.style.color = ""; 9344 const getSteps = (fg, bg, n) => { 9345 const arr = new Array(256); 9346 const step = (bgGray - fgGray) / n; 9347 const newStart = fg / 255; 9348 const newStep = (bg - fg) / (255 * n); 9349 let prev = 0; 9350 for (let i = 0; i <= n; i++) { 9351 const k = Math.round(fgGray + i * step); 9352 const value = newStart + i * newStep; 9353 for (let j = prev; j <= k; j++) { 9354 arr[j] = value; 9355 } 9356 prev = k + 1; 9357 } 9358 for (let i = prev; i < 256; i++) { 9359 arr[i] = arr[prev - 1]; 9360 } 9361 return arr.join(","); 9362 }; 9363 const id = `g_${this.#docId}_hcm_${filterName}_filter`; 9364 const filter = info.filter = this.#createFilter(id); 9365 this.#addGrayConversion(filter); 9366 this.#addTransferMapConversion(getSteps(newFgRGB[0], newBgRGB[0], 5), getSteps(newFgRGB[1], newBgRGB[1], 5), getSteps(newFgRGB[2], newBgRGB[2], 5), filter); 9367 info.url = this.#createUrl(id); 9368 return info.url; 9369 } 9370 destroy(keepHCM = false) { 9371 if (keepHCM && this.#_hcmCache?.size) { 9372 return; 9373 } 9374 this.#_defs?.parentNode.parentNode.remove(); 9375 this.#_defs = null; 9376 this.#_cache?.clear(); 9377 this.#_cache = null; 9378 this.#_hcmCache?.clear(); 9379 this.#_hcmCache = null; 9380 this.#id = 0; 9381 } 9382 #addLuminosityConversion(filter) { 9383 const feColorMatrix = this.#document.createElementNS(SVG_NS, "feColorMatrix"); 9384 feColorMatrix.setAttribute("type", "matrix"); 9385 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"); 9386 filter.append(feColorMatrix); 9387 } 9388 #addGrayConversion(filter) { 9389 const feColorMatrix = this.#document.createElementNS(SVG_NS, "feColorMatrix"); 9390 feColorMatrix.setAttribute("type", "matrix"); 9391 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"); 9392 filter.append(feColorMatrix); 9393 } 9394 #createFilter(id) { 9395 const filter = this.#document.createElementNS(SVG_NS, "filter"); 9396 filter.setAttribute("color-interpolation-filters", "sRGB"); 9397 filter.setAttribute("id", id); 9398 this.#defs.append(filter); 9399 return filter; 9400 } 9401 #appendFeFunc(feComponentTransfer, func, table) { 9402 const feFunc = this.#document.createElementNS(SVG_NS, func); 9403 feFunc.setAttribute("type", "discrete"); 9404 feFunc.setAttribute("tableValues", table); 9405 feComponentTransfer.append(feFunc); 9406 } 9407 #addTransferMapConversion(rTable, gTable, bTable, filter) { 9408 const feComponentTransfer = this.#document.createElementNS(SVG_NS, "feComponentTransfer"); 9409 filter.append(feComponentTransfer); 9410 this.#appendFeFunc(feComponentTransfer, "feFuncR", rTable); 9411 this.#appendFeFunc(feComponentTransfer, "feFuncG", gTable); 9412 this.#appendFeFunc(feComponentTransfer, "feFuncB", bTable); 9413 } 9414 #addTransferMapAlphaConversion(aTable, filter) { 9415 const feComponentTransfer = this.#document.createElementNS(SVG_NS, "feComponentTransfer"); 9416 filter.append(feComponentTransfer); 9417 this.#appendFeFunc(feComponentTransfer, "feFuncA", aTable); 9418 } 9419 #getRGB(color) { 9420 this.#defs.style.color = color; 9421 return getRGB(getComputedStyle(this.#defs).getPropertyValue("color")); 9422 } 9423 #getRGBA(color) { 9424 this.#defs.style.color = color; 9425 return getRGBA(getComputedStyle(this.#defs).getPropertyValue("color"))
vendor: 10,632 bytes, lines 9425-9805
9425; 9426 } 9427 #getOpaqueTextColor(color) { 9428 const [r, g, b, alpha] = this.#getRGBA(color); 9429 if (alpha === 1) { 9430 return [r, g, b]; 9431 } 9432 const [canvasR, canvasG, canvasB] = this.#getRGB("Canvas"); 9433 return [blend(r, canvasR, alpha), blend(g, canvasG, alpha), blend(b, canvasB, alpha)]; 9434 } 9435} 9436function blend(fg, bg, alpha) { 9437 return Math.round(alpha * fg + (1 - alpha) * bg); 9438} 9439 9440;// ./src/display/node_utils.js 9441 9442 9443 9444 9445if (isNodeJS) { 9446 warn("Please use the `legacy` build in Node.js environments."); 9447} 9448async function node_utils_fetchData(url) { 9449 const fs = process.getBuiltinModule("fs/promises"); 9450 const data = await fs.readFile(url); 9451 return new Uint8Array(data); 9452} 9453class NodeFilterFactory extends BaseFilterFactory {} 9454class NodeCanvasFactory extends BaseCanvasFactory { 9455 _createCanvas(width, height) { 9456 const require = process.getBuiltinModule("module").createRequire(import.meta.url); 9457 const canvas = require("@napi-rs/canvas"); 9458 return canvas.createCanvas(width, height); 9459 } 9460} 9461class NodeBinaryDataFactory extends BaseBinaryDataFactory { 9462 async _fetch(url, kind) { 9463 return node_utils_fetchData(url); 9464 } 9465} 9466 9467;// ./src/shared/image_utils.js 9468/* unused harmony import specifier */ var image_utils_ImageKind; 9469/* unused harmony import specifier */ var image_utils_FeatureTest; 9470 9471function convertToRGBA(params) { 9472 switch (params.kind) { 9473 case image_utils_ImageKind.GRAYSCALE_1BPP: 9474 return convertBlackAndWhiteToRGBA(params); 9475 case image_utils_ImageKind.RGB_24BPP: 9476 return convertRGBToRGBA(params); 9477 } 9478 return null; 9479} 9480function convertBlackAndWhiteToRGBA({ 9481 src, 9482 srcPos = 0, 9483 dest, 9484 width, 9485 height, 9486 nonBlackColor = 0xffffffff, 9487 inverseDecode = false 9488}) { 9489 const black = FeatureTest.isLittleEndian ? 0xff000000 : 0x000000ff; 9490 const [zeroMapping, oneMapping] = inverseDecode ? [nonBlackColor, black] : [black, nonBlackColor]; 9491 const widthInSource = width >> 3; 9492 const widthRemainder = width & 7; 9493 const xorMask = zeroMapping ^ oneMapping; 9494 const srcLength = src.length; 9495 dest = new Uint32Array(dest.buffer); 9496 let destPos = 0; 9497 for (let i = 0; i < height; ++i) { 9498 for (const max = srcPos + widthInSource; srcPos < max; ++srcPos, destPos += 8) { 9499 const elem = src[srcPos]; 9500 dest[destPos] = zeroMapping ^ -(elem >> 7 & 1) & xorMask; 9501 dest[destPos + 1] = zeroMapping ^ -(elem >> 6 & 1) & xorMask; 9502 dest[destPos + 2] = zeroMapping ^ -(elem >> 5 & 1) & xorMask; 9503 dest[destPos + 3] = zeroMapping ^ -(elem >> 4 & 1) & xorMask; 9504 dest[destPos + 4] = zeroMapping ^ -(elem >> 3 & 1) & xorMask; 9505 dest[destPos + 5] = zeroMapping ^ -(elem >> 2 & 1) & xorMask; 9506 dest[destPos + 6] = zeroMapping ^ -(elem >> 1 & 1) & xorMask; 9507 dest[destPos + 7] = zeroMapping ^ -(elem & 1) & xorMask; 9508 } 9509 if (widthRemainder === 0) { 9510 continue; 9511 } 9512 const elem = srcPos < srcLength ? src[srcPos++] : 255; 9513 for (let j = 0; j < widthRemainder; ++j, ++destPos) { 9514 dest[destPos] = zeroMapping ^ -(elem >> 7 - j & 1) & xorMask; 9515 } 9516 } 9517 return { 9518 srcPos, 9519 destPos 9520 }; 9521} 9522function convertRGBToRGBA({ 9523 src, 9524 srcPos = 0, 9525 dest, 9526 destPos = 0, 9527 width, 9528 height 9529}) { 9530 let i = 0; 9531 const len = width * height * 3; 9532 const len32 = len >> 2; 9533 const src32 = new Uint32Array(src.buffer, srcPos, len32); 9534 const alphaMask = FeatureTest.isLittleEndian ? 0xff000000 : 0xff; 9535 if (FeatureTest.isLittleEndian) { 9536 for (; i < len32 - 2; i += 3, destPos += 4) { 9537 const s1 = src32[i], 9538 s2 = src32[i + 1], 9539 s3 = src32[i + 2]; 9540 dest[destPos] = s1 | alphaMask; 9541 dest[destPos + 1] = s1 >>> 24 | s2 << 8 | alphaMask; 9542 dest[destPos + 2] = s2 >>> 16 | s3 << 16 | alphaMask; 9543 dest[destPos + 3] = s3 >>> 8 | alphaMask; 9544 } 9545 for (let j = i * 4, jj = srcPos + len; j < jj; j += 3) { 9546 dest[destPos++] = src[j] | src[j + 1] << 8 | src[j + 2] << 16 | alphaMask; 9547 } 9548 } else { 9549 for (; i < len32 - 2; i += 3, destPos += 4) { 9550 const s1 = src32[i], 9551 s2 = src32[i + 1], 9552 s3 = src32[i + 2]; 9553 dest[destPos] = s1 | alphaMask; 9554 dest[destPos + 1] = s1 << 24 | s2 >>> 8 | alphaMask; 9555 dest[destPos + 2] = s2 << 16 | s3 >>> 16 | alphaMask; 9556 dest[destPos + 3] = s3 << 8 | alphaMask; 9557 } 9558 for (let j = i * 4, jj = srcPos + len; j < jj; j += 3) { 9559 dest[destPos++] = src[j] << 24 | src[j + 1] << 16 | src[j + 2] << 8 | alphaMask; 9560 } 9561 } 9562 return { 9563 srcPos: srcPos + len, 9564 destPos 9565 }; 9566} 9567function grayToRGBA(src, dest) { 9568 if (image_utils_FeatureTest.isLittleEndian) { 9569 for (let i = 0, ii = src.length; i < ii; i++) { 9570 dest[i] = src[i] * 0x10101 | 0xff000000; 9571 } 9572 } else { 9573 for (let i = 0, ii = src.length; i < ii; i++) { 9574 dest[i] = src[i] * 0x1010100 | 0x000000ff; 9575 } 9576 } 9577} 9578 9579;// ./src/display/webgpu.js 9580const MESH_WGSL = ` 9581struct Uniforms { 9582 offsetX : f32, 9583 offsetY : f32, 9584 scaleX : f32, 9585 scaleY : f32, 9586 paddedWidth : f32, 9587 paddedHeight : f32, 9588 borderSize : f32, 9589 _pad : f32, 9590}; 9591 9592@group(0) @binding(0) var<uniform> u : Uniforms; 9593 9594struct VertexInput { 9595 @location(0) position : vec2<f32>, 9596 @location(1) color : vec4<f32>, 9597}; 9598 9599struct VertexOutput { 9600 @builtin(position) position : vec4<f32>, 9601 @location(0) color : vec3<f32>, 9602}; 9603 9604@vertex 9605fn vs_main(in : VertexInput) -> VertexOutput { 9606 var out : VertexOutput; 9607 let cx = (in.position.x + u.offsetX) * u.scaleX; 9608 let cy = (in.position.y + u.offsetY) * u.scaleY; 9609 out.position = vec4<f32>( 9610 ((cx + u.borderSize) / u.paddedWidth) * 2.0 - 1.0, 9611 1.0 - ((cy + u.borderSize) / u.paddedHeight) * 2.0, 9612 0.0, 9613 1.0 9614 ); 9615 out.color = in.color.rgb; 9616 return out; 9617} 9618 9619@fragment 9620fn fs_main(in : VertexOutput) -> @location(0) vec4<f32> { 9621 return vec4<f32>(in.color, 1.0); 9622} 9623`; 9624class WebGPU { 9625 #initPromise = null; 9626 #device = null; 9627 #meshPipeline = null; 9628 #preferredFormat = null; 9629 async #initGPU() { 9630 if (!globalThis.navigator?.gpu) { 9631 return false; 9632 } 9633 try { 9634 const adapter = await navigator.gpu.requestAdapter(); 9635 if (!adapter) { 9636 return false; 9637 } 9638 this.#preferredFormat = navigator.gpu.getPreferredCanvasFormat(); 9639 this.#device = await adapter.requestDevice(); 9640 return true; 9641 } catch { 9642 return false; 9643 } 9644 } 9645 init() { 9646 return this.#initPromise ||= this.#initGPU(); 9647 } 9648 get isReady() { 9649 return this.#device !== null; 9650 } 9651 loadMeshShader() { 9652 if (!this.#device || this.#meshPipeline) { 9653 return; 9654 } 9655 const shaderModule = this.#device.createShaderModule({ 9656 code: MESH_WGSL 9657 }); 9658 this.#meshPipeline = this.#device.createRenderPipeline({ 9659 layout: "auto", 9660 vertex: { 9661 module: shaderModule, 9662 entryPoint: "vs_main", 9663 buffers: [{ 9664 arrayStride: 2 * 4, 9665 attributes: [{ 9666 shaderLocation: 0, 9667 offset: 0, 9668 format: "float32x2" 9669 }] 9670 }, { 9671 arrayStride: 4, 9672 attributes: [{ 9673 shaderLocation: 1, 9674 offset: 0, 9675 format: "unorm8x4" 9676 }] 9677 }] 9678 }, 9679 fragment: { 9680 module: shaderModule, 9681 entryPoint: "fs_main", 9682 targets: [{ 9683 format: this.#preferredFormat 9684 }] 9685 }, 9686 primitive: { 9687 topology: "triangle-list" 9688 } 9689 }); 9690 } 9691 draw(posData, colData, vertexCount, context, backgroundColor, paddedWidth, paddedHeight, borderSize) { 9692 this.loadMeshShader(); 9693 const device = this.#device; 9694 const { 9695 offsetX, 9696 offsetY, 9697 scaleX, 9698 scaleY 9699 } = context; 9700 const posBuffer = device.createBuffer({ 9701 size: Math.max(posData.byteLength, 4), 9702 usage: GPUBufferUsage.VERTEX | GPUBufferUsage.COPY_DST 9703 }); 9704 if (posData.byteLength > 0) { 9705 device.queue.writeBuffer(posBuffer, 0, posData); 9706 } 9707 const colBuffer = device.createBuffer({ 9708 size: Math.max(colData.byteLength, 4), 9709 usage: GPUBufferUsage.VERTEX | GPUBufferUsage.COPY_DST 9710 }); 9711 if (colData.byteLength > 0) { 9712 device.queue.writeBuffer(colBuffer, 0, colData); 9713 } 9714 const uniformBuffer = device.createBuffer({ 9715 size: 8 * 4, 9716 usage: GPUBufferUsage.UNIFORM | GPUBufferUsage.COPY_DST 9717 }); 9718 device.queue.writeBuffer(uniformBuffer, 0, new Float32Array([offsetX, offsetY, scaleX, scaleY, paddedWidth, paddedHeight, borderSize, 0])); 9719 const bindGroup = device.createBindGroup({ 9720 layout: this.#meshPipeline.getBindGroupLayout(0), 9721 entries: [{ 9722 binding: 0, 9723 resource: { 9724 buffer: uniformBuffer 9725 } 9726 }] 9727 }); 9728 const offscreen = new OffscreenCanvas(paddedWidth, paddedHeight); 9729 const gpuCtx = offscreen.getContext("webgpu"); 9730 gpuCtx.configure({ 9731 device, 9732 format: this.#preferredFormat, 9733 alphaMode: backgroundColor ? "opaque" : "premultiplied" 9734 }); 9735 const clearValue = backgroundColor ? { 9736 r: backgroundColor[0] / 255, 9737 g: backgroundColor[1] / 255, 9738 b: backgroundColor[2] / 255, 9739 a: 1 9740 } : { 9741 r: 0, 9742 g: 0, 9743 b: 0, 9744 a: 0 9745 }; 9746 const commandEncoder = device.createCommandEncoder(); 9747 const renderPass = commandEncoder.beginRenderPass({ 9748 colorAttachments: [{ 9749 view: gpuCtx.getCurrentTexture().createView(), 9750 clearValue, 9751 loadOp: "clear", 9752 storeOp: "store" 9753 }] 9754 }); 9755 if (vertexCount > 0) { 9756 renderPass.setPipeline(this.#meshPipeline); 9757 renderPass.setBindGroup(0, bindGroup); 9758 renderPass.setVertexBuffer(0, posBuffer); 9759 renderPass.setVertexBuffer(1, colBuffer); 9760 renderPass.draw(vertexCount); 9761 } 9762 renderPass.end(); 9763 device.queue.submit([commandEncoder.finish()]); 9764 posBuffer.destroy(); 9765 colBuffer.destroy(); 9766 uniformBuffer.destroy(); 9767 return offscreen.transferToImageBitmap(); 9768 } 9769} 9770const _webGPU = new WebGPU(); 9771function initGPU() { 9772 return _webGPU.init(); 9773} 9774function isGPUReady() { 9775 return _webGPU.isReady; 9776} 9777function loadMeshShader() { 9778 _webGPU.loadMeshShader(); 9779} 9780function drawMeshWithGPU(posData, colData, vertexCount, context, backgroundColor, paddedWidth, paddedHeight, borderSize) { 9781 return _webGPU.draw(posData, colData, vertexCount, context, backgroundColor, paddedWidth, paddedHeight, borderSize); 9782} 9783 9784;// ./src/display/pattern_helper.js 9785 9786 9787 9788 9789const PathType = { 9790 FILL: "Fill", 9791 STROKE: "Stroke", 9792 SHADING: "Shading" 9793}; 9794function applyBoundingBox(ctx, bbox) { 9795 if (!bbox) { 9796 return; 9797 } 9798 const width = bbox[2] - bbox[0]; 9799 const height = bbox[3] - bbox[1]; 9800 const region = new Path2D(); 9801 region.rect(bbox[0], bbox[1], width, height); 9802 ctx.clip(region); 9803} 9804class BaseShadingPattern { 9805
vendor: 8,038 bytes, lines 9805-10061
9805isModifyingCurrentTransform() { 9806 return false; 9807 } 9808 getPattern() { 9809 unreachable("Abstract method `getPattern` called."); 9810 } 9811} 9812class RadialAxialShadingPattern extends BaseShadingPattern { 9813 constructor(IR) { 9814 super(); 9815 this._type = IR[1]; 9816 this._bbox = IR[2]; 9817 this._colorStops = IR[3]; 9818 this._p0 = IR[4]; 9819 this._p1 = IR[5]; 9820 this._r0 = IR[6]; 9821 this._r1 = IR[7]; 9822 this.matrix = null; 9823 } 9824 isOriginBased() { 9825 return this._p0[0] === 0 && this._p0[1] === 0 && (!this.isRadial() || this._p1[0] === 0 && this._p1[1] === 0); 9826 } 9827 isRadial() { 9828 return this._type === "radial"; 9829 } 9830 areConic() { 9831 if (!this.isRadial()) { 9832 return false; 9833 } 9834 const dist = Math.hypot(this._p0[0] - this._p1[0], this._p0[1] - this._p1[1]); 9835 return dist + this._r1 > this._r0 && dist + this._r0 > this._r1; 9836 } 9837 _createGradient(ctx, transform = null) { 9838 let grad; 9839 let firstPoint = this._p0; 9840 let secondPoint = this._p1; 9841 if (transform) { 9842 firstPoint = firstPoint.slice(); 9843 secondPoint = secondPoint.slice(); 9844 Util.applyTransform(firstPoint, transform); 9845 Util.applyTransform(secondPoint, transform); 9846 } 9847 if (this._type === "axial") { 9848 grad = ctx.createLinearGradient(firstPoint[0], firstPoint[1], secondPoint[0], secondPoint[1]); 9849 } else if (this._type === "radial") { 9850 let r0 = this._r0; 9851 let r1 = this._r1; 9852 if (transform) { 9853 const scale = new Float32Array(2); 9854 Util.singularValueDecompose2dScale(transform, scale); 9855 r0 *= scale[0]; 9856 r1 *= scale[0]; 9857 } 9858 grad = ctx.createRadialGradient(firstPoint[0], firstPoint[1], r0, secondPoint[0], secondPoint[1], r1); 9859 } 9860 for (const colorStop of this._colorStops) { 9861 grad.addColorStop(colorStop[0], colorStop[1]); 9862 } 9863 return grad; 9864 } 9865 _createReversedGradient(ctx, transform = null) { 9866 let firstPoint = this._p1; 9867 let secondPoint = this._p0; 9868 if (transform) { 9869 firstPoint = firstPoint.slice(); 9870 secondPoint = secondPoint.slice(); 9871 Util.applyTransform(firstPoint, transform); 9872 Util.applyTransform(secondPoint, transform); 9873 } 9874 let r0 = this._r1; 9875 let r1 = this._r0; 9876 if (transform) { 9877 const scale = new Float32Array(2); 9878 Util.singularValueDecompose2dScale(transform, scale); 9879 r0 *= scale[0]; 9880 r1 *= scale[0]; 9881 } 9882 const grad = ctx.createRadialGradient(firstPoint[0], firstPoint[1], r0, secondPoint[0], secondPoint[1], r1); 9883 const reversedStops = this._colorStops.map(([t, c]) => [1 - t, c]).reverse(); 9884 for (const [t, c] of reversedStops) { 9885 grad.addColorStop(t, c); 9886 } 9887 return grad; 9888 } 9889 getPattern(ctx, owner, inverse, pathType) { 9890 let pattern; 9891 if (pathType === PathType.STROKE || pathType === PathType.FILL) { 9892 if (this.isOriginBased()) { 9893 let transf = Util.transform(inverse, owner.baseTransform); 9894 if (this.matrix) { 9895 transf = Util.transform(transf, this.matrix); 9896 } 9897 const precision = 1e-3; 9898 const n1 = Math.hypot(transf[0], transf[1]); 9899 const n2 = Math.hypot(transf[2], transf[3]); 9900 const ps = (transf[0] * transf[2] + transf[1] * transf[3]) / (n1 * n2); 9901 if (Math.abs(ps) < precision) { 9902 if (this.isRadial()) { 9903 if (Math.abs(n1 - n2) < precision) { 9904 return this._createGradient(ctx, transf); 9905 } 9906 } else { 9907 return this._createGradient(ctx, transf); 9908 } 9909 } 9910 } 9911 const ownerBBox = owner.current.getClippedPathBoundingBox(pathType, getCurrentTransform(ctx)) || [0, 0, 0, 0]; 9912 const width = Math.ceil(ownerBBox[2] - ownerBBox[0]) || 1; 9913 const height = Math.ceil(ownerBBox[3] - ownerBBox[1]) || 1; 9914 const tmpCanvas = owner.canvasFactory.create(width, height); 9915 const tmpCtx = tmpCanvas.context; 9916 tmpCtx.clearRect(0, 0, tmpCtx.canvas.width, tmpCtx.canvas.height); 9917 tmpCtx.beginPath(); 9918 tmpCtx.rect(0, 0, tmpCtx.canvas.width, tmpCtx.canvas.height); 9919 tmpCtx.translate(-ownerBBox[0], -ownerBBox[1]); 9920 inverse = Util.transform(inverse, [1, 0, 0, 1, ownerBBox[0], ownerBBox[1]]); 9921 tmpCtx.transform(...owner.baseTransform); 9922 if (this.matrix) { 9923 tmpCtx.transform(...this.matrix); 9924 } 9925 applyBoundingBox(tmpCtx, this._bbox); 9926 if (this.areConic()) { 9927 tmpCtx.fillStyle = this._createReversedGradient(tmpCtx); 9928 tmpCtx.fill(); 9929 } 9930 tmpCtx.fillStyle = this._createGradient(tmpCtx); 9931 tmpCtx.fill(); 9932 pattern = ctx.createPattern(tmpCanvas.canvas, "no-repeat"); 9933 owner.canvasFactory.destroy(tmpCanvas); 9934 const domMatrix = new DOMMatrix(inverse); 9935 pattern.setTransform(domMatrix); 9936 } else { 9937 if (this.areConic()) { 9938 ctx.save(); 9939 applyBoundingBox(ctx, this._bbox); 9940 ctx.fillStyle = this._createReversedGradient(ctx); 9941 ctx.fillRect(-1e10, -1e10, 2e10, 2e10); 9942 ctx.restore(); 9943 } 9944 applyBoundingBox(ctx, this._bbox); 9945 pattern = this._createGradient(ctx); 9946 } 9947 return pattern; 9948 } 9949} 9950function drawTriangle(data, context, p1, p2, p3, c1, c2, c3) { 9951 const coords = context.coords, 9952 colors = context.colors; 9953 const bytes = data.data, 9954 rowSize = data.width * 4; 9955 let tmp; 9956 if (coords[p1 * 2 + 1] > coords[p2 * 2 + 1]) { 9957 tmp = p1; 9958 p1 = p2; 9959 p2 = tmp; 9960 tmp = c1; 9961 c1 = c2; 9962 c2 = tmp; 9963 } 9964 if (coords[p2 * 2 + 1] > coords[p3 * 2 + 1]) { 9965 tmp = p2; 9966 p2 = p3; 9967 p3 = tmp; 9968 tmp = c2; 9969 c2 = c3; 9970 c3 = tmp; 9971 } 9972 if (coords[p1 * 2 + 1] > coords[p2 * 2 + 1]) { 9973 tmp = p1; 9974 p1 = p2; 9975 p2 = tmp; 9976 tmp = c1; 9977 c1 = c2; 9978 c2 = tmp; 9979 } 9980 const x1 = (coords[p1 * 2] + context.offsetX) * context.scaleX; 9981 const y1 = (coords[p1 * 2 + 1] + context.offsetY) * context.scaleY; 9982 const x2 = (coords[p2 * 2] + context.offsetX) * context.scaleX; 9983 const y2 = (coords[p2 * 2 + 1] + context.offsetY) * context.scaleY; 9984 const x3 = (coords[p3 * 2] + context.offsetX) * context.scaleX; 9985 const y3 = (coords[p3 * 2 + 1] + context.offsetY) * context.scaleY; 9986 if (y1 >= y3) { 9987 return; 9988 } 9989 const c1r = colors[c1 * 4], 9990 c1g = colors[c1 * 4 + 1], 9991 c1b = colors[c1 * 4 + 2]; 9992 const c2r = colors[c2 * 4], 9993 c2g = colors[c2 * 4 + 1], 9994 c2b = colors[c2 * 4 + 2]; 9995 const c3r = colors[c3 * 4], 9996 c3g = colors[c3 * 4 + 1], 9997 c3b = colors[c3 * 4 + 2]; 9998 const minY = Math.round(y1), 9999 maxY = Math.round(y3); 10000 let xa, car, cag, cab; 10001 let xb, cbr, cbg, cbb; 10002 for (let y = minY; y <= maxY; y++) { 10003 if (y < y2) { 10004 const k = y < y1 ? 0 : (y1 - y) / (y1 - y2); 10005 xa = x1 - (x1 - x2) * k; 10006 car = c1r - (c1r - c2r) * k; 10007 cag = c1g - (c1g - c2g) * k; 10008 cab = c1b - (c1b - c2b) * k; 10009 } else { 10010 let k; 10011 if (y > y3) { 10012 k = 1; 10013 } else if (y2 === y3) { 10014 k = 0; 10015 } else { 10016 k = (y2 - y) / (y2 - y3); 10017 } 10018 xa = x2 - (x2 - x3) * k; 10019 car = c2r - (c2r - c3r) * k; 10020 cag = c2g - (c2g - c3g) * k; 10021 cab = c2b - (c2b - c3b) * k; 10022 } 10023 let k; 10024 if (y < y1) { 10025 k = 0; 10026 } else if (y > y3) { 10027 k = 1; 10028 } else { 10029 k = (y1 - y) / (y1 - y3); 10030 } 10031 xb = x1 - (x1 - x3) * k; 10032 cbr = c1r - (c1r - c3r) * k; 10033 cbg = c1g - (c1g - c3g) * k; 10034 cbb = c1b - (c1b - c3b) * k; 10035 const x1_ = Math.round(Math.min(xa, xb)); 10036 const x2_ = Math.round(Math.max(xa, xb)); 10037 let j = rowSize * y + x1_ * 4; 10038 for (let x = x1_; x <= x2_; x++) { 10039 k = (xa - x) / (xa - xb); 10040 if (k < 0) { 10041 k = 0; 10042 } else if (k > 1) { 10043 k = 1; 10044 } 10045 bytes[j++] = car - (car - cbr) * k | 0; 10046 bytes[j++] = cag - (cag - cbg) * k | 0; 10047 bytes[j++] = cab - (cab - cbb) * k | 0; 10048 bytes[j++] = 255; 10049 } 10050 } 10051} 10052class MeshShadingPattern extends BaseShadingPattern { 10053 constructor(IR) { 10054 super(); 10055 this._posData = IR[2]; 10056 this._colData = IR[3]; 10057 this._vertexCount = IR[4]; 10058 this._bounds = IR[5]; 10059 this._bbox = IR[6]; 10060 this._background = IR[7]; 10061 this.matrix = null
vendor: 13,078 bytes, lines 10061-10407
10061; 10062 loadMeshShader(); 10063 } 10064 _createMeshCanvas(combinedScale, backgroundColor, canvasFactory) { 10065 const EXPECTED_SCALE = 1.1; 10066 const MAX_PATTERN_SIZE = 3000; 10067 const BORDER_SIZE = 2; 10068 const offsetX = Math.floor(this._bounds[0]); 10069 const offsetY = Math.floor(this._bounds[1]); 10070 const boundsWidth = Math.ceil(this._bounds[2]) - offsetX; 10071 const boundsHeight = Math.ceil(this._bounds[3]) - offsetY; 10072 const width = Math.min(Math.ceil(Math.abs(boundsWidth * combinedScale[0] * EXPECTED_SCALE)), MAX_PATTERN_SIZE) || 1; 10073 const height = Math.min(Math.ceil(Math.abs(boundsHeight * combinedScale[1] * EXPECTED_SCALE)), MAX_PATTERN_SIZE) || 1; 10074 const scaleX = boundsWidth ? boundsWidth / width : 1; 10075 const scaleY = boundsHeight ? boundsHeight / height : 1; 10076 const context = { 10077 coords: this._posData, 10078 colors: this._colData, 10079 offsetX: -offsetX, 10080 offsetY: -offsetY, 10081 scaleX: 1 / scaleX, 10082 scaleY: 1 / scaleY 10083 }; 10084 const paddedWidth = width + BORDER_SIZE * 2; 10085 const paddedHeight = height + BORDER_SIZE * 2; 10086 const tmpCanvas = canvasFactory.create(paddedWidth, paddedHeight); 10087 if (isGPUReady() && this._vertexCount > 48) { 10088 tmpCanvas.context.drawImage(drawMeshWithGPU(this._posData, this._colData, this._vertexCount, context, backgroundColor, paddedWidth, paddedHeight, BORDER_SIZE), 0, 0); 10089 } else { 10090 const data = tmpCanvas.context.createImageData(width, height); 10091 if (backgroundColor) { 10092 const bytes = data.data; 10093 for (let i = 0, ii = bytes.length; i < ii; i += 4) { 10094 bytes[i] = backgroundColor[0]; 10095 bytes[i + 1] = backgroundColor[1]; 10096 bytes[i + 2] = backgroundColor[2]; 10097 bytes[i + 3] = 255; 10098 } 10099 } 10100 for (let i = 0, ii = this._vertexCount; i < ii; i += 3) { 10101 drawTriangle(data, context, i, i + 1, i + 2, i, i + 1, i + 2); 10102 } 10103 tmpCanvas.context.putImageData(data, BORDER_SIZE, BORDER_SIZE); 10104 } 10105 return { 10106 canvas: tmpCanvas.canvas, 10107 offsetX: offsetX - BORDER_SIZE * scaleX, 10108 offsetY: offsetY - BORDER_SIZE * scaleY, 10109 scaleX, 10110 scaleY 10111 }; 10112 } 10113 isModifyingCurrentTransform() { 10114 return true; 10115 } 10116 getPattern(ctx, owner, inverse, pathType) { 10117 applyBoundingBox(ctx, this._bbox); 10118 const scale = new Float32Array(2); 10119 if (pathType === PathType.SHADING) { 10120 Util.singularValueDecompose2dScale(getCurrentTransform(ctx), scale); 10121 } else if (this.matrix) { 10122 Util.singularValueDecompose2dScale(this.matrix, scale); 10123 const [matrixScaleX, matrixScaleY] = scale; 10124 Util.singularValueDecompose2dScale(owner.baseTransform, scale); 10125 scale[0] *= matrixScaleX; 10126 scale[1] *= matrixScaleY; 10127 } else { 10128 Util.singularValueDecompose2dScale(owner.baseTransform, scale); 10129 } 10130 const temporaryPatternCanvas = this._createMeshCanvas(scale, pathType === PathType.SHADING ? null : this._background, owner.canvasFactory); 10131 if (pathType !== PathType.SHADING) { 10132 ctx.setTransform(...owner.baseTransform); 10133 if (this.matrix) { 10134 ctx.transform(...this.matrix); 10135 } 10136 } 10137 ctx.translate(temporaryPatternCanvas.offsetX, temporaryPatternCanvas.offsetY); 10138 ctx.scale(temporaryPatternCanvas.scaleX, temporaryPatternCanvas.scaleY); 10139 const pattern = ctx.createPattern(temporaryPatternCanvas.canvas, "no-repeat"); 10140 owner.canvasFactory.destroy(temporaryPatternCanvas); 10141 return pattern; 10142 } 10143} 10144class DummyShadingPattern extends BaseShadingPattern { 10145 getPattern() { 10146 return "hotpink"; 10147 } 10148} 10149function getShadingPattern(IR) { 10150 switch (IR[0]) { 10151 case "RadialAxial": 10152 return new RadialAxialShadingPattern(IR); 10153 case "Mesh": 10154 return new MeshShadingPattern(IR); 10155 case "Dummy": 10156 return new DummyShadingPattern(); 10157 } 10158 throw new Error(`Unknown IR type: ${IR[0]}`); 10159} 10160const PaintType = { 10161 COLORED: 1, 10162 UNCOLORED: 2 10163}; 10164class TilingPattern { 10165 static MAX_PATTERN_SIZE = 3000; 10166 constructor(IR, ctx, canvasGraphicsFactory, baseTransform) { 10167 this.color = IR[1]; 10168 this.operatorList = IR[2]; 10169 this.matrix = IR[3]; 10170 this.bbox = IR[4]; 10171 this.xstep = IR[5]; 10172 this.ystep = IR[6]; 10173 this.paintType = IR[7]; 10174 this.tilingType = IR[8]; 10175 this.needsIsolation = IR[9] ?? true; 10176 this.ctx = ctx; 10177 this.canvasGraphicsFactory = canvasGraphicsFactory; 10178 this.baseTransform = baseTransform; 10179 this.patternBaseMatrix = this.matrix ? Util.transform(baseTransform, this.matrix) : baseTransform; 10180 } 10181 canSkipPatternCanvas([width, height, offsetX, offsetY]) { 10182 const [x0, y0, x1, y1] = this.bbox; 10183 const absXStep = Math.abs(this.xstep); 10184 const absYStep = Math.abs(this.ystep); 10185 if (width > absXStep + 1e-6 || height > absYStep + 1e-6) { 10186 return null; 10187 } 10188 const nXFirst = Math.floor((offsetX - x1) / absXStep) + 1; 10189 const nXLast = Math.ceil((offsetX + width - x0) / absXStep) - 1; 10190 const nYFirst = Math.floor((offsetY - y1) / absYStep) + 1; 10191 const nYLast = Math.ceil((offsetY + height - y0) / absYStep) - 1; 10192 return nXLast <= nXFirst && nYLast <= nYFirst ? [nXFirst, nYFirst] : null; 10193 } 10194 updatePatternDims(clippedBBox, dims) { 10195 const inv = Util.inverseTransform(this.patternBaseMatrix); 10196 const c1 = [clippedBBox[0], clippedBBox[1]]; 10197 const c2 = [clippedBBox[2], clippedBBox[3]]; 10198 Util.applyTransform(c1, inv); 10199 Util.applyTransform(c2, inv); 10200 dims[0] = Math.abs(c2[0] - c1[0]); 10201 dims[1] = Math.abs(c2[1] - c1[1]); 10202 dims[2] = Math.min(c1[0], c2[0]); 10203 dims[3] = Math.min(c1[1], c2[1]); 10204 } 10205 _renderTileCanvas(owner, opIdx, dimx, dimy) { 10206 const [x0, y0, x1, y1] = this.bbox; 10207 const tmpCanvas = owner.canvasFactory.create(dimx.size, dimy.size); 10208 const tmpCtx = tmpCanvas.context; 10209 const graphics = this.canvasGraphicsFactory.createCanvasGraphics(tmpCtx, opIdx); 10210 graphics.groupLevel = owner.groupLevel; 10211 this.setFillAndStrokeStyleToContext(graphics, this.paintType, this.color); 10212 tmpCtx.translate(-dimx.scale * x0, -dimy.scale * y0); 10213 graphics.transform(0, dimx.scale, 0, 0, dimy.scale, 0, 0); 10214 tmpCtx.save(); 10215 graphics.dependencyTracker?.save(); 10216 this.clipBbox(graphics, x0, y0, x1, y1); 10217 graphics.baseTransform = getCurrentTransform(graphics.ctx); 10218 graphics.executeOperatorList(this.operatorList); 10219 graphics.endDrawing(); 10220 graphics.dependencyTracker?.restore(); 10221 tmpCtx.restore(); 10222 return tmpCanvas; 10223 } 10224 _getCombinedScales() { 10225 const scale = new Float32Array(2); 10226 Util.singularValueDecompose2dScale(this.matrix, scale); 10227 const [matrixScaleX, matrixScaleY] = scale; 10228 Util.singularValueDecompose2dScale(this.baseTransform, scale); 10229 return [matrixScaleX * scale[0], matrixScaleY * scale[1]]; 10230 } 10231 drawPattern(owner, path, useEOFill = false, [n, m], opIdx) { 10232 const [x0, y0, x1, y1] = this.bbox; 10233 const dependencyTracker = owner.dependencyTracker; 10234 if (dependencyTracker) { 10235 owner.dependencyTracker = new CanvasNestedDependencyTracker(dependencyTracker, opIdx); 10236 } 10237 owner.save(); 10238 if (useEOFill) { 10239 owner.ctx.clip(path, "evenodd"); 10240 } else { 10241 owner.ctx.clip(path); 10242 } 10243 owner.ctx.setTransform(...this.patternBaseMatrix); 10244 owner.ctx.translate(n * this.xstep, m * this.ystep); 10245 if (this.needsIsolation || owner.ctx.globalAlpha !== 1 || owner.ctx.globalCompositeOperation !== "source-over" || owner.inSMaskMode) { 10246 const bboxWidth = x1 - x0; 10247 const bboxHeight = y1 - y0; 10248 const [combinedScaleX, combinedScaleY] = this._getCombinedScales(); 10249 const dimx = this.getSizeAndScale(bboxWidth, this.ctx.canvas.width, combinedScaleX); 10250 const dimy = this.getSizeAndScale(bboxHeight, this.ctx.canvas.height, combinedScaleY); 10251 const tmpCanvas = this._renderTileCanvas(owner, opIdx, dimx, dimy); 10252 owner.ctx.drawImage(tmpCanvas.canvas, x0, y0, bboxWidth, bboxHeight); 10253 owner.canvasFactory.destroy(tmpCanvas); 10254 } else { 10255 this.setFillAndStrokeStyleToContext(owner, this.paintType, this.color); 10256 this.clipBbox(owner, x0, y0, x1, y1); 10257 owner.baseTransformStack.push(owner.baseTransform); 10258 owner.baseTransform = getCurrentTransform(owner.ctx); 10259 owner.executeOperatorList(this.operatorList); 10260 owner.baseTransform = owner.baseTransformStack.pop(); 10261 } 10262 owner.restore(); 10263 if (dependencyTracker) { 10264 owner.dependencyTracker = dependencyTracker; 10265 } 10266 } 10267 createPatternCanvas(owner, opIdx) { 10268 const [x0, y0, x1, y1] = this.bbox; 10269 const width = x1 - x0; 10270 const height = y1 - y0; 10271 let { 10272 xstep, 10273 ystep 10274 } = this; 10275 xstep = Math.abs(xstep); 10276 ystep = Math.abs(ystep); 10277 info("TilingType: " + this.tilingType); 10278 const [combinedScaleX, combinedScaleY] = this._getCombinedScales(); 10279 let canvasWidth = width, 10280 canvasHeight = height, 10281 redrawHorizontally = false, 10282 redrawVertically = false; 10283 if (Math.ceil(xstep * combinedScaleX) >= Math.ceil(width * combinedScaleX)) { 10284 canvasWidth = xstep; 10285 } else { 10286 redrawHorizontally = true; 10287 } 10288 if (Math.ceil(ystep * combinedScaleY) >= Math.ceil(height * combinedScaleY)) { 10289 canvasHeight = ystep; 10290 } else { 10291 redrawVertically = true; 10292 } 10293 const dimx = this.getSizeAndScale(canvasWidth, this.ctx.canvas.width, combinedScaleX); 10294 const dimy = this.getSizeAndScale(canvasHeight, this.ctx.canvas.height, combinedScaleY); 10295 const tmpCanvas = this._renderTileCanvas(owner, opIdx, dimx, dimy); 10296 if (redrawHorizontally || redrawVertically) { 10297 const image = tmpCanvas.canvas; 10298 if (redrawHorizontally) { 10299 canvasWidth = xstep; 10300 } 10301 if (redrawVertically) { 10302 canvasHeight = ystep; 10303 } 10304 const dimx2 = this.getSizeAndScale(canvasWidth, this.ctx.canvas.width, combinedScaleX); 10305 const dimy2 = this.getSizeAndScale(canvasHeight, this.ctx.canvas.height, combinedScaleY); 10306 const xSize = dimx2.size; 10307 const ySize = dimy2.size; 10308 const tmpCanvas2 = owner.canvasFactory.create(xSize, ySize); 10309 const tmpCtx2 = tmpCanvas2.context; 10310 const ii = redrawHorizontally ? Math.floor(width / xstep) : 0; 10311 const jj = redrawVertically ? Math.floor(height / ystep) : 0; 10312 for (let i = 0; i <= ii; i++) { 10313 for (let j = 0; j <= jj; j++) { 10314 tmpCtx2.drawImage(image, xSize * i, ySize * j, xSize, ySize, 0, 0, xSize, ySize); 10315 } 10316 } 10317 owner.canvasFactory.destroy(tmpCanvas); 10318 return { 10319 canvas: tmpCanvas2.canvas, 10320 canvasEntry: tmpCanvas2, 10321 scaleX: dimx2.scale, 10322 scaleY: dimy2.scale, 10323 offsetX: x0, 10324 offsetY: y0 10325 }; 10326 } 10327 return { 10328 canvas: tmpCanvas.canvas, 10329 canvasEntry: tmpCanvas, 10330 scaleX: dimx.scale, 10331 scaleY: dimy.scale, 10332 offsetX: x0, 10333 offsetY: y0 10334 }; 10335 } 10336 getSizeAndScale(step, realOutputSize, scale) { 10337 const maxSize = Math.max(TilingPattern.MAX_PATTERN_SIZE, realOutputSize); 10338 let size = Math.ceil(step * scale); 10339 if (size >= maxSize) { 10340 size = maxSize; 10341 } else { 10342 scale = size / step; 10343 } 10344 return { 10345 scale, 10346 size 10347 }; 10348 } 10349 clipBbox(graphics, x0, y0, x1, y1) { 10350 const bboxWidth = x1 - x0; 10351 const bboxHeight = y1 - y0; 10352 const clip = new Path2D(); 10353 clip.rect(x0, y0, bboxWidth, bboxHeight); 10354 Util.axialAlignedBoundingBox([x0, y0, x1, y1], getCurrentTransform(graphics.ctx), graphics.current.minMax); 10355 graphics.ctx.clip(clip); 10356 graphics.current.updateClipFromPath(); 10357 } 10358 setFillAndStrokeStyleToContext(graphics, paintType, color) { 10359 const context = graphics.ctx, 10360 current = graphics.current; 10361 current.patternFill = current.patternStroke = false; 10362 switch (paintType) { 10363 case PaintType.COLORED: 10364 const { 10365 fillStyle, 10366 strokeStyle 10367 } = this.ctx; 10368 context.fillStyle = current.fillColor = fillStyle; 10369 context.strokeStyle = current.strokeColor = strokeStyle; 10370 break; 10371 case PaintType.UNCOLORED: 10372 context.fillStyle = context.strokeStyle = color; 10373 current.fillColor = current.strokeColor = color; 10374 break; 10375 default: 10376 throw new FormatError(`Unsupported paint type: ${paintType}`); 10377 } 10378 } 10379 isModifyingCurrentTransform() { 10380 return false; 10381 } 10382 getPattern(ctx, owner, inverse, pathType, opIdx) { 10383 const matrix = pathType !== PathType.SHADING ? Util.transform(inverse, this.patternBaseMatrix) : inverse; 10384 const temporaryPatternCanvas = this.createPatternCanvas(owner, opIdx); 10385 let domMatrix = new DOMMatrix(matrix); 10386 domMatrix = domMatrix.translate(temporaryPatternCanvas.offsetX, temporaryPatternCanvas.offsetY); 10387 domMatrix = domMatrix.scale(1 / temporaryPatternCanvas.scaleX, 1 / temporaryPatternCanvas.scaleY); 10388 const pattern = ctx.createPattern(temporaryPatternCanvas.canvas, "repeat"); 10389 owner.canvasFactory.destroy(temporaryPatternCanvas.canvasEntry); 10390 pattern.setTransform(domMatrix); 10391 return pattern; 10392 } 10393} 10394 10395;// ./src/display/canvas.js 10396 10397 10398 10399 10400 10401 10402const MIN_FONT_SIZE = 16; 10403const MAX_FONT_SIZE = 100; 10404const EXECUTION_TIME = 15; 10405const EXECUTION_STEPS = 10; 10406const FULL_CHUNK_HEIGHT = 16; 10407const
10407SCALE_MATRIX = new DOMMatrix()
vendor: 64,347 bytes, lines 10407-12169
10407; 10408const XY = new Float32Array(2); 10409function mirrorContextOperations(ctx, destCtx) { 10410 if (ctx._removeMirroring) { 10411 throw new Error("Context is already forwarding operations."); 10412 } 10413 const originalMethods = new Map(); 10414 for (const name of ["save", "restore", "rotate", "scale", "translate", "transform", "setTransform", "resetTransform", "clip", "moveTo", "lineTo", "bezierCurveTo", "quadraticCurveTo", "arc", "arcTo", "ellipse", "rect", "roundRect", "closePath", "beginPath"]) { 10415 const original = ctx[name]; 10416 if (typeof original !== "function" || typeof destCtx[name] !== "function") { 10417 continue; 10418 } 10419 originalMethods.set(name, original); 10420 ctx[name] = function (...args) { 10421 destCtx[name](...args); 10422 return original.apply(this, args); 10423 }; 10424 } 10425 ctx._removeMirroring = () => { 10426 for (const [name, original] of originalMethods) { 10427 ctx[name] = original; 10428 } 10429 delete ctx._removeMirroring; 10430 }; 10431} 10432function drawImageAtIntegerCoords(ctx, srcImg, srcX, srcY, srcW, srcH, destX, destY, destW, destH) { 10433 const [a, b, c, d, tx, ty] = getCurrentTransform(ctx); 10434 if (b === 0 && c === 0) { 10435 const tlX = destX * a + tx; 10436 const rTlX = Math.round(tlX); 10437 const tlY = destY * d + ty; 10438 const rTlY = Math.round(tlY); 10439 const brX = (destX + destW) * a + tx; 10440 const rWidth = Math.abs(Math.round(brX) - rTlX) || 1; 10441 const brY = (destY + destH) * d + ty; 10442 const rHeight = Math.abs(Math.round(brY) - rTlY) || 1; 10443 ctx.setTransform(Math.sign(a), 0, 0, Math.sign(d), rTlX, rTlY); 10444 ctx.drawImage(srcImg, srcX, srcY, srcW, srcH, 0, 0, rWidth, rHeight); 10445 ctx.setTransform(a, b, c, d, tx, ty); 10446 return [rWidth, rHeight]; 10447 } 10448 if (a === 0 && d === 0) { 10449 const tlX = destY * c + tx; 10450 const rTlX = Math.round(tlX); 10451 const tlY = destX * b + ty; 10452 const rTlY = Math.round(tlY); 10453 const brX = (destY + destH) * c + tx; 10454 const rWidth = Math.abs(Math.round(brX) - rTlX) || 1; 10455 const brY = (destX + destW) * b + ty; 10456 const rHeight = Math.abs(Math.round(brY) - rTlY) || 1; 10457 ctx.setTransform(0, Math.sign(b), Math.sign(c), 0, rTlX, rTlY); 10458 ctx.drawImage(srcImg, srcX, srcY, srcW, srcH, 0, 0, rHeight, rWidth); 10459 ctx.setTransform(a, b, c, d, tx, ty); 10460 return [rHeight, rWidth]; 10461 } 10462 ctx.drawImage(srcImg, srcX, srcY, srcW, srcH, destX, destY, destW, destH); 10463 const scaleX = Math.hypot(a, b); 10464 const scaleY = Math.hypot(c, d); 10465 return [scaleX * destW, scaleY * destH]; 10466} 10467class CanvasExtraState { 10468 alphaIsShape = false; 10469 fontSize = 0; 10470 fontSizeScale = 1; 10471 textMatrix = null; 10472 textMatrixScale = 1; 10473 fontMatrix = FONT_IDENTITY_MATRIX; 10474 leading = 0; 10475 x = 0; 10476 y = 0; 10477 lineX = 0; 10478 lineY = 0; 10479 charSpacing = 0; 10480 wordSpacing = 0; 10481 textHScale = 1; 10482 textRenderingMode = TextRenderingMode.FILL; 10483 textRise = 0; 10484 fillColor = "#000000"; 10485 strokeColor = "#000000"; 10486 tilingPatternDims = null; 10487 patternFill = false; 10488 patternStroke = false; 10489 fillAlpha = 1; 10490 strokeAlpha = 1; 10491 lineWidth = 1; 10492 activeSMask = null; 10493 transferMaps = "none"; 10494 minMax = F32_BBOX_INIT.slice(); 10495 constructor(width, height) { 10496 this.clipBox = new Float32Array([0, 0, width, height]); 10497 } 10498 clone() { 10499 const clone = Object.create(this); 10500 clone.clipBox = this.clipBox.slice(); 10501 clone.minMax = this.minMax.slice(); 10502 clone.tilingPatternDims = this.tilingPatternDims?.slice(); 10503 return clone; 10504 } 10505 getPathBoundingBox(pathType = PathType.FILL, transform = null) { 10506 const box = this.minMax.slice(); 10507 if (pathType === PathType.STROKE) { 10508 if (!transform) { 10509 unreachable("Stroke bounding box must include transform."); 10510 } 10511 Util.singularValueDecompose2dScale(transform, XY); 10512 const xStrokePad = XY[0] * this.lineWidth / 2; 10513 const yStrokePad = XY[1] * this.lineWidth / 2; 10514 box[0] -= xStrokePad; 10515 box[1] -= yStrokePad; 10516 box[2] += xStrokePad; 10517 box[3] += yStrokePad; 10518 } 10519 return box; 10520 } 10521 updateClipFromPath() { 10522 const intersect = Util.intersect(this.clipBox, this.getPathBoundingBox()); 10523 this.startNewPathAndClipBox(intersect || [0, 0, 0, 0]); 10524 } 10525 isEmptyClip() { 10526 return this.minMax[0] === Infinity; 10527 } 10528 startNewPathAndClipBox(box) { 10529 this.clipBox.set(box, 0); 10530 this.minMax.set(F32_BBOX_INIT, 0); 10531 } 10532 getClippedPathBoundingBox(pathType = PathType.FILL, transform = null) { 10533 return Util.intersect(this.clipBox, this.getPathBoundingBox(pathType, transform)); 10534 } 10535} 10536function putBinaryImageData(ctx, imgData) { 10537 const { 10538 width, 10539 height, 10540 kind 10541 } = imgData; 10542 const partialChunkHeight = height % FULL_CHUNK_HEIGHT; 10543 const fullChunks = (height - partialChunkHeight) / FULL_CHUNK_HEIGHT; 10544 const totalChunks = partialChunkHeight === 0 ? fullChunks : fullChunks + 1; 10545 const chunkImgData = ctx.createImageData(width, FULL_CHUNK_HEIGHT); 10546 let srcPos = 0; 10547 const src = imgData.data; 10548 const dest = chunkImgData.data; 10549 let i; 10550 if (kind === ImageKind.GRAYSCALE_1BPP) { 10551 for (i = 0; i < totalChunks; i++) { 10552 ({ 10553 srcPos 10554 } = convertBlackAndWhiteToRGBA({ 10555 src, 10556 srcPos, 10557 dest, 10558 width, 10559 height: i < fullChunks ? FULL_CHUNK_HEIGHT : partialChunkHeight 10560 })); 10561 ctx.putImageData(chunkImgData, 0, i * FULL_CHUNK_HEIGHT); 10562 } 10563 } else if (kind === ImageKind.RGBA_32BPP) { 10564 let j = 0; 10565 let elemsInThisChunk = width * FULL_CHUNK_HEIGHT * 4; 10566 for (i = 0; i < fullChunks; i++) { 10567 dest.set(src.subarray(srcPos, srcPos + elemsInThisChunk)); 10568 srcPos += elemsInThisChunk; 10569 ctx.putImageData(chunkImgData, 0, j); 10570 j += FULL_CHUNK_HEIGHT; 10571 } 10572 if (i < totalChunks) { 10573 elemsInThisChunk = width * partialChunkHeight * 4; 10574 dest.set(src.subarray(srcPos, srcPos + elemsInThisChunk)); 10575 ctx.putImageData(chunkImgData, 0, j); 10576 } 10577 } else if (kind === ImageKind.RGB_24BPP) { 10578 for (i = 0; i < totalChunks; i++) { 10579 ({ 10580 srcPos 10581 } = convertRGBToRGBA({ 10582 src, 10583 srcPos, 10584 dest: new Uint32Array(dest.buffer), 10585 width, 10586 height: i < fullChunks ? FULL_CHUNK_HEIGHT : partialChunkHeight 10587 })); 10588 ctx.putImageData(chunkImgData, 0, i * FULL_CHUNK_HEIGHT); 10589 } 10590 } else { 10591 throw new Error(`bad image kind: ${kind}`); 10592 } 10593} 10594function putBinaryImageMask(ctx, imgData) { 10595 if (imgData.bitmap) { 10596 ctx.drawImage(imgData.bitmap, 0, 0); 10597 return; 10598 } 10599 const { 10600 width, 10601 height 10602 } = imgData; 10603 const partialChunkHeight = height % FULL_CHUNK_HEIGHT; 10604 const fullChunks = (height - partialChunkHeight) / FULL_CHUNK_HEIGHT; 10605 const totalChunks = partialChunkHeight === 0 ? fullChunks : fullChunks + 1; 10606 const chunkImgData = ctx.createImageData(width, FULL_CHUNK_HEIGHT); 10607 let srcPos = 0; 10608 const src = imgData.data; 10609 const dest = chunkImgData.data; 10610 for (let i = 0; i < totalChunks; i++) { 10611 ({ 10612 srcPos 10613 } = convertBlackAndWhiteToRGBA({ 10614 src, 10615 srcPos, 10616 dest, 10617 width, 10618 height: i < fullChunks ? FULL_CHUNK_HEIGHT : partialChunkHeight, 10619 nonBlackColor: 0 10620 })); 10621 ctx.putImageData(chunkImgData, 0, i * FULL_CHUNK_HEIGHT); 10622 } 10623} 10624function copyCtxState(sourceCtx, destCtx) { 10625 const properties = ["strokeStyle", "fillStyle", "fillRule", "globalAlpha", "lineWidth", "lineCap", "lineJoin", "miterLimit", "globalCompositeOperation", "font", "filter"]; 10626 for (const property of properties) { 10627 if (sourceCtx[property] !== undefined) { 10628 destCtx[property] = sourceCtx[property]; 10629 } 10630 } 10631 if (sourceCtx.setLineDash !== undefined) { 10632 destCtx.setLineDash(sourceCtx.getLineDash()); 10633 destCtx.lineDashOffset = sourceCtx.lineDashOffset; 10634 } 10635} 10636function resetCtxToDefault(ctx) { 10637 ctx.strokeStyle = ctx.fillStyle = "#000000"; 10638 ctx.fillRule = "nonzero"; 10639 ctx.globalAlpha = 1; 10640 ctx.lineWidth = 1; 10641 ctx.lineCap = "butt"; 10642 ctx.lineJoin = "miter"; 10643 ctx.miterLimit = 10; 10644 ctx.globalCompositeOperation = "source-over"; 10645 ctx.font = "10px sans-serif"; 10646 if (ctx.setLineDash !== undefined) { 10647 ctx.setLineDash([]); 10648 ctx.lineDashOffset = 0; 10649 } 10650 const { 10651 filter 10652 } = ctx; 10653 if (filter !== "none" && filter !== "") { 10654 ctx.filter = "none"; 10655 } 10656} 10657function getImageSmoothingEnabled(transform, interpolate) { 10658 if (interpolate) { 10659 return true; 10660 } 10661 Util.singularValueDecompose2dScale(transform, XY); 10662 const actualScale = Math.fround(OutputScale.pixelRatio * PixelsPerInch.PDF_TO_CSS_UNITS); 10663 return XY[0] <= actualScale && XY[1] <= actualScale; 10664} 10665const LINE_CAP_STYLES = ["butt", "round", "square"]; 10666const LINE_JOIN_STYLES = ["miter", "round", "bevel"]; 10667const NORMAL_CLIP = {}; 10668const EO_CLIP = {}; 10669class CanvasGraphics { 10670 #knockoutGroupLevel = 0; 10671 #knockoutElementDepth = 0; 10672 #knockoutTempCanvasEntry = null; 10673 #knockoutSavedCtx = null; 10674 #knockoutSavedSMaskCtx = null; 10675 #knockoutSavedGCO = null; 10676 #knockoutElementAlpha = 1; 10677 #knockoutFilterCache; 10678 #knockoutElementGroupMeta = null; 10679 #groupStackMeta = []; 10680 constructor(canvasCtx, commonObjs, objs, canvasFactory, filterFactory, { 10681 optionalContentConfig, 10682 markedContentStack = null 10683 }, annotationCanvasMap, pageColors, dependencyTracker, imagesTracker) { 10684 this.ctx = canvasCtx; 10685 this.current = new CanvasExtraState(this.ctx.canvas.width, this.ctx.canvas.height); 10686 this.stateStack = []; 10687 this.pendingClip = null; 10688 this.pendingEOFill = false; 10689 this.commonObjs = commonObjs; 10690 this.objs = objs; 10691 this.canvasFactory = canvasFactory; 10692 this.filterFactory = filterFactory; 10693 this.groupStack = []; 10694 this.baseTransform = null; 10695 this.baseTransformStack = []; 10696 this.groupLevel = 0; 10697 this.smaskStack = []; 10698 this.tempSMask = null; 10699 this.smaskGroupCanvases = []; 10700 this.smaskPreparedEntry = null; 10701 this.smaskPreparedFor = null; 10702 this.smaskPreparedOffsetX = 0; 10703 this.smaskPreparedOffsetY = 0; 10704 this.smaskPreparedOOBAlpha = null; 10705 this.suspendedCtx = null; 10706 this.contentVisible = true; 10707 this.markedContentStack = markedContentStack || []; 10708 this.optionalContentConfig = optionalContentConfig; 10709 this.cachedPatterns = new Map(); 10710 this.annotationCanvasMap = annotationCanvasMap; 10711 this.viewportScale = 1; 10712 this.outputScaleX = 1; 10713 this.outputScaleY = 1; 10714 this.pageColors = pageColors; 10715 this._cachedScaleForStroking = [-1, 0]; 10716 this._cachedGetSinglePixelWidth = null; 10717 this._cachedBitmapsMap = new Map(); 10718 this.dependencyTracker = dependencyTracker ?? null; 10719 this.imagesTracker = imagesTracker ?? null; 10720 } 10721 getObject(opIdx, data, fallback = null) { 10722 if (typeof data === "string") { 10723 this.dependencyTracker?.recordNamedDependency(opIdx, data); 10724 return data.startsWith("g_") ? this.commonObjs.get(data) : this.objs.get(data); 10725 } 10726 return fallback; 10727 } 10728 beginDrawing({ 10729 transform, 10730 viewport, 10731 transparency = false, 10732 background = null 10733 }) { 10734 const width = this.ctx.canvas.width; 10735 const height = this.ctx.canvas.height; 10736 const savedFillStyle = this.ctx.fillStyle; 10737 this.ctx.fillStyle = background || "#ffffff"; 10738 this.ctx.fillRect(0, 0, width, height); 10739 this.ctx.fillStyle = savedFillStyle; 10740 if (transparency) { 10741 const transparentCanvas = this.transparentCanvasEntry = this.canvasFactory.create(width, height); 10742 this.compositeCtx = this.ctx; 10743 ({ 10744 canvas: this.transparentCanvas, 10745 context: this.ctx 10746 } = transparentCanvas); 10747 this.ctx.save(); 10748 this.ctx.transform(...getCurrentTransform(this.compositeCtx)); 10749 } 10750 this.ctx.save(); 10751 resetCtxToDefault(this.ctx); 10752 if (transform) { 10753 this.ctx.transform(...transform); 10754 this.outputScaleX = transform[0]; 10755 this.outputScaleY = transform[3]; 10756 } 10757 this.ctx.transform(...viewport.transform); 10758 this.viewportScale = viewport.scale; 10759 this.baseTransform = getCurrentTransform(this.ctx); 10760 } 10761 executeOperatorList(operatorList, executionStartIdx, continueCallback, stepper, operationsFilter) { 10762 const argsArray = operatorList.argsArray; 10763 const fnArray = operatorList.fnArray; 10764 let i = executionStartIdx || 0; 10765 const argsArrayLen = argsArray.length; 10766 if (argsArrayLen === i) { 10767 return i; 10768 } 10769 const chunkOperations = argsArrayLen - i > EXECUTION_STEPS && typeof continueCallback === "function"; 10770 const endTime = chunkOperations ? Date.now() + EXECUTION_TIME : 0; 10771 let steps = 0; 10772 const commonObjs = this.commonObjs; 10773 const objs = this.objs; 10774 let fnId, fnArgs; 10775 while (true) { 10776 if (stepper !== undefined) { 10777 if (i === stepper.nextBreakPoint) { 10778 stepper.breakIt(i, continueCallback); 10779 return i; 10780 } 10781 if (stepper.shouldSkip(i)) { 10782 if (++i === argsArrayLen) { 10783 return i; 10784 } 10785 continue; 10786 } 10787 } 10788 if (!operationsFilter || operationsFilter(i)) { 10789 fnId = fnArray[i]; 10790 fnArgs = argsArray[i] ?? null; 10791 if (fnId !== OPS.dependency) { 10792 if (fnArgs === null) { 10793 this[fnId](i); 10794 } else { 10795 this[fnId](i, ...fnArgs); 10796 } 10797 } else { 10798 for (const depObjId of fnArgs) { 10799 this.dependencyTracker?.recordNamedData(depObjId, i); 10800 const objsPool = depObjId.startsWith("g_") ? commonObjs : objs; 10801 if (!objsPool.has(depObjId)) { 10802 objsPool.get(depObjId, continueCallback); 10803 return i; 10804 } 10805 } 10806 } 10807 } 10808 i++; 10809 if (i === argsArrayLen) { 10810 return i; 10811 } 10812 if (chunkOperations && ++steps > EXECUTION_STEPS) { 10813 if (Date.now() > endTime) { 10814 continueCallback(); 10815 return i; 10816 } 10817 steps = 0; 10818 } 10819 } 10820 } 10821 #restoreInitialState() { 10822 while (this.stateStack.length || this.inSMaskMode) { 10823 this.restore(); 10824 } 10825 this.current.activeSMask = null; 10826 this.ctx.restore(); 10827 if (this.transparentCanvas) { 10828 this.ctx = this.compositeCtx; 10829 this.ctx.save(); 10830 this.ctx.setTransform(1, 0, 0, 1, 0, 0); 10831 this.ctx.drawImage(this.transparentCanvas, 0, 0); 10832 this.ctx.restore(); 10833 this.canvasFactory.destroy(this.transparentCanvasEntry); 10834 this.transparentCanvas = null; 10835 this.transparentCanvasEntry = null; 10836 } 10837 } 10838 endDrawing() { 10839 this.#restoreInitialState(); 10840 for (const canvas of this.smaskGroupCanvases) { 10841 this.canvasFactory.destroy(canvas); 10842 } 10843 this.smaskGroupCanvases.length = 0; 10844 this._clearPreparedSMask(); 10845 this.tempSMask = null; 10846 this.smaskStack.length = 0; 10847 for (const meta of this.#groupStackMeta) { 10848 this.#destroyKnockoutPools(meta); 10849 } 10850 this.#groupStackMeta.length = 0; 10851 this.#knockoutTempCanvasEntry = null; 10852 this.#knockoutSavedCtx = null; 10853 this.#knockoutSavedSMaskCtx = null; 10854 this.#knockoutSavedGCO = null; 10855 this.#knockoutElementAlpha = 1; 10856 this.#knockoutElementGroupMeta = null; 10857 this.#knockoutElementDepth = 0; 10858 this.#knockoutGroupLevel = 0; 10859 this.cachedPatterns.clear(); 10860 for (const cache of this._cachedBitmapsMap.values()) { 10861 for (const canvas of cache.values()) { 10862 if (typeof HTMLCanvasElement !== "undefined" && canvas instanceof HTMLCanvasElement) { 10863 canvas.width = canvas.height = 0; 10864 } 10865 } 10866 cache.clear(); 10867 } 10868 this._cachedBitmapsMap.clear(); 10869 this.#drawFilter(); 10870 } 10871 #drawFilter() { 10872 if (this.pageColors) { 10873 const hcmFilterId = this.filterFactory.addHCMFilter(this.pageColors.foreground, this.pageColors.background); 10874 if (hcmFilterId !== "none") { 10875 const savedFilter = this.ctx.filter; 10876 this.ctx.filter = hcmFilterId; 10877 this.ctx.drawImage(this.ctx.canvas, 0, 0); 10878 this.ctx.filter = savedFilter; 10879 } 10880 } 10881 } 10882 _scaleImage(img, inverseTransform) { 10883 const width = img.width ?? img.displayWidth; 10884 const height = img.height ?? img.displayHeight; 10885 const widthScale = Math.max(Math.hypot(inverseTransform[0], inverseTransform[1]), 1); 10886 const heightScale = Math.max(Math.hypot(inverseTransform[2], inverseTransform[3]), 1); 10887 const scaleSteps = []; 10888 let ws = widthScale, 10889 hs = heightScale, 10890 pw = width, 10891 ph = height; 10892 while (ws > 2 && pw > 1 || hs > 2 && ph > 1) { 10893 let nw = pw, 10894 nh = ph; 10895 if (ws > 2 && pw > 1) { 10896 nw = Math.ceil(pw / 2); 10897 ws /= pw / nw; 10898 } 10899 if (hs > 2 && ph > 1) { 10900 nh = Math.ceil(ph / 2); 10901 hs /= ph / nh; 10902 } 10903 scaleSteps.push({ 10904 newWidth: nw, 10905 newHeight: nh 10906 }); 10907 pw = nw; 10908 ph = nh; 10909 } 10910 if (scaleSteps.length === 0) { 10911 return { 10912 img, 10913 paintWidth: width, 10914 paintHeight: height, 10915 tmpCanvas: null 10916 }; 10917 } 10918 if (scaleSteps.length === 1) { 10919 const { 10920 newWidth, 10921 newHeight 10922 } = scaleSteps[0]; 10923 const tmpCanvas = this.canvasFactory.create(newWidth, newHeight); 10924 tmpCanvas.context.drawImage(img, 0, 0, width, height, 0, 0, newWidth, newHeight); 10925 return { 10926 img: tmpCanvas.canvas, 10927 paintWidth: newWidth, 10928 paintHeight: newHeight, 10929 tmpCanvas 10930 }; 10931 } 10932 let readEntry = this.canvasFactory.create(1, 1); 10933 let writeEntry = this.canvasFactory.create(1, 1); 10934 let paintWidth = width, 10935 paintHeight = height; 10936 let source = img; 10937 for (const { 10938 newWidth, 10939 newHeight 10940 } of scaleSteps) { 10941 this.canvasFactory.reset(writeEntry, newWidth, newHeight); 10942 writeEntry.context.drawImage(source, 0, 0, paintWidth, paintHeight, 0, 0, newWidth, newHeight); 10943 [readEntry, writeEntry] = [writeEntry, readEntry]; 10944 source = readEntry.canvas; 10945 paintWidth = newWidth; 10946 paintHeight = newHeight; 10947 } 10948 this.canvasFactory.destroy(writeEntry); 10949 return { 10950 img: readEntry.canvas, 10951 paintWidth, 10952 paintHeight, 10953 tmpCanvas: readEntry 10954 }; 10955 } 10956 _createMaskCanvas(opIdx, img) { 10957 const ctx = this.ctx; 10958 const { 10959 width, 10960 height 10961 } = img; 10962 const fillColor = this.current.fillColor; 10963 const isPatternFill = this.current.patternFill; 10964 const currentTransform = getCurrentTransform(ctx); 10965 let cache, cacheKey, scaled, maskCanvas; 10966 if ((img.bitmap || img.data) && img.count > 1) { 10967 const mainKey = img.bitmap || img.data.buffer; 10968 cacheKey = JSON.stringify(isPatternFill ? currentTransform : [currentTransform.slice(0, 4), fillColor]); 10969 cache = this._cachedBitmapsMap.getOrInsertComputed(mainKey, makeMap); 10970 const cachedImage = cache.get(cacheKey); 10971 if (cachedImage && !isPatternFill) { 10972 const offsetX = Math.round(Math.min(currentTransform[0], currentTransform[2]) + currentTransform[4]); 10973 const offsetY = Math.round(Math.min(currentTransform[1], currentTransform[3]) + currentTransform[5]); 10974 this.dependencyTracker?.recordDependencies(opIdx, Dependencies.transformAndFill); 10975 return { 10976 canvas: cachedImage, 10977 offsetX, 10978 offsetY 10979 }; 10980 } 10981 scaled = cachedImage; 10982 } 10983 if (!scaled) { 10984 maskCanvas = this.canvasFactory.create(width, height); 10985 putBinaryImageMask(maskCanvas.context, img); 10986 } 10987 let maskToCanvas = Util.transform(currentTransform, [1 / width, 0, 0, -1 / height, 0, 0]); 10988 maskToCanvas = Util.transform(maskToCanvas, [1, 0, 0, 1, 0, -height]); 10989 const minMax = F32_BBOX_INIT.slice(); 10990 Util.axialAlignedBoundingBox([0, 0, width, height], maskToCanvas, minMax); 10991 const [minX, minY, maxX, maxY] = minMax; 10992 const drawnWidth = Math.round(maxX - minX) || 1; 10993 const drawnHeight = Math.round(maxY - minY) || 1; 10994 const fillCanvas = this.canvasFactory.create(drawnWidth, drawnHeight); 10995 const fillCtx = fillCanvas.context; 10996 const offsetX = minX; 10997 const offsetY = minY; 10998 fillCtx.translate(-offsetX, -offsetY); 10999 fillCtx.transform(...maskToCanvas); 11000 let scaledEntry = null; 11001 if (!scaled) { 11002 const scaleResult = this._scaleImage(maskCanvas.canvas, getCurrentTransformInverse(fillCtx)); 11003 scaled = scaleResult.img; 11004 scaledEntry = scaleResult.tmpCanvas; 11005 if (scaled !== maskCanvas.canvas) { 11006 this.canvasFactory.destroy(maskCanvas); 11007 maskCanvas = null; 11008 } 11009 if (cache && isPatternFill) { 11010 cache.set(cacheKey, scaled); 11011 scaledEntry = null; 11012 maskCanvas = null; 11013 } 11014 } 11015 fillCtx.imageSmoothingEnabled = getImageSmoothingEnabled(getCurrentTransform(fillCtx), img.interpolate); 11016 drawImageAtIntegerCoords(fillCtx, scaled, 0, 0, scaled.width, scaled.height, 0, 0, width, height); 11017 if (scaledEntry) { 11018 this.canvasFactory.destroy(scaledEntry); 11019 } 11020 if (maskCanvas) { 11021 this.canvasFactory.destroy(maskCanvas); 11022 } 11023 fillCtx.globalCompositeOperation = "source-in"; 11024 const inverse = Util.transform(getCurrentTransformInverse(fillCtx), [1, 0, 0, 1, -offsetX, -offsetY]); 11025 fillCtx.fillStyle = isPatternFill ? fillColor.getPattern(ctx, this, inverse, PathType.FILL, opIdx) : fillColor; 11026 fillCtx.fillRect(0, 0, width, height); 11027 if (cache && !isPatternFill) { 11028 cache.set(cacheKey, fillCanvas.canvas); 11029 } 11030 this.dependencyTracker?.recordDependencies(opIdx, Dependencies.transformAndFill); 11031 return { 11032 canvas: fillCanvas.canvas, 11033 canvasEntry: cache && !isPatternFill ? null : fillCanvas, 11034 offsetX: Math.round(offsetX), 11035 offsetY: Math.round(offsetY) 11036 }; 11037 } 11038 setLineWidth(opIdx, width) { 11039 this.dependencyTracker?.recordSimpleData("lineWidth", opIdx); 11040 if (width !== this.current.lineWidth) { 11041 this._cachedScaleForStroking[0] = -1; 11042 } 11043 this.current.lineWidth = width; 11044 this.ctx.lineWidth = width; 11045 } 11046 setLineCap(opIdx, style) { 11047 this.dependencyTracker?.recordSimpleData("lineCap", opIdx); 11048 this.ctx.lineCap = LINE_CAP_STYLES[style]; 11049 } 11050 setLineJoin(opIdx, style) { 11051 this.dependencyTracker?.recordSimpleData("lineJoin", opIdx); 11052 this.ctx.lineJoin = LINE_JOIN_STYLES[style]; 11053 } 11054 setMiterLimit(opIdx, limit) { 11055 this.dependencyTracker?.recordSimpleData("miterLimit", opIdx); 11056 this.ctx.miterLimit = limit; 11057 } 11058 setDash(opIdx, dashArray, dashPhase) { 11059 this.dependencyTracker?.recordSimpleData("dash", opIdx); 11060 const ctx = this.ctx; 11061 if (ctx.setLineDash !== undefined) { 11062 ctx.setLineDash(dashArray); 11063 ctx.lineDashOffset = dashPhase; 11064 } 11065 } 11066 setRenderingIntent(opIdx, intent) {} 11067 setFlatness(opIdx, flatness) {} 11068 setGState(opIdx, states) { 11069 for (const [key, value] of states) { 11070 switch (key) { 11071 case "LW": 11072 this.setLineWidth(opIdx, value); 11073 break; 11074 case "LC": 11075 this.setLineCap(opIdx, value); 11076 break; 11077 case "LJ": 11078 this.setLineJoin(opIdx, value); 11079 break; 11080 case "ML": 11081 this.setMiterLimit(opIdx, value); 11082 break; 11083 case "D": 11084 this.setDash(opIdx, value[0], value[1]); 11085 break; 11086 case "RI": 11087 this.setRenderingIntent(opIdx, value); 11088 break; 11089 case "FL": 11090 this.setFlatness(opIdx, value); 11091 break; 11092 case "Font": 11093 this.setFont(opIdx, value[0], value[1]); 11094 break; 11095 case "CA": 11096 this.dependencyTracker?.recordSimpleData("strokeAlpha", opIdx); 11097 this.current.strokeAlpha = value; 11098 break; 11099 case "ca": 11100 this.dependencyTracker?.recordSimpleData("fillAlpha", opIdx); 11101 this.ctx.globalAlpha = this.current.fillAlpha = value; 11102 break; 11103 case "BM": 11104 this.dependencyTracker?.recordSimpleData("globalCompositeOperation", opIdx); 11105 this.ctx.globalCompositeOperation = value; 11106 break; 11107 case "SMask": 11108 this.dependencyTracker?.recordSimpleData("SMask", opIdx); 11109 this.current.activeSMask = value ? this.tempSMask : null; 11110 if (this.current.activeSMask) { 11111 this.current.activeSMask.blendMode = this.ctx.globalCompositeOperation; 11112 } 11113 this.tempSMask = null; 11114 this.checkSMaskState(opIdx); 11115 break; 11116 case "TR": 11117 this.dependencyTracker?.recordSimpleData("filter", opIdx); 11118 this.ctx.filter = this.current.transferMaps = this.filterFactory.addFilter(value); 11119 break; 11120 } 11121 } 11122 } 11123 get inSMaskMode() { 11124 return !!this.suspendedCtx; 11125 } 11126 _clearPreparedSMask() { 11127 if (this.smaskPreparedEntry) { 11128 this.canvasFactory.destroy(this.smaskPreparedEntry); 11129 this.smaskPreparedEntry = null; 11130 } 11131 this.smaskPreparedFor = null; 11132 this.smaskPreparedOffsetX = 0; 11133 this.smaskPreparedOffsetY = 0; 11134 this.smaskPreparedOOBAlpha = null; 11135 } 11136 _ensurePreparedSMask(smask) { 11137 if (smask === this.smaskPreparedFor) { 11138 return; 11139 } 11140 this._clearPreparedSMask(); 11141 this._prepareSMaskCanvas(smask); 11142 } 11143 checkSMaskState(opIdx) { 11144 const inSMaskMode = this.inSMaskMode; 11145 if (this.current.activeSMask && !inSMaskMode) { 11146 this.beginSMaskMode(opIdx); 11147 } else if (!this.current.activeSMask && inSMaskMode) { 11148 this.endSMaskMode(); 11149 } else if (this.current.activeSMask && inSMaskMode) { 11150 this._ensurePreparedSMask(this.current.activeSMask); 11151 } 11152 } 11153 _prepareSMaskCanvas(smask) { 11154 const { 11155 canvas: maskCanvas, 11156 subtype, 11157 backdrop, 11158 transferMap 11159 } = smask; 11160 const hasFilter = subtype === "Luminosity" || subtype === "Alpha" && transferMap; 11161 if (!hasFilter && !(subtype === "Luminosity" && backdrop)) { 11162 this.smaskPreparedFor = smask; 11163 return; 11164 } 11165 let filteredOOBAlpha; 11166 if (subtype === "Luminosity" && backdrop) { 11167 const [r, g, b] = getRGBA(backdrop); 11168 const inputAlpha = Math.round(0.3 * r + 0.59 * g + 0.11 * b); 11169 filteredOOBAlpha = transferMap?.[inputAlpha] ?? inputAlpha; 11170 } else { 11171 filteredOOBAlpha = transferMap?.[0] ?? 0; 11172 } 11173 const SMASK_LAYER_TO_MASK_AREA_RATIO = 4; 11174 const { 11175 width: layerW, 11176 height: layerH 11177 } = this.ctx.canvas; 11178 const maskArea = maskCanvas.width * maskCanvas.height; 11179 const useLayerSize = layerW * layerH < SMASK_LAYER_TO_MASK_AREA_RATIO * maskArea; 11180 const filterSpec = hasFilter ? { 11181 url: subtype === "Alpha" ? this.filterFactory.addAlphaFilter(transferMap) : this.filterFactory.addLuminosityFilter(transferMap), 11182 subtype, 11183 transferMap 11184 } : null; 11185 const bakedBackdrop = subtype === "Luminosity" ? backdrop : null; 11186 let preparedEntry, offsetX, offsetY; 11187 if (useLayerSize) { 11188 preparedEntry = this._bakeSMaskCanvas(maskCanvas, smask.offsetX, smask.offsetY, layerW, layerH, bakedBackdrop, filterSpec); 11189 offsetX = 0; 11190 offsetY = 0; 11191 } else { 11192 preparedEntry = this._bakeSMaskCanvas(maskCanvas, 0, 0, maskCanvas.width, maskCanvas.height, bakedBackdrop, filterSpec); 11193 offsetX = smask.offsetX; 11194 offsetY = smask.offsetY; 11195 } 11196 this.smaskPreparedEntry = preparedEntry; 11197 this.smaskPreparedFor = smask; 11198 this.smaskPreparedOffsetX = offsetX; 11199 this.smaskPreparedOffsetY = offsetY; 11200 this.smaskPreparedOOBAlpha = !useLayerSize && filteredOOBAlpha !== 0 ? filteredOOBAlpha : null; 11201 } 11202 _bakeSMaskCanvas(maskCanvas, drawX, drawY, w, h, backdrop, filterSpec) { 11203 if (!backdrop && !filterSpec) { 11204 unreachable("_bakeSMaskCanvas with neither backdrop nor filter"); 11205 } 11206 const srcEntry = this.canvasFactory.create(w, h); 11207 const sCtx = srcEntry.context; 11208 sCtx.drawImage(maskCanvas, drawX, drawY); 11209 if (backdrop) { 11210 sCtx.globalCompositeOperation = "destination-atop"; 11211 sCtx.fillStyle = backdrop; 11212 sCtx.fillRect(0, 0, w, h); 11213 } 11214 if (!filterSpec) { 11215 return srcEntry; 11216 } 11217 const preparedEntry = this.canvasFactory.create(w, h); 11218 const pCtx = preparedEntry.context; 11219 pCtx.filter = filterSpec.url; 11220 const filterApplied = FeatureTest.isCanvasFilterSupported && pCtx.filter !== "none" && pCtx.filter !== ""; 11221 pCtx.drawImage(srcEntry.canvas, 0, 0); 11222 if (FeatureTest.isCanvasFilterSupported) { 11223 pCtx.filter = "none"; 11224 } 11225 if (!filterApplied) { 11226 const img = pCtx.getImageData(0, 0, w, h); 11227 const { 11228 data 11229 } = img; 11230 const { 11231 transferMap 11232 } = filterSpec; 11233 if (filterSpec.subtype === "Luminosity") { 11234 for (let i = 0, ii = data.length; i < ii; i += 4) { 11235 const a = 0.3 * data[i] + 0.59 * data[i + 1] + 0.11 * data[i + 2] + 0.5 | 0; 11236 data[i] = data[i + 1] = data[i + 2] = 0; 11237 data[i + 3] = transferMap?.[a] ?? a; 11238 } 11239 } else { 11240 for (let i = 3, ii = data.length; i < ii; i += 4) { 11241 data[i] = transferMap[data[i]]; 11242 } 11243 } 11244 pCtx.putImageData(img, 0, 0); 11245 } 11246 this.canvasFactory.destroy(srcEntry); 11247 return preparedEntry; 11248 } 11249 beginSMaskMode(opIdx) { 11250 if (this.inSMaskMode) { 11251 throw new Error("beginSMaskMode called while already in smask mode"); 11252 } 11253 const { 11254 width: drawnWidth, 11255 height: drawnHeight 11256 } = this.ctx.canvas; 11257 const scratchCanvas = this.canvasFactory.create(drawnWidth, drawnHeight); 11258 this.smaskScratchCanvas = scratchCanvas; 11259 this.suspendedCtx = this.ctx; 11260 const ctx = this.ctx = scratchCanvas.context; 11261 ctx.setTransform(this.suspendedCtx.getTransform()); 11262 copyCtxState(this.suspendedCtx, ctx); 11263 mirrorContextOperations(ctx, this.suspendedCtx); 11264 this._ensurePreparedSMask(this.current.activeSMask); 11265 this.setGState(opIdx, [["BM", "source-over"]]); 11266 } 11267 endSMaskMode() { 11268 if (!this.inSMaskMode) { 11269 throw new Error("endSMaskMode called while not in smask mode"); 11270 } 11271 this.ctx._removeMirroring(); 11272 copyCtxState(this.ctx, this.suspendedCtx); 11273 this.ctx = this.suspendedCtx; 11274 this.suspendedCtx = null; 11275 this.canvasFactory.destroy(this.smaskScratchCanvas); 11276 this.smaskScratchCanvas = null; 11277 this._clearPreparedSMask(); 11278 } 11279 #createKnockoutMaskCanvas(sourceCanvas, reuseEntry = null, alpha = 1) { 11280 const { 11281 width, 11282 height 11283 } = sourceCanvas; 11284 const maskEntry = reuseEntry ?? this.canvasFactory.create(width, height); 11285 const maskCtx = maskEntry.context; 11286 alpha = Math.round(alpha * 255) / 255; 11287 const needsAlphaScaling = alpha < 1; 11288 if (needsAlphaScaling && this.#knockoutFilterCache === undefined) { 11289 this.#knockoutFilterCache = FeatureTest.isCanvasFilterSupported ? new Map() : "none"; 11290 } 11291 let knockoutFilter = "none"; 11292 if (needsAlphaScaling && this.#knockoutFilterCache instanceof Map) { 11293 knockoutFilter = this.#knockoutFilterCache.getOrInsertComputed(alpha, () => this.filterFactory.addKnockoutFilter(alpha)); 11294 } 11295 if (!needsAlphaScaling || knockoutFilter !== "none") { 11296 if (reuseEntry) { 11297 maskCtx.save(); 11298 maskCtx.setTransform(1, 0, 0, 1, 0, 0); 11299 maskCtx.clearRect(0, 0, width, height); 11300 maskCtx.restore(); 11301 } 11302 maskCtx.filter = knockoutFilter; 11303 maskCtx.drawImage(sourceCanvas, 0, 0); 11304 maskCtx.filter = "none"; 11305 return maskEntry; 11306 } 11307 const sourceData = sourceCanvas.getContext("2d", { 11308 willReadFrequently: true 11309 }).getImageData(0, 0, width, height); 11310 const maskData = maskCtx.createImageData(width, height); 11311 const sourcePixels = sourceData.data, 11312 maskPixels = maskData.data; 11313 const alphaScale = alpha > 0 ? 1 / alpha : 1e6; 11314 for (let i = 3, ii = sourcePixels.length; i < ii; i += 4) { 11315 maskPixels[i] = Math.min(Math.round(sourcePixels[i] * alphaScale), 255); 11316 } 11317 maskCtx.putImageData(maskData, 0, 0); 11318 return maskEntry; 11319 } 11320 #getOrCreatePooledEntry(meta, key, width, height) { 11321 let entry = meta?.[key] ?? null; 11322 if (entry && (entry.canvas.width !== width || entry.canvas.height !== height)) { 11323 this.canvasFactory.destroy(entry); 11324 entry = null; 11325 } 11326 if (!entry) { 11327 entry = this.canvasFactory.create(width, height); 11328 if (meta) { 11329 meta[key] = entry; 11330 } 11331 return entry; 11332 } 11333 const ctx = entry.context; 11334 ctx.save(); 11335 ctx.setTransform(1, 0, 0, 1, 0, 0); 11336 ctx.clearRect(0, 0, width, height); 11337 ctx.restore(); 11338 return entry; 11339 } 11340 #compositeKnockoutSurface(destCtx, surfaceCanvas, options = {}) { 11341 const { 11342 backdropCanvas = null, 11343 destTransform = [1, 0, 0, 1, 0, 0], 11344 backdropOffset = [0, 0], 11345 reuseMaskEntry = null, 11346 poolMeta = null, 11347 sourceAlpha = 1, 11348 sourceFilter = "none", 11349 knockoutAlpha = 1 11350 } = options; 11351 const { 11352 width, 11353 height 11354 } = surfaceCanvas; 11355 const knockoutMaskEntry = this.#createKnockoutMaskCanvas(surfaceCanvas, reuseMaskEntry, knockoutAlpha); 11356 const sourceCompositeOperation = destCtx.globalCompositeOperation; 11357 destCtx.save(); 11358 destCtx.setTransform(...destTransform); 11359 destCtx.globalAlpha = 1; 11360 if (FeatureTest.isCanvasFilterSupported) { 11361 destCtx.filter = "none"; 11362 } 11363 destCtx.globalCompositeOperation = "destination-out"; 11364 destCtx.drawImage(knockoutMaskEntry.canvas, 0, 0); 11365 if (backdropCanvas) { 11366 const [bx, by] = backdropOffset; 11367 const backdropEntry = this.#getOrCreatePooledEntry(poolMeta, "knockoutBackdropEntry", width, height); 11368 const backdropCtx = backdropEntry.context; 11369 backdropCtx.drawImage(backdropCanvas, bx, by, width, height, 0, 0, width, height); 11370 backdropCtx.globalCompositeOperation = "destination-in"; 11371 backdropCtx.drawImage(knockoutMaskEntry.canvas, 0, 0); 11372 backdropCtx.globalCompositeOperation = "source-over"; 11373 destCtx.globalCompositeOperation = "destination-over"; 11374 destCtx.drawImage(backdropEntry.canvas, 0, 0); 11375 if (!poolMeta) { 11376 this.canvasFactory.destroy(backdropEntry); 11377 } 11378 } 11379 destCtx.globalCompositeOperation = sourceCompositeOperation; 11380 destCtx.globalAlpha = sourceAlpha; 11381 if (FeatureTest.isCanvasFilterSupported) { 11382 destCtx.filter = sourceFilter ?? "none"; 11383 } 11384 destCtx.drawImage(surfaceCanvas, 0, 0); 11385 destCtx.restore(); 11386 if (!reuseMaskEntry) { 11387 this.canvasFactory.destroy(knockoutMaskEntry); 11388 } 11389 } 11390 #beginKnockoutElement(alpha = 1) { 11391 if (this.#knockoutGroupLevel === 0 || this.#knockoutElementDepth > 0 || !this.contentVisible) { 11392 return false; 11393 } 11394 this.#knockoutElementDepth++; 11395 this.#knockoutElementAlpha = alpha; 11396 const groupMeta = this.#groupStackMeta.at(-1); 11397 const { 11398 canvas 11399 } = this.ctx; 11400 const tempEntry = this.#getOrCreatePooledEntry(groupMeta, "knockoutTempEntry", canvas.width, canvas.height); 11401 this.#knockoutTempCanvasEntry = tempEntry; 11402 const tempCtx = tempEntry.context; 11403 tempCtx.save(); 11404 tempCtx.setTransform(this.ctx.getTransform()); 11405 copyCtxState(this.ctx, tempCtx); 11406 this.#knockoutSavedGCO = tempCtx.globalCompositeOperation; 11407 tempCtx.globalCompositeOperation = "source-over"; 11408 mirrorContextOperations(tempCtx, this.ctx); 11409 this.#knockoutElementGroupMeta = groupMeta; 11410 this.#knockoutSavedCtx = this.ctx; 11411 this.#knockoutSavedSMaskCtx = this.suspendedCtx; 11412 this.ctx = tempCtx; 11413 if (this.inSMaskMode) { 11414 this.suspendedCtx = tempCtx; 11415 } 11416 return true; 11417 } 11418 #endKnockoutElement(started) { 11419 if (!started) { 11420 return; 11421 } 11422 const tempEntry = this.#knockoutTempCanvasEntry; 11423 const savedCtx = this.#knockoutSavedCtx; 11424 const savedSMaskCtx = this.#knockoutSavedSMaskCtx; 11425 const tempCtx = tempEntry.context; 11426 this.#knockoutTempCanvasEntry = null; 11427 this.#knockoutSavedCtx = null; 11428 this.#knockoutSavedSMaskCtx = null; 11429 if (this.inSMaskMode && this.suspendedCtx === tempCtx && this.ctx !== tempCtx) { 11430 this.endSMaskMode(); 11431 } 11432 if (this.inSMaskMode) { 11433 this.suspendedCtx = savedSMaskCtx; 11434 } 11435 this.ctx._removeMirroring(); 11436 this.ctx.globalCompositeOperation = this.#knockoutSavedGCO; 11437 this.#knockoutSavedGCO = null; 11438 copyCtxState(this.ctx, savedCtx); 11439 this.ctx = savedCtx; 11440 const groupMeta = this.#knockoutElementGroupMeta; 11441 this.#knockoutElementGroupMeta = null; 11442 const knockoutAlpha = this.#knockoutElementAlpha; 11443 this.#knockoutElementAlpha = 1; 11444 try { 11445 this.#compositeKnockoutSurface(savedSMaskCtx ?? savedCtx, tempEntry.canvas, { 11446 backdropCanvas: groupMeta?.backdropCtx?.canvas ?? null, 11447 backdropOffset: groupMeta?.backdropCtx ? [groupMeta.offsetX, groupMeta.offsetY] : [0, 0], 11448 reuseMaskEntry: groupMeta?.knockoutMaskEntry ?? null, 11449 poolMeta: groupMeta, 11450 knockoutAlpha 11451 }); 11452 } finally { 11453 tempCtx.restore(); 11454 this.#knockoutElementDepth--; 11455 if (!groupMeta) { 11456 this.canvasFactory.destroy(tempEntry); 11457 } 11458 } 11459 } 11460 compose(dirtyBox) { 11461 if (!this.current.activeSMask) { 11462 return; 11463 } 11464 dirtyBox = dirtyBox ? [Math.floor(dirtyBox[0]), Math.floor(dirtyBox[1]), Math.ceil(dirtyBox[2]), Math.ceil(dirtyBox[3])] : [0, 0, this.ctx.canvas.width, this.ctx.canvas.height]; 11465 const smask = this.current.activeSMask; 11466 const suspendedCtx = this.suspendedCtx; 11467 const applySMaskInPlace = this.#knockoutElementDepth > 0 && suspendedCtx === this.ctx; 11468 this.composeSMask(applySMaskInPlace ? null : suspendedCtx, smask, this.ctx, dirtyBox); 11469 if (applySMaskInPlace) { 11470 return; 11471 } 11472 this.ctx.save(); 11473 this.ctx.setTransform(1, 0, 0, 1, 0, 0); 11474 this.ctx.clearRect(0, 0, this.ctx.canvas.width, this.ctx.canvas.height); 11475 this.ctx.restore(); 11476 } 11477 composeSMask(ctx, smask, layerCtx, layerBox) { 11478 const layerOffsetX = layerBox[0]; 11479 const layerOffsetY = layerBox[1]; 11480 const layerWidth = layerBox[2] - layerOffsetX; 11481 const layerHeight = layerBox[3] - layerOffsetY; 11482 if (layerWidth === 0 || layerHeight === 0) { 11483 return; 11484 } 11485 const preparedEntry = this.smaskPreparedEntry; 11486 if (preparedEntry) { 11487 let clipX = layerOffsetX; 11488 let clipY = layerOffsetY; 11489 let clipW = layerWidth; 11490 let clipH = layerHeight; 11491 const oobAlpha = this.smaskPreparedOOBAlpha; 11492 const hasOOBAlpha = oobAlpha !== null; 11493 if (hasOOBAlpha) { 11494 clipX = Math.max(layerOffsetX, smask.offsetX); 11495 clipY = Math.max(layerOffsetY, smask.offsetY); 11496 const x1 = Math.min(layerOffsetX + layerWidth, smask.offsetX + smask.canvas.width); 11497 const y1 = Math.min(layerOffsetY + layerHeight, smask.offsetY + smask.canvas.height); 11498 clipW = x1 - clipX; 11499 clipH = y1 - clipY; 11500 } 11501 if (clipW > 0 && clipH > 0) { 11502 const srcX = clipX - this.smaskPreparedOffsetX; 11503 const srcY = clipY - this.smaskPreparedOffsetY; 11504 layerCtx.save(); 11505 layerCtx.globalAlpha = 1; 11506 layerCtx.setTransform(1, 0, 0, 1, 0, 0); 11507 const clip = new Path2D(); 11508 clip.rect(clipX, clipY, clipW, clipH); 11509 layerCtx.clip(clip); 11510 layerCtx.globalCompositeOperation = "destination-in"; 11511 layerCtx.drawImage(preparedEntry.canvas, srcX, srcY, clipW, clipH, clipX, clipY, clipW, clipH); 11512 layerCtx.restore(); 11513 } 11514 if (hasOOBAlpha && oobAlpha < 255) { 11515 this._applySMaskOOBAlpha(layerCtx, layerOffsetX, layerOffsetY, layerWidth, layerHeight, clipX, clipY, clipX + clipW, clipY + clipH, oobAlpha); 11516 } 11517 } else { 11518 this.genericComposeSMask(smask, layerCtx, layerWidth, layerHeight, layerOffsetX, layerOffsetY); 11519 } 11520 if (!ctx) { 11521 return; 11522 } 11523 ctx.save(); 11524 ctx.globalAlpha = 1; 11525 ctx.globalCompositeOperation = smask.blendMode || "source-over"; 11526 ctx.setTransform(1, 0, 0, 1, 0, 0); 11527 ctx.drawImage(layerCtx.canvas, layerOffsetX, layerOffsetY, layerWidth, layerHeight, layerOffsetX, layerOffsetY, layerWidth, layerHeight); 11528 ctx.restore(); 11529 } 11530 _applySMaskOOBAlpha(layerCtx, layerOffsetX, layerOffsetY, layerWidth, layerHeight, maskX0, maskY0, maskX1, maskY1, alpha) { 11531 const hasInnerCutout = maskX0 < maskX1 && maskY0 < maskY1; 11532 if (hasInnerCutout && maskX0 === layerOffsetX && maskY0 === layerOffsetY && maskX1 === layerOffsetX + layerWidth && maskY1 === layerOffsetY + layerHeight) { 11533 return; 11534 } 11535 const path = new Path2D(); 11536 path.rect(layerOffsetX, layerOffsetY, layerWidth, layerHeight); 11537 if (hasInnerCutout) { 11538 path.rect(maskX0, maskY0, maskX1 - maskX0, maskY1 - maskY0); 11539 } 11540 layerCtx.save(); 11541 layerCtx.globalAlpha = alpha / 255; 11542 layerCtx.setTransform(1, 0, 0, 1, 0, 0); 11543 layerCtx.clip(path, "evenodd"); 11544 layerCtx.globalCompositeOperation = "destination-in"; 11545 layerCtx.fillStyle = "#000000"; 11546 layerCtx.fillRect(layerOffsetX, layerOffsetY, layerWidth, layerHeight); 11547 layerCtx.restore(); 11548 } 11549 genericComposeSMask(smask, layerCtx, width, height, layerOffsetX, layerOffsetY) { 11550 const { 11551 context: maskCtx, 11552 offsetX: maskOffsetX, 11553 offsetY: maskOffsetY 11554 } = smask; 11555 layerCtx.save(); 11556 layerCtx.globalAlpha = 1; 11557 layerCtx.setTransform(1, 0, 0, 1, 0, 0); 11558 const clip = new Path2D(); 11559 clip.rect(layerOffsetX, layerOffsetY, width, height); 11560 layerCtx.clip(clip); 11561 layerCtx.globalCompositeOperation = "destination-in"; 11562 layerCtx.drawImage(maskCtx.canvas, layerOffsetX - maskOffsetX, layerOffsetY - maskOffsetY, width, height, layerOffsetX, layerOffsetY, width, height); 11563 layerCtx.restore(); 11564 } 11565 save(opIdx) { 11566 if (this.inSMaskMode) { 11567 copyCtxState(this.ctx, this.suspendedCtx); 11568 } 11569 this.ctx.save(); 11570 const old = this.current; 11571 this.stateStack.push(old); 11572 this.current = old.clone(); 11573 this.dependencyTracker?.save(opIdx); 11574 } 11575 restore(opIdx) { 11576 this.dependencyTracker?.restore(opIdx); 11577 if (this.stateStack.length === 0) { 11578 if (this.inSMaskMode) { 11579 this.endSMaskMode(); 11580 } 11581 return; 11582 } 11583 this.current = this.stateStack.pop(); 11584 this.ctx.restore(); 11585 if (this.inSMaskMode) { 11586 copyCtxState(this.suspendedCtx, this.ctx); 11587 this.ctx.setTransform(this.suspendedCtx.getTransform()); 11588 } 11589 this.checkSMaskState(opIdx); 11590 this.pendingClip = null; 11591 this._cachedScaleForStroking[0] = -1; 11592 this._cachedGetSinglePixelWidth = null; 11593 } 11594 transform(opIdx, a, b, c, d, e, f) { 11595 this.dependencyTracker?.recordIncrementalData("transform", opIdx); 11596 this.ctx.transform(a, b, c, d, e, f); 11597 this._cachedScaleForStroking[0] = -1; 11598 this._cachedGetSinglePixelWidth = null; 11599 } 11600 constructPath(opIdx, op, data, minMax) { 11601 let [path] = data; 11602 if (!minMax) { 11603 path ||= data[0] = new Path2D(); 11604 if (op !== OPS.stroke && op !== OPS.closeStroke) { 11605 this.current.tilingPatternDims = null; 11606 } 11607 this[op](opIdx, path); 11608 return; 11609 } 11610 if (this.dependencyTracker !== null) { 11611 const outerExtraSize = op === OPS.stroke ? this.current.lineWidth / 2 : 0; 11612 this.dependencyTracker.resetBBox(opIdx).recordBBox(opIdx, this.ctx, minMax[0] - outerExtraSize, minMax[2] + outerExtraSize, minMax[1] - outerExtraSize, minMax[3] + outerExtraSize).recordDependencies(opIdx, ["transform"]); 11613 } 11614 if (!(path instanceof Path2D)) { 11615 path = data[0] = makePathFromDrawOPS(path); 11616 } 11617 Util.axialAlignedBoundingBox(minMax, getCurrentTransform(this.ctx), this.current.minMax); 11618 const tilingDims = this.current.tilingPatternDims; 11619 if (tilingDims && op !== OPS.stroke && op !== OPS.closeStroke && this.current.fillColor instanceof TilingPattern) { 11620 const clippedBBox = Util.intersect(this.current.clipBox, this.current.minMax); 11621 if (!clippedBBox) { 11622 this.current.tilingPatternDims = null; 11623 } else { 11624 this.current.fillColor.updatePatternDims(clippedBBox, tilingDims); 11625 } 11626 } 11627 this[op](opIdx, path); 11628 this._pathStartIdx = opIdx; 11629 } 11630 closePath(opIdx) { 11631 this.ctx.closePath(); 11632 } 11633 stroke(opIdx, path, consumePath = true) { 11634 const started = consumePath && this.#beginKnockoutElement(this.current.strokeAlpha); 11635 const ctx = this.ctx; 11636 const strokeColor = this.current.strokeColor; 11637 ctx.globalAlpha = this.current.strokeAlpha; 11638 if (this.contentVisible) { 11639 if (typeof strokeColor === "object" && strokeColor?.getPattern) { 11640 const baseTransform = strokeColor.isModifyingCurrentTransform() ? ctx.getTransform() : null; 11641 ctx.save(); 11642 ctx.strokeStyle = strokeColor.getPattern(ctx, this, getCurrentTransformInverse(ctx), PathType.STROKE, opIdx); 11643 if (baseTransform) { 11644 const newPath = new Path2D(); 11645 newPath.addPath(path, ctx.getTransform().invertSelf().multiplySelf(baseTransform)); 11646 path = newPath; 11647 } 11648 this.rescaleAndStroke(path, false); 11649 ctx.restore(); 11650 } else { 11651 this.rescaleAndStroke(path, true); 11652 } 11653 } 11654 this.dependencyTracker?.recordDependencies(opIdx, Dependencies.stroke); 11655 if (consumePath) { 11656 this.consumePath(opIdx, path, this.current.getClippedPathBoundingBox(PathType.STROKE, getCurrentTransform(this.ctx))); 11657 } 11658 ctx.globalAlpha = this.current.fillAlpha; 11659 this.#endKnockoutElement(started); 11660 } 11661 closeStroke(opIdx, path) { 11662 this.stroke(opIdx, path); 11663 } 11664 fill(opIdx, path, consumePath = true) { 11665 const started = consumePath && this.#beginKnockoutElement(this.current.fillAlpha); 11666 const ctx = this.ctx; 11667 const fillColor = this.current.fillColor; 11668 const isPatternFill = this.current.patternFill; 11669 let needRestore = false; 11670 const intersect = this.current.getClippedPathBoundingBox(); 11671 this.dependencyTracker?.recordDependencies(opIdx, Dependencies.fill); 11672 if (isPatternFill) { 11673 const dims = this.current.tilingPatternDims; 11674 const tileIdx = dims && fillColor.canSkipPatternCanvas(dims); 11675 if (tileIdx) { 11676 fillColor.drawPattern(this, path, this.pendingEOFill, tileIdx, opIdx); 11677 this.pendingEOFill = false; 11678 if (consumePath) { 11679 this.consumePath(opIdx, path, intersect); 11680 } 11681 this.current.tilingPatternDims = null; 11682 this.#endKnockoutElement(started); 11683 return; 11684 } 11685 const baseTransform = fillColor.isModifyingCurrentTransform() ? ctx.getTransform() : null; 11686 this.dependencyTracker?.save(opIdx); 11687 ctx.save(); 11688 ctx.fillStyle = fillColor.getPattern(ctx, this, getCurrentTransformInverse(ctx), PathType.FILL, opIdx); 11689 if (baseTransform) { 11690 const newPath = new Path2D(); 11691 newPath.addPath(path, ctx.getTransform().invertSelf().multiplySelf(baseTransform)); 11692 path = newPath; 11693 } 11694 needRestore = true; 11695 } 11696 if (this.contentVisible && intersect !== null) { 11697 if (this.pendingEOFill) { 11698 ctx.fill(path, "evenodd"); 11699 this.pendingEOFill = false; 11700 } else { 11701 ctx.fill(path); 11702 } 11703 } 11704 if (needRestore) { 11705 ctx.restore(); 11706 this.dependencyTracker?.restore(opIdx); 11707 } 11708 if (consumePath) { 11709 this.consumePath(opIdx, path, intersect); 11710 } 11711 this.#endKnockoutElement(started); 11712 } 11713 eoFill(opIdx, path) { 11714 this.pendingEOFill = true; 11715 this.fill(opIdx, path); 11716 } 11717 fillStroke(opIdx, path) { 11718 const started = this.#beginKnockoutElement(Math.min(this.current.fillAlpha, this.current.strokeAlpha)); 11719 this.fill(opIdx, path, false); 11720 this.stroke(opIdx, path, false); 11721 this.consumePath(opIdx, path); 11722 this.#endKnockoutElement(started); 11723 } 11724 eoFillStroke(opIdx, path) { 11725 this.pendingEOFill = true; 11726 this.fillStroke(opIdx, path); 11727 } 11728 closeFillStroke(opIdx, path) { 11729 this.fillStroke(opIdx, path); 11730 } 11731 closeEOFillStroke(opIdx, path) { 11732 this.pendingEOFill = true; 11733 this.fillStroke(opIdx, path); 11734 } 11735 endPath(opIdx, path) { 11736 this.consumePath(opIdx, path); 11737 } 11738 rawFillPath(opIdx, path) { 11739 const started = this.#beginKnockoutElement(this.current.fillAlpha); 11740 this.ctx.fill(path); 11741 this.dependencyTracker?.recordDependencies(opIdx, Dependencies.rawFillPath).recordOperation(opIdx); 11742 this.#endKnockoutElement(started); 11743 } 11744 clip(opIdx) { 11745 this.dependencyTracker?.recordFutureForcedDependency("clipMode", opIdx); 11746 this.pendingClip = NORMAL_CLIP; 11747 } 11748 eoClip(opIdx) { 11749 this.dependencyTracker?.recordFutureForcedDependency("clipMode", opIdx); 11750 this.pendingClip = EO_CLIP; 11751 } 11752 beginText(opIdx) { 11753 this.current.textMatrix = null; 11754 this.current.textMatrixScale = 1; 11755 this.current.x = this.current.lineX = 0; 11756 this.current.y = this.current.lineY = 0; 11757 this.dependencyTracker?.recordOpenMarker(opIdx).resetIncrementalData("sameLineText").resetIncrementalData("moveText", opIdx); 11758 } 11759 endText(opIdx) { 11760 const paths = this.pendingTextPaths; 11761 const ctx = this.ctx; 11762 if (this.dependencyTracker) { 11763 const { 11764 dependencyTracker 11765 } = this; 11766 if (paths !== undefined) { 11767 dependencyTracker.recordFutureForcedDependency("textClip", dependencyTracker.getOpenMarker()).recordFutureForcedDependency("textClip", opIdx); 11768 } 11769 dependencyTracker.recordCloseMarker(opIdx); 11770 } 11771 if (paths !== undefined) { 11772 const newPath = new Path2D(); 11773 const invTransf = ctx.getTransform().invertSelf(); 11774 for (const { 11775 transform, 11776 x, 11777 y, 11778 fontSize, 11779 path 11780 } of paths) { 11781 if (!path) { 11782 continue; 11783 } 11784 newPath.addPath(path, new DOMMatrix(transform).preMultiplySelf(invTransf).translate(x, y).scale(fontSize, -fontSize)); 11785 } 11786 ctx.clip(newPath); 11787 } 11788 delete this.pendingTextPaths; 11789 } 11790 setCharSpacing(opIdx, spacing) { 11791 this.dependencyTracker?.recordSimpleData("charSpacing", opIdx); 11792 this.current.charSpacing = spacing; 11793 } 11794 setWordSpacing(opIdx, spacing) { 11795 this.dependencyTracker?.recordSimpleData("wordSpacing", opIdx); 11796 this.current.wordSpacing = spacing; 11797 } 11798 setHScale(opIdx, scale) { 11799 this.dependencyTracker?.recordSimpleData("hScale", opIdx); 11800 this.current.textHScale = scale / 100; 11801 } 11802 setLeading(opIdx, leading) { 11803 this.dependencyTracker?.recordSimpleData("leading", opIdx); 11804 this.current.leading = -leading; 11805 } 11806 setFont(opIdx, fontRefName, size) { 11807 this.dependencyTracker?.recordSimpleData("font", opIdx).recordSimpleDataFromNamed("fontObj", fontRefName, opIdx); 11808 const fontObj = this.commonObjs.get(fontRefName); 11809 const current = this.current; 11810 if (!fontObj) { 11811 throw new Error(`Can't find font for ${fontRefName}`); 11812 } 11813 current.fontMatrix = fontObj.fontMatrix || FONT_IDENTITY_MATRIX; 11814 if (current.fontMatrix[0] === 0 || current.fontMatrix[3] === 0) { 11815 warn("Invalid font matrix for font " + fontRefName); 11816 } 11817 if (size < 0) { 11818 size = -size; 11819 current.fontDirection = -1; 11820 } else { 11821 current.fontDirection = 1; 11822 } 11823 this.current.font = fontObj; 11824 this.current.fontSize = size; 11825 if (fontObj.isType3Font) { 11826 return; 11827 } 11828 const name = fontObj.loadedName || "sans-serif"; 11829 const typeface = fontObj.systemFontInfo?.css || `"${name}", ${fontObj.fallbackName}`; 11830 let bold = "normal"; 11831 if (fontObj.black) { 11832 bold = "900"; 11833 } else if (fontObj.bold) { 11834 bold = "bold"; 11835 } 11836 const italic = fontObj.italic ? "italic" : "normal"; 11837 const browserFontSize = MathClamp(size, MIN_FONT_SIZE, MAX_FONT_SIZE); 11838 this.current.fontSizeScale = size / browserFontSize; 11839 this.ctx.font = `${italic} ${bold} ${browserFontSize}px ${typeface}`; 11840 } 11841 setTextRenderingMode(opIdx, mode) { 11842 this.dependencyTracker?.recordSimpleData("textRenderingMode", opIdx); 11843 this.current.textRenderingMode = mode; 11844 } 11845 setTextRise(opIdx, rise) { 11846 this.dependencyTracker?.recordSimpleData("textRise", opIdx); 11847 this.current.textRise = rise; 11848 } 11849 moveText(opIdx, x, y) { 11850 this.dependencyTracker?.resetIncrementalData("sameLineText").recordIncrementalData("moveText", opIdx); 11851 this.current.x = this.current.lineX += x; 11852 this.current.y = this.current.lineY += y; 11853 } 11854 setLeadingMoveText(opIdx, x, y) { 11855 this.setLeading(opIdx, -y); 11856 this.moveText(opIdx, x, y); 11857 } 11858 setTextMatrix(opIdx, matrix) { 11859 this.dependencyTracker?.resetIncrementalData("sameLineText").recordSimpleData("textMatrix", opIdx); 11860 const { 11861 current 11862 } = this; 11863 current.textMatrix = matrix; 11864 current.textMatrixScale = Math.hypot(matrix[0], matrix[1]); 11865 current.x = current.lineX = 0; 11866 current.y = current.lineY = 0; 11867 } 11868 nextLine(opIdx) { 11869 this.moveText(opIdx, 0, this.current.leading); 11870 this.dependencyTracker?.recordIncrementalData("moveText", this.dependencyTracker.getSimpleIndex("leading") ?? opIdx); 11871 } 11872 #getScaledPath(path, currentTransform, transform) { 11873 const newPath = new Path2D(); 11874 newPath.addPath(path, new DOMMatrix(transform).invertSelf().multiplySelf(currentTransform)); 11875 return newPath; 11876 } 11877 paintChar(opIdx, character, x, y, patternFillTransform, patternStrokeTransform) { 11878 const ctx = this.ctx; 11879 const current = this.current; 11880 const font = current.font; 11881 const textRenderingMode = current.textRenderingMode; 11882 const fontSize = current.fontSize / current.fontSizeScale; 11883 const fillStrokeMode = textRenderingMode & TextRenderingMode.FILL_STROKE_MASK; 11884 const isAddToPathSet = !!(textRenderingMode & TextRenderingMode.ADD_TO_PATH_FLAG); 11885 const patternFill = current.patternFill && !font.missingFile; 11886 const patternStroke = current.patternStroke && !font.missingFile; 11887 let path; 11888 if ((font.disableFontFace || isAddToPathSet || patternFill || patternStroke) && !font.missingFile) { 11889 path = font.getPathGenerator(this.commonObjs, character); 11890 } 11891 if (path && (font.disableFontFace || patternFill || patternStroke)) { 11892 ctx.save(); 11893 ctx.translate(x, y); 11894 ctx.scale(fontSize, -fontSize); 11895 this.dependencyTracker?.recordCharacterBBox(opIdx, ctx, font); 11896 let currentTransform; 11897 if (fillStrokeMode === TextRenderingMode.FILL || fillStrokeMode === TextRenderingMode.FILL_STROKE) { 11898 if (patternFillTransform) { 11899 currentTransform = ctx.getTransform(); 11900 ctx.setTransform(...patternFillTransform); 11901 const scaledPath = this.#getScaledPath(path, currentTransform, patternFillTransform); 11902 ctx.fill(scaledPath); 11903 } else { 11904 ctx.fill(path); 11905 } 11906 } 11907 if (fillStrokeMode === TextRenderingMode.STROKE || fillStrokeMode === TextRenderingMode.FILL_STROKE) { 11908 if (patternStrokeTransform) { 11909 currentTransform ||= ctx.getTransform(); 11910 ctx.setTransform(...patternStrokeTransform); 11911 const { 11912 a, 11913 b, 11914 c, 11915 d 11916 } = currentTransform; 11917 const invPatternTransform = Util.inverseTransform(patternStrokeTransform); 11918 const transf = Util.transform([a, b, c, d, 0, 0], invPatternTransform); 11919 Util.singularValueDecompose2dScale(transf, XY); 11920 ctx.lineWidth *= Math.max(XY[0], XY[1]) / fontSize; 11921 ctx.stroke(this.#getScaledPath(path, currentTransform, patternStrokeTransform)); 11922 } else { 11923 ctx.lineWidth /= fontSize; 11924 ctx.stroke(path); 11925 } 11926 } 11927 ctx.restore(); 11928 } else { 11929 if (fillStrokeMode === TextRenderingMode.FILL || fillStrokeMode === TextRenderingMode.FILL_STROKE) { 11930 ctx.fillText(character, x, y); 11931 this.dependencyTracker?.recordCharacterBBox(opIdx, ctx, font, fontSize, x, y, () => ctx.measureText(character)); 11932 } 11933 if (fillStrokeMode === TextRenderingMode.STROKE || fillStrokeMode === TextRenderingMode.FILL_STROKE) { 11934 if (this.dependencyTracker) { 11935 this.dependencyTracker?.recordCharacterBBox(opIdx, ctx, font, fontSize, x, y, () => ctx.measureText(character)).recordDependencies(opIdx, Dependencies.stroke); 11936 } 11937 ctx.strokeText(character, x, y); 11938 } 11939 } 11940 if (isAddToPathSet) { 11941 const paths = this.pendingTextPaths ||= []; 11942 paths.push({ 11943 transform: getCurrentTransform(ctx), 11944 x, 11945 y, 11946 fontSize, 11947 path 11948 }); 11949 this.dependencyTracker?.recordCharacterBBox(opIdx, ctx, font, fontSize, x, y); 11950 } 11951 } 11952 get isFontSubpixelAAEnabled() { 11953 const tmpCanvas = this.canvasFactory.create(10, 10); 11954 const ctx = tmpCanvas.context; 11955 ctx.scale(1.5, 1); 11956 ctx.fillText("I", 0, 10); 11957 const data = ctx.getImageData(0, 0, 10, 10).data; 11958 this.canvasFactory.destroy(tmpCanvas); 11959 let enabled = false; 11960 for (let i = 3; i < data.length; i += 4) { 11961 if (data[i] > 0 && data[i] < 255) { 11962 enabled = true; 11963 break; 11964 } 11965 } 11966 return shadow(this, "isFontSubpixelAAEnabled", enabled); 11967 } 11968 showText(opIdx, glyphs) { 11969 if (this.dependencyTracker) { 11970 this.dependencyTracker.recordDependencies(opIdx, Dependencies.showText).resetBBox(opIdx); 11971 if (this.current.textRenderingMode & TextRenderingMode.ADD_TO_PATH_FLAG) { 11972 this.dependencyTracker.recordFutureForcedDependency("textClip", opIdx).inheritPendingDependenciesAsFutureForcedDependencies(); 11973 } 11974 } 11975 const current = this.current; 11976 const font = current.font; 11977 if (font.isType3Font) { 11978 const started = this.#beginKnockoutElement(current.fillAlpha); 11979 this.showType3Text(opIdx, glyphs); 11980 this.dependencyTracker?.recordShowTextOperation(opIdx); 11981 this.#endKnockoutElement(started); 11982 return; 11983 } 11984 const fontSize = current.fontSize; 11985 if (fontSize === 0) { 11986 this.dependencyTracker?.recordOperation(opIdx); 11987 return; 11988 } 11989 const started = this.#beginKnockoutElement(current.fillAlpha); 11990 const ctx = this.ctx; 11991 const fontSizeScale = current.fontSizeScale; 11992 const charSpacing = current.charSpacing; 11993 const wordSpacing = current.wordSpacing; 11994 const fontDirection = current.fontDirection; 11995 const textHScale = current.textHScale * fontDirection; 11996 const glyphsLength = glyphs.length; 11997 const vertical = font.vertical; 11998 const spacingDir = vertical ? 1 : -1; 11999 const defaultVMetrics = font.defaultVMetrics; 12000 const widthAdvanceScale = fontSize * current.fontMatrix[0]; 12001 const simpleFillText = current.textRenderingMode === TextRenderingMode.FILL && !font.disableFontFace && !current.patternFill; 12002 ctx.save(); 12003 if (current.textMatrix) { 12004 ctx.transform(...current.textMatrix); 12005 } 12006 ctx.translate(current.x, current.y + current.textRise); 12007 if (fontDirection > 0) { 12008 ctx.scale(textHScale, -1); 12009 } else { 12010 ctx.scale(textHScale, 1); 12011 } 12012 let patternFillTransform, patternStrokeTransform; 12013 const fillStrokeMode = current.textRenderingMode & TextRenderingMode.FILL_STROKE_MASK; 12014 const needsFill = fillStrokeMode === TextRenderingMode.FILL || fillStrokeMode === TextRenderingMode.FILL_STROKE; 12015 const needsStroke = fillStrokeMode === TextRenderingMode.STROKE || fillStrokeMode === TextRenderingMode.FILL_STROKE; 12016 let lineWidth = current.lineWidth; 12017 const scale = current.textMatrixScale; 12018 if (scale === 0 || lineWidth === 0) { 12019 if (needsStroke) { 12020 lineWidth = this.getSinglePixelWidth(); 12021 } 12022 } else { 12023 lineWidth /= scale; 12024 } 12025 if (fontSizeScale !== 1.0) { 12026 ctx.scale(fontSizeScale, fontSizeScale); 12027 lineWidth /= fontSizeScale; 12028 } 12029 ctx.lineWidth = lineWidth; 12030 if (needsFill && current.patternFill) { 12031 ctx.save(); 12032 const pattern = current.fillColor.getPattern(ctx, this, getCurrentTransformInverse(ctx), PathType.FILL, opIdx); 12033 patternFillTransform = getCurrentTransform(ctx); 12034 ctx.restore(); 12035 ctx.fillStyle = pattern; 12036 } 12037 if (needsStroke && current.patternStroke) { 12038 ctx.save(); 12039 const pattern = current.strokeColor.getPattern(ctx, this, getCurrentTransformInverse(ctx), PathType.STROKE, opIdx); 12040 patternStrokeTransform = getCurrentTransform(ctx); 12041 ctx.restore(); 12042 ctx.strokeStyle = pattern; 12043 } 12044 if (font.isInvalidPDFjsFont) { 12045 const chars = []; 12046 let width = 0; 12047 for (const glyph of glyphs) { 12048 chars.push(glyph.unicode); 12049 width += glyph.width; 12050 } 12051 const joinedChars = chars.join(""); 12052 ctx.fillText(joinedChars, 0, 0); 12053 if (this.dependencyTracker !== null) { 12054 const measure = ctx.measureText(joinedChars); 12055 this.dependencyTracker.recordBBox(opIdx, this.ctx, -measure.actualBoundingBoxLeft, measure.actualBoundingBoxRight, -measure.actualBoundingBoxAscent, measure.actualBoundingBoxDescent).recordShowTextOperation(opIdx); 12056 } 12057 current.x += width * widthAdvanceScale * textHScale; 12058 ctx.restore(); 12059 this.compose(); 12060 this.#endKnockoutElement(started); 12061 return; 12062 } 12063 let x = 0, 12064 i; 12065 for (i = 0; i < glyphsLength; ++i) { 12066 const glyph = glyphs[i]; 12067 if (typeof glyph === "number") { 12068 x += spacingDir * glyph * fontSize / 1000; 12069 continue; 12070 } 12071 let restoreNeeded = false; 12072 const spacing = (glyph.isSpace ? wordSpacing : 0) + charSpacing; 12073 const character = glyph.fontChar; 12074 const accent = glyph.accent; 12075 let scaledX, scaledY; 12076 let width = glyph.width; 12077 if (vertical) { 12078 const vmetric = glyph.vmetric || defaultVMetrics; 12079 const vx = -(glyph.vmetric ? vmetric[1] : width * 0.5) * widthAdvanceScale; 12080 const vy = vmetric[2] * widthAdvanceScale; 12081 width = vmetric ? -vmetric[0] : width; 12082 scaledX = vx / fontSizeScale; 12083 scaledY = (x + vy) / fontSizeScale; 12084 } else { 12085 scaledX = x / fontSizeScale; 12086 scaledY = 0; 12087 } 12088 let measure; 12089 if (font.remeasure && width > 0) { 12090 measure = ctx.measureText(character); 12091 const measuredWidth = measure.width * 1000 / fontSize * fontSizeScale; 12092 if (width < measuredWidth && this.isFontSubpixelAAEnabled) { 12093 const characterScaleX = width / measuredWidth; 12094 restoreNeeded = true; 12095 ctx.save(); 12096 ctx.scale(characterScaleX, 1); 12097 scaledX /= characterScaleX; 12098 } else if (width !== measuredWidth) { 12099 scaledX += (width - measuredWidth) / 2000 * fontSize / fontSizeScale; 12100 } 12101 } 12102 if (this.contentVisible && (glyph.isInFont || font.missingFile)) { 12103 if (simpleFillText && !accent) { 12104 ctx.fillText(character, scaledX, scaledY); 12105 this.dependencyTracker?.recordCharacterBBox(opIdx, ctx, measure ? { 12106 bbox: null 12107 } : font, fontSize / fontSizeScale, scaledX, scaledY, () => measure ?? ctx.measureText(character)); 12108 } else { 12109 this.paintChar(opIdx, character, scaledX, scaledY, patternFillTransform, patternStrokeTransform); 12110 if (accent) { 12111 const scaledAccentX = scaledX + fontSize * accent.offset.x / fontSizeScale; 12112 const scaledAccentY = scaledY - fontSize * accent.offset.y / fontSizeScale; 12113 this.paintChar(opIdx, accent.fontChar, scaledAccentX, scaledAccentY, patternFillTransform, patternStrokeTransform); 12114 } 12115 } 12116 } 12117 const charWidth = vertical ? width * widthAdvanceScale - spacing * fontDirection : width * widthAdvanceScale + spacing * fontDirection; 12118 x += charWidth; 12119 if (restoreNeeded) { 12120 ctx.restore(); 12121 } 12122 } 12123 if (vertical) { 12124 current.y -= x; 12125 } else { 12126 current.x += x * textHScale; 12127 } 12128 ctx.restore(); 12129 this.compose(); 12130 this.dependencyTracker?.recordShowTextOperation(opIdx); 12131 this.#endKnockoutElement(started); 12132 } 12133 showType3Text(opIdx, glyphs) { 12134 const ctx = this.ctx; 12135 const current = this.current; 12136 const font = current.font; 12137 const fontSize = current.fontSize; 12138 const fontDirection = current.fontDirection; 12139 const spacingDir = font.vertical ? 1 : -1; 12140 const charSpacing = current.charSpacing; 12141 const wordSpacing = current.wordSpacing; 12142 const textHScale = current.textHScale * fontDirection; 12143 const fontMatrix = current.fontMatrix || FONT_IDENTITY_MATRIX; 12144 const glyphsLength = glyphs.length; 12145 const isTextInvisible = current.textRenderingMode === TextRenderingMode.INVISIBLE; 12146 let i, glyph, width, spacingLength; 12147 if (isTextInvisible || fontSize === 0) { 12148 return; 12149 } 12150 this._cachedScaleForStroking[0] = -1; 12151 this._cachedGetSinglePixelWidth = null; 12152 ctx.save(); 12153 if (current.textMatrix) { 12154 ctx.transform(...current.textMatrix); 12155 } 12156 ctx.translate(current.x, current.y + current.textRise); 12157 ctx.scale(textHScale, fontDirection); 12158 const dependencyTracker = this.dependencyTracker; 12159 this.dependencyTracker = dependencyTracker ? new CanvasNestedDependencyTracker(dependencyTracker, opIdx) : null; 12160 for (i = 0; i < glyphsLength; ++i) { 12161 glyph = glyphs[i]; 12162 if (typeof glyph === "number") { 12163 spacingLength = spacingDir * glyph * fontSize / 1000; 12164 this.ctx.translate(spacingLength, 0); 12165 current.x += spacingLength * textHScale; 12166 continue; 12167 } 12168 const spacing = (glyph.isSpace ? wordSpacing : 0) + charSpacing; 12169 const operatorList = font.charProcOperatorList
vendor: 15,985 bytes, lines 12169-12600
12169[glyph.operatorListId]; 12170 if (!operatorList) { 12171 warn(`Type3 character "${glyph.operatorListId}" is not available.`); 12172 } else if (this.contentVisible) { 12173 this.save(); 12174 if (operatorList.fnArray[0] === OPS.setCharWidth) { 12175 current.fillAlpha = current.strokeAlpha = 1; 12176 ctx.globalAlpha = 1; 12177 } 12178 ctx.scale(fontSize, fontSize); 12179 ctx.transform(...fontMatrix); 12180 this.executeOperatorList(operatorList); 12181 this.restore(); 12182 } 12183 const p = [glyph.width, 0]; 12184 Util.applyTransform(p, fontMatrix); 12185 width = p[0] * fontSize + spacing; 12186 ctx.translate(width, 0); 12187 current.x += width * textHScale; 12188 } 12189 ctx.restore(); 12190 if (dependencyTracker) { 12191 this.dependencyTracker = dependencyTracker; 12192 } 12193 } 12194 setCharWidth(opIdx, xWidth, yWidth) {} 12195 setCharWidthAndBounds(opIdx, xWidth, yWidth, llx, lly, urx, ury) { 12196 const clip = new Path2D(); 12197 clip.rect(llx, lly, urx - llx, ury - lly); 12198 this.ctx.clip(clip); 12199 this.dependencyTracker?.recordBBox(opIdx, this.ctx, llx, urx, lly, ury).recordClipBox(opIdx, this.ctx, llx, urx, lly, ury); 12200 this.endPath(opIdx); 12201 } 12202 getColorN_Pattern(opIdx, IR) { 12203 let pattern; 12204 if (IR[0] === "TilingPattern") { 12205 const baseTransform = this.baseTransform || getCurrentTransform(this.ctx); 12206 const canvasGraphicsFactory = { 12207 createCanvasGraphics: (ctx, renderingOpIdx) => new CanvasGraphics(ctx, this.commonObjs, this.objs, this.canvasFactory, this.filterFactory, { 12208 optionalContentConfig: this.optionalContentConfig, 12209 markedContentStack: this.markedContentStack 12210 }, undefined, undefined, this.dependencyTracker ? new CanvasNestedDependencyTracker(this.dependencyTracker, renderingOpIdx, true) : null) 12211 }; 12212 pattern = new TilingPattern(IR, this.ctx, canvasGraphicsFactory, baseTransform); 12213 } else { 12214 pattern = this._getPattern(opIdx, IR[1], IR[2]); 12215 } 12216 return pattern; 12217 } 12218 setStrokeColorN(opIdx, ...args) { 12219 this.dependencyTracker?.recordSimpleData("strokeColor", opIdx); 12220 this.current.strokeColor = this.getColorN_Pattern(opIdx, args); 12221 this.current.patternStroke = true; 12222 } 12223 setFillColorN(opIdx, ...args) { 12224 this.dependencyTracker?.recordSimpleData("fillColor", opIdx); 12225 const pattern = this.current.fillColor = this.getColorN_Pattern(opIdx, args); 12226 this.current.patternFill = true; 12227 this.current.tilingPatternDims = pattern instanceof TilingPattern ? [0, 0, 0, 0] : null; 12228 } 12229 setStrokeRGBColor(opIdx, color) { 12230 this.dependencyTracker?.recordSimpleData("strokeColor", opIdx); 12231 this.ctx.strokeStyle = this.current.strokeColor = color; 12232 this.current.patternStroke = false; 12233 } 12234 setStrokeTransparent(opIdx) { 12235 this.dependencyTracker?.recordSimpleData("strokeColor", opIdx); 12236 this.ctx.strokeStyle = this.current.strokeColor = "transparent"; 12237 this.current.patternStroke = false; 12238 } 12239 setFillRGBColor(opIdx, color) { 12240 this.dependencyTracker?.recordSimpleData("fillColor", opIdx); 12241 this.ctx.fillStyle = this.current.fillColor = color; 12242 this.current.patternFill = false; 12243 this.current.tilingPatternDims = null; 12244 } 12245 setFillTransparent(opIdx) { 12246 this.dependencyTracker?.recordSimpleData("fillColor", opIdx); 12247 this.ctx.fillStyle = this.current.fillColor = "transparent"; 12248 this.current.patternFill = false; 12249 this.current.tilingPatternDims = null; 12250 } 12251 _getPattern(opIdx, objId, matrix = null) { 12252 let pattern; 12253 if (this.cachedPatterns.has(objId)) { 12254 pattern = this.cachedPatterns.get(objId); 12255 } else { 12256 pattern = getShadingPattern(this.getObject(opIdx, objId)); 12257 this.cachedPatterns.set(objId, pattern); 12258 } 12259 if (matrix) { 12260 pattern.matrix = matrix; 12261 } 12262 return pattern; 12263 } 12264 shadingFill(opIdx, objId) { 12265 if (!this.contentVisible) { 12266 return; 12267 } 12268 const started = this.#beginKnockoutElement(this.current.fillAlpha); 12269 const ctx = this.ctx; 12270 this.save(opIdx); 12271 const pattern = this._getPattern(opIdx, objId); 12272 ctx.fillStyle = pattern.getPattern(ctx, this, getCurrentTransformInverse(ctx), PathType.SHADING, opIdx); 12273 const inv = getCurrentTransformInverse(ctx); 12274 if (inv) { 12275 const { 12276 width, 12277 height 12278 } = ctx.canvas; 12279 const minMax = F32_BBOX_INIT.slice(); 12280 Util.axialAlignedBoundingBox([0, 0, width, height], inv, minMax); 12281 const [x0, y0, x1, y1] = minMax; 12282 this.ctx.fillRect(x0, y0, x1 - x0, y1 - y0); 12283 } else { 12284 this.ctx.fillRect(-1e10, -1e10, 2e10, 2e10); 12285 } 12286 this.dependencyTracker?.resetBBox(opIdx).recordFullPageBBox(opIdx).recordDependencies(opIdx, Dependencies.transform).recordDependencies(opIdx, Dependencies.fill).recordOperation(opIdx); 12287 this.compose(this.current.getClippedPathBoundingBox()); 12288 this.restore(opIdx); 12289 this.#endKnockoutElement(started); 12290 } 12291 beginInlineImage() { 12292 unreachable("Should not call beginInlineImage"); 12293 } 12294 beginImageData() { 12295 unreachable("Should not call beginImageData"); 12296 } 12297 paintFormXObjectBegin(opIdx, matrix, bbox) { 12298 if (!this.contentVisible) { 12299 return; 12300 } 12301 this.save(opIdx); 12302 this.baseTransformStack.push(this.baseTransform); 12303 if (matrix) { 12304 this.transform(opIdx, ...matrix); 12305 } 12306 this.baseTransform = getCurrentTransform(this.ctx); 12307 if (bbox) { 12308 Util.axialAlignedBoundingBox(bbox, this.baseTransform, this.current.minMax); 12309 const [x0, y0, x1, y1] = bbox; 12310 const clip = new Path2D(); 12311 clip.rect(x0, y0, x1 - x0, y1 - y0); 12312 this.ctx.clip(clip); 12313 this.dependencyTracker?.recordClipBox(opIdx, this.ctx, x0, x1, y0, y1); 12314 this.endPath(opIdx); 12315 } 12316 } 12317 paintFormXObjectEnd(opIdx) { 12318 if (!this.contentVisible) { 12319 return; 12320 } 12321 this.restore(opIdx); 12322 this.baseTransform = this.baseTransformStack.pop(); 12323 } 12324 beginGroup(opIdx, group) { 12325 if (!this.contentVisible) { 12326 return; 12327 } 12328 this.save(opIdx); 12329 const { 12330 inSMaskMode 12331 } = this; 12332 if (inSMaskMode) { 12333 this.endSMaskMode(); 12334 this.current.activeSMask = null; 12335 } 12336 const currentCtx = this.ctx; 12337 if ((!group.needsIsolation || !group.isolated && !group.hasSoftMask) && !group.knockout && !group.isGray && this.#knockoutGroupLevel === 0 && currentCtx.globalAlpha === 1 && currentCtx.globalCompositeOperation === "source-over" && !inSMaskMode) { 12338 if (group.bbox) { 12339 let clip = new Path2D(); 12340 const [x0, y0, x1, y1] = group.bbox; 12341 clip.rect(x0, y0, x1 - x0, y1 - y0); 12342 if (group.matrix) { 12343 const path = new Path2D(); 12344 path.addPath(clip, new DOMMatrix(group.matrix)); 12345 clip = path; 12346 } 12347 currentCtx.clip(clip); 12348 } 12349 this.groupStack.push(null); 12350 this.#groupStackMeta.push(null); 12351 this.groupLevel++; 12352 return; 12353 } 12354 if (!group.isolated && !group.knockout && this.#knockoutGroupLevel === 0) { 12355 info("TODO: Fully support non-isolated non-knockout groups."); 12356 } 12357 const currentTransform = getCurrentTransform(currentCtx); 12358 if (group.matrix) { 12359 currentCtx.transform(...group.matrix); 12360 } 12361 const canvasBounds = [0, 0, currentCtx.canvas.width, currentCtx.canvas.height]; 12362 let bounds; 12363 if (group.bbox) { 12364 bounds = F32_BBOX_INIT.slice(); 12365 Util.axialAlignedBoundingBox(group.bbox, getCurrentTransform(currentCtx), bounds); 12366 bounds = Util.intersect(bounds, canvasBounds) || [0, 0, 0, 0]; 12367 } else { 12368 bounds = canvasBounds; 12369 } 12370 const offsetX = Math.floor(bounds[0]); 12371 const offsetY = Math.floor(bounds[1]); 12372 const drawnWidth = Math.max(Math.ceil(bounds[2]) - offsetX, 1); 12373 const drawnHeight = Math.max(Math.ceil(bounds[3]) - offsetY, 1); 12374 this.current.startNewPathAndClipBox([0, 0, drawnWidth, drawnHeight]); 12375 const scratchCanvas = this.canvasFactory.create(drawnWidth, drawnHeight); 12376 if (group.smask) { 12377 this.smaskGroupCanvases.push(scratchCanvas); 12378 } 12379 const groupCtx = scratchCanvas.context; 12380 const backdropCtx = group.knockout && !group.isolated ? currentCtx : null; 12381 const hasInnerBackdrop = !group.isolated && !group.knockout && !group.smask && group.needsIsolation && this.#knockoutGroupLevel > 0; 12382 const knockoutMaskEntry = group.knockout ? this.canvasFactory.create(drawnWidth, drawnHeight) : null; 12383 const savedKnockoutLevel = this.#knockoutGroupLevel; 12384 if (group.knockout) { 12385 this.#knockoutGroupLevel++; 12386 } else { 12387 this.#knockoutGroupLevel = 0; 12388 } 12389 groupCtx.translate(-offsetX, -offsetY); 12390 groupCtx.transform(...currentTransform); 12391 if (!group.isolated && !group.smask && inSMaskMode && group.needsIsolation) { 12392 groupCtx.save(); 12393 groupCtx.setTransform(1, 0, 0, 1, 0, 0); 12394 groupCtx.drawImage(currentCtx.canvas, -offsetX, -offsetY); 12395 groupCtx.restore(); 12396 } 12397 if (group.bbox) { 12398 let clip = new Path2D(); 12399 const [x0, y0, x1, y1] = group.bbox; 12400 clip.rect(x0, y0, x1 - x0, y1 - y0); 12401 if (group.matrix) { 12402 const path = new Path2D(); 12403 path.addPath(clip, new DOMMatrix(group.matrix)); 12404 clip = path; 12405 } 12406 groupCtx.clip(clip); 12407 } 12408 if (group.smask) { 12409 this.smaskStack.push({ 12410 canvas: scratchCanvas.canvas, 12411 context: groupCtx, 12412 offsetX, 12413 offsetY, 12414 subtype: group.smask.subtype, 12415 backdrop: group.smask.backdrop, 12416 transferMap: group.smask.transferMap || null 12417 }); 12418 } 12419 if (!group.smask || this.dependencyTracker) { 12420 currentCtx.setTransform(1, 0, 0, 1, 0, 0); 12421 currentCtx.translate(offsetX, offsetY); 12422 currentCtx.save(); 12423 } 12424 copyCtxState(currentCtx, groupCtx); 12425 this.ctx = groupCtx; 12426 this.dependencyTracker?.inheritSimpleDataAsFutureForcedDependencies(["fillAlpha", "strokeAlpha", "globalCompositeOperation"]).pushBaseTransform(currentCtx); 12427 this.setGState(opIdx, [["BM", "source-over"], ["ca", 1], ["CA", 1], ["TR", null]]); 12428 this.groupStack.push(currentCtx); 12429 this.#groupStackMeta.push({ 12430 backdropCtx, 12431 savedKnockoutLevel, 12432 offsetX, 12433 offsetY, 12434 hasInnerBackdrop, 12435 knockoutMaskEntry, 12436 knockoutTempEntry: null, 12437 knockoutBackdropEntry: null 12438 }); 12439 this.groupLevel++; 12440 } 12441 endGroup(opIdx, group) { 12442 if (!this.contentVisible) { 12443 return; 12444 } 12445 this.groupLevel--; 12446 const groupCtx = this.ctx; 12447 const ctx = this.groupStack.pop(); 12448 const groupMeta = this.#groupStackMeta.pop(); 12449 if (groupMeta) { 12450 this.#knockoutGroupLevel = groupMeta.savedKnockoutLevel; 12451 } 12452 if (ctx === null) { 12453 this.restore(opIdx); 12454 return; 12455 } 12456 if (group.isGray) { 12457 this.#convertGroupToGray(groupCtx); 12458 } 12459 this.ctx = ctx; 12460 this.ctx.imageSmoothingEnabled = false; 12461 this.dependencyTracker?.popBaseTransform(); 12462 if (group.smask) { 12463 this.tempSMask = this.smaskStack.pop(); 12464 this.restore(opIdx); 12465 if (this.dependencyTracker) { 12466 this.ctx.restore(); 12467 if (this.inSMaskMode) { 12468 this.ctx.setTransform(this.suspendedCtx.getTransform()); 12469 } 12470 } 12471 this.#destroyKnockoutPools(groupMeta); 12472 } else { 12473 this.ctx.restore(); 12474 const currentMtx = getCurrentTransform(this.ctx); 12475 this.restore(opIdx); 12476 this.ctx.save(); 12477 this.ctx.setTransform(...currentMtx); 12478 const dirtyBox = F32_BBOX_INIT.slice(); 12479 Util.axialAlignedBoundingBox([0, 0, groupCtx.canvas.width, groupCtx.canvas.height], currentMtx, dirtyBox); 12480 const parentGroupMeta = this.#groupStackMeta.at(-1); 12481 if (this.#knockoutGroupLevel > 0) { 12482 if (groupMeta.hasInnerBackdrop) { 12483 const { 12484 width, 12485 height 12486 } = groupCtx.canvas; 12487 const colorEntry = this.canvasFactory.create(width, height); 12488 const colorCtx = colorEntry.context; 12489 colorCtx.drawImage(ctx.canvas, groupMeta.offsetX, groupMeta.offsetY, width, height, 0, 0, width, height); 12490 colorCtx.globalCompositeOperation = "source-over"; 12491 colorCtx.drawImage(groupCtx.canvas, 0, 0); 12492 const shapeMaskEntry = this.#createKnockoutMaskCanvas(groupCtx.canvas); 12493 colorCtx.globalCompositeOperation = "destination-in"; 12494 colorCtx.drawImage(shapeMaskEntry.canvas, 0, 0); 12495 const sourceCompositeOperation = this.ctx.globalCompositeOperation; 12496 const sourceAlpha = this.ctx.globalAlpha; 12497 const sourceFilter = this.ctx.filter; 12498 this.ctx.save(); 12499 this.ctx.setTransform(...currentMtx); 12500 this.ctx.globalAlpha = 1; 12501 if (FeatureTest.isCanvasFilterSupported) { 12502 this.ctx.filter = "none"; 12503 } 12504 this.ctx.globalCompositeOperation = "destination-out"; 12505 this.ctx.drawImage(shapeMaskEntry.canvas, 0, 0); 12506 this.ctx.globalCompositeOperation = sourceCompositeOperation; 12507 this.ctx.globalAlpha = sourceAlpha; 12508 if (FeatureTest.isCanvasFilterSupported) { 12509 this.ctx.filter = sourceFilter ?? "none"; 12510 } 12511 this.ctx.drawImage(colorEntry.canvas, 0, 0); 12512 this.ctx.restore(); 12513 this.canvasFactory.destroy(shapeMaskEntry); 12514 this.canvasFactory.destroy(colorEntry); 12515 } else { 12516 const backdropCtx = parentGroupMeta?.backdropCtx ?? null; 12517 this.#compositeKnockoutSurface(this.ctx, groupCtx.canvas, { 12518 backdropCanvas: backdropCtx?.canvas ?? null, 12519 destTransform: currentMtx, 12520 backdropOffset: backdropCtx ? [parentGroupMeta.offsetX + groupMeta.offsetX, parentGroupMeta.offsetY + groupMeta.offsetY] : [0, 0], 12521 sourceAlpha: this.ctx.globalAlpha, 12522 sourceFilter: this.ctx.filter 12523 }); 12524 } 12525 } else { 12526 this.ctx.drawImage(groupCtx.canvas, 0, 0); 12527 } 12528 this.ctx.restore(); 12529 this.canvasFactory.destroy({ 12530 canvas: groupCtx.canvas, 12531 context: groupCtx 12532 }); 12533 this.#destroyKnockoutPools(groupMeta); 12534 this.compose(dirtyBox); 12535 } 12536 } 12537 #convertGroupToGray(groupCtx) { 12538 const { 12539 canvas 12540 } = groupCtx; 12541 const { 12542 width, 12543 height 12544 } = canvas; 12545 if (FeatureTest.isCanvasFilterSupported) { 12546 groupCtx.save(); 12547 groupCtx.setTransform(1, 0, 0, 1, 0, 0); 12548 groupCtx.filter = "grayscale(1)"; 12549 groupCtx.globalAlpha = 1; 12550 groupCtx.globalCompositeOperation = "copy"; 12551 groupCtx.drawImage(canvas, 0, 0); 12552 groupCtx.restore(); 12553 return; 12554 } 12555 const imageData = groupCtx.getImageData(0, 0, width, height); 12556 const { 12557 data 12558 } = imageData; 12559 for (let i = 0, ii = data.length; i < ii; i += 4) { 12560 const gray = data[i] * 0.2126 + data[i + 1] * 0.7152 + data[i + 2] * 0.0722 + 0.5 | 0; 12561 data[i] = data[i + 1] = data[i + 2] = gray; 12562 } 12563 groupCtx.putImageData(imageData, 0, 0); 12564 } 12565 #destroyKnockoutPools(groupMeta) { 12566 if (!groupMeta) { 12567 return; 12568 } 12569 if (groupMeta.knockoutMaskEntry) { 12570 this.canvasFactory.destroy(groupMeta.knockoutMaskEntry); 12571 groupMeta.knockoutMaskEntry = null; 12572 } 12573 if (groupMeta.knockoutTempEntry) { 12574 this.canvasFactory.destroy(groupMeta.knockoutTempEntry); 12575 groupMeta.knockoutTempEntry = null; 12576 } 12577 if (groupMeta.knockoutBackdropEntry) { 12578 this.canvasFactory.destroy(groupMeta.knockoutBackdropEntry); 12579 groupMeta.knockoutBackdropEntry = null; 12580 } 12581 } 12582 beginAnnotation(opIdx, id, rect, transform, matrix, hasOwnCanvas, canvasName) { 12583 this.#restoreInitialState(); 12584 resetCtxToDefault(this.ctx); 12585 this.ctx.save(); 12586 this.save(opIdx); 12587 if (this.baseTransform) { 12588 this.ctx.setTransform(...this.baseTransform); 12589 } 12590 if (rect) { 12591 const width = rect[2] - rect[0]; 12592 const height = rect[3] - rect[1]; 12593 if (hasOwnCanvas && this.annotationCanvasMap) { 12594 transform = transform.slice(); 12595 transform[4] -= rect[0]; 12596 transform[5] -= rect[1]; 12597 rect = rect.slice(); 12598 rect[0] = rect[1] = 0; 12599 rect[2] = width; 12600 rect[3] = height
vendor: 14,517 bytes, lines 12600-13019
12600; 12601 Util.singularValueDecompose2dScale(getCurrentTransform(this.ctx), XY); 12602 const { 12603 viewportScale 12604 } = this; 12605 const canvasWidth = Math.ceil(width * this.outputScaleX * viewportScale); 12606 const canvasHeight = Math.ceil(height * this.outputScaleY * viewportScale); 12607 this.annotationCanvas = this.canvasFactory.create(canvasWidth, canvasHeight); 12608 const { 12609 canvas, 12610 context 12611 } = this.annotationCanvas; 12612 if (canvasName) { 12613 const canvases = this.annotationCanvasMap.getOrInsertComputed(id, makeArr); 12614 canvas.setAttribute("data-canvas-name", canvasName); 12615 const index = canvases.findIndex(c => c.getAttribute("data-canvas-name") === canvasName); 12616 if (index === -1) { 12617 canvases.push(canvas); 12618 } else { 12619 canvases[index] = canvas; 12620 } 12621 } else { 12622 this.annotationCanvasMap.set(id, canvas); 12623 } 12624 this.annotationCanvas.savedCtx = this.ctx; 12625 this.ctx = context; 12626 this.ctx.save(); 12627 this.ctx.setTransform(XY[0], 0, 0, -XY[1], 0, height * XY[1]); 12628 resetCtxToDefault(this.ctx); 12629 } else { 12630 resetCtxToDefault(this.ctx); 12631 this.endPath(opIdx); 12632 const clip = new Path2D(); 12633 clip.rect(rect[0], rect[1], width, height); 12634 this.ctx.clip(clip); 12635 } 12636 } 12637 this.current = new CanvasExtraState(this.ctx.canvas.width, this.ctx.canvas.height); 12638 this.baseTransformStack.push(this.baseTransform); 12639 this.transform(opIdx, ...transform); 12640 this.transform(opIdx, ...matrix); 12641 this.baseTransform = getCurrentTransform(this.ctx); 12642 } 12643 endAnnotation(opIdx) { 12644 if (this.annotationCanvas) { 12645 this.ctx.restore(); 12646 this.#drawFilter(); 12647 this.ctx = this.annotationCanvas.savedCtx; 12648 delete this.annotationCanvas.savedCtx; 12649 delete this.annotationCanvas; 12650 } 12651 this.baseTransform = this.baseTransformStack.pop(); 12652 } 12653 paintImageMaskXObject(opIdx, img) { 12654 if (!this.contentVisible) { 12655 return; 12656 } 12657 const count = img.count; 12658 img = this.getObject(opIdx, img.data, img); 12659 img.count = count; 12660 const started = this.#beginKnockoutElement(this.current.fillAlpha); 12661 const ctx = this.ctx; 12662 const mask = this._createMaskCanvas(opIdx, img); 12663 const maskCanvas = mask.canvas; 12664 ctx.save(); 12665 ctx.setTransform(1, 0, 0, 1, 0, 0); 12666 ctx.drawImage(maskCanvas, mask.offsetX, mask.offsetY); 12667 this.dependencyTracker?.resetBBox(opIdx).recordBBox(opIdx, this.ctx, mask.offsetX, mask.offsetX + maskCanvas.width, mask.offsetY, mask.offsetY + maskCanvas.height).recordOperation(opIdx); 12668 ctx.restore(); 12669 if (mask.canvasEntry) { 12670 this.canvasFactory.destroy(mask.canvasEntry); 12671 } 12672 this.compose(); 12673 this.#endKnockoutElement(started); 12674 } 12675 paintImageMaskXObjectRepeat(opIdx, img, scaleX, skewX = 0, skewY = 0, scaleY, positions) { 12676 if (!this.contentVisible) { 12677 return; 12678 } 12679 img = this.getObject(opIdx, img.data, img); 12680 const started = this.#beginKnockoutElement(this.current.fillAlpha); 12681 const ctx = this.ctx; 12682 ctx.save(); 12683 const currentTransform = getCurrentTransform(ctx); 12684 ctx.transform(scaleX, skewX, skewY, scaleY, 0, 0); 12685 const mask = this._createMaskCanvas(opIdx, img); 12686 ctx.setTransform(1, 0, 0, 1, mask.offsetX - currentTransform[4], mask.offsetY - currentTransform[5]); 12687 this.dependencyTracker?.resetBBox(opIdx); 12688 for (let i = 0, ii = positions.length; i < ii; i += 2) { 12689 const trans = Util.transform(currentTransform, [scaleX, skewX, skewY, scaleY, positions[i], positions[i + 1]]); 12690 ctx.drawImage(mask.canvas, trans[4], trans[5]); 12691 this.dependencyTracker?.recordBBox(opIdx, this.ctx, trans[4], trans[4] + mask.canvas.width, trans[5], trans[5] + mask.canvas.height); 12692 } 12693 ctx.restore(); 12694 if (mask.canvasEntry) { 12695 this.canvasFactory.destroy(mask.canvasEntry); 12696 } 12697 this.compose(); 12698 this.dependencyTracker?.recordOperation(opIdx); 12699 this.#endKnockoutElement(started); 12700 } 12701 paintImageMaskXObjectGroup(opIdx, images) { 12702 if (!this.contentVisible) { 12703 return; 12704 } 12705 const started = this.#beginKnockoutElement(this.current.fillAlpha); 12706 const ctx = this.ctx; 12707 const fillColor = this.current.fillColor; 12708 const isPatternFill = this.current.patternFill; 12709 this.dependencyTracker?.resetBBox(opIdx).recordDependencies(opIdx, Dependencies.transformAndFill); 12710 for (const image of images) { 12711 const { 12712 data, 12713 width, 12714 height, 12715 transform 12716 } = image; 12717 const maskCanvas = this.canvasFactory.create(width, height); 12718 const maskCtx = maskCanvas.context; 12719 maskCtx.save(); 12720 const img = this.getObject(opIdx, data, image); 12721 putBinaryImageMask(maskCtx, img); 12722 maskCtx.globalCompositeOperation = "source-in"; 12723 maskCtx.fillStyle = isPatternFill ? fillColor.getPattern(maskCtx, this, getCurrentTransformInverse(ctx), PathType.FILL, opIdx) : fillColor; 12724 maskCtx.fillRect(0, 0, width, height); 12725 maskCtx.restore(); 12726 ctx.save(); 12727 ctx.transform(...transform); 12728 ctx.scale(1, -1); 12729 drawImageAtIntegerCoords(ctx, maskCanvas.canvas, 0, 0, width, height, 0, -1, 1, 1); 12730 this.canvasFactory.destroy(maskCanvas); 12731 this.dependencyTracker?.recordBBox(opIdx, ctx, 0, width, 0, height); 12732 ctx.restore(); 12733 } 12734 this.compose(); 12735 this.dependencyTracker?.recordOperation(opIdx); 12736 this.#endKnockoutElement(started); 12737 } 12738 paintImageXObject(opIdx, objId) { 12739 if (!this.contentVisible) { 12740 return; 12741 } 12742 const imgData = this.getObject(opIdx, objId); 12743 if (!imgData) { 12744 warn("Dependent image isn't ready yet"); 12745 return; 12746 } 12747 this.paintInlineImageXObject(opIdx, imgData); 12748 } 12749 paintImageXObjectRepeat(opIdx, objId, scaleX, scaleY, positions) { 12750 if (!this.contentVisible) { 12751 return; 12752 } 12753 const imgData = this.getObject(opIdx, objId); 12754 if (!imgData) { 12755 warn("Dependent image isn't ready yet"); 12756 return; 12757 } 12758 const width = imgData.width; 12759 const height = imgData.height; 12760 const map = []; 12761 for (let i = 0, ii = positions.length; i < ii; i += 2) { 12762 map.push({ 12763 transform: [scaleX, 0, 0, scaleY, positions[i], positions[i + 1]], 12764 x: 0, 12765 y: 0, 12766 w: width, 12767 h: height 12768 }); 12769 } 12770 this.paintInlineImageXObjectGroup(opIdx, imgData, map); 12771 } 12772 applyTransferMapsToCanvas(ctx) { 12773 if (this.current.transferMaps !== "none") { 12774 ctx.filter = this.current.transferMaps; 12775 ctx.drawImage(ctx.canvas, 0, 0); 12776 ctx.filter = "none"; 12777 } 12778 return ctx.canvas; 12779 } 12780 applyTransferMapsToBitmap(imgData) { 12781 if (this.current.transferMaps === "none") { 12782 return { 12783 img: imgData.bitmap, 12784 canvasEntry: null 12785 }; 12786 } 12787 const { 12788 bitmap, 12789 width, 12790 height 12791 } = imgData; 12792 const tmpCanvas = this.canvasFactory.create(width, height); 12793 const tmpCtx = tmpCanvas.context; 12794 tmpCtx.filter = this.current.transferMaps; 12795 tmpCtx.drawImage(bitmap, 0, 0); 12796 tmpCtx.filter = "none"; 12797 return { 12798 img: tmpCanvas.canvas, 12799 canvasEntry: tmpCanvas 12800 }; 12801 } 12802 paintInlineImageXObject(opIdx, imgData) { 12803 if (!this.contentVisible) { 12804 return; 12805 } 12806 const width = imgData.width; 12807 const height = imgData.height; 12808 const started = this.#beginKnockoutElement(this.current.fillAlpha); 12809 const ctx = this.ctx; 12810 this.save(opIdx); 12811 const { 12812 filter 12813 } = ctx; 12814 if (filter !== "none" && filter !== "") { 12815 ctx.filter = "none"; 12816 } 12817 ctx.scale(1 / width, -1 / height); 12818 let imgToPaint; 12819 let inlineImgCanvas = null; 12820 if (imgData.bitmap) { 12821 const result = this.applyTransferMapsToBitmap(imgData); 12822 imgToPaint = result.img; 12823 inlineImgCanvas = result.canvasEntry; 12824 } else { 12825 const tmpCanvas = this.canvasFactory.create(width, height); 12826 putBinaryImageData(tmpCanvas.context, imgData); 12827 imgToPaint = this.applyTransferMapsToCanvas(tmpCanvas.context); 12828 inlineImgCanvas = tmpCanvas; 12829 } 12830 const scaled = this._scaleImage(imgToPaint, getCurrentTransformInverse(ctx)); 12831 ctx.imageSmoothingEnabled = getImageSmoothingEnabled(getCurrentTransform(ctx), imgData.interpolate); 12832 if (this.dependencyTracker) { 12833 this.dependencyTracker.resetBBox(opIdx).recordBBox(opIdx, ctx, 0, width, -height, 0).recordDependencies(opIdx, Dependencies.imageXObject).recordOperation(opIdx); 12834 this.imagesTracker?.record(ctx, width, height, this.dependencyTracker.clipBox); 12835 } 12836 drawImageAtIntegerCoords(ctx, scaled.img, 0, 0, scaled.paintWidth, scaled.paintHeight, 0, -height, width, height); 12837 if (scaled.tmpCanvas) { 12838 this.canvasFactory.destroy(scaled.tmpCanvas); 12839 } 12840 if (inlineImgCanvas) { 12841 this.canvasFactory.destroy(inlineImgCanvas); 12842 } 12843 this.compose(); 12844 this.restore(opIdx); 12845 this.#endKnockoutElement(started); 12846 } 12847 paintInlineImageXObjectGroup(opIdx, imgData, map) { 12848 if (!this.contentVisible) { 12849 return; 12850 } 12851 const started = this.#beginKnockoutElement(this.current.fillAlpha); 12852 const ctx = this.ctx; 12853 let imgToPaint; 12854 let inlineImgCanvas = null; 12855 if (imgData.bitmap) { 12856 imgToPaint = imgData.bitmap; 12857 } else { 12858 const w = imgData.width; 12859 const h = imgData.height; 12860 const tmpCanvas = this.canvasFactory.create(w, h); 12861 putBinaryImageData(tmpCanvas.context, imgData); 12862 imgToPaint = this.applyTransferMapsToCanvas(tmpCanvas.context); 12863 inlineImgCanvas = tmpCanvas; 12864 } 12865 this.dependencyTracker?.resetBBox(opIdx); 12866 for (const entry of map) { 12867 ctx.save(); 12868 ctx.transform(...entry.transform); 12869 ctx.scale(1, -1); 12870 drawImageAtIntegerCoords(ctx, imgToPaint, entry.x, entry.y, entry.w, entry.h, 0, -1, 1, 1); 12871 this.dependencyTracker?.recordBBox(opIdx, ctx, 0, 1, -1, 0); 12872 ctx.restore(); 12873 } 12874 if (inlineImgCanvas) { 12875 this.canvasFactory.destroy(inlineImgCanvas); 12876 } 12877 this.dependencyTracker?.recordOperation(opIdx); 12878 this.compose(); 12879 this.#endKnockoutElement(started); 12880 } 12881 paintSolidColorImageMask(opIdx) { 12882 if (!this.contentVisible) { 12883 return; 12884 } 12885 const started = this.#beginKnockoutElement(this.current.fillAlpha); 12886 this.dependencyTracker?.resetBBox(opIdx).recordBBox(opIdx, this.ctx, 0, 1, 0, 1).recordDependencies(opIdx, Dependencies.fill).recordOperation(opIdx); 12887 this.ctx.fillRect(0, 0, 1, 1); 12888 this.compose(); 12889 this.#endKnockoutElement(started); 12890 } 12891 markPoint(opIdx, tag) {} 12892 markPointProps(opIdx, tag, properties) {} 12893 beginMarkedContent(opIdx, tag) { 12894 this.dependencyTracker?.beginMarkedContent(opIdx); 12895 this.markedContentStack.push({ 12896 visible: true 12897 }); 12898 } 12899 beginMarkedContentProps(opIdx, tag, properties) { 12900 this.dependencyTracker?.beginMarkedContent(opIdx); 12901 if (tag === "OC") { 12902 this.markedContentStack.push({ 12903 visible: this.optionalContentConfig.isVisible(properties) 12904 }); 12905 } else { 12906 this.markedContentStack.push({ 12907 visible: true 12908 }); 12909 } 12910 this.contentVisible = this.isContentVisible(); 12911 } 12912 endMarkedContent(opIdx) { 12913 this.dependencyTracker?.endMarkedContent(opIdx); 12914 this.markedContentStack.pop(); 12915 this.contentVisible = this.isContentVisible(); 12916 } 12917 beginCompat(opIdx) {} 12918 endCompat(opIdx) {} 12919 consumePath(opIdx, path, clipBox) { 12920 const isEmpty = this.current.isEmptyClip(); 12921 if (this.pendingClip) { 12922 this.current.updateClipFromPath(); 12923 } 12924 if (!this.pendingClip) { 12925 this.compose(clipBox); 12926 } 12927 const ctx = this.ctx; 12928 if (this.pendingClip) { 12929 if (!isEmpty) { 12930 if (this.pendingClip === EO_CLIP) { 12931 ctx.clip(path, "evenodd"); 12932 } else { 12933 ctx.clip(path); 12934 } 12935 } 12936 this.pendingClip = null; 12937 this.dependencyTracker?.bboxToClipBoxDropOperation(opIdx).recordFutureForcedDependency("clipPath", opIdx); 12938 } else { 12939 this.dependencyTracker?.recordOperation(opIdx); 12940 } 12941 this.current.startNewPathAndClipBox(this.current.clipBox); 12942 } 12943 getSinglePixelWidth() { 12944 if (!this._cachedGetSinglePixelWidth) { 12945 const m = getCurrentTransform(this.ctx); 12946 if (m[1] === 0 && m[2] === 0) { 12947 this._cachedGetSinglePixelWidth = 1 / Math.min(Math.abs(m[0]), Math.abs(m[3])); 12948 } else { 12949 const absDet = Math.abs(m[0] * m[3] - m[2] * m[1]); 12950 const normX = Math.hypot(m[0], m[2]); 12951 const normY = Math.hypot(m[1], m[3]); 12952 this._cachedGetSinglePixelWidth = Math.max(normX, normY) / absDet; 12953 } 12954 } 12955 return this._cachedGetSinglePixelWidth; 12956 } 12957 getScaleForStroking() { 12958 if (this._cachedScaleForStroking[0] === -1) { 12959 const { 12960 lineWidth 12961 } = this.current; 12962 const { 12963 a, 12964 b, 12965 c, 12966 d 12967 } = this.ctx.getTransform(); 12968 let scaleX, scaleY; 12969 if (b === 0 && c === 0) { 12970 const normX = Math.abs(a); 12971 const normY = Math.abs(d); 12972 if (normX === normY) { 12973 if (lineWidth === 0) { 12974 scaleX = scaleY = 1 / normX; 12975 } else { 12976 const scaledLineWidth = normX * lineWidth; 12977 scaleX = scaleY = scaledLineWidth < 1 ? 1 / scaledLineWidth : 1; 12978 } 12979 } else if (lineWidth === 0) { 12980 scaleX = 1 / normX; 12981 scaleY = 1 / normY; 12982 } else { 12983 const scaledXLineWidth = normX * lineWidth; 12984 const scaledYLineWidth = normY * lineWidth; 12985 scaleX = scaledXLineWidth < 1 ? 1 / scaledXLineWidth : 1; 12986 scaleY = scaledYLineWidth < 1 ? 1 / scaledYLineWidth : 1; 12987 } 12988 } else { 12989 const absDet = Math.abs(a * d - b * c); 12990 const normX = Math.hypot(a, b); 12991 const normY = Math.hypot(c, d); 12992 if (lineWidth === 0) { 12993 scaleX = normY / absDet; 12994 scaleY = normX / absDet; 12995 } else { 12996 const baseArea = lineWidth * absDet; 12997 scaleX = normY > baseArea ? normY / baseArea : 1; 12998 scaleY = normX > baseArea ? normX / baseArea : 1; 12999 } 13000 } 13001 this._cachedScaleForStroking[0] = scaleX; 13002 this._cachedScaleForStroking[1] = scaleY; 13003 } 13004 return this._cachedScaleForStroking; 13005 } 13006 rescaleAndStroke(path, saveRestore) { 13007 const { 13008 ctx, 13009 current: { 13010 lineWidth 13011 } 13012 } = this; 13013 const [scaleX, scaleY] = this.getScaleForStroking(); 13014 if (scaleX === scaleY) { 13015 ctx.lineWidth = (lineWidth || 1) * scaleX; 13016 ctx.stroke(path); 13017 return; 13018 } 13019 const
vendor: 7,361 bytes, lines 13019-13288
13019dashes = ctx.getLineDash(); 13020 if (saveRestore) { 13021 ctx.save(); 13022 } 13023 ctx.scale(scaleX, scaleY); 13024 SCALE_MATRIX.a = 1 / scaleX; 13025 SCALE_MATRIX.d = 1 / scaleY; 13026 const newPath = new Path2D(); 13027 newPath.addPath(path, SCALE_MATRIX); 13028 if (dashes.length > 0) { 13029 const scale = Math.max(scaleX, scaleY); 13030 ctx.setLineDash(dashes.map(x => x / scale)); 13031 ctx.lineDashOffset /= scale; 13032 } 13033 ctx.lineWidth = lineWidth || 1; 13034 ctx.stroke(newPath); 13035 if (saveRestore) { 13036 ctx.restore(); 13037 } 13038 } 13039 isContentVisible() { 13040 for (let i = this.markedContentStack.length - 1; i >= 0; i--) { 13041 if (!this.markedContentStack[i].visible) { 13042 return false; 13043 } 13044 } 13045 return true; 13046 } 13047} 13048for (const op in OPS) { 13049 if (CanvasGraphics.prototype[op] !== undefined) { 13050 CanvasGraphics.prototype[OPS[op]] = CanvasGraphics.prototype[op]; 13051 } 13052} 13053 13054;// ./src/shared/base_pdf_stream.js 13055 13056class BasePDFStream { 13057 #PDFStreamReader = null; 13058 #PDFStreamRangeReader = null; 13059 _fullReader = null; 13060 _rangeReaders = new Set(); 13061 _source = null; 13062 constructor(source, PDFStreamReader, PDFStreamRangeReader) { 13063 this._source = source; 13064 this.#PDFStreamReader = PDFStreamReader; 13065 this.#PDFStreamRangeReader = PDFStreamRangeReader; 13066 } 13067 get _progressiveDataLength() { 13068 return this._fullReader?._loaded ?? 0; 13069 } 13070 getFullReader() { 13071 assert(!this._fullReader, "BasePDFStream.getFullReader can only be called once."); 13072 return this._fullReader = new this.#PDFStreamReader(this); 13073 } 13074 getRangeReader(begin, end) { 13075 if (end <= this._progressiveDataLength) { 13076 return null; 13077 } 13078 const reader = new this.#PDFStreamRangeReader(this, begin, end); 13079 this._rangeReaders.add(reader); 13080 return reader; 13081 } 13082 cancelAllRequests(reason) { 13083 this._fullReader?.cancel(reason); 13084 for (const reader of new Set(this._rangeReaders)) { 13085 reader.cancel(reason); 13086 } 13087 } 13088} 13089class BasePDFStreamReader { 13090 onProgress = null; 13091 _contentLength = 0; 13092 _filename = null; 13093 _headersCapability = Promise.withResolvers(); 13094 _isRangeSupported = false; 13095 _isStreamingSupported = false; 13096 _loaded = 0; 13097 _stream = null; 13098 constructor(stream) { 13099 this._stream = stream; 13100 } 13101 _callOnProgress() { 13102 this.onProgress?.({ 13103 loaded: this._loaded, 13104 total: this._contentLength 13105 }); 13106 } 13107 get headersReady() { 13108 return this._headersCapability.promise; 13109 } 13110 get filename() { 13111 return this._filename; 13112 } 13113 get contentLength() { 13114 return this._contentLength; 13115 } 13116 get isRangeSupported() { 13117 return this._isRangeSupported; 13118 } 13119 get isStreamingSupported() { 13120 return this._isStreamingSupported; 13121 } 13122 async read() { 13123 unreachable("Abstract method `read` called"); 13124 } 13125 cancel(reason) { 13126 unreachable("Abstract method `cancel` called"); 13127 } 13128} 13129class BasePDFStreamRangeReader { 13130 _stream = null; 13131 constructor(stream, begin, end) { 13132 this._stream = stream; 13133 } 13134 async read() { 13135 unreachable("Abstract method `read` called"); 13136 } 13137 cancel(reason) { 13138 unreachable("Abstract method `cancel` called"); 13139 } 13140} 13141 13142;// ./src/display/content_disposition.js 13143 13144function getFilenameFromContentDispositionHeader(contentDisposition) { 13145 let needsEncodingFixup = true; 13146 let tmp = toParamRegExp("filename\\*", "i").exec(contentDisposition); 13147 if (tmp) { 13148 tmp = tmp[1]; 13149 let filename = rfc2616unquote(tmp); 13150 filename = unescape(filename); 13151 filename = rfc5987decode(filename); 13152 filename = rfc2047decode(filename); 13153 return fixupEncoding(filename); 13154 } 13155 tmp = rfc2231getparam(contentDisposition); 13156 if (tmp) { 13157 const filename = rfc2047decode(tmp); 13158 return fixupEncoding(filename); 13159 } 13160 tmp = toParamRegExp("filename", "i").exec(contentDisposition); 13161 if (tmp) { 13162 tmp = tmp[1]; 13163 let filename = rfc2616unquote(tmp); 13164 filename = rfc2047decode(filename); 13165 return fixupEncoding(filename); 13166 } 13167 function toParamRegExp(attributePattern, flags) { 13168 return new RegExp("(?:^|;)\\s*" + attributePattern + "\\s*=\\s*" + "(" + '[^";\\s][^;\\s]*' + "|" + '"(?:[^"\\\\]|\\\\"?)+"?' + ")", flags); 13169 } 13170 function textdecode(encoding, value) { 13171 if (encoding) { 13172 if (!/^[\x00-\xFF]+$/.test(value)) { 13173 return value; 13174 } 13175 try { 13176 const decoder = new TextDecoder(encoding, { 13177 fatal: true 13178 }); 13179 const buffer = stringToBytes(value); 13180 value = decoder.decode(buffer); 13181 needsEncodingFixup = false; 13182 } catch {} 13183 } 13184 return value; 13185 } 13186 function fixupEncoding(value) { 13187 if (needsEncodingFixup && /[\x80-\xff]/.test(value)) { 13188 value = textdecode("utf-8", value); 13189 if (needsEncodingFixup) { 13190 value = textdecode("iso-8859-1", value); 13191 } 13192 } 13193 return value; 13194 } 13195 function rfc2231getparam(contentDispositionStr) { 13196 const matches = []; 13197 let match; 13198 const iter = toParamRegExp("filename\\*((?!0\\d)\\d+)(\\*?)", "ig"); 13199 while ((match = iter.exec(contentDispositionStr)) !== null) { 13200 let [, n, quot, part] = match; 13201 n = parseInt(n, 10); 13202 if (n in matches) { 13203 if (n === 0) { 13204 break; 13205 } 13206 continue; 13207 } 13208 matches[n] = [quot, part]; 13209 } 13210 const parts = []; 13211 for (let n = 0; n < matches.length; ++n) { 13212 if (!(n in matches)) { 13213 break; 13214 } 13215 let [quot, part] = matches[n]; 13216 part = rfc2616unquote(part); 13217 if (quot) { 13218 part = unescape(part); 13219 if (n === 0) { 13220 part = rfc5987decode(part); 13221 } 13222 } 13223 parts.push(part); 13224 } 13225 return parts.join(""); 13226 } 13227 function rfc2616unquote(value) { 13228 if (value.startsWith('"')) { 13229 const parts = value.slice(1).split('\\"'); 13230 for (let i = 0; i < parts.length; ++i) { 13231 const quotindex = parts[i].indexOf('"'); 13232 if (quotindex !== -1) { 13233 parts[i] = parts[i].slice(0, quotindex); 13234 parts.length = i + 1; 13235 } 13236 parts[i] = parts[i].replaceAll(/\\(.)/g, "$1"); 13237 } 13238 value = parts.join('"'); 13239 } 13240 return value; 13241 } 13242 function rfc5987decode(extvalue) { 13243 const encodingend = extvalue.indexOf("'"); 13244 if (encodingend === -1) { 13245 return extvalue; 13246 } 13247 const encoding = extvalue.slice(0, encodingend); 13248 const langvalue = extvalue.slice(encodingend + 1); 13249 const value = langvalue.replace(/^[^']*'/, ""); 13250 return textdecode(encoding, value); 13251 } 13252 function rfc2047decode(value) { 13253 if (!value.startsWith("=?") || /[\x00-\x19\x80-\xff]/.test(value)) { 13254 return value; 13255 } 13256 return value.replaceAll(/=\?([\w-]*)\?([QB])\?((?:[^?]|\?(?!=))*)\?=/gi, function (matches, charset, encoding, text) { 13257 if (encoding === "q" || encoding === "Q") { 13258 text = text.replaceAll("_", " "); 13259 text = text.replaceAll(/=([0-9a-f]{2})/gi, function (match, hex) { 13260 return String.fromCharCode(parseInt(hex, 16)); 13261 }); 13262 return textdecode(charset, text); 13263 } 13264 try { 13265 text = atob(text); 13266 } catch {} 13267 return textdecode(charset, text); 13268 }); 13269 } 13270 return ""; 13271} 13272 13273;// ./src/display/network_utils.js 13274 13275 13276 13277function createHeaders(isHttp, httpHeaders) { 13278 const headers = new Headers(); 13279 if (!isHttp || !httpHeaders || typeof httpHeaders !== "object") { 13280 return headers; 13281 } 13282 for (const key in httpHeaders) { 13283 const val = httpHeaders[key]; 13284 if (val !== undefined) { 13285 headers.append(key, val); 13286 } 13287 } 13288 return headers
vendor: 15,331 bytes, lines 13288-13838
13288; 13289} 13290function getResponseOrigin(url) { 13291 return URL.parse(url)?.origin ?? null; 13292} 13293function validateRangeRequestCapabilities({ 13294 responseHeaders, 13295 isHttp, 13296 rangeChunkSize, 13297 disableRange 13298}) { 13299 const rv = { 13300 contentLength: 0, 13301 isRangeSupported: false 13302 }; 13303 const length = parseInt(responseHeaders.get("Content-Length"), 10); 13304 if (!Number.isInteger(length)) { 13305 return rv; 13306 } 13307 rv.contentLength = length; 13308 if (length <= 2 * rangeChunkSize) { 13309 return rv; 13310 } 13311 if (disableRange || !isHttp) { 13312 return rv; 13313 } 13314 if (responseHeaders.get("Accept-Ranges") !== "bytes") { 13315 return rv; 13316 } 13317 const contentEncoding = responseHeaders.get("Content-Encoding") || "identity"; 13318 if (contentEncoding === "identity") { 13319 rv.isRangeSupported = true; 13320 } 13321 return rv; 13322} 13323function extractFilenameFromHeader(responseHeaders) { 13324 const contentDisposition = responseHeaders.get("Content-Disposition"); 13325 if (contentDisposition) { 13326 let filename = getFilenameFromContentDispositionHeader(contentDisposition); 13327 if (filename.includes("%")) { 13328 try { 13329 filename = decodeURIComponent(filename); 13330 } catch {} 13331 } 13332 if (isPdfFile(filename)) { 13333 return filename; 13334 } 13335 } 13336 return null; 13337} 13338function createResponseError(status, url) { 13339 return new ResponseException(`Unexpected server response (${status}) while retrieving PDF "${url.href}".`, status, status === 404 || status === 0 && url.protocol === "file:"); 13340} 13341function ensureResponseOrigin(rangeOrigin, origin) { 13342 if (rangeOrigin !== origin) { 13343 throw new Error(`Expected range response-origin "${rangeOrigin}" to match "${origin}".`); 13344 } 13345} 13346 13347;// ./src/display/fetch_stream.js 13348 13349 13350 13351function fetchUrl(url, headers, withCredentials, abortController) { 13352 return fetch(url, { 13353 method: "GET", 13354 headers, 13355 signal: abortController.signal, 13356 mode: "cors", 13357 credentials: withCredentials ? "include" : "same-origin", 13358 redirect: "follow" 13359 }); 13360} 13361function ensureResponseStatus(status, url) { 13362 if (status !== 200 && status !== 206) { 13363 throw createResponseError(status, url); 13364 } 13365} 13366function getArrayBuffer(val) { 13367 if (val instanceof Uint8Array) { 13368 return val.buffer; 13369 } 13370 if (val instanceof ArrayBuffer) { 13371 return val; 13372 } 13373 throw new Error(`getArrayBuffer - unexpected data: ${val}`); 13374} 13375class PDFFetchStream extends BasePDFStream { 13376 _responseOrigin = null; 13377 constructor(source) { 13378 super(source, PDFFetchStreamReader, PDFFetchStreamRangeReader); 13379 const { 13380 httpHeaders, 13381 url 13382 } = source; 13383 assert(/https?:/.test(url.protocol), "PDFFetchStream only supports http(s):// URLs."); 13384 this.headers = createHeaders(true, httpHeaders); 13385 } 13386} 13387class PDFFetchStreamReader extends BasePDFStreamReader { 13388 _abortController = new AbortController(); 13389 _reader = null; 13390 constructor(stream) { 13391 super(stream); 13392 const { 13393 disableRange, 13394 disableStream, 13395 rangeChunkSize, 13396 url, 13397 withCredentials 13398 } = stream._source; 13399 this._isStreamingSupported = !disableStream; 13400 const headers = new Headers(stream.headers); 13401 fetchUrl(url, headers, withCredentials, this._abortController).then(response => { 13402 stream._responseOrigin = getResponseOrigin(response.url); 13403 ensureResponseStatus(response.status, url); 13404 this._reader = response.body.getReader(); 13405 const responseHeaders = response.headers; 13406 const { 13407 contentLength, 13408 isRangeSupported 13409 } = validateRangeRequestCapabilities({ 13410 responseHeaders, 13411 isHttp: true, 13412 rangeChunkSize, 13413 disableRange 13414 }); 13415 this._contentLength = contentLength; 13416 this._isRangeSupported = isRangeSupported; 13417 this._filename = extractFilenameFromHeader(responseHeaders); 13418 if (!this._isStreamingSupported && this._isRangeSupported) { 13419 this.cancel(new AbortException("Streaming is disabled.")); 13420 } 13421 this._headersCapability.resolve(); 13422 }).catch(this._headersCapability.reject); 13423 } 13424 async read() { 13425 await this._headersCapability.promise; 13426 const { 13427 value, 13428 done 13429 } = await this._reader.read(); 13430 if (done) { 13431 return { 13432 value, 13433 done 13434 }; 13435 } 13436 this._loaded += value.byteLength; 13437 this._callOnProgress(); 13438 return { 13439 value: getArrayBuffer(value), 13440 done: false 13441 }; 13442 } 13443 cancel(reason) { 13444 this._reader?.cancel(reason); 13445 this._abortController.abort(); 13446 } 13447} 13448class PDFFetchStreamRangeReader extends BasePDFStreamRangeReader { 13449 _abortController = new AbortController(); 13450 _readCapability = Promise.withResolvers(); 13451 _reader = null; 13452 constructor(stream, begin, end) { 13453 super(stream, begin, end); 13454 const { 13455 url, 13456 withCredentials 13457 } = stream._source; 13458 const headers = new Headers(stream.headers); 13459 headers.append("Range", `bytes=${begin}-${end - 1}`); 13460 fetchUrl(url, headers, withCredentials, this._abortController).then(response => { 13461 const responseOrigin = getResponseOrigin(response.url); 13462 ensureResponseOrigin(responseOrigin, stream._responseOrigin); 13463 ensureResponseStatus(response.status, url); 13464 this._reader = response.body.getReader(); 13465 this._readCapability.resolve(); 13466 }).catch(this._readCapability.reject); 13467 } 13468 async read() { 13469 await this._readCapability.promise; 13470 const { 13471 value, 13472 done 13473 } = await this._reader.read(); 13474 if (done) { 13475 return { 13476 value, 13477 done 13478 }; 13479 } 13480 return { 13481 value: getArrayBuffer(value), 13482 done: false 13483 }; 13484 } 13485 cancel(reason) { 13486 this._reader?.cancel(reason); 13487 this._abortController.abort(); 13488 } 13489} 13490 13491;// ./src/display/transport_stream.js 13492 13493 13494 13495function transport_stream_getArrayBuffer(val) { 13496 return val instanceof Uint8Array && val.byteLength === val.buffer.byteLength ? val.buffer : new Uint8Array(val).buffer; 13497} 13498function endRequests() { 13499 for (const capability of this._requests) { 13500 capability.resolve({ 13501 value: undefined, 13502 done: true 13503 }); 13504 } 13505 this._requests.length = 0; 13506} 13507class PDFDataTransportStream extends BasePDFStream { 13508 _progressiveDone = false; 13509 _queuedChunks = []; 13510 constructor(source) { 13511 super(source, PDFDataTransportStreamReader, PDFDataTransportStreamRangeReader); 13512 const { 13513 pdfDataRangeTransport 13514 } = source; 13515 const { 13516 initialData, 13517 progressiveDone 13518 } = pdfDataRangeTransport; 13519 if (initialData?.length > 0) { 13520 const buffer = transport_stream_getArrayBuffer(initialData); 13521 this._queuedChunks.push(buffer); 13522 } 13523 this._progressiveDone = progressiveDone; 13524 const listener = args => { 13525 switch (args.type) { 13526 case "range": 13527 case "progressiveRead": 13528 this.#onReceiveData(args.begin, args.chunk); 13529 break; 13530 case "progressiveDone": 13531 this._fullReader?.progressiveDone(); 13532 this._progressiveDone = true; 13533 break; 13534 } 13535 }; 13536 pdfDataRangeTransport.transportReady(listener); 13537 } 13538 #onReceiveData(begin, chunk) { 13539 const buffer = transport_stream_getArrayBuffer(chunk); 13540 if (begin === undefined) { 13541 if (this._fullReader) { 13542 this._fullReader._enqueue(buffer); 13543 } else { 13544 this._queuedChunks.push(buffer); 13545 } 13546 } else { 13547 const rangeReader = this._rangeReaders.keys().find(r => r._begin === begin); 13548 assert(rangeReader, "#onReceiveData - no `PDFDataTransportStreamRangeReader` instance found."); 13549 rangeReader._enqueue(buffer); 13550 } 13551 } 13552 getFullReader() { 13553 const reader = super.getFullReader(); 13554 this._queuedChunks = null; 13555 return reader; 13556 } 13557 getRangeReader(begin, end) { 13558 const reader = super.getRangeReader(begin, end); 13559 if (reader) { 13560 reader.onDone = () => this._rangeReaders.delete(reader); 13561 this._source.pdfDataRangeTransport.requestDataRange(begin, end); 13562 } 13563 return reader; 13564 } 13565 cancelAllRequests(reason) { 13566 super.cancelAllRequests(reason); 13567 this._source.pdfDataRangeTransport.abort(); 13568 } 13569} 13570class PDFDataTransportStreamReader extends BasePDFStreamReader { 13571 #endRequests = endRequests.bind(this); 13572 _done = false; 13573 _queuedChunks = null; 13574 _requests = []; 13575 constructor(stream) { 13576 super(stream); 13577 const { 13578 pdfDataRangeTransport, 13579 disableRange, 13580 disableStream 13581 } = stream._source; 13582 const { 13583 length, 13584 contentDispositionFilename 13585 } = pdfDataRangeTransport; 13586 this._queuedChunks = stream._queuedChunks || []; 13587 for (const chunk of this._queuedChunks) { 13588 this._loaded += chunk.byteLength; 13589 } 13590 this._done = stream._progressiveDone; 13591 this._contentLength = length; 13592 this._isStreamingSupported = !disableStream; 13593 this._isRangeSupported = !disableRange; 13594 if (isPdfFile(contentDispositionFilename)) { 13595 this._filename = contentDispositionFilename; 13596 } 13597 this._headersCapability.resolve(); 13598 const loaded = this._loaded; 13599 Promise.resolve().then(() => { 13600 if (loaded > 0 && this._loaded === loaded) { 13601 this._callOnProgress(); 13602 } 13603 }); 13604 } 13605 _enqueue(chunk) { 13606 if (this._done) { 13607 return; 13608 } 13609 if (this._requests.length > 0) { 13610 const capability = this._requests.shift(); 13611 capability.resolve({ 13612 value: chunk, 13613 done: false 13614 }); 13615 } else { 13616 this._queuedChunks.push(chunk); 13617 } 13618 this._loaded += chunk.byteLength; 13619 this._callOnProgress(); 13620 } 13621 async read() { 13622 if (this._queuedChunks.length > 0) { 13623 const chunk = this._queuedChunks.shift(); 13624 return { 13625 value: chunk, 13626 done: false 13627 }; 13628 } 13629 if (this._done) { 13630 return { 13631 value: undefined, 13632 done: true 13633 }; 13634 } 13635 const capability = Promise.withResolvers(); 13636 this._requests.push(capability); 13637 return capability.promise; 13638 } 13639 cancel(reason) { 13640 this._done = true; 13641 this.#endRequests(); 13642 } 13643 progressiveDone() { 13644 this._done ||= true; 13645 if (this._queuedChunks.length === 0) { 13646 this.#endRequests(); 13647 } 13648 } 13649} 13650class PDFDataTransportStreamRangeReader extends BasePDFStreamRangeReader { 13651 #endRequests = endRequests.bind(this); 13652 onDone = null; 13653 _begin = -1; 13654 _done = false; 13655 _queuedChunk = null; 13656 _requests = []; 13657 constructor(stream, begin, end) { 13658 super(stream, begin, end); 13659 this._begin = begin; 13660 } 13661 _enqueue(chunk) { 13662 if (this._done) { 13663 return; 13664 } 13665 if (this._requests.length === 0) { 13666 this._queuedChunk = chunk; 13667 } else { 13668 const capability = this._requests.shift(); 13669 capability.resolve({ 13670 value: chunk, 13671 done: false 13672 }); 13673 this.#endRequests(); 13674 } 13675 this._done = true; 13676 this.onDone?.(); 13677 } 13678 async read() { 13679 if (this._queuedChunk) { 13680 const chunk = this._queuedChunk; 13681 this._queuedChunk = null; 13682 return { 13683 value: chunk, 13684 done: false 13685 }; 13686 } 13687 if (this._done) { 13688 return { 13689 value: undefined, 13690 done: true 13691 }; 13692 } 13693 const capability = Promise.withResolvers(); 13694 this._requests.push(capability); 13695 return capability.promise; 13696 } 13697 cancel(reason) { 13698 this._done = true; 13699 this.#endRequests(); 13700 this.onDone?.(); 13701 } 13702} 13703 13704;// ./src/display/network.js 13705 13706 13707 13708 13709const OK_RESPONSE = 200; 13710const PARTIAL_CONTENT_RESPONSE = 206; 13711function network_getArrayBuffer(val) { 13712 return typeof val !== "string" ? val : stringToBytes(val).buffer; 13713} 13714class PDFNetworkStream extends BasePDFStream { 13715 #pendingRequests = new WeakMap(); 13716 _responseOrigin = null; 13717 constructor(source) { 13718 super(source, PDFNetworkStreamReader, PDFNetworkStreamRangeReader); 13719 const { 13720 httpHeaders, 13721 url 13722 } = source; 13723 this.url = url; 13724 this.isHttp = /https?:/.test(url.protocol); 13725 this.headers = createHeaders(this.isHttp, httpHeaders); 13726 } 13727 _request(args) { 13728 const xhr = new XMLHttpRequest(); 13729 const pendingRequest = { 13730 validateStatus: null, 13731 onHeadersReceived: args.onHeadersReceived, 13732 onDone: args.onDone, 13733 onError: args.onError, 13734 onProgress: args.onProgress 13735 }; 13736 this.#pendingRequests.set(xhr, pendingRequest); 13737 xhr.open("GET", this.url); 13738 xhr.withCredentials = this._source.withCredentials; 13739 for (const [key, val] of this.headers) { 13740 xhr.setRequestHeader(key, val); 13741 } 13742 if (this.isHttp && "begin" in args && "end" in args) { 13743 xhr.setRequestHeader("Range", `bytes=${args.begin}-${args.end - 1}`); 13744 pendingRequest.validateStatus = status => status === PARTIAL_CONTENT_RESPONSE || status === OK_RESPONSE; 13745 } else { 13746 pendingRequest.validateStatus = status => status === OK_RESPONSE; 13747 } 13748 xhr.responseType = "arraybuffer"; 13749 assert(args.onError, "Expected `onError` callback to be provided."); 13750 xhr.onerror = () => args.onError(xhr.status); 13751 xhr.onreadystatechange = this.#onStateChange.bind(this, xhr); 13752 xhr.onprogress = this.#onProgress.bind(this, xhr); 13753 xhr.send(null); 13754 return xhr; 13755 } 13756 #onProgress(xhr, evt) { 13757 const pendingRequest = this.#pendingRequests.get(xhr); 13758 pendingRequest?.onProgress?.(evt); 13759 } 13760 #onStateChange(xhr, evt) { 13761 const pendingRequest = this.#pendingRequests.get(xhr); 13762 if (!pendingRequest) { 13763 return; 13764 } 13765 if (xhr.readyState >= 2 && pendingRequest.onHeadersReceived) { 13766 pendingRequest.onHeadersReceived(); 13767 delete pendingRequest.onHeadersReceived; 13768 } 13769 if (xhr.readyState !== 4) { 13770 return; 13771 } 13772 if (!this.#pendingRequests.has(xhr)) { 13773 return; 13774 } 13775 this.#pendingRequests.delete(xhr); 13776 if (xhr.status === 0 && this.isHttp) { 13777 pendingRequest.onError(xhr.status); 13778 return; 13779 } 13780 const xhrStatus = xhr.status || OK_RESPONSE; 13781 if (!pendingRequest.validateStatus(xhrStatus)) { 13782 pendingRequest.onError(xhr.status); 13783 return; 13784 } 13785 const chunk = network_getArrayBuffer(xhr.response); 13786 if (xhrStatus === PARTIAL_CONTENT_RESPONSE) { 13787 const rangeHeader = xhr.getResponseHeader("Content-Range"); 13788 if (/bytes \d+-\d+\/\d+/.test(rangeHeader)) { 13789 pendingRequest.onDone(chunk); 13790 } else { 13791 warn(`Missing or invalid "Content-Range" header.`); 13792 pendingRequest.onError(0); 13793 } 13794 } else if (chunk) { 13795 pendingRequest.onDone(chunk); 13796 } else { 13797 pendingRequest.onError(xhr.status); 13798 } 13799 } 13800 _abortRequest(xhr) { 13801 if (this.#pendingRequests.has(xhr)) { 13802 this.#pendingRequests.delete(xhr); 13803 xhr.abort(); 13804 } 13805 } 13806 getRangeReader(begin, end) { 13807 const reader = super.getRangeReader(begin, end); 13808 if (reader) { 13809 reader.onClosed = () => this._rangeReaders.delete(reader); 13810 } 13811 return reader; 13812 } 13813} 13814class PDFNetworkStreamReader extends BasePDFStreamReader { 13815 #endRequests = endRequests.bind(this); 13816 _cachedChunks = []; 13817 _done = false; 13818 _requests = []; 13819 _storedError = null; 13820 constructor(stream) { 13821 super(stream); 13822 this._fullRequestXhr = stream._request({ 13823 onHeadersReceived: this.#onHeadersReceived.bind(this), 13824 onDone: this.#onDone.bind(this), 13825 onError: this.#onError.bind(this), 13826 onProgress: this.#onProgress.bind(this) 13827 }); 13828 } 13829 #onHeadersReceived() { 13830 const stream = this._stream; 13831 const { 13832 disableRange, 13833 rangeChunkSize 13834 } = stream._source; 13835 const fullRequestXhr = this._fullRequestXhr; 13836 stream._responseOrigin = getResponseOrigin(fullRequestXhr.responseURL); 13837 const rawResponseHeaders = fullRequestXhr.getAllResponseHeaders(); 13838 const responseHeaders = new Headers(rawResponseHeaders ?
13838rawResponseHeaders.trimStart().replace(/[^\S ]+$/, ""
vendor: 17,038 bytes, lines 13838-14517
13838).split(/[\r\n]+/).map(x => { 13839 const [key, ...val] = x.split(": "); 13840 return [key, val.join(": ")]; 13841 }) : []); 13842 const { 13843 contentLength, 13844 isRangeSupported 13845 } = validateRangeRequestCapabilities({ 13846 responseHeaders, 13847 isHttp: stream.isHttp, 13848 rangeChunkSize, 13849 disableRange 13850 }); 13851 this._contentLength = contentLength; 13852 this._isRangeSupported = isRangeSupported; 13853 this._filename = extractFilenameFromHeader(responseHeaders); 13854 if (this._isRangeSupported) { 13855 stream._abortRequest(fullRequestXhr); 13856 } 13857 this._headersCapability.resolve(); 13858 } 13859 #onDone(chunk) { 13860 if (this._requests.length > 0) { 13861 const capability = this._requests.shift(); 13862 capability.resolve({ 13863 value: chunk, 13864 done: false 13865 }); 13866 } else { 13867 this._cachedChunks.push(chunk); 13868 } 13869 this._done = true; 13870 if (this._cachedChunks.length === 0) { 13871 this.#endRequests(); 13872 } 13873 } 13874 #onError(status) { 13875 this._storedError = createResponseError(status, this._stream.url); 13876 this._headersCapability.reject(this._storedError); 13877 for (const capability of this._requests) { 13878 capability.reject(this._storedError); 13879 } 13880 this._requests.length = 0; 13881 this._cachedChunks.length = 0; 13882 } 13883 #onProgress(evt) { 13884 this.onProgress?.({ 13885 loaded: evt.loaded, 13886 total: evt.lengthComputable ? evt.total : this._contentLength 13887 }); 13888 } 13889 async read() { 13890 await this._headersCapability.promise; 13891 if (this._storedError) { 13892 throw this._storedError; 13893 } 13894 if (this._cachedChunks.length > 0) { 13895 const chunk = this._cachedChunks.shift(); 13896 return { 13897 value: chunk, 13898 done: false 13899 }; 13900 } 13901 if (this._done) { 13902 return { 13903 value: undefined, 13904 done: true 13905 }; 13906 } 13907 const capability = Promise.withResolvers(); 13908 this._requests.push(capability); 13909 return capability.promise; 13910 } 13911 cancel(reason) { 13912 this._done = true; 13913 this._headersCapability.reject(reason); 13914 this.#endRequests(); 13915 this._stream._abortRequest(this._fullRequestXhr); 13916 this._fullRequestXhr = null; 13917 } 13918} 13919class PDFNetworkStreamRangeReader extends BasePDFStreamRangeReader { 13920 #endRequests = endRequests.bind(this); 13921 onClosed = null; 13922 _done = false; 13923 _queuedChunk = null; 13924 _requests = []; 13925 _storedError = null; 13926 constructor(stream, begin, end) { 13927 super(stream, begin, end); 13928 this._requestXhr = stream._request({ 13929 begin, 13930 end, 13931 onHeadersReceived: this.#onHeadersReceived.bind(this), 13932 onDone: this.#onDone.bind(this), 13933 onError: this.#onError.bind(this), 13934 onProgress: null 13935 }); 13936 } 13937 #onHeadersReceived() { 13938 const responseOrigin = getResponseOrigin(this._requestXhr?.responseURL); 13939 try { 13940 ensureResponseOrigin(responseOrigin, this._stream._responseOrigin); 13941 } catch (ex) { 13942 this._storedError = ex; 13943 this.#onError(0); 13944 } 13945 } 13946 #onDone(chunk) { 13947 if (this._requests.length > 0) { 13948 const capability = this._requests.shift(); 13949 capability.resolve({ 13950 value: chunk, 13951 done: false 13952 }); 13953 } else { 13954 this._queuedChunk = chunk; 13955 } 13956 this._done = true; 13957 this.#endRequests(); 13958 this.onClosed?.(); 13959 } 13960 #onError(status) { 13961 this._storedError ??= createResponseError(status, this._stream.url); 13962 for (const capability of this._requests) { 13963 capability.reject(this._storedError); 13964 } 13965 this._requests.length = 0; 13966 this._queuedChunk = null; 13967 } 13968 async read() { 13969 if (this._storedError) { 13970 throw this._storedError; 13971 } 13972 if (this._queuedChunk !== null) { 13973 const chunk = this._queuedChunk; 13974 this._queuedChunk = null; 13975 return { 13976 value: chunk, 13977 done: false 13978 }; 13979 } 13980 if (this._done) { 13981 return { 13982 value: undefined, 13983 done: true 13984 }; 13985 } 13986 const capability = Promise.withResolvers(); 13987 this._requests.push(capability); 13988 return capability.promise; 13989 } 13990 cancel(reason) { 13991 this._done = true; 13992 this.#endRequests(); 13993 this._stream._abortRequest(this._requestXhr); 13994 this.onClosed?.(); 13995 } 13996} 13997 13998;// ./src/display/node_stream.js 13999 14000 14001 14002 14003function getReadableStream(url, opts = null) { 14004 const fs = process.getBuiltinModule("fs"); 14005 const { 14006 Readable 14007 } = process.getBuiltinModule("stream"); 14008 const readStream = fs.createReadStream(url, opts); 14009 return Readable.toWeb(readStream); 14010} 14011class PDFNodeStream extends BasePDFStream { 14012 constructor(source) { 14013 super(source, PDFNodeStreamReader, PDFNodeStreamRangeReader); 14014 const { 14015 url 14016 } = source; 14017 assert(url.protocol === "file:", "PDFNodeStream only supports file:// URLs."); 14018 } 14019} 14020class PDFNodeStreamReader extends BasePDFStreamReader { 14021 _reader = null; 14022 constructor(stream) { 14023 super(stream); 14024 const { 14025 disableRange, 14026 disableStream, 14027 rangeChunkSize, 14028 url 14029 } = stream._source; 14030 this._isStreamingSupported = !disableStream; 14031 const fs = process.getBuiltinModule("fs/promises"); 14032 fs.lstat(url).then(stat => { 14033 const readableStream = getReadableStream(url); 14034 this._reader = readableStream.getReader(); 14035 const { 14036 size 14037 } = stat; 14038 this._contentLength = size; 14039 this._isRangeSupported = !disableRange && size > 2 * rangeChunkSize; 14040 if (!this._isStreamingSupported && this._isRangeSupported) { 14041 this.cancel(new AbortException("Streaming is disabled.")); 14042 } 14043 this._headersCapability.resolve(); 14044 }).catch(error => { 14045 if (error.code === "ENOENT") { 14046 error = createResponseError(0, url); 14047 } 14048 this._headersCapability.reject(error); 14049 }); 14050 } 14051 async read() { 14052 await this._headersCapability.promise; 14053 const { 14054 value, 14055 done 14056 } = await this._reader.read(); 14057 if (done) { 14058 return { 14059 value, 14060 done 14061 }; 14062 } 14063 this._loaded += value.byteLength; 14064 this._callOnProgress(); 14065 return { 14066 value: getArrayBuffer(value), 14067 done: false 14068 }; 14069 } 14070 cancel(reason) { 14071 this._reader?.cancel(reason); 14072 } 14073} 14074class PDFNodeStreamRangeReader extends BasePDFStreamRangeReader { 14075 _readCapability = Promise.withResolvers(); 14076 _reader = null; 14077 constructor(stream, begin, end) { 14078 super(stream, begin, end); 14079 const { 14080 url 14081 } = stream._source; 14082 try { 14083 const readableStream = getReadableStream(url, { 14084 start: begin, 14085 end: end - 1 14086 }); 14087 this._reader = readableStream.getReader(); 14088 this._readCapability.resolve(); 14089 } catch (error) { 14090 this._readCapability.reject(error); 14091 } 14092 } 14093 async read() { 14094 await this._readCapability.promise; 14095 const { 14096 value, 14097 done 14098 } = await this._reader.read(); 14099 if (done) { 14100 return { 14101 value, 14102 done 14103 }; 14104 } 14105 return { 14106 value: getArrayBuffer(value), 14107 done: false 14108 }; 14109 } 14110 cancel(reason) { 14111 this._reader?.cancel(reason); 14112 } 14113} 14114 14115;// ./src/display/network_stream.js 14116 14117 14118 14119 14120 14121function getNetworkStream(url) { 14122 return isValidFetchUrl(url) ? PDFFetchStream : isNodeJS ? PDFNodeStream : PDFNetworkStream; 14123} 14124 14125;// ./src/display/worker_options.js 14126class GlobalWorkerOptions { 14127 static #port = null; 14128 static #src = ""; 14129 static get workerPort() { 14130 return this.#port; 14131 } 14132 static set workerPort(val) { 14133 if (!(typeof Worker !== "undefined" && val instanceof Worker) && val !== null) { 14134 throw new Error("Invalid `workerPort` type."); 14135 } 14136 this.#port = val; 14137 } 14138 static get workerSrc() { 14139 return this.#src; 14140 } 14141 static set workerSrc(val) { 14142 if (typeof val !== "string") { 14143 throw new Error("Invalid `workerSrc` type."); 14144 } 14145 this.#src = val; 14146 } 14147} 14148 14149;// ./src/display/metadata.js 14150class Metadata { 14151 #map; 14152 #data; 14153 constructor({ 14154 parsedData, 14155 rawData 14156 }) { 14157 this.#map = parsedData; 14158 this.#data = rawData; 14159 } 14160 getRaw() { 14161 return this.#data; 14162 } 14163 get(name) { 14164 return this.#map.get(name) ?? null; 14165 } 14166 [Symbol.iterator]() { 14167 return this.#map.entries(); 14168 } 14169} 14170 14171;// ./src/display/optional_content_config.js 14172 14173 14174const INTERNAL = Symbol("INTERNAL"); 14175class OptionalContentGroup { 14176 #isDisplay = false; 14177 #isPrint = false; 14178 #userSet = false; 14179 #visible = true; 14180 constructor(renderingIntent, { 14181 name, 14182 intent, 14183 usage, 14184 rbGroups 14185 }) { 14186 this.#isDisplay = !!(renderingIntent & RenderingIntentFlag.DISPLAY); 14187 this.#isPrint = !!(renderingIntent & RenderingIntentFlag.PRINT); 14188 this.name = name; 14189 this.intent = intent; 14190 this.usage = usage; 14191 this.rbGroups = rbGroups; 14192 } 14193 get visible() { 14194 if (this.#userSet) { 14195 return this.#visible; 14196 } 14197 if (!this.#visible) { 14198 return false; 14199 } 14200 const { 14201 print, 14202 view 14203 } = this.usage; 14204 if (this.#isDisplay) { 14205 return view?.viewState !== "OFF"; 14206 } else if (this.#isPrint) { 14207 return print?.printState !== "OFF"; 14208 } 14209 return true; 14210 } 14211 _setVisible(internal, visible, userSet = false) { 14212 if (internal !== INTERNAL) { 14213 unreachable("Internal method `_setVisible` called."); 14214 } 14215 this.#userSet = userSet; 14216 this.#visible = visible; 14217 } 14218 get serializable() { 14219 return { 14220 userSet: this.#userSet, 14221 visible: this.#visible 14222 }; 14223 } 14224} 14225class OptionalContentConfig { 14226 #cachedGetHash = null; 14227 #groups = new Map(); 14228 #initialHash = null; 14229 #order = null; 14230 #rawData; 14231 creator = null; 14232 name = null; 14233 constructor(data, renderingIntent = RenderingIntentFlag.DISPLAY, groupState = null) { 14234 this.#rawData = data; 14235 this.renderingIntent = renderingIntent; 14236 if (data === null) { 14237 return; 14238 } 14239 this.name = data.name; 14240 this.creator = data.creator; 14241 this.#order = data.order; 14242 for (const group of data.groups) { 14243 this.#groups.set(group.id, new OptionalContentGroup(renderingIntent, group)); 14244 } 14245 if (groupState) { 14246 if (groupState.size !== this.#groups.size) { 14247 unreachable("Incorrect serialized groupState."); 14248 } 14249 for (const [id, group] of groupState) { 14250 this.#groups.get(id)._setVisible(INTERNAL, group.visible, group.userSet); 14251 } 14252 } else { 14253 if (data.baseState === "OFF") { 14254 for (const group of this.#groups.values()) { 14255 group._setVisible(INTERNAL, false); 14256 } 14257 } 14258 for (const on of data.on) { 14259 this.#groups.get(on)._setVisible(INTERNAL, true); 14260 } 14261 for (const off of data.off) { 14262 this.#groups.get(off)._setVisible(INTERNAL, false); 14263 } 14264 } 14265 this.#initialHash = this.getHash(); 14266 } 14267 #evaluateVisibilityExpression(array) { 14268 const length = array.length; 14269 if (length < 2) { 14270 return true; 14271 } 14272 const operator = array[0]; 14273 for (let i = 1; i < length; i++) { 14274 const element = array[i]; 14275 let state; 14276 if (Array.isArray(element)) { 14277 state = this.#evaluateVisibilityExpression(element); 14278 } else if (this.#groups.has(element)) { 14279 state = this.#groups.get(element).visible; 14280 } else { 14281 warn(`Optional content group not found: ${element}`); 14282 return true; 14283 } 14284 switch (operator) { 14285 case "And": 14286 if (!state) { 14287 return false; 14288 } 14289 break; 14290 case "Or": 14291 if (state) { 14292 return true; 14293 } 14294 break; 14295 case "Not": 14296 return !state; 14297 default: 14298 return true; 14299 } 14300 } 14301 return operator === "And"; 14302 } 14303 isVisible(group) { 14304 if (this.#groups.size === 0) { 14305 return true; 14306 } 14307 if (!group) { 14308 info("Optional content group not defined."); 14309 return true; 14310 } 14311 if (group.type === "OCG") { 14312 if (!this.#groups.has(group.id)) { 14313 warn(`Optional content group not found: ${group.id}`); 14314 return true; 14315 } 14316 return this.#groups.get(group.id).visible; 14317 } else if (group.type === "OCMD") { 14318 if (group.expression) { 14319 return this.#evaluateVisibilityExpression(group.expression); 14320 } 14321 if (!group.policy || group.policy === "AnyOn") { 14322 for (const id of group.ids) { 14323 if (!this.#groups.has(id)) { 14324 warn(`Optional content group not found: ${id}`); 14325 return true; 14326 } 14327 if (this.#groups.get(id).visible) { 14328 return true; 14329 } 14330 } 14331 return false; 14332 } else if (group.policy === "AllOn") { 14333 for (const id of group.ids) { 14334 if (!this.#groups.has(id)) { 14335 warn(`Optional content group not found: ${id}`); 14336 return true; 14337 } 14338 if (!this.#groups.get(id).visible) { 14339 return false; 14340 } 14341 } 14342 return true; 14343 } else if (group.policy === "AnyOff") { 14344 for (const id of group.ids) { 14345 if (!this.#groups.has(id)) { 14346 warn(`Optional content group not found: ${id}`); 14347 return true; 14348 } 14349 if (!this.#groups.get(id).visible) { 14350 return true; 14351 } 14352 } 14353 return false; 14354 } else if (group.policy === "AllOff") { 14355 for (const id of group.ids) { 14356 if (!this.#groups.has(id)) { 14357 warn(`Optional content group not found: ${id}`); 14358 return true; 14359 } 14360 if (this.#groups.get(id).visible) { 14361 return false; 14362 } 14363 } 14364 return true; 14365 } 14366 warn(`Unknown optional content policy ${group.policy}.`); 14367 return true; 14368 } 14369 warn(`Unknown group type ${group.type}.`); 14370 return true; 14371 } 14372 setVisibility(id, visible = true, preserveRB = true) { 14373 const group = this.#groups.get(id); 14374 if (!group) { 14375 warn(`Optional content group not found: ${id}`); 14376 return; 14377 } 14378 if (preserveRB && visible && group.rbGroups.length) { 14379 for (const rbGroup of group.rbGroups) { 14380 for (const otherId of rbGroup) { 14381 if (otherId !== id) { 14382 this.#groups.get(otherId)?._setVisible(INTERNAL, false, true); 14383 } 14384 } 14385 } 14386 } 14387 group._setVisible(INTERNAL, !!visible, true); 14388 this.#cachedGetHash = null; 14389 } 14390 setOCGState({ 14391 state, 14392 preserveRB 14393 }) { 14394 let operator; 14395 for (const elem of state) { 14396 switch (elem) { 14397 case "ON": 14398 case "OFF": 14399 case "Toggle": 14400 operator = elem; 14401 continue; 14402 } 14403 const group = this.#groups.get(elem); 14404 if (!group) { 14405 continue; 14406 } 14407 switch (operator) { 14408 case "ON": 14409 this.setVisibility(elem, true, preserveRB); 14410 break; 14411 case "OFF": 14412 this.setVisibility(elem, false, preserveRB); 14413 break; 14414 case "Toggle": 14415 this.setVisibility(elem, !group.visible, preserveRB); 14416 break; 14417 } 14418 } 14419 this.#cachedGetHash = null; 14420 } 14421 get hasInitialVisibility() { 14422 return this.#initialHash === null || this.getHash() === this.#initialHash; 14423 } 14424 getOrder() { 14425 if (!this.#groups.size) { 14426 return null; 14427 } 14428 if (this.#order) { 14429 return this.#order.slice(); 14430 } 14431 return [...this.#groups.keys()]; 14432 } 14433 getGroup(id) { 14434 return this.#groups.get(id) || null; 14435 } 14436 getHash() { 14437 if (this.#cachedGetHash !== null) { 14438 return this.#cachedGetHash; 14439 } 14440 const hash = new MurmurHash3_64(); 14441 for (const [id, group] of this.#groups) { 14442 hash.update(`${id}:${group.visible}`); 14443 } 14444 return this.#cachedGetHash = hash.hexdigest(); 14445 } 14446 [Symbol.iterator]() { 14447 return this.#groups.entries(); 14448 } 14449 get serializable() { 14450 const groupState = new Map(); 14451 for (const [id, group] of this.#groups) { 14452 groupState.set(id, group.serializable); 14453 } 14454 return { 14455 data: this.#rawData, 14456 renderingIntent: this.renderingIntent, 14457 groupState 14458 }; 14459 } 14460 static fromSerializable({ 14461 data, 14462 renderingIntent, 14463 groupState 14464 }) { 14465 return new OptionalContentConfig(data, renderingIntent, groupState); 14466 } 14467} 14468 14469;// ./src/display/pages_mapper.js 14470 14471 14472class PagesMapper { 14473 #pageNumberToId = null; 14474 #prevPageNumbers = null; 14475 #pagesNumber = 0; 14476 #clipboard = null; 14477 #savedData = null; 14478 get pagesNumber() { 14479 return this.#pagesNumber; 14480 } 14481 set pagesNumber(n) { 14482 if (this.#pagesNumber === n) { 14483 return; 14484 } 14485 this.#pagesNumber = n; 14486 this.#pageNumberToId = null; 14487 this.#prevPageNumbers = null; 14488 } 14489 #ensureInit() { 14490 if (this.#pageNumberToId) { 14491 return; 14492 } 14493 const n = this.#pagesNumber; 14494 const pageNumberToId = this.#pageNumberToId = new Uint32Array(n); 14495 for (let i = 0; i < n; i++) { 14496 pageNumberToId[i] = i + 1; 14497 } 14498 this.#prevPageNumbers = new Int32Array(pageNumberToId); 14499 } 14500 #buildIdToPageNumber() { 14501 const idToPageNumber = new Map(); 14502 const pageNumberToId = this.#pageNumberToId; 14503 for (let i = 0, ii = this.#pagesNumber; i < ii; i++) { 14504 const id = pageNumberToId[i]; 14505 const pageNumbers = idToPageNumber.get(id); 14506 if (pageNumbers) { 14507 pageNumbers.push(i + 1); 14508 } else { 14509 idToPageNumber.set(id, [i + 1]); 14510 } 14511 } 14512 return idToPageNumber; 14513 } 14514 movePages(selectedPages, pagesToMove, index) { 14515 this.#ensureInit(); 14516 const pageNumberToId = this.#pageNumberToId; 14517 const
vendor: 12,885 bytes, lines 14517-14923
14517prevIdToPageNumber = this.#buildIdToPageNumber(); 14518 const movedCount = pagesToMove.length; 14519 const mappedPagesToMove = new Uint32Array(movedCount); 14520 let removedBeforeTarget = 0; 14521 for (let i = 0; i < movedCount; i++) { 14522 const pageIndex = pagesToMove[i] - 1; 14523 mappedPagesToMove[i] = pageNumberToId[pageIndex]; 14524 if (pageIndex < index) { 14525 removedBeforeTarget++; 14526 } 14527 } 14528 const pagesNumber = this.#pagesNumber; 14529 const remainingLen = pagesNumber - movedCount; 14530 const adjustedTarget = MathClamp(index - removedBeforeTarget, 0, remainingLen); 14531 for (let i = 0, r = 0; i < pagesNumber; i++) { 14532 if (!selectedPages.has(i + 1)) { 14533 pageNumberToId[r++] = pageNumberToId[i]; 14534 } 14535 } 14536 pageNumberToId.copyWithin(adjustedTarget + movedCount, adjustedTarget, remainingLen); 14537 pageNumberToId.set(mappedPagesToMove, adjustedTarget); 14538 this.#updatePrevPageNumbers(prevIdToPageNumber); 14539 if (pageNumberToId.every((id, i) => id === i + 1)) { 14540 this.#pageNumberToId = null; 14541 } 14542 } 14543 deletePages(pagesToDelete) { 14544 this.#ensureInit(); 14545 const pageNumberToId = this.#pageNumberToId; 14546 const prevIdToPageNumber = this.#buildIdToPageNumber(); 14547 this.#savedData = { 14548 pageNumberToId: pageNumberToId.slice(), 14549 pagesNumber: this.#pagesNumber, 14550 prevPageNumbers: this.#prevPageNumbers.slice() 14551 }; 14552 const newN = this.#pagesNumber - pagesToDelete.length; 14553 this.#pagesNumber = newN; 14554 const newPageNumberToId = this.#pageNumberToId = new Uint32Array(newN); 14555 this.#prevPageNumbers = new Int32Array(newN); 14556 let sourceIndex = 0; 14557 let destIndex = 0; 14558 for (const pageNumber of pagesToDelete) { 14559 const pageIndex = pageNumber - 1; 14560 if (pageIndex !== sourceIndex) { 14561 newPageNumberToId.set(pageNumberToId.subarray(sourceIndex, pageIndex), destIndex); 14562 destIndex += pageIndex - sourceIndex; 14563 } 14564 sourceIndex = pageIndex + 1; 14565 } 14566 if (sourceIndex < pageNumberToId.length) { 14567 newPageNumberToId.set(pageNumberToId.subarray(sourceIndex), destIndex); 14568 } 14569 this.#updatePrevPageNumbers(prevIdToPageNumber, new Set(pagesToDelete)); 14570 } 14571 cancelDelete() { 14572 if (this.#savedData) { 14573 this.#pageNumberToId = this.#savedData.pageNumberToId; 14574 this.#pagesNumber = this.#savedData.pagesNumber; 14575 this.#prevPageNumbers = this.#savedData.prevPageNumbers; 14576 this.#savedData = null; 14577 } 14578 } 14579 cleanSavedData() { 14580 this.#savedData = null; 14581 } 14582 copyPages(pagesToCopy) { 14583 this.#ensureInit(); 14584 this.#clipboard = { 14585 pageNumbers: pagesToCopy, 14586 pageIds: pagesToCopy.map(n => this.#pageNumberToId[n - 1]) 14587 }; 14588 } 14589 cancelCopy() { 14590 this.#clipboard = null; 14591 } 14592 pastePages(index) { 14593 this.#ensureInit(); 14594 const pageNumberToId = this.#pageNumberToId; 14595 const prevIdToPageNumber = this.#buildIdToPageNumber(); 14596 const { 14597 pageNumbers: copiedPageNumbers, 14598 pageIds: copiedPageIds 14599 } = this.#clipboard; 14600 const newN = this.#pagesNumber + copiedPageNumbers.length; 14601 this.#pagesNumber = newN; 14602 const newPageNumberToId = this.#pageNumberToId = new Uint32Array(newN); 14603 this.#prevPageNumbers = new Int32Array(newN); 14604 newPageNumberToId.set(pageNumberToId.subarray(0, index), 0); 14605 newPageNumberToId.set(copiedPageIds, index); 14606 newPageNumberToId.set(pageNumberToId.subarray(index), index + copiedPageNumbers.length); 14607 this.#updatePrevPageNumbers(prevIdToPageNumber, null, index, copiedPageNumbers); 14608 this.#clipboard = null; 14609 } 14610 #updatePrevPageNumbers(prevIdToPageNumber, deletedPageNumbers = null, pasteIndex = -1, copiedPageNumbers = null) { 14611 const prevPageNumbers = this.#prevPageNumbers; 14612 const newPageNumberToId = this.#pageNumberToId; 14613 const pasteEnd = pasteIndex + (copiedPageNumbers?.length ?? 0); 14614 const idsIndices = new Map(); 14615 for (let i = 0, ii = this.#pagesNumber; i < ii; i++) { 14616 if (i >= pasteIndex && i < pasteEnd) { 14617 prevPageNumbers[i] = -copiedPageNumbers[i - pasteIndex]; 14618 continue; 14619 } 14620 const id = newPageNumberToId[i]; 14621 const oldPositions = prevIdToPageNumber.get(id); 14622 let j = idsIndices.get(id) || 0; 14623 if (deletedPageNumbers && oldPositions) { 14624 while (j < oldPositions.length && deletedPageNumbers.has(oldPositions[j])) { 14625 j++; 14626 } 14627 } 14628 prevPageNumbers[i] = oldPositions?.[j]; 14629 idsIndices.set(id, j + 1); 14630 } 14631 } 14632 hasBeenAltered() { 14633 return this.#pageNumberToId !== null; 14634 } 14635 getPageMappingForSaving(idToPageNumber = null) { 14636 idToPageNumber ??= this.#buildIdToPageNumber(); 14637 let nCopy = 0; 14638 for (const pageNumbers of idToPageNumber.values()) { 14639 nCopy = Math.max(nCopy, pageNumbers.length); 14640 } 14641 const extractParams = new Array(nCopy); 14642 for (let i = 0; i < nCopy; i++) { 14643 extractParams[i] = { 14644 document: null, 14645 pageIndices: [], 14646 includePages: [] 14647 }; 14648 } 14649 for (const [id, pageNumbers] of idToPageNumber) { 14650 for (let i = 0, ii = pageNumbers.length; i < ii; i++) { 14651 extractParams[i].includePages.push([id - 1, pageNumbers[i] - 1]); 14652 } 14653 } 14654 for (const { 14655 includePages, 14656 pageIndices 14657 } of extractParams) { 14658 includePages.sort((a, b) => a[0] - b[0]); 14659 for (let i = 0, ii = includePages.length; i < ii; i++) { 14660 pageIndices.push(includePages[i][1]); 14661 includePages[i] = includePages[i][0]; 14662 } 14663 } 14664 return extractParams; 14665 } 14666 extractPages(extractedPageNumbers) { 14667 extractedPageNumbers = Array.from(extractedPageNumbers).sort((a, b) => a - b); 14668 const usedIds = new Map(); 14669 for (let i = 0, ii = extractedPageNumbers.length; i < ii; i++) { 14670 const id = this.getPageId(extractedPageNumbers[i]); 14671 const usedPageNumbers = usedIds.getOrInsertComputed(id, makeArr); 14672 usedPageNumbers.push(i + 1); 14673 } 14674 return this.getPageMappingForSaving(usedIds); 14675 } 14676 getPrevPageNumber(pageNumber) { 14677 return this.#prevPageNumbers?.[pageNumber - 1] ?? 0; 14678 } 14679 getPageNumber(id) { 14680 if (!this.#pageNumberToId) { 14681 return id; 14682 } 14683 const pageNumberToId = this.#pageNumberToId; 14684 for (let i = 0, ii = this.#pagesNumber; i < ii; i++) { 14685 if (pageNumberToId[i] === id) { 14686 return i + 1; 14687 } 14688 } 14689 return 0; 14690 } 14691 getPageId(pageNumber) { 14692 return this.#pageNumberToId?.[pageNumber - 1] ?? pageNumber; 14693 } 14694 getMapping() { 14695 return this.#pageNumberToId?.subarray(0, this.pagesNumber); 14696 } 14697} 14698 14699;// ./src/display/pdf_objects.js 14700const INITIAL_DATA = Symbol("INITIAL_DATA"); 14701const dataObj = () => ({ 14702 ...Promise.withResolvers(), 14703 data: INITIAL_DATA 14704}); 14705class PDFObjects { 14706 #objs = new Map(); 14707 get(objId, callback = null) { 14708 if (callback) { 14709 const obj = this.#objs.getOrInsertComputed(objId, dataObj); 14710 obj.promise.then(() => callback(obj.data)); 14711 return null; 14712 } 14713 const obj = this.#objs.get(objId); 14714 if (!obj || obj.data === INITIAL_DATA) { 14715 throw new Error(`Requesting object that isn't resolved yet ${objId}.`); 14716 } 14717 return obj.data; 14718 } 14719 has(objId) { 14720 const obj = this.#objs.get(objId); 14721 return !!obj && obj.data !== INITIAL_DATA; 14722 } 14723 delete(objId) { 14724 const obj = this.#objs.get(objId); 14725 if (!obj || obj.data === INITIAL_DATA) { 14726 return false; 14727 } 14728 this.#objs.delete(objId); 14729 return true; 14730 } 14731 resolve(objId, data = null) { 14732 const obj = this.#objs.getOrInsertComputed(objId, dataObj); 14733 if (obj.data !== INITIAL_DATA) { 14734 throw new Error(`Object already resolved ${objId}.`); 14735 } 14736 obj.data = data; 14737 obj.resolve(); 14738 } 14739 clear() { 14740 for (const { 14741 data 14742 } of this.#objs.values()) { 14743 data?.bitmap?.close(); 14744 } 14745 this.#objs.clear(); 14746 } 14747 *[Symbol.iterator]() { 14748 for (const [objId, { 14749 data 14750 }] of this.#objs) { 14751 if (data !== INITIAL_DATA) { 14752 yield [objId, data]; 14753 } 14754 } 14755 } 14756} 14757 14758;// ./src/display/text_layer.js 14759 14760 14761const MAX_TEXT_DIVS_TO_RENDER = 100000; 14762const DEFAULT_FONT_SIZE = 30; 14763class TextLayer { 14764 #capability = Promise.withResolvers(); 14765 #container = null; 14766 #disableProcessItems = false; 14767 #fontInspectorEnabled = !!globalThis.FontInspector?.enabled; 14768 #imagesHandler = null; 14769 #lang = null; 14770 #layoutTextParams = null; 14771 #pageHeight = 0; 14772 #pageWidth = 0; 14773 #reader = null; 14774 #rootContainer = null; 14775 #rotation = 0; 14776 #scale = 0; 14777 #styleCache = Object.create(null); 14778 #textContentItemsStr = []; 14779 #textContentSource = null; 14780 #textDivs = []; 14781 #textDivProperties = new WeakMap(); 14782 #transform = null; 14783 static #ascentCache = new Map(); 14784 static #canvasContexts = new Map(); 14785 static #canvasCtxFonts = new WeakMap(); 14786 static #minFontSize = null; 14787 static #pendingTextLayers = new Set(); 14788 constructor({ 14789 textContentSource, 14790 images, 14791 container, 14792 viewport 14793 }) { 14794 if (textContentSource instanceof ReadableStream) { 14795 this.#textContentSource = textContentSource; 14796 } else if (typeof textContentSource === "object") { 14797 this.#textContentSource = new ReadableStream({ 14798 start(controller) { 14799 controller.enqueue(textContentSource); 14800 controller.close(); 14801 } 14802 }); 14803 } else { 14804 throw new Error('No "textContentSource" parameter specified.'); 14805 } 14806 this.#container = this.#rootContainer = container; 14807 this.#imagesHandler = images; 14808 this.#scale = viewport.scale * OutputScale.pixelRatio; 14809 this.#rotation = viewport.rotation; 14810 this.#layoutTextParams = { 14811 div: null, 14812 properties: null, 14813 ctx: null 14814 }; 14815 const { 14816 pageWidth, 14817 pageHeight, 14818 pageX, 14819 pageY 14820 } = viewport.rawDims; 14821 this.#transform = [1, 0, 0, -1, -pageX, pageY + pageHeight]; 14822 this.#pageWidth = pageWidth; 14823 this.#pageHeight = pageHeight; 14824 TextLayer.#ensureMinFontSizeComputed(); 14825 container.style.setProperty("--min-font-size", TextLayer.#minFontSize); 14826 setLayerDimensions(container, viewport); 14827 this.#capability.promise.finally(() => { 14828 TextLayer.#pendingTextLayers.delete(this); 14829 this.#layoutTextParams = null; 14830 this.#styleCache = null; 14831 }).catch(() => {}); 14832 } 14833 static get fontFamilyMap() { 14834 const { 14835 isWindows, 14836 isFirefox 14837 } = FeatureTest.platform; 14838 return shadow(this, "fontFamilyMap", new Map([["sans-serif", `${isWindows && isFirefox ? "Calibri, " : ""}sans-serif`], ["monospace", `${isWindows && isFirefox ? "Lucida Console, " : ""}monospace`]])); 14839 } 14840 render() { 14841 if (this.#imagesHandler) { 14842 this.#container.append(this.#imagesHandler.render()); 14843 } 14844 const pump = () => { 14845 this.#reader.read().then(({ 14846 value, 14847 done 14848 }) => { 14849 if (done) { 14850 this.#capability.resolve(); 14851 return; 14852 } 14853 this.#lang ??= value.lang; 14854 Object.assign(this.#styleCache, value.styles); 14855 this.#processItems(value.items); 14856 pump(); 14857 }, this.#capability.reject); 14858 }; 14859 this.#reader = this.#textContentSource.getReader(); 14860 TextLayer.#pendingTextLayers.add(this); 14861 pump(); 14862 return this.#capability.promise; 14863 } 14864 update({ 14865 viewport, 14866 onBefore = null 14867 }) { 14868 const scale = viewport.scale * OutputScale.pixelRatio; 14869 const rotation = viewport.rotation; 14870 if (rotation !== this.#rotation) { 14871 onBefore?.(); 14872 this.#rotation = rotation; 14873 setLayerDimensions(this.#rootContainer, { 14874 rotation 14875 }); 14876 } 14877 if (scale !== this.#scale) { 14878 onBefore?.(); 14879 this.#scale = scale; 14880 const params = { 14881 div: null, 14882 properties: null, 14883 ctx: TextLayer.#getCtx(this.#lang) 14884 }; 14885 for (const div of this.#textDivs) { 14886 params.properties = this.#textDivProperties.get(div); 14887 params.div = div; 14888 this.#layout(params); 14889 } 14890 } 14891 } 14892 cancel() { 14893 const abortEx = new AbortException("TextLayer task cancelled."); 14894 this.#reader?.cancel(abortEx).catch(() => {}); 14895 this.#reader = null; 14896 this.#capability.reject(abortEx); 14897 } 14898 get textDivs() { 14899 return this.#textDivs; 14900 } 14901 get textContentItemsStr() { 14902 return this.#textContentItemsStr; 14903 } 14904 #processItems(items) { 14905 if (this.#disableProcessItems) { 14906 return; 14907 } 14908 this.#layoutTextParams.ctx ??= TextLayer.#getCtx(this.#lang); 14909 const textDivs = this.#textDivs, 14910 textContentItemsStr = this.#textContentItemsStr; 14911 for (const item of items) { 14912 if (textDivs.length > MAX_TEXT_DIVS_TO_RENDER) { 14913 warn("Ignoring additional textDivs for performance reasons."); 14914 this.#disableProcessItems = true; 14915 return; 14916 } 14917 if (item.str === undefined) { 14918 if (item.type === "beginMarkedContentProps" || item.type === "beginMarkedContent") { 14919 const parent = this.#container; 14920 this.#container = document.createElement("span"); 14921 this.#container.classList.add("markedContent"); 14922 if (item.id) { 14923 this.#container.setAttribute("id", `${item.id}`
vendor: 10,057 bytes, lines 14923-15216
14923); 14924 } 14925 if (item.tag === "Artifact") { 14926 this.#container.ariaHidden = true; 14927 } 14928 parent.append(this.#container); 14929 } else if (item.type === "endMarkedContent") { 14930 this.#container = this.#container.parentNode; 14931 } 14932 continue; 14933 } 14934 textContentItemsStr.push(item.str); 14935 this.#appendText(item); 14936 } 14937 } 14938 #appendText(geom) { 14939 const textDiv = document.createElement("span"); 14940 const textDivProperties = { 14941 angle: 0, 14942 canvasWidth: 0, 14943 hasText: geom.str !== "", 14944 hasEOL: geom.hasEOL, 14945 fontSize: 0 14946 }; 14947 this.#textDivs.push(textDiv); 14948 const tx = Util.transform(this.#transform, geom.transform); 14949 let angle = Math.atan2(tx[1], tx[0]); 14950 const style = this.#styleCache[geom.fontName]; 14951 if (style.vertical) { 14952 angle += Math.PI / 2; 14953 } 14954 let fontFamily = this.#fontInspectorEnabled && style.fontSubstitution || style.fontFamily; 14955 fontFamily = TextLayer.fontFamilyMap.get(fontFamily) || fontFamily; 14956 const fontHeight = Math.hypot(tx[2], tx[3]); 14957 const fontAscent = fontHeight * TextLayer.#getAscent(fontFamily, style, this.#lang); 14958 let left, top; 14959 if (angle === 0) { 14960 left = tx[4]; 14961 top = tx[5] - fontAscent; 14962 } else { 14963 left = tx[4] + fontAscent * Math.sin(angle); 14964 top = tx[5] - fontAscent * Math.cos(angle); 14965 } 14966 const divStyle = textDiv.style; 14967 divStyle.left = `${(100 * left / this.#pageWidth).toFixed(2)}%`; 14968 divStyle.top = `${(100 * top / this.#pageHeight).toFixed(2)}%`; 14969 divStyle.setProperty("--font-height", `${fontHeight.toFixed(2)}px`); 14970 divStyle.fontFamily = fontFamily; 14971 textDivProperties.fontSize = fontHeight; 14972 textDiv.setAttribute("role", "presentation"); 14973 textDiv.textContent = geom.str; 14974 textDiv.dir = geom.dir; 14975 if (this.#fontInspectorEnabled) { 14976 textDiv.dataset.fontName = style.fontSubstitutionLoadedName || geom.fontName; 14977 } 14978 if (angle !== 0) { 14979 textDivProperties.angle = angle * (180 / Math.PI); 14980 } 14981 let shouldScaleText = false; 14982 if (geom.str.length > 1) { 14983 shouldScaleText = true; 14984 } else if (geom.str !== " " && geom.transform[0] !== geom.transform[3]) { 14985 const absScaleX = Math.abs(geom.transform[0]), 14986 absScaleY = Math.abs(geom.transform[3]); 14987 if (absScaleX !== absScaleY && Math.max(absScaleX, absScaleY) / Math.min(absScaleX, absScaleY) > 1.5) { 14988 shouldScaleText = true; 14989 } 14990 } 14991 if (shouldScaleText) { 14992 textDivProperties.canvasWidth = style.vertical ? geom.height : geom.width; 14993 } 14994 this.#textDivProperties.set(textDiv, textDivProperties); 14995 this.#layoutTextParams.div = textDiv; 14996 this.#layoutTextParams.properties = textDivProperties; 14997 this.#layout(this.#layoutTextParams); 14998 if (textDivProperties.hasText) { 14999 this.#container.append(textDiv); 15000 } 15001 if (textDivProperties.hasEOL) { 15002 const br = document.createElement("br"); 15003 br.setAttribute("role", "presentation"); 15004 this.#container.append(br); 15005 } 15006 } 15007 #layout(params) { 15008 const { 15009 div, 15010 properties, 15011 ctx 15012 } = params; 15013 const { 15014 style 15015 } = div; 15016 if (properties.canvasWidth !== 0 && properties.hasText) { 15017 const { 15018 fontFamily 15019 } = style; 15020 const { 15021 canvasWidth, 15022 fontSize 15023 } = properties; 15024 TextLayer.#ensureCtxFont(ctx, fontSize * this.#scale, fontFamily); 15025 const { 15026 width 15027 } = ctx.measureText(div.textContent); 15028 if (width > 0) { 15029 style.setProperty("--scale-x", canvasWidth * this.#scale / width); 15030 } 15031 } 15032 if (properties.angle !== 0) { 15033 style.setProperty("--rotate", `${properties.angle}deg`); 15034 } 15035 } 15036 static cleanup() { 15037 if (this.#pendingTextLayers.size > 0) { 15038 return; 15039 } 15040 this.#ascentCache.clear(); 15041 for (const { 15042 canvas 15043 } of this.#canvasContexts.values()) { 15044 canvas.remove(); 15045 } 15046 this.#canvasContexts.clear(); 15047 } 15048 static #getCtx(lang = null) { 15049 let ctx = this.#canvasContexts.get(lang ||= ""); 15050 if (!ctx) { 15051 const canvas = document.createElement("canvas"); 15052 canvas.style.cssText = "position:absolute;top:0;left:0;width:0;height:0;display:none;" + "letter-spacing:normal;word-spacing:normal"; 15053 canvas.lang = lang; 15054 document.body.append(canvas); 15055 ctx = canvas.getContext("2d", { 15056 alpha: false, 15057 willReadFrequently: true 15058 }); 15059 this.#canvasContexts.set(lang, ctx); 15060 this.#canvasCtxFonts.set(ctx, { 15061 size: 0, 15062 family: "" 15063 }); 15064 } 15065 return ctx; 15066 } 15067 static #ensureCtxFont(ctx, size, family) { 15068 const cached = this.#canvasCtxFonts.get(ctx); 15069 if (size === cached.size && family === cached.family) { 15070 return; 15071 } 15072 ctx.font = `${size}px ${family}`; 15073 cached.size = size; 15074 cached.family = family; 15075 } 15076 static #ensureMinFontSizeComputed() { 15077 if (this.#minFontSize !== null) { 15078 return; 15079 } 15080 const div = document.createElement("div"); 15081 div.style.opacity = 0; 15082 div.style.lineHeight = 1; 15083 div.style.fontSize = "1px"; 15084 div.style.position = "absolute"; 15085 div.textContent = "X"; 15086 document.body.append(div); 15087 this.#minFontSize = div.getBoundingClientRect().height; 15088 div.remove(); 15089 } 15090 static #getAscent(fontFamily, style, lang) { 15091 const cachedAscent = this.#ascentCache.get(fontFamily); 15092 if (cachedAscent) { 15093 return cachedAscent; 15094 } 15095 const ctx = this.#getCtx(lang); 15096 ctx.canvas.width = ctx.canvas.height = DEFAULT_FONT_SIZE; 15097 this.#ensureCtxFont(ctx, DEFAULT_FONT_SIZE, fontFamily); 15098 const metrics = ctx.measureText(""); 15099 const ascent = metrics.fontBoundingBoxAscent; 15100 const descent = Math.abs(metrics.fontBoundingBoxDescent); 15101 ctx.canvas.width = ctx.canvas.height = 0; 15102 let ratio = 0.8; 15103 if (ascent) { 15104 ratio = ascent / (ascent + descent); 15105 } else { 15106 if (FeatureTest.platform.isFirefox) { 15107 warn("Enable the `dom.textMetrics.fontBoundingBox.enabled` preference " + "in `about:config` to improve TextLayer rendering."); 15108 } 15109 if (style.ascent) { 15110 ratio = style.ascent; 15111 } else if (style.descent) { 15112 ratio = 1 + style.descent; 15113 } 15114 } 15115 this.#ascentCache.set(fontFamily, ratio); 15116 return ratio; 15117 } 15118} 15119 15120;// ./src/display/api.js 15121 15122 15123 15124 15125 15126 15127 15128 15129 15130 15131 15132 15133 15134 15135 15136 15137 15138 15139 15140 15141 15142 15143 15144 15145 15146const RENDERING_CANCELLED_TIMEOUT = 100; 15147function getDocument(src = {}) { 15148 const task = new PDFDocumentLoadingTask(); 15149 const { 15150 docId 15151 } = task; 15152 const url = src.url ? getUrlProp(src.url) : null; 15153 const data = src.data ? getDataProp(src.data) : null; 15154 const httpHeaders = src.httpHeaders || null; 15155 const withCredentials = src.withCredentials === true; 15156 const password = src.password ?? null; 15157 const rangeTransport = src.range instanceof PDFDataRangeTransport ? src.range : null; 15158 const rangeChunkSize = Number.isInteger(src.rangeChunkSize) && src.rangeChunkSize > 0 ? src.rangeChunkSize : 2 ** 16; 15159 let worker = src.worker instanceof PDFWorker ? src.worker : null; 15160 const verbosity = src.verbosity; 15161 const docBaseUrl = typeof src.docBaseUrl === "string" && !isDataScheme(src.docBaseUrl) ? src.docBaseUrl : null; 15162 const cMapUrl = getFactoryUrlProp(src.cMapUrl); 15163 const cMapPacked = src.cMapPacked !== false; 15164 const iccUrl = getFactoryUrlProp(src.iccUrl); 15165 const standardFontDataUrl = getFactoryUrlProp(src.standardFontDataUrl); 15166 const wasmUrl = getFactoryUrlProp(src.wasmUrl); 15167 const ignoreErrors = src.stopAtErrors !== true; 15168 const maxImageSize = Number.isInteger(src.maxImageSize) && src.maxImageSize > -1 ? src.maxImageSize : -1; 15169 const isOffscreenCanvasSupported = typeof src.isOffscreenCanvasSupported === "boolean" ? src.isOffscreenCanvasSupported : !isNodeJS; 15170 const isImageDecoderSupported = typeof src.isImageDecoderSupported === "boolean" ? src.isImageDecoderSupported : !isNodeJS; 15171 const canvasMaxAreaInBytes = Number.isInteger(src.canvasMaxAreaInBytes) ? src.canvasMaxAreaInBytes : -1; 15172 const disableFontFace = typeof src.disableFontFace === "boolean" ? src.disableFontFace : isNodeJS; 15173 const fontExtraProperties = src.fontExtraProperties === true; 15174 const enableXfa = src.enableXfa === true; 15175 const ownerDocument = src.ownerDocument || globalThis.document; 15176 const disableRange = src.disableRange === true; 15177 const disableStream = src.disableStream === true; 15178 const disableAutoFetch = src.disableAutoFetch === true; 15179 const pdfBug = src.pdfBug === true; 15180 const CanvasFactory = src.CanvasFactory || (isNodeJS ? NodeCanvasFactory : DOMCanvasFactory); 15181 const FilterFactory = src.FilterFactory || (isNodeJS ? NodeFilterFactory : DOMFilterFactory); 15182 const BinaryDataFactory = src.BinaryDataFactory || (isNodeJS ? NodeBinaryDataFactory : DOMBinaryDataFactory); 15183 const enableHWA = src.enableHWA === true; 15184 const enableWebGPU = src.enableWebGPU === true; 15185 const gpuPromise = enableWebGPU ? initGPU() : Promise.resolve(false); 15186 const useWasm = src.useWasm !== false; 15187 const pagesMapper = src.pagesMapper || new PagesMapper(); 15188 const useSystemFonts = typeof src.useSystemFonts === "boolean" ? src.useSystemFonts : !isNodeJS && !disableFontFace; 15189 const useWorkerFetch = typeof src.useWorkerFetch === "boolean" ? src.useWorkerFetch : !!(BinaryDataFactory === DOMBinaryDataFactory && cMapUrl && cMapPacked && standardFontDataUrl && wasmUrl && isValidFetchUrl(cMapUrl, document.baseURI) && isValidFetchUrl(standardFontDataUrl, document.baseURI) && isValidFetchUrl(wasmUrl, document.baseURI)); 15190 const styleElement = null; 15191 setVerbosityLevel(verbosity); 15192 const transportFactory = { 15193 canvasFactory: new CanvasFactory({ 15194 ownerDocument, 15195 enableHWA 15196 }), 15197 filterFactory: new FilterFactory({ 15198 docId, 15199 ownerDocument 15200 }), 15201 binaryDataFactory: useWorkerFetch ? null : new BinaryDataFactory({ 15202 cMapUrl, 15203 standardFontDataUrl, 15204 wasmUrl 15205 }) 15206 }; 15207 if (!worker) { 15208 worker = PDFWorker.create({ 15209 verbosity, 15210 port: GlobalWorkerOptions.workerPort 15211 }); 15212 task._worker = worker; 15213 } 15214 const docParams = { 15215 docId, 15216 apiVersion: "6.
152161.200
vendor: 6,387 bytes, lines 15216-15463
15216", 15217 data, 15218 password, 15219 disableAutoFetch, 15220 rangeChunkSize, 15221 docBaseUrl, 15222 enableXfa, 15223 evaluatorOptions: { 15224 maxImageSize, 15225 disableFontFace, 15226 ignoreErrors, 15227 isOffscreenCanvasSupported, 15228 isImageDecoderSupported, 15229 canvasMaxAreaInBytes, 15230 fontExtraProperties, 15231 useSystemFonts, 15232 useWasm, 15233 useWorkerFetch, 15234 cMapUrl, 15235 cMapPacked, 15236 iccUrl, 15237 standardFontDataUrl, 15238 wasmUrl, 15239 hasGPU: false 15240 } 15241 }; 15242 const transportParams = { 15243 ownerDocument, 15244 pdfBug, 15245 styleElement, 15246 enableHWA, 15247 loadingParams: { 15248 disableAutoFetch, 15249 enableXfa 15250 } 15251 }; 15252 Promise.all([worker.promise, gpuPromise]).then(function ([, hasGPU]) { 15253 if (worker.destroyed) { 15254 throw new Error("Worker was destroyed"); 15255 } 15256 docParams.evaluatorOptions.hasGPU = hasGPU; 15257 const workerIdPromise = worker.messageHandler.sendWithPromise("GetDocRequest", docParams, data ? [data.buffer] : null); 15258 let networkStream; 15259 if (data) {} else if (rangeTransport) { 15260 networkStream = new PDFDataTransportStream({ 15261 pdfDataRangeTransport: rangeTransport, 15262 disableRange, 15263 disableStream 15264 }); 15265 } else if (url) { 15266 const NetworkStream = getNetworkStream(url); 15267 networkStream = new NetworkStream({ 15268 url, 15269 httpHeaders, 15270 withCredentials, 15271 rangeChunkSize, 15272 disableRange, 15273 disableStream 15274 }); 15275 } else { 15276 throw new Error("getDocument - expected either `data`, `range`, or `url` parameter."); 15277 } 15278 return workerIdPromise.then(workerId => { 15279 if (worker.destroyed) { 15280 throw new Error("Worker was destroyed"); 15281 } 15282 const messageHandler = new MessageHandler(docId, workerId, worker.port); 15283 const transport = new WorkerTransport(messageHandler, task, networkStream, transportParams, transportFactory, pagesMapper); 15284 task._transport = transport; 15285 if (task.destroyed) { 15286 throw new Error("Loading aborted"); 15287 } 15288 messageHandler.send("Ready", null); 15289 }); 15290 }).catch(task._capability.reject).finally(task._setupCapability.resolve); 15291 return task; 15292} 15293class PDFDocumentLoadingTask { 15294 static #docId = 0; 15295 _capability = Promise.withResolvers(); 15296 _setupCapability = Promise.withResolvers(); 15297 _transport = null; 15298 _worker = null; 15299 docId = `d${PDFDocumentLoadingTask.#docId++}`; 15300 destroyed = false; 15301 onPassword = null; 15302 onProgress = null; 15303 get promise() { 15304 return this._capability.promise; 15305 } 15306 async destroy() { 15307 this.destroyed = true; 15308 this._capability.promise.catch(() => {}); 15309 try { 15310 if (this._worker?.port) { 15311 this._worker._pendingDestroy = true; 15312 } 15313 await this._setupCapability.promise; 15314 await this._transport?.destroy(); 15315 } catch (ex) { 15316 if (this._worker?.port) { 15317 delete this._worker._pendingDestroy; 15318 } 15319 throw ex; 15320 } 15321 this._transport = null; 15322 this._worker?.destroy(); 15323 this._worker = null; 15324 } 15325 async getData() { 15326 return this._transport.getData(); 15327 } 15328} 15329class PDFDataRangeTransport { 15330 #capability = Promise.withResolvers(); 15331 #listener = null; 15332 constructor(length, initialData, progressiveDone = false, contentDispositionFilename = null) { 15333 this.length = length; 15334 this.initialData = initialData; 15335 this.progressiveDone = progressiveDone; 15336 this.contentDispositionFilename = contentDispositionFilename; 15337 } 15338 onDataRange(begin, chunk) { 15339 this.#listener({ 15340 type: "range", 15341 begin, 15342 chunk 15343 }); 15344 } 15345 onDataProgressiveRead(chunk) { 15346 this.#capability.promise.then(() => { 15347 this.#listener({ 15348 type: "progressiveRead", 15349 chunk 15350 }); 15351 }); 15352 } 15353 onDataProgressiveDone() { 15354 this.#capability.promise.then(() => { 15355 this.#listener({ 15356 type: "progressiveDone" 15357 }); 15358 }); 15359 } 15360 transportReady(listener) { 15361 this.#listener = listener; 15362 this.#capability.resolve(); 15363 } 15364 requestDataRange(begin, end) { 15365 unreachable("Abstract method PDFDataRangeTransport.requestDataRange"); 15366 } 15367 abort() {} 15368} 15369class PDFDocumentProxy { 15370 constructor(pdfInfo, transport) { 15371 this._pdfInfo = pdfInfo; 15372 this._transport = transport; 15373 } 15374 get pagesMapper() { 15375 return this._transport.pagesMapper; 15376 } 15377 get annotationStorage() { 15378 return this._transport.annotationStorage; 15379 } 15380 get canvasFactory() { 15381 return this._transport.canvasFactory; 15382 } 15383 get filterFactory() { 15384 return this._transport.filterFactory; 15385 } 15386 get numPages() { 15387 return this._pdfInfo.numPages; 15388 } 15389 get fingerprints() { 15390 return this._pdfInfo.fingerprints; 15391 } 15392 get isPureXfa() { 15393 return shadow(this, "isPureXfa", !!this._transport._htmlForXfa); 15394 } 15395 get allXfaHtml() { 15396 return this._transport._htmlForXfa; 15397 } 15398 getPage(pageNumber) { 15399 return this._transport.getPage(pageNumber); 15400 } 15401 getPageIndex(ref) { 15402 return this._transport.getPageIndex(ref); 15403 } 15404 getDestinations() { 15405 return this._transport.getDestinations(); 15406 } 15407 getDestination(id) { 15408 return this._transport.getDestination(id); 15409 } 15410 getPageLabels() { 15411 return this._transport.getPageLabels(); 15412 } 15413 getPageLayout() { 15414 return this._transport.getPageLayout(); 15415 } 15416 getPageMode() { 15417 return this._transport.getPageMode(); 15418 } 15419 getViewerPreferences() { 15420 return this._transport.getViewerPreferences(); 15421 } 15422 getOpenAction() { 15423 return this._transport.getOpenAction(); 15424 } 15425 getAttachments() { 15426 return this._transport.getAttachments(); 15427 } 15428 getAttachmentContent(id) { 15429 return this._transport.getAttachmentContent(id); 15430 } 15431 getAnnotationsByType(types, pageIndexesToSkip) { 15432 return this._transport.getAnnotationsByType(types, pageIndexesToSkip); 15433 } 15434 getJSActions() { 15435 return this._transport.getDocJSActions(); 15436 } 15437 getOutline() { 15438 return this._transport.getOutline(); 15439 } 15440 getOptionalContentConfig({ 15441 intent = "display" 15442 } = {}) { 15443 const { 15444 renderingIntent 15445 } = this._transport.getRenderingIntent(intent); 15446 return this._transport.getOptionalContentConfig(renderingIntent); 15447 } 15448 getPermissions() { 15449 return this._transport.getPermissions(); 15450 } 15451 getMetadata() { 15452 return this._transport.getMetadata(); 15453 } 15454 getMarkInfo() { 15455 return this._transport.getMarkInfo(); 15456 } 15457 getData() { 15458 return this._transport.getData(); 15459 } 15460 saveDocument() { 15461 return this._transport.saveDocument(); 15462 } 15463 extractPages(pageInfos
15463) { 15464 return this._transport.extractPages(pageInfos); 15465 } 15466 getDownloadInfo() { 15467 return this._transport.downloadInfoCapability.promise; 15468 } 15469 getRawData(data) { 15470 return this._transport.getRawData(data); 15471 } 15472 cleanup(keepLoadedFonts = false) { 15473 return this._transport.startCleanup(keepLoadedFonts || this.isPureXfa); 15474 } 15475 cachedPageNumber(ref) { 15476 return this._transport.cachedPageNumber(ref); 15477 } 15478 get loadingParams() { 15479 return this._transport.loadingParams; 15480 } 15481 get loadingTask() { 15482 return this._transport.loadingTask; 15483 } 15484 getFieldObjects() { 15485 return this._transport.getFieldObjects(
vendor: 33,108 bytes, lines 15485-16530
15485); 15486 } 15487 hasJSActions() { 15488 return this._transport.hasJSActions(); 15489 } 15490 getCalculationOrderIds() { 15491 return this._transport.getCalculationOrderIds(); 15492 } 15493} 15494class PDFPageProxy { 15495 #pendingCleanup = false; 15496 #pagesMapper = null; 15497 constructor(pageIndex, pageInfo, transport, pagesMapper, pdfBug = false) { 15498 this._pageIndex = pageIndex; 15499 this._pageInfo = pageInfo; 15500 this._transport = transport; 15501 this._stats = pdfBug ? new StatTimer() : null; 15502 this._pdfBug = pdfBug; 15503 this.commonObjs = transport.commonObjs; 15504 this.objs = new PDFObjects(); 15505 this._intentStates = new Map(); 15506 this.destroyed = false; 15507 this.recordedBBoxes = null; 15508 this.#pagesMapper = pagesMapper; 15509 this.imageCoordinates = null; 15510 } 15511 clone(id) { 15512 const clone = new PDFPageProxy(id, this._pageInfo, this._transport, this.#pagesMapper, this._pdfBug); 15513 clone.clonedFromIndex = this.clonedFromIndex ?? this._pageIndex; 15514 this._transport.updatePage(clone); 15515 return clone; 15516 } 15517 get pageNumber() { 15518 return this._pageIndex + 1; 15519 } 15520 set pageNumber(value) { 15521 this._pageIndex = value - 1; 15522 this._transport.updatePage(this); 15523 } 15524 get rotate() { 15525 return this._pageInfo.rotate; 15526 } 15527 get ref() { 15528 return this._pageInfo.ref; 15529 } 15530 get userUnit() { 15531 return this._pageInfo.userUnit; 15532 } 15533 get view() { 15534 return this._pageInfo.view; 15535 } 15536 getViewport({ 15537 scale, 15538 rotation = this.rotate, 15539 offsetX = 0, 15540 offsetY = 0, 15541 dontFlip = false 15542 } = {}) { 15543 return new PageViewport({ 15544 viewBox: this.view, 15545 userUnit: this.userUnit, 15546 scale, 15547 rotation, 15548 offsetX, 15549 offsetY, 15550 dontFlip 15551 }); 15552 } 15553 getAnnotations({ 15554 intent = "display" 15555 } = {}) { 15556 const { 15557 renderingIntent 15558 } = this._transport.getRenderingIntent(intent); 15559 return this._transport.getAnnotations(this._pageIndex, renderingIntent); 15560 } 15561 getJSActions() { 15562 return this._transport.getPageJSActions(this._pageIndex); 15563 } 15564 get filterFactory() { 15565 return this._transport.filterFactory; 15566 } 15567 get isPureXfa() { 15568 return shadow(this, "isPureXfa", !!this._transport._htmlForXfa); 15569 } 15570 async getXfa() { 15571 return this._transport._htmlForXfa?.children[this._pageIndex] || null; 15572 } 15573 render({ 15574 canvasContext, 15575 canvas = canvasContext.canvas, 15576 viewport, 15577 intent = "display", 15578 annotationMode = AnnotationMode.ENABLE, 15579 transform = null, 15580 background = null, 15581 optionalContentConfigPromise = null, 15582 annotationCanvasMap = null, 15583 pageColors = null, 15584 printAnnotationStorage = null, 15585 isEditing = false, 15586 recordImages = false, 15587 recordOperations = false, 15588 operationsFilter = null 15589 }) { 15590 this._stats?.time("Overall"); 15591 const intentArgs = this._transport.getRenderingIntent(intent, annotationMode, printAnnotationStorage, isEditing); 15592 const { 15593 renderingIntent, 15594 cacheKey 15595 } = intentArgs; 15596 this.#pendingCleanup = false; 15597 optionalContentConfigPromise ||= this._transport.getOptionalContentConfig(renderingIntent); 15598 const intentState = this._intentStates.getOrInsertComputed(cacheKey, makeObj); 15599 if (intentState.streamReaderCancelTimeout) { 15600 clearTimeout(intentState.streamReaderCancelTimeout); 15601 intentState.streamReaderCancelTimeout = null; 15602 } 15603 const intentPrint = !!(renderingIntent & RenderingIntentFlag.PRINT); 15604 if (!intentState.displayReadyCapability) { 15605 intentState.displayReadyCapability = Promise.withResolvers(); 15606 intentState.operatorList = { 15607 fnArray: [], 15608 argsArray: [], 15609 lastChunk: false, 15610 separateAnnots: null 15611 }; 15612 this._stats?.time("Page Request"); 15613 this._pumpOperatorList(intentArgs); 15614 } 15615 const recordForDebugger = !!(this._pdfBug && globalThis.StepperManager?.enabled); 15616 const shouldRecordOperations = !!canvas && !this.recordedBBoxes && (recordOperations || recordForDebugger); 15617 const shouldRecordImages = !!canvas && !this.imageCoordinates && recordImages; 15618 const complete = error => { 15619 intentState.renderTasks.delete(internalRenderTask); 15620 if (shouldRecordOperations) { 15621 const recordedBBoxes = internalRenderTask.gfx?.dependencyTracker.take(); 15622 if (recordedBBoxes) { 15623 internalRenderTask.stepper?.setOperatorBBoxes(recordedBBoxes, internalRenderTask.gfx.dependencyTracker.takeDebugMetadata()); 15624 if (recordOperations) { 15625 this.recordedBBoxes = recordedBBoxes; 15626 } 15627 } 15628 } 15629 if (shouldRecordImages && !error) { 15630 this.imageCoordinates = internalRenderTask.gfx?.imagesTracker.take(); 15631 } 15632 if (intentPrint) { 15633 this.#pendingCleanup = true; 15634 } 15635 this.#tryCleanup(); 15636 if (error) { 15637 internalRenderTask.capability.reject(error); 15638 this._abortOperatorList({ 15639 intentState, 15640 reason: error instanceof Error ? error : new Error(error) 15641 }); 15642 } else { 15643 internalRenderTask.capability.resolve(); 15644 } 15645 if (this._stats) { 15646 this._stats.timeEnd("Rendering"); 15647 this._stats.timeEnd("Overall"); 15648 if (globalThis.Stats?.enabled) { 15649 globalThis.Stats.add(this.pageNumber, this._stats); 15650 } 15651 } 15652 }; 15653 let dependencyTracker = null; 15654 let bboxTracker = null; 15655 if (shouldRecordOperations || shouldRecordImages) { 15656 bboxTracker = new CanvasBBoxTracker(canvas, intentState.operatorList.length); 15657 } 15658 if (shouldRecordOperations) { 15659 dependencyTracker = new CanvasDependencyTracker(bboxTracker, recordForDebugger); 15660 } 15661 const internalRenderTask = new InternalRenderTask({ 15662 callback: complete, 15663 params: { 15664 canvas, 15665 canvasContext, 15666 dependencyTracker: dependencyTracker ?? bboxTracker, 15667 imagesTracker: shouldRecordImages ? new CanvasImagesTracker(canvas) : null, 15668 viewport, 15669 transform, 15670 background 15671 }, 15672 objs: this.objs, 15673 commonObjs: this.commonObjs, 15674 annotationCanvasMap, 15675 operatorList: intentState.operatorList, 15676 pageIndex: this._pageIndex, 15677 canvasFactory: this._transport.canvasFactory, 15678 filterFactory: this._transport.filterFactory, 15679 useRequestAnimationFrame: !intentPrint, 15680 pdfBug: this._pdfBug, 15681 pageColors, 15682 enableHWA: this._transport.enableHWA, 15683 operationsFilter 15684 }); 15685 (intentState.renderTasks ||= new Set()).add(internalRenderTask); 15686 const renderTask = internalRenderTask.task; 15687 Promise.all([intentState.displayReadyCapability.promise, optionalContentConfigPromise]).then(([transparency, optionalContentConfig]) => { 15688 if (this.destroyed) { 15689 complete(); 15690 return; 15691 } 15692 this._stats?.time("Rendering"); 15693 if (!(optionalContentConfig.renderingIntent & renderingIntent)) { 15694 throw new Error("Must use the same `intent`-argument when calling the `PDFPageProxy.render` " + "and `PDFDocumentProxy.getOptionalContentConfig` methods."); 15695 } 15696 internalRenderTask.initializeGraphics({ 15697 transparency, 15698 optionalContentConfig 15699 }); 15700 internalRenderTask.operatorListChanged(); 15701 }).catch(complete); 15702 return renderTask; 15703 } 15704 getOperatorList({ 15705 intent = "display", 15706 annotationMode = AnnotationMode.ENABLE, 15707 printAnnotationStorage = null, 15708 isEditing = false 15709 } = {}) { 15710 function operatorListChanged() { 15711 if (intentState.operatorList.lastChunk) { 15712 intentState.opListReadCapability.resolve(intentState.operatorList); 15713 intentState.renderTasks.delete(opListTask); 15714 } 15715 } 15716 const intentArgs = this._transport.getRenderingIntent(intent, annotationMode, printAnnotationStorage, isEditing, true); 15717 const intentState = this._intentStates.getOrInsertComputed(intentArgs.cacheKey, makeObj); 15718 let opListTask; 15719 if (!intentState.opListReadCapability) { 15720 opListTask = Object.create(null); 15721 opListTask.operatorListChanged = operatorListChanged; 15722 intentState.opListReadCapability = Promise.withResolvers(); 15723 (intentState.renderTasks ||= new Set()).add(opListTask); 15724 intentState.operatorList = { 15725 fnArray: [], 15726 argsArray: [], 15727 lastChunk: false, 15728 separateAnnots: null 15729 }; 15730 this._stats?.time("Page Request"); 15731 this._pumpOperatorList(intentArgs); 15732 } 15733 return intentState.opListReadCapability.promise; 15734 } 15735 streamTextContent({ 15736 includeMarkedContent = false, 15737 disableNormalization = false 15738 } = {}) { 15739 const TEXT_CONTENT_CHUNK_SIZE = 100; 15740 return this._transport.messageHandler.sendWithStream("GetTextContent", { 15741 pageId: this.#pagesMapper.getPageId(this._pageIndex + 1) - 1, 15742 pageIndex: this._pageIndex, 15743 includeMarkedContent: includeMarkedContent === true, 15744 disableNormalization: disableNormalization === true 15745 }, { 15746 highWaterMark: TEXT_CONTENT_CHUNK_SIZE, 15747 size(textContent) { 15748 return textContent.items.length; 15749 } 15750 }); 15751 } 15752 async getTextContent(params = {}) { 15753 if (this._transport._htmlForXfa) { 15754 return this.getXfa().then(xfa => XfaText.textContent(xfa)); 15755 } 15756 const readableStream = this.streamTextContent(params); 15757 const textContent = { 15758 items: [], 15759 styles: Object.create(null), 15760 lang: null 15761 }; 15762 for await (const value of readableStream) { 15763 textContent.lang ??= value.lang; 15764 Object.assign(textContent.styles, value.styles); 15765 textContent.items.push(...value.items); 15766 } 15767 return textContent; 15768 } 15769 getStructTree() { 15770 return this._transport.getStructTree(this._pageIndex); 15771 } 15772 _destroy() { 15773 this.destroyed = true; 15774 const waitOn = []; 15775 for (const intentState of this._intentStates.values()) { 15776 this._abortOperatorList({ 15777 intentState, 15778 reason: new Error("Page was destroyed."), 15779 force: true 15780 }); 15781 if (intentState.opListReadCapability) { 15782 continue; 15783 } 15784 for (const internalRenderTask of intentState.renderTasks) { 15785 waitOn.push(internalRenderTask.completed); 15786 internalRenderTask.cancel(); 15787 } 15788 } 15789 this.objs.clear(); 15790 this.#pendingCleanup = false; 15791 return Promise.all(waitOn); 15792 } 15793 cleanup(resetStats = false) { 15794 this.#pendingCleanup = true; 15795 const success = this.#tryCleanup(); 15796 if (resetStats && success) { 15797 this._stats &&= new StatTimer(); 15798 } 15799 return success; 15800 } 15801 #tryCleanup() { 15802 if (!this.#pendingCleanup || this.destroyed) { 15803 return false; 15804 } 15805 for (const { 15806 renderTasks, 15807 operatorList 15808 } of this._intentStates.values()) { 15809 if (renderTasks.size > 0 || !operatorList.lastChunk) { 15810 return false; 15811 } 15812 } 15813 this._intentStates.clear(); 15814 this.objs.clear(); 15815 this.#pendingCleanup = false; 15816 return true; 15817 } 15818 _startRenderPage(transparency, cacheKey) { 15819 const intentState = this._intentStates.get(cacheKey); 15820 if (!intentState) { 15821 return; 15822 } 15823 this._stats?.timeEnd("Page Request"); 15824 intentState.displayReadyCapability?.resolve(transparency); 15825 } 15826 _renderPageChunk(operatorListChunk, intentState) { 15827 for (let i = 0, ii = operatorListChunk.length; i < ii; i++) { 15828 intentState.operatorList.fnArray.push(operatorListChunk.fnArray[i]); 15829 intentState.operatorList.argsArray.push(operatorListChunk.argsArray[i]); 15830 } 15831 intentState.operatorList.lastChunk = operatorListChunk.lastChunk; 15832 intentState.operatorList.separateAnnots = operatorListChunk.separateAnnots; 15833 for (const internalRenderTask of intentState.renderTasks) { 15834 internalRenderTask.operatorListChanged(); 15835 } 15836 if (operatorListChunk.lastChunk) { 15837 this.#tryCleanup(); 15838 } 15839 } 15840 _pumpOperatorList({ 15841 renderingIntent, 15842 cacheKey, 15843 annotationStorageSerializable, 15844 modifiedIds 15845 }) { 15846 const { 15847 map, 15848 transfer 15849 } = annotationStorageSerializable; 15850 const readableStream = this._transport.messageHandler.sendWithStream("GetOperatorList", { 15851 pageId: this.#pagesMapper.getPageId(this._pageIndex + 1) - 1, 15852 pageIndex: this._pageIndex, 15853 intent: renderingIntent, 15854 cacheKey, 15855 annotationStorage: map, 15856 modifiedIds 15857 }, undefined, transfer); 15858 const reader = readableStream.getReader(); 15859 const intentState = this._intentStates.get(cacheKey); 15860 intentState.streamReader = reader; 15861 const pump = () => { 15862 reader.read().then(({ 15863 value, 15864 done 15865 }) => { 15866 if (done) { 15867 intentState.streamReader = null; 15868 return; 15869 } 15870 if (this._transport.destroyed) { 15871 return; 15872 } 15873 this._renderPageChunk(value, intentState); 15874 pump(); 15875 }, reason => { 15876 intentState.streamReader = null; 15877 if (this._transport.destroyed) { 15878 return; 15879 } 15880 if (intentState.operatorList) { 15881 intentState.operatorList.lastChunk = true; 15882 for (const internalRenderTask of intentState.renderTasks) { 15883 internalRenderTask.operatorListChanged(); 15884 } 15885 this.#tryCleanup(); 15886 } 15887 if (intentState.displayReadyCapability) { 15888 intentState.displayReadyCapability.reject(reason); 15889 } else if (intentState.opListReadCapability) { 15890 intentState.opListReadCapability.reject(reason); 15891 } else { 15892 throw reason; 15893 } 15894 }); 15895 }; 15896 pump(); 15897 } 15898 _abortOperatorList({ 15899 intentState, 15900 reason, 15901 force = false 15902 }) { 15903 if (!intentState.streamReader) { 15904 return; 15905 } 15906 if (intentState.streamReaderCancelTimeout) { 15907 clearTimeout(intentState.streamReaderCancelTimeout); 15908 intentState.streamReaderCancelTimeout = null; 15909 } 15910 if (!force) { 15911 if (intentState.renderTasks.size > 0) { 15912 return; 15913 } 15914 if (reason instanceof RenderingCancelledException) { 15915 let delay = RENDERING_CANCELLED_TIMEOUT; 15916 if (reason.extraDelay > 0 && reason.extraDelay < 1000) { 15917 delay += reason.extraDelay; 15918 } 15919 intentState.streamReaderCancelTimeout = setTimeout(() => { 15920 intentState.streamReaderCancelTimeout = null; 15921 this._abortOperatorList({ 15922 intentState, 15923 reason, 15924 force: true 15925 }); 15926 }, delay); 15927 return; 15928 } 15929 } 15930 intentState.streamReader.cancel(new AbortException(reason.message)).catch(() => {}); 15931 intentState.streamReader = null; 15932 if (this._transport.destroyed) { 15933 return; 15934 } 15935 for (const [curCacheKey, curIntentState] of this._intentStates) { 15936 if (curIntentState === intentState) { 15937 this._intentStates.delete(curCacheKey); 15938 break; 15939 } 15940 } 15941 this.cleanup(); 15942 } 15943 get stats() { 15944 return this._stats; 15945 } 15946} 15947class PDFWorker { 15948 #capability = Promise.withResolvers(); 15949 #messageHandler = null; 15950 #port = null; 15951 #webWorker = null; 15952 static #fakeWorkerId = 0; 15953 static #isWorkerDisabled = false; 15954 static #workerPorts = new WeakMap(); 15955 static { 15956 if (isNodeJS) { 15957 this.#isWorkerDisabled = true; 15958 GlobalWorkerOptions.workerSrc ||= "./pdf.worker.mjs"; 15959 } 15960 this._isSameOrigin = (baseUrl, otherUrl) => { 15961 const base = URL.parse(baseUrl); 15962 if (!base?.origin || base.origin === "null") { 15963 return false; 15964 } 15965 const other = new URL(otherUrl, base); 15966 return base.origin === other.origin; 15967 }; 15968 this._createCDNWrapper = url => { 15969 const wrapper = `await import("${url}");`; 15970 return URL.createObjectURL(new Blob([wrapper], { 15971 type: "text/javascript" 15972 })); 15973 }; 15974 } 15975 constructor({ 15976 name = null, 15977 port = null, 15978 verbosity = getVerbosityLevel() 15979 } = {}) { 15980 this.name = name; 15981 this.destroyed = false; 15982 this.verbosity = verbosity; 15983 if (port) { 15984 if (PDFWorker.#workerPorts.has(port)) { 15985 throw new Error("Cannot use more than one PDFWorker per port."); 15986 } 15987 PDFWorker.#workerPorts.set(port, this); 15988 this.#initializeFromPort(port); 15989 } else { 15990 this.#initialize(); 15991 } 15992 } 15993 get promise() { 15994 return this.#capability.promise; 15995 } 15996 #resolve() { 15997 this.#capability.resolve(); 15998 this.#messageHandler.send("configure", { 15999 verbosity: this.verbosity 16000 }); 16001 } 16002 get port() { 16003 return this.#port; 16004 } 16005 get messageHandler() { 16006 return this.#messageHandler; 16007 } 16008 #initializeFromPort(port) { 16009 this.#port = port; 16010 this.#messageHandler = new MessageHandler("main", "worker", port); 16011 this.#messageHandler.on("ready", () => {}); 16012 this.#resolve(); 16013 } 16014 #initialize() { 16015 if (PDFWorker.#isWorkerDisabled || PDFWorker.#mainThreadWorkerMessageHandler) { 16016 this.#setupFakeWorker(); 16017 return; 16018 } 16019 let { 16020 workerSrc 16021 } = PDFWorker; 16022 try { 16023 if (!PDFWorker._isSameOrigin(window.location, workerSrc)) { 16024 workerSrc = PDFWorker._createCDNWrapper(new URL(workerSrc, window.location).href); 16025 } 16026 const worker = new Worker(workerSrc, { 16027 type: "module" 16028 }); 16029 const messageHandler = new MessageHandler("main", "worker", worker); 16030 const terminateEarly = () => { 16031 ac.abort(); 16032 messageHandler.destroy(); 16033 worker.terminate(); 16034 if (this.destroyed) { 16035 this.#capability.reject(new Error("Worker was destroyed")); 16036 } else { 16037 this.#setupFakeWorker(); 16038 } 16039 }; 16040 const ac = new AbortController(); 16041 worker.addEventListener("error", () => { 16042 if (!this.#webWorker) { 16043 terminateEarly(); 16044 } 16045 }, { 16046 signal: ac.signal 16047 }); 16048 messageHandler.on("test", data => { 16049 ac.abort(); 16050 if (this.destroyed || !data) { 16051 terminateEarly(); 16052 return; 16053 } 16054 this.#messageHandler = messageHandler; 16055 this.#port = worker; 16056 this.#webWorker = worker; 16057 this.#resolve(); 16058 }); 16059 messageHandler.on("ready", data => { 16060 ac.abort(); 16061 if (this.destroyed) { 16062 terminateEarly(); 16063 return; 16064 } 16065 try { 16066 sendTest(); 16067 } catch { 16068 this.#setupFakeWorker(); 16069 } 16070 }); 16071 const sendTest = () => { 16072 const testObj = new Uint8Array(); 16073 messageHandler.send("test", testObj, [testObj.buffer]); 16074 }; 16075 sendTest(); 16076 return; 16077 } catch { 16078 info("The worker has been disabled."); 16079 } 16080 this.#setupFakeWorker(); 16081 } 16082 #setupFakeWorker() { 16083 if (!PDFWorker.#isWorkerDisabled) { 16084 warn("Setting up fake worker."); 16085 PDFWorker.#isWorkerDisabled = true; 16086 } 16087 PDFWorker._setupFakeWorkerGlobal.then(WorkerMessageHandler => { 16088 if (this.destroyed) { 16089 this.#capability.reject(new Error("Worker was destroyed")); 16090 return; 16091 } 16092 const port = new LoopbackPort(); 16093 this.#port = port; 16094 const id = `fake${PDFWorker.#fakeWorkerId++}`; 16095 const workerHandler = new MessageHandler(id + "_worker", id, port); 16096 WorkerMessageHandler.setup(workerHandler, port); 16097 this.#messageHandler = new MessageHandler(id, id + "_worker", port); 16098 this.#resolve(); 16099 }).catch(reason => { 16100 this.#capability.reject(new Error(`Setting up fake worker failed: "${reason.message}".`)); 16101 }); 16102 } 16103 destroy() { 16104 this.destroyed = true; 16105 this.#webWorker?.terminate(); 16106 this.#webWorker = null; 16107 PDFWorker.#workerPorts.delete(this.#port); 16108 this.#port = null; 16109 this.#messageHandler?.destroy(); 16110 this.#messageHandler = null; 16111 } 16112 static create(params) { 16113 const cachedPort = this.#workerPorts.get(params?.port); 16114 if (cachedPort) { 16115 if (cachedPort._pendingDestroy) { 16116 throw new Error("PDFWorker.create - the worker is being destroyed.\n" + "Please remember to await `PDFDocumentLoadingTask.destroy()`-calls."); 16117 } 16118 return cachedPort; 16119 } 16120 return new PDFWorker(params); 16121 } 16122 static get workerSrc() { 16123 if (GlobalWorkerOptions.workerSrc) { 16124 return GlobalWorkerOptions.workerSrc; 16125 } 16126 throw new Error('No "GlobalWorkerOptions.workerSrc" specified.'); 16127 } 16128 static get #mainThreadWorkerMessageHandler() { 16129 try { 16130 return globalThis.pdfjsWorker?.WorkerMessageHandler || null; 16131 } catch { 16132 return null; 16133 } 16134 } 16135 static get _setupFakeWorkerGlobal() { 16136 const loader = async () => { 16137 if (this.#mainThreadWorkerMessageHandler) { 16138 return this.#mainThreadWorkerMessageHandler; 16139 } 16140 const worker = await import( 16141 /*webpackIgnore: true*/ 16142 /*@vite-ignore*/ 16143 this.workerSrc); 16144 return worker.WorkerMessageHandler; 16145 }; 16146 return shadow(this, "_setupFakeWorkerGlobal", loader()); 16147 } 16148} 16149class WorkerTransport { 16150 downloadInfoCapability = Promise.withResolvers(); 16151 #fullReader = null; 16152 #methodPromises = new Map(); 16153 #networkStream = null; 16154 #pageCache = new Map(); 16155 #pagePromises = new Map(); 16156 #pageRefCache = new Map(); 16157 #passwordCapability = null; 16158 constructor(messageHandler, loadingTask, networkStream, params, factory, pagesMapper) { 16159 this.messageHandler = messageHandler; 16160 this.loadingTask = loadingTask; 16161 this.#networkStream = networkStream; 16162 this.commonObjs = new PDFObjects(); 16163 this.fontLoader = new FontLoader({ 16164 ownerDocument: params.ownerDocument, 16165 styleElement: params.styleElement 16166 }); 16167 this.enableHWA = params.enableHWA; 16168 this.loadingParams = params.loadingParams; 16169 this._params = params; 16170 this.canvasFactory = factory.canvasFactory; 16171 this.filterFactory = factory.filterFactory; 16172 this.binaryDataFactory = factory.binaryDataFactory; 16173 this.pagesMapper = pagesMapper; 16174 this.destroyed = false; 16175 this.destroyCapability = null; 16176 this.setupMessageHandler(); 16177 } 16178 updatePage(page) { 16179 const { 16180 _pageIndex 16181 } = page; 16182 this.#pageCache.set(_pageIndex, page); 16183 this.#pagePromises.set(_pageIndex, Promise.resolve(page)); 16184 } 16185 #cacheSimpleMethod(name, data = null) { 16186 return this.#methodPromises.getOrInsertComputed(name, () => this.messageHandler.sendWithPromise(name, data)); 16187 } 16188 #onProgress({ 16189 loaded, 16190 total 16191 }) { 16192 this.loadingTask.onProgress?.({ 16193 loaded, 16194 total, 16195 percent: total ? MathClamp(Math.round(loaded / total * 100), 0, 100) : NaN 16196 }); 16197 } 16198 get annotationStorage() { 16199 return shadow(this, "annotationStorage", new AnnotationStorage()); 16200 } 16201 getRenderingIntent(intent, annotationMode = AnnotationMode.ENABLE, printAnnotationStorage = null, isEditing = false, isOpList = false) { 16202 let renderingIntent = RenderingIntentFlag.DISPLAY; 16203 let annotationStorageSerializable = SerializableEmpty; 16204 switch (intent) { 16205 case "any": 16206 renderingIntent = RenderingIntentFlag.ANY; 16207 break; 16208 case "display": 16209 break; 16210 case "print": 16211 renderingIntent = RenderingIntentFlag.PRINT; 16212 break; 16213 default: 16214 warn(`getRenderingIntent - invalid intent: ${intent}`); 16215 } 16216 const annotationStorage = renderingIntent & RenderingIntentFlag.PRINT && printAnnotationStorage instanceof PrintAnnotationStorage ? printAnnotationStorage : this.annotationStorage; 16217 switch (annotationMode) { 16218 case AnnotationMode.DISABLE: 16219 renderingIntent += RenderingIntentFlag.ANNOTATIONS_DISABLE; 16220 break; 16221 case AnnotationMode.ENABLE: 16222 break; 16223 case AnnotationMode.ENABLE_FORMS: 16224 renderingIntent += RenderingIntentFlag.ANNOTATIONS_FORMS; 16225 break; 16226 case AnnotationMode.ENABLE_STORAGE: 16227 renderingIntent += RenderingIntentFlag.ANNOTATIONS_STORAGE; 16228 annotationStorageSerializable = annotationStorage.serializable; 16229 break; 16230 default: 16231 warn(`getRenderingIntent - invalid annotationMode: ${annotationMode}`); 16232 } 16233 if (isEditing) { 16234 renderingIntent += RenderingIntentFlag.IS_EDITING; 16235 } 16236 if (isOpList) { 16237 renderingIntent += RenderingIntentFlag.OPLIST; 16238 } 16239 const { 16240 ids: modifiedIds, 16241 hash: modifiedIdsHash 16242 } = annotationStorage.modifiedIds; 16243 const cacheKeyBuf = [renderingIntent, annotationStorageSerializable.hash, modifiedIdsHash]; 16244 return { 16245 renderingIntent, 16246 cacheKey: cacheKeyBuf.join("_"), 16247 annotationStorageSerializable, 16248 modifiedIds 16249 }; 16250 } 16251 destroy() { 16252 if (this.destroyCapability) { 16253 return this.destroyCapability.promise; 16254 } 16255 this.destroyed = true; 16256 this.destroyCapability = Promise.withResolvers(); 16257 this.#passwordCapability?.reject(new Error("Worker was destroyed during onPassword callback")); 16258 const waitOn = []; 16259 for (const page of this.#pageCache.values()) { 16260 waitOn.push(page._destroy()); 16261 } 16262 this.#pageCache.clear(); 16263 this.#pagePromises.clear(); 16264 this.#pageRefCache.clear(); 16265 if (Object.hasOwn(this, "annotationStorage")) { 16266 this.annotationStorage.resetModified(); 16267 } 16268 const terminated = this.messageHandler.sendWithPromise("Terminate", null); 16269 waitOn.push(terminated); 16270 Promise.all(waitOn).then(() => { 16271 this.commonObjs.clear(); 16272 this.fontLoader.clear(); 16273 this.#methodPromises.clear(); 16274 this.filterFactory.destroy(); 16275 TextLayer.cleanup(); 16276 this.#networkStream?.cancelAllRequests(new AbortException("Worker was terminated.")); 16277 this.messageHandler?.destroy(); 16278 this.messageHandler = null; 16279 this.destroyCapability.resolve(); 16280 }, this.destroyCapability.reject); 16281 return this.destroyCapability.promise; 16282 } 16283 setupMessageHandler() { 16284 const { 16285 messageHandler, 16286 loadingTask 16287 } = this; 16288 messageHandler.on("GetReader", (data, sink) => { 16289 assert(this.#networkStream, "GetReader - no `BasePDFStream` instance available."); 16290 this.#fullReader = this.#networkStream.getFullReader(); 16291 this.#fullReader.onProgress = evt => this.#onProgress(evt); 16292 sink.onPull = () => { 16293 this.#fullReader.read().then(function ({ 16294 value, 16295 done 16296 }) { 16297 if (done) { 16298 sink.close(); 16299 return; 16300 } 16301 assert(value instanceof ArrayBuffer, "GetReader - expected an ArrayBuffer."); 16302 sink.enqueue(new Uint8Array(value), 1, [value]); 16303 }).catch(reason => { 16304 sink.error(reason); 16305 }); 16306 }; 16307 sink.onCancel = reason => { 16308 this.#fullReader.cancel(reason); 16309 sink.ready.catch(readyReason => { 16310 if (this.destroyed) { 16311 return; 16312 } 16313 throw readyReason; 16314 }); 16315 }; 16316 }); 16317 messageHandler.on("ReaderHeadersReady", async data => { 16318 await this.#fullReader.headersReady; 16319 const { 16320 isStreamingSupported, 16321 isRangeSupported, 16322 contentLength 16323 } = this.#fullReader; 16324 if (isStreamingSupported && isRangeSupported) { 16325 this.#fullReader.onProgress = null; 16326 } 16327 return { 16328 isStreamingSupported, 16329 isRangeSupported, 16330 contentLength 16331 }; 16332 }); 16333 messageHandler.on("GetRangeReader", (data, sink) => { 16334 assert(this.#networkStream, "GetRangeReader - no `BasePDFStream` instance available."); 16335 const rangeReader = this.#networkStream.getRangeReader(data.begin, data.end); 16336 if (!rangeReader) { 16337 sink.close(); 16338 return; 16339 } 16340 sink.onPull = () => { 16341 rangeReader.read().then(function ({ 16342 value, 16343 done 16344 }) { 16345 if (done) { 16346 sink.close(); 16347 return; 16348 } 16349 assert(value instanceof ArrayBuffer, "GetRangeReader - expected an ArrayBuffer."); 16350 sink.enqueue(new Uint8Array(value), 1, [value]); 16351 }).catch(reason => { 16352 sink.error(reason); 16353 }); 16354 }; 16355 sink.onCancel = reason => { 16356 rangeReader.cancel(reason); 16357 sink.ready.catch(readyReason => { 16358 if (this.destroyed) { 16359 return; 16360 } 16361 throw readyReason; 16362 }); 16363 }; 16364 }); 16365 messageHandler.on("GetDoc", ({ 16366 pdfInfo 16367 }) => { 16368 this.pagesMapper.pagesNumber = pdfInfo.numPages; 16369 this._numPages = pdfInfo.numPages; 16370 this._htmlForXfa = pdfInfo.htmlForXfa; 16371 delete pdfInfo.htmlForXfa; 16372 loadingTask._capability.resolve(new PDFDocumentProxy(pdfInfo, this)); 16373 }); 16374 messageHandler.on("DocException", ex => { 16375 loadingTask._capability.reject(wrapReason(ex)); 16376 }); 16377 messageHandler.on("PasswordRequest", ex => { 16378 this.#passwordCapability = Promise.withResolvers(); 16379 try { 16380 if (!loadingTask.onPassword) { 16381 throw wrapReason(ex); 16382 } 16383 const updatePassword = password => { 16384 if (password instanceof Error) { 16385 this.#passwordCapability.reject(password); 16386 } else { 16387 this.#passwordCapability.resolve({ 16388 password 16389 }); 16390 } 16391 }; 16392 loadingTask.onPassword(updatePassword, ex.code); 16393 } catch (err) { 16394 this.#passwordCapability.reject(err); 16395 } 16396 return this.#passwordCapability.promise; 16397 }); 16398 messageHandler.on("DataLoaded", data => { 16399 this.#onProgress({ 16400 loaded: data.length, 16401 total: data.length 16402 }); 16403 this.downloadInfoCapability.resolve(data); 16404 }); 16405 messageHandler.on("StartRenderPage", data => { 16406 if (this.destroyed) { 16407 return; 16408 } 16409 const page = this.#pageCache.get(data.pageIndex); 16410 page._startRenderPage(data.transparency, data.cacheKey); 16411 }); 16412 messageHandler.on("commonobj", ([id, type, exportedData]) => { 16413 if (this.destroyed) { 16414 return null; 16415 } 16416 if (this.commonObjs.has(id)) { 16417 return null; 16418 } 16419 switch (type) { 16420 case "Font": 16421 if ("error" in exportedData) { 16422 const exportedError = exportedData.error; 16423 warn(`Error during font loading: ${exportedError}`); 16424 this.commonObjs.resolve(id, exportedError); 16425 break; 16426 } 16427 const fontData = new FontInfo(exportedData); 16428 const inspectFont = this._params.pdfBug && globalThis.FontInspector?.enabled ? (font, url) => globalThis.FontInspector.fontAdded(font, url) : null; 16429 const font = new FontFaceObject(fontData, inspectFont, exportedData.charProcOperatorList, exportedData.extra); 16430 this.fontLoader.bind(font).catch(() => messageHandler.sendWithPromise("FontFallback", { 16431 id 16432 })).finally(() => { 16433 if (!font.fontExtraProperties) { 16434 font.clearData(); 16435 } 16436 this.commonObjs.resolve(id, font); 16437 }); 16438 break; 16439 case "CopyLocalImage": 16440 const { 16441 imageRef 16442 } = exportedData; 16443 assert(imageRef, "The imageRef must be defined."); 16444 for (const pageProxy of this.#pageCache.values()) { 16445 for (const [, data] of pageProxy.objs) { 16446 if (data?.ref !== imageRef) { 16447 continue; 16448 } 16449 if (!data.dataLen) { 16450 return null; 16451 } 16452 const copy = structuredClone(data); 16453 this.commonObjs.resolve(id, copy); 16454 return data.dataLen; 16455 } 16456 } 16457 break; 16458 case "FontPath": 16459 this.commonObjs.resolve(id, new FontPathInfo(exportedData)); 16460 break; 16461 case "Image": 16462 this.commonObjs.resolve(id, exportedData); 16463 break; 16464 case "Pattern": 16465 const pattern = new PatternInfo(exportedData); 16466 this.commonObjs.resolve(id, pattern.getIR()); 16467 break; 16468 default: 16469 throw new Error(`Got unknown common object type ${type}`); 16470 } 16471 return null; 16472 }); 16473 messageHandler.on("obj", ([id, pageIndex, type, imageData]) => { 16474 if (this.destroyed) { 16475 return; 16476 } 16477 const pageProxy = this.#pageCache.get(pageIndex); 16478 if (pageProxy.objs.has(id)) { 16479 return; 16480 } 16481 if (pageProxy._intentStates.size === 0) { 16482 imageData?.bitmap?.close(); 16483 return; 16484 } 16485 switch (type) { 16486 case "Image": 16487 case "Pattern": 16488 pageProxy.objs.resolve(id, imageData); 16489 break; 16490 default: 16491 throw new Error(`Got unknown object type ${type}`); 16492 } 16493 }); 16494 messageHandler.on("DocProgress", data => { 16495 if (this.destroyed) { 16496 return; 16497 } 16498 this.#onProgress(data); 16499 }); 16500 messageHandler.on("FetchBinaryData", async data => { 16501 if (this.destroyed) { 16502 throw new Error("Worker was destroyed."); 16503 } 16504 if (!this.binaryDataFactory) { 16505 throw new Error("`BinaryDataFactory` not initialized, see the `useWorkerFetch` parameter."); 16506 } 16507 return this.binaryDataFactory.fetch(data); 16508 }); 16509 } 16510 getData() { 16511 return this.messageHandler.sendWithPromise("GetData", null); 16512 } 16513 saveDocument() { 16514 if (this.annotationStorage.size <= 0) { 16515 warn("saveDocument called while `annotationStorage` is empty, " + "please use the getData-method instead."); 16516 } 16517 const { 16518 map, 16519 transfer 16520 } = this.annotationStorage.serializable; 16521 return this.messageHandler.sendWithPromise("SaveDocument", { 16522 isPureXfa: !!this._htmlForXfa, 16523 numPages: this._numPages, 16524 annotationStorage: map, 16525 filename: this.#fullReader?.filename ?? null 16526 }, transfer).finally(() => { 16527 this.annotationStorage.resetModified(); 16528 }); 16529 } 16530 extractPages(pageInfos
vendor: 28,028 bytes, lines 16530-17416
16530) { 16531 const params = { 16532 pageInfos 16533 }; 16534 let transfer; 16535 const ImageBitmapCtor = globalThis.ImageBitmap; 16536 if (typeof ImageBitmapCtor === "function") { 16537 const infos = Array.isArray(pageInfos) ? pageInfos : [pageInfos]; 16538 for (const pageInfo of infos) { 16539 if (pageInfo?.image instanceof ImageBitmapCtor) { 16540 (transfer ||= []).push(pageInfo.image); 16541 } 16542 } 16543 } 16544 if (this.annotationStorage.size > 0) { 16545 const serialized = this.annotationStorage.serializable; 16546 let { 16547 map 16548 } = serialized; 16549 if (serialized.transfer?.length) { 16550 if (transfer) { 16551 transfer.push(...serialized.transfer); 16552 } else { 16553 transfer = serialized.transfer; 16554 } 16555 } 16556 const mapping = this.pagesMapper.getMapping(); 16557 if (mapping) { 16558 const remapped = new Map(); 16559 for (const [k, v] of map) { 16560 if (v?.pageIndex !== undefined && v.pageIndex >= 0 && v.pageIndex < mapping.length) { 16561 const sourceIdx = mapping[v.pageIndex] - 1; 16562 if (sourceIdx !== v.pageIndex) { 16563 remapped.set(k, { 16564 ...v, 16565 pageIndex: sourceIdx 16566 }); 16567 continue; 16568 } 16569 } 16570 remapped.set(k, v); 16571 } 16572 map = remapped; 16573 } 16574 params.annotationStorage = map; 16575 } 16576 return this.messageHandler.sendWithPromise("ExtractPages", params, transfer).finally(() => { 16577 this.annotationStorage.resetModified(); 16578 }); 16579 } 16580 getPage(pageNumber) { 16581 if (!Number.isInteger(pageNumber) || pageNumber <= 0 || pageNumber > this.pagesMapper.pagesNumber) { 16582 return Promise.reject(new Error("Invalid page request.")); 16583 } 16584 const pageIndex = pageNumber - 1; 16585 const newPageIndex = this.pagesMapper.getPageId(pageNumber) - 1; 16586 const cachedPromise = this.#pagePromises.get(pageIndex); 16587 if (cachedPromise) { 16588 return cachedPromise; 16589 } 16590 const promise = this.messageHandler.sendWithPromise("GetPage", { 16591 pageIndex: newPageIndex 16592 }).then(pageInfo => { 16593 if (this.destroyed) { 16594 throw new Error("Transport destroyed"); 16595 } 16596 if (pageInfo.refStr) { 16597 this.#pageRefCache.set(pageInfo.refStr, newPageIndex); 16598 } 16599 const page = new PDFPageProxy(pageIndex, pageInfo, this, this.pagesMapper, this._params.pdfBug); 16600 this.#pageCache.set(pageIndex, page); 16601 return page; 16602 }); 16603 this.#pagePromises.set(pageIndex, promise); 16604 return promise; 16605 } 16606 async getPageIndex(ref) { 16607 if (!isRefProxy(ref)) { 16608 throw new Error("Invalid pageIndex request."); 16609 } 16610 const index = await this.messageHandler.sendWithPromise("GetPageIndex", { 16611 num: ref.num, 16612 gen: ref.gen 16613 }); 16614 const pageNumber = this.pagesMapper.getPageNumber(index + 1); 16615 if (pageNumber === 0) { 16616 throw new Error("GetPageIndex: page has been removed."); 16617 } 16618 return pageNumber - 1; 16619 } 16620 getAnnotations(pageIndex, intent) { 16621 return this.messageHandler.sendWithPromise("GetAnnotations", { 16622 pageIndex: this.pagesMapper.getPageId(pageIndex + 1) - 1, 16623 intent 16624 }); 16625 } 16626 getFieldObjects() { 16627 return this.#cacheSimpleMethod("GetFieldObjects"); 16628 } 16629 hasJSActions() { 16630 return this.#cacheSimpleMethod("HasJSActions"); 16631 } 16632 getCalculationOrderIds() { 16633 return this.messageHandler.sendWithPromise("GetCalculationOrderIds", null); 16634 } 16635 getDestinations() { 16636 return this.messageHandler.sendWithPromise("GetDestinations", null); 16637 } 16638 getDestination(id) { 16639 if (typeof id !== "string") { 16640 return Promise.reject(new Error("Invalid destination request.")); 16641 } 16642 return this.messageHandler.sendWithPromise("GetDestination", { 16643 id 16644 }); 16645 } 16646 getPageLabels() { 16647 return this.messageHandler.sendWithPromise("GetPageLabels", null); 16648 } 16649 getPageLayout() { 16650 return this.messageHandler.sendWithPromise("GetPageLayout", null); 16651 } 16652 getPageMode() { 16653 return this.messageHandler.sendWithPromise("GetPageMode", null); 16654 } 16655 getViewerPreferences() { 16656 return this.messageHandler.sendWithPromise("GetViewerPreferences", null); 16657 } 16658 getOpenAction() { 16659 return this.messageHandler.sendWithPromise("GetOpenAction", null); 16660 } 16661 getAttachments() { 16662 return this.messageHandler.sendWithPromise("GetAttachments", null); 16663 } 16664 getAttachmentContent(id) { 16665 return this.messageHandler.sendWithPromise("GetAttachmentContent", id); 16666 } 16667 getAnnotationsByType(types, pageIndexesToSkip) { 16668 return this.messageHandler.sendWithPromise("GetAnnotationsByType", { 16669 types, 16670 pageIndexesToSkip 16671 }); 16672 } 16673 getDocJSActions() { 16674 return this.#cacheSimpleMethod("GetDocJSActions"); 16675 } 16676 getPageJSActions(pageIndex) { 16677 return this.messageHandler.sendWithPromise("GetPageJSActions", { 16678 pageIndex: this.pagesMapper.getPageId(pageIndex + 1) - 1 16679 }); 16680 } 16681 getStructTree(pageIndex) { 16682 return this.messageHandler.sendWithPromise("GetStructTree", { 16683 pageIndex: this.pagesMapper.getPageId(pageIndex + 1) - 1 16684 }); 16685 } 16686 getOutline() { 16687 return this.messageHandler.sendWithPromise("GetOutline", null); 16688 } 16689 getOptionalContentConfig(renderingIntent) { 16690 return this.#cacheSimpleMethod("GetOptionalContentConfig").then(data => new OptionalContentConfig(data, renderingIntent)); 16691 } 16692 getPermissions() { 16693 return this.messageHandler.sendWithPromise("GetPermissions", null); 16694 } 16695 getMetadata() { 16696 const name = "GetMetadata"; 16697 return this.#methodPromises.getOrInsertComputed(name, () => this.messageHandler.sendWithPromise(name, null).then(results => ({ 16698 info: results[0], 16699 metadata: results[1] ? new Metadata(results[1]) : null, 16700 contentDispositionFilename: this.#fullReader?.filename ?? null, 16701 contentLength: this.#fullReader?.contentLength ?? null, 16702 hasStructTree: results[2] 16703 }))); 16704 } 16705 getMarkInfo() { 16706 return this.messageHandler.sendWithPromise("GetMarkInfo", null); 16707 } 16708 getRawData(data) { 16709 return this.messageHandler.sendWithPromise("GetRawData", data); 16710 } 16711 async startCleanup(keepLoadedFonts = false) { 16712 if (this.destroyed) { 16713 return; 16714 } 16715 await this.messageHandler.sendWithPromise("Cleanup", null); 16716 for (const page of this.#pageCache.values()) { 16717 const cleanupSuccessful = page.cleanup(); 16718 if (!cleanupSuccessful) { 16719 throw new Error(`startCleanup: Page ${page.pageNumber} is currently rendering.`); 16720 } 16721 } 16722 this.commonObjs.clear(); 16723 if (!keepLoadedFonts) { 16724 this.fontLoader.clear(); 16725 } 16726 this.#methodPromises.clear(); 16727 this.filterFactory.destroy(true); 16728 TextLayer.cleanup(); 16729 } 16730 cachedPageNumber(ref) { 16731 if (!isRefProxy(ref)) { 16732 return null; 16733 } 16734 const refStr = ref.gen === 0 ? `${ref.num}R` : `${ref.num}R${ref.gen}`; 16735 const pageIndex = this.#pageRefCache.get(refStr); 16736 if (pageIndex >= 0) { 16737 const pageNumber = this.pagesMapper.getPageNumber(pageIndex + 1); 16738 if (pageNumber !== 0) { 16739 return pageNumber; 16740 } 16741 } 16742 return null; 16743 } 16744} 16745class RenderTask { 16746 _internalRenderTask = null; 16747 onContinue = null; 16748 onError = null; 16749 constructor(internalRenderTask) { 16750 this._internalRenderTask = internalRenderTask; 16751 } 16752 get promise() { 16753 return this._internalRenderTask.capability.promise; 16754 } 16755 cancel(extraDelay = 0) { 16756 this._internalRenderTask.cancel(null, extraDelay); 16757 } 16758 get separateAnnots() { 16759 const { 16760 separateAnnots 16761 } = this._internalRenderTask.operatorList; 16762 if (!separateAnnots) { 16763 return false; 16764 } 16765 const { 16766 annotationCanvasMap 16767 } = this._internalRenderTask; 16768 return separateAnnots.form || separateAnnots.canvas && annotationCanvasMap?.size > 0; 16769 } 16770 get imageCoordinates() { 16771 return this._internalRenderTask.imageCoordinates || null; 16772 } 16773} 16774class InternalRenderTask { 16775 #rAF = null; 16776 static #canvasInUse = new WeakSet(); 16777 constructor({ 16778 callback, 16779 params, 16780 objs, 16781 commonObjs, 16782 annotationCanvasMap, 16783 operatorList, 16784 pageIndex, 16785 canvasFactory, 16786 filterFactory, 16787 useRequestAnimationFrame = false, 16788 pdfBug = false, 16789 pageColors = null, 16790 enableHWA = false, 16791 operationsFilter = null 16792 }) { 16793 this.callback = callback; 16794 this.params = params; 16795 this.objs = objs; 16796 this.commonObjs = commonObjs; 16797 this.annotationCanvasMap = annotationCanvasMap; 16798 this.operatorListIdx = null; 16799 this.operatorList = operatorList; 16800 this._pageIndex = pageIndex; 16801 this.canvasFactory = canvasFactory; 16802 this.filterFactory = filterFactory; 16803 this._pdfBug = pdfBug; 16804 this.pageColors = pageColors; 16805 this.running = false; 16806 this.graphicsReadyCallback = null; 16807 this.graphicsReady = false; 16808 this._useRequestAnimationFrame = useRequestAnimationFrame === true && typeof window !== "undefined"; 16809 this.cancelled = false; 16810 this.capability = Promise.withResolvers(); 16811 this.task = new RenderTask(this); 16812 this._cancelBound = this.cancel.bind(this); 16813 this._continueBound = this._continue.bind(this); 16814 this._scheduleNextBound = this._scheduleNext.bind(this); 16815 this._nextBound = this._next.bind(this); 16816 this._canvas = params.canvas; 16817 this._canvasContext = params.canvas ? null : params.canvasContext; 16818 this._enableHWA = enableHWA; 16819 this._dependencyTracker = params.dependencyTracker; 16820 this._imagesTracker = params.imagesTracker; 16821 this._operationsFilter = operationsFilter; 16822 } 16823 get completed() { 16824 return this.capability.promise.catch(function () {}); 16825 } 16826 initializeGraphics({ 16827 transparency = false, 16828 optionalContentConfig 16829 }) { 16830 if (this.cancelled) { 16831 return; 16832 } 16833 if (this._canvas) { 16834 if (InternalRenderTask.#canvasInUse.has(this._canvas)) { 16835 throw new Error("Cannot use the same canvas during multiple render() operations. " + "Use different canvas or ensure previous operations were " + "cancelled or completed."); 16836 } 16837 InternalRenderTask.#canvasInUse.add(this._canvas); 16838 } 16839 if (this._pdfBug && globalThis.StepperManager?.enabled) { 16840 this.stepper = globalThis.StepperManager.create(this._pageIndex); 16841 this.stepper.init(this.operatorList); 16842 this.stepper.nextBreakPoint = this.stepper.getNextBreakPoint(); 16843 } 16844 const { 16845 viewport, 16846 transform, 16847 background, 16848 dependencyTracker, 16849 imagesTracker 16850 } = this.params; 16851 const canvasContext = this._canvasContext || this._canvas.getContext("2d", { 16852 alpha: false, 16853 willReadFrequently: !this._enableHWA 16854 }); 16855 this.gfx = new CanvasGraphics(canvasContext, this.commonObjs, this.objs, this.canvasFactory, this.filterFactory, { 16856 optionalContentConfig 16857 }, this.annotationCanvasMap, this.pageColors, dependencyTracker, imagesTracker); 16858 this.gfx.beginDrawing({ 16859 transform, 16860 viewport, 16861 transparency, 16862 background 16863 }); 16864 this.operatorListIdx = 0; 16865 this.graphicsReady = true; 16866 this.graphicsReadyCallback?.(); 16867 } 16868 cancel(error = null, extraDelay = 0) { 16869 this.running = false; 16870 this.cancelled = true; 16871 this.gfx?.endDrawing(); 16872 if (this.#rAF) { 16873 window.cancelAnimationFrame(this.#rAF); 16874 this.#rAF = null; 16875 } 16876 InternalRenderTask.#canvasInUse.delete(this._canvas); 16877 error ||= new RenderingCancelledException(`Rendering cancelled, page ${this._pageIndex + 1}`, extraDelay); 16878 this.callback(error); 16879 this.task.onError?.(error); 16880 } 16881 operatorListChanged() { 16882 if (!this.graphicsReady) { 16883 this.graphicsReadyCallback ||= this._continueBound; 16884 return; 16885 } 16886 this.gfx.dependencyTracker?.growOperationsCount(this.operatorList.fnArray.length); 16887 this.stepper?.updateOperatorList(this.operatorList); 16888 if (this.running) { 16889 return; 16890 } 16891 this._continue(); 16892 } 16893 _continue() { 16894 this.running = true; 16895 if (this.cancelled) { 16896 return; 16897 } 16898 if (this.task.onContinue) { 16899 this.task.onContinue(this._scheduleNextBound); 16900 } else { 16901 this._scheduleNext(); 16902 } 16903 } 16904 _scheduleNext() { 16905 if (this._useRequestAnimationFrame) { 16906 this.#rAF = window.requestAnimationFrame(() => { 16907 this.#rAF = null; 16908 this._nextBound().catch(this._cancelBound); 16909 }); 16910 } else { 16911 Promise.resolve().then(this._nextBound).catch(this._cancelBound); 16912 } 16913 } 16914 async _next() { 16915 if (this.cancelled) { 16916 return; 16917 } 16918 this.operatorListIdx = this.gfx.executeOperatorList(this.operatorList, this.operatorListIdx, this._continueBound, this.stepper, this._operationsFilter); 16919 if (this.operatorListIdx === this.operatorList.argsArray.length) { 16920 this.running = false; 16921 if (this.operatorList.lastChunk) { 16922 this.gfx.endDrawing(); 16923 InternalRenderTask.#canvasInUse.delete(this._canvas); 16924 this.callback(); 16925 } 16926 } 16927 } 16928} 16929const version = "6.1.200"; 16930const build = "6353acefe"; 16931 16932;// ./src/display/editor/color_picker.js 16933 16934 16935 16936class ColorPicker { 16937 #button = null; 16938 #buttonSwatch = null; 16939 #defaultColor; 16940 #dropdown = null; 16941 #dropdownWasFromKeyboard = false; 16942 #isMainColorPicker = false; 16943 #editor = null; 16944 #eventBus; 16945 #openDropdownAC = null; 16946 #uiManager = null; 16947 static #l10nColor = null; 16948 static get _keyboardManager() { 16949 return shadow(this, "_keyboardManager", new KeyboardManager([[["Escape"], ColorPicker.prototype._hideDropdownFromKeyboard], [["Space"], ColorPicker.prototype._colorSelectFromKeyboard], [["ArrowDown", "ArrowRight"], ColorPicker.prototype._moveToNext], [["ArrowUp", "ArrowLeft"], ColorPicker.prototype._moveToPrevious], [["Home"], ColorPicker.prototype._moveToBeginning], [["End"], ColorPicker.prototype._moveToEnd]])); 16950 } 16951 constructor({ 16952 editor = null, 16953 uiManager = null 16954 }) { 16955 if (editor) { 16956 this.#isMainColorPicker = false; 16957 this.#editor = editor; 16958 } else { 16959 this.#isMainColorPicker = true; 16960 } 16961 this.#uiManager = editor?._uiManager || uiManager; 16962 this.#eventBus = this.#uiManager._eventBus; 16963 this.#defaultColor = editor?.color?.toUpperCase() || this.#uiManager?.highlightColors.values().next().value || "#FFFF98"; 16964 ColorPicker.#l10nColor ||= Object.freeze({ 16965 blue: "pdfjs-editor-colorpicker-blue", 16966 green: "pdfjs-editor-colorpicker-green", 16967 pink: "pdfjs-editor-colorpicker-pink", 16968 red: "pdfjs-editor-colorpicker-red", 16969 yellow: "pdfjs-editor-colorpicker-yellow" 16970 }); 16971 } 16972 renderButton() { 16973 const button = this.#button = document.createElement("button"); 16974 button.className = "colorPicker"; 16975 button.tabIndex = "0"; 16976 button.setAttribute("data-l10n-id", "pdfjs-editor-colorpicker-button"); 16977 button.ariaHasPopup = "true"; 16978 if (this.#editor) { 16979 button.ariaControls = `${this.#editor.id}_colorpicker_dropdown`; 16980 } 16981 const signal = this.#uiManager._signal; 16982 button.addEventListener("click", this.#openDropdown.bind(this), { 16983 signal 16984 }); 16985 button.addEventListener("keydown", this.#keyDown.bind(this), { 16986 signal 16987 }); 16988 const swatch = this.#buttonSwatch = document.createElement("span"); 16989 swatch.className = "swatch"; 16990 swatch.ariaHidden = "true"; 16991 swatch.style.backgroundColor = this.#defaultColor; 16992 button.append(swatch); 16993 return button; 16994 } 16995 renderMainDropdown() { 16996 const dropdown = this.#dropdown = this.#getDropdownRoot(); 16997 dropdown.ariaOrientation = "horizontal"; 16998 dropdown.ariaLabelledBy = "highlightColorPickerLabel"; 16999 return dropdown; 17000 } 17001 #getDropdownRoot() { 17002 const div = document.createElement("div"); 17003 const signal = this.#uiManager._signal; 17004 div.addEventListener("contextmenu", noContextMenu, { 17005 signal 17006 }); 17007 div.className = "dropdown"; 17008 div.role = "listbox"; 17009 div.ariaMultiSelectable = "false"; 17010 div.ariaOrientation = "vertical"; 17011 div.setAttribute("data-l10n-id", "pdfjs-editor-colorpicker-dropdown"); 17012 if (this.#editor) { 17013 div.id = `${this.#editor.id}_colorpicker_dropdown`; 17014 } 17015 for (const [name, color] of this.#uiManager.highlightColors) { 17016 const button = document.createElement("button"); 17017 button.tabIndex = "0"; 17018 button.role = "option"; 17019 button.setAttribute("data-color", color); 17020 button.title = name; 17021 button.setAttribute("data-l10n-id", ColorPicker.#l10nColor[name]); 17022 const swatch = document.createElement("span"); 17023 button.append(swatch); 17024 swatch.className = "swatch"; 17025 swatch.style.backgroundColor = color; 17026 button.ariaSelected = color === this.#defaultColor; 17027 button.addEventListener("click", this.#colorSelect.bind(this, color), { 17028 signal 17029 }); 17030 div.append(button); 17031 } 17032 div.addEventListener("keydown", this.#keyDown.bind(this), { 17033 signal 17034 }); 17035 return div; 17036 } 17037 #colorSelect(color, event) { 17038 event.stopPropagation(); 17039 this.#eventBus.dispatch("switchannotationeditorparams", { 17040 source: this, 17041 type: AnnotationEditorParamsType.HIGHLIGHT_COLOR, 17042 value: color 17043 }); 17044 this.updateColor(color); 17045 } 17046 _colorSelectFromKeyboard(event) { 17047 if (event.target === this.#button) { 17048 this.#openDropdown(event); 17049 return; 17050 } 17051 const color = event.target.getAttribute("data-color"); 17052 if (!color) { 17053 return; 17054 } 17055 this.#colorSelect(color, event); 17056 } 17057 _moveToNext(event) { 17058 if (!this.#isDropdownVisible) { 17059 this.#openDropdown(event); 17060 return; 17061 } 17062 if (event.target === this.#button) { 17063 this.#dropdown.firstElementChild?.focus(); 17064 return; 17065 } 17066 event.target.nextSibling?.focus(); 17067 } 17068 _moveToPrevious(event) { 17069 if (event.target === this.#dropdown?.firstElementChild || event.target === this.#button) { 17070 if (this.#isDropdownVisible) { 17071 this._hideDropdownFromKeyboard(); 17072 } 17073 return; 17074 } 17075 if (!this.#isDropdownVisible) { 17076 this.#openDropdown(event); 17077 } 17078 event.target.previousSibling?.focus(); 17079 } 17080 _moveToBeginning(event) { 17081 if (!this.#isDropdownVisible) { 17082 this.#openDropdown(event); 17083 return; 17084 } 17085 this.#dropdown.firstElementChild?.focus(); 17086 } 17087 _moveToEnd(event) { 17088 if (!this.#isDropdownVisible) { 17089 this.#openDropdown(event); 17090 return; 17091 } 17092 this.#dropdown.lastElementChild?.focus(); 17093 } 17094 #keyDown(event) { 17095 ColorPicker._keyboardManager.exec(this, event); 17096 } 17097 #openDropdown(event) { 17098 if (this.#isDropdownVisible) { 17099 this.hideDropdown(); 17100 return; 17101 } 17102 this.#dropdownWasFromKeyboard = event.detail === 0; 17103 if (!this.#openDropdownAC) { 17104 this.#openDropdownAC = new AbortController(); 17105 window.addEventListener("pointerdown", this.#pointerDown.bind(this), { 17106 signal: this.#uiManager.combinedSignal(this.#openDropdownAC) 17107 }); 17108 } 17109 this.#button.ariaExpanded = "true"; 17110 if (this.#dropdown) { 17111 this.#dropdown.classList.remove("hidden"); 17112 return; 17113 } 17114 const root = this.#dropdown = this.#getDropdownRoot(); 17115 this.#button.append(root); 17116 } 17117 #pointerDown(event) { 17118 if (this.#dropdown?.contains(event.target)) { 17119 return; 17120 } 17121 this.hideDropdown(); 17122 } 17123 hideDropdown() { 17124 this.#dropdown?.classList.add("hidden"); 17125 this.#button.ariaExpanded = "false"; 17126 this.#openDropdownAC?.abort(); 17127 this.#openDropdownAC = null; 17128 } 17129 get #isDropdownVisible() { 17130 return this.#dropdown && !this.#dropdown.classList.contains("hidden"); 17131 } 17132 _hideDropdownFromKeyboard() { 17133 if (this.#isMainColorPicker) { 17134 return; 17135 } 17136 if (!this.#isDropdownVisible) { 17137 this.#editor?.unselect(); 17138 return; 17139 } 17140 this.hideDropdown(); 17141 this.#button.focus({ 17142 preventScroll: true, 17143 focusVisible: this.#dropdownWasFromKeyboard 17144 }); 17145 } 17146 updateColor(color) { 17147 if (this.#buttonSwatch) { 17148 this.#buttonSwatch.style.backgroundColor = color; 17149 } 17150 if (!this.#dropdown) { 17151 return; 17152 } 17153 const i = this.#uiManager.highlightColors.values(); 17154 for (const child of this.#dropdown.children) { 17155 child.ariaSelected = i.next().value === color.toUpperCase(); 17156 } 17157 } 17158 destroy() { 17159 this.#button?.remove(); 17160 this.#button = null; 17161 this.#buttonSwatch = null; 17162 this.#dropdown?.remove(); 17163 this.#dropdown = null; 17164 } 17165} 17166class BasicColorPicker { 17167 #input = null; 17168 #hasAlpha = false; 17169 #editor = null; 17170 #uiManager = null; 17171 static #l10nColor = null; 17172 constructor(editor) { 17173 this.#editor = editor; 17174 this.#uiManager = editor._uiManager; 17175 BasicColorPicker.#l10nColor ||= Object.freeze({ 17176 freetext: "pdfjs-editor-color-picker-free-text-input", 17177 ink: "pdfjs-editor-color-picker-ink-input" 17178 }); 17179 } 17180 renderButton() { 17181 if (this.#input) { 17182 return this.#input; 17183 } 17184 const { 17185 editorType, 17186 colorType, 17187 colorAndOpacityType, 17188 opacityType, 17189 color, 17190 opacity 17191 } = this.#editor; 17192 const hasAlpha = this.#hasAlpha = FeatureTest.isAlphaColorInputSupported && opacityType !== undefined; 17193 const input = this.#input = document.createElement("input"); 17194 input.type = "color"; 17195 if (hasAlpha) { 17196 input.setAttribute("alpha", ""); 17197 const alphaHex = Util.hexNums[Math.round((opacity ?? 1) * 255)]; 17198 input.value = (color || "#000000") + alphaHex; 17199 } else { 17200 input.value = color || "#000000"; 17201 } 17202 input.className = "basicColorPicker"; 17203 input.tabIndex = 0; 17204 input.setAttribute("data-l10n-id", BasicColorPicker.#l10nColor[editorType]); 17205 input.addEventListener("input", () => { 17206 if (hasAlpha) { 17207 const rgba = getRGBA(input.value); 17208 if (!rgba) { 17209 return; 17210 } 17211 const [r, g, b, op] = rgba; 17212 const hex = Util.makeHexColor(r, g, b); 17213 if (colorAndOpacityType !== undefined) { 17214 this.#uiManager.updateParams(colorAndOpacityType, { 17215 color: hex, 17216 opacity: op 17217 }); 17218 } else { 17219 this.#uiManager.updateParams(colorType, hex); 17220 this.#uiManager.updateParams(opacityType, op); 17221 } 17222 } else { 17223 this.#uiManager.updateParams(colorType, input.value); 17224 } 17225 }, { 17226 signal: this.#uiManager._signal 17227 }); 17228 return input; 17229 } 17230 update(value) { 17231 if (!this.#input) { 17232 return; 17233 } 17234 if (this.#hasAlpha) { 17235 const alphaHex = Util.hexNums[Math.round(this.#editor.opacity * 255)]; 17236 this.#input.value = value + alphaHex; 17237 } else { 17238 this.#input.value = value; 17239 } 17240 } 17241 updateOpacity(value) { 17242 if (!this.#input || !this.#hasAlpha) { 17243 return; 17244 } 17245 const alphaHex = Util.hexNums[Math.round(value * 255)]; 17246 this.#input.value = this.#editor.color + alphaHex; 17247 } 17248 destroy() { 17249 this.#input?.remove(); 17250 this.#input = null; 17251 } 17252 hideDropdown() {} 17253} 17254 17255;// ./src/shared/scripting_utils.js 17256 17257function makeColorComp(n) { 17258 return Math.floor(MathClamp(n, 0, 1) * 255).toString(16).padStart(2, "0"); 17259} 17260function scaleAndClamp(x) { 17261 return MathClamp(x, 0, 1) * 255; 17262} 17263class ColorConverters { 17264 static CMYK_G([c, y, m, k]) { 17265 return ["G", 1 - Math.min(1, 0.3 * c + 0.59 * m + 0.11 * y + k)]; 17266 } 17267 static G_CMYK([g]) { 17268 return ["CMYK", 0, 0, 0, 1 - g]; 17269 } 17270 static G_RGB([g]) { 17271 return ["RGB", g, g, g]; 17272 } 17273 static G_rgb([g]) { 17274 g = scaleAndClamp(g); 17275 return [g, g, g]; 17276 } 17277 static G_HTML([g]) { 17278 const G = makeColorComp(g); 17279 return `#${G}${G}${G}`; 17280 } 17281 static RGB_G([r, g, b]) { 17282 return ["G", 0.3 * r + 0.59 * g + 0.11 * b]; 17283 } 17284 static RGB_rgb(color) { 17285 return color.map(scaleAndClamp); 17286 } 17287 static RGB_HTML(color) { 17288 return `#${color.map(makeColorComp).join("")}`; 17289 } 17290 static T_HTML() { 17291 return "#00000000"; 17292 } 17293 static T_rgb() { 17294 return [null]; 17295 } 17296 static CMYK_RGB([c, y, m, k]) { 17297 return ["RGB", 1 - Math.min(1, c + k), 1 - Math.min(1, m + k), 1 - Math.min(1, y + k)]; 17298 } 17299 static CMYK_rgb([c, y, m, k]) { 17300 return [scaleAndClamp(1 - Math.min(1, c + k)), scaleAndClamp(1 - Math.min(1, m + k)), scaleAndClamp(1 - Math.min(1, y + k))]; 17301 } 17302 static CMYK_HTML(components) { 17303 const rgb = this.CMYK_RGB(components).slice(1); 17304 return this.RGB_HTML(rgb); 17305 } 17306 static RGB_CMYK([r, g, b]) { 17307 const c = 1 - r; 17308 const m = 1 - g; 17309 const y = 1 - b; 17310 const k = Math.min(c, m, y); 17311 return ["CMYK", c, m, y, k]; 17312 } 17313} 17314const DateFormats = (/* unused pure expression or super */ null && (["m/d", "m/d/yy", "mm/dd/yy", "mm/yy", "d-mmm", "d-mmm-yy", "dd-mmm-yy", "yy-mm-dd", "mmm-yy", "mmmm-yy", "mmm d, yyyy", "mmmm d, yyyy", "m/d/yy h:MM tt", "m/d/yy HH:MM"])); 17315const TimeFormats = (/* unused pure expression or super */ null && (["HH:MM", "h:MM tt", "HH:MM:ss", "h:MM:ss tt"])); 17316 17317;// ./src/display/svg_factory.js 17318 17319class BaseSVGFactory { 17320 create(width, height, skipDimensions = false) { 17321 if (width <= 0 || height <= 0) { 17322 throw new Error("Invalid SVG dimensions"); 17323 } 17324 const svg = this._createSVG("svg:svg"); 17325 svg.setAttribute("version", "1.1"); 17326 if (!skipDimensions) { 17327 svg.setAttribute("width", `${width}px`); 17328 svg.setAttribute("height", `${height}px`); 17329 } 17330 svg.setAttribute("preserveAspectRatio", "none"); 17331 svg.setAttribute("viewBox", `0 0 ${width} ${height}`); 17332 return svg; 17333 } 17334 createElement(type) { 17335 if (typeof type !== "string") { 17336 throw new Error("Invalid SVG element type"); 17337 } 17338 return this._createSVG(type); 17339 } 17340 _createSVG(type) { 17341 unreachable("Abstract method `_createSVG` called."); 17342 } 17343} 17344class DOMSVGFactory extends BaseSVGFactory { 17345 _createSVG(type) { 17346 return document.createElementNS(SVG_NS, type); 17347 } 17348} 17349 17350;// ./src/display/annotation_layer.js 17351 17352 17353 17354 17355 17356const annotation_layer_DEFAULT_FONT_SIZE = 9; 17357const GetElementsByNameSet = new WeakSet(); 17358const TIMEZONE_OFFSET = new Date().getTimezoneOffset() * 60 * 1000; 17359class AnnotationElementFactory { 17360 static create(parameters) { 17361 const subtype = parameters.data.annotationType; 17362 switch (subtype) { 17363 case AnnotationType.LINK: 17364 return new LinkAnnotationElement(parameters); 17365 case AnnotationType.TEXT: 17366 return new TextAnnotationElement(parameters); 17367 case AnnotationType.WIDGET: 17368 const fieldType = parameters.data.fieldType; 17369 switch (fieldType) { 17370 case "Tx": 17371 return new TextWidgetAnnotationElement(parameters); 17372 case "Btn": 17373 if (parameters.data.radioButton) { 17374 return new RadioButtonWidgetAnnotationElement(parameters); 17375 } else if (parameters.data.checkBox) { 17376 return new CheckboxWidgetAnnotationElement(parameters); 17377 } 17378 return new PushButtonWidgetAnnotationElement(parameters); 17379 case "Ch": 17380 return new ChoiceWidgetAnnotationElement(parameters); 17381 case "Sig": 17382 return new SignatureWidgetAnnotationElement(parameters); 17383 } 17384 return new WidgetAnnotationElement(parameters); 17385 case AnnotationType.POPUP: 17386 return new PopupAnnotationElement(parameters); 17387 case AnnotationType.FREETEXT: 17388 return new FreeTextAnnotationElement(parameters); 17389 case AnnotationType.LINE: 17390 return new LineAnnotationElement(parameters); 17391 case AnnotationType.SQUARE: 17392 return new SquareAnnotationElement(parameters); 17393 case AnnotationType.CIRCLE: 17394 return new CircleAnnotationElement(parameters); 17395 case AnnotationType.POLYLINE: 17396 return new PolylineAnnotationElement(parameters); 17397 case AnnotationType.CARET: 17398 return new CaretAnnotationElement(parameters); 17399 case AnnotationType.INK: 17400 return new InkAnnotationElement(parameters); 17401 case AnnotationType.POLYGON: 17402 return new PolygonAnnotationElement(parameters); 17403 case AnnotationType.HIGHLIGHT: 17404 return new HighlightAnnotationElement(parameters); 17405 case AnnotationType.UNDERLINE: 17406 return new UnderlineAnnotationElement(parameters); 17407 case AnnotationType.SQUIGGLY: 17408 return new SquigglyAnnotationElement(parameters); 17409 case AnnotationType.STRIKEOUT: 17410 return new StrikeOutAnnotationElement(parameters); 17411 case AnnotationType.STAMP: 17412 return new StampAnnotationElement(parameters); 17413 case AnnotationType.FILEATTACHMENT: 17414 return new FileAttachmentAnnotationElement(parameters); 17415 case AnnotationType.RICHMEDIA: 17416 case AnnotationType.SCREEN
vendor: 12,826 bytes, lines 17416-17873
17416: 17417 return new MediaAnnotationElement(parameters); 17418 default: 17419 return new AnnotationElement(parameters); 17420 } 17421 } 17422} 17423class AnnotationElement { 17424 #updates = null; 17425 #hasBorder = false; 17426 #popupElement = null; 17427 constructor(parameters, { 17428 isRenderable = false, 17429 ignoreBorder = false, 17430 createQuadrilaterals = false 17431 } = {}) { 17432 this.isRenderable = isRenderable; 17433 this.data = parameters.data; 17434 this.layer = parameters.layer; 17435 this.linkService = parameters.linkService; 17436 this.downloadManager = parameters.downloadManager; 17437 this.imageResourcesPath = parameters.imageResourcesPath; 17438 this.renderForms = parameters.renderForms; 17439 this.svgFactory = parameters.svgFactory; 17440 this.annotationStorage = parameters.annotationStorage; 17441 this.enableComment = parameters.enableComment; 17442 this.enableScripting = parameters.enableScripting; 17443 this.hasJSActions = parameters.hasJSActions; 17444 this._fieldObjects = parameters.fieldObjects; 17445 this.parent = parameters.parent; 17446 this.hasOwnCommentButton = false; 17447 if (isRenderable) { 17448 this.contentElement = this.container = this._createContainer(ignoreBorder); 17449 } 17450 if (createQuadrilaterals) { 17451 this._createQuadrilaterals(); 17452 } 17453 } 17454 static _hasPopupData({ 17455 contentsObj, 17456 richText 17457 }) { 17458 return !!(contentsObj?.str || richText?.str); 17459 } 17460 get _isEditable() { 17461 return this.data.isEditable; 17462 } 17463 get hasPopupData() { 17464 return AnnotationElement._hasPopupData(this.data) || this.enableComment && !!this.commentText; 17465 } 17466 get commentData() { 17467 const { 17468 data 17469 } = this; 17470 const editor = this.annotationStorage?.getEditor(data.id); 17471 if (editor) { 17472 return editor.getData(); 17473 } 17474 return data; 17475 } 17476 get hasCommentButton() { 17477 return this.enableComment && this.hasPopupElement; 17478 } 17479 get commentButtonPosition() { 17480 const editor = this.annotationStorage?.getEditor(this.data.id); 17481 if (editor) { 17482 return editor.commentButtonPositionInPage; 17483 } 17484 const { 17485 quadPoints, 17486 inkLists, 17487 rect 17488 } = this.data; 17489 let maxX = -Infinity; 17490 let maxY = -Infinity; 17491 if (quadPoints?.length >= 8) { 17492 for (let i = 0; i < quadPoints.length; i += 8) { 17493 if (quadPoints[i + 1] > maxY) { 17494 maxY = quadPoints[i + 1]; 17495 maxX = quadPoints[i + 2]; 17496 } else if (quadPoints[i + 1] === maxY) { 17497 maxX = Math.max(maxX, quadPoints[i + 2]); 17498 } 17499 } 17500 return [maxX, maxY]; 17501 } 17502 if (inkLists?.length >= 1) { 17503 for (const inkList of inkLists) { 17504 for (let i = 0, ii = inkList.length; i < ii; i += 2) { 17505 if (inkList[i + 1] > maxY) { 17506 maxY = inkList[i + 1]; 17507 maxX = inkList[i]; 17508 } else if (inkList[i + 1] === maxY) { 17509 maxX = Math.max(maxX, inkList[i]); 17510 } 17511 } 17512 } 17513 if (maxX !== Infinity) { 17514 return [maxX, maxY]; 17515 } 17516 } 17517 if (rect) { 17518 return [rect[2], rect[3]]; 17519 } 17520 return null; 17521 } 17522 _normalizePoint(point) { 17523 const { 17524 page: { 17525 view 17526 }, 17527 viewport: { 17528 rawDims: { 17529 pageWidth, 17530 pageHeight, 17531 pageX, 17532 pageY 17533 } 17534 } 17535 } = this.parent; 17536 point[1] = view[3] - point[1] + view[1]; 17537 point[0] = 100 * (point[0] - pageX) / pageWidth; 17538 point[1] = 100 * (point[1] - pageY) / pageHeight; 17539 return point; 17540 } 17541 get commentText() { 17542 const { 17543 data 17544 } = this; 17545 return this.annotationStorage.getRawValue(`${AnnotationEditorPrefix}${data.id}`)?.popup?.contents || data.contentsObj?.str || ""; 17546 } 17547 set commentText(text) { 17548 const { 17549 data 17550 } = this; 17551 const popup = { 17552 deleted: !text, 17553 contents: text || "" 17554 }; 17555 if (!this.annotationStorage.updateEditor(data.id, { 17556 popup 17557 })) { 17558 this.annotationStorage.setValue(`${AnnotationEditorPrefix}${data.id}`, { 17559 id: data.id, 17560 annotationType: data.annotationType, 17561 page: this.parent.page, 17562 popup, 17563 popupRef: data.popupRef, 17564 modificationDate: new Date() 17565 }); 17566 } 17567 if (!text) { 17568 this.removePopup(); 17569 } 17570 } 17571 removePopup() { 17572 (this.#popupElement?.popup || this.popup)?.remove(); 17573 this.#popupElement = this.popup = null; 17574 } 17575 updateEdited(params) { 17576 if (!this.container) { 17577 return; 17578 } 17579 if (params.rect) { 17580 this.#updates ||= { 17581 rect: this.data.rect.slice(0) 17582 }; 17583 } 17584 const { 17585 rect, 17586 popup: newPopup 17587 } = params; 17588 if (rect) { 17589 this.#setRectEdited(rect); 17590 } 17591 let popup = this.#popupElement?.popup || this.popup; 17592 if (!popup && newPopup?.text) { 17593 this._createPopup(newPopup); 17594 popup = this.#popupElement.popup; 17595 } 17596 if (!popup) { 17597 return; 17598 } 17599 popup.updateEdited(params); 17600 if (newPopup?.deleted) { 17601 popup.remove(); 17602 this.#popupElement = null; 17603 this.popup = null; 17604 } 17605 } 17606 resetEdited() { 17607 if (!this.#updates) { 17608 return; 17609 } 17610 this.#setRectEdited(this.#updates.rect); 17611 this.#popupElement?.popup.resetEdited(); 17612 this.#updates = null; 17613 } 17614 #setRectEdited(rect) { 17615 const { 17616 container: { 17617 style 17618 }, 17619 data: { 17620 rect: currentRect, 17621 rotation 17622 }, 17623 parent: { 17624 viewport: { 17625 rawDims: { 17626 pageWidth, 17627 pageHeight, 17628 pageX, 17629 pageY 17630 } 17631 } 17632 } 17633 } = this; 17634 currentRect?.splice(0, 4, ...rect); 17635 style.left = `${100 * (rect[0] - pageX) / pageWidth}%`; 17636 style.top = `${100 * (pageHeight - rect[3] + pageY) / pageHeight}%`; 17637 if (rotation === 0) { 17638 style.width = `${100 * (rect[2] - rect[0]) / pageWidth}%`; 17639 style.height = `${100 * (rect[3] - rect[1]) / pageHeight}%`; 17640 } else { 17641 this.setRotation(rotation); 17642 } 17643 } 17644 _createContainer(ignoreBorder) { 17645 const { 17646 data, 17647 parent: { 17648 page, 17649 viewport 17650 } 17651 } = this; 17652 const container = document.createElement("section"); 17653 container.setAttribute("data-annotation-id", data.id); 17654 if (!(this instanceof WidgetAnnotationElement) && !(this instanceof LinkAnnotationElement) && !(this instanceof MediaAnnotationElement)) { 17655 container.tabIndex = 0; 17656 } 17657 const { 17658 style 17659 } = container; 17660 style.zIndex = this.parent.zIndex; 17661 this.parent.zIndex += 2; 17662 if (data.alternativeText) { 17663 container.title = data.alternativeText; 17664 } 17665 if (data.noRotate) { 17666 container.classList.add("norotate"); 17667 } 17668 if (!data.rect || this instanceof PopupAnnotationElement) { 17669 const { 17670 rotation 17671 } = data; 17672 if (!data.hasOwnCanvas && rotation !== 0) { 17673 this.setRotation(rotation, container); 17674 } 17675 return container; 17676 } 17677 const { 17678 width, 17679 height 17680 } = this; 17681 if (!ignoreBorder && data.borderStyle.width > 0) { 17682 style.borderWidth = `${data.borderStyle.width}px`; 17683 const horizontalRadius = data.borderStyle.horizontalCornerRadius; 17684 const verticalRadius = data.borderStyle.verticalCornerRadius; 17685 if (horizontalRadius > 0 || verticalRadius > 0) { 17686 const radius = `calc(${horizontalRadius}px * var(--total-scale-factor)) / calc(${verticalRadius}px * var(--total-scale-factor))`; 17687 style.borderRadius = radius; 17688 } 17689 switch (data.borderStyle.style) { 17690 case AnnotationBorderStyleType.SOLID: 17691 style.borderStyle = "solid"; 17692 break; 17693 case AnnotationBorderStyleType.DASHED: 17694 style.borderStyle = "dashed"; 17695 break; 17696 case AnnotationBorderStyleType.BEVELED: 17697 warn("Unimplemented border style: beveled"); 17698 break; 17699 case AnnotationBorderStyleType.INSET: 17700 warn("Unimplemented border style: inset"); 17701 break; 17702 case AnnotationBorderStyleType.UNDERLINE: 17703 style.borderBottomStyle = "solid"; 17704 break; 17705 default: 17706 break; 17707 } 17708 const borderColor = data.borderColor || null; 17709 if (borderColor) { 17710 this.#hasBorder = true; 17711 style.borderColor = Util.makeHexColor(...borderColor); 17712 } else { 17713 style.borderWidth = 0; 17714 } 17715 } 17716 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]]); 17717 const { 17718 pageWidth, 17719 pageHeight, 17720 pageX, 17721 pageY 17722 } = viewport.rawDims; 17723 style.left = `${100 * (rect[0] - pageX) / pageWidth}%`; 17724 style.top = `${100 * (rect[1] - pageY) / pageHeight}%`; 17725 const { 17726 rotation 17727 } = data; 17728 if (data.hasOwnCanvas || rotation === 0) { 17729 style.width = `${100 * width / pageWidth}%`; 17730 style.height = `${100 * height / pageHeight}%`; 17731 } else { 17732 this.setRotation(rotation, container); 17733 } 17734 return container; 17735 } 17736 setRotation(angle, container = this.container) { 17737 if (!this.data.rect) { 17738 return; 17739 } 17740 const { 17741 pageWidth, 17742 pageHeight 17743 } = this.parent.viewport.rawDims; 17744 let { 17745 width, 17746 height 17747 } = this; 17748 if (angle % 180 !== 0) { 17749 [width, height] = [height, width]; 17750 } 17751 container.style.width = `${100 * width / pageWidth}%`; 17752 container.style.height = `${100 * height / pageHeight}%`; 17753 container.setAttribute("data-main-rotation", (360 - angle) % 360); 17754 } 17755 get _commonActions() { 17756 const setColor = (jsName, styleName, event) => { 17757 const color = event.detail[jsName]; 17758 const colorType = color[0]; 17759 const colorArray = color.slice(1); 17760 event.target.style[styleName] = ColorConverters[`${colorType}_HTML`](colorArray); 17761 this.annotationStorage.setValue(this.data.id, { 17762 [styleName]: ColorConverters[`${colorType}_rgb`](colorArray) 17763 }); 17764 }; 17765 return shadow(this, "_commonActions", { 17766 display: event => { 17767 const { 17768 display 17769 } = event.detail; 17770 const hidden = display % 2 === 1; 17771 this.container.style.visibility = hidden ? "hidden" : "visible"; 17772 this.annotationStorage.setValue(this.data.id, { 17773 noView: hidden, 17774 noPrint: display === 1 || display === 2 17775 }); 17776 }, 17777 print: event => { 17778 this.annotationStorage.setValue(this.data.id, { 17779 noPrint: !event.detail.print 17780 }); 17781 }, 17782 hidden: event => { 17783 const { 17784 hidden 17785 } = event.detail; 17786 this.container.style.visibility = hidden ? "hidden" : "visible"; 17787 this.annotationStorage.setValue(this.data.id, { 17788 noPrint: hidden, 17789 noView: hidden 17790 }); 17791 }, 17792 focus: event => { 17793 setTimeout(() => event.target.focus({ 17794 preventScroll: false 17795 }), 0); 17796 }, 17797 userName: event => { 17798 event.target.title = event.detail.userName; 17799 }, 17800 readonly: event => { 17801 event.target.disabled = event.detail.readonly; 17802 }, 17803 required: event => { 17804 this._setRequired(event.target, event.detail.required); 17805 }, 17806 bgColor: event => { 17807 setColor("bgColor", "backgroundColor", event); 17808 }, 17809 fillColor: event => { 17810 setColor("fillColor", "backgroundColor", event); 17811 }, 17812 fgColor: event => { 17813 setColor("fgColor", "color", event); 17814 }, 17815 textColor: event => { 17816 setColor("textColor", "color", event); 17817 }, 17818 borderColor: event => { 17819 setColor("borderColor", "borderColor", event); 17820 }, 17821 strokeColor: event => { 17822 setColor("strokeColor", "borderColor", event); 17823 }, 17824 rotation: event => { 17825 const angle = event.detail.rotation; 17826 this.setRotation(angle); 17827 this.annotationStorage.setValue(this.data.id, { 17828 rotation: angle 17829 }); 17830 } 17831 }); 17832 } 17833 _dispatchEventFromSandbox(actions, jsEvent) { 17834 const commonActions = this._commonActions; 17835 for (const name of Object.keys(jsEvent.detail)) { 17836 const action = actions[name] || commonActions[name]; 17837 action?.(jsEvent); 17838 } 17839 } 17840 _setDefaultPropertiesFromJS(element) { 17841 if (!this.enableScripting) { 17842 return; 17843 } 17844 const storedData = this.annotationStorage.getRawValue(this.data.id); 17845 if (!storedData) { 17846 return; 17847 } 17848 const commonActions = this._commonActions; 17849 for (const [actionName, detail] of Object.entries(storedData)) { 17850 const action = commonActions[actionName]; 17851 if (action) { 17852 const eventProxy = { 17853 detail: { 17854 [actionName]: detail 17855 }, 17856 target: element 17857 }; 17858 action(eventProxy); 17859 delete storedData[actionName]; 17860 } 17861 } 17862 } 17863 _createQuadrilaterals() { 17864 if (!this.container) { 17865 return; 17866 } 17867 const { 17868 quadPoints 17869 } = this.data; 17870 if (!quadPoints) { 17871 return; 17872 } 17873 const [rectBlX, rectBlY, rectTrX, rectTrY] = this.data.rect.map(
vendor: 29,427 bytes, lines 17873-18804
17873x => Math.fround(x)); 17874 if (quadPoints.length === 8) { 17875 const [trX, trY, blX, blY] = quadPoints.subarray(2, 6); 17876 if (rectTrX === trX && rectTrY === trY && rectBlX === blX && rectBlY === blY) { 17877 return; 17878 } 17879 } 17880 const { 17881 style 17882 } = this.container; 17883 let svgBuffer; 17884 if (this.#hasBorder) { 17885 const { 17886 borderColor, 17887 borderWidth 17888 } = style; 17889 style.borderWidth = 0; 17890 svgBuffer = ["url('data:image/svg+xml;utf8,", `<svg xmlns="${SVG_NS}" preserveAspectRatio="none" viewBox="0 0 1 1">`, `<g fill="transparent" stroke="${borderColor}" stroke-width="${borderWidth}">`]; 17891 this.container.classList.add("hasBorder"); 17892 } 17893 const width = rectTrX - rectBlX; 17894 const height = rectTrY - rectBlY; 17895 const { 17896 svgFactory 17897 } = this; 17898 const svg = svgFactory.createElement("svg"); 17899 svg.classList.add("quadrilateralsContainer"); 17900 svg.setAttribute("width", 0); 17901 svg.setAttribute("height", 0); 17902 svg.role = "none"; 17903 const defs = svgFactory.createElement("defs"); 17904 svg.append(defs); 17905 const clipPath = svgFactory.createElement("clipPath"); 17906 const id = `clippath_${this.data.id}`; 17907 clipPath.setAttribute("id", id); 17908 clipPath.setAttribute("clipPathUnits", "objectBoundingBox"); 17909 defs.append(clipPath); 17910 for (let i = 2, ii = quadPoints.length; i < ii; i += 8) { 17911 const trX = quadPoints[i]; 17912 const trY = quadPoints[i + 1]; 17913 const blX = quadPoints[i + 2]; 17914 const blY = quadPoints[i + 3]; 17915 const rect = svgFactory.createElement("rect"); 17916 const x = (blX - rectBlX) / width; 17917 const y = (rectTrY - trY) / height; 17918 const rectWidth = (trX - blX) / width; 17919 const rectHeight = (trY - blY) / height; 17920 rect.setAttribute("x", x); 17921 rect.setAttribute("y", y); 17922 rect.setAttribute("width", rectWidth); 17923 rect.setAttribute("height", rectHeight); 17924 clipPath.append(rect); 17925 svgBuffer?.push(`<rect vector-effect="non-scaling-stroke" x="${x}" y="${y}" width="${rectWidth}" height="${rectHeight}"/>`); 17926 } 17927 if (this.#hasBorder) { 17928 svgBuffer.push(`</g></svg>')`); 17929 style.backgroundImage = svgBuffer.join(""); 17930 } 17931 this.container.append(svg); 17932 this.container.style.clipPath = `url(#${id})`; 17933 } 17934 _createPopup(popupData = null) { 17935 const { 17936 data 17937 } = this; 17938 let contentsObj, modificationDate; 17939 if (popupData) { 17940 contentsObj = { 17941 str: popupData.text 17942 }; 17943 modificationDate = popupData.date; 17944 } else { 17945 contentsObj = data.contentsObj; 17946 modificationDate = data.modificationDate; 17947 } 17948 this.#popupElement = new PopupAnnotationElement({ 17949 data: { 17950 color: data.color, 17951 titleObj: data.titleObj, 17952 modificationDate, 17953 contentsObj, 17954 richText: data.richText, 17955 parentRect: data.rect, 17956 borderStyle: 0, 17957 id: `popup_${data.id}`, 17958 rotation: data.rotation, 17959 noRotate: true 17960 }, 17961 linkService: this.linkService, 17962 parent: this.parent, 17963 elements: [this] 17964 }); 17965 } 17966 get hasPopupElement() { 17967 return !!(this.#popupElement || this.popup || this.data.popupRef); 17968 } 17969 get extraPopupElement() { 17970 return this.#popupElement; 17971 } 17972 render() { 17973 unreachable("Abstract method `AnnotationElement.render` called"); 17974 } 17975 _getElementsByName(name, skipId = null) { 17976 const fields = []; 17977 if (this._fieldObjects) { 17978 const fieldObj = this._fieldObjects[name]; 17979 if (fieldObj) { 17980 for (const { 17981 page, 17982 id, 17983 exportValues 17984 } of fieldObj) { 17985 if (page === -1) { 17986 continue; 17987 } 17988 if (id === skipId) { 17989 continue; 17990 } 17991 const exportValue = typeof exportValues === "string" ? exportValues : null; 17992 const domElement = document.querySelector(`[data-element-id="${id}"]`); 17993 if (domElement && !GetElementsByNameSet.has(domElement)) { 17994 warn(`_getElementsByName - element not allowed: ${id}`); 17995 continue; 17996 } 17997 fields.push({ 17998 id, 17999 exportValue, 18000 domElement 18001 }); 18002 } 18003 } 18004 return fields; 18005 } 18006 for (const domElement of document.getElementsByName(name)) { 18007 const { 18008 exportValue 18009 } = domElement; 18010 const id = domElement.getAttribute("data-element-id"); 18011 if (id === skipId) { 18012 continue; 18013 } 18014 if (!GetElementsByNameSet.has(domElement)) { 18015 continue; 18016 } 18017 fields.push({ 18018 id, 18019 exportValue, 18020 domElement 18021 }); 18022 } 18023 return fields; 18024 } 18025 show() { 18026 if (this.container) { 18027 this.container.hidden = false; 18028 } 18029 this.popup?.maybeShow(); 18030 } 18031 hide() { 18032 if (this.container) { 18033 this.container.hidden = true; 18034 } 18035 this.popup?.forceHide(); 18036 } 18037 getElementsToTriggerPopup() { 18038 return this.container; 18039 } 18040 addHighlightArea() { 18041 const triggers = this.getElementsToTriggerPopup(); 18042 if (Array.isArray(triggers)) { 18043 for (const element of triggers) { 18044 element.classList.add("highlightArea"); 18045 } 18046 } else { 18047 triggers.classList.add("highlightArea"); 18048 } 18049 } 18050 _editOnDoubleClick() { 18051 if (!this._isEditable) { 18052 return; 18053 } 18054 const { 18055 annotationEditorType: mode, 18056 data: { 18057 id: editId 18058 } 18059 } = this; 18060 this.container.addEventListener("dblclick", () => { 18061 this.linkService.eventBus?.dispatch("switchannotationeditormode", { 18062 source: this, 18063 mode, 18064 editId, 18065 mustEnterInEditMode: true 18066 }); 18067 }); 18068 } 18069 updateOC(optionalContentConfig) { 18070 if (!this.data.oc || !optionalContentConfig) { 18071 return; 18072 } 18073 const isVisible = optionalContentConfig.isVisible(this.data.oc); 18074 if (isVisible) { 18075 this.show(); 18076 } else { 18077 this.hide(); 18078 } 18079 } 18080 get width() { 18081 return this.data.rect[2] - this.data.rect[0]; 18082 } 18083 get height() { 18084 return this.data.rect[3] - this.data.rect[1]; 18085 } 18086 _setBackgroundColor(element) { 18087 const color = this.data.backgroundColor || null; 18088 element.style.backgroundColor = color === null ? "transparent" : Util.makeHexColor(...color); 18089 } 18090} 18091class EditorAnnotationElement extends AnnotationElement { 18092 constructor(parameters) { 18093 super(parameters, { 18094 isRenderable: true, 18095 ignoreBorder: true 18096 }); 18097 this.editor = parameters.editor; 18098 } 18099 render() { 18100 this.container.className = "editorAnnotation"; 18101 return this.container; 18102 } 18103 createOrUpdatePopup() { 18104 const { 18105 editor 18106 } = this; 18107 if (!editor.hasComment) { 18108 return; 18109 } 18110 this._createPopup(editor.comment); 18111 } 18112 get hasCommentButton() { 18113 return this.enableComment && this.editor.hasComment; 18114 } 18115 get commentButtonPosition() { 18116 return this.editor.commentButtonPositionInPage; 18117 } 18118 get commentText() { 18119 return this.editor.comment.text; 18120 } 18121 set commentText(text) { 18122 this.editor.comment = text; 18123 if (!text) { 18124 this.removePopup(); 18125 } 18126 } 18127 get commentData() { 18128 return this.editor.getData(); 18129 } 18130 remove() { 18131 this.parent.removeAnnotation(this.data.id); 18132 this.container.remove(); 18133 this.container = null; 18134 this.removePopup(); 18135 } 18136} 18137class LinkAnnotationElement extends AnnotationElement { 18138 constructor(parameters, options = null) { 18139 super(parameters, { 18140 isRenderable: true, 18141 ignoreBorder: !!options?.ignoreBorder, 18142 createQuadrilaterals: true 18143 }); 18144 this.isTooltipOnly = parameters.data.isTooltipOnly; 18145 } 18146 render() { 18147 const { 18148 data, 18149 linkService 18150 } = this; 18151 const link = document.createElement("a"); 18152 link.setAttribute("data-element-id", data.id); 18153 let isBound = false; 18154 if (data.url) { 18155 linkService.addLinkAttributes(link, data.url, data.newWindow); 18156 isBound = true; 18157 } else if (data.action) { 18158 this._bindNamedAction(link, data.action, data.overlaidText); 18159 isBound = true; 18160 } else if (data.attachment) { 18161 this.#bindAttachment(link, data.attachmentId, data.attachment, data.overlaidText, data.attachmentDest); 18162 isBound = true; 18163 } else if (data.setOCGState) { 18164 this.#bindSetOCGState(link, data.setOCGState, data.overlaidText); 18165 isBound = true; 18166 } else if (data.dest) { 18167 this._bindLink(link, data.dest, data.overlaidText); 18168 isBound = true; 18169 } else { 18170 if (data.actions && (data.actions.Action || data.actions["Mouse Up"] || data.actions["Mouse Down"]) && this.enableScripting && this.hasJSActions) { 18171 this._bindJSAction(link, data); 18172 isBound = true; 18173 } 18174 if (data.resetForm) { 18175 this._bindResetFormAction(link, data.resetForm); 18176 isBound = true; 18177 } else if (this.isTooltipOnly && !isBound) { 18178 this._bindLink(link, ""); 18179 isBound = true; 18180 } 18181 } 18182 this.container.classList.add("linkAnnotation"); 18183 if (isBound) { 18184 this.contentElement = link; 18185 this.container.append(link); 18186 } 18187 return this.container; 18188 } 18189 #setInternalLink() { 18190 this.container.setAttribute("data-internal-link", ""); 18191 } 18192 _bindLink(link, destination, overlaidText = "") { 18193 link.href = this.linkService.getDestinationHash(destination); 18194 link.onclick = () => { 18195 if (destination) { 18196 this.linkService.goToDestination(destination); 18197 } 18198 return false; 18199 }; 18200 if (destination || destination === "") { 18201 this.#setInternalLink(); 18202 } 18203 if (overlaidText) { 18204 link.title = overlaidText; 18205 } 18206 } 18207 _bindNamedAction(link, action, overlaidText = "") { 18208 link.href = this.linkService.getAnchorUrl(""); 18209 link.onclick = () => { 18210 this.linkService.executeNamedAction(action); 18211 return false; 18212 }; 18213 if (overlaidText) { 18214 link.title = overlaidText; 18215 } 18216 this.#setInternalLink(); 18217 } 18218 #bindAttachment(link, attachmentId, attachment, overlaidText = "", dest = null) { 18219 link.href = this.linkService.getAnchorUrl(""); 18220 if (attachment.description) { 18221 link.title = attachment.description; 18222 } else if (overlaidText) { 18223 link.title = overlaidText; 18224 } 18225 const openAttachment = async () => { 18226 const content = await this.linkService.getAttachmentContent(attachmentId); 18227 if (content) { 18228 this.downloadManager?.openOrDownloadData(content, attachment.filename, dest); 18229 } 18230 }; 18231 link.onclick = () => { 18232 openAttachment(); 18233 return false; 18234 }; 18235 this.#setInternalLink(); 18236 } 18237 #bindSetOCGState(link, action, overlaidText = "") { 18238 link.href = this.linkService.getAnchorUrl(""); 18239 link.onclick = () => { 18240 this.linkService.executeSetOCGState(action); 18241 return false; 18242 }; 18243 if (overlaidText) { 18244 link.title = overlaidText; 18245 } 18246 this.#setInternalLink(); 18247 } 18248 _bindJSAction(link, data) { 18249 link.href = this.linkService.getAnchorUrl(""); 18250 const map = new Map([["Action", "onclick"], ["Mouse Up", "onmouseup"], ["Mouse Down", "onmousedown"]]); 18251 for (const name of Object.keys(data.actions)) { 18252 const jsName = map.get(name); 18253 if (!jsName) { 18254 continue; 18255 } 18256 link[jsName] = () => { 18257 this.linkService.eventBus?.dispatch("dispatcheventinsandbox", { 18258 source: this, 18259 detail: { 18260 id: data.id, 18261 name 18262 } 18263 }); 18264 return false; 18265 }; 18266 } 18267 if (data.overlaidText) { 18268 link.title = data.overlaidText; 18269 } 18270 if (!link.onclick) { 18271 link.onclick = () => false; 18272 } 18273 this.#setInternalLink(); 18274 } 18275 _bindResetFormAction(link, resetForm) { 18276 const otherClickAction = link.onclick; 18277 if (!otherClickAction) { 18278 link.href = this.linkService.getAnchorUrl(""); 18279 } 18280 this.#setInternalLink(); 18281 if (!this._fieldObjects) { 18282 warn(`_bindResetFormAction - "resetForm" action not supported, ` + "ensure that the `fieldObjects` parameter is provided."); 18283 if (!otherClickAction) { 18284 link.onclick = () => false; 18285 } 18286 return; 18287 } 18288 link.onclick = () => { 18289 otherClickAction?.(); 18290 const { 18291 fields: resetFormFields, 18292 refs: resetFormRefs, 18293 include 18294 } = resetForm; 18295 const allFields = []; 18296 if (resetFormFields.length !== 0 || resetFormRefs.length !== 0) { 18297 const fieldIds = new Set(resetFormRefs); 18298 for (const fieldName of resetFormFields) { 18299 const fields = this._fieldObjects[fieldName] || []; 18300 for (const { 18301 id 18302 } of fields) { 18303 fieldIds.add(id); 18304 } 18305 } 18306 for (const fields of Object.values(this._fieldObjects)) { 18307 for (const field of fields) { 18308 if (fieldIds.has(field.id) === include) { 18309 allFields.push(field); 18310 } 18311 } 18312 } 18313 } else { 18314 for (const fields of Object.values(this._fieldObjects)) { 18315 allFields.push(...fields); 18316 } 18317 } 18318 const storage = this.annotationStorage; 18319 const allIds = []; 18320 for (const field of allFields) { 18321 const { 18322 id 18323 } = field; 18324 allIds.push(id); 18325 switch (field.type) { 18326 case "text": 18327 { 18328 const value = field.defaultValue || ""; 18329 storage.setValue(id, { 18330 value 18331 }); 18332 break; 18333 } 18334 case "checkbox": 18335 case "radiobutton": 18336 { 18337 const value = field.defaultValue === field.exportValues; 18338 storage.setValue(id, { 18339 value 18340 }); 18341 break; 18342 } 18343 case "combobox": 18344 case "listbox": 18345 { 18346 const value = field.defaultValue || ""; 18347 storage.setValue(id, { 18348 value 18349 }); 18350 break; 18351 } 18352 default: 18353 continue; 18354 } 18355 const domElement = document.querySelector(`[data-element-id="${id}"]`); 18356 if (!domElement) { 18357 continue; 18358 } else if (!GetElementsByNameSet.has(domElement)) { 18359 warn(`_bindResetFormAction - element not allowed: ${id}`); 18360 continue; 18361 } 18362 domElement.dispatchEvent(new Event("resetform")); 18363 } 18364 if (this.enableScripting) { 18365 this.linkService.eventBus?.dispatch("dispatcheventinsandbox", { 18366 source: this, 18367 detail: { 18368 id: "app", 18369 ids: allIds, 18370 name: "ResetForm" 18371 } 18372 }); 18373 } 18374 return false; 18375 }; 18376 } 18377} 18378class TextAnnotationElement extends AnnotationElement { 18379 constructor(parameters) { 18380 super(parameters, { 18381 isRenderable: true 18382 }); 18383 } 18384 render() { 18385 this.container.classList.add("textAnnotation"); 18386 const image = document.createElement("img"); 18387 image.src = this.imageResourcesPath + "annotation-" + this.data.name.toLowerCase() + ".svg"; 18388 image.setAttribute("data-l10n-id", "pdfjs-text-annotation-type"); 18389 image.setAttribute("data-l10n-args", JSON.stringify({ 18390 type: this.data.name 18391 })); 18392 if (!this.data.popupRef && this.hasPopupData) { 18393 this.hasOwnCommentButton = true; 18394 this._createPopup(); 18395 } 18396 this.container.append(image); 18397 return this.container; 18398 } 18399} 18400class WidgetAnnotationElement extends AnnotationElement { 18401 render() { 18402 return this.container; 18403 } 18404 _getKeyModifier(event) { 18405 return FeatureTest.platform.isMac ? event.metaKey : event.ctrlKey; 18406 } 18407 _setEventListener(element, elementData, baseName, eventName, valueGetter) { 18408 if (baseName.includes("mouse")) { 18409 element.addEventListener(baseName, event => { 18410 this.linkService.eventBus?.dispatch("dispatcheventinsandbox", { 18411 source: this, 18412 detail: { 18413 id: this.data.id, 18414 name: eventName, 18415 value: valueGetter(event), 18416 shift: event.shiftKey, 18417 modifier: this._getKeyModifier(event) 18418 } 18419 }); 18420 }); 18421 } else { 18422 element.addEventListener(baseName, event => { 18423 if (baseName === "blur") { 18424 if (!elementData.focused || !event.relatedTarget) { 18425 return; 18426 } 18427 elementData.focused = false; 18428 } else if (baseName === "focus") { 18429 if (elementData.focused) { 18430 return; 18431 } 18432 elementData.focused = true; 18433 } 18434 if (!valueGetter) { 18435 return; 18436 } 18437 this.linkService.eventBus?.dispatch("dispatcheventinsandbox", { 18438 source: this, 18439 detail: { 18440 id: this.data.id, 18441 name: eventName, 18442 value: valueGetter(event) 18443 } 18444 }); 18445 }); 18446 } 18447 } 18448 _setEventListeners(element, elementData, names, getter) { 18449 for (const [baseName, eventName] of names) { 18450 if (eventName === "Action" || this.data.actions?.[eventName]) { 18451 if (eventName === "Focus" || eventName === "Blur") { 18452 elementData ||= { 18453 focused: false 18454 }; 18455 } 18456 this._setEventListener(element, elementData, baseName, eventName, getter); 18457 if (eventName === "Focus" && !this.data.actions?.Blur) { 18458 this._setEventListener(element, elementData, "blur", "Blur", null); 18459 } else if (eventName === "Blur" && !this.data.actions?.Focus) { 18460 this._setEventListener(element, elementData, "focus", "Focus", null); 18461 } 18462 } 18463 } 18464 } 18465 _setTextStyle(element) { 18466 const TEXT_ALIGNMENT = ["left", "center", "right"]; 18467 const { 18468 fontColor 18469 } = this.data.defaultAppearanceData; 18470 const fontSize = this.data.defaultAppearanceData.fontSize || annotation_layer_DEFAULT_FONT_SIZE; 18471 const style = element.style; 18472 let computedFontSize; 18473 const BORDER_SIZE = 2; 18474 const roundToOneDecimal = x => Math.round(10 * x) / 10; 18475 if (this.data.multiLine) { 18476 const height = Math.abs(this.data.rect[3] - this.data.rect[1] - BORDER_SIZE); 18477 const numberOfLines = Math.round(height / (LINE_FACTOR * fontSize)) || 1; 18478 const lineHeight = height / numberOfLines; 18479 computedFontSize = Math.min(fontSize, roundToOneDecimal(lineHeight / LINE_FACTOR)); 18480 } else { 18481 const height = Math.abs(this.data.rect[3] - this.data.rect[1] - BORDER_SIZE); 18482 computedFontSize = Math.min(fontSize, roundToOneDecimal(height / LINE_FACTOR)); 18483 } 18484 style.fontSize = `calc(${computedFontSize}px * var(--total-scale-factor))`; 18485 style.color = Util.makeHexColor(...fontColor); 18486 if (this.data.textAlignment !== null && !this.data.comb) { 18487 style.textAlign = TEXT_ALIGNMENT[this.data.textAlignment]; 18488 } 18489 } 18490 _setRequired(element, isRequired) { 18491 if (isRequired) { 18492 element.setAttribute("required", true); 18493 } else { 18494 element.removeAttribute("required"); 18495 } 18496 element.setAttribute("aria-required", isRequired); 18497 } 18498} 18499class TextWidgetAnnotationElement extends WidgetAnnotationElement { 18500 constructor(parameters) { 18501 const isRenderable = parameters.renderForms || parameters.data.hasOwnCanvas || !parameters.data.hasAppearance && !!parameters.data.fieldValue; 18502 super(parameters, { 18503 isRenderable 18504 }); 18505 } 18506 setPropertyOnSiblings(base, key, value, keyInStorage) { 18507 const storage = this.annotationStorage; 18508 for (const element of this._getElementsByName(base.name, base.id)) { 18509 if (element.domElement) { 18510 element.domElement[key] = value; 18511 } 18512 storage.setValue(element.id, { 18513 [keyInStorage]: value 18514 }); 18515 } 18516 } 18517 render() { 18518 const storage = this.annotationStorage; 18519 const id = this.data.id; 18520 this.container.classList.add("textWidgetAnnotation"); 18521 let element = null; 18522 if (this.renderForms) { 18523 const storedData = storage.getValue(id, { 18524 value: this.data.fieldValue 18525 }); 18526 let textContent = storedData.value || ""; 18527 const maxLen = storage.getValue(id, { 18528 charLimit: this.data.maxLen 18529 }).charLimit; 18530 if (maxLen && textContent.length > maxLen) { 18531 textContent = textContent.slice(0, maxLen); 18532 } 18533 let fieldFormattedValues = storedData.formattedValue || this.data.textContent?.join("\n") || null; 18534 if (fieldFormattedValues && this.data.comb) { 18535 fieldFormattedValues = fieldFormattedValues.replaceAll(/\s+/g, ""); 18536 } 18537 const elementData = { 18538 userValue: textContent, 18539 formattedValue: fieldFormattedValues, 18540 lastCommittedValue: null, 18541 commitKey: 1, 18542 focused: false 18543 }; 18544 if (this.data.multiLine) { 18545 element = document.createElement("textarea"); 18546 element.textContent = fieldFormattedValues ?? textContent; 18547 if (this.data.doNotScroll) { 18548 element.style.overflowY = "hidden"; 18549 } 18550 } else { 18551 element = document.createElement("input"); 18552 element.type = this.data.password ? "password" : "text"; 18553 element.setAttribute("value", fieldFormattedValues ?? textContent); 18554 if (this.data.doNotScroll) { 18555 element.style.overflowX = "hidden"; 18556 } 18557 } 18558 if (this.data.hasOwnCanvas) { 18559 this.container.classList.add("hasOwnCanvas"); 18560 if (storage.has(id)) { 18561 this.container.classList.add("sandboxModified"); 18562 } 18563 } 18564 GetElementsByNameSet.add(element); 18565 this.contentElement = element; 18566 element.setAttribute("data-element-id", id); 18567 element.disabled = this.data.readOnly; 18568 element.name = this.data.fieldName; 18569 element.tabIndex = 0; 18570 const { 18571 datetimeFormat, 18572 datetimeType, 18573 timeStep 18574 } = this.data; 18575 const hasDateOrTime = !!datetimeType && this.enableScripting; 18576 if (datetimeFormat) { 18577 element.title = datetimeFormat; 18578 } 18579 this._setRequired(element, this.data.required); 18580 if (maxLen) { 18581 element.maxLength = maxLen; 18582 } 18583 element.addEventListener("input", event => { 18584 storage.setValue(id, { 18585 value: event.target.value 18586 }); 18587 this.setPropertyOnSiblings(element, "value", event.target.value, "value"); 18588 elementData.formattedValue = null; 18589 }); 18590 element.addEventListener("resetform", event => { 18591 const defaultValue = this.data.defaultFieldValue ?? ""; 18592 element.value = elementData.userValue = defaultValue; 18593 elementData.formattedValue = null; 18594 }); 18595 let blurListener = event => { 18596 const { 18597 formattedValue 18598 } = elementData; 18599 if (formattedValue !== null && formattedValue !== undefined) { 18600 event.target.value = formattedValue; 18601 } 18602 event.target.scrollLeft = 0; 18603 }; 18604 if (this.enableScripting && this.hasJSActions) { 18605 element.addEventListener("focus", event => { 18606 if (elementData.focused) { 18607 return; 18608 } 18609 const { 18610 target 18611 } = event; 18612 if (hasDateOrTime) { 18613 target.type = datetimeType; 18614 if (timeStep) { 18615 target.step = timeStep; 18616 } 18617 } 18618 if (elementData.userValue) { 18619 const value = elementData.userValue; 18620 if (hasDateOrTime) { 18621 if (datetimeType === "time") { 18622 const date = new Date(value); 18623 const parts = [date.getHours(), date.getMinutes(), date.getSeconds()]; 18624 target.value = parts.map(v => v.toString().padStart(2, "0")).join(":"); 18625 } else { 18626 target.value = new Date(value - TIMEZONE_OFFSET).toISOString().split(datetimeType === "date" ? "T" : ".", 1)[0]; 18627 } 18628 } else { 18629 target.value = value; 18630 } 18631 } 18632 elementData.lastCommittedValue = target.value; 18633 elementData.commitKey = 1; 18634 if (!this.data.actions?.Focus) { 18635 elementData.focused = true; 18636 } 18637 }); 18638 element.addEventListener("updatefromsandbox", jsEvent => { 18639 this.container.classList.add("sandboxModified"); 18640 const actions = { 18641 value(event) { 18642 elementData.userValue = event.detail.value ?? ""; 18643 if (!hasDateOrTime) { 18644 storage.setValue(id, { 18645 value: elementData.userValue.toString() 18646 }); 18647 } 18648 event.target.value = elementData.userValue; 18649 }, 18650 formattedValue(event) { 18651 const { 18652 formattedValue 18653 } = event.detail; 18654 elementData.formattedValue = formattedValue; 18655 if (formattedValue !== null && formattedValue !== undefined && event.target !== document.activeElement) { 18656 event.target.value = formattedValue; 18657 } 18658 const data = { 18659 formattedValue 18660 }; 18661 if (hasDateOrTime) { 18662 data.value = formattedValue; 18663 } 18664 storage.setValue(id, data); 18665 }, 18666 selRange(event) { 18667 event.target.setSelectionRange(...event.detail.selRange); 18668 }, 18669 charLimit: event => { 18670 const { 18671 charLimit 18672 } = event.detail; 18673 const { 18674 target 18675 } = event; 18676 if (charLimit === 0) { 18677 target.removeAttribute("maxLength"); 18678 return; 18679 } 18680 target.setAttribute("maxLength", charLimit); 18681 let value = elementData.userValue; 18682 if (!value || value.length <= charLimit) { 18683 return; 18684 } 18685 value = value.slice(0, charLimit); 18686 target.value = elementData.userValue = value; 18687 storage.setValue(id, { 18688 value 18689 }); 18690 this.linkService.eventBus?.dispatch("dispatcheventinsandbox", { 18691 source: this, 18692 detail: { 18693 id, 18694 name: "Keystroke", 18695 value, 18696 willCommit: true, 18697 commitKey: 1, 18698 selStart: target.selectionStart, 18699 selEnd: target.selectionEnd 18700 } 18701 }); 18702 } 18703 }; 18704 this._dispatchEventFromSandbox(actions, jsEvent); 18705 }); 18706 element.addEventListener("keydown", event => { 18707 elementData.commitKey = 1; 18708 let commitKey = -1; 18709 if (event.key === "Escape") { 18710 commitKey = 0; 18711 } else if (event.key === "Enter" && !this.data.multiLine) { 18712 commitKey = 2; 18713 } else if (event.key === "Tab") { 18714 elementData.commitKey = 3; 18715 } 18716 if (commitKey === -1) { 18717 return; 18718 } 18719 const { 18720 value 18721 } = event.target; 18722 if (elementData.lastCommittedValue === value) { 18723 return; 18724 } 18725 elementData.lastCommittedValue = value; 18726 elementData.userValue = value; 18727 this.linkService.eventBus?.dispatch("dispatcheventinsandbox", { 18728 source: this, 18729 detail: { 18730 id, 18731 name: "Keystroke", 18732 value, 18733 willCommit: true, 18734 commitKey, 18735 selStart: event.target.selectionStart, 18736 selEnd: event.target.selectionEnd 18737 } 18738 }); 18739 }); 18740 const _blurListener = blurListener; 18741 blurListener = null; 18742 element.addEventListener("blur", event => { 18743 if (!elementData.focused || !event.relatedTarget) { 18744 return; 18745 } 18746 if (!this.data.actions?.Blur) { 18747 elementData.focused = false; 18748 } 18749 const { 18750 target 18751 } = event; 18752 let { 18753 value 18754 } = target; 18755 if (hasDateOrTime) { 18756 if (value && datetimeType === "time") { 18757 const parts = value.split(":").map(v => parseInt(v, 10)); 18758 value = new Date(2000, 0, 1, parts[0], parts[1], parts[2] || 0).valueOf(); 18759 target.step = ""; 18760 } else { 18761 if (!value.includes("T")) { 18762 value = `${value}T00:00`; 18763 } 18764 value = new Date(value).valueOf(); 18765 } 18766 target.type = "text"; 18767 } 18768 elementData.userValue = value; 18769 if (elementData.lastCommittedValue !== value) { 18770 this.linkService.eventBus?.dispatch("dispatcheventinsandbox", { 18771 source: this, 18772 detail: { 18773 id, 18774 name: "Keystroke", 18775 value, 18776 willCommit: true, 18777 commitKey: elementData.commitKey, 18778 selStart: event.target.selectionStart, 18779 selEnd: event.target.selectionEnd 18780 } 18781 }); 18782 } 18783 _blurListener(event); 18784 }); 18785 if (this.data.actions?.Keystroke) { 18786 element.addEventListener("beforeinput", event => { 18787 elementData.lastCommittedValue = null; 18788 const { 18789 data, 18790 target 18791 } = event; 18792 const { 18793 value, 18794 selectionStart, 18795 selectionEnd 18796 } = target; 18797 let selStart = selectionStart, 18798 selEnd = selectionEnd; 18799 switch (event.inputType) { 18800 case "deleteWordBackward": 18801 { 18802 const match = value.substring(0, selectionStart).match(/\w*\W*$/); 18803 if (match) { 18804 selStart -= match[0].length
vendor: 34,318 bytes, lines 18804-19902
18804; 18805 } 18806 break; 18807 } 18808 case "deleteWordForward": 18809 { 18810 const match = value.substring(selectionStart).match(/^\W*\w*/); 18811 if (match) { 18812 selEnd += match[0].length; 18813 } 18814 break; 18815 } 18816 case "deleteContentBackward": 18817 if (selectionStart === selectionEnd) { 18818 selStart -= 1; 18819 } 18820 break; 18821 case "deleteContentForward": 18822 if (selectionStart === selectionEnd) { 18823 selEnd += 1; 18824 } 18825 break; 18826 } 18827 event.preventDefault(); 18828 this.linkService.eventBus?.dispatch("dispatcheventinsandbox", { 18829 source: this, 18830 detail: { 18831 id, 18832 name: "Keystroke", 18833 value, 18834 change: data || "", 18835 willCommit: false, 18836 selStart, 18837 selEnd 18838 } 18839 }); 18840 }); 18841 } 18842 this._setEventListeners(element, elementData, [["focus", "Focus"], ["blur", "Blur"], ["mousedown", "Mouse Down"], ["mouseenter", "Mouse Enter"], ["mouseleave", "Mouse Exit"], ["mouseup", "Mouse Up"]], event => event.target.value); 18843 } 18844 if (blurListener) { 18845 element.addEventListener("blur", blurListener); 18846 } 18847 if (this.data.comb) { 18848 const fieldWidth = this.data.rect[2] - this.data.rect[0]; 18849 const combWidth = fieldWidth / maxLen; 18850 element.classList.add("comb"); 18851 element.style.setProperty("--comb-width", `calc(${combWidth}px * var(--total-scale-factor))`); 18852 const alignment = this.data.textAlignment; 18853 if (alignment === 1 || alignment === 2) { 18854 const setCombOffset = () => { 18855 const free = maxLen - element.value.length; 18856 element.style.setProperty("--comb-offset", `${alignment === 1 ? free >> 1 : free}`); 18857 }; 18858 setCombOffset(); 18859 for (const evt of ["input", "blur", "resetform", "updatefromsandbox"]) { 18860 element.addEventListener(evt, setCombOffset); 18861 } 18862 } 18863 } 18864 } else { 18865 element = document.createElement("div"); 18866 element.textContent = this.data.fieldValue; 18867 element.style.verticalAlign = "middle"; 18868 element.style.display = "table-cell"; 18869 if (this.data.hasOwnCanvas) { 18870 element.hidden = true; 18871 } 18872 } 18873 this._setTextStyle(element); 18874 this._setBackgroundColor(element); 18875 this._setDefaultPropertiesFromJS(element); 18876 this.container.append(element); 18877 return this.container; 18878 } 18879} 18880class SignatureWidgetAnnotationElement extends WidgetAnnotationElement { 18881 constructor(parameters) { 18882 super(parameters, { 18883 isRenderable: !!parameters.data.hasOwnCanvas 18884 }); 18885 } 18886} 18887class CheckboxWidgetAnnotationElement extends WidgetAnnotationElement { 18888 constructor(parameters) { 18889 super(parameters, { 18890 isRenderable: parameters.renderForms 18891 }); 18892 } 18893 render() { 18894 const storage = this.annotationStorage; 18895 const data = this.data; 18896 const id = data.id; 18897 let value = storage.getValue(id, { 18898 value: data.exportValue === data.fieldValue 18899 }).value; 18900 if (typeof value === "string") { 18901 value = value !== "Off"; 18902 storage.setValue(id, { 18903 value 18904 }); 18905 } 18906 this.container.classList.add("buttonWidgetAnnotation", "checkBox"); 18907 const element = document.createElement("input"); 18908 GetElementsByNameSet.add(element); 18909 element.setAttribute("data-element-id", id); 18910 element.disabled = data.readOnly; 18911 this._setRequired(element, this.data.required); 18912 element.type = "checkbox"; 18913 element.name = data.fieldName; 18914 if (value) { 18915 element.setAttribute("checked", true); 18916 } 18917 element.setAttribute("exportValue", data.exportValue); 18918 element.tabIndex = 0; 18919 element.addEventListener("change", event => { 18920 const { 18921 name, 18922 checked 18923 } = event.target; 18924 for (const checkbox of this._getElementsByName(name, id)) { 18925 const curChecked = checked && checkbox.exportValue === data.exportValue; 18926 if (checkbox.domElement) { 18927 checkbox.domElement.checked = curChecked; 18928 } 18929 storage.setValue(checkbox.id, { 18930 value: curChecked 18931 }); 18932 } 18933 storage.setValue(id, { 18934 value: checked 18935 }); 18936 }); 18937 element.addEventListener("resetform", event => { 18938 const defaultValue = data.defaultFieldValue || "Off"; 18939 event.target.checked = defaultValue === data.exportValue; 18940 }); 18941 if (this.enableScripting && this.hasJSActions) { 18942 element.addEventListener("updatefromsandbox", jsEvent => { 18943 const actions = { 18944 value(event) { 18945 event.target.checked = event.detail.value !== "Off"; 18946 storage.setValue(id, { 18947 value: event.target.checked 18948 }); 18949 } 18950 }; 18951 this._dispatchEventFromSandbox(actions, jsEvent); 18952 }); 18953 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); 18954 } 18955 this._setDefaultPropertiesFromJS(element); 18956 this.container.append(element); 18957 return this.container; 18958 } 18959} 18960class RadioButtonWidgetAnnotationElement extends WidgetAnnotationElement { 18961 constructor(parameters) { 18962 super(parameters, { 18963 isRenderable: parameters.renderForms 18964 }); 18965 } 18966 render() { 18967 this.container.classList.add("buttonWidgetAnnotation", "radioButton"); 18968 const storage = this.annotationStorage; 18969 const data = this.data; 18970 const id = data.id; 18971 let value = storage.getValue(id, { 18972 value: data.buttonValue !== null && data.fieldValue === data.buttonValue 18973 }).value; 18974 if (typeof value === "string") { 18975 value = value !== data.buttonValue; 18976 storage.setValue(id, { 18977 value 18978 }); 18979 } 18980 if (value) { 18981 for (const radio of this._getElementsByName(data.fieldName, id)) { 18982 storage.setValue(radio.id, { 18983 value: false 18984 }); 18985 } 18986 } 18987 const element = document.createElement("input"); 18988 GetElementsByNameSet.add(element); 18989 element.setAttribute("data-element-id", id); 18990 element.disabled = data.readOnly; 18991 this._setRequired(element, this.data.required); 18992 element.type = "radio"; 18993 element.name = data.fieldName; 18994 if (value) { 18995 element.setAttribute("checked", true); 18996 } 18997 element.tabIndex = 0; 18998 element.addEventListener("change", event => { 18999 const { 19000 name, 19001 checked 19002 } = event.target; 19003 for (const radio of this._getElementsByName(name, id)) { 19004 storage.setValue(radio.id, { 19005 value: false 19006 }); 19007 } 19008 storage.setValue(id, { 19009 value: checked 19010 }); 19011 }); 19012 element.addEventListener("resetform", event => { 19013 const defaultValue = data.defaultFieldValue; 19014 event.target.checked = defaultValue !== null && defaultValue !== undefined && defaultValue === data.buttonValue; 19015 }); 19016 if (this.enableScripting && this.hasJSActions) { 19017 const pdfButtonValue = data.buttonValue; 19018 element.addEventListener("updatefromsandbox", jsEvent => { 19019 const actions = { 19020 value: event => { 19021 const checked = pdfButtonValue === event.detail.value; 19022 for (const radio of this._getElementsByName(event.target.name)) { 19023 const curChecked = checked && radio.id === id; 19024 if (radio.domElement) { 19025 radio.domElement.checked = curChecked; 19026 } 19027 storage.setValue(radio.id, { 19028 value: curChecked 19029 }); 19030 } 19031 } 19032 }; 19033 this._dispatchEventFromSandbox(actions, jsEvent); 19034 }); 19035 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); 19036 } 19037 this._setDefaultPropertiesFromJS(element); 19038 this.container.append(element); 19039 return this.container; 19040 } 19041} 19042class PushButtonWidgetAnnotationElement extends LinkAnnotationElement { 19043 constructor(parameters) { 19044 super(parameters, { 19045 ignoreBorder: parameters.data.hasAppearance 19046 }); 19047 } 19048 render() { 19049 const container = super.render(); 19050 container.classList.add("buttonWidgetAnnotation", "pushButton"); 19051 const linkElement = container.lastChild; 19052 if (this.enableScripting && this.hasJSActions && linkElement) { 19053 this._setDefaultPropertiesFromJS(linkElement); 19054 linkElement.addEventListener("updatefromsandbox", jsEvent => { 19055 this._dispatchEventFromSandbox({}, jsEvent); 19056 }); 19057 } 19058 return container; 19059 } 19060} 19061class ChoiceWidgetAnnotationElement extends WidgetAnnotationElement { 19062 constructor(parameters) { 19063 super(parameters, { 19064 isRenderable: parameters.renderForms 19065 }); 19066 } 19067 render() { 19068 this.container.classList.add("choiceWidgetAnnotation"); 19069 const storage = this.annotationStorage; 19070 const id = this.data.id; 19071 const storedData = storage.getValue(id, { 19072 value: this.data.fieldValue 19073 }); 19074 const selectElement = document.createElement("select"); 19075 GetElementsByNameSet.add(selectElement); 19076 selectElement.setAttribute("data-element-id", id); 19077 selectElement.disabled = this.data.readOnly; 19078 this._setRequired(selectElement, this.data.required); 19079 selectElement.name = this.data.fieldName; 19080 selectElement.tabIndex = 0; 19081 let addAnEmptyEntry = this.data.combo && this.data.options.length > 0; 19082 if (!this.data.combo) { 19083 selectElement.size = this.data.options.length; 19084 if (this.data.multiSelect) { 19085 selectElement.multiple = true; 19086 } 19087 } 19088 selectElement.addEventListener("resetform", event => { 19089 const defaultValue = this.data.defaultFieldValue; 19090 for (const option of selectElement.options) { 19091 option.selected = option.value === defaultValue; 19092 } 19093 }); 19094 const fixDisplayValue = (option, value) => { 19095 const newValue = value.replaceAll(" ", "\u00A0"); 19096 option.textContent = newValue; 19097 if (newValue !== value) { 19098 option.setAttribute("display-value", value); 19099 } 19100 }; 19101 for (const option of this.data.options) { 19102 const optionElement = document.createElement("option"); 19103 fixDisplayValue(optionElement, option.displayValue); 19104 optionElement.value = option.exportValue; 19105 if (storedData.value.includes(option.exportValue)) { 19106 optionElement.setAttribute("selected", true); 19107 addAnEmptyEntry = false; 19108 } 19109 selectElement.append(optionElement); 19110 } 19111 let removeEmptyEntry = null; 19112 if (addAnEmptyEntry) { 19113 const noneOptionElement = document.createElement("option"); 19114 noneOptionElement.value = " "; 19115 noneOptionElement.setAttribute("hidden", true); 19116 noneOptionElement.setAttribute("selected", true); 19117 selectElement.prepend(noneOptionElement); 19118 removeEmptyEntry = () => { 19119 noneOptionElement.remove(); 19120 selectElement.removeEventListener("input", removeEmptyEntry); 19121 removeEmptyEntry = null; 19122 }; 19123 selectElement.addEventListener("input", removeEmptyEntry); 19124 } 19125 const getValue = isExport => { 19126 const name = isExport ? "value" : "textContent"; 19127 const { 19128 options, 19129 multiple 19130 } = selectElement; 19131 if (!multiple) { 19132 return options.selectedIndex === -1 ? null : options[options.selectedIndex][name]; 19133 } 19134 return Array.prototype.filter.call(options, option => option.selected).map(option => option[name]); 19135 }; 19136 let selectedValues = getValue(false); 19137 const getItems = event => { 19138 const options = event.target.options; 19139 return Array.prototype.map.call(options, option => ({ 19140 displayValue: option.getAttribute("display-value") || option.textContent, 19141 exportValue: option.value 19142 })); 19143 }; 19144 if (this.enableScripting && this.hasJSActions) { 19145 selectElement.addEventListener("updatefromsandbox", jsEvent => { 19146 const actions = { 19147 value(event) { 19148 removeEmptyEntry?.(); 19149 const value = event.detail.value; 19150 const values = new Set(Array.isArray(value) ? value : [value]); 19151 for (const option of selectElement.options) { 19152 option.selected = values.has(option.value); 19153 } 19154 storage.setValue(id, { 19155 value: getValue(true) 19156 }); 19157 selectedValues = getValue(false); 19158 }, 19159 multipleSelection(event) { 19160 selectElement.multiple = true; 19161 }, 19162 remove(event) { 19163 const options = selectElement.options; 19164 const index = event.detail.remove; 19165 options[index].selected = false; 19166 selectElement.remove(index); 19167 if (options.length > 0) { 19168 const i = Array.prototype.findIndex.call(options, option => option.selected); 19169 if (i === -1) { 19170 options[0].selected = true; 19171 } 19172 } 19173 storage.setValue(id, { 19174 value: getValue(true), 19175 items: getItems(event) 19176 }); 19177 selectedValues = getValue(false); 19178 }, 19179 clear(event) { 19180 while (selectElement.length !== 0) { 19181 selectElement.remove(0); 19182 } 19183 storage.setValue(id, { 19184 value: null, 19185 items: [] 19186 }); 19187 selectedValues = getValue(false); 19188 }, 19189 insert(event) { 19190 const { 19191 index, 19192 displayValue, 19193 exportValue 19194 } = event.detail.insert; 19195 const selectChild = selectElement.children[index]; 19196 const optionElement = document.createElement("option"); 19197 fixDisplayValue(optionElement, displayValue); 19198 optionElement.value = exportValue; 19199 if (selectChild) { 19200 selectChild.before(optionElement); 19201 } else { 19202 selectElement.append(optionElement); 19203 } 19204 storage.setValue(id, { 19205 value: getValue(true), 19206 items: getItems(event) 19207 }); 19208 selectedValues = getValue(false); 19209 }, 19210 items(event) { 19211 const { 19212 items 19213 } = event.detail; 19214 while (selectElement.length !== 0) { 19215 selectElement.remove(0); 19216 } 19217 for (const item of items) { 19218 const { 19219 displayValue, 19220 exportValue 19221 } = item; 19222 const optionElement = document.createElement("option"); 19223 fixDisplayValue(optionElement, displayValue); 19224 optionElement.value = exportValue; 19225 selectElement.append(optionElement); 19226 } 19227 if (selectElement.options.length > 0) { 19228 selectElement.options[0].selected = true; 19229 } 19230 storage.setValue(id, { 19231 value: getValue(true), 19232 items: getItems(event) 19233 }); 19234 selectedValues = getValue(false); 19235 }, 19236 indices(event) { 19237 const indices = new Set(event.detail.indices); 19238 for (const option of event.target.options) { 19239 option.selected = indices.has(option.index); 19240 } 19241 storage.setValue(id, { 19242 value: getValue(true) 19243 }); 19244 selectedValues = getValue(false); 19245 }, 19246 editable(event) { 19247 event.target.disabled = !event.detail.editable; 19248 } 19249 }; 19250 this._dispatchEventFromSandbox(actions, jsEvent); 19251 }); 19252 selectElement.addEventListener("input", event => { 19253 const exportValue = getValue(true); 19254 const change = getValue(false); 19255 storage.setValue(id, { 19256 value: exportValue 19257 }); 19258 event.preventDefault(); 19259 this.linkService.eventBus?.dispatch("dispatcheventinsandbox", { 19260 source: this, 19261 detail: { 19262 id, 19263 name: "Keystroke", 19264 value: selectedValues, 19265 change, 19266 changeEx: exportValue, 19267 willCommit: false, 19268 commitKey: 1, 19269 keyDown: false 19270 } 19271 }); 19272 }); 19273 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); 19274 } else { 19275 selectElement.addEventListener("input", function (event) { 19276 storage.setValue(id, { 19277 value: getValue(true) 19278 }); 19279 }); 19280 } 19281 if (this.data.combo) { 19282 this._setTextStyle(selectElement); 19283 } else {} 19284 this._setBackgroundColor(selectElement); 19285 this._setDefaultPropertiesFromJS(selectElement); 19286 this.container.append(selectElement); 19287 return this.container; 19288 } 19289} 19290class PopupAnnotationElement extends AnnotationElement { 19291 constructor(parameters) { 19292 const { 19293 data, 19294 elements, 19295 parent 19296 } = parameters; 19297 const hasCommentManager = !!parent._commentManager; 19298 super(parameters, { 19299 isRenderable: !hasCommentManager && AnnotationElement._hasPopupData(data) 19300 }); 19301 this.elements = elements; 19302 if (hasCommentManager && AnnotationElement._hasPopupData(data)) { 19303 const popup = this.popup = this.#createPopup(); 19304 for (const element of elements) { 19305 element.popup = popup; 19306 } 19307 } else { 19308 this.popup = null; 19309 } 19310 } 19311 #createPopup() { 19312 return new PopupElement({ 19313 container: this.container, 19314 color: this.data.color, 19315 titleObj: this.data.titleObj, 19316 modificationDate: this.data.modificationDate || this.data.creationDate, 19317 contentsObj: this.data.contentsObj, 19318 richText: this.data.richText, 19319 rect: this.data.rect, 19320 parentRect: this.data.parentRect || null, 19321 parent: this.parent, 19322 elements: this.elements, 19323 open: this.data.open, 19324 commentManager: this.parent._commentManager 19325 }); 19326 } 19327 render() { 19328 const { 19329 container 19330 } = this; 19331 container.classList.add("popupAnnotation"); 19332 container.role = "comment"; 19333 const popup = this.popup = this.#createPopup(); 19334 const elementIds = []; 19335 for (const element of this.elements) { 19336 element.popup = popup; 19337 element.container.ariaHasPopup = "dialog"; 19338 elementIds.push(element.data.id); 19339 element.addHighlightArea(); 19340 } 19341 this.container.setAttribute("aria-controls", elementIds.map(id => `${AnnotationPrefix}${id}`).join(",")); 19342 return this.container; 19343 } 19344} 19345class PopupElement { 19346 #commentManager = null; 19347 #boundKeyDown = this.#keyDown.bind(this); 19348 #boundHide = this.#hide.bind(this); 19349 #boundShow = this.#show.bind(this); 19350 #boundToggle = this.#toggle.bind(this); 19351 #color = null; 19352 #container = null; 19353 #contentsObj = null; 19354 #dateObj = null; 19355 #elements = null; 19356 #parent = null; 19357 #parentRect = null; 19358 #pinned = false; 19359 #popup = null; 19360 #popupAbortController = null; 19361 #position = null; 19362 #commentButton = null; 19363 #commentButtonPosition = null; 19364 #popupPosition = null; 19365 #rect = null; 19366 #richText = null; 19367 #titleObj = null; 19368 #updates = null; 19369 #wasVisible = false; 19370 #firstElement = null; 19371 #commentText = null; 19372 constructor({ 19373 container, 19374 color, 19375 elements, 19376 titleObj, 19377 modificationDate, 19378 contentsObj, 19379 richText, 19380 parent, 19381 rect, 19382 parentRect, 19383 open, 19384 commentManager = null 19385 }) { 19386 this.#container = container; 19387 this.#titleObj = titleObj; 19388 this.#contentsObj = contentsObj; 19389 this.#richText = richText; 19390 this.#parent = parent; 19391 this.#color = color; 19392 this.#rect = rect; 19393 this.#parentRect = parentRect; 19394 this.#elements = elements; 19395 this.#commentManager = commentManager; 19396 this.#firstElement = elements[0]; 19397 this.#dateObj = PDFDateString.toDateObject(modificationDate); 19398 this.trigger = elements.flatMap(e => e.getElementsToTriggerPopup()); 19399 if (!commentManager) { 19400 this.#addEventListeners(); 19401 this.#container.hidden = true; 19402 if (open) { 19403 this.#toggle(); 19404 } 19405 } 19406 } 19407 #addEventListeners() { 19408 if (this.#popupAbortController) { 19409 return; 19410 } 19411 this.#popupAbortController = new AbortController(); 19412 const { 19413 signal 19414 } = this.#popupAbortController; 19415 for (const element of this.trigger) { 19416 element.addEventListener("click", this.#boundToggle, { 19417 signal 19418 }); 19419 element.addEventListener("pointerenter", this.#boundShow, { 19420 signal 19421 }); 19422 element.addEventListener("pointerleave", this.#boundHide, { 19423 signal 19424 }); 19425 element.classList.add("popupTriggerArea"); 19426 } 19427 for (const element of this.#elements) { 19428 element.container?.addEventListener("keydown", this.#boundKeyDown, { 19429 signal 19430 }); 19431 } 19432 } 19433 #setCommentButtonPosition() { 19434 const element = this.#elements.find(e => e.hasCommentButton); 19435 if (!element) { 19436 return; 19437 } 19438 this.#commentButtonPosition = element._normalizePoint(element.commentButtonPosition); 19439 } 19440 renderCommentButton() { 19441 if (this.#commentButton) { 19442 if (!this.#commentButton.parentNode) { 19443 this.#firstElement.container.after(this.#commentButton); 19444 } 19445 return; 19446 } 19447 if (!this.#commentButtonPosition) { 19448 this.#setCommentButtonPosition(); 19449 } 19450 if (!this.#commentButtonPosition) { 19451 return; 19452 } 19453 const { 19454 signal 19455 } = this.#popupAbortController = new AbortController(); 19456 const hasOwnButton = this.#firstElement.hasOwnCommentButton; 19457 const togglePopup = () => { 19458 this.#commentManager.toggleCommentPopup(this, true, undefined, !hasOwnButton); 19459 }; 19460 const showPopup = () => { 19461 this.#commentManager.toggleCommentPopup(this, false, true, !hasOwnButton); 19462 }; 19463 const hidePopup = () => { 19464 this.#commentManager.toggleCommentPopup(this, false, false); 19465 }; 19466 if (!hasOwnButton) { 19467 const button = this.#commentButton = document.createElement("button"); 19468 button.className = "annotationCommentButton"; 19469 const parentContainer = this.#firstElement.container; 19470 button.style.zIndex = parseInt(parentContainer.style.zIndex, 10) + 1; 19471 button.tabIndex = 0; 19472 button.ariaHasPopup = "dialog"; 19473 button.ariaControls = "commentPopup"; 19474 button.setAttribute("data-l10n-id", "pdfjs-show-comment-button"); 19475 this.#updateColor(); 19476 this.#updateCommentButtonPosition(); 19477 button.addEventListener("keydown", this.#boundKeyDown, { 19478 signal 19479 }); 19480 button.addEventListener("click", togglePopup, { 19481 signal 19482 }); 19483 button.addEventListener("pointerenter", showPopup, { 19484 signal 19485 }); 19486 button.addEventListener("pointerleave", hidePopup, { 19487 signal 19488 }); 19489 parentContainer.after(button); 19490 } else { 19491 this.#commentButton = this.#firstElement.container; 19492 for (const element of this.trigger) { 19493 element.ariaHasPopup = "dialog"; 19494 element.ariaControls = "commentPopup"; 19495 element.addEventListener("keydown", this.#boundKeyDown, { 19496 signal 19497 }); 19498 element.addEventListener("click", togglePopup, { 19499 signal 19500 }); 19501 element.addEventListener("pointerenter", showPopup, { 19502 signal 19503 }); 19504 element.addEventListener("pointerleave", hidePopup, { 19505 signal 19506 }); 19507 element.classList.add("popupTriggerArea"); 19508 } 19509 } 19510 } 19511 #updateCommentButtonPosition() { 19512 if (this.#firstElement.extraPopupElement && !this.#firstElement.editor) { 19513 return; 19514 } 19515 if (!this.#commentButton) { 19516 this.renderCommentButton(); 19517 } 19518 const [x, y] = this.#commentButtonPosition; 19519 const { 19520 style 19521 } = this.#commentButton; 19522 style.left = `calc(${x}%)`; 19523 style.top = `calc(${y}% - var(--comment-button-dim))`; 19524 } 19525 #updateColor() { 19526 if (this.#firstElement.extraPopupElement) { 19527 return; 19528 } 19529 if (!this.#commentButton) { 19530 this.renderCommentButton(); 19531 } 19532 this.#commentButton.style.backgroundColor = this.commentButtonColor || ""; 19533 } 19534 get commentButtonColor() { 19535 const { 19536 color, 19537 opacity 19538 } = this.#firstElement.commentData; 19539 if (!color) { 19540 return null; 19541 } 19542 return this.#parent._commentManager.makeCommentColor(color, opacity); 19543 } 19544 focusCommentButton() { 19545 setTimeout(() => { 19546 this.#commentButton?.focus(); 19547 }, 0); 19548 } 19549 getData() { 19550 const { 19551 richText, 19552 color, 19553 opacity, 19554 creationDate, 19555 modificationDate 19556 } = this.#firstElement.commentData; 19557 return { 19558 contentsObj: { 19559 str: this.comment 19560 }, 19561 richText, 19562 color, 19563 opacity, 19564 creationDate, 19565 modificationDate 19566 }; 19567 } 19568 get elementBeforePopup() { 19569 return this.#commentButton; 19570 } 19571 get comment() { 19572 this.#commentText ||= this.#firstElement.commentText; 19573 return this.#commentText; 19574 } 19575 set comment(text) { 19576 if (text === this.comment) { 19577 return; 19578 } 19579 this.#firstElement.commentText = this.#commentText = text; 19580 } 19581 focus() { 19582 this.#firstElement.container?.focus(); 19583 } 19584 get parentBoundingClientRect() { 19585 return this.#firstElement.layer.getBoundingClientRect(); 19586 } 19587 setCommentButtonStates({ 19588 selected, 19589 hasPopup 19590 }) { 19591 if (!this.#commentButton) { 19592 return; 19593 } 19594 this.#commentButton.classList.toggle("selected", selected); 19595 this.#commentButton.ariaExpanded = hasPopup; 19596 } 19597 setSelectedCommentButton(selected) { 19598 this.#commentButton.classList.toggle("selected", selected); 19599 } 19600 get commentPopupPosition() { 19601 if (this.#popupPosition) { 19602 return this.#popupPosition; 19603 } 19604 const { 19605 x, 19606 y, 19607 height 19608 } = this.#commentButton.getBoundingClientRect(); 19609 const { 19610 x: parentX, 19611 y: parentY, 19612 width: parentWidth, 19613 height: parentHeight 19614 } = this.#firstElement.layer.getBoundingClientRect(); 19615 return [(x - parentX) / parentWidth, (y + height - parentY) / parentHeight]; 19616 } 19617 set commentPopupPosition(pos) { 19618 this.#popupPosition = pos; 19619 } 19620 hasDefaultPopupPosition() { 19621 return this.#popupPosition === null; 19622 } 19623 get commentButtonPosition() { 19624 return this.#commentButtonPosition; 19625 } 19626 get commentButtonWidth() { 19627 return this.#commentButton.getBoundingClientRect().width / this.parentBoundingClientRect.width; 19628 } 19629 editComment(options) { 19630 const [posX, posY] = this.#popupPosition || this.commentButtonPosition.map(x => x / 100); 19631 const parentDimensions = this.parentBoundingClientRect; 19632 const { 19633 x: parentX, 19634 y: parentY, 19635 width: parentWidth, 19636 height: parentHeight 19637 } = parentDimensions; 19638 this.#commentManager.showDialog(null, this, parentX + posX * parentWidth, parentY + posY * parentHeight, { 19639 ...options, 19640 parentDimensions 19641 }); 19642 } 19643 render() { 19644 if (this.#popup) { 19645 return; 19646 } 19647 const popup = this.#popup = document.createElement("div"); 19648 popup.className = "popup"; 19649 if (this.#color) { 19650 const baseColor = popup.style.outlineColor = Util.makeHexColor(...this.#color); 19651 popup.style.backgroundColor = `color-mix(in srgb, ${baseColor} 30%, white)`; 19652 } 19653 const header = document.createElement("span"); 19654 header.className = "header"; 19655 if (this.#titleObj?.str) { 19656 const title = document.createElement("span"); 19657 title.className = "title"; 19658 header.append(title); 19659 ({ 19660 dir: title.dir, 19661 str: title.textContent 19662 } = this.#titleObj); 19663 } 19664 popup.append(header); 19665 if (this.#dateObj) { 19666 const modificationDate = document.createElement("time"); 19667 modificationDate.className = "popupDate"; 19668 modificationDate.setAttribute("data-l10n-id", "pdfjs-annotation-date-time-string"); 19669 modificationDate.setAttribute("data-l10n-args", JSON.stringify({ 19670 dateObj: this.#dateObj.valueOf() 19671 })); 19672 modificationDate.dateTime = this.#dateObj.toISOString(); 19673 header.append(modificationDate); 19674 } 19675 renderRichText({ 19676 html: this.#html || this.#contentsObj.str, 19677 dir: this.#contentsObj?.dir, 19678 className: "popupContent" 19679 }, popup); 19680 this.#container.append(popup); 19681 } 19682 get #html() { 19683 const richText = this.#richText; 19684 const contentsObj = this.#contentsObj; 19685 if (richText?.str && (!contentsObj?.str || contentsObj.str === richText.str)) { 19686 return this.#richText.html || null; 19687 } 19688 return null; 19689 } 19690 get #fontSize() { 19691 return this.#html?.attributes?.style?.fontSize || 0; 19692 } 19693 get #fontColor() { 19694 return this.#html?.attributes?.style?.color || null; 19695 } 19696 #makePopupContent(text) { 19697 const popupLines = []; 19698 const popupContent = { 19699 str: text, 19700 html: { 19701 name: "div", 19702 attributes: { 19703 dir: "auto" 19704 }, 19705 children: [{ 19706 name: "p", 19707 children: popupLines 19708 }] 19709 } 19710 }; 19711 const lineAttributes = { 19712 style: { 19713 color: this.#fontColor, 19714 fontSize: this.#fontSize ? `calc(${this.#fontSize}px * var(--total-scale-factor))` : "" 19715 } 19716 }; 19717 for (const line of text.split("\n")) { 19718 popupLines.push({ 19719 name: "span", 19720 value: line, 19721 attributes: lineAttributes 19722 }); 19723 } 19724 return popupContent; 19725 } 19726 #keyDown(event) { 19727 if (event.altKey || event.shiftKey || event.ctrlKey || event.metaKey) { 19728 return; 19729 } 19730 if (event.key === "Enter" || event.key === "Escape" && this.#pinned) { 19731 this.#toggle(); 19732 } 19733 } 19734 updateEdited({ 19735 rect, 19736 popup, 19737 deleted 19738 }) { 19739 if (this.#commentManager) { 19740 if (deleted) { 19741 this.remove(); 19742 this.#commentText = null; 19743 } else if (popup) { 19744 if (popup.deleted) { 19745 this.remove(); 19746 } else { 19747 this.#updateColor(); 19748 this.#commentText = popup.text; 19749 } 19750 } 19751 if (rect) { 19752 this.#commentButtonPosition = null; 19753 this.#setCommentButtonPosition(); 19754 this.#updateCommentButtonPosition(); 19755 } 19756 return; 19757 } 19758 if (deleted || popup?.deleted) { 19759 this.remove(); 19760 return; 19761 } 19762 this.#addEventListeners(); 19763 this.#updates ||= { 19764 contentsObj: this.#contentsObj, 19765 richText: this.#richText 19766 }; 19767 if (rect) { 19768 this.#position = null; 19769 } 19770 if (popup && popup.text) { 19771 this.#richText = this.#makePopupContent(popup.text); 19772 this.#dateObj = PDFDateString.toDateObject(popup.date); 19773 this.#contentsObj = null; 19774 } 19775 this.#popup?.remove(); 19776 this.#popup = null; 19777 } 19778 resetEdited() { 19779 if (!this.#updates) { 19780 return; 19781 } 19782 ({ 19783 contentsObj: this.#contentsObj, 19784 richText: this.#richText 19785 } = this.#updates); 19786 this.#updates = null; 19787 this.#popup?.remove(); 19788 this.#popup = null; 19789 this.#position = null; 19790 } 19791 remove() { 19792 this.#popupAbortController?.abort(); 19793 this.#popupAbortController = null; 19794 this.#popup?.remove(); 19795 this.#popup = null; 19796 this.#wasVisible = false; 19797 this.#pinned = false; 19798 this.#commentButton?.remove(); 19799 this.#commentButton = null; 19800 if (this.trigger) { 19801 for (const element of this.trigger) { 19802 element.classList.remove("popupTriggerArea"); 19803 } 19804 } 19805 } 19806 #setPosition() { 19807 if (this.#position !== null) { 19808 return; 19809 } 19810 const { 19811 page: { 19812 view 19813 }, 19814 viewport: { 19815 rawDims: { 19816 pageWidth, 19817 pageHeight, 19818 pageX, 19819 pageY 19820 } 19821 } 19822 } = this.#parent; 19823 let useParentRect = !!this.#parentRect; 19824 let rect = useParentRect ? this.#parentRect : this.#rect; 19825 for (const element of this.#elements) { 19826 if (!rect || Util.intersect(element.data.rect, rect) !== null) { 19827 rect = element.data.rect; 19828 useParentRect = true; 19829 break; 19830 } 19831 } 19832 const normalizedRect = Util.normalizeRect([rect[0], view[3] - rect[1] + view[1], rect[2], view[3] - rect[3] + view[1]]); 19833 const HORIZONTAL_SPACE_AFTER_ANNOTATION = 5; 19834 const parentWidth = useParentRect ? rect[2] - rect[0] + HORIZONTAL_SPACE_AFTER_ANNOTATION : 0; 19835 const popupLeft = normalizedRect[0] + parentWidth; 19836 const popupTop = normalizedRect[1]; 19837 this.#position = [100 * (popupLeft - pageX) / pageWidth, 100 * (popupTop - pageY) / pageHeight]; 19838 const { 19839 style 19840 } = this.#container; 19841 style.left = `${this.#position[0]}%`; 19842 style.top = `${this.#position[1]}%`; 19843 } 19844 #toggle() { 19845 if (this.#commentManager) { 19846 this.#commentManager.toggleCommentPopup(this, false); 19847 return; 19848 } 19849 this.#pinned = !this.#pinned; 19850 if (this.#pinned) { 19851 this.#show(); 19852 this.#container.addEventListener("click", this.#boundToggle); 19853 this.#container.addEventListener("keydown", this.#boundKeyDown); 19854 } else { 19855 this.#hide(); 19856 this.#container.removeEventListener("click", this.#boundToggle); 19857 this.#container.removeEventListener("keydown", this.#boundKeyDown); 19858 } 19859 } 19860 #show() { 19861 if (!this.#popup) { 19862 this.render(); 19863 } 19864 if (!this.isVisible) { 19865 this.#setPosition(); 19866 this.#container.hidden = false; 19867 this.#container.style.zIndex = parseInt(this.#container.style.zIndex, 10) + 1000; 19868 } else if (this.#pinned) { 19869 this.#container.classList.add("focused"); 19870 } 19871 } 19872 #hide() { 19873 this.#container.classList.remove("focused"); 19874 if (this.#pinned || !this.isVisible) { 19875 return; 19876 } 19877 this.#container.hidden = true; 19878 this.#container.style.zIndex = parseInt(this.#container.style.zIndex, 10) - 1000; 19879 } 19880 forceHide() { 19881 this.#wasVisible = this.isVisible; 19882 if (!this.#wasVisible) { 19883 return; 19884 } 19885 this.#container.hidden = true; 19886 } 19887 maybeShow() { 19888 if (this.#commentManager) { 19889 return; 19890 } 19891 this.#addEventListeners(); 19892 if (!this.#wasVisible) { 19893 return; 19894 } 19895 if (!this.#popup) { 19896 this.#show(); 19897 } 19898 this.#wasVisible = false; 19899 this.#container.hidden = false; 19900 } 19901 get isVisible() { 19902
vendor: 9,224 bytes, lines 19902-20203
19902if (this.#commentManager) { 19903 return false; 19904 } 19905 return this.#container.hidden === false; 19906 } 19907} 19908class FreeTextAnnotationElement extends AnnotationElement { 19909 constructor(parameters) { 19910 super(parameters, { 19911 isRenderable: true, 19912 ignoreBorder: true 19913 }); 19914 this.textContent = parameters.data.textContent; 19915 this.textPosition = parameters.data.textPosition; 19916 this.annotationEditorType = AnnotationEditorType.FREETEXT; 19917 } 19918 render() { 19919 this.container.classList.add("freeTextAnnotation"); 19920 if (this.textContent) { 19921 const content = this.contentElement = document.createElement("div"); 19922 content.classList.add("annotationTextContent"); 19923 content.setAttribute("role", "comment"); 19924 for (const line of this.textContent) { 19925 const lineSpan = document.createElement("span"); 19926 lineSpan.textContent = line; 19927 content.append(lineSpan); 19928 } 19929 this.container.append(content); 19930 } 19931 if (!this.data.popupRef && this.hasPopupData) { 19932 this.hasOwnCommentButton = true; 19933 this._createPopup(); 19934 } 19935 this._editOnDoubleClick(); 19936 return this.container; 19937 } 19938} 19939class LineAnnotationElement extends AnnotationElement { 19940 #line = null; 19941 constructor(parameters) { 19942 super(parameters, { 19943 isRenderable: true, 19944 ignoreBorder: true 19945 }); 19946 } 19947 render() { 19948 this.container.classList.add("lineAnnotation"); 19949 const { 19950 data, 19951 width, 19952 height 19953 } = this; 19954 const svg = this.svgFactory.create(width, height, true); 19955 const line = this.#line = this.svgFactory.createElement("svg:line"); 19956 line.setAttribute("x1", data.rect[2] - data.lineCoordinates[0]); 19957 line.setAttribute("y1", data.rect[3] - data.lineCoordinates[1]); 19958 line.setAttribute("x2", data.rect[2] - data.lineCoordinates[2]); 19959 line.setAttribute("y2", data.rect[3] - data.lineCoordinates[3]); 19960 line.setAttribute("stroke-width", data.borderStyle.width || 1); 19961 line.setAttribute("stroke", "transparent"); 19962 line.setAttribute("fill", "transparent"); 19963 svg.append(line); 19964 this.container.append(svg); 19965 if (!data.popupRef && this.hasPopupData) { 19966 this.hasOwnCommentButton = true; 19967 this._createPopup(); 19968 } 19969 return this.container; 19970 } 19971 getElementsToTriggerPopup() { 19972 return this.#line; 19973 } 19974 addHighlightArea() { 19975 this.container.classList.add("highlightArea"); 19976 } 19977} 19978class SquareAnnotationElement extends AnnotationElement { 19979 #square = null; 19980 constructor(parameters) { 19981 super(parameters, { 19982 isRenderable: true, 19983 ignoreBorder: true 19984 }); 19985 } 19986 render() { 19987 this.container.classList.add("squareAnnotation"); 19988 const { 19989 data, 19990 width, 19991 height 19992 } = this; 19993 const svg = this.svgFactory.create(width, height, true); 19994 const borderWidth = data.borderStyle.width; 19995 const square = this.#square = this.svgFactory.createElement("svg:rect"); 19996 square.setAttribute("x", borderWidth / 2); 19997 square.setAttribute("y", borderWidth / 2); 19998 square.setAttribute("width", width - borderWidth); 19999 square.setAttribute("height", height - borderWidth); 20000 square.setAttribute("stroke-width", borderWidth || 1); 20001 square.setAttribute("stroke", "transparent"); 20002 square.setAttribute("fill", "transparent"); 20003 svg.append(square); 20004 this.container.append(svg); 20005 if (!data.popupRef && this.hasPopupData) { 20006 this.hasOwnCommentButton = true; 20007 this._createPopup(); 20008 } 20009 return this.container; 20010 } 20011 getElementsToTriggerPopup() { 20012 return this.#square; 20013 } 20014 addHighlightArea() { 20015 this.container.classList.add("highlightArea"); 20016 } 20017} 20018class CircleAnnotationElement extends AnnotationElement { 20019 #circle = null; 20020 constructor(parameters) { 20021 super(parameters, { 20022 isRenderable: true, 20023 ignoreBorder: true 20024 }); 20025 } 20026 render() { 20027 this.container.classList.add("circleAnnotation"); 20028 const { 20029 data, 20030 width, 20031 height 20032 } = this; 20033 const svg = this.svgFactory.create(width, height, true); 20034 const borderWidth = data.borderStyle.width; 20035 const circle = this.#circle = this.svgFactory.createElement("svg:ellipse"); 20036 circle.setAttribute("cx", width / 2); 20037 circle.setAttribute("cy", height / 2); 20038 circle.setAttribute("rx", width / 2 - borderWidth / 2); 20039 circle.setAttribute("ry", height / 2 - borderWidth / 2); 20040 circle.setAttribute("stroke-width", borderWidth || 1); 20041 circle.setAttribute("stroke", "transparent"); 20042 circle.setAttribute("fill", "transparent"); 20043 svg.append(circle); 20044 this.container.append(svg); 20045 if (!data.popupRef && this.hasPopupData) { 20046 this.hasOwnCommentButton = true; 20047 this._createPopup(); 20048 } 20049 return this.container; 20050 } 20051 getElementsToTriggerPopup() { 20052 return this.#circle; 20053 } 20054 addHighlightArea() { 20055 this.container.classList.add("highlightArea"); 20056 } 20057} 20058class PolylineAnnotationElement extends AnnotationElement { 20059 #polyline = null; 20060 constructor(parameters) { 20061 super(parameters, { 20062 isRenderable: true, 20063 ignoreBorder: true 20064 }); 20065 this.containerClassName = "polylineAnnotation"; 20066 this.svgElementName = "svg:polyline"; 20067 } 20068 render() { 20069 this.container.classList.add(this.containerClassName); 20070 const { 20071 data: { 20072 rect, 20073 vertices, 20074 borderStyle, 20075 popupRef 20076 }, 20077 width, 20078 height 20079 } = this; 20080 if (!vertices) { 20081 return this.container; 20082 } 20083 const svg = this.svgFactory.create(width, height, true); 20084 let points = []; 20085 for (let i = 0, ii = vertices.length; i < ii; i += 2) { 20086 const x = vertices[i] - rect[0]; 20087 const y = rect[3] - vertices[i + 1]; 20088 points.push(`${x},${y}`); 20089 } 20090 points = points.join(" "); 20091 const polyline = this.#polyline = this.svgFactory.createElement(this.svgElementName); 20092 polyline.setAttribute("points", points); 20093 polyline.setAttribute("stroke-width", borderStyle.width || 1); 20094 polyline.setAttribute("stroke", "transparent"); 20095 polyline.setAttribute("fill", "transparent"); 20096 svg.append(polyline); 20097 this.container.append(svg); 20098 if (!popupRef && this.hasPopupData) { 20099 this.hasOwnCommentButton = true; 20100 this._createPopup(); 20101 } 20102 return this.container; 20103 } 20104 getElementsToTriggerPopup() { 20105 return this.#polyline; 20106 } 20107 addHighlightArea() { 20108 this.container.classList.add("highlightArea"); 20109 } 20110} 20111class PolygonAnnotationElement extends PolylineAnnotationElement { 20112 constructor(parameters) { 20113 super(parameters); 20114 this.containerClassName = "polygonAnnotation"; 20115 this.svgElementName = "svg:polygon"; 20116 } 20117} 20118class CaretAnnotationElement extends AnnotationElement { 20119 constructor(parameters) { 20120 super(parameters, { 20121 isRenderable: true, 20122 ignoreBorder: true 20123 }); 20124 } 20125 render() { 20126 this.container.classList.add("caretAnnotation"); 20127 if (!this.data.popupRef && this.hasPopupData) { 20128 this.hasOwnCommentButton = true; 20129 this._createPopup(); 20130 } 20131 return this.container; 20132 } 20133} 20134class InkAnnotationElement extends AnnotationElement { 20135 #polylinesGroupElement = null; 20136 #polylines = []; 20137 constructor(parameters) { 20138 super(parameters, { 20139 isRenderable: true, 20140 ignoreBorder: true 20141 }); 20142 this.containerClassName = "inkAnnotation"; 20143 this.svgElementName = "svg:polyline"; 20144 this.annotationEditorType = this.data.it === "InkHighlight" ? AnnotationEditorType.HIGHLIGHT : AnnotationEditorType.INK; 20145 } 20146 #getTransform(rotation, rect) { 20147 switch (rotation) { 20148 case 90: 20149 return { 20150 transform: `rotate(90) translate(${-rect[0]},${rect[1]}) scale(1,-1)`, 20151 width: rect[3] - rect[1], 20152 height: rect[2] - rect[0] 20153 }; 20154 case 180: 20155 return { 20156 transform: `rotate(180) translate(${-rect[2]},${rect[1]}) scale(1,-1)`, 20157 width: rect[2] - rect[0], 20158 height: rect[3] - rect[1] 20159 }; 20160 case 270: 20161 return { 20162 transform: `rotate(270) translate(${-rect[2]},${rect[3]}) scale(1,-1)`, 20163 width: rect[3] - rect[1], 20164 height: rect[2] - rect[0] 20165 }; 20166 default: 20167 return { 20168 transform: `translate(${-rect[0]},${rect[3]}) scale(1,-1)`, 20169 width: rect[2] - rect[0], 20170 height: rect[3] - rect[1] 20171 }; 20172 } 20173 } 20174 render() { 20175 this.container.classList.add(this.containerClassName); 20176 const { 20177 data: { 20178 rect, 20179 rotation, 20180 inkLists, 20181 borderStyle, 20182 popupRef 20183 } 20184 } = this; 20185 const { 20186 transform, 20187 width, 20188 height 20189 } = this.#getTransform(rotation, rect); 20190 const svg = this.svgFactory.create(width, height, true); 20191 const g = this.#polylinesGroupElement = this.svgFactory.createElement("svg:g"); 20192 svg.append(g); 20193 g.setAttribute("stroke-width", borderStyle.width || 1); 20194 g.setAttribute("stroke-linecap", "round"); 20195 g.setAttribute("stroke-linejoin", "round"); 20196 g.setAttribute("stroke-miterlimit", 10); 20197 g.setAttribute("stroke", "transparent"); 20198 g.setAttribute("fill", "transparent"); 20199 g.setAttribute("transform", transform); 20200 for (let i = 0, ii = inkLists.length; i < ii; i++) { 20201 const polyline = this.svgFactory.createElement(this.svgElementName); 20202 this.#polylines.push(polyline); 20203 polyline.setAttribute("points", inkLists[i]
vendor: 13,748 bytes, lines 20203-20701
20203.join(",")); 20204 g.append(polyline); 20205 } 20206 if (!popupRef && this.hasPopupData) { 20207 this.hasOwnCommentButton = true; 20208 this._createPopup(); 20209 } 20210 this.container.append(svg); 20211 this._editOnDoubleClick(); 20212 return this.container; 20213 } 20214 updateEdited(params) { 20215 super.updateEdited(params); 20216 const { 20217 thickness, 20218 points, 20219 rect 20220 } = params; 20221 const g = this.#polylinesGroupElement; 20222 if (thickness >= 0) { 20223 g.setAttribute("stroke-width", thickness || 1); 20224 } 20225 if (points) { 20226 for (let i = 0, ii = this.#polylines.length; i < ii; i++) { 20227 this.#polylines[i].setAttribute("points", points[i].join(",")); 20228 } 20229 } 20230 if (rect) { 20231 const { 20232 transform, 20233 width, 20234 height 20235 } = this.#getTransform(this.data.rotation, rect); 20236 const root = g.parentElement; 20237 root.setAttribute("viewBox", `0 0 ${width} ${height}`); 20238 g.setAttribute("transform", transform); 20239 } 20240 } 20241 getElementsToTriggerPopup() { 20242 return this.#polylines; 20243 } 20244 addHighlightArea() { 20245 this.container.classList.add("highlightArea"); 20246 } 20247} 20248class HighlightAnnotationElement extends AnnotationElement { 20249 constructor(parameters) { 20250 super(parameters, { 20251 isRenderable: true, 20252 ignoreBorder: true, 20253 createQuadrilaterals: true 20254 }); 20255 this.annotationEditorType = AnnotationEditorType.HIGHLIGHT; 20256 } 20257 render() { 20258 const { 20259 data: { 20260 overlaidText, 20261 popupRef 20262 } 20263 } = this; 20264 if (!popupRef && this.hasPopupData) { 20265 this.hasOwnCommentButton = true; 20266 this._createPopup(); 20267 } 20268 this.container.classList.add("highlightAnnotation"); 20269 this._editOnDoubleClick(); 20270 if (overlaidText) { 20271 const mark = document.createElement("mark"); 20272 mark.classList.add("overlaidText"); 20273 mark.textContent = overlaidText; 20274 this.container.append(mark); 20275 } 20276 return this.container; 20277 } 20278} 20279class UnderlineAnnotationElement extends AnnotationElement { 20280 constructor(parameters) { 20281 super(parameters, { 20282 isRenderable: true, 20283 ignoreBorder: true, 20284 createQuadrilaterals: true 20285 }); 20286 } 20287 render() { 20288 const { 20289 data: { 20290 overlaidText, 20291 popupRef 20292 } 20293 } = this; 20294 if (!popupRef && this.hasPopupData) { 20295 this.hasOwnCommentButton = true; 20296 this._createPopup(); 20297 } 20298 this.container.classList.add("underlineAnnotation"); 20299 if (overlaidText) { 20300 const underline = document.createElement("u"); 20301 underline.classList.add("overlaidText"); 20302 underline.textContent = overlaidText; 20303 this.container.append(underline); 20304 } 20305 return this.container; 20306 } 20307} 20308class SquigglyAnnotationElement extends AnnotationElement { 20309 constructor(parameters) { 20310 super(parameters, { 20311 isRenderable: true, 20312 ignoreBorder: true, 20313 createQuadrilaterals: true 20314 }); 20315 } 20316 render() { 20317 const { 20318 data: { 20319 overlaidText, 20320 popupRef 20321 } 20322 } = this; 20323 if (!popupRef && this.hasPopupData) { 20324 this.hasOwnCommentButton = true; 20325 this._createPopup(); 20326 } 20327 this.container.classList.add("squigglyAnnotation"); 20328 if (overlaidText) { 20329 const underline = document.createElement("u"); 20330 underline.classList.add("overlaidText"); 20331 underline.textContent = overlaidText; 20332 this.container.append(underline); 20333 } 20334 return this.container; 20335 } 20336} 20337class StrikeOutAnnotationElement extends AnnotationElement { 20338 constructor(parameters) { 20339 super(parameters, { 20340 isRenderable: true, 20341 ignoreBorder: true, 20342 createQuadrilaterals: true 20343 }); 20344 } 20345 render() { 20346 const { 20347 data: { 20348 overlaidText, 20349 popupRef 20350 } 20351 } = this; 20352 if (!popupRef && this.hasPopupData) { 20353 this.hasOwnCommentButton = true; 20354 this._createPopup(); 20355 } 20356 this.container.classList.add("strikeoutAnnotation"); 20357 if (overlaidText) { 20358 const strikeout = document.createElement("s"); 20359 strikeout.classList.add("overlaidText"); 20360 strikeout.textContent = overlaidText; 20361 this.container.append(strikeout); 20362 } 20363 return this.container; 20364 } 20365} 20366class StampAnnotationElement extends AnnotationElement { 20367 constructor(parameters) { 20368 super(parameters, { 20369 isRenderable: true, 20370 ignoreBorder: true 20371 }); 20372 this.annotationEditorType = AnnotationEditorType.STAMP; 20373 } 20374 render() { 20375 this.container.classList.add("stampAnnotation"); 20376 this.container.setAttribute("role", "img"); 20377 if (!this.data.popupRef && this.hasPopupData) { 20378 this.hasOwnCommentButton = true; 20379 this._createPopup(); 20380 } 20381 this._editOnDoubleClick(); 20382 return this.container; 20383 } 20384} 20385class FileAttachmentAnnotationElement extends AnnotationElement { 20386 #trigger = null; 20387 constructor(parameters) { 20388 super(parameters, { 20389 isRenderable: true 20390 }); 20391 const { 20392 fileId, 20393 file 20394 } = this.data; 20395 this.filename = file.filename; 20396 this.content = file.content; 20397 this.fileId = fileId; 20398 this.linkService.eventBus?.dispatch("fileattachmentannotation", { 20399 source: this, 20400 attachmentId: this.fileId, 20401 ...file 20402 }); 20403 } 20404 render() { 20405 this.container.classList.add("fileAttachmentAnnotation"); 20406 const { 20407 container, 20408 data 20409 } = this; 20410 let trigger; 20411 if (data.hasAppearance || data.fillAlpha === 0) { 20412 trigger = document.createElement("div"); 20413 } else { 20414 trigger = document.createElement("img"); 20415 trigger.src = `${this.imageResourcesPath}annotation-${/paperclip/i.test(data.name) ? "paperclip" : "pushpin"}.svg`; 20416 if (data.fillAlpha && data.fillAlpha < 1) { 20417 trigger.style = `filter: opacity(${Math.round(data.fillAlpha * 100)}%);`; 20418 } 20419 } 20420 trigger.addEventListener("dblclick", this.#download.bind(this)); 20421 this.#trigger = trigger; 20422 const { 20423 isMac 20424 } = FeatureTest.platform; 20425 container.addEventListener("keydown", evt => { 20426 if (evt.key === "Enter" && (isMac ? evt.metaKey : evt.ctrlKey)) { 20427 this.#download(); 20428 } 20429 }); 20430 if (!data.popupRef && this.hasPopupData) { 20431 this.hasOwnCommentButton = true; 20432 this._createPopup(); 20433 } else { 20434 trigger.classList.add("popupTriggerArea"); 20435 } 20436 container.append(trigger); 20437 return container; 20438 } 20439 getElementsToTriggerPopup() { 20440 return this.#trigger; 20441 } 20442 addHighlightArea() { 20443 this.container.classList.add("highlightArea"); 20444 } 20445 async #download() { 20446 const { 20447 fileId, 20448 filename, 20449 content: fallbackContent 20450 } = this; 20451 const content = (await this.linkService.getAttachmentContent(fileId)) || fallbackContent; 20452 if (content) { 20453 this.downloadManager?.openOrDownloadData(content, filename); 20454 } 20455 } 20456} 20457class MediaAnnotationElement extends AnnotationElement { 20458 #abortController = new AbortController(); 20459 #contentUrl = null; 20460 #media = null; 20461 constructor(parameters) { 20462 super(parameters, { 20463 isRenderable: !!parameters.data.richMedia 20464 }); 20465 } 20466 render() { 20467 this.container.classList.add("mediaAnnotation"); 20468 const { 20469 filename 20470 } = this.data.richMedia; 20471 const button = document.createElement("button"); 20472 button.className = "mediaPlayButton"; 20473 button.type = "button"; 20474 button.title = button.ariaLabel = filename; 20475 button.addEventListener("click", () => this.#load(button), { 20476 signal: this.#abortController.signal 20477 }); 20478 this.container.append(button); 20479 return this.container; 20480 } 20481 async #load(button) { 20482 const { 20483 fileId, 20484 filename, 20485 contentType 20486 } = this.data.richMedia; 20487 button.disabled = true; 20488 let content; 20489 try { 20490 content = await this.linkService.getAttachmentContent(fileId); 20491 } catch { 20492 return; 20493 } finally { 20494 button.disabled = false; 20495 } 20496 if (!content || !button.isConnected) { 20497 return; 20498 } 20499 const { 20500 signal 20501 } = this.#abortController; 20502 const url = URL.createObjectURL(new Blob([content], { 20503 type: contentType 20504 })); 20505 this.#contentUrl = url; 20506 const isAudio = contentType.startsWith("audio/"); 20507 const media = document.createElement(isAudio ? "audio" : "video"); 20508 this.#media = media; 20509 media.className = "mediaContent"; 20510 this._setBackgroundColor(media); 20511 media.src = url; 20512 media.title = filename; 20513 media.controls = true; 20514 media.autoplay = true; 20515 media.tabIndex = 0; 20516 if (isAudio) { 20517 let hovered = false; 20518 let focused = false; 20519 const updateControls = () => { 20520 media.controls = hovered || focused; 20521 }; 20522 this.container.addEventListener("pointerenter", () => { 20523 hovered = true; 20524 updateControls(); 20525 }, { 20526 signal 20527 }); 20528 this.container.addEventListener("pointerleave", () => { 20529 hovered = false; 20530 updateControls(); 20531 }, { 20532 signal 20533 }); 20534 this.container.addEventListener("focusin", () => { 20535 focused = true; 20536 updateControls(); 20537 }, { 20538 signal 20539 }); 20540 this.container.addEventListener("focusout", () => { 20541 focused = false; 20542 updateControls(); 20543 }, { 20544 signal 20545 }); 20546 } 20547 media.addEventListener("emptied", () => this.#revokeContentUrl(url), { 20548 once: true, 20549 signal 20550 }); 20551 button.replaceWith(media); 20552 media.play().catch(() => {}); 20553 } 20554 #revokeContentUrl(url = this.#contentUrl) { 20555 if (url && url === this.#contentUrl) { 20556 URL.revokeObjectURL(url); 20557 this.#contentUrl = null; 20558 } 20559 } 20560 destroy() { 20561 this.#abortController.abort(); 20562 if (this.#media) { 20563 this.#media.pause(); 20564 this.#media.removeAttribute("src"); 20565 this.#media.load(); 20566 this.#media = null; 20567 } 20568 this.#revokeContentUrl(); 20569 } 20570} 20571class AnnotationLayer { 20572 #accessibilityManager = null; 20573 #annotationCanvasMap = null; 20574 #annotationStorage = null; 20575 #editableAnnotations = new Map(); 20576 #structTreeLayer = null; 20577 #linkService = null; 20578 #elements = []; 20579 #hasAriaAttributesFromStructTree = false; 20580 zIndex = 0; 20581 constructor({ 20582 div, 20583 accessibilityManager, 20584 annotationCanvasMap, 20585 annotationEditorUIManager, 20586 page, 20587 viewport, 20588 structTreeLayer, 20589 commentManager, 20590 linkService, 20591 annotationStorage 20592 }) { 20593 this.div = div; 20594 this.#accessibilityManager = accessibilityManager; 20595 this.#annotationCanvasMap = annotationCanvasMap; 20596 this.#structTreeLayer = structTreeLayer || null; 20597 this.#linkService = linkService || null; 20598 this.#annotationStorage = annotationStorage || new AnnotationStorage(); 20599 this.page = page; 20600 this.viewport = viewport; 20601 this._annotationEditorUIManager = annotationEditorUIManager; 20602 this._commentManager = commentManager || null; 20603 } 20604 hasEditableAnnotations() { 20605 return this.#editableAnnotations.size > 0; 20606 } 20607 async render(params) { 20608 const { 20609 annotations, 20610 optionalContentConfig 20611 } = params; 20612 const layer = this.div; 20613 setLayerDimensions(layer, this.viewport); 20614 const popupToElements = new Map(); 20615 const popupAnnotations = []; 20616 const elementParams = { 20617 data: null, 20618 layer, 20619 linkService: this.#linkService, 20620 downloadManager: params.downloadManager, 20621 imageResourcesPath: params.imageResourcesPath || "", 20622 renderForms: params.renderForms !== false, 20623 svgFactory: new DOMSVGFactory(), 20624 annotationStorage: this.#annotationStorage, 20625 enableComment: params.enableComment === true, 20626 enableScripting: params.enableScripting === true, 20627 hasJSActions: params.hasJSActions, 20628 fieldObjects: params.fieldObjects, 20629 parent: this, 20630 elements: null 20631 }; 20632 for (const data of annotations) { 20633 if (data.noHTML) { 20634 continue; 20635 } 20636 const isPopupAnnotation = data.annotationType === AnnotationType.POPUP; 20637 if (!isPopupAnnotation) { 20638 if (data.rect[2] === data.rect[0] || data.rect[3] === data.rect[1]) { 20639 continue; 20640 } 20641 } else { 20642 const elements = popupToElements.get(data.id); 20643 if (!elements) { 20644 continue; 20645 } 20646 if (!this._commentManager) { 20647 popupAnnotations.push(data); 20648 continue; 20649 } 20650 elementParams.elements = elements; 20651 } 20652 elementParams.data = data; 20653 const element = AnnotationElementFactory.create(elementParams); 20654 if (!element.isRenderable) { 20655 continue; 20656 } 20657 if (!isPopupAnnotation) { 20658 this.#elements.push(element); 20659 if (data.popupRef) { 20660 popupToElements.getOrInsertComputed(data.popupRef, makeArr).push(element); 20661 } 20662 } 20663 const rendered = element.render(); 20664 if (data.hidden) { 20665 rendered.style.visibility = "hidden"; 20666 } 20667 element.updateOC(optionalContentConfig); 20668 if (element._isEditable) { 20669 this.#editableAnnotations.set(element.data.id, element); 20670 this._annotationEditorUIManager?.renderAnnotationElement(element); 20671 } 20672 } 20673 await this.#addElementsToDOM(); 20674 for (const data of popupAnnotations) { 20675 const elements = elementParams.elements = popupToElements.get(data.id); 20676 elementParams.data = data; 20677 const element = AnnotationElementFactory.create(elementParams); 20678 if (!element.isRenderable) { 20679 continue; 20680 } 20681 const rendered = element.render(); 20682 element.contentElement.id = `${AnnotationPrefix}${data.id}`; 20683 if (data.hidden) { 20684 rendered.style.visibility = "hidden"; 20685 } 20686 elements.at(-1).container.after(rendered); 20687 } 20688 this.#setAnnotationCanvasMap(); 20689 } 20690 async #addElementsToDOM() { 20691 if (this.#elements.length === 0) { 20692 return; 20693 } 20694 this.div.replaceChildren(); 20695 const promises = []; 20696 if (!this.#hasAriaAttributesFromStructTree) { 20697 this.#hasAriaAttributesFromStructTree = true; 20698 for (const { 20699 contentElement, 20700 data: { 20701
vendor: 1,708 bytes, lines 20701-20758
20701id 20702 } 20703 } of this.#elements) { 20704 const annotationId = contentElement.id = `${AnnotationPrefix}${id}`; 20705 promises.push(this.#structTreeLayer?.getAriaAttributes(annotationId).then(ariaAttributes => { 20706 if (ariaAttributes) { 20707 for (const [key, value] of ariaAttributes) { 20708 contentElement.setAttribute(key, value); 20709 } 20710 } 20711 })); 20712 } 20713 } 20714 this.#elements.sort(({ 20715 data: { 20716 rect: [a0, a1, a2, a3] 20717 } 20718 }, { 20719 data: { 20720 rect: [b0, b1, b2, b3] 20721 } 20722 }) => { 20723 if (a0 === a2 && a1 === a3) { 20724 return +1; 20725 } 20726 if (b0 === b2 && b1 === b3) { 20727 return -1; 20728 } 20729 const top1 = a3; 20730 const bot1 = a1; 20731 const mid1 = (a1 + a3) / 2; 20732 const top2 = b3; 20733 const bot2 = b1; 20734 const mid2 = (b1 + b3) / 2; 20735 if (mid1 >= top2 && mid2 <= bot1) { 20736 return -1; 20737 } 20738 if (mid2 >= top1 && mid1 <= bot2) { 20739 return +1; 20740 } 20741 const centerX1 = (a0 + a2) / 2; 20742 const centerX2 = (b0 + b2) / 2; 20743 return centerX1 - centerX2; 20744 }); 20745 const fragment = document.createDocumentFragment(); 20746 for (const element of this.#elements) { 20747 fragment.append(element.container); 20748 if (this._commentManager) { 20749 (element.extraPopupElement?.popup || element.popup)?.renderCommentButton(); 20750 } else if (element.extraPopupElement) { 20751 fragment.append(element.extraPopupElement.render()); 20752 } 20753 } 20754 this.div.append(fragment); 20755 await Promise.all(promises); 20756 if (this.#accessibilityManager) { 20757 for (const element of this.#elements) { 20758 this.#accessibilityManager.addPointerInTextLayer(element.
vendor: 24,856 bytes, lines 20758-21576
20758contentElement, false); 20759 } 20760 } 20761 } 20762 async addLinkAnnotations(annotations) { 20763 const elementParams = { 20764 data: null, 20765 layer: this.div, 20766 linkService: this.#linkService, 20767 svgFactory: new DOMSVGFactory(), 20768 parent: this 20769 }; 20770 for (const data of annotations) { 20771 data.borderStyle ||= AnnotationLayer._defaultBorderStyle; 20772 elementParams.data = data; 20773 const element = AnnotationElementFactory.create(elementParams); 20774 if (!element.isRenderable) { 20775 continue; 20776 } 20777 element.render(); 20778 element.contentElement.id = `${AnnotationPrefix}${data.id}`; 20779 this.#elements.push(element); 20780 } 20781 await this.#addElementsToDOM(); 20782 } 20783 update({ 20784 viewport, 20785 optionalContentConfig 20786 }) { 20787 const layer = this.div; 20788 this.viewport = viewport; 20789 setLayerDimensions(layer, { 20790 rotation: viewport.rotation 20791 }); 20792 for (const element of this.#elements) { 20793 element.updateOC(optionalContentConfig); 20794 } 20795 this.#setAnnotationCanvasMap(); 20796 layer.hidden = false; 20797 } 20798 destroy() { 20799 for (const element of this.#elements) { 20800 element.destroy?.(); 20801 this.#accessibilityManager?.removePointerInTextLayer(element.contentElement); 20802 } 20803 this.#elements.length = 0; 20804 this.#editableAnnotations.clear(); 20805 this.div.replaceChildren(); 20806 } 20807 #setAnnotationCanvasMap() { 20808 if (!this.#annotationCanvasMap) { 20809 return; 20810 } 20811 const layer = this.div; 20812 for (const [id, canvas] of this.#annotationCanvasMap) { 20813 const element = layer.querySelector(`[data-annotation-id="${id}"]`); 20814 if (!element) { 20815 continue; 20816 } 20817 if (Array.isArray(canvas)) { 20818 for (const cvs of canvas) { 20819 cvs.className = "annotationContent"; 20820 cvs.ariaHidden = true; 20821 } 20822 } else { 20823 canvas.className = "annotationContent"; 20824 canvas.ariaHidden = true; 20825 } 20826 const toRemove = []; 20827 for (const child of element.children) { 20828 if (child.nodeName === "CANVAS") { 20829 toRemove.push(child); 20830 } 20831 } 20832 for (const child of toRemove) { 20833 child.remove(); 20834 } 20835 const firstCanvas = Array.isArray(canvas) ? canvas[0] : canvas; 20836 const { 20837 firstChild 20838 } = element; 20839 if (!firstChild) { 20840 element.append(firstCanvas); 20841 } else if (!firstChild.classList.contains("annotationContent")) { 20842 firstChild.before(firstCanvas); 20843 } else { 20844 firstChild.after(firstCanvas); 20845 } 20846 if (Array.isArray(canvas)) { 20847 let lastCanvas = firstCanvas; 20848 for (let i = 1, ii = canvas.length; i < ii; i++) { 20849 lastCanvas.after(canvas[i]); 20850 lastCanvas = canvas[i]; 20851 } 20852 } 20853 this.#annotationCanvasMap.delete(id); 20854 const editableAnnotation = this.#editableAnnotations.get(id); 20855 if (!editableAnnotation) { 20856 continue; 20857 } 20858 if (editableAnnotation._hasNoCanvas) { 20859 this._annotationEditorUIManager?.setMissingCanvas(id, element.id, canvas); 20860 editableAnnotation._hasNoCanvas = false; 20861 } else { 20862 editableAnnotation.canvas = canvas; 20863 } 20864 } 20865 } 20866 refreshCanvases() { 20867 this.#setAnnotationCanvasMap(); 20868 } 20869 getEditableAnnotations() { 20870 return this.#editableAnnotations.values(); 20871 } 20872 getEditableAnnotation(id) { 20873 return this.#editableAnnotations.get(id); 20874 } 20875 addFakeAnnotation(editor) { 20876 const { 20877 div 20878 } = this; 20879 const { 20880 id, 20881 rotation 20882 } = editor; 20883 const element = new EditorAnnotationElement({ 20884 data: { 20885 id, 20886 rect: editor.getPDFRect(), 20887 rotation 20888 }, 20889 editor, 20890 layer: div, 20891 parent: this, 20892 enableComment: !!this._commentManager, 20893 linkService: this.#linkService, 20894 annotationStorage: this.#annotationStorage 20895 }); 20896 element.render(); 20897 element.contentElement.id = `${AnnotationPrefix}${id}`; 20898 element.createOrUpdatePopup(); 20899 this.#elements.push(element); 20900 return element; 20901 } 20902 removeAnnotation(id) { 20903 const index = this.#elements.findIndex(el => el.data.id === id); 20904 if (index < 0) { 20905 return; 20906 } 20907 const [element] = this.#elements.splice(index, 1); 20908 this.#accessibilityManager?.removePointerInTextLayer(element.contentElement); 20909 } 20910 updateFakeAnnotations(editors) { 20911 if (editors.length === 0) { 20912 return; 20913 } 20914 for (const editor of editors) { 20915 editor.updateFakeAnnotationElement(this); 20916 } 20917 this.#addElementsToDOM(); 20918 } 20919 togglePointerEvents(enabled = false) { 20920 this.div.classList.toggle("disabled", !enabled); 20921 } 20922 static get _defaultBorderStyle() { 20923 return shadow(this, "_defaultBorderStyle", Object.freeze({ 20924 width: 1, 20925 rawWidth: 1, 20926 style: AnnotationBorderStyleType.SOLID, 20927 dashArray: [3], 20928 horizontalCornerRadius: 0, 20929 verticalCornerRadius: 0 20930 })); 20931 } 20932} 20933 20934;// ./src/display/editor/freetext.js 20935 20936 20937 20938 20939 20940const EOL_PATTERN = /\r\n?|\n/g; 20941class FreeTextEditor extends AnnotationEditor { 20942 #content = ""; 20943 #editorDivId = `${this.id}-editor`; 20944 #editModeAC = null; 20945 #fontSize; 20946 _colorPicker = null; 20947 static _freeTextDefaultContent = ""; 20948 static _internalPadding = 0; 20949 static _defaultColor = null; 20950 static _defaultFontSize = 10; 20951 static get _keyboardManager() { 20952 const proto = FreeTextEditor.prototype; 20953 const arrowChecker = self => self.isEmpty(); 20954 const small = AnnotationEditorUIManager.TRANSLATE_SMALL; 20955 const big = AnnotationEditorUIManager.TRANSLATE_BIG; 20956 return shadow(this, "_keyboardManager", new KeyboardManager([[["ctrl+s", "mac+meta+s", "ctrl+p", "mac+meta+p"], proto.commitOrRemove, { 20957 bubbles: true 20958 }], [["ctrl+Enter", "mac+meta+Enter"], proto.commitOrRemove], [["Escape"], proto.commitOrRemove], [["ArrowLeft"], proto._translateEmpty, { 20959 args: [-small, 0], 20960 checker: arrowChecker 20961 }], [["ctrl+ArrowLeft", "mac+shift+ArrowLeft"], proto._translateEmpty, { 20962 args: [-big, 0], 20963 checker: arrowChecker 20964 }], [["ArrowRight"], proto._translateEmpty, { 20965 args: [small, 0], 20966 checker: arrowChecker 20967 }], [["ctrl+ArrowRight", "mac+shift+ArrowRight"], proto._translateEmpty, { 20968 args: [big, 0], 20969 checker: arrowChecker 20970 }], [["ArrowUp"], proto._translateEmpty, { 20971 args: [0, -small], 20972 checker: arrowChecker 20973 }], [["ctrl+ArrowUp", "mac+shift+ArrowUp"], proto._translateEmpty, { 20974 args: [0, -big], 20975 checker: arrowChecker 20976 }], [["ArrowDown"], proto._translateEmpty, { 20977 args: [0, small], 20978 checker: arrowChecker 20979 }], [["ctrl+ArrowDown", "mac+shift+ArrowDown"], proto._translateEmpty, { 20980 args: [0, big], 20981 checker: arrowChecker 20982 }]])); 20983 } 20984 static _type = "freetext"; 20985 static _editorType = AnnotationEditorType.FREETEXT; 20986 constructor(params) { 20987 super({ 20988 ...params, 20989 name: "freeTextEditor" 20990 }); 20991 this.color = params.color || FreeTextEditor._defaultColor || AnnotationEditor._defaultLineColor; 20992 this.#fontSize = params.fontSize || FreeTextEditor._defaultFontSize; 20993 if (!this.annotationElementId) { 20994 this._uiManager.a11yAlert(AnnotationEditor._l10nAlert.freetext); 20995 } 20996 this.canAddComment = false; 20997 } 20998 static initialize(l10n, uiManager) { 20999 AnnotationEditor.initialize(l10n, uiManager); 21000 const style = getComputedStyle(document.documentElement); 21001 this._internalPadding = parseFloat(style.getPropertyValue("--freetext-padding")); 21002 } 21003 static updateDefaultParams(type, value) { 21004 switch (type) { 21005 case AnnotationEditorParamsType.FREETEXT_SIZE: 21006 FreeTextEditor._defaultFontSize = value; 21007 break; 21008 case AnnotationEditorParamsType.FREETEXT_COLOR: 21009 FreeTextEditor._defaultColor = value; 21010 break; 21011 } 21012 } 21013 updateParams(type, value) { 21014 switch (type) { 21015 case AnnotationEditorParamsType.FREETEXT_SIZE: 21016 this.#updateFontSize(value); 21017 break; 21018 case AnnotationEditorParamsType.FREETEXT_COLOR: 21019 this.#updateColor(value); 21020 break; 21021 } 21022 } 21023 static get defaultPropertiesToUpdate() { 21024 return [[AnnotationEditorParamsType.FREETEXT_SIZE, FreeTextEditor._defaultFontSize], [AnnotationEditorParamsType.FREETEXT_COLOR, FreeTextEditor._defaultColor || AnnotationEditor._defaultLineColor]]; 21025 } 21026 get propertiesToUpdate() { 21027 return [[AnnotationEditorParamsType.FREETEXT_SIZE, this.#fontSize], [AnnotationEditorParamsType.FREETEXT_COLOR, this.color]]; 21028 } 21029 get toolbarButtons() { 21030 this._colorPicker ||= new BasicColorPicker(this); 21031 return [["colorPicker", this._colorPicker]]; 21032 } 21033 get colorType() { 21034 return AnnotationEditorParamsType.FREETEXT_COLOR; 21035 } 21036 #updateFontSize(fontSize) { 21037 const setFontsize = size => { 21038 this.editorDiv.style.fontSize = `calc(${size}px * var(--total-scale-factor))`; 21039 this.translate(0, -(size - this.#fontSize) * this.parentScale); 21040 this.#fontSize = size; 21041 this.#setEditorDimensions(); 21042 }; 21043 const savedFontsize = this.#fontSize; 21044 this.addCommands({ 21045 cmd: setFontsize.bind(this, fontSize), 21046 undo: setFontsize.bind(this, savedFontsize), 21047 post: this._uiManager.updateUI.bind(this._uiManager, this), 21048 mustExec: true, 21049 type: AnnotationEditorParamsType.FREETEXT_SIZE, 21050 overwriteIfSameType: true, 21051 keepUndo: true 21052 }); 21053 } 21054 onUpdatedColor() { 21055 this.editorDiv.style.color = this.color; 21056 this._colorPicker?.update(this.color); 21057 super.onUpdatedColor(); 21058 } 21059 #updateColor(color) { 21060 const setColor = col => { 21061 this.color = col; 21062 this.onUpdatedColor(); 21063 }; 21064 const savedColor = this.color; 21065 this.addCommands({ 21066 cmd: setColor.bind(this, color), 21067 undo: setColor.bind(this, savedColor), 21068 post: this._uiManager.updateUI.bind(this._uiManager, this), 21069 mustExec: true, 21070 type: AnnotationEditorParamsType.FREETEXT_COLOR, 21071 overwriteIfSameType: true, 21072 keepUndo: true 21073 }); 21074 } 21075 _translateEmpty(x, y) { 21076 this._uiManager.translateSelectedEditors(x, y, true); 21077 } 21078 getInitialTranslation() { 21079 const scale = this.parentScale; 21080 return [-FreeTextEditor._internalPadding * scale, -(FreeTextEditor._internalPadding + this.#fontSize) * scale]; 21081 } 21082 rebuild() { 21083 if (!this.parent) { 21084 return; 21085 } 21086 super.rebuild(); 21087 if (this.div === null) { 21088 return; 21089 } 21090 if (!this.isAttachedToDOM) { 21091 this.parent.add(this); 21092 } 21093 } 21094 enableEditMode() { 21095 if (!super.enableEditMode()) { 21096 return false; 21097 } 21098 this.overlayDiv.classList.remove("enabled"); 21099 this.editorDiv.contentEditable = true; 21100 this._isDraggable = false; 21101 this.div.removeAttribute("aria-activedescendant"); 21102 this.#editModeAC = new AbortController(); 21103 const signal = this._uiManager.combinedSignal(this.#editModeAC); 21104 this.editorDiv.addEventListener("keydown", this.editorDivKeydown.bind(this), { 21105 signal 21106 }); 21107 this.editorDiv.addEventListener("focus", this.editorDivFocus.bind(this), { 21108 signal 21109 }); 21110 this.editorDiv.addEventListener("blur", this.editorDivBlur.bind(this), { 21111 signal 21112 }); 21113 this.editorDiv.addEventListener("input", this.editorDivInput.bind(this), { 21114 signal 21115 }); 21116 this.editorDiv.addEventListener("paste", this.editorDivPaste.bind(this), { 21117 signal 21118 }); 21119 return true; 21120 } 21121 disableEditMode() { 21122 if (!super.disableEditMode()) { 21123 return false; 21124 } 21125 this.overlayDiv.classList.add("enabled"); 21126 this.editorDiv.contentEditable = false; 21127 this.div.setAttribute("aria-activedescendant", this.#editorDivId); 21128 this._isDraggable = true; 21129 this.#editModeAC?.abort(); 21130 this.#editModeAC = null; 21131 this.div.focus({ 21132 preventScroll: true 21133 }); 21134 this.isEditing = false; 21135 this.parent.div.classList.add("freetextEditing"); 21136 return true; 21137 } 21138 focusin(event) { 21139 if (!this._focusEventsAllowed) { 21140 return; 21141 } 21142 super.focusin(event); 21143 if (event.target !== this.editorDiv) { 21144 this.editorDiv.focus(); 21145 } 21146 } 21147 onceAdded(focus) { 21148 if (this.width) { 21149 return; 21150 } 21151 this.enableEditMode(); 21152 if (focus) { 21153 this.editorDiv.focus(); 21154 } 21155 if (this._initialOptions?.isCentered) { 21156 this.center(); 21157 } 21158 this._initialOptions = null; 21159 } 21160 isEmpty() { 21161 return !this.editorDiv || this.editorDiv.innerText.trim() === ""; 21162 } 21163 remove() { 21164 this.isEditing = false; 21165 if (this.parent) { 21166 this.parent.setEditingState(true); 21167 this.parent.div.classList.add("freetextEditing"); 21168 } 21169 super.remove(); 21170 } 21171 #extractText() { 21172 const buffer = []; 21173 this.editorDiv.normalize(); 21174 let prevChild = null; 21175 for (const child of this.editorDiv.childNodes) { 21176 if (prevChild?.nodeType === Node.TEXT_NODE && child.nodeName === "BR") { 21177 continue; 21178 } 21179 buffer.push(FreeTextEditor.#getNodeContent(child)); 21180 prevChild = child; 21181 } 21182 return buffer.join("\n"); 21183 } 21184 #setEditorDimensions() { 21185 const [parentWidth, parentHeight] = this.parentDimensions; 21186 let rect; 21187 if (this.isAttachedToDOM) { 21188 rect = this.div.getBoundingClientRect(); 21189 } else { 21190 const { 21191 currentLayer, 21192 div 21193 } = this; 21194 const savedDisplay = div.style.display; 21195 const savedVisibility = div.classList.contains("hidden"); 21196 div.classList.remove("hidden"); 21197 div.style.display = "hidden"; 21198 currentLayer.div.append(this.div); 21199 rect = div.getBoundingClientRect(); 21200 div.remove(); 21201 div.style.display = savedDisplay; 21202 div.classList.toggle("hidden", savedVisibility); 21203 } 21204 if (this.rotation % 180 === this.parentRotation % 180) { 21205 this.width = rect.width / parentWidth; 21206 this.height = rect.height / parentHeight; 21207 } else { 21208 this.width = rect.height / parentWidth; 21209 this.height = rect.width / parentHeight; 21210 } 21211 this.fixAndSetPosition(); 21212 } 21213 commit() { 21214 if (!this.isInEditMode()) { 21215 return; 21216 } 21217 super.commit(); 21218 this.disableEditMode(); 21219 const savedText = this.#content; 21220 const newText = this.#content = this.#extractText().trimEnd(); 21221 if (savedText === newText) { 21222 return; 21223 } 21224 const setText = text => { 21225 this.#content = text; 21226 if (!text) { 21227 this.remove(); 21228 return; 21229 } 21230 this.#setContent(); 21231 this._uiManager.rebuild(this); 21232 this.#setEditorDimensions(); 21233 }; 21234 this.addCommands({ 21235 cmd: () => { 21236 setText(newText); 21237 }, 21238 undo: () => { 21239 setText(savedText); 21240 }, 21241 mustExec: false 21242 }); 21243 this.#setEditorDimensions(); 21244 } 21245 shouldGetKeyboardEvents() { 21246 return this.isInEditMode(); 21247 } 21248 enterInEditMode() { 21249 this.enableEditMode(); 21250 this.editorDiv.focus(); 21251 } 21252 keydown(event) { 21253 if (event.target === this.div && event.key === "Enter") { 21254 this.enterInEditMode(); 21255 event.preventDefault(); 21256 } 21257 } 21258 editorDivKeydown(event) { 21259 FreeTextEditor._keyboardManager.exec(this, event); 21260 } 21261 editorDivFocus(event) { 21262 this.isEditing = true; 21263 } 21264 editorDivBlur(event) { 21265 this.isEditing = false; 21266 } 21267 editorDivInput(event) { 21268 this.parent.div.classList.toggle("freetextEditing", this.isEmpty()); 21269 } 21270 disableEditing() { 21271 this.editorDiv.setAttribute("role", "comment"); 21272 this.editorDiv.removeAttribute("aria-multiline"); 21273 } 21274 enableEditing() { 21275 this.editorDiv.setAttribute("role", "textbox"); 21276 this.editorDiv.setAttribute("aria-multiline", true); 21277 } 21278 get canChangeContent() { 21279 return true; 21280 } 21281 render() { 21282 if (this.div) { 21283 return this.div; 21284 } 21285 let baseX, baseY; 21286 if (this._isCopy || this.annotationElementId) { 21287 baseX = this.x; 21288 baseY = this.y; 21289 } 21290 super.render(); 21291 this.editorDiv = document.createElement("div"); 21292 this.editorDiv.className = "internal"; 21293 this.editorDiv.setAttribute("id", this.#editorDivId); 21294 this.editorDiv.setAttribute("data-l10n-id", "pdfjs-free-text2"); 21295 this.editorDiv.setAttribute("data-l10n-attrs", "default-content"); 21296 this.enableEditing(); 21297 this.editorDiv.contentEditable = true; 21298 const { 21299 style 21300 } = this.editorDiv; 21301 style.fontSize = `calc(${this.#fontSize}px * var(--total-scale-factor))`; 21302 style.color = this.color; 21303 this.div.append(this.editorDiv); 21304 this.overlayDiv = document.createElement("div"); 21305 this.overlayDiv.classList.add("overlay", "enabled"); 21306 this.div.append(this.overlayDiv); 21307 if (this._isCopy || this.annotationElementId) { 21308 const [parentWidth, parentHeight] = this.parentDimensions; 21309 if (this.annotationElementId) { 21310 const { 21311 position 21312 } = this._initialData; 21313 let [tx, ty] = this.getInitialTranslation(); 21314 [tx, ty] = this.pageTranslationToScreen(tx, ty); 21315 const [pageWidth, pageHeight] = this.pageDimensions; 21316 const [pageX, pageY] = this.pageTranslation; 21317 let posX, posY; 21318 switch (this.rotation) { 21319 case 0: 21320 posX = baseX + (position[0] - pageX) / pageWidth; 21321 posY = baseY + this.height - (position[1] - pageY) / pageHeight; 21322 break; 21323 case 90: 21324 posX = baseX + (position[0] - pageX) / pageWidth; 21325 posY = baseY - (position[1] - pageY) / pageHeight; 21326 [tx, ty] = [ty, -tx]; 21327 break; 21328 case 180: 21329 posX = baseX - this.width + (position[0] - pageX) / pageWidth; 21330 posY = baseY - (position[1] - pageY) / pageHeight; 21331 [tx, ty] = [-tx, -ty]; 21332 break; 21333 case 270: 21334 posX = baseX + (position[0] - pageX - this.height * pageHeight) / pageWidth; 21335 posY = baseY + (position[1] - pageY - this.width * pageWidth) / pageHeight; 21336 [tx, ty] = [-ty, tx]; 21337 break; 21338 } 21339 this.setAt(posX * parentWidth, posY * parentHeight, tx, ty); 21340 } else { 21341 this._moveAfterPaste(baseX, baseY); 21342 } 21343 this.#setContent(); 21344 this._isDraggable = true; 21345 this.editorDiv.contentEditable = false; 21346 } else { 21347 this._isDraggable = false; 21348 this.editorDiv.contentEditable = true; 21349 } 21350 return this.div; 21351 } 21352 static #getNodeContent(node) { 21353 return (node.nodeType === Node.TEXT_NODE ? node.nodeValue : node.innerText).replaceAll(EOL_PATTERN, ""); 21354 } 21355 editorDivPaste(event) { 21356 const clipboardData = event.clipboardData || window.clipboardData; 21357 const { 21358 types 21359 } = clipboardData; 21360 if (types.length === 1 && types[0] === "text/plain") { 21361 return; 21362 } 21363 event.preventDefault(); 21364 const paste = FreeTextEditor.#deserializeContent(clipboardData.getData("text") || "").replaceAll(EOL_PATTERN, "\n"); 21365 if (!paste) { 21366 return; 21367 } 21368 const selection = window.getSelection(); 21369 if (!selection.rangeCount) { 21370 return; 21371 } 21372 this.editorDiv.normalize(); 21373 selection.deleteFromDocument(); 21374 const range = selection.getRangeAt(0); 21375 if (!paste.includes("\n")) { 21376 range.insertNode(document.createTextNode(paste)); 21377 this.editorDiv.normalize(); 21378 selection.collapseToStart(); 21379 return; 21380 } 21381 const { 21382 startContainer, 21383 startOffset 21384 } = range; 21385 const bufferBefore = []; 21386 const bufferAfter = []; 21387 if (startContainer.nodeType === Node.TEXT_NODE) { 21388 const parent = startContainer.parentElement; 21389 bufferAfter.push(startContainer.nodeValue.slice(startOffset).replaceAll(EOL_PATTERN, "")); 21390 if (parent !== this.editorDiv) { 21391 let buffer = bufferBefore; 21392 for (const child of this.editorDiv.childNodes) { 21393 if (child === parent) { 21394 buffer = bufferAfter; 21395 continue; 21396 } 21397 buffer.push(FreeTextEditor.#getNodeContent(child)); 21398 } 21399 } 21400 bufferBefore.push(startContainer.nodeValue.slice(0, startOffset).replaceAll(EOL_PATTERN, "")); 21401 } else if (startContainer === this.editorDiv) { 21402 let buffer = bufferBefore; 21403 let i = 0; 21404 for (const child of this.editorDiv.childNodes) { 21405 if (i++ === startOffset) { 21406 buffer = bufferAfter; 21407 } 21408 buffer.push(FreeTextEditor.#getNodeContent(child)); 21409 } 21410 } 21411 this.#content = `${bufferBefore.join("\n")}${paste}${bufferAfter.join("\n")}`; 21412 this.#setContent(); 21413 const newRange = new Range(); 21414 let beforeLength = Math.sumPrecise(bufferBefore.map(line => line.length)); 21415 for (const { 21416 firstChild 21417 } of this.editorDiv.childNodes) { 21418 if (firstChild.nodeType === Node.TEXT_NODE) { 21419 const length = firstChild.nodeValue.length; 21420 if (beforeLength <= length) { 21421 newRange.setStart(firstChild, beforeLength); 21422 newRange.setEnd(firstChild, beforeLength); 21423 break; 21424 } 21425 beforeLength -= length; 21426 } 21427 } 21428 selection.removeAllRanges(); 21429 selection.addRange(newRange); 21430 } 21431 #setContent() { 21432 this.editorDiv.replaceChildren(); 21433 if (!this.#content) { 21434 return; 21435 } 21436 for (const line of this.#content.split("\n")) { 21437 const div = document.createElement("div"); 21438 div.append(line ? document.createTextNode(line) : document.createElement("br")); 21439 this.editorDiv.append(div); 21440 } 21441 } 21442 #serializeContent() { 21443 return this.#content.replaceAll("\xa0", " "); 21444 } 21445 static #deserializeContent(content) { 21446 return content.replaceAll(" ", "\xa0"); 21447 } 21448 get contentDiv() { 21449 return this.editorDiv; 21450 } 21451 getPDFRect() { 21452 const padding = FreeTextEditor._internalPadding * this.parentScale; 21453 return this.getRect(padding, padding); 21454 } 21455 static async deserialize(data, parent, uiManager) { 21456 let initialData = null; 21457 if (data instanceof FreeTextAnnotationElement) { 21458 const { 21459 data: { 21460 defaultAppearanceData: { 21461 fontSize, 21462 fontColor 21463 }, 21464 rect, 21465 rotation, 21466 id, 21467 popupRef, 21468 richText, 21469 contentsObj, 21470 creationDate, 21471 modificationDate 21472 }, 21473 textContent, 21474 textPosition, 21475 parent: { 21476 page: { 21477 pageNumber 21478 } 21479 } 21480 } = data; 21481 if (!textContent?.length) { 21482 return null; 21483 } 21484 initialData = data = { 21485 annotationType: AnnotationEditorType.FREETEXT, 21486 color: Array.from(fontColor), 21487 fontSize, 21488 value: textContent.join("\n"), 21489 position: textPosition, 21490 pageIndex: pageNumber - 1, 21491 rect: rect.slice(0), 21492 rotation, 21493 annotationElementId: id, 21494 id, 21495 deleted: false, 21496 popupRef, 21497 comment: contentsObj?.str || null, 21498 richText, 21499 creationDate, 21500 modificationDate 21501 }; 21502 } 21503 const editor = await super.deserialize(data, parent, uiManager); 21504 editor.#fontSize = data.fontSize; 21505 editor.color = Util.makeHexColor(...data.color); 21506 editor.#content = FreeTextEditor.#deserializeContent(data.value); 21507 editor._initialData = initialData; 21508 if (data.comment) { 21509 editor.setCommentData(data); 21510 } 21511 return editor; 21512 } 21513 serialize(isForCopying = false) { 21514 if (this.isEmpty()) { 21515 return null; 21516 } 21517 if (this.deleted) { 21518 return this.serializeDeleted(); 21519 } 21520 const color = AnnotationEditor._colorManager.convert(this.isAttachedToDOM ? getComputedStyle(this.editorDiv).color : this.color); 21521 const serialized = Object.assign(super.serialize(isForCopying), { 21522 color, 21523 fontSize: this.#fontSize, 21524 value: this.#serializeContent() 21525 }); 21526 this.addComment(serialized); 21527 if (isForCopying) { 21528 serialized.isCopy = true; 21529 return serialized; 21530 } 21531 if (this.annotationElementId && !this.#hasElementChanged(serialized)) { 21532 return null; 21533 } 21534 serialized.id = this.annotationElementId; 21535 return serialized; 21536 } 21537 #hasElementChanged(serialized) { 21538 const { 21539 value, 21540 fontSize, 21541 color, 21542 pageIndex 21543 } = this._initialData; 21544 return this.hasEditedComment || this._hasBeenMoved || serialized.value !== value || serialized.fontSize !== fontSize || serialized.color.some((c, i) => c !== color[i]) || serialized.pageIndex !== pageIndex; 21545 } 21546 renderAnnotationElement(annotation) { 21547 const content = super.renderAnnotationElement(annotation); 21548 if (!content) { 21549 return null; 21550 } 21551 const { 21552 style 21553 } = content; 21554 style.fontSize = `calc(${this.#fontSize}px * var(--total-scale-factor))`; 21555 style.color = this.color; 21556 content.replaceChildren(); 21557 for (const line of this.#content.split("\n")) { 21558 const div = document.createElement("div"); 21559 div.append(line ? document.createTextNode(line) : document.createElement("br")); 21560 content.append(div); 21561 } 21562 annotation.updateEdited({ 21563 rect: this.getPDFRect(), 21564 popup: this._uiManager.hasCommentManager() || this.hasEditedComment ? this.comment : { 21565 text: this.#content 21566 } 21567 }); 21568 return content; 21569 } 21570 resetAnnotationElement(annotation) { 21571 super.resetAnnotationElement(annotation); 21572 annotation.resetEdited(); 21573 } 21574} 21575 21576;// ./src/display/editor/draw
vendor: 3,038 bytes, lines 21576-21679
21576ers/outline.js 21577 21578class Outline { 21579 static PRECISION = 1e-4; 21580 toSVGPath() { 21581 unreachable("Abstract method `toSVGPath` must be implemented."); 21582 } 21583 get box() { 21584 unreachable("Abstract getter `box` must be implemented."); 21585 } 21586 serialize(_bbox, _rotation) { 21587 unreachable("Abstract method `serialize` must be implemented."); 21588 } 21589 static _rescale(src, tx, ty, sx, sy, dest) { 21590 dest ||= new Float32Array(src.length); 21591 for (let i = 0, ii = src.length; i < ii; i += 2) { 21592 dest[i] = tx + src[i] * sx; 21593 dest[i + 1] = ty + src[i + 1] * sy; 21594 } 21595 return dest; 21596 } 21597 static _rescaleAndSwap(src, tx, ty, sx, sy, dest) { 21598 dest ||= new Float32Array(src.length); 21599 for (let i = 0, ii = src.length; i < ii; i += 2) { 21600 dest[i] = tx + src[i + 1] * sx; 21601 dest[i + 1] = ty + src[i] * sy; 21602 } 21603 return dest; 21604 } 21605 static _translate(src, tx, ty, dest) { 21606 dest ||= new Float32Array(src.length); 21607 for (let i = 0, ii = src.length; i < ii; i += 2) { 21608 dest[i] = tx + src[i]; 21609 dest[i + 1] = ty + src[i + 1]; 21610 } 21611 return dest; 21612 } 21613 static svgRound(x) { 21614 return Math.round(x * 10000); 21615 } 21616 static _normalizePoint(x, y, parentWidth, parentHeight, rotation) { 21617 switch (rotation) { 21618 case 90: 21619 return [1 - y / parentWidth, x / parentHeight]; 21620 case 180: 21621 return [1 - x / parentWidth, 1 - y / parentHeight]; 21622 case 270: 21623 return [y / parentWidth, 1 - x / parentHeight]; 21624 default: 21625 return [x / parentWidth, y / parentHeight]; 21626 } 21627 } 21628 static createBezierPoints(x1, y1, x2, y2, x3, y3) { 21629 return [(x1 + 5 * x2) / 6, (y1 + 5 * y2) / 6, (5 * x2 + x3) / 6, (5 * y2 + y3) / 6, (x2 + x3) / 2, (y2 + y3) / 2]; 21630 } 21631} 21632 21633;// ./src/display/editor/drawers/freedraw.js 21634 21635 21636class FreeDrawOutliner { 21637 #box; 21638 #bottom = []; 21639 #innerMargin; 21640 #isLTR; 21641 #top = []; 21642 #last = new Float32Array(18); 21643 #lastX; 21644 #lastY; 21645 #min; 21646 #min_dist; 21647 #scaleFactor; 21648 #thickness; 21649 #points = []; 21650 static #MIN_DIST = 8; 21651 static #MIN_DIFF = 2; 21652 static #MIN = FreeDrawOutliner.#MIN_DIST + FreeDrawOutliner.#MIN_DIFF; 21653 constructor({ 21654 x, 21655 y 21656 }, box, scaleFactor, thickness, isLTR, innerMargin = 0) { 21657 this.#box = box; 21658 this.#thickness = thickness * scaleFactor; 21659 this.#isLTR = isLTR; 21660 this.#last.set([NaN, NaN, NaN, NaN, x, y], 6); 21661 this.#innerMargin = innerMargin; 21662 this.#min_dist = FreeDrawOutliner.#MIN_DIST * scaleFactor; 21663 this.#min = FreeDrawOutliner.#MIN * scaleFactor; 21664 this.#scaleFactor = scaleFactor; 21665 this.#points.push(x, y); 21666 } 21667 isEmpty() { 21668 return isNaN(this.#last[8]); 21669 } 21670 #getLastCoords() { 21671 const lastTop = this.#last.subarray(4, 6); 21672 const lastBottom = this.#last.subarray(16, 18); 21673 const [x, y, width, height] = this.#box; 21674 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]; 21675 } 21676 add({ 21677 x, 21678 y 21679 }
vendor: 12,363 bytes, lines 21679-21973
21679) { 21680 this.#lastX = x; 21681 this.#lastY = y; 21682 const [layerX, layerY, layerWidth, layerHeight] = this.#box; 21683 let [x1, y1, x2, y2] = this.#last.subarray(8, 12); 21684 const diffX = x - x2; 21685 const diffY = y - y2; 21686 const d = Math.hypot(diffX, diffY); 21687 if (d < this.#min) { 21688 return false; 21689 } 21690 const diffD = d - this.#min_dist; 21691 const K = diffD / d; 21692 const shiftX = K * diffX; 21693 const shiftY = K * diffY; 21694 let x0 = x1; 21695 let y0 = y1; 21696 x1 = x2; 21697 y1 = y2; 21698 x2 += shiftX; 21699 y2 += shiftY; 21700 this.#points?.push(x, y); 21701 const nX = -shiftY / diffD; 21702 const nY = shiftX / diffD; 21703 const thX = nX * this.#thickness; 21704 const thY = nY * this.#thickness; 21705 this.#last.set(this.#last.subarray(2, 8), 0); 21706 this.#last.set([x2 + thX, y2 + thY], 4); 21707 this.#last.set(this.#last.subarray(14, 18), 12); 21708 this.#last.set([x2 - thX, y2 - thY], 16); 21709 if (isNaN(this.#last[6])) { 21710 if (this.#top.length === 0) { 21711 this.#last.set([x1 + thX, y1 + thY], 2); 21712 this.#top.push(NaN, NaN, NaN, NaN, (x1 + thX - layerX) / layerWidth, (y1 + thY - layerY) / layerHeight); 21713 this.#last.set([x1 - thX, y1 - thY], 14); 21714 this.#bottom.push(NaN, NaN, NaN, NaN, (x1 - thX - layerX) / layerWidth, (y1 - thY - layerY) / layerHeight); 21715 } 21716 this.#last.set([x0, y0, x1, y1, x2, y2], 6); 21717 return !this.isEmpty(); 21718 } 21719 this.#last.set([x0, y0, x1, y1, x2, y2], 6); 21720 const angle = Math.abs(Math.atan2(y0 - y1, x0 - x1) - Math.atan2(shiftY, shiftX)); 21721 if (angle < Math.PI / 2) { 21722 [x1, y1, x2, y2] = this.#last.subarray(2, 6); 21723 this.#top.push(NaN, NaN, NaN, NaN, ((x1 + x2) / 2 - layerX) / layerWidth, ((y1 + y2) / 2 - layerY) / layerHeight); 21724 [x1, y1, x0, y0] = this.#last.subarray(14, 18); 21725 this.#bottom.push(NaN, NaN, NaN, NaN, ((x0 + x1) / 2 - layerX) / layerWidth, ((y0 + y1) / 2 - layerY) / layerHeight); 21726 return true; 21727 } 21728 [x0, y0, x1, y1, x2, y2] = this.#last.subarray(0, 6); 21729 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); 21730 [x2, y2, x1, y1, x0, y0] = this.#last.subarray(12, 18); 21731 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); 21732 return true; 21733 } 21734 toSVGPath() { 21735 if (this.isEmpty()) { 21736 return ""; 21737 } 21738 const top = this.#top; 21739 const bottom = this.#bottom; 21740 if (isNaN(this.#last[6]) && !this.isEmpty()) { 21741 return this.#toSVGPathTwoPoints(); 21742 } 21743 const buffer = []; 21744 buffer.push(`M${top[4]} ${top[5]}`); 21745 for (let i = 6; i < top.length; i += 6) { 21746 if (isNaN(top[i])) { 21747 buffer.push(`L${top[i + 4]} ${top[i + 5]}`); 21748 } else { 21749 buffer.push(`C${top[i]} ${top[i + 1]} ${top[i + 2]} ${top[i + 3]} ${top[i + 4]} ${top[i + 5]}`); 21750 } 21751 } 21752 this.#toSVGPathEnd(buffer); 21753 for (let i = bottom.length - 6; i >= 6; i -= 6) { 21754 if (isNaN(bottom[i])) { 21755 buffer.push(`L${bottom[i + 4]} ${bottom[i + 5]}`); 21756 } else { 21757 buffer.push(`C${bottom[i]} ${bottom[i + 1]} ${bottom[i + 2]} ${bottom[i + 3]} ${bottom[i + 4]} ${bottom[i + 5]}`); 21758 } 21759 } 21760 this.#toSVGPathStart(buffer); 21761 return buffer.join(" "); 21762 } 21763 #toSVGPathTwoPoints() { 21764 const [x, y, width, height] = this.#box; 21765 const [lastTopX, lastTopY, lastBottomX, lastBottomY] = this.#getLastCoords(); 21766 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`; 21767 } 21768 #toSVGPathStart(buffer) { 21769 const bottom = this.#bottom; 21770 buffer.push(`L${bottom[4]} ${bottom[5]} Z`); 21771 } 21772 #toSVGPathEnd(buffer) { 21773 const [x, y, width, height] = this.#box; 21774 const lastTop = this.#last.subarray(4, 6); 21775 const lastBottom = this.#last.subarray(16, 18); 21776 const [lastTopX, lastTopY, lastBottomX, lastBottomY] = this.#getLastCoords(); 21777 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}`); 21778 } 21779 newFreeDrawOutline(outline, points, box, scaleFactor, innerMargin, isLTR) { 21780 return new FreeDrawOutline(outline, points, box, scaleFactor, innerMargin, isLTR); 21781 } 21782 getOutlines() { 21783 const top = this.#top; 21784 const bottom = this.#bottom; 21785 const last = this.#last; 21786 const [layerX, layerY, layerWidth, layerHeight] = this.#box; 21787 const points = new Float32Array((this.#points?.length ?? 0) + 2); 21788 for (let i = 0, ii = points.length - 2; i < ii; i += 2) { 21789 points[i] = (this.#points[i] - layerX) / layerWidth; 21790 points[i + 1] = (this.#points[i + 1] - layerY) / layerHeight; 21791 } 21792 points[points.length - 2] = (this.#lastX - layerX) / layerWidth; 21793 points[points.length - 1] = (this.#lastY - layerY) / layerHeight; 21794 if (isNaN(last[6]) && !this.isEmpty()) { 21795 return this.#getOutlineTwoPoints(points); 21796 } 21797 const outline = new Float32Array(this.#top.length + 24 + this.#bottom.length); 21798 let N = top.length; 21799 for (let i = 0; i < N; i += 2) { 21800 if (isNaN(top[i])) { 21801 outline[i] = outline[i + 1] = NaN; 21802 continue; 21803 } 21804 outline[i] = top[i]; 21805 outline[i + 1] = top[i + 1]; 21806 } 21807 N = this.#getOutlineEnd(outline, N); 21808 for (let i = bottom.length - 6; i >= 6; i -= 6) { 21809 for (let j = 0; j < 6; j += 2) { 21810 if (isNaN(bottom[i + j])) { 21811 outline[N] = outline[N + 1] = NaN; 21812 N += 2; 21813 continue; 21814 } 21815 outline[N] = bottom[i + j]; 21816 outline[N + 1] = bottom[i + j + 1]; 21817 N += 2; 21818 } 21819 } 21820 this.#getOutlineStart(outline, N); 21821 return this.newFreeDrawOutline(outline, points, this.#box, this.#scaleFactor, this.#innerMargin, this.#isLTR); 21822 } 21823 #getOutlineTwoPoints(points) { 21824 const last = this.#last; 21825 const [layerX, layerY, layerWidth, layerHeight] = this.#box; 21826 const [lastTopX, lastTopY, lastBottomX, lastBottomY] = this.#getLastCoords(); 21827 const outline = new Float32Array(36); 21828 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); 21829 return this.newFreeDrawOutline(outline, points, this.#box, this.#scaleFactor, this.#innerMargin, this.#isLTR); 21830 } 21831 #getOutlineStart(outline, pos) { 21832 const bottom = this.#bottom; 21833 outline.set([NaN, NaN, NaN, NaN, bottom[4], bottom[5]], pos); 21834 return pos += 6; 21835 } 21836 #getOutlineEnd(outline, pos) { 21837 const lastTop = this.#last.subarray(4, 6); 21838 const lastBottom = this.#last.subarray(16, 18); 21839 const [layerX, layerY, layerWidth, layerHeight] = this.#box; 21840 const [lastTopX, lastTopY, lastBottomX, lastBottomY] = this.#getLastCoords(); 21841 outline.set([NaN, NaN, NaN, NaN, (lastTop[0] - layerX) / layerWidth, (lastTop[1] - layerY) / layerHeight, NaN, NaN, NaN, NaN, lastTopX, lastTopY, NaN, NaN, NaN, NaN, lastBottomX, lastBottomY, NaN, NaN, NaN, NaN, (lastBottom[0] - layerX) / layerWidth, (lastBottom[1] - layerY) / layerHeight], pos); 21842 return pos += 24; 21843 } 21844} 21845class FreeDrawOutline extends Outline { 21846 #box; 21847 #bbox = new Float32Array(4); 21848 #innerMargin; 21849 #isLTR; 21850 #points; 21851 #scaleFactor; 21852 #outline; 21853 constructor(outline, points, box, scaleFactor, innerMargin, isLTR) { 21854 super(); 21855 this.#outline = outline; 21856 this.#points = points; 21857 this.#box = box; 21858 this.#scaleFactor = scaleFactor; 21859 this.#innerMargin = innerMargin; 21860 this.#isLTR = isLTR; 21861 this.firstPoint = [NaN, NaN]; 21862 this.lastPoint = [NaN, NaN]; 21863 this.#computeMinMax(isLTR); 21864 const [x, y, width, height] = this.#bbox; 21865 for (let i = 0, ii = outline.length; i < ii; i += 2) { 21866 outline[i] = (outline[i] - x) / width; 21867 outline[i + 1] = (outline[i + 1] - y) / height; 21868 } 21869 for (let i = 0, ii = points.length; i < ii; i += 2) { 21870 points[i] = (points[i] - x) / width; 21871 points[i + 1] = (points[i + 1] - y) / height; 21872 } 21873 } 21874 toSVGPath() { 21875 const buffer = [`M${this.#outline[4]} ${this.#outline[5]}`]; 21876 for (let i = 6, ii = this.#outline.length; i < ii; i += 6) { 21877 if (isNaN(this.#outline[i])) { 21878 buffer.push(`L${this.#outline[i + 4]} ${this.#outline[i + 5]}`); 21879 continue; 21880 } 21881 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]}`); 21882 } 21883 buffer.push("Z"); 21884 return buffer.join(" "); 21885 } 21886 serialize([blX, blY, trX, trY], rotation) { 21887 const width = trX - blX; 21888 const height = trY - blY; 21889 let outline; 21890 let points; 21891 switch (rotation) { 21892 case 0: 21893 outline = Outline._rescale(this.#outline, blX, trY, width, -height); 21894 points = Outline._rescale(this.#points, blX, trY, width, -height); 21895 break; 21896 case 90: 21897 outline = Outline._rescaleAndSwap(this.#outline, blX, blY, width, height); 21898 points = Outline._rescaleAndSwap(this.#points, blX, blY, width, height); 21899 break; 21900 case 180: 21901 outline = Outline._rescale(this.#outline, trX, blY, -width, height); 21902 points = Outline._rescale(this.#points, trX, blY, -width, height); 21903 break; 21904 case 270: 21905 outline = Outline._rescaleAndSwap(this.#outline, trX, trY, -width, -height); 21906 points = Outline._rescaleAndSwap(this.#points, trX, trY, -width, -height); 21907 break; 21908 } 21909 return { 21910 outline: Array.from(outline), 21911 points: [Array.from(points)] 21912 }; 21913 } 21914 #computeMinMax(isLTR) { 21915 const outline = this.#outline; 21916 let lastX = outline[4]; 21917 let lastY = outline[5]; 21918 const minMax = [lastX, lastY, lastX, lastY]; 21919 let firstPointX = lastX; 21920 let firstPointY = lastY; 21921 let lastPointX = lastX; 21922 let lastPointY = lastY; 21923 const ltrCallback = isLTR ? Math.max : Math.min; 21924 const bezierBbox = new Float32Array(4); 21925 for (let i = 6, ii = outline.length; i < ii; i += 6) { 21926 const x = outline[i + 4], 21927 y = outline[i + 5]; 21928 if (isNaN(outline[i])) { 21929 Util.pointBoundingBox(x, y, minMax); 21930 if (firstPointY > y) { 21931 firstPointX = x; 21932 firstPointY = y; 21933 } else if (firstPointY === y) { 21934 firstPointX = ltrCallback(firstPointX, x); 21935 } 21936 if (lastPointY < y) { 21937 lastPointX = x; 21938 lastPointY = y; 21939 } else if (lastPointY === y) { 21940 lastPointX = ltrCallback(lastPointX, x); 21941 } 21942 } else { 21943 bezierBbox.set(BBOX_INIT, 0); 21944 Util.bezierBoundingBox(lastX, lastY, ...outline.slice(i, i + 6), bezierBbox); 21945 Util.rectBoundingBox(...bezierBbox, minMax); 21946 if (firstPointY > bezierBbox[1]) { 21947 firstPointX = bezierBbox[0]; 21948 firstPointY = bezierBbox[1]; 21949 } else if (firstPointY === bezierBbox[1]) { 21950 firstPointX = ltrCallback(firstPointX, bezierBbox[0]); 21951 } 21952 if (lastPointY < bezierBbox[3]) { 21953 lastPointX = bezierBbox[2]; 21954 lastPointY = bezierBbox[3]; 21955 } else if (lastPointY === bezierBbox[3]) { 21956 lastPointX = ltrCallback(lastPointX, bezierBbox[2]); 21957 } 21958 } 21959 lastX = x; 21960 lastY = y; 21961 } 21962 const bbox = this.#bbox; 21963 bbox[0] = minMax[0] - this.#innerMargin; 21964 bbox[1] = minMax[1] - this.#innerMargin; 21965 bbox[2] = minMax[2] - minMax[0] + 2 * this.#innerMargin; 21966 bbox[3] = minMax[3] - minMax[1] + 2 * this.#innerMargin; 21967 this.firstPoint = [firstPointX, firstPointY]; 21968 this.lastPoint = [lastPointX, lastPointY]; 21969 } 21970 get box() { 21971 return this.#bbox; 21972 } 21973 newOutliner(
vendor: 35,838 bytes, lines 21973-23243
21973point, box, scaleFactor, thickness, isLTR, innerMargin = 0) { 21974 return new FreeDrawOutliner(point, box, scaleFactor, thickness, isLTR, innerMargin); 21975 } 21976 getNewOutline(thickness, innerMargin) { 21977 const [x, y, width, height] = this.#bbox; 21978 const [layerX, layerY, layerWidth, layerHeight] = this.#box; 21979 const sx = width * layerWidth; 21980 const sy = height * layerHeight; 21981 const tx = x * layerWidth + layerX; 21982 const ty = y * layerHeight + layerY; 21983 const outliner = this.newOutliner({ 21984 x: this.#points[0] * sx + tx, 21985 y: this.#points[1] * sy + ty 21986 }, this.#box, this.#scaleFactor, thickness, this.#isLTR, innerMargin ?? this.#innerMargin); 21987 for (let i = 2; i < this.#points.length; i += 2) { 21988 outliner.add({ 21989 x: this.#points[i] * sx + tx, 21990 y: this.#points[i + 1] * sy + ty 21991 }); 21992 } 21993 return outliner.getOutlines(); 21994 } 21995} 21996 21997;// ./src/display/editor/drawers/highlight.js 21998 21999 22000 22001class HighlightOutliner { 22002 #box; 22003 #firstPoint; 22004 #lastPoint; 22005 #verticalEdges = []; 22006 #intervals = []; 22007 constructor(boxes, borderWidth = 0, innerMargin = 0, isLTR = true) { 22008 const minMax = BBOX_INIT.slice(); 22009 const NUMBER_OF_DIGITS = 4; 22010 const EPSILON = 10 ** -NUMBER_OF_DIGITS; 22011 for (const { 22012 x, 22013 y, 22014 width, 22015 height 22016 } of boxes) { 22017 const x1 = Math.floor((x - borderWidth) / EPSILON) * EPSILON; 22018 const x2 = Math.ceil((x + width + borderWidth) / EPSILON) * EPSILON; 22019 const y1 = Math.floor((y - borderWidth) / EPSILON) * EPSILON; 22020 const y2 = Math.ceil((y + height + borderWidth) / EPSILON) * EPSILON; 22021 const left = [x1, y1, y2, true]; 22022 const right = [x2, y1, y2, false]; 22023 this.#verticalEdges.push(left, right); 22024 Util.rectBoundingBox(x1, y1, x2, y2, minMax); 22025 } 22026 const bboxWidth = minMax[2] - minMax[0] + 2 * innerMargin; 22027 const bboxHeight = minMax[3] - minMax[1] + 2 * innerMargin; 22028 const shiftedMinX = minMax[0] - innerMargin; 22029 const shiftedMinY = minMax[1] - innerMargin; 22030 let firstPointX = isLTR ? -Infinity : Infinity; 22031 let firstPointY = Infinity; 22032 const lastEdge = this.#verticalEdges.at(isLTR ? -1 : -2); 22033 const lastPoint = [lastEdge[0], lastEdge[2]]; 22034 for (const edge of this.#verticalEdges) { 22035 const [x, y1, y2, left] = edge; 22036 if (!left && isLTR) { 22037 if (y1 < firstPointY) { 22038 firstPointY = y1; 22039 firstPointX = x; 22040 } else if (y1 === firstPointY) { 22041 firstPointX = Math.max(firstPointX, x); 22042 } 22043 } else if (left && !isLTR) { 22044 if (y1 < firstPointY) { 22045 firstPointY = y1; 22046 firstPointX = x; 22047 } else if (y1 === firstPointY) { 22048 firstPointX = Math.min(firstPointX, x); 22049 } 22050 } 22051 edge[0] = (x - shiftedMinX) / bboxWidth; 22052 edge[1] = (y1 - shiftedMinY) / bboxHeight; 22053 edge[2] = (y2 - shiftedMinY) / bboxHeight; 22054 } 22055 this.#box = new Float32Array([shiftedMinX, shiftedMinY, bboxWidth, bboxHeight]); 22056 this.#firstPoint = [firstPointX, firstPointY]; 22057 this.#lastPoint = lastPoint; 22058 } 22059 getOutlines() { 22060 this.#verticalEdges.sort((a, b) => a[0] - b[0] || a[1] - b[1] || a[2] - b[2]); 22061 const outlineVerticalEdges = []; 22062 for (const edge of this.#verticalEdges) { 22063 if (edge[3]) { 22064 outlineVerticalEdges.push(...this.#breakEdge(edge)); 22065 this.#insert(edge); 22066 } else { 22067 this.#remove(edge); 22068 outlineVerticalEdges.push(...this.#breakEdge(edge)); 22069 } 22070 } 22071 return this.#getOutlines(outlineVerticalEdges); 22072 } 22073 #getOutlines(outlineVerticalEdges) { 22074 const edges = []; 22075 const allEdges = new Set(); 22076 for (const edge of outlineVerticalEdges) { 22077 const [x, y1, y2] = edge; 22078 edges.push([x, y1, edge], [x, y2, edge]); 22079 } 22080 edges.sort((a, b) => a[1] - b[1] || a[0] - b[0]); 22081 for (let i = 0, ii = edges.length; i < ii; i += 2) { 22082 const edge1 = edges[i][2]; 22083 const edge2 = edges[i + 1][2]; 22084 edge1.push(edge2); 22085 edge2.push(edge1); 22086 allEdges.add(edge1); 22087 allEdges.add(edge2); 22088 } 22089 const outlines = []; 22090 let outline; 22091 while (allEdges.size > 0) { 22092 const edge = allEdges.values().next().value; 22093 let [x, y1, y2, edge1, edge2] = edge; 22094 allEdges.delete(edge); 22095 let lastPointX = x; 22096 let lastPointY = y1; 22097 outline = [x, y2]; 22098 outlines.push(outline); 22099 while (true) { 22100 let e; 22101 if (allEdges.has(edge1)) { 22102 e = edge1; 22103 } else if (allEdges.has(edge2)) { 22104 e = edge2; 22105 } else { 22106 break; 22107 } 22108 allEdges.delete(e); 22109 [x, y1, y2, edge1, edge2] = e; 22110 if (lastPointX !== x) { 22111 outline.push(lastPointX, lastPointY, x, lastPointY === y1 ? y1 : y2); 22112 lastPointX = x; 22113 } 22114 lastPointY = lastPointY === y1 ? y2 : y1; 22115 } 22116 outline.push(lastPointX, lastPointY); 22117 } 22118 return new HighlightOutline(outlines, this.#box, this.#firstPoint, this.#lastPoint); 22119 } 22120 #binarySearch(y) { 22121 const array = this.#intervals; 22122 let start = 0; 22123 let end = array.length - 1; 22124 while (start <= end) { 22125 const middle = start + end >> 1; 22126 const y1 = array[middle][0]; 22127 if (y1 === y) { 22128 return middle; 22129 } 22130 if (y1 < y) { 22131 start = middle + 1; 22132 } else { 22133 end = middle - 1; 22134 } 22135 } 22136 return end + 1; 22137 } 22138 #insert([, y1, y2]) { 22139 const index = this.#binarySearch(y1); 22140 this.#intervals.splice(index, 0, [y1, y2]); 22141 } 22142 #remove([, y1, y2]) { 22143 const index = this.#binarySearch(y1); 22144 for (let i = index; i < this.#intervals.length; i++) { 22145 const [start, end] = this.#intervals[i]; 22146 if (start !== y1) { 22147 break; 22148 } 22149 if (start === y1 && end === y2) { 22150 this.#intervals.splice(i, 1); 22151 return; 22152 } 22153 } 22154 for (let i = index - 1; i >= 0; i--) { 22155 const [start, end] = this.#intervals[i]; 22156 if (start !== y1) { 22157 break; 22158 } 22159 if (start === y1 && end === y2) { 22160 this.#intervals.splice(i, 1); 22161 return; 22162 } 22163 } 22164 } 22165 #breakEdge(edge) { 22166 const [x, y1, y2] = edge; 22167 const results = [[x, y1, y2]]; 22168 const index = this.#binarySearch(y2); 22169 for (let i = 0; i < index; i++) { 22170 const [start, end] = this.#intervals[i]; 22171 for (let j = 0, jj = results.length; j < jj; j++) { 22172 const [, y3, y4] = results[j]; 22173 if (end <= y3 || y4 <= start) { 22174 continue; 22175 } 22176 if (y3 >= start) { 22177 if (y4 > end) { 22178 results[j][1] = end; 22179 } else { 22180 if (jj === 1) { 22181 return []; 22182 } 22183 results.splice(j, 1); 22184 j--; 22185 jj--; 22186 } 22187 continue; 22188 } 22189 results[j][2] = start; 22190 if (y4 > end) { 22191 results.push([x, end, y4]); 22192 } 22193 } 22194 } 22195 return results; 22196 } 22197} 22198class HighlightOutline extends Outline { 22199 #box; 22200 #outlines; 22201 constructor(outlines, box, firstPoint, lastPoint) { 22202 super(); 22203 this.#outlines = outlines; 22204 this.#box = box; 22205 this.firstPoint = firstPoint; 22206 this.lastPoint = lastPoint; 22207 } 22208 toSVGPath() { 22209 const buffer = []; 22210 for (const polygon of this.#outlines) { 22211 let [prevX, prevY] = polygon; 22212 buffer.push(`M${prevX} ${prevY}`); 22213 for (let i = 2; i < polygon.length; i += 2) { 22214 const x = polygon[i]; 22215 const y = polygon[i + 1]; 22216 if (x === prevX) { 22217 buffer.push(`V${y}`); 22218 prevY = y; 22219 } else if (y === prevY) { 22220 buffer.push(`H${x}`); 22221 prevX = x; 22222 } 22223 } 22224 buffer.push("Z"); 22225 } 22226 return buffer.join(" "); 22227 } 22228 serialize([blX, blY, trX, trY], _rotation) { 22229 const outlines = []; 22230 const width = trX - blX; 22231 const height = trY - blY; 22232 for (const outline of this.#outlines) { 22233 const points = new Array(outline.length); 22234 for (let i = 0; i < outline.length; i += 2) { 22235 points[i] = blX + outline[i] * width; 22236 points[i + 1] = trY - outline[i + 1] * height; 22237 } 22238 outlines.push(points); 22239 } 22240 return outlines; 22241 } 22242 get box() { 22243 return this.#box; 22244 } 22245} 22246class FreeHighlightOutliner extends FreeDrawOutliner { 22247 newFreeDrawOutline(outline, points, box, scaleFactor, innerMargin, isLTR) { 22248 return new FreeHighlightOutline(outline, points, box, scaleFactor, innerMargin, isLTR); 22249 } 22250} 22251class FreeHighlightOutline extends FreeDrawOutline { 22252 newOutliner(point, box, scaleFactor, thickness, isLTR, innerMargin = 0) { 22253 return new FreeHighlightOutliner(point, box, scaleFactor, thickness, isLTR, innerMargin); 22254 } 22255} 22256 22257;// ./src/display/editor/highlight.js 22258 22259 22260 22261 22262 22263 22264 22265class HighlightEditor extends AnnotationEditor { 22266 #anchorNode = null; 22267 #anchorOffset = 0; 22268 #boxes; 22269 #clipPathId = null; 22270 #colorPicker = null; 22271 #focusOutlines = null; 22272 #focusNode = null; 22273 #focusOffset = 0; 22274 #highlightDiv = null; 22275 #highlightOutlines = null; 22276 #id = null; 22277 #isFreeHighlight = false; 22278 #firstPoint = null; 22279 #lastPoint = null; 22280 #outlineId = null; 22281 #text = ""; 22282 #thickness; 22283 #methodOfCreation = ""; 22284 static _defaultColor = null; 22285 static _defaultOpacity = 1; 22286 static _defaultThickness = 12; 22287 static _type = "highlight"; 22288 static _editorType = AnnotationEditorType.HIGHLIGHT; 22289 static _freeHighlightId = -1; 22290 static _freeHighlight = null; 22291 static _freeHighlightClipId = ""; 22292 static get _keyboardManager() { 22293 const proto = HighlightEditor.prototype; 22294 return shadow(this, "_keyboardManager", new KeyboardManager([[["ArrowLeft"], proto._moveCaret, { 22295 args: [0] 22296 }], [["ArrowRight"], proto._moveCaret, { 22297 args: [1] 22298 }], [["ArrowUp"], proto._moveCaret, { 22299 args: [2] 22300 }], [["ArrowDown"], proto._moveCaret, { 22301 args: [3] 22302 }]])); 22303 } 22304 constructor(params) { 22305 super({ 22306 ...params, 22307 name: "highlightEditor" 22308 }); 22309 this.color = params.color || HighlightEditor._defaultColor; 22310 this.#thickness = params.thickness || HighlightEditor._defaultThickness; 22311 this.opacity = params.opacity || HighlightEditor._defaultOpacity; 22312 this.#boxes = params.boxes || null; 22313 this.#methodOfCreation = params.methodOfCreation || ""; 22314 this.#text = params.text || ""; 22315 this._isDraggable = false; 22316 this.defaultL10nId = "pdfjs-editor-highlight-editor"; 22317 if (params.highlightId > -1) { 22318 this.#isFreeHighlight = true; 22319 this.#createFreeOutlines(params); 22320 this.#addToDrawLayer(); 22321 } else if (this.#boxes) { 22322 this.#anchorNode = params.anchorNode; 22323 this.#anchorOffset = params.anchorOffset; 22324 this.#focusNode = params.focusNode; 22325 this.#focusOffset = params.focusOffset; 22326 this.#createOutlines(); 22327 this.#addToDrawLayer(); 22328 this.rotate(this.rotation); 22329 } 22330 if (!this.annotationElementId) { 22331 this._uiManager.a11yAlert(AnnotationEditor._l10nAlert.highlight); 22332 } 22333 } 22334 get telemetryInitialData() { 22335 return { 22336 action: "added", 22337 type: this.#isFreeHighlight ? "free_highlight" : "highlight", 22338 color: this._uiManager.getNonHCMColorName(this.color), 22339 thickness: this.#thickness, 22340 methodOfCreation: this.#methodOfCreation 22341 }; 22342 } 22343 get telemetryFinalData() { 22344 return { 22345 type: "highlight", 22346 color: this._uiManager.getNonHCMColorName(this.color) 22347 }; 22348 } 22349 static computeTelemetryFinalData(data) { 22350 return { 22351 numberOfColors: data.get("color").size 22352 }; 22353 } 22354 #createOutlines() { 22355 const outliner = new HighlightOutliner(this.#boxes, 0.001); 22356 this.#highlightOutlines = outliner.getOutlines(); 22357 [this.x, this.y, this.width, this.height] = this.#highlightOutlines.box; 22358 const outlinerForOutline = new HighlightOutliner(this.#boxes, 0.0025, 0.001, this._uiManager.direction === "ltr"); 22359 this.#focusOutlines = outlinerForOutline.getOutlines(); 22360 const { 22361 firstPoint 22362 } = this.#highlightOutlines; 22363 this.#firstPoint = [(firstPoint[0] - this.x) / this.width, (firstPoint[1] - this.y) / this.height]; 22364 const { 22365 lastPoint 22366 } = this.#focusOutlines; 22367 this.#lastPoint = [(lastPoint[0] - this.x) / this.width, (lastPoint[1] - this.y) / this.height]; 22368 } 22369 #createFreeOutlines({ 22370 highlightOutlines, 22371 highlightId, 22372 clipPathId 22373 }) { 22374 this.#highlightOutlines = highlightOutlines; 22375 const extraThickness = 1.5; 22376 this.#focusOutlines = highlightOutlines.getNewOutline(this.#thickness / 2 + extraThickness, 0.0025); 22377 if (highlightId >= 0) { 22378 this.#id = highlightId; 22379 this.#clipPathId = clipPathId; 22380 this.parent.drawLayer.finalizeDraw(highlightId, { 22381 bbox: highlightOutlines.box, 22382 path: { 22383 d: highlightOutlines.toSVGPath() 22384 } 22385 }); 22386 this.#outlineId = this.parent.drawLayer.drawOutline({ 22387 rootClass: { 22388 highlightOutline: true, 22389 free: true 22390 }, 22391 bbox: this.#focusOutlines.box, 22392 path: { 22393 d: this.#focusOutlines.toSVGPath() 22394 } 22395 }, true); 22396 } else if (this.parent) { 22397 const angle = this.parent.viewport.rotation; 22398 this.parent.drawLayer.updateProperties(this.#id, { 22399 bbox: HighlightEditor.#rotateBbox(this.#highlightOutlines.box, (angle - this.rotation + 360) % 360), 22400 path: { 22401 d: highlightOutlines.toSVGPath() 22402 } 22403 }); 22404 this.parent.drawLayer.updateProperties(this.#outlineId, { 22405 bbox: HighlightEditor.#rotateBbox(this.#focusOutlines.box, angle), 22406 path: { 22407 d: this.#focusOutlines.toSVGPath() 22408 } 22409 }); 22410 } 22411 const [x, y, width, height] = highlightOutlines.box; 22412 switch (this.rotation) { 22413 case 0: 22414 this.x = x; 22415 this.y = y; 22416 this.width = width; 22417 this.height = height; 22418 break; 22419 case 90: 22420 { 22421 const [pageWidth, pageHeight] = this.parentDimensions; 22422 this.x = y; 22423 this.y = 1 - x; 22424 this.width = width * pageHeight / pageWidth; 22425 this.height = height * pageWidth / pageHeight; 22426 break; 22427 } 22428 case 180: 22429 this.x = 1 - x; 22430 this.y = 1 - y; 22431 this.width = width; 22432 this.height = height; 22433 break; 22434 case 270: 22435 { 22436 const [pageWidth, pageHeight] = this.parentDimensions; 22437 this.x = 1 - y; 22438 this.y = x; 22439 this.width = width * pageHeight / pageWidth; 22440 this.height = height * pageWidth / pageHeight; 22441 break; 22442 } 22443 } 22444 const { 22445 firstPoint 22446 } = highlightOutlines; 22447 this.#firstPoint = [(firstPoint[0] - x) / width, (firstPoint[1] - y) / height]; 22448 const { 22449 lastPoint 22450 } = this.#focusOutlines; 22451 this.#lastPoint = [(lastPoint[0] - x) / width, (lastPoint[1] - y) / height]; 22452 } 22453 static initialize(l10n, uiManager) { 22454 AnnotationEditor.initialize(l10n, uiManager); 22455 HighlightEditor._defaultColor ||= uiManager.highlightColors?.values().next().value || "#fff066"; 22456 } 22457 static updateDefaultParams(type, value) { 22458 switch (type) { 22459 case AnnotationEditorParamsType.HIGHLIGHT_COLOR: 22460 HighlightEditor._defaultColor = value; 22461 break; 22462 case AnnotationEditorParamsType.HIGHLIGHT_THICKNESS: 22463 HighlightEditor._defaultThickness = value; 22464 break; 22465 } 22466 } 22467 translateInPage(x, y) {} 22468 get toolbarPosition() { 22469 return this.#lastPoint; 22470 } 22471 get commentButtonPosition() { 22472 return this.#firstPoint; 22473 } 22474 updateParams(type, value) { 22475 switch (type) { 22476 case AnnotationEditorParamsType.HIGHLIGHT_COLOR: 22477 this.#updateColor(value); 22478 break; 22479 case AnnotationEditorParamsType.HIGHLIGHT_THICKNESS: 22480 this.#updateThickness(value); 22481 break; 22482 } 22483 } 22484 static get defaultPropertiesToUpdate() { 22485 return [[AnnotationEditorParamsType.HIGHLIGHT_COLOR, HighlightEditor._defaultColor], [AnnotationEditorParamsType.HIGHLIGHT_THICKNESS, HighlightEditor._defaultThickness]]; 22486 } 22487 get propertiesToUpdate() { 22488 return [[AnnotationEditorParamsType.HIGHLIGHT_COLOR, this.color || HighlightEditor._defaultColor], [AnnotationEditorParamsType.HIGHLIGHT_THICKNESS, this.#thickness || HighlightEditor._defaultThickness], [AnnotationEditorParamsType.HIGHLIGHT_FREE, this.#isFreeHighlight]]; 22489 } 22490 onUpdatedColor() { 22491 this.parent?.drawLayer.updateProperties(this.#id, { 22492 root: { 22493 fill: this.color, 22494 "fill-opacity": this.opacity 22495 } 22496 }); 22497 this.#colorPicker?.updateColor(this.color); 22498 super.onUpdatedColor(); 22499 } 22500 #updateColor(color) { 22501 const setColorAndOpacity = (col, opa) => { 22502 this.color = col; 22503 this.opacity = opa; 22504 this.onUpdatedColor(); 22505 }; 22506 const savedColor = this.color; 22507 const savedOpacity = this.opacity; 22508 this.addCommands({ 22509 cmd: setColorAndOpacity.bind(this, color, HighlightEditor._defaultOpacity), 22510 undo: setColorAndOpacity.bind(this, savedColor, savedOpacity), 22511 post: this._uiManager.updateUI.bind(this._uiManager, this), 22512 mustExec: true, 22513 type: AnnotationEditorParamsType.HIGHLIGHT_COLOR, 22514 overwriteIfSameType: true, 22515 keepUndo: true 22516 }); 22517 this._reportTelemetry({ 22518 action: "color_changed", 22519 color: this._uiManager.getNonHCMColorName(color) 22520 }, true); 22521 } 22522 #updateThickness(thickness) { 22523 const savedThickness = this.#thickness; 22524 const setThickness = th => { 22525 this.#thickness = th; 22526 this.#changeThickness(th); 22527 }; 22528 this.addCommands({ 22529 cmd: setThickness.bind(this, thickness), 22530 undo: setThickness.bind(this, savedThickness), 22531 post: this._uiManager.updateUI.bind(this._uiManager, this), 22532 mustExec: true, 22533 type: AnnotationEditorParamsType.INK_THICKNESS, 22534 overwriteIfSameType: true, 22535 keepUndo: true 22536 }); 22537 this._reportTelemetry({ 22538 action: "thickness_changed", 22539 thickness 22540 }, true); 22541 } 22542 get toolbarButtons() { 22543 if (this._uiManager.highlightColors) { 22544 const colorPicker = this.#colorPicker = new ColorPicker({ 22545 editor: this 22546 }); 22547 return [["colorPicker", colorPicker]]; 22548 } 22549 return super.toolbarButtons; 22550 } 22551 disableEditing() { 22552 super.disableEditing(); 22553 this.div.classList.toggle("disabled", true); 22554 } 22555 enableEditing() { 22556 super.enableEditing(); 22557 this.div.classList.toggle("disabled", false); 22558 } 22559 fixAndSetPosition() { 22560 return super.fixAndSetPosition(this.#getRotation()); 22561 } 22562 getBaseTranslation() { 22563 return [0, 0]; 22564 } 22565 getRect(tx, ty) { 22566 return super.getRect(tx, ty, this.#getRotation()); 22567 } 22568 onceAdded(focus) { 22569 if (!this.annotationElementId) { 22570 this.parent.addUndoableEditor(this); 22571 } 22572 if (focus) { 22573 this.div.focus(); 22574 } 22575 } 22576 remove() { 22577 this.#cleanDrawLayer(); 22578 this._reportTelemetry({ 22579 action: "deleted" 22580 }); 22581 super.remove(); 22582 } 22583 rebuild() { 22584 if (!this.parent) { 22585 return; 22586 } 22587 super.rebuild(); 22588 if (this.div === null) { 22589 return; 22590 } 22591 this.#addToDrawLayer(); 22592 if (!this.isAttachedToDOM) { 22593 this.parent.add(this); 22594 } 22595 } 22596 setParent(parent) { 22597 let mustBeSelected = false; 22598 if (this.parent && !parent) { 22599 this.#cleanDrawLayer(); 22600 } else if (parent) { 22601 this.#addToDrawLayer(parent); 22602 mustBeSelected = !this.parent && this.div?.classList.contains("selectedEditor"); 22603 } 22604 super.setParent(parent); 22605 this.show(this._isVisible); 22606 if (mustBeSelected) { 22607 this.select(); 22608 } 22609 } 22610 #changeThickness(thickness) { 22611 if (!this.#isFreeHighlight) { 22612 return; 22613 } 22614 this.#createFreeOutlines({ 22615 highlightOutlines: this.#highlightOutlines.getNewOutline(thickness / 2) 22616 }); 22617 this.fixAndSetPosition(); 22618 this.setDims(); 22619 } 22620 #cleanDrawLayer() { 22621 if (this.#id === null || !this.parent) { 22622 return; 22623 } 22624 this.parent.drawLayer.remove(this.#id); 22625 this.#id = null; 22626 this.parent.drawLayer.remove(this.#outlineId); 22627 this.#outlineId = null; 22628 } 22629 #addToDrawLayer(parent = this.parent) { 22630 if (this.#id !== null) { 22631 return; 22632 } 22633 ({ 22634 id: this.#id, 22635 clipPathId: this.#clipPathId 22636 } = parent.drawLayer.draw({ 22637 bbox: this.#highlightOutlines.box, 22638 root: { 22639 viewBox: "0 0 1 1", 22640 fill: this.color, 22641 "fill-opacity": this.opacity 22642 }, 22643 rootClass: { 22644 highlight: true, 22645 free: this.#isFreeHighlight 22646 }, 22647 path: { 22648 d: this.#highlightOutlines.toSVGPath() 22649 } 22650 }, false, true)); 22651 this.#outlineId = parent.drawLayer.drawOutline({ 22652 rootClass: { 22653 highlightOutline: true, 22654 free: this.#isFreeHighlight 22655 }, 22656 bbox: this.#focusOutlines.box, 22657 path: { 22658 d: this.#focusOutlines.toSVGPath() 22659 } 22660 }, this.#isFreeHighlight); 22661 if (this.#highlightDiv) { 22662 this.#highlightDiv.style.clipPath = this.#clipPathId; 22663 } 22664 } 22665 static #rotateBbox([x, y, width, height], angle) { 22666 switch (angle) { 22667 case 90: 22668 return [1 - y - height, x, height, width]; 22669 case 180: 22670 return [1 - x - width, 1 - y - height, width, height]; 22671 case 270: 22672 return [y, 1 - x - width, height, width]; 22673 } 22674 return [x, y, width, height]; 22675 } 22676 rotate(angle) { 22677 const { 22678 drawLayer 22679 } = this.parent; 22680 let box; 22681 if (this.#isFreeHighlight) { 22682 angle = (angle - this.rotation + 360) % 360; 22683 box = HighlightEditor.#rotateBbox(this.#highlightOutlines.box, angle); 22684 } else { 22685 box = HighlightEditor.#rotateBbox([this.x, this.y, this.width, this.height], angle); 22686 } 22687 drawLayer.updateProperties(this.#id, { 22688 bbox: box, 22689 root: { 22690 "data-main-rotation": angle 22691 } 22692 }); 22693 drawLayer.updateProperties(this.#outlineId, { 22694 bbox: HighlightEditor.#rotateBbox(this.#focusOutlines.box, angle), 22695 root: { 22696 "data-main-rotation": angle 22697 } 22698 }); 22699 } 22700 render() { 22701 if (this.div) { 22702 return this.div; 22703 } 22704 const div = super.render(); 22705 if (this.#text) { 22706 div.setAttribute("aria-label", this.#text); 22707 div.setAttribute("role", "mark"); 22708 } 22709 if (this.#isFreeHighlight) { 22710 div.classList.add("free"); 22711 } else { 22712 this.div.addEventListener("keydown", this.#keydown.bind(this), { 22713 signal: this._uiManager._signal 22714 }); 22715 } 22716 const highlightDiv = this.#highlightDiv = document.createElement("div"); 22717 div.append(highlightDiv); 22718 highlightDiv.setAttribute("aria-hidden", "true"); 22719 highlightDiv.className = "internal"; 22720 highlightDiv.style.clipPath = this.#clipPathId; 22721 this.setDims(); 22722 bindEvents(this, this.#highlightDiv, ["pointerover", "pointerleave"]); 22723 this.enableEditing(); 22724 return div; 22725 } 22726 pointerover() { 22727 if (!this.isSelected) { 22728 this.parent?.drawLayer.updateProperties(this.#outlineId, { 22729 rootClass: { 22730 hovered: true 22731 } 22732 }); 22733 } 22734 } 22735 pointerleave() { 22736 if (!this.isSelected) { 22737 this.parent?.drawLayer.updateProperties(this.#outlineId, { 22738 rootClass: { 22739 hovered: false 22740 } 22741 }); 22742 } 22743 } 22744 #keydown(event) { 22745 HighlightEditor._keyboardManager.exec(this, event); 22746 } 22747 _moveCaret(direction) { 22748 this.parent.unselect(this); 22749 switch (direction) { 22750 case 0: 22751 case 2: 22752 this.#setCaret(true); 22753 break; 22754 case 1: 22755 case 3: 22756 this.#setCaret(false); 22757 break; 22758 } 22759 } 22760 #setCaret(start) { 22761 if (!this.#anchorNode) { 22762 return; 22763 } 22764 const selection = window.getSelection(); 22765 if (start) { 22766 selection.setPosition(this.#anchorNode, this.#anchorOffset); 22767 } else { 22768 selection.setPosition(this.#focusNode, this.#focusOffset); 22769 } 22770 } 22771 select() { 22772 super.select(); 22773 if (!this.#outlineId) { 22774 return; 22775 } 22776 this.parent?.drawLayer.updateProperties(this.#outlineId, { 22777 rootClass: { 22778 hovered: false, 22779 selected: true 22780 } 22781 }); 22782 } 22783 unselect() { 22784 super.unselect(); 22785 if (!this.#outlineId) { 22786 return; 22787 } 22788 this.parent?.drawLayer.updateProperties(this.#outlineId, { 22789 rootClass: { 22790 selected: false 22791 } 22792 }); 22793 if (!this.#isFreeHighlight) { 22794 this.#setCaret(false); 22795 } 22796 } 22797 get _mustFixPosition() { 22798 return !this.#isFreeHighlight; 22799 } 22800 show(visible = this._isVisible) { 22801 super.show(visible); 22802 if (this.parent) { 22803 this.parent.drawLayer.updateProperties(this.#id, { 22804 rootClass: { 22805 hidden: !visible 22806 } 22807 }); 22808 this.parent.drawLayer.updateProperties(this.#outlineId, { 22809 rootClass: { 22810 hidden: !visible 22811 } 22812 }); 22813 } 22814 } 22815 #getRotation() { 22816 return this.#isFreeHighlight ? this.rotation : 0; 22817 } 22818 #serializeBoxes() { 22819 if (this.#isFreeHighlight) { 22820 return null; 22821 } 22822 const [pageWidth, pageHeight] = this.pageDimensions; 22823 const [pageX, pageY] = this.pageTranslation; 22824 const boxes = this.#boxes; 22825 const quadPoints = new Float32Array(boxes.length * 8); 22826 let i = 0; 22827 for (const { 22828 x, 22829 y, 22830 width, 22831 height 22832 } of boxes) { 22833 const sx = x * pageWidth + pageX; 22834 const sy = (1 - y) * pageHeight + pageY; 22835 quadPoints[i] = quadPoints[i + 4] = sx; 22836 quadPoints[i + 1] = quadPoints[i + 3] = sy; 22837 quadPoints[i + 2] = quadPoints[i + 6] = sx + width * pageWidth; 22838 quadPoints[i + 5] = quadPoints[i + 7] = sy - height * pageHeight; 22839 i += 8; 22840 } 22841 return quadPoints; 22842 } 22843 #serializeOutlines(rect) { 22844 return this.#highlightOutlines.serialize(rect, this.#getRotation()); 22845 } 22846 static startHighlighting(parent, isLTR, { 22847 target: textLayer, 22848 x, 22849 y 22850 }) { 22851 const { 22852 x: layerX, 22853 y: layerY, 22854 width: parentWidth, 22855 height: parentHeight 22856 } = textLayer.getBoundingClientRect(); 22857 const ac = new AbortController(); 22858 const signal = parent.combinedSignal(ac); 22859 const pointerUpCallback = e => { 22860 ac.abort(); 22861 this.#endHighlight(parent, e); 22862 }; 22863 window.addEventListener("blur", pointerUpCallback, { 22864 signal 22865 }); 22866 window.addEventListener("pointerup", pointerUpCallback, { 22867 signal 22868 }); 22869 window.addEventListener("pointerdown", stopEvent, { 22870 capture: true, 22871 passive: false, 22872 signal 22873 }); 22874 window.addEventListener("contextmenu", noContextMenu, { 22875 signal 22876 }); 22877 textLayer.addEventListener("pointermove", this.#highlightMove.bind(this, parent), { 22878 signal 22879 }); 22880 this._freeHighlight = new FreeHighlightOutliner({ 22881 x, 22882 y 22883 }, [layerX, layerY, parentWidth, parentHeight], parent.scale, this._defaultThickness / 2, isLTR, 0.001); 22884 ({ 22885 id: this._freeHighlightId, 22886 clipPathId: this._freeHighlightClipId 22887 } = parent.drawLayer.draw({ 22888 bbox: [0, 0, 1, 1], 22889 root: { 22890 viewBox: "0 0 1 1", 22891 fill: this._defaultColor, 22892 "fill-opacity": this._defaultOpacity 22893 }, 22894 rootClass: { 22895 highlight: true, 22896 free: true 22897 }, 22898 path: { 22899 d: this._freeHighlight.toSVGPath() 22900 } 22901 }, true, true)); 22902 } 22903 static #highlightMove(parent, event) { 22904 if (this._freeHighlight.add(event)) { 22905 parent.drawLayer.updateProperties(this._freeHighlightId, { 22906 path: { 22907 d: this._freeHighlight.toSVGPath() 22908 } 22909 }); 22910 } 22911 } 22912 static #endHighlight(parent, event) { 22913 if (!this._freeHighlight.isEmpty()) { 22914 parent.createAndAddNewEditor(event, false, { 22915 highlightId: this._freeHighlightId, 22916 highlightOutlines: this._freeHighlight.getOutlines(), 22917 clipPathId: this._freeHighlightClipId, 22918 methodOfCreation: "main_toolbar" 22919 }); 22920 } else { 22921 parent.drawLayer.remove(this._freeHighlightId); 22922 } 22923 this._freeHighlightId = -1; 22924 this._freeHighlight = null; 22925 this._freeHighlightClipId = ""; 22926 } 22927 static async deserialize(data, parent, uiManager) { 22928 let initialData = null; 22929 if (data instanceof HighlightAnnotationElement) { 22930 const { 22931 data: { 22932 quadPoints, 22933 rect, 22934 rotation, 22935 id, 22936 color, 22937 opacity, 22938 popupRef, 22939 richText, 22940 contentsObj, 22941 creationDate, 22942 modificationDate 22943 }, 22944 parent: { 22945 page: { 22946 pageNumber 22947 } 22948 } 22949 } = data; 22950 initialData = data = { 22951 annotationType: AnnotationEditorType.HIGHLIGHT, 22952 color: Array.from(color), 22953 opacity, 22954 quadPoints, 22955 boxes: null, 22956 pageIndex: pageNumber - 1, 22957 rect: rect.slice(0), 22958 rotation, 22959 annotationElementId: id, 22960 id, 22961 deleted: false, 22962 popupRef, 22963 richText, 22964 comment: contentsObj?.str || null, 22965 creationDate, 22966 modificationDate 22967 }; 22968 } else if (data instanceof InkAnnotationElement) { 22969 const { 22970 data: { 22971 inkLists, 22972 rect, 22973 rotation, 22974 id, 22975 color, 22976 borderStyle: { 22977 rawWidth: thickness 22978 }, 22979 popupRef, 22980 richText, 22981 contentsObj, 22982 creationDate, 22983 modificationDate 22984 }, 22985 parent: { 22986 page: { 22987 pageNumber 22988 } 22989 } 22990 } = data; 22991 initialData = data = { 22992 annotationType: AnnotationEditorType.HIGHLIGHT, 22993 color: Array.from(color), 22994 thickness, 22995 inkLists, 22996 boxes: null, 22997 pageIndex: pageNumber - 1, 22998 rect: rect.slice(0), 22999 rotation, 23000 annotationElementId: id, 23001 id, 23002 deleted: false, 23003 popupRef, 23004 richText, 23005 comment: contentsObj?.str || null, 23006 creationDate, 23007 modificationDate 23008 }; 23009 } 23010 const { 23011 color, 23012 quadPoints, 23013 inkLists, 23014 outlines, 23015 opacity 23016 } = data; 23017 const editor = await super.deserialize(data, parent, uiManager); 23018 editor.color = Util.makeHexColor(...color); 23019 editor.opacity = opacity || 1; 23020 if (inkLists) { 23021 editor.#thickness = data.thickness; 23022 } 23023 editor._initialData = initialData; 23024 if (data.comment) { 23025 editor.setCommentData(data); 23026 } 23027 const [pageWidth, pageHeight] = editor.pageDimensions; 23028 const [pageX, pageY] = editor.pageTranslation; 23029 if (quadPoints) { 23030 const boxes = editor.#boxes = []; 23031 for (let i = 0; i < quadPoints.length; i += 8) { 23032 boxes.push({ 23033 x: (quadPoints[i] - pageX) / pageWidth, 23034 y: 1 - (quadPoints[i + 1] - pageY) / pageHeight, 23035 width: (quadPoints[i + 2] - quadPoints[i]) / pageWidth, 23036 height: (quadPoints[i + 1] - quadPoints[i + 5]) / pageHeight 23037 }); 23038 } 23039 editor.#createOutlines(); 23040 editor.#addToDrawLayer(); 23041 editor.rotate(editor.rotation); 23042 } else if (inkLists || outlines) { 23043 editor.#isFreeHighlight = true; 23044 const points = (inkLists || outlines.points)[0]; 23045 const point = { 23046 x: points[0] - pageX, 23047 y: pageHeight - (points[1] - pageY) 23048 }; 23049 const outliner = new FreeHighlightOutliner(point, [0, 0, pageWidth, pageHeight], 1, editor.#thickness / 2, true, 0.001); 23050 for (let i = 0, ii = points.length; i < ii; i += 2) { 23051 point.x = points[i] - pageX; 23052 point.y = pageHeight - (points[i + 1] - pageY); 23053 outliner.add(point); 23054 } 23055 const { 23056 id, 23057 clipPathId 23058 } = parent.drawLayer.draw({ 23059 bbox: [0, 0, 1, 1], 23060 root: { 23061 viewBox: "0 0 1 1", 23062 fill: editor.color, 23063 "fill-opacity": editor._defaultOpacity 23064 }, 23065 rootClass: { 23066 highlight: true, 23067 free: true 23068 }, 23069 path: { 23070 d: outliner.toSVGPath() 23071 } 23072 }, true, true); 23073 editor.#createFreeOutlines({ 23074 highlightOutlines: outliner.getOutlines(), 23075 highlightId: id, 23076 clipPathId 23077 }); 23078 editor.#addToDrawLayer(); 23079 editor.rotate(editor.parentRotation); 23080 } 23081 return editor; 23082 } 23083 serialize(isForCopying = false) { 23084 if (this.isEmpty() || isForCopying) { 23085 return null; 23086 } 23087 if (this.deleted) { 23088 return this.serializeDeleted(); 23089 } 23090 const color = AnnotationEditor._colorManager.convert(this._uiManager.getNonHCMColor(this.color)); 23091 const serialized = super.serialize(isForCopying); 23092 Object.assign(serialized, { 23093 color, 23094 opacity: this.opacity, 23095 thickness: this.#thickness, 23096 quadPoints: this.#serializeBoxes(), 23097 outlines: this.#serializeOutlines(serialized.rect) 23098 }); 23099 this.addComment(serialized); 23100 if (this.annotationElementId && !this.#hasElementChanged(serialized)) { 23101 return null; 23102 } 23103 serialized.id = this.annotationElementId; 23104 return serialized; 23105 } 23106 #hasElementChanged(serialized) { 23107 const { 23108 color 23109 } = this._initialData; 23110 return this.hasEditedComment || serialized.color.some((c, i) => c !== color[i]); 23111 } 23112 renderAnnotationElement(annotation) { 23113 if (this.deleted) { 23114 annotation.hide(); 23115 return null; 23116 } 23117 annotation.updateEdited({ 23118 rect: this.getPDFRect(), 23119 popup: this.comment 23120 }); 23121 return null; 23122 } 23123 static canCreateNewEmptyEditor() { 23124 return false; 23125 } 23126} 23127 23128;// ./src/display/editor/draw.js 23129 23130 23131 23132 23133class DrawingOptions { 23134 #svgProperties = Object.create(null); 23135 updateProperty(name, value) { 23136 this[name] = value; 23137 this.updateSVGProperty(name, value); 23138 } 23139 updateProperties(properties) { 23140 if (!properties) { 23141 return; 23142 } 23143 for (const [name, value] of Object.entries(properties)) { 23144 if (!name.startsWith("_")) { 23145 this.updateProperty(name, value); 23146 } 23147 } 23148 } 23149 updateSVGProperty(name, value) { 23150 this.#svgProperties[name] = value; 23151 } 23152 toSVGProperties() { 23153 const root = this.#svgProperties; 23154 this.#svgProperties = Object.create(null); 23155 return { 23156 root 23157 }; 23158 } 23159 reset() { 23160 this.#svgProperties = Object.create(null); 23161 } 23162 updateAll(options = this) { 23163 this.updateProperties(options); 23164 } 23165 clone() { 23166 unreachable("Not implemented"); 23167 } 23168} 23169class DrawingEditor extends AnnotationEditor { 23170 #drawOutlines = null; 23171 #mustBeCommitted; 23172 _colorPicker = null; 23173 _drawId = null; 23174 static _currentDrawId = -1; 23175 static _currentParent = null; 23176 static #currentDraw = null; 23177 static #currentDrawingAC = null; 23178 static #currentDrawingOptions = null; 23179 static _INNER_MARGIN = 3; 23180 constructor(params) { 23181 super(params); 23182 this.#mustBeCommitted = params.mustBeCommitted || false; 23183 this._addOutlines(params); 23184 } 23185 onUpdatedColor() { 23186 this._colorPicker?.update(this.color); 23187 super.onUpdatedColor(); 23188 } 23189 onUpdatedOpacity() { 23190 this._colorPicker?.updateOpacity?.(this.opacity); 23191 } 23192 _addOutlines(params) { 23193 if (params.drawOutlines) { 23194 this.#createDrawOutlines(params); 23195 this.#addToDrawLayer(); 23196 } 23197 } 23198 #createDrawOutlines({ 23199 drawOutlines, 23200 drawId, 23201 drawingOptions 23202 }) { 23203 this.#drawOutlines = drawOutlines; 23204 this._drawingOptions ||= drawingOptions; 23205 if (!this.annotationElementId) { 23206 this._uiManager.a11yAlert(AnnotationEditor._l10nAlert[this.editorType]); 23207 } 23208 if (drawId >= 0) { 23209 this._drawId = drawId; 23210 this.parent.drawLayer.finalizeDraw(drawId, drawOutlines.defaultProperties); 23211 } else { 23212 this._drawId = this.#createDrawing(drawOutlines, this.parent); 23213 } 23214 this.#updateBbox(drawOutlines.box); 23215 } 23216 #createDrawing(drawOutlines, parent) { 23217 const { 23218 id 23219 } = parent.drawLayer.draw(DrawingEditor._mergeSVGProperties(this._drawingOptions.toSVGProperties(), drawOutlines.defaultSVGProperties), false, false); 23220 return id; 23221 } 23222 static _mergeSVGProperties(p1, p2) { 23223 const p1Keys = new Set(Object.keys(p1)); 23224 for (const [key, value] of Object.entries(p2)) { 23225 if (p1Keys.has(key)) { 23226 Object.assign(p1[key], value); 23227 } else { 23228 p1[key] = value; 23229 } 23230 } 23231 return p1; 23232 } 23233 static getDefaultDrawingOptions(_options) { 23234 unreachable("Not implemented"); 23235 } 23236 static get typesMap() { 23237 unreachable("Not implemented"); 23238 } 23239 static get isDrawer() { 23240 return true; 23241 } 23242 static get supportMultipleDrawings() { 23243 return false;
vendor: 4,719 bytes, lines 23244-23389
23244 } 23245 static updateDefaultParams(type, value) { 23246 const propertyName = this.typesMap.get(type); 23247 if (propertyName) { 23248 this._defaultDrawingOptions.updateProperty(propertyName, value); 23249 } 23250 if (this._currentParent) { 23251 DrawingEditor.#currentDraw.updateProperty(propertyName, value); 23252 this._currentParent.drawLayer.updateProperties(this._currentDrawId, this._defaultDrawingOptions.toSVGProperties()); 23253 } 23254 } 23255 updateParams(type, value) { 23256 const propertyName = this.constructor.typesMap.get(type); 23257 if (propertyName) { 23258 this._updateProperty(type, propertyName, value); 23259 } 23260 } 23261 static get defaultPropertiesToUpdate() { 23262 const properties = []; 23263 const options = this._defaultDrawingOptions; 23264 for (const [type, name] of this.typesMap) { 23265 properties.push([type, options[name]]); 23266 } 23267 return properties; 23268 } 23269 get propertiesToUpdate() { 23270 const properties = []; 23271 const { 23272 _drawingOptions 23273 } = this; 23274 for (const [type, name] of this.constructor.typesMap) { 23275 properties.push([type, _drawingOptions[name]]); 23276 } 23277 return properties; 23278 } 23279 _updateProperty(type, name, value) { 23280 const options = this._drawingOptions; 23281 const savedValue = options[name]; 23282 const setter = val => { 23283 options.updateProperty(name, val); 23284 const bbox = this.#drawOutlines.updateProperty(name, val); 23285 if (bbox) { 23286 this.#updateBbox(bbox); 23287 } 23288 this.parent?.drawLayer.updateProperties(this._drawId, options.toSVGProperties()); 23289 if (type === this.colorType) { 23290 this.onUpdatedColor(); 23291 } else if (type === this.opacityType) { 23292 this.onUpdatedOpacity(); 23293 } 23294 }; 23295 this.addCommands({ 23296 cmd: setter.bind(this, value), 23297 undo: setter.bind(this, savedValue), 23298 post: this._uiManager.updateUI.bind(this._uiManager, this), 23299 mustExec: true, 23300 type, 23301 overwriteIfSameType: true, 23302 keepUndo: true 23303 }); 23304 } 23305 _updateColorAndOpacity(color, opacity) { 23306 const colorName = this.constructor.typesMap.get(this.colorType); 23307 const opacityName = this.constructor.typesMap.get(this.opacityType); 23308 const options = this._drawingOptions; 23309 const savedColor = options[colorName]; 23310 const savedOpacity = options[opacityName]; 23311 const setter = (c, op) => { 23312 options.updateProperty(colorName, c); 23313 options.updateProperty(opacityName, op); 23314 this.#drawOutlines.updateProperty(colorName, c); 23315 this.#drawOutlines.updateProperty(opacityName, op); 23316 this.parent?.drawLayer.updateProperties(this._drawId, options.toSVGProperties()); 23317 this.onUpdatedColor(); 23318 this.onUpdatedOpacity(); 23319 }; 23320 this.addCommands({ 23321 cmd: setter.bind(this, color, opacity), 23322 undo: setter.bind(this, savedColor, savedOpacity), 23323 post: this._uiManager.updateUI.bind(this._uiManager, this), 23324 mustExec: true, 23325 type: AnnotationEditorParamsType.INK_COLOR_AND_OPACITY, 23326 overwriteIfSameType: true, 23327 keepUndo: true 23328 }); 23329 } 23330 _onResizing() { 23331 this.parent?.drawLayer.updateProperties(this._drawId, DrawingEditor._mergeSVGProperties(this.#drawOutlines.getPathResizingSVGProperties(this.#convertToDrawSpace()), { 23332 bbox: this.#rotateBox() 23333 })); 23334 } 23335 _onResized() { 23336 this.parent?.drawLayer.updateProperties(this._drawId, DrawingEditor._mergeSVGProperties(this.#drawOutlines.getPathResizedSVGProperties(this.#convertToDrawSpace()), { 23337 bbox: this.#rotateBox() 23338 })); 23339 } 23340 _onTranslating(_x, _y) { 23341 this.parent?.drawLayer.updateProperties(this._drawId, { 23342 bbox: this.#rotateBox() 23343 }); 23344 } 23345 _onTranslated() { 23346 this.parent?.drawLayer.updateProperties(this._drawId, DrawingEditor._mergeSVGProperties(this.#drawOutlines.getPathTranslatedSVGProperties(this.#convertToDrawSpace(), this.parentDimensions), { 23347 bbox: this.#rotateBox() 23348 })); 23349 } 23350 _onStartDragging() { 23351 this.parent?.drawLayer.updateProperties(this._drawId, { 23352 rootClass: { 23353 moving: true 23354 } 23355 }); 23356 } 23357 _onStopDragging() { 23358 this.parent?.drawLayer.updateProperties(this._drawId, { 23359 rootClass: { 23360 moving: false 23361 } 23362 }); 23363 } 23364 commit() { 23365 super.commit(); 23366 this.disableEditMode(); 23367 this.disableEditing(); 23368 } 23369 disableEditing() { 23370 super.disableEditing(); 23371 this.div.classList.toggle("disabled", true); 23372 } 23373 enableEditing() { 23374 super.enableEditing(); 23375 this.div.classList.toggle("disabled", false); 23376 } 23377 getBaseTranslation() { 23378 return [0, 0]; 23379 } 23380 get isResizable() { 23381 return true; 23382 } 23383 onceAdded(focus) { 23384 if (!this.annotationElementId) { 23385 this.parent.addUndoableEditor(this); 23386 } 23387 this._isDraggable = true; 23388 if (this.#mustBeCommitted) { 23389 this.#mustBeCommitted = false;
vendor: 6,101 bytes, lines 23390-23606
23390 this.commit(); 23391 this.parent.setSelected(this); 23392 if (focus && this.isOnScreen) { 23393 this.div.focus(); 23394 } 23395 } 23396 } 23397 remove() { 23398 this.#cleanDrawLayer(); 23399 super.remove(); 23400 } 23401 rebuild() { 23402 if (!this.parent) { 23403 return; 23404 } 23405 super.rebuild(); 23406 if (this.div === null) { 23407 return; 23408 } 23409 this.#addToDrawLayer(); 23410 this.#updateBbox(this.#drawOutlines.box); 23411 if (!this.isAttachedToDOM) { 23412 this.parent.add(this); 23413 } 23414 } 23415 setParent(parent) { 23416 let mustBeSelected = false; 23417 if (this.parent && !parent) { 23418 this._uiManager.removeShouldRescale(this); 23419 this.#cleanDrawLayer(); 23420 } else if (parent) { 23421 this._uiManager.addShouldRescale(this); 23422 this.#addToDrawLayer(parent); 23423 mustBeSelected = !this.parent && this.div?.classList.contains("selectedEditor"); 23424 } 23425 super.setParent(parent); 23426 if (mustBeSelected) { 23427 this.select(); 23428 } 23429 } 23430 #cleanDrawLayer() { 23431 if (this._drawId === null || !this.parent) { 23432 return; 23433 } 23434 this.parent.drawLayer.remove(this._drawId); 23435 this._drawId = null; 23436 this._drawingOptions.reset(); 23437 } 23438 #addToDrawLayer(parent = this.parent) { 23439 if (this._drawId !== null && this.parent === parent) { 23440 return; 23441 } 23442 if (this._drawId !== null) { 23443 this.parent.drawLayer.updateParent(this._drawId, parent.drawLayer); 23444 return; 23445 } 23446 this._drawingOptions.updateAll(); 23447 this._drawId = this.#createDrawing(this.#drawOutlines, parent); 23448 } 23449 #convertToParentSpace([x, y, width, height]) { 23450 const { 23451 parentDimensions: [pW, pH], 23452 rotation 23453 } = this; 23454 switch (rotation) { 23455 case 90: 23456 return [y, 1 - x, width * (pH / pW), height * (pW / pH)]; 23457 case 180: 23458 return [1 - x, 1 - y, width, height]; 23459 case 270: 23460 return [1 - y, x, width * (pH / pW), height * (pW / pH)]; 23461 default: 23462 return [x, y, width, height]; 23463 } 23464 } 23465 #convertToDrawSpace() { 23466 const { 23467 x, 23468 y, 23469 width, 23470 height, 23471 parentDimensions: [pW, pH], 23472 rotation 23473 } = this; 23474 switch (rotation) { 23475 case 90: 23476 return [1 - y, x, width * (pW / pH), height * (pH / pW)]; 23477 case 180: 23478 return [1 - x, 1 - y, width, height]; 23479 case 270: 23480 return [y, 1 - x, width * (pW / pH), height * (pH / pW)]; 23481 default: 23482 return [x, y, width, height]; 23483 } 23484 } 23485 #updateBbox(bbox) { 23486 [this.x, this.y, this.width, this.height] = this.#convertToParentSpace(bbox); 23487 if (this.div) { 23488 this.fixAndSetPosition(); 23489 this.setDims(); 23490 } 23491 this._onResized(); 23492 } 23493 #rotateBox() { 23494 const { 23495 x, 23496 y, 23497 width, 23498 height, 23499 rotation, 23500 parentRotation, 23501 parentDimensions: [pW, pH] 23502 } = this; 23503 switch ((rotation * 4 + parentRotation) / 90) { 23504 case 1: 23505 return [1 - y - height, x, height, width]; 23506 case 2: 23507 return [1 - x - width, 1 - y - height, width, height]; 23508 case 3: 23509 return [y, 1 - x - width, height, width]; 23510 case 4: 23511 return [x, y - width * (pW / pH), height * (pH / pW), width * (pW / pH)]; 23512 case 5: 23513 return [1 - y, x, width * (pW / pH), height * (pH / pW)]; 23514 case 6: 23515 return [1 - x - height * (pH / pW), 1 - y, height * (pH / pW), width * (pW / pH)]; 23516 case 7: 23517 return [y - width * (pW / pH), 1 - x - height * (pH / pW), width * (pW / pH), height * (pH / pW)]; 23518 case 8: 23519 return [x - width, y - height, width, height]; 23520 case 9: 23521 return [1 - y, x - width, height, width]; 23522 case 10: 23523 return [1 - x, 1 - y, width, height]; 23524 case 11: 23525 return [y - height, 1 - x, height, width]; 23526 case 12: 23527 return [x - height * (pH / pW), y, height * (pH / pW), width * (pW / pH)]; 23528 case 13: 23529 return [1 - y - width * (pW / pH), x - height * (pH / pW), width * (pW / pH), height * (pH / pW)]; 23530 case 14: 23531 return [1 - x, 1 - y - width * (pW / pH), height * (pH / pW), width * (pW / pH)]; 23532 case 15: 23533 return [y, 1 - x, width * (pW / pH), height * (pH / pW)]; 23534 default: 23535 return [x, y, width, height]; 23536 } 23537 } 23538 rotate() { 23539 if (!this.parent) { 23540 return; 23541 } 23542 this.parent.drawLayer.updateProperties(this._drawId, DrawingEditor._mergeSVGProperties({ 23543 bbox: this.#rotateBox() 23544 }, this.#drawOutlines.updateRotation((this.parentRotation - this.rotation + 360) % 360))); 23545 } 23546 onScaleChanging() { 23547 if (!this.parent) { 23548 return; 23549 } 23550 this.#updateBbox(this.#drawOutlines.updateParentDimensions(this.parentDimensions, this.parent.scale)); 23551 } 23552 static onScaleChangingWhenDrawing() {} 23553 render() { 23554 if (this.div) { 23555 return this.div; 23556 } 23557 let baseX, baseY; 23558 if (this._isCopy) { 23559 baseX = this.x; 23560 baseY = this.y; 23561 } 23562 const div = super.render(); 23563 div.classList.add("draw"); 23564 const drawDiv = document.createElement("div"); 23565 div.append(drawDiv); 23566 drawDiv.setAttribute("aria-hidden", "true"); 23567 drawDiv.className = "internal"; 23568 this.setDims(); 23569 this._uiManager.addShouldRescale(this); 23570 this.disableEditing(); 23571 if (this._isCopy) { 23572 this._moveAfterPaste(baseX, baseY); 23573 } 23574 return div; 23575 } 23576 static createDrawerInstance(_x, _y, _parentWidth, _parentHeight, _rotation) { 23577 unreachable("Not implemented"); 23578 } 23579 static startDrawing(parent, uiManager, _isLTR, event) { 23580 const { 23581 target, 23582 offsetX: x, 23583 offsetY: y, 23584 pointerId, 23585 pointerType 23586 } = event; 23587 if (CurrentPointers.isInitializedAndDifferentPointerType(pointerType)) { 23588 return; 23589 } 23590 const { 23591 viewport: { 23592 rotation 23593 } 23594 } = parent; 23595 const { 23596 width: parentWidth, 23597 height: parentHeight 23598 } = target.getBoundingClientRect(); 23599 const ac = DrawingEditor.#currentDrawingAC = new AbortController(); 23600 const signal = parent.combinedSignal(ac); 23601 CurrentPointers.setPointer(pointerType, pointerId); 23602 window.addEventListener("pointerup", e => { 23603 if (CurrentPointers.isSamePointerIdOrRemove(e.pointerId)) { 23604 this._endDraw(e); 23605 } 23606 }, {
vendor: 6,826 bytes, lines 23607-23831
23607 signal 23608 }); 23609 window.addEventListener("pointercancel", e => { 23610 if (CurrentPointers.isSamePointerIdOrRemove(e.pointerId)) { 23611 this._currentParent.endDrawingSession(); 23612 } 23613 }, { 23614 signal 23615 }); 23616 window.addEventListener("pointerdown", e => { 23617 if (!CurrentPointers.isSamePointerType(e.pointerType)) { 23618 return; 23619 } 23620 CurrentPointers.initializeAndAddPointerId(e.pointerId); 23621 if (DrawingEditor.#currentDraw.isCancellable()) { 23622 DrawingEditor.#currentDraw.removeLastElement(); 23623 if (DrawingEditor.#currentDraw.isEmpty()) { 23624 this._currentParent.endDrawingSession(true); 23625 } else { 23626 this._endDraw(null); 23627 } 23628 } 23629 }, { 23630 capture: true, 23631 passive: false, 23632 signal 23633 }); 23634 window.addEventListener("contextmenu", noContextMenu, { 23635 signal 23636 }); 23637 target.addEventListener("pointermove", this._drawMove.bind(this), { 23638 signal 23639 }); 23640 target.addEventListener("touchmove", e => { 23641 if (CurrentPointers.isSameTimeStamp(e.timeStamp)) { 23642 stopEvent(e); 23643 } 23644 }, { 23645 signal 23646 }); 23647 parent.toggleDrawing(); 23648 uiManager._editorUndoBar?.hide(); 23649 if (DrawingEditor.#currentDraw) { 23650 parent.drawLayer.updateProperties(this._currentDrawId, DrawingEditor.#currentDraw.startNew(x, y, parentWidth, parentHeight, rotation)); 23651 return; 23652 } 23653 uiManager.updateUIForDefaultProperties(this); 23654 DrawingEditor.#currentDraw = this.createDrawerInstance(x, y, parentWidth, parentHeight, rotation); 23655 DrawingEditor.#currentDrawingOptions = this.getDefaultDrawingOptions(); 23656 this._currentParent = parent; 23657 ({ 23658 id: this._currentDrawId 23659 } = parent.drawLayer.draw(this._mergeSVGProperties(DrawingEditor.#currentDrawingOptions.toSVGProperties(), DrawingEditor.#currentDraw.defaultSVGProperties), true, false)); 23660 } 23661 static _drawMove(event) { 23662 CurrentPointers.isSameTimeStamp(event.timeStamp); 23663 if (!DrawingEditor.#currentDraw) { 23664 return; 23665 } 23666 const { 23667 offsetX, 23668 offsetY, 23669 pointerId 23670 } = event; 23671 if (!CurrentPointers.isSamePointerId(pointerId)) { 23672 return; 23673 } 23674 if (CurrentPointers.isUsingMultiplePointers()) { 23675 this._endDraw(event); 23676 return; 23677 } 23678 this._currentParent.drawLayer.updateProperties(this._currentDrawId, DrawingEditor.#currentDraw.add(offsetX, offsetY)); 23679 CurrentPointers.setTimeStamp(event.timeStamp); 23680 stopEvent(event); 23681 } 23682 static _cleanup(all) { 23683 if (all) { 23684 this._currentDrawId = -1; 23685 this._currentParent = null; 23686 DrawingEditor.#currentDraw = null; 23687 DrawingEditor.#currentDrawingOptions = null; 23688 CurrentPointers.clearTimeStamp(); 23689 } 23690 if (DrawingEditor.#currentDrawingAC) { 23691 DrawingEditor.#currentDrawingAC.abort(); 23692 DrawingEditor.#currentDrawingAC = null; 23693 CurrentPointers.clearPointerIds(); 23694 } 23695 } 23696 static _endDraw(event) { 23697 const parent = this._currentParent; 23698 if (!parent) { 23699 return; 23700 } 23701 parent.toggleDrawing(true); 23702 this._cleanup(false); 23703 if (event?.target === parent.div) { 23704 parent.drawLayer.updateProperties(this._currentDrawId, DrawingEditor.#currentDraw.end(event.offsetX, event.offsetY)); 23705 } 23706 if (this.supportMultipleDrawings) { 23707 const draw = DrawingEditor.#currentDraw; 23708 const drawId = this._currentDrawId; 23709 const lastElement = draw.getLastElement(); 23710 parent.addCommands({ 23711 cmd: () => { 23712 parent.drawLayer.updateProperties(drawId, draw.setLastElement(lastElement)); 23713 }, 23714 undo: () => { 23715 parent.drawLayer.updateProperties(drawId, draw.removeLastElement()); 23716 }, 23717 mustExec: false, 23718 type: AnnotationEditorParamsType.DRAW_STEP 23719 }); 23720 return; 23721 } 23722 this.endDrawing(false); 23723 } 23724 static endDrawing(isAborted) { 23725 const parent = this._currentParent; 23726 if (!parent) { 23727 return null; 23728 } 23729 parent.toggleDrawing(true); 23730 parent.cleanUndoStack(AnnotationEditorParamsType.DRAW_STEP); 23731 if (!DrawingEditor.#currentDraw.isEmpty()) { 23732 const { 23733 pageDimensions: [pageWidth, pageHeight], 23734 scale 23735 } = parent; 23736 const editor = parent.createAndAddNewEditor({ 23737 offsetX: 0, 23738 offsetY: 0 23739 }, false, { 23740 drawId: this._currentDrawId, 23741 drawOutlines: DrawingEditor.#currentDraw.getOutlines(pageWidth * scale, pageHeight * scale, scale, this._INNER_MARGIN), 23742 drawingOptions: DrawingEditor.#currentDrawingOptions, 23743 mustBeCommitted: !isAborted 23744 }); 23745 this._cleanup(true); 23746 return editor; 23747 } 23748 parent.drawLayer.remove(this._currentDrawId); 23749 this._cleanup(true); 23750 return null; 23751 } 23752 createDrawingOptions(_data) {} 23753 static deserializeDraw(_pageX, _pageY, _pageWidth, _pageHeight, _innerWidth, _data) { 23754 unreachable("Not implemented"); 23755 } 23756 static async deserialize(data, parent, uiManager) { 23757 const { 23758 rawDims: { 23759 pageWidth, 23760 pageHeight, 23761 pageX, 23762 pageY 23763 } 23764 } = parent.viewport; 23765 const drawOutlines = this.deserializeDraw(pageX, pageY, pageWidth, pageHeight, this._INNER_MARGIN, data); 23766 const editor = await super.deserialize(data, parent, uiManager); 23767 editor.createDrawingOptions(data); 23768 editor.#createDrawOutlines({ 23769 drawOutlines 23770 }); 23771 editor.#addToDrawLayer(); 23772 editor.onScaleChanging(); 23773 editor.rotate(); 23774 return editor; 23775 } 23776 serializeDraw(isForCopying) { 23777 const [pageX, pageY] = this.pageTranslation; 23778 const [pageWidth, pageHeight] = this.pageDimensions; 23779 return this.#drawOutlines.serialize([pageX, pageY, pageWidth, pageHeight], isForCopying); 23780 } 23781 renderAnnotationElement(annotation) { 23782 annotation.updateEdited({ 23783 rect: this.getPDFRect() 23784 }); 23785 return null; 23786 } 23787 static canCreateNewEmptyEditor() { 23788 return false; 23789 } 23790} 23791 23792;// ./src/display/editor/drawers/inkdraw.js 23793 23794 23795 23796class InkDrawOutliner { 23797 #last = new Float64Array(6); 23798 #line; 23799 #lines; 23800 #rotation; 23801 #thickness; 23802 #points; 23803 #lastSVGPath = ""; 23804 #lastIndex = 0; 23805 #outlines = new InkDrawOutline(); 23806 #parentWidth; 23807 #parentHeight; 23808 constructor(x, y, parentWidth, parentHeight, rotation, thickness) { 23809 this.#parentWidth = parentWidth; 23810 this.#parentHeight = parentHeight; 23811 this.#rotation = rotation; 23812 this.#thickness = thickness; 23813 [x, y] = this.#normalizePoint(x, y); 23814 const line = this.#line = [NaN, NaN, NaN, NaN, x, y]; 23815 this.#points = [x, y]; 23816 this.#lines = [{ 23817 line, 23818 points: this.#points 23819 }]; 23820 this.#last.set(line, 0); 23821 } 23822 updateProperty(name, value) { 23823 if (name === "stroke-width") { 23824 this.#thickness = value; 23825 } 23826 } 23827 #normalizePoint(x, y) { 23828 return Outline._normalizePoint(x, y, this.#parentWidth, this.#parentHeight, this.#rotation); 23829 } 23830 isEmpty() { 23831 return !this.#lines?.length
vendor: 10,036 bytes, lines 23831-24185
23831; 23832 } 23833 isCancellable() { 23834 return this.#points.length <= 10; 23835 } 23836 add(x, y) { 23837 [x, y] = this.#normalizePoint(x, y); 23838 const [x1, y1, x2, y2] = this.#last.subarray(2, 6); 23839 const diffX = x - x2; 23840 const diffY = y - y2; 23841 const d = Math.hypot(this.#parentWidth * diffX, this.#parentHeight * diffY); 23842 if (d <= 2) { 23843 return null; 23844 } 23845 this.#points.push(x, y); 23846 if (isNaN(x1)) { 23847 this.#last.set([x2, y2, x, y], 2); 23848 this.#line.push(NaN, NaN, NaN, NaN, x, y); 23849 return { 23850 path: { 23851 d: this.toSVGPath() 23852 } 23853 }; 23854 } 23855 if (isNaN(this.#last[0])) { 23856 this.#line.splice(6, 6); 23857 } 23858 this.#last.set([x1, y1, x2, y2, x, y], 0); 23859 this.#line.push(...Outline.createBezierPoints(x1, y1, x2, y2, x, y)); 23860 return { 23861 path: { 23862 d: this.toSVGPath() 23863 } 23864 }; 23865 } 23866 end(x, y) { 23867 const change = this.add(x, y); 23868 if (change) { 23869 return change; 23870 } 23871 if (this.#points.length === 2) { 23872 return { 23873 path: { 23874 d: this.toSVGPath() 23875 } 23876 }; 23877 } 23878 return null; 23879 } 23880 startNew(x, y, parentWidth, parentHeight, rotation) { 23881 this.#parentWidth = parentWidth; 23882 this.#parentHeight = parentHeight; 23883 this.#rotation = rotation; 23884 [x, y] = this.#normalizePoint(x, y); 23885 const line = this.#line = [NaN, NaN, NaN, NaN, x, y]; 23886 this.#points = [x, y]; 23887 const last = this.#lines.at(-1); 23888 if (last) { 23889 last.line = new Float32Array(last.line); 23890 last.points = new Float32Array(last.points); 23891 } 23892 this.#lines.push({ 23893 line, 23894 points: this.#points 23895 }); 23896 this.#last.set(line, 0); 23897 this.#lastIndex = 0; 23898 this.toSVGPath(); 23899 return null; 23900 } 23901 getLastElement() { 23902 return this.#lines.at(-1); 23903 } 23904 setLastElement(element) { 23905 if (!this.#lines) { 23906 return this.#outlines.setLastElement(element); 23907 } 23908 this.#lines.push(element); 23909 this.#line = element.line; 23910 this.#points = element.points; 23911 this.#lastIndex = 0; 23912 return { 23913 path: { 23914 d: this.toSVGPath() 23915 } 23916 }; 23917 } 23918 removeLastElement() { 23919 if (!this.#lines) { 23920 return this.#outlines.removeLastElement(); 23921 } 23922 this.#lines.pop(); 23923 this.#lastSVGPath = ""; 23924 for (let i = 0, ii = this.#lines.length; i < ii; i++) { 23925 const { 23926 line, 23927 points 23928 } = this.#lines[i]; 23929 this.#line = line; 23930 this.#points = points; 23931 this.#lastIndex = 0; 23932 this.toSVGPath(); 23933 } 23934 return { 23935 path: { 23936 d: this.#lastSVGPath 23937 } 23938 }; 23939 } 23940 toSVGPath() { 23941 const firstX = Outline.svgRound(this.#line[4]); 23942 const firstY = Outline.svgRound(this.#line[5]); 23943 if (this.#points.length === 2) { 23944 this.#lastSVGPath = `${this.#lastSVGPath} M ${firstX} ${firstY} Z`; 23945 return this.#lastSVGPath; 23946 } 23947 if (this.#points.length <= 6) { 23948 const i = this.#lastSVGPath.lastIndexOf("M"); 23949 this.#lastSVGPath = `${this.#lastSVGPath.slice(0, i)} M ${firstX} ${firstY}`; 23950 this.#lastIndex = 6; 23951 } 23952 if (this.#points.length === 4) { 23953 const secondX = Outline.svgRound(this.#line[10]); 23954 const secondY = Outline.svgRound(this.#line[11]); 23955 this.#lastSVGPath = `${this.#lastSVGPath} L ${secondX} ${secondY}`; 23956 this.#lastIndex = 12; 23957 return this.#lastSVGPath; 23958 } 23959 const buffer = []; 23960 if (this.#lastIndex === 0) { 23961 buffer.push(`M ${firstX} ${firstY}`); 23962 this.#lastIndex = 6; 23963 } 23964 for (let i = this.#lastIndex, ii = this.#line.length; i < ii; i += 6) { 23965 const [c1x, c1y, c2x, c2y, x, y] = this.#line.slice(i, i + 6).map(Outline.svgRound); 23966 buffer.push(`C${c1x} ${c1y} ${c2x} ${c2y} ${x} ${y}`); 23967 } 23968 this.#lastSVGPath += buffer.join(" "); 23969 this.#lastIndex = this.#line.length; 23970 return this.#lastSVGPath; 23971 } 23972 getOutlines(parentWidth, parentHeight, scale, innerMargin) { 23973 const last = this.#lines.at(-1); 23974 last.line = new Float32Array(last.line); 23975 last.points = new Float32Array(last.points); 23976 this.#outlines.build(this.#lines, parentWidth, parentHeight, scale, this.#rotation, this.#thickness, innerMargin); 23977 this.#last = null; 23978 this.#line = null; 23979 this.#lines = null; 23980 this.#lastSVGPath = null; 23981 return this.#outlines; 23982 } 23983 get defaultSVGProperties() { 23984 return { 23985 root: { 23986 viewBox: "0 0 10000 10000" 23987 }, 23988 rootClass: { 23989 draw: true 23990 }, 23991 bbox: [0, 0, 1, 1] 23992 }; 23993 } 23994} 23995class InkDrawOutline extends Outline { 23996 #bbox; 23997 #currentRotation = 0; 23998 #innerMargin; 23999 #lines; 24000 #parentWidth; 24001 #parentHeight; 24002 #parentScale; 24003 #rotation; 24004 #thickness; 24005 build(lines, parentWidth, parentHeight, parentScale, rotation, thickness, innerMargin) { 24006 this.#parentWidth = parentWidth; 24007 this.#parentHeight = parentHeight; 24008 this.#parentScale = parentScale; 24009 this.#rotation = rotation; 24010 this.#thickness = thickness; 24011 this.#innerMargin = innerMargin ?? 0; 24012 this.#lines = lines; 24013 this.#computeBbox(); 24014 } 24015 get thickness() { 24016 return this.#thickness; 24017 } 24018 setLastElement(element) { 24019 this.#lines.push(element); 24020 return { 24021 path: { 24022 d: this.toSVGPath() 24023 } 24024 }; 24025 } 24026 removeLastElement() { 24027 this.#lines.pop(); 24028 return { 24029 path: { 24030 d: this.toSVGPath() 24031 } 24032 }; 24033 } 24034 toSVGPath() { 24035 const buffer = []; 24036 for (const { 24037 line 24038 } of this.#lines) { 24039 buffer.push(`M${Outline.svgRound(line[4])} ${Outline.svgRound(line[5])}`); 24040 if (line.length === 6) { 24041 buffer.push("Z"); 24042 continue; 24043 } 24044 if (line.length === 12 && isNaN(line[6])) { 24045 buffer.push(`L${Outline.svgRound(line[10])} ${Outline.svgRound(line[11])}`); 24046 continue; 24047 } 24048 for (let i = 6, ii = line.length; i < ii; i += 6) { 24049 const [c1x, c1y, c2x, c2y, x, y] = line.subarray(i, i + 6).map(Outline.svgRound); 24050 buffer.push(`C${c1x} ${c1y} ${c2x} ${c2y} ${x} ${y}`); 24051 } 24052 } 24053 return buffer.join(""); 24054 } 24055 serialize([pageX, pageY, pageWidth, pageHeight], isForCopying) { 24056 const serializedLines = []; 24057 const serializedPoints = []; 24058 const [x, y, width, height] = this.#getBBoxWithNoMargin(); 24059 let tx, ty, sx, sy, x1, y1, x2, y2, rescaleFn; 24060 switch (this.#rotation) { 24061 case 0: 24062 rescaleFn = Outline._rescale; 24063 tx = pageX; 24064 ty = pageY + pageHeight; 24065 sx = pageWidth; 24066 sy = -pageHeight; 24067 x1 = pageX + x * pageWidth; 24068 y1 = pageY + (1 - y - height) * pageHeight; 24069 x2 = pageX + (x + width) * pageWidth; 24070 y2 = pageY + (1 - y) * pageHeight; 24071 break; 24072 case 90: 24073 rescaleFn = Outline._rescaleAndSwap; 24074 tx = pageX; 24075 ty = pageY; 24076 sx = pageWidth; 24077 sy = pageHeight; 24078 x1 = pageX + y * pageWidth; 24079 y1 = pageY + x * pageHeight; 24080 x2 = pageX + (y + height) * pageWidth; 24081 y2 = pageY + (x + width) * pageHeight; 24082 break; 24083 case 180: 24084 rescaleFn = Outline._rescale; 24085 tx = pageX + pageWidth; 24086 ty = pageY; 24087 sx = -pageWidth; 24088 sy = pageHeight; 24089 x1 = pageX + (1 - x - width) * pageWidth; 24090 y1 = pageY + y * pageHeight; 24091 x2 = pageX + (1 - x) * pageWidth; 24092 y2 = pageY + (y + height) * pageHeight; 24093 break; 24094 case 270: 24095 rescaleFn = Outline._rescaleAndSwap; 24096 tx = pageX + pageWidth; 24097 ty = pageY + pageHeight; 24098 sx = -pageWidth; 24099 sy = -pageHeight; 24100 x1 = pageX + (1 - y - height) * pageWidth; 24101 y1 = pageY + (1 - x - width) * pageHeight; 24102 x2 = pageX + (1 - y) * pageWidth; 24103 y2 = pageY + (1 - x) * pageHeight; 24104 break; 24105 } 24106 for (const { 24107 line, 24108 points 24109 } of this.#lines) { 24110 serializedLines.push(rescaleFn(line, tx, ty, sx, sy, isForCopying ? new Array(line.length) : null)); 24111 serializedPoints.push(rescaleFn(points, tx, ty, sx, sy, isForCopying ? new Array(points.length) : null)); 24112 } 24113 return { 24114 lines: serializedLines, 24115 points: serializedPoints, 24116 rect: [x1, y1, x2, y2] 24117 }; 24118 } 24119 static deserialize(pageX, pageY, pageWidth, pageHeight, innerMargin, { 24120 paths: { 24121 lines, 24122 points 24123 }, 24124 rotation, 24125 thickness 24126 }) { 24127 const newLines = []; 24128 let tx, ty, sx, sy, rescaleFn; 24129 switch (rotation) { 24130 case 0: 24131 rescaleFn = Outline._rescale; 24132 tx = -pageX / pageWidth; 24133 ty = pageY / pageHeight + 1; 24134 sx = 1 / pageWidth; 24135 sy = -1 / pageHeight; 24136 break; 24137 case 90: 24138 rescaleFn = Outline._rescaleAndSwap; 24139 tx = -pageY / pageHeight; 24140 ty = -pageX / pageWidth; 24141 sx = 1 / pageHeight; 24142 sy = 1 / pageWidth; 24143 break; 24144 case 180: 24145 rescaleFn = Outline._rescale; 24146 tx = pageX / pageWidth + 1; 24147 ty = -pageY / pageHeight; 24148 sx = -1 / pageWidth; 24149 sy = 1 / pageHeight; 24150 break; 24151 case 270: 24152 rescaleFn = Outline._rescaleAndSwap; 24153 tx = pageY / pageHeight + 1; 24154 ty = pageX / pageWidth + 1; 24155 sx = -1 / pageHeight; 24156 sy = -1 / pageWidth; 24157 break; 24158 } 24159 if (!lines) { 24160 lines = []; 24161 for (const point of points) { 24162 const len = point.length; 24163 if (len === 2) { 24164 lines.push(new Float32Array([NaN, NaN, NaN, NaN, point[0], point[1]])); 24165 continue; 24166 } 24167 if (len === 4) { 24168 lines.push(new Float32Array([NaN, NaN, NaN, NaN, point[0], point[1], NaN, NaN, NaN, NaN, point[2], point[3]])); 24169 continue; 24170 } 24171 const line = new Float32Array(3 * (len - 2)); 24172 lines.push(line); 24173 let [x1, y1, x2, y2] = point.subarray(0, 4); 24174 line.set([NaN, NaN, NaN, NaN, x1, y1], 0); 24175 for (let i = 4; i < len; i += 2) { 24176 const x = point[i]; 24177 const y = point[i + 1]; 24178 line.set(Outline.createBezierPoints(x1, y1, x2, y2, x, y), (i - 2) * 3); 24179 [x1, y1, x2, y2] = [x2, y2, x, y]; 24180 } 24181 } 24182 } 24183 for (let i = 0, ii = lines.length; i < ii; i++) { 24184 newLines.push({ 24185 line: rescaleFn(lines[i].map(
vendor: 1,901 bytes, lines 24185-24237
24185x => x ?? NaN), tx, ty, sx, sy), 24186 points: rescaleFn(points[i].map(x => x ?? NaN), tx, ty, sx, sy) 24187 }); 24188 } 24189 const outlines = new this.prototype.constructor(); 24190 outlines.build(newLines, pageWidth, pageHeight, 1, rotation, thickness, innerMargin); 24191 return outlines; 24192 } 24193 #getMarginComponents(thickness = this.#thickness) { 24194 const margin = this.#innerMargin + thickness / 2 * this.#parentScale; 24195 return this.#rotation % 180 === 0 ? [margin / this.#parentWidth, margin / this.#parentHeight] : [margin / this.#parentHeight, margin / this.#parentWidth]; 24196 } 24197 #getBBoxWithNoMargin() { 24198 const [x, y, width, height] = this.#bbox; 24199 const [marginX, marginY] = this.#getMarginComponents(0); 24200 return [x + marginX, y + marginY, width - 2 * marginX, height - 2 * marginY]; 24201 } 24202 #computeBbox() { 24203 const bbox = this.#bbox = F32_BBOX_INIT.slice(); 24204 for (const { 24205 line 24206 } of this.#lines) { 24207 if (line.length <= 12) { 24208 for (let i = 4, ii = line.length; i < ii; i += 6) { 24209 Util.pointBoundingBox(line[i], line[i + 1], bbox); 24210 } 24211 continue; 24212 } 24213 let lastX = line[4], 24214 lastY = line[5]; 24215 for (let i = 6, ii = line.length; i < ii; i += 6) { 24216 const [c1x, c1y, c2x, c2y, x, y] = line.subarray(i, i + 6); 24217 Util.bezierBoundingBox(lastX, lastY, c1x, c1y, c2x, c2y, x, y, bbox); 24218 lastX = x; 24219 lastY = y; 24220 } 24221 } 24222 const [marginX, marginY] = this.#getMarginComponents(); 24223 bbox[0] = MathClamp(bbox[0] - marginX, 0, 1); 24224 bbox[1] = MathClamp(bbox[1] - marginY, 0, 1); 24225 bbox[2] = MathClamp(bbox[2] + marginX, 0, 1); 24226 bbox[3] = MathClamp(bbox[3] + marginY, 0, 1); 24227 bbox[2] -= bbox[0]; 24228 bbox[3] -= bbox[1]; 24229 } 24230 get box() { 24231 return this.#bbox; 24232 } 24233 updateProperty(name, value) { 24234 if (name === "stroke-width") { 24235 return this.#updateThickness(value); 24236 } 24237 return
vendor: 8,392 bytes, lines 24237-24515
24237 null; 24238 } 24239 #updateThickness(thickness) { 24240 const [oldMarginX, oldMarginY] = this.#getMarginComponents(); 24241 this.#thickness = thickness; 24242 const [newMarginX, newMarginY] = this.#getMarginComponents(); 24243 const [diffMarginX, diffMarginY] = [newMarginX - oldMarginX, newMarginY - oldMarginY]; 24244 const bbox = this.#bbox; 24245 bbox[0] -= diffMarginX; 24246 bbox[1] -= diffMarginY; 24247 bbox[2] += 2 * diffMarginX; 24248 bbox[3] += 2 * diffMarginY; 24249 return bbox; 24250 } 24251 updateParentDimensions([width, height], scale) { 24252 const [oldMarginX, oldMarginY] = this.#getMarginComponents(); 24253 this.#parentWidth = width; 24254 this.#parentHeight = height; 24255 this.#parentScale = scale; 24256 const [newMarginX, newMarginY] = this.#getMarginComponents(); 24257 const diffMarginX = newMarginX - oldMarginX; 24258 const diffMarginY = newMarginY - oldMarginY; 24259 const bbox = this.#bbox; 24260 bbox[0] -= diffMarginX; 24261 bbox[1] -= diffMarginY; 24262 bbox[2] += 2 * diffMarginX; 24263 bbox[3] += 2 * diffMarginY; 24264 return bbox; 24265 } 24266 updateRotation(rotation) { 24267 this.#currentRotation = rotation; 24268 return { 24269 path: { 24270 transform: this.rotationTransform 24271 } 24272 }; 24273 } 24274 get viewBox() { 24275 return this.#bbox.map(Outline.svgRound).join(" "); 24276 } 24277 get defaultProperties() { 24278 const [x, y] = this.#bbox; 24279 return { 24280 root: { 24281 viewBox: this.viewBox 24282 }, 24283 path: { 24284 "transform-origin": `${Outline.svgRound(x)} ${Outline.svgRound(y)}` 24285 } 24286 }; 24287 } 24288 get rotationTransform() { 24289 const [,, width, height] = this.#bbox; 24290 let a = 0, 24291 b = 0, 24292 c = 0, 24293 d = 0, 24294 e = 0, 24295 f = 0; 24296 switch (this.#currentRotation) { 24297 case 90: 24298 b = height / width; 24299 c = -width / height; 24300 e = width; 24301 break; 24302 case 180: 24303 a = -1; 24304 d = -1; 24305 e = width; 24306 f = height; 24307 break; 24308 case 270: 24309 b = -height / width; 24310 c = width / height; 24311 f = height; 24312 break; 24313 default: 24314 return ""; 24315 } 24316 return `matrix(${a} ${b} ${c} ${d} ${Outline.svgRound(e)} ${Outline.svgRound(f)})`; 24317 } 24318 getPathResizingSVGProperties([newX, newY, newWidth, newHeight]) { 24319 const [marginX, marginY] = this.#getMarginComponents(); 24320 const [x, y, width, height] = this.#bbox; 24321 if (Math.abs(width - marginX) <= Outline.PRECISION || Math.abs(height - marginY) <= Outline.PRECISION) { 24322 const tx = newX + newWidth / 2 - (x + width / 2); 24323 const ty = newY + newHeight / 2 - (y + height / 2); 24324 return { 24325 path: { 24326 "transform-origin": `${Outline.svgRound(newX)} ${Outline.svgRound(newY)}`, 24327 transform: `${this.rotationTransform} translate(${tx} ${ty})` 24328 } 24329 }; 24330 } 24331 const s1x = (newWidth - 2 * marginX) / (width - 2 * marginX); 24332 const s1y = (newHeight - 2 * marginY) / (height - 2 * marginY); 24333 const s2x = width / newWidth; 24334 const s2y = height / newHeight; 24335 return { 24336 path: { 24337 "transform-origin": `${Outline.svgRound(x)} ${Outline.svgRound(y)}`, 24338 transform: `${this.rotationTransform} scale(${s2x} ${s2y}) ` + `translate(${Outline.svgRound(marginX)} ${Outline.svgRound(marginY)}) scale(${s1x} ${s1y}) ` + `translate(${Outline.svgRound(-marginX)} ${Outline.svgRound(-marginY)})` 24339 } 24340 }; 24341 } 24342 getPathResizedSVGProperties([newX, newY, newWidth, newHeight]) { 24343 const [marginX, marginY] = this.#getMarginComponents(); 24344 const bbox = this.#bbox; 24345 const [x, y, width, height] = bbox; 24346 bbox[0] = newX; 24347 bbox[1] = newY; 24348 bbox[2] = newWidth; 24349 bbox[3] = newHeight; 24350 if (Math.abs(width - marginX) <= Outline.PRECISION || Math.abs(height - marginY) <= Outline.PRECISION) { 24351 const tx = newX + newWidth / 2 - (x + width / 2); 24352 const ty = newY + newHeight / 2 - (y + height / 2); 24353 for (const { 24354 line, 24355 points 24356 } of this.#lines) { 24357 Outline._translate(line, tx, ty, line); 24358 Outline._translate(points, tx, ty, points); 24359 } 24360 return { 24361 root: { 24362 viewBox: this.viewBox 24363 }, 24364 path: { 24365 "transform-origin": `${Outline.svgRound(newX)} ${Outline.svgRound(newY)}`, 24366 transform: this.rotationTransform || null, 24367 d: this.toSVGPath() 24368 } 24369 }; 24370 } 24371 const s1x = (newWidth - 2 * marginX) / (width - 2 * marginX); 24372 const s1y = (newHeight - 2 * marginY) / (height - 2 * marginY); 24373 const tx = -s1x * (x + marginX) + newX + marginX; 24374 const ty = -s1y * (y + marginY) + newY + marginY; 24375 if (s1x !== 1 || s1y !== 1 || tx !== 0 || ty !== 0) { 24376 for (const { 24377 line, 24378 points 24379 } of this.#lines) { 24380 Outline._rescale(line, tx, ty, s1x, s1y, line); 24381 Outline._rescale(points, tx, ty, s1x, s1y, points); 24382 } 24383 } 24384 return { 24385 root: { 24386 viewBox: this.viewBox 24387 }, 24388 path: { 24389 "transform-origin": `${Outline.svgRound(newX)} ${Outline.svgRound(newY)}`, 24390 transform: this.rotationTransform || null, 24391 d: this.toSVGPath() 24392 } 24393 }; 24394 } 24395 getPathTranslatedSVGProperties([newX, newY], parentDimensions) { 24396 const [newParentWidth, newParentHeight] = parentDimensions; 24397 const bbox = this.#bbox; 24398 const tx = newX - bbox[0]; 24399 const ty = newY - bbox[1]; 24400 if (this.#parentWidth === newParentWidth && this.#parentHeight === newParentHeight) { 24401 for (const { 24402 line, 24403 points 24404 } of this.#lines) { 24405 Outline._translate(line, tx, ty, line); 24406 Outline._translate(points, tx, ty, points); 24407 } 24408 } else { 24409 const sx = this.#parentWidth / newParentWidth; 24410 const sy = this.#parentHeight / newParentHeight; 24411 this.#parentWidth = newParentWidth; 24412 this.#parentHeight = newParentHeight; 24413 for (const { 24414 line, 24415 points 24416 } of this.#lines) { 24417 Outline._rescale(line, tx, ty, sx, sy, line); 24418 Outline._rescale(points, tx, ty, sx, sy, points); 24419 } 24420 bbox[2] *= sx; 24421 bbox[3] *= sy; 24422 } 24423 bbox[0] = newX; 24424 bbox[1] = newY; 24425 return { 24426 root: { 24427 viewBox: this.viewBox 24428 }, 24429 path: { 24430 d: this.toSVGPath(), 24431 "transform-origin": `${Outline.svgRound(newX)} ${Outline.svgRound(newY)}` 24432 } 24433 }; 24434 } 24435 get defaultSVGProperties() { 24436 const bbox = this.#bbox; 24437 return { 24438 root: { 24439 viewBox: this.viewBox 24440 }, 24441 rootClass: { 24442 draw: true 24443 }, 24444 path: { 24445 d: this.toSVGPath(), 24446 "transform-origin": `${Outline.svgRound(bbox[0])} ${Outline.svgRound(bbox[1])}`, 24447 transform: this.rotationTransform || null 24448 }, 24449 bbox 24450 }; 24451 } 24452} 24453 24454;// ./src/display/editor/ink.js 24455 24456 24457 24458 24459 24460 24461class InkDrawingOptions extends DrawingOptions { 24462 constructor(viewerParameters) { 24463 super(); 24464 this._viewParameters = viewerParameters; 24465 super.updateProperties({ 24466 fill: "none", 24467 stroke: AnnotationEditor._defaultLineColor, 24468 "stroke-opacity": 1, 24469 "stroke-width": 1, 24470 "stroke-linecap": "round", 24471 "stroke-linejoin": "round", 24472 "stroke-miterlimit": 10 24473 }); 24474 } 24475 updateSVGProperty(name, value) { 24476 if (name === "stroke-width") { 24477 value ??= this["stroke-width"]; 24478 value *= this._viewParameters.realScale; 24479 } 24480 super.updateSVGProperty(name, value); 24481 } 24482 clone() { 24483 const clone = new InkDrawingOptions(this._viewParameters); 24484 clone.updateAll(this); 24485 return clone; 24486 } 24487} 24488class InkEditor extends DrawingEditor { 24489 static _type = "ink"; 24490 static _editorType = AnnotationEditorType.INK; 24491 static _defaultDrawingOptions = null; 24492 constructor(params) { 24493 super({ 24494 ...params, 24495 name: "inkEditor" 24496 }); 24497 this._willKeepAspectRatio = true; 24498 this.defaultL10nId = "pdfjs-editor-ink-editor"; 24499 } 24500 static initialize(l10n, uiManager) { 24501 AnnotationEditor.initialize(l10n, uiManager); 24502 this._defaultDrawingOptions = new InkDrawingOptions(uiManager.viewParameters); 24503 } 24504 static getDefaultDrawingOptions(options) { 24505 const clone = this._defaultDrawingOptions.clone(); 24506 clone.updateProperties(options); 24507 return clone; 24508 } 24509 static get supportMultipleDrawings() { 24510 return true; 24511 } 24512 static get typesMap() { 24513 return shadow(this, "typesMap", new Map([[AnnotationEditorParamsType.INK_THICKNESS, "stroke-width"], [AnnotationEditorParamsType.INK_COLOR, "stroke"], [AnnotationEditorParamsType.INK_OPACITY, "stroke-opacity"]])); 24514 } 24515 static createDrawerInstance(
24515x, y, parentWidth, parentHeight, rotation) { 24516 return new InkDrawOutliner(x, y, parentWidth, parentH
vendor: 29,877 bytes, lines 24516-25546
24516eight, rotation, this._defaultDrawingOptions["stroke-width"]); 24517 } 24518 static deserializeDraw(pageX, pageY, pageWidth, pageHeight, innerMargin, data) { 24519 return InkDrawOutline.deserialize(pageX, pageY, pageWidth, pageHeight, innerMargin, data); 24520 } 24521 static async deserialize(data, parent, uiManager) { 24522 let initialData = null; 24523 if (data instanceof InkAnnotationElement) { 24524 const { 24525 data: { 24526 inkLists, 24527 rect, 24528 rotation, 24529 id, 24530 color, 24531 opacity, 24532 borderStyle: { 24533 rawWidth: thickness 24534 }, 24535 popupRef, 24536 richText, 24537 contentsObj, 24538 creationDate, 24539 modificationDate 24540 }, 24541 parent: { 24542 page: { 24543 pageNumber 24544 } 24545 } 24546 } = data; 24547 initialData = data = { 24548 annotationType: AnnotationEditorType.INK, 24549 color: Array.from(color), 24550 thickness, 24551 opacity, 24552 paths: { 24553 points: inkLists 24554 }, 24555 boxes: null, 24556 pageIndex: pageNumber - 1, 24557 rect: rect.slice(0), 24558 rotation, 24559 annotationElementId: id, 24560 id, 24561 deleted: false, 24562 popupRef, 24563 richText, 24564 comment: contentsObj?.str || null, 24565 creationDate, 24566 modificationDate 24567 }; 24568 } 24569 const editor = await super.deserialize(data, parent, uiManager); 24570 editor._initialData = initialData; 24571 if (data.comment) { 24572 editor.setCommentData(data); 24573 } 24574 return editor; 24575 } 24576 get toolbarButtons() { 24577 this._colorPicker ||= new BasicColorPicker(this); 24578 return [["colorPicker", this._colorPicker]]; 24579 } 24580 get colorType() { 24581 return AnnotationEditorParamsType.INK_COLOR; 24582 } 24583 get colorAndOpacityType() { 24584 return AnnotationEditorParamsType.INK_COLOR_AND_OPACITY; 24585 } 24586 get opacityType() { 24587 return AnnotationEditorParamsType.INK_OPACITY; 24588 } 24589 updateParams(type, value) { 24590 if (type === AnnotationEditorParamsType.INK_COLOR_AND_OPACITY) { 24591 this._updateColorAndOpacity(value.color, value.opacity); 24592 return; 24593 } 24594 super.updateParams(type, value); 24595 } 24596 static updateDefaultParams(type, value) { 24597 if (type === AnnotationEditorParamsType.INK_COLOR_AND_OPACITY) { 24598 super.updateDefaultParams(AnnotationEditorParamsType.INK_COLOR, value.color); 24599 super.updateDefaultParams(AnnotationEditorParamsType.INK_OPACITY, value.opacity); 24600 return; 24601 } 24602 super.updateDefaultParams(type, value); 24603 } 24604 get color() { 24605 return this._drawingOptions.stroke; 24606 } 24607 get opacity() { 24608 return this._drawingOptions["stroke-opacity"]; 24609 } 24610 onScaleChanging() { 24611 if (!this.parent) { 24612 return; 24613 } 24614 super.onScaleChanging(); 24615 const { 24616 _drawId, 24617 _drawingOptions, 24618 parent 24619 } = this; 24620 _drawingOptions.updateSVGProperty("stroke-width"); 24621 parent.drawLayer.updateProperties(_drawId, _drawingOptions.toSVGProperties()); 24622 } 24623 static onScaleChangingWhenDrawing() { 24624 const parent = this._currentParent; 24625 if (!parent) { 24626 return; 24627 } 24628 super.onScaleChangingWhenDrawing(); 24629 this._defaultDrawingOptions.updateSVGProperty("stroke-width"); 24630 parent.drawLayer.updateProperties(this._currentDrawId, this._defaultDrawingOptions.toSVGProperties()); 24631 } 24632 createDrawingOptions({ 24633 color, 24634 thickness, 24635 opacity 24636 }) { 24637 this._drawingOptions = InkEditor.getDefaultDrawingOptions({ 24638 stroke: Util.makeHexColor(...color), 24639 "stroke-width": thickness, 24640 "stroke-opacity": opacity 24641 }); 24642 } 24643 serialize(isForCopying = false) { 24644 if (this.isEmpty()) { 24645 return null; 24646 } 24647 if (this.deleted) { 24648 return this.serializeDeleted(); 24649 } 24650 const { 24651 lines, 24652 points 24653 } = this.serializeDraw(isForCopying); 24654 const { 24655 _drawingOptions: { 24656 stroke, 24657 "stroke-opacity": opacity, 24658 "stroke-width": thickness 24659 } 24660 } = this; 24661 const serialized = Object.assign(super.serialize(isForCopying), { 24662 color: AnnotationEditor._colorManager.convert(stroke), 24663 opacity, 24664 thickness, 24665 paths: { 24666 lines, 24667 points 24668 } 24669 }); 24670 this.addComment(serialized); 24671 if (isForCopying) { 24672 serialized.isCopy = true; 24673 return serialized; 24674 } 24675 if (this.annotationElementId && !this.#hasElementChanged(serialized)) { 24676 return null; 24677 } 24678 serialized.id = this.annotationElementId; 24679 return serialized; 24680 } 24681 #hasElementChanged(serialized) { 24682 const { 24683 color, 24684 thickness, 24685 opacity, 24686 pageIndex 24687 } = this._initialData; 24688 return this.hasEditedComment || this._hasBeenMoved || this._hasBeenResized || serialized.color.some((c, i) => c !== color[i]) || serialized.thickness !== thickness || serialized.opacity !== opacity || serialized.pageIndex !== pageIndex; 24689 } 24690 renderAnnotationElement(annotation) { 24691 if (this.deleted) { 24692 annotation.hide(); 24693 return null; 24694 } 24695 const { 24696 points, 24697 rect 24698 } = this.serializeDraw(false); 24699 annotation.updateEdited({ 24700 rect, 24701 thickness: this._drawingOptions["stroke-width"], 24702 points, 24703 popup: this.comment 24704 }); 24705 return null; 24706 } 24707} 24708 24709;// ./src/display/editor/drawers/contour.js 24710 24711class ContourDrawOutline extends InkDrawOutline { 24712 toSVGPath() { 24713 let path = super.toSVGPath(); 24714 if (!path.endsWith("Z")) { 24715 path += "Z"; 24716 } 24717 return path; 24718 } 24719} 24720 24721;// ./src/display/editor/drawers/signaturedraw.js 24722 24723 24724 24725 24726const BASE_HEADER_LENGTH = 8; 24727const POINTS_PROPERTIES_NUMBER = 3; 24728class SignatureExtractor { 24729 static #PARAMETERS = { 24730 maxDim: 512, 24731 sigmaSFactor: 0.02, 24732 sigmaR: 25, 24733 kernelSize: 16 24734 }; 24735 static #neighborIndexToId(i0, j0, i, j) { 24736 i -= i0; 24737 j -= j0; 24738 if (i === 0) { 24739 return j > 0 ? 0 : 4; 24740 } 24741 if (i === 1) { 24742 return j + 6; 24743 } 24744 return 2 - j; 24745 } 24746 static #neighborIdToIndex = new Int32Array([0, 1, -1, 1, -1, 0, -1, -1, 0, -1, 1, -1, 1, 0, 1, 1]); 24747 static #clockwiseNonZero(buf, width, i0, j0, i, j, offset) { 24748 const id = this.#neighborIndexToId(i0, j0, i, j); 24749 for (let k = 0; k < 8; k++) { 24750 const kk = (-k + id - offset + 16) % 8; 24751 const shiftI = this.#neighborIdToIndex[2 * kk]; 24752 const shiftJ = this.#neighborIdToIndex[2 * kk + 1]; 24753 if (buf[(i0 + shiftI) * width + (j0 + shiftJ)] !== 0) { 24754 return kk; 24755 } 24756 } 24757 return -1; 24758 } 24759 static #counterClockwiseNonZero(buf, width, i0, j0, i, j, offset) { 24760 const id = this.#neighborIndexToId(i0, j0, i, j); 24761 for (let k = 0; k < 8; k++) { 24762 const kk = (k + id + offset + 16) % 8; 24763 const shiftI = this.#neighborIdToIndex[2 * kk]; 24764 const shiftJ = this.#neighborIdToIndex[2 * kk + 1]; 24765 if (buf[(i0 + shiftI) * width + (j0 + shiftJ)] !== 0) { 24766 return kk; 24767 } 24768 } 24769 return -1; 24770 } 24771 static #findContours(buf, width, height, threshold) { 24772 const N = buf.length; 24773 const types = new Int32Array(N); 24774 for (let i = 0; i < N; i++) { 24775 types[i] = buf[i] <= threshold ? 1 : 0; 24776 } 24777 for (let i = 1; i < height - 1; i++) { 24778 types[i * width] = types[i * width + width - 1] = 0; 24779 } 24780 for (let i = 0; i < width; i++) { 24781 types[i] = types[width * height - 1 - i] = 0; 24782 } 24783 let nbd = 1; 24784 let lnbd; 24785 const contours = []; 24786 for (let i = 1; i < height - 1; i++) { 24787 lnbd = 1; 24788 for (let j = 1; j < width - 1; j++) { 24789 const ij = i * width + j; 24790 const pix = types[ij]; 24791 if (pix === 0) { 24792 continue; 24793 } 24794 let i2 = i; 24795 let j2 = j; 24796 if (pix === 1 && types[ij - 1] === 0) { 24797 nbd += 1; 24798 j2 -= 1; 24799 } else if (pix >= 1 && types[ij + 1] === 0) { 24800 nbd += 1; 24801 j2 += 1; 24802 if (pix > 1) { 24803 lnbd = pix; 24804 } 24805 } else { 24806 if (pix !== 1) { 24807 lnbd = Math.abs(pix); 24808 } 24809 continue; 24810 } 24811 const points = [j, i]; 24812 const isHole = j2 === j + 1; 24813 const contour = { 24814 isHole, 24815 points, 24816 id: nbd, 24817 parent: 0 24818 }; 24819 contours.push(contour); 24820 let contour0; 24821 for (const c of contours) { 24822 if (c.id === lnbd) { 24823 contour0 = c; 24824 break; 24825 } 24826 } 24827 if (!contour0) { 24828 contour.parent = isHole ? lnbd : 0; 24829 } else if (contour0.isHole) { 24830 contour.parent = isHole ? contour0.parent : lnbd; 24831 } else { 24832 contour.parent = isHole ? lnbd : contour0.parent; 24833 } 24834 const k = this.#clockwiseNonZero(types, width, i, j, i2, j2, 0); 24835 if (k === -1) { 24836 types[ij] = -nbd; 24837 if (types[ij] !== 1) { 24838 lnbd = Math.abs(types[ij]); 24839 } 24840 continue; 24841 } 24842 let shiftI = this.#neighborIdToIndex[2 * k]; 24843 let shiftJ = this.#neighborIdToIndex[2 * k + 1]; 24844 const i1 = i + shiftI; 24845 const j1 = j + shiftJ; 24846 i2 = i1; 24847 j2 = j1; 24848 let i3 = i; 24849 let j3 = j; 24850 while (true) { 24851 const kk = this.#counterClockwiseNonZero(types, width, i3, j3, i2, j2, 1); 24852 shiftI = this.#neighborIdToIndex[2 * kk]; 24853 shiftJ = this.#neighborIdToIndex[2 * kk + 1]; 24854 const i4 = i3 + shiftI; 24855 const j4 = j3 + shiftJ; 24856 points.push(j4, i4); 24857 const ij3 = i3 * width + j3; 24858 if (types[ij3 + 1] === 0) { 24859 types[ij3] = -nbd; 24860 } else if (types[ij3] === 1) { 24861 types[ij3] = nbd; 24862 } 24863 if (i4 === i && j4 === j && i3 === i1 && j3 === j1) { 24864 if (types[ij] !== 1) { 24865 lnbd = Math.abs(types[ij]); 24866 } 24867 break; 24868 } else { 24869 i2 = i3; 24870 j2 = j3; 24871 i3 = i4; 24872 j3 = j4; 24873 } 24874 } 24875 } 24876 } 24877 return contours; 24878 } 24879 static #douglasPeuckerHelper(points, start, end, output) { 24880 if (end - start <= 4) { 24881 for (let i = start; i < end - 2; i += 2) { 24882 output.push(points[i], points[i + 1]); 24883 } 24884 return; 24885 } 24886 const ax = points[start]; 24887 const ay = points[start + 1]; 24888 const abx = points[end - 4] - ax; 24889 const aby = points[end - 3] - ay; 24890 const dist = Math.hypot(abx, aby); 24891 const nabx = abx / dist; 24892 const naby = aby / dist; 24893 const aa = nabx * ay - naby * ax; 24894 const m = aby / abx; 24895 const invS = 1 / dist; 24896 const phi = Math.atan(m); 24897 const cosPhi = Math.cos(phi); 24898 const sinPhi = Math.sin(phi); 24899 const tmax = invS * (Math.abs(cosPhi) + Math.abs(sinPhi)); 24900 const poly = invS * (1 - tmax + tmax ** 2); 24901 const partialPhi = Math.max(Math.atan(Math.abs(sinPhi + cosPhi) * poly), Math.atan(Math.abs(sinPhi - cosPhi) * poly)); 24902 let dmax = 0; 24903 let index = start; 24904 for (let i = start + 2; i < end - 2; i += 2) { 24905 const d = Math.abs(aa - nabx * points[i + 1] + naby * points[i]); 24906 if (d > dmax) { 24907 index = i; 24908 dmax = d; 24909 } 24910 } 24911 if (dmax > (dist * partialPhi) ** 2) { 24912 this.#douglasPeuckerHelper(points, start, index + 2, output); 24913 this.#douglasPeuckerHelper(points, index, end, output); 24914 } else { 24915 output.push(ax, ay); 24916 } 24917 } 24918 static #douglasPeucker(points) { 24919 const output = []; 24920 const len = points.length; 24921 this.#douglasPeuckerHelper(points, 0, len, output); 24922 output.push(points[len - 2], points[len - 1]); 24923 return output.length <= 4 ? null : output; 24924 } 24925 static #bilateralFilter(buf, width, height, sigmaS, sigmaR, kernelSize) { 24926 const kernel = new Float32Array(kernelSize ** 2); 24927 const sigmaS2 = -2 * sigmaS ** 2; 24928 const halfSize = kernelSize >> 1; 24929 for (let i = 0; i < kernelSize; i++) { 24930 const x = (i - halfSize) ** 2; 24931 for (let j = 0; j < kernelSize; j++) { 24932 kernel[i * kernelSize + j] = Math.exp((x + (j - halfSize) ** 2) / sigmaS2); 24933 } 24934 } 24935 const rangeValues = new Float32Array(256); 24936 const sigmaR2 = -2 * sigmaR ** 2; 24937 for (let i = 0; i < 256; i++) { 24938 rangeValues[i] = Math.exp(i ** 2 / sigmaR2); 24939 } 24940 const N = buf.length; 24941 const out = new Uint8Array(N); 24942 const histogram = new Uint32Array(256); 24943 for (let i = 0; i < height; i++) { 24944 for (let j = 0; j < width; j++) { 24945 const ij = i * width + j; 24946 const center = buf[ij]; 24947 let sum = 0; 24948 let norm = 0; 24949 for (let k = 0; k < kernelSize; k++) { 24950 const y = i + k - halfSize; 24951 if (y < 0 || y >= height) { 24952 continue; 24953 } 24954 for (let l = 0; l < kernelSize; l++) { 24955 const x = j + l - halfSize; 24956 if (x < 0 || x >= width) { 24957 continue; 24958 } 24959 const neighbour = buf[y * width + x]; 24960 const w = kernel[k * kernelSize + l] * rangeValues[Math.abs(neighbour - center)]; 24961 sum += neighbour * w; 24962 norm += w; 24963 } 24964 } 24965 const pix = out[ij] = Math.round(sum / norm); 24966 histogram[pix]++; 24967 } 24968 } 24969 return [out, histogram]; 24970 } 24971 static #getHistogram(buf) { 24972 const histogram = new Uint32Array(256); 24973 for (const g of buf) { 24974 histogram[g]++; 24975 } 24976 return histogram; 24977 } 24978 static #toUint8(buf) { 24979 const N = buf.length; 24980 const out = new Uint8ClampedArray(N >> 2); 24981 let max = -Infinity; 24982 let min = Infinity; 24983 for (let i = 0, ii = out.length; i < ii; i++) { 24984 const pix = out[i] = buf[i << 2]; 24985 max = Math.max(max, pix); 24986 min = Math.min(min, pix); 24987 } 24988 const ratio = 255 / (max - min); 24989 for (let i = 0, ii = out.length; i < ii; i++) { 24990 out[i] = (out[i] - min) * ratio; 24991 } 24992 return out; 24993 } 24994 static #guessThreshold(histogram) { 24995 let i; 24996 let M = -Infinity; 24997 let L = -Infinity; 24998 const min = histogram.findIndex(v => v !== 0); 24999 let pos = min; 25000 let spos = min; 25001 for (i = min; i < 256; i++) { 25002 const v = histogram[i]; 25003 if (v > M) { 25004 if (i - pos > L) { 25005 L = i - pos; 25006 spos = i - 1; 25007 } 25008 M = v; 25009 pos = i; 25010 } 25011 } 25012 for (i = spos - 1; i >= 0; i--) { 25013 if (histogram[i] > histogram[i + 1]) { 25014 break; 25015 } 25016 } 25017 return i; 25018 } 25019 static #getGrayPixels(bitmap) { 25020 const originalBitmap = bitmap; 25021 const { 25022 width, 25023 height 25024 } = bitmap; 25025 const { 25026 maxDim 25027 } = this.#PARAMETERS; 25028 let newWidth = width; 25029 let newHeight = height; 25030 if (width > maxDim || height > maxDim) { 25031 let prevWidth = width; 25032 let prevHeight = height; 25033 let steps = Math.log2(Math.max(width, height) / maxDim); 25034 const isteps = Math.floor(steps); 25035 steps = steps === isteps ? isteps - 1 : isteps; 25036 for (let i = 0; i < steps; i++) { 25037 newWidth = Math.ceil(prevWidth / 2); 25038 newHeight = Math.ceil(prevHeight / 2); 25039 const offscreen = new OffscreenCanvas(newWidth, newHeight); 25040 const ctx = offscreen.getContext("2d"); 25041 ctx.drawImage(bitmap, 0, 0, prevWidth, prevHeight, 0, 0, newWidth, newHeight); 25042 prevWidth = newWidth; 25043 prevHeight = newHeight; 25044 if (bitmap !== originalBitmap) { 25045 bitmap.close(); 25046 } 25047 bitmap = offscreen.transferToImageBitmap(); 25048 } 25049 const ratio = Math.min(maxDim / newWidth, maxDim / newHeight); 25050 newWidth = Math.round(newWidth * ratio); 25051 newHeight = Math.round(newHeight * ratio); 25052 } 25053 const offscreen = new OffscreenCanvas(newWidth, newHeight); 25054 const ctx = offscreen.getContext("2d", { 25055 willReadFrequently: true 25056 }); 25057 ctx.fillStyle = "white"; 25058 ctx.fillRect(0, 0, newWidth, newHeight); 25059 ctx.filter = "grayscale(1)"; 25060 ctx.drawImage(bitmap, 0, 0, bitmap.width, bitmap.height, 0, 0, newWidth, newHeight); 25061 const grayImage = ctx.getImageData(0, 0, newWidth, newHeight).data; 25062 const uint8Buf = this.#toUint8(grayImage); 25063 return [uint8Buf, newWidth, newHeight]; 25064 } 25065 static extractContoursFromText(text, { 25066 fontFamily, 25067 fontStyle, 25068 fontWeight 25069 }, pageWidth, pageHeight, rotation, innerMargin) { 25070 let canvas = new OffscreenCanvas(1, 1); 25071 let ctx = canvas.getContext("2d", { 25072 alpha: false 25073 }); 25074 const fontSize = 200; 25075 const font = ctx.font = `${fontStyle} ${fontWeight} ${fontSize}px ${fontFamily}`; 25076 const { 25077 actualBoundingBoxLeft, 25078 actualBoundingBoxRight, 25079 actualBoundingBoxAscent, 25080 actualBoundingBoxDescent, 25081 fontBoundingBoxAscent, 25082 fontBoundingBoxDescent, 25083 width 25084 } = ctx.measureText(text); 25085 const SCALE = 1.5; 25086 const canvasWidth = Math.ceil(Math.max(Math.abs(actualBoundingBoxLeft) + Math.abs(actualBoundingBoxRight) || 0, width) * SCALE); 25087 const canvasHeight = Math.ceil(Math.max(Math.abs(actualBoundingBoxAscent) + Math.abs(actualBoundingBoxDescent) || fontSize, Math.abs(fontBoundingBoxAscent) + Math.abs(fontBoundingBoxDescent) || fontSize) * SCALE); 25088 canvas = new OffscreenCanvas(canvasWidth, canvasHeight); 25089 ctx = canvas.getContext("2d", { 25090 alpha: true, 25091 willReadFrequently: true 25092 }); 25093 ctx.font = font; 25094 ctx.filter = "grayscale(1)"; 25095 ctx.fillStyle = "white"; 25096 ctx.fillRect(0, 0, canvasWidth, canvasHeight); 25097 ctx.fillStyle = "black"; 25098 ctx.fillText(text, canvasWidth * (SCALE - 1) / 2, canvasHeight * (3 - SCALE) / 2); 25099 const uint8Buf = this.#toUint8(ctx.getImageData(0, 0, canvasWidth, canvasHeight).data); 25100 const histogram = this.#getHistogram(uint8Buf); 25101 const threshold = this.#guessThreshold(histogram); 25102 const contourList = this.#findContours(uint8Buf, canvasWidth, canvasHeight, threshold); 25103 return this.processDrawnLines({ 25104 lines: { 25105 curves: contourList, 25106 width: canvasWidth, 25107 height: canvasHeight 25108 }, 25109 pageWidth, 25110 pageHeight, 25111 rotation, 25112 innerMargin, 25113 mustSmooth: true, 25114 areContours: true 25115 }); 25116 } 25117 static process(bitmap, pageWidth, pageHeight, rotation, innerMargin) { 25118 const [uint8Buf, width, height] = this.#getGrayPixels(bitmap); 25119 const [buffer, histogram] = this.#bilateralFilter(uint8Buf, width, height, Math.hypot(width, height) * this.#PARAMETERS.sigmaSFactor, this.#PARAMETERS.sigmaR, this.#PARAMETERS.kernelSize); 25120 const threshold = this.#guessThreshold(histogram); 25121 const contourList = this.#findContours(buffer, width, height, threshold); 25122 return this.processDrawnLines({ 25123 lines: { 25124 curves: contourList, 25125 width, 25126 height 25127 }, 25128 pageWidth, 25129 pageHeight, 25130 rotation, 25131 innerMargin, 25132 mustSmooth: true, 25133 areContours: true 25134 }); 25135 } 25136 static processDrawnLines({ 25137 lines, 25138 pageWidth, 25139 pageHeight, 25140 rotation, 25141 innerMargin, 25142 mustSmooth, 25143 areContours 25144 }) { 25145 if (rotation % 180 !== 0) { 25146 [pageWidth, pageHeight] = [pageHeight, pageWidth]; 25147 } 25148 const { 25149 curves, 25150 width, 25151 height 25152 } = lines; 25153 const thickness = lines.thickness ?? 0; 25154 const linesAndPoints = []; 25155 const ratio = Math.min(pageWidth / width, pageHeight / height); 25156 const xScale = ratio / pageWidth; 25157 const yScale = ratio / pageHeight; 25158 const newCurves = []; 25159 for (const { 25160 points 25161 } of curves) { 25162 const reducedPoints = mustSmooth ? this.#douglasPeucker(points) : points; 25163 if (!reducedPoints) { 25164 continue; 25165 } 25166 newCurves.push(reducedPoints); 25167 const len = reducedPoints.length; 25168 const newPoints = new Float32Array(len); 25169 const line = new Float32Array(3 * (len === 2 ? 2 : len - 2)); 25170 linesAndPoints.push({ 25171 line, 25172 points: newPoints 25173 }); 25174 if (len === 2) { 25175 newPoints[0] = reducedPoints[0] * xScale; 25176 newPoints[1] = reducedPoints[1] * yScale; 25177 line.set([NaN, NaN, NaN, NaN, newPoints[0], newPoints[1]], 0); 25178 continue; 25179 } 25180 let [x1, y1, x2, y2] = reducedPoints; 25181 x1 *= xScale; 25182 y1 *= yScale; 25183 x2 *= xScale; 25184 y2 *= yScale; 25185 newPoints.set([x1, y1, x2, y2], 0); 25186 line.set([NaN, NaN, NaN, NaN, x1, y1], 0); 25187 for (let i = 4; i < len; i += 2) { 25188 const x = newPoints[i] = reducedPoints[i] * xScale; 25189 const y = newPoints[i + 1] = reducedPoints[i + 1] * yScale; 25190 line.set(Outline.createBezierPoints(x1, y1, x2, y2, x, y), (i - 2) * 3); 25191 [x1, y1, x2, y2] = [x2, y2, x, y]; 25192 } 25193 } 25194 if (linesAndPoints.length === 0) { 25195 return null; 25196 } 25197 const outline = areContours ? new ContourDrawOutline() : new InkDrawOutline(); 25198 outline.build(linesAndPoints, pageWidth, pageHeight, 1, rotation, areContours ? 0 : thickness, innerMargin); 25199 return { 25200 outline, 25201 newCurves, 25202 areContours, 25203 thickness, 25204 width, 25205 height 25206 }; 25207 } 25208 static async compressSignature({ 25209 outlines, 25210 areContours, 25211 thickness, 25212 width, 25213 height 25214 }) { 25215 let minDiff = Infinity; 25216 let maxDiff = -Infinity; 25217 let outlinesLength = 0; 25218 for (const points of outlines) { 25219 outlinesLength += points.length; 25220 for (let i = 2, ii = points.length; i < ii; i++) { 25221 const dx = points[i] - points[i - 2]; 25222 minDiff = Math.min(minDiff, dx); 25223 maxDiff = Math.max(maxDiff, dx); 25224 } 25225 } 25226 let bufferType; 25227 if (minDiff >= -128 && maxDiff <= 127) { 25228 bufferType = Int8Array; 25229 } else if (minDiff >= -32768 && maxDiff <= 32767) { 25230 bufferType = Int16Array; 25231 } else { 25232 bufferType = Int32Array; 25233 } 25234 const len = outlines.length; 25235 const headerLength = BASE_HEADER_LENGTH + POINTS_PROPERTIES_NUMBER * len; 25236 const header = new Uint32Array(headerLength); 25237 let offset = 0; 25238 header[offset++] = headerLength * Uint32Array.BYTES_PER_ELEMENT + (outlinesLength - 2 * len) * bufferType.BYTES_PER_ELEMENT; 25239 header[offset++] = 0; 25240 header[offset++] = width; 25241 header[offset++] = height; 25242 header[offset++] = areContours ? 0 : 1; 25243 header[offset++] = Math.max(0, Math.floor(thickness ?? 0)); 25244 header[offset++] = len; 25245 header[offset++] = bufferType.BYTES_PER_ELEMENT; 25246 for (const points of outlines) { 25247 header[offset++] = points.length - 2; 25248 header[offset++] = points[0]; 25249 header[offset++] = points[1]; 25250 } 25251 const cs = new CompressionStream("deflate-raw"); 25252 const writer = cs.writable.getWriter(); 25253 await writer.ready; 25254 writer.write(header); 25255 const BufferCtor = bufferType.prototype.constructor; 25256 for (const points of outlines) { 25257 const diffs = new BufferCtor(points.length - 2); 25258 for (let i = 2, ii = points.length; i < ii; i++) { 25259 diffs[i - 2] = points[i] - points[i - 2]; 25260 } 25261 writer.write(diffs); 25262 } 25263 writer.close(); 25264 const bytes = await new Response(cs.readable).bytes(); 25265 return bytes.toBase64(); 25266 } 25267 static async decompressSignature(signatureData) { 25268 try { 25269 const bytes = Uint8Array.fromBase64(signatureData); 25270 const { 25271 readable, 25272 writable 25273 } = new DecompressionStream("deflate-raw"); 25274 const writer = writable.getWriter(); 25275 await writer.ready; 25276 writer.write(bytes).then(async () => { 25277 await writer.ready; 25278 await writer.close(); 25279 }).catch(() => {}); 25280 let data = null; 25281 let offset = 0; 25282 for await (const chunk of readable) { 25283 data ||= new Uint8Array(new Uint32Array(chunk.buffer, 0, 4)[0]); 25284 data.set(chunk, offset); 25285 offset += chunk.length; 25286 } 25287 const header = new Uint32Array(data.buffer, 0, data.length >> 2); 25288 const version = header[1]; 25289 if (version !== 0) { 25290 throw new Error(`Invalid version: ${version}`); 25291 } 25292 const width = header[2]; 25293 const height = header[3]; 25294 const areContours = header[4] === 0; 25295 const thickness = header[5]; 25296 const numberOfDrawings = header[6]; 25297 const bufferType = header[7]; 25298 const outlines = []; 25299 const diffsOffset = (BASE_HEADER_LENGTH + POINTS_PROPERTIES_NUMBER * numberOfDrawings) * Uint32Array.BYTES_PER_ELEMENT; 25300 let diffs; 25301 switch (bufferType) { 25302 case Int8Array.BYTES_PER_ELEMENT: 25303 diffs = new Int8Array(data.buffer, diffsOffset); 25304 break; 25305 case Int16Array.BYTES_PER_ELEMENT: 25306 diffs = new Int16Array(data.buffer, diffsOffset); 25307 break; 25308 case Int32Array.BYTES_PER_ELEMENT: 25309 diffs = new Int32Array(data.buffer, diffsOffset); 25310 break; 25311 } 25312 offset = 0; 25313 for (let i = 0; i < numberOfDrawings; i++) { 25314 const len = header[POINTS_PROPERTIES_NUMBER * i + BASE_HEADER_LENGTH]; 25315 const points = new Float32Array(len + 2); 25316 outlines.push(points); 25317 for (let j = 0; j < POINTS_PROPERTIES_NUMBER - 1; j++) { 25318 points[j] = header[POINTS_PROPERTIES_NUMBER * i + BASE_HEADER_LENGTH + j + 1]; 25319 } 25320 for (let j = 0; j < len; j++) { 25321 points[j + 2] = points[j] + diffs[offset++]; 25322 } 25323 } 25324 return { 25325 areContours, 25326 thickness, 25327 outlines, 25328 width, 25329 height 25330 }; 25331 } catch (e) { 25332 warn(`decompressSignature: ${e}`); 25333 return null; 25334 } 25335 } 25336} 25337 25338;// ./src/display/editor/signature.js 25339 25340 25341 25342 25343 25344 25345 25346class SignatureOptions extends DrawingOptions { 25347 constructor() { 25348 super(); 25349 super.updateProperties({ 25350 fill: AnnotationEditor._defaultLineColor, 25351 "stroke-width": 0 25352 }); 25353 } 25354 clone() { 25355 const clone = new SignatureOptions(); 25356 clone.updateAll(this); 25357 return clone; 25358 } 25359} 25360class DrawnSignatureOptions extends InkDrawingOptions { 25361 constructor(viewerParameters) { 25362 super(viewerParameters); 25363 super.updateProperties({ 25364 stroke: AnnotationEditor._defaultLineColor, 25365 "stroke-width": 1 25366 }); 25367 } 25368 clone() { 25369 const clone = new DrawnSignatureOptions(this._viewParameters); 25370 clone.updateAll(this); 25371 return clone; 25372 } 25373} 25374class SignatureEditor extends DrawingEditor { 25375 #isExtracted = false; 25376 #description = null; 25377 #signatureData = null; 25378 #signatureUUID = null; 25379 static _type = "signature"; 25380 static _editorType = AnnotationEditorType.SIGNATURE; 25381 static _defaultDrawingOptions = null; 25382 constructor(params) { 25383 super({ 25384 ...params, 25385 mustBeCommitted: true, 25386 name: "signatureEditor" 25387 }); 25388 this._willKeepAspectRatio = true; 25389 this.#signatureData = params.signatureData || null; 25390 this.#description = null; 25391 this.defaultL10nId = "pdfjs-editor-signature-editor1"; 25392 } 25393 static initialize(l10n, uiManager) { 25394 AnnotationEditor.initialize(l10n, uiManager); 25395 this._defaultDrawingOptions = new SignatureOptions(); 25396 this._defaultDrawnSignatureOptions = new DrawnSignatureOptions(uiManager.viewParameters); 25397 } 25398 static getDefaultDrawingOptions(options) { 25399 const clone = this._defaultDrawingOptions.clone(); 25400 clone.updateProperties(options); 25401 return clone; 25402 } 25403 static get supportMultipleDrawings() { 25404 return false; 25405 } 25406 static get typesMap() { 25407 return shadow(this, "typesMap", new Map()); 25408 } 25409 static get isDrawer() { 25410 return false; 25411 } 25412 get telemetryFinalData() { 25413 return { 25414 type: "signature", 25415 hasDescription: !!this.#description 25416 }; 25417 } 25418 static computeTelemetryFinalData(data) { 25419 const hasDescriptionStats = data.get("hasDescription"); 25420 return { 25421 hasAltText: hasDescriptionStats.get(true) ?? 0, 25422 hasNoAltText: hasDescriptionStats.get(false) ?? 0 25423 }; 25424 } 25425 get isResizable() { 25426 return true; 25427 } 25428 onScaleChanging() { 25429 if (this._drawId === null) { 25430 return; 25431 } 25432 super.onScaleChanging(); 25433 } 25434 render() { 25435 if (this.div) { 25436 return this.div; 25437 } 25438 let baseX, baseY; 25439 const { 25440 _isCopy 25441 } = this; 25442 if (_isCopy) { 25443 this._isCopy = false; 25444 baseX = this.x; 25445 baseY = this.y; 25446 } 25447 super.render(); 25448 if (this._drawId === null) { 25449 if (this.#signatureData) { 25450 const { 25451 lines, 25452 mustSmooth, 25453 areContours, 25454 description, 25455 uuid, 25456 heightInPage 25457 } = this.#signatureData; 25458 const { 25459 rawDims: { 25460 pageWidth, 25461 pageHeight 25462 }, 25463 rotation 25464 } = this.parent.viewport; 25465 const outline = SignatureExtractor.processDrawnLines({ 25466 lines, 25467 pageWidth, 25468 pageHeight, 25469 rotation, 25470 innerMargin: SignatureEditor._INNER_MARGIN, 25471 mustSmooth, 25472 areContours 25473 }); 25474 this.addSignature(outline, heightInPage, description, uuid); 25475 } else { 25476 this.div.setAttribute("data-l10n-args", JSON.stringify({ 25477 description: "" 25478 })); 25479 this.div.hidden = true; 25480 this._uiManager.getSignature(this); 25481 } 25482 } else { 25483 this.div.setAttribute("data-l10n-args", JSON.stringify({ 25484 description: this.#description || "" 25485 })); 25486 } 25487 if (_isCopy) { 25488 this._isCopy = true; 25489 this._moveAfterPaste(baseX, baseY); 25490 } 25491 return this.div; 25492 } 25493 setUuid(uuid) { 25494 this.#signatureUUID = uuid; 25495 this.addEditToolbar(); 25496 } 25497 getUuid() { 25498 return this.#signatureUUID; 25499 } 25500 get description() { 25501 return this.#description; 25502 } 25503 set description(description) { 25504 this.#description = description; 25505 if (!this.div) { 25506 return; 25507 } 25508 this.div.setAttribute("data-l10n-args", JSON.stringify({ 25509 description 25510 })); 25511 super.addEditToolbar().then(toolbar => { 25512 toolbar?.updateEditSignatureButton(description); 25513 }); 25514 } 25515 getSignaturePreview() { 25516 const { 25517 newCurves, 25518 areContours, 25519 thickness, 25520 width, 25521 height 25522 } = this.#signatureData; 25523 const maxDim = Math.max(width, height); 25524 const outlineData = SignatureExtractor.processDrawnLines({ 25525 lines: { 25526 curves: newCurves.map(points => ({ 25527 points 25528 })), 25529 thickness, 25530 width, 25531 height 25532 }, 25533 pageWidth: maxDim, 25534 pageHeight: maxDim, 25535 rotation: 0, 25536 innerMargin: 0, 25537 mustSmooth: false, 25538 areContours 25539 }); 25540 return { 25541 areContours, 25542 outline: outlineData.outline 25543 }; 25544 } 25545 get toolbarButtons() { 25546
25546if (this._uiManager.signatureManager) { 25547 return [["editSignature", this._uiManager.signatureManager]]; 25548 } 25549 return
vendor: 13,925 bytes, lines 25549-26060
25549 super.toolbarButtons; 25550 } 25551 addSignature(data, heightInPage, description, uuid) { 25552 const { 25553 x: savedX, 25554 y: savedY 25555 } = this; 25556 const { 25557 outline 25558 } = this.#signatureData = data; 25559 this.#isExtracted = outline instanceof ContourDrawOutline; 25560 this.description = description; 25561 let drawingOptions; 25562 if (this.#isExtracted) { 25563 drawingOptions = SignatureEditor.getDefaultDrawingOptions(); 25564 } else { 25565 drawingOptions = SignatureEditor._defaultDrawnSignatureOptions.clone(); 25566 drawingOptions.updateProperties({ 25567 "stroke-width": outline.thickness 25568 }); 25569 } 25570 this._addOutlines({ 25571 drawOutlines: outline, 25572 drawingOptions 25573 }); 25574 const [, pageHeight] = this.pageDimensions; 25575 let newHeight = heightInPage / pageHeight; 25576 newHeight = newHeight >= 1 ? 0.5 : newHeight; 25577 this.width *= newHeight / this.height; 25578 if (this.width >= 1) { 25579 newHeight *= 0.9 / this.width; 25580 this.width = 0.9; 25581 } 25582 this.height = newHeight; 25583 this.setDims(); 25584 this.x = savedX; 25585 this.y = savedY; 25586 this.center(); 25587 this._onResized(); 25588 this.onScaleChanging(); 25589 this.rotate(); 25590 this._uiManager.addToAnnotationStorage(this); 25591 this.setUuid(uuid); 25592 this._reportTelemetry({ 25593 action: "pdfjs.signature.inserted", 25594 data: { 25595 hasBeenSaved: !!uuid, 25596 hasDescription: !!description 25597 } 25598 }); 25599 this.div.hidden = false; 25600 } 25601 getFromImage(bitmap) { 25602 const { 25603 rawDims: { 25604 pageWidth, 25605 pageHeight 25606 }, 25607 rotation 25608 } = this.parent.viewport; 25609 return SignatureExtractor.process(bitmap, pageWidth, pageHeight, rotation, SignatureEditor._INNER_MARGIN); 25610 } 25611 getFromText(text, fontInfo) { 25612 const { 25613 rawDims: { 25614 pageWidth, 25615 pageHeight 25616 }, 25617 rotation 25618 } = this.parent.viewport; 25619 return SignatureExtractor.extractContoursFromText(text, fontInfo, pageWidth, pageHeight, rotation, SignatureEditor._INNER_MARGIN); 25620 } 25621 getDrawnSignature(curves) { 25622 const { 25623 rawDims: { 25624 pageWidth, 25625 pageHeight 25626 }, 25627 rotation 25628 } = this.parent.viewport; 25629 return SignatureExtractor.processDrawnLines({ 25630 lines: curves, 25631 pageWidth, 25632 pageHeight, 25633 rotation, 25634 innerMargin: SignatureEditor._INNER_MARGIN, 25635 mustSmooth: false, 25636 areContours: false 25637 }); 25638 } 25639 createDrawingOptions({ 25640 areContours, 25641 thickness 25642 }) { 25643 if (areContours) { 25644 this._drawingOptions = SignatureEditor.getDefaultDrawingOptions(); 25645 } else { 25646 this._drawingOptions = SignatureEditor._defaultDrawnSignatureOptions.clone(); 25647 this._drawingOptions.updateProperties({ 25648 "stroke-width": thickness 25649 }); 25650 } 25651 } 25652 serialize(isForCopying = false) { 25653 if (this.isEmpty()) { 25654 return null; 25655 } 25656 const { 25657 lines, 25658 points 25659 } = this.serializeDraw(isForCopying); 25660 const { 25661 _drawingOptions: { 25662 "stroke-width": thickness 25663 } 25664 } = this; 25665 const serialized = Object.assign(super.serialize(isForCopying), { 25666 isSignature: true, 25667 areContours: this.#isExtracted, 25668 color: [0, 0, 0], 25669 thickness: this.#isExtracted ? 0 : thickness 25670 }); 25671 this.addComment(serialized); 25672 if (isForCopying) { 25673 serialized.paths = { 25674 lines, 25675 points 25676 }; 25677 serialized.uuid = this.#signatureUUID; 25678 serialized.isCopy = true; 25679 } else { 25680 serialized.lines = lines; 25681 } 25682 if (this.#description) { 25683 serialized.accessibilityData = { 25684 type: "Figure", 25685 alt: this.#description 25686 }; 25687 } 25688 return serialized; 25689 } 25690 static deserializeDraw(pageX, pageY, pageWidth, pageHeight, innerMargin, data) { 25691 if (data.areContours) { 25692 return ContourDrawOutline.deserialize(pageX, pageY, pageWidth, pageHeight, innerMargin, data); 25693 } 25694 return InkDrawOutline.deserialize(pageX, pageY, pageWidth, pageHeight, innerMargin, data); 25695 } 25696 static async deserialize(data, parent, uiManager) { 25697 const editor = await super.deserialize(data, parent, uiManager); 25698 editor.#isExtracted = data.areContours; 25699 editor.description = data.accessibilityData?.alt || ""; 25700 editor.#signatureUUID = data.uuid; 25701 return editor; 25702 } 25703} 25704 25705;// ./src/display/editor/stamp.js 25706 25707 25708 25709 25710class StampEditor extends AnnotationEditor { 25711 #bitmap = null; 25712 #bitmapId = null; 25713 #bitmapPromise = null; 25714 #bitmapUrl = null; 25715 #bitmapFile = null; 25716 #bitmapFileName = ""; 25717 #canvas = null; 25718 #missingCanvas = false; 25719 #resizeTimeoutId = null; 25720 #isSvg = false; 25721 #hasBeenAddedInUndoStack = false; 25722 static _type = "stamp"; 25723 static _editorType = AnnotationEditorType.STAMP; 25724 constructor(params) { 25725 super({ 25726 ...params, 25727 name: "stampEditor" 25728 }); 25729 this.#bitmapUrl = params.bitmapUrl; 25730 this.#bitmapFile = params.bitmapFile; 25731 this.defaultL10nId = "pdfjs-editor-stamp-editor"; 25732 } 25733 static initialize(l10n, uiManager) { 25734 AnnotationEditor.initialize(l10n, uiManager); 25735 } 25736 static isHandlingMimeForPasting(mime) { 25737 return SupportedImageMimeTypes.includes(mime); 25738 } 25739 static paste(item, parent) { 25740 parent.pasteEditor({ 25741 mode: AnnotationEditorType.STAMP 25742 }, { 25743 bitmapFile: item.getAsFile() 25744 }); 25745 } 25746 altTextFinish() { 25747 if (this._uiManager.useNewAltTextFlow) { 25748 this.div.hidden = false; 25749 } 25750 super.altTextFinish(); 25751 } 25752 get telemetryFinalData() { 25753 return { 25754 type: "stamp", 25755 hasAltText: !!this.altTextData?.altText 25756 }; 25757 } 25758 static computeTelemetryFinalData(data) { 25759 const hasAltTextStats = data.get("hasAltText"); 25760 return { 25761 hasAltText: hasAltTextStats.get(true) ?? 0, 25762 hasNoAltText: hasAltTextStats.get(false) ?? 0 25763 }; 25764 } 25765 #getBitmapFetched(data, fromId = false) { 25766 if (!data) { 25767 this.remove(); 25768 return; 25769 } 25770 this.#bitmap = data.bitmap; 25771 if (!fromId) { 25772 this.#bitmapId = data.id; 25773 this.#isSvg = data.isSvg; 25774 } 25775 if (data.file) { 25776 this.#bitmapFileName = data.file.name; 25777 } 25778 this.#createCanvas(); 25779 } 25780 #getBitmapDone() { 25781 this.#bitmapPromise = null; 25782 this._uiManager.enableWaiting(false); 25783 if (!this.#canvas) { 25784 return; 25785 } 25786 if (this._uiManager.useNewAltTextWhenAddingImage && this._uiManager.useNewAltTextFlow && this.#bitmap) { 25787 this.addEditToolbar().then(() => { 25788 this._editToolbar.hide(); 25789 this._uiManager.editAltText(this, true); 25790 }); 25791 return; 25792 } 25793 if (!this._uiManager.useNewAltTextWhenAddingImage && this._uiManager.useNewAltTextFlow && this.#bitmap) { 25794 this._reportTelemetry({ 25795 action: "pdfjs.image.image_added", 25796 data: { 25797 alt_text_modal: false, 25798 alt_text_type: "empty" 25799 } 25800 }); 25801 try { 25802 this.mlGuessAltText(); 25803 } catch {} 25804 } 25805 this.div.focus(); 25806 } 25807 async mlGuessAltText(imageData = null, updateAltTextData = true) { 25808 if (this.hasAltTextData()) { 25809 return null; 25810 } 25811 const { 25812 mlManager 25813 } = this._uiManager; 25814 if (!mlManager) { 25815 throw new Error("No ML."); 25816 } 25817 if (!(await mlManager.isEnabledFor("altText"))) { 25818 throw new Error("ML isn't enabled for alt text."); 25819 } 25820 const { 25821 data, 25822 width, 25823 height 25824 } = imageData || this.copyCanvas(null, null, true).imageData; 25825 const response = await mlManager.guess({ 25826 name: "altText", 25827 request: { 25828 data, 25829 width, 25830 height, 25831 channels: data.length / (width * height) 25832 } 25833 }); 25834 if (!response) { 25835 throw new Error("No response from the AI service."); 25836 } 25837 if (response.error) { 25838 throw new Error("Error from the AI service."); 25839 } 25840 if (response.cancel) { 25841 return null; 25842 } 25843 if (!response.output) { 25844 throw new Error("No valid response from the AI service."); 25845 } 25846 const altText = response.output; 25847 await this.setGuessedAltText(altText); 25848 if (updateAltTextData && !this.hasAltTextData()) { 25849 this.altTextData = { 25850 alt: altText, 25851 decorative: false 25852 }; 25853 } 25854 return altText; 25855 } 25856 #getBitmap() { 25857 if (this.#bitmapId) { 25858 this._uiManager.enableWaiting(true); 25859 this._uiManager.imageManager.getFromId(this.#bitmapId).then(data => this.#getBitmapFetched(data, true)).finally(() => this.#getBitmapDone()); 25860 return; 25861 } 25862 if (this.#bitmapUrl) { 25863 const url = this.#bitmapUrl; 25864 this.#bitmapUrl = null; 25865 this._uiManager.enableWaiting(true); 25866 this.#bitmapPromise = this._uiManager.imageManager.getFromUrl(url).then(data => this.#getBitmapFetched(data)).finally(() => this.#getBitmapDone()); 25867 return; 25868 } 25869 if (this.#bitmapFile) { 25870 const file = this.#bitmapFile; 25871 this.#bitmapFile = null; 25872 this._uiManager.enableWaiting(true); 25873 this.#bitmapPromise = this._uiManager.imageManager.getFromFile(file).then(data => this.#getBitmapFetched(data)).finally(() => this.#getBitmapDone()); 25874 return; 25875 } 25876 const input = document.createElement("input"); 25877 input.type = "file"; 25878 input.accept = SupportedImageMimeTypes.join(","); 25879 const signal = this._uiManager._signal; 25880 this.#bitmapPromise = new Promise(resolve => { 25881 input.addEventListener("change", async () => { 25882 if (!input.files || input.files.length === 0) { 25883 this.remove(); 25884 } else { 25885 this._uiManager.enableWaiting(true); 25886 const data = await this._uiManager.imageManager.getFromFile(input.files[0]); 25887 this._reportTelemetry({ 25888 action: "pdfjs.image.image_selected", 25889 data: { 25890 alt_text_modal: this._uiManager.useNewAltTextFlow 25891 } 25892 }); 25893 this.#getBitmapFetched(data); 25894 } 25895 resolve(); 25896 }, { 25897 signal 25898 }); 25899 input.addEventListener("cancel", () => { 25900 this.remove(); 25901 resolve(); 25902 }, { 25903 signal 25904 }); 25905 }).finally(() => this.#getBitmapDone()); 25906 input.click(); 25907 } 25908 remove() { 25909 if (this.#bitmapId) { 25910 this.#bitmap = null; 25911 this._uiManager.imageManager.deleteId(this.#bitmapId); 25912 this.#canvas?.remove(); 25913 this.#canvas = null; 25914 if (this.#resizeTimeoutId) { 25915 clearTimeout(this.#resizeTimeoutId); 25916 this.#resizeTimeoutId = null; 25917 } 25918 } 25919 super.remove(); 25920 } 25921 rebuild() { 25922 if (!this.parent) { 25923 if (this.#bitmapId) { 25924 this.#getBitmap(); 25925 } 25926 return; 25927 } 25928 super.rebuild(); 25929 if (this.div === null) { 25930 return; 25931 } 25932 if (this.#bitmapId && this.#canvas === null) { 25933 this.#getBitmap(); 25934 } 25935 if (!this.isAttachedToDOM) { 25936 this.parent.add(this); 25937 } 25938 } 25939 onceAdded(focus) { 25940 this._isDraggable = true; 25941 if (focus) { 25942 this.div.focus(); 25943 } 25944 } 25945 isEmpty() { 25946 return !(this.#bitmapPromise || this.#bitmap || this.#bitmapUrl || this.#bitmapFile || this.#bitmapId || this.#missingCanvas); 25947 } 25948 get toolbarButtons() { 25949 return [["altText", this.createAltText()]]; 25950 } 25951 get isResizable() { 25952 return true; 25953 } 25954 render() { 25955 if (this.div) { 25956 return this.div; 25957 } 25958 let baseX, baseY; 25959 if (this._isCopy) { 25960 baseX = this.x; 25961 baseY = this.y; 25962 } 25963 super.render(); 25964 this.div.hidden = true; 25965 this.createAltText(); 25966 if (!this.#missingCanvas) { 25967 if (this.#bitmap) { 25968 this.#createCanvas(); 25969 } else { 25970 this.#getBitmap(); 25971 } 25972 } 25973 if (this._isCopy) { 25974 this._moveAfterPaste(baseX, baseY); 25975 } 25976 this._uiManager.addShouldRescale(this); 25977 return this.div; 25978 } 25979 setCanvas(annotationElementId, canvas) { 25980 const { 25981 id: bitmapId, 25982 bitmap 25983 } = this._uiManager.imageManager.getFromCanvas(annotationElementId, canvas); 25984 canvas.remove(); 25985 if (bitmapId && this._uiManager.imageManager.isValidId(bitmapId)) { 25986 this.#bitmapId = bitmapId; 25987 if (bitmap) { 25988 this.#bitmap = bitmap; 25989 } 25990 this.#missingCanvas = false; 25991 this.#createCanvas(); 25992 } 25993 } 25994 _onResized() { 25995 this.onScaleChanging(); 25996 } 25997 onScaleChanging() { 25998 if (!this.parent) { 25999 return; 26000 } 26001 if (this.#resizeTimeoutId !== null) { 26002 clearTimeout(this.#resizeTimeoutId); 26003 } 26004 const TIME_TO_WAIT = 200; 26005 this.#resizeTimeoutId = setTimeout(() => { 26006 this.#resizeTimeoutId = null; 26007 this.#drawBitmap(); 26008 }, TIME_TO_WAIT); 26009 } 26010 #createCanvas() { 26011 const { 26012 div 26013 } = this; 26014 let { 26015 width, 26016 height 26017 } = this.#bitmap; 26018 const [pageWidth, pageHeight] = this.pageDimensions; 26019 const MAX_RATIO = 0.75; 26020 if (this.width) { 26021 width = this.width * pageWidth; 26022 height = this.height * pageHeight; 26023 } else if (width > MAX_RATIO * pageWidth || height > MAX_RATIO * pageHeight) { 26024 const factor = Math.min(MAX_RATIO * pageWidth / width, MAX_RATIO * pageHeight / height); 26025 width *= factor; 26026 height *= factor; 26027 } 26028 this._uiManager.enableWaiting(false); 26029 const canvas = this.#canvas = document.createElement("canvas"); 26030 canvas.setAttribute("role", "img"); 26031 this.addContainer(canvas); 26032 this.width = width / pageWidth; 26033 this.height = height / pageHeight; 26034 this.setDims(); 26035 if (this._initialOptions?.isCentered) { 26036 this.center(); 26037 } else { 26038 this.fixAndSetPosition(); 26039 } 26040 this._initialOptions = null; 26041 if (!this._uiManager.useNewAltTextWhenAddingImage || !this._uiManager.useNewAltTextFlow || this.annotationElementId) { 26042 div.hidden = false; 26043 } 26044 this.#drawBitmap(); 26045 if (!this.#hasBeenAddedInUndoStack) { 26046 this.parent.addUndoableEditor(this); 26047 this.#hasBeenAddedInUndoStack = true; 26048 } 26049 this._reportTelemetry({ 26050 action: "inserted_image" 26051 }); 26052 if (this.#bitmapFileName) { 26053 this.div.setAttribute("aria-description", this.#bitmapFileName); 26054 } 26055 if (!this.annotationElementId) { 26056 this._uiManager.a11yAlert(AnnotationEditor._l10nAlert.stamp); 26057 } 26058 } 26059 copyCanvas(maxDataDimension, maxPreviewDimension, createImageData = false) { 26060
26060if (!maxDataDimension) { 26061 maxDataDimension = 224; 26062 }
vendor: 21,259 bytes, lines 26062-26726
26062 26063 const { 26064 width: bitmapWidth, 26065 height: bitmapHeight 26066 } = this.#bitmap; 26067 const outputScale = new OutputScale(); 26068 let bitmap = this.#bitmap; 26069 let width = bitmapWidth, 26070 height = bitmapHeight; 26071 let canvas = null; 26072 if (maxPreviewDimension) { 26073 if (bitmapWidth > maxPreviewDimension || bitmapHeight > maxPreviewDimension) { 26074 const ratio = Math.min(maxPreviewDimension / bitmapWidth, maxPreviewDimension / bitmapHeight); 26075 width = Math.floor(bitmapWidth * ratio); 26076 height = Math.floor(bitmapHeight * ratio); 26077 } 26078 canvas = document.createElement("canvas"); 26079 const scaledWidth = canvas.width = Math.ceil(width * outputScale.sx); 26080 const scaledHeight = canvas.height = Math.ceil(height * outputScale.sy); 26081 if (!this.#isSvg) { 26082 bitmap = this.#scaleBitmap(scaledWidth, scaledHeight); 26083 } 26084 const ctx = canvas.getContext("2d"); 26085 ctx.filter = this._uiManager.hcmFilter; 26086 let white = "white", 26087 black = "#cfcfd8"; 26088 if (this._uiManager.hcmFilter !== "none") { 26089 black = "black"; 26090 } else if (ColorScheme.isDarkMode) { 26091 white = "#8f8f9d"; 26092 black = "#42414d"; 26093 } 26094 const boxDim = 15; 26095 const boxDimWidth = boxDim * outputScale.sx; 26096 const boxDimHeight = boxDim * outputScale.sy; 26097 const pattern = new OffscreenCanvas(boxDimWidth * 2, boxDimHeight * 2); 26098 const patternCtx = pattern.getContext("2d"); 26099 patternCtx.fillStyle = white; 26100 patternCtx.fillRect(0, 0, boxDimWidth * 2, boxDimHeight * 2); 26101 patternCtx.fillStyle = black; 26102 patternCtx.fillRect(0, 0, boxDimWidth, boxDimHeight); 26103 patternCtx.fillRect(boxDimWidth, boxDimHeight, boxDimWidth, boxDimHeight); 26104 ctx.fillStyle = ctx.createPattern(pattern, "repeat"); 26105 ctx.fillRect(0, 0, scaledWidth, scaledHeight); 26106 ctx.drawImage(bitmap, 0, 0, bitmap.width, bitmap.height, 0, 0, scaledWidth, scaledHeight); 26107 } 26108 let imageData = null; 26109 if (createImageData) { 26110 let dataWidth, dataHeight; 26111 if (outputScale.symmetric && bitmap.width < maxDataDimension && bitmap.height < maxDataDimension) { 26112 dataWidth = bitmap.width; 26113 dataHeight = bitmap.height; 26114 } else { 26115 bitmap = this.#bitmap; 26116 if (bitmapWidth > maxDataDimension || bitmapHeight > maxDataDimension) { 26117 const ratio = Math.min(maxDataDimension / bitmapWidth, maxDataDimension / bitmapHeight); 26118 dataWidth = Math.floor(bitmapWidth * ratio); 26119 dataHeight = Math.floor(bitmapHeight * ratio); 26120 if (!this.#isSvg) { 26121 bitmap = this.#scaleBitmap(dataWidth, dataHeight); 26122 } 26123 } 26124 } 26125 const offscreen = new OffscreenCanvas(dataWidth, dataHeight); 26126 const offscreenCtx = offscreen.getContext("2d", { 26127 willReadFrequently: true 26128 }); 26129 offscreenCtx.drawImage(bitmap, 0, 0, bitmap.width, bitmap.height, 0, 0, dataWidth, dataHeight); 26130 imageData = { 26131 width: dataWidth, 26132 height: dataHeight, 26133 data: offscreenCtx.getImageData(0, 0, dataWidth, dataHeight).data 26134 }; 26135 } 26136 return { 26137 canvas, 26138 width, 26139 height, 26140 imageData 26141 }; 26142 } 26143 #scaleBitmap(width, height) { 26144 const { 26145 width: bitmapWidth, 26146 height: bitmapHeight 26147 } = this.#bitmap; 26148 let newWidth = bitmapWidth; 26149 let newHeight = bitmapHeight; 26150 let bitmap = this.#bitmap; 26151 while (newWidth > 2 * width || newHeight > 2 * height) { 26152 const prevWidth = newWidth; 26153 const prevHeight = newHeight; 26154 if (newWidth > 2 * width) { 26155 newWidth = Math.ceil(newWidth / 2); 26156 } 26157 if (newHeight > 2 * height) { 26158 newHeight = Math.ceil(newHeight / 2); 26159 } 26160 const offscreen = new OffscreenCanvas(newWidth, newHeight); 26161 const ctx = offscreen.getContext("2d"); 26162 ctx.drawImage(bitmap, 0, 0, prevWidth, prevHeight, 0, 0, newWidth, newHeight); 26163 bitmap = offscreen.transferToImageBitmap(); 26164 } 26165 return bitmap; 26166 } 26167 #drawBitmap() { 26168 const [parentWidth, parentHeight] = this.parentDimensions; 26169 const { 26170 width, 26171 height 26172 } = this; 26173 const outputScale = new OutputScale(); 26174 const scaledWidth = Math.ceil(width * parentWidth * outputScale.sx); 26175 const scaledHeight = Math.ceil(height * parentHeight * outputScale.sy); 26176 const canvas = this.#canvas; 26177 if (!canvas || canvas.width === scaledWidth && canvas.height === scaledHeight) { 26178 return; 26179 } 26180 canvas.width = scaledWidth; 26181 canvas.height = scaledHeight; 26182 const bitmap = this.#isSvg ? this.#bitmap : this.#scaleBitmap(scaledWidth, scaledHeight); 26183 const ctx = canvas.getContext("2d"); 26184 ctx.filter = this._uiManager.hcmFilter; 26185 ctx.drawImage(bitmap, 0, 0, bitmap.width, bitmap.height, 0, 0, scaledWidth, scaledHeight); 26186 } 26187 #serializeBitmap(toUrl) { 26188 if (toUrl) { 26189 if (this.#isSvg) { 26190 const url = this._uiManager.imageManager.getSvgUrl(this.#bitmapId); 26191 if (url) { 26192 return url; 26193 } 26194 } 26195 const canvas = document.createElement("canvas"); 26196 ({ 26197 width: canvas.width, 26198 height: canvas.height 26199 } = this.#bitmap); 26200 const ctx = canvas.getContext("2d"); 26201 ctx.drawImage(this.#bitmap, 0, 0); 26202 return canvas.toDataURL(); 26203 } 26204 if (this.#isSvg) { 26205 const [pageWidth, pageHeight] = this.pageDimensions; 26206 const width = Math.round(this.width * pageWidth * PixelsPerInch.PDF_TO_CSS_UNITS); 26207 const height = Math.round(this.height * pageHeight * PixelsPerInch.PDF_TO_CSS_UNITS); 26208 const offscreen = new OffscreenCanvas(width, height); 26209 const ctx = offscreen.getContext("2d"); 26210 ctx.drawImage(this.#bitmap, 0, 0, this.#bitmap.width, this.#bitmap.height, 0, 0, width, height); 26211 return offscreen.transferToImageBitmap(); 26212 } 26213 return structuredClone(this.#bitmap); 26214 } 26215 static async deserialize(data, parent, uiManager) { 26216 let initialData = null; 26217 let missingCanvas = false; 26218 if (data instanceof StampAnnotationElement) { 26219 const { 26220 data: { 26221 rect, 26222 rotation, 26223 id, 26224 structParent, 26225 popupRef, 26226 richText, 26227 contentsObj, 26228 creationDate, 26229 modificationDate 26230 }, 26231 container, 26232 parent: { 26233 page: { 26234 pageNumber 26235 } 26236 }, 26237 canvas 26238 } = data; 26239 let bitmapId, bitmap; 26240 if (canvas) { 26241 delete data.canvas; 26242 ({ 26243 id: bitmapId, 26244 bitmap 26245 } = uiManager.imageManager.getFromCanvas(container.id, canvas)); 26246 canvas.remove(); 26247 } else { 26248 missingCanvas = true; 26249 data._hasNoCanvas = true; 26250 } 26251 const altText = (await parent._structTree.getAriaAttributes(`${AnnotationPrefix}${id}`))?.get("aria-label") || ""; 26252 initialData = data = { 26253 annotationType: AnnotationEditorType.STAMP, 26254 bitmapId, 26255 bitmap, 26256 pageIndex: pageNumber - 1, 26257 rect: rect.slice(0), 26258 rotation, 26259 annotationElementId: id, 26260 id, 26261 deleted: false, 26262 accessibilityData: { 26263 decorative: false, 26264 altText 26265 }, 26266 isSvg: false, 26267 structParent, 26268 popupRef, 26269 richText, 26270 comment: contentsObj?.str || null, 26271 creationDate, 26272 modificationDate 26273 }; 26274 } 26275 const editor = await super.deserialize(data, parent, uiManager); 26276 const { 26277 rect, 26278 bitmap, 26279 bitmapUrl, 26280 bitmapId, 26281 isSvg, 26282 accessibilityData 26283 } = data; 26284 if (missingCanvas) { 26285 uiManager.addMissingCanvas(data.id, editor); 26286 editor.#missingCanvas = true; 26287 } else if (bitmapId && uiManager.imageManager.isValidId(bitmapId)) { 26288 editor.#bitmapId = bitmapId; 26289 if (bitmap) { 26290 editor.#bitmap = bitmap; 26291 } 26292 } else { 26293 editor.#bitmapUrl = bitmapUrl; 26294 } 26295 editor.#isSvg = isSvg; 26296 const [parentWidth, parentHeight] = editor.pageDimensions; 26297 editor.width = (rect[2] - rect[0]) / parentWidth; 26298 editor.height = (rect[3] - rect[1]) / parentHeight; 26299 if (accessibilityData) { 26300 editor.altTextData = accessibilityData; 26301 } 26302 editor._initialData = initialData; 26303 if (data.comment) { 26304 editor.setCommentData(data); 26305 } 26306 editor.#hasBeenAddedInUndoStack = !!initialData; 26307 return editor; 26308 } 26309 serialize(isForCopying = false, context = null) { 26310 if (this.isEmpty()) { 26311 return null; 26312 } 26313 if (this.deleted) { 26314 return this.serializeDeleted(); 26315 } 26316 const serialized = Object.assign(super.serialize(isForCopying), { 26317 bitmapId: this.#bitmapId, 26318 isSvg: this.#isSvg 26319 }); 26320 this.addComment(serialized); 26321 if (isForCopying) { 26322 serialized.bitmapUrl = this.#serializeBitmap(true); 26323 serialized.accessibilityData = this.serializeAltText(true); 26324 serialized.isCopy = true; 26325 return serialized; 26326 } 26327 const { 26328 decorative, 26329 altText 26330 } = this.serializeAltText(false); 26331 if (!decorative && altText) { 26332 serialized.accessibilityData = { 26333 type: "Figure", 26334 alt: altText 26335 }; 26336 } 26337 if (this.annotationElementId) { 26338 const changes = this.#hasElementChanged(serialized); 26339 if (changes.isSame) { 26340 return null; 26341 } 26342 if (changes.isSameAltText) { 26343 delete serialized.accessibilityData; 26344 } else { 26345 serialized.accessibilityData.structParent = this._initialData.structParent ?? -1; 26346 } 26347 serialized.id = this.annotationElementId; 26348 delete serialized.bitmapId; 26349 return serialized; 26350 } 26351 if (context === null) { 26352 return serialized; 26353 } 26354 context.stamps ||= new Map(); 26355 const area = this.#isSvg ? (serialized.rect[2] - serialized.rect[0]) * (serialized.rect[3] - serialized.rect[1]) : null; 26356 if (!context.stamps.has(this.#bitmapId)) { 26357 context.stamps.set(this.#bitmapId, { 26358 area, 26359 serialized 26360 }); 26361 serialized.bitmap = this.#serializeBitmap(false); 26362 } else if (this.#isSvg) { 26363 const prevData = context.stamps.get(this.#bitmapId); 26364 if (area > prevData.area) { 26365 prevData.area = area; 26366 prevData.serialized.bitmap.close(); 26367 prevData.serialized.bitmap = this.#serializeBitmap(false); 26368 } 26369 } 26370 return serialized; 26371 } 26372 #hasElementChanged(serialized) { 26373 const { 26374 pageIndex, 26375 accessibilityData: { 26376 altText 26377 } 26378 } = this._initialData; 26379 const isSamePageIndex = serialized.pageIndex === pageIndex; 26380 const isSameAltText = (serialized.accessibilityData?.alt || "") === altText; 26381 return { 26382 isSame: !this.hasEditedComment && !this._hasBeenMoved && !this._hasBeenResized && isSamePageIndex && isSameAltText, 26383 isSameAltText 26384 }; 26385 } 26386 renderAnnotationElement(annotation) { 26387 if (this.deleted) { 26388 annotation.hide(); 26389 return null; 26390 } 26391 annotation.updateEdited({ 26392 rect: this.getPDFRect(), 26393 popup: this.comment 26394 }); 26395 return null; 26396 } 26397} 26398 26399;// ./src/display/editor/annotation_editor_layer.js 26400 26401 26402 26403 26404 26405 26406 26407 26408class AnnotationEditorLayer { 26409 #accessibilityManager; 26410 #allowClick = false; 26411 #annotationLayer = null; 26412 #clickAC = null; 26413 #editorFocusTimeoutId = null; 26414 #editors = new Map(); 26415 #hadPointerDown = false; 26416 #isDisabling = false; 26417 #isEnabling = false; 26418 #drawingAC = null; 26419 #focusedElement = null; 26420 #textLayer = null; 26421 #textSelectionAC = null; 26422 #textLayerDblClickAC = null; 26423 #lastPointerDownTimestamp = -1; 26424 #uiManager; 26425 static _initialized = false; 26426 static #editorTypes = new Map([FreeTextEditor, InkEditor, StampEditor, HighlightEditor, SignatureEditor].map(type => [type._editorType, type])); 26427 constructor({ 26428 uiManager, 26429 pageIndex, 26430 div, 26431 structTreeLayer, 26432 accessibilityManager, 26433 annotationLayer, 26434 drawLayer, 26435 textLayer, 26436 viewport, 26437 l10n 26438 }) { 26439 const editorTypes = [...AnnotationEditorLayer.#editorTypes.values()]; 26440 if (!AnnotationEditorLayer._initialized) { 26441 AnnotationEditorLayer._initialized = true; 26442 for (const editorType of editorTypes) { 26443 editorType.initialize(l10n, uiManager); 26444 } 26445 } 26446 uiManager.registerEditorTypes(editorTypes); 26447 this.#uiManager = uiManager; 26448 this.pageIndex = pageIndex; 26449 this.div = div; 26450 this.#accessibilityManager = accessibilityManager; 26451 this.#annotationLayer = annotationLayer; 26452 this.viewport = viewport; 26453 this.#textLayer = textLayer; 26454 this.drawLayer = drawLayer; 26455 this._structTree = structTreeLayer; 26456 this.#uiManager.addLayer(this); 26457 } 26458 get isEmpty() { 26459 return this.#editors.size === 0; 26460 } 26461 get isInvisible() { 26462 return this.isEmpty && this.#uiManager.getMode() === AnnotationEditorType.NONE; 26463 } 26464 updateToolbar(options) { 26465 this.#uiManager.updateToolbar(options); 26466 } 26467 updateMode(mode = this.#uiManager.getMode()) { 26468 this.#cleanup(); 26469 switch (mode) { 26470 case AnnotationEditorType.NONE: 26471 this.div.classList.toggle("nonEditing", true); 26472 this.disableTextSelection(); 26473 this.togglePointerEvents(false); 26474 this.toggleAnnotationLayerPointerEvents(true); 26475 this.disableClick(); 26476 return; 26477 case AnnotationEditorType.INK: 26478 this.disableTextSelection(); 26479 this.togglePointerEvents(true); 26480 this.enableClick(); 26481 break; 26482 case AnnotationEditorType.HIGHLIGHT: 26483 this.enableTextSelection(); 26484 this.togglePointerEvents(false); 26485 this.disableClick(); 26486 break; 26487 default: 26488 this.disableTextSelection(); 26489 this.togglePointerEvents(true); 26490 this.enableClick(); 26491 } 26492 this.toggleAnnotationLayerPointerEvents(false); 26493 const { 26494 classList 26495 } = this.div; 26496 classList.toggle("nonEditing", false); 26497 if (mode === AnnotationEditorType.POPUP) { 26498 classList.toggle("commentEditing", true); 26499 } else { 26500 classList.toggle("commentEditing", false); 26501 for (const editorType of AnnotationEditorLayer.#editorTypes.values()) { 26502 classList.toggle(`${editorType._type}Editing`, mode === editorType._editorType); 26503 } 26504 } 26505 this.div.hidden = false; 26506 } 26507 hasTextLayer(textLayer) { 26508 return textLayer === this.#textLayer?.div; 26509 } 26510 setEditingState(isEditing) { 26511 this.#uiManager.setEditingState(isEditing); 26512 } 26513 addCommands(params) { 26514 this.#uiManager.addCommands(params); 26515 } 26516 cleanUndoStack(type) { 26517 this.#uiManager.cleanUndoStack(type); 26518 } 26519 toggleDrawing(enabled = false) { 26520 this.div.classList.toggle("drawing", !enabled); 26521 } 26522 togglePointerEvents(enabled = false) { 26523 this.div.classList.toggle("disabled", !enabled); 26524 } 26525 toggleAnnotationLayerPointerEvents(enabled = false) { 26526 this.#annotationLayer?.togglePointerEvents(enabled); 26527 } 26528 get #allEditorsIterator() { 26529 return this.#editors.size !== 0 ? this.#editors.values() : this.#uiManager.getEditors(this.pageIndex); 26530 } 26531 async enable() { 26532 this.#isEnabling = true; 26533 this.div.tabIndex = 0; 26534 this.togglePointerEvents(true); 26535 this.div.classList.toggle("nonEditing", false); 26536 this.#textLayerDblClickAC?.abort(); 26537 this.#textLayerDblClickAC = null; 26538 const annotationElementIds = new Set(); 26539 for (const editor of this.#allEditorsIterator) { 26540 editor.enableEditing(); 26541 editor.show(true); 26542 if (editor.annotationElementId) { 26543 this.#uiManager.removeChangedExistingAnnotation(editor); 26544 annotationElementIds.add(editor.annotationElementId); 26545 } 26546 } 26547 const annotationLayer = this.#annotationLayer; 26548 if (annotationLayer) { 26549 for (const editable of annotationLayer.getEditableAnnotations()) { 26550 editable.hide(); 26551 if (this.#uiManager.isDeletedAnnotationElement(editable.data.id)) { 26552 continue; 26553 } 26554 if (annotationElementIds.has(editable.data.id)) { 26555 continue; 26556 } 26557 const editor = await this.deserialize(editable); 26558 if (!editor) { 26559 continue; 26560 } 26561 this.addOrRebuild(editor); 26562 editor.enableEditing(); 26563 } 26564 } 26565 this.#isEnabling = false; 26566 this.#uiManager._eventBus.dispatch("editorsrendered", { 26567 source: this, 26568 pageNumber: this.pageIndex + 1 26569 }); 26570 } 26571 disable() { 26572 this.#isDisabling = true; 26573 this.div.tabIndex = -1; 26574 this.togglePointerEvents(false); 26575 this.div.classList.toggle("nonEditing", true); 26576 if (this.#textLayer && !this.#textLayerDblClickAC) { 26577 this.#textLayerDblClickAC = new AbortController(); 26578 const signal = this.#uiManager.combinedSignal(this.#textLayerDblClickAC); 26579 this.#textLayer.div.addEventListener("pointerdown", e => { 26580 const DBL_CLICK_THRESHOLD = 500; 26581 const { 26582 clientX, 26583 clientY, 26584 timeStamp 26585 } = e; 26586 const lastPointerDownTimestamp = this.#lastPointerDownTimestamp; 26587 if (timeStamp - lastPointerDownTimestamp > DBL_CLICK_THRESHOLD) { 26588 this.#lastPointerDownTimestamp = timeStamp; 26589 return; 26590 } 26591 this.#lastPointerDownTimestamp = -1; 26592 const { 26593 classList 26594 } = this.div; 26595 classList.toggle("getElements", true); 26596 const elements = document.elementsFromPoint(clientX, clientY); 26597 classList.toggle("getElements", false); 26598 if (!this.div.contains(elements[0])) { 26599 return; 26600 } 26601 let id; 26602 const regex = new RegExp(`^${AnnotationEditorPrefix}[0-9]+$`); 26603 for (const element of elements) { 26604 if (regex.test(element.id)) { 26605 id = element.id; 26606 break; 26607 } 26608 } 26609 if (!id) { 26610 return; 26611 } 26612 const editor = this.#editors.get(id); 26613 if (editor?.annotationElementId === null) { 26614 stopEvent(e); 26615 editor.dblclick(e); 26616 } 26617 }, { 26618 signal, 26619 capture: true 26620 }); 26621 } 26622 const annotationLayer = this.#annotationLayer; 26623 const needFakeAnnotation = []; 26624 if (annotationLayer) { 26625 const changedAnnotations = new Map(); 26626 const resetAnnotations = new Map(); 26627 for (const editor of this.#allEditorsIterator) { 26628 editor.disableEditing(); 26629 if (!editor.annotationElementId) { 26630 needFakeAnnotation.push(editor); 26631 continue; 26632 } 26633 if (editor.serialize() !== null) { 26634 changedAnnotations.set(editor.annotationElementId, editor); 26635 continue; 26636 } else { 26637 resetAnnotations.set(editor.annotationElementId, editor); 26638 } 26639 this.getEditableAnnotation(editor.annotationElementId)?.show(); 26640 editor.remove(); 26641 } 26642 for (const editable of annotationLayer.getEditableAnnotations()) { 26643 const { 26644 id 26645 } = editable.data; 26646 if (this.#uiManager.isDeletedAnnotationElement(id)) { 26647 editable.updateEdited({ 26648 deleted: true 26649 }); 26650 continue; 26651 } 26652 let editor = resetAnnotations.get(id); 26653 if (editor) { 26654 editor.resetAnnotationElement(editable); 26655 editor.show(false); 26656 editable.show(); 26657 continue; 26658 } 26659 editor = changedAnnotations.get(id); 26660 if (editor) { 26661 this.#uiManager.addChangedExistingAnnotation(editor); 26662 if (editor.renderAnnotationElement(editable)) { 26663 editor.show(false); 26664 } 26665 } 26666 editable.show(); 26667 } 26668 } 26669 this.#cleanup(); 26670 if (this.isEmpty) { 26671 this.div.hidden = true; 26672 } 26673 const { 26674 classList 26675 } = this.div; 26676 for (const editorType of AnnotationEditorLayer.#editorTypes.values()) { 26677 classList.remove(`${editorType._type}Editing`); 26678 } 26679 this.disableTextSelection(); 26680 this.toggleAnnotationLayerPointerEvents(true); 26681 annotationLayer?.updateFakeAnnotations(needFakeAnnotation); 26682 this.#isDisabling = false; 26683 } 26684 getEditableAnnotation(id) { 26685 return this.#annotationLayer?.getEditableAnnotation(id) || null; 26686 } 26687 setActiveEditor(editor) { 26688 const currentActive = this.#uiManager.getActive(); 26689 if (currentActive === editor) { 26690 return; 26691 } 26692 this.#uiManager.setActiveEditor(editor); 26693 } 26694 enableTextSelection() { 26695 this.div.tabIndex = -1; 26696 if (this.#textLayer?.div && !this.#textSelectionAC) { 26697 this.#textSelectionAC = new AbortController(); 26698 const signal = this.#uiManager.combinedSignal(this.#textSelectionAC); 26699 this.#textLayer.div.addEventListener("pointerdown", this.#textLayerPointerDown.bind(this), { 26700 signal 26701 }); 26702 this.#textLayer.div.classList.add("highlighting"); 26703 } 26704 } 26705 disableTextSelection() { 26706 this.div.tabIndex = 0; 26707 if (this.#textLayer?.div && this.#textSelectionAC) { 26708 this.#textSelectionAC.abort(); 26709 this.#textSelectionAC = null; 26710 this.#textLayer.div.classList.remove("highlighting"); 26711 } 26712 } 26713 #textLayerPointerDown(event) { 26714 this.#uiManager.unselectAll(); 26715 const { 26716 target 26717 } = event; 26718 if (target === this.#textLayer.div || (target.getAttribute("role") === "img" || target.classList.contains("endOfContent") || target.classList.contains("textLayerImages") || target.classList.contains("textLayerImagePlaceholder")) && this.#textLayer.div.contains(target)) { 26719 const { 26720 isMac 26721 } = FeatureTest.platform; 26722 if (event.button !== 0 || event.ctrlKey && isMac) { 26723 return; 26724 } 26725 this.#uiManager.showAllEditors("highlight", true, true); 26726
26726this.#textLayer.div.classList.add("free"); 26727 this.toggleDrawing(); 26728 HighlightEditor.startHighlighting(this, this.#uiManager.direction === "ltr", { 26729 target: this.#textLayer.div, 26730 x: event.x, 26731 y: event.y 26732 }); 26733 this.#textLayer.div.addEventListener("pointerup", () => { 26734 this.#textLayer.div.classList.remove("free"); 26735 this.toggleDrawing(true); 26736 }, { 26737 once: true, 26738 signal: this.#uiManager._signal 26739 }
vendor: 15,980 bytes, lines 26739-27322
26739); 26740 event.preventDefault(); 26741 } 26742 } 26743 enableClick() { 26744 if (this.#clickAC) { 26745 return; 26746 } 26747 this.#clickAC = new AbortController(); 26748 const signal = this.#uiManager.combinedSignal(this.#clickAC); 26749 this.div.addEventListener("pointerdown", this.pointerdown.bind(this), { 26750 signal 26751 }); 26752 const pointerup = this.pointerup.bind(this); 26753 this.div.addEventListener("pointerup", pointerup, { 26754 signal 26755 }); 26756 this.div.addEventListener("pointercancel", pointerup, { 26757 signal 26758 }); 26759 } 26760 disableClick() { 26761 this.#clickAC?.abort(); 26762 this.#clickAC = null; 26763 } 26764 attach(editor) { 26765 this.#editors.set(editor.id, editor); 26766 const { 26767 annotationElementId 26768 } = editor; 26769 if (annotationElementId && this.#uiManager.isDeletedAnnotationElement(annotationElementId)) { 26770 this.#uiManager.removeDeletedAnnotationElement(editor); 26771 } 26772 } 26773 detach(editor) { 26774 this.#editors.delete(editor.id); 26775 this.#accessibilityManager?.removePointerInTextLayer(editor.contentDiv); 26776 if (!this.#isDisabling && editor.annotationElementId) { 26777 this.#uiManager.addDeletedAnnotationElement(editor); 26778 } 26779 } 26780 remove(editor) { 26781 this.detach(editor); 26782 this.#uiManager.removeEditor(editor); 26783 editor.div.remove(); 26784 editor.isAttachedToDOM = false; 26785 } 26786 changeParent(editor) { 26787 if (editor.parent === this) { 26788 return; 26789 } 26790 if (editor.parent && editor.annotationElementId) { 26791 this.#uiManager.addDeletedAnnotationElement(editor); 26792 AnnotationEditor.deleteAnnotationElement(editor); 26793 editor.annotationElementId = null; 26794 } 26795 this.attach(editor); 26796 editor.parent?.detach(editor); 26797 editor.setParent(this); 26798 if (editor.div && editor.isAttachedToDOM) { 26799 editor.div.remove(); 26800 this.div.append(editor.div); 26801 } 26802 } 26803 add(editor) { 26804 if (editor.parent === this && editor.isAttachedToDOM) { 26805 return; 26806 } 26807 this.changeParent(editor); 26808 this.#uiManager.addEditor(editor); 26809 this.attach(editor); 26810 if (!editor.isAttachedToDOM) { 26811 const div = editor.render(); 26812 this.div.append(div); 26813 editor.isAttachedToDOM = true; 26814 } 26815 editor.fixAndSetPosition(); 26816 editor.onceAdded(!this.#isEnabling); 26817 this.#uiManager.addToAnnotationStorage(editor); 26818 editor._reportTelemetry(editor.telemetryInitialData); 26819 } 26820 moveEditorInDOM(editor) { 26821 if (!editor.isAttachedToDOM) { 26822 return; 26823 } 26824 const { 26825 activeElement 26826 } = document; 26827 if (editor.div.contains(activeElement) && !this.#editorFocusTimeoutId) { 26828 editor._focusEventsAllowed = false; 26829 this.#editorFocusTimeoutId = setTimeout(() => { 26830 this.#editorFocusTimeoutId = null; 26831 if (!editor.div.contains(document.activeElement)) { 26832 editor.div.addEventListener("focusin", () => { 26833 editor._focusEventsAllowed = true; 26834 }, { 26835 once: true, 26836 signal: this.#uiManager._signal 26837 }); 26838 activeElement.focus(); 26839 } else { 26840 editor._focusEventsAllowed = true; 26841 } 26842 }, 0); 26843 } 26844 editor._structTreeParentId = this.#accessibilityManager?.moveElementInDOM(this.div, editor.div, editor.contentDiv, true); 26845 } 26846 addOrRebuild(editor) { 26847 if (editor.needsToBeRebuilt()) { 26848 editor.parent ||= this; 26849 editor.rebuild(); 26850 editor.show(); 26851 } else { 26852 this.add(editor); 26853 } 26854 } 26855 addUndoableEditor(editor) { 26856 const cmd = () => editor._uiManager.rebuild(editor); 26857 const undo = () => { 26858 editor.remove(); 26859 }; 26860 this.addCommands({ 26861 cmd, 26862 undo, 26863 mustExec: false 26864 }); 26865 } 26866 getEditorByUID(uid) { 26867 for (const editor of this.#editors.values()) { 26868 if (editor.uid === uid) { 26869 return editor; 26870 } 26871 } 26872 return null; 26873 } 26874 get #currentEditorType() { 26875 return AnnotationEditorLayer.#editorTypes.get(this.#uiManager.getMode()); 26876 } 26877 combinedSignal(ac) { 26878 return this.#uiManager.combinedSignal(ac); 26879 } 26880 #createNewEditor(params) { 26881 const editorType = this.#currentEditorType; 26882 return editorType ? new editorType.prototype.constructor(params) : null; 26883 } 26884 canCreateNewEmptyEditor() { 26885 return this.#currentEditorType?.canCreateNewEmptyEditor(); 26886 } 26887 async pasteEditor(options, params) { 26888 this.updateToolbar(options); 26889 await this.#uiManager.updateMode(options.mode); 26890 const { 26891 offsetX, 26892 offsetY 26893 } = this.#getCenterPoint(); 26894 const id = this.#uiManager.getId(); 26895 const editor = this.#createNewEditor({ 26896 parent: this, 26897 id, 26898 x: offsetX, 26899 y: offsetY, 26900 uiManager: this.#uiManager, 26901 isCentered: true, 26902 ...params 26903 }); 26904 if (editor) { 26905 this.add(editor); 26906 } 26907 } 26908 async deserialize(data) { 26909 return (await AnnotationEditorLayer.#editorTypes.get(data.annotationType ?? data.annotationEditorType)?.deserialize(data, this, this.#uiManager)) || null; 26910 } 26911 createAndAddNewEditor(event, isCentered, data = {}) { 26912 const id = this.#uiManager.getId(); 26913 const editor = this.#createNewEditor({ 26914 parent: this, 26915 id, 26916 x: event.offsetX, 26917 y: event.offsetY, 26918 uiManager: this.#uiManager, 26919 isCentered, 26920 ...data 26921 }); 26922 if (editor) { 26923 this.add(editor); 26924 } 26925 return editor; 26926 } 26927 get boundingClientRect() { 26928 return this.div.getBoundingClientRect(); 26929 } 26930 #getCenterPoint() { 26931 const { 26932 x, 26933 y, 26934 width, 26935 height 26936 } = this.boundingClientRect; 26937 const tlX = Math.max(0, x); 26938 const tlY = Math.max(0, y); 26939 const brX = Math.min(window.innerWidth, x + width); 26940 const brY = Math.min(window.innerHeight, y + height); 26941 const centerX = (tlX + brX) / 2 - x; 26942 const centerY = (tlY + brY) / 2 - y; 26943 const [offsetX, offsetY] = this.viewport.rotation % 180 === 0 ? [centerX, centerY] : [centerY, centerX]; 26944 return { 26945 offsetX, 26946 offsetY 26947 }; 26948 } 26949 addNewEditor(data = {}) { 26950 this.createAndAddNewEditor(this.#getCenterPoint(), true, data); 26951 } 26952 setSelected(editor) { 26953 this.#uiManager.setSelected(editor); 26954 } 26955 toggleSelected(editor) { 26956 this.#uiManager.toggleSelected(editor); 26957 } 26958 unselect(editor) { 26959 this.#uiManager.unselect(editor); 26960 } 26961 pointerup(event) { 26962 const { 26963 isMac 26964 } = FeatureTest.platform; 26965 if (event.button !== 0 || event.ctrlKey && isMac) { 26966 return; 26967 } 26968 if (event.target !== this.div) { 26969 return; 26970 } 26971 if (!this.#hadPointerDown) { 26972 return; 26973 } 26974 this.#hadPointerDown = false; 26975 if (this.#currentEditorType?.isDrawer && this.#currentEditorType.supportMultipleDrawings) { 26976 return; 26977 } 26978 if (!this.#allowClick) { 26979 this.#allowClick = true; 26980 return; 26981 } 26982 const currentMode = this.#uiManager.getMode(); 26983 if (currentMode === AnnotationEditorType.STAMP || currentMode === AnnotationEditorType.POPUP || currentMode === AnnotationEditorType.SIGNATURE) { 26984 this.#uiManager.unselectAll(); 26985 return; 26986 } 26987 this.createAndAddNewEditor(event, false); 26988 } 26989 pointerdown(event) { 26990 if (this.#uiManager.getMode() === AnnotationEditorType.HIGHLIGHT) { 26991 this.enableTextSelection(); 26992 } 26993 if (this.#hadPointerDown) { 26994 this.#hadPointerDown = false; 26995 return; 26996 } 26997 const { 26998 isMac 26999 } = FeatureTest.platform; 27000 if (event.button !== 0 || event.ctrlKey && isMac) { 27001 return; 27002 } 27003 if (event.target !== this.div) { 27004 return; 27005 } 27006 this.#hadPointerDown = true; 27007 if (this.#currentEditorType?.isDrawer) { 27008 this.startDrawingSession(event); 27009 return; 27010 } 27011 const editor = this.#uiManager.getActive(); 27012 this.#allowClick = !editor || editor.isEmpty(); 27013 } 27014 startDrawingSession(event) { 27015 this.div.focus({ 27016 preventScroll: true 27017 }); 27018 if (this.#drawingAC) { 27019 this.#currentEditorType.startDrawing(this, this.#uiManager, false, event); 27020 return; 27021 } 27022 this.#uiManager.setCurrentDrawingSession(this); 27023 this.#drawingAC = new AbortController(); 27024 const signal = this.#uiManager.combinedSignal(this.#drawingAC); 27025 this.div.addEventListener("blur", ({ 27026 relatedTarget 27027 }) => { 27028 if (relatedTarget && !this.div.contains(relatedTarget)) { 27029 this.#focusedElement = null; 27030 this.commitOrRemove(); 27031 } 27032 }, { 27033 signal 27034 }); 27035 this.#currentEditorType.startDrawing(this, this.#uiManager, false, event); 27036 } 27037 pause(on) { 27038 if (on) { 27039 const { 27040 activeElement 27041 } = document; 27042 if (this.div.contains(activeElement)) { 27043 this.#focusedElement = activeElement; 27044 } 27045 return; 27046 } 27047 if (this.#focusedElement) { 27048 setTimeout(() => { 27049 this.#focusedElement?.focus(); 27050 this.#focusedElement = null; 27051 }, 0); 27052 } 27053 } 27054 endDrawingSession(isAborted = false) { 27055 if (!this.#drawingAC) { 27056 return null; 27057 } 27058 this.#uiManager.setCurrentDrawingSession(null); 27059 this.#drawingAC.abort(); 27060 this.#drawingAC = null; 27061 this.#focusedElement = null; 27062 return this.#currentEditorType.endDrawing(isAborted); 27063 } 27064 findNewParent(editor, x, y) { 27065 const layer = this.#uiManager.findParent(x, y); 27066 if (layer === null || layer === this) { 27067 return false; 27068 } 27069 layer.changeParent(editor); 27070 return true; 27071 } 27072 commitOrRemove() { 27073 if (this.#drawingAC) { 27074 this.endDrawingSession(); 27075 return true; 27076 } 27077 return false; 27078 } 27079 onScaleChanging() { 27080 if (!this.#drawingAC) { 27081 return; 27082 } 27083 this.#currentEditorType.onScaleChangingWhenDrawing(this); 27084 } 27085 destroy() { 27086 this.commitOrRemove(); 27087 if (this.#uiManager.getActive()?.parent === this) { 27088 this.#uiManager.commitOrRemove(); 27089 this.#uiManager.setActiveEditor(null); 27090 } 27091 if (this.#editorFocusTimeoutId) { 27092 clearTimeout(this.#editorFocusTimeoutId); 27093 this.#editorFocusTimeoutId = null; 27094 } 27095 for (const editor of this.#editors.values()) { 27096 this.#accessibilityManager?.removePointerInTextLayer(editor.contentDiv); 27097 editor.setParent(null); 27098 editor.isAttachedToDOM = false; 27099 editor.div.remove(); 27100 } 27101 this.div = null; 27102 this.#editors.clear(); 27103 this.#uiManager.removeLayer(this); 27104 } 27105 #cleanup() { 27106 for (const editor of this.#editors.values()) { 27107 if (editor.isEmpty()) { 27108 editor.remove(); 27109 } 27110 } 27111 } 27112 async render({ 27113 viewport 27114 }) { 27115 this.viewport = viewport; 27116 setLayerDimensions(this.div, viewport); 27117 for (const editor of this.#uiManager.getEditors(this.pageIndex)) { 27118 this.add(editor); 27119 editor.rebuild(); 27120 } 27121 await this.#uiManager.findClonesForPage(this); 27122 this.div.hidden = this.isEmpty; 27123 this.updateMode(); 27124 } 27125 update({ 27126 viewport 27127 }) { 27128 this.#uiManager.commitOrRemove(); 27129 this.#cleanup(); 27130 const oldRotation = this.viewport.rotation; 27131 const rotation = viewport.rotation; 27132 this.viewport = viewport; 27133 setLayerDimensions(this.div, { 27134 rotation 27135 }); 27136 if (oldRotation !== rotation) { 27137 for (const editor of this.#editors.values()) { 27138 editor.rotate(rotation); 27139 } 27140 } 27141 } 27142 get pageDimensions() { 27143 const { 27144 pageWidth, 27145 pageHeight 27146 } = this.viewport.rawDims; 27147 return [pageWidth, pageHeight]; 27148 } 27149 get scale() { 27150 return this.#uiManager.viewParameters.realScale; 27151 } 27152} 27153 27154;// ./src/display/draw_layer.js 27155 27156 27157function compareTextLayers(a, b) { 27158 if (a === b) { 27159 return 0; 27160 } 27161 return a.compareDocumentPosition(b) & Node.DOCUMENT_POSITION_FOLLOWING ? -1 : 1; 27162} 27163function getTextLayer(node) { 27164 if (!node) { 27165 return null; 27166 } 27167 if (node.nodeType === Node.ELEMENT_NODE) { 27168 return node.closest(".textLayer"); 27169 } 27170 return node.parentElement?.closest(".textLayer") || null; 27171} 27172function isPointBefore(nodeA, offsetA, nodeB, offsetB) { 27173 if (nodeA === nodeB) { 27174 return offsetA <= offsetB; 27175 } 27176 const relation = nodeA.compareDocumentPosition(nodeB); 27177 if (relation & Node.DOCUMENT_POSITION_FOLLOWING) { 27178 return true; 27179 } 27180 if (relation & Node.DOCUMENT_POSITION_PRECEDING) { 27181 return false; 27182 } 27183 return null; 27184} 27185function normalizeEdgeBoundary(container, offset, textLayer) { 27186 if (container.nodeType !== Node.ELEMENT_NODE || !container.classList.contains("textLayer") || offset !== container.childNodes.length) { 27187 return { 27188 container, 27189 offset 27190 }; 27191 } 27192 let lastNode = container.lastChild; 27193 if (lastNode?.nodeType === Node.ELEMENT_NODE && lastNode.classList.contains("endOfContent")) { 27194 lastNode = lastNode.previousSibling; 27195 } 27196 if (!lastNode || !textLayer.contains(lastNode)) { 27197 return null; 27198 } 27199 if (lastNode.nodeType === Node.TEXT_NODE) { 27200 return { 27201 container: lastNode, 27202 offset: lastNode.textContent.length 27203 }; 27204 } 27205 return { 27206 container: lastNode, 27207 offset: lastNode.childNodes.length 27208 }; 27209} 27210class DrawLayer { 27211 #parent = null; 27212 #mapping = new Map(); 27213 #textLayer = null; 27214 #filterFactory = null; 27215 #pageColors = null; 27216 #textLayerObserver = null; 27217 #toUpdate = new Map(); 27218 static #id = 0; 27219 static #selectionId = 0; 27220 static #selectionChangeAC = null; 27221 static #selections = new Set(); 27222 static #isSelecting = false; 27223 static #textLayerSet = new Set(); 27224 static #textLayers = new WeakMap(); 27225 constructor({ 27226 filterFactory = null, 27227 pageColors = null, 27228 pageIndex, 27229 textLayer = null 27230 }) { 27231 this.pageIndex = pageIndex; 27232 this.#filterFactory = filterFactory; 27233 this.#pageColors = pageColors; 27234 if (textLayer) { 27235 const previousData = DrawLayer.#textLayers.get(textLayer); 27236 if (previousData?.selectionDiv) { 27237 previousData.selectionDiv.remove(); 27238 DrawLayer.#selections.delete(previousData.selectionDiv); 27239 } 27240 DrawLayer.#textLayers.set(textLayer, { 27241 drawLayer: this 27242 }); 27243 DrawLayer.#textLayerSet.add(textLayer); 27244 this.#textLayer = textLayer; 27245 this.#textLayerObserver = new MutationObserver(records => { 27246 if (!this.#parent || !this.#textLayer?.isConnected || !DrawLayer.#hasSelection()) { 27247 return; 27248 } 27249 for (const { 27250 addedNodes 27251 } of records) { 27252 for (const node of addedNodes) { 27253 if (node.nodeType === Node.ELEMENT_NODE && node.classList.contains("endOfContent")) { 27254 DrawLayer.#selectionChange(); 27255 return; 27256 } 27257 } 27258 } 27259 }); 27260 this.#textLayerObserver.observe(textLayer, { 27261 childList: true 27262 }); 27263 if (DrawLayer.#selectionChangeAC === null) { 27264 DrawLayer.#selectionChangeAC = new AbortController(); 27265 const { 27266 signal 27267 } = DrawLayer.#selectionChangeAC; 27268 document.addEventListener("selectionchange", DrawLayer.#selectionChange.bind(DrawLayer), { 27269 signal 27270 }); 27271 document.addEventListener("pointerdown", () => { 27272 DrawLayer.#isSelecting = true; 27273 }, { 27274 signal 27275 }); 27276 document.addEventListener("pointerup", () => { 27277 DrawLayer.#isSelecting = false; 27278 }, { 27279 signal 27280 }); 27281 window.addEventListener("blur", () => { 27282 DrawLayer.#isSelecting = false; 27283 }, { 27284 signal 27285 }); 27286 } 27287 } 27288 } 27289 setParent(parent) { 27290 if (!this.#parent) { 27291 this.#parent = parent; 27292 if (this.#textLayer?.isConnected && DrawLayer.#hasSelection()) { 27293 DrawLayer.#selectionChange(); 27294 } 27295 return; 27296 } 27297 if (this.#parent !== parent) { 27298 if (this.#mapping.size > 0) { 27299 for (const root of this.#mapping.values()) { 27300 root.remove(); 27301 parent.append(root); 27302 } 27303 } 27304 this.#parent = parent; 27305 } 27306 } 27307 static #cleanupTextLayerSelection(textLayer) { 27308 const textLayerData = this.#textLayers.get(textLayer); 27309 if (!textLayerData?.selectionDiv) { 27310 return; 27311 } 27312 textLayerData.selectionDiv.remove(); 27313 this.#selections.delete(textLayerData.selectionDiv); 27314 textLayerData.selectionDiv = null; 27315 textLayerData.path = null; 27316 } 27317 static #hasSelection() { 27318 const selection = document.getSelection(); 27319 return !!selection && !selection.isCollapsed; 27320 } 27321 static #getOrderedTextLayers() { 27322 return
27322[...this.#textLayerSet].filter(textLayer => textLayer.isConnected
vendor: 7,564 bytes, lines 27322-27535
27322).sort(compareTextLayers); 27323 } 27324 static #selectionChange() { 27325 const selection = document.getSelection(); 27326 if (!selection || selection.isCollapsed) { 27327 for (const root of this.#selections) { 27328 root.remove(); 27329 } 27330 this.#selections.clear(); 27331 return; 27332 } 27333 const rotators = new WeakMap(); 27334 const orderedTextLayers = this.#getOrderedTextLayers(); 27335 const ranges = []; 27336 for (let i = 0, ii = selection.rangeCount; i < ii; i++) { 27337 const range = selection.getRangeAt(i); 27338 if (range.collapsed) { 27339 continue; 27340 } 27341 let { 27342 startContainer, 27343 startOffset, 27344 endContainer, 27345 endOffset 27346 } = range; 27347 let startTextLayer = getTextLayer(startContainer); 27348 let endTextLayer = getTextLayer(endContainer); 27349 const startMissing = startTextLayer === null; 27350 const endMissing = endTextLayer === null; 27351 if (this.#isSelecting && startMissing !== endMissing) { 27352 return; 27353 } 27354 if (selection.rangeCount === 1) { 27355 const { 27356 anchorNode, 27357 anchorOffset, 27358 focusNode, 27359 focusOffset 27360 } = selection; 27361 const anchorLayer = getTextLayer(anchorNode); 27362 const focusLayer = getTextLayer(focusNode); 27363 const anchorBeforeFocus = isPointBefore(anchorNode, anchorOffset, focusNode, focusOffset); 27364 if (anchorLayer && focusLayer && anchorBeforeFocus !== null) { 27365 if (anchorBeforeFocus) { 27366 startContainer = anchorNode; 27367 startOffset = anchorOffset; 27368 startTextLayer = anchorLayer; 27369 endContainer = focusNode; 27370 endOffset = focusOffset; 27371 endTextLayer = focusLayer; 27372 } else { 27373 startContainer = focusNode; 27374 startOffset = focusOffset; 27375 startTextLayer = focusLayer; 27376 endContainer = anchorNode; 27377 endOffset = anchorOffset; 27378 endTextLayer = anchorLayer; 27379 } 27380 } 27381 } 27382 const activeTextLayers = orderedTextLayers.filter(textLayer => range.intersectsNode(textLayer)); 27383 if (activeTextLayers.length === 0) { 27384 continue; 27385 } 27386 let boundarySubstituted = false; 27387 if (!startTextLayer) { 27388 startTextLayer = activeTextLayers[0]; 27389 startContainer = startTextLayer; 27390 startOffset = 0; 27391 boundarySubstituted = true; 27392 } 27393 if (!endTextLayer) { 27394 endTextLayer = activeTextLayers.at(-1); 27395 endContainer = endTextLayer; 27396 endOffset = endTextLayer.childNodes.length; 27397 boundarySubstituted = true; 27398 } 27399 if (endContainer.nodeType === Node.ELEMENT_NODE) { 27400 if (endContainer.classList.contains("endOfContent")) { 27401 const previousNode = endContainer.previousSibling; 27402 if (!previousNode) { 27403 continue; 27404 } 27405 endContainer = previousNode; 27406 endOffset = previousNode.nodeType === Node.TEXT_NODE ? previousNode.textContent.length : previousNode.childNodes.length; 27407 } else if (endContainer.classList.contains("textLayer") && endContainer.childNodes.length === endOffset) { 27408 const normalizedEnd = normalizeEdgeBoundary(endContainer, endOffset, endTextLayer); 27409 if (!normalizedEnd) { 27410 continue; 27411 } 27412 endContainer = normalizedEnd.container; 27413 endOffset = normalizedEnd.offset; 27414 } 27415 } 27416 if (startContainer.nodeType === Node.ELEMENT_NODE) { 27417 const normalizedStart = normalizeEdgeBoundary(startContainer, startOffset, startTextLayer); 27418 if (!normalizedStart) { 27419 continue; 27420 } 27421 startContainer = normalizedStart.container; 27422 startOffset = normalizedStart.offset; 27423 } 27424 if (startTextLayer === endTextLayer && !boundarySubstituted && activeTextLayers.includes(startTextLayer)) { 27425 ranges.push([range, startTextLayer]); 27426 continue; 27427 } 27428 for (const textLayer of activeTextLayers) { 27429 const firstNode = textLayer.firstChild; 27430 if (!firstNode) { 27431 continue; 27432 } 27433 const subRange = document.createRange(); 27434 if (textLayer === startTextLayer) { 27435 subRange.setStart(startContainer, startOffset); 27436 } else { 27437 subRange.setStartBefore(firstNode); 27438 } 27439 if (textLayer === endTextLayer) { 27440 subRange.setEnd(endContainer, endOffset); 27441 } else { 27442 const lastNode = textLayer.lastChild; 27443 if (!lastNode) { 27444 continue; 27445 } 27446 if (lastNode.nodeType === Node.ELEMENT_NODE && lastNode.classList.contains("endOfContent")) { 27447 const lastTextNode = lastNode.previousSibling; 27448 if (!lastTextNode) { 27449 continue; 27450 } 27451 subRange.setEndAfter(lastTextNode); 27452 } else { 27453 subRange.setEndAfter(lastNode); 27454 } 27455 } 27456 if (!subRange.collapsed) { 27457 ranges.push([subRange, textLayer]); 27458 } 27459 } 27460 } 27461 const selectedTextLayers = new Set(ranges.map(range => range[1])); 27462 for (const textLayer of this.#textLayerSet) { 27463 if (!selectedTextLayers.has(textLayer)) { 27464 this.#cleanupTextLayerSelection(textLayer); 27465 } 27466 } 27467 for (const [range, textLayer] of ranges) { 27468 const textLayerData = DrawLayer.#textLayers.get(textLayer); 27469 if (!textLayerData) { 27470 continue; 27471 } 27472 let rotator = rotators.get(textLayer); 27473 if (!rotator) { 27474 const clientRect = textLayer.getBoundingClientRect(); 27475 rotator = (x, y, w, h) => ({ 27476 x: (x - clientRect.x) / clientRect.width, 27477 y: (y - clientRect.y) / clientRect.height, 27478 width: w / clientRect.width, 27479 height: h / clientRect.height 27480 }); 27481 rotators.set(textLayer, rotator); 27482 } 27483 const boxes = []; 27484 for (let { 27485 x, 27486 y, 27487 width, 27488 height 27489 } of range.getClientRects()) { 27490 if (width === 0 || height === 0) { 27491 continue; 27492 } 27493 ({ 27494 x, 27495 y, 27496 width, 27497 height 27498 } = rotator(x, y, width, height)); 27499 if (width === 1 && height === 1) { 27500 continue; 27501 } 27502 boxes.push(`M${x} ${y} h${width} v${height} h-${width} Z`); 27503 } 27504 if (boxes.length === 0) { 27505 continue; 27506 } 27507 const drawLayer = textLayerData.drawLayer; 27508 let div = textLayerData.selectionDiv; 27509 let path = textLayerData.path; 27510 if (!div) { 27511 const clipPathId = `clip_selection_${DrawLayer.#selectionId++}`; 27512 div = document.createElement("div"); 27513 div.className = "selection"; 27514 div.style.clipPath = `url(#${clipPathId})`; 27515 const selectionStyle = drawLayer.#filterFactory?.createSelectionStyle(drawLayer.#pageColors); 27516 if (selectionStyle) { 27517 for (const [name, value] of Object.entries(selectionStyle)) { 27518 div.style.setProperty(name, value); 27519 } 27520 } 27521 const svg = DrawLayer._svgFactory.create(1, 1, true); 27522 svg.setAttribute("aria-hidden", "true"); 27523 svg.setAttribute("width", "100%"); 27524 svg.setAttribute("height", "100%"); 27525 const clipPath = DrawLayer._svgFactory.createElement("clipPath"); 27526 clipPath.setAttribute("id", clipPathId); 27527 clipPath.setAttribute("clipPathUnits", "objectBoundingBox"); 27528 path = DrawLayer._svgFactory.createElement("path"); 27529 clipPath.append(path); 27530 svg.append(clipPath); 27531 div.append(svg); 27532 textLayerData.path = path; 27533 textLayerData.selectionDiv = div; 27534 } 27535 if (
27535!div.parentNode &&
vendor: 10,727 bytes, lines 27535-27892
27535 drawLayer.#parent) { 27536 drawLayer.#parent.append(div); 27537 this.#selections.add(div); 27538 } 27539 path.setAttribute("d", boxes.join(" ")); 27540 } 27541 } 27542 static get _svgFactory() { 27543 return shadow(this, "_svgFactory", new DOMSVGFactory()); 27544 } 27545 static #setBox(element, [x, y, width, height]) { 27546 const { 27547 style 27548 } = element; 27549 style.top = `${100 * y}%`; 27550 style.left = `${100 * x}%`; 27551 style.width = `${100 * width}%`; 27552 style.height = `${100 * height}%`; 27553 } 27554 #createSVG() { 27555 const svg = DrawLayer._svgFactory.create(1, 1, true); 27556 this.#parent.append(svg); 27557 svg.setAttribute("aria-hidden", "true"); 27558 return svg; 27559 } 27560 #createClipPath(defs, pathId) { 27561 const clipPath = DrawLayer._svgFactory.createElement("clipPath"); 27562 defs.append(clipPath); 27563 const clipPathId = `clip_${pathId}`; 27564 clipPath.setAttribute("id", clipPathId); 27565 clipPath.setAttribute("clipPathUnits", "objectBoundingBox"); 27566 const clipPathUse = DrawLayer._svgFactory.createElement("use"); 27567 clipPath.append(clipPathUse); 27568 clipPathUse.setAttribute("href", `#${pathId}`); 27569 clipPathUse.classList.add("clip"); 27570 return clipPathId; 27571 } 27572 #updateProperties(element, properties) { 27573 for (const [key, value] of Object.entries(properties)) { 27574 if (value === null) { 27575 element.removeAttribute(key); 27576 } else { 27577 element.setAttribute(key, value); 27578 } 27579 } 27580 } 27581 draw(properties, isPathUpdatable = false, hasClip = false) { 27582 const id = DrawLayer.#id++; 27583 const root = this.#createSVG(); 27584 const defs = DrawLayer._svgFactory.createElement("defs"); 27585 root.append(defs); 27586 const path = DrawLayer._svgFactory.createElement("path"); 27587 defs.append(path); 27588 const pathId = `path_${id}`; 27589 path.setAttribute("id", pathId); 27590 path.setAttribute("vector-effect", "non-scaling-stroke"); 27591 if (isPathUpdatable) { 27592 this.#toUpdate.set(id, path); 27593 } 27594 const clipPathId = hasClip ? this.#createClipPath(defs, pathId) : null; 27595 const use = DrawLayer._svgFactory.createElement("use"); 27596 root.append(use); 27597 use.setAttribute("href", `#${pathId}`); 27598 this.updateProperties(root, properties); 27599 this.#mapping.set(id, root); 27600 return { 27601 id, 27602 clipPathId: `url(#${clipPathId})` 27603 }; 27604 } 27605 drawOutline(properties, mustRemoveSelfIntersections) { 27606 const id = DrawLayer.#id++; 27607 const root = this.#createSVG(); 27608 const defs = DrawLayer._svgFactory.createElement("defs"); 27609 root.append(defs); 27610 const path = DrawLayer._svgFactory.createElement("path"); 27611 defs.append(path); 27612 const pathId = `path_${id}`; 27613 path.setAttribute("id", pathId); 27614 path.setAttribute("vector-effect", "non-scaling-stroke"); 27615 let maskId; 27616 if (mustRemoveSelfIntersections) { 27617 const mask = DrawLayer._svgFactory.createElement("mask"); 27618 defs.append(mask); 27619 maskId = `mask_${id}`; 27620 mask.setAttribute("id", maskId); 27621 mask.setAttribute("maskUnits", "objectBoundingBox"); 27622 const rect = DrawLayer._svgFactory.createElement("rect"); 27623 mask.append(rect); 27624 rect.setAttribute("width", "1"); 27625 rect.setAttribute("height", "1"); 27626 rect.setAttribute("fill", "white"); 27627 const use = DrawLayer._svgFactory.createElement("use"); 27628 mask.append(use); 27629 use.setAttribute("href", `#${pathId}`); 27630 use.setAttribute("stroke", "none"); 27631 use.setAttribute("fill", "black"); 27632 use.setAttribute("fill-rule", "nonzero"); 27633 use.classList.add("mask"); 27634 } 27635 const use1 = DrawLayer._svgFactory.createElement("use"); 27636 root.append(use1); 27637 use1.setAttribute("href", `#${pathId}`); 27638 if (maskId) { 27639 use1.setAttribute("mask", `url(#${maskId})`); 27640 } 27641 const use2 = use1.cloneNode(); 27642 root.append(use2); 27643 use1.classList.add("mainOutline"); 27644 use2.classList.add("secondaryOutline"); 27645 this.updateProperties(root, properties); 27646 this.#mapping.set(id, root); 27647 return id; 27648 } 27649 finalizeDraw(id, properties) { 27650 this.#toUpdate.delete(id); 27651 this.updateProperties(id, properties); 27652 } 27653 updateProperties(elementOrId, properties) { 27654 if (!properties) { 27655 return; 27656 } 27657 const { 27658 root, 27659 bbox, 27660 rootClass, 27661 path 27662 } = properties; 27663 const element = typeof elementOrId === "number" ? this.#mapping.get(elementOrId) : elementOrId; 27664 if (!element) { 27665 return; 27666 } 27667 if (root) { 27668 this.#updateProperties(element, root); 27669 } 27670 if (bbox) { 27671 DrawLayer.#setBox(element, bbox); 27672 } 27673 if (rootClass) { 27674 const { 27675 classList 27676 } = element; 27677 for (const [className, value] of Object.entries(rootClass)) { 27678 classList.toggle(className, value); 27679 } 27680 } 27681 if (path) { 27682 const defs = element.firstElementChild; 27683 const pathElement = defs.firstElementChild; 27684 this.#updateProperties(pathElement, path); 27685 } 27686 } 27687 updateParent(id, layer) { 27688 if (layer === this) { 27689 return; 27690 } 27691 const root = this.#mapping.get(id); 27692 if (!root) { 27693 return; 27694 } 27695 layer.#parent.append(root); 27696 this.#mapping.delete(id); 27697 layer.#mapping.set(id, root); 27698 } 27699 remove(id) { 27700 this.#toUpdate.delete(id); 27701 if (this.#parent === null) { 27702 return; 27703 } 27704 this.#mapping.get(id).remove(); 27705 this.#mapping.delete(id); 27706 } 27707 destroy() { 27708 this.#parent = null; 27709 for (const root of this.#mapping.values()) { 27710 root.remove(); 27711 } 27712 this.#mapping.clear(); 27713 this.#toUpdate.clear(); 27714 this.#textLayerObserver?.disconnect(); 27715 this.#textLayerObserver = null; 27716 if (this.#textLayer) { 27717 const data = DrawLayer.#textLayers.get(this.#textLayer); 27718 if (data?.drawLayer === this) { 27719 DrawLayer.#cleanupTextLayerSelection(this.#textLayer); 27720 DrawLayer.#textLayers.delete(this.#textLayer); 27721 DrawLayer.#textLayerSet.delete(this.#textLayer); 27722 if (DrawLayer.#textLayerSet.size === 0) { 27723 DrawLayer.#selectionChangeAC?.abort(); 27724 DrawLayer.#selectionChangeAC = null; 27725 DrawLayer.#isSelecting = false; 27726 } 27727 } 27728 this.#textLayer = null; 27729 } 27730 } 27731} 27732 27733;// ./src/display/text_layer_images.js 27734 27735function percentage(value) { 27736 return `${(value * 100).toFixed(2)}%`; 27737} 27738class TextLayerImages { 27739 #coordinates = []; 27740 #coordinatesByElement = new Map(); 27741 #getPageCanvas = null; 27742 #minSize = 0; 27743 #pageWidth = 0; 27744 #pageHeight = 0; 27745 static #activeImage = null; 27746 constructor(minSize, coordinates, viewport, getPageCanvas) { 27747 this.#minSize = minSize; 27748 this.#coordinates = coordinates; 27749 this.#pageWidth = viewport.rawDims.pageWidth; 27750 this.#pageHeight = viewport.rawDims.pageHeight; 27751 this.#getPageCanvas = getPageCanvas; 27752 } 27753 render() { 27754 const container = document.createElement("div"); 27755 container.className = "textLayerImages"; 27756 for (let i = 0; i < this.#coordinates.length; i += 6) { 27757 const el = this.#createImagePlaceholder(this.#coordinates.subarray(i, i + 6)); 27758 if (el) { 27759 container.append(el); 27760 } 27761 } 27762 container.addEventListener("contextmenu", event => { 27763 if (!(event.target instanceof HTMLCanvasElement)) { 27764 return; 27765 } 27766 const imgElement = event.target; 27767 const coords = this.#coordinatesByElement.get(imgElement); 27768 if (!coords) { 27769 return; 27770 } 27771 const activeImage = TextLayerImages.#activeImage?.deref(); 27772 if (activeImage === imgElement) { 27773 return; 27774 } 27775 if (activeImage) { 27776 activeImage.width = 0; 27777 activeImage.height = 0; 27778 } 27779 TextLayerImages.#activeImage = new WeakRef(imgElement); 27780 const { 27781 inverseTransform, 27782 x1, 27783 y1, 27784 width, 27785 height 27786 } = coords; 27787 const pageCanvas = this.#getPageCanvas(); 27788 const imageX1 = Math.ceil(x1 * pageCanvas.width); 27789 const imageY1 = Math.ceil(y1 * pageCanvas.height); 27790 const imageX2 = Math.floor((x1 + width / this.#pageWidth) * pageCanvas.width); 27791 const imageY2 = Math.floor((y1 + height / this.#pageHeight) * pageCanvas.height); 27792 imgElement.width = imageX2 - imageX1; 27793 imgElement.height = imageY2 - imageY1; 27794 const ctx = imgElement.getContext("2d"); 27795 ctx.setTransform(...inverseTransform); 27796 ctx.translate(-imageX1, -imageY1); 27797 ctx.drawImage(pageCanvas, 0, 0); 27798 }); 27799 return container; 27800 } 27801 #createImagePlaceholder([x1, y1, x2, y2, x3, y3]) { 27802 const width = Math.hypot((x3 - x1) * this.#pageWidth, (y3 - y1) * this.#pageHeight); 27803 const height = Math.hypot((x2 - x1) * this.#pageWidth, (y2 - y1) * this.#pageHeight); 27804 if (width < this.#minSize || height < this.#minSize) { 27805 return null; 27806 } 27807 const transform = [(x3 - x1) * this.#pageWidth / width, (y3 - y1) * this.#pageHeight / width, (x2 - x1) * this.#pageWidth / height, (y2 - y1) * this.#pageHeight / height, 0, 0]; 27808 const inverseTransform = Util.inverseTransform(transform); 27809 const imgElement = document.createElement("canvas"); 27810 imgElement.className = "textLayerImagePlaceholder"; 27811 imgElement.width = 0; 27812 imgElement.height = 0; 27813 Object.assign(imgElement.style, { 27814 opacity: 0, 27815 position: "absolute", 27816 left: percentage(x1), 27817 top: percentage(y1), 27818 width: percentage(width / this.#pageWidth), 27819 height: percentage(height / this.#pageHeight), 27820 transformOrigin: "0% 0%", 27821 transform: `matrix(${transform.join(",")})` 27822 }); 27823 this.#coordinatesByElement.set(imgElement, { 27824 inverseTransform, 27825 width, 27826 height, 27827 x1, 27828 y1 27829 }); 27830 return imgElement; 27831 } 27832} 27833 27834;// ./src/pdf.js 27835 27836 27837 27838 27839 27840 27841 27842 27843 27844 27845 27846 27847 27848 27849 27850 27851 27852 27853{ 27854 globalThis._pdfjsTestingUtils = { 27855 HighlightOutliner: HighlightOutliner 27856 }; 27857} 27858globalThis.pdfjsLib = { 27859 AbortException: AbortException, 27860 AnnotationEditorLayer: AnnotationEditorLayer, 27861 AnnotationEditorParamsType: AnnotationEditorParamsType, 27862 AnnotationEditorType: AnnotationEditorType, 27863 AnnotationEditorUIManager: AnnotationEditorUIManager, 27864 AnnotationLayer: AnnotationLayer, 27865 AnnotationMode: AnnotationMode, 27866 AnnotationType: AnnotationType, 27867 applyOpacity: applyOpacity, 27868 build: build, 27869 ColorPicker: ColorPicker, 27870 createValidAbsoluteUrl: createValidAbsoluteUrl, 27871 CSSConstants: CSSConstants, 27872 DOMSVGFactory: DOMSVGFactory, 27873 DrawLayer: DrawLayer, 27874 FeatureTest: FeatureTest, 27875 fetchData: fetchData, 27876 findContrastColor: findContrastColor, 27877 getDocument: getDocument, 27878 getFilenameFromUrl: getFilenameFromUrl, 27879 getPdfFilenameFromUrl: getPdfFilenameFromUrl, 27880 getRGB: getRGB, 27881 getRGBA: getRGBA, 27882 getUuid: getUuid, 27883 GlobalWorkerOptions: GlobalWorkerOptions, 27884 ImageKind: ImageKind, 27885 InvalidPDFException: InvalidPDFException, 27886 isDataScheme: isDataScheme, 27887 isPdfFile: isPdfFile, 27888 isValidExplicitDest: isValidExplicitDest, 27889 makeArr: makeArr, 27890 makeMap: makeMap, 27891 makeObj: makeObj, 27892
27892MathClamp: MathClamp, 27893 noContextMenu: noContextMenu, 27894 normalizeUnicode: normalizeUnicode, 27895 OPS: OPS, 27896 OutputScale: OutputScale, 27897 PasswordException: PasswordException, 27898 PasswordResponses: PasswordResponses, 27899 PDFDataRangeTransport: PDFDataRangeTransport, 27900 PDFDateString: PDFDateString, 27901 PDFWorker: PDFWorker, 27902 PermissionFlag: PermissionFlag, 27903 PixelsPerInch: PixelsPerInch, 27904 RenderingCancelledException: RenderingCancelledException, 27905 renderRichText: renderRichText, 27906 ResponseException: ResponseException, 27907 setLayerDimensions: setLayerDimensions, 27908 shadow: shadow, 27909 SignatureExtractor: SignatureExtractor, 27910 stopEvent: stopEvent, 27911 SupportedImageMimeTypes: SupportedImageMimeTypes, 27912 TextLayer: TextLayer, 27913 TextLayerImages: TextLayerImages, 27914 TouchManager: TouchManager, 27915 updateUrlHash: updateUrlHash, 27916 Util: Util, 27917 VerbosityLevel: VerbosityLevel, 27918 version: version, 27919 XfaLayer: XfaLayer 27920}; 27921 27922export { AbortException, AnnotationEditorLayer, AnnotationEditorParamsType, AnnotationEditorType, AnnotationEditorUIManager, AnnotationLayer, AnnotationMode, AnnotationType, CSSConstants, ColorPicker, DOMSVGFactory, DrawLayer, FeatureTest, GlobalWorkerOptions, ImageKind, InvalidPDFException, MathClamp, OPS, OutputScale, PDFDataRangeTransport, PDFDateString, PDFWorker, PasswordException, PasswordResponses, PermissionFlag, PixelsPerInch, RenderingCancelledException, ResponseException, SignatureExtractor, SupportedImageMimeTypes, TextLayer, TextLayerImages, TouchManager, Util, VerbosityLevel, XfaLayer, applyOpacity, build, createValidAbsoluteUrl, fetchData, findContrastColor, getDocument, getFilenameFromUrl, getPdfFilenameFromUrl, getRGB, getRGBA, getUuid, isDataScheme, isPdfFile, isValidExplicitDest, makeArr, makeMap, makeObj, noContextMenu, normalizeUnicode, renderRichText, setLayerDimensions, shadow, stopEvent, updateUrlHash, version }; 27923 27924//# sourceMappingURL=pdf.mjs.map
Line numbers count LF bytes from the start of the resource, as the search results do. Vendor segments are library code the classifier recognised; they are stored but not indexed. Bytes are shown as Latin1 characters, one per byte.