require 'open3'
module Esvg
class Symbol
attr_reader :name, :id, :path, :content, :optimized, :size, :group, :mtime, :defs
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
# Ensure that cache optimization matches current optimization settings
# If config has changed name, reset optimized build (name gets baked in)
if @mtime != time || @svgo_optimized != svgo? || name != file_name
@optimized = nil
@optimized_at = nil
end
@group = dir_key
@name = file_name
@id = file_id file_key
if @mtime != time
@content = prep_defs pre_optimize File.read(@path)
@mtime = time
@size = dimensions
end
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,
defs: @defs,
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
svg_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].nil? && options[:height].nil? && !options[:scale]
svg_attr[:width] = width
svg_attr[:height] = height
else
# Add sizes (nil options will be stripped)
svg_attr[:width] = options[:width]
svg_attr[:height] = options[:height]
end
use_attr = {
height: options[:height],
width: options[:width],
scale: options[:scale],
}
%Q{}
end
def use_tag(options={})
options["xlink:href"] = "##{@id}"
# If user doesn't pass a size or set scale: true
if options[:width].nil? && options[:height].nil? && options[:scale].nil?
options[:width] ||= width
options[:height] ||= height
end
options.delete(:scale)
%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
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(/ blocks for IDs
# If urls(#id) are used, ensure these IDs are unique to this file
# Only replace IDs if urls exist to avoid replacing defs
# used in other svg files
#
def prep_defs(svg)
# should be moved to the beginning of the SVG file for braod browser support. Ahem, Firefox ಠ_ಠ
# When symbols are reassembled, @defs will be added back
if @defs = svg.scan(/(.+)<\/defs>/m).flatten[0]
svg.sub!("#{@defs}", '')
@defs.gsub!(/(\n|\s{2,})/,'')
@defs.scan(/id="(.+?)"/).flatten.uniq.each_with_index do |id, index|
# If there are urls matching def ids
if svg.match(/url\(##{id}\)/)
new_id = "def-#{@id}-#{index}" # Generate a unique id
@defs.gsub!(/id="#{id}"/, %Q{id="#{new_id}"}) # Replace the def ids
svg.gsub!(/url\(##{id}\)/, "url(##{new_id})") # Replace url references to these old def ids
end
end
end
svg
end
end
end