Compare commits

...
8 Commits
Author SHA1 Message Date
calixteman c33c32aed4 Merge pull request #22049 from calixteman/update_openjpeg3
Update OpenJpeg to 8314119b067c0fc77834731168daaebd379fdb12
2026-10-01 17:17:58 +02:00
calixteman d9080794fa Merge pull request #22034 from calixteman/sandbox-resetform-non-terminal
Reset the descendants of non-terminal fields in Doc.resetForm
2026-10-01 16:11:29 +02:00
calixteman f786355550 Update OpenJpeg to 8314119b067c0fc77834731168daaebd379fdb12 2026-10-01 15:43:30 +02:00
calixteman e3e606de23 Merge pull request #22047 from calixteman/vertical-default-vx
Use the glyph width for the default vertical origin
2026-10-01 15:05:55 +02:00
Jonas Jenwald 62de7c55e1 Merge pull request #22042 from Snuffleupagus/rm-defaultVMetrics-export
Stop exporting the Font `defaultVMetrics` by default
2026-10-01 14:09:30 +02:00
calixteman 29a06285dc Use the glyph width for the default vertical origin
Without a W2 entry, use half the glyph's horizontal width for vx
(PDF 32000-1, 9.7.4.3), retaining the DW2 vertical metrics.

Follow-up to #20933.
2026-10-01 13:21:23 +02:00
Jonas Jenwald a02decd96c Stop exporting the Font defaultVMetrics by default
After PR 20933 the `Glyph.prototype.vmetric` property is now guaranteed to be always be defined for vertical fonts, since it'll fallback to the `defaultVMetrics` property; see https://github.com/mozilla/pdf.js/blob/18e8a26a3813a319b38c806076f0b0ef9baf1bf4/src/core/fonts.js#L3564
Hence it's no longer necessary to export the `defaultVMetrics` as part of the compiled Font info-data.

Additionally, with `Glyph.prototype.vmetric` always being defined, there's fallback code in both the worker/main-thread that should no longer be necessary.
2026-10-01 10:43:12 +02:00
calixteman 40cc2082df Reset the descendants of non-terminal fields in Doc.resetForm
When the array given to `resetForm` contains the name of a non-terminal
field, only that field was reset, so its terminal descendants kept their
values. It's the sandbox counterpart of #22014.

