25 changed files with 7581 additions and 7588 deletions
@ -0,0 +1,837 @@
@@ -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 @@
@@ -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 @@
@@ -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 @@
@@ -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 @@
@@ -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 @@
@@ -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 @@
@@ -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 @@
@@ -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 @@
@@ -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 @@
@@ -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