Merge from gal's master branch (got a regression on the mapping between char->glyph)

This commit is contained in:
Vivien Nicolas 2011-06-16 03:05:55 +02:00
commit a57b53b3b3
3 changed files with 530 additions and 216 deletions

View File

@ -34,8 +34,8 @@ var Fonts = {
return this._active || { encoding: {} }; return this._active || { encoding: {} };
}, },
set active(aFontName) { set active(aName) {
this._active = this[aFontName]; this._active = this[aName];
}, },
unicodeFromCode: function fonts_unicodeFromCode(aCode) { unicodeFromCode: function fonts_unicodeFromCode(aCode) {

644
pdf.js
View File

@ -607,7 +607,7 @@ function IsString(v) {
} }
function IsNull(v) { function IsNull(v) {
return v == null; return v === null;
} }
function IsName(v) { function IsName(v) {
@ -634,7 +634,7 @@ function IsRef(v) {
return v instanceof Ref; return v instanceof Ref;
} }
function IsFunction(v) { function IsPDFFunction(v) {
var fnDict; var fnDict;
if (typeof v != "object") if (typeof v != "object")
return false; return false;
@ -647,14 +647,6 @@ function IsFunction(v) {
return fnDict.has("FunctionType"); return fnDict.has("FunctionType");
} }
function IsFunctionDict(v) {
return IsFunction(v) && IsDict(v);
}
function IsFunctionStream(v) {
return IsFunction(v) && IsStream(v);
}
var EOF = {}; var EOF = {};
function IsEOF(v) { function IsEOF(v) {
@ -861,10 +853,12 @@ var Lexer = (function() {
ch = stream.getChar(); ch = stream.getChar();
if (ch == '>') { if (ch == '>') {
break; break;
} else if (!ch) { }
if (!ch) {
warn("Unterminated hex string"); warn("Unterminated hex string");
break; break;
} else if (specialChars[ch.charCodeAt(0)] != 1) { }
if (specialChars[ch.charCodeAt(0)] != 1) {
var x, x2; var x, x2;
if (((x = ToHexDigit(ch)) == -1) || if (((x = ToHexDigit(ch)) == -1) ||
((x2 = ToHexDigit(stream.getChar())) == -1)) { ((x2 = ToHexDigit(stream.getChar())) == -1)) {
@ -1394,8 +1388,8 @@ var Page = (function() {
} }
constructor.prototype = { constructor.prototype = {
get contents() { get content() {
return shadow(this, "contents", this.pageDict.get("Contents")); return shadow(this, "content", this.pageDict.get("Contents"));
}, },
get resources() { get resources() {
return shadow(this, "resources", this.pageDict.get("Resources")); return shadow(this, "resources", this.pageDict.get("Resources"));
@ -1406,47 +1400,25 @@ var Page = (function() {
? obj ? obj
: null)); : null));
}, },
get fonts() { compile: function(gfx, fonts) {
var xref = this.xref; if (!this.code) {
var resources = xref.fetchIfRef(this.resources); var xref = this.xref;
var fontsDict = new Dict(); var content = xref.fetchIfRef(this.content);
var resources = xref.fetchIfRef(this.resources);
// Get the fonts use on the page if any this.code = gfx.compile(content, xref, resources, fonts);
var fontResources = resources.get("Font");
if (IsDict(fontResources)) {
fontResources.forEach(function(fontKey, fontData) {
fontsDict.set(fontKey, xref.fetch(fontData))
});
} }
// Get the fonts use on xobjects of the page if any
var xobjs = xref.fetchIfRef(resources.get("XObject"));
if (xobjs) {
xobjs.forEach(function(key, xobj) {
xobj = xref.fetchIfRef(xobj);
assertWellFormed(IsStream(xobj), "XObject should be a stream");
var xobjFonts = xobj.dict.get("Resources").get("Font");
xobjFonts.forEach(function(fontKey, fontData) {
fontsDict.set(fontKey, xref.fetch(fontData))
});
});
}
return shadow(this, "fonts", fontsDict);
}, },
display: function(gfx) { display: function(gfx) {
var xref = this.xref; var xref = this.xref;
var contents = xref.fetchIfRef(this.contents); var content = xref.fetchIfRef(this.content);
var resources = xref.fetchIfRef(this.resources); var resources = xref.fetchIfRef(this.resources);
var mediaBox = xref.fetchIfRef(this.mediaBox); var mediaBox = xref.fetchIfRef(this.mediaBox);
assertWellFormed(IsStream(contents) && IsDict(resources), assertWellFormed(IsStream(content) && IsDict(resources),
"invalid page contents or resources"); "invalid page content or resources");
gfx.beginDrawing({ x: mediaBox[0], y: mediaBox[1], gfx.beginDrawing({ x: mediaBox[0], y: mediaBox[1],
width: mediaBox[2] - mediaBox[0], width: mediaBox[2] - mediaBox[0],
height: mediaBox[3] - mediaBox[1] }); height: mediaBox[3] - mediaBox[1] });
gfx.interpret(new Parser(new Lexer(contents), false), gfx.execute(this.code, xref, resources);
xref, resources);
gfx.endDrawing(); gfx.endDrawing();
} }
}; };
@ -1655,68 +1627,96 @@ var CanvasGraphics = (function() {
this.xobjs = null; this.xobjs = null;
this.map = { this.map = {
// Graphics state // Graphics state
w: this.setLineWidth, w: "setLineWidth",
J: this.setLineCap, J: "setLineCap",
j: this.setLineJoin, j: "setLineJoin",
d: this.setDash, M: "setMiterLimit",
ri: this.setRenderingIntent, d: "setDash",
i: this.setFlatness, ri: "setRenderingIntent",
gs: this.setGState, i: "setFlatness",
q: this.save, gs: "setGState",
Q: this.restore, q: "save",
cm: this.transform, Q: "restore",
cm: "transform",
// Path // Path
m: this.moveTo, m: "moveTo",
l: this.lineTo, l: "lineTo",
c: this.curveTo, c: "curveTo",
h: this.closePath, v: "curveTo2",
re: this.rectangle, y: "curveTo3",
S: this.stroke, h: "closePath",
f: this.fill, re: "rectangle",
"f*": this.eoFill, S: "stroke",
B: this.fillStroke, s: "closeStroke",
b: this.closeFillStroke, f: "fill",
n: this.endPath, "f*": "eoFill",
B: "fillStroke",
"B*": "eoFillStroke",
b: "closeFillStroke",
"b*": "closeEOFillStroke",
n: "endPath",
// Clipping // Clipping
W: this.clip, W: "clip",
"W*": this.eoClip, "W*": "eoClip",
// Text // Text
BT: this.beginText, BT: "beginText",
ET: this.endText, ET: "endText",
TL: this.setLeading, Tc: "setCharSpacing",
Tf: this.setFont, Tw: "setWordSpacing",
Td: this.moveText, Tz: "setHScale",
Tm: this.setTextMatrix, TL: "setLeading",
"T*": this.nextLine, Tf: "setFont",
Tj: this.showText, Tr: "setTextRenderingMode",
TJ: this.showSpacedText, Ts: "setTextRise",
Td: "moveText",
TD: "setLeadingMoveText",
Tm: "setTextMatrix",
"T*": "nextLine",
Tj: "showText",
TJ: "showSpacedText",
"'": "nextLineShowText",
'"': "nextLineSetSpacingShowText",
// Type3 fonts // Type3 fonts
d0: "setCharWidth",
d1: "setCharWidthAndBounds",
// Color // Color
CS: this.setStrokeColorSpace, CS: "setStrokeColorSpace",
cs: this.setFillColorSpace, cs: "setFillColorSpace",
SC: this.setStrokeColor, SC: "setStrokeColor",
SCN: this.setStrokeColorN, SCN: "setStrokeColorN",
sc: this.setFillColor, sc: "setFillColor",
scn: this.setFillColorN, scn: "setFillColorN",
G: this.setStrokeGray, G: "setStrokeGray",
g: this.setFillGray, g: "setFillGray",
RG: this.setStrokeRGBColor, RG: "setStrokeRGBColor",
rg: this.setFillRGBColor, rg: "setFillRGBColor",
K: "setStrokeCMYKColor",
k: "setFillCMYKColor",
// Shading // Shading
sh: this.shadingFill, sh: "shadingFill",
// Images // Images
BI: "beginInlineImage",
// XObjects // XObjects
Do: this.paintXObject, Do: "paintXObject",
// Marked content // Marked content
MP: "markPoint",
DP: "markPointProps",
BMC: "beginMarkedContent",
BDC: "beginMarkedContentProps",
EMC: "endMarkedContent",
// Compatibility // Compatibility
BX: "beginCompat",
EX: "endCompat",
}; };
} }
@ -1726,6 +1726,52 @@ var CanvasGraphics = (function() {
const EO_CLIP = {}; const EO_CLIP = {};
constructor.prototype = { constructor.prototype = {
translateFont: function(fontDict, xref, resources) {
var descriptor = xref.fetch(fontDict.get("FontDescriptor"));
var fontName = descriptor.get("FontName").name;
fontName = fontName.replace("+", "_");
var font = Fonts[fontName];
if (!font) {
var fontFile = descriptor.get2("FontFile", "FontFile2");
fontFile = xref.fetchIfRef(fontFile);
// Generate the custom cmap of the font if needed
var encodingMap = {};
if (fontDict.has("Encoding")) {
var encoding = xref.fetchIfRef(fontDict.get("Encoding"));
if (IsDict(encoding)) {
// Build an map between codes and glyphs
var differences = encoding.get("Differences");
var index = 0;
for (var j = 0; j < differences.length; j++) {
var data = differences[j];
IsNum(data) ? index = data : encodingMap[index++] = data;
}
// Get the font charset
var charset = descriptor.get("CharSet").split("/");
} else if (IsName(encoding)) {
var encoding = Encodings[encoding];
var widths = xref.fetchIfRef(fontDict.get("Widths"));
var firstchar = xref.fetchIfRef(fontDict.get("FirstChar"));
var charset = [];
for (var j = 0; j < widths.length; j++) {
var index = widths[j];
if (index)
charset.push(encoding[j + firstchar]);
}
}
}
var fontBBox = descriptor.get("FontBBox");
var subtype = fontDict.get("Subtype").name;
new Font(fontName, fontFile, encodingMap, charset, fontBBox, subtype);
}
return Fonts[fontName];
},
beginDrawing: function(mediaBox) { beginDrawing: function(mediaBox) {
var cw = this.ctx.canvas.width, ch = this.ctx.canvas.height; var cw = this.ctx.canvas.width, ch = this.ctx.canvas.height;
this.ctx.save(); this.ctx.save();
@ -1733,12 +1779,35 @@ var CanvasGraphics = (function() {
this.ctx.translate(0, -mediaBox.height); this.ctx.translate(0, -mediaBox.height);
}, },
interpret: function(parser, xref, resources) { execute: function(code, xref, resources) {
var savedXref = this.xref, savedRes = this.res, savedXobjs = this.xobjs; var savedXref = this.xref, savedRes = this.res, savedXobjs = this.xobjs;
this.xref = xref; this.xref = xref;
this.res = resources || new Dict(); this.res = resources || new Dict();
this.xobjs = this.res.get("XObject") || new Dict(); this.xobjs = xref.fetchIfRef(this.res.get("XObject")) || new Dict();
this.xobjs = this.xref.fetchIfRef(this.xobjs);
code(this);
this.xobjs = savedXobjs;
this.res = savedRes;
this.xref = savedXref;
},
compile: function(stream, xref, resources, fonts) {
var xobjs = xref.fetchIfRef(resources.get("XObject")) || new Dict();
var parser = new Parser(new Lexer(stream), false);
var objpool = [];
function emitArg(arg) {
if (typeof arg == "object" || typeof arg == "string") {
var index = objpool.length;
objpool[index] = arg;
return "objpool[" + index + "]";
}
return arg;
}
var src = "";
var args = []; var args = [];
var map = this.map; var map = this.map;
@ -1749,7 +1818,46 @@ var CanvasGraphics = (function() {
var fn = map[cmd]; var fn = map[cmd];
assertWellFormed(fn, "Unknown command '" + cmd + "'"); assertWellFormed(fn, "Unknown command '" + cmd + "'");
// TODO figure out how to type-check vararg functions // TODO figure out how to type-check vararg functions
fn.apply(this, args);
if (cmd == "Do" && !args[0].code) { // eagerly compile XForm objects
var name = args[0].name;
var xobj = xobjs.get(name);
if (xobj) {
xobj = xref.fetchIfRef(xobj);
assertWellFormed(IsStream(xobj), "XObject should be a stream");
var type = xobj.dict.get("Subtype");
assertWellFormed(IsName(type), "XObject should have a Name subtype");
if ("Form" == type.name) {
args[0].code = this.compile(xobj,
xref,
xobj.dict.get("Resources"),
fonts);
}
}
} else if (cmd == "Tf") { // eagerly collect all fonts
var fontRes = resources.get("Font");
if (fontRes) {
fontRes = xref.fetchIfRef(fontRes);
var font = xref.fetchIfRef(fontRes.get(args[0].name));
assertWellFormed(IsDict(font));
if (!font.translated) {
font.translated = this.translateFont(font, xref, resources);
if (fonts && font.translated) {
// keep track of each font we translated so the caller can
// load them asynchronously before calling display on a page
fonts.push(font.translated);
}
}
}
}
src += "this.";
src += fn;
src += "(";
src += args.map(emitArg).join(",");
src += ");\n";
args.length = 0; args.length = 0;
} else { } else {
@ -1758,9 +1866,8 @@ var CanvasGraphics = (function() {
} }
} }
this.xobjs = savedXobjs; var fn = Function("objpool", src);
this.res = savedRes; return function (gfx) { fn.call(gfx, objpool); };
this.xref = savedXref;
}, },
endDrawing: function() { endDrawing: function() {
@ -1777,8 +1884,12 @@ var CanvasGraphics = (function() {
setLineJoin: function(style) { setLineJoin: function(style) {
this.ctx.lineJoin = LINE_JOIN_STYLES[style]; this.ctx.lineJoin = LINE_JOIN_STYLES[style];
}, },
setMiterLimit: function(limit) {
this.ctx.miterLimit = limit;
},
setDash: function(dashArray, dashPhase) { setDash: function(dashArray, dashPhase) {
TODO("set dash"); this.ctx.mozDash = dashArray;
this.ctx.mozDashOffset = dashPhase;
}, },
setRenderingIntent: function(intent) { setRenderingIntent: function(intent) {
TODO("set rendering intent"); TODO("set rendering intent");
@ -1815,6 +1926,12 @@ var CanvasGraphics = (function() {
curveTo: function(x1, y1, x2, y2, x3, y3) { curveTo: function(x1, y1, x2, y2, x3, y3) {
this.ctx.bezierCurveTo(x1, y1, x2, y2, x3, y3); this.ctx.bezierCurveTo(x1, y1, x2, y2, x3, y3);
}, },
curveTo2: function(x2, y2, x3, y3) {
TODO("'v' operator: need current point in gfx context");
},
curveTo3: function(x1, y1, x3, y3) {
this.curveTo(x1, y1, x3, y3, x3, y3);
},
closePath: function() { closePath: function() {
this.ctx.closePath(); this.ctx.closePath();
}, },
@ -1825,6 +1942,10 @@ var CanvasGraphics = (function() {
this.ctx.stroke(); this.ctx.stroke();
this.consumePath(); this.consumePath();
}, },
closeStroke: function() {
this.closePath();
this.stroke();
},
fill: function() { fill: function() {
this.ctx.fill(); this.ctx.fill();
this.consumePath(); this.consumePath();
@ -1839,9 +1960,19 @@ var CanvasGraphics = (function() {
this.ctx.stroke(); this.ctx.stroke();
this.consumePath(); this.consumePath();
}, },
eoFillStroke: function() {
var savedFillRule = this.setEOFillRule();
this.fillStroke();
this.restoreFillRule(savedFillRule);
},
closeFillStroke: function() { closeFillStroke: function() {
return this.fillStroke(); return this.fillStroke();
}, },
closeEOFillStroke: function() {
var savedFillRule = this.setEOFillRule();
this.fillStroke();
this.restoreFillRule(savedFillRule);
},
endPath: function() { endPath: function() {
this.consumePath(); this.consumePath();
}, },
@ -1862,6 +1993,15 @@ var CanvasGraphics = (function() {
}, },
endText: function() { endText: function() {
}, },
setCharSpacing: function(spacing) {
TODO("character (glyph?) spacing");
},
setWordSpacing: function(spacing) {
TODO("word spacing");
},
setHSpacing: function(scale) {
TODO("horizontal text scale");
},
setLeading: function(leading) { setLeading: function(leading) {
this.current.leading = leading; this.current.leading = leading;
}, },
@ -1886,10 +2026,20 @@ var CanvasGraphics = (function() {
this.current.fontSize = size; this.current.fontSize = size;
this.ctx.font = this.current.fontSize +'px "' + fontName + '"'; this.ctx.font = this.current.fontSize +'px "' + fontName + '"';
}, },
setTextRenderingMode: function(mode) {
TODO("text rendering mode");
},
setTextRise: function(rise) {
TODO("text rise");
},
moveText: function (x, y) { moveText: function (x, y) {
this.current.x = this.current.lineX += x; this.current.x = this.current.lineX += x;
this.current.y = this.current.lineY += y; this.current.y = this.current.lineY += y;
}, },
setLeadingMoveText: function(x, y) {
this.setLeading(-y);
this.moveText(x, y);
},
setTextMatrix: function(a, b, c, d, e, f) { setTextMatrix: function(a, b, c, d, e, f) {
this.current.textMatrix = [ a, b, c, d, e, f ]; this.current.textMatrix = [ a, b, c, d, e, f ];
this.current.x = this.current.lineX = 0; this.current.x = this.current.lineX = 0;
@ -1921,8 +2071,23 @@ var CanvasGraphics = (function() {
} }
} }
}, },
nextLineShowText: function(text) {
this.nextLine();
this.showText(text);
},
nextLineSetSpacingShowText: function(wordSpacing, charSpacing, text) {
this.setWordSpacing(wordSpacing);
this.setCharSpacing(charSpacing);
this.nextLineShowText(text);
},
// Type3 fonts // Type3 fonts
setCharWidth: function(xWidth, yWidth) {
TODO("type 3 fonts ('d0' operator)");
},
setCharWidthAndBounds: function(xWidth, yWidth, llx, lly, urx, ury) {
TODO("type 3 fonts ('d1' operator)");
},
// Color // Color
setStrokeColorSpace: function(space) { setStrokeColorSpace: function(space) {
@ -1967,19 +2132,25 @@ var CanvasGraphics = (function() {
setFillRGBColor: function(r, g, b) { setFillRGBColor: function(r, g, b) {
this.ctx.fillStyle = this.makeCssRgb(r, g, b); this.ctx.fillStyle = this.makeCssRgb(r, g, b);
}, },
setStrokeCMYKColor: function(c, m, y, k) {
TODO("CMYK space");
},
setFillCMYKColor: function(c, m, y, k) {
TODO("CMYK space");
},
// Shading // Shading
shadingFill: function(entryRef) { shadingFill: function(entryRef) {
var shadingRes = this.res.get("Shading"); var xref = this.xref;
var res = this.res;
var shadingRes = xref.fetchIfRef(res.get("Shading"));
if (!shadingRes) if (!shadingRes)
return; error("No shading resource found");
shadingRes = this.xref.fetchIfRef(shadingRes);
var shading = shadingRes.get(entryRef.name); var shading = xref.fetchIfRef(shadingRes.get(entryRef.name));
if (!shading) if (!shading)
return; error("No shading object found");
shading = this.xref.fetchIfRef(shading);
if (!shading)
return;
this.save(); this.save();
@ -1990,32 +2161,30 @@ var CanvasGraphics = (function() {
this.endPath(); this.endPath();
} }
var cs = shading.get2("ColorSpace", "CS");
TODO("shading-fill color space"); TODO("shading-fill color space");
var type = shading.get("ShadingType"); var background = shading.get("Background");
switch (type) { if (background)
case 1: TODO("handle background colors");
this.fillFunctionShading(shading);
break; const types = [null, this.fillFunctionShading,
case 2: this.fillAxialShading, this.fillRadialShading];
this.fillAxialShading(shading);
break; var typeNum = shading.get("ShadingType");
case 3: var fillFn = types[typeNum];
this.fillRadialShading(shading); if (!fillFn)
break; error("Unknown type of shading");
case 4: case 5: case 6: case 7: fillFn.apply(this, [shading]);
TODO("shading fill type "+ type);
default:
malformed("Unknown shading type "+ type);
}
this.restore(); this.restore();
}, },
fillAxialShading: function(sh) { fillAxialShading: function(sh) {
var coordsArr = sh.get("Coords"); var coordsArr = sh.get("Coords");
var x0 = coordsArr[0], y0 = coordsArr[1], var x0 = coordsArr[0], y0 = coordsArr[1],
x1 = coordsArr[2], y1 = coordsArr[3]; x1 = coordsArr[2], y1 = coordsArr[3];
var t0 = 0.0, t1 = 1.0; var t0 = 0.0, t1 = 1.0;
if (sh.has("Domain")) { if (sh.has("Domain")) {
var domainArr = sh.get("Domain"); var domainArr = sh.get("Domain");
@ -2026,19 +2195,39 @@ var CanvasGraphics = (function() {
if (sh.has("Extend")) { if (sh.has("Extend")) {
var extendArr = sh.get("Extend"); var extendArr = sh.get("Extend");
extendStart = extendArr[0], extendEnd = extendArr[1]; extendStart = extendArr[0], extendEnd = extendArr[1];
TODO("Support extend");
} }
var fnObj = sh.get("Function");
var fn = sh.get("Function"); fnObj = this.xref.fetchIfRef(fnObj);
fn = this.xref.fetchIfRef(fn); if (IsArray(fnObj))
error("No support for array of functions");
else if (!IsPDFFunction(fnObj))
error("Invalid function");
fn = new PDFFunction(this.xref, fnObj);
var gradient = this.ctx.createLinearGradient(x0, y0, x1, y1); var gradient = this.ctx.createLinearGradient(x0, y0, x1, y1);
var step = (t1 - t0) / 10;
gradient.addColorStop(0, 'rgb(0,0,255)');
gradient.addColorStop(1, 'rgb(0,255,0)'); for (var i = t0; i <= t1; i += step) {
var c = fn.func([i]);
gradient.addColorStop(i, this.makeCssRgb.apply(this, c));
}
this.ctx.fillStyle = gradient; this.ctx.fillStyle = gradient;
this.ctx.fill();
this.consumePath(); // 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
this.ctx.fillRect(-1e10, -1e10, 2e10, 2e10);
},
// Images
beginInlineImage: function() {
TODO("inline images");
error("(Stream will not be parsed properly, bailing now)");
// Like an inline stream:
// - key/value pairs up to Cmd(ID)
// - then image data up to Cmd(EI)
}, },
// XObjects // XObjects
@ -2062,9 +2251,9 @@ var CanvasGraphics = (function() {
var type = xobj.dict.get("Subtype"); var type = xobj.dict.get("Subtype");
assertWellFormed(IsName(type), "XObject should have a Name subtype"); assertWellFormed(IsName(type), "XObject should have a Name subtype");
if ("Image" == type.name) { if ("Image" == type.name) {
this.paintImageXObject(xobj, false); this.paintImageXObject(obj, xobj, false);
} else if ("Form" == type.name) { } else if ("Form" == type.name) {
this.paintFormXObject(xobj); this.paintFormXObject(obj, xobj);
} else if ("PS" == type.name) { } else if ("PS" == type.name) {
warn("(deprecated) PostScript XObjects are not supported"); warn("(deprecated) PostScript XObjects are not supported");
} else { } else {
@ -2072,27 +2261,26 @@ var CanvasGraphics = (function() {
} }
}, },
paintFormXObject: function(form) { paintFormXObject: function(ref, stream) {
this.save(); this.save();
var matrix = form.dict.get("Matrix"); var matrix = stream.dict.get("Matrix");
if (matrix && IsArray(matrix) && 6 == matrix.length) if (matrix && IsArray(matrix) && 6 == matrix.length)
this.transform.apply(this, matrix); this.transform.apply(this, matrix);
var bbox = form.dict.get("BBox"); var bbox = stream.dict.get("BBox");
if (bbox && IsArray(bbox) && 4 == bbox.length) { if (bbox && IsArray(bbox) && 4 == bbox.length) {
this.rectangle.apply(this, bbox); this.rectangle.apply(this, bbox);
this.clip(); this.clip();
this.endPath(); this.endPath();
} }
this.interpret(new Parser(new Lexer(form), false), this.execute(ref.code, this.xref, stream.dict.get("Resources"));
this.xref, form.dict.get("Resources"));
this.restore(); this.restore();
}, },
paintImageXObject: function(image, inline) { paintImageXObject: function(ref, image, inline) {
this.save(); this.save();
if (image.getParams) { if (image.getParams) {
// JPX/JPEG2000 streams directly contain bits per component // JPX/JPEG2000 streams directly contain bits per component
@ -2269,6 +2457,33 @@ var CanvasGraphics = (function() {
this.restore(); this.restore();
}, },
// Marked content
markPoint: function(tag) {
TODO("Marked content");
},
markPointProps: function(tag, properties) {
TODO("Marked content");
},
beginMarkedContent: function(tag) {
TODO("Marked content");
},
beginMarkedContentProps: function(tag, properties) {
TODO("Marked content");
},
endMarkedContent: function() {
TODO("Marked content");
},
// Compatibility
beginCompat: function() {
TODO("ignore undefined operators (should we do that anyway?)");
},
endCompat: function() {
TODO("stop ignoring undefined operators");
},
// Helper functions // Helper functions
consumePath: function() { consumePath: function() {
@ -2351,3 +2566,154 @@ var ColorSpace = (function() {
return constructor; return constructor;
})(); })();
var PDFFunction = (function() {
function constructor(xref, fn) {
var dict = fn.dict;
if (!dict)
dict = fn;
const types = [this.constructSampled, null,
this.constructInterpolated, this.constructStiched,
this.constructPostScript];
var typeNum = dict.get("FunctionType");
var typeFn = types[typeNum];
if (!typeFn)
error("Unknown type of function");
typeFn.apply(this, [fn, dict]);
};
constructor.prototype = {
constructSampled: function(str, dict) {
var domain = dict.get("Domain");
var range = dict.get("Range");
if (!domain || !range)
error("No domain or range");
var inputSize = domain.length / 2;
var outputSize = range.length / 2;
if (inputSize != 1)
error("No support for multi-variable inputs to functions");
var size = dict.get("Size");
var bps = dict.get("BitsPerSample");
var order = dict.get("Order");
if (!order)
order = 1;
if (order !== 1)
error ("No support for cubic spline interpolation");
var encode = dict.get("Encode");
if (!encode) {
encode = [];
for (var i = 0; i < inputSize; ++i) {
encode.push(0);
encode.push(size[i] - 1);
}
}
var decode = dict.get("Decode");
if (!decode)
decode = range;
var samples = this.getSampleArray(size, outputSize, bps, str);
this.func = function(args) {
var clip = function(v, min, max) {
if (v > max)
v = max;
else if (v < min)
v = min
return v;
}
if (inputSize != args.length)
error("Incorrect number of arguments");
for (var i = 0; i < inputSize; i++) {
var i2 = i * 2;
// clip to the domain
var v = clip(args[i], domain[i2], domain[i2 + 1]);
// encode
v = encode[i2] + ((v - domain[i2]) *
(encode[i2 + 1] - encode[i2]) /
(domain[i2 + 1] - domain[i2]));
// clip to the size
args[i] = clip(v, 0, size[i] - 1);
}
// interpolate to table
TODO("Multi-dimensional interpolation");
var floor = Math.floor(args[0]);
var ceil = Math.ceil(args[0]);
var scale = args[0] - floor;
floor *= outputSize;
ceil *= outputSize;
var output = [];
for (var i = 0; i < outputSize; ++i) {
if (ceil == floor) {
var v = samples[ceil + i];
} else {
var low = samples[floor + i];
var high = samples[ceil + i];
var v = low * scale + high * (1 - scale);
}
var i2 = i * 2;
// decode
v = decode[i2] + (v * (decode[i2 + 1] - decode[i2]) /
((1 << bps) - 1));
// clip to the domain
output.push(clip(v, range[i2], range[i2 + 1]));
}
return output;
}
},
getSampleArray: function(size, outputSize, bps, str) {
var length = 1;
for (var i = 0; i < size.length; i++)
length *= size[i];
length *= outputSize;
var array = [];
var codeSize = 0;
var codeBuf = 0;
var strBytes = str.getBytes((length * bps + 7) / 8);
var strIdx = 0;
for (var i = 0; i < length; i++) {
var b;
while (codeSize < bps) {
codeBuf <<= 8;
codeBuf |= strBytes[strIdx++];
codeSize += 8;
}
codeSize -= bps
array.push(codeBuf >> codeSize);
codeBuf &= (1 << codeSize) - 1;
}
return array;
},
constructInterpolated: function() {
error("unhandled type of function");
},
constructStiched: function() {
error("unhandled type of function");
},
constructPostScript: function() {
error("unhandled type of function");
}
};
return constructor;
})();

98
test.js
View File

@ -56,87 +56,35 @@ function displayPage(num) {
var page = pdfDocument.getPage(pageNum = num); var page = pdfDocument.getPage(pageNum = num);
function display() { var t1 = Date.now();
var t1 = Date.now();
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 ctx = canvas.getContext("2d");
page.display(gfx); ctx.save();
ctx.fillStyle = "rgb(255, 255, 255)";
ctx.fillRect(0, 0, canvas.width, canvas.height);
ctx.restore();
var t2 = Date.now(); var gfx = new CanvasGraphics(ctx);
var infoDisplay = document.getElementById("info");
infoDisplay.innerHTML = "Time to render: "+ (t1 - t0) + "/" + (t2 - t1) + " ms";
}
// Loading a font via data uri is asynchronous, so wait for all font // page.compile will collect all fonts for us, once we have loaded them
// of the page to be fully loaded before loading the page // we can trigger the actual page rendering with page.display
var fontsReady = true; var fonts = [];
var fonts = page.fonts;
var xref = page.xref; page.compile(gfx, fonts);
fonts.forEach(function(fontKey, fontDict) { var t2 = Date.now();
var descriptor = xref.fetch(fontDict.get("FontDescriptor"));
var fontName = descriptor.get("FontName").name;
fontName = fontName.replace("+", "_");
// Check if the font has been loaded or is still loading var interval = setInterval(function() {
var font = Fonts[fontName]; for (var i = 0; i < fonts.length; i++) {
if (!font) { if (fonts[i].loading)
var fontFile = descriptor.get2("FontFile", "FontFile2"); return;
fontFile = xref.fetchIfRef(fontFile);
// Generate the custom cmap of the font if needed
var encodingMap = {};
if (fontDict.has("Encoding")) {
var encoding = xref.fetchIfRef(fontDict.get("Encoding"));
if (IsDict(encoding)) {
// Build an map between codes and glyphs
var differences = encoding.get("Differences");
var index = 0;
for (var j = 0; j < differences.length; j++) {
var data = differences[j];
IsNum(data) ? index = data : encodingMap[index++] = data;
}
// Get the font charset
var charset = descriptor.get("CharSet").split("/");
} else if (IsName(encoding)) {
var encoding = Encodings[encoding];
var widths = xref.fetchIfRef(fontDict.get("Widths"));
var firstchar = xref.fetchIfRef(fontDict.get("FirstChar"));
var charset = [];
for (var j = 0; j < widths.length; j++) {
var index = widths[j];
if (index)
charset.push(encoding[j + firstchar]);
}
}
}
var fontBBox = descriptor.get("FontBBox");
var subtype = fontDict.get("Subtype").name;
new Font(fontName, fontFile, encodingMap, charset, fontBBox, subtype);
return fontsReady = false;
} else if (font.loading) {
return fontsReady = false;
} }
});
// If everything is ready do not delayed the page loading any more page.display(gfx);
if (fontsReady) var t3 = Date.now();
display(); var infoDisplay = document.getElementById("info");
else { infoDisplay.innerHTML = "Time to load/compile/render: "+ (t1 - t0) + "/" + (t2 - t1) + "/" + (t3 - t2) + " ms";
// FIXME Relying on an event seems much more cleaner here instead clearInterval(interval);
// of a setTimeout... }, 10);
pageTimeout = window.setTimeout(displayPage, 150, num);
}
} }
function nextPage() { function nextPage() {