UNPKG

64 kBJavaScriptView Raw
1/**
2 * @licstart The following is the entire license notice for the
3 * Javascript code in this page
4 *
5 * Copyright 2021 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"use strict";
23
24Object.defineProperty(exports, "__esModule", {
25 value: true
26});
27exports.AnnotationLayer = void 0;
28
29var _util = require("../shared/util.js");
30
31var _display_utils = require("./display_utils.js");
32
33var _annotation_storage = require("./annotation_storage.js");
34
35var _scripting_utils = require("../shared/scripting_utils.js");
36
37var _xfa_layer = require("./xfa_layer.js");
38
39const DEFAULT_TAB_INDEX = 1000;
40const GetElementsByNameSet = new WeakSet();
41
42class AnnotationElementFactory {
43 static create(parameters) {
44 const subtype = parameters.data.annotationType;
45
46 switch (subtype) {
47 case _util.AnnotationType.LINK:
48 return new LinkAnnotationElement(parameters);
49
50 case _util.AnnotationType.TEXT:
51 return new TextAnnotationElement(parameters);
52
53 case _util.AnnotationType.WIDGET:
54 const fieldType = parameters.data.fieldType;
55
56 switch (fieldType) {
57 case "Tx":
58 return new TextWidgetAnnotationElement(parameters);
59
60 case "Btn":
61 if (parameters.data.radioButton) {
62 return new RadioButtonWidgetAnnotationElement(parameters);
63 } else if (parameters.data.checkBox) {
64 return new CheckboxWidgetAnnotationElement(parameters);
65 }
66
67 return new PushButtonWidgetAnnotationElement(parameters);
68
69 case "Ch":
70 return new ChoiceWidgetAnnotationElement(parameters);
71 }
72
73 return new WidgetAnnotationElement(parameters);
74
75 case _util.AnnotationType.POPUP:
76 return new PopupAnnotationElement(parameters);
77
78 case _util.AnnotationType.FREETEXT:
79 return new FreeTextAnnotationElement(parameters);
80
81 case _util.AnnotationType.LINE:
82 return new LineAnnotationElement(parameters);
83
84 case _util.AnnotationType.SQUARE:
85 return new SquareAnnotationElement(parameters);
86
87 case _util.AnnotationType.CIRCLE:
88 return new CircleAnnotationElement(parameters);
89
90 case _util.AnnotationType.POLYLINE:
91 return new PolylineAnnotationElement(parameters);
92
93 case _util.AnnotationType.CARET:
94 return new CaretAnnotationElement(parameters);
95
96 case _util.AnnotationType.INK:
97 return new InkAnnotationElement(parameters);
98
99 case _util.AnnotationType.POLYGON:
100 return new PolygonAnnotationElement(parameters);
101
102 case _util.AnnotationType.HIGHLIGHT:
103 return new HighlightAnnotationElement(parameters);
104
105 case _util.AnnotationType.UNDERLINE:
106 return new UnderlineAnnotationElement(parameters);
107
108 case _util.AnnotationType.SQUIGGLY:
109 return new SquigglyAnnotationElement(parameters);
110
111 case _util.AnnotationType.STRIKEOUT:
112 return new StrikeOutAnnotationElement(parameters);
113
114 case _util.AnnotationType.STAMP:
115 return new StampAnnotationElement(parameters);
116
117 case _util.AnnotationType.FILEATTACHMENT:
118 return new FileAttachmentAnnotationElement(parameters);
119
120 default:
121 return new AnnotationElement(parameters);
122 }
123 }
124
125}
126
127class AnnotationElement {
128 constructor(parameters, {
129 isRenderable = false,
130 ignoreBorder = false,
131 createQuadrilaterals = false
132 } = {}) {
133 this.isRenderable = isRenderable;
134 this.data = parameters.data;
135 this.layer = parameters.layer;
136 this.page = parameters.page;
137 this.viewport = parameters.viewport;
138 this.linkService = parameters.linkService;
139 this.downloadManager = parameters.downloadManager;
140 this.imageResourcesPath = parameters.imageResourcesPath;
141 this.renderForms = parameters.renderForms;
142 this.svgFactory = parameters.svgFactory;
143 this.annotationStorage = parameters.annotationStorage;
144 this.enableScripting = parameters.enableScripting;
145 this.hasJSActions = parameters.hasJSActions;
146 this._fieldObjects = parameters.fieldObjects;
147 this._mouseState = parameters.mouseState;
148
149 if (isRenderable) {
150 this.container = this._createContainer(ignoreBorder);
151 }
152
153 if (createQuadrilaterals) {
154 this.quadrilaterals = this._createQuadrilaterals(ignoreBorder);
155 }
156 }
157
158 _createContainer(ignoreBorder = false) {
159 const data = this.data,
160 page = this.page,
161 viewport = this.viewport;
162 const container = document.createElement("section");
163 let width = data.rect[2] - data.rect[0];
164 let height = data.rect[3] - data.rect[1];
165 container.setAttribute("data-annotation-id", data.id);
166
167 const rect = _util.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]]);
168
169 if (data.hasOwnCanvas) {
170 const transform = viewport.transform.slice();
171
172 const [scaleX, scaleY] = _util.Util.singularValueDecompose2dScale(transform);
173
174 width = Math.ceil(width * scaleX);
175 height = Math.ceil(height * scaleY);
176 rect[0] *= scaleX;
177 rect[1] *= scaleY;
178
179 for (let i = 0; i < 4; i++) {
180 transform[i] = Math.sign(transform[i]);
181 }
182
183 container.style.transform = `matrix(${transform.join(",")})`;
184 } else {
185 container.style.transform = `matrix(${viewport.transform.join(",")})`;
186 }
187
188 container.style.transformOrigin = `${-rect[0]}px ${-rect[1]}px`;
189
190 if (!ignoreBorder && data.borderStyle.width > 0) {
191 container.style.borderWidth = `${data.borderStyle.width}px`;
192
193 if (data.borderStyle.style !== _util.AnnotationBorderStyleType.UNDERLINE) {
194 width -= 2 * data.borderStyle.width;
195 height -= 2 * data.borderStyle.width;
196 }
197
198 const horizontalRadius = data.borderStyle.horizontalCornerRadius;
199 const verticalRadius = data.borderStyle.verticalCornerRadius;
200
201 if (horizontalRadius > 0 || verticalRadius > 0) {
202 const radius = `${horizontalRadius}px / ${verticalRadius}px`;
203 container.style.borderRadius = radius;
204 }
205
206 switch (data.borderStyle.style) {
207 case _util.AnnotationBorderStyleType.SOLID:
208 container.style.borderStyle = "solid";
209 break;
210
211 case _util.AnnotationBorderStyleType.DASHED:
212 container.style.borderStyle = "dashed";
213 break;
214
215 case _util.AnnotationBorderStyleType.BEVELED:
216 (0, _util.warn)("Unimplemented border style: beveled");
217 break;
218
219 case _util.AnnotationBorderStyleType.INSET:
220 (0, _util.warn)("Unimplemented border style: inset");
221 break;
222
223 case _util.AnnotationBorderStyleType.UNDERLINE:
224 container.style.borderBottomStyle = "solid";
225 break;
226
227 default:
228 break;
229 }
230
231 const borderColor = data.borderColor || data.color || null;
232
233 if (borderColor) {
234 container.style.borderColor = _util.Util.makeHexColor(data.color[0] | 0, data.color[1] | 0, data.color[2] | 0);
235 } else {
236 container.style.borderWidth = 0;
237 }
238 }
239
240 container.style.left = `${rect[0]}px`;
241 container.style.top = `${rect[1]}px`;
242
243 if (data.hasOwnCanvas) {
244 container.style.width = container.style.height = "auto";
245 } else {
246 container.style.width = `${width}px`;
247 container.style.height = `${height}px`;
248 }
249
250 return container;
251 }
252
253 _createQuadrilaterals(ignoreBorder = false) {
254 if (!this.data.quadPoints) {
255 return null;
256 }
257
258 const quadrilaterals = [];
259 const savedRect = this.data.rect;
260
261 for (const quadPoint of this.data.quadPoints) {
262 this.data.rect = [quadPoint[2].x, quadPoint[2].y, quadPoint[1].x, quadPoint[1].y];
263 quadrilaterals.push(this._createContainer(ignoreBorder));
264 }
265
266 this.data.rect = savedRect;
267 return quadrilaterals;
268 }
269
270 _createPopup(trigger, data) {
271 let container = this.container;
272
273 if (this.quadrilaterals) {
274 trigger = trigger || this.quadrilaterals;
275 container = this.quadrilaterals[0];
276 }
277
278 if (!trigger) {
279 trigger = document.createElement("div");
280 trigger.style.height = container.style.height;
281 trigger.style.width = container.style.width;
282 container.appendChild(trigger);
283 }
284
285 const popupElement = new PopupElement({
286 container,
287 trigger,
288 color: data.color,
289 titleObj: data.titleObj,
290 modificationDate: data.modificationDate,
291 contentsObj: data.contentsObj,
292 richText: data.richText,
293 hideWrapper: true
294 });
295 const popup = popupElement.render();
296 popup.style.left = container.style.width;
297 container.appendChild(popup);
298 }
299
300 _renderQuadrilaterals(className) {
301 for (const quadrilateral of this.quadrilaterals) {
302 quadrilateral.className = className;
303 }
304
305 return this.quadrilaterals;
306 }
307
308 render() {
309 (0, _util.unreachable)("Abstract method `AnnotationElement.render` called");
310 }
311
312 _getElementsByName(name, skipId = null) {
313 const fields = [];
314
315 if (this._fieldObjects) {
316 const fieldObj = this._fieldObjects[name];
317
318 if (fieldObj) {
319 for (const {
320 page,
321 id,
322 exportValues
323 } of fieldObj) {
324 if (page === -1) {
325 continue;
326 }
327
328 if (id === skipId) {
329 continue;
330 }
331
332 const exportValue = typeof exportValues === "string" ? exportValues : null;
333 const domElement = document.getElementById(id);
334
335 if (domElement && !GetElementsByNameSet.has(domElement)) {
336 (0, _util.warn)(`_getElementsByName - element not allowed: ${id}`);
337 continue;
338 }
339
340 fields.push({
341 id,
342 exportValue,
343 domElement
344 });
345 }
346 }
347
348 return fields;
349 }
350
351 for (const domElement of document.getElementsByName(name)) {
352 const {
353 id,
354 exportValue
355 } = domElement;
356
357 if (id === skipId) {
358 continue;
359 }
360
361 if (!GetElementsByNameSet.has(domElement)) {
362 continue;
363 }
364
365 fields.push({
366 id,
367 exportValue,
368 domElement
369 });
370 }
371
372 return fields;
373 }
374
375 static get platform() {
376 const platform = typeof navigator !== "undefined" ? navigator.platform : "";
377 return (0, _util.shadow)(this, "platform", {
378 isWin: platform.includes("Win"),
379 isMac: platform.includes("Mac")
380 });
381 }
382
383}
384
385class LinkAnnotationElement extends AnnotationElement {
386 constructor(parameters, options = null) {
387 const isRenderable = !!(parameters.data.url || parameters.data.dest || parameters.data.action || parameters.data.isTooltipOnly || parameters.data.resetForm || parameters.data.actions && (parameters.data.actions.Action || parameters.data.actions["Mouse Up"] || parameters.data.actions["Mouse Down"]));
388 super(parameters, {
389 isRenderable,
390 ignoreBorder: !!options?.ignoreBorder,
391 createQuadrilaterals: true
392 });
393 }
394
395 render() {
396 const {
397 data,
398 linkService
399 } = this;
400 const link = document.createElement("a");
401
402 if (data.url) {
403 if (!linkService.addLinkAttributes) {
404 (0, _util.warn)("LinkAnnotationElement.render - missing `addLinkAttributes`-method on the `linkService`-instance.");
405 }
406
407 linkService.addLinkAttributes?.(link, data.url, data.newWindow);
408 } else if (data.action) {
409 this._bindNamedAction(link, data.action);
410 } else if (data.dest) {
411 this._bindLink(link, data.dest);
412 } else {
413 let hasClickAction = false;
414
415 if (data.actions && (data.actions.Action || data.actions["Mouse Up"] || data.actions["Mouse Down"]) && this.enableScripting && this.hasJSActions) {
416 hasClickAction = true;
417
418 this._bindJSAction(link, data);
419 }
420
421 if (data.resetForm) {
422 this._bindResetFormAction(link, data.resetForm);
423 } else if (!hasClickAction) {
424 this._bindLink(link, "");
425 }
426 }
427
428 if (this.quadrilaterals) {
429 return this._renderQuadrilaterals("linkAnnotation").map((quadrilateral, index) => {
430 const linkElement = index === 0 ? link : link.cloneNode();
431 quadrilateral.appendChild(linkElement);
432 return quadrilateral;
433 });
434 }
435
436 this.container.className = "linkAnnotation";
437 this.container.appendChild(link);
438 return this.container;
439 }
440
441 _bindLink(link, destination) {
442 link.href = this.linkService.getDestinationHash(destination);
443
444 link.onclick = () => {
445 if (destination) {
446 this.linkService.goToDestination(destination);
447 }
448
449 return false;
450 };
451
452 if (destination || destination === "") {
453 link.className = "internalLink";
454 }
455 }
456
457 _bindNamedAction(link, action) {
458 link.href = this.linkService.getAnchorUrl("");
459
460 link.onclick = () => {
461 this.linkService.executeNamedAction(action);
462 return false;
463 };
464
465 link.className = "internalLink";
466 }
467
468 _bindJSAction(link, data) {
469 link.href = this.linkService.getAnchorUrl("");
470 const map = new Map([["Action", "onclick"], ["Mouse Up", "onmouseup"], ["Mouse Down", "onmousedown"]]);
471
472 for (const name of Object.keys(data.actions)) {
473 const jsName = map.get(name);
474
475 if (!jsName) {
476 continue;
477 }
478
479 link[jsName] = () => {
480 this.linkService.eventBus?.dispatch("dispatcheventinsandbox", {
481 source: this,
482 detail: {
483 id: data.id,
484 name
485 }
486 });
487 return false;
488 };
489 }
490
491 if (!link.onclick) {
492 link.onclick = () => false;
493 }
494
495 link.className = "internalLink";
496 }
497
498 _bindResetFormAction(link, resetForm) {
499 const otherClickAction = link.onclick;
500
501 if (!otherClickAction) {
502 link.href = this.linkService.getAnchorUrl("");
503 }
504
505 link.className = "internalLink";
506
507 if (!this._fieldObjects) {
508 (0, _util.warn)(`_bindResetFormAction - "resetForm" action not supported, ` + "ensure that the `fieldObjects` parameter is provided.");
509
510 if (!otherClickAction) {
511 link.onclick = () => false;
512 }
513
514 return;
515 }
516
517 link.onclick = () => {
518 if (otherClickAction) {
519 otherClickAction();
520 }
521
522 const {
523 fields: resetFormFields,
524 refs: resetFormRefs,
525 include
526 } = resetForm;
527 const allFields = [];
528
529 if (resetFormFields.length !== 0 || resetFormRefs.length !== 0) {
530 const fieldIds = new Set(resetFormRefs);
531
532 for (const fieldName of resetFormFields) {
533 const fields = this._fieldObjects[fieldName] || [];
534
535 for (const {
536 id
537 } of fields) {
538 fieldIds.add(id);
539 }
540 }
541
542 for (const fields of Object.values(this._fieldObjects)) {
543 for (const field of fields) {
544 if (fieldIds.has(field.id) === include) {
545 allFields.push(field);
546 }
547 }
548 }
549 } else {
550 for (const fields of Object.values(this._fieldObjects)) {
551 allFields.push(...fields);
552 }
553 }
554
555 const storage = this.annotationStorage;
556 const allIds = [];
557
558 for (const field of allFields) {
559 const {
560 id
561 } = field;
562 allIds.push(id);
563
564 switch (field.type) {
565 case "text":
566 {
567 const value = field.defaultValue || "";
568 storage.setValue(id, {
569 value,
570 valueAsString: value
571 });
572 break;
573 }
574
575 case "checkbox":
576 case "radiobutton":
577 {
578 const value = field.defaultValue === field.exportValues;
579 storage.setValue(id, {
580 value
581 });
582 break;
583 }
584
585 case "combobox":
586 case "listbox":
587 {
588 const value = field.defaultValue || "";
589 storage.setValue(id, {
590 value
591 });
592 break;
593 }
594
595 default:
596 continue;
597 }
598
599 const domElement = document.getElementById(id);
600
601 if (!domElement || !GetElementsByNameSet.has(domElement)) {
602 continue;
603 }
604
605 domElement.dispatchEvent(new Event("resetform"));
606 }
607
608 if (this.enableScripting) {
609 this.linkService.eventBus?.dispatch("dispatcheventinsandbox", {
610 source: this,
611 detail: {
612 id: "app",
613 ids: allIds,
614 name: "ResetForm"
615 }
616 });
617 }
618
619 return false;
620 };
621 }
622
623}
624
625class TextAnnotationElement extends AnnotationElement {
626 constructor(parameters) {
627 const isRenderable = !!(parameters.data.hasPopup || parameters.data.titleObj?.str || parameters.data.contentsObj?.str || parameters.data.richText?.str);
628 super(parameters, {
629 isRenderable
630 });
631 }
632
633 render() {
634 this.container.className = "textAnnotation";
635 const image = document.createElement("img");
636 image.style.height = this.container.style.height;
637 image.style.width = this.container.style.width;
638 image.src = this.imageResourcesPath + "annotation-" + this.data.name.toLowerCase() + ".svg";
639 image.alt = "[{{type}} Annotation]";
640 image.dataset.l10nId = "text_annotation_type";
641 image.dataset.l10nArgs = JSON.stringify({
642 type: this.data.name
643 });
644
645 if (!this.data.hasPopup) {
646 this._createPopup(image, this.data);
647 }
648
649 this.container.appendChild(image);
650 return this.container;
651 }
652
653}
654
655class WidgetAnnotationElement extends AnnotationElement {
656 render() {
657 if (this.data.alternativeText) {
658 this.container.title = this.data.alternativeText;
659 }
660
661 return this.container;
662 }
663
664 _getKeyModifier(event) {
665 const {
666 isWin,
667 isMac
668 } = AnnotationElement.platform;
669 return isWin && event.ctrlKey || isMac && event.metaKey;
670 }
671
672 _setEventListener(element, baseName, eventName, valueGetter) {
673 if (baseName.includes("mouse")) {
674 element.addEventListener(baseName, event => {
675 this.linkService.eventBus?.dispatch("dispatcheventinsandbox", {
676 source: this,
677 detail: {
678 id: this.data.id,
679 name: eventName,
680 value: valueGetter(event),
681 shift: event.shiftKey,
682 modifier: this._getKeyModifier(event)
683 }
684 });
685 });
686 } else {
687 element.addEventListener(baseName, event => {
688 this.linkService.eventBus?.dispatch("dispatcheventinsandbox", {
689 source: this,
690 detail: {
691 id: this.data.id,
692 name: eventName,
693 value: event.target.checked
694 }
695 });
696 });
697 }
698 }
699
700 _setEventListeners(element, names, getter) {
701 for (const [baseName, eventName] of names) {
702 if (eventName === "Action" || this.data.actions?.[eventName]) {
703 this._setEventListener(element, baseName, eventName, getter);
704 }
705 }
706 }
707
708 _setBackgroundColor(element) {
709 const color = this.data.backgroundColor || null;
710 element.style.backgroundColor = color === null ? "transparent" : _util.Util.makeHexColor(color[0], color[1], color[2]);
711 }
712
713 _dispatchEventFromSandbox(actions, jsEvent) {
714 const setColor = (jsName, styleName, event) => {
715 const color = event.detail[jsName];
716 event.target.style[styleName] = _scripting_utils.ColorConverters[`${color[0]}_HTML`](color.slice(1));
717 };
718
719 const commonActions = {
720 display: event => {
721 const hidden = event.detail.display % 2 === 1;
722 event.target.style.visibility = hidden ? "hidden" : "visible";
723 this.annotationStorage.setValue(this.data.id, {
724 hidden,
725 print: event.detail.display === 0 || event.detail.display === 3
726 });
727 },
728 print: event => {
729 this.annotationStorage.setValue(this.data.id, {
730 print: event.detail.print
731 });
732 },
733 hidden: event => {
734 event.target.style.visibility = event.detail.hidden ? "hidden" : "visible";
735 this.annotationStorage.setValue(this.data.id, {
736 hidden: event.detail.hidden
737 });
738 },
739 focus: event => {
740 setTimeout(() => event.target.focus({
741 preventScroll: false
742 }), 0);
743 },
744 userName: event => {
745 event.target.title = event.detail.userName;
746 },
747 readonly: event => {
748 if (event.detail.readonly) {
749 event.target.setAttribute("readonly", "");
750 } else {
751 event.target.removeAttribute("readonly");
752 }
753 },
754 required: event => {
755 if (event.detail.required) {
756 event.target.setAttribute("required", "");
757 } else {
758 event.target.removeAttribute("required");
759 }
760 },
761 bgColor: event => {
762 setColor("bgColor", "backgroundColor", event);
763 },
764 fillColor: event => {
765 setColor("fillColor", "backgroundColor", event);
766 },
767 fgColor: event => {
768 setColor("fgColor", "color", event);
769 },
770 textColor: event => {
771 setColor("textColor", "color", event);
772 },
773 borderColor: event => {
774 setColor("borderColor", "borderColor", event);
775 },
776 strokeColor: event => {
777 setColor("strokeColor", "borderColor", event);
778 }
779 };
780
781 for (const name of Object.keys(jsEvent.detail)) {
782 const action = actions[name] || commonActions[name];
783
784 if (action) {
785 action(jsEvent);
786 }
787 }
788 }
789
790}
791
792class TextWidgetAnnotationElement extends WidgetAnnotationElement {
793 constructor(parameters) {
794 const isRenderable = parameters.renderForms || !parameters.data.hasAppearance && !!parameters.data.fieldValue;
795 super(parameters, {
796 isRenderable
797 });
798 }
799
800 setPropertyOnSiblings(base, key, value, keyInStorage) {
801 const storage = this.annotationStorage;
802
803 for (const element of this._getElementsByName(base.name, base.id)) {
804 if (element.domElement) {
805 element.domElement[key] = value;
806 }
807
808 storage.setValue(element.id, {
809 [keyInStorage]: value
810 });
811 }
812 }
813
814 render() {
815 const storage = this.annotationStorage;
816 const id = this.data.id;
817 this.container.className = "textWidgetAnnotation";
818 let element = null;
819
820 if (this.renderForms) {
821 const storedData = storage.getValue(id, {
822 value: this.data.fieldValue,
823 valueAsString: this.data.fieldValue
824 });
825 const textContent = storedData.valueAsString || storedData.value || "";
826 const elementData = {
827 userValue: null,
828 formattedValue: null,
829 beforeInputSelectionRange: null,
830 beforeInputValue: null
831 };
832
833 if (this.data.multiLine) {
834 element = document.createElement("textarea");
835 element.textContent = textContent;
836 } else {
837 element = document.createElement("input");
838 element.type = "text";
839 element.setAttribute("value", textContent);
840 }
841
842 GetElementsByNameSet.add(element);
843 element.disabled = this.data.readOnly;
844 element.name = this.data.fieldName;
845 element.tabIndex = DEFAULT_TAB_INDEX;
846 elementData.userValue = textContent;
847 element.setAttribute("id", id);
848 element.addEventListener("input", event => {
849 storage.setValue(id, {
850 value: event.target.value
851 });
852 this.setPropertyOnSiblings(element, "value", event.target.value, "value");
853 });
854 element.addEventListener("resetform", event => {
855 const defaultValue = this.data.defaultFieldValue || "";
856 element.value = elementData.userValue = defaultValue;
857 delete elementData.formattedValue;
858 });
859
860 let blurListener = event => {
861 if (elementData.formattedValue) {
862 event.target.value = elementData.formattedValue;
863 }
864
865 event.target.scrollLeft = 0;
866 elementData.beforeInputSelectionRange = null;
867 };
868
869 if (this.enableScripting && this.hasJSActions) {
870 element.addEventListener("focus", event => {
871 if (elementData.userValue) {
872 event.target.value = elementData.userValue;
873 }
874 });
875 element.addEventListener("updatefromsandbox", jsEvent => {
876 const actions = {
877 value(event) {
878 elementData.userValue = event.detail.value || "";
879 storage.setValue(id, {
880 value: elementData.userValue.toString()
881 });
882
883 if (!elementData.formattedValue) {
884 event.target.value = elementData.userValue;
885 }
886 },
887
888 valueAsString(event) {
889 elementData.formattedValue = event.detail.valueAsString || "";
890
891 if (event.target !== document.activeElement) {
892 event.target.value = elementData.formattedValue;
893 }
894
895 storage.setValue(id, {
896 formattedValue: elementData.formattedValue
897 });
898 },
899
900 selRange(event) {
901 const [selStart, selEnd] = event.detail.selRange;
902
903 if (selStart >= 0 && selEnd < event.target.value.length) {
904 event.target.setSelectionRange(selStart, selEnd);
905 }
906 }
907
908 };
909
910 this._dispatchEventFromSandbox(actions, jsEvent);
911 });
912 element.addEventListener("keydown", event => {
913 elementData.beforeInputValue = event.target.value;
914 let commitKey = -1;
915
916 if (event.key === "Escape") {
917 commitKey = 0;
918 } else if (event.key === "Enter") {
919 commitKey = 2;
920 } else if (event.key === "Tab") {
921 commitKey = 3;
922 }
923
924 if (commitKey === -1) {
925 return;
926 }
927
928 elementData.userValue = event.target.value;
929 this.linkService.eventBus?.dispatch("dispatcheventinsandbox", {
930 source: this,
931 detail: {
932 id,
933 name: "Keystroke",
934 value: event.target.value,
935 willCommit: true,
936 commitKey,
937 selStart: event.target.selectionStart,
938 selEnd: event.target.selectionEnd
939 }
940 });
941 });
942 const _blurListener = blurListener;
943 blurListener = null;
944 element.addEventListener("blur", event => {
945 if (this._mouseState.isDown) {
946 elementData.userValue = event.target.value;
947 this.linkService.eventBus?.dispatch("dispatcheventinsandbox", {
948 source: this,
949 detail: {
950 id,
951 name: "Keystroke",
952 value: event.target.value,
953 willCommit: true,
954 commitKey: 1,
955 selStart: event.target.selectionStart,
956 selEnd: event.target.selectionEnd
957 }
958 });
959 }
960
961 _blurListener(event);
962 });
963 element.addEventListener("mousedown", event => {
964 elementData.beforeInputValue = event.target.value;
965 elementData.beforeInputSelectionRange = null;
966 });
967 element.addEventListener("keyup", event => {
968 if (event.target.selectionStart === event.target.selectionEnd) {
969 elementData.beforeInputSelectionRange = null;
970 }
971 });
972 element.addEventListener("select", event => {
973 elementData.beforeInputSelectionRange = [event.target.selectionStart, event.target.selectionEnd];
974 });
975
976 if (this.data.actions?.Keystroke) {
977 element.addEventListener("input", event => {
978 let selStart = -1;
979 let selEnd = -1;
980
981 if (elementData.beforeInputSelectionRange) {
982 [selStart, selEnd] = elementData.beforeInputSelectionRange;
983 }
984
985 this.linkService.eventBus?.dispatch("dispatcheventinsandbox", {
986 source: this,
987 detail: {
988 id,
989 name: "Keystroke",
990 value: elementData.beforeInputValue,
991 change: event.data,
992 willCommit: false,
993 selStart,
994 selEnd
995 }
996 });
997 });
998 }
999
1000 this._setEventListeners(element, [["focus", "Focus"], ["blur", "Blur"], ["mousedown", "Mouse Down"], ["mouseenter", "Mouse Enter"], ["mouseleave", "Mouse Exit"], ["mouseup", "Mouse Up"]], event => event.target.value);
1001 }
1002
1003 if (blurListener) {
1004 element.addEventListener("blur", blurListener);
1005 }
1006
1007 if (this.data.maxLen !== null) {
1008 element.maxLength = this.data.maxLen;
1009 }
1010
1011 if (this.data.comb) {
1012 const fieldWidth = this.data.rect[2] - this.data.rect[0];
1013 const combWidth = fieldWidth / this.data.maxLen;
1014 element.classList.add("comb");
1015 element.style.letterSpacing = `calc(${combWidth}px - 1ch)`;
1016 }
1017 } else {
1018 element = document.createElement("div");
1019 element.textContent = this.data.fieldValue;
1020 element.style.verticalAlign = "middle";
1021 element.style.display = "table-cell";
1022 }
1023
1024 this._setTextStyle(element);
1025
1026 this._setBackgroundColor(element);
1027
1028 this.container.appendChild(element);
1029 return this.container;
1030 }
1031
1032 _setTextStyle(element) {
1033 const TEXT_ALIGNMENT = ["left", "center", "right"];
1034 const {
1035 fontSize,
1036 fontColor
1037 } = this.data.defaultAppearanceData;
1038 const style = element.style;
1039
1040 if (fontSize) {
1041 style.fontSize = `${fontSize}px`;
1042 }
1043
1044 style.color = _util.Util.makeHexColor(fontColor[0], fontColor[1], fontColor[2]);
1045
1046 if (this.data.textAlignment !== null) {
1047 style.textAlign = TEXT_ALIGNMENT[this.data.textAlignment];
1048 }
1049 }
1050
1051}
1052
1053class CheckboxWidgetAnnotationElement extends WidgetAnnotationElement {
1054 constructor(parameters) {
1055 super(parameters, {
1056 isRenderable: parameters.renderForms
1057 });
1058 }
1059
1060 render() {
1061 const storage = this.annotationStorage;
1062 const data = this.data;
1063 const id = data.id;
1064 let value = storage.getValue(id, {
1065 value: data.exportValue === data.fieldValue
1066 }).value;
1067
1068 if (typeof value === "string") {
1069 value = value !== "Off";
1070 storage.setValue(id, {
1071 value
1072 });
1073 }
1074
1075 this.container.className = "buttonWidgetAnnotation checkBox";
1076 const element = document.createElement("input");
1077 GetElementsByNameSet.add(element);
1078 element.disabled = data.readOnly;
1079 element.type = "checkbox";
1080 element.name = data.fieldName;
1081
1082 if (value) {
1083 element.setAttribute("checked", true);
1084 }
1085
1086 element.setAttribute("id", id);
1087 element.setAttribute("exportValue", data.exportValue);
1088 element.tabIndex = DEFAULT_TAB_INDEX;
1089 element.addEventListener("change", event => {
1090 const {
1091 name,
1092 checked
1093 } = event.target;
1094
1095 for (const checkbox of this._getElementsByName(name, id)) {
1096 const curChecked = checked && checkbox.exportValue === data.exportValue;
1097
1098 if (checkbox.domElement) {
1099 checkbox.domElement.checked = curChecked;
1100 }
1101
1102 storage.setValue(checkbox.id, {
1103 value: curChecked
1104 });
1105 }
1106
1107 storage.setValue(id, {
1108 value: checked
1109 });
1110 });
1111 element.addEventListener("resetform", event => {
1112 const defaultValue = data.defaultFieldValue || "Off";
1113 event.target.checked = defaultValue === data.exportValue;
1114 });
1115
1116 if (this.enableScripting && this.hasJSActions) {
1117 element.addEventListener("updatefromsandbox", jsEvent => {
1118 const actions = {
1119 value(event) {
1120 event.target.checked = event.detail.value !== "Off";
1121 storage.setValue(id, {
1122 value: event.target.checked
1123 });
1124 }
1125
1126 };
1127
1128 this._dispatchEventFromSandbox(actions, jsEvent);
1129 });
1130
1131 this._setEventListeners(element, [["change", "Validate"], ["change", "Action"], ["focus", "Focus"], ["blur", "Blur"], ["mousedown", "Mouse Down"], ["mouseenter", "Mouse Enter"], ["mouseleave", "Mouse Exit"], ["mouseup", "Mouse Up"]], event => event.target.checked);
1132 }
1133
1134 this._setBackgroundColor(element);
1135
1136 this.container.appendChild(element);
1137 return this.container;
1138 }
1139
1140}
1141
1142class RadioButtonWidgetAnnotationElement extends WidgetAnnotationElement {
1143 constructor(parameters) {
1144 super(parameters, {
1145 isRenderable: parameters.renderForms
1146 });
1147 }
1148
1149 render() {
1150 this.container.className = "buttonWidgetAnnotation radioButton";
1151 const storage = this.annotationStorage;
1152 const data = this.data;
1153 const id = data.id;
1154 let value = storage.getValue(id, {
1155 value: data.fieldValue === data.buttonValue
1156 }).value;
1157
1158 if (typeof value === "string") {
1159 value = value !== data.buttonValue;
1160 storage.setValue(id, {
1161 value
1162 });
1163 }
1164
1165 const element = document.createElement("input");
1166 GetElementsByNameSet.add(element);
1167 element.disabled = data.readOnly;
1168 element.type = "radio";
1169 element.name = data.fieldName;
1170
1171 if (value) {
1172 element.setAttribute("checked", true);
1173 }
1174
1175 element.setAttribute("id", id);
1176 element.tabIndex = DEFAULT_TAB_INDEX;
1177 element.addEventListener("change", event => {
1178 const {
1179 name,
1180 checked
1181 } = event.target;
1182
1183 for (const radio of this._getElementsByName(name, id)) {
1184 storage.setValue(radio.id, {
1185 value: false
1186 });
1187 }
1188
1189 storage.setValue(id, {
1190 value: checked
1191 });
1192 });
1193 element.addEventListener("resetform", event => {
1194 const defaultValue = data.defaultFieldValue;
1195 event.target.checked = defaultValue !== null && defaultValue !== undefined && defaultValue === data.buttonValue;
1196 });
1197
1198 if (this.enableScripting && this.hasJSActions) {
1199 const pdfButtonValue = data.buttonValue;
1200 element.addEventListener("updatefromsandbox", jsEvent => {
1201 const actions = {
1202 value: event => {
1203 const checked = pdfButtonValue === event.detail.value;
1204
1205 for (const radio of this._getElementsByName(event.target.name)) {
1206 const curChecked = checked && radio.id === id;
1207
1208 if (radio.domElement) {
1209 radio.domElement.checked = curChecked;
1210 }
1211
1212 storage.setValue(radio.id, {
1213 value: curChecked
1214 });
1215 }
1216 }
1217 };
1218
1219 this._dispatchEventFromSandbox(actions, jsEvent);
1220 });
1221
1222 this._setEventListeners(element, [["change", "Validate"], ["change", "Action"], ["focus", "Focus"], ["blur", "Blur"], ["mousedown", "Mouse Down"], ["mouseenter", "Mouse Enter"], ["mouseleave", "Mouse Exit"], ["mouseup", "Mouse Up"]], event => event.target.checked);
1223 }
1224
1225 this._setBackgroundColor(element);
1226
1227 this.container.appendChild(element);
1228 return this.container;
1229 }
1230
1231}
1232
1233class PushButtonWidgetAnnotationElement extends LinkAnnotationElement {
1234 constructor(parameters) {
1235 super(parameters, {
1236 ignoreBorder: parameters.data.hasAppearance
1237 });
1238 }
1239
1240 render() {
1241 const container = super.render();
1242 container.className = "buttonWidgetAnnotation pushButton";
1243
1244 if (this.data.alternativeText) {
1245 container.title = this.data.alternativeText;
1246 }
1247
1248 return container;
1249 }
1250
1251}
1252
1253class ChoiceWidgetAnnotationElement extends WidgetAnnotationElement {
1254 constructor(parameters) {
1255 super(parameters, {
1256 isRenderable: parameters.renderForms
1257 });
1258 }
1259
1260 render() {
1261 this.container.className = "choiceWidgetAnnotation";
1262 const storage = this.annotationStorage;
1263 const id = this.data.id;
1264 storage.getValue(id, {
1265 value: this.data.fieldValue.length > 0 ? this.data.fieldValue[0] : undefined
1266 });
1267 let {
1268 fontSize
1269 } = this.data.defaultAppearanceData;
1270
1271 if (!fontSize) {
1272 fontSize = 9;
1273 }
1274
1275 const fontSizeStyle = `calc(${fontSize}px * var(--zoom-factor))`;
1276 const selectElement = document.createElement("select");
1277 GetElementsByNameSet.add(selectElement);
1278 selectElement.disabled = this.data.readOnly;
1279 selectElement.name = this.data.fieldName;
1280 selectElement.setAttribute("id", id);
1281 selectElement.tabIndex = DEFAULT_TAB_INDEX;
1282 selectElement.style.fontSize = `${fontSize}px`;
1283
1284 if (!this.data.combo) {
1285 selectElement.size = this.data.options.length;
1286
1287 if (this.data.multiSelect) {
1288 selectElement.multiple = true;
1289 }
1290 }
1291
1292 selectElement.addEventListener("resetform", event => {
1293 const defaultValue = this.data.defaultFieldValue;
1294
1295 for (const option of selectElement.options) {
1296 option.selected = option.value === defaultValue;
1297 }
1298 });
1299
1300 for (const option of this.data.options) {
1301 const optionElement = document.createElement("option");
1302 optionElement.textContent = option.displayValue;
1303 optionElement.value = option.exportValue;
1304
1305 if (this.data.combo) {
1306 optionElement.style.fontSize = fontSizeStyle;
1307 }
1308
1309 if (this.data.fieldValue.includes(option.exportValue)) {
1310 optionElement.setAttribute("selected", true);
1311 }
1312
1313 selectElement.appendChild(optionElement);
1314 }
1315
1316 const getValue = (event, isExport) => {
1317 const name = isExport ? "value" : "textContent";
1318 const options = event.target.options;
1319
1320 if (!event.target.multiple) {
1321 return options.selectedIndex === -1 ? null : options[options.selectedIndex][name];
1322 }
1323
1324 return Array.prototype.filter.call(options, option => option.selected).map(option => option[name]);
1325 };
1326
1327 const getItems = event => {
1328 const options = event.target.options;
1329 return Array.prototype.map.call(options, option => {
1330 return {
1331 displayValue: option.textContent,
1332 exportValue: option.value
1333 };
1334 });
1335 };
1336
1337 if (this.enableScripting && this.hasJSActions) {
1338 selectElement.addEventListener("updatefromsandbox", jsEvent => {
1339 const actions = {
1340 value(event) {
1341 const value = event.detail.value;
1342 const values = new Set(Array.isArray(value) ? value : [value]);
1343
1344 for (const option of selectElement.options) {
1345 option.selected = values.has(option.value);
1346 }
1347
1348 storage.setValue(id, {
1349 value: getValue(event, true)
1350 });
1351 },
1352
1353 multipleSelection(event) {
1354 selectElement.multiple = true;
1355 },
1356
1357 remove(event) {
1358 const options = selectElement.options;
1359 const index = event.detail.remove;
1360 options[index].selected = false;
1361 selectElement.remove(index);
1362
1363 if (options.length > 0) {
1364 const i = Array.prototype.findIndex.call(options, option => option.selected);
1365
1366 if (i === -1) {
1367 options[0].selected = true;
1368 }
1369 }
1370
1371 storage.setValue(id, {
1372 value: getValue(event, true),
1373 items: getItems(event)
1374 });
1375 },
1376
1377 clear(event) {
1378 while (selectElement.length !== 0) {
1379 selectElement.remove(0);
1380 }
1381
1382 storage.setValue(id, {
1383 value: null,
1384 items: []
1385 });
1386 },
1387
1388 insert(event) {
1389 const {
1390 index,
1391 displayValue,
1392 exportValue
1393 } = event.detail.insert;
1394 const optionElement = document.createElement("option");
1395 optionElement.textContent = displayValue;
1396 optionElement.value = exportValue;
1397 selectElement.insertBefore(optionElement, selectElement.children[index]);
1398 storage.setValue(id, {
1399 value: getValue(event, true),
1400 items: getItems(event)
1401 });
1402 },
1403
1404 items(event) {
1405 const {
1406 items
1407 } = event.detail;
1408
1409 while (selectElement.length !== 0) {
1410 selectElement.remove(0);
1411 }
1412
1413 for (const item of items) {
1414 const {
1415 displayValue,
1416 exportValue
1417 } = item;
1418 const optionElement = document.createElement("option");
1419 optionElement.textContent = displayValue;
1420 optionElement.value = exportValue;
1421 selectElement.appendChild(optionElement);
1422 }
1423
1424 if (selectElement.options.length > 0) {
1425 selectElement.options[0].selected = true;
1426 }
1427
1428 storage.setValue(id, {
1429 value: getValue(event, true),
1430 items: getItems(event)
1431 });
1432 },
1433
1434 indices(event) {
1435 const indices = new Set(event.detail.indices);
1436
1437 for (const option of event.target.options) {
1438 option.selected = indices.has(option.index);
1439 }
1440
1441 storage.setValue(id, {
1442 value: getValue(event, true)
1443 });
1444 },
1445
1446 editable(event) {
1447 event.target.disabled = !event.detail.editable;
1448 }
1449
1450 };
1451
1452 this._dispatchEventFromSandbox(actions, jsEvent);
1453 });
1454 selectElement.addEventListener("input", event => {
1455 const exportValue = getValue(event, true);
1456 const value = getValue(event, false);
1457 storage.setValue(id, {
1458 value: exportValue
1459 });
1460 this.linkService.eventBus?.dispatch("dispatcheventinsandbox", {
1461 source: this,
1462 detail: {
1463 id,
1464 name: "Keystroke",
1465 value,
1466 changeEx: exportValue,
1467 willCommit: true,
1468 commitKey: 1,
1469 keyDown: false
1470 }
1471 });
1472 });
1473
1474 this._setEventListeners(selectElement, [["focus", "Focus"], ["blur", "Blur"], ["mousedown", "Mouse Down"], ["mouseenter", "Mouse Enter"], ["mouseleave", "Mouse Exit"], ["mouseup", "Mouse Up"], ["input", "Action"]], event => event.target.checked);
1475 } else {
1476 selectElement.addEventListener("input", function (event) {
1477 storage.setValue(id, {
1478 value: getValue(event)
1479 });
1480 });
1481 }
1482
1483 this._setBackgroundColor(selectElement);
1484
1485 this.container.appendChild(selectElement);
1486 return this.container;
1487 }
1488
1489}
1490
1491class PopupAnnotationElement extends AnnotationElement {
1492 constructor(parameters) {
1493 const isRenderable = !!(parameters.data.titleObj?.str || parameters.data.contentsObj?.str || parameters.data.richText?.str);
1494 super(parameters, {
1495 isRenderable
1496 });
1497 }
1498
1499 render() {
1500 const IGNORE_TYPES = ["Line", "Square", "Circle", "PolyLine", "Polygon", "Ink"];
1501 this.container.className = "popupAnnotation";
1502
1503 if (IGNORE_TYPES.includes(this.data.parentType)) {
1504 return this.container;
1505 }
1506
1507 const selector = `[data-annotation-id="${this.data.parentId}"]`;
1508 const parentElements = this.layer.querySelectorAll(selector);
1509
1510 if (parentElements.length === 0) {
1511 return this.container;
1512 }
1513
1514 const popup = new PopupElement({
1515 container: this.container,
1516 trigger: Array.from(parentElements),
1517 color: this.data.color,
1518 titleObj: this.data.titleObj,
1519 modificationDate: this.data.modificationDate,
1520 contentsObj: this.data.contentsObj,
1521 richText: this.data.richText
1522 });
1523 const page = this.page;
1524
1525 const rect = _util.Util.normalizeRect([this.data.parentRect[0], page.view[3] - this.data.parentRect[1] + page.view[1], this.data.parentRect[2], page.view[3] - this.data.parentRect[3] + page.view[1]]);
1526
1527 const popupLeft = rect[0] + this.data.parentRect[2] - this.data.parentRect[0];
1528 const popupTop = rect[1];
1529 this.container.style.transformOrigin = `${-popupLeft}px ${-popupTop}px`;
1530 this.container.style.left = `${popupLeft}px`;
1531 this.container.style.top = `${popupTop}px`;
1532 this.container.appendChild(popup.render());
1533 return this.container;
1534 }
1535
1536}
1537
1538class PopupElement {
1539 constructor(parameters) {
1540 this.container = parameters.container;
1541 this.trigger = parameters.trigger;
1542 this.color = parameters.color;
1543 this.titleObj = parameters.titleObj;
1544 this.modificationDate = parameters.modificationDate;
1545 this.contentsObj = parameters.contentsObj;
1546 this.richText = parameters.richText;
1547 this.hideWrapper = parameters.hideWrapper || false;
1548 this.pinned = false;
1549 }
1550
1551 render() {
1552 const BACKGROUND_ENLIGHT = 0.7;
1553 const wrapper = document.createElement("div");
1554 wrapper.className = "popupWrapper";
1555 this.hideElement = this.hideWrapper ? wrapper : this.container;
1556 this.hideElement.hidden = true;
1557 const popup = document.createElement("div");
1558 popup.className = "popup";
1559 const color = this.color;
1560
1561 if (color) {
1562 const r = BACKGROUND_ENLIGHT * (255 - color[0]) + color[0];
1563 const g = BACKGROUND_ENLIGHT * (255 - color[1]) + color[1];
1564 const b = BACKGROUND_ENLIGHT * (255 - color[2]) + color[2];
1565 popup.style.backgroundColor = _util.Util.makeHexColor(r | 0, g | 0, b | 0);
1566 }
1567
1568 const title = document.createElement("h1");
1569 title.dir = this.titleObj.dir;
1570 title.textContent = this.titleObj.str;
1571 popup.appendChild(title);
1572
1573 const dateObject = _display_utils.PDFDateString.toDateObject(this.modificationDate);
1574
1575 if (dateObject) {
1576 const modificationDate = document.createElement("span");
1577 modificationDate.className = "popupDate";
1578 modificationDate.textContent = "{{date}}, {{time}}";
1579 modificationDate.dataset.l10nId = "annotation_date_string";
1580 modificationDate.dataset.l10nArgs = JSON.stringify({
1581 date: dateObject.toLocaleDateString(),
1582 time: dateObject.toLocaleTimeString()
1583 });
1584 popup.appendChild(modificationDate);
1585 }
1586
1587 if (this.richText?.str && (!this.contentsObj?.str || this.contentsObj.str === this.richText.str)) {
1588 _xfa_layer.XfaLayer.render({
1589 xfaHtml: this.richText.html,
1590 intent: "richText",
1591 div: popup
1592 });
1593
1594 popup.lastChild.className = "richText popupContent";
1595 } else {
1596 const contents = this._formatContents(this.contentsObj);
1597
1598 popup.appendChild(contents);
1599 }
1600
1601 if (!Array.isArray(this.trigger)) {
1602 this.trigger = [this.trigger];
1603 }
1604
1605 for (const element of this.trigger) {
1606 element.addEventListener("click", this._toggle.bind(this));
1607 element.addEventListener("mouseover", this._show.bind(this, false));
1608 element.addEventListener("mouseout", this._hide.bind(this, false));
1609 }
1610
1611 popup.addEventListener("click", this._hide.bind(this, true));
1612 wrapper.appendChild(popup);
1613 return wrapper;
1614 }
1615
1616 _formatContents({
1617 str,
1618 dir
1619 }) {
1620 const p = document.createElement("p");
1621 p.className = "popupContent";
1622 p.dir = dir;
1623 const lines = str.split(/(?:\r\n?|\n)/);
1624
1625 for (let i = 0, ii = lines.length; i < ii; ++i) {
1626 const line = lines[i];
1627 p.appendChild(document.createTextNode(line));
1628
1629 if (i < ii - 1) {
1630 p.appendChild(document.createElement("br"));
1631 }
1632 }
1633
1634 return p;
1635 }
1636
1637 _toggle() {
1638 if (this.pinned) {
1639 this._hide(true);
1640 } else {
1641 this._show(true);
1642 }
1643 }
1644
1645 _show(pin = false) {
1646 if (pin) {
1647 this.pinned = true;
1648 }
1649
1650 if (this.hideElement.hidden) {
1651 this.hideElement.hidden = false;
1652 this.container.style.zIndex += 1;
1653 }
1654 }
1655
1656 _hide(unpin = true) {
1657 if (unpin) {
1658 this.pinned = false;
1659 }
1660
1661 if (!this.hideElement.hidden && !this.pinned) {
1662 this.hideElement.hidden = true;
1663 this.container.style.zIndex -= 1;
1664 }
1665 }
1666
1667}
1668
1669class FreeTextAnnotationElement extends AnnotationElement {
1670 constructor(parameters) {
1671 const isRenderable = !!(parameters.data.hasPopup || parameters.data.titleObj?.str || parameters.data.contentsObj?.str || parameters.data.richText?.str);
1672 super(parameters, {
1673 isRenderable,
1674 ignoreBorder: true
1675 });
1676 }
1677
1678 render() {
1679 this.container.className = "freeTextAnnotation";
1680
1681 if (!this.data.hasPopup) {
1682 this._createPopup(null, this.data);
1683 }
1684
1685 return this.container;
1686 }
1687
1688}
1689
1690class LineAnnotationElement extends AnnotationElement {
1691 constructor(parameters) {
1692 const isRenderable = !!(parameters.data.hasPopup || parameters.data.titleObj?.str || parameters.data.contentsObj?.str || parameters.data.richText?.str);
1693 super(parameters, {
1694 isRenderable,
1695 ignoreBorder: true
1696 });
1697 }
1698
1699 render() {
1700 this.container.className = "lineAnnotation";
1701 const data = this.data;
1702 const width = data.rect[2] - data.rect[0];
1703 const height = data.rect[3] - data.rect[1];
1704 const svg = this.svgFactory.create(width, height);
1705 const line = this.svgFactory.createElement("svg:line");
1706 line.setAttribute("x1", data.rect[2] - data.lineCoordinates[0]);
1707 line.setAttribute("y1", data.rect[3] - data.lineCoordinates[1]);
1708 line.setAttribute("x2", data.rect[2] - data.lineCoordinates[2]);
1709 line.setAttribute("y2", data.rect[3] - data.lineCoordinates[3]);
1710 line.setAttribute("stroke-width", data.borderStyle.width || 1);
1711 line.setAttribute("stroke", "transparent");
1712 line.setAttribute("fill", "transparent");
1713 svg.appendChild(line);
1714 this.container.append(svg);
1715
1716 this._createPopup(line, data);
1717
1718 return this.container;
1719 }
1720
1721}
1722
1723class SquareAnnotationElement extends AnnotationElement {
1724 constructor(parameters) {
1725 const isRenderable = !!(parameters.data.hasPopup || parameters.data.titleObj?.str || parameters.data.contentsObj?.str || parameters.data.richText?.str);
1726 super(parameters, {
1727 isRenderable,
1728 ignoreBorder: true
1729 });
1730 }
1731
1732 render() {
1733 this.container.className = "squareAnnotation";
1734 const data = this.data;
1735 const width = data.rect[2] - data.rect[0];
1736 const height = data.rect[3] - data.rect[1];
1737 const svg = this.svgFactory.create(width, height);
1738 const borderWidth = data.borderStyle.width;
1739 const square = this.svgFactory.createElement("svg:rect");
1740 square.setAttribute("x", borderWidth / 2);
1741 square.setAttribute("y", borderWidth / 2);
1742 square.setAttribute("width", width - borderWidth);
1743 square.setAttribute("height", height - borderWidth);
1744 square.setAttribute("stroke-width", borderWidth || 1);
1745 square.setAttribute("stroke", "transparent");
1746 square.setAttribute("fill", "transparent");
1747 svg.appendChild(square);
1748 this.container.append(svg);
1749
1750 this._createPopup(square, data);
1751
1752 return this.container;
1753 }
1754
1755}
1756
1757class CircleAnnotationElement extends AnnotationElement {
1758 constructor(parameters) {
1759 const isRenderable = !!(parameters.data.hasPopup || parameters.data.titleObj?.str || parameters.data.contentsObj?.str || parameters.data.richText?.str);
1760 super(parameters, {
1761 isRenderable,
1762 ignoreBorder: true
1763 });
1764 }
1765
1766 render() {
1767 this.container.className = "circleAnnotation";
1768 const data = this.data;
1769 const width = data.rect[2] - data.rect[0];
1770 const height = data.rect[3] - data.rect[1];
1771 const svg = this.svgFactory.create(width, height);
1772 const borderWidth = data.borderStyle.width;
1773 const circle = this.svgFactory.createElement("svg:ellipse");
1774 circle.setAttribute("cx", width / 2);
1775 circle.setAttribute("cy", height / 2);
1776 circle.setAttribute("rx", width / 2 - borderWidth / 2);
1777 circle.setAttribute("ry", height / 2 - borderWidth / 2);
1778 circle.setAttribute("stroke-width", borderWidth || 1);
1779 circle.setAttribute("stroke", "transparent");
1780 circle.setAttribute("fill", "transparent");
1781 svg.appendChild(circle);
1782 this.container.append(svg);
1783
1784 this._createPopup(circle, data);
1785
1786 return this.container;
1787 }
1788
1789}
1790
1791class PolylineAnnotationElement extends AnnotationElement {
1792 constructor(parameters) {
1793 const isRenderable = !!(parameters.data.hasPopup || parameters.data.titleObj?.str || parameters.data.contentsObj?.str || parameters.data.richText?.str);
1794 super(parameters, {
1795 isRenderable,
1796 ignoreBorder: true
1797 });
1798 this.containerClassName = "polylineAnnotation";
1799 this.svgElementName = "svg:polyline";
1800 }
1801
1802 render() {
1803 this.container.className = this.containerClassName;
1804 const data = this.data;
1805 const width = data.rect[2] - data.rect[0];
1806 const height = data.rect[3] - data.rect[1];
1807 const svg = this.svgFactory.create(width, height);
1808 let points = [];
1809
1810 for (const coordinate of data.vertices) {
1811 const x = coordinate.x - data.rect[0];
1812 const y = data.rect[3] - coordinate.y;
1813 points.push(x + "," + y);
1814 }
1815
1816 points = points.join(" ");
1817 const polyline = this.svgFactory.createElement(this.svgElementName);
1818 polyline.setAttribute("points", points);
1819 polyline.setAttribute("stroke-width", data.borderStyle.width || 1);
1820 polyline.setAttribute("stroke", "transparent");
1821 polyline.setAttribute("fill", "transparent");
1822 svg.appendChild(polyline);
1823 this.container.append(svg);
1824
1825 this._createPopup(polyline, data);
1826
1827 return this.container;
1828 }
1829
1830}
1831
1832class PolygonAnnotationElement extends PolylineAnnotationElement {
1833 constructor(parameters) {
1834 super(parameters);
1835 this.containerClassName = "polygonAnnotation";
1836 this.svgElementName = "svg:polygon";
1837 }
1838
1839}
1840
1841class CaretAnnotationElement extends AnnotationElement {
1842 constructor(parameters) {
1843 const isRenderable = !!(parameters.data.hasPopup || parameters.data.titleObj?.str || parameters.data.contentsObj?.str || parameters.data.richText?.str);
1844 super(parameters, {
1845 isRenderable,
1846 ignoreBorder: true
1847 });
1848 }
1849
1850 render() {
1851 this.container.className = "caretAnnotation";
1852
1853 if (!this.data.hasPopup) {
1854 this._createPopup(null, this.data);
1855 }
1856
1857 return this.container;
1858 }
1859
1860}
1861
1862class InkAnnotationElement extends AnnotationElement {
1863 constructor(parameters) {
1864 const isRenderable = !!(parameters.data.hasPopup || parameters.data.titleObj?.str || parameters.data.contentsObj?.str || parameters.data.richText?.str);
1865 super(parameters, {
1866 isRenderable,
1867 ignoreBorder: true
1868 });
1869 this.containerClassName = "inkAnnotation";
1870 this.svgElementName = "svg:polyline";
1871 }
1872
1873 render() {
1874 this.container.className = this.containerClassName;
1875 const data = this.data;
1876 const width = data.rect[2] - data.rect[0];
1877 const height = data.rect[3] - data.rect[1];
1878 const svg = this.svgFactory.create(width, height);
1879
1880 for (const inkList of data.inkLists) {
1881 let points = [];
1882
1883 for (const coordinate of inkList) {
1884 const x = coordinate.x - data.rect[0];
1885 const y = data.rect[3] - coordinate.y;
1886 points.push(`${x},${y}`);
1887 }
1888
1889 points = points.join(" ");
1890 const polyline = this.svgFactory.createElement(this.svgElementName);
1891 polyline.setAttribute("points", points);
1892 polyline.setAttribute("stroke-width", data.borderStyle.width || 1);
1893 polyline.setAttribute("stroke", "transparent");
1894 polyline.setAttribute("fill", "transparent");
1895
1896 this._createPopup(polyline, data);
1897
1898 svg.appendChild(polyline);
1899 }
1900
1901 this.container.append(svg);
1902 return this.container;
1903 }
1904
1905}
1906
1907class HighlightAnnotationElement extends AnnotationElement {
1908 constructor(parameters) {
1909 const isRenderable = !!(parameters.data.hasPopup || parameters.data.titleObj?.str || parameters.data.contentsObj?.str || parameters.data.richText?.str);
1910 super(parameters, {
1911 isRenderable,
1912 ignoreBorder: true,
1913 createQuadrilaterals: true
1914 });
1915 }
1916
1917 render() {
1918 if (!this.data.hasPopup) {
1919 this._createPopup(null, this.data);
1920 }
1921
1922 if (this.quadrilaterals) {
1923 return this._renderQuadrilaterals("highlightAnnotation");
1924 }
1925
1926 this.container.className = "highlightAnnotation";
1927 return this.container;
1928 }
1929
1930}
1931
1932class UnderlineAnnotationElement extends AnnotationElement {
1933 constructor(parameters) {
1934 const isRenderable = !!(parameters.data.hasPopup || parameters.data.titleObj?.str || parameters.data.contentsObj?.str || parameters.data.richText?.str);
1935 super(parameters, {
1936 isRenderable,
1937 ignoreBorder: true,
1938 createQuadrilaterals: true
1939 });
1940 }
1941
1942 render() {
1943 if (!this.data.hasPopup) {
1944 this._createPopup(null, this.data);
1945 }
1946
1947 if (this.quadrilaterals) {
1948 return this._renderQuadrilaterals("underlineAnnotation");
1949 }
1950
1951 this.container.className = "underlineAnnotation";
1952 return this.container;
1953 }
1954
1955}
1956
1957class SquigglyAnnotationElement extends AnnotationElement {
1958 constructor(parameters) {
1959 const isRenderable = !!(parameters.data.hasPopup || parameters.data.titleObj?.str || parameters.data.contentsObj?.str || parameters.data.richText?.str);
1960 super(parameters, {
1961 isRenderable,
1962 ignoreBorder: true,
1963 createQuadrilaterals: true
1964 });
1965 }
1966
1967 render() {
1968 if (!this.data.hasPopup) {
1969 this._createPopup(null, this.data);
1970 }
1971
1972 if (this.quadrilaterals) {
1973 return this._renderQuadrilaterals("squigglyAnnotation");
1974 }
1975
1976 this.container.className = "squigglyAnnotation";
1977 return this.container;
1978 }
1979
1980}
1981
1982class StrikeOutAnnotationElement extends AnnotationElement {
1983 constructor(parameters) {
1984 const isRenderable = !!(parameters.data.hasPopup || parameters.data.titleObj?.str || parameters.data.contentsObj?.str || parameters.data.richText?.str);
1985 super(parameters, {
1986 isRenderable,
1987 ignoreBorder: true,
1988 createQuadrilaterals: true
1989 });
1990 }
1991
1992 render() {
1993 if (!this.data.hasPopup) {
1994 this._createPopup(null, this.data);
1995 }
1996
1997 if (this.quadrilaterals) {
1998 return this._renderQuadrilaterals("strikeoutAnnotation");
1999 }
2000
2001 this.container.className = "strikeoutAnnotation";
2002 return this.container;
2003 }
2004
2005}
2006
2007class StampAnnotationElement extends AnnotationElement {
2008 constructor(parameters) {
2009 const isRenderable = !!(parameters.data.hasPopup || parameters.data.titleObj?.str || parameters.data.contentsObj?.str || parameters.data.richText?.str);
2010 super(parameters, {
2011 isRenderable,
2012 ignoreBorder: true
2013 });
2014 }
2015
2016 render() {
2017 this.container.className = "stampAnnotation";
2018
2019 if (!this.data.hasPopup) {
2020 this._createPopup(null, this.data);
2021 }
2022
2023 return this.container;
2024 }
2025
2026}
2027
2028class FileAttachmentAnnotationElement extends AnnotationElement {
2029 constructor(parameters) {
2030 super(parameters, {
2031 isRenderable: true
2032 });
2033 const {
2034 filename,
2035 content
2036 } = this.data.file;
2037 this.filename = (0, _display_utils.getFilenameFromUrl)(filename);
2038 this.content = content;
2039 this.linkService.eventBus?.dispatch("fileattachmentannotation", {
2040 source: this,
2041 id: (0, _util.stringToPDFString)(filename),
2042 filename,
2043 content
2044 });
2045 }
2046
2047 render() {
2048 this.container.className = "fileAttachmentAnnotation";
2049 const trigger = document.createElement("div");
2050 trigger.style.height = this.container.style.height;
2051 trigger.style.width = this.container.style.width;
2052 trigger.addEventListener("dblclick", this._download.bind(this));
2053
2054 if (!this.data.hasPopup && (this.data.titleObj?.str || this.data.contentsObj?.str || this.data.richText)) {
2055 this._createPopup(trigger, this.data);
2056 }
2057
2058 this.container.appendChild(trigger);
2059 return this.container;
2060 }
2061
2062 _download() {
2063 this.downloadManager?.openOrDownloadData(this.container, this.content, this.filename);
2064 }
2065
2066}
2067
2068class AnnotationLayer {
2069 static render(parameters) {
2070 const sortedAnnotations = [],
2071 popupAnnotations = [];
2072
2073 for (const data of parameters.annotations) {
2074 if (!data) {
2075 continue;
2076 }
2077
2078 if (data.annotationType === _util.AnnotationType.POPUP) {
2079 popupAnnotations.push(data);
2080 continue;
2081 }
2082
2083 sortedAnnotations.push(data);
2084 }
2085
2086 if (popupAnnotations.length) {
2087 sortedAnnotations.push(...popupAnnotations);
2088 }
2089
2090 const div = parameters.div;
2091
2092 for (const data of sortedAnnotations) {
2093 const element = AnnotationElementFactory.create({
2094 data,
2095 layer: div,
2096 page: parameters.page,
2097 viewport: parameters.viewport,
2098 linkService: parameters.linkService,
2099 downloadManager: parameters.downloadManager,
2100 imageResourcesPath: parameters.imageResourcesPath || "",
2101 renderForms: parameters.renderForms !== false,
2102 svgFactory: new _display_utils.DOMSVGFactory(),
2103 annotationStorage: parameters.annotationStorage || new _annotation_storage.AnnotationStorage(),
2104 enableScripting: parameters.enableScripting,
2105 hasJSActions: parameters.hasJSActions,
2106 fieldObjects: parameters.fieldObjects,
2107 mouseState: parameters.mouseState || {
2108 isDown: false
2109 }
2110 });
2111
2112 if (element.isRenderable) {
2113 const rendered = element.render();
2114
2115 if (data.hidden) {
2116 rendered.style.visibility = "hidden";
2117 }
2118
2119 if (Array.isArray(rendered)) {
2120 for (const renderedElement of rendered) {
2121 div.appendChild(renderedElement);
2122 }
2123 } else {
2124 if (element instanceof PopupAnnotationElement) {
2125 div.prepend(rendered);
2126 } else {
2127 div.appendChild(rendered);
2128 }
2129 }
2130 }
2131 }
2132
2133 this.#setAnnotationCanvasMap(div, parameters.annotationCanvasMap);
2134 }
2135
2136 static update(parameters) {
2137 const {
2138 page,
2139 viewport,
2140 annotations,
2141 annotationCanvasMap,
2142 div
2143 } = parameters;
2144 const transform = viewport.transform;
2145 const matrix = `matrix(${transform.join(",")})`;
2146 let scale, ownMatrix;
2147
2148 for (const data of annotations) {
2149 const elements = div.querySelectorAll(`[data-annotation-id="${data.id}"]`);
2150
2151 if (elements) {
2152 for (const element of elements) {
2153 if (data.hasOwnCanvas) {
2154 const rect = _util.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]]);
2155
2156 if (!ownMatrix) {
2157 scale = Math.abs(transform[0] || transform[1]);
2158 const ownTransform = transform.slice();
2159
2160 for (let i = 0; i < 4; i++) {
2161 ownTransform[i] = Math.sign(ownTransform[i]);
2162 }
2163
2164 ownMatrix = `matrix(${ownTransform.join(",")})`;
2165 }
2166
2167 const left = rect[0] * scale;
2168 const top = rect[1] * scale;
2169 element.style.left = `${left}px`;
2170 element.style.top = `${top}px`;
2171 element.style.transformOrigin = `${-left}px ${-top}px`;
2172 element.style.transform = ownMatrix;
2173 } else {
2174 element.style.transform = matrix;
2175 }
2176 }
2177 }
2178 }
2179
2180 this.#setAnnotationCanvasMap(div, annotationCanvasMap);
2181 div.hidden = false;
2182 }
2183
2184 static #setAnnotationCanvasMap(div, annotationCanvasMap) {
2185 if (!annotationCanvasMap) {
2186 return;
2187 }
2188
2189 for (const [id, canvas] of annotationCanvasMap) {
2190 const element = div.querySelector(`[data-annotation-id="${id}"]`);
2191
2192 if (!element) {
2193 continue;
2194 }
2195
2196 const {
2197 firstChild
2198 } = element;
2199
2200 if (firstChild.nodeName === "CANVAS") {
2201 element.replaceChild(canvas, firstChild);
2202 } else {
2203 element.insertBefore(canvas, firstChild);
2204 }
2205 }
2206
2207 annotationCanvasMap.clear();
2208 }
2209
2210}
2211
2212exports.AnnotationLayer = AnnotationLayer;
\No newline at end of file