1package text
2
3import (
4 "crypto/sha256"
5 "encoding/binary"
6 "errors"
7 "image"
8 "image/draw"
9 "math"
10
11 "github.com/go-text/typesetting/font"
12 "golang.org/x/image/font/sfnt"
13 "golang.org/x/image/math/fixed"
14 "golang.org/x/image/vector"
15)
16
17// Key identifies a grayscale glyph mask. Size is physical px in 26.6 units;
18// Phase is one of four horizontal quarter-pixel origins (vertical phase is zero).
19type Key struct {
20 FaceID string
21 Glyph font.GID
22 Size fixed.Int26_6
23 Variations [32]byte
24 Phase uint8
25}
26type Mask struct {
27 Alpha *image.Alpha
28 Origin image.Point
29}
30
31func VariationHash(v []font.Variation) [32]byte {
32 h := sha256.New()
33 var b [8]byte
34 for _, coord := range v {
35 binary.BigEndian.PutUint32(b[:4], uint32(coord.Tag))
36 binary.BigEndian.PutUint32(b[4:], math.Float32bits(coord.Value))
37 _, _ = h.Write(b[:])
38 }
39 var out [32]byte
40 copy(out[:], h.Sum(nil))
41 return out
42}
43func GlyphKey(face *Face, id font.GID, physicalSize, originX float64, variations []font.Variation) (Key, error) {
44 if face == nil || VariationHash(variations) != VariationHash(face.Variations) || physicalSize <= 0 || physicalSize > 4096 || math.IsNaN(physicalSize) || math.IsInf(physicalSize, 0) || math.IsNaN(originX) || math.IsInf(originX, 0) {
45 return Key{}, errors.New("invalid glyph key")
46 }
47 frac := originX - math.Floor(originX)
48 return Key{FaceID: face.ID, Glyph: id, Size: fixed.Int26_6(math.Round(physicalSize * 64)), Phase: uint8(math.Floor(frac * 4)), Variations: VariationHash(variations)}, nil
49}
50
51// Rasterize uses sfnt for static faces and go-text outlines for variable faces.
52// Neither path hints glyphs. Masks have one pixel of transparent padding for filtering.
53func Rasterize(face *Face, key Key) (Mask, error) {
54 if face == nil || key.FaceID != face.ID || key.Size <= 0 || key.Size > 4096*64 || key.Phase > 3 {
55 return Mask{}, errors.New("invalid raster request")
56 }
57 if VariationHash(face.Variations) != key.Variations {
58 return Mask{}, errors.New("variation key does not match face")
59 }
60 if key.Glyph == font.EmptyGlyph {
61 return Mask{Alpha: image.NewAlpha(image.Rect(0, 0, 0, 0))}, nil
62 }
63 var segments []outlineSegment
64 if face.Variable {
65 outline, ok := face.Shape.GlyphData(key.Glyph).(font.GlyphOutline)
66 if !ok {
67 return Mask{}, errors.New("outline unavailable")
68 }
69 scale := float64(key.Size) / 64 / float64(face.Shape.Upem())
70 for _, s := range outline.Segments {
71 seg := outlineSegment{op: int(s.Op)}
72 for i, p := range s.ArgsSlice() {
73 seg.points[i] = point{float64(p.X) * scale, -float64(p.Y) * scale}
74 }
75 segments = append(segments, seg)
76 }
77 } else {
78 if key.Glyph > 65535 {
79 return Mask{}, errors.New("glyph id out of sfnt range")
80 }
81 var buf sfnt.Buffer
82 segs, err := face.Raster.LoadGlyph(&buf, sfnt.GlyphIndex(key.Glyph), key.Size, nil)
83 if err != nil {
84 return Mask{}, err
85 }
86 for _, s := range segs {
87 seg := outlineSegment{op: int(s.Op)}
88 n := 1
89 if s.Op == sfnt.SegmentOpQuadTo {
90 n = 2
91 } else if s.Op == sfnt.SegmentOpCubeTo {
92 n = 3
93 }
94 for i, p := range s.Args[:n] {
95 seg.points[i] = point{float64(p.X) / 64, float64(p.Y) / 64}
96 }
97 segments = append(segments, seg)
98 }
99 }
100 if len(segments) == 0 {
101 return Mask{Alpha: image.NewAlpha(image.Rect(0, 0, 0, 0))}, nil
102 }
103 phase := float64(key.Phase) / 4
104 loX, loY := math.Inf(1), math.Inf(1)
105 hiX, hiY := math.Inf(-1), math.Inf(-1)
106 for _, s := range segments {
107 n := 1
108 if s.op == 2 {
109 n = 2
110 }
111 if s.op == 3 {
112 n = 3
113 }
114 for _, p := range s.points[:n] {
115 loX = math.Min(loX, p.x+phase)
116 hiX = math.Max(hiX, p.x+phase)
117 loY = math.Min(loY, p.y)
118 hiY = math.Max(hiY, p.y)
119 }
120 }
121 x0, y0 := int(math.Floor(loX))-1, int(math.Floor(loY))-1
122 x1, y1 := int(math.Ceil(hiX))+1, int(math.Ceil(hiY))+1
123 if x1-x0 > 8192 || y1-y0 > 8192 || x1 <= x0 || y1 <= y0 {
124 return Mask{}, errors.New("invalid glyph bounds")
125 }
126 dst := image.NewAlpha(image.Rect(0, 0, x1-x0, y1-y0))
127 v := vector.NewRasterizer(dst.Rect.Dx(), dst.Rect.Dy())
128 v.DrawOp = draw.Src
129 for _, s := range segments {
130 p := s.points
131 a := func(i int) (float32, float32) {
132 return float32(p[i].x + phase - float64(x0)), float32(p[i].y - float64(y0))
133 }
134 switch s.op {
135 case 0:
136 x, y := a(0)
137 v.MoveTo(x, y)
138 case 1:
139 x, y := a(0)
140 v.LineTo(x, y)
141 case 2:
142 x, y := a(0)
143 xx, yy := a(1)
144 v.QuadTo(x, y, xx, yy)
145 case 3:
146 x, y := a(0)
147 xx, yy := a(1)
148 xxx, yyy := a(2)
149 v.CubeTo(x, y, xx, yy, xxx, yyy)
150 }
151 }
152 v.Draw(dst, dst.Bounds(), image.Opaque, image.Point{})
153 return Mask{Alpha: dst, Origin: image.Pt(x0, y0)}, nil
154}
155
156type point struct{ x, y float64 }
157type outlineSegment struct {
158 op int
159 points [3]point
160}