From ae842e1c3a75b34344e5c37ca09e497bc49a0d24 Mon Sep 17 00:00:00 2001
From: Calixte Denizet <calixte.denizet@gmail.com>
Date: Thu, 27 Jan 2022 22:51:30 +0100
Subject: [PATCH] [api-minor] Annotations - Adjust the font size in text field
 in considering the total width (bug 1721335)  - it aims to fix #14502 and bug
 1721335;  - Acrobat and Pdfium do the same;  - it'll avoid to have truncated
 data when printed;  - change the factor to compute font size in using field
 height: lineHeight = 1.35*fontSize   - this is the value used by Acrobat.  -
 in order to not have truncated strings on the bottom, add few basic metrics
 for standard fonts.

---
 src/core/annotation.js       |  98 +++++++++++++++++++++++++----------
 src/core/fonts.js            |  16 ++++++
 src/core/metrics.js          |  89 ++++++++++++++++++++++++++++++-
 test/pdfs/.gitignore         |   1 +
 test/pdfs/issue14502.pdf     | Bin 0 -> 7255 bytes
 test/test_manifest.json      |  21 ++++++++
 test/unit/annotation_spec.js |  23 ++++----
 test/unit/api_spec.js        |   6 ++-
 8 files changed, 214 insertions(+), 40 deletions(-)
 create mode 100755 test/pdfs/issue14502.pdf

diff --git a/src/core/annotation.js b/src/core/annotation.js
index 601d3cbe8..b63bb274d 100644
--- a/src/core/annotation.js
+++ b/src/core/annotation.js
@@ -1523,13 +1523,15 @@ class WidgetAnnotation extends Annotation {
       );
     }
 
+    const font = await this._getFontData(evaluator, task);
     const [defaultAppearance, fontSize] = this._computeFontSize(
-      totalHeight,
+      totalHeight - defaultPadding,
+      totalWidth - 2 * hPadding,
+      value,
+      font,
       lineCount
     );
 
