...

Source file src/oss.terrastruct.com/d2/d2renderers/d2animate/d2animate.go

Documentation: oss.terrastruct.com/d2/d2renderers/d2animate

     1  package d2animate
     2  
     3  import (
     4  	"bytes"
     5  	"fmt"
     6  	"math"
     7  	"strings"
     8  
     9  	"oss.terrastruct.com/d2/d2renderers/d2sketch"
    10  	"oss.terrastruct.com/d2/d2renderers/d2svg"
    11  	"oss.terrastruct.com/d2/d2target"
    12  	"oss.terrastruct.com/d2/lib/version"
    13  )
    14  
    15  var transitionDurationMS = 1
    16  
    17  func makeKeyframe(delayMS, durationMS, totalMS, identifier int, diagramHash string) string {
    18  	percentageBefore := (math.Max(0, float64(delayMS-transitionDurationMS)) / float64(totalMS)) * 100.
    19  	percentageStart := (float64(delayMS) / float64(totalMS)) * 100.
    20  	percentageEnd := (float64(delayMS+durationMS-transitionDurationMS) / float64(totalMS)) * 100.
    21  	if int(math.Ceil(percentageEnd)) == 100 {
    22  		return fmt.Sprintf(`@keyframes d2Transition-%s-%d {
    23  		0%%, %f%% {
    24  				opacity: 0;
    25  		}
    26  		%f%%, %f%% {
    27  				opacity: 1;
    28  		}
    29  }`, diagramHash, identifier, percentageBefore, percentageStart, math.Ceil(percentageEnd))
    30  	}
    31  
    32  	percentageAfter := (float64(delayMS+durationMS) / float64(totalMS)) * 100.
    33  	return fmt.Sprintf(`@keyframes d2Transition-%s-%d {
    34  		0%%, %f%% {
    35  				opacity: 0;
    36  		}
    37  		%f%%, %f%% {
    38  				opacity: 1;
    39  		}
    40  		%f%%, 100%% {
    41  				opacity: 0;
    42  		}
    43  }`, diagramHash, identifier, percentageBefore, percentageStart, percentageEnd, percentageAfter)
    44  }
    45  
    46  func Wrap(rootDiagram *d2target.Diagram, svgs [][]byte, renderOpts d2svg.RenderOpts, intervalMS int) ([]byte, error) {
    47  	buf := &bytes.Buffer{}
    48  
    49  	// TODO account for stroke width of root border
    50  
    51  	tl, br := rootDiagram.NestedBoundingBox()
    52  	left := tl.X - int(*renderOpts.Pad)
    53  	top := tl.Y - int(*renderOpts.Pad)
    54  	width := br.X - tl.X + int(*renderOpts.Pad)*2
    55  	height := br.Y - tl.Y + int(*renderOpts.Pad)*2
    56  
    57  	fitToScreenWrapperOpening := fmt.Sprintf(`<?xml version="1.0" encoding="utf-8"?><svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" d2Version="%s" preserveAspectRatio="xMinYMin meet" viewBox="0 0 %d %d">`,
    58  		version.Version,
    59  		width, height,
    60  	)
    61  	fmt.Fprint(buf, fitToScreenWrapperOpening)
    62  
    63  	innerOpening := fmt.Sprintf(`<svg id="d2-svg" width="%d" height="%d" viewBox="%d %d %d %d">`,
    64  		width, height, left, top, width, height)
    65  	fmt.Fprint(buf, innerOpening)
    66  
    67  	svgsStr := ""
    68  	for _, svg := range svgs {
    69  		svgsStr += string(svg) + " "
    70  	}
    71  
    72  	diagramHash, err := rootDiagram.HashID()
    73  	if err != nil {
    74  		return nil, err
    75  	}
    76  
    77  	d2svg.EmbedFonts(buf, diagramHash, svgsStr, rootDiagram.FontFamily, rootDiagram.GetNestedCorpus())
    78  
    79  	themeStylesheet, err := d2svg.ThemeCSS(diagramHash, renderOpts.ThemeID, renderOpts.DarkThemeID, renderOpts.ThemeOverrides, renderOpts.DarkThemeOverrides)
    80  	if err != nil {
    81  		return nil, err
    82  	}
    83  	fmt.Fprintf(buf, `<style type="text/css"><![CDATA[%s%s]]></style>`, d2svg.BaseStylesheet, themeStylesheet)
    84  
    85  	if rootDiagram.HasShape(func(s d2target.Shape) bool {
    86  		return s.Label != "" && s.Type == d2target.ShapeText
    87  	}) {
    88  		css := d2svg.MarkdownCSS
    89  		css = strings.ReplaceAll(css, "font-italic", fmt.Sprintf("%s-font-italic", diagramHash))
    90  		css = strings.ReplaceAll(css, "font-bold", fmt.Sprintf("%s-font-bold", diagramHash))
    91  		css = strings.ReplaceAll(css, "font-mono", fmt.Sprintf("%s-font-mono", diagramHash))
    92  		css = strings.ReplaceAll(css, "font-regular", fmt.Sprintf("%s-font-regular", diagramHash))
    93  		fmt.Fprintf(buf, `<style type="text/css">%s</style>`, css)
    94  	}
    95  
    96  	if renderOpts.Sketch != nil && *renderOpts.Sketch {
    97  		d2sketch.DefineFillPatterns(buf)
    98  	}
    99  
   100  	fmt.Fprint(buf, `<style type="text/css"><![CDATA[`)
   101  	for i := range svgs {
   102  		fmt.Fprint(buf, makeKeyframe(i*intervalMS, intervalMS, len(svgs)*intervalMS, i, diagramHash))
   103  	}
   104  	fmt.Fprint(buf, `]]></style>`)
   105  
   106  	for i, svg := range svgs {
   107  		str := string(svg)
   108  		str = strings.Replace(str, "<g", fmt.Sprintf(`<g style="animation: d2Transition-%s-%d %dms infinite"`, diagramHash, i, len(svgs)*intervalMS), 1)
   109  		buf.Write([]byte(str))
   110  	}
   111  
   112  	fmt.Fprint(buf, "</svg>")
   113  	fmt.Fprint(buf, "</svg>")
   114  
   115  	return buf.Bytes(), nil
   116  }
   117  

View as plain text