prawn-svg 0.34.2 → 0.35.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (93) hide show
  1. checksums.yaml +4 -4
  2. data/.github/workflows/lint.yml +19 -0
  3. data/.github/workflows/test.yml +14 -27
  4. data/.gitignore +0 -1
  5. data/.rubocop.yml +86 -0
  6. data/.rubocop_todo.yml +51 -0
  7. data/Gemfile +4 -3
  8. data/Gemfile.lock +81 -0
  9. data/README.md +1 -1
  10. data/Rakefile +1 -1
  11. data/lib/prawn/svg/attributes/clip_path.rb +3 -3
  12. data/lib/prawn/svg/attributes/opacity.rb +3 -3
  13. data/lib/prawn/svg/attributes/stroke.rb +9 -9
  14. data/lib/prawn/svg/attributes/transform.rb +2 -2
  15. data/lib/prawn/svg/attributes.rb +1 -1
  16. data/lib/prawn/svg/calculators/arc_to_bezier_curve.rb +17 -15
  17. data/lib/prawn/svg/calculators/aspect_ratio.rb +16 -14
  18. data/lib/prawn/svg/calculators/document_sizing.rb +9 -10
  19. data/lib/prawn/svg/calculators/pixels.rb +8 -5
  20. data/lib/prawn/svg/color.rb +209 -212
  21. data/lib/prawn/svg/css/font_family_parser.rb +2 -2
  22. data/lib/prawn/svg/css/selector_parser.rb +39 -35
  23. data/lib/prawn/svg/css/stylesheets.rb +24 -24
  24. data/lib/prawn/svg/css/values_parser.rb +68 -0
  25. data/lib/prawn/svg/document.rb +6 -5
  26. data/lib/prawn/svg/elements/base.rb +29 -34
  27. data/lib/prawn/svg/elements/circle.rb +4 -4
  28. data/lib/prawn/svg/elements/clip_path.rb +0 -1
  29. data/lib/prawn/svg/elements/depth_first_base.rb +6 -6
  30. data/lib/prawn/svg/elements/ellipse.rb +3 -4
  31. data/lib/prawn/svg/elements/gradient.rb +49 -51
  32. data/lib/prawn/svg/elements/image.rb +5 -5
  33. data/lib/prawn/svg/elements/marker.rb +5 -5
  34. data/lib/prawn/svg/elements/path.rb +46 -47
  35. data/lib/prawn/svg/elements/polygon.rb +1 -1
  36. data/lib/prawn/svg/elements/polyline.rb +2 -2
  37. data/lib/prawn/svg/elements/rect.rb +3 -3
  38. data/lib/prawn/svg/elements/text.rb +1 -1
  39. data/lib/prawn/svg/elements/text_component.rb +22 -22
  40. data/lib/prawn/svg/elements/use.rb +3 -7
  41. data/lib/prawn/svg/elements/viewport.rb +1 -3
  42. data/lib/prawn/svg/elements.rb +30 -29
  43. data/lib/prawn/svg/extension.rb +2 -1
  44. data/lib/prawn/svg/font.rb +7 -7
  45. data/lib/prawn/svg/font_registry.rb +13 -13
  46. data/lib/prawn/svg/gradients.rb +3 -2
  47. data/lib/prawn/svg/interface.rb +4 -3
  48. data/lib/prawn/svg/loaders/data.rb +2 -2
  49. data/lib/prawn/svg/loaders/file.rb +12 -14
  50. data/lib/prawn/svg/loaders/web.rb +4 -8
  51. data/lib/prawn/svg/pathable.rb +41 -37
  52. data/lib/prawn/svg/properties.rb +34 -33
  53. data/lib/prawn/svg/renderer.rb +7 -7
  54. data/lib/prawn/svg/state.rb +1 -1
  55. data/lib/prawn/svg/transform_parser.rb +5 -5
  56. data/lib/prawn/svg/ttf.rb +21 -17
  57. data/lib/prawn/svg/url_loader.rb +1 -1
  58. data/lib/prawn/svg/version.rb +1 -1
  59. data/lib/prawn-svg.rb +1 -0
  60. data/prawn-svg.gemspec +4 -6
  61. data/spec/integration_spec.rb +77 -70
  62. data/spec/prawn/svg/attributes/opacity_spec.rb +11 -15
  63. data/spec/prawn/svg/attributes/transform_spec.rb +6 -6
  64. data/spec/prawn/svg/calculators/aspect_ratio_spec.rb +50 -50
  65. data/spec/prawn/svg/calculators/document_sizing_spec.rb +35 -35
  66. data/spec/prawn/svg/calculators/pixels_spec.rb +31 -30
  67. data/spec/prawn/svg/color_spec.rb +31 -31
  68. data/spec/prawn/svg/css/font_family_parser_spec.rb +12 -12
  69. data/spec/prawn/svg/css/selector_parser_spec.rb +21 -21
  70. data/spec/prawn/svg/css/stylesheets_spec.rb +51 -45
  71. data/spec/prawn/svg/css/values_parser_spec.rb +16 -0
  72. data/spec/prawn/svg/document_spec.rb +15 -14
  73. data/spec/prawn/svg/elements/base_spec.rb +39 -34
  74. data/spec/prawn/svg/elements/gradient_spec.rb +39 -39
  75. data/spec/prawn/svg/elements/line_spec.rb +22 -22
  76. data/spec/prawn/svg/elements/marker_spec.rb +36 -40
  77. data/spec/prawn/svg/elements/path_spec.rb +134 -110
  78. data/spec/prawn/svg/elements/polygon_spec.rb +18 -18
  79. data/spec/prawn/svg/elements/polyline_spec.rb +16 -16
  80. data/spec/prawn/svg/elements/text_spec.rb +149 -127
  81. data/spec/prawn/svg/font_registry_spec.rb +34 -34
  82. data/spec/prawn/svg/font_spec.rb +4 -4
  83. data/spec/prawn/svg/interface_spec.rb +47 -39
  84. data/spec/prawn/svg/loaders/data_spec.rb +21 -21
  85. data/spec/prawn/svg/loaders/file_spec.rb +43 -40
  86. data/spec/prawn/svg/loaders/web_spec.rb +15 -15
  87. data/spec/prawn/svg/pathable_spec.rb +21 -21
  88. data/spec/prawn/svg/properties_spec.rb +51 -51
  89. data/spec/prawn/svg/transform_parser_spec.rb +12 -12
  90. data/spec/prawn/svg/ttf_spec.rb +5 -5
  91. data/spec/prawn/svg/url_loader_spec.rb +25 -23
  92. data/spec/spec_helper.rb +4 -4
  93. metadata +12 -143
