Browse Source

Merge branch 'master' of https://github.com/mozilla/pdf.js into amowarnings

Brendan Dahl 13 years ago
parent
commit
b381c960d9
  1. 6
      Makefile
  2. 28
      README.md
  3. 1
      examples/acroforms/index.html
  4. 1
      examples/helloworld/index.html
  5. 23
      src/canvas.js
  6. 59
      src/colorspace.js
  7. 10
      src/core.js
  8. 9
      src/evaluator.js
  9. 82
      src/fonts.js
  10. 1854
      src/jpx.js
  11. 4
      src/obj.js
  12. 7
      src/parser.js
  13. 150
      src/stream.js
  14. 16
      src/util.js
  15. 9
      src/worker.js
  16. 3
      src/worker_loader.js
  17. 3
      test/.gitignore
  18. 2
      test/pdfs/.gitignore
  19. 2549
      test/pdfs/S2.pdf
  20. 1
      test/pdfs/issue1096.pdf.link
  21. 1
      test/pdfs/issue1127.pdf.link
  22. BIN
      test/pdfs/zerowidthline.pdf
  23. 4
      test/test.py
  24. 29
      test/test_manifest.json
  25. 1
      test/test_slave.html
  26. 7
      web/compatibility.js
  27. 1
      web/viewer.html
  28. 64
      web/viewer.js

6
Makefile

@ -3,6 +3,7 @@ BUILD_DIR := build
BUILD_TARGET := $(BUILD_DIR)/pdf.js BUILD_TARGET := $(BUILD_DIR)/pdf.js
DEFAULT_BROWSERS := resources/browser_manifests/browser_manifest.json DEFAULT_BROWSERS := resources/browser_manifests/browser_manifest.json
DEFAULT_TESTS := test_manifest.json DEFAULT_TESTS := test_manifest.json
DEFAULT_PYTHON := python2.7
EXTENSION_SRC := ./extensions/ EXTENSION_SRC := ./extensions/
EXTENSION_BASE_VERSION := 4bb289ec499013de66eb421737a4dbb4a9273eda EXTENSION_BASE_VERSION := 4bb289ec499013de66eb421737a4dbb4a9273eda
@ -36,6 +37,7 @@ PDF_JS_FILES = \
stream.js \ stream.js \
worker.js \ worker.js \
../external/jpgjs/jpg.js \ ../external/jpgjs/jpg.js \
jpx.js \
$(NULL) $(NULL)
# make server # make server
@ -43,7 +45,7 @@ PDF_JS_FILES = \
# This target starts a local web server at localhost:8888. This can be # This target starts a local web server at localhost:8888. This can be
# used for testing all browsers. # used for testing all browsers.
server: server:
@cd test; python test.py --port=8888; @cd test; $(DEFAULT_PYTHON) test.py --port=8888;
# make test # make test
# #
@ -106,7 +108,7 @@ browser-test:
fi; fi;
cd test; \ cd test; \
python test.py --reftest \ $(DEFAULT_PYTHON) test.py --reftest \
--browserManifestFile=$(PDF_BROWSERS) \ --browserManifestFile=$(PDF_BROWSERS) \
--manifestFile=$(PDF_TESTS) --manifestFile=$(PDF_TESTS)

28
README.md

@ -1,9 +1,6 @@
# pdf.js # PDF.JS
## Overview
pdf.js is an HTML5 technology experiment that explores building a faithful pdf.js is an HTML5 technology experiment that explores building a faithful
and efficient Portable Document Format (PDF) renderer without native code and efficient Portable Document Format (PDF) renderer without native code
assistance. assistance.
@ -16,7 +13,7 @@ successful.
## Getting started # Getting started
### Online demo ### Online demo
@ -29,11 +26,12 @@ using the pdf.js API.
### Extension ### Extension
An up-to-date Firefox extension is also available: A Firefox extension is also available:
+ http://mozilla.github.com/pdf.js/extensions/firefox/pdf.js.xpi + http://mozilla.github.com/pdf.js/extensions/firefox/pdf.js.xpi
(The above link is updated upon every merge to our master branch). Note that this extension is self-updating, and by default Firefox will auto-update extensions on a
daily basis (you can change this through the `extensions.update.interval` option in `about:config`).
For an experimental Chrome extension, get the code as explained below and issue `make extension`. For an experimental Chrome extension, get the code as explained below and issue `make extension`.
Then open Chrome with the flag `--enable-experimental-extension-apis`, go to `Tools > Extension` Then open Chrome with the flag `--enable-experimental-extension-apis`, go to `Tools > Extension`
@ -68,12 +66,12 @@ In order to bundle all `src/` files into a final `pdf.js`, issue:
This will generate the file `build/pdf.js` that can be included in your final project. (WARNING: That's a large file! Consider minifying it). This will generate the file `build/pdf.js` that can be included in your final project. (WARNING: That's a large file! Consider minifying it).
## Learning # Learning
Here are some initial pointers to help contributors get off the ground. Here are some initial pointers to help contributors get off the ground.
Additional resources are available in a separate section below. Additional resources are available in a separate section below.
#### Hello world ### Hello world
For a "hello world" example, take a look at: For a "hello world" example, take a look at:
@ -82,7 +80,7 @@ For a "hello world" example, take a look at:
This example illustrates the bare minimum ingredients for integrating pdf.js This example illustrates the bare minimum ingredients for integrating pdf.js
in a custom project. in a custom project.
#### Introductory video ### Introductory video
Check out the presentation by our contributor Julian Viereck on the inner Check out the presentation by our contributor Julian Viereck on the inner
workings of PDF and pdf.js: workings of PDF and pdf.js:
@ -92,7 +90,7 @@ workings of PDF and pdf.js:
## Contributing # Contributing
pdf.js is a community-driven project, so contributors are always welcome. pdf.js is a community-driven project, so contributors are always welcome.
Simply fork our repo and contribute away. Good starting places for picking Simply fork our repo and contribute away. Good starting places for picking
@ -122,7 +120,7 @@ You can add your name to it! :)
## Running the tests # Running the tests
pdf.js comes with browser-level regression tests that allow one to probe pdf.js comes with browser-level regression tests that allow one to probe
whether it's able to successfully parse PDFs, as well as compare its output whether it's able to successfully parse PDFs, as well as compare its output
@ -148,7 +146,7 @@ images. The test type `load` simply tests whether the file loads without
raising any errors. raising any errors.
## Running tests through our bot ### Running tests through our bot
If you are a reviewer, you can use our remote bot to issue comprehensive tests If you are a reviewer, you can use our remote bot to issue comprehensive tests
against reference images before merging pull requests. against reference images before merging pull requests.
@ -158,7 +156,7 @@ See the bot repo for details:
+ https://github.com/mozilla/pdf.js-bot + https://github.com/mozilla/pdf.js-bot
## Additional resources # Additional resources
Gallery of user projects and modifications: Gallery of user projects and modifications:
@ -188,7 +186,7 @@ Follow us on twitter: @pdfjs
## PDF-related resources ### PDF-related resources
A really basic overview of PDF is described here: A really basic overview of PDF is described here:

