diff --git a/CHANGELOG.md b/CHANGELOG.md index a311fef..cf007c0 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,11 +1,11 @@ # Squib CHANGELOG # v0.5.0 -* Embedding of SVGs and PNGs into text! See README, `text_options.rb`, and `embed_text.rb`, and API documentation. This was a finnicky feature, so feedback and bug reports are welcome. (#30) +* Embedding of SVGs and PNGs into text! See README, `text_options.rb`, and `embed_text.rb`, and API documentation. This was a finnicky feature, so feedback and bug reports are welcome. * Curves! We can now do Bezier curves. Documented, and added to the sample `draw_shapes.rb` (#37). +* Smart Quotes! The `text` rule now has a `quotes: 'smart'` option where straight quotes get converted to curly quotes. Assumes UTF-8, or you can specify your own quote characters if you're not in UTF-8. Known issues -* Embedding icons for right-aligned and center-aligned is wrong for the first icon (#46). This is likely a Pango bug - I'm working on getting that fixed. There is a workaround. * OSX Yosemite will show this warning: `: The function ‘CGFontGetGlyphPath’ is obsolete and will be removed in an upcoming update. Unfortunately, this application, or a library it uses, is using this obsolete function, and is thereby contributing to an overall degradation of system performance.` This warning will go away when the Ruby Cairo bindings upgrades from 1.14.1 to 1.14.2. # v0.4.0 diff --git a/lib/squib/api/text.rb b/lib/squib/api/text.rb index 8f26195..63fade0 100644 --- a/lib/squib/api/text.rb +++ b/lib/squib/api/text.rb @@ -1,4 +1,5 @@ require 'squib/api/text_embed' +require 'squib/args/smart_quotes' module Squib class Deck @@ -38,17 +39,20 @@ module Squib # @option opts ellipsize [:none, :start, :middle, :end, true, false] (:end) When width and height are set, determines the behavior of overflowing text. Also: `true` maps to `:end` and `false` maps to `:none`. Default `:end` # @option opts angle [FixNum] (0) Rotation of the text in radians. Note that this rotates around the upper-left corner of the text box, making the placement of x-y coordinates slightly tricky. # @option opts hint [String] (:nil) draw a rectangle around the text with the given color. Overrides global hints (see {Deck#hint}). + # @options ops quotes [:smart, :dumb, or Array]. Convert straight ("dumb") quotes to curly ("smart") quotes. The 'smart' option assumes UTF-8 characters. If you supply a two-element array of characters, those will be used (first is left, second is right). Smart quoting looks for a quote next to a letter, word, number, or underscore character. Default is to show straight quotes. # @return [Array] Returns an Array of hashes keyed by :width and :height that mark the ink extents of the text rendered. # @api public 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]) + :align, :justify, :spacing, :valign, :markup, :ellipsize, :hint, :layout, + :angle, :quotes]) 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(embed, - opts[:str][i], opts[:font][i], opts[:font_size][i], opts[:color][i], + str = Args::SmartQuotes.new.process(opts[:str][i], opts[:quotes][i]) + extents[i] = @cards[i].text(embed, str, + 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/args/smart_quotes.rb b/lib/squib/args/smart_quotes.rb new file mode 100644 index 0000000..a220b0b --- /dev/null +++ b/lib/squib/args/smart_quotes.rb @@ -0,0 +1,29 @@ +module Squib + module Args + class SmartQuotes + + def process(str, opt) + clean_opt = opt.to_s.downcase.strip + return str if clean_opt.eql? 'dumb' + if clean_opt.eql? 'smart' + quotify(str) # default to UTF-8 + else + quotify(str, opt) # supplied quotes + end + end + + # Convert regular quotes to smart quotes by looking for + # a boundary between a word character (letters, numbers, underscore) + # and a quote. Replaces with the UTF-8 equivalent. + # :nodoc: + # @api private + def quotify(str, quote_chars = ["\u201C", "\u201D"]) + left_regex = /(\")(\w)/ + right_regex = /(\w)(\")/ + str.gsub(left_regex, quote_chars[0] + '\2') + .gsub(right_regex, '\1' + quote_chars[1]) + end + + end + end +end \ No newline at end of file diff --git a/lib/squib/constants.rb b/lib/squib/constants.rb index 14bed30..7864d5a 100644 --- a/lib/squib/constants.rb +++ b/lib/squib/constants.rb @@ -37,6 +37,7 @@ module Squib :offset => 1.1, :prefix => 'card_', :progress_bar => false, + :quotes => :dumb, :reflect_offset => 15, :reflect_percent => 0.25, :reflect_strength => 0.2, @@ -121,6 +122,7 @@ module Squib :layout => :layout, :markup => :markup, :mask => :mask, + :quotes => :quotes, :rect_radius => :radius, :spacing => :spacing, :str => :str, diff --git a/samples/embed_text.rb b/samples/embed_text.rb index 44d9581..88ae18c 100644 --- a/samples/embed_text.rb +++ b/samples/embed_text.rb @@ -72,7 +72,7 @@ end Squib::Deck.new(cards: 3) do embed_text = 'Take 1 :tool: and gain 2 :health:.' - text(str: embed_text, font: 'Sans', font_size: [18, 25, 32], + text(str: embed_text, font: 'Sans', font_size: [18, 32, 45], x: 0, y: 0, width: 180, height: 300, valign: :bottom, align: :left, ellipsize: false, justify: false, hint: :cyan) do |embed| embed.svg key: ':tool:', width: 28, height: 28, file: 'spanner.svg' diff --git a/samples/text_options.rb b/samples/text_options.rb index 85ae037..72f783d 100644 --- a/samples/text_options.rb +++ b/samples/text_options.rb @@ -23,12 +23,12 @@ Squib::Deck.new(width: 825, height: 1125, cards: 3) do text str: 'Font string sizes can be overridden per card.', x: 65, y: 350, font: 'Impact 36', font_size: [16, 20, 24] - text str: 'This text has fixed width, fixed height, center-aligned, middle-valigned, and has a red hint', + text str: 'This text has fixed width, fixed height, center-aligned, middle-valigned, has a red hint, and "smart quotes"', hint: :red, x: 65, y: 400, width: 300, height: 125, - align: :center, valign: 'MIDDLE', #case-insenstive strings allowed too. - font: 'Arial 18' + align: :center, valign: 'MIDDLE', # these can be specified with case-insenstive strings too + font: 'Serif 16', quotes: [:smart,:smart, :dumb] extents = text str: 'Ink extent return value', x: 65, y: 550, diff --git a/spec/args/smart_quotes_spec.rb b/spec/args/smart_quotes_spec.rb new file mode 100644 index 0000000..04ea087 --- /dev/null +++ b/spec/args/smart_quotes_spec.rb @@ -0,0 +1,46 @@ +require 'spec_helper' +require 'squib/args/smart_quotes' + +describe Squib::Args::SmartQuotes do + + it 'does nothing on a non-quoted string' do + expect(subject.quotify('nothing')).to eq('nothing') + end + + it 'left quotes at the beginning' do + expect(subject.quotify('"foo')).to eq("\u201Cfoo") + end + + it 'left quotes in the middle of the string' do + expect(subject.quotify('hello "foo')).to eq("hello \u201Cfoo") + end + + it 'right quotes at the end of a string' do + expect(subject.quotify('foo"')).to eq("foo\u201D") + end + + it 'handles the entire string quoted' do + expect(subject.quotify('"foo"')).to eq("\u201Cfoo\u201D") + end + + it "quotes in the middle of the string" do + expect(subject.quotify('hello "foo" world')).to eq("hello \u201Cfoo\u201D world") + end + + it "allows custom quotes for different character sets" do + expect(subject.quotify('hello "foo" world', %w({ }))).to eq("hello {foo} world") + end + + it "processes dumb quotes" do + expect(subject.process('hello "foo" world', :dumb)).to eq("hello \"foo\" world") + end + + it "processes smart quotes" do + expect(subject.process('hello "foo" world', :smart)).to eq("hello \u201Cfoo\u201D world") + end + + it "processes custom quotes" do + expect(subject.process('hello "foo" world', %w({ }))).to eq("hello {foo} world") + end + +end \ No newline at end of file diff --git a/spec/data/samples/text_options.rb.txt b/spec/data/samples/text_options.rb.txt index 002fc6e..e34bf98 100644 --- a/spec/data/samples/text_options.rb.txt +++ b/spec/data/samples/text_options.rb.txt @@ -331,7 +331,7 @@ cairo: translate([65, 400]) cairo: rotate([0]) cairo: move_to([0, 0]) pango: font_description=([MockDouble]) -pango: text=(["This text has fixed width, fixed height, center-aligned, middle-valigned, and has a red hint"]) +pango: text=(["This text has fixed width, fixed height, center-aligned, middle-valigned, has a red hint, and \u201Csmart quotes\u201D"]) pango: width=([307200]) pango: height=([128000]) pango: wrap=([#]) @@ -354,7 +354,7 @@ cairo: translate([65, 400]) cairo: rotate([0]) cairo: move_to([0, 0]) pango: font_description=([MockDouble]) -pango: text=(["This text has fixed width, fixed height, center-aligned, middle-valigned, and has a red hint"]) +pango: text=(["This text has fixed width, fixed height, center-aligned, middle-valigned, has a red hint, and \u201Csmart quotes\u201D"]) pango: width=([307200]) pango: height=([128000]) pango: wrap=([#]) @@ -377,7 +377,7 @@ cairo: translate([65, 400]) cairo: rotate([0]) cairo: move_to([0, 0]) pango: font_description=([MockDouble]) -pango: text=(["This text has fixed width, fixed height, center-aligned, middle-valigned, and has a red hint"]) +pango: text=(["This text has fixed width, fixed height, center-aligned, middle-valigned, has a red hint, and \"smart quotes\""]) pango: width=([307200]) pango: height=([128000]) pango: wrap=([#])