display_utils.js 13 KB

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