Merge with upstream

This commit is contained in:
Vivien Nicolas 2011-09-20 03:42:34 +02:00
commit 06a3b49e49
25 changed files with 1028 additions and 620 deletions

View File

@ -8,6 +8,7 @@
Justin D'Arcangelo <justindarc@gmail.com>
Yury Delendik
Kalervo Kujala
Adil Allawi <@ironymark>
Permission is hereby granted, free of charge, to any person obtaining a
copy of this software and associated documentation files (the "Software"),

View File

@ -55,30 +55,30 @@ browser-test:
--browserManifestFile=$(PDF_BROWSERS) \
--manifestFile=$(PDF_TESTS)
# make shell-test
#
# This target runs all of the tests that can be run in a JS shell.
# The shell used is taken from the JS_SHELL environment variable. If
# that variable is not defined, the script will attempt to use the copy
# of Rhino that comes with the Closure compiler used for producing the
# website.
SHELL_TARGET = $(NULL)
ifeq ($(JS_SHELL),)
JS_SHELL := "java -cp $(BUILD_DIR)/compiler.jar"
JS_SHELL += "com.google.javascript.jscomp.mozilla.rhino.tools.shell.Main"
SHELL_TARGET = compiler
endif
shell-test: shell-msg $(SHELL_TARGET) font-test
shell-msg:
ifeq ($(SHELL_TARGET), compiler)
@echo "No JS_SHELL env variable present."
@echo "The default is to find a copy of Rhino and try that."
endif
@echo "JS shell command is: $(JS_SHELL)"
font-test:
@echo "font test stub."
# # make shell-test
# #
# # This target runs all of the tests that can be run in a JS shell.
# # The shell used is taken from the JS_SHELL environment variable. If
# # that variable is not defined, the script will attempt to use the copy
# # of Rhino that comes with the Closure compiler used for producing the
# # website.
# SHELL_TARGET = $(NULL)
# ifeq ($(JS_SHELL),)
# JS_SHELL := "java -cp $(BUILD_DIR)/compiler.jar"
# JS_SHELL += "com.google.javascript.jscomp.mozilla.rhino.tools.shell.Main"
# SHELL_TARGET = compiler
# endif
#
# shell-test: shell-msg $(SHELL_TARGET) font-test
# shell-msg:
# ifeq ($(SHELL_TARGET), compiler)
# @echo "No JS_SHELL env variable present."
# @echo "The default is to find a copy of Rhino and try that."
# endif
# @echo "JS shell command is: $(JS_SHELL)"
#
# font-test:
# @echo "font test stub."
# make lint
#
@ -133,18 +133,18 @@ $(GH_PAGES)/web/%: web/%
$(GH_PAGES)/web/images/%: web/images/%
@cp $< $@
# make compiler
#
# This target downloads the Closure compiler, and places it in the
# build directory. This target is also useful when the user doesn't
# have a JS shell available--we can have them use the Rhino shell that
# comes with Closure.
COMPILER_URL = http://closure-compiler.googlecode.com/files/compiler-latest.zip
compiler: $(BUILD_DIR)/compiler.zip
$(BUILD_DIR)/compiler.zip: | $(BUILD_DIR)
curl $(COMPILER_URL) > $(BUILD_DIR)/compiler.zip;
cd $(BUILD_DIR); unzip compiler.zip compiler.jar;
# # make compiler
# #
# # This target downloads the Closure compiler, and places it in the
# # build directory. This target is also useful when the user doesn't
# # have a JS shell available--we can have them use the Rhino shell that
# # comes with Closure.
# COMPILER_URL = http://closure-compiler.googlecode.com/files/compiler-latest.zip
#
# compiler: $(BUILD_DIR)/compiler.zip
# $(BUILD_DIR)/compiler.zip: | $(BUILD_DIR)
# curl $(COMPILER_URL) > $(BUILD_DIR)/compiler.zip;
# cd $(BUILD_DIR); unzip compiler.zip compiler.jar;
# make firefox-extension
#

View File

@ -1,17 +1,84 @@
# pdf.js
pdf.js is a technology demonstrator prototype to explore whether the HTML5
platform is complete enough to faithfully and efficiently render the ISO
32000-1:2008 Portable Document Format (PDF) without native code assistance.
pdf.js is not currently part of the Mozilla project, and there is no plan
yet to integrate it into Firefox. We will explore that possibility once
pdf.js is production ready. Until then we aim to publish a Firefox
PDF reader extension powered by pdf.js.
## Overview
pdf.js is an HTML5 technology experiment that explores building a faithful
and efficient Portable Document Format (PDF) renderer without native code
assistance.
pdf.js is community-driven and supported by Mozilla Labs. Our goal is to
create a general-purpose, web standards-based platform for parsing and
rendering PDFs, and eventually release a PDF reader extension powered by
pdf.js. Integration with Firefox is a possibility if the experiment proves
successful.
## Getting started
**Online demo**
For an online demo, visit:
http://andreasgal.github.com/pdf.js/web/viewer.html
This demo provides an interactive interface for displaying and browsing PDFs
using the pdf.js API.
**Hello world**
For a "hello world" example, take a look at:
examples/helloworld/
This example illustrates the bare minimum ingredients for integrating pdf.js
in a custom project.
## Running the Tests
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
against reference images, pixel-by-pixel.
To run the tests, first configure the browser manifest file at:
test/resources/browser_manifests/browser_manifest.json
Sample manifests for different platforms are provided in that directory.
To run all the bundled tests, type:
$ make test
and cross your fingers. Different types of tests are available, see the test
manifest file at:
test/test_manifest.json
The test type `eq` tests whether the output images are identical to reference
images. The test type `load` simply tests whether the file loads without
raising any errors.
## Contributing
pdf.js is a community-driver project, so contributors are always welcome.
Simply fork our repo and contribute away. A great place to start is our
open issues.
For better consistency and long-term stability, please do look around the
code and try to follow our conventions.
## Additional resources
Our demo site is here:
http://andreasgal.github.com/pdf.js/
http://andreasgal.github.com/pdf.js/web/viewer.html
You can read more about pdf.js here:
@ -19,14 +86,19 @@ You can read more about pdf.js here:
http://blog.mozilla.com/cjones/2011/06/15/overview-of-pdf-js-guts/
follow us on twitter: @pdfjs
Follow us on twitter: @pdfjs
http://twitter.com/#!/pdfjs
join our mailing list:
Join our mailing list:
dev-pdf-js@lists.mozilla.org
Subscribe either using lists.mozilla.org or Google Groups:
https://lists.mozilla.org/listinfo/dev-pdf-js
https://groups.google.com/group/mozilla.dev.pdf-js/topics
and talk to us on IRC:
Talk to us on IRC:
#pdfjs on irc.mozilla.org

View File

