prawn-svg 0.37.0 → 0.38.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: d5c28b2e706923d04785979a68f0e457f14df7c71b519d16db6e0bb45e3458bc
4
- data.tar.gz: 3ade59453c7005f624b4dcd36d9930f27285291c410b00fe57b3d3d278673e4c
3
+ metadata.gz: d9d63836ebf08ec1ebd6320b61a8f4b93c5df29e91e6e1397b53c65d548e8afe
4
+ data.tar.gz: 25ad6658329c42589d086e592589908a1c556a631503578219c308ab095fa513
5
5
  SHA512:
6
- metadata.gz: 250c187430ff8042898b9ee55a595162824613e13a9760a8e32dd21e5a026dfabe012a80f3c19442b3a97635ab020aa1eb113cd70dcc86ffef888f6bc2d39fa1
7
- data.tar.gz: 1d79258d401547d57dd694878e08d9a50d0166cce4fefd9351cfda0ef6a7c3096910a88a29650993f0eeadd44d7f2fba21d25f438e31660750cc4b847cacc760
6
+ metadata.gz: d73e5adc3fa832d2ed9159cf02cea3b47cf3923ff0e145af4de426effeb6157d4d133d600bf708ccd963a736d517fccb8a7c8d410ee479025288193659d34859
7
+ data.tar.gz: 20d5d9f28438c3ca4c23454541f763b1766f3df9719ced30a3451abc08e21913cf4276d2c148d9af695bab9e4670b01dcc1985fe5d9a9ebd94cfc08cee755818
data/Gemfile.lock CHANGED
@@ -1,7 +1,7 @@
1
1
  PATH
2
2
  remote: .
3
3
  specs:
4
- prawn-svg (0.37.0)
4
+ prawn-svg (0.38.0)
5
5
  css_parser (~> 1.6)
6
6
  matrix (~> 0.4.2)
7
7
  prawn (>= 0.11.1, < 3)
@@ -1,5 +1,7 @@
1
1
  module Prawn::SVG::Attributes::Transform
2
2
  def parse_transform_attribute_and_call
3
+ # Some elements do not support transforms
4
+ return unless transformable?
3
5
  return unless (transform = attributes['transform'])
4
6
 
5
7
  matrix = matrix_for_pdf(parse_transform_attribute(transform))
@@ -36,7 +36,7 @@ class Prawn::SVG::Elements::Base
36
36
  @attributes = {}
37
37
  @properties = Prawn::SVG::Properties.new
38
38
 
39
- if source && !state.inside_use
39
+ if source && add_to_elements_by_id? && !state.inside_use
40
40
  id = source.attributes['id']
41
41
  id = id.strip if id
42
42
 
@@ -114,6 +114,10 @@ class Prawn::SVG::Elements::Base
114
114
  @calls.concat duplicate_calls(other.base_calls)
115
115
  end
116
116
 
117
+ def add_yield_call(&block)
118
+ add_call('svg:yield', block)
119
+ end
120
+
117
121
  def new_call_context_from_base
118
122
  old_calls = @calls
119
123
  @calls = @base_calls
@@ -269,6 +273,14 @@ class Prawn::SVG::Elements::Base
269
273
  ['hidden', 'scroll'].include?(computed_properties.overflow)
270
274
  end
271
275
 
276
+ def transformable?
277
+ true
278
+ end
279
+
280
+ def add_to_elements_by_id?
281
+ true
282
+ end
283
+
272
284
  def stroke_width
273
285
  if computed_properties.stroke.none?
274
286
  0
@@ -0,0 +1,27 @@
1
+ #
2
+ # This element base class is used for elements that render directly to the Prawn canvas.
3
+ #
4
+ # Initially when I wrote prawn-svg, I was expecting to have multiple renderers and thought separating the codebase
5
+ # from the Prawn renderer would be a good idea. However, it turns out that the Prawn renderer was the only one
6
+ # that was targeted, and it ended up being tightly coupled with the Prawn library.
7
+ #
8
+ # This class is probably how I should have written it. Direct render is required to do text rendering properly
9
+ # because we need to know the width of all the things we print. As of the time of this comment it's the only
10
+ # system that uses DirectRenderBase in prawn-svg.
11
+ #
12
+ class Prawn::SVG::Elements::DirectRenderBase < Prawn::SVG::Elements::Base
13
+ # Called by Renderer when it finds the svg:render call added below.
14
+ def render(prawn, renderer); end
15
+
16
+ protected
17
+
18
+ def parse_and_apply
19
+ parse_standard_attributes
20
+ parse
21
+ apply_calls_from_standard_attributes
22
+ @parent_calls << ['svg:render', [self], [], []] unless computed_properties.display == 'none'
23
+ rescue SkipElementQuietly
24
+ rescue SkipElementError => e
25
+ @document.warnings << e.message
26
+ end
27
+ end
@@ -16,9 +16,9 @@ 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..].map { |point|
19
+ ] + @points[1..].map do |point|
20
20
  Prawn::SVG::Pathable::Line.new(point)
