require 'open3'
module Esvg
class Symbol
attr_reader :name, :id, :path, :content, :optimized, :size, :group, :mtime
include Esvg::Utils
def initialize(path, config={})
@config = config
@path = path
load_data
read
end
def read
return if !File.exist?(@path)
time = last_modified
if @mtime != time
@mtime = time
@content = pre_optimize File.read(@path)
@size = dimensions
@optimized = nil
@optimized_at = nil
end
# Ensure that cache optimization matches current optimization settings
# If config has changed name, reset optimized build (name gets baked in)
if @svgo_optimized != svgo? || name != file_name
@optimized = nil
@optimized_at = nil
end
@group = dir_key
@name = file_name
@id = file_id file_key
self
end
def width
@size[:width]
end
def height
@size[:height]
end
def data
{
path: @path,
id: @id,
name: @name,
group: @group,
mtime: @mtime,
size: @size,
content: @content,
optimized: @optimized,
optimized_at: @optimized_at,
svgo_optimized: svgo? && @svgo_optimized
}
end
def attr
{ id: @id, 'data-name': @name }.merge @size
end
def use(options={})
if options[:color]
options[:style] ||= ''
options[:style] += "color:#{options[:color]};#{options[:style]}"
end
use_attr = {
class: [@config[:class], @config[:prefix]+"-"+@name, options[:class]].compact.join(' '),
viewBox: @size[:viewBox],
style: options[:style],
fill: options[:fill],
role: 'img'
}
# If user doesn't pass a size or set scale: true
if !(options[:width] || options[:height] || options[:scale])
# default to svg dimensions
use_attr[:width] = @size[:width]
use_attr[:height] = @size[:height]
else
# Add sizes (nil options will be stripped)
use_attr[:width] = options[:width]
use_attr[:height] = options[:height]
end
%Q{}
end
def use_tag(options={})
options["xlink:href"] = "##{@id}"
options[:width] ||= width
options[:height] ||= height
%Q{}
end
def title(options)
if options[:title]
"
#{options[:title]}"
else
''
end
end
def desc(options)
if options[:desc]
"#{options[:desc]}"
else
''
end
end
def svgo?
@config[:optimize] && !!Esvg.node_module('svgo')
end
def optimize
# Only optimize again if the file has changed
return @optimized if @optimized && @optimized_at > @mtime
@optimized = @content
sub_def_ids
if svgo?
response = Open3.capture3(%Q{#{Esvg.node_module('svgo')} --disable=removeUselessDefs -s '#{@optimized}' -o -})
if !response[0].empty? && response[2].success?
@optimized = response[0]
@svgo_optimized = true
end
end
post_optimize
@optimized_at = Time.now.to_i
@optimized
end
private
def load_data
if @config[:cache]
@config.delete(:cache).each do |name, value|
instance_variable_set("@#{name}", value)
end
end
end
def last_modified
File.mtime(@path).to_i
end
def file_id(name)
dasherize "#{@config[:prefix]}-#{name}"
end
def local_path
@local_path ||= sub_path(@config[:source], @path)
end
def file_name
dasherize flatten_path.sub('.svg','')
end
def file_key
dasherize local_path.sub('.svg','')
end
def dir_key
dir = File.dirname(flatten_path)
# Flattened paths which should be treated as assets will use '_' as their dir key
# - flatten: _foo - _foo/icon.svg will have a dirkey of _
# - filename: _icons - treats all root or flattened files as assets
if dir == '.' && ( local_path.start_with?('_') || @config[:filename].start_with?('_') )
'_'
else
dir
end
end
def flatten_path
@flattened_path ||= local_path.sub(Regexp.new(@config[:flatten]), '')
end
def name_key(key)
if key == '_' # Root level asset file
"_#{@config[:filename]}".sub(/_+/, '_')
elsif key == '.' # Root level build file
@config[:filename]
else
"#{key}"
end
end
def dimensions
if viewbox = @content.scan(/ classes with ids (classes are generated in sub_def_ids)
.gsub(/\w+=""/,'') # Remove empty attributes
end
def set_attributes
attr.keys.each do |key|
@optimized.sub!(/ #{key}=".+?"/,'')
end
@optimized.sub(/