Serialize pattern data into ArrayBuffer
Follow up on https://github.com/mozilla/pdf.js/pull/20197, This serializes pattern data into an ArrayBuffer which is then transferred from the worker to the main thread. It sets up the stage for us to eventually switch to a SharedArrayBuffer in the future.
This commit is contained in:
parent
30fdf16071
commit
fa631806bf
@ -40,6 +40,7 @@ import {
|
|||||||
lookupMatrix,
|
lookupMatrix,
|
||||||
lookupNormalRect,
|
lookupNormalRect,
|
||||||
} from "./core_utils.js";
|
} from "./core_utils.js";
|
||||||
|
import { FontInfo, PatternInfo } from "../shared/obj-bin-transform.js";
|
||||||
import {
|
import {
|
||||||
getEncoding,
|
getEncoding,
|
||||||
MacRomanEncoding,
|
MacRomanEncoding,
|
||||||
@ -72,7 +73,6 @@ import { BaseStream } from "./base_stream.js";
|
|||||||
import { bidi } from "./bidi.js";
|
import { bidi } from "./bidi.js";
|
||||||
import { ColorSpace } from "./colorspace.js";
|
import { ColorSpace } from "./colorspace.js";
|
||||||
import { ColorSpaceUtils } from "./colorspace_utils.js";
|
import { ColorSpaceUtils } from "./colorspace_utils.js";
|
||||||
import { FontInfo } from "../shared/obj-bin-transform.js";
|
|
||||||
import { getFontSubstitution } from "./font_substitutions.js";
|
import { getFontSubstitution } from "./font_substitutions.js";
|
||||||
import { getGlyphsUnicode } from "./glyphlist.js";
|
import { getGlyphsUnicode } from "./glyphlist.js";
|
||||||
import { getMetrics } from "./metrics.js";
|
import { getMetrics } from "./metrics.js";
|
||||||
@ -1517,7 +1517,10 @@ class PartialEvaluator {
|
|||||||
localShadingPatternCache.set(shading, id);
|
localShadingPatternCache.set(shading, id);
|
||||||
|
|
||||||
if (this.parsingType3Font) {
|
if (this.parsingType3Font) {
|
||||||
this.handler.send("commonobj", [id, "Pattern", patternIR]);
|
const transfers = [];
|
||||||
|
const patternBuffer = PatternInfo.write(patternIR);
|
||||||
|
transfers.push(patternBuffer);
|
||||||
|
this.handler.send("commonobj", [id, "Pattern", patternBuffer], transfers);
|
||||||
} else {
|
} else {
|
||||||
this.handler.send("obj", [id, this.pageIndex, "Pattern", patternIR]);
|
this.handler.send("obj", [id, this.pageIndex, "Pattern", patternIR]);
|
||||||
}
|
}
|
||||||
|
|||||||
@ -45,6 +45,7 @@ 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 {
|
import {
|
||||||
getDataProp,
|
getDataProp,
|
||||||
getFactoryUrlProp,
|
getFactoryUrlProp,
|
||||||
@ -67,7 +68,6 @@ import { DOMCMapReaderFactory } from "display-cmap_reader_factory";
|
|||||||
import { DOMFilterFactory } from "./filter_factory.js";
|
import { DOMFilterFactory } from "./filter_factory.js";
|
||||||
import { DOMStandardFontDataFactory } from "display-standard_fontdata_factory";
|
import { DOMStandardFontDataFactory } from "display-standard_fontdata_factory";
|
||||||
import { DOMWasmFactory } from "display-wasm_factory";
|
import { DOMWasmFactory } from "display-wasm_factory";
|
||||||
import { FontInfo } from "../shared/obj-bin-transform.js";
|
|
||||||
import { GlobalWorkerOptions } from "./worker_options.js";
|
import { GlobalWorkerOptions } from "./worker_options.js";
|
||||||
import { Metadata } from "./metadata.js";
|
import { Metadata } from "./metadata.js";
|
||||||
import { OptionalContentConfig } from "./optional_content_config.js";
|
import { OptionalContentConfig } from "./optional_content_config.js";
|
||||||
@ -2804,9 +2804,12 @@ class WorkerTransport {
|
|||||||
break;
|
break;
|
||||||
case "FontPath":
|
case "FontPath":
|
||||||
case "Image":
|
case "Image":
|
||||||
case "Pattern":
|
|
||||||
this.commonObjs.resolve(id, exportedData);
|
this.commonObjs.resolve(id, exportedData);
|
||||||
break;
|
break;
|
||||||
|
case "Pattern":
|
||||||
|
const pattern = new PatternInfo(exportedData);
|
||||||
|
this.commonObjs.resolve(id, pattern.getIR());
|
||||||
|
break;
|
||||||
default:
|
default:
|
||||||
throw new Error(`Got unknown common object type ${type}`);
|
throw new Error(`Got unknown common object type ${type}`);
|
||||||
}
|
}
|
||||||
|
|||||||
@ -13,7 +13,7 @@
|
|||||||
* limitations under the License.
|
* limitations under the License.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import { assert } from "./util.js";
|
import { assert, MeshFigureType } from "./util.js";
|
||||||
|
|
||||||
class CssFontInfo {
|
class CssFontInfo {
|
||||||
#buffer;
|
#buffer;
|
||||||
@ -606,4 +606,279 @@ class FontInfo {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export { CssFontInfo, FontInfo, SystemFontInfo };
|
class PatternInfo {
|
||||||
|
static #KIND = 0; // 1=axial, 2=radial, 3=mesh
|
||||||
|
|
||||||
|
static #HAS_BBOX = 1; // 0/1
|
||||||
|
|
||||||
|
static #HAS_BACKGROUND = 2; // 0/1 (background for mesh patterns)
|
||||||
|
|
||||||
|
static #SHADING_TYPE = 3; // shadingType (only for mesh patterns)
|
||||||
|
|
||||||
|
static #N_COORD = 4; // number of coordinate pairs
|
||||||
|
|
||||||
|
static #N_COLOR = 8; // number of rgb triplets
|
||||||
|
|
||||||
|
static #N_STOP = 12; // number of gradient stops
|
||||||
|
|
||||||
|
static #N_FIGURES = 16; // number of figures
|
||||||
|
|
||||||
|
constructor(buffer) {
|
||||||
|
this.buffer = buffer;
|
||||||
|
this.view = new DataView(buffer);
|
||||||
|
this.data = new Uint8Array(buffer);
|
||||||
|
}
|
||||||
|
|
||||||
|
static write(ir) {
|
||||||
|
let kind,
|
||||||
|
bbox = null,
|
||||||
|
coords = [],
|
||||||
|
colors = [],
|
||||||
|
colorStops = [],
|
||||||
|
figures = [],
|
||||||
|
shadingType = null, // only needed for mesh patterns
|
||||||
|
background = null; // background for mesh patterns
|
||||||
|
|
||||||
|
switch (ir[0]) {
|
||||||
|
case "RadialAxial":
|
||||||
|
kind = ir[1] === "axial" ? 1 : 2;
|
||||||
|
bbox = ir[2];
|
||||||
|
colorStops = ir[3];
|
||||||
|
if (kind === 1) {
|
||||||
|
coords.push(...ir[4], ...ir[5]);
|
||||||
|
} else {
|
||||||
|
coords.push(ir[4][0], ir[4][1], ir[6], ir[5][0], ir[5][1], ir[7]);
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case "Mesh":
|
||||||
|
kind = 3;
|
||||||
|
shadingType = ir[1];
|
||||||
|
coords = ir[2];
|
||||||
|
colors = ir[3];
|
||||||
|
figures = ir[4] || [];
|
||||||
|
bbox = ir[6];
|
||||||
|
background = ir[7];
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
throw new Error(`Unsupported pattern type: ${ir[0]}`);
|
||||||
|
}
|
||||||
|
|
||||||
|
const nCoord = Math.floor(coords.length / 2);
|
||||||
|
const nColor = Math.floor(colors.length / 3);
|
||||||
|
const nStop = colorStops.length;
|
||||||
|
const nFigures = figures.length;
|
||||||
|
|
||||||
|
let figuresSize = 0;
|
||||||
|
for (const figure of figures) {
|
||||||
|
figuresSize += 1;
|
||||||
|
figuresSize = Math.ceil(figuresSize / 4) * 4; // Ensure 4-byte alignment
|
||||||
|
figuresSize += 4 + figure.coords.length * 4;
|
||||||
|
figuresSize += 4 + figure.colors.length * 4;
|
||||||
|
if (figure.verticesPerRow !== undefined) {
|
||||||
|
figuresSize += 4;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const byteLen =
|
||||||
|
20 +
|
||||||
|
nCoord * 8 +
|
||||||
|
nColor * 3 +
|
||||||
|
nStop * 8 +
|
||||||
|
(bbox ? 16 : 0) +
|
||||||
|
(background ? 3 : 0) +
|
||||||
|
figuresSize;
|
||||||
|
const buffer = new ArrayBuffer(byteLen);
|
||||||
|
const dataView = new DataView(buffer);
|
||||||
|
const u8data = new Uint8Array(buffer);
|
||||||
|
|
||||||
|
dataView.setUint8(PatternInfo.#KIND, kind);
|
||||||
|
dataView.setUint8(PatternInfo.#HAS_BBOX, bbox ? 1 : 0);
|
||||||
|
dataView.setUint8(PatternInfo.#HAS_BACKGROUND, background ? 1 : 0);
|
||||||
|
dataView.setUint8(PatternInfo.#SHADING_TYPE, shadingType); // Only for mesh pattern, null otherwise
|
||||||
|
dataView.setUint32(PatternInfo.#N_COORD, nCoord, true);
|
||||||
|
dataView.setUint32(PatternInfo.#N_COLOR, nColor, true);
|
||||||
|
dataView.setUint32(PatternInfo.#N_STOP, nStop, true);
|
||||||
|
dataView.setUint32(PatternInfo.#N_FIGURES, nFigures, true);
|
||||||
|
|
||||||
|
let offset = 20;
|
||||||
|
const coordsView = new Float32Array(buffer, offset, nCoord * 2);
|
||||||
|
coordsView.set(coords);
|
||||||
|
offset += nCoord * 8;
|
||||||
|
|
||||||
|
u8data.set(colors, offset);
|
||||||
|
offset += nColor * 3;
|
||||||
|
|
||||||
|
for (const [pos, hex] of colorStops) {
|
||||||
|
dataView.setFloat32(offset, pos, true);
|
||||||
|
offset += 4;
|
||||||
|
dataView.setUint32(offset, parseInt(hex.slice(1), 16), true);
|
||||||
|
offset += 4;
|
||||||
|
}
|
||||||
|
if (bbox) {
|
||||||
|
for (const v of bbox) {
|
||||||
|
dataView.setFloat32(offset, v, true);
|
||||||
|
offset += 4;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (background) {
|
||||||
|
u8data.set(background, offset);
|
||||||
|
offset += 3;
|
||||||
|
}
|
||||||
|
|
||||||
|
for (let i = 0; i < figures.length; i++) {
|
||||||
|
const figure = figures[i];
|
||||||
|
dataView.setUint8(offset, figure.type);
|
||||||
|
offset += 1;
|
||||||
|
// Ensure 4-byte alignment
|
||||||
|
offset = Math.ceil(offset / 4) * 4;
|
||||||
|
dataView.setUint32(offset, figure.coords.length, true);
|
||||||
|
offset += 4;
|
||||||
|
const figureCoordsView = new Int32Array(
|
||||||
|
buffer,
|
||||||
|
offset,
|
||||||
|
figure.coords.length
|
||||||
|
);
|
||||||
|
figureCoordsView.set(figure.coords);
|
||||||
|
offset += figure.coords.length * 4;
|
||||||
|
dataView.setUint32(offset, figure.colors.length, true);
|
||||||
|
offset += 4;
|
||||||
|
const colorsView = new Int32Array(buffer, offset, figure.colors.length);
|
||||||
|
colorsView.set(figure.colors);
|
||||||
|
offset += figure.colors.length * 4;
|
||||||
|
|
||||||
|
if (figure.verticesPerRow !== undefined) {
|
||||||
|
dataView.setUint32(offset, figure.verticesPerRow, true);
|
||||||
|
offset += 4;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return buffer;
|
||||||
|
}
|
||||||
|
|
||||||
|
getIR() {
|
||||||
|
const dataView = this.view;
|
||||||
|
const kind = this.data[PatternInfo.#KIND];
|
||||||
|
const hasBBox = !!this.data[PatternInfo.#HAS_BBOX];
|
||||||
|
const hasBackground = !!this.data[PatternInfo.#HAS_BACKGROUND];
|
||||||
|
const nCoord = dataView.getUint32(PatternInfo.#N_COORD, true);
|
||||||
|
const nColor = dataView.getUint32(PatternInfo.#N_COLOR, true);
|
||||||
|
const nStop = dataView.getUint32(PatternInfo.#N_STOP, true);
|
||||||
|
const nFigures = dataView.getUint32(PatternInfo.#N_FIGURES, true);
|
||||||
|
|
||||||
|
let offset = 20;
|
||||||
|
const coords = new Float32Array(this.buffer, offset, nCoord * 2);
|
||||||
|
offset += nCoord * 8;
|
||||||
|
const colors = new Uint8Array(this.buffer, offset, nColor * 3);
|
||||||
|
offset += nColor * 3;
|
||||||
|
const stops = [];
|
||||||
|
for (let i = 0; i < nStop; ++i) {
|
||||||
|
const p = dataView.getFloat32(offset, true);
|
||||||
|
offset += 4;
|
||||||
|
const rgb = dataView.getUint32(offset, true);
|
||||||
|
offset += 4;
|
||||||
|
stops.push([p, `#${rgb.toString(16).padStart(6, "0")}`]);
|
||||||
|
}
|
||||||
|
let bbox = null;
|
||||||
|
if (hasBBox) {
|
||||||
|
bbox = [];
|
||||||
|
for (let i = 0; i < 4; ++i) {
|
||||||
|
bbox.push(dataView.getFloat32(offset, true));
|
||||||
|
offset += 4;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
let background = null;
|
||||||
|
if (hasBackground) {
|
||||||
|
background = new Uint8Array(this.buffer, offset, 3);
|
||||||
|
offset += 3;
|
||||||
|
}
|
||||||
|
|
||||||
|
const figures = [];
|
||||||
|
for (let i = 0; i < nFigures; ++i) {
|
||||||
|
const type = dataView.getUint8(offset);
|
||||||
|
offset += 1;
|
||||||
|
// Ensure 4-byte alignment
|
||||||
|
offset = Math.ceil(offset / 4) * 4;
|
||||||
|
|
||||||
|
const coordsLength = dataView.getUint32(offset, true);
|
||||||
|
offset += 4;
|
||||||
|
const figureCoords = new Int32Array(this.buffer, offset, coordsLength);
|
||||||
|
offset += coordsLength * 4;
|
||||||
|
|
||||||
|
const colorsLength = dataView.getUint32(offset, true);
|
||||||
|
offset += 4;
|
||||||
|
const figureColors = new Int32Array(this.buffer, offset, colorsLength);
|
||||||
|
offset += colorsLength * 4;
|
||||||
|
|
||||||
|
const figure = {
|
||||||
|
type,
|
||||||
|
coords: figureCoords,
|
||||||
|
colors: figureColors,
|
||||||
|
};
|
||||||
|
|
||||||
|
if (type === MeshFigureType.LATTICE) {
|
||||||
|
figure.verticesPerRow = dataView.getUint32(offset, true);
|
||||||
|
offset += 4;
|
||||||
|
}
|
||||||
|
|
||||||
|
figures.push(figure);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (kind === 1) {
|
||||||
|
// axial
|
||||||
|
return [
|
||||||
|
"RadialAxial",
|
||||||
|
"axial",
|
||||||
|
bbox,
|
||||||
|
stops,
|
||||||
|
Array.from(coords.slice(0, 2)),
|
||||||
|
Array.from(coords.slice(2, 4)),
|
||||||
|
null,
|
||||||
|
null,
|
||||||
|
];
|
||||||
|
}
|
||||||
|
if (kind === 2) {
|
||||||
|
return [
|
||||||
|
"RadialAxial",
|
||||||
|
"radial",
|
||||||
|
bbox,
|
||||||
|
stops,
|
||||||
|
[coords[0], coords[1]],
|
||||||
|
[coords[3], coords[4]],
|
||||||
|
coords[2],
|
||||||
|
coords[5],
|
||||||
|
];
|
||||||
|
}
|
||||||
|
if (kind === 3) {
|
||||||
|
const shadingType = this.data[PatternInfo.#SHADING_TYPE];
|
||||||
|
let bounds = null;
|
||||||
|
if (coords.length > 0) {
|
||||||
|
let minX = coords[0],
|
||||||
|
maxX = coords[0];
|
||||||
|
let minY = coords[1],
|
||||||
|
maxY = coords[1];
|
||||||
|
for (let i = 0; i < coords.length; i += 2) {
|
||||||
|
const x = coords[i],
|
||||||
|
y = coords[i + 1];
|
||||||
|
minX = minX > x ? x : minX;
|
||||||
|
minY = minY > y ? y : minY;
|
||||||
|
maxX = maxX < x ? x : maxX;
|
||||||
|
maxY = maxY < y ? y : maxY;
|
||||||
|
}
|
||||||
|
bounds = [minX, minY, maxX, maxY];
|
||||||
|
}
|
||||||
|
return [
|
||||||
|
"Mesh",
|
||||||
|
shadingType,
|
||||||
|
coords,
|
||||||
|
colors,
|
||||||
|
figures,
|
||||||
|
bounds,
|
||||||
|
bbox,
|
||||||
|
background,
|
||||||
|
];
|
||||||
|
}
|
||||||
|
throw new Error(`Unsupported pattern kind: ${kind}`);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
export { CssFontInfo, FontInfo, PatternInfo, SystemFontInfo };
|
||||||
|
|||||||
@ -1,164 +0,0 @@
|
|||||||
/* 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 {
|
|
||||||
CssFontInfo,
|
|
||||||
FontInfo,
|
|
||||||
SystemFontInfo,
|
|
||||||
} from "../../src/shared/obj-bin-transform.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();
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
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");
|
|
||||||
});
|
|
||||||
});
|
|
||||||
});
|
|
||||||
@ -10,7 +10,6 @@
|
|||||||
"app_options_spec.js",
|
"app_options_spec.js",
|
||||||
"autolinker_spec.js",
|
"autolinker_spec.js",
|
||||||
"bidi_spec.js",
|
"bidi_spec.js",
|
||||||
"bin_font_info_spec.js",
|
|
||||||
"canvas_factory_spec.js",
|
"canvas_factory_spec.js",
|
||||||
"cff_parser_spec.js",
|
"cff_parser_spec.js",
|
||||||
"cmap_spec.js",
|
"cmap_spec.js",
|
||||||
@ -33,6 +32,7 @@
|
|||||||
"murmurhash3_spec.js",
|
"murmurhash3_spec.js",
|
||||||
"network_utils_spec.js",
|
"network_utils_spec.js",
|
||||||
"node_stream_spec.js",
|
"node_stream_spec.js",
|
||||||
|
"obj_bin_transform_spec.js",
|
||||||
"parser_spec.js",
|
"parser_spec.js",
|
||||||
"pdf.image_decoders_spec.js",
|
"pdf.image_decoders_spec.js",
|
||||||
"pdf.worker_spec.js",
|
"pdf.worker_spec.js",
|
||||||
|
|||||||
@ -53,7 +53,6 @@ async function initializePDFJS(callback) {
|
|||||||
"pdfjs-test/unit/app_options_spec.js",
|
"pdfjs-test/unit/app_options_spec.js",
|
||||||
"pdfjs-test/unit/autolinker_spec.js",
|
"pdfjs-test/unit/autolinker_spec.js",
|
||||||
"pdfjs-test/unit/bidi_spec.js",
|
"pdfjs-test/unit/bidi_spec.js",
|
||||||
"pdfjs-test/unit/bin_font_info_spec.js",
|
|
||||||
"pdfjs-test/unit/canvas_factory_spec.js",
|
"pdfjs-test/unit/canvas_factory_spec.js",
|
||||||
"pdfjs-test/unit/cff_parser_spec.js",
|
"pdfjs-test/unit/cff_parser_spec.js",
|
||||||
"pdfjs-test/unit/cmap_spec.js",
|
"pdfjs-test/unit/cmap_spec.js",
|
||||||
@ -76,6 +75,7 @@ async function initializePDFJS(callback) {
|
|||||||
"pdfjs-test/unit/murmurhash3_spec.js",
|
"pdfjs-test/unit/murmurhash3_spec.js",
|
||||||
"pdfjs-test/unit/network_spec.js",
|
"pdfjs-test/unit/network_spec.js",
|
||||||
"pdfjs-test/unit/network_utils_spec.js",
|
"pdfjs-test/unit/network_utils_spec.js",
|
||||||
|
"pdfjs-test/unit/obj_bin_transform_spec.js",
|
||||||
"pdfjs-test/unit/parser_spec.js",
|
"pdfjs-test/unit/parser_spec.js",
|
||||||
"pdfjs-test/unit/pdf.image_decoders_spec.js",
|
"pdfjs-test/unit/pdf.image_decoders_spec.js",
|
||||||
"pdfjs-test/unit/pdf.worker_spec.js",
|
"pdfjs-test/unit/pdf.worker_spec.js",
|
||||||
|
|||||||
452
test/unit/obj_bin_transform_spec.js
Normal file
452
test/unit/obj_bin_transform_spec.js
Normal file
@ -0,0 +1,452 @@
|
|||||||
|
/* 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 {
|
||||||
|
CssFontInfo,
|
||||||
|
FontInfo,
|
||||||
|
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();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
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 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],
|
||||||
|
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();
|
||||||
|
});
|
||||||
|
});
|
||||||
Loading…
x
Reference in New Issue
Block a user