@@ -3,15 +3,15 @@ class Prawn::SVG::Elements::Gradient < Prawn::SVG::Elements::Base
3
3
  attr_reader :x1, :y1, :x2, :y2, :cx, :cy, :fx, :fy, :radius, :units, :stops, :transform_matrix
4
4
 
5
5
  TAG_NAME_TO_TYPE = {
6
- "linearGradient" => :linear,
7
- "radialGradient" => :radial
8
- }
6
+ 'linearGradient' => :linear,
7
+ 'radialGradient' => :radial
8
+ }.freeze
9
9
 
10
10
  def parse
11
11
  # A gradient tag without an ID is inaccessible and can never be used
12
12
  raise SkipElementQuietly if attributes['id'].nil?
13
13
 
14
- @parent_gradient = document.gradients[href_attribute[1..-1]] if href_attribute && href_attribute[0] == '#'
14
+ @parent_gradient = document.gradients[href_attribute[1..]] if href_attribute && href_attribute[0] == '#'
15
15
 
16
16
  assert_compatible_prawn_version
17
17
  load_gradient_configuration
@@ -28,10 +28,10 @@ class Prawn::SVG::Elements::Gradient < Prawn::SVG::Elements::Base
28
28
  # by a monkey patch installed by prawn-svg. Prawn only sees this as a truthy variable.
29
29
  #
30
30
  # See Prawn::SVG::Extensions::AdditionalGradientTransforms for details.
31
- base_arguments = {stops: stops, apply_transformations: transform_matrix || true}
31
+ base_arguments = { stops: stops, apply_transformations: transform_matrix || true }
32
32
 
33
33
  arguments = specific_gradient_arguments(element)
34
- arguments.merge(base_arguments) if arguments
34
+ arguments&.merge(base_arguments)
35
35
  end
36
36
 
37
37
  private
@@ -47,31 +47,31 @@ class Prawn::SVG::Elements::Gradient < Prawn::SVG::Elements::Base
47
47
 
48
48
  case [type, units]
49
49
  when [:linear, :bounding_box]
50
- from = [bounding_x1 + width * x1, bounding_y1 - height * y1]
51
- to = [bounding_x1 + width * x2, bounding_y1 - height * y2]
50
+ from = [bounding_x1 + (width * x1), bounding_y1 - (height * y1)]
51
+ to = [bounding_x1 + (width * x2), bounding_y1 - (height * y2)]
52
52
 
53
- {from: from, to: to}
53
+ { from: from, to: to }
54
54
 
55
55
  when [:linear, :user_space]
56
- {from: [x1, y1], to: [x2, y2]}
56
+ { from: [x1, y1], to: [x2, y2] }
57
57
 
58
58
  when [:radial, :bounding_box]
59
- center = [bounding_x1 + width * cx, bounding_y1 - height * cy]
60
- focus = [bounding_x1 + width * fx, bounding_y1 - height * fy]
59
+ center = [bounding_x1 + (width * cx), bounding_y1 - (height * cy)]
60
+ focus = [bounding_x1 + (width * fx), bounding_y1 - (height * fy)]
61
61
 
62
- # Note: Chrome, at least, implements radial bounding box radiuses as
62
+ # NOTE: Chrome, at least, implements radial bounding box radiuses as
63
63
  # having separate X and Y components, so in bounding box mode their
64
64
  # gradients come out as ovals instead of circles. PDF radial shading
65
65
  # doesn't have the option to do this, and it's confusing why the
66
66
  # Chrome user space gradients don't apply the same logic anyway.
67
- hypot = Math.sqrt(width * width + height * height)
68
- {from: focus, r1: 0, to: center, r2: radius * hypot}
67
+ hypot = Math.sqrt((width * width) + (height * height))
68
+ { from: focus, r1: 0, to: center, r2: radius * hypot }
69
69
 
70
70
  when [:radial, :user_space]
71
- {from: [fx, fy], r1: 0, to: [cx, cy], r2: radius}
71
+ { from: [fx, fy], r1: 0, to: [cx, cy], r2: radius }
72
72
 
73
73
  else
74
- raise "unexpected type/unit system"
74
+ raise 'unexpected type/unit system'
75
75
  end
76
76
  end
77
77
 
@@ -80,19 +80,19 @@ class Prawn::SVG::Elements::Gradient < Prawn::SVG::Elements::Base
80
80
  end
81
81
 
82
82
  def assert_compatible_prawn_version
83
- if (Prawn::VERSION.split(".").map(&:to_i) <=> [2, 2, 0]) == -1
83
+ if (Prawn::VERSION.split('.').map(&:to_i) <=> [2, 2, 0]) == -1
84
84
  raise SkipElementError, "Prawn 2.2.0+ must be used if you'd like prawn-svg to render gradients"