21
- } + [
21
+ end + [
22
22
  Prawn::SVG::Pathable::Close.new(@points[0])
23
23
  ]
24
24
  end
@@ -1,70 +1,72 @@
1
- class Prawn::SVG::Elements::Text < Prawn::SVG::Elements::DepthFirstBase
2
- private
1
+ module Prawn::SVG
2
+ class Elements::Text < Elements::DirectRenderBase
3
+ Cursor = Struct.new(:x, :y)
3
4
 
4
- # This class doesn't represent an element in the SVG document; it's here
5
- # to hold together text information. Because of this, we overload the
6
- # parse_step and apply_step methods, and bypass all the normal processing
7
- # of the element, delegating it to our root text component.
5
+ def parse
6
+ @root_component = Elements::TextComponent.new(document, source, [], state.dup)
7
+ @root_component.process
8
8
 
9
- def parse_step
10
- state.text = Prawn::SVG::Elements::TextComponent::TextState.new
9
+ reintroduce_trailing_and_leading_whitespace
10
+ end
11
11
 
12
- @text_root = Prawn::SVG::Elements::TextComponent.new(document, source, nil, state.dup)
13
- @text_root.parse_step
12
+ def render(prawn, renderer)
13
+ @root_component.lay_out(prawn)
14
14
 
15
- reintroduce_trailing_and_leading_whitespace
16
- end
15
+ translate_x =
16
+ case @root_component.computed_properties.text_anchor
17
+ when 'middle'
18
+ -@root_component.calculated_width / 2.0
19
+ when 'end'
20
+ -@root_component.calculated_width
21
+ end
17
22
 
18
- def apply_step(calls)
19
- @base_calls = @calls = calls
20
- add_call_and_enter 'text_group'
21
- @text_root.apply_step(@calls)
22
- end
23
+ cursor = Cursor.new(0, document.sizing.output_height)
24
+ @root_component.render_component(prawn, renderer, cursor, translate_x)
25
+ end
23
26
 
24
- def drawable?
25
- false
26
- end
27
+ private
27
28
 
28
- def reintroduce_trailing_and_leading_whitespace
29
- printables = []
30
- built_printable_queue(printables, @text_root)
29
+ def reintroduce_trailing_and_leading_whitespace
30
+ text_nodes = []
31
+ build_text_node_queue(text_nodes, @root_component)
31
32
 
32
- remove_whitespace_only_printables_and_start_and_end(printables)
33
- remove_printables_that_are_completely_empty(printables)
34
- apportion_leading_and_trailing_spaces(printables)
35
- end
33
+ remove_whitespace_only_text_nodes_and_start_and_end(text_nodes)
34
+ remove_text_nodes_that_are_completely_empty(text_nodes)
35
+ apportion_leading_and_trailing_spaces(text_nodes)
36
+ end
36
37
 
37
- def built_printable_queue(queue, component)
38
- component.commands.each do |command|
39
- case command
40
- when Prawn::SVG::Elements::TextComponent::Printable
41
- queue << command
42
- else
43
- built_printable_queue(queue, command)
38
+ def build_text_node_queue(queue, component)
39
+ component.children.each do |element|
40
+ case element
41
+ when Elements::TextNode
42
+ queue << element
43
+ else
44
+ build_text_node_queue(queue, element)
45
+ end
44
46
  end
45
47
  end
46
- end
47
48
 
48
- def remove_whitespace_only_printables_and_start_and_end(printables)
49
- printables.pop while printables.last && printables.last.text.empty?
50
- printables.shift while printables.first && printables.first.text.empty?
51
- end
49
+ def remove_whitespace_only_text_nodes_and_start_and_end(text_nodes)
50
+ text_nodes.pop while text_nodes.last && text_nodes.last.text.empty?
51
+ text_nodes.shift while text_nodes.first && text_nodes.first.text.empty?
52
+ end
52
53
 
53
- def remove_printables_that_are_completely_empty(printables)
54
- printables.reject! do |printable|
55
- printable.text.empty? && !printable.trailing_space? && !printable.leading_space?
54
+ def remove_text_nodes_that_are_completely_empty(text_nodes)
55
+ text_nodes.reject! do |text_node|
56
+ text_node.text.empty? && !text_node.trailing_space? && !text_node.leading_space?
57
+ end
56
58
  end
57
- end
58
59
 
59
- def apportion_leading_and_trailing_spaces(printables)
60
- printables.each_cons(2) do |a, b|
61
- if a.text.empty?
62
- # Empty strings can only get a leading space from the previous non-empty text,
63
- # and never get a trailing space
64
- elsif a.trailing_space?
65
- a.text += ' '
66
- elsif b.leading_space?
67
- b.text = " #{b.text}"
60
+ def apportion_leading_and_trailing_spaces(text_nodes)
61
+ text_nodes.each_cons(2) do |a, b|
62
+ if a.text.empty?
63
+ # Empty strings can only get a leading space from the previous non-empty text,
64
+ # and never get a trailing space
65
+ elsif a.trailing_space?
66
+ a.text += ' '
67
+ elsif b.leading_space?
68
+ b.text = " #{b.text}"
69
+ end
68
70
  end
69
71
  end
70
72
  end
@@ -1,250 +1,228 @@
1
- class Prawn::SVG::Elements::TextComponent < Prawn::SVG::Elements::DepthFirstBase
2
- attr_reader :commands
3
-
4
- Printable = Struct.new(:element, :text, :leading_space?, :trailing_space?)
5
- TextState = Struct.new(:parent, :x, :y, :dx, :dy, :rotation, :spacing, :mode, :text_length, :length_adjust)
6
-
7
- def parse
8
- raise SkipElementError, '<text> elements are not supported in clip paths' if state.inside_clip_path
1
+ module Prawn::SVG
2
+ class Elements::TextComponent < Elements::DirectRenderBase
3
+ attr_reader :children, :parent_component
4
+ attr_reader :x_values, :y_values, :dx, :dy, :rotation, :text_length, :length_adjust
5
+ attr_reader :font
6
+
7
+ def initialize(document, source, _calls, state, parent_component = nil)
8
+ if parent_component.nil? && source.name != 'text'
9
+ raise SkipElementError, 'attempted to <use> a component inside a text element, this is not supported'
10
+ end
9
11
 
10
- if state.text.nil?
11
- raise SkipElementError, 'attempted to <use> an component inside a text element, this is not supported'
12
+ super(document, source, [], state)
13
+ @parent_component = parent_component
12
14
  end
13
15
 
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)
19
- state.text.text_length = normalize_length(attributes['textLength'])
20
- state.text.length_adjust = attributes['lengthAdjust']
21
- state.text.spacing = calculate_character_spacing
22
- state.text.mode = calculate_text_rendering_mode
16
+ def parse
17
+ raise SkipElementError, '<text> elements are not supported in clip paths' if state.inside_clip_path
23
18
 
24
- @commands = []
19
+ @x_values = parse_wsp('x').map { |n| x(n) }
20
+ @y_values = parse_wsp('y').map { |n| y(n) }
21
+ @dx = parse_wsp('dx').map { |n| x_pixels(n) }
22
+ @dy = parse_wsp('dy').map { |n| y_pixels(n) }
23
+ @rotation = parse_wsp('rotate').map(&:to_f)
24
+ @text_length = normalize_length(attributes['textLength'])
25
+ @length_adjust = attributes['lengthAdjust']
25
26
 
26
- svg_text_children.each do |child|
27
- if child.node_type == :text
28
- append_text(child)
29
- else
30
- case child.name
31
- when 'tspan', 'tref'
32
- append_child(child)
27
+ @font = select_font
28
+
29
+ @children = svg_text_children.flat_map do |child|
30
+ if child.node_type == :text
31
+ build_text_node(child)
33
32
  else
34
- warnings << "Unknown tag '#{child.name}' inside text tag; ignoring"
33
+ case child.name
34
+ when 'tspan', 'tref'
35
+ build_child(child)
36
+ else
37
+ warnings << "Unknown tag '#{child.name}' inside text tag; ignoring"
38
+ []
39
+ end
35
40
  end
36
41
  end
37
42
  end
