svg_optimizer 0.0.5 → 0.1.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.
Potentially problematic release.
This version of svg_optimizer might be problematic. Click here for more details.
- checksums.yaml +4 -4
- data/README.md +29 -0
- data/lib/svg_optimizer.rb +7 -10
- data/lib/svg_optimizer/plugins/base.rb +1 -0
- data/lib/svg_optimizer/plugins/cleanup_attribute.rb +1 -0
- data/lib/svg_optimizer/plugins/cleanup_id.rb +1 -0
- data/lib/svg_optimizer/plugins/collapse_groups.rb +15 -11
- data/lib/svg_optimizer/plugins/remove_comment.rb +1 -0
- data/lib/svg_optimizer/plugins/remove_editor_namespace.rb +14 -13
- data/lib/svg_optimizer/plugins/remove_empty_attribute.rb +2 -0
- data/lib/svg_optimizer/plugins/remove_empty_container.rb +2 -0
- data/lib/svg_optimizer/plugins/remove_empty_text_node.rb +2 -0
- data/lib/svg_optimizer/plugins/remove_hidden_element.rb +1 -0
- data/lib/svg_optimizer/plugins/remove_metadata.rb +1 -0
- data/lib/svg_optimizer/plugins/remove_raster_image.rb +1 -0
- data/lib/svg_optimizer/plugins/remove_unused_namespace.rb +1 -0
- data/lib/svg_optimizer/plugins/remove_useless_stroke_and_fill.rb +18 -17
- data/lib/svg_optimizer/version.rb +2 -1
- data/spec/spec_helper.rb +13 -6
- data/spec/svg_optimizer/plugins/cleanup_attribute_spec.rb +1 -0
- data/spec/svg_optimizer/plugins/cleanup_id_spec.rb +1 -0
- data/spec/svg_optimizer/plugins/collapse_groups_spec.rb +2 -1
- data/spec/svg_optimizer/plugins/remove_comment_spec.rb +1 -0
- data/spec/svg_optimizer/plugins/remove_editor_namespace_spec.rb +1 -0
- data/spec/svg_optimizer/plugins/remove_empty_attribute_spec.rb +1 -0
- data/spec/svg_optimizer/plugins/remove_empty_container_spec.rb +1 -0
- data/spec/svg_optimizer/plugins/remove_empty_text_node_spec.rb +1 -0
- data/spec/svg_optimizer/plugins/remove_hidden_element_spec.rb +1 -0
- data/spec/svg_optimizer/plugins/remove_metadata_spec.rb +1 -0
- data/spec/svg_optimizer/plugins/remove_raster_image_spec.rb +1 -0
- data/spec/svg_optimizer/plugins/remove_unused_namespace_spec.rb +1 -0
- data/spec/svg_optimizer/plugins/remove_useless_stroke_and_fill_spec.rb +2 -1
- data/spec/svg_optimizer/svg_optimizer_spec.rb +54 -0
- metadata +17 -1
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: e80d8684aae3105745cd84db8c935a332bc58b02
|
4
|
+
data.tar.gz: 698bc37343bf931cea862afb939cd0a45547208b
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 1d4bf32d76d719e774137c9a00231de35caaed49d4a7f970606f9f6e58b2729bbae67708df72719ac31a008c29bdbd3b0c3548fe6d54e6dd8a9f7bb888eacddf
|
7
|
+
data.tar.gz: 28a362b536013a61c426e0ccc3e3fc6d1f9674e9e011cfe50772e22d5aab954904925fb9209714e950556aef3700ab91cee876c0b634fae207b36ed3cd201a80
|
data/README.md
CHANGED
@@ -1,5 +1,11 @@
|
|
1
1
|
# SvgOptimizer
|
2
2
|
|
3
|
+
[](https://travis-ci.org/fnando/svg_optimizer)
|
4
|
+
[](https://codeclimate.com/github/fnando/svg_optimizer)
|
5
|
+
[](https://codeclimate.com/github/fnando/svg_optimizer/coverage)
|
6
|
+
[](https://rubygems.org/gems/svg_optimizer)
|
7
|
+
[](https://rubygems.org/gems/svg_optimizer)
|
8
|
+
|
3
9
|
Some small optimizations for SVG files.
|
4
10
|
|
5
11
|
## Installation
|
@@ -30,6 +36,29 @@ SvgOptimizer.optimize_file("file.svg")
|
|
30
36
|
SvgOptimizer.optimize_file("file.svg", "optimized/file.svg")
|
31
37
|
```
|
32
38
|
|
39
|
+
You can specify the plugins you want to enable. The method signature is:
|
40
|
+
|
41
|
+
```ruby
|
42
|
+
SvgOptimizer.optimize(xml, plugins)
|
43
|
+
SvgOptimizer.optimize_file(input, output, plugins)
|
44
|
+
```
|
45
|
+
|
46
|
+
where `plugins` is an array of classes that implement the following contract:
|
47
|
+
|
48
|
+
```ruby
|
49
|
+
class MyPlugin < SvgOptimizer::Plugins::Base
|
50
|
+
def process
|
51
|
+
xml.xpath("//comment()").remove
|
52
|
+
end
|
53
|
+
end
|
54
|
+
```
|
55
|
+
|
56
|
+
The default list of plugins is stored at `SvgOptimizer::DEFAULT_PLUGINS`. To use your new plugin, just do something like this:
|
57
|
+
|
58
|
+
```ruby
|
59
|
+
SvgOptimizer.optimize(xml, SvgOptimizer::DEFAULT_PLUGINS + [MyPlugin])
|
60
|
+
```
|
61
|
+
|
33
62
|
## Contributing
|
34
63
|
|
35
64
|
1. Fork it
|
data/lib/svg_optimizer.rb
CHANGED
@@ -1,3 +1,4 @@
|
|
1
|
+
# frozen_string_literal: true
|
1
2
|
require "nokogiri"
|
2
3
|
|
3
4
|
require "svg_optimizer/version"
|
@@ -17,7 +18,7 @@ require "svg_optimizer/plugins/remove_unused_namespace"
|
|
17
18
|
require "svg_optimizer/plugins/remove_useless_stroke_and_fill"
|
18
19
|
|
19
20
|
module SvgOptimizer
|
20
|
-
|
21
|
+
DEFAULT_PLUGINS = %w[
|
21
22
|
CleanupAttribute
|
22
23
|
CleanupId
|
23
24
|
RemoveComment
|
@@ -31,20 +32,16 @@ module SvgOptimizer
|
|
31
32
|
RemoveUselessStrokeAndFill
|
32
33
|
RemoveEmptyTextNode
|
33
34
|
RemoveEmptyContainer
|
34
|
-
]
|
35
|
+
].map {|name| Plugins.const_get(name) }
|
35
36
|
|
36
|
-
def self.optimize(contents)
|
37
|
+
def self.optimize(contents, plugins = DEFAULT_PLUGINS)
|
37
38
|
xml = Nokogiri::XML(contents)
|
38
|
-
|
39
|
-
Plugins.const_get(plugin_name).new(xml).process
|
40
|
-
end
|
41
|
-
|
39
|
+
plugins.each {|plugin| plugin.new(xml).process }
|
42
40
|
xml.root.to_xml
|
43
41
|
end
|
44
42
|
|
45
|
-
def self.optimize_file(path, target = path)
|
46
|
-
contents = optimize(File.read(path))
|
47
|
-
|
43
|
+
def self.optimize_file(path, target = path, plugins = DEFAULT_PLUGINS)
|
44
|
+
contents = optimize(File.read(path), plugins)
|
48
45
|
File.open(target, "w") {|file| file << contents }
|
49
46
|
true
|
50
47
|
end
|
@@ -1,8 +1,11 @@
|
|
1
|
+
# frozen_string_literal: true
|
1
2
|
module SvgOptimizer
|
2
3
|
module Plugins
|
3
4
|
class CollapseGroups < Base
|
5
|
+
COLLAPSABLE_ATTRS = %w[transform class]
|
6
|
+
|
4
7
|
def process
|
5
|
-
xml.css(
|
8
|
+
xml.css("g:not(:empty)").each do |group|
|
6
9
|
collapse(group)
|
7
10
|
end
|
8
11
|
end
|
@@ -19,28 +22,29 @@ module SvgOptimizer
|
|
19
22
|
return unless collapse_attributes?(group, child)
|
20
23
|
|
21
24
|
group.attributes.each do |attr, val|
|
22
|
-
if
|
23
|
-
child[attr] = [val, child[attr]].compact.join(
|
25
|
+
if COLLAPSABLE_ATTRS.include?(attr)
|
26
|
+
child[attr] = [val, child[attr]].compact.join(" ")
|
24
27
|
else
|
25
28
|
child[attr] ||= val
|
26
29
|
end
|
30
|
+
|
27
31
|
group.delete(attr)
|
28
32
|
end
|
29
33
|
end
|
30
34
|
|
31
35
|
def collapse?(group)
|
32
36
|
group.attributes.empty? &&
|
33
|
-
!group.element_children.map(&:name).include?(
|
37
|
+
!group.element_children.map(&:name).include?("animate")
|
34
38
|
end
|
35
39
|
|
36
40
|
def collapse_attributes?(group, child)
|
37
|
-
return
|
38
|
-
return
|
39
|
-
return
|
40
|
-
return
|
41
|
-
|
42
|
-
|
43
|
-
|
41
|
+
return unless group.attributes.any?
|
42
|
+
return unless group.element_children.length == 1
|
43
|
+
return if child.has_attribute?("id")
|
44
|
+
return unless !group.has_attribute?("clip-path") ||
|
45
|
+
child.name == "g" &&
|
46
|
+
!group.has_attribute?("transform") &&
|
47
|
+
!child.has_attribute?("transform")
|
44
48
|
true
|
45
49
|
end
|
46
50
|
end
|
@@ -1,3 +1,4 @@
|
|
1
|
+
# frozen_string_literal: true
|
1
2
|
module SvgOptimizer
|
2
3
|
module Plugins
|
3
4
|
class RemoveEditorNamespace < Base
|
@@ -17,6 +18,19 @@ module SvgOptimizer
|
|
17
18
|
http://www.bohemiancoding.com/sketch/ns
|
18
19
|
]
|
19
20
|
|
21
|
+
def process
|
22
|
+
namespaces = xml.namespaces
|
23
|
+
remove_namespaced_attributes
|
24
|
+
xml.remove_namespaces!
|
25
|
+
|
26
|
+
namespaces.each do |name, value|
|
27
|
+
next if NAMESPACES.include?(value)
|
28
|
+
|
29
|
+
_, name = name.split(":")
|
30
|
+
xml.root.add_namespace name, value
|
31
|
+
end
|
32
|
+
end
|
33
|
+
|
20
34
|
def namespaces_to_be_removed
|
21
35
|
xml.namespaces.map do |name, value|
|
22
36
|
_, name = name.split(":")
|
@@ -34,19 +48,6 @@ module SvgOptimizer
|
|
34
48
|
end
|
35
49
|
end
|
36
50
|
end
|
37
|
-
|
38
|
-
def process
|
39
|
-
namespaces = xml.namespaces
|
40
|
-
remove_namespaced_attributes
|
41
|
-
xml.remove_namespaces!
|
42
|
-
|
43
|
-
namespaces.each do |name, value|
|
44
|
-
next if NAMESPACES.include?(value)
|
45
|
-
|
46
|
-
_, name = name.split(":")
|
47
|
-
xml.root.add_namespace name, value
|
48
|
-
end
|
49
|
-
end
|
50
51
|
end
|
51
52
|
end
|
52
53
|
end
|
@@ -1,3 +1,4 @@
|
|
1
|
+
# frozen_string_literal: true
|
1
2
|
module SvgOptimizer
|
2
3
|
module Plugins
|
3
4
|
class RemoveEmptyAttribute < Base
|
@@ -10,6 +11,7 @@ module SvgOptimizer
|
|
10
11
|
end
|
11
12
|
|
12
13
|
private
|
14
|
+
|
13
15
|
def remove_if_empty(node, attr)
|
14
16
|
node.remove_attribute(attr) if node[attr].empty?
|
15
17
|
end
|
@@ -1,3 +1,4 @@
|
|
1
|
+
# frozen_string_literal: true
|
1
2
|
module SvgOptimizer
|
2
3
|
module Plugins
|
3
4
|
class RemoveEmptyContainer < Base
|
@@ -11,6 +12,7 @@ module SvgOptimizer
|
|
11
12
|
end
|
12
13
|
|
13
14
|
private
|
15
|
+
|
14
16
|
def remove_node(node)
|
15
17
|
node.children.empty? && node.remove
|
16
18
|
remove_node(node.parent) if node.parent
|
@@ -1,12 +1,14 @@
|
|
1
|
+
# frozen_string_literal: true
|
1
2
|
module SvgOptimizer
|
2
3
|
module Plugins
|
3
4
|
class RemoveUselessStrokeAndFill < Base
|
4
|
-
SELECTOR = %w
|
5
|
-
STROKE_ATTRS = %w
|
6
|
-
FILL_ATTRS = %w
|
5
|
+
SELECTOR = %w[circle ellipse line path polygon polyline rect].join(",")
|
6
|
+
STROKE_ATTRS = %w[stroke stroke-opacity stroke-width stroke-dashoffset]
|
7
|
+
FILL_ATTRS = %w[fill-opacity fill-rule]
|
7
8
|
|
8
9
|
def process
|
9
|
-
return if xml.css(
|
10
|
+
return if xml.css("style, script").any?
|
11
|
+
|
10
12
|
xml.css(SELECTOR).each do |node|
|
11
13
|
remove_useless_attributes(node)
|
12
14
|
end
|
@@ -15,48 +17,47 @@ module SvgOptimizer
|
|
15
17
|
private
|
16
18
|
|
17
19
|
def remove_useless_attributes(node)
|
18
|
-
return if inherited_attribute(node,
|
20
|
+
return if inherited_attribute(node, "id")
|
19
21
|
remove_stroke(node) if remove_stroke?(node)
|
20
22
|
remove_fill(node) if remove_fill?(node)
|
21
23
|
end
|
22
24
|
|
23
25
|
def remove_stroke(node)
|
24
26
|
STROKE_ATTRS.each { |attr| node.delete(attr) }
|
25
|
-
node[
|
27
|
+
node["stroke"] = "none" if decline_inherited_stroke?(node)
|
26
28
|
end
|
27
29
|
|
28
30
|
def remove_fill(node)
|
29
31
|
FILL_ATTRS.each { |attr| node.delete(attr) }
|
30
|
-
node[
|
32
|
+
node["fill"] = "none" if decline_inherited_fill?(node)
|
31
33
|
end
|
32
34
|
|
33
35
|
def decline_inherited_stroke?(node)
|
34
|
-
(inherited_attribute(node.parent,
|
36
|
+
(inherited_attribute(node.parent, "stroke") || "none") != "none"
|
35
37
|
end
|
36
38
|
|
37
39
|
def decline_inherited_fill?(node)
|
38
|
-
!inherited_attribute(node,
|
40
|
+
!inherited_attribute(node, "fill") || node.has_attribute?("fill")
|
39
41
|
end
|
40
42
|
|
41
43
|
def remove_stroke?(node)
|
42
|
-
return true if (inherited_attribute(node,
|
43
|
-
return true if inherited_attribute(node,
|
44
|
-
return inherited_attribute(node,
|
44
|
+
return true if (inherited_attribute(node, "stroke") || "none") == "none"
|
45
|
+
return true if inherited_attribute(node, "stroke-opacity") == "0"
|
46
|
+
return inherited_attribute(node, "stroke-width") == "0"
|
45
47
|
end
|
46
48
|
|
47
49
|
def remove_fill?(node)
|
48
|
-
fill = inherited_attribute(node,
|
49
|
-
return true if fill ==
|
50
|
-
return true if inherited_attribute(node,
|
50
|
+
fill = inherited_attribute(node, "fill")
|
51
|
+
return true if fill == "none"
|
52
|
+
return true if inherited_attribute(node, "fill-opacity") == "0"
|
51
53
|
return !fill
|
52
54
|
end
|
53
55
|
|
54
56
|
def inherited_attribute(node, attr)
|
55
|
-
return
|
57
|
+
return if node.nil? || node.document?
|
56
58
|
return node[attr] if node.has_attribute?(attr)
|
57
59
|
inherited_attribute(node.parent, attr)
|
58
60
|
end
|
59
61
|
end
|
60
62
|
end
|
61
63
|
end
|
62
|
-
|
data/spec/spec_helper.rb
CHANGED
@@ -1,5 +1,10 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
require "codeclimate-test-reporter"
|
3
|
+
CodeClimate::TestReporter.start
|
4
|
+
|
1
5
|
require "bundler/setup"
|
2
6
|
require "svg_optimizer"
|
7
|
+
require "securerandom"
|
3
8
|
|
4
9
|
module RSpecHelpers
|
5
10
|
SVG = <<-XML
|
@@ -11,6 +16,10 @@ module RSpecHelpers
|
|
11
16
|
</svg>
|
12
17
|
XML
|
13
18
|
|
19
|
+
def build_svg(content)
|
20
|
+
SVG % content
|
21
|
+
end
|
22
|
+
|
14
23
|
def fixtures_path
|
15
24
|
File.expand_path("../fixtures", __FILE__)
|
16
25
|
end
|
@@ -24,16 +33,16 @@ module RSpecHelpers
|
|
24
33
|
|
25
34
|
def with_svg_plugin(content)
|
26
35
|
let(:file_path) { File.join(fixtures_path, content) }
|
27
|
-
let(:input_content) { File.file?(file_path) ? File.read(file_path) :
|
36
|
+
let(:input_content) { File.file?(file_path) ? File.read(file_path) : build_svg(content) }
|
28
37
|
setup_plugin_test
|
29
38
|
end
|
30
39
|
|
31
40
|
def test_with_fixture_set(name)
|
32
|
-
Dir.glob(File.join(fixtures_path, name,
|
41
|
+
Dir.glob(File.join(fixtures_path, name, "*.svg")).each do |fixture|
|
33
42
|
context "output from fixture: #{fixture}" do
|
34
43
|
let(:fixture_content) { File.read(fixture) }
|
35
|
-
let(:input_content) { fixture_content.split(
|
36
|
-
let(:expected_content) { fixture_content.split(
|
44
|
+
let(:input_content) { fixture_content.split("@@@")[0].strip }
|
45
|
+
let(:expected_content) { fixture_content.split("@@@")[1].strip }
|
37
46
|
let(:expected) { Nokogiri::XML(expected_content, &:noblanks).root.to_xml }
|
38
47
|
subject { xml.root.to_xml }
|
39
48
|
setup_plugin_test
|
@@ -46,8 +55,6 @@ module RSpecHelpers
|
|
46
55
|
end
|
47
56
|
|
48
57
|
RSpec.configure do |config|
|
49
|
-
config.run_all_when_everything_filtered = true
|
50
|
-
config.filter_run :focus
|
51
58
|
config.order = "random"
|
52
59
|
config.extend RSpecHelpers
|
53
60
|
config.include RSpecHelpers
|
@@ -0,0 +1,54 @@
|
|
1
|
+
require "spec_helper"
|
2
|
+
|
3
|
+
describe SvgOptimizer do
|
4
|
+
context "file saving" do
|
5
|
+
let(:input) { "/tmp/#{SecureRandom.uuid}.svg" }
|
6
|
+
let(:output) { "/tmp/#{SecureRandom.uuid}.svg" }
|
7
|
+
let(:raw_svg) { build_svg("<!-- foo --><text>SVG</text>") }
|
8
|
+
let(:output_svg) { SvgOptimizer.optimize(raw_svg) }
|
9
|
+
|
10
|
+
before do
|
11
|
+
File.open(input, "w") {|file| file << raw_svg }
|
12
|
+
end
|
13
|
+
|
14
|
+
it "overrides existing file with optimized version" do
|
15
|
+
SvgOptimizer.optimize_file(input)
|
16
|
+
expect(File.read(input)).to eq(output_svg)
|
17
|
+
end
|
18
|
+
|
19
|
+
it "saves new file file with optimized version" do
|
20
|
+
SvgOptimizer.optimize_file(input, output)
|
21
|
+
expect(File.read(input)).to eq(raw_svg)
|
22
|
+
expect(File.read(output)).to eq(output_svg)
|
23
|
+
end
|
24
|
+
end
|
25
|
+
|
26
|
+
context "plugin selection" do
|
27
|
+
it "applies all plugins" do
|
28
|
+
svg = build_svg <<-SVG
|
29
|
+
<!-- foo -->
|
30
|
+
<g></g>
|
31
|
+
<!-- bar -->
|
32
|
+
SVG
|
33
|
+
|
34
|
+
xml = Nokogiri::XML(SvgOptimizer.optimize(svg))
|
35
|
+
|
36
|
+
expect(xml.xpath("//comment()")).to be_empty
|
37
|
+
expect(xml.css("g")).to be_empty
|
38
|
+
end
|
39
|
+
|
40
|
+
it "applies just specified plugins" do
|
41
|
+
plugins = [SvgOptimizer::Plugins::RemoveComment]
|
42
|
+
svg = build_svg <<-SVG
|
43
|
+
<!-- foo -->
|
44
|
+
<g></g>
|
45
|
+
<!-- bar -->
|
46
|
+
SVG
|
47
|
+
|
48
|
+
xml = Nokogiri::XML(SvgOptimizer.optimize(svg, plugins))
|
49
|
+
|
50
|
+
expect(xml.xpath("//comment()")).to be_empty
|
51
|
+
expect(xml.css("g").length).to eql(1)
|
52
|
+
end
|
53
|
+
end
|
54
|
+
end
|
metadata
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: svg_optimizer
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.0
|
4
|
+
version: 0.1.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Nando Vieira
|
@@ -80,6 +80,20 @@ dependencies:
|
|
80
80
|
- - ">="
|
81
81
|
- !ruby/object:Gem::Version
|
82
82
|
version: '0'
|
83
|
+
- !ruby/object:Gem::Dependency
|
84
|
+
name: codeclimate-test-reporter
|
85
|
+
requirement: !ruby/object:Gem::Requirement
|
86
|
+
requirements:
|
87
|
+
- - ">="
|
88
|
+
- !ruby/object:Gem::Version
|
89
|
+
version: '0'
|
90
|
+
type: :development
|
91
|
+
prerelease: false
|
92
|
+
version_requirements: !ruby/object:Gem::Requirement
|
93
|
+
requirements:
|
94
|
+
- - ">="
|
95
|
+
- !ruby/object:Gem::Version
|
96
|
+
version: '0'
|
83
97
|
description: SVG optimization based on Node's SVGO
|
84
98
|
email:
|
85
99
|
- fnando.vieira@gmail.com
|
@@ -138,6 +152,7 @@ files:
|
|
138
152
|
- spec/svg_optimizer/plugins/remove_raster_image_spec.rb
|
139
153
|
- spec/svg_optimizer/plugins/remove_unused_namespace_spec.rb
|
140
154
|
- spec/svg_optimizer/plugins/remove_useless_stroke_and_fill_spec.rb
|
155
|
+
- spec/svg_optimizer/svg_optimizer_spec.rb
|
141
156
|
homepage: https://github.com/fnando/svg_optimizer
|
142
157
|
licenses:
|
143
158
|
- MIT
|
@@ -196,3 +211,4 @@ test_files:
|
|
196
211
|
- spec/svg_optimizer/plugins/remove_raster_image_spec.rb
|
197
212
|
- spec/svg_optimizer/plugins/remove_unused_namespace_spec.rb
|
198
213
|
- spec/svg_optimizer/plugins/remove_useless_stroke_and_fill_spec.rb
|
214
|
+
- spec/svg_optimizer/svg_optimizer_spec.rb
|