...

Source file src/gonum.org/v1/plot/font/font.go

Documentation: gonum.org/v1/plot/font

     1  // Copyright ©2021 The Gonum Authors. All rights reserved.
     2  // Use of this source code is governed by a BSD-style
     3  // license that can be found in the LICENSE file.
     4  
     5  package font
     6  
     7  import (
     8  	"errors"
     9  	"fmt"
    10  	"sync"
    11  
    12  	"golang.org/x/image/font"
    13  	"golang.org/x/image/font/opentype"
    14  	"golang.org/x/image/font/sfnt"
    15  	"golang.org/x/image/math/fixed"
    16  )
    17  
    18  // DefaultCache is the global cache for fonts.
    19  var DefaultCache *Cache = NewCache(nil)
    20  
    21  // Font represents a font face.
    22  type Font struct {
    23  	// Typeface identifies the Font.
    24  	Typeface Typeface
    25  
    26  	// TODO(sbinet): Gio@v0.2.0 has dropped font.Font.Variant
    27  	// we should probably follow suit.
    28  
    29  	// Variant is the variant of a font, such as "Mono" or "Smallcaps".
    30  	Variant Variant
    31  
    32  	// Style is the style of a font, such as Regular or Italic.
    33  	Style font.Style
    34  
    35  	// Weight is the weight of a font, such as Normal or Bold.
    36  	Weight font.Weight
    37  
    38  	// Size is the size of the font.
    39  	Size Length
    40  }
    41  
    42  // Name returns a fully qualified name for the given font.
    43  func (f *Font) Name() string {
    44  	v := f.Variant
    45  	w := weightName(f.Weight)
    46  	s := styleName(f.Style)
    47  
    48  	switch f.Style {
    49  	case font.StyleNormal:
    50  		s = ""
    51  		if f.Weight == font.WeightNormal {
    52  			w = "Regular"
    53  		}
    54  	default:
    55  		if f.Weight == font.WeightNormal {
    56  			w = ""
    57  		}
    58  	}
    59  
    60  	return fmt.Sprintf("%s%s-%s%s", f.Typeface, v, w, s)
    61  }
    62  
    63  // From returns a copy of the provided font with its size set.
    64  func From(fnt Font, size Length) Font {
    65  	o := fnt
    66  	o.Size = size
    67  	return o
    68  }
    69  
    70  // Typeface identifies a particular typeface design.
    71  // The empty string denotes the default typeface.
    72  type Typeface string
    73  
    74  // Variant denotes a typeface variant, such as "Mono", "Smallcaps" or "Math".
    75  type Variant string
    76  
    77  // Extents contains font metric information.
    78  type Extents struct {
    79  	// Ascent is the distance that the text
    80  	// extends above the baseline.
    81  	Ascent Length
    82  
    83  	// Descent is the distance that the text
    84  	// extends below the baseline. The descent
    85  	// is given as a positive value.
    86  	Descent Length
    87  
    88  	// Height is the distance from the lowest
    89  	// descending point to the highest ascending
    90  	// point.
    91  	Height Length
    92  }
    93  
    94  // Face holds a font descriptor and the associated font face.
    95  type Face struct {
    96  	Font Font
    97  	Face *opentype.Font
    98  }
    99  
   100  // Name returns a fully qualified name for the given font.
   101  func (f *Face) Name() string {
   102  	return f.Font.Name()
   103  }
   104  
   105  // FontFace returns the opentype font face for the requested
   106  // dots-per-inch resolution.
   107  func (f *Face) FontFace(dpi float64) font.Face {
   108  	face, err := opentype.NewFace(f.Face, &opentype.FaceOptions{
   109  		Size: f.Font.Size.Points(),
   110  		DPI:  dpi,
   111  	})
   112  	if err != nil {
   113  		panic(err)
   114  	}
   115  	return face
   116  }
   117  
   118  // default hinting for OpenType fonts
   119  const defaultHinting = font.HintingNone
   120  
   121  // Extents returns the FontExtents for a font.
   122  func (f *Face) Extents() Extents {
   123  	var (
   124  		// TODO(sbinet): re-use a Font-level sfnt.Buffer instead?
   125  		buf  sfnt.Buffer
   126  		ppem = fixed.Int26_6(f.Face.UnitsPerEm())
   127  	)
   128  
   129  	met, err := f.Face.Metrics(&buf, ppem, defaultHinting)
   130  	if err != nil {
   131  		panic(fmt.Errorf("could not extract font extents: %v", err))
   132  	}
   133  	scale := f.Font.Size / Points(float64(ppem))
   134  	return Extents{
   135  		Ascent:  Points(float64(met.Ascent)) * scale,
   136  		Descent: Points(float64(met.Descent)) * scale,
   137  		Height:  Points(float64(met.Height)) * scale,
   138  	}
   139  }
   140  
   141  // Width returns width of a string when drawn using the font.
   142  func (f *Face) Width(s string) Length {
   143  	var (
   144  		pixelsPerEm = fixed.Int26_6(f.Face.UnitsPerEm())
   145  
   146  		// scale converts sfnt.Unit to float64
   147  		scale = f.Font.Size / Points(float64(pixelsPerEm))
   148  
   149  		width     = 0
   150  		hasPrev   = false
   151  		buf       sfnt.Buffer
   152  		prev, idx sfnt.GlyphIndex
   153  		hinting   = defaultHinting
   154  	)
   155  	for _, rune := range s {
   156  		var err error
   157  		idx, err = f.Face.GlyphIndex(&buf, rune)
   158  		if err != nil {
   159  			panic(fmt.Errorf("could not get glyph index: %v", err))
   160  		}
   161  		if hasPrev {
   162  			kern, err := f.Face.Kern(&buf, prev, idx, pixelsPerEm, hinting)
   163  			switch {
   164  			case err == nil:
   165  				width += int(kern)
   166  			case errors.Is(err, sfnt.ErrNotFound):
   167  				// no-op
   168  			default:
   169  				panic(fmt.Errorf("could not get kerning: %v", err))
   170  			}
   171  		}
   172  		adv, err := f.Face.GlyphAdvance(&buf, idx, pixelsPerEm, hinting)
   173  		if err != nil {
   174  			panic(fmt.Errorf("could not retrieve glyph's advance: %v", err))
   175  		}
   176  		width += int(adv)
   177  		prev, hasPrev = idx, true
   178  	}
   179  	return Points(float64(width)) * scale
   180  }
   181  
   182  // Collection is a collection of fonts, regrouped under a common typeface.
   183  type Collection []Face
   184  
   185  // Cache collects font faces.
   186  type Cache struct {
   187  	mu    sync.RWMutex
   188  	def   Typeface
   189  	faces map[Font]*opentype.Font
   190  }
   191  
   192  // We make Cache implement dummy GobDecoder and GobEncoder interfaces
   193  // to allow plot.Plot (or any other type holding a Cache) to be (de)serialized
   194  // with encoding/gob.
   195  // As Cache holds opentype.Font, the reflect-based gob (de)serialization can not
   196  // work: gob isn't happy with opentype.Font having no exported field:
   197  //
   198  //   error: gob: type font.Cache has no exported fields
   199  //
   200  // FIXME(sbinet): perhaps encode/decode Cache.def typeface?
   201  
   202  func (c *Cache) GobEncode() ([]byte, error) { return nil, nil }
   203  func (c *Cache) GobDecode([]byte) error {
   204  	if c.faces == nil {
   205  		c.faces = make(map[Font]*opentype.Font)
   206  	}
   207  	return nil
   208  }
   209  
   210  // NewCache creates a new cache of fonts from the provided collection of
   211  // font Faces.
   212  // The first font Face in the collection is set to be the default one.
   213  func NewCache(coll Collection) *Cache {
   214  	cache := &Cache{
   215  		faces: make(map[Font]*opentype.Font, len(coll)),
   216  	}
   217  	cache.Add(coll)
   218  	return cache
   219  }
   220  
   221  // Add adds a whole collection of font Faces to the font cache.
   222  // If the cache is empty, the first font Face in the collection is set
   223  // to be the default one.
   224  func (c *Cache) Add(coll Collection) {
   225  	c.mu.Lock()
   226  	defer c.mu.Unlock()
   227  
   228  	if c.faces == nil {
   229  		c.faces = make(map[Font]*opentype.Font, len(coll))
   230  	}
   231  	for i, f := range coll {
   232  		if i == 0 && c.def == "" {
   233  			c.def = f.Font.Typeface
   234  		}
   235  		fnt := f.Font
   236  		fnt.Size = 0 // store all font descriptors with the same size.
   237  		c.faces[fnt] = f.Face
   238  	}
   239  }
   240  
   241  // Lookup returns the font Face corresponding to the provided Font descriptor,
   242  // with the provided font size set.
   243  //
   244  // If no matching font Face could be found, the one corresponding to
   245  // the default typeface is selected and returned.
   246  func (c *Cache) Lookup(fnt Font, size Length) Face {
   247  	c.mu.RLock()
   248  	defer c.mu.RUnlock()
   249  
   250  	if len(c.faces) == 0 {
   251  		return Face{}
   252  	}
   253  
   254  	face := c.lookup(fnt)
   255  	if face == nil {
   256  		fnt.Typeface = c.def
   257  		face = c.lookup(fnt)
   258  	}
   259  
   260  	ff := Face{
   261  		Font: fnt,
   262  		Face: face,
   263  	}
   264  	ff.Font.Size = size
   265  	return ff
   266  }
   267  
   268  // Has returns whether the cache contains the exact font descriptor.
   269  func (c *Cache) Has(fnt Font) bool {
   270  	c.mu.RLock()
   271  	defer c.mu.RUnlock()
   272  
   273  	face := c.lookup(fnt)
   274  	return face != nil
   275  }
   276  
   277  func (c *Cache) lookup(key Font) *opentype.Font {
   278  	key.Size = 0
   279  
   280  	tf := c.faces[key]
   281  	if tf == nil {
   282  		key := key
   283  		key.Weight = font.WeightNormal
   284  		tf = c.faces[key]
   285  	}
   286  	if tf == nil {
   287  		key := key
   288  		key.Style = font.StyleNormal
   289  		tf = c.faces[key]
   290  	}
   291  	if tf == nil {
   292  		key := key
   293  		key.Style = font.StyleNormal
   294  		key.Weight = font.WeightNormal
   295  		tf = c.faces[key]
   296  	}
   297  
   298  	return tf
   299  }
   300  
   301  func weightName(w font.Weight) string {
   302  	switch w {
   303  	case font.WeightThin:
   304  		return "Thin"
   305  	case font.WeightExtraLight:
   306  		return "ExtraLight"
   307  	case font.WeightLight:
   308  		return "Light"
   309  	case font.WeightNormal:
   310  		return "Regular"
   311  	case font.WeightMedium:
   312  		return "Medium"
   313  	case font.WeightSemiBold:
   314  		return "SemiBold"
   315  	case font.WeightBold:
   316  		return "Bold"
   317  	case font.WeightExtraBold:
   318  		return "ExtraBold"
   319  	case font.WeightBlack:
   320  		return "Black"
   321  	}
   322  	return fmt.Sprintf("weight(%d)", w)
   323  }
   324  
   325  func styleName(sty font.Style) string {
   326  	switch sty {
   327  	case font.StyleNormal:
   328  		return "Normal"
   329  	case font.StyleItalic:
   330  		return "Italic"
   331  	case font.StyleOblique:
   332  		return "Oblique"
   333  	}
   334  	return fmt.Sprintf("style(%d)", sty)
   335  }
   336  

View as plain text