pdf.js.mirror/test/unit/obj_bin_transform_spec.js
Calixte Denizet a2c57ee69e
Use the gpu for drawing meshes only when it has more than 16 triangles (bug 2030745)
And in order to slightly improve performances, move the figure creation in the worker.
2026-04-13 22:23:03 +02:00

448 lines
16 KiB
JavaScript
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

/* Copyright 2025 Mozilla Foundation
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import {
compileCssFontInfo,
compileFontInfo,
compileFontPathInfo,
compilePatternInfo,
compileSystemFontInfo,
} from "../../src/core/obj_bin_transform_core.js";
import {
CssFontInfo,
FontInfo,
FontPathInfo,
PatternInfo,
SystemFontInfo,
} from "../../src/display/obj_bin_transform_display.js";
import { FeatureTest } from "../../src/shared/util.js";
describe("obj_bin_transform", function () {
describe("Font data", function () {
const cssFontInfo = {
fontFamily: "Sample Family",
fontWeight: "not a number",
italicAngle: "angle",
uselessProp: "doesn't matter",
};
const systemFontInfo = {
guessFallback: false,
css: "some string",
loadedName: "another string",
baseFontName: "base name",
src: "source",
style: {
style: "normal",
weight: "400",
uselessProp: "doesn't matter",
},
uselessProp: "doesn't matter",
};
const fontInfo = {
black: true,
bold: true,
disableFontFace: true,
fontExtraProperties: true,
isInvalidPDFjsFont: true,
isType3Font: true,
italic: true,
missingFile: true,
remeasure: true,
vertical: true,
ascent: 1,
defaultWidth: 1,
descent: 1,
bbox: [1, 1, 1, 1],
fontMatrix: [1, 1, 1, 1, 1, 1],
defaultVMetrics: [1, 1, 1],
fallbackName: "string",
loadedName: "string",
mimetype: "string",
name: "string",
data: new Uint8Array([1, 2, 3, 4, 5, 6, 7, 8, 9, 10]),
uselessProp: "something",
};
describe("font data serialization and deserialization", function () {
describe("CssFontInfo", function () {
it("must roundtrip correctly for CssFontInfo", function () {
const encoder = new TextEncoder();
let sizeEstimate = 0;
for (const string of ["Sample Family", "not a number", "angle"]) {
sizeEstimate += 4 + encoder.encode(string).length;
}
const buffer = compileCssFontInfo(cssFontInfo);
expect(buffer.byteLength).toEqual(sizeEstimate);
const deserialized = new CssFontInfo(buffer);
expect(deserialized.fontFamily).toEqual("Sample Family");
expect(deserialized.fontWeight).toEqual("not a number");
expect(deserialized.italicAngle).toEqual("angle");
expect(deserialized.uselessProp).toBeUndefined();
});
});
describe("SystemFontInfo", function () {
it("must roundtrip correctly for SystemFontInfo", function () {
const encoder = new TextEncoder();
let sizeEstimate = 1 + 4;
for (const string of [
"some string",
"another string",
"base name",
"source",
"normal",
"400",
]) {
sizeEstimate += 4 + encoder.encode(string).length;
}
const buffer = compileSystemFontInfo(systemFontInfo);
expect(buffer.byteLength).toEqual(sizeEstimate);
const deserialized = new SystemFontInfo(buffer);
expect(deserialized.guessFallback).toEqual(false);
expect(deserialized.css).toEqual("some string");
expect(deserialized.loadedName).toEqual("another string");
expect(deserialized.baseFontName).toEqual("base name");
expect(deserialized.src).toEqual("source");
expect(deserialized.style.style).toEqual("normal");
expect(deserialized.style.weight).toEqual("400");
expect(deserialized.style.uselessProp).toBeUndefined();
expect(deserialized.uselessProp).toBeUndefined();
});
});
describe("FontInfo", function () {
it("must roundtrip correctly for FontInfo", function () {
let sizeEstimate = 92; // fixed offset until the strings
const encoder = new TextEncoder();
sizeEstimate += 4 + 4 * (4 + encoder.encode("string").length);
sizeEstimate += 4 + 4; // cssFontInfo and systemFontInfo
sizeEstimate += 4 + fontInfo.data.length;
const buffer = compileFontInfo(fontInfo);
expect(buffer.byteLength).toEqual(sizeEstimate);
const deserialized = new FontInfo({ buffer });
expect(deserialized.black).toEqual(true);
expect(deserialized.bold).toEqual(true);
expect(deserialized.disableFontFace).toEqual(true);
expect(deserialized.fontExtraProperties).toEqual(true);
expect(deserialized.isInvalidPDFjsFont).toEqual(true);
expect(deserialized.isType3Font).toEqual(true);
expect(deserialized.italic).toEqual(true);
expect(deserialized.missingFile).toEqual(true);
expect(deserialized.remeasure).toEqual(true);
expect(deserialized.vertical).toEqual(true);
expect(deserialized.ascent).toEqual(1);
expect(deserialized.defaultWidth).toEqual(1);
expect(deserialized.descent).toEqual(1);
expect(deserialized.bbox).toEqual([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.loadedName).toEqual("string");
expect(deserialized.mimetype).toEqual("string");
expect(deserialized.name).toEqual("string");
expect(Array.from(deserialized.data)).toEqual([
1, 2, 3, 4, 5, 6, 7, 8, 9, 10,
]);
expect(deserialized.uselessProp).toBeUndefined();
expect(deserialized.cssFontInfo).toBeNull();
expect(deserialized.systemFontInfo).toBeNull();
});
it("nesting should work as expected", function () {
const buffer = compileFontInfo({
...fontInfo,
cssFontInfo,
systemFontInfo,
});
const deserialized = new FontInfo({ buffer });
expect(deserialized.cssFontInfo.fontWeight).toEqual("not a number");
expect(deserialized.systemFontInfo.src).toEqual("source");
});
});
});
});
describe("Pattern data", function () {
const axialPatternIR = [
"RadialAxial",
"axial",
[0, 0, 100, 50],
[
[0, "#ff0000"],
[0.5, "#00ff00"],
[1, "#0000ff"],
],
[10, 20],
[90, 40],
null,
null,
];
const radialPatternIR = [
"RadialAxial",
"radial",
[5, 5, 95, 45],
[
[0, "#ffff00"],
[0.3, "#ff00ff"],
[0.7, "#00ffff"],
[1, "#ffffff"],
],
[25, 25],
[75, 35],
5,
25,
];
// Vertices are pre-expanded in the new IR format: posData/colData contain
// one entry per vertex (no indexing), and ir[4] is the vertex count.
const meshPatternIR = [
"Mesh",
4,
new Float32Array([
0, 0, 50, 0, 100, 0, 0, 50, 50, 50, 100, 50, 0, 100, 50, 100, 100, 100,
]),
new Uint8Array([
255, 0, 0, 0, 0, 255, 0, 0, 0, 0, 255, 0, 255, 255, 0, 0, 128, 128, 128,
0, 255, 0, 255, 0, 0, 255, 255, 0, 255, 128, 0, 0, 128, 0, 128, 0,
]),
9, // vertexCount (3 triangles × 3 vertices)
[0, 0, 100, 100],
[0, 0, 100, 100],
[128, 128, 128],
];
describe("Pattern serialization and deserialization", function () {
it("must serialize and deserialize axial gradients correctly", function () {
const buffer = compilePatternInfo(axialPatternIR);
expect(buffer).toBeInstanceOf(ArrayBuffer);
expect(buffer.byteLength).toBeGreaterThan(0);
const patternInfo = new PatternInfo(buffer);
const reconstructedIR = patternInfo.getIR();
expect(reconstructedIR[0]).toEqual("RadialAxial");
expect(reconstructedIR[1]).toEqual("axial");
expect(reconstructedIR[2]).toEqual([0, 0, 100, 50]);
expect(reconstructedIR[3]).toEqual([
[0, "#ff0000"],
[0.5, "#00ff00"],
[1, "#0000ff"],
]);
expect(reconstructedIR[4]).toEqual([10, 20]);
expect(reconstructedIR[5]).toEqual([90, 40]);
expect(reconstructedIR[6]).toBeNull();
expect(reconstructedIR[7]).toBeNull();
});
it("must serialize and deserialize radial gradients correctly", function () {
const buffer = compilePatternInfo(radialPatternIR);
expect(buffer).toBeInstanceOf(ArrayBuffer);
expect(buffer.byteLength).toBeGreaterThan(0);
const patternInfo = new PatternInfo(buffer);
const reconstructedIR = patternInfo.getIR();
expect(reconstructedIR[0]).toEqual("RadialAxial");
expect(reconstructedIR[1]).toEqual("radial");
expect(reconstructedIR[2]).toEqual([5, 5, 95, 45]);
expect(reconstructedIR[3]).toEqual([
[0, "#ffff00"],
jasmine.objectContaining([jasmine.any(Number), "#ff00ff"]),
jasmine.objectContaining([jasmine.any(Number), "#00ffff"]),
[1, "#ffffff"],
]);
expect(reconstructedIR[4]).toEqual([25, 25]);
expect(reconstructedIR[5]).toEqual([75, 35]);
expect(reconstructedIR[6]).toEqual(5);
expect(reconstructedIR[7]).toEqual(25);
});
it("must serialize and deserialize mesh patterns with figures correctly", function () {
const buffer = compilePatternInfo(meshPatternIR);
expect(buffer).toBeInstanceOf(ArrayBuffer);
expect(buffer.byteLength).toBeGreaterThan(0);
const patternInfo = new PatternInfo(buffer);
const reconstructedIR = patternInfo.getIR();
expect(reconstructedIR[0]).toEqual("Mesh");
expect(reconstructedIR[1]).toEqual(4);
expect(reconstructedIR[2]).toBeInstanceOf(Float32Array);
expect(Array.from(reconstructedIR[2])).toEqual(
Array.from(meshPatternIR[2])
);
expect(reconstructedIR[3]).toBeInstanceOf(Uint8Array);
expect(Array.from(reconstructedIR[3])).toEqual(
Array.from(meshPatternIR[3])
);
expect(reconstructedIR[4]).toEqual(9); // vertexCount
expect(reconstructedIR[5]).toEqual([0, 0, 100, 100]);
expect(reconstructedIR[6]).toEqual([0, 0, 100, 100]);
expect(reconstructedIR[7]).toBeInstanceOf(Uint8Array);
expect(Array.from(reconstructedIR[7])).toEqual([128, 128, 128]);
});
it("must handle mesh patterns with no vertices", function () {
const noVerticesIR = [
"Mesh",
4,
new Float32Array([0, 0, 10, 10]),
new Uint8Array([255, 0, 0, 0]),
2, // vertexCount
[0, 0, 10, 10],
[0, 0, 10, 10],
null,
];
const buffer = compilePatternInfo(noVerticesIR);
const patternInfo = new PatternInfo(buffer);
const reconstructedIR = patternInfo.getIR();
expect(reconstructedIR[4]).toEqual(2); // vertexCount
expect(reconstructedIR[7]).toBeNull(); // background should be null
});
it("must preserve vertex data integrity across serialization", function () {
const buffer = compilePatternInfo(meshPatternIR);
const patternInfo = new PatternInfo(buffer);
const reconstructedIR = patternInfo.getIR();
// Verify posData and colData are preserved exactly
expect(Array.from(reconstructedIR[2])).toEqual(
Array.from(meshPatternIR[2])
);
expect(Array.from(reconstructedIR[3])).toEqual(
Array.from(meshPatternIR[3])
);
});
it("must calculate correct buffer sizes for different pattern types", function () {
const axialBuffer = compilePatternInfo(axialPatternIR);
const radialBuffer = compilePatternInfo(radialPatternIR);
const meshBuffer = compilePatternInfo(meshPatternIR);
expect(axialBuffer.byteLength).toBeLessThan(radialBuffer.byteLength);
expect(meshBuffer.byteLength).toBeGreaterThan(axialBuffer.byteLength);
expect(meshBuffer.byteLength).toBeGreaterThan(radialBuffer.byteLength);
});
it("must round-trip mesh pattern posData and colData correctly", function () {
const customMeshIR = [
"Mesh",
6,
new Float32Array([0, 0, 10, 10]),
new Uint8Array([255, 128, 64, 0]),
2, // vertexCount
[0, 0, 10, 10],
null,
null,
];
const buffer = compilePatternInfo(customMeshIR);
const patternInfo = new PatternInfo(buffer);
const reconstructedIR = patternInfo.getIR();
expect(reconstructedIR[4]).toEqual(2); // vertexCount
expect(Array.from(reconstructedIR[2])).toEqual([0, 0, 10, 10]);
expect(Array.from(reconstructedIR[3])).toEqual([255, 128, 64, 0]);
});
it("must handle mesh patterns with different background values", function () {
const meshWithBgIR = [
"Mesh",
4,
new Float32Array([0, 0, 10, 10]),
new Uint8Array([255, 0, 0, 0]),
2, // vertexCount
[0, 0, 10, 10],
[0, 0, 10, 10],
new Uint8Array([255, 128, 64]),
];
const buffer = compilePatternInfo(meshWithBgIR);
const patternInfo = new PatternInfo(buffer);
const reconstructedIR = patternInfo.getIR();
expect(reconstructedIR[7]).toBeInstanceOf(Uint8Array);
expect(Array.from(reconstructedIR[7])).toEqual([255, 128, 64]);
const meshNoBgIR = [
"Mesh",
5,
new Float32Array([0, 0, 5, 5]),
new Uint8Array([0, 255, 0, 0]),
2, // vertexCount
[0, 0, 5, 5],
null,
null,
];
const buffer2 = compilePatternInfo(meshNoBgIR);
const patternInfo2 = new PatternInfo(buffer2);
const reconstructedIR2 = patternInfo2.getIR();
expect(reconstructedIR2[7]).toBeNull();
});
it("must calculate bounds correctly from coordinates", function () {
const customMeshIR = [
"Mesh",
4,
new Float32Array([-10, -5, 20, 15, 0, 30]),
new Uint8Array([255, 0, 0, 0, 0, 255, 0, 0, 0, 0, 255, 0]),
3, // vertexCount
null,
null,
null,
];
const buffer = compilePatternInfo(customMeshIR);
const patternInfo = new PatternInfo(buffer);
const reconstructedIR = patternInfo.getIR();
expect(reconstructedIR[5]).toEqual([-10, -5, 20, 30]);
expect(reconstructedIR[7]).toBeNull();
});
});
});
describe("FontPath data", function () {
const path = FeatureTest.isFloat16ArraySupported
? new Float16Array([
0.214, 0.27, 0.23, 0.33, 0.248, 0.395, 0.265, 0.471, 0.281, 0.54,
0.285, 0.54, 0.302, 0.472, 0.32, 0.395, 0.338, 0.33, 0.353, 0.27,
0.214, 0.27, 0.423, 0, 0.579, 0, 0.375, 0.652, 0.198, 0.652, -0.006,
0, 0.144, 0, 0.184, 0.155, 0.383, 0.155,
])
: new Float32Array([
0.214, 0.27, 0.23, 0.33, 0.248, 0.395, 0.265, 0.471, 0.281, 0.54,
0.285, 0.54, 0.302, 0.472, 0.32, 0.395, 0.338, 0.33, 0.353, 0.27,
0.214, 0.27, 0.423, 0, 0.579, 0, 0.375, 0.652, 0.198, 0.652, -0.006,
0, 0.144, 0, 0.184, 0.155, 0.383, 0.155,
]);
it("should create a FontPathInfo instance from an array of path commands", function () {
const buffer = compileFontPathInfo(path);
const fontPathInfo = new FontPathInfo(buffer);
expect(fontPathInfo.path).toEqual(path);
});
});
});