Move the creation of canvas path fonts to the worker.

This commit is contained in:
Brendan Dahl 2013-08-19 16:33:20 -07:00
parent 81c9eeef4b
commit bb2529de03
16 changed files with 131 additions and 76 deletions

View File

@ -13,7 +13,6 @@
<script type="text/javascript" src="../../src/display/metadata.js"></script> <script type="text/javascript" src="../../src/display/metadata.js"></script>
<script type="text/javascript" src="../../src/display/canvas.js"></script> <script type="text/javascript" src="../../src/display/canvas.js"></script>
<script type="text/javascript" src="../../src/display/font_loader.js"></script> <script type="text/javascript" src="../../src/display/font_loader.js"></script>
<script type="text/javascript" src="../../src/display/font_renderer.js"></script>
<script type="text/javascript"> <script type="text/javascript">
// Specify the main script used to create a new PDF.JS web worker. // Specify the main script used to create a new PDF.JS web worker.

View File

@ -13,7 +13,6 @@
<script type="text/javascript" src="../../src/display/metadata.js"></script> <script type="text/javascript" src="../../src/display/metadata.js"></script>
<script type="text/javascript" src="../../src/display/canvas.js"></script> <script type="text/javascript" src="../../src/display/canvas.js"></script>
<script type="text/javascript" src="../../src/display/font_loader.js"></script> <script type="text/javascript" src="../../src/display/font_loader.js"></script>
<script type="text/javascript" src="../../src/display/font_renderer.js"></script>
<script type="text/javascript"> <script type="text/javascript">
// Specify the main script used to create a new PDF.JS web worker. // Specify the main script used to create a new PDF.JS web worker.

View File

