|
7 | 7 | "bytes" |
8 | 8 | "compress/gzip" |
9 | 9 | _ "embed" |
| 10 | + "encoding/binary" |
| 11 | + "iter" |
| 12 | + "math" |
10 | 13 | "slices" |
11 | 14 | "sync/atomic" |
12 | 15 |
|
@@ -41,26 +44,90 @@ func init() { |
41 | 44 | theDefaultFaceSource = e |
42 | 45 | } |
43 | 46 |
|
44 | | -var ( |
45 | | - tagWght = text.MustParseTag("wght") |
46 | | - tagLiga = text.MustParseTag("liga") |
47 | | - tagTnum = text.MustParseTag("tnum") |
48 | | -) |
| 47 | +// Feature is an OpenType feature setting. |
| 48 | +type Feature struct { |
| 49 | + // Tag identifies the feature, like 'liga'. |
| 50 | + Tag text.Tag |
| 51 | + |
| 52 | + // Value is the feature value handed to the font. For typical on/off |
| 53 | + // features, 0 disables and 1 enables the feature. |
| 54 | + Value uint32 |
| 55 | +} |
| 56 | + |
| 57 | +// Variation is an OpenType variation axis setting. |
| 58 | +type Variation struct { |
| 59 | + // Tag identifies the axis, like 'wght'. |
| 60 | + Tag text.Tag |
| 61 | + |
| 62 | + // Value is the axis value. |
| 63 | + Value float32 |
| 64 | +} |
| 65 | + |
| 66 | +// taggedValues is a canonical, comparable encoding of OpenType (tag, value) |
| 67 | +// settings: 8 bytes per entry, a big-endian uint32 tag followed by a |
| 68 | +// big-endian uint32 value, sorted by tag with at most one entry per tag. |
| 69 | +type taggedValues string |
| 70 | + |
| 71 | +// uint32At returns the big-endian uint32 at byte offset i. |
| 72 | +func (t taggedValues) uint32At(i int) uint32 { |
| 73 | + return uint32(t[i])<<24 | uint32(t[i+1])<<16 | uint32(t[i+2])<<8 | uint32(t[i+3]) |
| 74 | +} |
| 75 | + |
| 76 | +// with returns t with tag set to value, keeping the canonical order. |
| 77 | +func (t taggedValues) with(tag text.Tag, value uint32) taggedValues { |
| 78 | + var buf [8]byte |
| 79 | + binary.BigEndian.PutUint32(buf[:4], uint32(tag)) |
| 80 | + binary.BigEndian.PutUint32(buf[4:], value) |
| 81 | + entry := taggedValues(buf[:]) |
| 82 | + for i := 0; i < len(t); i += 8 { |
| 83 | + switch existing := text.Tag(t.uint32At(i)); { |
| 84 | + case existing < tag: |
| 85 | + continue |
| 86 | + case existing == tag: |
| 87 | + return t[:i] + entry + t[i+8:] |
| 88 | + default: |
| 89 | + return t[:i] + entry + t[i:] |
| 90 | + } |
| 91 | + } |
| 92 | + return t + entry |
| 93 | +} |
49 | 94 |
|
50 | | -// Attributes is a comparable set of text rendering attributes: size, weight, |
51 | | -// ligatures, tabular numerals, and language. A face is resolved from |
52 | | -// Attributes together with a [Family]; the attributes alone do not identify a |
53 | | -// font. |
54 | | -// |
55 | | -// TODO: Weight, Liga, and Tnum are ad-hoc special cases. Represent them as |
56 | | -// general OpenType variations (wght) and features (liga, tnum) while keeping |
57 | | -// Attributes comparable (#131). |
| 95 | +// all iterates the settings in tag order. |
| 96 | +func (t taggedValues) all() iter.Seq2[text.Tag, uint32] { |
| 97 | + return func(yield func(text.Tag, uint32) bool) { |
| 98 | + for i := 0; i < len(t); i += 8 { |
| 99 | + if !yield(text.Tag(t.uint32At(i)), t.uint32At(i+4)) { |
| 100 | + return |
| 101 | + } |
| 102 | + } |
| 103 | + } |
| 104 | +} |
| 105 | + |
| 106 | +// Attributes is a comparable set of text rendering attributes: size, |
| 107 | +// language, and OpenType variation and feature settings. A face is resolved |
| 108 | +// from Attributes together with a [Family]; the attributes alone do not |
| 109 | +// identify a font. |
58 | 110 | type Attributes struct { |
59 | | - Size float64 |
60 | | - Weight text.Weight |
61 | | - Liga bool |
62 | | - Tnum bool |
63 | | - Lang language.Tag |
| 111 | + Size float64 |
| 112 | + Lang language.Tag |
| 113 | + |
| 114 | + // variations is the canonical encoding of the variation axis settings. |
| 115 | + variations taggedValues |
| 116 | + |
| 117 | + // features is the canonical encoding of the feature settings. |
| 118 | + features taggedValues |
| 119 | +} |
| 120 | + |
| 121 | +// WithVariation returns a with the OpenType variation axis tag set to value. |
| 122 | +func (a Attributes) WithVariation(tag text.Tag, value float32) Attributes { |
| 123 | + a.variations = a.variations.with(tag, math.Float32bits(value)) |
| 124 | + return a |
| 125 | +} |
| 126 | + |
| 127 | +// WithFeature returns a with the OpenType feature tag set to value. |
| 128 | +func (a Attributes) WithFeature(tag text.Tag, value uint32) Attributes { |
| 129 | + a.features = a.features.with(tag, value) |
| 130 | + return a |
64 | 131 | } |
65 | 132 |
|
66 | 133 | // Face is a resolved text face. |
@@ -238,16 +305,11 @@ func resolveFace(context *guigui.Context, fnt *Family, attributes Attributes) (t |
238 | 305 | Size: attributes.Size, |
239 | 306 | Language: attributes.Lang, |
240 | 307 | } |
241 | | - gtf.SetVariation(tagWght, float32(attributes.Weight)) |
242 | | - if attributes.Liga { |
243 | | - gtf.SetFeature(tagLiga, 1) |
244 | | - } else { |
245 | | - gtf.SetFeature(tagLiga, 0) |
| 308 | + for tag, value := range attributes.variations.all() { |
| 309 | + gtf.SetVariation(tag, math.Float32frombits(value)) |
246 | 310 | } |
247 | | - if attributes.Tnum { |
248 | | - gtf.SetFeature(tagTnum, 1) |
249 | | - } else { |
250 | | - gtf.SetFeature(tagTnum, 0) |
| 311 | + for tag, value := range attributes.features.all() { |
| 312 | + gtf.SetFeature(tag, value) |
251 | 313 | } |
252 | 314 |
|
253 | 315 | var f text.Face |
|
0 commit comments