/* -*- mode: java; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ /* vim: set shiftwidth=2 tabstop=2 autoindent cindent expandtab: */ /* 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. */ /* globals pdfjs */ 'use strict'; var fontinspector = (function fontinspectorclosure() { var fonts; var active = false; var fontattribute = 'data-font-name'; function removeselection() { var divs = document.queryselectorall('div[' + fontattribute + ']'); for (var i = 0, ii = divs.length; i < ii; ++i) { var div = divs[i]; div.classname = ''; } } function resetselection() { var divs = document.queryselectorall('div[' + fontattribute + ']'); for (var i = 0, ii = divs.length; i < ii; ++i) { var div = divs[i]; div.classname = 'debuggerhidetext'; } } function selectfont(fontname, show) { var divs = document.queryselectorall('div[' + fontattribute + '=' + fontname + ']'); for (var i = 0, ii = divs.length; i < ii; ++i) { var div = divs[i]; div.classname = show ? 'debuggershowtext' : 'debuggerhidetext'; } } function textlayerclick(e) { if (!e.target.dataset.fontname || e.target.tagname.touppercase() !== 'div') { 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() { var panel = this.panel; panel.setattribute('style', 'padding: 5px;'); 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']); var 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) { url = url.createobjecturl(new blob([fontobj.data], { type: fontobj.mimetype })); download.href = url; } download.textcontent = 'download'; var logit = document.createelement('a'); logit.href = ''; logit.textcontent = 'log'; logit.addeventlistener('click', function(event) { event.preventdefault(); console.log(fontobj); }); var select = document.createelement('input'); select.setattribute('type', 'checkbox'); select.dataset.fontname = fontname; select.addeventlistener('click', (function(select, fontname) { return (function() { selectfont(fontname, select.checked); }); })(select, fontname)); 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(function() { if (this.active) { resetselection(); } }.bind(this), 2000); } }; })(); // manages all the page steppers. var steppermanager = (function steppermanagerclosure() { var steppers = []; var stepperdiv = null; var steppercontrols = null; var stepperchooser = null; var breakpoints = {}; return { // properties/functions needed by pdfbug. id: 'stepper', name: 'stepper', panel: null, manager: null, init: function init() { var self = this; this.panel.setattribute('style', 'padding: 5px;'); 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')); } }, 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; } var opmap = null; function simplifyargs(args) { if (typeof args === 'string') { var max_string_length = 75; return args.length <= max_string_length ? args : args.substr(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; } 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() { 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; if (!opmap) { opmap = object.create(null); for (var key in pdfjs.ops) { opmap[pdfjs.ops[key]] = key; } } }, 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.indexof(i) !== -1; 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) { line = c('tr'); 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, false); self.nextbreakpoint = self.currentidx + 1; self.goto(-1); callback(); break; case 67: // continue dom.removeeventlistener('keydown', listener, false); var breakpoint = self.getnextbreakpoint(); self.nextbreakpoint = breakpoint; self.goto(-1); callback(); break; } }; dom.addeventlistener('keydown', listener, false); 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: function init() { this.panel.setattribute('style', 'padding: 5px;'); pdfjs.enablestats = true; }, enabled: false, active: false, // stats specific functions. add: function(pagenumber, stat) { if (!stat) { return; } var statsindex = getstatindex(pagenumber); if (statsindex !== false) { var 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: 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: function () { stats = []; clear(this.panel); } }; })(); // manages all the debugging tools. var pdfbug = (function pdfbugclosure() { var panelwidth = 300; var buttons = []; var activepanel = null; return { tools: [ fontinspector, steppermanager, stats ], enable: function(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.indexof(tool.id) !== -1) { 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: function init() { /* * 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); var container = document.getelementbyid('viewercontainer'); 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(); } else { panel.textcontent = tool.name + ' is disabled. to enable add ' + ' "' + tool.id + '" to the pdfbug parameter ' + 'and refresh (seperate multiple by commas).'; } buttons.push(panelbutton); } this.selectpanel(0); }, cleanup: function cleanup() { for (var i = 0, ii = this.tools.length; i < ii; i++) { if (this.tools[i].enabled) { this.tools[i].cleanup(); } } }, selectpanel: function 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'); } } } }; })();