Merge pull request #1683 from gavin-ts/fontencoding-syncmap

fix d2fonts race condition in TestCLI_E2E
This commit is contained in:
gavin-ts 2023-10-30 17:41:09 -07:00 committed by GitHub
commit df1d49f6be
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
6 changed files with 153 additions and 77 deletions

View file

@ -13,6 +13,7 @@ import (
"oss.terrastruct.com/d2/lib/font" "oss.terrastruct.com/d2/lib/font"
fontlib "oss.terrastruct.com/d2/lib/font" fontlib "oss.terrastruct.com/d2/lib/font"
"oss.terrastruct.com/d2/lib/syncmap"
) )
type FontFamily string type FontFamily string
@ -44,14 +45,15 @@ func (f Font) GetEncodedSubset(corpus string) string {
FontFamiliesMu.Lock() FontFamiliesMu.Lock()
defer FontFamiliesMu.Unlock() defer FontFamiliesMu.Unlock()
fontBuf := make([]byte, len(FontFaces[f])) face := FontFaces.Get(f)
copy(fontBuf, FontFaces[f]) fontBuf := make([]byte, len(face))
copy(fontBuf, face)
fontBuf = font.UTF8CutFont(fontBuf, uniqueChars) fontBuf = font.UTF8CutFont(fontBuf, uniqueChars)
fontBuf, err := fontlib.Sfnt2Woff(fontBuf) fontBuf, err := fontlib.Sfnt2Woff(fontBuf)
if err != nil { if err != nil {
// If subset fails, return full encoding // If subset fails, return full encoding
return FontEncodings[f] return FontEncodings.Get(f)
} }
return fmt.Sprintf("data:application/font-woff;base64,%v", base64.StdEncoding.EncodeToString(fontBuf)) return fmt.Sprintf("data:application/font-woff;base64,%v", base64.StdEncoding.EncodeToString(fontBuf))
@ -134,165 +136,197 @@ var fuzzyBubblesBoldBase64 string
//go:embed ttf/* //go:embed ttf/*
var fontFacesFS embed.FS var fontFacesFS embed.FS
var FontEncodings map[Font]string var FontEncodings syncmap.SyncMap[Font, string]
var FontFaces map[Font][]byte var FontFaces syncmap.SyncMap[Font, []byte]
func init() { func init() {
FontEncodings = map[Font]string{ FontEncodings = syncmap.New[Font, string]()
{
FontEncodings.Set(
Font{
Family: SourceSansPro, Family: SourceSansPro,
Style: FONT_STYLE_REGULAR, Style: FONT_STYLE_REGULAR,
}: sourceSansProRegularBase64, },
{ sourceSansProRegularBase64)
FontEncodings.Set(
Font{
Family: SourceSansPro, Family: SourceSansPro,
Style: FONT_STYLE_BOLD, Style: FONT_STYLE_BOLD,
}: sourceSansProBoldBase64, },
{ sourceSansProBoldBase64)
FontEncodings.Set(
Font{
Family: SourceSansPro, Family: SourceSansPro,
Style: FONT_STYLE_SEMIBOLD, Style: FONT_STYLE_SEMIBOLD,
}: sourceSansProSemiboldBase64, },
{ sourceSansProSemiboldBase64)
FontEncodings.Set(
Font{
Family: SourceSansPro, Family: SourceSansPro,
Style: FONT_STYLE_ITALIC, Style: FONT_STYLE_ITALIC,
}: sourceSansProItalicBase64, },
{ sourceSansProItalicBase64)
FontEncodings.Set(
Font{
Family: SourceCodePro, Family: SourceCodePro,
Style: FONT_STYLE_REGULAR, Style: FONT_STYLE_REGULAR,
}: sourceCodeProRegularBase64, },
{ sourceCodeProRegularBase64)
FontEncodings.Set(
Font{
Family: SourceCodePro, Family: SourceCodePro,
Style: FONT_STYLE_BOLD, Style: FONT_STYLE_BOLD,
}: sourceCodeProBoldBase64, },
{ sourceCodeProBoldBase64)
FontEncodings.Set(
Font{
Family: SourceCodePro, Family: SourceCodePro,
Style: FONT_STYLE_SEMIBOLD, Style: FONT_STYLE_SEMIBOLD,
}: sourceCodeProSemiboldBase64, },
{ sourceCodeProSemiboldBase64)
FontEncodings.Set(
Font{
Family: SourceCodePro, Family: SourceCodePro,
Style: FONT_STYLE_ITALIC, Style: FONT_STYLE_ITALIC,
}: sourceCodeProItalicBase64, },
{ sourceCodeProItalicBase64)
FontEncodings.Set(
Font{
Family: HandDrawn, Family: HandDrawn,
Style: FONT_STYLE_REGULAR, Style: FONT_STYLE_REGULAR,
}: fuzzyBubblesRegularBase64, },
{ fuzzyBubblesRegularBase64)
FontEncodings.Set(
Font{
Family: HandDrawn, Family: HandDrawn,
Style: FONT_STYLE_ITALIC, Style: FONT_STYLE_ITALIC,
// This font has no italic, so just reuse regular // This font has no italic, so just reuse regular
}: fuzzyBubblesRegularBase64, }, fuzzyBubblesRegularBase64)
{ FontEncodings.Set(
Font{
Family: HandDrawn, Family: HandDrawn,
Style: FONT_STYLE_BOLD, Style: FONT_STYLE_BOLD,
}: fuzzyBubblesBoldBase64, }, fuzzyBubblesBoldBase64)
{ FontEncodings.Set(
Font{
Family: HandDrawn, Family: HandDrawn,
Style: FONT_STYLE_SEMIBOLD, Style: FONT_STYLE_SEMIBOLD,
// This font has no semibold, so just reuse bold // This font has no semibold, so just reuse bold
}: fuzzyBubblesBoldBase64, }, fuzzyBubblesBoldBase64)
}
for k, v := range FontEncodings { FontEncodings.Range(func(k Font, v string) bool {
FontEncodings[k] = strings.TrimSuffix(v, "\n") FontEncodings.Set(k, strings.TrimSuffix(v, "\n"))
} return true
})
FontFaces = syncmap.New[Font, []byte]()
FontFaces = map[Font][]byte{}
b, err := fontFacesFS.ReadFile("ttf/SourceSansPro-Regular.ttf") b, err := fontFacesFS.ReadFile("ttf/SourceSansPro-Regular.ttf")
if err != nil { if err != nil {
panic(err) panic(err)
} }
FontFaces[Font{ FontFaces.Set(Font{
Family: SourceSansPro, Family: SourceSansPro,
Style: FONT_STYLE_REGULAR, Style: FONT_STYLE_REGULAR,
}] = b }, b)
b, err = fontFacesFS.ReadFile("ttf/SourceCodePro-Regular.ttf") b, err = fontFacesFS.ReadFile("ttf/SourceCodePro-Regular.ttf")
if err != nil { if err != nil {
panic(err) panic(err)
} }
FontFaces[Font{ FontFaces.Set(Font{
Family: SourceCodePro, Family: SourceCodePro,
Style: FONT_STYLE_REGULAR, Style: FONT_STYLE_REGULAR,
}] = b }, b)
b, err = fontFacesFS.ReadFile("ttf/SourceCodePro-Bold.ttf") b, err = fontFacesFS.ReadFile("ttf/SourceCodePro-Bold.ttf")
if err != nil { if err != nil {
panic(err) panic(err)
} }
FontFaces[Font{ FontFaces.Set(Font{
Family: SourceCodePro, Family: SourceCodePro,
Style: FONT_STYLE_BOLD, Style: FONT_STYLE_BOLD,
}] = b }, b)
b, err = fontFacesFS.ReadFile("ttf/SourceCodePro-Semibold.ttf") b, err = fontFacesFS.ReadFile("ttf/SourceCodePro-Semibold.ttf")
if err != nil { if err != nil {
panic(err) panic(err)
} }
FontFaces[Font{ FontFaces.Set(Font{
Family: SourceCodePro, Family: SourceCodePro,
Style: FONT_STYLE_SEMIBOLD, Style: FONT_STYLE_SEMIBOLD,
}] = b }, b)
b, err = fontFacesFS.ReadFile("ttf/SourceCodePro-Italic.ttf") b, err = fontFacesFS.ReadFile("ttf/SourceCodePro-Italic.ttf")
if err != nil { if err != nil {
panic(err) panic(err)
} }
FontFaces[Font{ FontFaces.Set(Font{
Family: SourceCodePro, Family: SourceCodePro,
Style: FONT_STYLE_ITALIC, Style: FONT_STYLE_ITALIC,
}] = b }, b)
b, err = fontFacesFS.ReadFile("ttf/SourceSansPro-Bold.ttf") b, err = fontFacesFS.ReadFile("ttf/SourceSansPro-Bold.ttf")
if err != nil { if err != nil {
panic(err) panic(err)
} }
FontFaces[Font{ FontFaces.Set(Font{
Family: SourceSansPro, Family: SourceSansPro,
Style: FONT_STYLE_BOLD, Style: FONT_STYLE_BOLD,
}] = b }, b)
b, err = fontFacesFS.ReadFile("ttf/SourceSansPro-Semibold.ttf") b, err = fontFacesFS.ReadFile("ttf/SourceSansPro-Semibold.ttf")
if err != nil { if err != nil {
panic(err) panic(err)
} }
FontFaces[Font{ FontFaces.Set(Font{
Family: SourceSansPro, Family: SourceSansPro,
Style: FONT_STYLE_SEMIBOLD, Style: FONT_STYLE_SEMIBOLD,
}] = b }, b)
b, err = fontFacesFS.ReadFile("ttf/SourceSansPro-Italic.ttf") b, err = fontFacesFS.ReadFile("ttf/SourceSansPro-Italic.ttf")
if err != nil { if err != nil {
panic(err) panic(err)
} }
FontFaces[Font{ FontFaces.Set(Font{
Family: SourceSansPro, Family: SourceSansPro,
Style: FONT_STYLE_ITALIC, Style: FONT_STYLE_ITALIC,
}] = b }, b)
b, err = fontFacesFS.ReadFile("ttf/FuzzyBubbles-Regular.ttf") b, err = fontFacesFS.ReadFile("ttf/FuzzyBubbles-Regular.ttf")
if err != nil { if err != nil {
panic(err) panic(err)
} }
FontFaces[Font{ FontFaces.Set(Font{
Family: HandDrawn, Family: HandDrawn,
Style: FONT_STYLE_REGULAR, Style: FONT_STYLE_REGULAR,
}] = b }, b)
FontFaces[Font{ FontFaces.Set(Font{
Family: HandDrawn, Family: HandDrawn,
Style: FONT_STYLE_ITALIC, Style: FONT_STYLE_ITALIC,
}] = b }, b)
b, err = fontFacesFS.ReadFile("ttf/FuzzyBubbles-Bold.ttf") b, err = fontFacesFS.ReadFile("ttf/FuzzyBubbles-Bold.ttf")
if err != nil { if err != nil {
panic(err) panic(err)
} }
FontFaces[Font{ FontFaces.Set(Font{
Family: HandDrawn, Family: HandDrawn,
Style: FONT_STYLE_BOLD, Style: FONT_STYLE_BOLD,
}] = b }, b)
FontFaces[Font{ FontFaces.Set(Font{
Family: HandDrawn, Family: HandDrawn,
Style: FONT_STYLE_SEMIBOLD, Style: FONT_STYLE_SEMIBOLD,
}] = b }, b)
} }
var D2_FONT_TO_FAMILY = map[string]FontFamily{ var D2_FONT_TO_FAMILY = map[string]FontFamily{
@ -301,14 +335,14 @@ var D2_FONT_TO_FAMILY = map[string]FontFamily{
} }
func AddFontStyle(font Font, style FontStyle, ttf []byte) error { func AddFontStyle(font Font, style FontStyle, ttf []byte) error {
FontFaces[font] = ttf FontFaces.Set(font, ttf)
woff, err := fontlib.Sfnt2Woff(ttf) woff, err := fontlib.Sfnt2Woff(ttf)
if err != nil { if err != nil {
return fmt.Errorf("failed to encode ttf to woff: %v", err) return fmt.Errorf("failed to encode ttf to woff: %v", err)
} }
encodedWoff := fmt.Sprintf("data:application/font-woff;base64,%v", base64.StdEncoding.EncodeToString(woff)) encodedWoff := fmt.Sprintf("data:application/font-woff;base64,%v", base64.StdEncoding.EncodeToString(woff))
FontEncodings[font] = encodedWoff FontEncodings.Set(font, encodedWoff)
return nil return nil
} }
@ -332,8 +366,8 @@ func AddFontFamily(name string, regularTTF, italicTTF, boldTTF, semiboldTTF []by
Family: SourceSansPro, Family: SourceSansPro,
Style: FONT_STYLE_REGULAR, Style: FONT_STYLE_REGULAR,
} }
FontFaces[regularFont] = FontFaces[fallbackFont] FontFaces.Set(regularFont, FontFaces.Get(fallbackFont))
FontEncodings[regularFont] = FontEncodings[fallbackFont] FontEncodings.Set(regularFont, FontEncodings.Get(fallbackFont))
} }
italicFont := Font{ italicFont := Font{
@ -350,8 +384,8 @@ func AddFontFamily(name string, regularTTF, italicTTF, boldTTF, semiboldTTF []by
Family: SourceSansPro, Family: SourceSansPro,
Style: FONT_STYLE_ITALIC, Style: FONT_STYLE_ITALIC,
} }
FontFaces[italicFont] = FontFaces[fallbackFont] FontFaces.Set(italicFont, FontFaces.Get(fallbackFont))
FontEncodings[italicFont] = FontEncodings[fallbackFont] FontEncodings.Set(italicFont, FontEncodings.Get(fallbackFont))
} }
boldFont := Font{ boldFont := Font{
@ -368,8 +402,8 @@ func AddFontFamily(name string, regularTTF, italicTTF, boldTTF, semiboldTTF []by
Family: SourceSansPro, Family: SourceSansPro,
Style: FONT_STYLE_BOLD, Style: FONT_STYLE_BOLD,
} }
FontFaces[boldFont] = FontFaces[fallbackFont] FontFaces.Set(boldFont, FontFaces.Get(fallbackFont))
FontEncodings[boldFont] = FontEncodings[fallbackFont] FontEncodings.Set(boldFont, FontEncodings.Get(fallbackFont))
} }
semiboldFont := Font{ semiboldFont := Font{
@ -386,8 +420,8 @@ func AddFontFamily(name string, regularTTF, italicTTF, boldTTF, semiboldTTF []by
Family: SourceSansPro, Family: SourceSansPro,
Style: FONT_STYLE_SEMIBOLD, Style: FONT_STYLE_SEMIBOLD,
} }
FontFaces[semiboldFont] = FontFaces[fallbackFont] FontFaces.Set(semiboldFont, FontFaces.Get(fallbackFont))
FontEncodings[semiboldFont] = FontEncodings[fallbackFont] FontEncodings.Set(semiboldFont, FontEncodings.Get(fallbackFont))
} }
FontFamilies = append(FontFamilies, customFontFamily) FontFamilies = append(FontFamilies, customFontFamily)