38
- end
39
43
 
40
- def apply
41
- raise SkipElementQuietly if computed_properties.display == 'none'
42
-
43
- font = select_font
44
- apply_font(font) if font
45
-
46
- # text_anchor and dominant_baseline aren't Prawn options; we have to do some math to support them
47
- # and so we handle them in Prawn::SVG::Interface#rewrite_call_arguments
48
- opts = {
49
- size: computed_properties.numeric_font_size,
50
- style: font&.subfamily,
51
- text_anchor: computed_properties.text_anchor
52
- }
53
-
54
- unless computed_properties.dominant_baseline == 'auto'
55
- opts[:dominant_baseline] =
56
- computed_properties.dominant_baseline
57
- end
58
- opts[:decoration] = computed_properties.text_decoration unless computed_properties.text_decoration == 'none'
59
-
60
- if state.text.parent
61
- add_call_and_enter 'character_spacing', state.text.spacing unless state.text.spacing == state.text.parent.spacing
62
- add_call_and_enter 'text_rendering_mode', state.text.mode unless state.text.mode == state.text.parent.mode
63
- else
64
- add_call_and_enter 'character_spacing', state.text.spacing unless state.text.spacing.zero?
65
- add_call_and_enter 'text_rendering_mode', state.text.mode unless state.text.mode == :fill
66
- end
67
-
68
- @commands.each do |command|
69
- case command
70
- when Printable
71
- apply_text(command.text, opts)
72
- when self.class
73
- add_call 'save'
74
- command.apply_step(calls)
75
- add_call 'restore'
76
- else
77
- raise
44
+ def lay_out(prawn)
45
+ @children.each do |child|
46
+ prawn.save_font do
47
+ prawn.font(font.name, style: font.subfamily) if font
48
+ child.lay_out(prawn)
49
+ end
78
50
  end
79
- end
80
-
81
- # It's possible there was no text to render. In that case, add a 'noop' so character_spacing/text_rendering_mode
82
- # don't blow up when they find they don't have a block to execute.
83
- add_call 'noop' if calls.empty?
84
- end
85
51
 
86
- protected
52
+ if @text_length
53
+ flexible_width, fixed_width = total_flexible_and_fixed_width
87
54
 
88
- def append_text(child)
89
- if state.preserve_space
90
- text = child.value.tr("\n\t", ' ')
91
- else
92
- text = child.value.tr("\n", '').tr("\t", ' ')
93
- leading = text[0] == ' '
94
- trailing = text[-1] == ' '
95
- text = text.strip.gsub(/ {2,}/, ' ')
55
+ if flexible_width.positive?
56
+ target_width = [@text_length - fixed_width, 0].max
57
+ factor = target_width / flexible_width
58
+ apply_factor_to_base_width(factor)
59
+ end
60
+ end
96
61
  end
97
62
 
98
- @commands << Printable.new(self, text, leading, trailing)
99
- end
63
+ def render_component(prawn, renderer, cursor, translate_x = nil)
64
+ raise SkipElementQuietly if computed_properties.display == 'none'
100
65
 
101
- def append_child(child)
102
- new_state = state.dup
103
- new_state.text = TextState.new(state.text)
66
+ add_yield_call do
67
+ prawn.translate(translate_x, 0) if translate_x
104
68
 
105
- element = self.class.new(document, child, calls, new_state)
106
- @commands << element
107
- element.parse_step
108
- end
69
+ size = computed_properties.numeric_font_size
109
70
 
110
- def apply_text(text, opts)
111
- while text != ''
112
- x = y = dx = dy = rotate = nil
113
- remaining = rotation_remaining = false
114
-
115
- list = state.text
116
- while list
117
- shifted = list.x.shift
118
- x ||= shifted
119
- shifted = list.y.shift
120
- y ||= shifted
121
- shifted = list.dx.shift
122
- dx ||= shifted
123
- shifted = list.dy.shift
124
- dy ||= shifted
125
-
126
- shifted = list.rotation.length > 1 ? list.rotation.shift : list.rotation.first
127
- if shifted && rotate.nil?
128
- rotate = shifted
129
- remaining ||= list.rotation != [0]
71
+ if computed_properties.dominant_baseline == 'middle'
72
+ height = FontMetrics.x_height_in_points(prawn, size || prawn.font_size)
73
+ y_offset = -height / 2.0
130
74
  end
