Merge branch 'master' of git://github.com/mozilla/pdf.js.git into forms-1
Conflicts: web/viewer.css
This commit is contained in:
commit
dbf869f195
31 changed files with 4134 additions and 279 deletions
421
src/canvas.js
421
src/canvas.js
|
@ -6,6 +6,17 @@
|
|||
// <canvas> contexts store most of the state we need natively.
|
||||
// However, PDF needs a bit more state, which we store here.
|
||||
|
||||
var TextRenderingMode = {
|
||||
FILL: 0,
|
||||
STROKE: 1,
|
||||
FILL_STROKE: 2,
|
||||
INVISIBLE: 3,
|
||||
FILL_ADD_TO_PATH: 4,
|
||||
STROKE_ADD_TO_PATH: 5,
|
||||
FILL_STROKE_ADD_TO_PATH: 6,
|
||||
ADD_TO_PATH: 7
|
||||
};
|
||||
|
||||
var CanvasExtraState = (function canvasExtraState() {
|
||||
function constructor(old) {
|
||||
// Are soft masks and alpha values shapes or opacities?
|
||||
|
@ -23,6 +34,7 @@ var CanvasExtraState = (function canvasExtraState() {
|
|||
this.charSpacing = 0;
|
||||
this.wordSpacing = 0;
|
||||
this.textHScale = 1;
|
||||
this.textRenderingMode = TextRenderingMode.FILL;
|
||||
// Color spaces
|
||||
this.fillColorSpace = new DeviceGrayCS();
|
||||
this.fillColorSpaceObj = null;
|
||||
|
@ -59,16 +71,122 @@ function ScratchCanvas(width, height) {
|
|||
return canvas;
|
||||
}
|
||||
|
||||
function addContextCurrentTransform(ctx) {
|
||||
// If the context doesn't expose a `mozCurrentTransform`, add a JS based on.
|
||||
if (!ctx.mozCurrentTransform) {
|
||||
// Store the original context
|
||||
ctx._originalSave = ctx.save;
|
||||
ctx._originalRestore = ctx.restore;
|
||||
ctx._originalRotate = ctx.rotate;
|
||||
ctx._originalScale = ctx.scale;
|
||||
ctx._originalTranslate = ctx.translate;
|
||||
ctx._originalTransform = ctx.transform;
|
||||
|
||||
ctx._transformMatrix = [1, 0, 0, 1, 0, 0];
|
||||
ctx._transformStack = [];
|
||||
|
||||
Object.defineProperty(ctx, 'mozCurrentTransform', {
|
||||
get: function getCurrentTransform() {
|
||||
return this._transformMatrix;
|
||||
}
|
||||
});
|
||||
|
||||
Object.defineProperty(ctx, 'mozCurrentTransformInverse', {
|
||||
get: function getCurrentTransformInverse() {
|
||||
// Calculation done using WolframAlpha:
|
||||
// http://www.wolframalpha.com/input/?
|
||||
// i=Inverse+{{a%2C+c%2C+e}%2C+{b%2C+d%2C+f}%2C+{0%2C+0%2C+1}}
|
||||
|
||||
var m = this._transformMatrix;
|
||||
var a = m[0], b = m[1], c = m[2], d = m[3], e = m[4], f = m[5];
|
||||
|
||||
var ad_bc = a * d - b * c;
|
||||
var bc_ad = b * c - a * d;
|
||||
|
||||
return [
|
||||
d / ad_bc,
|
||||
b / bc_ad,
|
||||
c / bc_ad,
|
||||
a / ad_bc,
|
||||
(d * e - c * f) / bc_ad,
|
||||
(b * e - a * f) / ad_bc
|
||||
];
|
||||
}
|
||||
});
|
||||
|
||||
ctx.save = function ctxSave() {
|
||||
var old = this._transformMatrix;
|
||||
this._transformStack.push(old);
|
||||
this._transformMatrix = old.slice(0, 6);
|
||||
|
||||
this._originalSave();
|
||||
};
|
||||
|
||||
ctx.restore = function ctxRestore() {
|
||||
var prev = this._transformStack.pop();
|
||||
if (prev) {
|
||||
this._transformMatrix = prev;
|
||||
this._originalRestore();
|
||||
}
|
||||
};
|
||||
|
||||
ctx.translate = function ctxTranslate(x, y) {
|
||||
var m = this._transformMatrix;
|
||||
m[4] = m[0] * x + m[2] * y + m[4];
|
||||
m[5] = m[1] * x + m[3] * y + m[5];
|
||||
|
||||
this._originalTranslate(x, y);
|
||||
};
|
||||
|
||||
ctx.scale = function ctxScale(x, y) {
|
||||
var m = this._transformMatrix;
|
||||
m[0] = m[0] * x;
|
||||
m[1] = m[1] * x;
|
||||
m[2] = m[2] * y;
|
||||
m[3] = m[3] * y;
|
||||
|
||||
this._originalScale(x, y);
|
||||
};
|
||||
|
||||
ctx.transform = function ctxTransform(a, b, c, d, e, f) {
|
||||
var m = this._transformMatrix;
|
||||
this._transformMatrix = [
|
||||
m[0] * a + m[2] * b,
|
||||
m[1] * a + m[3] * b,
|
||||
m[0] * c + m[2] * d,
|
||||
m[1] * c + m[3] * d,
|
||||
m[0] * e + m[2] * f + m[4],
|
||||
m[1] * e + m[3] * f + m[5]
|
||||
];
|
||||
|
||||
ctx._originalTransform(a, b, c, d, e, f);
|
||||
};
|
||||
|
||||
ctx.rotate = function ctxRotate(angle) {
|
||||
var cosValue = Math.cos(angle);
|
||||
var sinValue = Math.sin(angle);
|
||||
|
||||
var m = this._transformMatrix;
|
||||
this._transformMatrix = [
|
||||
m[0] * cosValue + m[2] * sinValue,
|
||||
m[1] * cosValue + m[3] * sinValue,
|
||||
m[0] * (-sinValue) + m[2] * cosValue,
|
||||
m[1] * (-sinValue) + m[3] * cosValue,
|
||||
m[4],
|
||||
m[5]
|
||||
];
|
||||
|
||||
this._originalRotate(angle);
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
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) {
|
||||
function constructor(canvasCtx, objs, textLayer) {
|
||||
this.ctx = canvasCtx;
|
||||
this.current = new CanvasExtraState();
|
||||
this.stateStack = [];
|
||||
|
@ -77,6 +195,10 @@ var CanvasGraphics = (function canvasGraphics() {
|
|||
this.xobjs = null;
|
||||
this.ScratchCanvas = ScratchCanvas;
|
||||
this.objs = objs;
|
||||
this.textLayer = textLayer;
|
||||
if (canvasCtx) {
|
||||
addContextCurrentTransform(canvasCtx);
|
||||
}
|
||||
}
|
||||
|
||||
var LINE_CAP_STYLES = ['butt', 'round', 'square'];
|
||||
|
@ -85,6 +207,35 @@ var CanvasGraphics = (function canvasGraphics() {
|
|||
var EO_CLIP = {};
|
||||
|
||||
constructor.prototype = {
|
||||
slowCommands: {
|
||||
'stroke': true,
|
||||
'closeStroke': true,
|
||||
'fill': true,
|
||||
'eoFill': true,
|
||||
'fillStroke': true,
|
||||
'eoFillStroke': true,
|
||||
'closeFillStroke': true,
|
||||
'closeEOFillStroke': true,
|
||||
'showText': true,
|
||||
'showSpacedText': true,
|
||||
'setStrokeColorSpace': true,
|
||||
'setFillColorSpace': true,
|
||||
'setStrokeColor': true,
|
||||
'setStrokeColorN': true,
|
||||
'setFillColor': true,
|
||||
'setFillColorN_IR': true,
|
||||
'setStrokeGray': true,
|
||||
'setFillGray': true,
|
||||
'setStrokeRGBColor': true,
|
||||
'setFillRGBColor': true,
|
||||
'setStrokeCMYKColor': true,
|
||||
'setFillCMYKColor': true,
|
||||
'paintJpegXObject': true,
|
||||
'paintImageXObject': true,
|
||||
'paintImageMaskXObject': true,
|
||||
'shadingFill': true
|
||||
},
|
||||
|
||||
beginDrawing: function canvasGraphicsBeginDrawing(mediaBox) {
|
||||
var cw = this.ctx.canvas.width, ch = this.ctx.canvas.height;
|
||||
this.ctx.save();
|
||||
|
@ -102,7 +253,13 @@ var CanvasGraphics = (function canvasGraphics() {
|
|||
this.ctx.transform(0, -1, -1, 0, cw, ch);
|
||||
break;
|
||||
}
|
||||
// Scale so that canvas units are the same as PDF user space units
|
||||
this.ctx.scale(cw / mediaBox.width, ch / mediaBox.height);
|
||||
this.textDivs = [];
|
||||
this.textLayerQueue = [];
|
||||
// Prevent textLayerQueue from being rendered while rendering a new page
|
||||
if (this.textLayerTimer)
|
||||
clearTimeout(this.textLayerTimer);
|
||||
},
|
||||
|
||||
executeIRQueue: function canvasGraphicsExecuteIRQueue(codeIR,
|
||||
|
@ -112,32 +269,39 @@ var CanvasGraphics = (function canvasGraphics() {
|
|||
var i = executionStartIdx || 0;
|
||||
var argsArrayLen = argsArray.length;
|
||||
|
||||
// Sometimes the IRQueue to execute is empty.
|
||||
if (argsArrayLen == i) {
|
||||
return i;
|
||||
}
|
||||
|
||||
var executionEndIdx;
|
||||
var startTime = Date.now();
|
||||
var endTime = Date.now() + kExecutionTime;
|
||||
|
||||
var objs = this.objs;
|
||||
var fnName;
|
||||
var slowCommands = this.slowCommands;
|
||||
|
||||
do {
|
||||
executionEndIdx = Math.min(argsArrayLen, i + kExecutionTimeCheck);
|
||||
while (true) {
|
||||
fnName = fnArray[i];
|
||||
|
||||
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, nn = deps.length; n < nn; n++) {
|
||||
var depObjId = deps[n];
|
||||
if (fnName !== 'dependency') {
|
||||
this[fnName].apply(this, argsArray[i]);
|
||||
} else {
|
||||
var deps = argsArray[i];
|
||||
for (var n = 0, nn = deps.length; n < nn; 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 promise isn't resolved yet, add the continueCallback
|
||||
// to the promise and bail out.
|
||||
if (!objs.isResolved(depObjId)) {
|
||||
objs.get(depObjId, continueCallback);
|
||||
return i;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
i++;
|
||||
|
||||
// If the entire IRQueue was executed, stop as were done.
|
||||
if (i == argsArrayLen) {
|
||||
return i;
|
||||
|
@ -146,18 +310,49 @@ var CanvasGraphics = (function canvasGraphics() {
|
|||
// 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) {
|
||||
if (continueCallback && slowCommands[fnName] && Date.now() > endTime) {
|
||||
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();
|
||||
|
||||
var textLayer = this.textLayer;
|
||||
if (!textLayer)
|
||||
return;
|
||||
|
||||
var self = this;
|
||||
var renderTextLayer = function canvasRenderTextLayer() {
|
||||
var textDivs = self.textDivs;
|
||||
for (var i = 0, length = textDivs.length; i < length; ++i) {
|
||||
if (textDivs[i].dataset.textLength > 1) { // avoid div by zero
|
||||
textLayer.appendChild(textDivs[i]);
|
||||
// Adjust div width (via letterSpacing) to match canvas text
|
||||
// Due to the .offsetWidth calls, this is slow
|
||||
textDivs[i].style.letterSpacing =
|
||||
((textDivs[i].dataset.canvasWidth - textDivs[i].offsetWidth) /
|
||||
(textDivs[i].dataset.textLength - 1)) + 'px';
|
||||
}
|
||||
}
|
||||
}
|
||||
var textLayerQueue = this.textLayerQueue;
|
||||
textLayerQueue.push(renderTextLayer);
|
||||
|
||||
// Lazy textLayer rendering (to prevent UI hangs)
|
||||
// Only render queue if activity has stopped, where "no activity" ==
|
||||
// "no beginDrawing() calls in the last N ms"
|
||||
this.textLayerTimer = setTimeout(function renderTextLayerQueue() {
|
||||
// Render most recent (==most relevant) layers first
|
||||
for (var i = textLayerQueue.length - 1; i >= 0; i--) {
|
||||
textLayerQueue.pop().call();
|
||||
}
|
||||
}, 500);
|
||||
},
|
||||
|
||||
// Graphics state
|
||||
|
@ -394,7 +589,9 @@ var CanvasGraphics = (function canvasGraphics() {
|
|||
this.ctx.font = rule;
|
||||
},
|
||||
setTextRenderingMode: function canvasGraphicsSetTextRenderingMode(mode) {
|
||||
TODO('text rendering mode: ' + mode);
|
||||
if (mode >= TextRenderingMode.FILL_ADD_TO_PATH)
|
||||
TODO('unsupported text rendering mode: ' + mode);
|
||||
this.current.textRenderingMode = mode;
|
||||
},
|
||||
setTextRise: function canvasGraphicsSetTextRise(rise) {
|
||||
TODO('text rise: ' + rise);
|
||||
|
@ -416,23 +613,94 @@ var CanvasGraphics = (function canvasGraphics() {
|
|||
nextLine: function canvasGraphicsNextLine() {
|
||||
this.moveText(0, this.current.leading);
|
||||
},
|
||||
showText: function canvasGraphicsShowText(text) {
|
||||
applyTextTransforms: function canvasApplyTransforms() {
|
||||
var ctx = this.ctx;
|
||||
var current = this.current;
|
||||
var textHScale = current.textHScale;
|
||||
var fontMatrix = current.font.fontMatrix || IDENTITY_MATRIX;
|
||||
|
||||
ctx.transform.apply(ctx, current.textMatrix);
|
||||
ctx.scale(1, -1);
|
||||
ctx.translate(current.x, -1 * current.y);
|
||||
ctx.transform.apply(ctx, fontMatrix);
|
||||
ctx.scale(textHScale, 1);
|
||||
},
|
||||
getTextGeometry: function canvasGetTextGeometry() {
|
||||
var geometry = {};
|
||||
var ctx = this.ctx;
|
||||
var font = this.current.font;
|
||||
var ctxMatrix = ctx.mozCurrentTransform;
|
||||
if (ctxMatrix) {
|
||||
var bl = Util.applyTransform([0, 0], ctxMatrix);
|
||||
var tr = Util.applyTransform([1, 1], ctxMatrix);
|
||||
geometry.x = bl[0];
|
||||
geometry.y = bl[1];
|
||||
geometry.hScale = tr[0] - bl[0];
|
||||
geometry.vScale = tr[1] - bl[1];
|
||||
}
|
||||
var spaceGlyph = font.charsToGlyphs(' ');
|
||||
|
||||
// Hack (sometimes space is not encoded)
|
||||
if (spaceGlyph.length === 0 || spaceGlyph[0].width === 0)
|
||||
spaceGlyph = font.charsToGlyphs('i');
|
||||
|
||||
// Fallback
|
||||
if (spaceGlyph.length === 0 || spaceGlyph[0].width === 0)
|
||||
spaceGlyph = [{width: 0}];
|
||||
|
||||
geometry.spaceWidth = spaceGlyph[0].width;
|
||||
return geometry;
|
||||
},
|
||||
|
||||
pushTextDivs: function canvasGraphicsPushTextDivs(text) {
|
||||
var div = document.createElement('div');
|
||||
var fontSize = this.current.fontSize;
|
||||
|
||||
// vScale and hScale already contain the scaling to pixel units
|
||||
// as mozCurrentTransform reflects ctx.scale() changes
|
||||
// (see beginDrawing())
|
||||
var fontHeight = fontSize * text.geom.vScale;
|
||||
div.dataset.canvasWidth = text.canvasWidth * text.geom.hScale;
|
||||
|
||||
div.style.fontSize = fontHeight + 'px';
|
||||
div.style.fontFamily = this.current.font.loadedName || 'sans-serif';
|
||||
div.style.left = text.geom.x + 'px';
|
||||
div.style.top = (text.geom.y - fontHeight) + 'px';
|
||||
div.innerHTML = text.str;
|
||||
div.dataset.textLength = text.length;
|
||||
this.textDivs.push(div);
|
||||
},
|
||||
showText: function canvasGraphicsShowText(str, skipTextSelection) {
|
||||
var ctx = this.ctx;
|
||||
var current = this.current;
|
||||
var font = current.font;
|
||||
var glyphs = font.charsToGlyphs(text);
|
||||
var glyphs = font.charsToGlyphs(str);
|
||||
var fontSize = current.fontSize;
|
||||
var charSpacing = current.charSpacing;
|
||||
var wordSpacing = current.wordSpacing;
|
||||
var textHScale = current.textHScale;
|
||||
var fontMatrix = font.fontMatrix || IDENTITY_MATRIX;
|
||||
var textHScale2 = textHScale * fontMatrix[0];
|
||||
var glyphsLength = glyphs.length;
|
||||
var textLayer = this.textLayer;
|
||||
var text = {str: '', length: 0, canvasWidth: 0, geom: {}};
|
||||
var textSelection = textLayer && !skipTextSelection ? true : false;
|
||||
var textRenderingMode = current.textRenderingMode;
|
||||
|
||||
if (textSelection) {
|
||||
ctx.save();
|
||||
this.applyTextTransforms();
|
||||
text.geom = this.getTextGeometry();
|
||||
ctx.restore();
|
||||
}
|
||||
|
||||
// Type3 fonts - each glyph is a "mini-PDF"
|
||||
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);
|
||||
ctx.scale(textHScale, 1);
|
||||
for (var i = 0; i < glyphsLength; ++i) {
|
||||
|
||||
var glyph = glyphs[i];
|
||||
|
@ -452,18 +720,16 @@ var CanvasGraphics = (function canvasGraphics() {
|
|||
var width = transformed[0] * fontSize + charSpacing;
|
||||
|
||||
ctx.translate(width, 0);
|
||||
current.x += width;
|
||||
current.x += width * textHScale2;
|
||||
|
||||
text.str += glyph.unicode;
|
||||
text.length++;
|
||||
text.canvasWidth += 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);
|
||||
this.applyTextTransforms();
|
||||
|
||||
var width = 0;
|
||||
for (var i = 0; i < glyphsLength; ++i) {
|
||||
|
@ -474,36 +740,97 @@ var CanvasGraphics = (function canvasGraphics() {
|
|||
continue;
|
||||
}
|
||||
|
||||
var unicode = glyph.unicode;
|
||||
var char = (unicode >= 0x10000) ?
|
||||
String.fromCharCode(0xD800 | ((unicode - 0x10000) >> 10),
|
||||
0xDC00 | (unicode & 0x3FF)) : String.fromCharCode(unicode);
|
||||
var char = glyph.fontChar;
|
||||
var charWidth = glyph.width * fontSize * 0.001 + charSpacing;
|
||||
|
||||
ctx.fillText(char, width, 0);
|
||||
width += glyph.width * fontSize * 0.001 + charSpacing;
|
||||
switch (textRenderingMode) {
|
||||
default: // other unsupported rendering modes
|
||||
case TextRenderingMode.FILL:
|
||||
case TextRenderingMode.FILL_ADD_TO_PATH:
|
||||
ctx.fillText(char, width, 0);
|
||||
break;
|
||||
case TextRenderingMode.STROKE:
|
||||
case TextRenderingMode.STROKE_ADD_TO_PATH:
|
||||
ctx.strokeText(char, width, 0);
|
||||
break;
|
||||
case TextRenderingMode.FILL_STROKE:
|
||||
case TextRenderingMode.FILL_STROKE_ADD_TO_PATH:
|
||||
ctx.fillText(char, width, 0);
|
||||
ctx.strokeText(char, width, 0);
|
||||
break;
|
||||
case TextRenderingMode.INVISIBLE:
|
||||
break;
|
||||
}
|
||||
|
||||
width += charWidth;
|
||||
|
||||
text.str += glyph.unicode === ' ' ? ' ' : glyph.unicode;
|
||||
text.length++;
|
||||
text.canvasWidth += charWidth;
|
||||
}
|
||||
current.x += width;
|
||||
|
||||
current.x += width * textHScale2;
|
||||
ctx.restore();
|
||||
}
|
||||
},
|
||||
|
||||
if (textSelection)
|
||||
this.pushTextDivs(text);
|
||||
|
||||
return text;
|
||||
},
|
||||
showSpacedText: function canvasGraphicsShowSpacedText(arr) {
|
||||
var ctx = this.ctx;
|
||||
var current = this.current;
|
||||
var fontSize = current.fontSize;
|
||||
var textHScale = current.textHScale;
|
||||
var textHScale2 = current.textHScale *
|
||||
(current.font.fontMatrix || IDENTITY_MATRIX)[0];
|
||||
var arrLength = arr.length;
|
||||
var textLayer = this.textLayer;
|
||||
var font = current.font;
|
||||
var text = {str: '', length: 0, canvasWidth: 0, geom: {}};
|
||||
var textSelection = textLayer ? true : false;
|
||||
|
||||
if (textSelection) {
|
||||
ctx.save();
|
||||
this.applyTextTransforms();
|
||||
text.geom = this.getTextGeometry();
|
||||
ctx.restore();
|
||||
}
|
||||
|
||||
for (var i = 0; i < arrLength; ++i) {
|
||||
var e = arr[i];
|
||||
if (isNum(e)) {
|
||||
current.x -= e * 0.001 * fontSize * textHScale;
|
||||
var spacingLength = -e * 0.001 * fontSize * textHScale2;
|
||||
current.x += spacingLength;
|
||||
|
||||
if (textSelection) {
|
||||
// Emulate precise spacing via HTML spaces
|
||||
text.canvasWidth += spacingLength;
|
||||
if (e < 0 && text.geom.spaceWidth > 0) { // avoid div by zero
|
||||
var numFakeSpaces = Math.round(-e / text.geom.spaceWidth);
|
||||
for (var j = 0; j < numFakeSpaces; ++j)
|
||||
text.str += ' ';
|
||||
text.length += numFakeSpaces > 0 ? 1 : 0;
|
||||
}
|
||||
}
|
||||
} else if (isString(e)) {
|
||||
this.showText(e);
|
||||
var shownText = this.showText(e, true);
|
||||
|
||||
if (textSelection) {
|
||||
if (shownText.str === ' ') {
|
||||
text.str += ' ';
|
||||
} else {
|
||||
text.str += shownText.str;
|
||||
}
|
||||
text.canvasWidth += shownText.canvasWidth;
|
||||
text.length += e.length;
|
||||
}
|
||||
} else {
|
||||
malformed('TJ array element ' + e + ' is not string or num');
|
||||
}
|
||||
}
|
||||
|
||||
if (textSelection)
|
||||
this.pushTextDivs(text);
|
||||
},
|
||||
nextLineShowText: function canvasGraphicsNextLineShowText(text) {
|
||||
this.nextLine();
|
||||
|
|
54
src/core.js
54
src/core.js
|
@ -39,11 +39,15 @@ function getPdf(arg, callback) {
|
|||
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.onreadystatechange = function getPdfOnreadystatechange(e) {
|
||||
if (xhr.readyState === 4) {
|
||||
if (xhr.status === xhr.expected) {
|
||||
var data = (xhr.mozResponseArrayBuffer || xhr.mozResponse ||
|
||||
xhr.responseArrayBuffer || xhr.response);
|
||||
callback(data);
|
||||
} else if (params.error) {
|
||||
params.error(e);
|
||||
}
|
||||
}
|
||||
};
|
||||
xhr.send(null);
|
||||
|
@ -63,6 +67,9 @@ var Page = (function pagePage() {
|
|||
};
|
||||
this.xref = xref;
|
||||
this.ref = ref;
|
||||
|
||||
this.ctx = null;
|
||||
this.callback = null;
|
||||
}
|
||||
|
||||
constructor.prototype = {
|
||||
|
@ -156,7 +163,7 @@ var Page = (function pagePage() {
|
|||
IRQueue, fonts) {
|
||||
var self = this;
|
||||
this.IRQueue = IRQueue;
|
||||
var gfx = new CanvasGraphics(this.ctx, this.objs);
|
||||
var gfx = new CanvasGraphics(this.ctx, this.objs, this.textLayer);
|
||||
|
||||
var displayContinuation = function pageDisplayContinuation() {
|
||||
// Always defer call to display() to work around bug in
|
||||
|
@ -165,8 +172,10 @@ var Page = (function pagePage() {
|
|||
try {
|
||||
self.display(gfx, self.callback);
|
||||
} catch (e) {
|
||||
if (self.callback) self.callback(e.toString());
|
||||
throw e;
|
||||
if (self.callback)
|
||||
self.callback(e);
|
||||
else
|
||||
throw e;
|
||||
}
|
||||
});
|
||||
};
|
||||
|
@ -241,6 +250,7 @@ var Page = (function pagePage() {
|
|||
startIdx = gfx.executeIRQueue(IRQueue, startIdx, next);
|
||||
if (startIdx == length) {
|
||||
self.stats.render = Date.now();
|
||||
gfx.endDrawing();
|
||||
if (callback) callback();
|
||||
}
|
||||
}
|
||||
|
@ -372,9 +382,10 @@ var Page = (function pagePage() {
|
|||
}
|
||||
return items;
|
||||
},
|
||||
startRendering: function pageStartRendering(ctx, callback) {
|
||||
startRendering: function pageStartRendering(ctx, callback, textLayer) {
|
||||
this.ctx = ctx;
|
||||
this.callback = callback;
|
||||
this.textLayer = textLayer;
|
||||
|
||||
this.startRenderingTime = Date.now();
|
||||
this.pdf.startRendering(this);
|
||||
|
@ -620,7 +631,7 @@ var PDFDoc = (function pdfDoc() {
|
|||
switch (type) {
|
||||
case 'JpegStream':
|
||||
var IR = data[2];
|
||||
new JpegImage(id, IR, this.objs);
|
||||
new JpegImageLoader(id, IR, this.objs);
|
||||
break;
|
||||
case 'Font':
|
||||
var name = data[2];
|
||||
|
@ -628,20 +639,9 @@ var PDFDoc = (function pdfDoc() {
|
|||
var properties = data[4];
|
||||
|
||||
if (file) {
|
||||
// Rewrap the ArrayBuffer in a stream.
|
||||
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;
|
||||
}
|
||||
file = new Stream(file, 0, file.length, fontFileDict);
|
||||
}
|
||||
|
||||
// For now, resolve the font object here direclty. The real font
|
||||
|
@ -669,6 +669,14 @@ var PDFDoc = (function pdfDoc() {
|
|||
}
|
||||
}.bind(this));
|
||||
|
||||
messageHandler.on('page_error', function pdfDocError(data) {
|
||||
var page = this.pageCache[data.pageNum];
|
||||
if (page.callback)
|
||||
page.callback(data.error);
|
||||
else
|
||||
throw data.error;
|
||||
}, this);
|
||||
|
||||
setTimeout(function pdfDocFontReadySetTimeout() {
|
||||
messageHandler.send('doc', this.data);
|
||||
this.workerReadyPromise.resolve(true);
|
||||
|
|
|
@ -155,6 +155,11 @@ var PartialEvaluator = (function partialEvaluator() {
|
|||
font.loadedName = loadedName;
|
||||
|
||||
var translated = font.translated;
|
||||
// Convert the file to an ArrayBuffer which will be turned back into
|
||||
// a Stream in the main thread.
|
||||
if (translated.file)
|
||||
translated.file = translated.file.getBytes();
|
||||
|
||||
handler.send('obj', [
|
||||
loadedName,
|
||||
'Font',
|
||||
|
@ -179,7 +184,7 @@ var PartialEvaluator = (function partialEvaluator() {
|
|||
var w = dict.get('Width', 'W');
|
||||
var h = dict.get('Height', 'H');
|
||||
|
||||
if (image instanceof JpegStream) {
|
||||
if (image instanceof JpegStream && image.isNative) {
|
||||
var objId = 'img_' + uniquePrefix + (++self.objIdCounter);
|
||||
handler.send('obj', [objId, 'JpegStream', image.getIR()]);
|
||||
|
||||
|
@ -493,6 +498,8 @@ var PartialEvaluator = (function partialEvaluator() {
|
|||
var baseName = encoding.get('BaseEncoding');
|
||||
if (baseName)
|
||||
baseEncoding = Encodings[baseName.name];
|
||||
else
|
||||
hasEncoding = false; // base encoding was not provided
|
||||
|
||||
// Load the differences between the base and original
|
||||
if (encoding.has('Differences')) {
|
||||
|
@ -512,6 +519,7 @@ var PartialEvaluator = (function partialEvaluator() {
|
|||
error('Encoding is not a Name nor a Dict');
|
||||
}
|
||||
}
|
||||
|
||||
properties.differences = differences;
|
||||
properties.baseEncoding = baseEncoding;
|
||||
properties.hasEncoding = hasEncoding;
|
||||
|
@ -554,9 +562,21 @@ var PartialEvaluator = (function partialEvaluator() {
|
|||
var startRange = tokens[j];
|
||||
var endRange = tokens[j + 1];
|
||||
var code = tokens[j + 2];
|
||||
while (startRange <= endRange) {
|
||||
charToUnicode[startRange] = code++;
|
||||
++startRange;
|
||||
if (code == 0xFFFF) {
|
||||
// CMap is broken, assuming code == startRange
|
||||
code = startRange;
|
||||
}
|
||||
if (isArray(code)) {
|
||||
var codeindex = 0;
|
||||
while (startRange <= endRange) {
|
||||
charToUnicode[startRange] = code[codeindex++];
|
||||
++startRange;
|
||||
}
|
||||
} else {
|
||||
while (startRange <= endRange) {
|
||||
charToUnicode[startRange] = code++;
|
||||
++startRange;
|
||||
}
|
||||
}
|
||||
}
|
||||
break;
|
||||
|
@ -595,9 +615,18 @@ var PartialEvaluator = (function partialEvaluator() {
|
|||
}
|
||||
} else if (byte == 0x3E) {
|
||||
if (token.length) {
|
||||
// parsing hex number
|
||||
tokens.push(parseInt(token, 16));
|
||||
token = '';
|
||||
if (token.length <= 4) {
|
||||
// parsing hex number
|
||||
tokens.push(parseInt(token, 16));
|
||||
token = '';
|
||||
} else {
|
||||
// parsing hex UTF-16BE numbers
|
||||
var str = [];
|
||||
for (var i = 0, ii = token.length; i < ii; i += 4)
|
||||
str.push(parseInt(token.substr(i, 4), 16));
|
||||
tokens.push(String.fromCharCode.apply(String, str));
|
||||
token = '';
|
||||
}
|
||||
}
|
||||
} else {
|
||||
token += String.fromCharCode(byte);
|
||||
|
|
327
src/fonts.js
327
src/fonts.js
|
@ -719,20 +719,10 @@ function getUnicodeRangeFor(value) {
|
|||
return -1;
|
||||
}
|
||||
|
||||
function adaptUnicode(unicode) {
|
||||
return (unicode <= 0x1F || (unicode >= 127 && unicode < kSizeOfGlyphArea)) ?
|
||||
unicode + kCmapGlyphOffset : unicode;
|
||||
}
|
||||
|
||||
function isAdaptedUnicode(unicode) {
|
||||
return unicode >= kCmapGlyphOffset &&
|
||||
unicode < kCmapGlyphOffset + kSizeOfGlyphArea;
|
||||
}
|
||||
|
||||
function isSpecialUnicode(unicode) {
|
||||
return (unicode <= 0x1F || (unicode >= 127 && unicode < kSizeOfGlyphArea)) ||
|
||||
unicode >= kCmapGlyphOffset &&
|
||||
unicode < kCmapGlyphOffset + kSizeOfGlyphArea;
|
||||
(unicode >= kCmapGlyphOffset &&
|
||||
unicode < kCmapGlyphOffset + kSizeOfGlyphArea);
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -771,16 +761,21 @@ var Font = (function Font() {
|
|||
this.widths = properties.widths;
|
||||
this.defaultWidth = properties.defaultWidth;
|
||||
this.composite = properties.composite;
|
||||
this.toUnicode = properties.toUnicode;
|
||||
this.hasEncoding = properties.hasEncoding;
|
||||
|
||||
this.fontMatrix = properties.fontMatrix;
|
||||
this.widthMultiplier = 1.0;
|
||||
if (properties.type == 'Type3')
|
||||
return;
|
||||
|
||||
// Trying to fix encoding using glyph CIDSystemInfo.
|
||||
this.loadCidToUnicode(properties);
|
||||
|
||||
if (properties.toUnicode)
|
||||
this.toUnicode = properties.toUnicode;
|
||||
else
|
||||
this.rebuildToUnicode(properties);
|
||||
|
||||
if (!file) {
|
||||
// The file data is not specified. Trying to fix the font name
|
||||
// to be used with the canvas.font.
|
||||
|
@ -832,6 +827,8 @@ var Font = (function Font() {
|
|||
|
||||
this.data = data;
|
||||
this.fontMatrix = properties.fontMatrix;
|
||||
this.widthMultiplier = !properties.fontMatrix ? 1.0 :
|
||||
1.0 / properties.fontMatrix[0];
|
||||
this.encoding = properties.baseEncoding;
|
||||
this.hasShortCmap = properties.hasShortCmap;
|
||||
this.loadedName = getUniqueName();
|
||||
|
@ -961,15 +958,15 @@ var Font = (function Font() {
|
|||
var ranges = [];
|
||||
for (var n = 0; n < length; ) {
|
||||
var start = codes[n].unicode;
|
||||
var startCode = codes[n].code;
|
||||
var codeIndices = [codes[n].code];
|
||||
++n;
|
||||
var end = start;
|
||||
while (n < length && end + 1 == codes[n].unicode) {
|
||||
codeIndices.push(codes[n].code);
|
||||
++end;
|
||||
++n;
|
||||
}
|
||||
var endCode = codes[n - 1].code;
|
||||
ranges.push([start, end, startCode, endCode]);
|
||||
ranges.push([start, end, codeIndices]);
|
||||
}
|
||||
|
||||
return ranges;
|
||||
|
@ -1012,17 +1009,16 @@ var Font = (function Font() {
|
|||
idDeltas += string16(0);
|
||||
idRangeOffsets += string16(offset);
|
||||
|
||||
var startCode = range[2];
|
||||
var endCode = range[3];
|
||||
for (var j = startCode; j <= endCode; ++j)
|
||||
glyphsIds += string16(deltas[j]);
|
||||
var codes = range[2];
|
||||
for (var j = 0, jj = codes.length; j < jj; ++j)
|
||||
glyphsIds += string16(deltas[codes[j]]);
|
||||
}
|
||||
} else {
|
||||
for (var i = 0; i < segCount - 1; i++) {
|
||||
var range = ranges[i];
|
||||
var start = range[0];
|
||||
var end = range[1];
|
||||
var startCode = range[2];
|
||||
var startCode = range[2][0];
|
||||
|
||||
startCount += string16(start);
|
||||
endCount += string16(end);
|
||||
|
@ -1299,7 +1295,7 @@ var Font = (function Font() {
|
|||
properties.baseEncoding = encoding;
|
||||
}
|
||||
|
||||
function replaceCMapTable(cmap, font, properties) {
|
||||
function readCMapTable(cmap, font) {
|
||||
var start = (font.start ? font.start : 0) + cmap.offset;
|
||||
font.pos = start;
|
||||
|
||||
|
@ -1316,7 +1312,7 @@ var Font = (function Font() {
|
|||
}
|
||||
|
||||
// Check that table are sorted by platformID then encodingID,
|
||||
records.sort(function fontReplaceCMapTableSort(a, b) {
|
||||
records.sort(function fontReadCMapTableSort(a, b) {
|
||||
return ((a.platformID << 16) + a.encodingID) -
|
||||
((b.platformID << 16) + b.encodingID);
|
||||
});
|
||||
|
@ -1371,16 +1367,15 @@ var Font = (function Font() {
|
|||
for (var j = 0; j < 256; j++) {
|
||||
var index = font.getByte();
|
||||
if (index) {
|
||||
var unicode = adaptUnicode(j);
|
||||
glyphs.push({ unicode: unicode, code: j });
|
||||
glyphs.push({ unicode: j, code: j });
|
||||
ids.push(index);
|
||||
}
|
||||
}
|
||||
|
||||
properties.hasShortCmap = true;
|
||||
|
||||
createGlyphNameMap(glyphs, ids, properties);
|
||||
return cmap.data = createCMapTable(glyphs, ids);
|
||||
return {
|
||||
glyphs: glyphs,
|
||||
ids: ids,
|
||||
hasShortCmap: true
|
||||
};
|
||||
} else if (format == 4) {
|
||||
// re-creating the table in format 4 since the encoding
|
||||
// might be changed
|
||||
|
@ -1432,17 +1427,18 @@ var Font = (function Font() {
|
|||
var glyphCode = offsetIndex < 0 ? j :
|
||||
offsets[offsetIndex + j - start];
|
||||
glyphCode = (glyphCode + delta) & 0xFFFF;
|
||||
if (glyphCode == 0 || isAdaptedUnicode(j))
|
||||
if (glyphCode == 0)
|
||||
continue;
|
||||
|
||||
var unicode = adaptUnicode(j);
|
||||
glyphs.push({ unicode: unicode, code: j });
|
||||
glyphs.push({ unicode: j, code: j });
|
||||
ids.push(glyphCode);
|
||||
}
|
||||
}
|
||||
|
||||
createGlyphNameMap(glyphs, ids, properties);
|
||||
return cmap.data = createCMapTable(glyphs, ids);
|
||||
return {
|
||||
glyphs: glyphs,
|
||||
ids: ids
|
||||
};
|
||||
} else if (format == 6) {
|
||||
// Format 6 is a 2-bytes dense mapping, which means the font data
|
||||
// lives glue together even if they are pretty far in the unicode
|
||||
|
@ -1457,19 +1453,18 @@ var Font = (function Font() {
|
|||
for (var j = 0; j < entryCount; j++) {
|
||||
var glyphCode = int16(font.getBytes(2));
|
||||
var code = firstCode + j;
|
||||
if (isAdaptedUnicode(glyphCode))
|
||||
continue;
|
||||
|
||||
var unicode = adaptUnicode(code);
|
||||
glyphs.push({ unicode: unicode, code: code });
|
||||
glyphs.push({ unicode: code, code: code });
|
||||
ids.push(glyphCode);
|
||||
}
|
||||
|
||||
createGlyphNameMap(glyphs, ids, properties);
|
||||
return cmap.data = createCMapTable(glyphs, ids);
|
||||
return {
|
||||
glyphs: glyphs,
|
||||
ids: ids
|
||||
};
|
||||
}
|
||||
}
|
||||
return cmap.data;
|
||||
error('Unsupported cmap table format');
|
||||
};
|
||||
|
||||
function sanitizeMetrics(font, header, metrics, numGlyphs) {
|
||||
|
@ -1708,17 +1703,85 @@ var Font = (function Font() {
|
|||
tables.push(cmap);
|
||||
}
|
||||
|
||||
var glyphs = [];
|
||||
for (i = 1; i < numGlyphs; i++) {
|
||||
if (isAdaptedUnicode(i))
|
||||
continue;
|
||||
|
||||
glyphs.push({ unicode: adaptUnicode(i) });
|
||||
var cidToGidMap = properties.cidToGidMap || [];
|
||||
var gidToCidMap = [0];
|
||||
if (cidToGidMap.length > 0) {
|
||||
for (var j = cidToGidMap.length - 1; j >= 0; j--) {
|
||||
var gid = cidToGidMap[j];
|
||||
if (gid)
|
||||
gidToCidMap[gid] = j;
|
||||
}
|
||||
// filling the gaps using CID above the CIDs currently used in font
|
||||
var nextCid = cidToGidMap.length;
|
||||
for (var i = 1; i < numGlyphs; i++) {
|
||||
if (!gidToCidMap[i])
|
||||
gidToCidMap[i] = nextCid++;
|
||||
}
|
||||
}
|
||||
cmap.data = createCMapTable(glyphs);
|
||||
|
||||
var glyphs = [], ids = [];
|
||||
var usedUnicodes = [];
|
||||
var unassignedUnicodeItems = [];
|
||||
for (var i = 1; i < numGlyphs; i++) {
|
||||
var cid = gidToCidMap[i] || i;
|
||||
var unicode = this.toUnicode[cid];
|
||||
if (!unicode || isSpecialUnicode(unicode) ||
|
||||
unicode in usedUnicodes) {
|
||||
unassignedUnicodeItems.push(i);
|
||||
continue;
|
||||
}
|
||||
usedUnicodes[unicode] = true;
|
||||
glyphs.push({ unicode: unicode, code: cid });
|
||||
ids.push(i);
|
||||
}
|
||||
// trying to fit as many unassigned symbols as we can
|
||||
// in the range allocated for the user defined symbols
|
||||
var unusedUnicode = kCmapGlyphOffset;
|
||||
for (var j = 0, jj = unassignedUnicodeItems.length; j < jj; j++) {
|
||||
var i = unassignedUnicodeItems[j];
|
||||
var cid = gidToCidMap[i] || i;
|
||||
while (unusedUnicode in usedUnicodes)
|
||||
unusedUnicode++;
|
||||
if (unusedUnicode >= kCmapGlyphOffset + kSizeOfGlyphArea)
|
||||
break;
|
||||
var unicode = unusedUnicode++;
|
||||
this.toUnicode[cid] = unicode;
|
||||
usedUnicodes[unicode] = true;
|
||||
glyphs.push({ unicode: unicode, code: cid });
|
||||
ids.push(i);
|
||||
}
|
||||
cmap.data = createCMapTable(glyphs, ids);
|
||||
} else {
|
||||
replaceCMapTable(cmap, font, properties);
|
||||
var cmapTable = readCMapTable(cmap, font);
|
||||
var glyphs = cmapTable.glyphs;
|
||||
var ids = cmapTable.ids;
|
||||
var hasShortCmap = !!cmapTable.hasShortCmap;
|
||||
var toUnicode = this.toUnicode;
|
||||
|
||||
if (hasShortCmap && toUnicode) {
|
||||
// checking if cmap is just identity map
|
||||
var isIdentity = true;
|
||||
for (var i = 0, ii = glyphs.length; i < ii; i++) {
|
||||
if (glyphs[i].unicode != i + 1) {
|
||||
isIdentity = false;
|
||||
break;
|
||||
}
|
||||
}
|
||||
// if it is, replacing with meaningful toUnicode values
|
||||
if (isIdentity) {
|
||||
for (var i = 0, ii = glyphs.length; i < ii; i++) {
|
||||
var unicode = toUnicode[i + 1] || i + 1;
|
||||
glyphs[i].unicode = unicode;
|
||||
}
|
||||
this.useToUnicode = true;
|
||||
}
|
||||
}
|
||||
properties.hasShortCmap = hasShortCmap;
|
||||
|
||||
createGlyphNameMap(glyphs, ids, properties);
|
||||
this.glyphNameMap = properties.glyphNameMap;
|
||||
|
||||
cmap.data = createCMapTable(glyphs, ids);
|
||||
}
|
||||
|
||||
// Rewrite the 'post' table if needed
|
||||
|
@ -1808,6 +1871,14 @@ var Font = (function Font() {
|
|||
}
|
||||
properties.baseEncoding = encoding;
|
||||
}
|
||||
if (properties.subtype == 'CIDFontType0C') {
|
||||
var toUnicode = [];
|
||||
for (var i = 0; i < charstrings.length; ++i) {
|
||||
var charstring = charstrings[i];
|
||||
toUnicode[charstring.code] = charstring.unicode;
|
||||
}
|
||||
this.toUnicode = toUnicode;
|
||||
}
|
||||
|
||||
var fields = {
|
||||
// PostScript Font Program
|
||||
|
@ -1868,8 +1939,11 @@ var Font = (function Font() {
|
|||
// Horizontal metrics
|
||||
'hmtx': (function fontFieldsHmtx() {
|
||||
var hmtx = '\x00\x00\x00\x00'; // Fake .notdef
|
||||
for (var i = 0, ii = charstrings.length; i < ii; i++)
|
||||
hmtx += string16(charstrings[i].width) + string16(0);
|
||||
for (var i = 0, ii = charstrings.length; i < ii; i++) {
|
||||
var charstring = charstrings[i];
|
||||
var width = 'width' in charstring ? charstring.width : 0;
|
||||
hmtx += string16(width) + string16(0);
|
||||
}
|
||||
return stringToArray(hmtx);
|
||||
})(),
|
||||
|
||||
|
@ -1898,17 +1972,35 @@ var Font = (function Font() {
|
|||
return stringToArray(otf.file);
|
||||
},
|
||||
|
||||
loadCidToUnicode: function font_loadCidToUnicode(properties) {
|
||||
if (properties.cidToGidMap) {
|
||||
this.cidToUnicode = properties.cidToGidMap;
|
||||
return;
|
||||
rebuildToUnicode: function font_rebuildToUnicode(properties) {
|
||||
var firstChar = properties.firstChar, lastChar = properties.lastChar;
|
||||
var map = [];
|
||||
if (properties.composite) {
|
||||
var isIdentityMap = this.cidToUnicode.length == 0;
|
||||
for (var i = firstChar, ii = lastChar; i <= ii; i++) {
|
||||
// TODO missing map the character according font's CMap
|
||||
var cid = i;
|
||||
map[i] = isIdentityMap ? cid : this.cidToUnicode[cid];
|
||||
}
|
||||
} else {
|
||||
for (var i = firstChar, ii = lastChar; i <= ii; i++) {
|
||||
var glyph = properties.differences[i];
|
||||
if (!glyph)
|
||||
glyph = properties.baseEncoding[i];
|
||||
if (!!glyph && (glyph in GlyphsUnicode))
|
||||
map[i] = GlyphsUnicode[glyph];
|
||||
}
|
||||
}
|
||||
this.toUnicode = map;
|
||||
},
|
||||
|
||||
loadCidToUnicode: function font_loadCidToUnicode(properties) {
|
||||
if (!properties.cidSystemInfo)
|
||||
return;
|
||||
|
||||
var cidToUnicodeMap = [];
|
||||
var cidToUnicodeMap = [], unicodeToCIDMap = [];
|
||||
this.cidToUnicode = cidToUnicodeMap;
|
||||
this.unicodeToCID = unicodeToCIDMap;
|
||||
|
||||
var cidSystemInfo = properties.cidSystemInfo;
|
||||
var cidToUnicode;
|
||||
|
@ -1920,28 +2012,34 @@ var Font = (function Font() {
|
|||
if (!cidToUnicode)
|
||||
return; // identity encoding
|
||||
|
||||
var glyph = 1, i, j, k, ii;
|
||||
var cid = 1, i, j, k, ii;
|
||||
for (i = 0, ii = cidToUnicode.length; i < ii; ++i) {
|
||||
var unicode = cidToUnicode[i];
|
||||
if (isArray(unicode)) {
|
||||
var length = unicode.length;
|
||||
for (j = 0; j < length; j++)
|
||||
cidToUnicodeMap[unicode[j]] = glyph;
|
||||
glyph++;
|
||||
for (j = 0; j < length; j++) {
|
||||
cidToUnicodeMap[cid] = unicode[j];
|
||||
unicodeToCIDMap[unicode[j]] = cid;
|
||||
}
|
||||
cid++;
|
||||
} else if (typeof unicode === 'object') {
|
||||
var fillLength = unicode.f;
|
||||
if (fillLength) {
|
||||
k = unicode.c;
|
||||
for (j = 0; j < fillLength; ++j) {
|
||||
cidToUnicodeMap[k] = glyph++;
|
||||
cidToUnicodeMap[cid] = k;
|
||||
unicodeToCIDMap[k] = cid;
|
||||
cid++;
|
||||
k++;
|
||||
}
|
||||
} else
|
||||
glyph += unicode.s;
|
||||
cid += unicode.s;
|
||||
} else if (unicode) {
|
||||
cidToUnicodeMap[unicode] = glyph++;
|
||||
cidToUnicodeMap[cid] = unicode;
|
||||
unicodeToCIDMap[unicode] = cid;
|
||||
cid++;
|
||||
} else
|
||||
glyph++;
|
||||
cid++;
|
||||
}
|
||||
},
|
||||
|
||||
|
@ -1981,19 +2079,19 @@ var Font = (function Font() {
|
|||
switch (this.type) {
|
||||
case 'CIDFontType0':
|
||||
if (this.noUnicodeAdaptation) {
|
||||
width = this.widths[this.cidToUnicode[charcode]];
|
||||
width = this.widths[this.unicodeToCID[charcode] || charcode];
|
||||
unicode = charcode;
|
||||
break;
|
||||
}
|
||||
unicode = adaptUnicode(this.cidToUnicode[charcode] || charcode);
|
||||
unicode = this.toUnicode[charcode] || charcode;
|
||||
break;
|
||||
case 'CIDFontType2':
|
||||
if (this.noUnicodeAdaptation) {
|
||||
width = this.widths[this.cidToUnicode[charcode]];
|
||||
width = this.widths[this.unicodeToCID[charcode] || charcode];
|
||||
unicode = charcode;
|
||||
break;
|
||||
}
|
||||
unicode = adaptUnicode(this.cidToUnicode[charcode] || charcode);
|
||||
unicode = this.toUnicode[charcode] || charcode;
|
||||
break;
|
||||
case 'Type1':
|
||||
var glyphName = this.differences[charcode] || this.encoding[charcode];
|
||||
|
@ -2004,7 +2102,7 @@ var Font = (function Font() {
|
|||
break;
|
||||
}
|
||||
unicode = this.glyphNameMap[glyphName] ||
|
||||
adaptUnicode(GlyphsUnicode[glyphName] || charcode);
|
||||
GlyphsUnicode[glyphName] || charcode;
|
||||
break;
|
||||
case 'Type3':
|
||||
var glyphName = this.differences[charcode] || this.encoding[charcode];
|
||||
|
@ -2022,16 +2120,16 @@ var Font = (function Font() {
|
|||
break;
|
||||
}
|
||||
if (!this.hasEncoding) {
|
||||
unicode = adaptUnicode(charcode);
|
||||
unicode = this.useToUnicode ? this.toUnicode[charcode] : charcode;
|
||||
break;
|
||||
}
|
||||
if (this.hasShortCmap) {
|
||||
if (this.hasShortCmap && false) {
|
||||
var j = Encodings.MacRomanEncoding.indexOf(glyphName);
|
||||
unicode = j >= 0 && !isSpecialUnicode(j) ? j :
|
||||
unicode = j >= 0 ? j :
|
||||
this.glyphNameMap[glyphName];
|
||||
} else {
|
||||
unicode = glyphName in GlyphsUnicode ?
|
||||
adaptUnicode(GlyphsUnicode[glyphName]) :
|
||||
GlyphsUnicode[glyphName] :
|
||||
this.glyphNameMap[glyphName];
|
||||
}
|
||||
break;
|
||||
|
@ -2039,9 +2137,17 @@ var Font = (function Font() {
|
|||
warn('Unsupported font type: ' + this.type);
|
||||
break;
|
||||
}
|
||||
|
||||
var unicodeChars = this.toUnicode ? this.toUnicode[charcode] : charcode;
|
||||
if (typeof unicodeChars === 'number')
|
||||
unicodeChars = String.fromCharCode(unicodeChars);
|
||||
|
||||
width = (isNum(width) ? width : this.defaultWidth) * this.widthMultiplier;
|
||||
|
||||
return {
|
||||
unicode: unicode,
|
||||
width: isNum(width) ? width : this.defaultWidth,
|
||||
fontChar: String.fromCharCode(unicode),
|
||||
unicode: unicodeChars,
|
||||
width: width,
|
||||
codeIRQueue: codeIRQueue
|
||||
};
|
||||
},
|
||||
|
@ -2753,22 +2859,13 @@ CFF.prototype = {
|
|||
getOrderedCharStrings: function cff_getOrderedCharStrings(glyphs,
|
||||
properties) {
|
||||
var charstrings = [];
|
||||
var reverseMapping = {};
|
||||
var encoding = properties.baseEncoding;
|
||||
var i, length, glyphName;
|
||||
for (i = 0, length = encoding.length; i < length; ++i) {
|
||||
glyphName = encoding[i];
|
||||
if (!glyphName || isSpecialUnicode(i))
|
||||
continue;
|
||||
reverseMapping[glyphName] = i;
|
||||
}
|
||||
reverseMapping['.notdef'] = 0;
|
||||
var unusedUnicode = kCmapGlyphOffset;
|
||||
for (i = 0, length = glyphs.length; i < length; i++) {
|
||||
var item = glyphs[i];
|
||||
var glyphName = item.glyph;
|
||||
var unicode = glyphName in reverseMapping ?
|
||||
reverseMapping[glyphName] : unusedUnicode++;
|
||||
var unicode = glyphName in GlyphsUnicode ?
|
||||
GlyphsUnicode[glyphName] : unusedUnicode++;
|
||||
charstrings.push({
|
||||
glyph: glyphName,
|
||||
unicode: unicode,
|
||||
|
@ -3055,16 +3152,14 @@ var Type2CFF = (function type2CFF() {
|
|||
}
|
||||
|
||||
var charStrings = this.parseIndex(topDict.CharStrings);
|
||||
var charset = this.parseCharsets(topDict.charset,
|
||||
charStrings.length, strings);
|
||||
var encoding = this.parseEncoding(topDict.Encoding, properties,
|
||||
strings, charset);
|
||||
|
||||
var charset, encoding;
|
||||
var isCIDFont = properties.subtype == 'CIDFontType0C';
|
||||
if (isCIDFont) {
|
||||
charset = [];
|
||||
charset.length = charStrings.length;
|
||||
charset = ['.notdef'];
|
||||
for (var i = 1, ii = charStrings.length; i < ii; ++i)
|
||||
charset.push('glyph' + i);
|
||||
|
||||
encoding = this.parseCidMap(topDict.charset,
|
||||
charStrings.length);
|
||||
} else {
|
||||
|
@ -3133,38 +3228,44 @@ var Type2CFF = (function type2CFF() {
|
|||
var charstrings = [];
|
||||
var unicodeUsed = [];
|
||||
var unassignedUnicodeItems = [];
|
||||
var inverseEncoding = [];
|
||||
for (var charcode in encoding)
|
||||
inverseEncoding[encoding[charcode]] = charcode | 0;
|
||||
for (var i = 0, ii = charsets.length; i < ii; i++) {
|
||||
var glyph = charsets[i];
|
||||
var encodingFound = false;
|
||||
for (var charcode in encoding) {
|
||||
if (encoding[charcode] == i) {
|
||||
var code = charcode | 0;
|
||||
charstrings.push({
|
||||
unicode: adaptUnicode(code),
|
||||
code: code,
|
||||
gid: i,
|
||||
glyph: glyph
|
||||
});
|
||||
unicodeUsed[code] = true;
|
||||
encodingFound = true;
|
||||
break;
|
||||
}
|
||||
if (glyph == '.notdef') {
|
||||
charstrings.push({
|
||||
unicode: 0,
|
||||
code: 0,
|
||||
gid: i,
|
||||
glyph: glyph
|
||||
});
|
||||
continue;
|
||||
}
|
||||
if (!encodingFound) {
|
||||
var code = inverseEncoding[i];
|
||||
if (!code || isSpecialUnicode(code)) {
|
||||
unassignedUnicodeItems.push(i);
|
||||
continue;
|
||||
}
|
||||
charstrings.push({
|
||||
unicode: code,
|
||||
code: code,
|
||||
gid: i,
|
||||
glyph: glyph
|
||||
});
|
||||
unicodeUsed[code] = true;
|
||||
}
|
||||
|
||||
var nextUnusedUnicode = 0x21;
|
||||
var nextUnusedUnicode = kCmapGlyphOffset;
|
||||
for (var j = 0, jj = unassignedUnicodeItems.length; j < jj; ++j) {
|
||||
var i = unassignedUnicodeItems[j];
|
||||
// giving unicode value anyway
|
||||
while (unicodeUsed[nextUnusedUnicode])
|
||||
while (nextUnusedUnicode in unicodeUsed)
|
||||
nextUnusedUnicode++;
|
||||
var code = nextUnusedUnicode++;
|
||||
var unicode = nextUnusedUnicode++;
|
||||
charstrings.push({
|
||||
unicode: adaptUnicode(code),
|
||||
code: code,
|
||||
unicode: unicode,
|
||||
code: inverseEncoding[i] || 0,
|
||||
gid: i,
|
||||
glyph: charsets[i]
|
||||
});
|
||||
|
|
|
@ -4287,6 +4287,7 @@ var GlyphsUnicode = {
|
|||
zretroflexhook: 0x0290,
|
||||
zstroke: 0x01B6,
|
||||
zuhiragana: 0x305A,
|
||||
zukatakana: 0x30BA
|
||||
zukatakana: 0x30BA,
|
||||
'.notdef': 0x0000
|
||||
};
|
||||
|
||||
|
|
12
src/image.js
12
src/image.js
|
@ -229,12 +229,12 @@ var PDFImage = (function pdfImage() {
|
|||
return constructor;
|
||||
})();
|
||||
|
||||
var JpegImage = (function jpegImage() {
|
||||
function JpegImage(objId, imageData, objs) {
|
||||
var JpegImageLoader = (function jpegImage() {
|
||||
function JpegImageLoader(objId, imageData, objs) {
|
||||
var src = 'data:image/jpeg;base64,' + window.btoa(imageData);
|
||||
|
||||
var img = new Image();
|
||||
img.onload = (function jpegImageOnload() {
|
||||
img.onload = (function jpegImageLoaderOnload() {
|
||||
this.loaded = true;
|
||||
|
||||
objs.resolve(objId, this);
|
||||
|
@ -246,12 +246,12 @@ var JpegImage = (function jpegImage() {
|
|||
this.domImage = img;
|
||||
}
|
||||
|
||||
JpegImage.prototype = {
|
||||
getImage: function jpegImageGetImage() {
|
||||
JpegImageLoader.prototype = {
|
||||
getImage: function jpegImageLoaderGetImage() {
|
||||
return this.domImage;
|
||||
}
|
||||
};
|
||||
|
||||
return JpegImage;
|
||||
return JpegImageLoader;
|
||||
})();
|
||||
|
||||
|
|
|
@ -3,6 +3,9 @@
|
|||
|
||||
'use strict';
|
||||
|
||||
// The Metrics object contains glyph widths (in glyph space units).
|
||||
// As per PDF spec, for most fonts (Type 3 being an exception) a glyph
|
||||
// space unit corresponds to 1/1000th of text space unit.
|
||||
var Metrics = {
|
||||
'Courier': 600,
|
||||
'Courier-Bold': 600,
|
||||
|
|
33
src/obj.js
33
src/obj.js
|
@ -518,20 +518,29 @@ var XRef = (function xRefXRef() {
|
|||
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');
|
||||
|
||||
try {
|
||||
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.readXRefStream(obj);
|
||||
} catch (e) {
|
||||
log('Reading of the xref table/stream failed: ' + e);
|
||||
}
|
||||
|
||||
warn('Indexing all PDF objects');
|
||||
return this.indexObjects();
|
||||
},
|
||||
getEntry: function xRefGetEntry(i) {
|
||||
|
|
|
@ -225,7 +225,8 @@ var Parser = (function parserParser() {
|
|||
return new PredictorStream(new FlateStream(stream), params);
|
||||
}
|
||||
return new FlateStream(stream);
|
||||
} else if (name == 'LZWDecode' || name == 'LZW') {
|
||||
}
|
||||
if (name == 'LZWDecode' || name == 'LZW') {
|
||||
var earlyChange = 1;
|
||||
if (params) {
|
||||
if (params.has('EarlyChange'))
|
||||
|
@ -234,18 +235,21 @@ var Parser = (function parserParser() {
|
|||
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 {
|
||||
TODO('filter "' + name + '" not supported yet');
|
||||
}
|
||||
if (name == 'DCTDecode' || name == 'DCT') {
|
||||
var bytes = stream.getBytes(length);
|
||||
return new JpegStream(bytes, stream.dict, this.xref);
|
||||
}
|
||||
if (name == 'ASCII85Decode' || name == 'A85') {
|
||||
return new Ascii85Stream(stream);
|
||||
}
|
||||
if (name == 'ASCIIHexDecode' || name == 'AHx') {
|
||||
return new AsciiHexStream(stream);
|
||||
}
|
||||
if (name == 'CCITTFaxDecode' || name == 'CCF') {
|
||||
return new CCITTFaxStream(stream, params);
|
||||
}
|
||||
TODO('filter "' + name + '" not supported yet');
|
||||
return stream;
|
||||
}
|
||||
};
|
||||
|
|
|
@ -3,6 +3,11 @@
|
|||
|
||||
'use strict';
|
||||
|
||||
var PatternType = {
|
||||
AXIAL: 2,
|
||||
RADIAL: 3
|
||||
};
|
||||
|
||||
var Pattern = (function patternPattern() {
|
||||
// Constructor should define this.getPattern
|
||||
function constructor() {
|
||||
|
@ -28,9 +33,9 @@ var Pattern = (function patternPattern() {
|
|||
var type = dict.get('ShadingType');
|
||||
|
||||
switch (type) {
|
||||
case 2:
|
||||
case 3:
|
||||
// both radial and axial shadings are handled by RadialAxial shading
|
||||
case PatternType.AXIAL:
|
||||
case PatternType.RADIAL:
|
||||
// Both radial and axial shadings are handled by RadialAxial shading.
|
||||
return new Shadings.RadialAxial(dict, matrix, xref, res, ctx);
|
||||
default:
|
||||
return new Shadings.Dummy();
|
||||
|
@ -117,9 +122,9 @@ Shadings.RadialAxial = (function radialAxialShading() {
|
|||
}
|
||||
|
||||
var grad;
|
||||
if (type == 2)
|
||||
if (type == PatternType.AXIAL)
|
||||
grad = ctx.createLinearGradient(p0[0], p0[1], p1[0], p1[1]);
|
||||
else if (type == 3)
|
||||
else if (type == PatternType.RADIAL)
|
||||
grad = ctx.createRadialGradient(p0[0], p0[1], r0, p1[0], p1[1], r1);
|
||||
|
||||
for (var i = 0, ii = colorStops.length; i < ii; ++i) {
|
||||
|
@ -133,12 +138,12 @@ Shadings.RadialAxial = (function radialAxialShading() {
|
|||
getIR: function radialAxialShadingGetIR() {
|
||||
var coordsArr = this.coordsArr;
|
||||
var type = this.shadingType;
|
||||
if (type == 2) {
|
||||
if (type == PatternType.AXIAL) {
|
||||
var p0 = [coordsArr[0], coordsArr[1]];
|
||||
var p1 = [coordsArr[2], coordsArr[3]];
|
||||
var r0 = null;
|
||||
var r1 = null;
|
||||
} else if (type == 3) {
|
||||
} else if (type == PatternType.RADIAL) {
|
||||
var p0 = [coordsArr[0], coordsArr[1]];
|
||||
var p1 = [coordsArr[3], coordsArr[4]];
|
||||
var r0 = coordsArr[2];
|
||||
|
|
|
@ -756,8 +756,13 @@ var PredictorStream = (function predictorStream() {
|
|||
return constructor;
|
||||
})();
|
||||
|
||||
// A JpegStream can't be read directly. We use the platform to render
|
||||
// the underlying JPEG data for us.
|
||||
/**
|
||||
* Depending on the type of JPEG a JpegStream is handled in different ways. For
|
||||
* JPEG's that are supported natively such as DeviceGray and DeviceRGB the image
|
||||
* data is stored and then loaded by the browser. For unsupported JPEG's we use
|
||||
* a library to decode these images and the stream behaves like all the other
|
||||
* DecodeStreams.
|
||||
*/
|
||||
var JpegStream = (function jpegStream() {
|
||||
function isAdobeImage(bytes) {
|
||||
var maxBytesScanned = Math.max(bytes.length - 16, 1024);
|
||||
|
@ -789,24 +794,56 @@ var JpegStream = (function jpegStream() {
|
|||
return newBytes;
|
||||
}
|
||||
|
||||
function constructor(bytes, dict) {
|
||||
function constructor(bytes, dict, xref) {
|
||||
// TODO: per poppler, some images may have 'junk' before that
|
||||
// need to be removed
|
||||
this.dict = dict;
|
||||
|
||||
if (isAdobeImage(bytes))
|
||||
bytes = fixAdobeImage(bytes);
|
||||
// Flag indicating wether the image can be natively loaded.
|
||||
this.isNative = true;
|
||||
|
||||
this.src = bytesToString(bytes);
|
||||
this.colorTransform = -1;
|
||||
|
||||
if (isAdobeImage(bytes)) {
|
||||
// when bug 674619 land, let's check if browser can do
|
||||
// normal cmyk and then we won't have to the following
|
||||
var cs = xref.fetchIfRef(dict.get('ColorSpace'));
|
||||
|
||||
// DeviceRGB and DeviceGray are the only Adobe images that work natively
|
||||
if (isName(cs) && (cs.name === 'DeviceRGB' || cs.name === 'DeviceGray')) {
|
||||
bytes = fixAdobeImage(bytes);
|
||||
this.src = bytesToString(bytes);
|
||||
} else {
|
||||
this.colorTransform = dict.get('ColorTransform');
|
||||
this.isNative = false;
|
||||
this.bytes = bytes;
|
||||
}
|
||||
} else {
|
||||
this.src = bytesToString(bytes);
|
||||
}
|
||||
|
||||
DecodeStream.call(this);
|
||||
}
|
||||
|
||||
constructor.prototype = {
|
||||
getIR: function jpegStreamGetIR() {
|
||||
return this.src;
|
||||
},
|
||||
getChar: function jpegStreamGetChar() {
|
||||
constructor.prototype = Object.create(DecodeStream.prototype);
|
||||
|
||||
constructor.prototype.ensureBuffer = function jpegStreamEnsureBuffer(req) {
|
||||
if (this.bufferLength)
|
||||
return;
|
||||
var jpegImage = new JpegImage();
|
||||
jpegImage.colorTransform = this.colorTransform;
|
||||
jpegImage.parse(this.bytes);
|
||||
var width = jpegImage.width;
|
||||
var height = jpegImage.height;
|
||||
var data = jpegImage.getData(width, height);
|
||||
this.buffer = data;
|
||||
this.bufferLength = data.length;
|
||||
};
|
||||
constructor.prototype.getIR = function jpegStreamGetIR() {
|
||||
return this.src;
|
||||
};
|
||||
constructor.prototype.getChar = function jpegStreamGetChar() {
|
||||
error('internal error: getChar is not valid on JpegStream');
|
||||
}
|
||||
};
|
||||
|
||||
return constructor;
|
||||
|
|
|
@ -14,7 +14,6 @@ function MessageHandler(name, comObj) {
|
|||
ah['console_error'] = [function ahConsoleError(data) {
|
||||
console.error.apply(console, data);
|
||||
}];
|
||||
|
||||
comObj.onmessage = function messageHandlerComObjOnMessage(event) {
|
||||
var data = event.data;
|
||||
if (data.action in ah) {
|
||||
|
@ -67,7 +66,6 @@ var WorkerMessageHandler = {
|
|||
handler.on('page_request', function wphSetupPageRequest(pageNum) {
|
||||
pageNum = parseInt(pageNum);
|
||||
|
||||
var page = pdfDoc.getPage(pageNum);
|
||||
|
||||
// The following code does quite the same as
|
||||
// Page.prototype.startRendering, but stops at one point and sends the
|
||||
|
@ -77,9 +75,23 @@ var WorkerMessageHandler = {
|
|||
var start = Date.now();
|
||||
|
||||
var dependency = [];
|
||||
|
||||
// Pre compile the pdf page and fetch the fonts/images.
|
||||
var IRQueue = page.getIRQueue(handler, dependency);
|
||||
var IRQueue = null;
|
||||
try {
|
||||
var page = pdfDoc.getPage(pageNum);
|
||||
// Pre compile the pdf page and fetch the fonts/images.
|
||||
IRQueue = page.getIRQueue(handler, dependency);
|
||||
} catch (e) {
|
||||
// Turn the error into an obj that can be serialized
|
||||
e = {
|
||||
message: e.message,
|
||||
stack: e.stack
|
||||
};
|
||||
handler.send('page_error', {
|
||||
pageNum: pageNum,
|
||||
error: e
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
console.log('page=%d - getIRQueue: time=%dms, len=%d', pageNum,
|
||||
Date.now() - start, IRQueue.fnArray.length);
|
||||
|
|
|
@ -40,7 +40,8 @@ function onMessageLoader(evt) {
|
|||
'parser.js',
|
||||
'pattern.js',
|
||||
'stream.js',
|
||||
'worker.js'
|
||||
'worker.js',
|
||||
'../external/jpgjs/jpg.js'
|
||||
];
|
||||
|
||||
// Load all the files.
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue