gotextfacesource.go 7.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280
  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. "io"
  18. "slices"
  19. "github.com/go-text/typesetting/font"
  20. "github.com/go-text/typesetting/font/opentype"
  21. "github.com/go-text/typesetting/language"
  22. "github.com/go-text/typesetting/shaping"
  23. "golang.org/x/image/math/fixed"
  24. "github.com/hajimehoshi/ebiten/v2"
  25. )
  26. type goTextOutputCacheKey struct {
  27. text string
  28. direction Direction
  29. size float64
  30. language string
  31. script string
  32. variations string
  33. features string
  34. }
  35. type glyph struct {
  36. shapingGlyph *shaping.Glyph
  37. startIndex int
  38. endIndex int
  39. scaledSegments []opentype.Segment
  40. bounds fixed.Rectangle26_6
  41. }
  42. type goTextOutputCacheValue struct {
  43. outputs []shaping.Output
  44. glyphs []glyph
  45. }
  46. type goTextGlyphImageCacheKey struct {
  47. gid opentype.GID
  48. xoffset fixed.Int26_6
  49. yoffset fixed.Int26_6
  50. variations string
  51. }
  52. // GoTextFaceSource is a source of a GoTextFace. This can be shared by multiple GoTextFace objects.
  53. type GoTextFaceSource struct {
  54. f *font.Face
  55. metadata Metadata
  56. outputCache *cache[goTextOutputCacheKey, goTextOutputCacheValue]
  57. glyphImageCache map[float64]*cache[goTextGlyphImageCacheKey, *ebiten.Image]
  58. addr *GoTextFaceSource
  59. shaper shaping.HarfbuzzShaper
  60. }
  61. func toFontResource(source io.Reader) (font.Resource, error) {
  62. // font.Resource has io.Seeker and io.ReaderAt in addition to io.Reader.
  63. // If source has it, use it as it is.
  64. if s, ok := source.(font.Resource); ok {
  65. return s, nil
  66. }
  67. // Read all the bytes and convert this to bytes.Reader.
  68. // This is a very rough solution, but it works.
  69. // TODO: Implement io.ReaderAt in a more efficient way when source is io.Seeker.
  70. bs, err := io.ReadAll(source)
  71. if err != nil {
  72. return nil, err
  73. }
  74. return bytes.NewReader(bs), nil
  75. }
  76. // NewGoTextFaceSource parses an OpenType or TrueType font and returns a GoTextFaceSource object.
  77. func NewGoTextFaceSource(source io.Reader) (*GoTextFaceSource, error) {
  78. src, err := toFontResource(source)
  79. if err != nil {
  80. return nil, err
  81. }
  82. l, err := opentype.NewLoader(src)
  83. if err != nil {
  84. return nil, err
  85. }
  86. f, err := font.NewFont(l)
  87. if err != nil {
  88. return nil, err
  89. }
  90. s := &GoTextFaceSource{
  91. f: font.NewFace(f),
  92. }
  93. s.addr = s
  94. s.metadata = metadataFromLoader(l)
  95. s.outputCache = newCache[goTextOutputCacheKey, goTextOutputCacheValue](512)
  96. return s, nil
  97. }
  98. // NewGoTextFaceSourcesFromCollection parses an OpenType or TrueType font collection and returns a slice of GoTextFaceSource objects.
  99. func NewGoTextFaceSourcesFromCollection(source io.Reader) ([]*GoTextFaceSource, error) {
  100. src, err := toFontResource(source)
  101. if err != nil {
  102. return nil, err
  103. }
  104. ls, err := opentype.NewLoaders(src)
  105. if err != nil {
  106. return nil, err
  107. }
  108. sources := make([]*GoTextFaceSource, len(ls))
  109. for i, l := range ls {
  110. f, err := font.NewFont(l)
  111. if err != nil {
  112. return nil, err
  113. }
  114. s := &GoTextFaceSource{
  115. f: &font.Face{Font: f},
  116. }
  117. s.addr = s
  118. s.metadata = metadataFromLoader(l)
  119. sources[i] = s
  120. }
  121. return sources, nil
  122. }
  123. func (g *GoTextFaceSource) copyCheck() {
  124. if g.addr != g {
  125. panic("text: illegal use of non-zero GoTextFaceSource copied by value")
  126. }
  127. }
  128. // Metadata returns its metadata.
  129. func (g *GoTextFaceSource) Metadata() Metadata {
  130. return g.metadata
  131. }
  132. // UnsafeInternal returns its font.Face.
  133. // The return value type is any since github.com/go-text/typesettings's API is now unstable.
  134. //
  135. // UnsafeInternal is unsafe since this might make internal cache states out of sync.
  136. //
  137. // UnsafeInternal might have breaking changes even in the same major version.
  138. func (g *GoTextFaceSource) UnsafeInternal() any {
  139. return g.f
  140. }
  141. func (g *GoTextFaceSource) shape(text string, face *GoTextFace) ([]shaping.Output, []glyph) {
  142. g.copyCheck()
  143. key := face.outputCacheKey(text)
  144. e := g.outputCache.getOrCreate(key, func() (goTextOutputCacheValue, bool) {
  145. outputs, gs := g.shapeImpl(text, face)
  146. return goTextOutputCacheValue{
  147. outputs: outputs,
  148. glyphs: gs,
  149. }, true
  150. })
  151. return e.outputs, e.glyphs
  152. }
  153. func (g *GoTextFaceSource) shapeImpl(text string, face *GoTextFace) ([]shaping.Output, []glyph) {
  154. f := face.Source.f
  155. f.SetVariations(face.variations)
  156. runes := []rune(text)
  157. input := shaping.Input{
  158. Text: runes,
  159. RunStart: 0,
  160. RunEnd: len(runes),
  161. Direction: face.diDirection(),
  162. Face: f,
  163. FontFeatures: face.features,
  164. Size: float64ToFixed26_6(face.Size),
  165. Script: face.gScript(),
  166. Language: language.Language(face.Language.String()),
  167. }
  168. var seg shaping.Segmenter
  169. inputs := seg.Split(input, &singleFontmap{face: f})
  170. // Reverse the input for RTL texts.
  171. if face.Direction == DirectionRightToLeft {
  172. slices.Reverse(inputs)
  173. }
  174. outputs := make([]shaping.Output, len(inputs))
  175. var gs []glyph
  176. for i, input := range inputs {
  177. out := g.shaper.Shape(input)
  178. outputs[i] = out
  179. (shaping.Line{out}).AdjustBaselines()
  180. var indices []int
  181. for i := range text {
  182. indices = append(indices, i)
  183. }
  184. indices = append(indices, len(text))
  185. for _, gl := range out.Glyphs {
  186. gl := gl
  187. var segs []opentype.Segment
  188. switch data := g.f.GlyphData(gl.GlyphID).(type) {
  189. case font.GlyphOutline:
  190. if out.Direction.IsSideways() {
  191. data.Sideways(fixed26_6ToFloat32(-gl.YOffset) / fixed26_6ToFloat32(out.Size) * float32(f.Upem()))
  192. }
  193. segs = data.Segments
  194. case font.GlyphSVG:
  195. segs = data.Outline.Segments
  196. case font.GlyphBitmap:
  197. if data.Outline != nil {
  198. segs = data.Outline.Segments
  199. }
  200. }
  201. scaledSegs := make([]opentype.Segment, len(segs))
  202. scale := float32(g.scale(fixed26_6ToFloat64(out.Size)))
  203. for i, seg := range segs {
  204. scaledSegs[i] = seg
  205. for j := range seg.Args {
  206. scaledSegs[i].Args[j].X *= scale
  207. scaledSegs[i].Args[j].Y *= -scale
  208. }
  209. }
  210. gs = append(gs, glyph{
  211. shapingGlyph: &gl,
  212. startIndex: indices[gl.ClusterIndex],
  213. endIndex: indices[gl.ClusterIndex+gl.RuneCount],
  214. scaledSegments: scaledSegs,
  215. bounds: segmentsToBounds(scaledSegs),
  216. })
  217. }
  218. }
  219. return outputs, gs
  220. }
  221. func (g *GoTextFaceSource) scale(size float64) float64 {
  222. return size / float64(g.f.Upem())
  223. }
  224. func (g *GoTextFaceSource) getOrCreateGlyphImage(goTextFace *GoTextFace, key goTextGlyphImageCacheKey, create func() (*ebiten.Image, bool)) *ebiten.Image {
  225. if g.glyphImageCache == nil {
  226. g.glyphImageCache = map[float64]*cache[goTextGlyphImageCacheKey, *ebiten.Image]{}
  227. }
  228. if _, ok := g.glyphImageCache[goTextFace.Size]; !ok {
  229. g.glyphImageCache[goTextFace.Size] = newCache[goTextGlyphImageCacheKey, *ebiten.Image](128 * glyphVariationCount(goTextFace))
  230. }
  231. return g.glyphImageCache[goTextFace.Size].getOrCreate(key, create)
  232. }
  233. type singleFontmap struct {
  234. face *font.Face
  235. }
  236. func (s *singleFontmap) ResolveFace(r rune) *font.Face {
  237. return s.face
  238. }