Browse Source

Merge remote branch 'upstream/master'

Rob Sayre 14 years ago
parent
commit
f4e5b2bcfd
  1. 12
      README
  2. 27
      README.md
  3. 10
      crypto.js
  4. 695
      fonts.js
  5. 2
      multi_page_viewer.css
  6. 4
      multi_page_viewer.html
  7. 55
      multi_page_viewer.js
  8. 24
      pdf.js
  9. 10
      test/test_slave.html
  10. 7
      viewer.css
  11. 4
      viewer.html
  12. 10
      viewer.js
  13. 10
      viewer_worker.html
  14. 3
      worker/canvas.js
  15. 161
      worker/client.js
  16. 27
      worker/console.js
  17. 65
      worker/font.js
  18. 41
      worker/pdf.js

12
README

@ -1,12 +0,0 @@ @@ -1,12 +0,0 @@
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.
You can read more about pdf.js here:
http://andreasgal.com/2011/06/15/pdf-js/
http://blog.mozilla.com/cjones/2011/06/15/overview-of-pdf-js-guts/
Or follow us on twitter: @pdfjs
http://twitter.com/#!/pdfjs

27
README.md

@ -0,0 +1,27 @@ @@ -0,0 +1,27 @@
# pdf.js
pdf.js is a technology demonstrator prototype to explore whether the HTML5
platform is complete enough to faithfully and efficiently render the ISO
32000-1:2008 Portable Document Format (PDF) without native code assistance.
pdf.js is not currently part of the Mozilla project, and there is no plan
yet to integrate it into Firefox. We will explore that possibility once
pdf.js is production ready. Until then we aim to publish a Firefox
PDF reader extension powered by pdf.js.
You can read more about pdf.js here:
http://andreasgal.com/2011/06/15/pdf-js/
http://blog.mozilla.com/cjones/2011/06/15/overview-of-pdf-js-guts/
follow us on twitter: @pdfjs
http://twitter.com/#!/pdfjs
join our mailing list:
dev-pdf-js@lists.mozilla.org
and talk to us on IRC:
#pdfjs on irc.mozilla.org

10
crypto.js

