From e974880e032ca52a140ed3db602c79166ac40006 Mon Sep 17 00:00:00 2001 From: Andy Meneely Date: Wed, 29 Apr 2015 16:47:45 -0400 Subject: [PATCH] Add 'data' field to svg method for loading XML directly Implements #65 --- CHANGELOG.md | 3 ++- lib/squib/api/image.rb | 8 +++++--- lib/squib/api/text_embed.rb | 2 +- lib/squib/constants.rb | 2 ++ lib/squib/graphics/image.rb | 8 +++++--- samples/embed_text.rb | 2 +- samples/load_images.rb | 14 ++++++++++++++ spec/api/api_image_spec.rb | 2 +- spec/data/samples/load_images.rb.txt | 6 ++++++ spec/graphics/graphics_images_spec.rb | 10 +++++----- squib.gemspec | 1 + squib.sublime-project | 4 ++++ 12 files changed, 47 insertions(+), 15 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index c311774..792814e 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,8 +4,9 @@ Squib follows [semantic versioning](http://semver.org). ## v0.6.0 / Unreleased Features: -* Upgraded roo (Excel parsing) to 2.0.0. Nothing major for Squib users, just keeping up with the times. +* Added `data` field to `svg` to allow for manipulating SVG XML data directly. Works nicely with my new `game_icons` [gem](https://github.com/andymeneely/game_icons) (#65) * Added `stroke_width` and `stroke_color` to the `text` method to outlines text. (#51) +* Upgraded roo (Excel parsing) to 2.0.0. Nothing major for Squib users, just keeping up with the times. Bugs: * Fixed global text hinting (#63) diff --git a/lib/squib/api/image.rb b/lib/squib/api/image.rb index a2caff6..41a886d 100644 --- a/lib/squib/api/image.rb +++ b/lib/squib/api/image.rb @@ -34,7 +34,7 @@ module Squib end end - # Renders an entire svg file at the given location. Uses the SVG-specified units and DPI to determine the pixel width and height. + # Renders an entire svg file at the given location. Uses the SVG-specified units and DPI to determine the pixel width and height. If neither data nor file are specified for a given card, this method does nothing. # # See {file:samples/load-images.rb samples/load-images.rb} and {file:samples/tgc-overlay.rb samples/tgc-overlay.rb} as examples. # @example @@ -42,6 +42,7 @@ module Squib # # @option opts range [Enumerable, :all] (:all) the range of cards over which this will be rendered. See {file:README.md#Specifying_Ranges Specifying Ranges} # @option opts file [String] ('') file(s) to read in. If it's a single file, then it's use for every card in range. If the parameter is an Array of files, then each file is looked up for each card. If any of them are nil or '', nothing is done. See {file:README.md#Specifying_Files Specifying Files}. Supports Arrays, see {file:README.md#Arrays_and_Singleton_Expansion Arrays and Singleon Expansion} + # @option opts data [String] (nil) render from an SVG XML string. Overrides file if both are specified (a warning is shown) . If it's a single file, then it's use for every card in range. If the parameter is an Array of files, then each file is looked up for each card. If any of them are nil or '', nothing is done. See {file:README.md#Specifying_Files Specifying Files}. Supports Arrays, see {file:README.md#Arrays_and_Singleton_Expansion Arrays and Singleon Expansion}. # @option opts id [String] (nil) if set, then only render the SVG element with the given id. Prefix '#' is optional. Note: the x-y coordinates are still relative to the SVG document's page. Supports Arrays, see {file:README.md#Arrays_and_Singleton_Expansion Arrays and Singleon Expansion} # @option opts force_id [Boolean] (false) if set, then this svg will not be rendered at all if the id is empty or nil. If not set, the entire SVG is rendered. Supports Arrays, see {file:README.md#Arrays_and_Singleton_Expansion Arrays and Singleon Expansion} # @option opts x [Integer] (0) the x-coordinate to place. Supports Arrays, see {file:README.md#Arrays_and_Singleton_Expansion Arrays and Singleon Expansion}. Supports Unit Conversion, see {file:README.md#Units Units}. @@ -56,12 +57,13 @@ module Squib # @return [nil] Returns nil # @api public def svg(opts = {}) - p = needs(opts,[:range, :files, :svgid, :force_svgid, :x, :y, :width, :height, :layout, :alpha, :blend, :angle, :mask]) + p = needs(opts,[:range, :files, :svgdata, :svgid, :force_svgid, :x, :y, :width, :height, + :layout, :alpha, :blend, :angle, :mask]) Dir.chdir(img_dir) do @progress_bar.start('Loading SVG(s)', p[:range].size) do |bar| p[:range].each do |i| unless p[:force_id][i] && p[:id][i].to_s.empty? - @cards[i].svg(p[:file][i], p[:id][i], p[:x][i], p[:y][i], + @cards[i].svg(p[:file][i], p[:data][i], p[:id][i], p[:x][i], p[:y][i], p[:width][i], p[:height][i], p[:alpha][i], p[:blend][i], p[:angle][i],p[:mask][i]) end bar.increment diff --git a/lib/squib/api/text_embed.rb b/lib/squib/api/text_embed.rb index 033d6b9..15c1ed3 100644 --- a/lib/squib/api/text_embed.rb +++ b/lib/squib/api/text_embed.rb @@ -30,7 +30,7 @@ module Squib # TODO: add input validation here. We need the key for example. rule = {type: :svg}.merge(opts) rule[:draw] = Proc.new do |card, x,y| - card.svg(rule[:file], rule[:id], x, y, rule[:width], rule[:height], + card.svg(rule[:file], rule[:data], rule[:id], x, y, rule[:width], rule[:height], rule[:alpha], rule[:blend], rule[:angle], rule[:mask]) end @rules[opts[:key]] = rule diff --git a/lib/squib/constants.rb b/lib/squib/constants.rb index 2b4a675..87c8a6c 100644 --- a/lib/squib/constants.rb +++ b/lib/squib/constants.rb @@ -14,6 +14,7 @@ module Squib :cx2 => 0, :cy1 => 0, :cy2 => 0, + :data => nil, :default_font => 'Arial 36', :dir => '_output', :dx => 0, # delta @@ -107,6 +108,7 @@ module Squib :str => :str, :stroke_color => :stroke_color, :stroke_width => :stroke_width, + :svgdata => :data, :svgid => :id, :valign => :valign, :width => :width, diff --git a/lib/squib/graphics/image.rb b/lib/squib/graphics/image.rb index 6b7631b..b3f7295 100644 --- a/lib/squib/graphics/image.rb +++ b/lib/squib/graphics/image.rb @@ -41,10 +41,12 @@ module Squib # :nodoc: # @api private - def svg(file, id, x, y, width, height, alpha, blend, angle, mask) + def svg(file, data, id, x, y, width, height, alpha, blend, angle, mask) Squib.logger.debug {"Rendering: #{file}, id: #{id} @#{x},#{y} #{width}x#{height}, alpha: #{alpha}, blend: #{blend}, angle: #{angle}, mask: #{mask}"} - return if file.nil? or file.eql? '' - svg = RSVG::Handle.new_from_file(file) + Squib.logger.warn 'Both an SVG file and SVG data were specified' unless file.to_s.empty? or data.to_s.empty? + return if (file.nil? or file.eql? '') and data.nil? # nothing specified + data = File.read(file) if data.to_s.empty? + svg = RSVG::Handle.new_from_data(data) width = svg.width if width == :native height = svg.height if height == :native scale_width = width.to_f / svg.width.to_f diff --git a/samples/embed_text.rb b/samples/embed_text.rb index 81e93ba..6fddf37 100644 --- a/samples/embed_text.rb +++ b/samples/embed_text.rb @@ -61,7 +61,7 @@ Squib::Deck.new do text(str: embed_text, font: 'Sans 21', x: 400, y: 320, width: 180, height: 300, align: :center, ellipsize: false, justify: false, hint: :magenta) do |embed| - embed.svg key: ':tool:', width: 28, height: 28, file: 'spanner.svg' + embed.svg key: ':tool:', width: 28, height: 28, data: File.read('spanner.svg') embed.svg key: ':health:', width: 28, height: 28, file: 'glass-heart.svg' embed.png key: ':purse:', width: 28, height: 28, file: 'shiny-purse.png' end diff --git a/samples/load_images.rb b/samples/load_images.rb index 44ab331..095c3a3 100644 --- a/samples/load_images.rb +++ b/samples/load_images.rb @@ -17,6 +17,20 @@ Squib::Deck.new(width: 825, height: 1125, cards: 1) do # Squib prepends a #-sign if one is not specified svg file: 'spanner.svg', id: 'backdrop', x: 50, y: 450, width: 125, height: 125 + # We can also load SVGs as a string of XML + svg data: File.read('spanner.svg'), x: 50, y: 600, width: 75, height: 75 + + # The svg data field works nicely with modifying the SVG XML on-the-fly. + # To run this one, do `gem install game_icons` and uncomment the following + # + # require 'game_icons' + # svg data: GameIcons.get('angler-fish').recolor(fg: '#ccc', bg: '#333').string, + # x: 150, y: 600, width: 75, height: 75 + # + # More examples at https://github.com/andymeneely/game_icons + # (or `gem install game_icons`) to get & manipulate art from game-icons.net + # Nokogiri (already included in Squib) is also great for XML manipulation. + # WARNING! If you choose to use the SVG ID, the x-y coordinates are still # relative to the SVG page. See this example in an SVG editor svg file: 'offset.svg', id: 'thing', x: 0, y: 0, width: 600, height: 600 diff --git a/spec/api/api_image_spec.rb b/spec/api/api_image_spec.rb index cefdaed..026b3a8 100644 --- a/spec/api/api_image_spec.rb +++ b/spec/api/api_image_spec.rb @@ -23,7 +23,7 @@ describe Squib::Deck, 'images' do it 'calls Card#svg, Dir, and progress bar' do card = instance_double(Squib::Card) progress = double(Squib::Progress) - expect(card).to receive(:svg).with('foo', '#bar', 0, 1, 20, 30, 0.5, :overlay, 0.75, nil).once + expect(card).to receive(:svg).with('foo', nil, '#bar', 0, 1, 20, 30, 0.5, :overlay, 0.75, nil).once expect(Dir).to receive(:chdir).with('.').and_yield.once expect(progress).to receive(:start).and_yield(progress).once expect(progress).to receive(:increment).once diff --git a/spec/data/samples/load_images.rb.txt b/spec/data/samples/load_images.rb.txt index 6e64b5e..2ed2a0c 100644 --- a/spec/data/samples/load_images.rb.txt +++ b/spec/data/samples/load_images.rb.txt @@ -52,6 +52,12 @@ cairo: scale([0.9765625, 0.9765625]) cairo: render_rsvg_handle([RSVG::Handle, "#backdrop"]) cairo: restore([]) cairo: save([]) +cairo: translate([50, 600]) +cairo: rotate([0]) +cairo: scale([0.5859375, 0.5859375]) +cairo: render_rsvg_handle([RSVG::Handle, nil]) +cairo: restore([]) +cairo: save([]) cairo: translate([0, 0]) cairo: rotate([0]) cairo: scale([0.8021390374331551, 0.8032128514056225]) diff --git a/spec/graphics/graphics_images_spec.rb b/spec/graphics/graphics_images_spec.rb index 6b590d8..42ef5d8 100644 --- a/spec/graphics/graphics_images_spec.rb +++ b/spec/graphics/graphics_images_spec.rb @@ -12,7 +12,7 @@ describe Squib::Card do allow(Cairo::Context).to receive(:new).and_return(context) allow(Cairo::ImageSurface).to receive(:from_png).and_return(png) allow(Cairo::ImageSurface).to receive(:new).and_return(png) - allow(RSVG::Handle).to receive(:new_from_file).and_return(svg) + allow(RSVG::Handle).to receive(:new_from_data).and_return(svg) allow(deck).to receive(:dir).and_return('_output') allow(deck).to receive(:count_format).and_return('%02d') allow(deck).to receive(:prefix).and_return('card_') @@ -58,8 +58,8 @@ describe Squib::Card do expect(context).to receive(:restore).once card = Squib::Card.new(deck, 100, 150) - # svg(file, id, x, y, width, height, alpha, blend, angle) - card.svg('foo.png', 'id', 37, 38, :native, :native, 0.9, :none, 0.0, nil) + # svg(file, data, id, x, y, width, height, alpha, blend, angle) + card.svg(nil, '', 'id', 37, 38, :native, :native, 0.9, :none, 0.0, nil) end it 'sets blend when needed' do @@ -68,7 +68,7 @@ describe Squib::Card do expect(context).to receive(:operator=).with(:overlay).once card = Squib::Card.new(deck, 100, 150) - card.svg('foo.png', nil, 37, 38, :native, :native, 0.9, :overlay, 0.0, nil) + card.svg(nil, '', nil, 37, 38, :native, :native, 0.9, :overlay, 0.0, nil) end it 'sets width & height when needed' do @@ -78,7 +78,7 @@ describe Squib::Card do expect(context).to receive(:scale).with(2.0, 3.0).once card = Squib::Card.new(deck, 100, 150) - card.svg('foo.png', nil, 37, 38, 200, 300, 0.9, :none, 0.0, nil) + card.svg(nil, '', nil, 37, 38, 200, 300, 0.9, :none, 0.0, nil) end end diff --git a/squib.gemspec b/squib.gemspec index df4ed88..fdeaf65 100644 --- a/squib.gemspec +++ b/squib.gemspec @@ -43,5 +43,6 @@ Gem::Specification.new do |spec| spec.add_development_dependency 'coveralls' spec.add_development_dependency 'byebug' spec.add_development_dependency 'launchy' + spec.add_development_dependency 'game_icons' end diff --git a/squib.sublime-project b/squib.sublime-project index 063a2c8..32f18b3 100644 --- a/squib.sublime-project +++ b/squib.sublime-project @@ -40,6 +40,10 @@ "name": "rake run[custom_config]", "shell_cmd": "rake run[custom_config]", }, + { + "name": "rake run[load_images]", + "shell_cmd": "rake run[load_images]", + }, { "name": "rspec spec/samples/samples_regression_spec.rb", "shell_cmd": "rspec spec/samples/samples_regression_spec.rb",