a177afc206
This fixes only those warnings, as reported by https://lgtm.com/projects/g/mozilla/pdf.js?mode=list, that make sense (as far as I'm concerned). Hence this patch leaves the following things unaddressed: - The "recommendation"-category, since it only complains about unused variables. However, note that all of those cases are purposely included and that there's thus ESLint-disable comments added to explictly allow them. - The "warning"-category, which still contains two complaints. However, as far as I can tell, they are both false positives. Given first of all the false positives of the LGTM static analyzer, and secondly that we'd need to add (essentially duplicated) disable-comments for the unused variable cases, it's not entirely clear to me if we actually want to work towards including LGTM in the PDF.js project (e.g. running alongside Travis) or if we should just close issue 11965.
624 lines
18 KiB
JavaScript
624 lines
18 KiB
JavaScript
/* Copyright 2012 Mozilla Foundation
|
|
*
|
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
|
* you may not use this file except in compliance with the License.
|
|
* You may obtain a copy of the License at
|
|
*
|
|
* http://www.apache.org/licenses/LICENSE-2.0
|
|
*
|
|
* Unless required by applicable law or agreed to in writing, software
|
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
* See the License for the specific language governing permissions and
|
|
* limitations under the License.
|
|
*/
|
|
/* eslint-disable no-var */
|
|
|
|
"use strict";
|
|
|
|
var FontInspector = (function FontInspectorClosure() {
|
|
var fonts;
|
|
var active = false;
|
|
var fontAttribute = "data-font-name";
|
|
function removeSelection() {
|
|
const divs = document.querySelectorAll(`span[${fontAttribute}]`);
|
|
for (const div of divs) {
|
|
div.className = "";
|
|
}
|
|
}
|
|
function resetSelection() {
|
|
const divs = document.querySelectorAll(`span[${fontAttribute}]`);
|
|
for (const div of divs) {
|
|
div.className = "debuggerHideText";
|
|
}
|
|
}
|
|
function selectFont(fontName, show) {
|
|
const divs = document.querySelectorAll(
|
|
`span[${fontAttribute}=${fontName}]`
|
|
);
|
|
for (const div of divs) {
|
|
div.className = show ? "debuggerShowText" : "debuggerHideText";
|
|
}
|
|
}
|
|
function textLayerClick(e) {
|
|
if (
|
|
!e.target.dataset.fontName ||
|
|
e.target.tagName.toUpperCase() !== "SPAN"
|
|
) {
|
|
return;
|
|
}
|
|
var fontName = e.target.dataset.fontName;
|
|
var selects = document.getElementsByTagName("input");
|
|
for (var i = 0; i < selects.length; ++i) {
|
|
var select = selects[i];
|
|
if (select.dataset.fontName !== fontName) {
|
|
continue;
|
|
}
|
|
select.checked = !select.checked;
|
|
selectFont(fontName, select.checked);
|
|
select.scrollIntoView();
|
|
}
|
|
}
|
|
return {
|
|
// Properties/functions needed by PDFBug.
|
|
id: "FontInspector",
|
|
name: "Font Inspector",
|
|
panel: null,
|
|
manager: null,
|
|
init: function init(pdfjsLib) {
|
|
var panel = this.panel;
|
|
var tmp = document.createElement("button");
|
|
tmp.addEventListener("click", resetSelection);
|
|
tmp.textContent = "Refresh";
|
|
panel.appendChild(tmp);
|
|
|
|
fonts = document.createElement("div");
|
|
panel.appendChild(fonts);
|
|
},
|
|
cleanup: function cleanup() {
|
|
fonts.textContent = "";
|
|
},
|
|
enabled: false,
|
|
get active() {
|
|
return active;
|
|
},
|
|
set active(value) {
|
|
active = value;
|
|
if (active) {
|
|
document.body.addEventListener("click", textLayerClick, true);
|
|
resetSelection();
|
|
} else {
|
|
document.body.removeEventListener("click", textLayerClick, true);
|
|
removeSelection();
|
|
}
|
|
},
|
|
// FontInspector specific functions.
|
|
fontAdded: function fontAdded(fontObj, url) {
|
|
function properties(obj, list) {
|
|
var moreInfo = document.createElement("table");
|
|
for (var i = 0; i < list.length; i++) {
|
|
var tr = document.createElement("tr");
|
|
var td1 = document.createElement("td");
|
|
td1.textContent = list[i];
|
|
tr.appendChild(td1);
|
|
var td2 = document.createElement("td");
|
|
td2.textContent = obj[list[i]].toString();
|
|
tr.appendChild(td2);
|
|
moreInfo.appendChild(tr);
|
|
}
|
|
return moreInfo;
|
|
}
|
|
var moreInfo = properties(fontObj, ["name", "type"]);
|
|
const fontName = fontObj.loadedName;
|
|
var font = document.createElement("div");
|
|
var name = document.createElement("span");
|
|
name.textContent = fontName;
|
|
var download = document.createElement("a");
|
|
if (url) {
|
|
url = /url\(['"]?([^\)"']+)/.exec(url);
|
|
download.href = url[1];
|
|
} else if (fontObj.data) {
|
|
download.href = URL.createObjectURL(
|
|
new Blob([fontObj.data], { type: fontObj.mimeType })
|
|
);
|
|
}
|
|
download.textContent = "Download";
|
|
var logIt = document.createElement("a");
|
|
logIt.href = "";
|
|
logIt.textContent = "Log";
|
|
logIt.addEventListener("click", function (event) {
|
|
event.preventDefault();
|
|
console.log(fontObj);
|
|
});
|
|
const select = document.createElement("input");
|
|
select.setAttribute("type", "checkbox");
|
|
select.dataset.fontName = fontName;
|
|
select.addEventListener("click", function () {
|
|
selectFont(fontName, select.checked);
|
|
});
|
|
font.appendChild(select);
|
|
font.appendChild(name);
|
|
font.appendChild(document.createTextNode(" "));
|
|
font.appendChild(download);
|
|
font.appendChild(document.createTextNode(" "));
|
|
font.appendChild(logIt);
|
|
font.appendChild(moreInfo);
|
|
fonts.appendChild(font);
|
|
// Somewhat of a hack, should probably add a hook for when the text layer
|
|
// is done rendering.
|
|
setTimeout(() => {
|
|
if (this.active) {
|
|
resetSelection();
|
|
}
|
|
}, 2000);
|
|
},
|
|
};
|
|
})();
|
|
|
|
var opMap;
|
|
|
|
// Manages all the page steppers.
|
|
var StepperManager = (function StepperManagerClosure() {
|
|
var steppers = [];
|
|
var stepperDiv = null;
|
|
var stepperControls = null;
|
|
var stepperChooser = null;
|
|
var breakPoints = Object.create(null);
|
|
return {
|
|
// Properties/functions needed by PDFBug.
|
|
id: "Stepper",
|
|
name: "Stepper",
|
|
panel: null,
|
|
manager: null,
|
|
init: function init(pdfjsLib) {
|
|
var self = this;
|
|
stepperControls = document.createElement("div");
|
|
stepperChooser = document.createElement("select");
|
|
stepperChooser.addEventListener("change", function (event) {
|
|
self.selectStepper(this.value);
|
|
});
|
|
stepperControls.appendChild(stepperChooser);
|
|
stepperDiv = document.createElement("div");
|
|
this.panel.appendChild(stepperControls);
|
|
this.panel.appendChild(stepperDiv);
|
|
if (sessionStorage.getItem("pdfjsBreakPoints")) {
|
|
breakPoints = JSON.parse(sessionStorage.getItem("pdfjsBreakPoints"));
|
|
}
|
|
|
|
opMap = Object.create(null);
|
|
for (var key in pdfjsLib.OPS) {
|
|
opMap[pdfjsLib.OPS[key]] = key;
|
|
}
|
|
},
|
|
cleanup: function cleanup() {
|
|
stepperChooser.textContent = "";
|
|
stepperDiv.textContent = "";
|
|
steppers = [];
|
|
},
|
|
enabled: false,
|
|
active: false,
|
|
// Stepper specific functions.
|
|
create: function create(pageIndex) {
|
|
var debug = document.createElement("div");
|
|
debug.id = "stepper" + pageIndex;
|
|
debug.setAttribute("hidden", true);
|
|
debug.className = "stepper";
|
|
stepperDiv.appendChild(debug);
|
|
var b = document.createElement("option");
|
|
b.textContent = "Page " + (pageIndex + 1);
|
|
b.value = pageIndex;
|
|
stepperChooser.appendChild(b);
|
|
var initBreakPoints = breakPoints[pageIndex] || [];
|
|
var stepper = new Stepper(debug, pageIndex, initBreakPoints);
|
|
steppers.push(stepper);
|
|
if (steppers.length === 1) {
|
|
this.selectStepper(pageIndex, false);
|
|
}
|
|
return stepper;
|
|
},
|
|
selectStepper: function selectStepper(pageIndex, selectPanel) {
|
|
var i;
|
|
pageIndex = pageIndex | 0;
|
|
if (selectPanel) {
|
|
this.manager.selectPanel(this);
|
|
}
|
|
for (i = 0; i < steppers.length; ++i) {
|
|
var stepper = steppers[i];
|
|
if (stepper.pageIndex === pageIndex) {
|
|
stepper.panel.removeAttribute("hidden");
|
|
} else {
|
|
stepper.panel.setAttribute("hidden", true);
|
|
}
|
|
}
|
|
var options = stepperChooser.options;
|
|
for (i = 0; i < options.length; ++i) {
|
|
var option = options[i];
|
|
option.selected = (option.value | 0) === pageIndex;
|
|
}
|
|
},
|
|
saveBreakPoints: function saveBreakPoints(pageIndex, bps) {
|
|
breakPoints[pageIndex] = bps;
|
|
sessionStorage.setItem("pdfjsBreakPoints", JSON.stringify(breakPoints));
|
|
},
|
|
};
|
|
})();
|
|
|
|
// The stepper for each page's IRQueue.
|
|
var Stepper = (function StepperClosure() {
|
|
// Shorter way to create element and optionally set textContent.
|
|
function c(tag, textContent) {
|
|
var d = document.createElement(tag);
|
|
if (textContent) {
|
|
d.textContent = textContent;
|
|
}
|
|
return d;
|
|
}
|
|
|
|
function simplifyArgs(args) {
|
|
if (typeof args === "string") {
|
|
var MAX_STRING_LENGTH = 75;
|
|
return args.length <= MAX_STRING_LENGTH
|
|
? args
|
|
: args.substring(0, MAX_STRING_LENGTH) + "...";
|
|
}
|
|
if (typeof args !== "object" || args === null) {
|
|
return args;
|
|
}
|
|
if ("length" in args) {
|
|
// array
|
|
var simpleArgs = [],
|
|
i,
|
|
ii;
|
|
var MAX_ITEMS = 10;
|
|
for (i = 0, ii = Math.min(MAX_ITEMS, args.length); i < ii; i++) {
|
|
simpleArgs.push(simplifyArgs(args[i]));
|
|
}
|
|
if (i < args.length) {
|
|
simpleArgs.push("...");
|
|
}
|
|
return simpleArgs;
|
|
}
|
|
var simpleObj = {};
|
|
for (var key in args) {
|
|
simpleObj[key] = simplifyArgs(args[key]);
|
|
}
|
|
return simpleObj;
|
|
}
|
|
|
|
// eslint-disable-next-line no-shadow
|
|
function Stepper(panel, pageIndex, initialBreakPoints) {
|
|
this.panel = panel;
|
|
this.breakPoint = 0;
|
|
this.nextBreakPoint = null;
|
|
this.pageIndex = pageIndex;
|
|
this.breakPoints = initialBreakPoints;
|
|
this.currentIdx = -1;
|
|
this.operatorListIdx = 0;
|
|
}
|
|
Stepper.prototype = {
|
|
init: function init(operatorList) {
|
|
var panel = this.panel;
|
|
var content = c("div", "c=continue, s=step");
|
|
var table = c("table");
|
|
content.appendChild(table);
|
|
table.cellSpacing = 0;
|
|
var headerRow = c("tr");
|
|
table.appendChild(headerRow);
|
|
headerRow.appendChild(c("th", "Break"));
|
|
headerRow.appendChild(c("th", "Idx"));
|
|
headerRow.appendChild(c("th", "fn"));
|
|
headerRow.appendChild(c("th", "args"));
|
|
panel.appendChild(content);
|
|
this.table = table;
|
|
this.updateOperatorList(operatorList);
|
|
},
|
|
updateOperatorList: function updateOperatorList(operatorList) {
|
|
var self = this;
|
|
|
|
function cboxOnClick() {
|
|
var x = +this.dataset.idx;
|
|
if (this.checked) {
|
|
self.breakPoints.push(x);
|
|
} else {
|
|
self.breakPoints.splice(self.breakPoints.indexOf(x), 1);
|
|
}
|
|
StepperManager.saveBreakPoints(self.pageIndex, self.breakPoints);
|
|
}
|
|
|
|
var MAX_OPERATORS_COUNT = 15000;
|
|
if (this.operatorListIdx > MAX_OPERATORS_COUNT) {
|
|
return;
|
|
}
|
|
|
|
var chunk = document.createDocumentFragment();
|
|
var operatorsToDisplay = Math.min(
|
|
MAX_OPERATORS_COUNT,
|
|
operatorList.fnArray.length
|
|
);
|
|
for (var i = this.operatorListIdx; i < operatorsToDisplay; i++) {
|
|
var line = c("tr");
|
|
line.className = "line";
|
|
line.dataset.idx = i;
|
|
chunk.appendChild(line);
|
|
var checked = this.breakPoints.includes(i);
|
|
var args = operatorList.argsArray[i] || [];
|
|
|
|
var breakCell = c("td");
|
|
var cbox = c("input");
|
|
cbox.type = "checkbox";
|
|
cbox.className = "points";
|
|
cbox.checked = checked;
|
|
cbox.dataset.idx = i;
|
|
cbox.onclick = cboxOnClick;
|
|
|
|
breakCell.appendChild(cbox);
|
|
line.appendChild(breakCell);
|
|
line.appendChild(c("td", i.toString()));
|
|
var fn = opMap[operatorList.fnArray[i]];
|
|
var decArgs = args;
|
|
if (fn === "showText") {
|
|
var glyphs = args[0];
|
|
var newArgs = [];
|
|
var str = [];
|
|
for (var j = 0; j < glyphs.length; j++) {
|
|
var glyph = glyphs[j];
|
|
if (typeof glyph === "object" && glyph !== null) {
|
|
str.push(glyph.fontChar);
|
|
} else {
|
|
if (str.length > 0) {
|
|
newArgs.push(str.join(""));
|
|
str = [];
|
|
}
|
|
newArgs.push(glyph); // null or number
|
|
}
|
|
}
|
|
if (str.length > 0) {
|
|
newArgs.push(str.join(""));
|
|
}
|
|
decArgs = [newArgs];
|
|
}
|
|
line.appendChild(c("td", fn));
|
|
line.appendChild(c("td", JSON.stringify(simplifyArgs(decArgs))));
|
|
}
|
|
if (operatorsToDisplay < operatorList.fnArray.length) {
|
|
var lastCell = c("td", "...");
|
|
lastCell.colspan = 4;
|
|
chunk.appendChild(lastCell);
|
|
}
|
|
this.operatorListIdx = operatorList.fnArray.length;
|
|
this.table.appendChild(chunk);
|
|
},
|
|
getNextBreakPoint: function getNextBreakPoint() {
|
|
this.breakPoints.sort(function (a, b) {
|
|
return a - b;
|
|
});
|
|
for (var i = 0; i < this.breakPoints.length; i++) {
|
|
if (this.breakPoints[i] > this.currentIdx) {
|
|
return this.breakPoints[i];
|
|
}
|
|
}
|
|
return null;
|
|
},
|
|
breakIt: function breakIt(idx, callback) {
|
|
StepperManager.selectStepper(this.pageIndex, true);
|
|
var self = this;
|
|
var dom = document;
|
|
self.currentIdx = idx;
|
|
var listener = function (e) {
|
|
switch (e.keyCode) {
|
|
case 83: // step
|
|
dom.removeEventListener("keydown", listener);
|
|
self.nextBreakPoint = self.currentIdx + 1;
|
|
self.goTo(-1);
|
|
callback();
|
|
break;
|
|
case 67: // continue
|
|
dom.removeEventListener("keydown", listener);
|
|
var breakPoint = self.getNextBreakPoint();
|
|
self.nextBreakPoint = breakPoint;
|
|
self.goTo(-1);
|
|
callback();
|
|
break;
|
|
}
|
|
};
|
|
dom.addEventListener("keydown", listener);
|
|
self.goTo(idx);
|
|
},
|
|
goTo: function goTo(idx) {
|
|
var allRows = this.panel.getElementsByClassName("line");
|
|
for (var x = 0, xx = allRows.length; x < xx; ++x) {
|
|
var row = allRows[x];
|
|
if ((row.dataset.idx | 0) === idx) {
|
|
row.style.backgroundColor = "rgb(251,250,207)";
|
|
row.scrollIntoView();
|
|
} else {
|
|
row.style.backgroundColor = null;
|
|
}
|
|
}
|
|
},
|
|
};
|
|
return Stepper;
|
|
})();
|
|
|
|
var Stats = (function Stats() {
|
|
var stats = [];
|
|
function clear(node) {
|
|
while (node.hasChildNodes()) {
|
|
node.removeChild(node.lastChild);
|
|
}
|
|
}
|
|
function getStatIndex(pageNumber) {
|
|
for (var i = 0, ii = stats.length; i < ii; ++i) {
|
|
if (stats[i].pageNumber === pageNumber) {
|
|
return i;
|
|
}
|
|
}
|
|
return false;
|
|
}
|
|
return {
|
|
// Properties/functions needed by PDFBug.
|
|
id: "Stats",
|
|
name: "Stats",
|
|
panel: null,
|
|
manager: null,
|
|
init(pdfjsLib) {},
|
|
enabled: false,
|
|
active: false,
|
|
// Stats specific functions.
|
|
add(pageNumber, stat) {
|
|
if (!stat) {
|
|
return;
|
|
}
|
|
var statsIndex = getStatIndex(pageNumber);
|
|
if (statsIndex !== false) {
|
|
const b = stats[statsIndex];
|
|
this.panel.removeChild(b.div);
|
|
stats.splice(statsIndex, 1);
|
|
}
|
|
var wrapper = document.createElement("div");
|
|
wrapper.className = "stats";
|
|
var title = document.createElement("div");
|
|
title.className = "title";
|
|
title.textContent = "Page: " + pageNumber;
|
|
var statsDiv = document.createElement("div");
|
|
statsDiv.textContent = stat.toString();
|
|
wrapper.appendChild(title);
|
|
wrapper.appendChild(statsDiv);
|
|
stats.push({ pageNumber, div: wrapper });
|
|
stats.sort(function (a, b) {
|
|
return a.pageNumber - b.pageNumber;
|
|
});
|
|
clear(this.panel);
|
|
for (var i = 0, ii = stats.length; i < ii; ++i) {
|
|
this.panel.appendChild(stats[i].div);
|
|
}
|
|
},
|
|
cleanup() {
|
|
stats = [];
|
|
clear(this.panel);
|
|
},
|
|
};
|
|
})();
|
|
|
|
// Manages all the debugging tools.
|
|
window.PDFBug = (function PDFBugClosure() {
|
|
var panelWidth = 300;
|
|
var buttons = [];
|
|
var activePanel = null;
|
|
|
|
return {
|
|
tools: [FontInspector, StepperManager, Stats],
|
|
enable(ids) {
|
|
var all = false,
|
|
tools = this.tools;
|
|
if (ids.length === 1 && ids[0] === "all") {
|
|
all = true;
|
|
}
|
|
for (var i = 0; i < tools.length; ++i) {
|
|
var tool = tools[i];
|
|
if (all || ids.includes(tool.id)) {
|
|
tool.enabled = true;
|
|
}
|
|
}
|
|
if (!all) {
|
|
// Sort the tools by the order they are enabled.
|
|
tools.sort(function (a, b) {
|
|
var indexA = ids.indexOf(a.id);
|
|
indexA = indexA < 0 ? tools.length : indexA;
|
|
var indexB = ids.indexOf(b.id);
|
|
indexB = indexB < 0 ? tools.length : indexB;
|
|
return indexA - indexB;
|
|
});
|
|
}
|
|
},
|
|
init(pdfjsLib, container) {
|
|
/*
|
|
* Basic Layout:
|
|
* PDFBug
|
|
* Controls
|
|
* Panels
|
|
* Panel
|
|
* Panel
|
|
* ...
|
|
*/
|
|
var ui = document.createElement("div");
|
|
ui.id = "PDFBug";
|
|
|
|
var controls = document.createElement("div");
|
|
controls.setAttribute("class", "controls");
|
|
ui.appendChild(controls);
|
|
|
|
var panels = document.createElement("div");
|
|
panels.setAttribute("class", "panels");
|
|
ui.appendChild(panels);
|
|
|
|
container.appendChild(ui);
|
|
container.style.right = panelWidth + "px";
|
|
|
|
// Initialize all the debugging tools.
|
|
var tools = this.tools;
|
|
var self = this;
|
|
for (var i = 0; i < tools.length; ++i) {
|
|
var tool = tools[i];
|
|
var panel = document.createElement("div");
|
|
var panelButton = document.createElement("button");
|
|
panelButton.textContent = tool.name;
|
|
panelButton.addEventListener(
|
|
"click",
|
|
(function (selected) {
|
|
return function (event) {
|
|
event.preventDefault();
|
|
self.selectPanel(selected);
|
|
};
|
|
})(i)
|
|
);
|
|
controls.appendChild(panelButton);
|
|
panels.appendChild(panel);
|
|
tool.panel = panel;
|
|
tool.manager = this;
|
|
if (tool.enabled) {
|
|
tool.init(pdfjsLib);
|
|
} else {
|
|
panel.textContent =
|
|
tool.name +
|
|
" is disabled. To enable add " +
|
|
' "' +
|
|
tool.id +
|
|
'" to the pdfBug parameter ' +
|
|
"and refresh (separate multiple by commas).";
|
|
}
|
|
buttons.push(panelButton);
|
|
}
|
|
this.selectPanel(0);
|
|
},
|
|
cleanup() {
|
|
for (var i = 0, ii = this.tools.length; i < ii; i++) {
|
|
if (this.tools[i].enabled) {
|
|
this.tools[i].cleanup();
|
|
}
|
|
}
|
|
},
|
|
selectPanel(index) {
|
|
if (typeof index !== "number") {
|
|
index = this.tools.indexOf(index);
|
|
}
|
|
if (index === activePanel) {
|
|
return;
|
|
}
|
|
activePanel = index;
|
|
var tools = this.tools;
|
|
for (var j = 0; j < tools.length; ++j) {
|
|
if (j === index) {
|
|
buttons[j].setAttribute("class", "active");
|
|
tools[j].active = true;
|
|
tools[j].panel.removeAttribute("hidden");
|
|
} else {
|
|
buttons[j].setAttribute("class", "");
|
|
tools[j].active = false;
|
|
tools[j].panel.setAttribute("hidden", "true");
|
|
}
|
|
}
|
|
},
|
|
};
|
|
})();
|