Browse Source

Merge upstream and change to error parameter for callback.

Brendan Dahl 14 years ago
parent
commit
e0c231eec7
  1. 2
      README.md
  2. 5
      extensions/firefox/install.rdf
  3. 197
      src/canvas.js
  4. 31
      src/core.js
  5. 41
      src/evaluator.js
  6. 325
      src/fonts.js
  7. 3
      src/glyphlist.js
  8. 3
      src/metrics.js
  9. 10
      test/driver.js
  10. 1
      test/pdfs/.gitignore
  11. BIN
      test/pdfs/issue840.pdf
  12. 1
      test/pdfs/piperine.pdf.link
  13. 1
      test/pdfs/protectip.pdf.link
  14. 20
      test/test_manifest.json
  15. 21
      web/viewer.css
  16. 19
      web/viewer.js

2
README.md

@ -1,6 +1,6 @@
# pdf.js # pdf.js
## Overview ## Overview

5
extensions/firefox/install.rdf

@ -6,13 +6,13 @@
<Description about="urn:mozilla:install-manifest"> <Description about="urn:mozilla:install-manifest">
<em:id>uriloader@pdf.js</em:id> <em:id>uriloader@pdf.js</em:id>
<em:name>pdf.js</em:name> <em:name>pdf.js</em:name>
<em:version>0.1</em:version> <em:version>0.1.0</em:version>
<em:iconURL>chrome://pdf.js/skin/logo.png</em:iconURL> <em:iconURL>chrome://pdf.js/skin/logo.png</em:iconURL>
<em:targetApplication> <em:targetApplication>
<Description> <Description>
<em:id>{ec8030f7-c20a-464f-9b0e-13a3a9e97384}</em:id> <em:id>{ec8030f7-c20a-464f-9b0e-13a3a9e97384}</em:id>
<em:minVersion>6.0</em:minVersion> <em:minVersion>6.0</em:minVersion>
<em:maxVersion>11.0.*</em:maxVersion> <em:maxVersion>11.0a1</em:maxVersion>
</Description> </Description>
</em:targetApplication> </em:targetApplication>
<em:bootstrap>true</em:bootstrap> <em:bootstrap>true</em:bootstrap>
@ -20,5 +20,6 @@
<em:creator>Vivien Nicolas</em:creator> <em:creator>Vivien Nicolas</em:creator>
<em:description>pdf.js uri loader</em:description> <em:description>pdf.js uri loader</em:description>
<em:homepageURL>https://github.com/mozilla/pdf.js/</em:homepageURL> <em:homepageURL>https://github.com/mozilla/pdf.js/</em:homepageURL>
<em:type>2</em:type>
</Description> </Description>
</RDF> </RDF>

197
src/canvas.js

