Merge with upstream

This commit is contained in:
Vivien Nicolas 2011-09-20 03:56:33 +02:00
commit 42859177fd
32 changed files with 4489 additions and 1082 deletions

View File

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

View File

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

View File

@ -1,17 +1,84 @@
# pdf.js # 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 ## Overview
pdf.js is production ready. Until then we aim to publish a Firefox
PDF reader extension powered by pdf.js. 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: 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: 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/ 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 http://twitter.com/#!/pdfjs
join our mailing list: Join our mailing list:
dev-pdf-js@lists.mozilla.org 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 #pdfjs on irc.mozilla.org

View File

@ -1,4 +1,4 @@
/* -*- Mode: Java; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- / /* -*- Mode: Java; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
/* vim: set shiftwidth=2 tabstop=2 autoindent cindent expandtab: */ /* vim: set shiftwidth=2 tabstop=2 autoindent cindent expandtab: */
'use strict'; 'use strict';

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>

512
fonts.js Executable file → Normal file
View File

@ -1,4 +1,4 @@
/* -*- Mode: Java; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- / /* -*- Mode: Java; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
/* vim: set shiftwidth=2 tabstop=2 autoindent cindent expandtab: */ /* vim: set shiftwidth=2 tabstop=2 autoindent cindent expandtab: */
'use strict'; 'use strict';
@ -12,6 +12,13 @@ var kMaxWaitForFontFace = 1000;
// Unicode Private Use Area // Unicode Private Use Area
var kCmapGlyphOffset = 0xE000; 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;
/** /**
* Hold a map of decoded fonts and of the standard fourteen Type1 * Hold a map of decoded fonts and of the standard fourteen Type1
* fonts and their acronyms. * fonts and their acronyms.
@ -114,54 +121,6 @@ var serifFonts = {
'Wide Latin': true, 'Windsor': true, 'XITS': true 'Wide Latin': true, 'Windsor': true, 'XITS': true
}; };
var FontMeasure = (function FontMeasure() {
var kScalePrecision = 30;
var ctx = document.createElement('canvas').getContext('2d');
ctx.scale(1 / kScalePrecision, 1);
var current;
var measureCache;
return {
setActive: function fonts_setActive(font, size) {
if (current == font) {
var sizes = current.sizes;
if (!(measureCache = sizes[size]))
measureCache = sizes[size] = Object.create(null);
} else {
measureCache = null;
}
var name = font.loadedName;
var bold = font.bold ? 'bold' : 'normal';
var italic = font.italic ? 'italic' : 'normal';
size *= kScalePrecision;
var rule = italic + ' ' + bold + ' ' + size + 'px "' + name + '"';
ctx.font = rule;
current = font;
},
measureText: function fonts_measureText(text, encoding, size) {
var width;
if (measureCache && (width = measureCache[text]))
return width;
try {
width = 0.0;
for (var i = 0; i < text.length; i++) {
var charWidth = encoding[text.charCodeAt(i)].width;
width += parseFloat(charWidth);
}
width = width * size / 1000;
} catch(e) {
width = ctx.measureText(text).width / kScalePrecision;
}
if (measureCache)
measureCache[text] = width;
return width;
}
};
})();
var FontLoader = { var FontLoader = {
listeningForFontLoad: false, listeningForFontLoad: false,
@ -456,10 +415,10 @@ var Font = (function Font() {
this.encoding = properties.encoding; this.encoding = properties.encoding;
this.sizes = []; this.sizes = [];
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.serif = serifFonts[names] || (name.indexOf("Serif") != -1); this.serif = serifFonts[names] || (name.search(/serif/gi) != -1);
// If the font is to be ignored, register it like an already loaded font // If the font is to be ignored, register it like an already loaded font
// to avoid the cost of waiting for it be be loaded by the platform. // to avoid the cost of waiting for it be be loaded by the platform.
@ -473,14 +432,15 @@ var Font = (function Font() {
// The file data is not specified. Trying to fix the font name // The file data is not specified. Trying to fix the font name
// to be used with the canvas.font. // to be used with the canvas.font.
var fontName = stdFontMap[name] || name.replace('_', '-'); var fontName = stdFontMap[name] || name.replace('_', '-');
this.bold = (fontName.indexOf('Bold') != -1); this.bold = (fontName.search(/bold/gi) != -1);
this.italic = (fontName.indexOf('Oblique') != -1) || this.italic = (fontName.search(/oblique/gi) != -1) ||
(fontName.indexOf('Italic') != -1); (fontName.search(/italic/gi) != -1);
// Use 'name' instead of 'fontName' here because the original // Use 'name' instead of 'fontName' here because the original
// name ArialBlack for example will be replaced by Helvetica. // name ArialBlack for example will be replaced by Helvetica.
this.black = (name.indexOf("Black") != -1) this.black = (name.search(/Black/g) != -1);
this.defaultWidth = properties.defaultWidth;
this.loadedName = fontName.split('-')[0]; this.loadedName = fontName.split('-')[0];
this.loading = false; this.loading = false;
return; return;
@ -493,8 +453,8 @@ var Font = (function Font() {
this.mimetype = 'font/opentype'; this.mimetype = 'font/opentype';
var subtype = properties.subtype; var subtype = properties.subtype;
var cff = (subtype === 'Type1C') ? new Type2CFF(file, properties) var cff = (subtype === 'Type1C') ?
: new CFF(name, file, properties); new Type2CFF(file, properties) : new CFF(name, file, properties);
// Wrap the CFF data inside an OTF font file // Wrap the CFF data inside an OTF font file
data = this.convert(name, cff, properties); data = this.convert(name, cff, properties);
@ -517,6 +477,7 @@ var Font = (function Font() {
this.data = data; this.data = data;
this.type = properties.type; this.type = properties.type;
this.textMatrix = properties.textMatrix; this.textMatrix = properties.textMatrix;
this.defaultWidth = properties.defaultWidth;
this.loadedName = getUniqueName(); this.loadedName = getUniqueName();
this.composite = properties.composite; this.composite = properties.composite;
this.loading = true; this.loading = true;
@ -579,6 +540,10 @@ var Font = (function Font() {
}; };
function createOpenTypeHeader(sfnt, file, numTables) { function createOpenTypeHeader(sfnt, file, numTables) {
// Windows hates the Mac TrueType sfnt version number
if (sfnt == 'true')
sfnt = string32(0x00010000);
// sfnt version (4 bytes) // sfnt version (4 bytes)
var header = sfnt; var header = sfnt;
@ -632,19 +597,24 @@ var Font = (function Font() {
var codes = []; var codes = [];
var length = glyphs.length; var length = glyphs.length;
for (var n = 0; n < length; ++n) for (var n = 0; n < length; ++n)
codes.push(String.fromCharCode(glyphs[n].unicode)); codes.push({ unicode: glyphs[n].unicode, code: n });
codes.sort(); codes.sort(function(a, b) {
return a.unicode - b.unicode;
});
// Split the sorted codes into ranges. // Split the sorted codes into ranges.
var ranges = []; var ranges = [];
for (var n = 0; n < length; ) { 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; var end = start;
while (n < length && end + 1 == codes[n].charCodeAt(0)) { while (n < length && end + 1 == codes[n].unicode) {
++end; ++end;
++n; ++n;
} }
ranges.push([start, end]); var endCode = codes[n - 1].code;
ranges.push([start, end, startCode, endCode]);
} }
return ranges; return ranges;
@ -673,22 +643,39 @@ var Font = (function Font() {
var idRangeOffsets = ''; var idRangeOffsets = '';
var glyphsIds = ''; var glyphsIds = '';
var bias = 0; 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); if (deltas) {
endCount += string16(end); for (var i = 0; i < segCount - 1; i++) {
idDeltas += string16(0); var range = ranges[i];
idRangeOffsets += string16(offset); 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'; endCount += '\xFF\xFF';
startCount += '\xFF\xFF'; startCount += '\xFF\xFF';
idDeltas += '\x00\x01'; idDeltas += '\x00\x01';
@ -708,7 +695,9 @@ var Font = (function Font() {
format314); format314);
}; };
function createOS2Table(properties) { function createOS2Table(properties, override) {
var override = override || {};
var ulUnicodeRange1 = 0; var ulUnicodeRange1 = 0;
var ulUnicodeRange2 = 0; var ulUnicodeRange2 = 0;
var ulUnicodeRange3 = 0; var ulUnicodeRange3 = 0;
@ -739,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 return '\x00\x03' + // version
'\x02\x24' + // xAvgCharWidth '\x02\x24' + // xAvgCharWidth
'\x01\xF4' + // usWeightClass '\x01\xF4' + // usWeightClass
@ -767,11 +774,11 @@ var Font = (function Font() {
string16(firstCharIndex || string16(firstCharIndex ||
properties.firstChar) + // usFirstCharIndex properties.firstChar) + // usFirstCharIndex
string16(lastCharIndex || properties.lastChar) + // usLastCharIndex string16(lastCharIndex || properties.lastChar) + // usLastCharIndex
string16(properties.ascent) + // sTypoAscender string16(typoAscent) + // sTypoAscender
string16(properties.descent) + // sTypoDescender string16(typoDescent) + // sTypoDescender
'\x00\x64' + // sTypoLineGap (7%-10% of the unitsPerEM value) '\x00\x64' + // sTypoLineGap (7%-10% of the unitsPerEM value)
string16(properties.ascent) + // usWinAscent string16(winAscent) + // usWinAscent
string16(-properties.descent) + // usWinDescent string16(winDescent) + // usWinDescent
'\x00\x00\x00\x00' + // ulCodePageRange1 (Bits 0-31) '\x00\x00\x00\x00' + // ulCodePageRange1 (Bits 0-31)
'\x00\x00\x00\x00' + // ulCodePageRange2 (Bits 32-63) '\x00\x00\x00\x00' + // ulCodePageRange2 (Bits 32-63)
string16(properties.xHeight) + // sxHeight string16(properties.xHeight) + // sxHeight
@ -878,9 +885,11 @@ var Font = (function Font() {
var data = file.getBytes(length); var data = file.getBytes(length);
file.pos = previousPosition; file.pos = previousPosition;
if (tag == 'head') if (tag == 'head') {
// clearing checksum adjustment // clearing checksum adjustment
data[8] = data[9] = data[10] = data[11] = 0; data[8] = data[9] = data[10] = data[11] = 0;
data[17] |= 0x20; //Set font optimized for cleartype flag
}
return { return {
tag: tag, tag: tag,
@ -920,7 +929,7 @@ var Font = (function Font() {
// Check that table are sorted by platformID then encodingID, // Check that table are sorted by platformID then encodingID,
records.sort(function(a, b) { records.sort(function(a, b) {
return ((a.platformID << 16) + a.encodingID) - return ((a.platformID << 16) + a.encodingID) -
((b.platformID << 16) + b.encodingID) ((b.platformID << 16) + b.encodingID);
}); });
var tables = [records[0]]; var tables = [records[0]];
@ -952,7 +961,7 @@ var Font = (function Font() {
} }
for (var i = 0; i < data.length; i++) for (var i = 0; i < data.length; i++)
cmap.data[i] = data.charCodeAt(i); cmap.data[i] = data.charCodeAt(i);
} }
var encoding = properties.encoding; var encoding = properties.encoding;
@ -979,11 +988,13 @@ var Font = (function Font() {
deltas.push(index); deltas.push(index);
var unicode = j + kCmapGlyphOffset; var unicode = j + kCmapGlyphOffset;
encoding[j].unicode = unicode; var mapping = encoding[j] || {};
mapping.unicode = unicode;
encoding[j] = mapping;
glyphs.push({ unicode: unicode }); glyphs.push({ unicode: unicode });
} }
} }
return cmap.data = createCMapTable(glyphs, deltas); return cmap.data = createCMapTable(glyphs, deltas);
} else if (format == 6) { } else if (format == 6) {
// Format 6 is a 2-bytes dense mapping, which means the font data // Format 6 is a 2-bytes dense mapping, which means the font data
@ -995,48 +1006,98 @@ var Font = (function Font() {
var entryCount = int16(font.getBytes(2)); var entryCount = int16(font.getBytes(2));
var glyphs = []; var glyphs = [];
var min = 0xffff, max = 0; var ids = [];
for (var j = 0; j < entryCount; j++) { for (var j = 0; j < firstCode + entryCount; j++) {
var charcode = int16(font.getBytes(2)); var code = (j >= firstCode) ? int16(font.getBytes(2)) : j;
if (!charcode) glyphs.push({ unicode: j + kCmapGlyphOffset });
continue; ids.push(code);
glyphs.push(charcode);
if (charcode < min) var mapping = encoding[j] || {};
min = charcode; mapping.unicode = glyphs[j].unicode;
if (charcode > max) encoding[j] = mapping;
max = charcode;
} }
return cmap.data = createCMapTable(glyphs, ids);
// Since Format 6 is a dense array, check for gaps
for (var j = min; j < max; j++) {
if (glyphs.indexOf(j) == -1)
glyphs.push(j);
}
for (var j = 0; j < glyphs.length; j++)
glyphs[j] = { unicode: glyphs[j] + firstCode };
var ranges = getRanges(glyphs);
assert(ranges.length == 1, 'Got ' + ranges.length +
' ranges in a dense array');
var denseRange = ranges[0];
var start = denseRange[0];
var end = denseRange[1];
var index = firstCode;
for (var j = start; j <= end; j++) {
var code = j - firstCode - 1;
var mapping = encoding[index + 1] || {};
mapping.unicode = glyphs[code].unicode;
encoding[index++] = mapping;
}
return cmap.data = createCMapTable(glyphs);
} }
} }
return cmap.data; return cmap.data;
}; };
function sanitizeMetrics(font, header, metrics, numGlyphs) {
if (!header && !metrics)
return;
// The vhea/vmtx tables are not required, so it happens that
// some fonts embed a vmtx table without a vhea table. In this
// situation the sanitizer assume numOfLongVerMetrics = 1. As
// a result it tries to read numGlyphs - 1 SHORT from the vmtx
// table, and if it is not possible, the font is rejected.
// So remove the vmtx table if there is no vhea table.
if (!header && metrics) {
metrics.data = null;
return;
}
font.pos = (font.start ? font.start : 0) + header.offset;
font.pos += header.length - 2;
var numOfMetrics = int16(font.getBytes(2));
var numOfSidebearings = numGlyphs - numOfMetrics;
var numMissing = numOfSidebearings -
((hmtx.length - numOfMetrics * 4) >> 1);
if (numMissing > 0) {
font.pos = (font.start ? font.start : 0) + metrics.offset;
var entries = '';
for (var i = 0; i < hmtx.length; i++)
entries += String.fromCharCode(font.getByte());
for (var i = 0; i < numMissing; i++)
entries += '\x00\x00';
metrics.data = stringToArray(entries);
}
};
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 // Check that required tables are present
var requiredTables = ['OS/2', 'cmap', 'head', 'hhea', var requiredTables = ['OS/2', 'cmap', 'head', 'hhea',
'hmtx', 'maxp', 'name', 'post']; 'hmtx', 'maxp', 'name', 'post'];
@ -1044,7 +1105,7 @@ var Font = (function Font() {
var header = readOpenTypeHeader(font); var header = readOpenTypeHeader(font);
var numTables = header.numTables; var numTables = header.numTables;
var cmap, maxp, hhea, hmtx; var cmap, maxp, hhea, hmtx, vhea, vmtx, head, loca, glyf;
var tables = []; var tables = [];
for (var i = 0; i < numTables; i++) { for (var i = 0; i < numTables; i++) {
var table = readTableEntry(font); var table = readTableEntry(font);
@ -1058,8 +1119,19 @@ var Font = (function Font() {
hhea = table; hhea = table;
else if (table.tag == 'hmtx') else if (table.tag == 'hmtx')
hmtx = table; hmtx = table;
else if (table.tag == 'head')
head = table;
requiredTables.splice(index, 1); requiredTables.splice(index, 1);
} else {
if (table.tag == 'vmtx')
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); tables.push(table);
} }
@ -1079,33 +1151,34 @@ var Font = (function Font() {
createOpenTypeHeader(header.version, ttf, numTables); createOpenTypeHeader(header.version, ttf, numTables);
if (requiredTables.indexOf('OS/2') != -1) { 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({ tables.push({
tag: 'OS/2', tag: 'OS/2',
data: stringToArray(createOS2Table(properties)) data: stringToArray(createOS2Table(properties, override))
}); });
} }
// Ensure the hmtx tables contains an advance width and a sidebearing // Ensure the [h/v]mtx tables contains the advance width and
// for the number of glyphs declared in the maxp table // sidebearings information for numGlyphs in the maxp table
font.pos = (font.start ? font.start : 0) + maxp.offset; font.pos = (font.start || 0) + maxp.offset;
var version = int16(font.getBytes(4)); var version = int16(font.getBytes(4));
var numGlyphs = int16(font.getBytes(2)); var numGlyphs = int16(font.getBytes(2));
font.pos = (font.start ? font.start : 0) + hhea.offset; sanitizeMetrics(font, hhea, hmtx, numGlyphs);
font.pos += hhea.length - 2; sanitizeMetrics(font, vhea, vmtx, numGlyphs);
var numOfHMetrics = int16(font.getBytes(2));
var numOfSidebearings = numGlyphs - numOfHMetrics; if (head && loca && glyf) {
var numMissing = numOfSidebearings - var isGlyphLocationsLong = int16([head.data[50], head.data[51]]);
((hmtx.length - numOfHMetrics * 4) >> 1); sanitizeGlyphLocations(loca, glyf, numGlyphs, isGlyphLocationsLong);
if (numMissing > 0) {
font.pos = (font.start ? font.start : 0) + hmtx.offset;
var metrics = '';
for (var i = 0; i < hmtx.length; i++)
metrics += String.fromCharCode(font.getByte());
for (var i = 0; i < numMissing; i++)
metrics += '\x00\x00';
hmtx.data = stringToArray(metrics);
} }
// Sanitizer reduces the glyph advanceWidth to the maxAdvanceWidth // Sanitizer reduces the glyph advanceWidth to the maxAdvanceWidth
@ -1132,20 +1205,36 @@ var Font = (function Font() {
tables.push(cmap); tables.push(cmap);
} }
var encoding = properties.encoding; var encoding = properties.encoding, i;
if (!encoding[0]) { if (!encoding[0]) {
// the font is directly characters to glyphs with no encoding // the font is directly characters to glyphs with no encoding
// so create an identity encoding // so create an identity encoding
for (i = 0; i < numGlyphs; i++) var widths = properties.widths;
encoding[i] = { unicode: i + kCmapGlyphOffset }; for (i = 0; i < numGlyphs; i++) {
var width = widths[i];
encoding[i] = {
unicode: i <= 0x1f || (i >= 127 && i <= 255) ?
i + kCmapGlyphOffset : i,
width: IsNum(width) ? width : properties.defaultWidth
};
}
} else { } else {
for (var code in encoding) for (i in encoding) {
encoding[code].unicode += kCmapGlyphOffset; if (encoding.hasOwnProperty(i)) {
var unicode = encoding[i].unicode;
if (unicode <= 0x1f || (unicode >= 127 && unicode <= 255))
encoding[i].unicode = unicode += kCmapGlyphOffset;
}
}
} }
var glyphs = []; var glyphs = [];
for (var i = 1; i < numGlyphs; i++) for (i = 1; i < numGlyphs; i++) {
glyphs.push({ unicode: i + kCmapGlyphOffset }); glyphs.push({
unicode: i <= 0x1f || (i >= 127 && i <= 255) ?
i + kCmapGlyphOffset : i
});
}
cmap.data = createCMapTable(glyphs); cmap.data = createCMapTable(glyphs);
} else { } else {
replaceCMapTable(cmap, font, properties); replaceCMapTable(cmap, font, properties);
@ -1204,7 +1293,7 @@ var Font = (function Font() {
return false; return false;
} }
return true; return true;
}; }
// The offsets object holds at the same time a representation of where // The offsets object holds at the same time a representation of where
// to write the table entry information about a table and another offset // to write the table entry information about a table and another offset
@ -1330,20 +1419,25 @@ var Font = (function Font() {
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 styleSheet = document.styleSheets[0]; var styleSheet = document.styleSheets[0];
if (!styleSheet) {
document.documentElement.firstChild.appendChild(
document.createElement('style'));
styleSheet = document.styleSheets[0];
}
styleSheet.insertRule(rule, styleSheet.cssRules.length); styleSheet.insertRule(rule, styleSheet.cssRules.length);
return rule; return rule;
}, },
charsToUnicode: function fonts_chars2Unicode(chars) { charsToGlyphs: function fonts_chars2Glyphs(chars) {
var charsCache = this.charsCache; var charsCache = this.charsCache;
var str; var glyphs;
// if we translated this string before, just grab it from the cache // if we translated this string before, just grab it from the cache
if (charsCache) { if (charsCache) {
str = charsCache[chars]; glyphs = charsCache[chars];
if (str) if (glyphs)
return str; return glyphs;
} }
// lazily create the translation cache // lazily create the translation cache
@ -1354,7 +1448,8 @@ var Font = (function Font() {
var encoding = this.encoding; var encoding = this.encoding;
if (!encoding) if (!encoding)
return chars; return chars;
str = '';
glyphs = [];
if (this.composite) { if (this.composite) {
// composite fonts have multi-byte strings convert the string from // composite fonts have multi-byte strings convert the string from
@ -1365,30 +1460,39 @@ var Font = (function Font() {
// loop should never end on the last byte // loop should never end on the last byte
for (var i = 0; i < length; i++) { for (var i = 0; i < length; i++) {
var charcode = int16([chars.charCodeAt(i++), chars.charCodeAt(i)]); var charcode = int16([chars.charCodeAt(i++), chars.charCodeAt(i)]);
var unicode = encoding[charcode].unicode; var glyph = encoding[charcode];
str += String.fromCharCode(unicode); if ('undefined' == typeof(glyph)) {
warn('Unencoded charcode ' + charcode);
glyph = {
unicode: charcode,
width: this.defaultWidth
};
}
glyphs.push(glyph);
// placing null after each word break charcode (ASCII SPACE)
if (charcode == 0x20)
glyphs.push(null);
} }
} }
else { else {
for (var i = 0; i < chars.length; ++i) { for (var i = 0; i < chars.length; ++i) {
var charcode = chars.charCodeAt(i); var charcode = chars.charCodeAt(i);
var unicode = encoding[charcode].unicode; var glyph = encoding[charcode];
if ('undefined' == typeof(unicode)) { if ('undefined' == typeof(glyph)) {
warn('Unencoded charcode ' + charcode); warn('Unencoded charcode ' + charcode);
unicode = charcode; glyph = {
unicode: charcode,
width: this.defaultWidth
};
} }
glyphs.push(glyph);
// Handle surrogate pairs if (charcode == 0x20)
if (unicode > 0xFFFF) { glyphs.push(null);
str += String.fromCharCode(unicode & 0xFFFF);
unicode >>= 16;
}
str += String.fromCharCode(unicode);
} }
} }
// Enter the translated string into the cache // Enter the translated string into the cache
return charsCache[chars] = str; return (charsCache[chars] = glyphs);
} }
}; };
@ -1421,7 +1525,7 @@ var Type1Parser = function() {
r = ((value + r) * c1 + c2) & ((1 << 16) - 1); r = ((value + r) * c1 + c2) & ((1 << 16) - 1);
} }
return decryptedString.slice(discardNumber); return decryptedString.slice(discardNumber);
}; }
/* /*
* CharStrings are encoded following the the CharString Encoding sequence * CharStrings are encoded following the the CharString Encoding sequence
@ -1562,6 +1666,9 @@ var Type1Parser = function() {
i++; i++;
continue; continue;
} }
} else if (!kHintingEnabled && (value == 1 || value == 2)) {
charstring.push('drop', 'drop', 'drop', 'drop', 'drop', 'drop');
continue;
} }
command = charStringDictionary['12'][escape]; command = charStringDictionary['12'][escape];
@ -1586,6 +1693,9 @@ var Type1Parser = function() {
charstring.push(lsb, 'hmoveto'); charstring.push(lsb, 'hmoveto');
continue; continue;
} else if (!kHintingEnabled && (value == 1 || value == 3)) {
charstring.push('drop', 'drop');
continue;
} }
command = charStringDictionary[value]; command = charStringDictionary[value];
} }
@ -1618,7 +1728,7 @@ var Type1Parser = function() {
} }
return { charstring: charstring, width: width, lsb: lsb }; return { charstring: charstring, width: width, lsb: lsb };
}; }
/* /*
* Returns an object containing a Subrs array and a CharStrings * Returns an object containing a Subrs array and a CharStrings
@ -1638,7 +1748,7 @@ var Type1Parser = function() {
for (var i = 0; i < array.length; i++) for (var i = 0; i < array.length; i++)
array[i] = parseFloat(array[i] || 0); array[i] = parseFloat(array[i] || 0);
return array; return array;
}; }
function readNumber(str, index) { function readNumber(str, index) {
while (str[index] == ' ') while (str[index] == ' ')
@ -1651,11 +1761,11 @@ var Type1Parser = function() {
count++; count++;
return parseFloat(str.substr(start, count) || 0); return parseFloat(str.substr(start, count) || 0);
}; }
function isSeparator(c) { function isSeparator(c) {
return c == ' ' || c == '\n' || c == '\x0d'; return c == ' ' || c == '\n' || c == '\x0d';
}; }
this.extractFontProgram = function t1_extractFontProgram(stream) { this.extractFontProgram = function t1_extractFontProgram(stream) {
var eexec = decrypt(stream, kEexecEncryptionKey, 4); var eexec = decrypt(stream, kEexecEncryptionKey, 4);
@ -1693,7 +1803,7 @@ var Type1Parser = function() {
}; };
var c = eexecStr[i]; var c = eexecStr[i];
if ((glyphsSection || subrsSection) && if ((glyphsSection || subrsSection) &&
(token == 'RD' || token == '-|')) { (token == 'RD' || token == '-|')) {
i++; i++;
var data = eexec.slice(i, i + length); var data = eexec.slice(i, i + length);
@ -1729,7 +1839,7 @@ var Type1Parser = function() {
getToken(); // read in 'array' getToken(); // read in 'array'
for (var j = 0; j < num; ++j) { for (var j = 0; j < num; ++j) {
var t = getToken(); // read in 'dup' var t = getToken(); // read in 'dup'
if (t == 'ND' || t == '|-' || t == 'noaccess') if (t == 'ND' || t == '|-' || t == 'noaccess')
break; break;
var index = parseInt(getToken(), 10); var index = parseInt(getToken(), 10);
if (index > j) if (index > j)
@ -1780,7 +1890,7 @@ var Type1Parser = function() {
} }
return program; return program;
}, };
this.extractFontHeader = function t1_extractFontHeader(stream, properties) { this.extractFontHeader = function t1_extractFontHeader(stream, properties) {
var headerString = ''; var headerString = '';
@ -1828,10 +1938,12 @@ var Type1Parser = function() {
if (token == 'dup') { if (token == 'dup') {
var index = parseInt(getToken(), 10); var index = parseInt(getToken(), 10);
var glyph = getToken(); var glyph = getToken();
if ('undefined' == typeof(properties.differences[index])) { if ('undefined' == typeof(properties.differences[index])) {
var mapping = { unicode: GlyphsUnicode[glyph] || j }; var mapping = properties.encoding[index] || {};
properties.glyphs[glyph] = properties.encoding[index] = mapping; mapping.unicode = GlyphsUnicode[glyph] || index;
properties.glyphs[glyph] = properties.encoding[index] =
mapping;
} }
getToken(); // read the in 'put' getToken(); // read the in 'put'
} }
@ -1847,7 +1959,7 @@ var Type1Parser = function() {
}; };
/** /**
* The CFF class takes a Type1 file and wrap it into a * The CFF class takes a Type1 file and wrap it into a
* 'Compact Font Format' which itself embed Type2 charstrings. * 'Compact Font Format' which itself embed Type2 charstrings.
*/ */
var CFFStrings = [ var CFFStrings = [
@ -2177,7 +2289,7 @@ CFF.prototype = {
'globalSubrs': this.createCFFIndexHeader([]), 'globalSubrs': this.createCFFIndexHeader([]),
'charset': (function charset(self) { 'charset': (function charset(self) {
var charset = '\x00'; // Encoding var charsetString = '\x00'; // Encoding
var count = glyphs.length; var count = glyphs.length;
for (var i = 0; i < count; i++) { for (var i = 0; i < count; i++) {
@ -2189,9 +2301,9 @@ CFF.prototype = {
if (index == -1) if (index == -1)
index = 0; index = 0;
charset += String.fromCharCode(index >> 8, index & 0xff); charsetString += String.fromCharCode(index >> 8, index & 0xff);
} }
return charset; return charsetString;
})(this), })(this),
'charstrings': this.createCFFIndexHeader([[0x8B, 0x0E]].concat(glyphs), 'charstrings': this.createCFFIndexHeader([[0x8B, 0x0E]].concat(glyphs),
@ -2258,7 +2370,7 @@ var Type2CFF = (function() {
this.properties = properties; this.properties = properties;
this.data = this.parse(); this.data = this.parse();
}; }
constructor.prototype = { constructor.prototype = {
parse: function cff_parse() { parse: function cff_parse() {
@ -2289,7 +2401,7 @@ var Type2CFF = (function() {
var charStrings = this.parseIndex(topDict.CharStrings); var charStrings = this.parseIndex(topDict.CharStrings);
var charset = this.parseCharsets(topDict.charset, var charset = this.parseCharsets(topDict.charset,
charStrings.length, strings); charStrings.length, strings);
var hasSupplement = this.parseEncoding(topDict.Encoding, properties, var hasSupplement = this.parseEncoding(topDict.Encoding, properties,
strings, charset); strings, charset);
// The font sanitizer does not support CFF encoding with a // The font sanitizer does not support CFF encoding with a
@ -2321,8 +2433,6 @@ var Type2CFF = (function() {
getCharStrings: function cff_charstrings(charsets, charStrings, getCharStrings: function cff_charstrings(charsets, charStrings,
privDict, properties) { privDict, properties) {
var widths = properties.widths;
var defaultWidth = privDict['defaultWidthX']; var defaultWidth = privDict['defaultWidthX'];
var nominalWidth = privDict['nominalWidthX']; var nominalWidth = privDict['nominalWidthX'];
@ -2344,13 +2454,13 @@ var Type2CFF = (function() {
if (code == -1) if (code == -1)
index = code = mapping.unicode || index; index = code = mapping.unicode || index;
var width = mapping.width || defaultWidth;
if (code <= 0x1f || (code >= 127 && code <= 255)) if (code <= 0x1f || (code >= 127 && code <= 255))
code += kCmapGlyphOffset; code += kCmapGlyphOffset;
var width = mapping.width;
properties.glyphs[glyph] = properties.encoding[index] = { properties.glyphs[glyph] = properties.encoding[index] = {
unicode: code, unicode: code,
width: width width: IsNum(width) ? width : defaultWidth
}; };
charstrings.push({ charstrings.push({
@ -2366,7 +2476,8 @@ var Type2CFF = (function() {
return charstrings; return charstrings;
}, },
parseEncoding: function cff_parseencoding(pos, properties, strings, charset) { parseEncoding: function cff_parseencoding(pos, properties, strings,
charset) {
var encoding = {}; var encoding = {};
var bytes = this.bytes; var bytes = this.bytes;
@ -2381,8 +2492,8 @@ var Type2CFF = (function() {
if (pos == 0 || pos == 1) { if (pos == 0 || pos == 1) {
var gid = 1; var gid = 1;
var baseEncoding = pos ? Encodings.ExpertEncoding var baseEncoding =
: Encodings.StandardEncoding; pos ? Encodings.ExpertEncoding : Encodings.StandardEncoding;
for (var i = 0; i < charset.length; i++) { for (var i = 0; i < charset.length; i++) {
var index = baseEncoding.indexOf(charset[i]); var index = baseEncoding.indexOf(charset[i]);
if (index != -1) if (index != -1)
@ -2393,7 +2504,7 @@ var Type2CFF = (function() {
switch (format & 0x7f) { switch (format & 0x7f) {
case 0: case 0:
var glyphsCount = bytes[pos++]; var glyphsCount = bytes[pos++];
for (var i = 1; i <= glyphsCount; i++) for (var i = 1; i <= glyphsCount; i++)
encoding[bytes[pos++]] = i; encoding[bytes[pos++]] = i;
if (format & 0x80) { if (format & 0x80) {
@ -2419,7 +2530,7 @@ var Type2CFF = (function() {
break; break;
default: default:
error('Unknow encoding format: ' + format + " in CFF"); error('Unknow encoding format: ' + format + ' in CFF');
break; break;
} }
} }
@ -2482,7 +2593,7 @@ var Type2CFF = (function() {
case 21: case 21:
dict['nominalWidthX'] = value[0]; dict['nominalWidthX'] = value[0];
default: default:
TODO('interpret top dict key'); TODO('interpret top dict key: ' + key);
} }
} }
return dict; return dict;
@ -2594,7 +2705,7 @@ var Type2CFF = (function() {
error('Incorrect byte'); error('Incorrect byte');
} }
return -1; return -1;
}; }
function parseFloatOperand() { function parseFloatOperand() {
var str = ''; var str = '';
@ -2616,7 +2727,7 @@ var Type2CFF = (function() {
str += lookup[b2]; str += lookup[b2];
} }
return parseFloat(str); return parseFloat(str);
}; }
var operands = []; var operands = [];
var entries = []; var entries = [];
@ -2642,15 +2753,14 @@ var Type2CFF = (function() {
parseIndex: function cff_parseIndex(pos) { parseIndex: function cff_parseIndex(pos) {
var bytes = this.bytes; var bytes = this.bytes;
var count = bytes[pos++] << 8 | bytes[pos++]; var count = bytes[pos++] << 8 | bytes[pos++];
if (count == 0) { var offsets = [];
var offsets = []; var end = pos;
var end = pos;
} else { if (count != 0) {
var offsetSize = bytes[pos++]; var offsetSize = bytes[pos++];
// add 1 for offset to determine size of last object // add 1 for offset to determine size of last object
var startPos = pos + ((count + 1) * offsetSize) - 1; var startPos = pos + ((count + 1) * offsetSize) - 1;
var offsets = [];
for (var i = 0, ii = count + 1; i < ii; ++i) { for (var i = 0, ii = count + 1; i < ii; ++i) {
var offset = 0; var offset = 0;
for (var j = 0; j < offsetSize; ++j) { for (var j = 0; j < offsetSize; ++j) {
@ -2659,7 +2769,7 @@ var Type2CFF = (function() {
} }
offsets.push(startPos + offset); offsets.push(startPos + offset);
} }
var end = offsets[count]; end = offsets[count];
} }
return { return {

View File

@ -1,4 +1,4 @@
/* -*- Mode: Java; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- / /* -*- Mode: Java; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
/* vim: set shiftwidth=2 tabstop=2 autoindent cindent expandtab: */ /* vim: set shiftwidth=2 tabstop=2 autoindent cindent expandtab: */
'use strict'; 'use strict';

2941
metrics.js Normal file

File diff suppressed because it is too large Load Diff

1478
pdf.js

File diff suppressed because it is too large Load Diff

View File

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

View File

@ -0,0 +1 @@
http://www.puppetpress.com/classics/ArtofWarbySunTzu.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, "rounds": 1,
"type": "load" "type": "load"
}, },
{ "id": "wnv_chinese-pdf",
"file": "pdfs/wnv_chinese.pdf",
"link": true,
"rounds": 1,
"type": "eq"
},
{ "id": "i9-pdf", { "id": "i9-pdf",
"file": "pdfs/i9.pdf", "file": "pdfs/i9.pdf",
"link": true, "link": true,
@ -109,5 +115,23 @@
"link": true, "link": true,
"rounds": 1, "rounds": 1,
"type": "eq" "type": "eq"
},
{ "id": "artofwar",
"file": "pdfs/artofwar.pdf",
"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

@ -6,6 +6,7 @@
<script type="text/javascript" src="/fonts.js"></script> <script type="text/javascript" src="/fonts.js"></script>
<script type="text/javascript" src="/crypto.js"></script> <script type="text/javascript" src="/crypto.js"></script>
<script type="text/javascript" src="/glyphlist.js"></script> <script type="text/javascript" src="/glyphlist.js"></script>
<script type="text/javascript" src="/metrics.js"></script>
<script type="text/javascript" src="driver.js"></script> <script type="text/javascript" src="driver.js"></script>
</head> </head>

View File

@ -1,4 +1,4 @@
/* -*- Mode: Java; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- / /* -*- Mode: Java; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
/* vim: set shiftwidth=2 tabstop=2 autoindent cindent expandtab: */ /* vim: set shiftwidth=2 tabstop=2 autoindent cindent expandtab: */
'use strict'; 'use strict';
@ -560,7 +560,7 @@ var CFFDictDataMap = {
'18': { '18': {
name: 'ExpansionFactor' name: 'ExpansionFactor'
}, },
'9': { '19': {
name: 'initialRandomSeed' name: 'initialRandomSeed'
}, },
'20': { '20': {

View File

@ -1,4 +1,4 @@
/* -*- Mode: Java; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- / /* -*- Mode: Java; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
/* vim: set shiftwidth=2 tabstop=2 autoindent cindent expandtab: */ /* vim: set shiftwidth=2 tabstop=2 autoindent cindent expandtab: */
'use strict'; 'use strict';
@ -69,7 +69,7 @@ function readCharstringEncoding(aString) {
} else if (value <= 31) { } else if (value <= 31) {
token = CFFEncodingMap[value]; token = CFFEncodingMap[value];
} else if (value < 247) { } else if (value < 247) {
token = parseInt(value) - 139; token = parseInt(value, 10) - 139;
} else if (value < 251) { } else if (value < 251) {
token = ((value - 247) * 256) + aString[i++] + 108; token = ((value - 247) * 256) + aString[i++] + 108;
} else if (value < 255) { } else if (value < 255) {
@ -113,7 +113,7 @@ function readFontDictData(aString, aMap) {
while (!parsed) { while (!parsed) {
var byte = aString[i++]; 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++) { for (var j = 0; j < nibbles.length; j++) {
var nibble = nibbles[j]; var nibble = nibbles[j];
switch (nibble) { switch (nibble) {
@ -144,7 +144,7 @@ function readFontDictData(aString, aMap) {
} else if (value <= 31) { } else if (value <= 31) {
token = aMap[value]; token = aMap[value];
} else if (value <= 246) { } else if (value <= 246) {
token = parseInt(value) - 139; token = parseInt(value, 10) - 139;
} else if (value <= 250) { } else if (value <= 250) {
token = ((value - 247) * 256) + aString[i++] + 108; token = ((value - 247) * 256) + aString[i++] + 108;
} else if (value <= 254) { } else if (value <= 254) {
@ -193,7 +193,7 @@ function readFontIndexData(aStream, aIsByte) {
} }
error(offsize + ' is not a valid offset size'); error(offsize + ' is not a valid offset size');
return null; return null;
}; }
var offsets = []; var offsets = [];
for (var i = 0; i < count + 1; i++) for (var i = 0; i < count + 1; i++)
@ -236,7 +236,7 @@ var Type2Parser = function(aFilePath) {
function dump(aStr) { function dump(aStr) {
if (debug) if (debug)
log(aStr); log(aStr);
}; }
function parseAsToken(aString, aMap) { function parseAsToken(aString, aMap) {
var decoded = readFontDictData(aString, aMap); var decoded = readFontDictData(aString, aMap);
@ -277,7 +277,7 @@ var Type2Parser = function(aFilePath) {
} }
} }
} }
}; }
this.parse = function(aStream) { this.parse = function(aStream) {
font.set('major', aStream.getByte()); font.set('major', aStream.getByte());
@ -353,7 +353,7 @@ var Type2Parser = function(aFilePath) {
aStream.pos = charsetEntry; aStream.pos = charsetEntry;
var charset = readCharset(aStream, charStrings); var charset = readCharset(aStream, charStrings);
} }
} };
}; };
/* /*

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

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

View File

@ -10,6 +10,7 @@
<script type="text/javascript" src="../fonts.js"></script> <script type="text/javascript" src="../fonts.js"></script>
<script type="text/javascript" src="../crypto.js"></script> <script type="text/javascript" src="../crypto.js"></script>
<script type="text/javascript" src="../glyphlist.js"></script> <script type="text/javascript" src="../glyphlist.js"></script>
<script type="text/javascript" src="../metrics.js"></script>
</head> </head>
<body> <body>

View File

@ -1,4 +1,4 @@
/* -*- Mode: Java; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- / /* -*- Mode: Java; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
/* vim: set shiftwidth=2 tabstop=2 autoindent cindent expandtab: */ /* vim: set shiftwidth=2 tabstop=2 autoindent cindent expandtab: */
'use strict'; 'use strict';
@ -98,7 +98,7 @@ var PDFView = {
}, },
get page() { get page() {
return parseInt(document.location.hash.substring(1)) || 1; return parseInt(document.location.hash.substring(1), 10) || 1;
}, },
open: function(url, scale) { open: function(url, scale) {
@ -170,7 +170,7 @@ var PDFView = {
} }
this.setScale(scale || kDefaultScale, true); 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.pagesRefMap = pagesRefMap;
this.destinations = pdf.catalog.destinations; this.destinations = pdf.catalog.destinations;
if (pdf.catalog.documentOutline) { if (pdf.catalog.documentOutline) {
@ -209,7 +209,7 @@ var PDFView = {
var currentHeight = kBottomMargin; var currentHeight = kBottomMargin;
var windowTop = window.pageYOffset; 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 page = pages[i - 1];
var pageHeight = page.height * page.scale + kBottomMargin; var pageHeight = page.height * page.scale + kBottomMargin;
if (currentHeight + pageHeight > windowTop) if (currentHeight + pageHeight > windowTop)
@ -219,10 +219,11 @@ var PDFView = {
} }
var windowBottom = window.pageYOffset + window.innerHeight; var windowBottom = window.pageYOffset + window.innerHeight;
for (; i <= pages.length && currentHeight < windowBottom; i++) { for (; i <= pages.length && currentHeight < windowBottom; ++i) {
var page = pages[i - 1]; var singlePage = pages[i - 1];
visiblePages.push({ id: page.id, y: currentHeight, view: page }); visiblePages.push({ id: singlePage.id, y: currentHeight,
currentHeight += page.height * page.scale + kBottomMargin; view: singlePage });
currentHeight += singlePage.height * singlePage.scale + kBottomMargin;
} }
return visiblePages; return visiblePages;
@ -256,13 +257,13 @@ var PageView = function(container, content, id, width, height,
div.removeAttribute('data-loaded'); div.removeAttribute('data-loaded');
}; };
function setupLinks(canvas, content, scale) { function setupLinks(content, scale) {
function bindLink(link, dest) { function bindLink(link, dest) {
link.onclick = function() { link.onclick = function() {
if (dest) if (dest)
PDFView.navigateTo(dest); PDFView.navigateTo(dest);
return false; return false;
} };
} }
var links = content.getLinks(); var links = content.getLinks();
for (var i = 0; i < links.length; i++) { 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 width = 0, height = 0, widthScale, heightScale;
var scale = 0; var scale = 0;
switch (dest[1].name) { switch (dest[1].name) {
default:
return;
case 'XYZ': case 'XYZ':
x = dest[2]; x = dest[2];
y = dest[3]; y = dest[3];
@ -315,6 +314,8 @@ var PageView = function(container, content, id, width, height,
height / kCssUnits; height / kCssUnits;
scale = Math.min(widthScale, heightScale); scale = Math.min(widthScale, heightScale);
break; break;
default:
return;
} }
var boundingRect = [ var boundingRect = [
@ -369,7 +370,7 @@ var PageView = function(container, content, id, width, height,
stats.begin = Date.now(); stats.begin = Date.now();
this.content.startRendering(ctx, this.updateStats); this.content.startRendering(ctx, this.updateStats);
setupLinks(canvas, this.content, this.scale); setupLinks(this.content, this.scale);
div.setAttribute('data-loaded', true); div.setAttribute('data-loaded', true);
return true; return true;
@ -593,7 +594,7 @@ window.addEventListener('pagechange', function pagechange(evt) {
}, true); }, true);
window.addEventListener('keydown', function keydown(evt) { window.addEventListener('keydown', function keydown(evt) {
switch(evt.keyCode) { switch (evt.keyCode) {
case 61: // FF/Mac '=' case 61: // FF/Mac '='
case 107: // FF '+' and '=' case 107: // FF '+' and '='
case 187: // Chrome '+' case 187: // Chrome '+'

View File

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

View File

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

@ -1,4 +1,4 @@
/* -*- Mode: Java; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- / /* -*- Mode: Java; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
/* vim: set shiftwidth=2 tabstop=2 autoindent cindent expandtab: */ /* vim: set shiftwidth=2 tabstop=2 autoindent cindent expandtab: */
'use strict'; 'use strict';
@ -25,3 +25,4 @@ var console = {
this.log('Timer:', name, Date.now() - time); this.log('Timer:', name, Date.now() - time);
} }
}; };

View File

@ -1,4 +1,4 @@
/* -*- Mode: Java; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- / /* -*- Mode: Java; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
/* vim: set shiftwidth=2 tabstop=2 autoindent cindent expandtab: */ /* vim: set shiftwidth=2 tabstop=2 autoindent cindent expandtab: */
'use strict'; 'use strict';
@ -56,11 +56,12 @@ var actionHandler = {
}; };
// Listen to the MainThread for data and call actionHandler on it. // Listen to the MainThread for data and call actionHandler on it.
this.onmessage = function(event) { addEventListener('message', function(event) {
var data = event.data; var data = event.data;
if (data.action in actionHandler) { if (data.action in actionHandler) {
actionHandler[data.action].call(this, data.data); actionHandler[data.action].call(this, data.data);
} else { } else {
throw 'Unkown action from worker: ' + data.action; throw 'Unkown action from worker: ' + data.action;
} }
}; });

View File

@ -1,4 +1,4 @@
/* -*- Mode: Java; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- / /* -*- Mode: Java; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
/* vim: set shiftwidth=2 tabstop=2 autoindent cindent expandtab: */ /* vim: set shiftwidth=2 tabstop=2 autoindent cindent expandtab: */
'use strict'; 'use strict';
@ -11,8 +11,10 @@ var console = {
action: 'log', action: 'log',
data: args data: args
}); });
}, }
};
var consoleUtils = {
time: function(name) { time: function(name) {
consoleTimer[name] = Date.now(); consoleTimer[name] = Date.now();
}, },
@ -22,7 +24,7 @@ var console = {
if (time == null) { if (time == null) {
throw 'Unkown timer name ' + name; 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. // Listen for messages from the main thread.
var pdfDocument = null; var pdfDocument = null;
onmessage = function(event) { addEventListener('message', function(event) {
var data = event.data; var data = event.data;
// If there is no pdfDocument yet, then the sent data is the PDFDocument. // If there is no pdfDocument yet, then the sent data is the PDFDocument.
if (!pdfDocument) { if (!pdfDocument) {
@ -55,10 +57,10 @@ onmessage = function(event) {
} }
// User requested to render a certain page. // User requested to render a certain page.
else { else {
console.time('compile'); consoleUtils.time('compile');
// Let's try to render the first page... // 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 pdfToCssUnitsCoef = 96.0 / 72.0;
var pageWidth = (page.mediaBox[2] - page.mediaBox[0]) * pdfToCssUnitsCoef; var pageWidth = (page.mediaBox[2] - page.mediaBox[0]) * pdfToCssUnitsCoef;
@ -77,19 +79,19 @@ onmessage = function(event) {
var fonts = []; var fonts = [];
var gfx = new CanvasGraphics(canvas.getContext('2d'), CanvasProxy); var gfx = new CanvasGraphics(canvas.getContext('2d'), CanvasProxy);
page.compile(gfx, fonts); page.compile(gfx, fonts);
console.timeEnd('compile'); consoleUtils.timeEnd('compile');
// Send fonts to the main thread. // Send fonts to the main thread.
console.time('fonts'); consoleUtils.time('fonts');
postMessage({ postMessage({
action: 'fonts', action: 'fonts',
data: fonts data: fonts
}); });
console.timeEnd('fonts'); consoleUtils.timeEnd('fonts');
console.time('display'); consoleUtils.time('display');
page.display(gfx); page.display(gfx);
canvas.flush(); canvas.flush();
console.timeEnd('display'); consoleUtils.timeEnd('display');
} }
}; });