Merge pull request #20340 from Aditi-1400/serialize-pattern-ab
Serialize pattern data into ArrayBuffer
This commit is contained in:
commit
1a8689b9be
@ -40,6 +40,7 @@ import {
|
||||
lookupMatrix,
|
||||
lookupNormalRect,
|
||||
} from "./core_utils.js";
|
||||
import { FontInfo, PatternInfo } from "../shared/obj-bin-transform.js";
|
||||
import {
|
||||
getEncoding,
|
||||
MacRomanEncoding,
|
||||
@ -72,7 +73,6 @@ import { BaseStream } from "./base_stream.js";
|
||||
import { bidi } from "./bidi.js";
|
||||
import { ColorSpace } from "./colorspace.js";
|
||||
import { ColorSpaceUtils } from "./colorspace_utils.js";
|
||||
import { FontInfo } from "../shared/obj-bin-transform.js";
|
||||
import { getFontSubstitution } from "./font_substitutions.js";
|
||||
import { getGlyphsUnicode } from "./glyphlist.js";
|
||||
import { getMetrics } from "./metrics.js";
|
||||
@ -1517,7 +1517,10 @@ class PartialEvaluator {
|
||||
localShadingPatternCache.set(shading, id);
|
||||
|
||||
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 {
|
||||
this.handler.send("obj", [id, this.pageIndex, "Pattern", patternIR]);
|
||||
}
|
||||
|
||||
@ -45,6 +45,7 @@ import {
|
||||
StatTimer,
|
||||
} from "./display_utils.js";
|
||||
import { FontFaceObject, FontLoader } from "./font_loader.js";
|
||||
import { FontInfo, PatternInfo } from "../shared/obj-bin-transform.js";
|
||||
import {
|
||||
getDataProp,
|
||||
getFactoryUrlProp,
|
||||
@ -67,7 +68,6 @@ import { DOMCMapReaderFactory } from "display-cmap_reader_factory";
|
||||
import { DOMFilterFactory } from "./filter_factory.js";
|
||||
import { DOMStandardFontDataFactory } from "display-standard_fontdata_factory";
|
||||
import { DOMWasmFactory } from "display-wasm_factory";
|
||||
import { FontInfo } from "../shared/obj-bin-transform.js";
|
||||
import { GlobalWorkerOptions } from "./worker_options.js";
|
||||
import { Metadata } from "./metadata.js";
|
||||
import { OptionalContentConfig } from "./optional_content_config.js";
|
||||
@ -2804,9 +2804,12 @@ class WorkerTransport {
|
||||
break;
|
||||
case "FontPath":
|
||||
case "Image":
|
||||
case "Pattern":
|
||||
this.commonObjs.resolve(id, exportedData);
|
||||
break;
|
||||
case "Pattern":
|
||||
const pattern = new PatternInfo(exportedData);
|
||||
this.commonObjs.resolve(id, pattern.getIR());
|
||||
break;
|
||||
default:
|
||||
throw new Error(`Got unknown common object type ${type}`);
|
||||
}
|
||||
|
||||
@ -13,7 +13,7 @@
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
import { assert } from "./util.js";
|
||||
import { assert, MeshFigureType } from "./util.js";
|
||||
|
||||
class CssFontInfo {
|
||||
#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",
|
||||
"autolinker_spec.js",
|
||||
"bidi_spec.js",
|
||||
"bin_font_info_spec.js",
|
||||
"canvas_factory_spec.js",
|
||||
"cff_parser_spec.js",
|
||||
"cmap_spec.js",
|
||||
@ -33,6 +32,7 @@
|
||||
"murmurhash3_spec.js",
|
||||
"network_utils_spec.js",
|
||||
"node_stream_spec.js",
|
||||
"obj_bin_transform_spec.js",
|
||||
"parser_spec.js",
|
||||
"pdf.image_decoders_spec.js",
|
||||
"pdf.worker_spec.js",
|
||||
|
||||
@ -53,7 +53,6 @@ async function initializePDFJS(callback) {
|
||||
"pdfjs-test/unit/app_options_spec.js",
|
||||
"pdfjs-test/unit/autolinker_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/cff_parser_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/network_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/pdf.image_decoders_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