@ -0,0 +1,16 @@
## "Hello World" overview
This example is a minimalistic application of the pdf.js project. The file
`helloworld.pdf` is from the GNUpdf project (see [Introduction to PDF at GNUpdf](http://gnupdf.org/Introduction_to_PDF), and contains a simple and
human-readable PDF.
## Getting started
Point your browser to `index.html`. Voila. Take a peek at `hello.js` to see
how to make basic calls to `pdf.js`.
## Additional resources
+ [GNUpdf - Introduction to PDF](http://gnupdf.org/Introduction_to_PDF)

View File

@ -0,0 +1,49 @@
//
// See README for overview
//
//
// Ajax GET request, for binary files
// (like jQuery's $.get(), but supports the binary type ArrayBuffer)
//
var ajaxGet = function(url, callback){
var xhr = new XMLHttpRequest();
xhr.open('GET', url);
xhr.mozResponseType = xhr.responseType = 'arraybuffer';
xhr.expected = (document.URL.indexOf('file:') === 0) ? 0 : 200;
xhr.onreadystatechange = function() {
if (xhr.readyState === 4 && xhr.status === xhr.expected) {
var data = (xhr.mozResponseArrayBuffer || xhr.mozResponse ||
xhr.responseArrayBuffer || xhr.response);
callback(data);
}
};
xhr.send(null);
}
//
// This is where the fun happens
//
ajaxGet('helloworld.pdf', function(data){
//
// Instantiate PDFDoc with PDF data
//
var pdf = new PDFDoc(new Stream(data));
var page = pdf.getPage(1);
var scale = 1.5;
//
// Prepare canvas using PDF page dimensions
//
var canvas = document.getElementById('the-canvas');
var context = canvas.getContext('2d');
canvas.height = page.height * scale;
canvas.width = page.width * scale;
//
// Render PDF page into canvas context
//
page.startRendering(context);
});

View File

@ -0,0 +1,68 @@
%PDF-1.7
1 0 obj % entry point
<<
/Type /Catalog
/Pages 2 0 R
>>
endobj
2 0 obj
<<
/Type /Pages
/MediaBox [ 0 0 200 200 ]
/Count 1
/Kids [ 3 0 R ]
>>
endobj
3 0 obj
<<
/Type /Page
/Parent 2 0 R
/Resources <<
/Font <<
/F1 4 0 R
>>
>>
/Contents 5 0 R
>>
endobj
4 0 obj
<<
/Type /Font
/Subtype /Type1
/BaseFont /Times-Roman
>>
endobj
5 0 obj % page content
<<
/Length 44
>>
stream
BT
70 50 TD
/F1 12 Tf
(Hello, world!) Tj
ET
endstream
endobj
xref
0 6
0000000000 65535 f
0000000010 00000 n
0000000079 00000 n
0000000173 00000 n
0000000301 00000 n
0000000380 00000 n
trailer
<<
/Size 6
/Root 1 0 R
>>
startxref
492
%%EOF

View File

@ -0,0 +1,18 @@
<!doctype html>
<html>
<head>
<!-- PDF.js-specific -->
<script type="text/javascript" src="../../pdf.js"></script>
<script type="text/javascript" src="../../metrics.js"></script>
<script type="text/javascript" src="../../fonts.js"></script>
<script type="text/javascript" src="../../glyphlist.js"></script>
<script type="text/javascript" src="hello.js"></script>
</head>
<body>
<canvas id="the-canvas" style="border:1px solid black;"/>
</body>
</html>

247
fonts.js Executable file → Normal file
View File

@ -12,6 +12,10 @@ var kMaxWaitForFontFace = 1000;
// Unicode Private Use Area
var kCmapGlyphOffset = 0xE000;
// PDF Glyph Space Units are one Thousandth of a TextSpace Unit
// except for Type 3 fonts
var kPDFGlyphSpaceUnits = 1000;
// Until hinting is fully supported this constant can be used
var kHintingEnabled = false;
@ -436,6 +440,7 @@ var Font = (function Font() {
// name ArialBlack for example will be replaced by Helvetica.
this.black = (name.search(/Black/g) != -1);
this.defaultWidth = properties.defaultWidth;
this.loadedName = fontName.split('-')[0];
this.loading = false;
return;
@ -472,6 +477,7 @@ var Font = (function Font() {
this.data = data;
this.type = properties.type;
this.textMatrix = properties.textMatrix;
this.defaultWidth = properties.defaultWidth;
this.loadedName = getUniqueName();
this.composite = properties.composite;
this.loading = true;
@ -534,6 +540,10 @@ var Font = (function Font() {
};
function createOpenTypeHeader(sfnt, file, numTables) {
// Windows hates the Mac TrueType sfnt version number
if (sfnt == 'true')
sfnt = string32(0x00010000);
// sfnt version (4 bytes)
var header = sfnt;
@ -587,19 +597,24 @@ var Font = (function Font() {
var codes = [];
var length = glyphs.length;
for (var n = 0; n < length; ++n)
codes.push(String.fromCharCode(glyphs[n].unicode));
codes.sort();
codes.push({ unicode: glyphs[n].unicode, code: n });
codes.sort(function(a, b) {
return a.unicode - b.unicode;
});
// Split the sorted codes into ranges.
var ranges = [];
for (var n = 0; n < length; ) {
var start = codes[n++].charCodeAt(0);
var start = codes[n].unicode;
var startCode = codes[n].code;
++n;
var end = start;
while (n < length && end + 1 == codes[n].charCodeAt(0)) {
while (n < length && end + 1 == codes[n].unicode) {
++end;
++n;
}
ranges.push([start, end]);
var endCode = codes[n - 1].code;
ranges.push([start, end, startCode, endCode]);
}
return ranges;
@ -628,22 +643,39 @@ var Font = (function Font() {
var idRangeOffsets = '';
var glyphsIds = '';
var bias = 0;
for (var i = 0; i < segCount - 1; i++) {
var range = ranges[i];
var start = range[0];
var end = range[1];
var offset = (segCount - i) * 2 + bias * 2;
bias += (end - start + 1);
startCount += string16(start);
endCount += string16(end);
idDeltas += string16(0);
idRangeOffsets += string16(offset);
if (deltas) {
for (var i = 0; i < segCount - 1; i++) {
var range = ranges[i];
var start = range[0];
var end = range[1];
var offset = (segCount - i) * 2 + bias * 2;
bias += (end - start + 1);
startCount += string16(start);
endCount += string16(end);
idDeltas += string16(0);
idRangeOffsets += string16(offset);
var startCode = range[2];
var endCode = range[3];
for (var j = startCode; j <= endCode; ++j)
glyphsIds += string16(deltas[j]);
}
} else {
for (var i = 0; i < segCount - 1; i++) {
var range = ranges[i];
var start = range[0];
var end = range[1];
var startCode = range[2];
startCount += string16(start);
endCount += string16(end);
idDeltas += string16((startCode - start + 1) & 0xFFFF);
idRangeOffsets += string16(0);
}
}
for (var i = 0; i < glyphs.length; i++)
glyphsIds += string16(deltas ? deltas[i] : i + 1);
endCount += '\xFF\xFF';
startCount += '\xFF\xFF';
idDeltas += '\x00\x01';
@ -663,7 +695,9 @@ var Font = (function Font() {
format314);
};
function createOS2Table(properties) {
function createOS2Table(properties, override) {
var override = override || {};
var ulUnicodeRange1 = 0;
var ulUnicodeRange2 = 0;
var ulUnicodeRange3 = 0;
@ -694,6 +728,24 @@ var Font = (function Font() {
}
}
var unitsPerEm = override.unitsPerEm || kPDFGlyphSpaceUnits;
var typoAscent = override.ascent || properties.ascent;
var typoDescent = override.descent || properties.descent;
var winAscent = override.yMax || typoAscent;
var winDescent = -override.yMin || -typoDescent;
// if there is a units per em value but no other override
// then scale the calculated ascent
if (unitsPerEm != kPDFGlyphSpaceUnits &&
'undefined' == typeof(override.ascent)) {
// if the font units differ to the PDF glyph space units
// then scale up the values
typoAscent = Math.round(typoAscent * unitsPerEm / kPDFGlyphSpaceUnits);
typoDescent = Math.round(typoDescent * unitsPerEm / kPDFGlyphSpaceUnits);
winAscent = typoAscent;
winDescent = -typoDescent;
}
return '\x00\x03' + // version
'\x02\x24' + // xAvgCharWidth
'\x01\xF4' + // usWeightClass
@ -722,11 +774,11 @@ var Font = (function Font() {
string16(firstCharIndex ||
properties.firstChar) + // usFirstCharIndex
string16(lastCharIndex || properties.lastChar) + // usLastCharIndex
string16(properties.ascent) + // sTypoAscender
string16(properties.descent) + // sTypoDescender
string16(typoAscent) + // sTypoAscender
string16(typoDescent) + // sTypoDescender
'\x00\x64' + // sTypoLineGap (7%-10% of the unitsPerEM value)
string16(properties.ascent) + // usWinAscent
string16(-properties.descent) + // usWinDescent
string16(winAscent) + // usWinAscent
string16(winDescent) + // usWinDescent
'\x00\x00\x00\x00' + // ulCodePageRange1 (Bits 0-31)
'\x00\x00\x00\x00' + // ulCodePageRange2 (Bits 32-63)
string16(properties.xHeight) + // sxHeight
@ -833,9 +885,11 @@ var Font = (function Font() {
var data = file.getBytes(length);
file.pos = previousPosition;
if (tag == 'head')
if (tag == 'head') {
// clearing checksum adjustment
data[8] = data[9] = data[10] = data[11] = 0;
data[17] |= 0x20; //Set font optimized for cleartype flag
}
return {
tag: tag,
@ -1001,6 +1055,49 @@ var Font = (function Font() {
}
};
function sanitizeGlyphLocations(loca, glyf, numGlyphs,
isGlyphLocationsLong) {
var itemSize, itemDecode, itemEncode;
if (isGlyphLocationsLong) {
itemSize = 4;
itemDecode = function(data, offset) {
return (data[offset] << 24) | (data[offset + 1] << 16) |
(data[offset + 2] << 8) | data[offset + 3];
};
itemEncode = function(data, offset, value) {
data[offset] = (value >>> 24) & 0xFF;
data[offset + 1] = (value >> 16) & 0xFF;
data[offset + 2] = (value >> 8) & 0xFF;
data[offset + 3] = value & 0xFF;
};
} else {
itemSize = 2;
itemDecode = function(data, offset) {
return (data[offset] << 9) | (data[offset + 1] << 1);
};
itemEncode = function(data, offset, value) {
data[offset] = (value >> 9) & 0xFF;
data[offset + 1] = (value >> 1) & 0xFF;
};
}
var locaData = loca.data;
var startOffset = itemDecode(locaData, 0);
var firstOffset = itemDecode(locaData, itemSize);
if (firstOffset - startOffset < 12 || startOffset > 0) {
// removing first glyph
glyf.data = glyf.data.subarray(firstOffset);
glyf.length -= firstOffset;
itemEncode(locaData, 0, 0);
var i, pos = itemSize;
for (i = 1; i <= numGlyphs; ++i) {
itemEncode(locaData, pos,
itemDecode(locaData, pos) - firstOffset);
pos += itemSize;
}
}
}
// Check that required tables are present
var requiredTables = ['OS/2', 'cmap', 'head', 'hhea',
'hmtx', 'maxp', 'name', 'post'];
@ -1008,7 +1105,7 @@ var Font = (function Font() {
var header = readOpenTypeHeader(font);
var numTables = header.numTables;
var cmap, maxp, hhea, hmtx, vhea, vmtx;
var cmap, maxp, hhea, hmtx, vhea, vmtx, head, loca, glyf;
var tables = [];
for (var i = 0; i < numTables; i++) {
var table = readTableEntry(font);
@ -1022,6 +1119,8 @@ var Font = (function Font() {
hhea = table;
else if (table.tag == 'hmtx')
hmtx = table;
else if (table.tag == 'head')
head = table;
requiredTables.splice(index, 1);
} else {
@ -1029,6 +1128,10 @@ var Font = (function Font() {
vmtx = table;
else if (table.tag == 'vhea')
vhea = table;
else if (table.tag == 'loca')
loca = table;
else if (table.tag == 'glyf')
glyf = table;
}
tables.push(table);
}
@ -1048,9 +1151,19 @@ var Font = (function Font() {
createOpenTypeHeader(header.version, ttf, numTables);
if (requiredTables.indexOf('OS/2') != -1) {
// extract some more font properties from the OpenType head and
// hhea tables; yMin and descent value are always negative
var override = {
unitsPerEm: int16([head.data[18], head.data[19]]),
yMax: int16([head.data[42], head.data[43]]),
yMin: int16([head.data[38], head.data[39]]) - 0x10000,
ascent: int16([hhea.data[4], hhea.data[5]]),
descent: int16([hhea.data[6], hhea.data[7]]) - 0x10000
};
tables.push({
tag: 'OS/2',
data: stringToArray(createOS2Table(properties))
data: stringToArray(createOS2Table(properties, override))
});
}
@ -1063,6 +1176,11 @@ var Font = (function Font() {
sanitizeMetrics(font, hhea, hmtx, numGlyphs);
sanitizeMetrics(font, vhea, vmtx, numGlyphs);
if (head && loca && glyf) {
var isGlyphLocationsLong = int16([head.data[50], head.data[51]]);
sanitizeGlyphLocations(loca, glyf, numGlyphs, isGlyphLocationsLong);
}
// Sanitizer reduces the glyph advanceWidth to the maxAdvanceWidth
// Sometimes it's 0. That needs to be fixed
if (hhea.data[10] == 0 && hhea.data[11] == 0) {
@ -1087,7 +1205,7 @@ var Font = (function Font() {
tables.push(cmap);
}
var encoding = properties.encoding;
var encoding = properties.encoding, i;
if (!encoding[0]) {
// the font is directly characters to glyphs with no encoding
// so create an identity encoding
@ -1095,18 +1213,28 @@ var Font = (function Font() {
for (i = 0; i < numGlyphs; i++) {
var width = widths[i];
encoding[i] = {
unicode: i + kCmapGlyphOffset,
unicode: i <= 0x1f || (i >= 127 && i <= 255) ?
i + kCmapGlyphOffset : i,
width: IsNum(width) ? width : properties.defaultWidth
};
}
} else {
for (var code in encoding)
encoding[code].unicode += kCmapGlyphOffset;
for (i in encoding) {
if (encoding.hasOwnProperty(i)) {
var unicode = encoding[i].unicode;
if (unicode <= 0x1f || (unicode >= 127 && unicode <= 255))
encoding[i].unicode = unicode += kCmapGlyphOffset;
}
}
}
var glyphs = [];
for (var i = 1; i < numGlyphs; i++)
glyphs.push({ unicode: i + kCmapGlyphOffset });
for (i = 1; i < numGlyphs; i++) {
glyphs.push({
unicode: i <= 0x1f || (i >= 127 && i <= 255) ?
i + kCmapGlyphOffset : i
});
}
cmap.data = createCMapTable(glyphs);
} else {
replaceCMapTable(cmap, font, properties);
@ -1292,7 +1420,8 @@ var Font = (function Font() {
var rule = "@font-face { font-family:'" + fontName + "';src:" + url + '}';
var styleSheet = document.styleSheets[0];
if (!styleSheet) {
document.documentElement.firstChild.appendChild( document.createElement('style') );
document.documentElement.firstChild.appendChild(
document.createElement('style'));
styleSheet = document.styleSheets[0];
}
styleSheet.insertRule(rule, styleSheet.cssRules.length);
@ -1300,15 +1429,15 @@ var Font = (function Font() {
return rule;
},
charsToUnicode: function fonts_chars2Unicode(chars) {
charsToGlyphs: function fonts_chars2Glyphs(chars) {
var charsCache = this.charsCache;
var str;
var glyphs;
// if we translated this string before, just grab it from the cache
if (charsCache) {
str = charsCache[chars];
if (str)
return str;
glyphs = charsCache[chars];
if (glyphs)
return glyphs;
}
// lazily create the translation cache
@ -1319,7 +1448,8 @@ var Font = (function Font() {
var encoding = this.encoding;
if (!encoding)
return chars;
str = '';
glyphs = [];
if (this.composite) {
// composite fonts have multi-byte strings convert the string from
@ -1330,38 +1460,39 @@ var Font = (function Font() {
// loop should never end on the last byte
for (var i = 0; i < length; i++) {
var charcode = int16([chars.charCodeAt(i++), chars.charCodeAt(i)]);
var unicode = encoding[charcode];
if ('undefined' == typeof(unicode)) {
var glyph = encoding[charcode];
if ('undefined' == typeof(glyph)) {
warn('Unencoded charcode ' + charcode);
unicode = charcode;
} else {
unicode = unicode.unicode;
glyph = {
unicode: charcode,
width: this.defaultWidth
};
}
str += String.fromCharCode(unicode);
glyphs.push(glyph);
// placing null after each word break charcode (ASCII SPACE)
if (charcode == 0x20)
glyphs.push(null);
}
}
else {
for (var i = 0; i < chars.length; ++i) {
var charcode = chars.charCodeAt(i);
var unicode = encoding[charcode];
if ('undefined' == typeof(unicode)) {
var glyph = encoding[charcode];
if ('undefined' == typeof(glyph)) {
warn('Unencoded charcode ' + charcode);
unicode = charcode;
} else {
unicode = unicode.unicode;
glyph = {
unicode: charcode,
width: this.defaultWidth
};
}
// Handle surrogate pairs
if (unicode > 0xFFFF) {
str += String.fromCharCode(unicode & 0xFFFF);
unicode >>= 16;
}
str += String.fromCharCode(unicode);
glyphs.push(glyph);
if (charcode == 0x20)
glyphs.push(null);
}
}
// Enter the translated string into the cache
return (charsCache[chars] = str);
return (charsCache[chars] = glyphs);
}
};

798
pdf.js
View File

@ -799,59 +799,58 @@ var PredictorStream = (function() {
prevRow = currentRow;
switch (predictor) {
case 0:
break;
case 1:
for (var i = 0; i < pixBytes; ++i)
currentRow[i] = rawBytes[i];
for (; i < rowBytes; ++i)
currentRow[i] = (currentRow[i - pixBytes] + rawBytes[i]) & 0xFF;
break;
case 2:
for (var i = 0; i < rowBytes; ++i)
currentRow[i] = (prevRow[i] + rawBytes[i]) & 0xFF;
break;
case 3:
for (var i = 0; i < pixBytes; ++i)
currentRow[i] = (prevRow[i] >> 1) + rawBytes[i];
for (; i < rowBytes; ++i) {
currentRow[i] = (((prevRow[i] + currentRow[i - pixBytes]) >> 1) +
rawBytes[i]) & 0xFF;
}
break;
case 4:
// we need to save the up left pixels values. the simplest way
// is to create a new buffer
for (var i = 0; i < pixBytes; ++i)
currentRow[i] = rawBytes[i];
for (; i < rowBytes; ++i) {
var up = prevRow[i];
var upLeft = prevRow[i - pixBytes];
var left = currentRow[i - pixBytes];
var p = left + up - upLeft;
case 0:
break;
case 1:
for (var i = 0; i < pixBytes; ++i)
currentRow[i] = rawBytes[i];
for (; i < rowBytes; ++i)
currentRow[i] = (currentRow[i - pixBytes] + rawBytes[i]) & 0xFF;
break;
case 2:
for (var i = 0; i < rowBytes; ++i)
currentRow[i] = (prevRow[i] + rawBytes[i]) & 0xFF;
break;
case 3:
for (var i = 0; i < pixBytes; ++i)
currentRow[i] = (prevRow[i] >> 1) + rawBytes[i];
for (; i < rowBytes; ++i) {
currentRow[i] = (((prevRow[i] + currentRow[i - pixBytes]) >> 1) +
rawBytes[i]) & 0xFF;
}
break;
case 4:
// we need to save the up left pixels values. the simplest way
// is to create a new buffer
for (var i = 0; i < pixBytes; ++i)
currentRow[i] = rawBytes[i];
for (; i < rowBytes; ++i) {
var up = prevRow[i];
var upLeft = prevRow[i - pixBytes];
var left = currentRow[i - pixBytes];
var p = left + up - upLeft;
var pa = p - left;
if (pa < 0)
pa = -pa;
var pb = p - up;
if (pb < 0)
pb = -pb;
var pc = p - upLeft;
if (pc < 0)
pc = -pc;
var pa = p - left;
if (pa < 0)
pa = -pa;
var pb = p - up;
if (pb < 0)
pb = -pb;
var pc = p - upLeft;
if (pc < 0)
pc = -pc;
var c = rawBytes[i];
if (pa <= pb && pa <= pc)
currentRow[i] = left + c;
else if (pb <= pc)
currentRow[i] = up + c;
else
currentRow[i] = upLeft + c;
}
break;
default:
error('Unsupported predictor: ' + predictor);
break;
var c = rawBytes[i];
if (pa <= pb && pa <= pc)
currentRow[i] = left + c;
else if (pb <= pc)
currentRow[i] = up + c;
else
currentRow[i] = upLeft + c;
}
break;
default:
error('Unsupported predictor: ' + predictor);
}
this.bufferLength += rowBytes;
};
@ -1695,129 +1694,128 @@ var CCITTFaxStream = (function() {
while (codingLine[this.codingPos] < columns) {
code1 = this.getTwoDimCode();
switch (code1) {
case twoDimPass:
this.addPixels(refLine[refPos + 1], blackPixels);
if (refLine[refPos + 1] < columns)
refPos += 2;
break;
case twoDimHoriz:
code1 = code2 = 0;
if (blackPixels) {
do {
code1 += (code3 = this.getBlackCode());
} while (code3 >= 64);
do {
code2 += (code3 = this.getWhiteCode());
} while (code3 >= 64);
} else {
do {
code1 += (code3 = this.getWhiteCode());
} while (code3 >= 64);
do {
code2 += (code3 = this.getBlackCode());
} while (code3 >= 64);
}
this.addPixels(codingLine[this.codingPos] +
code1, blackPixels);
if (codingLine[this.codingPos] < columns) {
this.addPixels(codingLine[this.codingPos] + code2,
blackPixels ^ 1);
}
while (refLine[refPos] <= codingLine[this.codingPos] &&
refLine[refPos] < columns) {
refPos += 2;
}
break;
case twoDimVertR3:
this.addPixels(refLine[refPos] + 3, blackPixels);
blackPixels ^= 1;
if (codingLine[this.codingPos] < columns) {
++refPos;
while (refLine[refPos] <= codingLine[this.codingPos] &&
refLine[refPos] < columns)
case twoDimPass:
this.addPixels(refLine[refPos + 1], blackPixels);
if (refLine[refPos + 1] < columns)
refPos += 2;
}
break;
case twoDimVertR2:
this.addPixels(refLine[refPos] + 2, blackPixels);
blackPixels ^= 1;
if (codingLine[this.codingPos] < columns) {
++refPos;
break;
case twoDimHoriz:
code1 = code2 = 0;
if (blackPixels) {
do {
code1 += (code3 = this.getBlackCode());
} while (code3 >= 64);
do {
code2 += (code3 = this.getWhiteCode());
} while (code3 >= 64);
} else {
do {
code1 += (code3 = this.getWhiteCode());
} while (code3 >= 64);
do {
code2 += (code3 = this.getBlackCode());
} while (code3 >= 64);
}
this.addPixels(codingLine[this.codingPos] +
code1, blackPixels);
if (codingLine[this.codingPos] < columns) {
this.addPixels(codingLine[this.codingPos] + code2,
blackPixels ^ 1);
}
while (refLine[refPos] <= codingLine[this.codingPos] &&
refLine[refPos] < columns) {
refPos += 2;
}
}
break;
case twoDimVertR1:
this.addPixels(refLine[refPos] + 1, blackPixels);
blackPixels ^= 1;
if (codingLine[this.codingPos] < columns) {
++refPos;
while (refLine[refPos] <= codingLine[this.codingPos] &&
refLine[refPos] < columns)
refPos += 2;
}
break;
case twoDimVert0:
this.addPixels(refLine[refPos], blackPixels);
blackPixels ^= 1;
if (codingLine[this.codingPos] < columns) {
++refPos;
while (refLine[refPos] <= codingLine[this.codingPos] &&
refLine[refPos] < columns)
refPos += 2;
}
break;
case twoDimVertL3:
this.addPixelsNeg(refLine[refPos] - 3, blackPixels);
blackPixels ^= 1;
if (codingLine[this.codingPos] < columns) {
if (refPos > 0)
--refPos;
else
break;
case twoDimVertR3:
this.addPixels(refLine[refPos] + 3, blackPixels);
blackPixels ^= 1;
if (codingLine[this.codingPos] < columns) {
++refPos;
while (refLine[refPos] <= codingLine[this.codingPos] &&
refLine[refPos] < columns)
refPos += 2;
}
break;
case twoDimVertL2:
this.addPixelsNeg(refLine[refPos] - 2, blackPixels);
blackPixels ^= 1;
if (codingLine[this.codingPos] < columns) {
if (refPos > 0)
--refPos;
else
while (refLine[refPos] <= codingLine[this.codingPos] &&
refLine[refPos] < columns)
refPos += 2;
}
break;
case twoDimVertR2:
this.addPixels(refLine[refPos] + 2, blackPixels);
blackPixels ^= 1;
if (codingLine[this.codingPos] < columns) {
++refPos;
while (refLine[refPos] <= codingLine[this.codingPos] &&
refLine[refPos] < columns)
refPos += 2;
}
break;
case twoDimVertL1:
this.addPixelsNeg(refLine[refPos] - 1, blackPixels);
blackPixels ^= 1;
if (codingLine[this.codingPos] < columns) {
if (refPos > 0)
--refPos;
else
while (refLine[refPos] <= codingLine[this.codingPos] &&
refLine[refPos] < columns) {
refPos += 2;
}
}
break;
case twoDimVertR1:
this.addPixels(refLine[refPos] + 1, blackPixels);
blackPixels ^= 1;
if (codingLine[this.codingPos] < columns) {
++refPos;
while (refLine[refPos] <= codingLine[this.codingPos] &&
refLine[refPos] < columns)
refPos += 2;
}
break;
case twoDimVert0:
this.addPixels(refLine[refPos], blackPixels);
blackPixels ^= 1;
if (codingLine[this.codingPos] < columns) {
++refPos;
while (refLine[refPos] <= codingLine[this.codingPos] &&
refLine[refPos] < columns)
refPos += 2;
}
break;
case twoDimVertL3:
this.addPixelsNeg(refLine[refPos] - 3, blackPixels);
blackPixels ^= 1;
if (codingLine[this.codingPos] < columns) {
if (refPos > 0)
--refPos;
else
++refPos;
while (refLine[refPos] <= codingLine[this.codingPos] &&
refLine[refPos] < columns)
refPos += 2;
}
break;
case twoDimVertL2:
this.addPixelsNeg(refLine[refPos] - 2, blackPixels);
blackPixels ^= 1;
if (codingLine[this.codingPos] < columns) {
if (refPos > 0)
--refPos;
else
++refPos;
while (refLine[refPos] <= codingLine[this.codingPos] &&
refLine[refPos] < columns)
refPos += 2;
}
break;
case twoDimVertL1:
this.addPixelsNeg(refLine[refPos] - 1, blackPixels);
blackPixels ^= 1;
if (codingLine[this.codingPos] < columns) {
if (refPos > 0)
--refPos;
else
++refPos;
while (refLine[refPos] <= codingLine[this.codingPos] &&
refLine[refPos] < columns)
refPos += 2;
}
break;
case EOF:
this.addPixels(columns, 0);
this.eof = true;
break;
default:
warn('bad 2d code');
this.addPixels(columns, 0);
this.err = true;
break;
while (refLine[refPos] <= codingLine[this.codingPos] &&
refLine[refPos] < columns)
refPos += 2;
}
break;
case EOF:
this.addPixels(columns, 0);
this.eof = true;
break;
default:
warn('bad 2d code');
this.addPixels(columns, 0);
this.err = true;
}
}
} else {
@ -2488,79 +2486,77 @@ var Lexer = (function() {
do {
ch = stream.getChar();
switch (ch) {
case undefined:
warn('Unterminated string');
done = true;
break;
case '(':
++numParen;
str += ch;
break;
case ')':
if (--numParen == 0) {
done = true;
} else {
str += ch;
}
break;
case '\\':
ch = stream.getChar();
switch (ch) {
case undefined:
warn('Unterminated string');
done = true;
break;
case 'n':
str += '\n';
break;
case 'r':
str += '\r';
break;
case 't':
str += '\t';
break;
case 'b':
str += '\b';
break;
case 'f':
str += '\f';
break;
case '\\':
case '(':
case ')':
++numParen;
str += ch;
break;
case '0': case '1': case '2': case '3':
case '4': case '5': case '6': case '7':
var x = ch - '0';
ch = stream.lookChar();
if (ch >= '0' && ch <= '7') {
stream.skip();
x = (x << 3) + (ch - '0');
ch = stream.lookChar();
if (ch >= '0' && ch <= '7') {
stream.skip();
x = (x << 3) + (ch - '0');
}
case ')':
if (--numParen == 0) {
done = true;
} else {
str += ch;
}
break;
case '\\':
ch = stream.getChar();
switch (ch) {
case undefined:
warn('Unterminated string');
done = true;
break;
case 'n':
str += '\n';
break;
case 'r':
str += '\r';
break;
case 't':
str += '\t';
break;
case 'b':
str += '\b';
break;
case 'f':
str += '\f';
break;
case '\\':
case '(':
case ')':
str += ch;
break;
case '0': case '1': case '2': case '3':
case '4': case '5': case '6': case '7':
var x = ch - '0';
ch = stream.lookChar();
if (ch >= '0' && ch <= '7') {
stream.skip();
x = (x << 3) + (ch - '0');
ch = stream.lookChar();
if (ch >= '0' && ch <= '7') {
stream.skip();
x = (x << 3) + (ch - '0');
}
}
str += String.fromCharCode(x);
break;
case '\r':
ch = stream.lookChar();
if (ch == '\n')
stream.skip();
break;
case '\n':
str += String.fromCharCode(x);
break;
case '\r':
ch = stream.lookChar();
if (ch == '\n')
stream.skip();
break;
case '\n':
break;
default:
str += ch;
}
break;
default:
str += ch;
break;
}
break;
default:
str += ch;
break;
}
} while (!done);
return str;
@ -2641,41 +2637,41 @@ var Lexer = (function() {
// start reading token
switch (ch) {
case '0': case '1': case '2': case '3': case '4':
case '5': case '6': case '7': case '8': case '9':
case '+': case '-': case '.':
return this.getNumber(ch);
case '(':
return this.getString();
case '/':
return this.getName(ch);
// array punctuation
case '[':
case ']':
return new Cmd(ch);
// hex string or dict punctuation
case '<':
ch = stream.lookChar();
if (ch == '<') {
case '0': case '1': case '2': case '3': case '4':
case '5': case '6': case '7': case '8': case '9':
case '+': case '-': case '.':
return this.getNumber(ch);
case '(':
return this.getString();
case '/':
return this.getName(ch);
// array punctuation
case '[':
case ']':
return new Cmd(ch);
// hex string or dict punctuation
case '<':
ch = stream.lookChar();
if (ch == '<') {
// dict punctuation
stream.skip();
return new Cmd('<<');
}
return this.getHexString(ch);
// dict punctuation
stream.skip();
return new Cmd('<<');
}
return this.getHexString(ch);
// dict punctuation
case '>':
ch = stream.lookChar();
if (ch == '>') {
stream.skip();
return new Cmd('>>');
}
case '{':
case '}':
return new Cmd(ch);
// fall through
case ')':
error('Illegal character: ' + ch);
return Error;
case '>':
ch = stream.lookChar();
if (ch == '>') {
stream.skip();
return new Cmd('>>');
}
case '{':
case '}':
return new Cmd(ch);
// fall through
case ')':
error('Illegal character: ' + ch);
return Error;
}
// command
@ -3139,17 +3135,16 @@ var XRef = (function() {
entry.offset = offset;
entry.gen = generation;
switch (type) {
case 0:
entry.free = true;
break;
case 1:
entry.uncompressed = true;
break;
case 2:
break;
default:
error('Invalid XRef entry type: ' + type);
break;
case 0:
entry.free = true;
break;
case 1:
entry.uncompressed = true;
break;
case 2:
break;
default:
error('Invalid XRef entry type: ' + type);
}
if (!this.entries[first + i])
this.entries[first + i] = entry;
@ -3224,7 +3219,14 @@ var XRef = (function() {
error('bad XRef entry');
}
if (this.encrypt && !suppressEncryption) {
e = parser.getObj(this.encrypt.createCipherTransform(num, gen));
try {
e = parser.getObj(this.encrypt.createCipherTransform(num, gen));
} catch (ex) {
// almost all streams must be encrypted, but sometimes
// they are not probably due to some broken generators
// re-trying without encryption
return this.fetch(ref, true);
}
} else {
e = parser.getObj();
}
@ -3476,7 +3478,6 @@ var Page = (function() {
break;
default:
TODO('other link types');
break;
}
} else if (annotation.has('Dest')) {
// simple destination link
@ -3706,7 +3707,9 @@ var PDFDoc = (function() {
if (find(stream, 'startxref', 1024, true)) {
stream.skip(9);
var ch;
while (Lexer.isSpace(ch = stream.getChar())) {}
do {
ch = stream.getChar();
} while (Lexer.isSpace(ch));
var str = '';
while ((ch - '0') <= 9) {
str += ch;
@ -4294,7 +4297,6 @@ var PartialEvaluator = (function() {
};
}
} else if (type == 'CIDFontType0') {
encoding = xref.fetchIfRef(dict.get('Encoding'));
if (IsName(encoding)) {
// Encoding is a predefined CMap
if (encoding.name == 'Identity-H') {
@ -4347,19 +4349,25 @@ var PartialEvaluator = (function() {
baseEncoding = Encodings.StandardEncoding.slice();
break;
default:
warn('Unknown type of font: ' + type);
break;
warn('Unknown type of font: ' + type);
}
}
// merge in the differences
var firstChar = properties.firstChar;
var lastChar = properties.lastChar;
var widths = properties.widths || [];
var glyphs = {};
for (var i = firstChar; i <= lastChar; i++) {
var glyph = differences[i] || baseEncoding[i];
var glyph = differences[i];
if (!glyph) {
glyph = baseEncoding[i];
// skipping already specified by difference glyphs
if (differences.indexOf(glyph) >= 0)
continue;
}
var index = GlyphsUnicode[glyph] || i;
var width = properties.widths[i] || properties.widths[glyph];
var width = widths[i] || widths[glyph];
map[i] = {
unicode: index,
width: IsNum(width) ? width : properties.defaultWidth
@ -4381,7 +4389,7 @@ var PartialEvaluator = (function() {
if (type == 'TrueType' && dict.has('ToUnicode') && differences) {
var cmapObj = dict.get('ToUnicode');
if (IsRef(cmapObj)) {
cmapObj = xref.fetch(cmapObj, true);
cmapObj = xref.fetch(cmapObj);
}
if (IsName(cmapObj)) {
error('ToUnicode file cmap translation not implemented');
@ -4442,7 +4450,6 @@ var PartialEvaluator = (function() {
token = parseInt(token, 10); // a number
tokens.push(token);
token = '';
break;
}
switch (byte) {
case 0x5B:
@ -4453,9 +4460,9 @@ var PartialEvaluator = (function() {
// collect array items
var items = [], item;
while (tokens.length &&
(item = tokens.pop()) != beginArrayToken)
items.unshift(item);
tokens.push(items);
(item = tokens.pop()) != beginArrayToken)
items.unshift(item);
tokens.push(items);
break;
}
} else if (byte == 0x3E) {
@ -4530,7 +4537,7 @@ var PartialEvaluator = (function() {
type: type.name,
encoding: map,
differences: [],
widths: widths,
widths: widths || {},
defaultWidth: defaultWidth,
firstChar: 0,
lastChar: 256
@ -4921,7 +4928,7 @@ var CanvasGraphics = (function() {
font = font.get(fontRef.name);
font = this.xref.fetchIfRef(font);
if (!font)
return;
error('Referenced font is not found');
var fontObj = font.fontObj;
this.current.font = fontObj;
@ -4973,21 +4980,15 @@ var CanvasGraphics = (function() {
showText: function(text) {
var ctx = this.ctx;
var current = this.current;
var originalText = text;
var font = current.font;
ctx.save();
ctx.transform.apply(ctx, current.textMatrix);
ctx.scale(1, -1);
ctx.translate(current.x, -1 * current.y);
ctx.transform.apply(ctx, font.textMatrix || IDENTITY_MATRIX);
var font = current.font;
if (font) {
ctx.transform.apply(ctx, font.textMatrix || IDENTITY_MATRIX);
text = font.charsToUnicode(text);
}
var composite = font.composite;
var glyphs = font.charsToGlyphs(text);
var fontSize = current.fontSize;
var charSpacing = current.charSpacing;
var wordSpacing = current.wordSpacing;
@ -4995,24 +4996,23 @@ var CanvasGraphics = (function() {
ctx.scale(1 / textHScale, 1);
var width = 0;
for (var i = 0; i < text.length; i++) {
if (composite) {
var position = i * 2 + 1;
var charcode = (originalText.charCodeAt(position - 1) << 8) +
originalText.charCodeAt(position);
} else {
var charcode = originalText.charCodeAt(i);
for (var i = 0; i < glyphs.length; i++) {
var glyph = glyphs[i];
if (glyph === null) {
// word break
width += wordSpacing;
continue;
}
var encoding = font.encoding[charcode];
var charWidth = (encoding ? encoding.width : font.defaultWidth);
charWidth *= (fontSize * 0.001);
charWidth += charSpacing;
if (charcode == 32)
charWidth += wordSpacing;
var unicode = glyph.unicode;
var char = unicode >= 0x10000 ?
String.fromCharCode(0xD800 | ((unicode - 0x10000) >> 10),
0xDC00 | (unicode & 0x3FF)) : String.fromCharCode(unicode);
ctx.fillText(text.charAt(i), 0, 0);
ctx.translate(charWidth, 0);
var charWidth = glyph.width * fontSize * 0.001;
charWidth += charSpacing;
ctx.fillText(char, width, 0);
width += charWidth;
}
current.x += width;
@ -5399,67 +5399,67 @@ var ColorSpace = (function() {
this.mode = mode;
switch (mode) {
case 'DeviceGray':
case 'G':
return new DeviceGrayCS();
case 'DeviceRGB':
case 'RGB':
return new DeviceRgbCS();
case 'DeviceCMYK':
case 'CMYK':
return new DeviceCmykCS();
case 'Pattern':
return new PatternCS(null);
default:
error('unrecognized colorspace ' + mode);
case 'DeviceGray':
case 'G':
return new DeviceGrayCS();
case 'DeviceRGB':
case 'RGB':
return new DeviceRgbCS();
case 'DeviceCMYK':
case 'CMYK':
return new DeviceCmykCS();
case 'Pattern':
return new PatternCS(null);
default:
error('unrecognized colorspace ' + mode);
}
} else if (IsArray(cs)) {
var mode = cs[0].name;
this.mode = mode;
switch (mode) {
case 'DeviceGray':
case 'G':
return new DeviceGrayCS();
case 'DeviceRGB':
case 'RGB':
return new DeviceRgbCS();
case 'DeviceCMYK':
case 'CMYK':
return new DeviceCmykCS();
case 'CalGray':
return new DeviceGrayCS();
case 'CalRGB':
return new DeviceRgbCS();
case 'ICCBased':
var stream = xref.fetchIfRef(cs[1]);
var dict = stream.dict;
var numComps = dict.get('N');
if (numComps == 1)
case 'DeviceGray':
case 'G':
return new DeviceGrayCS();
if (numComps == 3)
case 'DeviceRGB':
case 'RGB':
return new DeviceRgbCS();
if (numComps == 4)
case 'DeviceCMYK':
case 'CMYK':
return new DeviceCmykCS();
break;
case 'Pattern':
var baseCS = cs[1];
if (baseCS)
baseCS = ColorSpace.parse(baseCS, xref, res);
return new PatternCS(baseCS);
case 'Indexed':
var base = ColorSpace.parse(cs[1], xref, res);
var hiVal = cs[2] + 1;
var lookup = xref.fetchIfRef(cs[3]);
return new IndexedCS(base, hiVal, lookup);
case 'Separation':
var alt = ColorSpace.parse(cs[2], xref, res);
var tintFn = new PDFFunction(xref, xref.fetchIfRef(cs[3]));
return new SeparationCS(alt, tintFn);
case 'Lab':
case 'DeviceN':
default:
error('unimplemented color space object "' + mode + '"');
case 'CalGray':
return new DeviceGrayCS();
case 'CalRGB':
return new DeviceRgbCS();
case 'ICCBased':
var stream = xref.fetchIfRef(cs[1]);
var dict = stream.dict;
var numComps = dict.get('N');
if (numComps == 1)
return new DeviceGrayCS();
if (numComps == 3)
return new DeviceRgbCS();
if (numComps == 4)
return new DeviceCmykCS();
break;
case 'Pattern':
var baseCS = cs[1];
if (baseCS)
baseCS = ColorSpace.parse(baseCS, xref, res);
return new PatternCS(baseCS);
case 'Indexed':
var base = ColorSpace.parse(cs[1], xref, res);
var hiVal = cs[2] + 1;
var lookup = xref.fetchIfRef(cs[3]);
return new IndexedCS(base, hiVal, lookup);
case 'Separation':
var alt = ColorSpace.parse(cs[2], xref, res);
var tintFn = new PDFFunction(xref, xref.fetchIfRef(cs[3]));
return new SeparationCS(alt, tintFn);
case 'Lab':
case 'DeviceN':
default:
error('unimplemented color space object "' + mode + '"');
}
} else {
error('unrecognized color space object: "' + cs + '"');
@ -5742,26 +5742,26 @@ var Pattern = (function() {
var typeNum = dict.get('PatternType');
switch (typeNum) {
case 1:
var base = cs.base;
var color;
if (base) {
var baseComps = base.numComps;
case 1:
var base = cs.base;
var color;
if (base) {
var baseComps = base.numComps;
color = [];
for (var i = 0; i < baseComps; ++i)
color.push(args[i]);
color = [];
for (var i = 0; i < baseComps; ++i)
color.push(args[i]);
color = base.getRgb(color);
}
var code = patternName.code;
return new TilingPattern(pattern, code, dict, color, xref, ctx);
case 2:
var shading = xref.fetchIfRef(dict.get('Shading'));
var matrix = dict.get('Matrix');
return Pattern.parseShading(shading, matrix, xref, res, ctx);
default:
error('Unknown type of pattern: ' + typeNum);
color = base.getRgb(color);
}
var code = patternName.code;
return new TilingPattern(pattern, code, dict, color, xref, ctx);
case 2:
var shading = xref.fetchIfRef(dict.get('Shading'));
var matrix = dict.get('Matrix');
return Pattern.parseShading(shading, matrix, xref, res, ctx);
default:
error('Unknown type of pattern: ' + typeNum);
}
return null;
};
@ -5773,12 +5773,12 @@ var Pattern = (function() {
var type = dict.get('ShadingType');
switch (type) {
case 2:
case 3:
// both radial and axial shadings are handled by RadialAxial shading
return new RadialAxialShading(dict, matrix, xref, res, ctx);
default:
return new DummyShading();
case 2:
case 3:
// both radial and axial shadings are handled by RadialAxial shading
return new RadialAxialShading(dict, matrix, xref, res, ctx);
default:
return new DummyShading();
}
};
return constructor;
@ -5959,17 +5959,17 @@ var TilingPattern = (function() {
var paintType = dict.get('PaintType');
switch (paintType) {
case PAINT_TYPE_COLORED:
tmpCtx.fillStyle = ctx.fillStyle;
tmpCtx.strokeStyle = ctx.strokeStyle;
break;
case PAINT_TYPE_UNCOLORED:
color = Util.makeCssRgb.apply(this, color);
tmpCtx.fillStyle = color;
tmpCtx.strokeStyle = color;
break;
default:
error('Unsupported paint type: ' + paintType);
case PAINT_TYPE_COLORED:
tmpCtx.fillStyle = ctx.fillStyle;
tmpCtx.strokeStyle = ctx.strokeStyle;
break;
case PAINT_TYPE_UNCOLORED:
color = Util.makeCssRgb.apply(this, color);
tmpCtx.fillStyle = color;
tmpCtx.strokeStyle = color;
break;
default:
error('Unsupported paint type: ' + paintType);
}
var scale = [width / xstep, height / ystep];
@ -6062,8 +6062,8 @@ var PDFImage = (function() {
this.decode = dict.get('Decode', 'D');
var mask = xref.fetchIfRef(image.dict.get('Mask'));
var smask = xref.fetchIfRef(image.dict.get('SMask'));
var mask = xref.fetchIfRef(dict.get('Mask'));
var smask = xref.fetchIfRef(dict.get('SMask'));
if (mask) {
TODO('masked images');

View File

@ -8,6 +8,7 @@
'use strict';
var appPath, browser, canvas, currentTaskIdx, manifest, stdout;
var inFlightRequests = 0;
function queryParams() {
var qs = window.location.search.substring(1);
@ -42,12 +43,12 @@ function load() {
if (r.readyState == 4) {
log('done\n');
manifest = JSON.parse(r.responseText);
currentTaskIdx = 0, nextTask();
currentTaskIdx = 0;
nextTask();
}
};
r.send(null);
}
window.onload = load;
function nextTask() {
if (currentTaskIdx == manifest.length) {
@ -73,7 +74,8 @@ function nextTask() {
failure = 'load PDF doc : ' + e.toString();
}
task.pageNum = 1, nextPage(task, failure);
task.pageNum = 1;
nextPage(task, failure);
}
};
r.send(null);
@ -89,7 +91,8 @@ function nextPage(task, loadError) {
if (!task.pdfDoc) {
sendTaskResult(canvas.toDataURL('image/png'), task, failure);
log('done' + (failure ? ' (failed !: ' + failure + ')' : '') + '\n');
++currentTaskIdx, nextTask();
++currentTaskIdx;
nextTask();
return;
}
@ -98,7 +101,8 @@ function nextPage(task, loadError) {
log(' Round ' + (1 + task.round) + '\n');
task.pageNum = 1;
} else {
++currentTaskIdx, nextTask();
++currentTaskIdx;
nextTask();
return;
}
}
@ -123,7 +127,7 @@ function nextPage(task, loadError) {
page.startRendering(
ctx,
function(e) {
snapshotCurrentPage(page, task, (!failure && e) ?
snapshotCurrentPage(task, (!failure && e) ?
('render : ' + e) : failure);
}
);
@ -135,11 +139,11 @@ function nextPage(task, loadError) {
if (failure) {
// Skip right to snapshotting if there was a failure, since the
// fonts might be in an inconsistent state.
snapshotCurrentPage(page, task, failure);
snapshotCurrentPage(task, failure);
}
}
function snapshotCurrentPage(page, task, failure) {
function snapshotCurrentPage(task, failure) {
log('done, snapshotting... ');
sendTaskResult(canvas.toDataURL('image/png'), task, failure);
@ -149,7 +153,8 @@ function snapshotCurrentPage(page, task, failure) {
var backoff = (inFlightRequests > 0) ? inFlightRequests * 10 : 0;
setTimeout(
function() {
++task.pageNum, nextPage(task);
++task.pageNum;
nextPage(task);
},
backoff
);
@ -182,7 +187,6 @@ function done() {
}
}
var inFlightRequests = 0;
function sendTaskResult(snapshot, task, failure) {
var result = { browser: browser,
id: task.id,
@ -201,7 +205,7 @@ function sendTaskResult(snapshot, task, failure) {
if (r.readyState == 4) {
inFlightRequests--;
}
}
};
document.getElementById('inFlightCount').innerHTML = inFlightRequests++;
r.send(JSON.stringify(result));
}

12
test/pdfs/.gitignore vendored
View File

@ -1,3 +1,13 @@
pdf.pdf
DiwanProfile.pdf
artofwar.pdf
cable.pdf
ecma262.pdf
hmm.pdf
i9.pdf
intelisa.pdf
openweb_tm-PRINT.pdf
pdf.pdf
pdkids.pdf
shavian.pdf
jai.pdf

View File

@ -0,0 +1 @@
http://csrc.nist.gov/publications/fips/fips197/fips-197.pdf

View File

@ -0,0 +1 @@
http://www.airgid.com/book/wdsg_fitc.pdf

View File

@ -0,0 +1 @@
http://www.cdc.gov/ncidod/dvbid/westnile/languages/chinese.pdf

View File

@ -0,0 +1,2 @@
browser_manifest.json

View File

@ -69,6 +69,12 @@
"rounds": 1,
"type": "load"
},
{ "id": "wnv_chinese-pdf",
"file": "pdfs/wnv_chinese.pdf",
"link": true,
"rounds": 1,
"type": "eq"
},
{ "id": "i9-pdf",
"file": "pdfs/i9.pdf",
"link": true,
@ -115,5 +121,17 @@
"link": true,
"rounds": 1,
"type": "eq"
},
{ "id": "wdsg_fitc",
"file": "pdfs/wdsg_fitc.pdf",
"link": true,
"rounds": 1,
"type": "eq"
},
{ "id": "fips197",
"file": "pdfs/fips197.pdf",
"link": true,
"rounds": 1,
"type": "load"
}
]

View File

@ -82,7 +82,7 @@ function readCharstringEncoding(aString) {
} else if (value <= 31) {
token = CFFEncodingMap[value];
} else if (value < 247) {
token = parseInt(value) - 139;
token = parseInt(value, 10) - 139;
} else if (value < 251) {
token = (value - 247) * 256 + aString[i++] + 108;
} else if (value < 255) {
@ -126,7 +126,7 @@ function readFontDictData(aString, aMap) {
while (!parsed) {
var byte = aString[i++];
var nibbles = [parseInt(byte / 16), parseInt(byte % 16)];
var nibbles = [parseInt(byte / 16, 10), parseInt(byte % 16, 10)];
for (var j = 0; j < nibbles.length; j++) {
var nibble = nibbles[j];
switch (nibble) {
@ -157,7 +157,7 @@ function readFontDictData(aString, aMap) {
} else if (value <= 31) {
token = aMap[value];
} else if (value <= 246) {
token = parseInt(value) - 139;
token = parseInt(value, 10) - 139;
} else if (value <= 250) {
token = (value - 247) * 256 + aString[i++] + 108;
} else if (value <= 254) {
@ -206,7 +206,7 @@ function readFontIndexData(aStream, aIsByte) {
}
error(offsize + ' is not a valid offset size');
return null;
};
}
var offsets = [];
for (var i = 0; i < count + 1; i++)
@ -249,7 +249,7 @@ var Type2Parser = function(aFilePath) {
function dump(aStr) {
if (debug)
log(aStr);
};
}
function parseAsToken(aString, aMap) {
var decoded = readFontDictData(aString, aMap);
@ -290,7 +290,7 @@ var Type2Parser = function(aFilePath) {
}
}
}
};
}
this.parse = function(aStream) {
font.set('major', aStream.getByte());
@ -363,7 +363,7 @@ var Type2Parser = function(aFilePath) {
aStream.pos = charsetEntry;
var charset = readCharset(aStream, charStrings);
}
}
};
};
/*

19
web/compatibility.js Executable file → Normal file
View File

@ -1,6 +1,8 @@
/* -*- Mode: Java; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
/* vim: set shiftwidth=2 tabstop=2 autoindent cindent expandtab: */
'use strict';
// Checking if the typed arrays are supported
(function() {
if (typeof Uint8Array !== 'undefined')
@ -10,8 +12,9 @@
return this.slice(start, end);
}
function set_(array, offset) {
if (arguments.length < 2) offset = 0;
function set_function(array, offset) {
if (arguments.length < 2)
offset = 0;
for (var i = 0, n = array.length; i < n; ++i, ++offset)
this[offset] = array[i] & 0xFF;
}
@ -19,15 +22,17 @@
function TypedArray(arg1) {
var result;
if (typeof arg1 === 'number') {
result = new Array(arg1);
for (var i = 0; i < arg1; ++i)
result[i] = 0;
result = [];
for (var i = 0; i < arg1; ++i)
result[i] = 0;
} else
result = arg1.slice(0);
result = arg1.slice(0);
result.subarray = subarray;
result.buffer = result;
result.byteLength = result.length;
result.set = set_;
result.set = set_function;
if (typeof arg1 === 'object' && arg1.buffer)
result.buffer = arg1.buffer;

0
web/viewer.css Executable file → Normal file
View File

View File

@ -98,7 +98,7 @@ var PDFView = {
},
get page() {
return parseInt(document.location.hash.substring(1)) || 1;
return parseInt(document.location.hash.substring(1), 10) || 1;
},
open: function(url, scale) {
@ -170,7 +170,7 @@ var PDFView = {
}
this.setScale(scale || kDefaultScale, true);
this.page = parseInt(document.location.hash.substring(1)) || 1;
this.page = parseInt(document.location.hash.substring(1), 10) || 1;
this.pagesRefMap = pagesRefMap;
this.destinations = pdf.catalog.destinations;
if (pdf.catalog.documentOutline) {
@ -209,7 +209,7 @@ var PDFView = {
var currentHeight = kBottomMargin;
var windowTop = window.pageYOffset;
for (var i = 1; i <= pages.length; i++) {
for (var i = 1; i <= pages.length; ++i) {
var page = pages[i - 1];
var pageHeight = page.height * page.scale + kBottomMargin;
if (currentHeight + pageHeight > windowTop)
@ -219,10 +219,11 @@ var PDFView = {
}
var windowBottom = window.pageYOffset + window.innerHeight;
for (; i <= pages.length && currentHeight < windowBottom; i++) {
var page = pages[i - 1];
visiblePages.push({ id: page.id, y: currentHeight, view: page });
currentHeight += page.height * page.scale + kBottomMargin;
for (; i <= pages.length && currentHeight < windowBottom; ++i) {
var singlePage = pages[i - 1];
visiblePages.push({ id: singlePage.id, y: currentHeight,
view: singlePage });
currentHeight += singlePage.height * singlePage.scale + kBottomMargin;
}
return visiblePages;
@ -256,13 +257,13 @@ var PageView = function(container, content, id, width, height,
div.removeAttribute('data-loaded');
};
function setupLinks(canvas, content, scale) {
function setupLinks(content, scale) {
function bindLink(link, dest) {
link.onclick = function() {
if (dest)
PDFView.navigateTo(dest);
return false;
}
};
}
var links = content.getLinks();
for (var i = 0; i < links.length; i++) {
@ -283,8 +284,6 @@ var PageView = function(container, content, id, width, height,
var width = 0, height = 0, widthScale, heightScale;
var scale = 0;
switch (dest[1].name) {
default:
return;
case 'XYZ':
x = dest[2];
y = dest[3];
@ -315,6 +314,8 @@ var PageView = function(container, content, id, width, height,
height / kCssUnits;
scale = Math.min(widthScale, heightScale);
break;
default:
return;
}
var boundingRect = [
@ -369,7 +370,7 @@ var PageView = function(container, content, id, width, height,
stats.begin = Date.now();
this.content.startRendering(ctx, this.updateStats);
setupLinks(canvas, this.content, this.scale);
setupLinks(this.content, this.scale);
div.setAttribute('data-loaded', true);
return true;

View File

@ -39,7 +39,7 @@ function GradientProxy(cmdQueue, x0, y0, x1, y1) {
cmdQueue.push(['$createLinearGradient', [x0, y0, x1, y1]]);
this.addColorStop = function(i, rgba) {
cmdQueue.push(['$addColorStop', [i, rgba]]);
}
};
}
// Really simple PatternProxy.
@ -72,7 +72,7 @@ function CanvasProxy(width, height) {
throw 'CanvasProxy can only provide a 2d context.';
}
return ctx;
}
};
// Expose only the minimum of the canvas object - there is no dom to do
// more here.
@ -127,7 +127,7 @@ function CanvasProxy(width, height) {
return function() {
// console.log("funcCall", name)
cmdQueue.push([name, Array.prototype.slice.call(arguments)]);
}
};
}
var name;
for (var i = 0; i < ctxFunc.length; i++) {
@ -139,11 +139,11 @@ function CanvasProxy(width, height) {
ctx.createPattern = function(object, kind) {
return new PatternProxy(cmdQueue, object, kind);
}
};
ctx.createLinearGradient = function(x0, y0, x1, y1) {
return new GradientProxy(cmdQueue, x0, y0, x1, y1);
}
};
ctx.getImageData = function(x, y, w, h) {
return {
@ -151,11 +151,11 @@ function CanvasProxy(width, height) {
height: h,
data: Uint8ClampedArray(w * h * 4)
};
}
};
ctx.putImageData = function(data, x, y, width, height) {
cmdQueue.push(['$putImageData', [data, x, y, width, height]]);
}
};
ctx.drawImage = function(image, x, y, width, height,
sx, sy, swidth, sheight) {
@ -168,7 +168,7 @@ function CanvasProxy(width, height) {
} else {
throw 'unkown type to drawImage';
}
}
};
// Setup property access to `ctx`.
var ctxProp = {
@ -195,14 +195,14 @@ function CanvasProxy(width, height) {
function buildGetter(name) {
return function() {
return ctx['$' + name];
}
};
}
function buildSetter(name) {
return function(value) {
cmdQueue.push(['$', name, value]);
return ctx['$' + name] = value;
}
return (ctx['$' + name] = value);
};
}
// Setting the value to `stroke|fillStyle` needs special handling, as it
@ -215,9 +215,9 @@ function CanvasProxy(width, height) {
cmdQueue.push(['$' + name + 'Pattern', [value.id]]);
} else {
cmdQueue.push(['$', name, value]);
return ctx['$' + name] = value;
return (ctx['$' + name] = value);
}
}
};
}
for (var name in ctxProp) {

View File

@ -3,23 +3,26 @@
'use strict';
if (typeof console.time == 'undefined') {
var consoleUtils = (function() {
var consoleTimer = {};
console.time = function(name) {
var obj = {};
obj.time = function(name) {
consoleTimer[name] = Date.now();
};
console.timeEnd = function(name) {
obj.timeEnd = function(name) {
var time = consoleTimer[name];
if (time == null) {
throw 'Unkown timer name ' + name;
}
this.log('Timer:', name, Date.now() - time);
console.log('Timer:', name, Date.now() - time);
};
}
return obj;
})();
function FontWorker() {
this.worker = new Worker('worker/font.js');
this.worker = new Worker('../worker/font.js');
this.fontsWaiting = 0;
this.fontsWaitingCallbacks = [];
@ -58,7 +61,7 @@ FontWorker.prototype = {
'fonts': function(data) {
// console.log("got processed fonts from worker", Object.keys(data));
for (name in data) {
for (var name in data) {
// Update the encoding property.
var font = Fonts.lookup(name);
font.properties = {
@ -96,7 +99,7 @@ FontWorker.prototype = {
this.fontsWaiting++;
}
console.time('ensureFonts');
consoleUtils.time('ensureFonts');
// If there are fonts, that need to get loaded, tell the FontWorker to get
// started and push the callback on the waiting-callback-stack.
if (notLoaded.length != 0) {
@ -124,7 +127,7 @@ function WorkerPDFDoc(canvas) {
this.ctx = canvas.getContext('2d');
this.canvas = canvas;
this.worker = new Worker('worker/pdf.js');
this.worker = new Worker('../worker/pdf.js');
this.fontWorker = new FontWorker();
this.waitingForFonts = false;
this.waitingForFontsCallback = [];
@ -167,7 +170,8 @@ function WorkerPDFDoc(canvas) {
},
'$putImageData': function(imageData, x, y) {
var imgData = this.getImageData(0, 0, imageData.width, imageData.height);
var imgData = this.getImageData(0, 0,
imageData.width, imageData.height);
// Store the .data property to avaid property lookups.
var imageRealData = imageData.data;
@ -176,7 +180,7 @@ function WorkerPDFDoc(canvas) {
// Copy over the imageData.
var len = imageRealData.length;
while (len--)
imgRealData[len] = imageRealData[len];
imgRealData[len] = imageRealData[len];
this.putImageData(imgData, x, y);
},
@ -273,7 +277,7 @@ function WorkerPDFDoc(canvas) {
},
'pdf_num_pages': function(data) {
this.numPages = parseInt(data);
this.numPages = parseInt(data, 10);
if (this.loadCallback) {
this.loadCallback();
}
@ -302,8 +306,8 @@ function WorkerPDFDoc(canvas) {
'setup_page': function(data) {
var size = data.split(',');
var canvas = this.canvas, ctx = this.ctx;
canvas.width = parseInt(size[0]);
canvas.height = parseInt(size[1]);
canvas.width = parseInt(size[0], 10);
canvas.height = parseInt(size[1], 10);
},
'fonts': function(data) {
@ -339,7 +343,7 @@ function WorkerPDFDoc(canvas) {
var renderData = function() {
if (id == 0) {
console.time('main canvas rendering');
consoleUtils.time('main canvas rendering');
var ctx = this.ctx;
ctx.save();
ctx.fillStyle = 'rgb(255, 255, 255)';
@ -348,8 +352,8 @@ function WorkerPDFDoc(canvas) {
}
renderProxyCanvas(canvasList[id], cmdQueue);
if (id == 0) {
console.timeEnd('main canvas rendering');
console.timeEnd('>>> total page display time:');
consoleUtils.timeEnd('main canvas rendering');
consoleUtils.timeEnd('>>> total page display time:');
}
}.bind(this);
@ -368,50 +372,52 @@ function WorkerPDFDoc(canvas) {
};
// Listen to the WebWorker for data and call actionHandler on it.
this.worker.onmessage = function(event) {
this.worker.addEventListener('message', function(event) {
var data = event.data;
if (data.action in actionHandler) {
actionHandler[data.action].call(this, data.data);
} else {
throw 'Unkown action from worker: ' + data.action;
}
}.bind(this);
}.bind(this));
}
WorkerPDFDoc.prototype.open = function(url, callback) {
var req = new XMLHttpRequest();
req.open('GET', url);
req.mozResponseType = req.responseType = 'arraybuffer';
req.expected = (document.URL.indexOf('file:') == 0) ? 0 : 200;
req.onreadystatechange = function() {
if (req.readyState == 4 && req.status == req.expected) {
var data = req.mozResponseArrayBuffer || req.mozResponse ||
req.responseArrayBuffer || req.response;
WorkerPDFDoc.prototype = {
open: function(url, callback) {
var req = new XMLHttpRequest();
req.open('GET', url);
req.mozResponseType = req.responseType = 'arraybuffer';
req.expected = (document.URL.indexOf('file:') == 0) ? 0 : 200;
req.onreadystatechange = function() {
if (req.readyState == 4 && req.status == req.expected) {
var data = req.mozResponseArrayBuffer || req.mozResponse ||
req.responseArrayBuffer || req.response;
this.loadCallback = callback;
this.worker.postMessage(data);
this.showPage(this.numPage);
this.loadCallback = callback;
this.worker.postMessage(data);
this.showPage(this.numPage);
}
}.bind(this);
req.send(null);
},
showPage: function(numPage) {
this.numPage = parseInt(numPage, 10);
console.log('=== start rendering page ' + numPage + ' ===');
consoleUtils.time('>>> total page display time:');
this.worker.postMessage(numPage);
if (this.onChangePage) {
this.onChangePage(numPage);
}
}.bind(this);
req.send(null);
};
},
WorkerPDFDoc.prototype.showPage = function(numPage) {
this.numPage = parseInt(numPage);
console.log('=== start rendering page ' + numPage + ' ===');
console.time('>>> total page display time:');
this.worker.postMessage(numPage);
if (this.onChangePage) {
this.onChangePage(numPage);
nextPage: function() {
if (this.numPage != this.numPages)
this.showPage(++this.numPage);
},
prevPage: function() {
if (this.numPage != 1)
this.showPage(--this.numPage);
}
};
WorkerPDFDoc.prototype.nextPage = function() {
if (this.numPage == this.numPages) return;
this.showPage(++this.numPage);
};
WorkerPDFDoc.prototype.prevPage = function() {
if (this.numPage == 1) return;
this.showPage(--this.numPage);
};

View File

@ -25,3 +25,4 @@ var console = {
this.log('Timer:', name, Date.now() - time);
}
};

View File

@ -56,11 +56,12 @@ var actionHandler = {
};
// Listen to the MainThread for data and call actionHandler on it.
this.onmessage = function(event) {
addEventListener('message', function(event) {
var data = event.data;
if (data.action in actionHandler) {
actionHandler[data.action].call(this, data.data);
} else {
throw 'Unkown action from worker: ' + data.action;
}
};
});

View File

@ -11,8 +11,10 @@ var console = {
action: 'log',
data: args
});
},
}
};
var consoleUtils = {
time: function(name) {
consoleTimer[name] = Date.now();
},
@ -22,7 +24,7 @@ var console = {
if (time == null) {
throw 'Unkown timer name ' + name;
}
this.log('Timer:', name, Date.now() - time);
console.log('Timer:', name, Date.now() - time);
}
};
@ -42,7 +44,7 @@ var canvas = new CanvasProxy(1224, 1584);
// Listen for messages from the main thread.
var pdfDocument = null;
onmessage = function(event) {
addEventListener('message', function(event) {
var data = event.data;
// If there is no pdfDocument yet, then the sent data is the PDFDocument.
if (!pdfDocument) {
@ -55,10 +57,10 @@ onmessage = function(event) {
}
// User requested to render a certain page.
else {
console.time('compile');
consoleUtils.time('compile');
// Let's try to render the first page...
var page = pdfDocument.getPage(parseInt(data));
var page = pdfDocument.getPage(parseInt(data, 10));
var pdfToCssUnitsCoef = 96.0 / 72.0;
var pageWidth = (page.mediaBox[2] - page.mediaBox[0]) * pdfToCssUnitsCoef;
@ -77,19 +79,19 @@ onmessage = function(event) {
var fonts = [];
var gfx = new CanvasGraphics(canvas.getContext('2d'), CanvasProxy);
page.compile(gfx, fonts);
console.timeEnd('compile');
consoleUtils.timeEnd('compile');
// Send fonts to the main thread.
console.time('fonts');
consoleUtils.time('fonts');
postMessage({
action: 'fonts',
data: fonts
});
console.timeEnd('fonts');
consoleUtils.timeEnd('fonts');
console.time('display');
consoleUtils.time('display');
page.display(gfx);
canvas.flush();
console.timeEnd('display');
consoleUtils.timeEnd('display');
}
};
});