2011-10-26 10:18:22 +09:00
|
|
|
/* -*- Mode: Java; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
|
|
|
|
/* vim: set shiftwidth=2 tabstop=2 autoindent cindent expandtab: */
|
2012-09-01 07:48:21 +09:00
|
|
|
/* Copyright 2012 Mozilla Foundation
|
|
|
|
*
|
|
|
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
|
|
|
* you may not use this file except in compliance with the License.
|
|
|
|
* You may obtain a copy of the License at
|
|
|
|
*
|
|
|
|
* http://www.apache.org/licenses/LICENSE-2.0
|
|
|
|
*
|
|
|
|
* Unless required by applicable law or agreed to in writing, software
|
|
|
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
|
|
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
|
|
* See the License for the specific language governing permissions and
|
|
|
|
* limitations under the License.
|
|
|
|
*/
|
2015-02-09 18:05:55 +09:00
|
|
|
/* globals assert, CMapFactory, ColorSpace, DecodeStream, Dict, Encodings,
|
2014-03-07 14:27:32 +09:00
|
|
|
error, ErrorFont, Font, FONT_IDENTITY_MATRIX, fontCharsToUnicode,
|
|
|
|
FontFlags, ImageKind, info, isArray, isCmd, isDict, isEOF, isName,
|
2015-02-09 18:05:55 +09:00
|
|
|
isNum, isStream, isString, JpegStream, Lexer, Metrics, IdentityCMap,
|
2014-03-04 02:44:45 +09:00
|
|
|
MurmurHash3_64, Name, Parser, Pattern, PDFImage, PDFJS, serifFonts,
|
|
|
|
stdFontMap, symbolsFonts, getTilingPatternIR, warn, Util, Promise,
|
2015-02-09 18:05:55 +09:00
|
|
|
RefSetCache, isRef, TextRenderingMode, IdentityToUnicodeMap,
|
2014-08-07 10:02:11 +09:00
|
|
|
OPS, UNSUPPORTED_FEATURES, UnsupportedManager, NormalizedUnicodes,
|
2015-02-09 18:05:55 +09:00
|
|
|
IDENTITY_MATRIX, reverseIfRtl, createPromiseCapability, ToUnicodeMap,
|
2014-06-16 23:52:04 +09:00
|
|
|
getFontType */
|
2011-10-26 10:18:22 +09:00
|
|
|
|
|
|
|
'use strict';
|
|
|
|
|
2011-12-09 07:18:43 +09:00
|
|
|
var PartialEvaluator = (function PartialEvaluatorClosure() {
|
2013-04-19 02:41:33 +09:00
|
|
|
function PartialEvaluator(pdfManager, xref, handler, pageIndex,
|
2013-11-15 06:43:38 +09:00
|
|
|
uniquePrefix, idCounters, fontCache) {
|
2013-04-19 02:41:33 +09:00
|
|
|
this.pdfManager = pdfManager;
|
2011-10-25 08:55:23 +09:00
|
|
|
this.xref = xref;
|
|
|
|
this.handler = handler;
|
2012-10-29 05:10:34 +09:00
|
|
|
this.pageIndex = pageIndex;
|
2011-10-25 08:55:23 +09:00
|
|
|
this.uniquePrefix = uniquePrefix;
|
2013-04-23 06:20:49 +09:00
|
|
|
this.idCounters = idCounters;
|
2013-11-15 06:43:38 +09:00
|
|
|
this.fontCache = fontCache;
|
2011-10-25 08:55:23 +09:00
|
|
|
}
|
|
|
|
|
2014-05-10 10:41:03 +09:00
|
|
|
// Trying to minimize Date.now() usage and check every 100 time
|
|
|
|
var TIME_SLOT_DURATION_MS = 20;
|
|
|
|
var CHECK_TIME_EVERY = 100;
|
|
|
|
function TimeSlotManager() {
|
|
|
|
this.reset();
|
|
|
|
}
|
|
|
|
TimeSlotManager.prototype = {
|
|
|
|
check: function TimeSlotManager_check() {
|
|
|
|
if (++this.checked < CHECK_TIME_EVERY) {
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
this.checked = 0;
|
|
|
|
return this.endTime <= Date.now();
|
|
|
|
},
|
|
|
|
reset: function TimeSlotManager_reset() {
|
|
|
|
this.endTime = Date.now() + TIME_SLOT_DURATION_MS;
|
|
|
|
this.checked = 0;
|
|
|
|
}
|
|
|
|
};
|
|
|
|
|
|
|
|
var deferred = Promise.resolve();
|
|
|
|
|
2013-04-09 07:14:56 +09:00
|
|
|
var TILING_PATTERN = 1, SHADING_PATTERN = 2;
|
|
|
|
|
2011-12-09 07:18:43 +09:00
|
|
|
PartialEvaluator.prototype = {
|
2013-08-01 03:17:36 +09:00
|
|
|
hasBlendModes: function PartialEvaluator_hasBlendModes(resources) {
|
|
|
|
if (!isDict(resources)) {
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
2014-03-26 23:07:38 +09:00
|
|
|
var processed = Object.create(null);
|
|
|
|
if (resources.objId) {
|
|
|
|
processed[resources.objId] = true;
|
|
|
|
}
|
|
|
|
|
2013-08-01 03:17:36 +09:00
|
|
|
var nodes = [resources];
|
|
|
|
while (nodes.length) {
|
2014-04-08 06:42:54 +09:00
|
|
|
var key;
|
2013-08-01 03:17:36 +09:00
|
|
|
var node = nodes.shift();
|
|
|
|
// First check the current resources for blend modes.
|
|
|
|
var graphicStates = node.get('ExtGState');
|
|
|
|
if (isDict(graphicStates)) {
|
|
|
|
graphicStates = graphicStates.getAll();
|
2014-04-08 06:42:54 +09:00
|
|
|
for (key in graphicStates) {
|
2013-08-01 03:17:36 +09:00
|
|
|
var graphicState = graphicStates[key];
|
|
|
|
var bm = graphicState['BM'];
|
|
|
|
if (isName(bm) && bm.name !== 'Normal') {
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
// Descend into the XObjects to look for more resources and blend modes.
|
|
|
|
var xObjects = node.get('XObject');
|
|
|
|
if (!isDict(xObjects)) {
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
xObjects = xObjects.getAll();
|
2014-04-08 06:42:54 +09:00
|
|
|
for (key in xObjects) {
|
2013-08-01 03:17:36 +09:00
|
|
|
var xObject = xObjects[key];
|
|
|
|
if (!isStream(xObject)) {
|
|
|
|
continue;
|
|
|
|
}
|
2014-06-10 18:29:25 +09:00
|
|
|
if (xObject.dict.objId) {
|
|
|
|
if (processed[xObject.dict.objId]) {
|
|
|
|
// stream has objId and is processed already
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
processed[xObject.dict.objId] = true;
|
|
|
|
}
|
2013-08-01 03:17:36 +09:00
|
|
|
var xResources = xObject.dict.get('Resources');
|
2014-03-26 23:07:38 +09:00
|
|
|
// Checking objId to detect an infinite loop.
|
|
|
|
if (isDict(xResources) &&
|
|
|
|
(!xResources.objId || !processed[xResources.objId])) {
|
2013-08-01 03:17:36 +09:00
|
|
|
nodes.push(xResources);
|
2014-03-26 23:07:38 +09:00
|
|
|
if (xResources.objId) {
|
|
|
|
processed[xResources.objId] = true;
|
|
|
|
}
|
2013-08-01 03:17:36 +09:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return false;
|
|
|
|
},
|
2012-09-14 00:09:46 +09:00
|
|
|
|
2013-04-09 07:14:56 +09:00
|
|
|
buildFormXObject: function PartialEvaluator_buildFormXObject(resources,
|
2013-08-01 03:17:36 +09:00
|
|
|
xobj, smask,
|
2014-01-17 22:16:52 +09:00
|
|
|
operatorList,
|
2015-10-21 10:50:32 +09:00
|
|
|
task,
|
2014-04-10 08:44:07 +09:00
|
|
|
initialState) {
|
2015-09-28 22:09:24 +09:00
|
|
|
var matrix = xobj.dict.getArray('Matrix');
|
|
|
|
var bbox = xobj.dict.getArray('BBox');
|
2013-04-09 07:14:56 +09:00
|
|
|
var group = xobj.dict.get('Group');
|
|
|
|
if (group) {
|
|
|
|
var groupOptions = {
|
|
|
|
matrix: matrix,
|
|
|
|
bbox: bbox,
|
2014-01-24 02:13:32 +09:00
|
|
|
smask: smask,
|
2013-04-09 07:14:56 +09:00
|
|
|
isolated: false,
|
|
|
|
knockout: false
|
2012-10-16 01:48:45 +09:00
|
|
|
};
|
2013-04-09 07:14:56 +09:00
|
|
|
|
|
|
|
var groupSubtype = group.get('S');
|
2014-05-22 02:47:42 +09:00
|
|
|
var colorSpace;
|
2013-04-09 07:14:56 +09:00
|
|
|
if (isName(groupSubtype) && groupSubtype.name === 'Transparency') {
|
2014-03-23 03:15:51 +09:00
|
|
|
groupOptions.isolated = (group.get('I') || false);
|
|
|
|
groupOptions.knockout = (group.get('K') || false);
|
2014-05-22 02:47:42 +09:00
|
|
|
colorSpace = (group.has('CS') ?
|
|
|
|
ColorSpace.parse(group.get('CS'), this.xref, resources) : null);
|
2013-04-09 07:14:56 +09:00
|
|
|
}
|
2014-05-22 02:47:42 +09:00
|
|
|
|
|
|
|
if (smask && smask.backdrop) {
|
|
|
|
colorSpace = colorSpace || ColorSpace.singletons.rgb;
|
|
|
|
smask.backdrop = colorSpace.getRgb(smask.backdrop, 0);
|
|
|
|
}
|
|
|
|
|
2013-11-14 04:43:38 +09:00
|
|
|
operatorList.addOp(OPS.beginGroup, [groupOptions]);
|
2012-10-16 01:48:45 +09:00
|
|
|
}
|
2012-09-14 00:09:46 +09:00
|
|
|
|
2013-11-14 04:43:38 +09:00
|
|
|
operatorList.addOp(OPS.paintFormXObjectBegin, [matrix, bbox]);
|
2012-09-14 00:09:46 +09:00
|
|
|
|
2015-10-21 10:50:32 +09:00
|
|
|
return this.getOperatorList(xobj, task,
|
2014-05-10 10:21:15 +09:00
|
|
|
(xobj.dict.get('Resources') || resources), operatorList, initialState).
|
|
|
|
then(function () {
|
|
|
|
operatorList.addOp(OPS.paintFormXObjectEnd, []);
|
2013-04-09 07:14:56 +09:00
|
|
|
|
2014-05-10 10:21:15 +09:00
|
|
|
if (group) {
|
|
|
|
operatorList.addOp(OPS.endGroup, [groupOptions]);
|
|
|
|
}
|
|
|
|
});
|
2013-04-09 07:14:56 +09:00
|
|
|
},
|
2011-10-25 08:55:23 +09:00
|
|
|
|
2014-03-23 03:15:51 +09:00
|
|
|
buildPaintImageXObject:
|
|
|
|
function PartialEvaluator_buildPaintImageXObject(resources, image,
|
|
|
|
inline, operatorList,
|
2014-10-27 01:03:44 +09:00
|
|
|
cacheKey, imageCache) {
|
2011-10-25 08:55:23 +09:00
|
|
|
var self = this;
|
2013-04-09 07:14:56 +09:00
|
|
|
var dict = image.dict;
|
|
|
|
var w = dict.get('Width', 'W');
|
|
|
|
var h = dict.get('Height', 'H');
|
2011-10-25 08:55:23 +09:00
|
|
|
|
2014-04-09 20:04:27 +09:00
|
|
|
if (!(w && isNum(w)) || !(h && isNum(h))) {
|
|
|
|
warn('Image dimensions are missing, or not numbers.');
|
|
|
|
return;
|
|
|
|
}
|
2013-07-11 01:52:37 +09:00
|
|
|
if (PDFJS.maxImageSize !== -1 && w * h > PDFJS.maxImageSize) {
|
|
|
|
warn('Image exceeded maximum allowed size and was removed.');
|
2013-08-01 03:17:36 +09:00
|
|
|
return;
|
2013-07-11 01:52:37 +09:00
|
|
|
}
|
|
|
|
|
2014-03-23 03:15:51 +09:00
|
|
|
var imageMask = (dict.get('ImageMask', 'IM') || false);
|
2014-04-08 06:42:54 +09:00
|
|
|
var imgData, args;
|
2013-04-09 07:14:56 +09:00
|
|
|
if (imageMask) {
|
2014-03-23 03:15:51 +09:00
|
|
|
// This depends on a tmpCanvas being filled with the
|
2013-04-09 07:14:56 +09:00
|
|
|
// 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.
|
|
|
|
|
|
|
|
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');
|
2014-03-23 03:15:51 +09:00
|
|
|
var inverseDecode = (!!decode && decode[0] > 0);
|
2013-04-09 07:14:56 +09:00
|
|
|
|
2014-04-08 06:42:54 +09:00
|
|
|
imgData = PDFImage.createMask(imgArray, width, height,
|
2014-06-13 09:37:41 +09:00
|
|
|
image instanceof DecodeStream,
|
|
|
|
inverseDecode);
|
2014-02-25 00:59:02 +09:00
|
|
|
imgData.cached = true;
|
2014-04-08 06:42:54 +09:00
|
|
|
args = [imgData];
|
2014-02-25 00:59:02 +09:00
|
|
|
operatorList.addOp(OPS.paintImageMaskXObject, args);
|
|
|
|
if (cacheKey) {
|
2014-10-27 01:03:44 +09:00
|
|
|
imageCache[cacheKey] = {
|
|
|
|
fn: OPS.paintImageMaskXObject,
|
|
|
|
args: args
|
|
|
|
};
|
2014-02-25 00:59:02 +09:00
|
|
|
}
|
2013-08-01 03:17:36 +09:00
|
|
|
return;
|
2013-04-09 07:14:56 +09:00
|
|
|
}
|
|
|
|
|
2014-03-23 03:15:51 +09:00
|
|
|
var softMask = (dict.get('SMask', 'SM') || false);
|
|
|
|
var mask = (dict.get('Mask') || false);
|
2013-04-09 07:14:56 +09:00
|
|
|
|
|
|
|
var SMALL_IMAGE_DIMENSIONS = 200;
|
|
|
|
// Inlining small images into the queue as RGB data
|
2014-03-23 03:15:51 +09:00
|
|
|
if (inline && !softMask && !mask && !(image instanceof JpegStream) &&
|
2013-04-09 07:14:56 +09:00
|
|
|
(w + h) < SMALL_IMAGE_DIMENSIONS) {
|
|
|
|
var imageObj = new PDFImage(this.xref, resources, image,
|
|
|
|
inline, null, null);
|
2014-02-26 08:11:15 +09:00
|
|
|
// We force the use of RGBA_32BPP images here, because we can't handle
|
|
|
|
// any other kind.
|
2014-04-08 06:42:54 +09:00
|
|
|
imgData = imageObj.createImageData(/* forceRGBA = */ true);
|
2013-11-14 04:43:38 +09:00
|
|
|
operatorList.addOp(OPS.paintInlineImageXObject, [imgData]);
|
2013-08-01 03:17:36 +09:00
|
|
|
return;
|
2011-10-25 08:55:23 +09:00
|
|
|
}
|
|
|
|
|
2013-04-09 07:14:56 +09:00
|
|
|
// If there is no imageMask, create the PDFImage and a lot
|
|
|
|
// of image processing can be done here.
|
2014-03-23 03:15:51 +09:00
|
|
|
var uniquePrefix = (this.uniquePrefix || '');
|
2013-04-23 06:20:49 +09:00
|
|
|
var objId = 'img_' + uniquePrefix + (++this.idCounters.obj);
|
2013-08-01 03:17:36 +09:00
|
|
|
operatorList.addDependency(objId);
|
2014-04-08 06:42:54 +09:00
|
|
|
args = [objId, w, h];
|
2013-04-09 07:14:56 +09:00
|
|
|
|
|
|
|
if (!softMask && !mask && image instanceof JpegStream &&
|
|
|
|
image.isNativelySupported(this.xref, resources)) {
|
|
|
|
// These JPEGs don't need any more processing so we can just send it.
|
2013-11-14 04:43:38 +09:00
|
|
|
operatorList.addOp(OPS.paintJpegXObject, args);
|
2014-03-23 03:15:51 +09:00
|
|
|
this.handler.send('obj',
|
|
|
|
[objId, this.pageIndex, 'JpegStream', image.getIR()]);
|
2013-08-01 03:17:36 +09:00
|
|
|
return;
|
2013-04-09 07:14:56 +09:00
|
|
|
}
|
|
|
|
|
2014-04-15 05:22:35 +09:00
|
|
|
PDFImage.buildImage(self.handler, self.xref, resources, image, inline).
|
|
|
|
then(function(imageObj) {
|
2014-02-25 12:37:19 +09:00
|
|
|
var imgData = imageObj.createImageData(/* forceRGBA = */ false);
|
2013-11-12 12:30:26 +09:00
|
|
|
self.handler.send('obj', [objId, self.pageIndex, 'Image', imgData],
|
2014-05-08 08:15:25 +09:00
|
|
|
[imgData.data.buffer]);
|
2014-11-25 21:40:27 +09:00
|
|
|
}).then(undefined, function (reason) {
|
2014-04-15 05:22:35 +09:00
|
|
|
warn('Unable to decode image: ' + reason);
|
|
|
|
self.handler.send('obj', [objId, self.pageIndex, 'Image', null]);
|
|
|
|
});
|
2014-03-23 03:15:51 +09:00
|
|
|
|
2013-11-14 04:43:38 +09:00
|
|
|
operatorList.addOp(OPS.paintImageXObject, args);
|
2014-02-24 23:00:08 +09:00
|
|
|
if (cacheKey) {
|
2014-10-27 01:03:44 +09:00
|
|
|
imageCache[cacheKey] = {
|
|
|
|
fn: OPS.paintImageXObject,
|
|
|
|
args: args
|
|
|
|
};
|
2014-02-24 23:00:08 +09:00
|
|
|
}
|
2013-04-09 07:14:56 +09:00
|
|
|
},
|
|
|
|
|
2014-01-24 02:13:32 +09:00
|
|
|
handleSMask: function PartialEvaluator_handleSmask(smask, resources,
|
2015-10-21 10:50:32 +09:00
|
|
|
operatorList, task,
|
2014-04-10 08:44:07 +09:00
|
|
|
stateManager) {
|
2014-01-24 02:13:32 +09:00
|
|
|
var smaskContent = smask.get('G');
|
|
|
|
var smaskOptions = {
|
|
|
|
subtype: smask.get('S').name,
|
|
|
|
backdrop: smask.get('BC')
|
|
|
|
};
|
2014-05-10 10:21:15 +09:00
|
|
|
return this.buildFormXObject(resources, smaskContent, smaskOptions,
|
2015-10-21 10:50:32 +09:00
|
|
|
operatorList, task, stateManager.state.clone());
|
2014-01-24 02:13:32 +09:00
|
|
|
},
|
|
|
|
|
2014-03-23 03:15:51 +09:00
|
|
|
handleTilingType:
|
|
|
|
function PartialEvaluator_handleTilingType(fn, args, resources,
|
|
|
|
pattern, patternDict,
|
2015-10-21 10:50:32 +09:00
|
|
|
operatorList, task) {
|
2013-04-09 07:14:56 +09:00
|
|
|
// Create an IR of the pattern code.
|
2014-05-10 10:21:15 +09:00
|
|
|
var tilingOpList = new OperatorList();
|
2015-10-21 03:22:06 +09:00
|
|
|
// Merge the available resources, to prevent issues when the patternDict
|
|
|
|
// is missing some /Resources entries (fixes issue6541.pdf).
|
|
|
|
var resourcesArray = [patternDict.get('Resources'), resources];
|
|
|
|
var patternResources = Dict.merge(this.xref, resourcesArray);
|
|
|
|
|
2015-10-21 10:50:32 +09:00
|
|
|
return this.getOperatorList(pattern, task, patternResources,
|
|
|
|
tilingOpList).then(function () {
|
2014-05-10 10:21:15 +09:00
|
|
|
// Add the dependencies to the parent operator list so they are
|
|
|
|
// resolved before sub operator list is executed synchronously.
|
|
|
|
operatorList.addDependencies(tilingOpList.dependencies);
|
|
|
|
operatorList.addOp(fn, getTilingPatternIR({
|
|
|
|
fnArray: tilingOpList.fnArray,
|
|
|
|
argsArray: tilingOpList.argsArray
|
|
|
|
}, patternDict, args));
|
|
|
|
});
|
2013-04-09 07:14:56 +09:00
|
|
|
},
|
2012-09-14 00:09:46 +09:00
|
|
|
|
2014-03-23 03:15:51 +09:00
|
|
|
handleSetFont:
|
|
|
|
function PartialEvaluator_handleSetFont(resources, fontArgs, fontRef,
|
2015-10-21 10:50:32 +09:00
|
|
|
operatorList, task, state) {
|
2013-04-09 07:14:56 +09:00
|
|
|
// TODO(mack): Not needed?
|
|
|
|
var fontName;
|
|
|
|
if (fontArgs) {
|
|
|
|
fontArgs = fontArgs.slice();
|
|
|
|
fontName = fontArgs[0].name;
|
|
|
|
}
|
2013-08-01 03:17:36 +09:00
|
|
|
|
2014-05-20 06:27:54 +09:00
|
|
|
var self = this;
|
|
|
|
return this.loadFont(fontName, fontRef, this.xref, resources).then(
|
|
|
|
function (translated) {
|
|
|
|
if (!translated.font.isType3Font) {
|
|
|
|
return translated;
|
|
|
|
}
|
2015-10-21 10:50:32 +09:00
|
|
|
return translated.loadType3Data(self, resources, operatorList, task).
|
|
|
|
then(function () {
|
2014-05-20 06:27:54 +09:00
|
|
|
return translated;
|
2014-05-10 10:21:15 +09:00
|
|
|
});
|
2014-05-20 06:27:54 +09:00
|
|
|
}).then(function (translated) {
|
|
|
|
state.font = translated.font;
|
|
|
|
translated.send(self.handler);
|
|
|
|
return translated.loadedName;
|
|
|
|
});
|
2013-04-09 07:14:56 +09:00
|
|
|
},
|
2013-03-13 09:20:38 +09:00
|
|
|
|
2014-04-10 08:44:07 +09:00
|
|
|
handleText: function PartialEvaluator_handleText(chars, state) {
|
2014-05-20 06:27:54 +09:00
|
|
|
var font = state.font;
|
2013-08-20 08:33:20 +09:00
|
|
|
var glyphs = font.charsToGlyphs(chars);
|
2014-04-10 08:44:07 +09:00
|
|
|
var isAddToPathSet = !!(state.textRenderingMode &
|
2013-08-20 08:33:20 +09:00
|
|
|
TextRenderingMode.ADD_TO_PATH_FLAG);
|
2013-08-23 04:55:43 +09:00
|
|
|
if (font.data && (isAddToPathSet || PDFJS.disableFontFace)) {
|
2014-05-04 00:28:30 +09:00
|
|
|
var buildPath = function (fontChar) {
|
2013-08-20 08:33:20 +09:00
|
|
|
if (!font.renderer.hasBuiltPath(fontChar)) {
|
|
|
|
var path = font.renderer.getPathJs(fontChar);
|
|
|
|
this.handler.send('commonobj', [
|
|
|
|
font.loadedName + '_path_' + fontChar,
|
|
|
|
'FontPath',
|
|
|
|
path
|
|
|
|
]);
|
|
|
|
}
|
2014-05-04 00:28:30 +09:00
|
|
|
}.bind(this);
|
|
|
|
|
|
|
|
for (var i = 0, ii = glyphs.length; i < ii; i++) {
|
|
|
|
var glyph = glyphs[i];
|
|
|
|
if (glyph === null) {
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
buildPath(glyph.fontChar);
|
|
|
|
|
|
|
|
// If the glyph has an accent we need to build a path for its
|
|
|
|
// fontChar too, otherwise CanvasGraphics_paintChar will fail.
|
|
|
|
var accent = glyph.accent;
|
|
|
|
if (accent && accent.fontChar) {
|
|
|
|
buildPath(accent.fontChar);
|
|
|
|
}
|
2013-08-20 08:33:20 +09:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
return glyphs;
|
|
|
|
},
|
|
|
|
|
2013-08-01 03:17:36 +09:00
|
|
|
setGState: function PartialEvaluator_setGState(resources, gState,
|
2015-10-21 10:50:32 +09:00
|
|
|
operatorList, task,
|
|
|
|
xref, stateManager) {
|
2014-08-18 14:21:45 +09:00
|
|
|
// This array holds the converted/processed state data.
|
|
|
|
var gStateObj = [];
|
|
|
|
var gStateMap = gState.map;
|
|
|
|
var self = this;
|
|
|
|
var promise = Promise.resolve();
|
|
|
|
for (var key in gStateMap) {
|
|
|
|
var value = gStateMap[key];
|
2013-04-09 07:14:56 +09:00
|
|
|
switch (key) {
|
|
|
|
case 'Type':
|
|
|
|
break;
|
|
|
|
case 'LW':
|
|
|
|
case 'LC':
|
|
|
|
case 'LJ':
|
|
|
|
case 'ML':
|
|
|
|
case 'D':
|
|
|
|
case 'RI':
|
|
|
|
case 'FL':
|
|
|
|
case 'CA':
|
|
|
|
case 'ca':
|
|
|
|
gStateObj.push([key, value]);
|
|
|
|
break;
|
|
|
|
case 'Font':
|
2014-05-10 10:21:15 +09:00
|
|
|
promise = promise.then(function () {
|
2015-10-21 10:50:32 +09:00
|
|
|
return self.handleSetFont(resources, null, value[0], operatorList,
|
|
|
|
task, stateManager.state).
|
2014-05-10 10:21:15 +09:00
|
|
|
then(function (loadedName) {
|
|
|
|
operatorList.addDependency(loadedName);
|
|
|
|
gStateObj.push([key, [loadedName, value[1]]]);
|
|
|
|
});
|
|
|
|
});
|
2013-04-09 07:14:56 +09:00
|
|
|
break;
|
|
|
|
case 'BM':
|
|
|
|
gStateObj.push([key, value]);
|
|
|
|
break;
|
|
|
|
case 'SMask':
|
2014-01-24 02:13:32 +09:00
|
|
|
if (isName(value) && value.name === 'None') {
|
|
|
|
gStateObj.push([key, false]);
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
var dict = xref.fetchIfRef(value);
|
|
|
|
if (isDict(dict)) {
|
2014-05-10 10:21:15 +09:00
|
|
|
promise = promise.then(function () {
|
|
|
|
return self.handleSMask(dict, resources, operatorList,
|
2015-10-21 10:50:32 +09:00
|
|
|
task, stateManager);
|
2014-05-10 10:21:15 +09:00
|
|
|
});
|
2014-01-24 02:13:32 +09:00
|
|
|
gStateObj.push([key, true]);
|
|
|
|
} else {
|
|
|
|
warn('Unsupported SMask type');
|
|
|
|
}
|
|
|
|
|
2013-04-09 07:14:56 +09:00
|
|
|
break;
|
|
|
|
// Only generate info log messages for the following since
|
2014-08-15 12:34:53 +09:00
|
|
|
// they are unlikely to have a big impact on the rendering.
|
2013-04-09 07:14:56 +09:00
|
|
|
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 'AIS':
|
|
|
|
case 'TK':
|
|
|
|
// TODO implement these operators.
|
|
|
|
info('graphic state operator ' + key);
|
|
|
|
break;
|
|
|
|
default:
|
|
|
|
info('Unknown graphic state operator ' + key);
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
}
|
2014-05-10 10:21:15 +09:00
|
|
|
return promise.then(function () {
|
2014-08-15 12:34:53 +09:00
|
|
|
if (gStateObj.length >= 0) {
|
|
|
|
operatorList.addOp(OPS.setGState, [gStateObj]);
|
|
|
|
}
|
2014-05-10 10:21:15 +09:00
|
|
|
});
|
2013-04-09 07:14:56 +09:00
|
|
|
},
|
2012-12-08 03:19:43 +09:00
|
|
|
|
2013-04-09 07:14:56 +09:00
|
|
|
loadFont: function PartialEvaluator_loadFont(fontName, font, xref,
|
2014-05-20 06:27:54 +09:00
|
|
|
resources) {
|
2013-08-01 03:17:36 +09:00
|
|
|
|
|
|
|
function errorFont() {
|
2014-05-20 06:27:54 +09:00
|
|
|
return Promise.resolve(new TranslatedFont('g_font_error',
|
|
|
|
new ErrorFont('Font ' + fontName + ' is not available'), font));
|
2011-10-25 08:55:23 +09:00
|
|
|
}
|
2013-06-26 02:33:53 +09:00
|
|
|
var fontRef;
|
|
|
|
if (font) { // Loading by ref.
|
|
|
|
assert(isRef(font));
|
|
|
|
fontRef = font;
|
|
|
|
} else { // Loading by name.
|
|
|
|
var fontRes = resources.get('Font');
|
|
|
|
if (fontRes) {
|
|
|
|
fontRef = fontRes.getRaw(fontName);
|
|
|
|
} else {
|
|
|
|
warn('fontRes not available');
|
2013-08-01 03:17:36 +09:00
|
|
|
return errorFont();
|
2013-06-26 02:33:53 +09:00
|
|
|
}
|
|
|
|
}
|
2014-06-12 19:46:39 +09:00
|
|
|
if (!fontRef) {
|
|
|
|
warn('fontRef not available');
|
|
|
|
return errorFont();
|
|
|
|
}
|
|
|
|
|
2013-06-26 02:33:53 +09:00
|
|
|
if (this.fontCache.has(fontRef)) {
|
|
|
|
return this.fontCache.get(fontRef);
|
|
|
|
}
|
|
|
|
|
|
|
|
font = xref.fetchIfRef(fontRef);
|
|
|
|
if (!isDict(font)) {
|
2013-08-01 03:17:36 +09:00
|
|
|
return errorFont();
|
2013-05-04 03:13:45 +09:00
|
|
|
}
|
2014-03-04 02:44:45 +09:00
|
|
|
|
2014-05-20 06:27:54 +09:00
|
|
|
// We are holding font.translated references just for fontRef that are not
|
|
|
|
// dictionaries (Dict). See explanation below.
|
|
|
|
if (font.translated) {
|
|
|
|
return font.translated;
|
|
|
|
}
|
|
|
|
|
2014-05-10 10:21:15 +09:00
|
|
|
var fontCapability = createPromiseCapability();
|
|
|
|
|
2014-03-04 02:44:45 +09:00
|
|
|
var preEvaluatedFont = this.preEvaluateFont(font, xref);
|
|
|
|
var descriptor = preEvaluatedFont.descriptor;
|
|
|
|
var fontID = fontRef.num + '_' + fontRef.gen;
|
|
|
|
if (isDict(descriptor)) {
|
|
|
|
if (!descriptor.fontAliases) {
|
|
|
|
descriptor.fontAliases = Object.create(null);
|
|
|
|
}
|
|
|
|
|
|
|
|
var fontAliases = descriptor.fontAliases;
|
|
|
|
var hash = preEvaluatedFont.hash;
|
|
|
|
if (fontAliases[hash]) {
|
|
|
|
var aliasFontRef = fontAliases[hash].aliasRef;
|
|
|
|
if (aliasFontRef && this.fontCache.has(aliasFontRef)) {
|
|
|
|
this.fontCache.putAlias(fontRef, aliasFontRef);
|
2014-05-20 06:27:54 +09:00
|
|
|
return this.fontCache.get(fontRef);
|
2014-03-04 02:44:45 +09:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
if (!fontAliases[hash]) {
|
|
|
|
fontAliases[hash] = {
|
|
|
|
fontID: Font.getFontID()
|
|
|
|
};
|
|
|
|
}
|
|
|
|
|
|
|
|
fontAliases[hash].aliasRef = fontRef;
|
|
|
|
fontID = fontAliases[hash].fontID;
|
|
|
|
}
|
|
|
|
|
2014-03-23 03:15:51 +09:00
|
|
|
// Workaround for bad PDF generators that don't reference fonts
|
2013-12-17 08:19:31 +09:00
|
|
|
// properly, i.e. by not using an object identifier.
|
|
|
|
// Check if the fontRef is a Dict (as opposed to a standard object),
|
|
|
|
// in which case we don't cache the font and instead reference it by
|
|
|
|
// fontName in font.loadedName below.
|
|
|
|
var fontRefIsDict = isDict(fontRef);
|
|
|
|
if (!fontRefIsDict) {
|
2014-05-10 10:21:15 +09:00
|
|
|
this.fontCache.put(fontRef, fontCapability.promise);
|
2013-12-17 08:19:31 +09:00
|
|
|
}
|
2013-05-04 03:13:45 +09:00
|
|
|
|
2014-03-23 03:15:51 +09:00
|
|
|
// Keep track of each font we translated so the caller can
|
|
|
|
// load them asynchronously before calling display on a page.
|
2013-12-17 08:19:31 +09:00
|
|
|
font.loadedName = 'g_font_' + (fontRefIsDict ?
|
2014-03-04 02:44:45 +09:00
|
|
|
fontName.replace(/\W/g, '') : fontID);
|
2012-02-21 22:28:42 +09:00
|
|
|
|
2014-05-20 06:27:54 +09:00
|
|
|
font.translated = fontCapability.promise;
|
2013-04-09 07:14:56 +09:00
|
|
|
|
2014-05-20 06:27:54 +09:00
|
|
|
// TODO move promises into translate font
|
|
|
|
var translatedPromise;
|
|
|
|
try {
|
|
|
|
translatedPromise = Promise.resolve(
|
|
|
|
this.translateFont(preEvaluatedFont, xref));
|
|
|
|
} catch (e) {
|
|
|
|
translatedPromise = Promise.reject(e);
|
2011-10-25 08:55:23 +09:00
|
|
|
}
|
2014-05-20 06:27:54 +09:00
|
|
|
|
|
|
|
translatedPromise.then(function (translatedFont) {
|
2014-06-16 23:52:04 +09:00
|
|
|
if (translatedFont.fontType !== undefined) {
|
|
|
|
var xrefFontStats = xref.stats.fontTypes;
|
|
|
|
xrefFontStats[translatedFont.fontType] = true;
|
|
|
|
}
|
|
|
|
|
2014-05-20 06:27:54 +09:00
|
|
|
fontCapability.resolve(new TranslatedFont(font.loadedName,
|
|
|
|
translatedFont, font));
|
|
|
|
}, function (reason) {
|
|
|
|
// TODO fontCapability.reject?
|
|
|
|
UnsupportedManager.notify(UNSUPPORTED_FEATURES.font);
|
2014-06-16 23:52:04 +09:00
|
|
|
|
|
|
|
try {
|
|
|
|
// error, but it's still nice to have font type reported
|
|
|
|
var descriptor = preEvaluatedFont.descriptor;
|
|
|
|
var fontFile3 = descriptor && descriptor.get('FontFile3');
|
|
|
|
var subtype = fontFile3 && fontFile3.get('Subtype');
|
|
|
|
var fontType = getFontType(preEvaluatedFont.type,
|
|
|
|
subtype && subtype.name);
|
|
|
|
var xrefFontStats = xref.stats.fontTypes;
|
|
|
|
xrefFontStats[fontType] = true;
|
|
|
|
} catch (ex) { }
|
|
|
|
|
2014-05-20 06:27:54 +09:00
|
|
|
fontCapability.resolve(new TranslatedFont(font.loadedName,
|
|
|
|
new ErrorFont(reason instanceof Error ? reason.message : reason),
|
|
|
|
font));
|
|
|
|
});
|
|
|
|
return fontCapability.promise;
|
2013-04-09 07:14:56 +09:00
|
|
|
},
|
|
|
|
|
2014-04-30 23:09:04 +09:00
|
|
|
buildPath: function PartialEvaluator_buildPath(operatorList, fn, args) {
|
|
|
|
var lastIndex = operatorList.length - 1;
|
2014-08-08 19:50:54 +09:00
|
|
|
if (!args) {
|
|
|
|
args = [];
|
|
|
|
}
|
2014-04-30 23:09:04 +09:00
|
|
|
if (lastIndex < 0 ||
|
|
|
|
operatorList.fnArray[lastIndex] !== OPS.constructPath) {
|
|
|
|
operatorList.addOp(OPS.constructPath, [[fn], args]);
|
|
|
|
} else {
|
|
|
|
var opArgs = operatorList.argsArray[lastIndex];
|
|
|
|
opArgs[0].push(fn);
|
|
|
|
Array.prototype.push.apply(opArgs[1], args);
|
|
|
|
}
|
|
|
|
},
|
|
|
|
|
2014-05-22 02:47:42 +09:00
|
|
|
handleColorN: function PartialEvaluator_handleColorN(operatorList, fn, args,
|
2015-10-21 10:50:32 +09:00
|
|
|
cs, patterns, resources, task, xref) {
|
2014-05-22 02:47:42 +09:00
|
|
|
// compile tiling patterns
|
|
|
|
var patternName = args[args.length - 1];
|
|
|
|
// SCN/scn applies patterns along with normal colors
|
|
|
|
var pattern;
|
|
|
|
if (isName(patternName) &&
|
|
|
|
(pattern = patterns.get(patternName.name))) {
|
|
|
|
var dict = (isStream(pattern) ? pattern.dict : pattern);
|
|
|
|
var typeNum = dict.get('PatternType');
|
|
|
|
|
2014-06-02 19:43:20 +09:00
|
|
|
if (typeNum === TILING_PATTERN) {
|
2014-05-22 02:47:42 +09:00
|
|
|
var color = cs.base ? cs.base.getRgb(args, 0) : null;
|
|
|
|
return this.handleTilingType(fn, color, resources, pattern,
|
2015-10-21 10:50:32 +09:00
|
|
|
dict, operatorList, task);
|
2014-06-02 19:43:20 +09:00
|
|
|
} else if (typeNum === SHADING_PATTERN) {
|
2014-05-22 02:47:42 +09:00
|
|
|
var shading = dict.get('Shading');
|
|
|
|
var matrix = dict.get('Matrix');
|
|
|
|
pattern = Pattern.parseShading(shading, matrix, xref, resources);
|
|
|
|
operatorList.addOp(fn, pattern.getIR());
|
|
|
|
return Promise.resolve();
|
|
|
|
} else {
|
|
|
|
return Promise.reject('Unknown PatternType: ' + typeNum);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
// TODO shall we fail here?
|
|
|
|
operatorList.addOp(fn, args);
|
|
|
|
return Promise.resolve();
|
|
|
|
},
|
|
|
|
|
2013-04-09 07:14:56 +09:00
|
|
|
getOperatorList: function PartialEvaluator_getOperatorList(stream,
|
2015-10-21 10:50:32 +09:00
|
|
|
task,
|
2013-08-01 03:17:36 +09:00
|
|
|
resources,
|
2014-01-17 22:16:52 +09:00
|
|
|
operatorList,
|
2014-04-10 08:44:07 +09:00
|
|
|
initialState) {
|
2013-04-09 07:14:56 +09:00
|
|
|
|
|
|
|
var self = this;
|
|
|
|
var xref = this.xref;
|
2014-02-24 23:00:08 +09:00
|
|
|
var imageCache = {};
|
2011-10-25 08:55:23 +09:00
|
|
|
|
2014-05-10 10:21:15 +09:00
|
|
|
assert(operatorList);
|
2011-10-25 08:55:23 +09:00
|
|
|
|
2014-03-26 23:07:38 +09:00
|
|
|
resources = (resources || Dict.empty);
|
|
|
|
var xobjs = (resources.get('XObject') || Dict.empty);
|
|
|
|
var patterns = (resources.get('Pattern') || Dict.empty);
|
2014-04-10 08:44:07 +09:00
|
|
|
var stateManager = new StateManager(initialState || new EvalState());
|
|
|
|
var preprocessor = new EvaluatorPreprocessor(stream, xref, stateManager);
|
2014-05-10 10:41:03 +09:00
|
|
|
var timeSlotManager = new TimeSlotManager();
|
2014-05-10 10:21:15 +09:00
|
|
|
|
|
|
|
return new Promise(function next(resolve, reject) {
|
2015-10-21 10:50:32 +09:00
|
|
|
task.ensureNotTerminated();
|
2014-05-10 10:41:03 +09:00
|
|
|
timeSlotManager.reset();
|
2014-06-19 00:15:35 +09:00
|
|
|
var stop, operation = {}, i, ii, cs;
|
2014-08-11 09:23:23 +09:00
|
|
|
while (!(stop = timeSlotManager.check())) {
|
|
|
|
// The arguments parsed by read() are used beyond this loop, so we
|
|
|
|
// cannot reuse the same array on each iteration. Therefore we pass
|
|
|
|
// in |null| as the initial value (see the comment on
|
|
|
|
// EvaluatorPreprocessor_read() for why).
|
|
|
|
operation.args = null;
|
|
|
|
if (!(preprocessor.read(operation))) {
|
|
|
|
break;
|
|
|
|
}
|
2014-05-10 10:21:15 +09:00
|
|
|
var args = operation.args;
|
|
|
|
var fn = operation.fn;
|
|
|
|
|
|
|
|
switch (fn | 0) {
|
|
|
|
case OPS.paintXObject:
|
|
|
|
if (args[0].code) {
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
// eagerly compile XForm objects
|
|
|
|
var name = args[0].name;
|
2015-06-22 20:33:15 +09:00
|
|
|
if (!name) {
|
|
|
|
warn('XObject must be referred to by name.');
|
|
|
|
continue;
|
|
|
|
}
|
2014-10-27 01:03:44 +09:00
|
|
|
if (imageCache[name] !== undefined) {
|
|
|
|
operatorList.addOp(imageCache[name].fn, imageCache[name].args);
|
2014-06-20 12:52:39 +09:00
|
|
|
args = null;
|
2014-02-24 23:00:08 +09:00
|
|
|
continue;
|
|
|
|
}
|
2014-03-23 03:15:51 +09:00
|
|
|
|
2014-05-10 10:21:15 +09:00
|
|
|
var xobj = xobjs.get(name);
|
|
|
|
if (xobj) {
|
|
|
|
assert(isStream(xobj), 'XObject should be a stream');
|
|
|
|
|
|
|
|
var type = xobj.dict.get('Subtype');
|
|
|
|
assert(isName(type),
|
|
|
|
'XObject should have a Name subtype');
|
|
|
|
|
2014-05-29 06:20:08 +09:00
|
|
|
if (type.name === 'Form') {
|
2014-05-10 10:21:15 +09:00
|
|
|
stateManager.save();
|
|
|
|
return self.buildFormXObject(resources, xobj, null,
|
2015-10-21 10:50:32 +09:00
|
|
|
operatorList, task,
|
2014-05-10 10:21:15 +09:00
|
|
|
stateManager.state.clone()).
|
|
|
|
then(function () {
|
|
|
|
stateManager.restore();
|
|
|
|
next(resolve, reject);
|
|
|
|
}, reject);
|
2014-05-29 06:20:08 +09:00
|
|
|
} else if (type.name === 'Image') {
|
2014-05-10 10:21:15 +09:00
|
|
|
self.buildPaintImageXObject(resources, xobj, false,
|
|
|
|
operatorList, name, imageCache);
|
2014-06-20 12:52:39 +09:00
|
|
|
args = null;
|
2014-05-10 10:21:15 +09:00
|
|
|
continue;
|
2014-05-29 06:20:08 +09:00
|
|
|
} else if (type.name === 'PS') {
|
|
|
|
// PostScript XObjects are unused when viewing documents.
|
|
|
|
// See section 4.7.1 of Adobe's PDF reference.
|
|
|
|
info('Ignored XObject subtype PS');
|
|
|
|
continue;
|
2014-05-10 10:21:15 +09:00
|
|
|
} else {
|
|
|
|
error('Unhandled XObject subtype ' + type.name);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
break;
|
|
|
|
case OPS.setFont:
|
|
|
|
var fontSize = args[1];
|
|
|
|
// eagerly collect all fonts
|
2015-10-21 10:50:32 +09:00
|
|
|
return self.handleSetFont(resources, args, null, operatorList,
|
|
|
|
task, stateManager.state).
|
2014-05-10 10:21:15 +09:00
|
|
|
then(function (loadedName) {
|
|
|
|
operatorList.addDependency(loadedName);
|
|
|
|
operatorList.addOp(OPS.setFont, [loadedName, fontSize]);
|
|
|
|
next(resolve, reject);
|
|
|
|
}, reject);
|
|
|
|
case OPS.endInlineImage:
|
|
|
|
var cacheKey = args[0].cacheKey;
|
2014-10-27 01:03:44 +09:00
|
|
|
if (cacheKey) {
|
|
|
|
var cacheEntry = imageCache[cacheKey];
|
|
|
|
if (cacheEntry !== undefined) {
|
|
|
|
operatorList.addOp(cacheEntry.fn, cacheEntry.args);
|
|
|
|
args = null;
|
|
|
|
continue;
|
|
|
|
}
|
2014-02-25 00:59:02 +09:00
|
|
|
}
|
2014-05-10 10:21:15 +09:00
|
|
|
self.buildPaintImageXObject(resources, args[0], true,
|
|
|
|
operatorList, cacheKey, imageCache);
|
2014-06-20 12:52:39 +09:00
|
|
|
args = null;
|
2013-11-19 09:50:58 +09:00
|
|
|
continue;
|
2014-05-10 10:21:15 +09:00
|
|
|
case OPS.showText:
|
|
|
|
args[0] = self.handleText(args[0], stateManager.state);
|
|
|
|
break;
|
|
|
|
case OPS.showSpacedText:
|
|
|
|
var arr = args[0];
|
2014-05-24 03:36:54 +09:00
|
|
|
var combinedGlyphs = [];
|
2014-05-10 10:21:15 +09:00
|
|
|
var arrLength = arr.length;
|
2015-08-28 20:42:01 +09:00
|
|
|
var state = stateManager.state;
|
2014-05-10 10:21:15 +09:00
|
|
|
for (i = 0; i < arrLength; ++i) {
|
2014-05-24 03:36:54 +09:00
|
|
|
var arrItem = arr[i];
|
|
|
|
if (isString(arrItem)) {
|
|
|
|
Array.prototype.push.apply(combinedGlyphs,
|
2015-08-28 20:42:01 +09:00
|
|
|
self.handleText(arrItem, state));
|
2014-05-24 03:36:54 +09:00
|
|
|
} else if (isNum(arrItem)) {
|
|
|
|
combinedGlyphs.push(arrItem);
|
2014-05-10 10:21:15 +09:00
|
|
|
}
|
|
|
|
}
|
2014-05-24 03:36:54 +09:00
|
|
|
args[0] = combinedGlyphs;
|
|
|
|
fn = OPS.showText;
|
2014-05-10 10:21:15 +09:00
|
|
|
break;
|
|
|
|
case OPS.nextLineShowText:
|
2014-05-24 03:36:54 +09:00
|
|
|
operatorList.addOp(OPS.nextLine);
|
2014-05-10 10:21:15 +09:00
|
|
|
args[0] = self.handleText(args[0], stateManager.state);
|
2014-05-24 03:36:54 +09:00
|
|
|
fn = OPS.showText;
|
2014-05-10 10:21:15 +09:00
|
|
|
break;
|
|
|
|
case OPS.nextLineSetSpacingShowText:
|
2014-05-24 03:36:54 +09:00
|
|
|
operatorList.addOp(OPS.nextLine);
|
|
|
|
operatorList.addOp(OPS.setWordSpacing, [args.shift()]);
|
|
|
|
operatorList.addOp(OPS.setCharSpacing, [args.shift()]);
|
|
|
|
args[0] = self.handleText(args[0], stateManager.state);
|
|
|
|
fn = OPS.showText;
|
2014-05-10 10:21:15 +09:00
|
|
|
break;
|
|
|
|
case OPS.setTextRenderingMode:
|
|
|
|
stateManager.state.textRenderingMode = args[0];
|
|
|
|
break;
|
2014-05-22 02:47:42 +09:00
|
|
|
|
2014-05-10 10:21:15 +09:00
|
|
|
case OPS.setFillColorSpace:
|
2014-05-22 02:47:42 +09:00
|
|
|
stateManager.state.fillColorSpace =
|
|
|
|
ColorSpace.parse(args[0], xref, resources);
|
|
|
|
continue;
|
2014-05-10 10:21:15 +09:00
|
|
|
case OPS.setStrokeColorSpace:
|
2014-05-22 02:47:42 +09:00
|
|
|
stateManager.state.strokeColorSpace =
|
|
|
|
ColorSpace.parse(args[0], xref, resources);
|
|
|
|
continue;
|
|
|
|
case OPS.setFillColor:
|
|
|
|
cs = stateManager.state.fillColorSpace;
|
|
|
|
args = cs.getRgb(args, 0);
|
|
|
|
fn = OPS.setFillRGBColor;
|
|
|
|
break;
|
|
|
|
case OPS.setStrokeColor:
|
|
|
|
cs = stateManager.state.strokeColorSpace;
|
|
|
|
args = cs.getRgb(args, 0);
|
|
|
|
fn = OPS.setStrokeRGBColor;
|
|
|
|
break;
|
|
|
|
case OPS.setFillGray:
|
|
|
|
stateManager.state.fillColorSpace = ColorSpace.singletons.gray;
|
|
|
|
args = ColorSpace.singletons.gray.getRgb(args, 0);
|
|
|
|
fn = OPS.setFillRGBColor;
|
|
|
|
break;
|
|
|
|
case OPS.setStrokeGray:
|
|
|
|
stateManager.state.strokeColorSpace = ColorSpace.singletons.gray;
|
|
|
|
args = ColorSpace.singletons.gray.getRgb(args, 0);
|
|
|
|
fn = OPS.setStrokeRGBColor;
|
|
|
|
break;
|
|
|
|
case OPS.setFillCMYKColor:
|
|
|
|
stateManager.state.fillColorSpace = ColorSpace.singletons.cmyk;
|
|
|
|
args = ColorSpace.singletons.cmyk.getRgb(args, 0);
|
|
|
|
fn = OPS.setFillRGBColor;
|
|
|
|
break;
|
|
|
|
case OPS.setStrokeCMYKColor:
|
|
|
|
stateManager.state.strokeColorSpace = ColorSpace.singletons.cmyk;
|
|
|
|
args = ColorSpace.singletons.cmyk.getRgb(args, 0);
|
|
|
|
fn = OPS.setStrokeRGBColor;
|
|
|
|
break;
|
|
|
|
case OPS.setFillRGBColor:
|
|
|
|
stateManager.state.fillColorSpace = ColorSpace.singletons.rgb;
|
|
|
|
args = ColorSpace.singletons.rgb.getRgb(args, 0);
|
2014-05-10 10:21:15 +09:00
|
|
|
break;
|
2014-05-22 02:47:42 +09:00
|
|
|
case OPS.setStrokeRGBColor:
|
|
|
|
stateManager.state.strokeColorSpace = ColorSpace.singletons.rgb;
|
|
|
|
args = ColorSpace.singletons.rgb.getRgb(args, 0);
|
|
|
|
break;
|
|
|
|
case OPS.setFillColorN:
|
|
|
|
cs = stateManager.state.fillColorSpace;
|
|
|
|
if (cs.name === 'Pattern') {
|
|
|
|
return self.handleColorN(operatorList, OPS.setFillColorN,
|
2015-10-21 10:50:32 +09:00
|
|
|
args, cs, patterns, resources, task, xref).then(function() {
|
2014-05-22 02:47:42 +09:00
|
|
|
next(resolve, reject);
|
|
|
|
}, reject);
|
|
|
|
}
|
|
|
|
args = cs.getRgb(args, 0);
|
|
|
|
fn = OPS.setFillRGBColor;
|
|
|
|
break;
|
|
|
|
case OPS.setStrokeColorN:
|
|
|
|
cs = stateManager.state.strokeColorSpace;
|
|
|
|
if (cs.name === 'Pattern') {
|
|
|
|
return self.handleColorN(operatorList, OPS.setStrokeColorN,
|
2015-10-21 10:50:32 +09:00
|
|
|
args, cs, patterns, resources, task, xref).then(function() {
|
2014-05-22 02:47:42 +09:00
|
|
|
next(resolve, reject);
|
|
|
|
}, reject);
|
|
|
|
}
|
|
|
|
args = cs.getRgb(args, 0);
|
|
|
|
fn = OPS.setStrokeRGBColor;
|
|
|
|
break;
|
|
|
|
|
2014-05-10 10:21:15 +09:00
|
|
|
case OPS.shadingFill:
|
|
|
|
var shadingRes = resources.get('Shading');
|
|
|
|
if (!shadingRes) {
|
|
|
|
error('No shading resource found');
|
2013-11-19 09:50:58 +09:00
|
|
|
}
|
2013-06-05 09:57:52 +09:00
|
|
|
|
2014-05-22 02:47:42 +09:00
|
|
|
var shading = shadingRes.get(args[0].name);
|
2014-05-10 10:21:15 +09:00
|
|
|
if (!shading) {
|
|
|
|
error('No shading object found');
|
|
|
|
}
|
2013-06-05 09:57:52 +09:00
|
|
|
|
2014-05-10 10:21:15 +09:00
|
|
|
var shadingFill = Pattern.parseShading(shading, null, xref,
|
|
|
|
resources);
|
|
|
|
var patternIR = shadingFill.getIR();
|
|
|
|
args = [patternIR];
|
|
|
|
fn = OPS.shadingFill;
|
2014-03-23 03:15:51 +09:00
|
|
|
break;
|
2014-05-10 10:21:15 +09:00
|
|
|
case OPS.setGState:
|
|
|
|
var dictName = args[0];
|
|
|
|
var extGState = resources.get('ExtGState');
|
2014-01-17 22:16:52 +09:00
|
|
|
|
2014-05-10 10:21:15 +09:00
|
|
|
if (!isDict(extGState) || !extGState.has(dictName.name)) {
|
|
|
|
break;
|
|
|
|
}
|
2011-10-25 08:55:23 +09:00
|
|
|
|
2014-05-10 10:21:15 +09:00
|
|
|
var gState = extGState.get(dictName.name);
|
2015-10-21 10:50:32 +09:00
|
|
|
return self.setGState(resources, gState, operatorList, task,
|
|
|
|
xref, stateManager).then(function() {
|
2014-05-10 10:21:15 +09:00
|
|
|
next(resolve, reject);
|
|
|
|
}, reject);
|
|
|
|
case OPS.moveTo:
|
|
|
|
case OPS.lineTo:
|
|
|
|
case OPS.curveTo:
|
|
|
|
case OPS.curveTo2:
|
|
|
|
case OPS.curveTo3:
|
|
|
|
case OPS.closePath:
|
|
|
|
self.buildPath(operatorList, fn, args);
|
|
|
|
continue;
|
2014-06-24 05:07:31 +09:00
|
|
|
case OPS.rectangle:
|
|
|
|
self.buildPath(operatorList, fn, args);
|
|
|
|
continue;
|
2015-10-21 22:39:25 +09:00
|
|
|
case OPS.markPoint:
|
|
|
|
case OPS.markPointProps:
|
|
|
|
case OPS.beginMarkedContent:
|
|
|
|
case OPS.beginMarkedContentProps:
|
|
|
|
case OPS.endMarkedContent:
|
|
|
|
case OPS.beginCompat:
|
|
|
|
case OPS.endCompat:
|
|
|
|
// Ignore operators where the corresponding handlers are known to
|
|
|
|
// be no-op in CanvasGraphics (display/canvas.js). This prevents
|
|
|
|
// serialization errors and is also a bit more efficient.
|
|
|
|
// We could also try to serialize all objects in a general way,
|
|
|
|
// e.g. as done in https://github.com/mozilla/pdf.js/pull/6266,
|
|
|
|
// but doing so is meaningless without knowing the semantics.
|
|
|
|
continue;
|
|
|
|
default:
|
|
|
|
// Note: Let's hope that the ignored operator does not have any
|
|
|
|
// non-serializable arguments, otherwise postMessage will throw
|
|
|
|
// "An object could not be cloned.".
|
2014-05-10 10:21:15 +09:00
|
|
|
}
|
|
|
|
operatorList.addOp(fn, args);
|
|
|
|
}
|
2014-05-10 10:41:03 +09:00
|
|
|
if (stop) {
|
|
|
|
deferred.then(function () {
|
|
|
|
next(resolve, reject);
|
2015-10-21 10:50:32 +09:00
|
|
|
}, reject);
|
2014-05-10 10:41:03 +09:00
|
|
|
return;
|
|
|
|
}
|
2014-05-10 10:21:15 +09:00
|
|
|
// Some PDFs don't close all restores inside object/form.
|
|
|
|
// Closing those for them.
|
|
|
|
for (i = 0, ii = preprocessor.savedStatesDepth; i < ii; i++) {
|
|
|
|
operatorList.addOp(OPS.restore, []);
|
|
|
|
}
|
|
|
|
resolve();
|
|
|
|
});
|
2011-10-25 08:55:23 +09:00
|
|
|
},
|
|
|
|
|
2015-10-21 10:50:32 +09:00
|
|
|
getTextContent: function PartialEvaluator_getTextContent(stream, task,
|
|
|
|
resources,
|
2014-04-10 08:44:07 +09:00
|
|
|
stateManager) {
|
2013-04-09 07:14:56 +09:00
|
|
|
|
2014-04-10 08:44:07 +09:00
|
|
|
stateManager = (stateManager || new StateManager(new TextState()));
|
2014-01-18 04:26:00 +09:00
|
|
|
|
2014-04-10 08:44:07 +09:00
|
|
|
var textContent = {
|
|
|
|
items: [],
|
|
|
|
styles: Object.create(null)
|
|
|
|
};
|
|
|
|
var bidiTexts = textContent.items;
|
2015-02-14 15:27:17 +09:00
|
|
|
var SPACE_FACTOR = 0.3;
|
2012-11-10 06:34:11 +09:00
|
|
|
var MULTI_SPACE_FACTOR = 1.5;
|
2011-12-11 08:24:54 +09:00
|
|
|
|
2013-08-01 03:17:36 +09:00
|
|
|
var self = this;
|
|
|
|
var xref = this.xref;
|
|
|
|
|
2014-03-26 23:07:38 +09:00
|
|
|
resources = (xref.fetchIfRef(resources) || Dict.empty);
|
2014-04-10 08:44:07 +09:00
|
|
|
|
2012-09-12 07:10:34 +09:00
|
|
|
// The xobj is parsed iff it's needed, e.g. if there is a `DO` cmd.
|
|
|
|
var xobjs = null;
|
2014-02-24 23:00:08 +09:00
|
|
|
var xobjsCache = {};
|
2011-12-11 08:24:54 +09:00
|
|
|
|
2014-04-10 08:44:07 +09:00
|
|
|
var preprocessor = new EvaluatorPreprocessor(stream, xref, stateManager);
|
2013-04-09 07:14:56 +09:00
|
|
|
|
2014-04-10 08:44:07 +09:00
|
|
|
var textState;
|
|
|
|
|
|
|
|
function newTextChunk() {
|
|
|
|
var font = textState.font;
|
|
|
|
if (!(font.loadedName in textContent.styles)) {
|
|
|
|
textContent.styles[font.loadedName] = {
|
|
|
|
fontFamily: font.fallbackName,
|
|
|
|
ascent: font.ascent,
|
|
|
|
descent: font.descent,
|
|
|
|
vertical: font.vertical
|
|
|
|
};
|
|
|
|
}
|
|
|
|
return {
|
2014-06-18 22:59:52 +09:00
|
|
|
// |str| is initially an array which we push individual chars to, and
|
|
|
|
// then runBidi() overwrites it with the final string.
|
|
|
|
str: [],
|
2014-04-10 08:44:07 +09:00
|
|
|
dir: null,
|
|
|
|
width: 0,
|
|
|
|
height: 0,
|
|
|
|
transform: null,
|
|
|
|
fontName: font.loadedName
|
|
|
|
};
|
|
|
|
}
|
|
|
|
|
|
|
|
function runBidi(textChunk) {
|
2014-06-18 22:59:52 +09:00
|
|
|
var str = textChunk.str.join('');
|
|
|
|
var bidiResult = PDFJS.bidi(str, -1, textState.font.vertical);
|
2014-04-10 08:44:07 +09:00
|
|
|
textChunk.str = bidiResult.str;
|
|
|
|
textChunk.dir = bidiResult.dir;
|
|
|
|
return textChunk;
|
|
|
|
}
|
|
|
|
|
|
|
|
function handleSetFont(fontName, fontRef) {
|
2014-05-20 06:27:54 +09:00
|
|
|
return self.loadFont(fontName, fontRef, xref, resources).
|
|
|
|
then(function (translated) {
|
|
|
|
textState.font = translated.font;
|
|
|
|
textState.fontMatrix = translated.font.fontMatrix ||
|
2014-05-10 10:21:15 +09:00
|
|
|
FONT_IDENTITY_MATRIX;
|
|
|
|
});
|
2014-04-10 08:44:07 +09:00
|
|
|
}
|
|
|
|
|
|
|
|
function buildTextGeometry(chars, textChunk) {
|
|
|
|
var font = textState.font;
|
|
|
|
textChunk = textChunk || newTextChunk();
|
|
|
|
if (!textChunk.transform) {
|
|
|
|
// 9.4.4 Text Space Details
|
|
|
|
var tsm = [textState.fontSize * textState.textHScale, 0,
|
|
|
|
0, textState.fontSize,
|
|
|
|
0, textState.textRise];
|
2015-04-03 22:49:06 +09:00
|
|
|
|
|
|
|
if (font.isType3Font &&
|
|
|
|
textState.fontMatrix !== FONT_IDENTITY_MATRIX &&
|
|
|
|
textState.fontSize === 1) {
|
|
|
|
var glyphHeight = font.bbox[3] - font.bbox[1];
|
|
|
|
if (glyphHeight > 0) {
|
|
|
|
glyphHeight = glyphHeight * textState.fontMatrix[3];
|
|
|
|
tsm[3] *= glyphHeight;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2014-04-10 08:44:07 +09:00
|
|
|
var trm = textChunk.transform = Util.transform(textState.ctm,
|
|
|
|
Util.transform(textState.textMatrix, tsm));
|
|
|
|
if (!font.vertical) {
|
|
|
|
textChunk.height = Math.sqrt(trm[2] * trm[2] + trm[3] * trm[3]);
|
|
|
|
} else {
|
|
|
|
textChunk.width = Math.sqrt(trm[0] * trm[0] + trm[1] * trm[1]);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
var width = 0;
|
|
|
|
var height = 0;
|
|
|
|
var glyphs = font.charsToGlyphs(chars);
|
|
|
|
var defaultVMetrics = font.defaultVMetrics;
|
|
|
|
for (var i = 0; i < glyphs.length; i++) {
|
|
|
|
var glyph = glyphs[i];
|
|
|
|
if (!glyph) { // Previous glyph was a space.
|
2014-04-17 01:14:57 +09:00
|
|
|
width += textState.wordSpacing * textState.textHScale;
|
2014-04-10 08:44:07 +09:00
|
|
|
continue;
|
|
|
|
}
|
|
|
|
var vMetricX = null;
|
|
|
|
var vMetricY = null;
|
|
|
|
var glyphWidth = null;
|
|
|
|
if (font.vertical) {
|
|
|
|
if (glyph.vmetric) {
|
|
|
|
glyphWidth = glyph.vmetric[0];
|
|
|
|
vMetricX = glyph.vmetric[1];
|
|
|
|
vMetricY = glyph.vmetric[2];
|
|
|
|
} else {
|
|
|
|
glyphWidth = glyph.width;
|
|
|
|
vMetricX = glyph.width * 0.5;
|
|
|
|
vMetricY = defaultVMetrics[2];
|
|
|
|
}
|
|
|
|
} else {
|
|
|
|
glyphWidth = glyph.width;
|
|
|
|
}
|
|
|
|
|
|
|
|
var glyphUnicode = glyph.unicode;
|
2014-06-02 19:43:20 +09:00
|
|
|
if (NormalizedUnicodes[glyphUnicode] !== undefined) {
|
2014-04-10 08:44:07 +09:00
|
|
|
glyphUnicode = NormalizedUnicodes[glyphUnicode];
|
|
|
|
}
|
|
|
|
glyphUnicode = reverseIfRtl(glyphUnicode);
|
|
|
|
|
|
|
|
// The following will calculate the x and y of the individual glyphs.
|
|
|
|
// if (font.vertical) {
|
|
|
|
// tsm[4] -= vMetricX * Math.abs(textState.fontSize) *
|
|
|
|
// textState.fontMatrix[0];
|
|
|
|
// tsm[5] -= vMetricY * textState.fontSize *
|
|
|
|
// textState.fontMatrix[0];
|
|
|
|
// }
|
|
|
|
// var trm = Util.transform(textState.textMatrix, tsm);
|
|
|
|
// var pt = Util.applyTransform([trm[4], trm[5]], textState.ctm);
|
|
|
|
// var x = pt[0];
|
|
|
|
// var y = pt[1];
|
|
|
|
|
2015-05-10 18:28:15 +09:00
|
|
|
var charSpacing = 0;
|
|
|
|
if (textChunk.str.length > 0) {
|
|
|
|
// Apply char spacing only when there are chars.
|
|
|
|
// As a result there is only spacing between glyphs.
|
|
|
|
charSpacing = textState.charSpacing;
|
|
|
|
}
|
|
|
|
|
2014-04-10 08:44:07 +09:00
|
|
|
var tx = 0;
|
|
|
|
var ty = 0;
|
|
|
|
if (!font.vertical) {
|
|
|
|
var w0 = glyphWidth * textState.fontMatrix[0];
|
2015-05-10 18:28:15 +09:00
|
|
|
tx = (w0 * textState.fontSize + charSpacing) *
|
2014-04-10 08:44:07 +09:00
|
|
|
textState.textHScale;
|
|
|
|
width += tx;
|
|
|
|
} else {
|
|
|
|
var w1 = glyphWidth * textState.fontMatrix[0];
|
2015-05-10 18:28:15 +09:00
|
|
|
ty = w1 * textState.fontSize + charSpacing;
|
2014-04-10 08:44:07 +09:00
|
|
|
height += ty;
|
|
|
|
}
|
|
|
|
textState.translateTextMatrix(tx, ty);
|
|
|
|
|
2014-06-18 22:59:52 +09:00
|
|
|
textChunk.str.push(glyphUnicode);
|
2014-04-10 08:44:07 +09:00
|
|
|
}
|
|
|
|
|
|
|
|
var a = textState.textLineMatrix[0];
|
|
|
|
var b = textState.textLineMatrix[1];
|
|
|
|
var scaleLineX = Math.sqrt(a * a + b * b);
|
|
|
|
a = textState.ctm[0];
|
|
|
|
b = textState.ctm[1];
|
|
|
|
var scaleCtmX = Math.sqrt(a * a + b * b);
|
|
|
|
if (!font.vertical) {
|
|
|
|
textChunk.width += width * scaleCtmX * scaleLineX;
|
|
|
|
} else {
|
|
|
|
textChunk.height += Math.abs(height * scaleCtmX * scaleLineX);
|
|
|
|
}
|
|
|
|
return textChunk;
|
|
|
|
}
|
|
|
|
|
2014-05-10 10:41:03 +09:00
|
|
|
var timeSlotManager = new TimeSlotManager();
|
|
|
|
|
2014-05-10 10:21:15 +09:00
|
|
|
return new Promise(function next(resolve, reject) {
|
2015-10-21 10:50:32 +09:00
|
|
|
task.ensureNotTerminated();
|
2014-05-10 10:41:03 +09:00
|
|
|
timeSlotManager.reset();
|
2014-08-11 09:23:23 +09:00
|
|
|
var stop, operation = {}, args = [];
|
|
|
|
while (!(stop = timeSlotManager.check())) {
|
|
|
|
// The arguments parsed by read() are not used beyond this loop, so
|
|
|
|
// we can reuse the same array on every iteration, thus avoiding
|
|
|
|
// unnecessary allocations.
|
|
|
|
args.length = 0;
|
|
|
|
operation.args = args;
|
|
|
|
if (!(preprocessor.read(operation))) {
|
|
|
|
break;
|
|
|
|
}
|
2014-05-10 10:21:15 +09:00
|
|
|
textState = stateManager.state;
|
|
|
|
var fn = operation.fn;
|
2014-08-11 09:23:23 +09:00
|
|
|
args = operation.args;
|
|
|
|
|
2014-05-10 10:21:15 +09:00
|
|
|
switch (fn | 0) {
|
|
|
|
case OPS.setFont:
|
|
|
|
textState.fontSize = args[1];
|
|
|
|
return handleSetFont(args[0].name).then(function() {
|
|
|
|
next(resolve, reject);
|
|
|
|
}, reject);
|
|
|
|
case OPS.setTextRise:
|
|
|
|
textState.textRise = args[0];
|
|
|
|
break;
|
|
|
|
case OPS.setHScale:
|
|
|
|
textState.textHScale = args[0] / 100;
|
|
|
|
break;
|
|
|
|
case OPS.setLeading:
|
|
|
|
textState.leading = args[0];
|
|
|
|
break;
|
|
|
|
case OPS.moveText:
|
|
|
|
textState.translateTextLineMatrix(args[0], args[1]);
|
|
|
|
textState.textMatrix = textState.textLineMatrix.slice();
|
|
|
|
break;
|
|
|
|
case OPS.setLeadingMoveText:
|
|
|
|
textState.leading = -args[1];
|
|
|
|
textState.translateTextLineMatrix(args[0], args[1]);
|
|
|
|
textState.textMatrix = textState.textLineMatrix.slice();
|
|
|
|
break;
|
|
|
|
case OPS.nextLine:
|
|
|
|
textState.carriageReturn();
|
|
|
|
break;
|
|
|
|
case OPS.setTextMatrix:
|
|
|
|
textState.setTextMatrix(args[0], args[1], args[2], args[3],
|
|
|
|
args[4], args[5]);
|
|
|
|
textState.setTextLineMatrix(args[0], args[1], args[2], args[3],
|
|
|
|
args[4], args[5]);
|
|
|
|
break;
|
|
|
|
case OPS.setCharSpacing:
|
|
|
|
textState.charSpacing = args[0];
|
|
|
|
break;
|
|
|
|
case OPS.setWordSpacing:
|
|
|
|
textState.wordSpacing = args[0];
|
|
|
|
break;
|
|
|
|
case OPS.beginText:
|
|
|
|
textState.textMatrix = IDENTITY_MATRIX.slice();
|
|
|
|
textState.textLineMatrix = IDENTITY_MATRIX.slice();
|
|
|
|
break;
|
|
|
|
case OPS.showSpacedText:
|
|
|
|
var items = args[0];
|
|
|
|
var textChunk = newTextChunk();
|
|
|
|
var offset;
|
|
|
|
for (var j = 0, jj = items.length; j < jj; j++) {
|
|
|
|
if (typeof items[j] === 'string') {
|
|
|
|
buildTextGeometry(items[j], textChunk);
|
2014-04-10 08:44:07 +09:00
|
|
|
} else {
|
2015-08-28 20:42:01 +09:00
|
|
|
// PDF Specification 5.3.2 states:
|
|
|
|
// The number is expressed in thousandths of a unit of text
|
|
|
|
// space.
|
|
|
|
// This amount is subtracted from the current horizontal or
|
|
|
|
// vertical coordinate, depending on the writing mode.
|
|
|
|
// In the default coordinate system, a positive adjustment
|
|
|
|
// has the effect of moving the next glyph painted either to
|
|
|
|
// the left or down by the given amount.
|
|
|
|
var val = items[j] * textState.fontSize / 1000;
|
|
|
|
if (textState.font.vertical) {
|
|
|
|
offset = val * textState.textMatrix[3];
|
2014-05-10 10:21:15 +09:00
|
|
|
textState.translateTextMatrix(0, offset);
|
2015-08-28 20:42:01 +09:00
|
|
|
// Value needs to be added to height to paint down.
|
2014-05-10 10:21:15 +09:00
|
|
|
textChunk.height += offset;
|
2015-08-28 20:42:01 +09:00
|
|
|
} else {
|
|
|
|
offset = val * textState.textHScale *
|
|
|
|
textState.textMatrix[0];
|
|
|
|
textState.translateTextMatrix(offset, 0);
|
|
|
|
// Value needs to be subtracted from width to paint left.
|
|
|
|
textChunk.width -= offset;
|
2014-05-10 10:21:15 +09:00
|
|
|
}
|
|
|
|
if (items[j] < 0 && textState.font.spaceWidth > 0) {
|
|
|
|
var fakeSpaces = -items[j] / textState.font.spaceWidth;
|
|
|
|
if (fakeSpaces > MULTI_SPACE_FACTOR) {
|
|
|
|
fakeSpaces = Math.round(fakeSpaces);
|
|
|
|
while (fakeSpaces--) {
|
2014-06-18 22:59:52 +09:00
|
|
|
textChunk.str.push(' ');
|
2014-05-10 10:21:15 +09:00
|
|
|
}
|
|
|
|
} else if (fakeSpaces > SPACE_FACTOR) {
|
2014-06-18 22:59:52 +09:00
|
|
|
textChunk.str.push(' ');
|
2014-04-10 08:44:07 +09:00
|
|
|
}
|
2012-09-15 02:53:06 +09:00
|
|
|
}
|
2013-06-05 09:57:52 +09:00
|
|
|
}
|
2013-08-01 03:17:36 +09:00
|
|
|
}
|
2014-05-10 10:21:15 +09:00
|
|
|
bidiTexts.push(runBidi(textChunk));
|
2014-03-23 03:15:51 +09:00
|
|
|
break;
|
2014-05-10 10:21:15 +09:00
|
|
|
case OPS.showText:
|
|
|
|
bidiTexts.push(runBidi(buildTextGeometry(args[0])));
|
|
|
|
break;
|
|
|
|
case OPS.nextLineShowText:
|
|
|
|
textState.carriageReturn();
|
|
|
|
bidiTexts.push(runBidi(buildTextGeometry(args[0])));
|
|
|
|
break;
|
|
|
|
case OPS.nextLineSetSpacingShowText:
|
|
|
|
textState.wordSpacing = args[0];
|
|
|
|
textState.charSpacing = args[1];
|
|
|
|
textState.carriageReturn();
|
|
|
|
bidiTexts.push(runBidi(buildTextGeometry(args[2])));
|
|
|
|
break;
|
|
|
|
case OPS.paintXObject:
|
|
|
|
if (args[0].code) {
|
|
|
|
break;
|
|
|
|
}
|
2012-09-12 07:10:34 +09:00
|
|
|
|
2014-05-10 10:21:15 +09:00
|
|
|
if (!xobjs) {
|
|
|
|
xobjs = (resources.get('XObject') || Dict.empty);
|
|
|
|
}
|
2012-09-12 07:10:34 +09:00
|
|
|
|
2014-05-10 10:21:15 +09:00
|
|
|
var name = args[0].name;
|
|
|
|
if (xobjsCache.key === name) {
|
|
|
|
if (xobjsCache.texts) {
|
2014-06-17 05:10:10 +09:00
|
|
|
Util.appendToArray(bidiTexts, xobjsCache.texts.items);
|
2014-05-10 10:21:15 +09:00
|
|
|
Util.extendObj(textContent.styles, xobjsCache.texts.styles);
|
|
|
|
}
|
|
|
|
break;
|
2014-02-24 23:00:08 +09:00
|
|
|
}
|
|
|
|
|
2014-05-10 10:21:15 +09:00
|
|
|
var xobj = xobjs.get(name);
|
|
|
|
if (!xobj) {
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
assert(isStream(xobj), 'XObject should be a stream');
|
2013-06-05 09:57:52 +09:00
|
|
|
|
2014-05-10 10:21:15 +09:00
|
|
|
var type = xobj.dict.get('Subtype');
|
|
|
|
assert(isName(type),
|
|
|
|
'XObject should have a Name subtype');
|
2013-06-05 09:57:52 +09:00
|
|
|
|
2014-05-10 10:21:15 +09:00
|
|
|
if ('Form' !== type.name) {
|
|
|
|
xobjsCache.key = name;
|
|
|
|
xobjsCache.texts = null;
|
|
|
|
break;
|
|
|
|
}
|
2012-09-15 11:52:37 +09:00
|
|
|
|
2014-05-10 10:21:15 +09:00
|
|
|
stateManager.save();
|
|
|
|
var matrix = xobj.dict.get('Matrix');
|
|
|
|
if (isArray(matrix) && matrix.length === 6) {
|
|
|
|
stateManager.transform(matrix);
|
|
|
|
}
|
2014-04-10 08:44:07 +09:00
|
|
|
|
2015-10-21 10:50:32 +09:00
|
|
|
return self.getTextContent(xobj, task,
|
2014-05-10 10:21:15 +09:00
|
|
|
xobj.dict.get('Resources') || resources, stateManager).
|
|
|
|
then(function (formTextContent) {
|
2014-06-17 05:10:10 +09:00
|
|
|
Util.appendToArray(bidiTexts, formTextContent.items);
|
2014-05-10 10:21:15 +09:00
|
|
|
Util.extendObj(textContent.styles, formTextContent.styles);
|
|
|
|
stateManager.restore();
|
2013-06-05 09:57:52 +09:00
|
|
|
|
2014-05-10 10:21:15 +09:00
|
|
|
xobjsCache.key = name;
|
|
|
|
xobjsCache.texts = formTextContent;
|
2013-06-05 09:57:52 +09:00
|
|
|
|
2014-05-10 10:21:15 +09:00
|
|
|
next(resolve, reject);
|
|
|
|
}, reject);
|
|
|
|
case OPS.setGState:
|
|
|
|
var dictName = args[0];
|
|
|
|
var extGState = resources.get('ExtGState');
|
2014-03-23 03:15:51 +09:00
|
|
|
|
2014-05-10 10:21:15 +09:00
|
|
|
if (!isDict(extGState) || !extGState.has(dictName.name)) {
|
|
|
|
break;
|
2012-09-15 11:52:37 +09:00
|
|
|
}
|
2014-03-23 03:15:51 +09:00
|
|
|
|
2014-05-10 10:21:15 +09:00
|
|
|
var gsStateMap = extGState.get(dictName.name);
|
|
|
|
var gsStateFont = null;
|
|
|
|
for (var key in gsStateMap) {
|
|
|
|
if (key === 'Font') {
|
|
|
|
assert(!gsStateFont);
|
|
|
|
gsStateFont = gsStateMap[key];
|
|
|
|
}
|
|
|
|
}
|
|
|
|
if (gsStateFont) {
|
|
|
|
textState.fontSize = gsStateFont[1];
|
|
|
|
return handleSetFont(gsStateFont[0]).then(function() {
|
|
|
|
next(resolve, reject);
|
|
|
|
}, reject);
|
|
|
|
}
|
|
|
|
break;
|
|
|
|
} // switch
|
|
|
|
} // while
|
2014-05-10 10:41:03 +09:00
|
|
|
if (stop) {
|
|
|
|
deferred.then(function () {
|
|
|
|
next(resolve, reject);
|
2015-10-21 10:50:32 +09:00
|
|
|
}, reject);
|
2014-05-10 10:41:03 +09:00
|
|
|
return;
|
|
|
|
}
|
2014-05-10 10:21:15 +09:00
|
|
|
resolve(textContent);
|
|
|
|
});
|
2011-12-11 08:24:54 +09:00
|
|
|
},
|
|
|
|
|
2011-10-29 10:38:31 +09:00
|
|
|
extractDataStructures: function
|
|
|
|
partialEvaluatorExtractDataStructures(dict, baseDict,
|
|
|
|
xref, properties) {
|
|
|
|
// 9.10.2
|
2014-03-23 03:15:51 +09:00
|
|
|
var toUnicode = (dict.get('ToUnicode') || baseDict.get('ToUnicode'));
|
|
|
|
if (toUnicode) {
|
2014-06-27 07:41:44 +09:00
|
|
|
properties.toUnicode = this.readToUnicode(toUnicode);
|
2014-03-23 03:15:51 +09:00
|
|
|
}
|
2011-10-29 10:38:31 +09:00
|
|
|
if (properties.composite) {
|
|
|
|
// CIDSystemInfo helps to match CID to glyphs
|
2012-04-05 03:43:04 +09:00
|
|
|
var cidSystemInfo = dict.get('CIDSystemInfo');
|
2011-10-25 08:55:23 +09:00
|
|
|
if (isDict(cidSystemInfo)) {
|
|
|
|
properties.cidSystemInfo = {
|
|
|
|
registry: cidSystemInfo.get('Registry'),
|
|
|
|
ordering: cidSystemInfo.get('Ordering'),
|
|
|
|
supplement: cidSystemInfo.get('Supplement')
|
|
|
|
};
|
|
|
|
}
|
|
|
|
|
2012-04-05 03:43:04 +09:00
|
|
|
var cidToGidMap = dict.get('CIDToGIDMap');
|
2014-03-23 03:15:51 +09:00
|
|
|
if (isStream(cidToGidMap)) {
|
2011-10-29 10:38:31 +09:00
|
|
|
properties.cidToGidMap = this.readCidToGidMap(cidToGidMap);
|
2014-03-23 03:15:51 +09:00
|
|
|
}
|
2011-10-25 08:55:23 +09:00
|
|
|
}
|
|
|
|
|
2013-04-11 01:51:06 +09:00
|
|
|
// Based on 9.6.6 of the spec the encoding can come from multiple places
|
2014-02-12 03:27:09 +09:00
|
|
|
// and depends on the font type. The base encoding and differences are
|
|
|
|
// read here, but the encoding that is actually used is chosen during
|
|
|
|
// glyph mapping in the font.
|
|
|
|
// TODO: Loading the built in encoding in the font would allow the
|
|
|
|
// differences to be merged in here not require us to hold on to it.
|
2011-10-29 10:38:31 +09:00
|
|
|
var differences = [];
|
2014-02-12 03:27:09 +09:00
|
|
|
var baseEncodingName = null;
|
2014-04-08 06:42:54 +09:00
|
|
|
var encoding;
|
2013-04-11 01:51:06 +09:00
|
|
|
if (dict.has('Encoding')) {
|
2014-04-08 06:42:54 +09:00
|
|
|
encoding = dict.get('Encoding');
|
2011-10-25 08:55:23 +09:00
|
|
|
if (isDict(encoding)) {
|
2014-02-12 03:27:09 +09:00
|
|
|
baseEncodingName = encoding.get('BaseEncoding');
|
2014-03-23 03:15:51 +09:00
|
|
|
baseEncodingName = (isName(baseEncodingName) ?
|
|
|
|
baseEncodingName.name : null);
|
2011-10-25 08:55:23 +09:00
|
|
|
// Load the differences between the base and original
|
|
|
|
if (encoding.has('Differences')) {
|
|
|
|
var diffEncoding = encoding.get('Differences');
|
|
|
|
var index = 0;
|
2011-11-03 04:08:19 +09:00
|
|
|
for (var j = 0, jj = diffEncoding.length; j < jj; j++) {
|
2011-10-25 08:55:23 +09:00
|
|
|
var data = diffEncoding[j];
|
2014-03-23 03:15:51 +09:00
|
|
|
if (isNum(data)) {
|
2011-10-25 08:55:23 +09:00
|
|
|
index = data;
|
2015-03-09 22:36:45 +09:00
|
|
|
} else if (isName(data)) {
|
2011-10-25 08:55:23 +09:00
|
|
|
differences[index++] = data.name;
|
2015-03-09 22:36:45 +09:00
|
|
|
} else if (isRef(data)) {
|
|
|
|
diffEncoding[j--] = xref.fetch(data);
|
|
|
|
continue;
|
|
|
|
} else {
|
|
|
|
error('Invalid entry in \'Differences\' array: ' + data);
|
2014-03-23 03:15:51 +09:00
|
|
|
}
|
2011-10-25 08:55:23 +09:00
|
|
|
}
|
|
|
|
}
|
|
|
|
} else if (isName(encoding)) {
|
2014-02-12 03:27:09 +09:00
|
|
|
baseEncodingName = encoding.name;
|
2011-10-25 08:55:23 +09:00
|
|
|
} else {
|
|
|
|
error('Encoding is not a Name nor a Dict');
|
|
|
|
}
|
2014-02-12 03:27:09 +09:00
|
|
|
// According to table 114 if the encoding is a named encoding it must be
|
|
|
|
// one of these predefined encodings.
|
|
|
|
if ((baseEncodingName !== 'MacRomanEncoding' &&
|
|
|
|
baseEncodingName !== 'MacExpertEncoding' &&
|
|
|
|
baseEncodingName !== 'WinAnsiEncoding')) {
|
|
|
|
baseEncodingName = null;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
if (baseEncodingName) {
|
|
|
|
properties.defaultEncoding = Encodings[baseEncodingName].slice();
|
|
|
|
} else {
|
2014-04-08 06:42:54 +09:00
|
|
|
encoding = (properties.type === 'TrueType' ?
|
|
|
|
Encodings.WinAnsiEncoding : Encodings.StandardEncoding);
|
2014-02-12 03:27:09 +09:00
|
|
|
// The Symbolic attribute can be misused for regular fonts
|
|
|
|
// Heuristic: we have to check if the font is a standard one also
|
|
|
|
if (!!(properties.flags & FontFlags.Symbolic)) {
|
2014-09-01 10:22:24 +09:00
|
|
|
encoding = Encodings.MacRomanEncoding;
|
|
|
|
if (!properties.file) {
|
|
|
|
if (/Symbol/i.test(properties.name)) {
|
|
|
|
encoding = Encodings.SymbolSetEncoding;
|
|
|
|
} else if (/Dingbats/i.test(properties.name)) {
|
|
|
|
encoding = Encodings.ZapfDingbatsEncoding;
|
|
|
|
}
|
|
|
|
}
|
2014-02-12 03:27:09 +09:00
|
|
|
}
|
|
|
|
properties.defaultEncoding = encoding;
|
2011-10-25 08:55:23 +09:00
|
|
|
}
|
2011-11-25 00:38:09 +09:00
|
|
|
|
2011-10-29 10:38:31 +09:00
|
|
|
properties.differences = differences;
|
2014-02-12 03:27:09 +09:00
|
|
|
properties.baseEncodingName = baseEncodingName;
|
|
|
|
properties.dict = dict;
|
2011-10-29 10:38:31 +09:00
|
|
|
},
|
2011-10-25 08:55:23 +09:00
|
|
|
|
2014-02-12 03:27:09 +09:00
|
|
|
readToUnicode: function PartialEvaluator_readToUnicode(toUnicode) {
|
2014-08-07 10:02:11 +09:00
|
|
|
var cmap, cmapObj = toUnicode;
|
2011-10-29 10:38:31 +09:00
|
|
|
if (isName(cmapObj)) {
|
2014-08-07 10:02:11 +09:00
|
|
|
cmap = CMapFactory.create(cmapObj,
|
2015-02-09 18:05:55 +09:00
|
|
|
{ url: PDFJS.cMapUrl, packed: PDFJS.cMapPacked }, null);
|
|
|
|
if (cmap instanceof IdentityCMap) {
|
|
|
|
return new IdentityToUnicodeMap(0, 0xFFFF);
|
|
|
|
}
|
|
|
|
return new ToUnicodeMap(cmap.getMap());
|
2011-10-29 10:38:31 +09:00
|
|
|
} else if (isStream(cmapObj)) {
|
2014-08-07 10:02:11 +09:00
|
|
|
cmap = CMapFactory.create(cmapObj,
|
2015-03-06 23:01:26 +09:00
|
|
|
{ url: PDFJS.cMapUrl, packed: PDFJS.cMapPacked }, null);
|
|
|
|
if (cmap instanceof IdentityCMap) {
|
|
|
|
return new IdentityToUnicodeMap(0, 0xFFFF);
|
|
|
|
}
|
2015-09-19 23:54:19 +09:00
|
|
|
var map = new Array(cmap.length);
|
2013-09-26 02:32:04 +09:00
|
|
|
// Convert UTF-16BE
|
2014-01-26 02:04:33 +09:00
|
|
|
// NOTE: cmap can be a sparse array, so use forEach instead of for(;;)
|
2014-03-23 03:15:51 +09:00
|
|
|
// to iterate over all keys.
|
2015-08-08 08:16:05 +09:00
|
|
|
cmap.forEach(function(charCode, token) {
|
2013-09-26 02:32:04 +09:00
|
|
|
var str = [];
|
|
|
|
for (var k = 0; k < token.length; k += 2) {
|
|
|
|
var w1 = (token.charCodeAt(k) << 8) | token.charCodeAt(k + 1);
|
|
|
|
if ((w1 & 0xF800) !== 0xD800) { // w1 < 0xD800 || w1 > 0xDFFF
|
|
|
|
str.push(w1);
|
|
|
|
continue;
|
2011-10-29 10:38:31 +09:00
|
|
|
}
|
2013-09-26 02:32:04 +09:00
|
|
|
k += 2;
|
|
|
|
var w2 = (token.charCodeAt(k) << 8) | token.charCodeAt(k + 1);
|
|
|
|
str.push(((w1 & 0x3ff) << 10) + (w2 & 0x3ff) + 0x10000);
|
2011-10-25 08:55:23 +09:00
|
|
|
}
|
2015-08-08 08:16:05 +09:00
|
|
|
map[charCode] = String.fromCharCode.apply(String, str);
|
2014-01-26 02:04:33 +09:00
|
|
|
});
|
2015-08-08 08:16:05 +09:00
|
|
|
return new ToUnicodeMap(map);
|
2011-10-25 08:55:23 +09:00
|
|
|
}
|
2014-02-12 03:27:09 +09:00
|
|
|
return null;
|
2011-10-25 08:55:23 +09:00
|
|
|
},
|
2014-03-23 03:15:51 +09:00
|
|
|
|
2012-04-05 05:43:26 +09:00
|
|
|
readCidToGidMap: function PartialEvaluator_readCidToGidMap(cidToGidStream) {
|
2011-10-29 10:38:31 +09:00
|
|
|
// Extract the encoding from the CIDToGIDMap
|
|
|
|
var glyphsData = cidToGidStream.getBytes();
|
|
|
|
|
|
|
|
// Set encoding 0 to later verify the font has an encoding
|
|
|
|
var result = [];
|
2011-11-13 02:09:19 +09:00
|
|
|
for (var j = 0, jj = glyphsData.length; j < jj; j++) {
|
2011-10-29 10:38:31 +09:00
|
|
|
var glyphID = (glyphsData[j++] << 8) | glyphsData[j];
|
2014-03-23 03:15:51 +09:00
|
|
|
if (glyphID === 0) {
|
2011-10-29 10:38:31 +09:00
|
|
|
continue;
|
2014-03-23 03:15:51 +09:00
|
|
|
}
|
2011-10-29 10:38:31 +09:00
|
|
|
var code = j >> 1;
|
|
|
|
result[code] = glyphID;
|
2011-10-25 08:55:23 +09:00
|
|
|
}
|
2011-10-29 10:38:31 +09:00
|
|
|
return result;
|
|
|
|
},
|
2011-10-25 08:55:23 +09:00
|
|
|
|
2014-03-23 03:15:51 +09:00
|
|
|
extractWidths: function PartialEvaluator_extractWidths(dict, xref,
|
|
|
|
descriptor,
|
|
|
|
properties) {
|
2011-10-29 10:38:31 +09:00
|
|
|
var glyphsWidths = [];
|
2011-10-25 08:55:23 +09:00
|
|
|
var defaultWidth = 0;
|
2013-02-08 21:29:22 +09:00
|
|
|
var glyphsVMetrics = [];
|
|
|
|
var defaultVMetrics;
|
2014-04-08 06:42:54 +09:00
|
|
|
var i, ii, j, jj, start, code, widths;
|
2011-10-29 10:38:31 +09:00
|
|
|
if (properties.composite) {
|
2012-04-05 03:43:04 +09:00
|
|
|
defaultWidth = dict.get('DW') || 1000;
|
2011-10-29 10:38:31 +09:00
|
|
|
|
2014-04-08 06:42:54 +09:00
|
|
|
widths = dict.get('W');
|
2011-10-29 10:38:31 +09:00
|
|
|
if (widths) {
|
2014-04-08 06:42:54 +09:00
|
|
|
for (i = 0, ii = widths.length; i < ii; i++) {
|
|
|
|
start = widths[i++];
|
|
|
|
code = xref.fetchIfRef(widths[i]);
|
2011-10-29 10:38:31 +09:00
|
|
|
if (isArray(code)) {
|
2014-04-08 06:42:54 +09:00
|
|
|
for (j = 0, jj = code.length; j < jj; j++) {
|
2011-10-29 10:38:31 +09:00
|
|
|
glyphsWidths[start++] = code[j];
|
2014-03-23 03:15:51 +09:00
|
|
|
}
|
2013-01-30 07:19:08 +09:00
|
|
|
} else {
|
2011-10-29 10:38:31 +09:00
|
|
|
var width = widths[++i];
|
2014-04-08 06:42:54 +09:00
|
|
|
for (j = start; j <= code; j++) {
|
2011-10-29 10:38:31 +09:00
|
|
|
glyphsWidths[j] = width;
|
2014-03-23 03:15:51 +09:00
|
|
|
}
|
2011-10-29 10:38:31 +09:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
2013-02-08 21:29:22 +09:00
|
|
|
|
|
|
|
if (properties.vertical) {
|
2014-03-23 03:15:51 +09:00
|
|
|
var vmetrics = (dict.get('DW2') || [880, -1000]);
|
2013-03-13 01:27:45 +09:00
|
|
|
defaultVMetrics = [vmetrics[1], defaultWidth * 0.5, vmetrics[0]];
|
2013-02-08 21:29:22 +09:00
|
|
|
vmetrics = dict.get('W2');
|
|
|
|
if (vmetrics) {
|
2014-04-08 06:42:54 +09:00
|
|
|
for (i = 0, ii = vmetrics.length; i < ii; i++) {
|
|
|
|
start = vmetrics[i++];
|
|
|
|
code = xref.fetchIfRef(vmetrics[i]);
|
2013-02-08 21:29:22 +09:00
|
|
|
if (isArray(code)) {
|
2014-04-08 06:42:54 +09:00
|
|
|
for (j = 0, jj = code.length; j < jj; j++) {
|
2013-02-08 21:29:22 +09:00
|
|
|
glyphsVMetrics[start++] = [code[j++], code[j++], code[j]];
|
2014-03-23 03:15:51 +09:00
|
|
|
}
|
2013-02-08 21:29:22 +09:00
|
|
|
} else {
|
|
|
|
var vmetric = [vmetrics[++i], vmetrics[++i], vmetrics[++i]];
|
2014-04-08 06:42:54 +09:00
|
|
|
for (j = start; j <= code; j++) {
|
2013-02-08 21:29:22 +09:00
|
|
|
glyphsVMetrics[j] = vmetric;
|
2014-03-23 03:15:51 +09:00
|
|
|
}
|
2013-02-08 21:29:22 +09:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
2011-10-29 10:38:31 +09:00
|
|
|
} else {
|
|
|
|
var firstChar = properties.firstChar;
|
2014-04-08 06:42:54 +09:00
|
|
|
widths = dict.get('Widths');
|
2011-10-29 10:38:31 +09:00
|
|
|
if (widths) {
|
2014-04-08 06:42:54 +09:00
|
|
|
j = firstChar;
|
|
|
|
for (i = 0, ii = widths.length; i < ii; i++) {
|
2011-11-13 02:09:19 +09:00
|
|
|
glyphsWidths[j++] = widths[i];
|
2014-03-23 03:15:51 +09:00
|
|
|
}
|
|
|
|
defaultWidth = (parseFloat(descriptor.get('MissingWidth')) || 0);
|
2011-10-29 10:38:31 +09:00
|
|
|
} else {
|
|
|
|
// Trying get the BaseFont metrics (see comment above).
|
|
|
|
var baseFontName = dict.get('BaseFont');
|
|
|
|
if (isName(baseFontName)) {
|
|
|
|
var metrics = this.getBaseFontMetrics(baseFontName.name);
|
|
|
|
|
2014-02-12 03:27:09 +09:00
|
|
|
glyphsWidths = this.buildCharCodeToWidth(metrics.widths,
|
|
|
|
properties);
|
2011-10-29 10:38:31 +09:00
|
|
|
defaultWidth = metrics.defaultWidth;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2012-09-17 04:38:30 +09:00
|
|
|
// Heuristic: detection of monospace font by checking all non-zero widths
|
2014-03-23 03:15:51 +09:00
|
|
|
var isMonospace = true;
|
|
|
|
var firstWidth = defaultWidth;
|
2012-09-17 04:38:30 +09:00
|
|
|
for (var glyph in glyphsWidths) {
|
|
|
|
var glyphWidth = glyphsWidths[glyph];
|
2014-03-23 03:15:51 +09:00
|
|
|
if (!glyphWidth) {
|
2012-09-17 04:38:30 +09:00
|
|
|
continue;
|
2014-03-23 03:15:51 +09:00
|
|
|
}
|
2012-09-17 04:38:30 +09:00
|
|
|
if (!firstWidth) {
|
|
|
|
firstWidth = glyphWidth;
|
|
|
|
continue;
|
|
|
|
}
|
2014-08-02 01:25:21 +09:00
|
|
|
if (firstWidth !== glyphWidth) {
|
2012-09-17 04:38:30 +09:00
|
|
|
isMonospace = false;
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
}
|
2014-03-23 03:15:51 +09:00
|
|
|
if (isMonospace) {
|
2012-09-17 04:38:30 +09:00
|
|
|
properties.flags |= FontFlags.FixedPitch;
|
2014-03-23 03:15:51 +09:00
|
|
|
}
|
2012-09-17 04:38:30 +09:00
|
|
|
|
2011-10-29 10:38:31 +09:00
|
|
|
properties.defaultWidth = defaultWidth;
|
|
|
|
properties.widths = glyphsWidths;
|
2013-02-08 21:29:22 +09:00
|
|
|
properties.defaultVMetrics = defaultVMetrics;
|
|
|
|
properties.vmetrics = glyphsVMetrics;
|
2011-10-29 10:38:31 +09:00
|
|
|
},
|
|
|
|
|
2013-01-12 04:04:56 +09:00
|
|
|
isSerifFont: function PartialEvaluator_isSerifFont(baseFontName) {
|
|
|
|
// Simulating descriptor flags attribute
|
|
|
|
var fontNameWoStyle = baseFontName.split('-')[0];
|
|
|
|
return (fontNameWoStyle in serifFonts) ||
|
2014-03-23 03:15:51 +09:00
|
|
|
(fontNameWoStyle.search(/serif/gi) !== -1);
|
2013-01-12 04:04:56 +09:00
|
|
|
},
|
|
|
|
|
2012-04-05 05:43:26 +09:00
|
|
|
getBaseFontMetrics: function PartialEvaluator_getBaseFontMetrics(name) {
|
2014-03-23 03:15:51 +09:00
|
|
|
var defaultWidth = 0;
|
|
|
|
var widths = [];
|
|
|
|
var monospace = false;
|
|
|
|
var lookupName = (stdFontMap[name] || name);
|
2013-01-12 04:04:56 +09:00
|
|
|
|
|
|
|
if (!(lookupName in Metrics)) {
|
|
|
|
// Use default fonts for looking up font metrics if the passed
|
|
|
|
// font is not a base font
|
|
|
|
if (this.isSerifFont(name)) {
|
|
|
|
lookupName = 'Times-Roman';
|
|
|
|
} else {
|
|
|
|
lookupName = 'Helvetica';
|
|
|
|
}
|
|
|
|
}
|
|
|
|
var glyphWidths = Metrics[lookupName];
|
|
|
|
|
2011-10-29 10:38:31 +09:00
|
|
|
if (isNum(glyphWidths)) {
|
|
|
|
defaultWidth = glyphWidths;
|
2012-09-17 04:38:30 +09:00
|
|
|
monospace = true;
|
2011-10-29 10:38:31 +09:00
|
|
|
} else {
|
|
|
|
widths = glyphWidths;
|
2011-10-25 08:55:23 +09:00
|
|
|
}
|
|
|
|
|
|
|
|
return {
|
|
|
|
defaultWidth: defaultWidth,
|
2012-09-17 04:38:30 +09:00
|
|
|
monospace: monospace,
|
2011-10-29 10:38:31 +09:00
|
|
|
widths: widths
|
2011-10-25 08:55:23 +09:00
|
|
|
};
|
|
|
|
},
|
|
|
|
|
2014-03-23 03:15:51 +09:00
|
|
|
buildCharCodeToWidth:
|
|
|
|
function PartialEvaluator_bulildCharCodeToWidth(widthsByGlyphName,
|
|
|
|
properties) {
|
2014-02-12 03:27:09 +09:00
|
|
|
var widths = Object.create(null);
|
|
|
|
var differences = properties.differences;
|
|
|
|
var encoding = properties.defaultEncoding;
|
|
|
|
for (var charCode = 0; charCode < 256; charCode++) {
|
|
|
|
if (charCode in differences &&
|
|
|
|
widthsByGlyphName[differences[charCode]]) {
|
|
|
|
widths[charCode] = widthsByGlyphName[differences[charCode]];
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
if (charCode in encoding && widthsByGlyphName[encoding[charCode]]) {
|
|
|
|
widths[charCode] = widthsByGlyphName[encoding[charCode]];
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return widths;
|
|
|
|
},
|
|
|
|
|
2014-03-04 02:44:45 +09:00
|
|
|
preEvaluateFont: function PartialEvaluator_preEvaluateFont(dict, xref) {
|
2011-10-25 08:55:23 +09:00
|
|
|
var baseDict = dict;
|
|
|
|
var type = dict.get('Subtype');
|
2014-04-13 23:02:56 +09:00
|
|
|
assert(isName(type), 'invalid font Subtype');
|
2011-10-25 08:55:23 +09:00
|
|
|
|
|
|
|
var composite = false;
|
2014-04-10 02:47:42 +09:00
|
|
|
var uint8array;
|
2014-06-02 19:43:20 +09:00
|
|
|
if (type.name === 'Type0') {
|
2011-10-25 08:55:23 +09:00
|
|
|
// 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');
|
2014-03-23 03:15:51 +09:00
|
|
|
if (!df) {
|
2012-09-14 00:09:46 +09:00
|
|
|
error('Descendant fonts are not specified');
|
2014-03-23 03:15:51 +09:00
|
|
|
}
|
|
|
|
dict = (isArray(df) ? xref.fetchIfRef(df[0]) : df);
|
2011-10-25 08:55:23 +09:00
|
|
|
|
|
|
|
type = dict.get('Subtype');
|
2014-04-13 23:02:56 +09:00
|
|
|
assert(isName(type), 'invalid font Subtype');
|
2011-10-25 08:55:23 +09:00
|
|
|
composite = true;
|
|
|
|
}
|
|
|
|
|
2012-04-05 03:43:04 +09:00
|
|
|
var descriptor = dict.get('FontDescriptor');
|
2014-03-04 02:44:45 +09:00
|
|
|
if (descriptor) {
|
|
|
|
var hash = new MurmurHash3_64();
|
|
|
|
var encoding = baseDict.getRaw('Encoding');
|
|
|
|
if (isName(encoding)) {
|
|
|
|
hash.update(encoding.name);
|
|
|
|
} else if (isRef(encoding)) {
|
|
|
|
hash.update(encoding.num + '_' + encoding.gen);
|
2015-04-25 20:27:10 +09:00
|
|
|
} else if (isDict(encoding)) {
|
|
|
|
var keys = encoding.getKeys();
|
|
|
|
for (var i = 0, ii = keys.length; i < ii; i++) {
|
|
|
|
var entry = encoding.getRaw(keys[i]);
|
|
|
|
if (isName(entry)) {
|
|
|
|
hash.update(entry.name);
|
|
|
|
} else if (isRef(entry)) {
|
|
|
|
hash.update(entry.num + '_' + entry.gen);
|
|
|
|
} else if (isArray(entry)) { // 'Differences' entry.
|
|
|
|
// Ideally we should check the contents of the array, but to avoid
|
|
|
|
// parsing it here and then again in |extractDataStructures|,
|
|
|
|
// we only use the array length for now (fixes bug1157493.pdf).
|
|
|
|
hash.update(entry.length.toString());
|
|
|
|
}
|
|
|
|
}
|
2014-03-04 02:44:45 +09:00
|
|
|
}
|
|
|
|
|
|
|
|
var toUnicode = dict.get('ToUnicode') || baseDict.get('ToUnicode');
|
|
|
|
if (isStream(toUnicode)) {
|
|
|
|
var stream = toUnicode.str || toUnicode;
|
2014-04-10 02:47:42 +09:00
|
|
|
uint8array = stream.buffer ?
|
2014-03-04 02:44:45 +09:00
|
|
|
new Uint8Array(stream.buffer.buffer, 0, stream.bufferLength) :
|
|
|
|
new Uint8Array(stream.bytes.buffer,
|
|
|
|
stream.start, stream.end - stream.start);
|
|
|
|
hash.update(uint8array);
|
|
|
|
|
|
|
|
} else if (isName(toUnicode)) {
|
|
|
|
hash.update(toUnicode.name);
|
|
|
|
}
|
|
|
|
|
|
|
|
var widths = dict.get('Widths') || baseDict.get('Widths');
|
|
|
|
if (widths) {
|
2014-04-10 02:47:42 +09:00
|
|
|
uint8array = new Uint8Array(new Uint32Array(widths).buffer);
|
2014-03-04 02:44:45 +09:00
|
|
|
hash.update(uint8array);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
return {
|
|
|
|
descriptor: descriptor,
|
|
|
|
dict: dict,
|
|
|
|
baseDict: baseDict,
|
|
|
|
composite: composite,
|
2014-06-16 23:52:04 +09:00
|
|
|
type: type.name,
|
2014-03-04 02:44:45 +09:00
|
|
|
hash: hash ? hash.hexdigest() : ''
|
|
|
|
};
|
|
|
|
},
|
|
|
|
|
|
|
|
translateFont: function PartialEvaluator_translateFont(preEvaluatedFont,
|
|
|
|
xref) {
|
|
|
|
var baseDict = preEvaluatedFont.baseDict;
|
|
|
|
var dict = preEvaluatedFont.dict;
|
|
|
|
var composite = preEvaluatedFont.composite;
|
|
|
|
var descriptor = preEvaluatedFont.descriptor;
|
2014-06-16 23:52:04 +09:00
|
|
|
var type = preEvaluatedFont.type;
|
2014-03-04 02:44:45 +09:00
|
|
|
var maxCharIndex = (composite ? 0xFFFF : 0xFF);
|
2014-04-10 02:47:42 +09:00
|
|
|
var properties;
|
2014-03-04 02:44:45 +09:00
|
|
|
|
2011-10-25 08:55:23 +09:00
|
|
|
if (!descriptor) {
|
2014-06-16 23:52:04 +09:00
|
|
|
if (type === 'Type3') {
|
2011-10-25 08:55:23 +09:00
|
|
|
// FontDescriptor is only required for Type3 fonts when the document
|
|
|
|
// is a tagged pdf. Create a barbebones one to get by.
|
2014-03-26 23:07:38 +09:00
|
|
|
descriptor = new Dict(null);
|
2014-06-16 23:52:04 +09:00
|
|
|
descriptor.set('FontName', Name.get(type));
|
2015-04-03 22:49:06 +09:00
|
|
|
descriptor.set('FontBBox', dict.get('FontBBox'));
|
2011-10-25 08:55:23 +09:00
|
|
|
} 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');
|
2014-03-23 03:15:51 +09:00
|
|
|
if (!isName(baseFontName)) {
|
2012-09-14 00:09:46 +09:00
|
|
|
error('Base font is not specified');
|
2014-03-23 03:15:51 +09:00
|
|
|
}
|
2011-10-25 08:55:23 +09:00
|
|
|
|
|
|
|
// Using base font name as a font name.
|
|
|
|
baseFontName = baseFontName.name.replace(/[,_]/g, '-');
|
2011-10-29 10:38:31 +09:00
|
|
|
var metrics = this.getBaseFontMetrics(baseFontName);
|
2011-10-25 08:55:23 +09:00
|
|
|
|
2012-01-10 12:15:18 +09:00
|
|
|
// Simulating descriptor flags attribute
|
|
|
|
var fontNameWoStyle = baseFontName.split('-')[0];
|
2014-03-23 03:15:51 +09:00
|
|
|
var flags =
|
|
|
|
(this.isSerifFont(fontNameWoStyle) ? FontFlags.Serif : 0) |
|
2012-09-17 04:38:30 +09:00
|
|
|
(metrics.monospace ? FontFlags.FixedPitch : 0) |
|
2012-01-28 09:53:05 +09:00
|
|
|
(symbolsFonts[fontNameWoStyle] ? FontFlags.Symbolic :
|
2014-03-23 03:15:51 +09:00
|
|
|
FontFlags.Nonsymbolic);
|
2012-01-10 12:15:18 +09:00
|
|
|
|
2014-04-08 06:42:54 +09:00
|
|
|
properties = {
|
2014-06-16 23:52:04 +09:00
|
|
|
type: type,
|
2014-01-09 07:33:22 +09:00
|
|
|
name: baseFontName,
|
2011-10-29 10:38:31 +09:00
|
|
|
widths: metrics.widths,
|
|
|
|
defaultWidth: metrics.defaultWidth,
|
2012-01-10 12:15:18 +09:00
|
|
|
flags: flags,
|
2011-10-25 08:55:23 +09:00
|
|
|
firstChar: 0,
|
2011-10-29 10:38:31 +09:00
|
|
|
lastChar: maxCharIndex
|
2011-10-25 08:55:23 +09:00
|
|
|
};
|
2011-10-29 10:38:31 +09:00
|
|
|
this.extractDataStructures(dict, dict, xref, properties);
|
2014-02-12 03:27:09 +09:00
|
|
|
properties.widths = this.buildCharCodeToWidth(metrics.widths,
|
|
|
|
properties);
|
2012-09-14 00:09:46 +09:00
|
|
|
return new Font(baseFontName, null, properties);
|
2011-10-25 08:55:23 +09:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// According to the spec if 'FontDescriptor' is declared, 'FirstChar',
|
2012-01-20 04:19:19 +09:00
|
|
|
// 'LastChar' and 'Widths' should exist too, but some PDF encoders seem
|
2011-10-25 08:55:23 +09:00
|
|
|
// 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.
|
2014-03-23 03:15:51 +09:00
|
|
|
var firstChar = (dict.get('FirstChar') || 0);
|
|
|
|
var lastChar = (dict.get('LastChar') || maxCharIndex);
|
2013-01-12 10:10:09 +09:00
|
|
|
|
2012-04-05 03:43:04 +09:00
|
|
|
var fontName = descriptor.get('FontName');
|
2013-02-06 06:47:41 +09:00
|
|
|
var baseFont = dict.get('BaseFont');
|
2014-03-23 03:15:51 +09:00
|
|
|
// Some bad PDFs have a string as the font name.
|
2013-01-12 10:10:09 +09:00
|
|
|
if (isString(fontName)) {
|
2014-02-28 13:41:03 +09:00
|
|
|
fontName = Name.get(fontName);
|
2013-01-12 10:10:09 +09:00
|
|
|
}
|
|
|
|
if (isString(baseFont)) {
|
2014-02-28 13:41:03 +09:00
|
|
|
baseFont = Name.get(baseFont);
|
2013-01-12 10:10:09 +09:00
|
|
|
}
|
|
|
|
|
2014-06-16 23:52:04 +09:00
|
|
|
if (type !== 'Type3') {
|
2013-03-03 21:30:08 +09:00
|
|
|
var fontNameStr = fontName && fontName.name;
|
|
|
|
var baseFontStr = baseFont && baseFont.name;
|
|
|
|
if (fontNameStr !== baseFontStr) {
|
2013-04-17 07:45:29 +09:00
|
|
|
info('The FontDescriptor\'s FontName is "' + fontNameStr +
|
2013-03-03 21:30:08 +09:00
|
|
|
'" but should be the same as the Font\'s BaseFont "' +
|
|
|
|
baseFontStr + '"');
|
2014-04-09 05:49:51 +09:00
|
|
|
// Workaround for cases where e.g. fontNameStr = 'Arial' and
|
|
|
|
// baseFontStr = 'Arial,Bold' (needed when no font file is embedded).
|
|
|
|
if (fontNameStr && baseFontStr &&
|
2014-06-11 00:25:49 +09:00
|
|
|
baseFontStr.indexOf(fontNameStr) === 0) {
|
2014-04-09 05:49:51 +09:00
|
|
|
fontName = baseFont;
|
|
|
|
}
|
2013-03-03 21:30:08 +09:00
|
|
|
}
|
2013-01-12 10:10:09 +09:00
|
|
|
}
|
2014-03-23 03:15:51 +09:00
|
|
|
fontName = (fontName || baseFont);
|
2013-01-12 10:10:09 +09:00
|
|
|
|
2014-04-13 23:02:56 +09:00
|
|
|
assert(isName(fontName), 'invalid font name');
|
2011-10-25 08:55:23 +09:00
|
|
|
|
|
|
|
var fontFile = descriptor.get('FontFile', 'FontFile2', 'FontFile3');
|
|
|
|
if (fontFile) {
|
|
|
|
if (fontFile.dict) {
|
|
|
|
var subtype = fontFile.dict.get('Subtype');
|
2014-03-23 03:15:51 +09:00
|
|
|
if (subtype) {
|
2011-10-25 08:55:23 +09:00
|
|
|
subtype = subtype.name;
|
2014-03-23 03:15:51 +09:00
|
|
|
}
|
2011-10-25 08:55:23 +09:00
|
|
|
var length1 = fontFile.dict.get('Length1');
|
|
|
|
var length2 = fontFile.dict.get('Length2');
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2014-04-08 06:42:54 +09:00
|
|
|
properties = {
|
2014-06-16 23:52:04 +09:00
|
|
|
type: type,
|
2014-01-09 07:33:22 +09:00
|
|
|
name: fontName.name,
|
2011-10-25 08:55:23 +09:00
|
|
|
subtype: subtype,
|
|
|
|
file: fontFile,
|
|
|
|
length1: length1,
|
|
|
|
length2: length2,
|
2012-09-14 00:09:46 +09:00
|
|
|
loadedName: baseDict.loadedName,
|
2011-10-25 08:55:23 +09:00
|
|
|
composite: composite,
|
2012-04-24 07:44:51 +09:00
|
|
|
wideChars: composite,
|
2011-10-25 08:55:23 +09:00
|
|
|
fixedPitch: false,
|
2014-03-23 03:15:51 +09:00
|
|
|
fontMatrix: (dict.get('FontMatrix') || FONT_IDENTITY_MATRIX),
|
2011-10-25 08:55:23 +09:00
|
|
|
firstChar: firstChar || 0,
|
2014-03-23 03:15:51 +09:00
|
|
|
lastChar: (lastChar || maxCharIndex),
|
2011-10-25 08:55:23 +09:00
|
|
|
bbox: descriptor.get('FontBBox'),
|
|
|
|
ascent: descriptor.get('Ascent'),
|
|
|
|
descent: descriptor.get('Descent'),
|
|
|
|
xHeight: descriptor.get('XHeight'),
|
|
|
|
capHeight: descriptor.get('CapHeight'),
|
|
|
|
flags: descriptor.get('Flags'),
|
|
|
|
italicAngle: descriptor.get('ItalicAngle'),
|
|
|
|
coded: false
|
|
|
|
};
|
2013-02-08 21:29:22 +09:00
|
|
|
|
|
|
|
if (composite) {
|
|
|
|
var cidEncoding = baseDict.get('Encoding');
|
|
|
|
if (isName(cidEncoding)) {
|
|
|
|
properties.cidEncoding = cidEncoding.name;
|
|
|
|
}
|
2014-03-15 03:22:02 +09:00
|
|
|
properties.cMap = CMapFactory.create(cidEncoding,
|
|
|
|
{ url: PDFJS.cMapUrl, packed: PDFJS.cMapPacked }, null);
|
2014-02-12 03:27:09 +09:00
|
|
|
properties.vertical = properties.cMap.vertical;
|
2013-02-08 21:29:22 +09:00
|
|
|
}
|
2011-10-29 10:38:31 +09:00
|
|
|
this.extractDataStructures(dict, baseDict, xref, properties);
|
2014-02-12 03:27:09 +09:00
|
|
|
this.extractWidths(dict, xref, descriptor, properties);
|
2011-10-25 08:55:23 +09:00
|
|
|
|
2014-06-16 23:52:04 +09:00
|
|
|
if (type === 'Type3') {
|
2014-05-20 06:27:54 +09:00
|
|
|
properties.isType3Font = true;
|
2011-10-25 08:55:23 +09:00
|
|
|
}
|
|
|
|
|
2012-09-14 00:09:46 +09:00
|
|
|
return new Font(fontName.name, fontFile, properties);
|
2011-10-25 08:55:23 +09:00
|
|
|
}
|
|
|
|
};
|
|
|
|
|
2011-12-09 07:18:43 +09:00
|
|
|
return PartialEvaluator;
|
2011-10-25 08:55:23 +09:00
|
|
|
})();
|
|
|
|
|
2014-05-20 06:27:54 +09:00
|
|
|
var TranslatedFont = (function TranslatedFontClosure() {
|
|
|
|
function TranslatedFont(loadedName, font, dict) {
|
|
|
|
this.loadedName = loadedName;
|
|
|
|
this.font = font;
|
|
|
|
this.dict = dict;
|
|
|
|
this.type3Loaded = null;
|
|
|
|
this.sent = false;
|
|
|
|
}
|
|
|
|
TranslatedFont.prototype = {
|
|
|
|
send: function (handler) {
|
|
|
|
if (this.sent) {
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
var fontData = this.font.exportData();
|
|
|
|
handler.send('commonobj', [
|
|
|
|
this.loadedName,
|
|
|
|
'Font',
|
|
|
|
fontData
|
|
|
|
]);
|
|
|
|
this.sent = true;
|
|
|
|
},
|
2015-10-21 10:50:32 +09:00
|
|
|
loadType3Data: function (evaluator, resources, parentOperatorList, task) {
|
2014-05-20 06:27:54 +09:00
|
|
|
assert(this.font.isType3Font);
|
|
|
|
|
|
|
|
if (this.type3Loaded) {
|
|
|
|
return this.type3Loaded;
|
|
|
|
}
|
|
|
|
|
|
|
|
var translatedFont = this.font;
|
|
|
|
var loadCharProcsPromise = Promise.resolve();
|
|
|
|
var charProcs = this.dict.get('CharProcs').getAll();
|
|
|
|
var fontResources = this.dict.get('Resources') || resources;
|
|
|
|
var charProcKeys = Object.keys(charProcs);
|
|
|
|
var charProcOperatorList = {};
|
|
|
|
for (var i = 0, n = charProcKeys.length; i < n; ++i) {
|
|
|
|
loadCharProcsPromise = loadCharProcsPromise.then(function (key) {
|
|
|
|
var glyphStream = charProcs[key];
|
|
|
|
var operatorList = new OperatorList();
|
2015-10-21 10:50:32 +09:00
|
|
|
return evaluator.getOperatorList(glyphStream, task, fontResources,
|
2014-07-24 21:59:21 +09:00
|
|
|
operatorList).then(function () {
|
|
|
|
charProcOperatorList[key] = operatorList.getIR();
|
|
|
|
|
|
|
|
// Add the dependencies to the parent operator list so they are
|
|
|
|
// resolved before sub operator list is executed synchronously.
|
|
|
|
parentOperatorList.addDependencies(operatorList.dependencies);
|
|
|
|
}, function (reason) {
|
|
|
|
warn('Type3 font resource \"' + key + '\" is not available');
|
|
|
|
var operatorList = new OperatorList();
|
|
|
|
charProcOperatorList[key] = operatorList.getIR();
|
|
|
|
});
|
2014-05-20 06:27:54 +09:00
|
|
|
}.bind(this, charProcKeys[i]));
|
|
|
|
}
|
|
|
|
this.type3Loaded = loadCharProcsPromise.then(function () {
|
|
|
|
translatedFont.charProcOperatorList = charProcOperatorList;
|
|
|
|
});
|
|
|
|
return this.type3Loaded;
|
|
|
|
}
|
|
|
|
};
|
|
|
|
return TranslatedFont;
|
|
|
|
})();
|
|
|
|
|
2013-08-01 03:17:36 +09:00
|
|
|
var OperatorList = (function OperatorListClosure() {
|
2014-02-24 23:00:08 +09:00
|
|
|
var CHUNK_SIZE = 1000;
|
2014-02-25 01:07:36 +09:00
|
|
|
var CHUNK_SIZE_ABOUT = CHUNK_SIZE - 5; // close to chunk size
|
2013-08-01 03:17:36 +09:00
|
|
|
|
2014-03-15 22:01:07 +09:00
|
|
|
function getTransfers(queue) {
|
|
|
|
var transfers = [];
|
|
|
|
var fnArray = queue.fnArray, argsArray = queue.argsArray;
|
|
|
|
for (var i = 0, ii = queue.length; i < ii; i++) {
|
|
|
|
switch (fnArray[i]) {
|
|
|
|
case OPS.paintInlineImageXObject:
|
|
|
|
case OPS.paintInlineImageXObjectGroup:
|
|
|
|
case OPS.paintImageMaskXObject:
|
|
|
|
var arg = argsArray[i][0]; // first param in imgData
|
|
|
|
if (!arg.cached) {
|
|
|
|
transfers.push(arg.data.buffer);
|
|
|
|
}
|
|
|
|
break;
|
2013-11-12 12:30:26 +09:00
|
|
|
}
|
|
|
|
}
|
2014-03-15 22:01:07 +09:00
|
|
|
return transfers;
|
|
|
|
}
|
2013-11-12 12:30:26 +09:00
|
|
|
|
2014-03-15 22:01:07 +09:00
|
|
|
function OperatorList(intent, messageHandler, pageIndex) {
|
2013-08-01 03:17:36 +09:00
|
|
|
this.messageHandler = messageHandler;
|
2014-03-15 22:04:13 +09:00
|
|
|
this.fnArray = [];
|
2013-08-01 03:17:36 +09:00
|
|
|
this.argsArray = [];
|
2014-02-10 16:30:39 +09:00
|
|
|
this.dependencies = {};
|
2015-10-27 00:38:06 +09:00
|
|
|
this._totalLength = 0;
|
2013-08-01 03:17:36 +09:00
|
|
|
this.pageIndex = pageIndex;
|
2014-03-07 23:48:42 +09:00
|
|
|
this.intent = intent;
|
2013-08-01 03:17:36 +09:00
|
|
|
}
|
|
|
|
|
|
|
|
OperatorList.prototype = {
|
2013-11-14 04:43:38 +09:00
|
|
|
get length() {
|
|
|
|
return this.argsArray.length;
|
|
|
|
},
|
|
|
|
|
2015-10-27 00:38:06 +09:00
|
|
|
/**
|
|
|
|
* @returns {number} The total length of the entire operator list,
|
|
|
|
* since `this.length === 0` after flushing.
|
|
|
|
*/
|
|
|
|
get totalLength() {
|
|
|
|
return (this._totalLength + this.length);
|
|
|
|
},
|
|
|
|
|
2013-08-01 03:17:36 +09:00
|
|
|
addOp: function(fn, args) {
|
2014-03-15 22:04:13 +09:00
|
|
|
this.fnArray.push(fn);
|
|
|
|
this.argsArray.push(args);
|
2013-11-14 04:43:38 +09:00
|
|
|
if (this.messageHandler) {
|
2014-03-15 22:04:13 +09:00
|
|
|
if (this.fnArray.length >= CHUNK_SIZE) {
|
2013-11-14 04:43:38 +09:00
|
|
|
this.flush();
|
2014-03-15 22:04:13 +09:00
|
|
|
} else if (this.fnArray.length >= CHUNK_SIZE_ABOUT &&
|
2014-03-23 03:15:51 +09:00
|
|
|
(fn === OPS.restore || fn === OPS.endText)) {
|
2014-02-25 01:07:36 +09:00
|
|
|
// heuristic to flush on boundary of restore or endText
|
|
|
|
this.flush();
|
2013-11-14 04:43:38 +09:00
|
|
|
}
|
2013-08-01 03:17:36 +09:00
|
|
|
}
|
|
|
|
},
|
|
|
|
|
|
|
|
addDependency: function(dependency) {
|
|
|
|
if (dependency in this.dependencies) {
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
this.dependencies[dependency] = true;
|
2013-11-14 04:43:38 +09:00
|
|
|
this.addOp(OPS.dependency, [dependency]);
|
2013-08-01 03:17:36 +09:00
|
|
|
},
|
|
|
|
|
|
|
|
addDependencies: function(dependencies) {
|
|
|
|
for (var key in dependencies) {
|
|
|
|
this.addDependency(key);
|
|
|
|
}
|
|
|
|
},
|
|
|
|
|
|
|
|
addOpList: function(opList) {
|
|
|
|
Util.extendObj(this.dependencies, opList.dependencies);
|
2013-11-14 04:43:38 +09:00
|
|
|
for (var i = 0, ii = opList.length; i < ii; i++) {
|
|
|
|
this.addOp(opList.fnArray[i], opList.argsArray[i]);
|
|
|
|
}
|
2013-08-01 03:17:36 +09:00
|
|
|
},
|
|
|
|
|
|
|
|
getIR: function() {
|
|
|
|
return {
|
|
|
|
fnArray: this.fnArray,
|
2013-11-14 04:43:38 +09:00
|
|
|
argsArray: this.argsArray,
|
|
|
|
length: this.length
|
2013-08-01 03:17:36 +09:00
|
|
|
};
|
|
|
|
},
|
|
|
|
|
|
|
|
flush: function(lastChunk) {
|
2014-08-08 01:30:48 +09:00
|
|
|
if (this.intent !== 'oplist') {
|
|
|
|
new QueueOptimizer().optimize(this);
|
|
|
|
}
|
2013-11-12 12:30:26 +09:00
|
|
|
var transfers = getTransfers(this);
|
2015-10-27 00:38:06 +09:00
|
|
|
var length = this.length;
|
|
|
|
this._totalLength += length;
|
|
|
|
|
2013-08-01 03:17:36 +09:00
|
|
|
this.messageHandler.send('RenderPageChunk', {
|
|
|
|
operatorList: {
|
|
|
|
fnArray: this.fnArray,
|
|
|
|
argsArray: this.argsArray,
|
2013-11-14 04:43:38 +09:00
|
|
|
lastChunk: lastChunk,
|
2015-10-27 00:38:06 +09:00
|
|
|
length: length
|
2013-08-01 03:17:36 +09:00
|
|
|
},
|
2014-03-07 23:48:42 +09:00
|
|
|
pageIndex: this.pageIndex,
|
|
|
|
intent: this.intent
|
2014-05-08 08:15:25 +09:00
|
|
|
}, transfers);
|
2014-03-15 22:04:13 +09:00
|
|
|
this.dependencies = {};
|
|
|
|
this.fnArray.length = 0;
|
|
|
|
this.argsArray.length = 0;
|
2013-08-01 03:17:36 +09:00
|
|
|
}
|
|
|
|
};
|
|
|
|
|
|
|
|
return OperatorList;
|
|
|
|
})();
|
2013-11-12 12:30:26 +09:00
|
|
|
|
2014-04-10 08:44:07 +09:00
|
|
|
var StateManager = (function StateManagerClosure() {
|
|
|
|
function StateManager(initialState) {
|
|
|
|
this.state = initialState;
|
|
|
|
this.stateStack = [];
|
|
|
|
}
|
|
|
|
StateManager.prototype = {
|
|
|
|
save: function () {
|
|
|
|
var old = this.state;
|
|
|
|
this.stateStack.push(this.state);
|
|
|
|
this.state = old.clone();
|
|
|
|
},
|
|
|
|
restore: function () {
|
|
|
|
var prev = this.stateStack.pop();
|
|
|
|
if (prev) {
|
|
|
|
this.state = prev;
|
|
|
|
}
|
|
|
|
},
|
|
|
|
transform: function (args) {
|
|
|
|
this.state.ctm = Util.transform(this.state.ctm, args);
|
|
|
|
}
|
|
|
|
};
|
|
|
|
return StateManager;
|
|
|
|
})();
|
|
|
|
|
2013-09-15 02:58:58 +09:00
|
|
|
var TextState = (function TextStateClosure() {
|
|
|
|
function TextState() {
|
2014-04-10 08:44:07 +09:00
|
|
|
this.ctm = new Float32Array(IDENTITY_MATRIX);
|
2013-09-15 02:58:58 +09:00
|
|
|
this.fontSize = 0;
|
2014-04-10 08:44:07 +09:00
|
|
|
this.font = null;
|
|
|
|
this.fontMatrix = FONT_IDENTITY_MATRIX;
|
|
|
|
this.textMatrix = IDENTITY_MATRIX.slice();
|
|
|
|
this.textLineMatrix = IDENTITY_MATRIX.slice();
|
|
|
|
this.charSpacing = 0;
|
|
|
|
this.wordSpacing = 0;
|
2013-09-15 02:58:58 +09:00
|
|
|
this.leading = 0;
|
|
|
|
this.textHScale = 1;
|
|
|
|
this.textRise = 0;
|
|
|
|
}
|
2014-03-23 03:15:51 +09:00
|
|
|
|
2013-09-15 02:58:58 +09:00
|
|
|
TextState.prototype = {
|
|
|
|
setTextMatrix: function TextState_setTextMatrix(a, b, c, d, e, f) {
|
|
|
|
var m = this.textMatrix;
|
2014-02-10 16:30:39 +09:00
|
|
|
m[0] = a; m[1] = b; m[2] = c; m[3] = d; m[4] = e; m[5] = f;
|
2013-09-15 02:58:58 +09:00
|
|
|
},
|
2014-04-10 08:44:07 +09:00
|
|
|
setTextLineMatrix: function TextState_setTextMatrix(a, b, c, d, e, f) {
|
|
|
|
var m = this.textLineMatrix;
|
|
|
|
m[0] = a; m[1] = b; m[2] = c; m[3] = d; m[4] = e; m[5] = f;
|
|
|
|
},
|
2013-09-15 02:58:58 +09:00
|
|
|
translateTextMatrix: function TextState_translateTextMatrix(x, y) {
|
|
|
|
var m = this.textMatrix;
|
|
|
|
m[4] = m[0] * x + m[2] * y + m[4];
|
|
|
|
m[5] = m[1] * x + m[3] * y + m[5];
|
|
|
|
},
|
2014-04-10 08:44:07 +09:00
|
|
|
translateTextLineMatrix: function TextState_translateTextMatrix(x, y) {
|
|
|
|
var m = this.textLineMatrix;
|
|
|
|
m[4] = m[0] * x + m[2] * y + m[4];
|
|
|
|
m[5] = m[1] * x + m[3] * y + m[5];
|
2013-09-15 02:58:58 +09:00
|
|
|
},
|
2014-04-10 08:44:07 +09:00
|
|
|
calcRenderMatrix: function TextState_calcRendeMatrix(ctm) {
|
|
|
|
// 9.4.4 Text Space Details
|
|
|
|
var tsm = [this.fontSize * this.textHScale, 0,
|
|
|
|
0, this.fontSize,
|
|
|
|
0, this.textRise];
|
|
|
|
return Util.transform(ctm, Util.transform(this.textMatrix, tsm));
|
|
|
|
},
|
|
|
|
carriageReturn: function TextState_carriageReturn() {
|
|
|
|
this.translateTextLineMatrix(0, -this.leading);
|
|
|
|
this.textMatrix = this.textLineMatrix.slice();
|
|
|
|
},
|
|
|
|
clone: function TextState_clone() {
|
|
|
|
var clone = Object.create(this);
|
|
|
|
clone.textMatrix = this.textMatrix.slice();
|
|
|
|
clone.textLineMatrix = this.textLineMatrix.slice();
|
|
|
|
clone.fontMatrix = this.fontMatrix.slice();
|
|
|
|
return clone;
|
|
|
|
}
|
2013-09-15 02:58:58 +09:00
|
|
|
};
|
|
|
|
return TextState;
|
|
|
|
})();
|
2013-08-01 03:17:36 +09:00
|
|
|
|
2011-12-09 07:18:43 +09:00
|
|
|
var EvalState = (function EvalStateClosure() {
|
|
|
|
function EvalState() {
|
2014-04-10 08:44:07 +09:00
|
|
|
this.ctm = new Float32Array(IDENTITY_MATRIX);
|
2013-08-01 06:01:55 +09:00
|
|
|
this.font = null;
|
2013-08-20 08:33:20 +09:00
|
|
|
this.textRenderingMode = TextRenderingMode.FILL;
|
2014-05-22 02:47:42 +09:00
|
|
|
this.fillColorSpace = ColorSpace.singletons.gray;
|
|
|
|
this.strokeColorSpace = ColorSpace.singletons.gray;
|
2011-10-25 08:55:23 +09:00
|
|
|
}
|
2011-12-09 07:18:43 +09:00
|
|
|
EvalState.prototype = {
|
2013-08-01 06:01:55 +09:00
|
|
|
clone: function CanvasExtraState_clone() {
|
|
|
|
return Object.create(this);
|
|
|
|
},
|
2011-10-25 08:55:23 +09:00
|
|
|
};
|
2011-12-09 07:18:43 +09:00
|
|
|
return EvalState;
|
2011-10-25 08:55:23 +09:00
|
|
|
})();
|
2011-10-28 03:51:10 +09:00
|
|
|
|
2014-04-08 06:42:54 +09:00
|
|
|
var EvaluatorPreprocessor = (function EvaluatorPreprocessorClosure() {
|
2014-01-17 22:16:52 +09:00
|
|
|
// Specifies properties for each command
|
|
|
|
//
|
|
|
|
// If variableArgs === true: [0, `numArgs`] expected
|
|
|
|
// If variableArgs === false: exactly `numArgs` expected
|
|
|
|
var OP_MAP = {
|
|
|
|
// Graphic state
|
|
|
|
w: { id: OPS.setLineWidth, numArgs: 1, variableArgs: false },
|
|
|
|
J: { id: OPS.setLineCap, numArgs: 1, variableArgs: false },
|
|
|
|
j: { id: OPS.setLineJoin, numArgs: 1, variableArgs: false },
|
|
|
|
M: { id: OPS.setMiterLimit, numArgs: 1, variableArgs: false },
|
|
|
|
d: { id: OPS.setDash, numArgs: 2, variableArgs: false },
|
|
|
|
ri: { id: OPS.setRenderingIntent, numArgs: 1, variableArgs: false },
|
|
|
|
i: { id: OPS.setFlatness, numArgs: 1, variableArgs: false },
|
|
|
|
gs: { id: OPS.setGState, numArgs: 1, variableArgs: false },
|
|
|
|
q: { id: OPS.save, numArgs: 0, variableArgs: false },
|
|
|
|
Q: { id: OPS.restore, numArgs: 0, variableArgs: false },
|
|
|
|
cm: { id: OPS.transform, numArgs: 6, variableArgs: false },
|
|
|
|
|
|
|
|
// Path
|
|
|
|
m: { id: OPS.moveTo, numArgs: 2, variableArgs: false },
|
|
|
|
l: { id: OPS.lineTo, numArgs: 2, variableArgs: false },
|
|
|
|
c: { id: OPS.curveTo, numArgs: 6, variableArgs: false },
|
|
|
|
v: { id: OPS.curveTo2, numArgs: 4, variableArgs: false },
|
|
|
|
y: { id: OPS.curveTo3, numArgs: 4, variableArgs: false },
|
|
|
|
h: { id: OPS.closePath, numArgs: 0, variableArgs: false },
|
|
|
|
re: { id: OPS.rectangle, numArgs: 4, variableArgs: false },
|
|
|
|
S: { id: OPS.stroke, numArgs: 0, variableArgs: false },
|
|
|
|
s: { id: OPS.closeStroke, numArgs: 0, variableArgs: false },
|
|
|
|
f: { id: OPS.fill, numArgs: 0, variableArgs: false },
|
|
|
|
F: { id: OPS.fill, numArgs: 0, variableArgs: false },
|
|
|
|
'f*': { id: OPS.eoFill, numArgs: 0, variableArgs: false },
|
|
|
|
B: { id: OPS.fillStroke, numArgs: 0, variableArgs: false },
|
|
|
|
'B*': { id: OPS.eoFillStroke, numArgs: 0, variableArgs: false },
|
|
|
|
b: { id: OPS.closeFillStroke, numArgs: 0, variableArgs: false },
|
|
|
|
'b*': { id: OPS.closeEOFillStroke, numArgs: 0, variableArgs: false },
|
|
|
|
n: { id: OPS.endPath, numArgs: 0, variableArgs: false },
|
|
|
|
|
|
|
|
// Clipping
|
|
|
|
W: { id: OPS.clip, numArgs: 0, variableArgs: false },
|
|
|
|
'W*': { id: OPS.eoClip, numArgs: 0, variableArgs: false },
|
|
|
|
|
|
|
|
// Text
|
|
|
|
BT: { id: OPS.beginText, numArgs: 0, variableArgs: false },
|
|
|
|
ET: { id: OPS.endText, numArgs: 0, variableArgs: false },
|
|
|
|
Tc: { id: OPS.setCharSpacing, numArgs: 1, variableArgs: false },
|
|
|
|
Tw: { id: OPS.setWordSpacing, numArgs: 1, variableArgs: false },
|
|
|
|
Tz: { id: OPS.setHScale, numArgs: 1, variableArgs: false },
|
|
|
|
TL: { id: OPS.setLeading, numArgs: 1, variableArgs: false },
|
|
|
|
Tf: { id: OPS.setFont, numArgs: 2, variableArgs: false },
|
|
|
|
Tr: { id: OPS.setTextRenderingMode, numArgs: 1, variableArgs: false },
|
|
|
|
Ts: { id: OPS.setTextRise, numArgs: 1, variableArgs: false },
|
|
|
|
Td: { id: OPS.moveText, numArgs: 2, variableArgs: false },
|
|
|
|
TD: { id: OPS.setLeadingMoveText, numArgs: 2, variableArgs: false },
|
|
|
|
Tm: { id: OPS.setTextMatrix, numArgs: 6, variableArgs: false },
|
|
|
|
'T*': { id: OPS.nextLine, numArgs: 0, variableArgs: false },
|
|
|
|
Tj: { id: OPS.showText, numArgs: 1, variableArgs: false },
|
|
|
|
TJ: { id: OPS.showSpacedText, numArgs: 1, variableArgs: false },
|
|
|
|
'\'': { id: OPS.nextLineShowText, numArgs: 1, variableArgs: false },
|
|
|
|
'"': { id: OPS.nextLineSetSpacingShowText, numArgs: 3,
|
2014-03-23 03:15:51 +09:00
|
|
|
variableArgs: false },
|
2014-01-17 22:16:52 +09:00
|
|
|
|
|
|
|
// Type3 fonts
|
|
|
|
d0: { id: OPS.setCharWidth, numArgs: 2, variableArgs: false },
|
|
|
|
d1: { id: OPS.setCharWidthAndBounds, numArgs: 6, variableArgs: false },
|
|
|
|
|
|
|
|
// Color
|
|
|
|
CS: { id: OPS.setStrokeColorSpace, numArgs: 1, variableArgs: false },
|
|
|
|
cs: { id: OPS.setFillColorSpace, numArgs: 1, variableArgs: false },
|
|
|
|
SC: { id: OPS.setStrokeColor, numArgs: 4, variableArgs: true },
|
|
|
|
SCN: { id: OPS.setStrokeColorN, numArgs: 33, variableArgs: true },
|
|
|
|
sc: { id: OPS.setFillColor, numArgs: 4, variableArgs: true },
|
|
|
|
scn: { id: OPS.setFillColorN, numArgs: 33, variableArgs: true },
|
|
|
|
G: { id: OPS.setStrokeGray, numArgs: 1, variableArgs: false },
|
|
|
|
g: { id: OPS.setFillGray, numArgs: 1, variableArgs: false },
|
|
|
|
RG: { id: OPS.setStrokeRGBColor, numArgs: 3, variableArgs: false },
|
|
|
|
rg: { id: OPS.setFillRGBColor, numArgs: 3, variableArgs: false },
|
|
|
|
K: { id: OPS.setStrokeCMYKColor, numArgs: 4, variableArgs: false },
|
|
|
|
k: { id: OPS.setFillCMYKColor, numArgs: 4, variableArgs: false },
|
|
|
|
|
|
|
|
// Shading
|
|
|
|
sh: { id: OPS.shadingFill, numArgs: 1, variableArgs: false },
|
|
|
|
|
|
|
|
// Images
|
|
|
|
BI: { id: OPS.beginInlineImage, numArgs: 0, variableArgs: false },
|
|
|
|
ID: { id: OPS.beginImageData, numArgs: 0, variableArgs: false },
|
|
|
|
EI: { id: OPS.endInlineImage, numArgs: 1, variableArgs: false },
|
|
|
|
|
|
|
|
// XObjects
|
|
|
|
Do: { id: OPS.paintXObject, numArgs: 1, variableArgs: false },
|
|
|
|
MP: { id: OPS.markPoint, numArgs: 1, variableArgs: false },
|
|
|
|
DP: { id: OPS.markPointProps, numArgs: 2, variableArgs: false },
|
|
|
|
BMC: { id: OPS.beginMarkedContent, numArgs: 1, variableArgs: false },
|
|
|
|
BDC: { id: OPS.beginMarkedContentProps, numArgs: 2,
|
2014-03-23 03:15:51 +09:00
|
|
|
variableArgs: false },
|
2014-01-17 22:16:52 +09:00
|
|
|
EMC: { id: OPS.endMarkedContent, numArgs: 0, variableArgs: false },
|
|
|
|
|
|
|
|
// Compatibility
|
|
|
|
BX: { id: OPS.beginCompat, numArgs: 0, variableArgs: false },
|
|
|
|
EX: { id: OPS.endCompat, numArgs: 0, variableArgs: false },
|
|
|
|
|
|
|
|
// (reserved partial commands for the lexer)
|
|
|
|
BM: null,
|
|
|
|
BD: null,
|
|
|
|
'true': null,
|
|
|
|
fa: null,
|
|
|
|
fal: null,
|
|
|
|
fals: null,
|
|
|
|
'false': null,
|
|
|
|
nu: null,
|
|
|
|
nul: null,
|
|
|
|
'null': null
|
|
|
|
};
|
|
|
|
|
2014-04-10 08:44:07 +09:00
|
|
|
function EvaluatorPreprocessor(stream, xref, stateManager) {
|
2014-01-17 22:16:52 +09:00
|
|
|
// TODO(mduan): pass array of knownCommands rather than OP_MAP
|
|
|
|
// dictionary
|
|
|
|
this.parser = new Parser(new Lexer(stream, OP_MAP), false, xref);
|
2014-04-10 08:44:07 +09:00
|
|
|
this.stateManager = stateManager;
|
2014-05-15 15:07:43 +09:00
|
|
|
this.nonProcessedArgs = [];
|
2014-01-17 22:16:52 +09:00
|
|
|
}
|
2014-03-23 03:15:51 +09:00
|
|
|
|
2014-01-17 22:16:52 +09:00
|
|
|
EvaluatorPreprocessor.prototype = {
|
|
|
|
get savedStatesDepth() {
|
2014-04-10 08:44:07 +09:00
|
|
|
return this.stateManager.stateStack.length;
|
2014-01-17 22:16:52 +09:00
|
|
|
},
|
2014-03-23 03:15:51 +09:00
|
|
|
|
2014-08-11 09:23:23 +09:00
|
|
|
// |operation| is an object with two fields:
|
|
|
|
//
|
|
|
|
// - |fn| is an out param.
|
|
|
|
//
|
|
|
|
// - |args| is an inout param. On entry, it should have one of two values.
|
|
|
|
//
|
|
|
|
// - An empty array. This indicates that the caller is providing the
|
|
|
|
// array in which the args will be stored in. The caller should use
|
|
|
|
// this value if it can reuse a single array for each call to read().
|
|
|
|
//
|
|
|
|
// - |null|. This indicates that the caller needs this function to create
|
|
|
|
// the array in which any args are stored in. If there are zero args,
|
|
|
|
// this function will leave |operation.args| as |null| (thus avoiding
|
|
|
|
// allocations that would occur if we used an empty array to represent
|
|
|
|
// zero arguments). Otherwise, it will replace |null| with a new array
|
|
|
|
// containing the arguments. The caller should use this value if it
|
|
|
|
// cannot reuse an array for each call to read().
|
|
|
|
//
|
|
|
|
// These two modes are present because this function is very hot and so
|
|
|
|
// avoiding allocations where possible is worthwhile.
|
|
|
|
//
|
2014-06-19 00:15:35 +09:00
|
|
|
read: function EvaluatorPreprocessor_read(operation) {
|
2014-08-11 09:23:23 +09:00
|
|
|
var args = operation.args;
|
2014-01-17 22:16:52 +09:00
|
|
|
while (true) {
|
|
|
|
var obj = this.parser.getObj();
|
2014-06-19 19:47:00 +09:00
|
|
|
if (isCmd(obj)) {
|
|
|
|
var cmd = obj.cmd;
|
|
|
|
// Check that the command is valid
|
|
|
|
var opSpec = OP_MAP[cmd];
|
|
|
|
if (!opSpec) {
|
|
|
|
warn('Unknown command "' + cmd + '"');
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
|
|
|
|
var fn = opSpec.id;
|
|
|
|
var numArgs = opSpec.numArgs;
|
2014-08-08 19:50:54 +09:00
|
|
|
var argsLength = args !== null ? args.length : 0;
|
2014-06-19 19:47:00 +09:00
|
|
|
|
|
|
|
if (!opSpec.variableArgs) {
|
|
|
|
// Postscript commands can be nested, e.g. /F2 /GS2 gs 5.711 Tf
|
|
|
|
if (argsLength !== numArgs) {
|
|
|
|
var nonProcessedArgs = this.nonProcessedArgs;
|
|
|
|
while (argsLength > numArgs) {
|
|
|
|
nonProcessedArgs.push(args.shift());
|
|
|
|
argsLength--;
|
|
|
|
}
|
|
|
|
while (argsLength < numArgs && nonProcessedArgs.length !== 0) {
|
|
|
|
if (!args) {
|
|
|
|
args = [];
|
|
|
|
}
|
|
|
|
args.unshift(nonProcessedArgs.pop());
|
|
|
|
argsLength++;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
if (argsLength < numArgs) {
|
|
|
|
// If we receive too few args, it's not possible to possible
|
|
|
|
// to execute the command, so skip the command
|
|
|
|
info('Command ' + fn + ': because expected ' +
|
|
|
|
numArgs + ' args, but received ' + argsLength +
|
|
|
|
' args; skipping');
|
|
|
|
args = null;
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
} else if (argsLength > numArgs) {
|
|
|
|
info('Command ' + fn + ': expected [0,' + numArgs +
|
|
|
|
'] args, but received ' + argsLength + ' args');
|
|
|
|
}
|
|
|
|
|
|
|
|
// TODO figure out how to type-check vararg functions
|
|
|
|
this.preprocessCommand(fn, args);
|
|
|
|
|
|
|
|
operation.fn = fn;
|
|
|
|
operation.args = args;
|
|
|
|
return true;
|
|
|
|
} else {
|
|
|
|
if (isEOF(obj)) {
|
|
|
|
return false; // no more commands
|
|
|
|
}
|
2014-01-17 22:16:52 +09:00
|
|
|
// argument
|
2014-06-02 21:58:16 +09:00
|
|
|
if (obj !== null) {
|
2014-06-20 12:52:39 +09:00
|
|
|
if (!args) {
|
|
|
|
args = [];
|
|
|
|
}
|
2014-03-23 03:15:51 +09:00
|
|
|
args.push((obj instanceof Dict ? obj.getAll() : obj));
|
2014-04-13 23:02:56 +09:00
|
|
|
assert(args.length <= 33, 'Too many arguments');
|
2014-01-17 22:16:52 +09:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
},
|
2014-03-23 03:15:51 +09:00
|
|
|
|
|
|
|
preprocessCommand:
|
|
|
|
function EvaluatorPreprocessor_preprocessCommand(fn, args) {
|
2014-01-17 22:16:52 +09:00
|
|
|
switch (fn | 0) {
|
|
|
|
case OPS.save:
|
2014-04-10 08:44:07 +09:00
|
|
|
this.stateManager.save();
|
2014-01-17 22:16:52 +09:00
|
|
|
break;
|
|
|
|
case OPS.restore:
|
2014-04-10 08:44:07 +09:00
|
|
|
this.stateManager.restore();
|
2014-01-17 22:16:52 +09:00
|
|
|
break;
|
|
|
|
case OPS.transform:
|
2014-04-10 08:44:07 +09:00
|
|
|
this.stateManager.transform(args);
|
2014-01-17 22:16:52 +09:00
|
|
|
break;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
};
|
|
|
|
return EvaluatorPreprocessor;
|
|
|
|
})();
|
2014-02-24 11:42:54 +09:00
|
|
|
|
|
|
|
var QueueOptimizer = (function QueueOptimizerClosure() {
|
|
|
|
function addState(parentState, pattern, fn) {
|
|
|
|
var state = parentState;
|
|
|
|
for (var i = 0, ii = pattern.length - 1; i < ii; i++) {
|
|
|
|
var item = pattern[i];
|
2014-03-23 03:15:51 +09:00
|
|
|
state = (state[item] || (state[item] = []));
|
2014-02-24 11:42:54 +09:00
|
|
|
}
|
|
|
|
state[pattern[pattern.length - 1]] = fn;
|
|
|
|
}
|
|
|
|
|
2014-06-24 15:34:54 +09:00
|
|
|
function handlePaintSolidColorImageMask(iFirstSave, count, fnArray,
|
|
|
|
argsArray) {
|
|
|
|
// Handles special case of mainly LaTeX documents which use image masks to
|
|
|
|
// draw lines with the current fill style.
|
2014-03-17 00:17:13 +09:00
|
|
|
// 'count' groups of (save, transform, paintImageMaskXObject, restore)+
|
2014-06-24 15:34:54 +09:00
|
|
|
// have been found at iFirstSave.
|
|
|
|
var iFirstPIMXO = iFirstSave + 2;
|
2014-03-17 00:17:13 +09:00
|
|
|
for (var i = 0; i < count; i++) {
|
2014-06-24 15:34:54 +09:00
|
|
|
var arg = argsArray[iFirstPIMXO + 4 * i];
|
2014-06-02 19:43:20 +09:00
|
|
|
var imageMask = arg.length === 1 && arg[0];
|
|
|
|
if (imageMask && imageMask.width === 1 && imageMask.height === 1 &&
|
2014-06-24 15:34:54 +09:00
|
|
|
(!imageMask.data.length ||
|
|
|
|
(imageMask.data.length === 1 && imageMask.data[0] === 0))) {
|
|
|
|
fnArray[iFirstPIMXO + 4 * i] = OPS.paintSolidColorImageMask;
|
2014-03-17 00:17:13 +09:00
|
|
|
continue;
|
|
|
|
}
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
return count - i;
|
|
|
|
}
|
|
|
|
|
2014-02-24 11:42:54 +09:00
|
|
|
var InitialState = [];
|
|
|
|
|
2014-06-24 15:34:54 +09:00
|
|
|
// This replaces (save, transform, paintInlineImageXObject, restore)+
|
|
|
|
// sequences with one |paintInlineImageXObjectGroup| operation.
|
2014-02-24 11:42:54 +09:00
|
|
|
addState(InitialState,
|
|
|
|
[OPS.save, OPS.transform, OPS.paintInlineImageXObject, OPS.restore],
|
|
|
|
function foundInlineImageGroup(context) {
|
|
|
|
var MIN_IMAGES_IN_INLINE_IMAGES_BLOCK = 10;
|
|
|
|
var MAX_IMAGES_IN_INLINE_IMAGES_BLOCK = 200;
|
|
|
|
var MAX_WIDTH = 1000;
|
|
|
|
var IMAGE_PADDING = 1;
|
|
|
|
|
|
|
|
var fnArray = context.fnArray, argsArray = context.argsArray;
|
2014-06-24 15:34:54 +09:00
|
|
|
var curr = context.iCurr;
|
|
|
|
var iFirstSave = curr - 3;
|
|
|
|
var iFirstTransform = curr - 2;
|
|
|
|
var iFirstPIIXO = curr - 1;
|
|
|
|
|
|
|
|
// Look for the quartets.
|
|
|
|
var i = iFirstSave + 4;
|
2014-03-15 22:04:13 +09:00
|
|
|
var ii = fnArray.length;
|
2014-06-24 15:34:54 +09:00
|
|
|
while (i + 3 < ii) {
|
|
|
|
if (fnArray[i] !== OPS.save ||
|
|
|
|
fnArray[i + 1] !== OPS.transform ||
|
|
|
|
fnArray[i + 2] !== OPS.paintInlineImageXObject ||
|
|
|
|
fnArray[i + 3] !== OPS.restore) {
|
|
|
|
break; // ops don't match
|
|
|
|
}
|
|
|
|
i += 4;
|
|
|
|
}
|
2014-02-24 11:42:54 +09:00
|
|
|
|
2014-06-24 15:34:54 +09:00
|
|
|
// At this point, i is the index of the first op past the last valid
|
|
|
|
// quartet.
|
|
|
|
var count = Math.min((i - iFirstSave) / 4,
|
|
|
|
MAX_IMAGES_IN_INLINE_IMAGES_BLOCK);
|
2014-02-24 11:42:54 +09:00
|
|
|
if (count < MIN_IMAGES_IN_INLINE_IMAGES_BLOCK) {
|
2014-06-24 15:34:54 +09:00
|
|
|
return i;
|
2014-02-24 11:42:54 +09:00
|
|
|
}
|
2014-06-24 15:34:54 +09:00
|
|
|
|
2014-02-24 11:42:54 +09:00
|
|
|
// assuming that heights of those image is too small (~1 pixel)
|
|
|
|
// packing as much as possible by lines
|
|
|
|
var maxX = 0;
|
|
|
|
var map = [], maxLineHeight = 0;
|
|
|
|
var currentX = IMAGE_PADDING, currentY = IMAGE_PADDING;
|
2014-04-08 06:42:54 +09:00
|
|
|
var q;
|
|
|
|
for (q = 0; q < count; q++) {
|
2014-06-24 15:34:54 +09:00
|
|
|
var transform = argsArray[iFirstTransform + (q << 2)];
|
|
|
|
var img = argsArray[iFirstPIIXO + (q << 2)][0];
|
2014-02-24 11:42:54 +09:00
|
|
|
if (currentX + img.width > MAX_WIDTH) {
|
|
|
|
// starting new line
|
|
|
|
maxX = Math.max(maxX, currentX);
|
|
|
|
currentY += maxLineHeight + 2 * IMAGE_PADDING;
|
|
|
|
currentX = 0;
|
|
|
|
maxLineHeight = 0;
|
|
|
|
}
|
|
|
|
map.push({
|
|
|
|
transform: transform,
|
|
|
|
x: currentX, y: currentY,
|
|
|
|
w: img.width, h: img.height
|
|
|
|
});
|
|
|
|
currentX += img.width + 2 * IMAGE_PADDING;
|
|
|
|
maxLineHeight = Math.max(maxLineHeight, img.height);
|
|
|
|
}
|
|
|
|
var imgWidth = Math.max(maxX, currentX) + IMAGE_PADDING;
|
|
|
|
var imgHeight = currentY + maxLineHeight + IMAGE_PADDING;
|
|
|
|
var imgData = new Uint8Array(imgWidth * imgHeight * 4);
|
|
|
|
var imgRowSize = imgWidth << 2;
|
2014-04-08 06:42:54 +09:00
|
|
|
for (q = 0; q < count; q++) {
|
2014-06-24 15:34:54 +09:00
|
|
|
var data = argsArray[iFirstPIIXO + (q << 2)][0].data;
|
|
|
|
// Copy image by lines and extends pixels into padding.
|
2014-02-24 11:42:54 +09:00
|
|
|
var rowSize = map[q].w << 2;
|
|
|
|
var dataOffset = 0;
|
|
|
|
var offset = (map[q].x + map[q].y * imgWidth) << 2;
|
2014-03-23 03:15:51 +09:00
|
|
|
imgData.set(data.subarray(0, rowSize), offset - imgRowSize);
|
2014-02-24 11:42:54 +09:00
|
|
|
for (var k = 0, kk = map[q].h; k < kk; k++) {
|
2014-03-23 03:15:51 +09:00
|
|
|
imgData.set(data.subarray(dataOffset, dataOffset + rowSize), offset);
|
2014-02-24 11:42:54 +09:00
|
|
|
dataOffset += rowSize;
|
|
|
|
offset += imgRowSize;
|
|
|
|
}
|
2014-03-23 03:15:51 +09:00
|
|
|
imgData.set(data.subarray(dataOffset - rowSize, dataOffset), offset);
|
2014-02-24 11:42:54 +09:00
|
|
|
while (offset >= 0) {
|
|
|
|
data[offset - 4] = data[offset];
|
|
|
|
data[offset - 3] = data[offset + 1];
|
|
|
|
data[offset - 2] = data[offset + 2];
|
|
|
|
data[offset - 1] = data[offset + 3];
|
|
|
|
data[offset + rowSize] = data[offset + rowSize - 4];
|
|
|
|
data[offset + rowSize + 1] = data[offset + rowSize - 3];
|
|
|
|
data[offset + rowSize + 2] = data[offset + rowSize - 2];
|
|
|
|
data[offset + rowSize + 3] = data[offset + rowSize - 1];
|
|
|
|
offset -= imgRowSize;
|
|
|
|
}
|
|
|
|
}
|
2014-06-24 15:34:54 +09:00
|
|
|
|
|
|
|
// Replace queue items.
|
|
|
|
fnArray.splice(iFirstSave, count * 4, OPS.paintInlineImageXObjectGroup);
|
|
|
|
argsArray.splice(iFirstSave, count * 4,
|
2014-03-23 03:15:51 +09:00
|
|
|
[{ width: imgWidth, height: imgHeight, kind: ImageKind.RGBA_32BPP,
|
|
|
|
data: imgData }, map]);
|
2014-06-24 15:34:54 +09:00
|
|
|
|
|
|
|
return iFirstSave + 1;
|
2014-02-24 11:42:54 +09:00
|
|
|
});
|
|
|
|
|
2014-06-24 15:34:54 +09:00
|
|
|
// This replaces (save, transform, paintImageMaskXObject, restore)+
|
|
|
|
// sequences with one |paintImageMaskXObjectGroup| or one
|
|
|
|
// |paintImageMaskXObjectRepeat| operation.
|
2014-02-24 11:42:54 +09:00
|
|
|
addState(InitialState,
|
|
|
|
[OPS.save, OPS.transform, OPS.paintImageMaskXObject, OPS.restore],
|
|
|
|
function foundImageMaskGroup(context) {
|
|
|
|
var MIN_IMAGES_IN_MASKS_BLOCK = 10;
|
|
|
|
var MAX_IMAGES_IN_MASKS_BLOCK = 100;
|
2014-02-25 00:59:02 +09:00
|
|
|
var MAX_SAME_IMAGES_IN_MASKS_BLOCK = 1000;
|
2014-02-24 11:42:54 +09:00
|
|
|
|
|
|
|
var fnArray = context.fnArray, argsArray = context.argsArray;
|
2014-06-24 15:34:54 +09:00
|
|
|
var curr = context.iCurr;
|
|
|
|
var iFirstSave = curr - 3;
|
|
|
|
var iFirstTransform = curr - 2;
|
|
|
|
var iFirstPIMXO = curr - 1;
|
2014-02-24 11:42:54 +09:00
|
|
|
|
2014-06-24 15:34:54 +09:00
|
|
|
// Look for the quartets.
|
|
|
|
var i = iFirstSave + 4;
|
|
|
|
var ii = fnArray.length;
|
|
|
|
while (i + 3 < ii) {
|
|
|
|
if (fnArray[i] !== OPS.save ||
|
|
|
|
fnArray[i + 1] !== OPS.transform ||
|
|
|
|
fnArray[i + 2] !== OPS.paintImageMaskXObject ||
|
|
|
|
fnArray[i + 3] !== OPS.restore) {
|
|
|
|
break; // ops don't match
|
|
|
|
}
|
|
|
|
i += 4;
|
|
|
|
}
|
|
|
|
|
|
|
|
// At this point, i is the index of the first op past the last valid
|
|
|
|
// quartet.
|
|
|
|
var count = (i - iFirstSave) / 4;
|
|
|
|
count = handlePaintSolidColorImageMask(iFirstSave, count, fnArray,
|
|
|
|
argsArray);
|
2014-02-24 11:42:54 +09:00
|
|
|
if (count < MIN_IMAGES_IN_MASKS_BLOCK) {
|
2014-06-24 15:34:54 +09:00
|
|
|
return i;
|
2014-02-24 11:42:54 +09:00
|
|
|
}
|
2014-02-25 00:59:02 +09:00
|
|
|
|
2014-06-24 15:34:54 +09:00
|
|
|
var q;
|
2014-02-25 00:59:02 +09:00
|
|
|
var isSameImage = false;
|
2014-06-24 15:34:54 +09:00
|
|
|
var iTransform, transformArgs;
|
|
|
|
var firstPIMXOArg0 = argsArray[iFirstPIMXO][0];
|
|
|
|
if (argsArray[iFirstTransform][1] === 0 &&
|
|
|
|
argsArray[iFirstTransform][2] === 0) {
|
2014-02-25 00:59:02 +09:00
|
|
|
isSameImage = true;
|
2014-06-24 15:34:54 +09:00
|
|
|
var firstTransformArg0 = argsArray[iFirstTransform][0];
|
|
|
|
var firstTransformArg3 = argsArray[iFirstTransform][3];
|
|
|
|
iTransform = iFirstTransform + 4;
|
|
|
|
var iPIMXO = iFirstPIMXO + 4;
|
|
|
|
for (q = 1; q < count; q++, iTransform += 4, iPIMXO += 4) {
|
|
|
|
transformArgs = argsArray[iTransform];
|
|
|
|
if (argsArray[iPIMXO][0] !== firstPIMXOArg0 ||
|
|
|
|
transformArgs[0] !== firstTransformArg0 ||
|
|
|
|
transformArgs[1] !== 0 ||
|
|
|
|
transformArgs[2] !== 0 ||
|
|
|
|
transformArgs[3] !== firstTransformArg3) {
|
2014-02-25 00:59:02 +09:00
|
|
|
if (q < MIN_IMAGES_IN_MASKS_BLOCK) {
|
|
|
|
isSameImage = false;
|
|
|
|
} else {
|
|
|
|
count = q;
|
|
|
|
}
|
|
|
|
break; // different image or transform
|
|
|
|
}
|
|
|
|
}
|
2014-02-24 11:42:54 +09:00
|
|
|
}
|
|
|
|
|
2014-02-25 00:59:02 +09:00
|
|
|
if (isSameImage) {
|
|
|
|
count = Math.min(count, MAX_SAME_IMAGES_IN_MASKS_BLOCK);
|
|
|
|
var positions = new Float32Array(count * 2);
|
2014-06-24 15:34:54 +09:00
|
|
|
iTransform = iFirstTransform;
|
|
|
|
for (q = 0; q < count; q++, iTransform += 4) {
|
|
|
|
transformArgs = argsArray[iTransform];
|
2014-02-25 00:59:02 +09:00
|
|
|
positions[(q << 1)] = transformArgs[4];
|
|
|
|
positions[(q << 1) + 1] = transformArgs[5];
|
|
|
|
}
|
|
|
|
|
2014-06-24 15:34:54 +09:00
|
|
|
// Replace queue items.
|
|
|
|
fnArray.splice(iFirstSave, count * 4, OPS.paintImageMaskXObjectRepeat);
|
|
|
|
argsArray.splice(iFirstSave, count * 4,
|
|
|
|
[firstPIMXOArg0, firstTransformArg0, firstTransformArg3, positions]);
|
2014-02-25 00:59:02 +09:00
|
|
|
} else {
|
|
|
|
count = Math.min(count, MAX_IMAGES_IN_MASKS_BLOCK);
|
|
|
|
var images = [];
|
2014-04-08 06:42:54 +09:00
|
|
|
for (q = 0; q < count; q++) {
|
2014-06-24 15:34:54 +09:00
|
|
|
transformArgs = argsArray[iFirstTransform + (q << 2)];
|
|
|
|
var maskParams = argsArray[iFirstPIMXO + (q << 2)][0];
|
2014-03-23 03:15:51 +09:00
|
|
|
images.push({ data: maskParams.data, width: maskParams.width,
|
|
|
|
height: maskParams.height,
|
|
|
|
transform: transformArgs });
|
2014-02-25 00:59:02 +09:00
|
|
|
}
|
|
|
|
|
2014-06-24 15:34:54 +09:00
|
|
|
// Replace queue items.
|
|
|
|
fnArray.splice(iFirstSave, count * 4, OPS.paintImageMaskXObjectGroup);
|
|
|
|
argsArray.splice(iFirstSave, count * 4, [images]);
|
2014-02-25 00:59:02 +09:00
|
|
|
}
|
2014-06-24 15:34:54 +09:00
|
|
|
|
|
|
|
return iFirstSave + 1;
|
2014-02-24 11:42:54 +09:00
|
|
|
});
|
|
|
|
|
2014-06-24 15:34:54 +09:00
|
|
|
// This replaces (save, transform, paintImageXObject, restore)+ sequences
|
|
|
|
// with one paintImageXObjectRepeat operation, if the |transform| and
|
|
|
|
// |paintImageXObjectRepeat| ops are appropriate.
|
2014-02-24 23:00:08 +09:00
|
|
|
addState(InitialState,
|
|
|
|
[OPS.save, OPS.transform, OPS.paintImageXObject, OPS.restore],
|
|
|
|
function (context) {
|
|
|
|
var MIN_IMAGES_IN_BLOCK = 3;
|
|
|
|
var MAX_IMAGES_IN_BLOCK = 1000;
|
|
|
|
|
|
|
|
var fnArray = context.fnArray, argsArray = context.argsArray;
|
2014-06-24 15:34:54 +09:00
|
|
|
var curr = context.iCurr;
|
|
|
|
var iFirstSave = curr - 3;
|
|
|
|
var iFirstTransform = curr - 2;
|
|
|
|
var iFirstPIXO = curr - 1;
|
|
|
|
var iFirstRestore = curr;
|
|
|
|
|
|
|
|
if (argsArray[iFirstTransform][1] !== 0 ||
|
|
|
|
argsArray[iFirstTransform][2] !== 0) {
|
|
|
|
return iFirstRestore + 1; // transform has the wrong form
|
|
|
|
}
|
|
|
|
|
|
|
|
// Look for the quartets.
|
|
|
|
var firstPIXOArg0 = argsArray[iFirstPIXO][0];
|
|
|
|
var firstTransformArg0 = argsArray[iFirstTransform][0];
|
|
|
|
var firstTransformArg3 = argsArray[iFirstTransform][3];
|
|
|
|
var i = iFirstSave + 4;
|
2014-03-15 22:04:13 +09:00
|
|
|
var ii = fnArray.length;
|
2014-06-24 15:34:54 +09:00
|
|
|
while (i + 3 < ii) {
|
|
|
|
if (fnArray[i] !== OPS.save ||
|
|
|
|
fnArray[i + 1] !== OPS.transform ||
|
|
|
|
fnArray[i + 2] !== OPS.paintImageXObject ||
|
|
|
|
fnArray[i + 3] !== OPS.restore) {
|
|
|
|
break; // ops don't match
|
2014-02-24 23:00:08 +09:00
|
|
|
}
|
2014-06-24 15:34:54 +09:00
|
|
|
if (argsArray[i + 1][0] !== firstTransformArg0 ||
|
|
|
|
argsArray[i + 1][1] !== 0 ||
|
|
|
|
argsArray[i + 1][2] !== 0 ||
|
|
|
|
argsArray[i + 1][3] !== firstTransformArg3) {
|
|
|
|
break; // transforms don't match
|
2014-02-24 23:00:08 +09:00
|
|
|
}
|
2014-06-24 15:34:54 +09:00
|
|
|
if (argsArray[i + 2][0] !== firstPIXOArg0) {
|
|
|
|
break; // images don't match
|
2014-02-24 23:00:08 +09:00
|
|
|
}
|
2014-06-24 15:34:54 +09:00
|
|
|
i += 4;
|
2014-02-24 23:00:08 +09:00
|
|
|
}
|
2014-06-24 15:34:54 +09:00
|
|
|
|
|
|
|
// At this point, i is the index of the first op past the last valid
|
|
|
|
// quartet.
|
|
|
|
var count = Math.min((i - iFirstSave) / 4, MAX_IMAGES_IN_BLOCK);
|
2014-02-24 23:00:08 +09:00
|
|
|
if (count < MIN_IMAGES_IN_BLOCK) {
|
2014-06-24 15:34:54 +09:00
|
|
|
return i;
|
2014-02-24 23:00:08 +09:00
|
|
|
}
|
|
|
|
|
2014-06-24 15:34:54 +09:00
|
|
|
// Extract the (x,y) positions from all of the matching transforms.
|
2014-02-24 23:00:08 +09:00
|
|
|
var positions = new Float32Array(count * 2);
|
2014-06-24 15:34:54 +09:00
|
|
|
var iTransform = iFirstTransform;
|
|
|
|
for (var q = 0; q < count; q++, iTransform += 4) {
|
|
|
|
var transformArgs = argsArray[iTransform];
|
2014-02-24 23:00:08 +09:00
|
|
|
positions[(q << 1)] = transformArgs[4];
|
|
|
|
positions[(q << 1) + 1] = transformArgs[5];
|
|
|
|
}
|
|
|
|
|
2014-06-24 15:34:54 +09:00
|
|
|
// Replace queue items.
|
|
|
|
var args = [firstPIXOArg0, firstTransformArg0, firstTransformArg3,
|
|
|
|
positions];
|
|
|
|
fnArray.splice(iFirstSave, count * 4, OPS.paintImageXObjectRepeat);
|
|
|
|
argsArray.splice(iFirstSave, count * 4, args);
|
|
|
|
|
|
|
|
return iFirstSave + 1;
|
2014-02-24 23:00:08 +09:00
|
|
|
});
|
|
|
|
|
2014-06-24 15:34:54 +09:00
|
|
|
// This replaces (beginText, setFont, setTextMatrix, showText, endText)+
|
|
|
|
// sequences with (beginText, setFont, (setTextMatrix, showText)+, endText)+
|
|
|
|
// sequences, if the font for each one is the same.
|
2014-02-24 12:51:14 +09:00
|
|
|
addState(InitialState,
|
|
|
|
[OPS.beginText, OPS.setFont, OPS.setTextMatrix, OPS.showText, OPS.endText],
|
|
|
|
function (context) {
|
|
|
|
var MIN_CHARS_IN_BLOCK = 3;
|
|
|
|
var MAX_CHARS_IN_BLOCK = 1000;
|
|
|
|
|
|
|
|
var fnArray = context.fnArray, argsArray = context.argsArray;
|
2014-06-24 15:34:54 +09:00
|
|
|
var curr = context.iCurr;
|
|
|
|
var iFirstBeginText = curr - 4;
|
|
|
|
var iFirstSetFont = curr - 3;
|
|
|
|
var iFirstSetTextMatrix = curr - 2;
|
|
|
|
var iFirstShowText = curr - 1;
|
|
|
|
var iFirstEndText = curr;
|
|
|
|
|
|
|
|
// Look for the quintets.
|
|
|
|
var firstSetFontArg0 = argsArray[iFirstSetFont][0];
|
|
|
|
var firstSetFontArg1 = argsArray[iFirstSetFont][1];
|
|
|
|
var i = iFirstBeginText + 5;
|
2014-03-15 22:04:13 +09:00
|
|
|
var ii = fnArray.length;
|
2014-06-24 15:34:54 +09:00
|
|
|
while (i + 4 < ii) {
|
|
|
|
if (fnArray[i] !== OPS.beginText ||
|
|
|
|
fnArray[i + 1] !== OPS.setFont ||
|
|
|
|
fnArray[i + 2] !== OPS.setTextMatrix ||
|
|
|
|
fnArray[i + 3] !== OPS.showText ||
|
|
|
|
fnArray[i + 4] !== OPS.endText) {
|
|
|
|
break; // ops don't match
|
|
|
|
}
|
|
|
|
if (argsArray[i + 1][0] !== firstSetFontArg0 ||
|
|
|
|
argsArray[i + 1][1] !== firstSetFontArg1) {
|
|
|
|
break; // fonts don't match
|
2014-02-24 12:51:14 +09:00
|
|
|
}
|
2014-06-24 15:34:54 +09:00
|
|
|
i += 5;
|
2014-02-24 12:51:14 +09:00
|
|
|
}
|
2014-06-24 15:34:54 +09:00
|
|
|
|
|
|
|
// At this point, i is the index of the first op past the last valid
|
|
|
|
// quintet.
|
|
|
|
var count = Math.min(((i - iFirstBeginText) / 5), MAX_CHARS_IN_BLOCK);
|
2014-02-24 12:51:14 +09:00
|
|
|
if (count < MIN_CHARS_IN_BLOCK) {
|
2014-06-24 15:34:54 +09:00
|
|
|
return i;
|
|
|
|
}
|
|
|
|
|
|
|
|
// If the preceding quintet is (<something>, setFont, setTextMatrix,
|
|
|
|
// showText, endText), include that as well. (E.g. <something> might be
|
|
|
|
// |dependency|.)
|
|
|
|
var iFirst = iFirstBeginText;
|
|
|
|
if (iFirstBeginText >= 4 &&
|
|
|
|
fnArray[iFirstBeginText - 4] === fnArray[iFirstSetFont] &&
|
|
|
|
fnArray[iFirstBeginText - 3] === fnArray[iFirstSetTextMatrix] &&
|
|
|
|
fnArray[iFirstBeginText - 2] === fnArray[iFirstShowText] &&
|
|
|
|
fnArray[iFirstBeginText - 1] === fnArray[iFirstEndText] &&
|
|
|
|
argsArray[iFirstBeginText - 4][0] === firstSetFontArg0 &&
|
|
|
|
argsArray[iFirstBeginText - 4][1] === firstSetFontArg1) {
|
2014-02-24 12:51:14 +09:00
|
|
|
count++;
|
2014-06-24 15:34:54 +09:00
|
|
|
iFirst -= 5;
|
2014-02-24 12:51:14 +09:00
|
|
|
}
|
2014-06-24 15:34:54 +09:00
|
|
|
|
|
|
|
// Remove (endText, beginText, setFont) trios.
|
|
|
|
var iEndText = iFirst + 4;
|
2014-02-24 12:51:14 +09:00
|
|
|
for (var q = 1; q < count; q++) {
|
2014-06-24 15:34:54 +09:00
|
|
|
fnArray.splice(iEndText, 3);
|
|
|
|
argsArray.splice(iEndText, 3);
|
|
|
|
iEndText += 2;
|
|
|
|
}
|
|
|
|
|
|
|
|
return iEndText + 1;
|
2014-02-24 12:51:14 +09:00
|
|
|
});
|
|
|
|
|
2014-03-23 03:15:51 +09:00
|
|
|
function QueueOptimizer() {}
|
|
|
|
|
2014-02-24 11:42:54 +09:00
|
|
|
QueueOptimizer.prototype = {
|
|
|
|
optimize: function QueueOptimizer_optimize(queue) {
|
|
|
|
var fnArray = queue.fnArray, argsArray = queue.argsArray;
|
|
|
|
var context = {
|
2014-06-24 15:34:54 +09:00
|
|
|
iCurr: 0,
|
2014-02-24 11:42:54 +09:00
|
|
|
fnArray: fnArray,
|
|
|
|
argsArray: argsArray
|
|
|
|
};
|
|
|
|
var state;
|
2014-06-24 15:34:54 +09:00
|
|
|
var i = 0, ii = fnArray.length;
|
|
|
|
while (i < ii) {
|
2014-02-24 11:42:54 +09:00
|
|
|
state = (state || InitialState)[fnArray[i]];
|
|
|
|
if (typeof state === 'function') { // we found some handler
|
2014-06-24 15:34:54 +09:00
|
|
|
context.iCurr = i;
|
|
|
|
// state() returns the index of the first non-matching op (if we
|
|
|
|
// didn't match) or the first op past the modified ops (if we did
|
|
|
|
// match and replace).
|
|
|
|
i = state(context);
|
|
|
|
state = undefined; // reset the state machine
|
2014-03-15 22:04:13 +09:00
|
|
|
ii = context.fnArray.length;
|
2014-06-24 15:34:54 +09:00
|
|
|
} else {
|
|
|
|
i++;
|
2014-02-24 11:42:54 +09:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
};
|
|
|
|
return QueueOptimizer;
|
|
|
|
})();
|