Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
quarto-dev
GitHub Repository: quarto-dev/quarto-cli
Path: blob/main/src/resources/formats/pdf/pdfjs/web/debugger.js
12923 views
1
/* Copyright 2012 Mozilla Foundation
2
*
3
* Licensed under the Apache License, Version 2.0 (the "License");
4
* you may not use this file except in compliance with the License.
5
* You may obtain a copy of the License at
6
*
7
* http://www.apache.org/licenses/LICENSE-2.0
8
*
9
* Unless required by applicable law or agreed to in writing, software
10
* distributed under the License is distributed on an "AS IS" BASIS,
11
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12
* See the License for the specific language governing permissions and
13
* limitations under the License.
14
*/
15
/* eslint-disable no-var */
16
17
"use strict";
18
19
var FontInspector = (function FontInspectorClosure() {
20
var fonts;
21
var active = false;
22
var fontAttribute = "data-font-name";
23
function removeSelection() {
24
const divs = document.querySelectorAll(`span[${fontAttribute}]`);
25
for (const div of divs) {
26
div.className = "";
27
}
28
}
29
function resetSelection() {
30
const divs = document.querySelectorAll(`span[${fontAttribute}]`);
31
for (const div of divs) {
32
div.className = "debuggerHideText";
33
}
34
}
35
function selectFont(fontName, show) {
36
const divs = document.querySelectorAll(
37
`span[${fontAttribute}=${fontName}]`
38
);
39
for (const div of divs) {
40
div.className = show ? "debuggerShowText" : "debuggerHideText";
41
}
42
}
43
function textLayerClick(e) {
44
if (
45
!e.target.dataset.fontName ||
46
e.target.tagName.toUpperCase() !== "SPAN"
47
) {
48
return;
49
}
50
var fontName = e.target.dataset.fontName;
51
var selects = document.getElementsByTagName("input");
52
for (var i = 0; i < selects.length; ++i) {
53
var select = selects[i];
54
if (select.dataset.fontName !== fontName) {
55
continue;
56
}
57
select.checked = !select.checked;
58
selectFont(fontName, select.checked);
59
select.scrollIntoView();
60
}
61
}
62
return {
63
// Properties/functions needed by PDFBug.
64
id: "FontInspector",
65
name: "Font Inspector",
66
panel: null,
67
manager: null,
68
init: function init(pdfjsLib) {
69
var panel = this.panel;
70
var tmp = document.createElement("button");
71
tmp.addEventListener("click", resetSelection);
72
tmp.textContent = "Refresh";
73
panel.appendChild(tmp);
74
75
fonts = document.createElement("div");
76
panel.appendChild(fonts);
77
},
78
cleanup: function cleanup() {
79
fonts.textContent = "";
80
},
81
enabled: false,
82
get active() {
83
return active;
84
},
85
set active(value) {
86
active = value;
87
if (active) {
88
document.body.addEventListener("click", textLayerClick, true);
89
resetSelection();
90
} else {
91
document.body.removeEventListener("click", textLayerClick, true);
92
removeSelection();
93
}
94
},
95
// FontInspector specific functions.
96
fontAdded: function fontAdded(fontObj, url) {
97
function properties(obj, list) {
98
var moreInfo = document.createElement("table");
99
for (var i = 0; i < list.length; i++) {
100
var tr = document.createElement("tr");
101
var td1 = document.createElement("td");
102
td1.textContent = list[i];
103
tr.appendChild(td1);
104
var td2 = document.createElement("td");
105
td2.textContent = obj[list[i]].toString();
106
tr.appendChild(td2);
107
moreInfo.appendChild(tr);
108
}
109
return moreInfo;
110
}
111
var moreInfo = properties(fontObj, ["name", "type"]);
112
const fontName = fontObj.loadedName;
113
var font = document.createElement("div");
114
var name = document.createElement("span");
115
name.textContent = fontName;
116
var download = document.createElement("a");
117
if (url) {
118
url = /url\(['"]?([^)"']+)/.exec(url);
119
download.href = url[1];
120
} else if (fontObj.data) {
121
download.href = URL.createObjectURL(
122
new Blob([fontObj.data], { type: fontObj.mimeType })
123
);
124
}
125
download.textContent = "Download";
126
var logIt = document.createElement("a");
127
logIt.href = "";
128
logIt.textContent = "Log";
129
logIt.addEventListener("click", function (event) {
130
event.preventDefault();
131
console.log(fontObj);
132
});
133
const select = document.createElement("input");
134
select.setAttribute("type", "checkbox");
135
select.dataset.fontName = fontName;
136
select.addEventListener("click", function () {
137
selectFont(fontName, select.checked);
138
});
139
font.appendChild(select);
140
font.appendChild(name);
141
font.appendChild(document.createTextNode(" "));
142
font.appendChild(download);
143
font.appendChild(document.createTextNode(" "));
144
font.appendChild(logIt);
145
font.appendChild(moreInfo);
146
fonts.appendChild(font);
147
// Somewhat of a hack, should probably add a hook for when the text layer
148
// is done rendering.
149
setTimeout(() => {
150
if (this.active) {
151
resetSelection();
152
}
153
}, 2000);
154
},
155
};
156
})();
157
158
var opMap;
159
160
// Manages all the page steppers.
161
var StepperManager = (function StepperManagerClosure() {
162
var steppers = [];
163
var stepperDiv = null;
164
var stepperControls = null;
165
var stepperChooser = null;
166
var breakPoints = Object.create(null);
167
return {
168
// Properties/functions needed by PDFBug.
169
id: "Stepper",
170
name: "Stepper",
171
panel: null,
172
manager: null,
173
init: function init(pdfjsLib) {
174
var self = this;
175
stepperControls = document.createElement("div");
176
stepperChooser = document.createElement("select");
177
stepperChooser.addEventListener("change", function (event) {
178
self.selectStepper(this.value);
179
});
180
stepperControls.appendChild(stepperChooser);
181
stepperDiv = document.createElement("div");
182
this.panel.appendChild(stepperControls);
183
this.panel.appendChild(stepperDiv);
184
if (sessionStorage.getItem("pdfjsBreakPoints")) {
185
breakPoints = JSON.parse(sessionStorage.getItem("pdfjsBreakPoints"));
186
}
187
188
opMap = Object.create(null);
189
for (var key in pdfjsLib.OPS) {
190
opMap[pdfjsLib.OPS[key]] = key;
191
}
192
},
193
cleanup: function cleanup() {
194
stepperChooser.textContent = "";
195
stepperDiv.textContent = "";
196
steppers = [];
197
},
198
enabled: false,
199
active: false,
200
// Stepper specific functions.
201
create: function create(pageIndex) {
202
var debug = document.createElement("div");
203
debug.id = "stepper" + pageIndex;
204
debug.hidden = true;
205
debug.className = "stepper";
206
stepperDiv.appendChild(debug);
207
var b = document.createElement("option");
208
b.textContent = "Page " + (pageIndex + 1);
209
b.value = pageIndex;
210
stepperChooser.appendChild(b);
211
var initBreakPoints = breakPoints[pageIndex] || [];
212
var stepper = new Stepper(debug, pageIndex, initBreakPoints);
213
steppers.push(stepper);
214
if (steppers.length === 1) {
215
this.selectStepper(pageIndex, false);
216
}
217
return stepper;
218
},
219
selectStepper: function selectStepper(pageIndex, selectPanel) {
220
var i;
221
pageIndex = pageIndex | 0;
222
if (selectPanel) {
223
this.manager.selectPanel(this);
224
}
225
for (i = 0; i < steppers.length; ++i) {
226
var stepper = steppers[i];
227
stepper.panel.hidden = stepper.pageIndex !== pageIndex;
228
}
229
var options = stepperChooser.options;
230
for (i = 0; i < options.length; ++i) {
231
var option = options[i];
232
option.selected = (option.value | 0) === pageIndex;
233
}
234
},
235
saveBreakPoints: function saveBreakPoints(pageIndex, bps) {
236
breakPoints[pageIndex] = bps;
237
sessionStorage.setItem("pdfjsBreakPoints", JSON.stringify(breakPoints));
238
},
239
};
240
})();
241
242
// The stepper for each page's IRQueue.
243
var Stepper = (function StepperClosure() {
244
// Shorter way to create element and optionally set textContent.
245
function c(tag, textContent) {
246
var d = document.createElement(tag);
247
if (textContent) {
248
d.textContent = textContent;
249
}
250
return d;
251
}
252
253
function simplifyArgs(args) {
254
if (typeof args === "string") {
255
var MAX_STRING_LENGTH = 75;
256
return args.length <= MAX_STRING_LENGTH
257
? args
258
: args.substring(0, MAX_STRING_LENGTH) + "...";
259
}
260
if (typeof args !== "object" || args === null) {
261
return args;
262
}
263
if ("length" in args) {
264
// array
265
var simpleArgs = [],
266
i,
267
ii;
268
var MAX_ITEMS = 10;
269
for (i = 0, ii = Math.min(MAX_ITEMS, args.length); i < ii; i++) {
270
simpleArgs.push(simplifyArgs(args[i]));
271
}
272
if (i < args.length) {
273
simpleArgs.push("...");
274
}
275
return simpleArgs;
276
}
277
var simpleObj = {};
278
for (var key in args) {
279
simpleObj[key] = simplifyArgs(args[key]);
280
}
281
return simpleObj;
282
}
283
284
// eslint-disable-next-line no-shadow
285
function Stepper(panel, pageIndex, initialBreakPoints) {
286
this.panel = panel;
287
this.breakPoint = 0;
288
this.nextBreakPoint = null;
289
this.pageIndex = pageIndex;
290
this.breakPoints = initialBreakPoints;
291
this.currentIdx = -1;
292
this.operatorListIdx = 0;
293
}
294
Stepper.prototype = {
295
init: function init(operatorList) {
296
var panel = this.panel;
297
var content = c("div", "c=continue, s=step");
298
var table = c("table");
299
content.appendChild(table);
300
table.cellSpacing = 0;
301
var headerRow = c("tr");
302
table.appendChild(headerRow);
303
headerRow.appendChild(c("th", "Break"));
304
headerRow.appendChild(c("th", "Idx"));
305
headerRow.appendChild(c("th", "fn"));
306
headerRow.appendChild(c("th", "args"));
307
panel.appendChild(content);
308
this.table = table;
309
this.updateOperatorList(operatorList);
310
},
311
updateOperatorList: function updateOperatorList(operatorList) {
312
var self = this;
313
314
function cboxOnClick() {
315
var x = +this.dataset.idx;
316
if (this.checked) {
317
self.breakPoints.push(x);
318
} else {
319
self.breakPoints.splice(self.breakPoints.indexOf(x), 1);
320
}
321
StepperManager.saveBreakPoints(self.pageIndex, self.breakPoints);
322
}
323
324
var MAX_OPERATORS_COUNT = 15000;
325
if (this.operatorListIdx > MAX_OPERATORS_COUNT) {
326
return;
327
}
328
329
var chunk = document.createDocumentFragment();
330
var operatorsToDisplay = Math.min(
331
MAX_OPERATORS_COUNT,
332
operatorList.fnArray.length
333
);
334
for (var i = this.operatorListIdx; i < operatorsToDisplay; i++) {
335
var line = c("tr");
336
line.className = "line";
337
line.dataset.idx = i;
338
chunk.appendChild(line);
339
var checked = this.breakPoints.includes(i);
340
var args = operatorList.argsArray[i] || [];
341
342
var breakCell = c("td");
343
var cbox = c("input");
344
cbox.type = "checkbox";
345
cbox.className = "points";
346
cbox.checked = checked;
347
cbox.dataset.idx = i;
348
cbox.onclick = cboxOnClick;
349
350
breakCell.appendChild(cbox);
351
line.appendChild(breakCell);
352
line.appendChild(c("td", i.toString()));
353
var fn = opMap[operatorList.fnArray[i]];
354
var decArgs = args;
355
if (fn === "showText") {
356
var glyphs = args[0];
357
var newArgs = [];
358
var str = [];
359
for (var j = 0; j < glyphs.length; j++) {
360
var glyph = glyphs[j];
361
if (typeof glyph === "object" && glyph !== null) {
362
str.push(glyph.fontChar);
363
} else {
364
if (str.length > 0) {
365
newArgs.push(str.join(""));
366
str = [];
367
}
368
newArgs.push(glyph); // null or number
369
}
370
}
371
if (str.length > 0) {
372
newArgs.push(str.join(""));
373
}
374
decArgs = [newArgs];
375
}
376
line.appendChild(c("td", fn));
377
line.appendChild(c("td", JSON.stringify(simplifyArgs(decArgs))));
378
}
379
if (operatorsToDisplay < operatorList.fnArray.length) {
380
var lastCell = c("td", "...");
381
lastCell.colspan = 4;
382
chunk.appendChild(lastCell);
383
}
384
this.operatorListIdx = operatorList.fnArray.length;
385
this.table.appendChild(chunk);
386
},
387
getNextBreakPoint: function getNextBreakPoint() {
388
this.breakPoints.sort(function (a, b) {
389
return a - b;
390
});
391
for (var i = 0; i < this.breakPoints.length; i++) {
392
if (this.breakPoints[i] > this.currentIdx) {
393
return this.breakPoints[i];
394
}
395
}
396
return null;
397
},
398
breakIt: function breakIt(idx, callback) {
399
StepperManager.selectStepper(this.pageIndex, true);
400
var self = this;
401
var dom = document;
402
self.currentIdx = idx;
403
var listener = function (e) {
404
switch (e.keyCode) {
405
case 83: // step
406
dom.removeEventListener("keydown", listener);
407
self.nextBreakPoint = self.currentIdx + 1;
408
self.goTo(-1);
409
callback();
410
break;
411
case 67: // continue
412
dom.removeEventListener("keydown", listener);
413
var breakPoint = self.getNextBreakPoint();
414
self.nextBreakPoint = breakPoint;
415
self.goTo(-1);
416
callback();
417
break;
418
}
419
};
420
dom.addEventListener("keydown", listener);
421
self.goTo(idx);
422
},
423
goTo: function goTo(idx) {
424
var allRows = this.panel.getElementsByClassName("line");
425
for (var x = 0, xx = allRows.length; x < xx; ++x) {
426
var row = allRows[x];
427
if ((row.dataset.idx | 0) === idx) {
428
row.style.backgroundColor = "rgb(251,250,207)";
429
row.scrollIntoView();
430
} else {
431
row.style.backgroundColor = null;
432
}
433
}
434
},
435
};
436
return Stepper;
437
})();
438
439
var Stats = (function Stats() {
440
var stats = [];
441
function clear(node) {
442
while (node.hasChildNodes()) {
443
node.removeChild(node.lastChild);
444
}
445
}
446
function getStatIndex(pageNumber) {
447
for (var i = 0, ii = stats.length; i < ii; ++i) {
448
if (stats[i].pageNumber === pageNumber) {
449
return i;
450
}
451
}
452
return false;
453
}
454
return {
455
// Properties/functions needed by PDFBug.
456
id: "Stats",
457
name: "Stats",
458
panel: null,
459
manager: null,
460
init(pdfjsLib) {},
461
enabled: false,
462
active: false,
463
// Stats specific functions.
464
add(pageNumber, stat) {
465
if (!stat) {
466
return;
467
}
468
var statsIndex = getStatIndex(pageNumber);
469
if (statsIndex !== false) {
470
const b = stats[statsIndex];
471
this.panel.removeChild(b.div);
472
stats.splice(statsIndex, 1);
473
}
474
var wrapper = document.createElement("div");
475
wrapper.className = "stats";
476
var title = document.createElement("div");
477
title.className = "title";
478
title.textContent = "Page: " + pageNumber;
479
var statsDiv = document.createElement("div");
480
statsDiv.textContent = stat.toString();
481
wrapper.appendChild(title);
482
wrapper.appendChild(statsDiv);
483
stats.push({ pageNumber, div: wrapper });
484
stats.sort(function (a, b) {
485
return a.pageNumber - b.pageNumber;
486
});
487
clear(this.panel);
488
for (var i = 0, ii = stats.length; i < ii; ++i) {
489
this.panel.appendChild(stats[i].div);
490
}
491
},
492
cleanup() {
493
stats = [];
494
clear(this.panel);
495
},
496
};
497
})();
498
499
// Manages all the debugging tools.
500
window.PDFBug = (function PDFBugClosure() {
501
var panelWidth = 300;
502
var buttons = [];
503
var activePanel = null;
504
505
return {
506
tools: [FontInspector, StepperManager, Stats],
507
enable(ids) {
508
var all = false,
509
tools = this.tools;
510
if (ids.length === 1 && ids[0] === "all") {
511
all = true;
512
}
513
for (var i = 0; i < tools.length; ++i) {
514
var tool = tools[i];
515
if (all || ids.includes(tool.id)) {
516
tool.enabled = true;
517
}
518
}
519
if (!all) {
520
// Sort the tools by the order they are enabled.
521
tools.sort(function (a, b) {
522
var indexA = ids.indexOf(a.id);
523
indexA = indexA < 0 ? tools.length : indexA;
524
var indexB = ids.indexOf(b.id);
525
indexB = indexB < 0 ? tools.length : indexB;
526
return indexA - indexB;
527
});
528
}
529
},
530
init(pdfjsLib, container) {
531
/*
532
* Basic Layout:
533
* PDFBug
534
* Controls
535
* Panels
536
* Panel
537
* Panel
538
* ...
539
*/
540
var ui = document.createElement("div");
541
ui.id = "PDFBug";
542
543
var controls = document.createElement("div");
544
controls.setAttribute("class", "controls");
545
ui.appendChild(controls);
546
547
var panels = document.createElement("div");
548
panels.setAttribute("class", "panels");
549
ui.appendChild(panels);
550
551
container.appendChild(ui);
552
container.style.right = panelWidth + "px";
553
554
// Initialize all the debugging tools.
555
var tools = this.tools;
556
var self = this;
557
for (var i = 0; i < tools.length; ++i) {
558
var tool = tools[i];
559
var panel = document.createElement("div");
560
var panelButton = document.createElement("button");
561
panelButton.textContent = tool.name;
562
panelButton.addEventListener(
563
"click",
564
(function (selected) {
565
return function (event) {
566
event.preventDefault();
567
self.selectPanel(selected);
568
};
569
})(i)
570
);
571
controls.appendChild(panelButton);
572
panels.appendChild(panel);
573
tool.panel = panel;
574
tool.manager = this;
575
if (tool.enabled) {
576
tool.init(pdfjsLib);
577
} else {
578
panel.textContent =
579
tool.name +
580
" is disabled. To enable add " +
581
' "' +
582
tool.id +
583
'" to the pdfBug parameter ' +
584
"and refresh (separate multiple by commas).";
585
}
586
buttons.push(panelButton);
587
}
588
this.selectPanel(0);
589
},
590
cleanup() {
591
for (var i = 0, ii = this.tools.length; i < ii; i++) {
592
if (this.tools[i].enabled) {
593
this.tools[i].cleanup();
594
}
595
}
596
},
597
selectPanel(index) {
598
if (typeof index !== "number") {
599
index = this.tools.indexOf(index);
600
}
601
if (index === activePanel) {
602
return;
603
}
604
activePanel = index;
605
var tools = this.tools;
606
for (var j = 0; j < tools.length; ++j) {
607
var isActive = j === index;
608
buttons[j].classList.toggle("active", isActive);
609
tools[j].active = isActive;
610
tools[j].panel.hidden = !isActive;
611
}
612
},
613
};
614
})();
615
616