utils.go 4.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172
  1. // Copyright 2017 The oksvg Authors. All rights reserved.
  2. // created: 2/12/2017 by S.R.Wiley
  3. //
  4. // utils.go implements translation of an SVG2.0 path into a rasterx Path.
  5. package oksvg
  6. import (
  7. "errors"
  8. "image/color"
  9. "strconv"
  10. "strings"
  11. "github.com/srwiley/rasterx"
  12. "golang.org/x/image/colornames"
  13. )
  14. // unitSuffixes are suffixes sometimes applied to the width and height attributes
  15. // of the svg element.
  16. var unitSuffixes = []string{"cm", "mm", "px", "pt"}
  17. func parseColorValue(v string) (uint8, error) {
  18. if v[len(v)-1] == '%' {
  19. n, err := strconv.Atoi(strings.TrimSpace(v[:len(v)-1]))
  20. if err != nil {
  21. return 0, err
  22. }
  23. return uint8(n * 0xFF / 100), nil
  24. }
  25. n, err := strconv.Atoi(strings.TrimSpace(v))
  26. if n > 255 {
  27. n = 255
  28. }
  29. return uint8(n), err
  30. }
  31. // trimSuffixes removes unitSuffixes from any number that is not just numeric
  32. func trimSuffixes(a string) (b string) {
  33. if a == "" || (a[len(a)-1] >= '0' && a[len(a)-1] <= '9') {
  34. return a
  35. }
  36. b = a
  37. for _, v := range unitSuffixes {
  38. b = strings.TrimSuffix(b, v)
  39. }
  40. return
  41. }
  42. // parseFloat is a helper function that strips suffixes before passing to strconv.ParseFloat
  43. func parseFloat(s string, bitSize int) (float64, error) {
  44. val := trimSuffixes(s)
  45. return strconv.ParseFloat(val, bitSize)
  46. }
  47. // splitOnCommaOrSpace returns a list of strings after splitting the input on comma and space delimiters
  48. func splitOnCommaOrSpace(s string) []string {
  49. return strings.FieldsFunc(s,
  50. func(r rune) bool {
  51. return r == ',' || r == ' '
  52. })
  53. }
  54. func parseClasses(data string) (map[string]styleAttribute, error) {
  55. res := map[string]styleAttribute{}
  56. arr := strings.Split(data, "}")
  57. for _, v := range arr {
  58. v = strings.TrimSpace(v)
  59. if v == "" {
  60. continue
  61. }
  62. valueIndex := strings.Index(v, "{")
  63. if valueIndex == -1 || valueIndex == len(v)-1 {
  64. return res, errors.New(v + "}: invalid map format in class definitions")
  65. }
  66. classesStr := v[:valueIndex]
  67. attrStr := v[valueIndex+1:]
  68. attrMap, err := parseAttrs(attrStr)
  69. if err != nil {
  70. return res, err
  71. }
  72. classes := strings.Split(classesStr, ",")
  73. for _, class := range classes {
  74. class = strings.TrimSpace(class)
  75. if len(class) > 0 && class[0] == '.' {
  76. class = class[1:]
  77. }
  78. for attrKey, attrVal := range attrMap {
  79. if res[class] == nil {
  80. res[class] = make(styleAttribute, len(attrMap))
  81. }
  82. res[class][attrKey] = attrVal
  83. }
  84. }
  85. }
  86. return res, nil
  87. }
  88. func parseAttrs(attrStr string) (styleAttribute, error) {
  89. arr := strings.Split(attrStr, ";")
  90. res := make(styleAttribute, len(arr))
  91. for _, kv := range arr {
  92. kv = strings.TrimSpace(kv)
  93. if kv == "" {
  94. continue
  95. }
  96. tmp := strings.SplitN(kv, ":", 2)
  97. if len(tmp) != 2 {
  98. return res, errors.New(kv + ": invalid attribute format")
  99. }
  100. k := strings.TrimSpace(tmp[0])
  101. v := strings.TrimSpace(tmp[1])
  102. res[k] = v
  103. }
  104. return res, nil
  105. }
  106. func readFraction(v string) (f float64, err error) {
  107. v = strings.TrimSpace(v)
  108. d := 1.0
  109. if strings.HasSuffix(v, "%") {
  110. d = 100
  111. v = strings.TrimSuffix(v, "%")
  112. }
  113. f, err = parseFloat(v, 64)
  114. f /= d
  115. // Is this is an unnecessary restriction? For now fractions can be all values not just in the range [0,1]
  116. // if f > 1 {
  117. // f = 1
  118. // } else if f < 0 {
  119. // f = 0
  120. // }
  121. return
  122. }
  123. // getColor is a helper function to get the background color
  124. // if ReadGradUrl needs it.
  125. func getColor(clr interface{}) color.Color {
  126. switch c := clr.(type) {
  127. case rasterx.Gradient: // This is a bit lazy but oh well
  128. for _, s := range c.Stops {
  129. if s.StopColor != nil {
  130. return s.StopColor
  131. }
  132. }
  133. case color.NRGBA:
  134. return c
  135. }
  136. return colornames.Black
  137. }
  138. func localizeGradIfStopClrNil(g *rasterx.Gradient, defaultColor interface{}) (grad rasterx.Gradient) {
  139. grad = *g
  140. for _, s := range grad.Stops {
  141. if s.StopColor == nil { // This means we need copy the gradient's Stop slice
  142. // and fill in the default color
  143. // Copy the stops
  144. stops := make([]rasterx.GradStop, len(grad.Stops))
  145. copy(stops, grad.Stops)
  146. grad.Stops = stops
  147. // Use the background color when a stop color is nil
  148. clr := getColor(defaultColor)
  149. for i, s := range stops {
  150. if s.StopColor == nil {
  151. grad.Stops[i].StopColor = clr
  152. }
  153. }
  154. break // Only need to do this once
  155. }
  156. }
  157. return
  158. }