85
85
  end
86
86
  end
87
87
 
88
88
  def load_gradient_configuration
89
- @units = attributes["gradientUnits"] == 'userSpaceOnUse' ? :user_space : :bounding_box
89
+ @units = attributes['gradientUnits'] == 'userSpaceOnUse' ? :user_space : :bounding_box
90
90
 
91
- if transform = attributes["gradientTransform"]
91
+ if (transform = attributes['gradientTransform'])
92
92
  @transform_matrix = parse_transform_attribute(transform)
93
93
  end
94
94
 
95
- if (spread_method = attributes['spreadMethod']) && spread_method != "pad"
95
+ if (spread_method = attributes['spreadMethod']) && spread_method != 'pad'
96
96
  warnings << "prawn-svg only currently supports the 'pad' spreadMethod attribute value"
97
97
  end
98
98
  end
@@ -100,33 +100,33 @@ class Prawn::SVG::Elements::Gradient < Prawn::SVG::Elements::Base
100
100
  def load_coordinates
101
101
  case [type, units]
102
102
  when [:linear, :bounding_box]
103
- @x1 = parse_zero_to_one(attributes["x1"], 0)
104
- @y1 = parse_zero_to_one(attributes["y1"], 0)
105
- @x2 = parse_zero_to_one(attributes["x2"], 1)
106
- @y2 = parse_zero_to_one(attributes["y2"], 0)
103
+ @x1 = parse_zero_to_one(attributes['x1'], 0)
104
+ @y1 = parse_zero_to_one(attributes['y1'], 0)
105
+ @x2 = parse_zero_to_one(attributes['x2'], 1)
106
+ @y2 = parse_zero_to_one(attributes['y2'], 0)
107
107
 
108
108
  when [:linear, :user_space]
109
- @x1 = x(attributes["x1"])
110
- @y1 = y(attributes["y1"])
111
- @x2 = x(attributes["x2"])
112
- @y2 = y(attributes["y2"])
109
+ @x1 = x(attributes['x1'])
110
+ @y1 = y(attributes['y1'])
111
+ @x2 = x(attributes['x2'])
112
+ @y2 = y(attributes['y2'])
113
113
 
114
114
  when [:radial, :bounding_box]
115
- @cx = parse_zero_to_one(attributes["cx"], 0.5)
116
- @cy = parse_zero_to_one(attributes["cy"], 0.5)
117
- @fx = parse_zero_to_one(attributes["fx"], cx)
118
- @fy = parse_zero_to_one(attributes["fy"], cy)
119
- @radius = parse_zero_to_one(attributes["r"], 0.5)
115
+ @cx = parse_zero_to_one(attributes['cx'], 0.5)
116
+ @cy = parse_zero_to_one(attributes['cy'], 0.5)
117
+ @fx = parse_zero_to_one(attributes['fx'], cx)
118
+ @fy = parse_zero_to_one(attributes['fy'], cy)
119
+ @radius = parse_zero_to_one(attributes['r'], 0.5)
120
120
 
121
121
  when [:radial, :user_space]
122
- @cx = x(attributes["cx"] || '50%')
123
- @cy = y(attributes["cy"] || '50%')
124
- @fx = x(attributes["fx"] || attributes["cx"])
125
- @fy = y(attributes["fy"] || attributes["cy"])
126
- @radius = pixels(attributes["r"] || '50%')
122
+ @cx = x(attributes['cx'] || '50%')
123
+ @cy = y(attributes['cy'] || '50%')
124
+ @fx = x(attributes['fx'] || attributes['cx'])
125
+ @fy = y(attributes['fy'] || attributes['cy'])
126
+ @radius = pixels(attributes['r'] || '50%')
127
127
 
128
128
  else
129
- raise "unexpected type/unit system"
129
+ raise 'unexpected type/unit system'
130
130
  end
131
131
  end
132
132
 
@@ -136,40 +136,38 @@ class Prawn::SVG::Elements::Gradient < Prawn::SVG::Elements::Base
136
136
  element.process
137
137
  element
138
138
  end.select do |element|
139
- element.name == 'stop' && element.attributes["offset"]
139
+ element.name == 'stop' && element.attributes['offset']
140
140
  end
141
141
 
142
142
  @stops = stop_elements.each.with_object([]) do |child, result|
143
- offset = parse_zero_to_one(child.attributes["offset"])
143
+ offset = parse_zero_to_one(child.attributes['offset'])
144
144
 
145
145
  # Offsets must be strictly increasing (SVG 13.2.4)
146
- if result.last && result.last.first > offset
147
- offset = result.last.first
148
- end
146
+ offset = result.last.first if result.last && result.last.first > offset
149
147
 
150
- if color = Prawn::SVG::Color.css_color_to_prawn_color(child.properties.stop_color)
148
+ if (color = Prawn::SVG::Color.css_color_to_prawn_color(child.properties.stop_color))
151
149
  result << [offset, color]
152
150
  end
153
151
  end
154
152
 
155
153
  if stops.empty?
156
154
  if parent_gradient.nil? || parent_gradient.stops.empty?
157
- raise SkipElementError, "gradient does not have any valid stops"
155
+ raise SkipElementError, 'gradient does not have any valid stops'
158
156
  end
159
157
 
160
158
  @stops = parent_gradient.stops
161
159
  else
162
- stops.unshift([0, stops.first.last]) if stops.first.first > 0
163
- stops.push([1, stops.last.last]) if stops.last.first < 1
160
+ stops.unshift([0, stops.first.last]) if stops.first.first.positive?
161
+ stops.push([1, stops.last.last]) if stops.last.first < 1
164
162
  end
