Browse Source

Merge branch 'master' of github.com:andreasgal/pdf.js into predictor

sbarman 14 years ago
parent
commit
16c0e0410c
  1. 2
      Makefile
  2. 70
      README.md
  3. 101
      charsets.js
  4. 49
      crypto.js
  5. 165
      fonts.js
  6. 684
      pdf.js
  7. 32
      test/driver.js
  8. 105
      test/pdfs/extgstate.pdf
  9. 1
      test/pdfs/f1040.pdf.link
  10. 1
      test/pdfs/hudsonsurvey.pdf.link
  11. 18
      test/test_manifest.json
  12. 2
      test/test_slave.html
  13. 394
      utils/cffStandardStrings.js
  14. 38
      utils/fonts_utils.js
  15. 15
      web/viewer.css
  16. 4
      web/viewer.html
  17. 52
      web/viewer.js

2
Makefile

@ -12,6 +12,8 @@ PDF_JS_FILES = \
pdf.js \ pdf.js \
crypto.js \ crypto.js \
fonts.js \ fonts.js \
metrics.js \
charsets.js \
glyphlist.js \ glyphlist.js \
$(NULL) $(NULL)

70
README.md

@ -27,6 +27,32 @@ For an online demo, visit:
This demo provides an interactive interface for displaying and browsing PDFs This demo provides an interactive interface for displaying and browsing PDFs
using the pdf.js API. using the pdf.js API.
**Getting the code**
To get a local copy of the current code, clone it using git:
```bash
git clone git://github.com/andreasgal/pdf.js.git pdfjs
cd pdfjs
```
Next, you need to start a local web server as some browsers don't allow opening
PDF files for a file:// url:
```bash
make server
```
If everything worked out, you can now serve
http://localhost:8888/web/viewer.html
You can also view all the test pdf files on the right side serving
http://localhost:8888/test/pdfs/?frame
**Hello world** **Hello world**
For a "hello world" example, take a look at: For a "hello world" example, take a look at:
@ -38,6 +64,20 @@ in a custom project.
## 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.
If you __don't want to hack__ on the project or have short spare times, you still
can help! Just open PDFs in the
[online demo](http://andreasgal.github.com/pdf.js/web/viewer.html) and report
any breakage in rendering.
## Running the Tests ## Running the Tests
pdf.js comes with browser-level regression tests that allow one to probe pdf.js comes with browser-level regression tests that allow one to probe
@ -64,16 +104,6 @@ images. The test type `load` simply tests whether the file loads without
raising any errors. 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 ## Additional resources
Our demo site is here: Our demo site is here:
@ -97,8 +127,28 @@ Join our mailing list:
Subscribe either using lists.mozilla.org or Google Groups: Subscribe either using lists.mozilla.org or Google Groups:
https://lists.mozilla.org/listinfo/dev-pdf-js https://lists.mozilla.org/listinfo/dev-pdf-js
https://groups.google.com/group/mozilla.dev.pdf-js/topics https://groups.google.com/group/mozilla.dev.pdf-js/topics
Talk to us on IRC: Talk to us on IRC:
#pdfjs on irc.mozilla.org #pdfjs on irc.mozilla.org
## Additional resources to understand the structure of PDF
A really basic overview of PDF is described here:
http://partners.adobe.com/public/developer/en/livecycle/lc_pdf_overview_format.pdf
A more detailed file example:
http://gnupdf.org/Introduction_to_PDF
The PDF specification itself is an ISO and not free available. However, there is
a "PDF Reference" from Adobe:
http://wwwimages.adobe.com/www.adobe.com/content/dam/Adobe/en/devnet/pdf/pdfs/pdf_reference_1-7.pdf
Recommanded chapters to read: "2. Overview", "3.4 File Structure",
"4.1 Graphics Objects" that lists the PDF commands.

101
charsets.js

@ -0,0 +1,101 @@
var ISOAdobeCharset = [
'.notdef', 'space', 'exclam', 'quotedbl', 'numbersign', 'dollar',
'percent', 'ampersand', 'quoteright', 'parenleft', 'parenright',
'asterisk', 'plus', 'comma', 'hyphen', 'period', 'slash', 'zero',
'one', 'two', 'three', 'four', 'five', 'six', 'seven', 'eight',
'nine', 'colon', 'semicolon', 'less', 'equal', 'greater', 'question',
'at', 'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J', 'K', 'L', 'M',
'N', 'O', 'P', 'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X', 'Y', 'Z',
'bracketleft', 'backslash', 'bracketright', 'asciicircum', 'underscore',
'quoteleft', 'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j', 'k', 'l',
'm', 'n', 'o', 'p', 'q', 'r', 's', 't', 'u', 'v', 'w', 'x', 'y', 'z',
'braceleft', 'bar', 'braceright', 'asciitilde', 'exclamdown', 'cent',
'sterling', 'fraction', 'yen', 'florin', 'section', 'currency',
'quotesingle', 'quotedblleft', 'guillemotleft', 'guilsinglleft',
'guilsinglright', 'fi', 'fl', 'endash', 'dagger', 'daggerdbl',
'periodcentered', 'paragraph', 'bullet', 'quotesinglbase',
'quotedblbase', 'quotedblright', 'guillemotright', 'ellipsis',
'perthousand', 'questiondown', 'grave', 'acute', 'circumflex', 'tilde',
'macron', 'breve', 'dotaccent', 'dieresis', 'ring', 'cedilla',
'hungarumlaut', 'ogonek', 'caron', 'emdash', 'AE', 'ordfeminine',
'Lslash', 'Oslash', 'OE', 'ordmasculine', 'ae', 'dotlessi', 'lslash',
'oslash', 'oe', 'germandbls', 'onesuperior', 'logicalnot', 'mu',
'trademark', 'Eth', 'onehalf', 'plusminus', 'Thorn', 'onequarter',
'divide', 'brokenbar', 'degree', 'thorn', 'threequarters', 'twosuperior',
'registered', 'minus', 'eth', 'multiply', 'threesuperior', 'copyright',
'Aacute', 'Acircumflex', 'Adieresis', 'Agrave', 'Aring', 'Atilde',
'Ccedilla', 'Eacute', 'Ecircumflex', 'Edieresis', 'Egrave', 'Iacute',
'Icircumflex', 'Idieresis', 'Igrave', 'Ntilde', 'Oacute', 'Ocircumflex',
'Odieresis', 'Ograve', 'Otilde', 'Scaron', 'Uacute', 'Ucircumflex',
'Udieresis', 'Ugrave', 'Yacute', 'Ydieresis', 'Zcaron', 'aacute',
'acircumflex', 'adieresis', 'agrave', 'aring', 'atilde', 'ccedilla',
'eacute', 'ecircumflex', 'edieresis', 'egrave', 'iacute', 'icircumflex',
'idieresis', 'igrave', 'ntilde', 'oacute', 'ocircumflex', 'odieresis',
'ograve', 'otilde', 'scaron', 'uacute', 'ucircumflex', 'udieresis',
'ugrave', 'yacute', 'ydieresis', 'zcaron'
];
var ExpertCharset = [
'.notdef', 'space', 'exclamsmall', 'Hungarumlautsmall', 'dollaroldstyle',
'dollarsuperior', 'ampersandsmall', 'Acutesmall', 'parenleftsuperior',
'parenrightsuperior', 'twodotenleader', 'onedotenleader', 'comma',
'hyphen', 'period', 'fraction', 'zerooldstyle', 'oneoldstyle',
'twooldstyle', 'threeoldstyle', 'fouroldstyle', 'fiveoldstyle',
'sixoldstyle', 'sevenoldstyle', 'eightoldstyle', 'nineoldstyle',
'colon', 'semicolon', 'commasuperior', 'threequartersemdash',
'periodsuperior', 'questionsmall', 'asuperior', 'bsuperior',
'centsuperior', 'dsuperior', 'esuperior', 'isuperior', 'lsuperior',
'msuperior', 'nsuperior', 'osuperior', 'rsuperior', 'ssuperior',
'tsuperior', 'ff', 'fi', 'fl', 'ffi', 'ffl', 'parenleftinferior',
'parenrightinferior', 'Circumflexsmall', 'hyphensuperior', 'Gravesmall',
'Asmall', 'Bsmall', 'Csmall', 'Dsmall', 'Esmall', 'Fsmall', 'Gsmall',
'Hsmall', 'Ismall', 'Jsmall', 'Ksmall', 'Lsmall', 'Msmall', 'Nsmall',
'Osmall', 'Psmall', 'Qsmall', 'Rsmall', 'Ssmall', 'Tsmall', 'Usmall',
'Vsmall', 'Wsmall', 'Xsmall', 'Ysmall', 'Zsmall', 'colonmonetary',
'onefitted', 'rupiah', 'Tildesmall', 'exclamdownsmall', 'centoldstyle',
'Lslashsmall', 'Scaronsmall', 'Zcaronsmall', 'Dieresissmall',
'Brevesmall', 'Caronsmall', 'Dotaccentsmall', 'Macronsmall',
'figuredash', 'hypheninferior', 'Ogoneksmall', 'Ringsmall',
'Cedillasmall', 'onequarter', 'onehalf', 'threequarters',
'questiondownsmall', 'oneeighth', 'threeeighths', 'fiveeighths',
'seveneighths', 'onethird', 'twothirds', 'zerosuperior', 'onesuperior',
'twosuperior', 'threesuperior', 'foursuperior', 'fivesuperior',
'sixsuperior', 'sevensuperior', 'eightsuperior', 'ninesuperior',
'zeroinferior', 'oneinferior', 'twoinferior', 'threeinferior',
'fourinferior', 'fiveinferior', 'sixinferior', 'seveninferior',
'eightinferior', 'nineinferior', 'centinferior', 'dollarinferior',
'periodinferior', 'commainferior', 'Agravesmall', 'Aacutesmall',
'Acircumflexsmall', 'Atildesmall', 'Adieresissmall', 'Aringsmall',
'AEsmall', 'Ccedillasmall', 'Egravesmall', 'Eacutesmall',
'Ecircumflexsmall', 'Edieresissmall', 'Igravesmall', 'Iacutesmall',
'Icircumflexsmall', 'Idieresissmall', 'Ethsmall', 'Ntildesmall',
'Ogravesmall', 'Oacutesmall', 'Ocircumflexsmall', 'Otildesmall',
'Odieresissmall', 'OEsmall', 'Oslashsmall', 'Ugravesmall', 'Uacutesmall',
'Ucircumflexsmall', 'Udieresissmall', 'Yacutesmall', 'Thornsmall',
'Ydieresissmall'
];
var ExpertSubsetCharset = [
'.notdef', 'space', 'dollaroldstyle', 'dollarsuperior',
'parenleftsuperior', 'parenrightsuperior', 'twodotenleader',
'onedotenleader', 'comma', 'hyphen', 'period', 'fraction',
'zerooldstyle', 'oneoldstyle', 'twooldstyle', 'threeoldstyle',
'fouroldstyle', 'fiveoldstyle', 'sixoldstyle', 'sevenoldstyle',
'eightoldstyle', 'nineoldstyle', 'colon', 'semicolon', 'commasuperior',
'threequartersemdash', 'periodsuperior', 'asuperior', 'bsuperior',
'centsuperior', 'dsuperior', 'esuperior', 'isuperior', 'lsuperior',
'msuperior', 'nsuperior', 'osuperior', 'rsuperior', 'ssuperior',
'tsuperior', 'ff', 'fi', 'fl', 'ffi', 'ffl', 'parenleftinferior',
'parenrightinferior', 'hyphensuperior', 'colonmonetary', 'onefitted',
'rupiah', 'centoldstyle', 'figuredash', 'hypheninferior', 'onequarter',
'onehalf', 'threequarters', 'oneeighth', 'threeeighths', 'fiveeighths',
'seveneighths', 'onethird', 'twothirds', 'zerosuperior', 'onesuperior',
'twosuperior', 'threesuperior', 'foursuperior', 'fivesuperior',
'sixsuperior', 'sevensuperior', 'eightsuperior', 'ninesuperior',
'zeroinferior', 'oneinferior', 'twoinferior', 'threeinferior',
'fourinferior', 'fiveinferior', 'sixinferior', 'seveninferior',
'eightinferior', 'nineinferior', 'centinferior', 'dollarinferior',
'periodinferior', 'commainferior'
];

49
crypto.js

@ -3,7 +3,7 @@
'use strict'; 'use strict';
var ARCFourCipher = (function() { var ARCFourCipher = (function aRCFourCipher() {
function constructor(key) { function constructor(key) {
this.a = 0; this.a = 0;
this.b = 0; this.b = 0;
@ -21,7 +21,7 @@ var ARCFourCipher = (function() {
} }
constructor.prototype = { constructor.prototype = {
encryptBlock: function(data) { encryptBlock: function aRCFourCipherEncryptBlock(data) {
var i, n = data.length, tmp, tmp2; var i, n = data.length, tmp, tmp2;
var a = this.a, b = this.b, s = this.s; var a = this.a, b = this.b, s = this.s;
var output = new Uint8Array(n); var output = new Uint8Array(n);
@ -45,7 +45,7 @@ var ARCFourCipher = (function() {
return constructor; return constructor;
})(); })();
var md5 = (function() { var calculateMD5 = (function calculateMD5() {
var r = new Uint8Array([ var r = new Uint8Array([
7, 12, 17, 22, 7, 12, 17, 22, 7, 12, 17, 22, 7, 12, 17, 22, 7, 12, 17, 22, 7, 12, 17, 22, 7, 12, 17, 22, 7, 12, 17, 22,
5, 9, 14, 20, 5, 9, 14, 20, 5, 9, 14, 20, 5, 9, 14, 20, 5, 9, 14, 20, 5, 9, 14, 20, 5, 9, 14, 20, 5, 9, 14, 20,
@ -129,12 +129,12 @@ var md5 = (function() {
return hash; return hash;
})(); })();
var NullCipher = (function() { var NullCipher = (function nullCipher() {
function constructor() { function constructor() {
} }
constructor.prototype = { constructor.prototype = {
decryptBlock: function(data) { decryptBlock: function nullCipherDecryptBlock(data) {
return data; return data;
} }
}; };
@ -142,7 +142,7 @@ var NullCipher = (function() {
return constructor; return constructor;
})(); })();
var AES128Cipher = (function() { var AES128Cipher = (function aES128Cipher() {
var rcon = new Uint8Array([ var rcon = new Uint8Array([
0x8d, 0x01, 0x02, 0x04, 0x08, 0x10, 0x20, 0x40, 0x80, 0x1b, 0x36, 0x6c, 0x8d, 0x01, 0x02, 0x04, 0x08, 0x10, 0x20, 0x40, 0x80, 0x1b, 0x36, 0x6c,
0xd8, 0xab, 0x4d, 0x9a, 0x2f, 0x5e, 0xbc, 0x63, 0xc6, 0x97, 0x35, 0x6a, 0xd8, 0xab, 0x4d, 0x9a, 0x2f, 0x5e, 0xbc, 0x63, 0xc6, 0x97, 0x35, 0x6a,
@ -372,7 +372,7 @@ var AES128Cipher = (function() {
} }
constructor.prototype = { constructor.prototype = {
decryptBlock: function(data) { decryptBlock: function aES128CipherDecryptBlock(data) {
var i, sourceLength = data.length; var i, sourceLength = data.length;
var buffer = this.buffer, bufferLength = this.bufferPosition; var buffer = this.buffer, bufferLength = this.bufferPosition;
// waiting for IV values -- they are at the start of the stream // waiting for IV values -- they are at the start of the stream
@ -395,19 +395,21 @@ var AES128Cipher = (function() {
return constructor; return constructor;
})(); })();
var CipherTransform = (function() { var CipherTransform = (function cipherTransform() {
function constructor(stringCipherConstructor, streamCipherConstructor) { function constructor(stringCipherConstructor, streamCipherConstructor) {
this.stringCipherConstructor = stringCipherConstructor; this.stringCipherConstructor = stringCipherConstructor;
this.streamCipherConstructor = streamCipherConstructor; this.streamCipherConstructor = streamCipherConstructor;
} }
constructor.prototype = { constructor.prototype = {
createStream: function(stream) { createStream: function cipherTransformCreateStream(stream) {
var cipher = new this.streamCipherConstructor(); var cipher = new this.streamCipherConstructor();
return new DecryptStream(stream, function(data) { return new DecryptStream(stream,
return cipher.decryptBlock(data); function cipherTransformDecryptStream(data) {
}); return cipher.decryptBlock(data);
}
);
}, },
decryptString: function(s) { decryptString: function cipherTransformDecryptString(s) {
var cipher = new this.stringCipherConstructor(); var cipher = new this.stringCipherConstructor();
var data = stringToBytes(s); var data = stringToBytes(s);
data = cipher.decryptBlock(data); data = cipher.decryptBlock(data);
@ -417,7 +419,7 @@ var CipherTransform = (function() {
return constructor; return constructor;
})(); })();
var CipherTransformFactory = (function() { var CipherTransformFactory = (function cipherTransformFactory() {
function prepareKeyData(fileId, password, ownerPassword, userPassword, function prepareKeyData(fileId, password, ownerPassword, userPassword,
flags, revision, keyLength, encryptMetadata) { flags, revision, keyLength, encryptMetadata) {
var defaultPasswordBytes = new Uint8Array([ var defaultPasswordBytes = new Uint8Array([
@ -450,11 +452,11 @@ var CipherTransformFactory = (function() {
hashData[i++] = 0xFF; hashData[i++] = 0xFF;
hashData[i++] = 0xFF; hashData[i++] = 0xFF;
} }
var hash = md5(hashData, 0, i); var hash = calculateMD5(hashData, 0, i);
var keyLengthInBytes = keyLength >> 3; var keyLengthInBytes = keyLength >> 3;
if (revision >= 3) { if (revision >= 3) {
for (j = 0; j < 50; ++j) { for (j = 0; j < 50; ++j) {
hash = md5(hash, 0, keyLengthInBytes); hash = calculateMD5(hash, 0, keyLengthInBytes);
} }
} }
var encryptionKey = hash.subarray(0, keyLengthInBytes); var encryptionKey = hash.subarray(0, keyLengthInBytes);
@ -467,7 +469,7 @@ var CipherTransformFactory = (function() {
for (j = 0, n = fileId.length; j < n; ++j) for (j = 0, n = fileId.length; j < n; ++j)
hashData[i++] = fileId[j]; hashData[i++] = fileId[j];
cipher = new ARCFourCipher(encryptionKey); cipher = new ARCFourCipher(encryptionKey);
var checkData = cipher.encryptBlock(md5(hashData, 0, i)); var checkData = cipher.encryptBlock(calculateMD5(hashData, 0, i));
n = encryptionKey.length; n = encryptionKey.length;
var derrivedKey = new Uint8Array(n), k; var derrivedKey = new Uint8Array(n), k;
for (j = 1; j <= 19; ++j) { for (j = 1; j <= 19; ++j) {
@ -542,7 +544,7 @@ var CipherTransformFactory = (function() {
key[i++] = 0x6C; key[i++] = 0x6C;
key[i++] = 0x54; key[i++] = 0x54;
} }
var hash = md5(key, 0, i); var hash = calculateMD5(key, 0, i);
return hash.subarray(0, Math.min(encryptionKey.length + 5, 16)); return hash.subarray(0, Math.min(encryptionKey.length + 5, 16));
} }
@ -552,18 +554,18 @@ var CipherTransformFactory = (function() {
if (cryptFilter != null) if (cryptFilter != null)
cfm = cryptFilter.get('CFM'); cfm = cryptFilter.get('CFM');
if (!cfm || cfm.name == 'None') { if (!cfm || cfm.name == 'None') {
return function() { return function cipherTransformFactoryBuildCipherConstructorNone() {
return new NullCipher(); return new NullCipher();
}; };
} }
if ('V2' == cfm.name) { if ('V2' == cfm.name) {
return function() { return function cipherTransformFactoryBuildCipherConstructorV2() {
return new ARCFourCipher( return new ARCFourCipher(
buildObjectKey(num, gen, key, false)); buildObjectKey(num, gen, key, false));
}; };
} }
if ('AESV2' == cfm.name) { if ('AESV2' == cfm.name) {
return function() { return function cipherTransformFactoryBuildCipherConstructorAESV2() {
return new AES128Cipher( return new AES128Cipher(
buildObjectKey(num, gen, key, true)); buildObjectKey(num, gen, key, true));
}; };
@ -573,7 +575,8 @@ var CipherTransformFactory = (function() {
} }
constructor.prototype = { constructor.prototype = {
createCipherTransform: function(num, gen) { createCipherTransform: function buildCipherCreateCipherTransform(num,
gen) {
if (this.algorithm == 4) { if (this.algorithm == 4) {
return new CipherTransform( return new CipherTransform(
buildCipherConstructor(this.cf, this.stmf, buildCipherConstructor(this.cf, this.stmf,
@ -583,7 +586,7 @@ var CipherTransformFactory = (function() {
} }
// algorithms 1 and 2 // algorithms 1 and 2
var key = buildObjectKey(num, gen, this.encryptionKey, false); var key = buildObjectKey(num, gen, this.encryptionKey, false);
var cipherConstructor = function() { var cipherConstructor = function buildCipherCipherConstructor() {
return new ARCFourCipher(key); return new ARCFourCipher(key);
}; };
return new CipherTransform(cipherConstructor, cipherConstructor); return new CipherTransform(cipherConstructor, cipherConstructor);

165
fonts.js

@ -124,7 +124,7 @@ var serifFonts = {
var FontLoader = { var FontLoader = {
listeningForFontLoad: false, listeningForFontLoad: false,
bind: function(fonts, callback) { bind: function fontLoaderBind(fonts, callback) {
function checkFontsLoaded() { function checkFontsLoaded() {
for (var i = 0; i < objs.length; i++) { for (var i = 0; i < objs.length; i++) {
var fontObj = objs[i]; var fontObj = objs[i];
@ -180,7 +180,8 @@ var FontLoader = {
// loaded in a subdocument. It's expected that the load of |rules| // loaded in a subdocument. It's expected that the load of |rules|
// has already started in this (outer) document, so that they should // has already started in this (outer) document, so that they should
// be ordered before the load in the subdocument. // be ordered before the load in the subdocument.
prepareFontLoadEvent: function(rules, names, objs) { prepareFontLoadEvent: function fontLoaderPrepareFontLoadEvent(rules, names,
objs) {
/** Hack begin */ /** Hack begin */
// There's no event when a font has finished downloading so the // There's no event when a font has finished downloading so the
// following code is a dirty hack to 'guess' when a font is // following code is a dirty hack to 'guess' when a font is
@ -219,7 +220,7 @@ var FontLoader = {
if (!this.listeningForFontLoad) { if (!this.listeningForFontLoad) {
window.addEventListener( window.addEventListener(
'message', 'message',
function(e) { function fontLoaderMessage(e) {
var fontNames = JSON.parse(e.data); var fontNames = JSON.parse(e.data);
for (var i = 0; i < objs.length; ++i) { for (var i = 0; i < objs.length; ++i) {
var font = objs[i]; var font = objs[i];
@ -247,7 +248,7 @@ var FontLoader = {
fontNamesArray += '"' + names[i] + '", '; fontNamesArray += '"' + names[i] + '", ';
} }
src += ' var fontNames=[' + fontNamesArray + '];\n'; src += ' var fontNames=[' + fontNamesArray + '];\n';
src += ' window.onload = function () {\n'; src += ' window.onload = function fontLoaderOnload() {\n';
src += ' parent.postMessage(JSON.stringify(fontNames), "*");\n'; src += ' parent.postMessage(JSON.stringify(fontNames), "*");\n';
src += ' }'; src += ' }';
src += '</script></head><body>'; src += '</script></head><body>';
@ -447,13 +448,14 @@ var Font = (function Font() {
} }
var data; var data;
switch (properties.type) { var type = properties.type;
switch (type) {
case 'Type1': case 'Type1':
case 'CIDFontType0': case 'CIDFontType0':
this.mimetype = 'font/opentype'; this.mimetype = 'font/opentype';
var subtype = properties.subtype; var subtype = properties.subtype;
var cff = (subtype === 'Type1C') ? var cff = (subtype == 'Type1C' || subtype == 'CIDFontType0C') ?
new Type2CFF(file, properties) : 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
@ -475,7 +477,7 @@ var Font = (function Font() {
} }
this.data = data; this.data = data;
this.type = properties.type; this.type = type;
this.textMatrix = properties.textMatrix; this.textMatrix = properties.textMatrix;
this.defaultWidth = properties.defaultWidth; this.defaultWidth = properties.defaultWidth;
this.loadedName = getUniqueName(); this.loadedName = getUniqueName();
@ -598,7 +600,7 @@ var Font = (function Font() {
var length = glyphs.length; var length = glyphs.length;
for (var n = 0; n < length; ++n) for (var n = 0; n < length; ++n)
codes.push({ unicode: glyphs[n].unicode, code: n }); codes.push({ unicode: glyphs[n].unicode, code: n });
codes.sort(function(a, b) { codes.sort(function fontGetRangesSort(a, b) {
return a.unicode - b.unicode; return a.unicode - b.unicode;
}); });
@ -927,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 fontReplaceCMapTableSort(a, b) {
return ((a.platformID << 16) + a.encodingID) - return ((a.platformID << 16) + a.encodingID) -
((b.platformID << 16) + b.encodingID); ((b.platformID << 16) + b.encodingID);
}); });
@ -1060,11 +1062,11 @@ var Font = (function Font() {
var itemSize, itemDecode, itemEncode; var itemSize, itemDecode, itemEncode;
if (isGlyphLocationsLong) { if (isGlyphLocationsLong) {
itemSize = 4; itemSize = 4;
itemDecode = function(data, offset) { itemDecode = function fontItemDecodeLong(data, offset) {
return (data[offset] << 24) | (data[offset + 1] << 16) | return (data[offset] << 24) | (data[offset + 1] << 16) |
(data[offset + 2] << 8) | data[offset + 3]; (data[offset + 2] << 8) | data[offset + 3];
}; };
itemEncode = function(data, offset, value) { itemEncode = function fontItemEncodeLong(data, offset, value) {
data[offset] = (value >>> 24) & 0xFF; data[offset] = (value >>> 24) & 0xFF;
data[offset + 1] = (value >> 16) & 0xFF; data[offset + 1] = (value >> 16) & 0xFF;
data[offset + 2] = (value >> 8) & 0xFF; data[offset + 2] = (value >> 8) & 0xFF;
@ -1072,10 +1074,10 @@ var Font = (function Font() {
}; };
} else { } else {
itemSize = 2; itemSize = 2;
itemDecode = function(data, offset) { itemDecode = function fontItemDecode(data, offset) {
return (data[offset] << 9) | (data[offset + 1] << 1); return (data[offset] << 9) | (data[offset + 1] << 1);
}; };
itemEncode = function(data, offset, value) { itemEncode = function fontItemEncode(data, offset, value) {
data[offset] = (value >> 9) & 0xFF; data[offset] = (value >> 9) & 0xFF;
data[offset + 1] = (value >> 1) & 0xFF; data[offset + 1] = (value >> 1) & 0xFF;
}; };
@ -1322,7 +1324,7 @@ var Font = (function Font() {
'cmap': createCMapTable(charstrings.slice(), font.glyphIds), 'cmap': createCMapTable(charstrings.slice(), font.glyphIds),
// Font header // Font header
'head': (function() { 'head': (function fontFieldsHead() {
return stringToArray( return stringToArray(
'\x00\x01\x00\x00' + // Version number '\x00\x01\x00\x00' + // Version number
'\x00\x00\x10\x00' + // fontRevision '\x00\x00\x10\x00' + // fontRevision
@ -1344,7 +1346,7 @@ var Font = (function Font() {
})(), })(),
// Horizontal header // Horizontal header
'hhea': (function() { 'hhea': (function fontFieldsHhea() {
return stringToArray( return stringToArray(
'\x00\x01\x00\x00' + // Version number '\x00\x01\x00\x00' + // Version number
string16(properties.ascent) + // Typographic Ascent string16(properties.ascent) + // Typographic Ascent
@ -1367,7 +1369,7 @@ var Font = (function Font() {
})(), })(),
// Horizontal metrics // Horizontal metrics
'hmtx': (function() { 'hmtx': (function fontFieldsHmtx() {
var hmtx = '\x00\x00\x00\x00'; // Fake .notdef var hmtx = '\x00\x00\x00\x00'; // Fake .notdef
for (var i = 0; i < charstrings.length; i++) { for (var i = 0; i < charstrings.length; i++) {
hmtx += string16(charstrings[i].width) + string16(0); hmtx += string16(charstrings[i].width) + string16(0);
@ -1376,7 +1378,7 @@ var Font = (function Font() {
})(), })(),
// Maximum profile // Maximum profile
'maxp': (function() { 'maxp': (function fontFieldsMaxp() {
return stringToArray( return stringToArray(
'\x00\x00\x50\x00' + // Version number '\x00\x00\x50\x00' + // Version number
string16(charstrings.length + 1)); // Num of glyphs string16(charstrings.length + 1)); // Num of glyphs
@ -1504,7 +1506,7 @@ var Font = (function Font() {
* program. Some of its logic depends on the Type2 charstrings * program. Some of its logic depends on the Type2 charstrings
* structure. * structure.
*/ */
var Type1Parser = function() { var Type1Parser = function type1Parser() {
/* /*
* Decrypt a Sequence of Ciphertext Bytes to Produce the Original Sequence * Decrypt a Sequence of Ciphertext Bytes to Produce the Original Sequence
* of Plaintext Bytes. The function took a key as a parameter which can be * of Plaintext Bytes. The function took a key as a parameter which can be
@ -2032,7 +2034,7 @@ var CFFStrings = [
var type1Parser = new Type1Parser(); var type1Parser = new Type1Parser();
var CFF = function(name, file, properties) { var CFF = function cFF(name, file, properties) {
// Get the data block containing glyphs and subrs informations // Get the data block containing glyphs and subrs informations
var headerBlock = file.getBytes(properties.length1); var headerBlock = file.getBytes(properties.length1);
type1Parser.extractFontHeader(headerBlock, properties); type1Parser.extractFontHeader(headerBlock, properties);
@ -2232,7 +2234,7 @@ CFF.prototype = {
'names': this.createCFFIndexHeader([name]), 'names': this.createCFFIndexHeader([name]),
'topDict': (function topDict(self) { 'topDict': (function topDict(self) {
return function() { return function cFFWrapTopDict() {
var header = '\x00\x01\x01\x01'; var header = '\x00\x01\x01\x01';
var dict = var dict =
'\xf8\x1b\x00' + // version '\xf8\x1b\x00' + // version
@ -2309,7 +2311,7 @@ CFF.prototype = {
'charstrings': this.createCFFIndexHeader([[0x8B, 0x0E]].concat(glyphs), 'charstrings': this.createCFFIndexHeader([[0x8B, 0x0E]].concat(glyphs),
true), true),
'private': (function(self) { 'private': (function cFFWrapPrivate(self) {
var data = var data =
'\x8b\x14' + // defaultWidth '\x8b\x14' + // defaultWidth
'\x8b\x15'; // nominalWidth '\x8b\x15'; // nominalWidth
@ -2362,7 +2364,7 @@ CFF.prototype = {
} }
}; };
var Type2CFF = (function() { var Type2CFF = (function type2CFF() {
// TODO: replace parsing code with the Type2Parser in font_utils.js // TODO: replace parsing code with the Type2Parser in font_utils.js
function constructor(file, properties) { function constructor(file, properties) {
var bytes = file.getBytes(); var bytes = file.getBytes();
@ -2387,16 +2389,21 @@ var Type2CFF = (function() {
var strings = this.getStrings(stringIndex); var strings = this.getStrings(stringIndex);
var baseDict = this.parseDict(dictIndex.get(0)); var baseDict = this.parseDict(dictIndex.get(0).data);
var topDict = this.getTopDict(baseDict, strings); var topDict = this.getTopDict(baseDict, strings);
var bytes = this.bytes; var bytes = this.bytes;
var privateDict = {};
var privateInfo = topDict.Private; var privateInfo = topDict.Private;
var privOffset = privateInfo[1], privLength = privateInfo[0]; if (privateInfo) {
var privBytes = bytes.subarray(privOffset, privOffset + privLength); var privOffset = privateInfo[1], privLength = privateInfo[0];
baseDict = this.parseDict(privBytes); var privBytes = bytes.subarray(privOffset, privOffset + privLength);
var privDict = this.getPrivDict(baseDict, strings); baseDict = this.parseDict(privBytes);
privateDict = this.getPrivDict(baseDict, strings);
} else {
privateDict.defaultWidthX = properties.defaultWidth;
}
var charStrings = this.parseIndex(topDict.CharStrings); var charStrings = this.parseIndex(topDict.CharStrings);
var charset = this.parseCharsets(topDict.charset, var charset = this.parseCharsets(topDict.charset,
@ -2412,10 +2419,37 @@ var Type2CFF = (function() {
if (hasSupplement) if (hasSupplement)
bytes[topDict.Encoding] = 0; bytes[topDict.Encoding] = 0;
// The CFF specification state that the 'dotsection' command
// (12, 0) is deprecated and treated as a no-op, but all Type2
// charstrings processors should support them. Unfortunately
// the font sanitizer don't. As a workaround the sequence (12, 0)
// is replaced by a useless (0, hmoveto).
var count = charStrings.length;
for (var i = 0; i < count; i++) {
var charstring = charStrings.get(i);
var start = charstring.start;
var data = charstring.data;
var length = data.length;
for (var j = 0; j <= length; j) {
var value = data[j++];
if (value == 12 && data[j++] == 0) {
bytes[start + j - 2] = 139;
bytes[start + j - 1] = 22;
} else if (value === 28) {
j += 2;
} else if (value >= 247 && value <= 254) {
j++;
} else if (value == 255) {
j += 4;
}
}
}
// charstrings contains info about glyphs (one element per glyph // charstrings contains info about glyphs (one element per glyph
// containing mappings for {unicode, width}) // containing mappings for {unicode, width})
var charstrings = this.getCharStrings(charset, charStrings, var charstrings = this.getCharStrings(charset, charStrings,
privDict, this.properties); privateDict, this.properties);
// create the mapping between charstring and glyph id // create the mapping between charstring and glyph id
var glyphIds = []; var glyphIds = [];
@ -2432,10 +2466,8 @@ var Type2CFF = (function() {
}, },
getCharStrings: function cff_charstrings(charsets, charStrings, getCharStrings: function cff_charstrings(charsets, charStrings,
privDict, properties) { privateDict, properties) {
var defaultWidth = privDict['defaultWidthX']; var defaultWidth = privateDict['defaultWidthX'];
var nominalWidth = privDict['nominalWidthX'];
var charstrings = []; var charstrings = [];
var differences = properties.differences; var differences = properties.differences;
var index = 0; var index = 0;
@ -2472,7 +2504,9 @@ var Type2CFF = (function() {
} }
// sort the array by the unicode value // sort the array by the unicode value
charstrings.sort(function(a, b) {return a.unicode - b.unicode}); charstrings.sort(function type2CFFGetCharStringsSort(a, b) {
return a.unicode - b.unicode;
});
return charstrings; return charstrings;
}, },
@ -2492,8 +2526,8 @@ var Type2CFF = (function() {
if (pos == 0 || pos == 1) { if (pos == 0 || pos == 1) {
var gid = 1; var gid = 1;
var baseEncoding = var baseEncoding = pos ? Encodings.ExpertEncoding.slice() :
pos ? Encodings.ExpertEncoding : Encodings.StandardEncoding; Encodings.StandardEncoding.slice();
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)
@ -2538,37 +2572,42 @@ var Type2CFF = (function() {
}, },
parseCharsets: function cff_parsecharsets(pos, length, strings) { parseCharsets: function cff_parsecharsets(pos, length, strings) {
if (pos == 0) {
return ISOAdobeCharset.slice();
} else if (pos == 1) {
return ExpertCharset.slice();
} else if (pos == 2) {
return ExpertSubsetCharset.slice();
}
var bytes = this.bytes; var bytes = this.bytes;
var format = bytes[pos++]; var format = bytes[pos++];
var charset = ['.notdef']; var charset = ['.notdef'];
// subtract 1 for the .notdef glyph // subtract 1 for the .notdef glyph
length -= 1; length -= 1;
switch (format) { switch (format) {
case 0: case 0:
for (var i = 0; i < length; ++i) { for (var i = 0; i < length; i++) {
var id = bytes[pos++]; var sid = (bytes[pos++] << 8) | bytes[pos++];
id = (id << 8) | bytes[pos++]; charset.push(strings[sid]);
charset.push(strings[id]);
} }
break; break;
case 1: case 1:
while (charset.length <= length) { while (charset.length <= length) {
var first = bytes[pos++]; var sid = (bytes[pos++] << 8) | bytes[pos++];
first = (first << 8) | bytes[pos++]; var count = bytes[pos++];
var numLeft = bytes[pos++]; for (var i = 0; i <= count; i++)
for (var i = 0; i <= numLeft; ++i) charset.push(strings[sid++]);
charset.push(strings[first++]);
} }
break; break;
case 2: case 2:
while (charset.length <= length) { while (charset.length <= length) {
var first = bytes[pos++]; var sid = (bytes[pos++] << 8) | bytes[pos++];
first = (first << 8) | bytes[pos++]; var count = (bytes[pos++] << 8) | bytes[pos++];
var numLeft = bytes[pos++]; for (var i = 0; i <= count; i++)
numLeft = (numLeft << 8) | bytes[pos++]; charset.push(strings[sid++]);
for (var i = 0; i <= numLeft; ++i)
charset.push(strings[first++]);
} }
break; break;
default: default:
@ -2643,20 +2682,20 @@ var Type2CFF = (function() {
} }
return dict; return dict;
}, },
getStrings: function cff_getstrings(stringIndex) { getStrings: function cff_getStrings(stringIndex) {
function bytesToString(bytesArr) { function bytesToString(bytesArray) {
var s = ''; var str = '';
for (var i = 0, ii = bytesArr.length; i < ii; ++i) for (var i = 0, length = bytesArray.length; i < length; i++)
s += String.fromCharCode(bytesArr[i]); str += String.fromCharCode(bytesArray[i]);
return s; return str;
} }
var stringArray = []; var stringArray = [];
for (var i = 0, ii = CFFStrings.length; i < ii; ++i) for (var i = 0, length = CFFStrings.length; i < length; i++)
stringArray.push(CFFStrings[i]); stringArray.push(CFFStrings[i]);
for (var i = 0, ii = stringIndex.length; i < ii; ++i) for (var i = 0, length = stringIndex.length; i < length; i++)
stringArray.push(bytesToString(stringIndex.get(i))); stringArray.push(bytesToString(stringIndex.get(i).data));
return stringArray; return stringArray;
}, },
@ -2702,7 +2741,7 @@ var Type2CFF = (function() {
} else if (value <= 254) { } else if (value <= 254) {
return -((value - 251) * 256) - dict[pos++] - 108; return -((value - 251) * 256) - dict[pos++] - 108;
} else { } else {
error('Incorrect byte'); error('255 is not a valid DICT command');
} }
return -1; return -1;
} }
@ -2779,7 +2818,11 @@ var Type2CFF = (function() {
var start = offsets[index]; var start = offsets[index];
var end = offsets[index + 1]; var end = offsets[index + 1];
return bytes.subarray(start, end); return {
start: start,
end: end,
data: bytes.subarray(start, end)
};
}, },
length: count, length: count,
endPos: end endPos: end

684
pdf.js

File diff suppressed because it is too large Load Diff

32
test/driver.js

@ -39,7 +39,7 @@ function load() {
var r = new XMLHttpRequest(); var r = new XMLHttpRequest();
r.open('GET', manifestFile, false); r.open('GET', manifestFile, false);
r.onreadystatechange = function(e) { r.onreadystatechange = function loadOnreadystatechange(e) {
if (r.readyState == 4) { if (r.readyState == 4) {
log('done\n'); log('done\n');
manifest = JSON.parse(r.responseText); manifest = JSON.parse(r.responseText);
@ -50,7 +50,21 @@ function load() {
r.send(null); r.send(null);
} }
function cleanup() {
var styleSheet = document.styleSheets[0];
if (styleSheet) {
while (styleSheet.cssRules.length > 0)
styleSheet.deleteRule(0);
}
var guard = document.getElementById('content-end');
var body = document.body;
while (body.lastChild !== guard)
body.removeChild(body.lastChild);
}
function nextTask() { function nextTask() {
cleanup();
if (currentTaskIdx == manifest.length) { if (currentTaskIdx == manifest.length) {
return done(); return done();
} }
@ -62,7 +76,7 @@ function nextTask() {
var r = new XMLHttpRequest(); var r = new XMLHttpRequest();
r.open('GET', task.file); r.open('GET', task.file);
r.mozResponseType = r.responseType = 'arraybuffer'; r.mozResponseType = r.responseType = 'arraybuffer';
r.onreadystatechange = function() { r.onreadystatechange = function nextTaskOnreadystatechange() {
var failure; var failure;
if (r.readyState == 4) { if (r.readyState == 4) {
var data = r.mozResponseArrayBuffer || r.mozResponse || var data = r.mozResponseArrayBuffer || r.mozResponse ||
@ -85,11 +99,15 @@ function isLastPage(task) {
return (task.pageNum > task.pdfDoc.numPages); return (task.pageNum > task.pdfDoc.numPages);
} }
function canvasToDataURL() {
return canvas.toDataURL('image/png');
}
function nextPage(task, loadError) { function nextPage(task, loadError) {
var failure = loadError || ''; var failure = loadError || '';
if (!task.pdfDoc) { if (!task.pdfDoc) {
sendTaskResult(canvas.toDataURL('image/png'), task, failure); sendTaskResult(canvasToDataURL(), task, failure);
log('done' + (failure ? ' (failed !: ' + failure + ')' : '') + '\n'); log('done' + (failure ? ' (failed !: ' + failure + ')' : '') + '\n');
++currentTaskIdx; ++currentTaskIdx;
nextTask(); nextTask();
@ -126,7 +144,7 @@ function nextPage(task, loadError) {
page.startRendering( page.startRendering(
ctx, ctx,
function(e) { function nextPageStartRendering(e) {
snapshotCurrentPage(task, (!failure && e) ? snapshotCurrentPage(task, (!failure && e) ?
('render : ' + e) : failure); ('render : ' + e) : failure);
} }
@ -146,13 +164,13 @@ function nextPage(task, loadError) {
function snapshotCurrentPage(task, failure) { function snapshotCurrentPage(task, failure) {
log('done, snapshotting... '); log('done, snapshotting... ');
sendTaskResult(canvas.toDataURL('image/png'), task, failure); sendTaskResult(canvasToDataURL(), task, failure);
log('done' + (failure ? ' (failed !: ' + failure + ')' : '') + '\n'); log('done' + (failure ? ' (failed !: ' + failure + ')' : '') + '\n');
// Set up the next request // Set up the next request
var backoff = (inFlightRequests > 0) ? inFlightRequests * 10 : 0; var backoff = (inFlightRequests > 0) ? inFlightRequests * 10 : 0;
setTimeout( setTimeout(
function() { function snapshotCurrentPageSetTimeout() {
++task.pageNum; ++task.pageNum;
nextPage(task); nextPage(task);
}, },
@ -201,7 +219,7 @@ function sendTaskResult(snapshot, task, failure) {
// (The POST URI is ignored atm.) // (The POST URI is ignored atm.)
r.open('POST', '/submit_task_results', true); r.open('POST', '/submit_task_results', true);
r.setRequestHeader('Content-Type', 'application/json'); r.setRequestHeader('Content-Type', 'application/json');
r.onreadystatechange = function(e) { r.onreadystatechange = function sendTaskResultOnreadystatechange(e) {
if (r.readyState == 4) { if (r.readyState == 4) {
inFlightRequests--; inFlightRequests--;
} }

105
test/pdfs/extgstate.pdf

@ -0,0 +1,105 @@
%PDF-1.4
%öäüß
1 0 obj
<<
/Type /Catalog
/Version /1.4
/Pages 2 0 R
>>
endobj
2 0 obj
<<
/Type /Pages
/Kids [3 0 R]
/Count 1
>>
endobj
3 0 obj
<<
/Type /Page
/MediaBox [0 0 612 792]
/Resources 4 0 R
/Parent 2 0 R
/Contents 5 0 R
>>
endobj
4 0 obj
<<
/ExtGState 6 0 R
/Font 7 0 R
/XObject <<
>>
>>
endobj
5 0 obj
<<
/Length 8 0 R
>>
stream
/GS1 gs
/F0 12 Tf
BT
100 700 Td
(I should be courier!) Tj
ET
50 600 m
400 600 l
S
endstream
endobj
6 0 obj
<<
/GS1 9 0 R
>>
endobj
7 0 obj
<<
/F0 10 0 R
>>
endobj
8 0 obj
82
endobj
9 0 obj
<<
/Type /ExtGState
/LW 10
/LC 1
/LJ 2
/ML 0.3000000119
/D [[0.0917000026 183.3300018311]
0]
/Font [10 0 R 36]
>>
endobj
10 0 obj
<<
/Type /Font
/Subtype /Type1
/BaseFont /Courier
/Encoding /WinAnsiEncoding
>>
endobj
xref
0 11
0000000000 65535 f
0000000015 00000 n
0000000078 00000 n
0000000135 00000 n
0000000239 00000 n
0000000304 00000 n
0000000441 00000 n
0000000473 00000 n
0000000505 00000 n
0000000523 00000 n
0000000653 00000 n
trailer
<<
/Root 1 0 R
/ID [<BFFF29B7D1C75EC69AC080682C2AFC5B> <BFFF29B7D1C75EC69AC080682C2AFC5B>]
/Size 11
>>
startxref
749
%%EOF

1
test/pdfs/f1040.pdf.link

@ -0,0 +1 @@
http://www.irs.gov/pub/irs-pdf/f1040.pdf

1
test/pdfs/hudsonsurvey.pdf.link

@ -0,0 +1 @@
https://issues.apache.org/jira/secure/attachment/12421789/survey.pdf

18
test/test_manifest.json

@ -139,5 +139,23 @@
"link": true, "link": true,
"rounds": 1, "rounds": 1,
"type": "load" "type": "load"
},
{ "id": "f1040",
"file": "pdfs/f1040.pdf",
"link": true,
"rounds": 1,
"type": "load"
},
{ "id": "hudsonsurvey",
"file": "pdfs/hudsonsurvey.pdf",
"link": true,
"rounds": 1,
"type": "load"
},
{ "id": "extgstate",
"file": "pdfs/extgstate.pdf",
"link": false,
"rounds": 1,
"type": "load"
} }
] ]

2
test/test_slave.html

@ -7,12 +7,14 @@
<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="/metrics.js"></script>
<script type="text/javascript" src="/charsets.js"></script>
<script type="text/javascript" src="driver.js"></script> <script type="text/javascript" src="driver.js"></script>
</head> </head>
<body onload="load();"> <body onload="load();">
<pre style="width:800; height:800; overflow: scroll;"id="stdout"></pre> <pre style="width:800; height:800; overflow: scroll;"id="stdout"></pre>
<p>Inflight requests: <span id="inFlightCount"></span></p> <p>Inflight requests: <span id="inFlightCount"></span></p>
<div id="content-end"><!-- cleanup() guard --></div>
</body> </body>
</html> </html>

394
utils/cffStandardStrings.js

@ -3,400 +3,6 @@
'use strict'; 'use strict';
var CFFStrings = [
'.notdef',
'space',
'exclam',
'quotedbl',
'numbersign',
'dollar',
'percent',
'ampersand',
'quoteright',
'parenleft',
'parenright',
'asterisk',
'plus',
'comma',
'hyphen',
'period',
'slash',
'zero',
'one',
'two',
'three',
'four',
'five',
'six',
'seven',
'eight',
'nine',
'colon',
'semicolon',
'less',
'equal',
'greater',
'question',
'at',
'A',
'B',
'C',
'D',
'E',
'F',
'G',
'H',
'I',
'J',
'K',
'L',
'M',
'N',
'O',
'P',
'Q',
'R',
'S',
'T',
'U',
'V',
'W',
'X',
'Y',
'Z',
'bracketleft',
'backslash',
'bracketright',
'asciicircum',
'underscore',
'quoteleft',
'a',
'b',
'c',
'd',
'e',
'f',
'g',
'h',
'i',
'j',
'k',
'l',
'm',
'n',
'o',
'p',
'q',
'r',
's',
't',
'u',
'v',
'w',
'x',
'y',
'z',
'braceleft',
'bar',
'braceright',
'asciitilde',
'exclamdown',
'cent',
'sterling',
'fraction',
'yen',
'florin',
'section',
'currency',
'quotesingle',
'quotedblleft',
'guillemotleft',
'guilsinglleft',
'guilsinglright',
'fi',
'fl',
'endash',
'dagger',
'daggerdbl',
'periodcentered',
'paragraph',
'bullet',
'quotesinglbase',
'quotedblbase',
'quotedblright',
'guillemotright',
'ellipsis',
'perthousand',
'questiondown',
'grave',
'acute',
'circumflex',
'tilde',
'macron',
'breve',
'dotaccent',
'dieresis',
'ring',
'cedilla',
'hungarumlaut',
'ogonek',
'caron',
'emdash',
'AE',
'ordfeminine',
'Lslash',
'Oslash',
'OE',
'ordmasculine',
'ae',
'dotlessi',
'lslash',
'oslash',
'oe',
'germandbls',
'onesuperior',
'logicalnot',
'mu',
'trademark',
'Eth',
'onehalf',
'plusminus',
'Thorn',
'onequarter',
'divide',
'brokenbar',
'degree',
'thorn',
'threequarters',
'twosuperior',
'registered',
'minus',
'eth',
'multiply',
'threesuperior',
'copyright',
'Aacute',
'Acircumflex',
'Adieresis',
'Agrave',
'Aring',
'Atilde',
'Ccedilla',
'Eacute',
'Ecircumflex',
'Edieresis',
'Egrave',
'Iacute',
'Icircumflex',
'Idieresis',
'Igrave',
'Ntilde',
'Oacute',
'Ocircumflex',
'Odieresis',
'Ograve',
'Otilde',
'Scaron',
'Uacute',
'Ucircumflex',
'Udieresis',
'Ugrave',
'Yacute',
'Ydieresis',
'Zcaron',
'aacute',
'acircumflex',
'adieresis',
'agrave',
'aring',
'atilde',
'ccedilla',
'eacute',
'ecircumflex',
'edieresis',
'egrave',
'iacute',
'icircumflex',
'idieresis',
'igrave',
'ntilde',
'oacute',
'ocircumflex',
'odieresis',
'ograve',
'otilde',
'scaron',
'uacute',
'ucircumflex',
'udieresis',
'ugrave',
'yacute',
'ydieresis',
'zcaron',
'exclamsmall',
'Hungarumlautsmall',
'dollaroldstyle',
'dollarsuperior',
'ampersandsmall',
'Acutesmall',
'parenleftsuperior',
'parenrightsuperior',
'266 ff',
'onedotenleader',
'zerooldstyle',
'oneoldstyle',
'twooldstyle',
'threeoldstyle',
'fouroldstyle',
'fiveoldstyle',
'sixoldstyle',
'sevenoldstyle',
'eightoldstyle',
'nineoldstyle',
'commasuperior',
'threequartersemdash',
'periodsuperior',
'questionsmall',
'asuperior',
'bsuperior',
'centsuperior',
'dsuperior',
'esuperior',
'isuperior',
'lsuperior',
'msuperior',
'nsuperior',
'osuperior',
'rsuperior',
'ssuperior',
'tsuperior',
'ff',
'ffi',
'ffl',
'parenleftinferior',
'parenrightinferior',
'Circumflexsmall',
'hyphensuperior',
'Gravesmall',
'Asmall',
'Bsmall',
'Csmall',
'Dsmall',
'Esmall',
'Fsmall',
'Gsmall',
'Hsmall',
'Ismall',
'Jsmall',
'Ksmall',
'Lsmall',
'Msmall',
'Nsmall',
'Osmall',
'Psmall',
'Qsmall',
'Rsmall',
'Ssmall',
'Tsmall',
'Usmall',
'Vsmall',
'Wsmall',
'Xsmall',
'Ysmall',
'Zsmall',
'colonmonetary',
'onefitted',
'rupiah',
'Tildesmall',
'exclamdownsmall',
'centoldstyle',
'Lslashsmall',
'Scaronsmall',
'Zcaronsmall',
'Dieresissmall',
'Brevesmall',
'Caronsmall',
'Dotaccentsmall',
'Macronsmall',
'figuredash',
'hypheninferior',
'Ogoneksmall',
'Ringsmall',
'Cedillasmall',
'questiondownsmall',
'oneeighth',
'threeeighths',
'fiveeighths',
'seveneighths',
'onethird',
'twothirds',
'zerosuperior',
'foursuperior',
'fivesuperior',
'sixsuperior',
'sevensuperior',
'eightsuperior',
'ninesuperior',
'zeroinferior',
'oneinferior',
'twoinferior',
'threeinferior',
'fourinferior',
'fiveinferior',
'sixinferior',
'seveninferior',
'eightinferior',
'nineinferior',
'centinferior',
'dollarinferior',
'periodinferior',
'commainferior',
'Agravesmall',
'Aacutesmall',
'Acircumflexsmall',
'Atildesmall',
'Adieresissmall',
'Aringsmall',
'AEsmall',
'Ccedillasmall',
'Egravesmall',
'Eacutesmall',
'Ecircumflexsmall',
'Edieresissmall',
'Igravesmall',
'Iacutesmall',
'Icircumflexsmall',
'Idieresissmall',
'Ethsmall',
'Ntildesmall',
'Ogravesmall',
'Oacutesmall',
'Ocircumflexsmall',
'Otildesmall',
'Odieresissmall',
'OEsmall',
'Oslashsmall',
'Ugravesmall',
'Uacutesmall',
'Ucircumflexsmall',
'Udieresissmall',
'Yacutesmall',
'Thornsmall',
'Ydieresissmall',
'001.000',
'001.001',
'001.002',
'001.003',
'Black',
'Bold',
'Book',
'Light',
'Medium',
'Regular',
'Roman',
'Semibold'
];
var CFFEncodingMap = { var CFFEncodingMap = {
'0': '-reserved-', '0': '-reserved-',
'1': 'hstem', '1': 'hstem',

38
utils/fonts_utils.js

@ -20,17 +20,27 @@ function readCharset(aStream, aCharstrings) {
var charset = {}; var charset = {};
var format = aStream.getByte(); var format = aStream.getByte();
var count = aCharstrings.length - 1;
if (format == 0) { if (format == 0) {
charset['.notdef'] = readCharstringEncoding(aCharstrings[0]); charset['.notdef'] = readCharstringEncoding(aCharstrings[0]);
var count = aCharstrings.length - 1;
for (var i = 1; i < count + 1; i++) { for (var i = 1; i < count + 1; i++) {
var sid = aStream.getByte() << 8 | aStream.getByte(); var sid = aStream.getByte() << 8 | aStream.getByte();
charset[CFFStrings[sid]] = readCharstringEncoding(aCharstrings[i]); charset[CFFStrings[sid]] = readCharstringEncoding(aCharstrings[i]);
//log(CFFStrings[sid] + "::" + charset[CFFStrings[sid]]); //log(CFFStrings[sid] + "::" + charset[CFFStrings[sid]]);
} }
} else if (format == 1) { } else if (format == 1) {
error('Charset Range are not supported'); for (var i = 1; i < count + 1; i++) {
var first = aStream.getByte();
first = (first << 8) | aStream.getByte();
var numLeft = aStream.getByte();
for (var j = 0; j <= numLeft; j++) {
var sid = first++;
if (CFFStrings[sid] == 'three')
log(aCharstrings[j]);
charset[CFFStrings[sid]] = readCharstringEncoding(aCharstrings[j]);
}
}
} else { } else {
error('Invalid charset format'); error('Invalid charset format');
} }
@ -44,6 +54,9 @@ function readCharset(aStream, aCharstrings) {
* chapter 3.1. * chapter 3.1.
*/ */
function readCharstringEncoding(aString) { function readCharstringEncoding(aString) {
if (!aString)
return '';
var charstringTokens = []; var charstringTokens = [];
var count = aString.length; var count = aString.length;
@ -71,9 +84,9 @@ function readCharstringEncoding(aString) {
} else if (value < 247) { } else if (value < 247) {
token = parseInt(value, 10) - 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) {
token = -((value - 251) * 256) - aString[i++] - 108; token = -(value - 251) * 256 - aString[i++] - 108;
} else {// value == 255 } else {// value == 255
token = aString[i++] << 24 | aString[i++] << 16 | token = aString[i++] << 24 | aString[i++] << 16 |
aString[i++] << 8 | aString[i]; aString[i++] << 8 | aString[i];
@ -146,9 +159,9 @@ function readFontDictData(aString, aMap) {
} else if (value <= 246) { } else if (value <= 246) {
token = parseInt(value, 10) - 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) {
token = -((value - 251) * 256) - aString[i++] - 108; token = -(value - 251) * 256 - aString[i++] - 108;
} else if (value == 255) { } else if (value == 255) {
error('255 is not a valid DICT command'); error('255 is not a valid DICT command');
} }
@ -199,7 +212,7 @@ function readFontIndexData(aStream, aIsByte) {
for (var i = 0; i < count + 1; i++) for (var i = 0; i < count + 1; i++)
offsets.push(getNextOffset()); offsets.push(getNextOffset());
log('Found ' + count + ' objects at offsets :' + dump('Found ' + count + ' objects at offsets :' +
offsets + ' (offsize: ' + offsize + ')'); offsets + ' (offsize: ' + offsize + ')');
// Now extract the objects // Now extract the objects
@ -285,23 +298,20 @@ var Type2Parser = function(aFilePath) {
font.set('hdrSize', aStream.getByte()); font.set('hdrSize', aStream.getByte());
font.set('offsize', aStream.getByte()); font.set('offsize', aStream.getByte());
// Move the cursor after the header
aStream.skip(font.get('hdrSize') - aStream.pos);
// Read the NAME Index // Read the NAME Index
dump('Reading Index: Names'); dump('Reading Index: Names');
font.set('Names', readFontIndexData(aStream)); font.set('Names', readFontIndexData(aStream));
log('Names: ' + font.get('Names')); dump('Names: ' + font.get('Names'));
// Read the Top Dict Index // Read the Top Dict Index
dump('Reading Index: TopDict'); dump('Reading Index: TopDict');
var topDict = readFontIndexData(aStream, true); var topDict = readFontIndexData(aStream, true);
log('TopDict: ' + topDict); dump('TopDict: ' + topDict);
// Read the String Index // Read the String Index
dump('Reading Index: Strings'); dump('Reading Index: Strings');
var strings = readFontIndexData(aStream); var strings = readFontIndexData(aStream);
log('strings: ' + strings); dump('strings: ' + strings);
// Fill up the Strings dictionary with the new unique strings // Fill up the Strings dictionary with the new unique strings
for (var i = 0; i < strings.length; i++) for (var i = 0; i < strings.length; i++)
@ -321,7 +331,7 @@ var Type2Parser = function(aFilePath) {
// Reading Private Dict // Reading Private Dict
var priv = font.get('Private'); var priv = font.get('Private');
log('Reading Private Dict (offset: ' + priv.offset + dump('Reading Private Dict (offset: ' + priv.offset +
' size: ' + priv.size + ')'); ' size: ' + priv.size + ')');
aStream.pos = priv.offset; aStream.pos = priv.offset;

15
web/viewer.css

@ -119,6 +119,7 @@ span#info {
margin-right:auto; margin-right:auto;
line-height: 134px; line-height: 134px;
text-align: center; text-align: center;
overflow: hidden;
} }
.thumbnail:not([data-loaded]) { .thumbnail:not([data-loaded]) {
@ -195,16 +196,17 @@ span#info {
canvas { canvas {
margin: auto; margin: auto;
display: block; display: block;
box-shadow: 0px 4px 10px #000;
-moz-box-shadow: 0px 4px 10px #000;
-webkit-box-shadow: 0px 4px 10px #000;
} }
.page { .page {
width: 816px; width: 816px;
height: 1056px; height: 1056px;
margin: 10px auto; margin: 10px auto;
position:relative; position: relative;
overflow: hidden;
box-shadow: 0px 4px 10px #000;
-moz-box-shadow: 0px 4px 10px #000;
-webkit-box-shadow: 0px 4px 10px #000;
} }
.page > a { .page > a {
@ -271,3 +273,8 @@ canvas {
page-break-after: always; page-break-after: always;
} }
} }
#loading {
margin: 100px 0;
text-align: center;
}

4
web/viewer.html

@ -11,6 +11,7 @@
<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="../metrics.js"></script>
<script type="text/javascript" src="../charsets.js"></script>
</head> </head>
<body> <body>
@ -89,7 +90,8 @@
</div> </div>
</div> </div>
</div> </div>
<div id="loading">Loading... 0%</div>
<div id="viewer"></div> <div id="viewer"></div>
</body> </body>
</html> </html>

52
web/viewer.js

@ -58,9 +58,9 @@ var PDFView = {
var currentPage = this.pages[this.page - 1]; var currentPage = this.pages[this.page - 1];
var pageWidthScale = (window.innerWidth - kScrollbarPadding) / var pageWidthScale = (window.innerWidth - kScrollbarPadding) /
currentPage.width / kCssUnits; currentPage.width / kCssUnits;
var pageHeightScale = (window.innerHeight - kScrollbarPadding) / var pageHeightScale = (window.innerHeight - kScrollbarPadding) /
currentPage.height / kCssUnits; currentPage.height / kCssUnits;
if ('page-width' == value) if ('page-width' == value)
this.setScale(pageWidthScale, resetAutoSettings); this.setScale(pageWidthScale, resetAutoSettings);
if ('page-height' == value) if ('page-height' == value)
@ -111,12 +111,14 @@ var PDFView = {
xhr.open('GET', url); xhr.open('GET', url);
xhr.mozResponseType = xhr.responseType = 'arraybuffer'; xhr.mozResponseType = xhr.responseType = 'arraybuffer';
xhr.expected = (document.URL.indexOf('file:') === 0) ? 0 : 200; xhr.expected = (document.URL.indexOf('file:') === 0) ? 0 : 200;
xhr.onprogress = PDFView.progressLevel;
xhr.onreadystatechange = function() { xhr.onreadystatechange = function() {
if (xhr.readyState === 4 && xhr.status === xhr.expected) { if (xhr.readyState === 4 && xhr.status === xhr.expected) {
var data = (xhr.mozResponseArrayBuffer || xhr.mozResponse || var data = (xhr.mozResponseArrayBuffer || xhr.mozResponse ||
xhr.responseArrayBuffer || xhr.response); xhr.responseArrayBuffer || xhr.response);
document.getElementById('loading').style.display = 'none';
PDFView.load(data, scale); PDFView.load(data, scale);
} }
}; };
@ -124,6 +126,11 @@ var PDFView = {
xhr.send(null); xhr.send(null);
}, },
progressLevel: function(evt) {
var p = Math.round((evt.loaded / evt.total) * 100);
document.getElementById('loading').innerHTML = 'Loading... ' + p + '%';
},
navigateTo: function(dest) { navigateTo: function(dest) {
if (typeof dest === 'string') if (typeof dest === 'string')
dest = this.destinations[dest]; dest = this.destinations[dest];
@ -163,7 +170,7 @@ var PDFView = {
var page = pdf.getPage(i); var page = pdf.getPage(i);
pages.push(new PageView(container, page, i, page.width, page.height, pages.push(new PageView(container, page, i, page.width, page.height,
page.stats, this.navigateTo.bind(this))); page.stats, this.navigateTo.bind(this)));
thumbnails.push(new ThumbnailView(sidebar, pages[i - 1], thumbnails.push(new ThumbnailView(sidebar, page, i,
page.width / page.height)); page.width / page.height));
var pageRef = page.ref; var pageRef = page.ref;
pagesRefMap[pageRef.num + ' ' + pageRef.gen + ' R'] = i; pagesRefMap[pageRef.num + ' ' + pageRef.gen + ' R'] = i;
@ -230,13 +237,17 @@ var PDFView = {
} }
}; };
var PageView = function(container, content, id, width, height, var PageView = function(container, content, id, pageWidth, pageHeight,
stats, navigateTo) { stats, navigateTo) {
this.width = width;
this.height = height;
this.id = id; this.id = id;
this.content = content; this.content = content;
var view = this.content.view;
this.x = view.x;
this.y = view.y;
this.width = view.width;
this.height = view.height;
var anchor = document.createElement('a'); var anchor = document.createElement('a');
anchor.name = '' + this.id; anchor.name = '' + this.id;
@ -265,11 +276,12 @@ var PageView = function(container, content, id, width, height,
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++) {
var link = document.createElement('a'); var link = document.createElement('a');
link.style.left = Math.floor(links[i].x * scale) + 'px'; link.style.left = (Math.floor(links[i].x - view.x) * scale) + 'px';
link.style.top = Math.floor(links[i].y * scale) + 'px'; link.style.top = (Math.floor(links[i].y - view.y) * scale) + 'px';
link.style.width = Math.ceil(links[i].width * scale) + 'px'; link.style.width = Math.ceil(links[i].width * scale) + 'px';
link.style.height = Math.ceil(links[i].height * scale) + 'px'; link.style.height = Math.ceil(links[i].height * scale) + 'px';
link.href = links[i].url || ''; link.href = links[i].url || '';
@ -357,8 +369,9 @@ var PageView = function(container, content, id, width, height,
canvas.id = 'page' + this.id; canvas.id = 'page' + this.id;
canvas.mozOpaque = true; canvas.mozOpaque = true;
canvas.width = this.width * this.scale; var scale = this.scale;
canvas.height = this.height * this.scale; canvas.width = pageWidth * scale;
canvas.height = pageHeight * scale;
div.appendChild(canvas); div.appendChild(canvas);
var ctx = canvas.getContext('2d'); var ctx = canvas.getContext('2d');
@ -366,6 +379,7 @@ var PageView = function(container, content, id, width, height,
ctx.fillStyle = 'rgb(255, 255, 255)'; ctx.fillStyle = 'rgb(255, 255, 255)';
ctx.fillRect(0, 0, canvas.width, canvas.height); ctx.fillRect(0, 0, canvas.width, canvas.height);
ctx.restore(); ctx.restore();
ctx.translate(-this.x * scale, -this.y * scale);
stats.begin = Date.now(); stats.begin = Date.now();
this.content.startRendering(ctx, this.updateStats); this.content.startRendering(ctx, this.updateStats);
@ -384,12 +398,12 @@ var PageView = function(container, content, id, width, height,
}; };
}; };
var ThumbnailView = function(container, page, pageRatio) { var ThumbnailView = function(container, page, id, pageRatio) {
var anchor = document.createElement('a'); var anchor = document.createElement('a');
anchor.href = '#' + page.id; anchor.href = '#' + id;
var div = document.createElement('div'); var div = document.createElement('div');
div.id = 'thumbnailContainer' + page.id; div.id = 'thumbnailContainer' + id;
div.className = 'thumbnail'; div.className = 'thumbnail';
anchor.appendChild(div); anchor.appendChild(div);
@ -400,7 +414,7 @@ var ThumbnailView = function(container, page, pageRatio) {
return; return;
var canvas = document.createElement('canvas'); var canvas = document.createElement('canvas');
canvas.id = 'thumbnail' + page.id; canvas.id = 'thumbnail' + id;
canvas.mozOpaque = true; canvas.mozOpaque = true;
var maxThumbSize = 134; var maxThumbSize = 134;
@ -418,7 +432,15 @@ var ThumbnailView = function(container, page, pageRatio) {
ctx.fillRect(0, 0, canvas.width, canvas.height); ctx.fillRect(0, 0, canvas.width, canvas.height);
ctx.restore(); ctx.restore();
page.content.startRendering(ctx, function() { }); var view = page.view;
var scaleX = (canvas.width / page.width);
var scaleY = (canvas.height / page.height);
ctx.translate(-view.x * scaleX, -view.y * scaleY);
div.style.width = (view.width * scaleX) + 'px';
div.style.height = (view.height * scaleY) + 'px';
div.style.lineHeight = (view.height * scaleY) + 'px';
page.startRendering(ctx, function() { });
}; };
}; };

Loading…
Cancel
Save