prawn-svg 0.33.0 → 0.34.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 +4 -4
- data/.github/workflows/test.yml +1 -1
- data/.gitignore +1 -0
- data/README.md +30 -24
- data/lib/prawn/svg/document.rb +5 -2
- data/lib/prawn/svg/elements/image.rb +53 -19
- data/lib/prawn/svg/elements/text_component.rb +4 -0
- data/lib/prawn/svg/elements/use.rb +20 -12
- data/lib/prawn/svg/elements.rb +1 -0
- data/lib/prawn/svg/interface.rb +17 -194
- data/lib/prawn/svg/loaders/data.rb +4 -3
- data/lib/prawn/svg/renderer.rb +236 -0
- data/lib/prawn/svg/version.rb +1 -1
- data/lib/prawn-svg.rb +1 -0
- data/prawn-svg.gemspec +1 -0
- data/spec/sample_images/image_svg_embed.svg +6 -0
- data/spec/sample_svg/image_svg.svg +9 -0
- data/spec/sample_svg/symbol.svg +12 -0
- data/spec/spec_helper.rb +29 -5
- metadata +24 -3
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: fa5a995fd0ec6d8426dcc87603695e91ff743e1fddc559da6cc7fcc08ab5eb88
|
|
4
|
+
data.tar.gz: d76a03c53c2d7dab41db812e3309f59f32baefb730970015a8bb35d6fb791653
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 0a686e69f4759243fc9b672a4bad663f17bf3f4f1a7016e560d816859d3d49464dfbb6416d877cf35f5362cd316111661e96817b645f707cc19999bae7e98f17
|
|
7
|
+
data.tar.gz: 57fbf5408c56228a286ca10221759440c4fc1326c0855acc4c5a74a30652ca59af277d170cf5bcb7f40b24bfe511e6ff82fd24e81da301e15595dc7f9d283e7a
|
data/.github/workflows/test.yml
CHANGED
data/.gitignore
CHANGED
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.
|
|
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
|
|
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
|
-
-
|
|
57
|
+
- `<line>`, `<polyline>`, `<polygon>`, `<circle>` and `<ellipse>`
|
|
58
58
|
|
|
59
|
-
-
|
|
59
|
+
- `<rect>`. Rounded rects are supported, but only one radius is applied to all corners.
|
|
60
60
|
|
|
61
|
-
-
|
|
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`,
|
|
65
|
-
`text-anchor`, `text-decoration` (underline only), `font-size`, `font-family`,
|
|
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
|
-
-
|
|
68
|
+
- `<svg>`, `<g>` and `<symbol>`
|
|
68
69
|
|
|
69
|
-
-
|
|
70
|
+
- `<use>`
|
|
70
71
|
|
|
71
|
-
-
|
|
72
|
+
- `<style>` (see CSS section below)
|
|
72
73
|
|
|
73
|
-
- `<image>` referencing a JPEG or
|
|
74
|
-
(`file:` is disabled by default for
|
|
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
|
-
-
|
|
78
|
+
- `<clipPath>`
|
|
77
79
|
|
|
78
80
|
- `<marker>`
|
|
79
81
|
|
|
80
|
-
- `<linearGradient>` and `<radialGradient>` are implemented on Prawn 2.2.0+ with attributes `gradientUnits` and
|
|
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`,
|
|
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
|
|
95
|
+
- the `viewBox` attribute on `<svg>` and `<marker>` elements
|
|
92
96
|
|
|
93
|
-
- the
|
|
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,
|
|
98
|
-
|
|
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
|
|
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
|
|
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
|
|
130
|
-
|
|
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
|
|
148
|
+
MIT licence. Copyright Mog Nesbitt.
|
data/lib/prawn/svg/document.rb
CHANGED
|
@@ -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
|
-
|
|
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
|
-
|
|
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 ==
|
|
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
|
-
@
|
|
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
|
|
53
|
-
add_call
|
|
54
|
-
add_call
|
|
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
|
-
|
|
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
|
|
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
|
|
70
|
-
|
|
71
|
-
|
|
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
|
-
|
|
74
|
-
|
|
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)
|
|
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,
|
|
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
|
-
|
|
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
|
|
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
|
|
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,
|
|
63
|
+
child = referenced_element_class.new(document, source, calls, state.dup)
|
|
56
64
|
child.process
|
|
57
65
|
|
|
58
|
-
add_call
|
|
66
|
+
add_call 'restore'
|
|
59
67
|
end
|
|
60
68
|
end
|
data/lib/prawn/svg/elements.rb
CHANGED
data/lib/prawn/svg/interface.rb
CHANGED
|
@@ -5,7 +5,15 @@
|
|
|
5
5
|
module Prawn
|
|
6
6
|
module SVG
|
|
7
7
|
class Interface
|
|
8
|
-
VALID_OPTIONS = [
|
|
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(
|
|
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
|
-
|
|
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
|
-
@
|
|
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
|
|
@@ -2,14 +2,15 @@ require 'base64'
|
|
|
2
2
|
|
|
3
3
|
module Prawn::SVG::Loaders
|
|
4
4
|
class Data
|
|
5
|
-
REGEXP = %r
|
|
5
|
+
REGEXP = %r{\Adata:image/(png|jpeg|svg\+xml);base64(;[a-z0-9]+)*,}i
|
|
6
6
|
|
|
7
7
|
def from_url(url)
|
|
8
|
-
return if url[0..4].downcase !=
|
|
8
|
+
return if url[0..4].downcase != 'data:'
|
|
9
9
|
|
|
10
10
|
matches = url.match(REGEXP)
|
|
11
11
|
if matches.nil?
|
|
12
|
-
raise Prawn::SVG::UrlLoader::Error,
|
|
12
|
+
raise Prawn::SVG::UrlLoader::Error,
|
|
13
|
+
'prawn-svg only supports base64-encoded image/png, image/jpeg, and image/svg+xml data URLs'
|
|
13
14
|
end
|
|
14
15
|
|
|
15
16
|
Base64.decode64(matches.post_match)
|
|
@@ -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
|
data/lib/prawn/svg/version.rb
CHANGED
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'
|
data/prawn-svg.gemspec
CHANGED
|
@@ -19,6 +19,7 @@ Gem::Specification.new do |gem|
|
|
|
19
19
|
|
|
20
20
|
gem.required_ruby_version = '>= 2.5.0'
|
|
21
21
|
|
|
22
|
+
gem.add_runtime_dependency 'base64', '~> 0.1.0'
|
|
22
23
|
gem.add_runtime_dependency 'css_parser', '~> 1.6'
|
|
23
24
|
gem.add_runtime_dependency 'matrix', '~> 0.4.2'
|
|
24
25
|
gem.add_runtime_dependency 'prawn', '>= 0.11.1', '< 3'
|
|
@@ -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(
|
|
9
|
+
def flatten_calls(_calls)
|
|
10
10
|
[].tap do |flattened_calls|
|
|
11
|
-
add =
|
|
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 = [
|
|
31
|
+
c.syntax = %i[should expect]
|
|
32
32
|
end
|
|
33
33
|
|
|
34
34
|
config.expect_with :rspec do |c|
|
|
35
|
-
c.syntax = [
|
|
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,15 +1,29 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: prawn-svg
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 0.
|
|
4
|
+
version: 0.34.0
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Mog Nesbitt
|
|
8
8
|
autorequire:
|
|
9
9
|
bindir: bin
|
|
10
10
|
cert_chain: []
|
|
11
|
-
date:
|
|
11
|
+
date: 2024-01-28 00:00:00.000000000 Z
|
|
12
12
|
dependencies:
|
|
13
|
+
- !ruby/object:Gem::Dependency
|
|
14
|
+
name: base64
|
|
15
|
+
requirement: !ruby/object:Gem::Requirement
|
|
16
|
+
requirements:
|
|
17
|
+
- - "~>"
|
|
18
|
+
- !ruby/object:Gem::Version
|
|
19
|
+
version: 0.1.0
|
|
20
|
+
type: :runtime
|
|
21
|
+
prerelease: false
|
|
22
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
23
|
+
requirements:
|
|
24
|
+
- - "~>"
|
|
25
|
+
- !ruby/object:Gem::Version
|
|
26
|
+
version: 0.1.0
|
|
13
27
|
- !ruby/object:Gem::Dependency
|
|
14
28
|
name: css_parser
|
|
15
29
|
requirement: !ruby/object:Gem::Requirement
|
|
@@ -164,6 +178,7 @@ files:
|
|
|
164
178
|
- lib/prawn/svg/loaders/web.rb
|
|
165
179
|
- lib/prawn/svg/pathable.rb
|
|
166
180
|
- lib/prawn/svg/properties.rb
|
|
181
|
+
- lib/prawn/svg/renderer.rb
|
|
167
182
|
- lib/prawn/svg/state.rb
|
|
168
183
|
- lib/prawn/svg/transform_parser.rb
|
|
169
184
|
- lib/prawn/svg/ttf.rb
|
|
@@ -201,6 +216,7 @@ files:
|
|
|
201
216
|
- spec/prawn/svg/transform_parser_spec.rb
|
|
202
217
|
- spec/prawn/svg/ttf_spec.rb
|
|
203
218
|
- spec/prawn/svg/url_loader_spec.rb
|
|
219
|
+
- spec/sample_images/image_svg_embed.svg
|
|
204
220
|
- spec/sample_images/mushroom-long.jpg
|
|
205
221
|
- spec/sample_images/mushroom-wide.jpg
|
|
206
222
|
- spec/sample_output/.keep
|
|
@@ -227,6 +243,7 @@ files:
|
|
|
227
243
|
- spec/sample_svg/image01.svg
|
|
228
244
|
- spec/sample_svg/image02_base64.svg
|
|
229
245
|
- spec/sample_svg/image03.svg
|
|
246
|
+
- spec/sample_svg/image_svg.svg
|
|
230
247
|
- spec/sample_svg/line01.svg
|
|
231
248
|
- spec/sample_svg/links.svg
|
|
232
249
|
- spec/sample_svg/marker.svg
|
|
@@ -257,6 +274,7 @@ files:
|
|
|
257
274
|
- spec/sample_svg/subviewports.svg
|
|
258
275
|
- spec/sample_svg/subviewports2.svg
|
|
259
276
|
- spec/sample_svg/svg_fill.svg
|
|
277
|
+
- spec/sample_svg/symbol.svg
|
|
260
278
|
- spec/sample_svg/text-decoration.svg
|
|
261
279
|
- spec/sample_svg/text_entities.svg
|
|
262
280
|
- spec/sample_svg/text_stroke.svg
|
|
@@ -296,7 +314,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
|
296
314
|
- !ruby/object:Gem::Version
|
|
297
315
|
version: '0'
|
|
298
316
|
requirements: []
|
|
299
|
-
rubygems_version: 3.
|
|
317
|
+
rubygems_version: 3.5.3
|
|
300
318
|
signing_key:
|
|
301
319
|
specification_version: 4
|
|
302
320
|
summary: SVG renderer for Prawn PDF library
|
|
@@ -332,6 +350,7 @@ test_files:
|
|
|
332
350
|
- spec/prawn/svg/transform_parser_spec.rb
|
|
333
351
|
- spec/prawn/svg/ttf_spec.rb
|
|
334
352
|
- spec/prawn/svg/url_loader_spec.rb
|
|
353
|
+
- spec/sample_images/image_svg_embed.svg
|
|
335
354
|
- spec/sample_images/mushroom-long.jpg
|
|
336
355
|
- spec/sample_images/mushroom-wide.jpg
|
|
337
356
|
- spec/sample_output/.keep
|
|
@@ -358,6 +377,7 @@ test_files:
|
|
|
358
377
|
- spec/sample_svg/image01.svg
|
|
359
378
|
- spec/sample_svg/image02_base64.svg
|
|
360
379
|
- spec/sample_svg/image03.svg
|
|
380
|
+
- spec/sample_svg/image_svg.svg
|
|
361
381
|
- spec/sample_svg/line01.svg
|
|
362
382
|
- spec/sample_svg/links.svg
|
|
363
383
|
- spec/sample_svg/marker.svg
|
|
@@ -388,6 +408,7 @@ test_files:
|
|
|
388
408
|
- spec/sample_svg/subviewports.svg
|
|
389
409
|
- spec/sample_svg/subviewports2.svg
|
|
390
410
|
- spec/sample_svg/svg_fill.svg
|
|
411
|
+
- spec/sample_svg/symbol.svg
|
|
391
412
|
- spec/sample_svg/text-decoration.svg
|
|
392
413
|
- spec/sample_svg/text_entities.svg
|
|
393
414
|
- spec/sample_svg/text_stroke.svg
|