// d2fonts holds fonts for renderings

// TODO write a script to do this as part of CI // Currently using an online converter: https://dopiaza.org/tools/datauri/index.php
package d2fonts import ( fontlib ) type FontFamily string type FontStyle string type Font struct { Family FontFamily Style FontStyle Size int } func ( FontFamily) ( int, FontStyle) Font { return Font{ Family: , Style: , Size: , } } func ( Font) ( string) string { var string := make(map[rune]bool) for , := range { if , := []; ! { [] = true = + string() } } FontFamiliesMu.Lock() defer FontFamiliesMu.Unlock() := FontFaces.Get() := make([]byte, len()) copy(, ) = font.UTF8CutFont(, ) , := fontlib.Sfnt2Woff() if != nil { // If subset fails, return full encoding return FontEncodings.Get() } return fmt.Sprintf("data:application/font-woff;base64,%v", base64.StdEncoding.EncodeToString()) } const ( FONT_SIZE_XS = 13 FONT_SIZE_S = 14 FONT_SIZE_M = 16 FONT_SIZE_L = 20 FONT_SIZE_XL = 24 FONT_SIZE_XXL = 28 FONT_SIZE_XXXL = 32 FONT_STYLE_REGULAR FontStyle = "regular" FONT_STYLE_BOLD FontStyle = "bold" FONT_STYLE_SEMIBOLD FontStyle = "semibold" FONT_STYLE_ITALIC FontStyle = "italic" SourceSansPro FontFamily = "SourceSansPro" SourceCodePro FontFamily = "SourceCodePro" HandDrawn FontFamily = "HandDrawn" ) var FontSizes = []int{ FONT_SIZE_XS, FONT_SIZE_S, FONT_SIZE_M, FONT_SIZE_L, FONT_SIZE_XL, FONT_SIZE_XXL, FONT_SIZE_XXXL, } var FontStyles = []FontStyle{ FONT_STYLE_REGULAR, FONT_STYLE_BOLD, FONT_STYLE_SEMIBOLD, FONT_STYLE_ITALIC, } var FontFamilies = []FontFamily{ SourceSansPro, SourceCodePro, HandDrawn, } var FontFamiliesMu sync.Mutex //go:embed encoded/SourceSansPro-Regular.txt var sourceSansProRegularBase64 string //go:embed encoded/SourceSansPro-Bold.txt var sourceSansProBoldBase64 string //go:embed encoded/SourceSansPro-Semibold.txt var sourceSansProSemiboldBase64 string //go:embed encoded/SourceSansPro-Italic.txt var sourceSansProItalicBase64 string //go:embed encoded/SourceCodePro-Regular.txt var sourceCodeProRegularBase64 string //go:embed encoded/SourceCodePro-Bold.txt var sourceCodeProBoldBase64 string //go:embed encoded/SourceCodePro-Semibold.txt var sourceCodeProSemiboldBase64 string //go:embed encoded/SourceCodePro-Italic.txt var sourceCodeProItalicBase64 string //go:embed encoded/FuzzyBubbles-Regular.txt var fuzzyBubblesRegularBase64 string //go:embed encoded/FuzzyBubbles-Bold.txt var fuzzyBubblesBoldBase64 string //go:embed ttf/* var fontFacesFS embed.FS var FontEncodings syncmap.SyncMap[Font, string] var FontFaces syncmap.SyncMap[Font, []byte] func init() { FontEncodings = syncmap.New[Font, string]() FontEncodings.Set( Font{ Family: SourceSansPro, Style: FONT_STYLE_REGULAR, }, sourceSansProRegularBase64) FontEncodings.Set( Font{ Family: SourceSansPro, Style: FONT_STYLE_BOLD, }, sourceSansProBoldBase64) FontEncodings.Set( Font{ Family: SourceSansPro, Style: FONT_STYLE_SEMIBOLD, }, sourceSansProSemiboldBase64) FontEncodings.Set( Font{ Family: SourceSansPro, Style: FONT_STYLE_ITALIC, }, sourceSansProItalicBase64) FontEncodings.Set( Font{ Family: SourceCodePro, Style: FONT_STYLE_REGULAR, }, sourceCodeProRegularBase64) FontEncodings.Set( Font{ Family: SourceCodePro, Style: FONT_STYLE_BOLD, }, sourceCodeProBoldBase64) FontEncodings.Set( Font{ Family: SourceCodePro, Style: FONT_STYLE_SEMIBOLD, }, sourceCodeProSemiboldBase64) FontEncodings.Set( Font{ Family: SourceCodePro, Style: FONT_STYLE_ITALIC, }, sourceCodeProItalicBase64) FontEncodings.Set( Font{ Family: HandDrawn, Style: FONT_STYLE_REGULAR, }, fuzzyBubblesRegularBase64) FontEncodings.Set( Font{ Family: HandDrawn, Style: FONT_STYLE_ITALIC, // This font has no italic, so just reuse regular }, fuzzyBubblesRegularBase64) FontEncodings.Set( Font{ Family: HandDrawn, Style: FONT_STYLE_BOLD, }, fuzzyBubblesBoldBase64) FontEncodings.Set( Font{ Family: HandDrawn, Style: FONT_STYLE_SEMIBOLD, // This font has no semibold, so just reuse bold }, fuzzyBubblesBoldBase64) FontEncodings.Range(func( Font, string) bool { FontEncodings.Set(, strings.TrimSuffix(, "\n")) return true }) FontFaces = syncmap.New[Font, []byte]() , := fontFacesFS.ReadFile("ttf/SourceSansPro-Regular.ttf") if != nil { panic() } FontFaces.Set(Font{ Family: SourceSansPro, Style: FONT_STYLE_REGULAR, }, ) , = fontFacesFS.ReadFile("ttf/SourceCodePro-Regular.ttf") if != nil { panic() } FontFaces.Set(Font{ Family: SourceCodePro, Style: FONT_STYLE_REGULAR, }, ) , = fontFacesFS.ReadFile("ttf/SourceCodePro-Bold.ttf") if != nil { panic() } FontFaces.Set(Font{ Family: SourceCodePro, Style: FONT_STYLE_BOLD, }, ) , = fontFacesFS.ReadFile("ttf/SourceCodePro-Semibold.ttf") if != nil { panic() } FontFaces.Set(Font{ Family: SourceCodePro, Style: FONT_STYLE_SEMIBOLD, }, ) , = fontFacesFS.ReadFile("ttf/SourceCodePro-Italic.ttf") if != nil { panic() } FontFaces.Set(Font{ Family: SourceCodePro, Style: FONT_STYLE_ITALIC, }, ) , = fontFacesFS.ReadFile("ttf/SourceSansPro-Bold.ttf") if != nil { panic() } FontFaces.Set(Font{ Family: SourceSansPro, Style: FONT_STYLE_BOLD, }, ) , = fontFacesFS.ReadFile("ttf/SourceSansPro-Semibold.ttf") if != nil { panic() } FontFaces.Set(Font{ Family: SourceSansPro, Style: FONT_STYLE_SEMIBOLD, }, ) , = fontFacesFS.ReadFile("ttf/SourceSansPro-Italic.ttf") if != nil { panic() } FontFaces.Set(Font{ Family: SourceSansPro, Style: FONT_STYLE_ITALIC, }, ) , = fontFacesFS.ReadFile("ttf/FuzzyBubbles-Regular.ttf") if != nil { panic() } FontFaces.Set(Font{ Family: HandDrawn, Style: FONT_STYLE_REGULAR, }, ) FontFaces.Set(Font{ Family: HandDrawn, Style: FONT_STYLE_ITALIC, }, ) , = fontFacesFS.ReadFile("ttf/FuzzyBubbles-Bold.ttf") if != nil { panic() } FontFaces.Set(Font{ Family: HandDrawn, Style: FONT_STYLE_BOLD, }, ) FontFaces.Set(Font{ Family: HandDrawn, Style: FONT_STYLE_SEMIBOLD, }, ) } var D2_FONT_TO_FAMILY = map[string]FontFamily{ "default": SourceSansPro, "mono": SourceCodePro, } func ( Font, FontStyle, []byte) error { FontFaces.Set(, ) , := fontlib.Sfnt2Woff() if != nil { return fmt.Errorf("failed to encode ttf to woff: %v", ) } := fmt.Sprintf("data:application/font-woff;base64,%v", base64.StdEncoding.EncodeToString()) FontEncodings.Set(, ) return nil } func ( string, , , , []byte) (*FontFamily, error) { FontFamiliesMu.Lock() defer FontFamiliesMu.Unlock() := FontFamily() := Font{ Family: , Style: FONT_STYLE_REGULAR, } if != nil { := AddFontStyle(, FONT_STYLE_REGULAR, ) if != nil { return nil, } } else { := Font{ Family: SourceSansPro, Style: FONT_STYLE_REGULAR, } FontFaces.Set(, FontFaces.Get()) FontEncodings.Set(, FontEncodings.Get()) } := Font{ Family: , Style: FONT_STYLE_ITALIC, } if != nil { := AddFontStyle(, FONT_STYLE_ITALIC, ) if != nil { return nil, } } else { := Font{ Family: SourceSansPro, Style: FONT_STYLE_ITALIC, } FontFaces.Set(, FontFaces.Get()) FontEncodings.Set(, FontEncodings.Get()) } := Font{ Family: , Style: FONT_STYLE_BOLD, } if != nil { := AddFontStyle(, FONT_STYLE_BOLD, ) if != nil { return nil, } } else { := Font{ Family: SourceSansPro, Style: FONT_STYLE_BOLD, } FontFaces.Set(, FontFaces.Get()) FontEncodings.Set(, FontEncodings.Get()) } := Font{ Family: , Style: FONT_STYLE_SEMIBOLD, } if != nil { := AddFontStyle(, FONT_STYLE_SEMIBOLD, ) if != nil { return nil, } } else { := Font{ Family: SourceSansPro, Style: FONT_STYLE_SEMIBOLD, } FontFaces.Set(, FontFaces.Get()) FontEncodings.Set(, FontEncodings.Get()) } FontFamilies = append(FontFamilies, ) return &, nil }