Merge 4fed542e3f808350cc4fdb37013b368b12778071 into a96553648551b94652f4b1139c440d53c806836f

This commit is contained in:
Ujjwal Sharma 2025-11-29 04:20:42 +05:30 committed by GitHub
commit 94e48bc23b
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
5 changed files with 490 additions and 414 deletions

View File

@ -40,7 +40,11 @@ import {
lookupMatrix, lookupMatrix,
lookupNormalRect, lookupNormalRect,
} from "./core_utils.js"; } from "./core_utils.js";
import { FontInfo, PatternInfo } from "../shared/obj-bin-transform.js"; import {
FontInfo,
FontPathInfo,
PatternInfo,
} from "../shared/obj-bin-transform.js";
import { import {
getEncoding, getEncoding,
MacRomanEncoding, MacRomanEncoding,
@ -4663,11 +4667,8 @@ class PartialEvaluator {
if (font.renderer.hasBuiltPath(fontChar)) { if (font.renderer.hasBuiltPath(fontChar)) {
return; return;
} }
handler.send("commonobj", [ const buffer = FontPathInfo.write(font.renderer.getPathJs(fontChar));
glyphName, handler.send("commonobj", [glyphName, "FontPath", buffer], [buffer]);
"FontPath",
font.renderer.getPathJs(fontChar),
]);
} catch (reason) { } catch (reason) {
if (evaluatorOptions.ignoreErrors) { if (evaluatorOptions.ignoreErrors) {
warn(`buildFontPaths - ignoring ${glyphName} glyph: "${reason}".`); warn(`buildFontPaths - ignoring ${glyphName} glyph: "${reason}".`);

View File

@ -45,7 +45,11 @@ import {
StatTimer, StatTimer,
} from "./display_utils.js"; } from "./display_utils.js";
import { FontFaceObject, FontLoader } from "./font_loader.js"; import { FontFaceObject, FontLoader } from "./font_loader.js";
import { FontInfo, PatternInfo } from "../shared/obj-bin-transform.js"; import {
FontInfo,
FontPathInfo,
PatternInfo,
} from "../shared/obj-bin-transform.js";
import { import {
getDataProp, getDataProp,
getFactoryUrlProp, getFactoryUrlProp,
@ -2821,6 +2825,8 @@ class WorkerTransport {
} }
break; break;
case "FontPath": case "FontPath":
this.commonObjs.resolve(id, new FontPathInfo(exportedData));
break;
case "Image": case "Image":
this.commonObjs.resolve(id, exportedData); this.commonObjs.resolve(id, exportedData);
break; break;

View File

@ -436,7 +436,7 @@ class FontFaceObject {
} catch (ex) { } catch (ex) {
warn(`getPathGenerator - ignoring character: "${ex}".`); warn(`getPathGenerator - ignoring character: "${ex}".`);
} }
const path = makePathFromDrawOPS(cmds); const path = makePathFromDrawOPS(cmds.path);
if (!this.fontExtraProperties) { if (!this.fontExtraProperties) {
// Remove the raw path-string, since we don't need it anymore. // Remove the raw path-string, since we don't need it anymore.

View File

@ -13,7 +13,7 @@
* limitations under the License. * limitations under the License.
*/ */
import { assert, MeshFigureType } from "./util.js"; import { assert, FeatureTest, MeshFigureType } from "./util.js";
class CssFontInfo { class CssFontInfo {
#buffer; #buffer;
@ -881,4 +881,40 @@ class PatternInfo {
throw new Error(`Unsupported pattern kind: ${kind}`); throw new Error(`Unsupported pattern kind: ${kind}`);
} }
} }
export { CssFontInfo, FontInfo, PatternInfo, SystemFontInfo };
class FontPathInfo {
static write(path) {
let data;
let buffer;
if (
(typeof PDFJSDev !== "undefined" && PDFJSDev.test("MOZCENTRAL")) ||
FeatureTest.isFloat16ArraySupported
) {
buffer = new ArrayBuffer(path.length * 2);
data = new Float16Array(buffer);
} else {
buffer = new ArrayBuffer(path.length * 4);
data = new Float32Array(buffer);
}
data.set(path);
return buffer;
}
#buffer;
constructor(buffer) {
this.#buffer = buffer;
}
get path() {
if (
(typeof PDFJSDev !== "undefined" && PDFJSDev.test("MOZCENTRAL")) ||
FeatureTest.isFloat16ArraySupported
) {
return new Float16Array(this.#buffer);
}
return new Float32Array(this.#buffer);
}
}
export { CssFontInfo, FontInfo, FontPathInfo, PatternInfo, SystemFontInfo };

View File

@ -16,437 +16,470 @@
import { import {
CssFontInfo, CssFontInfo,
FontInfo, FontInfo,
FontPathInfo,
PatternInfo, PatternInfo,
SystemFontInfo, SystemFontInfo,
} from "../../src/shared/obj-bin-transform.js"; } from "../../src/shared/obj-bin-transform.js";
import { MeshFigureType } from "../../src/shared/util.js"; import { FeatureTest, MeshFigureType } from "../../src/shared/util.js";
const cssFontInfo = { describe("obj-bin-transform", function () {
fontFamily: "Sample Family", describe("Font data", function () {
fontWeight: "not a number", const cssFontInfo = {
italicAngle: "angle", fontFamily: "Sample Family",
uselessProp: "doesn't matter", fontWeight: "not a number",
}; italicAngle: "angle",
uselessProp: "doesn't matter",
};
const systemFontInfo = { const systemFontInfo = {
guessFallback: false, guessFallback: false,
css: "some string", css: "some string",
loadedName: "another string", loadedName: "another string",
baseFontName: "base name", baseFontName: "base name",
src: "source", src: "source",
style: { style: {
style: "normal", style: "normal",
weight: "400", weight: "400",
uselessProp: "doesn't matter", uselessProp: "doesn't matter",
}, },
uselessProp: "doesn't matter", uselessProp: "doesn't matter",
}; };
const fontInfo = { const fontInfo = {
black: true, black: true,
bold: true, bold: true,
disableFontFace: true, disableFontFace: true,
fontExtraProperties: true, fontExtraProperties: true,
isInvalidPDFjsFont: true, isInvalidPDFjsFont: true,
isType3Font: true, isType3Font: true,
italic: true, italic: true,
missingFile: true, missingFile: true,
remeasure: true, remeasure: true,
vertical: true, vertical: true,
ascent: 1, ascent: 1,
defaultWidth: 1, defaultWidth: 1,
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], defaultVMetrics: [1, 1, 1],
fallbackName: "string", fallbackName: "string",
loadedName: "string", loadedName: "string",
mimetype: "string", mimetype: "string",
name: "string", name: "string",
data: new Uint8Array([1, 2, 3, 4, 5, 6, 7, 8, 9, 10]), data: new Uint8Array([1, 2, 3, 4, 5, 6, 7, 8, 9, 10]),
uselessProp: "something", uselessProp: "something",
}; };
describe("font data serialization and deserialization", function () { describe("font data serialization and deserialization", function () {
describe("CssFontInfo", function () { describe("CssFontInfo", function () {
it("must roundtrip correctly for CssFontInfo", function () { it("must roundtrip correctly for CssFontInfo", function () {
const encoder = new TextEncoder(); const encoder = new TextEncoder();
let sizeEstimate = 0; let sizeEstimate = 0;
for (const string of ["Sample Family", "not a number", "angle"]) { for (const string of ["Sample Family", "not a number", "angle"]) {
sizeEstimate += 4 + encoder.encode(string).length; sizeEstimate += 4 + encoder.encode(string).length;
} }
const buffer = CssFontInfo.write(cssFontInfo); const buffer = CssFontInfo.write(cssFontInfo);
expect(buffer.byteLength).toEqual(sizeEstimate); expect(buffer.byteLength).toEqual(sizeEstimate);
const deserialized = new CssFontInfo(buffer); const deserialized = new CssFontInfo(buffer);
expect(deserialized.fontFamily).toEqual("Sample Family"); expect(deserialized.fontFamily).toEqual("Sample Family");
expect(deserialized.fontWeight).toEqual("not a number"); expect(deserialized.fontWeight).toEqual("not a number");
expect(deserialized.italicAngle).toEqual("angle"); expect(deserialized.italicAngle).toEqual("angle");
expect(deserialized.uselessProp).toBeUndefined(); expect(deserialized.uselessProp).toBeUndefined();
}); });
}); });
describe("SystemFontInfo", function () { describe("SystemFontInfo", function () {
it("must roundtrip correctly for SystemFontInfo", function () { it("must roundtrip correctly for SystemFontInfo", function () {
const encoder = new TextEncoder(); const encoder = new TextEncoder();
let sizeEstimate = 1 + 4; let sizeEstimate = 1 + 4;
for (const string of [ for (const string of [
"some string", "some string",
"another string", "another string",
"base name", "base name",
"source", "source",
"normal", "normal",
"400", "400",
]) { ]) {
sizeEstimate += 4 + encoder.encode(string).length; sizeEstimate += 4 + encoder.encode(string).length;
} }
const buffer = SystemFontInfo.write(systemFontInfo); const buffer = SystemFontInfo.write(systemFontInfo);
expect(buffer.byteLength).toEqual(sizeEstimate); expect(buffer.byteLength).toEqual(sizeEstimate);
const deserialized = new SystemFontInfo(buffer); const deserialized = new SystemFontInfo(buffer);
expect(deserialized.guessFallback).toEqual(false); expect(deserialized.guessFallback).toEqual(false);
expect(deserialized.css).toEqual("some string"); expect(deserialized.css).toEqual("some string");
expect(deserialized.loadedName).toEqual("another string"); expect(deserialized.loadedName).toEqual("another string");
expect(deserialized.baseFontName).toEqual("base name"); expect(deserialized.baseFontName).toEqual("base name");
expect(deserialized.src).toEqual("source"); expect(deserialized.src).toEqual("source");
expect(deserialized.style.style).toEqual("normal"); expect(deserialized.style.style).toEqual("normal");
expect(deserialized.style.weight).toEqual("400"); expect(deserialized.style.weight).toEqual("400");
expect(deserialized.style.uselessProp).toBeUndefined(); expect(deserialized.style.uselessProp).toBeUndefined();
expect(deserialized.uselessProp).toBeUndefined(); expect(deserialized.uselessProp).toBeUndefined();
}); });
}); });
describe("FontInfo", function () { describe("FontInfo", function () {
it("must roundtrip correctly for FontInfo", function () { it("must roundtrip correctly for FontInfo", function () {
let sizeEstimate = 92; // fixed offset until the strings let sizeEstimate = 92; // fixed offset until the strings
const encoder = new TextEncoder(); const encoder = new TextEncoder();
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
sizeEstimate += 4 + fontInfo.data.length; sizeEstimate += 4 + fontInfo.data.length;
const buffer = FontInfo.write(fontInfo); const buffer = FontInfo.write(fontInfo);
expect(buffer.byteLength).toEqual(sizeEstimate); expect(buffer.byteLength).toEqual(sizeEstimate);
const deserialized = new FontInfo({ data: buffer }); const deserialized = new FontInfo({ data: buffer });
expect(deserialized.black).toEqual(true); expect(deserialized.black).toEqual(true);
expect(deserialized.bold).toEqual(true); expect(deserialized.bold).toEqual(true);
expect(deserialized.disableFontFace).toEqual(true); expect(deserialized.disableFontFace).toEqual(true);
expect(deserialized.fontExtraProperties).toEqual(true); expect(deserialized.fontExtraProperties).toEqual(true);
expect(deserialized.isInvalidPDFjsFont).toEqual(true); expect(deserialized.isInvalidPDFjsFont).toEqual(true);
expect(deserialized.isType3Font).toEqual(true); expect(deserialized.isType3Font).toEqual(true);
expect(deserialized.italic).toEqual(true); expect(deserialized.italic).toEqual(true);
expect(deserialized.missingFile).toEqual(true); expect(deserialized.missingFile).toEqual(true);
expect(deserialized.remeasure).toEqual(true); expect(deserialized.remeasure).toEqual(true);
expect(deserialized.vertical).toEqual(true); expect(deserialized.vertical).toEqual(true);
expect(deserialized.ascent).toEqual(1); expect(deserialized.ascent).toEqual(1);
expect(deserialized.defaultWidth).toEqual(1); expect(deserialized.defaultWidth).toEqual(1);
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.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");
expect(deserialized.name).toEqual("string"); expect(deserialized.name).toEqual("string");
expect(Array.from(deserialized.data)).toEqual([ expect(Array.from(deserialized.data)).toEqual([
1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10,
]); ]);
expect(deserialized.uselessProp).toBeUndefined(); expect(deserialized.uselessProp).toBeUndefined();
expect(deserialized.cssFontInfo).toBeNull(); expect(deserialized.cssFontInfo).toBeNull();
expect(deserialized.systemFontInfo).toBeNull(); expect(deserialized.systemFontInfo).toBeNull();
}); });
it("nesting should work as expected", function () { it("nesting should work as expected", function () {
const buffer = FontInfo.write({ const buffer = FontInfo.write({
...fontInfo, ...fontInfo,
cssFontInfo, cssFontInfo,
systemFontInfo, systemFontInfo,
});
const deserialized = new FontInfo({ data: buffer });
expect(deserialized.cssFontInfo.fontWeight).toEqual("not a number");
expect(deserialized.systemFontInfo.src).toEqual("source");
});
}); });
const deserialized = new FontInfo({ data: buffer });
expect(deserialized.cssFontInfo.fontWeight).toEqual("not a number");
expect(deserialized.systemFontInfo.src).toEqual("source");
}); });
}); });
});
const axialPatternIR = [ describe("Pattern data", function () {
"RadialAxial", const axialPatternIR = [
"axial", "RadialAxial",
[0, 0, 100, 50], "axial",
[ [0, 0, 100, 50],
[0, "#ff0000"], [
[0.5, "#00ff00"], [0, "#ff0000"],
[1, "#0000ff"], [0.5, "#00ff00"],
], [1, "#0000ff"],
[10, 20], ],
[90, 40], [10, 20],
null, [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,
];
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, 255, 0, 0, 0, 255, 255, 255, 0, 128, 128, 128, 255, 0, 255, 0,
255, 255, 255, 128, 0, 128, 0, 128,
]),
[
{
type: MeshFigureType.TRIANGLES,
coords: new Int32Array([0, 2, 4, 6, 8, 10, 12, 14, 16]),
colors: new Int32Array([0, 2, 4, 6, 8, 10, 12, 14, 16]),
},
{
type: MeshFigureType.LATTICE,
coords: new Int32Array([0, 2, 4, 6, 8, 10]),
colors: new Int32Array([0, 2, 4, 6, 8, 10]),
verticesPerRow: 3,
},
],
[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 = PatternInfo.write(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 = PatternInfo.write(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 = PatternInfo.write(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].length).toEqual(2);
const fig1 = reconstructedIR[4][0];
expect(fig1.type).toEqual(MeshFigureType.TRIANGLES);
expect(fig1.coords).toBeInstanceOf(Int32Array);
expect(Array.from(fig1.coords)).toEqual([0, 2, 4, 6, 8, 10, 12, 14, 16]);
expect(fig1.colors).toBeInstanceOf(Int32Array);
expect(Array.from(fig1.colors)).toEqual([0, 2, 4, 6, 8, 10, 12, 14, 16]);
expect(fig1.verticesPerRow).toBeUndefined();
const fig2 = reconstructedIR[4][1];
expect(fig2.type).toEqual(MeshFigureType.LATTICE);
expect(fig2.coords).toBeInstanceOf(Int32Array);
expect(Array.from(fig2.coords)).toEqual([0, 2, 4, 6, 8, 10]);
expect(fig2.colors).toBeInstanceOf(Int32Array);
expect(Array.from(fig2.colors)).toEqual([0, 2, 4, 6, 8, 10]);
expect(fig2.verticesPerRow).toEqual(3);
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 figures", function () {
const noFiguresIR = [
"Mesh",
4,
new Float32Array([0, 0, 10, 10]),
new Uint8Array([255, 0, 0]),
[],
[0, 0, 10, 10],
[0, 0, 10, 10],
null, null,
]; ];
const buffer = PatternInfo.write(noFiguresIR); const radialPatternIR = [
const patternInfo = new PatternInfo(buffer); "RadialAxial",
const reconstructedIR = patternInfo.getIR(); "radial",
[5, 5, 95, 45],
[
[0, "#ffff00"],
[0.3, "#ff00ff"],
[0.7, "#00ffff"],
[1, "#ffffff"],
],
[25, 25],
[75, 35],
5,
25,
];
expect(reconstructedIR[4]).toEqual([]); const meshPatternIR = [
expect(reconstructedIR[7]).toBeNull(); // background should be null
});
it("must preserve figure data integrity across serialization", function () {
const buffer = PatternInfo.write(meshPatternIR);
const patternInfo = new PatternInfo(buffer);
const reconstructedIR = patternInfo.getIR();
// Verify data integrity by checking exact values
const originalFig = meshPatternIR[4][0];
const reconstructedFig = reconstructedIR[4][0];
for (let i = 0; i < originalFig.coords.length; i++) {
expect(reconstructedFig.coords[i]).toEqual(originalFig.coords[i]);
}
for (let i = 0; i < originalFig.colors.length; i++) {
expect(reconstructedFig.colors[i]).toEqual(originalFig.colors[i]);
}
});
it("must calculate correct buffer sizes for different pattern types", function () {
const axialBuffer = PatternInfo.write(axialPatternIR);
const radialBuffer = PatternInfo.write(radialPatternIR);
const meshBuffer = PatternInfo.write(meshPatternIR);
expect(axialBuffer.byteLength).toBeLessThan(radialBuffer.byteLength);
expect(meshBuffer.byteLength).toBeGreaterThan(axialBuffer.byteLength);
expect(meshBuffer.byteLength).toBeGreaterThan(radialBuffer.byteLength);
});
it("must handle figures with different type enums correctly", function () {
const customFiguresIR = [
"Mesh", "Mesh",
6, 4,
new Float32Array([0, 0, 10, 10]), new Float32Array([
new Uint8Array([255, 128, 64]), 0, 0, 50, 0, 100, 0, 0, 50, 50, 50, 100, 50, 0, 100, 50, 100, 100, 100,
]),
new Uint8Array([
255, 0, 0, 0, 255, 0, 0, 0, 255, 255, 255, 0, 128, 128, 128, 255, 0,
255, 0, 255, 255, 255, 128, 0, 128, 0, 128,
]),
[ [
{ {
type: MeshFigureType.PATCH, type: MeshFigureType.TRIANGLES,
coords: new Int32Array([0, 2]), coords: new Int32Array([0, 2, 4, 6, 8, 10, 12, 14, 16]),
colors: new Int32Array([0, 2]), colors: new Int32Array([0, 2, 4, 6, 8, 10, 12, 14, 16]),
}, },
{ {
type: MeshFigureType.TRIANGLES, type: MeshFigureType.LATTICE,
coords: new Int32Array([0]), coords: new Int32Array([0, 2, 4, 6, 8, 10]),
colors: new Int32Array([0]), colors: new Int32Array([0, 2, 4, 6, 8, 10]),
verticesPerRow: 3,
}, },
], ],
[0, 0, 10, 10], [0, 0, 100, 100],
null, [0, 0, 100, 100],
null, [128, 128, 128],
]; ];
const buffer = PatternInfo.write(customFiguresIR); describe("Pattern serialization and deserialization", function () {
const patternInfo = new PatternInfo(buffer); it("must serialize and deserialize axial gradients correctly", function () {
const reconstructedIR = patternInfo.getIR(); const buffer = PatternInfo.write(axialPatternIR);
expect(buffer).toBeInstanceOf(ArrayBuffer);
expect(buffer.byteLength).toBeGreaterThan(0);
expect(reconstructedIR[4].length).toEqual(2); const patternInfo = new PatternInfo(buffer);
expect(reconstructedIR[4][0].type).toEqual(MeshFigureType.PATCH); const reconstructedIR = patternInfo.getIR();
expect(reconstructedIR[4][1].type).toEqual(MeshFigureType.TRIANGLES);
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 = PatternInfo.write(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 = PatternInfo.write(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].length).toEqual(2);
const fig1 = reconstructedIR[4][0];
expect(fig1.type).toEqual(MeshFigureType.TRIANGLES);
expect(fig1.coords).toBeInstanceOf(Int32Array);
expect(Array.from(fig1.coords)).toEqual([
0, 2, 4, 6, 8, 10, 12, 14, 16,
]);
expect(fig1.colors).toBeInstanceOf(Int32Array);
expect(Array.from(fig1.colors)).toEqual([
0, 2, 4, 6, 8, 10, 12, 14, 16,
]);
expect(fig1.verticesPerRow).toBeUndefined();
const fig2 = reconstructedIR[4][1];
expect(fig2.type).toEqual(MeshFigureType.LATTICE);
expect(fig2.coords).toBeInstanceOf(Int32Array);
expect(Array.from(fig2.coords)).toEqual([0, 2, 4, 6, 8, 10]);
expect(fig2.colors).toBeInstanceOf(Int32Array);
expect(Array.from(fig2.colors)).toEqual([0, 2, 4, 6, 8, 10]);
expect(fig2.verticesPerRow).toEqual(3);
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 figures", function () {
const noFiguresIR = [
"Mesh",
4,
new Float32Array([0, 0, 10, 10]),
new Uint8Array([255, 0, 0]),
[],
[0, 0, 10, 10],
[0, 0, 10, 10],
null,
];
const buffer = PatternInfo.write(noFiguresIR);
const patternInfo = new PatternInfo(buffer);
const reconstructedIR = patternInfo.getIR();
expect(reconstructedIR[4]).toEqual([]);
expect(reconstructedIR[7]).toBeNull(); // background should be null
});
it("must preserve figure data integrity across serialization", function () {
const buffer = PatternInfo.write(meshPatternIR);
const patternInfo = new PatternInfo(buffer);
const reconstructedIR = patternInfo.getIR();
// Verify data integrity by checking exact values
const originalFig = meshPatternIR[4][0];
const reconstructedFig = reconstructedIR[4][0];
for (let i = 0; i < originalFig.coords.length; i++) {
expect(reconstructedFig.coords[i]).toEqual(originalFig.coords[i]);
}
for (let i = 0; i < originalFig.colors.length; i++) {
expect(reconstructedFig.colors[i]).toEqual(originalFig.colors[i]);
}
});
it("must calculate correct buffer sizes for different pattern types", function () {
const axialBuffer = PatternInfo.write(axialPatternIR);
const radialBuffer = PatternInfo.write(radialPatternIR);
const meshBuffer = PatternInfo.write(meshPatternIR);
expect(axialBuffer.byteLength).toBeLessThan(radialBuffer.byteLength);
expect(meshBuffer.byteLength).toBeGreaterThan(axialBuffer.byteLength);
expect(meshBuffer.byteLength).toBeGreaterThan(radialBuffer.byteLength);
});
it("must handle figures with different type enums correctly", function () {
const customFiguresIR = [
"Mesh",
6,
new Float32Array([0, 0, 10, 10]),
new Uint8Array([255, 128, 64]),
[
{
type: MeshFigureType.PATCH,
coords: new Int32Array([0, 2]),
colors: new Int32Array([0, 2]),
},
{
type: MeshFigureType.TRIANGLES,
coords: new Int32Array([0]),
colors: new Int32Array([0]),
},
],
[0, 0, 10, 10],
null,
null,
];
const buffer = PatternInfo.write(customFiguresIR);
const patternInfo = new PatternInfo(buffer);
const reconstructedIR = patternInfo.getIR();
expect(reconstructedIR[4].length).toEqual(2);
expect(reconstructedIR[4][0].type).toEqual(MeshFigureType.PATCH);
expect(reconstructedIR[4][1].type).toEqual(MeshFigureType.TRIANGLES);
});
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, 0, 10, 10],
[0, 0, 10, 10],
new Uint8Array([255, 128, 64]),
];
const buffer = PatternInfo.write(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, 0, 5, 5],
null,
null,
];
const buffer2 = PatternInfo.write(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, 255, 0, 0, 0, 255]),
[],
null,
null,
null,
];
const buffer = PatternInfo.write(customMeshIR);
const patternInfo = new PatternInfo(buffer);
const reconstructedIR = patternInfo.getIR();
expect(reconstructedIR[5]).toEqual([-10, -5, 20, 30]);
expect(reconstructedIR[7]).toBeNull();
});
});
}); });
it("must handle mesh patterns with different background values", function () { describe("FontPath data", function () {
const meshWithBgIR = [ const path = FeatureTest.isFloat16ArraySupported
"Mesh", ? new Float16Array([
4, 0.214, 0.27, 0.23, 0.33, 0.248, 0.395, 0.265, 0.471, 0.281, 0.54,
new Float32Array([0, 0, 10, 10]), 0.285, 0.54, 0.302, 0.472, 0.32, 0.395, 0.338, 0.33, 0.353, 0.27,
new Uint8Array([255, 0, 0]), 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,
[0, 0, 10, 10], ])
[0, 0, 10, 10], : new Float32Array([
new Uint8Array([255, 128, 64]), 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,
]);
const buffer = PatternInfo.write(meshWithBgIR); it("should create a FontPathInfo instance from an array of path commands", function () {
const patternInfo = new PatternInfo(buffer); const buffer = FontPathInfo.write(path);
const reconstructedIR = patternInfo.getIR(); const fontPathInfo = new FontPathInfo(buffer);
expect(fontPathInfo.path).toEqual(path);
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, 0, 5, 5],
null,
null,
];
const buffer2 = PatternInfo.write(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, 255, 0, 0, 0, 255]),
[],
null,
null,
null,
];
const buffer = PatternInfo.write(customMeshIR);
const patternInfo = new PatternInfo(buffer);
const reconstructedIR = patternInfo.getIR();
expect(reconstructedIR[5]).toEqual([-10, -5, 20, 30]);
expect(reconstructedIR[7]).toBeNull();
}); });
}); });