mirror of
https://github.com/mozilla/pdf.js.git
synced 2026-06-22 16:05:56 +02:00
Split the src/shared/obj-bin-transform.js file into separate files for the main/worker threads (PR 20197 follow-up)
On the worker-thread only the static `write` methods are actually used, and on the main-thread only class instances are being created. Hence this, after PR 20197, leads to a bunch of dead code in both of the *built* `pdf.mjs` and `pdf.worker.js` files. This patch reduces the size of the `gulp mozcentral` output by `21 419` bytes, i.e. `21` kilo-bytes, which I believe is way too large of a saving to not do this. (I can't even remember the last time we managed to reduce build-size this much with a single patch.)
This commit is contained in:
parent
ea949b9a0a
commit
3842936edf
@ -31,6 +31,11 @@ import {
|
|||||||
} from "../shared/util.js";
|
} from "../shared/util.js";
|
||||||
import { CMapFactory, IdentityCMap } from "./cmap.js";
|
import { CMapFactory, IdentityCMap } from "./cmap.js";
|
||||||
import { Cmd, Dict, EOF, isName, Name, Ref, RefSet } from "./primitives.js";
|
import { Cmd, Dict, EOF, isName, Name, Ref, RefSet } from "./primitives.js";
|
||||||
|
import {
|
||||||
|
compileFontInfo,
|
||||||
|
compileFontPathInfo,
|
||||||
|
compilePatternInfo,
|
||||||
|
} from "./obj_bin_transform_core.js";
|
||||||
import {
|
import {
|
||||||
compileType3Glyph,
|
compileType3Glyph,
|
||||||
FontFlags,
|
FontFlags,
|
||||||
@ -44,11 +49,6 @@ import {
|
|||||||
lookupMatrix,
|
lookupMatrix,
|
||||||
lookupNormalRect,
|
lookupNormalRect,
|
||||||
} from "./core_utils.js";
|
} from "./core_utils.js";
|
||||||
import {
|
|
||||||
FontInfo,
|
|
||||||
FontPathInfo,
|
|
||||||
PatternInfo,
|
|
||||||
} from "../shared/obj-bin-transform.js";
|
|
||||||
import {
|
import {
|
||||||
getEncoding,
|
getEncoding,
|
||||||
MacRomanEncoding,
|
MacRomanEncoding,
|
||||||
@ -1531,10 +1531,8 @@ class PartialEvaluator {
|
|||||||
localShadingPatternCache.set(shading, id);
|
localShadingPatternCache.set(shading, id);
|
||||||
|
|
||||||
if (this.parsingType3Font) {
|
if (this.parsingType3Font) {
|
||||||
const transfers = [];
|
const buffer = compilePatternInfo(patternIR);
|
||||||
const patternBuffer = PatternInfo.write(patternIR);
|
this.handler.send("commonobj", [id, "Pattern", buffer], [buffer]);
|
||||||
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]);
|
||||||
}
|
}
|
||||||
@ -4755,7 +4753,7 @@ class PartialEvaluator {
|
|||||||
if (font.renderer.hasBuiltPath(fontChar)) {
|
if (font.renderer.hasBuiltPath(fontChar)) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
const buffer = FontPathInfo.write(font.renderer.getPathJs(fontChar));
|
const buffer = compileFontPathInfo(font.renderer.getPathJs(fontChar));
|
||||||
handler.send("commonobj", [glyphName, "FontPath", buffer], [buffer]);
|
handler.send("commonobj", [glyphName, "FontPath", buffer], [buffer]);
|
||||||
} catch (reason) {
|
} catch (reason) {
|
||||||
if (evaluatorOptions.ignoreErrors) {
|
if (evaluatorOptions.ignoreErrors) {
|
||||||
@ -4812,7 +4810,7 @@ class TranslatedFont {
|
|||||||
if (fontData.data.charProcOperatorList) {
|
if (fontData.data.charProcOperatorList) {
|
||||||
fontData.charProcOperatorList = fontData.data.charProcOperatorList;
|
fontData.charProcOperatorList = fontData.data.charProcOperatorList;
|
||||||
}
|
}
|
||||||
fontData.data = FontInfo.write(fontData.data);
|
fontData.data = compileFontInfo(fontData.data);
|
||||||
transfer.push(fontData.data);
|
transfer.push(fontData.data);
|
||||||
}
|
}
|
||||||
handler.send("commonobj", [this.loadedName, "Font", fontData], transfer);
|
handler.send("commonobj", [this.loadedName, "Font", fontData], transfer);
|
||||||
|
|||||||
414
src/core/obj_bin_transform_core.js
Normal file
414
src/core/obj_bin_transform_core.js
Normal file
@ -0,0 +1,414 @@
|
|||||||
|
/* 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 { assert, FeatureTest } from "../shared/util.js";
|
||||||
|
import {
|
||||||
|
CSS_FONT_INFO,
|
||||||
|
FONT_INFO,
|
||||||
|
PATTERN_INFO,
|
||||||
|
SYSTEM_FONT_INFO,
|
||||||
|
} from "../shared/obj_bin_transform_utils.js";
|
||||||
|
|
||||||
|
function compileCssFontInfo(info) {
|
||||||
|
const encoder = new TextEncoder();
|
||||||
|
const encodedStrings = {};
|
||||||
|
let stringsLength = 0;
|
||||||
|
for (const prop of CSS_FONT_INFO.strings) {
|
||||||
|
const encoded = encoder.encode(info[prop]);
|
||||||
|
encodedStrings[prop] = encoded;
|
||||||
|
stringsLength += 4 + encoded.length;
|
||||||
|
}
|
||||||
|
|
||||||
|
const buffer = new ArrayBuffer(stringsLength);
|
||||||
|
const data = new Uint8Array(buffer);
|
||||||
|
const view = new DataView(buffer);
|
||||||
|
let offset = 0;
|
||||||
|
|
||||||
|
for (const prop of CSS_FONT_INFO.strings) {
|
||||||
|
const encoded = encodedStrings[prop];
|
||||||
|
const length = encoded.length;
|
||||||
|
view.setUint32(offset, length);
|
||||||
|
data.set(encoded, offset + 4);
|
||||||
|
offset += 4 + length;
|
||||||
|
}
|
||||||
|
assert(offset === buffer.byteLength, "compileCssFontInfo: Buffer overflow");
|
||||||
|
return buffer;
|
||||||
|
}
|
||||||
|
|
||||||
|
function compileSystemFontInfo(info) {
|
||||||
|
const encoder = new TextEncoder();
|
||||||
|
const encodedStrings = {};
|
||||||
|
let stringsLength = 0;
|
||||||
|
for (const prop of SYSTEM_FONT_INFO.strings) {
|
||||||
|
const encoded = encoder.encode(info[prop]);
|
||||||
|
encodedStrings[prop] = encoded;
|
||||||
|
stringsLength += 4 + encoded.length;
|
||||||
|
}
|
||||||
|
stringsLength += 4;
|
||||||
|
let encodedStyleStyle,
|
||||||
|
encodedStyleWeight,
|
||||||
|
lengthEstimate = 1 + stringsLength;
|
||||||
|
if (info.style) {
|
||||||
|
encodedStyleStyle = encoder.encode(info.style.style);
|
||||||
|
encodedStyleWeight = encoder.encode(info.style.weight);
|
||||||
|
lengthEstimate +=
|
||||||
|
4 + encodedStyleStyle.length + 4 + encodedStyleWeight.length;
|
||||||
|
}
|
||||||
|
|
||||||
|
const buffer = new ArrayBuffer(lengthEstimate);
|
||||||
|
const data = new Uint8Array(buffer);
|
||||||
|
const view = new DataView(buffer);
|
||||||
|
let offset = 0;
|
||||||
|
|
||||||
|
view.setUint8(offset++, info.guessFallback ? 1 : 0);
|
||||||
|
view.setUint32(offset, 0);
|
||||||
|
offset += 4;
|
||||||
|
stringsLength = 0;
|
||||||
|
for (const prop of SYSTEM_FONT_INFO.strings) {
|
||||||
|
const encoded = encodedStrings[prop];
|
||||||
|
const length = encoded.length;
|
||||||
|
stringsLength += 4 + length;
|
||||||
|
view.setUint32(offset, length);
|
||||||
|
data.set(encoded, offset + 4);
|
||||||
|
offset += 4 + length;
|
||||||
|
}
|
||||||
|
view.setUint32(offset - stringsLength - 4, stringsLength);
|
||||||
|
|
||||||
|
if (info.style) {
|
||||||
|
view.setUint32(offset, encodedStyleStyle.length);
|
||||||
|
data.set(encodedStyleStyle, offset + 4);
|
||||||
|
offset += 4 + encodedStyleStyle.length;
|
||||||
|
view.setUint32(offset, encodedStyleWeight.length);
|
||||||
|
data.set(encodedStyleWeight, offset + 4);
|
||||||
|
offset += 4 + encodedStyleWeight.length;
|
||||||
|
}
|
||||||
|
assert(offset <= buffer.byteLength, "compileSystemFontInfo: Buffer overflow");
|
||||||
|
return buffer.transferToFixedLength(offset);
|
||||||
|
}
|
||||||
|
|
||||||
|
function compileFontInfo(font) {
|
||||||
|
const systemFontInfoBuffer = font.systemFontInfo
|
||||||
|
? compileSystemFontInfo(font.systemFontInfo)
|
||||||
|
: null;
|
||||||
|
const cssFontInfoBuffer = font.cssFontInfo
|
||||||
|
? compileCssFontInfo(font.cssFontInfo)
|
||||||
|
: null;
|
||||||
|
|
||||||
|
const encoder = new TextEncoder();
|
||||||
|
const encodedStrings = {};
|
||||||
|
let stringsLength = 0;
|
||||||
|
for (const prop of FONT_INFO.strings) {
|
||||||
|
encodedStrings[prop] = encoder.encode(font[prop]);
|
||||||
|
stringsLength += 4 + encodedStrings[prop].length;
|
||||||
|
}
|
||||||
|
|
||||||
|
const lengthEstimate =
|
||||||
|
FONT_INFO.OFFSET_STRINGS +
|
||||||
|
4 +
|
||||||
|
stringsLength +
|
||||||
|
4 +
|
||||||
|
(systemFontInfoBuffer?.byteLength ?? 0) +
|
||||||
|
4 +
|
||||||
|
(cssFontInfoBuffer?.byteLength ?? 0) +
|
||||||
|
4 +
|
||||||
|
(font.data?.length ?? 0);
|
||||||
|
|
||||||
|
const buffer = new ArrayBuffer(lengthEstimate);
|
||||||
|
const data = new Uint8Array(buffer);
|
||||||
|
const view = new DataView(buffer);
|
||||||
|
let offset = 0;
|
||||||
|
|
||||||
|
const numBools = FONT_INFO.bools.length;
|
||||||
|
let boolByte = 0,
|
||||||
|
boolBit = 0;
|
||||||
|
for (let i = 0; i < numBools; i++) {
|
||||||
|
const value = font[FONT_INFO.bools[i]];
|
||||||
|
// eslint-disable-next-line no-nested-ternary
|
||||||
|
const bits = value === undefined ? 0x00 : value ? 0x02 : 0x01;
|
||||||
|
boolByte |= bits << boolBit;
|
||||||
|
boolBit += 2;
|
||||||
|
if (boolBit === 8 || i === numBools - 1) {
|
||||||
|
view.setUint8(offset++, boolByte);
|
||||||
|
boolByte = 0;
|
||||||
|
boolBit = 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
assert(
|
||||||
|
offset === FONT_INFO.OFFSET_NUMBERS,
|
||||||
|
"compileFontInfo: Boolean properties offset mismatch"
|
||||||
|
);
|
||||||
|
|
||||||
|
for (const prop of FONT_INFO.numbers) {
|
||||||
|
view.setFloat64(offset, font[prop]);
|
||||||
|
offset += 8;
|
||||||
|
}
|
||||||
|
assert(
|
||||||
|
offset === FONT_INFO.OFFSET_BBOX,
|
||||||
|
"compileFontInfo: Number properties offset mismatch"
|
||||||
|
);
|
||||||
|
|
||||||
|
if (font.bbox) {
|
||||||
|
view.setUint8(offset++, 4);
|
||||||
|
for (const coord of font.bbox) {
|
||||||
|
view.setInt16(offset, coord, true);
|
||||||
|
offset += 2;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
view.setUint8(offset++, 0);
|
||||||
|
offset += 2 * 4; // TODO: optimize this padding away
|
||||||
|
}
|
||||||
|
|
||||||
|
assert(
|
||||||
|
offset === FONT_INFO.OFFSET_FONT_MATRIX,
|
||||||
|
"compileFontInfo: BBox properties offset mismatch"
|
||||||
|
);
|
||||||
|
|
||||||
|
if (font.fontMatrix) {
|
||||||
|
view.setUint8(offset++, 6);
|
||||||
|
for (const point of font.fontMatrix) {
|
||||||
|
view.setFloat64(offset, point, true);
|
||||||
|
offset += 8;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
view.setUint8(offset++, 0);
|
||||||
|
offset += 8 * 6; // TODO: optimize this padding away
|
||||||
|
}
|
||||||
|
|
||||||
|
assert(
|
||||||
|
offset === FONT_INFO.OFFSET_DEFAULT_VMETRICS,
|
||||||
|
"compileFontInfo: FontMatrix properties offset mismatch"
|
||||||
|
);
|
||||||
|
|
||||||
|
if (font.defaultVMetrics) {
|
||||||
|
view.setUint8(offset++, 1);
|
||||||
|
for (const metric of font.defaultVMetrics) {
|
||||||
|
view.setInt16(offset, metric, true);
|
||||||
|
offset += 2;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
view.setUint8(offset++, 0);
|
||||||
|
offset += 3 * 2; // TODO: optimize this padding away
|
||||||
|
}
|
||||||
|
|
||||||
|
assert(
|
||||||
|
offset === FONT_INFO.OFFSET_STRINGS,
|
||||||
|
"compileFontInfo: DefaultVMetrics properties offset mismatch"
|
||||||
|
);
|
||||||
|
|
||||||
|
view.setUint32(FONT_INFO.OFFSET_STRINGS, 0);
|
||||||
|
offset += 4;
|
||||||
|
for (const prop of FONT_INFO.strings) {
|
||||||
|
const encoded = encodedStrings[prop];
|
||||||
|
const length = encoded.length;
|
||||||
|
view.setUint32(offset, length);
|
||||||
|
data.set(encoded, offset + 4);
|
||||||
|
offset += 4 + length;
|
||||||
|
}
|
||||||
|
view.setUint32(
|
||||||
|
FONT_INFO.OFFSET_STRINGS,
|
||||||
|
offset - FONT_INFO.OFFSET_STRINGS - 4
|
||||||
|
);
|
||||||
|
|
||||||
|
if (!systemFontInfoBuffer) {
|
||||||
|
view.setUint32(offset, 0);
|
||||||
|
offset += 4;
|
||||||
|
} else {
|
||||||
|
const length = systemFontInfoBuffer.byteLength;
|
||||||
|
view.setUint32(offset, length);
|
||||||
|
assert(
|
||||||
|
offset + 4 + length <= buffer.byteLength,
|
||||||
|
"compileFontInfo: Buffer overflow at systemFontInfo"
|
||||||
|
);
|
||||||
|
data.set(new Uint8Array(systemFontInfoBuffer), offset + 4);
|
||||||
|
offset += 4 + length;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!cssFontInfoBuffer) {
|
||||||
|
view.setUint32(offset, 0);
|
||||||
|
offset += 4;
|
||||||
|
} else {
|
||||||
|
const length = cssFontInfoBuffer.byteLength;
|
||||||
|
view.setUint32(offset, length);
|
||||||
|
assert(
|
||||||
|
offset + 4 + length <= buffer.byteLength,
|
||||||
|
"compileFontInfo: Buffer overflow at cssFontInfo"
|
||||||
|
);
|
||||||
|
data.set(new Uint8Array(cssFontInfoBuffer), offset + 4);
|
||||||
|
offset += 4 + length;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (font.data === undefined) {
|
||||||
|
view.setUint32(offset, 0);
|
||||||
|
offset += 4;
|
||||||
|
} else {
|
||||||
|
view.setUint32(offset, font.data.length);
|
||||||
|
data.set(font.data, offset + 4);
|
||||||
|
offset += 4 + font.data.length;
|
||||||
|
}
|
||||||
|
|
||||||
|
assert(offset <= buffer.byteLength, "compileFontInfo: Buffer overflow");
|
||||||
|
return buffer.transferToFixedLength(offset);
|
||||||
|
}
|
||||||
|
|
||||||
|
function compilePatternInfo(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(PATTERN_INFO.KIND, kind);
|
||||||
|
dataView.setUint8(PATTERN_INFO.HAS_BBOX, bbox ? 1 : 0);
|
||||||
|
dataView.setUint8(PATTERN_INFO.HAS_BACKGROUND, background ? 1 : 0);
|
||||||
|
dataView.setUint8(PATTERN_INFO.SHADING_TYPE, shadingType); // Only for mesh pattern, null otherwise
|
||||||
|
dataView.setUint32(PATTERN_INFO.N_COORD, nCoord, true);
|
||||||
|
dataView.setUint32(PATTERN_INFO.N_COLOR, nColor, true);
|
||||||
|
dataView.setUint32(PATTERN_INFO.N_STOP, nStop, true);
|
||||||
|
dataView.setUint32(PATTERN_INFO.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;
|
||||||
|
}
|
||||||
|
|
||||||
|
function compileFontPathInfo(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;
|
||||||
|
}
|
||||||
|
|
||||||
|
export {
|
||||||
|
compileCssFontInfo,
|
||||||
|
compileFontInfo,
|
||||||
|
compileFontPathInfo,
|
||||||
|
compilePatternInfo,
|
||||||
|
compileSystemFontInfo,
|
||||||
|
};
|
||||||
@ -56,7 +56,7 @@ import {
|
|||||||
FontInfo,
|
FontInfo,
|
||||||
FontPathInfo,
|
FontPathInfo,
|
||||||
PatternInfo,
|
PatternInfo,
|
||||||
} from "../shared/obj-bin-transform.js";
|
} from "./obj_bin_transform_display.js";
|
||||||
import {
|
import {
|
||||||
getDataProp,
|
getDataProp,
|
||||||
getFactoryUrlProp,
|
getFactoryUrlProp,
|
||||||
|
|||||||
491
src/display/obj_bin_transform_display.js
Normal file
491
src/display/obj_bin_transform_display.js
Normal file
@ -0,0 +1,491 @@
|
|||||||
|
/* 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 { assert, FeatureTest, MeshFigureType } from "../shared/util.js";
|
||||||
|
import {
|
||||||
|
CSS_FONT_INFO,
|
||||||
|
FONT_INFO,
|
||||||
|
PATTERN_INFO,
|
||||||
|
SYSTEM_FONT_INFO,
|
||||||
|
} from "../shared/obj_bin_transform_utils.js";
|
||||||
|
|
||||||
|
class CssFontInfo {
|
||||||
|
#buffer;
|
||||||
|
|
||||||
|
#decoder = new TextDecoder();
|
||||||
|
|
||||||
|
#view;
|
||||||
|
|
||||||
|
constructor(buffer) {
|
||||||
|
this.#buffer = buffer;
|
||||||
|
this.#view = new DataView(this.#buffer);
|
||||||
|
}
|
||||||
|
|
||||||
|
#readString(index) {
|
||||||
|
assert(index < CSS_FONT_INFO.strings.length, "Invalid string index");
|
||||||
|
let offset = 0;
|
||||||
|
for (let i = 0; i < index; i++) {
|
||||||
|
offset += this.#view.getUint32(offset) + 4;
|
||||||
|
}
|
||||||
|
const length = this.#view.getUint32(offset);
|
||||||
|
return this.#decoder.decode(
|
||||||
|
new Uint8Array(this.#buffer, offset + 4, length)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
get fontFamily() {
|
||||||
|
return this.#readString(0);
|
||||||
|
}
|
||||||
|
|
||||||
|
get fontWeight() {
|
||||||
|
return this.#readString(1);
|
||||||
|
}
|
||||||
|
|
||||||
|
get italicAngle() {
|
||||||
|
return this.#readString(2);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class SystemFontInfo {
|
||||||
|
#buffer;
|
||||||
|
|
||||||
|
#decoder = new TextDecoder();
|
||||||
|
|
||||||
|
#view;
|
||||||
|
|
||||||
|
constructor(buffer) {
|
||||||
|
this.#buffer = buffer;
|
||||||
|
this.#view = new DataView(this.#buffer);
|
||||||
|
}
|
||||||
|
|
||||||
|
get guessFallback() {
|
||||||
|
return this.#view.getUint8(0) !== 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
#readString(index) {
|
||||||
|
assert(index < SYSTEM_FONT_INFO.strings.length, "Invalid string index");
|
||||||
|
let offset = 5;
|
||||||
|
for (let i = 0; i < index; i++) {
|
||||||
|
offset += this.#view.getUint32(offset) + 4;
|
||||||
|
}
|
||||||
|
const length = this.#view.getUint32(offset);
|
||||||
|
return this.#decoder.decode(
|
||||||
|
new Uint8Array(this.#buffer, offset + 4, length)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
get css() {
|
||||||
|
return this.#readString(0);
|
||||||
|
}
|
||||||
|
|
||||||
|
get loadedName() {
|
||||||
|
return this.#readString(1);
|
||||||
|
}
|
||||||
|
|
||||||
|
get baseFontName() {
|
||||||
|
return this.#readString(2);
|
||||||
|
}
|
||||||
|
|
||||||
|
get src() {
|
||||||
|
return this.#readString(3);
|
||||||
|
}
|
||||||
|
|
||||||
|
get style() {
|
||||||
|
let offset = 1;
|
||||||
|
offset += 4 + this.#view.getUint32(offset);
|
||||||
|
const styleLength = this.#view.getUint32(offset);
|
||||||
|
const style = this.#decoder.decode(
|
||||||
|
new Uint8Array(this.#buffer, offset + 4, styleLength)
|
||||||
|
);
|
||||||
|
offset += 4 + styleLength;
|
||||||
|
const weightLength = this.#view.getUint32(offset);
|
||||||
|
const weight = this.#decoder.decode(
|
||||||
|
new Uint8Array(this.#buffer, offset + 4, weightLength)
|
||||||
|
);
|
||||||
|
return { style, weight };
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class FontInfo {
|
||||||
|
#buffer;
|
||||||
|
|
||||||
|
#decoder = new TextDecoder();
|
||||||
|
|
||||||
|
#view;
|
||||||
|
|
||||||
|
constructor({ data, extra }) {
|
||||||
|
this.#buffer = data;
|
||||||
|
this.#view = new DataView(this.#buffer);
|
||||||
|
if (extra) {
|
||||||
|
Object.assign(this, extra);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#readBoolean(index) {
|
||||||
|
assert(index < FONT_INFO.bools.length, "Invalid boolean index");
|
||||||
|
const byteOffset = Math.floor(index / 4);
|
||||||
|
const bitOffset = (index * 2) % 8;
|
||||||
|
const value = (this.#view.getUint8(byteOffset) >> bitOffset) & 0x03;
|
||||||
|
return value === 0x00 ? undefined : value === 0x02;
|
||||||
|
}
|
||||||
|
|
||||||
|
get black() {
|
||||||
|
return this.#readBoolean(0);
|
||||||
|
}
|
||||||
|
|
||||||
|
get bold() {
|
||||||
|
return this.#readBoolean(1);
|
||||||
|
}
|
||||||
|
|
||||||
|
get disableFontFace() {
|
||||||
|
return this.#readBoolean(2);
|
||||||
|
}
|
||||||
|
|
||||||
|
get fontExtraProperties() {
|
||||||
|
return this.#readBoolean(3);
|
||||||
|
}
|
||||||
|
|
||||||
|
get isInvalidPDFjsFont() {
|
||||||
|
return this.#readBoolean(4);
|
||||||
|
}
|
||||||
|
|
||||||
|
get isType3Font() {
|
||||||
|
return this.#readBoolean(5);
|
||||||
|
}
|
||||||
|
|
||||||
|
get italic() {
|
||||||
|
return this.#readBoolean(6);
|
||||||
|
}
|
||||||
|
|
||||||
|
get missingFile() {
|
||||||
|
return this.#readBoolean(7);
|
||||||
|
}
|
||||||
|
|
||||||
|
get remeasure() {
|
||||||
|
return this.#readBoolean(8);
|
||||||
|
}
|
||||||
|
|
||||||
|
get vertical() {
|
||||||
|
return this.#readBoolean(9);
|
||||||
|
}
|
||||||
|
|
||||||
|
#readNumber(index) {
|
||||||
|
assert(index < FONT_INFO.numbers.length, "Invalid number index");
|
||||||
|
return this.#view.getFloat64(FONT_INFO.OFFSET_NUMBERS + index * 8);
|
||||||
|
}
|
||||||
|
|
||||||
|
get ascent() {
|
||||||
|
return this.#readNumber(0);
|
||||||
|
}
|
||||||
|
|
||||||
|
get defaultWidth() {
|
||||||
|
return this.#readNumber(1);
|
||||||
|
}
|
||||||
|
|
||||||
|
get descent() {
|
||||||
|
return this.#readNumber(2);
|
||||||
|
}
|
||||||
|
|
||||||
|
get bbox() {
|
||||||
|
let offset = FONT_INFO.OFFSET_BBOX;
|
||||||
|
const numCoords = this.#view.getUint8(offset);
|
||||||
|
if (numCoords === 0) {
|
||||||
|
return undefined;
|
||||||
|
}
|
||||||
|
offset += 1;
|
||||||
|
const bbox = [];
|
||||||
|
for (let i = 0; i < 4; i++) {
|
||||||
|
bbox.push(this.#view.getInt16(offset, true));
|
||||||
|
offset += 2;
|
||||||
|
}
|
||||||
|
return bbox;
|
||||||
|
}
|
||||||
|
|
||||||
|
get fontMatrix() {
|
||||||
|
let offset = FONT_INFO.OFFSET_FONT_MATRIX;
|
||||||
|
const numPoints = this.#view.getUint8(offset);
|
||||||
|
if (numPoints === 0) {
|
||||||
|
return undefined;
|
||||||
|
}
|
||||||
|
offset += 1;
|
||||||
|
const fontMatrix = [];
|
||||||
|
for (let i = 0; i < 6; i++) {
|
||||||
|
fontMatrix.push(this.#view.getFloat64(offset, true));
|
||||||
|
offset += 8;
|
||||||
|
}
|
||||||
|
return fontMatrix;
|
||||||
|
}
|
||||||
|
|
||||||
|
get defaultVMetrics() {
|
||||||
|
let offset = FONT_INFO.OFFSET_DEFAULT_VMETRICS;
|
||||||
|
const numMetrics = this.#view.getUint8(offset);
|
||||||
|
if (numMetrics === 0) {
|
||||||
|
return undefined;
|
||||||
|
}
|
||||||
|
offset += 1;
|
||||||
|
const defaultVMetrics = [];
|
||||||
|
for (let i = 0; i < 3; i++) {
|
||||||
|
defaultVMetrics.push(this.#view.getInt16(offset, true));
|
||||||
|
offset += 2;
|
||||||
|
}
|
||||||
|
return defaultVMetrics;
|
||||||
|
}
|
||||||
|
|
||||||
|
#readString(index) {
|
||||||
|
assert(index < FONT_INFO.strings.length, "Invalid string index");
|
||||||
|
let offset = FONT_INFO.OFFSET_STRINGS + 4;
|
||||||
|
for (let i = 0; i < index; i++) {
|
||||||
|
offset += this.#view.getUint32(offset) + 4;
|
||||||
|
}
|
||||||
|
const length = this.#view.getUint32(offset);
|
||||||
|
const stringData = new Uint8Array(length);
|
||||||
|
stringData.set(new Uint8Array(this.#buffer, offset + 4, length));
|
||||||
|
return this.#decoder.decode(stringData);
|
||||||
|
}
|
||||||
|
|
||||||
|
get fallbackName() {
|
||||||
|
return this.#readString(0);
|
||||||
|
}
|
||||||
|
|
||||||
|
get loadedName() {
|
||||||
|
return this.#readString(1);
|
||||||
|
}
|
||||||
|
|
||||||
|
get mimetype() {
|
||||||
|
return this.#readString(2);
|
||||||
|
}
|
||||||
|
|
||||||
|
get name() {
|
||||||
|
return this.#readString(3);
|
||||||
|
}
|
||||||
|
|
||||||
|
#getDataOffsets() {
|
||||||
|
let offset = FONT_INFO.OFFSET_STRINGS;
|
||||||
|
const stringsLength = this.#view.getUint32(offset);
|
||||||
|
offset += 4 + stringsLength;
|
||||||
|
const systemFontInfoLength = this.#view.getUint32(offset);
|
||||||
|
offset += 4 + systemFontInfoLength;
|
||||||
|
const cssFontInfoLength = this.#view.getUint32(offset);
|
||||||
|
offset += 4 + cssFontInfoLength;
|
||||||
|
const length = this.#view.getUint32(offset);
|
||||||
|
|
||||||
|
return { offset, length };
|
||||||
|
}
|
||||||
|
|
||||||
|
get data() {
|
||||||
|
const { offset, length } = this.#getDataOffsets();
|
||||||
|
return length === 0
|
||||||
|
? undefined
|
||||||
|
: new Uint8Array(this.#buffer, offset + 4, length);
|
||||||
|
}
|
||||||
|
|
||||||
|
clearData() {
|
||||||
|
const { offset, length } = this.#getDataOffsets();
|
||||||
|
if (length === 0) {
|
||||||
|
return; // The data is either not present, or it was previously cleared.
|
||||||
|
}
|
||||||
|
this.#view.setUint32(offset, 0); // Zero the data-length.
|
||||||
|
|
||||||
|
// Replace the buffer/view with only its contents *before* the data-block.
|
||||||
|
this.#buffer = new Uint8Array(this.#buffer, 0, offset + 4).slice().buffer;
|
||||||
|
this.#view = new DataView(this.#buffer);
|
||||||
|
}
|
||||||
|
|
||||||
|
get cssFontInfo() {
|
||||||
|
let offset = FONT_INFO.OFFSET_STRINGS;
|
||||||
|
const stringsLength = this.#view.getUint32(offset);
|
||||||
|
offset += 4 + stringsLength;
|
||||||
|
const systemFontInfoLength = this.#view.getUint32(offset);
|
||||||
|
offset += 4 + systemFontInfoLength;
|
||||||
|
const cssFontInfoLength = this.#view.getUint32(offset);
|
||||||
|
if (cssFontInfoLength === 0) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
const cssFontInfoData = new Uint8Array(cssFontInfoLength);
|
||||||
|
cssFontInfoData.set(
|
||||||
|
new Uint8Array(this.#buffer, offset + 4, cssFontInfoLength)
|
||||||
|
);
|
||||||
|
return new CssFontInfo(cssFontInfoData.buffer);
|
||||||
|
}
|
||||||
|
|
||||||
|
get systemFontInfo() {
|
||||||
|
let offset = FONT_INFO.OFFSET_STRINGS;
|
||||||
|
const stringsLength = this.#view.getUint32(offset);
|
||||||
|
offset += 4 + stringsLength;
|
||||||
|
const systemFontInfoLength = this.#view.getUint32(offset);
|
||||||
|
if (systemFontInfoLength === 0) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
const systemFontInfoData = new Uint8Array(systemFontInfoLength);
|
||||||
|
systemFontInfoData.set(
|
||||||
|
new Uint8Array(this.#buffer, offset + 4, systemFontInfoLength)
|
||||||
|
);
|
||||||
|
return new SystemFontInfo(systemFontInfoData.buffer);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class PatternInfo {
|
||||||
|
constructor(buffer) {
|
||||||
|
this.buffer = buffer;
|
||||||
|
this.view = new DataView(buffer);
|
||||||
|
this.data = new Uint8Array(buffer);
|
||||||
|
}
|
||||||
|
|
||||||
|
getIR() {
|
||||||
|
const dataView = this.view;
|
||||||
|
const kind = this.data[PATTERN_INFO.KIND];
|
||||||
|
const hasBBox = !!this.data[PATTERN_INFO.HAS_BBOX];
|
||||||
|
const hasBackground = !!this.data[PATTERN_INFO.HAS_BACKGROUND];
|
||||||
|
const nCoord = dataView.getUint32(PATTERN_INFO.N_COORD, true);
|
||||||
|
const nColor = dataView.getUint32(PATTERN_INFO.N_COLOR, true);
|
||||||
|
const nStop = dataView.getUint32(PATTERN_INFO.N_STOP, true);
|
||||||
|
const nFigures = dataView.getUint32(PATTERN_INFO.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[PATTERN_INFO.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}`);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class FontPathInfo {
|
||||||
|
#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 };
|
||||||
@ -1,919 +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 { assert, FeatureTest, MeshFigureType } from "./util.js";
|
|
||||||
|
|
||||||
class CssFontInfo {
|
|
||||||
#buffer;
|
|
||||||
|
|
||||||
#decoder = new TextDecoder();
|
|
||||||
|
|
||||||
#view;
|
|
||||||
|
|
||||||
static strings = ["fontFamily", "fontWeight", "italicAngle"];
|
|
||||||
|
|
||||||
static write(info) {
|
|
||||||
const encoder = new TextEncoder();
|
|
||||||
const encodedStrings = {};
|
|
||||||
let stringsLength = 0;
|
|
||||||
for (const prop of CssFontInfo.strings) {
|
|
||||||
const encoded = encoder.encode(info[prop]);
|
|
||||||
encodedStrings[prop] = encoded;
|
|
||||||
stringsLength += 4 + encoded.length;
|
|
||||||
}
|
|
||||||
|
|
||||||
const buffer = new ArrayBuffer(stringsLength);
|
|
||||||
const data = new Uint8Array(buffer);
|
|
||||||
const view = new DataView(buffer);
|
|
||||||
let offset = 0;
|
|
||||||
|
|
||||||
for (const prop of CssFontInfo.strings) {
|
|
||||||
const encoded = encodedStrings[prop];
|
|
||||||
const length = encoded.length;
|
|
||||||
view.setUint32(offset, length);
|
|
||||||
data.set(encoded, offset + 4);
|
|
||||||
offset += 4 + length;
|
|
||||||
}
|
|
||||||
assert(offset === buffer.byteLength, "CssFontInfo.write: Buffer overflow");
|
|
||||||
return buffer;
|
|
||||||
}
|
|
||||||
|
|
||||||
constructor(buffer) {
|
|
||||||
this.#buffer = buffer;
|
|
||||||
this.#view = new DataView(this.#buffer);
|
|
||||||
}
|
|
||||||
|
|
||||||
#readString(index) {
|
|
||||||
assert(index < CssFontInfo.strings.length, "Invalid string index");
|
|
||||||
let offset = 0;
|
|
||||||
for (let i = 0; i < index; i++) {
|
|
||||||
offset += this.#view.getUint32(offset) + 4;
|
|
||||||
}
|
|
||||||
const length = this.#view.getUint32(offset);
|
|
||||||
return this.#decoder.decode(
|
|
||||||
new Uint8Array(this.#buffer, offset + 4, length)
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
get fontFamily() {
|
|
||||||
return this.#readString(0);
|
|
||||||
}
|
|
||||||
|
|
||||||
get fontWeight() {
|
|
||||||
return this.#readString(1);
|
|
||||||
}
|
|
||||||
|
|
||||||
get italicAngle() {
|
|
||||||
return this.#readString(2);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
class SystemFontInfo {
|
|
||||||
#buffer;
|
|
||||||
|
|
||||||
#decoder = new TextDecoder();
|
|
||||||
|
|
||||||
#view;
|
|
||||||
|
|
||||||
static strings = ["css", "loadedName", "baseFontName", "src"];
|
|
||||||
|
|
||||||
static write(info) {
|
|
||||||
const encoder = new TextEncoder();
|
|
||||||
const encodedStrings = {};
|
|
||||||
let stringsLength = 0;
|
|
||||||
for (const prop of SystemFontInfo.strings) {
|
|
||||||
const encoded = encoder.encode(info[prop]);
|
|
||||||
encodedStrings[prop] = encoded;
|
|
||||||
stringsLength += 4 + encoded.length;
|
|
||||||
}
|
|
||||||
stringsLength += 4;
|
|
||||||
let encodedStyleStyle,
|
|
||||||
encodedStyleWeight,
|
|
||||||
lengthEstimate = 1 + stringsLength;
|
|
||||||
if (info.style) {
|
|
||||||
encodedStyleStyle = encoder.encode(info.style.style);
|
|
||||||
encodedStyleWeight = encoder.encode(info.style.weight);
|
|
||||||
lengthEstimate +=
|
|
||||||
4 + encodedStyleStyle.length + 4 + encodedStyleWeight.length;
|
|
||||||
}
|
|
||||||
|
|
||||||
const buffer = new ArrayBuffer(lengthEstimate);
|
|
||||||
const data = new Uint8Array(buffer);
|
|
||||||
const view = new DataView(buffer);
|
|
||||||
let offset = 0;
|
|
||||||
|
|
||||||
view.setUint8(offset++, info.guessFallback ? 1 : 0);
|
|
||||||
view.setUint32(offset, 0);
|
|
||||||
offset += 4;
|
|
||||||
stringsLength = 0;
|
|
||||||
for (const prop of SystemFontInfo.strings) {
|
|
||||||
const encoded = encodedStrings[prop];
|
|
||||||
const length = encoded.length;
|
|
||||||
stringsLength += 4 + length;
|
|
||||||
view.setUint32(offset, length);
|
|
||||||
data.set(encoded, offset + 4);
|
|
||||||
offset += 4 + length;
|
|
||||||
}
|
|
||||||
view.setUint32(offset - stringsLength - 4, stringsLength);
|
|
||||||
|
|
||||||
if (info.style) {
|
|
||||||
view.setUint32(offset, encodedStyleStyle.length);
|
|
||||||
data.set(encodedStyleStyle, offset + 4);
|
|
||||||
offset += 4 + encodedStyleStyle.length;
|
|
||||||
view.setUint32(offset, encodedStyleWeight.length);
|
|
||||||
data.set(encodedStyleWeight, offset + 4);
|
|
||||||
offset += 4 + encodedStyleWeight.length;
|
|
||||||
}
|
|
||||||
assert(
|
|
||||||
offset <= buffer.byteLength,
|
|
||||||
"SubstitionInfo.write: Buffer overflow"
|
|
||||||
);
|
|
||||||
return buffer.transferToFixedLength(offset);
|
|
||||||
}
|
|
||||||
|
|
||||||
constructor(buffer) {
|
|
||||||
this.#buffer = buffer;
|
|
||||||
this.#view = new DataView(this.#buffer);
|
|
||||||
}
|
|
||||||
|
|
||||||
get guessFallback() {
|
|
||||||
return this.#view.getUint8(0) !== 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
#readString(index) {
|
|
||||||
assert(index < SystemFontInfo.strings.length, "Invalid string index");
|
|
||||||
let offset = 5;
|
|
||||||
for (let i = 0; i < index; i++) {
|
|
||||||
offset += this.#view.getUint32(offset) + 4;
|
|
||||||
}
|
|
||||||
const length = this.#view.getUint32(offset);
|
|
||||||
return this.#decoder.decode(
|
|
||||||
new Uint8Array(this.#buffer, offset + 4, length)
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
get css() {
|
|
||||||
return this.#readString(0);
|
|
||||||
}
|
|
||||||
|
|
||||||
get loadedName() {
|
|
||||||
return this.#readString(1);
|
|
||||||
}
|
|
||||||
|
|
||||||
get baseFontName() {
|
|
||||||
return this.#readString(2);
|
|
||||||
}
|
|
||||||
|
|
||||||
get src() {
|
|
||||||
return this.#readString(3);
|
|
||||||
}
|
|
||||||
|
|
||||||
get style() {
|
|
||||||
let offset = 1;
|
|
||||||
offset += 4 + this.#view.getUint32(offset);
|
|
||||||
const styleLength = this.#view.getUint32(offset);
|
|
||||||
const style = this.#decoder.decode(
|
|
||||||
new Uint8Array(this.#buffer, offset + 4, styleLength)
|
|
||||||
);
|
|
||||||
offset += 4 + styleLength;
|
|
||||||
const weightLength = this.#view.getUint32(offset);
|
|
||||||
const weight = this.#decoder.decode(
|
|
||||||
new Uint8Array(this.#buffer, offset + 4, weightLength)
|
|
||||||
);
|
|
||||||
return { style, weight };
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
class FontInfo {
|
|
||||||
static bools = [
|
|
||||||
"black",
|
|
||||||
"bold",
|
|
||||||
"disableFontFace",
|
|
||||||
"fontExtraProperties",
|
|
||||||
"isInvalidPDFjsFont",
|
|
||||||
"isType3Font",
|
|
||||||
"italic",
|
|
||||||
"missingFile",
|
|
||||||
"remeasure",
|
|
||||||
"vertical",
|
|
||||||
];
|
|
||||||
|
|
||||||
static numbers = ["ascent", "defaultWidth", "descent"];
|
|
||||||
|
|
||||||
static strings = ["fallbackName", "loadedName", "mimetype", "name"];
|
|
||||||
|
|
||||||
static #OFFSET_NUMBERS = Math.ceil((this.bools.length * 2) / 8);
|
|
||||||
|
|
||||||
static #OFFSET_BBOX = this.#OFFSET_NUMBERS + this.numbers.length * 8;
|
|
||||||
|
|
||||||
static #OFFSET_FONT_MATRIX = this.#OFFSET_BBOX + 1 + 2 * 4;
|
|
||||||
|
|
||||||
static #OFFSET_DEFAULT_VMETRICS = this.#OFFSET_FONT_MATRIX + 1 + 8 * 6;
|
|
||||||
|
|
||||||
static #OFFSET_STRINGS = this.#OFFSET_DEFAULT_VMETRICS + 1 + 2 * 3;
|
|
||||||
|
|
||||||
#buffer;
|
|
||||||
|
|
||||||
#decoder = new TextDecoder();
|
|
||||||
|
|
||||||
#view;
|
|
||||||
|
|
||||||
constructor({ data, extra }) {
|
|
||||||
this.#buffer = data;
|
|
||||||
this.#view = new DataView(this.#buffer);
|
|
||||||
if (extra) {
|
|
||||||
Object.assign(this, extra);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#readBoolean(index) {
|
|
||||||
assert(index < FontInfo.bools.length, "Invalid boolean index");
|
|
||||||
const byteOffset = Math.floor(index / 4);
|
|
||||||
const bitOffset = (index * 2) % 8;
|
|
||||||
const value = (this.#view.getUint8(byteOffset) >> bitOffset) & 0x03;
|
|
||||||
return value === 0x00 ? undefined : value === 0x02;
|
|
||||||
}
|
|
||||||
|
|
||||||
get black() {
|
|
||||||
return this.#readBoolean(0);
|
|
||||||
}
|
|
||||||
|
|
||||||
get bold() {
|
|
||||||
return this.#readBoolean(1);
|
|
||||||
}
|
|
||||||
|
|
||||||
get disableFontFace() {
|
|
||||||
return this.#readBoolean(2);
|
|
||||||
}
|
|
||||||
|
|
||||||
get fontExtraProperties() {
|
|
||||||
return this.#readBoolean(3);
|
|
||||||
}
|
|
||||||
|
|
||||||
get isInvalidPDFjsFont() {
|
|
||||||
return this.#readBoolean(4);
|
|
||||||
}
|
|
||||||
|
|
||||||
get isType3Font() {
|
|
||||||
return this.#readBoolean(5);
|
|
||||||
}
|
|
||||||
|
|
||||||
get italic() {
|
|
||||||
return this.#readBoolean(6);
|
|
||||||
}
|
|
||||||
|
|
||||||
get missingFile() {
|
|
||||||
return this.#readBoolean(7);
|
|
||||||
}
|
|
||||||
|
|
||||||
get remeasure() {
|
|
||||||
return this.#readBoolean(8);
|
|
||||||
}
|
|
||||||
|
|
||||||
get vertical() {
|
|
||||||
return this.#readBoolean(9);
|
|
||||||
}
|
|
||||||
|
|
||||||
#readNumber(index) {
|
|
||||||
assert(index < FontInfo.numbers.length, "Invalid number index");
|
|
||||||
return this.#view.getFloat64(FontInfo.#OFFSET_NUMBERS + index * 8);
|
|
||||||
}
|
|
||||||
|
|
||||||
get ascent() {
|
|
||||||
return this.#readNumber(0);
|
|
||||||
}
|
|
||||||
|
|
||||||
get defaultWidth() {
|
|
||||||
return this.#readNumber(1);
|
|
||||||
}
|
|
||||||
|
|
||||||
get descent() {
|
|
||||||
return this.#readNumber(2);
|
|
||||||
}
|
|
||||||
|
|
||||||
get bbox() {
|
|
||||||
let offset = FontInfo.#OFFSET_BBOX;
|
|
||||||
const numCoords = this.#view.getUint8(offset);
|
|
||||||
if (numCoords === 0) {
|
|
||||||
return undefined;
|
|
||||||
}
|
|
||||||
offset += 1;
|
|
||||||
const bbox = [];
|
|
||||||
for (let i = 0; i < 4; i++) {
|
|
||||||
bbox.push(this.#view.getInt16(offset, true));
|
|
||||||
offset += 2;
|
|
||||||
}
|
|
||||||
return bbox;
|
|
||||||
}
|
|
||||||
|
|
||||||
get fontMatrix() {
|
|
||||||
let offset = FontInfo.#OFFSET_FONT_MATRIX;
|
|
||||||
const numPoints = this.#view.getUint8(offset);
|
|
||||||
if (numPoints === 0) {
|
|
||||||
return undefined;
|
|
||||||
}
|
|
||||||
offset += 1;
|
|
||||||
const fontMatrix = [];
|
|
||||||
for (let i = 0; i < 6; i++) {
|
|
||||||
fontMatrix.push(this.#view.getFloat64(offset, true));
|
|
||||||
offset += 8;
|
|
||||||
}
|
|
||||||
return fontMatrix;
|
|
||||||
}
|
|
||||||
|
|
||||||
get defaultVMetrics() {
|
|
||||||
let offset = FontInfo.#OFFSET_DEFAULT_VMETRICS;
|
|
||||||
const numMetrics = this.#view.getUint8(offset);
|
|
||||||
if (numMetrics === 0) {
|
|
||||||
return undefined;
|
|
||||||
}
|
|
||||||
offset += 1;
|
|
||||||
const defaultVMetrics = [];
|
|
||||||
for (let i = 0; i < 3; i++) {
|
|
||||||
defaultVMetrics.push(this.#view.getInt16(offset, true));
|
|
||||||
offset += 2;
|
|
||||||
}
|
|
||||||
return defaultVMetrics;
|
|
||||||
}
|
|
||||||
|
|
||||||
#readString(index) {
|
|
||||||
assert(index < FontInfo.strings.length, "Invalid string index");
|
|
||||||
let offset = FontInfo.#OFFSET_STRINGS + 4;
|
|
||||||
for (let i = 0; i < index; i++) {
|
|
||||||
offset += this.#view.getUint32(offset) + 4;
|
|
||||||
}
|
|
||||||
const length = this.#view.getUint32(offset);
|
|
||||||
const stringData = new Uint8Array(length);
|
|
||||||
stringData.set(new Uint8Array(this.#buffer, offset + 4, length));
|
|
||||||
return this.#decoder.decode(stringData);
|
|
||||||
}
|
|
||||||
|
|
||||||
get fallbackName() {
|
|
||||||
return this.#readString(0);
|
|
||||||
}
|
|
||||||
|
|
||||||
get loadedName() {
|
|
||||||
return this.#readString(1);
|
|
||||||
}
|
|
||||||
|
|
||||||
get mimetype() {
|
|
||||||
return this.#readString(2);
|
|
||||||
}
|
|
||||||
|
|
||||||
get name() {
|
|
||||||
return this.#readString(3);
|
|
||||||
}
|
|
||||||
|
|
||||||
#getDataOffsets() {
|
|
||||||
let offset = FontInfo.#OFFSET_STRINGS;
|
|
||||||
const stringsLength = this.#view.getUint32(offset);
|
|
||||||
offset += 4 + stringsLength;
|
|
||||||
const systemFontInfoLength = this.#view.getUint32(offset);
|
|
||||||
offset += 4 + systemFontInfoLength;
|
|
||||||
const cssFontInfoLength = this.#view.getUint32(offset);
|
|
||||||
offset += 4 + cssFontInfoLength;
|
|
||||||
const length = this.#view.getUint32(offset);
|
|
||||||
|
|
||||||
return { offset, length };
|
|
||||||
}
|
|
||||||
|
|
||||||
get data() {
|
|
||||||
const { offset, length } = this.#getDataOffsets();
|
|
||||||
return length === 0
|
|
||||||
? undefined
|
|
||||||
: new Uint8Array(this.#buffer, offset + 4, length);
|
|
||||||
}
|
|
||||||
|
|
||||||
clearData() {
|
|
||||||
const { offset, length } = this.#getDataOffsets();
|
|
||||||
if (length === 0) {
|
|
||||||
return; // The data is either not present, or it was previously cleared.
|
|
||||||
}
|
|
||||||
this.#view.setUint32(offset, 0); // Zero the data-length.
|
|
||||||
|
|
||||||
// Replace the buffer/view with only its contents *before* the data-block.
|
|
||||||
this.#buffer = new Uint8Array(this.#buffer, 0, offset + 4).slice().buffer;
|
|
||||||
this.#view = new DataView(this.#buffer);
|
|
||||||
}
|
|
||||||
|
|
||||||
get cssFontInfo() {
|
|
||||||
let offset = FontInfo.#OFFSET_STRINGS;
|
|
||||||
const stringsLength = this.#view.getUint32(offset);
|
|
||||||
offset += 4 + stringsLength;
|
|
||||||
const systemFontInfoLength = this.#view.getUint32(offset);
|
|
||||||
offset += 4 + systemFontInfoLength;
|
|
||||||
const cssFontInfoLength = this.#view.getUint32(offset);
|
|
||||||
if (cssFontInfoLength === 0) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
const cssFontInfoData = new Uint8Array(cssFontInfoLength);
|
|
||||||
cssFontInfoData.set(
|
|
||||||
new Uint8Array(this.#buffer, offset + 4, cssFontInfoLength)
|
|
||||||
);
|
|
||||||
return new CssFontInfo(cssFontInfoData.buffer);
|
|
||||||
}
|
|
||||||
|
|
||||||
get systemFontInfo() {
|
|
||||||
let offset = FontInfo.#OFFSET_STRINGS;
|
|
||||||
const stringsLength = this.#view.getUint32(offset);
|
|
||||||
offset += 4 + stringsLength;
|
|
||||||
const systemFontInfoLength = this.#view.getUint32(offset);
|
|
||||||
if (systemFontInfoLength === 0) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
const systemFontInfoData = new Uint8Array(systemFontInfoLength);
|
|
||||||
systemFontInfoData.set(
|
|
||||||
new Uint8Array(this.#buffer, offset + 4, systemFontInfoLength)
|
|
||||||
);
|
|
||||||
return new SystemFontInfo(systemFontInfoData.buffer);
|
|
||||||
}
|
|
||||||
|
|
||||||
static write(font) {
|
|
||||||
const systemFontInfoBuffer = font.systemFontInfo
|
|
||||||
? SystemFontInfo.write(font.systemFontInfo)
|
|
||||||
: null;
|
|
||||||
const cssFontInfoBuffer = font.cssFontInfo
|
|
||||||
? CssFontInfo.write(font.cssFontInfo)
|
|
||||||
: null;
|
|
||||||
|
|
||||||
const encoder = new TextEncoder();
|
|
||||||
const encodedStrings = {};
|
|
||||||
let stringsLength = 0;
|
|
||||||
for (const prop of FontInfo.strings) {
|
|
||||||
encodedStrings[prop] = encoder.encode(font[prop]);
|
|
||||||
stringsLength += 4 + encodedStrings[prop].length;
|
|
||||||
}
|
|
||||||
|
|
||||||
const lengthEstimate =
|
|
||||||
FontInfo.#OFFSET_STRINGS +
|
|
||||||
4 +
|
|
||||||
stringsLength +
|
|
||||||
4 +
|
|
||||||
(systemFontInfoBuffer?.byteLength ?? 0) +
|
|
||||||
4 +
|
|
||||||
(cssFontInfoBuffer?.byteLength ?? 0) +
|
|
||||||
4 +
|
|
||||||
(font.data?.length ?? 0);
|
|
||||||
|
|
||||||
const buffer = new ArrayBuffer(lengthEstimate);
|
|
||||||
const data = new Uint8Array(buffer);
|
|
||||||
const view = new DataView(buffer);
|
|
||||||
let offset = 0;
|
|
||||||
|
|
||||||
const numBools = FontInfo.bools.length;
|
|
||||||
let boolByte = 0,
|
|
||||||
boolBit = 0;
|
|
||||||
for (let i = 0; i < numBools; i++) {
|
|
||||||
const value = font[FontInfo.bools[i]];
|
|
||||||
// eslint-disable-next-line no-nested-ternary
|
|
||||||
const bits = value === undefined ? 0x00 : value ? 0x02 : 0x01;
|
|
||||||
boolByte |= bits << boolBit;
|
|
||||||
boolBit += 2;
|
|
||||||
if (boolBit === 8 || i === numBools - 1) {
|
|
||||||
view.setUint8(offset++, boolByte);
|
|
||||||
boolByte = 0;
|
|
||||||
boolBit = 0;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
assert(
|
|
||||||
offset === FontInfo.#OFFSET_NUMBERS,
|
|
||||||
"FontInfo.write: Boolean properties offset mismatch"
|
|
||||||
);
|
|
||||||
|
|
||||||
for (const prop of FontInfo.numbers) {
|
|
||||||
view.setFloat64(offset, font[prop]);
|
|
||||||
offset += 8;
|
|
||||||
}
|
|
||||||
assert(
|
|
||||||
offset === FontInfo.#OFFSET_BBOX,
|
|
||||||
"FontInfo.write: Number properties offset mismatch"
|
|
||||||
);
|
|
||||||
|
|
||||||
if (font.bbox) {
|
|
||||||
view.setUint8(offset++, 4);
|
|
||||||
for (const coord of font.bbox) {
|
|
||||||
view.setInt16(offset, coord, true);
|
|
||||||
offset += 2;
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
view.setUint8(offset++, 0);
|
|
||||||
offset += 2 * 4; // TODO: optimize this padding away
|
|
||||||
}
|
|
||||||
|
|
||||||
assert(
|
|
||||||
offset === FontInfo.#OFFSET_FONT_MATRIX,
|
|
||||||
"FontInfo.write: BBox properties offset mismatch"
|
|
||||||
);
|
|
||||||
|
|
||||||
if (font.fontMatrix) {
|
|
||||||
view.setUint8(offset++, 6);
|
|
||||||
for (const point of font.fontMatrix) {
|
|
||||||
view.setFloat64(offset, point, true);
|
|
||||||
offset += 8;
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
view.setUint8(offset++, 0);
|
|
||||||
offset += 8 * 6; // TODO: optimize this padding away
|
|
||||||
}
|
|
||||||
|
|
||||||
assert(
|
|
||||||
offset === FontInfo.#OFFSET_DEFAULT_VMETRICS,
|
|
||||||
"FontInfo.write: FontMatrix properties offset mismatch"
|
|
||||||
);
|
|
||||||
|
|
||||||
if (font.defaultVMetrics) {
|
|
||||||
view.setUint8(offset++, 1);
|
|
||||||
for (const metric of font.defaultVMetrics) {
|
|
||||||
view.setInt16(offset, metric, true);
|
|
||||||
offset += 2;
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
view.setUint8(offset++, 0);
|
|
||||||
offset += 3 * 2; // TODO: optimize this padding away
|
|
||||||
}
|
|
||||||
|
|
||||||
assert(
|
|
||||||
offset === FontInfo.#OFFSET_STRINGS,
|
|
||||||
"FontInfo.write: DefaultVMetrics properties offset mismatch"
|
|
||||||
);
|
|
||||||
|
|
||||||
view.setUint32(FontInfo.#OFFSET_STRINGS, 0);
|
|
||||||
offset += 4;
|
|
||||||
for (const prop of FontInfo.strings) {
|
|
||||||
const encoded = encodedStrings[prop];
|
|
||||||
const length = encoded.length;
|
|
||||||
view.setUint32(offset, length);
|
|
||||||
data.set(encoded, offset + 4);
|
|
||||||
offset += 4 + length;
|
|
||||||
}
|
|
||||||
view.setUint32(
|
|
||||||
FontInfo.#OFFSET_STRINGS,
|
|
||||||
offset - FontInfo.#OFFSET_STRINGS - 4
|
|
||||||
);
|
|
||||||
|
|
||||||
if (!systemFontInfoBuffer) {
|
|
||||||
view.setUint32(offset, 0);
|
|
||||||
offset += 4;
|
|
||||||
} else {
|
|
||||||
const length = systemFontInfoBuffer.byteLength;
|
|
||||||
view.setUint32(offset, length);
|
|
||||||
assert(
|
|
||||||
offset + 4 + length <= buffer.byteLength,
|
|
||||||
"FontInfo.write: Buffer overflow at systemFontInfo"
|
|
||||||
);
|
|
||||||
data.set(new Uint8Array(systemFontInfoBuffer), offset + 4);
|
|
||||||
offset += 4 + length;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!cssFontInfoBuffer) {
|
|
||||||
view.setUint32(offset, 0);
|
|
||||||
offset += 4;
|
|
||||||
} else {
|
|
||||||
const length = cssFontInfoBuffer.byteLength;
|
|
||||||
view.setUint32(offset, length);
|
|
||||||
assert(
|
|
||||||
offset + 4 + length <= buffer.byteLength,
|
|
||||||
"FontInfo.write: Buffer overflow at cssFontInfo"
|
|
||||||
);
|
|
||||||
data.set(new Uint8Array(cssFontInfoBuffer), offset + 4);
|
|
||||||
offset += 4 + length;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (font.data === undefined) {
|
|
||||||
view.setUint32(offset, 0);
|
|
||||||
offset += 4;
|
|
||||||
} else {
|
|
||||||
view.setUint32(offset, font.data.length);
|
|
||||||
data.set(font.data, offset + 4);
|
|
||||||
offset += 4 + font.data.length;
|
|
||||||
}
|
|
||||||
|
|
||||||
assert(offset <= buffer.byteLength, "FontInfo.write: Buffer overflow");
|
|
||||||
return buffer.transferToFixedLength(offset);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
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}`);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
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 };
|
|
||||||
71
src/shared/obj_bin_transform_utils.js
Normal file
71
src/shared/obj_bin_transform_utils.js
Normal file
@ -0,0 +1,71 @@
|
|||||||
|
/* 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.
|
||||||
|
*/
|
||||||
|
|
||||||
|
class CSS_FONT_INFO {
|
||||||
|
static strings = ["fontFamily", "fontWeight", "italicAngle"];
|
||||||
|
}
|
||||||
|
|
||||||
|
class SYSTEM_FONT_INFO {
|
||||||
|
static strings = ["css", "loadedName", "baseFontName", "src"];
|
||||||
|
}
|
||||||
|
|
||||||
|
class FONT_INFO {
|
||||||
|
static bools = [
|
||||||
|
"black",
|
||||||
|
"bold",
|
||||||
|
"disableFontFace",
|
||||||
|
"fontExtraProperties",
|
||||||
|
"isInvalidPDFjsFont",
|
||||||
|
"isType3Font",
|
||||||
|
"italic",
|
||||||
|
"missingFile",
|
||||||
|
"remeasure",
|
||||||
|
"vertical",
|
||||||
|
];
|
||||||
|
|
||||||
|
static numbers = ["ascent", "defaultWidth", "descent"];
|
||||||
|
|
||||||
|
static strings = ["fallbackName", "loadedName", "mimetype", "name"];
|
||||||
|
|
||||||
|
static OFFSET_NUMBERS = Math.ceil((this.bools.length * 2) / 8);
|
||||||
|
|
||||||
|
static OFFSET_BBOX = this.OFFSET_NUMBERS + this.numbers.length * 8;
|
||||||
|
|
||||||
|
static OFFSET_FONT_MATRIX = this.OFFSET_BBOX + 1 + 2 * 4;
|
||||||
|
|
||||||
|
static OFFSET_DEFAULT_VMETRICS = this.OFFSET_FONT_MATRIX + 1 + 8 * 6;
|
||||||
|
|
||||||
|
static OFFSET_STRINGS = this.OFFSET_DEFAULT_VMETRICS + 1 + 2 * 3;
|
||||||
|
}
|
||||||
|
|
||||||
|
class PATTERN_INFO {
|
||||||
|
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
|
||||||
|
}
|
||||||
|
|
||||||
|
export { CSS_FONT_INFO, FONT_INFO, PATTERN_INFO, SYSTEM_FONT_INFO };
|
||||||
@ -13,16 +13,23 @@
|
|||||||
* limitations under the License.
|
* limitations under the License.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
import {
|
||||||
|
compileCssFontInfo,
|
||||||
|
compileFontInfo,
|
||||||
|
compileFontPathInfo,
|
||||||
|
compilePatternInfo,
|
||||||
|
compileSystemFontInfo,
|
||||||
|
} from "../../src/core/obj_bin_transform_core.js";
|
||||||
import {
|
import {
|
||||||
CssFontInfo,
|
CssFontInfo,
|
||||||
FontInfo,
|
FontInfo,
|
||||||
FontPathInfo,
|
FontPathInfo,
|
||||||
PatternInfo,
|
PatternInfo,
|
||||||
SystemFontInfo,
|
SystemFontInfo,
|
||||||
} from "../../src/shared/obj-bin-transform.js";
|
} from "../../src/display/obj_bin_transform_display.js";
|
||||||
import { FeatureTest, MeshFigureType } from "../../src/shared/util.js";
|
import { FeatureTest, MeshFigureType } from "../../src/shared/util.js";
|
||||||
|
|
||||||
describe("obj-bin-transform", function () {
|
describe("obj_bin_transform", function () {
|
||||||
describe("Font data", function () {
|
describe("Font data", function () {
|
||||||
const cssFontInfo = {
|
const cssFontInfo = {
|
||||||
fontFamily: "Sample Family",
|
fontFamily: "Sample Family",
|
||||||
@ -78,7 +85,7 @@ describe("obj-bin-transform", function () {
|
|||||||
for (const string of ["Sample Family", "not a number", "angle"]) {
|
for (const string of ["Sample Family", "not a number", "angle"]) {
|
||||||
sizeEstimate += 4 + encoder.encode(string).length;
|
sizeEstimate += 4 + encoder.encode(string).length;
|
||||||
}
|
}
|
||||||
const buffer = CssFontInfo.write(cssFontInfo);
|
const buffer = compileCssFontInfo(cssFontInfo);
|
||||||
expect(buffer.byteLength).toEqual(sizeEstimate);
|
expect(buffer.byteLength).toEqual(sizeEstimate);
|
||||||
const deserialized = new CssFontInfo(buffer);
|
const deserialized = new CssFontInfo(buffer);
|
||||||
expect(deserialized.fontFamily).toEqual("Sample Family");
|
expect(deserialized.fontFamily).toEqual("Sample Family");
|
||||||
@ -102,7 +109,7 @@ describe("obj-bin-transform", function () {
|
|||||||
]) {
|
]) {
|
||||||
sizeEstimate += 4 + encoder.encode(string).length;
|
sizeEstimate += 4 + encoder.encode(string).length;
|
||||||
}
|
}
|
||||||
const buffer = SystemFontInfo.write(systemFontInfo);
|
const buffer = compileSystemFontInfo(systemFontInfo);
|
||||||
expect(buffer.byteLength).toEqual(sizeEstimate);
|
expect(buffer.byteLength).toEqual(sizeEstimate);
|
||||||
const deserialized = new SystemFontInfo(buffer);
|
const deserialized = new SystemFontInfo(buffer);
|
||||||
expect(deserialized.guessFallback).toEqual(false);
|
expect(deserialized.guessFallback).toEqual(false);
|
||||||
@ -124,7 +131,7 @@ describe("obj-bin-transform", function () {
|
|||||||
sizeEstimate += 4 + 4 * (4 + encoder.encode("string").length);
|
sizeEstimate += 4 + 4 * (4 + encoder.encode("string").length);
|
||||||
sizeEstimate += 4 + 4; // cssFontInfo and systemFontInfo
|
sizeEstimate += 4 + 4; // cssFontInfo and systemFontInfo
|
||||||
sizeEstimate += 4 + fontInfo.data.length;
|
sizeEstimate += 4 + fontInfo.data.length;
|
||||||
const buffer = FontInfo.write(fontInfo);
|
const buffer = compileFontInfo(fontInfo);
|
||||||
expect(buffer.byteLength).toEqual(sizeEstimate);
|
expect(buffer.byteLength).toEqual(sizeEstimate);
|
||||||
const deserialized = new FontInfo({ data: buffer });
|
const deserialized = new FontInfo({ data: buffer });
|
||||||
expect(deserialized.black).toEqual(true);
|
expect(deserialized.black).toEqual(true);
|
||||||
@ -156,7 +163,7 @@ describe("obj-bin-transform", function () {
|
|||||||
});
|
});
|
||||||
|
|
||||||
it("nesting should work as expected", function () {
|
it("nesting should work as expected", function () {
|
||||||
const buffer = FontInfo.write({
|
const buffer = compileFontInfo({
|
||||||
...fontInfo,
|
...fontInfo,
|
||||||
cssFontInfo,
|
cssFontInfo,
|
||||||
systemFontInfo,
|
systemFontInfo,
|
||||||
@ -231,7 +238,7 @@ describe("obj-bin-transform", function () {
|
|||||||
|
|
||||||
describe("Pattern serialization and deserialization", function () {
|
describe("Pattern serialization and deserialization", function () {
|
||||||
it("must serialize and deserialize axial gradients correctly", function () {
|
it("must serialize and deserialize axial gradients correctly", function () {
|
||||||
const buffer = PatternInfo.write(axialPatternIR);
|
const buffer = compilePatternInfo(axialPatternIR);
|
||||||
expect(buffer).toBeInstanceOf(ArrayBuffer);
|
expect(buffer).toBeInstanceOf(ArrayBuffer);
|
||||||
expect(buffer.byteLength).toBeGreaterThan(0);
|
expect(buffer.byteLength).toBeGreaterThan(0);
|
||||||
|
|
||||||
@ -253,7 +260,7 @@ describe("obj-bin-transform", function () {
|
|||||||
});
|
});
|
||||||
|
|
||||||
it("must serialize and deserialize radial gradients correctly", function () {
|
it("must serialize and deserialize radial gradients correctly", function () {
|
||||||
const buffer = PatternInfo.write(radialPatternIR);
|
const buffer = compilePatternInfo(radialPatternIR);
|
||||||
expect(buffer).toBeInstanceOf(ArrayBuffer);
|
expect(buffer).toBeInstanceOf(ArrayBuffer);
|
||||||
expect(buffer.byteLength).toBeGreaterThan(0);
|
expect(buffer.byteLength).toBeGreaterThan(0);
|
||||||
|
|
||||||
@ -276,7 +283,7 @@ describe("obj-bin-transform", function () {
|
|||||||
});
|
});
|
||||||
|
|
||||||
it("must serialize and deserialize mesh patterns with figures correctly", function () {
|
it("must serialize and deserialize mesh patterns with figures correctly", function () {
|
||||||
const buffer = PatternInfo.write(meshPatternIR);
|
const buffer = compilePatternInfo(meshPatternIR);
|
||||||
expect(buffer).toBeInstanceOf(ArrayBuffer);
|
expect(buffer).toBeInstanceOf(ArrayBuffer);
|
||||||
expect(buffer.byteLength).toBeGreaterThan(0);
|
expect(buffer.byteLength).toBeGreaterThan(0);
|
||||||
|
|
||||||
@ -335,7 +342,7 @@ describe("obj-bin-transform", function () {
|
|||||||
null,
|
null,
|
||||||
];
|
];
|
||||||
|
|
||||||
const buffer = PatternInfo.write(noFiguresIR);
|
const buffer = compilePatternInfo(noFiguresIR);
|
||||||
const patternInfo = new PatternInfo(buffer);
|
const patternInfo = new PatternInfo(buffer);
|
||||||
const reconstructedIR = patternInfo.getIR();
|
const reconstructedIR = patternInfo.getIR();
|
||||||
|
|
||||||
@ -344,7 +351,7 @@ describe("obj-bin-transform", function () {
|
|||||||
});
|
});
|
||||||
|
|
||||||
it("must preserve figure data integrity across serialization", function () {
|
it("must preserve figure data integrity across serialization", function () {
|
||||||
const buffer = PatternInfo.write(meshPatternIR);
|
const buffer = compilePatternInfo(meshPatternIR);
|
||||||
const patternInfo = new PatternInfo(buffer);
|
const patternInfo = new PatternInfo(buffer);
|
||||||
const reconstructedIR = patternInfo.getIR();
|
const reconstructedIR = patternInfo.getIR();
|
||||||
|
|
||||||
@ -362,9 +369,9 @@ describe("obj-bin-transform", function () {
|
|||||||
});
|
});
|
||||||
|
|
||||||
it("must calculate correct buffer sizes for different pattern types", function () {
|
it("must calculate correct buffer sizes for different pattern types", function () {
|
||||||
const axialBuffer = PatternInfo.write(axialPatternIR);
|
const axialBuffer = compilePatternInfo(axialPatternIR);
|
||||||
const radialBuffer = PatternInfo.write(radialPatternIR);
|
const radialBuffer = compilePatternInfo(radialPatternIR);
|
||||||
const meshBuffer = PatternInfo.write(meshPatternIR);
|
const meshBuffer = compilePatternInfo(meshPatternIR);
|
||||||
|
|
||||||
expect(axialBuffer.byteLength).toBeLessThan(radialBuffer.byteLength);
|
expect(axialBuffer.byteLength).toBeLessThan(radialBuffer.byteLength);
|
||||||
expect(meshBuffer.byteLength).toBeGreaterThan(axialBuffer.byteLength);
|
expect(meshBuffer.byteLength).toBeGreaterThan(axialBuffer.byteLength);
|
||||||
@ -394,7 +401,7 @@ describe("obj-bin-transform", function () {
|
|||||||
null,
|
null,
|
||||||
];
|
];
|
||||||
|
|
||||||
const buffer = PatternInfo.write(customFiguresIR);
|
const buffer = compilePatternInfo(customFiguresIR);
|
||||||
const patternInfo = new PatternInfo(buffer);
|
const patternInfo = new PatternInfo(buffer);
|
||||||
const reconstructedIR = patternInfo.getIR();
|
const reconstructedIR = patternInfo.getIR();
|
||||||
|
|
||||||
@ -415,7 +422,7 @@ describe("obj-bin-transform", function () {
|
|||||||
new Uint8Array([255, 128, 64]),
|
new Uint8Array([255, 128, 64]),
|
||||||
];
|
];
|
||||||
|
|
||||||
const buffer = PatternInfo.write(meshWithBgIR);
|
const buffer = compilePatternInfo(meshWithBgIR);
|
||||||
const patternInfo = new PatternInfo(buffer);
|
const patternInfo = new PatternInfo(buffer);
|
||||||
const reconstructedIR = patternInfo.getIR();
|
const reconstructedIR = patternInfo.getIR();
|
||||||
|
|
||||||
@ -432,7 +439,7 @@ describe("obj-bin-transform", function () {
|
|||||||
null,
|
null,
|
||||||
];
|
];
|
||||||
|
|
||||||
const buffer2 = PatternInfo.write(meshNoBgIR);
|
const buffer2 = compilePatternInfo(meshNoBgIR);
|
||||||
const patternInfo2 = new PatternInfo(buffer2);
|
const patternInfo2 = new PatternInfo(buffer2);
|
||||||
const reconstructedIR2 = patternInfo2.getIR();
|
const reconstructedIR2 = patternInfo2.getIR();
|
||||||
|
|
||||||
@ -451,7 +458,7 @@ describe("obj-bin-transform", function () {
|
|||||||
null,
|
null,
|
||||||
];
|
];
|
||||||
|
|
||||||
const buffer = PatternInfo.write(customMeshIR);
|
const buffer = compilePatternInfo(customMeshIR);
|
||||||
const patternInfo = new PatternInfo(buffer);
|
const patternInfo = new PatternInfo(buffer);
|
||||||
const reconstructedIR = patternInfo.getIR();
|
const reconstructedIR = patternInfo.getIR();
|
||||||
|
|
||||||
@ -477,7 +484,7 @@ describe("obj-bin-transform", function () {
|
|||||||
]);
|
]);
|
||||||
|
|
||||||
it("should create a FontPathInfo instance from an array of path commands", function () {
|
it("should create a FontPathInfo instance from an array of path commands", function () {
|
||||||
const buffer = FontPathInfo.write(path);
|
const buffer = compileFontPathInfo(path);
|
||||||
const fontPathInfo = new FontPathInfo(buffer);
|
const fontPathInfo = new FontPathInfo(buffer);
|
||||||
expect(fontPathInfo.path).toEqual(path);
|
expect(fontPathInfo.path).toEqual(path);
|
||||||
});
|
});
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user