Merge remote branch 'upstream/master'

This commit is contained in:
Rob Sayre 2011-06-30 21:02:47 -07:00
commit f4e5b2bcfd
18 changed files with 852 additions and 321 deletions

12
README
View File

@ -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 Normal file
View File

@ -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

View File

@ -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() {
})();
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() {
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;

701
fonts.js
View File

@ -3,6 +3,8 @@
"use strict";
var isWorker = (typeof window == "undefined");
/**
* Maximum file size of the font.
*/
@ -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];
},
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;
}
str += String.fromCharCode(unicode);
}
// Enter the translated string into the cache
return active.cache[chars] = str;
if (!isWorker) {
var ctx = document.createElement("canvas").getContext("2d");
ctx.scale(1 / kScalePrecision, 1);
}
};
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);
}
// 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 = {
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 () {
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 () {
// 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 () {
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 () {
// 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 () {
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 () {
};
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 () {
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 () {
};
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 () {
"\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,45 +800,76 @@ 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 =
font.data, // PostScript Font Program
@ -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 () {
/** 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 () {
* 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 () {
});
},
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 () {
// 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 = {
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() {
"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() {
"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() {
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() {
} 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() {
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 = {
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 = {
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 = {
} 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 = {
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 = [

View File

@ -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;

View File

@ -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>

View File

@ -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 = {
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 = {
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 = {
if (PDFViewer.pdf) {
for (i = 1; i <= PDFViewer.numberOfPages; i++) {
PDFViewer.createThumbnail(i);
PDFViewer.createPage(i);
}
}
@ -250,6 +252,9 @@ var PDFViewer = {
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' : '';
@ -273,6 +278,12 @@ var PDFViewer = {
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);
req.mozResponseType = req.responseType = 'arraybuffer';
@ -289,6 +300,8 @@ 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 = {
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' : '';

22
pdf.js
View File

@ -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() {
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() {
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() {
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();

View File

@ -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() {
}
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();

View File

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

View File

@ -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>

View File

@ -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");

View File

@ -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() {
</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>

View File

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

View File

@ -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) {
},
"$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) {
throw "Pattern not found";
}
this.strokeStyle = pattern;
},
"$setFont": function(name, size) {
this.font = size + 'px "' + name + '"';
Fonts.setActive(name, size);
}
}
@ -188,6 +297,25 @@ function WorkerPDFDoc(canvas) {
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();
img.src = "data:image/jpeg;base64," + window.btoa(data.raw);
@ -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) {
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) {
} 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) {
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 Normal file
View File

@ -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 Normal file
View File

@ -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;
}
}

View File

@ -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) {
// 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,19 +79,12 @@ 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");