forked from tdewolff/canvas
-
Notifications
You must be signed in to change notification settings - Fork 0
/
text.go
146 lines (128 loc) · 4.24 KB
/
text.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
package text
import (
"fmt"
"github.com/jilieryuyi/canvas/font"
)
type ScriptItem struct {
Script
Level int
Text string
}
// ScriptItemizer divides the string in parts for each different script.
// Also separates on different embedding levels and runs of U+FFFC (object replacement characters)
func ScriptItemizer(runes []rune, embeddingLevels []int) []ScriptItem {
if len(runes) == 0 {
return []ScriptItem{}
}
i := 0
items := []ScriptItem{}
scripts := []Script{ScriptUnknown} // script stack for embedding levels
for j, r := range runes {
script, level := LookupScript(r), embeddingLevels[j]
if script == ScriptInherited {
if r == '\u200C' || r == '\u200D' {
script = ScriptCommon
} else {
script = scripts[level] // take leven from preceding base character
}
}
prevScript := scripts[len(scripts)-1]
prevLevel := len(scripts) - 1
if len(scripts)-1 < level {
// increase level
for len(scripts) < level {
scripts = append(scripts, ScriptUnknown)
}
scripts = append(scripts, script)
} else if level < len(scripts)-1 {
// decrease level
scripts[level] = script
scripts = scripts[:level+1]
} else if script == ScriptUnknown || script == ScriptCommon {
script = prevScript
} else {
scripts[level] = script
if prevScript == ScriptUnknown || prevScript == ScriptCommon {
prevScript = script
}
}
scriptBoundary := script != prevScript
levelBoundary := level != prevLevel
objectReplacementBoundary := 0 < j && (r == '\uFFFC') != (runes[j-1] == '\uFFFC')
if 0 < j && (levelBoundary || scriptBoundary || objectReplacementBoundary) {
items = append(items, ScriptItem{
Script: prevScript,
Level: prevLevel,
Text: string(runes[i:j]),
})
i = j
}
}
items = append(items, ScriptItem{
Script: scripts[len(scripts)-1],
Level: len(scripts) - 1,
Text: string(runes[i:]),
})
return items
}
// Glyph is a shaped glyph for the given font and font size. It specified the glyph ID, the cluster ID, its X and Y advance and offset in font units, and its representation as text.
type Glyph struct {
SFNT *font.SFNT
Size float64
Script
Vertical bool // is false for Latin/Mongolian/etc in a vertical layout
ID uint16
Cluster uint32
XAdvance int32
YAdvance int32
XOffset int32
YOffset int32
Text rune
}
func (g Glyph) Advance() float64 {
if !g.Vertical {
return float64(g.XAdvance) * g.Size / float64(g.SFNT.Head.UnitsPerEm)
} else {
return float64(-g.YAdvance) * g.Size / float64(g.SFNT.Head.UnitsPerEm)
}
}
func (g Glyph) String() string {
return fmt.Sprintf("%s GID=%v Cluster=%v Adv=(%v,%v) Off=(%v,%v)", string(g.Text), g.ID, g.Cluster, g.XAdvance, g.YAdvance, g.XOffset, g.YOffset)
}
func (g Glyph) Rotation() Rotation {
rot := NoRotation
if !g.Vertical {
rot = ScriptRotation(g.Script)
if rot == NoRotation {
rot = CW
}
}
return rot
}
// TODO: implement Liang's (soft) hyphenation algorithm? Add \u00AD at opportunities, unless \u2060 or \uFEFF is present
// IsParagraphSeparator returns true for paragraph separator runes.
func IsParagraphSeparator(r rune) bool {
// line feed, vertical tab, form feed, carriage return, next line, line separator, paragraph separator
return 0x0A <= r && r <= 0x0D || r == 0x85 || r == '\u2028' || r == '\u2029'
}
func IsSpacelessScript(script Script) bool {
// missing: S'gaw Karen
return script == Han || script == Hangul || script == Katakana || script == Khmer || script == Lao || script == PhagsPa || script == Brahmi || script == TaiTham || script == NewTaiLue || script == TaiLe || script == TaiViet || script == Thai || script == Tibetan || script == Myanmar
}
func IsVerticalScript(script Script) bool {
return script == Bopomofo || script == EgyptianHieroglyphs || script == Hiragana || script == Katakana || script == Han || script == Hangul || script == MeroiticCursive || script == MeroiticHieroglyphs || script == Mongolian || script == Ogham || script == OldTurkic || script == PhagsPa || script == Yi
}
type Rotation float64
const (
NoRotation Rotation = 0.0
CW Rotation = -90.0
CCW Rotation = 90.0
)
func ScriptRotation(script Script) Rotation {
if script == Mongolian || script == PhagsPa {
return CW
} else if script == Ogham || script == OldTurkic {
return CCW
}
return NoRotation
}