render.go 5.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160
  1. package render
  2. import (
  3. "image/color"
  4. "image/draw"
  5. "math"
  6. "github.com/go-text/typesetting/font"
  7. "github.com/go-text/typesetting/opentype/api"
  8. "github.com/go-text/typesetting/shaping"
  9. "github.com/srwiley/rasterx"
  10. "golang.org/x/image/math/fixed"
  11. )
  12. // Renderer defines a type that can render strings to a bitmap canvas.
  13. // The size and look of output depends on the various fields in this struct.
  14. // Developers should provide suitable output images for their draw requests.
  15. // This type is not thread safe so instances should be used from only 1 goroutine.
  16. type Renderer struct {
  17. // FontSize defines the point size of output text, commonly between 10 and 14 for regular text
  18. FontSize float32
  19. // PixScale is used to indicate the pixel density of your output target.
  20. // For example on a hi-DPI (or "retina") display this may be 2.0.
  21. // Default value is 1.0, meaning 1 pixel on the image for each render pixel.
  22. PixScale float32
  23. // Color is the pen colour for rendering
  24. Color color.Color
  25. segmenter shaping.Segmenter
  26. shaper shaping.Shaper
  27. filler *rasterx.Filler
  28. fillerScale float32
  29. }
  30. func (r *Renderer) shape(str string, face font.Face) (_ shaping.Line, ascent int) {
  31. text := []rune(str)
  32. in := shaping.Input{
  33. Text: text,
  34. RunStart: 0,
  35. RunEnd: len(text),
  36. Face: face,
  37. Size: fixed.I(int(r.FontSize)),
  38. }
  39. runs := r.segmenter.Split(in, singleFontMap{face})
  40. line := make(shaping.Line, len(runs))
  41. shaper := r.cachedShaper()
  42. for i, run := range runs {
  43. line[i] = shaper.Shape(run)
  44. if a := line[i].LineBounds.Ascent.Ceil(); a > ascent {
  45. ascent = a
  46. }
  47. }
  48. return line, ascent
  49. }
  50. // DrawString will rasterise the given string into the output image using the specified font face.
  51. // The text will be drawn starting at the left edge, down from the image top by the
  52. // font ascent value, so that the text is all visible.
  53. // The return value is the X pixel position of the end of the drawn string.
  54. func (r *Renderer) DrawString(str string, img draw.Image, face font.Face) int {
  55. line, ascent := r.shape(str, face)
  56. x := 0
  57. for _, run := range line {
  58. x = r.DrawShapedRunAt(run, img, x, ascent)
  59. }
  60. return x
  61. }
  62. // DrawStringAt will rasterise the given string into the output image using the specified font face.
  63. // The text will be drawn starting at the x, y pixel position.
  64. // Note that x and y are not multiplied by the `PixScale` value as they refer to output coordinates.
  65. // The return value is the X pixel position of the end of the drawn string.
  66. func (r *Renderer) DrawStringAt(str string, img draw.Image, x, y int, face font.Face) int {
  67. line, _ := r.shape(str, face)
  68. for _, run := range line {
  69. x = r.DrawShapedRunAt(run, img, x, y)
  70. }
  71. return x
  72. }
  73. // DrawShapedRunAt will rasterise the given shaper run into the output image using font face referenced in the shaping.
  74. // The text will be drawn starting at the startX, startY pixel position.
  75. // Note that startX and startY are not multiplied by the `PixScale` value as they refer to output coordinates.
  76. // The return value is the X pixel position of the end of the drawn string.
  77. func (r *Renderer) DrawShapedRunAt(run shaping.Output, img draw.Image, startX, startY int) int {
  78. if r.PixScale == 0 {
  79. r.PixScale = 1
  80. }
  81. scale := r.FontSize * r.PixScale / float32(run.Face.Upem())
  82. r.fillerScale = scale
  83. b := img.Bounds()
  84. scanner := rasterx.NewScannerGV(b.Dx(), b.Dy(), img, b)
  85. f := rasterx.NewFiller(b.Dx(), b.Dy(), scanner)
  86. r.filler = f
  87. f.SetColor(r.Color)
  88. x := float32(startX)
  89. y := float32(startY)
  90. for _, g := range run.Glyphs {
  91. xPos := x + fixed266ToFloat(g.XOffset)*r.PixScale
  92. yPos := y - fixed266ToFloat(g.YOffset)*r.PixScale
  93. data := run.Face.GlyphData(g.GlyphID)
  94. switch format := data.(type) {
  95. case api.GlyphOutline:
  96. r.drawOutline(g, format, f, scale, xPos, yPos)
  97. case api.GlyphBitmap:
  98. _ = r.drawBitmap(g, format, img, xPos, yPos)
  99. case api.GlyphSVG:
  100. _ = r.drawSVG(g, format, img, xPos, yPos)
  101. }
  102. x += fixed266ToFloat(g.XAdvance) * r.PixScale
  103. }
  104. f.Draw()
  105. r.filler = nil
  106. return int(math.Ceil(float64(x)))
  107. }
  108. func (r *Renderer) cachedShaper() shaping.Shaper {
  109. if r.shaper == nil {
  110. r.shaper = &shaping.HarfbuzzShaper{}
  111. }
  112. return r.shaper
  113. }
  114. func (r *Renderer) drawOutline(g shaping.Glyph, bitmap api.GlyphOutline, f *rasterx.Filler, scale float32, x, y float32) {
  115. for _, s := range bitmap.Segments {
  116. switch s.Op {
  117. case api.SegmentOpMoveTo:
  118. f.Start(fixed.Point26_6{X: floatToFixed266(s.Args[0].X*scale + x), Y: floatToFixed266(-s.Args[0].Y*scale + y)})
  119. case api.SegmentOpLineTo:
  120. f.Line(fixed.Point26_6{X: floatToFixed266(s.Args[0].X*scale + x), Y: floatToFixed266(-s.Args[0].Y*scale + y)})
  121. case api.SegmentOpQuadTo:
  122. f.QuadBezier(fixed.Point26_6{X: floatToFixed266(s.Args[0].X*scale + x), Y: floatToFixed266(-s.Args[0].Y*scale + y)},
  123. fixed.Point26_6{X: floatToFixed266(s.Args[1].X*scale + x), Y: floatToFixed266(-s.Args[1].Y*scale + y)})
  124. case api.SegmentOpCubeTo:
  125. f.CubeBezier(fixed.Point26_6{X: floatToFixed266(s.Args[0].X*scale + x), Y: floatToFixed266(-s.Args[0].Y*scale + y)},
  126. fixed.Point26_6{X: floatToFixed266(s.Args[1].X*scale + x), Y: floatToFixed266(-s.Args[1].Y*scale + y)},
  127. fixed.Point26_6{X: floatToFixed266(s.Args[2].X*scale + x), Y: floatToFixed266(-s.Args[2].Y*scale + y)})
  128. }
  129. }
  130. f.Stop(true)
  131. }
  132. func fixed266ToFloat(i fixed.Int26_6) float32 {
  133. return float32(float64(i) / 64)
  134. }
  135. func floatToFixed266(f float32) fixed.Int26_6 {
  136. return fixed.Int26_6(int(float64(f) * 64))
  137. }
  138. type singleFontMap struct {
  139. face font.Face
  140. }
  141. func (sf singleFontMap) ResolveFace(rune) font.Face { return sf.face }