Merge with master
This commit is contained in:
commit
d923953ee3
2
LICENSE
2
LICENSE
@ -5,6 +5,8 @@
|
|||||||
Chris G Jones <cjones@mozilla.com>
|
Chris G Jones <cjones@mozilla.com>
|
||||||
Shaon Barman <shaon.barman@gmail.com>
|
Shaon Barman <shaon.barman@gmail.com>
|
||||||
Vivien Nicolas <21@vingtetun.org>
|
Vivien Nicolas <21@vingtetun.org>
|
||||||
|
Justin D'Arcangelo <justindarc@gmail.com>
|
||||||
|
Yury Delendik
|
||||||
|
|
||||||
Permission is hereby granted, free of charge, to any person obtaining a
|
Permission is hereby granted, free of charge, to any person obtaining a
|
||||||
copy of this software and associated documentation files (the "Software"),
|
copy of this software and associated documentation files (the "Software"),
|
||||||
|
728
fonts.js
728
fonts.js
@ -8,11 +8,6 @@
|
|||||||
*/
|
*/
|
||||||
var kMaxFontFileSize = 40000;
|
var kMaxFontFileSize = 40000;
|
||||||
|
|
||||||
/**
|
|
||||||
* Maximum number of glyphs per font.
|
|
||||||
*/
|
|
||||||
var kMaxGlyphsCount = 65526;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Maximum time to wait for a font to be loaded by @font-face
|
* Maximum time to wait for a font to be loaded by @font-face
|
||||||
*/
|
*/
|
||||||
@ -45,7 +40,6 @@ var Fonts = {
|
|||||||
},
|
},
|
||||||
|
|
||||||
set active(aName) {
|
set active(aName) {
|
||||||
fontName = aName;
|
|
||||||
this._active = this[aName];
|
this._active = this[aName];
|
||||||
},
|
},
|
||||||
|
|
||||||
@ -70,6 +64,10 @@ var Fonts = {
|
|||||||
var uc = encoding[ch];
|
var uc = encoding[ch];
|
||||||
if (uc instanceof Name) // we didn't convert the glyph yet
|
if (uc instanceof Name) // we didn't convert the glyph yet
|
||||||
uc = encoding[ch] = GlyphsUnicode[uc.name];
|
uc = encoding[ch] = GlyphsUnicode[uc.name];
|
||||||
|
if (uc > 0xffff) { // handle surrogate pairs
|
||||||
|
ret += String.fromCharCode(uc & 0xffff);
|
||||||
|
uc >>= 16;
|
||||||
|
}
|
||||||
ret += String.fromCharCode(uc);
|
ret += String.fromCharCode(uc);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -88,16 +86,18 @@ var Fonts = {
|
|||||||
* var type1Font = new Font("MyFontName", binaryFile, propertiesObject);
|
* var type1Font = new Font("MyFontName", binaryFile, propertiesObject);
|
||||||
* type1Font.bind();
|
* type1Font.bind();
|
||||||
*/
|
*/
|
||||||
var Font = function(aName, aFile, aProperties) {
|
var Font = (function () {
|
||||||
|
var constructor = function(aName, aFile, aProperties) {
|
||||||
this.name = aName;
|
this.name = aName;
|
||||||
this.encoding = aProperties.encoding;
|
this.encoding = aProperties.encoding;
|
||||||
|
|
||||||
// If the font has already been decoded simply return
|
// If the font has already been decoded simply return it
|
||||||
if (Fonts[aName]) {
|
if (Fonts[aName]) {
|
||||||
this.font = Fonts[aName].data;
|
this.font = Fonts[aName].data;
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
fontCount++;
|
fontCount++;
|
||||||
|
fontName = aName;
|
||||||
|
|
||||||
if (aProperties.ignore || kDisableFonts) {
|
if (aProperties.ignore || kDisableFonts) {
|
||||||
Fonts[aName] = {
|
Fonts[aName] = {
|
||||||
@ -119,6 +119,18 @@ var Font = function(aName, aFile, aProperties) {
|
|||||||
break;
|
break;
|
||||||
|
|
||||||
case "TrueType":
|
case "TrueType":
|
||||||
|
// TrueType is disabled for the moment since the sanitizer prevent it
|
||||||
|
// from loading due to missing tables
|
||||||
|
return Fonts[aName] = {
|
||||||
|
data: null,
|
||||||
|
properties: {
|
||||||
|
encoding: {},
|
||||||
|
charset: null
|
||||||
|
},
|
||||||
|
loading: false,
|
||||||
|
cache: Object.create(null)
|
||||||
|
};
|
||||||
|
|
||||||
this.mimetype = "font/opentype";
|
this.mimetype = "font/opentype";
|
||||||
var ttf = new TrueType(aName, aFile, aProperties);
|
var ttf = new TrueType(aName, aFile, aProperties);
|
||||||
this.font = ttf.data;
|
this.font = ttf.data;
|
||||||
@ -138,10 +150,9 @@ var Font = function(aName, aFile, aProperties) {
|
|||||||
|
|
||||||
// Attach the font to the document
|
// Attach the font to the document
|
||||||
this.bind();
|
this.bind();
|
||||||
};
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
/**
|
|
||||||
* A bunch of the OpenType code is duplicate between this class and the
|
* A bunch of the OpenType code is duplicate between this class and the
|
||||||
* TrueType code, this is intentional and will merge in a future version
|
* TrueType code, this is intentional and will merge in a future version
|
||||||
* where all the code relative to OpenType will probably have its own
|
* where all the code relative to OpenType will probably have its own
|
||||||
@ -149,7 +160,7 @@ var Font = function(aName, aFile, aProperties) {
|
|||||||
* But at the moment it allows to develop around the TrueType rewriting
|
* But at the moment it allows to develop around the TrueType rewriting
|
||||||
* on the fly without messing up with the 'regular' Type1 to OTF conversion.
|
* on the fly without messing up with the 'regular' Type1 to OTF conversion.
|
||||||
*/
|
*/
|
||||||
Font.prototype = {
|
constructor.prototype = {
|
||||||
name: null,
|
name: null,
|
||||||
font: null,
|
font: null,
|
||||||
mimetype: null,
|
mimetype: null,
|
||||||
@ -158,14 +169,13 @@ Font.prototype = {
|
|||||||
bind: function font_bind() {
|
bind: function font_bind() {
|
||||||
var data = this.font;
|
var data = this.font;
|
||||||
|
|
||||||
// Compute the binary data to base 64
|
// Get the base64 encoding of the binary font data
|
||||||
var str = [];
|
var str = "";
|
||||||
var count = data.length;
|
var length = data.length;
|
||||||
for (var i = 0; i < count; i++)
|
for (var i = 0; i < length; ++i)
|
||||||
str.push(data.getChar ? data.getChar()
|
str += String.fromCharCode(data[i]);
|
||||||
: String.fromCharCode(data[i]));
|
|
||||||
|
|
||||||
var dataBase64 = window.btoa(str.join(""));
|
var dataBase64 = window.btoa(str);
|
||||||
var fontName = this.name;
|
var fontName = this.name;
|
||||||
|
|
||||||
/** Hack begin */
|
/** Hack begin */
|
||||||
@ -193,6 +203,7 @@ Font.prototype = {
|
|||||||
|
|
||||||
// Retrieve font charset
|
// Retrieve font charset
|
||||||
var charset = Fonts[fontName].properties.charset || [];
|
var charset = Fonts[fontName].properties.charset || [];
|
||||||
|
|
||||||
// if the charset is too small make it repeat a few times
|
// if the charset is too small make it repeat a few times
|
||||||
var count = 30;
|
var count = 30;
|
||||||
while (count-- && charset.length <= 30)
|
while (count-- && charset.length <= 30)
|
||||||
@ -205,7 +216,6 @@ Font.prototype = {
|
|||||||
// When debugging use the characters provided by the charsets to visually
|
// When debugging use the characters provided by the charsets to visually
|
||||||
// see what's happening
|
// see what's happening
|
||||||
if (debug) {
|
if (debug) {
|
||||||
var encoding = this.encoding;
|
|
||||||
for (var i = 0; i < charset.length; i++) {
|
for (var i = 0; i < charset.length; i++) {
|
||||||
var unicode = GlyphsUnicode[charset[i]];
|
var unicode = GlyphsUnicode[charset[i]];
|
||||||
if (!unicode)
|
if (!unicode)
|
||||||
@ -249,46 +259,55 @@ Font.prototype = {
|
|||||||
styleSheet.insertRule(rule, styleSheet.length);
|
styleSheet.insertRule(rule, styleSheet.length);
|
||||||
},
|
},
|
||||||
|
|
||||||
_createOpenTypeHeader: function font_createOpenTypeHeader(aFile, aOffsets, aNumTables) {
|
cover: function font_cover(aName, aFont, aProperties) {
|
||||||
|
var otf = Uint8Array(kMaxFontFileSize);
|
||||||
|
|
||||||
|
function s2a(s) {
|
||||||
|
var a = [];
|
||||||
|
for (var i = 0; i < s.length; ++i)
|
||||||
|
a[i] = s.charCodeAt(i);
|
||||||
|
return a;
|
||||||
|
}
|
||||||
|
|
||||||
|
function s16(value) {
|
||||||
|
return String.fromCharCode((value >> 8) & 0xff) + String.fromCharCode(value & 0xff);
|
||||||
|
}
|
||||||
|
|
||||||
|
function s32(value) {
|
||||||
|
return String.fromCharCode((value >> 24) & 0xff) + String.fromCharCode((value >> 16) & 0xff) +
|
||||||
|
String.fromCharCode((value >> 8) & 0xff) + String.fromCharCode(value & 0xff);
|
||||||
|
}
|
||||||
|
|
||||||
|
function createOpenTypeHeader(aFile, aOffsets, numTables) {
|
||||||
|
var header = "";
|
||||||
|
|
||||||
// sfnt version (4 bytes)
|
// sfnt version (4 bytes)
|
||||||
var version = [0x4F, 0x54, 0x54, 0X4F];
|
header += "\x4F\x54\x54\x4F";
|
||||||
|
|
||||||
// numTables (2 bytes)
|
// numTables (2 bytes)
|
||||||
var numTables = aNumTables;
|
header += s16(numTables);
|
||||||
|
|
||||||
// searchRange (2 bytes)
|
// searchRange (2 bytes)
|
||||||
var tablesMaxPower2 = FontsUtils.getMaxPower2(numTables);
|
var tablesMaxPower2 = FontsUtils.getMaxPower2(numTables);
|
||||||
var searchRange = tablesMaxPower2 * 16;
|
var searchRange = tablesMaxPower2 * 16;
|
||||||
|
header += s16(searchRange);
|
||||||
|
|
||||||
// entrySelector (2 bytes)
|
// entrySelector (2 bytes)
|
||||||
var entrySelector = Math.log(tablesMaxPower2) / Math.log(2);
|
header += s16(Math.log(tablesMaxPower2) / Math.log(2));
|
||||||
|
|
||||||
// rangeShift (2 bytes)
|
// rangeShift (2 bytes)
|
||||||
var rangeShift = numTables * 16 - searchRange;
|
header += s16(numTables * 16 - searchRange);
|
||||||
|
|
||||||
var header = [].concat(version,
|
aFile.set(s2a(header), aOffsets.currentOffset);
|
||||||
FontsUtils.integerToBytes(numTables, 2),
|
|
||||||
FontsUtils.integerToBytes(searchRange, 2),
|
|
||||||
FontsUtils.integerToBytes(entrySelector, 2),
|
|
||||||
FontsUtils.integerToBytes(rangeShift, 2));
|
|
||||||
aFile.set(header, aOffsets.currentOffset);
|
|
||||||
aOffsets.currentOffset += header.length;
|
aOffsets.currentOffset += header.length;
|
||||||
aOffsets.virtualOffset += header.length;
|
aOffsets.virtualOffset += header.length;
|
||||||
},
|
}
|
||||||
|
|
||||||
_createTableEntry: function font_createTableEntry(aFile, aOffsets, aTag, aData) {
|
|
||||||
// tag
|
|
||||||
var tag = [
|
|
||||||
aTag.charCodeAt(0),
|
|
||||||
aTag.charCodeAt(1),
|
|
||||||
aTag.charCodeAt(2),
|
|
||||||
aTag.charCodeAt(3)
|
|
||||||
];
|
|
||||||
|
|
||||||
|
function createTableEntry(aFile, aOffsets, aTag, aData) {
|
||||||
// offset
|
// offset
|
||||||
var offset = aOffsets.virtualOffset;
|
var offset = aOffsets.virtualOffset;
|
||||||
|
|
||||||
// Per spec tables must be 4-bytes align so add some 0x00 if needed
|
// Per spec tables must be 4-bytes align so add padding as needed
|
||||||
while (aData.length & 3)
|
while (aData.length & 3)
|
||||||
aData.push(0x00);
|
aData.push(0x00);
|
||||||
|
|
||||||
@ -296,155 +315,136 @@ Font.prototype = {
|
|||||||
var length = aData.length;
|
var length = aData.length;
|
||||||
|
|
||||||
// checksum
|
// checksum
|
||||||
var checksum = FontsUtils.bytesToInteger(tag) + offset + length;
|
var checksum = aTag.charCodeAt(0) +
|
||||||
|
aTag.charCodeAt(1) +
|
||||||
|
aTag.charCodeAt(2) +
|
||||||
|
aTag.charCodeAt(3) +
|
||||||
|
offset +
|
||||||
|
length;
|
||||||
|
|
||||||
var tableEntry = [].concat(tag,
|
var tableEntry = aTag + s32(checksum) + s32(offset) + s32(length);
|
||||||
FontsUtils.integerToBytes(checksum, 4),
|
tableEntry = s2a(tableEntry);
|
||||||
FontsUtils.integerToBytes(offset, 4),
|
|
||||||
FontsUtils.integerToBytes(length, 4));
|
|
||||||
aFile.set(tableEntry, aOffsets.currentOffset);
|
aFile.set(tableEntry, aOffsets.currentOffset);
|
||||||
aOffsets.currentOffset += tableEntry.length;
|
aOffsets.currentOffset += tableEntry.length;
|
||||||
aOffsets.virtualOffset += aData.length;
|
aOffsets.virtualOffset += aData.length;
|
||||||
},
|
|
||||||
|
|
||||||
_createCMAPTable: function font_createCMAPTable(aGlyphs) {
|
|
||||||
var characters = new Uint16Array(kMaxGlyphsCount);
|
|
||||||
for (var i = 0; i < aGlyphs.length; i++)
|
|
||||||
characters[aGlyphs[i].unicode] = i + 1;
|
|
||||||
|
|
||||||
// Separate the glyphs into continuous range of codes, aka segment.
|
|
||||||
var ranges = [];
|
|
||||||
var range = [];
|
|
||||||
var count = characters.length;
|
|
||||||
for (var i = 0; i < count; i++) {
|
|
||||||
if (characters[i]) {
|
|
||||||
range.push(i);
|
|
||||||
} else if (range.length) {
|
|
||||||
ranges.push(range.slice());
|
|
||||||
range = [];
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// The size in bytes of the header is equal to the size of the
|
function createNameTable(aName) {
|
||||||
// different fields * length of a short + (size of the 4 parallels arrays
|
var names =
|
||||||
// describing segments * length of a short).
|
"See original licence" + // Copyright
|
||||||
var headerSize = (12 * 2 + (ranges.length * 4 * 2));
|
aName + // Font family
|
||||||
|
"undefined" + // Font subfamily (font weight)
|
||||||
|
"uniqueID" + // Unique ID
|
||||||
|
aName + // Full font name
|
||||||
|
"0.1" + // Version
|
||||||
|
"undefined" + // Postscript name
|
||||||
|
"undefined" + // Trademark
|
||||||
|
"undefined" + // Manufacturer
|
||||||
|
"undefined"; // Designer
|
||||||
|
|
||||||
var segCount = ranges.length + 1;
|
var name =
|
||||||
var segCount2 = segCount * 2;
|
"\x00\x00" + // format
|
||||||
var searchRange = FontsUtils.getMaxPower2(segCount) * 2;
|
"\x00\x0A" + // Number of names Record
|
||||||
var searchEntry = Math.log(segCount) / Math.log(2);
|
"\x00\x7E"; // Storage
|
||||||
var rangeShift = 2 * segCount - searchRange;
|
|
||||||
var cmap = [].concat(
|
|
||||||
[
|
|
||||||
0x00, 0x00, // version
|
|
||||||
0x00, 0x01, // numTables
|
|
||||||
0x00, 0x03, // platformID
|
|
||||||
0x00, 0x01, // encodingID
|
|
||||||
0x00, 0x00, 0x00, 0x0C, // start of the table record
|
|
||||||
0x00, 0x04 // format
|
|
||||||
],
|
|
||||||
FontsUtils.integerToBytes(headerSize, 2), // length
|
|
||||||
[0x00, 0x00], // language
|
|
||||||
FontsUtils.integerToBytes(segCount2, 2),
|
|
||||||
FontsUtils.integerToBytes(searchRange, 2),
|
|
||||||
FontsUtils.integerToBytes(searchEntry, 2),
|
|
||||||
FontsUtils.integerToBytes(rangeShift, 2)
|
|
||||||
);
|
|
||||||
|
|
||||||
// Fill up the 4 parallel arrays describing the segments.
|
|
||||||
var startCount = [];
|
|
||||||
var endCount = [];
|
|
||||||
var idDeltas = [];
|
|
||||||
var idRangeOffsets = [];
|
|
||||||
var glyphsIdsArray = [];
|
|
||||||
var bias = 0;
|
|
||||||
for (var i = 0; i < segCount - 1; i++) {
|
|
||||||
var range = ranges[i];
|
|
||||||
var start = FontsUtils.integerToBytes(range[0], 2);
|
|
||||||
var end = FontsUtils.integerToBytes(range[range.length - 1], 2);
|
|
||||||
|
|
||||||
var delta = FontsUtils.integerToBytes(((range[0] - 1) - bias) % 65536, 2);
|
|
||||||
bias += range.length;
|
|
||||||
|
|
||||||
// deltas are signed shorts
|
|
||||||
delta[0] ^= 0xFF;
|
|
||||||
delta[1] ^= 0xFF;
|
|
||||||
delta[1] += 1;
|
|
||||||
|
|
||||||
startCount.push(start[0], start[1]);
|
|
||||||
endCount.push(end[0], end[1]);
|
|
||||||
idDeltas.push(delta[0], delta[1]);
|
|
||||||
idRangeOffsets.push(0x00, 0x00);
|
|
||||||
|
|
||||||
for (var j = 0; j < range.length; j++)
|
|
||||||
glyphsIdsArray.push(range[j]);
|
|
||||||
}
|
|
||||||
startCount.push(0xFF, 0xFF);
|
|
||||||
endCount.push(0xFF, 0xFF);
|
|
||||||
idDeltas.push(0x00, 0x01);
|
|
||||||
idRangeOffsets.push(0x00, 0x00);
|
|
||||||
|
|
||||||
return cmap.concat(endCount, [0x00, 0x00], startCount,
|
|
||||||
idDeltas, idRangeOffsets, glyphsIdsArray);
|
|
||||||
},
|
|
||||||
|
|
||||||
_createNameTable: function font_createNameTable(aName) {
|
|
||||||
var names = [
|
|
||||||
"See original licence", // Copyright
|
|
||||||
aName, // Font family
|
|
||||||
"undefined", // Font subfamily (font weight)
|
|
||||||
"uniqueID", // Unique ID
|
|
||||||
aName, // Full font name
|
|
||||||
"0.1", // Version
|
|
||||||
"undefined", // Postscript name
|
|
||||||
"undefined", // Trademark
|
|
||||||
"undefined", // Manufacturer
|
|
||||||
"undefined" // Designer
|
|
||||||
];
|
|
||||||
|
|
||||||
var name = [
|
|
||||||
0x00, 0x00, // format
|
|
||||||
0x00, 0x0A, // Number of names Record
|
|
||||||
0x00, 0x7E // Storage
|
|
||||||
];
|
|
||||||
|
|
||||||
// Build the name records field
|
// Build the name records field
|
||||||
var strOffset = 0;
|
var strOffset = 0;
|
||||||
for (var i = 0; i < names.length; i++) {
|
for (var i = 0; i < names.length; i++) {
|
||||||
var str = names[i];
|
var str = names[i];
|
||||||
|
|
||||||
var nameRecord = [
|
var nameRecord =
|
||||||
0x00, 0x01, // platform ID
|
"\x00\x01" + // platform ID
|
||||||
0x00, 0x00, // encoding ID
|
"\x00\x00" + // encoding ID
|
||||||
0x00, 0x00, // language ID
|
"\x00\x00" + // language ID
|
||||||
0x00, 0x00 // name ID
|
"\x00\x00" + // name ID
|
||||||
];
|
s16(str.length) +
|
||||||
|
s16(strOffset);
|
||||||
nameRecord = nameRecord.concat(
|
name += nameRecord;
|
||||||
FontsUtils.integerToBytes(str.length, 2),
|
|
||||||
FontsUtils.integerToBytes(strOffset, 2)
|
|
||||||
);
|
|
||||||
name = name.concat(nameRecord);
|
|
||||||
|
|
||||||
strOffset += str.length;
|
strOffset += str.length;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Add the name records data
|
name += names;
|
||||||
for (var i = 0; i < names.length; i++) {
|
|
||||||
var str = names[i];
|
|
||||||
var strBytes = [];
|
|
||||||
for (var j = 0; j < str.length; j++) {
|
|
||||||
strBytes.push(str.charCodeAt(j));
|
|
||||||
}
|
|
||||||
name = name.concat(strBytes);
|
|
||||||
}
|
|
||||||
|
|
||||||
return name;
|
return name;
|
||||||
},
|
}
|
||||||
|
|
||||||
cover: function font_cover(aName, aFont, aProperties) {
|
function getRanges(glyphs) {
|
||||||
var otf = new Uint8Array(kMaxFontFileSize);
|
// Array.sort() sorts by characters, not numerically, so convert to an
|
||||||
|
// array of characters.
|
||||||
|
var codes = [];
|
||||||
|
var length = glyphs.length;
|
||||||
|
for (var n = 0; n < length; ++n)
|
||||||
|
codes.push(String.fromCharCode(glyphs[n].unicode))
|
||||||
|
codes.sort();
|
||||||
|
|
||||||
|
// Split the sorted codes into ranges.
|
||||||
|
var ranges = [];
|
||||||
|
for (var n = 0; n < length; ) {
|
||||||
|
var start = codes[n++].charCodeAt(0);
|
||||||
|
var end = start;
|
||||||
|
while (n < length && end + 1 == codes[n].charCodeAt(0)) {
|
||||||
|
++end;
|
||||||
|
++n;
|
||||||
|
}
|
||||||
|
ranges.push([start, end]);
|
||||||
|
}
|
||||||
|
return ranges;
|
||||||
|
}
|
||||||
|
|
||||||
|
function createCMAPTable(aGlyphs) {
|
||||||
|
var ranges = getRanges(aGlyphs);
|
||||||
|
|
||||||
|
var headerSize = (12 * 2 + (ranges.length * 4 * 2));
|
||||||
|
var segCount = ranges.length + 1;
|
||||||
|
var segCount2 = segCount * 2;
|
||||||
|
var searchRange = FontsUtils.getMaxPower2(segCount) * 2;
|
||||||
|
var searchEntry = Math.log(segCount) / Math.log(2);
|
||||||
|
var rangeShift = 2 * segCount - searchRange;
|
||||||
|
|
||||||
|
var cmap = "\x00\x00" + // version
|
||||||
|
"\x00\x01" + // numTables
|
||||||
|
"\x00\x03" + // platformID
|
||||||
|
"\x00\x01" + // encodingID
|
||||||
|
"\x00\x00\x00\x0C" + // start of the table record
|
||||||
|
"\x00\x04" + // format
|
||||||
|
s16(headerSize) + // length
|
||||||
|
"\x00\x00" + // languages
|
||||||
|
s16(segCount2) +
|
||||||
|
s16(searchRange) +
|
||||||
|
s16(searchEntry) +
|
||||||
|
s16(rangeShift);
|
||||||
|
|
||||||
|
// Fill up the 4 parallel arrays describing the segments.
|
||||||
|
var startCount = "";
|
||||||
|
var endCount = "";
|
||||||
|
var idDeltas = "";
|
||||||
|
var idRangeOffsets = "";
|
||||||
|
var glyphsIds = "";
|
||||||
|
var bias = 0;
|
||||||
|
for (var i = 0; i < segCount - 1; i++) {
|
||||||
|
var range = ranges[i];
|
||||||
|
var start = range[0];
|
||||||
|
var end = range[1];
|
||||||
|
var delta = (((start - 1) - bias) ^ 0xffff) + 1;
|
||||||
|
bias += (end - start + 1);
|
||||||
|
|
||||||
|
startCount += s16(start);
|
||||||
|
endCount += s16(end);
|
||||||
|
idDeltas += s16(delta);
|
||||||
|
idRangeOffsets += s16(0);
|
||||||
|
|
||||||
|
for (var j = start; j <= end; j++)
|
||||||
|
glyphsIds += String.fromCharCode(j);
|
||||||
|
}
|
||||||
|
|
||||||
|
startCount += "\xFF\xFF";
|
||||||
|
endCount += "\xFF\xFF";
|
||||||
|
idDeltas += "\x00\x01";
|
||||||
|
idRangeOffsets += "\x00\x00";
|
||||||
|
|
||||||
|
return s2a(cmap + endCount + "\x00\x00" + startCount +
|
||||||
|
idDeltas + idRangeOffsets + glyphsIds);
|
||||||
|
}
|
||||||
|
|
||||||
// Required Tables
|
// Required Tables
|
||||||
var CFF = aFont.data, // PostScript Font Program
|
var CFF = aFont.data, // PostScript Font Program
|
||||||
@ -469,145 +469,140 @@ Font.prototype = {
|
|||||||
|
|
||||||
// For files with only one font the offset table is the first thing of the
|
// For files with only one font the offset table is the first thing of the
|
||||||
// file
|
// file
|
||||||
this._createOpenTypeHeader(otf, offsets, tables.length);
|
createOpenTypeHeader(otf, offsets, tables.length);
|
||||||
|
|
||||||
// XXX It is probable that in a future we want to get rid of this glue
|
// TODO: It is probable that in a future we want to get rid of this glue
|
||||||
// between the CFF and the OTF format in order to be able to embed TrueType
|
// between the CFF and the OTF format in order to be able to embed TrueType
|
||||||
// data.
|
// data.
|
||||||
this._createTableEntry(otf, offsets, "CFF ", CFF);
|
createTableEntry(otf, offsets, "CFF ", CFF);
|
||||||
|
|
||||||
/** OS/2 */
|
/** OS/2 */
|
||||||
OS2 = [
|
OS2 = s2a(
|
||||||
0x00, 0x03, // version
|
"\x00\x03" + // version
|
||||||
0x02, 0x24, // xAvgCharWidth
|
"\x02\x24" + // xAvgCharWidth
|
||||||
0x01, 0xF4, // usWeightClass
|
"\x01\xF4" + // usWeightClass
|
||||||
0x00, 0x05, // usWidthClass
|
"\x00\x05" + // usWidthClass
|
||||||
0x00, 0x00, // fstype
|
"\x00\x00" + // fstype
|
||||||
0x02, 0x8A, // ySubscriptXSize
|
"\x02\x8A" + // ySubscriptXSize
|
||||||
0x02, 0xBB, // ySubscriptYSize
|
"\x02\xBB" + // ySubscriptYSize
|
||||||
0x00, 0x00, // ySubscriptXOffset
|
"\x00\x00" + // ySubscriptXOffset
|
||||||
0x00, 0x8C, // ySubscriptYOffset
|
"\x00\x8C" + // ySubscriptYOffset
|
||||||
0x02, 0x8A, // ySuperScriptXSize
|
"\x02\x8A" + // ySuperScriptXSize
|
||||||
0x02, 0xBB, // ySuperScriptYSize
|
"\x02\xBB" + // ySuperScriptYSize
|
||||||
0x00, 0x00, // ySuperScriptXOffset
|
"\x00\x00" + // ySuperScriptXOffset
|
||||||
0x01, 0xDF, // ySuperScriptYOffset
|
"\x01\xDF" + // ySuperScriptYOffset
|
||||||
0x00, 0x31, // yStrikeOutSize
|
"\x00\x31" + // yStrikeOutSize
|
||||||
0x01, 0x02, // yStrikeOutPosition
|
"\x01\x02" + // yStrikeOutPosition
|
||||||
0x00, 0x00, // sFamilyClass
|
"\x00\x00" + // sFamilyClass
|
||||||
0x02, 0x00, 0x06, 0x03, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // Panose
|
"\x02\x00\x06\x03\x00\x00\x00\x00\x00\x00" + // Panose
|
||||||
0xFF, 0xFF, 0xFF, 0xFF, // ulUnicodeRange1 (Bits 0-31)
|
"\xFF\xFF\xFF\xFF" + // ulUnicodeRange1 (Bits 0-31)
|
||||||
0xFF, 0xFF, 0xFF, 0xFF, // ulUnicodeRange1 (Bits 32-63)
|
"\xFF\xFF\xFF\xFF" + // ulUnicodeRange1 (Bits 32-63)
|
||||||
0xFF, 0xFF, 0xFF, 0xFF, // ulUnicodeRange1 (Bits 64-95)
|
"\xFF\xFF\xFF\xFF" + // ulUnicodeRange1 (Bits 64-95)
|
||||||
0xFF, 0xFF, 0xFF, 0xFF, // ulUnicodeRange1 (Bits 96-127)
|
"\xFF\xFF\xFF\xFF" + // ulUnicodeRange1 (Bits 96-127)
|
||||||
0x2A, 0x32, 0x31, 0x2A, // achVendID
|
"\x2A\x32\x31\x2A" + // achVendID
|
||||||
0x00, 0x20, // fsSelection
|
"\x00\x20" + // fsSelection
|
||||||
0x00, 0x2D, // usFirstCharIndex
|
"\x00\x2D" + // usFirstCharIndex
|
||||||
0x00, 0x7A, // usLastCharIndex
|
"\x00\x7A" + // usLastCharIndex
|
||||||
0x00, 0x03, // sTypoAscender
|
"\x00\x03" + // sTypoAscender
|
||||||
0x00, 0x20, // sTypeDescender
|
"\x00\x20" + // sTypeDescender
|
||||||
0x00, 0x38, // sTypoLineGap
|
"\x00\x38" + // sTypoLineGap
|
||||||
0x00, 0x5A, // usWinAscent
|
"\x00\x5A" + // usWinAscent
|
||||||
0x02, 0xB4, // usWinDescent
|
"\x02\xB4" + // usWinDescent
|
||||||
0x00, 0xCE, 0x00, 0x00, // ulCodePageRange1 (Bits 0-31)
|
"\x00\xCE\x00\x00" + // ulCodePageRange1 (Bits 0-31)
|
||||||
0x00, 0x01, 0x00, 0x00, // ulCodePageRange2 (Bits 32-63)
|
"\x00\x01\x00\x00" + // ulCodePageRange2 (Bits 32-63)
|
||||||
0x00, 0x00, // sxHeight
|
"\x00\x00" + // sxHeight
|
||||||
0x00, 0x00, // sCapHeight
|
"\x00\x00" + // sCapHeight
|
||||||
0x00, 0x01, // usDefaultChar
|
"\x00\x01" + // usDefaultChar
|
||||||
0x00, 0xCD, // usBreakChar
|
"\x00\xCD" + // usBreakChar
|
||||||
0x00, 0x02 // usMaxContext
|
"\x00\x02" // usMaxContext
|
||||||
];
|
);
|
||||||
this._createTableEntry(otf, offsets, "OS/2", OS2);
|
createTableEntry(otf, offsets, "OS/2", OS2);
|
||||||
|
|
||||||
//XXX Getting charstrings here seems wrong since this is another CFF glue
|
//XXX Getting charstrings here seems wrong since this is another CFF glue
|
||||||
var charstrings = aFont.getOrderedCharStrings(aProperties.glyphs);
|
var charstrings = aFont.getOrderedCharStrings(aProperties.glyphs);
|
||||||
|
|
||||||
/** CMAP */
|
/** CMAP */
|
||||||
cmap = this._createCMAPTable(charstrings);
|
cmap = createCMAPTable(charstrings);
|
||||||
this._createTableEntry(otf, offsets, "cmap", cmap);
|
createTableEntry(otf, offsets, "cmap", cmap);
|
||||||
|
|
||||||
/** HEAD */
|
/** HEAD */
|
||||||
head = [
|
head = s2a(
|
||||||
0x00, 0x01, 0x00, 0x00, // Version number
|
"\x00\x01\x00\x00" + // Version number
|
||||||
0x00, 0x00, 0x50, 0x00, // fontRevision
|
"\x00\x00\x50\x00" + // fontRevision
|
||||||
0x00, 0x00, 0x00, 0x00, // checksumAdjustement
|
"\x00\x00\x00\x00" + // checksumAdjustement
|
||||||
0x5F, 0x0F, 0x3C, 0xF5, // magicNumber
|
"\x5F\x0F\x3C\xF5" + // magicNumber
|
||||||
0x00, 0x00, // Flags
|
"\x00\x00" + // Flags
|
||||||
0x03, 0xE8, // unitsPerEM (defaulting to 1000)
|
"\x03\xE8" + // unitsPerEM (defaulting to 1000)
|
||||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // creation date
|
"\x00\x00\x00\x00\x00\x00\x00\x00" + // creation date
|
||||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // modifification date
|
"\x00\x00\x00\x00\x00\x00\x00\x00" + // modifification date
|
||||||
0x00, 0x00, // xMin
|
"\x00\x00" + // xMin
|
||||||
0x00, 0x00, // yMin
|
"\x00\x00" + // yMin
|
||||||
0x00, 0x00, // xMax
|
"\x00\x00" + // xMax
|
||||||
0x00, 0x00, // yMax
|
"\x00\x00" + // yMax
|
||||||
0x00, 0x00, // macStyle
|
"\x00\x00" + // macStyle
|
||||||
0x00, 0x00, // lowestRecPPEM
|
"\x00\x00" + // lowestRecPPEM
|
||||||
0x00, 0x00, // fontDirectionHint
|
"\x00\x00" + // fontDirectionHint
|
||||||
0x00, 0x00, // indexToLocFormat
|
"\x00\x00" + // indexToLocFormat
|
||||||
0x00, 0x00 // glyphDataFormat
|
"\x00\x00" // glyphDataFormat
|
||||||
];
|
);
|
||||||
this._createTableEntry(otf, offsets, "head", head);
|
createTableEntry(otf, offsets, "head", head);
|
||||||
|
|
||||||
/** HHEA */
|
/** HHEA */
|
||||||
hhea = [].concat(
|
hhea = s2a(
|
||||||
[
|
"\x00\x01\x00\x00" + // Version number
|
||||||
0x00, 0x01, 0x00, 0x00, // Version number
|
"\x00\x00" + // Typographic Ascent
|
||||||
0x00, 0x00, // Typographic Ascent
|
"\x00\x00" + // Typographic Descent
|
||||||
0x00, 0x00, // Typographic Descent
|
"\x00\x00" + // Line Gap
|
||||||
0x00, 0x00, // Line Gap
|
"\xFF\xFF" + // advanceWidthMax
|
||||||
0xFF, 0xFF, // advanceWidthMax
|
"\x00\x00" + // minLeftSidebearing
|
||||||
0x00, 0x00, // minLeftSidebearing
|
"\x00\x00" + // minRightSidebearing
|
||||||
0x00, 0x00, // minRightSidebearing
|
"\x00\x00" + // xMaxExtent
|
||||||
0x00, 0x00, // xMaxExtent
|
"\x00\x00" + // caretSlopeRise
|
||||||
0x00, 0x00, // caretSlopeRise
|
"\x00\x00" + // caretSlopeRun
|
||||||
0x00, 0x00, // caretSlopeRun
|
"\x00\x00" + // caretOffset
|
||||||
0x00, 0x00, // caretOffset
|
"\x00\x00" + // -reserved-
|
||||||
0x00, 0x00, // -reserved-
|
"\x00\x00" + // -reserved-
|
||||||
0x00, 0x00, // -reserved-
|
"\x00\x00" + // -reserved-
|
||||||
0x00, 0x00, // -reserved-
|
"\x00\x00" + // -reserved-
|
||||||
0x00, 0x00, // -reserved-
|
"\x00\x00" + // metricDataFormat
|
||||||
0x00, 0x00 // metricDataFormat
|
s16(charstrings.length)
|
||||||
],
|
|
||||||
FontsUtils.integerToBytes(charstrings.length, 2) // numberOfHMetrics
|
|
||||||
);
|
);
|
||||||
this._createTableEntry(otf, offsets, "hhea", hhea);
|
createTableEntry(otf, offsets, "hhea", hhea);
|
||||||
|
|
||||||
/** HMTX */
|
/** HMTX */
|
||||||
hmtx = [0x01, 0xF4, 0x00, 0x00];
|
hmtx = "\x01\xF4\x00\x00";
|
||||||
for (var i = 0; i < charstrings.length; i++) {
|
for (var i = 0; i < charstrings.length; i++) {
|
||||||
var charstring = charstrings[i].charstring;
|
var charstring = charstrings[i].charstring;
|
||||||
var width = FontsUtils.integerToBytes(charstring[1], 2);
|
var width = charstring[1];
|
||||||
var lsb = FontsUtils.integerToBytes(charstring[0], 2);
|
var lsb = charstring[0];
|
||||||
hmtx = hmtx.concat(width, lsb);
|
hmtx += s16(width) + s16(lsb);
|
||||||
}
|
}
|
||||||
this._createTableEntry(otf, offsets, "hmtx", hmtx);
|
hmtx = s2a(hmtx);
|
||||||
|
createTableEntry(otf, offsets, "hmtx", hmtx);
|
||||||
|
|
||||||
/** MAXP */
|
/** MAXP */
|
||||||
maxp = [].concat(
|
maxp = "\x00\x00\x50\x00" + // Version number
|
||||||
[
|
s16(charstrings.length + 1); // Num of glyphs (+1 to pass the sanitizer...)
|
||||||
0x00, 0x00, 0x50, 0x00, // Version number
|
maxp = s2a(maxp);
|
||||||
],
|
createTableEntry(otf, offsets, "maxp", maxp);
|
||||||
FontsUtils.integerToBytes(charstrings.length + 1, 2) // Num of glyphs (+1 to pass the sanitizer...)
|
|
||||||
);
|
|
||||||
this._createTableEntry(otf, offsets, "maxp", maxp);
|
|
||||||
|
|
||||||
/** NAME */
|
/** NAME */
|
||||||
var name = this._createNameTable(aName);
|
name = s2a(createNameTable(aName));
|
||||||
this._createTableEntry(otf, offsets, "name", name);
|
createTableEntry(otf, offsets, "name", name);
|
||||||
|
|
||||||
/** POST */
|
/** POST */
|
||||||
// FIXME Get those informations from the FontInfo structure
|
// TODO: get those informations from the FontInfo structure
|
||||||
post = [
|
post = "\x00\x03\x00\x00" + // Version number
|
||||||
0x00, 0x03, 0x00, 0x00, // Version number
|
"\x00\x00\x01\x00" + // italicAngle
|
||||||
0x00, 0x00, 0x01, 0x00, // italicAngle
|
"\x00\x00" + // underlinePosition
|
||||||
0x00, 0x00, // underlinePosition
|
"\x00\x00" + // underlineThickness
|
||||||
0x00, 0x00, // underlineThickness
|
"\x00\x00\x00\x00" + // isFixedPitch
|
||||||
0x00, 0x00, 0x00, 0x00, // isFixedPitch
|
"\x00\x00\x00\x00" + // minMemType42
|
||||||
0x00, 0x00, 0x00, 0x00, // minMemType42
|
"\x00\x00\x00\x00" + // maxMemType42
|
||||||
0x00, 0x00, 0x00, 0x00, // maxMemType42
|
"\x00\x00\x00\x00" + // minMemType1
|
||||||
0x00, 0x00, 0x00, 0x00, // minMemType1
|
"\x00\x00\x00\x00"; // maxMemType1
|
||||||
0x00, 0x00, 0x00, 0x00 // maxMemType1
|
post = s2a(post);
|
||||||
];
|
createTableEntry(otf, offsets, "post", post);
|
||||||
this._createTableEntry(otf, offsets, "post", post);
|
|
||||||
|
|
||||||
// Once all the table entries header are written, dump the data!
|
// Once all the table entries header are written, dump the data!
|
||||||
var tables = [CFF, OS2, cmap, head, hhea, hmtx, maxp, name, post];
|
var tables = [CFF, OS2, cmap, head, hhea, hmtx, maxp, name, post];
|
||||||
@ -622,8 +617,10 @@ Font.prototype = {
|
|||||||
fontData.push(otf[i]);
|
fontData.push(otf[i]);
|
||||||
return fontData;
|
return fontData;
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
return constructor;
|
||||||
|
})();
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* FontsUtils is a static class dedicated to hold codes that are not related
|
* FontsUtils is a static class dedicated to hold codes that are not related
|
||||||
@ -705,11 +702,9 @@ var TrueType = function(aName, aFile, aProperties) {
|
|||||||
if (table.tag == "cmap")
|
if (table.tag == "cmap")
|
||||||
originalCMAP = table;
|
originalCMAP = table;
|
||||||
|
|
||||||
requiredTables.splice(index, 1);
|
|
||||||
}
|
|
||||||
|
|
||||||
tables.push(table);
|
tables.push(table);
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// If any tables are still in the array this means some required tables are
|
// If any tables are still in the array this means some required tables are
|
||||||
// missing, which means that we need to rebuild the font in order to pass
|
// missing, which means that we need to rebuild the font in order to pass
|
||||||
@ -830,7 +825,6 @@ var TrueType = function(aName, aFile, aProperties) {
|
|||||||
data: OS2
|
data: OS2
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
||||||
// Tables needs to be written by ascendant alphabetic order
|
// Tables needs to be written by ascendant alphabetic order
|
||||||
tables.sort(function(a, b) {
|
tables.sort(function(a, b) {
|
||||||
return a.tag > b.tag;
|
return a.tag > b.tag;
|
||||||
@ -866,9 +860,10 @@ var TrueType = function(aName, aFile, aProperties) {
|
|||||||
this.data = fontData;
|
this.data = fontData;
|
||||||
return;
|
return;
|
||||||
} else if (requiredTables.length) {
|
} else if (requiredTables.length) {
|
||||||
warn("Missing " + requiredTables + " in the TrueType font");
|
error("Table " + requiredTables[0] + " is missing from the TruType font");
|
||||||
|
} else {
|
||||||
|
this.data = aFile.getBytes();
|
||||||
}
|
}
|
||||||
this.data = aFile;
|
|
||||||
};
|
};
|
||||||
|
|
||||||
TrueType.prototype = {
|
TrueType.prototype = {
|
||||||
@ -901,79 +896,8 @@ TrueType.prototype = {
|
|||||||
aOffsets.virtualOffset += header.length;
|
aOffsets.virtualOffset += header.length;
|
||||||
},
|
},
|
||||||
|
|
||||||
_createTableEntry: function font_createTableEntry(aFile, aOffsets, aTag, aData) {
|
|
||||||
// tag
|
|
||||||
var tag = [
|
|
||||||
aTag.charCodeAt(0),
|
|
||||||
aTag.charCodeAt(1),
|
|
||||||
aTag.charCodeAt(2),
|
|
||||||
aTag.charCodeAt(3)
|
|
||||||
];
|
|
||||||
|
|
||||||
// Per spec tables must be 4-bytes align so add some 0x00 if needed
|
|
||||||
while (aData.length & 3)
|
|
||||||
aData.push(0x00);
|
|
||||||
|
|
||||||
while (aOffsets.virtualOffset & 3)
|
|
||||||
aOffsets.virtualOffset++;
|
|
||||||
|
|
||||||
// offset
|
|
||||||
var offset = aOffsets.virtualOffset;
|
|
||||||
|
|
||||||
// length
|
|
||||||
var length = aData.length;
|
|
||||||
|
|
||||||
// checksum
|
|
||||||
var checksum = FontsUtils.bytesToInteger(tag) + offset + length;
|
|
||||||
|
|
||||||
var tableEntry = [].concat(tag,
|
|
||||||
FontsUtils.integerToBytes(checksum, 4),
|
|
||||||
FontsUtils.integerToBytes(offset, 4),
|
|
||||||
FontsUtils.integerToBytes(length, 4));
|
|
||||||
aFile.set(tableEntry, aOffsets.currentOffset);
|
|
||||||
aOffsets.currentOffset += tableEntry.length;
|
|
||||||
aOffsets.virtualOffset += aData.length;
|
|
||||||
},
|
|
||||||
|
|
||||||
_readOpenTypeHeader: function tt_readOpenTypeHeader(aFile) {
|
|
||||||
return {
|
|
||||||
version: aFile.getBytes(4),
|
|
||||||
numTables: FontsUtils.bytesToInteger(aFile.getBytes(2)),
|
|
||||||
searchRange: FontsUtils.bytesToInteger(aFile.getBytes(2)),
|
|
||||||
entrySelector: FontsUtils.bytesToInteger(aFile.getBytes(2)),
|
|
||||||
rangeShift: FontsUtils.bytesToInteger(aFile.getBytes(2))
|
|
||||||
}
|
|
||||||
},
|
|
||||||
|
|
||||||
_readTableEntry: function tt_readTableEntry(aFile) {
|
|
||||||
// tag
|
|
||||||
var tag = aFile.getBytes(4);
|
|
||||||
tag = String.fromCharCode(tag[0]) +
|
|
||||||
String.fromCharCode(tag[1]) +
|
|
||||||
String.fromCharCode(tag[2]) +
|
|
||||||
String.fromCharCode(tag[3]);
|
|
||||||
|
|
||||||
var checksum = FontsUtils.bytesToInteger(aFile.getBytes(4));
|
|
||||||
var offset = FontsUtils.bytesToInteger(aFile.getBytes(4));
|
|
||||||
var length = FontsUtils.bytesToInteger(aFile.getBytes(4));
|
|
||||||
|
|
||||||
// Read the table associated data
|
|
||||||
var currentPosition = aFile.pos;
|
|
||||||
aFile.pos = aFile.start + offset;
|
|
||||||
var data = aFile.getBytes(length);
|
|
||||||
aFile.pos = currentPosition;
|
|
||||||
|
|
||||||
return {
|
|
||||||
tag: tag,
|
|
||||||
checksum: checksum,
|
|
||||||
length: offset,
|
|
||||||
offset: length,
|
|
||||||
data: data
|
|
||||||
}
|
|
||||||
},
|
|
||||||
|
|
||||||
_createCMAPTable: function font_createCMAPTable(aGlyphs) {
|
_createCMAPTable: function font_createCMAPTable(aGlyphs) {
|
||||||
var characters = new Uint16Array(kMaxGlyphsCount);
|
var characters = Uint16Array(65535);
|
||||||
for (var i = 0; i < aGlyphs.length; i++)
|
for (var i = 0; i < aGlyphs.length; i++)
|
||||||
characters[aGlyphs[i].unicode] = i + 1;
|
characters[aGlyphs[i].unicode] = i + 1;
|
||||||
|
|
||||||
@ -1052,6 +976,77 @@ TrueType.prototype = {
|
|||||||
|
|
||||||
return cmap.concat(endCount, [0x00, 0x00], startCount,
|
return cmap.concat(endCount, [0x00, 0x00], startCount,
|
||||||
idDeltas, idRangeOffsets, glyphsIdsArray);
|
idDeltas, idRangeOffsets, glyphsIdsArray);
|
||||||
|
},
|
||||||
|
|
||||||
|
_createTableEntry: function font_createTableEntry(aFile, aOffsets, aTag, aData) {
|
||||||
|
// tag
|
||||||
|
var tag = [
|
||||||
|
aTag.charCodeAt(0),
|
||||||
|
aTag.charCodeAt(1),
|
||||||
|
aTag.charCodeAt(2),
|
||||||
|
aTag.charCodeAt(3)
|
||||||
|
];
|
||||||
|
|
||||||
|
// Per spec tables must be 4-bytes align so add some 0x00 if needed
|
||||||
|
while (aData.length & 3)
|
||||||
|
aData.push(0x00);
|
||||||
|
|
||||||
|
while (aOffsets.virtualOffset & 3)
|
||||||
|
aOffsets.virtualOffset++;
|
||||||
|
|
||||||
|
// offset
|
||||||
|
var offset = aOffsets.virtualOffset;
|
||||||
|
|
||||||
|
// length
|
||||||
|
var length = aData.length;
|
||||||
|
|
||||||
|
// checksum
|
||||||
|
var checksum = FontsUtils.bytesToInteger(tag) + offset + length;
|
||||||
|
|
||||||
|
var tableEntry = [].concat(tag,
|
||||||
|
FontsUtils.integerToBytes(checksum, 4),
|
||||||
|
FontsUtils.integerToBytes(offset, 4),
|
||||||
|
FontsUtils.integerToBytes(length, 4));
|
||||||
|
aFile.set(tableEntry, aOffsets.currentOffset);
|
||||||
|
aOffsets.currentOffset += tableEntry.length;
|
||||||
|
aOffsets.virtualOffset += aData.length;
|
||||||
|
},
|
||||||
|
|
||||||
|
_readOpenTypeHeader: function tt_readOpenTypeHeader(aFile) {
|
||||||
|
return {
|
||||||
|
version: aFile.getBytes(4),
|
||||||
|
numTables: FontsUtils.bytesToInteger(aFile.getBytes(2)),
|
||||||
|
searchRange: FontsUtils.bytesToInteger(aFile.getBytes(2)),
|
||||||
|
entrySelector: FontsUtils.bytesToInteger(aFile.getBytes(2)),
|
||||||
|
rangeShift: FontsUtils.bytesToInteger(aFile.getBytes(2))
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
_readTableEntry: function tt_readTableEntry(aFile) {
|
||||||
|
// tag
|
||||||
|
var tag = aFile.getBytes(4);
|
||||||
|
tag = String.fromCharCode(tag[0]) +
|
||||||
|
String.fromCharCode(tag[1]) +
|
||||||
|
String.fromCharCode(tag[2]) +
|
||||||
|
String.fromCharCode(tag[3]);
|
||||||
|
|
||||||
|
var checksum = FontsUtils.bytesToInteger(aFile.getBytes(4));
|
||||||
|
var offset = FontsUtils.bytesToInteger(aFile.getBytes(4));
|
||||||
|
var length = FontsUtils.bytesToInteger(aFile.getBytes(4));
|
||||||
|
|
||||||
|
// Read the table associated data
|
||||||
|
var currentPosition = aFile.pos;
|
||||||
|
aFile.pos = aFile.start + offset;
|
||||||
|
var data = aFile.getBytes(length);
|
||||||
|
aFile.pos = currentPosition;
|
||||||
|
|
||||||
|
return {
|
||||||
|
tag: tag,
|
||||||
|
checksum: checksum,
|
||||||
|
length: offset,
|
||||||
|
offset: length,
|
||||||
|
data: data
|
||||||
|
}
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -1245,8 +1240,8 @@ var Type1Parser = function() {
|
|||||||
this.extractFontProgram = function t1_extractFontProgram(aStream) {
|
this.extractFontProgram = function t1_extractFontProgram(aStream) {
|
||||||
var eexecString = decrypt(aStream, kEexecEncryptionKey, 4);
|
var eexecString = decrypt(aStream, kEexecEncryptionKey, 4);
|
||||||
var subrs = [], glyphs = [];
|
var subrs = [], glyphs = [];
|
||||||
var inSubrs = false;
|
|
||||||
var inGlyphs = false;
|
var inGlyphs = false;
|
||||||
|
var inSubrs = false;
|
||||||
var glyph = "";
|
var glyph = "";
|
||||||
|
|
||||||
var token = "";
|
var token = "";
|
||||||
@ -1641,6 +1636,7 @@ CFF.prototype = {
|
|||||||
}
|
}
|
||||||
|
|
||||||
var charstringsIndex = this.createCFFIndexHeader([[0x40, 0x0E]].concat(glyphs), true);
|
var charstringsIndex = this.createCFFIndexHeader([[0x40, 0x0E]].concat(glyphs), true);
|
||||||
|
charstringsIndex = charstringsIndex.join(" ").split(" "); // XXX why?
|
||||||
|
|
||||||
//Top Dict Index
|
//Top Dict Index
|
||||||
var topDictIndex = [
|
var topDictIndex = [
|
||||||
@ -1674,6 +1670,7 @@ CFF.prototype = {
|
|||||||
var privateOffset = charstringsOffset + charstringsIndex.length;
|
var privateOffset = charstringsOffset + charstringsIndex.length;
|
||||||
topDictIndex = topDictIndex.concat(this.encodeNumber(privateOffset));
|
topDictIndex = topDictIndex.concat(this.encodeNumber(privateOffset));
|
||||||
topDictIndex.push(18); // Private
|
topDictIndex.push(18); // Private
|
||||||
|
topDictIndex = topDictIndex.join(" ").split(" ");
|
||||||
|
|
||||||
var indexes = [
|
var indexes = [
|
||||||
topDictIndex, stringsIndex,
|
topDictIndex, stringsIndex,
|
||||||
@ -1703,6 +1700,7 @@ CFF.prototype = {
|
|||||||
139, 12, 14,
|
139, 12, 14,
|
||||||
28, 0, 55, 19
|
28, 0, 55, 19
|
||||||
]);
|
]);
|
||||||
|
privateData = privateData.join(" ").split(" ");
|
||||||
cff.set(privateData, currentOffset);
|
cff.set(privateData, currentOffset);
|
||||||
currentOffset += privateData.length;
|
currentOffset += privateData.length;
|
||||||
|
|
||||||
|
155
glyphlist.js
155
glyphlist.js
@ -1,3 +1,6 @@
|
|||||||
|
/* -*- Mode: Java; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- /
|
||||||
|
/* vim: set shiftwidth=2 tabstop=2 autoindent cindent expandtab: */
|
||||||
|
|
||||||
"use strict";
|
"use strict";
|
||||||
|
|
||||||
var GlyphsUnicode = {
|
var GlyphsUnicode = {
|
||||||
@ -1502,27 +1505,27 @@ var GlyphsUnicode = {
|
|||||||
dalet: 0x05D3,
|
dalet: 0x05D3,
|
||||||
daletdagesh: 0xFB33,
|
daletdagesh: 0xFB33,
|
||||||
daletdageshhebrew: 0xFB33,
|
daletdageshhebrew: 0xFB33,
|
||||||
dalethatafpatah: "05D3 05B2",
|
dalethatafpatah: 0x05D305B2,
|
||||||
dalethatafpatahhebrew: "05D3 05B2",
|
dalethatafpatahhebrew: 0x05D305B2,
|
||||||
dalethatafsegol: "05D3 05B1",
|
dalethatafsegol: 0x05D305B1,
|
||||||
dalethatafsegolhebrew: "05D3 05B1",
|
dalethatafsegolhebrew: 0x05D305B1,
|
||||||
dalethebrew: 0x05D3,
|
dalethebrew: 0x05D3,
|
||||||
dalethiriq: "05D3 05B4",
|
dalethiriq: 0x05D305B4,
|
||||||
dalethiriqhebrew: "05D3 05B4",
|
dalethiriqhebrew: 0x05D305B4,
|
||||||
daletholam: "05D3 05B9",
|
daletholam: 0x05D305B9,
|
||||||
daletholamhebrew: "05D3 05B9",
|
daletholamhebrew: 0x05D305B9,
|
||||||
daletpatah: "05D3 05B7",
|
daletpatah: 0x05D305B7,
|
||||||
daletpatahhebrew: "05D3 05B7",
|
daletpatahhebrew: 0x05D305B7,
|
||||||
daletqamats: "05D3 05B8",
|
daletqamats: 0x05D305B8,
|
||||||
daletqamatshebrew: "05D3 05B8",
|
daletqamatshebrew: 0x05D305B8,
|
||||||
daletqubuts: "05D3 05BB",
|
daletqubuts: 0x05D305BB,
|
||||||
daletqubutshebrew: "05D3 05BB",
|
daletqubutshebrew: 0x05D305BB,
|
||||||
daletsegol: "05D3 05B6",
|
daletsegol: 0x05D305B6,
|
||||||
daletsegolhebrew: "05D3 05B6",
|
daletsegolhebrew: 0x05D305B6,
|
||||||
daletsheva: "05D3 05B0",
|
daletsheva: 0x05D305B0,
|
||||||
daletshevahebrew: "05D3 05B0",
|
daletshevahebrew: 0x05D305B0,
|
||||||
dalettsere: "05D3 05B5",
|
dalettsere: 0x05D305B5,
|
||||||
dalettserehebrew: "05D3 05B5",
|
dalettserehebrew: 0x05D305B5,
|
||||||
dalfinalarabic: 0xFEAA,
|
dalfinalarabic: 0xFEAA,
|
||||||
dammaarabic: 0x064F,
|
dammaarabic: 0x064F,
|
||||||
dammalowarabic: 0x064F,
|
dammalowarabic: 0x064F,
|
||||||
@ -1839,10 +1842,10 @@ var GlyphsUnicode = {
|
|||||||
finalkafdagesh: 0xFB3A,
|
finalkafdagesh: 0xFB3A,
|
||||||
finalkafdageshhebrew: 0xFB3A,
|
finalkafdageshhebrew: 0xFB3A,
|
||||||
finalkafhebrew: 0x05DA,
|
finalkafhebrew: 0x05DA,
|
||||||
finalkafqamats: "05DA 05B8",
|
finalkafqamats: 0x05DA05B8,
|
||||||
finalkafqamatshebrew: "05DA 05B8",
|
finalkafqamatshebrew: 0x05DA05B8,
|
||||||
finalkafsheva: "05DA 05B0",
|
finalkafsheva: 0x05DA05B0,
|
||||||
finalkafshevahebrew: "05DA 05B0",
|
finalkafshevahebrew: 0x05DA05B0,
|
||||||
finalmem: 0x05DD,
|
finalmem: 0x05DD,
|
||||||
finalmemhebrew: 0x05DD,
|
finalmemhebrew: 0x05DD,
|
||||||
finalnun: 0x05DF,
|
finalnun: 0x05DF,
|
||||||
@ -2031,14 +2034,14 @@ var GlyphsUnicode = {
|
|||||||
hakatakanahalfwidth: 0xFF8A,
|
hakatakanahalfwidth: 0xFF8A,
|
||||||
halantgurmukhi: 0x0A4D,
|
halantgurmukhi: 0x0A4D,
|
||||||
hamzaarabic: 0x0621,
|
hamzaarabic: 0x0621,
|
||||||
hamzadammaarabic: "0621 064F",
|
hamzadammaarabic: 0x0621064F,
|
||||||
hamzadammatanarabic: "0621 064C",
|
hamzadammatanarabic: 0x0621064C,
|
||||||
hamzafathaarabic: "0621 064E",
|
hamzafathaarabic: 0x0621064E,
|
||||||
hamzafathatanarabic: "0621 064B",
|
hamzafathatanarabic: 0x0621064B,
|
||||||
hamzalowarabic: 0x0621,
|
hamzalowarabic: 0x0621,
|
||||||
hamzalowkasraarabic: "0621 0650",
|
hamzalowkasraarabic: 0x06210650,
|
||||||
hamzalowkasratanarabic: "0621 064D",
|
hamzalowkasratanarabic: 0x0621064D,
|
||||||
hamzasukunarabic: "0621 0652",
|
hamzasukunarabic: 0x06210652,
|
||||||
hangulfiller: 0x3164,
|
hangulfiller: 0x3164,
|
||||||
hardsigncyrillic: 0x044A,
|
hardsigncyrillic: 0x044A,
|
||||||
harpoonleftbarbup: 0x21BC,
|
harpoonleftbarbup: 0x21BC,
|
||||||
@ -2470,10 +2473,10 @@ var GlyphsUnicode = {
|
|||||||
lameddagesh: 0xFB3C,
|
lameddagesh: 0xFB3C,
|
||||||
lameddageshhebrew: 0xFB3C,
|
lameddageshhebrew: 0xFB3C,
|
||||||
lamedhebrew: 0x05DC,
|
lamedhebrew: 0x05DC,
|
||||||
lamedholam: "05DC 05B9",
|
lamedholam: 0x05DC05B9,
|
||||||
lamedholamdagesh: "05DC 05B9 05BC",
|
lamedholamdagesh: "05DC 05B9 05BC",
|
||||||
lamedholamdageshhebrew: "05DC 05B9 05BC",
|
lamedholamdageshhebrew: "05DC 05B9 05BC",
|
||||||
lamedholamhebrew: "05DC 05B9",
|
lamedholamhebrew: 0x05DC05B9,
|
||||||
lamfinalarabic: 0xFEDE,
|
lamfinalarabic: 0xFEDE,
|
||||||
lamhahinitialarabic: 0xFCCA,
|
lamhahinitialarabic: 0xFCCA,
|
||||||
laminitialarabic: 0xFEDF,
|
laminitialarabic: 0xFEDF,
|
||||||
@ -2781,7 +2784,7 @@ var GlyphsUnicode = {
|
|||||||
noonfinalarabic: 0xFEE6,
|
noonfinalarabic: 0xFEE6,
|
||||||
noonghunnaarabic: 0x06BA,
|
noonghunnaarabic: 0x06BA,
|
||||||
noonghunnafinalarabic: 0xFB9F,
|
noonghunnafinalarabic: 0xFB9F,
|
||||||
noonhehinitialarabic: "FEE7 FEEC",
|
noonhehinitialarabic: 0xFEE7FEEC,
|
||||||
nooninitialarabic: 0xFEE7,
|
nooninitialarabic: 0xFEE7,
|
||||||
noonjeeminitialarabic: 0xFCD2,
|
noonjeeminitialarabic: 0xFCD2,
|
||||||
noonjeemisolatedarabic: 0xFC4B,
|
noonjeemisolatedarabic: 0xFC4B,
|
||||||
@ -3153,27 +3156,27 @@ var GlyphsUnicode = {
|
|||||||
qof: 0x05E7,
|
qof: 0x05E7,
|
||||||
qofdagesh: 0xFB47,
|
qofdagesh: 0xFB47,
|
||||||
qofdageshhebrew: 0xFB47,
|
qofdageshhebrew: 0xFB47,
|
||||||
qofhatafpatah: "05E7 05B2",
|
qofhatafpatah: 0x05E705B2,
|
||||||
qofhatafpatahhebrew: "05E7 05B2",
|
qofhatafpatahhebrew: 0x05E705B2,
|
||||||
qofhatafsegol: "05E7 05B1",
|
qofhatafsegol: 0x05E705B1,
|
||||||
qofhatafsegolhebrew: "05E7 05B1",
|
qofhatafsegolhebrew: 0x05E705B1,
|
||||||
qofhebrew: 0x05E7,
|
qofhebrew: 0x05E7,
|
||||||
qofhiriq: "05E7 05B4",
|
qofhiriq: 0x05E705B4,
|
||||||
qofhiriqhebrew: "05E7 05B4",
|
qofhiriqhebrew: 0x05E705B4,
|
||||||
qofholam: "05E7 05B9",
|
qofholam: 0x05E705B9,
|
||||||
qofholamhebrew: "05E7 05B9",
|
qofholamhebrew: 0x05E705B9,
|
||||||
qofpatah: "05E7 05B7",
|
qofpatah: 0x05E705B7,
|
||||||
qofpatahhebrew: "05E7 05B7",
|
qofpatahhebrew: 0x05E705B7,
|
||||||
qofqamats: "05E7 05B8",
|
qofqamats: 0x05E705B8,
|
||||||
qofqamatshebrew: "05E7 05B8",
|
qofqamatshebrew: 0x05E705B8,
|
||||||
qofqubuts: "05E7 05BB",
|
qofqubuts: 0x05E705BB,
|
||||||
qofqubutshebrew: "05E7 05BB",
|
qofqubutshebrew: 0x05E705BB,
|
||||||
qofsegol: "05E7 05B6",
|
qofsegol: 0x05E705B6,
|
||||||
qofsegolhebrew: "05E7 05B6",
|
qofsegolhebrew: 0x05E705B6,
|
||||||
qofsheva: "05E7 05B0",
|
qofsheva: 0x05E705B0,
|
||||||
qofshevahebrew: "05E7 05B0",
|
qofshevahebrew: 0x05E705B0,
|
||||||
qoftsere: "05E7 05B5",
|
qoftsere: 0x05E705B5,
|
||||||
qoftserehebrew: "05E7 05B5",
|
qoftserehebrew: 0x05E705B5,
|
||||||
qparen: 0x24AC,
|
qparen: 0x24AC,
|
||||||
quarternote: 0x2669,
|
quarternote: 0x2669,
|
||||||
qubuts: 0x05BB,
|
qubuts: 0x05BB,
|
||||||
@ -3252,27 +3255,27 @@ var GlyphsUnicode = {
|
|||||||
rekatakanahalfwidth: 0xFF9A,
|
rekatakanahalfwidth: 0xFF9A,
|
||||||
resh: 0x05E8,
|
resh: 0x05E8,
|
||||||
reshdageshhebrew: 0xFB48,
|
reshdageshhebrew: 0xFB48,
|
||||||
reshhatafpatah: "05E8 05B2",
|
reshhatafpatah: 0x05E805B2,
|
||||||
reshhatafpatahhebrew: "05E8 05B2",
|
reshhatafpatahhebrew: 0x05E805B2,
|
||||||
reshhatafsegol: "05E8 05B1",
|
reshhatafsegol: 0x05E805B1,
|
||||||
reshhatafsegolhebrew: "05E8 05B1",
|
reshhatafsegolhebrew: 0x05E805B1,
|
||||||
reshhebrew: 0x05E8,
|
reshhebrew: 0x05E8,
|
||||||
reshhiriq: "05E8 05B4",
|
reshhiriq: 0x05E805B4,
|
||||||
reshhiriqhebrew: "05E8 05B4",
|
reshhiriqhebrew: 0x05E805B4,
|
||||||
reshholam: "05E8 05B9",
|
reshholam: 0x05E805B9,
|
||||||
reshholamhebrew: "05E8 05B9",
|
reshholamhebrew: 0x05E805B9,
|
||||||
reshpatah: "05E8 05B7",
|
reshpatah: 0x05E805B7,
|
||||||
reshpatahhebrew: "05E8 05B7",
|
reshpatahhebrew: 0x05E805B7,
|
||||||
reshqamats: "05E8 05B8",
|
reshqamats: 0x05E805B8,
|
||||||
reshqamatshebrew: "05E8 05B8",
|
reshqamatshebrew: 0x05E805B8,
|
||||||
reshqubuts: "05E8 05BB",
|
reshqubuts: 0x05E805BB,
|
||||||
reshqubutshebrew: "05E8 05BB",
|
reshqubutshebrew: 0x05E805BB,
|
||||||
reshsegol: "05E8 05B6",
|
reshsegol: 0x05E805B6,
|
||||||
reshsegolhebrew: "05E8 05B6",
|
reshsegolhebrew: 0x05E805B6,
|
||||||
reshsheva: "05E8 05B0",
|
reshsheva: 0x05E805B0,
|
||||||
reshshevahebrew: "05E8 05B0",
|
reshshevahebrew: 0x05E805B0,
|
||||||
reshtsere: "05E8 05B5",
|
reshtsere: 0x05E805B5,
|
||||||
reshtserehebrew: "05E8 05B5",
|
reshtserehebrew: 0x05E805B5,
|
||||||
reversedtilde: 0x223D,
|
reversedtilde: 0x223D,
|
||||||
reviahebrew: 0x0597,
|
reviahebrew: 0x0597,
|
||||||
reviamugrashhebrew: 0x0597,
|
reviamugrashhebrew: 0x0597,
|
||||||
@ -3471,7 +3474,7 @@ var GlyphsUnicode = {
|
|||||||
shaddadammaarabic: 0xFC61,
|
shaddadammaarabic: 0xFC61,
|
||||||
shaddadammatanarabic: 0xFC5E,
|
shaddadammatanarabic: 0xFC5E,
|
||||||
shaddafathaarabic: 0xFC60,
|
shaddafathaarabic: 0xFC60,
|
||||||
shaddafathatanarabic: "0651 064B",
|
shaddafathatanarabic: 0x0651064B,
|
||||||
shaddakasraarabic: 0xFC62,
|
shaddakasraarabic: 0xFC62,
|
||||||
shaddakasratanarabic: 0xFC5F,
|
shaddakasratanarabic: 0xFC5F,
|
||||||
shade: 0x2592,
|
shade: 0x2592,
|
||||||
@ -3668,7 +3671,7 @@ var GlyphsUnicode = {
|
|||||||
tchehfinalarabic: 0xFB7B,
|
tchehfinalarabic: 0xFB7B,
|
||||||
tchehinitialarabic: 0xFB7C,
|
tchehinitialarabic: 0xFB7C,
|
||||||
tchehmedialarabic: 0xFB7D,
|
tchehmedialarabic: 0xFB7D,
|
||||||
tchehmeeminitialarabic: "FB7C FEE4",
|
tchehmeeminitialarabic: 0xFB7CFEE4,
|
||||||
tcircle: 0x24E3,
|
tcircle: 0x24E3,
|
||||||
tcircumflexbelow: 0x1E71,
|
tcircumflexbelow: 0x1E71,
|
||||||
tcommaaccent: 0x0163,
|
tcommaaccent: 0x0163,
|
||||||
|
BIN
images/buttons.png
Normal file
BIN
images/buttons.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 2.7 KiB |
BIN
images/combobox.png
Normal file
BIN
images/combobox.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 1.9 KiB |
BIN
images/source/Buttons.psd.zip
Normal file
BIN
images/source/Buttons.psd.zip
Normal file
Binary file not shown.
BIN
images/source/ComboBox.psd.zip
Normal file
BIN
images/source/ComboBox.psd.zip
Normal file
Binary file not shown.
184
multi-page-viewer.css
Normal file
184
multi-page-viewer.css
Normal file
@ -0,0 +1,184 @@
|
|||||||
|
/* -*- Mode: Java; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- /
|
||||||
|
/* vim: set shiftwidth=4 tabstop=8 autoindent cindent expandtab: */
|
||||||
|
|
||||||
|
body {
|
||||||
|
background-color: #929292;
|
||||||
|
font-family: 'Lucida Grande', 'Lucida Sans Unicode', Helvetica, Arial, Verdana, sans-serif;
|
||||||
|
margin: 0px;
|
||||||
|
padding: 0px;
|
||||||
|
}
|
||||||
|
|
||||||
|
canvas {
|
||||||
|
box-shadow: 0px 4px 10px #000;
|
||||||
|
-moz-box-shadow: 0px 4px 10px #000;
|
||||||
|
-webkit-box-shadow: 0px 4px 10px #000;
|
||||||
|
}
|
||||||
|
|
||||||
|
span {
|
||||||
|
font-size: 0.8em;
|
||||||
|
}
|
||||||
|
|
||||||
|
.control {
|
||||||
|
display: inline-block;
|
||||||
|
float: left;
|
||||||
|
margin: 0px 20px 0px 0px;
|
||||||
|
padding: 0px 4px 0px 0px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.control > input {
|
||||||
|
float: left;
|
||||||
|
margin: 0px 2px 0px 0px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.control > span {
|
||||||
|
cursor: default;
|
||||||
|
float: left;
|
||||||
|
height: 18px;
|
||||||
|
margin: 5px 2px 0px;
|
||||||
|
padding: 0px;
|
||||||
|
user-select: none;
|
||||||
|
-moz-user-select: none;
|
||||||
|
-webkit-user-select: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.control .label {
|
||||||
|
clear: both;
|
||||||
|
float: left;
|
||||||
|
font-size: 0.65em;
|
||||||
|
margin: 2px 0px 0px;
|
||||||
|
position: relative;
|
||||||
|
text-align: center;
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
.page {
|
||||||
|
width: 816px;
|
||||||
|
height: 1056px;
|
||||||
|
margin: 10px auto;
|
||||||
|
}
|
||||||
|
|
||||||
|
#controls {
|
||||||
|
background-color: #eee;
|
||||||
|
border-bottom: 1px solid #666;
|
||||||
|
padding: 4px 0px 0px 8px;
|
||||||
|
position:fixed;
|
||||||
|
left: 0px;
|
||||||
|
top: 0px;
|
||||||
|
height: 40px;
|
||||||
|
width: 100%;
|
||||||
|
box-shadow: 0px 2px 8px #000;
|
||||||
|
-moz-box-shadow: 0px 2px 8px #000;
|
||||||
|
-webkit-box-shadow: 0px 2px 8px #000;
|
||||||
|
}
|
||||||
|
|
||||||
|
#controls input {
|
||||||
|
user-select: text;
|
||||||
|
-moz-user-select: text;
|
||||||
|
-webkit-user-select: text;
|
||||||
|
}
|
||||||
|
|
||||||
|
#previousPageButton {
|
||||||
|
background: url('images/buttons.png') no-repeat 0px -23px;
|
||||||
|
cursor: pointer;
|
||||||
|
display: inline-block;
|
||||||
|
float: left;
|
||||||
|
margin: 0px;
|
||||||
|
width: 28px;
|
||||||
|
height: 23px;
|
||||||
|
}
|
||||||
|
|
||||||
|
#previousPageButton.down {
|
||||||
|
background: url('images/buttons.png') no-repeat 0px -46px;
|
||||||
|
}
|
||||||
|
|
||||||
|
#previousPageButton.disabled {
|
||||||
|
background: url('images/buttons.png') no-repeat 0px 0px;
|
||||||
|
}
|
||||||
|
|
||||||
|
#nextPageButton {
|
||||||
|
background: url('images/buttons.png') no-repeat -28px -23px;
|
||||||
|
cursor: pointer;
|
||||||
|
display: inline-block;
|
||||||
|
float: left;
|
||||||
|
margin: 0px;
|
||||||
|
width: 28px;
|
||||||
|
height: 23px;
|
||||||
|
}
|
||||||
|
|
||||||
|
#nextPageButton.down {
|
||||||
|
background: url('images/buttons.png') no-repeat -28px -46px;
|
||||||
|
}
|
||||||
|
|
||||||
|
#nextPageButton.disabled {
|
||||||
|
background: url('images/buttons.png') no-repeat -28px 0px;
|
||||||
|
}
|
||||||
|
|
||||||
|
#scaleComboBoxInput {
|
||||||
|
background: url('images/combobox.png') no-repeat 0px -23px;
|
||||||
|
display: inline-block;
|
||||||
|
float: left;
|
||||||
|
margin: 0px;
|
||||||
|
width: 35px;
|
||||||
|
height: 23px;
|
||||||
|
}
|
||||||
|
|
||||||
|
#scaleComboBoxInput input {
|
||||||
|
background: none;
|
||||||
|
border: 0px;
|
||||||
|
margin: 3px 2px 0px;
|
||||||
|
width: 31px;
|
||||||
|
}
|
||||||
|
|
||||||
|
#scaleComboBoxButton {
|
||||||
|
background: url('images/combobox.png') no-repeat -41px -23px;
|
||||||
|
cursor: pointer;
|
||||||
|
display: inline-block;
|
||||||
|
float: left;
|
||||||
|
margin: 0px;
|
||||||
|
width: 21px;
|
||||||
|
height: 23px;
|
||||||
|
}
|
||||||
|
|
||||||
|
#scaleComboBoxButton.down {
|
||||||
|
background: url('images/combobox.png') no-repeat -41px -46px;
|
||||||
|
}
|
||||||
|
|
||||||
|
#scaleComboBoxButton.disabled {
|
||||||
|
background: url('images/combobox.png') no-repeat -41px 0px;
|
||||||
|
}
|
||||||
|
|
||||||
|
#scaleComboBoxList {
|
||||||
|
background-color: #fff;
|
||||||
|
border: 1px solid #666;
|
||||||
|
clear: both;
|
||||||
|
position: relative;
|
||||||
|
display: none;
|
||||||
|
top: -20px;
|
||||||
|
width: 48px;
|
||||||
|
}
|
||||||
|
|
||||||
|
#scaleComboBoxList > ul {
|
||||||
|
list-style: none;
|
||||||
|
padding: 0px;
|
||||||
|
margin: 0px;
|
||||||
|
}
|
||||||
|
|
||||||
|
#scaleComboBoxList > ul > li {
|
||||||
|
display: inline-block;
|
||||||
|
cursor: pointer;
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
#scaleComboBoxList > ul > li:hover {
|
||||||
|
background-color: #09f;
|
||||||
|
color: #fff;
|
||||||
|
}
|
||||||
|
|
||||||
|
#pageNumber, #scale {
|
||||||
|
text-align: right;
|
||||||
|
}
|
||||||
|
|
||||||
|
#viewer {
|
||||||
|
margin: 44px 0px 0px;
|
||||||
|
padding: 8px 0px;
|
||||||
|
}
|
40
multi-page-viewer.html
Normal file
40
multi-page-viewer.html
Normal file
@ -0,0 +1,40 @@
|
|||||||
|
<!DOCTYPE html>
|
||||||
|
<html>
|
||||||
|
<head>
|
||||||
|
<title>pdf.js Multi-Page Viewer</title>
|
||||||
|
<link rel="stylesheet" href="multi-page-viewer.css" type="text/css" media="screen" charset="utf-8"/>
|
||||||
|
<script type="text/javascript" src="pdf.js"></script>
|
||||||
|
<script type="text/javascript" src="fonts.js"></script>
|
||||||
|
<script type="text/javascript" src="glyphlist.js"></script>
|
||||||
|
<script type="text/javascript" src="multi-page-viewer.js"></script>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<div id="controls">
|
||||||
|
<span class="control">
|
||||||
|
<span id="previousPageButton"></span><span id="nextPageButton"></span>
|
||||||
|
<span class="label">Previous/Next</span>
|
||||||
|
</span>
|
||||||
|
<span class="control">
|
||||||
|
<input type="text" id="pageNumber" value="1" size="2"/>
|
||||||
|
<span>/</span>
|
||||||
|
<span id="numPages">--</span>
|
||||||
|
<span class="label">Page Number</span>
|
||||||
|
</span>
|
||||||
|
<span class="control">
|
||||||
|
<span id="scaleComboBoxInput"><input type="text" id="scale" value="100%" size="2"/></span><span id="scaleComboBoxButton"></span>
|
||||||
|
<span class="label">Zoom</span>
|
||||||
|
<div id="scaleComboBoxList">
|
||||||
|
<ul>
|
||||||
|
<li>50%</li>
|
||||||
|
<li>75%</li>
|
||||||
|
<li>100%</li>
|
||||||
|
<li>125%</li>
|
||||||
|
<li>150%</li>
|
||||||
|
<li>200%</li>
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
<div id="viewer"></div>
|
||||||
|
</body>
|
||||||
|
</html>
|
400
multi-page-viewer.js
Normal file
400
multi-page-viewer.js
Normal file
@ -0,0 +1,400 @@
|
|||||||
|
/* -*- Mode: Java; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- /
|
||||||
|
/* vim: set shiftwidth=4 tabstop=8 autoindent cindent expandtab: */
|
||||||
|
|
||||||
|
"use strict";
|
||||||
|
|
||||||
|
var PDFViewer = {
|
||||||
|
queryParams: {},
|
||||||
|
|
||||||
|
element: null,
|
||||||
|
|
||||||
|
previousPageButton: null,
|
||||||
|
nextPageButton: null,
|
||||||
|
pageNumberInput: null,
|
||||||
|
scaleInput: null,
|
||||||
|
|
||||||
|
willJumpToPage: false,
|
||||||
|
|
||||||
|
pdf: null,
|
||||||
|
|
||||||
|
url: 'compressed.tracemonkey-pldi-09.pdf',
|
||||||
|
pageNumber: 1,
|
||||||
|
numberOfPages: 1,
|
||||||
|
|
||||||
|
scale: 1.0,
|
||||||
|
|
||||||
|
pageWidth: function() {
|
||||||
|
return 816 * PDFViewer.scale;
|
||||||
|
},
|
||||||
|
|
||||||
|
pageHeight: function() {
|
||||||
|
return 1056 * PDFViewer.scale;
|
||||||
|
},
|
||||||
|
|
||||||
|
lastPagesDrawn: [],
|
||||||
|
|
||||||
|
visiblePages: function() {
|
||||||
|
var pageHeight = PDFViewer.pageHeight() + 20; // Add 20 for the margins.
|
||||||
|
var windowTop = window.pageYOffset;
|
||||||
|
var windowBottom = window.pageYOffset + window.innerHeight;
|
||||||
|
var pageStartIndex = Math.floor(windowTop / pageHeight);
|
||||||
|
var pageStopIndex = Math.ceil(windowBottom / pageHeight);
|
||||||
|
|
||||||
|
var pages = [];
|
||||||
|
|
||||||
|
for (var i = pageStartIndex; i <= pageStopIndex; i++) {
|
||||||
|
pages.push(i + 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
return pages;
|
||||||
|
},
|
||||||
|
|
||||||
|
createPage: function(num) {
|
||||||
|
var anchor = document.createElement('a');
|
||||||
|
anchor.name = '' + num;
|
||||||
|
|
||||||
|
var div = document.createElement('div');
|
||||||
|
div.id = 'pageContainer' + num;
|
||||||
|
div.className = 'page';
|
||||||
|
div.style.width = PDFViewer.pageWidth() + 'px';
|
||||||
|
div.style.height = PDFViewer.pageHeight() + 'px';
|
||||||
|
|
||||||
|
PDFViewer.element.appendChild(anchor);
|
||||||
|
PDFViewer.element.appendChild(div);
|
||||||
|
},
|
||||||
|
|
||||||
|
removePage: function(num) {
|
||||||
|
var div = document.getElementById('pageContainer' + num);
|
||||||
|
|
||||||
|
if (div && div.hasChildNodes()) {
|
||||||
|
while (div.childNodes.length > 0) {
|
||||||
|
div.removeChild(div.firstChild);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
drawPage: function(num) {
|
||||||
|
if (PDFViewer.pdf) {
|
||||||
|
var page = PDFViewer.pdf.getPage(num);
|
||||||
|
var div = document.getElementById('pageContainer' + num);
|
||||||
|
|
||||||
|
if (div && !div.hasChildNodes()) {
|
||||||
|
var canvas = document.createElement('canvas');
|
||||||
|
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();
|
||||||
|
canvas.height = PDFViewer.pageHeight();
|
||||||
|
|
||||||
|
var ctx = canvas.getContext('2d');
|
||||||
|
ctx.save();
|
||||||
|
ctx.fillStyle = 'rgb(255, 255, 255)';
|
||||||
|
ctx.fillRect(0, 0, canvas.width, canvas.height);
|
||||||
|
ctx.restore();
|
||||||
|
|
||||||
|
var gfx = new CanvasGraphics(ctx);
|
||||||
|
var fonts = [];
|
||||||
|
|
||||||
|
// page.compile will collect all fonts for us, once we have loaded them
|
||||||
|
// we can trigger the actual page rendering with page.display
|
||||||
|
page.compile(gfx, fonts);
|
||||||
|
|
||||||
|
var fontsReady = true;
|
||||||
|
|
||||||
|
// Inspect fonts and translate the missing one
|
||||||
|
var fontCount = fonts.length;
|
||||||
|
|
||||||
|
for (var i = 0; i < fontCount; i++) {
|
||||||
|
var font = fonts[i];
|
||||||
|
|
||||||
|
if (Fonts[font.name]) {
|
||||||
|
fontsReady = fontsReady && !Fonts[font.name].loading;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
new Font(font.name, font.file, font.properties);
|
||||||
|
|
||||||
|
fontsReady = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
var pageInterval;
|
||||||
|
var delayLoadFont = function() {
|
||||||
|
for (var i = 0; i < fontCount; i++) {
|
||||||
|
if (Fonts[font.name].loading) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
clearInterval(pageInterval);
|
||||||
|
|
||||||
|
PDFViewer.drawPage(num);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!fontsReady) {
|
||||||
|
pageInterval = setInterval(delayLoadFont, 10);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
page.display(gfx);
|
||||||
|
div.appendChild(canvas);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
changeScale: function(num) {
|
||||||
|
while (PDFViewer.element.childNodes.length > 0) {
|
||||||
|
PDFViewer.element.removeChild(PDFViewer.element.firstChild);
|
||||||
|
}
|
||||||
|
|
||||||
|
PDFViewer.scale = num / 100;
|
||||||
|
|
||||||
|
if (PDFViewer.pdf) {
|
||||||
|
for (var i = 1; i <= PDFViewer.numberOfPages; i++) {
|
||||||
|
PDFViewer.createPage(i);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (PDFViewer.numberOfPages > 0) {
|
||||||
|
PDFViewer.drawPage(1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
PDFViewer.scaleInput.value = Math.floor(PDFViewer.scale * 100) + '%';
|
||||||
|
},
|
||||||
|
|
||||||
|
goToPage: function(num) {
|
||||||
|
if (1 <= num && num <= PDFViewer.numberOfPages) {
|
||||||
|
PDFViewer.pageNumber = num;
|
||||||
|
PDFViewer.pageNumberInput.value = PDFViewer.pageNumber;
|
||||||
|
PDFViewer.willJumpToPage = true;
|
||||||
|
|
||||||
|
document.location.hash = PDFViewer.pageNumber;
|
||||||
|
|
||||||
|
PDFViewer.previousPageButton.className = (PDFViewer.pageNumber === 1) ?
|
||||||
|
'disabled' : '';
|
||||||
|
PDFViewer.nextPageButton.className = (PDFViewer.pageNumber === PDFViewer.numberOfPages) ?
|
||||||
|
'disabled' : '';
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
goToPreviousPage: function() {
|
||||||
|
if (PDFViewer.pageNumber > 1) {
|
||||||
|
PDFViewer.goToPage(--PDFViewer.pageNumber);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
goToNextPage: function() {
|
||||||
|
if (PDFViewer.pageNumber < PDFViewer.numberOfPages) {
|
||||||
|
PDFViewer.goToPage(++PDFViewer.pageNumber);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
open: function(url) {
|
||||||
|
PDFViewer.url = url;
|
||||||
|
document.title = url;
|
||||||
|
|
||||||
|
var req = new XMLHttpRequest();
|
||||||
|
req.open('GET', url);
|
||||||
|
req.mozResponseType = req.responseType = 'arraybuffer';
|
||||||
|
req.expected = (document.URL.indexOf('file:') === 0) ? 0 : 200;
|
||||||
|
|
||||||
|
req.onreadystatechange = function() {
|
||||||
|
if (req.readyState === 4 && req.status === req.expected) {
|
||||||
|
var data = req.mozResponseArrayBuffer ||
|
||||||
|
req.mozResponse ||
|
||||||
|
req.responseArrayBuffer ||
|
||||||
|
req.response;
|
||||||
|
|
||||||
|
PDFViewer.pdf = new PDFDoc(new Stream(data));
|
||||||
|
PDFViewer.numberOfPages = PDFViewer.pdf.numPages;
|
||||||
|
document.getElementById('numPages').innerHTML = PDFViewer.numberOfPages.toString();
|
||||||
|
|
||||||
|
for (var i = 1; i <= PDFViewer.numberOfPages; i++) {
|
||||||
|
PDFViewer.createPage(i);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (PDFViewer.numberOfPages > 0) {
|
||||||
|
PDFViewer.drawPage(1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
req.send(null);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
window.onload = function() {
|
||||||
|
|
||||||
|
// Parse the URL query parameters into a cached object.
|
||||||
|
PDFViewer.queryParams = function() {
|
||||||
|
var qs = window.location.search.substring(1);
|
||||||
|
var kvs = qs.split('&');
|
||||||
|
var params = {};
|
||||||
|
for (var i = 0; i < kvs.length; ++i) {
|
||||||
|
var kv = kvs[i].split('=');
|
||||||
|
params[unescape(kv[0])] = unescape(kv[1]);
|
||||||
|
}
|
||||||
|
|
||||||
|
return params;
|
||||||
|
}();
|
||||||
|
|
||||||
|
PDFViewer.element = document.getElementById('viewer');
|
||||||
|
|
||||||
|
PDFViewer.pageNumberInput = document.getElementById('pageNumber');
|
||||||
|
PDFViewer.pageNumberInput.onkeydown = function(evt) {
|
||||||
|
var charCode = evt.charCode || evt.keyCode;
|
||||||
|
|
||||||
|
// Up arrow key.
|
||||||
|
if (charCode === 38) {
|
||||||
|
PDFViewer.goToNextPage();
|
||||||
|
this.select();
|
||||||
|
}
|
||||||
|
|
||||||
|
// Down arrow key.
|
||||||
|
else if (charCode === 40) {
|
||||||
|
PDFViewer.goToPreviousPage();
|
||||||
|
this.select();
|
||||||
|
}
|
||||||
|
|
||||||
|
// All other non-numeric keys (excluding Left arrow, Right arrow,
|
||||||
|
// Backspace, and Delete keys).
|
||||||
|
else if ((charCode < 48 || charCode > 57) &&
|
||||||
|
charCode !== 8 && // Backspace
|
||||||
|
charCode !== 46 && // Delete
|
||||||
|
charCode !== 37 && // Left arrow
|
||||||
|
charCode !== 39 // Right arrow
|
||||||
|
) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
};
|
||||||
|
PDFViewer.pageNumberInput.onkeyup = function(evt) {
|
||||||
|
var charCode = evt.charCode || evt.keyCode;
|
||||||
|
|
||||||
|
// All numeric keys, Backspace, and Delete.
|
||||||
|
if ((charCode >= 48 && charCode <= 57) ||
|
||||||
|
charCode === 8 || // Backspace
|
||||||
|
charCode === 46 // Delete
|
||||||
|
) {
|
||||||
|
PDFViewer.goToPage(this.value);
|
||||||
|
}
|
||||||
|
|
||||||
|
this.focus();
|
||||||
|
};
|
||||||
|
|
||||||
|
PDFViewer.previousPageButton = document.getElementById('previousPageButton');
|
||||||
|
PDFViewer.previousPageButton.onclick = function(evt) {
|
||||||
|
if (this.className.indexOf('disabled') === -1) {
|
||||||
|
PDFViewer.goToPreviousPage();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
PDFViewer.previousPageButton.onmousedown = function(evt) {
|
||||||
|
if (this.className.indexOf('disabled') === -1) {
|
||||||
|
this.className = 'down';
|
||||||
|
}
|
||||||
|
};
|
||||||
|
PDFViewer.previousPageButton.onmouseup = function(evt) {
|
||||||
|
this.className = (this.className.indexOf('disabled') !== -1) ? 'disabled' : '';
|
||||||
|
};
|
||||||
|
PDFViewer.previousPageButton.onmouseout = function(evt) {
|
||||||
|
this.className = (this.className.indexOf('disabled') !== -1) ? 'disabled' : '';
|
||||||
|
};
|
||||||
|
|
||||||
|
PDFViewer.nextPageButton = document.getElementById('nextPageButton');
|
||||||
|
PDFViewer.nextPageButton.onclick = function(evt) {
|
||||||
|
if (this.className.indexOf('disabled') === -1) {
|
||||||
|
PDFViewer.goToNextPage();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
PDFViewer.nextPageButton.onmousedown = function(evt) {
|
||||||
|
if (this.className.indexOf('disabled') === -1) {
|
||||||
|
this.className = 'down';
|
||||||
|
}
|
||||||
|
};
|
||||||
|
PDFViewer.nextPageButton.onmouseup = function(evt) {
|
||||||
|
this.className = (this.className.indexOf('disabled') !== -1) ? 'disabled' : '';
|
||||||
|
};
|
||||||
|
PDFViewer.nextPageButton.onmouseout = function(evt) {
|
||||||
|
this.className = (this.className.indexOf('disabled') !== -1) ? 'disabled' : '';
|
||||||
|
};
|
||||||
|
|
||||||
|
PDFViewer.scaleInput = document.getElementById('scale');
|
||||||
|
PDFViewer.scaleInput.buttonElement = document.getElementById('scaleComboBoxButton');
|
||||||
|
PDFViewer.scaleInput.buttonElement.listElement = document.getElementById('scaleComboBoxList');
|
||||||
|
PDFViewer.scaleInput.onchange = function(evt) {
|
||||||
|
PDFViewer.changeScale(parseInt(this.value));
|
||||||
|
};
|
||||||
|
|
||||||
|
PDFViewer.scaleInput.buttonElement.onclick = function(evt) {
|
||||||
|
this.listElement.style.display = (this.listElement.style.display === 'block') ? 'none' : 'block';
|
||||||
|
};
|
||||||
|
PDFViewer.scaleInput.buttonElement.onmousedown = function(evt) {
|
||||||
|
if (this.className.indexOf('disabled') === -1) {
|
||||||
|
this.className = 'down';
|
||||||
|
}
|
||||||
|
};
|
||||||
|
PDFViewer.scaleInput.buttonElement.onmouseup = function(evt) {
|
||||||
|
this.className = (this.className.indexOf('disabled') !== -1) ? 'disabled' : '';
|
||||||
|
};
|
||||||
|
PDFViewer.scaleInput.buttonElement.onmouseout = function(evt) {
|
||||||
|
this.className = (this.className.indexOf('disabled') !== -1) ? 'disabled' : '';
|
||||||
|
};
|
||||||
|
|
||||||
|
var listItems = PDFViewer.scaleInput.buttonElement.listElement.getElementsByTagName('LI');
|
||||||
|
|
||||||
|
for (var i = 0; i < listItems.length; i++) {
|
||||||
|
var listItem = listItems[i];
|
||||||
|
listItem.onclick = function(evt) {
|
||||||
|
PDFViewer.changeScale(parseInt(this.innerHTML));
|
||||||
|
PDFViewer.scaleInput.buttonElement.listElement.style.display = 'none';
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
PDFViewer.pageNumber = parseInt(PDFViewer.queryParams.page) || PDFViewer.pageNumber;
|
||||||
|
PDFViewer.scale = parseInt(PDFViewer.scaleInput.value) / 100 || 1.0;
|
||||||
|
PDFViewer.open(PDFViewer.queryParams.file || PDFViewer.url);
|
||||||
|
|
||||||
|
window.onscroll = function(evt) {
|
||||||
|
var lastPagesDrawn = PDFViewer.lastPagesDrawn;
|
||||||
|
var visiblePages = PDFViewer.visiblePages();
|
||||||
|
|
||||||
|
var pagesToDraw = [];
|
||||||
|
var pagesToKeep = [];
|
||||||
|
var pagesToRemove = [];
|
||||||
|
|
||||||
|
var i;
|
||||||
|
|
||||||
|
// Determine which visible pages were not previously drawn.
|
||||||
|
for (i = 0; i < visiblePages.length; i++) {
|
||||||
|
if (lastPagesDrawn.indexOf(visiblePages[i]) === -1) {
|
||||||
|
pagesToDraw.push(visiblePages[i]);
|
||||||
|
PDFViewer.drawPage(visiblePages[i]);
|
||||||
|
} else {
|
||||||
|
pagesToKeep.push(visiblePages[i]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Determine which previously drawn pages are no longer visible.
|
||||||
|
for (i = 0; i < lastPagesDrawn.length; i++) {
|
||||||
|
if (visiblePages.indexOf(lastPagesDrawn[i]) === -1) {
|
||||||
|
pagesToRemove.push(lastPagesDrawn[i]);
|
||||||
|
PDFViewer.removePage(lastPagesDrawn[i]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
PDFViewer.lastPagesDrawn = pagesToDraw.concat(pagesToKeep);
|
||||||
|
|
||||||
|
// Update the page number input with the current page number.
|
||||||
|
if (!PDFViewer.willJumpToPage && visiblePages.length > 0) {
|
||||||
|
PDFViewer.pageNumber = PDFViewer.pageNumberInput.value = visiblePages[0];
|
||||||
|
PDFViewer.previousPageButton.className = (PDFViewer.pageNumber === 1) ?
|
||||||
|
'disabled' : '';
|
||||||
|
PDFViewer.nextPageButton.className = (PDFViewer.pageNumber === PDFViewer.numberOfPages) ?
|
||||||
|
'disabled' : '';
|
||||||
|
} else {
|
||||||
|
PDFViewer.willJumpToPage = false;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
};
|
416
pdf.js
416
pdf.js
@ -50,7 +50,7 @@ function shadow(obj, prop, value) {
|
|||||||
|
|
||||||
var Stream = (function() {
|
var Stream = (function() {
|
||||||
function constructor(arrayBuffer, start, length, dict) {
|
function constructor(arrayBuffer, start, length, dict) {
|
||||||
this.bytes = new Uint8Array(arrayBuffer);
|
this.bytes = Uint8Array(arrayBuffer);
|
||||||
this.start = start || 0;
|
this.start = start || 0;
|
||||||
this.pos = this.start;
|
this.pos = this.start;
|
||||||
this.end = (start + length) || this.bytes.byteLength;
|
this.end = (start + length) || this.bytes.byteLength;
|
||||||
@ -115,7 +115,7 @@ var Stream = (function() {
|
|||||||
var StringStream = (function() {
|
var StringStream = (function() {
|
||||||
function constructor(str) {
|
function constructor(str) {
|
||||||
var length = str.length;
|
var length = str.length;
|
||||||
var bytes = new Uint8Array(length);
|
var bytes = Uint8Array(length);
|
||||||
for (var n = 0; n < length; ++n)
|
for (var n = 0; n < length; ++n)
|
||||||
bytes[n] = str.charCodeAt(n);
|
bytes[n] = str.charCodeAt(n);
|
||||||
Stream.call(this, bytes);
|
Stream.call(this, bytes);
|
||||||
@ -127,11 +127,11 @@ var StringStream = (function() {
|
|||||||
})();
|
})();
|
||||||
|
|
||||||
var FlateStream = (function() {
|
var FlateStream = (function() {
|
||||||
const codeLenCodeMap = new Uint32Array([
|
const codeLenCodeMap = Uint32Array([
|
||||||
16, 17, 18, 0, 8, 7, 9, 6, 10, 5, 11, 4, 12, 3, 13, 2, 14, 1, 15
|
16, 17, 18, 0, 8, 7, 9, 6, 10, 5, 11, 4, 12, 3, 13, 2, 14, 1, 15
|
||||||
]);
|
]);
|
||||||
|
|
||||||
const lengthDecode = new Uint32Array([
|
const lengthDecode = Uint32Array([
|
||||||
0x00003, 0x00004, 0x00005, 0x00006, 0x00007, 0x00008, 0x00009,
|
0x00003, 0x00004, 0x00005, 0x00006, 0x00007, 0x00008, 0x00009,
|
||||||
0x0000a, 0x1000b, 0x1000d, 0x1000f, 0x10011, 0x20013, 0x20017,
|
0x0000a, 0x1000b, 0x1000d, 0x1000f, 0x10011, 0x20013, 0x20017,
|
||||||
0x2001b, 0x2001f, 0x30023, 0x3002b, 0x30033, 0x3003b, 0x40043,
|
0x2001b, 0x2001f, 0x30023, 0x3002b, 0x30033, 0x3003b, 0x40043,
|
||||||
@ -139,7 +139,7 @@ var FlateStream = (function() {
|
|||||||
0x00102, 0x00102, 0x00102
|
0x00102, 0x00102, 0x00102
|
||||||
]);
|
]);
|
||||||
|
|
||||||
const distDecode = new Uint32Array([
|
const distDecode = Uint32Array([
|
||||||
0x00001, 0x00002, 0x00003, 0x00004, 0x10005, 0x10007, 0x20009,
|
0x00001, 0x00002, 0x00003, 0x00004, 0x10005, 0x10007, 0x20009,
|
||||||
0x2000d, 0x30011, 0x30019, 0x40021, 0x40031, 0x50041, 0x50061,
|
0x2000d, 0x30011, 0x30019, 0x40021, 0x40031, 0x50041, 0x50061,
|
||||||
0x60081, 0x600c1, 0x70101, 0x70181, 0x80201, 0x80301, 0x90401,
|
0x60081, 0x600c1, 0x70101, 0x70181, 0x80201, 0x80301, 0x90401,
|
||||||
@ -147,7 +147,7 @@ var FlateStream = (function() {
|
|||||||
0xd4001, 0xd6001
|
0xd4001, 0xd6001
|
||||||
]);
|
]);
|
||||||
|
|
||||||
const fixedLitCodeTab = [new Uint32Array([
|
const fixedLitCodeTab = [Uint32Array([
|
||||||
0x70100, 0x80050, 0x80010, 0x80118, 0x70110, 0x80070, 0x80030,
|
0x70100, 0x80050, 0x80010, 0x80118, 0x70110, 0x80070, 0x80030,
|
||||||
0x900c0, 0x70108, 0x80060, 0x80020, 0x900a0, 0x80000, 0x80080,
|
0x900c0, 0x70108, 0x80060, 0x80020, 0x900a0, 0x80000, 0x80080,
|
||||||
0x80040, 0x900e0, 0x70104, 0x80058, 0x80018, 0x90090, 0x70114,
|
0x80040, 0x900e0, 0x70104, 0x80058, 0x80018, 0x90090, 0x70114,
|
||||||
@ -224,7 +224,7 @@ var FlateStream = (function() {
|
|||||||
0x900ff
|
0x900ff
|
||||||
]), 9];
|
]), 9];
|
||||||
|
|
||||||
const fixedDistCodeTab = [new Uint32Array([
|
const fixedDistCodeTab = [Uint32Array([
|
||||||
0x50000, 0x50010, 0x50008, 0x50018, 0x50004, 0x50014, 0x5000c,
|
0x50000, 0x50010, 0x50008, 0x50018, 0x50004, 0x50014, 0x5000c,
|
||||||
0x5001c, 0x50002, 0x50012, 0x5000a, 0x5001a, 0x50006, 0x50016,
|
0x5001c, 0x50002, 0x50012, 0x5000a, 0x5001a, 0x50006, 0x50016,
|
||||||
0x5000e, 0x00000, 0x50001, 0x50011, 0x50009, 0x50019, 0x50005,
|
0x5000e, 0x00000, 0x50001, 0x50011, 0x50009, 0x50019, 0x50005,
|
||||||
@ -300,7 +300,7 @@ var FlateStream = (function() {
|
|||||||
var size = 512;
|
var size = 512;
|
||||||
while (size < requested)
|
while (size < requested)
|
||||||
size <<= 1;
|
size <<= 1;
|
||||||
var buffer2 = new Uint8Array(size);
|
var buffer2 = Uint8Array(size);
|
||||||
for (var i = 0; i < current; ++i)
|
for (var i = 0; i < current; ++i)
|
||||||
buffer2[i] = buffer[i];
|
buffer2[i] = buffer[i];
|
||||||
return this.buffer = buffer2;
|
return this.buffer = buffer2;
|
||||||
@ -308,7 +308,7 @@ var FlateStream = (function() {
|
|||||||
getByte: function() {
|
getByte: function() {
|
||||||
var bufferLength = this.bufferLength;
|
var bufferLength = this.bufferLength;
|
||||||
var pos = this.pos;
|
var pos = this.pos;
|
||||||
if (bufferLength == pos) {
|
if (bufferLength <= pos) {
|
||||||
if (this.eof)
|
if (this.eof)
|
||||||
return;
|
return;
|
||||||
this.readBlock();
|
this.readBlock();
|
||||||
@ -333,7 +333,7 @@ var FlateStream = (function() {
|
|||||||
lookChar: function() {
|
lookChar: function() {
|
||||||
var bufferLength = this.bufferLength;
|
var bufferLength = this.bufferLength;
|
||||||
var pos = this.pos;
|
var pos = this.pos;
|
||||||
if (bufferLength == pos) {
|
if (bufferLength <= pos) {
|
||||||
if (this.eof)
|
if (this.eof)
|
||||||
return;
|
return;
|
||||||
this.readBlock();
|
this.readBlock();
|
||||||
@ -365,7 +365,7 @@ var FlateStream = (function() {
|
|||||||
|
|
||||||
// build the table
|
// build the table
|
||||||
var size = 1 << maxLen;
|
var size = 1 << maxLen;
|
||||||
var codes = new Uint32Array(size);
|
var codes = Uint32Array(size);
|
||||||
for (var len = 1, code = 0, skip = 2;
|
for (var len = 1, code = 0, skip = 2;
|
||||||
len <= maxLen;
|
len <= maxLen;
|
||||||
++len, code <<= 1, skip <<= 1) {
|
++len, code <<= 1, skip <<= 1) {
|
||||||
@ -391,6 +391,12 @@ var FlateStream = (function() {
|
|||||||
return [codes, maxLen];
|
return [codes, maxLen];
|
||||||
},
|
},
|
||||||
readBlock: function() {
|
readBlock: function() {
|
||||||
|
function repeat(stream, array, len, offset, what) {
|
||||||
|
var repeat = stream.getBits(len) + offset;
|
||||||
|
while (repeat-- > 0)
|
||||||
|
array[i++] = what;
|
||||||
|
}
|
||||||
|
|
||||||
var stream = this.stream;
|
var stream = this.stream;
|
||||||
|
|
||||||
// read block header
|
// read block header
|
||||||
@ -434,11 +440,6 @@ var FlateStream = (function() {
|
|||||||
litCodeTable = fixedLitCodeTab;
|
litCodeTable = fixedLitCodeTab;
|
||||||
distCodeTable = fixedDistCodeTab;
|
distCodeTable = fixedDistCodeTab;
|
||||||
} else if (hdr == 2) { // compressed block, dynamic codes
|
} else if (hdr == 2) { // compressed block, dynamic codes
|
||||||
var repeat = function repeat(stream, array, len, offset, what) {
|
|
||||||
var repeat = stream.getBits(len) + offset;
|
|
||||||
while (repeat-- > 0)
|
|
||||||
array[i++] = what;
|
|
||||||
}
|
|
||||||
var numLitCodes = this.getBits(5) + 257;
|
var numLitCodes = this.getBits(5) + 257;
|
||||||
var numDistCodes = this.getBits(5) + 1;
|
var numDistCodes = this.getBits(5) + 1;
|
||||||
var numCodeLenCodes = this.getBits(4) + 4;
|
var numCodeLenCodes = this.getBits(4) + 4;
|
||||||
@ -508,9 +509,97 @@ var FlateStream = (function() {
|
|||||||
return constructor;
|
return constructor;
|
||||||
})();
|
})();
|
||||||
|
|
||||||
|
var PredictorStream = (function() {
|
||||||
|
function constructor(stream, params) {
|
||||||
|
this.stream = stream;
|
||||||
|
this.predictor = params.get("Predictor") || 1;
|
||||||
|
if (this.predictor <= 1) {
|
||||||
|
return stream; // no prediction
|
||||||
|
}
|
||||||
|
if (params.has("EarlyChange")) {
|
||||||
|
error("EarlyChange predictor parameter is not supported");
|
||||||
|
}
|
||||||
|
this.colors = params.get("Colors") || 1;
|
||||||
|
this.bitsPerComponent = params.get("BitsPerComponent") || 8;
|
||||||
|
this.columns = params.get("Columns") || 1;
|
||||||
|
if (this.colors !== 1 || this.bitsPerComponent !== 8) {
|
||||||
|
error("Multi-color and multi-byte predictors are not supported");
|
||||||
|
}
|
||||||
|
if (this.predictor < 10 || this.predictor > 15) {
|
||||||
|
error("Unsupported predictor");
|
||||||
|
}
|
||||||
|
this.currentRow = new Uint8Array(this.columns);
|
||||||
|
this.pos = 0;
|
||||||
|
this.bufferLength = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
constructor.prototype = {
|
||||||
|
readRow : function() {
|
||||||
|
var lastRow = this.currentRow;
|
||||||
|
var predictor = this.stream.getByte();
|
||||||
|
var currentRow = this.stream.getBytes(this.columns), i;
|
||||||
|
switch (predictor) {
|
||||||
|
default:
|
||||||
|
error("Unsupported predictor");
|
||||||
|
break;
|
||||||
|
case 0:
|
||||||
|
break;
|
||||||
|
case 2:
|
||||||
|
for (i = 0; i < currentRow.length; ++i) {
|
||||||
|
currentRow[i] = (lastRow[i] + currentRow[i]) & 0xFF;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
this.pos = 0;
|
||||||
|
this.bufferLength = currentRow.length;
|
||||||
|
this.currentRow = currentRow;
|
||||||
|
},
|
||||||
|
getByte : function() {
|
||||||
|
if (this.pos >= this.bufferLength) {
|
||||||
|
this.readRow();
|
||||||
|
}
|
||||||
|
return this.currentRow[this.pos++];
|
||||||
|
},
|
||||||
|
getBytes : function(n) {
|
||||||
|
var i, bytes;
|
||||||
|
bytes = new Uint8Array(n);
|
||||||
|
for (i = 0; i < n; ++i) {
|
||||||
|
if (this.pos >= this.bufferLength) {
|
||||||
|
this.readRow();
|
||||||
|
}
|
||||||
|
bytes[i] = this.currentRow[this.pos++];
|
||||||
|
}
|
||||||
|
return bytes;
|
||||||
|
},
|
||||||
|
getChar : function() {
|
||||||
|
return String.formCharCode(this.getByte());
|
||||||
|
},
|
||||||
|
lookChar : function() {
|
||||||
|
if (this.pos >= this.bufferLength) {
|
||||||
|
this.readRow();
|
||||||
|
}
|
||||||
|
return String.formCharCode(this.currentRow[this.pos]);
|
||||||
|
},
|
||||||
|
skip : function(n) {
|
||||||
|
var i;
|
||||||
|
if (!n) {
|
||||||
|
n = 1;
|
||||||
|
}
|
||||||
|
while (n > this.bufferLength - this.pos) {
|
||||||
|
n -= this.bufferLength - this.pos;
|
||||||
|
this.readRow();
|
||||||
|
if (this.bufferLength === 0) break;
|
||||||
|
}
|
||||||
|
this.pos += n;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
return constructor;
|
||||||
|
})();
|
||||||
|
|
||||||
var DecryptStream = (function() {
|
var DecryptStream = (function() {
|
||||||
function constructor(str, fileKey, encAlgorithm, keyLength) {
|
function constructor(str, fileKey, encAlgorithm, keyLength) {
|
||||||
// TODO
|
TODO("decrypt stream is not implemented");
|
||||||
}
|
}
|
||||||
|
|
||||||
constructor.prototype = Stream.prototype;
|
constructor.prototype = Stream.prototype;
|
||||||
@ -727,7 +816,7 @@ var Lexer = (function() {
|
|||||||
var done = false;
|
var done = false;
|
||||||
var str = "";
|
var str = "";
|
||||||
var stream = this.stream;
|
var stream = this.stream;
|
||||||
var ch = null;
|
var ch;
|
||||||
do {
|
do {
|
||||||
switch (ch = stream.getChar()) {
|
switch (ch = stream.getChar()) {
|
||||||
case undefined:
|
case undefined:
|
||||||
@ -1088,7 +1177,9 @@ var Parser = (function() {
|
|||||||
this.encAlgorithm,
|
this.encAlgorithm,
|
||||||
this.keyLength);
|
this.keyLength);
|
||||||
}
|
}
|
||||||
return this.filter(stream, dict);
|
stream = this.filter(stream, dict);
|
||||||
|
stream.parameters = dict;
|
||||||
|
return stream;
|
||||||
},
|
},
|
||||||
filter: function(stream, dict) {
|
filter: function(stream, dict) {
|
||||||
var filter = dict.get2("Filter", "F");
|
var filter = dict.get2("Filter", "F");
|
||||||
@ -1113,8 +1204,9 @@ var Parser = (function() {
|
|||||||
},
|
},
|
||||||
makeFilter: function(stream, name, params) {
|
makeFilter: function(stream, name, params) {
|
||||||
if (name == "FlateDecode" || name == "Fl") {
|
if (name == "FlateDecode" || name == "Fl") {
|
||||||
if (params)
|
if (params) {
|
||||||
error("params not supported yet for FlateDecode");
|
return new PredictorStream(new FlateStream(stream), params);
|
||||||
|
}
|
||||||
return new FlateStream(stream);
|
return new FlateStream(stream);
|
||||||
} else {
|
} else {
|
||||||
error("filter '" + name + "' not supported yet");
|
error("filter '" + name + "' not supported yet");
|
||||||
@ -1207,10 +1299,10 @@ var XRef = (function() {
|
|||||||
this.stream = stream;
|
this.stream = stream;
|
||||||
this.entries = [];
|
this.entries = [];
|
||||||
this.xrefstms = {};
|
this.xrefstms = {};
|
||||||
this.readXRef(startXRef);
|
var trailerDict = this.readXRef(startXRef);
|
||||||
|
|
||||||
// get the root dictionary (catalog) object
|
// get the root dictionary (catalog) object
|
||||||
if (!IsRef(this.root = this.trailerDict.get("Root")))
|
if (!IsRef(this.root = trailerDict.get("Root")))
|
||||||
error("Invalid root reference");
|
error("Invalid root reference");
|
||||||
|
|
||||||
// prepare the XRef cache
|
// prepare the XRef cache
|
||||||
@ -1265,18 +1357,18 @@ var XRef = (function() {
|
|||||||
error("Invalid XRef table");
|
error("Invalid XRef table");
|
||||||
|
|
||||||
// get the 'Prev' pointer
|
// get the 'Prev' pointer
|
||||||
var more = false;
|
var prev;
|
||||||
obj = dict.get("Prev");
|
obj = dict.get("Prev");
|
||||||
if (IsInt(obj)) {
|
if (IsInt(obj)) {
|
||||||
this.prev = obj;
|
prev = obj;
|
||||||
more = true;
|
|
||||||
} else if (IsRef(obj)) {
|
} else if (IsRef(obj)) {
|
||||||
// certain buggy PDF generators generate "/Prev NNN 0 R" instead
|
// certain buggy PDF generators generate "/Prev NNN 0 R" instead
|
||||||
// of "/Prev NNN"
|
// of "/Prev NNN"
|
||||||
this.prev = obj.num;
|
prev = obj.num;
|
||||||
more = true;
|
}
|
||||||
|
if (prev) {
|
||||||
|
this.readXRef(prev);
|
||||||
}
|
}
|
||||||
this.trailerDict = dict;
|
|
||||||
|
|
||||||
// check for 'XRefStm' key
|
// check for 'XRefStm' key
|
||||||
if (IsInt(obj = dict.get("XRefStm"))) {
|
if (IsInt(obj = dict.get("XRefStm"))) {
|
||||||
@ -1287,10 +1379,56 @@ var XRef = (function() {
|
|||||||
this.readXRef(pos);
|
this.readXRef(pos);
|
||||||
}
|
}
|
||||||
|
|
||||||
return more;
|
return dict;
|
||||||
},
|
},
|
||||||
readXRefStream: function(parser) {
|
readXRefStream: function(stream) {
|
||||||
error("Invalid XRef stream");
|
var streamParameters = stream.parameters;
|
||||||
|
var length = streamParameters.get("Length");
|
||||||
|
var byteWidths = streamParameters.get("W");
|
||||||
|
var range = streamParameters.get("Index");
|
||||||
|
if (!range)
|
||||||
|
range = [0, streamParameters.get("Size")];
|
||||||
|
var i, j;
|
||||||
|
while (range.length > 0) {
|
||||||
|
var first = range[0], n = range[1];
|
||||||
|
if (!IsInt(first) || !IsInt(n))
|
||||||
|
error("Invalid XRef range fields");
|
||||||
|
var typeFieldWidth = byteWidths[0], offsetFieldWidth = byteWidths[1], generationFieldWidth = byteWidths[2];
|
||||||
|
if (!IsInt(typeFieldWidth) || !IsInt(offsetFieldWidth) || !IsInt(generationFieldWidth))
|
||||||
|
error("Invalid XRef entry fields length");
|
||||||
|
for (i = 0; i < n; ++i) {
|
||||||
|
var type = 0, offset = 0, generation = 0;
|
||||||
|
for (j = 0; j < typeFieldWidth; ++j)
|
||||||
|
type = (type << 8) | stream.getByte();
|
||||||
|
for (j = 0; j < offsetFieldWidth; ++j)
|
||||||
|
offset = (offset << 8) | stream.getByte();
|
||||||
|
for (j = 0; j < generationFieldWidth; ++j)
|
||||||
|
generation = (generation << 8) | stream.getByte();
|
||||||
|
var entry = new Ref(offset, generation);
|
||||||
|
if (typeFieldWidth > 0) {
|
||||||
|
switch (type) {
|
||||||
|
case 0:
|
||||||
|
entry.free = true;
|
||||||
|
break;
|
||||||
|
case 1:
|
||||||
|
entry.uncompressed = true;
|
||||||
|
break;
|
||||||
|
case 2:
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
error("Invalid XRef entry type");
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (!this.entries[first + i])
|
||||||
|
this.entries[first + i] = entry;
|
||||||
|
}
|
||||||
|
range.splice(0, 2);
|
||||||
|
}
|
||||||
|
var prev = streamParameters.get("Prev");
|
||||||
|
if (IsInt(prev))
|
||||||
|
this.readXRef(prev);
|
||||||
|
return streamParameters;
|
||||||
},
|
},
|
||||||
readXRef: function(startXRef) {
|
readXRef: function(startXRef) {
|
||||||
var stream = this.stream;
|
var stream = this.stream;
|
||||||
@ -1330,11 +1468,12 @@ var XRef = (function() {
|
|||||||
|
|
||||||
e = this.getEntry(num);
|
e = this.getEntry(num);
|
||||||
var gen = ref.gen;
|
var gen = ref.gen;
|
||||||
|
var stream, parser;
|
||||||
if (e.uncompressed) {
|
if (e.uncompressed) {
|
||||||
if (e.gen != gen)
|
if (e.gen != gen)
|
||||||
throw("inconsistent generation in XRef");
|
throw("inconsistent generation in XRef");
|
||||||
var stream = this.stream.makeSubStream(e.offset);
|
stream = this.stream.makeSubStream(e.offset);
|
||||||
var parser = new Parser(new Lexer(stream), true, this);
|
parser = new Parser(new Lexer(stream), true, this);
|
||||||
var obj1 = parser.getObj();
|
var obj1 = parser.getObj();
|
||||||
var obj2 = parser.getObj();
|
var obj2 = parser.getObj();
|
||||||
var obj3 = parser.getObj();
|
var obj3 = parser.getObj();
|
||||||
@ -1358,7 +1497,40 @@ var XRef = (function() {
|
|||||||
this.cache[num] = e;
|
this.cache[num] = e;
|
||||||
return e;
|
return e;
|
||||||
}
|
}
|
||||||
error("compressed entry");
|
|
||||||
|
// compressed entry
|
||||||
|
stream = this.fetch(new Ref(e.offset, 0));
|
||||||
|
if (!IsStream(stream))
|
||||||
|
error("bad ObjStm stream");
|
||||||
|
var first = stream.parameters.get("First");
|
||||||
|
var n = stream.parameters.get("N");
|
||||||
|
if (!IsInt(first) || !IsInt(n)) {
|
||||||
|
error("invalid first and n parameters for ObjStm stream");
|
||||||
|
}
|
||||||
|
parser = new Parser(new Lexer(stream), false);
|
||||||
|
var i, entries = [], nums = [];
|
||||||
|
// read the object numbers to populate cache
|
||||||
|
for (i = 0; i < n; ++i) {
|
||||||
|
var num = parser.getObj();
|
||||||
|
if (!IsInt(num)) {
|
||||||
|
error("invalid object number in the ObjStm stream");
|
||||||
|
}
|
||||||
|
nums.push(num);
|
||||||
|
var offset = parser.getObj();
|
||||||
|
if (!IsInt(offset)) {
|
||||||
|
error("invalid object offset in the ObjStm stream");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// read stream objects for cache
|
||||||
|
for (i = 0; i < n; ++i) {
|
||||||
|
entries.push(parser.getObj());
|
||||||
|
this.cache[nums[i]] = entries[i];
|
||||||
|
}
|
||||||
|
e = entries[e.gen];
|
||||||
|
if (!e) {
|
||||||
|
error("bad XRef entry for compressed object");
|
||||||
|
}
|
||||||
|
return e;
|
||||||
},
|
},
|
||||||
getCatalogObj: function() {
|
getCatalogObj: function() {
|
||||||
return this.fetch(this.root);
|
return this.fetch(this.root);
|
||||||
@ -1389,20 +1561,40 @@ var Page = (function() {
|
|||||||
: null));
|
: null));
|
||||||
},
|
},
|
||||||
compile: function(gfx, fonts) {
|
compile: function(gfx, fonts) {
|
||||||
if (!this.code) {
|
if (this.code) {
|
||||||
var xref = this.xref;
|
// content was compiled
|
||||||
var content = xref.fetchIfRef(this.content);
|
return;
|
||||||
var resources = xref.fetchIfRef(this.resources);
|
|
||||||
this.code = gfx.compile(content, xref, resources, fonts);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
var xref = this.xref;
|
||||||
|
var content;
|
||||||
|
var resources = xref.fetchIfRef(this.resources);
|
||||||
|
if (!IsArray(this.content)) {
|
||||||
|
// content is not an array, shortcut
|
||||||
|
content = xref.fetchIfRef(this.content);
|
||||||
|
this.code = gfx.compile(content, xref, resources, fonts);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
// the content is an array, compiling all items
|
||||||
|
var i, n = this.content.length, compiledItems = [];
|
||||||
|
for (i = 0; i < n; ++i) {
|
||||||
|
content = xref.fetchIfRef(this.content[i]);
|
||||||
|
compiledItems.push(gfx.compile(content, xref, resources, fonts));
|
||||||
|
}
|
||||||
|
// creating the function that executes all compiled items
|
||||||
|
this.code = function(gfx) {
|
||||||
|
var i, n = compiledItems.length;
|
||||||
|
for (i = 0; i < n; ++i) {
|
||||||
|
compiledItems[i](gfx);
|
||||||
|
}
|
||||||
|
};
|
||||||
},
|
},
|
||||||
display: function(gfx) {
|
display: function(gfx) {
|
||||||
|
assert(this.code instanceof Function, "page content must be compiled first");
|
||||||
var xref = this.xref;
|
var xref = this.xref;
|
||||||
var content = xref.fetchIfRef(this.content);
|
|
||||||
var resources = xref.fetchIfRef(this.resources);
|
var resources = xref.fetchIfRef(this.resources);
|
||||||
var mediaBox = xref.fetchIfRef(this.mediaBox);
|
var mediaBox = xref.fetchIfRef(this.mediaBox);
|
||||||
assertWellFormed(IsStream(content) && IsDict(resources),
|
assertWellFormed(IsDict(resources), "invalid page resources");
|
||||||
"invalid page content or resources");
|
|
||||||
gfx.beginDrawing({ x: mediaBox[0], y: mediaBox[1],
|
gfx.beginDrawing({ x: mediaBox[0], y: mediaBox[1],
|
||||||
width: mediaBox[2] - mediaBox[0],
|
width: mediaBox[2] - mediaBox[0],
|
||||||
height: mediaBox[3] - mediaBox[1] });
|
height: mediaBox[3] - mediaBox[1] });
|
||||||
@ -1574,7 +1766,7 @@ var PDFDoc = (function() {
|
|||||||
},
|
},
|
||||||
getPage: function(n) {
|
getPage: function(n) {
|
||||||
var linearization = this.linearization;
|
var linearization = this.linearization;
|
||||||
assert(!linearization, "linearized page access not implemented");
|
// assert(!linearization, "linearized page access not implemented");
|
||||||
return this.catalog.getPage(n);
|
return this.catalog.getPage(n);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
@ -1593,6 +1785,7 @@ var CanvasExtraState = (function() {
|
|||||||
this.fontSize = 0.0;
|
this.fontSize = 0.0;
|
||||||
this.textMatrix = IDENTITY_MATRIX;
|
this.textMatrix = IDENTITY_MATRIX;
|
||||||
this.leading = 0.0;
|
this.leading = 0.0;
|
||||||
|
this.colorSpace = null;
|
||||||
// Current point (in user coordinates)
|
// Current point (in user coordinates)
|
||||||
this.x = 0.0;
|
this.x = 0.0;
|
||||||
this.y = 0.0;
|
this.y = 0.0;
|
||||||
@ -1887,6 +2080,9 @@ var CanvasGraphics = (function() {
|
|||||||
const NORMAL_CLIP = {};
|
const NORMAL_CLIP = {};
|
||||||
const EO_CLIP = {};
|
const EO_CLIP = {};
|
||||||
|
|
||||||
|
// Used for tiling patterns
|
||||||
|
const PAINT_TYPE_COLORED = 1, PAINT_TYPE_UNCOLORED = 2;
|
||||||
|
|
||||||
constructor.prototype = {
|
constructor.prototype = {
|
||||||
translateFont: function(fontDict, xref, resources) {
|
translateFont: function(fontDict, xref, resources) {
|
||||||
var descriptor = xref.fetch(fontDict.get("FontDescriptor"));
|
var descriptor = xref.fetch(fontDict.get("FontDescriptor"));
|
||||||
@ -1922,6 +2118,7 @@ var CanvasGraphics = (function() {
|
|||||||
|
|
||||||
// Get the font charset if any
|
// Get the font charset if any
|
||||||
var charset = descriptor.get("CharSet");
|
var charset = descriptor.get("CharSet");
|
||||||
|
if (charset)
|
||||||
assertWellFormed(IsString(charset), "invalid charset");
|
assertWellFormed(IsString(charset), "invalid charset");
|
||||||
|
|
||||||
charset = charset.split("/");
|
charset = charset.split("/");
|
||||||
@ -1958,7 +2155,8 @@ var CanvasGraphics = (function() {
|
|||||||
var tokens = [];
|
var tokens = [];
|
||||||
var token = "";
|
var token = "";
|
||||||
|
|
||||||
var cmap = cmapObj.getBytes(cmapObj.length);
|
var buffer = cmapObj.ensureBuffer();
|
||||||
|
var cmap = cmapObj.getBytes(buffer.byteLength);
|
||||||
for (var i =0; i < cmap.length; i++) {
|
for (var i =0; i < cmap.length; i++) {
|
||||||
var byte = cmap[i];
|
var byte = cmap[i];
|
||||||
if (byte == 0x20 || byte == 0x0A || byte == 0x3C || byte == 0x3E) {
|
if (byte == 0x20 || byte == 0x0A || byte == 0x3C || byte == 0x3E) {
|
||||||
@ -2354,6 +2552,10 @@ var CanvasGraphics = (function() {
|
|||||||
},
|
},
|
||||||
setFillColorSpace: function(space) {
|
setFillColorSpace: function(space) {
|
||||||
// TODO real impl
|
// TODO real impl
|
||||||
|
if (space.name === "Pattern")
|
||||||
|
this.current.colorSpace = "Pattern";
|
||||||
|
else
|
||||||
|
this.current.colorSpace = "DeviceRGB";
|
||||||
},
|
},
|
||||||
setStrokeColor: function(/*...*/) {
|
setStrokeColor: function(/*...*/) {
|
||||||
// TODO real impl
|
// TODO real impl
|
||||||
@ -2376,8 +2578,126 @@ var CanvasGraphics = (function() {
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
setFillColorN: function(/*...*/) {
|
setFillColorN: function(/*...*/) {
|
||||||
|
// TODO real impl
|
||||||
|
var colorSpace = this.current.colorSpace;
|
||||||
|
if (!colorSpace) {
|
||||||
|
var stateStack = this.stateStack;
|
||||||
|
var i = stateStack.length - 1;
|
||||||
|
while (!colorSpace && i >= 0) {
|
||||||
|
colorSpace = stateStack[i--].colorSpace;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (this.current.colorSpace == "Pattern") {
|
||||||
|
var patternName = arguments[0];
|
||||||
|
if (IsName(patternName)) {
|
||||||
|
var xref = this.xref;
|
||||||
|
var patternRes = xref.fetchIfRef(this.res.get("Pattern"));
|
||||||
|
if (!patternRes)
|
||||||
|
error("Unable to find pattern resource");
|
||||||
|
|
||||||
|
var pattern = xref.fetchIfRef(patternRes.get(patternName.name));
|
||||||
|
|
||||||
|
const types = [null, this.tilingFill];
|
||||||
|
var typeNum = pattern.dict.get("PatternType");
|
||||||
|
var patternFn = types[typeNum];
|
||||||
|
if (!patternFn)
|
||||||
|
error("Unhandled pattern type");
|
||||||
|
patternFn.call(this, pattern);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
// TODO real impl
|
// TODO real impl
|
||||||
this.setFillColor.apply(this, arguments);
|
this.setFillColor.apply(this, arguments);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
tilingFill: function(pattern) {
|
||||||
|
function applyMatrix(point, m) {
|
||||||
|
var x = point[0] * m[0] + point[1] * m[2] + m[4];
|
||||||
|
var y = point[0] * m[1] + point[1] * m[3] + m[5];
|
||||||
|
return [x,y];
|
||||||
|
};
|
||||||
|
|
||||||
|
function multiply(m, tm) {
|
||||||
|
var a = m[0] * tm[0] + m[1] * tm[2];
|
||||||
|
var b = m[0] * tm[1] + m[1] * tm[3];
|
||||||
|
var c = m[2] * tm[0] + m[3] * tm[2];
|
||||||
|
var d = m[2] * tm[1] + m[3] * tm[3];
|
||||||
|
var e = m[4] * tm[0] + m[5] * tm[2] + tm[4];
|
||||||
|
var f = m[4] * tm[1] + m[5] * tm[3] + tm[5];
|
||||||
|
return [a, b, c, d, e, f]
|
||||||
|
};
|
||||||
|
|
||||||
|
this.save();
|
||||||
|
var dict = pattern.dict;
|
||||||
|
var ctx = this.ctx;
|
||||||
|
|
||||||
|
var paintType = dict.get("PaintType");
|
||||||
|
switch (paintType) {
|
||||||
|
case PAINT_TYPE_COLORED:
|
||||||
|
// should go to default for color space
|
||||||
|
ctx.fillStyle = this.makeCssRgb(1, 1, 1);
|
||||||
|
ctx.strokeStyle = this.makeCssRgb(0, 0, 0);
|
||||||
|
break;
|
||||||
|
case PAINT_TYPE_UNCOLORED:
|
||||||
|
default:
|
||||||
|
error("Unsupported paint type");
|
||||||
|
}
|
||||||
|
|
||||||
|
TODO("TilingType");
|
||||||
|
|
||||||
|
var matrix = dict.get("Matrix") || IDENTITY_MATRIX;
|
||||||
|
|
||||||
|
var bbox = dict.get("BBox");
|
||||||
|
var x0 = bbox[0], y0 = bbox[1], x1 = bbox[2], y1 = bbox[3];
|
||||||
|
|
||||||
|
var xstep = dict.get("XStep");
|
||||||
|
var ystep = dict.get("YStep");
|
||||||
|
|
||||||
|
// top left corner should correspond to the top left of the bbox
|
||||||
|
var topLeft = applyMatrix([x0,y0], matrix);
|
||||||
|
// we want the canvas to be as large as the step size
|
||||||
|
var botRight = applyMatrix([x0 + xstep, y0 + ystep], matrix);
|
||||||
|
|
||||||
|
var tmpCanvas = document.createElement("canvas");
|
||||||
|
tmpCanvas.width = Math.ceil(botRight[0] - topLeft[0]);
|
||||||
|
tmpCanvas.height = Math.ceil(botRight[1] - topLeft[1]);
|
||||||
|
|
||||||
|
// set the new canvas element context as the graphics context
|
||||||
|
var tmpCtx = tmpCanvas.getContext("2d");
|
||||||
|
var savedCtx = ctx;
|
||||||
|
this.ctx = tmpCtx;
|
||||||
|
|
||||||
|
// normalize transform matrix so each step
|
||||||
|
// takes up the entire tmpCanvas (need to remove white borders)
|
||||||
|
if (matrix[1] === 0 && matrix[2] === 0) {
|
||||||
|
matrix[0] = tmpCanvas.width / xstep;
|
||||||
|
matrix[3] = tmpCanvas.height / ystep;
|
||||||
|
topLeft = applyMatrix([x0,y0], matrix);
|
||||||
|
}
|
||||||
|
|
||||||
|
// move the top left corner of bounding box to [0,0]
|
||||||
|
matrix = multiply(matrix, [1, 0, 0, 1, -topLeft[0], -topLeft[1]]);
|
||||||
|
|
||||||
|
this.transform.apply(this, matrix);
|
||||||
|
|
||||||
|
if (bbox && IsArray(bbox) && 4 == bbox.length) {
|
||||||
|
this.rectangle.apply(this, bbox);
|
||||||
|
this.clip();
|
||||||
|
this.endPath();
|
||||||
|
}
|
||||||
|
|
||||||
|
var xref = this.xref;
|
||||||
|
var res = xref.fetchIfRef(dict.get("Resources"));
|
||||||
|
if (!pattern.code)
|
||||||
|
pattern.code = this.compile(pattern, xref, res, []);
|
||||||
|
this.execute(pattern.code, xref, res);
|
||||||
|
|
||||||
|
this.ctx = savedCtx;
|
||||||
|
this.restore();
|
||||||
|
|
||||||
|
TODO("Inverse pattern is painted");
|
||||||
|
var pattern = this.ctx.createPattern(tmpCanvas, "repeat");
|
||||||
|
this.ctx.fillStyle = pattern;
|
||||||
},
|
},
|
||||||
setStrokeGray: function(gray) {
|
setStrokeGray: function(gray) {
|
||||||
this.setStrokeRGBColor(gray, gray, gray);
|
this.setStrokeRGBColor(gray, gray, gray);
|
||||||
@ -2465,6 +2785,10 @@ var CanvasGraphics = (function() {
|
|||||||
var fn = new PDFFunction(this.xref, fnObj);
|
var fn = new PDFFunction(this.xref, fnObj);
|
||||||
|
|
||||||
var gradient = this.ctx.createLinearGradient(x0, y0, x1, y1);
|
var gradient = this.ctx.createLinearGradient(x0, y0, x1, y1);
|
||||||
|
|
||||||
|
// 10 samples seems good enough for now, but probably won't work
|
||||||
|
// if there are sharp color changes. Ideally, we would implement
|
||||||
|
// the spec faithfully and add lossless optimizations.
|
||||||
var step = (t1 - t0) / 10;
|
var step = (t1 - t0) / 10;
|
||||||
|
|
||||||
for (var i = t0; i <= t1; i += step) {
|
for (var i = t0; i <= t1; i += step) {
|
||||||
@ -2477,6 +2801,8 @@ var CanvasGraphics = (function() {
|
|||||||
// HACK to draw the gradient onto an infinite rectangle.
|
// HACK to draw the gradient onto an infinite rectangle.
|
||||||
// PDF gradients are drawn across the entire image while
|
// PDF gradients are drawn across the entire image while
|
||||||
// Canvas only allows gradients to be drawn in a rectangle
|
// Canvas only allows gradients to be drawn in a rectangle
|
||||||
|
// The following bug should allow us to remove this.
|
||||||
|
// https://bugzilla.mozilla.org/show_bug.cgi?id=664884
|
||||||
this.ctx.fillRect(-1e10, -1e10, 2e10, 2e10);
|
this.ctx.fillRect(-1e10, -1e10, 2e10, 2e10);
|
||||||
},
|
},
|
||||||
|
|
||||||
|
175
test.py
Normal file
175
test.py
Normal file
@ -0,0 +1,175 @@
|
|||||||
|
import json, os, sys, subprocess
|
||||||
|
from BaseHTTPServer import BaseHTTPRequestHandler, HTTPServer
|
||||||
|
|
||||||
|
ANAL = True
|
||||||
|
VERBOSE = False
|
||||||
|
|
||||||
|
MIMEs = {
|
||||||
|
'.css': 'text/css',
|
||||||
|
'.html': 'text/html',
|
||||||
|
'.js': 'application/json',
|
||||||
|
'.json': 'application/json',
|
||||||
|
'.pdf': 'application/pdf',
|
||||||
|
'.xhtml': 'application/xhtml+xml',
|
||||||
|
}
|
||||||
|
|
||||||
|
class State:
|
||||||
|
browsers = [ ]
|
||||||
|
manifest = { }
|
||||||
|
taskResults = { }
|
||||||
|
remaining = 0
|
||||||
|
results = { }
|
||||||
|
done = False
|
||||||
|
|
||||||
|
class Result:
|
||||||
|
def __init__(self, snapshot, failure):
|
||||||
|
self.snapshot = snapshot
|
||||||
|
self.failure = failure
|
||||||
|
|
||||||
|
|
||||||
|
class PDFTestHandler(BaseHTTPRequestHandler):
|
||||||
|
# Disable annoying noise by default
|
||||||
|
def log_request(code=0, size=0):
|
||||||
|
if VERBOSE:
|
||||||
|
BaseHTTPRequestHandler.log_request(code, size)
|
||||||
|
|
||||||
|
def do_GET(self):
|
||||||
|
cwd = os.getcwd()
|
||||||
|
path = os.path.abspath(os.path.realpath(cwd + os.sep + self.path))
|
||||||
|
cwd = os.path.abspath(cwd)
|
||||||
|
prefix = os.path.commonprefix(( path, cwd ))
|
||||||
|
_, ext = os.path.splitext(path)
|
||||||
|
|
||||||
|
if not (prefix == cwd
|
||||||
|
and os.path.isfile(path)
|
||||||
|
and ext in MIMEs):
|
||||||
|
self.send_error(404)
|
||||||
|
return
|
||||||
|
|
||||||
|
if 'Range' in self.headers:
|
||||||
|
# TODO for fetch-as-you-go
|
||||||
|
self.send_error(501)
|
||||||
|
return
|
||||||
|
|
||||||
|
self.send_response(200)
|
||||||
|
self.send_header("Content-Type", MIMEs[ext])
|
||||||
|
self.end_headers()
|
||||||
|
|
||||||
|
# Sigh, os.sendfile() plz
|
||||||
|
f = open(path)
|
||||||
|
self.wfile.write(f.read())
|
||||||
|
f.close()
|
||||||
|
|
||||||
|
|
||||||
|
def do_POST(self):
|
||||||
|
numBytes = int(self.headers['Content-Length'])
|
||||||
|
|
||||||
|
self.send_response(200)
|
||||||
|
self.send_header('Content-Type', 'text/plain')
|
||||||
|
self.end_headers()
|
||||||
|
|
||||||
|
result = json.loads(self.rfile.read(numBytes))
|
||||||
|
browser = 'firefox4'
|
||||||
|
id, failure, round, page, snapshot = result['id'], result['failure'], result['round'], result['page'], result['snapshot']
|
||||||
|
taskResults = State.taskResults[browser][id]
|
||||||
|
taskResults[round][page - 1] = Result(snapshot, failure)
|
||||||
|
|
||||||
|
if result['taskDone']:
|
||||||
|
check(State.manifest[id], taskResults, browser)
|
||||||
|
State.remaining -= 1
|
||||||
|
|
||||||
|
State.done = (0 == State.remaining)
|
||||||
|
|
||||||
|
|
||||||
|
def set_up():
|
||||||
|
# Only serve files from a pdf.js clone
|
||||||
|
assert not ANAL or os.path.isfile('pdf.js') and os.path.isdir('.git')
|
||||||
|
|
||||||
|
testBrowsers = [ b for b in
|
||||||
|
( 'firefox4', )
|
||||||
|
#'chrome12', 'chrome13', 'firefox5', 'firefox6','opera11' ):
|
||||||
|
if os.access(b, os.R_OK | os.X_OK) ]
|
||||||
|
|
||||||
|
mf = open('test_manifest.json')
|
||||||
|
manifestList = json.load(mf)
|
||||||
|
mf.close()
|
||||||
|
|
||||||
|
for b in testBrowsers:
|
||||||
|
State.taskResults[b] = { }
|
||||||
|
for item in manifestList:
|
||||||
|
id, rounds = item['id'], int(item['rounds'])
|
||||||
|
State.manifest[id] = item
|
||||||
|
taskResults = [ ]
|
||||||
|
for r in xrange(rounds):
|
||||||
|
taskResults.append([ None ] * 100)
|
||||||
|
State.taskResults[b][id] = taskResults
|
||||||
|
|
||||||
|
State.remaining = len(manifestList)
|
||||||
|
|
||||||
|
for b in testBrowsers:
|
||||||
|
print 'Launching', b
|
||||||
|
subprocess.Popen(( os.path.abspath(os.path.realpath(b)),
|
||||||
|
'http://localhost:8080/test_slave.html' ))
|
||||||
|
|
||||||
|
|
||||||
|
def check(task, results, browser):
|
||||||
|
failed = False
|
||||||
|
for r in xrange(len(results)):
|
||||||
|
pageResults = results[r]
|
||||||
|
for p in xrange(len(pageResults)):
|
||||||
|
pageResult = pageResults[p]
|
||||||
|
if pageResult is None:
|
||||||
|
continue
|
||||||
|
failure = pageResult.failure
|
||||||
|
if failure:
|
||||||
|
failed = True
|
||||||
|
print 'TEST-UNEXPECTED-FAIL | test failed', task['id'], '| in', browser, '| page', p + 1, 'round', r, '|', failure
|
||||||
|
|
||||||
|
if failed:
|
||||||
|
return
|
||||||
|
|
||||||
|
kind = task['type']
|
||||||
|
if '==' == kind:
|
||||||
|
checkEq(task, results, browser)
|
||||||
|
elif 'fbf' == kind:
|
||||||
|
checkFBF(task, results, browser)
|
||||||
|
elif 'load' == kind:
|
||||||
|
checkLoad(task, results, browser)
|
||||||
|
else:
|
||||||
|
assert 0 and 'Unknown test type'
|
||||||
|
|
||||||
|
|
||||||
|
def checkEq(task, results, browser):
|
||||||
|
print ' !!! [TODO: == tests] !!!'
|
||||||
|
print 'TEST-PASS | == test', task['id'], '| in', browser
|
||||||
|
|
||||||
|
|
||||||
|
printed = [False]
|
||||||
|
|
||||||
|
def checkFBF(task, results, browser):
|
||||||
|
round0, round1 = results[0], results[1]
|
||||||
|
assert len(round0) == len(round1)
|
||||||
|
|
||||||
|
for page in xrange(len(round1)):
|
||||||
|
r0Page, r1Page = round0[page], round1[page]
|
||||||
|
if r0Page is None:
|
||||||
|
break
|
||||||
|
if r0Page.snapshot != r1Page.snapshot:
|
||||||
|
print 'TEST-UNEXPECTED-FAIL | forward-back-forward test', task['id'], '| in', browser, '| first rendering of page', page + 1, '!= second'
|
||||||
|
print 'TEST-PASS | forward-back-forward test', task['id'], '| in', browser
|
||||||
|
|
||||||
|
|
||||||
|
def checkLoad(task, results, browser):
|
||||||
|
# Load just checks for absence of failure, so if we got here the
|
||||||
|
# test has passed
|
||||||
|
print 'TEST-PASS | load test', task['id'], '| in', browser
|
||||||
|
|
||||||
|
|
||||||
|
def main():
|
||||||
|
set_up()
|
||||||
|
server = HTTPServer(('127.0.0.1', 8080), PDFTestHandler)
|
||||||
|
while not State.done:
|
||||||
|
server.handle_request()
|
||||||
|
|
||||||
|
if __name__ == '__main__':
|
||||||
|
main()
|
17
test_manifest.json
Normal file
17
test_manifest.json
Normal file
@ -0,0 +1,17 @@
|
|||||||
|
[
|
||||||
|
{ "id": "tracemonkey-==",
|
||||||
|
"file": "tests/tracemonkey.pdf",
|
||||||
|
"rounds": 1,
|
||||||
|
"type": "=="
|
||||||
|
},
|
||||||
|
{ "id": "tracemonkey-fbf",
|
||||||
|
"file": "tests/tracemonkey.pdf",
|
||||||
|
"rounds": 2,
|
||||||
|
"type": "fbf"
|
||||||
|
},
|
||||||
|
{ "id": "html5-canvas-cheat-sheet-load",
|
||||||
|
"file": "tests/canvas.pdf",
|
||||||
|
"rounds": 1,
|
||||||
|
"type": "load"
|
||||||
|
}
|
||||||
|
]
|
149
test_slave.html
Normal file
149
test_slave.html
Normal file
@ -0,0 +1,149 @@
|
|||||||
|
<html>
|
||||||
|
<head>
|
||||||
|
<title>pdf.js test slave</title>
|
||||||
|
<script type="text/javascript" src="pdf.js"></script>
|
||||||
|
<script type="text/javascript" src="fonts.js"></script>
|
||||||
|
<script type="text/javascript" src="glyphlist.js"></script>
|
||||||
|
<script type="application/javascript">
|
||||||
|
var canvas, currentTask, currentTaskIdx, failure, manifest, pdfDoc, stdout;
|
||||||
|
|
||||||
|
function load() {
|
||||||
|
canvas = document.createElement("canvas");
|
||||||
|
// 8.5x11in @ 100% ... XXX need something better here
|
||||||
|
canvas.width = 816;
|
||||||
|
canvas.height = 1056;
|
||||||
|
canvas.mozOpaque = true;
|
||||||
|
stdout = document.getElementById("stdout");
|
||||||
|
|
||||||
|
log("Fetching manifest ...");
|
||||||
|
|
||||||
|
var r = new XMLHttpRequest();
|
||||||
|
r.open("GET", "test_manifest.json", false);
|
||||||
|
r.onreadystatechange = function(e) {
|
||||||
|
if (r.readyState == 4) {
|
||||||
|
log("done\n");
|
||||||
|
|
||||||
|
manifest = JSON.parse(r.responseText);
|
||||||
|
currentTaskIdx = 0, nextTask();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
r.send(null);
|
||||||
|
}
|
||||||
|
|
||||||
|
function nextTask() {
|
||||||
|
if (currentTaskIdx == manifest.length) {
|
||||||
|
return done();
|
||||||
|
}
|
||||||
|
currentTask = manifest[currentTaskIdx];
|
||||||
|
currentTask.round = 0;
|
||||||
|
|
||||||
|
log("Loading file "+ currentTask.file +"\n");
|
||||||
|
|
||||||
|
var r = new XMLHttpRequest();
|
||||||
|
r.open("GET", currentTask.file);
|
||||||
|
r.mozResponseType = r.responseType = "arraybuffer";
|
||||||
|
r.onreadystatechange = function() {
|
||||||
|
if (r.readyState == 4) {
|
||||||
|
var data = r.mozResponseArrayBuffer || r.mozResponse ||
|
||||||
|
r.responseArrayBuffer || r.response;
|
||||||
|
pdfDoc = new PDFDoc(new Stream(data));
|
||||||
|
currentTask.pageNum = 1, nextPage();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
r.send(null);
|
||||||
|
}
|
||||||
|
|
||||||
|
function nextPage() {
|
||||||
|
if (currentTask.pageNum > pdfDoc.numPages) {
|
||||||
|
if (++currentTask.round < currentTask.rounds) {
|
||||||
|
log(" Round "+ (1 + currentTask.round) +"\n");
|
||||||
|
currentTask.pageNum = 1;
|
||||||
|
} else {
|
||||||
|
++currentTaskIdx, nextTask();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
failure = '';
|
||||||
|
log(" drawing page "+ currentTask.pageNum +"...");
|
||||||
|
|
||||||
|
currentPage = pdfDoc.getPage(currentTask.pageNum);
|
||||||
|
|
||||||
|
var ctx = canvas.getContext("2d");
|
||||||
|
clear(ctx);
|
||||||
|
|
||||||
|
var fonts = [];
|
||||||
|
var gfx = new CanvasGraphics(ctx);
|
||||||
|
try {
|
||||||
|
currentPage.compile(gfx, fonts);
|
||||||
|
} catch(e) {
|
||||||
|
failure = 'compile: '+ e.toString();
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO load fonts
|
||||||
|
setTimeout(function() {
|
||||||
|
if (!failure) {
|
||||||
|
try {
|
||||||
|
currentPage.display(gfx);
|
||||||
|
} catch(e) {
|
||||||
|
failure = 'render: '+ e.toString();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
currentTask.taskDone = (currentTask.pageNum == pdfDoc.numPages
|
||||||
|
&& (1 + currentTask.round) == currentTask.rounds);
|
||||||
|
sendTaskResult(canvas.toDataURL("image/png"));
|
||||||
|
log("done"+ (failure ? " (failed!)" : "") +"\n");
|
||||||
|
|
||||||
|
++currentTask.pageNum, nextPage();
|
||||||
|
},
|
||||||
|
0
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
function done() {
|
||||||
|
log("Done!\n");
|
||||||
|
setTimeout(function() {
|
||||||
|
document.body.innerHTML = "Tests are finished. <h1>CLOSE ME!</h1>";
|
||||||
|
window.close();
|
||||||
|
},
|
||||||
|
100
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
function sendTaskResult(snapshot) {
|
||||||
|
var result = { id: currentTask.id,
|
||||||
|
taskDone: currentTask.taskDone,
|
||||||
|
failure: failure,
|
||||||
|
file: currentTask.file,
|
||||||
|
round: currentTask.round,
|
||||||
|
page: currentTask.pageNum,
|
||||||
|
snapshot: snapshot };
|
||||||
|
|
||||||
|
var r = new XMLHttpRequest();
|
||||||
|
// (The POST URI is ignored atm.)
|
||||||
|
r.open("POST", "submit_task_results", false);
|
||||||
|
r.setRequestHeader("Content-Type", "application/json");
|
||||||
|
// XXX async
|
||||||
|
r.send(JSON.stringify(result));
|
||||||
|
}
|
||||||
|
|
||||||
|
function clear(ctx) {
|
||||||
|
var ctx = canvas.getContext("2d");
|
||||||
|
ctx.save();
|
||||||
|
ctx.fillStyle = "rgb(255, 255, 255)";
|
||||||
|
ctx.fillRect(0, 0, canvas.width, canvas.height);
|
||||||
|
ctx.restore();
|
||||||
|
}
|
||||||
|
|
||||||
|
function log(str) {
|
||||||
|
stdout.innerHTML += str;
|
||||||
|
window.scrollTo(0, stdout.getBoundingClientRect().bottom);
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
</head>
|
||||||
|
|
||||||
|
<body onload="load();">
|
||||||
|
<pre id="stdout"></pre>
|
||||||
|
</body>
|
||||||
|
|
||||||
|
</html>
|
BIN
tests/canvas.pdf
Normal file
BIN
tests/canvas.pdf
Normal file
Binary file not shown.
BIN
tests/tracemonkey.pdf
Normal file
BIN
tests/tracemonkey.pdf
Normal file
Binary file not shown.
@ -1,3 +1,8 @@
|
|||||||
|
/* -*- 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 CFFStrings = [
|
var CFFStrings = [
|
||||||
".notdef",
|
".notdef",
|
||||||
"space",
|
"space",
|
||||||
|
@ -1,3 +1,8 @@
|
|||||||
|
/* -*- Mode: Java; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- /
|
||||||
|
/* vim: set shiftwidth=2 tabstop=2 autoindent cindent expandtab: */
|
||||||
|
|
||||||
|
"use strict";
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The Type2 reader code below is only used for debugging purpose since Type2
|
* The Type2 reader code below is only used for debugging purpose since Type2
|
||||||
* is only a CharString format and is never used directly as a Font file.
|
* is only a CharString format and is never used directly as a Font file.
|
||||||
|
Loading…
Reference in New Issue
Block a user