1
2
3
4
5 package d2fonts
6
7 import (
8 "embed"
9 "encoding/base64"
10 "fmt"
11 "strings"
12 "sync"
13
14 "oss.terrastruct.com/d2/lib/font"
15 fontlib "oss.terrastruct.com/d2/lib/font"
16 "oss.terrastruct.com/d2/lib/syncmap"
17 )
18
19 type FontFamily string
20 type FontStyle string
21
22 type Font struct {
23 Family FontFamily
24 Style FontStyle
25 Size int
26 }
27
28 func (f FontFamily) Font(size int, style FontStyle) Font {
29 return Font{
30 Family: f,
31 Style: style,
32 Size: size,
33 }
34 }
35
36 func (f Font) GetEncodedSubset(corpus string) string {
37 var uniqueChars string
38 uniqueMap := make(map[rune]bool)
39 for _, char := range corpus {
40 if _, exists := uniqueMap[char]; !exists {
41 uniqueMap[char] = true
42 uniqueChars = uniqueChars + string(char)
43 }
44 }
45
46 FontFamiliesMu.Lock()
47 defer FontFamiliesMu.Unlock()
48 face := FontFaces.Get(f)
49 fontBuf := make([]byte, len(face))
50 copy(fontBuf, face)
51 fontBuf = font.UTF8CutFont(fontBuf, uniqueChars)
52
53 fontBuf, err := fontlib.Sfnt2Woff(fontBuf)
54 if err != nil {
55
56 return FontEncodings.Get(f)
57 }
58
59 return fmt.Sprintf("data:application/font-woff;base64,%v", base64.StdEncoding.EncodeToString(fontBuf))
60 }
61
62 const (
63 FONT_SIZE_XS = 13
64 FONT_SIZE_S = 14
65 FONT_SIZE_M = 16
66 FONT_SIZE_L = 20
67 FONT_SIZE_XL = 24
68 FONT_SIZE_XXL = 28
69 FONT_SIZE_XXXL = 32
70
71 FONT_STYLE_REGULAR FontStyle = "regular"
72 FONT_STYLE_BOLD FontStyle = "bold"
73 FONT_STYLE_SEMIBOLD FontStyle = "semibold"
74 FONT_STYLE_ITALIC FontStyle = "italic"
75
76 SourceSansPro FontFamily = "SourceSansPro"
77 SourceCodePro FontFamily = "SourceCodePro"
78 HandDrawn FontFamily = "HandDrawn"
79 )
80
81 var FontSizes = []int{
82 FONT_SIZE_XS,
83 FONT_SIZE_S,
84 FONT_SIZE_M,
85 FONT_SIZE_L,
86 FONT_SIZE_XL,
87 FONT_SIZE_XXL,
88 FONT_SIZE_XXXL,
89 }
90
91 var FontStyles = []FontStyle{
92 FONT_STYLE_REGULAR,
93 FONT_STYLE_BOLD,
94 FONT_STYLE_SEMIBOLD,
95 FONT_STYLE_ITALIC,
96 }
97
98 var FontFamilies = []FontFamily{
99 SourceSansPro,
100 SourceCodePro,
101 HandDrawn,
102 }
103
104 var FontFamiliesMu sync.Mutex
105
106
107 var sourceSansProRegularBase64 string
108
109
110 var sourceSansProBoldBase64 string
111
112
113 var sourceSansProSemiboldBase64 string
114
115
116 var sourceSansProItalicBase64 string
117
118
119 var sourceCodeProRegularBase64 string
120
121
122 var sourceCodeProBoldBase64 string
123
124
125 var sourceCodeProSemiboldBase64 string
126
127
128 var sourceCodeProItalicBase64 string
129
130
131 var fuzzyBubblesRegularBase64 string
132
133
134 var fuzzyBubblesBoldBase64 string
135
136
137 var fontFacesFS embed.FS
138
139 var FontEncodings syncmap.SyncMap[Font, string]
140 var FontFaces syncmap.SyncMap[Font, []byte]
141
142 func init() {
143 FontEncodings = syncmap.New[Font, string]()
144
145 FontEncodings.Set(
146 Font{
147 Family: SourceSansPro,
148 Style: FONT_STYLE_REGULAR,
149 },
150 sourceSansProRegularBase64)
151
152 FontEncodings.Set(
153 Font{
154 Family: SourceSansPro,
155 Style: FONT_STYLE_BOLD,
156 },
157 sourceSansProBoldBase64)
158
159 FontEncodings.Set(
160 Font{
161 Family: SourceSansPro,
162 Style: FONT_STYLE_SEMIBOLD,
163 },
164 sourceSansProSemiboldBase64)
165
166 FontEncodings.Set(
167 Font{
168 Family: SourceSansPro,
169 Style: FONT_STYLE_ITALIC,
170 },
171 sourceSansProItalicBase64)
172
173 FontEncodings.Set(
174 Font{
175 Family: SourceCodePro,
176 Style: FONT_STYLE_REGULAR,
177 },
178 sourceCodeProRegularBase64)
179
180 FontEncodings.Set(
181 Font{
182 Family: SourceCodePro,
183 Style: FONT_STYLE_BOLD,
184 },
185 sourceCodeProBoldBase64)
186
187 FontEncodings.Set(
188 Font{
189 Family: SourceCodePro,
190 Style: FONT_STYLE_SEMIBOLD,
191 },
192 sourceCodeProSemiboldBase64)
193
194 FontEncodings.Set(
195 Font{
196 Family: SourceCodePro,
197 Style: FONT_STYLE_ITALIC,
198 },
199 sourceCodeProItalicBase64)
200
201 FontEncodings.Set(
202 Font{
203 Family: HandDrawn,
204 Style: FONT_STYLE_REGULAR,
205 },
206 fuzzyBubblesRegularBase64)
207
208 FontEncodings.Set(
209 Font{
210 Family: HandDrawn,
211 Style: FONT_STYLE_ITALIC,
212
213 }, fuzzyBubblesRegularBase64)
214 FontEncodings.Set(
215 Font{
216 Family: HandDrawn,
217 Style: FONT_STYLE_BOLD,
218 }, fuzzyBubblesBoldBase64)
219 FontEncodings.Set(
220 Font{
221 Family: HandDrawn,
222 Style: FONT_STYLE_SEMIBOLD,
223
224 }, fuzzyBubblesBoldBase64)
225
226 FontEncodings.Range(func(k Font, v string) bool {
227 FontEncodings.Set(k, strings.TrimSuffix(v, "\n"))
228 return true
229 })
230
231 FontFaces = syncmap.New[Font, []byte]()
232
233 b, err := fontFacesFS.ReadFile("ttf/SourceSansPro-Regular.ttf")
234 if err != nil {
235 panic(err)
236 }
237 FontFaces.Set(Font{
238 Family: SourceSansPro,
239 Style: FONT_STYLE_REGULAR,
240 }, b)
241
242 b, err = fontFacesFS.ReadFile("ttf/SourceCodePro-Regular.ttf")
243 if err != nil {
244 panic(err)
245 }
246 FontFaces.Set(Font{
247 Family: SourceCodePro,
248 Style: FONT_STYLE_REGULAR,
249 }, b)
250
251 b, err = fontFacesFS.ReadFile("ttf/SourceCodePro-Bold.ttf")
252 if err != nil {
253 panic(err)
254 }
255 FontFaces.Set(Font{
256 Family: SourceCodePro,
257 Style: FONT_STYLE_BOLD,
258 }, b)
259
260 b, err = fontFacesFS.ReadFile("ttf/SourceCodePro-Semibold.ttf")
261 if err != nil {
262 panic(err)
263 }
264 FontFaces.Set(Font{
265 Family: SourceCodePro,
266 Style: FONT_STYLE_SEMIBOLD,
267 }, b)
268
269 b, err = fontFacesFS.ReadFile("ttf/SourceCodePro-Italic.ttf")
270 if err != nil {
271 panic(err)
272 }
273 FontFaces.Set(Font{
274 Family: SourceCodePro,
275 Style: FONT_STYLE_ITALIC,
276 }, b)
277
278 b, err = fontFacesFS.ReadFile("ttf/SourceSansPro-Bold.ttf")
279 if err != nil {
280 panic(err)
281 }
282 FontFaces.Set(Font{
283 Family: SourceSansPro,
284 Style: FONT_STYLE_BOLD,
285 }, b)
286
287 b, err = fontFacesFS.ReadFile("ttf/SourceSansPro-Semibold.ttf")
288 if err != nil {
289 panic(err)
290 }
291 FontFaces.Set(Font{
292 Family: SourceSansPro,
293 Style: FONT_STYLE_SEMIBOLD,
294 }, b)
295
296 b, err = fontFacesFS.ReadFile("ttf/SourceSansPro-Italic.ttf")
297 if err != nil {
298 panic(err)
299 }
300 FontFaces.Set(Font{
301 Family: SourceSansPro,
302 Style: FONT_STYLE_ITALIC,
303 }, b)
304
305 b, err = fontFacesFS.ReadFile("ttf/FuzzyBubbles-Regular.ttf")
306 if err != nil {
307 panic(err)
308 }
309 FontFaces.Set(Font{
310 Family: HandDrawn,
311 Style: FONT_STYLE_REGULAR,
312 }, b)
313 FontFaces.Set(Font{
314 Family: HandDrawn,
315 Style: FONT_STYLE_ITALIC,
316 }, b)
317
318 b, err = fontFacesFS.ReadFile("ttf/FuzzyBubbles-Bold.ttf")
319 if err != nil {
320 panic(err)
321 }
322 FontFaces.Set(Font{
323 Family: HandDrawn,
324 Style: FONT_STYLE_BOLD,
325 }, b)
326 FontFaces.Set(Font{
327 Family: HandDrawn,
328 Style: FONT_STYLE_SEMIBOLD,
329 }, b)
330 }
331
332 var D2_FONT_TO_FAMILY = map[string]FontFamily{
333 "default": SourceSansPro,
334 "mono": SourceCodePro,
335 }
336
337 func AddFontStyle(font Font, style FontStyle, ttf []byte) error {
338 FontFaces.Set(font, ttf)
339
340 woff, err := fontlib.Sfnt2Woff(ttf)
341 if err != nil {
342 return fmt.Errorf("failed to encode ttf to woff: %v", err)
343 }
344 encodedWoff := fmt.Sprintf("data:application/font-woff;base64,%v", base64.StdEncoding.EncodeToString(woff))
345 FontEncodings.Set(font, encodedWoff)
346
347 return nil
348 }
349
350 func AddFontFamily(name string, regularTTF, italicTTF, boldTTF, semiboldTTF []byte) (*FontFamily, error) {
351 FontFamiliesMu.Lock()
352 defer FontFamiliesMu.Unlock()
353 customFontFamily := FontFamily(name)
354
355 regularFont := Font{
356 Family: customFontFamily,
357 Style: FONT_STYLE_REGULAR,
358 }
359 if regularTTF != nil {
360 err := AddFontStyle(regularFont, FONT_STYLE_REGULAR, regularTTF)
361 if err != nil {
362 return nil, err
363 }
364 } else {
365 fallbackFont := Font{
366 Family: SourceSansPro,
367 Style: FONT_STYLE_REGULAR,
368 }
369 FontFaces.Set(regularFont, FontFaces.Get(fallbackFont))
370 FontEncodings.Set(regularFont, FontEncodings.Get(fallbackFont))
371 }
372
373 italicFont := Font{
374 Family: customFontFamily,
375 Style: FONT_STYLE_ITALIC,
376 }
377 if italicTTF != nil {
378 err := AddFontStyle(italicFont, FONT_STYLE_ITALIC, italicTTF)
379 if err != nil {
380 return nil, err
381 }
382 } else {
383 fallbackFont := Font{
384 Family: SourceSansPro,
385 Style: FONT_STYLE_ITALIC,
386 }
387 FontFaces.Set(italicFont, FontFaces.Get(fallbackFont))
388 FontEncodings.Set(italicFont, FontEncodings.Get(fallbackFont))
389 }
390
391 boldFont := Font{
392 Family: customFontFamily,
393 Style: FONT_STYLE_BOLD,
394 }
395 if boldTTF != nil {
396 err := AddFontStyle(boldFont, FONT_STYLE_BOLD, boldTTF)
397 if err != nil {
398 return nil, err
399 }
400 } else {
401 fallbackFont := Font{
402 Family: SourceSansPro,
403 Style: FONT_STYLE_BOLD,
404 }
405 FontFaces.Set(boldFont, FontFaces.Get(fallbackFont))
406 FontEncodings.Set(boldFont, FontEncodings.Get(fallbackFont))
407 }
408
409 semiboldFont := Font{
410 Family: customFontFamily,
411 Style: FONT_STYLE_SEMIBOLD,
412 }
413 if semiboldTTF != nil {
414 err := AddFontStyle(semiboldFont, FONT_STYLE_SEMIBOLD, semiboldTTF)
415 if err != nil {
416 return nil, err
417 }
418 } else {
419 fallbackFont := Font{
420 Family: SourceSansPro,
421 Style: FONT_STYLE_SEMIBOLD,
422 }
423 FontFaces.Set(semiboldFont, FontFaces.Get(fallbackFont))
424 FontEncodings.Set(semiboldFont, FontEncodings.Get(fallbackFont))
425 }
426
427 FontFamilies = append(FontFamilies, customFontFamily)
428
429 return &customFontFamily, nil
430 }
431
View as plain text