@@ -315,6 +315,13 @@ func (r *svgRenderer) withClip(clipRef string, parentTransform svg.Transform, st
315315 return fn ()
316316 }
317317 clipStyle := clip .Style .Resolve (style )
318+ if len (clip .Children ) == 1 {
319+ if textNode , ok := clip .Children [0 ].(* svg.Text ); ok {
320+ textStyle := textNode .Style .Resolve (clipStyle )
321+ textTransform := parentTransform .Mul (textNode .NodeCommon ().Transform )
322+ return r .clipWithText (textNode , textStyle , textTransform , fn )
323+ }
324+ }
318325 var renderErr error
319326 err := r .pw .Path (func () {
320327 for _ , child := range clip .Children {
@@ -339,6 +346,26 @@ func (r *svgRenderer) withClip(clipRef string, parentTransform svg.Transform, st
339346 return renderErr
340347}
341348
349+ func (r * svgRenderer ) clipWithText (text * svg.Text , style svg.Style , transform svg.Transform , fn func () error ) error {
350+ layout , err := r .layoutText (text , style , transform )
351+ if err != nil || layout == nil {
352+ return err
353+ }
354+ return r .withPreparedTextState (style , func () error {
355+ r .pw .MoveTo (layout .startX , layout .mappedY )
356+ var renderErr error
357+ err := r .pw .ClipRichText (layout .rich , func () {
358+ if fn != nil {
359+ renderErr = fn ()
360+ }
361+ })
362+ if err != nil {
363+ return err
364+ }
365+ return renderErr
366+ })
367+ }
368+
342369func (r * svgRenderer ) pathForNode (node svg.Node ) []svg.Segment {
343370 switch n := node .(type ) {
344371 case * svg.Line :
@@ -1253,68 +1280,28 @@ func normalizeFontWeight(style svg.Style) string {
12531280}
12541281
12551282func (r * svgRenderer ) drawText (text * svg.Text , style svg.Style , transform svg.Transform ) error {
1256- if text .Body == "" {
1257- return nil
1258- }
1259- if transform .B != 0 || transform .C != 0 || transform .A != 1 || transform .D != 1 {
1260- logSVGWarnings ([]svg.Warning {{Element : "text" , Message : "text transforms beyond translation are not yet implemented" }})
1261- }
1262- point := transform .Apply (svg.Point {X : text .X , Y : text .Y })
1263- mapped := r .mapPoint (point )
1264- savedFonts := append ([]* font.Font (nil ), r .pw .fonts ... )
1265- savedFontSize := r .pw .fontSize
1266- savedFontColor := r .pw .fontColor
1267- prevUnits := r .pw .units
1268- defer func () {
1269- r .pw .fonts = savedFonts
1270- r .pw .fontSize = savedFontSize
1271- r .pw .fontColor = savedFontColor
1272- r .pw .units = prevUnits
1273- }()
1274- r .pw .SetFontColor (style .Fill .Color )
1275- r .pw .units = UnitConversions ["pt" ]
1276- if _ , err := r .pw .SetFont (style .FontFamily , style .FontSize * r .scaleY , options.Options {
1277- "style" : normalizeFontStyle (style ),
1278- "weight" : normalizeFontWeight (style ),
1279- }); err != nil {
1280- logSVGWarnings ([]svg.Warning {{Element : "text" , Attribute : "font-family" , Message : fmt .Sprintf ("font %q unavailable: %v" , style .FontFamily , err )}})
1281- return nil
1282- }
1283- rich , err := r .pw .richTextForString (text .Body )
1284- if err != nil {
1283+ layout , err := r .layoutText (text , style , transform )
1284+ if err != nil || layout == nil {
12851285 return err
12861286 }
1287- if rich == nil || rich .Len () == 0 {
1288- return nil
1289- }
1290- startX := mapped .X
1291- startXSVG := point .X
1292- if style .TextAnchor != "" && style .TextAnchor != "start" {
1293- switch strings .ToLower (style .TextAnchor ) {
1294- case "middle" :
1295- startX -= rich .Width () / 2
1296- startXSVG -= r .textWidthSVG (rich ) / 2
1297- case "end" :
1298- startX -= rich .Width ()
1299- startXSVG -= r .textWidthSVG (rich )
1300- }
1301- }
13021287 blendMode := mapSVGBlendMode (style .BlendMode )
13031288 alpha := clamp01 (style .Opacity * style .FillOpacity )
13041289 if style .Stroke .IsGradient () {
13051290 logSVGWarnings ([]svg.Warning {{Element : "text" , Attribute : "stroke" , Message : "gradient stroke on text is not yet implemented" }})
13061291 }
13071292 if style .Fill .IsGradient () {
1308- resolved , err := r .resolveTextGradient (style .Fill .Ref , alpha , rich , startXSVG , point . Y , transform )
1293+ resolved , err := r .resolveTextGradient (style .Fill .Ref , alpha , layout . rich , layout . startXSVG , layout . baselineY , transform )
13091294 if err != nil {
13101295 logSVGWarnings ([]svg.Warning {{Element : "linearGradient" , Message : err .Error ()}})
13111296 if style .Fill .None {
13121297 return nil
13131298 }
13141299 return r .withScopedGraphicsState (alpha , 1 , blendMode , nil , "" , nil , func () error {
1315- r .pw .SetFontColor (style .Fill .Color )
1316- r .pw .MoveTo (startX , mapped .Y )
1317- return r .pw .Print (text .Body )
1300+ return r .withPreparedTextState (style , func () error {
1301+ r .pw .SetFontColor (style .Fill .Color )
1302+ r .pw .MoveTo (layout .startX , layout .mappedY )
1303+ return r .pw .Print (text .Body )
1304+ })
13181305 })
13191306 }
13201307 if resolved .varyingAlpha {
@@ -1328,32 +1315,112 @@ func (r *svgRenderer) drawText(text *svg.Text, style svg.Style, transform svg.Tr
13281315 alpha = resolved .uniformAlpha
13291316 }
13301317 return r .withScopedGraphicsState (alpha , 1 , blendMode , nil , "" , nil , func () error {
1331- r .pw .MoveTo (startX , mapped .Y )
1332- var renderErr error
1333- err := r .pw .ClipRichText (rich , func () {
1334- switch {
1335- case resolved .linear != nil :
1336- renderErr = r .pw .PaintLinearGradient (resolved .linear )
1337- case resolved .radial != nil :
1338- renderErr = r .pw .PaintRadialGradient (resolved .radial )
1318+ return r .withPreparedTextState (style , func () error {
1319+ r .pw .MoveTo (layout .startX , layout .mappedY )
1320+ var renderErr error
1321+ err := r .pw .ClipRichText (layout .rich , func () {
1322+ switch {
1323+ case resolved .linear != nil :
1324+ renderErr = r .pw .PaintLinearGradient (resolved .linear )
1325+ case resolved .radial != nil :
1326+ renderErr = r .pw .PaintRadialGradient (resolved .radial )
1327+ }
1328+ })
1329+ if err != nil {
1330+ return err
13391331 }
1332+ return renderErr
13401333 })
1341- if err != nil {
1342- return err
1343- }
1344- return renderErr
13451334 })
13461335 }
13471336 if style .Fill .None {
13481337 return nil
13491338 }
13501339 return r .withScopedGraphicsState (alpha , 1 , blendMode , nil , "" , nil , func () error {
1351- r .pw .SetFontColor (style .Fill .Color )
1352- r .pw .MoveTo (startX , mapped .Y )
1353- return r .pw .Print (text .Body )
1340+ return r .withPreparedTextState (style , func () error {
1341+ r .pw .SetFontColor (style .Fill .Color )
1342+ r .pw .MoveTo (layout .startX , layout .mappedY )
1343+ return r .pw .Print (text .Body )
1344+ })
13541345 })
13551346}
13561347
1348+ type svgTextLayout struct {
1349+ rich * rich_text.RichText
1350+ startX float64
1351+ mappedY float64
1352+ startXSVG float64
1353+ baselineY float64
1354+ }
1355+
1356+ func (r * svgRenderer ) layoutText (text * svg.Text , style svg.Style , transform svg.Transform ) (* svgTextLayout , error ) {
1357+ if text .Body == "" {
1358+ return nil , nil
1359+ }
1360+ if transform .B != 0 || transform .C != 0 || transform .A != 1 || transform .D != 1 {
1361+ logSVGWarnings ([]svg.Warning {{Element : "text" , Message : "text transforms beyond translation are not yet implemented" }})
1362+ }
1363+ point := transform .Apply (svg.Point {X : text .X , Y : text .Y })
1364+ mapped := r .mapPoint (point )
1365+ var rich * rich_text.RichText
1366+ err := r .withPreparedTextState (style , func () error {
1367+ var err error
1368+ rich , err = r .pw .richTextForString (text .Body )
1369+ return err
1370+ })
1371+ if err != nil {
1372+ return nil , err
1373+ }
1374+ if rich == nil || rich .Len () == 0 {
1375+ return nil , nil
1376+ }
1377+ startX := mapped .X
1378+ startXSVG := point .X
1379+ if style .TextAnchor != "" && style .TextAnchor != "start" {
1380+ switch strings .ToLower (style .TextAnchor ) {
1381+ case "middle" :
1382+ startX -= rich .Width () / 2
1383+ startXSVG -= r .textWidthSVG (rich ) / 2
1384+ case "end" :
1385+ startX -= rich .Width ()
1386+ startXSVG -= r .textWidthSVG (rich )
1387+ }
1388+ }
1389+ return & svgTextLayout {
1390+ rich : rich ,
1391+ startX : startX ,
1392+ mappedY : mapped .Y ,
1393+ startXSVG : startXSVG ,
1394+ baselineY : point .Y ,
1395+ }, nil
1396+ }
1397+
1398+ func (r * svgRenderer ) withPreparedTextState (style svg.Style , fn func () error ) error {
1399+ savedFonts := append ([]* font.Font (nil ), r .pw .fonts ... )
1400+ savedFontSize := r .pw .fontSize
1401+ savedFontColor := r .pw .fontColor
1402+ prevUnits := r .pw .units
1403+ defer func () {
1404+ r .pw .fonts = savedFonts
1405+ r .pw .fontSize = savedFontSize
1406+ r .pw .fontColor = savedFontColor
1407+ r .pw .units = prevUnits
1408+ }()
1409+ r .pw .SetFontColor (style .Fill .Color )
1410+ r .pw .units = UnitConversions ["pt" ]
1411+ if _ , err := r .pw .SetFont (style .FontFamily , style .FontSize * r .scaleY , options.Options {
1412+ "style" : normalizeFontStyle (style ),
1413+ "weight" : normalizeFontWeight (style ),
1414+ }); err != nil {
1415+ logSVGWarnings ([]svg.Warning {{Element : "text" , Attribute : "font-family" , Message : fmt .Sprintf ("font %q unavailable: %v" , style .FontFamily , err )}})
1416+ return nil
1417+ }
1418+ if fn == nil {
1419+ return nil
1420+ }
1421+ return fn ()
1422+ }
1423+
13571424func (r * svgRenderer ) resolveTextGradient (ref string , opacityScale float64 , text * rich_text.RichText , startX , baselineY float64 , transform svg.Transform ) (* resolvedSVGGradient , error ) {
13581425 if text == nil {
13591426 return nil , fmt .Errorf ("gradient text has no content" )
0 commit comments