UNPKG

17.6 kBJavaScriptView Raw
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
24Object.defineProperty(exports, "__esModule", {
25 value: true
26});
27exports.PDFBug = void 0;
28let opMap;
29
30const FontInspector = function FontInspectorClosure() {
31 let fonts;
32 let active = false;
33 const fontAttribute = "data-font-name";
34
35 function removeSelection() {
36 const divs = document.querySelectorAll(`span[${fontAttribute}]`);
37
38 for (const div of divs) {
39 div.className = "";
40 }
41 }
42
43 function resetSelection() {
44 const divs = document.querySelectorAll(`span[${fontAttribute}]`);
45
46 for (const div of divs) {
47 div.className = "debuggerHideText";
48 }
49 }
50
51 function selectFont(fontName, show) {
52 const divs = document.querySelectorAll(`span[${fontAttribute}=${fontName}]`);
53
54 for (const div of divs) {
55 div.className = show ? "debuggerShowText" : "debuggerHideText";
56 }
57 }
58
59 function textLayerClick(e) {
60 if (!e.target.dataset.fontName || e.target.tagName.toUpperCase() !== "SPAN") {
61 return;
62 }
63
64 const fontName = e.target.dataset.fontName;
65 const selects = document.getElementsByTagName("input");
66
67 for (const select of selects) {
68 if (select.dataset.fontName !== fontName) {
69 continue;
70 }
71
72 select.checked = !select.checked;
73 selectFont(fontName, select.checked);
74 select.scrollIntoView();
75 }
76 }
77
78 return {
79 id: "FontInspector",
80 name: "Font Inspector",
81 panel: null,
82 manager: null,
83
84 init(pdfjsLib) {
85 const panel = this.panel;
86 const tmp = document.createElement("button");
87 tmp.addEventListener("click", resetSelection);
88 tmp.textContent = "Refresh";
89 panel.append(tmp);
90 fonts = document.createElement("div");
91 panel.append(fonts);
92 },
93
94 cleanup() {
95 fonts.textContent = "";
96 },
97
98 enabled: false,
99
100 get active() {
101 return active;
102 },
103
104 set active(value) {
105 active = value;
106
107 if (active) {
108 document.body.addEventListener("click", textLayerClick, true);
109 resetSelection();
110 } else {
111 document.body.removeEventListener("click", textLayerClick, true);
112 removeSelection();
113 }
114 },
115
116 fontAdded(fontObj, url) {
117 function properties(obj, list) {
118 const moreInfo = document.createElement("table");
119
120 for (const entry of list) {
121 const tr = document.createElement("tr");
122 const td1 = document.createElement("td");
123 td1.textContent = entry;
124 tr.append(td1);
125 const td2 = document.createElement("td");
126 td2.textContent = obj[entry].toString();
127 tr.append(td2);
128 moreInfo.append(tr);
129 }
130
131 return moreInfo;
132 }
133
134 const moreInfo = properties(fontObj, ["name", "type"]);
135 const fontName = fontObj.loadedName;
136 const font = document.createElement("div");
137 const name = document.createElement("span");
138 name.textContent = fontName;
139 const download = document.createElement("a");
140
141 if (url) {
142 url = /url\(['"]?([^)"']+)/.exec(url);
143 download.href = url[1];
144 } else if (fontObj.data) {
145 download.href = URL.createObjectURL(new Blob([fontObj.data], {
146 type: fontObj.mimetype
147 }));
148 }
149
150 download.textContent = "Download";
151 const logIt = document.createElement("a");
152 logIt.href = "";
153 logIt.textContent = "Log";
154 logIt.addEventListener("click", function (event) {
155 event.preventDefault();
156 console.log(fontObj);
157 });
158 const select = document.createElement("input");
159 select.setAttribute("type", "checkbox");
160 select.dataset.fontName = fontName;
161 select.addEventListener("click", function () {
162 selectFont(fontName, select.checked);
163 });
164 font.append(select, name, " ", download, " ", logIt, moreInfo);
165 fonts.append(font);
166 setTimeout(() => {
167 if (this.active) {
168 resetSelection();
169 }
170 }, 2000);
171 }
172
173 };
174}();
175
176const StepperManager = function StepperManagerClosure() {
177 let steppers = [];
178 let stepperDiv = null;
179 let stepperControls = null;
180 let stepperChooser = null;
181 let breakPoints = Object.create(null);
182 return {
183 id: "Stepper",
184 name: "Stepper",
185 panel: null,
186 manager: null,
187
188 init(pdfjsLib) {
189 const self = this;
190 stepperControls = document.createElement("div");
191 stepperChooser = document.createElement("select");
192 stepperChooser.addEventListener("change", function (event) {
193 self.selectStepper(this.value);
194 });
195 stepperControls.append(stepperChooser);
196 stepperDiv = document.createElement("div");
197 this.panel.append(stepperControls, stepperDiv);
198
199 if (sessionStorage.getItem("pdfjsBreakPoints")) {
200 breakPoints = JSON.parse(sessionStorage.getItem("pdfjsBreakPoints"));
201 }
202
203 opMap = Object.create(null);
204
205 for (const key in pdfjsLib.OPS) {
206 opMap[pdfjsLib.OPS[key]] = key;
207 }
208 },
209
210 cleanup() {
211 stepperChooser.textContent = "";
212 stepperDiv.textContent = "";
213 steppers = [];
214 },
215
216 enabled: false,
217 active: false,
218
219 create(pageIndex) {
220 const debug = document.createElement("div");
221 debug.id = "stepper" + pageIndex;
222 debug.hidden = true;
223 debug.className = "stepper";
224 stepperDiv.append(debug);
225 const b = document.createElement("option");
226 b.textContent = "Page " + (pageIndex + 1);
227 b.value = pageIndex;
228 stepperChooser.append(b);
229 const initBreakPoints = breakPoints[pageIndex] || [];
230 const stepper = new Stepper(debug, pageIndex, initBreakPoints);
231 steppers.push(stepper);
232
233 if (steppers.length === 1) {
234 this.selectStepper(pageIndex, false);
235 }
236
237 return stepper;
238 },
239
240 selectStepper(pageIndex, selectPanel) {
241 pageIndex |= 0;
242
243 if (selectPanel) {
244 this.manager.selectPanel(this);
245 }
246
247 for (const stepper of steppers) {
248 stepper.panel.hidden = stepper.pageIndex !== pageIndex;
249 }
250
251 for (const option of stepperChooser.options) {
252 option.selected = (option.value | 0) === pageIndex;
253 }
254 },
255
256 saveBreakPoints(pageIndex, bps) {
257 breakPoints[pageIndex] = bps;
258 sessionStorage.setItem("pdfjsBreakPoints", JSON.stringify(breakPoints));
259 }
260
261 };
262}();
263
264const Stepper = function StepperClosure() {
265 function c(tag, textContent) {
266 const d = document.createElement(tag);
267
268 if (textContent) {
269 d.textContent = textContent;
270 }
271
272 return d;
273 }
274
275 function simplifyArgs(args) {
276 if (typeof args === "string") {
277 const MAX_STRING_LENGTH = 75;
278 return args.length <= MAX_STRING_LENGTH ? args : args.substring(0, MAX_STRING_LENGTH) + "...";
279 }
280
281 if (typeof args !== "object" || args === null) {
282 return args;
283 }
284
285 if ("length" in args) {
286 const MAX_ITEMS = 10,
287 simpleArgs = [];
288 let i, ii;
289
290 for (i = 0, ii = Math.min(MAX_ITEMS, args.length); i < ii; i++) {
291 simpleArgs.push(simplifyArgs(args[i]));
292 }
293
294 if (i < args.length) {
295 simpleArgs.push("...");
296 }
297
298 return simpleArgs;
299 }
300
301 const simpleObj = {};
302
303 for (const key in args) {
304 simpleObj[key] = simplifyArgs(args[key]);
305 }
306
307 return simpleObj;
308 }
309
310 class Stepper {
311 constructor(panel, pageIndex, initialBreakPoints) {
312 this.panel = panel;
313 this.breakPoint = 0;
314 this.nextBreakPoint = null;
315 this.pageIndex = pageIndex;
316 this.breakPoints = initialBreakPoints;
317 this.currentIdx = -1;
318 this.operatorListIdx = 0;
319 this.indentLevel = 0;
320 }
321
322 init(operatorList) {
323 const panel = this.panel;
324 const content = c("div", "c=continue, s=step");
325 const table = c("table");
326 content.append(table);
327 table.cellSpacing = 0;
328 const headerRow = c("tr");
329 table.append(headerRow);
330 headerRow.append(c("th", "Break"), c("th", "Idx"), c("th", "fn"), c("th", "args"));
331 panel.append(content);
332 this.table = table;
333 this.updateOperatorList(operatorList);
334 }
335
336 updateOperatorList(operatorList) {
337 const self = this;
338
339 function cboxOnClick() {
340 const x = +this.dataset.idx;
341
342 if (this.checked) {
343 self.breakPoints.push(x);
344 } else {
345 self.breakPoints.splice(self.breakPoints.indexOf(x), 1);
346 }
347
348 StepperManager.saveBreakPoints(self.pageIndex, self.breakPoints);
349 }
350
351 const MAX_OPERATORS_COUNT = 15000;
352
353 if (this.operatorListIdx > MAX_OPERATORS_COUNT) {
354 return;
355 }
356
357 const chunk = document.createDocumentFragment();
358 const operatorsToDisplay = Math.min(MAX_OPERATORS_COUNT, operatorList.fnArray.length);
359
360 for (let i = this.operatorListIdx; i < operatorsToDisplay; i++) {
361 const line = c("tr");
362 line.className = "line";
363 line.dataset.idx = i;
364 chunk.append(line);
365 const checked = this.breakPoints.includes(i);
366 const args = operatorList.argsArray[i] || [];
367 const breakCell = c("td");
368 const cbox = c("input");
369 cbox.type = "checkbox";
370 cbox.className = "points";
371 cbox.checked = checked;
372 cbox.dataset.idx = i;
373 cbox.onclick = cboxOnClick;
374 breakCell.append(cbox);
375 line.append(breakCell, c("td", i.toString()));
376 const fn = opMap[operatorList.fnArray[i]];
377 let decArgs = args;
378
379 if (fn === "showText") {
380 const glyphs = args[0];
381 const charCodeRow = c("tr");
382 const fontCharRow = c("tr");
383 const unicodeRow = c("tr");
384
385 for (const glyph of glyphs) {
386 if (typeof glyph === "object" && glyph !== null) {
387 charCodeRow.append(c("td", glyph.originalCharCode));
388 fontCharRow.append(c("td", glyph.fontChar));
389 unicodeRow.append(c("td", glyph.unicode));
390 } else {
391 const advanceEl = c("td", glyph);
392 advanceEl.classList.add("advance");
393 charCodeRow.append(advanceEl);
394 fontCharRow.append(c("td"));
395 unicodeRow.append(c("td"));
396 }
397 }
398
399 decArgs = c("td");
400 const table = c("table");
401 table.classList.add("showText");
402 decArgs.append(table);
403 table.append(charCodeRow, fontCharRow, unicodeRow);
404 } else if (fn === "restore") {
405 this.indentLevel--;
406 }
407
408 line.append(c("td", " ".repeat(this.indentLevel * 2) + fn));
409
410 if (fn === "save") {
411 this.indentLevel++;
412 }
413
414 if (decArgs instanceof HTMLElement) {
415 line.append(decArgs);
416 } else {
417 line.append(c("td", JSON.stringify(simplifyArgs(decArgs))));
418 }
419 }
420
421 if (operatorsToDisplay < operatorList.fnArray.length) {
422 const lastCell = c("td", "...");
423 lastCell.colspan = 4;
424 chunk.append(lastCell);
425 }
426
427 this.operatorListIdx = operatorList.fnArray.length;
428 this.table.append(chunk);
429 }
430
431 getNextBreakPoint() {
432 this.breakPoints.sort(function (a, b) {
433 return a - b;
434 });
435
436 for (const breakPoint of this.breakPoints) {
437 if (breakPoint > this.currentIdx) {
438 return breakPoint;
439 }
440 }
441
442 return null;
443 }
444
445 breakIt(idx, callback) {
446 StepperManager.selectStepper(this.pageIndex, true);
447 this.currentIdx = idx;
448
449 const listener = evt => {
450 switch (evt.keyCode) {
451 case 83:
452 document.removeEventListener("keydown", listener);
453 this.nextBreakPoint = this.currentIdx + 1;
454 this.goTo(-1);
455 callback();
456 break;
457
458 case 67:
459 document.removeEventListener("keydown", listener);
460 this.nextBreakPoint = this.getNextBreakPoint();
461 this.goTo(-1);
462 callback();
463 break;
464 }
465 };
466
467 document.addEventListener("keydown", listener);
468 this.goTo(idx);
469 }
470
471 goTo(idx) {
472 const allRows = this.panel.getElementsByClassName("line");
473
474 for (const row of allRows) {
475 if ((row.dataset.idx | 0) === idx) {
476 row.style.backgroundColor = "rgb(251,250,207)";
477 row.scrollIntoView();
478 } else {
479 row.style.backgroundColor = null;
480 }
481 }
482 }
483
484 }
485
486 return Stepper;
487}();
488
489const Stats = function Stats() {
490 let stats = [];
491
492 function clear(node) {
493 node.textContent = "";
494 }
495
496 function getStatIndex(pageNumber) {
497 for (const [i, stat] of stats.entries()) {
498 if (stat.pageNumber === pageNumber) {
499 return i;
500 }
501 }
502
503 return false;
504 }
505
506 return {
507 id: "Stats",
508 name: "Stats",
509 panel: null,
510 manager: null,
511
512 init(pdfjsLib) {},
513
514 enabled: false,
515 active: false,
516
517 add(pageNumber, stat) {
518 if (!stat) {
519 return;
520 }
521
522 const statsIndex = getStatIndex(pageNumber);
523
524 if (statsIndex !== false) {
525 stats[statsIndex].div.remove();
526 stats.splice(statsIndex, 1);
527 }
528
529 const wrapper = document.createElement("div");
530 wrapper.className = "stats";
531 const title = document.createElement("div");
532 title.className = "title";
533 title.textContent = "Page: " + pageNumber;
534 const statsDiv = document.createElement("div");
535 statsDiv.textContent = stat.toString();
536 wrapper.append(title, statsDiv);
537 stats.push({
538 pageNumber,
539 div: wrapper
540 });
541 stats.sort(function (a, b) {
542 return a.pageNumber - b.pageNumber;
543 });
544 clear(this.panel);
545
546 for (const entry of stats) {
547 this.panel.append(entry.div);
548 }
549 },
550
551 cleanup() {
552 stats = [];
553 clear(this.panel);
554 }
555
556 };
557}();
558
559const PDFBug = function PDFBugClosure() {
560 const panelWidth = 300;
561 const buttons = [];
562 let activePanel = null;
563 return {
564 tools: [FontInspector, StepperManager, Stats],
565
566 enable(ids) {
567 const all = ids.length === 1 && ids[0] === "all";
568 const tools = this.tools;
569
570 for (const tool of tools) {
571 if (all || ids.includes(tool.id)) {
572 tool.enabled = true;
573 }
574 }
575
576 if (!all) {
577 tools.sort(function (a, b) {
578 let indexA = ids.indexOf(a.id);
579 indexA = indexA < 0 ? tools.length : indexA;
580 let indexB = ids.indexOf(b.id);
581 indexB = indexB < 0 ? tools.length : indexB;
582 return indexA - indexB;
583 });
584 }
585 },
586
587 init(pdfjsLib, container, ids) {
588 this.loadCSS();
589 this.enable(ids);
590 const ui = document.createElement("div");
591 ui.id = "PDFBug";
592 const controls = document.createElement("div");
593 controls.setAttribute("class", "controls");
594 ui.append(controls);
595 const panels = document.createElement("div");
596 panels.setAttribute("class", "panels");
597 ui.append(panels);
598 container.append(ui);
599 container.style.right = panelWidth + "px";
600
601 for (const tool of this.tools) {
602 const panel = document.createElement("div");
603 const panelButton = document.createElement("button");
604 panelButton.textContent = tool.name;
605 panelButton.addEventListener("click", event => {
606 event.preventDefault();
607 this.selectPanel(tool);
608 });
609 controls.append(panelButton);
610 panels.append(panel);
611 tool.panel = panel;
612 tool.manager = this;
613
614 if (tool.enabled) {
615 tool.init(pdfjsLib);
616 } else {
617 panel.textContent = `${tool.name} is disabled. To enable add "${tool.id}" to ` + "the pdfBug parameter and refresh (separate multiple by commas).";
618 }
619
620 buttons.push(panelButton);
621 }
622
623 this.selectPanel(0);
624 },
625
626 loadCSS() {
627 const {
628 url
629 } = import.meta;
630 const link = document.createElement("link");
631 link.rel = "stylesheet";
632 link.href = url.replace(/.js$/, ".css");
633 document.head.append(link);
634 },
635
636 cleanup() {
637 for (const tool of this.tools) {
638 if (tool.enabled) {
639 tool.cleanup();
640 }
641 }
642 },
643
644 selectPanel(index) {
645 if (typeof index !== "number") {
646 index = this.tools.indexOf(index);
647 }
648
649 if (index === activePanel) {
650 return;
651 }
652
653 activePanel = index;
654
655 for (const [j, tool] of this.tools.entries()) {
656 const isActive = j === index;
657 buttons[j].classList.toggle("active", isActive);
658 tool.active = isActive;
659 tool.panel.hidden = !isActive;
660 }
661 }
662
663 };
664}();
665
666exports.PDFBug = PDFBug;
667globalThis.FontInspector = FontInspector;
668globalThis.StepperManager = StepperManager;
669globalThis.Stats = Stats;
\No newline at end of file