pdf.js/src/display/svg.js

1472 lines
43 KiB
JavaScript
Raw Normal View History

2014-05-20 08:53:40 +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.
*/
/* globals __non_webpack_require__ */
/* eslint no-var: error */
2014-05-20 08:53:40 +09:00
import {
createObjectURL, FONT_IDENTITY_MATRIX, IDENTITY_MATRIX, ImageKind, isNum, OPS,
TextRenderingMode, Util, warn
} from '../shared/util';
import { DOMSVGFactory } from './display_utils';
import isNodeJS from '../shared/is_node';
let SVGGraphics = function() {
throw new Error('Not implemented: SVGGraphics');
};
if (typeof PDFJSDev === 'undefined' || PDFJSDev.test('GENERIC')) {
const SVG_DEFAULTS = {
2014-08-15 06:13:15 +09:00
fontStyle: 'normal',
fontWeight: 'normal',
Fix inconsistent spacing and trailing commas in objects in remaining `src/` files, so we can enable the `comma-dangle` and `object-curly-spacing` ESLint rules later on http://eslint.org/docs/rules/comma-dangle http://eslint.org/docs/rules/object-curly-spacing Given that we currently have quite inconsistent object formatting, fixing this in *one* big patch probably wouldn't be feasible (since I cannot imagine anyone wanting to review that); hence I've opted to try and do this piecewise instead. Please note: This patch was created automatically, using the ESLint `--fix` command line option. In a couple of places this caused lines to become too long, and I've fixed those manually; please refer to the interdiff below for the only hand-edits in this patch. ```diff diff --git a/src/display/canvas.js b/src/display/canvas.js index 5739f6f2..4216b2d2 100644 --- a/src/display/canvas.js +++ b/src/display/canvas.js @@ -2071,7 +2071,7 @@ var CanvasGraphics = (function CanvasGraphicsClosure() { var map = []; for (var i = 0, ii = positions.length; i < ii; i += 2) { map.push({ transform: [scaleX, 0, 0, scaleY, positions[i], - positions[i + 1]], x: 0, y: 0, w: width, h: height, }); + positions[i + 1]], x: 0, y: 0, w: width, h: height, }); } this.paintInlineImageXObjectGroup(imgData, map); }, diff --git a/src/display/svg.js b/src/display/svg.js index 9eb05dfa..2aa21482 100644 --- a/src/display/svg.js +++ b/src/display/svg.js @@ -458,7 +458,11 @@ SVGGraphics = (function SVGGraphicsClosure() { for (var x = 0; x < fnArrayLen; x++) { var fnId = fnArray[x]; - opList.push({ 'fnId': fnId, 'fn': REVOPS[fnId], 'args': argsArray[x], }); + opList.push({ + 'fnId': fnId, + 'fn': REVOPS[fnId], + 'args': argsArray[x], + }); } return opListToTree(opList); }, ```
2017-06-02 18:26:37 +09:00
fillColor: '#000000',
2014-08-15 06:13:15 +09:00
};
const XML_NS = 'http://www.w3.org/XML/1998/namespace';
const XLINK_NS = 'http://www.w3.org/1999/xlink';
const LINE_CAP_STYLES = ['butt', 'round', 'square'];
const LINE_JOIN_STYLES = ['miter', 'round', 'bevel'];
2014-08-15 06:13:15 +09:00
const convertImgDataToPng = (function() {
const PNG_HEADER =
new Uint8Array([0x89, 0x50, 0x4e, 0x47, 0x0d, 0x0a, 0x1a, 0x0a]);
const CHUNK_WRAPPER_SIZE = 12;
const crcTable = new Int32Array(256);
for (let i = 0; i < 256; i++) {
let c = i;
for (let 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) {
let crc = -1;
for (let i = start; i < end; i++) {
const a = (crc ^ data[i]) & 0xff;
const b = crcTable[a];
crc = (crc >>> 8) ^ b;
}
return crc ^ -1;
}
function writePngChunk(type, body, data, offset) {
let p = offset;
const 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;
const 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) {
let a = 1;
let b = 0;
for (let i = start; i < end; ++i) {
a = (a + (data[i] & 0xff)) % 65521;
b = (b + a) % 65521;
}
return (b << 16) | a;
}
/**
* @param {Uint8Array} literals The input data.
* @returns {Uint8Array} The DEFLATE-compressed data stream in zlib format.
* This is the required format for compressed streams in the PNG format:
* http://www.libpng.org/pub/png/spec/1.2/PNG-Compression.html
*/
function deflateSync(literals) {
if (!isNodeJS()) {
// zlib is certainly not available outside of Node.js. We can either use
// the pako library for client-side DEFLATE compression, or use the canvas
// API of the browser to obtain a more optimal PNG file.
return deflateSyncUncompressed(literals);
}
try {
// NOTE: This implementation is far from perfect, but already way better
// than not applying any compression.
//
// A better algorithm will try to choose a good predictor/filter and
// then choose a suitable zlib compression strategy (e.g. 3,Z_RLE).
//
// Node v0.11.12 zlib.deflateSync is introduced (and returns a Buffer).
// Node v3.0.0 Buffer inherits from Uint8Array.
// Node v8.0.0 zlib.deflateSync accepts Uint8Array as input.
let input;
// eslint-disable-next-line no-undef
if (parseInt(process.versions.node) >= 8) {
input = literals;
} else {
// eslint-disable-next-line no-undef
input = new Buffer(literals);
}
const output = __non_webpack_require__('zlib')
.deflateSync(input, { level: 9, });
return output instanceof Uint8Array ? output : new Uint8Array(output);
} catch (e) {
warn('Not compressing PNG because zlib.deflateSync is unavailable: ' + e);
}
return deflateSyncUncompressed(literals);
}
// An implementation of DEFLATE with compression level 0 (Z_NO_COMPRESSION).
function deflateSyncUncompressed(literals) {
let len = literals.length;
const maxBlockLength = 0xFFFF;
const deflateBlocks = Math.ceil(len / maxBlockLength);
const idat = new Uint8Array(2 + len + deflateBlocks * 5 + 4);
let pi = 0;
idat[pi++] = 0x78; // compression method and flags
idat[pi++] = 0x9c; // flags
let 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;
const 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;
return idat;
}
function encode(imgData, kind, forceDataSchema, isMask) {
const width = imgData.width;
const height = imgData.height;
let bitDepth, colorType, lineSize;
const 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
const literals = new Uint8Array((1 + lineSize) * height);
let offsetLiterals = 0, offsetBytes = 0;
for (let 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 && isMask) {
// inverting for image masks
offsetLiterals = 0;
for (let y = 0; y < height; y++) {
offsetLiterals++; // skipping predictor
for (let i = 0; i < lineSize; i++) {
literals[offsetLiterals++] ^= 0xFF;
}
}
}
const 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
]);
const idat = deflateSync(literals);
// PNG consists of: header, IHDR+data, IDAT+data, and IEND.
const pngLength = PNG_HEADER.length + (CHUNK_WRAPPER_SIZE * 3) +
ihdr.length + idat.length;
const data = new Uint8Array(pngLength);
let 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, isMask) {
const kind = (imgData.kind === undefined ?
ImageKind.GRAYSCALE_1BPP : imgData.kind);
return encode(imgData, kind, forceDataSchema, isMask);
};
})();
class SVGExtraState {
constructor() {
2014-05-20 08:53:40 +09:00
this.fontSizeScale = 1;
2014-08-15 06:13:15 +09:00
this.fontWeight = SVG_DEFAULTS.fontWeight;
this.fontSize = 0;
2014-05-20 08:53:40 +09:00
this.textMatrix = IDENTITY_MATRIX;
this.fontMatrix = FONT_IDENTITY_MATRIX;
this.leading = 0;
this.textRenderingMode = TextRenderingMode.FILL;
this.textMatrixScale = 1;
2014-05-20 08:53:40 +09:00
// Current point (in user coordinates)
this.x = 0;
this.y = 0;
2014-05-20 08:53:40 +09:00
// Start of text line (in text coordinates)
this.lineX = 0;
this.lineY = 0;
2014-05-20 08:53:40 +09:00
// Character and word spacing
this.charSpacing = 0;
this.wordSpacing = 0;
this.textHScale = 1;
this.textRise = 0;
// Default foreground and background colors
2014-08-15 06:13:15 +09:00
this.fillColor = SVG_DEFAULTS.fillColor;
2014-05-20 08:53:40 +09:00
this.strokeColor = '#000000';
this.fillAlpha = 1;
this.strokeAlpha = 1;
this.lineWidth = 1;
this.lineJoin = '';
this.lineCap = '';
this.miterLimit = 0;
2015-02-03 00:12:52 +09:00
2014-07-04 01:07:05 +09:00
this.dashArray = [];
this.dashPhase = 0;
2014-05-20 08:53:40 +09:00
this.dependencies = [];
2014-07-04 00:59:04 +09:00
// Clipping
2016-10-18 06:09:24 +09:00
this.activeClipUrl = null;
this.clipGroup = null;
this.maskId = '';
2014-05-20 08:53:40 +09:00
}
clone() {
return Object.create(this);
}
setCurrentPoint(x, y) {
this.x = x;
this.y = y;
}
}
// eslint-disable-next-line no-inner-declarations
function opListToTree(opList) {
let opTree = [];
const tmp = [];
for (const opListElement of opList) {
if (opListElement.fn === 'save') {
opTree.push({ 'fnId': 92, 'fn': 'group', 'items': [], });
tmp.push(opTree);
opTree = opTree[opTree.length - 1].items;
continue;
2014-05-20 08:53:40 +09:00
}
if (opListElement.fn === 'restore') {
opTree = tmp.pop();
} else {
opTree.push(opListElement);
}
}
return opTree;
}
2014-05-20 08:53:40 +09:00
/**
* Format a float number as a string.
*
* @param value {number} - The float number to format.
* @returns {string}
*/
// eslint-disable-next-line no-inner-declarations
function pf(value) {
if (Number.isInteger(value)) {
return value.toString();
}
const s = value.toFixed(10);
let i = s.length - 1;
if (s[i] !== '0') {
return s;
}
// Remove trailing zeros.
do {
i--;
} while (s[i] === '0');
return s.substring(0, s[i] === '.' ? i : i + 1);
}
/**
* Format a transform matrix as a string. The standard rotation, scale and
* translation matrices are replaced by their shorter forms, and for
* identity matrices an empty string is returned to save memory.
*
* @param m {Array} - The transform matrix to format.
* @returns {string}
*/
// eslint-disable-next-line no-inner-declarations
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]) {
const 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])})`;
2014-05-20 08:53:40 +09:00
}
}
return `matrix(${pf(m[0])} ${pf(m[1])} ${pf(m[2])} ${pf(m[3])} ${pf(m[4])} ` +
`${pf(m[5])})`;
}
2014-05-20 08:53:40 +09:00
// The counts below are relevant for all pages, so they have to be global
// instead of being members of `SVGGraphics` (which is recreated for
// each page).
let clipCount = 0;
let maskCount = 0;
let shadingCount = 0;
SVGGraphics = class SVGGraphics {
constructor(commonObjs, objs, forceDataSchema) {
this.svgFactory = new DOMSVGFactory();
2014-05-20 08:53:40 +09:00
this.current = new SVGExtraState();
this.transformMatrix = IDENTITY_MATRIX; // Graphics state matrix
this.transformStack = [];
this.extraStack = [];
this.commonObjs = commonObjs;
2014-06-25 05:24:00 +09:00
this.objs = objs;
2017-06-19 19:40:48 +09:00
this.pendingClip = null;
2014-05-20 08:53:40 +09:00
this.pendingEOFill = false;
2014-08-15 05:11:27 +09:00
this.embedFonts = false;
this.embeddedFonts = Object.create(null);
2014-08-15 05:11:27 +09:00
this.cssStyle = null;
this.forceDataSchema = !!forceDataSchema;
2014-05-20 08:53:40 +09:00
}
save() {
this.transformStack.push(this.transformMatrix);
const old = this.current;
this.extraStack.push(old);
this.current = old.clone();
}
restore() {
this.transformMatrix = this.transformStack.pop();
this.current = this.extraStack.pop();
this.pendingClip = null;
this.tgrp = null;
}
group(items) {
this.save();
this.executeOpTree(items);
this.restore();
}
2014-05-20 08:53:40 +09:00
loadDependencies(operatorList) {
const fnArray = operatorList.fnArray;
const argsArray = operatorList.argsArray;
2014-05-20 08:53:40 +09:00
for (let i = 0, ii = fnArray.length; i < ii; i++) {
if (fnArray[i] !== OPS.dependency) {
continue;
2014-05-20 08:53:40 +09:00
}
for (const obj of argsArray[i]) {
const objsPool = obj.startsWith('g_') ? this.commonObjs : this.objs;
const promise = new Promise((resolve) => {
objsPool.get(obj, resolve);
Fix inconsistent spacing and trailing commas in objects in remaining `src/` files, so we can enable the `comma-dangle` and `object-curly-spacing` ESLint rules later on http://eslint.org/docs/rules/comma-dangle http://eslint.org/docs/rules/object-curly-spacing Given that we currently have quite inconsistent object formatting, fixing this in *one* big patch probably wouldn't be feasible (since I cannot imagine anyone wanting to review that); hence I've opted to try and do this piecewise instead. Please note: This patch was created automatically, using the ESLint `--fix` command line option. In a couple of places this caused lines to become too long, and I've fixed those manually; please refer to the interdiff below for the only hand-edits in this patch. ```diff diff --git a/src/display/canvas.js b/src/display/canvas.js index 5739f6f2..4216b2d2 100644 --- a/src/display/canvas.js +++ b/src/display/canvas.js @@ -2071,7 +2071,7 @@ var CanvasGraphics = (function CanvasGraphicsClosure() { var map = []; for (var i = 0, ii = positions.length; i < ii; i += 2) { map.push({ transform: [scaleX, 0, 0, scaleY, positions[i], - positions[i + 1]], x: 0, y: 0, w: width, h: height, }); + positions[i + 1]], x: 0, y: 0, w: width, h: height, }); } this.paintInlineImageXObjectGroup(imgData, map); }, diff --git a/src/display/svg.js b/src/display/svg.js index 9eb05dfa..2aa21482 100644 --- a/src/display/svg.js +++ b/src/display/svg.js @@ -458,7 +458,11 @@ SVGGraphics = (function SVGGraphicsClosure() { for (var x = 0; x < fnArrayLen; x++) { var fnId = fnArray[x]; - opList.push({ 'fnId': fnId, 'fn': REVOPS[fnId], 'args': argsArray[x], }); + opList.push({ + 'fnId': fnId, + 'fn': REVOPS[fnId], + 'args': argsArray[x], + }); } return opListToTree(opList); }, ```
2017-06-02 18:26:37 +09:00
});
this.current.dependencies.push(promise);
2014-05-20 08:53:40 +09:00
}
}
return Promise.all(this.current.dependencies);
}
transform(a, b, c, d, e, f) {
const transformMatrix = [a, b, c, d, e, f];
this.transformMatrix = Util.transform(this.transformMatrix,
transformMatrix);
this.tgrp = null;
}
getSVG(operatorList, viewport) {
this.viewport = viewport;
const svgElement = this._initialize(viewport);
return this.loadDependencies(operatorList).then(() => {
this.transformMatrix = IDENTITY_MATRIX;
this.executeOpTree(this.convertOpList(operatorList));
return svgElement;
});
}
convertOpList(operatorList) {
const REVOPS = [];
for (const op in OPS) {
REVOPS[OPS[op]] = op;
}
const argsArray = operatorList.argsArray;
const fnArray = operatorList.fnArray;
const opList = [];
for (let i = 0, ii = fnArray.length; i < ii; i++) {
const fnId = fnArray[i];
opList.push({
'fnId': fnId,
'fn': REVOPS[fnId],
'args': argsArray[i],
});
}
return opListToTree(opList);
}
executeOpTree(opTree) {
for (const opTreeElement of opTree) {
const fn = opTreeElement.fn;
const fnId = opTreeElement.fnId;
const args = opTreeElement.args;
switch (fnId | 0) {
case OPS.beginText:
this.beginText();
break;
case OPS.dependency:
// Handled in `loadDependencies`, so no warning should be shown.
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.setTextRise:
this.setTextRise(args[0]);
break;
case OPS.setTextRenderingMode:
this.setTextRenderingMode(args[0]);
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.setStrokeColorN:
this.setStrokeColorN(args);
break;
case OPS.setFillColorN:
this.setFillColorN(args);
break;
case OPS.shadingFill:
this.shadingFill(args[0]);
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.closeEOFillStroke:
this.closeEOFillStroke();
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(opTreeElement.items);
break;
default:
warn(`Unimplemented operator ${fn}`);
break;
2014-05-20 08:53:40 +09:00
}
}
}
setWordSpacing(wordSpacing) {
this.current.wordSpacing = wordSpacing;
}
setCharSpacing(charSpacing) {
this.current.charSpacing = charSpacing;
}
nextLine() {
this.moveText(0, this.current.leading);
}
setTextMatrix(a, b, c, d, e, f) {
const current = this.current;
current.textMatrix = current.lineMatrix = [a, b, c, d, e, f];
current.textMatrixScale = Math.sqrt(a * a + b * b);
current.x = current.lineX = 0;
current.y = current.lineY = 0;
current.xcoords = [];
current.tspan = this.svgFactory.createElement('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 = this.svgFactory.createElement('svg:text');
current.txtElement.appendChild(current.tspan);
}
beginText() {
const current = this.current;
current.x = current.lineX = 0;
current.y = current.lineY = 0;
current.textMatrix = IDENTITY_MATRIX;
current.lineMatrix = IDENTITY_MATRIX;
current.textMatrixScale = 1;
current.tspan = this.svgFactory.createElement('svg:tspan');
current.txtElement = this.svgFactory.createElement('svg:text');
current.txtgrp = this.svgFactory.createElement('svg:g');
current.xcoords = [];
}
moveText(x, y) {
const current = this.current;
current.x = current.lineX += x;
current.y = current.lineY += y;
current.xcoords = [];
current.tspan = this.svgFactory.createElement('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(glyphs) {
const current = this.current;
const font = current.font;
const fontSize = current.fontSize;
if (fontSize === 0) {
return;
}
const charSpacing = current.charSpacing;
const wordSpacing = current.wordSpacing;
const fontDirection = current.fontDirection;
const textHScale = current.textHScale * fontDirection;
const vertical = font.vertical;
const widthAdvanceScale = fontSize * current.fontMatrix[0];
let x = 0;
for (const glyph of glyphs) {
if (glyph === null) {
// Word break
x += fontDirection * wordSpacing;
continue;
} else if (isNum(glyph)) {
x += -glyph * fontSize * 0.001;
continue;
2014-05-20 08:53:40 +09:00
}
const width = glyph.width;
const character = glyph.fontChar;
const spacing = (glyph.isSpace ? wordSpacing : 0) + charSpacing;
const charWidth = width * widthAdvanceScale + spacing * fontDirection;
2014-05-20 08:53:40 +09:00
if (!glyph.isInFont && !font.missingFile) {
x += charWidth;
// TODO: To assist with text selection, we should replace the missing
// character with a space character if charWidth is not zero.
// But we cannot just do "character = ' '", because the ' ' character
// might actually map to a different glyph.
continue;
2014-05-20 08:53:40 +09:00
}
current.xcoords.push(current.x + x * textHScale);
current.tspan.textContent += character;
x += charWidth;
}
if (vertical) {
current.y -= x * textHScale;
} else {
current.x += x * textHScale;
}
2014-05-20 08:53:40 +09:00
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);
}
const fillStrokeMode = current.textRenderingMode &
TextRenderingMode.FILL_STROKE_MASK;
if (fillStrokeMode === TextRenderingMode.FILL ||
fillStrokeMode === TextRenderingMode.FILL_STROKE) {
if (current.fillColor !== SVG_DEFAULTS.fillColor) {
current.tspan.setAttributeNS(null, 'fill', current.fillColor);
2014-08-15 06:13:15 +09:00
}
if (current.fillAlpha < 1) {
current.tspan.setAttributeNS(null, 'fill-opacity', current.fillAlpha);
2014-08-15 06:13:15 +09:00
}
} else if (current.textRenderingMode === TextRenderingMode.ADD_TO_PATH) {
// Workaround for Firefox: We must set fill="transparent" because
// fill="none" would generate an empty clipping path.
current.tspan.setAttributeNS(null, 'fill', 'transparent');
} else {
current.tspan.setAttributeNS(null, 'fill', 'none');
}
if (fillStrokeMode === TextRenderingMode.STROKE ||
fillStrokeMode === TextRenderingMode.FILL_STROKE) {
const lineWidthScale = 1 / (current.textMatrixScale || 1);
this._setStrokeAttributes(current.tspan, lineWidthScale);
}
// Include the text rise in the text matrix since the `pm` function
// creates the SVG element's `translate` entry (work on a copy to avoid
// altering the original text matrix).
let textMatrix = current.textMatrix;
if (current.textRise !== 0) {
textMatrix = textMatrix.slice();
textMatrix[5] += current.textRise;
}
current.txtElement.setAttributeNS(null, 'transform',
`${pm(textMatrix)} scale(1, -1)`);
current.txtElement.setAttributeNS(XML_NS, 'xml:space', 'preserve');
current.txtElement.appendChild(current.tspan);
current.txtgrp.appendChild(current.txtElement);
2014-05-20 08:53:40 +09:00
this._ensureTransformGroup().appendChild(current.txtElement);
}
setLeadingMoveText(x, y) {
this.setLeading(-y);
this.moveText(x, y);
}
addFontStyle(fontObj) {
if (!this.cssStyle) {
this.cssStyle = this.svgFactory.createElement('svg:style');
this.cssStyle.setAttributeNS(null, 'type', 'text/css');
this.defs.appendChild(this.cssStyle);
}
2014-08-15 05:11:27 +09:00
const url = createObjectURL(fontObj.data, fontObj.mimetype,
this.forceDataSchema);
this.cssStyle.textContent +=
`@font-face { font-family: "${fontObj.loadedName}";` +
` src: url(${url}); }\n`;
}
setFont(details) {
const current = this.current;
const fontObj = this.commonObjs.get(details[0]);
let size = details[1];
current.font = fontObj;
if (this.embedFonts && fontObj.data &&
!this.embeddedFonts[fontObj.loadedName]) {
this.addFontStyle(fontObj);
this.embeddedFonts[fontObj.loadedName] = fontObj;
}
2014-08-15 05:11:27 +09:00
current.fontMatrix = (fontObj.fontMatrix ?
fontObj.fontMatrix : FONT_IDENTITY_MATRIX);
2014-05-20 08:53:40 +09:00
const bold = fontObj.black ? (fontObj.bold ? 'bolder' : 'bold') :
2014-05-20 08:53:40 +09:00
(fontObj.bold ? 'bold' : 'normal');
const italic = fontObj.italic ? 'italic' : 'normal';
2014-05-20 08:53:40 +09:00
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 = this.svgFactory.createElement('svg:tspan');
current.tspan.setAttributeNS(null, 'y', pf(-current.y));
current.xcoords = [];
}
2014-05-20 08:53:40 +09:00
endText() {
const current = this.current;
if ((current.textRenderingMode & TextRenderingMode.ADD_TO_PATH_FLAG) &&
current.txtElement && current.txtElement.hasChildNodes()) {
// If no glyphs are shown (i.e. no child nodes), no clipping occurs.
current.element = current.txtElement;
this.clip('nonzero');
this.endPath();
}
}
// Path properties
setLineWidth(width) {
if (width > 0) {
this.current.lineWidth = width;
}
}
setLineCap(style) {
this.current.lineCap = LINE_CAP_STYLES[style];
}
setLineJoin(style) {
this.current.lineJoin = LINE_JOIN_STYLES[style];
}
setMiterLimit(limit) {
this.current.miterLimit = limit;
}
setStrokeAlpha(strokeAlpha) {
this.current.strokeAlpha = strokeAlpha;
}
setStrokeRGBColor(r, g, b) {
this.current.strokeColor = Util.makeCssRgb(r, g, b);
}
setFillAlpha(fillAlpha) {
this.current.fillAlpha = fillAlpha;
}
setFillRGBColor(r, g, b) {
this.current.fillColor = Util.makeCssRgb(r, g, b);
this.current.tspan = this.svgFactory.createElement('svg:tspan');
this.current.xcoords = [];
}
setStrokeColorN(args) {
this.current.strokeColor = this._makeColorN_Pattern(args);
}
setFillColorN(args) {
this.current.fillColor = this._makeColorN_Pattern(args);
}
shadingFill(args) {
const width = this.viewport.width;
const height = this.viewport.height;
const inv = Util.inverseTransform(this.transformMatrix);
const bl = Util.applyTransform([0, 0], inv);
const br = Util.applyTransform([0, height], inv);
const ul = Util.applyTransform([width, 0], inv);
const ur = Util.applyTransform([width, height], inv);
const x0 = Math.min(bl[0], br[0], ul[0], ur[0]);
const y0 = Math.min(bl[1], br[1], ul[1], ur[1]);
const x1 = Math.max(bl[0], br[0], ul[0], ur[0]);
const y1 = Math.max(bl[1], br[1], ul[1], ur[1]);
const rect = this.svgFactory.createElement('svg:rect');
rect.setAttributeNS(null, 'x', x0);
rect.setAttributeNS(null, 'y', y0);
rect.setAttributeNS(null, 'width', x1 - x0);
rect.setAttributeNS(null, 'height', y1 - y0);
rect.setAttributeNS(null, 'fill', this._makeShadingPattern(args));
this._ensureTransformGroup().appendChild(rect);
}
/**
* @private
*/
_makeColorN_Pattern(args) {
if (args[0] === 'TilingPattern') {
warn('Unimplemented pattern TilingPattern');
return null;
}
return this._makeShadingPattern(args);
}
/**
* @private
*/
_makeShadingPattern(args) {
switch (args[0]) {
case 'RadialAxial':
const shadingId = `shading${shadingCount++}`;
const colorStops = args[2];
let gradient;
switch (args[1]) {
case 'axial':
const point0 = args[3];
const point1 = args[4];
gradient = this.svgFactory.createElement('svg:linearGradient');
gradient.setAttributeNS(null, 'id', shadingId);
gradient.setAttributeNS(null, 'gradientUnits', 'userSpaceOnUse');
gradient.setAttributeNS(null, 'x1', point0[0]);
gradient.setAttributeNS(null, 'y1', point0[1]);
gradient.setAttributeNS(null, 'x2', point1[0]);
gradient.setAttributeNS(null, 'y2', point1[1]);
break;
case 'radial':
const focalPoint = args[3];
const circlePoint = args[4];
const focalRadius = args[5];
const circleRadius = args[6];
gradient = this.svgFactory.createElement('svg:radialGradient');
gradient.setAttributeNS(null, 'id', shadingId);
gradient.setAttributeNS(null, 'gradientUnits', 'userSpaceOnUse');
gradient.setAttributeNS(null, 'cx', circlePoint[0]);
gradient.setAttributeNS(null, 'cy', circlePoint[1]);
gradient.setAttributeNS(null, 'r', circleRadius);
gradient.setAttributeNS(null, 'fx', focalPoint[0]);
gradient.setAttributeNS(null, 'fy', focalPoint[1]);
gradient.setAttributeNS(null, 'fr', focalRadius);
2014-05-20 08:53:40 +09:00
break;
default:
throw new Error(`Unknown RadialAxial type: ${args[1]}`);
2014-05-20 08:53:40 +09:00
}
for (const colorStop of colorStops) {
const stop = this.svgFactory.createElement('svg:stop');
stop.setAttributeNS(null, 'offset', colorStop[0]);
stop.setAttributeNS(null, 'stop-color', colorStop[1]);
gradient.appendChild(stop);
}
this.defs.appendChild(gradient);
return `url(#${shadingId})`;
case 'Mesh':
warn('Unimplemented pattern Mesh');
return null;
case 'Dummy':
return 'hotpink';
default:
throw new Error(`Unknown IR type: ${args[0]}`);
}
}
setDash(dashArray, dashPhase) {
this.current.dashArray = dashArray;
this.current.dashPhase = dashPhase;
}
2019-02-27 03:00:35 +09:00
constructPath(ops, args) {
const current = this.current;
let x = current.x, y = current.y;
let d = [];
let j = 0;
for (const op of ops) {
switch (op | 0) {
case OPS.rectangle:
x = args[j++];
y = args[j++];
const width = args[j++];
const height = args[j++];
const xw = x + width;
const 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;
2019-02-27 03:00:35 +09:00
}
}
d = d.join(' ');
if (current.path && ops.length > 0 && ops[0] !== OPS.rectangle &&
ops[0] !== OPS.moveTo) {
// If a path does not start with an OPS.rectangle or OPS.moveTo, it has
// probably been divided into two OPS.constructPath operators by
// OperatorList. Append the commands to the previous path element.
d = current.path.getAttributeNS(null, 'd') + d;
} else {
current.path = this.svgFactory.createElement('svg:path');
this._ensureTransformGroup().appendChild(current.path);
}
current.path.setAttributeNS(null, 'd', d);
current.path.setAttributeNS(null, 'fill', 'none');
// 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() {
const current = this.current;
// Painting operators end a path.
current.path = null;
2019-02-27 03:00:35 +09:00
if (!this.pendingClip) {
return;
}
// Add the current path to a clipping path.
const clipId = `clippath${clipCount++}`;
const clipPath = this.svgFactory.createElement('svg:clipPath');
clipPath.setAttributeNS(null, 'id', clipId);
clipPath.setAttributeNS(null, 'transform', pm(this.transformMatrix));
// A deep clone is needed when text is used as a clipping path.
const clipElement = current.element.cloneNode(true);
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;
});
// Intersect with the previous clipping path.
clipPath.setAttributeNS(null, 'clip-path', current.activeClipUrl);
}
current.activeClipUrl = `url(#${clipId})`;
this.tgrp = null;
}
clip(type) {
this.pendingClip = type;
}
closePath() {
const current = this.current;
if (current.path) {
const d = `${current.path.getAttributeNS(null, 'd')}Z`;
2019-02-27 03:00:35 +09:00
current.path.setAttributeNS(null, 'd', d);
}
}
setLeading(leading) {
this.current.leading = -leading;
}
2014-05-20 08:53:40 +09:00
setTextRise(textRise) {
this.current.textRise = textRise;
}
2019-02-27 03:00:35 +09:00
setTextRenderingMode(textRenderingMode) {
this.current.textRenderingMode = textRenderingMode;
}
setHScale(scale) {
this.current.textHScale = scale / 100;
}
setGState(states) {
for (const [key, value] of states) {
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 operator ${key}`);
break;
2016-10-18 06:09:24 +09:00
}
}
}
2016-10-18 06:09:24 +09:00
fill() {
const current = this.current;
if (current.element) {
current.element.setAttributeNS(null, 'fill', current.fillColor);
current.element.setAttributeNS(null, 'fill-opacity', current.fillAlpha);
this.endPath();
}
}
2014-07-04 00:59:04 +09:00
stroke() {
const current = this.current;
if (current.element) {
this._setStrokeAttributes(current.element);
current.element.setAttributeNS(null, 'fill', 'none');
this.endPath();
}
}
2017-06-19 19:40:48 +09:00
/**
* @private
*/
_setStrokeAttributes(element, lineWidthScale = 1) {
const current = this.current;
let dashArray = current.dashArray;
if (lineWidthScale !== 1 && dashArray.length > 0) {
dashArray = dashArray.map(function(value) {
return lineWidthScale * value;
});
}
element.setAttributeNS(null, 'stroke', current.strokeColor);
element.setAttributeNS(null, 'stroke-opacity', current.strokeAlpha);
element.setAttributeNS(null, 'stroke-miterlimit', pf(current.miterLimit));
element.setAttributeNS(null, 'stroke-linecap', current.lineCap);
element.setAttributeNS(null, 'stroke-linejoin', current.lineJoin);
element.setAttributeNS(null, 'stroke-width',
pf(lineWidthScale * current.lineWidth) + 'px');
element.setAttributeNS(null, 'stroke-dasharray',
dashArray.map(pf).join(' '));
element.setAttributeNS(null, 'stroke-dashoffset',
pf(lineWidthScale * current.dashPhase) + 'px');
}
2014-05-20 08:53:40 +09:00
eoFill() {
if (this.current.element) {
this.current.element.setAttributeNS(null, 'fill-rule', 'evenodd');
}
this.fill();
}
2014-05-20 08:53:40 +09:00
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();
}
2014-05-20 08:53:40 +09:00
eoFillStroke() {
if (this.current.element) {
this.current.element.setAttributeNS(null, 'fill-rule', 'evenodd');
}
this.fillStroke();
}
closeStroke() {
this.closePath();
this.stroke();
}
2014-05-20 08:53:40 +09:00
closeFillStroke() {
this.closePath();
this.fillStroke();
}
2014-05-20 08:53:40 +09:00
closeEOFillStroke() {
this.closePath();
this.eoFillStroke();
}
2014-05-20 08:53:40 +09:00
paintSolidColorImageMask() {
const rect = this.svgFactory.createElement('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', this.current.fillColor);
this._ensureTransformGroup().appendChild(rect);
}
paintJpegXObject(objId, w, h) {
const imgObj = this.objs.get(objId);
const imgEl = this.svgFactory.createElement('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);
}
2018-04-18 01:20:29 +09:00
paintImageXObject(objId) {
const imgData = this.objs.get(objId);
if (!imgData) {
warn(`Dependent image with object ID ${objId} is not ready yet`);
return;
}
this.paintInlineImageXObject(imgData);
}
paintInlineImageXObject(imgData, mask) {
const width = imgData.width;
const height = imgData.height;
const imgSrc = convertImgDataToPng(imgData, this.forceDataSchema, !!mask);
const cliprect = this.svgFactory.createElement('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');
const imgEl = this.svgFactory.createElement('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(imgData) {
const current = this.current;
const width = imgData.width;
const height = imgData.height;
const fillColor = current.fillColor;
current.maskId = `mask${maskCount++}`;
const mask = this.svgFactory.createElement('svg:mask');
mask.setAttributeNS(null, 'id', current.maskId);
const rect = this.svgFactory.createElement('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(matrix, bbox) {
if (Array.isArray(matrix) && matrix.length === 6) {
this.transform(matrix[0], matrix[1], matrix[2],
matrix[3], matrix[4], matrix[5]);
}
if (bbox) {
const width = bbox[2] - bbox[0];
const height = bbox[3] - bbox[1];
const cliprect = this.svgFactory.createElement('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();
}
}
2014-08-15 05:54:38 +09:00
paintFormXObjectEnd() {}
/**
* @private
*/
_initialize(viewport) {
const svg = this.svgFactory.create(viewport.width, viewport.height);
// Create the definitions element.
const definitions = this.svgFactory.createElement('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.
const rootGroup = this.svgFactory.createElement('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() {
if (!this.current.clipGroup) {
const clipGroup = this.svgFactory.createElement('svg:g');
clipGroup.setAttributeNS(null, 'clip-path', this.current.activeClipUrl);
this.svg.appendChild(clipGroup);
this.current.clipGroup = clipGroup;
}
return this.current.clipGroup;
}
/**
* @private
*/
_ensureTransformGroup() {
if (!this.tgrp) {
this.tgrp = this.svgFactory.createElement('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;
}
};
}
export {
SVGGraphics,
};