annotation_layer.js 32 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059106010611062106310641065106610671068106910701071107210731074107510761077107810791080108110821083108410851086108710881089109010911092
  1. /**
  2. * @licstart The following is the entire license notice for the
  3. * Javascript code in this page
  4. *
  5. * Copyright 2020 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. Object.defineProperty(exports, "__esModule", {
  24. value: true
  25. });
  26. exports.AnnotationLayer = void 0;
  27. var _display_utils = require("./display_utils.js");
  28. var _util = require("../shared/util.js");
  29. var _annotation_storage = require("./annotation_storage.js");
  30. class AnnotationElementFactory {
  31. static create(parameters) {
  32. const subtype = parameters.data.annotationType;
  33. switch (subtype) {
  34. case _util.AnnotationType.LINK:
  35. return new LinkAnnotationElement(parameters);
  36. case _util.AnnotationType.TEXT:
  37. return new TextAnnotationElement(parameters);
  38. case _util.AnnotationType.WIDGET:
  39. const fieldType = parameters.data.fieldType;
  40. switch (fieldType) {
  41. case "Tx":
  42. return new TextWidgetAnnotationElement(parameters);
  43. case "Btn":
  44. if (parameters.data.radioButton) {
  45. return new RadioButtonWidgetAnnotationElement(parameters);
  46. } else if (parameters.data.checkBox) {
  47. return new CheckboxWidgetAnnotationElement(parameters);
  48. }
  49. return new PushButtonWidgetAnnotationElement(parameters);
  50. case "Ch":
  51. return new ChoiceWidgetAnnotationElement(parameters);
  52. }
  53. return new WidgetAnnotationElement(parameters);
  54. case _util.AnnotationType.POPUP:
  55. return new PopupAnnotationElement(parameters);
  56. case _util.AnnotationType.FREETEXT:
  57. return new FreeTextAnnotationElement(parameters);
  58. case _util.AnnotationType.LINE:
  59. return new LineAnnotationElement(parameters);
  60. case _util.AnnotationType.SQUARE:
  61. return new SquareAnnotationElement(parameters);
  62. case _util.AnnotationType.CIRCLE:
  63. return new CircleAnnotationElement(parameters);
  64. case _util.AnnotationType.POLYLINE:
  65. return new PolylineAnnotationElement(parameters);
  66. case _util.AnnotationType.CARET:
  67. return new CaretAnnotationElement(parameters);
  68. case _util.AnnotationType.INK:
  69. return new InkAnnotationElement(parameters);
  70. case _util.AnnotationType.POLYGON:
  71. return new PolygonAnnotationElement(parameters);
  72. case _util.AnnotationType.HIGHLIGHT:
  73. return new HighlightAnnotationElement(parameters);
  74. case _util.AnnotationType.UNDERLINE:
  75. return new UnderlineAnnotationElement(parameters);
  76. case _util.AnnotationType.SQUIGGLY:
  77. return new SquigglyAnnotationElement(parameters);
  78. case _util.AnnotationType.STRIKEOUT:
  79. return new StrikeOutAnnotationElement(parameters);
  80. case _util.AnnotationType.STAMP:
  81. return new StampAnnotationElement(parameters);
  82. case _util.AnnotationType.FILEATTACHMENT:
  83. return new FileAttachmentAnnotationElement(parameters);
  84. default:
  85. return new AnnotationElement(parameters);
  86. }
  87. }
  88. }
  89. class AnnotationElement {
  90. constructor(parameters, isRenderable = false, ignoreBorder = false) {
  91. this.isRenderable = isRenderable;
  92. this.data = parameters.data;
  93. this.layer = parameters.layer;
  94. this.page = parameters.page;
  95. this.viewport = parameters.viewport;
  96. this.linkService = parameters.linkService;
  97. this.downloadManager = parameters.downloadManager;
  98. this.imageResourcesPath = parameters.imageResourcesPath;
  99. this.renderInteractiveForms = parameters.renderInteractiveForms;
  100. this.svgFactory = parameters.svgFactory;
  101. this.annotationStorage = parameters.annotationStorage;
  102. if (isRenderable) {
  103. this.container = this._createContainer(ignoreBorder);
  104. }
  105. }
  106. _createContainer(ignoreBorder = false) {
  107. const data = this.data,
  108. page = this.page,
  109. viewport = this.viewport;
  110. const container = document.createElement("section");
  111. let width = data.rect[2] - data.rect[0];
  112. let height = data.rect[3] - data.rect[1];
  113. container.setAttribute("data-annotation-id", data.id);
  114. 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]]);
  115. container.style.transform = `matrix(${viewport.transform.join(",")})`;
  116. container.style.transformOrigin = `-${rect[0]}px -${rect[1]}px`;
  117. if (!ignoreBorder && data.borderStyle.width > 0) {
  118. container.style.borderWidth = `${data.borderStyle.width}px`;
  119. if (data.borderStyle.style !== _util.AnnotationBorderStyleType.UNDERLINE) {
  120. width = width - 2 * data.borderStyle.width;
  121. height = height - 2 * data.borderStyle.width;
  122. }
  123. const horizontalRadius = data.borderStyle.horizontalCornerRadius;
  124. const verticalRadius = data.borderStyle.verticalCornerRadius;
  125. if (horizontalRadius > 0 || verticalRadius > 0) {
  126. const radius = `${horizontalRadius}px / ${verticalRadius}px`;
  127. container.style.borderRadius = radius;
  128. }
  129. switch (data.borderStyle.style) {
  130. case _util.AnnotationBorderStyleType.SOLID:
  131. container.style.borderStyle = "solid";
  132. break;
  133. case _util.AnnotationBorderStyleType.DASHED:
  134. container.style.borderStyle = "dashed";
  135. break;
  136. case _util.AnnotationBorderStyleType.BEVELED:
  137. (0, _util.warn)("Unimplemented border style: beveled");
  138. break;
  139. case _util.AnnotationBorderStyleType.INSET:
  140. (0, _util.warn)("Unimplemented border style: inset");
  141. break;
  142. case _util.AnnotationBorderStyleType.UNDERLINE:
  143. container.style.borderBottomStyle = "solid";
  144. break;
  145. default:
  146. break;
  147. }
  148. if (data.color) {
  149. container.style.borderColor = _util.Util.makeCssRgb(data.color[0] | 0, data.color[1] | 0, data.color[2] | 0);
  150. } else {
  151. container.style.borderWidth = 0;
  152. }
  153. }
  154. container.style.left = `${rect[0]}px`;
  155. container.style.top = `${rect[1]}px`;
  156. container.style.width = `${width}px`;
  157. container.style.height = `${height}px`;
  158. return container;
  159. }
  160. _createPopup(container, trigger, data) {
  161. if (!trigger) {
  162. trigger = document.createElement("div");
  163. trigger.style.height = container.style.height;
  164. trigger.style.width = container.style.width;
  165. container.appendChild(trigger);
  166. }
  167. const popupElement = new PopupElement({
  168. container,
  169. trigger,
  170. color: data.color,
  171. title: data.title,
  172. modificationDate: data.modificationDate,
  173. contents: data.contents,
  174. hideWrapper: true
  175. });
  176. const popup = popupElement.render();
  177. popup.style.left = container.style.width;
  178. container.appendChild(popup);
  179. }
  180. render() {
  181. (0, _util.unreachable)("Abstract method `AnnotationElement.render` called");
  182. }
  183. }
  184. class LinkAnnotationElement extends AnnotationElement {
  185. constructor(parameters) {
  186. const isRenderable = !!(parameters.data.url || parameters.data.dest || parameters.data.action);
  187. super(parameters, isRenderable);
  188. }
  189. render() {
  190. this.container.className = "linkAnnotation";
  191. const {
  192. data,
  193. linkService
  194. } = this;
  195. const link = document.createElement("a");
  196. if (data.url) {
  197. (0, _display_utils.addLinkAttributes)(link, {
  198. url: data.url,
  199. target: data.newWindow ? _display_utils.LinkTarget.BLANK : linkService.externalLinkTarget,
  200. rel: linkService.externalLinkRel,
  201. enabled: linkService.externalLinkEnabled
  202. });
  203. } else if (data.action) {
  204. this._bindNamedAction(link, data.action);
  205. } else {
  206. this._bindLink(link, data.dest);
  207. }
  208. this.container.appendChild(link);
  209. return this.container;
  210. }
  211. _bindLink(link, destination) {
  212. link.href = this.linkService.getDestinationHash(destination);
  213. link.onclick = () => {
  214. if (destination) {
  215. this.linkService.navigateTo(destination);
  216. }
  217. return false;
  218. };
  219. if (destination) {
  220. link.className = "internalLink";
  221. }
  222. }
  223. _bindNamedAction(link, action) {
  224. link.href = this.linkService.getAnchorUrl("");
  225. link.onclick = () => {
  226. this.linkService.executeNamedAction(action);
  227. return false;
  228. };
  229. link.className = "internalLink";
  230. }
  231. }
  232. class TextAnnotationElement extends AnnotationElement {
  233. constructor(parameters) {
  234. const isRenderable = !!(parameters.data.hasPopup || parameters.data.title || parameters.data.contents);
  235. super(parameters, isRenderable);
  236. }
  237. render() {
  238. this.container.className = "textAnnotation";
  239. const image = document.createElement("img");
  240. image.style.height = this.container.style.height;
  241. image.style.width = this.container.style.width;
  242. image.src = this.imageResourcesPath + "annotation-" + this.data.name.toLowerCase() + ".svg";
  243. image.alt = "[{{type}} Annotation]";
  244. image.dataset.l10nId = "text_annotation_type";
  245. image.dataset.l10nArgs = JSON.stringify({
  246. type: this.data.name
  247. });
  248. if (!this.data.hasPopup) {
  249. this._createPopup(this.container, image, this.data);
  250. }
  251. this.container.appendChild(image);
  252. return this.container;
  253. }
  254. }
  255. class WidgetAnnotationElement extends AnnotationElement {
  256. render() {
  257. return this.container;
  258. }
  259. }
  260. class TextWidgetAnnotationElement extends WidgetAnnotationElement {
  261. constructor(parameters) {
  262. const isRenderable = parameters.renderInteractiveForms || !parameters.data.hasAppearance && !!parameters.data.fieldValue;
  263. super(parameters, isRenderable);
  264. }
  265. render() {
  266. const TEXT_ALIGNMENT = ["left", "center", "right"];
  267. const storage = this.annotationStorage;
  268. const id = this.data.id;
  269. this.container.className = "textWidgetAnnotation";
  270. let element = null;
  271. if (this.renderInteractiveForms) {
  272. const textContent = storage.getOrCreateValue(id, this.data.fieldValue);
  273. if (this.data.multiLine) {
  274. element = document.createElement("textarea");
  275. element.textContent = textContent;
  276. } else {
  277. element = document.createElement("input");
  278. element.type = "text";
  279. element.setAttribute("value", textContent);
  280. }
  281. element.addEventListener("input", function (event) {
  282. storage.setValue(id, event.target.value);
  283. });
  284. element.disabled = this.data.readOnly;
  285. element.name = this.data.fieldName;
  286. if (this.data.maxLen !== null) {
  287. element.maxLength = this.data.maxLen;
  288. }
  289. if (this.data.comb) {
  290. const fieldWidth = this.data.rect[2] - this.data.rect[0];
  291. const combWidth = fieldWidth / this.data.maxLen;
  292. element.classList.add("comb");
  293. element.style.letterSpacing = `calc(${combWidth}px - 1ch)`;
  294. }
  295. } else {
  296. element = document.createElement("div");
  297. element.textContent = this.data.fieldValue;
  298. element.style.verticalAlign = "middle";
  299. element.style.display = "table-cell";
  300. let font = null;
  301. if (this.data.fontRefName && this.page.commonObjs.has(this.data.fontRefName)) {
  302. font = this.page.commonObjs.get(this.data.fontRefName);
  303. }
  304. this._setTextStyle(element, font);
  305. }
  306. if (this.data.textAlignment !== null) {
  307. element.style.textAlign = TEXT_ALIGNMENT[this.data.textAlignment];
  308. }
  309. this.container.appendChild(element);
  310. return this.container;
  311. }
  312. _setTextStyle(element, font) {
  313. const style = element.style;
  314. style.fontSize = `${this.data.fontSize}px`;
  315. style.direction = this.data.fontDirection < 0 ? "rtl" : "ltr";
  316. if (!font) {
  317. return;
  318. }
  319. let bold = "normal";
  320. if (font.black) {
  321. bold = "900";
  322. } else if (font.bold) {
  323. bold = "bold";
  324. }
  325. style.fontWeight = bold;
  326. style.fontStyle = font.italic ? "italic" : "normal";
  327. const fontFamily = font.loadedName ? `"${font.loadedName}", ` : "";
  328. const fallbackName = font.fallbackName || "Helvetica, sans-serif";
  329. style.fontFamily = fontFamily + fallbackName;
  330. }
  331. }
  332. class CheckboxWidgetAnnotationElement extends WidgetAnnotationElement {
  333. constructor(parameters) {
  334. super(parameters, parameters.renderInteractiveForms);
  335. }
  336. render() {
  337. const storage = this.annotationStorage;
  338. const data = this.data;
  339. const id = data.id;
  340. const value = storage.getOrCreateValue(id, data.fieldValue && data.fieldValue !== "Off");
  341. this.container.className = "buttonWidgetAnnotation checkBox";
  342. const element = document.createElement("input");
  343. element.disabled = data.readOnly;
  344. element.type = "checkbox";
  345. element.name = this.data.fieldName;
  346. if (value) {
  347. element.setAttribute("checked", true);
  348. }
  349. element.addEventListener("change", function (event) {
  350. storage.setValue(id, event.target.checked);
  351. });
  352. this.container.appendChild(element);
  353. return this.container;
  354. }
  355. }
  356. class RadioButtonWidgetAnnotationElement extends WidgetAnnotationElement {
  357. constructor(parameters) {
  358. super(parameters, parameters.renderInteractiveForms);
  359. }
  360. render() {
  361. this.container.className = "buttonWidgetAnnotation radioButton";
  362. const storage = this.annotationStorage;
  363. const data = this.data;
  364. const id = data.id;
  365. const value = storage.getOrCreateValue(id, data.fieldValue === data.buttonValue);
  366. const element = document.createElement("input");
  367. element.disabled = data.readOnly;
  368. element.type = "radio";
  369. element.name = data.fieldName;
  370. if (value) {
  371. element.setAttribute("checked", true);
  372. }
  373. element.addEventListener("change", function (event) {
  374. const name = event.target.name;
  375. for (const radio of document.getElementsByName(name)) {
  376. if (radio !== event.target) {
  377. storage.setValue(radio.parentNode.getAttribute("data-annotation-id"), false);
  378. }
  379. }
  380. storage.setValue(id, event.target.checked);
  381. });
  382. this.container.appendChild(element);
  383. return this.container;
  384. }
  385. }
  386. class PushButtonWidgetAnnotationElement extends LinkAnnotationElement {
  387. render() {
  388. const container = super.render();
  389. container.className = "buttonWidgetAnnotation pushButton";
  390. return container;
  391. }
  392. }
  393. class ChoiceWidgetAnnotationElement extends WidgetAnnotationElement {
  394. constructor(parameters) {
  395. super(parameters, parameters.renderInteractiveForms);
  396. }
  397. render() {
  398. this.container.className = "choiceWidgetAnnotation";
  399. const storage = this.annotationStorage;
  400. const id = this.data.id;
  401. storage.getOrCreateValue(id, this.data.fieldValue.length > 0 ? this.data.fieldValue[0] : null);
  402. const selectElement = document.createElement("select");
  403. selectElement.disabled = this.data.readOnly;
  404. selectElement.name = this.data.fieldName;
  405. if (!this.data.combo) {
  406. selectElement.size = this.data.options.length;
  407. if (this.data.multiSelect) {
  408. selectElement.multiple = true;
  409. }
  410. }
  411. for (const option of this.data.options) {
  412. const optionElement = document.createElement("option");
  413. optionElement.textContent = option.displayValue;
  414. optionElement.value = option.exportValue;
  415. if (this.data.fieldValue.includes(option.exportValue)) {
  416. optionElement.setAttribute("selected", true);
  417. }
  418. selectElement.appendChild(optionElement);
  419. }
  420. selectElement.addEventListener("input", function (event) {
  421. const options = event.target.options;
  422. const value = options[options.selectedIndex].value;
  423. storage.setValue(id, value);
  424. });
  425. this.container.appendChild(selectElement);
  426. return this.container;
  427. }
  428. }
  429. class PopupAnnotationElement extends AnnotationElement {
  430. constructor(parameters) {
  431. const isRenderable = !!(parameters.data.title || parameters.data.contents);
  432. super(parameters, isRenderable);
  433. }
  434. render() {
  435. const IGNORE_TYPES = ["Line", "Square", "Circle", "PolyLine", "Polygon", "Ink"];
  436. this.container.className = "popupAnnotation";
  437. if (IGNORE_TYPES.includes(this.data.parentType)) {
  438. return this.container;
  439. }
  440. const selector = `[data-annotation-id="${this.data.parentId}"]`;
  441. const parentElement = this.layer.querySelector(selector);
  442. if (!parentElement) {
  443. return this.container;
  444. }
  445. const popup = new PopupElement({
  446. container: this.container,
  447. trigger: parentElement,
  448. color: this.data.color,
  449. title: this.data.title,
  450. modificationDate: this.data.modificationDate,
  451. contents: this.data.contents
  452. });
  453. const parentLeft = parseFloat(parentElement.style.left);
  454. const parentWidth = parseFloat(parentElement.style.width);
  455. this.container.style.transformOrigin = `-${parentLeft + parentWidth}px -${parentElement.style.top}`;
  456. this.container.style.left = `${parentLeft + parentWidth}px`;
  457. this.container.appendChild(popup.render());
  458. return this.container;
  459. }
  460. }
  461. class PopupElement {
  462. constructor(parameters) {
  463. this.container = parameters.container;
  464. this.trigger = parameters.trigger;
  465. this.color = parameters.color;
  466. this.title = parameters.title;
  467. this.modificationDate = parameters.modificationDate;
  468. this.contents = parameters.contents;
  469. this.hideWrapper = parameters.hideWrapper || false;
  470. this.pinned = false;
  471. }
  472. render() {
  473. const BACKGROUND_ENLIGHT = 0.7;
  474. const wrapper = document.createElement("div");
  475. wrapper.className = "popupWrapper";
  476. this.hideElement = this.hideWrapper ? wrapper : this.container;
  477. this.hideElement.setAttribute("hidden", true);
  478. const popup = document.createElement("div");
  479. popup.className = "popup";
  480. const color = this.color;
  481. if (color) {
  482. const r = BACKGROUND_ENLIGHT * (255 - color[0]) + color[0];
  483. const g = BACKGROUND_ENLIGHT * (255 - color[1]) + color[1];
  484. const b = BACKGROUND_ENLIGHT * (255 - color[2]) + color[2];
  485. popup.style.backgroundColor = _util.Util.makeCssRgb(r | 0, g | 0, b | 0);
  486. }
  487. const title = document.createElement("h1");
  488. title.textContent = this.title;
  489. popup.appendChild(title);
  490. const dateObject = _display_utils.PDFDateString.toDateObject(this.modificationDate);
  491. if (dateObject) {
  492. const modificationDate = document.createElement("span");
  493. modificationDate.textContent = "{{date}}, {{time}}";
  494. modificationDate.dataset.l10nId = "annotation_date_string";
  495. modificationDate.dataset.l10nArgs = JSON.stringify({
  496. date: dateObject.toLocaleDateString(),
  497. time: dateObject.toLocaleTimeString()
  498. });
  499. popup.appendChild(modificationDate);
  500. }
  501. const contents = this._formatContents(this.contents);
  502. popup.appendChild(contents);
  503. this.trigger.addEventListener("click", this._toggle.bind(this));
  504. this.trigger.addEventListener("mouseover", this._show.bind(this, false));
  505. this.trigger.addEventListener("mouseout", this._hide.bind(this, false));
  506. popup.addEventListener("click", this._hide.bind(this, true));
  507. wrapper.appendChild(popup);
  508. return wrapper;
  509. }
  510. _formatContents(contents) {
  511. const p = document.createElement("p");
  512. const lines = contents.split(/(?:\r\n?|\n)/);
  513. for (let i = 0, ii = lines.length; i < ii; ++i) {
  514. const line = lines[i];
  515. p.appendChild(document.createTextNode(line));
  516. if (i < ii - 1) {
  517. p.appendChild(document.createElement("br"));
  518. }
  519. }
  520. return p;
  521. }
  522. _toggle() {
  523. if (this.pinned) {
  524. this._hide(true);
  525. } else {
  526. this._show(true);
  527. }
  528. }
  529. _show(pin = false) {
  530. if (pin) {
  531. this.pinned = true;
  532. }
  533. if (this.hideElement.hasAttribute("hidden")) {
  534. this.hideElement.removeAttribute("hidden");
  535. this.container.style.zIndex += 1;
  536. }
  537. }
  538. _hide(unpin = true) {
  539. if (unpin) {
  540. this.pinned = false;
  541. }
  542. if (!this.hideElement.hasAttribute("hidden") && !this.pinned) {
  543. this.hideElement.setAttribute("hidden", true);
  544. this.container.style.zIndex -= 1;
  545. }
  546. }
  547. }
  548. class FreeTextAnnotationElement extends AnnotationElement {
  549. constructor(parameters) {
  550. const isRenderable = !!(parameters.data.hasPopup || parameters.data.title || parameters.data.contents);
  551. super(parameters, isRenderable, true);
  552. }
  553. render() {
  554. this.container.className = "freeTextAnnotation";
  555. if (!this.data.hasPopup) {
  556. this._createPopup(this.container, null, this.data);
  557. }
  558. return this.container;
  559. }
  560. }
  561. class LineAnnotationElement extends AnnotationElement {
  562. constructor(parameters) {
  563. const isRenderable = !!(parameters.data.hasPopup || parameters.data.title || parameters.data.contents);
  564. super(parameters, isRenderable, true);
  565. }
  566. render() {
  567. this.container.className = "lineAnnotation";
  568. const data = this.data;
  569. const width = data.rect[2] - data.rect[0];
  570. const height = data.rect[3] - data.rect[1];
  571. const svg = this.svgFactory.create(width, height);
  572. const line = this.svgFactory.createElement("svg:line");
  573. line.setAttribute("x1", data.rect[2] - data.lineCoordinates[0]);
  574. line.setAttribute("y1", data.rect[3] - data.lineCoordinates[1]);
  575. line.setAttribute("x2", data.rect[2] - data.lineCoordinates[2]);
  576. line.setAttribute("y2", data.rect[3] - data.lineCoordinates[3]);
  577. line.setAttribute("stroke-width", data.borderStyle.width || 1);
  578. line.setAttribute("stroke", "transparent");
  579. svg.appendChild(line);
  580. this.container.append(svg);
  581. this._createPopup(this.container, line, data);
  582. return this.container;
  583. }
  584. }
  585. class SquareAnnotationElement extends AnnotationElement {
  586. constructor(parameters) {
  587. const isRenderable = !!(parameters.data.hasPopup || parameters.data.title || parameters.data.contents);
  588. super(parameters, isRenderable, true);
  589. }
  590. render() {
  591. this.container.className = "squareAnnotation";
  592. const data = this.data;
  593. const width = data.rect[2] - data.rect[0];
  594. const height = data.rect[3] - data.rect[1];
  595. const svg = this.svgFactory.create(width, height);
  596. const borderWidth = data.borderStyle.width;
  597. const square = this.svgFactory.createElement("svg:rect");
  598. square.setAttribute("x", borderWidth / 2);
  599. square.setAttribute("y", borderWidth / 2);
  600. square.setAttribute("width", width - borderWidth);
  601. square.setAttribute("height", height - borderWidth);
  602. square.setAttribute("stroke-width", borderWidth || 1);
  603. square.setAttribute("stroke", "transparent");
  604. square.setAttribute("fill", "none");
  605. svg.appendChild(square);
  606. this.container.append(svg);
  607. this._createPopup(this.container, square, data);
  608. return this.container;
  609. }
  610. }
  611. class CircleAnnotationElement extends AnnotationElement {
  612. constructor(parameters) {
  613. const isRenderable = !!(parameters.data.hasPopup || parameters.data.title || parameters.data.contents);
  614. super(parameters, isRenderable, true);
  615. }
  616. render() {
  617. this.container.className = "circleAnnotation";
  618. const data = this.data;
  619. const width = data.rect[2] - data.rect[0];
  620. const height = data.rect[3] - data.rect[1];
  621. const svg = this.svgFactory.create(width, height);
  622. const borderWidth = data.borderStyle.width;
  623. const circle = this.svgFactory.createElement("svg:ellipse");
  624. circle.setAttribute("cx", width / 2);
  625. circle.setAttribute("cy", height / 2);
  626. circle.setAttribute("rx", width / 2 - borderWidth / 2);
  627. circle.setAttribute("ry", height / 2 - borderWidth / 2);
  628. circle.setAttribute("stroke-width", borderWidth || 1);
  629. circle.setAttribute("stroke", "transparent");
  630. circle.setAttribute("fill", "none");
  631. svg.appendChild(circle);
  632. this.container.append(svg);
  633. this._createPopup(this.container, circle, data);
  634. return this.container;
  635. }
  636. }
  637. class PolylineAnnotationElement extends AnnotationElement {
  638. constructor(parameters) {
  639. const isRenderable = !!(parameters.data.hasPopup || parameters.data.title || parameters.data.contents);
  640. super(parameters, isRenderable, true);
  641. this.containerClassName = "polylineAnnotation";
  642. this.svgElementName = "svg:polyline";
  643. }
  644. render() {
  645. this.container.className = this.containerClassName;
  646. const data = this.data;
  647. const width = data.rect[2] - data.rect[0];
  648. const height = data.rect[3] - data.rect[1];
  649. const svg = this.svgFactory.create(width, height);
  650. let points = [];
  651. for (const coordinate of data.vertices) {
  652. const x = coordinate.x - data.rect[0];
  653. const y = data.rect[3] - coordinate.y;
  654. points.push(x + "," + y);
  655. }
  656. points = points.join(" ");
  657. const polyline = this.svgFactory.createElement(this.svgElementName);
  658. polyline.setAttribute("points", points);
  659. polyline.setAttribute("stroke-width", data.borderStyle.width || 1);
  660. polyline.setAttribute("stroke", "transparent");
  661. polyline.setAttribute("fill", "none");
  662. svg.appendChild(polyline);
  663. this.container.append(svg);
  664. this._createPopup(this.container, polyline, data);
  665. return this.container;
  666. }
  667. }
  668. class PolygonAnnotationElement extends PolylineAnnotationElement {
  669. constructor(parameters) {
  670. super(parameters);
  671. this.containerClassName = "polygonAnnotation";
  672. this.svgElementName = "svg:polygon";
  673. }
  674. }
  675. class CaretAnnotationElement extends AnnotationElement {
  676. constructor(parameters) {
  677. const isRenderable = !!(parameters.data.hasPopup || parameters.data.title || parameters.data.contents);
  678. super(parameters, isRenderable, true);
  679. }
  680. render() {
  681. this.container.className = "caretAnnotation";
  682. if (!this.data.hasPopup) {
  683. this._createPopup(this.container, null, this.data);
  684. }
  685. return this.container;
  686. }
  687. }
  688. class InkAnnotationElement extends AnnotationElement {
  689. constructor(parameters) {
  690. const isRenderable = !!(parameters.data.hasPopup || parameters.data.title || parameters.data.contents);
  691. super(parameters, isRenderable, true);
  692. this.containerClassName = "inkAnnotation";
  693. this.svgElementName = "svg:polyline";
  694. }
  695. render() {
  696. this.container.className = this.containerClassName;
  697. const data = this.data;
  698. const width = data.rect[2] - data.rect[0];
  699. const height = data.rect[3] - data.rect[1];
  700. const svg = this.svgFactory.create(width, height);
  701. for (const inkList of data.inkLists) {
  702. let points = [];
  703. for (const coordinate of inkList) {
  704. const x = coordinate.x - data.rect[0];
  705. const y = data.rect[3] - coordinate.y;
  706. points.push(`${x},${y}`);
  707. }
  708. points = points.join(" ");
  709. const polyline = this.svgFactory.createElement(this.svgElementName);
  710. polyline.setAttribute("points", points);
  711. polyline.setAttribute("stroke-width", data.borderStyle.width || 1);
  712. polyline.setAttribute("stroke", "transparent");
  713. polyline.setAttribute("fill", "none");
  714. this._createPopup(this.container, polyline, data);
  715. svg.appendChild(polyline);
  716. }
  717. this.container.append(svg);
  718. return this.container;
  719. }
  720. }
  721. class HighlightAnnotationElement extends AnnotationElement {
  722. constructor(parameters) {
  723. const isRenderable = !!(parameters.data.hasPopup || parameters.data.title || parameters.data.contents);
  724. super(parameters, isRenderable, true);
  725. }
  726. render() {
  727. this.container.className = "highlightAnnotation";
  728. if (!this.data.hasPopup) {
  729. this._createPopup(this.container, null, this.data);
  730. }
  731. return this.container;
  732. }
  733. }
  734. class UnderlineAnnotationElement extends AnnotationElement {
  735. constructor(parameters) {
  736. const isRenderable = !!(parameters.data.hasPopup || parameters.data.title || parameters.data.contents);
  737. super(parameters, isRenderable, true);
  738. }
  739. render() {
  740. this.container.className = "underlineAnnotation";
  741. if (!this.data.hasPopup) {
  742. this._createPopup(this.container, null, this.data);
  743. }
  744. return this.container;
  745. }
  746. }
  747. class SquigglyAnnotationElement extends AnnotationElement {
  748. constructor(parameters) {
  749. const isRenderable = !!(parameters.data.hasPopup || parameters.data.title || parameters.data.contents);
  750. super(parameters, isRenderable, true);
  751. }
  752. render() {
  753. this.container.className = "squigglyAnnotation";
  754. if (!this.data.hasPopup) {
  755. this._createPopup(this.container, null, this.data);
  756. }
  757. return this.container;
  758. }
  759. }
  760. class StrikeOutAnnotationElement extends AnnotationElement {
  761. constructor(parameters) {
  762. const isRenderable = !!(parameters.data.hasPopup || parameters.data.title || parameters.data.contents);
  763. super(parameters, isRenderable, true);
  764. }
  765. render() {
  766. this.container.className = "strikeoutAnnotation";
  767. if (!this.data.hasPopup) {
  768. this._createPopup(this.container, null, this.data);
  769. }
  770. return this.container;
  771. }
  772. }
  773. class StampAnnotationElement extends AnnotationElement {
  774. constructor(parameters) {
  775. const isRenderable = !!(parameters.data.hasPopup || parameters.data.title || parameters.data.contents);
  776. super(parameters, isRenderable, true);
  777. }
  778. render() {
  779. this.container.className = "stampAnnotation";
  780. if (!this.data.hasPopup) {
  781. this._createPopup(this.container, null, this.data);
  782. }
  783. return this.container;
  784. }
  785. }
  786. class FileAttachmentAnnotationElement extends AnnotationElement {
  787. constructor(parameters) {
  788. super(parameters, true);
  789. const {
  790. filename,
  791. content
  792. } = this.data.file;
  793. this.filename = (0, _display_utils.getFilenameFromUrl)(filename);
  794. this.content = content;
  795. if (this.linkService.eventBus) {
  796. this.linkService.eventBus.dispatch("fileattachmentannotation", {
  797. source: this,
  798. id: (0, _util.stringToPDFString)(filename),
  799. filename,
  800. content
  801. });
  802. }
  803. }
  804. render() {
  805. this.container.className = "fileAttachmentAnnotation";
  806. const trigger = document.createElement("div");
  807. trigger.style.height = this.container.style.height;
  808. trigger.style.width = this.container.style.width;
  809. trigger.addEventListener("dblclick", this._download.bind(this));
  810. if (!this.data.hasPopup && (this.data.title || this.data.contents)) {
  811. this._createPopup(this.container, trigger, this.data);
  812. }
  813. this.container.appendChild(trigger);
  814. return this.container;
  815. }
  816. _download() {
  817. if (!this.downloadManager) {
  818. (0, _util.warn)("Download cannot be started due to unavailable download manager");
  819. return;
  820. }
  821. this.downloadManager.downloadData(this.content, this.filename, "");
  822. }
  823. }
  824. class AnnotationLayer {
  825. static render(parameters) {
  826. const sortedAnnotations = [],
  827. popupAnnotations = [];
  828. for (const data of parameters.annotations) {
  829. if (!data) {
  830. continue;
  831. }
  832. if (data.annotationType === _util.AnnotationType.POPUP) {
  833. popupAnnotations.push(data);
  834. continue;
  835. }
  836. sortedAnnotations.push(data);
  837. }
  838. if (popupAnnotations.length) {
  839. sortedAnnotations.push(...popupAnnotations);
  840. }
  841. for (const data of sortedAnnotations) {
  842. const element = AnnotationElementFactory.create({
  843. data,
  844. layer: parameters.div,
  845. page: parameters.page,
  846. viewport: parameters.viewport,
  847. linkService: parameters.linkService,
  848. downloadManager: parameters.downloadManager,
  849. imageResourcesPath: parameters.imageResourcesPath || "",
  850. renderInteractiveForms: typeof parameters.renderInteractiveForms === "boolean" ? parameters.renderInteractiveForms : true,
  851. svgFactory: new _display_utils.DOMSVGFactory(),
  852. annotationStorage: parameters.annotationStorage || new _annotation_storage.AnnotationStorage()
  853. });
  854. if (element.isRenderable) {
  855. parameters.div.appendChild(element.render());
  856. }
  857. }
  858. }
  859. static update(parameters) {
  860. for (const data of parameters.annotations) {
  861. const element = parameters.div.querySelector(`[data-annotation-id="${data.id}"]`);
  862. if (element) {
  863. element.style.transform = `matrix(${parameters.viewport.transform.join(",")})`;
  864. }
  865. }
  866. parameters.div.removeAttribute("hidden");
  867. }
  868. }
  869. exports.AnnotationLayer = AnnotationLayer;