131
75
 
132
- remaining ||= list.x.any? || list.y.any? || list.dx.any? || list.dy.any? || (rotate && rotate != 0)
133
- rotation_remaining ||= list.rotation.length > 1
134
- list = list.parent
76
+ prawn.save_font do
77
+ prawn.font(font.name, style: font.subfamily) if font
78
+
79
+ children.each do |child|
80
+ case child
81
+ when Elements::TextNode
82
+ child.render(prawn, size, cursor, y_offset)
83
+ when self.class
84
+ prawn.save_graphics_state
85
+ child.render_component(prawn, renderer, cursor)
86
+ prawn.restore_graphics_state
87
+ else
88
+ raise
89
+ end
90
+ end
91
+ end
135
92
  end
136
93
 
137
- opts[:at] = [x || :relative, y || :relative]
138
- opts[:offset] = [dx || 0, dy || 0]
94
+ renderer.render_calls(prawn, base_calls)
95
+ end
96
+
97
+ def calculated_width
98
+ children.reduce(0) { |total, child| total + child.calculated_width }
99
+ end
139
100
 
140
- if rotate && rotate != 0
141
- opts[:rotate] = -rotate
101
+ def current_length_adjust_is_scaling?
102
+ if @text_length
103
+ @length_adjust == 'spacingAndGlyphs'
104
+ elsif parent_component
105
+ parent_component.current_length_adjust_is_scaling?
142
106
  else
143
- opts.delete(:rotate)
107
+ false
144
108
  end
109
+ end
145
110
 
146
- if state.text.text_length
147
- if state.text.length_adjust == 'spacingAndGlyphs'
148
- opts[:stretch_to_width] = state.text.text_length
149
- else
150
- opts[:pad_to_width] = state.text.text_length
151
- end
111
+ def letter_spacing_pixels
112
+ if computed_properties.letter_spacing == 'normal'
113
+ nil
114
+ else
115
+ x_pixels(computed_properties.letter_spacing)
152
116
  end
117
+ end
153
118
 
154
- if remaining
155
- add_call 'draw_text', text[0..0], **opts.dup
156
- text = text[1..]
157
- else
158
- add_call 'draw_text', text, **opts.dup
159
-
160
- # we can get to this path with rotations still pending
161
- # solve this by shifting them out by the number of
162
- # characters we've just drawn
163
- shift = text.length - 1
164
- if rotation_remaining && shift.positive?
165
- list = state.text
166
- while list
167
- count = [shift, list.rotation.length - 1].min
168
- list.rotation.shift(count) if count.positive?
169
- list = list.parent
170
- end
171
- end
119
+ protected
172
120
 
173
- break
121
+ def build_text_node(child)
122
+ if state.preserve_space
123
+ text = child.value.tr("\n\t", ' ')
124
+ else
125
+ text = child.value.tr("\n", '').tr("\t", ' ')
126
+ leading = text[0] == ' '
127
+ trailing = text[-1] == ' '
128
+ text = text.strip.gsub(/ {2,}/, ' ')
174
129
  end
130
+
131
+ Elements::TextNode.new(self, text, leading, trailing)
132
+ end
133
+
134
+ def build_child(child)
135
+ component = self.class.new(document, child, [], state.dup, self)
136
+ component.process
137
+ component
175
138
  end
176
- end
177
139
 
