Browse Source

[PATCH] Add fallback for font loading when eval disabled

In some cases, such as in use with a CSP header, constructing a function with a
string of javascript is not allowed. However, compiling the various commands
that need to be done on the canvas element is faster than interpreting them.
This patch changes the font renderer to instead emit commands that are compiled
by the font loader. If, during compilation, we receive an EvalError, we instead
interpret them.
Mike Skalnik 10 years ago
parent
commit
341c5e9d1f
  1. 60
      src/core/font_renderer.js
  2. 8
      src/display/api.js
  3. 45
      src/display/font_loader.js

60
src/core/font_renderer.js

@ -134,16 +134,15 @@ var FontRendererFactory = (function FontRendererFactoryClosure() {
return 0; return 0;
} }
function compileGlyf(code, js, font) { function compileGlyf(code, cmds, font) {
function moveTo(x, y) { function moveTo(x, y) {
js.push('c.moveTo(' + x + ',' + y + ');'); cmds.push({cmd: 'moveTo', args: [x, y]});
} }
function lineTo(x, y) { function lineTo(x, y) {
js.push('c.lineTo(' + x + ',' + y + ');'); cmds.push({cmd: 'lineTo', args: [x, y]});
} }
function quadraticCurveTo(xa, ya, x, y) { function quadraticCurveTo(xa, ya, x, y) {
js.push('c.quadraticCurveTo(' + xa + ',' + ya + ',' + cmds.push({cmd: 'quadraticCurveTo', args: [xa, ya, x, y]});
x + ',' + y + ');');
} }
var i = 0; var i = 0;
@ -189,11 +188,11 @@ var FontRendererFactory = (function FontRendererFactoryClosure() {
} }
var subglyph = font.glyphs[glyphIndex]; var subglyph = font.glyphs[glyphIndex];
if (subglyph) { if (subglyph) {
js.push('c.save();'); cmds.push({cmd: 'save'});
js.push('c.transform(' + scaleX + ',' + scale01 + ',' + cmds.push({cmd: 'transform',
scale10 + ',' + scaleY + ',' + x + ',' + y + ');'); args: [scaleX, scale01, scale10, scaleY, x, y]});
compileGlyf(subglyph, js, font); compileGlyf(subglyph, cmds, font);
js.push('c.restore();'); cmds.push({cmd: 'restore'});
} }
} while ((flags & 0x20)); } while ((flags & 0x20));
} else { } else {
@ -289,20 +288,19 @@ var FontRendererFactory = (function FontRendererFactoryClosure() {
} }
} }
function compileCharString(code, js, font) { function compileCharString(code, cmds, font) {
var stack = []; var stack = [];
var x = 0, y = 0; var x = 0, y = 0;
var stems = 0; var stems = 0;
function moveTo(x, y) { function moveTo(x, y) {
js.push('c.moveTo(' + x + ',' + y + ');'); cmds.push({cmd: 'moveTo', args: [x, y]});
} }
function lineTo(x, y) { function lineTo(x, y) {
js.push('c.lineTo(' + x + ',' + y + ');'); cmds.push({cmd: 'lineTo', args: [x, y]});
} }
function bezierCurveTo(x1, y1, x2, y2, x, y) { function bezierCurveTo(x1, y1, x2, y2, x, y) {
js.push('c.bezierCurveTo(' + x1 + ',' + y1 + ',' + x2 + ',' + y2 + ',' + cmds.push({cmd: 'bezierCurveTo', args: [x1, y1, x2, y2, x, y]});
x + ',' + y + ');');
} }
function parse(code) { function parse(code) {
@ -431,16 +429,16 @@ var FontRendererFactory = (function FontRendererFactoryClosure() {
var bchar = stack.pop(); var bchar = stack.pop();
y = stack.pop(); y = stack.pop();
x = stack.pop(); x = stack.pop();
js.push('c.save();'); cmds.push({cmd: 'save'});
js.push('c.translate('+ x + ',' + y + ');'); cmds.push({cmd: 'translate', args: [x, y]});
var gid = lookupCmap(font.cmap, String.fromCharCode( var gid = lookupCmap(font.cmap, String.fromCharCode(
font.glyphNameMap[Encodings.StandardEncoding[achar]])); font.glyphNameMap[Encodings.StandardEncoding[achar]]));
compileCharString(font.glyphs[gid], js, font); compileCharString(font.glyphs[gid], cmds, font);
js.push('c.restore();'); cmds.push({cmd: 'restore'});
gid = lookupCmap(font.cmap, String.fromCharCode( gid = lookupCmap(font.cmap, String.fromCharCode(
font.glyphNameMap[Encodings.StandardEncoding[bchar]])); font.glyphNameMap[Encodings.StandardEncoding[bchar]]));
compileCharString(font.glyphs[gid], js, font); compileCharString(font.glyphs[gid], cmds, font);
} }
return; return;
case 18: // hstemhm case 18: // hstemhm
@ -609,16 +607,16 @@ var FontRendererFactory = (function FontRendererFactoryClosure() {
return noop; return noop;
} }
var js = []; var cmds = [];
js.push('c.save();'); cmds.push({cmd: 'save'});
js.push('c.transform(' + this.fontMatrix.join(',') + ');'); cmds.push({cmd: 'transform', args: this.fontMatrix.slice()});
js.push('c.scale(size, -size);'); cmds.push({cmd: 'scale', args: ['size', '-size']});
this.compileGlyphImpl(code, js); this.compileGlyphImpl(code, cmds);
js.push('c.restore();'); cmds.push({cmd: 'restore'});
return js.join('\n'); return cmds;
}, },
compileGlyphImpl: function () { compileGlyphImpl: function () {
@ -642,8 +640,8 @@ var FontRendererFactory = (function FontRendererFactoryClosure() {
} }
Util.inherit(TrueTypeCompiled, CompiledFont, { Util.inherit(TrueTypeCompiled, CompiledFont, {
compileGlyphImpl: function (code, js) { compileGlyphImpl: function (code, cmds) {
compileGlyf(code, js, this); compileGlyf(code, cmds, this);
} }
}); });
@ -664,8 +662,8 @@ var FontRendererFactory = (function FontRendererFactoryClosure() {
} }
Util.inherit(Type2Compiled, CompiledFont, { Util.inherit(Type2Compiled, CompiledFont, {
compileGlyphImpl: function (code, js) { compileGlyphImpl: function (code, cmds) {
compileCharString(code, js, this); compileCharString(code, cmds, this);
} }
}); });

8
src/display/api.js

@ -177,6 +177,14 @@ PDFJS.openExternalLinksInNewWindow = (
PDFJS.openExternalLinksInNewWindow === undefined ? PDFJS.openExternalLinksInNewWindow === undefined ?
false : PDFJS.openExternalLinksInNewWindow); false : PDFJS.openExternalLinksInNewWindow);
/**
* Determines if we can eval strings as JS. Primarily used to improve
* performance for font rendering.
* @var {boolean}
*/
PDFJS.isEvalSupported = (PDFJS.isEvalSupported === undefined ?
true : PDFJS.isEvalSupported);
/** /**
* Document initialization / loading parameters object. * Document initialization / loading parameters object.
* *

45
src/display/font_loader.js

@ -77,6 +77,18 @@ var FontLoader = {
)); ));
}, },
get isEvalSupported() {
var evalSupport = false;
if (PDFJS.isEvalSupported) {
try {
/* jshint evil: true */
new Function('');
evalSupport = true;
} catch (e) {}
}
return shadow(this, 'isEvalSupported', evalSupport);
},
loadTestFontId: 0, loadTestFontId: 0,
loadingContext: { loadingContext: {
@ -365,9 +377,40 @@ var FontFaceObject = (function FontFaceObjectClosure() {
getPathGenerator: function FontLoader_getPathGenerator(objs, character) { getPathGenerator: function FontLoader_getPathGenerator(objs, character) {
if (!(character in this.compiledGlyphs)) { if (!(character in this.compiledGlyphs)) {
var js = objs.get(this.loadedName + '_path_' + character); var cmds = objs.get(this.loadedName + '_path_' + character);
var current, i, len;
// If we can, compile cmds into JS for MAXIMUM SPEED
if (FontLoader.isEvalSupported) {
var args, js = '';
for (i = 0, len = cmds.length; i < len; i++) {
current = cmds[i];
if (current.args !== undefined) {
args = current.args.join(',');
} else {
args = '';
}
js += 'c.' + current.cmd + '(' + args + ');\n';
}
/* jshint -W054 */ /* jshint -W054 */
this.compiledGlyphs[character] = new Function('c', 'size', js); this.compiledGlyphs[character] = new Function('c', 'size', js);
} else {
// But fall back on using Function.prototype.apply() if we're
// blocked from using eval() for whatever reason (like CSP policies)
this.compiledGlyphs[character] = function(c, size) {
for (i = 0, len = cmds.length; i < len; i++) {
current = cmds[i];
if (current.cmd === 'scale') {
current.args = [size, -size];
}
c[current.cmd].apply(c, current.args);
}
};
}
} }
return this.compiledGlyphs[character]; return this.compiledGlyphs[character];
} }

Loading…
Cancel
Save