Each well-formed SVG image has the following structure: SVG element - Definitions element - Root group - Other group 1 - ... - Other group n This patch factors out initialization code into a private method in such as way that the creation of this structure is clear from the code. The root group is the replacement for the parent group from before. We need this group as we cannot apply the viewport transform on the SVG element itself (this caused issues in Chrome). If other code appends groups to the SVG image, in reality it is appending those groups to the root group, but this detail is abstracted away by this patch.
1189 lines
37 KiB
JavaScript
1189 lines
37 KiB
JavaScript
/* 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.
|
|
*/
|
|
|
|
'use strict';
|
|
|
|
(function (root, factory) {
|
|
if (typeof define === 'function' && define.amd) {
|
|
define('pdfjs/display/svg', ['exports', 'pdfjs/shared/util'], factory);
|
|
} else if (typeof exports !== 'undefined') {
|
|
factory(exports, require('../shared/util.js'));
|
|
} else {
|
|
factory((root.pdfjsDisplaySVG = {}), root.pdfjsSharedUtil);
|
|
}
|
|
}(this, function (exports, sharedUtil) {
|
|
//#if (GENERIC || SINGLE_FILE)
|
|
var FONT_IDENTITY_MATRIX = sharedUtil.FONT_IDENTITY_MATRIX;
|
|
var IDENTITY_MATRIX = sharedUtil.IDENTITY_MATRIX;
|
|
var ImageKind = sharedUtil.ImageKind;
|
|
var OPS = sharedUtil.OPS;
|
|
var Util = sharedUtil.Util;
|
|
var isNum = sharedUtil.isNum;
|
|
var isArray = sharedUtil.isArray;
|
|
var warn = sharedUtil.warn;
|
|
var createObjectURL = sharedUtil.createObjectURL;
|
|
|
|
var SVG_DEFAULTS = {
|
|
fontStyle: 'normal',
|
|
fontWeight: 'normal',
|
|
fillColor: '#000000'
|
|
};
|
|
|
|
var convertImgDataToPng = (function convertImgDataToPngClosure() {
|
|
var PNG_HEADER =
|
|
new Uint8Array([0x89, 0x50, 0x4e, 0x47, 0x0d, 0x0a, 0x1a, 0x0a]);
|
|
|
|
var CHUNK_WRAPPER_SIZE = 12;
|
|
|
|
var crcTable = new Int32Array(256);
|
|
for (var i = 0; i < 256; i++) {
|
|
var c = i;
|
|
for (var h = 0; h < 8; h++) {
|
|
if (c & 1) {
|
|
c = 0xedB88320 ^ ((c >> 1) & 0x7fffffff);
|
|
} else {
|
|
c = (c >> 1) & 0x7fffffff;
|
|
}
|
|
}
|
|
crcTable[i] = c;
|
|
}
|
|
|
|
function crc32(data, start, end) {
|
|
var crc = -1;
|
|
for (var i = start; i < end; i++) {
|
|
var a = (crc ^ data[i]) & 0xff;
|
|
var b = crcTable[a];
|
|
crc = (crc >>> 8) ^ b;
|
|
}
|
|
return crc ^ -1;
|
|
}
|
|
|
|
function writePngChunk(type, body, data, offset) {
|
|
var p = offset;
|
|
var len = body.length;
|
|
|
|
data[p] = len >> 24 & 0xff;
|
|
data[p + 1] = len >> 16 & 0xff;
|
|
data[p + 2] = len >> 8 & 0xff;
|
|
data[p + 3] = len & 0xff;
|
|
p += 4;
|
|
|
|
data[p] = type.charCodeAt(0) & 0xff;
|
|
data[p + 1] = type.charCodeAt(1) & 0xff;
|
|
data[p + 2] = type.charCodeAt(2) & 0xff;
|
|
data[p + 3] = type.charCodeAt(3) & 0xff;
|
|
p += 4;
|
|
|
|
data.set(body, p);
|
|
p += body.length;
|
|
|
|
var crc = crc32(data, offset + 4, p);
|
|
|
|
data[p] = crc >> 24 & 0xff;
|
|
data[p + 1] = crc >> 16 & 0xff;
|
|
data[p + 2] = crc >> 8 & 0xff;
|
|
data[p + 3] = crc & 0xff;
|
|
}
|
|
|
|
function adler32(data, start, end) {
|
|
var a = 1;
|
|
var b = 0;
|
|
for (var i = start; i < end; ++i) {
|
|
a = (a + (data[i] & 0xff)) % 65521;
|
|
b = (b + a) % 65521;
|
|
}
|
|
return (b << 16) | a;
|
|
}
|
|
|
|
function encode(imgData, kind, forceDataSchema) {
|
|
var width = imgData.width;
|
|
var height = imgData.height;
|
|
var bitDepth, colorType, lineSize;
|
|
var bytes = imgData.data;
|
|
|
|
switch (kind) {
|
|
case ImageKind.GRAYSCALE_1BPP:
|
|
colorType = 0;
|
|
bitDepth = 1;
|
|
lineSize = (width + 7) >> 3;
|
|
break;
|
|
case ImageKind.RGB_24BPP:
|
|
colorType = 2;
|
|
bitDepth = 8;
|
|
lineSize = width * 3;
|
|
break;
|
|
case ImageKind.RGBA_32BPP:
|
|
colorType = 6;
|
|
bitDepth = 8;
|
|
lineSize = width * 4;
|
|
break;
|
|
default:
|
|
throw new Error('invalid format');
|
|
}
|
|
|
|
// prefix every row with predictor 0
|
|
var literals = new Uint8Array((1 + lineSize) * height);
|
|
var offsetLiterals = 0, offsetBytes = 0;
|
|
var y, i;
|
|
for (y = 0; y < height; ++y) {
|
|
literals[offsetLiterals++] = 0; // no prediction
|
|
literals.set(bytes.subarray(offsetBytes, offsetBytes + lineSize),
|
|
offsetLiterals);
|
|
offsetBytes += lineSize;
|
|
offsetLiterals += lineSize;
|
|
}
|
|
|
|
if (kind === ImageKind.GRAYSCALE_1BPP) {
|
|
// inverting for B/W
|
|
offsetLiterals = 0;
|
|
for (y = 0; y < height; y++) {
|
|
offsetLiterals++; // skipping predictor
|
|
for (i = 0; i < lineSize; i++) {
|
|
literals[offsetLiterals++] ^= 0xFF;
|
|
}
|
|
}
|
|
}
|
|
|
|
var ihdr = new Uint8Array([
|
|
width >> 24 & 0xff,
|
|
width >> 16 & 0xff,
|
|
width >> 8 & 0xff,
|
|
width & 0xff,
|
|
height >> 24 & 0xff,
|
|
height >> 16 & 0xff,
|
|
height >> 8 & 0xff,
|
|
height & 0xff,
|
|
bitDepth, // bit depth
|
|
colorType, // color type
|
|
0x00, // compression method
|
|
0x00, // filter method
|
|
0x00 // interlace method
|
|
]);
|
|
|
|
var len = literals.length;
|
|
var maxBlockLength = 0xFFFF;
|
|
|
|
var deflateBlocks = Math.ceil(len / maxBlockLength);
|
|
var idat = new Uint8Array(2 + len + deflateBlocks * 5 + 4);
|
|
var pi = 0;
|
|
idat[pi++] = 0x78; // compression method and flags
|
|
idat[pi++] = 0x9c; // flags
|
|
|
|
var pos = 0;
|
|
while (len > maxBlockLength) {
|
|
// writing non-final DEFLATE blocks type 0 and length of 65535
|
|
idat[pi++] = 0x00;
|
|
idat[pi++] = 0xff;
|
|
idat[pi++] = 0xff;
|
|
idat[pi++] = 0x00;
|
|
idat[pi++] = 0x00;
|
|
idat.set(literals.subarray(pos, pos + maxBlockLength), pi);
|
|
pi += maxBlockLength;
|
|
pos += maxBlockLength;
|
|
len -= maxBlockLength;
|
|
}
|
|
|
|
// writing non-final DEFLATE blocks type 0
|
|
idat[pi++] = 0x01;
|
|
idat[pi++] = len & 0xff;
|
|
idat[pi++] = len >> 8 & 0xff;
|
|
idat[pi++] = (~len & 0xffff) & 0xff;
|
|
idat[pi++] = (~len & 0xffff) >> 8 & 0xff;
|
|
idat.set(literals.subarray(pos), pi);
|
|
pi += literals.length - pos;
|
|
|
|
var adler = adler32(literals, 0, literals.length); // checksum
|
|
idat[pi++] = adler >> 24 & 0xff;
|
|
idat[pi++] = adler >> 16 & 0xff;
|
|
idat[pi++] = adler >> 8 & 0xff;
|
|
idat[pi++] = adler & 0xff;
|
|
|
|
// PNG will consists: header, IHDR+data, IDAT+data, and IEND.
|
|
var pngLength = PNG_HEADER.length + (CHUNK_WRAPPER_SIZE * 3) +
|
|
ihdr.length + idat.length;
|
|
var data = new Uint8Array(pngLength);
|
|
var offset = 0;
|
|
data.set(PNG_HEADER, offset);
|
|
offset += PNG_HEADER.length;
|
|
writePngChunk('IHDR', ihdr, data, offset);
|
|
offset += CHUNK_WRAPPER_SIZE + ihdr.length;
|
|
writePngChunk('IDATA', idat, data, offset);
|
|
offset += CHUNK_WRAPPER_SIZE + idat.length;
|
|
writePngChunk('IEND', new Uint8Array(0), data, offset);
|
|
|
|
return createObjectURL(data, 'image/png', forceDataSchema);
|
|
}
|
|
|
|
return function convertImgDataToPng(imgData, forceDataSchema) {
|
|
var kind = (imgData.kind === undefined ?
|
|
ImageKind.GRAYSCALE_1BPP : imgData.kind);
|
|
return encode(imgData, kind, forceDataSchema);
|
|
};
|
|
})();
|
|
|
|
var SVGExtraState = (function SVGExtraStateClosure() {
|
|
function SVGExtraState() {
|
|
this.fontSizeScale = 1;
|
|
this.fontWeight = SVG_DEFAULTS.fontWeight;
|
|
this.fontSize = 0;
|
|
|
|
this.textMatrix = IDENTITY_MATRIX;
|
|
this.fontMatrix = FONT_IDENTITY_MATRIX;
|
|
this.leading = 0;
|
|
|
|
// Current point (in user coordinates)
|
|
this.x = 0;
|
|
this.y = 0;
|
|
|
|
// Start of text line (in text coordinates)
|
|
this.lineX = 0;
|
|
this.lineY = 0;
|
|
|
|
// Character and word spacing
|
|
this.charSpacing = 0;
|
|
this.wordSpacing = 0;
|
|
this.textHScale = 1;
|
|
this.textRise = 0;
|
|
|
|
// Default foreground and background colors
|
|
this.fillColor = SVG_DEFAULTS.fillColor;
|
|
this.strokeColor = '#000000';
|
|
|
|
this.fillAlpha = 1;
|
|
this.strokeAlpha = 1;
|
|
this.lineWidth = 1;
|
|
this.lineJoin = '';
|
|
this.lineCap = '';
|
|
this.miterLimit = 0;
|
|
|
|
this.dashArray = [];
|
|
this.dashPhase = 0;
|
|
|
|
this.dependencies = [];
|
|
|
|
// Clipping
|
|
this.clipId = '';
|
|
this.pendingClip = false;
|
|
|
|
this.maskId = '';
|
|
}
|
|
|
|
SVGExtraState.prototype = {
|
|
clone: function SVGExtraState_clone() {
|
|
return Object.create(this);
|
|
},
|
|
setCurrentPoint: function SVGExtraState_setCurrentPoint(x, y) {
|
|
this.x = x;
|
|
this.y = y;
|
|
}
|
|
};
|
|
return SVGExtraState;
|
|
})();
|
|
|
|
var SVGGraphics = (function SVGGraphicsClosure() {
|
|
function opListToTree(opList) {
|
|
var opTree = [];
|
|
var tmp = [];
|
|
var opListLen = opList.length;
|
|
|
|
for (var x = 0; x < opListLen; x++) {
|
|
if (opList[x].fn === 'save') {
|
|
opTree.push({'fnId': 92, 'fn': 'group', 'items': []});
|
|
tmp.push(opTree);
|
|
opTree = opTree[opTree.length - 1].items;
|
|
continue;
|
|
}
|
|
|
|
if(opList[x].fn === 'restore') {
|
|
opTree = tmp.pop();
|
|
} else {
|
|
opTree.push(opList[x]);
|
|
}
|
|
}
|
|
return opTree;
|
|
}
|
|
|
|
/**
|
|
* Formats float number.
|
|
* @param value {number} number to format.
|
|
* @returns {string}
|
|
*/
|
|
function pf(value) {
|
|
if (value === (value | 0)) { // integer number
|
|
return value.toString();
|
|
}
|
|
var s = value.toFixed(10);
|
|
var i = s.length - 1;
|
|
if (s[i] !== '0') {
|
|
return s;
|
|
}
|
|
// removing trailing zeros
|
|
do {
|
|
i--;
|
|
} while (s[i] === '0');
|
|
return s.substr(0, s[i] === '.' ? i : i + 1);
|
|
}
|
|
|
|
/**
|
|
* Formats transform matrix. The standard rotation, scale and translate
|
|
* matrices are replaced by their shorter forms, and for identity matrix
|
|
* returns empty string to save the memory.
|
|
* @param m {Array} matrix to format.
|
|
* @returns {string}
|
|
*/
|
|
function pm(m) {
|
|
if (m[4] === 0 && m[5] === 0) {
|
|
if (m[1] === 0 && m[2] === 0) {
|
|
if (m[0] === 1 && m[3] === 1) {
|
|
return '';
|
|
}
|
|
return 'scale(' + pf(m[0]) + ' ' + pf(m[3]) + ')';
|
|
}
|
|
if (m[0] === m[3] && m[1] === -m[2]) {
|
|
var a = Math.acos(m[0]) * 180 / Math.PI;
|
|
return 'rotate(' + pf(a) + ')';
|
|
}
|
|
} else {
|
|
if (m[0] === 1 && m[1] === 0 && m[2] === 0 && m[3] === 1) {
|
|
return 'translate(' + pf(m[4]) + ' ' + pf(m[5]) + ')';
|
|
}
|
|
}
|
|
return 'matrix(' + pf(m[0]) + ' ' + pf(m[1]) + ' ' + pf(m[2]) + ' ' +
|
|
pf(m[3]) + ' ' + pf(m[4]) + ' ' + pf(m[5]) + ')';
|
|
}
|
|
|
|
function SVGGraphics(commonObjs, objs, forceDataSchema) {
|
|
this.current = new SVGExtraState();
|
|
this.transformMatrix = IDENTITY_MATRIX; // Graphics state matrix
|
|
this.transformStack = [];
|
|
this.extraStack = [];
|
|
this.commonObjs = commonObjs;
|
|
this.objs = objs;
|
|
this.pendingEOFill = false;
|
|
|
|
this.embedFonts = false;
|
|
this.embeddedFonts = Object.create(null);
|
|
this.cssStyle = null;
|
|
this.forceDataSchema = !!forceDataSchema;
|
|
}
|
|
|
|
var NS = 'http://www.w3.org/2000/svg';
|
|
var XML_NS = 'http://www.w3.org/XML/1998/namespace';
|
|
var XLINK_NS = 'http://www.w3.org/1999/xlink';
|
|
var LINE_CAP_STYLES = ['butt', 'round', 'square'];
|
|
var LINE_JOIN_STYLES = ['miter', 'round', 'bevel'];
|
|
var clipCount = 0;
|
|
var maskCount = 0;
|
|
|
|
SVGGraphics.prototype = {
|
|
save: function SVGGraphics_save() {
|
|
this.transformStack.push(this.transformMatrix);
|
|
var old = this.current;
|
|
this.extraStack.push(old);
|
|
this.current = old.clone();
|
|
},
|
|
|
|
restore: function SVGGraphics_restore() {
|
|
this.transformMatrix = this.transformStack.pop();
|
|
this.current = this.extraStack.pop();
|
|
|
|
this.tgrp = null;
|
|
},
|
|
|
|
group: function SVGGraphics_group(items) {
|
|
this.save();
|
|
this.executeOpTree(items);
|
|
this.restore();
|
|
},
|
|
|
|
loadDependencies: function SVGGraphics_loadDependencies(operatorList) {
|
|
var fnArray = operatorList.fnArray;
|
|
var fnArrayLen = fnArray.length;
|
|
var argsArray = operatorList.argsArray;
|
|
|
|
var self = this;
|
|
for (var i = 0; i < fnArrayLen; i++) {
|
|
if (OPS.dependency === fnArray[i]) {
|
|
var deps = argsArray[i];
|
|
for (var n = 0, nn = deps.length; n < nn; n++) {
|
|
var obj = deps[n];
|
|
var common = obj.substring(0, 2) === 'g_';
|
|
var promise;
|
|
if (common) {
|
|
promise = new Promise(function(resolve) {
|
|
self.commonObjs.get(obj, resolve);
|
|
});
|
|
} else {
|
|
promise = new Promise(function(resolve) {
|
|
self.objs.get(obj, resolve);
|
|
});
|
|
}
|
|
this.current.dependencies.push(promise);
|
|
}
|
|
}
|
|
}
|
|
return Promise.all(this.current.dependencies);
|
|
},
|
|
|
|
transform: function SVGGraphics_transform(a, b, c, d, e, f) {
|
|
var transformMatrix = [a, b, c, d, e, f];
|
|
this.transformMatrix = Util.transform(this.transformMatrix,
|
|
transformMatrix);
|
|
this.tgrp = null;
|
|
},
|
|
|
|
getSVG: function SVGGraphics_getSVG(operatorList, viewport) {
|
|
this.viewport = viewport;
|
|
|
|
var svgElement = this._initialize(viewport);
|
|
return this.loadDependencies(operatorList).then(function () {
|
|
this.transformMatrix = IDENTITY_MATRIX;
|
|
var opTree = this.convertOpList(operatorList);
|
|
this.executeOpTree(opTree);
|
|
return svgElement;
|
|
}.bind(this));
|
|
},
|
|
|
|
convertOpList: function SVGGraphics_convertOpList(operatorList) {
|
|
var argsArray = operatorList.argsArray;
|
|
var fnArray = operatorList.fnArray;
|
|
var fnArrayLen = fnArray.length;
|
|
var REVOPS = [];
|
|
var opList = [];
|
|
|
|
for (var op in OPS) {
|
|
REVOPS[OPS[op]] = op;
|
|
}
|
|
|
|
for (var x = 0; x < fnArrayLen; x++) {
|
|
var fnId = fnArray[x];
|
|
opList.push({'fnId' : fnId, 'fn': REVOPS[fnId], 'args': argsArray[x]});
|
|
}
|
|
return opListToTree(opList);
|
|
},
|
|
|
|
executeOpTree: function SVGGraphics_executeOpTree(opTree) {
|
|
var opTreeLen = opTree.length;
|
|
for(var x = 0; x < opTreeLen; x++) {
|
|
var fn = opTree[x].fn;
|
|
var fnId = opTree[x].fnId;
|
|
var args = opTree[x].args;
|
|
|
|
switch (fnId | 0) {
|
|
case OPS.beginText:
|
|
this.beginText();
|
|
break;
|
|
case OPS.setLeading:
|
|
this.setLeading(args);
|
|
break;
|
|
case OPS.setLeadingMoveText:
|
|
this.setLeadingMoveText(args[0], args[1]);
|
|
break;
|
|
case OPS.setFont:
|
|
this.setFont(args);
|
|
break;
|
|
case OPS.showText:
|
|
this.showText(args[0]);
|
|
break;
|
|
case OPS.showSpacedText:
|
|
this.showText(args[0]);
|
|
break;
|
|
case OPS.endText:
|
|
this.endText();
|
|
break;
|
|
case OPS.moveText:
|
|
this.moveText(args[0], args[1]);
|
|
break;
|
|
case OPS.setCharSpacing:
|
|
this.setCharSpacing(args[0]);
|
|
break;
|
|
case OPS.setWordSpacing:
|
|
this.setWordSpacing(args[0]);
|
|
break;
|
|
case OPS.setHScale:
|
|
this.setHScale(args[0]);
|
|
break;
|
|
case OPS.setTextMatrix:
|
|
this.setTextMatrix(args[0], args[1], args[2],
|
|
args[3], args[4], args[5]);
|
|
break;
|
|
case OPS.setLineWidth:
|
|
this.setLineWidth(args[0]);
|
|
break;
|
|
case OPS.setLineJoin:
|
|
this.setLineJoin(args[0]);
|
|
break;
|
|
case OPS.setLineCap:
|
|
this.setLineCap(args[0]);
|
|
break;
|
|
case OPS.setMiterLimit:
|
|
this.setMiterLimit(args[0]);
|
|
break;
|
|
case OPS.setFillRGBColor:
|
|
this.setFillRGBColor(args[0], args[1], args[2]);
|
|
break;
|
|
case OPS.setStrokeRGBColor:
|
|
this.setStrokeRGBColor(args[0], args[1], args[2]);
|
|
break;
|
|
case OPS.setDash:
|
|
this.setDash(args[0], args[1]);
|
|
break;
|
|
case OPS.setGState:
|
|
this.setGState(args[0]);
|
|
break;
|
|
case OPS.fill:
|
|
this.fill();
|
|
break;
|
|
case OPS.eoFill:
|
|
this.eoFill();
|
|
break;
|
|
case OPS.stroke:
|
|
this.stroke();
|
|
break;
|
|
case OPS.fillStroke:
|
|
this.fillStroke();
|
|
break;
|
|
case OPS.eoFillStroke:
|
|
this.eoFillStroke();
|
|
break;
|
|
case OPS.clip:
|
|
this.clip('nonzero');
|
|
break;
|
|
case OPS.eoClip:
|
|
this.clip('evenodd');
|
|
break;
|
|
case OPS.paintSolidColorImageMask:
|
|
this.paintSolidColorImageMask();
|
|
break;
|
|
case OPS.paintJpegXObject:
|
|
this.paintJpegXObject(args[0], args[1], args[2]);
|
|
break;
|
|
case OPS.paintImageXObject:
|
|
this.paintImageXObject(args[0]);
|
|
break;
|
|
case OPS.paintInlineImageXObject:
|
|
this.paintInlineImageXObject(args[0]);
|
|
break;
|
|
case OPS.paintImageMaskXObject:
|
|
this.paintImageMaskXObject(args[0]);
|
|
break;
|
|
case OPS.paintFormXObjectBegin:
|
|
this.paintFormXObjectBegin(args[0], args[1]);
|
|
break;
|
|
case OPS.paintFormXObjectEnd:
|
|
this.paintFormXObjectEnd();
|
|
break;
|
|
case OPS.closePath:
|
|
this.closePath();
|
|
break;
|
|
case OPS.closeStroke:
|
|
this.closeStroke();
|
|
break;
|
|
case OPS.closeFillStroke:
|
|
this.closeFillStroke();
|
|
break;
|
|
case OPS.nextLine:
|
|
this.nextLine();
|
|
break;
|
|
case OPS.transform:
|
|
this.transform(args[0], args[1], args[2], args[3],
|
|
args[4], args[5]);
|
|
break;
|
|
case OPS.constructPath:
|
|
this.constructPath(args[0], args[1]);
|
|
break;
|
|
case OPS.endPath:
|
|
this.endPath();
|
|
break;
|
|
case 92:
|
|
this.group(opTree[x].items);
|
|
break;
|
|
default:
|
|
warn('Unimplemented operator ' + fn);
|
|
break;
|
|
}
|
|
}
|
|
},
|
|
|
|
setWordSpacing: function SVGGraphics_setWordSpacing(wordSpacing) {
|
|
this.current.wordSpacing = wordSpacing;
|
|
},
|
|
|
|
setCharSpacing: function SVGGraphics_setCharSpacing(charSpacing) {
|
|
this.current.charSpacing = charSpacing;
|
|
},
|
|
|
|
nextLine: function SVGGraphics_nextLine() {
|
|
this.moveText(0, this.current.leading);
|
|
},
|
|
|
|
setTextMatrix: function SVGGraphics_setTextMatrix(a, b, c, d, e, f) {
|
|
var current = this.current;
|
|
this.current.textMatrix = this.current.lineMatrix = [a, b, c, d, e, f];
|
|
|
|
this.current.x = this.current.lineX = 0;
|
|
this.current.y = this.current.lineY = 0;
|
|
|
|
current.xcoords = [];
|
|
current.tspan = document.createElementNS(NS, 'svg:tspan');
|
|
current.tspan.setAttributeNS(null, 'font-family', current.fontFamily);
|
|
current.tspan.setAttributeNS(null, 'font-size',
|
|
pf(current.fontSize) + 'px');
|
|
current.tspan.setAttributeNS(null, 'y', pf(-current.y));
|
|
|
|
current.txtElement = document.createElementNS(NS, 'svg:text');
|
|
current.txtElement.appendChild(current.tspan);
|
|
},
|
|
|
|
beginText: function SVGGraphics_beginText() {
|
|
this.current.x = this.current.lineX = 0;
|
|
this.current.y = this.current.lineY = 0;
|
|
this.current.textMatrix = IDENTITY_MATRIX;
|
|
this.current.lineMatrix = IDENTITY_MATRIX;
|
|
this.current.tspan = document.createElementNS(NS, 'svg:tspan');
|
|
this.current.txtElement = document.createElementNS(NS, 'svg:text');
|
|
this.current.txtgrp = document.createElementNS(NS, 'svg:g');
|
|
this.current.xcoords = [];
|
|
},
|
|
|
|
moveText: function SVGGraphics_moveText(x, y) {
|
|
var current = this.current;
|
|
this.current.x = this.current.lineX += x;
|
|
this.current.y = this.current.lineY += y;
|
|
|
|
current.xcoords = [];
|
|
current.tspan = document.createElementNS(NS, 'svg:tspan');
|
|
current.tspan.setAttributeNS(null, 'font-family', current.fontFamily);
|
|
current.tspan.setAttributeNS(null, 'font-size',
|
|
pf(current.fontSize) + 'px');
|
|
current.tspan.setAttributeNS(null, 'y', pf(-current.y));
|
|
},
|
|
|
|
showText: function SVGGraphics_showText(glyphs) {
|
|
var current = this.current;
|
|
var font = current.font;
|
|
var fontSize = current.fontSize;
|
|
|
|
if (fontSize === 0) {
|
|
return;
|
|
}
|
|
|
|
var charSpacing = current.charSpacing;
|
|
var wordSpacing = current.wordSpacing;
|
|
var fontDirection = current.fontDirection;
|
|
var textHScale = current.textHScale * fontDirection;
|
|
var glyphsLength = glyphs.length;
|
|
var vertical = font.vertical;
|
|
var widthAdvanceScale = fontSize * current.fontMatrix[0];
|
|
|
|
var x = 0, i;
|
|
for (i = 0; i < glyphsLength; ++i) {
|
|
var glyph = glyphs[i];
|
|
if (glyph === null) {
|
|
// word break
|
|
x += fontDirection * wordSpacing;
|
|
continue;
|
|
} else if (isNum(glyph)) {
|
|
x += -glyph * fontSize * 0.001;
|
|
continue;
|
|
}
|
|
current.xcoords.push(current.x + x * textHScale);
|
|
|
|
var width = glyph.width;
|
|
var character = glyph.fontChar;
|
|
var charWidth = width * widthAdvanceScale + charSpacing * fontDirection;
|
|
x += charWidth;
|
|
|
|
current.tspan.textContent += character;
|
|
}
|
|
if (vertical) {
|
|
current.y -= x * textHScale;
|
|
} else {
|
|
current.x += x * textHScale;
|
|
}
|
|
|
|
current.tspan.setAttributeNS(null, 'x',
|
|
current.xcoords.map(pf).join(' '));
|
|
current.tspan.setAttributeNS(null, 'y', pf(-current.y));
|
|
current.tspan.setAttributeNS(null, 'font-family', current.fontFamily);
|
|
current.tspan.setAttributeNS(null, 'font-size',
|
|
pf(current.fontSize) + 'px');
|
|
if (current.fontStyle !== SVG_DEFAULTS.fontStyle) {
|
|
current.tspan.setAttributeNS(null, 'font-style', current.fontStyle);
|
|
}
|
|
if (current.fontWeight !== SVG_DEFAULTS.fontWeight) {
|
|
current.tspan.setAttributeNS(null, 'font-weight', current.fontWeight);
|
|
}
|
|
if (current.fillColor !== SVG_DEFAULTS.fillColor) {
|
|
current.tspan.setAttributeNS(null, 'fill', current.fillColor);
|
|
}
|
|
|
|
current.txtElement.setAttributeNS(null, 'transform',
|
|
pm(current.textMatrix) +
|
|
' scale(1, -1)' );
|
|
current.txtElement.setAttributeNS(XML_NS, 'xml:space', 'preserve');
|
|
current.txtElement.appendChild(current.tspan);
|
|
current.txtgrp.appendChild(current.txtElement);
|
|
|
|
this._ensureTransformGroup().appendChild(current.txtElement);
|
|
},
|
|
|
|
setLeadingMoveText: function SVGGraphics_setLeadingMoveText(x, y) {
|
|
this.setLeading(-y);
|
|
this.moveText(x, y);
|
|
},
|
|
|
|
addFontStyle: function SVGGraphics_addFontStyle(fontObj) {
|
|
if (!this.cssStyle) {
|
|
this.cssStyle = document.createElementNS(NS, 'svg:style');
|
|
this.cssStyle.setAttributeNS(null, 'type', 'text/css');
|
|
this.defs.appendChild(this.cssStyle);
|
|
}
|
|
|
|
var url = createObjectURL(fontObj.data, fontObj.mimetype,
|
|
this.forceDataSchema);
|
|
this.cssStyle.textContent +=
|
|
'@font-face { font-family: "' + fontObj.loadedName + '";' +
|
|
' src: url(' + url + '); }\n';
|
|
},
|
|
|
|
setFont: function SVGGraphics_setFont(details) {
|
|
var current = this.current;
|
|
var fontObj = this.commonObjs.get(details[0]);
|
|
var size = details[1];
|
|
this.current.font = fontObj;
|
|
|
|
if (this.embedFonts && fontObj.data &&
|
|
!this.embeddedFonts[fontObj.loadedName]) {
|
|
this.addFontStyle(fontObj);
|
|
this.embeddedFonts[fontObj.loadedName] = fontObj;
|
|
}
|
|
|
|
current.fontMatrix = (fontObj.fontMatrix ?
|
|
fontObj.fontMatrix : FONT_IDENTITY_MATRIX);
|
|
|
|
var bold = fontObj.black ? (fontObj.bold ? 'bolder' : 'bold') :
|
|
(fontObj.bold ? 'bold' : 'normal');
|
|
var italic = fontObj.italic ? 'italic' : 'normal';
|
|
|
|
if (size < 0) {
|
|
size = -size;
|
|
current.fontDirection = -1;
|
|
} else {
|
|
current.fontDirection = 1;
|
|
}
|
|
current.fontSize = size;
|
|
current.fontFamily = fontObj.loadedName;
|
|
current.fontWeight = bold;
|
|
current.fontStyle = italic;
|
|
|
|
current.tspan = document.createElementNS(NS, 'svg:tspan');
|
|
current.tspan.setAttributeNS(null, 'y', pf(-current.y));
|
|
current.xcoords = [];
|
|
},
|
|
|
|
endText: function SVGGraphics_endText() {},
|
|
|
|
// Path properties
|
|
setLineWidth: function SVGGraphics_setLineWidth(width) {
|
|
this.current.lineWidth = width;
|
|
},
|
|
setLineCap: function SVGGraphics_setLineCap(style) {
|
|
this.current.lineCap = LINE_CAP_STYLES[style];
|
|
},
|
|
setLineJoin: function SVGGraphics_setLineJoin(style) {
|
|
this.current.lineJoin = LINE_JOIN_STYLES[style];
|
|
},
|
|
setMiterLimit: function SVGGraphics_setMiterLimit(limit) {
|
|
this.current.miterLimit = limit;
|
|
},
|
|
setStrokeRGBColor: function SVGGraphics_setStrokeRGBColor(r, g, b) {
|
|
var color = Util.makeCssRgb(r, g, b);
|
|
this.current.strokeColor = color;
|
|
},
|
|
setFillRGBColor: function SVGGraphics_setFillRGBColor(r, g, b) {
|
|
var color = Util.makeCssRgb(r, g, b);
|
|
this.current.fillColor = color;
|
|
this.current.tspan = document.createElementNS(NS, 'svg:tspan');
|
|
this.current.xcoords = [];
|
|
},
|
|
setDash: function SVGGraphics_setDash(dashArray, dashPhase) {
|
|
this.current.dashArray = dashArray;
|
|
this.current.dashPhase = dashPhase;
|
|
},
|
|
|
|
constructPath: function SVGGraphics_constructPath(ops, args) {
|
|
var current = this.current;
|
|
var x = current.x, y = current.y;
|
|
current.path = document.createElementNS(NS, 'svg:path');
|
|
var d = [];
|
|
var opLength = ops.length;
|
|
|
|
for (var i = 0, j = 0; i < opLength; i++) {
|
|
switch (ops[i] | 0) {
|
|
case OPS.rectangle:
|
|
x = args[j++];
|
|
y = args[j++];
|
|
var width = args[j++];
|
|
var height = args[j++];
|
|
var xw = x + width;
|
|
var yh = y + height;
|
|
d.push('M', pf(x), pf(y), 'L', pf(xw) , pf(y), 'L', pf(xw), pf(yh),
|
|
'L', pf(x), pf(yh), 'Z');
|
|
break;
|
|
case OPS.moveTo:
|
|
x = args[j++];
|
|
y = args[j++];
|
|
d.push('M', pf(x), pf(y));
|
|
break;
|
|
case OPS.lineTo:
|
|
x = args[j++];
|
|
y = args[j++];
|
|
d.push('L', pf(x) , pf(y));
|
|
break;
|
|
case OPS.curveTo:
|
|
x = args[j + 4];
|
|
y = args[j + 5];
|
|
d.push('C', pf(args[j]), pf(args[j + 1]), pf(args[j + 2]),
|
|
pf(args[j + 3]), pf(x), pf(y));
|
|
j += 6;
|
|
break;
|
|
case OPS.curveTo2:
|
|
x = args[j + 2];
|
|
y = args[j + 3];
|
|
d.push('C', pf(x), pf(y), pf(args[j]), pf(args[j + 1]),
|
|
pf(args[j + 2]), pf(args[j + 3]));
|
|
j += 4;
|
|
break;
|
|
case OPS.curveTo3:
|
|
x = args[j + 2];
|
|
y = args[j + 3];
|
|
d.push('C', pf(args[j]), pf(args[j + 1]), pf(x), pf(y),
|
|
pf(x), pf(y));
|
|
j += 4;
|
|
break;
|
|
case OPS.closePath:
|
|
d.push('Z');
|
|
break;
|
|
}
|
|
}
|
|
current.path.setAttributeNS(null, 'd', d.join(' '));
|
|
current.path.setAttributeNS(null, 'stroke-miterlimit',
|
|
pf(current.miterLimit));
|
|
current.path.setAttributeNS(null, 'stroke-linecap', current.lineCap);
|
|
current.path.setAttributeNS(null, 'stroke-linejoin', current.lineJoin);
|
|
current.path.setAttributeNS(null, 'stroke-width',
|
|
pf(current.lineWidth) + 'px');
|
|
current.path.setAttributeNS(null, 'stroke-dasharray',
|
|
current.dashArray.map(pf).join(' '));
|
|
current.path.setAttributeNS(null, 'stroke-dashoffset',
|
|
pf(current.dashPhase) + 'px');
|
|
current.path.setAttributeNS(null, 'fill', 'none');
|
|
|
|
this._ensureTransformGroup().appendChild(current.path);
|
|
|
|
// Saving a reference in current.element so that it can be addressed
|
|
// in 'fill' and 'stroke'
|
|
current.element = current.path;
|
|
current.setCurrentPoint(x, y);
|
|
},
|
|
|
|
endPath: function SVGGraphics_endPath() {},
|
|
|
|
clip: function SVGGraphics_clip(type) {
|
|
var current = this.current;
|
|
// Add current path to clipping path
|
|
current.clipId = 'clippath' + clipCount;
|
|
clipCount++;
|
|
this.clippath = document.createElementNS(NS, 'svg:clipPath');
|
|
this.clippath.setAttributeNS(null, 'id', current.clipId);
|
|
var clipElement = current.element.cloneNode();
|
|
if (type === 'evenodd') {
|
|
clipElement.setAttributeNS(null, 'clip-rule', 'evenodd');
|
|
} else {
|
|
clipElement.setAttributeNS(null, 'clip-rule', 'nonzero');
|
|
}
|
|
this.clippath.setAttributeNS(null, 'transform', pm(this.transformMatrix));
|
|
this.clippath.appendChild(clipElement);
|
|
this.defs.appendChild(this.clippath);
|
|
|
|
// Create a clipping group that references the clipping path.
|
|
current.pendingClip = true;
|
|
this.cgrp = document.createElementNS(NS, 'svg:g');
|
|
this.cgrp.setAttributeNS(null, 'clip-path',
|
|
'url(#' + current.clipId + ')');
|
|
this.svg.appendChild(this.cgrp);
|
|
},
|
|
|
|
closePath: function SVGGraphics_closePath() {
|
|
var current = this.current;
|
|
var d = current.path.getAttributeNS(null, 'd');
|
|
d += 'Z';
|
|
current.path.setAttributeNS(null, 'd', d);
|
|
},
|
|
|
|
setLeading: function SVGGraphics_setLeading(leading) {
|
|
this.current.leading = -leading;
|
|
},
|
|
|
|
setTextRise: function SVGGraphics_setTextRise(textRise) {
|
|
this.current.textRise = textRise;
|
|
},
|
|
|
|
setHScale: function SVGGraphics_setHScale(scale) {
|
|
this.current.textHScale = scale / 100;
|
|
},
|
|
|
|
setGState: function SVGGraphics_setGState(states) {
|
|
for (var i = 0, ii = states.length; i < ii; i++) {
|
|
var state = states[i];
|
|
var key = state[0];
|
|
var value = state[1];
|
|
|
|
switch (key) {
|
|
case 'LW':
|
|
this.setLineWidth(value);
|
|
break;
|
|
case 'LC':
|
|
this.setLineCap(value);
|
|
break;
|
|
case 'LJ':
|
|
this.setLineJoin(value);
|
|
break;
|
|
case 'ML':
|
|
this.setMiterLimit(value);
|
|
break;
|
|
case 'D':
|
|
this.setDash(value[0], value[1]);
|
|
break;
|
|
case 'Font':
|
|
this.setFont(value);
|
|
break;
|
|
default:
|
|
warn('Unimplemented graphic state ' + key);
|
|
break;
|
|
}
|
|
}
|
|
},
|
|
|
|
fill: function SVGGraphics_fill() {
|
|
var current = this.current;
|
|
current.element.setAttributeNS(null, 'fill', current.fillColor);
|
|
},
|
|
|
|
stroke: function SVGGraphics_stroke() {
|
|
var current = this.current;
|
|
current.element.setAttributeNS(null, 'stroke', current.strokeColor);
|
|
current.element.setAttributeNS(null, 'fill', 'none');
|
|
},
|
|
|
|
eoFill: function SVGGraphics_eoFill() {
|
|
var current = this.current;
|
|
current.element.setAttributeNS(null, 'fill', current.fillColor);
|
|
current.element.setAttributeNS(null, 'fill-rule', 'evenodd');
|
|
},
|
|
|
|
fillStroke: function SVGGraphics_fillStroke() {
|
|
// Order is important since stroke wants fill to be none.
|
|
// First stroke, then if fill needed, it will be overwritten.
|
|
this.stroke();
|
|
this.fill();
|
|
},
|
|
|
|
eoFillStroke: function SVGGraphics_eoFillStroke() {
|
|
this.current.element.setAttributeNS(null, 'fill-rule', 'evenodd');
|
|
this.fillStroke();
|
|
},
|
|
|
|
closeStroke: function SVGGraphics_closeStroke() {
|
|
this.closePath();
|
|
this.stroke();
|
|
},
|
|
|
|
closeFillStroke: function SVGGraphics_closeFillStroke() {
|
|
this.closePath();
|
|
this.fillStroke();
|
|
},
|
|
|
|
paintSolidColorImageMask:
|
|
function SVGGraphics_paintSolidColorImageMask() {
|
|
var current = this.current;
|
|
var rect = document.createElementNS(NS, 'svg:rect');
|
|
rect.setAttributeNS(null, 'x', '0');
|
|
rect.setAttributeNS(null, 'y', '0');
|
|
rect.setAttributeNS(null, 'width', '1px');
|
|
rect.setAttributeNS(null, 'height', '1px');
|
|
rect.setAttributeNS(null, 'fill', current.fillColor);
|
|
|
|
this._ensureTransformGroup().appendChild(rect);
|
|
},
|
|
|
|
paintJpegXObject: function SVGGraphics_paintJpegXObject(objId, w, h) {
|
|
var imgObj = this.objs.get(objId);
|
|
var imgEl = document.createElementNS(NS, 'svg:image');
|
|
imgEl.setAttributeNS(XLINK_NS, 'xlink:href', imgObj.src);
|
|
imgEl.setAttributeNS(null, 'width', imgObj.width + 'px');
|
|
imgEl.setAttributeNS(null, 'height', imgObj.height + 'px');
|
|
imgEl.setAttributeNS(null, 'x', '0');
|
|
imgEl.setAttributeNS(null, 'y', pf(-h));
|
|
imgEl.setAttributeNS(null, 'transform',
|
|
'scale(' + pf(1 / w) + ' ' + pf(-1 / h) + ')');
|
|
|
|
this._ensureTransformGroup().appendChild(imgEl);
|
|
},
|
|
|
|
paintImageXObject: function SVGGraphics_paintImageXObject(objId) {
|
|
var imgData = this.objs.get(objId);
|
|
if (!imgData) {
|
|
warn('Dependent image isn\'t ready yet');
|
|
return;
|
|
}
|
|
this.paintInlineImageXObject(imgData);
|
|
},
|
|
|
|
paintInlineImageXObject:
|
|
function SVGGraphics_paintInlineImageXObject(imgData, mask) {
|
|
var width = imgData.width;
|
|
var height = imgData.height;
|
|
|
|
var imgSrc = convertImgDataToPng(imgData, this.forceDataSchema);
|
|
var cliprect = document.createElementNS(NS, 'svg:rect');
|
|
cliprect.setAttributeNS(null, 'x', '0');
|
|
cliprect.setAttributeNS(null, 'y', '0');
|
|
cliprect.setAttributeNS(null, 'width', pf(width));
|
|
cliprect.setAttributeNS(null, 'height', pf(height));
|
|
this.current.element = cliprect;
|
|
this.clip('nonzero');
|
|
var imgEl = document.createElementNS(NS, 'svg:image');
|
|
imgEl.setAttributeNS(XLINK_NS, 'xlink:href', imgSrc);
|
|
imgEl.setAttributeNS(null, 'x', '0');
|
|
imgEl.setAttributeNS(null, 'y', pf(-height));
|
|
imgEl.setAttributeNS(null, 'width', pf(width) + 'px');
|
|
imgEl.setAttributeNS(null, 'height', pf(height) + 'px');
|
|
imgEl.setAttributeNS(null, 'transform',
|
|
'scale(' + pf(1 / width) + ' ' +
|
|
pf(-1 / height) + ')');
|
|
if (mask) {
|
|
mask.appendChild(imgEl);
|
|
} else {
|
|
this._ensureTransformGroup().appendChild(imgEl);
|
|
}
|
|
},
|
|
|
|
paintImageMaskXObject:
|
|
function SVGGraphics_paintImageMaskXObject(imgData) {
|
|
var current = this.current;
|
|
var width = imgData.width;
|
|
var height = imgData.height;
|
|
var fillColor = current.fillColor;
|
|
|
|
current.maskId = 'mask' + maskCount++;
|
|
var mask = document.createElementNS(NS, 'svg:mask');
|
|
mask.setAttributeNS(null, 'id', current.maskId);
|
|
|
|
var rect = document.createElementNS(NS, 'svg:rect');
|
|
rect.setAttributeNS(null, 'x', '0');
|
|
rect.setAttributeNS(null, 'y', '0');
|
|
rect.setAttributeNS(null, 'width', pf(width));
|
|
rect.setAttributeNS(null, 'height', pf(height));
|
|
rect.setAttributeNS(null, 'fill', fillColor);
|
|
rect.setAttributeNS(null, 'mask', 'url(#' + current.maskId +')');
|
|
this.defs.appendChild(mask);
|
|
|
|
this._ensureTransformGroup().appendChild(rect);
|
|
|
|
this.paintInlineImageXObject(imgData, mask);
|
|
},
|
|
|
|
paintFormXObjectBegin:
|
|
function SVGGraphics_paintFormXObjectBegin(matrix, bbox) {
|
|
if (isArray(matrix) && matrix.length === 6) {
|
|
this.transform(matrix[0], matrix[1], matrix[2],
|
|
matrix[3], matrix[4], matrix[5]);
|
|
}
|
|
|
|
if (isArray(bbox) && bbox.length === 4) {
|
|
var width = bbox[2] - bbox[0];
|
|
var height = bbox[3] - bbox[1];
|
|
|
|
var cliprect = document.createElementNS(NS, 'svg:rect');
|
|
cliprect.setAttributeNS(null, 'x', bbox[0]);
|
|
cliprect.setAttributeNS(null, 'y', bbox[1]);
|
|
cliprect.setAttributeNS(null, 'width', pf(width));
|
|
cliprect.setAttributeNS(null, 'height', pf(height));
|
|
this.current.element = cliprect;
|
|
this.clip('nonzero');
|
|
this.endPath();
|
|
}
|
|
},
|
|
|
|
paintFormXObjectEnd:
|
|
function SVGGraphics_paintFormXObjectEnd() {},
|
|
|
|
/**
|
|
* @private
|
|
*/
|
|
_initialize: function SVGGraphics_initialize(viewport) {
|
|
// Create the SVG element.
|
|
var svg = document.createElementNS(NS, 'svg:svg');
|
|
svg.setAttributeNS(null, 'version', '1.1');
|
|
svg.setAttributeNS(null, 'width', viewport.width + 'px');
|
|
svg.setAttributeNS(null, 'height', viewport.height + 'px');
|
|
svg.setAttributeNS(null, 'viewBox', '0 0 ' + viewport.width +
|
|
' ' + viewport.height);
|
|
|
|
// Create the definitions element.
|
|
var definitions = document.createElementNS(NS, 'svg:defs');
|
|
svg.appendChild(definitions);
|
|
this.defs = definitions;
|
|
|
|
// Create the root group element, which acts a container for all other
|
|
// groups and applies the viewport transform.
|
|
var rootGroup = document.createElementNS(NS, 'svg:g');
|
|
rootGroup.setAttributeNS(null, 'transform', pm(viewport.transform));
|
|
svg.appendChild(rootGroup);
|
|
|
|
// For the construction of the SVG image we are only interested in the
|
|
// root group, so we expose it as the entry point of the SVG image for
|
|
// the other code in this class.
|
|
this.svg = rootGroup;
|
|
|
|
return svg;
|
|
},
|
|
|
|
/**
|
|
* @private
|
|
*/
|
|
_ensureTransformGroup: function SVGGraphics_ensureTransformGroup() {
|
|
if (!this.tgrp) {
|
|
this.tgrp = document.createElementNS(NS, 'svg:g');
|
|
this.tgrp.setAttributeNS(null, 'transform', pm(this.transformMatrix));
|
|
|
|
if (this.current.pendingClip) {
|
|
this.cgrp.appendChild(this.tgrp);
|
|
} else {
|
|
this.svg.appendChild(this.tgrp);
|
|
}
|
|
}
|
|
return this.tgrp;
|
|
}
|
|
};
|
|
return SVGGraphics;
|
|
})();
|
|
|
|
exports.SVGGraphics = SVGGraphics;
|
|
//#endif
|
|
}));
|