25 changed files with 7581 additions and 7588 deletions
@ -0,0 +1,837 @@ |
|||||||
|
// <canvas> contexts store most of the state we need natively.
|
||||||
|
// However, PDF needs a bit more state, which we store here.
|
||||||
|
var CanvasExtraState = (function canvasExtraState() { |
||||||
|
function constructor(old) { |
||||||
|
// Are soft masks and alpha values shapes or opacities?
|
||||||
|
this.alphaIsShape = false; |
||||||
|
this.fontSize = 0; |
||||||
|
this.textMatrix = IDENTITY_MATRIX; |
||||||
|
this.leading = 0; |
||||||
|
// Current point (in user coordinates)
|
||||||
|
this.x = 0; |
||||||
|
this.y = 0; |
||||||
|
// Start of text line (in text coordinates)
|
||||||
|
this.lineX = 0; |
||||||
|
this.lineY = 0; |
||||||
|
// Character and word spacing
|
||||||
|
this.charSpacing = 0; |
||||||
|
this.wordSpacing = 0; |
||||||
|
this.textHScale = 1; |
||||||
|
// Color spaces
|
||||||
|
this.fillColorSpaceObj = null; |
||||||
|
this.strokeColorSpaceObj = null; |
||||||
|
this.fillColorObj = null; |
||||||
|
this.strokeColorObj = null; |
||||||
|
// Default fore and background colors
|
||||||
|
this.fillColor = '#000000'; |
||||||
|
this.strokeColor = '#000000'; |
||||||
|
|
||||||
|
this.old = old; |
||||||
|
} |
||||||
|
|
||||||
|
constructor.prototype = { |
||||||
|
clone: function canvasextra_clone() { |
||||||
|
return Object.create(this); |
||||||
|
}, |
||||||
|
setCurrentPoint: function canvasextra_setCurrentPoint(x, y) { |
||||||
|
this.x = x; |
||||||
|
this.y = y; |
||||||
|
} |
||||||
|
}; |
||||||
|
return constructor; |
||||||
|
})(); |
||||||
|
|
||||||
|
function ScratchCanvas(width, height) { |
||||||
|
var canvas = document.createElement('canvas'); |
||||||
|
canvas.width = width; |
||||||
|
canvas.height = height; |
||||||
|
return canvas; |
||||||
|
} |
||||||
|
|
||||||
|
var CanvasGraphics = (function canvasGraphics() { |
||||||
|
// Defines the time the executeIRQueue is going to be executing
|
||||||
|
// before it stops and shedules a continue of execution.
|
||||||
|
var kExecutionTime = 50; |
||||||
|
|
||||||
|
// Number of IR commands to execute before checking
|
||||||
|
// if we execute longer then `kExecutionTime`.
|
||||||
|
var kExecutionTimeCheck = 500; |
||||||
|
|
||||||
|
function constructor(canvasCtx, objs) { |
||||||
|
this.ctx = canvasCtx; |
||||||
|
this.current = new CanvasExtraState(); |
||||||
|
this.stateStack = []; |
||||||
|
this.pendingClip = null; |
||||||
|
this.res = null; |
||||||
|
this.xobjs = null; |
||||||
|
this.ScratchCanvas = ScratchCanvas; |
||||||
|
this.objs = objs; |
||||||
|
} |
||||||
|
|
||||||
|
var LINE_CAP_STYLES = ['butt', 'round', 'square']; |
||||||
|
var LINE_JOIN_STYLES = ['miter', 'round', 'bevel']; |
||||||
|
var NORMAL_CLIP = {}; |
||||||
|
var EO_CLIP = {}; |
||||||
|
|
||||||
|
constructor.prototype = { |
||||||
|
beginDrawing: function canvasGraphicsBeginDrawing(mediaBox) { |
||||||
|
var cw = this.ctx.canvas.width, ch = this.ctx.canvas.height; |
||||||
|
this.ctx.save(); |
||||||
|
switch (mediaBox.rotate) { |
||||||
|
case 0: |
||||||
|
this.ctx.transform(1, 0, 0, -1, 0, ch); |
||||||
|
break; |
||||||
|
case 90: |
||||||
|
this.ctx.transform(0, 1, 1, 0, 0, 0); |
||||||
|
break; |
||||||
|
case 180: |
||||||
|
this.ctx.transform(-1, 0, 0, 1, cw, 0); |
||||||
|
break; |
||||||
|
case 270: |
||||||
|
this.ctx.transform(0, -1, -1, 0, cw, ch); |
||||||
|
break; |
||||||
|
} |
||||||
|
this.ctx.scale(cw / mediaBox.width, ch / mediaBox.height); |
||||||
|
}, |
||||||
|
|
||||||
|
executeIRQueue: function canvasGraphicsExecuteIRQueue(codeIR, |
||||||
|
executionStartIdx, continueCallback) { |
||||||
|
var argsArray = codeIR.argsArray; |
||||||
|
var fnArray = codeIR.fnArray; |
||||||
|
var i = executionStartIdx || 0; |
||||||
|
var argsArrayLen = argsArray.length; |
||||||
|
|
||||||
|
var executionEndIdx; |
||||||
|
var startTime = Date.now(); |
||||||
|
|
||||||
|
var objs = this.objs; |
||||||
|
|
||||||
|
do { |
||||||
|
executionEndIdx = Math.min(argsArrayLen, i + kExecutionTimeCheck); |
||||||
|
|
||||||
|
for (i; i < executionEndIdx; i++) { |
||||||
|
if (fnArray[i] !== 'dependency') { |
||||||
|
this[fnArray[i]].apply(this, argsArray[i]); |
||||||
|
} else { |
||||||
|
var deps = argsArray[i]; |
||||||
|
for (var n = 0; n < deps.length; n++) { |
||||||
|
var depObjId = deps[n]; |
||||||
|
|
||||||
|
// If the promise isn't resolved yet, add the continueCallback
|
||||||
|
// to the promise and bail out.
|
||||||
|
if (!objs.isResolved(depObjId)) { |
||||||
|
objs.get(depObjId, continueCallback); |
||||||
|
return i; |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
// If the entire IRQueue was executed, stop as were done.
|
||||||
|
if (i == argsArrayLen) { |
||||||
|
return i; |
||||||
|
} |
||||||
|
|
||||||
|
// If the execution took longer then a certain amount of time, shedule
|
||||||
|
// to continue exeution after a short delay.
|
||||||
|
// However, this is only possible if a 'continueCallback' is passed in.
|
||||||
|
if (continueCallback && (Date.now() - startTime) > kExecutionTime) { |
||||||
|
setTimeout(continueCallback, 0); |
||||||
|
return i; |
||||||
|
} |
||||||
|
|
||||||
|
// If the IRQueue isn't executed completly yet OR the execution time
|
||||||
|
// was short enough, do another execution round.
|
||||||
|
} while (true); |
||||||
|
}, |
||||||
|
|
||||||
|
endDrawing: function canvasGraphicsEndDrawing() { |
||||||
|
this.ctx.restore(); |
||||||
|
}, |
||||||
|
|
||||||
|
// Graphics state
|
||||||
|
setLineWidth: function canvasGraphicsSetLineWidth(width) { |
||||||
|
this.ctx.lineWidth = width; |
||||||
|
}, |
||||||
|
setLineCap: function canvasGraphicsSetLineCap(style) { |
||||||
|
this.ctx.lineCap = LINE_CAP_STYLES[style]; |
||||||
|
}, |
||||||
|
setLineJoin: function canvasGraphicsSetLineJoin(style) { |
||||||
|
this.ctx.lineJoin = LINE_JOIN_STYLES[style]; |
||||||
|
}, |
||||||
|
setMiterLimit: function canvasGraphicsSetMiterLimit(limit) { |
||||||
|
this.ctx.miterLimit = limit; |
||||||
|
}, |
||||||
|
setDash: function canvasGraphicsSetDash(dashArray, dashPhase) { |
||||||
|
this.ctx.mozDash = dashArray; |
||||||
|
this.ctx.mozDashOffset = dashPhase; |
||||||
|
}, |
||||||
|
setRenderingIntent: function canvasGraphicsSetRenderingIntent(intent) { |
||||||
|
TODO('set rendering intent: ' + intent); |
||||||
|
}, |
||||||
|
setFlatness: function canvasGraphicsSetFlatness(flatness) { |
||||||
|
TODO('set flatness: ' + flatness); |
||||||
|
}, |
||||||
|
setGState: function canvasGraphicsSetGState(states) { |
||||||
|
for (var i = 0; i < states.length; i++) { |
||||||
|
var state = states[i]; |
||||||
|
var key = state[0]; |
||||||
|
var value = state[1]; |
||||||
|
|
||||||
|
switch (key) { |
||||||
|
case 'LW': |
||||||
|
this.setLineWidth(value); |
||||||
|
break; |
||||||
|
case 'LC': |
||||||
|
this.setLineCap(value); |
||||||
|
break; |
||||||
|
case 'LJ': |
||||||
|
this.setLineJoin(value); |
||||||
|
break; |
||||||
|
case 'ML': |
||||||
|
this.setMiterLimit(value); |
||||||
|
break; |
||||||
|
case 'D': |
||||||
|
this.setDash(value[0], value[1]); |
||||||
|
break; |
||||||
|
case 'RI': |
||||||
|
this.setRenderingIntent(value); |
||||||
|
break; |
||||||
|
case 'FL': |
||||||
|
this.setFlatness(value); |
||||||
|
break; |
||||||
|
case 'Font': |
||||||
|
this.setFont(state[1], state[2]); |
||||||
|
break; |
||||||
|
} |
||||||
|
} |
||||||
|
}, |
||||||
|
save: function canvasGraphicsSave() { |
||||||
|
this.ctx.save(); |
||||||
|
var old = this.current; |
||||||
|
this.stateStack.push(old); |
||||||
|
this.current = old.clone(); |
||||||
|
}, |
||||||
|
restore: function canvasGraphicsRestore() { |
||||||
|
var prev = this.stateStack.pop(); |
||||||
|
if (prev) { |
||||||
|
this.current = prev; |
||||||
|
this.ctx.restore(); |
||||||
|
} |
||||||
|
}, |
||||||
|
transform: function canvasGraphicsTransform(a, b, c, d, e, f) { |
||||||
|
this.ctx.transform(a, b, c, d, e, f); |
||||||
|
}, |
||||||
|
|
||||||
|
// Path
|
||||||
|
moveTo: function canvasGraphicsMoveTo(x, y) { |
||||||
|
this.ctx.moveTo(x, y); |
||||||
|
this.current.setCurrentPoint(x, y); |
||||||
|
}, |
||||||
|
lineTo: function canvasGraphicsLineTo(x, y) { |
||||||
|
this.ctx.lineTo(x, y); |
||||||
|
this.current.setCurrentPoint(x, y); |
||||||
|
}, |
||||||
|
curveTo: function canvasGraphicsCurveTo(x1, y1, x2, y2, x3, y3) { |
||||||
|
this.ctx.bezierCurveTo(x1, y1, x2, y2, x3, y3); |
||||||
|
this.current.setCurrentPoint(x3, y3); |
||||||
|
}, |
||||||
|
curveTo2: function canvasGraphicsCurveTo2(x2, y2, x3, y3) { |
||||||
|
var current = this.current; |
||||||
|
this.ctx.bezierCurveTo(current.x, current.y, x2, y2, x3, y3); |
||||||
|
current.setCurrentPoint(x3, y3); |
||||||
|
}, |
||||||
|
curveTo3: function canvasGraphicsCurveTo3(x1, y1, x3, y3) { |
||||||
|
this.curveTo(x1, y1, x3, y3, x3, y3); |
||||||
|
this.current.setCurrentPoint(x3, y3); |
||||||
|
}, |
||||||
|
closePath: function canvasGraphicsClosePath() { |
||||||
|
this.ctx.closePath(); |
||||||
|
}, |
||||||
|
rectangle: function canvasGraphicsRectangle(x, y, width, height) { |
||||||
|
this.ctx.rect(x, y, width, height); |
||||||
|
}, |
||||||
|
stroke: function canvasGraphicsStroke() { |
||||||
|
var ctx = this.ctx; |
||||||
|
var strokeColor = this.current.strokeColor; |
||||||
|
if (strokeColor && strokeColor.hasOwnProperty('type') && |
||||||
|
strokeColor.type === 'Pattern') { |
||||||
|
// for patterns, we transform to pattern space, calculate
|
||||||
|
// the pattern, call stroke, and restore to user space
|
||||||
|
ctx.save(); |
||||||
|
ctx.strokeStyle = strokeColor.getPattern(ctx); |
||||||
|
ctx.stroke(); |
||||||
|
ctx.restore(); |
||||||
|
} else { |
||||||
|
ctx.stroke(); |
||||||
|
} |
||||||
|
|
||||||
|
this.consumePath(); |
||||||
|
}, |
||||||
|
closeStroke: function canvasGraphicsCloseStroke() { |
||||||
|
this.closePath(); |
||||||
|
this.stroke(); |
||||||
|
}, |
||||||
|
fill: function canvasGraphicsFill() { |
||||||
|
var ctx = this.ctx; |
||||||
|
var fillColor = this.current.fillColor; |
||||||
|
|
||||||
|
if (fillColor && fillColor.hasOwnProperty('type') && |
||||||
|
fillColor.type === 'Pattern') { |
||||||
|
ctx.save(); |
||||||
|
ctx.fillStyle = fillColor.getPattern(ctx); |
||||||
|
ctx.fill(); |
||||||
|
ctx.restore(); |
||||||
|
} else { |
||||||
|
ctx.fill(); |
||||||
|
} |
||||||
|
|
||||||
|
this.consumePath(); |
||||||
|
}, |
||||||
|
eoFill: function canvasGraphicsEoFill() { |
||||||
|
var savedFillRule = this.setEOFillRule(); |
||||||
|
this.fill(); |
||||||
|
this.restoreFillRule(savedFillRule); |
||||||
|
}, |
||||||
|
fillStroke: function canvasGraphicsFillStroke() { |
||||||
|
var ctx = this.ctx; |
||||||
|
|
||||||
|
var fillColor = this.current.fillColor; |
||||||
|
if (fillColor && fillColor.hasOwnProperty('type') && |
||||||
|
fillColor.type === 'Pattern') { |
||||||
|
ctx.save(); |
||||||
|
ctx.fillStyle = fillColor.getPattern(ctx); |
||||||
|
ctx.fill(); |
||||||
|
ctx.restore(); |
||||||
|
} else { |
||||||
|
ctx.fill(); |
||||||
|
} |
||||||
|
|
||||||
|
var strokeColor = this.current.strokeColor; |
||||||
|
if (strokeColor && strokeColor.hasOwnProperty('type') && |
||||||
|
strokeColor.type === 'Pattern') { |
||||||
|
ctx.save(); |
||||||
|
ctx.strokeStyle = strokeColor.getPattern(ctx); |
||||||
|
ctx.stroke(); |
||||||
|
ctx.restore(); |
||||||
|
} else { |
||||||
|
ctx.stroke(); |
||||||
|
} |
||||||
|
|
||||||
|
this.consumePath(); |
||||||
|
}, |
||||||
|
eoFillStroke: function canvasGraphicsEoFillStroke() { |
||||||
|
var savedFillRule = this.setEOFillRule(); |
||||||
|
this.fillStroke(); |
||||||
|
this.restoreFillRule(savedFillRule); |
||||||
|
}, |
||||||
|
closeFillStroke: function canvasGraphicsCloseFillStroke() { |
||||||
|
return this.fillStroke(); |
||||||
|
}, |
||||||
|
closeEOFillStroke: function canvasGraphicsCloseEOFillStroke() { |
||||||
|
var savedFillRule = this.setEOFillRule(); |
||||||
|
this.fillStroke(); |
||||||
|
this.restoreFillRule(savedFillRule); |
||||||
|
}, |
||||||
|
endPath: function canvasGraphicsEndPath() { |
||||||
|
this.consumePath(); |
||||||
|
}, |
||||||
|
|
||||||
|
// Clipping
|
||||||
|
clip: function canvasGraphicsClip() { |
||||||
|
this.pendingClip = NORMAL_CLIP; |
||||||
|
}, |
||||||
|
eoClip: function canvasGraphicsEoClip() { |
||||||
|
this.pendingClip = EO_CLIP; |
||||||
|
}, |
||||||
|
|
||||||
|
// Text
|
||||||
|
beginText: function canvasGraphicsBeginText() { |
||||||
|
this.current.textMatrix = IDENTITY_MATRIX; |
||||||
|
this.current.x = this.current.lineX = 0; |
||||||
|
this.current.y = this.current.lineY = 0; |
||||||
|
}, |
||||||
|
endText: function canvasGraphicsEndText() { |
||||||
|
}, |
||||||
|
setCharSpacing: function canvasGraphicsSetCharSpacing(spacing) { |
||||||
|
this.current.charSpacing = spacing; |
||||||
|
}, |
||||||
|
setWordSpacing: function canvasGraphicsSetWordSpacing(spacing) { |
||||||
|
this.current.wordSpacing = spacing; |
||||||
|
}, |
||||||
|
setHScale: function canvasGraphicsSetHScale(scale) { |
||||||
|
this.current.textHScale = scale / 100; |
||||||
|
}, |
||||||
|
setLeading: function canvasGraphicsSetLeading(leading) { |
||||||
|
this.current.leading = -leading; |
||||||
|
}, |
||||||
|
setFont: function canvasGraphicsSetFont(fontRefName, size) { |
||||||
|
var fontObj = this.objs.get(fontRefName).fontObj; |
||||||
|
|
||||||
|
if (!fontObj) { |
||||||
|
throw 'Can\'t find font for ' + fontRefName; |
||||||
|
} |
||||||
|
|
||||||
|
var name = fontObj.loadedName || 'sans-serif'; |
||||||
|
|
||||||
|
this.current.font = fontObj; |
||||||
|
this.current.fontSize = size; |
||||||
|
|
||||||
|
var name = fontObj.loadedName || 'sans-serif'; |
||||||
|
var bold = fontObj.black ? (fontObj.bold ? 'bolder' : 'bold') : |
||||||
|
(fontObj.bold ? 'bold' : 'normal'); |
||||||
|
|
||||||
|
var italic = fontObj.italic ? 'italic' : 'normal'; |
||||||
|
var serif = fontObj.serif ? 'serif' : 'sans-serif'; |
||||||
|
var typeface = '"' + name + '", ' + serif; |
||||||
|
var rule = italic + ' ' + bold + ' ' + size + 'px ' + typeface; |
||||||
|
this.ctx.font = rule; |
||||||
|
}, |
||||||
|
setTextRenderingMode: function canvasGraphicsSetTextRenderingMode(mode) { |
||||||
|
TODO('text rendering mode: ' + mode); |
||||||
|
}, |
||||||
|
setTextRise: function canvasGraphicsSetTextRise(rise) { |
||||||
|
TODO('text rise: ' + rise); |
||||||
|
}, |
||||||
|
moveText: function canvasGraphicsMoveText(x, y) { |
||||||
|
this.current.x = this.current.lineX += x; |
||||||
|
this.current.y = this.current.lineY += y; |
||||||
|
}, |
||||||
|
setLeadingMoveText: function canvasGraphicsSetLeadingMoveText(x, y) { |
||||||
|
this.setLeading(-y); |
||||||
|
this.moveText(x, y); |
||||||
|
}, |
||||||
|
setTextMatrix: function canvasGraphicsSetTextMatrix(a, b, c, d, e, f) { |
||||||
|
this.current.textMatrix = [a, b, c, d, e, f]; |
||||||
|
|
||||||
|
this.current.x = this.current.lineX = 0; |
||||||
|
this.current.y = this.current.lineY = 0; |
||||||
|
}, |
||||||
|
nextLine: function canvasGraphicsNextLine() { |
||||||
|
this.moveText(0, this.current.leading); |
||||||
|
}, |
||||||
|
showText: function canvasGraphicsShowText(text) { |
||||||
|
var ctx = this.ctx; |
||||||
|
var current = this.current; |
||||||
|
var font = current.font; |
||||||
|
var glyphs = font.charsToGlyphs(text); |
||||||
|
var fontSize = current.fontSize; |
||||||
|
var charSpacing = current.charSpacing; |
||||||
|
var wordSpacing = current.wordSpacing; |
||||||
|
var textHScale = current.textHScale; |
||||||
|
var glyphsLength = glyphs.length; |
||||||
|
if (font.coded) { |
||||||
|
ctx.save(); |
||||||
|
ctx.transform.apply(ctx, current.textMatrix); |
||||||
|
ctx.translate(current.x, current.y); |
||||||
|
|
||||||
|
var fontMatrix = font.fontMatrix || IDENTITY_MATRIX; |
||||||
|
ctx.scale(1 / textHScale, 1); |
||||||
|
for (var i = 0; i < glyphsLength; ++i) { |
||||||
|
|
||||||
|
var glyph = glyphs[i]; |
||||||
|
if (glyph === null) { |
||||||
|
// word break
|
||||||
|
this.ctx.translate(wordSpacing, 0); |
||||||
|
continue; |
||||||
|
} |
||||||
|
|
||||||
|
this.save(); |
||||||
|
ctx.scale(fontSize, fontSize); |
||||||
|
ctx.transform.apply(ctx, fontMatrix); |
||||||
|
this.executeIRQueue(glyph.IRQueue); |
||||||
|
this.restore(); |
||||||
|
|
||||||
|
var transformed = Util.applyTransform([glyph.width, 0], fontMatrix); |
||||||
|
var width = transformed[0] * fontSize + charSpacing; |
||||||
|
|
||||||
|
ctx.translate(width, 0); |
||||||
|
current.x += width; |
||||||
|
|
||||||
|
} |
||||||
|
ctx.restore(); |
||||||
|
} else { |
||||||
|
ctx.save(); |
||||||
|
ctx.transform.apply(ctx, current.textMatrix); |
||||||
|
ctx.scale(1, -1); |
||||||
|
ctx.translate(current.x, -1 * current.y); |
||||||
|
ctx.transform.apply(ctx, font.fontMatrix || IDENTITY_MATRIX); |
||||||
|
|
||||||
|
ctx.scale(1 / textHScale, 1); |
||||||
|
|
||||||
|
var width = 0; |
||||||
|
for (var i = 0; i < glyphsLength; ++i) { |
||||||
|
var glyph = glyphs[i]; |
||||||
|
if (glyph === null) { |
||||||
|
// word break
|
||||||
|
width += wordSpacing; |
||||||
|
continue; |
||||||
|
} |
||||||
|
|
||||||
|
var unicode = glyph.unicode; |
||||||
|
var char = (unicode >= 0x10000) ? |
||||||
|
String.fromCharCode(0xD800 | ((unicode - 0x10000) >> 10), |
||||||
|
0xDC00 | (unicode & 0x3FF)) : String.fromCharCode(unicode); |
||||||
|
|
||||||
|
ctx.fillText(char, width, 0); |
||||||
|
width += glyph.width * fontSize * 0.001 + charSpacing; |
||||||
|
} |
||||||
|
current.x += width; |
||||||
|
|
||||||
|
ctx.restore(); |
||||||
|
} |
||||||
|
}, |
||||||
|
|
||||||
|
showSpacedText: function canvasGraphicsShowSpacedText(arr) { |
||||||
|
var ctx = this.ctx; |
||||||
|
var current = this.current; |
||||||
|
var fontSize = current.fontSize; |
||||||
|
var textHScale = current.textHScale; |
||||||
|
var arrLength = arr.length; |
||||||
|
for (var i = 0; i < arrLength; ++i) { |
||||||
|
var e = arr[i]; |
||||||
|
if (isNum(e)) { |
||||||
|
current.x -= e * 0.001 * fontSize * textHScale; |
||||||
|
} else if (isString(e)) { |
||||||
|
this.showText(e); |
||||||
|
} else { |
||||||
|
malformed('TJ array element ' + e + ' is not string or num'); |
||||||
|
} |
||||||
|
} |
||||||
|
}, |
||||||
|
nextLineShowText: function canvasGraphicsNextLineShowText(text) { |
||||||
|
this.nextLine(); |
||||||
|
this.showText(text); |
||||||
|
}, |
||||||
|
nextLineSetSpacingShowText: |
||||||
|
function canvasGraphicsNextLineSetSpacingShowText(wordSpacing, |
||||||
|
charSpacing, |
||||||
|
text) { |
||||||
|
this.setWordSpacing(wordSpacing); |
||||||
|
this.setCharSpacing(charSpacing); |
||||||
|
this.nextLineShowText(text); |
||||||
|
}, |
||||||
|
|
||||||
|
// Type3 fonts
|
||||||
|
setCharWidth: function canvasGraphicsSetCharWidth(xWidth, yWidth) { |
||||||
|
// We can safely ignore this since the width should be the same
|
||||||
|
// as the width in the Widths array.
|
||||||
|
}, |
||||||
|
setCharWidthAndBounds: function canvasGraphicsSetCharWidthAndBounds(xWidth, |
||||||
|
yWidth, |
||||||
|
llx, |
||||||
|
lly, |
||||||
|
urx, |
||||||
|
ury) { |
||||||
|
// TODO According to the spec we're also suppose to ignore any operators
|
||||||
|
// that set color or include images while processing this type3 font.
|
||||||
|
this.rectangle(llx, lly, urx - llx, ury - lly); |
||||||
|
this.clip(); |
||||||
|
this.endPath(); |
||||||
|
}, |
||||||
|
|
||||||
|
// Color
|
||||||
|
setStrokeColorSpace: |
||||||
|
function canvasGraphicsSetStrokeColorSpacefunction(raw) { |
||||||
|
this.current.strokeColorSpace = ColorSpace.fromIR(raw); |
||||||
|
}, |
||||||
|
setFillColorSpace: function canvasGraphicsSetFillColorSpace(raw) { |
||||||
|
this.current.fillColorSpace = ColorSpace.fromIR(raw); |
||||||
|
}, |
||||||
|
setStrokeColor: function canvasGraphicsSetStrokeColor(/*...*/) { |
||||||
|
var cs = this.current.strokeColorSpace; |
||||||
|
var color = cs.getRgb(arguments); |
||||||
|
this.setStrokeRGBColor.apply(this, color); |
||||||
|
}, |
||||||
|
getColorN_IR_Pattern: function(IR, cs) { |
||||||
|
if (IR[0] == 'TilingPattern') { |
||||||
|
var args = IR[1]; |
||||||
|
var base = cs.base; |
||||||
|
var color; |
||||||
|
if (base) { |
||||||
|
var baseComps = base.numComps; |
||||||
|
|
||||||
|
color = []; |
||||||
|
for (var i = 0; i < baseComps; ++i) |
||||||
|
color.push(args[i]); |
||||||
|
|
||||||
|
color = base.getRgb(color); |
||||||
|
} |
||||||
|
var pattern = new TilingPattern(IR, color, this.ctx, this.objs); |
||||||
|
} else if (IR[0] == 'RadialAxialShading' || IR[0] == 'DummyShading') { |
||||||
|
var pattern = Pattern.shadingFromIR(this.ctx, IR); |
||||||
|
} else { |
||||||
|
throw 'Unkown IR type'; |
||||||
|
} |
||||||
|
return pattern; |
||||||
|
}, |
||||||
|
setStrokeColorN_IR: function canvasGraphicsSetStrokeColorN(/*...*/) { |
||||||
|
var cs = this.current.strokeColorSpace; |
||||||
|
|
||||||
|
if (cs.name == 'Pattern') { |
||||||
|
this.current.strokeColor = this.getColorN_IR_Pattern(arguments, cs); |
||||||
|
} else { |
||||||
|
this.setStrokeColor.apply(this, arguments); |
||||||
|
} |
||||||
|
}, |
||||||
|
setFillColor: function canvasGraphicsSetFillColor(/*...*/) { |
||||||
|
var cs = this.current.fillColorSpace; |
||||||
|
var color = cs.getRgb(arguments); |
||||||
|
this.setFillRGBColor.apply(this, color); |
||||||
|
}, |
||||||
|
setFillColorN_IR: function canvasGraphicsSetFillColorN(/*...*/) { |
||||||
|
var cs = this.current.fillColorSpace; |
||||||
|
|
||||||
|
if (cs.name == 'Pattern') { |
||||||
|
this.current.fillColor = this.getColorN_IR_Pattern(arguments, cs); |
||||||
|
} else { |
||||||
|
this.setFillColor.apply(this, arguments); |
||||||
|
} |
||||||
|
}, |
||||||
|
setStrokeGray: function canvasGraphicsSetStrokeGray(gray) { |
||||||
|
this.setStrokeRGBColor(gray, gray, gray); |
||||||
|
}, |
||||||
|
setFillGray: function canvasGraphicsSetFillGray(gray) { |
||||||
|
this.setFillRGBColor(gray, gray, gray); |
||||||
|
}, |
||||||
|
setStrokeRGBColor: function canvasGraphicsSetStrokeRGBColor(r, g, b) { |
||||||
|
var color = Util.makeCssRgb(r, g, b); |
||||||
|
this.ctx.strokeStyle = color; |
||||||
|
this.current.strokeColor = color; |
||||||
|
}, |
||||||
|
setFillRGBColor: function canvasGraphicsSetFillRGBColor(r, g, b) { |
||||||
|
var color = Util.makeCssRgb(r, g, b); |
||||||
|
this.ctx.fillStyle = color; |
||||||
|
this.current.fillColor = color; |
||||||
|
}, |
||||||
|
setStrokeCMYKColor: function canvasGraphicsSetStrokeCMYKColor(c, m, y, k) { |
||||||
|
var color = Util.makeCssCmyk(c, m, y, k); |
||||||
|
this.ctx.strokeStyle = color; |
||||||
|
this.current.strokeColor = color; |
||||||
|
}, |
||||||
|
setFillCMYKColor: function canvasGraphicsSetFillCMYKColor(c, m, y, k) { |
||||||
|
var color = Util.makeCssCmyk(c, m, y, k); |
||||||
|
this.ctx.fillStyle = color; |
||||||
|
this.current.fillColor = color; |
||||||
|
}, |
||||||
|
|
||||||
|
shadingFill: function canvasGraphicsShadingFill(patternIR) { |
||||||
|
var ctx = this.ctx; |
||||||
|
|
||||||
|
this.save(); |
||||||
|
ctx.fillStyle = Pattern.shadingFromIR(ctx, patternIR); |
||||||
|
|
||||||
|
var inv = ctx.mozCurrentTransformInverse; |
||||||
|
if (inv) { |
||||||
|
var canvas = ctx.canvas; |
||||||
|
var width = canvas.width; |
||||||
|
var height = canvas.height; |
||||||
|
|
||||||
|
var bl = Util.applyTransform([0, 0], inv); |
||||||
|
var br = Util.applyTransform([0, width], inv); |
||||||
|
var ul = Util.applyTransform([height, 0], inv); |
||||||
|
var ur = Util.applyTransform([height, width], inv); |
||||||
|
|
||||||
|
var x0 = Math.min(bl[0], br[0], ul[0], ur[0]); |
||||||
|
var y0 = Math.min(bl[1], br[1], ul[1], ur[1]); |
||||||
|
var x1 = Math.max(bl[0], br[0], ul[0], ur[0]); |
||||||
|
var y1 = Math.max(bl[1], br[1], ul[1], ur[1]); |
||||||
|
|
||||||
|
this.ctx.fillRect(x0, y0, x1 - x0, y1 - y0); |
||||||
|
} else { |
||||||
|
// 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); |
||||||
|
} |
||||||
|
|
||||||
|
this.restore(); |
||||||
|
}, |
||||||
|
|
||||||
|
// Images
|
||||||
|
beginInlineImage: function canvasGraphicsBeginInlineImage() { |
||||||
|
error('Should not call beginInlineImage'); |
||||||
|
}, |
||||||
|
beginImageData: function canvasGraphicsBeginImageData() { |
||||||
|
error('Should not call beginImageData'); |
||||||
|
}, |
||||||
|
|
||||||
|
paintFormXObjectBegin: |
||||||
|
function canvasGraphicsPaintFormXObject(matrix, bbox) { |
||||||
|
this.save(); |
||||||
|
|
||||||
|
if (matrix && isArray(matrix) && 6 == matrix.length) |
||||||
|
this.transform.apply(this, matrix); |
||||||
|
|
||||||
|
if (bbox && isArray(bbox) && 4 == bbox.length) { |
||||||
|
var width = bbox[2] - bbox[0]; |
||||||
|
var height = bbox[3] - bbox[1]; |
||||||
|
this.rectangle(bbox[0], bbox[1], width, height); |
||||||
|
this.clip(); |
||||||
|
this.endPath(); |
||||||
|
} |
||||||
|
}, |
||||||
|
|
||||||
|
paintFormXObjectEnd: function() { |
||||||
|
this.restore(); |
||||||
|
}, |
||||||
|
|
||||||
|
paintJpegXObject: function(objId, w, h) { |
||||||
|
var image = this.objs.get(objId); |
||||||
|
if (!image) { |
||||||
|
error('Dependent image isn\'t ready yet'); |
||||||
|
} |
||||||
|
|
||||||
|
this.save(); |
||||||
|
|
||||||
|
var ctx = this.ctx; |
||||||
|
// scale the image to the unit square
|
||||||
|
ctx.scale(1 / w, -1 / h); |
||||||
|
|
||||||
|
var domImage = image.getImage(); |
||||||
|
ctx.drawImage(domImage, 0, 0, domImage.width, domImage.height, |
||||||
|
0, -h, w, h); |
||||||
|
|
||||||
|
this.restore(); |
||||||
|
}, |
||||||
|
|
||||||
|
paintImageMaskXObject: function(imgArray, inverseDecode, width, height) { |
||||||
|
function applyStencilMask(buffer, inverseDecode) { |
||||||
|
var imgArrayPos = 0; |
||||||
|
var i, j, mask, buf; |
||||||
|
// removing making non-masked pixels transparent
|
||||||
|
var bufferPos = 3; // alpha component offset
|
||||||
|
for (i = 0; i < height; i++) { |
||||||
|
mask = 0; |
||||||
|
for (j = 0; j < width; j++) { |
||||||
|
if (!mask) { |
||||||
|
buf = imgArray[imgArrayPos++]; |
||||||
|
mask = 128; |
||||||
|
} |
||||||
|
if (!(buf & mask) == inverseDecode) { |
||||||
|
buffer[bufferPos] = 0; |
||||||
|
} |
||||||
|
bufferPos += 4; |
||||||
|
mask >>= 1; |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
this.save(); |
||||||
|
|
||||||
|
var ctx = this.ctx; |
||||||
|
var w = width, h = height; |
||||||
|
// scale the image to the unit square
|
||||||
|
ctx.scale(1 / w, -1 / h); |
||||||
|
|
||||||
|
var tmpCanvas = new this.ScratchCanvas(w, h); |
||||||
|
var tmpCtx = tmpCanvas.getContext('2d'); |
||||||
|
|
||||||
|
var fillColor = this.current.fillColor; |
||||||
|
tmpCtx.fillStyle = (fillColor && fillColor.hasOwnProperty('type') && |
||||||
|
fillColor.type === 'Pattern') ? |
||||||
|
fillColor.getPattern(tmpCtx) : fillColor; |
||||||
|
tmpCtx.fillRect(0, 0, w, h); |
||||||
|
|
||||||
|
var imgData = tmpCtx.getImageData(0, 0, w, h); |
||||||
|
var pixels = imgData.data; |
||||||
|
|
||||||
|
applyStencilMask(pixels, inverseDecode); |
||||||
|
|
||||||
|
tmpCtx.putImageData(imgData, 0, 0); |
||||||
|
ctx.drawImage(tmpCanvas, 0, -h); |
||||||
|
this.restore(); |
||||||
|
}, |
||||||
|
|
||||||
|
paintImageXObject: function(imgData) { |
||||||
|
this.save(); |
||||||
|
var ctx = this.ctx; |
||||||
|
var w = imgData.width; |
||||||
|
var h = imgData.height; |
||||||
|
// scale the image to the unit square
|
||||||
|
ctx.scale(1 / w, -1 / h); |
||||||
|
|
||||||
|
var tmpCanvas = new this.ScratchCanvas(w, h); |
||||||
|
var tmpCtx = tmpCanvas.getContext('2d'); |
||||||
|
var tmpImgData; |
||||||
|
|
||||||
|
// Some browsers can set an UInt8Array directly as imageData, some
|
||||||
|
// can't. As long as we don't have proper feature detection, just
|
||||||
|
// copy over each pixel and set the imageData that way.
|
||||||
|
tmpImgData = tmpCtx.getImageData(0, 0, w, h); |
||||||
|
|
||||||
|
// Copy over the imageData.
|
||||||
|
var tmpImgDataPixels = tmpImgData.data; |
||||||
|
var len = tmpImgDataPixels.length; |
||||||
|
|
||||||
|
while (len--) { |
||||||
|
tmpImgDataPixels[len] = imgData.data[len]; |
||||||
|
} |
||||||
|
|
||||||
|
tmpCtx.putImageData(tmpImgData, 0, 0); |
||||||
|
ctx.drawImage(tmpCanvas, 0, -h); |
||||||
|
this.restore(); |
||||||
|
}, |
||||||
|
|
||||||
|
// Marked content
|
||||||
|
|
||||||
|
markPoint: function canvasGraphicsMarkPoint(tag) { |
||||||
|
TODO('Marked content'); |
||||||
|
}, |
||||||
|
markPointProps: function canvasGraphicsMarkPointProps(tag, properties) { |
||||||
|
TODO('Marked content'); |
||||||
|
}, |
||||||
|
beginMarkedContent: function canvasGraphicsBeginMarkedContent(tag) { |
||||||
|
TODO('Marked content'); |
||||||
|
}, |
||||||
|
beginMarkedContentProps: |
||||||
|
function canvasGraphicsBeginMarkedContentProps(tag, properties) { |
||||||
|
TODO('Marked content'); |
||||||
|
}, |
||||||
|
endMarkedContent: function canvasGraphicsEndMarkedContent() { |
||||||
|
TODO('Marked content'); |
||||||
|
}, |
||||||
|
|
||||||
|
// Compatibility
|
||||||
|
|
||||||
|
beginCompat: function canvasGraphicsBeginCompat() { |
||||||
|
TODO('ignore undefined operators (should we do that anyway?)'); |
||||||
|
}, |
||||||
|
endCompat: function canvasGraphicsEndCompat() { |
||||||
|
TODO('stop ignoring undefined operators'); |
||||||
|
}, |
||||||
|
|
||||||
|
// Helper functions
|
||||||
|
|
||||||
|
consumePath: function canvasGraphicsConsumePath() { |
||||||
|
if (this.pendingClip) { |
||||||
|
var savedFillRule = null; |
||||||
|
if (this.pendingClip == EO_CLIP) |
||||||
|
savedFillRule = this.setEOFillRule(); |
||||||
|
|
||||||
|
this.ctx.clip(); |
||||||
|
|
||||||
|
this.pendingClip = null; |
||||||
|
if (savedFillRule !== null) |
||||||
|
this.restoreFillRule(savedFillRule); |
||||||
|
} |
||||||
|
this.ctx.beginPath(); |
||||||
|
}, |
||||||
|
// We generally keep the canvas context set for
|
||||||
|
// nonzero-winding, and just set evenodd for the operations
|
||||||
|
// that need them.
|
||||||
|
setEOFillRule: function canvasGraphicsSetEOFillRule() { |
||||||
|
var savedFillRule = this.ctx.mozFillRule; |
||||||
|
this.ctx.mozFillRule = 'evenodd'; |
||||||
|
return savedFillRule; |
||||||
|
}, |
||||||
|
restoreFillRule: function canvasGraphicsRestoreFillRule(rule) { |
||||||
|
this.ctx.mozFillRule = rule; |
||||||
|
} |
||||||
|
}; |
||||||
|
|
||||||
|
return constructor; |
||||||
|
})(); |
@ -0,0 +1,396 @@ |
|||||||
|
var ColorSpace = (function colorSpaceColorSpace() { |
||||||
|
// Constructor should define this.numComps, this.defaultColor, this.name
|
||||||
|
function constructor() { |
||||||
|
error('should not call ColorSpace constructor'); |
||||||
|
} |
||||||
|
|
||||||
|
constructor.prototype = { |
||||||
|
// Input: array of size numComps representing color component values
|
||||||
|
// Output: array of rgb values, each value ranging from [0.1]
|
||||||
|
getRgb: function cs_getRgb(color) { |
||||||
|
error('Should not call ColorSpace.getRgb: ' + color); |
||||||
|
}, |
||||||
|
// Input: Uint8Array of component values, each value scaled to [0,255]
|
||||||
|
// Output: Uint8Array of rgb values, each value scaled to [0,255]
|
||||||
|
getRgbBuffer: function cs_getRgbBuffer(input) { |
||||||
|
error('Should not call ColorSpace.getRgbBuffer: ' + input); |
||||||
|
} |
||||||
|
}; |
||||||
|
|
||||||
|
constructor.parse = function colorspace_parse(cs, xref, res) { |
||||||
|
var IR = constructor.parseToIR(cs, xref, res, true); |
||||||
|
if (IR instanceof SeparationCS) |
||||||
|
return IR; |
||||||
|
|
||||||
|
return constructor.fromIR(IR); |
||||||
|
}; |
||||||
|
|
||||||
|
constructor.fromIR = function(IR) { |
||||||
|
var name; |
||||||
|
if (isArray(IR)) { |
||||||
|
name = IR[0]; |
||||||
|
} else { |
||||||
|
name = IR; |
||||||
|
} |
||||||
|
|
||||||
|
switch (name) { |
||||||
|
case 'DeviceGrayCS': |
||||||
|
return new DeviceGrayCS(); |
||||||
|
case 'DeviceRgbCS': |
||||||
|
return new DeviceRgbCS(); |
||||||
|
case 'DeviceCmykCS': |
||||||
|
return new DeviceCmykCS(); |
||||||
|
case 'PatternCS': |
||||||
|
var baseCS = IR[1]; |
||||||
|
if (baseCS == null) { |
||||||
|
return new PatternCS(null); |
||||||
|
} else { |
||||||
|
return new PatternCS(ColorSpace.fromIR(baseCS)); |
||||||
|
} |
||||||
|
case 'IndexedCS': |
||||||
|
var baseCS = IR[1]; |
||||||
|
var hiVal = IR[2]; |
||||||
|
var lookup = IR[3]; |
||||||
|
return new IndexedCS(ColorSpace.fromIR(baseCS), hiVal, lookup); |
||||||
|
case 'SeparationCS': |
||||||
|
var alt = IR[1]; |
||||||
|
var tintFnIR = IR[2]; |
||||||
|
|
||||||
|
return new SeparationCS( |
||||||
|
ColorSpace.fromIR(alt), |
||||||
|
PDFFunction.fromIR(tintFnIR) |
||||||
|
); |
||||||
|
default: |
||||||
|
error('Unkown name ' + name); |
||||||
|
} |
||||||
|
return null; |
||||||
|
} |
||||||
|
|
||||||
|
constructor.parseToIR = function colorspace_parse(cs, xref, res, parseOnly) { |
||||||
|
if (isName(cs)) { |
||||||
|
var colorSpaces = xref.fetchIfRef(res.get('ColorSpace')); |
||||||
|
if (isDict(colorSpaces)) { |
||||||
|
var refcs = colorSpaces.get(cs.name); |
||||||
|
if (refcs) |
||||||
|
cs = refcs; |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
cs = xref.fetchIfRef(cs); |
||||||
|
|
||||||
|
if (isName(cs)) { |
||||||
|
var mode = cs.name; |
||||||
|
this.mode = mode; |
||||||
|
|
||||||
|
switch (mode) { |
||||||
|
case 'DeviceGray': |
||||||
|
case 'G': |
||||||
|
return 'DeviceGrayCS'; |
||||||
|
case 'DeviceRGB': |
||||||
|
case 'RGB': |
||||||
|
return 'DeviceRgbCS'; |
||||||
|
case 'DeviceCMYK': |
||||||
|
case 'CMYK': |
||||||
|
return 'DeviceCmykCS'; |
||||||
|
case 'Pattern': |
||||||
|
return ['PatternCS', null]; |
||||||
|
default: |
||||||
|
error('unrecognized colorspace ' + mode); |
||||||
|
} |
||||||
|
} else if (isArray(cs)) { |
||||||
|
var mode = cs[0].name; |
||||||
|
this.mode = mode; |
||||||
|
|
||||||
|
switch (mode) { |
||||||
|
case 'DeviceGray': |
||||||
|
case 'G': |
||||||
|
return 'DeviceGrayCS'; |
||||||
|
case 'DeviceRGB': |
||||||
|
case 'RGB': |
||||||
|
return 'DeviceRgbCS'; |
||||||
|
case 'DeviceCMYK': |
||||||
|
case 'CMYK': |
||||||
|
return 'DeviceCmykCS'; |
||||||
|
case 'CalGray': |
||||||
|
return 'DeviceGrayCS'; |
||||||
|
case 'CalRGB': |
||||||
|
return 'DeviceRgbCS'; |
||||||
|
case 'ICCBased': |
||||||
|
var stream = xref.fetchIfRef(cs[1]); |
||||||
|
var dict = stream.dict; |
||||||
|
var numComps = dict.get('N'); |
||||||
|
if (numComps == 1) |
||||||
|
return 'DeviceGrayCS'; |
||||||
|
if (numComps == 3) |
||||||
|
return 'DeviceRgbCS'; |
||||||
|
if (numComps == 4) |
||||||
|
return 'DeviceCmykCS'; |
||||||
|
break; |
||||||
|
case 'Pattern': |
||||||
|
var baseCS = cs[1]; |
||||||
|
if (baseCS) |
||||||
|
baseCS = ColorSpace.parseToIR(baseCS, xref, res); |
||||||
|
return ['PatternCS', baseCS]; |
||||||
|
case 'Indexed': |
||||||
|
var baseCS = ColorSpace.parseToIR(cs[1], xref, res); |
||||||
|
var hiVal = cs[2] + 1; |
||||||
|
var lookup = xref.fetchIfRef(cs[3]); |
||||||
|
return ['IndexedCS', baseCS, hiVal, lookup]; |
||||||
|
case 'Separation': |
||||||
|
var alt = ColorSpace.parseToIR(cs[2], xref, res); |
||||||
|
var tintFnIR = PDFFunction.getIR(xref, xref.fetchIfRef(cs[3])); |
||||||
|
return ['SeparationCS', alt, tintFnIR]; |
||||||
|
case 'Lab': |
||||||
|
case 'DeviceN': |
||||||
|
default: |
||||||
|
error('unimplemented color space object "' + mode + '"'); |
||||||
|
} |
||||||
|
} else { |
||||||
|
error('unrecognized color space object: "' + cs + '"'); |
||||||
|
} |
||||||
|
return null; |
||||||
|
}; |
||||||
|
|
||||||
|
return constructor; |
||||||
|
})(); |
||||||
|
|
||||||
|
var SeparationCS = (function separationCS() { |
||||||
|
function constructor(base, tintFn) { |
||||||
|
this.name = 'Separation'; |
||||||
|
this.numComps = 1; |
||||||
|
this.defaultColor = [1]; |
||||||
|
|
||||||
|
this.base = base; |
||||||
|
this.tintFn = tintFn; |
||||||
|
} |
||||||
|
|
||||||
|
constructor.prototype = { |
||||||
|
getRgb: function sepcs_getRgb(color) { |
||||||
|
var tinted = this.tintFn(color); |
||||||
|
return this.base.getRgb(tinted); |
||||||
|
}, |
||||||
|
getRgbBuffer: function sepcs_getRgbBuffer(input, bits) { |
||||||
|
var tintFn = this.tintFn; |
||||||
|
var base = this.base; |
||||||
|
var scale = 1 / ((1 << bits) - 1); |
||||||
|
|
||||||
|
var length = input.length; |
||||||
|
var pos = 0; |
||||||
|
|
||||||
|
var numComps = base.numComps; |
||||||
|
var baseBuf = new Uint8Array(numComps * length); |
||||||
|
for (var i = 0; i < length; ++i) { |
||||||
|
var scaled = input[i] * scale; |
||||||
|
var tinted = tintFn([scaled]); |
||||||
|
for (var j = 0; j < numComps; ++j) |
||||||
|
baseBuf[pos++] = 255 * tinted[j]; |
||||||
|
} |
||||||
|
return base.getRgbBuffer(baseBuf, 8); |
||||||
|
} |
||||||
|
}; |
||||||
|
|
||||||
|
return constructor; |
||||||
|
})(); |
||||||
|
|
||||||
|
var PatternCS = (function patternCS() { |
||||||
|
function constructor(baseCS) { |
||||||
|
this.name = 'Pattern'; |
||||||
|
this.base = baseCS; |
||||||
|
} |
||||||
|
constructor.prototype = {}; |
||||||
|
|
||||||
|
return constructor; |
||||||
|
})(); |
||||||
|
|
||||||
|
var IndexedCS = (function indexedCS() { |
||||||
|
function constructor(base, highVal, lookup) { |
||||||
|
this.name = 'Indexed'; |
||||||
|
this.numComps = 1; |
||||||
|
this.defaultColor = [0]; |
||||||
|
|
||||||
|
this.base = base; |
||||||
|
var baseNumComps = base.numComps; |
||||||
|
this.highVal = highVal; |
||||||
|
|
||||||
|
var length = baseNumComps * highVal; |
||||||
|
var lookupArray = new Uint8Array(length); |
||||||
|
if (isStream(lookup)) { |
||||||
|
var bytes = lookup.getBytes(length); |
||||||
|
lookupArray.set(bytes); |
||||||
|
} else if (isString(lookup)) { |
||||||
|
for (var i = 0; i < length; ++i) |
||||||
|
lookupArray[i] = lookup.charCodeAt(i); |
||||||
|
} else { |
||||||
|
error('Unrecognized lookup table: ' + lookup); |
||||||
|
} |
||||||
|
this.lookup = lookupArray; |
||||||
|
} |
||||||
|
|
||||||
|
constructor.prototype = { |
||||||
|
getRgb: function indexcs_getRgb(color) { |
||||||
|
var numComps = this.base.numComps; |
||||||
|
|
||||||
|
var start = color[0] * numComps; |
||||||
|
var c = []; |
||||||
|
|
||||||
|
for (var i = start, ii = start + numComps; i < ii; ++i) |
||||||
|
c.push(this.lookup[i]); |
||||||
|
|
||||||
|
return this.base.getRgb(c); |
||||||
|
}, |
||||||
|
getRgbBuffer: function indexcs_getRgbBuffer(input) { |
||||||
|
var base = this.base; |
||||||
|
var numComps = base.numComps; |
||||||
|
var lookup = this.lookup; |
||||||
|
var length = input.length; |
||||||
|
|
||||||
|
var baseBuf = new Uint8Array(length * numComps); |
||||||
|
var baseBufPos = 0; |
||||||
|
for (var i = 0; i < length; ++i) { |
||||||
|
var lookupPos = input[i] * numComps; |
||||||
|
for (var j = 0; j < numComps; ++j) { |
||||||
|
baseBuf[baseBufPos++] = lookup[lookupPos + j]; |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
return base.getRgbBuffer(baseBuf, 8); |
||||||
|
} |
||||||
|
}; |
||||||
|
return constructor; |
||||||
|
})(); |
||||||
|
|
||||||
|
var DeviceGrayCS = (function deviceGrayCS() { |
||||||
|
function constructor() { |
||||||
|
this.name = 'DeviceGray'; |
||||||
|
this.numComps = 1; |
||||||
|
this.defaultColor = [0]; |
||||||
|
} |
||||||
|
|
||||||
|
constructor.prototype = { |
||||||
|
getRgb: function graycs_getRgb(color) { |
||||||
|
var c = color[0]; |
||||||
|
return [c, c, c]; |
||||||
|
}, |
||||||
|
getRgbBuffer: function graycs_getRgbBuffer(input, bits) { |
||||||
|
var scale = 255 / ((1 << bits) - 1); |
||||||
|
var length = input.length; |
||||||
|
var rgbBuf = new Uint8Array(length * 3); |
||||||
|
for (var i = 0, j = 0; i < length; ++i) { |
||||||
|
var c = (scale * input[i]) | 0; |
||||||
|
rgbBuf[j++] = c; |
||||||
|
rgbBuf[j++] = c; |
||||||
|
rgbBuf[j++] = c; |
||||||
|
} |
||||||
|
return rgbBuf; |
||||||
|
} |
||||||
|
}; |
||||||
|
return constructor; |
||||||
|
})(); |
||||||
|
|
||||||
|
var DeviceRgbCS = (function deviceRgbCS() { |
||||||
|
function constructor(bits) { |
||||||
|
this.name = 'DeviceRGB'; |
||||||
|
this.numComps = 3; |
||||||
|
this.defaultColor = [0, 0, 0]; |
||||||
|
} |
||||||
|
constructor.prototype = { |
||||||
|
getRgb: function rgbcs_getRgb(color) { |
||||||
|
return color; |
||||||
|
}, |
||||||
|
getRgbBuffer: function rgbcs_getRgbBuffer(input, bits) { |
||||||
|
if (bits == 8) |
||||||
|
return input; |
||||||
|
var scale = 255 / ((1 << bits) - 1); |
||||||
|
var i, length = input.length; |
||||||
|
var rgbBuf = new Uint8Array(length); |
||||||
|
for (i = 0; i < length; ++i) |
||||||
|
rgbBuf[i] = (scale * input[i]) | 0; |
||||||
|
return rgbBuf; |
||||||
|
} |
||||||
|
}; |
||||||
|
return constructor; |
||||||
|
})(); |
||||||
|
|
||||||
|
var DeviceCmykCS = (function deviceCmykCS() { |
||||||
|
function constructor() { |
||||||
|
this.name = 'DeviceCMYK'; |
||||||
|
this.numComps = 4; |
||||||
|
this.defaultColor = [0, 0, 0, 1]; |
||||||
|
} |
||||||
|
constructor.prototype = { |
||||||
|
getRgb: function cmykcs_getRgb(color) { |
||||||
|
var c = color[0], m = color[1], y = color[2], k = color[3]; |
||||||
|
var c1 = 1 - c, m1 = 1 - m, y1 = 1 - y, k1 = 1 - k; |
||||||
|
|
||||||
|
var x, r, g, b; |
||||||
|
// this is a matrix multiplication, unrolled for performance
|
||||||
|
// code is taken from the poppler implementation
|
||||||
|
x = c1 * m1 * y1 * k1; // 0 0 0 0
|
||||||
|
r = g = b = x; |
||||||
|
x = c1 * m1 * y1 * k; // 0 0 0 1
|
||||||
|
r += 0.1373 * x; |
||||||
|
g += 0.1216 * x; |
||||||
|
b += 0.1255 * x; |
||||||
|
x = c1 * m1 * y * k1; // 0 0 1 0
|
||||||
|
r += x; |
||||||
|
g += 0.9490 * x; |
||||||
|
x = c1 * m1 * y * k; // 0 0 1 1
|
||||||
|
r += 0.1098 * x; |
||||||
|
g += 0.1020 * x; |
||||||
|
x = c1 * m * y1 * k1; // 0 1 0 0
|
||||||
|
r += 0.9255 * x; |
||||||
|
b += 0.5490 * x; |
||||||
|
x = c1 * m * y1 * k; // 0 1 0 1
|
||||||
|
r += 0.1412 * x; |
||||||
|
x = c1 * m * y * k1; // 0 1 1 0
|
||||||
|
r += 0.9294 * x; |
||||||
|
g += 0.1098 * x; |
||||||
|
b += 0.1412 * x; |
||||||
|
x = c1 * m * y * k; // 0 1 1 1
|
||||||
|
r += 0.1333 * x; |
||||||
|
x = c * m1 * y1 * k1; // 1 0 0 0
|
||||||
|
g += 0.6784 * x; |
||||||
|
b += 0.9373 * x; |
||||||
|
x = c * m1 * y1 * k; // 1 0 0 1
|
||||||
|
g += 0.0588 * x; |
||||||
|
b += 0.1412 * x; |
||||||
|
x = c * m1 * y * k1; // 1 0 1 0
|
||||||
|
g += 0.6510 * x; |
||||||
|
b += 0.3137 * x; |
||||||
|
x = c * m1 * y * k; // 1 0 1 1
|
||||||
|
g += 0.0745 * x; |
||||||
|
x = c * m * y1 * k1; // 1 1 0 0
|
||||||
|
r += 0.1804 * x; |
||||||
|
g += 0.1922 * x; |
||||||
|
b += 0.5725 * x; |
||||||
|
x = c * m * y1 * k; // 1 1 0 1
|
||||||
|
b += 0.0078 * x; |
||||||
|
x = c * m * y * k1; // 1 1 1 0
|
||||||
|
r += 0.2118 * x; |
||||||
|
g += 0.2119 * x; |
||||||
|
b += 0.2235 * x; |
||||||
|
|
||||||
|
return [r, g, b]; |
||||||
|
}, |
||||||
|
getRgbBuffer: function cmykcs_getRgbBuffer(colorBuf, bits) { |
||||||
|
var scale = 1 / ((1 << bits) - 1); |
||||||
|
var length = colorBuf.length / 4; |
||||||
|
var rgbBuf = new Uint8Array(length * 3); |
||||||
|
var rgbBufPos = 0; |
||||||
|
var colorBufPos = 0; |
||||||
|
|
||||||
|
for (var i = 0; i < length; i++) { |
||||||
|
var cmyk = []; |
||||||
|
for (var j = 0; j < 4; ++j) |
||||||
|
cmyk.push(scale * colorBuf[colorBufPos++]); |
||||||
|
|
||||||
|
var rgb = this.getRgb(cmyk); |
||||||
|
for (var j = 0; j < 3; ++j) |
||||||
|
rgbBuf[rgbBufPos++] = Math.round(rgb[j] * 255); |
||||||
|
} |
||||||
|
|
||||||
|
return rgbBuf; |
||||||
|
} |
||||||
|
}; |
||||||
|
return constructor; |
||||||
|
})(); |
@ -0,0 +1,912 @@ |
|||||||
|
var PartialEvaluator = (function partialEvaluator() { |
||||||
|
function constructor(xref, handler, uniquePrefix) { |
||||||
|
this.state = new EvalState(); |
||||||
|
this.stateStack = []; |
||||||
|
|
||||||
|
this.xref = xref; |
||||||
|
this.handler = handler; |
||||||
|
this.uniquePrefix = uniquePrefix; |
||||||
|
this.objIdCounter = 0; |
||||||
|
} |
||||||
|
|
||||||
|
var OP_MAP = { |
||||||
|
// Graphics state
|
||||||
|
w: 'setLineWidth', |
||||||
|
J: 'setLineCap', |
||||||
|
j: 'setLineJoin', |
||||||
|
M: 'setMiterLimit', |
||||||
|
d: 'setDash', |
||||||
|
ri: 'setRenderingIntent', |
||||||
|
i: 'setFlatness', |
||||||
|
gs: 'setGState', |
||||||
|
q: 'save', |
||||||
|
Q: 'restore', |
||||||
|
cm: 'transform', |
||||||
|
|
||||||
|
// Path
|
||||||
|
m: 'moveTo', |
||||||
|
l: 'lineTo', |
||||||
|
c: 'curveTo', |
||||||
|
v: 'curveTo2', |
||||||
|
y: 'curveTo3', |
||||||
|
h: 'closePath', |
||||||
|
re: 'rectangle', |
||||||
|
S: 'stroke', |
||||||
|
s: 'closeStroke', |
||||||
|
f: 'fill', |
||||||
|
F: 'fill', |
||||||
|
'f*': 'eoFill', |
||||||
|
B: 'fillStroke', |
||||||
|
'B*': 'eoFillStroke', |
||||||
|
b: 'closeFillStroke', |
||||||
|
'b*': 'closeEOFillStroke', |
||||||
|
n: 'endPath', |
||||||
|
|
||||||
|
// Clipping
|
||||||
|
W: 'clip', |
||||||
|
'W*': 'eoClip', |
||||||
|
|
||||||
|
// Text
|
||||||
|
BT: 'beginText', |
||||||
|
ET: 'endText', |
||||||
|
Tc: 'setCharSpacing', |
||||||
|
Tw: 'setWordSpacing', |
||||||
|
Tz: 'setHScale', |
||||||
|
TL: 'setLeading', |
||||||
|
Tf: 'setFont', |
||||||
|
Tr: 'setTextRenderingMode', |
||||||
|
Ts: 'setTextRise', |
||||||
|
Td: 'moveText', |
||||||
|
TD: 'setLeadingMoveText', |
||||||
|
Tm: 'setTextMatrix', |
||||||
|
'T*': 'nextLine', |
||||||
|
Tj: 'showText', |
||||||
|
TJ: 'showSpacedText', |
||||||
|
"'": 'nextLineShowText', |
||||||
|
'"': 'nextLineSetSpacingShowText', |
||||||
|
|
||||||
|
// Type3 fonts
|
||||||
|
d0: 'setCharWidth', |
||||||
|
d1: 'setCharWidthAndBounds', |
||||||
|
|
||||||
|
// Color
|
||||||
|
CS: 'setStrokeColorSpace', |
||||||
|
cs: 'setFillColorSpace', |
||||||
|
SC: 'setStrokeColor', |
||||||
|
SCN: 'setStrokeColorN', |
||||||
|
sc: 'setFillColor', |
||||||
|
scn: 'setFillColorN', |
||||||
|
G: 'setStrokeGray', |
||||||
|
g: 'setFillGray', |
||||||
|
RG: 'setStrokeRGBColor', |
||||||
|
rg: 'setFillRGBColor', |
||||||
|
K: 'setStrokeCMYKColor', |
||||||
|
k: 'setFillCMYKColor', |
||||||
|
|
||||||
|
// Shading
|
||||||
|
sh: 'shadingFill', |
||||||
|
|
||||||
|
// Images
|
||||||
|
BI: 'beginInlineImage', |
||||||
|
ID: 'beginImageData', |
||||||
|
EI: 'endInlineImage', |
||||||
|
|
||||||
|
// XObjects
|
||||||
|
Do: 'paintXObject', |
||||||
|
|
||||||
|
// Marked content
|
||||||
|
MP: 'markPoint', |
||||||
|
DP: 'markPointProps', |
||||||
|
BMC: 'beginMarkedContent', |
||||||
|
BDC: 'beginMarkedContentProps', |
||||||
|
EMC: 'endMarkedContent', |
||||||
|
|
||||||
|
// Compatibility
|
||||||
|
BX: 'beginCompat', |
||||||
|
EX: 'endCompat' |
||||||
|
}; |
||||||
|
|
||||||
|
constructor.prototype = { |
||||||
|
getIRQueue: function partialEvaluatorGetIRQueue(stream, resources, |
||||||
|
queue, dependency) { |
||||||
|
|
||||||
|
var self = this; |
||||||
|
var xref = this.xref; |
||||||
|
var handler = this.handler; |
||||||
|
var uniquePrefix = this.uniquePrefix; |
||||||
|
|
||||||
|
function insertDependency(depList) { |
||||||
|
fnArray.push('dependency'); |
||||||
|
argsArray.push(depList); |
||||||
|
for (var i = 0; i < depList.length; i++) { |
||||||
|
var dep = depList[i]; |
||||||
|
if (dependency.indexOf(dep) == -1) { |
||||||
|
dependency.push(depList[i]); |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
function handleSetFont(fontName, fontRef) { |
||||||
|
var loadedName = null; |
||||||
|
|
||||||
|
var fontRes = resources.get('Font'); |
||||||
|
|
||||||
|
// TODO: TOASK: Is it possible to get here? If so, what does
|
||||||
|
// args[0].name should be like???
|
||||||
|
assert(fontRes, 'fontRes not available'); |
||||||
|
|
||||||
|
fontRes = xref.fetchIfRef(fontRes); |
||||||
|
fontRef = fontRef || fontRes.get(fontName); |
||||||
|
var font = xref.fetchIfRef(fontRef); |
||||||
|
assertWellFormed(isDict(font)); |
||||||
|
if (!font.translated) { |
||||||
|
font.translated = self.translateFont(font, xref, resources, handler, |
||||||
|
uniquePrefix, dependency); |
||||||
|
if (font.translated) { |
||||||
|
// keep track of each font we translated so the caller can
|
||||||
|
// load them asynchronously before calling display on a page
|
||||||
|
loadedName = 'font_' + uniquePrefix + ++self.objIdCounter; |
||||||
|
font.translated.properties.loadedName = loadedName; |
||||||
|
font.loadedName = loadedName; |
||||||
|
|
||||||
|
var translated = font.translated; |
||||||
|
handler.send('obj', [ |
||||||
|
loadedName, |
||||||
|
'Font', |
||||||
|
translated.name, |
||||||
|
translated.file, |
||||||
|
translated.properties |
||||||
|
]); |
||||||
|
} |
||||||
|
} |
||||||
|
loadedName = loadedName || font.loadedName; |
||||||
|
|
||||||
|
// Ensure the font is ready before the font is set
|
||||||
|
// and later on used for drawing.
|
||||||
|
// TODO: This should get insert to the IRQueue only once per
|
||||||
|
// page.
|
||||||
|
insertDependency([loadedName]); |
||||||
|
return loadedName; |
||||||
|
} |
||||||
|
|
||||||
|
function buildPaintImageXObject(image, inline) { |
||||||
|
var dict = image.dict; |
||||||
|
var w = dict.get('Width', 'W'); |
||||||
|
var h = dict.get('Height', 'H'); |
||||||
|
|
||||||
|
if (image instanceof JpegStream) { |
||||||
|
var objId = 'img_' + uniquePrefix + ++self.objIdCounter; |
||||||
|
handler.send('obj', [objId, 'JpegStream', image.getIR()]); |
||||||
|
|
||||||
|
// Add the dependency on the image object.
|
||||||
|
insertDependency([objId]); |
||||||
|
|
||||||
|
// The normal fn.
|
||||||
|
fn = 'paintJpegXObject'; |
||||||
|
args = [objId, w, h]; |
||||||
|
|
||||||
|
return; |
||||||
|
} |
||||||
|
|
||||||
|
// Needs to be rendered ourself.
|
||||||
|
|
||||||
|
// Figure out if the image has an imageMask.
|
||||||
|
var imageMask = dict.get('ImageMask', 'IM') || false; |
||||||
|
|
||||||
|
// If there is no imageMask, create the PDFImage and a lot
|
||||||
|
// of image processing can be done here.
|
||||||
|
if (!imageMask) { |
||||||
|
var imageObj = new PDFImage(xref, resources, image, inline); |
||||||
|
|
||||||
|
if (imageObj.imageMask) { |
||||||
|
throw 'Can\'t handle this in the web worker :/'; |
||||||
|
} |
||||||
|
|
||||||
|
var imgData = { |
||||||
|
width: w, |
||||||
|
height: h, |
||||||
|
data: new Uint8Array(w * h * 4) |
||||||
|
}; |
||||||
|
var pixels = imgData.data; |
||||||
|
imageObj.fillRgbaBuffer(pixels, imageObj.decode); |
||||||
|
|
||||||
|
fn = 'paintImageXObject'; |
||||||
|
args = [imgData]; |
||||||
|
return; |
||||||
|
} |
||||||
|
|
||||||
|
// This depends on a tmpCanvas beeing filled with the
|
||||||
|
// current fillStyle, such that processing the pixel
|
||||||
|
// data can't be done here. Instead of creating a
|
||||||
|
// complete PDFImage, only read the information needed
|
||||||
|
// for later.
|
||||||
|
fn = 'paintImageMaskXObject'; |
||||||
|
|
||||||
|
var width = dict.get('Width', 'W'); |
||||||
|
var height = dict.get('Height', 'H'); |
||||||
|
var bitStrideLength = (width + 7) >> 3; |
||||||
|
var imgArray = image.getBytes(bitStrideLength * height); |
||||||
|
var decode = dict.get('Decode', 'D'); |
||||||
|
var inverseDecode = !!decode && decode[0] > 0; |
||||||
|
|
||||||
|
args = [imgArray, inverseDecode, width, height]; |
||||||
|
} |
||||||
|
|
||||||
|
uniquePrefix = uniquePrefix || ''; |
||||||
|
if (!queue.argsArray) { |
||||||
|
queue.argsArray = []; |
||||||
|
} |
||||||
|
if (!queue.fnArray) { |
||||||
|
queue.fnArray = []; |
||||||
|
} |
||||||
|
|
||||||
|
var fnArray = queue.fnArray, argsArray = queue.argsArray; |
||||||
|
var dependency = dependency || []; |
||||||
|
|
||||||
|
resources = xref.fetchIfRef(resources) || new Dict(); |
||||||
|
var xobjs = xref.fetchIfRef(resources.get('XObject')) || new Dict(); |
||||||
|
var patterns = xref.fetchIfRef(resources.get('Pattern')) || new Dict(); |
||||||
|
var parser = new Parser(new Lexer(stream), false); |
||||||
|
var res = resources; |
||||||
|
var args = [], obj; |
||||||
|
var getObjBt = function getObjBt() { |
||||||
|
parser = this.oldParser; |
||||||
|
return { name: 'BT' }; |
||||||
|
}; |
||||||
|
var TILING_PATTERN = 1, SHADING_PATTERN = 2; |
||||||
|
|
||||||
|
while (!isEOF(obj = parser.getObj())) { |
||||||
|
if (isCmd(obj)) { |
||||||
|
var cmd = obj.cmd; |
||||||
|
var fn = OP_MAP[cmd]; |
||||||
|
if (!fn) { |
||||||
|
// invalid content command, trying to recover
|
||||||
|
if (cmd.substr(-2) == 'BT') { |
||||||
|
fn = OP_MAP[cmd.substr(0, cmd.length - 2)]; |
||||||
|
// feeding 'BT' on next interation
|
||||||
|
parser = { |
||||||
|
getObj: getObjBt, |
||||||
|
oldParser: parser |
||||||
|
}; |
||||||
|
} |
||||||
|
} |
||||||
|
assertWellFormed(fn, 'Unknown command "' + cmd + '"'); |
||||||
|
// TODO figure out how to type-check vararg functions
|
||||||
|
|
||||||
|
if ((cmd == 'SCN' || cmd == 'scn') && !args[args.length - 1].code) { |
||||||
|
// Use the IR version for setStroke/FillColorN.
|
||||||
|
fn += '_IR'; |
||||||
|
|
||||||
|
// compile tiling patterns
|
||||||
|
var patternName = args[args.length - 1]; |
||||||
|
// SCN/scn applies patterns along with normal colors
|
||||||
|
if (isName(patternName)) { |
||||||
|
var pattern = xref.fetchIfRef(patterns.get(patternName.name)); |
||||||
|
if (pattern) { |
||||||
|
var dict = isStream(pattern) ? pattern.dict : pattern; |
||||||
|
var typeNum = dict.get('PatternType'); |
||||||
|
|
||||||
|
if (typeNum == TILING_PATTERN) { |
||||||
|
// Create an IR of the pattern code.
|
||||||
|
var depIdx = dependency.length; |
||||||
|
var codeIR = this.getIRQueue(pattern, |
||||||
|
dict.get('Resources'), {}, dependency); |
||||||
|
|
||||||
|
// Add the dependencies that are required to execute the
|
||||||
|
// codeIR.
|
||||||
|
insertDependency(dependency.slice(depIdx)); |
||||||
|
|
||||||
|
args = TilingPattern.getIR(codeIR, dict, args); |
||||||
|
} |
||||||
|
else if (typeNum == SHADING_PATTERN) { |
||||||
|
var shading = xref.fetchIfRef(dict.get('Shading')); |
||||||
|
var matrix = dict.get('Matrix'); |
||||||
|
var pattern = Pattern.parseShading(shading, matrix, xref, res, |
||||||
|
null /*ctx*/); |
||||||
|
args = pattern.getIR(); |
||||||
|
} else { |
||||||
|
error('Unkown PatternType ' + typeNum); |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
} else 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) { |
||||||
|
var matrix = xobj.dict.get('Matrix'); |
||||||
|
var bbox = xobj.dict.get('BBox'); |
||||||
|
|
||||||
|
fnArray.push('paintFormXObjectBegin'); |
||||||
|
argsArray.push([matrix, bbox]); |
||||||
|
|
||||||
|
// This adds the IRQueue of the xObj to the current queue.
|
||||||
|
var depIdx = dependency.length; |
||||||
|
|
||||||
|
this.getIRQueue(xobj, xobj.dict.get('Resources'), queue, |
||||||
|
dependency); |
||||||
|
|
||||||
|
// Add the dependencies that are required to execute the
|
||||||
|
// codeIR.
|
||||||
|
insertDependency(dependency.slice(depIdx)); |
||||||
|
|
||||||
|
fn = 'paintFormXObjectEnd'; |
||||||
|
args = []; |
||||||
|
} else if ('Image' == type.name) { |
||||||
|
buildPaintImageXObject(xobj, false); |
||||||
|
} else { |
||||||
|
error('Unhandled XObject subtype ' + type.name); |
||||||
|
} |
||||||
|
} |
||||||
|
} else if (cmd == 'Tf') { // eagerly collect all fonts
|
||||||
|
args[0] = handleSetFont(args[0].name); |
||||||
|
} else if (cmd == 'EI') { |
||||||
|
buildPaintImageXObject(args[0], true); |
||||||
|
} |
||||||
|
|
||||||
|
switch (fn) { |
||||||
|
// Parse the ColorSpace data to a raw format.
|
||||||
|
case 'setFillColorSpace': |
||||||
|
case 'setStrokeColorSpace': |
||||||
|
args = [ColorSpace.parseToIR(args[0], xref, resources)]; |
||||||
|
break; |
||||||
|
case 'shadingFill': |
||||||
|
var shadingRes = xref.fetchIfRef(res.get('Shading')); |
||||||
|
if (!shadingRes) |
||||||
|
error('No shading resource found'); |
||||||
|
|
||||||
|
var shading = xref.fetchIfRef(shadingRes.get(args[0].name)); |
||||||
|
if (!shading) |
||||||
|
error('No shading object found'); |
||||||
|
|
||||||
|
var shadingFill = Pattern.parseShading(shading, null, xref, res, |
||||||
|
null); |
||||||
|
var patternIR = shadingFill.getIR(); |
||||||
|
args = [patternIR]; |
||||||
|
fn = 'shadingFill'; |
||||||
|
break; |
||||||
|
case 'setGState': |
||||||
|
var dictName = args[0]; |
||||||
|
var extGState = xref.fetchIfRef(resources.get('ExtGState')); |
||||||
|
|
||||||
|
if (!isDict(extGState) || !extGState.has(dictName.name)) |
||||||
|
break; |
||||||
|
|
||||||
|
var gsState = xref.fetchIfRef(extGState.get(dictName.name)); |
||||||
|
|
||||||
|
// This array holds the converted/processed state data.
|
||||||
|
var gsStateObj = []; |
||||||
|
|
||||||
|
gsState.forEach( |
||||||
|
function canvasGraphicsSetGStateForEach(key, value) { |
||||||
|
switch (key) { |
||||||
|
case 'Type': |
||||||
|
break; |
||||||
|
case 'LW': |
||||||
|
case 'LC': |
||||||
|
case 'LJ': |
||||||
|
case 'ML': |
||||||
|
case 'D': |
||||||
|
case 'RI': |
||||||
|
case 'FL': |
||||||
|
gsStateObj.push([key, value]); |
||||||
|
break; |
||||||
|
case 'Font': |
||||||
|
gsStateObj.push([ |
||||||
|
'Font', |
||||||
|
handleSetFont(null, value[0]), |
||||||
|
value[1] |
||||||
|
]); |
||||||
|
break; |
||||||
|
case 'OP': |
||||||
|
case 'op': |
||||||
|
case 'OPM': |
||||||
|
case 'BG': |
||||||
|
case 'BG2': |
||||||
|
case 'UCR': |
||||||
|
case 'UCR2': |
||||||
|
case 'TR': |
||||||
|
case 'TR2': |
||||||
|
case 'HT': |
||||||
|
case 'SM': |
||||||
|
case 'SA': |
||||||
|
case 'BM': |
||||||
|
case 'SMask': |
||||||
|
case 'CA': |
||||||
|
case 'ca': |
||||||
|
case 'AIS': |
||||||
|
case 'TK': |
||||||
|
TODO('graphic state operator ' + key); |
||||||
|
break; |
||||||
|
default: |
||||||
|
warn('Unknown graphic state operator ' + key); |
||||||
|
break; |
||||||
|
} |
||||||
|
} |
||||||
|
); |
||||||
|
args = [gsStateObj]; |
||||||
|
break; |
||||||
|
} // switch
|
||||||
|
|
||||||
|
fnArray.push(fn); |
||||||
|
argsArray.push(args); |
||||||
|
args = []; |
||||||
|
} else if (obj != null) { |
||||||
|
assertWellFormed(args.length <= 33, 'Too many arguments'); |
||||||
|
args.push(obj); |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
return { |
||||||
|
fnArray: fnArray, |
||||||
|
argsArray: argsArray |
||||||
|
}; |
||||||
|
}, |
||||||
|
|
||||||
|
extractEncoding: function partialEvaluatorExtractEncoding(dict, |
||||||
|
xref, |
||||||
|
properties) { |
||||||
|
var type = properties.type, encoding; |
||||||
|
if (properties.composite) { |
||||||
|
var defaultWidth = xref.fetchIfRef(dict.get('DW')) || 1000; |
||||||
|
properties.defaultWidth = defaultWidth; |
||||||
|
|
||||||
|
var glyphsWidths = {}; |
||||||
|
var widths = xref.fetchIfRef(dict.get('W')); |
||||||
|
if (widths) { |
||||||
|
var start = 0, end = 0; |
||||||
|
for (var i = 0; i < widths.length; i++) { |
||||||
|
var code = widths[i]; |
||||||
|
if (isArray(code)) { |
||||||
|
for (var j = 0; j < code.length; j++) |
||||||
|
glyphsWidths[start++] = code[j]; |
||||||
|
start = 0; |
||||||
|
} else if (start) { |
||||||
|
var width = widths[++i]; |
||||||
|
for (var j = start; j <= code; j++) |
||||||
|
glyphsWidths[j] = width; |
||||||
|
start = 0; |
||||||
|
} else { |
||||||
|
start = code; |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
properties.widths = glyphsWidths; |
||||||
|
|
||||||
|
// Glyph ids are big-endian 2-byte values
|
||||||
|
encoding = properties.encoding; |
||||||
|
|
||||||
|
// CIDSystemInfo might help to match width and glyphs
|
||||||
|
var cidSystemInfo = dict.get('CIDSystemInfo'); |
||||||
|
if (isDict(cidSystemInfo)) { |
||||||
|
properties.cidSystemInfo = { |
||||||
|
registry: cidSystemInfo.get('Registry'), |
||||||
|
ordering: cidSystemInfo.get('Ordering'), |
||||||
|
supplement: cidSystemInfo.get('Supplement') |
||||||
|
}; |
||||||
|
} |
||||||
|
|
||||||
|
var cidToGidMap = dict.get('CIDToGIDMap'); |
||||||
|
if (!cidToGidMap || !isRef(cidToGidMap)) { |
||||||
|
|
||||||
|
|
||||||
|
return Object.create(GlyphsUnicode); |
||||||
|
} |
||||||
|
|
||||||
|
// Extract the encoding from the CIDToGIDMap
|
||||||
|
var glyphsStream = xref.fetchIfRef(cidToGidMap); |
||||||
|
var glyphsData = glyphsStream.getBytes(0); |
||||||
|
|
||||||
|
// Set encoding 0 to later verify the font has an encoding
|
||||||
|
encoding[0] = { unicode: 0, width: 0 }; |
||||||
|
for (var j = 0; j < glyphsData.length; j++) { |
||||||
|
var glyphID = (glyphsData[j++] << 8) | glyphsData[j]; |
||||||
|
if (glyphID == 0) |
||||||
|
continue; |
||||||
|
|
||||||
|
var code = j >> 1; |
||||||
|
var width = glyphsWidths[code]; |
||||||
|
encoding[code] = { |
||||||
|
unicode: glyphID, |
||||||
|
width: isNum(width) ? width : defaultWidth |
||||||
|
}; |
||||||
|
} |
||||||
|
|
||||||
|
return Object.create(GlyphsUnicode); |
||||||
|
} |
||||||
|
|
||||||
|
var differences = properties.differences; |
||||||
|
var map = properties.encoding; |
||||||
|
var baseEncoding = null; |
||||||
|
if (dict.has('Encoding')) { |
||||||
|
encoding = xref.fetchIfRef(dict.get('Encoding')); |
||||||
|
if (isDict(encoding)) { |
||||||
|
var baseName = encoding.get('BaseEncoding'); |
||||||
|
if (baseName) |
||||||
|
baseEncoding = Encodings[baseName.name].slice(); |
||||||
|
|
||||||
|
// Load the differences between the base and original
|
||||||
|
if (encoding.has('Differences')) { |
||||||
|
var diffEncoding = encoding.get('Differences'); |
||||||
|
var index = 0; |
||||||
|
for (var j = 0; j < diffEncoding.length; j++) { |
||||||
|
var data = diffEncoding[j]; |
||||||
|
if (isNum(data)) |
||||||
|
index = data; |
||||||
|
else |
||||||
|
differences[index++] = data.name; |
||||||
|
} |
||||||
|
} |
||||||
|
} else if (isName(encoding)) { |
||||||
|
baseEncoding = Encodings[encoding.name].slice(); |
||||||
|
} else { |
||||||
|
error('Encoding is not a Name nor a Dict'); |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
if (!baseEncoding) { |
||||||
|
switch (type) { |
||||||
|
case 'TrueType': |
||||||
|
baseEncoding = Encodings.WinAnsiEncoding.slice(); |
||||||
|
break; |
||||||
|
case 'Type1': |
||||||
|
case 'Type3': |
||||||
|
baseEncoding = Encodings.StandardEncoding.slice(); |
||||||
|
break; |
||||||
|
default: |
||||||
|
warn('Unknown type of font: ' + type); |
||||||
|
baseEncoding = []; |
||||||
|
break; |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
// merge in the differences
|
||||||
|
var firstChar = properties.firstChar; |
||||||
|
var lastChar = properties.lastChar; |
||||||
|
var widths = properties.widths || []; |
||||||
|
var glyphs = {}; |
||||||
|
for (var i = firstChar; i <= lastChar; i++) { |
||||||
|
var glyph = differences[i]; |
||||||
|
var replaceGlyph = true; |
||||||
|
if (!glyph) { |
||||||
|
glyph = baseEncoding[i] || i; |
||||||
|
replaceGlyph = false; |
||||||
|
} |
||||||
|
var index = GlyphsUnicode[glyph] || i; |
||||||
|
var width = widths[i] || widths[glyph]; |
||||||
|
map[i] = { |
||||||
|
unicode: index, |
||||||
|
width: isNum(width) ? width : properties.defaultWidth |
||||||
|
}; |
||||||
|
|
||||||
|
if (replaceGlyph || !glyphs[glyph]) |
||||||
|
glyphs[glyph] = map[i]; |
||||||
|
if (replaceGlyph || !glyphs[index]) |
||||||
|
glyphs[index] = map[i]; |
||||||
|
|
||||||
|
// If there is no file, the character mapping can't be modified
|
||||||
|
// but this is unlikely that there is any standard encoding with
|
||||||
|
// chars below 0x1f, so that's fine.
|
||||||
|
if (!properties.file) |
||||||
|
continue; |
||||||
|
|
||||||
|
if (index <= 0x1f || (index >= 127 && index <= 255)) |
||||||
|
map[i].unicode += kCmapGlyphOffset; |
||||||
|
} |
||||||
|
|
||||||
|
if (type == 'TrueType' && dict.has('ToUnicode') && differences) { |
||||||
|
var cmapObj = dict.get('ToUnicode'); |
||||||
|
if (isRef(cmapObj)) { |
||||||
|
cmapObj = xref.fetch(cmapObj); |
||||||
|
} |
||||||
|
if (isName(cmapObj)) { |
||||||
|
error('ToUnicode file cmap translation not implemented'); |
||||||
|
} else if (isStream(cmapObj)) { |
||||||
|
var tokens = []; |
||||||
|
var token = ''; |
||||||
|
var beginArrayToken = {}; |
||||||
|
|
||||||
|
var cmap = cmapObj.getBytes(cmapObj.length); |
||||||
|
for (var i = 0; i < cmap.length; i++) { |
||||||
|
var byte = cmap[i]; |
||||||
|
if (byte == 0x20 || byte == 0x0D || byte == 0x0A || |
||||||
|
byte == 0x3C || byte == 0x5B || byte == 0x5D) { |
||||||
|
switch (token) { |
||||||
|
case 'usecmap': |
||||||
|
error('usecmap is not implemented'); |
||||||
|
break; |
||||||
|
|
||||||
|
case 'beginbfchar': |
||||||
|
case 'beginbfrange': |
||||||
|
case 'begincidchar': |
||||||
|
case 'begincidrange': |
||||||
|
token = ''; |
||||||
|
tokens = []; |
||||||
|
break; |
||||||
|
|
||||||
|
case 'endcidrange': |
||||||
|
case 'endbfrange': |
||||||
|
for (var j = 0; j < tokens.length; j += 3) { |
||||||
|
var startRange = tokens[j]; |
||||||
|
var endRange = tokens[j + 1]; |
||||||
|
var code = tokens[j + 2]; |
||||||
|
while (startRange < endRange) { |
||||||
|
var mapping = map[startRange] || {}; |
||||||
|
mapping.unicode = code++; |
||||||
|
map[startRange] = mapping; |
||||||
|
++startRange; |
||||||
|
} |
||||||
|
} |
||||||
|
break; |
||||||
|
|
||||||
|
case 'endcidchar': |
||||||
|
case 'endbfchar': |
||||||
|
for (var j = 0; j < tokens.length; j += 2) { |
||||||
|
var index = tokens[j]; |
||||||
|
var code = tokens[j + 1]; |
||||||
|
var mapping = map[index] || {}; |
||||||
|
mapping.unicode = code; |
||||||
|
map[index] = mapping; |
||||||
|
} |
||||||
|
break; |
||||||
|
|
||||||
|
case '': |
||||||
|
break; |
||||||
|
|
||||||
|
default: |
||||||
|
if (token[0] >= '0' && token[0] <= '9') |
||||||
|
token = parseInt(token, 10); // a number
|
||||||
|
tokens.push(token); |
||||||
|
token = ''; |
||||||
|
} |
||||||
|
switch (byte) { |
||||||
|
case 0x5B: |
||||||
|
// begin list parsing
|
||||||
|
tokens.push(beginArrayToken); |
||||||
|
break; |
||||||
|
case 0x5D: |
||||||
|
// collect array items
|
||||||
|
var items = [], item; |
||||||
|
while (tokens.length && |
||||||
|
(item = tokens.pop()) != beginArrayToken) |
||||||
|
items.unshift(item); |
||||||
|
tokens.push(items); |
||||||
|
break; |
||||||
|
} |
||||||
|
} else if (byte == 0x3E) { |
||||||
|
if (token.length) { |
||||||
|
// parsing hex number
|
||||||
|
tokens.push(parseInt(token, 16)); |
||||||
|
token = ''; |
||||||
|
} |
||||||
|
} else { |
||||||
|
token += String.fromCharCode(byte); |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
return glyphs; |
||||||
|
}, |
||||||
|
|
||||||
|
getBaseFontMetricsAndMap: function getBaseFontMetricsAndMap(name) { |
||||||
|
var map = {}; |
||||||
|
if (/^Symbol(-?(Bold|Italic))*$/.test(name)) { |
||||||
|
// special case for symbols
|
||||||
|
var encoding = Encodings.symbolsEncoding.slice(); |
||||||
|
for (var i = 0, n = encoding.length, j; i < n; i++) { |
||||||
|
if (!(j = encoding[i])) |
||||||
|
continue; |
||||||
|
map[i] = GlyphsUnicode[j] || 0; |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
var defaultWidth = 0; |
||||||
|
var widths = Metrics[stdFontMap[name] || name]; |
||||||
|
if (isNum(widths)) { |
||||||
|
defaultWidth = widths; |
||||||
|
widths = null; |
||||||
|
} |
||||||
|
|
||||||
|
return { |
||||||
|
defaultWidth: defaultWidth, |
||||||
|
widths: widths || [], |
||||||
|
map: map |
||||||
|
}; |
||||||
|
}, |
||||||
|
|
||||||
|
translateFont: function partialEvaluatorTranslateFont(dict, xref, resources, |
||||||
|
queue, handler, uniquePrefix, dependency) { |
||||||
|
var baseDict = dict; |
||||||
|
var type = dict.get('Subtype'); |
||||||
|
assertWellFormed(isName(type), 'invalid font Subtype'); |
||||||
|
|
||||||
|
var composite = false; |
||||||
|
if (type.name == 'Type0') { |
||||||
|
// If font is a composite
|
||||||
|
// - get the descendant font
|
||||||
|
// - set the type according to the descendant font
|
||||||
|
// - get the FontDescriptor from the descendant font
|
||||||
|
var df = dict.get('DescendantFonts'); |
||||||
|
if (!df) |
||||||
|
return null; |
||||||
|
|
||||||
|
if (isRef(df)) |
||||||
|
df = xref.fetch(df); |
||||||
|
|
||||||
|
dict = xref.fetchIfRef(isRef(df) ? df : df[0]); |
||||||
|
|
||||||
|
type = dict.get('Subtype'); |
||||||
|
assertWellFormed(isName(type), 'invalid font Subtype'); |
||||||
|
composite = true; |
||||||
|
} |
||||||
|
|
||||||
|
var descriptor = xref.fetchIfRef(dict.get('FontDescriptor')); |
||||||
|
if (!descriptor) { |
||||||
|
if (type.name == 'Type3') { |
||||||
|
// FontDescriptor is only required for Type3 fonts when the document
|
||||||
|
// is a tagged pdf. Create a barbebones one to get by.
|
||||||
|
descriptor = new Dict(); |
||||||
|
descriptor.set('FontName', new Name(type.name)); |
||||||
|
} else { |
||||||
|
// Before PDF 1.5 if the font was one of the base 14 fonts, having a
|
||||||
|
// FontDescriptor was not required.
|
||||||
|
// This case is here for compatibility.
|
||||||
|
var baseFontName = dict.get('BaseFont'); |
||||||
|
if (!isName(baseFontName)) |
||||||
|
return null; |
||||||
|
|
||||||
|
// Using base font name as a font name.
|
||||||
|
baseFontName = baseFontName.name.replace(/[,_]/g, '-'); |
||||||
|
var metricsAndMap = this.getBaseFontMetricsAndMap(baseFontName); |
||||||
|
|
||||||
|
var properties = { |
||||||
|
type: type.name, |
||||||
|
encoding: metricsAndMap.map, |
||||||
|
differences: [], |
||||||
|
widths: metricsAndMap.widths, |
||||||
|
defaultWidth: metricsAndMap.defaultWidth, |
||||||
|
firstChar: 0, |
||||||
|
lastChar: 256 |
||||||
|
}; |
||||||
|
this.extractEncoding(dict, xref, properties); |
||||||
|
|
||||||
|
return { |
||||||
|
name: baseFontName, |
||||||
|
dict: baseDict, |
||||||
|
properties: properties |
||||||
|
}; |
||||||
|
} |
||||||
|
|
||||||
|
} |
||||||
|
|
||||||
|
// According to the spec if 'FontDescriptor' is declared, 'FirstChar',
|
||||||
|
// 'LastChar' and 'Widths' should exists too, but some PDF encoders seems
|
||||||
|
// to ignore this rule when a variant of a standart font is used.
|
||||||
|
// TODO Fill the width array depending on which of the base font this is
|
||||||
|
// a variant.
|
||||||
|
var firstChar = xref.fetchIfRef(dict.get('FirstChar')) || 0; |
||||||
|
var lastChar = xref.fetchIfRef(dict.get('LastChar')) || 256; |
||||||
|
var defaultWidth = 0; |
||||||
|
var glyphWidths = {}; |
||||||
|
var encoding = {}; |
||||||
|
var widths = xref.fetchIfRef(dict.get('Widths')); |
||||||
|
if (widths) { |
||||||
|
for (var i = 0, j = firstChar; i < widths.length; i++, j++) |
||||||
|
glyphWidths[j] = widths[i]; |
||||||
|
defaultWidth = parseFloat(descriptor.get('MissingWidth')) || 0; |
||||||
|
} else { |
||||||
|
// Trying get the BaseFont metrics (see comment above).
|
||||||
|
var baseFontName = dict.get('BaseFont'); |
||||||
|
if (isName(baseFontName)) { |
||||||
|
var metricsAndMap = this.getBaseFontMetricsAndMap(baseFontName.name); |
||||||
|
|
||||||
|
glyphWidths = metricsAndMap.widths; |
||||||
|
defaultWidth = metricsAndMap.defaultWidth; |
||||||
|
encoding = metricsAndMap.map; |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
var fontName = xref.fetchIfRef(descriptor.get('FontName')); |
||||||
|
assertWellFormed(isName(fontName), 'invalid font name'); |
||||||
|
|
||||||
|
var fontFile = descriptor.get('FontFile', 'FontFile2', 'FontFile3'); |
||||||
|
if (fontFile) { |
||||||
|
fontFile = xref.fetchIfRef(fontFile); |
||||||
|
if (fontFile.dict) { |
||||||
|
var subtype = fontFile.dict.get('Subtype'); |
||||||
|
if (subtype) |
||||||
|
subtype = subtype.name; |
||||||
|
|
||||||
|
var length1 = fontFile.dict.get('Length1'); |
||||||
|
if (!isInt(length1)) |
||||||
|
length1 = xref.fetchIfRef(length1); |
||||||
|
|
||||||
|
var length2 = fontFile.dict.get('Length2'); |
||||||
|
if (!isInt(length2)) |
||||||
|
length2 = xref.fetchIfRef(length2); |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
var properties = { |
||||||
|
type: type.name, |
||||||
|
subtype: subtype, |
||||||
|
file: fontFile, |
||||||
|
length1: length1, |
||||||
|
length2: length2, |
||||||
|
composite: composite, |
||||||
|
fixedPitch: false, |
||||||
|
fontMatrix: dict.get('FontMatrix') || IDENTITY_MATRIX, |
||||||
|
firstChar: firstChar || 0, |
||||||
|
lastChar: lastChar || 256, |
||||||
|
bbox: descriptor.get('FontBBox'), |
||||||
|
ascent: descriptor.get('Ascent'), |
||||||
|
descent: descriptor.get('Descent'), |
||||||
|
xHeight: descriptor.get('XHeight'), |
||||||
|
capHeight: descriptor.get('CapHeight'), |
||||||
|
defaultWidth: defaultWidth, |
||||||
|
flags: descriptor.get('Flags'), |
||||||
|
italicAngle: descriptor.get('ItalicAngle'), |
||||||
|
differences: [], |
||||||
|
widths: glyphWidths, |
||||||
|
encoding: encoding, |
||||||
|
coded: false |
||||||
|
}; |
||||||
|
properties.glyphs = this.extractEncoding(dict, xref, properties); |
||||||
|
|
||||||
|
if (type.name === 'Type3') { |
||||||
|
properties.coded = true; |
||||||
|
var charProcs = xref.fetchIfRef(dict.get('CharProcs')); |
||||||
|
var fontResources = xref.fetchIfRef(dict.get('Resources')) || resources; |
||||||
|
properties.resources = fontResources; |
||||||
|
for (var key in charProcs.map) { |
||||||
|
var glyphStream = xref.fetchIfRef(charProcs.map[key]); |
||||||
|
var queue = {}; |
||||||
|
properties.glyphs[key].IRQueue = this.getIRQueue(glyphStream, |
||||||
|
fontResources, queue, dependency); |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
return { |
||||||
|
name: fontName.name, |
||||||
|
dict: baseDict, |
||||||
|
file: fontFile, |
||||||
|
properties: properties |
||||||
|
}; |
||||||
|
} |
||||||
|
}; |
||||||
|
|
||||||
|
return constructor; |
||||||
|
})(); |
||||||
|
|
||||||
|
var EvalState = (function evalState() { |
||||||
|
function constructor() { |
||||||
|
// Are soft masks and alpha values shapes or opacities?
|
||||||
|
this.alphaIsShape = false; |
||||||
|
this.fontSize = 0; |
||||||
|
this.textMatrix = IDENTITY_MATRIX; |
||||||
|
this.leading = 0; |
||||||
|
// Start of text line (in text coordinates)
|
||||||
|
this.lineX = 0; |
||||||
|
this.lineY = 0; |
||||||
|
// Character and word spacing
|
||||||
|
this.charSpacing = 0; |
||||||
|
this.wordSpacing = 0; |
||||||
|
this.textHScale = 1; |
||||||
|
// Color spaces
|
||||||
|
this.fillColorSpace = null; |
||||||
|
this.strokeColorSpace = null; |
||||||
|
} |
||||||
|
constructor.prototype = { |
||||||
|
}; |
||||||
|
return constructor; |
||||||
|
})(); |
@ -0,0 +1,300 @@ |
|||||||
|
var PDFFunction = (function() { |
||||||
|
var CONSTRUCT_SAMPLED = 0; |
||||||
|
var CONSTRUCT_INTERPOLATED = 2; |
||||||
|
var CONSTRUCT_STICHED = 3; |
||||||
|
var CONSTRUCT_POSTSCRIPT = 4; |
||||||
|
|
||||||
|
return { |
||||||
|
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++) { |
||||||
|
while (codeSize < bps) { |
||||||
|
codeBuf <<= 8; |
||||||
|
codeBuf |= strBytes[strIdx++]; |
||||||
|
codeSize += 8; |
||||||
|
} |
||||||
|
codeSize -= bps; |
||||||
|
array.push(codeBuf >> codeSize); |
||||||
|
codeBuf &= (1 << codeSize) - 1; |
||||||
|
} |
||||||
|
return array; |
||||||
|
}, |
||||||
|
|
||||||
|
getIR: function(xref, fn) { |
||||||
|
var dict = fn.dict; |
||||||
|
if (!dict) |
||||||
|
dict = fn; |
||||||
|
|
||||||
|
var 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'); |
||||||
|
|
||||||
|
return typeFn.call(this, fn, dict, xref); |
||||||
|
}, |
||||||
|
|
||||||
|
fromIR: function(IR) { |
||||||
|
var type = IR[0]; |
||||||
|
switch (type) { |
||||||
|
case CONSTRUCT_SAMPLED: |
||||||
|
return this.constructSampledFromIR(IR); |
||||||
|
case CONSTRUCT_INTERPOLATED: |
||||||
|
return this.constructInterpolatedFromIR(IR); |
||||||
|
case CONSTRUCT_STICHED: |
||||||
|
return this.constructStichedFromIR(IR); |
||||||
|
case CONSTRUCT_POSTSCRIPT: |
||||||
|
return this.constructPostScriptFromIR(IR); |
||||||
|
} |
||||||
|
}, |
||||||
|
|
||||||
|
parse: function(xref, fn) { |
||||||
|
var IR = this.getIR(xref, fn); |
||||||
|
return this.fromIR(IR); |
||||||
|
}, |
||||||
|
|
||||||
|
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: ' + |
||||||
|
inputSize); |
||||||
|
|
||||||
|
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: ' + order); |
||||||
|
|
||||||
|
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); |
||||||
|
|
||||||
|
return [ |
||||||
|
CONSTRUCT_SAMPLED, inputSize, domain, encode, decode, samples, size, |
||||||
|
outputSize, bps, range |
||||||
|
]; |
||||||
|
}, |
||||||
|
|
||||||
|
constructSampledFromIR: function(IR) { |
||||||
|
var inputSize = IR[1]; |
||||||
|
var domain = IR[2]; |
||||||
|
var encode = IR[3]; |
||||||
|
var decode = IR[4]; |
||||||
|
var samples = IR[5]; |
||||||
|
var size = IR[6]; |
||||||
|
var outputSize = IR[7]; |
||||||
|
var bps = IR[8]; |
||||||
|
var range = IR[9]; |
||||||
|
|
||||||
|
return 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: ' + inputSize + ' != ' + |
||||||
|
args.length); |
||||||
|
|
||||||
|
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 = [], v = 0; |
||||||
|
for (var i = 0; i < outputSize; ++i) { |
||||||
|
if (ceil == floor) { |
||||||
|
v = samples[ceil + i]; |
||||||
|
} else { |
||||||
|
var low = samples[floor + i]; |
||||||
|
var high = samples[ceil + i]; |
||||||
|
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; |
||||||
|
} |
||||||
|
}, |
||||||
|
|
||||||
|
constructInterpolated: |
||||||
|
function pdfFunctionConstructInterpolated(str, dict) { |
||||||
|
var c0 = dict.get('C0') || [0]; |
||||||
|
var c1 = dict.get('C1') || [1]; |
||||||
|
var n = dict.get('N'); |
||||||
|
|
||||||
|
if (!isArray(c0) || !isArray(c1)) |
||||||
|
error('Illegal dictionary for interpolated function'); |
||||||
|
|
||||||
|
var length = c0.length; |
||||||
|
var diff = []; |
||||||
|
for (var i = 0; i < length; ++i) |
||||||
|
diff.push(c1[i] - c0[i]); |
||||||
|
|
||||||
|
return [CONSTRUCT_INTERPOLATED, c0, diff, n]; |
||||||
|
}, |
||||||
|
|
||||||
|
constructInterpolatedFromIR: |
||||||
|
function pdfFunctionconstructInterpolatedFromIR(IR) { |
||||||
|
var c0 = IR[1]; |
||||||
|
var diff = IR[2]; |
||||||
|
var n = IR[3]; |
||||||
|
|
||||||
|
var length = diff.length; |
||||||
|
|
||||||
|
return function(args) { |
||||||
|
var x = n == 1 ? args[0] : Math.pow(args[0], n); |
||||||
|
|
||||||
|
var out = []; |
||||||
|
for (var j = 0; j < length; ++j) |
||||||
|
out.push(c0[j] + (x * diff[j])); |
||||||
|
|
||||||
|
return out; |
||||||
|
|
||||||
|
} |
||||||
|
}, |
||||||
|
|
||||||
|
constructStiched: function pdfFunctionConstructStiched(fn, dict, xref) { |
||||||
|
var domain = dict.get('Domain'); |
||||||
|
var range = dict.get('Range'); |
||||||
|
|
||||||
|
if (!domain) |
||||||
|
error('No domain'); |
||||||
|
|
||||||
|
var inputSize = domain.length / 2; |
||||||
|
if (inputSize != 1) |
||||||
|
error('Bad domain for stiched function'); |
||||||
|
|
||||||
|
var fnRefs = dict.get('Functions'); |
||||||
|
var fns = []; |
||||||
|
for (var i = 0, ii = fnRefs.length; i < ii; ++i) |
||||||
|
fns.push(PDFFunction.getIR(xref, xref.fetchIfRef(fnRefs[i]))); |
||||||
|
|
||||||
|
var bounds = dict.get('Bounds'); |
||||||
|
var encode = dict.get('Encode'); |
||||||
|
|
||||||
|
return [CONSTRUCT_STICHED, domain, bounds, encode, fns]; |
||||||
|
}, |
||||||
|
|
||||||
|
constructStichedFromIR: function pdfFunctionConstructStichedFromIR(IR) { |
||||||
|
var domain = IR[1]; |
||||||
|
var bounds = IR[2]; |
||||||
|
var encode = IR[3]; |
||||||
|
var fnsIR = IR[4]; |
||||||
|
var fns = []; |
||||||
|
|
||||||
|
for (var i = 0; i < fnsIR.length; i++) { |
||||||
|
fns.push(PDFFunction.fromIR(fnsIR[i])); |
||||||
|
} |
||||||
|
|
||||||
|
return function(args) { |
||||||
|
var clip = function(v, min, max) { |
||||||
|
if (v > max) |
||||||
|
v = max; |
||||||
|
else if (v < min) |
||||||
|
v = min; |
||||||
|
return v; |
||||||
|
}; |
||||||
|
|
||||||
|
// clip to domain
|
||||||
|
var v = clip(args[0], domain[0], domain[1]); |
||||||
|
// calulate which bound the value is in
|
||||||
|
for (var i = 0, ii = bounds.length; i < ii; ++i) { |
||||||
|
if (v < bounds[i]) |
||||||
|
break; |
||||||
|
} |
||||||
|
|
||||||
|
// encode value into domain of function
|
||||||
|
var dmin = domain[0]; |
||||||
|
if (i > 0) |
||||||
|
dmin = bounds[i - 1]; |
||||||
|
var dmax = domain[1]; |
||||||
|
if (i < bounds.length) |
||||||
|
dmax = bounds[i]; |
||||||
|
|
||||||
|
var rmin = encode[2 * i]; |
||||||
|
var rmax = encode[2 * i + 1]; |
||||||
|
|
||||||
|
var v2 = rmin + (v - dmin) * (rmax - rmin) / (dmax - dmin); |
||||||
|
|
||||||
|
// call the appropropriate function
|
||||||
|
return fns[i]([v2]); |
||||||
|
}; |
||||||
|
}, |
||||||
|
|
||||||
|
constructPostScript: function pdfFunctionConstructPostScript() { |
||||||
|
return [CONSTRUCT_POSTSCRIPT]; |
||||||
|
}, |
||||||
|
|
||||||
|
constructPostScriptFromIR: function pdfFunctionConstructPostScriptFromIR() { |
||||||
|
TODO('unhandled type of function'); |
||||||
|
return function() { |
||||||
|
return [255, 105, 180]; |
||||||
|
}; |
||||||
|
} |
||||||
|
}; |
||||||
|
})(); |
@ -0,0 +1,251 @@ |
|||||||
|
var PDFImage = (function pdfImage() { |
||||||
|
function constructor(xref, res, image, inline) { |
||||||
|
this.image = image; |
||||||
|
if (image.getParams) { |
||||||
|
// JPX/JPEG2000 streams directly contain bits per component
|
||||||
|
// and color space mode information.
|
||||||
|
TODO('get params from actual stream'); |
||||||
|
// var bits = ...
|
||||||
|
// var colorspace = ...
|
||||||
|
} |
||||||
|
// TODO cache rendered images?
|
||||||
|
|
||||||
|
var dict = image.dict; |
||||||
|
this.width = dict.get('Width', 'W'); |
||||||
|
this.height = dict.get('Height', 'H'); |
||||||
|
|
||||||
|
if (this.width < 1 || this.height < 1) |
||||||
|
error('Invalid image width: ' + this.width + ' or height: ' + |
||||||
|
this.height); |
||||||
|
|
||||||
|
this.interpolate = dict.get('Interpolate', 'I') || false; |
||||||
|
this.imageMask = dict.get('ImageMask', 'IM') || false; |
||||||
|
|
||||||
|
var bitsPerComponent = image.bitsPerComponent; |
||||||
|
if (!bitsPerComponent) { |
||||||
|
bitsPerComponent = dict.get('BitsPerComponent', 'BPC'); |
||||||
|
if (!bitsPerComponent) { |
||||||
|
if (this.imageMask) |
||||||
|
bitsPerComponent = 1; |
||||||
|
else |
||||||
|
error('Bits per component missing in image: ' + this.imageMask); |
||||||
|
} |
||||||
|
} |
||||||
|
this.bpc = bitsPerComponent; |
||||||
|
|
||||||
|
if (!this.imageMask) { |
||||||
|
var colorSpace = dict.get('ColorSpace', 'CS'); |
||||||
|
if (!colorSpace) { |
||||||
|
TODO('JPX images (which don"t require color spaces'); |
||||||
|
colorSpace = new Name('DeviceRGB'); |
||||||
|
} |
||||||
|
this.colorSpace = ColorSpace.parse(colorSpace, xref, res); |
||||||
|
this.numComps = this.colorSpace.numComps; |
||||||
|
} |
||||||
|
|
||||||
|
this.decode = dict.get('Decode', 'D'); |
||||||
|
|
||||||
|
var mask = xref.fetchIfRef(dict.get('Mask')); |
||||||
|
var smask = xref.fetchIfRef(dict.get('SMask')); |
||||||
|
|
||||||
|
if (mask) { |
||||||
|
TODO('masked images'); |
||||||
|
} else if (smask) { |
||||||
|
this.smask = new PDFImage(xref, res, smask); |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
constructor.prototype = { |
||||||
|
getComponents: function getComponents(buffer, decodeMap) { |
||||||
|
var bpc = this.bpc; |
||||||
|
if (bpc == 8) |
||||||
|
return buffer; |
||||||
|
|
||||||
|
var width = this.width; |
||||||
|
var height = this.height; |
||||||
|
var numComps = this.numComps; |
||||||
|
|
||||||
|
var length = width * height; |
||||||
|
var bufferPos = 0; |
||||||
|
var output = bpc <= 8 ? new Uint8Array(length) : |
||||||
|
bpc <= 16 ? new Uint16Array(length) : new Uint32Array(length); |
||||||
|
var rowComps = width * numComps; |
||||||
|
|
||||||
|
if (bpc == 1) { |
||||||
|
var valueZero = 0, valueOne = 1; |
||||||
|
if (decodeMap) { |
||||||
|
valueZero = decodeMap[0] ? 1 : 0; |
||||||
|
valueOne = decodeMap[1] ? 1 : 0; |
||||||
|
} |
||||||
|
var mask = 0; |
||||||
|
var buf = 0; |
||||||
|
|
||||||
|
for (var i = 0, ii = length; i < ii; ++i) { |
||||||
|
if (i % rowComps == 0) { |
||||||
|
mask = 0; |
||||||
|
buf = 0; |
||||||
|
} else { |
||||||
|
mask >>= 1; |
||||||
|
} |
||||||
|
|
||||||
|
if (mask <= 0) { |
||||||
|
buf = buffer[bufferPos++]; |
||||||
|
mask = 128; |
||||||
|
} |
||||||
|
|
||||||
|
output[i] = !(buf & mask) ? valueZero : valueOne; |
||||||
|
} |
||||||
|
} else { |
||||||
|
if (decodeMap != null) |
||||||
|
TODO('interpolate component values'); |
||||||
|
var bits = 0, buf = 0; |
||||||
|
for (var i = 0, ii = length; i < ii; ++i) { |
||||||
|
if (i % rowComps == 0) { |
||||||
|
buf = 0; |
||||||
|
bits = 0; |
||||||
|
} |
||||||
|
|
||||||
|
while (bits < bpc) { |
||||||
|
buf = (buf << 8) | buffer[bufferPos++]; |
||||||
|
bits += 8; |
||||||
|
} |
||||||
|
|
||||||
|
var remainingBits = bits - bpc; |
||||||
|
output[i] = buf >> remainingBits; |
||||||
|
buf = buf & ((1 << remainingBits) - 1); |
||||||
|
bits = remainingBits; |
||||||
|
} |
||||||
|
} |
||||||
|
return output; |
||||||
|
}, |
||||||
|
getOpacity: function getOpacity() { |
||||||
|
var smask = this.smask; |
||||||
|
var width = this.width; |
||||||
|
var height = this.height; |
||||||
|
var buf = new Uint8Array(width * height); |
||||||
|
|
||||||
|
if (smask) { |
||||||
|
if (smask.image.getImage) { |
||||||
|
// smask is a DOM image
|
||||||
|
var tempCanvas = new ScratchCanvas(width, height); |
||||||
|
var tempCtx = tempCanvas.getContext('2d'); |
||||||
|
var domImage = smask.image.getImage(); |
||||||
|
tempCtx.drawImage(domImage, 0, 0, domImage.width, domImage.height, |
||||||
|
0, 0, width, height); |
||||||
|
var data = tempCtx.getImageData(0, 0, width, height).data; |
||||||
|
for (var i = 0, j = 0, ii = width * height; i < ii; ++i, j += 4) |
||||||
|
buf[i] = data[j]; // getting first component value
|
||||||
|
return buf; |
||||||
|
} |
||||||
|
var sw = smask.width; |
||||||
|
var sh = smask.height; |
||||||
|
if (sw != this.width || sh != this.height) |
||||||
|
error('smask dimensions do not match image dimensions: ' + sw + |
||||||
|
' != ' + this.width + ', ' + sh + ' != ' + this.height); |
||||||
|
|
||||||
|
smask.fillGrayBuffer(buf); |
||||||
|
return buf; |
||||||
|
} else { |
||||||
|
for (var i = 0, ii = width * height; i < ii; ++i) |
||||||
|
buf[i] = 255; |
||||||
|
} |
||||||
|
return buf; |
||||||
|
}, |
||||||
|
applyStencilMask: function applyStencilMask(buffer, inverseDecode) { |
||||||
|
var width = this.width, height = this.height; |
||||||
|
var bitStrideLength = (width + 7) >> 3; |
||||||
|
this.image.reset(); |
||||||
|
var imgArray = this.image.getBytes(bitStrideLength * height); |
||||||
|
var imgArrayPos = 0; |
||||||
|
var i, j, mask, buf; |
||||||
|
// removing making non-masked pixels transparent
|
||||||
|
var bufferPos = 3; // alpha component offset
|
||||||
|
for (i = 0; i < height; i++) { |
||||||
|
mask = 0; |
||||||
|
for (j = 0; j < width; j++) { |
||||||
|
if (!mask) { |
||||||
|
buf = imgArray[imgArrayPos++]; |
||||||
|
mask = 128; |
||||||
|
} |
||||||
|
if (!(buf & mask) == inverseDecode) { |
||||||
|
buffer[bufferPos] = 0; |
||||||
|
} |
||||||
|
bufferPos += 4; |
||||||
|
mask >>= 1; |
||||||
|
} |
||||||
|
} |
||||||
|
}, |
||||||
|
fillRgbaBuffer: function fillRgbaBuffer(buffer, decodeMap) { |
||||||
|
var numComps = this.numComps; |
||||||
|
var width = this.width; |
||||||
|
var height = this.height; |
||||||
|
var bpc = this.bpc; |
||||||
|
|
||||||
|
// rows start at byte boundary;
|
||||||
|
var rowBytes = (width * numComps * bpc + 7) >> 3; |
||||||
|
this.image.reset(); |
||||||
|
var imgArray = this.image.getBytes(height * rowBytes); |
||||||
|
|
||||||
|
var comps = this.colorSpace.getRgbBuffer( |
||||||
|
this.getComponents(imgArray, decodeMap), bpc); |
||||||
|
var compsPos = 0; |
||||||
|
var opacity = this.getOpacity(); |
||||||
|
var opacityPos = 0; |
||||||
|
var length = width * height * 4; |
||||||
|
|
||||||
|
for (var i = 0; i < length; i += 4) { |
||||||
|
buffer[i] = comps[compsPos++]; |
||||||
|
buffer[i + 1] = comps[compsPos++]; |
||||||
|
buffer[i + 2] = comps[compsPos++]; |
||||||
|
buffer[i + 3] = opacity[opacityPos++]; |
||||||
|
} |
||||||
|
}, |
||||||
|
fillGrayBuffer: function fillGrayBuffer(buffer) { |
||||||
|
var numComps = this.numComps; |
||||||
|
if (numComps != 1) |
||||||
|
error('Reading gray scale from a color image: ' + numComps); |
||||||
|
|
||||||
|
var width = this.width; |
||||||
|
var height = this.height; |
||||||
|
var bpc = this.bpc; |
||||||
|
|
||||||
|
// rows start at byte boundary;
|
||||||
|
var rowBytes = (width * numComps * bpc + 7) >> 3; |
||||||
|
this.image.reset(); |
||||||
|
var imgArray = this.image.getBytes(height * rowBytes); |
||||||
|
|
||||||
|
var comps = this.getComponents(imgArray); |
||||||
|
var length = width * height; |
||||||
|
|
||||||
|
for (var i = 0; i < length; ++i) |
||||||
|
buffer[i] = comps[i]; |
||||||
|
} |
||||||
|
}; |
||||||
|
return constructor; |
||||||
|
})(); |
||||||
|
|
||||||
|
var JpegImage = (function() { |
||||||
|
function JpegImage(objId, imageData, objs) { |
||||||
|
var src = 'data:image/jpeg;base64,' + window.btoa(imageData); |
||||||
|
|
||||||
|
var img = new Image(); |
||||||
|
img.onload = (function() { |
||||||
|
this.loaded = true; |
||||||
|
|
||||||
|
objs.resolve(objId, this); |
||||||
|
|
||||||
|
if (this.onLoad) |
||||||
|
this.onLoad(); |
||||||
|
}).bind(this); |
||||||
|
img.src = src; |
||||||
|
this.domImage = img; |
||||||
|
} |
||||||
|
|
||||||
|
JpegImage.prototype = { |
||||||
|
getImage: function() { |
||||||
|
return this.domImage; |
||||||
|
} |
||||||
|
}; |
||||||
|
|
||||||
|
return JpegImage; |
||||||
|
})(); |
@ -0,0 +1,734 @@ |
|||||||
|
var Name = (function nameName() { |
||||||
|
function constructor(name) { |
||||||
|
this.name = name; |
||||||
|
} |
||||||
|
|
||||||
|
constructor.prototype = { |
||||||
|
}; |
||||||
|
|
||||||
|
return constructor; |
||||||
|
})(); |
||||||
|
|
||||||
|
var Cmd = (function cmdCmd() { |
||||||
|
function constructor(cmd) { |
||||||
|
this.cmd = cmd; |
||||||
|
} |
||||||
|
|
||||||
|
constructor.prototype = { |
||||||
|
}; |
||||||
|
|
||||||
|
return constructor; |
||||||
|
})(); |
||||||
|
|
||||||
|
var Dict = (function dictDict() { |
||||||
|
function constructor() { |
||||||
|
this.map = Object.create(null); |
||||||
|
} |
||||||
|
|
||||||
|
constructor.prototype = { |
||||||
|
get: function dictGet(key1, key2, key3) { |
||||||
|
var value; |
||||||
|
if (typeof (value = this.map[key1]) != 'undefined' || key1 in this.map || |
||||||
|
typeof key2 == 'undefined') { |
||||||
|
return value; |
||||||
|
} |
||||||
|
if (typeof (value = this.map[key2]) != 'undefined' || key2 in this.map || |
||||||
|
typeof key3 == 'undefined') { |
||||||
|
return value; |
||||||
|
} |
||||||
|
|
||||||
|
return this.map[key3] || null; |
||||||
|
}, |
||||||
|
|
||||||
|
set: function dictSet(key, value) { |
||||||
|
this.map[key] = value; |
||||||
|
}, |
||||||
|
|
||||||
|
has: function dictHas(key) { |
||||||
|
return key in this.map; |
||||||
|
}, |
||||||
|
|
||||||
|
forEach: function dictForEach(callback) { |
||||||
|
for (var key in this.map) { |
||||||
|
callback(key, this.map[key]); |
||||||
|
} |
||||||
|
} |
||||||
|
}; |
||||||
|
|
||||||
|
return constructor; |
||||||
|
})(); |
||||||
|
|
||||||
|
var Ref = (function refRef() { |
||||||
|
function constructor(num, gen) { |
||||||
|
this.num = num; |
||||||
|
this.gen = gen; |
||||||
|
} |
||||||
|
|
||||||
|
constructor.prototype = { |
||||||
|
}; |
||||||
|
|
||||||
|
return constructor; |
||||||
|
})(); |
||||||
|
|
||||||
|
// The reference is identified by number and generation,
|
||||||
|
// this structure stores only one instance of the reference.
|
||||||
|
var RefSet = (function refSet() { |
||||||
|
function constructor() { |
||||||
|
this.dict = {}; |
||||||
|
} |
||||||
|
|
||||||
|
constructor.prototype = { |
||||||
|
has: function refSetHas(ref) { |
||||||
|
return !!this.dict['R' + ref.num + '.' + ref.gen]; |
||||||
|
}, |
||||||
|
|
||||||
|
put: function refSetPut(ref) { |
||||||
|
this.dict['R' + ref.num + '.' + ref.gen] = ref; |
||||||
|
} |
||||||
|
}; |
||||||
|
|
||||||
|
return constructor; |
||||||
|
})(); |
||||||
|
|
||||||
|
var Catalog = (function catalogCatalog() { |
||||||
|
function constructor(xref) { |
||||||
|
this.xref = xref; |
||||||
|
var obj = xref.getCatalogObj(); |
||||||
|
assertWellFormed(isDict(obj), 'catalog object is not a dictionary'); |
||||||
|
this.catDict = obj; |
||||||
|
} |
||||||
|
|
||||||
|
constructor.prototype = { |
||||||
|
get toplevelPagesDict() { |
||||||
|
var pagesObj = this.catDict.get('Pages'); |
||||||
|
assertWellFormed(isRef(pagesObj), 'invalid top-level pages reference'); |
||||||
|
var xrefObj = this.xref.fetch(pagesObj); |
||||||
|
assertWellFormed(isDict(xrefObj), 'invalid top-level pages dictionary'); |
||||||
|
// shadow the prototype getter
|
||||||
|
return shadow(this, 'toplevelPagesDict', xrefObj); |
||||||
|
}, |
||||||
|
get documentOutline() { |
||||||
|
var obj = this.catDict.get('Outlines'); |
||||||
|
var xref = this.xref; |
||||||
|
var root = { items: [] }; |
||||||
|
if (isRef(obj)) { |
||||||
|
obj = xref.fetch(obj).get('First'); |
||||||
|
var processed = new RefSet(); |
||||||
|
if (isRef(obj)) { |
||||||
|
var queue = [{obj: obj, parent: root}]; |
||||||
|
// to avoid recursion keeping track of the items
|
||||||
|
// in the processed dictionary
|
||||||
|
processed.put(obj); |
||||||
|
while (queue.length > 0) { |
||||||
|
var i = queue.shift(); |
||||||
|
var outlineDict = xref.fetch(i.obj); |
||||||
|
if (!outlineDict.has('Title')) |
||||||
|
error('Invalid outline item'); |
||||||
|
var dest = outlineDict.get('A'); |
||||||
|
if (dest) |
||||||
|
dest = xref.fetchIfRef(dest).get('D'); |
||||||
|
else if (outlineDict.has('Dest')) { |
||||||
|
dest = outlineDict.get('Dest'); |
||||||
|
if (isName(dest)) |
||||||
|
dest = dest.name; |
||||||
|
} |
||||||
|
var title = xref.fetchIfRef(outlineDict.get('Title')); |
||||||
|
var outlineItem = { |
||||||
|
dest: dest, |
||||||
|
title: stringToPDFString(title), |
||||||
|
color: outlineDict.get('C') || [0, 0, 0], |
||||||
|
count: outlineDict.get('Count'), |
||||||
|
bold: !!(outlineDict.get('F') & 2), |
||||||
|
italic: !!(outlineDict.get('F') & 1), |
||||||
|
items: [] |
||||||
|
}; |
||||||
|
i.parent.items.push(outlineItem); |
||||||
|
obj = outlineDict.get('First'); |
||||||
|
if (isRef(obj) && !processed.has(obj)) { |
||||||
|
queue.push({obj: obj, parent: outlineItem}); |
||||||
|
processed.put(obj); |
||||||
|
} |
||||||
|
obj = outlineDict.get('Next'); |
||||||
|
if (isRef(obj) && !processed.has(obj)) { |
||||||
|
queue.push({obj: obj, parent: i.parent}); |
||||||
|
processed.put(obj); |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
obj = root.items.length > 0 ? root.items : null; |
||||||
|
return shadow(this, 'documentOutline', obj); |
||||||
|
}, |
||||||
|
get numPages() { |
||||||
|
var obj = this.toplevelPagesDict.get('Count'); |
||||||
|
assertWellFormed( |
||||||
|
isInt(obj), |
||||||
|
'page count in top level pages object is not an integer' |
||||||
|
); |
||||||
|
// shadow the prototype getter
|
||||||
|
return shadow(this, 'num', obj); |
||||||
|
}, |
||||||
|
traverseKids: function catalogTraverseKids(pagesDict) { |
||||||
|
var pageCache = this.pageCache; |
||||||
|
var kids = pagesDict.get('Kids'); |
||||||
|
assertWellFormed(isArray(kids), |
||||||
|
'page dictionary kids object is not an array'); |
||||||
|
for (var i = 0; i < kids.length; ++i) { |
||||||
|
var kid = kids[i]; |
||||||
|
assertWellFormed(isRef(kid), |
||||||
|
'page dictionary kid is not a reference'); |
||||||
|
var obj = this.xref.fetch(kid); |
||||||
|
if (isDict(obj, 'Page') || (isDict(obj) && !obj.has('Kids'))) { |
||||||
|
pageCache.push(new Page(this.xref, pageCache.length, obj, kid)); |
||||||
|
} else { // must be a child page dictionary
|
||||||
|
assertWellFormed( |
||||||
|
isDict(obj), |
||||||
|
'page dictionary kid reference points to wrong type of object' |
||||||
|
); |
||||||
|
this.traverseKids(obj); |
||||||
|
} |
||||||
|
} |
||||||
|
}, |
||||||
|
get destinations() { |
||||||
|
function fetchDestination(xref, ref) { |
||||||
|
var dest = xref.fetchIfRef(ref); |
||||||
|
return isDict(dest) ? dest.get('D') : dest; |
||||||
|
} |
||||||
|
|
||||||
|
var xref = this.xref; |
||||||
|
var dests = {}, nameTreeRef, nameDictionaryRef; |
||||||
|
var obj = this.catDict.get('Names'); |
||||||
|
if (obj) |
||||||
|
nameTreeRef = xref.fetchIfRef(obj).get('Dests'); |
||||||
|
else if (this.catDict.has('Dests')) |
||||||
|
nameDictionaryRef = this.catDict.get('Dests'); |
||||||
|
|
||||||
|
if (nameDictionaryRef) { |
||||||
|
// reading simple destination dictionary
|
||||||
|
obj = xref.fetchIfRef(nameDictionaryRef); |
||||||
|
obj.forEach(function catalogForEach(key, value) { |
||||||
|
if (!value) return; |
||||||
|
dests[key] = fetchDestination(xref, value); |
||||||
|
}); |
||||||
|
} |
||||||
|
if (nameTreeRef) { |
||||||
|
// reading name tree
|
||||||
|
var processed = new RefSet(); |
||||||
|
processed.put(nameTreeRef); |
||||||
|
var queue = [nameTreeRef]; |
||||||
|
while (queue.length > 0) { |
||||||
|
var i, n; |
||||||
|
obj = xref.fetch(queue.shift()); |
||||||
|
if (obj.has('Kids')) { |
||||||
|
var kids = obj.get('Kids'); |
||||||
|
for (i = 0, n = kids.length; i < n; i++) { |
||||||
|
var kid = kids[i]; |
||||||
|
if (processed.has(kid)) |
||||||
|
error('invalid destinations'); |
||||||
|
queue.push(kid); |
||||||
|
processed.put(kid); |
||||||
|
} |
||||||
|
continue; |
||||||
|
} |
||||||
|
var names = obj.get('Names'); |
||||||
|
for (i = 0, n = names.length; i < n; i += 2) { |
||||||
|
dests[names[i]] = fetchDestination(xref, names[i + 1]); |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
return shadow(this, 'destinations', dests); |
||||||
|
}, |
||||||
|
getPage: function catalogGetPage(n) { |
||||||
|
var pageCache = this.pageCache; |
||||||
|
if (!pageCache) { |
||||||
|
pageCache = this.pageCache = []; |
||||||
|
this.traverseKids(this.toplevelPagesDict); |
||||||
|
} |
||||||
|
return this.pageCache[n - 1]; |
||||||
|
} |
||||||
|
}; |
||||||
|
|
||||||
|
return constructor; |
||||||
|
})(); |
||||||
|
|
||||||
|
var XRef = (function xRefXRef() { |
||||||
|
function constructor(stream, startXRef, mainXRefEntriesOffset) { |
||||||
|
this.stream = stream; |
||||||
|
this.entries = []; |
||||||
|
this.xrefstms = {}; |
||||||
|
var trailerDict = this.readXRef(startXRef); |
||||||
|
|
||||||
|
// prepare the XRef cache
|
||||||
|
this.cache = []; |
||||||
|
|
||||||
|
var encrypt = trailerDict.get('Encrypt'); |
||||||
|
if (encrypt) { |
||||||
|
var fileId = trailerDict.get('ID'); |
||||||
|
this.encrypt = new CipherTransformFactory(this.fetch(encrypt), |
||||||
|
fileId[0] /*, password */); |
||||||
|
} |
||||||
|
|
||||||
|
// get the root dictionary (catalog) object
|
||||||
|
if (!isRef(this.root = trailerDict.get('Root'))) |
||||||
|
error('Invalid root reference'); |
||||||
|
} |
||||||
|
|
||||||
|
constructor.prototype = { |
||||||
|
readXRefTable: function readXRefTable(parser) { |
||||||
|
var obj; |
||||||
|
while (true) { |
||||||
|
if (isCmd(obj = parser.getObj(), 'trailer')) |
||||||
|
break; |
||||||
|
if (!isInt(obj)) |
||||||
|
error('Invalid XRef table'); |
||||||
|
var first = obj; |
||||||
|
if (!isInt(obj = parser.getObj())) |
||||||
|
error('Invalid XRef table'); |
||||||
|
var n = obj; |
||||||
|
if (first < 0 || n < 0 || (first + n) != ((first + n) | 0)) |
||||||
|
error('Invalid XRef table: ' + first + ', ' + n); |
||||||
|
for (var i = first; i < first + n; ++i) { |
||||||
|
var entry = {}; |
||||||
|
if (!isInt(obj = parser.getObj())) |
||||||
|
error('Invalid XRef table: ' + first + ', ' + n); |
||||||
|
entry.offset = obj; |
||||||
|
if (!isInt(obj = parser.getObj())) |
||||||
|
error('Invalid XRef table: ' + first + ', ' + n); |
||||||
|
entry.gen = obj; |
||||||
|
obj = parser.getObj(); |
||||||
|
if (isCmd(obj, 'n')) { |
||||||
|
entry.uncompressed = true; |
||||||
|
} else if (isCmd(obj, 'f')) { |
||||||
|
entry.free = true; |
||||||
|
} else { |
||||||
|
error('Invalid XRef table: ' + first + ', ' + n); |
||||||
|
} |
||||||
|
if (!this.entries[i]) { |
||||||
|
// In some buggy PDF files the xref table claims to start at 1
|
||||||
|
// instead of 0.
|
||||||
|
if (i == 1 && first == 1 && |
||||||
|
entry.offset == 0 && entry.gen == 65535 && entry.free) { |
||||||
|
i = first = 0; |
||||||
|
} |
||||||
|
this.entries[i] = entry; |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
// read the trailer dictionary
|
||||||
|
var dict; |
||||||
|
if (!isDict(dict = parser.getObj())) |
||||||
|
error('Invalid XRef table'); |
||||||
|
|
||||||
|
// get the 'Prev' pointer
|
||||||
|
var prev; |
||||||
|
obj = dict.get('Prev'); |
||||||
|
if (isInt(obj)) { |
||||||
|
prev = obj; |
||||||
|
} else if (isRef(obj)) { |
||||||
|
// certain buggy PDF generators generate "/Prev NNN 0 R" instead
|
||||||
|
// of "/Prev NNN"
|
||||||
|
prev = obj.num; |
||||||
|
} |
||||||
|
if (prev) { |
||||||
|
this.readXRef(prev); |
||||||
|
} |
||||||
|
|
||||||
|
// check for 'XRefStm' key
|
||||||
|
if (isInt(obj = dict.get('XRefStm'))) { |
||||||
|
var pos = obj; |
||||||
|
// ignore previously loaded xref streams (possible infinite recursion)
|
||||||
|
if (!(pos in this.xrefstms)) { |
||||||
|
this.xrefstms[pos] = 1; |
||||||
|
this.readXRef(pos); |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
return dict; |
||||||
|
}, |
||||||
|
readXRefStream: function readXRefStream(stream) { |
||||||
|
var streamParameters = stream.parameters; |
||||||
|
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: ' + first + ', ' + n); |
||||||
|
var typeFieldWidth = byteWidths[0]; |
||||||
|
var offsetFieldWidth = byteWidths[1]; |
||||||
|
var generationFieldWidth = byteWidths[2]; |
||||||
|
if (!isInt(typeFieldWidth) || !isInt(offsetFieldWidth) || |
||||||
|
!isInt(generationFieldWidth)) { |
||||||
|
error('Invalid XRef entry fields length: ' + first + ', ' + n); |
||||||
|
} |
||||||
|
for (i = 0; i < n; ++i) { |
||||||
|
var type = 0, offset = 0, generation = 0; |
||||||
|
for (j = 0; j < typeFieldWidth; ++j) |
||||||
|
type = (type << 8) | stream.getByte(); |
||||||
|
// if type field is absent, its default value = 1
|
||||||
|
if (typeFieldWidth == 0) |
||||||
|
type = 1; |
||||||
|
for (j = 0; j < offsetFieldWidth; ++j) |
||||||
|
offset = (offset << 8) | stream.getByte(); |
||||||
|
for (j = 0; j < generationFieldWidth; ++j) |
||||||
|
generation = (generation << 8) | stream.getByte(); |
||||||
|
var entry = {}; |
||||||
|
entry.offset = offset; |
||||||
|
entry.gen = generation; |
||||||
|
switch (type) { |
||||||
|
case 0: |
||||||
|
entry.free = true; |
||||||
|
break; |
||||||
|
case 1: |
||||||
|
entry.uncompressed = true; |
||||||
|
break; |
||||||
|
case 2: |
||||||
|
break; |
||||||
|
default: |
||||||
|
error('Invalid XRef entry type: ' + type); |
||||||
|
} |
||||||
|
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; |
||||||
|
}, |
||||||
|
indexObjects: function indexObjects() { |
||||||
|
// Simple scan through the PDF content to find objects,
|
||||||
|
// trailers and XRef streams.
|
||||||
|
function readToken(data, offset) { |
||||||
|
var token = '', ch = data[offset]; |
||||||
|
while (ch !== 13 && ch !== 10) { |
||||||
|
if (++offset >= data.length) |
||||||
|
break; |
||||||
|
token += String.fromCharCode(ch); |
||||||
|
ch = data[offset]; |
||||||
|
} |
||||||
|
return token; |
||||||
|
} |
||||||
|
function skipUntil(data, offset, what) { |
||||||
|
var length = what.length, dataLength = data.length; |
||||||
|
var skipped = 0; |
||||||
|
// finding byte sequence
|
||||||
|
while (offset < dataLength) { |
||||||
|
var i = 0; |
||||||
|
while (i < length && data[offset + i] == what[i]) |
||||||
|
++i; |
||||||
|
if (i >= length) |
||||||
|
break; // sequence found
|
||||||
|
|
||||||
|
offset++; |
||||||
|
skipped++; |
||||||
|
} |
||||||
|
return skipped; |
||||||
|
} |
||||||
|
var trailerBytes = new Uint8Array([116, 114, 97, 105, 108, 101, 114]); |
||||||
|
var startxrefBytes = new Uint8Array([115, 116, 97, 114, 116, 120, 114, |
||||||
|
101, 102]); |
||||||
|
var endobjBytes = new Uint8Array([101, 110, 100, 111, 98, 106]); |
||||||
|
var xrefBytes = new Uint8Array([47, 88, 82, 101, 102]); |
||||||
|
|
||||||
|
var stream = this.stream; |
||||||
|
stream.pos = 0; |
||||||
|
var buffer = stream.getBytes(); |
||||||
|
var position = stream.start, length = buffer.length; |
||||||
|
var trailers = [], xrefStms = []; |
||||||
|
var state = 0; |
||||||
|
var currentToken; |
||||||
|
while (position < length) { |
||||||
|
var ch = buffer[position]; |
||||||
|
if (ch === 32 || ch === 9 || ch === 13 || ch === 10) { |
||||||
|
++position; |
||||||
|
continue; |
||||||
|
} |
||||||
|
if (ch === 37) { // %-comment
|
||||||
|
do { |
||||||
|
++position; |
||||||
|
ch = buffer[position]; |
||||||
|
} while (ch !== 13 && ch !== 10); |
||||||
|
continue; |
||||||
|
} |
||||||
|
var token = readToken(buffer, position); |
||||||
|
var m; |
||||||
|
if (token === 'xref') { |
||||||
|
position += skipUntil(buffer, position, trailerBytes); |
||||||
|
trailers.push(position); |
||||||
|
position += skipUntil(buffer, position, startxrefBytes); |
||||||
|
} else if ((m = /^(\d+)\s+(\d+)\s+obj\b/.exec(token))) { |
||||||
|
this.entries[m[1]] = { |
||||||
|
offset: position, |
||||||
|
gen: m[2] | 0, |
||||||
|
uncompressed: true |
||||||
|
}; |
||||||
|
|
||||||
|
var contentLength = skipUntil(buffer, position, endobjBytes) + 7; |
||||||
|
var content = buffer.subarray(position, position + contentLength); |
||||||
|
|
||||||
|
// checking XRef stream suspect
|
||||||
|
// (it shall have '/XRef' and next char is not a letter)
|
||||||
|
var xrefTagOffset = skipUntil(content, 0, xrefBytes); |
||||||
|
if (xrefTagOffset < contentLength && |
||||||
|
content[xrefTagOffset + 5] < 64) { |
||||||
|
xrefStms.push(position); |
||||||
|
this.xrefstms[position] = 1; // don't read it recursively
|
||||||
|
} |
||||||
|
|
||||||
|
position += contentLength; |
||||||
|
} else |
||||||
|
position += token.length + 1; |
||||||
|
} |
||||||
|
// reading XRef streams
|
||||||
|
for (var i = 0; i < xrefStms.length; ++i) { |
||||||
|
this.readXRef(xrefStms[i]); |
||||||
|
} |
||||||
|
// finding main trailer
|
||||||
|
var dict; |
||||||
|
for (var i = 0; i < trailers.length; ++i) { |
||||||
|
stream.pos = trailers[i]; |
||||||
|
var parser = new Parser(new Lexer(stream), true); |
||||||
|
var obj = parser.getObj(); |
||||||
|
if (!isCmd(obj, 'trailer')) |
||||||
|
continue; |
||||||
|
// read the trailer dictionary
|
||||||
|
if (!isDict(dict = parser.getObj())) |
||||||
|
continue; |
||||||
|
// taking the first one with 'ID'
|
||||||
|
if (dict.has('ID')) |
||||||
|
return dict; |
||||||
|
} |
||||||
|
// no tailer with 'ID', taking last one (if exists)
|
||||||
|
if (dict) |
||||||
|
return dict; |
||||||
|
// nothing helps
|
||||||
|
error('Invalid PDF structure'); |
||||||
|
return null; |
||||||
|
}, |
||||||
|
readXRef: function readXref(startXRef) { |
||||||
|
var stream = this.stream; |
||||||
|
stream.pos = startXRef; |
||||||
|
var parser = new Parser(new Lexer(stream), true); |
||||||
|
var obj = parser.getObj(); |
||||||
|
// parse an old-style xref table
|
||||||
|
if (isCmd(obj, 'xref')) |
||||||
|
return this.readXRefTable(parser); |
||||||
|
// parse an xref stream
|
||||||
|
if (isInt(obj)) { |
||||||
|
if (!isInt(parser.getObj()) || |
||||||
|
!isCmd(parser.getObj(), 'obj') || |
||||||
|
!isStream(obj = parser.getObj())) { |
||||||
|
error('Invalid XRef stream'); |
||||||
|
} |
||||||
|
return this.readXRefStream(obj); |
||||||
|
} |
||||||
|
return this.indexObjects(); |
||||||
|
}, |
||||||
|
getEntry: function xRefGetEntry(i) { |
||||||
|
var e = this.entries[i]; |
||||||
|
if (e.free) |
||||||
|
error('reading an XRef stream not implemented yet'); |
||||||
|
return e; |
||||||
|
}, |
||||||
|
fetchIfRef: function xRefFetchIfRef(obj) { |
||||||
|
if (!isRef(obj)) |
||||||
|
return obj; |
||||||
|
return this.fetch(obj); |
||||||
|
}, |
||||||
|
fetch: function xRefFetch(ref, suppressEncryption) { |
||||||
|
var num = ref.num; |
||||||
|
var e = this.cache[num]; |
||||||
|
if (e) |
||||||
|
return e; |
||||||
|
|
||||||
|
e = this.getEntry(num); |
||||||
|
var gen = ref.gen; |
||||||
|
var stream, parser; |
||||||
|
if (e.uncompressed) { |
||||||
|
if (e.gen != gen) |
||||||
|
throw ('inconsistent generation in XRef'); |
||||||
|
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(); |
||||||
|
if (!isInt(obj1) || obj1 != num || |
||||||
|
!isInt(obj2) || obj2 != gen || |
||||||
|
!isCmd(obj3)) { |
||||||
|
error('bad XRef entry'); |
||||||
|
} |
||||||
|
if (!isCmd(obj3, 'obj')) { |
||||||
|
// some bad pdfs use "obj1234" and really mean 1234
|
||||||
|
if (obj3.cmd.indexOf('obj') == 0) { |
||||||
|
num = parseInt(obj3.cmd.substring(3), 10); |
||||||
|
if (!isNaN(num)) |
||||||
|
return num; |
||||||
|
} |
||||||
|
error('bad XRef entry'); |
||||||
|
} |
||||||
|
if (this.encrypt && !suppressEncryption) { |
||||||
|
try { |
||||||
|
e = parser.getObj(this.encrypt.createCipherTransform(num, gen)); |
||||||
|
} catch (ex) { |
||||||
|
// almost all streams must be encrypted, but sometimes
|
||||||
|
// they are not probably due to some broken generators
|
||||||
|
// re-trying without encryption
|
||||||
|
return this.fetch(ref, true); |
||||||
|
} |
||||||
|
} else { |
||||||
|
e = parser.getObj(); |
||||||
|
} |
||||||
|
// Don't cache streams since they are mutable (except images).
|
||||||
|
if (!isStream(e) || e.getImage) |
||||||
|
this.cache[num] = e; |
||||||
|
return e; |
||||||
|
} |
||||||
|
|
||||||
|
// 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) { |
||||||
|
num = parser.getObj(); |
||||||
|
if (!isInt(num)) { |
||||||
|
error('invalid object number in the ObjStm stream: ' + num); |
||||||
|
} |
||||||
|
nums.push(num); |
||||||
|
var offset = parser.getObj(); |
||||||
|
if (!isInt(offset)) { |
||||||
|
error('invalid object offset in the ObjStm stream: ' + offset); |
||||||
|
} |
||||||
|
} |
||||||
|
// 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 xRefGetCatalogObj() { |
||||||
|
return this.fetch(this.root); |
||||||
|
} |
||||||
|
}; |
||||||
|
|
||||||
|
return constructor; |
||||||
|
})(); |
||||||
|
|
||||||
|
/** |
||||||
|
* A PDF document and page is built of many objects. E.g. there are objects |
||||||
|
* for fonts, images, rendering code and such. These objects might get processed |
||||||
|
* inside of a worker. The `PDFObjects` implements some basic functions to |
||||||
|
* manage these objects. |
||||||
|
*/ |
||||||
|
var PDFObjects = (function() { |
||||||
|
function PDFObjects() { |
||||||
|
this.objs = {}; |
||||||
|
} |
||||||
|
|
||||||
|
PDFObjects.prototype = { |
||||||
|
objs: null, |
||||||
|
|
||||||
|
/** |
||||||
|
* Internal function. |
||||||
|
* Ensures there is an object defined for `objId`. Stores `data` on the |
||||||
|
* object *if* it is created. |
||||||
|
*/ |
||||||
|
ensureObj: function(objId, data) { |
||||||
|
if (this.objs[objId]) |
||||||
|
return this.objs[objId]; |
||||||
|
return this.objs[objId] = new Promise(objId, data); |
||||||
|
}, |
||||||
|
|
||||||
|
/** |
||||||
|
* If called *without* callback, this returns the data of `objId` but the |
||||||
|
* object needs to be resolved. If it isn't, this function throws. |
||||||
|
* |
||||||
|
* If called *with* a callback, the callback is called with the data of the |
||||||
|
* object once the object is resolved. That means, if you call this |
||||||
|
* function and the object is already resolved, the callback gets called |
||||||
|
* right away. |
||||||
|
*/ |
||||||
|
get: function(objId, callback) { |
||||||
|
// If there is a callback, then the get can be async and the object is
|
||||||
|
// not required to be resolved right now
|
||||||
|
if (callback) { |
||||||
|
this.ensureObj(objId).then(callback); |
||||||
|
return; |
||||||
|
} |
||||||
|
|
||||||
|
// If there isn't a callback, the user expects to get the resolved data
|
||||||
|
// directly.
|
||||||
|
var obj = this.objs[objId]; |
||||||
|
|
||||||
|
// If there isn't an object yet or the object isn't resolved, then the
|
||||||
|
// data isn't ready yet!
|
||||||
|
if (!obj || !obj.isResolved) |
||||||
|
throw 'Requesting object that isn\'t resolved yet ' + objId; |
||||||
|
else |
||||||
|
return obj.data; |
||||||
|
}, |
||||||
|
|
||||||
|
/** |
||||||
|
* Resolves the object `objId` with optional `data`. |
||||||
|
*/ |
||||||
|
resolve: function(objId, data) { |
||||||
|
var objs = this.objs; |
||||||
|
|
||||||
|
// In case there is a promise already on this object, just resolve it.
|
||||||
|
if (objs[objId]) { |
||||||
|
objs[objId].resolve(data); |
||||||
|
} else { |
||||||
|
this.ensureObj(objId, data); |
||||||
|
} |
||||||
|
}, |
||||||
|
|
||||||
|
onData: function(objId, callback) { |
||||||
|
this.ensureObj(objId).onData(callback); |
||||||
|
}, |
||||||
|
|
||||||
|
isResolved: function(objId) { |
||||||
|
var objs = this.objs; |
||||||
|
if (!objs[objId]) { |
||||||
|
return false; |
||||||
|
} else { |
||||||
|
return objs[objId].isResolved; |
||||||
|
} |
||||||
|
}, |
||||||
|
|
||||||
|
hasData: function(objId) { |
||||||
|
var objs = this.objs; |
||||||
|
if (!objs[objId]) { |
||||||
|
return false; |
||||||
|
} else { |
||||||
|
return objs[objId].hasData; |
||||||
|
} |
||||||
|
}, |
||||||
|
|
||||||
|
/** |
||||||
|
* Sets the data of an object but *doesn't* resolve it. |
||||||
|
*/ |
||||||
|
setData: function(objId, data) { |
||||||
|
// Watchout! If you call `this.ensureObj(objId, data)` you're going to
|
||||||
|
// create a *resolved* promise which shouldn't be the case!
|
||||||
|
this.ensureObj(objId).data = data; |
||||||
|
} |
||||||
|
}; |
||||||
|
return PDFObjects; |
||||||
|
})(); |
@ -0,0 +1,630 @@ |
|||||||
|
var EOF = {}; |
||||||
|
|
||||||
|
function isEOF(v) { |
||||||
|
return v == EOF; |
||||||
|
} |
||||||
|
|
||||||
|
var Parser = (function parserParser() { |
||||||
|
function constructor(lexer, allowStreams, xref) { |
||||||
|
this.lexer = lexer; |
||||||
|
this.allowStreams = allowStreams; |
||||||
|
this.xref = xref; |
||||||
|
this.inlineImg = 0; |
||||||
|
this.refill(); |
||||||
|
} |
||||||
|
|
||||||
|
constructor.prototype = { |
||||||
|
refill: function parserRefill() { |
||||||
|
this.buf1 = this.lexer.getObj(); |
||||||
|
this.buf2 = this.lexer.getObj(); |
||||||
|
}, |
||||||
|
shift: function parserShift() { |
||||||
|
if (isCmd(this.buf2, 'ID')) { |
||||||
|
this.buf1 = this.buf2; |
||||||
|
this.buf2 = null; |
||||||
|
// skip byte after ID
|
||||||
|
this.lexer.skip(); |
||||||
|
} else { |
||||||
|
this.buf1 = this.buf2; |
||||||
|
this.buf2 = this.lexer.getObj(); |
||||||
|
} |
||||||
|
}, |
||||||
|
getObj: function parserGetObj(cipherTransform) { |
||||||
|
if (isCmd(this.buf1, 'BI')) { // inline image
|
||||||
|
this.shift(); |
||||||
|
return this.makeInlineImage(cipherTransform); |
||||||
|
} |
||||||
|
if (isCmd(this.buf1, '[')) { // array
|
||||||
|
this.shift(); |
||||||
|
var array = []; |
||||||
|
while (!isCmd(this.buf1, ']') && !isEOF(this.buf1)) |
||||||
|
array.push(this.getObj()); |
||||||
|
if (isEOF(this.buf1)) |
||||||
|
error('End of file inside array'); |
||||||
|
this.shift(); |
||||||
|
return array; |
||||||
|
} |
||||||
|
if (isCmd(this.buf1, '<<')) { // dictionary or stream
|
||||||
|
this.shift(); |
||||||
|
var dict = new Dict(); |
||||||
|
while (!isCmd(this.buf1, '>>') && !isEOF(this.buf1)) { |
||||||
|
if (!isName(this.buf1)) { |
||||||
|
error('Dictionary key must be a name object'); |
||||||
|
} else { |
||||||
|
var key = this.buf1.name; |
||||||
|
this.shift(); |
||||||
|
if (isEOF(this.buf1)) |
||||||
|
break; |
||||||
|
dict.set(key, this.getObj(cipherTransform)); |
||||||
|
} |
||||||
|
} |
||||||
|
if (isEOF(this.buf1)) |
||||||
|
error('End of file inside dictionary'); |
||||||
|
|
||||||
|
// stream objects are not allowed inside content streams or
|
||||||
|
// object streams
|
||||||
|
if (isCmd(this.buf2, 'stream')) { |
||||||
|
return this.allowStreams ? |
||||||
|
this.makeStream(dict, cipherTransform) : dict; |
||||||
|
} |
||||||
|
this.shift(); |
||||||
|
return dict; |
||||||
|
} |
||||||
|
if (isInt(this.buf1)) { // indirect reference or integer
|
||||||
|
var num = this.buf1; |
||||||
|
this.shift(); |
||||||
|
if (isInt(this.buf1) && isCmd(this.buf2, 'R')) { |
||||||
|
var ref = new Ref(num, this.buf1); |
||||||
|
this.shift(); |
||||||
|
this.shift(); |
||||||
|
return ref; |
||||||
|
} |
||||||
|
return num; |
||||||
|
} |
||||||
|
if (isString(this.buf1)) { // string
|
||||||
|
var str = this.buf1; |
||||||
|
this.shift(); |
||||||
|
if (cipherTransform) |
||||||
|
str = cipherTransform.decryptString(str); |
||||||
|
return str; |
||||||
|
} |
||||||
|
|
||||||
|
// simple object
|
||||||
|
var obj = this.buf1; |
||||||
|
this.shift(); |
||||||
|
return obj; |
||||||
|
}, |
||||||
|
makeInlineImage: function parserMakeInlineImage(cipherTransform) { |
||||||
|
var lexer = this.lexer; |
||||||
|
var stream = lexer.stream; |
||||||
|
|
||||||
|
// parse dictionary
|
||||||
|
var dict = new Dict(); |
||||||
|
while (!isCmd(this.buf1, 'ID') && !isEOF(this.buf1)) { |
||||||
|
if (!isName(this.buf1)) { |
||||||
|
error('Dictionary key must be a name object'); |
||||||
|
} else { |
||||||
|
var key = this.buf1.name; |
||||||
|
this.shift(); |
||||||
|
if (isEOF(this.buf1)) |
||||||
|
break; |
||||||
|
dict.set(key, this.getObj(cipherTransform)); |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
// parse image stream
|
||||||
|
var startPos = stream.pos; |
||||||
|
|
||||||
|
// searching for the /\sEI\s/
|
||||||
|
var state = 0, ch; |
||||||
|
while (state != 4 && (ch = stream.getByte()) != null) { |
||||||
|
switch (ch) { |
||||||
|
case 0x20: |
||||||
|
case 0x0D: |
||||||
|
case 0x0A: |
||||||
|
state = state === 3 ? 4 : 1; |
||||||
|
break; |
||||||
|
case 0x45: |
||||||
|
state = state === 1 ? 2 : 0; |
||||||
|
break; |
||||||
|
case 0x49: |
||||||
|
state = state === 2 ? 3 : 0; |
||||||
|
break; |
||||||
|
default: |
||||||
|
state = 0; |
||||||
|
break; |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
// TODO improve the small images performance to remove the limit
|
||||||
|
var inlineImgLimit = 500; |
||||||
|
if (++this.inlineImg >= inlineImgLimit) { |
||||||
|
if (this.inlineImg === inlineImgLimit) |
||||||
|
warn('Too many inline images'); |
||||||
|
this.shift(); |
||||||
|
return null; |
||||||
|
} |
||||||
|
|
||||||
|
var length = (stream.pos - 4) - startPos; |
||||||
|
var imageStream = stream.makeSubStream(startPos, length, dict); |
||||||
|
if (cipherTransform) |
||||||
|
imageStream = cipherTransform.createStream(imageStream); |
||||||
|
imageStream = this.filter(imageStream, dict, length); |
||||||
|
imageStream.parameters = dict; |
||||||
|
|
||||||
|
this.buf2 = new Cmd('EI'); |
||||||
|
this.shift(); |
||||||
|
|
||||||
|
return imageStream; |
||||||
|
}, |
||||||
|
makeStream: function parserMakeStream(dict, cipherTransform) { |
||||||
|
var lexer = this.lexer; |
||||||
|
var stream = lexer.stream; |
||||||
|
|
||||||
|
// get stream start position
|
||||||
|
lexer.skipToNextLine(); |
||||||
|
var pos = stream.pos; |
||||||
|
|
||||||
|
// get length
|
||||||
|
var length = dict.get('Length'); |
||||||
|
var xref = this.xref; |
||||||
|
if (xref) |
||||||
|
length = xref.fetchIfRef(length); |
||||||
|
if (!isInt(length)) { |
||||||
|
error('Bad ' + length + ' attribute in stream'); |
||||||
|
length = 0; |
||||||
|
} |
||||||
|
|
||||||
|
// skip over the stream data
|
||||||
|
stream.pos = pos + length; |
||||||
|
this.shift(); // '>>'
|
||||||
|
this.shift(); // 'stream'
|
||||||
|
if (!isCmd(this.buf1, 'endstream')) |
||||||
|
error('Missing endstream'); |
||||||
|
this.shift(); |
||||||
|
|
||||||
|
stream = stream.makeSubStream(pos, length, dict); |
||||||
|
if (cipherTransform) |
||||||
|
stream = cipherTransform.createStream(stream); |
||||||
|
stream = this.filter(stream, dict, length); |
||||||
|
stream.parameters = dict; |
||||||
|
return stream; |
||||||
|
}, |
||||||
|
filter: function parserFilter(stream, dict, length) { |
||||||
|
var filter = dict.get('Filter', 'F'); |
||||||
|
var params = dict.get('DecodeParms', 'DP'); |
||||||
|
if (isName(filter)) |
||||||
|
return this.makeFilter(stream, filter.name, length, params); |
||||||
|
if (isArray(filter)) { |
||||||
|
var filterArray = filter; |
||||||
|
var paramsArray = params; |
||||||
|
for (var i = 0, ii = filterArray.length; i < ii; ++i) { |
||||||
|
filter = filterArray[i]; |
||||||
|
if (!isName(filter)) |
||||||
|
error('Bad filter name: ' + filter); |
||||||
|
else { |
||||||
|
params = null; |
||||||
|
if (isArray(paramsArray) && (i in paramsArray)) |
||||||
|
params = paramsArray[i]; |
||||||
|
stream = this.makeFilter(stream, filter.name, length, params); |
||||||
|
// after the first stream the length variable is invalid
|
||||||
|
length = null; |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
return stream; |
||||||
|
}, |
||||||
|
makeFilter: function parserMakeFilter(stream, name, length, params) { |
||||||
|
if (name == 'FlateDecode' || name == 'Fl') { |
||||||
|
if (params) { |
||||||
|
return new PredictorStream(new FlateStream(stream), params); |
||||||
|
} |
||||||
|
return new FlateStream(stream); |
||||||
|
} else if (name == 'LZWDecode' || name == 'LZW') { |
||||||
|
var earlyChange = 1; |
||||||
|
if (params) { |
||||||
|
if (params.has('EarlyChange')) |
||||||
|
earlyChange = params.get('EarlyChange'); |
||||||
|
return new PredictorStream( |
||||||
|
new LZWStream(stream, earlyChange), params); |
||||||
|
} |
||||||
|
return new LZWStream(stream, earlyChange); |
||||||
|
} else if (name == 'DCTDecode' || name == 'DCT') { |
||||||
|
var bytes = stream.getBytes(length); |
||||||
|
return new JpegStream(bytes, stream.dict); |
||||||
|
} else if (name == 'ASCII85Decode' || name == 'A85') { |
||||||
|
return new Ascii85Stream(stream); |
||||||
|
} else if (name == 'ASCIIHexDecode' || name == 'AHx') { |
||||||
|
return new AsciiHexStream(stream); |
||||||
|
} else if (name == 'CCITTFaxDecode' || name == 'CCF') { |
||||||
|
return new CCITTFaxStream(stream, params); |
||||||
|
} else { |
||||||
|
error('filter "' + name + '" not supported yet'); |
||||||
|
} |
||||||
|
return stream; |
||||||
|
} |
||||||
|
}; |
||||||
|
|
||||||
|
return constructor; |
||||||
|
})(); |
||||||
|
|
||||||
|
var Lexer = (function lexer() { |
||||||
|
function constructor(stream) { |
||||||
|
this.stream = stream; |
||||||
|
} |
||||||
|
|
||||||
|
constructor.isSpace = function lexerIsSpace(ch) { |
||||||
|
return ch == ' ' || ch == '\t' || ch == '\x0d' || ch == '\x0a'; |
||||||
|
}; |
||||||
|
|
||||||
|
// A '1' in this array means the character is white space. A '1' or
|
||||||
|
// '2' means the character ends a name or command.
|
||||||
|
var specialChars = [ |
||||||
|
1, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 1, 1, 0, 0, // 0x
|
||||||
|
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // 1x
|
||||||
|
1, 0, 0, 0, 0, 2, 0, 0, 2, 2, 0, 0, 0, 0, 0, 2, // 2x
|
||||||
|
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 2, 0, 2, 0, // 3x
|
||||||
|
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // 4x
|
||||||
|
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 2, 0, 2, 0, 0, // 5x
|
||||||
|
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // 6x
|
||||||
|
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 2, 0, 2, 0, 0, // 7x
|
||||||
|
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // 8x
|
||||||
|
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // 9x
|
||||||
|
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // ax
|
||||||
|
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // bx
|
||||||
|
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // cx
|
||||||
|
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // dx
|
||||||
|
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // ex
|
||||||
|
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 // fx
|
||||||
|
]; |
||||||
|
|
||||||
|
function toHexDigit(ch) { |
||||||
|
if (ch >= '0' && ch <= '9') |
||||||
|
return ch.charCodeAt(0) - 48; |
||||||
|
ch = ch.toUpperCase(); |
||||||
|
if (ch >= 'A' && ch <= 'F') |
||||||
|
return ch.charCodeAt(0) - 55; |
||||||
|
return -1; |
||||||
|
} |
||||||
|
|
||||||
|
constructor.prototype = { |
||||||
|
getNumber: function lexerGetNumber(ch) { |
||||||
|
var floating = false; |
||||||
|
var str = ch; |
||||||
|
var stream = this.stream; |
||||||
|
for (;;) { |
||||||
|
ch = stream.lookChar(); |
||||||
|
if (ch == '.' && !floating) { |
||||||
|
str += ch; |
||||||
|
floating = true; |
||||||
|
} else if (ch == '-') { |
||||||
|
// ignore minus signs in the middle of numbers to match
|
||||||
|
// Adobe's behavior
|
||||||
|
warn('Badly formated number'); |
||||||
|
} else if (ch >= '0' && ch <= '9') { |
||||||
|
str += ch; |
||||||
|
} else if (ch == 'e' || ch == 'E') { |
||||||
|
floating = true; |
||||||
|
} else { |
||||||
|
// the last character doesn't belong to us
|
||||||
|
break; |
||||||
|
} |
||||||
|
stream.skip(); |
||||||
|
} |
||||||
|
var value = parseFloat(str); |
||||||
|
if (isNaN(value)) |
||||||
|
error('Invalid floating point number: ' + value); |
||||||
|
return value; |
||||||
|
}, |
||||||
|
getString: function lexerGetString() { |
||||||
|
var numParen = 1; |
||||||
|
var done = false; |
||||||
|
var str = ''; |
||||||
|
var stream = this.stream; |
||||||
|
var ch; |
||||||
|
do { |
||||||
|
ch = stream.getChar(); |
||||||
|
switch (ch) { |
||||||
|
case undefined: |
||||||
|
warn('Unterminated string'); |
||||||
|
done = true; |
||||||
|
break; |
||||||
|
case '(': |
||||||
|
++numParen; |
||||||
|
str += ch; |
||||||
|
break; |
||||||
|
case ')': |
||||||
|
if (--numParen == 0) { |
||||||
|
done = true; |
||||||
|
} else { |
||||||
|
str += ch; |
||||||
|
} |
||||||
|
break; |
||||||
|
case '\\': |
||||||
|
ch = stream.getChar(); |
||||||
|
switch (ch) { |
||||||
|
case undefined: |
||||||
|
warn('Unterminated string'); |
||||||
|
done = true; |
||||||
|
break; |
||||||
|
case 'n': |
||||||
|
str += '\n'; |
||||||
|
break; |
||||||
|
case 'r': |
||||||
|
str += '\r'; |
||||||
|
break; |
||||||
|
case 't': |
||||||
|
str += '\t'; |
||||||
|
break; |
||||||
|
case 'b': |
||||||
|
str += '\b'; |
||||||
|
break; |
||||||
|
case 'f': |
||||||
|
str += '\f'; |
||||||
|
break; |
||||||
|
case '\\': |
||||||
|
case '(': |
||||||
|
case ')': |
||||||
|
str += ch; |
||||||
|
break; |
||||||
|
case '0': case '1': case '2': case '3': |
||||||
|
case '4': case '5': case '6': case '7': |
||||||
|
var x = ch - '0'; |
||||||
|
ch = stream.lookChar(); |
||||||
|
if (ch >= '0' && ch <= '7') { |
||||||
|
stream.skip(); |
||||||
|
x = (x << 3) + (ch - '0'); |
||||||
|
ch = stream.lookChar(); |
||||||
|
if (ch >= '0' && ch <= '7') { |
||||||
|
stream.skip(); |
||||||
|
x = (x << 3) + (ch - '0'); |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
str += String.fromCharCode(x); |
||||||
|
break; |
||||||
|
case '\r': |
||||||
|
ch = stream.lookChar(); |
||||||
|
if (ch == '\n') |
||||||
|
stream.skip(); |
||||||
|
break; |
||||||
|
case '\n': |
||||||
|
break; |
||||||
|
default: |
||||||
|
str += ch; |
||||||
|
} |
||||||
|
break; |
||||||
|
default: |
||||||
|
str += ch; |
||||||
|
} |
||||||
|
} while (!done); |
||||||
|
return str; |
||||||
|
}, |
||||||
|
getName: function lexerGetName(ch) { |
||||||
|
var str = ''; |
||||||
|
var stream = this.stream; |
||||||
|
while (!!(ch = stream.lookChar()) && !specialChars[ch.charCodeAt(0)]) { |
||||||
|
stream.skip(); |
||||||
|
if (ch == '#') { |
||||||
|
ch = stream.lookChar(); |
||||||
|
var x = toHexDigit(ch); |
||||||
|
if (x != -1) { |
||||||
|
stream.skip(); |
||||||
|
var x2 = toHexDigit(stream.getChar()); |
||||||
|
if (x2 == -1) |
||||||
|
error('Illegal digit in hex char in name: ' + x2); |
||||||
|
str += String.fromCharCode((x << 4) | x2); |
||||||
|
} else { |
||||||
|
str += '#'; |
||||||
|
str += ch; |
||||||
|
} |
||||||
|
} else { |
||||||
|
str += ch; |
||||||
|
} |
||||||
|
} |
||||||
|
if (str.length > 128) |
||||||
|
error('Warning: name token is longer than allowed by the spec: ' + |
||||||
|
str.length); |
||||||
|
return new Name(str); |
||||||
|
}, |
||||||
|
getHexString: function lexerGetHexString(ch) { |
||||||
|
var str = ''; |
||||||
|
var stream = this.stream; |
||||||
|
for (;;) { |
||||||
|
ch = stream.getChar(); |
||||||
|
if (ch == '>') { |
||||||
|
break; |
||||||
|
} |
||||||
|
if (!ch) { |
||||||
|
warn('Unterminated hex string'); |
||||||
|
break; |
||||||
|
} |
||||||
|
if (specialChars[ch.charCodeAt(0)] != 1) { |
||||||
|
var x, x2; |
||||||
|
if ((x = toHexDigit(ch)) == -1) |
||||||
|
error('Illegal character in hex string: ' + ch); |
||||||
|
|
||||||
|
ch = stream.getChar(); |
||||||
|
while (specialChars[ch.charCodeAt(0)] == 1) |
||||||
|
ch = stream.getChar(); |
||||||
|
|
||||||
|
if ((x2 = toHexDigit(ch)) == -1) |
||||||
|
error('Illegal character in hex string: ' + ch); |
||||||
|
|
||||||
|
str += String.fromCharCode((x << 4) | x2); |
||||||
|
} |
||||||
|
} |
||||||
|
return str; |
||||||
|
}, |
||||||
|
getObj: function lexerGetObj() { |
||||||
|
// skip whitespace and comments
|
||||||
|
var comment = false; |
||||||
|
var stream = this.stream; |
||||||
|
var ch; |
||||||
|
while (true) { |
||||||
|
if (!(ch = stream.getChar())) |
||||||
|
return EOF; |
||||||
|
if (comment) { |
||||||
|
if (ch == '\r' || ch == '\n') |
||||||
|
comment = false; |
||||||
|
} else if (ch == '%') { |
||||||
|
comment = true; |
||||||
|
} else if (specialChars[ch.charCodeAt(0)] != 1) { |
||||||
|
break; |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
// start reading token
|
||||||
|
switch (ch) { |
||||||
|
case '0': case '1': case '2': case '3': case '4': |
||||||
|
case '5': case '6': case '7': case '8': case '9': |
||||||
|
case '+': case '-': case '.': |
||||||
|
return this.getNumber(ch); |
||||||
|
case '(': |
||||||
|
return this.getString(); |
||||||
|
case '/': |
||||||
|
return this.getName(ch); |
||||||
|
// array punctuation
|
||||||
|
case '[': |
||||||
|
case ']': |
||||||
|
return new Cmd(ch); |
||||||
|
// hex string or dict punctuation
|
||||||
|
case '<': |
||||||
|
ch = stream.lookChar(); |
||||||
|
if (ch == '<') { |
||||||
|
// dict punctuation
|
||||||
|
stream.skip(); |
||||||
|
return new Cmd('<<'); |
||||||
|
} |
||||||
|
return this.getHexString(ch); |
||||||
|
// dict punctuation
|
||||||
|
case '>': |
||||||
|
ch = stream.lookChar(); |
||||||
|
if (ch == '>') { |
||||||
|
stream.skip(); |
||||||
|
return new Cmd('>>'); |
||||||
|
} |
||||||
|
case '{': |
||||||
|
case '}': |
||||||
|
return new Cmd(ch); |
||||||
|
// fall through
|
||||||
|
case ')': |
||||||
|
error('Illegal character: ' + ch); |
||||||
|
return Error; |
||||||
|
} |
||||||
|
|
||||||
|
// command
|
||||||
|
var str = ch; |
||||||
|
while (!!(ch = stream.lookChar()) && !specialChars[ch.charCodeAt(0)]) { |
||||||
|
stream.skip(); |
||||||
|
if (str.length == 128) { |
||||||
|
error('Command token too long: ' + str.length); |
||||||
|
break; |
||||||
|
} |
||||||
|
str += ch; |
||||||
|
} |
||||||
|
if (str == 'true') |
||||||
|
return true; |
||||||
|
if (str == 'false') |
||||||
|
return false; |
||||||
|
if (str == 'null') |
||||||
|
return null; |
||||||
|
return new Cmd(str); |
||||||
|
}, |
||||||
|
skipToNextLine: function lexerSkipToNextLine() { |
||||||
|
var stream = this.stream; |
||||||
|
while (true) { |
||||||
|
var ch = stream.getChar(); |
||||||
|
if (!ch || ch == '\n') |
||||||
|
return; |
||||||
|
if (ch == '\r') { |
||||||
|
if ((ch = stream.lookChar()) == '\n') |
||||||
|
stream.skip(); |
||||||
|
return; |
||||||
|
} |
||||||
|
} |
||||||
|
}, |
||||||
|
skip: function lexerSkip() { |
||||||
|
this.stream.skip(); |
||||||
|
} |
||||||
|
}; |
||||||
|
|
||||||
|
return constructor; |
||||||
|
})(); |
||||||
|
|
||||||
|
var Linearization = (function linearizationLinearization() { |
||||||
|
function constructor(stream) { |
||||||
|
this.parser = new Parser(new Lexer(stream), false); |
||||||
|
var obj1 = this.parser.getObj(); |
||||||
|
var obj2 = this.parser.getObj(); |
||||||
|
var obj3 = this.parser.getObj(); |
||||||
|
this.linDict = this.parser.getObj(); |
||||||
|
if (isInt(obj1) && isInt(obj2) && isCmd(obj3, 'obj') && |
||||||
|
isDict(this.linDict)) { |
||||||
|
var obj = this.linDict.get('Linearized'); |
||||||
|
if (!(isNum(obj) && obj > 0)) |
||||||
|
this.linDict = null; |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
constructor.prototype = { |
||||||
|
getInt: function linearizationGetInt(name) { |
||||||
|
var linDict = this.linDict; |
||||||
|
var obj; |
||||||
|
if (isDict(linDict) && |
||||||
|
isInt(obj = linDict.get(name)) && |
||||||
|
obj > 0) { |
||||||
|
return obj; |
||||||
|
} |
||||||
|
error('"' + name + '" field in linearization table is invalid'); |
||||||
|
return 0; |
||||||
|
}, |
||||||
|
getHint: function linearizationGetHint(index) { |
||||||
|
var linDict = this.linDict; |
||||||
|
var obj1, obj2; |
||||||
|
if (isDict(linDict) && |
||||||
|
isArray(obj1 = linDict.get('H')) && |
||||||
|
obj1.length >= 2 && |
||||||
|
isInt(obj2 = obj1[index]) && |
||||||
|
obj2 > 0) { |
||||||
|
return obj2; |
||||||
|
} |
||||||
|
error('Hints table in linearization table is invalid: ' + index); |
||||||
|
return 0; |
||||||
|
}, |
||||||
|
get length() { |
||||||
|
if (!isDict(this.linDict)) |
||||||
|
return 0; |
||||||
|
return this.getInt('L'); |
||||||
|
}, |
||||||
|
get hintsOffset() { |
||||||
|
return this.getHint(0); |
||||||
|
}, |
||||||
|
get hintsLength() { |
||||||
|
return this.getHint(1); |
||||||
|
}, |
||||||
|
get hintsOffset2() { |
||||||
|
return this.getHint(2); |
||||||
|
}, |
||||||
|
get hintsLenth2() { |
||||||
|
return this.getHint(3); |
||||||
|
}, |
||||||
|
get objectNumberFirst() { |
||||||
|
return this.getInt('O'); |
||||||
|
}, |
||||||
|
get endFirst() { |
||||||
|
return this.getInt('E'); |
||||||
|
}, |
||||||
|
get numPages() { |
||||||
|
return this.getInt('N'); |
||||||
|
}, |
||||||
|
get mainXRefEntriesOffset() { |
||||||
|
return this.getInt('T'); |
||||||
|
}, |
||||||
|
get pageFirst() { |
||||||
|
return this.getInt('P'); |
||||||
|
} |
||||||
|
}; |
||||||
|
|
||||||
|
return constructor; |
||||||
|
})(); |
@ -0,0 +1,283 @@ |
|||||||
|
var Pattern = (function patternPattern() { |
||||||
|
// Constructor should define this.getPattern
|
||||||
|
function constructor() { |
||||||
|
error('should not call Pattern constructor'); |
||||||
|
} |
||||||
|
|
||||||
|
constructor.prototype = { |
||||||
|
// Input: current Canvas context
|
||||||
|
// Output: the appropriate fillStyle or strokeStyle
|
||||||
|
getPattern: function pattern_getStyle(ctx) { |
||||||
|
error('Should not call Pattern.getStyle: ' + ctx); |
||||||
|
} |
||||||
|
}; |
||||||
|
|
||||||
|
constructor.shadingFromIR = function pattern_shadingFromIR(ctx, raw) { |
||||||
|
var obj = window[raw[0]]; |
||||||
|
return obj.fromIR(ctx, raw); |
||||||
|
} |
||||||
|
|
||||||
|
constructor.parseShading = function pattern_shading(shading, matrix, |
||||||
|
xref, res, ctx) { |
||||||
|
|
||||||
|
var dict = isStream(shading) ? shading.dict : shading; |
||||||
|
var type = dict.get('ShadingType'); |
||||||
|
|
||||||
|
switch (type) { |
||||||
|
case 2: |
||||||
|
case 3: |
||||||
|
// both radial and axial shadings are handled by RadialAxial shading
|
||||||
|
return new RadialAxialShading(dict, matrix, xref, res, ctx); |
||||||
|
default: |
||||||
|
return new DummyShading(); |
||||||
|
} |
||||||
|
}; |
||||||
|
return constructor; |
||||||
|
})(); |
||||||
|
|
||||||
|
var DummyShading = (function dummyShading() { |
||||||
|
function constructor() { |
||||||
|
this.type = 'Pattern'; |
||||||
|
} |
||||||
|
|
||||||
|
constructor.fromIR = function() { |
||||||
|
return 'hotpink'; |
||||||
|
} |
||||||
|
|
||||||
|
constructor.prototype = { |
||||||
|
getIR: function dummpy_getir() { |
||||||
|
return ['DummyShading']; |
||||||
|
} |
||||||
|
}; |
||||||
|
return constructor; |
||||||
|
})(); |
||||||
|
|
||||||
|
// Radial and axial shading have very similar implementations
|
||||||
|
// If needed, the implementations can be broken into two classes
|
||||||
|
var RadialAxialShading = (function radialAxialShading() { |
||||||
|
function constructor(dict, matrix, xref, res, ctx) { |
||||||
|
this.matrix = matrix; |
||||||
|
this.coordsArr = dict.get('Coords'); |
||||||
|
this.shadingType = dict.get('ShadingType'); |
||||||
|
this.type = 'Pattern'; |
||||||
|
|
||||||
|
this.ctx = ctx; |
||||||
|
var cs = dict.get('ColorSpace', 'CS'); |
||||||
|
cs = ColorSpace.parse(cs, xref, res); |
||||||
|
this.cs = cs; |
||||||
|
|
||||||
|
var t0 = 0.0, t1 = 1.0; |
||||||
|
if (dict.has('Domain')) { |
||||||
|
var domainArr = dict.get('Domain'); |
||||||
|
t0 = domainArr[0]; |
||||||
|
t1 = domainArr[1]; |
||||||
|
} |
||||||
|
|
||||||
|
var extendStart = false, extendEnd = false; |
||||||
|
if (dict.has('Extend')) { |
||||||
|
var extendArr = dict.get('Extend'); |
||||||
|
extendStart = extendArr[0]; |
||||||
|
extendEnd = extendArr[1]; |
||||||
|
TODO('Support extend'); |
||||||
|
} |
||||||
|
|
||||||
|
this.extendStart = extendStart; |
||||||
|
this.extendEnd = extendEnd; |
||||||
|
|
||||||
|
var fnObj = dict.get('Function'); |
||||||
|
fnObj = xref.fetchIfRef(fnObj); |
||||||
|
if (isArray(fnObj)) |
||||||
|
error('No support for array of functions'); |
||||||
|
else if (!isPDFFunction(fnObj)) |
||||||
|
error('Invalid function'); |
||||||
|
var fn = PDFFunction.parse(xref, fnObj); |
||||||
|
|
||||||
|
// 10 samples seems good enough for now, but probably won't work
|
||||||
|
// if there are sharp color changes. Ideally, we would implement
|
||||||
|
// the spec faithfully and add lossless optimizations.
|
||||||
|
var step = (t1 - t0) / 10; |
||||||
|
var diff = t1 - t0; |
||||||
|
|
||||||
|
var colorStops = []; |
||||||
|
for (var i = t0; i <= t1; i += step) { |
||||||
|
var color = fn([i]); |
||||||
|
var rgbColor = Util.makeCssRgb.apply(this, cs.getRgb(color)); |
||||||
|
colorStops.push([(i - t0) / diff, rgbColor]); |
||||||
|
} |
||||||
|
|
||||||
|
this.colorStops = colorStops; |
||||||
|
} |
||||||
|
|
||||||
|
constructor.fromIR = function(ctx, raw) { |
||||||
|
var type = raw[1]; |
||||||
|
var colorStops = raw[2]; |
||||||
|
var p0 = raw[3]; |
||||||
|
var p1 = raw[4]; |
||||||
|
var r0 = raw[5]; |
||||||
|
var r1 = raw[6]; |
||||||
|
|
||||||
|
var curMatrix = ctx.mozCurrentTransform; |
||||||
|
if (curMatrix) { |
||||||
|
var userMatrix = ctx.mozCurrentTransformInverse; |
||||||
|
|
||||||
|
p0 = Util.applyTransform(p0, curMatrix); |
||||||
|
p0 = Util.applyTransform(p0, userMatrix); |
||||||
|
|
||||||
|
p1 = Util.applyTransform(p1, curMatrix); |
||||||
|
p1 = Util.applyTransform(p1, userMatrix); |
||||||
|
} |
||||||
|
|
||||||
|
if (type == 2) |
||||||
|
var grad = ctx.createLinearGradient(p0[0], p0[1], p1[0], p1[1]); |
||||||
|
else if (type == 3) |
||||||
|
var grad = ctx.createRadialGradient(p0[0], p0[1], r0, p1[0], p1[1], r1); |
||||||
|
|
||||||
|
for (var i = 0, ii = colorStops.length; i < ii; ++i) { |
||||||
|
var c = colorStops[i]; |
||||||
|
grad.addColorStop(c[0], c[1]); |
||||||
|
} |
||||||
|
return grad; |
||||||
|
} |
||||||
|
|
||||||
|
constructor.prototype = { |
||||||
|
getIR: function RadialAxialShading_getIR() { |
||||||
|
var coordsArr = this.coordsArr; |
||||||
|
var type = this.shadingType; |
||||||
|
if (type == 2) { |
||||||
|
var p0 = [coordsArr[0], coordsArr[1]]; |
||||||
|
var p1 = [coordsArr[2], coordsArr[3]]; |
||||||
|
var r0 = null; |
||||||
|
var r1 = null; |
||||||
|
} else if (type == 3) { |
||||||
|
var p0 = [coordsArr[0], coordsArr[1]]; |
||||||
|
var p1 = [coordsArr[3], coordsArr[4]]; |
||||||
|
var r0 = coordsArr[2]; |
||||||
|
var r1 = coordsArr[5]; |
||||||
|
} else { |
||||||
|
error('getPattern type unknown: ' + type); |
||||||
|
} |
||||||
|
|
||||||
|
var matrix = this.matrix; |
||||||
|
if (matrix) { |
||||||
|
p0 = Util.applyTransform(p0, matrix); |
||||||
|
p1 = Util.applyTransform(p1, matrix); |
||||||
|
} |
||||||
|
|
||||||
|
return ['RadialAxialShading', type, this.colorStops, p0, p1, r0, r1]; |
||||||
|
} |
||||||
|
}; |
||||||
|
|
||||||
|
return constructor; |
||||||
|
})(); |
||||||
|
|
||||||
|
var TilingPattern = (function tilingPattern() { |
||||||
|
var PAINT_TYPE_COLORED = 1, PAINT_TYPE_UNCOLORED = 2; |
||||||
|
|
||||||
|
function TilingPattern(IR, color, ctx, objs) { |
||||||
|
var IRQueue = IR[2]; |
||||||
|
this.matrix = IR[3]; |
||||||
|
var bbox = IR[4]; |
||||||
|
var xstep = IR[5]; |
||||||
|
var ystep = IR[6]; |
||||||
|
var paintType = IR[7]; |
||||||
|
|
||||||
|
TODO('TilingType'); |
||||||
|
|
||||||
|
this.curMatrix = ctx.mozCurrentTransform; |
||||||
|
this.invMatrix = ctx.mozCurrentTransformInverse; |
||||||
|
this.ctx = ctx; |
||||||
|
this.type = 'Pattern'; |
||||||
|
|
||||||
|
var x0 = bbox[0], y0 = bbox[1], x1 = bbox[2], y1 = bbox[3]; |
||||||
|
|
||||||
|
var topLeft = [x0, y0]; |
||||||
|
// we want the canvas to be as large as the step size
|
||||||
|
var botRight = [x0 + xstep, y0 + ystep]; |
||||||
|
|
||||||
|
var width = botRight[0] - topLeft[0]; |
||||||
|
var height = botRight[1] - topLeft[1]; |
||||||
|
|
||||||
|
// TODO: hack to avoid OOM, we would idealy compute the tiling
|
||||||
|
// pattern to be only as large as the acual size in device space
|
||||||
|
// This could be computed with .mozCurrentTransform, but still
|
||||||
|
// needs to be implemented
|
||||||
|
while (Math.abs(width) > 512 || Math.abs(height) > 512) { |
||||||
|
width = 512; |
||||||
|
height = 512; |
||||||
|
} |
||||||
|
|
||||||
|
var tmpCanvas = new ScratchCanvas(width, height); |
||||||
|
|
||||||
|
// set the new canvas element context as the graphics context
|
||||||
|
var tmpCtx = tmpCanvas.getContext('2d'); |
||||||
|
var graphics = new CanvasGraphics(tmpCtx, objs); |
||||||
|
|
||||||
|
switch (paintType) { |
||||||
|
case PAINT_TYPE_COLORED: |
||||||
|
tmpCtx.fillStyle = ctx.fillStyle; |
||||||
|
tmpCtx.strokeStyle = ctx.strokeStyle; |
||||||
|
break; |
||||||
|
case PAINT_TYPE_UNCOLORED: |
||||||
|
color = Util.makeCssRgb.apply(this, color); |
||||||
|
tmpCtx.fillStyle = color; |
||||||
|
tmpCtx.strokeStyle = color; |
||||||
|
break; |
||||||
|
default: |
||||||
|
error('Unsupported paint type: ' + paintType); |
||||||
|
} |
||||||
|
|
||||||
|
var scale = [width / xstep, height / ystep]; |
||||||
|
this.scale = scale; |
||||||
|
|
||||||
|
// transform coordinates to pattern space
|
||||||
|
var tmpTranslate = [1, 0, 0, 1, -topLeft[0], -topLeft[1]]; |
||||||
|
var tmpScale = [scale[0], 0, 0, scale[1], 0, 0]; |
||||||
|
graphics.transform.apply(graphics, tmpScale); |
||||||
|
graphics.transform.apply(graphics, tmpTranslate); |
||||||
|
|
||||||
|
if (bbox && isArray(bbox) && 4 == bbox.length) { |
||||||
|
var bboxWidth = bbox[2] - bbox[0]; |
||||||
|
var bboxHeight = bbox[3] - bbox[1]; |
||||||
|
graphics.rectangle(bbox[0], bbox[1], bboxWidth, bboxHeight); |
||||||
|
graphics.clip(); |
||||||
|
graphics.endPath(); |
||||||
|
} |
||||||
|
|
||||||
|
graphics.executeIRQueue(IRQueue); |
||||||
|
|
||||||
|
this.canvas = tmpCanvas; |
||||||
|
} |
||||||
|
|
||||||
|
TilingPattern.getIR = function tiling_getIR(codeIR, dict, args) { |
||||||
|
var matrix = dict.get('Matrix'); |
||||||
|
var bbox = dict.get('BBox'); |
||||||
|
var xstep = dict.get('XStep'); |
||||||
|
var ystep = dict.get('YStep'); |
||||||
|
var paintType = dict.get('PaintType'); |
||||||
|
|
||||||
|
return [ |
||||||
|
'TilingPattern', args, codeIR, matrix, bbox, xstep, ystep, paintType |
||||||
|
]; |
||||||
|
} |
||||||
|
|
||||||
|
TilingPattern.prototype = { |
||||||
|
getPattern: function tiling_getPattern() { |
||||||
|
var matrix = this.matrix; |
||||||
|
var curMatrix = this.curMatrix; |
||||||
|
var ctx = this.ctx; |
||||||
|
|
||||||
|
if (curMatrix) |
||||||
|
ctx.setTransform.apply(ctx, curMatrix); |
||||||
|
|
||||||
|
if (matrix) |
||||||
|
ctx.transform.apply(ctx, matrix); |
||||||
|
|
||||||
|
var scale = this.scale; |
||||||
|
ctx.scale(1 / scale[0], 1 / scale[1]); |
||||||
|
|
||||||
|
return ctx.createPattern(this.canvas, 'repeat'); |
||||||
|
} |
||||||
|
}; |
||||||
|
|
||||||
|
return TilingPattern; |
||||||
|
})(); |
@ -0,0 +1,604 @@ |
|||||||
|
/* -*- 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 ERRORS = 0, WARNINGS = 1, TODOS = 5; |
||||||
|
var verbosity = WARNINGS; |
||||||
|
|
||||||
|
// Set this to true if you want to use workers.
|
||||||
|
var useWorker = false; |
||||||
|
|
||||||
|
//
|
||||||
|
// getPdf()
|
||||||
|
// Convenience function to perform binary Ajax GET
|
||||||
|
// Usage: getPdf('http://...', callback)
|
||||||
|
// getPdf({
|
||||||
|
// url:String ,
|
||||||
|
// [,progress:Function, error:Function]
|
||||||
|
// },
|
||||||
|
// callback)
|
||||||
|
//
|
||||||
|
function getPdf(arg, callback) { |
||||||
|
var params = arg; |
||||||
|
if (typeof arg === 'string') |
||||||
|
params = { url: arg }; |
||||||
|
|
||||||
|
var xhr = new XMLHttpRequest(); |
||||||
|
xhr.open('GET', params.url); |
||||||
|
xhr.mozResponseType = xhr.responseType = 'arraybuffer'; |
||||||
|
xhr.expected = (document.URL.indexOf('file:') === 0) ? 0 : 200; |
||||||
|
|
||||||
|
if ('progress' in params) |
||||||
|
xhr.onprogress = params.progress || undefined; |
||||||
|
|
||||||
|
if ('error' in params) |
||||||
|
xhr.onerror = params.error || undefined; |
||||||
|
|
||||||
|
xhr.onreadystatechange = function getPdfOnreadystatechange() { |
||||||
|
if (xhr.readyState === 4 && xhr.status === xhr.expected) { |
||||||
|
var data = (xhr.mozResponseArrayBuffer || xhr.mozResponse || |
||||||
|
xhr.responseArrayBuffer || xhr.response); |
||||||
|
callback(data); |
||||||
|
} |
||||||
|
}; |
||||||
|
xhr.send(null); |
||||||
|
} |
||||||
|
|
||||||
|
var Page = (function pagePage() { |
||||||
|
function constructor(xref, pageNumber, pageDict, ref) { |
||||||
|
this.pageNumber = pageNumber; |
||||||
|
this.pageDict = pageDict; |
||||||
|
this.stats = { |
||||||
|
create: Date.now(), |
||||||
|
compile: 0.0, |
||||||
|
fonts: 0.0, |
||||||
|
images: 0.0, |
||||||
|
render: 0.0 |
||||||
|
}; |
||||||
|
this.xref = xref; |
||||||
|
this.ref = ref; |
||||||
|
} |
||||||
|
|
||||||
|
constructor.prototype = { |
||||||
|
getPageProp: function pageGetPageProp(key) { |
||||||
|
return this.xref.fetchIfRef(this.pageDict.get(key)); |
||||||
|
}, |
||||||
|
inheritPageProp: function pageInheritPageProp(key) { |
||||||
|
var dict = this.pageDict; |
||||||
|
var obj = dict.get(key); |
||||||
|
while (obj === undefined) { |
||||||
|
dict = this.xref.fetchIfRef(dict.get('Parent')); |
||||||
|
if (!dict) |
||||||
|
break; |
||||||
|
obj = dict.get(key); |
||||||
|
} |
||||||
|
return obj; |
||||||
|
}, |
||||||
|
get content() { |
||||||
|
return shadow(this, 'content', this.getPageProp('Contents')); |
||||||
|
}, |
||||||
|
get resources() { |
||||||
|
return shadow(this, 'resources', this.inheritPageProp('Resources')); |
||||||
|
}, |
||||||
|
get mediaBox() { |
||||||
|
var obj = this.inheritPageProp('MediaBox'); |
||||||
|
// Reset invalid media box to letter size.
|
||||||
|
if (!isArray(obj) || obj.length !== 4) |
||||||
|
obj = [0, 0, 612, 792]; |
||||||
|
return shadow(this, 'mediaBox', obj); |
||||||
|
}, |
||||||
|
get view() { |
||||||
|
var obj = this.inheritPageProp('CropBox'); |
||||||
|
var view = { |
||||||
|
x: 0, |
||||||
|
y: 0, |
||||||
|
width: this.width, |
||||||
|
height: this.height |
||||||
|
}; |
||||||
|
if (isArray(obj) && obj.length == 4) { |
||||||
|
var tl = this.rotatePoint(obj[0], obj[1]); |
||||||
|
var br = this.rotatePoint(obj[2], obj[3]); |
||||||
|
view.x = Math.min(tl.x, br.x); |
||||||
|
view.y = Math.min(tl.y, br.y); |
||||||
|
view.width = Math.abs(tl.x - br.x); |
||||||
|
view.height = Math.abs(tl.y - br.y); |
||||||
|
} |
||||||
|
|
||||||
|
return shadow(this, 'cropBox', view); |
||||||
|
}, |
||||||
|
get annotations() { |
||||||
|
return shadow(this, 'annotations', this.inheritPageProp('Annots')); |
||||||
|
}, |
||||||
|
get width() { |
||||||
|
var mediaBox = this.mediaBox; |
||||||
|
var rotate = this.rotate; |
||||||
|
var width; |
||||||
|
if (rotate == 0 || rotate == 180) { |
||||||
|
width = (mediaBox[2] - mediaBox[0]); |
||||||
|
} else { |
||||||
|
width = (mediaBox[3] - mediaBox[1]); |
||||||
|
} |
||||||
|
return shadow(this, 'width', width); |
||||||
|
}, |
||||||
|
get height() { |
||||||
|
var mediaBox = this.mediaBox; |
||||||
|
var rotate = this.rotate; |
||||||
|
var height; |
||||||
|
if (rotate == 0 || rotate == 180) { |
||||||
|
height = (mediaBox[3] - mediaBox[1]); |
||||||
|
} else { |
||||||
|
height = (mediaBox[2] - mediaBox[0]); |
||||||
|
} |
||||||
|
return shadow(this, 'height', height); |
||||||
|
}, |
||||||
|
get rotate() { |
||||||
|
var rotate = this.inheritPageProp('Rotate') || 0; |
||||||
|
// Normalize rotation so it's a multiple of 90 and between 0 and 270
|
||||||
|
if (rotate % 90 != 0) { |
||||||
|
rotate = 0; |
||||||
|
} else if (rotate >= 360) { |
||||||
|
rotate = rotate % 360; |
||||||
|
} else if (rotate < 0) { |
||||||
|
// The spec doesn't cover negatives, assume its counterclockwise
|
||||||
|
// rotation. The following is the other implementation of modulo.
|
||||||
|
rotate = ((rotate % 360) + 360) % 360; |
||||||
|
} |
||||||
|
return shadow(this, 'rotate', rotate); |
||||||
|
}, |
||||||
|
|
||||||
|
startRenderingFromIRQueue: function startRenderingFromIRQueue( |
||||||
|
IRQueue, fonts) { |
||||||
|
var self = this; |
||||||
|
this.IRQueue = IRQueue; |
||||||
|
var gfx = new CanvasGraphics(this.ctx, this.objs); |
||||||
|
var startTime = Date.now(); |
||||||
|
|
||||||
|
var displayContinuation = function pageDisplayContinuation() { |
||||||
|
// Always defer call to display() to work around bug in
|
||||||
|
// Firefox error reporting from XHR callbacks.
|
||||||
|
setTimeout(function pageSetTimeout() { |
||||||
|
try { |
||||||
|
self.display(gfx, self.callback); |
||||||
|
} catch (e) { |
||||||
|
if (self.callback) self.callback(e.toString()); |
||||||
|
throw e; |
||||||
|
} |
||||||
|
}); |
||||||
|
}; |
||||||
|
|
||||||
|
this.ensureFonts(fonts, function() { |
||||||
|
displayContinuation(); |
||||||
|
}); |
||||||
|
}, |
||||||
|
|
||||||
|
getIRQueue: function(handler, dependency) { |
||||||
|
if (this.IRQueue) { |
||||||
|
// content was compiled
|
||||||
|
return this.IRQueue; |
||||||
|
} |
||||||
|
|
||||||
|
var xref = this.xref; |
||||||
|
var content = xref.fetchIfRef(this.content); |
||||||
|
var resources = xref.fetchIfRef(this.resources); |
||||||
|
if (isArray(content)) { |
||||||
|
// fetching items
|
||||||
|
var i, n = content.length; |
||||||
|
for (i = 0; i < n; ++i) |
||||||
|
content[i] = xref.fetchIfRef(content[i]); |
||||||
|
content = new StreamsSequenceStream(content); |
||||||
|
} |
||||||
|
|
||||||
|
var pe = this.pe = new PartialEvaluator( |
||||||
|
xref, handler, 'p' + this.pageNumber + '_'); |
||||||
|
var IRQueue = {}; |
||||||
|
return this.IRQueue = pe.getIRQueue( |
||||||
|
content, resources, IRQueue, dependency); |
||||||
|
}, |
||||||
|
|
||||||
|
ensureFonts: function(fonts, callback) { |
||||||
|
// Convert the font names to the corresponding font obj.
|
||||||
|
for (var i = 0; i < fonts.length; i++) { |
||||||
|
fonts[i] = this.objs.objs[fonts[i]].data; |
||||||
|
} |
||||||
|
|
||||||
|
// Load all the fonts
|
||||||
|
var fontObjs = FontLoader.bind( |
||||||
|
fonts, |
||||||
|
function(fontObjs) { |
||||||
|
this.stats.fonts = Date.now(); |
||||||
|
|
||||||
|
callback.call(this); |
||||||
|
}.bind(this), |
||||||
|
this.objs |
||||||
|
); |
||||||
|
}, |
||||||
|
|
||||||
|
display: function(gfx, callback) { |
||||||
|
var xref = this.xref; |
||||||
|
var resources = xref.fetchIfRef(this.resources); |
||||||
|
var mediaBox = xref.fetchIfRef(this.mediaBox); |
||||||
|
assertWellFormed(isDict(resources), 'invalid page resources'); |
||||||
|
|
||||||
|
gfx.xref = xref; |
||||||
|
gfx.res = resources; |
||||||
|
gfx.beginDrawing({ x: mediaBox[0], y: mediaBox[1], |
||||||
|
width: this.width, |
||||||
|
height: this.height, |
||||||
|
rotate: this.rotate }); |
||||||
|
|
||||||
|
var startIdx = 0; |
||||||
|
var length = this.IRQueue.fnArray.length; |
||||||
|
var IRQueue = this.IRQueue; |
||||||
|
|
||||||
|
var self = this; |
||||||
|
var startTime = Date.now(); |
||||||
|
function next() { |
||||||
|
startIdx = gfx.executeIRQueue(IRQueue, startIdx, next); |
||||||
|
if (startIdx == length) { |
||||||
|
self.stats.render = Date.now(); |
||||||
|
if (callback) callback(); |
||||||
|
} |
||||||
|
} |
||||||
|
next(); |
||||||
|
}, |
||||||
|
rotatePoint: function pageRotatePoint(x, y, reverse) { |
||||||
|
var rotate = reverse ? (360 - this.rotate) : this.rotate; |
||||||
|
switch (rotate) { |
||||||
|
case 180: |
||||||
|
return {x: this.width - x, y: y}; |
||||||
|
case 90: |
||||||
|
return {x: this.width - y, y: this.height - x}; |
||||||
|
case 270: |
||||||
|
return {x: y, y: x}; |
||||||
|
case 360: |
||||||
|
case 0: |
||||||
|
default: |
||||||
|
return {x: x, y: this.height - y}; |
||||||
|
} |
||||||
|
}, |
||||||
|
getLinks: function pageGetLinks() { |
||||||
|
var xref = this.xref; |
||||||
|
var annotations = xref.fetchIfRef(this.annotations) || []; |
||||||
|
var i, n = annotations.length; |
||||||
|
var links = []; |
||||||
|
for (i = 0; i < n; ++i) { |
||||||
|
var annotation = xref.fetch(annotations[i]); |
||||||
|
if (!isDict(annotation)) |
||||||
|
continue; |
||||||
|
var subtype = annotation.get('Subtype'); |
||||||
|
if (!isName(subtype) || subtype.name != 'Link') |
||||||
|
continue; |
||||||
|
var rect = annotation.get('Rect'); |
||||||
|
var topLeftCorner = this.rotatePoint(rect[0], rect[1]); |
||||||
|
var bottomRightCorner = this.rotatePoint(rect[2], rect[3]); |
||||||
|
|
||||||
|
var link = {}; |
||||||
|
link.x = Math.min(topLeftCorner.x, bottomRightCorner.x); |
||||||
|
link.y = Math.min(topLeftCorner.y, bottomRightCorner.y); |
||||||
|
link.width = Math.abs(topLeftCorner.x - bottomRightCorner.x); |
||||||
|
link.height = Math.abs(topLeftCorner.y - bottomRightCorner.y); |
||||||
|
var a = this.xref.fetchIfRef(annotation.get('A')); |
||||||
|
if (a) { |
||||||
|
switch (a.get('S').name) { |
||||||
|
case 'URI': |
||||||
|
link.url = a.get('URI'); |
||||||
|
break; |
||||||
|
case 'GoTo': |
||||||
|
link.dest = a.get('D'); |
||||||
|
break; |
||||||
|
default: |
||||||
|
TODO('other link types'); |
||||||
|
} |
||||||
|
} else if (annotation.has('Dest')) { |
||||||
|
// simple destination link
|
||||||
|
var dest = annotation.get('Dest'); |
||||||
|
link.dest = isName(dest) ? dest.name : dest; |
||||||
|
} |
||||||
|
links.push(link); |
||||||
|
} |
||||||
|
return links; |
||||||
|
}, |
||||||
|
startRendering: function(ctx, callback) { |
||||||
|
this.ctx = ctx; |
||||||
|
this.callback = callback; |
||||||
|
|
||||||
|
this.startRenderingTime = Date.now(); |
||||||
|
this.pdf.startRendering(this); |
||||||
|
} |
||||||
|
}; |
||||||
|
|
||||||
|
return constructor; |
||||||
|
})(); |
||||||
|
|
||||||
|
/** |
||||||
|
* The `PDFDocModel` holds all the data of the PDF file. Compared to the |
||||||
|
* `PDFDoc`, this one doesn't have any job management code. |
||||||
|
* Right now there exists one PDFDocModel on the main thread + one object |
||||||
|
* for each worker. If there is no worker support enabled, there are two |
||||||
|
* `PDFDocModel` objects on the main thread created. |
||||||
|
* TODO: Refactor the internal object structure, such that there is no |
||||||
|
* need for the `PDFDocModel` anymore and there is only one object on the |
||||||
|
* main thread and not one entire copy on each worker instance. |
||||||
|
*/ |
||||||
|
var PDFDocModel = (function pdfDoc() { |
||||||
|
function constructor(arg, callback) { |
||||||
|
if (isStream(arg)) |
||||||
|
init.call(this, arg); |
||||||
|
else if (isArrayBuffer(arg)) |
||||||
|
init.call(this, new Stream(arg)); |
||||||
|
else |
||||||
|
error('PDFDocModel: Unknown argument type'); |
||||||
|
} |
||||||
|
|
||||||
|
function init(stream) { |
||||||
|
assertWellFormed(stream.length > 0, 'stream must have data'); |
||||||
|
this.stream = stream; |
||||||
|
this.setup(); |
||||||
|
} |
||||||
|
|
||||||
|
function find(stream, needle, limit, backwards) { |
||||||
|
var pos = stream.pos; |
||||||
|
var end = stream.end; |
||||||
|
var str = ''; |
||||||
|
if (pos + limit > end) |
||||||
|
limit = end - pos; |
||||||
|
for (var n = 0; n < limit; ++n) |
||||||
|
str += stream.getChar(); |
||||||
|
stream.pos = pos; |
||||||
|
var index = backwards ? str.lastIndexOf(needle) : str.indexOf(needle); |
||||||
|
if (index == -1) |
||||||
|
return false; /* not found */ |
||||||
|
stream.pos += index; |
||||||
|
return true; /* found */ |
||||||
|
} |
||||||
|
|
||||||
|
constructor.prototype = { |
||||||
|
get linearization() { |
||||||
|
var length = this.stream.length; |
||||||
|
var linearization = false; |
||||||
|
if (length) { |
||||||
|
linearization = new Linearization(this.stream); |
||||||
|
if (linearization.length != length) |
||||||
|
linearization = false; |
||||||
|
} |
||||||
|
// shadow the prototype getter with a data property
|
||||||
|
return shadow(this, 'linearization', linearization); |
||||||
|
}, |
||||||
|
get startXRef() { |
||||||
|
var stream = this.stream; |
||||||
|
var startXRef = 0; |
||||||
|
var linearization = this.linearization; |
||||||
|
if (linearization) { |
||||||
|
// Find end of first obj.
|
||||||
|
stream.reset(); |
||||||
|
if (find(stream, 'endobj', 1024)) |
||||||
|
startXRef = stream.pos + 6; |
||||||
|
} else { |
||||||
|
// Find startxref at the end of the file.
|
||||||
|
var start = stream.end - 1024; |
||||||
|
if (start < 0) |
||||||
|
start = 0; |
||||||
|
stream.pos = start; |
||||||
|
if (find(stream, 'startxref', 1024, true)) { |
||||||
|
stream.skip(9); |
||||||
|
var ch; |
||||||
|
do { |
||||||
|
ch = stream.getChar(); |
||||||
|
} while (Lexer.isSpace(ch)); |
||||||
|
var str = ''; |
||||||
|
while ((ch - '0') <= 9) { |
||||||
|
str += ch; |
||||||
|
ch = stream.getChar(); |
||||||
|
} |
||||||
|
startXRef = parseInt(str, 10); |
||||||
|
if (isNaN(startXRef)) |
||||||
|
startXRef = 0; |
||||||
|
} |
||||||
|
} |
||||||
|
// shadow the prototype getter with a data property
|
||||||
|
return shadow(this, 'startXRef', startXRef); |
||||||
|
}, |
||||||
|
get mainXRefEntriesOffset() { |
||||||
|
var mainXRefEntriesOffset = 0; |
||||||
|
var linearization = this.linearization; |
||||||
|
if (linearization) |
||||||
|
mainXRefEntriesOffset = linearization.mainXRefEntriesOffset; |
||||||
|
// shadow the prototype getter with a data property
|
||||||
|
return shadow(this, 'mainXRefEntriesOffset', mainXRefEntriesOffset); |
||||||
|
}, |
||||||
|
// Find the header, remove leading garbage and setup the stream
|
||||||
|
// starting from the header.
|
||||||
|
checkHeader: function pdfDocCheckHeader() { |
||||||
|
var stream = this.stream; |
||||||
|
stream.reset(); |
||||||
|
if (find(stream, '%PDF-', 1024)) { |
||||||
|
// Found the header, trim off any garbage before it.
|
||||||
|
stream.moveStart(); |
||||||
|
return; |
||||||
|
} |
||||||
|
// May not be a PDF file, continue anyway.
|
||||||
|
}, |
||||||
|
setup: function pdfDocSetup(ownerPassword, userPassword) { |
||||||
|
this.checkHeader(); |
||||||
|
this.xref = new XRef(this.stream, |
||||||
|
this.startXRef, |
||||||
|
this.mainXRefEntriesOffset); |
||||||
|
this.catalog = new Catalog(this.xref); |
||||||
|
}, |
||||||
|
get numPages() { |
||||||
|
var linearization = this.linearization; |
||||||
|
var num = linearization ? linearization.numPages : this.catalog.numPages; |
||||||
|
// shadow the prototype getter
|
||||||
|
return shadow(this, 'numPages', num); |
||||||
|
}, |
||||||
|
getPage: function pdfDocGetPage(n) { |
||||||
|
return this.catalog.getPage(n); |
||||||
|
} |
||||||
|
}; |
||||||
|
|
||||||
|
return constructor; |
||||||
|
})(); |
||||||
|
|
||||||
|
var PDFDoc = (function() { |
||||||
|
function constructor(arg, callback) { |
||||||
|
var stream = null; |
||||||
|
var data = null; |
||||||
|
|
||||||
|
if (isStream(arg)) { |
||||||
|
stream = arg; |
||||||
|
data = arg.bytes; |
||||||
|
} else if (isArrayBuffer(arg)) { |
||||||
|
stream = new Stream(arg); |
||||||
|
data = arg; |
||||||
|
} else { |
||||||
|
error('PDFDoc: Unknown argument type'); |
||||||
|
} |
||||||
|
|
||||||
|
this.data = data; |
||||||
|
this.stream = stream; |
||||||
|
this.pdf = new PDFDocModel(stream); |
||||||
|
|
||||||
|
this.catalog = this.pdf.catalog; |
||||||
|
this.objs = new PDFObjects(); |
||||||
|
|
||||||
|
this.pageCache = []; |
||||||
|
|
||||||
|
if (useWorker) { |
||||||
|
var worker = new Worker('../worker/pdf_worker_loader.js'); |
||||||
|
} else { |
||||||
|
// If we don't use a worker, just post/sendMessage to the main thread.
|
||||||
|
var worker = { |
||||||
|
postMessage: function(obj) { |
||||||
|
worker.onmessage({data: obj}); |
||||||
|
}, |
||||||
|
terminate: function() {} |
||||||
|
}; |
||||||
|
} |
||||||
|
this.worker = worker; |
||||||
|
|
||||||
|
this.fontsLoading = {}; |
||||||
|
|
||||||
|
var processorHandler = this.processorHandler = |
||||||
|
new MessageHandler('main', worker); |
||||||
|
|
||||||
|
processorHandler.on('page', function(data) { |
||||||
|
var pageNum = data.pageNum; |
||||||
|
var page = this.pageCache[pageNum]; |
||||||
|
var depFonts = data.depFonts; |
||||||
|
|
||||||
|
page.startRenderingFromIRQueue(data.IRQueue, depFonts); |
||||||
|
}, this); |
||||||
|
|
||||||
|
processorHandler.on('obj', function(data) { |
||||||
|
var id = data[0]; |
||||||
|
var type = data[1]; |
||||||
|
|
||||||
|
switch (type) { |
||||||
|
case 'JpegStream': |
||||||
|
var IR = data[2]; |
||||||
|
new JpegImage(id, IR, this.objs); |
||||||
|
break; |
||||||
|
case 'Font': |
||||||
|
var name = data[2]; |
||||||
|
var file = data[3]; |
||||||
|
var properties = data[4]; |
||||||
|
|
||||||
|
if (file) { |
||||||
|
var fontFileDict = new Dict(); |
||||||
|
fontFileDict.map = file.dict.map; |
||||||
|
|
||||||
|
var fontFile = new Stream(file.bytes, file.start, |
||||||
|
file.end - file.start, fontFileDict); |
||||||
|
|
||||||
|
// Check if this is a FlateStream. Otherwise just use the created
|
||||||
|
// Stream one. This makes complex_ttf_font.pdf work.
|
||||||
|
var cmf = file.bytes[0]; |
||||||
|
if ((cmf & 0x0f) == 0x08) { |
||||||
|
file = new FlateStream(fontFile); |
||||||
|
} else { |
||||||
|
file = fontFile; |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
// For now, resolve the font object here direclty. The real font
|
||||||
|
// object is then created in FontLoader.bind().
|
||||||
|
this.objs.resolve(id, { |
||||||
|
name: name, |
||||||
|
file: file, |
||||||
|
properties: properties |
||||||
|
}); |
||||||
|
break; |
||||||
|
default: |
||||||
|
throw 'Got unkown object type ' + type; |
||||||
|
} |
||||||
|
}, this); |
||||||
|
|
||||||
|
processorHandler.on('font_ready', function(data) { |
||||||
|
var id = data[0]; |
||||||
|
var font = new FontShape(data[1]); |
||||||
|
|
||||||
|
// If there is no string, then there is nothing to attach to the DOM.
|
||||||
|
if (!font.str) { |
||||||
|
this.objs.resolve(id, font); |
||||||
|
} else { |
||||||
|
this.objs.setData(id, font); |
||||||
|
} |
||||||
|
}.bind(this)); |
||||||
|
|
||||||
|
if (!useWorker) { |
||||||
|
// If the main thread is our worker, setup the handling for the messages
|
||||||
|
// the main thread sends to it self.
|
||||||
|
WorkerProcessorHandler.setup(processorHandler); |
||||||
|
} |
||||||
|
|
||||||
|
this.workerReadyPromise = new Promise('workerReady'); |
||||||
|
setTimeout(function() { |
||||||
|
processorHandler.send('doc', this.data); |
||||||
|
this.workerReadyPromise.resolve(true); |
||||||
|
}.bind(this)); |
||||||
|
} |
||||||
|
|
||||||
|
constructor.prototype = { |
||||||
|
get numPages() { |
||||||
|
return this.pdf.numPages; |
||||||
|
}, |
||||||
|
|
||||||
|
startRendering: function(page) { |
||||||
|
// The worker might not be ready to receive the page request yet.
|
||||||
|
this.workerReadyPromise.then(function() { |
||||||
|
this.processorHandler.send('page_request', page.pageNumber + 1); |
||||||
|
}.bind(this)); |
||||||
|
}, |
||||||
|
|
||||||
|
getPage: function(n) { |
||||||
|
if (this.pageCache[n]) |
||||||
|
return this.pageCache[n]; |
||||||
|
|
||||||
|
var page = this.pdf.getPage(n); |
||||||
|
// Add a reference to the objects such that Page can forward the reference
|
||||||
|
// to the CanvasGraphics and so on.
|
||||||
|
page.objs = this.objs; |
||||||
|
page.pdf = this; |
||||||
|
return this.pageCache[n] = page; |
||||||
|
}, |
||||||
|
|
||||||
|
destroy: function() { |
||||||
|
if (this.worker) |
||||||
|
this.worker.terminate(); |
||||||
|
|
||||||
|
if (this.fontWorker) |
||||||
|
this.fontWorker.terminate(); |
||||||
|
|
||||||
|
for (var n in this.pageCache) |
||||||
|
delete this.pageCache[n]; |
||||||
|
|
||||||
|
delete this.data; |
||||||
|
delete this.stream; |
||||||
|
delete this.pdf; |
||||||
|
delete this.catalog; |
||||||
|
} |
||||||
|
}; |
||||||
|
|
||||||
|
return constructor; |
||||||
|
})(); |
@ -0,0 +1,280 @@ |
|||||||
|
function log(msg) { |
||||||
|
if (console && console.log) |
||||||
|
console.log(msg); |
||||||
|
else if (print) |
||||||
|
print(msg); |
||||||
|
} |
||||||
|
|
||||||
|
function warn(msg) { |
||||||
|
if (verbosity >= WARNINGS) |
||||||
|
log('Warning: ' + msg); |
||||||
|
} |
||||||
|
|
||||||
|
function backtrace() { |
||||||
|
var stackStr; |
||||||
|
try { |
||||||
|
throw new Error(); |
||||||
|
} catch (e) { |
||||||
|
stackStr = e.stack; |
||||||
|
} |
||||||
|
return stackStr.split('\n').slice(1).join('\n'); |
||||||
|
} |
||||||
|
|
||||||
|
function error(msg) { |
||||||
|
log(backtrace()); |
||||||
|
throw new Error(msg); |
||||||
|
} |
||||||
|
|
||||||
|
function TODO(what) { |
||||||
|
if (verbosity >= TODOS) |
||||||
|
log('TODO: ' + what); |
||||||
|
} |
||||||
|
|
||||||
|
function malformed(msg) { |
||||||
|
error('Malformed PDF: ' + msg); |
||||||
|
} |
||||||
|
|
||||||
|
function assert(cond, msg) { |
||||||
|
if (!cond) |
||||||
|
error(msg); |
||||||
|
} |
||||||
|
|
||||||
|
// In a well-formed PDF, |cond| holds. If it doesn't, subsequent
|
||||||
|
// behavior is undefined.
|
||||||
|
function assertWellFormed(cond, msg) { |
||||||
|
if (!cond) |
||||||
|
malformed(msg); |
||||||
|
} |
||||||
|
|
||||||
|
function shadow(obj, prop, value) { |
||||||
|
Object.defineProperty(obj, prop, { value: value, |
||||||
|
enumerable: true, |
||||||
|
configurable: true, |
||||||
|
writable: false }); |
||||||
|
return value; |
||||||
|
} |
||||||
|
|
||||||
|
function bytesToString(bytes) { |
||||||
|
var str = ''; |
||||||
|
var length = bytes.length; |
||||||
|
for (var n = 0; n < length; ++n) |
||||||
|
str += String.fromCharCode(bytes[n]); |
||||||
|
return str; |
||||||
|
} |
||||||
|
|
||||||
|
function stringToBytes(str) { |
||||||
|
var length = str.length; |
||||||
|
var bytes = new Uint8Array(length); |
||||||
|
for (var n = 0; n < length; ++n) |
||||||
|
bytes[n] = str.charCodeAt(n) & 0xFF; |
||||||
|
return bytes; |
||||||
|
} |
||||||
|
|
||||||
|
var IDENTITY_MATRIX = [1, 0, 0, 1, 0, 0]; |
||||||
|
|
||||||
|
var Util = (function utilUtil() { |
||||||
|
function constructor() {} |
||||||
|
constructor.makeCssRgb = function makergb(r, g, b) { |
||||||
|
var ri = (255 * r) | 0, gi = (255 * g) | 0, bi = (255 * b) | 0; |
||||||
|
return 'rgb(' + ri + ',' + gi + ',' + bi + ')'; |
||||||
|
}; |
||||||
|
constructor.makeCssCmyk = function makecmyk(c, m, y, k) { |
||||||
|
c = (new DeviceCmykCS()).getRgb([c, m, y, k]); |
||||||
|
var ri = (255 * c[0]) | 0, gi = (255 * c[1]) | 0, bi = (255 * c[2]) | 0; |
||||||
|
return 'rgb(' + ri + ',' + gi + ',' + bi + ')'; |
||||||
|
}; |
||||||
|
constructor.applyTransform = function apply(p, m) { |
||||||
|
var xt = p[0] * m[0] + p[1] * m[2] + m[4]; |
||||||
|
var yt = p[0] * m[1] + p[1] * m[3] + m[5]; |
||||||
|
return [xt, yt]; |
||||||
|
}; |
||||||
|
|
||||||
|
return constructor; |
||||||
|
})(); |
||||||
|
|
||||||
|
var PDFStringTranslateTable = [ |
||||||
|
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, |
||||||
|
0x2D8, 0x2C7, 0x2C6, 0x2D9, 0x2DD, 0x2DB, 0x2DA, 0x2DC, 0, 0, 0, 0, 0, 0, 0, |
||||||
|
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, |
||||||
|
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, |
||||||
|
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, |
||||||
|
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0x2022, 0x2020, 0x2021, 0x2026, 0x2014, |
||||||
|
0x2013, 0x192, 0x2044, 0x2039, 0x203A, 0x2212, 0x2030, 0x201E, 0x201C, |
||||||
|
0x201D, 0x2018, 0x2019, 0x201A, 0x2122, 0xFB01, 0xFB02, 0x141, 0x152, 0x160, |
||||||
|
0x178, 0x17D, 0x131, 0x142, 0x153, 0x161, 0x17E, 0, 0x20AC |
||||||
|
]; |
||||||
|
|
||||||
|
function stringToPDFString(str) { |
||||||
|
var i, n = str.length, str2 = ''; |
||||||
|
if (str[0] === '\xFE' && str[1] === '\xFF') { |
||||||
|
// UTF16BE BOM
|
||||||
|
for (i = 2; i < n; i += 2) |
||||||
|
str2 += String.fromCharCode( |
||||||
|
(str.charCodeAt(i) << 8) | str.charCodeAt(i + 1)); |
||||||
|
} else { |
||||||
|
for (i = 0; i < n; ++i) { |
||||||
|
var code = PDFStringTranslateTable[str.charCodeAt(i)]; |
||||||
|
str2 += code ? String.fromCharCode(code) : str.charAt(i); |
||||||
|
} |
||||||
|
} |
||||||
|
return str2; |
||||||
|
} |
||||||
|
|
||||||
|
function isBool(v) { |
||||||
|
return typeof v == 'boolean'; |
||||||
|
} |
||||||
|
|
||||||
|
function isInt(v) { |
||||||
|
return typeof v == 'number' && ((v | 0) == v); |
||||||
|
} |
||||||
|
|
||||||
|
function isNum(v) { |
||||||
|
return typeof v == 'number'; |
||||||
|
} |
||||||
|
|
||||||
|
function isString(v) { |
||||||
|
return typeof v == 'string'; |
||||||
|
} |
||||||
|
|
||||||
|
function isNull(v) { |
||||||
|
return v === null; |
||||||
|
} |
||||||
|
|
||||||
|
function isName(v) { |
||||||
|
return v instanceof Name; |
||||||
|
} |
||||||
|
|
||||||
|
function isCmd(v, cmd) { |
||||||
|
return v instanceof Cmd && (!cmd || v.cmd == cmd); |
||||||
|
} |
||||||
|
|
||||||
|
function isDict(v, type) { |
||||||
|
return v instanceof Dict && (!type || v.get('Type').name == type); |
||||||
|
} |
||||||
|
|
||||||
|
function isArray(v) { |
||||||
|
return v instanceof Array; |
||||||
|
} |
||||||
|
|
||||||
|
function isStream(v) { |
||||||
|
return typeof v == 'object' && v != null && ('getChar' in v); |
||||||
|
} |
||||||
|
|
||||||
|
function isArrayBuffer(v) { |
||||||
|
return typeof v == 'object' && v != null && ('byteLength' in v); |
||||||
|
} |
||||||
|
|
||||||
|
function isRef(v) { |
||||||
|
return v instanceof Ref; |
||||||
|
} |
||||||
|
|
||||||
|
function isPDFFunction(v) { |
||||||
|
var fnDict; |
||||||
|
if (typeof v != 'object') |
||||||
|
return false; |
||||||
|
else if (isDict(v)) |
||||||
|
fnDict = v; |
||||||
|
else if (isStream(v)) |
||||||
|
fnDict = v.dict; |
||||||
|
else |
||||||
|
return false; |
||||||
|
return fnDict.has('FunctionType'); |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* 'Promise' object. |
||||||
|
* Each object that is stored in PDFObjects is based on a Promise object that |
||||||
|
* contains the status of the object and the data. There migth be situations, |
||||||
|
* where a function want to use the value of an object, but it isn't ready at |
||||||
|
* that time. To get a notification, once the object is ready to be used, s.o. |
||||||
|
* can add a callback using the `then` method on the promise that then calls |
||||||
|
* the callback once the object gets resolved. |
||||||
|
* A promise can get resolved only once and only once the data of the promise |
||||||
|
* can be set. If any of these happens twice or the data is required before |
||||||
|
* it was set, an exception is throw. |
||||||
|
*/ |
||||||
|
var Promise = (function() { |
||||||
|
var EMPTY_PROMISE = {}; |
||||||
|
|
||||||
|
/** |
||||||
|
* If `data` is passed in this constructor, the promise is created resolved. |
||||||
|
* If there isn't data, it isn't resolved at the beginning. |
||||||
|
*/ |
||||||
|
function Promise(name, data) { |
||||||
|
this.name = name; |
||||||
|
// If you build a promise and pass in some data it's already resolved.
|
||||||
|
if (data != null) { |
||||||
|
this.isResolved = true; |
||||||
|
this._data = data; |
||||||
|
this.hasData = true; |
||||||
|
} else { |
||||||
|
this.isResolved = false; |
||||||
|
this._data = EMPTY_PROMISE; |
||||||
|
} |
||||||
|
this.callbacks = []; |
||||||
|
}; |
||||||
|
|
||||||
|
Promise.prototype = { |
||||||
|
hasData: false, |
||||||
|
|
||||||
|
set data(data) { |
||||||
|
if (data === undefined) { |
||||||
|
return; |
||||||
|
} |
||||||
|
if (this._data !== EMPTY_PROMISE) { |
||||||
|
throw 'Promise ' + this.name + |
||||||
|
': Cannot set the data of a promise twice'; |
||||||
|
} |
||||||
|
this._data = data; |
||||||
|
this.hasData = true; |
||||||
|
|
||||||
|
if (this.onDataCallback) { |
||||||
|
this.onDataCallback(data); |
||||||
|
} |
||||||
|
}, |
||||||
|
|
||||||
|
get data() { |
||||||
|
if (this._data === EMPTY_PROMISE) { |
||||||
|
throw 'Promise ' + this.name + ': Cannot get data that isn\'t set'; |
||||||
|
} |
||||||
|
return this._data; |
||||||
|
}, |
||||||
|
|
||||||
|
onData: function(callback) { |
||||||
|
if (this._data !== EMPTY_PROMISE) { |
||||||
|
callback(this._data); |
||||||
|
} else { |
||||||
|
this.onDataCallback = callback; |
||||||
|
} |
||||||
|
}, |
||||||
|
|
||||||
|
resolve: function(data) { |
||||||
|
if (this.isResolved) { |
||||||
|
throw 'A Promise can be resolved only once ' + this.name; |
||||||
|
} |
||||||
|
|
||||||
|
this.isResolved = true; |
||||||
|
this.data = data; |
||||||
|
var callbacks = this.callbacks; |
||||||
|
|
||||||
|
for (var i = 0; i < callbacks.length; i++) { |
||||||
|
callbacks[i].call(null, data); |
||||||
|
} |
||||||
|
}, |
||||||
|
|
||||||
|
then: function(callback) { |
||||||
|
if (!callback) { |
||||||
|
throw 'Requiring callback' + this.name; |
||||||
|
} |
||||||
|
|
||||||
|
// If the promise is already resolved, call the callback directly.
|
||||||
|
if (this.isResolved) { |
||||||
|
var data = this.data; |
||||||
|
callback.call(null, data); |
||||||
|
} else { |
||||||
|
this.callbacks.push(callback); |
||||||
|
} |
||||||
|
} |
||||||
|
}; |
||||||
|
return Promise; |
||||||
|
})(); |
Loading…
Reference in new issue