display_utils.js 13 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528
  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. Object.defineProperty(exports, "__esModule", {
  24. value: true
  25. });
  26. exports.addLinkAttributes = addLinkAttributes;
  27. exports.deprecated = deprecated;
  28. exports.getFilenameFromUrl = getFilenameFromUrl;
  29. exports.getPdfFilenameFromUrl = getPdfFilenameFromUrl;
  30. exports.getXfaPageViewport = getXfaPageViewport;
  31. exports.isDataScheme = isDataScheme;
  32. exports.isPdfFile = isPdfFile;
  33. exports.isValidFetchUrl = isValidFetchUrl;
  34. exports.loadScript = loadScript;
  35. exports.StatTimer = exports.RenderingCancelledException = exports.PDFDateString = exports.PageViewport = exports.LinkTarget = exports.DOMSVGFactory = exports.DOMStandardFontDataFactory = exports.DOMCMapReaderFactory = exports.DOMCanvasFactory = exports.DEFAULT_LINK_REL = void 0;
  36. var _util = require("../shared/util.js");
  37. var _base_factory = require("./base_factory.js");
  38. const DEFAULT_LINK_REL = "noopener noreferrer nofollow";
  39. exports.DEFAULT_LINK_REL = DEFAULT_LINK_REL;
  40. const SVG_NS = "http://www.w3.org/2000/svg";
  41. class DOMCanvasFactory extends _base_factory.BaseCanvasFactory {
  42. constructor({
  43. ownerDocument = globalThis.document
  44. } = {}) {
  45. super();
  46. this._document = ownerDocument;
  47. }
  48. _createCanvas(width, height) {
  49. const canvas = this._document.createElement("canvas");
  50. canvas.width = width;
  51. canvas.height = height;
  52. return canvas;
  53. }
  54. }
  55. exports.DOMCanvasFactory = DOMCanvasFactory;
  56. async function fetchData(url, asTypedArray = false) {
  57. if (isValidFetchUrl(url, document.baseURI)) {
  58. const response = await fetch(url);
  59. if (!response.ok) {
  60. throw new Error(response.statusText);
  61. }
  62. return asTypedArray ? new Uint8Array(await response.arrayBuffer()) : (0, _util.stringToBytes)(await response.text());
  63. }
  64. return new Promise((resolve, reject) => {
  65. const request = new XMLHttpRequest();
  66. request.open("GET", url, true);
  67. if (asTypedArray) {
  68. request.responseType = "arraybuffer";
  69. }
  70. request.onreadystatechange = () => {
  71. if (request.readyState !== XMLHttpRequest.DONE) {
  72. return;
  73. }
  74. if (request.status === 200 || request.status === 0) {
  75. let data;
  76. if (asTypedArray && request.response) {
  77. data = new Uint8Array(request.response);
  78. } else if (!asTypedArray && request.responseText) {
  79. data = (0, _util.stringToBytes)(request.responseText);
  80. }
  81. if (data) {
  82. resolve(data);
  83. return;
  84. }
  85. }
  86. reject(new Error(request.statusText));
  87. };
  88. request.send(null);
  89. });
  90. }
  91. class DOMCMapReaderFactory extends _base_factory.BaseCMapReaderFactory {
  92. _fetchData(url, compressionType) {
  93. return fetchData(url, this.isCompressed).then(data => {
  94. return {
  95. cMapData: data,
  96. compressionType
  97. };
  98. });
  99. }
  100. }
  101. exports.DOMCMapReaderFactory = DOMCMapReaderFactory;
  102. class DOMStandardFontDataFactory extends _base_factory.BaseStandardFontDataFactory {
  103. _fetchData(url) {
  104. return fetchData(url, true);
  105. }
  106. }
  107. exports.DOMStandardFontDataFactory = DOMStandardFontDataFactory;
  108. class DOMSVGFactory extends _base_factory.BaseSVGFactory {
  109. _createSVG(type) {
  110. return document.createElementNS(SVG_NS, type);
  111. }
  112. }
  113. exports.DOMSVGFactory = DOMSVGFactory;
  114. class PageViewport {
  115. constructor({
  116. viewBox,
  117. scale,
  118. rotation,
  119. offsetX = 0,
  120. offsetY = 0,
  121. dontFlip = false
  122. }) {
  123. this.viewBox = viewBox;
  124. this.scale = scale;
  125. this.rotation = rotation;
  126. this.offsetX = offsetX;
  127. this.offsetY = offsetY;
  128. const centerX = (viewBox[2] + viewBox[0]) / 2;
  129. const centerY = (viewBox[3] + viewBox[1]) / 2;
  130. let rotateA, rotateB, rotateC, rotateD;
  131. rotation %= 360;
  132. if (rotation < 0) {
  133. rotation += 360;
  134. }
  135. switch (rotation) {
  136. case 180:
  137. rotateA = -1;
  138. rotateB = 0;
  139. rotateC = 0;
  140. rotateD = 1;
  141. break;
  142. case 90:
  143. rotateA = 0;
  144. rotateB = 1;
  145. rotateC = 1;
  146. rotateD = 0;
  147. break;
  148. case 270:
  149. rotateA = 0;
  150. rotateB = -1;
  151. rotateC = -1;
  152. rotateD = 0;
  153. break;
  154. case 0:
  155. rotateA = 1;
  156. rotateB = 0;
  157. rotateC = 0;
  158. rotateD = -1;
  159. break;
  160. default:
  161. throw new Error("PageViewport: Invalid rotation, must be a multiple of 90 degrees.");
  162. }
  163. if (dontFlip) {
  164. rotateC = -rotateC;
  165. rotateD = -rotateD;
  166. }
  167. let offsetCanvasX, offsetCanvasY;
  168. let width, height;
  169. if (rotateA === 0) {
  170. offsetCanvasX = Math.abs(centerY - viewBox[1]) * scale + offsetX;
  171. offsetCanvasY = Math.abs(centerX - viewBox[0]) * scale + offsetY;
  172. width = Math.abs(viewBox[3] - viewBox[1]) * scale;
  173. height = Math.abs(viewBox[2] - viewBox[0]) * scale;
  174. } else {
  175. offsetCanvasX = Math.abs(centerX - viewBox[0]) * scale + offsetX;
  176. offsetCanvasY = Math.abs(centerY - viewBox[1]) * scale + offsetY;
  177. width = Math.abs(viewBox[2] - viewBox[0]) * scale;
  178. height = Math.abs(viewBox[3] - viewBox[1]) * scale;
  179. }
  180. this.transform = [rotateA * scale, rotateB * scale, rotateC * scale, rotateD * scale, offsetCanvasX - rotateA * scale * centerX - rotateC * scale * centerY, offsetCanvasY - rotateB * scale * centerX - rotateD * scale * centerY];
  181. this.width = width;
  182. this.height = height;
  183. }
  184. clone({
  185. scale = this.scale,
  186. rotation = this.rotation,
  187. offsetX = this.offsetX,
  188. offsetY = this.offsetY,
  189. dontFlip = false
  190. } = {}) {
  191. return new PageViewport({
  192. viewBox: this.viewBox.slice(),
  193. scale,
  194. rotation,
  195. offsetX,
  196. offsetY,
  197. dontFlip
  198. });
  199. }
  200. convertToViewportPoint(x, y) {
  201. return _util.Util.applyTransform([x, y], this.transform);
  202. }
  203. convertToViewportRectangle(rect) {
  204. const topLeft = _util.Util.applyTransform([rect[0], rect[1]], this.transform);
  205. const bottomRight = _util.Util.applyTransform([rect[2], rect[3]], this.transform);
  206. return [topLeft[0], topLeft[1], bottomRight[0], bottomRight[1]];
  207. }
  208. convertToPdfPoint(x, y) {
  209. return _util.Util.applyInverseTransform([x, y], this.transform);
  210. }
  211. }
  212. exports.PageViewport = PageViewport;
  213. class RenderingCancelledException extends _util.BaseException {
  214. constructor(msg, type) {
  215. super(msg);
  216. this.type = type;
  217. }
  218. }
  219. exports.RenderingCancelledException = RenderingCancelledException;
  220. const LinkTarget = {
  221. NONE: 0,
  222. SELF: 1,
  223. BLANK: 2,
  224. PARENT: 3,
  225. TOP: 4
  226. };
  227. exports.LinkTarget = LinkTarget;
  228. function addLinkAttributes(link, {
  229. url,
  230. target,
  231. rel,
  232. enabled = true
  233. } = {}) {
  234. (0, _util.assert)(url && typeof url === "string", 'addLinkAttributes: A valid "url" parameter must provided.');
  235. const urlNullRemoved = (0, _util.removeNullCharacters)(url);
  236. if (enabled) {
  237. link.href = link.title = urlNullRemoved;
  238. } else {
  239. link.href = "";
  240. link.title = `Disabled: ${urlNullRemoved}`;
  241. link.onclick = () => {
  242. return false;
  243. };
  244. }
  245. let targetStr = "";
  246. switch (target) {
  247. case LinkTarget.NONE:
  248. break;
  249. case LinkTarget.SELF:
  250. targetStr = "_self";
  251. break;
  252. case LinkTarget.BLANK:
  253. targetStr = "_blank";
  254. break;
  255. case LinkTarget.PARENT:
  256. targetStr = "_parent";
  257. break;
  258. case LinkTarget.TOP:
  259. targetStr = "_top";
  260. break;
  261. }
  262. link.target = targetStr;
  263. link.rel = typeof rel === "string" ? rel : DEFAULT_LINK_REL;
  264. }
  265. function isDataScheme(url) {
  266. const ii = url.length;
  267. let i = 0;
  268. while (i < ii && url[i].trim() === "") {
  269. i++;
  270. }
  271. return url.substring(i, i + 5).toLowerCase() === "data:";
  272. }
  273. function isPdfFile(filename) {
  274. return typeof filename === "string" && /\.pdf$/i.test(filename);
  275. }
  276. function getFilenameFromUrl(url) {
  277. const anchor = url.indexOf("#");
  278. const query = url.indexOf("?");
  279. const end = Math.min(anchor > 0 ? anchor : url.length, query > 0 ? query : url.length);
  280. return url.substring(url.lastIndexOf("/", end) + 1, end);
  281. }
  282. function getPdfFilenameFromUrl(url, defaultFilename = "document.pdf") {
  283. if (typeof url !== "string") {
  284. return defaultFilename;
  285. }
  286. if (isDataScheme(url)) {
  287. (0, _util.warn)('getPdfFilenameFromUrl: ignore "data:"-URL for performance reasons.');
  288. return defaultFilename;
  289. }
  290. const reURI = /^(?:(?:[^:]+:)?\/\/[^/]+)?([^?#]*)(\?[^#]*)?(#.*)?$/;
  291. const reFilename = /[^/?#=]+\.pdf\b(?!.*\.pdf\b)/i;
  292. const splitURI = reURI.exec(url);
  293. let suggestedFilename = reFilename.exec(splitURI[1]) || reFilename.exec(splitURI[2]) || reFilename.exec(splitURI[3]);
  294. if (suggestedFilename) {
  295. suggestedFilename = suggestedFilename[0];
  296. if (suggestedFilename.includes("%")) {
  297. try {
  298. suggestedFilename = reFilename.exec(decodeURIComponent(suggestedFilename))[0];
  299. } catch (ex) {}
  300. }
  301. }
  302. return suggestedFilename || defaultFilename;
  303. }
  304. class StatTimer {
  305. constructor() {
  306. this.started = Object.create(null);
  307. this.times = [];
  308. }
  309. time(name) {
  310. if (name in this.started) {
  311. (0, _util.warn)(`Timer is already running for ${name}`);
  312. }
  313. this.started[name] = Date.now();
  314. }
  315. timeEnd(name) {
  316. if (!(name in this.started)) {
  317. (0, _util.warn)(`Timer has not been started for ${name}`);
  318. }
  319. this.times.push({
  320. name,
  321. start: this.started[name],
  322. end: Date.now()
  323. });
  324. delete this.started[name];
  325. }
  326. toString() {
  327. const outBuf = [];
  328. let longest = 0;
  329. for (const time of this.times) {
  330. const name = time.name;
  331. if (name.length > longest) {
  332. longest = name.length;
  333. }
  334. }
  335. for (const time of this.times) {
  336. const duration = time.end - time.start;
  337. outBuf.push(`${time.name.padEnd(longest)} ${duration}ms\n`);
  338. }
  339. return outBuf.join("");
  340. }
  341. }
  342. exports.StatTimer = StatTimer;
  343. function isValidFetchUrl(url, baseUrl) {
  344. try {
  345. const {
  346. protocol
  347. } = baseUrl ? new URL(url, baseUrl) : new URL(url);
  348. return protocol === "http:" || protocol === "https:";
  349. } catch (ex) {
  350. return false;
  351. }
  352. }
  353. function loadScript(src, removeScriptElement = false) {
  354. return new Promise((resolve, reject) => {
  355. const script = document.createElement("script");
  356. script.src = src;
  357. script.onload = function (evt) {
  358. if (removeScriptElement) {
  359. script.remove();
  360. }
  361. resolve(evt);
  362. };
  363. script.onerror = function () {
  364. reject(new Error(`Cannot load script at: ${script.src}`));
  365. };
  366. (document.head || document.documentElement).appendChild(script);
  367. });
  368. }
  369. function deprecated(details) {
  370. console.log("Deprecated API usage: " + details);
  371. }
  372. let pdfDateStringRegex;
  373. class PDFDateString {
  374. static toDateObject(input) {
  375. if (!input || !(0, _util.isString)(input)) {
  376. return null;
  377. }
  378. if (!pdfDateStringRegex) {
  379. pdfDateStringRegex = new RegExp("^D:" + "(\\d{4})" + "(\\d{2})?" + "(\\d{2})?" + "(\\d{2})?" + "(\\d{2})?" + "(\\d{2})?" + "([Z|+|-])?" + "(\\d{2})?" + "'?" + "(\\d{2})?" + "'?");
  380. }
  381. const matches = pdfDateStringRegex.exec(input);
  382. if (!matches) {
  383. return null;
  384. }
  385. const year = parseInt(matches[1], 10);
  386. let month = parseInt(matches[2], 10);
  387. month = month >= 1 && month <= 12 ? month - 1 : 0;
  388. let day = parseInt(matches[3], 10);
  389. day = day >= 1 && day <= 31 ? day : 1;
  390. let hour = parseInt(matches[4], 10);
  391. hour = hour >= 0 && hour <= 23 ? hour : 0;
  392. let minute = parseInt(matches[5], 10);
  393. minute = minute >= 0 && minute <= 59 ? minute : 0;
  394. let second = parseInt(matches[6], 10);
  395. second = second >= 0 && second <= 59 ? second : 0;
  396. const universalTimeRelation = matches[7] || "Z";
  397. let offsetHour = parseInt(matches[8], 10);
  398. offsetHour = offsetHour >= 0 && offsetHour <= 23 ? offsetHour : 0;
  399. let offsetMinute = parseInt(matches[9], 10) || 0;
  400. offsetMinute = offsetMinute >= 0 && offsetMinute <= 59 ? offsetMinute : 0;
  401. if (universalTimeRelation === "-") {
  402. hour += offsetHour;
  403. minute += offsetMinute;
  404. } else if (universalTimeRelation === "+") {
  405. hour -= offsetHour;
  406. minute -= offsetMinute;
  407. }
  408. return new Date(Date.UTC(year, month, day, hour, minute, second));
  409. }
  410. }
  411. exports.PDFDateString = PDFDateString;
  412. function getXfaPageViewport(xfaPage, {
  413. scale = 1,
  414. rotation = 0
  415. }) {
  416. const {
  417. width,
  418. height
  419. } = xfaPage.attributes.style;
  420. const viewBox = [0, 0, parseInt(width), parseInt(height)];
  421. return new PageViewport({
  422. viewBox,
  423. scale,
  424. rotation
  425. });
  426. }