Browse Source

Initial merge of master.

= 14 years ago
parent
commit
65ea07a3a2
  1. 18
      Makefile
  2. 4
      charsets.js
  3. 125
      extensions/firefox/components/pdfContentHandler.js
  4. 244
      fonts.js
  5. 2
      metrics.js
  6. 133
      pdf.js
  7. 9
      test/driver.js
  8. 33
      test/pdfs/extgstate.pdf
  9. 19
      test/test_manifest.json
  10. 2
      web/compatibility.js
  11. 8
      web/viewer.css
  12. 25
      web/viewer.html
  13. 139
      web/viewer.js

18
Makefile

@ -3,6 +3,9 @@ BUILD_DIR := build
DEFAULT_BROWSERS := resources/browser_manifests/browser_manifest.json DEFAULT_BROWSERS := resources/browser_manifests/browser_manifest.json
DEFAULT_TESTS := test_manifest.json DEFAULT_TESTS := test_manifest.json
EXTENSION_SRC := ./extensions/firefox
EXTENSION_NAME := pdf.js.xpi
# Let folks define custom rules for their clones. # Let folks define custom rules for their clones.
-include local.mk -include local.mk
@ -103,14 +106,17 @@ lint:
# TODO: Use the Closure compiler to optimize the pdf.js files. # TODO: Use the Closure compiler to optimize the pdf.js files.
# #
GH_PAGES = $(BUILD_DIR)/gh-pages GH_PAGES = $(BUILD_DIR)/gh-pages
web: | compiler pages-repo \ web: | extension compiler pages-repo \
$(addprefix $(GH_PAGES)/, $(PDF_JS_FILES)) \ $(addprefix $(GH_PAGES)/, $(PDF_JS_FILES)) \
$(addprefix $(GH_PAGES)/, $(wildcard web/*.*)) \ $(addprefix $(GH_PAGES)/, $(wildcard web/*.*)) \
$(addprefix $(GH_PAGES)/, $(wildcard web/images/*.*)) $(addprefix $(GH_PAGES)/, $(wildcard web/images/*.*)) \
$(addprefix $(GH_PAGES)/, $(wildcard $(EXTENSION_SRC)/*.xpi))
@cp $(GH_PAGES)/web/index.html.template $(GH_PAGES)/index.html; @cp $(GH_PAGES)/web/index.html.template $(GH_PAGES)/index.html;
@cd $(GH_PAGES); git add -A; @cd $(GH_PAGES); git add -A;
@echo
@echo "Website built in $(GH_PAGES)." @echo "Website built in $(GH_PAGES)."
@echo "Don't forget to cd into $(GH_PAGES)/ and issue 'git commit' to push changes."
# make pages-repo # make pages-repo
# #
@ -126,6 +132,7 @@ pages-repo: | $(BUILD_DIR)
fi; fi;
@mkdir -p $(GH_PAGES)/web; @mkdir -p $(GH_PAGES)/web;
@mkdir -p $(GH_PAGES)/web/images; @mkdir -p $(GH_PAGES)/web/images;
@mkdir -p $(GH_PAGES)/$(EXTENSION_SRC);
$(GH_PAGES)/%.js: %.js $(GH_PAGES)/%.js: %.js
@cp $< $@ @cp $< $@
@ -136,6 +143,9 @@ $(GH_PAGES)/web/%: web/%
$(GH_PAGES)/web/images/%: web/images/% $(GH_PAGES)/web/images/%: web/images/%
@cp $< $@ @cp $< $@
$(GH_PAGES)/$(EXTENSION_SRC)/%: $(EXTENSION_SRC)/%
@cp -R $< $@
# # make compiler # # make compiler
# # # #
# # This target downloads the Closure compiler, and places it in the # # This target downloads the Closure compiler, and places it in the
@ -149,13 +159,11 @@ $(GH_PAGES)/web/images/%: web/images/%
# curl $(COMPILER_URL) > $(BUILD_DIR)/compiler.zip; # curl $(COMPILER_URL) > $(BUILD_DIR)/compiler.zip;
# cd $(BUILD_DIR); unzip compiler.zip compiler.jar; # cd $(BUILD_DIR); unzip compiler.zip compiler.jar;
# make firefox-extension # make extension
# #
# This target produce a restartless firefox extension containing a # This target produce a restartless firefox extension containing a
# copy of the pdf.js source. # copy of the pdf.js source.
CONTENT_DIR := content CONTENT_DIR := content
EXTENSION_SRC := ./extensions/firefox
EXTENSION_NAME := pdf.js.xpi
PDF_WEB_FILES = \ PDF_WEB_FILES = \
web/images \ web/images \
web/compatibility.js \ web/compatibility.js \

4
charsets.js

@ -1,3 +1,7 @@
/* -*- 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 ISOAdobeCharset = [ var ISOAdobeCharset = [
'.notdef', 'space', 'exclam', 'quotedbl', 'numbersign', 'dollar', '.notdef', 'space', 'exclam', 'quotedbl', 'numbersign', 'dollar',

125
extensions/firefox/components/pdfContentHandler.js

@ -20,136 +20,40 @@ function log(aMsg) {
dump(msg + '\n'); dump(msg + '\n');
} }
function fireEventTo(aName, aData, aWindow) { const NS_ERROR_WONT_HANDLE_CONTENT = 0x805d0001;
let window = aWindow.wrappedJSObject;
let evt = window.document.createEvent('CustomEvent');
evt.initCustomEvent('pdf' + aName, false, false, aData);
window.document.dispatchEvent(evt);
}
function loadDocument(aWindow, aDocumentUrl) {
let xhr = Cc['@mozilla.org/xmlextras/xmlhttprequest;1']
.createInstance(Ci.nsIXMLHttpRequest);
xhr.onprogress = function updateProgress(evt) {
if (evt.lengthComputable)
fireEventTo(evt.type, evt.loaded / evt.total, aWindow);
};
xhr.onerror = function error(evt) {
fireEventTo(evt.type, false, aWindow);
};
xhr.onload = function load(evt) {
let data = (xhr.mozResponseArrayBuffer || xhr.mozResponse ||
xhr.responseArrayBuffer || xhr.response);
try {
let view = new Uint8Array(data);
let window = aWindow.wrappedJSObject;
let arrayBuffer = new window.ArrayBuffer(data.byteLength);
let view2 = new window.Uint8Array(arrayBuffer);
view2.set(view);
fireEventTo(evt.type, arrayBuffer, aWindow);
} catch (e) {
log('Error - ' + e);
}
};
xhr.open('GET', aDocumentUrl);
xhr.responseType = 'arraybuffer';
xhr.send(null);
}
let WebProgressListener = {
init: function WebProgressListenerInit(aWindow, aUrl) {
this._locationHasChanged = false;
this._documentUrl = aUrl;
let flags = Ci.nsIWebProgress.NOTIFY_LOCATION |
Ci.nsIWebProgress.NOTIFY_STATE_NETWORK |
Ci.nsIWebProgress.NOTIFY_STATE_DOCUMENT;
let docShell = aWindow.QueryInterface(Ci.nsIInterfaceRequestor)
.getInterface(Ci.nsIWebNavigation)
.QueryInterface(Ci.nsIDocShell);
let webProgress = docShell.QueryInterface(Ci.nsIInterfaceRequestor)
.getInterface(Ci.nsIWebProgress);
try {
webProgress.removeProgressListener(this);
} catch (e) {}
webProgress.addProgressListener(this, flags);
},
onStateChange: function onStateChange(aWebProgress, aRequest, aStateFlags,
aStatus) {
const complete = Ci.nsIWebProgressListener.STATE_IS_WINDOW +
Ci.nsIWebProgressListener.STATE_STOP;
if ((aStateFlags & complete) == complete && this._locationHasChanged) {
aWebProgress.removeProgressListener(this);
loadDocument(aWebProgress.DOMWindow, this._documentUrl);
}
},
onProgressChange: function onProgressChange(aWebProgress, aRequest, aCurSelf,
aMaxSelf, aCurTotal, aMaxTotal) {
},
onLocationChange: function onLocationChange(aWebProgress, aRequest,
aLocationURI) {
this._locationHasChanged = true;
},
onStatusChange: function onStatusChange(aWebProgress, aRequest, aStatus,
aMessage) {
},
onSecurityChange: function onSecurityChange(aWebProgress, aRequest, aState) {
},
QueryInterface: function QueryInterface(aIID) {
if (aIID.equals(Ci.nsIWebProgressListener) ||
aIID.equals(Ci.nsISupportsWeakReference) ||
aIID.equals(Ci.nsISupports)) {
return this;
}
throw Components.results.NS_ERROR_NO_INTERFACE;
}
};
function pdfContentHandler() { function pdfContentHandler() {
} }
pdfContentHandler.prototype = { pdfContentHandler.prototype = {
handleContent: function handleContent(aMimetype, aContext, aRequest) { handleContent: function handleContent(aMimetype, aContext, aRequest) {
if (aMimetype != PDF_CONTENT_TYPE) if (aMimetype != PDF_CONTENT_TYPE)
throw Cr.NS_ERROR_WONT_HANDLE_CONTENT; throw NS_ERROR_WONT_HANDLE_CONTENT;
if (!(aRequest instanceof Ci.nsIChannel)) if (!(aRequest instanceof Ci.nsIChannel))
throw Cr.NS_ERROR_WONT_HANDLE_CONTENT; throw NS_ERROR_WONT_HANDLE_CONTENT;
let window = null; let window = null;
let callbacks = aRequest.notificationCallbacks ? let callbacks = aRequest.notificationCallbacks ||
aRequest.notificationCallbacks :
aRequest.loadGroup.notificationCallbacks; aRequest.loadGroup.notificationCallbacks;
if (!callbacks) if (!callbacks)
return; return;
aRequest.cancel(Cr.NS_BINDING_ABORTED);
let uri = aRequest.URI;
window = callbacks.getInterface(Ci.nsIDOMWindow); window = callbacks.getInterface(Ci.nsIDOMWindow);
WebProgressListener.init(window, uri.spec);
let url = null;
try { try {
let url = Services.prefs.getCharPref('extensions.pdf.js.url'); url = Services.prefs.getCharPref('extensions.pdf.js.url');
url = url.replace('%s', uri.spec);
window.location = url;
} catch (e) { } catch (e) {
log('Error retrieving the pdf.js base url - ' + e); log('Error retrieving the pdf.js base url - ' + e);
throw NS_ERROR_WONT_HANDLE_CONTENT;
} }
let targetUrl = aRequest.URI.spec;
if (targetUrl.indexOf('?pdfjs.action=download') >= 0)
throw NS_ERROR_WONT_HANDLE_CONTENT;
aRequest.cancel(Cr.NS_BINDING_ABORTED);
window.location = url.replace('%s', targetUrl);
}, },
classID: Components.ID('{2278dfd0-b75c-11e0-8257-1ba3d93c9f1a}'), classID: Components.ID('{2278dfd0-b75c-11e0-8257-1ba3d93c9f1a}'),
@ -158,4 +62,3 @@ pdfContentHandler.prototype = {
var NSGetFactory = XPCOMUtils.generateNSGetFactory([pdfContentHandler]); var NSGetFactory = XPCOMUtils.generateNSGetFactory([pdfContentHandler]);

244
fonts.js

@ -26,53 +26,53 @@ var kHintingEnabled = false;
*/ */
var stdFontMap = { var stdFontMap = {
'ArialNarrow': 'Helvetica', 'ArialNarrow': 'Helvetica',
'ArialNarrow_Bold': 'Helvetica-Bold', 'ArialNarrow-Bold': 'Helvetica-Bold',
'ArialNarrow_BoldItalic': 'Helvetica-BoldOblique', 'ArialNarrow-BoldItalic': 'Helvetica-BoldOblique',
'ArialNarrow_Italic': 'Helvetica-Oblique', 'ArialNarrow-Italic': 'Helvetica-Oblique',
'ArialBlack': 'Helvetica', 'ArialBlack': 'Helvetica',
'ArialBlack_Bold': 'Helvetica-Bold', 'ArialBlack-Bold': 'Helvetica-Bold',
'ArialBlack_BoldItalic': 'Helvetica-BoldOblique', 'ArialBlack-BoldItalic': 'Helvetica-BoldOblique',
'ArialBlack_Italic': 'Helvetica-Oblique', 'ArialBlack-Italic': 'Helvetica-Oblique',
'Arial': 'Helvetica', 'Arial': 'Helvetica',
'Arial_Bold': 'Helvetica-Bold', 'Arial-Bold': 'Helvetica-Bold',
'Arial_BoldItalic': 'Helvetica-BoldOblique', 'Arial-BoldItalic': 'Helvetica-BoldOblique',
'Arial_Italic': 'Helvetica-Oblique', 'Arial-Italic': 'Helvetica-Oblique',
'Arial_BoldItalicMT': 'Helvetica-BoldOblique', 'Arial-BoldItalicMT': 'Helvetica-BoldOblique',
'Arial_BoldMT': 'Helvetica-Bold', 'Arial-BoldMT': 'Helvetica-Bold',
'Arial_ItalicMT': 'Helvetica-Oblique', 'Arial-ItalicMT': 'Helvetica-Oblique',
'ArialMT': 'Helvetica', 'ArialMT': 'Helvetica',
'Courier_Bold': 'Courier-Bold', 'Courier-Bold': 'Courier-Bold',
'Courier_BoldItalic': 'Courier-BoldOblique', 'Courier-BoldItalic': 'Courier-BoldOblique',
'Courier_Italic': 'Courier-Oblique', 'Courier-Italic': 'Courier-Oblique',
'CourierNew': 'Courier', 'CourierNew': 'Courier',
'CourierNew_Bold': 'Courier-Bold', 'CourierNew-Bold': 'Courier-Bold',
'CourierNew_BoldItalic': 'Courier-BoldOblique', 'CourierNew-BoldItalic': 'Courier-BoldOblique',
'CourierNew_Italic': 'Courier-Oblique', 'CourierNew-Italic': 'Courier-Oblique',
'CourierNewPS_BoldItalicMT': 'Courier-BoldOblique', 'CourierNewPS-BoldItalicMT': 'Courier-BoldOblique',
'CourierNewPS_BoldMT': 'Courier-Bold', 'CourierNewPS-BoldMT': 'Courier-Bold',
'CourierNewPS_ItalicMT': 'Courier-Oblique', 'CourierNewPS-ItalicMT': 'Courier-Oblique',
'CourierNewPSMT': 'Courier', 'CourierNewPSMT': 'Courier',
'Helvetica_Bold': 'Helvetica-Bold', 'Helvetica-Bold': 'Helvetica-Bold',
'Helvetica_BoldItalic': 'Helvetica-BoldOblique', 'Helvetica-BoldItalic': 'Helvetica-BoldOblique',
'Helvetica_Italic': 'Helvetica-Oblique', 'Helvetica-Italic': 'Helvetica-Oblique',
'Symbol_Bold': 'Symbol', 'Symbol-Bold': 'Symbol',
'Symbol_BoldItalic': 'Symbol', 'Symbol-BoldItalic': 'Symbol',
'Symbol_Italic': 'Symbol', 'Symbol-Italic': 'Symbol',
'TimesNewRoman': 'Times-Roman', 'TimesNewRoman': 'Times-Roman',
'TimesNewRoman_Bold': 'Times-Bold', 'TimesNewRoman-Bold': 'Times-Bold',
'TimesNewRoman_BoldItalic': 'Times-BoldItalic', 'TimesNewRoman-BoldItalic': 'Times-BoldItalic',
'TimesNewRoman_Italic': 'Times-Italic', 'TimesNewRoman-Italic': 'Times-Italic',
'TimesNewRomanPS': 'Times-Roman', 'TimesNewRomanPS': 'Times-Roman',
'TimesNewRomanPS_Bold': 'Times-Bold', 'TimesNewRomanPS-Bold': 'Times-Bold',
'TimesNewRomanPS_BoldItalic': 'Times-BoldItalic', 'TimesNewRomanPS-BoldItalic': 'Times-BoldItalic',
'TimesNewRomanPS_BoldItalicMT': 'Times-BoldItalic', 'TimesNewRomanPS-BoldItalicMT': 'Times-BoldItalic',
'TimesNewRomanPS_BoldMT': 'Times-Bold', 'TimesNewRomanPS-BoldMT': 'Times-Bold',
'TimesNewRomanPS_Italic': 'Times-Italic', 'TimesNewRomanPS-Italic': 'Times-Italic',
'TimesNewRomanPS_ItalicMT': 'Times-Italic', 'TimesNewRomanPS-ItalicMT': 'Times-Italic',
'TimesNewRomanPSMT': 'Times-Roman', 'TimesNewRomanPSMT': 'Times-Roman',
'TimesNewRomanPSMT_Bold': 'Times-Bold', 'TimesNewRomanPSMT-Bold': 'Times-Bold',
'TimesNewRomanPSMT_BoldItalic': 'Times-BoldItalic', 'TimesNewRomanPSMT-BoldItalic': 'Times-BoldItalic',
'TimesNewRomanPSMT_Italic': 'Times-Italic' 'TimesNewRomanPSMT-Italic': 'Times-Italic'
}; };
var serifFonts = { var serifFonts = {
@ -453,7 +453,9 @@ var Font = (function Font() {
if (!file) { if (!file) {
// The file data is not specified. Trying to fix the font name // The file data is not specified. Trying to fix the font name
// to be used with the canvas.font. // to be used with the canvas.font.
var fontName = stdFontMap[name] || name.replace('_', '-'); var fontName = name.replace(/[,_]/g, '-');
fontName = stdFontMap[fontName] || fontName;
this.bold = (fontName.search(/bold/gi) != -1); this.bold = (fontName.search(/bold/gi) != -1);
this.italic = (fontName.search(/oblique/gi) != -1) || this.italic = (fontName.search(/oblique/gi) != -1) ||
(fontName.search(/italic/gi) != -1); (fontName.search(/italic/gi) != -1);
@ -720,7 +722,13 @@ var Font = (function Font() {
}; };
function createOS2Table(properties, override) { function createOS2Table(properties, override) {
var override = override || {}; override = override || {
unitsPerEm: 0,
yMax: 0,
yMin: 0,
ascent: 0,
descent: 0
};
var ulUnicodeRange1 = 0; var ulUnicodeRange1 = 0;
var ulUnicodeRange2 = 0; var ulUnicodeRange2 = 0;
@ -1332,7 +1340,8 @@ var Font = (function Font() {
'OS/2': stringToArray(createOS2Table(properties)), 'OS/2': stringToArray(createOS2Table(properties)),
// Character to glyphs mapping // Character to glyphs mapping
'cmap': createCMapTable(charstrings.slice(), font.glyphIds), 'cmap': createCMapTable(charstrings.slice(),
('glyphIds' in font) ? font.glyphIds : null),
// Font header // Font header
'head': (function fontFieldsHead() { 'head': (function fontFieldsHead() {
@ -1727,6 +1736,7 @@ var Type1Parser = function type1Parser() {
var charstring = []; var charstring = [];
var lsb = 0; var lsb = 0;
var width = 0; var width = 0;
var flexState = 0;
var value = ''; var value = '';
var count = array.length; var count = array.length;
@ -1760,7 +1770,11 @@ var Type1Parser = function type1Parser() {
i++; i++;
continue; continue;
} }
} else if (!kHintingEnabled && (value == 1 || value == 2)) { } else if (escape == 17 || escape == 33) {
// pop or setcurrentpoint commands can be ignored
// since we are not doing callothersubr
continue;
} else if (!kHintingEnabled && (escape == 1 || escape == 2)) {
charstring.push('drop', 'drop', 'drop', 'drop', 'drop', 'drop'); charstring.push('drop', 'drop', 'drop', 'drop', 'drop', 'drop');
continue; continue;
} }
@ -1787,6 +1801,29 @@ var Type1Parser = function type1Parser() {
charstring.push(lsb, 'hmoveto'); charstring.push(lsb, 'hmoveto');
continue; continue;
} else if (value == 10) { // callsubr
if (charstring[charstring.length - 1] < 3) { // subr #0..2
var subrNumber = charstring.pop();
switch (subrNumber) {
case 1:
flexState = 1; // prepare for flex coordinates
break;
case 2:
flexState = 2; // flex in progress
break;
case 0:
// type2 flex command does not need final coords
charstring.push('exch', 'drop', 'exch', 'drop');
charstring.push('flex');
flexState = 0;
break;
}
continue;
}
} else if (value == 21 && flexState > 0) {
if (flexState > 1)
continue; // ignoring rmoveto
value = 5; // first segment replacing with rlineto
} else if (!kHintingEnabled && (value == 1 || value == 3)) { } else if (!kHintingEnabled && (value == 1 || value == 3)) {
charstring.push('drop', 'drop'); charstring.push('drop', 'drop');
continue; continue;
@ -2283,7 +2320,8 @@ CFF.prototype = {
'return': 11, 'return': 11,
'sub': [12, 11], 'sub': [12, 11],
'div': [12, 12], 'div': [12, 12],
'pop': [1, 12, 18], 'exch': [12, 28],
'flex': [12, 35],
'drop' : [12, 18], 'drop' : [12, 18],
'endchar': 14, 'endchar': 14,
'rmoveto': 21, 'rmoveto': 21,
@ -2500,7 +2538,7 @@ var Type2CFF = (function type2CFF() {
var charStrings = this.parseIndex(topDict.CharStrings); var charStrings = this.parseIndex(topDict.CharStrings);
var charset = this.parseCharsets(topDict.charset, var charset = this.parseCharsets(topDict.charset,
charStrings.length, strings); charStrings.length, strings);
var hasSupplement = this.parseEncoding(topDict.Encoding, properties, var encoding = this.parseEncoding(topDict.Encoding, properties,
strings, charset); strings, charset);
// The font sanitizer does not support CFF encoding with a // The font sanitizer does not support CFF encoding with a
@ -2508,8 +2546,8 @@ var Type2CFF = (function type2CFF() {
// between gid to glyph, let's overwrite what is declared in // between gid to glyph, let's overwrite what is declared in
// the top dictionary to let the sanitizer think the font use // the top dictionary to let the sanitizer think the font use
// StandardEncoding, that's a lie but that's ok. // StandardEncoding, that's a lie but that's ok.
if (hasSupplement) if (encoding.hasSupplement)
bytes[topDict.Encoding] = 0; bytes[topDict.Encoding] &= 0x7F;
// The CFF specification state that the 'dotsection' command // The CFF specification state that the 'dotsection' command
// (12, 0) is deprecated and treated as a no-op, but all Type2 // (12, 0) is deprecated and treated as a no-op, but all Type2
@ -2540,7 +2578,7 @@ var Type2CFF = (function type2CFF() {
// charstrings contains info about glyphs (one element per glyph // charstrings contains info about glyphs (one element per glyph
// containing mappings for {unicode, width}) // containing mappings for {unicode, width})
var charstrings = this.getCharStrings(charset, charStrings, var charstrings = this.getCharStrings(charset, encoding.encoding,
privateDict, this.properties); privateDict, this.properties);
// create the mapping between charstring and glyph id // create the mapping between charstring and glyph id
@ -2557,49 +2595,85 @@ var Type2CFF = (function type2CFF() {
return data; return data;
}, },
getCharStrings: function cff_charstrings(charsets, charStrings, getCharStrings: function cff_charstrings(charsets, encoding,
privateDict, properties) { privateDict, properties) {
var defaultWidth = privateDict['defaultWidthX']; var defaultWidth = privateDict['defaultWidthX'];
var charstrings = []; var charstrings = [];
var firstChar = properties.firstChar;
var glyphMap = {};
for (var i = 0; i < charsets.length; i++) {
var glyph = charsets[i];
for (var charcode in encoding) {
if (encoding[charcode] == i)
glyphMap[glyph] = charcode | 0;
}
}
var differences = properties.differences; var differences = properties.differences;
var index = properties.firstChar || 0; for (var i = 0; i < differences.length; ++i) {
var glyph = differences[i];
if (!glyph)
continue;
var oldGlyph = charsets[i];
if (oldGlyph)
delete glyphMap[oldGlyph];
glyphMap[differences[i]] = i;
}
var glyphs = properties.glyphs;
for (var i = 1; i < charsets.length; i++) { for (var i = 1; i < charsets.length; i++) {
var code = -1;
var glyph = charsets[i]; var glyph = charsets[i];
for (var j = 0; j < differences.length; j++) { var code = glyphMap[glyph] || 0;
if (differences[j] == glyph) {
index = j;
code = differences.indexOf(glyph);
break;
}
}
var mapping = var mapping = glyphs[code] || glyphs[glyph] || { width: defaultWidth };
properties.glyphs[glyph] || properties.glyphs[index] || {}; var unicode = mapping.unicode;
if (code == -1)
index = code = mapping.unicode || index;
if (code <= 0x1f || (code >= 127 && code <= 255)) if (unicode <= 0x1f || (unicode >= 127 && unicode <= 255))
code += kCmapGlyphOffset; unicode += kCmapGlyphOffset;
var width = mapping.width; var width = isNum(mapping.width) ? mapping.width : defaultWidth;
properties.glyphs[glyph] = properties.encoding[index] = { properties.encoding[code] = {
unicode: code, unicode: unicode,
width: isNum(width) ? width : defaultWidth width: width
}; };
charstrings.push({ charstrings.push({
unicode: code, unicode: unicode,
width: width, width: width,
code: code,
gid: i gid: i
}); });
index++;
} }
// sort the array by the unicode value // sort the array by the unicode value
charstrings.sort(function type2CFFGetCharStringsSort(a, b) { charstrings.sort(function type2CFFGetCharStringsSort(a, b) {
return a.unicode - b.unicode; return a.unicode - b.unicode;
}); });
// remove duplicates -- they might appear during selection:
// properties.glyphs[code] || properties.glyphs[glyph]
var nextUnusedUnicode = kCmapGlyphOffset + 0x0020;
var lastUnicode = charstrings[0].unicode, wasModified = false;
for (var i = 1; i < charstrings.length; ++i) {
if (lastUnicode != charstrings[i].unicode) {
lastUnicode = charstrings[i].unicode;
continue;
}
// duplicate found -- keeping the item that has
// different code and unicode, that one created
// as result of modification of the base encoding
var duplicateIndex =
charstrings[i].unicode == charstrings[i].code ? i : i - 1;
charstrings[duplicateIndex].unicode = nextUnusedUnicode++;
wasModified = true;
}
if (!wasModified)
return charstrings;
// sort the array by the unicode value (again)
charstrings.sort(function type2CFFGetCharStringsSort(a, b) {
return a.unicode - b.unicode;
});
return charstrings; return charstrings;
}, },
@ -2607,6 +2681,10 @@ var Type2CFF = (function type2CFF() {
charset) { charset) {
var encoding = {}; var encoding = {};
var bytes = this.bytes; var bytes = this.bytes;
var result = {
encoding: encoding,
hasSupplement: false
};
function readSupplement() { function readSupplement() {
var supplementsCount = bytes[pos++]; var supplementsCount = bytes[pos++];
@ -2633,11 +2711,6 @@ var Type2CFF = (function type2CFF() {
var glyphsCount = bytes[pos++]; var glyphsCount = bytes[pos++];
for (var i = 1; i <= glyphsCount; i++) for (var i = 1; i <= glyphsCount; i++)
encoding[bytes[pos++]] = i; encoding[bytes[pos++]] = i;
if (format & 0x80) {
readSupplement();
return true;
}
break; break;
case 1: case 1:
@ -2649,19 +2722,18 @@ var Type2CFF = (function type2CFF() {
for (var j = start; j <= start + count; j++) for (var j = start; j <= start + count; j++)
encoding[j] = gid++; encoding[j] = gid++;
} }
if (format & 0x80) {
readSupplement();
return true;
}
break; break;
default: default:
error('Unknow encoding format: ' + format + ' in CFF'); error('Unknow encoding format: ' + format + ' in CFF');
break; break;
} }
if (format & 0x80) {
readSupplement();
result.hasSupplement = true;
}
} }
return false; return result;
}, },
parseCharsets: function cff_parsecharsets(pos, length, strings) { parseCharsets: function cff_parsecharsets(pos, length, strings) {
@ -2871,7 +2943,15 @@ var Type2CFF = (function type2CFF() {
if (b <= 21) { if (b <= 21) {
if (b === 12) { if (b === 12) {
++pos; ++pos;
var b = (b << 8) | dict[pos]; var op = dict[pos];
if ((op > 14 && op < 17) ||
(op > 23 && op < 30) || op > 38) {
warn('Invalid CFF dictionary key: ' + op);
// trying to replace it with initialRandomSeed
// to pass sanitizer
dict[pos] = 19;
}
var b = (b << 8) | op;
} }
entries.push([b, operands]); entries.push([b, operands]);
operands = []; operands = [];

2
metrics.js

@ -1,6 +1,8 @@
/* -*- Mode: Java; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ /* -*- Mode: Java; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
/* vim: set shiftwidth=2 tabstop=2 autoindent cindent expandtab: */ /* vim: set shiftwidth=2 tabstop=2 autoindent cindent expandtab: */
'use strict';
var Metrics = { var Metrics = {
'Courier': 600, 'Courier': 600,
'Courier-Bold': 600, 'Courier-Bold': 600,

133
pdf.js

@ -2002,6 +2002,25 @@ var CCITTFaxStream = (function ccittFaxStream() {
return EOF; return EOF;
}; };
var findTableCode = function ccittFaxStreamFindTableCode(start, end, table,
limit) {
for (var i = start; i <= end; ++i) {
var code = this.lookBits(i);
if (code == EOF)
return [true, 1];
if (i < end)
code <<= end - i;
if (code >= limit) {
var p = table[code - ((limit == ccittEOL) ? 0 : limit)];
if (p[0] == i) {
this.eatBits(i);
return [true, p[1]];
}
}
}
return [false, 0];
};
constructor.prototype.getWhiteCode = function ccittFaxStreamGetWhiteCode() { constructor.prototype.getWhiteCode = function ccittFaxStreamGetWhiteCode() {
var code = 0; var code = 0;
var p; var p;
@ -2021,31 +2040,13 @@ var CCITTFaxStream = (function ccittFaxStream() {
return p[1]; return p[1];
} }
} else { } else {
for (var n = 1; n <= 9; ++n) { var result = findTableCode(1, 9, whiteTable2, ccittEOL);
code = this.lookBits(n); if (result[0])
if (code == EOF) return result[1];
return 1;
if (n < 9) result = findTableCode(11, 12, whiteTable1, ccittEOL);
code <<= 9 - n; if (result[0])
p = whiteTable2[code]; return result[1];
if (p[0] == n) {
this.eatBits(n);
return p[0];
}
}
for (var n = 11; n <= 12; ++n) {
code = this.lookBits(n);
if (code == EOF)
return 1;
if (n < 12)
code <<= 12 - n;
p = whiteTable1[code];
if (p[0] == n) {
this.eatBits(n);
return p[1];
}
}
} }
warn('bad white code'); warn('bad white code');
this.eatBits(1); this.eatBits(1);
@ -2070,45 +2071,17 @@ var CCITTFaxStream = (function ccittFaxStream() {
return p[1]; return p[1];
} }
} else { } else {
var n; var result = findTableCode(2, 6, blackTable3, ccittEOL);
for (n = 2; n <= 6; ++n) { if (result[0])
code = this.lookBits(n); return result[1];
if (code == EOF)
return 1; result = findTableCode(7, 12, blackTable2, 64);
if (n < 6) if (result[0])
code <<= 6 - n; return result[1];
p = blackTable3[code];
if (p[0] == n) { result = findTableCode(10, 13, blackTable1, ccittEOL);
this.eatBits(n); if (result[0])
return p[1]; return result[1];
}
}
for (n = 7; n <= 12; ++n) {
code = this.lookBits(n);
if (code == EOF)
return 1;
if (n < 12)
code <<= 12 - n;
if (code >= 64) {
p = blackTable2[code - 64];
if (p[0] == n) {
this.eatBits(n);
return p[1];
}
}
}
for (n = 10; n <= 13; ++n) {
code = this.lookBits(n);
if (code == EOF)
return 1;
if (n < 13)
code <<= 13 - n;
p = blackTable1[code];
if (p[0] == n) {
this.eatBits(n);
return p[1];
}
}
} }
warn('bad black code'); warn('bad black code');
this.eatBits(1); this.eatBits(1);
@ -3636,8 +3609,8 @@ var Page = (function pagePage() {
} }
next(); next();
}, },
rotatePoint: function pageRotatePoint(x, y) { rotatePoint: function pageRotatePoint(x, y, reverse) {
var rotate = this.rotate; var rotate = reverse ? (360 - this.rotate) : this.rotate;
switch (rotate) { switch (rotate) {
case 180: case 180:
return {x: this.width - x, y: y}; return {x: this.width - x, y: y};
@ -3645,6 +3618,7 @@ var Page = (function pagePage() {
return {x: this.width - y, y: this.height - x}; return {x: this.width - y, y: this.height - x};
case 270: case 270:
return {x: y, y: x}; return {x: y, y: x};
case 360:
case 0: case 0:
default: default:
return {x: x, y: this.height - y}; return {x: x, y: this.height - y};
@ -4698,8 +4672,12 @@ var PartialEvaluator = (function partialEvaluator() {
var xobjs = xref.fetchIfRef(resources.get('XObject')) || new Dict(); var xobjs = xref.fetchIfRef(resources.get('XObject')) || new Dict();
var patterns = xref.fetchIfRef(resources.get('Pattern')) || new Dict(); var patterns = xref.fetchIfRef(resources.get('Pattern')) || new Dict();
var parser = new Parser(new Lexer(stream), false); var parser = new Parser(new Lexer(stream), false);
var args = [], obj;
var res = resources; var res = resources;
var args = [], argsArray = [], fnArray = [], obj;
var getObjBt = function getObjBt() {
parser = this.oldParser;
return { name: 'BT' };
};
while (!isEOF(obj = parser.getObj())) { while (!isEOF(obj = parser.getObj())) {
if (isCmd(obj)) { if (isCmd(obj)) {
@ -4711,10 +4689,7 @@ var PartialEvaluator = (function partialEvaluator() {
fn = OP_MAP[cmd.substr(0, cmd.length - 2)]; fn = OP_MAP[cmd.substr(0, cmd.length - 2)];
// feeding 'BT' on next interation // feeding 'BT' on next interation
parser = { parser = {
getObj: function() { getObj: getObjBt,
parser = this.oldParser;
return { name: 'BT' };
},
oldParser: parser oldParser: parser
}; };
} }
@ -5042,7 +5017,9 @@ var PartialEvaluator = (function partialEvaluator() {
}; };
if (replaceGlyph || !glyphs[glyph]) if (replaceGlyph || !glyphs[glyph])
glyphs[glyph] = map[i]; glyphs[glyph] = map[i];
if (replaceGlyph || !glyphs[index])
glyphs[index] = map[i];
// If there is no file, the character mapping can't be modified // If there is no file, the character mapping can't be modified
// but this is unlikely that there is any standard encoding with // but this is unlikely that there is any standard encoding with
@ -5216,7 +5193,7 @@ var PartialEvaluator = (function partialEvaluator() {
return null; return null;
// Using base font name as a font name. // Using base font name as a font name.
baseFontName = baseFontName.name.replace(/,/g, '_'); baseFontName = baseFontName.name.replace(/[,_]/g, '-');
var metricsAndMap = this.getBaseFontMetricsAndMap(baseFontName); var metricsAndMap = this.getBaseFontMetricsAndMap(baseFontName);
var properties = { var properties = {
@ -5593,7 +5570,8 @@ var CanvasGraphics = (function canvasGraphics() {
stroke: function canvasGraphicsStroke() { stroke: function canvasGraphicsStroke() {
var ctx = this.ctx; var ctx = this.ctx;
var strokeColor = this.current.strokeColor; var strokeColor = this.current.strokeColor;
if (strokeColor && strokeColor.type === 'Pattern') { if (strokeColor && strokeColor.hasOwnProperty('type') &&
strokeColor.type === 'Pattern') {
// for patterns, we transform to pattern space, calculate // for patterns, we transform to pattern space, calculate
// the pattern, call stroke, and restore to user space // the pattern, call stroke, and restore to user space
ctx.save(); ctx.save();
@ -5614,7 +5592,8 @@ var CanvasGraphics = (function canvasGraphics() {
var ctx = this.ctx; var ctx = this.ctx;
var fillColor = this.current.fillColor; var fillColor = this.current.fillColor;
if (fillColor && fillColor.type === 'Pattern') { if (fillColor && fillColor.hasOwnProperty('type') &&
fillColor.type === 'Pattern') {
ctx.save(); ctx.save();
ctx.fillStyle = fillColor.getPattern(ctx); ctx.fillStyle = fillColor.getPattern(ctx);
ctx.fill(); ctx.fill();
@ -5634,7 +5613,8 @@ var CanvasGraphics = (function canvasGraphics() {
var ctx = this.ctx; var ctx = this.ctx;
var fillColor = this.current.fillColor; var fillColor = this.current.fillColor;
if (fillColor && fillColor.type === 'Pattern') { if (fillColor && fillColor.hasOwnProperty('type') &&
fillColor.type === 'Pattern') {
ctx.save(); ctx.save();
ctx.fillStyle = fillColor.getPattern(ctx); ctx.fillStyle = fillColor.getPattern(ctx);
ctx.fill(); ctx.fill();
@ -5644,7 +5624,8 @@ var CanvasGraphics = (function canvasGraphics() {
} }
var strokeColor = this.current.strokeColor; var strokeColor = this.current.strokeColor;
if (strokeColor && strokeColor.type === 'Pattern') { if (strokeColor && strokeColor.hasOwnProperty('type') &&
strokeColor.type === 'Pattern') {
ctx.save(); ctx.save();
ctx.strokeStyle = strokeColor.getPattern(ctx); ctx.strokeStyle = strokeColor.getPattern(ctx);
ctx.stroke(); ctx.stroke();

9
test/driver.js

@ -71,7 +71,8 @@ function nextTask() {
cleanup(); cleanup();
if (currentTaskIdx == manifest.length) { if (currentTaskIdx == manifest.length) {
return done(); done();
return;
} }
var task = manifest[currentTaskIdx]; var task = manifest[currentTaskIdx];
task.round = 0; task.round = 0;
@ -91,7 +92,11 @@ function nextTask() {
} }
function isLastPage(task) { function isLastPage(task) {
return task.pageNum > task.pdfDoc.numPages || task.pageNum > task.pageLimit; var limit = task.pageLimit || 0;
if (!limit || limit > task.pdfDoc.numPages)
limit = task.pdfDoc.numPages;
return task.pageNum > limit;
} }
function canvasToDataURL() { function canvasToDataURL() {

33
test/pdfs/extgstate.pdf

@ -36,8 +36,9 @@ endobj
/Length 8 0 R /Length 8 0 R
>> >>
stream stream
/F0 12 Tf
/F1 12 Tf
/GS1 gs /GS1 gs
/F0 12 Tf
BT BT
100 700 Td 100 700 Td
(I should be courier!) Tj (I should be courier!) Tj
@ -56,10 +57,11 @@ endobj
7 0 obj 7 0 obj
<< <<
/F0 10 0 R /F0 10 0 R
/F1 11 0 R
>> >>
endobj endobj
8 0 obj 8 0 obj
82 93
endobj endobj
9 0 obj 9 0 obj
<< <<
@ -81,25 +83,34 @@ endobj
/Encoding /WinAnsiEncoding /Encoding /WinAnsiEncoding
>> >>
endobj endobj
11 0 obj
<<
/Type /Font
/Subtype /Type1
/BaseFont /Times-Italic
/Encoding /WinAnsiEncoding
>>
endobj
xref xref
0 11 0 12
0000000000 65535 f 0000000000 65535 f
0000000015 00000 n 0000000015 00000 n
0000000078 00000 n 0000000078 00000 n
0000000135 00000 n 0000000135 00000 n
0000000239 00000 n 0000000239 00000 n
0000000304 00000 n 0000000304 00000 n
0000000441 00000 n 0000000452 00000 n
0000000473 00000 n 0000000484 00000 n
0000000505 00000 n 0000000527 00000 n
0000000523 00000 n 0000000545 00000 n
0000000653 00000 n 0000000675 00000 n
0000000771 00000 n
trailer trailer
<< <<
/Root 1 0 R /Root 1 0 R
/ID [<BFFF29B7D1C75EC69AC080682C2AFC5B> <BFFF29B7D1C75EC69AC080682C2AFC5B>] /ID [<FCE2529ACCE848A953BDB5D497D71C36> <FCE2529ACCE848A953BDB5D497D71C36>]
/Size 11 /Size 12
>> >>
startxref startxref
749 872
%%EOF %%EOF

19
test/test_manifest.json

@ -100,7 +100,7 @@
{ "id": "rotation", { "id": "rotation",
"file": "pdfs/rotation.pdf", "file": "pdfs/rotation.pdf",
"rounds": 1, "rounds": 1,
"type": "load" "type": "eq"
}, },
{ "id": "ecma262-pdf", { "id": "ecma262-pdf",
"file": "pdfs/ecma262.pdf", "file": "pdfs/ecma262.pdf",
@ -144,11 +144,18 @@
"rounds": 1, "rounds": 1,
"type": "eq" "type": "eq"
}, },
{ "id": "fit11-talk",
"file": "pdfs/fit11-talk.pdf",
"link": true,
"rounds": 1,
"skipPages": [12,31],
"type": "eq"
},
{ "id": "fips197", { "id": "fips197",
"file": "pdfs/fips197.pdf", "file": "pdfs/fips197.pdf",
"link": true, "link": true,
"rounds": 1, "rounds": 1,
"type": "load" "type": "eq"
}, },
{ "id": "txt2pdf", { "id": "txt2pdf",
"file": "pdfs/txt2pdf.pdf", "file": "pdfs/txt2pdf.pdf",
@ -172,7 +179,7 @@
"file": "pdfs/extgstate.pdf", "file": "pdfs/extgstate.pdf",
"link": false, "link": false,
"rounds": 1, "rounds": 1,
"type": "load" "type": "eq"
}, },
{ "id": "usmanm-bad", { "id": "usmanm-bad",
"file": "pdfs/usmanm-bad.pdf", "file": "pdfs/usmanm-bad.pdf",
@ -199,6 +206,12 @@
"rounds": 1, "rounds": 1,
"type": "eq" "type": "eq"
}, },
{ "id": "pal-o47",
"file": "pdfs/pal-o47.pdf",
"link": true,
"rounds": 1,
"type": "eq"
},
{ "id": "simpletype3font", { "id": "simpletype3font",
"file": "pdfs/simpletype3font.pdf", "file": "pdfs/simpletype3font.pdf",
"link": false, "link": false,

2
web/compatibility.js

@ -163,7 +163,7 @@
// IE9 text/html data URI // IE9 text/html data URI
(function checkDocumentDocumentModeCompatibility() { (function checkDocumentDocumentModeCompatibility() {
if (document.documentMode !== 9) if (!('documentMode' in document) || document.documentMode !== 9)
return; return;
// overriding the src property // overriding the src property
var originalSrcDescriptor = Object.getOwnPropertyDescriptor( var originalSrcDescriptor = Object.getOwnPropertyDescriptor(

8
web/viewer.css

@ -8,6 +8,10 @@ body {
padding: 0px; padding: 0px;
} }
[hidden] {
display: none;
}
/* === Toolbar === */ /* === Toolbar === */
#controls { #controls {
background-color: #eee; background-color: #eee;
@ -34,6 +38,10 @@ body {
margin: 4px; margin: 4px;
} }
#controls > a > img {
margin: 2px;
}
#controls > button { #controls > button {
line-height: 32px; line-height: 32px;
} }

25
web/viewer.html

@ -20,12 +20,12 @@
<body> <body>
<div id="controls"> <div id="controls">
<button id="previous" onclick="PDFView.page--;"> <button id="previous" onclick="PDFView.page--;" oncontextmenu="return false;">
<img src="images/go-up.svg" align="top" height="32"/> <img src="images/go-up.svg" align="top" height="32"/>
Previous Previous
</button> </button>
<button id="next" onclick="PDFView.page++;"> <button id="next" onclick="PDFView.page++;" oncontextmenu="return false;">
<img src="images/go-down.svg" align="top" height="32"/> <img src="images/go-down.svg" align="top" height="32"/>
Next Next
</button> </button>
@ -39,16 +39,16 @@
<div class="separator"></div> <div class="separator"></div>
<button id="next" title="Zoom Out" onclick="PDFView.zoomOut();"> <button id="zoomOut" title="Zoom Out" onclick="PDFView.zoomOut();" oncontextmenu="return false;">
<img src="images/zoom-out.svg" align="top" height="32"/> <img src="images/zoom-out.svg" align="top" height="32"/>
</button> </button>
<button id="next" title="Zoom In" onclick="PDFView.zoomIn();"> <button id="zoomIn" title="Zoom In" onclick="PDFView.zoomIn();" oncontextmenu="return false;">
<img src="images/zoom-in.svg" align="top" height="32"/> <img src="images/zoom-in.svg" align="top" height="32"/>
</button> </button>
<div class="separator"></div> <div class="separator"></div>
<select id="scaleSelect" onchange="PDFView.parseScale(this.value);"> <select id="scaleSelect" onchange="PDFView.parseScale(this.value);" oncontextmenu="return false;">
<option id="customScaleOption" value="custom"></option> <option id="customScaleOption" value="custom"></option>
<option value="0.5">50%</option> <option value="0.5">50%</option>
<option value="0.75">75%</option> <option value="0.75">75%</option>
@ -62,17 +62,26 @@
<div class="separator"></div> <div class="separator"></div>
<button id="print" onclick="window.print();"> <button id="print" onclick="window.print();" oncontextmenu="return false;">
<img src="images/document-print.svg" align="top" height="32"/> <img src="images/document-print.svg" align="top" height="32"/>
Print Print
</button> </button>
<button id="download" title="Download" onclick="PDFView.download();" oncontextmenu="return false;">
<img src="images/download.svg" align="top" height="32"/>
Download
</button>
<div class="separator"></div> <div class="separator"></div>
<input id="fileInput" type="file"/> <input id="fileInput" type="file" oncontextmenu="return false;"/>
<div class="separator"></div> <div class="separator"></div>
<a href="#" id="viewBookmark" title="Current View (bookmark or copy the location)">
<img src="images/bookmark.svg" alt="Bookmark" align="top" height="32"/>
</a>
<span id="info">--</span> <span id="info">--</span>
</div> </div>
@ -81,7 +90,7 @@
<div id="sidebarScrollView"> <div id="sidebarScrollView">
<div id="sidebarView"></div> <div id="sidebarView"></div>
</div> </div>
<div id="outlineScrollView" style="display:none"> <div id="outlineScrollView" hidden='true'>
<div id="outlineView"></div> <div id="outlineView"></div>
</div> </div>
<div id="sidebarControls"> <div id="sidebarControls">

139
web/viewer.js

@ -89,16 +89,23 @@ var PDFView = {
var pages = this.pages; var pages = this.pages;
var input = document.getElementById('pageNumber'); var input = document.getElementById('pageNumber');
if (!(0 < val && val <= pages.length)) { if (!(0 < val && val <= pages.length)) {
input.value = this.page; var event = document.createEvent('UIEvents');
event.initUIEvent('pagechange', false, false, window, 0);
event.pageNumber = this.page;
window.dispatchEvent(event);
return; return;
} }
currentPageNumber = val; currentPageNumber = val;
document.getElementById('previous').disabled = (val == 1); var event = document.createEvent('UIEvents');
document.getElementById('next').disabled = (val == pages.length); event.initUIEvent('pagechange', false, false, window, 0);
if (input.value != val) { event.pageNumber = val;
input.value = val; window.dispatchEvent(event);
}
// checking if the this.page was called from the updateViewarea function:
// avoiding the creation of two "set page" method (internal and public)
if (updateViewarea.inProgress)
return;
pages[val - 1].scrollIntoView(); pages[val - 1].scrollIntoView();
}, },
@ -108,10 +115,7 @@ var PDFView = {
}, },
open: function pdfViewOpen(url, scale) { open: function pdfViewOpen(url, scale) {
if (url.indexOf('http') == 0) document.title = this.url = url;
return;
document.title = url;
getPdf( getPdf(
{ {
@ -127,6 +131,10 @@ var PDFView = {
}); });
}, },
download: function pdfViewDownload() {
window.open(this.url + '?pdfjs.action=download', '_parent');
},
navigateTo: function pdfViewNavigateTo(dest) { navigateTo: function pdfViewNavigateTo(dest) {
if (typeof dest === 'string') if (typeof dest === 'string')
dest = this.destinations[dest]; dest = this.destinations[dest];
@ -147,12 +155,20 @@ var PDFView = {
if (typeof dest === 'string') if (typeof dest === 'string')
return '#' + escape(dest); return '#' + escape(dest);
if (dest instanceof Array) { if (dest instanceof Array) {
var destRef = dest[0]; // see nevigateTo method for dest format var destRef = dest[0]; // see navigateTo method for dest format
var pageNumber = destRef instanceof Object ? var pageNumber = destRef instanceof Object ?
this.pagesRefMap[destRef.num + ' ' + destRef.gen + ' R'] : this.pagesRefMap[destRef.num + ' ' + destRef.gen + ' R'] :
(destRef + 1); (destRef + 1);
if (pageNumber) { if (pageNumber) {
return '#page=' + pageNumber + '&dest=' + dest.slice(1).join(','); var pdfOpenParams = '#page=' + pageNumber;
if (isName(dest[1], 'XYZ')) {
var scale = (dest[4] || this.currentScale);
pdfOpenParams += '&zoom=' + (scale * 100);
if (dest[2] || dest[3]) {
pdfOpenParams += ',' + (dest[2] || 0) + ',' + (dest[3] || 0);
}
}
return pdfOpenParams;
} }
} }
return ''; return '';
@ -171,14 +187,16 @@ var PDFView = {
load: function pdfViewLoad(data, scale) { load: function pdfViewLoad(data, scale) {
var loadingIndicator = document.getElementById('loading'); var loadingIndicator = document.getElementById('loading');
loadingIndicator.style.display = 'none'; loadingIndicator.setAttribute('hidden', 'true');
var sidebar = document.getElementById('sidebarView'); var sidebar = document.getElementById('sidebarView');
sidebar.parentNode.scrollTop = 0; sidebar.parentNode.scrollTop = 0;
while (sidebar.hasChildNodes()) while (sidebar.hasChildNodes())
sidebar.removeChild(sidebar.lastChild); sidebar.removeChild(sidebar.lastChild);
clearInterval(sidebar._loadingInterval);
if ('_loadingInterval' in sidebar)
clearInterval(sidebar._loadingInterval);
var container = document.getElementById('viewer'); var container = document.getElementById('viewer');
while (container.hasChildNodes()) while (container.hasChildNodes())
@ -226,10 +244,32 @@ var PDFView = {
return; return;
if (hash.indexOf('=') >= 0) { if (hash.indexOf('=') >= 0) {
// TODO more complex hashes, for now catching page=XX only // parsing query string
var m = /\bpage=(\d+)/.exec(hash); var paramsPairs = hash.split('&');
if (m && m[1] > 0) var params = {};
this.page = m[1]; for (var i = 0; i < paramsPairs.length; ++i) {
var paramPair = paramsPairs[i].split('=');
params[paramPair[0]] = paramPair[1];
}
// borrowing syntax from "Parameters for Opening PDF Files"
if ('nameddest' in params) {
PDFView.navigateTo(params.nameddest);
return;
}
if ('page' in params) {
var pageNumber = (params.page | 0) || 1;
this.page = pageNumber;
if ('zoom' in params) {
var zoomArgs = params.zoom.split(','); // scale,left,top
// building destination array
var dest = [null, new Name('XYZ'), (zoomArgs[1] | 0),
(zoomArgs[2] | 0), (zoomArgs[0] | 0) / 100];
var currentPage = this.pages[pageNumber - 1];
currentPage.scrollIntoView(dest);
} else
this.page = page; // simple page
return;
}
} else if (/^\d+$/.test(hash)) // page number } else if (/^\d+$/.test(hash)) // page number
this.page = hash; this.page = hash;
else // named destination else // named destination
@ -243,14 +283,14 @@ var PDFView = {
var outlineSwitchButton = document.getElementById('outlineSwitch'); var outlineSwitchButton = document.getElementById('outlineSwitch');
switch (view) { switch (view) {
case 'thumbs': case 'thumbs':
thumbsScrollView.style.display = 'block'; thumbsScrollView.removeAttribute('hidden');
outlineScrollView.style.display = 'none'; outlineScrollView.setAttribute('hidden', 'true');
thumbsSwitchButton.setAttribute('data-selected', true); thumbsSwitchButton.setAttribute('data-selected', true);
outlineSwitchButton.removeAttribute('data-selected'); outlineSwitchButton.removeAttribute('data-selected');
break; break;
case 'outline': case 'outline':
thumbsScrollView.style.display = 'none'; thumbsScrollView.setAttribute('hidden', 'true');
outlineScrollView.style.display = 'block'; outlineScrollView.removeAttribute('hidden');
thumbsSwitchButton.removeAttribute('data-selected'); thumbsSwitchButton.removeAttribute('data-selected');
outlineSwitchButton.setAttribute('data-selected', true); outlineSwitchButton.setAttribute('data-selected', true);
break; break;
@ -339,6 +379,11 @@ var PageView = function pageView(container, content, id, pageWidth, pageHeight,
} }
} }
this.getPagePoint = function pageViewGetPagePoint(x, y) {
var scale = PDFView.currentScale;
return this.content.rotatePoint(x / scale, y / scale);
};
this.scrollIntoView = function pageViewScrollIntoView(dest) { this.scrollIntoView = function pageViewScrollIntoView(dest) {
if (!dest) { if (!dest) {
div.scrollIntoView(true); div.scrollIntoView(true);
@ -388,7 +433,7 @@ var PageView = function pageView(container, content, id, pageWidth, pageHeight,
this.content.rotatePoint(x + width, y + height) this.content.rotatePoint(x + width, y + height)
]; ];
if (scale) if (scale && scale !== PDFView.currentScale)
PDFView.setScale(scale, true); PDFView.setScale(scale, true);
setTimeout(function pageViewScrollIntoViewRelayout() { setTimeout(function pageViewScrollIntoViewRelayout() {
@ -544,26 +589,15 @@ window.addEventListener('load', function webViewerLoad(evt) {
params[unescape(param[0])] = unescape(param[1]); params[unescape(param[0])] = unescape(param[1]);
} }
PDFView.open(params.file || kDefaultURL, parseFloat(params.scale)); var scale = ('scale' in params) ? params.scale : kDefaultScale;
PDFView.open(params.file || kDefaultURL, parseFloat(scale));
if (!window.File || !window.FileReader || !window.FileList || !window.Blob) if (!window.File || !window.FileReader || !window.FileList || !window.Blob)
document.getElementById('fileInput').style.display = 'none'; document.getElementById('fileInput').setAttribute('hidden', 'true');
else else
document.getElementById('fileInput').value = null; document.getElementById('fileInput').value = null;
}, true); }, true);
window.addEventListener('pdfload', function webViewerPdfload(evt) {
PDFView.load(evt.detail);
}, true);
window.addEventListener('pdfprogress', function webViewerPdfProgress(evt) {
PDFView.progress(evt.detail);
}, true);
window.addEventListener('pdferror', function webViewerPdfError(evt) {
PDFView.error();
}, true);
function updateViewarea() { function updateViewarea() {
var visiblePages = PDFView.getVisiblePages(); var visiblePages = PDFView.getVisiblePages();
for (var i = 0; i < visiblePages.length; i++) { for (var i = 0; i < visiblePages.length; i++) {
@ -575,13 +609,21 @@ function updateViewarea() {
if (!visiblePages.length) if (!visiblePages.length)
return; return;
updateViewarea.inProgress = true; // used in "set page"
var currentId = PDFView.page; var currentId = PDFView.page;
var firstPage = visiblePages[0]; var firstPage = visiblePages[0];
var lastPage = visiblePages[visiblePages.length - 1]; PDFView.page = firstPage.id;
if (currentId > lastPage.id && lastPage.y > window.pageYOffset) updateViewarea.inProgress = false;
PDFView.page = lastPage.id;
else if (currentId < firstPage.id) var kViewerTopMargin = 52;
PDFView.page = firstPage.id; var pageNumber = firstPage.id;
var pdfOpenParams = '#page=' + pageNumber;
pdfOpenParams += '&zoom=' + Math.round(PDFView.currentScale * 100);
var currentPage = PDFView.pages[pageNumber - 1];
var topLeft = currentPage.getPagePoint(window.pageXOffset,
window.pageYOffset - firstPage.y - kViewerTopMargin);
pdfOpenParams += ',' + Math.round(topLeft.x) + ',' + Math.round(topLeft.y);
document.getElementById('viewBookmark').href = pdfOpenParams;
} }
window.addEventListener('scroll', function webViewerScroll(evt) { window.addEventListener('scroll', function webViewerScroll(evt) {
@ -622,6 +664,10 @@ window.addEventListener('change', function webViewerChange(evt) {
fileReader.readAsBinaryString(file); fileReader.readAsBinaryString(file);
document.title = file.name; document.title = file.name;
// URL does not reflect proper document location - hiding some icons.
document.getElementById('viewBookmark').setAttribute('hidden', 'true');
document.getElementById('download').setAttribute('hidden', 'true');
}, true); }, true);
window.addEventListener('transitionend', function webViewerTransitionend(evt) { window.addEventListener('transitionend', function webViewerTransitionend(evt) {
@ -672,10 +718,11 @@ window.addEventListener('scalechange', function scalechange(evt) {
}, true); }, true);
window.addEventListener('pagechange', function pagechange(evt) { window.addEventListener('pagechange', function pagechange(evt) {
var page = evt.detail; var page = evt.pageNumber;
document.getElementById('pageNumber').value = page; if (document.getElementById('pageNumber').value != page)
document.getElementById('previous').disabled = (page == 1); document.getElementById('pageNumber').value = page;
document.getElementById('next').disabled = (page == PDFView.pages.length); document.getElementById('previous').disabled = (page <= 1);
document.getElementById('next').disabled = (page >= PDFView.pages.length);
}, true); }, true);
window.addEventListener('keydown', function keydown(evt) { window.addEventListener('keydown', function keydown(evt) {

Loading…
Cancel
Save