Skip to content

Commit

Permalink
vg/vgsvg: drop fontMap, use informations from font.Font
Browse files Browse the repository at this point in the history
This CL drops the use of the internal fontMap that was associating some
pre-defined set of fonts with a set of SVG naming schemes (derived from
PostScript).
Instead, use the informations contained in plot/font.Font to derive the
expected SVG font-family (and friends) font style string.

Updates gonum#702.
  • Loading branch information
sbinet committed Jun 14, 2021
1 parent 8204fe4 commit 9438be6
Show file tree
Hide file tree
Showing 4 changed files with 266 additions and 55 deletions.
161 changes: 161 additions & 0 deletions vg/vgsvg/font_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,161 @@
// Copyright ©2021 The Gonum Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.

package vgsvg

import (
"testing"

stdfnt "golang.org/x/image/font"
"gonum.org/v1/plot/font"
)

func TestSVGFontDescr(t *testing.T) {
for i, tc := range []struct {
fnt font.Font
want string
}{
// typefaces
{
fnt: font.Font{Typeface: "Liberation"},
want: "font-family:Liberation;font-variant:normal;font-weight:normal;font-style:normal",
},
{
fnt: font.Font{
Typeface: "Liberation",
Variant: "",
Style: stdfnt.StyleNormal,
Weight: stdfnt.WeightNormal,
},
want: "font-family:Liberation;font-variant:normal;font-weight:normal;font-style:normal",
},
{
fnt: font.Font{
Typeface: "My-Font-Name",
Variant: "",
Style: stdfnt.StyleNormal,
Weight: stdfnt.WeightNormal,
},
want: "font-family:My-Font-Name;font-variant:normal;font-weight:normal;font-style:normal",
},
// variants
{
fnt: font.Font{
Typeface: "Liberation",
Variant: "Mono",
Style: stdfnt.StyleNormal,
Weight: stdfnt.WeightNormal,
},
want: "font-family:Liberation, monospace;font-variant:monospace;font-weight:normal;font-style:normal",
},
{
fnt: font.Font{
Typeface: "Liberation",
Variant: "Serif",
Style: stdfnt.StyleNormal,
Weight: stdfnt.WeightNormal,
},
want: "font-family:Liberation, serif;font-variant:serif;font-weight:normal;font-style:normal",
},
{
fnt: font.Font{
Typeface: "Liberation",
Variant: "Sans",
Style: stdfnt.StyleNormal,
Weight: stdfnt.WeightNormal,
},
want: "font-family:Liberation, sans-serif;font-variant:sans-serif;font-weight:normal;font-style:normal",
},
{
fnt: font.Font{
Typeface: "Liberation",
Variant: "SansSerif",
Style: stdfnt.StyleNormal,
Weight: stdfnt.WeightNormal,
},
want: "font-family:Liberation, sans-serif;font-variant:sans-serif;font-weight:normal;font-style:normal",
},
{
fnt: font.Font{
Typeface: "Liberation",
Variant: "Sans-Serif",
Style: stdfnt.StyleNormal,
Weight: stdfnt.WeightNormal,
},
want: "font-family:Liberation, sans-serif;font-variant:sans-serif;font-weight:normal;font-style:normal",
},
{
fnt: font.Font{
Typeface: "Liberation",
Variant: "Smallcaps",
Style: stdfnt.StyleNormal,
Weight: stdfnt.WeightNormal,
},
want: "font-family:Liberation, small-caps;font-variant:small-caps;font-weight:normal;font-style:normal",
},
// styles
{
fnt: font.Font{
Typeface: "Liberation",
Variant: "",
Style: stdfnt.StyleItalic,
Weight: stdfnt.WeightNormal,
},
want: "font-family:Liberation;font-variant:normal;font-weight:normal;font-style:italic",
},
{
fnt: font.Font{
Typeface: "Liberation",
Variant: "",
Style: stdfnt.StyleOblique,
Weight: stdfnt.WeightNormal,
},
want: "font-family:Liberation;font-variant:normal;font-weight:normal;font-style:oblique",
},
// weights
{
fnt: font.Font{
Typeface: "Liberation",
Variant: "",
Style: stdfnt.StyleNormal,
Weight: stdfnt.WeightThin,
},
want: "font-family:Liberation;font-variant:normal;font-weight:100;font-style:normal",
},
{
fnt: font.Font{
Typeface: "Liberation",
Variant: "",
Style: stdfnt.StyleNormal,
Weight: stdfnt.WeightBold,
},
want: "font-family:Liberation;font-variant:normal;font-weight:bold;font-style:normal",
},
{
fnt: font.Font{
Typeface: "Liberation",
Variant: "",
Style: stdfnt.StyleNormal,
},
want: "font-family:Liberation;font-variant:normal;font-weight:normal;font-style:normal",
},
{
fnt: font.Font{
Typeface: "Liberation",
Variant: "",
Style: stdfnt.StyleNormal,
Weight: stdfnt.WeightExtraBold,
},
want: "font-family:Liberation;font-variant:normal;font-weight:800;font-style:normal",
},
} {
got := svgFontDescr(tc.fnt)
if got != tc.want {
t.Errorf(
"invalid SVG font[%d] description:\ngot= %s\nwant=%s",
i, got, tc.want,
)
}
}
}
18 changes: 9 additions & 9 deletions vg/vgsvg/testdata/scatter_golden.svg
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
18 changes: 9 additions & 9 deletions vg/vgsvg/testdata/scatter_line_golden.svg
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
124 changes: 87 additions & 37 deletions vg/vgsvg/vgsvg.go
Original file line number Diff line number Diff line change
Expand Up @@ -17,8 +17,10 @@ import (
"image/png"
"io"
"math"
"strings"

svgo "github.com/ajstarks/svgo"
stdfnt "golang.org/x/image/font"

"gonum.org/v1/plot/font"
"gonum.org/v1/plot/vg"
Expand Down Expand Up @@ -307,13 +309,12 @@ func large(a float64) int {
// FillString draws str at position pt using the specified font.
// Text passed to FillString is escaped with html.EscapeString.
func (c *Canvas) FillString(font font.Face, pt vg.Point, str string) {
fontStr, ok := fontMap[font.Name()]
if !ok {
panic(fmt.Sprintf("Unknown font: %s", font.Name()))
}
sty := style(fontStr,
name := svgFontDescr(font.Font)
sty := style(
name,
elm("font-size", "medium", "%.*gpx", pr, font.Font.Size.Points()),
elm("fill", "#000000", colorString(c.context().color)))
elm("fill", "#000000", colorString(c.context().color)),
)
if sty != "" {
sty = "\n\t" + sty
}
Expand Down Expand Up @@ -350,38 +351,87 @@ func (c *Canvas) DrawImage(rect vg.Rectangle, img image.Image) {
)
}

var (
// fontMap maps Postscript-style font names to their
// corresponding SVG style string.
fontMap = map[string]string{
"Courier": "font-family:Courier;font-weight:normal;font-style:normal",
"Courier-Bold": "font-family:Courier;font-weight:bold;font-style:normal",
"Courier-Oblique": "font-family:Courier;font-weight:normal;font-style:oblique",
"Courier-BoldOblique": "font-family:Courier;font-weight:bold;font-style:oblique",
"Helvetica": "font-family:Helvetica;font-weight:normal;font-style:normal",
"Helvetica-Bold": "font-family:Helvetica;font-weight:bold;font-style:normal",
"Helvetica-Oblique": "font-family:Helvetica;font-weight:normal;font-style:oblique",
"Helvetica-BoldOblique": "font-family:Helvetica;font-weight:bold;font-style:oblique",
"Times-Roman": "font-family:Times;font-weight:normal;font-style:normal",
"Times-Bold": "font-family:Times;font-weight:bold;font-style:normal",
"Times-Italic": "font-family:Times;font-weight:normal;font-style:italic",
"Times-BoldItalic": "font-family:Times;font-weight:bold;font-style:italic",

// Liberation fonts
"LiberationMono-Regular": "font-family:Courier;font-weight:normal;font-style:normal",
"LiberationMono-Bold": "font-family:Courier;font-weight:bold;font-style:normal",
"LiberationMono-Italic": "font-family:Courier;font-weight:normal;font-style:oblique",
"LiberationMono-BoldItalic": "font-family:Courier;font-weight:bold;font-style:oblique",
"LiberationSans-Regular": "font-family:Helvetica;font-weight:normal;font-style:normal",
"LiberationSans-Bold": "font-family:Helvetica;font-weight:bold;font-style:normal",
"LiberationSans-Italic": "font-family:Helvetica;font-weight:normal;font-style:oblique",
"LiberationSans-BoldItalic": "font-family:Helvetica;font-weight:bold;font-style:oblique",
"LiberationSerif-Regular": "font-family:Times;font-weight:normal;font-style:normal",
"LiberationSerif-Bold": "font-family:Times;font-weight:bold;font-style:normal",
"LiberationSerif-Italic": "font-family:Times;font-weight:normal;font-style:italic",
"LiberationSerif-BoldItalic": "font-family:Times;font-weight:bold;font-style:italic",
// svgFontDescr returns a SVG compliant font name from the provided font face.
func svgFontDescr(fnt font.Font) string {
var (
family = svgFamilyName(fnt)
variant = svgVariantName(fnt.Variant)
style = svgStyleName(fnt.Style)
weight = svgWeightName(fnt.Weight)
)

o := "font-family:" + family + ";" +
"font-variant:" + variant + ";" +
"font-weight:" + weight + ";" +
"font-style:" + style
return o
}

func svgFamilyName(fnt font.Font) string {
// https://developer.mozilla.org/en-US/docs/Web/SVG/Attribute/font-family
v := string(fnt.Typeface)
if fnt.Variant != "" {
v += ", " + svgVariantName(fnt.Variant)
}
)
return v
}

func svgVariantName(v font.Variant) string {
// https://developer.mozilla.org/en-US/docs/Web/SVG/Attribute/font-variant
str := strings.ToLower(string(v))
switch str {
case "":
return "normal"
case "smallcaps":
return "small-caps"
case "mono":
return "monospace"
case "sans", "sansserif", "sans-serif":
return "sans-serif"
default:
return str
}
}

func svgStyleName(sty stdfnt.Style) string {
// https://developer.mozilla.org/en-US/docs/Web/SVG/Attribute/font-style
switch sty {
case stdfnt.StyleNormal:
return "normal"
case stdfnt.StyleItalic:
return "italic"
case stdfnt.StyleOblique:
return "oblique"
}
panic(fmt.Errorf("vgsvg: invalid font style %+v (v=%d)", sty, int(sty)))
}

func svgWeightName(w stdfnt.Weight) string {
// see:
// https://developer.mozilla.org/en-US/docs/Web/SVG/Attribute/font-weight
// https://developer.mozilla.org/en-US/docs/Web/CSS/font-weight
switch w {
case stdfnt.WeightThin:
return "100"
case stdfnt.WeightExtraLight:
return "200"
case stdfnt.WeightLight:
return "300"
case stdfnt.WeightNormal:
return "normal"
case stdfnt.WeightMedium:
return "500"
case stdfnt.WeightSemiBold:
return "600"
case stdfnt.WeightBold:
return "bold"
case stdfnt.WeightExtraBold:
return "800"
case stdfnt.WeightBlack:
return "900"
}
return "normal"
}

// WriteTo writes the canvas to an io.Writer.
func (c *Canvas) WriteTo(w io.Writer) (int64, error) {
Expand Down

0 comments on commit 9438be6

Please sign in to comment.