1
examples/acroforms/index.html

@ -23,6 +23,7 @@
<script type="text/javascript" src="../../src/stream.js"></script> <script type="text/javascript" src="../../src/stream.js"></script>
<script type="text/javascript" src="../../src/worker.js"></script> <script type="text/javascript" src="../../src/worker.js"></script>
<script type="text/javascript" src="../../external/jpgjs/jpg.js"></script> <script type="text/javascript" src="../../external/jpgjs/jpg.js"></script>
<script type="text/javascript" src="../../src/jpx.js"></script>
<script type="text/javascript"> <script type="text/javascript">
// Specify the main script used to create a new PDF.JS web worker. // Specify the main script used to create a new PDF.JS web worker.

1
examples/helloworld/index.html

@ -23,6 +23,7 @@
<script type="text/javascript" src="../../src/stream.js"></script> <script type="text/javascript" src="../../src/stream.js"></script>
<script type="text/javascript" src="../../src/worker.js"></script> <script type="text/javascript" src="../../src/worker.js"></script>
<script type="text/javascript" src="../../external/jpgjs/jpg.js"></script> <script type="text/javascript" src="../../external/jpgjs/jpg.js"></script>
<script type="text/javascript" src="../../src/jpx.js"></script>
<script type="text/javascript"> <script type="text/javascript">
// Specify the main script used to create a new PDF.JS web worker. // Specify the main script used to create a new PDF.JS web worker.

23
src/canvas.js

