pdf.js/src/display/svg.js
Rob Wu fc6448d18c Move svg:clipPath generation from clip to endPath
In the PDF from issue 8527, the clip operator (W) shows up before a path
is defined. The current SVG backend however expects a path to exist
before generating a `<svg:clipPath>` element.
In the example, the path was defined after the clip, followed by a
endPath operator (n).
So this commit fixes the bug by moving the path generation logic from
clip to endPath.

Our canvas backend appears to use similar logic:
`CanvasGraphics_endPath` calls `consumePath`, which in turn draws the
clip and resets the `pendingClip` state. The canvas backend calls
`consumePath` from multiple other places, so we probably need to check
whether doing so is also necessary for the SVG backend.

I scanned our corpus of PDF files in test/pdfs, and found that in every
instance (except for one), the "W" PDF operator (clip) is immediately
followed by "n" (endPath). The new test from this commit (clippath.pdf)
starts with "W", followed by a path definition and then "n".

    # Commands used to find some of the clipping commands:
    grep -ra '^W$' -C7 | less -S
    grep -ra '^W ' -C7 | less -S
    grep -ra ' W$' -C7 | less -S

test/pdfs/issue6413.pdf is the only file where "W" (a tline 55) is not
followed by "n". In fact, the "W" is the last operation of a series of
XObject painting operations, and removing it does not have any effect
on the rendered PDF (confirmed by looking at the output of PDF.js's
canvas backend, and ImageMagick's convert command).
2017-06-22 01:08:17 +02:00

1212 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;
},
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() {
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;
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', 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,
};