diff --git a/src/core/annotation.js b/src/core/annotation.js index 5934f251952b0..3c271feace0bb 100644 --- a/src/core/annotation.js +++ b/src/core/annotation.js @@ -2586,6 +2586,21 @@ class WidgetAnnotation extends Annotation { ); const alignment = this.data.textAlignment; + let { ascent: fontAscent, descent: fontDescent } = font; + if ( + isNaN(fontAscent) || + isNaN(fontDescent) || + (!fontAscent && !fontDescent) + ) { + fontAscent = LINE_FACTOR - LINE_DESCENT_FACTOR; + fontDescent = LINE_DESCENT_FACTOR; + } else { + fontDescent = Math.abs(fontDescent); + } + const vShift = + (totalHeight - (fontAscent + fontDescent) * fontSize) / 2 + + fontDescent * fontSize; + if (this.data.multiLine) { return this._getMultilineAppearance( defaultAppearance, @@ -2610,14 +2625,14 @@ class WidgetAnnotation extends Annotation { encodedLines[0], fontSize, totalWidth, - totalHeight, + vShift, alignment, bidi(lines[0]).dir === "rtl", annotationStorage ); } - const bottomPadding = defaultVPadding + descent; + const bottomPadding = vShift; if (alignment === 0 || alignment > 2) { // Left alignment: nothing to do return ( @@ -2971,7 +2986,7 @@ class TextWidgetAnnotation extends WidgetAnnotation { text, fontSize, width, - height, + vShift, alignment, isRTL, annotationStorage @@ -3009,12 +3024,6 @@ class TextWidgetAnnotation extends WidgetAnnotation { } const renderedComb = buf.join(" "); - // Vertically center the glyphs within the field: comb fields are mostly - // filled with uppercase letters and/or digits, hence we use the cap height - // (with a fallback on the ascent or the font size) to center them. - const vShift = - (height - (font.capHeight || font.ascent || 1) * fontSize) / 2; - return ( `/Tx BMC q ${colors}BT ` + defaultAppearance + diff --git a/src/core/decrypt_stream.js b/src/core/decrypt_stream.js index e8ac379af18ca..ea2659e6066f9 100644 --- a/src/core/decrypt_stream.js +++ b/src/core/decrypt_stream.js @@ -30,12 +30,12 @@ class DecryptStream extends DecodeStream { readBlock() { let chunk = this.#nextChunk ?? this.stream.getBytes(chunkSize); - if (!chunk?.length) { + if (!chunk.length) { this.eof = true; return; } this.#nextChunk = this.stream.getBytes(chunkSize); - const hasMoreData = this.#nextChunk?.length > 0; + const hasMoreData = this.#nextChunk.length > 0; const decrypt = this.decrypt; chunk = decrypt(chunk, !hasMoreData); diff --git a/src/core/obj_bin_transform_core.js b/src/core/obj_bin_transform_core.js index 8704bc062b2ca..25dbbf63bce56 100644 --- a/src/core/obj_bin_transform_core.js +++ b/src/core/obj_bin_transform_core.js @@ -17,12 +17,13 @@ import { assert, FeatureTest } from "../shared/util.js"; import { CSS_FONT_INFO, FONT_INFO, + InfoUtils, PATTERN_INFO, SYSTEM_FONT_INFO, } from "../shared/obj_bin_transform_utils.js"; function compileCssFontInfo(info) { - const encoder = new TextEncoder(); + const { encoder } = InfoUtils; const encodedStrings = {}; let stringsLength = 0; for (const prop of CSS_FONT_INFO.strings) { @@ -48,7 +49,7 @@ function compileCssFontInfo(info) { } function compileSystemFontInfo(info) { - const encoder = new TextEncoder(); + const { encoder } = InfoUtils; const encodedStrings = {}; let stringsLength = 0; for (const prop of SYSTEM_FONT_INFO.strings) { @@ -106,7 +107,7 @@ function compileFontInfo(font) { ? compileCssFontInfo(font.cssFontInfo) : null; - const encoder = new TextEncoder(); + const { encoder } = InfoUtils; const encodedStrings = {}; let stringsLength = 0; for (const prop of FONT_INFO.strings) { diff --git a/src/core/run_length_stream.js b/src/core/run_length_stream.js index db664b733620a..30c54719f59da 100644 --- a/src/core/run_length_stream.js +++ b/src/core/run_length_stream.js @@ -29,7 +29,7 @@ class RunLengthStream extends DecodeStream { // (in addition to the second byte from the header), n = 129 through 255 - // duplicate the second byte from the header (257 - n) times, n = 128 - end. const repeatHeader = this.stream.getBytes(2); - if (!repeatHeader || repeatHeader.length < 2 || repeatHeader[0] === 128) { + if (repeatHeader.length < 2 || repeatHeader[0] === 128) { this.eof = true; return; } diff --git a/src/display/obj_bin_transform_display.js b/src/display/obj_bin_transform_display.js index 9e21c7bb02a72..a584198f2eee0 100644 --- a/src/display/obj_bin_transform_display.js +++ b/src/display/obj_bin_transform_display.js @@ -17,6 +17,7 @@ import { assert, BBOX_INIT, FeatureTest, Util } from "../shared/util.js"; import { CSS_FONT_INFO, FONT_INFO, + InfoUtils, PATTERN_INFO, SYSTEM_FONT_INFO, } from "../shared/obj_bin_transform_utils.js"; @@ -24,8 +25,6 @@ import { class CssFontInfo { #buffer; - #decoder = new TextDecoder(); - #view; constructor(buffer) { @@ -35,14 +34,13 @@ class CssFontInfo { #readString(index) { assert(index < CSS_FONT_INFO.strings.length, "Invalid string index"); + const { decoder } = InfoUtils; let offset = 0; for (let i = 0; i < index; i++) { offset += this.#view.getUint32(offset) + 4; } const length = this.#view.getUint32(offset); - return this.#decoder.decode( - new Uint8Array(this.#buffer, offset + 4, length) - ); + return decoder.decode(new Uint8Array(this.#buffer, offset + 4, length)); } get fontFamily() { @@ -61,8 +59,6 @@ class CssFontInfo { class SystemFontInfo { #buffer; - #decoder = new TextDecoder(); - #view; constructor(buffer) { @@ -76,14 +72,13 @@ class SystemFontInfo { #readString(index) { assert(index < SYSTEM_FONT_INFO.strings.length, "Invalid string index"); + const { decoder } = InfoUtils; let offset = 5; for (let i = 0; i < index; i++) { offset += this.#view.getUint32(offset) + 4; } const length = this.#view.getUint32(offset); - return this.#decoder.decode( - new Uint8Array(this.#buffer, offset + 4, length) - ); + return decoder.decode(new Uint8Array(this.#buffer, offset + 4, length)); } get css() { @@ -103,15 +98,16 @@ class SystemFontInfo { } get style() { + const { decoder } = InfoUtils; let offset = 1; offset += 4 + this.#view.getUint32(offset); const styleLength = this.#view.getUint32(offset); - const style = this.#decoder.decode( + const style = decoder.decode( new Uint8Array(this.#buffer, offset + 4, styleLength) ); offset += 4 + styleLength; const weightLength = this.#view.getUint32(offset); - const weight = this.#decoder.decode( + const weight = decoder.decode( new Uint8Array(this.#buffer, offset + 4, weightLength) ); return { style, weight }; @@ -121,8 +117,6 @@ class SystemFontInfo { class FontInfo { #buffer; - #decoder = new TextDecoder(); - #view; constructor({ buffer, extra }) { @@ -242,14 +236,13 @@ class FontInfo { #readString(index) { assert(index < FONT_INFO.strings.length, "Invalid string index"); + const { decoder } = InfoUtils; let offset = FONT_INFO.OFFSET_STRINGS + 4; for (let i = 0; i < index; i++) { offset += this.#view.getUint32(offset) + 4; } const length = this.#view.getUint32(offset); - return this.#decoder.decode( - new Uint8Array(this.#buffer, offset + 4, length) - ); + return decoder.decode(new Uint8Array(this.#buffer, offset + 4, length)); } get fallbackName() { diff --git a/src/shared/obj_bin_transform_utils.js b/src/shared/obj_bin_transform_utils.js index 9682485efbcb0..1dd39a9d26416 100644 --- a/src/shared/obj_bin_transform_utils.js +++ b/src/shared/obj_bin_transform_utils.js @@ -13,6 +13,8 @@ * limitations under the License. */ +import { shadow } from "./util.js"; + class CSS_FONT_INFO { static strings = ["fontFamily", "fontWeight", "italicAngle"]; } @@ -68,4 +70,14 @@ class PATTERN_INFO { static N_FIGURES = 16; // number of figures } -export { CSS_FONT_INFO, FONT_INFO, PATTERN_INFO, SYSTEM_FONT_INFO }; +class InfoUtils { + static get decoder() { + return shadow(this, "decoder", new TextDecoder()); + } + + static get encoder() { + return shadow(this, "encoder", new TextEncoder()); + } +} + +export { CSS_FONT_INFO, FONT_INFO, InfoUtils, PATTERN_INFO, SYSTEM_FONT_INFO }; diff --git a/test/pdfs/.gitignore b/test/pdfs/.gitignore index 8148a5d3202a4..141d407b92ff7 100644 --- a/test/pdfs/.gitignore +++ b/test/pdfs/.gitignore @@ -952,3 +952,4 @@ !function_based_shading_cmyk.pdf !large_jpeg_downscale.pdf !bug1898053_minimal.pdf +!bug2055455.pdf diff --git a/test/pdfs/bug2055455.pdf b/test/pdfs/bug2055455.pdf new file mode 100644 index 0000000000000..e694450980f62 Binary files /dev/null and b/test/pdfs/bug2055455.pdf differ diff --git a/test/test_manifest.json b/test/test_manifest.json index 05dea0a73d430..2beca8f239432 100644 --- a/test/test_manifest.json +++ b/test/test_manifest.json @@ -14524,5 +14524,14 @@ "rounds": 1, "lastPage": 1, "type": "eq" + }, + { + "id": "bug2055455", + "file": "pdfs/bug2055455.pdf", + "md5": "e9a7d04240532aa4c6849f291f275895", + "rounds": 1, + "lastPage": 1, + "type": "eq", + "print": true } ] diff --git a/test/unit/annotation_spec.js b/test/unit/annotation_spec.js index 0562a2fe06e44..444b2037a0c8d 100644 --- a/test/unit/annotation_spec.js +++ b/test/unit/annotation_spec.js @@ -1764,7 +1764,7 @@ describe("annotation", function () { ); expect(appearance).toEqual( "/Tx BMC q BT /Helv 5 Tf 1 0 0 1 0 0 Tm" + - " 2 3.07 Td (test\\\\print) Tj ET Q EMC" + " 2 3.72 Td (test\\\\print) Tj ET Q EMC" ); }); @@ -1801,7 +1801,7 @@ describe("annotation", function () { "\x30\x53\x30\x93\x30\x6b\x30\x61\x30\x6f\x4e\x16\x75\x4c\x30\x6e"; expect(appearance).toEqual( "/Tx BMC q BT /Goth 5 Tf 1 0 0 1 0 0 Tm" + - ` 2 3.07 Td (${utf16String}) Tj ET Q EMC` + ` 2 3.38 Td (${utf16String}) Tj ET Q EMC` ); }); @@ -1884,7 +1884,7 @@ describe("annotation", function () { ); expect(appearance).toEqual( "/Tx BMC q BT /Helv 5.92 Tf 0 g 1 0 0 1 0 0 Tm" + - " 2 3.07 Td (test \\(print\\)) Tj ET Q EMC" + " 2 3.49 Td (test \\(print\\)) Tj ET Q EMC" ); }); @@ -1921,7 +1921,7 @@ describe("annotation", function () { "\x30\x53\x30\x93\x30\x6b\x30\x61\x30\x6f\x4e\x16\x75\x4c\x30\x6e"; expect(appearance).toEqual( "/Tx BMC q BT /Goth 3.5 Tf 0 g 1 0 0 1 0 0 Tm" + - ` 2 3.07 Td (${utf16String}) Tj ET Q EMC` + ` 2 3.86 Td (${utf16String}) Tj ET Q EMC` ); }); @@ -2124,7 +2124,7 @@ describe("annotation", function () { annotationStorage ); expect(appearance).toEqual( - "/Tx BMC q BT /Helv 5 Tf 1 0 0 1 0 3.21 Tm" + + "/Tx BMC q BT /Helv 5 Tf 1 0 0 1 0 3.72 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" + @@ -2165,7 +2165,7 @@ describe("annotation", function () { annotationStorage ); expect(appearance).toEqual( - "/Tx BMC q BT /Goth 5 Tf 1 0 0 1 0 2.5 Tm" + + "/Tx BMC q BT /Goth 5 Tf 1 0 0 1 0 3.38 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" + @@ -2209,7 +2209,7 @@ describe("annotation", function () { expect(newData.data).toEqual( "2 0 obj\n<< /Subtype /Form /Resources " + "<< /Font << /Helv 314 0 R>>>> /BBox [0 0 32 10] /Length 74>> stream\n" + - "/Tx BMC q BT /Helv 5 Tf 1 0 0 1 0 0 Tm 2 3.07 Td (hello world) Tj " + + "/Tx BMC q BT /Helv 5 Tf 1 0 0 1 0 0 Tm 2 3.72 Td (hello world) Tj " + "ET Q EMC\nendstream\nendobj\n" ); }); @@ -2327,8 +2327,8 @@ describe("annotation", function () { ); expect(newData.data).toEqual( "2 0 obj\n<< /Subtype /Form /Resources " + - "<< /Font << /Helv 314 0 R>>>> /BBox [0 0 10 32] /Matrix [0 1 -1 0 32 0] /Length 74>> stream\n" + - "/Tx BMC q BT /Helv 5 Tf 1 0 0 1 0 0 Tm 2 2.94 Td (hello world) Tj " + + "<< /Font << /Helv 314 0 R>>>> /BBox [0 0 10 32] /Matrix [0 1 -1 0 32 0] /Length 75>> stream\n" + + "/Tx BMC q BT /Helv 5 Tf 1 0 0 1 0 0 Tm 2 14.72 Td (hello world) Tj " + "ET Q EMC\nendstream\nendobj\n" ); }); @@ -2388,7 +2388,7 @@ describe("annotation", function () { // generated data with the PDF.js library. const flateStream = new FlateStream(new StringStream(compressedStream)); expect(flateStream.getString()).toEqual( - `/Tx BMC q BT /Helv 5 Tf 1 0 0 1 0 0 Tm 2 3.07 Td (${value}) Tj ET Q EMC` + `/Tx BMC q BT /Helv 5 Tf 1 0 0 1 0 0 Tm 2 3.72 Td (${value}) Tj ET Q EMC` ); }); @@ -2513,7 +2513,7 @@ describe("annotation", function () { expect(newData.data).toEqual( "2 0 obj\n<< /Subtype /Form /Resources " + "<< /Font << /Helv 314 0 R /Goth 159 0 R>>>> /BBox [0 0 32 10] /Length 79>> stream\n" + - `/Tx BMC q BT /Goth 5 Tf 1 0 0 1 0 0 Tm 2 3.07 Td (${utf16String}) Tj ` + + `/Tx BMC q BT /Goth 5 Tf 1 0 0 1 0 0 Tm 2 3.38 Td (${utf16String}) Tj ` + "ET Q EMC\nendstream\nendobj\n" ); }); diff --git a/test/unit/obj_bin_transform_spec.js b/test/unit/obj_bin_transform_spec.js index a96c08c8e0c31..7094157d6db32 100644 --- a/test/unit/obj_bin_transform_spec.js +++ b/test/unit/obj_bin_transform_spec.js @@ -28,6 +28,7 @@ import { SystemFontInfo, } from "../../src/display/obj_bin_transform_display.js"; import { FeatureTest } from "../../src/shared/util.js"; +import { InfoUtils } from "../../src/shared/obj_bin_transform_utils.js"; describe("obj_bin_transform", function () { describe("Font data", function () { @@ -80,7 +81,7 @@ describe("obj_bin_transform", function () { describe("font data serialization and deserialization", function () { describe("CssFontInfo", function () { it("must roundtrip correctly for CssFontInfo", function () { - const encoder = new TextEncoder(); + const { encoder } = InfoUtils; let sizeEstimate = 0; for (const string of ["Sample Family", "not a number", "angle"]) { sizeEstimate += 4 + encoder.encode(string).length; @@ -97,7 +98,7 @@ describe("obj_bin_transform", function () { describe("SystemFontInfo", function () { it("must roundtrip correctly for SystemFontInfo", function () { - const encoder = new TextEncoder(); + const { encoder } = InfoUtils; let sizeEstimate = 1 + 4; for (const string of [ "some string", @@ -127,7 +128,7 @@ describe("obj_bin_transform", function () { describe("FontInfo", function () { it("must roundtrip correctly for FontInfo", function () { let sizeEstimate = 92; // fixed offset until the strings - const encoder = new TextEncoder(); + const { encoder } = InfoUtils; sizeEstimate += 4 + 4 * (4 + encoder.encode("string").length); sizeEstimate += 4 + 4; // cssFontInfo and systemFontInfo sizeEstimate += 4 + fontInfo.data.length;