165
163
  end
166
164
 
167
165
  def parse_zero_to_one(string, default = 0)
168
166
  string = string.to_s.strip
169
- return default if string == ""
167
+ return default if string == ''
170
168
 
171
169
  value = string.to_f
172
- value /= 100.0 if string[-1..-1] == '%'
170
+ value /= 100.0 if string[-1..] == '%'
173
171
  [0.0, value, 1.0].sort[1]
174
172
  end
175
173
  end
@@ -25,7 +25,7 @@ class Prawn::SVG::Elements::Image < Prawn::SVG::Elements::Base
25
25
  y = y(attributes['y'] || 0)
26
26
  width = x_pixels(attributes['width'])
27
27
  height = y_pixels(attributes['height'])
28
- preserveAspectRatio = attributes['preserveAspectRatio']
28
+ preserve_aspect_ratio = attributes['preserveAspectRatio']
29
29
 
30
30
  raise SkipElementQuietly if width.zero? || height.zero?
31
31
 
@@ -37,9 +37,9 @@ class Prawn::SVG::Elements::Image < Prawn::SVG::Elements::Base
37
37
  raise SkipElementError, "Error retrieving URL #{@url}: #{e.message}"
38
38
  end
39
39
 
40
- @image_data = process_image(@image, width, height, preserveAspectRatio)
40
+ @image_data = process_image(@image, width, height, preserve_aspect_ratio)
41
41
 
42
- @aspect = Prawn::SVG::Calculators::AspectRatio.new(preserveAspectRatio, [width, height], @image_data.dimensions)
42
+ @aspect = Prawn::SVG::Calculators::AspectRatio.new(preserve_aspect_ratio, [width, height], @image_data.dimensions)
43
43
 
44
44
  @clip_x = x
45
45
  @clip_y = y
@@ -77,7 +77,7 @@ class Prawn::SVG::Elements::Image < Prawn::SVG::Elements::Base
77
77
 
78
78
  protected
79
79
 
80
- def process_image(data, width, height, preserveAspectRatio)
80
+ def process_image(data, width, height, preserve_aspect_ratio)
81
81
  if (handler = find_image_handler(data))
82
82
  image = handler.new(data)
83
83
  ImageData.new([image.width.to_f, image.height.to_f], nil)
@@ -85,7 +85,7 @@ class Prawn::SVG::Elements::Image < Prawn::SVG::Elements::Base
85
85
  elsif potentially_svg?(data)
86
86
  document = Prawn::SVG::Document.new(
87
87
  data, [width, height], { width: width, height: height },
88
- attribute_overrides: { 'preserveAspectRatio' => preserveAspectRatio }
88
+ attribute_overrides: { 'preserveAspectRatio' => preserve_aspect_ratio }
89
89
  )
90
90
 
91
91
  dimensions = [document.sizing.output_width, document.sizing.output_height]
@@ -10,12 +10,12 @@ class Prawn::SVG::Elements::Marker < Prawn::SVG::Elements::Base
10
10
 
11
11
  def apply_marker(element, point: nil, angle: 0)
12
12
  sizing = Prawn::SVG::Calculators::DocumentSizing.new([0, 0], attributes)
13
- sizing.document_width = attributes["markerWidth"] || 3
14
- sizing.document_height = attributes["markerHeight"] || 3
13
+ sizing.document_width = attributes['markerWidth'] || 3
14
+ sizing.document_height = attributes['markerHeight'] || 3
15
15
  sizing.calculate
16
16
 
17
17
  if sizing.invalid?
18
- document.warnings << "<marker> cannot be rendered due to invalid sizing information"
18
+ document.warnings << '<marker> cannot be rendered due to invalid sizing information'
19
19
  return
20
20
  end
21
21
 
@@ -48,8 +48,8 @@ class Prawn::SVG::Elements::Marker < Prawn::SVG::Elements::Base
48
48
 
49
49
  if overflow_hidden?
50
50
  point = [sizing.x_offset * sizing.x_scale, y(sizing.y_offset * sizing.y_scale)]
51
- element.add_call "rectangle", point, sizing.output_width, sizing.output_height
52
- element.add_call "clip"
51
+ element.add_call 'rectangle', point, sizing.output_width, sizing.output_height
52
+ element.add_call 'clip'
53
53
  end
54
54
 
55
55
  element.add_call 'transformation_matrix', sizing.x_scale, 0, 0, sizing.y_scale, 0, 0
@@ -2,17 +2,17 @@ class Prawn::SVG::Elements::Path < Prawn::SVG::Elements::Base
2
2
  include Prawn::SVG::Calculators::ArcToBezierCurve
3
3
  include Prawn::SVG::Pathable
4
4
 
