f1a87bab10
In general, we may not know the stroke properties when path construction happens. Since we must know the properties when we apply the stroke, we should set the properties at that point. Note that we already do that for the color and opacity, but not yet for the other properties.
1228 lines
38 KiB
JavaScript
1228 lines
38 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.
|
|
*/
|
|
|
|
import {
|
|
createObjectURL, FONT_IDENTITY_MATRIX, IDENTITY_MATRIX, ImageKind, isArray,
|
|
isNum, OPS, Util, warn
|
|
} from '../shared/util';
|
|
|
|
var SVGGraphics = function() {
|
|
throw new Error('Not implemented: SVGGraphics');
|
|
};
|
|
|
|
if (typeof PDFJSDev === 'undefined' ||
|
|
PDFJSDev.test('GENERIC || SINGLE_FILE')) {
|
|
|
|
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.activeClipUrl = null;
|
|
this.clipGroup = null;
|
|
|
|
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;
|
|
})();
|
|
|
|
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.pendingClip = null;
|
|
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.pendingClip = null;
|
|
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;
|
|
|
|
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((resolve) => {
|
|
this.commonObjs.get(obj, resolve);
|
|
});
|
|
} else {
|
|
promise = new Promise((resolve) => {
|
|
this.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(() => {
|
|
this.transformMatrix = IDENTITY_MATRIX;
|
|
var opTree = this.convertOpList(operatorList);
|
|
this.executeOpTree(opTree);
|
|
return svgElement;
|
|
});
|
|
},
|
|
|
|
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 spacing = (glyph.isSpace ? wordSpacing : 0) + charSpacing;
|
|
var charWidth = width * widthAdvanceScale + spacing * 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;
|
|
},
|
|
setStrokeAlpha: function SVGGraphics_setStrokeAlpha(strokeAlpha) {
|
|
this.current.strokeAlpha = strokeAlpha;
|
|
},
|
|
setStrokeRGBColor: function SVGGraphics_setStrokeRGBColor(r, g, b) {
|
|
var color = Util.makeCssRgb(r, g, b);
|
|
this.current.strokeColor = color;
|
|
},
|
|
setFillAlpha: function SVGGraphics_setFillAlpha(fillAlpha) {
|
|
this.current.fillAlpha = fillAlpha;
|
|
},
|
|
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, '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() {
|
|
if (!this.pendingClip) {
|
|
return;
|
|
}
|
|
var current = this.current;
|
|
// Add current path to clipping path
|
|
var clipId = 'clippath' + clipCount;
|
|
clipCount++;
|
|
var clipPath = document.createElementNS(NS, 'svg:clipPath');
|
|
clipPath.setAttributeNS(null, 'id', clipId);
|
|
clipPath.setAttributeNS(null, 'transform', pm(this.transformMatrix));
|
|
var clipElement = current.element.cloneNode();
|
|
if (this.pendingClip === 'evenodd') {
|
|
clipElement.setAttributeNS(null, 'clip-rule', 'evenodd');
|
|
} else {
|
|
clipElement.setAttributeNS(null, 'clip-rule', 'nonzero');
|
|
}
|
|
this.pendingClip = null;
|
|
clipPath.appendChild(clipElement);
|
|
this.defs.appendChild(clipPath);
|
|
|
|
if (current.activeClipUrl) {
|
|
// The previous clipping group content can go out of order -- resetting
|
|
// cached clipGroups.
|
|
current.clipGroup = null;
|
|
this.extraStack.forEach(function (prev) {
|
|
prev.clipGroup = null;
|
|
});
|
|
}
|
|
current.activeClipUrl = 'url(#' + clipId + ')';
|
|
|
|
this.tgrp = null;
|
|
},
|
|
|
|
clip: function SVGGraphics_clip(type) {
|
|
this.pendingClip = type;
|
|
},
|
|
|
|
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;
|
|
case 'CA':
|
|
this.setStrokeAlpha(value);
|
|
break;
|
|
case 'ca':
|
|
this.setFillAlpha(value);
|
|
break;
|
|
default:
|
|
warn('Unimplemented graphic state ' + key);
|
|
break;
|
|
}
|
|
}
|
|
},
|
|
|
|
fill: function SVGGraphics_fill() {
|
|
var current = this.current;
|
|
current.element.setAttributeNS(null, 'fill', current.fillColor);
|
|
current.element.setAttributeNS(null, 'fill-opacity', current.fillAlpha);
|
|
},
|
|
|
|
stroke: function SVGGraphics_stroke() {
|
|
var current = this.current;
|
|
|
|
current.element.setAttributeNS(null, 'stroke', current.strokeColor);
|
|
current.element.setAttributeNS(null, 'stroke-opacity',
|
|
current.strokeAlpha);
|
|
current.element.setAttributeNS(null, 'stroke-miterlimit',
|
|
pf(current.miterLimit));
|
|
current.element.setAttributeNS(null, 'stroke-linecap', current.lineCap);
|
|
current.element.setAttributeNS(null, 'stroke-linejoin', current.lineJoin);
|
|
current.element.setAttributeNS(null, 'stroke-width',
|
|
pf(current.lineWidth) + 'px');
|
|
current.element.setAttributeNS(null, 'stroke-dasharray',
|
|
current.dashArray.map(pf).join(' '));
|
|
current.element.setAttributeNS(null, 'stroke-dashoffset',
|
|
pf(current.dashPhase) + 'px');
|
|
|
|
current.element.setAttributeNS(null, 'fill', 'none');
|
|
},
|
|
|
|
eoFill: function SVGGraphics_eoFill() {
|
|
this.current.element.setAttributeNS(null, 'fill-rule', 'evenodd');
|
|
this.fill();
|
|
},
|
|
|
|
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', pf(w));
|
|
imgEl.setAttributeNS(null, 'height', pf(h));
|
|
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, 'preserveAspectRatio', 'none');
|
|
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
|
|
*/
|
|
_ensureClipGroup: function SVGGraphics_ensureClipGroup() {
|
|
if (!this.current.clipGroup) {
|
|
var clipGroup = document.createElementNS(NS, 'svg:g');
|
|
clipGroup.setAttributeNS(null, 'clip-path',
|
|
this.current.activeClipUrl);
|
|
this.svg.appendChild(clipGroup);
|
|
this.current.clipGroup = clipGroup;
|
|
}
|
|
return this.current.clipGroup;
|
|
},
|
|
|
|
/**
|
|
* @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.activeClipUrl) {
|
|
this._ensureClipGroup().appendChild(this.tgrp);
|
|
} else {
|
|
this.svg.appendChild(this.tgrp);
|
|
}
|
|
}
|
|
return this.tgrp;
|
|
},
|
|
};
|
|
return SVGGraphics;
|
|
})();
|
|
|
|
}
|
|
|
|
export {
|
|
SVGGraphics,
|
|
};
|