mirror of
https://github.com/mozilla/pdf.js.git
synced 2026-04-14 17:24:04 +02:00
Merge pull request #21012 from calixteman/shading_function
Add support for function-based shadings (bug 1254066)
This commit is contained in:
commit
f33f816991
@ -294,7 +294,7 @@ function compilePatternInfo(ir) {
|
||||
}
|
||||
|
||||
const nCoord = Math.floor(coords.length / 2);
|
||||
const nColor = Math.floor(colors.length / 3);
|
||||
const nColor = Math.floor(colors.length / 4);
|
||||
const nStop = colorStops.length;
|
||||
const nFigures = figures.length;
|
||||
|
||||
@ -312,7 +312,7 @@ function compilePatternInfo(ir) {
|
||||
const byteLen =
|
||||
20 +
|
||||
nCoord * 8 +
|
||||
nColor * 3 +
|
||||
nColor * 4 +
|
||||
nStop * 8 +
|
||||
(bbox ? 16 : 0) +
|
||||
(background ? 3 : 0) +
|
||||
@ -336,7 +336,7 @@ function compilePatternInfo(ir) {
|
||||
offset += nCoord * 8;
|
||||
|
||||
u8data.set(colors, offset);
|
||||
offset += nColor * 3;
|
||||
offset += nColor * 4;
|
||||
|
||||
for (const [pos, hex] of colorStops) {
|
||||
dataView.setFloat32(offset, pos, true);
|
||||
|
||||
@ -29,6 +29,7 @@ import {
|
||||
isNumberArray,
|
||||
lookupMatrix,
|
||||
lookupNormalRect,
|
||||
lookupRect,
|
||||
MissingDataException,
|
||||
} from "./core_utils.js";
|
||||
import { BaseStream } from "./base_stream.js";
|
||||
@ -63,6 +64,16 @@ class Pattern {
|
||||
|
||||
try {
|
||||
switch (type) {
|
||||
case ShadingType.FUNCTION_BASED:
|
||||
prepareWebGPU?.();
|
||||
return new FunctionBasedShading(
|
||||
dict,
|
||||
xref,
|
||||
res,
|
||||
pdfFunctionFactory,
|
||||
globalColorSpaceCache,
|
||||
localColorSpaceCache
|
||||
);
|
||||
case ShadingType.AXIAL:
|
||||
case ShadingType.RADIAL:
|
||||
return new RadialAxialShading(
|
||||
@ -312,6 +323,183 @@ class RadialAxialShading extends BaseShading {
|
||||
}
|
||||
}
|
||||
|
||||
// Helpers for MeshShading, which builds its mesh from a stream.
|
||||
function meshUpdateBounds(self) {
|
||||
let minX = self.coords[0][0],
|
||||
minY = self.coords[0][1],
|
||||
maxX = minX,
|
||||
maxY = minY;
|
||||
for (let i = 1, ii = self.coords.length; i < ii; i++) {
|
||||
const x = self.coords[i][0],
|
||||
y = self.coords[i][1];
|
||||
minX = minX > x ? x : minX;
|
||||
minY = minY > y ? y : minY;
|
||||
maxX = maxX < x ? x : maxX;
|
||||
maxY = maxY < y ? y : maxY;
|
||||
}
|
||||
self.bounds = [minX, minY, maxX, maxY];
|
||||
}
|
||||
|
||||
function meshPackData(self) {
|
||||
let i, j, ii;
|
||||
|
||||
const coords = self.coords;
|
||||
const coordsPacked = new Float32Array(coords.length * 2);
|
||||
for (i = 0, j = 0, ii = coords.length; i < ii; i++) {
|
||||
const xy = coords[i];
|
||||
coordsPacked[j++] = xy[0];
|
||||
coordsPacked[j++] = xy[1];
|
||||
}
|
||||
self.coords = coordsPacked;
|
||||
|
||||
// Stride 4 (RGB + 1 padding byte) so each color fits in one u32, letting
|
||||
// the WebGPU vertex shader read colors as array<u32> without repacking.
|
||||
const colors = self.colors;
|
||||
const colorsPacked = new Uint8Array(colors.length * 4);
|
||||
for (i = 0, j = 0, ii = colors.length; i < ii; i++) {
|
||||
const c = colors[i];
|
||||
colorsPacked[j++] = c[0];
|
||||
colorsPacked[j++] = c[1];
|
||||
colorsPacked[j++] = c[2];
|
||||
j++; // alpha — unused, stays 0
|
||||
}
|
||||
self.colors = colorsPacked;
|
||||
|
||||
// Store raw vertex indices (not byte offsets) so the GPU shader can
|
||||
// address coords / colors without knowing their strides, and so the
|
||||
// arrays are transferable Uint32Arrays.
|
||||
for (const figure of self.figures) {
|
||||
figure.coords = new Uint32Array(figure.coords);
|
||||
figure.colors = new Uint32Array(figure.colors);
|
||||
}
|
||||
}
|
||||
|
||||
// Type 1 shading: a 2-in, n-out function sampled over a rectangular domain.
|
||||
class FunctionBasedShading extends BaseShading {
|
||||
// Maximum grid steps per axis to avoid huge meshes.
|
||||
static MAX_STEP_COUNT = 512;
|
||||
|
||||
constructor(
|
||||
dict,
|
||||
xref,
|
||||
resources,
|
||||
pdfFunctionFactory,
|
||||
globalColorSpaceCache,
|
||||
localColorSpaceCache
|
||||
) {
|
||||
super();
|
||||
this.bbox = lookupNormalRect(dict.getArray("BBox"), null);
|
||||
|
||||
const cs = ColorSpaceUtils.parse({
|
||||
cs: dict.getRaw("CS") || dict.getRaw("ColorSpace"),
|
||||
xref,
|
||||
resources,
|
||||
pdfFunctionFactory,
|
||||
globalColorSpaceCache,
|
||||
localColorSpaceCache,
|
||||
});
|
||||
this.background = dict.has("Background")
|
||||
? cs.getRgb(dict.get("Background"), 0)
|
||||
: null;
|
||||
|
||||
const fnObj = dict.getRaw("Function");
|
||||
if (!fnObj) {
|
||||
throw new FormatError("FunctionBasedShading: missing /Function");
|
||||
}
|
||||
const fn = pdfFunctionFactory.create(fnObj, /* parseArray = */ true);
|
||||
|
||||
// Domain [x0, x1, y0, y1]; defaults to [0, 1, 0, 1].
|
||||
let x0 = 0,
|
||||
x1 = 1,
|
||||
y0 = 0,
|
||||
y1 = 1;
|
||||
const domainArr = lookupRect(dict.getArray("Domain"), null);
|
||||
if (domainArr) {
|
||||
[x0, x1, y0, y1] = domainArr;
|
||||
}
|
||||
|
||||
// Matrix maps shading (domain) space to user space; defaults to identity.
|
||||
const matrix = lookupMatrix(dict.getArray("Matrix"), IDENTITY_MATRIX);
|
||||
|
||||
// Transform the four domain corners to find the user-space bounding box.
|
||||
this.bounds = [Infinity, Infinity, -Infinity, -Infinity];
|
||||
Util.axialAlignedBoundingBox([x0, y0, x1, y1], matrix, this.bounds);
|
||||
|
||||
const bboxW = this.bounds[2] - this.bounds[0];
|
||||
const bboxH = this.bounds[3] - this.bounds[1];
|
||||
|
||||
// 1 step per user-space unit, capped for performance.
|
||||
const stepsX = MathClamp(
|
||||
Math.ceil(bboxW),
|
||||
1,
|
||||
FunctionBasedShading.MAX_STEP_COUNT
|
||||
);
|
||||
const stepsY = MathClamp(
|
||||
Math.ceil(bboxH),
|
||||
1,
|
||||
FunctionBasedShading.MAX_STEP_COUNT
|
||||
);
|
||||
|
||||
const verticesPerRow = stepsX + 1;
|
||||
const totalVertices = (stepsY + 1) * verticesPerRow;
|
||||
const coords = (this.coords = new Float32Array(totalVertices * 2));
|
||||
const colors = (this.colors = new Uint8ClampedArray(totalVertices * 4));
|
||||
|
||||
const xyBuf = new Float32Array(2);
|
||||
const colorBuf = new Float32Array(cs.numComps);
|
||||
const rangeX = (x1 - x0) / stepsX;
|
||||
const rangeY = (y1 - y0) / stepsY;
|
||||
const halfStepX = rangeX / 2;
|
||||
const halfStepY = rangeY / 2;
|
||||
let coordOffset = 0;
|
||||
let colorOffset = 0;
|
||||
for (let row = 0; row <= stepsY; row++) {
|
||||
const yDomain = y0 + rangeY * row;
|
||||
// Evaluate half a step inside at boundary vertices to avoid a spurious
|
||||
// strip for discontinuous functions; vertex positions stay unchanged.
|
||||
xyBuf[1] = row === stepsY ? yDomain - halfStepY : yDomain;
|
||||
for (let col = 0; col <= stepsX; col++) {
|
||||
const xDomain = x0 + rangeX * col;
|
||||
xyBuf[0] = col === stepsX ? xDomain - halfStepX : xDomain;
|
||||
fn(xyBuf, 0, colorBuf, 0);
|
||||
coords[coordOffset] = xDomain;
|
||||
coords[coordOffset + 1] = yDomain;
|
||||
Util.applyTransform(coords, matrix, coordOffset);
|
||||
coordOffset += 2;
|
||||
|
||||
cs.getRgbItem(colorBuf, 0, colors, colorOffset);
|
||||
colorOffset += 4; // alpha — unused, stays 0
|
||||
}
|
||||
}
|
||||
|
||||
const ps = new Uint32Array(totalVertices);
|
||||
for (let i = 0; i < totalVertices; i++) {
|
||||
ps[i] = i;
|
||||
}
|
||||
this.figures = [
|
||||
{
|
||||
type: MeshFigureType.LATTICE,
|
||||
coords: ps,
|
||||
colors: new Uint32Array(ps),
|
||||
verticesPerRow,
|
||||
},
|
||||
];
|
||||
}
|
||||
|
||||
getIR() {
|
||||
return [
|
||||
"Mesh",
|
||||
ShadingType.FUNCTION_BASED,
|
||||
this.coords,
|
||||
this.colors,
|
||||
this.figures,
|
||||
this.bounds,
|
||||
this.bbox,
|
||||
this.background,
|
||||
];
|
||||
}
|
||||
}
|
||||
|
||||
// All mesh shadings. For now, they will be presented as set of the triangles
|
||||
// to be drawn on the canvas and rgb color for each vertex.
|
||||
class MeshStreamReader {
|
||||
@ -920,55 +1108,11 @@ class MeshShading extends BaseShading {
|
||||
}
|
||||
|
||||
_updateBounds() {
|
||||
let minX = this.coords[0][0],
|
||||
minY = this.coords[0][1],
|
||||
maxX = minX,
|
||||
maxY = minY;
|
||||
for (let i = 1, ii = this.coords.length; i < ii; i++) {
|
||||
const x = this.coords[i][0],
|
||||
y = this.coords[i][1];
|
||||
minX = minX > x ? x : minX;
|
||||
minY = minY > y ? y : minY;
|
||||
maxX = maxX < x ? x : maxX;
|
||||
maxY = maxY < y ? y : maxY;
|
||||
}
|
||||
this.bounds = [minX, minY, maxX, maxY];
|
||||
meshUpdateBounds(this);
|
||||
}
|
||||
|
||||
_packData() {
|
||||
let i, ii, j;
|
||||
|
||||
const coords = this.coords;
|
||||
const coordsPacked = new Float32Array(coords.length * 2);
|
||||
for (i = 0, j = 0, ii = coords.length; i < ii; i++) {
|
||||
const xy = coords[i];
|
||||
coordsPacked[j++] = xy[0];
|
||||
coordsPacked[j++] = xy[1];
|
||||
}
|
||||
this.coords = coordsPacked;
|
||||
|
||||
// Stride 4 (RGBA layout, alpha unused) so the buffer maps directly to
|
||||
// array<u32> in the WebGPU vertex shader without any repacking.
|
||||
const colors = this.colors;
|
||||
const colorsPacked = new Uint8Array(colors.length * 4);
|
||||
for (i = 0, j = 0, ii = colors.length; i < ii; i++) {
|
||||
const c = colors[i];
|
||||
colorsPacked[j++] = c[0];
|
||||
colorsPacked[j++] = c[1];
|
||||
colorsPacked[j++] = c[2];
|
||||
j++; // alpha — unused, stays 0
|
||||
}
|
||||
this.colors = colorsPacked;
|
||||
|
||||
// Store raw vertex indices (not byte offsets) so the GPU shader can
|
||||
// address coords / colors without knowing their strides, and so the
|
||||
// arrays are transferable Uint32Arrays.
|
||||
const figures = this.figures;
|
||||
for (i = 0, ii = figures.length; i < ii; i++) {
|
||||
const figure = figures[i];
|
||||
figure.coords = new Uint32Array(figure.coords);
|
||||
figure.colors = new Uint32Array(figure.colors);
|
||||
}
|
||||
meshPackData(this);
|
||||
}
|
||||
|
||||
getIR() {
|
||||
|
||||
@ -353,8 +353,8 @@ class PatternInfo {
|
||||
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 colors = new Uint8Array(this.buffer, offset, nColor * 4);
|
||||
offset += nColor * 4;
|
||||
const stops = [];
|
||||
for (let i = 0; i < nStop; ++i) {
|
||||
const p = dataView.getFloat32(offset, true);
|
||||
|
||||
@ -61,7 +61,7 @@ class PATTERN_INFO {
|
||||
|
||||
static N_COORD = 4; // number of coordinate pairs
|
||||
|
||||
static N_COLOR = 8; // number of rgb triplets
|
||||
static N_COLOR = 8; // number of RGBA-stride color entries
|
||||
|
||||
static N_STOP = 12; // number of gradient stops
|
||||
|
||||
|
||||
1
test/pdfs/.gitignore
vendored
1
test/pdfs/.gitignore
vendored
@ -895,3 +895,4 @@
|
||||
!bug2025674.pdf
|
||||
!bug2026037.pdf
|
||||
!tiling_patterns_variations.pdf
|
||||
!function_based_shading.pdf
|
||||
|
||||
315
test/pdfs/function_based_shading.pdf
Normal file
315
test/pdfs/function_based_shading.pdf
Normal file
@ -0,0 +1,315 @@
|
||||
%PDF-1.4
|
||||
%âãÏÓ
|
||||
14 0 obj
|
||||
<<
|
||||
/FunctionType 4
|
||||
/Domain [0 1 0 1]
|
||||
/Range [0 1]
|
||||
/Length 7
|
||||
>>
|
||||
stream
|
||||
{ pop }
|
||||
endstream
|
||||
endobj
|
||||
|
||||
15 0 obj
|
||||
<<
|
||||
/FunctionType 4
|
||||
/Domain [0 1 0 1]
|
||||
/Range [0 1]
|
||||
/Length 13
|
||||
>>
|
||||
stream
|
||||
{ add 2 div }
|
||||
endstream
|
||||
endobj
|
||||
|
||||
16 0 obj
|
||||
<<
|
||||
/FunctionType 4
|
||||
/Domain [0 1 0 1]
|
||||
/Range [0 1 0 1 0 1]
|
||||
/Length 5
|
||||
>>
|
||||
stream
|
||||
{ 0 }
|
||||
endstream
|
||||
endobj
|
||||
|
||||
17 0 obj
|
||||
<<
|
||||
/FunctionType 4
|
||||
/Domain [0 1 0 1]
|
||||
/Range [0 1]
|
||||
/Length 65
|
||||
>>
|
||||
stream
|
||||
{ 0.5 sub exch 0.5 sub dup mul exch dup mul add sqrt 2 sqrt mul }
|
||||
endstream
|
||||
endobj
|
||||
|
||||
18 0 obj
|
||||
<<
|
||||
/FunctionType 4
|
||||
/Domain [0 100 0 100]
|
||||
/Range [0 1]
|
||||
/Length 15
|
||||
>>
|
||||
stream
|
||||
{ pop 100 div }
|
||||
endstream
|
||||
endobj
|
||||
|
||||
19 0 obj
|
||||
<<
|
||||
/FunctionType 4
|
||||
/Domain [0 1 0 1]
|
||||
/Range [0 1 0 1 0 1]
|
||||
/Length 14
|
||||
>>
|
||||
stream
|
||||
{ 2 copy mul }
|
||||
endstream
|
||||
endobj
|
||||
|
||||
20 0 obj
|
||||
<<
|
||||
/FunctionType 4
|
||||
/Domain [0 1 0 1]
|
||||
/Range [0 1]
|
||||
/Length 42
|
||||
>>
|
||||
stream
|
||||
{ 4 mul floor exch 4 mul floor add 2 mod }
|
||||
endstream
|
||||
endobj
|
||||
|
||||
21 0 obj
|
||||
<<
|
||||
/FunctionType 4
|
||||
/Domain [0 1 0 1]
|
||||
/Range [0 1]
|
||||
/Length 58
|
||||
>>
|
||||
stream
|
||||
{ dup mul exch dup mul add sqrt 1440 mul sin 1 add 2 div }
|
||||
endstream
|
||||
endobj
|
||||
|
||||
22 0 obj
|
||||
<<
|
||||
/FunctionType 4
|
||||
/Domain [0 1 0 1]
|
||||
/Range [0 1 0 1 0 1]
|
||||
/Length 101
|
||||
>>
|
||||
stream
|
||||
{ 2 copy add 90 mul sin dup mul 3 1 roll 180 mul sin dup mul exch 180 mul sin dup mul 3 1 roll exch }
|
||||
endstream
|
||||
endobj
|
||||
|
||||
5 0 obj
|
||||
<<
|
||||
/ShadingType 1
|
||||
/ColorSpace /DeviceGray
|
||||
/Domain [0 1 0 1]
|
||||
/Matrix [170 0 0 170 30 582]
|
||||
/Function 14 0 R
|
||||
>>
|
||||
endobj
|
||||
|
||||
6 0 obj
|
||||
<<
|
||||
/ShadingType 1
|
||||
/ColorSpace /DeviceGray
|
||||
/Domain [0 1 0 1]
|
||||
/Matrix [170 0 0 170 230 582]
|
||||
/Function 15 0 R
|
||||
>>
|
||||
endobj
|
||||
|
||||
7 0 obj
|
||||
<<
|
||||
/ShadingType 1
|
||||
/ColorSpace /DeviceRGB
|
||||
/Domain [0 1 0 1]
|
||||
/Matrix [170 0 0 170 430 582]
|
||||
/Function 16 0 R
|
||||
>>
|
||||
endobj
|
||||
|
||||
8 0 obj
|
||||
<<
|
||||
/ShadingType 1
|
||||
/ColorSpace /DeviceGray
|
||||
/Domain [0 1 0 1]
|
||||
/Matrix [170 0 0 170 30 382]
|
||||
/BBox [30 382 200 552]
|
||||
/Function 17 0 R
|
||||
>>
|
||||
endobj
|
||||
|
||||
9 0 obj
|
||||
<<
|
||||
/ShadingType 1
|
||||
/ColorSpace /DeviceGray
|
||||
/Domain [0 100 0 100]
|
||||
/Matrix [1.7 0 0 1.7 230 382]
|
||||
/Function 18 0 R
|
||||
>>
|
||||
endobj
|
||||
|
||||
10 0 obj
|
||||
<<
|
||||
/ShadingType 1
|
||||
/ColorSpace /DeviceRGB
|
||||
/Domain [0 1 0 1]
|
||||
/Matrix [85 85 -85 85 515 382]
|
||||
/Function 19 0 R
|
||||
>>
|
||||
endobj
|
||||
|
||||
11 0 obj
|
||||
<<
|
||||
/ShadingType 1
|
||||
/ColorSpace /DeviceGray
|
||||
/Domain [0 1 0 1]
|
||||
/Matrix [170 0 0 170 30 182]
|
||||
/Function 20 0 R
|
||||
>>
|
||||
endobj
|
||||
|
||||
12 0 obj
|
||||
<<
|
||||
/ShadingType 1
|
||||
/ColorSpace /DeviceGray
|
||||
/Domain [0 1 0 1]
|
||||
/Matrix [170 0 0 170 230 182]
|
||||
/Function 21 0 R
|
||||
>>
|
||||
endobj
|
||||
|
||||
13 0 obj
|
||||
<<
|
||||
/ShadingType 1
|
||||
/ColorSpace /DeviceRGB
|
||||
/Domain [0 1 0 1]
|
||||
/Matrix [170 0 0 170 430 182]
|
||||
/Function 22 0 R
|
||||
>>
|
||||
endobj
|
||||
|
||||
4 0 obj
|
||||
<<
|
||||
/Length 312
|
||||
>>
|
||||
stream
|
||||
q
|
||||
30 582 170 170 re W n
|
||||
/SH1 sh
|
||||
Q
|
||||
q
|
||||
230 582 170 170 re W n
|
||||
/SH2 sh
|
||||
Q
|
||||
q
|
||||
430 582 170 170 re W n
|
||||
/SH3 sh
|
||||
Q
|
||||
q
|
||||
30 382 170 170 re W n
|
||||
/SH4 sh
|
||||
Q
|
||||
q
|
||||
230 382 170 170 re W n
|
||||
/SH5 sh
|
||||
Q
|
||||
q
|
||||
430 382 170 170 re W n
|
||||
/SH6 sh
|
||||
Q
|
||||
q
|
||||
30 182 170 170 re W n
|
||||
/SH7 sh
|
||||
Q
|
||||
q
|
||||
230 182 170 170 re W n
|
||||
/SH8 sh
|
||||
Q
|
||||
q
|
||||
430 182 170 170 re W n
|
||||
/SH9 sh
|
||||
Q
|
||||
endstream
|
||||
endobj
|
||||
|
||||
3 0 obj
|
||||
<<
|
||||
/Type /Page
|
||||
/Parent 2 0 R
|
||||
/MediaBox [0 0 612 792]
|
||||
/Contents 4 0 R
|
||||
/Resources <<
|
||||
/Shading <<
|
||||
/SH1 5 0 R
|
||||
/SH2 6 0 R
|
||||
/SH3 7 0 R
|
||||
/SH4 8 0 R
|
||||
/SH5 9 0 R
|
||||
/SH6 10 0 R
|
||||
/SH7 11 0 R
|
||||
/SH8 12 0 R
|
||||
/SH9 13 0 R
|
||||
>>
|
||||
>>
|
||||
>>
|
||||
endobj
|
||||
|
||||
2 0 obj
|
||||
<<
|
||||
/Type /Pages
|
||||
/Kids [3 0 R]
|
||||
/Count 1
|
||||
>>
|
||||
endobj
|
||||
|
||||
1 0 obj
|
||||
<<
|
||||
/Type /Catalog
|
||||
/Pages 2 0 R
|
||||
>>
|
||||
endobj
|
||||
|
||||
xref
|
||||
0 23
|
||||
0000000000 65535 f
|
||||
0000003285 00000 n
|
||||
0000003221 00000 n
|
||||
0000002942 00000 n
|
||||
0000002577 00000 n
|
||||
0000001325 00000 n
|
||||
0000001460 00000 n
|
||||
0000001596 00000 n
|
||||
0000001731 00000 n
|
||||
0000001891 00000 n
|
||||
0000002031 00000 n
|
||||
0000002168 00000 n
|
||||
0000002304 00000 n
|
||||
0000002441 00000 n
|
||||
0000000015 00000 n
|
||||
0000000128 00000 n
|
||||
0000000248 00000 n
|
||||
0000000367 00000 n
|
||||
0000000539 00000 n
|
||||
0000000665 00000 n
|
||||
0000000794 00000 n
|
||||
0000000943 00000 n
|
||||
0000001108 00000 n
|
||||
trailer
|
||||
<<
|
||||
/Size 23
|
||||
/Root 1 0 R
|
||||
>>
|
||||
startxref
|
||||
3339
|
||||
%%EOF
|
||||
@ -14035,5 +14035,12 @@
|
||||
"md5": "2870c3136be00ddd975149b2c7d1e6df",
|
||||
"rounds": 1,
|
||||
"type": "eq"
|
||||
},
|
||||
{
|
||||
"id": "function-based-shading",
|
||||
"file": "pdfs/function_based_shading.pdf",
|
||||
"md5": "7796f0131e7d6428c1bf24a73ff13f95",
|
||||
"rounds": 1,
|
||||
"type": "eq"
|
||||
}
|
||||
]
|
||||
|
||||
@ -36,6 +36,7 @@
|
||||
"node_stream_spec.js",
|
||||
"obj_bin_transform_spec.js",
|
||||
"parser_spec.js",
|
||||
"pattern_spec.js",
|
||||
"pdf.image_decoders_spec.js",
|
||||
"pdf.worker_spec.js",
|
||||
"pdf_find_controller_spec.js",
|
||||
|
||||
@ -79,6 +79,7 @@ async function initializePDFJS(callback) {
|
||||
"pdfjs-test/unit/network_utils_spec.js",
|
||||
"pdfjs-test/unit/obj_bin_transform_spec.js",
|
||||
"pdfjs-test/unit/parser_spec.js",
|
||||
"pdfjs-test/unit/pattern_spec.js",
|
||||
"pdfjs-test/unit/pdf.image_decoders_spec.js",
|
||||
"pdfjs-test/unit/pdf.worker_spec.js",
|
||||
"pdfjs-test/unit/pdf_find_controller_spec.js",
|
||||
|
||||
@ -215,8 +215,8 @@ describe("obj_bin_transform", function () {
|
||||
0, 0, 50, 0, 100, 0, 0, 50, 50, 50, 100, 50, 0, 100, 50, 100, 100, 100,
|
||||
]),
|
||||
new Uint8Array([
|
||||
255, 0, 0, 0, 255, 0, 0, 0, 255, 255, 255, 0, 128, 128, 128, 255, 0,
|
||||
255, 0, 255, 255, 255, 128, 0, 128, 0, 128,
|
||||
255, 0, 0, 0, 0, 255, 0, 0, 0, 0, 255, 0, 255, 255, 0, 0, 128, 128, 128,
|
||||
0, 255, 0, 255, 0, 0, 255, 255, 0, 255, 128, 0, 0, 128, 0, 128, 0,
|
||||
]),
|
||||
[
|
||||
{
|
||||
@ -335,7 +335,7 @@ describe("obj_bin_transform", function () {
|
||||
"Mesh",
|
||||
4,
|
||||
new Float32Array([0, 0, 10, 10]),
|
||||
new Uint8Array([255, 0, 0]),
|
||||
new Uint8Array([255, 0, 0, 0]),
|
||||
[],
|
||||
[0, 0, 10, 10],
|
||||
[0, 0, 10, 10],
|
||||
@ -383,7 +383,7 @@ describe("obj_bin_transform", function () {
|
||||
"Mesh",
|
||||
6,
|
||||
new Float32Array([0, 0, 10, 10]),
|
||||
new Uint8Array([255, 128, 64]),
|
||||
new Uint8Array([255, 128, 64, 0]),
|
||||
[
|
||||
{
|
||||
type: MeshFigureType.PATCH,
|
||||
@ -415,7 +415,7 @@ describe("obj_bin_transform", function () {
|
||||
"Mesh",
|
||||
4,
|
||||
new Float32Array([0, 0, 10, 10]),
|
||||
new Uint8Array([255, 0, 0]),
|
||||
new Uint8Array([255, 0, 0, 0]),
|
||||
[],
|
||||
[0, 0, 10, 10],
|
||||
[0, 0, 10, 10],
|
||||
@ -432,7 +432,7 @@ describe("obj_bin_transform", function () {
|
||||
"Mesh",
|
||||
5,
|
||||
new Float32Array([0, 0, 5, 5]),
|
||||
new Uint8Array([0, 255, 0]),
|
||||
new Uint8Array([0, 255, 0, 0]),
|
||||
[],
|
||||
[0, 0, 5, 5],
|
||||
null,
|
||||
@ -451,7 +451,7 @@ describe("obj_bin_transform", function () {
|
||||
"Mesh",
|
||||
4,
|
||||
new Float32Array([-10, -5, 20, 15, 0, 30]),
|
||||
new Uint8Array([255, 0, 0, 0, 255, 0, 0, 0, 255]),
|
||||
new Uint8Array([255, 0, 0, 0, 0, 255, 0, 0, 0, 0, 255, 0]),
|
||||
[],
|
||||
null,
|
||||
null,
|
||||
|
||||
141
test/unit/pattern_spec.js
Normal file
141
test/unit/pattern_spec.js
Normal file
@ -0,0 +1,141 @@
|
||||
/* Copyright 2026 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 { Dict, Name } from "../../src/core/primitives.js";
|
||||
import {
|
||||
GlobalColorSpaceCache,
|
||||
LocalColorSpaceCache,
|
||||
} from "../../src/core/image_utils.js";
|
||||
import { compilePatternInfo } from "../../src/core/obj_bin_transform_core.js";
|
||||
import { Pattern } from "../../src/core/pattern.js";
|
||||
import { PatternInfo } from "../../src/display/obj_bin_transform_display.js";
|
||||
|
||||
describe("pattern", function () {
|
||||
describe("FunctionBasedShading", function () {
|
||||
function createFunctionBasedShading({
|
||||
colorSpace = "DeviceRGB",
|
||||
domain = [0, 1, 0, 1],
|
||||
matrix = [2, 0, 0, 3, 10, 20],
|
||||
background = null,
|
||||
fn = (src, srcOffset, dest, destOffset) => {
|
||||
dest[destOffset] = src[srcOffset];
|
||||
dest[destOffset + 1] = src[srcOffset + 1];
|
||||
dest[destOffset + 2] = 0;
|
||||
},
|
||||
} = {}) {
|
||||
const dict = new Dict();
|
||||
dict.set("ShadingType", 1);
|
||||
dict.set("ColorSpace", Name.get(colorSpace));
|
||||
dict.set("Domain", domain);
|
||||
dict.set("Matrix", matrix);
|
||||
if (background) {
|
||||
dict.set("Background", background);
|
||||
}
|
||||
dict.set("Function", {
|
||||
fn,
|
||||
});
|
||||
|
||||
const pdfFunctionFactory = {
|
||||
create(fnObj) {
|
||||
return fnObj.fn;
|
||||
},
|
||||
};
|
||||
const xref = {
|
||||
fetchIfRef(obj) {
|
||||
return obj;
|
||||
},
|
||||
};
|
||||
|
||||
return Pattern.parseShading(
|
||||
dict,
|
||||
xref,
|
||||
/* res = */ null,
|
||||
pdfFunctionFactory,
|
||||
new GlobalColorSpaceCache(),
|
||||
new LocalColorSpaceCache()
|
||||
);
|
||||
}
|
||||
|
||||
it("must convert Type 1 shading into packed mesh IR", function () {
|
||||
const shading = createFunctionBasedShading();
|
||||
const ir = shading.getIR();
|
||||
|
||||
expect(ir[0]).toEqual("Mesh");
|
||||
expect(ir[1]).toEqual(1);
|
||||
expect(ir[2]).toBeInstanceOf(Float32Array);
|
||||
expect(ir[2].length).toEqual(24);
|
||||
expect(Array.from(ir[2].slice(0, 6))).toEqual([10, 20, 11, 20, 12, 20]);
|
||||
expect(Array.from(ir[2].slice(-6))).toEqual([10, 23, 11, 23, 12, 23]);
|
||||
|
||||
expect(ir[3]).toBeInstanceOf(Uint8ClampedArray);
|
||||
expect(ir[3].length).toEqual(48);
|
||||
expect(Array.from(ir[3].slice(0, 12))).toEqual([
|
||||
0, 0, 0, 0, 128, 0, 0, 0, 191, 0, 0, 0,
|
||||
]);
|
||||
expect(Array.from(ir[3].slice(-12))).toEqual([
|
||||
0, 212, 0, 0, 128, 212, 0, 0, 191, 212, 0, 0,
|
||||
]);
|
||||
|
||||
expect(ir[4]).toEqual([
|
||||
jasmine.objectContaining({
|
||||
verticesPerRow: 3,
|
||||
}),
|
||||
]);
|
||||
expect(ir[4][0].coords).toBeInstanceOf(Uint32Array);
|
||||
expect(Array.from(ir[4][0].coords)).toEqual([
|
||||
0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11,
|
||||
]);
|
||||
expect(ir[4][0].colors).toBeInstanceOf(Uint32Array);
|
||||
expect(Array.from(ir[4][0].colors)).toEqual([
|
||||
0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11,
|
||||
]);
|
||||
expect(ir[5]).toEqual([10, 20, 12, 23]);
|
||||
expect(ir[6]).toBeNull();
|
||||
expect(ir[7]).toBeNull();
|
||||
});
|
||||
|
||||
it("must keep mesh colors intact through binary serialization", function () {
|
||||
const shading = createFunctionBasedShading({
|
||||
background: [0.25, 0.5, 0.75],
|
||||
});
|
||||
const buffer = compilePatternInfo(shading.getIR());
|
||||
const reconstructedIR = new PatternInfo(buffer).getIR();
|
||||
|
||||
expect(reconstructedIR[0]).toEqual("Mesh");
|
||||
expect(reconstructedIR[1]).toEqual(1);
|
||||
expect(Array.from(reconstructedIR[2])).toEqual(
|
||||
Array.from(shading.coords)
|
||||
);
|
||||
expect(Array.from(reconstructedIR[3])).toEqual(
|
||||
Array.from(shading.colors)
|
||||
);
|
||||
expect(Array.from(reconstructedIR[7])).toEqual([64, 128, 191]);
|
||||
});
|
||||
|
||||
it("must sample the upper and right edges half a step inside", function () {
|
||||
const shading = createFunctionBasedShading({
|
||||
colorSpace: "DeviceGray",
|
||||
fn(src, srcOffset, dest, destOffset) {
|
||||
dest[destOffset] =
|
||||
src[srcOffset] === 1 || src[srcOffset + 1] === 1 ? 1 : 0;
|
||||
},
|
||||
});
|
||||
const [, , , colors] = shading.getIR();
|
||||
|
||||
expect(colors.length).toEqual(48);
|
||||
expect(Array.from(colors)).toEqual(new Array(48).fill(0));
|
||||
});
|
||||
});
|
||||
});
|
||||
Loading…
x
Reference in New Issue
Block a user