Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

plot: Changing the default font for plots is unintuitive and/or not possible #702

Open
stippi2 opened this issue Jun 10, 2021 · 9 comments

Comments

@stippi2
Copy link

stippi2 commented Jun 10, 2021

What are you trying to do?

Render a plot to SVG with a certain font as a default for title and axis

What did you do?

plot.DefaultFont = font.Font{Typeface: "Arial"}
p := plot.New()
err := plotutil.AddLinePoints(p, ...)
...
err = p.Save(4*vg.Inch, 4*vg.Inch, "plot.svg")
...

What did you expect to happen?

That all labels in the plot use the Arial font.

What actually happened?

Rending the plot panic'ed, because Font objects are used unchecked in places using Extent().

It is not clear at all, what fonts can even be used for plot.DefaultFont and that it needs to happen before calling plot.New(). Digging through the code reveals there was a built-in map, which is currently being removed.

It is possible to load a custom font such as Arial, by extending the vg.FontDirs global. In addition, the default font cache needs to be manually extended with a mapping for "Arial" to a font object loaded via vg.MakeFont().

However, this will still fail later in the SVG renderer, because it has a hard-coded mapping of typefaces to CSS style strings. This mapping cannot currently be extended. If a font is not part of this mapping, it will at least throw a proper error.

What version of Go and Gonum/plot are you using?

From go.sum, it looks like I am using v0.9.0.

Does this issue reproduce with the current master?

No idea.

@stippi2
Copy link
Author

stippi2 commented Jun 10, 2021

This is the code I've had to use (on macOS) to get as far as the error in the SVG renderer:

vg.FontDirs = append(vg.FontDirs, "/System/Library/Fonts/Supplemental/")
vg.FontMap["Arial"] = "Arial"
arial, err := vg.MakeFont("Arial", 12)
if err != nil {
	return fmt.Errorf("failed to load Arial: %w", err)
}

font.DefaultCache.Add([]font.Face{{
	Font: font.Font{Typeface: "Arial"},
	Face: arial.Font(),
}})

plot.DefaultFont = font.Font{Typeface: "Arial"}
p := plot.New()
...

@sbinet
Copy link
Member

sbinet commented Jun 10, 2021

thanks for the report.

there are 2 issues in this issue:

  • usability/intuitiveness of the new font system compared to the previous one
  • ability to use 3rd-party fonts with the SVG backend.

font

as for the former, I'd say it's a lack of examples (or discoverability of these examples, such as: this one) or familiarity with the new API.

as hinted in:

your snippet of code in #702 (comment) would translate to:

	ttf, err := os.ReadFile("/System/Library/Fonts/Supplemental/Arial.ttf")
	if err != nil {
		panic(err)
	}
	fontTTF, err := opentype.Parse(ttf)
	if err != nil {
		log.Fatal(err)
	}

	arial := font.Font{Typeface: "Arial"}
	font.DefaultCache.Add([]font.Face{
		{
			Font: arial,
			Face: fontTTF,
		},
	})

	plot.DefaultFont = arial
	p := p.New()

(compared to v0.8.0's way, it's not too dissimilar: see here)

svg fonts

as for the latter issue, it's "just" an overlook :)
I will say that ITSMT vg/vgsvg actually never handled 3rd-party fonts and just played tricks (ie: replacing your use of Arial with the "equivalent" Liberation font, for example).

it seems like SVG can embed fonts, like PDF does.
we could add that feature to produce something along the lines of:

<svg xmlns="http://www.w3.org/2000/svg" width="450" height="150" font-size="24" text-anchor="middle">
    <defs>
        <style>
            @font-face{
                font-family:"Roboto Condensed";
                src:url(data:font/ttf;charset=utf-8;base64,your_base64_encoded_long_string) format("ttf"); 
                font-weight:normal;font-style:normal;
            }
            @font-face{
                font-family:"Open Sans";
                src:url(data:font/ttf;charset=utf-8;base64,your_base64_encoded_long_string) format("ttf"); 
                font-weight:normal;font-style:normal;
            }
            @font-face{
                font-family:"Anonymous Pro";
                src:url(data:font/ttf;charset=utf-8;base64,your_base64_encoded_long_string) format("ttf"); 
                font-weight:normal;font-style:normal;
            }
        </style>
    </defs>
        <text font-family="Roboto Condensed" x="190" y="32.92">
        This is a Roboto Condensed font
    </text>
    <text font-family="Open Sans" x="190" y="82.92">
        This is a Open Sans font
    </text>
    <text font-family="Anonymous Pro" x="190" y="132.92">
        This is a Anonymous Pro font
    </text>
</svg>

