path.go 3.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163
  1. package gg
  2. import (
  3. "math"
  4. "github.com/golang/freetype/raster"
  5. "golang.org/x/image/math/fixed"
  6. )
  7. func flattenPath(p raster.Path) [][]Point {
  8. var result [][]Point
  9. var path []Point
  10. var cx, cy float64
  11. for i := 0; i < len(p); {
  12. switch p[i] {
  13. case 0:
  14. if len(path) > 0 {
  15. result = append(result, path)
  16. path = nil
  17. }
  18. x := unfix(p[i+1])
  19. y := unfix(p[i+2])
  20. path = append(path, Point{x, y})
  21. cx, cy = x, y
  22. i += 4
  23. case 1:
  24. x := unfix(p[i+1])
  25. y := unfix(p[i+2])
  26. path = append(path, Point{x, y})
  27. cx, cy = x, y
  28. i += 4
  29. case 2:
  30. x1 := unfix(p[i+1])
  31. y1 := unfix(p[i+2])
  32. x2 := unfix(p[i+3])
  33. y2 := unfix(p[i+4])
  34. points := QuadraticBezier(cx, cy, x1, y1, x2, y2)
  35. path = append(path, points...)
  36. cx, cy = x2, y2
  37. i += 6
  38. case 3:
  39. x1 := unfix(p[i+1])
  40. y1 := unfix(p[i+2])
  41. x2 := unfix(p[i+3])
  42. y2 := unfix(p[i+4])
  43. x3 := unfix(p[i+5])
  44. y3 := unfix(p[i+6])
  45. points := CubicBezier(cx, cy, x1, y1, x2, y2, x3, y3)
  46. path = append(path, points...)
  47. cx, cy = x3, y3
  48. i += 8
  49. default:
  50. panic("bad path")
  51. }
  52. }
  53. if len(path) > 0 {
  54. result = append(result, path)
  55. }
  56. return result
  57. }
  58. func dashPath(paths [][]Point, dashes []float64, offset float64) [][]Point {
  59. var result [][]Point
  60. if len(dashes) == 0 {
  61. return paths
  62. }
  63. if len(dashes) == 1 {
  64. dashes = append(dashes, dashes[0])
  65. }
  66. for _, path := range paths {
  67. if len(path) < 2 {
  68. continue
  69. }
  70. previous := path[0]
  71. pathIndex := 1
  72. dashIndex := 0
  73. segmentLength := 0.0
  74. // offset
  75. if offset != 0 {
  76. var totalLength float64
  77. for _, dashLength := range dashes {
  78. totalLength += dashLength
  79. }
  80. offset = math.Mod(offset, totalLength)
  81. if offset < 0 {
  82. offset += totalLength
  83. }
  84. for i, dashLength := range dashes {
  85. offset -= dashLength
  86. if offset < 0 {
  87. dashIndex = i
  88. segmentLength = dashLength + offset
  89. break
  90. }
  91. }
  92. }
  93. var segment []Point
  94. segment = append(segment, previous)
  95. for pathIndex < len(path) {
  96. dashLength := dashes[dashIndex]
  97. point := path[pathIndex]
  98. d := previous.Distance(point)
  99. maxd := dashLength - segmentLength
  100. if d > maxd {
  101. t := maxd / d
  102. p := previous.Interpolate(point, t)
  103. segment = append(segment, p)
  104. if dashIndex%2 == 0 && len(segment) > 1 {
  105. result = append(result, segment)
  106. }
  107. segment = nil
  108. segment = append(segment, p)
  109. segmentLength = 0
  110. previous = p
  111. dashIndex = (dashIndex + 1) % len(dashes)
  112. } else {
  113. segment = append(segment, point)
  114. previous = point
  115. segmentLength += d
  116. pathIndex++
  117. }
  118. }
  119. if dashIndex%2 == 0 && len(segment) > 1 {
  120. result = append(result, segment)
  121. }
  122. }
  123. return result
  124. }
  125. func rasterPath(paths [][]Point) raster.Path {
  126. var result raster.Path
  127. for _, path := range paths {
  128. var previous fixed.Point26_6
  129. for i, point := range path {
  130. f := point.Fixed()
  131. if i == 0 {
  132. result.Start(f)
  133. } else {
  134. dx := f.X - previous.X
  135. dy := f.Y - previous.Y
  136. if dx < 0 {
  137. dx = -dx
  138. }
  139. if dy < 0 {
  140. dy = -dy
  141. }
  142. if dx+dy > 8 {
  143. // TODO: this is a hack for cases where two points are
  144. // too close - causes rendering issues with joins / caps
  145. result.Add1(f)
  146. }
  147. }
  148. previous = f
  149. }
  150. }
  151. return result
  152. }
  153. func dashed(path raster.Path, dashes []float64, offset float64) raster.Path {
  154. return rasterPath(dashPath(flattenPath(path), dashes, offset))
  155. }