class Esvg::Symbol
Attributes
content[R]
defs[R]
group[R]
id[R]
mtime[R]
name[R]
optimized[R]
path[R]
size[R]
Public Class Methods
new(path, parent)
click to toggle source
# File lib/esvg/symbol.rb, line 9 def initialize(path, parent) @parent = parent @path = path @last_checked = 0 load_data read end
Public Instance Methods
attr()
click to toggle source
# File lib/esvg/symbol.rb, line 110 def attr { id: @id, 'data-name' => @name }.merge @size end
changed?()
click to toggle source
# File lib/esvg/symbol.rb, line 209 def changed? last_modified != mtime end
config()
click to toggle source
# File lib/esvg/symbol.rb, line 17 def config @parent.config end
data()
click to toggle source
# File lib/esvg/symbol.rb, line 95 def data { path: @path, name: @name, group: @group, mtime: @mtime, size: @size, content: @content, defs: @defs, optimized: @optimized, optimized_at: @optimized_at, svgo_optimized: optimize? && @svgo_optimized } end
dir()
click to toggle source
# File lib/esvg/symbol.rb, line 25 def dir @group end
exist?()
click to toggle source
# File lib/esvg/symbol.rb, line 21 def exist? File.exist?(@path) end
height()
click to toggle source
# File lib/esvg/symbol.rb, line 56 def height @size[:height] end
optimize()
click to toggle source
# File lib/esvg/symbol.rb, line 179 def optimize read if changed? # Only optimize again if the file has changed if @optimized && @optimized_at && @optimized_at > @mtime return @optimized end # Only optimize if SVGO is installed if optimize? puts "Optimizing #{name}.svg" if config[:print] response = Open3.capture3(%Q{#{Esvg.node_module('svgo')} --disable=removeUselessDefs -s '#{@content}' -o -}) if !response[0].empty? && response[2].success? @optimized = response[0] @svgo_optimized = true end post_optimize @optimized_at = Time.now.to_i @optimized end end
optimize?()
click to toggle source
Only optimize if
-
Configuration asks for it
-
SVGO is present
-
If Rails is present
# File lib/esvg/symbol.rb, line 175 def optimize? config[:optimize] && !!Esvg.node_module('svgo') && config[:env] == 'production' end
read()
click to toggle source
# File lib/esvg/symbol.rb, line 29 def read return if !exist? # Ensure that cache optimization matches current optimization settings # If config has changed name, reset optimized build (name gets baked in) if changed? || @svgo_optimized != optimize? || name != file_name @optimized = nil @optimized_at = nil end @group = dir_key @name = file_name @id = file_id file_key if changed? @content = prep_defs pre_optimize File.read(@path) @mtime = last_modified @size = dimensions end self end
scale( a )
click to toggle source
# File lib/esvg/symbol.rb, line 79 def scale( a ) # Width was set, determine scaled height if a[:width] a[:height] ||= scale_height( a[:width] ) # Height was set, determine scaled width elsif a[:height] a[:width] ||= scale_width( a[:height] ) # Nothing was set, default to dimensions else a[:width] = width a[:height] = height end a end
scale_height( w )
click to toggle source
Scale height based on propotion to width
# File lib/esvg/symbol.rb, line 67 def scale_height( w ) s = split_unit( w ) "#{( s[:size] / width * height ).round(2)}#{s[:unit]}" end
scale_width( h )
click to toggle source
Scale width based on propotion to height
# File lib/esvg/symbol.rb, line 61 def scale_width( h ) s = split_unit( h ) "#{( s[:size] / height * width ).round(2)}#{s[:unit]}" end
split_unit( size )
click to toggle source
Separate size and unit for easier math. Returns: { size: 10, unit: 'px' }
# File lib/esvg/symbol.rb, line 74 def split_unit( size ) m = size.to_s.match(/(\d+)\s*(\D*)/) { size: m[1].to_f, unit: m[2] } end
symbol()
click to toggle source
# File lib/esvg/symbol.rb, line 205 def symbol symbolize( optimize || @content ) end
use(options={})
click to toggle source
# File lib/esvg/symbol.rb, line 114 def use(options={}) # If preset key is set, merge presets from configuration if options[:preset] && preset = config[:presets][ options.delete(:preset).to_sym ] options = options.merge( preset ) end # If size key is set, merge size class from configuration if options[:size] && size_class = config[:sizes][ options.delete(:size).to_sym ] options = options.merge( size_class ) end options.delete(:fallback) content = options.delete(:content) || '' if desc = options.delete(:desc) content = "<desc>#{desc}</desc>#{content}" end if title = options.delete(:title) content = "<title>#{title}</title>#{content}" end use_attr = options.delete(:use) || {} svg_attr = { class: [config[:class], config[:prefix]+"-"+@name, options.delete(:class)].compact.join(' '), viewBox: @size[:viewBox], role: 'img' }.merge(options) if svg_attr[:scale] # User doesn't want dimensions to be set svg_attr.delete(:scale) else # Scale dimensions based on attributes svg_attr = scale( svg_attr ) end %Q{<svg #{attributes(svg_attr)}>#{use_tag(use_attr)}#{content}</svg>} end
use_tag(options={})
click to toggle source
# File lib/esvg/symbol.rb, line 155 def use_tag(options={}) options["xlink:href"] = "##{@id}" if options[:scale] && config[:scale] # User doesn't want dimensions to be set options.delete(:scale) else # Scale dimensions based on attributes options = scale( options ) end options.delete(:scale) %Q{<use #{attributes(options)}></use>} end
width()
click to toggle source
# File lib/esvg/symbol.rb, line 52 def width @size[:width] end
Private Instance Methods
dimensions()
click to toggle source
# File lib/esvg/symbol.rb, line 275 def dimensions if viewbox = @content.scan(/<svg.+(viewBox=["'](.+?)["'])/).flatten.last coords = viewbox.split(' ') { viewBox: viewbox, width: (coords[2].to_i - coords[0].to_i).abs, height: (coords[3].to_i - coords[1].to_i).abs } else # In case a viewbox hasn't been set, derive one. if height = @content.scan(/<svg.+(height=["'](.+?)["'])/).flatten.last && width = @content.scan(/<svg.+(width=["'](.+?)["'])/).flatten.last { viewBox: "0 0 #{width} #{height}", width: width.to_i, height: height.to_i } else {} end end end
dir_key()
click to toggle source
# File lib/esvg/symbol.rb, line 248 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
file_id(name)
click to toggle source
# File lib/esvg/symbol.rb, line 232 def file_id(name) dasherize "#{config[:prefix]}-#{name}" end
file_key()
click to toggle source
# File lib/esvg/symbol.rb, line 244 def file_key dasherize local_path.sub('.svg','') end
file_name()
click to toggle source
# File lib/esvg/symbol.rb, line 240 def file_name dasherize flatten_path.sub('.svg','') end
flatten_path()
click to toggle source
# File lib/esvg/symbol.rb, line 261 def flatten_path @flattened_path ||= local_path.sub(Regexp.new(config[:flatten_dir]), '') end
last_modified()
click to toggle source
# File lib/esvg/symbol.rb, line 223 def last_modified if Time.now.to_i - @last_checked < config[:throttle_read] @last_modified else @last_checked = Time.now.to_i @last_modified = File.mtime(@path).to_i end end
load_data()
click to toggle source
# File lib/esvg/symbol.rb, line 215 def load_data if config[:cache] config.delete(:cache).each do |name, value| instance_variable_set("@#{name}", value) end end end
local_path()
click to toggle source
# File lib/esvg/symbol.rb, line 236 def local_path @local_path ||= sub_path(config[:source], @path) end
name_key(key)
click to toggle source
# File lib/esvg/symbol.rb, line 265 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
post_optimize()
click to toggle source
# File lib/esvg/symbol.rb, line 316 def post_optimize @optimized.gsub!(/\w+=""/,'') # Remove empty attributes end
pre_optimize(svg)
click to toggle source
# File lib/esvg/symbol.rb, line 299 def pre_optimize(svg) # Generate a regex of attributes to be removed att = Regexp.new %w(xmlns xmlns:xlink xml:space version).map { |m| "#{m}=\".+?\"" }.join('|') svg.strip .gsub(att, '') # Remove unwanted attributes .sub(/.+?<svg/,'<svg') # Get rid of doctypes and comments .gsub(/<!--(.+?)-->/m, '') # Remove XML comments .gsub(/style="([^"]*?)fill:(.+?);/m, 'fill="\2" style="\1') # Make fill a property instead of a style .gsub(/style="([^"]*?)fill-opacity:(.+?);/m, 'fill-opacity="\2" style="\1') # Move fill-opacity a property instead of a style .gsub(/\n/m, ' ') # Remove endlines .gsub(/\s{2,}/, ' ') # Remove whitespace .gsub(/>\s+</, '><') # Remove whitespace between tags .gsub(/\s?fill="(#0{3,6}|black|none|rgba?\(0,0,0\))"/,'') # Strip black fill end
prep_defs(svg)
click to toggle source
Scans <def> 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
# File lib/esvg/symbol.rb, line 340 def prep_defs(svg) # <defs> 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>(.+)<\/defs>/m).flatten[0] svg.sub!("<defs>#{@defs}</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
strip_attributes( str )
click to toggle source
# File lib/esvg/symbol.rb, line 327 def strip_attributes( str ) attr.keys.each do |key| str.sub!(/ #{key}=".+?"/,'') end str end
symbolize( str )
click to toggle source
# File lib/esvg/symbol.rb, line 320 def symbolize( str ) strip_attributes( str ) .gsub(/<\/svg/,'</symbol') # Replace svgs with symbols .gsub(/\w+=""/,'') # Remove empty attributes .sub(/<svg/, "<symbol #{attributes(attr)}") end