@@ -29,6 +29,7 @@ import {
2929 isNumberArray ,
3030 lookupMatrix ,
3131 lookupNormalRect ,
32+ lookupRect ,
3233 MissingDataException ,
3334} from "./core_utils.js" ;
3435import { BaseStream } from "./base_stream.js" ;
@@ -63,6 +64,16 @@ class Pattern {
6364
6465 try {
6566 switch ( type ) {
67+ case ShadingType . FUNCTION_BASED :
68+ prepareWebGPU ?. ( ) ;
69+ return new FunctionBasedShading (
70+ dict ,
71+ xref ,
72+ res ,
73+ pdfFunctionFactory ,
74+ globalColorSpaceCache ,
75+ localColorSpaceCache
76+ ) ;
6677 case ShadingType . AXIAL :
6778 case ShadingType . RADIAL :
6879 return new RadialAxialShading (
@@ -312,6 +323,183 @@ class RadialAxialShading extends BaseShading {
312323 }
313324}
314325
326+ // Helpers for MeshShading, which builds its mesh from a stream.
327+ function meshUpdateBounds ( self ) {
328+ let minX = self . coords [ 0 ] [ 0 ] ,
329+ minY = self . coords [ 0 ] [ 1 ] ,
330+ maxX = minX ,
331+ maxY = minY ;
332+ for ( let i = 1 , ii = self . coords . length ; i < ii ; i ++ ) {
333+ const x = self . coords [ i ] [ 0 ] ,
334+ y = self . coords [ i ] [ 1 ] ;
335+ minX = minX > x ? x : minX ;
336+ minY = minY > y ? y : minY ;
337+ maxX = maxX < x ? x : maxX ;
338+ maxY = maxY < y ? y : maxY ;
339+ }
340+ self . bounds = [ minX , minY , maxX , maxY ] ;
341+ }
342+
343+ function meshPackData ( self ) {
344+ let i , j , ii ;
345+
346+ const coords = self . coords ;
347+ const coordsPacked = new Float32Array ( coords . length * 2 ) ;
348+ for ( i = 0 , j = 0 , ii = coords . length ; i < ii ; i ++ ) {
349+ const xy = coords [ i ] ;
350+ coordsPacked [ j ++ ] = xy [ 0 ] ;
351+ coordsPacked [ j ++ ] = xy [ 1 ] ;
352+ }
353+ self . coords = coordsPacked ;
354+
355+ // Stride 4 (RGB + 1 padding byte) so each color fits in one u32, letting
356+ // the WebGPU vertex shader read colors as array<u32> without repacking.
357+ const colors = self . colors ;
358+ const colorsPacked = new Uint8Array ( colors . length * 4 ) ;
359+ for ( i = 0 , j = 0 , ii = colors . length ; i < ii ; i ++ ) {
360+ const c = colors [ i ] ;
361+ colorsPacked [ j ++ ] = c [ 0 ] ;
362+ colorsPacked [ j ++ ] = c [ 1 ] ;
363+ colorsPacked [ j ++ ] = c [ 2 ] ;
364+ j ++ ; // alpha — unused, stays 0
365+ }
366+ self . colors = colorsPacked ;
367+
368+ // Store raw vertex indices (not byte offsets) so the GPU shader can
369+ // address coords / colors without knowing their strides, and so the
370+ // arrays are transferable Uint32Arrays.
371+ for ( const figure of self . figures ) {
372+ figure . coords = new Uint32Array ( figure . coords ) ;
373+ figure . colors = new Uint32Array ( figure . colors ) ;
374+ }
375+ }
376+
377+ // Type 1 shading: a 2-in, n-out function sampled over a rectangular domain.
378+ class FunctionBasedShading extends BaseShading {
379+ // Maximum grid steps per axis to avoid huge meshes.
380+ static MAX_STEP_COUNT = 512 ;
381+
382+ constructor (
383+ dict ,
384+ xref ,
385+ resources ,
386+ pdfFunctionFactory ,
387+ globalColorSpaceCache ,
388+ localColorSpaceCache
389+ ) {
390+ super ( ) ;
391+ this . bbox = lookupNormalRect ( dict . getArray ( "BBox" ) , null ) ;
392+
393+ const cs = ColorSpaceUtils . parse ( {
394+ cs : dict . getRaw ( "CS" ) || dict . getRaw ( "ColorSpace" ) ,
395+ xref,
396+ resources,
397+ pdfFunctionFactory,
398+ globalColorSpaceCache,
399+ localColorSpaceCache,
400+ } ) ;
401+ this . background = dict . has ( "Background" )
402+ ? cs . getRgb ( dict . get ( "Background" ) , 0 )
403+ : null ;
404+
405+ const fnObj = dict . getRaw ( "Function" ) ;
406+ if ( ! fnObj ) {
407+ throw new FormatError ( "FunctionBasedShading: missing /Function" ) ;
408+ }
409+ const fn = pdfFunctionFactory . create ( fnObj , /* parseArray = */ true ) ;
410+
411+ // Domain [x0, x1, y0, y1]; defaults to [0, 1, 0, 1].
412+ let x0 = 0 ,
413+ x1 = 1 ,
414+ y0 = 0 ,
415+ y1 = 1 ;
416+ const domainArr = lookupRect ( dict . getArray ( "Domain" ) , null ) ;
417+ if ( domainArr ) {
418+ [ x0 , x1 , y0 , y1 ] = domainArr ;
419+ }
420+
421+ // Matrix maps shading (domain) space to user space; defaults to identity.
422+ const matrix = lookupMatrix ( dict . getArray ( "Matrix" ) , IDENTITY_MATRIX ) ;
423+
424+ // Transform the four domain corners to find the user-space bounding box.
425+ this . bounds = [ Infinity , Infinity , - Infinity , - Infinity ] ;
426+ Util . axialAlignedBoundingBox ( [ x0 , y0 , x1 , y1 ] , matrix , this . bounds ) ;
427+
428+ const bboxW = this . bounds [ 2 ] - this . bounds [ 0 ] ;
429+ const bboxH = this . bounds [ 3 ] - this . bounds [ 1 ] ;
430+
431+ // 1 step per user-space unit, capped for performance.
432+ const stepsX = MathClamp (
433+ Math . ceil ( bboxW ) ,
434+ 1 ,
435+ FunctionBasedShading . MAX_STEP_COUNT
436+ ) ;
437+ const stepsY = MathClamp (
438+ Math . ceil ( bboxH ) ,
439+ 1 ,
440+ FunctionBasedShading . MAX_STEP_COUNT
441+ ) ;
442+
443+ const verticesPerRow = stepsX + 1 ;
444+ const totalVertices = ( stepsY + 1 ) * verticesPerRow ;
445+ const coords = ( this . coords = new Float32Array ( totalVertices * 2 ) ) ;
446+ const colors = ( this . colors = new Uint8ClampedArray ( totalVertices * 4 ) ) ;
447+
448+ const xyBuf = new Float32Array ( 2 ) ;
449+ const colorBuf = new Float32Array ( cs . numComps ) ;
450+ const rangeX = ( x1 - x0 ) / stepsX ;
451+ const rangeY = ( y1 - y0 ) / stepsY ;
452+ const halfStepX = rangeX / 2 ;
453+ const halfStepY = rangeY / 2 ;
454+ let coordOffset = 0 ;
455+ let colorOffset = 0 ;
456+ for ( let row = 0 ; row <= stepsY ; row ++ ) {
457+ const yDomain = y0 + rangeY * row ;
458+ // Evaluate half a step inside at boundary vertices to avoid a spurious
459+ // strip for discontinuous functions; vertex positions stay unchanged.
460+ xyBuf [ 1 ] = row === stepsY ? yDomain - halfStepY : yDomain ;
461+ for ( let col = 0 ; col <= stepsX ; col ++ ) {
462+ const xDomain = x0 + rangeX * col ;
463+ xyBuf [ 0 ] = col === stepsX ? xDomain - halfStepX : xDomain ;
464+ fn ( xyBuf , 0 , colorBuf , 0 ) ;
465+ coords [ coordOffset ] = xDomain ;
466+ coords [ coordOffset + 1 ] = yDomain ;
467+ Util . applyTransform ( coords , matrix , coordOffset ) ;
468+ coordOffset += 2 ;
469+
470+ cs . getRgbItem ( colorBuf , 0 , colors , colorOffset ) ;
471+ colorOffset += 4 ; // alpha — unused, stays 0
472+ }
473+ }
474+
475+ const ps = new Uint32Array ( totalVertices ) ;
476+ for ( let i = 0 ; i < totalVertices ; i ++ ) {
477+ ps [ i ] = i ;
478+ }
479+ this . figures = [
480+ {
481+ type : MeshFigureType . LATTICE ,
482+ coords : ps ,
483+ colors : new Uint32Array ( ps ) ,
484+ verticesPerRow,
485+ } ,
486+ ] ;
487+ }
488+
489+ getIR ( ) {
490+ return [
491+ "Mesh" ,
492+ ShadingType . FUNCTION_BASED ,
493+ this . coords ,
494+ this . colors ,
495+ this . figures ,
496+ this . bounds ,
497+ this . bbox ,
498+ this . background ,
499+ ] ;
500+ }
501+ }
502+
315503// All mesh shadings. For now, they will be presented as set of the triangles
316504// to be drawn on the canvas and rgb color for each vertex.
317505class MeshStreamReader {
@@ -920,55 +1108,11 @@ class MeshShading extends BaseShading {
9201108 }
9211109
9221110 _updateBounds ( ) {
923- let minX = this . coords [ 0 ] [ 0 ] ,
924- minY = this . coords [ 0 ] [ 1 ] ,
925- maxX = minX ,
926- maxY = minY ;
927- for ( let i = 1 , ii = this . coords . length ; i < ii ; i ++ ) {
928- const x = this . coords [ i ] [ 0 ] ,
929- y = this . coords [ i ] [ 1 ] ;
930- minX = minX > x ? x : minX ;
931- minY = minY > y ? y : minY ;
932- maxX = maxX < x ? x : maxX ;
933- maxY = maxY < y ? y : maxY ;
934- }
935- this . bounds = [ minX , minY , maxX , maxY ] ;
1111+ meshUpdateBounds ( this ) ;
9361112 }
9371113
9381114 _packData ( ) {
939- let i , ii , j ;
940-
941- const coords = this . coords ;
942- const coordsPacked = new Float32Array ( coords . length * 2 ) ;
943- for ( i = 0 , j = 0 , ii = coords . length ; i < ii ; i ++ ) {
944- const xy = coords [ i ] ;
945- coordsPacked [ j ++ ] = xy [ 0 ] ;
946- coordsPacked [ j ++ ] = xy [ 1 ] ;
947- }
948- this . coords = coordsPacked ;
949-
950- // Stride 4 (RGBA layout, alpha unused) so the buffer maps directly to
951- // array<u32> in the WebGPU vertex shader without any repacking.
952- const colors = this . colors ;
953- const colorsPacked = new Uint8Array ( colors . length * 4 ) ;
954- for ( i = 0 , j = 0 , ii = colors . length ; i < ii ; i ++ ) {
955- const c = colors [ i ] ;
956- colorsPacked [ j ++ ] = c [ 0 ] ;
957- colorsPacked [ j ++ ] = c [ 1 ] ;
958- colorsPacked [ j ++ ] = c [ 2 ] ;
959- j ++ ; // alpha — unused, stays 0
960- }
961- this . colors = colorsPacked ;
962-
963- // Store raw vertex indices (not byte offsets) so the GPU shader can
964- // address coords / colors without knowing their strides, and so the
965- // arrays are transferable Uint32Arrays.
966- const figures = this . figures ;
967- for ( i = 0 , ii = figures . length ; i < ii ; i ++ ) {
968- const figure = figures [ i ] ;
969- figure . coords = new Uint32Array ( figure . coords ) ;
970- figure . colors = new Uint32Array ( figure . colors ) ;
971- }
1115+ meshPackData ( this ) ;
9721116 }
9731117
9741118 getIR ( ) {
0 commit comments