UNPKG

pdfjs-dist

Version:

Generic build of Mozilla's PDF.js library.

1,480 lines (1,479 loc) • 76.6 kB
/** * @licstart The following is the entire license notice for the * JavaScript code in this page * * Copyright 2022 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. * * @licend The above is the entire license notice for the * JavaScript code in this page */ "use strict"; Object.defineProperty(exports, "__esModule", { value: true }); exports.CanvasGraphics = void 0; var _util = require("../shared/util.js"); var _display_utils = require("./display_utils.js"); var _pattern_helper = require("./pattern_helper.js"); var _image_utils = require("../shared/image_utils.js"); var _is_node = require("../shared/is_node.js"); const MIN_FONT_SIZE = 16; const MAX_FONT_SIZE = 100; const MAX_GROUP_SIZE = 4096; const EXECUTION_TIME = 15; const EXECUTION_STEPS = 10; const MAX_SIZE_TO_COMPILE = _is_node.isNodeJS && typeof Path2D === "undefined" ? -1 : 1000; const FULL_CHUNK_HEIGHT = 16; function mirrorContextOperations(ctx, destCtx) { if (ctx._removeMirroring) { throw new Error("Context is already forwarding operations."); } ctx.__originalSave = ctx.save; ctx.__originalRestore = ctx.restore; ctx.__originalRotate = ctx.rotate; ctx.__originalScale = ctx.scale; ctx.__originalTranslate = ctx.translate; ctx.__originalTransform = ctx.transform; ctx.__originalSetTransform = ctx.setTransform; ctx.__originalResetTransform = ctx.resetTransform; ctx.__originalClip = ctx.clip; ctx.__originalMoveTo = ctx.moveTo; ctx.__originalLineTo = ctx.lineTo; ctx.__originalBezierCurveTo = ctx.bezierCurveTo; ctx.__originalRect = ctx.rect; ctx.__originalClosePath = ctx.closePath; ctx.__originalBeginPath = ctx.beginPath; ctx._removeMirroring = () => { ctx.save = ctx.__originalSave; ctx.restore = ctx.__originalRestore; ctx.rotate = ctx.__originalRotate; ctx.scale = ctx.__originalScale; ctx.translate = ctx.__originalTranslate; ctx.transform = ctx.__originalTransform; ctx.setTransform = ctx.__originalSetTransform; ctx.resetTransform = ctx.__originalResetTransform; ctx.clip = ctx.__originalClip; ctx.moveTo = ctx.__originalMoveTo; ctx.lineTo = ctx.__originalLineTo; ctx.bezierCurveTo = ctx.__originalBezierCurveTo; ctx.rect = ctx.__originalRect; ctx.closePath = ctx.__originalClosePath; ctx.beginPath = ctx.__originalBeginPath; delete ctx._removeMirroring; }; ctx.save = function ctxSave() { destCtx.save(); this.__originalSave(); }; ctx.restore = function ctxRestore() { destCtx.restore(); this.__originalRestore(); }; ctx.translate = function ctxTranslate(x, y) { destCtx.translate(x, y); this.__originalTranslate(x, y); }; ctx.scale = function ctxScale(x, y) { destCtx.scale(x, y); this.__originalScale(x, y); }; ctx.transform = function ctxTransform(a, b, c, d, e, f) { destCtx.transform(a, b, c, d, e, f); this.__originalTransform(a, b, c, d, e, f); }; ctx.setTransform = function ctxSetTransform(a, b, c, d, e, f) { destCtx.setTransform(a, b, c, d, e, f); this.__originalSetTransform(a, b, c, d, e, f); }; ctx.resetTransform = function ctxResetTransform() { destCtx.resetTransform(); this.__originalResetTransform(); }; ctx.rotate = function ctxRotate(angle) { destCtx.rotate(angle); this.__originalRotate(angle); }; ctx.clip = function ctxRotate(rule) { destCtx.clip(rule); this.__originalClip(rule); }; ctx.moveTo = function (x, y) { destCtx.moveTo(x, y); this.__originalMoveTo(x, y); }; ctx.lineTo = function (x, y) { destCtx.lineTo(x, y); this.__originalLineTo(x, y); }; ctx.bezierCurveTo = function (cp1x, cp1y, cp2x, cp2y, x, y) { destCtx.bezierCurveTo(cp1x, cp1y, cp2x, cp2y, x, y); this.__originalBezierCurveTo(cp1x, cp1y, cp2x, cp2y, x, y); }; ctx.rect = function (x, y, width, height) { destCtx.rect(x, y, width, height); this.__originalRect(x, y, width, height); }; ctx.closePath = function () { destCtx.closePath(); this.__originalClosePath(); }; ctx.beginPath = function () { destCtx.beginPath(); this.__originalBeginPath(); }; } class CachedCanvases { constructor(canvasFactory) { this.canvasFactory = canvasFactory; this.cache = Object.create(null); } getCanvas(id, width, height) { let canvasEntry; if (this.cache[id] !== undefined) { canvasEntry = this.cache[id]; this.canvasFactory.reset(canvasEntry, width, height); } else { canvasEntry = this.canvasFactory.create(width, height); this.cache[id] = canvasEntry; } return canvasEntry; } delete(id) { delete this.cache[id]; } clear() { for (const id in this.cache) { const canvasEntry = this.cache[id]; this.canvasFactory.destroy(canvasEntry); delete this.cache[id]; } } } function drawImageAtIntegerCoords(ctx, srcImg, srcX, srcY, srcW, srcH, destX, destY, destW, destH) { const [a, b, c, d, tx, ty] = (0, _display_utils.getCurrentTransform)(ctx); if (b === 0 && c === 0) { const tlX = destX * a + tx; const rTlX = Math.round(tlX); const tlY = destY * d + ty; const rTlY = Math.round(tlY); const brX = (destX + destW) * a + tx; const rWidth = Math.abs(Math.round(brX) - rTlX) || 1; const brY = (destY + destH) * d + ty; const rHeight = Math.abs(Math.round(brY) - rTlY) || 1; ctx.setTransform(Math.sign(a), 0, 0, Math.sign(d), rTlX, rTlY); ctx.drawImage(srcImg, srcX, srcY, srcW, srcH, 0, 0, rWidth, rHeight); ctx.setTransform(a, b, c, d, tx, ty); return [rWidth, rHeight]; } if (a === 0 && d === 0) { const tlX = destY * c + tx; const rTlX = Math.round(tlX); const tlY = destX * b + ty; const rTlY = Math.round(tlY); const brX = (destY + destH) * c + tx; const rWidth = Math.abs(Math.round(brX) - rTlX) || 1; const brY = (destX + destW) * b + ty; const rHeight = Math.abs(Math.round(brY) - rTlY) || 1; ctx.setTransform(0, Math.sign(b), Math.sign(c), 0, rTlX, rTlY); ctx.drawImage(srcImg, srcX, srcY, srcW, srcH, 0, 0, rHeight, rWidth); ctx.setTransform(a, b, c, d, tx, ty); return [rHeight, rWidth]; } ctx.drawImage(srcImg, srcX, srcY, srcW, srcH, destX, destY, destW, destH); const scaleX = Math.hypot(a, b); const scaleY = Math.hypot(c, d); return [scaleX * destW, scaleY * destH]; } function compileType3Glyph(imgData) { const { width, height } = imgData; if (width > MAX_SIZE_TO_COMPILE || height > MAX_SIZE_TO_COMPILE) { return null; } const POINT_TO_PROCESS_LIMIT = 1000; const POINT_TYPES = new Uint8Array([0, 2, 4, 0, 1, 0, 5, 4, 8, 10, 0, 8, 0, 2, 1, 0]); const width1 = width + 1; let points = new Uint8Array(width1 * (height + 1)); let i, j, j0; const lineSize = width + 7 & ~7; let data = new Uint8Array(lineSize * height), pos = 0; for (const elem of imgData.data) { let mask = 128; while (mask > 0) { data[pos++] = elem & mask ? 0 : 255; mask >>= 1; } } let count = 0; pos = 0; if (data[pos] !== 0) { points[0] = 1; ++count; } for (j = 1; j < width; j++) { if (data[pos] !== data[pos + 1]) { points[j] = data[pos] ? 2 : 1; ++count; } pos++; } if (data[pos] !== 0) { points[j] = 2; ++count; } for (i = 1; i < height; i++) { pos = i * lineSize; j0 = i * width1; if (data[pos - lineSize] !== data[pos]) { points[j0] = data[pos] ? 1 : 8; ++count; } let sum = (data[pos] ? 4 : 0) + (data[pos - lineSize] ? 8 : 0); for (j = 1; j < width; j++) { sum = (sum >> 2) + (data[pos + 1] ? 4 : 0) + (data[pos - lineSize + 1] ? 8 : 0); if (POINT_TYPES[sum]) { points[j0 + j] = POINT_TYPES[sum]; ++count; } pos++; } if (data[pos - lineSize] !== data[pos]) { points[j0 + j] = data[pos] ? 2 : 4; ++count; } if (count > POINT_TO_PROCESS_LIMIT) { return null; } } pos = lineSize * (height - 1); j0 = i * width1; if (data[pos] !== 0) { points[j0] = 8; ++count; } for (j = 1; j < width; j++) { if (data[pos] !== data[pos + 1]) { points[j0 + j] = data[pos] ? 4 : 8; ++count; } pos++; } if (data[pos] !== 0) { points[j0 + j] = 4; ++count; } if (count > POINT_TO_PROCESS_LIMIT) { return null; } const steps = new Int32Array([0, width1, -1, 0, -width1, 0, 0, 0, 1]); const path = new Path2D(); for (i = 0; count && i <= height; i++) { let p = i * width1; const end = p + width; while (p < end && !points[p]) { p++; } if (p === end) { continue; } path.moveTo(p % width1, i); const p0 = p; let type = points[p]; do { const step = steps[type]; do { p += step; } while (!points[p]); const pp = points[p]; if (pp !== 5 && pp !== 10) { type = pp; points[p] = 0; } else { type = pp & 0x33 * type >> 4; points[p] &= type >> 2 | type << 2; } path.lineTo(p % width1, p / width1 | 0); if (!points[p]) { --count; } } while (p0 !== p); --i; } data = null; points = null; const drawOutline = function (c) { c.save(); c.scale(1 / width, -1 / height); c.translate(0, -height); c.fill(path); c.beginPath(); c.restore(); }; return drawOutline; } class CanvasExtraState { constructor(width, height) { this.alphaIsShape = false; this.fontSize = 0; this.fontSizeScale = 1; this.textMatrix = _util.IDENTITY_MATRIX; this.textMatrixScale = 1; this.fontMatrix = _util.FONT_IDENTITY_MATRIX; this.leading = 0; this.x = 0; this.y = 0; this.lineX = 0; this.lineY = 0; this.charSpacing = 0; this.wordSpacing = 0; this.textHScale = 1; this.textRenderingMode = _util.TextRenderingMode.FILL; this.textRise = 0; this.fillColor = "#000000"; this.strokeColor = "#000000"; this.patternFill = false; this.fillAlpha = 1; this.strokeAlpha = 1; this.lineWidth = 1; this.activeSMask = null; this.transferMaps = null; this.startNewPathAndClipBox([0, 0, width, height]); } clone() { const clone = Object.create(this); clone.clipBox = this.clipBox.slice(); return clone; } setCurrentPoint(x, y) { this.x = x; this.y = y; } updatePathMinMax(transform, x, y) { [x, y] = _util.Util.applyTransform([x, y], transform); this.minX = Math.min(this.minX, x); this.minY = Math.min(this.minY, y); this.maxX = Math.max(this.maxX, x); this.maxY = Math.max(this.maxY, y); } updateRectMinMax(transform, rect) { const p1 = _util.Util.applyTransform(rect, transform); const p2 = _util.Util.applyTransform(rect.slice(2), transform); this.minX = Math.min(this.minX, p1[0], p2[0]); this.minY = Math.min(this.minY, p1[1], p2[1]); this.maxX = Math.max(this.maxX, p1[0], p2[0]); this.maxY = Math.max(this.maxY, p1[1], p2[1]); } updateScalingPathMinMax(transform, minMax) { _util.Util.scaleMinMax(transform, minMax); this.minX = Math.min(this.minX, minMax[0]); this.maxX = Math.max(this.maxX, minMax[1]); this.minY = Math.min(this.minY, minMax[2]); this.maxY = Math.max(this.maxY, minMax[3]); } updateCurvePathMinMax(transform, x0, y0, x1, y1, x2, y2, x3, y3, minMax) { const box = _util.Util.bezierBoundingBox(x0, y0, x1, y1, x2, y2, x3, y3); if (minMax) { minMax[0] = Math.min(minMax[0], box[0], box[2]); minMax[1] = Math.max(minMax[1], box[0], box[2]); minMax[2] = Math.min(minMax[2], box[1], box[3]); minMax[3] = Math.max(minMax[3], box[1], box[3]); return; } this.updateRectMinMax(transform, box); } getPathBoundingBox(pathType = _pattern_helper.PathType.FILL, transform = null) { const box = [this.minX, this.minY, this.maxX, this.maxY]; if (pathType === _pattern_helper.PathType.STROKE) { if (!transform) { (0, _util.unreachable)("Stroke bounding box must include transform."); } const scale = _util.Util.singularValueDecompose2dScale(transform); const xStrokePad = scale[0] * this.lineWidth / 2; const yStrokePad = scale[1] * this.lineWidth / 2; box[0] -= xStrokePad; box[1] -= yStrokePad; box[2] += xStrokePad; box[3] += yStrokePad; } return box; } updateClipFromPath() { const intersect = _util.Util.intersect(this.clipBox, this.getPathBoundingBox()); this.startNewPathAndClipBox(intersect || [0, 0, 0, 0]); } isEmptyClip() { return this.minX === Infinity; } startNewPathAndClipBox(box) { this.clipBox = box; this.minX = Infinity; this.minY = Infinity; this.maxX = 0; this.maxY = 0; } getClippedPathBoundingBox(pathType = _pattern_helper.PathType.FILL, transform = null) { return _util.Util.intersect(this.clipBox, this.getPathBoundingBox(pathType, transform)); } } function putBinaryImageData(ctx, imgData, transferMaps = null) { if (typeof ImageData !== "undefined" && imgData instanceof ImageData) { ctx.putImageData(imgData, 0, 0); return; } const height = imgData.height, width = imgData.width; const partialChunkHeight = height % FULL_CHUNK_HEIGHT; const fullChunks = (height - partialChunkHeight) / FULL_CHUNK_HEIGHT; const totalChunks = partialChunkHeight === 0 ? fullChunks : fullChunks + 1; const chunkImgData = ctx.createImageData(width, FULL_CHUNK_HEIGHT); let srcPos = 0, destPos; const src = imgData.data; const dest = chunkImgData.data; let i, j, thisChunkHeight, elemsInThisChunk; let transferMapRed, transferMapGreen, transferMapBlue, transferMapGray; if (transferMaps) { switch (transferMaps.length) { case 1: transferMapRed = transferMaps[0]; transferMapGreen = transferMaps[0]; transferMapBlue = transferMaps[0]; transferMapGray = transferMaps[0]; break; case 4: transferMapRed = transferMaps[0]; transferMapGreen = transferMaps[1]; transferMapBlue = transferMaps[2]; transferMapGray = transferMaps[3]; break; } } if (imgData.kind === _util.ImageKind.GRAYSCALE_1BPP) { const srcLength = src.byteLength; const dest32 = new Uint32Array(dest.buffer, 0, dest.byteLength >> 2); const dest32DataLength = dest32.length; const fullSrcDiff = width + 7 >> 3; let white = 0xffffffff; let black = _util.FeatureTest.isLittleEndian ? 0xff000000 : 0x000000ff; if (transferMapGray) { if (transferMapGray[0] === 0xff && transferMapGray[0xff] === 0) { [white, black] = [black, white]; } } for (i = 0; i < totalChunks; i++) { thisChunkHeight = i < fullChunks ? FULL_CHUNK_HEIGHT : partialChunkHeight; destPos = 0; for (j = 0; j < thisChunkHeight; j++) { const srcDiff = srcLength - srcPos; let k = 0; const kEnd = srcDiff > fullSrcDiff ? width : srcDiff * 8 - 7; const kEndUnrolled = kEnd & ~7; let mask = 0; let srcByte = 0; for (; k < kEndUnrolled; k += 8) { srcByte = src[srcPos++]; dest32[destPos++] = srcByte & 128 ? white : black; dest32[destPos++] = srcByte & 64 ? white : black; dest32[destPos++] = srcByte & 32 ? white : black; dest32[destPos++] = srcByte & 16 ? white : black; dest32[destPos++] = srcByte & 8 ? white : black; dest32[destPos++] = srcByte & 4 ? white : black; dest32[destPos++] = srcByte & 2 ? white : black; dest32[destPos++] = srcByte & 1 ? white : black; } for (; k < kEnd; k++) { if (mask === 0) { srcByte = src[srcPos++]; mask = 128; } dest32[destPos++] = srcByte & mask ? white : black; mask >>= 1; } } while (destPos < dest32DataLength) { dest32[destPos++] = 0; } ctx.putImageData(chunkImgData, 0, i * FULL_CHUNK_HEIGHT); } } else if (imgData.kind === _util.ImageKind.RGBA_32BPP) { const hasTransferMaps = !!(transferMapRed || transferMapGreen || transferMapBlue); j = 0; elemsInThisChunk = width * FULL_CHUNK_HEIGHT * 4; for (i = 0; i < fullChunks; i++) { dest.set(src.subarray(srcPos, srcPos + elemsInThisChunk)); srcPos += elemsInThisChunk; if (hasTransferMaps) { for (let k = 0; k < elemsInThisChunk; k += 4) { if (transferMapRed) { dest[k + 0] = transferMapRed[dest[k + 0]]; } if (transferMapGreen) { dest[k + 1] = transferMapGreen[dest[k + 1]]; } if (transferMapBlue) { dest[k + 2] = transferMapBlue[dest[k + 2]]; } } } ctx.putImageData(chunkImgData, 0, j); j += FULL_CHUNK_HEIGHT; } if (i < totalChunks) { elemsInThisChunk = width * partialChunkHeight * 4; dest.set(src.subarray(srcPos, srcPos + elemsInThisChunk)); if (hasTransferMaps) { for (let k = 0; k < elemsInThisChunk; k += 4) { if (transferMapRed) { dest[k + 0] = transferMapRed[dest[k + 0]]; } if (transferMapGreen) { dest[k + 1] = transferMapGreen[dest[k + 1]]; } if (transferMapBlue) { dest[k + 2] = transferMapBlue[dest[k + 2]]; } } } ctx.putImageData(chunkImgData, 0, j); } } else if (imgData.kind === _util.ImageKind.RGB_24BPP) { const hasTransferMaps = !!(transferMapRed || transferMapGreen || transferMapBlue); thisChunkHeight = FULL_CHUNK_HEIGHT; elemsInThisChunk = width * thisChunkHeight; for (i = 0; i < totalChunks; i++) { if (i >= fullChunks) { thisChunkHeight = partialChunkHeight; elemsInThisChunk = width * thisChunkHeight; } destPos = 0; for (j = elemsInThisChunk; j--;) { dest[destPos++] = src[srcPos++]; dest[destPos++] = src[srcPos++]; dest[destPos++] = src[srcPos++]; dest[destPos++] = 255; } if (hasTransferMaps) { for (let k = 0; k < destPos; k += 4) { if (transferMapRed) { dest[k + 0] = transferMapRed[dest[k + 0]]; } if (transferMapGreen) { dest[k + 1] = transferMapGreen[dest[k + 1]]; } if (transferMapBlue) { dest[k + 2] = transferMapBlue[dest[k + 2]]; } } } ctx.putImageData(chunkImgData, 0, i * FULL_CHUNK_HEIGHT); } } else { throw new Error(`bad image kind: ${imgData.kind}`); } } function putBinaryImageMask(ctx, imgData) { if (imgData.bitmap) { ctx.drawImage(imgData.bitmap, 0, 0); return; } const height = imgData.height, width = imgData.width; const partialChunkHeight = height % FULL_CHUNK_HEIGHT; const fullChunks = (height - partialChunkHeight) / FULL_CHUNK_HEIGHT; const totalChunks = partialChunkHeight === 0 ? fullChunks : fullChunks + 1; const chunkImgData = ctx.createImageData(width, FULL_CHUNK_HEIGHT); let srcPos = 0; const src = imgData.data; const dest = chunkImgData.data; for (let i = 0; i < totalChunks; i++) { const thisChunkHeight = i < fullChunks ? FULL_CHUNK_HEIGHT : partialChunkHeight; ({ srcPos } = (0, _image_utils.applyMaskImageData)({ src, srcPos, dest, width, height: thisChunkHeight })); ctx.putImageData(chunkImgData, 0, i * FULL_CHUNK_HEIGHT); } } function copyCtxState(sourceCtx, destCtx) { const properties = ["strokeStyle", "fillStyle", "fillRule", "globalAlpha", "lineWidth", "lineCap", "lineJoin", "miterLimit", "globalCompositeOperation", "font"]; for (const property of properties) { if (sourceCtx[property] !== undefined) { destCtx[property] = sourceCtx[property]; } } if (sourceCtx.setLineDash !== undefined) { destCtx.setLineDash(sourceCtx.getLineDash()); destCtx.lineDashOffset = sourceCtx.lineDashOffset; } } function resetCtxToDefault(ctx, foregroundColor) { ctx.strokeStyle = ctx.fillStyle = foregroundColor || "#000000"; ctx.fillRule = "nonzero"; ctx.globalAlpha = 1; ctx.lineWidth = 1; ctx.lineCap = "butt"; ctx.lineJoin = "miter"; ctx.miterLimit = 10; ctx.globalCompositeOperation = "source-over"; ctx.font = "10px sans-serif"; if (ctx.setLineDash !== undefined) { ctx.setLineDash([]); ctx.lineDashOffset = 0; } } function composeSMaskBackdrop(bytes, r0, g0, b0) { const length = bytes.length; for (let i = 3; i < length; i += 4) { const alpha = bytes[i]; if (alpha === 0) { bytes[i - 3] = r0; bytes[i - 2] = g0; bytes[i - 1] = b0; } else if (alpha < 255) { const alpha_ = 255 - alpha; bytes[i - 3] = bytes[i - 3] * alpha + r0 * alpha_ >> 8; bytes[i - 2] = bytes[i - 2] * alpha + g0 * alpha_ >> 8; bytes[i - 1] = bytes[i - 1] * alpha + b0 * alpha_ >> 8; } } } function composeSMaskAlpha(maskData, layerData, transferMap) { const length = maskData.length; const scale = 1 / 255; for (let i = 3; i < length; i += 4) { const alpha = transferMap ? transferMap[maskData[i]] : maskData[i]; layerData[i] = layerData[i] * alpha * scale | 0; } } function composeSMaskLuminosity(maskData, layerData, transferMap) { const length = maskData.length; for (let i = 3; i < length; i += 4) { const y = maskData[i - 3] * 77 + maskData[i - 2] * 152 + maskData[i - 1] * 28; layerData[i] = transferMap ? layerData[i] * transferMap[y >> 8] >> 8 : layerData[i] * y >> 16; } } function genericComposeSMask(maskCtx, layerCtx, width, height, subtype, backdrop, transferMap, layerOffsetX, layerOffsetY, maskOffsetX, maskOffsetY) { const hasBackdrop = !!backdrop; const r0 = hasBackdrop ? backdrop[0] : 0; const g0 = hasBackdrop ? backdrop[1] : 0; const b0 = hasBackdrop ? backdrop[2] : 0; let composeFn; if (subtype === "Luminosity") { composeFn = composeSMaskLuminosity; } else { composeFn = composeSMaskAlpha; } const PIXELS_TO_PROCESS = 1048576; const chunkSize = Math.min(height, Math.ceil(PIXELS_TO_PROCESS / width)); for (let row = 0; row < height; row += chunkSize) { const chunkHeight = Math.min(chunkSize, height - row); const maskData = maskCtx.getImageData(layerOffsetX - maskOffsetX, row + (layerOffsetY - maskOffsetY), width, chunkHeight); const layerData = layerCtx.getImageData(layerOffsetX, row + layerOffsetY, width, chunkHeight); if (hasBackdrop) { composeSMaskBackdrop(maskData.data, r0, g0, b0); } composeFn(maskData.data, layerData.data, transferMap); layerCtx.putImageData(layerData, layerOffsetX, row + layerOffsetY); } } function composeSMask(ctx, smask, layerCtx, layerBox) { const layerOffsetX = layerBox[0]; const layerOffsetY = layerBox[1]; const layerWidth = layerBox[2] - layerOffsetX; const layerHeight = layerBox[3] - layerOffsetY; if (layerWidth === 0 || layerHeight === 0) { return; } genericComposeSMask(smask.context, layerCtx, layerWidth, layerHeight, smask.subtype, smask.backdrop, smask.transferMap, layerOffsetX, layerOffsetY, smask.offsetX, smask.offsetY); ctx.save(); ctx.globalAlpha = 1; ctx.globalCompositeOperation = "source-over"; ctx.setTransform(1, 0, 0, 1, 0, 0); ctx.drawImage(layerCtx.canvas, 0, 0); ctx.restore(); } function getImageSmoothingEnabled(transform, interpolate) { const scale = _util.Util.singularValueDecompose2dScale(transform); scale[0] = Math.fround(scale[0]); scale[1] = Math.fround(scale[1]); const actualScale = Math.fround((globalThis.devicePixelRatio || 1) * _display_utils.PixelsPerInch.PDF_TO_CSS_UNITS); if (interpolate !== undefined) { return interpolate; } else if (scale[0] <= actualScale || scale[1] <= actualScale) { return true; } return false; } const LINE_CAP_STYLES = ["butt", "round", "square"]; const LINE_JOIN_STYLES = ["miter", "round", "bevel"]; const NORMAL_CLIP = {}; const EO_CLIP = {}; class CanvasGraphics { constructor(canvasCtx, commonObjs, objs, canvasFactory, { optionalContentConfig, markedContentStack = null }, annotationCanvasMap, pageColors) { this.ctx = canvasCtx; this.current = new CanvasExtraState(this.ctx.canvas.width, this.ctx.canvas.height); this.stateStack = []; this.pendingClip = null; this.pendingEOFill = false; this.res = null; this.xobjs = null; this.commonObjs = commonObjs; this.objs = objs; this.canvasFactory = canvasFactory; this.groupStack = []; this.processingType3 = null; this.baseTransform = null; this.baseTransformStack = []; this.groupLevel = 0; this.smaskStack = []; this.smaskCounter = 0; this.tempSMask = null; this.suspendedCtx = null; this.contentVisible = true; this.markedContentStack = markedContentStack || []; this.optionalContentConfig = optionalContentConfig; this.cachedCanvases = new CachedCanvases(this.canvasFactory); this.cachedPatterns = new Map(); this.annotationCanvasMap = annotationCanvasMap; this.viewportScale = 1; this.outputScaleX = 1; this.outputScaleY = 1; this.backgroundColor = pageColors?.background || null; this.foregroundColor = pageColors?.foreground || null; this._cachedScaleForStroking = null; this._cachedGetSinglePixelWidth = null; this._cachedBitmapsMap = new Map(); } getObject(data, fallback = null) { if (typeof data === "string") { return data.startsWith("g_") ? this.commonObjs.get(data) : this.objs.get(data); } return fallback; } beginDrawing({ transform, viewport, transparency = false, background = null }) { const width = this.ctx.canvas.width; const height = this.ctx.canvas.height; const defaultBackgroundColor = background || "#ffffff"; this.ctx.save(); if (this.foregroundColor && this.backgroundColor) { this.ctx.fillStyle = this.foregroundColor; const fg = this.foregroundColor = this.ctx.fillStyle; this.ctx.fillStyle = this.backgroundColor; const bg = this.backgroundColor = this.ctx.fillStyle; let isValidDefaultBg = true; let defaultBg = defaultBackgroundColor; this.ctx.fillStyle = defaultBackgroundColor; defaultBg = this.ctx.fillStyle; isValidDefaultBg = typeof defaultBg === "string" && /^#[0-9A-Fa-f]{6}$/.test(defaultBg); if (fg === "#000000" && bg === "#ffffff" || fg === bg || !isValidDefaultBg) { this.foregroundColor = this.backgroundColor = null; } else { const [rB, gB, bB] = (0, _display_utils.getRGB)(defaultBg); const newComp = x => { x /= 255; return x <= 0.03928 ? x / 12.92 : ((x + 0.055) / 1.055) ** 2.4; }; const lumB = Math.round(0.2126 * newComp(rB) + 0.7152 * newComp(gB) + 0.0722 * newComp(bB)); this.selectColor = (r, g, b) => { const lumC = 0.2126 * newComp(r) + 0.7152 * newComp(g) + 0.0722 * newComp(b); return Math.round(lumC) === lumB ? bg : fg; }; } } this.ctx.fillStyle = this.backgroundColor || defaultBackgroundColor; this.ctx.fillRect(0, 0, width, height); this.ctx.restore(); if (transparency) { const transparentCanvas = this.cachedCanvases.getCanvas("transparent", width, height); this.compositeCtx = this.ctx; this.transparentCanvas = transparentCanvas.canvas; this.ctx = transparentCanvas.context; this.ctx.save(); this.ctx.transform(...(0, _display_utils.getCurrentTransform)(this.compositeCtx)); } this.ctx.save(); resetCtxToDefault(this.ctx, this.foregroundColor); if (transform) { this.ctx.transform(...transform); this.outputScaleX = transform[0]; this.outputScaleY = transform[0]; } this.ctx.transform(...viewport.transform); this.viewportScale = viewport.scale; this.baseTransform = (0, _display_utils.getCurrentTransform)(this.ctx); } executeOperatorList(operatorList, executionStartIdx, continueCallback, stepper) { const argsArray = operatorList.argsArray; const fnArray = operatorList.fnArray; let i = executionStartIdx || 0; const argsArrayLen = argsArray.length; if (argsArrayLen === i) { return i; } const chunkOperations = argsArrayLen - i > EXECUTION_STEPS && typeof continueCallback === "function"; const endTime = chunkOperations ? Date.now() + EXECUTION_TIME : 0; let steps = 0; const commonObjs = this.commonObjs; const objs = this.objs; let fnId; while (true) { if (stepper !== undefined && i === stepper.nextBreakPoint) { stepper.breakIt(i, continueCallback); return i; } fnId = fnArray[i]; if (fnId !== _util.OPS.dependency) { this[fnId].apply(this, argsArray[i]); } else { for (const depObjId of argsArray[i]) { const objsPool = depObjId.startsWith("g_") ? commonObjs : objs; if (!objsPool.has(depObjId)) { objsPool.get(depObjId, continueCallback); return i; } } } i++; if (i === argsArrayLen) { return i; } if (chunkOperations && ++steps > EXECUTION_STEPS) { if (Date.now() > endTime) { continueCallback(); return i; } steps = 0; } } } #restoreInitialState() { while (this.stateStack.length || this.inSMaskMode) { this.restore(); } this.ctx.restore(); if (this.transparentCanvas) { this.ctx = this.compositeCtx; this.ctx.save(); this.ctx.setTransform(1, 0, 0, 1, 0, 0); this.ctx.drawImage(this.transparentCanvas, 0, 0); this.ctx.restore(); this.transparentCanvas = null; } } endDrawing() { this.#restoreInitialState(); this.cachedCanvases.clear(); this.cachedPatterns.clear(); for (const cache of this._cachedBitmapsMap.values()) { for (const canvas of cache.values()) { if (typeof HTMLCanvasElement !== "undefined" && canvas instanceof HTMLCanvasElement) { canvas.width = canvas.height = 0; } } cache.clear(); } this._cachedBitmapsMap.clear(); } _scaleImage(img, inverseTransform) { const width = img.width; const height = img.height; let widthScale = Math.max(Math.hypot(inverseTransform[0], inverseTransform[1]), 1); let heightScale = Math.max(Math.hypot(inverseTransform[2], inverseTransform[3]), 1); let paintWidth = width, paintHeight = height; let tmpCanvasId = "prescale1"; let tmpCanvas, tmpCtx; while (widthScale > 2 && paintWidth > 1 || heightScale > 2 && paintHeight > 1) { let newWidth = paintWidth, newHeight = paintHeight; if (widthScale > 2 && paintWidth > 1) { newWidth = Math.ceil(paintWidth / 2); widthScale /= paintWidth / newWidth; } if (heightScale > 2 && paintHeight > 1) { newHeight = Math.ceil(paintHeight / 2); heightScale /= paintHeight / newHeight; } tmpCanvas = this.cachedCanvases.getCanvas(tmpCanvasId, newWidth, newHeight); tmpCtx = tmpCanvas.context; tmpCtx.clearRect(0, 0, newWidth, newHeight); tmpCtx.drawImage(img, 0, 0, paintWidth, paintHeight, 0, 0, newWidth, newHeight); img = tmpCanvas.canvas; paintWidth = newWidth; paintHeight = newHeight; tmpCanvasId = tmpCanvasId === "prescale1" ? "prescale2" : "prescale1"; } return { img, paintWidth, paintHeight }; } _createMaskCanvas(img) { const ctx = this.ctx; const { width, height } = img; const fillColor = this.current.fillColor; const isPatternFill = this.current.patternFill; const currentTransform = (0, _display_utils.getCurrentTransform)(ctx); let cache, cacheKey, scaled, maskCanvas; if ((img.bitmap || img.data) && img.count > 1) { const mainKey = img.bitmap || img.data.buffer; cacheKey = JSON.stringify(isPatternFill ? currentTransform : [currentTransform.slice(0, 4), fillColor]); cache = this._cachedBitmapsMap.get(mainKey); if (!cache) { cache = new Map(); this._cachedBitmapsMap.set(mainKey, cache); } const cachedImage = cache.get(cacheKey); if (cachedImage && !isPatternFill) { const offsetX = Math.round(Math.min(currentTransform[0], currentTransform[2]) + currentTransform[4]); const offsetY = Math.round(Math.min(currentTransform[1], currentTransform[3]) + currentTransform[5]); return { canvas: cachedImage, offsetX, offsetY }; } scaled = cachedImage; } if (!scaled) { maskCanvas = this.cachedCanvases.getCanvas("maskCanvas", width, height); putBinaryImageMask(maskCanvas.context, img); } let maskToCanvas = _util.Util.transform(currentTransform, [1 / width, 0, 0, -1 / height, 0, 0]); maskToCanvas = _util.Util.transform(maskToCanvas, [1, 0, 0, 1, 0, -height]); const cord1 = _util.Util.applyTransform([0, 0], maskToCanvas); const cord2 = _util.Util.applyTransform([width, height], maskToCanvas); const rect = _util.Util.normalizeRect([cord1[0], cord1[1], cord2[0], cord2[1]]); const drawnWidth = Math.round(rect[2] - rect[0]) || 1; const drawnHeight = Math.round(rect[3] - rect[1]) || 1; const fillCanvas = this.cachedCanvases.getCanvas("fillCanvas", drawnWidth, drawnHeight); const fillCtx = fillCanvas.context; const offsetX = Math.min(cord1[0], cord2[0]); const offsetY = Math.min(cord1[1], cord2[1]); fillCtx.translate(-offsetX, -offsetY); fillCtx.transform(...maskToCanvas); if (!scaled) { scaled = this._scaleImage(maskCanvas.canvas, (0, _display_utils.getCurrentTransformInverse)(fillCtx)); scaled = scaled.img; if (cache && isPatternFill) { cache.set(cacheKey, scaled); } } fillCtx.imageSmoothingEnabled = getImageSmoothingEnabled((0, _display_utils.getCurrentTransform)(fillCtx), img.interpolate); drawImageAtIntegerCoords(fillCtx, scaled, 0, 0, scaled.width, scaled.height, 0, 0, width, height); fillCtx.globalCompositeOperation = "source-in"; const inverse = _util.Util.transform((0, _display_utils.getCurrentTransformInverse)(fillCtx), [1, 0, 0, 1, -offsetX, -offsetY]); fillCtx.fillStyle = isPatternFill ? fillColor.getPattern(ctx, this, inverse, _pattern_helper.PathType.FILL) : fillColor; fillCtx.fillRect(0, 0, width, height); if (cache && !isPatternFill) { this.cachedCanvases.delete("fillCanvas"); cache.set(cacheKey, fillCanvas.canvas); } return { canvas: fillCanvas.canvas, offsetX: Math.round(offsetX), offsetY: Math.round(offsetY) }; } setLineWidth(width) { if (width !== this.current.lineWidth) { this._cachedScaleForStroking = null; } this.current.lineWidth = width; this.ctx.lineWidth = width; } setLineCap(style) { this.ctx.lineCap = LINE_CAP_STYLES[style]; } setLineJoin(style) { this.ctx.lineJoin = LINE_JOIN_STYLES[style]; } setMiterLimit(limit) { this.ctx.miterLimit = limit; } setDash(dashArray, dashPhase) { const ctx = this.ctx; if (ctx.setLineDash !== undefined) { ctx.setLineDash(dashArray); ctx.lineDashOffset = dashPhase; } } setRenderingIntent(intent) {} setFlatness(flatness) {} 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 "RI": this.setRenderingIntent(value); break; case "FL": this.setFlatness(value); break; case "Font": this.setFont(value[0], value[1]); break; case "CA": this.current.strokeAlpha = value; break; case "ca": this.current.fillAlpha = value; this.ctx.globalAlpha = value; break; case "BM": this.ctx.globalCompositeOperation = value; break; case "SMask": this.current.activeSMask = value ? this.tempSMask : null; this.tempSMask = null; this.checkSMaskState(); break; case "TR": this.current.transferMaps = value; } } } get inSMaskMode() { return !!this.suspendedCtx; } checkSMaskState() { const inSMaskMode = this.inSMaskMode; if (this.current.activeSMask && !inSMaskMode) { this.beginSMaskMode(); } else if (!this.current.activeSMask && inSMaskMode) { this.endSMaskMode(); } } beginSMaskMode() { if (this.inSMaskMode) { throw new Error("beginSMaskMode called while already in smask mode"); } const drawnWidth = this.ctx.canvas.width; const drawnHeight = this.ctx.canvas.height; const cacheId = "smaskGroupAt" + this.groupLevel; const scratchCanvas = this.cachedCanvases.getCanvas(cacheId, drawnWidth, drawnHeight); this.suspendedCtx = this.ctx; this.ctx = scratchCanvas.context; const ctx = this.ctx; ctx.setTransform(...(0, _display_utils.getCurrentTransform)(this.suspendedCtx)); copyCtxState(this.suspendedCtx, ctx); mirrorContextOperations(ctx, this.suspendedCtx); this.setGState([["BM", "source-over"], ["ca", 1], ["CA", 1]]); } endSMaskMode() { if (!this.inSMaskMode) { throw new Error("endSMaskMode called while not in smask mode"); } this.ctx._removeMirroring(); copyCtxState(this.ctx, this.suspendedCtx); this.ctx = this.suspendedCtx; this.suspendedCtx = null; } compose(dirtyBox) { if (!this.current.activeSMask) { return; } if (!dirtyBox) { dirtyBox = [0, 0, this.ctx.canvas.width, this.ctx.canvas.height]; } else { dirtyBox[0] = Math.floor(dirtyBox[0]); dirtyBox[1] = Math.floor(dirtyBox[1]); dirtyBox[2] = Math.ceil(dirtyBox[2]); dirtyBox[3] = Math.ceil(dirtyBox[3]); } const smask = this.current.activeSMask; const suspendedCtx = this.suspendedCtx; composeSMask(suspendedCtx, smask, this.ctx, dirtyBox); this.ctx.save(); this.ctx.setTransform(1, 0, 0, 1, 0, 0); this.ctx.clearRect(0, 0, this.ctx.canvas.width, this.ctx.canvas.height); this.ctx.restore(); } save() { if (this.inSMaskMode) { copyCtxState(this.ctx, this.suspendedCtx); this.suspendedCtx.save(); } else { this.ctx.save(); } const old = this.current; this.stateStack.push(old); this.current = old.clone(); } restore() { if (this.stateStack.length === 0 && this.inSMaskMode) { this.endSMaskMode(); } if (this.stateStack.length !== 0) { this.current = this.stateStack.pop(); if (this.inSMaskMode) { this.suspendedCtx.restore(); copyCtxState(this.suspendedCtx, this.ctx); } else { this.ctx.restore(); } this.checkSMaskState(); this.pendingClip = null; this._cachedScaleForStroking = null; this._cachedGetSinglePixelWidth = null; } } transform(a, b, c, d, e, f) { this.ctx.transform(a, b, c, d, e, f); this._cachedScaleForStroking = null; this._cachedGetSinglePixelWidth = null; } constructPath(ops, args, minMax) { const ctx = this.ctx; const current = this.current; let x = current.x, y = current.y; let startX, startY; const currentTransform = (0, _display_utils.getCurrentTransform)(ctx); const isScalingMatrix = currentTransform[0] === 0 && currentTransform[3] === 0 || currentTransform[1] === 0 && currentTransform[2] === 0; const minMaxForBezier = isScalingMatrix ? minMax.slice(0) : null; for (let i = 0, j = 0, ii = ops.length; i < ii; i++) { switch (ops[i] | 0) { case _util.OPS.rectangle: x = args[j++]; y = args[j++]; const width = args[j++]; const height = args[j++]; const xw = x + width; const yh = y + height; ctx.moveTo(x, y); if (width === 0 || height === 0) { ctx.lineTo(xw, yh); } else { ctx.lineTo(xw, y); ctx.lineTo(xw, yh); ctx.lineTo(x, yh); } if (!isScalingMatrix) { current.updateRectMinMax(currentTransform, [x, y, xw, yh]); } ctx.closePath(); break; case _util.OPS.moveTo: x = args[j++]; y = args[j++]; ctx.moveTo(x, y); if (!isScalingMatrix) { current.updatePathMinMax(currentTransform, x, y); } break; case _util.OPS.lineTo: x = args[j++]; y = args[j++]; ctx.lineTo(x, y); if (!isScalingMatrix) { current.updatePathMinMax(currentTransform, x, y); } break; case _util.OPS.curveTo: startX = x; startY = y; x = args[j + 4]; y = args[j + 5]; ctx.bezierCurveTo(args[j], args[j + 1], args[j + 2], args[j + 3], x, y); current.updateCurvePathMinMax(currentTransform, startX, startY, args[j], args[j + 1], args[j + 2], args[j + 3], x, y, minMaxForBezier); j += 6; break; case _util.OPS.curveTo2: startX = x; startY = y; ctx.bezierCurveTo(x, y, args[j], args[j + 1], args[j + 2], args[j + 3]); current.updateCurvePathMinMax(currentTransform, startX, startY, x, y, args[j], args[j + 1], args[j + 2], args[j + 3], minMaxForBezier); x = args[j + 2]; y = args[j + 3]; j += 4; break; case _util.OPS.curveTo3: startX = x; startY = y; x = args[j + 2]; y = args[j + 3]; ctx.bezierCurveTo(args[j], args[j + 1], x, y, x, y); current.updateCurvePathMinMax(currentTransform, startX, startY, args[j], args[j + 1], x, y, x, y, minMaxForBezier); j += 4; break; case _util.OPS.closePath: ctx.closePath(); break; } } if (isScalingMatrix) { current.updateScalingPathMinMax(currentTransform, minMaxForBezier); } current.setCurrentPoint(x, y); } closePath() { this.ctx.closePath(); } stroke(consumePath) { consumePath = typeof consumePath !== "undefined" ? consumePath : true; const ctx = this.ctx; const strokeColor = this.current.strokeColor; ctx.globalAlpha = this.current.strokeAlpha; if (this.contentVisible) { if (typeof strokeColor === "object" && strokeColor?.getPattern) { ctx.save(); ctx.strokeStyle = strokeColor.getPattern(ctx, this, (0, _display_utils.getCurrentTransformInverse)(ctx), _pattern_helper.PathType.STROKE); this.rescaleAndStroke(false); ctx.restore(); } else { this.rescaleAndStroke(true); } } if (consumePath) { this.consumePath(this.current.getClippedPathBoundingBox()); } ctx.globalAlpha = this.current.fillAlpha; } closeStroke() { this.closePath(); this.stroke(); } fill(consumePath) { consumePath = typeof consumePath !== "undefined" ? consumePath : true; const ctx = this.ctx; const fillColor = this.current.fillColor; const isPatternFill = this.current.patternFill; let needRestore = false; if (isPatternFill) { ctx.save(); ctx.fillStyle = fillColor.getPattern(ctx, this, (0, _display_utils.getCurrentTransformInverse)(ctx), _pattern_helper.PathType.FILL); needRestore = true; } const intersect = this.current.getClippedPathBoundingBox(); if (this.contentVisible && intersect !== null) { if (this.pendingEOFill) { ctx.fill("evenodd"); this.pendingEOFill = false; } else { ctx.fill(); } } if (needRestore) { ctx.restore(); } if (consumePath) { this.consumePath(intersect); } } eoFill() { this.pendingEOFill = true; this.fill(); } fillStroke() { this.fill(false); this.stroke(false); this.consumePath(); } eoFillStroke() { this.pendingEOFill = true; this.fillStroke(); } closeFillStroke() { this.closePath(); this.fillStroke(); } closeEOFillStroke() { this.pendingEOFill = true; this.closePath(); this.fillStroke(); } endPath() { this.consumePath(); } clip() { this.pendingClip = NORMAL_CLIP; } eoClip() { this.pendingClip = EO_CLIP; } beginText() { this.current.textMatrix = _util.IDENTITY_MATRIX; this.current.textMatrixScale = 1; this.current.x = this.current.lineX = 0; this.current.y = this.current.lineY = 0; } endText() { const paths = this.pendingTextPaths; const ctx = this.ctx; if (paths === undefined) { ctx.beginPath(); return; } ctx.save(); ctx.beginPath(); for (const path of paths) { ctx.setTransform(...path.transform); ctx.translate(path.x, path.y); path.addToPath(ctx, path.fontSize); } ctx.restore(); ctx.clip(); ctx.beginPath(); delete this.pendingTextPaths; } setCharSpacing(spacing) { this.current.charSpacing = spacing; } setWordSpacing(spacing) { this.current.wordSpacing = spacing; } setHScale(scale) { this.current.textHScale = scale / 100; } setLeading(leading) { this.current.leading = -leading; } setFont(fontRefName, size) { const fontObj = this.commonObjs.get(fontRefName); const current = this.current; if (!fontObj) { throw new Error(`Can't find font for ${fontRefName}`); } current.fontMatrix = fontObj.fontMatrix || _util.FONT_IDENTITY_MATRIX; if (current.fontMatrix[0] === 0 || current.fontMatrix[3] === 0) { (0, _util.warn)("Invalid font matrix for font " + fontRefName); } if (size < 0) { size = -size; current.fontDirection = -1; } else { current.fontDirection = 1; } this.current.font = fontObj; this.current.fontSize = size; if (fontObj.isType3Font) { return; } const name = fontObj.loadedName || "sans-serif"; let bold = "normal"; if (fontObj.black) { bold = "900"; } else if (fontObj.bold) { bold = "bold"; } const italic = fontObj.italic ? "italic" : "normal"; const typeface = `"${name}", ${fontObj.fallbackName}`; let browserFontSize = size; if (size < MIN_FONT_SIZE) { browserFontSize = MIN_FONT_SIZE; } else if (size > MAX_FONT_SIZE) { browserFontSize = MAX_FONT_SIZE; } this.current.fontSizeScale = size / browserFontSize; this.ctx.font = `${italic} ${bold} ${browserFontSize}px ${typeface}`; } setTextRenderingMode(mode) { this.current.textRenderingMode = mode; } setTextRise(rise) { this.current.textRise = rise; } moveText(x, y) { this.current.x = this.current.lineX += x; this.current.y = this.current.lineY += y; } setLeadingMoveText(x, y) { this.setLeading(-y); this.moveText(x, y); } setTextMatrix(a, b, c, d, e, f) { this.current.textMatrix = [a, b, c, d, e, f]; this.current.textMatrixScale = Math.hypot(a, b); this.current.x = this.current.lineX = 0; this.current.y = this.current.lineY = 0; } nextLine() { this.moveText(0, this.current.leading); } paintChar(character, x, y, patternTransform) { const ctx = this.ctx; const current = this.current; const font = current.font; const textRenderingMode = current.textRenderingMode; const fontSize = current.fontSize / current.fontSizeScale; const fillStrokeMode = textRenderingMode & _util.TextRenderingMode.FILL_STROKE_MASK; const isAddToPathSet = !!(textRenderingMode & _util.TextRenderingMode.ADD_TO_PATH_FLAG); const patternFill = current.patternFill && !font.missingFile; let addToPath; if (font.disableFontFace || isAddToPathSet || patternFill) { addToPath = font.getPathGenerator(this.commonObjs, character); } if (font.disableFontFace || patternFill) { ctx.save(); ctx.translate(x, y); ctx.beginPath(); addToPath(ctx, fontSize); if (patternTransform) { ctx.setTransform(...patternTransform); } if (fillStrokeMode === _util.TextRenderingMode.FILL || fillStrokeMode === _util.TextRenderingMode.FILL_STROKE) { ctx.fill(); } if (fillStrokeMode === _util.TextRenderingMode.STROKE || fillStrokeMode === _util.TextRenderingMode.FILL_STROKE) { ctx.stroke(); } ctx.restore(); } else { if (fillStrokeMode === _util.TextRenderingMode.FILL || fillStrokeMode === _util.TextRenderingMode.FILL_STROKE) { ctx.fillText(character, x, y); } if (fillStrokeMode === _util.TextRenderingMode.STROKE || fillStrokeMode === _util.TextRenderingMode.FILL_STROKE) { ctx.strokeText(character, x, y); } } if (isAddToPathSet) { const paths = this.pendingTextPaths || (this.pendingTextPaths = []); paths.push({ transform: (0, _displ