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.
|
|
|
|
*/
|
2013-02-03 07:49:19 +09:00
|
|
|
/* globals assert, assertWellFormed, ColorSpace, Dict, Encodings, error,
|
|
|
|
ErrorFont, Font, FONT_IDENTITY_MATRIX, fontCharsToUnicode, FontFlags,
|
2013-08-13 02:48:06 +09:00
|
|
|
info, isArray, isCmd, isDict, isEOF, isName, isNum,
|
2013-02-03 07:49:19 +09:00
|
|
|
isStream, isString, JpegStream, Lexer, Metrics, Name, Parser,
|
|
|
|
Pattern, PDFImage, PDFJS, serifFonts, stdFontMap, symbolsFonts,
|
2013-08-13 02:48:06 +09:00
|
|
|
TilingPattern, TODO, warn, Util, Promise,
|
2013-08-20 08:33:20 +09:00
|
|
|
RefSetCache, isRef, TextRenderingMode */
|
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-04-23 06:20:49 +09:00
|
|
|
uniquePrefix, idCounters) {
|
2011-10-25 08:55:23 +09:00
|
|
|
this.state = new EvalState();
|
|
|
|
this.stateStack = [];
|
|
|
|
|
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-06-26 02:33:53 +09:00
|
|
|
this.fontCache = new RefSetCache();
|
2011-10-25 08:55:23 +09:00
|
|
|
}
|
|
|
|
|
2013-01-11 11:00:44 +09:00
|
|
|
// Specifies properties for each command
|
|
|
|
//
|
|
|
|
// If variableArgs === true: [0, `numArgs`] expected
|
|
|
|
// If variableArgs === false: exactly `numArgs` expected
|
2011-10-25 08:55:23 +09:00
|
|
|
var OP_MAP = {
|
2013-01-11 11:00:44 +09:00
|
|
|
// Graphic state
|
|
|
|
w: { fnName: 'setLineWidth', numArgs: 1, variableArgs: false },
|
|
|
|
J: { fnName: 'setLineCap', numArgs: 1, variableArgs: false },
|
|
|
|
j: { fnName: 'setLineJoin', numArgs: 1, variableArgs: false },
|
|
|
|
M: { fnName: 'setMiterLimit', numArgs: 1, variableArgs: false },
|
|
|
|
d: { fnName: 'setDash', numArgs: 2, variableArgs: false },
|
|
|
|
ri: { fnName: 'setRenderingIntent', numArgs: 1, variableArgs: false },
|
|
|
|
i: { fnName: 'setFlatness', numArgs: 1, variableArgs: false },
|
|
|
|
gs: { fnName: 'setGState', numArgs: 1, variableArgs: false },
|
|
|
|
q: { fnName: 'save', numArgs: 0, variableArgs: false },
|
|
|
|
Q: { fnName: 'restore', numArgs: 0, variableArgs: false },
|
|
|
|
cm: { fnName: 'transform', numArgs: 6, variableArgs: false },
|
2011-10-25 08:55:23 +09:00
|
|
|
|
|
|
|
// Path
|
2013-01-11 11:00:44 +09:00
|
|
|
m: { fnName: 'moveTo', numArgs: 2, variableArgs: false },
|
|
|
|
l: { fnName: 'lineTo', numArgs: 2, variableArgs: false },
|
|
|
|
c: { fnName: 'curveTo', numArgs: 6, variableArgs: false },
|
|
|
|
v: { fnName: 'curveTo2', numArgs: 4, variableArgs: false },
|
|
|
|
y: { fnName: 'curveTo3', numArgs: 4, variableArgs: false },
|
|
|
|
h: { fnName: 'closePath', numArgs: 0, variableArgs: false },
|
|
|
|
re: { fnName: 'rectangle', numArgs: 4, variableArgs: false },
|
|
|
|
S: { fnName: 'stroke', numArgs: 0, variableArgs: false },
|
|
|
|
s: { fnName: 'closeStroke', numArgs: 0, variableArgs: false },
|
|
|
|
f: { fnName: 'fill', numArgs: 0, variableArgs: false },
|
|
|
|
F: { fnName: 'fill', numArgs: 0, variableArgs: false },
|
|
|
|
'f*': { fnName: 'eoFill', numArgs: 0, variableArgs: false },
|
|
|
|
B: { fnName: 'fillStroke', numArgs: 0, variableArgs: false },
|
|
|
|
'B*': { fnName: 'eoFillStroke', numArgs: 0, variableArgs: false },
|
|
|
|
b: { fnName: 'closeFillStroke', numArgs: 0, variableArgs: false },
|
|
|
|
'b*': { fnName: 'closeEOFillStroke', numArgs: 0, variableArgs: false },
|
|
|
|
n: { fnName: 'endPath', numArgs: 0, variableArgs: false },
|
2011-10-25 08:55:23 +09:00
|
|
|
|
|
|
|
// Clipping
|
2013-01-11 11:00:44 +09:00
|
|
|
W: { fnName: 'clip', numArgs: 0, variableArgs: false },
|
|
|
|
'W*': { fnName: 'eoClip', numArgs: 0, variableArgs: false },
|
2011-10-25 08:55:23 +09:00
|
|
|
|
|
|
|
// Text
|
2013-01-11 11:00:44 +09:00
|
|
|
BT: { fnName: 'beginText', numArgs: 0, variableArgs: false },
|
|
|
|
ET: { fnName: 'endText', numArgs: 0, variableArgs: false },
|
|
|
|
Tc: { fnName: 'setCharSpacing', numArgs: 1, variableArgs: false },
|
|
|
|
Tw: { fnName: 'setWordSpacing', numArgs: 1, variableArgs: false },
|
|
|
|
Tz: { fnName: 'setHScale', numArgs: 1, variableArgs: false },
|
|
|
|
TL: { fnName: 'setLeading', numArgs: 1, variableArgs: false },
|
|
|
|
Tf: { fnName: 'setFont', numArgs: 2, variableArgs: false },
|
|
|
|
Tr: { fnName: 'setTextRenderingMode', numArgs: 1, variableArgs: false },
|
|
|
|
Ts: { fnName: 'setTextRise', numArgs: 1, variableArgs: false },
|
|
|
|
Td: { fnName: 'moveText', numArgs: 2, variableArgs: false },
|
|
|
|
TD: { fnName: 'setLeadingMoveText', numArgs: 2, variableArgs: false },
|
|
|
|
Tm: { fnName: 'setTextMatrix', numArgs: 6, variableArgs: false },
|
|
|
|
'T*': { fnName: 'nextLine', numArgs: 0, variableArgs: false },
|
|
|
|
Tj: { fnName: 'showText', numArgs: 1, variableArgs: false },
|
|
|
|
TJ: { fnName: 'showSpacedText', numArgs: 1, variableArgs: false },
|
|
|
|
'\'': { fnName: 'nextLineShowText', numArgs: 1, variableArgs: false },
|
|
|
|
'"': { fnName: 'nextLineSetSpacingShowText', numArgs: 3,
|
|
|
|
variableArgs: false },
|
2011-10-25 08:55:23 +09:00
|
|
|
|
|
|
|
// Type3 fonts
|
2013-01-11 11:00:44 +09:00
|
|
|
d0: { fnName: 'setCharWidth', numArgs: 2, variableArgs: false },
|
|
|
|
d1: { fnName: 'setCharWidthAndBounds', numArgs: 6, variableArgs: false },
|
2011-10-25 08:55:23 +09:00
|
|
|
|
|
|
|
// Color
|
2013-01-11 11:00:44 +09:00
|
|
|
CS: { fnName: 'setStrokeColorSpace', numArgs: 1, variableArgs: false },
|
|
|
|
cs: { fnName: 'setFillColorSpace', numArgs: 1, variableArgs: false },
|
|
|
|
SC: { fnName: 'setStrokeColor', numArgs: 4, variableArgs: true },
|
|
|
|
SCN: { fnName: 'setStrokeColorN', numArgs: 33, variableArgs: true },
|
|
|
|
sc: { fnName: 'setFillColor', numArgs: 4, variableArgs: true },
|
|
|
|
scn: { fnName: 'setFillColorN', numArgs: 33, variableArgs: true },
|
|
|
|
G: { fnName: 'setStrokeGray', numArgs: 1, variableArgs: false },
|
|
|
|
g: { fnName: 'setFillGray', numArgs: 1, variableArgs: false },
|
|
|
|
RG: { fnName: 'setStrokeRGBColor', numArgs: 3, variableArgs: false },
|
|
|
|
rg: { fnName: 'setFillRGBColor', numArgs: 3, variableArgs: false },
|
|
|
|
K: { fnName: 'setStrokeCMYKColor', numArgs: 4, variableArgs: false },
|
|
|
|
k: { fnName: 'setFillCMYKColor', numArgs: 4, variableArgs: false },
|
2011-10-25 08:55:23 +09:00
|
|
|
|
|
|
|
// Shading
|
2013-01-11 11:00:44 +09:00
|
|
|
sh: { fnName: 'shadingFill', numArgs: 1, variableArgs: false },
|
2011-10-25 08:55:23 +09:00
|
|
|
|
|
|
|
// Images
|
2013-01-11 11:00:44 +09:00
|
|
|
BI: { fnName: 'beginInlineImage', numArgs: 0, variableArgs: false },
|
|
|
|
ID: { fnName: 'beginImageData', numArgs: 0, variableArgs: false },
|
2013-06-22 08:47:10 +09:00
|
|
|
EI: { fnName: 'endInlineImage', numArgs: 1, variableArgs: false },
|
2011-10-25 08:55:23 +09:00
|
|
|
|
|
|
|
// XObjects
|
2013-01-11 11:00:44 +09:00
|
|
|
Do: { fnName: 'paintXObject', numArgs: 1, variableArgs: false },
|
|
|
|
MP: { fnName: 'markPoint', numArgs: 1, variableArgs: false },
|
|
|
|
DP: { fnName: 'markPointProps', numArgs: 2, variableArgs: false },
|
|
|
|
BMC: { fnName: 'beginMarkedContent', numArgs: 1, variableArgs: false },
|
|
|
|
BDC: { fnName: 'beginMarkedContentProps', numArgs: 2, variableArgs: false },
|
|
|
|
EMC: { fnName: 'endMarkedContent', numArgs: 0, variableArgs: false },
|
2011-10-25 08:55:23 +09:00
|
|
|
|
|
|
|
// Compatibility
|
2013-01-11 11:00:44 +09:00
|
|
|
BX: { fnName: 'beginCompat', numArgs: 0, variableArgs: false },
|
|
|
|
EX: { fnName: 'endCompat', numArgs: 0, variableArgs: false },
|
2012-05-10 02:29:50 +09:00
|
|
|
|
2012-05-21 03:44:03 +09:00
|
|
|
// (reserved partial commands for the lexer)
|
|
|
|
BM: null,
|
2012-05-21 04:05:23 +09:00
|
|
|
BD: null,
|
|
|
|
'true': null,
|
|
|
|
fa: null,
|
|
|
|
fal: null,
|
|
|
|
fals: null,
|
|
|
|
'false': null,
|
|
|
|
nu: null,
|
|
|
|
nul: null,
|
|
|
|
'null': null
|
2011-10-25 08:55:23 +09:00
|
|
|
};
|
|
|
|
|
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;
|
|
|
|
}
|
|
|
|
|
|
|
|
var nodes = [resources];
|
|
|
|
while (nodes.length) {
|
|
|
|
var node = nodes.shift();
|
|
|
|
// First check the current resources for blend modes.
|
|
|
|
var graphicStates = node.get('ExtGState');
|
|
|
|
if (isDict(graphicStates)) {
|
|
|
|
graphicStates = graphicStates.getAll();
|
|
|
|
for (var key in graphicStates) {
|
|
|
|
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();
|
|
|
|
for (var key in xObjects) {
|
|
|
|
var xObject = xObjects[key];
|
|
|
|
if (!isStream(xObject)) {
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
var xResources = xObject.dict.get('Resources');
|
|
|
|
if (isDict(xResources)) {
|
|
|
|
nodes.push(xResources);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
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,
|
|
|
|
operatorList) {
|
2013-04-09 07:14:56 +09:00
|
|
|
var self = this;
|
2012-09-14 00:09:46 +09:00
|
|
|
|
2013-04-09 07:14:56 +09:00
|
|
|
var matrix = xobj.dict.get('Matrix');
|
|
|
|
var bbox = xobj.dict.get('BBox');
|
|
|
|
var group = xobj.dict.get('Group');
|
|
|
|
if (group) {
|
|
|
|
var groupOptions = {
|
|
|
|
matrix: matrix,
|
|
|
|
bbox: bbox,
|
|
|
|
smask: !!smask,
|
|
|
|
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');
|
|
|
|
if (isName(groupSubtype) && groupSubtype.name === 'Transparency') {
|
|
|
|
groupOptions.isolated = group.get('I') || false;
|
|
|
|
groupOptions.knockout = group.get('K') || false;
|
|
|
|
// There is also a group colorspace, but since we put everything in
|
|
|
|
// RGB I'm not sure we need it.
|
|
|
|
}
|
2013-08-01 03:17:36 +09:00
|
|
|
operatorList.addOp('beginGroup', [groupOptions]);
|
2012-10-16 01:48:45 +09:00
|
|
|
}
|
2012-09-14 00:09:46 +09:00
|
|
|
|
2013-08-01 03:17:36 +09:00
|
|
|
operatorList.addOp('paintFormXObjectBegin', [matrix, bbox]);
|
2012-09-14 00:09:46 +09:00
|
|
|
|
2013-08-01 03:17:36 +09:00
|
|
|
this.getOperatorList(xobj, xobj.dict.get('Resources') || resources,
|
|
|
|
operatorList);
|
|
|
|
operatorList.addOp('paintFormXObjectEnd', []);
|
2013-04-09 07:14:56 +09:00
|
|
|
|
2013-08-01 03:17:36 +09:00
|
|
|
if (group) {
|
|
|
|
operatorList.addOp('endGroup', [groupOptions]);
|
|
|
|
}
|
2013-04-09 07:14:56 +09:00
|
|
|
},
|
2011-10-25 08:55:23 +09:00
|
|
|
|
2013-04-09 07:14:56 +09:00
|
|
|
buildPaintImageXObject: function PartialEvaluator_buildPaintImageXObject(
|
2013-08-01 03:17:36 +09:00
|
|
|
resources, image, inline, operatorList) {
|
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
|
|
|
|
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
|
|
|
}
|
|
|
|
|
2013-04-09 07:14:56 +09:00
|
|
|
var imageMask = dict.get('ImageMask', 'IM') || false;
|
|
|
|
if (imageMask) {
|
|
|
|
// This depends on a tmpCanvas beeing filled with the
|
|
|
|
// current fillStyle, such that processing the pixel
|
|
|
|
// data can't be done here. Instead of creating a
|
|
|
|
// complete PDFImage, only read the information needed
|
|
|
|
// for later.
|
|
|
|
|
|
|
|
var width = dict.get('Width', 'W');
|
|
|
|
var height = dict.get('Height', 'H');
|
|
|
|
var bitStrideLength = (width + 7) >> 3;
|
|
|
|
var imgArray = image.getBytes(bitStrideLength * height);
|
|
|
|
var decode = dict.get('Decode', 'D');
|
|
|
|
var inverseDecode = !!decode && decode[0] > 0;
|
|
|
|
|
2013-08-01 03:17:36 +09:00
|
|
|
operatorList.addOp('paintImageMaskXObject',
|
|
|
|
[PDFImage.createMask(imgArray, width, height,
|
|
|
|
inverseDecode)]
|
|
|
|
);
|
|
|
|
return;
|
2013-04-09 07:14:56 +09:00
|
|
|
}
|
|
|
|
|
|
|
|
var softMask = dict.get('SMask', 'SM') || false;
|
|
|
|
var mask = dict.get('Mask') || false;
|
|
|
|
|
|
|
|
var SMALL_IMAGE_DIMENSIONS = 200;
|
|
|
|
// Inlining small images into the queue as RGB data
|
|
|
|
if (inline && !softMask && !mask &&
|
|
|
|
!(image instanceof JpegStream) &&
|
|
|
|
(w + h) < SMALL_IMAGE_DIMENSIONS) {
|
|
|
|
var imageObj = new PDFImage(this.xref, resources, image,
|
|
|
|
inline, null, null);
|
|
|
|
var imgData = imageObj.getImageData();
|
2013-08-01 03:17:36 +09:00
|
|
|
operatorList.addOp('paintInlineImageXObject', [imgData]);
|
|
|
|
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.
|
|
|
|
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);
|
|
|
|
var 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-08-01 03:17:36 +09:00
|
|
|
operatorList.addOp('paintJpegXObject', args);
|
2013-04-09 07:14:56 +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
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
PDFImage.buildImage(function(imageObj) {
|
|
|
|
var imgData = imageObj.getImageData();
|
|
|
|
self.handler.send('obj', [objId, self.pageIndex, 'Image', imgData]);
|
|
|
|
}, self.handler, self.xref, resources, image, inline);
|
|
|
|
|
2013-08-01 03:17:36 +09:00
|
|
|
operatorList.addOp('paintImageXObject', args);
|
2013-04-09 07:14:56 +09:00
|
|
|
},
|
|
|
|
|
|
|
|
handleTilingType: function PartialEvaluator_handleTilingType(
|
2013-08-01 03:17:36 +09:00
|
|
|
fn, args, resources, pattern, patternDict,
|
|
|
|
operatorList) {
|
2013-04-09 07:14:56 +09:00
|
|
|
// Create an IR of the pattern code.
|
2013-08-01 03:17:36 +09:00
|
|
|
var tilingOpList = this.getOperatorList(pattern,
|
|
|
|
patternDict.get('Resources') || resources);
|
|
|
|
// 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, TilingPattern.getIR({
|
|
|
|
fnArray: tilingOpList.fnArray,
|
|
|
|
argsArray: tilingOpList.argsArray
|
|
|
|
}, patternDict, args));
|
2013-04-09 07:14:56 +09:00
|
|
|
},
|
2012-09-14 00:09:46 +09:00
|
|
|
|
2013-04-09 07:14:56 +09:00
|
|
|
handleSetFont: function PartialEvaluator_handleSetFont(
|
2013-08-01 03:17:36 +09:00
|
|
|
resources, fontArgs, fontRef, operatorList) {
|
2013-04-09 07:14:56 +09:00
|
|
|
|
|
|
|
// TODO(mack): Not needed?
|
|
|
|
var fontName;
|
|
|
|
if (fontArgs) {
|
|
|
|
fontArgs = fontArgs.slice();
|
|
|
|
fontName = fontArgs[0].name;
|
|
|
|
}
|
|
|
|
var self = this;
|
2013-08-01 03:17:36 +09:00
|
|
|
var font = this.loadFont(fontName, fontRef, this.xref, resources,
|
|
|
|
operatorList);
|
2013-08-01 06:01:55 +09:00
|
|
|
this.state.font = font;
|
2013-08-01 03:17:36 +09:00
|
|
|
var loadedName = font.loadedName;
|
|
|
|
if (!font.sent) {
|
|
|
|
var fontData = font.translated.exportData();
|
|
|
|
|
|
|
|
self.handler.send('commonobj', [
|
|
|
|
loadedName,
|
|
|
|
'Font',
|
|
|
|
fontData
|
|
|
|
]);
|
|
|
|
font.sent = true;
|
2013-04-09 07:14:56 +09:00
|
|
|
}
|
2013-08-01 03:17:36 +09:00
|
|
|
|
|
|
|
return loadedName;
|
2013-04-09 07:14:56 +09:00
|
|
|
},
|
2013-03-13 09:20:38 +09:00
|
|
|
|
2013-08-20 08:33:20 +09:00
|
|
|
handleText: function PartialEvaluator_handleText(chars) {
|
|
|
|
var font = this.state.font.translated;
|
|
|
|
var glyphs = font.charsToGlyphs(chars);
|
|
|
|
var isAddToPathSet = !!(this.state.textRenderingMode &
|
|
|
|
TextRenderingMode.ADD_TO_PATH_FLAG);
|
2013-08-23 04:55:43 +09:00
|
|
|
if (font.data && (isAddToPathSet || PDFJS.disableFontFace)) {
|
2013-08-20 08:33:20 +09:00
|
|
|
for (var i = 0; i < glyphs.length; i++) {
|
|
|
|
if (glyphs[i] === null) {
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
var fontChar = glyphs[i].fontChar;
|
|
|
|
if (!font.renderer.hasBuiltPath(fontChar)) {
|
|
|
|
var path = font.renderer.getPathJs(fontChar);
|
|
|
|
this.handler.send('commonobj', [
|
|
|
|
font.loadedName + '_path_' + fontChar,
|
|
|
|
'FontPath',
|
|
|
|
path
|
|
|
|
]);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
return glyphs;
|
|
|
|
},
|
|
|
|
|
2013-08-01 03:17:36 +09:00
|
|
|
setGState: function PartialEvaluator_setGState(resources, gState,
|
|
|
|
operatorList) {
|
2013-03-13 09:20:38 +09:00
|
|
|
|
2013-04-09 07:14:56 +09:00
|
|
|
var self = this;
|
|
|
|
// TODO(mack): This should be rewritten so that this function returns
|
|
|
|
// what should be added to the queue during each iteration
|
|
|
|
function setGStateForKey(gStateObj, key, value) {
|
|
|
|
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':
|
2013-08-01 03:17:36 +09:00
|
|
|
var loadedName = self.handleSetFont(resources, null, value[0],
|
|
|
|
operatorList);
|
|
|
|
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':
|
|
|
|
// We support the default so don't trigger the TODO.
|
|
|
|
if (!isName(value) || value.name != 'None')
|
|
|
|
TODO('graphic state operator ' + key);
|
|
|
|
break;
|
|
|
|
// Only generate info log messages for the following since
|
|
|
|
// they are unlikey to have a big impact on the rendering.
|
|
|
|
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;
|
|
|
|
}
|
|
|
|
}
|
2013-03-13 09:20:38 +09:00
|
|
|
|
2013-04-09 07:14:56 +09:00
|
|
|
// This array holds the converted/processed state data.
|
|
|
|
var gStateObj = [];
|
|
|
|
var gStateMap = gState.map;
|
|
|
|
for (var key in gStateMap) {
|
|
|
|
var value = gStateMap[key];
|
|
|
|
setGStateForKey(gStateObj, key, value);
|
|
|
|
}
|
2013-03-13 09:20:38 +09:00
|
|
|
|
2013-08-01 03:17:36 +09:00
|
|
|
operatorList.addOp('setGState', [gStateObj]);
|
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,
|
2013-08-01 03:17:36 +09:00
|
|
|
resources,
|
|
|
|
parentOperatorList) {
|
|
|
|
|
|
|
|
function errorFont() {
|
|
|
|
return {
|
|
|
|
translated: new ErrorFont('Font ' + fontName + ' is not available'),
|
|
|
|
loadedName: 'g_font_error'
|
|
|
|
};
|
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
|
|
|
}
|
|
|
|
}
|
|
|
|
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
|
|
|
}
|
2013-08-01 03:17:36 +09:00
|
|
|
this.fontCache.put(fontRef, font);
|
2013-05-04 03:13:45 +09:00
|
|
|
|
|
|
|
// keep track of each font we translated so the caller can
|
|
|
|
// load them asynchronously before calling display on a page
|
2013-06-26 02:33:53 +09:00
|
|
|
font.loadedName = 'g_font_' + fontRef.num + '_' + fontRef.gen;
|
2012-02-21 22:28:42 +09:00
|
|
|
|
2013-05-04 03:13:45 +09:00
|
|
|
if (!font.translated) {
|
2013-04-09 07:14:56 +09:00
|
|
|
var translated;
|
|
|
|
try {
|
|
|
|
translated = this.translateFont(font, xref);
|
|
|
|
} catch (e) {
|
|
|
|
translated = new ErrorFont(e instanceof Error ? e.message : e);
|
|
|
|
}
|
|
|
|
font.translated = translated;
|
2013-05-04 03:13:45 +09:00
|
|
|
}
|
2013-04-09 07:14:56 +09:00
|
|
|
|
2013-05-04 03:13:45 +09:00
|
|
|
if (font.translated.loadCharProcs) {
|
|
|
|
var charProcs = font.get('CharProcs').getAll();
|
|
|
|
var fontResources = font.get('Resources') || resources;
|
|
|
|
var charProcKeys = Object.keys(charProcs);
|
2013-08-01 03:17:36 +09:00
|
|
|
var charProcOperatorList = {};
|
2013-05-04 03:13:45 +09:00
|
|
|
for (var i = 0, n = charProcKeys.length; i < n; ++i) {
|
|
|
|
var key = charProcKeys[i];
|
|
|
|
var glyphStream = charProcs[key];
|
2013-08-01 03:17:36 +09:00
|
|
|
var operatorList = this.getOperatorList(glyphStream, fontResources);
|
|
|
|
charProcOperatorList[key] = operatorList.getIR();
|
|
|
|
if (!parentOperatorList) {
|
|
|
|
continue;
|
2013-04-09 07:14:56 +09:00
|
|
|
}
|
2013-08-01 03:17:36 +09:00
|
|
|
// Add the dependencies to the parent operator list so they are
|
|
|
|
// resolved before sub operator list is executed synchronously.
|
|
|
|
parentOperatorList.addDependencies(charProcOperatorList.dependencies);
|
|
|
|
}
|
|
|
|
font.translated.charProcOperatorList = charProcOperatorList;
|
|
|
|
font.loaded = true;
|
2013-04-09 07:14:56 +09:00
|
|
|
} else {
|
2013-05-04 03:13:45 +09:00
|
|
|
font.loaded = true;
|
2011-10-25 08:55:23 +09:00
|
|
|
}
|
2013-08-01 03:17:36 +09:00
|
|
|
return font;
|
2013-04-09 07:14:56 +09:00
|
|
|
},
|
|
|
|
|
|
|
|
getOperatorList: function PartialEvaluator_getOperatorList(stream,
|
2013-08-01 03:17:36 +09:00
|
|
|
resources,
|
|
|
|
operatorList) {
|
2013-04-09 07:14:56 +09:00
|
|
|
|
|
|
|
var self = this;
|
|
|
|
var xref = this.xref;
|
|
|
|
var handler = this.handler;
|
2011-10-25 08:55:23 +09:00
|
|
|
|
2013-08-01 03:17:36 +09:00
|
|
|
operatorList = operatorList || new OperatorList();
|
2011-10-25 08:55:23 +09:00
|
|
|
|
2012-04-05 03:43:04 +09:00
|
|
|
resources = resources || new Dict();
|
|
|
|
var xobjs = resources.get('XObject') || new Dict();
|
|
|
|
var patterns = resources.get('Pattern') || new Dict();
|
2013-01-11 11:00:44 +09:00
|
|
|
// TODO(mduan): pass array of knownCommands rather than OP_MAP
|
|
|
|
// dictionary
|
2012-05-21 03:44:03 +09:00
|
|
|
var parser = new Parser(new Lexer(stream, OP_MAP), false, xref);
|
2011-10-25 08:55:23 +09:00
|
|
|
|
2013-04-19 02:41:33 +09:00
|
|
|
var promise = new Promise();
|
2013-06-05 09:57:52 +09:00
|
|
|
var args = [];
|
2013-08-01 03:17:36 +09:00
|
|
|
nextOp:
|
2013-06-05 09:57:52 +09:00
|
|
|
while (true) {
|
2012-04-05 01:50:20 +09:00
|
|
|
|
2013-06-05 09:57:52 +09:00
|
|
|
var obj = parser.getObj();
|
2013-01-11 11:00:44 +09:00
|
|
|
|
2013-06-05 09:57:52 +09:00
|
|
|
if (isEOF(obj)) {
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
|
|
|
|
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.fnName;
|
|
|
|
|
|
|
|
// Validate the number of arguments for the command
|
|
|
|
if (opSpec.variableArgs) {
|
|
|
|
if (args.length > opSpec.numArgs) {
|
|
|
|
info('Command ' + fn + ': expected [0,' + opSpec.numArgs +
|
|
|
|
'] args, but received ' + args.length + ' args');
|
2013-04-09 07:14:56 +09:00
|
|
|
}
|
2013-06-05 09:57:52 +09:00
|
|
|
} else {
|
|
|
|
if (args.length < opSpec.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 ' +
|
|
|
|
opSpec.numArgs + ' args, but received ' + args.length +
|
|
|
|
' args; skipping');
|
|
|
|
args = [];
|
|
|
|
continue;
|
|
|
|
} else if (args.length > opSpec.numArgs) {
|
|
|
|
info('Command ' + fn + ': expected ' + opSpec.numArgs +
|
|
|
|
' args, but received ' + args.length + ' args');
|
|
|
|
}
|
|
|
|
}
|
2013-01-11 11:00:44 +09:00
|
|
|
|
2013-06-05 09:57:52 +09:00
|
|
|
// TODO figure out how to type-check vararg functions
|
2013-01-11 11:00:44 +09:00
|
|
|
|
2013-06-05 09:57:52 +09:00
|
|
|
if ((cmd == 'SCN' || cmd == 'scn') &&
|
|
|
|
!args[args.length - 1].code) {
|
|
|
|
// 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))) {
|
2013-04-09 07:14:56 +09:00
|
|
|
|
2013-06-05 09:57:52 +09:00
|
|
|
var dict = isStream(pattern) ? pattern.dict : pattern;
|
|
|
|
var typeNum = dict.get('PatternType');
|
2013-01-11 11:00:44 +09:00
|
|
|
|
2013-06-05 09:57:52 +09:00
|
|
|
if (typeNum == TILING_PATTERN) {
|
2013-08-01 03:17:36 +09:00
|
|
|
self.handleTilingType(fn, args, resources, pattern, dict,
|
|
|
|
operatorList);
|
|
|
|
args = [];
|
|
|
|
continue;
|
2013-06-05 09:57:52 +09:00
|
|
|
} else if (typeNum == SHADING_PATTERN) {
|
|
|
|
var shading = dict.get('Shading');
|
|
|
|
var matrix = dict.get('Matrix');
|
|
|
|
var pattern = Pattern.parseShading(shading, matrix, xref,
|
|
|
|
resources);
|
|
|
|
args = pattern.getIR();
|
2013-04-09 07:14:56 +09:00
|
|
|
} else {
|
2013-06-05 09:57:52 +09:00
|
|
|
error('Unkown PatternType ' + typeNum);
|
2011-10-25 08:55:23 +09:00
|
|
|
}
|
2013-06-05 09:57:52 +09:00
|
|
|
}
|
|
|
|
} else if (cmd == 'Do' && !args[0].code) {
|
|
|
|
// eagerly compile XForm objects
|
|
|
|
var name = args[0].name;
|
|
|
|
var xobj = xobjs.get(name);
|
|
|
|
if (xobj) {
|
|
|
|
assertWellFormed(
|
|
|
|
isStream(xobj), 'XObject should be a stream');
|
|
|
|
|
|
|
|
var type = xobj.dict.get('Subtype');
|
|
|
|
assertWellFormed(
|
|
|
|
isName(type),
|
|
|
|
'XObject should have a Name subtype'
|
|
|
|
);
|
|
|
|
|
|
|
|
if ('Form' == type.name) {
|
2013-08-01 03:17:36 +09:00
|
|
|
self.buildFormXObject(resources, xobj, null, operatorList);
|
|
|
|
args = [];
|
|
|
|
continue;
|
2013-06-05 09:57:52 +09:00
|
|
|
} else if ('Image' == type.name) {
|
2013-08-01 03:17:36 +09:00
|
|
|
self.buildPaintImageXObject(resources, xobj, false,
|
|
|
|
operatorList);
|
|
|
|
args = [];
|
|
|
|
continue;
|
2013-06-05 09:57:52 +09:00
|
|
|
} else {
|
|
|
|
error('Unhandled XObject subtype ' + type.name);
|
2011-10-25 08:55:23 +09:00
|
|
|
}
|
|
|
|
}
|
2013-06-05 09:57:52 +09:00
|
|
|
} else if (cmd == 'Tf') { // eagerly collect all fonts
|
2013-08-01 03:17:36 +09:00
|
|
|
var loadedName = self.handleSetFont(resources, args, null,
|
|
|
|
operatorList);
|
|
|
|
operatorList.addDependency(loadedName);
|
|
|
|
fn = 'setFont';
|
|
|
|
args[0] = loadedName;
|
2013-06-05 09:57:52 +09:00
|
|
|
} else if (cmd == 'EI') {
|
2013-08-01 03:17:36 +09:00
|
|
|
self.buildPaintImageXObject(resources, args[0], true, operatorList);
|
|
|
|
args = [];
|
|
|
|
continue;
|
2013-08-01 06:01:55 +09:00
|
|
|
} else if (cmd === 'q') { // save
|
|
|
|
var old = this.state;
|
|
|
|
this.stateStack.push(this.state);
|
|
|
|
this.state = old.clone();
|
|
|
|
} else if (cmd === 'Q') { // restore
|
|
|
|
var prev = this.stateStack.pop();
|
|
|
|
if (prev) {
|
|
|
|
this.state = prev;
|
|
|
|
}
|
|
|
|
} else if (cmd === 'Tj') { // showText
|
2013-08-20 08:33:20 +09:00
|
|
|
args[0] = this.handleText(args[0]);
|
2013-08-01 06:01:55 +09:00
|
|
|
} else if (cmd === 'TJ') { // showSpacedText
|
|
|
|
var arr = args[0];
|
|
|
|
var arrLength = arr.length;
|
|
|
|
for (var i = 0; i < arrLength; ++i) {
|
|
|
|
if (isString(arr[i])) {
|
2013-08-20 08:33:20 +09:00
|
|
|
arr[i] = this.handleText(arr[i]);
|
2013-08-01 06:01:55 +09:00
|
|
|
}
|
|
|
|
}
|
|
|
|
} else if (cmd === '\'') { // nextLineShowText
|
2013-08-20 08:33:20 +09:00
|
|
|
args[0] = this.handleText(args[0]);
|
2013-08-01 06:01:55 +09:00
|
|
|
} else if (cmd === '"') { // nextLineSetSpacingShowText
|
2013-08-20 08:33:20 +09:00
|
|
|
args[2] = this.handleText(args[2]);
|
|
|
|
} else if (cmd === 'Tr') { // setTextRenderingMode
|
|
|
|
this.state.textRenderingMode = args[0];
|
2011-10-25 08:55:23 +09:00
|
|
|
}
|
|
|
|
|
2013-06-05 09:57:52 +09:00
|
|
|
switch (fn) {
|
|
|
|
// Parse the ColorSpace data to a raw format.
|
|
|
|
case 'setFillColorSpace':
|
|
|
|
case 'setStrokeColorSpace':
|
|
|
|
args = [ColorSpace.parseToIR(args[0], xref, resources)];
|
|
|
|
break;
|
|
|
|
case 'shadingFill':
|
|
|
|
var shadingRes = resources.get('Shading');
|
|
|
|
if (!shadingRes)
|
|
|
|
error('No shading resource found');
|
|
|
|
|
|
|
|
var shading = shadingRes.get(args[0].name);
|
|
|
|
if (!shading)
|
|
|
|
error('No shading object found');
|
|
|
|
|
|
|
|
var shadingFill = Pattern.parseShading(
|
|
|
|
shading, null, xref, resources);
|
|
|
|
var patternIR = shadingFill.getIR();
|
|
|
|
args = [patternIR];
|
|
|
|
fn = 'shadingFill';
|
|
|
|
break;
|
|
|
|
case 'setGState':
|
|
|
|
var dictName = args[0];
|
|
|
|
var extGState = resources.get('ExtGState');
|
|
|
|
|
|
|
|
if (!isDict(extGState) || !extGState.has(dictName.name))
|
|
|
|
break;
|
|
|
|
|
|
|
|
var gState = extGState.get(dictName.name);
|
2013-08-01 03:17:36 +09:00
|
|
|
self.setGState(resources, gState, operatorList);
|
|
|
|
args = [];
|
|
|
|
continue nextOp;
|
2013-06-05 09:57:52 +09:00
|
|
|
} // switch
|
|
|
|
|
2013-08-01 03:17:36 +09:00
|
|
|
operatorList.addOp(fn, args);
|
2013-06-05 09:57:52 +09:00
|
|
|
args = [];
|
|
|
|
parser.saveState();
|
|
|
|
} else if (obj !== null && obj !== undefined) {
|
|
|
|
args.push(obj instanceof Dict ? obj.getAll() : obj);
|
|
|
|
assertWellFormed(args.length <= 33, 'Too many arguments');
|
|
|
|
}
|
|
|
|
}
|
2011-10-25 08:55:23 +09:00
|
|
|
|
2013-08-01 03:17:36 +09:00
|
|
|
return operatorList;
|
2011-10-25 08:55:23 +09:00
|
|
|
},
|
|
|
|
|
2012-10-30 07:03:30 +09:00
|
|
|
getTextContent: function PartialEvaluator_getTextContent(
|
2013-08-01 03:17:36 +09:00
|
|
|
stream, resources, state) {
|
2013-04-09 07:14:56 +09:00
|
|
|
|
2013-08-01 03:17:36 +09:00
|
|
|
var bidiTexts;
|
2012-11-10 06:34:11 +09:00
|
|
|
var SPACE_FACTOR = 0.35;
|
|
|
|
var MULTI_SPACE_FACTOR = 1.5;
|
2012-09-16 23:45:07 +09:00
|
|
|
|
2013-08-01 03:17:36 +09:00
|
|
|
if (!state) {
|
|
|
|
bidiTexts = [];
|
|
|
|
state = {
|
|
|
|
bidiTexts: bidiTexts
|
|
|
|
};
|
|
|
|
} else {
|
|
|
|
bidiTexts = state.bidiTexts;
|
2013-04-09 07:14:56 +09:00
|
|
|
}
|
2011-12-11 08:24:54 +09:00
|
|
|
|
2013-08-01 03:17:36 +09:00
|
|
|
var self = this;
|
|
|
|
var xref = this.xref;
|
|
|
|
|
|
|
|
function handleSetFont(fontName, fontRef) {
|
|
|
|
return self.loadFont(fontName, fontRef, xref, resources, null);
|
2011-12-11 08:24:54 +09:00
|
|
|
}
|
|
|
|
|
2013-08-01 03:17:36 +09:00
|
|
|
resources = xref.fetchIfRef(resources) || new Dict();
|
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;
|
2011-12-11 08:24:54 +09:00
|
|
|
|
|
|
|
var parser = new Parser(new Lexer(stream), false);
|
2013-08-01 03:17:36 +09:00
|
|
|
var res = resources;
|
|
|
|
var args = [], obj;
|
2013-04-09 07:14:56 +09:00
|
|
|
|
2013-08-01 03:17:36 +09:00
|
|
|
var chunk = '';
|
|
|
|
var font = null;
|
|
|
|
while (!isEOF(obj = parser.getObj())) {
|
2013-06-05 09:57:52 +09:00
|
|
|
if (isCmd(obj)) {
|
|
|
|
var cmd = obj.cmd;
|
|
|
|
switch (cmd) {
|
|
|
|
// TODO: Add support for SAVE/RESTORE and XFORM here.
|
|
|
|
case 'Tf':
|
2013-08-01 03:17:36 +09:00
|
|
|
font = handleSetFont(args[0].name).translated;
|
2013-06-05 09:57:52 +09:00
|
|
|
break;
|
|
|
|
case 'TJ':
|
2013-08-01 03:17:36 +09:00
|
|
|
var items = args[0];
|
|
|
|
for (var j = 0, jj = items.length; j < jj; j++) {
|
|
|
|
if (typeof items[j] === 'string') {
|
|
|
|
chunk += fontCharsToUnicode(items[j], font);
|
|
|
|
} else if (items[j] < 0 && font.spaceWidth > 0) {
|
|
|
|
var fakeSpaces = -items[j] / font.spaceWidth;
|
|
|
|
if (fakeSpaces > MULTI_SPACE_FACTOR) {
|
|
|
|
fakeSpaces = Math.round(fakeSpaces);
|
|
|
|
while (fakeSpaces--) {
|
2013-06-05 09:57:52 +09:00
|
|
|
chunk += ' ';
|
2012-09-25 23:07:59 +09:00
|
|
|
}
|
2013-08-01 03:17:36 +09:00
|
|
|
} else if (fakeSpaces > SPACE_FACTOR) {
|
|
|
|
chunk += ' ';
|
2012-09-15 02:53:06 +09:00
|
|
|
}
|
2013-06-05 09:57:52 +09:00
|
|
|
}
|
2013-08-01 03:17:36 +09:00
|
|
|
}
|
2013-06-05 09:57:52 +09:00
|
|
|
break;
|
|
|
|
case 'Tj':
|
2013-08-01 03:17:36 +09:00
|
|
|
chunk += fontCharsToUnicode(args[0], font);
|
2013-06-05 09:57:52 +09:00
|
|
|
break;
|
|
|
|
case '\'':
|
2013-08-01 03:17:36 +09:00
|
|
|
// For search, adding a extra white space for line breaks would be
|
|
|
|
// better here, but that causes too much spaces in the
|
|
|
|
// text-selection divs.
|
|
|
|
chunk += fontCharsToUnicode(args[0], font);
|
2013-06-05 09:57:52 +09:00
|
|
|
break;
|
|
|
|
case '"':
|
|
|
|
// Note comment in "'"
|
2013-08-01 03:17:36 +09:00
|
|
|
chunk += fontCharsToUnicode(args[2], font);
|
2013-06-05 09:57:52 +09:00
|
|
|
break;
|
|
|
|
case 'Do':
|
2013-08-01 03:17:36 +09:00
|
|
|
// Set the chunk such that the following if won't add something
|
|
|
|
// to the state.
|
|
|
|
chunk = '';
|
|
|
|
|
2013-06-05 09:57:52 +09:00
|
|
|
if (args[0].code) {
|
|
|
|
break;
|
|
|
|
}
|
2012-09-12 07:10:34 +09:00
|
|
|
|
2013-06-05 09:57:52 +09:00
|
|
|
if (!xobjs) {
|
|
|
|
xobjs = resources.get('XObject') || new Dict();
|
|
|
|
}
|
2012-09-12 07:10:34 +09:00
|
|
|
|
2013-06-05 09:57:52 +09:00
|
|
|
var name = args[0].name;
|
|
|
|
var xobj = xobjs.get(name);
|
|
|
|
if (!xobj)
|
|
|
|
break;
|
2013-08-01 03:17:36 +09:00
|
|
|
assertWellFormed(isStream(xobj), 'XObject should be a stream');
|
2013-06-05 09:57:52 +09:00
|
|
|
|
|
|
|
var type = xobj.dict.get('Subtype');
|
|
|
|
assertWellFormed(
|
|
|
|
isName(type),
|
|
|
|
'XObject should have a Name subtype'
|
|
|
|
);
|
|
|
|
|
|
|
|
if ('Form' !== type.name)
|
|
|
|
break;
|
|
|
|
|
2013-08-01 03:17:36 +09:00
|
|
|
state = this.getTextContent(
|
2013-06-05 09:57:52 +09:00
|
|
|
xobj,
|
2013-08-01 03:17:36 +09:00
|
|
|
xobj.dict.get('Resources') || resources,
|
|
|
|
state
|
2013-06-05 09:57:52 +09:00
|
|
|
);
|
|
|
|
break;
|
|
|
|
case 'gs':
|
|
|
|
var dictName = args[0];
|
|
|
|
var extGState = resources.get('ExtGState');
|
2012-09-15 11:52:37 +09:00
|
|
|
|
2013-06-05 09:57:52 +09:00
|
|
|
if (!isDict(extGState) || !extGState.has(dictName.name))
|
|
|
|
break;
|
|
|
|
|
|
|
|
var gsState = extGState.get(dictName.name);
|
|
|
|
|
|
|
|
for (var i = 0; i < gsState.length; i++) {
|
|
|
|
if (gsState[i] === 'Font') {
|
2013-08-01 03:17:36 +09:00
|
|
|
font = handleSetFont(args[0].name).translated;
|
2013-06-05 09:57:52 +09:00
|
|
|
}
|
2012-09-15 11:52:37 +09:00
|
|
|
}
|
2013-06-05 09:57:52 +09:00
|
|
|
break;
|
|
|
|
} // switch
|
2011-12-11 08:24:54 +09:00
|
|
|
|
2013-08-01 03:17:36 +09:00
|
|
|
if (chunk !== '') {
|
|
|
|
var bidiText = PDFJS.bidi(chunk, -1, font.vertical);
|
|
|
|
bidiTexts.push(bidiText);
|
|
|
|
|
|
|
|
chunk = '';
|
|
|
|
}
|
|
|
|
|
2013-06-05 09:57:52 +09:00
|
|
|
args = [];
|
|
|
|
} else if (obj !== null && obj !== undefined) {
|
|
|
|
assertWellFormed(args.length <= 33, 'Too many arguments');
|
|
|
|
args.push(obj);
|
2011-12-11 08:24:54 +09:00
|
|
|
}
|
2013-06-05 09:57:52 +09:00
|
|
|
} // while
|
|
|
|
|
2013-08-01 03:17:36 +09:00
|
|
|
return state;
|
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
|
|
|
|
var toUnicode = dict.get('ToUnicode') ||
|
|
|
|
baseDict.get('ToUnicode');
|
|
|
|
if (toUnicode)
|
2012-04-24 07:44:51 +09:00
|
|
|
properties.toUnicode = this.readToUnicode(toUnicode, xref, properties);
|
2011-10-25 08:55:23 +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');
|
2011-10-29 10:38:31 +09:00
|
|
|
if (isStream(cidToGidMap))
|
|
|
|
properties.cidToGidMap = this.readCidToGidMap(cidToGidMap);
|
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
|
|
|
|
// but should be prioritized in the following order:
|
|
|
|
// 1. Encoding dictionary
|
|
|
|
// 2. Encoding within font file (Type1 or Type1C)
|
|
|
|
// 3. Default (depends on font type)
|
|
|
|
// Differences applied to the above.
|
|
|
|
// Note: we don't fill in the encoding from the font file(2) here but use
|
|
|
|
// the flag overridableEncoding to signal that the font can override the
|
|
|
|
// encoding if it has one built in.
|
|
|
|
var overridableEncoding = true;
|
|
|
|
var hasEncoding = false;
|
2012-01-28 09:53:05 +09:00
|
|
|
var flags = properties.flags;
|
2011-10-29 10:38:31 +09:00
|
|
|
var differences = [];
|
2013-04-11 01:51:06 +09:00
|
|
|
var baseEncoding = properties.type === 'TrueType' ?
|
|
|
|
Encodings.WinAnsiEncoding :
|
|
|
|
Encodings.StandardEncoding;
|
2012-08-24 12:36:39 +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 (!!(flags & FontFlags.Symbolic)) {
|
|
|
|
baseEncoding = !properties.file ? Encodings.symbolsEncoding :
|
|
|
|
Encodings.MacRomanEncoding;
|
|
|
|
}
|
2013-04-11 01:51:06 +09:00
|
|
|
if (dict.has('Encoding')) {
|
2012-04-05 03:43:04 +09:00
|
|
|
var encoding = dict.get('Encoding');
|
2011-10-25 08:55:23 +09:00
|
|
|
if (isDict(encoding)) {
|
|
|
|
var baseName = encoding.get('BaseEncoding');
|
2013-04-11 01:51:06 +09:00
|
|
|
if (baseName) {
|
|
|
|
overridableEncoding = false;
|
|
|
|
hasEncoding = true;
|
2011-10-29 10:38:31 +09:00
|
|
|
baseEncoding = Encodings[baseName.name];
|
2013-04-11 01:51:06 +09:00
|
|
|
}
|
2011-10-25 08:55:23 +09:00
|
|
|
|
|
|
|
// Load the differences between the base and original
|
|
|
|
if (encoding.has('Differences')) {
|
2013-04-11 01:51:06 +09:00
|
|
|
hasEncoding = true;
|
2011-10-25 08:55:23 +09:00
|
|
|
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];
|
|
|
|
if (isNum(data))
|
|
|
|
index = data;
|
|
|
|
else
|
|
|
|
differences[index++] = data.name;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
} else if (isName(encoding)) {
|
2013-04-11 01:51:06 +09:00
|
|
|
overridableEncoding = false;
|
|
|
|
hasEncoding = true;
|
2011-10-29 10:38:31 +09:00
|
|
|
baseEncoding = Encodings[encoding.name];
|
2011-10-25 08:55:23 +09:00
|
|
|
} else {
|
|
|
|
error('Encoding is not a Name nor a Dict');
|
|
|
|
}
|
|
|
|
}
|
2011-11-25 00:38:09 +09:00
|
|
|
|
2011-10-29 10:38:31 +09:00
|
|
|
properties.differences = differences;
|
|
|
|
properties.baseEncoding = baseEncoding;
|
|
|
|
properties.hasEncoding = hasEncoding;
|
2013-04-11 01:51:06 +09:00
|
|
|
properties.overridableEncoding = overridableEncoding;
|
2011-10-29 10:38:31 +09:00
|
|
|
},
|
2011-10-25 08:55:23 +09:00
|
|
|
|
2012-04-24 07:44:51 +09:00
|
|
|
readToUnicode: function PartialEvaluator_readToUnicode(toUnicode, xref,
|
|
|
|
properties) {
|
2012-04-05 03:43:04 +09:00
|
|
|
var cmapObj = toUnicode;
|
2011-10-29 10:38:31 +09:00
|
|
|
var charToUnicode = [];
|
|
|
|
if (isName(cmapObj)) {
|
2011-11-16 09:23:45 +09:00
|
|
|
var isIdentityMap = cmapObj.name.substr(0, 9) == 'Identity-';
|
|
|
|
if (!isIdentityMap)
|
|
|
|
error('ToUnicode file cmap translation not implemented');
|
2011-10-29 10:38:31 +09:00
|
|
|
} else if (isStream(cmapObj)) {
|
|
|
|
var tokens = [];
|
|
|
|
var token = '';
|
|
|
|
var beginArrayToken = {};
|
|
|
|
|
|
|
|
var cmap = cmapObj.getBytes(cmapObj.length);
|
2011-11-13 02:09:19 +09:00
|
|
|
for (var i = 0, ii = cmap.length; i < ii; i++) {
|
2012-03-31 06:17:04 +09:00
|
|
|
var octet = cmap[i];
|
|
|
|
if (octet == 0x20 || octet == 0x0D || octet == 0x0A ||
|
|
|
|
octet == 0x3C || octet == 0x5B || octet == 0x5D) {
|
2011-10-29 10:38:31 +09:00
|
|
|
switch (token) {
|
|
|
|
case 'usecmap':
|
|
|
|
error('usecmap is not implemented');
|
|
|
|
break;
|
2011-10-25 08:55:23 +09:00
|
|
|
|
2011-10-29 10:38:31 +09:00
|
|
|
case 'beginbfchar':
|
|
|
|
case 'beginbfrange':
|
|
|
|
case 'begincidchar':
|
|
|
|
case 'begincidrange':
|
|
|
|
token = '';
|
|
|
|
tokens = [];
|
|
|
|
break;
|
2011-10-25 08:55:23 +09:00
|
|
|
|
2011-10-29 10:38:31 +09:00
|
|
|
case 'endcidrange':
|
|
|
|
case 'endbfrange':
|
2011-11-13 02:09:19 +09:00
|
|
|
for (var j = 0, jj = tokens.length; j < jj; j += 3) {
|
2011-10-29 10:38:31 +09:00
|
|
|
var startRange = tokens[j];
|
|
|
|
var endRange = tokens[j + 1];
|
|
|
|
var code = tokens[j + 2];
|
2011-11-28 11:43:23 +09:00
|
|
|
if (code == 0xFFFF) {
|
|
|
|
// CMap is broken, assuming code == startRange
|
|
|
|
code = startRange;
|
|
|
|
}
|
|
|
|
if (isArray(code)) {
|
|
|
|
var codeindex = 0;
|
|
|
|
while (startRange <= endRange) {
|
|
|
|
charToUnicode[startRange] = code[codeindex++];
|
|
|
|
++startRange;
|
|
|
|
}
|
|
|
|
} else {
|
|
|
|
while (startRange <= endRange) {
|
|
|
|
charToUnicode[startRange] = code++;
|
|
|
|
++startRange;
|
|
|
|
}
|
2011-10-25 08:55:23 +09:00
|
|
|
}
|
2011-10-29 10:38:31 +09:00
|
|
|
}
|
|
|
|
break;
|
2011-10-25 08:55:23 +09:00
|
|
|
|
2011-10-29 10:38:31 +09:00
|
|
|
case 'endcidchar':
|
|
|
|
case 'endbfchar':
|
2011-11-13 02:09:19 +09:00
|
|
|
for (var j = 0, jj = tokens.length; j < jj; j += 2) {
|
2011-10-29 10:38:31 +09:00
|
|
|
var index = tokens[j];
|
|
|
|
var code = tokens[j + 1];
|
|
|
|
charToUnicode[index] = code;
|
|
|
|
}
|
|
|
|
break;
|
2011-10-25 08:55:23 +09:00
|
|
|
|
2011-10-29 10:38:31 +09:00
|
|
|
case '':
|
|
|
|
break;
|
|
|
|
|
|
|
|
default:
|
|
|
|
if (token[0] >= '0' && token[0] <= '9')
|
|
|
|
token = parseInt(token, 10); // a number
|
|
|
|
tokens.push(token);
|
2011-10-25 08:55:23 +09:00
|
|
|
token = '';
|
|
|
|
}
|
2012-03-31 06:17:04 +09:00
|
|
|
switch (octet) {
|
2011-10-29 10:38:31 +09:00
|
|
|
case 0x5B:
|
|
|
|
// begin list parsing
|
|
|
|
tokens.push(beginArrayToken);
|
|
|
|
break;
|
|
|
|
case 0x5D:
|
|
|
|
// collect array items
|
|
|
|
var items = [], item;
|
|
|
|
while (tokens.length &&
|
|
|
|
(item = tokens.pop()) != beginArrayToken)
|
|
|
|
items.unshift(item);
|
|
|
|
tokens.push(items);
|
|
|
|
break;
|
|
|
|
}
|
2012-03-31 06:17:04 +09:00
|
|
|
} else if (octet == 0x3E) {
|
2011-10-29 10:38:31 +09:00
|
|
|
if (token.length) {
|
2012-05-22 12:15:09 +09:00
|
|
|
// Heuristic: guessing chars size by checking numbers sizes
|
|
|
|
// in the CMap entries.
|
|
|
|
if (token.length == 2 && properties.composite)
|
2012-04-24 07:44:51 +09:00
|
|
|
properties.wideChars = false;
|
|
|
|
|
2011-11-25 00:38:09 +09:00
|
|
|
if (token.length <= 4) {
|
|
|
|
// parsing hex number
|
|
|
|
tokens.push(parseInt(token, 16));
|
|
|
|
token = '';
|
|
|
|
} else {
|
|
|
|
// parsing hex UTF-16BE numbers
|
|
|
|
var str = [];
|
2012-02-10 11:40:44 +09:00
|
|
|
for (var k = 0, kk = token.length; k < kk; k += 4) {
|
|
|
|
var b = parseInt(token.substr(k, 4), 16);
|
|
|
|
if (b <= 0x10) {
|
|
|
|
k += 4;
|
|
|
|
b = (b << 16) | parseInt(token.substr(k, 4), 16);
|
|
|
|
b -= 0x10000;
|
|
|
|
str.push(0xD800 | (b >> 10));
|
|
|
|
str.push(0xDC00 | (b & 0x3FF));
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
str.push(b);
|
|
|
|
}
|
2011-11-25 00:38:09 +09:00
|
|
|
tokens.push(String.fromCharCode.apply(String, str));
|
|
|
|
token = '';
|
|
|
|
}
|
2011-10-29 10:38:31 +09:00
|
|
|
}
|
|
|
|
} else {
|
2012-03-31 06:17:04 +09:00
|
|
|
token += String.fromCharCode(octet);
|
2011-10-25 08:55:23 +09:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
2011-10-29 10:38:31 +09:00
|
|
|
return charToUnicode;
|
2011-10-25 08:55:23 +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];
|
2013-02-01 08:31:31 +09:00
|
|
|
if (glyphID === 0)
|
2011-10-29 10:38:31 +09:00
|
|
|
continue;
|
2011-10-25 08:55:23 +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
|
|
|
|
2012-04-05 05:43:26 +09:00
|
|
|
extractWidths: function PartialEvaluator_extractWidths(dict,
|
2011-10-29 10:38:31 +09:00
|
|
|
xref,
|
|
|
|
descriptor,
|
|
|
|
properties) {
|
|
|
|
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;
|
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
|
|
|
|
2012-04-05 03:43:04 +09:00
|
|
|
var widths = dict.get('W');
|
2011-10-29 10:38:31 +09:00
|
|
|
if (widths) {
|
2011-11-13 02:09:19 +09:00
|
|
|
for (var i = 0, ii = widths.length; i < ii; i++) {
|
2013-01-30 07:19:08 +09:00
|
|
|
var start = widths[i++];
|
2012-08-01 07:58:28 +09:00
|
|
|
var code = xref.fetchIfRef(widths[i]);
|
2011-10-29 10:38:31 +09:00
|
|
|
if (isArray(code)) {
|
2011-11-13 02:09:19 +09:00
|
|
|
for (var j = 0, jj = code.length; j < jj; j++)
|
2011-10-29 10:38:31 +09:00
|
|
|
glyphsWidths[start++] = code[j];
|
2013-01-30 07:19:08 +09:00
|
|
|
} else {
|
2011-10-29 10:38:31 +09:00
|
|
|
var width = widths[++i];
|
|
|
|
for (var j = start; j <= code; j++)
|
|
|
|
glyphsWidths[j] = width;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
2013-02-08 21:29:22 +09:00
|
|
|
|
|
|
|
if (properties.vertical) {
|
|
|
|
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) {
|
|
|
|
for (var i = 0, ii = vmetrics.length; i < ii; i++) {
|
|
|
|
var start = vmetrics[i++];
|
|
|
|
var code = xref.fetchIfRef(vmetrics[i]);
|
|
|
|
if (isArray(code)) {
|
|
|
|
for (var j = 0, jj = code.length; j < jj; j++)
|
|
|
|
glyphsVMetrics[start++] = [code[j++], code[j++], code[j]];
|
|
|
|
} else {
|
|
|
|
var vmetric = [vmetrics[++i], vmetrics[++i], vmetrics[++i]];
|
|
|
|
for (var j = start; j <= code; j++)
|
|
|
|
glyphsVMetrics[j] = vmetric;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
2011-10-29 10:38:31 +09:00
|
|
|
} else {
|
|
|
|
var firstChar = properties.firstChar;
|
2012-04-05 03:43:04 +09:00
|
|
|
var widths = dict.get('Widths');
|
2011-10-29 10:38:31 +09:00
|
|
|
if (widths) {
|
2011-11-13 02:09:19 +09:00
|
|
|
var j = firstChar;
|
|
|
|
for (var i = 0, ii = widths.length; i < ii; i++)
|
|
|
|
glyphsWidths[j++] = widths[i];
|
2011-10-29 10:38:31 +09:00
|
|
|
defaultWidth = parseFloat(descriptor.get('MissingWidth')) || 0;
|
|
|
|
} else {
|
|
|
|
// Trying get the BaseFont metrics (see comment above).
|
|
|
|
var baseFontName = dict.get('BaseFont');
|
|
|
|
if (isName(baseFontName)) {
|
|
|
|
var metrics = this.getBaseFontMetrics(baseFontName.name);
|
|
|
|
|
|
|
|
glyphsWidths = metrics.widths;
|
|
|
|
defaultWidth = metrics.defaultWidth;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2012-09-17 04:38:30 +09:00
|
|
|
// Heuristic: detection of monospace font by checking all non-zero widths
|
|
|
|
var isMonospace = true, firstWidth = defaultWidth;
|
|
|
|
for (var glyph in glyphsWidths) {
|
|
|
|
var glyphWidth = glyphsWidths[glyph];
|
|
|
|
if (!glyphWidth)
|
|
|
|
continue;
|
|
|
|
if (!firstWidth) {
|
|
|
|
firstWidth = glyphWidth;
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
if (firstWidth != glyphWidth) {
|
|
|
|
isMonospace = false;
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
if (isMonospace)
|
|
|
|
properties.flags |= FontFlags.FixedPitch;
|
|
|
|
|
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) ||
|
|
|
|
(fontNameWoStyle.search(/serif/gi) !== -1);
|
|
|
|
},
|
|
|
|
|
2012-04-05 05:43:26 +09:00
|
|
|
getBaseFontMetrics: function PartialEvaluator_getBaseFontMetrics(name) {
|
2012-09-17 04:38:30 +09:00
|
|
|
var defaultWidth = 0, widths = [], monospace = false;
|
2013-01-12 04:04:56 +09:00
|
|
|
|
|
|
|
var lookupName = stdFontMap[name] || name;
|
|
|
|
|
|
|
|
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
|
|
|
};
|
|
|
|
},
|
|
|
|
|
2012-04-05 05:43:26 +09:00
|
|
|
translateFont: function PartialEvaluator_translateFont(dict,
|
2013-04-09 07:14:56 +09:00
|
|
|
xref) {
|
2011-10-25 08:55:23 +09:00
|
|
|
var baseDict = dict;
|
|
|
|
var type = dict.get('Subtype');
|
|
|
|
assertWellFormed(isName(type), 'invalid font Subtype');
|
|
|
|
|
|
|
|
var composite = false;
|
|
|
|
if (type.name == 'Type0') {
|
|
|
|
// If font is a composite
|
|
|
|
// - get the descendant font
|
|
|
|
// - set the type according to the descendant font
|
|
|
|
// - get the FontDescriptor from the descendant font
|
|
|
|
var df = dict.get('DescendantFonts');
|
|
|
|
if (!df)
|
2012-09-14 00:09:46 +09:00
|
|
|
error('Descendant fonts are not specified');
|
2011-10-25 08:55:23 +09:00
|
|
|
|
2012-04-05 03:43:04 +09:00
|
|
|
dict = isArray(df) ? xref.fetchIfRef(df[0]) : df;
|
2011-10-25 08:55:23 +09:00
|
|
|
|
|
|
|
type = dict.get('Subtype');
|
|
|
|
assertWellFormed(isName(type), 'invalid font Subtype');
|
|
|
|
composite = true;
|
|
|
|
}
|
2011-10-29 10:38:31 +09:00
|
|
|
var maxCharIndex = composite ? 0xFFFF : 0xFF;
|
2011-10-25 08:55:23 +09:00
|
|
|
|
2012-04-05 03:43:04 +09:00
|
|
|
var descriptor = dict.get('FontDescriptor');
|
2011-10-25 08:55:23 +09:00
|
|
|
if (!descriptor) {
|
|
|
|
if (type.name == 'Type3') {
|
|
|
|
// FontDescriptor is only required for Type3 fonts when the document
|
|
|
|
// is a tagged pdf. Create a barbebones one to get by.
|
|
|
|
descriptor = new Dict();
|
|
|
|
descriptor.set('FontName', new Name(type.name));
|
|
|
|
} else {
|
|
|
|
// Before PDF 1.5 if the font was one of the base 14 fonts, having a
|
|
|
|
// FontDescriptor was not required.
|
|
|
|
// This case is here for compatibility.
|
|
|
|
var baseFontName = dict.get('BaseFont');
|
|
|
|
if (!isName(baseFontName))
|
2012-09-14 00:09:46 +09:00
|
|
|
error('Base font is not specified');
|
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];
|
2013-01-12 04:04:56 +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 :
|
|
|
|
FontFlags.Nonsymbolic);
|
2012-01-10 12:15:18 +09:00
|
|
|
|
2011-10-25 08:55:23 +09:00
|
|
|
var properties = {
|
|
|
|
type: type.name,
|
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);
|
2011-10-25 08:55:23 +09:00
|
|
|
|
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.
|
2012-04-05 03:43:04 +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');
|
2012-03-17 03:58:23 +09:00
|
|
|
// Some bad pdf's have a string as the font name.
|
2013-01-12 10:10:09 +09:00
|
|
|
if (isString(fontName)) {
|
2012-03-17 03:58:23 +09:00
|
|
|
fontName = new Name(fontName);
|
2013-01-12 10:10:09 +09:00
|
|
|
}
|
|
|
|
if (isString(baseFont)) {
|
|
|
|
baseFont = new Name(baseFont);
|
|
|
|
}
|
|
|
|
|
2013-03-03 21:30:08 +09:00
|
|
|
if (type.name !== 'Type3') {
|
|
|
|
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 + '"');
|
|
|
|
}
|
2013-01-12 10:10:09 +09:00
|
|
|
}
|
|
|
|
fontName = fontName || baseFont;
|
|
|
|
|
2011-10-25 08:55:23 +09:00
|
|
|
assertWellFormed(isName(fontName), 'invalid font name');
|
|
|
|
|
|
|
|
var fontFile = descriptor.get('FontFile', 'FontFile2', 'FontFile3');
|
|
|
|
if (fontFile) {
|
|
|
|
if (fontFile.dict) {
|
|
|
|
var subtype = fontFile.dict.get('Subtype');
|
|
|
|
if (subtype)
|
|
|
|
subtype = subtype.name;
|
|
|
|
|
|
|
|
var length1 = fontFile.dict.get('Length1');
|
|
|
|
|
|
|
|
var length2 = fontFile.dict.get('Length2');
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
var properties = {
|
|
|
|
type: type.name,
|
|
|
|
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,
|
2013-01-04 09:39:06 +09:00
|
|
|
fontMatrix: dict.get('FontMatrix') || FONT_IDENTITY_MATRIX,
|
2011-10-25 08:55:23 +09:00
|
|
|
firstChar: firstChar || 0,
|
2011-10-29 10:38:31 +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;
|
|
|
|
properties.vertical = /-V$/.test(cidEncoding.name);
|
|
|
|
}
|
|
|
|
}
|
2011-10-29 10:38:31 +09:00
|
|
|
this.extractWidths(dict, xref, descriptor, properties);
|
|
|
|
this.extractDataStructures(dict, baseDict, xref, properties);
|
2011-10-25 08:55:23 +09:00
|
|
|
|
|
|
|
if (type.name === 'Type3') {
|
|
|
|
properties.coded = true;
|
|
|
|
}
|
|
|
|
|
2012-09-14 00:09:46 +09:00
|
|
|
return new Font(fontName.name, fontFile, properties);
|
2011-10-25 08:55:23 +09:00
|
|
|
}
|
|
|
|
};
|
|
|
|
|
2013-04-09 07:14:56 +09:00
|
|
|
PartialEvaluator.optimizeQueue =
|
|
|
|
function PartialEvaluator_optimizeQueue(queue) {
|
|
|
|
|
|
|
|
var fnArray = queue.fnArray, argsArray = queue.argsArray;
|
|
|
|
// grouping paintInlineImageXObject's into paintInlineImageXObjectGroup
|
|
|
|
// searching for (save, transform, paintInlineImageXObject, restore)+
|
|
|
|
var MIN_IMAGES_IN_INLINE_IMAGES_BLOCK = 10;
|
|
|
|
var MAX_IMAGES_IN_INLINE_IMAGES_BLOCK = 200;
|
|
|
|
var MAX_WIDTH = 1000;
|
|
|
|
var IMAGE_PADDING = 1;
|
|
|
|
for (var i = 0, ii = fnArray.length; i < ii; i++) {
|
|
|
|
if (fnArray[i] === 'paintInlineImageXObject' &&
|
|
|
|
fnArray[i - 2] === 'save' && fnArray[i - 1] === 'transform' &&
|
|
|
|
fnArray[i + 1] === 'restore') {
|
|
|
|
var j = i - 2;
|
|
|
|
for (i += 2; i < ii && fnArray[i - 4] === fnArray[i]; i++) {
|
|
|
|
}
|
|
|
|
var count = Math.min((i - j) >> 2,
|
|
|
|
MAX_IMAGES_IN_INLINE_IMAGES_BLOCK);
|
|
|
|
if (count < MIN_IMAGES_IN_INLINE_IMAGES_BLOCK) {
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
// 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;
|
|
|
|
for (var q = 0; q < count; q++) {
|
|
|
|
var transform = argsArray[j + (q << 2) + 1];
|
|
|
|
var img = argsArray[j + (q << 2) + 2][0];
|
|
|
|
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;
|
|
|
|
for (var q = 0; q < count; q++) {
|
|
|
|
var data = argsArray[j + (q << 2) + 2][0].data;
|
|
|
|
// copy image by lines and extends pixels into padding
|
|
|
|
var rowSize = map[q].w << 2;
|
|
|
|
var dataOffset = 0;
|
|
|
|
var offset = (map[q].x + map[q].y * imgWidth) << 2;
|
|
|
|
imgData.set(
|
|
|
|
data.subarray(0, rowSize), offset - imgRowSize);
|
|
|
|
for (var k = 0, kk = map[q].h; k < kk; k++) {
|
|
|
|
imgData.set(
|
|
|
|
data.subarray(dataOffset, dataOffset + rowSize), offset);
|
|
|
|
dataOffset += rowSize;
|
|
|
|
offset += imgRowSize;
|
|
|
|
}
|
|
|
|
imgData.set(
|
|
|
|
data.subarray(dataOffset - rowSize, dataOffset), offset);
|
|
|
|
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;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
// replacing queue items
|
|
|
|
fnArray.splice(j, count * 4, ['paintInlineImageXObjectGroup']);
|
|
|
|
argsArray.splice(j, count * 4,
|
|
|
|
[{width: imgWidth, height: imgHeight, data: imgData}, map]);
|
|
|
|
i = j;
|
|
|
|
ii = fnArray.length;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
// grouping paintImageMaskXObject's into paintImageMaskXObjectGroup
|
|
|
|
// searching for (save, transform, paintImageMaskXObject, restore)+
|
|
|
|
var MIN_IMAGES_IN_MASKS_BLOCK = 10;
|
|
|
|
var MAX_IMAGES_IN_MASKS_BLOCK = 100;
|
|
|
|
for (var i = 0, ii = fnArray.length; i < ii; i++) {
|
|
|
|
if (fnArray[i] === 'paintImageMaskXObject' &&
|
|
|
|
fnArray[i - 2] === 'save' && fnArray[i - 1] === 'transform' &&
|
|
|
|
fnArray[i + 1] === 'restore') {
|
|
|
|
var j = i - 2;
|
|
|
|
for (i += 2; i < ii && fnArray[i - 4] === fnArray[i]; i++) {
|
|
|
|
}
|
|
|
|
var count = Math.min((i - j) >> 2,
|
|
|
|
MAX_IMAGES_IN_MASKS_BLOCK);
|
|
|
|
if (count < MIN_IMAGES_IN_MASKS_BLOCK) {
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
var images = [];
|
|
|
|
for (var q = 0; q < count; q++) {
|
|
|
|
var transform = argsArray[j + (q << 2) + 1];
|
2013-06-25 00:21:12 +09:00
|
|
|
var maskParams = argsArray[j + (q << 2) + 2][0];
|
2013-05-31 09:42:26 +09:00
|
|
|
images.push({data: maskParams.data, width: maskParams.width,
|
|
|
|
height: maskParams.height, transform: transform});
|
2013-04-09 07:14:56 +09:00
|
|
|
}
|
|
|
|
// replacing queue items
|
|
|
|
fnArray.splice(j, count * 4, ['paintImageMaskXObjectGroup']);
|
|
|
|
argsArray.splice(j, count * 4, [images]);
|
|
|
|
i = j;
|
|
|
|
ii = fnArray.length;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
};
|
|
|
|
|
|
|
|
|
2011-12-09 07:18:43 +09:00
|
|
|
return PartialEvaluator;
|
2011-10-25 08:55:23 +09:00
|
|
|
})();
|
|
|
|
|
2013-08-01 03:17:36 +09:00
|
|
|
|
|
|
|
var OperatorList = (function OperatorListClosure() {
|
|
|
|
var CHUNK_SIZE = 100;
|
|
|
|
|
|
|
|
function OperatorList(messageHandler, pageIndex) {
|
|
|
|
this.messageHandler = messageHandler;
|
|
|
|
this.fnArray = [];
|
|
|
|
this.argsArray = [];
|
|
|
|
this.dependencies = {},
|
|
|
|
this.pageIndex = pageIndex;
|
|
|
|
}
|
|
|
|
|
|
|
|
OperatorList.prototype = {
|
|
|
|
|
|
|
|
addOp: function(fn, args) {
|
|
|
|
this.fnArray.push(fn);
|
|
|
|
this.argsArray.push(args);
|
|
|
|
if (this.messageHandler && this.fnArray.length >= CHUNK_SIZE) {
|
|
|
|
this.flush();
|
|
|
|
}
|
|
|
|
},
|
|
|
|
|
|
|
|
addDependency: function(dependency) {
|
|
|
|
if (dependency in this.dependencies) {
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
this.dependencies[dependency] = true;
|
|
|
|
this.addOp('dependency', [dependency]);
|
|
|
|
},
|
|
|
|
|
|
|
|
addDependencies: function(dependencies) {
|
|
|
|
for (var key in dependencies) {
|
|
|
|
this.addDependency(key);
|
|
|
|
}
|
|
|
|
},
|
|
|
|
|
|
|
|
addOpList: function(opList) {
|
|
|
|
Util.concatenateToArray(this.fnArray, opList.fnArray);
|
|
|
|
Util.concatenateToArray(this.argsArray, opList.argsArray);
|
|
|
|
Util.extendObj(this.dependencies, opList.dependencies);
|
|
|
|
},
|
|
|
|
|
|
|
|
getIR: function() {
|
|
|
|
return {
|
|
|
|
fnArray: this.fnArray,
|
|
|
|
argsArray: this.argsArray
|
|
|
|
};
|
|
|
|
},
|
|
|
|
|
|
|
|
flush: function(lastChunk) {
|
|
|
|
PartialEvaluator.optimizeQueue(this);
|
|
|
|
this.messageHandler.send('RenderPageChunk', {
|
|
|
|
operatorList: {
|
|
|
|
fnArray: this.fnArray,
|
|
|
|
argsArray: this.argsArray,
|
|
|
|
lastChunk: lastChunk
|
|
|
|
},
|
|
|
|
pageIndex: this.pageIndex
|
|
|
|
});
|
|
|
|
this.dependencies = [];
|
|
|
|
this.fnArray = [];
|
|
|
|
this.argsArray = [];
|
|
|
|
}
|
|
|
|
};
|
|
|
|
|
|
|
|
return OperatorList;
|
|
|
|
})();
|
|
|
|
|
2011-12-09 07:18:43 +09:00
|
|
|
var EvalState = (function EvalStateClosure() {
|
|
|
|
function EvalState() {
|
2013-08-01 06:01:55 +09:00
|
|
|
this.font = null;
|
2013-08-20 08:33:20 +09:00
|
|
|
this.textRenderingMode = TextRenderingMode.FILL;
|
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
|
|
|
|