diff --git a/src/core/evaluator.js b/src/core/evaluator.js index 3e836ce16ca4a..55fb31669f30e 100644 --- a/src/core/evaluator.js +++ b/src/core/evaluator.js @@ -40,7 +40,11 @@ import { lookupMatrix, lookupNormalRect, } from "./core_utils.js"; -import { FontInfo, PatternInfo } from "../shared/obj-bin-transform.js"; +import { + FontInfo, + FontPathInfo, + PatternInfo, +} from "../shared/obj-bin-transform.js"; import { getEncoding, MacRomanEncoding, @@ -4663,11 +4667,8 @@ class PartialEvaluator { if (font.renderer.hasBuiltPath(fontChar)) { return; } - handler.send("commonobj", [ - glyphName, - "FontPath", - font.renderer.getPathJs(fontChar), - ]); + const buffer = FontPathInfo.write(font.renderer.getPathJs(fontChar)); + handler.send("commonobj", [glyphName, "FontPath", buffer], [buffer]); } catch (reason) { if (evaluatorOptions.ignoreErrors) { warn(`buildFontPaths - ignoring ${glyphName} glyph: "${reason}".`); diff --git a/src/display/api.js b/src/display/api.js index 75ed964d33f97..3642f08ead6d0 100644 --- a/src/display/api.js +++ b/src/display/api.js @@ -45,7 +45,11 @@ import { StatTimer, } from "./display_utils.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 { getDataProp, getFactoryUrlProp, @@ -2821,6 +2825,8 @@ class WorkerTransport { } break; case "FontPath": + this.commonObjs.resolve(id, new FontPathInfo(exportedData)); + break; case "Image": this.commonObjs.resolve(id, exportedData); break; diff --git a/src/display/font_loader.js b/src/display/font_loader.js index 7d8f01a9fc52a..e52847fb33497 100644 --- a/src/display/font_loader.js +++ b/src/display/font_loader.js @@ -436,7 +436,7 @@ class FontFaceObject { } catch (ex) { warn(`getPathGenerator - ignoring character: "${ex}".`); } - const path = makePathFromDrawOPS(cmds); + const path = makePathFromDrawOPS(cmds.path); if (!this.fontExtraProperties) { // Remove the raw path-string, since we don't need it anymore. diff --git a/src/shared/obj-bin-transform.js b/src/shared/obj-bin-transform.js index 2deeb3f611eac..7e0f4b14178c7 100644 --- a/src/shared/obj-bin-transform.js +++ b/src/shared/obj-bin-transform.js @@ -13,7 +13,7 @@ * limitations under the License. */ -import { assert, MeshFigureType } from "./util.js"; +import { assert, FeatureTest, MeshFigureType } from "./util.js"; class CssFontInfo { #buffer; @@ -881,4 +881,40 @@ class PatternInfo { 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 }; diff --git a/test/unit/obj_bin_transform_spec.js b/test/unit/obj_bin_transform_spec.js index 6378ed9b8a5b9..157efa3cb06ab 100644 --- a/test/unit/obj_bin_transform_spec.js +++ b/test/unit/obj_bin_transform_spec.js @@ -16,437 +16,470 @@ import { CssFontInfo, FontInfo, + FontPathInfo, PatternInfo, SystemFontInfo, } from "../../src/shared/obj-bin-transform.js"; -import { MeshFigureType } from "../../src/shared/util.js"; - -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 = CssFontInfo.write(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 = SystemFontInfo.write(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(); - }); - }); +import { FeatureTest, MeshFigureType } 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 = CssFontInfo.write(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("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 = FontInfo.write(fontInfo); - expect(buffer.byteLength).toEqual(sizeEstimate); - const deserialized = new FontInfo({ data: 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(); - }); + 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 = SystemFontInfo.write(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(); + }); + }); - it("nesting should work as expected", function () { - const buffer = FontInfo.write({ - ...fontInfo, - cssFontInfo, - systemFontInfo, + 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 = FontInfo.write(fontInfo); + expect(buffer.byteLength).toEqual(sizeEstimate); + const deserialized = new FontInfo({ data: 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 = FontInfo.write({ + ...fontInfo, + cssFontInfo, + 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 = [ - "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, -]; - -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], + 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 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); - }); + const radialPatternIR = [ + "RadialAxial", + "radial", + [5, 5, 95, 45], + [ + [0, "#ffff00"], + [0.3, "#ff00ff"], + [0.7, "#00ffff"], + [1, "#ffffff"], + ], + [25, 25], + [75, 35], + 5, + 25, + ]; - it("must handle figures with different type enums correctly", function () { - const customFiguresIR = [ + const meshPatternIR = [ "Mesh", - 6, - new Float32Array([0, 0, 10, 10]), - new Uint8Array([255, 128, 64]), + 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.PATCH, - coords: new Int32Array([0, 2]), - colors: new Int32Array([0, 2]), + 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.TRIANGLES, - coords: new Int32Array([0]), - colors: new Int32Array([0]), + 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, 10, 10], - null, - null, + [0, 0, 100, 100], + [0, 0, 100, 100], + [128, 128, 128], ]; - const buffer = PatternInfo.write(customFiguresIR); - const patternInfo = new PatternInfo(buffer); - const reconstructedIR = patternInfo.getIR(); + 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(); + }); - expect(reconstructedIR[4].length).toEqual(2); - expect(reconstructedIR[4][0].type).toEqual(MeshFigureType.PATCH); - expect(reconstructedIR[4][1].type).toEqual(MeshFigureType.TRIANGLES); - }); + 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 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]), - ]; + 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]); + }); - const buffer = PatternInfo.write(meshWithBgIR); - const patternInfo = new PatternInfo(buffer); - const reconstructedIR = patternInfo.getIR(); + 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 + }); - 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, - ]; + it("must preserve figure data integrity across serialization", function () { + const buffer = PatternInfo.write(meshPatternIR); + const patternInfo = new PatternInfo(buffer); + const reconstructedIR = patternInfo.getIR(); - const buffer2 = PatternInfo.write(meshNoBgIR); - const patternInfo2 = new PatternInfo(buffer2); - const reconstructedIR2 = patternInfo2.getIR(); + // Verify data integrity by checking exact values + const originalFig = meshPatternIR[4][0]; + const reconstructedFig = reconstructedIR[4][0]; - expect(reconstructedIR2[7]).toBeNull(); - }); + for (let i = 0; i < originalFig.coords.length; i++) { + expect(reconstructedFig.coords[i]).toEqual(originalFig.coords[i]); + } - 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, - ]; + 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); - const buffer = PatternInfo.write(customMeshIR); - const patternInfo = new PatternInfo(buffer); - const reconstructedIR = patternInfo.getIR(); + expect(axialBuffer.byteLength).toBeLessThan(radialBuffer.byteLength); + expect(meshBuffer.byteLength).toBeGreaterThan(axialBuffer.byteLength); + expect(meshBuffer.byteLength).toBeGreaterThan(radialBuffer.byteLength); + }); - expect(reconstructedIR[5]).toEqual([-10, -5, 20, 30]); - expect(reconstructedIR[7]).toBeNull(); + 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(); + }); + }); + }); + + 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 = FontPathInfo.write(path); + const fontPathInfo = new FontPathInfo(buffer); + expect(fontPathInfo.path).toEqual(path); + }); }); });