From b197c4c7b07169797982f2efe683a916f86396cd Mon Sep 17 00:00:00 2001 From: Sebastien Binet Date: Wed, 13 Jan 2021 15:10:20 +0100 Subject: [PATCH] font{,/liberation}: introduce Font, Collection and Cache Fixes #613. --- font/font.go | 320 ++++++++++++++++++++++++++++++++++ {vg => font}/font_test.go | 168 +++++++++++++----- font/liberation/liberation.go | 97 +++++++++++ go.mod | 1 + go.sum | 3 +- text/text.go | 4 +- 6 files changed, 545 insertions(+), 48 deletions(-) create mode 100644 font/font.go rename {vg => font}/font_test.go (52%) create mode 100644 font/liberation/liberation.go diff --git a/font/font.go b/font/font.go new file mode 100644 index 00000000..edf2a766 --- /dev/null +++ b/font/font.go @@ -0,0 +1,320 @@ +// 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 font + +import ( + "errors" + "fmt" + + "golang.org/x/image/font" + "golang.org/x/image/font/opentype" + "golang.org/x/image/font/sfnt" + "golang.org/x/image/math/fixed" +) + +// DefaultCache is the global cache for fonts. +var DefaultCache *Cache = NewCache(nil) + +// Font represents a font face. +type Font struct { + // Typeface identifies the Font. + Typeface Typeface + + // Variant is the variant of a font, such as "Mono" or "Smallcaps". + Variant Variant + + // Style is the style of a font, such as Regular or Italic. + Style font.Style + + // Weight is the weight of a font, such as Normal or Bold. + Weight font.Weight + + // Size is the size of the font. + Size Length +} + +// Name returns a fully qualified name for the given font. +func (f *Font) Name() string { + v := f.Variant + w := weightName(f.Weight) + s := styleName(f.Style) + + switch f.Style { + case font.StyleNormal: + s = "" + if f.Weight == font.WeightNormal { + w = "Regular" + } + default: + if f.Weight == font.WeightNormal { + w = "" + } + } + + return fmt.Sprintf("%s%s-%s%s", f.Typeface, v, w, s) +} + +// From returns a copy of the provided font with its size set. +func From(fnt Font, size Length) Font { + o := fnt + o.Size = size + return o +} + +// Typeface identifies a particular typeface design. +// The empty string denotes the default typeface. +type Typeface string + +// Variant denotes a typeface variant, such as "Mono", "Smallcaps" or "Math". +type Variant string + +// Extents contains font metric information. +type Extents struct { + // Ascent is the distance that the text + // extends above the baseline. + Ascent Length + + // Descent is the distance that the text + // extends below the baseline. The descent + // is given as a positive value. + Descent Length + + // Height is the distance from the lowest + // descending point to the highest ascending + // point. + Height Length +} + +// Face holds a font descriptor and the associated font face. +type Face struct { + Font Font + Face *opentype.Font +} + +// Name returns a fully qualified name for the given font. +func (f *Face) Name() string { + return f.Font.Name() +} + +// FontFace returns the opentype font face for the requested +// dots-per-inch resolution. +func (f *Face) FontFace(dpi float64) font.Face { + face, err := opentype.NewFace(f.Face, &opentype.FaceOptions{ + Size: f.Font.Size.Points(), + DPI: dpi, + }) + if err != nil { + panic(err) + } + return face +} + +// default hinting for OpenType fonts +const defaultHinting = font.HintingNone + +// Extents returns the FontExtents for a font. +func (f *Face) Extents() Extents { + var ( + // TODO(sbinet): re-use a Font-level sfnt.Buffer instead? + buf sfnt.Buffer + ppem = fixed.Int26_6(f.Face.UnitsPerEm()) + ) + + met, err := f.Face.Metrics(&buf, ppem, defaultHinting) + if err != nil { + panic(fmt.Errorf("could not extract font extents: %v", err)) + } + scale := f.Font.Size / Points(float64(ppem)) + return Extents{ + Ascent: Points(float64(met.Ascent)) * scale, + Descent: Points(float64(met.Descent)) * scale, + Height: Points(float64(met.Height)) * scale, + } +} + +// Width returns width of a string when drawn using the font. +func (f *Face) Width(s string) Length { + var ( + pixelsPerEm = fixed.Int26_6(f.Face.UnitsPerEm()) + + // scale converts sfnt.Unit to float64 + scale = f.Font.Size / Points(float64(pixelsPerEm)) + + width = 0 + hasPrev = false + buf sfnt.Buffer + prev, idx sfnt.GlyphIndex + hinting = defaultHinting + ) + for _, rune := range s { + var err error + idx, err = f.Face.GlyphIndex(&buf, rune) + if err != nil { + panic(fmt.Errorf("could not get glyph index: %v", err)) + } + if hasPrev { + kern, err := f.Face.Kern(&buf, prev, idx, pixelsPerEm, hinting) + switch { + case err == nil: + width += int(kern) + case errors.Is(err, sfnt.ErrNotFound): + // no-op + default: + panic(fmt.Errorf("could not get kerning: %v", err)) + } + } + adv, err := f.Face.GlyphAdvance(&buf, idx, pixelsPerEm, hinting) + if err != nil { + panic(fmt.Errorf("could not retrieve glyph's advance: %v", err)) + } + width += int(adv) + prev, hasPrev = idx, true + } + return Points(float64(width)) * scale +} + +// Collection is a collection of fonts, regrouped under a common typeface. +type Collection []Face + +// Cache collects font faces. +type Cache struct { + def Typeface + faces map[Font]*opentype.Font +} + +// We make Cache implement dummy GobDecoder and GobEncoder interfaces +// to allow plot.Plot (or any other type holding a Cache) to be (de)serialized +// with encoding/gob. +// As Cache holds opentype.Font, the reflect-based gob (de)serialization can not +// work: gob isn't happy with opentype.Font having no exported field: +// +// error: gob: type font.Cache has no exported fields +// +// FIXME(sbinet): perhaps encode/decode Cache.def typeface? + +func (c *Cache) GobEncode() ([]byte, error) { return nil, nil } +func (c *Cache) GobDecode([]byte) error { + if c.faces == nil { + c.faces = make(map[Font]*opentype.Font) + } + return nil +} + +// NewCache creates a new cache of fonts from the provided collection of +// font Faces. +// The first font Face in the collection is set to be the default one. +func NewCache(coll Collection) *Cache { + cache := &Cache{ + faces: make(map[Font]*opentype.Font, len(coll)), + } + cache.Add(coll) + return cache +} + +// Add adds a whole collection of font Faces to the font cache. +// If the cache is empty, the first font Face in the collection is set +// to be the default one. +func (c *Cache) Add(coll Collection) { + if c.faces == nil { + c.faces = make(map[Font]*opentype.Font, len(coll)) + } + for i, f := range coll { + if i == 0 && c.def == "" { + c.def = f.Font.Typeface + } + fnt := f.Font + fnt.Size = 0 // store all font descriptors with the same size. + c.faces[fnt] = f.Face + } +} + +// Lookup returns the font Face corresponding to the provided Font descriptor, +// with the provided font size set. +// +// If no matching font Face could be found, the one corresponding to +// the default typeface is selected and returned. +func (c *Cache) Lookup(fnt Font, size Length) Face { + if len(c.faces) == 0 { + return Face{} + } + + face := c.lookup(fnt) + if face == nil { + fnt.Typeface = c.def + face = c.lookup(fnt) + } + + ff := Face{ + Font: fnt, + Face: face, + } + ff.Font.Size = size + return ff +} + +// Has returns whether the cache contains the exact font descriptor. +func (c *Cache) Has(fnt Font) bool { + face := c.lookup(fnt) + return face != nil +} + +func (c *Cache) lookup(key Font) *opentype.Font { + key.Size = 0 + + tf := c.faces[key] + if tf == nil { + key := key + key.Weight = font.WeightNormal + tf = c.faces[key] + } + if tf == nil { + key := key + key.Style = font.StyleNormal + tf = c.faces[key] + } + if tf == nil { + key := key + key.Style = font.StyleNormal + key.Weight = font.WeightNormal + tf = c.faces[key] + } + + return tf +} +func weightName(w font.Weight) string { + switch w { + case font.WeightThin: + return "Thin" + case font.WeightExtraLight: + return "ExtraLight" + case font.WeightLight: + return "Light" + case font.WeightNormal: + return "Regular" + case font.WeightMedium: + return "Medium" + case font.WeightSemiBold: + return "SemiBold" + case font.WeightBold: + return "Bold" + case font.WeightExtraBold: + return "ExtraBold" + case font.WeightBlack: + return "Black" + } + return fmt.Sprintf("weight(%d)", w) +} + +func styleName(sty font.Style) string { + switch sty { + case font.StyleNormal: + return "Normal" + case font.StyleItalic: + return "Italic" + case font.StyleOblique: + return "Oblique" + } + return fmt.Sprintf("style(%d)", sty) +} diff --git a/vg/font_test.go b/font/font_test.go similarity index 52% rename from vg/font_test.go rename to font/font_test.go index 44859c03..7783b715 100644 --- a/vg/font_test.go +++ b/font/font_test.go @@ -2,38 +2,41 @@ // Use of this source code is governed by a BSD-style // license that can be found in the LICENSE file. -package vg +package font_test import ( "errors" "testing" - "golang.org/x/image/font" + stdfnt "golang.org/x/image/font" "golang.org/x/image/font/sfnt" "golang.org/x/image/math/fixed" + "gonum.org/v1/plot/font" + "gonum.org/v1/plot/font/liberation" ) func TestFontExtends(t *testing.T) { + cache := font.NewCache(liberation.Collection()) for _, tc := range []struct { - font string - want map[Length]FontExtents + font font.Font + want map[font.Length]font.Extents }{ // values obtained when gonum/plot used the package // github.com/freetype/truetype for handling fonts. { - font: "Times-Roman", - want: map[Length]FontExtents{ - 10: FontExtents{ + font: font.Font{Typeface: "Liberation", Variant: "Serif"}, + want: map[font.Length]font.Extents{ + 10: font.Extents{ Ascent: 8.9111328125, Descent: 2.1630859375, Height: 11.4990234375, }, - 12: FontExtents{ + 12: font.Extents{ Ascent: 10.693359375, Descent: 2.595703125, Height: 13.798828125, }, - 24: FontExtents{ + 24: font.Extents{ Ascent: 21.38671875, Descent: 5.19140625, Height: 27.59765625, @@ -41,19 +44,19 @@ func TestFontExtends(t *testing.T) { }, }, { - font: "Times-Bold", - want: map[Length]FontExtents{ - 10: FontExtents{ + font: font.Font{Typeface: "Liberation", Variant: "Serif", Weight: stdfnt.WeightBold}, + want: map[font.Length]font.Extents{ + 10: font.Extents{ Ascent: 8.9111328125, Descent: 2.1630859375, Height: 11.4990234375, }, - 12: FontExtents{ + 12: font.Extents{ Ascent: 10.693359375, Descent: 2.595703125, Height: 13.798828125, }, - 24: FontExtents{ + 24: font.Extents{ Ascent: 21.38671875, Descent: 5.19140625, Height: 27.59765625, @@ -61,19 +64,19 @@ func TestFontExtends(t *testing.T) { }, }, { - font: "Times-Italic", - want: map[Length]FontExtents{ - 10: FontExtents{ + font: font.Font{Typeface: "Liberation", Variant: "Serif", Style: stdfnt.StyleItalic}, + want: map[font.Length]font.Extents{ + 10: font.Extents{ Ascent: 8.9111328125, Descent: 2.1630859375, Height: 11.4990234375, }, - 12: FontExtents{ + 12: font.Extents{ Ascent: 10.693359375, Descent: 2.595703125, Height: 13.798828125, }, - 24: FontExtents{ + 24: font.Extents{ Ascent: 21.38671875, Descent: 5.19140625, Height: 27.59765625, @@ -81,19 +84,19 @@ func TestFontExtends(t *testing.T) { }, }, { - font: "Times-BoldItalic", - want: map[Length]FontExtents{ - 10: FontExtents{ + font: font.Font{Typeface: "Liberation", Variant: "Serif", Style: stdfnt.StyleItalic, Weight: stdfnt.WeightBold}, + want: map[font.Length]font.Extents{ + 10: font.Extents{ Ascent: 8.9111328125, Descent: 2.1630859375, Height: 11.4990234375, }, - 12: FontExtents{ + 12: font.Extents{ Ascent: 10.693359375, Descent: 2.595703125, Height: 13.798828125, }, - 24: FontExtents{ + 24: font.Extents{ Ascent: 21.38671875, Descent: 5.19140625, Height: 27.59765625, @@ -101,17 +104,13 @@ func TestFontExtends(t *testing.T) { }, }, } { - for _, size := range []Length{10, 12, 24} { - fnt, err := MakeFont(tc.font, size) - if err != nil { - t.Fatal(err) - } - + for _, size := range []font.Length{10, 12, 24} { + fnt := cache.Lookup(tc.font, size) got := fnt.Extents() if got, want := got, tc.want[size]; got != want { t.Errorf( "invalid font extents for %q, size=%v:\ngot= %#v\nwant=%#v", - tc.font, size, got, want, + tc.font.Name(), size, got, want, ) } } @@ -119,14 +118,12 @@ func TestFontExtends(t *testing.T) { } func TestFontWidth(t *testing.T) { - fnt, err := MakeFont("Times-Roman", 12) - if err != nil { - t.Fatal(err) - } + cache := font.NewCache(liberation.Collection()) + fnt := cache.Lookup(font.Font{Typeface: "Liberation", Variant: "Serif"}, 12) for _, tc := range []struct { txt string - want Length + want font.Length }{ // values obtained when gonum/plot used the package // github.com/freetype/truetype for handling fonts. @@ -165,10 +162,8 @@ func TestFontWidth(t *testing.T) { } func TestFontKern(t *testing.T) { - fnt, err := MakeFont("Times-Roman", 12) - if err != nil { - t.Fatal(err) - } + cache := font.NewCache(liberation.Collection()) + fnt := cache.Lookup(font.Font{Typeface: "Liberation", Variant: "Serif"}, 12) for _, tc := range []struct { txt string @@ -190,18 +185,18 @@ func TestFontKern(t *testing.T) { t1 = rune(tc.txt[1]) buf sfnt.Buffer - ppem = fixed.Int26_6(fnt.Font().UnitsPerEm()) + ppem = fixed.Int26_6(fnt.Face.UnitsPerEm()) ) - i0, err := fnt.Font().GlyphIndex(&buf, t0) + i0, err := fnt.Face.GlyphIndex(&buf, t0) if err != nil { t.Fatalf("could not find glyph %q: %+v", t0, err) } - i1, err := fnt.Font().GlyphIndex(&buf, t1) + i1, err := fnt.Face.GlyphIndex(&buf, t1) if err != nil { t.Fatalf("could not find glyph %q: %+v", t1, err) } - kern, err := fnt.Font().Kern(&buf, i0, i1, ppem, font.HintingNone) + kern, err := fnt.Face.Kern(&buf, i0, i1, ppem, stdfnt.HintingNone) switch { case err == nil: // ok @@ -217,3 +212,88 @@ func TestFontKern(t *testing.T) { }) } } + +func TestFontName(t *testing.T) { + for _, tc := range []struct { + font *font.Font + want string + }{ + { + font: &font.Font{ + Typeface: "Liberation", + Variant: "Sans", + Style: stdfnt.StyleNormal, + Weight: stdfnt.WeightNormal, + }, + want: "LiberationSans-Regular", + }, + { + font: &font.Font{ + Typeface: "Liberation", + Variant: "Sans", + Style: stdfnt.StyleItalic, + Weight: stdfnt.WeightNormal, + }, + want: "LiberationSans-Italic", + }, + { + font: &font.Font{ + Typeface: "Liberation", + Variant: "Sans", + Style: stdfnt.StyleNormal, + Weight: stdfnt.WeightBold, + }, + want: "LiberationSans-Bold", + }, + { + font: &font.Font{ + Typeface: "Liberation", + Variant: "Sans", + Style: stdfnt.StyleItalic, + Weight: stdfnt.WeightBold, + }, + want: "LiberationSans-BoldItalic", + }, + { + font: &font.Font{ + Typeface: "Liberation", + Variant: "Mono", + Style: stdfnt.StyleNormal, + Weight: stdfnt.WeightNormal, + }, + want: "LiberationMono-Regular", + }, + { + font: &font.Font{ + Typeface: "Liberation", + Variant: "Mono", + Style: stdfnt.StyleItalic, + Weight: stdfnt.WeightNormal, + }, + want: "LiberationMono-Italic", + }, + { + font: &font.Font{ + Typeface: "Liberation", + Variant: "Mono", + Style: stdfnt.StyleNormal, + Weight: stdfnt.WeightBold, + }, + want: "LiberationMono-Bold", + }, + { + font: &font.Font{ + Typeface: "Liberation", + Variant: "Mono", + Style: stdfnt.StyleItalic, + Weight: stdfnt.WeightBold, + }, + want: "LiberationMono-BoldItalic", + }, + } { + got := tc.font.Name() + if got != tc.want { + t.Errorf("invalid name: got=%q, want=%q", got, tc.want) + } + } +} diff --git a/font/liberation/liberation.go b/font/liberation/liberation.go new file mode 100644 index 00000000..434103f7 --- /dev/null +++ b/font/liberation/liberation.go @@ -0,0 +1,97 @@ +// 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 liberation exports the Liberation fonts as a font.Collection. +package liberation // import "gonum.org/v1/plot/font/liberation" + +import ( + "fmt" + "sync" + + "github.com/go-fonts/liberation/liberationmonobold" + "github.com/go-fonts/liberation/liberationmonobolditalic" + "github.com/go-fonts/liberation/liberationmonoitalic" + "github.com/go-fonts/liberation/liberationmonoregular" + "github.com/go-fonts/liberation/liberationsansbold" + "github.com/go-fonts/liberation/liberationsansbolditalic" + "github.com/go-fonts/liberation/liberationsansitalic" + "github.com/go-fonts/liberation/liberationsansregular" + "github.com/go-fonts/liberation/liberationserifbold" + "github.com/go-fonts/liberation/liberationserifbolditalic" + "github.com/go-fonts/liberation/liberationserifitalic" + "github.com/go-fonts/liberation/liberationserifregular" + stdfnt "golang.org/x/image/font" + "golang.org/x/image/font/opentype" + + "gonum.org/v1/plot/font" +) + +var ( + once sync.Once + collection font.Collection +) + +func Collection() font.Collection { + once.Do(func() { + addColl(font.Font{}, liberationserifregular.TTF) + addColl(font.Font{Style: stdfnt.StyleItalic}, liberationserifitalic.TTF) + addColl(font.Font{Weight: stdfnt.WeightBold}, liberationserifbold.TTF) + addColl(font.Font{ + Style: stdfnt.StyleItalic, + Weight: stdfnt.WeightBold, + }, liberationserifbolditalic.TTF) + + // mono variant + addColl(font.Font{Variant: "Mono"}, liberationmonoregular.TTF) + addColl(font.Font{ + Variant: "Mono", + Style: stdfnt.StyleItalic, + }, liberationmonoitalic.TTF) + addColl(font.Font{ + Variant: "Mono", + Weight: stdfnt.WeightBold, + }, liberationmonobold.TTF) + addColl(font.Font{ + Variant: "Mono", + Style: stdfnt.StyleItalic, + Weight: stdfnt.WeightBold, + }, liberationmonobolditalic.TTF) + + // sans-serif variant + addColl(font.Font{Variant: "Sans"}, liberationsansregular.TTF) + addColl(font.Font{ + Variant: "Sans", + Style: stdfnt.StyleItalic, + }, liberationsansitalic.TTF) + addColl(font.Font{ + Variant: "Sans", + Weight: stdfnt.WeightBold, + }, liberationsansbold.TTF) + addColl(font.Font{ + Variant: "Sans", + Style: stdfnt.StyleItalic, + Weight: stdfnt.WeightBold, + }, liberationsansbolditalic.TTF) + + n := len(collection) + collection = collection[:n:n] + }) + + return collection +} + +func addColl(fnt font.Font, ttf []byte) { + face, err := opentype.Parse(ttf) + if err != nil { + panic(fmt.Errorf("vg: could not parse font: %+v", err)) + } + fnt.Typeface = "Liberation" + if fnt.Variant == "" { + fnt.Variant = "Serif" + } + collection = append(collection, font.Face{ + Font: fnt, + Face: face, + }) +} diff --git a/go.mod b/go.mod index bc3cf4be..87eeb8a5 100644 --- a/go.mod +++ b/go.mod @@ -6,6 +6,7 @@ require ( gioui.org v0.0.0-20210106084211-c030065af7bc github.com/ajstarks/svgo v0.0.0-20180226025133-644b8db467af github.com/fogleman/gg v1.3.0 + github.com/go-fonts/liberation v0.1.1 github.com/go-latex/latex v0.0.0-20201220152147-94de1316b515 github.com/phpdave11/gofpdf v1.4.2 golang.org/x/exp v0.0.0-20191002040644-a1355ae1e2c3 diff --git a/go.sum b/go.sum index c3ec540b..2b35d675 100644 --- a/go.sum +++ b/go.sum @@ -12,6 +12,7 @@ github.com/fogleman/gg v1.3.0/go.mod h1:R/bRT+9gY/C5z7JzPU0zXsXHKM4/ayA+zqcVNZzP github.com/go-fonts/dejavu v0.1.0 h1:JSajPXURYqpr+Cu8U9bt8K+XcACIHWqWrvWCKyeFmVQ= github.com/go-fonts/dejavu v0.1.0/go.mod h1:4Wt4I4OU2Nq9asgDCteaAaWZOV24E+0/Pwo0gppep4g= github.com/go-fonts/latin-modern v0.2.0/go.mod h1:rQVLdDMK+mK1xscDwsqM5J8U2jrRa3T0ecnM9pNujks= +github.com/go-fonts/liberation v0.1.1 h1:wBrPaMkrXFBW3qXpXAjiKljdVUMxn9bX2ia3XjPHoik= github.com/go-fonts/liberation v0.1.1/go.mod h1:K6qoJYypsmfVjWg8KOVDQhLc8UDgIK2HYqyqAO9z7GY= github.com/go-fonts/stix v0.1.0/go.mod h1:w/c1f0ldAUlJmLBvlbkvVXLAD+tAMqobIIQpmnUIzUY= github.com/go-gl/glfw v0.0.0-20190409004039-e6da0acd62b1/go.mod h1:vR7hzQXu2zJy9AVAgeJqvqgH9Q5CA+iKCZ2gyEVpxRU= @@ -21,10 +22,8 @@ github.com/golang/freetype v0.0.0-20170609003504-e2365dfdc4a0 h1:DACJavvAHhabrF0 github.com/golang/freetype v0.0.0-20170609003504-e2365dfdc4a0/go.mod h1:E/TSTwGwJL78qG/PmXZO1EjYhfJinVAhrmmHX6Z8B9k= github.com/jung-kurt/gofpdf v1.0.0/go.mod h1:7Id9E/uU8ce6rXgefFLlgrJj/GYY22cpxn+r32jIOes= github.com/jung-kurt/gofpdf v1.0.3-0.20190309125859-24315acbbda5/go.mod h1:7Id9E/uU8ce6rXgefFLlgrJj/GYY22cpxn+r32jIOes= -github.com/jung-kurt/gofpdf v1.16.2/go.mod h1:1hl7y57EsiPAkLbOwzpzqgx1A30nQCk/YmFV8S2vmK0= github.com/phpdave11/gofpdf v1.4.2 h1:KPKiIbfwbvC/wOncwhrpRdXVj2CZTCFlw4wnoyjtHfQ= github.com/phpdave11/gofpdf v1.4.2/go.mod h1:zpO6xFn9yxo3YLyMvW8HcKWVdbNqgIfOOp2dXMnm1mY= -github.com/phpdave11/gofpdi v1.0.7/go.mod h1:vBmVV0Do6hSBHC8uKUQ71JGW+ZGQq74llk/7bXwjDoI= github.com/phpdave11/gofpdi v1.0.12/go.mod h1:vBmVV0Do6hSBHC8uKUQ71JGW+ZGQq74llk/7bXwjDoI= github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= diff --git a/text/text.go b/text/text.go index b6f88a8d..632b533a 100644 --- a/text/text.go +++ b/text/text.go @@ -14,8 +14,8 @@ import ( // Handler parses, formats and renders text. type Handler interface { - // Extents returns the FontExtents for a font. - Extents(fnt font.Font) font.FontExtents + // Extents returns the Extents of a font. + Extents(fnt font.Font) font.Extents // Lines splits a given block of text into separate lines. Lines(txt string) []string