View file

@ -14,8 +14,9 @@ func TestCutFont(t *testing.T) {
Family: SourceCodePro, Family: SourceCodePro,
Style: FONT_STYLE_BOLD, Style: FONT_STYLE_BOLD,
} }
fontBuf := make([]byte, len(FontFaces[f])) face := FontFaces.Get(f)
copy(fontBuf, FontFaces[f]) fontBuf := make([]byte, len(face))
copy(fontBuf, face)
fontBuf = font.UTF8CutFont(fontBuf, " 1") fontBuf = font.UTF8CutFont(fontBuf, " 1")
err := diff.Testdata(filepath.Join("testdata", "d2fonts", "cut"), ".txt", fontBuf) err := diff.Testdata(filepath.Join("testdata", "d2fonts", "cut"), ".txt", fontBuf)
assert.Success(t, err) assert.Success(t, err)

View file

@ -129,7 +129,7 @@ func Append(diagram *d2target.Diagram, ruler *textmeasure.Ruler, in []byte) []by
font-family: font-regular; font-family: font-regular;
src: url("%s"); src: url("%s");
} }
]]></style>`, d2fonts.FontEncodings[d2fonts.SourceSansPro.Font(0, d2fonts.FONT_STYLE_REGULAR)]) ]]></style>`, d2fonts.FontEncodings.Get(d2fonts.SourceSansPro.Font(0, d2fonts.FONT_STYLE_REGULAR)))
} }
if !strings.Contains(svg, `font-family: "font-bold"`) { if !strings.Contains(svg, `font-family: "font-bold"`) {
appendix += fmt.Sprintf(`<style type="text/css"><![CDATA[ appendix += fmt.Sprintf(`<style type="text/css"><![CDATA[
@ -140,7 +140,7 @@ func Append(diagram *d2target.Diagram, ruler *textmeasure.Ruler, in []byte) []by
font-family: font-bold; font-family: font-bold;
src: url("%s"); src: url("%s");
} }
]]></style>`, d2fonts.FontEncodings[d2fonts.SourceSansPro.Font(0, d2fonts.FONT_STYLE_BOLD)]) ]]></style>`, d2fonts.FontEncodings.Get(d2fonts.SourceSansPro.Font(0, d2fonts.FONT_STYLE_BOLD)))
} }
closingIndex := strings.LastIndex(svg, "</svg></svg>") closingIndex := strings.LastIndex(svg, "</svg></svg>")