178
- def svg_text_children
179
- text_children.select do |child|
180
- child.node_type == :text || (
181
- child.node_type == :element && (
182
- child.namespace == SVG_NAMESPACE || child.namespace == ''
140
+ def svg_text_children
141
+ text_children.select do |child|
142
+ child.node_type == :text || (
143
+ child.node_type == :element &&
144
+ [SVG_NAMESPACE, ''].include?(child.namespace)
145
+
183
146
  )
184
- )
147
+ end
185
148
  end
186
- end
187
149
 
188
- def text_children
189
- if name == 'tref'
190
- reference = find_referenced_element
191
- reference ? reference.source.children : []
192
- else
193
- source.children
150
+ def text_children
151
+ if name == 'tref'
152
+ reference = find_referenced_element
153
+ reference ? reference.source.children : []
154
+ else
155
+ source.children
156
+ end
194
157
  end
195
- end
196
158
 
197
- def find_referenced_element
198
- href = href_attribute
159
+ def find_referenced_element
160
+ href = href_attribute
199
161
 
200
- if href && href[0..0] == '#'
201
- element = document.elements_by_id[href[1..]]
202
- element if element.name == 'text'
162
+ if href && href[0..0] == '#'
163
+ element = document.elements_by_id[href[1..]]
164
+ element if element.name == 'text'
165
+ end
203
166
  end
204
- end
205
167
 
206
- def select_font
207
- font_families = [computed_properties.font_family, document.fallback_font_name]
208
- font_style = :italic if computed_properties.font_style == 'italic'
209
- font_weight = Prawn::SVG::Font.weight_for_css_font_weight(computed_properties.font_weight)
168
+ def select_font
169
+ font_families = [computed_properties.font_family, document.fallback_font_name]
170
+ font_style = :italic if computed_properties.font_style == 'italic'
171
+ font_weight = computed_properties.font_weight
210
172
 
211
- font_families.compact.each do |name|
212
- font = document.font_registry.load(name, font_weight, font_style)
213
- return font if font
173
+ font_families.compact.each do |name|
174
+ font = document.font_registry.load(name, font_weight, font_style)
175
+ return font if font
176
+ end
177
+
178
+ warnings << "Font family '#{computed_properties.font_family}' style '#{computed_properties.font_style}' is not a known font, and the fallback font could not be found."
179
+ nil
214
180
  end
215
181
 
216
- warnings << "Font family '#{computed_properties.font_family}' style '#{computed_properties.font_style}' is not a known font, and the fallback font could not be found."
217
- nil
218
- end
182
+ def total_flexible_and_fixed_width
183
+ flexible = fixed = 0
184
+ @children.each do |child|
185
+ child.total_flexible_and_fixed_width.tap do |a, b|
186
+ flexible += a
187
+ fixed += b
188
+ end
189
+ end
190
+ [flexible, fixed]
191
+ end
219
192
 
220
- def apply_font(font)
221
- add_call 'font', font.name, style: font.subfamily
222
- end
193
+ def apply_factor_to_base_width(factor)
194
+ @children.each do |child|
195
+ if child.is_a?(Elements::TextNode)
196
+ child.chunks.reject(&:fixed_width).each do |chunk|
197
+ chunk.fixed_width = chunk.base_width * factor
198
+ end
199
+ elsif child.is_a?(self.class)
200
+ child.apply_factor_to_base_width(factor)
201
+ else
202
+ raise
203
+ end
204
+ end
205
+ end
223
206
 
224
- def calculate_text_rendering_mode
225
- fill = !computed_properties.fill.none? # rubocop:disable Style/InverseMethods
226
- stroke = !computed_properties.stroke.none? # rubocop:disable Style/InverseMethods
207
+ # overridden from Base, we don't want to call fill/stroke as draw_text does this for us
208
+ def apply_drawing_call; end
227
209
 
228
- if fill && stroke
229
- :fill_stroke
230
- elsif fill
231
- :fill
232
- elsif stroke
233
- :stroke
234
- else
235
- :invisible
210
+ # overridden from Base, transforms can't be applied to tspan elements
211
+ def transformable?
212
+ source.name != 'tspan'
236
213
  end
237
- end
238
214
 
239
- def calculate_character_spacing
240
- spacing = computed_properties.letter_spacing
241
- spacing == 'normal' ? 0 : pixels(spacing)
242
- end
215
+ # overridden from Base, we want the id to point to the Text element
216
+ def add_to_elements_by_id?
217
+ source.name != 'text'
218
+ end
243
219
 
244
- # overridden, we don't want to call fill/stroke as draw_text does this for us
245
- def apply_drawing_call; end
220
+ def normalize_length(length)
221
+ x_pixels(length) if length&.match(/\d/)
222
+ end
246
223
 
247
- def normalize_length(length)
248
- x_pixels(length) if length&.match(/\d/)
224
+ def parse_wsp(name)
225
+ (attributes[name] || '').split(COMMA_WSP_REGEXP)
226
+ end
249
227
  end
250
228
  end