1
0

gotext.go 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408
  1. // Copyright 2023 The Ebitengine Authors
  2. //
  3. // Licensed under the Apache License, Version 2.0 (the "License");
  4. // you may not use this file except in compliance with the License.
  5. // You may obtain a copy of the License at
  6. //
  7. // http://www.apache.org/licenses/LICENSE-2.0
  8. //
  9. // Unless required by applicable law or agreed to in writing, software
  10. // distributed under the License is distributed on an "AS IS" BASIS,
  11. // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  12. // See the License for the specific language governing permissions and
  13. // limitations under the License.
  14. package text
  15. import (
  16. "bytes"
  17. "encoding/binary"
  18. "fmt"
  19. "github.com/go-text/typesetting/di"
  20. "github.com/go-text/typesetting/font"
  21. glanguage "github.com/go-text/typesetting/language"
  22. "github.com/go-text/typesetting/shaping"
  23. "golang.org/x/image/math/fixed"
  24. "golang.org/x/text/language"
  25. "github.com/hajimehoshi/ebiten/v2"
  26. "github.com/hajimehoshi/ebiten/v2/vector"
  27. )
  28. var _ Face = (*GoTextFace)(nil)
  29. // GoTextFace is a Face implementation for go-text's font.Face (github.com/go-text/typesetting).
  30. // With a GoTextFace, shaping.HarfBuzzShaper is always used as a shaper internally.
  31. // GoTextFace includes the source and various options.
  32. //
  33. // Unlike GoXFace, one GoTextFace instance doesn't have its own glyph image cache.
  34. // Instead, a GoTextFaceSource has a glyph image cache.
  35. // You can casually create multiple GoTextFace instances from the same GoTextFaceSource.
  36. type GoTextFace struct {
  37. // Source is the font face source.
  38. Source *GoTextFaceSource
  39. // Direction is the rendering direction.
  40. // The default (zero) value is left-to-right horizontal.
  41. Direction Direction
  42. // Size is the font size in pixels.
  43. //
  44. // This package creates glyph images for each size. Thus, gradual change of font size is not efficient.
  45. // If you want to change the font size gradually, draw the text on an offscreen with a larger size and scale it down.
  46. Size float64
  47. // Language is a hint for a language (BCP 47).
  48. Language language.Tag
  49. // Script is a hint for a script code hint of (ISO 15924).
  50. // If this is empty, the script is guessed from the specified language.
  51. Script language.Script
  52. variations []font.Variation
  53. features []shaping.FontFeature
  54. variationsString string
  55. featuresString string
  56. }
  57. // SetVariation sets a variation value.
  58. // For font variations, see https://developer.mozilla.org/en-US/docs/Web/CSS/CSS_fonts/Variable_fonts_guide for more details.
  59. func (g *GoTextFace) SetVariation(tag Tag, value float32) {
  60. idx := len(g.variations)
  61. for i, v := range g.variations {
  62. if uint32(v.Tag) < uint32(tag) {
  63. continue
  64. }
  65. if uint32(v.Tag) > uint32(tag) {
  66. idx = i
  67. break
  68. }
  69. if v.Value == value {
  70. return
  71. }
  72. g.variations[i].Value = value
  73. g.variationsString = ""
  74. return
  75. }
  76. // Keep the alphabetical order in order to make the cache key deterministic.
  77. g.variations = append(g.variations, font.Variation{})
  78. copy(g.variations[idx+1:], g.variations[idx:])
  79. g.variations[idx] = font.Variation{
  80. Tag: font.Tag(tag),
  81. Value: value,
  82. }
  83. g.variationsString = ""
  84. }
  85. // RemoveVariation removes a variation value.
  86. func (g *GoTextFace) RemoveVariation(tag Tag) {
  87. for i, v := range g.variations {
  88. if uint32(v.Tag) < uint32(tag) {
  89. continue
  90. }
  91. if uint32(v.Tag) > uint32(tag) {
  92. return
  93. }
  94. copy(g.variations[i:], g.variations[i+1:])
  95. g.variations = g.variations[:len(g.variations)-1]
  96. g.variationsString = ""
  97. return
  98. }
  99. }
  100. // SetFeature sets a feature value.
  101. // For font features, see https://developer.mozilla.org/en-US/docs/Web/CSS/CSS_fonts/OpenType_fonts_guide for more details.
  102. func (g *GoTextFace) SetFeature(tag Tag, value uint32) {
  103. idx := len(g.features)
  104. for i, f := range g.features {
  105. if uint32(f.Tag) < uint32(tag) {
  106. continue
  107. }
  108. if uint32(f.Tag) > uint32(tag) {
  109. idx = i
  110. break
  111. }
  112. if f.Value == value {
  113. return
  114. }
  115. g.features[i].Value = value
  116. g.featuresString = ""
  117. return
  118. }
  119. // Keep the alphabetical order in order to make the cache key deterministic.
  120. g.features = append(g.features, shaping.FontFeature{})
  121. copy(g.features[idx+1:], g.features[idx:])
  122. g.features[idx] = shaping.FontFeature{
  123. Tag: font.Tag(tag),
  124. Value: value,
  125. }
  126. g.featuresString = ""
  127. }
  128. // RemoveFeature removes a feature value.
  129. func (g *GoTextFace) RemoveFeature(tag Tag) {
  130. for i, v := range g.features {
  131. if uint32(v.Tag) < uint32(tag) {
  132. continue
  133. }
  134. if uint32(v.Tag) > uint32(tag) {
  135. return
  136. }
  137. copy(g.features[i:], g.features[i+1:])
  138. g.features = g.features[:len(g.features)-1]
  139. g.featuresString = ""
  140. return
  141. }
  142. }
  143. // Tag is a tag for font variations and features.
  144. // Tag is a 4-byte value like 'cmap'.
  145. type Tag uint32
  146. // String returns the Tag's string representation.
  147. func (t Tag) String() string {
  148. return string([]byte{byte(t >> 24), byte(t >> 16), byte(t >> 8), byte(t)})
  149. }
  150. // ParseTag converts a string to Tag.
  151. func ParseTag(str string) (Tag, error) {
  152. if len(str) != 4 {
  153. return 0, fmt.Errorf("text: a string's length must be 4 but was %d at ParseTag", len(str))
  154. }
  155. return Tag((uint32(str[0]) << 24) | (uint32(str[1]) << 16) | (uint32(str[2]) << 8) | uint32(str[3])), nil
  156. }
  157. // MustParseTag converts a string to Tag.
  158. // If parsing fails, MustParseTag panics.
  159. func MustParseTag(str string) Tag {
  160. t, err := ParseTag(str)
  161. if err != nil {
  162. panic(err)
  163. }
  164. return t
  165. }
  166. // Metrics implements Face.
  167. func (g *GoTextFace) Metrics() Metrics {
  168. scale := g.Source.scale(g.Size)
  169. var m Metrics
  170. if h, ok := g.Source.f.FontHExtents(); ok {
  171. m.HLineGap = float64(h.LineGap) * scale
  172. m.HAscent = float64(h.Ascender) * scale
  173. m.HDescent = float64(-h.Descender) * scale
  174. }
  175. if v, ok := g.Source.f.FontVExtents(); ok {
  176. m.VLineGap = float64(v.LineGap) * scale
  177. m.VAscent = float64(v.Ascender) * scale
  178. m.VDescent = float64(-v.Descender) * scale
  179. }
  180. m.XHeight = float64(g.Source.f.LineMetric(font.XHeight)) * scale
  181. m.CapHeight = float64(g.Source.f.LineMetric(font.CapHeight)) * scale
  182. // XHeight and CapHeight might not be correct for some old fonts (go-text/typesetting#169).
  183. if m.XHeight <= 0 {
  184. if _, gs := g.Source.shape("x", g); len(gs) > 0 {
  185. m.XHeight = fixed26_6ToFloat64(-gs[0].bounds.Min.Y)
  186. }
  187. }
  188. if m.CapHeight <= 0 {
  189. if _, gs := g.Source.shape("H", g); len(gs) > 0 {
  190. m.CapHeight = fixed26_6ToFloat64(-gs[0].bounds.Min.Y)
  191. }
  192. }
  193. return m
  194. }
  195. func (g *GoTextFace) ensureVariationsString() string {
  196. if g.variationsString != "" {
  197. return g.variationsString
  198. }
  199. if len(g.variations) == 0 {
  200. return ""
  201. }
  202. var buf bytes.Buffer
  203. for _, t := range g.variations {
  204. _ = binary.Write(&buf, binary.LittleEndian, t.Tag)
  205. _ = binary.Write(&buf, binary.LittleEndian, t.Value)
  206. }
  207. g.variationsString = buf.String()
  208. return g.variationsString
  209. }
  210. func (g *GoTextFace) ensureFeaturesString() string {
  211. if g.featuresString != "" {
  212. return g.featuresString
  213. }
  214. if len(g.features) == 0 {
  215. return ""
  216. }
  217. var buf bytes.Buffer
  218. for _, t := range g.features {
  219. _ = binary.Write(&buf, binary.LittleEndian, t.Tag)
  220. _ = binary.Write(&buf, binary.LittleEndian, t.Value)
  221. }
  222. g.featuresString = buf.String()
  223. return g.featuresString
  224. }
  225. func (g *GoTextFace) outputCacheKey(text string) goTextOutputCacheKey {
  226. return goTextOutputCacheKey{
  227. text: text,
  228. direction: g.Direction,
  229. size: g.Size,
  230. language: g.Language.String(),
  231. script: g.Script.String(),
  232. variations: g.ensureVariationsString(),
  233. features: g.ensureFeaturesString(),
  234. }
  235. }
  236. func (g *GoTextFace) diDirection() di.Direction {
  237. switch g.Direction {
  238. case DirectionLeftToRight:
  239. return di.DirectionLTR
  240. case DirectionRightToLeft:
  241. return di.DirectionRTL
  242. default:
  243. return di.DirectionTTB
  244. }
  245. }
  246. func (g *GoTextFace) gScript() glanguage.Script {
  247. var str string
  248. if g.Script != (language.Script{}) {
  249. str = g.Script.String()
  250. } else {
  251. s, _ := g.Language.Script()
  252. str = s.String()
  253. }
  254. s, err := glanguage.ParseScript(str)
  255. if err != nil {
  256. panic(err)
  257. }
  258. return s
  259. }
  260. // advance implements Face.
  261. func (g *GoTextFace) advance(text string) float64 {
  262. outputs, _ := g.Source.shape(text, g)
  263. var a fixed.Int26_6
  264. for _, output := range outputs {
  265. a += output.Advance
  266. }
  267. if g.direction().isHorizontal() {
  268. return fixed26_6ToFloat64(a)
  269. }
  270. return -fixed26_6ToFloat64(a)
  271. }
  272. // hasGlyph implements Face.
  273. func (g *GoTextFace) hasGlyph(r rune) bool {
  274. _, ok := g.Source.f.Cmap.Lookup(r)
  275. return ok
  276. }
  277. // appendGlyphsForLine implements Face.
  278. func (g *GoTextFace) appendGlyphsForLine(glyphs []Glyph, line string, indexOffset int, originX, originY float64) []Glyph {
  279. origin := fixed.Point26_6{
  280. X: float64ToFixed26_6(originX),
  281. Y: float64ToFixed26_6(originY),
  282. }
  283. _, gs := g.Source.shape(line, g)
  284. for _, glyph := range gs {
  285. o := origin.Add(fixed.Point26_6{
  286. X: glyph.shapingGlyph.XOffset,
  287. Y: -glyph.shapingGlyph.YOffset,
  288. })
  289. // imgX and imgY are integers so that the nearest filter can be used.
  290. img, imgX, imgY := g.glyphImage(glyph, o)
  291. // Append a glyph even if img is nil.
  292. // This is necessary to return index information for control characters.
  293. glyphs = append(glyphs, Glyph{
  294. StartIndexInBytes: indexOffset + glyph.startIndex,
  295. EndIndexInBytes: indexOffset + glyph.endIndex,
  296. GID: uint32(glyph.shapingGlyph.GlyphID),
  297. Image: img,
  298. X: float64(imgX),
  299. Y: float64(imgY),
  300. OriginX: fixed26_6ToFloat64(origin.X),
  301. OriginY: fixed26_6ToFloat64(origin.Y),
  302. OriginOffsetX: fixed26_6ToFloat64(glyph.shapingGlyph.XOffset),
  303. OriginOffsetY: fixed26_6ToFloat64(-glyph.shapingGlyph.YOffset),
  304. })
  305. origin = origin.Add(fixed.Point26_6{
  306. X: glyph.shapingGlyph.XAdvance,
  307. Y: -glyph.shapingGlyph.YAdvance,
  308. })
  309. }
  310. return glyphs
  311. }
  312. func (g *GoTextFace) glyphImage(glyph glyph, origin fixed.Point26_6) (*ebiten.Image, int, int) {
  313. if g.direction().isHorizontal() {
  314. origin.X = adjustGranularity(origin.X, g)
  315. origin.Y &^= ((1 << 6) - 1)
  316. } else {
  317. origin.X &^= ((1 << 6) - 1)
  318. origin.Y = adjustGranularity(origin.Y, g)
  319. }
  320. b := glyph.bounds
  321. subpixelOffset := fixed.Point26_6{
  322. X: (origin.X + b.Min.X) & ((1 << 6) - 1),
  323. Y: (origin.Y + b.Min.Y) & ((1 << 6) - 1),
  324. }
  325. key := goTextGlyphImageCacheKey{
  326. gid: glyph.shapingGlyph.GlyphID,
  327. xoffset: subpixelOffset.X,
  328. yoffset: subpixelOffset.Y,
  329. variations: g.ensureVariationsString(),
  330. }
  331. img := g.Source.getOrCreateGlyphImage(g, key, func() (*ebiten.Image, bool) {
  332. img := segmentsToImage(glyph.scaledSegments, subpixelOffset, b)
  333. return img, img != nil
  334. })
  335. imgX := (origin.X + b.Min.X).Floor()
  336. imgY := (origin.Y + b.Min.Y).Floor()
  337. return img, imgX, imgY
  338. }
  339. // appendVectorPathForLine implements Face.
  340. func (g *GoTextFace) appendVectorPathForLine(path *vector.Path, line string, originX, originY float64) {
  341. origin := fixed.Point26_6{
  342. X: float64ToFixed26_6(originX),
  343. Y: float64ToFixed26_6(originY),
  344. }
  345. _, gs := g.Source.shape(line, g)
  346. for _, glyph := range gs {
  347. appendVectorPathFromSegments(path, glyph.scaledSegments, fixed26_6ToFloat32(origin.X), fixed26_6ToFloat32(origin.Y))
  348. origin = origin.Add(fixed.Point26_6{
  349. X: glyph.shapingGlyph.XAdvance,
  350. Y: -glyph.shapingGlyph.YAdvance,
  351. })
  352. }
  353. }
  354. // direction implements Face.
  355. func (g *GoTextFace) direction() Direction {
  356. return g.Direction
  357. }
  358. // private implements Face.
  359. func (g *GoTextFace) private() {
  360. }