1
1
import CssDimension from '../vendor/parse-css-dimension/index.js'
2
- import { buildXMLString } from '../utils.js'
2
+ import { buildXMLString , lengthToNumber } from '../utils.js'
3
3
4
4
import gradient from '../vendor/gradient-parser/index.js'
5
5
import { resolveImageData } from '../handler/image.js'
@@ -167,7 +167,8 @@ export default async function backgroundImage(
167
167
left,
168
168
top,
169
169
} : { id : string ; width : number ; height : number ; left : number ; top : number } ,
170
- { image, size, position, repeat } : Background
170
+ { image, size, position, repeat } : Background ,
171
+ inheritableStyle : Record < string , number | string >
171
172
) : Promise < string [ ] > {
172
173
// Default to `repeat`.
173
174
repeat = repeat || 'repeat'
@@ -327,8 +328,16 @@ export default async function backgroundImage(
327
328
if ( ! orientation . at ) {
328
329
// Defaults to center.
329
330
} else if ( orientation . at . type === 'position' ) {
330
- cx = orientation . at . value . x . value
331
- cy = orientation . at . value . y . value
331
+ const pos = calcRadialGradient (
332
+ orientation . at . value . x ,
333
+ orientation . at . value . y ,
334
+ xDelta ,
335
+ yDelta ,
336
+ inheritableStyle . fontSize as number ,
337
+ inheritableStyle
338
+ )
339
+ cx = pos . x
340
+ cy = pos . y
332
341
} else {
333
342
throw new Error (
334
343
'orientation.at.type not implemented: ' + orientation . at . type
@@ -346,25 +355,14 @@ export default async function backgroundImage(
346
355
347
356
// We currently only support `farthest-corner`:
348
357
// https://developer.mozilla.org/en-US/docs/Web/CSS/gradient/radial-gradient()#values
349
- const spread : Record < string , number > = { }
350
-
351
- // Farest corner.
352
- const fx = Math . max ( Math . abs ( xDelta - cx ) , Math . abs ( cx ) )
353
- const fy = Math . max ( Math . abs ( yDelta - cy ) , Math . abs ( cy ) )
354
- if ( shape === 'circle' ) {
355
- spread . r = Math . sqrt ( fx * fx + fy * fy )
356
- } else if ( shape === 'ellipse' ) {
357
- // Spec: https://drafts.csswg.org/css-images/#typedef-size
358
- // Get the aspect ratio of the closest-side size.
359
- const ratio = fy !== 0 ? fx / fy : 1
360
-
361
- // fx^2/a^2 + fy^2/b^2 = 1
362
- // fx^2/(b*ratio)^2 + fy^2/b^2 = 1
363
- // (fx^2+fy^2*ratio^2) = (b*ratio)^2
364
- // b = sqrt(fx^2+fy^2*ratio^2)/ratio
365
- spread . ry = Math . sqrt ( fx * fx + fy * fy * ratio * ratio ) / ratio
366
- spread . rx = spread . ry * ratio
367
- }
358
+ const spread = calcRadius (
359
+ shape as Shape ,
360
+ orientation . style ,
361
+ inheritableStyle . fontSize as number ,
362
+ { x : cx , y : cy } ,
363
+ [ xDelta , yDelta ] ,
364
+ inheritableStyle
365
+ )
368
366
369
367
// TODO: check for repeat-x/repeat-y
370
368
const defs = buildXMLString (
@@ -404,6 +402,13 @@ export default async function backgroundImage(
404
402
fill : '#fff' ,
405
403
} )
406
404
) +
405
+ buildXMLString ( 'rect' , {
406
+ x : 0 ,
407
+ y : 0 ,
408
+ width : xDelta ,
409
+ height : yDelta ,
410
+ fill : stops . at ( - 1 ) . color ,
411
+ } ) +
407
412
buildXMLString ( shape , {
408
413
cx : cx ,
409
414
cy : cy ,
@@ -459,3 +464,178 @@ export default async function backgroundImage(
459
464
460
465
throw new Error ( `Invalid background image: "${ image } "` )
461
466
}
467
+
468
+ type PositionKeyWord = 'center' | 'left' | 'right' | 'top' | 'bottom'
469
+ interface Position {
470
+ type : string
471
+ value : PositionKeyWord
472
+ }
473
+
474
+ function calcRadialGradient (
475
+ cx : Position ,
476
+ cy : Position ,
477
+ xDelta : number ,
478
+ yDelta : number ,
479
+ baseFontSize : number ,
480
+ style : Record < string , string | number >
481
+ ) {
482
+ const pos : { x : number ; y : number } = { x : xDelta / 2 , y : yDelta / 2 }
483
+ if ( cx . type === 'position-keyword' ) {
484
+ Object . assign ( pos , calcPos ( cx . value , xDelta , yDelta , 'x' ) )
485
+ } else {
486
+ pos . x = lengthToNumber (
487
+ `${ cx . value } ${ cx . type } ` ,
488
+ baseFontSize ,
489
+ xDelta ,
490
+ style ,
491
+ true
492
+ )
493
+ }
494
+
495
+ if ( cy . type === 'position-keyword' ) {
496
+ Object . assign ( pos , calcPos ( cy . value , xDelta , yDelta , 'y' ) )
497
+ } else {
498
+ pos . y = lengthToNumber (
499
+ `${ cy . value } ${ cy . type } ` ,
500
+ baseFontSize ,
501
+ yDelta ,
502
+ style ,
503
+ true
504
+ )
505
+ }
506
+
507
+ return pos
508
+ }
509
+
510
+ function calcPos (
511
+ key : PositionKeyWord ,
512
+ xDelta : number ,
513
+ yDelta : number ,
514
+ dir : 'x' | 'y'
515
+ ) {
516
+ switch ( key ) {
517
+ case 'center' :
518
+ return { [ dir ] : dir === 'x' ? xDelta / 2 : yDelta / 2 }
519
+ case 'left' :
520
+ return { x : 0 }
521
+ case 'top' :
522
+ return { y : 0 }
523
+ case 'right' :
524
+ return { x : xDelta }
525
+ case 'bottom' :
526
+ return { y : yDelta }
527
+ }
528
+ }
529
+
530
+ type Shape = 'circle' | 'ellipse'
531
+ function calcRadius (
532
+ shape : Shape ,
533
+ endingShape : Array < { type : string ; value : string } > ,
534
+ baseFontSize : number ,
535
+ centerAxis : { x : number ; y : number } ,
536
+ length : [ number , number ] ,
537
+ inheritableStyle : Record < string , string | number >
538
+ ) {
539
+ const [ xDelta , yDelta ] = length
540
+ const { x : cx , y : cy } = centerAxis
541
+ const spread : Record < string , number > = { }
542
+ let fx = 0
543
+ let fy = 0
544
+ const isExtentKeyWord = endingShape . some ( ( v ) => v . type === 'extent-keyword' )
545
+
546
+ if ( ! isExtentKeyWord ) {
547
+ if ( endingShape . some ( ( v ) => v . value . startsWith ( '-' ) ) ) {
548
+ throw new Error (
549
+ 'disallow setting negative values to the size of the shape. Check https://w3c.github.io/csswg-drafts/css-images/#valdef-rg-size-length-0'
550
+ )
551
+ }
552
+ if ( shape === 'circle' ) {
553
+ return {
554
+ r : lengthToNumber (
555
+ `${ endingShape [ 0 ] . value } ${ endingShape [ 0 ] . type } ` ,
556
+ baseFontSize ,
557
+ xDelta ,
558
+ inheritableStyle ,
559
+ true
560
+ ) ,
561
+ }
562
+ } else {
563
+ return {
564
+ rx : lengthToNumber (
565
+ `${ endingShape [ 0 ] . value } ${ endingShape [ 0 ] . type } ` ,
566
+ baseFontSize ,
567
+ xDelta ,
568
+ inheritableStyle ,
569
+ true
570
+ ) ,
571
+ ry : lengthToNumber (
572
+ `${ endingShape [ 1 ] . value } ${ endingShape [ 1 ] . type } ` ,
573
+ baseFontSize ,
574
+ yDelta ,
575
+ inheritableStyle ,
576
+ true
577
+ ) ,
578
+ }
579
+ }
580
+ }
581
+
582
+ switch ( endingShape [ 0 ] . value ) {
583
+ case 'farthest-corner' :
584
+ fx = Math . max ( Math . abs ( xDelta - cx ) , Math . abs ( cx ) )
585
+ fy = Math . max ( Math . abs ( yDelta - cy ) , Math . abs ( cy ) )
586
+ break
587
+ case 'closest-corner' :
588
+ fx = Math . min ( Math . abs ( xDelta - cx ) , Math . abs ( cx ) )
589
+ fy = Math . min ( Math . abs ( yDelta - cy ) , Math . abs ( cy ) )
590
+ break
591
+ case 'farthest-side' :
592
+ if ( shape === 'circle' ) {
593
+ spread . r = Math . max (
594
+ Math . abs ( xDelta - cx ) ,
595
+ Math . abs ( cx ) ,
596
+ Math . abs ( yDelta - cy ) ,
597
+ Math . abs ( cy )
598
+ )
599
+ } else {
600
+ spread . rx = Math . max ( Math . abs ( xDelta - cx ) , Math . abs ( cx ) )
601
+ spread . ry = Math . max ( Math . abs ( yDelta - cy ) , Math . abs ( cy ) )
602
+ }
603
+ return spread
604
+ case 'closest-side' :
605
+ if ( shape === 'circle' ) {
606
+ spread . r = Math . min (
607
+ Math . abs ( xDelta - cx ) ,
608
+ Math . abs ( cx ) ,
609
+ Math . abs ( yDelta - cy ) ,
610
+ Math . abs ( cy )
611
+ )
612
+ } else {
613
+ spread . rx = Math . min ( Math . abs ( xDelta - cx ) , Math . abs ( cx ) )
614
+ spread . ry = Math . min ( Math . abs ( yDelta - cy ) , Math . abs ( cy ) )
615
+ }
616
+
617
+ return spread
618
+ }
619
+ if ( shape === 'circle' ) {
620
+ spread . r = Math . sqrt ( fx * fx + fy * fy )
621
+ } else {
622
+ // Spec: https://drafts.csswg.org/css-images/#typedef-size
623
+ // Get the aspect ratio of the closest-side size.
624
+ const ratio = fy !== 0 ? fx / fy : 1
625
+
626
+ if ( fx === 0 ) {
627
+ spread . rx = 0
628
+ spread . ry = 0
629
+ } else {
630
+ // fx^2/a^2 + fy^2/b^2 = 1
631
+ // fx^2/(b*ratio)^2 + fy^2/b^2 = 1
632
+ // (fx^2+fy^2*ratio^2) = (b*ratio)^2
633
+ // b = sqrt(fx^2+fy^2*ratio^2)/ratio
634
+
635
+ spread . ry = Math . sqrt ( fx * fx + fy * fy * ratio * ratio ) / ratio
636
+ spread . rx = spread . ry * ratio
637
+ }
638
+ }
639
+
640
+ return spread
641
+ }
1 commit comments
vercel[bot] commentedon Apr 22, 2023
Successfully deployed to the following URLs:
satori-playground – ./
og-playground.vercel.app
satori-playground.vercel.app
og-playground.vercel.sh
satori-playground.vercel.sh
satori-playground-git-main.vercel.sh