Browse Source

Merge with master

Vivien Nicolas 14 years ago
parent
commit
d923953ee3
  1. 2
      LICENSE
  2. 1142
      fonts.js
  3. 155
      glyphlist.js
  4. BIN
      images/buttons.png
  5. BIN
      images/combobox.png
  6. BIN
      images/source/Buttons.psd.zip
  7. BIN
      images/source/ComboBox.psd.zip
  8. 184
      multi-page-viewer.css
  9. 40
      multi-page-viewer.html
  10. 400
      multi-page-viewer.js
  11. 420
      pdf.js
  12. 175
      test.py
  13. 17
      test_manifest.json
  14. 149
      test_slave.html
  15. BIN
      tests/canvas.pdf
  16. BIN
      tests/tracemonkey.pdf
  17. 5
      utils/cffStandardStrings.js
  18. 5
      utils/fonts_utils.js

2
LICENSE

@ -5,6 +5,8 @@ @@ -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"),

1142
fonts.js

File diff suppressed because it is too large Load Diff

155
glyphlist.js

@ -1,3 +1,6 @@ @@ -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 = { @@ -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 = { @@ -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 = { @@ -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 = { @@ -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 = { @@ -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 = { @@ -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 = { @@ -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 = { @@ -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 = { @@ -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

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.7 KiB

BIN
images/combobox.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.9 KiB

BIN
images/source/Buttons.psd.zip

Binary file not shown.

BIN
images/source/ComboBox.psd.zip

Binary file not shown.

184
multi-page-viewer.css

@ -0,0 +1,184 @@ @@ -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

@ -0,0 +1,40 @@ @@ -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

@ -0,0 +1,400 @@ @@ -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;
}
};
};

420
pdf.js

@ -50,7 +50,7 @@ function shadow(obj, prop, value) { @@ -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() { @@ -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() { @@ -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() { @@ -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() { @@ -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() { @@ -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() { @@ -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() { @@ -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() { @@ -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() { @@ -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() { @@ -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() { @@ -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() { @@ -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() { @@ -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() { @@ -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() { @@ -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() { @@ -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() { @@ -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() { @@ -1287,10 +1379,56 @@ var XRef = (function() {
this.readXRef(pos);
}
return more;
},
readXRefStream: function(parser) {
error("Invalid XRef stream");
return dict;
},
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() { @@ -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() { @@ -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() { @@ -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);
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() { @@ -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() { @@ -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() { @@ -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() { @@ -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() { @@ -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() { @@ -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() { @@ -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,6 +2785,10 @@ var CanvasGraphics = (function() { @@ -2465,6 +2785,10 @@ 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) {
@ -2477,6 +2801,8 @@ var CanvasGraphics = (function() { @@ -2477,6 +2801,8 @@ var CanvasGraphics = (function() {
// 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

@ -0,0 +1,175 @@ @@ -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

@ -0,0 +1,17 @@ @@ -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

@ -0,0 +1,149 @@ @@ -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

Binary file not shown.

BIN
tests/tracemonkey.pdf

Binary file not shown.

5
utils/cffStandardStrings.js

@ -1,3 +1,8 @@ @@ -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 = [
".notdef",
"space",

5
utils/fonts_utils.js

@ -1,3 +1,8 @@ @@ -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.

Loading…
Cancel
Save