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