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 }