View file

@ -31,8 +31,8 @@ func Init() *GoFPDF {
UnitStr: "pt", UnitStr: "pt",
}) })
newGofPDF.AddUTF8FontFromBytes("source", "", d2fonts.FontFaces[d2fonts.SourceSansPro.Font(0, d2fonts.FONT_STYLE_REGULAR)]) newGofPDF.AddUTF8FontFromBytes("source", "", d2fonts.FontFaces.Get(d2fonts.SourceSansPro.Font(0, d2fonts.FONT_STYLE_REGULAR)))
newGofPDF.AddUTF8FontFromBytes("source", "B", d2fonts.FontFaces[d2fonts.SourceSansPro.Font(0, d2fonts.FONT_STYLE_BOLD)]) newGofPDF.AddUTF8FontFromBytes("source", "B", d2fonts.FontFaces.Get(d2fonts.SourceSansPro.Font(0, d2fonts.FONT_STYLE_BOLD)))
newGofPDF.SetAutoPageBreak(false, 0) newGofPDF.SetAutoPageBreak(false, 0)
newGofPDF.SetLineWidth(2) newGofPDF.SetLineWidth(2)
newGofPDF.SetMargins(0, 0, 0) newGofPDF.SetMargins(0, 0, 0)

40
lib/syncmap/syncmap.go Normal file
View file