@ -174,7 +174,7 @@ var CanvasGraphics = (function canvasGraphics() {
// before it stops and shedules a continue of execution. // before it stops and shedules a continue of execution.
var kExecutionTime = 50; var kExecutionTime = 50;
function constructor(canvasCtx, objs) { function constructor(canvasCtx, objs, textLayer) {
this.ctx = canvasCtx; this.ctx = canvasCtx;
this.current = new CanvasExtraState(); this.current = new CanvasExtraState();
this.stateStack = []; this.stateStack = [];
@ -183,7 +183,7 @@ var CanvasGraphics = (function canvasGraphics() {
this.xobjs = null; this.xobjs = null;
this.ScratchCanvas = ScratchCanvas; this.ScratchCanvas = ScratchCanvas;
this.objs = objs; this.objs = objs;
this.textLayer = textLayer;
if (canvasCtx) { if (canvasCtx) {
addContextCurrentTransform(canvasCtx); addContextCurrentTransform(canvasCtx);
} }
@ -212,7 +212,13 @@ var CanvasGraphics = (function canvasGraphics() {
this.ctx.transform(0, -1, -1, 0, cw, ch); this.ctx.transform(0, -1, -1, 0, cw, ch);
break; break;
} }
// Scale so that canvas units are the same as PDF user space units
this.ctx.scale(cw / mediaBox.width, ch / mediaBox.height); this.ctx.scale(cw / mediaBox.width, ch / mediaBox.height);
this.textDivs = [];
this.textLayerQueue = [];
// Prevent textLayerQueue from being rendered while rendering a new page
if (this.textLayerTimer)
clearTimeout(this.textLayerTimer);
}, },
executeIRQueue: function canvasGraphicsExecuteIRQueue(codeIR, executeIRQueue: function canvasGraphicsExecuteIRQueue(codeIR,
@ -270,6 +276,37 @@ var CanvasGraphics = (function canvasGraphics() {
endDrawing: function canvasGraphicsEndDrawing() { endDrawing: function canvasGraphicsEndDrawing() {
this.ctx.restore(); this.ctx.restore();
var textLayer = this.textLayer;
if (!textLayer)
return;
var self = this;
var renderTextLayer = function canvasRenderTextLayer() {
var textDivs = self.textDivs;
for (var i = 0, length = textDivs.length; i < length; ++i) {
if (textDivs[i].dataset.textLength > 1) { // avoid div by zero
textLayer.appendChild(textDivs[i]);
// Adjust div width (via letterSpacing) to match canvas text
// Due to the .offsetWidth calls, this is slow
textDivs[i].style.letterSpacing =
((textDivs[i].dataset.canvasWidth - textDivs[i].offsetWidth) /
(textDivs[i].dataset.textLength - 1)) + 'px';
}
}
}
var textLayerQueue = this.textLayerQueue;
textLayerQueue.push(renderTextLayer);
// Lazy textLayer rendering (to prevent UI hangs)
// Only render queue if activity has stopped, where "no activity" ==
// "no beginDrawing() calls in the last N ms"
this.textLayerTimer = setTimeout(function renderTextLayerQueue() {
// Render most recent (==most relevant) layers first
for (var i = textLayerQueue.length - 1; i >= 0; i--) {
textLayerQueue.pop().call();
}
}, 500);
}, },
// Graphics state // Graphics state
@ -528,23 +565,93 @@ var CanvasGraphics = (function canvasGraphics() {
nextLine: function canvasGraphicsNextLine() { nextLine: function canvasGraphicsNextLine() {
this.moveText(0, this.current.leading); this.moveText(0, this.current.leading);
}, },
showText: function canvasGraphicsShowText(text) { applyTextTransforms: function canvasApplyTransforms() {
var ctx = this.ctx;
var current = this.current;
var textHScale = current.textHScale;
var fontMatrix = current.font.fontMatrix || IDENTITY_MATRIX;
ctx.transform.apply(ctx, current.textMatrix);
ctx.scale(1, -1);
ctx.translate(current.x, -1 * current.y);
ctx.transform.apply(ctx, fontMatrix);
ctx.scale(textHScale, 1);
},
getTextGeometry: function canvasGetTextGeometry() {
var geometry = {};
var ctx = this.ctx;
var font = this.current.font;
var ctxMatrix = ctx.mozCurrentTransform;
if (ctxMatrix) {
var bl = Util.applyTransform([0, 0], ctxMatrix);
var tr = Util.applyTransform([1, 1], ctxMatrix);
geometry.x = bl[0];
geometry.y = bl[1];
geometry.hScale = tr[0] - bl[0];
geometry.vScale = tr[1] - bl[1];
}
var spaceGlyph = font.charsToGlyphs(' ');
// Hack (sometimes space is not encoded)
if (spaceGlyph.length === 0 || spaceGlyph[0].width === 0)
spaceGlyph = font.charsToGlyphs('i');
// Fallback
if (spaceGlyph.length === 0 || spaceGlyph[0].width === 0)
spaceGlyph = [{width: 0}];
geometry.spaceWidth = spaceGlyph[0].width;
return geometry;
},
pushTextDivs: function canvasGraphicsPushTextDivs(text) {
var div = document.createElement('div');
var fontSize = this.current.fontSize;
// vScale and hScale already contain the scaling to pixel units
// as mozCurrentTransform reflects ctx.scale() changes
// (see beginDrawing())
var fontHeight = fontSize * text.geom.vScale;
div.dataset.canvasWidth = text.canvasWidth * text.geom.hScale;
div.style.fontSize = fontHeight + 'px';
div.style.fontFamily = this.current.font.loadedName || 'sans-serif';
div.style.left = text.geom.x + 'px';
div.style.top = (text.geom.y - fontHeight) + 'px';
div.innerHTML = text.str;
div.dataset.textLength = text.length;
this.textDivs.push(div);
},
showText: function canvasGraphicsShowText(str, skipTextSelection) {
var ctx = this.ctx; var ctx = this.ctx;
var current = this.current; var current = this.current;
var font = current.font; var font = current.font;
var glyphs = font.charsToGlyphs(text); var glyphs = font.charsToGlyphs(str);
var fontSize = current.fontSize; var fontSize = current.fontSize;
var charSpacing = current.charSpacing; var charSpacing = current.charSpacing;
var wordSpacing = current.wordSpacing; var wordSpacing = current.wordSpacing;
var textHScale = current.textHScale; var textHScale = current.textHScale;
var fontMatrix = font.fontMatrix || IDENTITY_MATRIX;
var textHScale2 = textHScale * fontMatrix[0];
var glyphsLength = glyphs.length; var glyphsLength = glyphs.length;
var textLayer = this.textLayer;
var text = {str: '', length: 0, canvasWidth: 0, geom: {}};
var textSelection = textLayer && !skipTextSelection ? true : false;
if (textSelection) {
ctx.save();
this.applyTextTransforms();
text.geom = this.getTextGeometry();
ctx.restore();
}
// Type3 fonts - each glyph is a "mini-PDF"
if (font.coded) { if (font.coded) {
ctx.save(); ctx.save();
ctx.transform.apply(ctx, current.textMatrix); ctx.transform.apply(ctx, current.textMatrix);
ctx.translate(current.x, current.y); ctx.translate(current.x, current.y);
var fontMatrix = font.fontMatrix || IDENTITY_MATRIX; ctx.scale(textHScale, 1);
ctx.scale(1 / textHScale, 1);
for (var i = 0; i < glyphsLength; ++i) { for (var i = 0; i < glyphsLength; ++i) {
var glyph = glyphs[i]; var glyph = glyphs[i];
@ -564,18 +671,16 @@ var CanvasGraphics = (function canvasGraphics() {
var width = transformed[0] * fontSize + charSpacing; var width = transformed[0] * fontSize + charSpacing;
ctx.translate(width, 0); ctx.translate(width, 0);
current.x += width; current.x += width * textHScale2;
text.str += glyph.unicode;
text.length++;
text.canvasWidth += width;
} }
ctx.restore(); ctx.restore();
} else { } else {
ctx.save(); ctx.save();
ctx.transform.apply(ctx, current.textMatrix); this.applyTextTransforms();
ctx.scale(1, -1);
ctx.translate(current.x, -1 * current.y);
ctx.transform.apply(ctx, font.fontMatrix || IDENTITY_MATRIX);
ctx.scale(1 / textHScale, 1);
var width = 0; var width = 0;
for (var i = 0; i < glyphsLength; ++i) { for (var i = 0; i < glyphsLength; ++i) {
@ -586,36 +691,78 @@ var CanvasGraphics = (function canvasGraphics() {
continue; continue;
} }
var unicode = glyph.unicode; var char = glyph.fontChar;
var char = (unicode >= 0x10000) ? var charWidth = glyph.width * fontSize * 0.001 + charSpacing;
String.fromCharCode(0xD800 | ((unicode - 0x10000) >> 10),
0xDC00 | (unicode & 0x3FF)) : String.fromCharCode(unicode);
ctx.fillText(char, width, 0); ctx.fillText(char, width, 0);
width += glyph.width * fontSize * 0.001 + charSpacing; width += charWidth;
}
current.x += width;
text.str += glyph.unicode === ' ' ? '&nbsp;' : glyph.unicode;
text.length++;
text.canvasWidth += charWidth;
}
current.x += width * textHScale2;
ctx.restore(); ctx.restore();
} }
},
if (textSelection)
this.pushTextDivs(text);
return text;
},
showSpacedText: function canvasGraphicsShowSpacedText(arr) { showSpacedText: function canvasGraphicsShowSpacedText(arr) {
var ctx = this.ctx; var ctx = this.ctx;
var current = this.current; var current = this.current;
var fontSize = current.fontSize; var fontSize = current.fontSize;
var textHScale = current.textHScale; var textHScale2 = current.textHScale *
(current.font.fontMatrix || IDENTITY_MATRIX)[0];
var arrLength = arr.length; var arrLength = arr.length;
var textLayer = this.textLayer;
var font = current.font;
var text = {str: '', length: 0, canvasWidth: 0, geom: {}};
var textSelection = textLayer ? true : false;
if (textSelection) {
ctx.save();
this.applyTextTransforms();
text.geom = this.getTextGeometry();
ctx.restore();
}
for (var i = 0; i < arrLength; ++i) { for (var i = 0; i < arrLength; ++i) {
var e = arr[i]; var e = arr[i];
if (isNum(e)) { if (isNum(e)) {
current.x -= e * 0.001 * fontSize * textHScale; var spacingLength = -e * 0.001 * fontSize * textHScale2;
current.x += spacingLength;
if (textSelection) {
// Emulate precise spacing via HTML spaces
text.canvasWidth += spacingLength;
if (e < 0 && text.geom.spaceWidth > 0) { // avoid div by zero
var numFakeSpaces = Math.round(-e / text.geom.spaceWidth);
for (var j = 0; j < numFakeSpaces; ++j)
text.str += '&nbsp;';
text.length += numFakeSpaces > 0 ? 1 : 0;
}
}
} else if (isString(e)) { } else if (isString(e)) {
this.showText(e); var shownText = this.showText(e, true);
if (textSelection) {
if (shownText.str === ' ') {
text.str += '&nbsp;';
} else {
text.str += shownText.str;
}
text.canvasWidth += shownText.canvasWidth;
text.length += e.length;
}
} else { } else {
malformed('TJ array element ' + e + ' is not string or num'); malformed('TJ array element ' + e + ' is not string or num');
} }
} }
if (textSelection)
this.pushTextDivs(text);
}, },
nextLineShowText: function canvasGraphicsNextLineShowText(text) { nextLineShowText: function canvasGraphicsNextLineShowText(text) {
this.nextLine(); this.nextLine();

31
src/core.js

@ -70,7 +70,6 @@ var Page = (function pagePage() {
this.ctx = null; this.ctx = null;
this.callback = null; this.callback = null;
this.errorback = null;
} }
constructor.prototype = { constructor.prototype = {
@ -164,7 +163,7 @@ var Page = (function pagePage() {
IRQueue, fonts) { IRQueue, fonts) {
var self = this; var self = this;
this.IRQueue = IRQueue; this.IRQueue = IRQueue;
var gfx = new CanvasGraphics(this.ctx, this.objs); var gfx = new CanvasGraphics(this.ctx, this.objs, this.textLayer);
var displayContinuation = function pageDisplayContinuation() { var displayContinuation = function pageDisplayContinuation() {
// Always defer call to display() to work around bug in // Always defer call to display() to work around bug in
@ -173,8 +172,8 @@ var Page = (function pagePage() {
try { try {
self.display(gfx, self.callback); self.display(gfx, self.callback);
} catch (e) { } catch (e) {
if (self.errorback) if (self.callback)
self.errorback(e); self.callback(e);
else else
throw e; throw e;
} }
@ -251,6 +250,7 @@ var Page = (function pagePage() {
startIdx = gfx.executeIRQueue(IRQueue, startIdx, next); startIdx = gfx.executeIRQueue(IRQueue, startIdx, next);
if (startIdx == length) { if (startIdx == length) {
self.stats.render = Date.now(); self.stats.render = Date.now();
gfx.endDrawing();
if (callback) callback(); if (callback) callback();
} }
} }
@ -313,10 +313,10 @@ var Page = (function pagePage() {
} }
return links; return links;
}, },
startRendering: function pageStartRendering(ctx, callback, errorback) { startRendering: function pageStartRendering(ctx, callback, textLayer) {
this.ctx = ctx; this.ctx = ctx;
this.callback = callback; this.callback = callback;
this.errorback = errorback; this.textLayer = textLayer;
this.startRenderingTime = Date.now(); this.startRenderingTime = Date.now();
this.pdf.startRendering(this); this.pdf.startRendering(this);
@ -569,20 +569,9 @@ var PDFDoc = (function pdfDoc() {
var properties = data[4]; var properties = data[4];
if (file) { if (file) {
// Rewrap the ArrayBuffer in a stream.
var fontFileDict = new Dict(); var fontFileDict = new Dict();
fontFileDict.map = file.dict.map; file = new Stream(file, 0, file.length, fontFileDict);
var fontFile = new Stream(file.bytes, file.start,
file.end - file.start, fontFileDict);
// Check if this is a FlateStream. Otherwise just use the created
// Stream one. This makes complex_ttf_font.pdf work.
var cmf = file.bytes[0];
if ((cmf & 0x0f) == 0x08) {
file = new FlateStream(fontFile);
} else {
file = fontFile;
}
} }
// For now, resolve the font object here direclty. The real font // For now, resolve the font object here direclty. The real font
@ -612,8 +601,8 @@ var PDFDoc = (function pdfDoc() {
messageHandler.on('page_error', function pdfDocError(data) { messageHandler.on('page_error', function pdfDocError(data) {
var page = this.pageCache[data.pageNum]; var page = this.pageCache[data.pageNum];
if (page.errorback) if (page.callback)
page.errorback(data.error); page.callback(data.error);
else else
throw data.error; throw data.error;
}, this); }, this);

41
src/evaluator.js

@ -155,6 +155,11 @@ var PartialEvaluator = (function partialEvaluator() {
font.loadedName = loadedName; font.loadedName = loadedName;
var translated = font.translated; var translated = font.translated;
// Convert the file to an ArrayBuffer which will be turned back into
// a Stream in the main thread.
if (translated.file)
translated.file = translated.file.getBytes();
handler.send('obj', [ handler.send('obj', [
loadedName, loadedName,
'Font', 'Font',
@ -493,6 +498,8 @@ var PartialEvaluator = (function partialEvaluator() {
var baseName = encoding.get('BaseEncoding'); var baseName = encoding.get('BaseEncoding');
if (baseName) if (baseName)
baseEncoding = Encodings[baseName.name]; baseEncoding = Encodings[baseName.name];
else
hasEncoding = false; // base encoding was not provided
// Load the differences between the base and original // Load the differences between the base and original
if (encoding.has('Differences')) { if (encoding.has('Differences')) {
@ -512,6 +519,7 @@ var PartialEvaluator = (function partialEvaluator() {
error('Encoding is not a Name nor a Dict'); error('Encoding is not a Name nor a Dict');
} }
} }
properties.differences = differences; properties.differences = differences;
properties.baseEncoding = baseEncoding; properties.baseEncoding = baseEncoding;
properties.hasEncoding = hasEncoding; properties.hasEncoding = hasEncoding;
@ -554,9 +562,21 @@ var PartialEvaluator = (function partialEvaluator() {
var startRange = tokens[j]; var startRange = tokens[j];
var endRange = tokens[j + 1]; var endRange = tokens[j + 1];
var code = tokens[j + 2]; var code = tokens[j + 2];
while (startRange <= endRange) { if (code == 0xFFFF) {
charToUnicode[startRange] = code++; // CMap is broken, assuming code == startRange
++startRange; code = startRange;
}
if (isArray(code)) {
var codeindex = 0;
while (startRange <= endRange) {
charToUnicode[startRange] = code[codeindex++];
++startRange;
}
} else {
while (startRange <= endRange) {
charToUnicode[startRange] = code++;
++startRange;
}
} }
} }
break; break;
@ -595,9 +615,18 @@ var PartialEvaluator = (function partialEvaluator() {
} }
} else if (byte == 0x3E) { } else if (byte == 0x3E) {
if (token.length) { if (token.length) {
// parsing hex number if (token.length <= 4) {
tokens.push(parseInt(token, 16)); // parsing hex number
token = ''; tokens.push(parseInt(token, 16));
token = '';
} else {
// parsing hex UTF-16BE numbers
var str = [];
for (var i = 0, ii = token.length; i < ii; i += 4)
str.push(parseInt(token.substr(i, 4), 16));
tokens.push(String.fromCharCode.apply(String, str));
token = '';
}
} }
} else { } else {
token += String.fromCharCode(byte); token += String.fromCharCode(byte);

325
src/fonts.js

@ -719,20 +719,10 @@ function getUnicodeRangeFor(value) {
return -1; return -1;
} }
function adaptUnicode(unicode) {
return (unicode <= 0x1F || (unicode >= 127 && unicode < kSizeOfGlyphArea)) ?
unicode + kCmapGlyphOffset : unicode;
}
function isAdaptedUnicode(unicode) {
return unicode >= kCmapGlyphOffset &&
unicode < kCmapGlyphOffset + kSizeOfGlyphArea;
}
function isSpecialUnicode(unicode) { function isSpecialUnicode(unicode) {
return (unicode <= 0x1F || (unicode >= 127 && unicode < kSizeOfGlyphArea)) || return (unicode <= 0x1F || (unicode >= 127 && unicode < kSizeOfGlyphArea)) ||
unicode >= kCmapGlyphOffset && (unicode >= kCmapGlyphOffset &&
unicode < kCmapGlyphOffset + kSizeOfGlyphArea; unicode < kCmapGlyphOffset + kSizeOfGlyphArea);
} }
/** /**
@ -771,16 +761,21 @@ var Font = (function Font() {
this.widths = properties.widths; this.widths = properties.widths;
this.defaultWidth = properties.defaultWidth; this.defaultWidth = properties.defaultWidth;
this.composite = properties.composite; this.composite = properties.composite;
this.toUnicode = properties.toUnicode;
this.hasEncoding = properties.hasEncoding; this.hasEncoding = properties.hasEncoding;
this.fontMatrix = properties.fontMatrix; this.fontMatrix = properties.fontMatrix;
this.widthMultiplier = 1.0;
if (properties.type == 'Type3') if (properties.type == 'Type3')
return; return;
// Trying to fix encoding using glyph CIDSystemInfo. // Trying to fix encoding using glyph CIDSystemInfo.
this.loadCidToUnicode(properties); this.loadCidToUnicode(properties);
if (properties.toUnicode)
this.toUnicode = properties.toUnicode;
else
this.rebuildToUnicode(properties);
if (!file) { if (!file) {
// The file data is not specified. Trying to fix the font name // The file data is not specified. Trying to fix the font name
// to be used with the canvas.font. // to be used with the canvas.font.
@ -832,6 +827,8 @@ var Font = (function Font() {
this.data = data; this.data = data;
this.fontMatrix = properties.fontMatrix; this.fontMatrix = properties.fontMatrix;
this.widthMultiplier = !properties.fontMatrix ? 1.0 :
1.0 / properties.fontMatrix[0];
this.encoding = properties.baseEncoding; this.encoding = properties.baseEncoding;
this.hasShortCmap = properties.hasShortCmap; this.hasShortCmap = properties.hasShortCmap;
this.loadedName = getUniqueName(); this.loadedName = getUniqueName();
@ -961,15 +958,15 @@ var Font = (function Font() {
var ranges = []; var ranges = [];
for (var n = 0; n < length; ) { for (var n = 0; n < length; ) {
var start = codes[n].unicode; var start = codes[n].unicode;
var startCode = codes[n].code; var codeIndices = [codes[n].code];
++n; ++n;
var end = start; var end = start;
while (n < length && end + 1 == codes[n].unicode) { while (n < length && end + 1 == codes[n].unicode) {
codeIndices.push(codes[n].code);
++end; ++end;
++n; ++n;
} }
var endCode = codes[n - 1].code; ranges.push([start, end, codeIndices]);
ranges.push([start, end, startCode, endCode]);
} }
return ranges; return ranges;
@ -1012,17 +1009,16 @@ var Font = (function Font() {
idDeltas += string16(0); idDeltas += string16(0);
idRangeOffsets += string16(offset); idRangeOffsets += string16(offset);
var startCode = range[2]; var codes = range[2];
var endCode = range[3]; for (var j = 0, jj = codes.length; j < jj; ++j)
for (var j = startCode; j <= endCode; ++j) glyphsIds += string16(deltas[codes[j]]);
glyphsIds += string16(deltas[j]);
} }
} else { } else {
for (var i = 0; i < segCount - 1; i++) { for (var i = 0; i < segCount - 1; i++) {
var range = ranges[i]; var range = ranges[i];
var start = range[0]; var start = range[0];
var end = range[1]; var end = range[1];
var startCode = range[2]; var startCode = range[2][0];
startCount += string16(start); startCount += string16(start);
endCount += string16(end); endCount += string16(end);
@ -1299,7 +1295,7 @@ var Font = (function Font() {
properties.baseEncoding = encoding; properties.baseEncoding = encoding;
} }
function replaceCMapTable(cmap, font, properties) { function readCMapTable(cmap, font) {
var start = (font.start ? font.start : 0) + cmap.offset; var start = (font.start ? font.start : 0) + cmap.offset;
font.pos = start; font.pos = start;
@ -1316,7 +1312,7 @@ var Font = (function Font() {
} }
// Check that table are sorted by platformID then encodingID, // Check that table are sorted by platformID then encodingID,
records.sort(function fontReplaceCMapTableSort(a, b) { records.sort(function fontReadCMapTableSort(a, b) {
return ((a.platformID << 16) + a.encodingID) - return ((a.platformID << 16) + a.encodingID) -
((b.platformID << 16) + b.encodingID); ((b.platformID << 16) + b.encodingID);
}); });
@ -1371,16 +1367,15 @@ var Font = (function Font() {
for (var j = 0; j < 256; j++) { for (var j = 0; j < 256; j++) {
var index = font.getByte(); var index = font.getByte();
if (index) { if (index) {
var unicode = adaptUnicode(j); glyphs.push({ unicode: j, code: j });
glyphs.push({ unicode: unicode, code: j });
ids.push(index); ids.push(index);
} }
} }
return {
properties.hasShortCmap = true; glyphs: glyphs,
ids: ids,
createGlyphNameMap(glyphs, ids, properties); hasShortCmap: true
return cmap.data = createCMapTable(glyphs, ids); };
} else if (format == 4) { } else if (format == 4) {
// re-creating the table in format 4 since the encoding // re-creating the table in format 4 since the encoding
// might be changed // might be changed
@ -1432,17 +1427,18 @@ var Font = (function Font() {
var glyphCode = offsetIndex < 0 ? j : var glyphCode = offsetIndex < 0 ? j :
offsets[offsetIndex + j - start]; offsets[offsetIndex + j - start];
glyphCode = (glyphCode + delta) & 0xFFFF; glyphCode = (glyphCode + delta) & 0xFFFF;
if (glyphCode == 0 || isAdaptedUnicode(j)) if (glyphCode == 0)
continue; continue;
var unicode = adaptUnicode(j); glyphs.push({ unicode: j, code: j });
glyphs.push({ unicode: unicode, code: j });
ids.push(glyphCode); ids.push(glyphCode);
} }
} }
createGlyphNameMap(glyphs, ids, properties); return {
return cmap.data = createCMapTable(glyphs, ids); glyphs: glyphs,
ids: ids
};
} else if (format == 6) { } else if (format == 6) {
// Format 6 is a 2-bytes dense mapping, which means the font data // Format 6 is a 2-bytes dense mapping, which means the font data
// lives glue together even if they are pretty far in the unicode // lives glue together even if they are pretty far in the unicode
@ -1457,19 +1453,18 @@ var Font = (function Font() {
for (var j = 0; j < entryCount; j++) { for (var j = 0; j < entryCount; j++) {
var glyphCode = int16(font.getBytes(2)); var glyphCode = int16(font.getBytes(2));
var code = firstCode + j; var code = firstCode + j;
if (isAdaptedUnicode(glyphCode))
continue;
var unicode = adaptUnicode(code); glyphs.push({ unicode: code, code: code });
glyphs.push({ unicode: unicode, code: code });
ids.push(glyphCode); ids.push(glyphCode);
} }
createGlyphNameMap(glyphs, ids, properties); return {
return cmap.data = createCMapTable(glyphs, ids); glyphs: glyphs,
ids: ids
};
} }
} }
return cmap.data; error('Unsupported cmap table format');
}; };
function sanitizeMetrics(font, header, metrics, numGlyphs) { function sanitizeMetrics(font, header, metrics, numGlyphs) {
@ -1708,17 +1703,85 @@ var Font = (function Font() {
tables.push(cmap); tables.push(cmap);
} }
var glyphs = []; var cidToGidMap = properties.cidToGidMap || [];
for (i = 1; i < numGlyphs; i++) { var gidToCidMap = [0];
if (isAdaptedUnicode(i)) if (cidToGidMap.length > 0) {
continue; for (var j = cidToGidMap.length - 1; j >= 0; j--) {
var gid = cidToGidMap[j];
if (gid)
gidToCidMap[gid] = j;
}
// filling the gaps using CID above the CIDs currently used in font
var nextCid = cidToGidMap.length;
for (var i = 1; i < numGlyphs; i++) {
if (!gidToCidMap[i])
gidToCidMap[i] = nextCid++;
}
}
glyphs.push({ unicode: adaptUnicode(i) }); var glyphs = [], ids = [];
var usedUnicodes = [];
var unassignedUnicodeItems = [];
for (var i = 1; i < numGlyphs; i++) {
var cid = gidToCidMap[i] || i;
var unicode = this.toUnicode[cid];
if (!unicode || isSpecialUnicode(unicode) ||
unicode in usedUnicodes) {
unassignedUnicodeItems.push(i);
continue;
}
usedUnicodes[unicode] = true;
glyphs.push({ unicode: unicode, code: cid });
ids.push(i);
}
// trying to fit as many unassigned symbols as we can
// in the range allocated for the user defined symbols
var unusedUnicode = kCmapGlyphOffset;
for (var j = 0, jj = unassignedUnicodeItems.length; j < jj; j++) {
var i = unassignedUnicodeItems[j];
var cid = gidToCidMap[i] || i;
while (unusedUnicode in usedUnicodes)
unusedUnicode++;
if (unusedUnicode >= kCmapGlyphOffset + kSizeOfGlyphArea)
break;
var unicode = unusedUnicode++;
this.toUnicode[cid] = unicode;
usedUnicodes[unicode] = true;
glyphs.push({ unicode: unicode, code: cid });
ids.push(i);
} }
cmap.data = createCMapTable(glyphs); cmap.data = createCMapTable(glyphs, ids);
} else { } else {
replaceCMapTable(cmap, font, properties); var cmapTable = readCMapTable(cmap, font);
var glyphs = cmapTable.glyphs;
var ids = cmapTable.ids;
var hasShortCmap = !!cmapTable.hasShortCmap;
var toUnicode = this.toUnicode;
if (hasShortCmap && toUnicode) {
// checking if cmap is just identity map
var isIdentity = true;
for (var i = 0, ii = glyphs.length; i < ii; i++) {
if (glyphs[i].unicode != i + 1) {
isIdentity = false;
break;
}
}
// if it is, replacing with meaningful toUnicode values
if (isIdentity) {
for (var i = 0, ii = glyphs.length; i < ii; i++) {
var unicode = toUnicode[i + 1] || i + 1;
glyphs[i].unicode = unicode;
}
this.useToUnicode = true;
}
}
properties.hasShortCmap = hasShortCmap;
createGlyphNameMap(glyphs, ids, properties);
this.glyphNameMap = properties.glyphNameMap; this.glyphNameMap = properties.glyphNameMap;
cmap.data = createCMapTable(glyphs, ids);
} }
// Rewrite the 'post' table if needed // Rewrite the 'post' table if needed
@ -1808,6 +1871,14 @@ var Font = (function Font() {
} }
properties.baseEncoding = encoding; properties.baseEncoding = encoding;
} }
if (properties.subtype == 'CIDFontType0C') {
var toUnicode = [];
for (var i = 0; i < charstrings.length; ++i) {
var charstring = charstrings[i];
toUnicode[charstring.code] = charstring.unicode;
}
this.toUnicode = toUnicode;
}
var fields = { var fields = {
// PostScript Font Program // PostScript Font Program
@ -1868,8 +1939,11 @@ var Font = (function Font() {
// Horizontal metrics // Horizontal metrics
'hmtx': (function fontFieldsHmtx() { 'hmtx': (function fontFieldsHmtx() {
var hmtx = '\x00\x00\x00\x00'; // Fake .notdef var hmtx = '\x00\x00\x00\x00'; // Fake .notdef
for (var i = 0, ii = charstrings.length; i < ii; i++) for (var i = 0, ii = charstrings.length; i < ii; i++) {
hmtx += string16(charstrings[i].width) + string16(0); var charstring = charstrings[i];
var width = 'width' in charstring ? charstring.width : 0;
hmtx += string16(width) + string16(0);
}
return stringToArray(hmtx); return stringToArray(hmtx);
})(), })(),
@ -1898,17 +1972,35 @@ var Font = (function Font() {
return stringToArray(otf.file); return stringToArray(otf.file);
}, },
loadCidToUnicode: function font_loadCidToUnicode(properties) { rebuildToUnicode: function font_rebuildToUnicode(properties) {
if (properties.cidToGidMap) { var firstChar = properties.firstChar, lastChar = properties.lastChar;
this.cidToUnicode = properties.cidToGidMap; var map = [];
return; if (properties.composite) {
var isIdentityMap = this.cidToUnicode.length == 0;
for (var i = firstChar, ii = lastChar; i <= ii; i++) {
// TODO missing map the character according font's CMap
var cid = i;
map[i] = isIdentityMap ? cid : this.cidToUnicode[cid];
}
} else {
for (var i = firstChar, ii = lastChar; i <= ii; i++) {
var glyph = properties.differences[i];
if (!glyph)
glyph = properties.baseEncoding[i];
if (!!glyph && (glyph in GlyphsUnicode))
map[i] = GlyphsUnicode[glyph];
}
} }
this.toUnicode = map;
},
loadCidToUnicode: function font_loadCidToUnicode(properties) {
if (!properties.cidSystemInfo) if (!properties.cidSystemInfo)
return; return;
var cidToUnicodeMap = []; var cidToUnicodeMap = [], unicodeToCIDMap = [];
this.cidToUnicode = cidToUnicodeMap; this.cidToUnicode = cidToUnicodeMap;
this.unicodeToCID = unicodeToCIDMap;
var cidSystemInfo = properties.cidSystemInfo; var cidSystemInfo = properties.cidSystemInfo;
var cidToUnicode; var cidToUnicode;
@ -1920,28 +2012,34 @@ var Font = (function Font() {
if (!cidToUnicode) if (!cidToUnicode)
return; // identity encoding return; // identity encoding
var glyph = 1, i, j, k, ii; var cid = 1, i, j, k, ii;
for (i = 0, ii = cidToUnicode.length; i < ii; ++i) { for (i = 0, ii = cidToUnicode.length; i < ii; ++i) {
var unicode = cidToUnicode[i]; var unicode = cidToUnicode[i];
if (isArray(unicode)) { if (isArray(unicode)) {
var length = unicode.length; var length = unicode.length;
for (j = 0; j < length; j++) for (j = 0; j < length; j++) {
cidToUnicodeMap[unicode[j]] = glyph; cidToUnicodeMap[cid] = unicode[j];
glyph++; unicodeToCIDMap[unicode[j]] = cid;
}
cid++;
} else if (typeof unicode === 'object') { } else if (typeof unicode === 'object') {
var fillLength = unicode.f; var fillLength = unicode.f;
if (fillLength) { if (fillLength) {
k = unicode.c; k = unicode.c;
for (j = 0; j < fillLength; ++j) { for (j = 0; j < fillLength; ++j) {
cidToUnicodeMap[k] = glyph++; cidToUnicodeMap[cid] = k;
unicodeToCIDMap[k] = cid;
cid++;
k++; k++;
} }
} else } else
glyph += unicode.s; cid += unicode.s;
} else if (unicode) { } else if (unicode) {
cidToUnicodeMap[unicode] = glyph++; cidToUnicodeMap[cid] = unicode;
unicodeToCIDMap[unicode] = cid;
cid++;
} else } else
glyph++; cid++;
} }
}, },
@ -1981,19 +2079,19 @@ var Font = (function Font() {
switch (this.type) { switch (this.type) {
case 'CIDFontType0': case 'CIDFontType0':
if (this.noUnicodeAdaptation) { if (this.noUnicodeAdaptation) {
width = this.widths[this.cidToUnicode[charcode]]; width = this.widths[this.unicodeToCID[charcode] || charcode];
unicode = charcode; unicode = charcode;
break; break;
} }
unicode = adaptUnicode(this.cidToUnicode[charcode] || charcode); unicode = this.toUnicode[charcode] || charcode;
break; break;
case 'CIDFontType2': case 'CIDFontType2':
if (this.noUnicodeAdaptation) { if (this.noUnicodeAdaptation) {
width = this.widths[this.cidToUnicode[charcode]]; width = this.widths[this.unicodeToCID[charcode] || charcode];
unicode = charcode; unicode = charcode;
break; break;
} }
unicode = adaptUnicode(this.cidToUnicode[charcode] || charcode); unicode = this.toUnicode[charcode] || charcode;
break; break;
case 'Type1': case 'Type1':
var glyphName = this.differences[charcode] || this.encoding[charcode]; var glyphName = this.differences[charcode] || this.encoding[charcode];
@ -2004,7 +2102,7 @@ var Font = (function Font() {
break; break;
} }
unicode = this.glyphNameMap[glyphName] || unicode = this.glyphNameMap[glyphName] ||
adaptUnicode(GlyphsUnicode[glyphName] || charcode); GlyphsUnicode[glyphName] || charcode;
break; break;
case 'Type3': case 'Type3':
var glyphName = this.differences[charcode] || this.encoding[charcode]; var glyphName = this.differences[charcode] || this.encoding[charcode];
@ -2022,16 +2120,16 @@ var Font = (function Font() {
break; break;
} }
if (!this.hasEncoding) { if (!this.hasEncoding) {
unicode = adaptUnicode(charcode); unicode = this.useToUnicode ? this.toUnicode[charcode] : charcode;
break; break;
} }
if (this.hasShortCmap) { if (this.hasShortCmap && false) {
var j = Encodings.MacRomanEncoding.indexOf(glyphName); var j = Encodings.MacRomanEncoding.indexOf(glyphName);
unicode = j >= 0 && !isSpecialUnicode(j) ? j : unicode = j >= 0 ? j :
this.glyphNameMap[glyphName]; this.glyphNameMap[glyphName];
} else { } else {
unicode = glyphName in GlyphsUnicode ? unicode = glyphName in GlyphsUnicode ?
adaptUnicode(GlyphsUnicode[glyphName]) : GlyphsUnicode[glyphName] :
this.glyphNameMap[glyphName]; this.glyphNameMap[glyphName];
} }
break; break;
@ -2039,9 +2137,17 @@ var Font = (function Font() {
warn('Unsupported font type: ' + this.type); warn('Unsupported font type: ' + this.type);
break; break;
} }
var unicodeChars = this.toUnicode ? this.toUnicode[charcode] : charcode;
if (typeof unicodeChars === 'number')
unicodeChars = String.fromCharCode(unicodeChars);
width = (isNum(width) ? width : this.defaultWidth) * this.widthMultiplier;
return { return {
unicode: unicode, fontChar: String.fromCharCode(unicode),
width: isNum(width) ? width : this.defaultWidth, unicode: unicodeChars,
width: width,
codeIRQueue: codeIRQueue codeIRQueue: codeIRQueue
}; };
}, },
@ -2753,22 +2859,13 @@ CFF.prototype = {
getOrderedCharStrings: function cff_getOrderedCharStrings(glyphs, getOrderedCharStrings: function cff_getOrderedCharStrings(glyphs,
properties) { properties) {
var charstrings = []; var charstrings = [];
var reverseMapping = {};
var encoding = properties.baseEncoding;
var i, length, glyphName; var i, length, glyphName;
for (i = 0, length = encoding.length; i < length; ++i) {
glyphName = encoding[i];
if (!glyphName || isSpecialUnicode(i))
continue;
reverseMapping[glyphName] = i;
}
reverseMapping['.notdef'] = 0;
var unusedUnicode = kCmapGlyphOffset; var unusedUnicode = kCmapGlyphOffset;
for (i = 0, length = glyphs.length; i < length; i++) { for (i = 0, length = glyphs.length; i < length; i++) {
var item = glyphs[i]; var item = glyphs[i];
var glyphName = item.glyph; var glyphName = item.glyph;
var unicode = glyphName in reverseMapping ? var unicode = glyphName in GlyphsUnicode ?
reverseMapping[glyphName] : unusedUnicode++; GlyphsUnicode[glyphName] : unusedUnicode++;
charstrings.push({ charstrings.push({
glyph: glyphName, glyph: glyphName,
unicode: unicode, unicode: unicode,
@ -3055,16 +3152,14 @@ var Type2CFF = (function type2CFF() {
} }
var charStrings = this.parseIndex(topDict.CharStrings); var charStrings = this.parseIndex(topDict.CharStrings);
var charset = this.parseCharsets(topDict.charset,
charStrings.length, strings);
var encoding = this.parseEncoding(topDict.Encoding, properties,
strings, charset);
var charset, encoding; var charset, encoding;
var isCIDFont = properties.subtype == 'CIDFontType0C'; var isCIDFont = properties.subtype == 'CIDFontType0C';
if (isCIDFont) { if (isCIDFont) {
charset = []; charset = ['.notdef'];
charset.length = charStrings.length; for (var i = 1, ii = charStrings.length; i < ii; ++i)
charset.push('glyph' + i);
encoding = this.parseCidMap(topDict.charset, encoding = this.parseCidMap(topDict.charset,
charStrings.length); charStrings.length);
} else { } else {
@ -3133,38 +3228,44 @@ var Type2CFF = (function type2CFF() {
var charstrings = []; var charstrings = [];
var unicodeUsed = []; var unicodeUsed = [];
var unassignedUnicodeItems = []; var unassignedUnicodeItems = [];
var inverseEncoding = [];
for (var charcode in encoding)
inverseEncoding[encoding[charcode]] = charcode | 0;
for (var i = 0, ii = charsets.length; i < ii; i++) { for (var i = 0, ii = charsets.length; i < ii; i++) {
var glyph = charsets[i]; var glyph = charsets[i];
var encodingFound = false; if (glyph == '.notdef') {
for (var charcode in encoding) { charstrings.push({
if (encoding[charcode] == i) { unicode: 0,
var code = charcode | 0; code: 0,
charstrings.push({ gid: i,
unicode: adaptUnicode(code), glyph: glyph
code: code, });
gid: i, continue;
glyph: glyph
});
unicodeUsed[code] = true;
encodingFound = true;
break;
}
} }
if (!encodingFound) { var code = inverseEncoding[i];
if (!code || isSpecialUnicode(code)) {
unassignedUnicodeItems.push(i); unassignedUnicodeItems.push(i);
continue;
} }
charstrings.push({
unicode: code,
code: code,
gid: i,
glyph: glyph
});
unicodeUsed[code] = true;
} }
var nextUnusedUnicode = 0x21; var nextUnusedUnicode = kCmapGlyphOffset;
for (var j = 0, jj = unassignedUnicodeItems.length; j < jj; ++j) { for (var j = 0, jj = unassignedUnicodeItems.length; j < jj; ++j) {
var i = unassignedUnicodeItems[j]; var i = unassignedUnicodeItems[j];
// giving unicode value anyway // giving unicode value anyway
while (unicodeUsed[nextUnusedUnicode]) while (nextUnusedUnicode in unicodeUsed)
nextUnusedUnicode++; nextUnusedUnicode++;
var code = nextUnusedUnicode++; var unicode = nextUnusedUnicode++;
charstrings.push({ charstrings.push({
unicode: adaptUnicode(code), unicode: unicode,
code: code, code: inverseEncoding[i] || 0,
gid: i, gid: i,
glyph: charsets[i] glyph: charsets[i]
}); });

3
src/glyphlist.js

@ -4287,6 +4287,7 @@ var GlyphsUnicode = {
zretroflexhook: 0x0290, zretroflexhook: 0x0290,
zstroke: 0x01B6, zstroke: 0x01B6,
zuhiragana: 0x305A, zuhiragana: 0x305A,
zukatakana: 0x30BA zukatakana: 0x30BA,
'.notdef': 0x0000
}; };

3
src/metrics.js

@ -3,6 +3,9 @@
'use strict'; 'use strict';
// The Metrics object contains glyph widths (in glyph space units).
// As per PDF spec, for most fonts (Type 3 being an exception) a glyph
// space unit corresponds to 1/1000th of text space unit.
var Metrics = { var Metrics = {
'Courier': 600, 'Courier': 600,
'Courier-Bold': 600, 'Courier-Bold': 600,

10
test/driver.js

@ -162,11 +162,11 @@ function nextPage(task, loadError) {
page.startRendering( page.startRendering(
ctx, ctx,
function nextPageStartRendering() { function nextPageStartRendering(error) {
snapshotCurrentPage(task, false); var failureMessage = false;
}, if (error)
function errorNextPageStartRendering(e) { failureMessage = 'render : ' + error.message;
snapshotCurrentPage(task, 'render : ' + e.message); snapshotCurrentPage(task, failureMessage);
} }
); );
} catch (e) { } catch (e) {

1
test/pdfs/.gitignore vendored

@ -16,3 +16,4 @@
!alphatrans.pdf !alphatrans.pdf
!devicen.pdf !devicen.pdf
!cmykjpeg.pdf !cmykjpeg.pdf
!issue840.pdf

BIN
test/pdfs/issue840.pdf

Binary file not shown.

1
test/pdfs/piperine.pdf.link

@ -0,0 +1 @@
http://www.erowid.org/archive/rhodium/chemistry/3base/piperonal.pepper/piperine.pepper/465e03piperine.pdf

1
test/pdfs/protectip.pdf.link

@ -0,0 +1 @@
http://leahy.senate.gov/imo/media/doc/BillText-PROTECTIPAct.pdf

20
test/test_manifest.json

@ -276,5 +276,25 @@
"link": false, "link": false,
"rounds": 1, "rounds": 1,
"type": "eq" "type": "eq"
},
{ "id": "protectip",
"file": "pdfs/protectip.pdf",
"md5": "676e7a7b8f96d04825361832b1838a93",
"link": true,
"rounds": 1,
"type": "eq"
},
{ "id": "piperine",
"file": "pdfs/piperine.pdf",
"md5": "603ca43dc5732dbba1579f122958c0c2",
"link": true,
"rounds": 1,
"type": "eq"
},
{ "id": "issue840",
"file": "pdfs/issue840.pdf",
"md5": "20d88011dd7e3c4fb5274979094dab93",
"rounds": 1,
"type": "eq"
} }
] ]

21
web/viewer.css

@ -232,6 +232,27 @@ canvas {
-webkit-box-shadow: 0px 2px 10px #ff0; -webkit-box-shadow: 0px 2px 10px #ff0;
} }
.textLayer {
position: absolute;
left: 0;
top: 0;
right: 0;
bottom: 0;
color: #000;
}
.textLayer > div {
color: transparent;
position: absolute;
line-height:1.3;
}
/* TODO: file FF bug to support ::-moz-selection:window-inactive
so we can override the opaque grey background when the window is inactive;
see https://bugzilla.mozilla.org/show_bug.cgi?id=706209 */
::selection { background:rgba(0,0,255,0.3); }
::-moz-selection { background:rgba(0,0,255,0.3); }
#viewer { #viewer {
margin: 44px 0px 0px; margin: 44px 0px 0px;
padding: 8px 0px; padding: 8px 0px;

19
web/viewer.js

@ -263,7 +263,7 @@ var PDFView = {
var container = document.getElementById('viewer'); var container = document.getElementById('viewer');
while (container.hasChildNodes()) while (container.hasChildNodes())
container.removeChild(container.lastChild); container.removeChild(container.lastChild);
var pdf; var pdf;
try { try {
pdf = new PDFJS.PDFDoc(data); pdf = new PDFJS.PDFDoc(data);
@ -291,10 +291,10 @@ var PDFView = {
pagesRefMap[pageRef.num + ' ' + pageRef.gen + ' R'] = i; pagesRefMap[pageRef.num + ' ' + pageRef.gen + ' R'] = i;
} }
this.setScale(scale || kDefaultScale, true);
this.pagesRefMap = pagesRefMap; this.pagesRefMap = pagesRefMap;
this.destinations = pdf.catalog.destinations; this.destinations = pdf.catalog.destinations;
this.setScale(scale || kDefaultScale, true);
if (pdf.catalog.documentOutline) { if (pdf.catalog.documentOutline) {
this.outline = new DocumentOutlineView(pdf.catalog.documentOutline); this.outline = new DocumentOutlineView(pdf.catalog.documentOutline);
var outlineSwitchButton = document.getElementById('outlineSwitch'); var outlineSwitchButton = document.getElementById('outlineSwitch');
@ -542,6 +542,10 @@ var PageView = function pageView(container, content, id, pageWidth, pageHeight,
div.appendChild(canvas); div.appendChild(canvas);
this.canvas = canvas; this.canvas = canvas;
var textLayer = document.createElement('div');
textLayer.className = 'textLayer';
div.appendChild(textLayer);
var scale = this.scale; var scale = this.scale;
canvas.width = pageWidth * scale; canvas.width = pageWidth * scale;
canvas.height = pageHeight * scale; canvas.height = pageHeight * scale;
@ -555,14 +559,13 @@ var PageView = function pageView(container, content, id, pageWidth, pageHeight,
stats.begin = Date.now(); stats.begin = Date.now();
this.content.startRendering(ctx, this.content.startRendering(ctx,
(function pageViewDrawCallback() { (function pageViewDrawCallback(error) {
if (error)
PDFView.error('An error occurred while rendering the page.', error);
this.updateStats(); this.updateStats();
if (this.onAfterDraw) if (this.onAfterDraw)
this.onAfterDraw(); this.onAfterDraw();
}).bind(this), }).bind(this), textLayer
function pageViewErrorback(e) {
PDFView.error('An error occurred while rendering the page.', e);
}
); );
setupLinks(this.content, this.scale); setupLinks(this.content, this.scale);

Loading…
Cancel
Save