From b82597bfacded290f4f780c4999dcb9a834c10ec Mon Sep 17 00:00:00 2001 From: Vladimir Date: Wed, 1 Jul 2026 10:03:51 +0300 Subject: [PATCH 1/2] Wrong bbox in location for vertical font (character spasing) --- src/core/bounding_boxes.js | 50 +++++++++++++++++++------------------- 1 file changed, 25 insertions(+), 25 deletions(-) diff --git a/src/core/bounding_boxes.js b/src/core/bounding_boxes.js index 26248b070bc9b..153c33ffce327 100644 --- a/src/core/bounding_boxes.js +++ b/src/core/bounding_boxes.js @@ -52,11 +52,8 @@ class BoundingBoxesCalculator { } getTextBoundingBox(glyphs) { - let tx = 0; - let ty = 0; // Save previous x value to take it into account while calculating // width of marked content - const ctm = this.graphicsStateManager.state.ctm; const descent = @@ -116,35 +113,44 @@ class BoundingBoxesCalculator { : 1; let glyphsSize = []; + let tx = 0, ty = 0; for (let i = 0; i < glyphs.length; i++) { + this.textStateManager.state.translateTextMatrix(tx, -ty); + tx = ty = 0; const glyph = glyphs[i]; if (typeof glyph === "number") { - if (this.textStateManager.state.font.vertical) { - ty = - (-glyph / 1000) * - this.textStateManager.state.fontSize * - this.textStateManager.state.textHScale; - } else { + if (!this.textStateManager.state.font.vertical) { tx = (-glyph / 1000) * this.textStateManager.state.fontSize * this.textStateManager.state.textHScale; + } else { + ty = + (glyph / 1000) * + this.textStateManager.state.fontSize; } } else { - let glyphWidth = null; - glyphWidth = - this.textStateManager.state.font.vertical && glyph.vmetric - ? glyph.vmetric[0] + const vmetric = glyph.vmetric ?? + this.textStateManager.state.font.defaultVMetrics; + const glyphWidth = + this.textStateManager.state.font.vertical && vmetric + ? -vmetric[0] : glyph.width; + const [x, y] = [ + this.textStateManager.state.textMatrix[4] + shift[0], + this.textStateManager.state.textMatrix[5] + shift[1], + ]; if (!this.textStateManager.state.font.vertical) { const w0 = glyphWidth * (this.textStateManager.state.fontMatrix ? this.textStateManager.state.fontMatrix[0] : 1 / 1000); + tx = w0 * this.textStateManager.state.fontSize * + this.textStateManager.state.textHScale; + this.textStateManager.state.translateTextMatrix(tx, 0); tx = - (w0 * this.textStateManager.state.fontSize + - this.textStateManager.state.charSpacing + + (this.textStateManager.state.charSpacing + (glyph.isSpace ? this.textStateManager.state.wordSpacing : 0)) * this.textStateManager.state.textHScale; } else { @@ -153,18 +159,12 @@ class BoundingBoxesCalculator { (this.textStateManager.state.fontMatrix ? this.textStateManager.state.fontMatrix[0] : 1 / 1000); + ty = w1 * this.textStateManager.state.fontSize; + this.textStateManager.state.translateTextMatrix(0, -ty); ty = - w1 * this.textStateManager.state.fontSize - - this.textStateManager.state.charSpacing - - (glyph.isSpace ? this.textStateManager.state.wordSpacing : 0); + -this.textStateManager.state.charSpacing - + (glyph.isSpace ? this.textStateManager.state.wordSpacing : 0); } - } - const [x, y] = [ - this.textStateManager.state.textMatrix[4] + shift[0], - this.textStateManager.state.textMatrix[5] + shift[1], - ]; - this.textStateManager.state.translateTextMatrix(tx, -ty); - if (typeof glyph !== "number") { glyphsSize.push([ x, y, From dceee0cf283179d6a9c5142fa2f23855f2df712f Mon Sep 17 00:00:00 2001 From: Vladimir Date: Tue, 14 Jul 2026 14:46:13 +0300 Subject: [PATCH 2/2] Ensure consistency with the backend --- src/core/bounding_boxes.js | 133 ++++++++++--------------------------- 1 file changed, 35 insertions(+), 98 deletions(-) diff --git a/src/core/bounding_boxes.js b/src/core/bounding_boxes.js index 153c33ffce327..7e080b403092c 100644 --- a/src/core/bounding_boxes.js +++ b/src/core/bounding_boxes.js @@ -55,130 +55,67 @@ class BoundingBoxesCalculator { // Save previous x value to take it into account while calculating // width of marked content const ctm = this.graphicsStateManager.state.ctm; + const textState = this.textStateManager.state; + const { + fontMatrix, textMatrix, textHScale, fontSize, + font, textRise, charSpacing, wordSpacing, + } = textState; + const scaledFontSize = textHScale * fontSize; - const descent = - (this.textStateManager.state.font.descent || 0) * - this.textStateManager.state.fontSize; - const ascent = - (this.textStateManager.state.font.ascent || 1) * - this.textStateManager.state.fontSize; - const rise = - this.textStateManager.state.textRise * - this.textStateManager.state.fontSize; + const descent = (font.descent || 0) * fontSize; + const ascent = (font.ascent || 1) * fontSize; + const rise = textRise * fontSize; let tx0, ty0, shift, height; - if (!this.textStateManager.state.font.vertical) { + if (!font.vertical) { // Left Bottom point of text bbox // Save before text matrix will be changed with going through glyphs - [tx0, ty0] = this.transformPoint( - 0, - descent + rise, - this.textStateManager.state.textMatrix - ); + [tx0, ty0] = this.transformPoint(0, descent + rise, textMatrix); // Calculate transformed height and shift to place // the whole glyph inside the bbox - shift = [ - tx0 - this.textStateManager.state.textMatrix[4], - ty0 - this.textStateManager.state.textMatrix[5], - ]; - height = this.transformPoint( - 0, - ascent - descent, - this.textStateManager.state.textMatrix - ); + shift = [tx0 - textMatrix[4], ty0 - textMatrix[5]]; + height = this.transformPoint(0, ascent - descent, textMatrix); } else { - [tx0, ty0] = this.transformPoint( - -this.textStateManager.state.fontSize / 2, - rise, - this.textStateManager.state.textMatrix - ); - shift = [ - tx0 - this.textStateManager.state.textMatrix[4], - ty0 - this.textStateManager.state.textMatrix[5], - ]; - height = this.transformPoint( - ascent - descent, - 0, - this.textStateManager.state.textMatrix - ); + [tx0, ty0] = this.transformPoint(-scaledFontSize / 2, rise, textMatrix); + shift = [tx0 - textMatrix[4], ty0 - textMatrix[5]]; + height = this.transformPoint(scaledFontSize, 0, textMatrix); } - height[0] -= this.textStateManager.state.textMatrix[4]; - height[1] -= this.textStateManager.state.textMatrix[5]; + height[0] -= textMatrix[4]; + height[1] -= textMatrix[5]; height = Math.hypot(height[0], height[1]); - height *= - this.textStateManager.state.textMatrix[0] * - this.textStateManager.state.textMatrix[3] < - 0 - ? -1 - : 1; + height *= textMatrix[0] * textMatrix[3] < 0 ? -1 : 1; let glyphsSize = []; let tx = 0, ty = 0; for (let i = 0; i < glyphs.length; i++) { - this.textStateManager.state.translateTextMatrix(tx, -ty); + textState.translateTextMatrix(tx, -ty); tx = ty = 0; const glyph = glyphs[i]; if (typeof glyph === "number") { - if (!this.textStateManager.state.font.vertical) { - tx = - (-glyph / 1000) * - this.textStateManager.state.fontSize * - this.textStateManager.state.textHScale; + if (!font.vertical) { + tx = (-glyph / 1000) * scaledFontSize; } else { - ty = - (glyph / 1000) * - this.textStateManager.state.fontSize; + ty = (glyph / 1000) * fontSize; } } else { - const vmetric = glyph.vmetric ?? - this.textStateManager.state.font.defaultVMetrics; - const glyphWidth = - this.textStateManager.state.font.vertical && vmetric - ? -vmetric[0] - : glyph.width; - const [x, y] = [ - this.textStateManager.state.textMatrix[4] + shift[0], - this.textStateManager.state.textMatrix[5] + shift[1], - ]; - if (!this.textStateManager.state.font.vertical) { - const w0 = - glyphWidth * - (this.textStateManager.state.fontMatrix - ? this.textStateManager.state.fontMatrix[0] - : 1 / 1000); - tx = w0 * this.textStateManager.state.fontSize * - this.textStateManager.state.textHScale; - this.textStateManager.state.translateTextMatrix(tx, 0); - tx = - (this.textStateManager.state.charSpacing + - (glyph.isSpace ? this.textStateManager.state.wordSpacing : 0)) * - this.textStateManager.state.textHScale; + const vmetric = glyph.vmetric ?? font.defaultVMetrics; + const glyphWidth = font.vertical && vmetric ? -vmetric[0] : glyph.width; + const [x, y] = [textMatrix[4] + shift[0], textMatrix[5] + shift[1]]; + if (!font.vertical) { + const w0 = glyphWidth * (fontMatrix ? fontMatrix[0] : 1 / 1000); + textState.translateTextMatrix(w0 * scaledFontSize, 0); + tx = (charSpacing + (glyph.isSpace ? wordSpacing : 0)) * textHScale; } else { - const w1 = - glyphWidth * - (this.textStateManager.state.fontMatrix - ? this.textStateManager.state.fontMatrix[0] - : 1 / 1000); - ty = w1 * this.textStateManager.state.fontSize; - this.textStateManager.state.translateTextMatrix(0, -ty); - ty = - -this.textStateManager.state.charSpacing - - (glyph.isSpace ? this.textStateManager.state.wordSpacing : 0); + const w1 = glyphWidth * (fontMatrix ? fontMatrix[0] : 1 / 1000); + textState.translateTextMatrix(0, -w1 * fontSize); + ty = -charSpacing - (glyph.isSpace ? wordSpacing : 0); } - glyphsSize.push([ - x, - y, - this.textStateManager.state.textMatrix[4] + shift[0], - this.textStateManager.state.textMatrix[5] + shift[1], - ]); + glyphsSize.push([x, y, textMatrix[4] + shift[0], textMatrix[5] + shift[1]]); } } // Right Bottom point is in text matrix after going through glyphs - const [tx1, ty1] = [ - this.textStateManager.state.textMatrix[4] + shift[0], - this.textStateManager.state.textMatrix[5] + shift[1], - ]; + const [tx1, ty1] = [textMatrix[4] + shift[0], textMatrix[5] + shift[1]]; // Top point can be calculated from base and height const [tx2, ty2, tx3, ty3] = this.getTopPoints(tx0, ty0, tx1, ty1, height); glyphsSize = glyphsSize.map(glyphSize => [