5
- INSIDE_SPACE_REGEXP = /[ \t\r\n,]*/
6
- OUTSIDE_SPACE_REGEXP = /[ \t\r\n]*/
7
- INSIDE_REGEXP = /#{INSIDE_SPACE_REGEXP}(?>([+-]?(?:[0-9]+(?:\.[0-9]*)?|\.[0-9]+)(?:(?<=[0-9])[eE][+-]?[0-9]+)?))/
8
- FLAG_REGEXP = /#{INSIDE_SPACE_REGEXP}([01])/
9
- COMMAND_REGEXP = /^#{OUTSIDE_SPACE_REGEXP}([A-Za-z])((?:#{INSIDE_REGEXP})*)#{OUTSIDE_SPACE_REGEXP}/
10
-
11
- A_PARAMETERS_REGEXP = /^#{INSIDE_REGEXP}#{INSIDE_REGEXP}#{INSIDE_REGEXP}#{FLAG_REGEXP}#{FLAG_REGEXP}#{INSIDE_REGEXP}#{INSIDE_REGEXP}/
12
- ONE_PARAMETER_REGEXP = /^#{INSIDE_REGEXP}/
13
- TWO_PARAMETER_REGEXP = /^#{INSIDE_REGEXP}#{INSIDE_REGEXP}/
14
- FOUR_PARAMETER_REGEXP = /^#{INSIDE_REGEXP}#{INSIDE_REGEXP}#{INSIDE_REGEXP}#{INSIDE_REGEXP}/
15
- SIX_PARAMETER_REGEXP = /^#{INSIDE_REGEXP}#{INSIDE_REGEXP}#{INSIDE_REGEXP}#{INSIDE_REGEXP}#{INSIDE_REGEXP}#{INSIDE_REGEXP}/
5
+ INSIDE_SPACE_REGEXP = /[ \t\r\n,]*/.freeze
6
+ OUTSIDE_SPACE_REGEXP = /[ \t\r\n]*/.freeze
7
+ INSIDE_REGEXP = /#{INSIDE_SPACE_REGEXP}(?>([+-]?(?:[0-9]+(?:\.[0-9]*)?|\.[0-9]+)(?:(?<=[0-9])[eE][+-]?[0-9]+)?))/.freeze
8
+ FLAG_REGEXP = /#{INSIDE_SPACE_REGEXP}([01])/.freeze
9
+ COMMAND_REGEXP = /^#{OUTSIDE_SPACE_REGEXP}([A-Za-z])((?:#{INSIDE_REGEXP})*)#{OUTSIDE_SPACE_REGEXP}/.freeze
10
+
11
+ A_PARAMETERS_REGEXP = /^#{INSIDE_REGEXP}#{INSIDE_REGEXP}#{INSIDE_REGEXP}#{FLAG_REGEXP}#{FLAG_REGEXP}#{INSIDE_REGEXP}#{INSIDE_REGEXP}/.freeze
12
+ ONE_PARAMETER_REGEXP = /^#{INSIDE_REGEXP}/.freeze
13
+ TWO_PARAMETER_REGEXP = /^#{INSIDE_REGEXP}#{INSIDE_REGEXP}/.freeze
14
+ FOUR_PARAMETER_REGEXP = /^#{INSIDE_REGEXP}#{INSIDE_REGEXP}#{INSIDE_REGEXP}#{INSIDE_REGEXP}/.freeze
15
+ SIX_PARAMETER_REGEXP = /^#{INSIDE_REGEXP}#{INSIDE_REGEXP}#{INSIDE_REGEXP}#{INSIDE_REGEXP}#{INSIDE_REGEXP}#{INSIDE_REGEXP}/.freeze
16
16
 
17
17
  COMMAND_MATCH_MAP = {
18
18
  'A' => A_PARAMETERS_REGEXP,
@@ -24,8 +24,8 @@ class Prawn::SVG::Elements::Path < Prawn::SVG::Elements::Base
24
24
  'S' => FOUR_PARAMETER_REGEXP,
25
25
  'T' => TWO_PARAMETER_REGEXP,
26
26
  'V' => ONE_PARAMETER_REGEXP,
27
- 'Z' => //,
28
- }
27
+ 'Z' => //
28
+ }.freeze
29
29
 
