Skip to content

Commit 75350d3

Browse files
committed
Serialize pattern data into ArrayBuffer
Follow up on #20197, This serializes pattern data into an ArrayBuffer which is then transferred from the worker to the main thread. It sets up the stage for us to eventually switch to a SharedArrayBuffer in the future.
1 parent f410432 commit 75350d3

File tree

6 files changed

+621
-5
lines changed

6 files changed

+621
-5
lines changed

src/core/evaluator.js

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,7 @@ import {
4040
lookupMatrix,
4141
lookupNormalRect,
4242
} from "./core_utils.js";
43+
import { FontInfo, PatternInfo } from "../shared/obj-bin-transform.js";
4344
import {
4445
getEncoding,
4546
MacRomanEncoding,
@@ -72,7 +73,6 @@ import { BaseStream } from "./base_stream.js";
7273
import { bidi } from "./bidi.js";
7374
import { ColorSpace } from "./colorspace.js";
7475
import { ColorSpaceUtils } from "./colorspace_utils.js";
75-
import { FontInfo } from "../shared/obj-bin-transform.js";
7676
import { getFontSubstitution } from "./font_substitutions.js";
7777
import { getGlyphsUnicode } from "./glyphlist.js";
7878
import { getMetrics } from "./metrics.js";
@@ -1517,7 +1517,10 @@ class PartialEvaluator {
15171517
localShadingPatternCache.set(shading, id);
15181518

15191519
if (this.parsingType3Font) {
1520-
this.handler.send("commonobj", [id, "Pattern", patternIR]);
1520+
const transfers = [];
1521+
const patternBuffer = PatternInfo.write(patternIR);
1522+
transfers.push(patternBuffer);
1523+
this.handler.send("commonobj", [id, "Pattern", patternBuffer], transfers);
15211524
} else {
15221525
this.handler.send("obj", [id, this.pageIndex, "Pattern", patternIR]);
15231526
}

src/display/api.js

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -45,6 +45,7 @@ import {
4545
StatTimer,
4646
} from "./display_utils.js";
4747
import { FontFaceObject, FontLoader } from "./font_loader.js";
48+
import { FontInfo, PatternInfo } from "../shared/obj-bin-transform.js";
4849
import {
4950
getDataProp,
5051
getFactoryUrlProp,
@@ -67,7 +68,6 @@ import { DOMCMapReaderFactory } from "display-cmap_reader_factory";
6768
import { DOMFilterFactory } from "./filter_factory.js";
6869
import { DOMStandardFontDataFactory } from "display-standard_fontdata_factory";
6970
import { DOMWasmFactory } from "display-wasm_factory";
70-
import { FontInfo } from "../shared/obj-bin-transform.js";
7171
import { GlobalWorkerOptions } from "./worker_options.js";
7272
import { Metadata } from "./metadata.js";
7373
import { OptionalContentConfig } from "./optional_content_config.js";
@@ -2804,9 +2804,12 @@ class WorkerTransport {
28042804
break;
28052805
case "FontPath":
28062806
case "Image":
2807-
case "Pattern":
28082807
this.commonObjs.resolve(id, exportedData);
28092808
break;
2809+
case "Pattern":
2810+
const pattern = new PatternInfo(exportedData);
2811+
this.commonObjs.resolve(id, pattern.getIR());
2812+
break;
28102813
default:
28112814
throw new Error(`Got unknown common object type ${type}`);
28122815
}

src/shared/obj-bin-transform.js

Lines changed: 288 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -606,4 +606,291 @@ class FontInfo {
606606
}
607607
}
608608

609-
export { CssFontInfo, FontInfo, SystemFontInfo };
609+
class PatternInfo {
610+
static #KIND = 0; // 1=axial, 2=radial, 3=mesh
611+
612+
static #HAS_BBOX = 1; // 0/1
613+
614+
static #HAS_BACKGROUND = 2; // 0/1 (background for mesh patterns)
615+
616+
static #SHADING_TYPE = 3; // shadingType (only for mesh patterns)
617+
618+
static #N_COORD = 4; // number of coordinate pairs
619+
620+
static #N_COLOR = 8; // number of rgb triplets
621+
622+
static #N_STOP = 12; // number of gradient stops
623+
624+
static #N_FIGURES = 16; // number of figures
625+
626+
constructor(buffer) {
627+
this.buffer = buffer;
628+
this.view = new DataView(buffer);
629+
this.data = new Uint8Array(buffer);
630+
}
631+
632+
static write(ir) {
633+
let kind,
634+
bbox = null,
635+
coords = [],
636+
colors = [],
637+
colorStops = [],
638+
figures = [],
639+
shadingType = null, // only needed for mesh patterns
640+
background = null; // background for mesh patterns
641+
642+
switch (ir[0]) {
643+
case "RadialAxial":
644+
kind = ir[1] === "axial" ? 1 : 2;
645+
bbox = ir[2];
646+
colorStops = ir[3];
647+
if (kind === 1) {
648+
coords.push(...ir[4], ...ir[5]);
649+
} else {
650+
coords.push(ir[4][0], ir[4][1], ir[6], ir[5][0], ir[5][1], ir[7]);
651+
}
652+
break;
653+
case "Mesh":
654+
kind = 3;
655+
shadingType = ir[1];
656+
coords = ir[2];
657+
colors = ir[3];
658+
figures = ir[4] || [];
659+
bbox = ir[6];
660+
background = ir[7];
661+
break;
662+
default:
663+
throw new Error(`Unsupported pattern type: ${ir[0]}`);
664+
}
665+
666+
const nCoord = Math.floor(coords.length / 2);
667+
const nColor = Math.floor(colors.length / 3);
668+
const nStop = colorStops.length;
669+
const nFigures = figures.length;
670+
671+
const encoder = new TextEncoder();
672+
const encodedFigureTypes = [];
673+
let figuresSize = 0;
674+
for (const figure of figures) {
675+
figuresSize += 4;
676+
const typeBytes = encoder.encode(figure.type);
677+
encodedFigureTypes.push(typeBytes);
678+
figuresSize += typeBytes.length;
679+
figuresSize = Math.ceil(figuresSize / 4) * 4; // Ensure 4-byte alignment
680+
figuresSize += 4 + figure.coords.length * 4;
681+
figuresSize += 4 + figure.colors.length * 4;
682+
if (figure.verticesPerRow !== undefined) {
683+
figuresSize += 4;
684+
}
685+
}
686+
687+
const byteLen =
688+
20 +
689+
nCoord * 8 +
690+
nColor * 3 +
691+
nStop * 8 +
692+
(bbox ? 16 : 0) +
693+
(background ? 3 : 0) +
694+
figuresSize;
695+
const buffer = new ArrayBuffer(byteLen);
696+
const dataView = new DataView(buffer);
697+
const u8data = new Uint8Array(buffer);
698+
699+
dataView.setUint8(PatternInfo.#KIND, kind);
700+
dataView.setUint8(PatternInfo.#HAS_BBOX, bbox ? 1 : 0);
701+
dataView.setUint8(PatternInfo.#HAS_BACKGROUND, background ? 1 : 0);
702+
dataView.setUint8(PatternInfo.#SHADING_TYPE, shadingType); // Only for mesh pattern, null otherwise
703+
dataView.setUint32(PatternInfo.#N_COORD, nCoord, true);
704+
dataView.setUint32(PatternInfo.#N_COLOR, nColor, true);
705+
dataView.setUint32(PatternInfo.#N_STOP, nStop, true);
706+
dataView.setUint32(PatternInfo.#N_FIGURES, nFigures, true);
707+
708+
let offset = 20;
709+
const coordsView = new Float32Array(buffer, offset, nCoord * 2);
710+
coordsView.set(coords);
711+
offset += nCoord * 8;
712+
713+
u8data.set(colors, offset);
714+
offset += nColor * 3;
715+
716+
for (const [pos, hex] of colorStops) {
717+
dataView.setFloat32(offset, pos, true);
718+
offset += 4;
719+
dataView.setUint32(offset, parseInt(hex.slice(1), 16), true);
720+
offset += 4;
721+
}
722+
if (bbox) {
723+
for (const v of bbox) {
724+
dataView.setFloat32(offset, v, true);
725+
offset += 4;
726+
}
727+
}
728+
729+
if (background) {
730+
u8data.set(background, offset);
731+
offset += 3;
732+
}
733+
734+
for (let i = 0; i < figures.length; i++) {
735+
const figure = figures[i];
736+
const typeBytes = encodedFigureTypes[i];
737+
dataView.setUint32(offset, typeBytes.length, true);
738+
offset += 4;
739+
u8data.set(typeBytes, offset);
740+
offset += typeBytes.length;
741+
// Ensure 4-byte alignment
742+
offset = Math.ceil(offset / 4) * 4;
743+
dataView.setUint32(offset, figure.coords.length, true);
744+
offset += 4;
745+
const figureCoordsView = new Int32Array(
746+
buffer,
747+
offset,
748+
figure.coords.length
749+
);
750+
figureCoordsView.set(figure.coords);
751+
offset += figure.coords.length * 4;
752+
dataView.setUint32(offset, figure.colors.length, true);
753+
offset += 4;
754+
const colorsView = new Int32Array(buffer, offset, figure.colors.length);
755+
colorsView.set(figure.colors);
756+
offset += figure.colors.length * 4;
757+
758+
if (figure.verticesPerRow !== undefined) {
759+
dataView.setUint32(offset, figure.verticesPerRow, true);
760+
offset += 4;
761+
}
762+
}
763+
return buffer;
764+
}
765+
766+
getIR() {
767+
const dataView = this.view;
768+
const kind = this.data[PatternInfo.#KIND];
769+
const hasBBox = !!this.data[PatternInfo.#HAS_BBOX];
770+
const hasBackground = !!this.data[PatternInfo.#HAS_BACKGROUND];
771+
const nCoord = dataView.getUint32(PatternInfo.#N_COORD, true);
772+
const nColor = dataView.getUint32(PatternInfo.#N_COLOR, true);
773+
const nStop = dataView.getUint32(PatternInfo.#N_STOP, true);
774+
const nFigures = dataView.getUint32(PatternInfo.#N_FIGURES, true);
775+
776+
let offset = 20;
777+
const coords = new Float32Array(this.buffer, offset, nCoord * 2);
778+
offset += nCoord * 8;
779+
const colors = new Uint8Array(this.buffer, offset, nColor * 3);
780+
offset += nColor * 3;
781+
const stops = [];
782+
for (let i = 0; i < nStop; ++i) {
783+
const p = dataView.getFloat32(offset, true);
784+
offset += 4;
785+
const rgb = dataView.getUint32(offset, true);
786+
offset += 4;
787+
stops.push([p, `#${rgb.toString(16).padStart(6, "0")}`]);
788+
}
789+
let bbox = null;
790+
if (hasBBox) {
791+
bbox = [];
792+
for (let i = 0; i < 4; ++i) {
793+
bbox.push(dataView.getFloat32(offset, true));
794+
offset += 4;
795+
}
796+
}
797+
798+
let background = null;
799+
if (hasBackground) {
800+
background = new Uint8Array(this.buffer, offset, 3);
801+
offset += 3;
802+
}
803+
804+
const figures = [];
805+
const decoder = new TextDecoder();
806+
for (let i = 0; i < nFigures; ++i) {
807+
const typeLength = dataView.getUint32(offset, true);
808+
offset += 4;
809+
const type = decoder.decode(
810+
new Uint8Array(this.buffer, offset, typeLength)
811+
);
812+
offset += typeLength;
813+
offset = Math.ceil(offset / 4) * 4;
814+
815+
const coordsLength = dataView.getUint32(offset, true);
816+
offset += 4;
817+
const figureCoords = new Int32Array(this.buffer, offset, coordsLength);
818+
offset += coordsLength * 4;
819+
820+
const colorsLength = dataView.getUint32(offset, true);
821+
offset += 4;
822+
const figureColors = new Int32Array(this.buffer, offset, colorsLength);
823+
offset += colorsLength * 4;
824+
825+
const figure = {
826+
type,
827+
coords: figureCoords,
828+
colors: figureColors,
829+
};
830+
831+
if (type === "lattice") {
832+
figure.verticesPerRow = dataView.getUint32(offset, true);
833+
offset += 4;
834+
}
835+
836+
figures.push(figure);
837+
}
838+
839+
if (kind === 1) {
840+
// axial
841+
return [
842+
"RadialAxial",
843+
"axial",
844+
bbox,
845+
stops,
846+
Array.from(coords.slice(0, 2)),
847+
Array.from(coords.slice(2, 4)),
848+
null,
849+
null,
850+
];
851+
}
852+
if (kind === 2) {
853+
return [
854+
"RadialAxial",
855+
"radial",
856+
bbox,
857+
stops,
858+
[coords[0], coords[1]],
859+
[coords[3], coords[4]],
860+
coords[2],
861+
coords[5],
862+
];
863+
}
864+
if (kind === 3) {
865+
const shadingType = this.data[PatternInfo.#SHADING_TYPE];
866+
let bounds = null;
867+
if (coords.length > 0) {
868+
let minX = coords[0],
869+
maxX = coords[0];
870+
let minY = coords[1],
871+
maxY = coords[1];
872+
for (let i = 0; i < coords.length; i += 2) {
873+
const x = coords[i],
874+
y = coords[i + 1];
875+
minX = minX > x ? x : minX;
876+
minY = minY > y ? y : minY;
877+
maxX = maxX < x ? x : maxX;
878+
maxY = maxY < y ? y : maxY;
879+
}
880+
bounds = [minX, minY, maxX, maxY];
881+
}
882+
return [
883+
"Mesh",
884+
shadingType,
885+
coords,
886+
colors,
887+
figures,
888+
bounds,
889+
bbox,
890+
background,
891+
];
892+
}
893+
throw new Error(`Unsupported pattern kind: ${kind}`);
894+
}
895+
}
896+
export { CssFontInfo, FontInfo, PatternInfo, SystemFontInfo };

test/unit/clitests.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,7 @@
3434
"network_utils_spec.js",
3535
"node_stream_spec.js",
3636
"parser_spec.js",
37+
"pattern_info_spec.js",
3738
"pdf.image_decoders_spec.js",
3839
"pdf.worker_spec.js",
3940
"pdf_find_controller_spec.js",

test/unit/jasmine-boot.js

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -75,6 +75,7 @@ async function initializePDFJS(callback) {
7575
"pdfjs-test/unit/metadata_spec.js",
7676
"pdfjs-test/unit/murmurhash3_spec.js",
7777
"pdfjs-test/unit/network_spec.js",
78+
"pdfjs-test/unit/pattern_info_spec.js",
7879
"pdfjs-test/unit/network_utils_spec.js",
7980
"pdfjs-test/unit/parser_spec.js",
8081
"pdfjs-test/unit/pdf.image_decoders_spec.js",

0 commit comments

Comments
 (0)