-    const font = await this._getFontData(evaluator, task);
-
     let descent = font.descent;
     if (isNaN(descent)) {
       descent = 0;
@@ -1618,34 +1620,84 @@ class WidgetAnnotation extends Annotation {
     return initialState.font;
   }
 
-  _computeFontSize(height, lineCount) {
+  _getTextWidth(text, font) {
+    return (
+      font
+        .charsToGlyphs(text)
+        .reduce((width, glyph) => width + glyph.width, 0) / 1000
+    );
+  }
+
+  _computeFontSize(height, width, text, font, lineCount) {
     let { fontSize } = this.data.defaultAppearanceData;
     if (!fontSize) {
       // A zero value for size means that the font shall be auto-sized:
       // its size shall be computed as a function of the height of the
       // annotation rectangle (see 12.7.3.3).
 
-      const roundWithOneDigit = x => Math.round(x * 10) / 10;
+      const roundWithTwoDigits = x => Math.floor(x * 100) / 100;
+
+      // Represent the percentage of the height of a single-line field over
+      // the font size.
+      // Acrobat seems to use this value.
+      const LINE_FACTOR = 1.35;
 
-      // Represent the percentage of the font size over the height
-      // of a single-line field.
-      const FONT_FACTOR = 0.8;
       if (lineCount === -1) {
-        fontSize = roundWithOneDigit(FONT_FACTOR * height);
+        const textWidth = this._getTextWidth(text, font);
+        fontSize = roundWithTwoDigits(
+          Math.min(height / LINE_FACTOR, width / textWidth)
+        );
       } else {
+        const lines = text.split(/\r\n?|\n/);
+        const cachedLines = [];
+        for (const line of lines) {
+          const encoded = font.encodeString(line).join("");
+          const glyphs = font.charsToGlyphs(encoded);
+          const positions = font.getCharPositions(encoded);
+          cachedLines.push({
+            line: encoded,
+            glyphs,
+            positions,
+          });
+        }
+
+        const isTooBig = fsize => {
+          // Return true when the text doesn't fit the given height.
+          let totalHeight = 0;
+          for (const cache of cachedLines) {
+            const chunks = this._splitLine(null, font, fsize, width, cache);
+            totalHeight += chunks.length * fsize;
+            if (totalHeight > height) {
+              return true;
+            }
+          }
+          return false;
+        };
+
         // Hard to guess how many lines there are.
         // The field may have been sized to have 10 lines
         // and the user entered only 1 so if we get font size from
         // height and number of lines then we'll get something too big.
         // So we compute a fake number of lines based on height and
-        // a font size equal to 10.
+        // a font size equal to 12 (this is the default font size in
+        // Acrobat).
         // Then we'll adjust font size to what we have really.
-        fontSize = 10;
-        let lineHeight = fontSize / FONT_FACTOR;
+        fontSize = 12;
+        let lineHeight = fontSize * LINE_FACTOR;
         let numberOfLines = Math.round(height / lineHeight);
         numberOfLines = Math.max(numberOfLines, lineCount);
-        lineHeight = height / numberOfLines;
-        fontSize = roundWithOneDigit(FONT_FACTOR * lineHeight);
+
+        while (true) {
+          lineHeight = height / numberOfLines;
+          fontSize = roundWithTwoDigits(lineHeight / LINE_FACTOR);
+
+          if (isTooBig(fontSize)) {
+            numberOfLines++;
+            continue;
+          }
+
+          break;
+        }
       }
 
       const { fontName, fontColor } = this.data.defaultAppearanceData;
@@ -1660,13 +1712,7 @@ class WidgetAnnotation extends Annotation {
 
   _renderText(text, font, fontSize, totalWidth, alignment, hPadding, vPadding) {
     // We need to get the width of the text in order to align it correctly
-    const glyphs = font.charsToGlyphs(text);
-    const scale = fontSize / 1000;
-    let width = 0;
-    for (const glyph of glyphs) {
-      width += glyph.width * scale;
-    }
-
+    const width = this._getTextWidth(text, font) * fontSize;
     let shift;
     if (alignment === 1) {
       // Center
@@ -1803,7 +1849,7 @@ class TextWidgetAnnotation extends WidgetAnnotation {
     hPadding,
     vPadding
   ) {
-    const lines = text.split(/\r\n|\r|\n/);
+    const lines = text.split(/\r\n?|\n/);
     const buf = [];
     const totalWidth = width - 2 * hPadding;
     for (const line of lines) {
@@ -1833,18 +1879,18 @@ class TextWidgetAnnotation extends WidgetAnnotation {
     );
   }
 
-  _splitLine(line, font, fontSize, width) {
+  _splitLine(line, font, fontSize, width, cache = {}) {
     // TODO: need to handle chars which are not in the font.
-    line = font.encodeString(line).join("");
+    line = cache.line || font.encodeString(line).join("");
 
-    const glyphs = font.charsToGlyphs(line);
+    const glyphs = cache.glyphs || font.charsToGlyphs(line);
 
     if (glyphs.length <= 1) {
       // Nothing to split
       return [line];
     }
 
-    const positions = font.getCharPositions(line);
+    const positions = cache.positions || font.getCharPositions(line);
     const scale = fontSize / 1000;
     const chunks = [];
 
diff --git a/src/core/fonts.js b/src/core/fonts.js
index a1c694455..555a052ed 100644
--- a/src/core/fonts.js
+++ b/src/core/fonts.js
@@ -59,6 +59,7 @@ import {
 import { IdentityToUnicodeMap, ToUnicodeMap } from "./to_unicode_map.js";
 import { CFFFont } from "./cff_font.js";
 import { FontRendererFactory } from "./font_renderer.js";
+import { getFontBasicMetrics } from "./metrics.js";
 import { GlyfTable } from "./glyf.js";
 import { IdentityCMap } from "./cmap.js";
 import { OpenTypeFileBuilder } from "./opentype_file_builder.js";
@@ -1074,6 +1075,21 @@ class Font {
     );
 
     fontName = stdFontMap[fontName] || nonStdFontMap[fontName] || fontName;
+
+    const fontBasicMetricsMap = getFontBasicMetrics();
+    const metrics = fontBasicMetricsMap[fontName];
+    if (metrics) {
+      if (isNaN(this.ascent)) {
+        this.ascent = metrics.ascent / PDF_GLYPH_SPACE_UNITS;
+      }
+      if (isNaN(this.descent)) {
+        this.descent = metrics.descent / PDF_GLYPH_SPACE_UNITS;
+      }
+      if (isNaN(this.capHeight)) {
+        this.capHeight = metrics.capHeight / PDF_GLYPH_SPACE_UNITS;
+      }
+    }
+
     this.bold = fontName.search(/bold/gi) !== -1;
     this.italic =
       fontName.search(/oblique/gi) !== -1 || fontName.search(/italic/gi) !== -1;
diff --git a/src/core/metrics.js b/src/core/metrics.js
index 2d8650142..c61c86080 100644
--- a/src/core/metrics.js
+++ b/src/core/metrics.js
@@ -2967,4 +2967,91 @@ const getMetrics = getLookupTableFactory(function (t) {
   });
 });
 
-export { getMetrics };
+const getFontBasicMetrics = getLookupTableFactory(function (t) {
+  t.Courier = {
+    ascent: 629,
+    descent: -157,
+    capHeight: 562,
+    xHeight: -426,
+  };
+  t["Courier-Bold"] = {
+    ascent: 629,
+    descent: -157,
+    capHeight: 562,
+    xHeight: 439,
+  };
+  t["Courier-Oblique"] = {
+    ascent: 629,
+    descent: -157,
+    capHeight: 562,
+    xHeight: 426,
+  };
+  t["Courier-BoldOblique"] = {
+    ascent: 629,
+    descent: -157,
+    capHeight: 562,
+    xHeight: 426,
+  };
+  t.Helvetica = {
+    ascent: 718,
+    descent: -207,
+    capHeight: 718,
+    xHeight: 523,
+  };
+  t["Helvetica-Bold"] = {
+    ascent: 718,
+    descent: -207,
+    capHeight: 718,
+    xHeight: 532,
+  };
+  t["Helvetica-Oblique"] = {
+    ascent: 718,
+    descent: -207,
+    capHeight: 718,
+    xHeight: 523,
+  };
+  t["Helvetica-BoldOblique"] = {
+    ascent: 718,
+    descent: -207,
+    capHeight: 718,
+    xHeight: 532,
+  };
+  t["Times-Roman"] = {
+    ascent: 683,
+    descent: -217,
+    capHeight: 662,
+    xHeight: 450,
+  };
+  t["Times-Bold"] = {
+    ascent: 683,
+    descent: -217,
+    capHeight: 676,
+    xHeight: 461,
+  };
+  t["Times-Italic"] = {
+    ascent: 683,
+    descent: -217,
+    capHeight: 653,
+    xHeight: 441,
+  };
+  t["Times-BoldItalic"] = {
+    ascent: 683,
+    descent: -217,
+    capHeight: 669,
+    xHeight: 462,
+  };
+  t.Symbol = {
+    ascent: Math.NaN,
+    descent: Math.NaN,
+    capHeight: Math.NaN,
+    xHeight: Math.NaN,
+  };
+  t.ZapfDingbats = {
+    ascent: Math.NaN,
+    descent: Math.NaN,
+    capHeight: Math.NaN,
+    xHeight: Math.NaN,
+  };
+});
+
+export { getFontBasicMetrics, getMetrics };
diff --git a/test/pdfs/.gitignore b/test/pdfs/.gitignore
index fcbab5b3d..d1d37ef26 100644
--- a/test/pdfs/.gitignore
+++ b/test/pdfs/.gitignore
@@ -508,3 +508,4 @@
 !issue14415.pdf
 !issue14307.pdf
 !issue14497.pdf
+!issue14502.pdf
diff --git a/test/pdfs/issue14502.pdf b/test/pdfs/issue14502.pdf
new file mode 100755
index 0000000000000000000000000000000000000000..2b8a0f168ebe032e34d626fe99cf195641249121
GIT binary patch
literal 7255
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

literal 0
HcmV?d00001

diff --git a/test/test_manifest.json b/test/test_manifest.json
index e91c3f6fe..61c8f04db 100644
--- a/test/test_manifest.json
+++ b/test/test_manifest.json
@@ -6250,5 +6250,26 @@
       "md5": "7f795a92caa612117b6928f8bb4c5b65",
       "rounds": 1,
       "type": "text"
+   },
+   {  "id": "issue14052",
+      "file": "pdfs/issue14502.pdf",
+      "md5": "7085cdc31243ab0b979f0c5fa151e491",
+      "rounds": 1,
+      "type": "eq",
+      "print": true,
+      "annotationStorage": {
+        "27R": {
+         "value": "Hello PDF.js World"
+        },
+        "28R": {
+         "value": "PDF.js PDF.js PDF.js PDF.js\nPDF.js PDF.js PDF.js PDF.js\nPDF.js PDF.js PDF.js PDF.js\nPDF.js PDF.js PDF.js PDF.js"
+        },
+        "29R": {
+         "value": "PDF.js"
+        },
+        "30R": {
+         "value": "PDF.js PDF.js PDF.js"
+        }
+      }
    }
 ]
diff --git a/test/unit/annotation_spec.js b/test/unit/annotation_spec.js
index 8767e5aca..39d126b52 100644
--- a/test/unit/annotation_spec.js
+++ b/test/unit/annotation_spec.js
@@ -1614,7 +1614,7 @@ describe("annotation", function () {
       );
       expect(appearance).toEqual(
         "/Tx BMC q BT /Helv 5 Tf 1 0 0 1 0 0 Tm" +
-          " 2.00 2.00 Td (test\\\\print) Tj ET Q EMC"
+          " 2.00 3.04 Td (test\\\\print) Tj ET Q EMC"
       );
     });
 
@@ -1732,8 +1732,8 @@ describe("annotation", function () {
         annotationStorage
       );
       expect(appearance).toEqual(
-        "/Tx BMC q BT /Helv 8 Tf 0 g 1 0 0 1 0 0 Tm" +
-          " 2.00 2.00 Td (test \\(print\\)) Tj ET Q EMC"
+        "/Tx BMC q BT /Helv 5.92 Tf 0 g 1 0 0 1 0 0 Tm" +
+          " 2.00 3.23 Td (test \\(print\\)) Tj ET Q EMC"
       );
     });
 
@@ -1768,7 +1768,7 @@ describe("annotation", function () {
       const utf16String =
         "\x30\x53\x30\x93\x30\x6b\x30\x61\x30\x6f\x4e\x16\x75\x4c\x30\x6e";
       expect(appearance).toEqual(
-        "/Tx BMC q BT /Goth 8 Tf 0 g 1 0 0 1 0 0 Tm" +
+        "/Tx BMC q BT /Goth 3.5 Tf 0 g 1 0 0 1 0 0 Tm" +
           ` 2.00 2.00 Td (${utf16String}) Tj ET Q EMC`
       );
     });
@@ -1966,7 +1966,7 @@ describe("annotation", function () {
         annotationStorage
       );
       expect(appearance).toEqual(
-        "/Tx BMC q BT /Helv 5 Tf 1 0 0 1 2 2 Tm" +
+        "/Tx BMC q BT /Helv 5 Tf 1 0 0 1 2 3.035 Tm" +
           " (a) Tj 8.00 0 Td (a) Tj 8.00 0 Td (\\() Tj" +
           " 8.00 0 Td (a) Tj 8.00 0 Td (a) Tj" +
           " 8.00 0 Td (\\)) Tj 8.00 0 Td (a) Tj" +
@@ -2052,7 +2052,7 @@ describe("annotation", function () {
       expect(newData.data).toEqual(
         "2 0 obj\n<< /Length 77 /Subtype /Form /Resources " +
           "<< /Font << /Helv 314 0 R>>>> /BBox [0 0 32 10]>> stream\n" +
-          "/Tx BMC q BT /Helv 5 Tf 1 0 0 1 0 0 Tm 2.00 2.00 Td (hello world) Tj " +
+          "/Tx BMC q BT /Helv 5 Tf 1 0 0 1 0 0 Tm 2.00 3.04 Td (hello world) Tj " +
           "ET Q EMC\nendstream\nendobj\n"
       );
     });
@@ -3377,7 +3377,7 @@ describe("annotation", function () {
       );
       expect(appearance).toEqual(
         "/Tx BMC q BT /Helv 5 Tf 1 0 0 1 0 0 Tm" +
-          " 2.00 2.00 Td (a value) Tj ET Q EMC"
+          " 2.00 3.04 Td (a value) Tj ET Q EMC"
       );
     });
 
@@ -3388,6 +3388,7 @@ describe("annotation", function () {
       const choiceWidgetRef = Ref.get(123, 0);
       const xref = new XRefMock([
         { ref: choiceWidgetRef, data: choiceWidgetDict },
+        fontRefObj,
       ]);
       partialEvaluator.xref = xref;
       const task = new WorkerTask("test save");
@@ -3409,7 +3410,7 @@ describe("annotation", function () {
       expect(data.length).toEqual(2);
       const [oldData, newData] = data;
       expect(oldData.ref).toEqual(Ref.get(123, 0));
-      expect(newData.ref).toEqual(Ref.get(1, 0));
+      expect(newData.ref).toEqual(Ref.get(2, 0));
 
       oldData.data = oldData.data.replace(/\(D:\d+\)/, "(date)");
       expect(oldData.data).toEqual(
@@ -3417,13 +3418,13 @@ describe("annotation", function () {
           "<< /Type /Annot /Subtype /Widget /FT /Ch /DA (/Helv 5 Tf) /DR " +
           "<< /Font << /Helv 314 0 R>>>> " +
           "/Rect [0 0 32 10] /Opt [(A) (B) (C)] /V (C) " +
-          "/AP << /N 1 0 R>> /M (date)>>\nendobj\n"
+          "/AP << /N 2 0 R>> /M (date)>>\nendobj\n"
       );
       expect(newData.data).toEqual(
-        "1 0 obj\n" +
+        "2 0 obj\n" +
           "<< /Length 67 /Subtype /Form /Resources << /Font << /Helv 314 0 R>>>> " +
           "/BBox [0 0 32 10]>> stream\n" +
-          "/Tx BMC q BT /Helv 5 Tf 1 0 0 1 0 0 Tm 2.00 2.00 Td (C) Tj ET Q EMC\n" +
+          "/Tx BMC q BT /Helv 5 Tf 1 0 0 1 0 0 Tm 2.00 3.04 Td (C) Tj ET Q EMC\n" +
           "endstream\nendobj\n"
       );
     });
diff --git a/test/unit/api_spec.js b/test/unit/api_spec.js
index 86c63675f..293c4563c 100644
--- a/test/unit/api_spec.js
+++ b/test/unit/api_spec.js
@@ -2010,8 +2010,10 @@ page 1 / 3`);
       });
       expect(styles[fontName]).toEqual({
         fontFamily: "serif",
-        ascent: NaN,
-        descent: NaN,
+        // `useSystemFonts` has a different value in web environments
+        // and in Node.js.
+        ascent: isNodeJS ? NaN : 0.683,
+        descent: isNodeJS ? NaN : -0.217,
         vertical: false,
       });