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
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