@ -278,8 +278,7 @@ target.bundle = function(args) {
'display/api.js', 'display/api.js',
'display/metadata.js', 'display/metadata.js',
'display/canvas.js', 'display/canvas.js',
'display/font_loader.js', 'display/font_loader.js'
'display/font_renderer.js'
]; ];
var WORKER_SRC_FILES = [ var WORKER_SRC_FILES = [
@ -298,6 +297,7 @@ target.bundle = function(args) {
'core/crypto.js', 'core/crypto.js',
'core/evaluator.js', 'core/evaluator.js',
'core/fonts.js', 'core/fonts.js',
'core/font_renderer.js',
'core/glyphlist.js', 'core/glyphlist.js',
'core/image.js', 'core/image.js',
'core/metrics.js', 'core/metrics.js',

View File

@ -20,7 +20,7 @@
isStream, isString, JpegStream, Lexer, Metrics, Name, Parser, isStream, isString, JpegStream, Lexer, Metrics, Name, Parser,
Pattern, PDFImage, PDFJS, serifFonts, stdFontMap, symbolsFonts, Pattern, PDFImage, PDFJS, serifFonts, stdFontMap, symbolsFonts,
TilingPattern, TODO, warn, Util, Promise, TilingPattern, TODO, warn, Util, Promise,
RefSetCache, isRef */ RefSetCache, isRef, TextRenderingMode */
'use strict'; 'use strict';
@ -349,6 +349,31 @@ var PartialEvaluator = (function PartialEvaluatorClosure() {
return loadedName; return loadedName;
}, },
handleText: function PartialEvaluator_handleText(chars) {
var font = this.state.font.translated;
var glyphs = font.charsToGlyphs(chars);
var isAddToPathSet = !!(this.state.textRenderingMode &
TextRenderingMode.ADD_TO_PATH_FLAG);
if (isAddToPathSet || PDFJS.disableFontFace) {
for (var i = 0; i < glyphs.length; i++) {
if (glyphs[i] === null) {
continue;
}
var fontChar = glyphs[i].fontChar;
if (!font.renderer.hasBuiltPath(fontChar)) {
var path = font.renderer.getPathJs(fontChar);
this.handler.send('commonobj', [
font.loadedName + '_path_' + fontChar,
'FontPath',
path
]);
}
}
}
return glyphs;
},
setGState: function PartialEvaluator_setGState(resources, gState, setGState: function PartialEvaluator_setGState(resources, gState,
operatorList) { operatorList) {
@ -631,19 +656,21 @@ var PartialEvaluator = (function PartialEvaluatorClosure() {
this.state = prev; this.state = prev;
} }
} else if (cmd === 'Tj') { // showText } else if (cmd === 'Tj') { // showText
args[0] = this.state.font.translated.charsToGlyphs(args[0]); args[0] = this.handleText(args[0]);
} else if (cmd === 'TJ') { // showSpacedText } else if (cmd === 'TJ') { // showSpacedText
var arr = args[0]; var arr = args[0];
var arrLength = arr.length; var arrLength = arr.length;
for (var i = 0; i < arrLength; ++i) { for (var i = 0; i < arrLength; ++i) {
if (isString(arr[i])) { if (isString(arr[i])) {
arr[i] = this.state.font.translated.charsToGlyphs(arr[i]); arr[i] = this.handleText(arr[i]);
} }
} }
} else if (cmd === '\'') { // nextLineShowText } else if (cmd === '\'') { // nextLineShowText
args[0] = this.state.font.translated.charsToGlyphs(args[0]); args[0] = this.handleText(args[0]);
} else if (cmd === '"') { // nextLineSetSpacingShowText } else if (cmd === '"') { // nextLineSetSpacingShowText
args[2] = this.state.font.translated.charsToGlyphs(args[2]); args[2] = this.handleText(args[2]);
} else if (cmd === 'Tr') { // setTextRenderingMode
this.state.textRenderingMode = args[0];
} }
switch (fn) { switch (fn) {
@ -1529,6 +1556,7 @@ var OperatorList = (function OperatorListClosure() {
var EvalState = (function EvalStateClosure() { var EvalState = (function EvalStateClosure() {
function EvalState() { function EvalState() {
this.font = null; this.font = null;
this.textRenderingMode = TextRenderingMode.FILL;
} }
EvalState.prototype = { EvalState.prototype = {
clone: function CanvasExtraState_clone() { clone: function CanvasExtraState_clone() {

View File

@ -14,7 +14,7 @@
* See the License for the specific language governing permissions and * See the License for the specific language governing permissions and
* limitations under the License. * limitations under the License.
*/ */
/* globals error, Stream, GlyphsUnicode, CFFParser, Encodings */ /* globals error, Stream, GlyphsUnicode, CFFParser, Encodings, Util */
'use strict'; 'use strict';
@ -585,18 +585,14 @@ var FontRendererFactory = (function FontRendererFactoryClosure() {
parse(code); parse(code);
} }
function TrueTypeCompiled(glyphs, cmap, fontMatrix) { var noop = '';
this.glyphs = glyphs;
this.cmap = cmap;
this.fontMatrix = fontMatrix || [0.000488, 0, 0, 0.000488, 0, 0];
this.compiledGlyphs = []; function CompiledFont(fontMatrix) {
this.compiledGlyphs = {};
this.fontMatrix = fontMatrix;
} }
CompiledFont.prototype = {
var noop = function () {}; getPathJs: function (unicode) {
TrueTypeCompiled.prototype = {
getPathGenerator: function (unicode) {
var gid = lookupCmap(this.cmap, unicode); var gid = lookupCmap(this.cmap, unicode);
var fn = this.compiledGlyphs[gid]; var fn = this.compiledGlyphs[gid];
if (!fn) { if (!fn) {
@ -604,6 +600,7 @@ var FontRendererFactory = (function FontRendererFactoryClosure() {
} }
return fn; return fn;
}, },
compileGlyph: function (code) { compileGlyph: function (code) {
if (!code || code.length === 0 || code[0] === 14) { if (!code || code.length === 0 || code[0] === 14) {
return noop; return noop;
@ -614,23 +611,47 @@ var FontRendererFactory = (function FontRendererFactoryClosure() {
js.push('c.transform(' + this.fontMatrix.join(',') + ');'); js.push('c.transform(' + this.fontMatrix.join(',') + ');');
js.push('c.scale(size, -size);'); js.push('c.scale(size, -size);');
var stack = [], x = 0, y = 0; this.compileGlyphImpl(code, js);
compileGlyf(code, js, this);
js.push('c.restore();'); js.push('c.restore();');
/*jshint -W054 */ return js.join('\n');
return new Function('c', 'size', js.join('\n')); },
compileGlyphImpl: function () {
error('Children classes should implement this.');
},
hasBuiltPath: function (unicode) {
var gid = lookupCmap(this.cmap, unicode);
return gid in this.compiledGlyphs;
} }
}; };
function TrueTypeCompiled(glyphs, cmap, fontMatrix) {
fontMatrix = fontMatrix || [0.000488, 0, 0, 0.000488, 0, 0];
CompiledFont.call(this, fontMatrix);
this.glyphs = glyphs;
this.cmap = cmap;
this.compiledGlyphs = [];
}
Util.inherit(TrueTypeCompiled, CompiledFont, {
compileGlyphImpl: function (code, js) {
compileGlyf(code, js, this);
}
});
function Type2Compiled(cffInfo, cmap, fontMatrix, glyphNameMap) { function Type2Compiled(cffInfo, cmap, fontMatrix, glyphNameMap) {
fontMatrix = fontMatrix || [0.001, 0, 0, 0.001, 0, 0];
CompiledFont.call(this, fontMatrix);
this.glyphs = cffInfo.glyphs; this.glyphs = cffInfo.glyphs;
this.gsubrs = cffInfo.gsubrs || []; this.gsubrs = cffInfo.gsubrs || [];
this.subrs = cffInfo.subrs || []; this.subrs = cffInfo.subrs || [];
this.cmap = cmap; this.cmap = cmap;
this.glyphNameMap = glyphNameMap || GlyphsUnicode; this.glyphNameMap = glyphNameMap || GlyphsUnicode;
this.fontMatrix = fontMatrix || [0.001, 0, 0, 0.001, 0, 0];
this.compiledGlyphs = []; this.compiledGlyphs = [];
this.gsubrsBias = this.gsubrs.length < 1240 ? 107 : this.gsubrsBias = this.gsubrs.length < 1240 ? 107 :
@ -639,34 +660,12 @@ var FontRendererFactory = (function FontRendererFactoryClosure() {
this.subrs.length < 33900 ? 1131 : 32768; this.subrs.length < 33900 ? 1131 : 32768;
} }
Type2Compiled.prototype = { Util.inherit(Type2Compiled, CompiledFont, {
getPathGenerator: function (unicode) { compileGlyphImpl: function (code, js) {
var gid = lookupCmap(this.cmap, unicode);
var fn = this.compiledGlyphs[gid];
if (!fn) {
this.compiledGlyphs[gid] = fn = this.compileGlyph(this.glyphs[gid]);
}
return fn;
},
compileGlyph: function (code) {
if (!code || code.length === 0 || code[0] === 14) {
return noop;
}
var js = [];
js.push('c.save();');
js.push('c.transform(' + this.fontMatrix.join(',') + ');');
js.push('c.scale(size, -size);');
var stack = [], x = 0, y = 0;
compileCharString(code, js, this); compileCharString(code, js, this);
js.push('c.restore();');
/*jshint -W054 */
return new Function('c', 'size', js.join('\n'));
} }
}; });
return { return {
create: function FontRendererFactory_create(font) { create: function FontRendererFactory_create(font) {

View File

@ -18,7 +18,7 @@
ExpertSubsetCharset, FileReaderSync, GlyphsUnicode, ExpertSubsetCharset, FileReaderSync, GlyphsUnicode,
info, isArray, isNum, ISOAdobeCharset, Stream, info, isArray, isNum, ISOAdobeCharset, Stream,
stringToBytes, TextDecoder, TODO, warn, Lexer, Util, stringToBytes, TextDecoder, TODO, warn, Lexer, Util,
FONT_IDENTITY_MATRIX */ FONT_IDENTITY_MATRIX, FontRendererFactory, shadow */
'use strict'; 'use strict';
@ -2791,6 +2791,10 @@ var Font = (function FontClosure() {
font: null, font: null,
mimetype: null, mimetype: null,
encoding: null, encoding: null,
get renderer() {
var renderer = FontRendererFactory.create(this);
return shadow(this, 'renderer', renderer);
},
exportData: function Font_exportData() { exportData: function Font_exportData() {
var data = {}; var data = {};

View File

@ -224,6 +224,7 @@ var WorkerMessageHandler = PDFJS.WorkerMessageHandler = {
PDFJS.maxImageSize = data.maxImageSize === undefined ? PDFJS.maxImageSize = data.maxImageSize === undefined ?
-1 : data.maxImageSize; -1 : data.maxImageSize;
PDFJS.disableFontFace = data.disableFontFace;
getPdfManager(data).then(function pdfManagerReady() { getPdfManager(data).then(function pdfManagerReady() {
loadDocument(false).then(onSuccess, function loadFailure(ex) { loadDocument(false).then(onSuccess, function loadFailure(ex) {

View File

@ -29,6 +29,16 @@
*/ */
PDFJS.maxImageSize = PDFJS.maxImageSize === undefined ? -1 : PDFJS.maxImageSize; PDFJS.maxImageSize = PDFJS.maxImageSize === undefined ? -1 : PDFJS.maxImageSize;
/**
* By default fonts are converted to OpenType fonts and loaded via font face
* rules. If disabled, the font will be rendered using a built in font renderer
* that constructs the glyphs with primitive path commands.
* @var {Boolean}
*/
PDFJS.disableFontFace = PDFJS.disableFontFace === undefined ?
false :
PDFJS.disableFontFace;
/** /**
* This is the main entry point for loading a PDF and interacting with it. * This is the main entry point for loading a PDF and interacting with it.
* NOTE: If a URL is used to fetch the PDF data a standard XMLHttpRequest(XHR) * NOTE: If a URL is used to fetch the PDF data a standard XMLHttpRequest(XHR)
@ -673,6 +683,9 @@ var WorkerTransport = (function WorkerTransportClosure() {
}.bind(this) }.bind(this)
); );
break; break;
case 'FontPath':
this.commonObjs.resolve(id, data[2]);
break;
default: default:
error('Got unknown common object type ' + type); error('Got unknown common object type ' + type);
} }
@ -770,7 +783,8 @@ var WorkerTransport = (function WorkerTransportClosure() {
this.messageHandler.send('GetDocRequest', { this.messageHandler.send('GetDocRequest', {
source: source, source: source,
disableRange: PDFJS.disableRange, disableRange: PDFJS.disableRange,
maxImageSize: PDFJS.maxImageSize maxImageSize: PDFJS.maxImageSize,
disableFontFace: PDFJS.disableFontFace
}); });
}, },

View File

@ -16,26 +16,14 @@
*/ */
/* globals ColorSpace, DeviceCmykCS, DeviceGrayCS, DeviceRgbCS, error, /* globals ColorSpace, DeviceCmykCS, DeviceGrayCS, DeviceRgbCS, error,
FONT_IDENTITY_MATRIX, IDENTITY_MATRIX, ImageData, isArray, isNum, FONT_IDENTITY_MATRIX, IDENTITY_MATRIX, ImageData, isArray, isNum,
Pattern, TilingPattern, TODO, Util, warn, assert, info */ Pattern, TilingPattern, TODO, Util, warn, assert, info,
TextRenderingMode */
'use strict'; 'use strict';
// <canvas> contexts store most of the state we need natively. // <canvas> contexts store most of the state we need natively.
// However, PDF needs a bit more state, which we store here. // 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,
FILL_STROKE_MASK: 3,
ADD_TO_PATH_FLAG: 4
};
// Minimal font size that would be used during canvas fillText operations. // Minimal font size that would be used during canvas fillText operations.
var MIN_FONT_SIZE = 16; var MIN_FONT_SIZE = 16;
@ -1007,7 +995,7 @@ var CanvasGraphics = (function CanvasGraphicsClosure() {
var addToPath; var addToPath;
if (font.disableFontFace || isAddToPathSet) { if (font.disableFontFace || isAddToPathSet) {
addToPath = font.renderer.getPathGenerator(character); addToPath = font.getPathGenerator(this.commonObjs, character);
} }
if (font.disableFontFace) { if (font.disableFontFace) {

View File

@ -14,8 +14,7 @@
* See the License for the specific language governing permissions and * See the License for the specific language governing permissions and
* limitations under the License. * limitations under the License.
*/ */
/* globals PDFJS, shadow, isWorker, assert, warn, FontRendererFactory, /* globals PDFJS, shadow, isWorker, assert, warn, bytesToString, globalScope */
bytesToString, globalScope */
'use strict'; 'use strict';
@ -267,6 +266,7 @@ var FontLoader = {
var FontFace = (function FontFaceClosure() { var FontFace = (function FontFaceClosure() {
function FontFace(name, file, properties) { function FontFace(name, file, properties) {
this.compiledGlyphs = {};
if (arguments.length === 1) { if (arguments.length === 1) {
// importing translated data // importing translated data
var data = arguments[0]; var data = arguments[0];
@ -277,10 +277,6 @@ var FontFace = (function FontFaceClosure() {
} }
} }
FontFace.prototype = { FontFace.prototype = {
get renderer() {
var renderer = FontRendererFactory.create(this);
return shadow(this, 'renderer', renderer);
},
bindDOM: function FontFace_bindDOM() { bindDOM: function FontFace_bindDOM() {
if (!this.data) if (!this.data)
return null; return null;
@ -305,6 +301,14 @@ var FontFace = (function FontFaceClosure() {
globalScope['FontInspector'].fontAdded(this, url); globalScope['FontInspector'].fontAdded(this, url);
return rule; return rule;
},
getPathGenerator: function (objs, character) {
if (!(character in this.compiledGlyphs)) {
var js = objs.get(this.loadedName + '_path_' + character);
/*jshint -W054 */
this.compiledGlyphs[character] = new Function('c', 'size', js);
}
return this.compiledGlyphs[character];
} }
}; };
return FontFace; return FontFace;

View File

@ -27,6 +27,19 @@ var verbosity = WARNINGS;
var FONT_IDENTITY_MATRIX = [0.001, 0, 0, 0.001, 0, 0]; var FONT_IDENTITY_MATRIX = [0.001, 0, 0, 0.001, 0, 0];
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,
FILL_STROKE_MASK: 3,
ADD_TO_PATH_FLAG: 4
};
// The global PDFJS object exposes the API // The global PDFJS object exposes the API
// In production, it will be declared outside a global wrapper // In production, it will be declared outside a global wrapper
// In development, it will be declared here // In development, it will be declared here

View File

@ -35,6 +35,7 @@ var files = [
'core/crypto.js', 'core/crypto.js',
'core/evaluator.js', 'core/evaluator.js',
'core/fonts.js', 'core/fonts.js',
'core/font_renderer.js',
'core/glyphlist.js', 'core/glyphlist.js',
'core/image.js', 'core/image.js',
'core/metrics.js', 'core/metrics.js',

BIN
test/pdfs/issue3584.pdf Normal file

Binary file not shown.

View File

@ -1063,6 +1063,13 @@
"link": true, "link": true,
"type": "eq" "type": "eq"
}, },
{ "id": "issue3584",
"file": "pdfs/issue3584.pdf",
"md5": "7a00646865a840eefc76f05c588b60ce",
"rounds": 1,
"type": "eq",
"about": "CFF font that is drawn with clipping."
},
{ "id": "annotation-tx", { "id": "annotation-tx",
"file": "pdfs/annotation-tx.pdf", "file": "pdfs/annotation-tx.pdf",
"md5": "56321ea830be9c4f8437ca17ac535b2d", "md5": "56321ea830be9c4f8437ca17ac535b2d",

View File

@ -28,7 +28,6 @@ limitations under the License.
<script type="text/javascript" src="/src/display/metadata.js"></script> <script type="text/javascript" src="/src/display/metadata.js"></script>
<script type="text/javascript" src="/src/display/canvas.js"></script> <script type="text/javascript" src="/src/display/canvas.js"></script>
<script type="text/javascript" src="/src/display/font_loader.js"></script> <script type="text/javascript" src="/src/display/font_loader.js"></script>
<script type="text/javascript" src="/src/display/font_renderer.js"></script>
<script type="text/javascript" src="driver.js"></script> <script type="text/javascript" src="driver.js"></script>
<script type="text/javascript"> <script type="text/javascript">

View File

@ -50,7 +50,6 @@ limitations under the License.
<script type="text/javascript" src="../src/display/metadata.js"></script> <script type="text/javascript" src="../src/display/metadata.js"></script>
<script type="text/javascript" src="../src/display/canvas.js"></script> <script type="text/javascript" src="../src/display/canvas.js"></script>
<script type="text/javascript" src="../src/display/font_loader.js"></script> <script type="text/javascript" src="../src/display/font_loader.js"></script>
<script type="text/javascript" src="../src/display/font_renderer.js"></script>
<script type="text/javascript">PDFJS.workerSrc = '../src/worker_loader.js';</script> <script type="text/javascript">PDFJS.workerSrc = '../src/worker_loader.js';</script>
<!--#endif--> <!--#endif-->