From 0b637f549daa8cfd75ba1a5929fec4eaef76e5fa Mon Sep 17 00:00:00 2001 From: Elias Naur Date: Sat, 5 Oct 2019 19:00:11 +0200 Subject: [PATCH] text: rename Face to Family and let Face denote a family configuration While here, rename Family.Path to Shape which a more precise term. Signed-off-by: Elias Naur --- text/editor.go | 14 +++++--- text/label.go | 9 ++--- text/measure.go | 31 +++++++++++++++-- text/shape/measure.go | 81 +++++++++++++++++++++---------------------- 4 files changed, 81 insertions(+), 54 deletions(-) diff --git a/text/editor.go b/text/editor.go index 1cf198b0..404c01a6 100644 --- a/text/editor.go +++ b/text/editor.go @@ -23,10 +23,13 @@ import ( // Editor implements an editable and scrollable text area. type Editor struct { - // Face defines the font and style of the text. + // Family defines the font and style of the text. + Family Family + // Face specifies the font family configuration. Face Face - // Size is the text size. If zero, a reasonable default is used. - Size unit.Value + // Size is the text size. If zero, a default size is used. + Size unit.Value + Alignment Alignment // SingleLine force the text to stay on a single line. // SingleLine also sets the scrolling direction to @@ -241,7 +244,7 @@ func (e *Editor) Layout(gtx *layout.Context) { var stack op.StackOp stack.Push(gtx.Ops) op.TransformOp{}.Offset(lineOff).Add(gtx.Ops) - e.Face.Path(tsize, str).Add(gtx.Ops) + e.Family.Shape(e.Face, tsize, str).Add(gtx.Ops) paint.PaintOp{Rect: toRectF(clip).Sub(lineOff)}.Add(gtx.Ops) stack.Pop() } @@ -369,7 +372,8 @@ func (e *Editor) layoutText(c unit.Converter) { s = e.Hint } tsize := textSize(c, e.Size) - textLayout := e.Face.Layout(tsize, s, LayoutOptions{SingleLine: e.SingleLine, MaxWidth: e.maxWidth}) + opts := LayoutOptions{SingleLine: e.SingleLine, MaxWidth: e.maxWidth} + textLayout := e.Family.Layout(e.Face, tsize, s, opts) lines := textLayout.Lines dims := linesDimens(lines) for i := 0; i < len(lines)-1; i++ { diff --git a/text/label.go b/text/label.go index 73314e1d..adfbecba 100644 --- a/text/label.go +++ b/text/label.go @@ -19,10 +19,11 @@ import ( // Label is a widget for laying out and drawing text. type Label struct { - // Face defines the font and style of the text. + // Face defines the style of the text. Face Face // Size is the text size. If zero, a default size is used. Size unit.Value + // Material is a macro recording the material to draw the // text. Use a ColorOp for colored text. Material op.MacroOp @@ -94,10 +95,10 @@ func (l *lineIterator) Next() (String, f32.Point, bool) { return String{}, f32.Point{}, false } -func (l Label) Layout(gtx *layout.Context) { +func (l Label) Layout(gtx *layout.Context, family Family) { cs := gtx.Constraints tsize := textSize(gtx, l.Size) - textLayout := l.Face.Layout(tsize, l.Text, LayoutOptions{MaxWidth: cs.Width.Max}) + textLayout := family.Layout(l.Face, tsize, l.Text, LayoutOptions{MaxWidth: cs.Width.Max}) lines := textLayout.Lines if max := l.MaxLines; max > 0 && len(lines) > max { lines = lines[:max] @@ -124,7 +125,7 @@ func (l Label) Layout(gtx *layout.Context) { var stack op.StackOp stack.Push(gtx.Ops) op.TransformOp{}.Offset(off).Add(gtx.Ops) - l.Face.Path(tsize, str).Add(gtx.Ops) + family.Shape(l.Face, tsize, str).Add(gtx.Ops) // Set a default color in case the material is empty. paint.ColorOp{Color: color.RGBA{A: 0xff}}.Add(gtx.Ops) l.Material.Add(gtx.Ops) diff --git a/text/measure.go b/text/measure.go index 9dcd2ad7..a948d3a6 100644 --- a/text/measure.go +++ b/text/measure.go @@ -45,12 +45,27 @@ type LayoutOptions struct { SingleLine bool } -type Face interface { +// Face specify a particular configuration of a Family. +type Face struct { + // Weight is the text weight. If zero, Normal is used instead. + Weight Weight + Style Style +} + +// Style is the font style. +type Style int + +// Weight is a font weight, in CSS units. +type Weight int + +// Family implements a font family. It can layout and shape text from +// a Face and size. +type Family interface { // Layout returns the text layout for a string given a set of // options. - Layout(size float32, s string, opts LayoutOptions) *Layout + Layout(face Face, size float32, s string, opts LayoutOptions) *Layout // Path returns the ClipOp outline of a text recorded in a macro. - Path(size float32, s String) op.MacroOp + Shape(face Face, size float32, s String) op.MacroOp } type Alignment uint8 @@ -61,6 +76,16 @@ const ( Middle ) +const ( + Regular Style = iota + Italic +) + +const ( + Normal Weight = 400 + Bold Weight = 700 +) + func linesDimens(lines []Line) layout.Dimensions { var width fixed.Int26_6 var h int diff --git a/text/shape/measure.go b/text/shape/measure.go index 53f19901..e6ae257f 100644 --- a/text/shape/measure.go +++ b/text/shape/measure.go @@ -19,9 +19,14 @@ import ( "golang.org/x/image/math/fixed" ) -// Faces is a cache of text layouts and paths. -type Faces struct { - faceCache map[faceKey]*Face +// Family is an implementation of text.Family. It caches +// layouts and paths. +// A Family must specify at least the Regular font to be useful. +type Family struct { + Regular *sfnt.Font + Italic *sfnt.Font + Bold *sfnt.Font + layoutCache map[layoutKey]cachedLayout pathCache map[pathKey]cachedPath } @@ -49,20 +54,9 @@ type pathKey struct { str string } -type faceKey struct { - font *sfnt.Font -} - -// Face is a cached implementation of text.Face. -type Face struct { - faces *Faces - font *opentype -} - -// Reset the cache, discarding any measures or paths that +// Reset the cache, discarding any layouts or paths that // haven't been used since the last call to Reset. -func (f *Faces) Reset() { - f.init() +func (f *Family) Reset() { for pk, p := range f.pathCache { if !p.active { delete(f.pathCache, pk) @@ -81,62 +75,65 @@ func (f *Faces) Reset() { } } -// For returns a Face for the given font. -func (f *Faces) For(fnt *sfnt.Font) *Face { - f.init() - fk := faceKey{fnt} - if f, exist := f.faceCache[fk]; exist { - return f +// for returns a font for the given face. +func (f *Family) fontFor(face text.Face) *sfnt.Font { + var font *sfnt.Font + switch { + case face.Style == text.Italic: + font = f.Italic + case face.Weight >= 600: + font = f.Bold } - face := &Face{ - faces: f, - font: &opentype{Font: fnt, Hinting: font.HintingFull}, + if font == nil { + font = f.Regular } - f.faceCache[fk] = face - return face + return font } -func (f *Faces) init() { - if f.faceCache != nil { +func (f *Family) init() { + if f.pathCache != nil { return } - f.faceCache = make(map[faceKey]*Face) f.pathCache = make(map[pathKey]cachedPath) f.layoutCache = make(map[layoutKey]cachedLayout) } -func (f *Face) Layout(size float32, str string, opts text.LayoutOptions) *text.Layout { +func (f *Family) Layout(face text.Face, size float32, str string, opts text.LayoutOptions) *text.Layout { + f.init() + fnt := f.fontFor(face) ppem := fixed.Int26_6(size * 64) lk := layoutKey{ - f: f.font.Font, + f: fnt, ppem: ppem, str: str, opts: opts, } - if l, ok := f.faces.layoutCache[lk]; ok { + if l, ok := f.layoutCache[lk]; ok { l.active = true - f.faces.layoutCache[lk] = l + f.layoutCache[lk] = l return l.layout } - l := layoutText(ppem, str, f.font, opts) - f.faces.layoutCache[lk] = cachedLayout{active: true, layout: l} + l := layoutText(ppem, str, &opentype{Font: fnt, Hinting: font.HintingFull}, opts) + f.layoutCache[lk] = cachedLayout{active: true, layout: l} return l } -func (f *Face) Path(size float32, str text.String) op.MacroOp { +func (f *Family) Shape(face text.Face, size float32, str text.String) op.MacroOp { + f.init() + fnt := f.fontFor(face) ppem := fixed.Int26_6(size * 64) pk := pathKey{ - f: f.font.Font, + f: fnt, ppem: ppem, str: str.String, } - if p, ok := f.faces.pathCache[pk]; ok { + if p, ok := f.pathCache[pk]; ok { p.active = true - f.faces.pathCache[pk] = p + f.pathCache[pk] = p return p.path } - p := textPath(ppem, f.font, str) - f.faces.pathCache[pk] = cachedPath{active: true, path: p} + p := textPath(ppem, &opentype{Font: fnt, Hinting: font.HintingFull}, str) + f.pathCache[pk] = cachedPath{active: true, path: p} return p }