so:

  • add an embed fonts field to vgsvg.Canvas, default to false

  • if embed is false and vgsvg.Canvas.FillString encounters an unknown font, either:

    • panic, or
    • embed font
      (I'd err on panic, to lead to reproducible results of the produced plot file.)
  • if embed is true, eagerly embed all fonts, even the "known" ones.

thoughts?

@stippi2
Copy link
Author

stippi2 commented Jun 11, 2021

Just my 2 cents, of course:

font

A distinction should be made between installed fonts and custom fonts. Why can't vg.FontDirs, upon first use, be initialized with platform-specific default font locations?

Then of course there is the problem with mapping font names to font files. Either you can scan the system font directories and build the mapping, but of course that will take time. Or there could at least be some "fuzzy logic". Most font files follow a common naming scheme. The actual font file scanning could be delayed until the naming scheme fails for the first time.

Implementing the above would go a long way. Then you could really do just

plot.DefaultFont = font.Font{Typeface: "Arial"}

This is what I tried first, so I'd consider that "intuitive". ;-)

For the case that you have a font file outside the system's installed fonts, and you want to import it into gonum, you would have to do what you describe in your snippet. I think that's fine, but it really shouldn't be necessary for system fonts.

svg fonts

I ended up replacing the font family name in the SVG without any embedding. I agree embedding should be manually enabled. In the SVG renderer, I think it doesn't need to have the built-in mapping at all. It should all be able to be generated from the information in font.Font{}.

@stippi2
Copy link
Author

stippi2 commented Jun 11, 2021

Actually, I take back what I said about custom font files. Instead of your snippet, I'd much prefer this in pkg font:

// AddFontFile parses the file and makes the contained font available via the default font cache
func AddFontFile(path string) (*Font, error) {
	ttf, err := os.ReadFile(path)
	if err != nil {
		nil, err
	}
	fontTTF, err := opentype.Parse(ttf)
	if err != nil {
		return nil, fmt.Errorf("failed to parse '%s' as font: %w", path, err)
	}

	font := Font{<name, style, weight, etc. from pulled from fontTTF>}
	DefaultCache.Add([]Face{
		{
			Font: font,
			Face: fontTTF,
		},
	})
	return &font, nil
}

@sbinet
Copy link
Member

sbinet commented Jun 11, 2021

A distinction should be made between installed fonts and custom fonts. Why can't vg.FontDirs, upon first use, be initialized with platform-specific default font locations?

I don't think that should be the default behaviour.
I believe it should be user opt-in as crawling a filesystem is an operation that may not be completely cheap.
that said, I am open to add a few convenience methods/funcs to, say, font.Cache:

  • font.Cache.AddFrom(fname string) (Font, error)
  • same but with the raw bytes instead ?
  • easy addition of a whole TTF collection (.ttc or .otc files)

In the SVG renderer, I think it doesn't need to have the built-in mapping at all. It should all be able to be generated from the information in font.Font{}.

you're right.
a fresh pair of eyes is always welcomed.

sbinet added a commit to sbinet-gonum/plot that referenced this issue Jun 11, 2021
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.
sbinet added a commit to sbinet-gonum/plot that referenced this issue Jun 14, 2021
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.
sbinet added a commit to sbinet-gonum/plot that referenced this issue Jun 14, 2021
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.
sbinet added a commit to sbinet-gonum/plot that referenced this issue Jun 14, 2021
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.
sbinet added a commit to sbinet-gonum/plot that referenced this issue Jun 14, 2021
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.
sbinet added a commit to sbinet-gonum/plot that referenced this issue Jun 16, 2021
sbinet added a commit to sbinet-gonum/plot that referenced this issue Jun 24, 2021
sbinet added a commit to sbinet-gonum/plot that referenced this issue Jul 1, 2021
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.
sbinet added a commit that referenced this issue Jul 1, 2021
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 #702.
sbinet added a commit to sbinet-gonum/plot that referenced this issue Aug 9, 2021
sbinet added a commit to sbinet-gonum/plot that referenced this issue Aug 17, 2021
sbinet added a commit to sbinet-gonum/plot that referenced this issue Sep 27, 2021
sbinet added a commit to sbinet-gonum/plot that referenced this issue Sep 27, 2021
@jurisbu
Copy link

jurisbu commented Sep 30, 2021

My 2 cents. As a new user of gonum I killed quite some time trying to figure out how to specify different than default font. In the end I gave up. I think this describes discover-ability to some extent.

Of course, it could very well be the fact that fonts are not supposed to be change. But then it could be specified in documentation I suppose.

@sbinet
Copy link
Member

sbinet commented Sep 30, 2021

Is it because the font example is not in a very discoverable place:

https://pkg.go.dev/gonum.org/v1/plot@v0.10.0/vg#example-package-AddFont

Or because the example isn't helpful enough?

@jurisbu
Copy link

jurisbu commented Sep 30, 2021

@sbinet I think it is not in discoverable place. I only stumbled on that example due to this issue. I naturally was hoping to have example on how to change font related stuff along side with plot examples. But given that adding font is non-trivila tasks it makes little sense to include it in https://pkg.go.dev/gonum.org/v1/plot@v0.10.0 documentation. maybe it makes sense to mantion and include link to example in vg.

Actually I would prefer non-go documentation approach - either in gonum.org or extended examples aka gallery in wiki.

Thanks!

@sbinet
Copy link
Member

sbinet commented Sep 30, 2021

On top of that, I guess it also makes more sense to now move that example to the 'plot/font' package.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

3 participants