prawn-svg 0.33.0 → 0.34.1

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: c9f71b82858f8ee91e1ccaf2903c2e2a0d49108b1532a81d15c2275362a0d136
4
- data.tar.gz: f3336c6a727df24e6a5877e4d26cf030eabbb1c9af9852f95c35c5bfe5128995
3
+ metadata.gz: 0676f2574c6a16d7d1ea8bec6c43f807f6520a40f99d64ae7731646915576dc8
4
+ data.tar.gz: a294ac8f3c92bf4bc5030958f1a3f242e4fcc3099fcb3ae7893bc7cdc522decc
5
5
  SHA512:
6
- metadata.gz: 94f1a140e837ab37b088cc659bc8ef63a3e89778539236441f43e7b4885b9028cce1cb83c9e9636b00ff2ac740770ec8d853c123aa42b0764fb1264405269e76
7
- data.tar.gz: 4d02f2e14875214bb5460ace33925f9ab17302b39f46e0a37e0e5521e63b0fdd2f23f6018c8e048bf2bce7d148548aa86b5b9967dca827faddccfa23e8e09b13
6
+ metadata.gz: bdd3bac1ac0b2ec43140a84f248acfae5839f51bd7bd757acfc8713e391d136e5753db8110bed039463a7d378c855573230eccdda23a28e11cea2555aa2cdbb7
7
+ data.tar.gz: a702f63f43e0db24aef08edc8b12033d7dde1c7032746bc3ec7dc0a1e06002587d03d2145ec8e994416e886663013fc6c36153096b5c647f6549f283789913fb
@@ -6,7 +6,7 @@ jobs:
6
6
  strategy:
7
7
  fail-fast: false
8
8
  matrix:
9
- ruby: [2.6, 2.7, '3.0', 3.1, 3.2]
9
+ ruby: [2.6, 2.7, '3.0', 3.1, 3.2, 3.3]
10
10
  steps:
11
11
  - uses: actions/checkout@v4
12
12
  - name: Set up Ruby
data/.gitignore CHANGED
@@ -7,3 +7,4 @@ Gemfile.lock
7
7
  .ruby-version
8
8
  vendor/bundle/
9
9
  .bundle/
10
+ .mise.toml
data/README.md CHANGED
@@ -10,7 +10,7 @@ This will take an SVG document as input and render it into your PDF. Find out m
10
10
  http://github.com/prawnpdf/prawn
11
11
 
12
12
  prawn-svg is compatible with all versions of Prawn from 0.11.1 onwards, including the 1.x and 2.x series.
13
- The minimum Ruby version required is 2.3.0.
13
+ The minimum Ruby version required is 2.5.0.
14
14
 
15
15
  ## Using prawn-svg
16
16
 
@@ -33,7 +33,7 @@ Option | Data type | Description
33
33
  :enable_web_requests | boolean | If true, prawn-svg will make http and https requests to fetch images. Defaults to true.
34
34
  :enable_file_requests_with_root | string | If not nil, prawn-svg will serve `file:` URLs from your local disk if the file is located under the specified directory. It is very dangerous to specify the root path ("/") if you're not fully in control of your input SVG. Defaults to `nil` (off).
35
35
  :cache_images | boolean | If true, prawn-svg will cache the result of all URL requests. Defaults to false.
36
- :fallback_font_name | string | A font name which will override the default fallback font of Times-Roman. If this value is set to <tt>nil</tt>, prawn-svg will ignore a request for an unknown font and log a warning.
36
+ :fallback_font_name | string | A font name which will override the default fallback font of Times-Roman. If this value is set to `nil`, prawn-svg will ignore a request for an unknown font and log a warning.
37
37
  :color_mode | :rgb, :cmyk | Output color mode. Defaults to :rgb.
38
38
 
39
39
  ## Examples
@@ -54,52 +54,57 @@ Option | Data type | Description
54
54
 
55
55
  prawn-svg supports most but not all of the full SVG 1.1 specification. It currently supports:
56
56
 
57
- - <tt>&lt;line&gt;</tt>, <tt>&lt;polyline&gt;</tt>, <tt>&lt;polygon&gt;</tt>, <tt>&lt;circle&gt;</tt> and <tt>&lt;ellipse&gt;</tt>
57
+ - `<line>`, `<polyline>`, `<polygon>`, `<circle>` and `<ellipse>`
58
58
 
59
- - <tt>&lt;rect&gt;</tt>. Rounded rects are supported, but only one radius is applied to all corners.
59
+ - `<rect>`. Rounded rects are supported, but only one radius is applied to all corners.
60
60
 
61
- - <tt>&lt;path&gt;</tt> supports all commands defined in SVG 1.1, although the
61
+ - `<path>` supports all commands defined in SVG 1.1, although the
62
62
  implementation of elliptical arc is a bit rough at the moment.
63
63
 
64
- - `<text>`, `<tspan>` and `<tref>` with attributes `x`, `y`, `dx`, `dy`, `rotate`, 'textLength', 'lengthAdjust', and with extra properties
65
- `text-anchor`, `text-decoration` (underline only), `font-size`, `font-family`, `font-weight`, `font-style`, `letter-spacing`, `dominant-baseline` (middle only)
64
+ - `<text>`, `<tspan>` and `<tref>` with attributes `x`, `y`, `dx`, `dy`, `rotate`, `textLength`, `lengthAdjust`,
65
+ and with extra properties `text-anchor`, `text-decoration` (underline only), `font-size`, `font-family`,
66
+ `font-weight`, `font-style`, `letter-spacing`, `dominant-baseline` (middle only)
66
67
 
67
- - <tt>&lt;svg&gt;</tt>, <tt>&lt;g&gt;</tt> and <tt>&lt;symbol&gt;</tt>
68
+ - `<svg>`, `<g>` and `<symbol>`
68
69
 
69
- - <tt>&lt;use&gt;</tt>
70
+ - `<use>`
70
71
 
71
- - <tt>&lt;style&gt;</tt> (see CSS section below)
72
+ - `<style>` (see CSS section below)
72
73
 
