1 |
|
2 |
|
3 |
|
4 |
|
5 |
|
6 |
|
7 |
|
8 |
|
9 |
|
10 |
|
11 |
|
12 |
|
13 |
|
14 |
|
15 |
|
16 |
|
17 |
|
18 |
|
19 |
|
20 |
|
21 |
|
22 | "use strict";
|
23 |
|
24 | Object.defineProperty(exports, "__esModule", {
|
25 | value: true
|
26 | });
|
27 | exports.PDFBug = void 0;
|
28 | let opMap;
|
29 |
|
30 | const 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 |
|
176 | const 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 |
|
264 | const 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 |
|
489 | const 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 |
|
559 | const 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 |
|
666 | exports.PDFBug = PDFBug;
|
667 | globalThis.FontInspector = FontInspector;
|
668 | globalThis.StepperManager = StepperManager;
|
669 | globalThis.Stats = Stats; |
\ | No newline at end of file |