@ -48,6 +48,7 @@ var CanvasExtraState = (function CanvasExtraStateClosure() {
// Note: fill alpha applies to all non-stroking operations // Note: fill alpha applies to all non-stroking operations
this.fillAlpha = 1; this.fillAlpha = 1;
this.strokeAlpha = 1; this.strokeAlpha = 1;
this.lineWidth = 1;
this.old = old; this.old = old;
} }
@ -329,6 +330,7 @@ var CanvasGraphics = (function CanvasGraphicsClosure() {
// Graphics state // Graphics state
setLineWidth: function canvasGraphicsSetLineWidth(width) { setLineWidth: function canvasGraphicsSetLineWidth(width) {
this.current.lineWidth = width;
this.ctx.lineWidth = width; this.ctx.lineWidth = width;
}, },
setLineCap: function canvasGraphicsSetLineCap(style) { setLineCap: function canvasGraphicsSetLineCap(style) {
@ -442,6 +444,8 @@ var CanvasGraphics = (function CanvasGraphicsClosure() {
consumePath = typeof consumePath !== 'undefined' ? consumePath : true; consumePath = typeof consumePath !== 'undefined' ? consumePath : true;
var ctx = this.ctx; var ctx = this.ctx;
var strokeColor = this.current.strokeColor; var strokeColor = this.current.strokeColor;
if (this.current.lineWidth === 0)
ctx.lineWidth = this.getSinglePixelWidth();
// For stroke we want to temporarily change the global alpha to the // For stroke we want to temporarily change the global alpha to the
// stroking alpha. // stroking alpha.
ctx.globalAlpha = this.current.strokeAlpha; ctx.globalAlpha = this.current.strokeAlpha;
@ -544,7 +548,7 @@ var CanvasGraphics = (function CanvasGraphicsClosure() {
var fontObj = this.objs.get(fontRefName).fontObj; var fontObj = this.objs.get(fontRefName).fontObj;
if (!fontObj) { if (!fontObj) {
throw 'Can\'t find font for ' + fontRefName; error('Can\'t find font for ' + fontRefName);
} }
var name = fontObj.loadedName || 'sans-serif'; var name = fontObj.loadedName || 'sans-serif';
@ -640,7 +644,6 @@ var CanvasGraphics = (function CanvasGraphicsClosure() {
ctx.translate(current.x, current.y); ctx.translate(current.x, current.y);
ctx.scale(textHScale, 1); ctx.scale(textHScale, 1);
ctx.lineWidth /= current.textMatrix[0];
if (textSelection) { if (textSelection) {
this.save(); this.save();
@ -677,7 +680,15 @@ var CanvasGraphics = (function CanvasGraphicsClosure() {
} else { } else {
ctx.save(); ctx.save();
this.applyTextTransforms(); this.applyTextTransforms();
ctx.lineWidth /= current.textMatrix[0] * fontMatrix[0];
var lineWidth = current.lineWidth;
var scale = Math.abs(current.textMatrix[0] * fontMatrix[0]);
if (scale == 0 || lineWidth == 0)
lineWidth = this.getSinglePixelWidth();
else
lineWidth /= scale;
ctx.lineWidth = lineWidth;
if (textSelection) if (textSelection)
text.geom = this.getTextGeometry(); text.geom = this.getTextGeometry();
@ -855,7 +866,7 @@ var CanvasGraphics = (function CanvasGraphicsClosure() {
} else if (IR[0] == 'RadialAxial' || IR[0] == 'Dummy') { } else if (IR[0] == 'RadialAxial' || IR[0] == 'Dummy') {
var pattern = Pattern.shadingFromIR(this.ctx, IR); var pattern = Pattern.shadingFromIR(this.ctx, IR);
} else { } else {
throw 'Unkown IR type'; error('Unkown IR type ' + IR[0]);
} }
return pattern; return pattern;
}, },
@ -1142,6 +1153,10 @@ var CanvasGraphics = (function CanvasGraphicsClosure() {
}, },
restoreFillRule: function canvasGraphicsRestoreFillRule(rule) { restoreFillRule: function canvasGraphicsRestoreFillRule(rule) {
this.ctx.mozFillRule = rule; this.ctx.mozFillRule = rule;
},
getSinglePixelWidth: function getSinglePixelWidth(scale) {
var inverse = this.ctx.mozCurrentTransformInverse;
return Math.abs(inverse[0] + inverse[2]);
} }
}; };

59
src/colorspace.js

@ -369,55 +369,16 @@ var DeviceCmykCS = (function DeviceCmykCSClosure() {
DeviceCmykCS.prototype = { DeviceCmykCS.prototype = {
getRgb: function cmykcs_getRgb(color) { getRgb: function cmykcs_getRgb(color) {
var c = color[0], m = color[1], y = color[2], k = color[3]; var c = color[0], m = color[1], y = color[2], k = color[3];
var c1 = 1 - c, m1 = 1 - m, y1 = 1 - y, k1 = 1 - k;
// CMYK -> CMY: http://www.easyrgb.com/index.php?X=MATH&H=14#text14
var x, r, g, b; c = (c * (1 - k) + k);
// this is a matrix multiplication, unrolled for performance m = (m * (1 - k) + k);
// code is taken from the poppler implementation y = (y * (1 - k) + k);
x = c1 * m1 * y1 * k1; // 0 0 0 0
r = g = b = x; // CMY -> RGB: http://www.easyrgb.com/index.php?X=MATH&H=12#text12
x = c1 * m1 * y1 * k; // 0 0 0 1 var r = (1 - c);
r += 0.1373 * x; var g = (1 - m);
g += 0.1216 * x; var b = (1 - y);
b += 0.1255 * x;
x = c1 * m1 * y * k1; // 0 0 1 0
r += x;
g += 0.9490 * x;
x = c1 * m1 * y * k; // 0 0 1 1
r += 0.1098 * x;
g += 0.1020 * x;
x = c1 * m * y1 * k1; // 0 1 0 0
r += 0.9255 * x;
b += 0.5490 * x;
x = c1 * m * y1 * k; // 0 1 0 1
r += 0.1412 * x;
x = c1 * m * y * k1; // 0 1 1 0
r += 0.9294 * x;
g += 0.1098 * x;
b += 0.1412 * x;
x = c1 * m * y * k; // 0 1 1 1
r += 0.1333 * x;
x = c * m1 * y1 * k1; // 1 0 0 0
g += 0.6784 * x;
b += 0.9373 * x;
x = c * m1 * y1 * k; // 1 0 0 1
g += 0.0588 * x;
b += 0.1412 * x;
x = c * m1 * y * k1; // 1 0 1 0
g += 0.6510 * x;
b += 0.3137 * x;
x = c * m1 * y * k; // 1 0 1 1
g += 0.0745 * x;
x = c * m * y1 * k1; // 1 1 0 0
r += 0.1804 * x;
g += 0.1922 * x;
b += 0.5725 * x;
x = c * m * y1 * k; // 1 1 0 1
b += 0.0078 * x;
x = c * m * y * k1; // 1 1 1 0
r += 0.2118 * x;
g += 0.2119 * x;
b += 0.2235 * x;
return [r, g, b]; return [r, g, b];
}, },

10
src/core.js

@ -410,14 +410,14 @@ var Page = (function PageClosure() {
if (callback) if (callback)
callback(e); callback(e);
else else
throw e; error(e);
} }
}.bind(this), }.bind(this),
function pageDisplayReadPromiseError(reason) { function pageDisplayReadPromiseError(reason) {
if (callback) if (callback)
callback(reason); callback(reason);
else else
throw reason; error(reason);
} }
); );
} }
@ -620,7 +620,7 @@ var PDFDoc = (function PDFDocClosure() {
if (!globalScope.PDFJS.disableWorker && typeof Worker !== 'undefined') { if (!globalScope.PDFJS.disableWorker && typeof Worker !== 'undefined') {
var workerSrc = PDFJS.workerSrc; var workerSrc = PDFJS.workerSrc;
if (typeof workerSrc === 'undefined') { if (typeof workerSrc === 'undefined') {
throw 'No PDFJS.workerSrc specified'; error('No PDFJS.workerSrc specified');
} }
try { try {
@ -728,7 +728,7 @@ var PDFDoc = (function PDFDocClosure() {
}); });
break; break;
default: default:
throw 'Got unkown object type ' + type; error('Got unkown object type ' + type);
} }
}, this); }, this);
@ -749,7 +749,7 @@ var PDFDoc = (function PDFDocClosure() {
if (page.displayReadyPromise) if (page.displayReadyPromise)
page.displayReadyPromise.reject(data.error); page.displayReadyPromise.reject(data.error);
else else
throw data.error; error(data.error);
}, this); }, this);
messageHandler.on('jpeg_decode', function(data, promise) { messageHandler.on('jpeg_decode', function(data, promise) {

9
src/evaluator.js

@ -481,8 +481,10 @@ var PartialEvaluator = (function PartialEvaluatorClosure() {
properties.cidToGidMap = this.readCidToGidMap(cidToGidMap); properties.cidToGidMap = this.readCidToGidMap(cidToGidMap);
} }
var flags = properties.flags;
var differences = []; var differences = [];
var baseEncoding = Encodings.StandardEncoding; var baseEncoding = !!(flags & FontFlags.Symbolic) ?
Encodings.symbolsEncoding : Encodings.StandardEncoding;
var hasEncoding = dict.has('Encoding'); var hasEncoding = dict.has('Encoding');
if (hasEncoding) { if (hasEncoding) {
var encoding = xref.fetchIfRef(dict.get('Encoding')); var encoding = xref.fetchIfRef(dict.get('Encoding'));
@ -761,8 +763,9 @@ var PartialEvaluator = (function PartialEvaluatorClosure() {
// Simulating descriptor flags attribute // Simulating descriptor flags attribute
var fontNameWoStyle = baseFontName.split('-')[0]; var fontNameWoStyle = baseFontName.split('-')[0];
var flags = (serifFonts[fontNameWoStyle] || var flags = (serifFonts[fontNameWoStyle] ||
(fontNameWoStyle.search(/serif/gi) != -1) ? 2 : 0) | (fontNameWoStyle.search(/serif/gi) != -1) ? FontFlags.Serif : 0) |
(symbolsFonts[fontNameWoStyle] ? 4 : 32); (symbolsFonts[fontNameWoStyle] ? FontFlags.Symbolic :
FontFlags.Nonsymbolic);
var properties = { var properties = {
type: type.name, type: type.name,

82
src/fonts.js

@ -19,6 +19,18 @@ var kPDFGlyphSpaceUnits = 1000;
// Until hinting is fully supported this constant can be used // Until hinting is fully supported this constant can be used
var kHintingEnabled = false; var kHintingEnabled = false;
var FontFlags = {
FixedPitch: 1,
Serif: 2,
Symbolic: 4,
Script: 8,
Nonsymbolic: 32,
Italic: 64,
AllCap: 65536,
SmallCap: 131072,
ForceBold: 262144
};
var Encodings = { var Encodings = {
get ExpertEncoding() { get ExpertEncoding() {
return shadow(this, 'ExpertEncoding', ['', '', '', '', '', '', '', '', '', return shadow(this, 'ExpertEncoding', ['', '', '', '', '', '', '', '', '',
@ -160,19 +172,20 @@ var Encodings = {
'bracketleft', 'backslash', 'bracketright', 'asciicircum', 'underscore', 'bracketleft', 'backslash', 'bracketright', 'asciicircum', 'underscore',
'quoteleft', 'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j', 'k', 'l', 'quoteleft', 'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j', 'k', 'l',
'm', 'n', 'o', 'p', 'q', 'r', 's', 't', 'u', 'v', 'w', 'x', 'y', 'z', 'm', 'n', 'o', 'p', 'q', 'r', 's', 't', 'u', 'v', 'w', 'x', 'y', 'z',
'braceleft', 'bar', 'braceright', 'asciitilde', '', '', 'exclamdown', 'braceleft', 'bar', 'braceright', 'asciitilde', '', '', '', '', '', '',
'cent', 'sterling', 'fraction', 'yen', 'florin', 'section', 'currency', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '',
'quotesingle', 'quotedblleft', 'guillemotleft', 'guilsinglleft', '', '', '', '', '', '', '', '', '', '', 'exclamdown', 'cent', 'sterling',
'guilsinglright', 'fi', 'fl', '', 'endash', 'dagger', 'daggerdbl', 'fraction', 'yen', 'florin', 'section', 'currency', 'quotesingle',
'periodcentered', '', 'paragraph', 'bullet', 'quotesinglbase', 'quotedblleft', 'guillemotleft', 'guilsinglleft', 'guilsinglright', 'fi',
'quotedblbase', 'quotedblright', 'guillemotright', 'ellipsis', 'fl', '', 'endash', 'dagger', 'daggerdbl', 'periodcentered', '',
'perthousand', '', 'questiondown', '', 'grave', 'acute', 'circumflex', 'paragraph', 'bullet', 'quotesinglbase', 'quotedblbase', 'quotedblright',
'tilde', 'macron', 'breve', 'dotaccent', 'dieresis', '', 'ring', 'guillemotright', 'ellipsis', 'perthousand', '', 'questiondown', '',
'cedilla', '', 'hungarumlaut', 'ogonek', 'caron', 'emdash', '', '', '', 'grave', 'acute', 'circumflex', 'tilde', 'macron', 'breve', 'dotaccent',
'', '', '', '', '', '', '', '', '', '', '', '', '', 'AE', '', 'dieresis', '', 'ring', 'cedilla', '', 'hungarumlaut', 'ogonek', 'caron',
'ordfeminine', '', '', '', '', 'Lslash', 'Oslash', 'OE', 'ordmasculine', 'emdash', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '',
'', '', '', '', '', 'ae', '', '', '', 'dotlessi', '', '', 'lslash', 'AE', '', 'ordfeminine', '', '', '', '', 'Lslash', 'Oslash', 'OE',
'oslash', 'oe', 'germandbls' 'ordmasculine', '', '', '', '', '', 'ae', '', '', '', 'dotlessi', '', '',
'lslash', 'oslash', 'oe', 'germandbls'
]); ]);
}, },
get WinAnsiEncoding() { get WinAnsiEncoding() {
@ -405,6 +418,19 @@ var symbolsFonts = {
'Dingbats': true, 'Symbol': true, 'ZapfDingbats': true 'Dingbats': true, 'Symbol': true, 'ZapfDingbats': true
}; };
// Some characters, e.g. copyrightserif, mapped to the private use area and
// might not be displayed using standard fonts. Mapping/hacking well-known chars
// to the similar equivalents in the normal characters range.
function mapPrivateUseChars(code) {
switch (code) {
case 0xF8E9: // copyrightsans
case 0xF6D9: // copyrightserif
return 0x00A9; // copyright
default:
return code;
}
}
var FontLoader = { var FontLoader = {
listeningForFontLoad: false, listeningForFontLoad: false,
@ -761,8 +787,8 @@ var Font = (function FontClosure() {
var names = name.split('+'); var names = name.split('+');
names = names.length > 1 ? names[1] : names[0]; names = names.length > 1 ? names[1] : names[0];
names = names.split(/[-,_]/g)[0]; names = names.split(/[-,_]/g)[0];
this.isSerifFont = !!(properties.flags & 2); this.isSerifFont = !!(properties.flags & FontFlags.Serif);
this.isSymbolicFont = !!(properties.flags & 4); this.isSymbolicFont = !!(properties.flags & FontFlags.Symbolic);
var type = properties.type; var type = properties.type;
this.type = type; this.type = type;
@ -2136,10 +2162,11 @@ var Font = (function FontClosure() {
window.btoa(data) + ');'); window.btoa(data) + ');');
var rule = "@font-face { font-family:'" + fontName + "';src:" + url + '}'; var rule = "@font-face { font-family:'" + fontName + "';src:" + url + '}';
var styleElement = document.createElement('style');
document.documentElement.getElementsByTagName('head')[0].appendChild( document.documentElement.getElementsByTagName('head')[0].appendChild(
document.createElement('style')); styleElement);
var styleSheet = document.styleSheets[document.styleSheets.length - 1]; var styleSheet = styleElement.sheet;
styleSheet.insertRule(rule, styleSheet.cssRules.length); styleSheet.insertRule(rule, styleSheet.cssRules.length);
return rule; return rule;
@ -2185,7 +2212,7 @@ var Font = (function FontClosure() {
case 'CIDFontType0': case 'CIDFontType0':
if (this.noUnicodeAdaptation) { if (this.noUnicodeAdaptation) {
width = this.widths[this.unicodeToCID[charcode] || charcode]; width = this.widths[this.unicodeToCID[charcode] || charcode];
unicode = charcode; unicode = mapPrivateUseChars(charcode);
break; break;
} }
unicode = this.toUnicode[charcode] || charcode; unicode = this.toUnicode[charcode] || charcode;
@ -2193,7 +2220,7 @@ var Font = (function FontClosure() {
case 'CIDFontType2': case 'CIDFontType2':
if (this.noUnicodeAdaptation) { if (this.noUnicodeAdaptation) {
width = this.widths[this.unicodeToCID[charcode] || charcode]; width = this.widths[this.unicodeToCID[charcode] || charcode];
unicode = charcode; unicode = mapPrivateUseChars(charcode);
break; break;
} }
unicode = this.toUnicode[charcode] || charcode; unicode = this.toUnicode[charcode] || charcode;
@ -2203,7 +2230,7 @@ var Font = (function FontClosure() {
if (!isNum(width)) if (!isNum(width))
width = this.widths[glyphName]; width = this.widths[glyphName];
if (this.noUnicodeAdaptation) { if (this.noUnicodeAdaptation) {
unicode = GlyphsUnicode[glyphName] || charcode; unicode = mapPrivateUseChars(GlyphsUnicode[glyphName] || charcode);
break; break;
} }
unicode = this.glyphNameMap[glyphName] || unicode = this.glyphNameMap[glyphName] ||
@ -2234,9 +2261,8 @@ var Font = (function FontClosure() {
} }
// MacRoman encoding address by re-encoding the cmap table // MacRoman encoding address by re-encoding the cmap table
unicode = glyphName in GlyphsUnicode ? unicode = glyphName in this.glyphNameMap ?
GlyphsUnicode[glyphName] : this.glyphNameMap[glyphName] : GlyphsUnicode[glyphName];
this.glyphNameMap[glyphName];
break; break;
default: default:
warn('Unsupported font type: ' + this.type); warn('Unsupported font type: ' + this.type);
@ -3339,15 +3365,9 @@ var Type2CFF = (function Type2CFFClosure() {
inverseEncoding[encoding[charcode]] = charcode | 0; 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];
if (glyph == '.notdef') { if (glyph == '.notdef')
charstrings.push({
unicode: 0,
code: 0,
gid: i,
glyph: glyph
});
continue; continue;
}
var code = inverseEncoding[i]; var code = inverseEncoding[i];
if (!code || isSpecialUnicode(code)) { if (!code || isSpecialUnicode(code)) {
unassignedUnicodeItems.push(i); unassignedUnicodeItems.push(i);

1854
src/jpx.js

File diff suppressed because it is too large Load Diff

4
src/obj.js

@ -574,7 +574,7 @@ var XRef = (function XRefClosure() {
var stream, parser; var stream, parser;
if (e.uncompressed) { if (e.uncompressed) {
if (e.gen != gen) if (e.gen != gen)
throw ('inconsistent generation in XRef'); error('inconsistent generation in XRef');
stream = this.stream.makeSubStream(e.offset); stream = this.stream.makeSubStream(e.offset);
parser = new Parser(new Lexer(stream), true, this); parser = new Parser(new Lexer(stream), true, this);
var obj1 = parser.getObj(); var obj1 = parser.getObj();
@ -703,7 +703,7 @@ var PDFObjects = (function PDFObjectsClosure() {
// If there isn't an object yet or the object isn't resolved, then the // If there isn't an object yet or the object isn't resolved, then the
// data isn't ready yet! // data isn't ready yet!
if (!obj || !obj.isResolved) { if (!obj || !obj.isResolved) {
throw 'Requesting object that isn\'t resolved yet ' + objId; error('Requesting object that isn\'t resolved yet ' + objId);
return null; return null;
} else { } else {
return obj.data; return obj.data;

7
src/parser.js

@ -240,6 +240,10 @@ var Parser = (function ParserClosure() {
var bytes = stream.getBytes(length); var bytes = stream.getBytes(length);
return new JpegStream(bytes, stream.dict, this.xref); return new JpegStream(bytes, stream.dict, this.xref);
} }
if (name == 'JPXDecode' || name == 'JPX') {
var bytes = stream.getBytes(length);
return new JpxStream(bytes, stream.dict);
}
if (name == 'ASCII85Decode' || name == 'A85') { if (name == 'ASCII85Decode' || name == 'A85') {
return new Ascii85Stream(stream); return new Ascii85Stream(stream);
} }
@ -249,6 +253,9 @@ var Parser = (function ParserClosure() {
if (name == 'CCITTFaxDecode' || name == 'CCF') { if (name == 'CCITTFaxDecode' || name == 'CCF') {
return new CCITTFaxStream(stream, params); return new CCITTFaxStream(stream, params);
} }
if (name == 'RunLengthDecode') {
return new RunLengthStream(stream);
}
warn('filter "' + name + '" not supported yet'); warn('filter "' + name + '" not supported yet');
return stream; return stream;
} }

150
src/stream.js

@ -821,6 +821,7 @@ var JpegStream = (function JpegStreamClosure() {
JpegStream.prototype.ensureBuffer = function jpegStreamEnsureBuffer(req) { JpegStream.prototype.ensureBuffer = function jpegStreamEnsureBuffer(req) {
if (this.bufferLength) if (this.bufferLength)
return; return;
try {
var jpegImage = new JpegImage(); var jpegImage = new JpegImage();
if (this.colorTransform != -1) if (this.colorTransform != -1)
jpegImage.colorTransform = this.colorTransform; jpegImage.colorTransform = this.colorTransform;
@ -830,6 +831,9 @@ var JpegStream = (function JpegStreamClosure() {
var data = jpegImage.getData(width, height); var data = jpegImage.getData(width, height);
this.buffer = data; this.buffer = data;
this.bufferLength = data.length; this.bufferLength = data.length;
} catch (e) {
error('JPEG error: ' + e);
}
}; };
JpegStream.prototype.getIR = function jpegStreamGetIR() { JpegStream.prototype.getIR = function jpegStreamGetIR() {
return bytesToString(this.bytes); return bytesToString(this.bytes);
@ -869,6 +873,107 @@ var JpegStream = (function JpegStreamClosure() {
return JpegStream; return JpegStream;
})(); })();
/**
* For JPEG 2000's we use a library to decode these images and
* the stream behaves like all the other DecodeStreams.
*/
var JpxStream = (function JpxStreamClosure() {
function JpxStream(bytes, dict) {
this.dict = dict;
this.bytes = bytes;
DecodeStream.call(this);
}
JpxStream.prototype = Object.create(DecodeStream.prototype);
JpxStream.prototype.ensureBuffer = function jpxStreamEnsureBuffer(req) {
if (this.bufferLength)
return;
var jpxImage = new JpxImage();
jpxImage.parse(this.bytes);
var width = jpxImage.width;
var height = jpxImage.height;
var componentsCount = jpxImage.componentsCount;
if (componentsCount != 1 && componentsCount != 3 && componentsCount != 4)
error('JPX with ' + componentsCount + ' components is not supported');
var data = new Uint8Array(width * height * componentsCount);
for (var k = 0, kk = jpxImage.tiles.length; k < kk; k++) {
var tileCompoments = jpxImage.tiles[k];
var tileWidth = tileCompoments[0].width;
var tileHeight = tileCompoments[0].height;
var tileLeft = tileCompoments[0].left;
var tileTop = tileCompoments[0].top;
var dataPosition, sourcePosition, data0, data1, data2, data3, rowFeed;
switch (componentsCount) {
case 1:
data0 = tileCompoments[0].items;
dataPosition = width * tileTop + tileLeft;
rowFeed = width - tileWidth;
sourcePosition = 0;
for (var j = 0; j < tileHeight; j++) {
for (var i = 0; i < tileWidth; i++)
data[dataPosition++] = data0[sourcePosition++];
dataPosition += rowFeed;
}
break;
case 3:
data0 = tileCompoments[0].items;
data1 = tileCompoments[1].items;
data2 = tileCompoments[2].items;
dataPosition = (width * tileTop + tileLeft) * 3;
rowFeed = (width - tileWidth) * 3;
sourcePosition = 0;
for (var j = 0; j < tileHeight; j++) {
for (var i = 0; i < tileWidth; i++) {
data[dataPosition++] = data0[sourcePosition];
data[dataPosition++] = data1[sourcePosition];
data[dataPosition++] = data2[sourcePosition];
sourcePosition++;
}
dataPosition += rowFeed;
}
break;
case 4:
data0 = tileCompoments[0].items;
data1 = tileCompoments[1].items;
data2 = tileCompoments[2].items;
data3 = tileCompoments[3].items;
dataPosition = (width * tileTop + tileLeft) * 4;
rowFeed = (width - tileWidth) * 4;
sourcePosition = 0;
for (var j = 0; j < tileHeight; j++) {
for (var i = 0; i < tileWidth; i++) {
data[dataPosition++] = data0[sourcePosition];
data[dataPosition++] = data1[sourcePosition];
data[dataPosition++] = data2[sourcePosition];
data[dataPosition++] = data3[sourcePosition];
sourcePosition++;
}
dataPosition += rowFeed;
}
break;
}
}
this.buffer = data;
this.bufferLength = data.length;
};
JpxStream.prototype.getChar = function jpxStreamGetChar() {
error('internal error: getChar is not valid on JpxStream');
};
return JpxStream;
})();
var DecryptStream = (function DecryptStreamClosure() { var DecryptStream = (function DecryptStreamClosure() {
function DecryptStream(str, decrypt) { function DecryptStream(str, decrypt) {
this.str = str; this.str = str;
@ -1041,6 +1146,51 @@ var AsciiHexStream = (function AsciiHexStreamClosure() {
return AsciiHexStream; return AsciiHexStream;
})(); })();
var RunLengthStream = (function RunLengthStreamClosure() {
function RunLengthStream(str) {
this.str = str;
this.dict = str.dict;
DecodeStream.call(this);
}
RunLengthStream.prototype = Object.create(DecodeStream.prototype);
RunLengthStream.prototype.readBlock = function runLengthStreamReadBlock() {
// The repeatHeader has following format. The first byte defines type of run
// and amount of bytes to repeat/copy: n = 0 through 127 - copy next n bytes
// (in addition to the second byte from the header), n = 129 through 255 -
// duplicate the second byte from the header (257 - n) times, n = 128 - end.
var repeatHeader = this.str.getBytes(2);
if (!repeatHeader || repeatHeader.length < 2 || repeatHeader[0] == 128) {
this.eof = true;
return;
}
var bufferLength = this.bufferLength;
var n = repeatHeader[0];
if (n < 128) {
// copy n bytes
var buffer = this.ensureBuffer(bufferLength + n + 1);
buffer[bufferLength++] = repeatHeader[1];
if (n > 0) {
var source = this.str.getBytes(n);
buffer.set(source, bufferLength);
bufferLength += n;
}
} else {
n = 257 - n;
var b = repeatHeader[1];
var buffer = this.ensureBuffer(bufferLength + n + 1);
for (var i = 0; i < n; i++)
buffer[bufferLength++] = b;
}
this.bufferLength = bufferLength;
};
return RunLengthStream;
})();
var CCITTFaxStream = (function CCITTFaxStreamClosure() { var CCITTFaxStream = (function CCITTFaxStreamClosure() {
var ccittEOL = -2; var ccittEOL = -2;

16
src/util.js

@ -255,8 +255,8 @@ var Promise = (function PromiseClosure() {
return; return;
} }
if (this._data !== EMPTY_PROMISE) { if (this._data !== EMPTY_PROMISE) {
throw 'Promise ' + this.name + error('Promise ' + this.name +
': Cannot set the data of a promise twice'; ': Cannot set the data of a promise twice');
} }
this._data = value; this._data = value;
this.hasData = true; this.hasData = true;
@ -268,7 +268,7 @@ var Promise = (function PromiseClosure() {
get data() { get data() {
if (this._data === EMPTY_PROMISE) { if (this._data === EMPTY_PROMISE) {
throw 'Promise ' + this.name + ': Cannot get data that isn\'t set'; error('Promise ' + this.name + ': Cannot get data that isn\'t set');
} }
return this._data; return this._data;
}, },
@ -283,10 +283,10 @@ var Promise = (function PromiseClosure() {
resolve: function promiseResolve(data) { resolve: function promiseResolve(data) {
if (this.isResolved) { if (this.isResolved) {
throw 'A Promise can be resolved only once ' + this.name; error('A Promise can be resolved only once ' + this.name);
} }
if (this.isRejected) { if (this.isRejected) {
throw 'The Promise was already rejected ' + this.name; error('The Promise was already rejected ' + this.name);
} }
this.isResolved = true; this.isResolved = true;
@ -300,10 +300,10 @@ var Promise = (function PromiseClosure() {
reject: function proimseReject(reason) { reject: function proimseReject(reason) {
if (this.isRejected) { if (this.isRejected) {
throw 'A Promise can be rejected only once ' + this.name; error('A Promise can be rejected only once ' + this.name);
} }
if (this.isResolved) { if (this.isResolved) {
throw 'The Promise was already resolved ' + this.name; error('The Promise was already resolved ' + this.name);
} }
this.isRejected = true; this.isRejected = true;
@ -317,7 +317,7 @@ var Promise = (function PromiseClosure() {
then: function promiseThen(callback, errback) { then: function promiseThen(callback, errback) {
if (!callback) { if (!callback) {
throw 'Requiring callback' + this.name; error('Requiring callback' + this.name);
} }
// If the promise is already resolved, call the callback directly. // If the promise is already resolved, call the callback directly.

9
src/worker.js

@ -26,7 +26,7 @@ function MessageHandler(name, comObj) {
delete callbacks[callbackId]; delete callbacks[callbackId];
callback(data.data); callback(data.data);
} else { } else {
throw 'Cannot resolve callback ' + callbackId; error('Cannot resolve callback ' + callbackId);
} }
} else if (data.action in ah) { } else if (data.action in ah) {
var action = ah[data.action]; var action = ah[data.action];
@ -44,7 +44,7 @@ function MessageHandler(name, comObj) {
action[0].call(action[1], data.data); action[0].call(action[1], data.data);
} }
} else { } else {
throw 'Unkown action from worker: ' + data.action; error('Unkown action from worker: ' + data.action);
} }
}; };
} }
@ -53,7 +53,7 @@ MessageHandler.prototype = {
on: function messageHandlerOn(actionName, handler, scope) { on: function messageHandlerOn(actionName, handler, scope) {
var ah = this.actionHandler; var ah = this.actionHandler;
if (ah[actionName]) { if (ah[actionName]) {
throw 'There is already an actionName called "' + actionName + '"'; error('There is already an actionName called "' + actionName + '"');
} }
ah[actionName] = [handler, scope]; ah[actionName] = [handler, scope];
}, },
@ -208,6 +208,7 @@ var workerConsole = {
action: 'console_error', action: 'console_error',
data: args data: args
}); });
throw 'pdf.js execution error';
}, },
time: function time(name) { time: function time(name) {
@ -217,7 +218,7 @@ var workerConsole = {
timeEnd: function timeEnd(name) { timeEnd: function timeEnd(name) {
var time = consoleTimer[name]; var time = consoleTimer[name];
if (time == null) { if (time == null) {
throw 'Unkown timer name ' + name; error('Unkown timer name ' + name);
} }
this.log('Timer:', name, Date.now() - time); this.log('Timer:', name, Date.now() - time);
} }

3
src/worker_loader.js

@ -23,7 +23,8 @@ var files = [
'pattern.js', 'pattern.js',
'stream.js', 'stream.js',
'worker.js', 'worker.js',
'../external/jpgjs/jpg.js' '../external/jpgjs/jpg.js',
'jpx.js'
]; ];
// Load all the files. // Load all the files.

3
test/.gitignore vendored

@ -0,0 +1,3 @@
ref/
tmp/

2
test/pdfs/.gitignore vendored

@ -22,3 +22,5 @@
!issue918.pdf !issue918.pdf
!smaskdim.pdf !smaskdim.pdf
!type4psfunc.pdf !type4psfunc.pdf
!S2.pdf
!zerowidthline.pdf

2549
test/pdfs/S2.pdf

File diff suppressed because one or more lines are too long

1
test/pdfs/issue1096.pdf.link

@ -0,0 +1 @@
http://www.faithaliveresources.org/Content/Site135/FilesSamples/105315400440pdf_00000009843.pdf

1
test/pdfs/issue1127.pdf.link

@ -0,0 +1 @@
https://vmp.ethz.ch/pdfs/diplome/vordiplome/Block%201/Algorithmen_%26_Komplexitaet/AlgoKo_f08_Aufg.pdf

BIN
test/pdfs/zerowidthline.pdf

Binary file not shown.

4
test/test.py

@ -9,7 +9,7 @@ USAGE_EXAMPLE = "%prog"
# The local web server uses the git repo as the document root. # The local web server uses the git repo as the document root.
DOC_ROOT = os.path.abspath(os.path.join(os.path.dirname(__file__),"..")) DOC_ROOT = os.path.abspath(os.path.join(os.path.dirname(__file__),".."))
ANAL = True GIT_CLONE_CHECK = True
DEFAULT_MANIFEST_FILE = 'test_manifest.json' DEFAULT_MANIFEST_FILE = 'test_manifest.json'
EQLOG_FILE = 'eq.log' EQLOG_FILE = 'eq.log'
BROWSERLOG_FILE = 'browser.log' BROWSERLOG_FILE = 'browser.log'
@ -344,7 +344,7 @@ def verifyPDFs(manifestList):
def setUp(options): def setUp(options):
# Only serve files from a pdf.js clone # Only serve files from a pdf.js clone
assert not ANAL or os.path.isfile('../src/pdf.js') and os.path.isdir('../.git') assert not GIT_CLONE_CHECK or os.path.isfile('../src/pdf.js') and os.path.isdir('../.git')
if options.masterMode and os.path.isdir(TMPDIR): if options.masterMode and os.path.isdir(TMPDIR):
print 'Temporary snapshot dir tmp/ is still around.' print 'Temporary snapshot dir tmp/ is still around.'

29
test/test_manifest.json

@ -176,7 +176,6 @@
"md5": "eb7b224107205db4fea9f7df0185f77d", "md5": "eb7b224107205db4fea9f7df0185f77d",
"link": true, "link": true,
"rounds": 1, "rounds": 1,
"skipPages": [12,31],
"type": "eq" "type": "eq"
}, },
{ "id": "fips197", { "id": "fips197",
@ -403,6 +402,21 @@
"link": true, "link": true,
"type": "eq" "type": "eq"
}, },
{ "id": "issue1096",
"file": "pdfs/issue1096.pdf",
"md5": "7f75d2b4b93c78d401ff39e8c1b00612",
"rounds": 1,
"pageLimit": 10,
"link": true,
"type": "eq"
},
{ "id": "issue1127",
"file": "pdfs/issue1127.pdf",
"md5": "4fb2be5ffefeafda4ba977de2a1bb4d8",
"rounds": 1,
"link": true,
"type": "eq"
},
{ "id": "liveprogramming", { "id": "liveprogramming",
"file": "pdfs/liveprogramming.pdf", "file": "pdfs/liveprogramming.pdf",
"md5": "7bd4dad1188232ef597d36fd72c33e52", "md5": "7bd4dad1188232ef597d36fd72c33e52",
@ -411,11 +425,24 @@
"link": true, "link": true,
"type": "load" "type": "load"
}, },
{ "id": "S2-eq",
"file": "pdfs/S2.pdf",
"md5": "d0b6137846df6e0fe058f234a87fb588",
"rounds": 1,
"type": "eq"
},
{ "id": "issue1055", { "id": "issue1055",
"file": "pdfs/issue1055.pdf", "file": "pdfs/issue1055.pdf",
"md5": "3ba56c2e48dce81da8669b1b9cf98ff0", "md5": "3ba56c2e48dce81da8669b1b9cf98ff0",
"rounds": 1, "rounds": 1,
"link": true, "link": true,
"type": "eq" "type": "eq"
},
{ "id": "zerowidthline",
"file": "pdfs/zerowidthline.pdf",
"md5": "295d26e61a85635433f8e4b768953f60",
"rounds": 1,
"link": false,
"type": "eq"
} }
] ]

1
test/test_slave.html

@ -22,6 +22,7 @@
<script type="text/javascript" src="/src/stream.js"></script> <script type="text/javascript" src="/src/stream.js"></script>
<script type="text/javascript" src="/src/worker.js"></script> <script type="text/javascript" src="/src/worker.js"></script>
<script type="text/javascript" src="/external/jpgjs/jpg.js"></script> <script type="text/javascript" src="/external/jpgjs/jpg.js"></script>
<script type="text/javascript" src="/src/jpx.js"></script>
<script type="text/javascript" src="driver.js"></script> <script type="text/javascript" src="driver.js"></script>
<script type="text/javascript"> <script type="text/javascript">

7
web/compatibility.js

@ -224,3 +224,10 @@
} }
}); });
})(); })();
// Check console compatability
(function checkConsoleCompatibility() {
if (typeof console == 'undefined') {
console = {log: function() {}};
}
})();

1
web/viewer.html

@ -28,6 +28,7 @@
<script type="text/javascript" src="../src/stream.js"></script> <!-- PDFJSSCRIPT_REMOVE --> <script type="text/javascript" src="../src/stream.js"></script> <!-- PDFJSSCRIPT_REMOVE -->
<script type="text/javascript" src="../src/worker.js"></script> <!-- PDFJSSCRIPT_REMOVE --> <script type="text/javascript" src="../src/worker.js"></script> <!-- PDFJSSCRIPT_REMOVE -->
<script type="text/javascript" src="../external/jpgjs/jpg.js"></script> <!-- PDFJSSCRIPT_REMOVE --> <script type="text/javascript" src="../external/jpgjs/jpg.js"></script> <!-- PDFJSSCRIPT_REMOVE -->
<script type="text/javascript" src="../src/jpx.js"></script> <!-- PDFJSSCRIPT_REMOVE -->
<script type="text/javascript">PDFJS.workerSrc = '../src/worker_loader.js';</script> <!-- PDFJSSCRIPT_REMOVE --> <script type="text/javascript">PDFJS.workerSrc = '../src/worker_loader.js';</script> <!-- PDFJSSCRIPT_REMOVE -->
<script type="text/javascript" src="viewer.js"></script> <script type="text/javascript" src="viewer.js"></script>

64
web/viewer.js

@ -6,6 +6,7 @@
var kDefaultURL = 'compressed.tracemonkey-pldi-09.pdf'; var kDefaultURL = 'compressed.tracemonkey-pldi-09.pdf';
var kDefaultScale = 'auto'; var kDefaultScale = 'auto';
var kDefaultScaleDelta = 1.1; var kDefaultScaleDelta = 1.1;
var kUnknownScale = 0;
var kCacheSize = 20; var kCacheSize = 20;
var kCssUnits = 96.0 / 72.0; var kCssUnits = 96.0 / 72.0;
var kScrollbarPadding = 40; var kScrollbarPadding = 40;
@ -157,7 +158,7 @@ var currentPageNumber = 1;
var PDFView = { var PDFView = {
pages: [], pages: [],
thumbnails: [], thumbnails: [],
currentScale: 0, currentScale: kUnknownScale,
currentScaleValue: null, currentScaleValue: null,
initialBookmark: document.location.hash.substring(1), initialBookmark: document.location.hash.substring(1),
@ -212,12 +213,12 @@ var PDFView = {
zoomIn: function pdfViewZoomIn() { zoomIn: function pdfViewZoomIn() {
var newScale = Math.min(kMaxScale, this.currentScale * kDefaultScaleDelta); var newScale = Math.min(kMaxScale, this.currentScale * kDefaultScaleDelta);
this.setScale(newScale, true); this.parseScale(newScale, true);
}, },
zoomOut: function pdfViewZoomOut() { zoomOut: function pdfViewZoomOut() {
var newScale = Math.max(kMinScale, this.currentScale / kDefaultScaleDelta); var newScale = Math.max(kMinScale, this.currentScale / kDefaultScaleDelta);
this.setScale(newScale, true); this.parseScale(newScale, true);
}, },
set page(val) { set page(val) {
@ -478,10 +479,16 @@ var PDFView = {
} }
else if (storedHash) else if (storedHash)
this.setHash(storedHash); this.setHash(storedHash);
else { else if (scale) {
this.parseScale(scale || kDefaultScale, true); this.parseScale(scale, true);
this.page = 1; this.page = 1;
} }
if (PDFView.currentScale === kUnknownScale) {
// Scale was not initialized: invalid bookmark or scale was not specified.
// Setting the default one.
this.parseScale(kDefaultScale, true);
}
}, },
setHash: function pdfViewSetHash(hash) { setHash: function pdfViewSetHash(hash) {
@ -776,6 +783,8 @@ var PageView = function pageView(container, content, id, pageWidth, pageHeight,
if (scale && scale !== PDFView.currentScale) if (scale && scale !== PDFView.currentScale)
PDFView.parseScale(scale, true); PDFView.parseScale(scale, true);
else if (PDFView.currentScale === kUnknownScale)
PDFView.parseScale(kDefaultScale, true);
setTimeout(function pageViewScrollIntoViewRelayout() { setTimeout(function pageViewScrollIntoViewRelayout() {
// letting page to re-layout before scrolling // letting page to re-layout before scrolling
@ -994,22 +1003,55 @@ var TextLayerBuilder = function textLayerBuilder(textLayerDiv) {
var self = this; var self = this;
var textDivs = this.textDivs; var textDivs = this.textDivs;
var textLayerDiv = this.textLayerDiv; var textLayerDiv = this.textLayerDiv;
this.textLayerTimer = setInterval(function renderTextLayer() { var renderTimer = null;
var renderingDone = false;
var renderInterval = 0;
var resumeInterval = 500; // in ms
// Render the text layer, one div at a time
function renderTextLayer() {
if (textDivs.length === 0) { if (textDivs.length === 0) {
clearInterval(self.textLayerTimer); clearInterval(renderTimer);
renderingDone = true;
return; return;
} }
var textDiv = textDivs.shift(); var textDiv = textDivs.shift();
if (textDiv.dataset.textLength >= 1) { // avoid div by zero if (textDiv.dataset.textLength > 0) {
textLayerDiv.appendChild(textDiv); textLayerDiv.appendChild(textDiv);
if (textDiv.dataset.textLength > 1) { // avoid div by zero
// Adjust div width (via letterSpacing) to match canvas text // Adjust div width (via letterSpacing) to match canvas text
// Due to the .offsetWidth calls, this is slow // Due to the .offsetWidth calls, this is slow
// This needs to come after appending to the DOM
textDiv.style.letterSpacing = textDiv.style.letterSpacing =
((textDiv.dataset.canvasWidth - textDiv.offsetWidth) / ((textDiv.dataset.canvasWidth - textDiv.offsetWidth) /
(textDiv.dataset.textLength - 1)) + 'px'; (textDiv.dataset.textLength - 1)) + 'px';
} }
}, 0); } // textLength > 0
}; }
renderTimer = setInterval(renderTextLayer, renderInterval);
// Stop rendering when user scrolls. Resume after XXX milliseconds
// of no scroll events
var scrollTimer = null;
function textLayerOnScroll() {
if (renderingDone) {
window.removeEventListener('scroll', textLayerOnScroll, false);
return;
}
// Immediately pause rendering
clearInterval(renderTimer);
clearTimeout(scrollTimer);
scrollTimer = setTimeout(function textLayerScrollTimer() {
// Resume rendering
renderTimer = setInterval(renderTextLayer, renderInterval);
}, resumeInterval);
}; // textLayerOnScroll
window.addEventListener('scroll', textLayerOnScroll, false);
}; // endLayout
this.appendText = function textLayerBuilderAppendText(text, this.appendText = function textLayerBuilderAppendText(text,
fontName, fontSize) { fontName, fontSize) {
@ -1274,7 +1316,7 @@ window.addEventListener('keydown', function keydown(evt) {
handled = true; handled = true;
break; break;
case 48: // '0' case 48: // '0'
PDFView.setScale(kDefaultScale, true); PDFView.parseScale(kDefaultScale, true);
handled = true; handled = true;
break; break;
case 37: // left arrow case 37: // left arrow

Loading…
Cancel
Save