73
- - `<image>` referencing a JPEG or PNG image, with `http:`, `https:`, `data:image/jpeg;base64`, `data:image/png;base64` and `file:` schemes
74
- (`file:` is disabled by default for security reasons, see Options section above)
74
+ - `<image>` referencing a JPEG, PNG, or SVG image, with `http:`, `https:`, `data:image/jpeg;base64`,
75
+ `data:image/png;base64`, `data:image/svg+xml;base64` and `file:` schemes (`file:` is disabled by default for
76
+ security reasons, see Options section above)
75
77
 
76
- - <tt>&lt;clipPath&gt;</tt>
78
+ - `<clipPath>`
77
79
 
78
80
  - `<marker>`
79
81
 
80
- - `<linearGradient>` and `<radialGradient>` are implemented on Prawn 2.2.0+ with attributes `gradientUnits` and `gradientTransform` (spreadMethod and stop-opacity are unimplemented.)
82
+ - `<linearGradient>` and `<radialGradient>` are implemented on Prawn 2.2.0+ with attributes `gradientUnits` and
83
+ `gradientTransform` (`spreadMethod` and `stop-opacity` are unimplemented.)
81
84
 
82
85
  - `<switch>` and `<foreignObject>`, although prawn-svg cannot handle any data that is not SVG so `<foreignObject>`
83
86
  tags are always ignored.
84
87
 
85
- - properties: `clip-path`, `color`, `display`, `fill`, `fill-opacity`, `fill-rule`, `opacity`, `overflow`, `stroke`, `stroke-dasharray`, `stroke-linecap`, `stroke-opacity`, `stroke-width`
88
+ - properties: `clip-path`, `color`, `display`, `fill`, `fill-opacity`, `fill-rule`, `opacity`, `overflow`,
89
+ `stroke`, `stroke-dasharray`, `stroke-linecap`, `stroke-linejoin`, `stroke-opacity`, `stroke-width`
86
90
 
87
91
  - properties on lines, polylines, polygons and paths: `marker-end`, `marker-mid`, `marker-start`
88
92
 
89
93
  - attributes on all elements: `class`, `id`, `style`, `transform`, `xml:space`
90
94
 
91
- - the <tt>viewBox</tt> attribute on <tt>&lt;svg&gt;</tt> and `<marker>` elements
95
+ - the `viewBox` attribute on `<svg>` and `<marker>` elements
92
96
 
93
- - the <tt>preserveAspectRatio</tt> attribute on <tt>&lt;svg&gt;</tt>, <tt>&lt;image&gt;</tt> and `<marker>` elements
97
+ - the `preserveAspectRatio` attribute on `<svg>`, `<image>` and `<marker>` elements
94
98
 
95
99
  - transform methods: `translate`, `translateX`, `translateY`, `rotate`, `scale`, `skewX`, `skewY`, `matrix`
96
100
 
97
- - colors: HTML standard names, <tt>#xxx</tt>, <tt>#xxxxxx</tt>, <tt>rgb(1, 2, 3)</tt>, <tt>rgb(1%, 2%, 3%)</tt>,
98
- and also the non-standard `device-cmyk(1, 2, 3, 4)` for CMYK colors
101
+ - colors: HTML standard names, `#xxx`, `#xxxxxx`, `rgb(1, 2, 3)`, `rgb(1%, 2%, 3%)`, and also the non-standard
102
+ `device-cmyk(1, 2, 3, 4)` for CMYK colors
99
103
 
100
- - measurements specified in <tt>pt</tt>, <tt>cm</tt>, <tt>dm</tt>, <tt>ft</tt>, <tt>in</tt>, <tt>m</tt>, <tt>mm</tt>, <tt>yd</tt>, <tt>pc</tt>, <tt>%</tt>
104
+ - measurements specified in `pt`, `cm`, `dm`, `ft`, `in`, `m`, `mm`, `yd`, `pc`, `%`
101
105
 
102
- - fonts: generic CSS fonts, built-in PDF fonts, and any TTF fonts in your fonts path, specified in any of the measurements above plus `em` or `rem`
106
+ - fonts: generic CSS fonts, built-in PDF fonts, and any TTF fonts in your fonts path, specified in any of the
107
+ measurements above plus `em` or `rem`
103
108
 
104
109
  ## CSS
105
110
 
@@ -126,8 +131,9 @@ It does not support text in the clip area, but you can clip shapes and text by a
126
131
 
127
132
  ### Fonts
128
133
 
129
- By default, prawn-svg has a fonts path of <tt>["/Library/Fonts", "/System/Library/Fonts", "#{ENV["HOME"]}/Library/Fonts", "/usr/share/fonts/truetype"]</tt> to catch
130
- Mac OS X and Debian Linux users. You can add to the font path:
134
+ By default, prawn-svg has a fonts path of `["/Library/Fonts", "/System/Library/Fonts",
135
+ "#{ENV["HOME"]}/Library/Fonts", "/usr/share/fonts/truetype"]` to catch MacOS and Debian Linux users. You can add
136
+ to the font path:
131
137
 