30
30
  PARAMETERLESS_COMMANDS = COMMAND_MATCH_MAP.select { |_, v| v == // }.map(&:first)
31
31
 
@@ -39,10 +39,10 @@ class Prawn::SVG::Elements::Path < Prawn::SVG::Elements::Base
39
39
  @commands = []
40
40
  @last_point = nil
41
41
 
42
- data = attributes["d"].gsub(/#{OUTSIDE_SPACE_REGEXP}$/, '')
42
+ data = attributes['d'].gsub(/#{OUTSIDE_SPACE_REGEXP}$/, '')
43
43
 
44
44
  matched_commands = match_all(data, COMMAND_REGEXP)
45
- raise SkipElementError, "Invalid/unsupported syntax for SVG path data" if matched_commands.nil?
45
+ raise SkipElementError, 'Invalid/unsupported syntax for SVG path data' if matched_commands.nil?
46
46
 
47
47
  matched_commands.each do |(command, parameters)|
48
48
  regexp = COMMAND_MATCH_MAP[command.upcase] or break
@@ -80,9 +80,7 @@ class Prawn::SVG::Elements::Path < Prawn::SVG::Elements::Base
80
80
  return parse_path_command(relative ? 'l' : 'L', values) if values.any?
81
81
 
82
82
  when 'Z' # closepath
83
- if @subpath_initial_point
84
- push_command Prawn::SVG::Pathable::Close.new(@subpath_initial_point)
85
- end
83
+ push_command Prawn::SVG::Pathable::Close.new(@subpath_initial_point) if @subpath_initial_point
86
84
 
87
85
  when 'L' # lineto
88
86
  while values.any?
@@ -138,8 +136,8 @@ class Prawn::SVG::Elements::Path < Prawn::SVG::Elements::Base
138
136
  end
139
137
 
140
138
  if @previous_control_point
141
- x1 = 2 * @last_point.first - @previous_control_point.first
142
- y1 = 2 * @last_point.last - @previous_control_point.last
139
+ x1 = (2 * @last_point.first) - @previous_control_point.first
140
+ y1 = (2 * @last_point.last) - @previous_control_point.last
143
141
  else
144
142
  x1, y1 = @last_point
145
143
  end
@@ -150,7 +148,7 @@ class Prawn::SVG::Elements::Path < Prawn::SVG::Elements::Base
150
148
 
151
149
  when 'Q', 'T' # quadratic curveto
152
150
  while values.any?
153
- if shorthand = upcase_command == 'T'
151
+ if (shorthand = upcase_command == 'T')
154
152
  x, y = values.shift
155
153
  else
156
154
  x1, y1, x, y = values.shift
@@ -165,18 +163,18 @@ class Prawn::SVG::Elements::Path < Prawn::SVG::Elements::Base
165
163
 
166
164
  if shorthand
167
165
  if @previous_quadratic_control_point
168
- x1 = 2 * @last_point.first - @previous_quadratic_control_point.first
169
- y1 = 2 * @last_point.last - @previous_quadratic_control_point.last
166
+ x1 = (2 * @last_point.first) - @previous_quadratic_control_point.first
167
+ y1 = (2 * @last_point.last) - @previous_quadratic_control_point.last
170
168
  else
171
169
  x1, y1 = @last_point
172
170
  end
173
171
  end
174
172
 
175
173
  # convert from quadratic to cubic
176
- cx1 = @last_point.first + (x1 - @last_point.first) * 2 / 3.0
177
- cy1 = @last_point.last + (y1 - @last_point.last) * 2 / 3.0
178
- cx2 = cx1 + (x - @last_point.first) / 3.0
179
- cy2 = cy1 + (y - @last_point.last) / 3.0
174
+ cx1 = @last_point.first + ((x1 - @last_point.first) * 2 / 3.0)
175
+ cy1 = @last_point.last + ((y1 - @last_point.last) * 2 / 3.0)
176
+ cx2 = cx1 + ((x - @last_point.first) / 3.0)
177
+ cy2 = cy1 + ((y - @last_point.last) / 3.0)
180
178
 
181
179
  @previous_quadratic_control_point = [x1, y1]
182
180
 
@@ -216,13 +214,13 @@ class Prawn::SVG::Elements::Path < Prawn::SVG::Elements::Base
216
214
  # points. To do this, we use the algorithm documented in the SVG specification section F.6.5.
217
215
 
218
216
  # F.6.5.1
219
- xp1 = Math.cos(phi) * ((x1-x2)/2.0) + Math.sin(phi) * ((y1-y2)/2.0)
220
- yp1 = -Math.sin(phi) * ((x1-x2)/2.0) + Math.cos(phi) * ((y1-y2)/2.0)
217
+ xp1 = (Math.cos(phi) * ((x1 - x2) / 2.0)) + (Math.sin(phi) * ((y1 - y2) / 2.0))
218
+ yp1 = (-Math.sin(phi) * ((x1 - x2) / 2.0)) + (Math.cos(phi) * ((y1 - y2) / 2.0))
221
219
 
222
220
  # F.6.6.2
223
221
  r2x = rx * rx
224
222
  r2y = ry * ry
225
- hat = xp1 * xp1 / r2x + yp1 * yp1 / r2y
223
+ hat = (xp1 * xp1 / r2x) + (yp1 * yp1 / r2y)
226
224
  if hat > 1
227
225
  rx *= Math.sqrt(hat)
228
226
  ry *= Math.sqrt(hat)
@@ -231,22 +229,22 @@ class Prawn::SVG::Elements::Path < Prawn::SVG::Elements::Base
231
229
  # F.6.5.2
232
230
  r2x = rx * rx
233
231
  r2y = ry * ry
234
- square = (r2x * r2y - r2x * yp1 * yp1 - r2y * xp1 * xp1) / (r2x * yp1 * yp1 + r2y * xp1 * xp1)
235
- square = 0 if square < 0 && square > -FLOAT_ERROR_DELTA # catch rounding errors
232
+ square = ((r2x * r2y) - (r2x * yp1 * yp1) - (r2y * xp1 * xp1)) / ((r2x * yp1 * yp1) + (r2y * xp1 * xp1))
233
+ square = 0 if square.negative? && square > -FLOAT_ERROR_DELTA # catch rounding errors
236
234
  base = Math.sqrt(square)
237
235
  base *= -1 if fa == fs
238
236
  cpx = base * rx * yp1 / ry
239
237
  cpy = base * -ry * xp1 / rx
240
238
 
241
239
  # F.6.5.3
242
- cx = Math.cos(phi) * cpx + -Math.sin(phi) * cpy + (x1 + x2) / 2
243
- cy = Math.sin(phi) * cpx + Math.cos(phi) * cpy + (y1 + y2) / 2
240
+ cx = (Math.cos(phi) * cpx) + (-Math.sin(phi) * cpy) + ((x1 + x2) / 2)
241
+ cy = (Math.sin(phi) * cpx) + (Math.cos(phi) * cpy) + ((y1 + y2) / 2)
244
242
 
245
243
  # F.6.5.5
246
244
  vx = (xp1 - cpx) / rx
247
245
  vy = (yp1 - cpy) / ry
248
- theta_1 = Math.acos(vx / Math.sqrt(vx * vx + vy * vy))
249
- theta_1 *= -1 if vy < 0
246
+ theta_1 = Math.acos(vx / Math.sqrt((vx * vx) + (vy * vy)))
247
+ theta_1 *= -1 if vy.negative?
250
248
 
251
249
  # F.6.5.6
252
250
  ux = vx
@@ -254,19 +252,19 @@ class Prawn::SVG::Elements::Path < Prawn::SVG::Elements::Base
254
252
  vx = (-xp1 - cpx) / rx
255
253
  vy = (-yp1 - cpy) / ry
256
254
 
257
- numerator = ux * vx + uy * vy
258
- denominator = Math.sqrt(ux * ux + uy * uy) * Math.sqrt(vx * vx + vy * vy)
255
+ numerator = (ux * vx) + (uy * vy)
256
+ denominator = Math.sqrt((ux * ux) + (uy * uy)) * Math.sqrt((vx * vx) + (vy * vy))
259
257
  division = numerator / denominator
260
258
  division = -1 if division < -1 # for rounding errors
261
259
 
262
260
  d_theta = Math.acos(division) % (2 * Math::PI)
263
- d_theta *= -1 if ux * vy - uy * vx < 0
261
+ d_theta *= -1 if ((ux * vy) - (uy * vx)).negative?
264
262
 
265
263
  # Adjust range
266
- if fs == 0
267
- d_theta -= 2 * Math::PI if d_theta > 0
268
- else
269
- d_theta += 2 * Math::PI if d_theta < 0
264
+ if fs.zero?
265
+ d_theta -= 2 * Math::PI if d_theta.positive?
266
+ elsif d_theta.negative?
267
+ d_theta += 2 * Math::PI
270
268
  end
271
269
 
272
270
  theta_2 = theta_1 + d_theta
@@ -277,17 +275,18 @@ class Prawn::SVG::Elements::Path < Prawn::SVG::Elements::Base
277
275
  end
278
276
  end
279
277
 
280
- @previous_control_point = nil unless %w(C S).include?(upcase_command)
281
- @previous_quadratic_control_point = nil unless %w(Q T).include?(upcase_command)
278
+ @previous_control_point = nil unless %w[C S].include?(upcase_command)
279
+ @previous_quadratic_control_point = nil unless %w[Q T].include?(upcase_command)
282
280
  end
283
281
 
284
282
  def within_float_delta?(a, b)
285
283
  (a - b).abs < FLOAT_ERROR_DELTA
286
284
  end
287
285
 
288
- def match_all(string, regexp) # regexp must start with ^
286
+ # regexp must start with ^
287
+ def match_all(string, regexp)
289
288
  result = []
290
- while string != ""
289
+ while string != ''
291
290
  matches = string.match(regexp) or return
292
291
  result << matches.captures
293
292
  string = matches.post_match
@@ -16,7 +16,7 @@ class Prawn::SVG::Elements::Polygon < Prawn::SVG::Elements::Base
16
16
  def commands
17
17
  @commands ||= [
18
18
  Prawn::SVG::Pathable::Move.new(@points[0])
19
- ] + @points[1..-1].map { |point|
19
+ ] + @points[1..].map { |point|
20
20
  Prawn::SVG::Pathable::Line.new(point)
21
21
  } + [
22
22
  Prawn::SVG::Pathable::Close.new(@points[0])
@@ -16,8 +16,8 @@ class Prawn::SVG::Elements::Polyline < Prawn::SVG::Elements::Base
16
16
  def commands
17
17
  @commands ||= [
18
18
  Prawn::SVG::Pathable::Move.new(@points[0])
19
- ] + @points[1..-1].map { |point|
19
+ ] + @points[1..].map do |point|
20
20
  Prawn::SVG::Pathable::Line.new(point)
21
- }
21
+ end
22
22
  end
23
23
  end
@@ -14,16 +14,16 @@ class Prawn::SVG::Elements::Rect < Prawn::SVG::Elements::Base
14
14
  # If you implement separate rx and ry in the future, you'll want to change this
15
15
  # so that rx is constrained to @width/2 and ry is constrained to @height/2.
16
16
  max_value = [@width, @height].min / 2.0
17
- @radius = clamp(@radius, 0, max_value)
17
+ @radius = @radius.clamp(0, max_value)
18
18
  end
19
19
  end
20
20
 
21
21
  def apply
22
22
  if @radius
23
23
  # n.b. does not support both rx and ry being specified with different values
24
- add_call "rounded_rectangle", [@x, @y], @width, @height, @radius
24
+ add_call 'rounded_rectangle', [@x, @y], @width, @height, @radius
25
25
  else
26
- add_call "rectangle", [@x, @y], @width, @height
26
+ add_call 'rectangle', [@x, @y], @width, @height
27
27
  end
28
28
  end
29
29
 
@@ -17,7 +17,7 @@ class Prawn::SVG::Elements::Text < Prawn::SVG::Elements::DepthFirstBase
17
17
 
18
18
  def apply_step(calls)
19
19
  @base_calls = @calls = calls
20
- add_call_and_enter "text_group"
20
+ add_call_and_enter 'text_group'
21
21
  @text_root.apply_step(@calls)
22
22
  end
23
23
 
@@ -5,19 +5,17 @@ class Prawn::SVG::Elements::TextComponent < Prawn::SVG::Elements::DepthFirstBase
5
5
  TextState = Struct.new(:parent, :x, :y, :dx, :dy, :rotation, :spacing, :mode, :text_length, :length_adjust)
6
6
 
7
7
  def parse
8
- if state.inside_clip_path
9
- raise SkipElementError, "<text> elements are not supported in clip paths"
10
- end
8
+ raise SkipElementError, '<text> elements are not supported in clip paths' if state.inside_clip_path
11
9
 
12
10
  if state.text.nil?
13
- raise SkipElementError, "attempted to <use> an component inside a text element, this is not supported"
11
+ raise SkipElementError, 'attempted to <use> an component inside a text element, this is not supported'
14
12
  end
15
13
 
16
- state.text.x = (attributes['x'] || "").split(COMMA_WSP_REGEXP).collect { |n| x(n) }
17
- state.text.y = (attributes['y'] || "").split(COMMA_WSP_REGEXP).collect { |n| y(n) }
18
- state.text.dx = (attributes['dx'] || "").split(COMMA_WSP_REGEXP).collect { |n| x_pixels(n) }
19
- state.text.dy = (attributes['dy'] || "").split(COMMA_WSP_REGEXP).collect { |n| y_pixels(n) }
20
- state.text.rotation = (attributes['rotate'] || "").split(COMMA_WSP_REGEXP).collect(&:to_f)
14
+ state.text.x = (attributes['x'] || '').split(COMMA_WSP_REGEXP).collect { |n| x(n) }
15
+ state.text.y = (attributes['y'] || '').split(COMMA_WSP_REGEXP).collect { |n| y(n) }
16
+ state.text.dx = (attributes['dx'] || '').split(COMMA_WSP_REGEXP).collect { |n| x_pixels(n) }
17
+ state.text.dy = (attributes['dy'] || '').split(COMMA_WSP_REGEXP).collect { |n| y_pixels(n) }
18
+ state.text.rotation = (attributes['rotate'] || '').split(COMMA_WSP_REGEXP).collect(&:to_f)
21
19
  state.text.text_length = normalize_length(attributes['textLength'])
22
20
  state.text.length_adjust = attributes['lengthAdjust']
23
21
  state.text.spacing = calculate_character_spacing
@@ -40,7 +38,7 @@ class Prawn::SVG::Elements::TextComponent < Prawn::SVG::Elements::DepthFirstBase
40
38
  end
41
39
 
42
40
  def apply
43
- raise SkipElementQuietly if computed_properties.display == "none"
41
+ raise SkipElementQuietly if computed_properties.display == 'none'
44
42
 
45
43
  font = select_font
46
44
  apply_font(font) if font
@@ -49,18 +47,21 @@ class Prawn::SVG::Elements::TextComponent < Prawn::SVG::Elements::DepthFirstBase
49
47
  # and so we handle them in Prawn::SVG::Interface#rewrite_call_arguments
50
48
  opts = {
51
49
  size: computed_properties.numerical_font_size,
52
- style: font && font.subfamily,
53
- text_anchor: computed_properties.text_anchor,
50
+ style: font&.subfamily,
51
+ text_anchor: computed_properties.text_anchor
54
52
  }
55
53
 
56
- opts[:dominant_baseline] = computed_properties.dominant_baseline unless computed_properties.dominant_baseline == 'auto'
54
+ unless computed_properties.dominant_baseline == 'auto'
55
+ opts[:dominant_baseline] =
56
+ computed_properties.dominant_baseline
57
+ end
57
58
  opts[:decoration] = computed_properties.text_decoration unless computed_properties.text_decoration == 'none'
58
59
 
59
60
  if state.text.parent
60
61
  add_call_and_enter 'character_spacing', state.text.spacing unless state.text.spacing == state.text.parent.spacing
61
62
  add_call_and_enter 'text_rendering_mode', state.text.mode unless state.text.mode == state.text.parent.mode
62
63
  else
63
- add_call_and_enter 'character_spacing', state.text.spacing unless state.text.spacing == 0
64
+ add_call_and_enter 'character_spacing', state.text.spacing unless state.text.spacing.zero?
64
65
  add_call_and_enter 'text_rendering_mode', state.text.mode unless state.text.mode == :fill
65
66
  end
66
67
 
@@ -107,7 +108,7 @@ class Prawn::SVG::Elements::TextComponent < Prawn::SVG::Elements::DepthFirstBase
107
108
  end
108
109
 
109
110
  def apply_text(text, opts)
110
- while text != ""
111
+ while text != ''
111
112
  x = y = dx = dy = rotate = nil
112
113
  remaining = rotation_remaining = false
113
114
 
@@ -152,7 +153,7 @@ class Prawn::SVG::Elements::TextComponent < Prawn::SVG::Elements::DepthFirstBase
152
153
 
153
154
  if remaining
154
155
  add_call 'draw_text', text[0..0], **opts.dup
155
- text = text[1..-1]
156
+ text = text[1..]
156
157
  else
157
158
  add_call 'draw_text', text, **opts.dup
158
159
 
@@ -160,11 +161,11 @@ class Prawn::SVG::Elements::TextComponent < Prawn::SVG::Elements::DepthFirstBase
160
161
  # solve this by shifting them out by the number of
161
162
  # characters we've just drawn
162
163
  shift = text.length - 1
163
- if rotation_remaining && shift > 0
164
+ if rotation_remaining && shift.positive?
164
165
  list = state.text
165
166
  while list
166
167
  count = [shift, list.rotation.length - 1].min
167
- list.rotation.shift(count) if count > 0
168
+ list.rotation.shift(count) if count.positive?
168
169
  list = list.parent
169
170
  end
170
171
  end
@@ -197,7 +198,7 @@ class Prawn::SVG::Elements::TextComponent < Prawn::SVG::Elements::DepthFirstBase
197
198
  href = href_attribute
198
199
 
199
200
  if href && href[0..0] == '#'
200
- element = document.elements_by_id[href[1..-1]]
201
+ element = document.elements_by_id[href[1..]]
201
202
  element if element.name == 'text'
202
203
  end
203
204
  end
@@ -241,10 +242,9 @@ class Prawn::SVG::Elements::TextComponent < Prawn::SVG::Elements::DepthFirstBase
241
242
  end
242
243
 
243
244
  # overridden, we don't want to call fill/stroke as draw_text does this for us
244
- def apply_drawing_call
245
- end
245
+ def apply_drawing_call; end
246
246
 
247
247
  def normalize_length(length)
248
- x_pixels(length) if length && length.match(/\d/)
248
+ x_pixels(length) if length&.match(/\d/)
249
249
  end
250
250
  end