Walk the `_kidIds` of the collected fields and add every descendant
before resetting them.
2026-09-29 17:29:36 +02:00
14 changed files with 138 additions and 57 deletions
+2 -2
View File
File diff suppressed because one or more lines are too long
BIN
View File
Binary file not shown.
File diff suppressed because one or more lines are too long
+1 -4
View File
@@ -3004,10 +3004,7 @@ class PartialEvaluator {
} }
let charSpacing = baseCharSpacing + (i + 1 === ii ? extraSpacing : 0); let charSpacing = baseCharSpacing + (i + 1 === ii ? extraSpacing : 0);
let glyphWidth = glyph.width; const glyphWidth = font.vertical ? glyph.vmetric[0] : glyph.width;
if (font.vertical) {
glyphWidth = glyph.vmetric ? glyph.vmetric[0] : -glyphWidth;
}
let scaledDim = glyphWidth * scale; let scaledDim = glyphWidth * scale;
if (originalCharCode === 0x20) { if (originalCharCode === 0x20) {
+7 -2
View File
@@ -87,7 +87,6 @@ const EXPORT_DATA_PROPERTIES = [
// "charProcOperatorList" is handled separately, since it's not compiled. // "charProcOperatorList" is handled separately, since it's not compiled.
"cssFontInfo", "cssFontInfo",
"data", "data",
"defaultVMetrics",
"descent", "descent",
"disableFontFace", "disableFontFace",
"fallbackName", "fallbackName",
@@ -108,6 +107,7 @@ const EXPORT_DATA_PROPERTIES = [
const EXPORT_DATA_EXTRA_PROPERTIES = [ const EXPORT_DATA_EXTRA_PROPERTIES = [
"composite", "composite",
"defaultEncoding", "defaultEncoding",
"defaultVMetrics",
"defaultWidth", "defaultWidth",
"differences", "differences",
"isMonospace", "isMonospace",
@@ -3561,7 +3561,12 @@ class Font {
if (typeof width !== "number") { if (typeof width !== "number") {
width = this.defaultWidth; width = this.defaultWidth;
} }
const vmetric = this.vmetrics?.[widthCode] || this.defaultVMetrics; let vmetric = this.vmetrics?.[widthCode];
if (!vmetric && this.defaultVMetrics) {
// Without a W2 entry, vx is half the glyph width (PDF 32000-1, 9.7.4.3).
const [w1y, , vy] = this.defaultVMetrics;
vmetric = [w1y, width * 0.5, vy];
}
let unicode = this.toUnicode.get(charcode) || charcode; let unicode = this.toUnicode.get(charcode) || charcode;
if (typeof unicode === "number") { if (typeof unicode === "number") {
+1 -12
View File
@@ -193,20 +193,9 @@ function compileFontInfo(font) {
/* writerName = */ "setFloat64", /* writerName = */ "setFloat64",
/* increment = */ 8 /* increment = */ 8
); );
assert(
offset === FONT_INFO.OFFSET_DEFAULT_VMETRICS,
"compileFontInfo: FontMatrix properties offset mismatch"
);
writeArray(
/* arr = */ font.defaultVMetrics,
/* arrLen = */ 3,
/* writerName = */ "setInt16",
/* increment = */ 2
);
assert( assert(
offset === FONT_INFO.OFFSET_STRINGS, offset === FONT_INFO.OFFSET_STRINGS,
"compileFontInfo: DefaultVMetrics properties offset mismatch" "compileFontInfo: FontMatrix properties offset mismatch"
); );
view.setUint32(offset, stringsLength); view.setUint32(offset, stringsLength);
+3 -5
View File
@@ -2772,7 +2772,6 @@ class CanvasGraphics {
const glyphsLength = glyphs.length; const glyphsLength = glyphs.length;
const vertical = font.vertical; const vertical = font.vertical;
const spacingDir = vertical ? 1 : -1; const spacingDir = vertical ? 1 : -1;
const defaultVMetrics = font.defaultVMetrics;
const widthAdvanceScale = fontSize * current.fontMatrix[0]; const widthAdvanceScale = fontSize * current.fontMatrix[0];
const simpleFillText = const simpleFillText =
@@ -2896,12 +2895,11 @@ class CanvasGraphics {
let scaledX, scaledY; let scaledX, scaledY;
let width = glyph.width; let width = glyph.width;
if (vertical) { if (vertical) {
const vmetric = glyph.vmetric || defaultVMetrics; const vmetric = glyph.vmetric;
const vx = const vx = -vmetric[1] * widthAdvanceScale;
-(glyph.vmetric ? vmetric[1] : width * 0.5) * widthAdvanceScale;
const vy = vmetric[2] * widthAdvanceScale; const vy = vmetric[2] * widthAdvanceScale;
width = vmetric ? -vmetric[0] : width; width = -vmetric[0];
scaledX = vx / fontSizeScale; scaledX = vx / fontSizeScale;
scaledY = (x + vy) / fontSizeScale; scaledY = (x + vy) / fontSizeScale;
} else { } else {
-4
View File
@@ -589,10 +589,6 @@ class FontFaceObject {
get systemFontInfo() { get systemFontInfo() {
return this.#fontData.systemFontInfo; return this.#fontData.systemFontInfo;
} }
get defaultVMetrics() {
return this.#fontData.defaultVMetrics;
}
} }
export { FontFaceObject, FontLoader }; export { FontFaceObject, FontLoader };
-9
View File
@@ -206,15 +206,6 @@ class FontInfo {
); );
} }
get defaultVMetrics() {
return this.#readArray(
/* offset = */ FONT_INFO.OFFSET_DEFAULT_VMETRICS,
/* arrLen = */ 3,
/* lookup = */ "getInt16",
/* increment = */ 2
);
}
#readString(index) { #readString(index) {
assert(index < FONT_INFO.strings.length, "Invalid string index"); assert(index < FONT_INFO.strings.length, "Invalid string index");
return readString( return readString(
+14 -3
View File
@@ -1236,7 +1236,7 @@ class Doc extends PDFObject {
let mustCalculate = false; let mustCalculate = false;
let fieldsToReset; let fieldsToReset;
if (aFields) { if (aFields) {
fieldsToReset = []; fieldsToReset = new Set();
for (const fieldName of aFields) { for (const fieldName of aFields) {
if (!fieldName) { if (!fieldName) {
continue; continue;
@@ -1250,12 +1250,23 @@ class Doc extends PDFObject {
if (!field) { if (!field) {
continue; continue;
} }
fieldsToReset.push(field); fieldsToReset.add(field);
mustCalculate = true; mustCalculate = true;
} }
} }
if (!fieldsToReset) { if (fieldsToReset) {
// A non-terminal field stands for all its descendants, hence add the
// kids (the Set iterator also visits the newly added fields).
for (const { obj } of fieldsToReset) {
for (const id of obj._kidIds || []) {
const kid = obj._appObjects[id];
if (kid) {
fieldsToReset.add(kid);
}
}
}
} else {
fieldsToReset = this._fields.values(); fieldsToReset = this._fields.values();
mustCalculate = this._fields.size !== 0; mustCalculate = this._fields.size !== 0;
} }
+1 -3
View File
@@ -47,9 +47,7 @@ class FONT_INFO {
static OFFSET_FONT_MATRIX = this.OFFSET_BBOX + 1 + 2 * 4; static OFFSET_FONT_MATRIX = this.OFFSET_BBOX + 1 + 2 * 4;
static OFFSET_DEFAULT_VMETRICS = this.OFFSET_FONT_MATRIX + 1 + 8 * 6; static OFFSET_STRINGS = this.OFFSET_FONT_MATRIX + 1 + 8 * 6;
static OFFSET_STRINGS = this.OFFSET_DEFAULT_VMETRICS + 1 + 2 * 3;
} }
class PATTERN_INFO { class PATTERN_INFO {
+49
View File
@@ -14,9 +14,58 @@
*/ */
import { Font } from "../../src/core/fonts.js"; import { Font } from "../../src/core/fonts.js";
import { IdentityCMap } from "../../src/core/cmap.js";
import { IdentityToUnicodeMap } from "../../src/core/to_unicode_map.js"; import { IdentityToUnicodeMap } from "../../src/core/to_unicode_map.js";
describe("Font", () => { describe("Font", () => {
describe("charsToGlyphs", () => {
// Match the sparse metric arrays from PartialEvaluator.extractWidths.
function createFont(vertical) {
return new Font(
"TestFont",
null,
{
type: "CIDFontType2",
composite: true,
vertical,
cMap: new IdentityCMap(vertical, 2),
cidEncoding: vertical ? "Identity-V" : "Identity-H",
toUnicode: new IdentityToUnicodeMap(65, 68),
differences: new Map(),
defaultEncoding: [],
widths: Object.assign([], { 65: 600, 66: 0, 68: 800 }),
defaultWidth: 1000,
vmetrics: vertical ? Object.assign([], { 68: [-800, 175, 700] }) : [],
defaultVMetrics: vertical ? [-1200, 500, 900] : undefined,
},
{}
);
}
it("should use half the glyph width for the default vertical origin", () => {
const font = createFont(/* vertical = */ true);
const glyphs = font.charsToGlyphs("\x00A\x00B\x00C\x00D");
// Cover per-glyph widths, zero width, DW fallback, and explicit W2.
expect(glyphs.map(glyph => glyph.vmetric)).toEqual([
[-1200, 300, 900],
[-1200, 0, 900],
[-1200, 500, 900],
[-800, 175, 700],
]);
expect(font.defaultVMetrics).toEqual([-1200, 500, 900]);
});
it("should not set a vertical metric for horizontal fonts", () => {
const font = createFont(/* vertical = */ false);
const glyphs = font.charsToGlyphs("\x00A\x00B\x00C\x00D");
for (const glyph of glyphs) {
expect(glyph.vmetric).toBeUndefined();
}
});
});
describe("encodeString", () => { describe("encodeString", () => {
// `encodeString` only reads `this.toUnicode` and `this.cMap`, so a // `encodeString` only reads `this.toUnicode` and `this.cMap`, so a
// full `Font` (which needs a complete properties/font-file setup) isn't // full `Font` (which needs a complete properties/font-file setup) isn't
+1 -3
View File
@@ -68,7 +68,6 @@ describe("obj_bin_transform", function () {
descent: -1, descent: -1,
bbox: [1, 1, 1, 1], bbox: [1, 1, 1, 1],
fontMatrix: [1, 1, 1, 1, 1, 1], fontMatrix: [1, 1, 1, 1, 1, 1],
defaultVMetrics: [1, 1, 1],
fallbackName: "string", fallbackName: "string",
loadedName: "string", loadedName: "string",
mimetype: "string", mimetype: "string",
@@ -125,7 +124,7 @@ describe("obj_bin_transform", function () {
describe("FontInfo", function () { describe("FontInfo", function () {
it("must roundtrip correctly for FontInfo", function () { it("must roundtrip correctly for FontInfo", function () {
let sizeEstimate = 84; // fixed offset until the strings let sizeEstimate = 77; // fixed offset until the strings
const { encoder } = InfoUtils; const { encoder } = InfoUtils;
sizeEstimate += 4 + 4 * (4 + encoder.encode("string").length); sizeEstimate += 4 + 4 * (4 + encoder.encode("string").length);
sizeEstimate += 4 + 4; // cssFontInfo and systemFontInfo sizeEstimate += 4 + 4; // cssFontInfo and systemFontInfo
@@ -147,7 +146,6 @@ describe("obj_bin_transform", function () {
expect(deserialized.descent).toEqual(-1); expect(deserialized.descent).toEqual(-1);
expect(deserialized.bbox).toEqual([1, 1, 1, 1]); expect(deserialized.bbox).toEqual([1, 1, 1, 1]);
expect(deserialized.fontMatrix).toEqual([1, 1, 1, 1, 1, 1]); expect(deserialized.fontMatrix).toEqual([1, 1, 1, 1, 1, 1]);
expect(deserialized.defaultVMetrics).toEqual([1, 1, 1]);
expect(deserialized.fallbackName).toEqual("string"); expect(deserialized.fallbackName).toEqual("string");
expect(deserialized.loadedName).toEqual("string"); expect(deserialized.loadedName).toEqual("string");
expect(deserialized.mimetype).toEqual("string"); expect(deserialized.mimetype).toEqual("string");
+49
View File
@@ -197,6 +197,55 @@ describe("Scripting", function () {
); );
expect(value).toEqual([4, 5, 6, 7]); expect(value).toEqual([4, 5, 6, 7]);
}); });
it("should reset the descendants of a non-terminal field", async () => {
const refIds = [0, 1, 2, 3, 4].map(_ => getId());
const text = id => ({
id,
value: "",
defaultValue: "",
actions: {},
type: "text",
});
const data = {
objects: {
A: [{ id: refIds[0], type: "", kidIds: [refIds[1], refIds[2]] }],
"A.B": [text(refIds[1])],
"A.C": [{ id: refIds[2], type: "", kidIds: [refIds[3]] }],
"A.C.D": [text(refIds[3])],
E: [text(refIds[4])],
},
appInfo: { language: "en-US", platform: "Linux x86_64" },
calculationOrder: [],
dispatchEventName: "_dispatchMe",
};
sandbox.createSandbox(data);
await myeval(
`(["A.B", "A.C.D", "E"].forEach(n => this.getField(n).value = "hello"), 0)`
);
for (const id of [refIds[1], refIds[3], refIds[4]]) {
send_queue.delete(id);
}
await myeval(`(this.resetForm(["A"]), 0)`);
for (const id of [refIds[1], refIds[3]]) {
expect(send_queue.get(id)).toEqual({
id,
siblings: null,
value: "",
formattedValue: null,
selRange: [0, 0],
});
}
expect(send_queue.has(refIds[4])).toBeFalse();
const values = await myeval(
`["A.B", "A.C.D", "E"].map(n => this.getField(n).value)`
);
expect(values).toEqual(["", "", "hello"]);
});
}); });
describe("Util", function () { describe("Util", function () {