2
0

display_utils.js 12 KB

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