github.com/gop9/olt@v0.0.0-20200202132135-d956aad50b08/gio/text/shaper.go (about)

     1  // SPDX-License-Identifier: Unlicense OR MIT
     2  
     3  package text
     4  
     5  import (
     6  	"golang.org/x/image/font"
     7  	"unicode/utf8"
     8  
     9  	"github.com/gop9/olt/gio/op"
    10  	"github.com/gop9/olt/gio/unit"
    11  	"golang.org/x/image/math/fixed"
    12  )
    13  
    14  // Shaper implements layout and shaping of text and a cache of
    15  // computed results.
    16  //
    17  // Specify the default and fallback font by calling Register with the
    18  // empty Font.
    19  type Shaper struct {
    20  	def   Typeface
    21  	faces map[Font]*face
    22  }
    23  
    24  type face struct {
    25  	face        Face
    26  	layoutCache layoutCache
    27  	pathCache   pathCache
    28  }
    29  
    30  func (s *Shaper) Register(font Font, tf Face) {
    31  	if s.faces == nil {
    32  		s.def = font.Typeface
    33  		s.faces = make(map[Font]*face)
    34  	}
    35  	// Treat all font sizes equally.
    36  	font.Size = unit.Value{}
    37  	if font.Weight == 0 {
    38  		font.Weight = Normal
    39  	}
    40  	s.faces[font] = &face{
    41  		face: tf,
    42  	}
    43  }
    44  
    45  func (s *Shaper) Layout(c unit.Converter, font Font, str string, opts LayoutOptions) *Layout {
    46  	tf := s.faceForFont(font)
    47  	return tf.layout(fixed.I(c.Px(font.Size)), str, opts)
    48  }
    49  
    50  func (s *Shaper) Shape(c unit.Converter, font Font, str String) op.CallOp {
    51  	tf := s.faceForFont(font)
    52  	return tf.shape(fixed.I(c.Px(font.Size)), str)
    53  }
    54  
    55  func (s *Shaper) Metrics(c unit.Converter, font Font) font.Metrics {
    56  	tf := s.faceForFont(font)
    57  	return tf.metrics(fixed.I(c.Px(font.Size)))
    58  }
    59  
    60  func (s *Shaper) faceForStyle(font Font) *face {
    61  	tf := s.faces[font]
    62  	if tf == nil {
    63  		font := font
    64  		font.Weight = Normal
    65  		tf = s.faces[font]
    66  	}
    67  	if tf == nil {
    68  		font := font
    69  		font.Style = Regular
    70  		tf = s.faces[font]
    71  	}
    72  	if tf == nil {
    73  		font := font
    74  		font.Style = Regular
    75  		font.Weight = Normal
    76  		tf = s.faces[font]
    77  	}
    78  	return tf
    79  }
    80  
    81  func (s *Shaper) faceForFont(font Font) *face {
    82  	font.Size = unit.Value{}
    83  	tf := s.faceForStyle(font)
    84  	if tf == nil {
    85  		font.Typeface = s.def
    86  		tf = s.faceForStyle(font)
    87  	}
    88  	return tf
    89  }
    90  
    91  func (t *face) layout(ppem fixed.Int26_6, str string, opts LayoutOptions) *Layout {
    92  	if t == nil {
    93  		return fallbackLayout(str)
    94  	}
    95  	lk := layoutKey{
    96  		ppem: ppem,
    97  		str:  str,
    98  		opts: opts,
    99  	}
   100  	if l, ok := t.layoutCache.Get(lk); ok {
   101  		return l
   102  	}
   103  	l := t.face.Layout(ppem, str, opts)
   104  	t.layoutCache.Put(lk, l)
   105  	return l
   106  }
   107  
   108  func (t *face) shape(ppem fixed.Int26_6, str String) op.CallOp {
   109  	if t == nil {
   110  		return op.CallOp{}
   111  	}
   112  	pk := pathKey{
   113  		ppem: ppem,
   114  		str:  str.String,
   115  	}
   116  	if clip, ok := t.pathCache.Get(pk); ok {
   117  		return clip
   118  	}
   119  	clip := t.face.Shape(ppem, str)
   120  	t.pathCache.Put(pk, clip)
   121  	return clip
   122  }
   123  
   124  func (t *face) metrics(ppem fixed.Int26_6) font.Metrics {
   125  	return t.face.Metrics(ppem)
   126  }
   127  
   128  func fallbackLayout(str string) *Layout {
   129  	l := &Layout{
   130  		Lines: []Line{
   131  			{Text: String{
   132  				String: str,
   133  			}},
   134  		},
   135  	}
   136  	strlen := utf8.RuneCountInString(str)
   137  	l.Lines[0].Text.Advances = make([]fixed.Int26_6, strlen)
   138  	return l
   139  }