Merge remote branch 'upstream/master'

Conflicts:
	web/viewer.js
This commit is contained in:
Vivien Nicolas 2011-08-23 16:40:49 +02:00
commit 62e7d2f608
18 changed files with 1923 additions and 608 deletions

View File

@ -7,6 +7,7 @@
Vivien Nicolas <21@vingtetun.org>
Justin D'Arcangelo <justindarc@gmail.com>
Yury Delendik
Kalervo Kujala
Permission is hereby granted, free of charge, to any person obtaining a
copy of this software and associated documentation files (the "Software"),
@ -25,3 +26,4 @@
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
DEALINGS IN THE SOFTWARE.

View File

@ -86,7 +86,7 @@ font-test:
# To install gjslint, see:
#
# <http://code.google.com/closure/utilities/docs/linter_howto.html>
SRC_DIRS := . utils worker web
SRC_DIRS := . utils worker web test
GJSLINT_FILES = $(foreach DIR,$(SRC_DIRS),$(wildcard $(DIR)/*.js))
lint:
gjslint $(GJSLINT_FILES)

370
fonts.js
View File

@ -4,11 +4,6 @@
'use strict';
var isWorker = (typeof window == 'undefined');
/**
* Maximum file size of the font.
*/
var kMaxFontFileSize = 200000;
/**
* Maximum time to wait for a font to be loaded by font-face rules.
*/
@ -19,46 +14,46 @@ var kMaxWaitForFontFace = 1000;
* fonts and their acronyms.
*/
var stdFontMap = {
"Arial": "Helvetica",
"Arial_Bold": "Helvetica-Bold",
"Arial_BoldItalic": "Helvetica-BoldOblique",
"Arial_Italic": "Helvetica-Oblique",
"Arial_BoldItalicMT": "Helvetica-BoldOblique",
"Arial_BoldMT": "Helvetica-Bold",
"Arial_ItalicMT": "Helvetica-Oblique",
"ArialMT": "Helvetica",
"Courier_Bold": "Courier-Bold",
"Courier_BoldItalic": "Courier-BoldOblique",
"Courier_Italic": "Courier-Oblique",
"CourierNew": "Courier",
"CourierNew_Bold": "Courier-Bold",
"CourierNew_BoldItalic": "Courier-BoldOblique",
"CourierNew_Italic": "Courier-Oblique",
"CourierNewPS_BoldItalicMT": "Courier-BoldOblique",
"CourierNewPS_BoldMT": "Courier-Bold",
"CourierNewPS_ItalicMT": "Courier-Oblique",
"CourierNewPSMT": "Courier",
"Helvetica_Bold": "Helvetica-Bold",
"Helvetica_BoldItalic": "Helvetica-BoldOblique",
"Helvetica_Italic": "Helvetica-Oblique",
"Symbol_Bold": "Symbol",
"Symbol_BoldItalic": "Symbol",
"Symbol_Italic": "Symbol",
"TimesNewRoman": "Times-Roman",
"TimesNewRoman_Bold": "Times-Bold",
"TimesNewRoman_BoldItalic": "Times-BoldItalic",
"TimesNewRoman_Italic": "Times-Italic",
"TimesNewRomanPS": "Times-Roman",
"TimesNewRomanPS_Bold": "Times-Bold",
"TimesNewRomanPS_BoldItalic": "Times-BoldItalic",
"TimesNewRomanPS_BoldItalicMT": "Times-BoldItalic",
"TimesNewRomanPS_BoldMT": "Times-Bold",
"TimesNewRomanPS_Italic": "Times-Italic",
"TimesNewRomanPS_ItalicMT": "Times-Italic",
"TimesNewRomanPSMT": "Times-Roman",
"TimesNewRomanPSMT_Bold": "Times-Bold",
"TimesNewRomanPSMT_BoldItalic": "Times-BoldItalic",
"TimesNewRomanPSMT_Italic": "Times-Italic"
'Arial': 'Helvetica',
'Arial_Bold': 'Helvetica-Bold',
'Arial_BoldItalic': 'Helvetica-BoldOblique',
'Arial_Italic': 'Helvetica-Oblique',
'Arial_BoldItalicMT': 'Helvetica-BoldOblique',
'Arial_BoldMT': 'Helvetica-Bold',
'Arial_ItalicMT': 'Helvetica-Oblique',
'ArialMT': 'Helvetica',
'Courier_Bold': 'Courier-Bold',
'Courier_BoldItalic': 'Courier-BoldOblique',
'Courier_Italic': 'Courier-Oblique',
'CourierNew': 'Courier',
'CourierNew_Bold': 'Courier-Bold',
'CourierNew_BoldItalic': 'Courier-BoldOblique',
'CourierNew_Italic': 'Courier-Oblique',
'CourierNewPS_BoldItalicMT': 'Courier-BoldOblique',
'CourierNewPS_BoldMT': 'Courier-Bold',
'CourierNewPS_ItalicMT': 'Courier-Oblique',
'CourierNewPSMT': 'Courier',
'Helvetica_Bold': 'Helvetica-Bold',
'Helvetica_BoldItalic': 'Helvetica-BoldOblique',
'Helvetica_Italic': 'Helvetica-Oblique',
'Symbol_Bold': 'Symbol',
'Symbol_BoldItalic': 'Symbol',
'Symbol_Italic': 'Symbol',
'TimesNewRoman': 'Times-Roman',
'TimesNewRoman_Bold': 'Times-Bold',
'TimesNewRoman_BoldItalic': 'Times-BoldItalic',
'TimesNewRoman_Italic': 'Times-Italic',
'TimesNewRomanPS': 'Times-Roman',
'TimesNewRomanPS_Bold': 'Times-Bold',
'TimesNewRomanPS_BoldItalic': 'Times-BoldItalic',
'TimesNewRomanPS_BoldItalicMT': 'Times-BoldItalic',
'TimesNewRomanPS_BoldMT': 'Times-Bold',
'TimesNewRomanPS_Italic': 'Times-Italic',
'TimesNewRomanPS_ItalicMT': 'Times-Italic',
'TimesNewRomanPSMT': 'Times-Roman',
'TimesNewRomanPSMT_Bold': 'Times-Bold',
'TimesNewRomanPSMT_BoldItalic': 'Times-BoldItalic',
'TimesNewRomanPSMT_Italic': 'Times-Italic'
};
var FontMeasure = (function FontMeasure() {
@ -76,14 +71,14 @@ var FontMeasure = (function FontMeasure() {
if (!(measureCache = sizes[size]))
measureCache = sizes[size] = Object.create(null);
} else {
measureCache = null
measureCache = null;
}
var name = font.loadedName;
var bold = font.bold ? "bold" : "normal";
var italic = font.italic ? "italic" : "normal";
var bold = font.bold ? 'bold' : 'normal';
var italic = font.italic ? 'italic' : 'normal';
size *= kScalePrecision;
var rule = bold + " " + italic + " " + size + 'px "' + name + '"';
var rule = italic + ' ' + bold + ' ' + size + 'px "' + name + '"';
ctx.font = rule;
},
measureText: function fonts_measureText(text) {
@ -395,17 +390,23 @@ var Font = (function Font() {
// If the font is to be ignored, register it like an already loaded font
// to avoid the cost of waiting for it be be loaded by the platform.
if (properties.ignore) {
this.loadedName = 'Arial';
this.loadedName = 'sans-serif';
this.loading = false;
return;
}
if (!file) {
var fontName = stdFontMap[name];
this.bold = (fontName.indexOf("Bold") != -1);
this.italic = (fontName.indexOf("Oblique") != -1);
this.loadedName = fontName.split("-")[0];
// The file data is not specified. Trying to fix the font name
// to be used with the canvas.font.
var fontName = stdFontMap[name] || name.replace('_', '-');
this.bold = (fontName.indexOf('Bold') != -1);
this.italic = (fontName.indexOf('Oblique') != -1) ||
(fontName.indexOf('Italic') != -1);
this.loadedName = fontName.split('-')[0];
this.loading = false;
this.charsToUnicode = function(s) {
return s;
};
return;
}
@ -461,6 +462,14 @@ var Font = (function Font() {
return array;
};
function arrayToString(arr) {
var str = '';
for (var i = 0; i < arr.length; ++i)
str += String.fromCharCode(arr[i]);
return str;
};
function int16(bytes) {
return (bytes[0] << 8) + (bytes[1] & 0xff);
};
@ -496,7 +505,7 @@ var Font = (function Font() {
String.fromCharCode(value & 0xff);
};
function createOpenTypeHeader(sfnt, file, offsets, numTables) {
function createOpenTypeHeader(sfnt, file, numTables) {
// sfnt version (4 bytes)
var header = sfnt;
@ -514,14 +523,13 @@ var Font = (function Font() {
// rangeShift (2 bytes)
header += string16(numTables * 16 - searchRange);
file.set(stringToArray(header), offsets.currentOffset);
offsets.currentOffset += header.length;
offsets.virtualOffset += header.length;
file.file += header;
file.virtualOffset += header.length;
};
function createTableEntry(file, offsets, tag, data) {
function createTableEntry(file, tag, data) {
// offset
var offset = offsets.virtualOffset;
var offset = file.virtualOffset;
// length
var length = data.length;
@ -530,21 +538,19 @@ var Font = (function Font() {
while (data.length & 3)
data.push(0x00);
while (offsets.virtualOffset & 3)
offsets.virtualOffset++;
while (file.virtualOffset & 3)
file.virtualOffset++;
// checksum
var checksum = 0, n = data.length;
for (var i = 0; i < n; i += 4)
checksum = (checksum + int32([data[i], data[i+1], data[i+2], data[i+3]])) | 0;
checksum = (checksum + int32([data[i], data[i + 1], data[i + 2],
data[i + 3]])) | 0;
var tableEntry = (tag + string32(checksum) +
string32(offset) + string32(length));
tableEntry = stringToArray(tableEntry);
file.set(tableEntry, offsets.currentOffset);
offsets.currentOffset += tableEntry.length;
offsets.virtualOffset += data.length;
file.file += tableEntry;
file.virtualOffset += data.length;
};
function getRanges(glyphs) {
@ -783,8 +789,10 @@ var Font = (function Font() {
encoding: null,
checkAndRepair: function font_checkAndRepair(name, font, properties) {
// offset glyphs to the Unicode Private Use Area
var kCmapGlyphOffset = 0xE000;
function readTableEntry(file) {
// tag
var tag = file.getBytes(4);
tag = String.fromCharCode(tag[0]) +
String.fromCharCode(tag[1]) +
@ -803,7 +811,8 @@ var Font = (function Font() {
file.pos = previousPosition;
if (tag == 'head')
data[8] = data[9] = data[10] = data[11] = 0; // clearing checksum adjustment
// clearing checksum adjustment
data[8] = data[9] = data[10] = data[11] = 0;
return {
tag: tag,
@ -838,7 +847,7 @@ var Font = (function Font() {
encodingID: int16(font.getBytes(2)),
offset: int32(font.getBytes(4))
});
};
}
var encoding = properties.encoding;
var charset = properties.charset;
@ -965,23 +974,19 @@ var Font = (function Font() {
tables.push(table);
}
// Create a new file to hold the new version of our truetype with a new
// header and new offsets
var ttf = new Uint8Array(kMaxFontFileSize);
// The offsets object holds at the same time a representation of where
// to write the table entry information about a table and another offset
// representing the offset where to put the actual data of a particular
// table
var numTables = header.numTables + requiredTables.length;
var offsets = {
currentOffset: 0,
// header and new offsets. Table entry information is appended to the
// end of file. The virtualOffset represents where to put the actual
// data of a particular table;
var ttf = {
file: '',
virtualOffset: numTables * (4 * 4)
};
// The new numbers of tables will be the last one plus the num
// of missing tables
createOpenTypeHeader('\x00\x01\x00\x00', ttf, offsets, numTables);
createOpenTypeHeader('\x00\x01\x00\x00', ttf, numTables);
if (requiredTables.indexOf('OS/2') != -1) {
tables.push({
@ -1001,42 +1006,50 @@ var Font = (function Font() {
var numOfHMetrics = int16(font.getBytes(2));
var numOfSidebearings = numGlyphs - numOfHMetrics;
var numMissing = numOfSidebearings - (hmtx.length - numOfHMetrics * 4);
var numMissing = numOfSidebearings -
((hmtx.length - numOfHMetrics * 4) >> 1);
if (numMissing > 0) {
font.pos = (font.start ? font.start : 0) + hmtx.offset;
var metrics = "";
var metrics = '';
for (var i = 0; i < hmtx.length; i++)
metrics += String.fromCharCode(font.getByte());
for (var i = 0; i < numMissing; i++)
metrics += "\x00\x00";
metrics += '\x00\x00';
hmtx.data = stringToArray(metrics);
}
// Sanitizer reduces the glyph advanceWidth to the maxAdvanceWidth
// Sometimes it's 0. That needs to be fixed
if (hhea.data[10] == 0 && hhea.data[11] == 0) {
hhea.data[10] = 0xFF;
hhea.data[11] = 0xFF;
}
// Replace the old CMAP table with a shiny new one
if (properties.type == 'CIDFontType2') {
// Type2 composite fonts map characters directly to glyphs so the cmap
// table must be replaced.
// canvas fillText will reencode some characters even if the font has a
// glyph at that position - e.g. newline is converted to a space and
// U+00AD (soft hyphen) is not drawn.
// So, offset all the glyphs by 0xFF to avoid these cases and use
// the encoding to map incoming characters to the new glyph positions
var glyphs = [];
var charset = properties.charset;
if (!charset.length) {
// Type2 composite fonts map characters directly to glyphs so the cmap
for (var i = 1; i < numGlyphs; i++) {
glyphs.push({
unicode: i
});
}
} else {
for (var i = 1; i < charset.length; i++) {
var index = charset.indexOf(i);
if (index == -1)
break;
var encoding = properties.encoding;
glyphs.push({
unicode: index
});
for (var i = 1; i < numGlyphs; i++) {
glyphs.push({ unicode: i + kCmapGlyphOffset });
}
if ('undefined' == typeof(encoding[0])) {
// the font is directly characters to glyphs with no encoding
// so create an identity encoding
for (i = 0; i < numGlyphs; i++)
encoding[i] = i + kCmapGlyphOffset;
} else {
for (var i in encoding)
encoding[i] = encoding[i] + kCmapGlyphOffset;
}
if (!cmap) {
@ -1080,26 +1093,21 @@ var Font = (function Font() {
var tableData = table.data;
for (var j = 0; j < tableData.length; j++)
data.push(tableData[j]);
createTableEntry(ttf, offsets, table.tag, data);
createTableEntry(ttf, table.tag, data);
}
// Add the table datas
for (var i = 0; i < tables.length; i++) {
var table = tables[i];
var tableData = table.data;
ttf.set(tableData, offsets.currentOffset);
offsets.currentOffset += tableData.length;
ttf.file += arrayToString(tableData);
// 4-byte aligned data
while (offsets.currentOffset & 3)
offsets.currentOffset++;
while (ttf.file.length & 3)
ttf.file += String.fromCharCode(0);
}
var fontData = [];
for (var i = 0; i < offsets.currentOffset; i++)
fontData.push(ttf[i]);
return fontData;
return stringToArray(ttf.file);
},
convert: function font_convert(fontName, font, properties) {
@ -1116,13 +1124,13 @@ var Font = (function Font() {
// representing the offset where to draw the actual data of a particular
// table
var kRequiredTablesCount = 9;
var offsets = {
currentOffset: 0,
var otf = {
file: '',
virtualOffset: 9 * (4 * 4)
};
var otf = new Uint8Array(kMaxFontFileSize);
createOpenTypeHeader('\x4F\x54\x54\x4F', otf, offsets, 9);
createOpenTypeHeader('\x4F\x54\x54\x4F', otf, 9);
var charstrings = font.charstrings;
properties.fixedPitch = isFixedPitch(charstrings);
@ -1206,18 +1214,14 @@ var Font = (function Font() {
};
for (var field in fields)
createTableEntry(otf, offsets, field, fields[field]);
createTableEntry(otf, field, fields[field]);
for (var field in fields) {
var table = fields[field];
otf.set(table, offsets.currentOffset);
offsets.currentOffset += table.length;
otf.file += arrayToString(table);
}
var fontData = [];
for (var i = 0; i < offsets.currentOffset; i++)
fontData.push(otf[i]);
return fontData;
return stringToArray(otf.file);
},
bindWorker: function font_bindWorker(data) {
@ -1259,32 +1263,26 @@ var Font = (function Font() {
if (!charsCache)
charsCache = this.charsCache = Object.create(null);
if (this.compositeFont) {
// composite fonts have multi-byte strings
// convert the string from single-byte to multi-byte
// XXX assuming CIDFonts are two-byte - later need to extract the correct byte encoding
// according to the PDF spec
str = '';
var multiByteStr = "";
var length = chars.length;
for (var i = 0; i < length; i++) {
var byte1 = chars.charCodeAt(i++) & 0xFF;
var byte2;
if (i == length)
byte2 = 0;
else
byte2 = chars.charCodeAt(i) & 0xFF;
multiByteStr += String.fromCharCode((byte1 << 8) | byte2);
}
str = multiByteStr;
}
else {
// translate the string using the font's encoding
var encoding = this.encoding;
if (!encoding)
return chars;
str = '';
if (this.compositeFont) {
// composite fonts have multi-byte strings convert the string from
// single-byte to multi-byte
// XXX assuming CIDFonts are two-byte - later need to extract the
// correct byte encoding according to the PDF spec
var length = chars.length - 1; // looping over two bytes at a time so
// loop should never end on the last byte
for (var i = 0; i < length; i++) {
var charcode = int16([chars.charCodeAt(i++), chars.charCodeAt(i)]);
var unicode = encoding[charcode];
str += String.fromCharCode(unicode);
}
}
else {
for (var i = 0; i < chars.length; ++i) {
var charcode = chars.charCodeAt(i);
var unicode = encoding[charcode];
@ -1455,7 +1453,7 @@ var Type1Parser = function() {
var value = '';
var count = array.length;
for (var i = 0; i < count; i++) {
value = parseInt(array[i]);
value = array[i];
if (value < 32) {
var command = null;
@ -1466,7 +1464,8 @@ var Type1Parser = function() {
if (escape == 16) {
var index = charstring.pop();
var argc = charstring.pop();
var data = charstring.pop();
for (var j = 0; j < argc; j++)
charstring.push('drop');
// 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
@ -1518,11 +1517,11 @@ var Type1Parser = function() {
value = command;
} else if (value <= 246) {
value = parseInt(value) - 139;
value = value - 139;
} else if (value <= 250) {
value = ((value - 247) * 256) + parseInt(array[++i]) + 108;
value = ((value - 247) * 256) + array[++i] + 108;
} else if (value <= 254) {
value = -((value - 251) * 256) - parseInt(array[++i]) - 108;
value = -((value - 251) * 256) - array[++i] - 108;
} else {
value = (array[++i] & 0xff) << 24 | (array[++i] & 0xff) << 16 |
(array[++i] & 0xff) << 8 | (array[++i] & 0xff) << 0;
@ -1551,8 +1550,8 @@ var Type1Parser = function() {
};
function readNumber(str, index) {
while (str[index++] == ' ')
;
while (str[index++] == ' ');
var start = index;
var count = 0;
@ -1584,6 +1583,17 @@ var Type1Parser = function() {
var c = '';
var count = eexecStr.length;
for (var i = 0; i < count; i++) {
var getToken = function() {
while (i < count && (eexecStr[i] == ' ' || eexecStr[i] == '\n'))
++i;
var t = '';
while (i < count && !(eexecStr[i] == ' ' || eexecStr[i] == '\n'))
t += eexecStr[i++];
return t;
}
var c = eexecStr[i];
if ((glyphsSection || subrsSection) && c == 'R') {
@ -1613,7 +1623,25 @@ var Type1Parser = function() {
glyphsSection = true;
break;
case '/Subrs':
subrsSection = true;
++i;
var num = parseInt(getToken());
getToken(); // read in 'array'
for (var j = 0; j < num; ++j) {
var t = getToken(); // read in 'dup'
if (t == 'ND')
break;
var index = parseInt(getToken());
if (index > j)
j = index;
var length = parseInt(getToken());
getToken(); // read in 'RD'
var data = eexec.slice(i + 1, i + 1 + length);
var encoded = decrypt(data, kCharStringsEncryptionKey, 4);
var str = decodeCharString(encoded);
i = i + 1 + length;
getToken(); //read in 'NP'
program.subrs[index] = str.charstring;
}
break;
case '/BlueValues':
case '/OtherBlues':
@ -1621,18 +1649,21 @@ var Type1Parser = function() {
case '/FamilyOtherBlues':
case '/StemSnapH':
case '/StemSnapV':
program.properties.private[token.substring(1)] = readNumberArray(eexecStr, i + 2);
program.properties.private[token.substring(1)] =
readNumberArray(eexecStr, i + 2);
break;
case '/StdHW':
case '/StdVW':
program.properties.private[token.substring(1)] = readNumberArray(eexecStr, i + 2)[0];
program.properties.private[token.substring(1)] =
readNumberArray(eexecStr, i + 2)[0];
break;
case '/BlueShift':
case '/BlueFuzz':
case '/BlueScale':
case '/LanguageGroup':
case '/ExpansionFactor':
program.properties.private[token.substring(1)] = readNumber(eexecStr, i + 1);
program.properties.private[token.substring(1)] =
readNumber(eexecStr, i + 1);
break;
}
} else if (c == '/') {
@ -1803,8 +1834,10 @@ CFF.prototype = {
// Add another offset after this one because we need a new offset
var relativeOffset = 1;
for (var i = 0; i < count + 1; i++) {
data += String.fromCharCode((relativeOffset >>> 24) & 0xFF, (relativeOffset >> 16) & 0xFF,
(relativeOffset >> 8) & 0xFF, relativeOffset & 0xFF);
data += String.fromCharCode((relativeOffset >>> 24) & 0xFF,
(relativeOffset >> 16) & 0xFF,
(relativeOffset >> 8) & 0xFF,
relativeOffset & 0xFF);
if (objects[i])
relativeOffset += objects[i].length;
@ -1812,7 +1845,8 @@ CFF.prototype = {
for (var i = 0; i < count; i++) {
for (var j = 0; j < objects[i].length; j++)
data += isByte ? String.fromCharCode(objects[i][j] & 0xFF) : objects[i][j];
data += isByte ? String.fromCharCode(objects[i][j] & 0xFF) :
objects[i][j];
}
return data;
},
@ -1889,8 +1923,13 @@ CFF.prototype = {
for (var i = 0; i < bias; i++)
type2Subrs.push([0x0B]);
for (var i = 0; i < count; i++)
type2Subrs.push(this.flattenCharstring(type1Subrs[i], this.commandsMap));
for (var i = 0; i < count; i++) {
var subr = type1Subrs[i];
if (!subr)
subr = [0x0B];
type2Subrs.push(this.flattenCharstring(subr, this.commandsMap));
}
return type2Subrs;
},
@ -1912,6 +1951,7 @@ CFF.prototype = {
'sub': [12, 11],
'div': [12, 12],
'pop': [1, 12, 18],
'drop' : [12, 18],
'endchar': 14,
'rmoveto': 21,
'hmoveto': 22,
@ -2082,7 +2122,7 @@ var Type2CFF = (function() {
this.properties = properties;
// Other classes expect this.data to be a Javascript array
var data = []
var data = [];
for (var i = 0, ii = bytes.length; i < ii; ++i)
data.push(bytes[i]);
this.data = data;
@ -2276,7 +2316,7 @@ var Type2CFF = (function() {
},
getStrings: function cff_getstrings(stringIndex) {
function bytesToString(bytesArr) {
var s = "";
var s = '';
for (var i = 0, ii = bytesArr.length; i < ii; ++i)
s += String.fromCharCode(bytesArr[i]);
return s;
@ -2299,7 +2339,7 @@ var Type2CFF = (function() {
++offset;
if (offset != 0) {
warning("cff data is shifted");
warning('cff data is shifted');
bytes = bytes.subarray(offset);
this.bytes = bytes;
}
@ -2307,7 +2347,7 @@ var Type2CFF = (function() {
return {
endPos: bytes[2],
offsetSize: bytes[3]
}
};
},
parseDict: function cff_parseDict(dict) {
var pos = 0;
@ -2338,7 +2378,7 @@ var Type2CFF = (function() {
};
function parseFloatOperand() {
var str = "";
var str = '';
var eof = 15;
var lookup = ['0', '1', '2', '3', '4', '5', '6', '7', '8',
'9', '.', 'E', 'E-', null, '-'];
@ -2414,8 +2454,8 @@ var Type2CFF = (function() {
},
length: count,
endPos: end
};
}
},
};
return constructor;

893
pdf.js

File diff suppressed because it is too large Load Diff

View File

@ -5,14 +5,16 @@
* A Test Driver for PDF.js
*/
'use strict';
var appPath, browser, canvas, currentTaskIdx, manifest, stdout;
function queryParams() {
var qs = window.location.search.substring(1);
var kvs = qs.split("&");
var kvs = qs.split('&');
var params = { };
for (var i = 0; i < kvs.length; ++i) {
var kv = kvs[i].split("=");
var kv = kvs[i].split('=');
params[unescape(kv[0])] = unescape(kv[1]);
}
return params;
@ -21,23 +23,24 @@ function queryParams() {
function load() {
var params = queryParams();
browser = params.browser;
manifestFile = params.manifestFile;
var manifestFile = params.manifestFile;
appPath = params.path;
canvas = document.createElement("canvas");
canvas = document.createElement('canvas');
canvas.mozOpaque = true;
stdout = document.getElementById("stdout");
stdout = document.getElementById('stdout');
log("load...\n");
log('load...\n');
log("Harness thinks this browser is '"+ browser + "' with path " + appPath + "\n");
log("Fetching manifest "+ manifestFile +"...");
log('Harness thinks this browser is "' + browser + '" with path "' +
appPath + '"\n');
log('Fetching manifest "' + manifestFile + '"... ');
var r = new XMLHttpRequest();
r.open("GET", manifestFile, false);
r.open('GET', manifestFile, false);
r.onreadystatechange = function(e) {
if (r.readyState == 4) {
log("done\n");
log('done\n');
manifest = JSON.parse(r.responseText);
currentTaskIdx = 0, nextTask();
}
@ -53,11 +56,11 @@ function nextTask() {
var task = manifest[currentTaskIdx];
task.round = 0;
log("Loading file "+ task.file +"\n");
log('Loading file "' + task.file + '"\n');
var r = new XMLHttpRequest();
r.open("GET", task.file);
r.mozResponseType = r.responseType = "arraybuffer";
r.open('GET', task.file);
r.mozResponseType = r.responseType = 'arraybuffer';
r.onreadystatechange = function() {
var failure;
if (r.readyState == 4) {
@ -77,13 +80,22 @@ function nextTask() {
}
function isLastPage(task) {
return (task.pdfDoc && (task.pageNum > task.pdfDoc.numPages));
return (task.pageNum > task.pdfDoc.numPages);
}
function nextPage(task, loadError) {
var failure = loadError || '';
if (!task.pdfDoc) {
sendTaskResult(canvas.toDataURL('image/png'), task, failure);
log('done' + (failure ? ' (failed !: ' + failure + ')' : '') + '\n');
++currentTaskIdx, nextTask();
return;
}
if (isLastPage(task)) {
if (++task.round < task.rounds) {
log(" Round "+ (1 + task.round) +"\n");
log(' Round ' + (1 + task.round) + '\n');
task.pageNum = 1;
} else {
++currentTaskIdx, nextTask();
@ -91,20 +103,19 @@ function nextPage(task, loadError) {
}
}
var failure = loadError || '';
var ctx = null;
var page = null;
if (!failure) {
try {
log(" loading page "+ task.pageNum +"... ");
ctx = canvas.getContext("2d");
log(' loading page ' + task.pageNum + '/' + task.pdfDoc.numPages +
'... ');
var ctx = canvas.getContext('2d');
page = task.pdfDoc.getPage(task.pageNum);
var pdfToCssUnitsCoef = 96.0 / 72.0;
// using mediaBox for the canvas size
var pageWidth = (page.mediaBox[2] - page.mediaBox[0]);
var pageHeight = (page.mediaBox[3] - page.mediaBox[1]);
var pageWidth = page.width;
var pageHeight = page.height;
canvas.width = pageWidth * pdfToCssUnitsCoef;
canvas.height = pageHeight * pdfToCssUnitsCoef;
clear(ctx);
@ -112,9 +123,10 @@ function nextPage(task, loadError) {
page.startRendering(
ctx,
function(e) {
snapshotCurrentPage(page, task,
(!failure && e) ? ('render: '+ e) : failure);
});
snapshotCurrentPage(page, task, (!failure && e) ?
('render : ' + e) : failure);
}
);
} catch (e) {
failure = 'page setup : ' + e.toString();
}
@ -128,14 +140,15 @@ function nextPage(task, loadError) {
}
function snapshotCurrentPage(page, task, failure) {
log("done, snapshotting... ");
log('done, snapshotting... ');
sendTaskResult(canvas.toDataURL("image/png"), task, failure);
log("done"+ (failure ? " (failed!: "+ failure +")" : "") +"\n");
sendTaskResult(canvas.toDataURL('image/png'), task, failure);
log('done' + (failure ? ' (failed !: ' + failure + ')' : '') + '\n');
// Set up the next request
backoff = (inFlightRequests > 0) ? inFlightRequests * 10 : 0;
setTimeout(function() {
var backoff = (inFlightRequests > 0) ? inFlightRequests * 10 : 0;
setTimeout(
function() {
++task.pageNum, nextPage(task);
},
backoff
@ -144,13 +157,14 @@ function snapshotCurrentPage(page, task, failure) {
function sendQuitRequest() {
var r = new XMLHttpRequest();
r.open("POST", "/tellMeToQuit?path=" + escape(appPath), false);
r.send("");
r.open('POST', '/tellMeToQuit?path = ' + escape(appPath), false);
r.send('');
}
function quitApp() {
log("Done!");
document.body.innerHTML = "Tests are finished. <h1>CLOSE ME!</h1>";
log('Done !');
document.body.innerHTML = 'Tests are finished. <h1>CLOSE ME!</h1>' +
document.body.innerHTML;
if (window.SpecialPowers) {
SpecialPowers.quitApplication();
} else {
@ -161,7 +175,7 @@ function quitApp() {
function done() {
if (inFlightRequests > 0) {
document.getElementById("inFlightCount").innerHTML = inFlightRequests;
document.getElementById('inFlightCount').innerHTML = inFlightRequests;
setTimeout(done, 100);
} else {
setTimeout(quitApp, 100);
@ -172,7 +186,7 @@ var inFlightRequests = 0;
function sendTaskResult(snapshot, task, failure) {
var result = { browser: browser,
id: task.id,
numPages: task.pdfDoc.numPages,
numPages: task.pdfDoc ? task.pdfDoc.numPages : 0,
failure: failure,
file: task.file,
round: task.round,
@ -181,20 +195,20 @@ function sendTaskResult(snapshot, task, failure) {
var r = new XMLHttpRequest();
// (The POST URI is ignored atm.)
r.open("POST", "/submit_task_results", true);
r.setRequestHeader("Content-Type", "application/json");
r.open('POST', '/submit_task_results', true);
r.setRequestHeader('Content-Type', 'application/json');
r.onreadystatechange = function(e) {
if (r.readyState == 4) {
inFlightRequests--;
}
}
document.getElementById("inFlightCount").innerHTML = inFlightRequests++;
document.getElementById('inFlightCount').innerHTML = inFlightRequests++;
r.send(JSON.stringify(result));
}
function clear(ctx) {
ctx.save();
ctx.fillStyle = "rgb(255, 255, 255)";
ctx.fillStyle = 'rgb(255, 255, 255)';
ctx.fillRect(0, 0, canvas.width, canvas.height);
ctx.restore();
}

File diff suppressed because one or more lines are too long

View File

@ -0,0 +1 @@
http://www.ecma-international.org/publications/files/ECMA-ST/Ecma-262.pdf

1
test/pdfs/hmm.pdf.link Normal file
View File

@ -0,0 +1 @@
http://speech.tifr.res.in/tutorials/hmmTutExamplesAlgo.pdf

View File

@ -1 +1 @@
http://www.intel.com/Assets/PDF/manual/253665.pdf
http://www.intel.com/content/dam/doc/manual/64-ia-32-architectures-software-developer-vol-1-manual.pdf

1
test/pdfs/jai.pdf.link Normal file
View File

@ -0,0 +1 @@
http://download.oracle.com/docs/cd/E19957-01/806-5413-10/806-5413-10.pdf

BIN
test/pdfs/rotation.pdf Normal file

Binary file not shown.

View File

@ -0,0 +1,10 @@
[
{
"name":"firefox",
"path":"C:/Program Files (x86)/Firefox/firefox.exe"
},
{
"name":"aurora",
"path":"C:/Program Files (x86)/Aurora/firefox.exe"
}
]

View File

@ -1,38 +1,40 @@
user_pref("browser.console.showInPanel", true);
user_pref("browser.dom.window.dump.enabled", true);
user_pref("browser.firstrun.show.localepicker", false);
user_pref("browser.firstrun.show.uidiscovery", false);
user_pref("dom.allow_scripts_to_close_windows", true);
user_pref("dom.disable_open_during_load", false);
user_pref("dom.max_script_run_time", 0); // no slow script dialogs
user_pref("dom.max_chrome_script_run_time", 0);
user_pref("dom.popup_maximum", -1);
user_pref("dom.send_after_paint_to_content", true);
user_pref("dom.successive_dialog_time_limit", 0);
user_pref("security.warn_submit_insecure", false);
user_pref("browser.shell.checkDefaultBrowser", false);
user_pref("shell.checkDefaultClient", false);
user_pref("browser.warnOnQuit", false);
user_pref("accessibility.typeaheadfind.autostart", false);
user_pref("javascript.options.showInConsole", true);
user_pref("devtools.errorconsole.enabled", true);
user_pref("layout.debug.enable_data_xbl", true);
user_pref("browser.EULA.override", true);
user_pref("javascript.options.tracejit.content", true);
user_pref("javascript.options.methodjit.content", true);
user_pref("javascript.options.jitprofiling.content", true);
user_pref("javascript.options.methodjit_always", false);
user_pref("gfx.color_management.force_srgb", true);
user_pref("network.manage-offline-status", false);
user_pref("test.mousescroll", true);
user_pref("network.http.prompt-temp-redirect", false);
user_pref("media.cache_size", 100);
user_pref("security.warn_viewing_mixed", false);
user_pref("app.update.enabled", false);
user_pref("browser.panorama.experienced_first_run", true); // Assume experienced
user_pref("dom.w3c_touch_events.enabled", true);
user_pref("extensions.checkCompatibility", false);
user_pref("extensions.installDistroAddons", false); // prevent testpilot etc
user_pref("browser.safebrowsing.enable", false); // prevent traffic to google servers
user_pref("toolkit.telemetry.prompted", true); // prevent telemetry banner
user_pref("toolkit.telemetry.enabled", false);
'use strict';
user_pref('browser.console.showInPanel', true);
user_pref('browser.dom.window.dump.enabled', true);
user_pref('browser.firstrun.show.localepicker', false);
user_pref('browser.firstrun.show.uidiscovery', false);
user_pref('dom.allow_scripts_to_close_windows', true);
user_pref('dom.disable_open_during_load', false);
user_pref('dom.max_script_run_time', 0); // no slow script dialogs
user_pref('dom.max_chrome_script_run_time', 0);
user_pref('dom.popup_maximum', -1);
user_pref('dom.send_after_paint_to_content', true);
user_pref('dom.successive_dialog_time_limit', 0);
user_pref('security.warn_submit_insecure', false);
user_pref('browser.shell.checkDefaultBrowser', false);
user_pref('shell.checkDefaultClient', false);
user_pref('browser.warnOnQuit', false);
user_pref('accessibility.typeaheadfind.autostart', false);
user_pref('javascript.options.showInConsole', true);
user_pref('devtools.errorconsole.enabled', true);
user_pref('layout.debug.enable_data_xbl', true);
user_pref('browser.EULA.override', true);
user_pref('javascript.options.tracejit.content', true);
user_pref('javascript.options.methodjit.content', true);
user_pref('javascript.options.jitprofiling.content', true);
user_pref('javascript.options.methodjit_always', false);
user_pref('gfx.color_management.force_srgb', true);
user_pref('network.manage-offline-status', false);
user_pref('test.mousescroll', true);
user_pref('network.http.prompt-temp-redirect', false);
user_pref('media.cache_size', 100);
user_pref('security.warn_viewing_mixed', false);
user_pref('app.update.enabled', false);
user_pref('browser.panorama.experienced_first_run', true); // Assume experienced
user_pref('dom.w3c_touch_events.enabled', true);
user_pref('extensions.checkCompatibility', false);
user_pref('extensions.installDistroAddons', false); // prevent testpilot etc
user_pref('browser.safebrowsing.enable', false); // prevent traffic to google servers
user_pref('toolkit.telemetry.prompted', true); // prevent telemetry banner
user_pref('toolkit.telemetry.enabled', false);

View File

@ -469,7 +469,7 @@ def maybeUpdateRefImages(options, browser):
else:
print ' Yes! The references in tmp/ can be synced with ref/.'
if options.reftest:
startReftest(browser)
startReftest(browser, options)
if not prompt('Would you like to update the master copy in ref/?'):
print ' OK, not updating.'
else:

View File

@ -64,10 +64,38 @@
"rounds": 1,
"type": "load"
},
{ "id": "complexttffont-pdf",
"file": "pdfs/complex_ttf_font.pdf",
"rounds": 1,
"type": "load"
},
{ "id": "i9-pdf",
"file": "pdfs/i9.pdf",
"link": true,
"rounds": 1,
"type": "eq"
},
{ "id": "hmm-pdf",
"file": "pdfs/hmm.pdf",
"link": true,
"rounds": 1,
"type": "load"
},
{ "id": "rotation",
"file": "pdfs/rotation.pdf",
"rounds": 1,
"type": "load"
},
{ "id": "ecma262-pdf",
"file": "pdfs/ecma262.pdf",
"link": true,
"rounds": 1,
"type": "load"
},
{ "id": "jai-pdf",
"file": "pdfs/jai.pdf",
"link": true,
"rounds": 1,
"type": "load"
}
]

View File

@ -40,6 +40,7 @@
// so we can use the TypedArray as well
window.Uint32Array = TypedArray;
window.Int32Array = TypedArray;
window.Uint16Array = TypedArray;
})();
// Object.create() ?

View File

@ -50,7 +50,7 @@
</div>
<h2>Try it out!</h2>
<p>Live <a href="web/multi_page_viewer.html">demo</a> lives here.</p>
<p>Live <a href="web/viewer.html">demo</a> lives here.</p>
<h2>Authors</h2>
<p>Vivien Nicolas (21@vingtetun.org)

View File

@ -38,21 +38,20 @@ var PDFView = {
set page(val) {
var pages = this.pages;
if (val <= 0 || val == this.page || val > pages.length) {
// TODO If the hash if set to a dumb value, like #123456, the input field
// of the UI will be set to it even if no page is changed because its out
// of bound.
val = this.page || 1;
} else {
// Draw the page before jumping to it in order to avoid seeing the
// possible gap between pages if the page has never been draw before.
pages[val - 1].draw();
document.location.hash = val;
var input = document.getElementById('pageNumber');
if (val <= 0 || val > pages.length) {
input.value = this.page;
return;
}
var event = document.createEvent("UIEvents");
event.initUIEvent("pagechange", false, false, window, val);
window.dispatchEvent(event);
document.location.hash = val;
document.getElementById('previous').disabled = (val == 1);
document.getElementById('next').disabled = (val == pages.length);
if (input.value == val)
return;
input.value = val;
pages[val - 1].draw();
},
get page() {
@ -60,7 +59,7 @@ var PDFView = {
},
open: function(url, scale) {
if (url.indexOf("http") == 0)
if (url.indexOf('http') == 0)
return;
document.title = url;
@ -82,6 +81,19 @@ var PDFView = {
xhr.send(null);
},
navigateTo: function(dest) {
if (typeof dest === 'string')
dest = this.destinations[dest];
// dest array looks like that: <page-ref> </XYZ|FitXXX> <args..>
var destRef = dest[0];
var pageNumber = this.pagesRefMap[destRef.num + ' ' + destRef.gen + ' R'];
if (pageNumber) {
this.page = pageNumber;
// TODO scroll to specific region on the page, the precise scaling
// required.
}
},
load: function(data, scale) {
var sidebar = document.getElementById('sidebarView');
sidebar.parentNode.scrollTop = 0;
@ -99,18 +111,21 @@ var PDFView = {
document.getElementById('numPages').innerHTML = pagesCount;
var pages = this.pages = [];
var pagesRefMap = {};
var thumbnails = this.thumbnails = [];
for (var i = 1; i <= pagesCount; i++) {
var page = pdf.getPage(i);
var mediaBox = page.mediaBox;
var width = (mediaBox[2] - mediaBox[0]);
var height = (mediaBox[3] - mediaBox[1]);
pages.push(new PageView(container, page, i, width, height, page.stats));
pages.push(new PageView(container, page, i, page.width, page.height,
page.stats, this.navigateTo.bind(this)));
thumbnails.push(new ThumbnailView(sidebar, pages[i - 1]));
};
var pageRef = page.ref;
pagesRefMap[pageRef.num + ' ' + pageRef.gen + ' R'] = i;
}
this.scale = (scale || kDefaultScale);
this.page = parseInt(document.location.hash.substring(1)) || 1;
this.pagesRefMap = pagesRefMap;
this.destinations = pdf.catalog.destinations;
},
getVisiblePages: function() {
@ -137,10 +152,11 @@ var PDFView = {
}
return visiblePages;
},
}
};
var PageView = function(container, content, id, width, height, stats) {
var PageView = function(container, content, id, width, height,
stats, navigateTo) {
this.width = width;
this.height = height;
this.id = id;
@ -165,6 +181,43 @@ var PageView = function(container, content, id, width, height, stats) {
div.removeChild(div.lastChild);
};
function setupLinks(canvas, content, scale) {
var links = content.getLinks();
var currentLink = null;
if (links.length > 0) {
canvas.addEventListener('mousemove', function(e) {
var x = e.pageX;
var y = e.pageY;
for (var p = canvas; p; p = p.offsetParent) {
x -= p.offsetLeft;
y -= p.offsetTop;
}
x /= scale;
y /= scale;
var i, n = links.length;
for (i = 0; i < n; i++) {
var link = links[i];
if (link.x <= x && link.y <= y &&
x < link.x + link.width && y < link.y + link.height) {
currentLink = link;
canvas.style.cursor = 'pointer';
return;
}
}
currentLink = null;
canvas.style.cursor = 'default';
}, false);
canvas.addEventListener('mousedown', function(e) {
if (!currentLink)
return;
if (currentLink.url)
window.location.href = currentLink.url;
if (currentLink.dest)
navigateTo(currentLink.dest);
}, false);
}
}
this.draw = function() {
if (div.hasChildNodes()) {
this.updateStats();
@ -188,6 +241,8 @@ var PageView = function(container, content, id, width, height, stats) {
stats.begin = Date.now();
this.content.startRendering(ctx, this.updateStats);
setupLinks(canvas, this.content, this.scale);
return true;
};
@ -271,11 +326,11 @@ window.addEventListener('scroll', function onscroll(evt) {
PDFView.page = firstPage.id;
}, true);
window.addEventListener("hashchange", function(evt) {
window.addEventListener('hashchange', function(evt) {
PDFView.page = PDFView.page;
});
window.addEventListener("change", function(evt) {
window.addEventListener('change', function(evt) {
var files = evt.target.files;
if (!files || files.length == 0)
return;
@ -301,7 +356,7 @@ window.addEventListener("change", function(evt) {
document.location.hash = 1;
}, true);
window.addEventListener("transitionend", function(evt) {
window.addEventListener('transitionend', function(evt) {
var pageIndex = 0;
var pagesCount = PDFView.pages.length;