Files
Chris Waldon 9576b659d7 text: [API] remove Text and Advances from Layout
These fields are no longer needed with the new text shaper.
Advances is redundant to the glyph information, and Text
should never be used during layout, as you should
traverse the cluster list instead. This commit also removed
the now-unused string field from the path LRU cache key.

References: https://todo.sr.ht/~eliasnaur/gio/146
Signed-off-by: Chris Waldon <christopher.waldon.dev@gmail.com>
2022-03-18 08:04:27 +01:00

276 lines
7.2 KiB
Go

// SPDX-License-Identifier: Unlicense OR MIT
package text
import (
"io"
"gioui.org/io/system"
"gioui.org/op/clip"
"github.com/go-text/typesetting/font"
"golang.org/x/image/math/fixed"
)
// A Line contains the measurements of a line of text.
type Line struct {
Layout Layout
// Width is the width of the line.
Width fixed.Int26_6
// Ascent is the height above the baseline.
Ascent fixed.Int26_6
// Descent is the height below the baseline, including
// the line gap.
Descent fixed.Int26_6
// Bounds is the visible bounds of the line.
Bounds fixed.Rectangle26_6
}
// Range describes the position and quantity of a range of text elements
// within a larger slice. The unit is usually runes of unicode data or
// glyphs of shaped font data.
type Range struct {
// Count describes the number of items represented by the Range.
Count int
// Offset describes the start position of the represented
// items within a larger list.
Offset int
}
// GlyphID uniquely identifies a glyph within a specific font.
type GlyphID = font.GID
// Glyph contains the metadata needed to render a glyph.
type Glyph struct {
// ID is this glyph's identifier within the font it was shaped with.
ID GlyphID
// ClusterIndex is the identifier for the text shaping cluster that
// this glyph is part of.
ClusterIndex int
// GlyphCount is the number of glyphs in the same cluster as this glyph.
GlyphCount int
// RuneCount is the quantity of runes in the source text that this glyph
// corresponds to.
RuneCount int
// XAdvance and YAdvance describe the distance the dot moves when
// laying out the glyph on the X or Y axis.
XAdvance, YAdvance fixed.Int26_6
// XOffset and YOffset describe offsets from the dot that should be
// applied when rendering the glyph.
XOffset, YOffset fixed.Int26_6
}
// GlyphCluster provides metadata about a sequence of indivisible shaped
// glyphs.
type GlyphCluster struct {
// Advance is the cumulative advance of all glyphs in the cluster.
Advance fixed.Int26_6
// Runes indicates the position and quantity of the runes represented by
// this cluster within the text.
Runes Range
// Glyphs indicates the position and quantity of the glyphs within this
// cluster in a Layout's Glyphs slice.
Glyphs Range
}
// RuneWidth returns the effective width of one rune for this cluster.
// If the cluster contains multiple runes, the width of the glyphs of
// the cluster is divided evenly among the runes.
func (c GlyphCluster) RuneWidth() fixed.Int26_6 {
if c.Runes.Count == 0 {
return 0
}
return c.Advance / fixed.Int26_6(c.Runes.Count)
}
type Layout struct {
// Glyphs are the actual font characters for the text. The are ordered
// from left to right regardless of the text direction of the underlying
// text.
Glyphs []Glyph
// Clusters is metadata about the shaped glyphs. It is mostly useful for
// interactive text widgets like editors. The order of clusters is logical,
// so the first cluster will describe the beginning of the text and may
// refer to the final glyphs in the Glyphs field if the text is RTL.
Clusters []GlyphCluster
// Runes describes the position of the text data this layout represents
// within the overall body of text being shaped.
Runes Range
// Direction is the layout direction of the text.
Direction system.TextDirection
}
// Slice returns a layout starting at the glyph cluster index start
// and running through the glyph cluster index end. The Offsets field
// of the returned layout is adjusted to reflect the new rune range
// covered by the layout. The returned layout will have no Clusters.
func (l Layout) Slice(start, end int) Layout {
if start == end || end == 0 || start == len(l.Clusters) {
return Layout{}
}
newRuneStart := l.Clusters[start].Runes.Offset
runesBefore := newRuneStart - l.Runes.Offset
endCluster := l.Clusters[end-1]
startCluster := l.Clusters[start]
runesAfter := l.Runes.Offset + l.Runes.Count - (endCluster.Runes.Offset + endCluster.Runes.Count)
if l.Direction.Progression() == system.TowardOrigin {
startCluster, endCluster = endCluster, startCluster
}
glyphStart := startCluster.Glyphs.Offset
glyphEnd := endCluster.Glyphs.Offset + endCluster.Glyphs.Count
out := l
out.Clusters = nil
out.Glyphs = out.Glyphs[glyphStart:glyphEnd]
out.Runes.Offset = newRuneStart
out.Runes.Count -= runesBefore + runesAfter
return out
}
// equals returns true when l2 is logically equivalent to l.
func (l Layout) equals(l2 Layout) bool {
if len(l.Glyphs) != len(l2.Glyphs) || len(l.Clusters) != len(l2.Clusters) {
return false
}
if l.Runes != l2.Runes || l.Direction != l2.Direction {
return false
}
for i := range l.Clusters {
if l.Clusters[i] != l2.Clusters[i] {
return false
}
}
for i := range l.Glyphs {
if l.Glyphs[i] != l2.Glyphs[i] {
return false
}
}
return true
}
// Style is the font style.
type Style int
// Weight is a font weight, in CSS units subtracted 400 so the zero value
// is normal text weight.
type Weight int
// Font specify a particular typeface variant, style and weight.
type Font struct {
Typeface Typeface
Variant Variant
Style Style
// Weight is the text weight. If zero, Normal is used instead.
Weight Weight
}
// Face implements text layout and shaping for a particular font. All
// methods must be safe for concurrent use.
type Face interface {
Layout(ppem fixed.Int26_6, maxWidth int, lc system.Locale, txt io.RuneReader) ([]Line, error)
Shape(ppem fixed.Int26_6, str Layout) clip.PathSpec
}
// Typeface identifies a particular typeface design. The empty
// string denotes the default typeface.
type Typeface string
// Variant denotes a typeface variant such as "Mono" or "Smallcaps".
type Variant string
type Alignment uint8
const (
Start Alignment = iota
End
Middle
)
const (
Regular Style = iota
Italic
)
const (
Thin Weight = 100 - 400
Hairline Weight = Thin
ExtraLight Weight = 200 - 400
UltraLight Weight = ExtraLight
Light Weight = 300 - 400
Normal Weight = 400 - 400
Medium Weight = 500 - 400
SemiBold Weight = 600 - 400
DemiBold Weight = SemiBold
Bold Weight = 700 - 400
ExtraBold Weight = 800 - 400
UltraBold Weight = ExtraBold
Black Weight = 900 - 400
Heavy Weight = Black
ExtraBlack Weight = 950 - 400
UltraBlack Weight = ExtraBlack
)
func (a Alignment) String() string {
switch a {
case Start:
return "Start"
case End:
return "End"
case Middle:
return "Middle"
default:
panic("invalid Alignment")
}
}
func (s Style) String() string {
switch s {
case Regular:
return "Regular"
case Italic:
return "Italic"
default:
panic("invalid Style")
}
}
func (w Weight) String() string {
switch w {
case Thin:
return "Thin"
case ExtraLight:
return "ExtraLight"
case Light:
return "Light"
case Normal:
return "Normal"
case Medium:
return "Medium"
case SemiBold:
return "SemiBold"
case Bold:
return "Bold"
case ExtraBold:
return "ExtraBold"
case Black:
return "Black"
case ExtraBlack:
return "ExtraBlack"
default:
panic("invalid Weight")
}
}
// weightDistance returns the distance value between two font weights.
func weightDistance(wa Weight, wb Weight) int {
// Avoid dealing with negative Weight values.
a := int(wa) + 400
b := int(wb) + 400
diff := a - b
if diff < 0 {
return -diff
}
return diff
}