diff --git a/src/core/annotation.js b/src/core/annotation.js index 562a7da5c..6b3d8e642 100644 --- a/src/core/annotation.js +++ b/src/core/annotation.js @@ -2559,7 +2559,6 @@ class WidgetAnnotation extends Annotation { fontSize, totalWidth, totalHeight, - defaultHPadding, defaultVPadding, descent, lineHeight, @@ -2924,7 +2923,6 @@ class TextWidgetAnnotation extends WidgetAnnotation { fontSize, width, height, - hPadding, vPadding, descent, lineHeight, @@ -2936,24 +2934,34 @@ class TextWidgetAnnotation extends WidgetAnnotation { // Empty or it has a trailing whitespace. const colors = this.getBorderAndBackgroundAppearances(annotationStorage); - const buf = []; - const positions = font.getCharPositions(text); - for (const [start, end] of positions) { - buf.push(`(${escapeString(text.substring(start, end))}) Tj`); - } + const cells = font.getCharPositions(text).map(([start, end]) => { + const glyph = text.substring(start, end); + return { glyph, width: this._getTextWidth(glyph, font) * fontSize }; + }); if (isRTL) { - buf.reverse(); + cells.reverse(); } - const textWidth = combWidth * positions.length; - let hShift = hPadding; + const textWidth = combWidth * cells.length; + let hShift = 0; if (alignment === 1) { hShift += Math.floor((width - textWidth) / (2 * combWidth)) * combWidth; } else if (alignment === 2) { hShift += width - textWidth; } - const renderedComb = buf.join(` ${numberToString(combWidth)} 0 Td `); + const buf = []; + let previousWidth = 0; + for (let i = 0, ii = cells.length; i < ii; i++) { + const { glyph, width: glyphWidth } = cells[i]; + const shift = + i === 0 + ? (combWidth - glyphWidth) / 2 + : combWidth + (previousWidth - glyphWidth) / 2; + buf.push(`${numberToString(shift)} 0 Td (${escapeString(glyph)}) Tj`); + previousWidth = glyphWidth; + } + const renderedComb = buf.join(" "); return ( `/Tx BMC q ${colors}BT ` + defaultAppearance + diff --git a/src/display/annotation_layer.js b/src/display/annotation_layer.js index 144a28b0a..00408d9f0 100644 --- a/src/display/annotation_layer.js +++ b/src/display/annotation_layer.js @@ -1450,7 +1450,7 @@ class WidgetAnnotationElement extends AnnotationElement { style.color = Util.makeHexColor(...fontColor); - if (this.data.textAlignment !== null) { + if (this.data.textAlignment !== null && !this.data.comb) { style.textAlign = TEXT_ALIGNMENT[this.data.textAlignment]; } } @@ -1879,7 +1879,30 @@ class TextWidgetAnnotationElement extends WidgetAnnotationElement { const combWidth = fieldWidth / maxLen; element.classList.add("comb"); - element.style.letterSpacing = `calc(${combWidth}px * var(--total-scale-factor) - 1ch)`; + element.style.setProperty( + "--comb-width", + `calc(${combWidth}px * var(--total-scale-factor))` + ); + + const alignment = this.data.textAlignment; + if (alignment === 1 || alignment === 2) { + const setCombOffset = () => { + const free = maxLen - element.value.length; + element.style.setProperty( + "--comb-offset", + `${alignment === 1 ? free >> 1 : free}` + ); + }; + setCombOffset(); + for (const evt of [ + "input", + "blur", + "resetform", + "updatefromsandbox", + ]) { + element.addEventListener(evt, setCombOffset); + } + } } } else { element = document.createElement("div"); diff --git a/test/unit/annotation_spec.js b/test/unit/annotation_spec.js index 4d272809a..cf622848e 100644 --- a/test/unit/annotation_spec.js +++ b/test/unit/annotation_spec.js @@ -2051,11 +2051,11 @@ describe("annotation", function () { annotationStorage ); expect(appearance).toEqual( - "/Tx BMC q BT /Helv 5 Tf 1 0 0 1 2 3.07 Tm" + - " (a) Tj 8 0 Td (a) Tj 8 0 Td (\\() Tj" + - " 8 0 Td (a) Tj 8 0 Td (a) Tj" + - " 8 0 Td (\\)) Tj 8 0 Td (a) Tj" + - " 8 0 Td (\\\\) Tj ET Q EMC" + "/Tx BMC q BT /Helv 5 Tf 1 0 0 1 0 3.07 Tm" + + " 2.61 0 Td (a) Tj 8 0 Td (a) Tj 8.56 0 Td (\\() Tj" + + " 7.44 0 Td (a) Tj 8 0 Td (a) Tj" + + " 8.56 0 Td (\\)) Tj 7.44 0 Td (a) Tj" + + " 8.7 0 Td (\\\\) Tj ET Q EMC" ); }); @@ -2092,8 +2092,8 @@ describe("annotation", function () { annotationStorage ); expect(appearance).toEqual( - "/Tx BMC q BT /Goth 5 Tf 1 0 0 1 2 3.07 Tm" + - " (\x30\x53) Tj 8 0 Td (\x30\x93) Tj 8 0 Td (\x30\x6b) Tj" + + "/Tx BMC q BT /Goth 5 Tf 1 0 0 1 0 3.07 Tm" + + " 1.5 0 Td (\x30\x53) Tj 8 0 Td (\x30\x93) Tj 8 0 Td (\x30\x6b) Tj" + " 8 0 Td (\x30\x61) Tj 8 0 Td (\x30\x6f) Tj" + " 8 0 Td (\x4e\x16) Tj 8 0 Td (\x75\x4c) Tj" + " 8 0 Td (\x30\x6e) Tj ET Q EMC" diff --git a/web/annotation_layer_builder.css b/web/annotation_layer_builder.css index f529108e8..833dc173e 100644 --- a/web/annotation_layer_builder.css +++ b/web/annotation_layer_builder.css @@ -292,19 +292,25 @@ } .textWidgetAnnotation input.comb { + --comb-width: 0px; + --comb-letter-spacing: calc(var(--comb-width) - 1ch); + font-family: monospace; - padding-left: 2px; - padding-right: 0; + letter-spacing: var(--comb-letter-spacing); + overflow-x: hidden; + padding: 0; + text-indent: calc( + var(--comb-offset, 0) * var(--comb-width) + var(--comb-letter-spacing) / 2 + ); } .textWidgetAnnotation input.comb:focus { - /* - * Letter spacing is placed on the right side of each character. Hence, the - * letter spacing of the last character may be placed outside the visible - * area, causing horizontal scrolling. We avoid this by extending the width - * when the element has focus and revert this when it loses focus. - */ - width: 103%; + background-image: repeating-linear-gradient( + to right, + transparent 0 calc(var(--comb-width) - 1px), + var(--input-focus-border-color) calc(var(--comb-width) - 1px) + var(--comb-width) + ); } .buttonWidgetAnnotation:is(.checkBox, .radioButton) input {