Merge with master

This commit is contained in:
Vivien Nicolas 2011-06-21 02:35:14 +02:00
commit d923953ee3
18 changed files with 2004 additions and 700 deletions

View File

@ -5,6 +5,8 @@
Chris G Jones <cjones@mozilla.com>
Shaon Barman <shaon.barman@gmail.com>
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
copy of this software and associated documentation files (the "Software"),
@ -22,4 +24,4 @@
THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
DEALINGS IN THE SOFTWARE.
DEALINGS IN THE SOFTWARE.

1140
fonts.js

File diff suppressed because it is too large Load Diff

View File

@ -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";
var GlyphsUnicode = {
@ -1502,27 +1505,27 @@ var GlyphsUnicode = {
dalet: 0x05D3,
daletdagesh: 0xFB33,
daletdageshhebrew: 0xFB33,
dalethatafpatah: "05D3 05B2",
dalethatafpatahhebrew: "05D3 05B2",
dalethatafsegol: "05D3 05B1",
dalethatafsegolhebrew: "05D3 05B1",
dalethatafpatah: 0x05D305B2,
dalethatafpatahhebrew: 0x05D305B2,
dalethatafsegol: 0x05D305B1,
dalethatafsegolhebrew: 0x05D305B1,
dalethebrew: 0x05D3,
dalethiriq: "05D3 05B4",
dalethiriqhebrew: "05D3 05B4",
daletholam: "05D3 05B9",
daletholamhebrew: "05D3 05B9",
daletpatah: "05D3 05B7",
daletpatahhebrew: "05D3 05B7",
daletqamats: "05D3 05B8",
daletqamatshebrew: "05D3 05B8",
daletqubuts: "05D3 05BB",
daletqubutshebrew: "05D3 05BB",
daletsegol: "05D3 05B6",
daletsegolhebrew: "05D3 05B6",
daletsheva: "05D3 05B0",
daletshevahebrew: "05D3 05B0",
dalettsere: "05D3 05B5",
dalettserehebrew: "05D3 05B5",
dalethiriq: 0x05D305B4,
dalethiriqhebrew: 0x05D305B4,
daletholam: 0x05D305B9,
daletholamhebrew: 0x05D305B9,
daletpatah: 0x05D305B7,
daletpatahhebrew: 0x05D305B7,
daletqamats: 0x05D305B8,
daletqamatshebrew: 0x05D305B8,
daletqubuts: 0x05D305BB,
daletqubutshebrew: 0x05D305BB,
daletsegol: 0x05D305B6,
daletsegolhebrew: 0x05D305B6,
daletsheva: 0x05D305B0,
daletshevahebrew: 0x05D305B0,
dalettsere: 0x05D305B5,
dalettserehebrew: 0x05D305B5,
dalfinalarabic: 0xFEAA,
dammaarabic: 0x064F,
dammalowarabic: 0x064F,
@ -1839,10 +1842,10 @@ var GlyphsUnicode = {
finalkafdagesh: 0xFB3A,
finalkafdageshhebrew: 0xFB3A,
finalkafhebrew: 0x05DA,
finalkafqamats: "05DA 05B8",
finalkafqamatshebrew: "05DA 05B8",
finalkafsheva: "05DA 05B0",
finalkafshevahebrew: "05DA 05B0",
finalkafqamats: 0x05DA05B8,
finalkafqamatshebrew: 0x05DA05B8,
finalkafsheva: 0x05DA05B0,
finalkafshevahebrew: 0x05DA05B0,
finalmem: 0x05DD,
finalmemhebrew: 0x05DD,
finalnun: 0x05DF,
@ -2031,14 +2034,14 @@ var GlyphsUnicode = {
hakatakanahalfwidth: 0xFF8A,
halantgurmukhi: 0x0A4D,
hamzaarabic: 0x0621,
hamzadammaarabic: "0621 064F",
hamzadammatanarabic: "0621 064C",
hamzafathaarabic: "0621 064E",
hamzafathatanarabic: "0621 064B",
hamzadammaarabic: 0x0621064F,
hamzadammatanarabic: 0x0621064C,
hamzafathaarabic: 0x0621064E,
hamzafathatanarabic: 0x0621064B,
hamzalowarabic: 0x0621,
hamzalowkasraarabic: "0621 0650",
hamzalowkasratanarabic: "0621 064D",
hamzasukunarabic: "0621 0652",
hamzalowkasraarabic: 0x06210650,
hamzalowkasratanarabic: 0x0621064D,
hamzasukunarabic: 0x06210652,
hangulfiller: 0x3164,
hardsigncyrillic: 0x044A,
harpoonleftbarbup: 0x21BC,
@ -2470,10 +2473,10 @@ var GlyphsUnicode = {
lameddagesh: 0xFB3C,
lameddageshhebrew: 0xFB3C,
lamedhebrew: 0x05DC,
lamedholam: "05DC 05B9",
lamedholam: 0x05DC05B9,
lamedholamdagesh: "05DC 05B9 05BC",
lamedholamdageshhebrew: "05DC 05B9 05BC",
lamedholamhebrew: "05DC 05B9",
lamedholamhebrew: 0x05DC05B9,
lamfinalarabic: 0xFEDE,
lamhahinitialarabic: 0xFCCA,
laminitialarabic: 0xFEDF,
@ -2781,7 +2784,7 @@ var GlyphsUnicode = {
noonfinalarabic: 0xFEE6,
noonghunnaarabic: 0x06BA,
noonghunnafinalarabic: 0xFB9F,
noonhehinitialarabic: "FEE7 FEEC",
noonhehinitialarabic: 0xFEE7FEEC,
nooninitialarabic: 0xFEE7,
noonjeeminitialarabic: 0xFCD2,
noonjeemisolatedarabic: 0xFC4B,
@ -3153,27 +3156,27 @@ var GlyphsUnicode = {
qof: 0x05E7,
qofdagesh: 0xFB47,
qofdageshhebrew: 0xFB47,
qofhatafpatah: "05E7 05B2",
qofhatafpatahhebrew: "05E7 05B2",
qofhatafsegol: "05E7 05B1",
qofhatafsegolhebrew: "05E7 05B1",
qofhatafpatah: 0x05E705B2,
qofhatafpatahhebrew: 0x05E705B2,
qofhatafsegol: 0x05E705B1,
qofhatafsegolhebrew: 0x05E705B1,
qofhebrew: 0x05E7,
qofhiriq: "05E7 05B4",
qofhiriqhebrew: "05E7 05B4",
qofholam: "05E7 05B9",
qofholamhebrew: "05E7 05B9",
qofpatah: "05E7 05B7",
qofpatahhebrew: "05E7 05B7",
qofqamats: "05E7 05B8",
qofqamatshebrew: "05E7 05B8",
qofqubuts: "05E7 05BB",
qofqubutshebrew: "05E7 05BB",
qofsegol: "05E7 05B6",
qofsegolhebrew: "05E7 05B6",
qofsheva: "05E7 05B0",
qofshevahebrew: "05E7 05B0",
qoftsere: "05E7 05B5",
qoftserehebrew: "05E7 05B5",
qofhiriq: 0x05E705B4,
qofhiriqhebrew: 0x05E705B4,
qofholam: 0x05E705B9,
qofholamhebrew: 0x05E705B9,
qofpatah: 0x05E705B7,
qofpatahhebrew: 0x05E705B7,
qofqamats: 0x05E705B8,
qofqamatshebrew: 0x05E705B8,
qofqubuts: 0x05E705BB,
qofqubutshebrew: 0x05E705BB,
qofsegol: 0x05E705B6,
qofsegolhebrew: 0x05E705B6,
qofsheva: 0x05E705B0,
qofshevahebrew: 0x05E705B0,
qoftsere: 0x05E705B5,
qoftserehebrew: 0x05E705B5,
qparen: 0x24AC,
quarternote: 0x2669,
qubuts: 0x05BB,
@ -3252,27 +3255,27 @@ var GlyphsUnicode = {
rekatakanahalfwidth: 0xFF9A,
resh: 0x05E8,
reshdageshhebrew: 0xFB48,
reshhatafpatah: "05E8 05B2",
reshhatafpatahhebrew: "05E8 05B2",
reshhatafsegol: "05E8 05B1",
reshhatafsegolhebrew: "05E8 05B1",
reshhatafpatah: 0x05E805B2,
reshhatafpatahhebrew: 0x05E805B2,
reshhatafsegol: 0x05E805B1,
reshhatafsegolhebrew: 0x05E805B1,
reshhebrew: 0x05E8,
reshhiriq: "05E8 05B4",
reshhiriqhebrew: "05E8 05B4",
reshholam: "05E8 05B9",
reshholamhebrew: "05E8 05B9",
reshpatah: "05E8 05B7",
reshpatahhebrew: "05E8 05B7",
reshqamats: "05E8 05B8",
reshqamatshebrew: "05E8 05B8",
reshqubuts: "05E8 05BB",
reshqubutshebrew: "05E8 05BB",
reshsegol: "05E8 05B6",
reshsegolhebrew: "05E8 05B6",
reshsheva: "05E8 05B0",
reshshevahebrew: "05E8 05B0",
reshtsere: "05E8 05B5",
reshtserehebrew: "05E8 05B5",
reshhiriq: 0x05E805B4,
reshhiriqhebrew: 0x05E805B4,
reshholam: 0x05E805B9,
reshholamhebrew: 0x05E805B9,
reshpatah: 0x05E805B7,
reshpatahhebrew: 0x05E805B7,
reshqamats: 0x05E805B8,
reshqamatshebrew: 0x05E805B8,
reshqubuts: 0x05E805BB,
reshqubutshebrew: 0x05E805BB,
reshsegol: 0x05E805B6,
reshsegolhebrew: 0x05E805B6,
reshsheva: 0x05E805B0,
reshshevahebrew: 0x05E805B0,
reshtsere: 0x05E805B5,
reshtserehebrew: 0x05E805B5,
reversedtilde: 0x223D,
reviahebrew: 0x0597,
reviamugrashhebrew: 0x0597,
@ -3471,7 +3474,7 @@ var GlyphsUnicode = {
shaddadammaarabic: 0xFC61,
shaddadammatanarabic: 0xFC5E,
shaddafathaarabic: 0xFC60,
shaddafathatanarabic: "0651 064B",
shaddafathatanarabic: 0x0651064B,
shaddakasraarabic: 0xFC62,
shaddakasratanarabic: 0xFC5F,
shade: 0x2592,
@ -3668,7 +3671,7 @@ var GlyphsUnicode = {
tchehfinalarabic: 0xFB7B,
tchehinitialarabic: 0xFB7C,
tchehmedialarabic: 0xFB7D,
tchehmeeminitialarabic: "FB7C FEE4",
tchehmeeminitialarabic: 0xFB7CFEE4,
tcircle: 0x24E3,
tcircumflexbelow: 0x1E71,
tcommaaccent: 0x0163,

BIN
images/buttons.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.7 KiB

BIN
images/combobox.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.9 KiB

Binary file not shown.

Binary file not shown.

184
multi-page-viewer.css Normal file
View 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
View 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
View 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;
}
};
};

424
pdf.js
View File

@ -50,7 +50,7 @@ function shadow(obj, prop, value) {
var Stream = (function() {
function constructor(arrayBuffer, start, length, dict) {
this.bytes = new Uint8Array(arrayBuffer);
this.bytes = Uint8Array(arrayBuffer);
this.start = start || 0;
this.pos = this.start;
this.end = (start + length) || this.bytes.byteLength;
@ -115,7 +115,7 @@ var Stream = (function() {
var StringStream = (function() {
function constructor(str) {
var length = str.length;
var bytes = new Uint8Array(length);
var bytes = Uint8Array(length);
for (var n = 0; n < length; ++n)
bytes[n] = str.charCodeAt(n);
Stream.call(this, bytes);
@ -127,11 +127,11 @@ var StringStream = (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
]);
const lengthDecode = new Uint32Array([
const lengthDecode = Uint32Array([
0x00003, 0x00004, 0x00005, 0x00006, 0x00007, 0x00008, 0x00009,
0x0000a, 0x1000b, 0x1000d, 0x1000f, 0x10011, 0x20013, 0x20017,
0x2001b, 0x2001f, 0x30023, 0x3002b, 0x30033, 0x3003b, 0x40043,
@ -139,7 +139,7 @@ var FlateStream = (function() {
0x00102, 0x00102, 0x00102
]);
const distDecode = new Uint32Array([
const distDecode = Uint32Array([
0x00001, 0x00002, 0x00003, 0x00004, 0x10005, 0x10007, 0x20009,
0x2000d, 0x30011, 0x30019, 0x40021, 0x40031, 0x50041, 0x50061,
0x60081, 0x600c1, 0x70101, 0x70181, 0x80201, 0x80301, 0x90401,
@ -147,7 +147,7 @@ var FlateStream = (function() {
0xd4001, 0xd6001
]);
const fixedLitCodeTab = [new Uint32Array([
const fixedLitCodeTab = [Uint32Array([
0x70100, 0x80050, 0x80010, 0x80118, 0x70110, 0x80070, 0x80030,
0x900c0, 0x70108, 0x80060, 0x80020, 0x900a0, 0x80000, 0x80080,
0x80040, 0x900e0, 0x70104, 0x80058, 0x80018, 0x90090, 0x70114,
@ -224,7 +224,7 @@ var FlateStream = (function() {
0x900ff
]), 9];
const fixedDistCodeTab = [new Uint32Array([
const fixedDistCodeTab = [Uint32Array([
0x50000, 0x50010, 0x50008, 0x50018, 0x50004, 0x50014, 0x5000c,
0x5001c, 0x50002, 0x50012, 0x5000a, 0x5001a, 0x50006, 0x50016,
0x5000e, 0x00000, 0x50001, 0x50011, 0x50009, 0x50019, 0x50005,
@ -300,7 +300,7 @@ var FlateStream = (function() {
var size = 512;
while (size < requested)
size <<= 1;
var buffer2 = new Uint8Array(size);
var buffer2 = Uint8Array(size);
for (var i = 0; i < current; ++i)
buffer2[i] = buffer[i];
return this.buffer = buffer2;
@ -308,7 +308,7 @@ var FlateStream = (function() {
getByte: function() {
var bufferLength = this.bufferLength;
var pos = this.pos;
if (bufferLength == pos) {
if (bufferLength <= pos) {
if (this.eof)
return;
this.readBlock();
@ -333,7 +333,7 @@ var FlateStream = (function() {
lookChar: function() {
var bufferLength = this.bufferLength;
var pos = this.pos;
if (bufferLength == pos) {
if (bufferLength <= pos) {
if (this.eof)
return;
this.readBlock();
@ -365,7 +365,7 @@ var FlateStream = (function() {
// build the table
var size = 1 << maxLen;
var codes = new Uint32Array(size);
var codes = Uint32Array(size);
for (var len = 1, code = 0, skip = 2;
len <= maxLen;
++len, code <<= 1, skip <<= 1) {
@ -391,6 +391,12 @@ var FlateStream = (function() {
return [codes, maxLen];
},
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;
// read block header
@ -434,11 +440,6 @@ var FlateStream = (function() {
litCodeTable = fixedLitCodeTab;
distCodeTable = fixedDistCodeTab;
} 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 numDistCodes = this.getBits(5) + 1;
var numCodeLenCodes = this.getBits(4) + 4;
@ -508,9 +509,97 @@ var FlateStream = (function() {
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() {
function constructor(str, fileKey, encAlgorithm, keyLength) {
// TODO
TODO("decrypt stream is not implemented");
}
constructor.prototype = Stream.prototype;
@ -727,7 +816,7 @@ var Lexer = (function() {
var done = false;
var str = "";
var stream = this.stream;
var ch = null;
var ch;
do {
switch (ch = stream.getChar()) {
case undefined:
@ -1088,7 +1177,9 @@ var Parser = (function() {
this.encAlgorithm,
this.keyLength);
}
return this.filter(stream, dict);
stream = this.filter(stream, dict);
stream.parameters = dict;
return stream;
},
filter: function(stream, dict) {
var filter = dict.get2("Filter", "F");
@ -1113,8 +1204,9 @@ var Parser = (function() {
},
makeFilter: function(stream, name, params) {
if (name == "FlateDecode" || name == "Fl") {
if (params)
error("params not supported yet for FlateDecode");
if (params) {
return new PredictorStream(new FlateStream(stream), params);
}
return new FlateStream(stream);
} else {
error("filter '" + name + "' not supported yet");
@ -1207,10 +1299,10 @@ var XRef = (function() {
this.stream = stream;
this.entries = [];
this.xrefstms = {};
this.readXRef(startXRef);
var trailerDict = this.readXRef(startXRef);
// 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");
// prepare the XRef cache
@ -1265,18 +1357,18 @@ var XRef = (function() {
error("Invalid XRef table");
// get the 'Prev' pointer
var more = false;
var prev;
obj = dict.get("Prev");
if (IsInt(obj)) {
this.prev = obj;
more = true;
prev = obj;
} else if (IsRef(obj)) {
// certain buggy PDF generators generate "/Prev NNN 0 R" instead
// of "/Prev NNN"
this.prev = obj.num;
more = true;
prev = obj.num;
}
if (prev) {
this.readXRef(prev);
}
this.trailerDict = dict;
// check for 'XRefStm' key
if (IsInt(obj = dict.get("XRefStm"))) {
@ -1287,10 +1379,56 @@ var XRef = (function() {
this.readXRef(pos);
}
return more;
return dict;
},
readXRefStream: function(parser) {
error("Invalid XRef stream");
readXRefStream: function(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) {
var stream = this.stream;
@ -1330,11 +1468,12 @@ var XRef = (function() {
e = this.getEntry(num);
var gen = ref.gen;
var stream, parser;
if (e.uncompressed) {
if (e.gen != gen)
throw("inconsistent generation in XRef");
var stream = this.stream.makeSubStream(e.offset);
var parser = new Parser(new Lexer(stream), true, this);
stream = this.stream.makeSubStream(e.offset);
parser = new Parser(new Lexer(stream), true, this);
var obj1 = parser.getObj();
var obj2 = parser.getObj();
var obj3 = parser.getObj();
@ -1358,7 +1497,40 @@ var XRef = (function() {
this.cache[num] = 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() {
return this.fetch(this.root);
@ -1389,20 +1561,40 @@ var Page = (function() {
: null));
},
compile: function(gfx, fonts) {
if (!this.code) {
var xref = this.xref;
var content = xref.fetchIfRef(this.content);
var resources = xref.fetchIfRef(this.resources);
this.code = gfx.compile(content, xref, resources, fonts);
if (this.code) {
// content was compiled
return;
}
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) {
assert(this.code instanceof Function, "page content must be compiled first");
var xref = this.xref;
var content = xref.fetchIfRef(this.content);
var resources = xref.fetchIfRef(this.resources);
var mediaBox = xref.fetchIfRef(this.mediaBox);
assertWellFormed(IsStream(content) && IsDict(resources),
"invalid page content or resources");
assertWellFormed(IsDict(resources), "invalid page resources");
gfx.beginDrawing({ x: mediaBox[0], y: mediaBox[1],
width: mediaBox[2] - mediaBox[0],
height: mediaBox[3] - mediaBox[1] });
@ -1574,7 +1766,7 @@ var PDFDoc = (function() {
},
getPage: function(n) {
var linearization = this.linearization;
assert(!linearization, "linearized page access not implemented");
// assert(!linearization, "linearized page access not implemented");
return this.catalog.getPage(n);
}
};
@ -1593,6 +1785,7 @@ var CanvasExtraState = (function() {
this.fontSize = 0.0;
this.textMatrix = IDENTITY_MATRIX;
this.leading = 0.0;
this.colorSpace = null;
// Current point (in user coordinates)
this.x = 0.0;
this.y = 0.0;
@ -1887,6 +2080,9 @@ var CanvasGraphics = (function() {
const NORMAL_CLIP = {};
const EO_CLIP = {};
// Used for tiling patterns
const PAINT_TYPE_COLORED = 1, PAINT_TYPE_UNCOLORED = 2;
constructor.prototype = {
translateFont: function(fontDict, xref, resources) {
var descriptor = xref.fetch(fontDict.get("FontDescriptor"));
@ -1922,7 +2118,8 @@ var CanvasGraphics = (function() {
// Get the font charset if any
var charset = descriptor.get("CharSet");
assertWellFormed(IsString(charset), "invalid charset");
if (charset)
assertWellFormed(IsString(charset), "invalid charset");
charset = charset.split("/");
} else if (IsName(encoding)) {
@ -1958,7 +2155,8 @@ var CanvasGraphics = (function() {
var tokens = [];
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++) {
var byte = cmap[i];
if (byte == 0x20 || byte == 0x0A || byte == 0x3C || byte == 0x3E) {
@ -2354,6 +2552,10 @@ var CanvasGraphics = (function() {
},
setFillColorSpace: function(space) {
// TODO real impl
if (space.name === "Pattern")
this.current.colorSpace = "Pattern";
else
this.current.colorSpace = "DeviceRGB";
},
setStrokeColor: function(/*...*/) {
// TODO real impl
@ -2377,7 +2579,125 @@ var CanvasGraphics = (function() {
},
setFillColorN: function(/*...*/) {
// TODO real impl
this.setFillColor.apply(this, arguments);
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
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) {
this.setStrokeRGBColor(gray, gray, gray);
@ -2465,18 +2785,24 @@ var CanvasGraphics = (function() {
var fn = new PDFFunction(this.xref, fnObj);
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;
for (var i = t0; i <= t1; i += step) {
var c = fn.func([i]);
gradient.addColorStop(i, this.makeCssRgb.apply(this, c));
}
this.ctx.fillStyle = gradient;
// HACK to draw the gradient onto an infinite rectangle.
// PDF gradients are drawn across the entire image while
// 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);
},

175
test.py Normal file
View 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
View 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
View 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

Binary file not shown.

BIN
tests/tracemonkey.pdf Normal file

Binary file not shown.

View File

@ -1,5 +1,10 @@
/* -*- 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 = [
".notdef",
".notdef",
"space",
"exclam",
"quotedbl",
@ -490,7 +495,7 @@ var CFFDictDataMap = {
},
"10": {
name: "StdHW"
},
},
"11": {
name: "StdVW"
},
@ -597,7 +602,7 @@ var CFFDictDataMap = {
},
"18": {
name: "Private",
operand: "number number"
operand: "number number"
},
"19": {
name: "Subrs"

View File

@ -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
* is only a CharString format and is never used directly as a Font file.