@ -0,0 +1,40 @@
package syncmap
import "sync"
type SyncMap[K comparable, V any] struct {
_map *sync.Map
}
func New[K comparable, V any]() SyncMap[K, V] {
return SyncMap[K, V]{
_map: &sync.Map{},
}
}
func (sm SyncMap[K, V]) Set(key K, value V) {
sm._map.Store(key, value)
}
func (sm SyncMap[K, V]) Lookup(key K) (value V, ok bool) {
v, has := sm._map.Load(key)
if !has {
return value, false
}
return v.(V), true
}
func (sm SyncMap[K, V]) Get(key K) (value V) {
v, _ := sm.Lookup(key)
return v
}
func (sm SyncMap[K, V]) Delete(key K) {
sm._map.Delete(key)
}
func (sm SyncMap[K, V]) Range(f func(key K, value V) bool) {
sm._map.Range(func(k, v any) bool {
return f(k.(K), v.(V))
})
}

View file

@ -126,11 +126,12 @@ func NewRuler() (*Ruler, error) {
Style: fontStyle, Style: fontStyle,
} }
// Note: FontFaces lookup is size-agnostic // Note: FontFaces lookup is size-agnostic
if _, ok := d2fonts.FontFaces[font]; !ok { face, has := d2fonts.FontFaces.Lookup(font)
if !has {
continue continue
} }
if _, loaded := r.ttfs[font]; !loaded { if _, loaded := r.ttfs[font]; !loaded {
ttf, err := truetype.Parse(d2fonts.FontFaces[font]) ttf, err := truetype.Parse(face)
if err != nil { if err != nil {
return nil, err return nil, err
} }