@ -1,5 +1,5 @@ @@ -1,5 +1,5 @@
/* -*- Mode: Java; tab-width: s; indent-tabs-mode: nil; c-basic-offset: 2 -*- /
/* vim: set shiftwidth=s tabstop=2 autoindent cindent expandtab: */
/* -*- Mode: Java; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- /
/* vim: set shiftwidth=2 tabstop=2 autoindent cindent expandtab: */
"use strict";
@ -45,12 +45,12 @@ var ARCFourCipher = (function() { @@ -45,12 +45,12 @@ var ARCFourCipher = (function() {
})();
var md5 = (function() {
const r = new Uint8Array([
var r = new Uint8Array([
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,
4, 11, 16, 23, 4, 11, 16, 23, 4, 11, 16, 23, 4, 11, 16, 23,
6, 10, 15, 21, 6, 10, 15, 21, 6, 10, 15, 21, 6, 10, 15, 21]);
const k = new Int32Array([
var k = new Int32Array([
-680876936, -389564586, 606105819, -1044525330, -176418897, 1200080426,
-1473231341, -45705983, 1770035416, -1958414417, -42063, -1990404162,
1804603682, -40341101, -1502002290, 1236535329, -165796510, -1069501632,
@ -149,7 +149,7 @@ var CipherTransform = (function() { @@ -149,7 +149,7 @@ var CipherTransform = (function() {
var CipherTransformFactory = (function() {
function prepareKeyData(fileId, password, ownerPassword, userPassword, flags, revision, keyLength) {
const defaultPasswordBytes = new Uint8Array([
var defaultPasswordBytes = new Uint8Array([
0x28, 0xBF, 0x4E, 0x5E, 0x4E, 0x75, 0x8A, 0x41, 0x64, 0x00, 0x4E, 0x56, 0xFF, 0xFA, 0x01, 0x08,
0x2E, 0x2E, 0x00, 0xB6, 0xD0, 0x68, 0x3E, 0x80, 0x2F, 0x0C, 0xA9, 0xFE, 0x64, 0x53, 0x69, 0x7A]);
var hashData = new Uint8Array(88), i = 0, j, n;

695
fonts.js

@ -3,6 +3,8 @@ @@ -3,6 +3,8 @@
"use strict";
var isWorker = (typeof window == "undefined");
/**
* Maximum file size of the font.
*/
@ -26,69 +28,109 @@ var fontName = ""; @@ -26,69 +28,109 @@ var fontName = "";
*/
var kDisableFonts = false;
/**
* Hold a map of decoded fonts and of the standard fourteen Type1 fonts and
* their acronyms.
* TODO Add the standard fourteen Type1 fonts list by default
* http://cgit.freedesktop.org/poppler/poppler/tree/poppler/GfxFont.cc#n65
*/
var Fonts = {
_active: null,
get active() {
return this._active;
},
var Fonts = (function () {
var kScalePrecision = 40;
var fonts = Object.create(null);
set active(name) {
this._active = this[name];
},
if (!isWorker) {
var ctx = document.createElement("canvas").getContext("2d");
ctx.scale(1 / kScalePrecision, 1);
}
charsToUnicode: function fonts_chars2Unicode(chars) {
var active = this._active;
if (!active)
return chars;
// if we translated this string before, just grab it from the cache
var str = active.cache[chars];
if (str)
return str;
// translate the string using the font's encoding
var encoding = active.properties.encoding;
if (!encoding)
return chars;
str = "";
for (var i = 0; i < chars.length; ++i) {
var charcode = chars.charCodeAt(i);
var unicode = encoding[charcode];
// Check if the glyph has already been converted
if (unicode instanceof Name)
unicode = encoding[unicode] = GlyphsUnicode[unicode.name];
// Handle surrogate pairs
if (unicode > 0xFFFF) {
str += String.fromCharCode(unicode & 0xFFFF);
unicode >>= 16;
function Font(name, data, properties) {
this.name = name;
this.data = data;
this.properties = properties;
this.loading = true;
this.charsCache = Object.create(null);
this.sizes = [];
}
var current;
var charsCache;
var measureCache;
return {
registerFont: function fonts_registerFont(fontName, data, properties) {
fonts[fontName] = new Font(fontName, data, properties);
},
blacklistFont: function fonts_blacklistFont(fontName) {
registerFont(fontName, null, {});
markLoaded(fontName);
},
lookup: function fonts_lookup(fontName) {
return fonts[fontName];
},
setActive: function fonts_setActive(fontName, size) {
current = fonts[fontName];
charsCache = current.charsCache;
var sizes = current.sizes;
if (!(measureCache = sizes[size]))
measureCache = sizes[size] = Object.create(null);
ctx.font = (size * kScalePrecision) + 'px "' + fontName + '"';
},
charsToUnicode: function fonts_chars2Unicode(chars) {
if (!charsCache)
return chars;
// if we translated this string before, just grab it from the cache
var str = charsCache[chars];
if (str)
return str;
// translate the string using the font's encoding
var encoding = current.properties.encoding;
if (!encoding)
return chars;
str = "";
for (var i = 0; i < chars.length; ++i) {
var charcode = chars.charCodeAt(i);
var unicode = encoding[charcode];
// Check if the glyph has already been converted
if (!IsNum(unicode))
unicode = encoding[unicode] = GlyphsUnicode[unicode.name];
// Handle surrogate pairs
if (unicode > 0xFFFF) {
str += String.fromCharCode(unicode & 0xFFFF);
unicode >>= 16;
}
str += String.fromCharCode(unicode);
}
str += String.fromCharCode(unicode);
}
// Enter the translated string into the cache
return active.cache[chars] = str;
// Enter the translated string into the cache
return charsCache[chars] = str;
},
measureText: function fonts_measureText(text) {
var width;
if (measureCache && (width = measureCache[text]))
return width;
width = ctx.measureText(text).width / kScalePrecision;
if (measureCache)
measureCache[text] = width;
return width;
}
}
};
})();
var FontLoader = {
bind: function(fonts) {
var worker = (typeof window == "undefined");
var ready = true;
for (var i = 0; i < fonts.length; i++) {
var font = fonts[i];
if (Fonts[font.name]) {
ready = ready && !Fonts[font.name].loading;
if (Fonts.lookup(font.name)) {
ready = ready && !Fonts.lookup(font.name).loading;
continue;
}
@ -97,18 +139,152 @@ var FontLoader = { @@ -97,18 +139,152 @@ var FontLoader = {
var obj = new Font(font.name, font.file, font.properties);
var str = "";
var data = Fonts[font.name].data;
var data = Fonts.lookup(font.name).data;
var length = data.length;
for (var j = 0; j < length; j++)
str += String.fromCharCode(data[j]);
worker ? obj.bindWorker(str) : obj.bindDOM(str);
isWorker ? obj.bindWorker(str) : obj.bindDOM(str);
}
return ready;
}
};
var UnicodeRanges = [
{ "begin": 0x0000, "end": 0x007F }, // Basic Latin
{ "begin": 0x0080, "end": 0x00FF }, // Latin-1 Supplement
{ "begin": 0x0100, "end": 0x017F }, // Latin Extended-A
{ "begin": 0x0180, "end": 0x024F }, // Latin Extended-B
{ "begin": 0x0250, "end": 0x02AF }, // IPA Extensions
{ "begin": 0x02B0, "end": 0x02FF }, // Spacing Modifier Letters
{ "begin": 0x0300, "end": 0x036F }, // Combining Diacritical Marks
{ "begin": 0x0370, "end": 0x03FF }, // Greek and Coptic
{ "begin": 0x2C80, "end": 0x2CFF }, // Coptic
{ "begin": 0x0400, "end": 0x04FF }, // Cyrillic
{ "begin": 0x0530, "end": 0x058F }, // Armenian
{ "begin": 0x0590, "end": 0x05FF }, // Hebrew
{ "begin": 0xA500, "end": 0xA63F }, // Vai
{ "begin": 0x0600, "end": 0x06FF }, // Arabic
{ "begin": 0x07C0, "end": 0x07FF }, // NKo
{ "begin": 0x0900, "end": 0x097F }, // Devanagari
{ "begin": 0x0980, "end": 0x09FF }, // Bengali
{ "begin": 0x0A00, "end": 0x0A7F }, // Gurmukhi
{ "begin": 0x0A80, "end": 0x0AFF }, // Gujarati
{ "begin": 0x0B00, "end": 0x0B7F }, // Oriya
{ "begin": 0x0B80, "end": 0x0BFF }, // Tamil
{ "begin": 0x0C00, "end": 0x0C7F }, // Telugu
{ "begin": 0x0C80, "end": 0x0CFF }, // Kannada
{ "begin": 0x0D00, "end": 0x0D7F }, // Malayalam
{ "begin": 0x0E00, "end": 0x0E7F }, // Thai
{ "begin": 0x0E80, "end": 0x0EFF }, // Lao
{ "begin": 0x10A0, "end": 0x10FF }, // Georgian
{ "begin": 0x1B00, "end": 0x1B7F }, // Balinese
{ "begin": 0x1100, "end": 0x11FF }, // Hangul Jamo
{ "begin": 0x1E00, "end": 0x1EFF }, // Latin Extended Additional
{ "begin": 0x1F00, "end": 0x1FFF }, // Greek Extended
{ "begin": 0x2000, "end": 0x206F }, // General Punctuation
{ "begin": 0x2070, "end": 0x209F }, // Superscripts And Subscripts
{ "begin": 0x20A0, "end": 0x20CF }, // Currency Symbol
{ "begin": 0x20D0, "end": 0x20FF }, // Combining Diacritical Marks For Symbols
{ "begin": 0x2100, "end": 0x214F }, // Letterlike Symbols
{ "begin": 0x2150, "end": 0x218F }, // Number Forms
{ "begin": 0x2190, "end": 0x21FF }, // Arrows
{ "begin": 0x2200, "end": 0x22FF }, // Mathematical Operators
{ "begin": 0x2300, "end": 0x23FF }, // Miscellaneous Technical
{ "begin": 0x2400, "end": 0x243F }, // Control Pictures
{ "begin": 0x2440, "end": 0x245F }, // Optical Character Recognition
{ "begin": 0x2460, "end": 0x24FF }, // Enclosed Alphanumerics
{ "begin": 0x2500, "end": 0x257F }, // Box Drawing
{ "begin": 0x2580, "end": 0x259F }, // Block Elements
{ "begin": 0x25A0, "end": 0x25FF }, // Geometric Shapes
{ "begin": 0x2600, "end": 0x26FF }, // Miscellaneous Symbols
{ "begin": 0x2700, "end": 0x27BF }, // Dingbats
{ "begin": 0x3000, "end": 0x303F }, // CJK Symbols And Punctuation
{ "begin": 0x3040, "end": 0x309F }, // Hiragana
{ "begin": 0x30A0, "end": 0x30FF }, // Katakana
{ "begin": 0x3100, "end": 0x312F }, // Bopomofo
{ "begin": 0x3130, "end": 0x318F }, // Hangul Compatibility Jamo
{ "begin": 0xA840, "end": 0xA87F }, // Phags-pa
{ "begin": 0x3200, "end": 0x32FF }, // Enclosed CJK Letters And Months
{ "begin": 0x3300, "end": 0x33FF }, // CJK Compatibility
{ "begin": 0xAC00, "end": 0xD7AF }, // Hangul Syllables
{ "begin": 0xD800, "end": 0xDFFF }, // Non-Plane 0 *
{ "begin": 0x10900, "end": 0x1091F }, // Phoenicia
{ "begin": 0x4E00, "end": 0x9FFF }, // CJK Unified Ideographs
{ "begin": 0xE000, "end": 0xF8FF }, // Private Use Area (plane 0)
{ "begin": 0x31C0, "end": 0x31EF }, // CJK Strokes
{ "begin": 0xFB00, "end": 0xFB4F }, // Alphabetic Presentation Forms
{ "begin": 0xFB50, "end": 0xFDFF }, // Arabic Presentation Forms-A
{ "begin": 0xFE20, "end": 0xFE2F }, // Combining Half Marks
{ "begin": 0xFE10, "end": 0xFE1F }, // Vertical Forms
{ "begin": 0xFE50, "end": 0xFE6F }, // Small Form Variants
{ "begin": 0xFE70, "end": 0xFEFF }, // Arabic Presentation Forms-B
{ "begin": 0xFF00, "end": 0xFFEF }, // Halfwidth And Fullwidth Forms
{ "begin": 0xFFF0, "end": 0xFFFF }, // Specials
{ "begin": 0x0F00, "end": 0x0FFF }, // Tibetan
{ "begin": 0x0700, "end": 0x074F }, // Syriac
{ "begin": 0x0780, "end": 0x07BF }, // Thaana
{ "begin": 0x0D80, "end": 0x0DFF }, // Sinhala
{ "begin": 0x1000, "end": 0x109F }, // Myanmar
{ "begin": 0x1200, "end": 0x137F }, // Ethiopic
{ "begin": 0x13A0, "end": 0x13FF }, // Cherokee
{ "begin": 0x1400, "end": 0x167F }, // Unified Canadian Aboriginal Syllabics
{ "begin": 0x1680, "end": 0x169F }, // Ogham
{ "begin": 0x16A0, "end": 0x16FF }, // Runic
{ "begin": 0x1780, "end": 0x17FF }, // Khmer
{ "begin": 0x1800, "end": 0x18AF }, // Mongolian
{ "begin": 0x2800, "end": 0x28FF }, // Braille Patterns
{ "begin": 0xA000, "end": 0xA48F }, // Yi Syllables
{ "begin": 0x1700, "end": 0x171F }, // Tagalog
{ "begin": 0x10300, "end": 0x1032F }, // Old Italic
{ "begin": 0x10330, "end": 0x1034F }, // Gothic
{ "begin": 0x10400, "end": 0x1044F }, // Deseret
{ "begin": 0x1D000, "end": 0x1D0FF }, // Byzantine Musical Symbols
{ "begin": 0x1D400, "end": 0x1D7FF }, // Mathematical Alphanumeric Symbols
{ "begin": 0xFF000, "end": 0xFFFFD }, // Private Use (plane 15)
{ "begin": 0xFE00, "end": 0xFE0F }, // Variation Selectors
{ "begin": 0xE0000, "end": 0xE007F }, // Tags
{ "begin": 0x1900, "end": 0x194F }, // Limbu
{ "begin": 0x1950, "end": 0x197F }, // Tai Le
{ "begin": 0x1980, "end": 0x19DF }, // New Tai Lue
{ "begin": 0x1A00, "end": 0x1A1F }, // Buginese
{ "begin": 0x2C00, "end": 0x2C5F }, // Glagolitic
{ "begin": 0x2D30, "end": 0x2D7F }, // Tifinagh
{ "begin": 0x4DC0, "end": 0x4DFF }, // Yijing Hexagram Symbols
{ "begin": 0xA800, "end": 0xA82F }, // Syloti Nagri
{ "begin": 0x10000, "end": 0x1007F }, // Linear B Syllabary
{ "begin": 0x10140, "end": 0x1018F }, // Ancient Greek Numbers
{ "begin": 0x10380, "end": 0x1039F }, // Ugaritic
{ "begin": 0x103A0, "end": 0x103DF }, // Old Persian
{ "begin": 0x10450, "end": 0x1047F }, // Shavian
{ "begin": 0x10480, "end": 0x104AF }, // Osmanya
{ "begin": 0x10800, "end": 0x1083F }, // Cypriot Syllabary
{ "begin": 0x10A00, "end": 0x10A5F }, // Kharoshthi
{ "begin": 0x1D300, "end": 0x1D35F }, // Tai Xuan Jing Symbols
{ "begin": 0x12000, "end": 0x123FF }, // Cuneiform
{ "begin": 0x1D360, "end": 0x1D37F }, // Counting Rod Numerals
{ "begin": 0x1B80, "end": 0x1BBF }, // Sundanese
{ "begin": 0x1C00, "end": 0x1C4F }, // Lepcha
{ "begin": 0x1C50, "end": 0x1C7F }, // Ol Chiki
{ "begin": 0xA880, "end": 0xA8DF }, // Saurashtra
{ "begin": 0xA900, "end": 0xA92F }, // Kayah Li
{ "begin": 0xA930, "end": 0xA95F }, // Rejang
{ "begin": 0xAA00, "end": 0xAA5F }, // Cham
{ "begin": 0x10190, "end": 0x101CF }, // Ancient Symbols
{ "begin": 0x101D0, "end": 0x101FF }, // Phaistos Disc
{ "begin": 0x102A0, "end": 0x102DF }, // Carian
{ "begin": 0x1F030, "end": 0x1F09F } // Domino Tiles
];
function getUnicodeRangeFor(value) {
for (var i = 0; i < UnicodeRanges.length; i++) {
var range = UnicodeRanges[i];
if (value >= range.begin && value < range.end)
return i;
}
return -1;
};
/**
* 'Font' is the class the outside world should use, it encapsulate all the font
@ -124,8 +300,8 @@ var Font = (function () { @@ -124,8 +300,8 @@ var Font = (function () {
this.encoding = properties.encoding;
// If the font has already been decoded simply return it
if (Fonts[name]) {
this.font = Fonts[name].data;
if (Fonts.lookup(name)) {
this.font = Fonts.lookup(name).data;
return;
}
fontCount++;
@ -134,12 +310,7 @@ var Font = (function () { @@ -134,12 +310,7 @@ var Font = (function () {
// 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.
if (properties.ignore || kDisableFonts) {
Fonts[name] = {
data: file,
loading: false,
properties: {},
cache: Object.create(null)
}
Fonts.blacklistFont(name);
return;
}
@ -165,13 +336,8 @@ var Font = (function () { @@ -165,13 +336,8 @@ var Font = (function () {
warn("Font " + properties.type + " is not supported");
break;
}
Fonts[name] = {
data: data,
properties: properties,
loading: true,
cache: Object.create(null)
};
this.data = data;
Fonts.registerFont(name, data, properties);
};
function stringToArray(str) {
@ -221,6 +387,9 @@ var Font = (function () { @@ -221,6 +387,9 @@ var Font = (function () {
// offset
var offset = offsets.virtualOffset;
// length
var length = data.length;
// Per spec tables must be 4-bytes align so add padding as needed
while (data.length & 3)
data.push(0x00);
@ -228,16 +397,10 @@ var Font = (function () { @@ -228,16 +397,10 @@ var Font = (function () {
while (offsets.virtualOffset & 3)
offsets.virtualOffset++;
// length
var length = data.length;
// checksum
var checksum = tag.charCodeAt(0) +
tag.charCodeAt(1) +
tag.charCodeAt(2) +
tag.charCodeAt(3) +
offset +
length;
var checksum = 0;
for (var i = 0; i < length; i+=4)
checksum += FontsUtils.bytesToInteger([data[i], data[i+1], data[i+2], data[i+3]]);
var tableEntry = tag + string32(checksum) + string32(offset) + string32(length);
tableEntry = stringToArray(tableEntry);
@ -271,6 +434,7 @@ var Font = (function () { @@ -271,6 +434,7 @@ var Font = (function () {
};
function createCMapTable(glyphs) {
glyphs.push({ unicode: 0x0000 });
var ranges = getRanges(glyphs);
var headerSize = (12 * 2 + (ranges.length * 4 * 2));
@ -304,13 +468,13 @@ var Font = (function () { @@ -304,13 +468,13 @@ var Font = (function () {
var range = ranges[i];
var start = range[0];
var end = range[1];
var delta = (((start - 1) - bias) ^ 0xffff) + 1;
var delta = (((start - 1) - bias) ^ 0xffff);
bias += (end - start + 1);
startCount += string16(start);
endCount += string16(end);
idDeltas += string16(delta);
idRangeOffsets += string16(0);
idRangeOffsets += string16(0);
for (var j = 0; j < range.length; j++)
glyphsIds += String.fromCharCode(range[j]);
@ -326,11 +490,43 @@ var Font = (function () { @@ -326,11 +490,43 @@ var Font = (function () {
};
function createOS2Table(properties) {
var ulUnicodeRange1 = 0;
var ulUnicodeRange2 = 0;
var ulUnicodeRange3 = 0;
var ulUnicodeRange4 = 0;
var charset = properties.charset;
if (charset && charset.length) {
var firstCharIndex = null;
var lastCharIndex = 0;
for (var i = 1; i < charset.length; i++) {
var code = GlyphsUnicode[charset[i]];
if (firstCharIndex > code || !firstCharIndex)
firstCharIndex = code;
if (lastCharIndex < code)
lastCharIndex = code;
var position = getUnicodeRangeFor(code);
if (position < 32) {
ulUnicodeRange1 |= 1 << position;
} else if (position < 64) {
ulUnicodeRange2 |= 1 << position - 32;
} else if (position < 96) {
ulUnicodeRange3 |= 1 << position - 64;
} else if (position < 123) {
ulUnicodeRange4 |= 1 << position - 96;
} else {
error("Unicode ranges Bits > 123 are reserved for internal usage");
}
}
}
return "\x00\x03" + // version
"\x02\x24" + // xAvgCharWidth
"\x01\xF4" + // usWeightClass
"\x00\x05" + // usWidthClass
"\x00\x00" + // fstype
"\x00\x02" + // fstype
"\x02\x8A" + // ySubscriptXSize
"\x02\xBB" + // ySubscriptYSize
"\x00\x00" + // ySubscriptXOffset
@ -342,41 +538,41 @@ var Font = (function () { @@ -342,41 +538,41 @@ var Font = (function () {
"\x00\x31" + // yStrikeOutSize
"\x01\x02" + // yStrikeOutPosition
"\x00\x00" + // sFamilyClass
"\x02\x00\x06\x03\x00\x00\x00\x00\x00\x00" + // Panose
"\xFF\xFF\xFF\xFF" + // ulUnicodeRange1 (Bits 0-31)
"\xFF\xFF\xFF\xFF" + // ulUnicodeRange1 (Bits 32-63)
"\xFF\xFF\xFF\xFF" + // ulUnicodeRange1 (Bits 64-95)
"\xFF\xFF\xFF\xFF" + // ulUnicodeRange1 (Bits 96-127)
"\x00\x00\x06" + String.fromCharCode(properties.fixedPitch ? 0x09 : 0x00) +
"\x00\x00\x00\x00\x00\x00" + // Panose
string32(ulUnicodeRange1) + // ulUnicodeRange1 (Bits 0-31)
string32(ulUnicodeRange2) + // ulUnicodeRange2 (Bits 32-63)
string32(ulUnicodeRange3) + // ulUnicodeRange3 (Bits 64-95)
string32(ulUnicodeRange4) + // ulUnicodeRange4 (Bits 96-127)
"\x2A\x32\x31\x2A" + // achVendID
"\x00\x20" + // fsSelection
"\x00\x2D" + // usFirstCharIndex
"\x00\x7A" + // usLastCharIndex
"\x00\x03" + // sTypoAscender
"\x00\x20" + // sTypeDescender
"\x00\x38" + // sTypoLineGap
string16(properties.italicAngle ? 1 : 0) + // fsSelection
string16(firstCharIndex || properties.firstChar) + // usFirstCharIndex
string16(lastCharIndex || properties.lastChar) + // usLastCharIndex
string16(properties.ascent) + // sTypoAscender
string16(properties.descent) + // sTypoDescender
"\x00\x64" + // sTypoLineGap (7%-10% of the unitsPerEM value)
string16(properties.ascent) + // usWinAscent
string16(properties.descent) + // usWinDescent
"\x00\xCE\x00\x00" + // ulCodePageRange1 (Bits 0-31)
"\x00\x01\x00\x00" + // ulCodePageRange2 (Bits 32-63)
string16(-properties.descent) + // usWinDescent
"\x00\x00\x00\x00" + // ulCodePageRange1 (Bits 0-31)
"\x00\x00\x00\x00" + // ulCodePageRange2 (Bits 32-63)
string16(properties.xHeight) + // sxHeight
string16(properties.capHeight) + // sCapHeight
"\x00\x01" + // usDefaultChar
"\x00\xCD" + // usBreakChar
"\x00\x02"; // usMaxContext
string16(0) + // usDefaultChar
string16(firstCharIndex || properties.firstChar) + // usBreakChar
"\x00\x03"; // usMaxContext
};
function createPostTable(properties) {
TODO("Fill with real values from the font dict");
return "\x00\x03\x00\x00" + // Version number
string32(properties.italicAngle) + // italicAngle
"\x00\x00" + // underlinePosition
"\x00\x00" + // underlineThickness
"\x00\x00\x00\x00" + // isFixedPitch
"\x00\x00\x00\x00" + // minMemType42
"\x00\x00\x00\x00" + // maxMemType42
"\x00\x00\x00\x00" + // minMemType1
"\x00\x00\x00\x00"; // maxMemType1
var angle = Math.floor(properties.italicAngle * (Math.pow(2, 16)));
return "\x00\x03\x00\x00" + // Version number
string32(angle) + // italicAngle
"\x00\x00" + // underlinePosition
"\x00\x00" + // underlineThickness
string32(properties.fixedPitch) + // isFixedPitch
"\x00\x00\x00\x00" + // minMemType42
"\x00\x00\x00\x00" + // maxMemType42
"\x00\x00\x00\x00" + // minMemType1
"\x00\x00\x00\x00"; // maxMemType1
};
constructor.prototype = {
@ -604,44 +800,75 @@ var Font = (function () { @@ -604,44 +800,75 @@ var Font = (function () {
var otf = new Uint8Array(kMaxFontFileSize);
function createNameTable(name) {
var names = [
"See original licence", // Copyright
fontName, // Font family
"undefined", // Font subfamily (font weight)
"uniqueID", // Unique ID
fontName, // Full font name
"0.1", // Version
"undefined", // Postscript name
"undefined", // Trademark
"undefined", // Manufacturer
"undefined" // Designer
// All the strings of the name table should be an odd number of bytes
if (name.length % 2)
name = name.slice(0, name.length - 1);
var strings = [
"Original licence", // 0.Copyright
name, // 1.Font family
"Unknown", // 2.Font subfamily (font weight)
"uniqueID", // 3.Unique ID
name, // 4.Full font name
"Version 0.11", // 5.Version
"Unknown", // 6.Postscript name
"Unknown", // 7.Trademark
"Unknown", // 8.Manufacturer
"Unknown" // 9.Designer
];
// Mac want 1-byte per character strings while Windows want
// 2-bytes per character, so duplicate the names table
var stringsUnicode = [];
for (var i = 0; i < strings.length; i++) {
var str = strings[i];
var strUnicode = "";
for (var j = 0; j < str.length; j++)
strUnicode += string16(str.charCodeAt(j));
stringsUnicode.push(strUnicode);
}
var names = [strings, stringsUnicode];
var platforms = ["\x00\x01", "\x00\x03"];
var encodings = ["\x00\x00", "\x00\x01"];
var languages = ["\x00\x00", "\x04\x09"];
var namesRecordCount = strings.length * platforms.length;
var nameTable =
"\x00\x00" + // format
"\x00\x0A" + // Number of names Record
"\x00\x7E"; // Storage
"\x00\x00" + // format
string16(namesRecordCount) + // Number of names Record
string16(namesRecordCount * 12 + 6); // Storage
// Build the name records field
var strOffset = 0;
for (var i = 0; i < names.length; i++) {
var str = names[i];
var nameRecord =
"\x00\x01" + // platform ID
"\x00\x00" + // encoding ID
"\x00\x00" + // language ID
"\x00\x00" + // name ID
string16(str.length) +
string16(strOffset);
nameTable += nameRecord;
strOffset += str.length;
for (var i = 0; i < platforms.length; i++) {
var strs = names[i];
for (var j = 0; j < strs.length; j++) {
var str = strs[j];
var nameRecord =
platforms[i] + // platform ID
encodings[i] + // encoding ID
languages[i] + // language ID
string16(i) + // name ID
string16(str.length) +
string16(strOffset);
nameTable += nameRecord;
strOffset += str.length;
}
}
nameTable += names.join("");
nameTable += strings.join("") + stringsUnicode.join("");
return nameTable;
}
function isFixedPitch(glyphs) {
for (var i = 0; i < glyphs.length - 1; i++) {
if (glyphs[i] != glyphs[i+1])
return false;
}
return true;
};
// Required Tables
var CFF =
@ -672,30 +899,31 @@ var Font = (function () { @@ -672,30 +899,31 @@ var Font = (function () {
createTableEntry(otf, offsets, "CFF ", CFF);
/** OS/2 */
var charstrings = font.charstrings;
properties.fixedPitch = isFixedPitch(charstrings);
OS2 = stringToArray(createOS2Table(properties));
createTableEntry(otf, offsets, "OS/2", OS2);
/** CMAP */
var charstrings = font.charstrings;
cmap = createCMapTable(charstrings);
cmap = createCMapTable(charstrings.slice());
createTableEntry(otf, offsets, "cmap", cmap);
/** HEAD */
head = stringToArray(
"\x00\x01\x00\x00" + // Version number
"\x00\x00\x50\x00" + // fontRevision
"\x00\x00\x10\x00" + // fontRevision
"\x00\x00\x00\x00" + // checksumAdjustement
"\x5F\x0F\x3C\xF5" + // magicNumber
"\x00\x00" + // Flags
"\x03\xE8" + // unitsPerEM (defaulting to 1000)
"\x00\x00\x00\x00\x00\x00\x00\x00" + // creation date
"\x00\x00\x00\x00\x00\x00\x00\x00" + // modifification date
"\x00\x00\x00\x00\x9e\x0b\x7e\x27" + // creation date
"\x00\x00\x00\x00\x9e\x0b\x7e\x27" + // modifification date
"\x00\x00" + // xMin
"\x00\x00" + // yMin
"\x00\x00" + // xMax
"\x00\x00" + // yMax
"\x00\x00" + // macStyle
"\x00\x00" + // lowestRecPPEM
string16(properties.descent) + // yMin
"\x0F\xFF" + // xMax
string16(properties.ascent) + // yMax
string16(properties.italicAngle ? 2 : 0) + // macStyle
"\x00\x11" + // lowestRecPPEM
"\x00\x00" + // fontDirectionHint
"\x00\x00" + // indexToLocFormat
"\x00\x00" // glyphDataFormat
@ -705,22 +933,22 @@ var Font = (function () { @@ -705,22 +933,22 @@ var Font = (function () {
/** HHEA */
hhea = stringToArray(
"\x00\x01\x00\x00" + // Version number
"\x00\x00" + // Typographic Ascent
"\x00\x00" + // Typographic Descent
string16(properties.ascent) + // Typographic Ascent
string16(properties.descent) + // Typographic Descent
"\x00\x00" + // Line Gap
"\xFF\xFF" + // advanceWidthMax
"\x00\x00" + // minLeftSidebearing
"\x00\x00" + // minRightSidebearing
"\x00\x00" + // xMaxExtent
"\x00\x00" + // caretSlopeRise
"\x00\x00" + // caretSlopeRun
string16(properties.capHeight) + // caretSlopeRise
string16(Math.tan(properties.italicAngle) * properties.xHeight) + // caretSlopeRun
"\x00\x00" + // caretOffset
"\x00\x00" + // -reserved-
"\x00\x00" + // -reserved-
"\x00\x00" + // -reserved-
"\x00\x00" + // -reserved-
"\x00\x00" + // metricDataFormat
string16(charstrings.length)
string16(charstrings.length + 1) // Number of HMetrics
);
createTableEntry(otf, offsets, "hhea", hhea);
@ -730,23 +958,21 @@ var Font = (function () { @@ -730,23 +958,21 @@ var Font = (function () {
* while Windows use this data. So be careful if you hack on Linux and
* have to touch the 'hmtx' table
*/
hmtx = "\x01\xF4\x00\x00"; // Fake .notdef
var width = 0, lsb = 0;
hmtx = "\x00\x00\x00\x00"; // Fake .notdef
for (var i = 0; i < charstrings.length; i++) {
width = charstrings[i].charstring[1];
hmtx += string16(width) + string16(lsb);
hmtx += string16(charstrings[i].width) + string16(0);
}
hmtx = stringToArray(hmtx);
createTableEntry(otf, offsets, "hmtx", hmtx);
/** MAXP */
maxp = "\x00\x00\x50\x00" + // Version number
string16(charstrings.length + 1); // Num of glyphs (+1 to pass the sanitizer...)
string16(charstrings.length + 1); // Num of glyphs
maxp = stringToArray(maxp);
createTableEntry(otf, offsets, "maxp", maxp);
/** NAME */
name = stringToArray(createNameTable(name));
name = stringToArray(createNameTable(fontName));
createTableEntry(otf, offsets, "name", name);
/** POST */
@ -778,9 +1004,18 @@ var Font = (function () { @@ -778,9 +1004,18 @@ var Font = (function () {
});
},
bindDOM: function font_bindDom(data) {
bindDOM: function font_bindDom(data, callback) {
var fontName = this.name;
// Just adding the font-face to the DOM doesn't make it load. It
// seems it's loaded once Gecko notices it's used. Therefore,
// add a div on the page using the loaded font.
var div = document.createElement("div");
var style = 'font-family:"' + name +
'";position: absolute;top:-99999;left:-99999;z-index:-99999';
div.setAttribute("style", style);
document.body.appendChild(div);
/** Hack begin */
// Actually there is not event when a font has finished downloading so
// the following code are a dirty hack to 'guess' when a font is ready
@ -800,15 +1035,19 @@ var Font = (function () { @@ -800,15 +1035,19 @@ var Font = (function () {
// For some reasons the font has not loaded, so mark it loaded for the
// page to proceed but cry
if ((Date.now() - this.start) >= kMaxWaitForFontFace) {
window.clearInterval(interval);
Fonts[fontName].loading = false;
warn("Is " + fontName + " loaded?");
this.start = 0;
} else if (textWidth != ctx.measureText(testString).width) {
window.clearInterval(interval);
Fonts[fontName].loading = false;
this.start = 0;
if (textWidth == ctx.measureText(testString).width) {
if ((Date.now() - this.start) < kMaxWaitForFontFace) {
return;
} else {
warn("Is " + fontName + " loaded?");
}
}
window.clearInterval(interval);
Fonts.lookup(fontName).loading = false;
this.start = 0;
if (callback) {
callback();
}
}, 30, this);
@ -839,7 +1078,7 @@ var FontsUtils = { @@ -839,7 +1078,7 @@ var FontsUtils = {
bytes.set([value]);
return bytes[0];
} else if (bytesCount == 2) {
bytes.set([value >> 8, value]);
bytes.set([value >> 8, value & 0xff]);
return [bytes[0], bytes[1]];
} else if (bytesCount == 4) {
bytes.set([value >> 24, value >> 16, value >> 8, value]);
@ -980,16 +1219,8 @@ var Type1Parser = function() { @@ -980,16 +1219,8 @@ var Type1Parser = function() {
"12": "div",
// callothersubr is a mechanism to make calls on the postscript
// interpreter.
// TODO When decodeCharstring encounter such a command it should
// directly do:
// - pop the previous charstring[] command into 'index'
// - pop the previous charstring[] command and ignore it, it is
// normally the number of element to push on the stack before
// the command but since everything will be pushed on the stack
// by the PS interpreter when it will read them that is safe to
// ignore this command
// - push the content of the OtherSubrs[index] inside charstring[]
// interpreter, this is not supported by Type2 charstring but hopefully
// most of the default commands can be ignored safely.
"16": "callothersubr",
"17": "pop",
@ -1009,8 +1240,13 @@ var Type1Parser = function() { @@ -1009,8 +1240,13 @@ var Type1Parser = function() {
"31": "hvcurveto"
};
var kEscapeCommand = 12;
function decodeCharString(array) {
var charString = [];
var charstring = [];
var lsb = 0;
var width = 0;
var used = false;
var value = "";
var count = array.length;
@ -1019,10 +1255,48 @@ var Type1Parser = function() { @@ -1019,10 +1255,48 @@ var Type1Parser = function() {
if (value < 32) {
var command = null;
if (value == 12) {
if (value == kEscapeCommand) {
var escape = array[++i];
// TODO Clean this code
if (escape == 16) {
var index = charstring.pop();
var argc = charstring.pop();
var data = charstring.pop();
// If the flex mechanishm is not used in a font program, Adobe
// state that that entries 0, 1 and 2 can simply be replace by
// {}, which means that we can simply ignore them.
if (index < 3) {
continue;
}
// This is the same things about hint replacement, if it is not used
// entry 3 can be replaced by {3}
if (index == 3) {
charstring.push(3);
i++;
continue;
}
}
command = charStringDictionary["12"][escape];
} else {
// TODO Clean this code
if (value == 13) {
if (charstring.length == 2) {
width = charstring[1];
} else if (charstring.length == 4 && charstring[3] == "div") {
width = charstring[1] / charstring[2];
} else {
error("Unsupported hsbw format: " + charstring);
}
lsb = charstring[0];
charstring.push(lsb, "hmoveto");
charstring.splice(0, 1);
continue;
}
command = charStringDictionary[value];
}
@ -1044,16 +1318,14 @@ var Type1Parser = function() { @@ -1044,16 +1318,14 @@ var Type1Parser = function() {
} else if (value <= 254) {
value = -((value - 251) * 256) - parseInt(array[++i]) - 108;
} else {
var byte = array[++i];
var high = (byte >> 1);
value = (byte - high) << 24 | array[++i] << 16 |
array[++i] << 8 | array[++i];
value = (array[++i] & 0xff) << 24 | (array[++i] & 0xff) << 16 |
(array[++i] & 0xff) << 8 | (array[++i] & 0xff) << 0;
}
charString.push(value);
charstring.push(value);
}
return charString;
return { charstring: charstring, width: width, lsb: lsb };
};
/**
@ -1080,19 +1352,21 @@ var Type1Parser = function() { @@ -1080,19 +1352,21 @@ var Type1Parser = function() {
length = parseInt(length);
var data = eexecString.slice(i + 3, i + 3 + length);
var encodedSubr = decrypt(data, kCharStringsEncryptionKey, 4);
var subr = decodeCharString(encodedSubr);
var str = decodeCharString(encodedSubr);
subrs.push(subr);
subrs.push(str.charstring);
i += 3 + length;
} else if (inGlyphs && c == 0x52) {
length = parseInt(length);
var data = eexecString.slice(i + 3, i + 3 + length);
var encodedCharstring = decrypt(data, kCharStringsEncryptionKey, 4);
var subr = decodeCharString(encodedCharstring);
var str = decodeCharString(encodedCharstring);
glyphs.push({
glyph: glyph,
data: subr
data: str.charstring,
lsb: str.lsb,
width: str.width
});
i += 3 + length;
} else if (inGlyphs && c == 0x2F) {
@ -1254,16 +1528,18 @@ CFF.prototype = { @@ -1254,16 +1528,18 @@ CFF.prototype = {
var charstrings = [];
for (var i = 0; i < glyphs.length; i++) {
var glyph = glyphs[i].glyph;
var unicode = GlyphsUnicode[glyph];
var glyph = glyphs[i];
var unicode = GlyphsUnicode[glyph.glyph];
if (!unicode) {
if (glyph != ".notdef")
if (glyph.glyph != ".notdef")
warn(glyph + " does not have an entry in the glyphs unicode dictionary");
} else {
charstrings.push({
glyph: glyph,
unicode: unicode,
charstring: glyphs[i].data
charstring: glyph.data,
width: glyph.width,
lsb: glyph.lsb
});
}
};
@ -1305,46 +1581,11 @@ CFF.prototype = { @@ -1305,46 +1581,11 @@ CFF.prototype = {
var i = 0;
while (true) {
var obj = charstring[i];
if (obj == null)
return [];
if (obj == undefined) {
error("unknow charstring command for " + i + " in " + charstring);
}
if (obj.charAt) {
switch (obj) {
case "callothersubr":
var index = charstring[i - 1];
var count = charstring[i - 2];
var data = charstring[i - 3];
// If the flex mechanishm is not used in a font program, Adobe
// state that that entries 0, 1 and 2 can simply be replace by
// {}, which means that we can simply ignore them.
if (index < 3) {
i -= 3;
continue;
}
// This is the same things about hint replacment, if it is not used
// entry 3 can be replaced by {}
if (index == 3) {
if (!data) {
charstring.splice(i - 2, 4, 3);
i -= 3;
} else {
// 5 to remove the arguments, the callothersubr call and the pop command
charstring.splice(i - 3, 5, 3);
i -= 3;
}
}
break;
case "hsbw":
var charWidthVector = charstring[1];
var leftSidebearing = charstring[0];
charstring.splice(i, 1, leftSidebearing, "hmoveto");
charstring.splice(0, 1);
break;
case "endchar":
case "return":
// CharString is ready to be re-encode to commands number at this point
@ -1356,7 +1597,7 @@ CFF.prototype = { @@ -1356,7 +1597,7 @@ CFF.prototype = {
} else if (command.charAt) {
var cmd = this.commandsMap[command];
if (!cmd)
error(command);
error("Unknow command: " + command);
if (IsArray(cmd)) {
charstring.splice(j, 1, cmd[0], cmd[1]);
@ -1428,7 +1669,7 @@ CFF.prototype = { @@ -1428,7 +1669,7 @@ CFF.prototype = {
charset.push(bytes[1]);
}
var charstringsIndex = this.createCFFIndexHeader([[0x40, 0x0E]].concat(glyphs), true);
var charstringsIndex = this.createCFFIndexHeader([[0x8B, 0x0E]].concat(glyphs), true);
//Top Dict Index
var topDictIndex = [

2
multi_page_viewer.css

@ -181,7 +181,7 @@ span { @@ -181,7 +181,7 @@ span {
width: 200px;
top: 62px;
bottom: 18px;
left: -170px;
left: -140px;
transition: left 0.25s ease-in-out 1s;
-moz-transition: left 0.25s ease-in-out 1s;
-webkit-transition: left 0.25s ease-in-out 1s;

4
multi_page_viewer.html

@ -27,9 +27,9 @@ @@ -27,9 +27,9 @@
<select id="scaleSelect">
<option value="50">50%</option>
<option value="75">75%</option>
<option value="100" selected="selected">100%</option>
<option value="100">100%</option>
<option value="125">125%</option>
<option value="150">150%</option>
<option value="150" selected="selected">150%</option>
<option value="200">200%</option>
</select>
<span class="label">Zoom</span>

55
multi_page_viewer.js

@ -29,11 +29,15 @@ var PDFViewer = { @@ -29,11 +29,15 @@ var PDFViewer = {
scale: 1.0,
pageWidth: function(page) {
return page.mediaBox[2] * PDFViewer.scale;
var pdfToCssUnitsCoef = 96.0 / 72.0;
var width = (page.mediaBox[2] - page.mediaBox[0]);
return width * PDFViewer.scale * pdfToCssUnitsCoef;
},
pageHeight: function(page) {
return page.mediaBox[3] * PDFViewer.scale;
var pdfToCssUnitsCoef = 96.0 / 72.0;
var height = (page.mediaBox[3] - page.mediaBox[1]);
return height * PDFViewer.scale * pdfToCssUnitsCoef;
},
lastPagesDrawn: [],
@ -106,10 +110,11 @@ var PDFViewer = { @@ -106,10 +110,11 @@ var PDFViewer = {
canvas.id = 'thumbnail' + num;
canvas.mozOpaque = true;
// Canvas dimensions must be specified in CSS pixels. CSS pixels
// are always 96 dpi. These dimensions are 8.5in x 11in at 96dpi.
canvas.width = 104;
canvas.height = 134;
var pageWidth = PDFViewer.pageWidth(page);
var pageHeight = PDFViewer.pageHeight(page);
var thumbScale = Math.min(104 / pageWidth, 134 / pageHeight);
canvas.width = pageWidth * thumbScale;
canvas.height = pageHeight * thumbScale;
div.appendChild(canvas);
var ctx = canvas.getContext('2d');
@ -175,8 +180,6 @@ var PDFViewer = { @@ -175,8 +180,6 @@ var PDFViewer = {
canvas.id = 'page' + num;
canvas.mozOpaque = true;
// Canvas dimensions must be specified in CSS pixels. CSS pixels
// are always 96 dpi. These dimensions are 8.5in x 11in at 96dpi.
canvas.width = PDFViewer.pageWidth(page);
canvas.height = PDFViewer.pageHeight(page);
div.appendChild(canvas);
@ -216,7 +219,6 @@ var PDFViewer = { @@ -216,7 +219,6 @@ var PDFViewer = {
if (PDFViewer.pdf) {
for (i = 1; i <= PDFViewer.numberOfPages; i++) {
PDFViewer.createThumbnail(i);
PDFViewer.createPage(i);
}
}
@ -249,7 +251,10 @@ var PDFViewer = { @@ -249,7 +251,10 @@ var PDFViewer = {
PDFViewer.pageNumber = num;
PDFViewer.pageNumberInput.value = PDFViewer.pageNumber;
PDFViewer.willJumpToPage = true;
if (document.location.hash.substr(1) == PDFViewer.pageNumber)
// Force a "scroll event" to redraw
setTimeout(window.onscroll, 0);
document.location.hash = PDFViewer.pageNumber;
PDFViewer.previousPageButton.className = (PDFViewer.pageNumber === 1) ? 'disabled' : '';
@ -272,6 +277,12 @@ var PDFViewer = { @@ -272,6 +277,12 @@ var PDFViewer = {
openURL: function(url) {
PDFViewer.url = url;
document.title = url;
if (this.thumbsLoadingInterval) {
// cancel thumbs loading operations
clearInterval(this.thumbsLoadingInterval);
this.thumbsLoadingInterval = null;
}
var req = new XMLHttpRequest();
req.open('GET', url);
@ -288,7 +299,9 @@ var PDFViewer = { @@ -288,7 +299,9 @@ var PDFViewer = {
req.send(null);
},
thumbsLoadingInterval: null,
readPDF: function(data) {
while (PDFViewer.element.hasChildNodes()) {
PDFViewer.element.removeChild(PDFViewer.element.firstChild);
@ -310,12 +323,22 @@ var PDFViewer = { @@ -310,12 +323,22 @@ var PDFViewer = {
PDFViewer.drawPage(1);
document.location.hash = 1;
setTimeout(function() {
for (var i = 1; i <= PDFViewer.numberOfPages; i++) {
PDFViewer.createThumbnail(i);
PDFViewer.drawThumbnail(i);
// slowly loading the thumbs (few per second)
// first time we are loading more images than subsequent
var currentPageIndex = 1, imagesToLoad = 15;
this.thumbsLoadingInterval = setInterval((function() {
while (imagesToLoad-- > 0) {
if (currentPageIndex > PDFViewer.numberOfPages) {
clearInterval(this.thumbsLoadingInterval);
this.thumbsLoadingInterval = null;
return;
}
PDFViewer.createThumbnail(currentPageIndex);
PDFViewer.drawThumbnail(currentPageIndex);
++currentPageIndex;
}
}, 500);
imagesToLoad = 3; // next time loading less images
}).bind(this), 500);
}
PDFViewer.previousPageButton.className = (PDFViewer.pageNumber === 1) ? 'disabled' : '';

24
pdf.js

@ -641,7 +641,7 @@ var PredictorStream = (function() { @@ -641,7 +641,7 @@ var PredictorStream = (function() {
var pixBytes = this.pixBytes = (colors * bits + 7) >> 3;
// add an extra pixByte to represent the pixel left of column 0
var rowBytes = this.rowBytes = (columns * colors * bits + 7) >> 3;
DecodeStream.call(this);
return this;
}
@ -3440,6 +3440,7 @@ var CanvasGraphics = (function() { @@ -3440,6 +3440,7 @@ var CanvasGraphics = (function() {
if (charset) {
assertWellFormed(IsString(charset), "invalid charset");
charset = charset.split("/");
charset.shift();
}
} else if (IsName(encoding)) {
var encoding = Encodings[encoding.name];
@ -3534,13 +3535,16 @@ var CanvasGraphics = (function() { @@ -3534,13 +3535,16 @@ var CanvasGraphics = (function() {
type: subType.name,
encoding: encodingMap,
charset: charset,
firstChar: fontDict.get("FirstChar"),
lastChar: fontDict.get("LastChar"),
bbox: descriptor.get("FontBBox"),
ascent: descriptor.get("Ascent"),
descent: descriptor.get("Descent"),
xHeight: descriptor.get("XHeight"),
capHeight: descriptor.get("CapHeight"),
flags: descriptor.get("Flags"),
italicAngle: descriptor.get("ItalicAngle")
italicAngle: descriptor.get("ItalicAngle"),
fixedPitch: false
};
return {
@ -3807,18 +3811,22 @@ var CanvasGraphics = (function() { @@ -3807,18 +3811,22 @@ var CanvasGraphics = (function() {
if (fontDescriptor && fontDescriptor.num) {
var fontDescriptor = this.xref.fetchIfRef(fontDescriptor);
fontName = fontDescriptor.get("FontName").name.replace("+", "_");
Fonts.active = fontName;
}
if (!fontName) {
// TODO: fontDescriptor is not available, fallback to default font
this.current.fontSize = size;
this.ctx.font = this.current.fontSize + 'px sans-serif';
return;
fontName = "sans-serif";
}
this.current.fontName = fontName;
this.current.fontSize = size;
this.ctx.font = this.current.fontSize +'px "' + fontName + '", Symbol';
if (this.ctx.$setFont) {
this.ctx.$setFont(fontName, size);
} else {
this.ctx.font = size + 'px "' + fontName + '"';
Fonts.setActive(fontName, size);
}
},
setTextRenderingMode: function(mode) {
TODO("text rendering mode");
@ -3862,7 +3870,7 @@ var CanvasGraphics = (function() { @@ -3862,7 +3870,7 @@ var CanvasGraphics = (function() {
text = Fonts.charsToUnicode(text);
this.ctx.translate(this.current.x, -1 * this.current.y);
this.ctx.fillText(text, 0, 0);
this.current.x += this.ctx.measureText(text).width;
this.current.x += Fonts.measureText(text);
}
this.ctx.restore();

10
test/test_slave.html

@ -4,6 +4,7 @@ @@ -4,6 +4,7 @@
<style type="text/css"></style>
<script type="text/javascript" src="/pdf.js"></script>
<script type="text/javascript" src="/fonts.js"></script>
<script type="text/javascript" src="/crypto.js"></script>
<script type="text/javascript" src="/glyphlist.js"></script>
<script type="application/javascript">
var appPath, browser, canvas, currentTask, currentTaskIdx, failure, manifest, numPages, pdfDoc, stdout;
@ -103,11 +104,12 @@ function nextPage() { @@ -103,11 +104,12 @@ function nextPage() {
}
try {
var pdfToCssUnitsCoef = 96.0 / 72.0;
// using mediaBox for the canvas size
var wTwips = (currentPage.mediaBox[2] - currentPage.mediaBox[0]);
var hTwips = (currentPage.mediaBox[3] - currentPage.mediaBox[1]);
canvas.width = wTwips * 96.0 / 72.0;
canvas.height = hTwips * 96.0 / 72.0;
var pageWidth = (currentPage.mediaBox[2] - currentPage.mediaBox[0]);
var pageHeight = (currentPage.mediaBox[3] - currentPage.mediaBox[1]);
canvas.width = pageWidth * pdfToCssUnitsCoef;
canvas.height = pageHeight * pdfToCssUnitsCoef;
clear(ctx);
} catch(e) {
failure = 'page setup: '+ e.toString();

7
viewer.css

@ -24,10 +24,11 @@ span#info { @@ -24,10 +24,11 @@ span#info {
}
#viewer {
}
#canvas {
margin: auto;
border: 1px solid black;
width: 12.75in;
height: 16.5in;
display: block;
}
#pageNumber {

4
viewer.html

@ -25,9 +25,7 @@ @@ -25,9 +25,7 @@
</div>
<div id="viewer">
<!-- Canvas dimensions must be specified in CSS pixels. CSS pixels
are always 96 dpi. 816x1056 is 8.5x11in at 96dpi. -->
<canvas id="canvas" width="816" height="1056" defaultwidth="816" defaultheight="1056"></canvas>
<canvas id="canvas"></canvas>
</div>
</body>
</html>

10
viewer.js

@ -60,12 +60,12 @@ function displayPage(num) { @@ -60,12 +60,12 @@ function displayPage(num) {
var t0 = Date.now();
var page = pdfDocument.getPage(pageNum = num);
canvas.width = parseInt(canvas.getAttribute("defaultwidth")) * pageScale;
canvas.height = parseInt(canvas.getAttribute("defaultheight")) * pageScale;
// scale canvas by 2
canvas.width = 2 * page.mediaBox[2];
canvas.hieght = 2 * page.mediaBox[3];
var pdfToCssUnitsCoef = 96.0 / 72.0;
var pageWidth = (page.mediaBox[2] - page.mediaBox[0]);
var pageHeight = (page.mediaBox[3] - page.mediaBox[1]);
canvas.width = pageScale * pageWidth * pdfToCssUnitsCoef;
canvas.height = pageScale * pageHeight * pdfToCssUnitsCoef;
var t1 = Date.now();
var ctx = canvas.getContext("2d");

10
viewer_worker.html

@ -1,7 +1,10 @@ @@ -1,7 +1,10 @@
<html>
<head>
<title>Simple pdf.js page worker viewer</title>
<script type="text/javascript" src="worker_client.js"></script>
<script type="text/javascript" src="fonts.js"></script>
<script type="text/javascript" src="glyphlist.js"></script>
<script type="text/javascript" src="pdf.js"></script>
<script type="text/javascript" src="worker/client.js"></script>
<script>
@ -36,10 +39,7 @@ window.onload = function() { @@ -36,10 +39,7 @@ window.onload = function() {
</div>
<div id="viewer">
<!-- Canvas dimensions must be specified in CSS pixels. CSS pixels
are always 96 dpi. 816x1056 is 8.5x11in at 96dpi. -->
<!-- We're rendering here at 1.5x scale. -->
<canvas id="canvas" width="1224" height="1584"></canvas>
<canvas id="canvas"></canvas>
</div>
</body>
</html>

3
canvas_proxy.js → worker/canvas.js

@ -119,7 +119,8 @@ function CanvasProxy(width, height) { @@ -119,7 +119,8 @@ function CanvasProxy(width, height) {
"$addCurrentX",
"$saveCurrentX",
"$restoreCurrentX",
"$showText"
"$showText",
"$setFont"
];
function buildFuncCall(name) {

161
worker_client.js → worker/client.js

@ -18,12 +18,115 @@ if (typeof console.time == "undefined") { @@ -18,12 +18,115 @@ if (typeof console.time == "undefined") {
};
}
function FontWorker() {
this.worker = new Worker("worker/font.js");
this.fontsWaiting = 0;
this.fontsWaitingCallbacks = [];
// Listen to the WebWorker for data and call actionHandler on it.
this.worker.onmessage = function(event) {
var data = event.data;
var actionHandler = this.actionHandler
if (data.action in actionHandler) {
actionHandler[data.action].call(this, data.data);
} else {
throw "Unkown action from worker: " + data.action;
}
}.bind(this);
this.$handleFontLoadedCallback = this.handleFontLoadedCallback.bind(this);
}
FontWorker.prototype = {
handleFontLoadedCallback: function() {
// Decrease the number of fonts wainting to be loaded.
this.fontsWaiting--;
// If all fonts are available now, then call all the callbacks.
if (this.fontsWaiting == 0) {
var callbacks = this.fontsWaitingCallbacks;
for (var i = 0; i < callbacks.length; i++) {
callbacks[i]();
}
this.fontsWaitingCallbacks.length = 0;
}
},
actionHandler: {
"log": function(data) {
console.log.apply(console, data);
},
"fonts": function(data) {
// console.log("got processed fonts from worker", Object.keys(data));
for (name in data) {
// Update the encoding property.
var font = Fonts.lookup(name);
font.properties = {
encoding: data[name].encoding
}
// Call `Font.prototype.bindDOM` to make the font get loaded on the page.
Font.prototype.bindDOM.call(
font,
data[name].str,
// IsLoadedCallback.
this.$handleFontLoadedCallback
);
}
}
},
ensureFonts: function(data, callback) {
var font;
var notLoaded = [];
for (var i = 0; i < data.length; i++) {
font = data[i];
if (Fonts[font.name]) {
continue;
}
// Register the font but don't pass in any real data. The idea is to
// store as less data as possible to reduce memory usage.
Fonts.registerFont(font.name, Object.create(null), Object.create(null));
// Mark this font to be handled later.
notLoaded.push(font);
// Increate the number of fonts to wait for.
this.fontsWaiting++;
}
console.time("ensureFonts");
// If there are fonts, that need to get loaded, tell the FontWorker to get
// started and push the callback on the waiting-callback-stack.
if (notLoaded.length != 0) {
console.log("fonts -> FontWorker");
// Send the worker the fonts to work on.
this.worker.postMessage({
action: "fonts",
data: notLoaded
});
if (callback) {
this.fontsWaitingCallbacks.push(callback);
}
}
// All fonts are present? Well, then just call the callback if there is one.
else {
if (callback) {
callback();
}
}
},
}
function WorkerPDFDoc(canvas) {
var timer = null
this.ctx = canvas.getContext("2d");
this.canvas = canvas;
this.worker = new Worker('pdf_worker.js');
this.worker = new Worker('worker/pdf.js');
this.fontWorker = new FontWorker();
this.waitingForFonts = false;
this.waitingForFontsCallback = [];
this.numPage = 1;
this.numPages = null;
@ -56,6 +159,7 @@ function WorkerPDFDoc(canvas) { @@ -56,6 +159,7 @@ function WorkerPDFDoc(canvas) {
},
"$showText": function(y, text) {
text = Fonts.charsToUnicode(text);
this.translate(currentX, -1 * y);
this.fillText(text, 0, 0);
currentX += this.measureText(text).width;
@ -136,6 +240,11 @@ function WorkerPDFDoc(canvas) { @@ -136,6 +240,11 @@ function WorkerPDFDoc(canvas) {
throw "Pattern not found";
}
this.strokeStyle = pattern;
},
"$setFont": function(name, size) {
this.font = size + 'px "' + name + '"';
Fonts.setActive(name, size);
}
}
@ -187,6 +296,25 @@ function WorkerPDFDoc(canvas) { @@ -187,6 +296,25 @@ function WorkerPDFDoc(canvas) {
div.setAttribute("style", style);
document.body.appendChild(div);
},
"setup_page": function(data) {
var size = data.split(",");
var canvas = this.canvas, ctx = this.ctx;
canvas.width = parseInt(size[0]);
canvas.height = parseInt(size[1]);
},
"fonts": function(data) {
this.waitingForFonts = true;
this.fontWorker.ensureFonts(data, function() {
this.waitingForFonts = false;
var callbacks = this.waitingForFontsCallback;
for (var i = 0; i < callbacks.length; i++) {
callbacks[i]();
}
this.waitingForFontsCallback.length = 0;
}.bind(this));
},
"jpeg_stream": function(data) {
var img = new Image();
@ -207,11 +335,9 @@ function WorkerPDFDoc(canvas) { @@ -207,11 +335,9 @@ function WorkerPDFDoc(canvas) {
canvasList[id] = newCanvas;
}
// There might be fonts that need to get loaded. Shedule the
// rendering at the end of the event queue ensures this.
setTimeout(function() {
var renderData = function() {
if (id == 0) {
console.time("canvas rendering");
console.time("main canvas rendering");
var ctx = this.ctx;
ctx.save();
ctx.fillStyle = "rgb(255, 255, 255)";
@ -219,12 +345,27 @@ function WorkerPDFDoc(canvas) { @@ -219,12 +345,27 @@ function WorkerPDFDoc(canvas) {
ctx.restore();
}
renderProxyCanvas(canvasList[id], cmdQueue);
if (id == 0) console.timeEnd("canvas rendering")
}, 0, this);
if (id == 0) {
console.timeEnd("main canvas rendering");
console.timeEnd(">>> total page display time:");
}
}.bind(this);
if (this.waitingForFonts) {
if (id == 0) {
console.log("want to render, but not all fonts are there", id);
this.waitingForFontsCallback.push(renderData);
} else {
// console.log("assume canvas doesn't have fonts", id);
renderData();
}
} else {
renderData();
}
}
}
// List 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) {
var data = event.data;
if (data.action in actionHandler) {
@ -232,7 +373,7 @@ function WorkerPDFDoc(canvas) { @@ -232,7 +373,7 @@ function WorkerPDFDoc(canvas) {
} else {
throw "Unkown action from worker: " + data.action;
}
}
}.bind(this)
}
WorkerPDFDoc.prototype.open = function(url, callback) {
@ -255,6 +396,8 @@ WorkerPDFDoc.prototype.open = function(url, callback) { @@ -255,6 +396,8 @@ WorkerPDFDoc.prototype.open = function(url, callback) {
WorkerPDFDoc.prototype.showPage = function(numPage) {
this.numPage = parseInt(numPage);
console.log("=== start rendering page " + numPage + " ===");
console.time(">>> total page display time:");
this.worker.postMessage(numPage);
if (this.onChangePage) {
this.onChangePage(numPage);

27
worker/console.js

@ -0,0 +1,27 @@ @@ -0,0 +1,27 @@
/* -*- Mode: Java; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- /
/* vim: set shiftwidth=2 tabstop=2 autoindent cindent expandtab: */
"use strict";
var consoleTimer = {};
var console = {
log: function log() {
var args = Array.prototype.slice.call(arguments);
postMessage({
action: "log",
data: args
});
},
time: function(name) {
consoleTimer[name] = Date.now();
},
timeEnd: function(name) {
var time = consoleTimer[name];
if (time == null) {
throw "Unkown timer name " + name;
}
this.log("Timer:", name, Date.now() - time);
}
}

65
worker/font.js

@ -0,0 +1,65 @@ @@ -0,0 +1,65 @@
/* -*- Mode: Java; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- /
/* vim: set shiftwidth=2 tabstop=2 autoindent cindent expandtab: */
"use strict";
importScripts("console.js");
importScripts("../pdf.js");
importScripts("../fonts.js");
importScripts("../glyphlist.js")
function fontDataToString(font) {
// Doing postMessage on objects make them lose their "shape". This adds the
// "shape" for all required objects agains, such that the encoding works as
// expected.
var fontFileDict = new Dict();
fontFileDict.map = font.file.dict.map;
var fontFile = new Stream(font.file.bytes, font.file.start, font.file.end - font.file.start, fontFileDict);
font.file = new FlateStream(fontFile);
// This will encode the font.
var fontObj = new Font(font.name, font.file, font.properties);
// Create string that is used for css later.
var str = "";
var data = fontObj.data;
var length = data.length;
for (var j = 0; j < length; j++)
str += String.fromCharCode(data[j]);
return {
str: str,
encoding: font.properties.encoding
}
}
/**
* Functions to handle data sent by the MainThread.
*/
var actionHandler = {
"fonts": function(data) {
var fontData;
var result = {};
for (var i = 0; i < data.length; i++) {
fontData = data[i];
result[fontData.name] = fontDataToString(fontData);
}
postMessage({
action: "fonts",
data: result
})
},
}
// Listen to the MainThread for data and call actionHandler on it.
this.onmessage = function(event) {
var data = event.data;
if (data.action in actionHandler) {
actionHandler[data.action].call(this, data.data);
} else {
throw "Unkown action from worker: " + data.action;
}
}

41
pdf_worker.js → worker/pdf.js

@ -27,10 +27,12 @@ var console = { @@ -27,10 +27,12 @@ var console = {
}
//
importScripts("canvas_proxy.js");
importScripts("pdf.js");
importScripts("fonts.js");
importScripts("glyphlist.js")
importScripts("console.js")
importScripts("canvas.js");
importScripts("../pdf.js");
importScripts("../fonts.js");
importScripts("../crypto.js");
importScripts("../glyphlist.js")
// Use the JpegStreamProxy proxy.
JpegStream = JpegStreamProxy;
@ -58,6 +60,18 @@ onmessage = function(event) { @@ -58,6 +60,18 @@ onmessage = function(event) {
// Let's try to render the first page...
var page = pdfDocument.getPage(parseInt(data));
var pdfToCssUnitsCoef = 96.0 / 72.0;
var pageWidth = (page.mediaBox[2] - page.mediaBox[0]) * pdfToCssUnitsCoef;
var pageHeight = (page.mediaBox[3] - page.mediaBox[1]) * pdfToCssUnitsCoef;
postMessage({
action: "setup_page",
data: pageWidth + "," + pageHeight
});
// Set canvas size.
canvas.width = pageWidth;
canvas.height = pageHeight;
// page.compile will collect all fonts for us, once we have loaded them
// we can trigger the actual page rendering with page.display
var fonts = [];
@ -65,21 +79,14 @@ onmessage = function(event) { @@ -65,21 +79,14 @@ onmessage = function(event) {
page.compile(gfx, fonts);
console.timeEnd("compile");
// Send fonts to the main thread.
console.time("fonts");
// Inspect fonts and translate the missing one.
var count = fonts.length;
for (var i = 0; i < count; i++) {
var font = fonts[i];
if (Fonts[font.name]) {
fontsReady = fontsReady && !Fonts[font.name].loading;
continue;
}
// This "builds" the font and sents it over to the main thread.
new Font(font.name, font.file, font.properties);
}
postMessage({
action: "fonts",
data: fonts
});
console.timeEnd("fonts");
console.time("display");
page.display(gfx);
canvas.flush();
Loading…
Cancel
Save