From 776629734175502253c39a383c51cc39ae0049a1 Mon Sep 17 00:00:00 2001 From: Martin Sourada Date: Mar 10 2013 11:09:26 +0000 Subject: [icon-artist] Add initial support for multiple icons in one source. Bump version to 0.1.95 as this is API/ABI change: * IconArtist::Icon.export now returns array. - typical expamle: [{"name" => "go-next", "context" => "actions", "sizes" => ["16x16","scalable"]}, {"name" => "go-previous", "context" => "actions", "sizes" => ["16x16","scalable"]}] * IconArtist::Icon.symlinks are hashed by icon name. - typical example: {"go-next" => {:defalut => ["gtk-next"], :optional => []}} * IconArtist::THEME_SIZES expanded to include "64x64" and "128x128" --- diff --git a/sources/icon-artist/data/buildsys/theme-create.rb.in b/sources/icon-artist/data/buildsys/theme-create.rb.in index 995201f..ff2f5df 100644 --- a/sources/icon-artist/data/buildsys/theme-create.rb.in +++ b/sources/icon-artist/data/buildsys/theme-create.rb.in @@ -6,8 +6,9 @@ icons = [] ARGV.each do |file| icon = Icon.new file + icon.separate_icons "../separated" icon_params = icon.export "../build" - icons += [icon_params] + icons += icon_params end theme = IconTheme.new("../build", {:create => true, diff --git a/sources/icon-artist/lib/icon-artist.rb b/sources/icon-artist/lib/icon-artist.rb index 27c07ec..896e8c2 100644 --- a/sources/icon-artist/lib/icon-artist.rb +++ b/sources/icon-artist/lib/icon-artist.rb @@ -1,17 +1,17 @@ # icon-artist.rb: Supporting library for icon artists # -# Copyright (C) 2008, 2009 Martin Sourada -# +# Copyright (C) 2008--2013 Martin Sourada +# # This script is free software; you can redistribute it and/or # modify it under the terms of the GNU Lesser General Public # License as published by the Free Software Foundation; either # version 2.1 of the License, or (at your option) any later version. -# +# # This script is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU # Lesser General Public License for more details. -# +# # You should have received a copy of the GNU Lesser General Public # License along with this script; if not, write to the Free Software # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA @@ -23,5 +23,5 @@ require "icon-artist/buildsystem" require "icon-artist/gitrepo" module IconArtist - VERSION = '0.1.94' + VERSION = '0.1.95' end diff --git a/sources/icon-artist/lib/icon-artist/buildsystem.rb b/sources/icon-artist/lib/icon-artist/buildsystem.rb index 2ca3011..11b627c 100644 --- a/sources/icon-artist/lib/icon-artist/buildsystem.rb +++ b/sources/icon-artist/lib/icon-artist/buildsystem.rb @@ -1,18 +1,18 @@ # buildsystem.rb: Supporting library for icon artists, build system related # stuff like adding icons to buildsystem or initializing a new one # -# Copyright (C) 2008, 2009 Martin Sourada -# +# Copyright (C) 2008--2013 Martin Sourada +# # This script is free software; you can redistribute it and/or # modify it under the terms of the GNU Lesser General Public # License as published by the Free Software Foundation; either # version 2.1 of the License, or (at your option) any later version. -# +# # This script is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU # Lesser General Public License for more details. -# +# # You should have received a copy of the GNU Lesser General Public # License along with this script; if not, write to the Free Software # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA @@ -27,11 +27,11 @@ include FileUtils module IconArtist class BuildSystem private - + def initialize_build_sys puts "Creating new build sys in #{@dir}." FileUtils.mkdir_p(@dir) unless File.exists?(@dir) - + fin = IO.readlines("#{ARTIST_SHAREDIR}/buildsys/configure.src.in") fout = File.new("#{@dir}/configure.ac", "w+") fin.each do |line| @@ -77,7 +77,7 @@ module IconArtist if !File.exist? "#{@dir}/legacy-icon-mapping.xml" FileUtils.cp("#{ARTIST_SHAREDIR}/buildsys/legacy-icon-mapping.xml", "#{@dir}/legacy-icon-mapping.xml") end - + ['NEWS', 'README', 'ChangeLog', 'COPYING', 'AUTHORS'].each do |file| unless File.exist?("#{@dir}/#{file}") FileUtils.touch("#{@dir}/#{file}") @@ -85,7 +85,7 @@ module IconArtist end end end - + def check_for_theme if !File.exist?("#{@dir}/index.theme") if !File.exist?("#{@dir}/src/folder.svg") @@ -94,37 +94,45 @@ module IconArtist end end end - - def create_symlinks(icon) + + def create_symlinks(icons) # add symlinks symlinks = @icon.symlinks - icon['sizes'].each do |size| - if size == 'scalable' - ext = '.svg' - else - ext = '.png' - end - symlinks[:default].each do |symlink| - FileUtils.ln_sf("#{icon['name']}#{ext}", - "#{@dir}/#{size}/#{icon['context']}/#{symlink}#{ext}") - end - symlinks[:optional].each do |symlink| - FileUtils.ln_sf("#{icon['name']}#{ext}", - "#{@dir}/#{size}/#{icon['context']}/#{symlink}#{ext}") + icons.each do |icon| + icon['sizes'].each do |size| + if size == 'scalable' + ext = '.svg' + else + ext = '.png' + end + symlinks[icon['name']][:default].each do |symlink| + FileUtils.ln_sf("#{icon['name']}#{ext}", + "#{@dir}/#{size}/#{icon['context']}/#{symlink}#{ext}") + end + symlinks[icon['name']][:optional].each do |symlink| + FileUtils.ln_sf("#{icon['name']}#{ext}", + "#{@dir}/#{size}/#{icon['context']}/#{symlink}#{ext}") + end end end end - + def add_icon_file_to_buildsys(lines, ext) - icons_data = false icons = [] - is_in = false + icons_data = false + is_in = {} + @icon.icons.each do |iconname| + is_in[iconname] = false + end + lines.each do |line| if line.include?("icons_DATA") icons_data = true elsif icons_data - if line.include?("#{@icon.iconname}#{ext}") - is_in = true + @icon.icons.each do |iconname| + if line.include?("#{iconname}#{ext}") + is_in[iconname] = true + end end if !line.include?("\\") icon = line.rstrip.delete "\\" @@ -139,19 +147,21 @@ module IconArtist end end - if !is_in - icons = icons + ["\t#{@icon.iconname}#{ext}"] + @icon.icons.each do |iconname| + if !is_in[iconname] + icons += ["\t#{iconname}#{ext}"] + end end icons.delete("") - return icons.sort + icons.sort! + return icons.uniq end - + def add_symlinks_to_buildsys(lines, ext) - symlinks = @icon.symlinks[:optional] symlinks_new = [] - install_local = false; + install_local = false; lines.each do |line| if line.include?("install-data-local") @@ -161,9 +171,12 @@ module IconArtist install_local = false else symlink = line - symlinks.each do |link| - if line.include?("#{link}#{ext}") - symlinks = symlinks - [link] + @icon.icons.each do |icon| + symlinks = @icon.symlinks[icon] + symlinks[:optional].each do |link| + if line.include?("#{link}#{ext}") + symlinks[:optional] -= [link] + end end end symlinks_new = symlinks_new + [symlink] @@ -171,17 +184,25 @@ module IconArtist end end - symlinks.each do |symlink| - symlinks_new = symlinks_new + ["\t(cd $(DESTDIR)$(themedir)/$(size)/$(context) && $(LN_S) #{@icon.iconname}#{ext} #{symlink}#{ext})"] + @icon.icons.each do |icon| + symlinks = @icon.symlinks[icon][:optional] + symlinks.each do |symlink| + symlinks_new = symlinks_new + ["\t(cd $(DESTDIR)$(themedir)/$(size)/$(context) && $(LN_S) #{icon}#{ext} #{symlink}#{ext})"] + end end - - return symlinks_new + + return symlinks_new.uniq end - + def add_symlinks - symlinks = @icon.symlinks[:optional] - symlinks_old = [] - if symlinks != [] + empty = true + @icon.symlinks.each do |icon, symlinks| + empty = false unless symlinks[:optional].empty? + end + + #symlinks = @icon.symlinks[:optional] + #symlinks_old = [] + if !empty symlinks_xml = "#{@dir}/legacy-icon-mapping.xml" if !File.exist?(symlinks_xml) symlinks_xml = "#{ARTIST_SHAREDIR}/buildsys/legacy-icon-mapping.xml.in" @@ -193,35 +214,38 @@ module IconArtist end xml = REXML::Document.new(File.new("#{symlinks_xml}", 'r')) context = xml.root.elements["//context[@dir='#{@icon.context}']"] - if context != nil - icon = context.elements["icon[@name='#{@icon.iconname}']"] - if icon == nil - icon = REXML::Element.new 'icon' - icon.add_attribute('name', "#{@icon.iconname}") - context.add_element icon - end - icon.elements.each do |symlink| - symlinks_old += [symlink.text] - end - - symlinks -= symlinks_old - symlinks.each do |symlink| - link = REXML::Element.new "link" - link.add_text symlink - icon.add_element link + if (context != nil) + @icon.symlinks.each do |iconname, symlinks| + symlinks_old = [] + icon = context.elements["icon[@name='#{iconname}']"] + if icon == nil + icon = REXML::Element.new 'icon' + icon.add_attribute('name', "#{iconname}") + context.add_element icon if symlinks[:optional] && symlinks[:optional].length > 0 + end + icon.elements.each do |symlink| + symlinks_old += [symlink.text] + end + + symlinks[:optional] -= symlinks_old + symlinks[:optional].each do |symlink| + link = REXML::Element.new "link" + link.add_text symlink + icon.add_element link + end end end - + f = File.open("#{@dir}/legacy-icon-mapping.xml", "w") xml.write(f, -1, false) f.close - + if !File.exist? "#{@dir}/legacy-icon-mapping.dtd" FileUtils.cp("#{ARTIST_SHAREDIR}/buildsys/legacy-icon-mapping.dtd", "#{@dir}/legacy-icon-mapping.dtd") end end end - + def parse_icons_data(lines, ext, f) icons = add_icon_file_to_buildsys(lines, ext) f.puts "icons_DATA = \\" @@ -233,7 +257,7 @@ module IconArtist end end end - + def parse_symlinks(lines, ext, f) symlinks_add = add_symlinks_to_buildsys(lines, ext) f.puts "install-data-local: install-iconsDATA" @@ -241,24 +265,25 @@ module IconArtist f.puts "#{symlink}" end end - - def add_icon_to_buildsys(icon) + + def add_icon_to_buildsys(icons) + icon = icons[0] icon['sizes'].each do |size| makefile = "#{@dir}/#{size}/#{icon['context']}/Makefile.am" - + if size == 'scalable' ext = '.svg' else ext = '.png' end - + if File.exist?(makefile) lines = IO.readlines("#{makefile}") else lines = IO.readlines("#{ARTIST_SHAREDIR}/buildsys/Makefile.context.in") end f = File.new(makefile, "w") - + icons_data = false extra_dist = false install_data_local = false @@ -285,42 +310,49 @@ module IconArtist f.puts "#{line}" end end - + if !icons_data parse_icons_data(lines, ext, f) f.puts "" end - + if !extra_dist f.puts "EXTRA_DIST = $(icons_DATA)" f.puts "" end - - if !install_data_local & !@icon.symlinks[:optional].empty? + + empty = true + @icon.icons.each do |icon| + empty = false unless @icon.symlinks[icon][:optional].empty? + end + + if !install_data_local & !empty parse_symlinks(lines, ext, f) f.puts "" end - + f.close end - + end - + def add_icon_none - icon = @icon.export(@dir) - create_symlinks(icon) - + @icon.separate_icons("./separated") + icons = @icon.export(@dir) + create_symlinks(icons) + if File.exist?("#{@source_dir}/#{@icon.iconname}.svg") FileUtils.rm("#{@source_dir}/#{@icon.iconname}.svg") end FileUtils.mkdir_p("#{@source_dir}/one-canvas") unless File.exist?("#{@source_dir}/one-canvas") FileUtils.cp("#{@icon.filename}", "#{@source_dir}/one-canvas/#{@icon.iconname}.svg") end - + def add_icon_binary - icon = @icon.export(@dir) - add_icon_to_buildsys(icon) + @icon.separate_icons("./separated") + icons = @icon.export(@dir) + add_icon_to_buildsys(icons) if File.exist?("#{@source_dir}/#{@icon.iconname}.svg") FileUtils.rm("#{@source_dir}/#{@icon.iconname}.svg") @@ -328,14 +360,14 @@ module IconArtist FileUtils.mkdir_p("#{@source_dir}/one-canvas") unless File.exist?("#{@source_dir}/one-canvas") FileUtils.cp("#{@icon.filename}", "#{@source_dir}/one-canvas/#{@icon.iconname}.svg") end - + def add_icon_source FileUtils.cp("#{@icon.filename}", "#{@dir}/src/#{@icon.iconname}.svg") add_symlinks end - - public - + + public + # Open or create buildsys in target directory # # target_dir:: @@ -366,7 +398,7 @@ module IconArtist @theme[:dir] = opts[:theme_dir] if opts[:theme_dir] @theme[:bugzilla] = opts[:theme_bugzilla] if opts[:theme_bugzilla] @theme[:inherits] = opts[:theme_inherits] if opts[:theme_inherits] - + if opts[:create] @buildtype = :source initialize_build_sys @@ -386,17 +418,22 @@ module IconArtist end end end - - # Add new icon and symlinks to it + + # Add new icons and symlinks to them # # filename:: # file in which the icon is stored # # symlinks:: - # non-standard symlinks to be added for the icon - def add_icon(filename,symlinks=[]) + # non-standard symlinks to be added for the icons, hash + # {"iconname" => [symlinks]} + def add_icons(filename,symlinks={}) @icon = Icon.new(filename) - @icon.add_symlinks(symlinks) + if symlinks + symlinks.each do |icon, links| + @icon.add_symlinks(links, icon) + end + end case @buildtype when :none add_icon_none @@ -407,5 +444,7 @@ module IconArtist end @icon.iconname end + + end end diff --git a/sources/icon-artist/lib/icon-artist/gitrepo.rb b/sources/icon-artist/lib/icon-artist/gitrepo.rb index 6440f2b..155bf57 100644 --- a/sources/icon-artist/lib/icon-artist/gitrepo.rb +++ b/sources/icon-artist/lib/icon-artist/gitrepo.rb @@ -1,17 +1,17 @@ # gitrepo.rb: Supporting library for icon artists, simplifying working with git # -# Copyright (C) 2008, 2009 Martin Sourada -# +# Copyright (C) 2008--2013 Martin Sourada +# # This script is free software; you can redistribute it and/or # modify it under the terms of the GNU Lesser General Public # License as published by the Free Software Foundation; either # version 2.1 of the License, or (at your option) any later version. -# +# # This script is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU # Lesser General Public License for more details. -# +# # You should have received a copy of the GNU Lesser General Public # License along with this script; if not, write to the Free Software # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA @@ -62,7 +62,7 @@ module IconArtist checkout(old_current) end end - + # Branches hash, contains :remote and :local arrays def branches @branches @@ -126,7 +126,7 @@ module IconArtist @branches[:local] += [b.name] end end - + # Push changes to remote repository # # remote:: diff --git a/sources/icon-artist/lib/icon-artist/icon.rb b/sources/icon-artist/lib/icon-artist/icon.rb index 3583878..90a9e7c 100644 --- a/sources/icon-artist/lib/icon-artist/icon.rb +++ b/sources/icon-artist/lib/icon-artist/icon.rb @@ -1,18 +1,18 @@ # icon.rb: Supporting library for icon artists, icon generation and # check utilities # -# Copyright (C) 2008, 2009 Martin Sourada -# +# Copyright (C) 2008--2013 Martin Sourada +# # This script is free software; you can redistribute it and/or # modify it under the terms of the GNU Lesser General Public # License as published by the Free Software Foundation; either # version 2.1 of the License, or (at your option) any later version. -# +# # This script is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU # Lesser General Public License for more details. -# +# # You should have received a copy of the GNU Lesser General Public # License along with this script; if not, write to the Free Software # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA @@ -26,15 +26,15 @@ include FileUtils module IconArtist INKSCAPE = '/usr/bin/inkscape' - + class Icon - + private def Icon.get_templatedir config = File.expand_path("~/.config/user-dirs.dirs") dir = "~/Templates" - if File.exist?(config) + if File.exist?(config) f = File.open(config,"r") dirs = IO.readlines("#{config}") dirs.each do |line| @@ -46,7 +46,7 @@ module IconArtist end return dir end - + def Icon.check_context(context) if !THEME_CONTEXTS.include? context puts "Error: Unknown icon context '#{context}'. \nContext must be one of: " @@ -57,30 +57,32 @@ module IconArtist return TRUE end - def Icon.check_document_metadata(svg, filename) + def Icon.check_document_metadata(svg, filename, multiple_icons = false) # Check for icon name - element = svg.root.elements['//cc:Work/dc:title'] messages = {:errors => [], :warnings => []} - if element - title = element.text - if !title + unless multiple_icons + element = svg.root.elements['//cc:Work/dc:title'] + if (element) + title = element.text + if !title + msg = "Error: Missing document metadata title (icon name)." + messages[:errors] += [msg] + puts "#{msg}" + else + iconname = File.basename(filename, ".svg") + if title != iconname + msg = "Warning: Icon name in document metadata differs from file name.\n" + msg += "Icon name in document metadata is: #{title}\n" + msg += "Icon file name (without extension) is: #{iconname}" + messages[:warnings] += [msg] + puts "#{msg}" + end + end + else msg = "Error: Missing document metadata title (icon name)." - messages[:errors] += [msg] + messages[:errors] += [msg] puts "#{msg}" - else - iconname = File.basename(filename, ".svg") - if title != iconname - msg = "Warning: Icon name in document metadata differs from file name.\n" - msg += "Icon name in document metadata is: #{title}\n" - msg += "Icon file name (without extension) is: #{iconname}" - messages[:warnings] += [msg] - puts "#{msg}" - end end - else - msg = "Error: Missing document metadata title (icon name)." - messages[:errors] += [msg] - puts "#{msg}" end # Check for icon context @@ -89,21 +91,21 @@ module IconArtist context = element.text if !context msg = "Error: Missing document metadata description (icon context)." - messages[:errors] += [msg] + messages[:errors] += [msg] puts "#{msg}" else if !check_context(context) - messages[:errors] += ["Error: Unknown icon context '#{context}'."] + messages[:errors] += ["Error: Unknown icon context '#{context}'."] end end else msg = "Error: Missing document metadata description (icon context)." - messages[:errors] += [msg] + messages[:errors] += [msg] puts "#{msg}" end - + return messages - + end def Icon.check_layer_plate(svg) @@ -115,14 +117,14 @@ module IconArtist style = plate.attributes['style'] if style != "display:none" msg = "Error: Plate layer is not hidden." - messages[:errors] += [msg] + messages[:errors] += [msg] puts "#{msg}" end end if !plate_present msg = "Error: Plate layer is missing." - messages[:errors] += [msg] + messages[:errors] += [msg] puts "#{msg}" end @@ -133,23 +135,23 @@ module IconArtist if THEME_SIZES.include? "#{size}" if plate_sizes.include? "#{size}" msg = "Error: Size #{size} detected multiple times." - messages[:errors] += [msg] + messages[:errors] += [msg] puts "#{msg}" else plate_sizes += ["#{size}"] end else - msg = "Error: Unknown icon size present in plate layer: #{size}." - messages[:errors] += [msg] + msg = "Warrning: Unknown icon size present in plate layer: #{size}." + messages[:warnings] += [msg] puts "#{msg}" end end - + # Check for missing sizes in plate layer THEME_SIZES.each do |icon| if !plate_sizes.include? icon msg = "Warning: Size #{icon} missing from plate layer." - messages[:warnings] += [msg] + messages[:warnings] += [msg] puts "#{msg}" end end @@ -158,37 +160,72 @@ module IconArtist scalable = svg.root.elements["//g[@inkscape:label='plate']/rect[@inkscape:label='scalable']"] if !scalable msg = "Error: Missing element with inkscape:label='scalable' in plate layer." - messages[:errors] += [msg] + messages[:errors] += [msg] puts "#{msg}" end return messages end - - def Icon.check_layer_artwork(svg) + + def Icon.check_layer_artwork(svg, multiple_icons = false) # Check if artwork layer is present messages = {:errors => [], :warnings => []} - artwork = svg.root.elements["//g[@inkscape:label='artwork']"] - if !artwork - msg = "Error: Artwork layer is missing." - messages[:errors] += [msg] - puts "#{msg}" + if multiple_icons + svg.root.elements["/svg"].each_element_with_attribute( + "inkscape:groupmode", "layer", 0, "g") do |layer| + layer_name = layer.attribute("inkscape:label").value + if (layer_name.match("artwork-+")) + scalable = layer.elements["g[@inkscape:label='scalable']"] + if !scalable + msg = "Error: Missing object group with inkscape:label='scalable' in '#{layer_name}' layer." + messages[:errors] += [msg] + puts "#{msg}" + end + end + end + else + artwork = svg.root.elements["//g[@inkscape:label='artwork']"] + if !artwork + msg = "Error: Artwork layer is missing." + messages[:errors] += [msg] + puts "#{msg}" + end + + # Check for scalable icon artwork + scalable = svg.root.elements["//g[@inkscape:label='artwork']/g[@inkscape:label='scalable']"] + if !scalable + msg = "Error: Missing object group with inkscape:label='scalable' in artwork layer." + messages[:errors] += [msg] + puts "#{msg}" + end end - # Check for scalable icon artwork - scalable = svg.root.elements["//g[@inkscape:label='artwork']/g[@inkscape:label='scalable']"] - if !scalable - msg = "Error: Missing object group with inkscape:label='scalable' in artwork layer." - messages[:errors] += [msg] - puts "#{msg}" + return messages + end + + def Icon.detect_multiple(svg) + multiple = false + icon_names = [] + svg.root.elements["/svg"].each_element_with_attribute( + "inkscape:groupmode", "layer", 0, "g") do |layer| + layer_name = layer.attribute("inkscape:label").value + if (layer_name.match("artwork-+")) + multiple = true + icon_names += [layer_name.split("artwork-")[1]] + end end - return messages + if multiple + return icon_names + else + return nil + end end - + def export_pngs(export_dir,sizes) + return nil if @multiple_icons export_sizes = [] - + @svg.root.each_element("//g[@inkscape:label='plate']/rect") do |icon| size = "#{icon.attributes['width']}x#{icon.attributes['height']}" if THEME_SIZES.include?(size) and sizes.include?(size) @@ -200,31 +237,34 @@ module IconArtist end end end - + return export_sizes end - + def export_scalable(export_dir) + return nil if @multiple_icons puts "Making scalable size..." sizes = [] - - plate = @svg.root.elements["//g[@inkscape:label='plate']/rect[@inkscape:label='scalable']"] + + scalable = REXML::Document.new(File.new("#{@filename}", 'r')) + + plate = scalable.root.elements["//g[@inkscape:label='plate']/rect[@inkscape:label='scalable']"] x = plate.attributes['x'] y = plate.attributes['y'] - artwork = @svg.root.elements["//g[@inkscape:label='artwork']/g[@inkscape:label='scalable']"] + artwork = scalable.root.elements["//g[@inkscape:label='artwork']/g[@inkscape:label='scalable']"] #XXX Need update to work with icons that already has transform translate = "translate(-#{x},-#{y})" artwork.add_attribute(REXML::Attribute.new('transform', translate)) - canvas = @svg.root.elements["/svg"] + canvas = scalable.root.elements["/svg"] canvas.add_attribute(REXML::Attribute.new('width', '48')) canvas.add_attribute(REXML::Attribute.new('height', '48')) - plate = @svg.root.elements["//g[@inkscape:label='plate']"] + plate = scalable.root.elements["//g[@inkscape:label='plate']"] plate.elements.delete_all('') - @svg.root.each_element("//g[@inkscape:label='artwork']/*") do |obj| + scalable.root.each_element("//g[@inkscape:label='artwork']/*") do |obj| label = obj.attributes['inkscape:label'] if (label != 'scalable') obj.parent.delete_element(obj) @@ -236,35 +276,49 @@ module IconArtist FileUtils.mkdir_p(icon_dir) unless File.exist?(icon_dir) f = File.open(out,"w") - @svg.write(f, -1, false) + scalable.write(f, -1, false) f.close if system "#{INKSCAPE} --vacuum-defs -l #{out} -f #{out} > /dev/null 2>&1" sizes += ['scalable'] end - + return sizes end - - def initialize_symlinks + + def initialize_symlinks_icon (iconname) icon_mapping = "/usr/share/icon-naming-utils/legacy-icon-mapping.xml" - @symlinks_def = [] - @symlinks_opt = [] + symlinks_def = [] + symlinks_opt = [] icon_map = REXML::Document.new(File.new(icon_mapping, 'r')) - icon = icon_map.root.elements["//context[@dir='#{@context}']/icon[@name='#{@iconname}']"] + icon = icon_map.root.elements["//context[@dir='#{@context}']/icon[@name='#{iconname}']"] if icon != nil icon.each_element('link') do |link| - @symlinks_def = @symlinks_def + [link.text] + symlinks_def = symlinks_def + [link.text] end end - @symlinks_initialized = true + + @symlinks[iconname] = {:default => symlinks_def, :optional => symlinks_opt} + end + + def initialize_symlinks + @symlinks = {} + if @multiple_icons + @icons.each do |iconname| + initialize_symlinks_icon (iconname) + @symlinks_initialized = true + end + else + initialize_symlinks_icon (@iconname) + @symlinks_initialized = true + end end public - + # Open new icon from set file # # filename:: @@ -277,20 +331,23 @@ module IconArtist @filename = filename @svg = REXML::Document.new(File.new("#{filename}", 'r')) + @icons = Icon.detect_multiple(@svg) + @multiple_icons = (@icons != nil) + unless opts[:skip_check] - messages = Icon.check_document_metadata(@svg, @filename) + messages = Icon.check_document_metadata(@svg, @filename, @multiple_icons) if messages[:errors].length > 0 raise "Failed. Check above messages for more info." end - + msg = Icon.check_layer_plate(@svg) messages[:warnings] += msg[:warnings] messages[:errors] += msg[:errors] if messages[:errors].length > 0 raise "Failed. Check above messages for more info." end - - msg = Icon.check_layer_artwork(@svg) + + msg = Icon.check_layer_artwork(@svg, @multiple_icons) messages[:warnings] += msg[:warnings] messages[:errors] += msg[:errors] if messages[:errors].length > 0 @@ -299,14 +356,15 @@ module IconArtist else messages = {:errors => [], :warnings => []} end - + @iconname = @svg.root.elements['//cc:Work/dc:title'].text + @icons = [@iconname] unless @icons @context = @svg.root.elements['//dc:description'].text puts "Icon #{@filename} loaded successfully." if messages[:warnings].length > 0 if messages[:warnings].length == 1 - puts "There was #{messages[:warnings].length} warning." + puts "There was #{messages[:warnings].length} warning." else puts "There were #{messages[:warnings].length} warnings." end @@ -316,16 +374,16 @@ module IconArtist # Create new icon with set name and context using selected template # # iconname:: - # name of the icon, preferably following freedesktop.org + # name of the icon, preferably following freedesktop.org # standards, to be created # # context:: # context of the icon, see THEME_CONTEXTS constant for more info # - # template:: - # select custom template, either template name (without .svg - # extension) that is located in your XDG User Dirs Templates dir or in - # /usr/share/icon-artist/templates/#{template}.svg, or a filename of a + # template:: + # select custom template, either template name (without .svg + # extension) that is located in your XDG User Dirs Templates dir or in + # /usr/share/icon-artist/templates/#{template}.svg, or a filename of a # custom template (with the .svg extension) def self.create(iconname, context, template="default") userdir_templates = Icon.get_templatedir @@ -339,27 +397,29 @@ module IconArtist end end end - + @iconname=iconname @context=context @filename=File.expand_path("#{@iconname}.svg") - + @multiple_icons = false + @icons = [@iconname] + if File.exist?(@filename) raise "Selected icon (#{@filename}) already exists. \nPlease set another icon name." end - + if !(THEME_CONTEXTS.include?(@context)) out = "Context must be one of: " THEME_CONTEXTS.each {|el| out = out + "#{el}, "} out = (out + "\n").gsub(", \n", ".\n") raise out end - + @svg = REXML::Document.new(File.new("#{@template}", 'r')) - + @svg.root.attributes.delete_all("inkscape:export-filename") @svg.root.attributes.delete_all("sodipodi:docname") - + element = @svg.root.elements["metadata/rdf:RDF/cc:Work"] element.elements.delete_all("dc:title") @@ -380,16 +440,110 @@ module IconArtist puts "New icon #{@filename} created." self.new(@filename, :skip_check => true) end - + + # Create multiple icons with set name and context using selected template + # into one SVG file + # + # filename:: + # name of the SVG file to be created + # + # iconnames:: + # names of the icons, preferably following freedesktop.org + # standards, to be created + # + # context:: + # context of the icon, see THEME_CONTEXTS constant for more info + # + # template:: + # select custom template, either template name (without .svg + # extension) that is located in your XDG User Dirs Templates dir or in + # /usr/share/icon-artist/templates/#{template}.svg, or a filename of a + # custom template (with the .svg extension) + def self.create_multiple(filename, iconnames, context, template="default") + if (filename == nil) || (iconnames == nil) ||(iconnames.empty?) + raise "Nothing to create!" + end + + if iconnames.length == 1 + return self.create iconnames[0], context, template + end + + userdir_templates = Icon.get_templatedir + @template = File.expand_path("#{template}") + if !File.exist?(@template) + @template = File.expand_path("#{userdir_templates}/#{template}.svg") + if !File.exist?(@template) + @template = "#{ARTIST_SHAREDIR}/templates/#{template}.svg" + if !File.exist?(@template) + raise "Could not find either '#{File.expand_path(template)}', '#{File.expand_path(userdir_templates)}/#{template}.svg' or '#{ARTIST_SHAREDIR}/templates/#{template}.svg'." + end + end + end + + @iconname = File.basename(filename, ".svg") + @context=context + @filename=File.expand_path(filename) + @multiple_icons = true + @icons = iconnames + + if File.exist?(@filename) + raise "Selected icon (#{@filename}) already exists. \nPlease set another icon name." + end + + if !(THEME_CONTEXTS.include?(@context)) + out = "Context must be one of: " + THEME_CONTEXTS.each {|el| out = out + "#{el}, "} + out = (out + "\n").gsub(", \n", ".\n") + raise out + end + + @svg = REXML::Document.new(File.new("#{@template}", 'r')) + + @svg.root.attributes.delete_all("inkscape:export-filename") + @svg.root.attributes.delete_all("sodipodi:docname") + + element = @svg.root.elements["metadata/rdf:RDF/cc:Work"] + + element.elements.delete_all("dc:title") + element.elements.delete_all("dc:description") + + title = REXML::Element.new "dc:title" + title.add_text "#{@iconname}" + element.add_element title + + description = REXML::Element.new "dc:description" + description.add_text "#{@context}" + element.add_element description + + @svg.root.elements.delete_all("//g[@inkscape:label='artwork']") + element = @svg.root.elements["/svg"] + @icons.each do |icon| + element.add_element "g", {"inkscape:label" => "artwork-#{icon}", + "inkscape:groupmode" => "layer", + "id" => "artwork-#{icon}"} + end + + f = File.open(@filename,"w") + @svg.write(f, -1, false) + f.close + + puts "New icon #{@filename} created." + self.new(@filename, :skip_check => true) + end + # Check if icon in given file follows one-canvas standards # - # filename:: + # filename:: # path to the file with the icon def self.check(filename) @filename = filename @svg = REXML::Document.new(File.new("#{filename}", 'r')) + + @icons = Icon.detect_multiple(@svg) + @multiple_icons = (@icons != nil) + puts "Checking document metadata..." - messages = Icon.check_document_metadata(@svg, @filename) + messages = Icon.check_document_metadata(@svg, @filename, @multiple_icons) if messages[:errors].length == 0 puts "Done." else @@ -409,7 +563,7 @@ module IconArtist end puts "Checking artwork layer..." - msg = Icon.check_layer_artwork(@svg) + msg = Icon.check_layer_artwork(@svg, @multiple_icons) messages[:warnings] += msg[:warnings] messages[:errors] += msg[:errors] if messages[:errors].length == 0 @@ -418,20 +572,21 @@ module IconArtist puts "Failed. Check above messages for more info." return messages end - + puts "\nCheck complete. No fatal errors found." if messages[:warnings].length > 0 if messages[:warnings].length == 1 - puts "There was #{messages[:warnings].length} warning." + puts "There was #{messages[:warnings].length} warning." else puts "There were #{messages[:warnings].length} warnings." end end return messages end - + # Export the PNGs and SVGs to selected directory using standard icon theme - # directory structure + # directory structure. Returns an array with export parameters which is hash + # {"name" => icon name, "context" => icon context, "sizes" => exported sizes} # # dir:: # export directory @@ -440,53 +595,144 @@ module IconArtist # set sizes to export def export(dir=".",sizes=THEME_SIZES) export_dir = File.expand_path(dir) - exported_sizes = export_pngs(export_dir,sizes) - if sizes.include?('scalable') - exported_sizes += export_scalable(export_dir) - end - puts "Icon #{@iconname} exported successfully." - icon = { - "name" => @iconname, - "context" => @context, - "sizes" => exported_sizes - } - return icon + + if @multiple_icons + icons = [] + separate_icons (export_dir) unless @separated + @icon_files.each_value do |icon_file| + single_icon = IconArtist::Icon.new(icon_file) + icons += single_icon.export export_dir + end + return icons + else + exported_sizes = export_pngs(export_dir,sizes) + if sizes.include?('scalable') + exported_sizes += export_scalable(export_dir) + end + puts "Icon #{@iconname} exported successfully." + icon = { + "name" => @iconname, + "context" => @context, + "sizes" => exported_sizes + } + return [icon] + end end - + # Add symlinks to the icon # # symlinks:: # array of symlinks to be added - def add_symlinks(symlinks) + # + # iconname:: + # original iconname, ignored when file contains single icon + def add_symlinks(symlinks, iconname = nil) initialize_symlinks unless @symlinks_initialized + + if (@multiple_icons && (@symlinks[iconname] != nil)) + iconlinks = @symlinks[iconname] + elsif (@symlinks[@iconname] != nil) + iconlinks = @symlinks[@iconname] + else + puts "Warning: Adding symlink for an unknown icon '#{iconname}'." + return + end + symlinks.each do |symlink| - if !@symlinks_def.include?(symlink) & !@symlinks_opt.include?(symlink) - @symlinks_opt += [symlink] + if !iconlinks[:default].include?(symlink) & !iconlinks[:optional].include?(symlink) + iconlinks[:optional] += [symlink] end end end - + # Accessor to symlinks. Returs a hash containing :default and :optional # symlinks. :default symlinks are found using icon-naming-utils. def symlinks initialize_symlinks unless @symlinks_initialized - return {:default => @symlinks_def, :optional => @symlinks_opt} + return @symlinks end - + # Accessor to icon contex def context @context end - + # Accessor to icon name def iconname @iconname end - + # Accessor to icon filename def filename @filename end - + + # Accessor to multiple_icons boolean + def multiple_icons + @multiple_icons + end + + # Accessor to included icons + def icons + @icons + end + + # Returns separated icon files if :separate is true and loaded icon contains + # multiple icons or if the file contains only one icon. In other cases it + # returns nil. + + # options:: + # :separate => separate multiple icons into multiple files + # :tmpdir => where the separated icons go, defauts to "./.separated" + def icon_files(opts={}) + if @multiple_icons + if opts[:separate] && !@separated + if opts[:tmpdir] + separate_icons(opts[:tmpdir]) + else + separate_icons('./.separated') + end + return @icon_files + else + return nil + end + + else + return {@iconname => @filename} + end + end + + def separate_icons(export_dir) + if @multiple_icons && (!@separated) + @icon_files = {} + @icons.each do |icon| + icon_svg = REXML::Document.new(File.new("#{@filename}", 'r')) + element = icon_svg.root.elements['//cc:Work/dc:title'] + element.text = icon + icon_svg.root.elements["/svg"].each_element_with_attribute( + "inkscape:groupmode", "layer", 0, "g") do |layer| + layer_name = "artwork-" + icon + if (layer.attribute("inkscape:label").value == "plate") + layer.attributes["style"] = "display:none" + elsif (layer.attribute("inkscape:label").value == layer_name) + layer.attributes["inkscape:label"] = "artwork" + layer.attributes["style"] = "display:inline" + else + icon_svg.root.elements["/svg"].delete(layer) + end + end + icon_dir = "#{export_dir}/one-cavas" + out ="#{icon_dir}/#{icon}.svg" + FileUtils.mkdir_p(icon_dir) unless File.exist?(icon_dir) + + f = File.open(out,"w") + icon_svg.write(f, -1, false) + f.close + puts "Temporary file #{out} has been created." + @icon_files[icon] = out + end + end + @separated = true + end end end diff --git a/sources/icon-artist/lib/icon-artist/icontheme.rb b/sources/icon-artist/lib/icon-artist/icontheme.rb index 20f6238..0dd1b86 100644 --- a/sources/icon-artist/lib/icon-artist/icontheme.rb +++ b/sources/icon-artist/lib/icon-artist/icontheme.rb @@ -1,18 +1,18 @@ # icontheme.rb: Supporting library for icon artists icon theme related # stuff like adding icons/folders to the theme # -# Copyright (C) 2008, 2009 Martin Sourada -# +# Copyright (C) 2008--2013 Martin Sourada +# # This script is free software; you can redistribute it and/or # modify it under the terms of the GNU Lesser General Public # License as published by the Free Software Foundation; either # version 2.1 of the License, or (at your option) any later version. -# +# # This script is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU # Lesser General Public License for more details. -# +# # You should have received a copy of the GNU Lesser General Public # License along with this script; if not, write to the Free Software # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA @@ -26,27 +26,27 @@ include FileUtils module IconArtist class IconTheme - + private - + DEFAULT_DIR_SETS = { - 'actions' => 'Actions', - 'animations' => 'Animations', - 'apps' => 'Applications', - 'categories' => 'Categories', - 'devices' => 'Devices', - 'emblems' => 'Devices', - 'emotes' => 'Emotes', - 'intl' => 'International', + 'actions' => 'Actions', + 'animations' => 'Animations', + 'apps' => 'Applications', + 'categories' => 'Categories', + 'devices' => 'Devices', + 'emblems' => 'Devices', + 'emotes' => 'Emotes', + 'intl' => 'International', 'mimetypes' => 'MimeTypes', - 'places' => 'Places', + 'places' => 'Places', 'status' => 'Status' } - + def dirs_parse(directories) @dirs = directories.scan(/[\w+\/]+/) end - + def index_add_dir(dir) if !@dirs.include?(dir) @dirs += [dir] @@ -56,16 +56,16 @@ module IconArtist size = aux[0] if size == "scalable" icon_size = "48" - else + else icon_size = size.scan(/\d+/)[0] end context = DEFAULT_DIR_SETS[aux[1]] - + @dir_settings[dir] += ["Size=#{icon_size}\n"] @dir_settings[dir] += ["Context=#{context}\n"] if size == "scalable" @dir_settings[dir] += ["Type=Scalable\n"] - @dir_settings[dir] += ["MinSize=24\n"] + @dir_settings[dir] += ["MinSize=48\n"] @dir_settings[dir] += ["MaxSize=256\n"] else @dir_settings[dir] += ["Type=Fixed\n"] @@ -73,7 +73,7 @@ module IconArtist end end - + def get_dirs(icons) dirs = [] icons.each do |icon| @@ -86,7 +86,7 @@ module IconArtist end return dirs end - + def index_add_dirs(dirs) dirs.each do |dir| index_add_dir(dir) @@ -96,19 +96,19 @@ module IconArtist def index_create(dirs) @dirs = [] @dir_settings = {} - + dirs.each do |dir| index_add_dir(dir) end end - + def index_parse(file) index = IO.readlines(file) @dirs = [] @dir_settings = {} set = "" reading = false - + index.each do |line| if line.include?("_Name=") @name = line.gsub("_Name=","").rstrip @@ -136,12 +136,12 @@ module IconArtist end end end - + def print_dirs dirs = @dirs.join(",") return dirs end - + def print_dir_settings dir_sets = "" @dirs.each do |dir| @@ -153,7 +153,7 @@ module IconArtist end return dir_sets end - + def index_initialize(icons, create=false) dirs = get_dirs(icons) if create or !File.exist?("#{@dir}/#{@index_name}") @@ -165,7 +165,7 @@ module IconArtist end end end - + def index_save index = IO.readlines("#{ARTIST_SHAREDIR}/theme/#{@index_name}.in") f = File.new("#{@dir}/#{@index_name}", "w+") @@ -179,9 +179,9 @@ module IconArtist end f.close end - + public - + # Open icon theme in selected dir. If not present, or +create+ set to true # create a new one # @@ -204,13 +204,13 @@ module IconArtist else create = false end - + if opts[:icons] icons = opts[:icons] else icons = opts[] end - + if opts[:name] @name = opts[:name] else @@ -228,21 +228,21 @@ module IconArtist else @inherits = 'gnome' end - + if opts[:translatable] @index_name = 'index.theme.in' else @index_name = 'index.theme' end - + index_initialize(icons,create) end - + # Save the changes made to the icon theme def save FileUtils.mkdir_p(@dir) unless File.exists?(@dir) index_save end - + end end diff --git a/sources/icon-artist/lib/icon-artist/theme-settings.rb b/sources/icon-artist/lib/icon-artist/theme-settings.rb index 5d7aa0e..e8f4089 100644 --- a/sources/icon-artist/lib/icon-artist/theme-settings.rb +++ b/sources/icon-artist/lib/icon-artist/theme-settings.rb @@ -1,25 +1,25 @@ # theme-settings.rb: General settings for the icon-artist library # -# Copyright (C) 2008, 2009 Martin Sourada -# +# Copyright (C) 2008--2013 Martin Sourada +# # This script is free software; you can redistribute it and/or # modify it under the terms of the GNU Lesser General Public # License as published by the Free Software Foundation; either # version 2.1 of the License, or (at your option) any later version. -# +# # This script is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU # Lesser General Public License for more details. -# +# # You should have received a copy of the GNU Lesser General Public # License along with this script; if not, write to the Free Software # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA module IconArtist - THEME_SIZES = ['16x16', '22x22', '24x24', '32x32', '48x48', '256x256', 'scalable'] + THEME_SIZES = ['16x16', '22x22', '24x24', '32x32', '48x48', '64x64', '128x128', '256x256', 'scalable'] THEME_CONTEXTS = ['actions', 'animations', 'apps', 'categories', 'devices', 'emblems', 'emotes', 'intl', 'mimetypes', 'places', 'status'] - + ARTIST_SHAREDIR = "/usr/share/icon-artist" end diff --git a/sources/icon-artist/lib/icon-artist/theme-settings.rb.in b/sources/icon-artist/lib/icon-artist/theme-settings.rb.in index c384ff9..12dcd3d 100644 --- a/sources/icon-artist/lib/icon-artist/theme-settings.rb.in +++ b/sources/icon-artist/lib/icon-artist/theme-settings.rb.in @@ -1,25 +1,25 @@ # theme-settings.rb: General settings for the icon-artist library # -# Copyright (C) 2008, 2009 Martin Sourada -# +# Copyright (C) 2008--2013 Martin Sourada +# # This script is free software; you can redistribute it and/or # modify it under the terms of the GNU Lesser General Public # License as published by the Free Software Foundation; either # version 2.1 of the License, or (at your option) any later version. -# +# # This script is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU # Lesser General Public License for more details. -# +# # You should have received a copy of the GNU Lesser General Public # License along with this script; if not, write to the Free Software # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA module IconArtist - THEME_SIZES = ['16x16', '22x22', '24x24', '32x32', '48x48', '256x256', 'scalable'] + THEME_SIZES = ['16x16', '22x22', '24x24', '32x32', '48x48', '64x64', '128x128', '256x256', 'scalable'] THEME_CONTEXTS = ['actions', 'animations', 'apps', 'categories', 'devices', 'emblems', 'emotes', 'intl', 'mimetypes', 'places', 'status'] - + ARTIST_SHAREDIR = @sharedir@ end