Merge pull request #1089 from berniexie/765/custom-fonts
fonts: Subsetting fonts
|
|
@ -2,6 +2,7 @@
|
||||||
|
|
||||||
- `--animate-interval` can be passed as a flag to animate multi-board diagrams. See [docs](https://d2lang.com/todo). [#1088](https://github.com/terrastruct/d2/pull/1088)
|
- `--animate-interval` can be passed as a flag to animate multi-board diagrams. See [docs](https://d2lang.com/todo). [#1088](https://github.com/terrastruct/d2/pull/1088)
|
||||||
- `paper` is available as a `fill-pattern` option [#1070](https://github.com/terrastruct/d2/pull/1070)
|
- `paper` is available as a `fill-pattern` option [#1070](https://github.com/terrastruct/d2/pull/1070)
|
||||||
|
- fonts are now subsetted to reduce svg file size [#1089](https://github.com/terrastruct/d2/pull/1089)
|
||||||
|
|
||||||
#### Improvements 🧹
|
#### Improvements 🧹
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -74,7 +74,7 @@ func Wrap(rootDiagram *d2target.Diagram, svgs [][]byte, renderOpts d2svg.RenderO
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
d2svg.EmbedFonts(buf, diagramHash, svgsStr, rootDiagram.FontFamily)
|
d2svg.EmbedFonts(buf, diagramHash, svgsStr, rootDiagram.FontFamily, rootDiagram.GetNestedCorpus())
|
||||||
|
|
||||||
themeStylesheet, err := d2svg.ThemeCSS(diagramHash, renderOpts.ThemeID, renderOpts.DarkThemeID)
|
themeStylesheet, err := d2svg.ThemeCSS(diagramHash, renderOpts.ThemeID, renderOpts.DarkThemeID)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
|
|
||||||
|
|
@ -6,7 +6,13 @@ package d2fonts
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"embed"
|
"embed"
|
||||||
|
"encoding/base64"
|
||||||
|
"fmt"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
|
"github.com/jung-kurt/gofpdf"
|
||||||
|
|
||||||
|
fontlib "oss.terrastruct.com/d2/lib/font"
|
||||||
)
|
)
|
||||||
|
|
||||||
type FontFamily string
|
type FontFamily string
|
||||||
|
|
@ -26,6 +32,29 @@ func (f FontFamily) Font(size int, style FontStyle) Font {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (f Font) GetEncodedSubset(corpus string) string {
|
||||||
|
var uniqueChars string
|
||||||
|
uniqueMap := make(map[rune]bool)
|
||||||
|
for _, char := range corpus {
|
||||||
|
if _, exists := uniqueMap[char]; !exists {
|
||||||
|
uniqueMap[char] = true
|
||||||
|
uniqueChars = uniqueChars + string(char)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fontBuf := make([]byte, len(FontFaces[f]))
|
||||||
|
copy(fontBuf, FontFaces[f])
|
||||||
|
fontBuf = gofpdf.UTF8CutFont(fontBuf, uniqueChars)
|
||||||
|
|
||||||
|
fontBuf, err := fontlib.Sfnt2Woff(fontBuf)
|
||||||
|
if err != nil {
|
||||||
|
// If subset fails, return full encoding
|
||||||
|
return FontEncodings[f]
|
||||||
|
}
|
||||||
|
|
||||||
|
return fmt.Sprintf("data:application/font-woff;base64,%v", base64.StdEncoding.EncodeToString(fontBuf))
|
||||||
|
}
|
||||||
|
|
||||||
const (
|
const (
|
||||||
FONT_SIZE_XS = 13
|
FONT_SIZE_XS = 13
|
||||||
FONT_SIZE_S = 14
|
FONT_SIZE_S = 14
|
||||||
|
|
|
||||||
23
d2renderers/d2fonts/d2fonts_test.go
Normal file
|
|
@ -0,0 +1,23 @@
|
||||||
|
package d2fonts
|
||||||
|
|
||||||
|
import (
|
||||||
|
"path/filepath"
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"github.com/jung-kurt/gofpdf"
|
||||||
|
|
||||||
|
"oss.terrastruct.com/util-go/assert"
|
||||||
|
"oss.terrastruct.com/util-go/diff"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestCutFont(t *testing.T) {
|
||||||
|
f := Font{
|
||||||
|
Family: SourceCodePro,
|
||||||
|
Style: FONT_STYLE_BOLD,
|
||||||
|
}
|
||||||
|
fontBuf := make([]byte, len(FontFaces[f]))
|
||||||
|
copy(fontBuf, FontFaces[f])
|
||||||
|
fontBuf = gofpdf.UTF8CutFont(fontBuf, " 1")
|
||||||
|
err := diff.Testdata(filepath.Join("testdata", "d2fonts", "cut"), ".txt", fontBuf)
|
||||||
|
assert.Success(t, err)
|
||||||
|
}
|
||||||
BIN
d2renderers/d2fonts/testdata/d2fonts/cut.exp.txt
vendored
Normal file
|
Before Width: | Height: | Size: 299 KiB After Width: | Height: | Size: 127 KiB |
|
Before Width: | Height: | Size: 290 KiB After Width: | Height: | Size: 118 KiB |
|
Before Width: | Height: | Size: 285 KiB After Width: | Height: | Size: 67 KiB |
|
Before Width: | Height: | Size: 276 KiB After Width: | Height: | Size: 58 KiB |
|
Before Width: | Height: | Size: 335 KiB After Width: | Height: | Size: 124 KiB |
|
Before Width: | Height: | Size: 326 KiB After Width: | Height: | Size: 115 KiB |
|
Before Width: | Height: | Size: 228 KiB After Width: | Height: | Size: 52 KiB |
|
Before Width: | Height: | Size: 219 KiB After Width: | Height: | Size: 42 KiB |
|
Before Width: | Height: | Size: 280 KiB After Width: | Height: | Size: 62 KiB |
|
Before Width: | Height: | Size: 271 KiB After Width: | Height: | Size: 53 KiB |
|
Before Width: | Height: | Size: 228 KiB After Width: | Height: | Size: 53 KiB |
|
Before Width: | Height: | Size: 285 KiB After Width: | Height: | Size: 71 KiB |
|
Before Width: | Height: | Size: 219 KiB After Width: | Height: | Size: 44 KiB |
|
Before Width: | Height: | Size: 278 KiB After Width: | Height: | Size: 58 KiB |
|
Before Width: | Height: | Size: 269 KiB After Width: | Height: | Size: 49 KiB |
|
Before Width: | Height: | Size: 334 KiB After Width: | Height: | Size: 160 KiB |
|
Before Width: | Height: | Size: 325 KiB After Width: | Height: | Size: 150 KiB |
|
Before Width: | Height: | Size: 228 KiB After Width: | Height: | Size: 51 KiB |
|
Before Width: | Height: | Size: 333 KiB After Width: | Height: | Size: 161 KiB |
|
Before Width: | Height: | Size: 336 KiB After Width: | Height: | Size: 165 KiB |
|
Before Width: | Height: | Size: 350 KiB After Width: | Height: | Size: 108 KiB |
|
Before Width: | Height: | Size: 290 KiB After Width: | Height: | Size: 74 KiB |
|
Before Width: | Height: | Size: 235 KiB After Width: | Height: | Size: 58 KiB |
|
Before Width: | Height: | Size: 341 KiB After Width: | Height: | Size: 89 KiB |
|
Before Width: | Height: | Size: 332 KiB After Width: | Height: | Size: 80 KiB |
|
Before Width: | Height: | Size: 230 KiB After Width: | Height: | Size: 56 KiB |
|
Before Width: | Height: | Size: 741 KiB After Width: | Height: | Size: 493 KiB |
|
Before Width: | Height: | Size: 307 KiB After Width: | Height: | Size: 100 KiB |
|
Before Width: | Height: | Size: 116 KiB After Width: | Height: | Size: 74 KiB |
|
Before Width: | Height: | Size: 107 KiB After Width: | Height: | Size: 65 KiB |
|
Before Width: | Height: | Size: 355 KiB After Width: | Height: | Size: 108 KiB |
|
Before Width: | Height: | Size: 420 KiB After Width: | Height: | Size: 180 KiB |
|
Before Width: | Height: | Size: 420 KiB After Width: | Height: | Size: 180 KiB |
|
Before Width: | Height: | Size: 1.1 MiB After Width: | Height: | Size: 676 KiB |
|
Before Width: | Height: | Size: 978 KiB After Width: | Height: | Size: 661 KiB |
|
Before Width: | Height: | Size: 978 KiB After Width: | Height: | Size: 661 KiB |
|
Before Width: | Height: | Size: 978 KiB After Width: | Height: | Size: 660 KiB |
|
Before Width: | Height: | Size: 978 KiB After Width: | Height: | Size: 660 KiB |
|
|
@ -77,6 +77,7 @@ type RenderOpts struct {
|
||||||
Center bool
|
Center bool
|
||||||
ThemeID int64
|
ThemeID int64
|
||||||
DarkThemeID *int64
|
DarkThemeID *int64
|
||||||
|
Font string
|
||||||
// disables the fit to screen behavior and ensures the exported svg has the exact dimensions
|
// disables the fit to screen behavior and ensures the exported svg has the exact dimensions
|
||||||
SetDimensions bool
|
SetDimensions bool
|
||||||
|
|
||||||
|
|
@ -1386,7 +1387,7 @@ func RenderText(text string, x, height float64) string {
|
||||||
return strings.Join(rendered, "")
|
return strings.Join(rendered, "")
|
||||||
}
|
}
|
||||||
|
|
||||||
func EmbedFonts(buf *bytes.Buffer, diagramHash, source string, fontFamily *d2fonts.FontFamily) {
|
func EmbedFonts(buf *bytes.Buffer, diagramHash, source string, fontFamily *d2fonts.FontFamily, corpus string) {
|
||||||
fmt.Fprint(buf, `<style type="text/css"><![CDATA[`)
|
fmt.Fprint(buf, `<style type="text/css"><![CDATA[`)
|
||||||
|
|
||||||
appendOnTrigger(
|
appendOnTrigger(
|
||||||
|
|
@ -1408,7 +1409,7 @@ func EmbedFonts(buf *bytes.Buffer, diagramHash, source string, fontFamily *d2fon
|
||||||
diagramHash,
|
diagramHash,
|
||||||
diagramHash,
|
diagramHash,
|
||||||
diagramHash,
|
diagramHash,
|
||||||
d2fonts.FontEncodings[fontFamily.Font(0, d2fonts.FONT_STYLE_REGULAR)],
|
fontFamily.Font(0, d2fonts.FONT_STYLE_REGULAR).GetEncodedSubset(corpus),
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
@ -1470,7 +1471,7 @@ func EmbedFonts(buf *bytes.Buffer, diagramHash, source string, fontFamily *d2fon
|
||||||
diagramHash,
|
diagramHash,
|
||||||
diagramHash,
|
diagramHash,
|
||||||
diagramHash,
|
diagramHash,
|
||||||
d2fonts.FontEncodings[fontFamily.Font(0, d2fonts.FONT_STYLE_BOLD)],
|
fontFamily.Font(0, d2fonts.FONT_STYLE_BOLD).GetEncodedSubset(corpus),
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
@ -1493,7 +1494,7 @@ func EmbedFonts(buf *bytes.Buffer, diagramHash, source string, fontFamily *d2fon
|
||||||
diagramHash,
|
diagramHash,
|
||||||
diagramHash,
|
diagramHash,
|
||||||
diagramHash,
|
diagramHash,
|
||||||
d2fonts.FontEncodings[fontFamily.Font(0, d2fonts.FONT_STYLE_ITALIC)],
|
fontFamily.Font(0, d2fonts.FONT_STYLE_ITALIC).GetEncodedSubset(corpus),
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
@ -1518,7 +1519,7 @@ func EmbedFonts(buf *bytes.Buffer, diagramHash, source string, fontFamily *d2fon
|
||||||
diagramHash,
|
diagramHash,
|
||||||
diagramHash,
|
diagramHash,
|
||||||
diagramHash,
|
diagramHash,
|
||||||
d2fonts.FontEncodings[d2fonts.SourceCodePro.Font(0, d2fonts.FONT_STYLE_REGULAR)],
|
d2fonts.SourceCodePro.Font(0, d2fonts.FONT_STYLE_REGULAR).GetEncodedSubset(corpus),
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
@ -1539,7 +1540,7 @@ func EmbedFonts(buf *bytes.Buffer, diagramHash, source string, fontFamily *d2fon
|
||||||
diagramHash,
|
diagramHash,
|
||||||
diagramHash,
|
diagramHash,
|
||||||
diagramHash,
|
diagramHash,
|
||||||
d2fonts.FontEncodings[d2fonts.SourceCodePro.Font(0, d2fonts.FONT_STYLE_BOLD)],
|
d2fonts.SourceCodePro.Font(0, d2fonts.FONT_STYLE_BOLD).GetEncodedSubset(corpus),
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
@ -1560,7 +1561,7 @@ func EmbedFonts(buf *bytes.Buffer, diagramHash, source string, fontFamily *d2fon
|
||||||
diagramHash,
|
diagramHash,
|
||||||
diagramHash,
|
diagramHash,
|
||||||
diagramHash,
|
diagramHash,
|
||||||
d2fonts.FontEncodings[d2fonts.SourceCodePro.Font(0, d2fonts.FONT_STYLE_ITALIC)],
|
d2fonts.SourceCodePro.Font(0, d2fonts.FONT_STYLE_ITALIC).GetEncodedSubset(corpus),
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
@ -1726,7 +1727,7 @@ func Render(diagram *d2target.Diagram, opts *RenderOpts) ([]byte, error) {
|
||||||
// generate style elements that will be appended to the SVG tag
|
// generate style elements that will be appended to the SVG tag
|
||||||
upperBuf := &bytes.Buffer{}
|
upperBuf := &bytes.Buffer{}
|
||||||
if opts.MasterID == "" {
|
if opts.MasterID == "" {
|
||||||
EmbedFonts(upperBuf, diagramHash, buf.String(), diagram.FontFamily) // EmbedFonts *must* run before `d2sketch.DefineFillPatterns`, but after all elements are appended to `buf`
|
EmbedFonts(upperBuf, diagramHash, buf.String(), diagram.FontFamily, diagram.GetCorpus()) // EmbedFonts *must* run before `d2sketch.DefineFillPatterns`, but after all elements are appended to `buf`
|
||||||
themeStylesheet, err := ThemeCSS(diagramHash, themeID, darkThemeID)
|
themeStylesheet, err := ThemeCSS(diagramHash, themeID, darkThemeID)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
|
|
|
||||||
|
Before Width: | Height: | Size: 197 KiB After Width: | Height: | Size: 25 KiB |
|
Before Width: | Height: | Size: 240 KiB After Width: | Height: | Size: 22 KiB |
|
Before Width: | Height: | Size: 254 KiB After Width: | Height: | Size: 42 KiB |
|
Before Width: | Height: | Size: 189 KiB After Width: | Height: | Size: 12 KiB |
|
Before Width: | Height: | Size: 239 KiB After Width: | Height: | Size: 21 KiB |
|
Before Width: | Height: | Size: 189 KiB After Width: | Height: | Size: 14 KiB |
|
Before Width: | Height: | Size: 415 KiB After Width: | Height: | Size: 47 KiB |
|
Before Width: | Height: | Size: 238 KiB After Width: | Height: | Size: 18 KiB |
|
Before Width: | Height: | Size: 300 KiB After Width: | Height: | Size: 47 KiB |
|
Before Width: | Height: | Size: 189 KiB After Width: | Height: | Size: 15 KiB |
|
Before Width: | Height: | Size: 65 KiB After Width: | Height: | Size: 24 KiB |
|
Before Width: | Height: | Size: 321 KiB After Width: | Height: | Size: 81 KiB |
|
|
@ -237,6 +237,51 @@ func (diagram Diagram) BoundingBox() (topLeft, bottomRight Point) {
|
||||||
return Point{x1, y1}, Point{x2, y2}
|
return Point{x1, y1}, Point{x2, y2}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (diagram Diagram) GetNestedCorpus() string {
|
||||||
|
corpus := diagram.GetCorpus()
|
||||||
|
for _, d := range diagram.Layers {
|
||||||
|
corpus += d.GetNestedCorpus()
|
||||||
|
}
|
||||||
|
for _, d := range diagram.Scenarios {
|
||||||
|
corpus += d.GetNestedCorpus()
|
||||||
|
}
|
||||||
|
for _, d := range diagram.Steps {
|
||||||
|
corpus += d.GetNestedCorpus()
|
||||||
|
}
|
||||||
|
|
||||||
|
return corpus
|
||||||
|
}
|
||||||
|
|
||||||
|
func (diagram Diagram) GetCorpus() string {
|
||||||
|
var corpus string
|
||||||
|
for _, s := range diagram.Shapes {
|
||||||
|
corpus += s.Label + s.Tooltip + s.Link
|
||||||
|
if s.Type == ShapeClass {
|
||||||
|
for _, cf := range s.Fields {
|
||||||
|
corpus += cf.Text(0).Text + cf.VisibilityToken()
|
||||||
|
}
|
||||||
|
for _, cm := range s.Methods {
|
||||||
|
corpus += cm.Text(0).Text + cm.VisibilityToken()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if s.Type == ShapeSQLTable {
|
||||||
|
for _, c := range s.Columns {
|
||||||
|
for _, t := range c.Texts(0) {
|
||||||
|
corpus += t.Text
|
||||||
|
}
|
||||||
|
corpus += c.ConstraintAbbr()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
for _, c := range diagram.Connections {
|
||||||
|
corpus += c.Label
|
||||||
|
corpus += c.SrcLabel
|
||||||
|
corpus += c.DstLabel
|
||||||
|
}
|
||||||
|
|
||||||
|
return corpus
|
||||||
|
}
|
||||||
|
|
||||||
func NewDiagram() *Diagram {
|
func NewDiagram() *Diagram {
|
||||||
return &Diagram{
|
return &Diagram{
|
||||||
Root: Shape{
|
Root: Shape{
|
||||||
|
|
|
||||||
|
Before Width: | Height: | Size: 330 KiB After Width: | Height: | Size: 9.3 KiB |
|
Before Width: | Height: | Size: 669 KiB After Width: | Height: | Size: 31 KiB |
|
Before Width: | Height: | Size: 330 KiB After Width: | Height: | Size: 9.3 KiB |
|
Before Width: | Height: | Size: 330 KiB After Width: | Height: | Size: 9.3 KiB |
|
Before Width: | Height: | Size: 329 KiB After Width: | Height: | Size: 9.5 KiB |
|
Before Width: | Height: | Size: 330 KiB After Width: | Height: | Size: 9.7 KiB |
|
Before Width: | Height: | Size: 330 KiB After Width: | Height: | Size: 9.7 KiB |
|
Before Width: | Height: | Size: 330 KiB After Width: | Height: | Size: 9.5 KiB |
|
Before Width: | Height: | Size: 330 KiB After Width: | Height: | Size: 9.3 KiB |
|
Before Width: | Height: | Size: 329 KiB After Width: | Height: | Size: 9.5 KiB |
|
Before Width: | Height: | Size: 330 KiB After Width: | Height: | Size: 9.7 KiB |
|
Before Width: | Height: | Size: 330 KiB After Width: | Height: | Size: 9.7 KiB |
|
Before Width: | Height: | Size: 330 KiB After Width: | Height: | Size: 9.5 KiB |
|
Before Width: | Height: | Size: 330 KiB After Width: | Height: | Size: 9.3 KiB |
|
Before Width: | Height: | Size: 341 KiB After Width: | Height: | Size: 19 KiB |
|
Before Width: | Height: | Size: 332 KiB After Width: | Height: | Size: 12 KiB |
|
Before Width: | Height: | Size: 342 KiB After Width: | Height: | Size: 24 KiB |
|
Before Width: | Height: | Size: 346 KiB After Width: | Height: | Size: 27 KiB |
|
Before Width: | Height: | Size: 766 KiB After Width: | Height: | Size: 448 KiB |
|
Before Width: | Height: | Size: 624 KiB After Width: | Height: | Size: 61 KiB |
|
Before Width: | Height: | Size: 268 KiB After Width: | Height: | Size: 19 KiB |
|
Before Width: | Height: | Size: 333 KiB After Width: | Height: | Size: 13 KiB |
|
Before Width: | Height: | Size: 333 KiB After Width: | Height: | Size: 13 KiB |
|
Before Width: | Height: | Size: 333 KiB After Width: | Height: | Size: 13 KiB |
|
Before Width: | Height: | Size: 331 KiB After Width: | Height: | Size: 11 KiB |
|
Before Width: | Height: | Size: 331 KiB After Width: | Height: | Size: 11 KiB |
|
Before Width: | Height: | Size: 187 KiB After Width: | Height: | Size: 9.9 KiB |
|
Before Width: | Height: | Size: 187 KiB After Width: | Height: | Size: 9.9 KiB |
|
Before Width: | Height: | Size: 359 KiB After Width: | Height: | Size: 29 KiB |
|
Before Width: | Height: | Size: 359 KiB After Width: | Height: | Size: 29 KiB |
|
Before Width: | Height: | Size: 807 KiB After Width: | Height: | Size: 30 KiB |
|
Before Width: | Height: | Size: 806 KiB After Width: | Height: | Size: 29 KiB |
|
Before Width: | Height: | Size: 799 KiB After Width: | Height: | Size: 20 KiB |
|
Before Width: | Height: | Size: 799 KiB After Width: | Height: | Size: 20 KiB |
|
Before Width: | Height: | Size: 800 KiB After Width: | Height: | Size: 26 KiB |
|
Before Width: | Height: | Size: 800 KiB After Width: | Height: | Size: 26 KiB |
|
Before Width: | Height: | Size: 332 KiB After Width: | Height: | Size: 13 KiB |
|
Before Width: | Height: | Size: 332 KiB After Width: | Height: | Size: 13 KiB |
|
Before Width: | Height: | Size: 804 KiB After Width: | Height: | Size: 32 KiB |
|
Before Width: | Height: | Size: 804 KiB After Width: | Height: | Size: 31 KiB |
|
Before Width: | Height: | Size: 654 KiB After Width: | Height: | Size: 12 KiB |
|
Before Width: | Height: | Size: 654 KiB After Width: | Height: | Size: 12 KiB |