: str_replace(): Passing null to parameter #2 ($replace) of type array|string is deprecated in
this.current.textRenderingMode = mode;
this.current.textRise = rise;
this.current.x = this.current.lineX += x;
this.current.y = this.current.lineY += y;
setLeadingMoveText(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;
this.moveText(0, this.current.leading);
paintChar(character, x, y, patternTransform) {
const current = this.current;
const font = current.font;
const textRenderingMode = current.textRenderingMode;
const fontSize = current.fontSize / current.fontSizeScale;
const fillStrokeMode = textRenderingMode & TextRenderingMode.FILL_STROKE_MASK;
const isAddToPathSet = !!(textRenderingMode & TextRenderingMode.ADD_TO_PATH_FLAG);
const patternFill = current.patternFill && !font.missingFile;
if (font.disableFontFace || isAddToPathSet || patternFill) {
addToPath = font.getPathGenerator(this.commonObjs, character);
if (font.disableFontFace || patternFill) {
addToPath(ctx, fontSize);
ctx.setTransform(...patternTransform);
if (fillStrokeMode === TextRenderingMode.FILL || fillStrokeMode === TextRenderingMode.FILL_STROKE) {
if (fillStrokeMode === TextRenderingMode.STROKE || fillStrokeMode === TextRenderingMode.FILL_STROKE) {
if (fillStrokeMode === TextRenderingMode.FILL || fillStrokeMode === TextRenderingMode.FILL_STROKE) {
ctx.fillText(character, x, y);
if (fillStrokeMode === TextRenderingMode.STROKE || fillStrokeMode === TextRenderingMode.FILL_STROKE) {
ctx.strokeText(character, x, y);
const paths = this.pendingTextPaths ||= [];
transform: getCurrentTransform(ctx),
get isFontSubpixelAAEnabled() {
} = this.cachedCanvases.getCanvas("isFontSubpixelAAEnabled", 10, 10);
ctx.fillText("I", 0, 10);
const data = ctx.getImageData(0, 0, 10, 10).data;
for (let i = 3; i < data.length; i += 4) {
if (data[i] > 0 && data[i] < 255) {
return shadow(this, "isFontSubpixelAAEnabled", enabled);
const current = this.current;
const font = current.font;
return this.showType3Text(glyphs);
const fontSize = current.fontSize;
const fontSizeScale = current.fontSizeScale;
const charSpacing = current.charSpacing;
const wordSpacing = current.wordSpacing;
const fontDirection = current.fontDirection;
const textHScale = current.textHScale * fontDirection;
const glyphsLength = glyphs.length;
const vertical = font.vertical;
const spacingDir = vertical ? 1 : -1;
const defaultVMetrics = font.defaultVMetrics;
const widthAdvanceScale = fontSize * current.fontMatrix[0];
const simpleFillText = current.textRenderingMode === TextRenderingMode.FILL && !font.disableFontFace && !current.patternFill;
ctx.transform(...current.textMatrix);
ctx.translate(current.x, current.y + current.textRise);
ctx.scale(textHScale, -1);
ctx.scale(textHScale, 1);
if (current.patternFill) {
const pattern = current.fillColor.getPattern(ctx, this, getCurrentTransformInverse(ctx), PathType.FILL);
patternTransform = getCurrentTransform(ctx);
let lineWidth = current.lineWidth;
const scale = current.textMatrixScale;
if (scale === 0 || lineWidth === 0) {
const fillStrokeMode = current.textRenderingMode & TextRenderingMode.FILL_STROKE_MASK;
if (fillStrokeMode === TextRenderingMode.STROKE || fillStrokeMode === TextRenderingMode.FILL_STROKE) {
lineWidth = this.getSinglePixelWidth();
if (fontSizeScale !== 1.0) {
ctx.scale(fontSizeScale, fontSizeScale);
lineWidth /= fontSizeScale;
ctx.lineWidth = lineWidth;
if (font.isInvalidPDFjsFont) {
for (const glyph of glyphs) {
chars.push(glyph.unicode);
ctx.fillText(chars.join(""), 0, 0);
current.x += width * widthAdvanceScale * textHScale;
for (i = 0; i < glyphsLength; ++i) {
if (typeof glyph === "number") {
x += spacingDir * glyph * fontSize / 1000;
let restoreNeeded = false;
const spacing = (glyph.isSpace ? wordSpacing : 0) + charSpacing;
const character = glyph.fontChar;
const accent = glyph.accent;
const vmetric = glyph.vmetric || defaultVMetrics;
const vx = -(glyph.vmetric ? vmetric[1] : width * 0.5) * widthAdvanceScale;
const vy = vmetric[2] * widthAdvanceScale;
width = vmetric ? -vmetric[0] : width;
scaledX = vx / fontSizeScale;
scaledY = (x + vy) / fontSizeScale;
scaledX = x / fontSizeScale;
if (font.remeasure && width > 0) {
const measuredWidth = ctx.measureText(character).width * 1000 / fontSize * fontSizeScale;
if (width < measuredWidth && this.isFontSubpixelAAEnabled) {
const characterScaleX = width / measuredWidth;
ctx.scale(characterScaleX, 1);
scaledX /= characterScaleX;
} else if (width !== measuredWidth) {
scaledX += (width - measuredWidth) / 2000 * fontSize / fontSizeScale;
if (this.contentVisible && (glyph.isInFont || font.missingFile)) {
if (simpleFillText && !accent) {
ctx.fillText(character, scaledX, scaledY);
this.paintChar(character, scaledX, scaledY, patternTransform);
const scaledAccentX = scaledX + fontSize * accent.offset.x / fontSizeScale;
const scaledAccentY = scaledY - fontSize * accent.offset.y / fontSizeScale;
this.paintChar(accent.fontChar, scaledAccentX, scaledAccentY, patternTransform);
const charWidth = vertical ? width * widthAdvanceScale - spacing * fontDirection : width * widthAdvanceScale + spacing * fontDirection;
current.x += x * textHScale;
const current = this.current;
const font = current.font;
const fontSize = current.fontSize;
const fontDirection = current.fontDirection;
const spacingDir = font.vertical ? 1 : -1;
const charSpacing = current.charSpacing;
const wordSpacing = current.wordSpacing;
const textHScale = current.textHScale * fontDirection;
const fontMatrix = current.fontMatrix || FONT_IDENTITY_MATRIX;
const glyphsLength = glyphs.length;
const isTextInvisible = current.textRenderingMode === TextRenderingMode.INVISIBLE;
let i, glyph, width, spacingLength;
if (isTextInvisible || fontSize === 0) {
this._cachedScaleForStroking[0] = -1;
this._cachedGetSinglePixelWidth = null;
ctx.transform(...current.textMatrix);
ctx.translate(current.x, current.y);
ctx.scale(textHScale, fontDirection);
for (i = 0; i < glyphsLength; ++i) {
if (typeof glyph === "number") {
spacingLength = spacingDir * glyph * fontSize / 1000;
this.ctx.translate(spacingLength, 0);
current.x += spacingLength * textHScale;
const spacing = (glyph.isSpace ? wordSpacing : 0) + charSpacing;
const operatorList = font.charProcOperatorList[glyph.operatorListId];
warn(`Type3 character "${glyph.operatorListId}" is not available.`);
if (this.contentVisible) {
this.processingType3 = glyph;
ctx.scale(fontSize, fontSize);
ctx.transform(...fontMatrix);
this.executeOperatorList(operatorList);
const transformed = Util.applyTransform([glyph.width, 0], fontMatrix);
width = transformed[0] * fontSize + spacing;
current.x += width * textHScale;
this.processingType3 = null;
setCharWidth(xWidth, yWidth) {}
setCharWidthAndBounds(xWidth, yWidth, llx, lly, urx, ury) {
this.ctx.rect(llx, lly, urx - llx, ury - lly);
if (IR[0] === "TilingPattern") {
const baseTransform = this.baseTransform || getCurrentTransform(this.ctx);
const canvasGraphicsFactory = {
createCanvasGraphics: ctx => new CanvasGraphics(ctx, this.commonObjs, this.objs, this.canvasFactory, this.filterFactory, {
optionalContentConfig: this.optionalContentConfig,
markedContentStack: this.markedContentStack
pattern = new TilingPattern(IR, color, this.ctx, canvasGraphicsFactory, baseTransform);
pattern = this._getPattern(IR[1], IR[2]);
this.current.strokeColor = this.getColorN_Pattern(arguments);
this.current.fillColor = this.getColorN_Pattern(arguments);
this.current.patternFill = true;
setStrokeRGBColor(r, g, b) {
const color = Util.makeHexColor(r, g, b);
this.ctx.strokeStyle = color;
this.current.strokeColor = color;
setFillRGBColor(r, g, b) {
const color = Util.makeHexColor(r, g, b);
this.ctx.fillStyle = color;
this.current.fillColor = color;
this.current.patternFill = false;
_getPattern(objId, matrix = null) {
if (this.cachedPatterns.has(objId)) {
pattern = this.cachedPatterns.get(objId);
pattern = getShadingPattern(this.getObject(objId));
this.cachedPatterns.set(objId, pattern);
if (!this.contentVisible) {
const pattern = this._getPattern(objId);
ctx.fillStyle = pattern.getPattern(ctx, this, getCurrentTransformInverse(ctx), PathType.SHADING);
const inv = getCurrentTransformInverse(ctx);
const [x0, y0, x1, y1] = Util.getAxialAlignedBoundingBox([0, 0, width, height], inv);
this.ctx.fillRect(x0, y0, x1 - x0, y1 - y0);
this.ctx.fillRect(-1e10, -1e10, 2e10, 2e10);
this.compose(this.current.getClippedPathBoundingBox());
unreachable("Should not call beginInlineImage");
unreachable("Should not call beginImageData");
paintFormXObjectBegin(matrix, bbox) {
if (!this.contentVisible) {
this.baseTransformStack.push(this.baseTransform);
this.transform(...matrix);
this.baseTransform = getCurrentTransform(this.ctx);
const width = bbox[2] - bbox[0];
const height = bbox[3] - bbox[1];
this.ctx.rect(bbox[0], bbox[1], width, height);
this.current.updateRectMinMax(getCurrentTransform(this.ctx), bbox);
if (!this.contentVisible) {
this.baseTransform = this.baseTransformStack.pop();
if (!this.contentVisible) {
this.current.activeSMask = null;
const currentCtx = this.ctx;
info("TODO: Support non-isolated groups.");
warn("Knockout groups not supported.");
const currentTransform = getCurrentTransform(currentCtx);
currentCtx.transform(...group.matrix);
throw new Error("Bounding box is required.");
let bounds = Util.getAxialAlignedBoundingBox(group.bbox, getCurrentTransform(currentCtx));
const canvasBounds = [0, 0, currentCtx.canvas.width, currentCtx.canvas.height];
bounds = Util.intersect(bounds, canvasBounds) || [0, 0, 0, 0];
const offsetX = Math.floor(bounds[0]);
const offsetY = Math.floor(bounds[1]);
const drawnWidth = Math.max(Math.ceil(bounds[2]) - offsetX, 1);
const drawnHeight = Math.max(Math.ceil(bounds[3]) - offsetY, 1);
this.current.startNewPathAndClipBox([0, 0, drawnWidth, drawnHeight]);
let cacheId = "groupAt" + this.groupLevel;
cacheId += "_smask_" + this.smaskCounter++ % 2;
const scratchCanvas = this.cachedCanvases.getCanvas(cacheId, drawnWidth, drawnHeight);
const groupCtx = scratchCanvas.context;
groupCtx.translate(-offsetX, -offsetY);
groupCtx.transform(...currentTransform);
canvas: scratchCanvas.canvas,
subtype: group.smask.subtype,
backdrop: group.smask.backdrop,
transferMap: group.smask.transferMap || null,
startTransformInverse: null
currentCtx.setTransform(1, 0, 0, 1, 0, 0);
currentCtx.translate(offsetX, offsetY);
copyCtxState(currentCtx, groupCtx);
this.setGState([["BM", "source-over"], ["ca", 1], ["CA", 1]]);
this.groupStack.push(currentCtx);
if (!this.contentVisible) {
const groupCtx = this.ctx;
const ctx = this.groupStack.pop();
this.ctx.imageSmoothingEnabled = false;
this.tempSMask = this.smaskStack.pop();
const currentMtx = getCurrentTransform(this.ctx);
this.ctx.setTransform(...currentMtx);
const dirtyBox = Util.getAxialAlignedBoundingBox([0, 0, groupCtx.canvas.width, groupCtx.canvas.height], currentMtx);
this.ctx.drawImage(groupCtx.canvas, 0, 0);
beginAnnotation(id, rect, transform, matrix, hasOwnCanvas) {
this.#restoreInitialState();
resetCtxToDefault(this.ctx);
if (this.baseTransform) {
this.ctx.setTransform(...this.baseTransform);
const width = rect[2] - rect[0];
const height = rect[3] - rect[1];
if (hasOwnCanvas && this.annotationCanvasMap) {
transform = transform.slice();
const [scaleX, scaleY] = Util.singularValueDecompose2dScale(getCurrentTransform(this.ctx));
const canvasWidth = Math.ceil(width * this.outputScaleX * viewportScale);
const canvasHeight = Math.ceil(height * this.outputScaleY * viewportScale);
this.annotationCanvas = this.canvasFactory.create(canvasWidth, canvasHeight);
} = this.annotationCanvas;
this.annotationCanvasMap.set(id, canvas);
this.annotationCanvas.savedCtx = this.ctx;
this.ctx.setTransform(scaleX, 0, 0, -scaleY, 0, height * scaleY);
resetCtxToDefault(this.ctx);
resetCtxToDefault(this.ctx);
this.ctx.rect(rect[0], rect[1], width, height);