accept font input
This commit is contained in:
parent
3dfafca650
commit
2f57ea5ca2
8 changed files with 263 additions and 6 deletions
|
|
@ -1,5 +1,6 @@
|
||||||
#### Features 🚀
|
#### Features 🚀
|
||||||
|
|
||||||
|
- Flags to set a custom font are supported. See [docs](https://d2lang.com/todo). [#1108](https://github.com/terrastruct/d2/pull/1108)
|
||||||
- `--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)
|
- fonts are now subsetted to reduce svg file size [#1089](https://github.com/terrastruct/d2/pull/1089)
|
||||||
|
|
|
||||||
|
|
@ -80,6 +80,15 @@ Renders the diagram to look like it was sketched by hand
|
||||||
.It Fl -center Ar flag
|
.It Fl -center Ar flag
|
||||||
Center the SVG in the containing viewbox, such as your browser screen
|
Center the SVG in the containing viewbox, such as your browser screen
|
||||||
.Ns .
|
.Ns .
|
||||||
|
.It Fl -font-regular
|
||||||
|
Path to .ttf file to use for the regular font. If none provided, Source Sans Pro Regular is used
|
||||||
|
.Ns .
|
||||||
|
.It Fl -font-italic
|
||||||
|
Path to .ttf file to use for the italic font. If none provided, Source Sans Pro Regular-Italic is used
|
||||||
|
.Ns .
|
||||||
|
.It Fl -font-bold
|
||||||
|
Path to .ttf file to use for the bold font. If none provided, Source Sans Pro Bold is used
|
||||||
|
.Ns .
|
||||||
.It Fl -pad Ar 100
|
.It Fl -pad Ar 100
|
||||||
Pixels padded around the rendered diagram
|
Pixels padded around the rendered diagram
|
||||||
.Ns .
|
.Ns .
|
||||||
|
|
|
||||||
|
|
@ -95,6 +95,10 @@ func Run(ctx context.Context, ms *xmain.State) (err error) {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fontRegularFlag := ms.Opts.String("D2_FONT_REGULAR", "font-regular", "", "", "path to .ttf file to use for the regular font. If none provided, Source Sans Pro Regular is used.")
|
||||||
|
fontItalicFlag := ms.Opts.String("D2_FONT_ITALIC", "font-italic", "", "", "path to .ttf file to use for the italic font. If none provided, Source Sans Pro Regular-Italic is used.")
|
||||||
|
fontBoldFlag := ms.Opts.String("D2_FONT_BOLD", "font-bold", "", "", "path to .ttf file to use for the bold font. If none provided, Source Sans Pro Bold is used.")
|
||||||
|
|
||||||
ps, err := d2plugin.ListPlugins(ctx)
|
ps, err := d2plugin.ListPlugins(ctx)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
|
|
@ -114,6 +118,11 @@ func Run(ctx context.Context, ms *xmain.State) (err error) {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fontFamily, err := loadFonts(ms, *fontRegularFlag, *fontItalicFlag, *fontBoldFlag)
|
||||||
|
if err != nil {
|
||||||
|
return xmain.UsageErrorf("failed to load specified fonts: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
if len(ms.Opts.Flags.Args()) > 0 {
|
if len(ms.Opts.Flags.Args()) > 0 {
|
||||||
switch ms.Opts.Flags.Arg(0) {
|
switch ms.Opts.Flags.Arg(0) {
|
||||||
case "init-playwright":
|
case "init-playwright":
|
||||||
|
|
@ -265,6 +274,7 @@ func Run(ctx context.Context, ms *xmain.State) (err error) {
|
||||||
bundle: *bundleFlag,
|
bundle: *bundleFlag,
|
||||||
forceAppendix: *forceAppendixFlag,
|
forceAppendix: *forceAppendixFlag,
|
||||||
pw: pw,
|
pw: pw,
|
||||||
|
fontFamily: fontFamily,
|
||||||
})
|
})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
|
|
@ -275,7 +285,7 @@ func Run(ctx context.Context, ms *xmain.State) (err error) {
|
||||||
ctx, cancel := context.WithTimeout(ctx, time.Minute*2)
|
ctx, cancel := context.WithTimeout(ctx, time.Minute*2)
|
||||||
defer cancel()
|
defer cancel()
|
||||||
|
|
||||||
_, written, err := compile(ctx, ms, plugin, renderOpts, *animateIntervalFlag, inputPath, outputPath, *bundleFlag, *forceAppendixFlag, pw.Page)
|
_, written, err := compile(ctx, ms, plugin, renderOpts, fontFamily, *animateIntervalFlag, inputPath, outputPath, *bundleFlag, *forceAppendixFlag, pw.Page)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
if written {
|
if written {
|
||||||
return fmt.Errorf("failed to fully compile (partial render written): %w", err)
|
return fmt.Errorf("failed to fully compile (partial render written): %w", err)
|
||||||
|
|
@ -285,7 +295,7 @@ func Run(ctx context.Context, ms *xmain.State) (err error) {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func compile(ctx context.Context, ms *xmain.State, plugin d2plugin.Plugin, renderOpts d2svg.RenderOpts, animateInterval int64, inputPath, outputPath string, bundle, forceAppendix bool, page playwright.Page) (_ []byte, written bool, _ error) {
|
func compile(ctx context.Context, ms *xmain.State, plugin d2plugin.Plugin, renderOpts d2svg.RenderOpts, fontFamily *d2fonts.FontFamily, animateInterval int64, inputPath, outputPath string, bundle, forceAppendix bool, page playwright.Page) (_ []byte, written bool, _ error) {
|
||||||
start := time.Now()
|
start := time.Now()
|
||||||
input, err := ms.ReadPath(inputPath)
|
input, err := ms.ReadPath(inputPath)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
|
@ -299,9 +309,10 @@ func compile(ctx context.Context, ms *xmain.State, plugin d2plugin.Plugin, rende
|
||||||
|
|
||||||
layout := plugin.Layout
|
layout := plugin.Layout
|
||||||
opts := &d2lib.CompileOptions{
|
opts := &d2lib.CompileOptions{
|
||||||
Layout: layout,
|
Layout: layout,
|
||||||
Ruler: ruler,
|
Ruler: ruler,
|
||||||
ThemeID: renderOpts.ThemeID,
|
ThemeID: renderOpts.ThemeID,
|
||||||
|
FontFamily: fontFamily,
|
||||||
}
|
}
|
||||||
if renderOpts.Sketch {
|
if renderOpts.Sketch {
|
||||||
opts.FontFamily = go2.Pointer(d2fonts.HandDrawn)
|
opts.FontFamily = go2.Pointer(d2fonts.HandDrawn)
|
||||||
|
|
@ -659,3 +670,47 @@ func initPlaywright() error {
|
||||||
}
|
}
|
||||||
return pw.Cleanup()
|
return pw.Cleanup()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func loadFont(ms *xmain.State, path string) ([]byte, error) {
|
||||||
|
if filepath.Ext(path) != ".ttf" {
|
||||||
|
return nil, fmt.Errorf("expected .ttf file but %s has extension %s", path, filepath.Ext(path))
|
||||||
|
}
|
||||||
|
ttf, err := os.ReadFile(path)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("failed to read font at %s: %v", path, err)
|
||||||
|
}
|
||||||
|
ms.Log.Info.Printf("font %s loaded", filepath.Base(path))
|
||||||
|
return ttf, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func loadFonts(ms *xmain.State, pathToRegular, pathToItalic, pathToBold string) (*d2fonts.FontFamily, error) {
|
||||||
|
if pathToRegular == "" && pathToItalic == "" && pathToBold == "" {
|
||||||
|
return nil, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
var regularTTF []byte
|
||||||
|
var italicTTF []byte
|
||||||
|
var boldTTF []byte
|
||||||
|
|
||||||
|
var err error
|
||||||
|
if pathToRegular != "" {
|
||||||
|
regularTTF, err = loadFont(ms, pathToRegular)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if pathToItalic != "" {
|
||||||
|
italicTTF, err = loadFont(ms, pathToItalic)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if pathToBold != "" {
|
||||||
|
boldTTF, err = loadFont(ms, pathToBold)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return d2fonts.AddFontFamily("custom", regularTTF, italicTTF, boldTTF)
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -26,6 +26,7 @@ import (
|
||||||
"oss.terrastruct.com/util-go/xmain"
|
"oss.terrastruct.com/util-go/xmain"
|
||||||
|
|
||||||
"oss.terrastruct.com/d2/d2plugin"
|
"oss.terrastruct.com/d2/d2plugin"
|
||||||
|
"oss.terrastruct.com/d2/d2renderers/d2fonts"
|
||||||
"oss.terrastruct.com/d2/d2renderers/d2svg"
|
"oss.terrastruct.com/d2/d2renderers/d2svg"
|
||||||
"oss.terrastruct.com/d2/lib/png"
|
"oss.terrastruct.com/d2/lib/png"
|
||||||
)
|
)
|
||||||
|
|
@ -51,6 +52,7 @@ type watcherOpts struct {
|
||||||
bundle bool
|
bundle bool
|
||||||
forceAppendix bool
|
forceAppendix bool
|
||||||
pw png.Playwright
|
pw png.Playwright
|
||||||
|
fontFamily *d2fonts.FontFamily
|
||||||
}
|
}
|
||||||
|
|
||||||
type watcher struct {
|
type watcher struct {
|
||||||
|
|
@ -358,7 +360,7 @@ func (w *watcher) compileLoop(ctx context.Context) error {
|
||||||
w.pw = newPW
|
w.pw = newPW
|
||||||
}
|
}
|
||||||
|
|
||||||
svg, _, err := compile(ctx, w.ms, w.layoutPlugin, w.renderOpts, w.animateInterval, w.inputPath, w.outputPath, w.bundle, w.forceAppendix, w.pw.Page)
|
svg, _, err := compile(ctx, w.ms, w.layoutPlugin, w.renderOpts, w.fontFamily, w.animateInterval, w.inputPath, w.outputPath, w.bundle, w.forceAppendix, w.pw.Page)
|
||||||
errs := ""
|
errs := ""
|
||||||
if err != nil {
|
if err != nil {
|
||||||
if len(svg) > 0 {
|
if len(svg) > 0 {
|
||||||
|
|
|
||||||
|
|
@ -245,3 +245,78 @@ var D2_FONT_TO_FAMILY = map[string]FontFamily{
|
||||||
"default": SourceSansPro,
|
"default": SourceSansPro,
|
||||||
"mono": SourceCodePro,
|
"mono": SourceCodePro,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func AddFontStyle(font Font, style FontStyle, ttf []byte) error {
|
||||||
|
FontFaces[font] = ttf
|
||||||
|
|
||||||
|
woff, err := fontlib.Sfnt2Woff(ttf)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("failed to encode ttf to woff: %v", err)
|
||||||
|
}
|
||||||
|
encodedWoff := fmt.Sprintf("data:application/font-woff;base64,%v", base64.StdEncoding.EncodeToString(woff))
|
||||||
|
FontEncodings[font] = encodedWoff
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func AddFontFamily(name string, regularTTF, italicTTF, boldTTF []byte) (*FontFamily, error) {
|
||||||
|
customFontFamily := FontFamily(name)
|
||||||
|
|
||||||
|
regularFont := Font{
|
||||||
|
Family: customFontFamily,
|
||||||
|
Style: FONT_STYLE_REGULAR,
|
||||||
|
}
|
||||||
|
if regularTTF != nil {
|
||||||
|
err := AddFontStyle(regularFont, FONT_STYLE_REGULAR, regularTTF)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
fallbackFont := Font{
|
||||||
|
Family: SourceSansPro,
|
||||||
|
Style: FONT_STYLE_REGULAR,
|
||||||
|
}
|
||||||
|
FontFaces[regularFont] = FontFaces[fallbackFont]
|
||||||
|
FontEncodings[regularFont] = FontEncodings[fallbackFont]
|
||||||
|
}
|
||||||
|
|
||||||
|
italicFont := Font{
|
||||||
|
Family: customFontFamily,
|
||||||
|
Style: FONT_STYLE_ITALIC,
|
||||||
|
}
|
||||||
|
if italicTTF != nil {
|
||||||
|
err := AddFontStyle(italicFont, FONT_STYLE_ITALIC, italicTTF)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
fallbackFont := Font{
|
||||||
|
Family: SourceSansPro,
|
||||||
|
Style: FONT_STYLE_ITALIC,
|
||||||
|
}
|
||||||
|
FontFaces[italicFont] = FontFaces[fallbackFont]
|
||||||
|
FontEncodings[italicFont] = FontEncodings[fallbackFont]
|
||||||
|
}
|
||||||
|
|
||||||
|
boldFont := Font{
|
||||||
|
Family: customFontFamily,
|
||||||
|
Style: FONT_STYLE_BOLD,
|
||||||
|
}
|
||||||
|
if boldTTF != nil {
|
||||||
|
err := AddFontStyle(boldFont, FONT_STYLE_BOLD, boldTTF)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
fallbackFont := Font{
|
||||||
|
Family: SourceSansPro,
|
||||||
|
Style: FONT_STYLE_BOLD,
|
||||||
|
}
|
||||||
|
FontFaces[boldFont] = FontFaces[fallbackFont]
|
||||||
|
FontEncodings[boldFont] = FontEncodings[fallbackFont]
|
||||||
|
}
|
||||||
|
|
||||||
|
FontFamilies = append(FontFamilies, customFontFamily)
|
||||||
|
|
||||||
|
return &customFontFamily, nil
|
||||||
|
}
|
||||||
|
|
|
||||||
BIN
e2etests-cli/RockSalt-Regular.ttf
Normal file
BIN
e2etests-cli/RockSalt-Regular.ttf
Normal file
Binary file not shown.
|
|
@ -82,6 +82,19 @@ steps: {
|
||||||
assert.Testdata(t, ".svg", svg)
|
assert.Testdata(t, ".svg", svg)
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
name: "with-font",
|
||||||
|
run: func(t *testing.T, ctx context.Context, dir string, env *xos.Env) {
|
||||||
|
writeFile(t, dir, "font.d2", `a: Why do computers get sick often?
|
||||||
|
b: Because their Windows are always open!
|
||||||
|
a -> b: italic font
|
||||||
|
`)
|
||||||
|
err := runTestMain(t, ctx, dir, env, "--font-bold=./RockSalt-Regular.ttf", "font.d2")
|
||||||
|
assert.Success(t, err)
|
||||||
|
svg := readFile(t, dir, "font.svg")
|
||||||
|
assert.Testdata(t, ".svg", svg)
|
||||||
|
},
|
||||||
|
},
|
||||||
{
|
{
|
||||||
name: "incompatible-animation",
|
name: "incompatible-animation",
|
||||||
run: func(t *testing.T, ctx context.Context, dir string, env *xos.Env) {
|
run: func(t *testing.T, ctx context.Context, dir string, env *xos.Env) {
|
||||||
|
|
|
||||||
102
e2etests-cli/testdata/TestCLI_E2E/with-font.exp.svg
vendored
Normal file
102
e2etests-cli/testdata/TestCLI_E2E/with-font.exp.svg
vendored
Normal file
File diff suppressed because one or more lines are too long
|
After Width: | Height: | Size: 26 KiB |
Loading…
Reference in a new issue