132
138
  ```ruby
133
139
  Prawn::SVG::FontRegistry.font_path << "/my/font/directory"
@@ -139,4 +145,4 @@ In your Gemfile, put `gem 'prawn-svg'` before `gem 'prawn-rails'` so that prawn-
139
145
 
140
146
  ## Licence
141
147
 
142
- MIT licence. Copyright Roger Nesbitt.
148
+ MIT licence. Copyright Mog Nesbitt.
@@ -16,7 +16,7 @@ class Prawn::SVG::Document
16
16
  :element_styles,
17
17
  :color_mode
18
18
 
19
- def initialize(data, bounds, options, font_registry: nil, css_parser: CssParser::Parser.new)
19
+ def initialize(data, bounds, options, font_registry: nil, css_parser: CssParser::Parser.new, attribute_overrides: {})
20
20
  @root = REXML::Document.new(data).root
21
21
 
22
22
  if @root.nil?
@@ -41,7 +41,10 @@ class Prawn::SVG::Document
41
41
  enable_file_with_root: options[:enable_file_requests_with_root]
42
42
  )
43
43
 
44
- @sizing = Prawn::SVG::Calculators::DocumentSizing.new(bounds, @root.attributes)
44
+ attributes = @root.attributes.dup
45
+ attribute_overrides.each { |key, value| attributes.add(REXML::Attribute.new(key, value)) }
46
+
47
+ @sizing = Prawn::SVG::Calculators::DocumentSizing.new(bounds, attributes)
45
48
  calculate_sizing(requested_width: options[:width], requested_height: options[:height])
46
49
 
47
50
  @element_styles = Prawn::SVG::CSS::Stylesheets.new(css_parser, root).load
@@ -3,29 +3,32 @@ class Prawn::SVG::Elements::Image < Prawn::SVG::Elements::Base
3
3
  def initialize(data)
4
4
  @data = data
5
5
  end
6
+
6
7
  def read
7
8
  @data
8
9
  end
9
- def rewind
10
- end
10
+
11
+ def rewind; end
11
12
  end
12
13
 
14
+ ImageData = Struct.new(:dimensions, :document)
15
+
13
16
  def parse
14
17
  require_attributes 'width', 'height'
15
18
 
16
- raise SkipElementQuietly if state.computed_properties.display == "none"
19
+ raise SkipElementQuietly if state.computed_properties.display == 'none'
17
20
 
18
21
  @url = href_attribute
19
- if @url.nil?
20
- raise SkipElementError, "image tag must have an href or xlink:href"
21
- end
22
+ raise SkipElementError, 'image tag must have an href or xlink:href' if @url.nil?
22
23
 
23
24
  x = x(attributes['x'] || 0)
24
25
  y = y(attributes['y'] || 0)
25
26
  width = x_pixels(attributes['width'])
26
27
  height = y_pixels(attributes['height'])
28
+ preserveAspectRatio = attributes['preserveAspectRatio']
27
29
 
28
30
  raise SkipElementQuietly if width.zero? || height.zero?
31
+
29
32
  require_positive_value width, height
30
33
 
31
34
  @image = begin
@@ -34,7 +37,9 @@ class Prawn::SVG::Elements::Image < Prawn::SVG::Elements::Base
34
37
  raise SkipElementError, "Error retrieving URL #{@url}: #{e.message}"
35
38
  end
36
39
 
37
- @aspect = Prawn::SVG::Calculators::AspectRatio.new(attributes['preserveAspectRatio'], [width, height], image_dimensions(@image))
40
+ @image_data = process_image(@image, width, height, preserveAspectRatio)
41
+
42
+ @aspect = Prawn::SVG::Calculators::AspectRatio.new(preserveAspectRatio, [width, height], @image_data.dimensions)
38
43
 
39
44
  @clip_x = x
40
45
  @clip_y = y
@@ -49,15 +54,21 @@ class Prawn::SVG::Elements::Image < Prawn::SVG::Elements::Base
49
54
 
50
55
  def apply
51
56
  if @aspect.slice?
52
- add_call "save"
53
- add_call "rectangle", [@clip_x, @clip_y], @clip_width, @clip_height
54
- add_call "clip"
57
+ add_call 'save'
58
+ add_call 'rectangle', [@clip_x, @clip_y], @clip_width, @clip_height
59
+ add_call 'clip'
55
60
  end
56
61
 
57
- options = {:width => @width, :height => @height, :at => [@x, @y]}
62
+ if (document = @image_data.document)
63
+ add_call_and_enter 'translate', @x, @y
64
+ add_call 'svg:render_sub_document', document
65
+ else
66
+ options = { width: @width, height: @height, at: [@x, @y] }
67
+
68
+ add_call 'image', FakeIO.new(@image), options
69
+ end
58
70
 
59
- add_call "image", FakeIO.new(@image), options
60
- add_call "restore" if @aspect.slice?
71
+ add_call 'restore' if @aspect.slice?
61
72
  end
62
73
 
63
74
  def bounding_box
@@ -66,15 +77,38 @@ class Prawn::SVG::Elements::Image < Prawn::SVG::Elements::Base
66
77
 
67
78
  protected
68
79
 
69
- def image_dimensions(data)
70
- unless (handler = find_image_handler(data))
71
- raise SkipElementError, 'Unsupported image type supplied to image tag'
80
+ def process_image(data, width, height, preserveAspectRatio)
81
+ if (handler = find_image_handler(data))
82
+ image = handler.new(data)
83
+ ImageData.new([image.width.to_f, image.height.to_f], nil)
84
+
85
+ elsif potentially_svg?(data)
86
+ document = Prawn::SVG::Document.new(
87
+ data, [width, height], { width: width, height: height },
88
+ attribute_overrides: { 'preserveAspectRatio' => preserveAspectRatio }
89
+ )
90
+
91
+ dimensions = [document.sizing.output_width, document.sizing.output_height]
92
+ ImageData.new(dimensions, document)
93
+
94
+ else
95
+ raise_invalid_image_type
72
96
  end
73
- image = handler.new(data)
74
- [image.width.to_f, image.height.to_f]
97
+ rescue Prawn::SVG::Document::InvalidSVGData
98
+ raise_invalid_image_type
75
99
  end
76
100
 
77
101
  def find_image_handler(data)
78
- Prawn.image_handler.find(data) rescue nil
102
+ Prawn.image_handler.find(data)
103
+ rescue StandardError
104
+ nil
105
+ end
106
+
107
+ def potentially_svg?(data)
108
+ data.include?('<svg')
109
+ end
110
+
111
+ def raise_invalid_image_type
112
+ raise SkipElementError, 'Unsupported image type supplied to image tag'
79
113
  end
80
114
  end
@@ -9,6 +9,10 @@ class Prawn::SVG::Elements::TextComponent < Prawn::SVG::Elements::DepthFirstBase
9
9
  raise SkipElementError, "<text> elements are not supported in clip paths"
10
10
  end
11
11
 
12
+ if state.text.nil?
13
+ raise SkipElementError, "attempted to <use> an component inside a text element, this is not supported"
14
+ end
15
+
12
16
  state.text.x = (attributes['x'] || "").split(COMMA_WSP_REGEXP).collect { |n| x(n) }
13
17
  state.text.y = (attributes['y'] || "").split(COMMA_WSP_REGEXP).collect { |n| y(n) }
14
18
  state.text.dx = (attributes['dx'] || "").split(COMMA_WSP_REGEXP).collect { |n| x_pixels(n) }
@@ -1,15 +1,12 @@
1
1
  class Prawn::SVG::Elements::Use < Prawn::SVG::Elements::Base
2
- attr_reader :referenced_element_class
3
- attr_reader :referenced_element_source
2
+ attr_reader :referenced_element_class, :referenced_element_source
4
3
 
5
4
  def parse
6
5
  href = href_attribute
7
- if href.nil?
8
- raise SkipElementError, "use tag must have an href or xlink:href"
9
- end
6
+ raise SkipElementError, 'use tag must have an href or xlink:href' if href.nil?
10
7
 
11
8
  if href[0..0] != '#'
12
- raise SkipElementError, "use tag has an href that is not a reference to an id; this is not supported"
9
+ raise SkipElementError, 'use tag has an href that is not a reference to an id; this is not supported'
13
10
  end
14
11
 
15
12
  id = href[1..-1]
@@ -29,14 +26,18 @@ class Prawn::SVG::Elements::Use < Prawn::SVG::Elements::Base
29
26
  end
30
27
  end
31
28
 
32
- if referenced_element_class.nil?
33
- raise SkipElementError, "no tag with ID '#{id}' was found, referenced by use tag"
29
+ raise SkipElementError, "no tag with ID '#{id}' was found, referenced by use tag" if referenced_element_class.nil?
30
+
31
+ if referenced_element_source.name == 'symbol'
32
+ @referenced_element_class = Prawn::SVG::Elements::Viewport
34
33
  end
35
34
 
36
35
  state.inside_use = true
37
36
 
38
37
  @x = attributes['x']
39
38
  @y = attributes['y']
39
+ @width = attributes['width']
40
+ @height = attributes['height']
40
41
  end
41
42
 
42
43
  def container?
@@ -45,16 +46,23 @@ class Prawn::SVG::Elements::Use < Prawn::SVG::Elements::Base
45
46
 
46
47
  def apply
47
48
  if @x || @y
48
- add_call_and_enter "translate", x_pixels(@x || 0), -y_pixels(@y || 0)
49
+ add_call_and_enter 'translate', x_pixels(@x || 0), -y_pixels(@y || 0)
49
50
  end
50
51
  end
51
52
 
52
53
  def process_child_elements
53
- add_call "save"
54
+ add_call 'save'
55
+
56
+ source = referenced_element_source.dup
57
+
58
+ if referenced_element_class == Prawn::SVG::Elements::Viewport
59
+ source.attributes['width'] = @width || '100%'
60
+ source.attributes['height'] = @height || '100%'
61
+ end
54
62
 
55
- child = referenced_element_class.new(document, referenced_element_source, calls, state.dup)
63
+ child = referenced_element_class.new(document, source, calls, state.dup)
56
64
  child.process
57
65
 
58
- add_call "restore"
66
+ add_call 'restore'
59
67
  end
60
68
  end
@@ -37,5 +37,6 @@ module Prawn::SVG::Elements
37
37
  foreignObject: Prawn::SVG::Elements::Ignored,
38
38
  :"font-face" => Prawn::SVG::Elements::Ignored,
39
39
  filter: Prawn::SVG::Elements::Ignored, # unsupported
40
+ mask: Prawn::SVG::Elements::Ignored, # unsupported
40
41
  }
41
42
  end
@@ -36,6 +36,7 @@ module Prawn::SVG
36
36
  end
37
37
 
38
38
  def gradient_element?(raw_element)
39
+ return false if raw_element.nil? || raw_element.name.nil?
39
40
  Elements::TAG_CLASS_MAPPING[raw_element.name.to_sym] == Elements::Gradient
40
41
  end
41
42
 
@@ -5,7 +5,15 @@
5
5
  module Prawn
6
6
  module SVG
7
7
  class Interface
8
- VALID_OPTIONS = [:at, :position, :vposition, :width, :height, :cache_images, :enable_web_requests, :enable_file_requests_with_root, :fallback_font_name, :color_mode]
8
+ VALID_OPTIONS = %i[
9
+ at position vposition width height cache_images enable_web_requests
10
+ enable_file_requests_with_root fallback_font_name color_mode
11
+ ]
12
+
13
+ INHERITABLE_OPTIONS = %i[
14
+ enable_web_requests enable_file_requests_with_root
15
+ cache_images fallback_font_name color_mode
16
+ ]
9
17
 
10
18
  attr_reader :data, :prawn, :document, :options
11
19
 
@@ -25,33 +33,19 @@ module Prawn
25
33
 
26
34
  font_registry = Prawn::SVG::FontRegistry.new(prawn.font_families)
27
35
 
28
- @document = Document.new(data, [prawn.bounds.width, prawn.bounds.height], options, font_registry: font_registry, &block)
36
+ @document = Document.new(
37
+ data, [prawn.bounds.width, prawn.bounds.height], options,
38
+ font_registry: font_registry, &block
39
+ )
40
+
41
+ @renderer = Renderer.new(prawn, document, options)
29
42
  end
30
43
 
31
44
  #
32
45
  # Draws the SVG to the Prawn::Document object.
33
46
  #
34
47
  def draw
35
- if @document.sizing.invalid?
36
- @document.warnings << "Zero or negative sizing data means this SVG cannot be rendered"
37
- return
38
- end
39
-
40
- @document.warnings.clear
41
-
42
- prawn.save_font do
43
- prawn.bounding_box(position, :width => @document.sizing.output_width, :height => @document.sizing.output_height) do
44
- prawn.save_graphics_state do
45
- clip_rectangle 0, 0, @document.sizing.output_width, @document.sizing.output_height
46
-
47
- calls = []
48
- root_element = Prawn::SVG::Elements::Root.new(@document, @document.root, calls)
49
- root_element.process
50
-
51
- proc_creator(prawn, calls).call
52
- end
53
- end
54
- end
48
+ @renderer.draw
55
49
  end
56
50
 
57
51
  def sizing
@@ -63,183 +57,12 @@ module Prawn
63
57
  end
64
58
 
65
59
  def position
66
- @options[:at] || [x_based_on_requested_alignment, y_based_on_requested_alignment]
60
+ @renderer.position
67
61
  end
68
62
 
69
63
  def self.font_path # backwards support for when the font_path used to be stored on this class
70
64
  Prawn::SVG::FontRegistry.font_path
71
65
  end
72
-
73
- private
74
-
75
- def x_based_on_requested_alignment
76
- case options[:position]
77
- when :left, nil
78
- 0
79
- when :center, :centre
80
- (@document.sizing.bounds[0] - @document.sizing.output_width) / 2.0
81
- when :right
82
- @document.sizing.bounds[0] - @document.sizing.output_width
83
- when Numeric
84
- options[:position]
85
- else
86
- raise ArgumentError, "options[:position] must be one of nil, :left, :right, :center or a number"
87
- end
88
- end
89
-
90
- def y_based_on_requested_alignment
91
- case options[:vposition]
92
- when nil
93
- prawn.cursor
94
- when :top
95
- @document.sizing.bounds[1]
96
- when :center, :centre
97
- @document.sizing.bounds[1] - (@document.sizing.bounds[1] - @document.sizing.output_height) / 2.0
98
- when :bottom
99
- @document.sizing.output_height
100
- when Numeric
101
- @document.sizing.bounds[1] - options[:vposition]
102
- else
103
- raise ArgumentError, "options[:vposition] must be one of nil, :top, :right, :bottom or a number"
104
- end
105
- end
106
-
107
- def proc_creator(prawn, calls)
108
- Proc.new {issue_prawn_command(prawn, calls)}
109
- end
110
-
111
- def issue_prawn_command(prawn, calls)
112
- calls.each do |call, arguments, kwarguments, children|
113
- skip = false
114
-
115
- rewrite_call_arguments(prawn, call, arguments, kwarguments) do
116
- issue_prawn_command(prawn, children) if children.any?
117
- skip = true
118
- end
119
-
120
- if skip
121
- # the call has been overridden
122
- elsif children.empty? && call != 'transparent' # some prawn calls complain if they aren't supplied a block
123
- if RUBY_VERSION >= '2.7' || !kwarguments.empty?
124
- prawn.send(call, *arguments, **kwarguments)
125
- else
126
- prawn.send(call, *arguments)
127
- end
128
- else
129
- if RUBY_VERSION >= '2.7' || !kwarguments.empty?
130
- prawn.send(call, *arguments, **kwarguments, &proc_creator(prawn, children))
131
- else
132
- prawn.send(call, *arguments, &proc_creator(prawn, children))
133
- end
134
- end
135
- end
136
- end
137
-
138
- def rewrite_call_arguments(prawn, call, arguments, kwarguments)
139
- case call
140
- when 'text_group'
141
- @cursor = [0, document.sizing.output_height]
142
- yield
143
-
144
- when 'draw_text'
145
- text, options = arguments.first, kwarguments
146
-
147
- at = options.fetch(:at)
148
-
149
- at[0] = @cursor[0] if at[0] == :relative
150
- at[1] = @cursor[1] if at[1] == :relative
151
-
152
- case options.delete(:dominant_baseline)
153
- when 'middle'
154
- height = prawn.font.height
155
- at[1] -= height / 2.0
156
- @cursor = [at[0], at[1]]
157
- end
158
-
159
- if offset = options.delete(:offset)
160
- at[0] += offset[0]
161
- at[1] -= offset[1]
162
- end
163
-
164
- width = prawn.width_of(text, options.merge(kerning: true))
165
-
166
- if stretch_to_width = options.delete(:stretch_to_width)
167
- factor = stretch_to_width.to_f * 100 / width.to_f
168
- prawn.add_content "#{factor} Tz"
169
- width = stretch_to_width.to_f
170
- end
171
-
172
- if pad_to_width = options.delete(:pad_to_width)
173
- padding_required = pad_to_width.to_f - width.to_f
174
- padding_per_character = padding_required / text.length.to_f
175
- prawn.add_content "#{padding_per_character} Tc"
176
- width = pad_to_width.to_f
177
- end
178
-
179
- case options.delete(:text_anchor)
180
- when 'middle'
181
- at[0] -= width / 2
182
- @cursor = [at[0] + width / 2, at[1]]
183
- when 'end'
184
- at[0] -= width
185
- @cursor = at.dup
186
- else
187
- @cursor = [at[0] + width, at[1]]
188
- end
189
-
190
- decoration = options.delete(:decoration)
191
- if decoration == 'underline'
192
- prawn.save_graphics_state do
193
- prawn.line_width 1
194
- prawn.line [at[0], at[1] - 1.25], [at[0] + width, at[1] - 1.25]
195
- prawn.stroke
196
- end
197
- end
198
-
199
- when 'transformation_matrix'
200
- left = prawn.bounds.absolute_left
201
- top = prawn.bounds.absolute_top
202
- arguments[4] += left - (left * arguments[0] + top * arguments[2])
203
- arguments[5] += top - (left * arguments[1] + top * arguments[3])
204
-
205
- when 'clip'
206
- prawn.add_content "W n" # clip to path
207
- yield
208
-
209
- when 'save'
210
- prawn.save_graphics_state
211
- yield
212
-
213
- when 'restore'
214
- prawn.restore_graphics_state
215
- yield
216
-
217
- when "end_path"
218
- yield
219
- prawn.add_content "n" # end path
220
-
221
- when 'fill_and_stroke'
222
- yield
223
- # prawn (as at 2.0.1 anyway) uses 'b' for its fill_and_stroke. 'b' is 'h' (closepath) + 'B', and we
224
- # never want closepath to be automatically run as it stuffs up many drawing operations, such as dashes
225
- # and line caps, and makes paths close that we didn't ask to be closed when fill is specified.
226
- even_odd = kwarguments[:fill_rule] == :even_odd
227
- content = even_odd ? 'B*' : 'B'
228
- prawn.add_content content
229
-
230
- when 'noop'
231
- yield
232
- end
233
- end
234
-
235
- def clip_rectangle(x, y, width, height)
236
- prawn.move_to x, y
237
- prawn.line_to x + width, y
238
- prawn.line_to x + width, y + height
239
- prawn.line_to x, y + height
240
- prawn.close_path
241
- prawn.add_content "W n" # clip to path
242
- end
243
66
  end
244
67
  end
245
68
  end
@@ -1,18 +1,17 @@
1
- require 'base64'
2
-
3
1
  module Prawn::SVG::Loaders
4
2
  class Data
5
- REGEXP = %r[\Adata:image/(png|jpeg);base64(;[a-z0-9]+)*,]i
3
+ REGEXP = %r{\Adata:image/(png|jpeg|svg\+xml);base64(;[a-z0-9]+)*,}i
6
4
 
7
5
  def from_url(url)
8
- return if url[0..4].downcase != "data:"
6
+ return if url[0..4].downcase != 'data:'
9
7
 
10
8
  matches = url.match(REGEXP)
11
9
  if matches.nil?
12
- raise Prawn::SVG::UrlLoader::Error, "prawn-svg only supports base64-encoded image/png and image/jpeg data URLs"
10
+ raise Prawn::SVG::UrlLoader::Error,
11
+ 'prawn-svg only supports base64-encoded image/png, image/jpeg, and image/svg+xml data URLs'
13
12
  end
14
13
 
15
- Base64.decode64(matches.post_match)
14
+ matches.post_match.unpack1('m')
16
15
  end
17
16
  end
18
17
  end
@@ -0,0 +1,236 @@
1
+ module Prawn
2
+ module SVG
3
+ class Renderer
4
+ attr_reader :prawn, :document, :options
5
+
6
+ #
7
+ # Creates a Prawn::SVG object.
8
+ #
9
+ # +data+ is the SVG data to convert. +prawn+ is your Prawn::Document object.
10
+ #
11
+ # See README.md for the options that can be passed to this method.
12
+ #
13
+ def initialize(prawn, document, options)
14
+ @prawn = prawn
15
+ @document = document
16
+ @options = options
17
+ end
18
+
19
+ #
20
+ # Draws the SVG to the Prawn::Document object.
21
+ #
22
+ def draw
23
+ if sizing.invalid?
24
+ document.warnings << 'Zero or negative sizing data means this SVG cannot be rendered'
25
+ return
26
+ end
27
+
28
+ document.warnings.clear
29
+
30
+ prawn.save_font do
31
+ prawn.bounding_box(position, width: sizing.output_width, height: sizing.output_height) do
32
+ prawn.save_graphics_state do
33
+ clip_rectangle 0, 0, sizing.output_width, sizing.output_height
34
+
35
+ calls = []
36
+ root_element = Prawn::SVG::Elements::Root.new(document, document.root, calls)
37
+ root_element.process
38
+
39
+ proc_creator(prawn, calls).call
40
+ end
41
+ end
42
+ end
43
+ end
44
+
45
+ def sizing
46
+ document.sizing
47
+ end
48
+
49
+ def position
50
+ options[:at] || [x_based_on_requested_alignment, y_based_on_requested_alignment]
51
+ end
52
+
53
+ private
54
+
55
+ def x_based_on_requested_alignment
56
+ case options[:position]
57
+ when :left, nil
58
+ 0
59
+ when :center, :centre
60
+ (sizing.bounds[0] - sizing.output_width) / 2.0
61
+ when :right
62
+ sizing.bounds[0] - sizing.output_width
63
+ when Numeric
64
+ options[:position]
65
+ else
66
+ raise ArgumentError, 'options[:position] must be one of nil, :left, :right, :center or a number'
67
+ end
68
+ end
69
+
70
+ def y_based_on_requested_alignment
71
+ case options[:vposition]
72
+ when nil
73
+ prawn.cursor
74
+ when :top
75
+ sizing.bounds[1]
76
+ when :center, :centre
77
+ sizing.bounds[1] - (sizing.bounds[1] - sizing.output_height) / 2.0
78
+ when :bottom
79
+ sizing.output_height
80
+ when Numeric
81
+ sizing.bounds[1] - options[:vposition]
82
+ else
83
+ raise ArgumentError, 'options[:vposition] must be one of nil, :top, :right, :bottom or a number'
84
+ end
85
+ end
86
+
87
+ def proc_creator(prawn, calls)
88
+ proc { issue_prawn_command(prawn, calls) }
89
+ end
90
+
91
+ def issue_prawn_command(prawn, calls)
92
+ calls.each do |call, arguments, kwarguments, children|
93
+ skip = false
94
+
95
+ rewrite_call_arguments(prawn, call, arguments, kwarguments) do
96
+ issue_prawn_command(prawn, children) if children.any?
97
+ skip = true
98
+ end
99
+
100
+ if skip
101
+ # the call has been overridden
102
+ elsif children.empty? && call != 'transparent' # some prawn calls complain if they aren't supplied a block
103
+ if RUBY_VERSION >= '2.7' || !kwarguments.empty?
104
+ prawn.send(call, *arguments, **kwarguments)
105
+ else
106
+ prawn.send(call, *arguments)
107
+ end
108
+ elsif RUBY_VERSION >= '2.7' || !kwarguments.empty?
109
+ prawn.send(call, *arguments, **kwarguments, &proc_creator(prawn, children))
110
+ else
111
+ prawn.send(call, *arguments, &proc_creator(prawn, children))
112
+ end
113
+ end
114
+ end
115
+
116
+ def rewrite_call_arguments(prawn, call, arguments, kwarguments)
117
+ case call
118
+ when 'text_group'
119
+ @cursor = [0, sizing.output_height]
120
+ yield
121
+
122
+ when 'draw_text'
123
+ text = arguments.first
124
+ options = kwarguments
125
+
126
+ at = options.fetch(:at)
127
+
128
+ at[0] = @cursor[0] if at[0] == :relative
129
+ at[1] = @cursor[1] if at[1] == :relative
130
+
131
+ case options.delete(:dominant_baseline)
132
+ when 'middle'
133
+ height = prawn.font.height
134
+ at[1] -= height / 2.0
135
+ @cursor = [at[0], at[1]]
136
+ end
137
+
138
+ if offset = options.delete(:offset)
139
+ at[0] += offset[0]
140
+ at[1] -= offset[1]
141
+ end
142
+
143
+ width = prawn.width_of(text, options.merge(kerning: true))
144
+
145
+ if stretch_to_width = options.delete(:stretch_to_width)
146
+ factor = stretch_to_width.to_f * 100 / width.to_f
147
+ prawn.add_content "#{factor} Tz"
148
+ width = stretch_to_width.to_f
149
+ end
150
+
151
+ if pad_to_width = options.delete(:pad_to_width)
152
+ padding_required = pad_to_width.to_f - width.to_f
153
+ padding_per_character = padding_required / text.length.to_f
154
+ prawn.add_content "#{padding_per_character} Tc"
155
+ width = pad_to_width.to_f
156
+ end
157
+
158
+ case options.delete(:text_anchor)
159
+ when 'middle'
160
+ at[0] -= width / 2
161
+ @cursor = [at[0] + width / 2, at[1]]
162
+ when 'end'
163
+ at[0] -= width
164
+ @cursor = at.dup
165
+ else
166
+ @cursor = [at[0] + width, at[1]]
167
+ end
168
+
169
+ decoration = options.delete(:decoration)
170
+ if decoration == 'underline'
171
+ prawn.save_graphics_state do
172
+ prawn.line_width 1
173
+ prawn.line [at[0], at[1] - 1.25], [at[0] + width, at[1] - 1.25]
174
+ prawn.stroke
175
+ end
176
+ end
177
+
178
+ when 'transformation_matrix'
179
+ left = prawn.bounds.absolute_left
180
+ top = prawn.bounds.absolute_top
181
+ arguments[4] += left - (left * arguments[0] + top * arguments[2])
182
+ arguments[5] += top - (left * arguments[1] + top * arguments[3])
183
+
184
+ when 'clip'
185
+ prawn.add_content 'W n' # clip to path
186
+ yield
187
+
188
+ when 'save'
189
+ prawn.save_graphics_state
190
+ yield
191
+
192
+ when 'restore'
193
+ prawn.restore_graphics_state
194
+ yield
195
+
196
+ when 'end_path'
197
+ yield
198
+ prawn.add_content 'n' # end path
199
+
200
+ when 'fill_and_stroke'
201
+ yield
202
+ # prawn (as at 2.0.1 anyway) uses 'b' for its fill_and_stroke. 'b' is 'h' (closepath) + 'B', and we
203
+ # never want closepath to be automatically run as it stuffs up many drawing operations, such as dashes
204
+ # and line caps, and makes paths close that we didn't ask to be closed when fill is specified.
205
+ even_odd = kwarguments[:fill_rule] == :even_odd
206
+ content = even_odd ? 'B*' : 'B'
207
+ prawn.add_content content
208
+
209
+ when 'noop'
210
+ yield
211
+
212
+ when 'svg:render_sub_document'
213
+ sub_document = arguments.first
214
+ sub_options = inheritable_options.merge({ at: [0, 0] })
215
+
216
+ Renderer.new(prawn, sub_document, sub_options).draw
217
+ document.warnings.concat(sub_document.warnings)
218
+ yield
219
+ end
220
+ end
221
+
222
+ def inheritable_options
223
+ (options || {}).slice(Prawn::SVG::Interface::INHERITABLE_OPTIONS)
224
+ end
225
+
226
+ def clip_rectangle(x, y, width, height)
227
+ prawn.move_to x, y
228
+ prawn.line_to x + width, y
229
+ prawn.line_to x + width, y + height
230
+ prawn.line_to x, y + height
231
+ prawn.close_path
232
+ prawn.add_content 'W n' # clip to path
233
+ end
234
+ end
235
+ end
236
+ end
@@ -1,5 +1,5 @@
1
1
  module Prawn
2
2
  module SVG
3
- VERSION = '0.33.0'
3
+ VERSION = '0.34.1'
4
4
  end
5
5
  end
data/lib/prawn-svg.rb CHANGED
@@ -21,6 +21,7 @@ require 'prawn/svg/properties'
21
21
  require 'prawn/svg/pathable'
22
22
  require 'prawn/svg/elements'
23
23
  require 'prawn/svg/extension'
24
+ require 'prawn/svg/renderer'
24
25
  require 'prawn/svg/interface'
25
26
  require 'prawn/svg/css/font_family_parser'
26
27
  require 'prawn/svg/css/selector_parser'
@@ -0,0 +1,6 @@
1
+ <?xml version="1.0" standalone="no"?>
2
+ <!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
3
+ <svg viewBox="0 0 100 100" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" version="1.1">
4
+ <rect x="0" y="0" width="100" height="100" fill="red" stroke="blue" stroke-width="5" />
5
+ </svg>
6
+
@@ -0,0 +1,9 @@
1
+ <?xml version="1.0" standalone="no"?>
2
+ <!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
3
+ <svg viewBox="0 0 1000 500" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" version="1.1">
4
+ <rect x="20" y="20" width="960" height="460" stroke="black" fill="white" />
5
+ <image xlink:href="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHZlcnNpb249IjEuMSIgd2lkdGg9IjExMDAiIGhlaWdodD0iNDAwIj4KICA8cmVjdCB4PSIxMDAiIHk9IjEwMCIgd2lkdGg9IjkwMCIgaGVpZ2h0PSIzMDAiIGZpbGw9Im5vbmUiIHN0cm9rZT0iYmx1ZSIgc3Ryb2tlLXdpZHRoPSIyIiAvPgoKICA8cGF0aCBkPSJNMjAwIDIwMCBMODAwIDMwMCBMODAwIDIwMCIgc3Ryb2tlPSJncmVlbiIgc3Ryb2tlLXdpZHRoPSIxMCIgZmlsbD0icmVkIiAvPgo8L3N2Zz4K" x="100" y="200" width="200" height="200" />
6
+ <image xlink:href="sample_images/image_svg_embed.svg" x="400" y="200" width="200" height="200" />
7
+ <image xlink:href="sample_images/image_svg_embed.svg" x="650" y="200" width="100" height="200" />
8
+ <image xlink:href="sample_images/image_svg_embed.svg" x="800" y="200" width="100" height="200" preserveAspectRatio="none" />
9
+ </svg>
@@ -0,0 +1,12 @@
1
+ <?xml version="1.0" encoding="UTF-8" standalone="no"?>
2
+ <!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
3
+ <svg viewBox="0 0 1000 1000" xmlns="http://www.w3.org/2000/svg" xmlns:xlink='http://www.w3.org/1999/xlink' version="1.1">
4
+ <symbol id="rects" viewBox="0 0 100 100">
5
+ <rect x="10" y="10" width="80" height="80" fill="none" stroke="red"/>
6
+ <rect x="20" y="20" width="60" height="60" fill="none" stroke="green"/>
7
+ <rect x="30" y="30" width="40" height="40" fill="none" stroke="blue"/>
8
+ </symbol>
9
+ <rect width="1000" height="1000" fill="none" stroke="black"/>
10
+ <use x="100" y="100" width="800" height="800" xlink:href="#rects"/>
11
+ <use x="400" y="400" width="200" height="200" xlink:href="#rects"/>
12
+ </svg>
data/spec/spec_helper.rb CHANGED
@@ -3,12 +3,12 @@ Bundler.require(:default, :development)
3
3
 
4
4
  # Requires supporting files with custom matchers and macros, etc,
5
5
  # in ./support/ and its subdirectories.
6
- Dir["#{File.dirname(__FILE__)}/support/**/*.rb"].each {|f| require f}
6
+ Dir["#{File.dirname(__FILE__)}/support/**/*.rb"].each { |f| require f }
7
7
 
8
8
  module Support
9
- def flatten_calls(calls)
9
+ def flatten_calls(_calls)
10
10
  [].tap do |flattened_calls|
11
- add = -> (local_calls) do
11
+ add = lambda do |local_calls|
12
12
  local_calls.each do |call|
13
13
  flattened_calls << call[0..2]
14
14
  add.call call[3]
@@ -28,12 +28,36 @@ end
28
28
 
29
29
  RSpec.configure do |config|
30
30
  config.mock_with :rspec do |c|
31
- c.syntax = [:should, :expect]
31
+ c.syntax = %i[should expect]
32
32
  end
33
33
 
34
34
  config.expect_with :rspec do |c|
35
- c.syntax = [:should, :expect]
35
+ c.syntax = %i[should expect]
36
36
  end
37
37
 
38
38
  config.include Support
39
+
40
+ config.before(:suite) do
41
+ # calculate the MD5 of all files in spec/sample_output and store in a hash
42
+ $hashes = {}
43
+
44
+ Dir["#{File.dirname(__FILE__)}/sample_output/*.pdf"].each do |file|
45
+ hash = Digest::MD5.file(file).hexdigest
46
+ $hashes[file] = hash
47
+ end
48
+ end
49
+
50
+ config.after(:suite) do
51
+ # print out the PDFs that have changed
52
+ changed = $hashes.select do |file, hash|
53
+ new_hash = Digest::MD5.file(file).hexdigest
54
+ new_hash != hash
55
+ end
56
+
57
+ if changed.any?
58
+ puts "\nThese PDFs have changed since the last test run:"
59
+ cwd = "#{Dir.pwd}/"
60
+ changed.each { |file, _| puts " #{file.sub(cwd, '')}" }
61
+ end
62
+ end
39
63
  end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: prawn-svg
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.33.0
4
+ version: 0.34.1
5
5
  platform: ruby
6
6
  authors:
7
7
  - Mog Nesbitt
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2023-12-16 00:00:00.000000000 Z
11
+ date: 2024-03-06 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: css_parser
@@ -164,6 +164,7 @@ files:
164
164
  - lib/prawn/svg/loaders/web.rb
165
165
  - lib/prawn/svg/pathable.rb
166
166
  - lib/prawn/svg/properties.rb
167
+ - lib/prawn/svg/renderer.rb
167
168
  - lib/prawn/svg/state.rb
168
169
  - lib/prawn/svg/transform_parser.rb
169
170
  - lib/prawn/svg/ttf.rb
@@ -201,6 +202,7 @@ files:
201
202
  - spec/prawn/svg/transform_parser_spec.rb
202
203
  - spec/prawn/svg/ttf_spec.rb
203
204
  - spec/prawn/svg/url_loader_spec.rb
205
+ - spec/sample_images/image_svg_embed.svg
204
206
  - spec/sample_images/mushroom-long.jpg
205
207
  - spec/sample_images/mushroom-wide.jpg
206
208
  - spec/sample_output/.keep
@@ -227,6 +229,7 @@ files:
227
229
  - spec/sample_svg/image01.svg
228
230
  - spec/sample_svg/image02_base64.svg
229
231
  - spec/sample_svg/image03.svg
232
+ - spec/sample_svg/image_svg.svg
230
233
  - spec/sample_svg/line01.svg
231
234
  - spec/sample_svg/links.svg
232
235
  - spec/sample_svg/marker.svg
@@ -257,6 +260,7 @@ files:
257
260
  - spec/sample_svg/subviewports.svg
258
261
  - spec/sample_svg/subviewports2.svg
259
262
  - spec/sample_svg/svg_fill.svg
263
+ - spec/sample_svg/symbol.svg
260
264
  - spec/sample_svg/text-decoration.svg
261
265
  - spec/sample_svg/text_entities.svg
262
266
  - spec/sample_svg/text_stroke.svg
@@ -296,7 +300,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
296
300
  - !ruby/object:Gem::Version
297
301
  version: '0'
298
302
  requirements: []
299
- rubygems_version: 3.4.10
303
+ rubygems_version: 3.5.3
300
304
  signing_key:
301
305
  specification_version: 4
302
306
  summary: SVG renderer for Prawn PDF library
@@ -332,6 +336,7 @@ test_files:
332
336
  - spec/prawn/svg/transform_parser_spec.rb
333
337
  - spec/prawn/svg/ttf_spec.rb
334
338
  - spec/prawn/svg/url_loader_spec.rb
339
+ - spec/sample_images/image_svg_embed.svg
335
340
  - spec/sample_images/mushroom-long.jpg
336
341
  - spec/sample_images/mushroom-wide.jpg
337
342
  - spec/sample_output/.keep
@@ -358,6 +363,7 @@ test_files:
358
363
  - spec/sample_svg/image01.svg
359
364
  - spec/sample_svg/image02_base64.svg
360
365
  - spec/sample_svg/image03.svg
366
+ - spec/sample_svg/image_svg.svg
361
367
  - spec/sample_svg/line01.svg
362
368
  - spec/sample_svg/links.svg
363
369
  - spec/sample_svg/marker.svg
@@ -388,6 +394,7 @@ test_files:
388
394
  - spec/sample_svg/subviewports.svg
389
395
  - spec/sample_svg/subviewports2.svg
390
396
  - spec/sample_svg/svg_fill.svg
397
+ - spec/sample_svg/symbol.svg
391
398
  - spec/sample_svg/text-decoration.svg
392
399
  - spec/sample_svg/text_entities.svg
393
400
  - spec/sample_svg/text_stroke.svg