diff --git a/lib/squib/api/text.rb b/lib/squib/api/text.rb index e3fa8cf..8f26195 100644 --- a/lib/squib/api/text.rb +++ b/lib/squib/api/text.rb @@ -1,3 +1,5 @@ +require 'squib/api/text_embed' + module Squib class Deck @@ -41,9 +43,12 @@ module Squib def text(opts = {}) opts = needs(opts, [:range, :str, :font, :font_size, :x, :y, :width, :height, :color, :wrap, :align, :justify, :spacing, :valign, :markup, :ellipsize, :hint, :layout, :angle]) + embed = TextEmbed.new + yield(embed) if block_given? #store the opts for later use extents = Array.new(@cards.size) opts[:range].each do |i| - extents[i] = @cards[i].text(opts[:str][i], opts[:font][i], opts[:font_size][i], opts[:color][i], + extents[i] = @cards[i].text(embed, + opts[:str][i], opts[:font][i], opts[:font_size][i], opts[:color][i], opts[:x][i], opts[:y][i], opts[:width][i], opts[:height][i], opts[:markup][i], opts[:justify][i], opts[:wrap][i], opts[:ellipsize][i], opts[:spacing][i], opts[:align][i], diff --git a/lib/squib/api/text_embed.rb b/lib/squib/api/text_embed.rb new file mode 100644 index 0000000..b90148e --- /dev/null +++ b/lib/squib/api/text_embed.rb @@ -0,0 +1,26 @@ +module Squib + class TextEmbed + attr_reader :rules + + def initialize + @rules = {} # store an array of options for later usage + end + + # Context object for embedding text within a string + # + # @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 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 layout [String, Symbol] (nil) entry in the layout to use as defaults for this command. See {file:README.md#Custom_Layouts Custom Layouts}. Supports Arrays, see {file:README.md#Arrays_and_Singleton_Expansion Arrays and Singleon Expansion} + # @option opts width [Integer, :native] (:native) the width of the image rendered + # @option opts height [Integer, :native] the height the height of the image rendered + # @option opts alpha [Decimal] (1.0) the alpha-transparency percentage used to blend this image. Supports Arrays, see {file:README.md#Arrays_and_Singleton_Expansion Arrays and Singleon Expansion} + # @option opts blend [:none, :multiply, :screen, :overlay, :darken, :lighten, :color_dodge, :color_burn, :hard_light, :soft_light, :difference, :exclusion, :hsl_hue, :hsl_saturation, :hsl_color, :hsl_luminosity] (:none) the composite blend operator used when applying this image. See Blend Modes at http://cairographics.org/operators. Supports Arrays, see {file:README.md#Arrays_and_Singleton_Expansion Arrays and Singleon Expansion} + # @option opts angle [FixNum] (0) Rotation of the in radians. Note that this rotates around the upper-left corner, making the placement of x-y coordinates slightly tricky. Supports Arrays, see {file:README.md#Arrays_and_Singleton_Expansion Arrays and Singleon Expansion} + def svg(opts) + opts = Squib::SYSTEM_DEFAULTS.merge(opts) + # TODO: add input validation here. We need the key for example. + @rules[opts[:key]] = {type: :svg}.merge(opts) + end + end +end \ No newline at end of file diff --git a/lib/squib/card.rb b/lib/squib/card.rb index 18a1bc6..7e4a29a 100644 --- a/lib/squib/card.rb +++ b/lib/squib/card.rb @@ -5,7 +5,6 @@ require 'squib/graphics/cairo_context_wrapper' module Squib # Back end graphics. Private. class Card - include Squib::InputHelpers # :nodoc: # @api private diff --git a/lib/squib/graphics/text.rb b/lib/squib/graphics/text.rb index c2ab532..ec95624 100644 --- a/lib/squib/graphics/text.rb +++ b/lib/squib/graphics/text.rb @@ -6,14 +6,14 @@ module Squib # :nodoc: # @api private def draw_text_hint(cc,x,y,layout, color,angle) - color = @deck.text_hint if color.to_s.eql? 'off' and not @deck.text_hint.to_s.eql? 'off' + color = @deck.text_hint if color.eql? 'off' and not @deck.text_hint.to_s.eql? 'off' return if color.to_s.eql? 'off' or color.nil? # when w,h < 0, it was never set. extents[1] are ink extents w = layout.width / Pango::SCALE w = layout.extents[1].width / Pango::SCALE if w < 0 h = layout.height / Pango::SCALE h = layout.extents[1].height / Pango::SCALE if h < 0 - cc.rounded_rectangle(x,y,w,h,0,0) + cc.rounded_rectangle(0, 0, w, h, 0, 0) cc.set_source_color(color) cc.set_line_width(2.0) cc.stroke @@ -62,14 +62,14 @@ module Squib # :nodoc: # @api private - def valign!(cc, layout, x, y, valign) + def valign!(cc, layout, valign) if layout.height > 0 ink_extents = layout.extents[1] - case valign.to_s + case valign.to_s.downcase when 'middle' - cc.move_to(x, y + (layout.height - ink_extents.height) / (2 * Pango::SCALE)) + cc.move_to(0, (layout.height - ink_extents.height) / (2 * Pango::SCALE)) when 'bottom' - cc.move_to(x, y + (layout.height - ink_extents.height) / Pango::SCALE) + cc.move_to(0, (layout.height - ink_extents.height) / Pango::SCALE) end end end @@ -82,36 +82,97 @@ module Squib layout end + def next_embed(keys, str) + ret = nil + ret_key = nil + keys.each do |key| + i = str.index(key) + ret ||= i + unless i.nil? || i > ret + ret = i + ret_key = key + end + end + ret_key + end + + def process_embeds(embed, str, layout, cc) + return unless embed.rules.any? + while (key = next_embed(embed.rules.keys, str)) != nil + rule = embed.rules[key] + spacing = rule[:width] * Pango::SCALE + index = str.gsub(/ <\/span>/,' ').index(key) + str = str.sub(key, " ") + layout.markup = str + cc.update_pango_layout(layout) + iter = layout.iter + while iter.next_char! && iter.index < index; end + rect = layout.index_to_pos(index) + letter_width = iter.char_extents.width - spacing # the spacing of our actual letter + x = (rect.x + letter_width / 2) / Pango::SCALE + # x = rect.x / Pango::SCALE + y = rect.y / Pango::SCALE + circle(x, y + 2, 2, :red, :red, 0) + # circle(x,y + 2, 2, :red, :red, 0) + puts <<-EOS + Embedding #{key} + at index: #{index} + xy #{x},#{y} + spacing at #{spacing} or #{spacing / Pango::SCALE}px + space character width #{letter_width} or #{letter_width / Pango::SCALE}px + and string is: + #{str} + EOS + svg(rule[:file], rule[:id], x, y, rule[:width], rule[:height], + rule[:alpha], rule[:blend], rule[:angle], SYSTEM_DEFAULTS[:mask]) + end + # embed.rules.each do |rule| + # cc.update_pango_layout(layout) + # index = str.index(rule[:key]) + # unless index.nil? + # puts "embed #{rule[:type]} #{rule[:file]} into #{rule[:key]} at #{x},#{y}, index #{index}" + # end + # end + end + # :nodoc: # @api private - def text(str, font, font_size, color, + def text(embed,str, font, font_size, color, x, y, width, height, markup, justify, wrap, ellipsize, spacing, align, valign, hint, angle) Squib.logger.debug {"Placing '#{str}'' with font '#{font}' @ #{x}, #{y}, color: #{color}, angle: #{angle} etc."} extents = nil + str = str.to_s use_cairo do |cc| cc.set_source_squibcolor(color) cc.translate(x,y) cc.rotate(angle) - cc.translate(-1*x,-1*y) - cc.move_to(x,y) + cc.move_to(0, 0) - layout = cc.create_pango_layout - font_desc = Pango::FontDescription.new(font) + font_desc = Pango::FontDescription.new(font) font_desc.size = font_size * Pango::SCALE unless font_size.nil? + layout = cc.create_pango_layout layout.font_description = font_desc - layout.text = str.to_s - layout.markup = str.to_s if markup + layout.text = str + layout.markup = str if markup + set_wh!(layout, width, height) set_wrap!(layout, wrap) set_ellipsize!(layout, ellipsize) set_align!(layout, align) + layout.justify = justify unless justify.nil? layout.spacing = spacing * Pango::SCALE unless spacing.nil? cc.update_pango_layout(layout) - valign!(cc, layout, x, y, valign) - cc.update_pango_layout(layout) ; cc.show_pango_layout(layout) + + valign!(cc, layout, valign) + cc.update_pango_layout(layout) + + process_embeds(embed, str, layout, cc) + cc.update_pango_layout(layout) + + cc.show_pango_layout(layout) draw_text_hint(cc,x,y,layout,hint,angle) extents = { width: layout.extents[1].width / Pango::SCALE, height: layout.extents[1].height / Pango::SCALE } diff --git a/samples/embed_text.rb b/samples/embed_text.rb new file mode 100644 index 0000000..ceb6936 --- /dev/null +++ b/samples/embed_text.rb @@ -0,0 +1,17 @@ +require 'squib' + +Squib::Deck.new do + rect x: 0, y: 0, width: 825, height: 1125 + rect x: 0, y: 0, width: 180, height: 180, stroke_color: :red + + # embed_text = 'Take 1:tool:and gain 2 :health:. Take 2 :tool: if level 2' + embed_text = '1 :tool: 2 :health:' + text(str: embed_text, font: 'Sans 18', + x: 0, y: 0, width: 180, + align: :center, ellipsize: false, justify: false) do |embed| + embed.svg key: ':tool:', width: 28, height: 28, file: 'spanner.svg' + embed.svg key: ':health:', width: 28, height: 28, file: 'glass-heart.svg' + end + + save_png prefix: 'embed_' +end \ No newline at end of file diff --git a/samples/text_options.rb b/samples/text_options.rb index 5634ca1..b55c896 100644 --- a/samples/text_options.rb +++ b/samples/text_options.rb @@ -3,9 +3,9 @@ require 'squib' data = {'name' => ['Thief', 'Grifter', 'Mastermind'], 'level' => [1,2,3]} -longtext = "This is left-justified text.\nWhat do you know about tweetle beetles? well... \nWhen tweetle beetles fight, it's called a tweetle beetle battle. And when they battle in a puddle, it's a tweetle beetle puddle battle. AND when tweetle beetles battle with paddles in a puddle, they call it a tweetle beetle puddle paddle battle. AND... When beetles battle beetles in a puddle paddle battle and the beetle battle puddle is a puddle in a bottle... ..they call this a tweetle beetle bottle puddle paddle battle muddle. AND... When beetles fight these battles in a bottle with their paddles and the bottle's on a poodle and the poodle's eating noodles... ...they call this a muddle puddle tweetle poodle beetle noodle bottle paddle battle." +longtext = "This is left-justified text, with newlines.\nWhat do you know about tweetle beetles? well... When tweetle beetles fight, it's called a tweetle beetle battle. And when they battle in a puddle, it's a tweetle beetle puddle battle. AND when tweetle beetles battle with paddles in a puddle, they call it a tweetle beetle puddle paddle battle. AND... When beetles battle beetles in a puddle paddle battle and the beetle battle puddle is a puddle in a bottle... ..they call this a tweetle beetle bottle puddle paddle battle muddle." -Squib::Deck.new(width: 825, height: 1125, cards: 3) do +Squib::Deck.new(width: 825, height: 1125, cards: 1) do background color: :white rect x: 15, y: 15, width: 795, height: 1095, x_radius: 50, y_radius: 50 rect x: 30, y: 30, width: 128, height: 128, x_radius: 25, y_radius: 25 @@ -62,9 +62,19 @@ Squib::Deck.new(width: 825, height: 1125, cards: 3) do text str: longtext, font: 'Arial 16', x: 65, y: 700, - width: inches(2.25), height: inches(1), + width: '1.5in', height: inches(1), justify: true + # Here's how you embed images into text. + embed_text = 'Embedded icons! Take one 1 :tool: and gain 2 :health:. If Level 2, take 2 :tool:' + # embed_text = '1 :tool: 2 :health: 2 :tool:' + text(str: embed_text, font: 'Sans 18', + x: '1.8in', y: '2.5in', width: '0.85in', + align: :center, ellipsize: false) do |embed| + embed.svg key: ':tool:', width: 28, height: 28, file: 'spanner.svg' + embed.svg key: ':health:', width: 28, height: 28, file: 'glass-heart.svg' + end + text str: 'Markup is also quite easy awesome', markup: true, x: 50, y: 1000, @@ -72,5 +82,6 @@ Squib::Deck.new(width: 825, height: 1125, cards: 3) do valign: :bottom, font: 'Arial 32', hint: :cyan - save prefix: 'text_', format: :png + + save range: 0, prefix: 'text_', format: :png end