Merge pull request #12759 from calixteman/font_size

Annotation -- Don't compute appearance when nothing has changed
This commit is contained in:
Brendan Dahl 2021-02-12 14:33:55 -08:00 committed by GitHub
commit 3f3b01b710
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
5 changed files with 81 additions and 40 deletions

View File

@ -1268,18 +1268,25 @@ class WidgetAnnotation extends Annotation {
if (!annotationStorage || isPassword) { if (!annotationStorage || isPassword) {
return null; return null;
} }
const value = let value =
annotationStorage[this.data.id] && annotationStorage[this.data.id].value; annotationStorage[this.data.id] && annotationStorage[this.data.id].value;
if (value === undefined) { if (value === undefined) {
// The annotation hasn't been rendered so use the appearance // The annotation hasn't been rendered so use the appearance
return null; return null;
} }
value = value.trim();
if (value === "") { if (value === "") {
// the field is empty: nothing to render // the field is empty: nothing to render
return ""; return "";
} }
let lineCount = -1;
if (this.data.multiLine) {
lineCount = value.split(/\r\n|\r|\n/).length;
}
const defaultPadding = 2; const defaultPadding = 2;
const hPadding = defaultPadding; const hPadding = defaultPadding;
const totalHeight = this.data.rect[3] - this.data.rect[1]; const totalHeight = this.data.rect[3] - this.data.rect[1];
@ -1297,8 +1304,12 @@ class WidgetAnnotation extends Annotation {
); );
} }
const [defaultAppearance, fontSize] = this._computeFontSize(
totalHeight,
lineCount
);
const font = await this._getFontData(evaluator, task); const font = await this._getFontData(evaluator, task);
const fontSize = this._computeFontSize(font, totalHeight);
let descent = font.descent; let descent = font.descent;
if (isNaN(descent)) { if (isNaN(descent)) {
@ -1306,7 +1317,6 @@ class WidgetAnnotation extends Annotation {
} }
const vPadding = defaultPadding + Math.abs(descent) * fontSize; const vPadding = defaultPadding + Math.abs(descent) * fontSize;
const defaultAppearance = this.data.defaultAppearance;
const alignment = this.data.textAlignment; const alignment = this.data.textAlignment;
if (this.data.multiLine) { if (this.data.multiLine) {
@ -1389,35 +1399,44 @@ class WidgetAnnotation extends Annotation {
return initialState.font; return initialState.font;
} }
_computeFontSize(font, height) { _computeFontSize(height, lineCount) {
let fontSize = this.data.defaultAppearanceData.fontSize; let { fontSize } = this.data.defaultAppearanceData;
if (!fontSize) { if (fontSize === null || fontSize === 0) {
const { fontColor, fontName } = this.data.defaultAppearanceData; // A zero value for size means that the font shall be auto-sized:
let capHeight; // its size shall be computed as a function of the height of the
if (font.capHeight) { // annotation rectangle (see 12.7.3.3).
capHeight = font.capHeight;
const roundWithOneDigit = x => Math.round(x * 10) / 10;
// 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);
} else { } else {
const glyphs = font.charsToGlyphs(font.encodeString("M").join("")); // Hard to guess how many lines there are.
if (glyphs.length === 1 && glyphs[0].width) { // The field may have been sized to have 10 lines
const em = glyphs[0].width / 1000; // and the user entered only 1 so if we get font size from
// According to https://en.wikipedia.org/wiki/Em_(typography) // height and number of lines then we'll get something too big.
// an average cap height should be 70% of 1em // So we compute a fake number of lines based on height and
capHeight = 0.7 * em; // a font size equal to 10.
} else { // Then we'll adjust font size to what we have really.
capHeight = 0.7; fontSize = 10;
} let lineHeight = fontSize / FONT_FACTOR;
let numberOfLines = Math.round(height / lineHeight);
numberOfLines = Math.max(numberOfLines, lineCount);
lineHeight = height / numberOfLines;
fontSize = roundWithOneDigit(FONT_FACTOR * lineHeight);
} }
// 1.5 * capHeight * fontSize seems to be a good value for lineHeight const { fontName, fontColor } = this.data.defaultAppearanceData;
fontSize = Math.max(1, Math.floor(height / (1.5 * capHeight)));
this.data.defaultAppearance = createDefaultAppearance({ this.data.defaultAppearance = createDefaultAppearance({
fontSize, fontSize,
fontName, fontName,
fontColor, fontColor,
}); });
} }
return fontSize; return [this.data.defaultAppearance, fontSize];
} }
_renderText(text, font, fontSize, totalWidth, alignment, hPadding, vPadding) { _renderText(text, font, fontSize, totalWidth, alignment, hPadding, vPadding) {

View File

@ -603,7 +603,7 @@ class TextWidgetAnnotationElement extends WidgetAnnotationElement {
// NOTE: We cannot set the values using `element.value` below, since it // NOTE: We cannot set the values using `element.value` below, since it
// prevents the AnnotationLayer rasterizer in `test/driver.js` // prevents the AnnotationLayer rasterizer in `test/driver.js`
// from parsing the elements correctly for the reference tests. // from parsing the elements correctly for the reference tests.
const textContent = storage.getOrCreateValue(id, { const textContent = storage.getValue(id, {
value: this.data.fieldValue, value: this.data.fieldValue,
}).value; }).value;
const elementData = { const elementData = {
@ -873,7 +873,7 @@ class CheckboxWidgetAnnotationElement extends WidgetAnnotationElement {
const storage = this.annotationStorage; const storage = this.annotationStorage;
const data = this.data; const data = this.data;
const id = data.id; const id = data.id;
const value = storage.getOrCreateValue(id, { const value = storage.getValue(id, {
value: value:
data.fieldValue && data.fieldValue &&
((data.exportValue && data.exportValue === data.fieldValue) || ((data.exportValue && data.exportValue === data.fieldValue) ||
@ -962,7 +962,7 @@ class RadioButtonWidgetAnnotationElement extends WidgetAnnotationElement {
const storage = this.annotationStorage; const storage = this.annotationStorage;
const data = this.data; const data = this.data;
const id = data.id; const id = data.id;
const value = storage.getOrCreateValue(id, { const value = storage.getValue(id, {
value: data.fieldValue === data.buttonValue, value: data.fieldValue === data.buttonValue,
}).value; }).value;
@ -1072,9 +1072,9 @@ class ChoiceWidgetAnnotationElement extends WidgetAnnotationElement {
// are not properly printed/saved yet, so we only store the first item in // are not properly printed/saved yet, so we only store the first item in
// the field value array instead of the entire array. Once support for those // the field value array instead of the entire array. Once support for those
// two field types is implemented, we should use the same pattern as the // two field types is implemented, we should use the same pattern as the
// other interactive widgets where the return value of `getOrCreateValue` is // other interactive widgets where the return value of `getValue`
// used and the full array of field values is stored. // is used and the full array of field values is stored.
storage.getOrCreateValue(id, { storage.getValue(id, {
value: value:
this.data.fieldValue.length > 0 ? this.data.fieldValue[0] : undefined, this.data.fieldValue.length > 0 ? this.data.fieldValue[0] : undefined,
}); });

View File

@ -13,6 +13,7 @@
* limitations under the License. * limitations under the License.
*/ */
import { deprecated } from "./display_utils.js";
import { objectFromEntries } from "../shared/util.js"; import { objectFromEntries } from "../shared/util.js";
/** /**
@ -33,7 +34,7 @@ class AnnotationStorage {
/** /**
* Get the value for a given key if it exists * Get the value for a given key if it exists
* or store and return the default value * or return the default value
* *
* @public * @public
* @memberof AnnotationStorage * @memberof AnnotationStorage
@ -41,7 +42,19 @@ class AnnotationStorage {
* @param {Object} defaultValue * @param {Object} defaultValue
* @returns {Object} * @returns {Object}
*/ */
getValue(key, defaultValue) {
if (this._storage.has(key)) {
return this._storage.get(key);
}
return defaultValue;
}
/**
* @deprecated
*/
getOrCreateValue(key, defaultValue) { getOrCreateValue(key, defaultValue) {
deprecated("Use getValue instead.");
if (this._storage.has(key)) { if (this._storage.has(key)) {
return this._storage.get(key); return this._storage.get(key);
} }

View File

@ -1808,7 +1808,7 @@ describe("annotation", function () {
}, done.fail) }, done.fail)
.then(appearance => { .then(appearance => {
expect(appearance).toEqual( expect(appearance).toEqual(
"/Tx BMC q BT /Helv 11 Tf 0 g 1 0 0 1 0 0 Tm" + "/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" " 2.00 2.00 Td (test \\(print\\)) Tj ET Q EMC"
); );
done(); done();
@ -1848,7 +1848,7 @@ describe("annotation", function () {
"\x30\x53\x30\x93\x30\x6b\x30\x61" + "\x30\x53\x30\x93\x30\x6b\x30\x61" +
"\x30\x6f\x4e\x16\x75\x4c\x30\x6e"; "\x30\x6f\x4e\x16\x75\x4c\x30\x6e";
expect(appearance).toEqual( expect(appearance).toEqual(
"/Tx BMC q BT /Goth 9 Tf 0 g 1 0 0 1 0 0 Tm" + "/Tx BMC q BT /Goth 8 Tf 0 g 1 0 0 1 0 0 Tm" +
` 2.00 2.00 Td (${utf16String}) Tj ET Q EMC` ` 2.00 2.00 Td (${utf16String}) Tj ET Q EMC`
); );
done(); done();

View File

@ -16,17 +16,21 @@
import { AnnotationStorage } from "../../src/display/annotation_storage.js"; import { AnnotationStorage } from "../../src/display/annotation_storage.js";
describe("AnnotationStorage", function () { describe("AnnotationStorage", function () {
describe("GetOrCreateValue", function () { describe("GetOrDefaultValue", function () {
it("should get and set a new value in the annotation storage", function (done) { it("should get and set a new value in the annotation storage", function (done) {
const annotationStorage = new AnnotationStorage(); const annotationStorage = new AnnotationStorage();
let value = annotationStorage.getOrCreateValue("123A", { let value = annotationStorage.getValue("123A", {
value: "hello world", value: "hello world",
}).value; }).value;
expect(value).toEqual("hello world"); expect(value).toEqual("hello world");
annotationStorage.setValue("123A", {
value: "hello world",
});
// the second argument is the default value to use // the second argument is the default value to use
// if the key isn't in the storage // if the key isn't in the storage
value = annotationStorage.getOrCreateValue("123A", { value = annotationStorage.getValue("123A", {
value: "an other string", value: "an other string",
}).value; }).value;
expect(value).toEqual("hello world"); expect(value).toEqual("hello world");
@ -50,16 +54,18 @@ describe("AnnotationStorage", function () {
called = true; called = true;
}; };
annotationStorage.onSetModified = callback; annotationStorage.onSetModified = callback;
annotationStorage.getOrCreateValue("asdf", { value: "original" });
expect(called).toBe(false);
// not changing value
annotationStorage.setValue("asdf", { value: "original" }); annotationStorage.setValue("asdf", { value: "original" });
expect(called).toBe(false); expect(called).toBe(true);
// changing value // changing value
annotationStorage.setValue("asdf", { value: "modified" }); annotationStorage.setValue("asdf", { value: "modified" });
expect(called).toBe(true); expect(called).toBe(true);
// not changing value
called = false;
annotationStorage.setValue("asdf", { value: "modified" });
expect(called).toBe(false);
done(); done();
}); });
}); });
@ -72,7 +78,10 @@ describe("AnnotationStorage", function () {
called = true; called = true;
}; };
annotationStorage.onResetModified = callback; annotationStorage.onResetModified = callback;
annotationStorage.getOrCreateValue("asdf", { value: "original" }); annotationStorage.setValue("asdf", { value: "original" });
annotationStorage.resetModified();
expect(called).toBe(true);
called = false;
// not changing value // not changing value
annotationStorage.setValue("asdf", { value: "original" }); annotationStorage.setValue("asdf", { value: "original" });