squib 0.4.0 → 0.5.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (52) hide show
  1. checksums.yaml +4 -4
  2. data/.gitignore +1 -0
  3. data/.travis.yml +5 -4
  4. data/CHANGELOG.md +12 -2
  5. data/Gemfile +2 -0
  6. data/README.md +177 -31
  7. data/Rakefile +1 -0
  8. data/lib/squib/api/shapes.rb +25 -0
  9. data/lib/squib/api/text.rb +9 -3
  10. data/lib/squib/api/text_embed.rb +64 -0
  11. data/lib/squib/args/typographer.rb +112 -0
  12. data/lib/squib/args/unit_conversion.rb +4 -0
  13. data/lib/squib/card.rb +2 -1
  14. data/lib/squib/constants.rb +32 -1
  15. data/lib/squib/deck.rb +6 -1
  16. data/lib/squib/graphics/cairo_context_wrapper.rb +9 -1
  17. data/lib/squib/graphics/save_doc.rb +1 -1
  18. data/lib/squib/graphics/shapes.rb +16 -0
  19. data/lib/squib/graphics/showcase.rb +2 -0
  20. data/lib/squib/graphics/text.rb +105 -23
  21. data/lib/squib/project_template/config.yml +13 -0
  22. data/lib/squib/version.rb +1 -1
  23. data/samples/config_disable_quotes.yml +3 -0
  24. data/samples/config_text_markup.rb +20 -0
  25. data/samples/config_text_markup.yml +9 -0
  26. data/samples/draw_shapes.rb +5 -0
  27. data/samples/embed_text.rb +90 -0
  28. data/samples/text_options.rb +21 -7
  29. data/spec/api/api_text_spec.rb +3 -3
  30. data/spec/args/typographer_spec.rb +71 -0
  31. data/spec/data/samples/autoscale_font.rb.txt +9 -9
  32. data/spec/data/samples/basic.rb.txt +12 -12
  33. data/spec/data/samples/config_text_markup.rb.txt +75 -0
  34. data/spec/data/samples/csv_import.rb.txt +12 -12
  35. data/spec/data/samples/custom_config.rb.txt +2 -6
  36. data/spec/data/samples/draw_shapes.rb.txt +11 -0
  37. data/spec/data/samples/embed_text.rb.txt +295 -0
  38. data/spec/data/samples/excel.rb.txt +18 -18
  39. data/spec/data/samples/gradients.rb.txt +2 -2
  40. data/spec/data/samples/hello_world.rb.txt +2 -2
  41. data/spec/data/samples/portrait-landscape.rb.txt +2 -2
  42. data/spec/data/samples/ranges.rb.txt +48 -48
  43. data/spec/data/samples/saves.rb.txt +32 -32
  44. data/spec/data/samples/showcase.rb.txt +8 -8
  45. data/spec/data/samples/text_options.rb.txt +162 -120
  46. data/spec/data/samples/tgc_proofs.rb.txt +4 -4
  47. data/spec/graphics/graphics_text_spec.rb +13 -11
  48. data/spec/samples/samples_regression_spec.rb +2 -0
  49. data/spec/spec_helper.rb +22 -5
  50. data/squib.gemspec +1 -1
  51. data/squib.sublime-project +49 -0
  52. metadata +17 -16
@@ -0,0 +1,64 @@
1
+ module Squib
2
+ class TextEmbed
3
+ # :nodoc:
4
+ # @api private
5
+ attr_reader :rules
6
+
7
+ # :nodoc:
8
+ # @api private
9
+ def initialize
10
+ @rules = {} # store an array of options for later usage
11
+ end
12
+
13
+ # Context object for embedding an svg icon within text
14
+ #
15
+ # @option opts key [String] ('*') the string to replace with the graphic. Can be multiple letters, e.g. ':tool:'
16
+ # @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}
17
+ # @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}
18
+ # @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}
19
+ # @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}
20
+ # @option opts width [Integer, :native] (:native) the width of the image rendered.
21
+ # @option opts height [Integer, :native] the height the height of the image rendered.
22
+ # @option opts dx [Integer] (0) "delta x", or adjust the icon horizontally by x pixels
23
+ # @option opts dy [Integer] (0) "delta y", or adjust the icon vertically by y pixels
24
+ # @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}
25
+ # @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}
26
+ # @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}
27
+ # @api public
28
+ def svg(opts)
29
+ opts = Squib::SYSTEM_DEFAULTS.merge(opts)
30
+ # TODO: add input validation here. We need the key for example.
31
+ rule = {type: :svg}.merge(opts)
32
+ rule[:draw] = Proc.new do |card, x,y|
33
+ card.svg(rule[:file], rule[:id], x, y, rule[:width], rule[:height],
34
+ rule[:alpha], rule[:blend], rule[:angle], rule[:mask])
35
+ end
36
+ @rules[opts[:key]] = rule
37
+ end
38
+
39
+ # Context object for embedding a png within text
40
+ #
41
+ # @option opts key [String] ('*') the string to replace with the graphic. Can be multiple letters, e.g. ':tool:'
42
+ # @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}
43
+ # @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}
44
+ # @option opts width [Integer, :native] (:native) the width of the image rendered
45
+ # @option opts height [Integer, :native] the height the height of the image rendered
46
+ # @option opts dx [Integer] (0) "delta x", or adjust the icon horizontally by x pixels
47
+ # @option opts dy [Integer] (0) "delta y", or adjust the icon vertically by y pixels
48
+ # @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}
49
+ # @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}
50
+ # @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}
51
+ # @api public
52
+ def png(opts)
53
+ opts = Squib::SYSTEM_DEFAULTS.merge(opts)
54
+ # TODO: add input validation here. We need the key for example.
55
+ rule = {type: :png}.merge(opts)
56
+ rule[:draw] = Proc.new do |card, x,y|
57
+ card.png(rule[:file], x, y, rule[:width], rule[:height],
58
+ rule[:alpha], rule[:blend], rule[:angle], rule[:mask])
59
+ end
60
+ @rules[opts[:key]] = rule
61
+ end
62
+
63
+ end
64
+ end
@@ -0,0 +1,112 @@
1
+ require 'squib/constants'
2
+ module Squib
3
+ module Args
4
+ class Typographer
5
+
6
+ def initialize(config = CONFIG_DEFAULTS)
7
+ @config = config
8
+ end
9
+
10
+ def process(str)
11
+ str = explicit_replacements(str)
12
+ str = smart_quotes(str) if @config['smart_quotes']
13
+ str
14
+ end
15
+
16
+ def explicit_replacements(str)
17
+ [ :left_curly, :right_curly, :apostraphize,
18
+ :ellipsificate, :em_dash, :en_dash ].each do |sym|
19
+ str = each_non_tag(str) do |token|
20
+ self.method(sym).call(token)
21
+ end
22
+ end
23
+ str
24
+ end
25
+
26
+ def smart_quotes(str)
27
+ [ :single_inside_double_quote,
28
+ :right_double_quote,
29
+ :left_double_quote,
30
+ :right_single_quote,
31
+ :left_single_quote].each do |sym|
32
+ str = each_non_tag(str) do |token|
33
+ self.method(sym).call(token)
34
+ end
35
+ end
36
+ str
37
+ end
38
+
39
+ # Iterate over each non-tag for processing
40
+ # Allows us to ignore anything inside < and >
41
+ def each_non_tag(str)
42
+ full_str = ''
43
+ tag_delimit = /(<(?:(?!<).)*>)/ # use non-capturing group w/ negative lookahead
44
+ str.split(tag_delimit).each do |token|
45
+ if token.start_with? '<'
46
+ full_str << token # don't process tags
47
+ else
48
+ full_str << yield(token)
49
+ end
50
+ end
51
+ return full_str
52
+ end
53
+
54
+ # Straightforward replace
55
+ def left_curly(str)
56
+ str.gsub('``', @config['ldquote'])
57
+ end
58
+
59
+ # Straightforward replace
60
+ def right_curly(str)
61
+ str.gsub(%{''}, @config['rdquote'])
62
+ end
63
+
64
+ # A quote between two letters is an apostraphe
65
+ def apostraphize(str)
66
+ str.gsub(/(\w)(\')(\w)/, '\1' + @config['rsquote'] + '\3')
67
+ end
68
+
69
+ # Straightforward replace
70
+ def ellipsificate(str)
71
+ str.gsub('...', @config['ellipsis'])
72
+ end
73
+
74
+ # Straightforward replace
75
+ def en_dash(str)
76
+ str.gsub('--', @config['en_dash'])
77
+ end
78
+
79
+ # Straightforward replace
80
+ def em_dash(str)
81
+ str.gsub('---', @config['em_dash'])
82
+ end
83
+
84
+ # Quote next to non-whitespace curls
85
+ def right_double_quote(str)
86
+ str.gsub(/(\S)(\")/, '\1' + @config['rdquote'])
87
+ end
88
+
89
+ # Quote next to non-whitespace curls
90
+ def left_double_quote(str)
91
+ str.gsub(/(\")(\S)/, @config['ldquote'] + '\2')
92
+ end
93
+
94
+ # Handle the cases where a double quote is next to a single quote
95
+ def single_inside_double_quote(str)
96
+ str.gsub(/(\")(\')(\S)/, @config['ldquote'] + @config['lsquote'] + '\3')
97
+ .gsub(/(\")(\')(\S)/, '\1' + @config['rsquote'] + @config['rdquote'])
98
+ end
99
+
100
+ # Quote next to non-whitespace curls
101
+ def right_single_quote(str)
102
+ str.gsub(/(\S)(\')/, '\1' + @config['rsquote'])
103
+ end
104
+
105
+ # Quote next to non-whitespace curls
106
+ def left_single_quote(str)
107
+ str.gsub(/(\')(\S)/, @config['lsquote'] + '\2')
108
+ end
109
+
110
+ end
111
+ end
112
+ end
@@ -2,8 +2,12 @@ require 'squib/constants'
2
2
 
3
3
  module Squib
4
4
  module Args
5
+ # :nodoc:
6
+ # @api private
5
7
  module UnitConversion
6
8
 
9
+ # :nodoc:
10
+ # @api private
7
11
  module_function
8
12
  def parse(arg, dpi=300)
9
13
  case arg.to_s.rstrip
data/lib/squib/card.rb CHANGED
@@ -5,7 +5,6 @@ require 'squib/graphics/cairo_context_wrapper'
5
5
  module Squib
6
6
  # Back end graphics. Private.
7
7
  class Card
8
- include Squib::InputHelpers
9
8
 
10
9
  # :nodoc:
11
10
  # @api private
@@ -28,6 +27,8 @@ module Squib
28
27
  @cairo_context.antialias = ANTIALIAS_OPTS[(@deck.antialias.downcase)] || 'subpixel'
29
28
  end
30
29
 
30
+ # :nodoc:
31
+ # @api private
31
32
  def make_surface(svgfile, backend)
32
33
  case backend
33
34
  when :memory
@@ -8,10 +8,16 @@ module Squib
8
8
  :angle => 0,
9
9
  :blend => :none,
10
10
  :color => :black,
11
- :columns => 1,
11
+ :columns => 5,
12
12
  :count_format => '%02d',
13
+ :cx1 => 0,
14
+ :cx2 => 0,
15
+ :cy1 => 0,
16
+ :cy2 => 0,
13
17
  :default_font => 'Arial 36',
14
18
  :dir => '_output',
19
+ :dx => 0, # delta
20
+ :dy => 0, # delta
15
21
  :ellipsize => :end,
16
22
  :face => :left,
17
23
  :fill_color => '#0000',
@@ -24,12 +30,14 @@ module Squib
24
30
  :hint => :off,
25
31
  :img_dir => '.',
26
32
  :justify => false,
33
+ :key => '*',
27
34
  :margin => 75,
28
35
  :markup => false,
29
36
  :mask => nil,
30
37
  :offset => 1.1,
31
38
  :prefix => 'card_',
32
39
  :progress_bar => false,
40
+ :quotes => :dumb,
33
41
  :reflect_offset => 15,
34
42
  :reflect_percent => 0.25,
35
43
  :reflect_strength => 0.2,
@@ -72,6 +80,14 @@ module Squib
72
80
  'hint' => :none,
73
81
  'img_dir' => '.',
74
82
  'progress_bar' => false,
83
+ 'ldquote' => "\u201C", # UTF8 chars
84
+ 'rdquote' => "\u201D",
85
+ 'lsquote' => "\u2018",
86
+ 'rsquote' => "\u2019",
87
+ 'em_dash' => "\u2014",
88
+ 'en_dash' => "\u2013",
89
+ 'ellipsis' => "\u2026",
90
+ 'smart_quotes' => true,
75
91
  }
76
92
 
77
93
  #Translate the hints to the methods.
@@ -79,6 +95,8 @@ module Squib
79
95
  'best' => 'subpixel',
80
96
  'good' => 'gray',
81
97
  'fast' => 'gray',
98
+ 'gray' => 'gray',
99
+ 'subpixel' => 'subpixel'
82
100
  }
83
101
  # These are parameters that are intended to be "expanded" across
84
102
  # range if they are singletons.
@@ -96,6 +114,12 @@ module Squib
96
114
  :blend => :blend,
97
115
  :circle_radius => :radius,
98
116
  :color => :color,
117
+ :cx1 => :cx1,
118
+ :cx2 => :cx2,
119
+ :cy1 => :cy1,
120
+ :cy2 => :cy2,
121
+ :dx => :dx,
122
+ :dy => :dy,
99
123
  :ellipsize => :ellipsize,
100
124
  :files => :file,
101
125
  :fill_color => :fill_color,
@@ -108,6 +132,7 @@ module Squib
108
132
  :layout => :layout,
109
133
  :markup => :markup,
110
134
  :mask => :mask,
135
+ :quotes => :quotes,
111
136
  :rect_radius => :radius,
112
137
  :spacing => :spacing,
113
138
  :str => :str,
@@ -138,6 +163,12 @@ module Squib
138
163
  # value: the user-facing API key (e.g. radius: '1in')
139
164
  UNIT_CONVERSION_PARAMS = {
140
165
  :circle_radius => :radius,
166
+ :cx1 => :cx1,
167
+ :cx2 => :cx2,
168
+ :cy1 => :cy1,
169
+ :cy2 => :cy2,
170
+ :dx => :dx, # delta
171
+ :dy => :dx, # delta
141
172
  :gap => :gap,
142
173
  :height => :height,
143
174
  :margin => :margin,
data/lib/squib/deck.rb CHANGED
@@ -34,7 +34,7 @@ module Squib
34
34
 
35
35
  # :nodoc:
36
36
  # @api private
37
- attr_reader :layout, :config
37
+ attr_reader :layout, :config, :quote_chars
38
38
 
39
39
  attr_reader :dir, :prefix, :count_format
40
40
 
@@ -70,6 +70,7 @@ module Squib
70
70
  @dir = SYSTEM_DEFAULTS[:dir]
71
71
  @prefix = SYSTEM_DEFAULTS[:prefix]
72
72
  @count_format = SYSTEM_DEFAULTS[:count_format]
73
+ @quote_chars = CONFIG_DEFAULTS.select {|k,v| %w(lsquote rsquote ldquote rdquote em_dash en_dash ellipsis smart_quotes).include?(k) }
73
74
  show_info(config, layout)
74
75
  load_config(config)
75
76
  @width = Args::UnitConversion.parse width, dpi
@@ -111,6 +112,10 @@ module Squib
111
112
  @prefix = config['prefix']
112
113
  @count_format = config['count_format']
113
114
  @antialias = config['antialias']
115
+ @quote_chars ||= {}
116
+ %w(lsquote rsquote ldquote rdquote smart_quotes em_dash en_dash ellipsis).each do |key|
117
+ @quote_chars[key] = config[key]
118
+ end
114
119
  end
115
120
  end
116
121
 
@@ -3,10 +3,16 @@ require 'squib/graphics/gradient_regex'
3
3
 
4
4
  module Squib
5
5
  module Graphics
6
+ # Wrapper class for the Cairo context. Private.
6
7
  class CairoContextWrapper
7
8
  extend Forwardable
9
+
10
+ # :nodoc:
11
+ # @api private
8
12
  attr_accessor :cairo_cxt
9
13
 
14
+ # :nodoc:
15
+ # @api private
10
16
  def initialize(cairo_cxt)
11
17
  @cairo_cxt = cairo_cxt
12
18
  end
@@ -16,8 +22,10 @@ module Squib
16
22
  :show_pango_layout, :rounded_rectangle, :set_line_width, :stroke, :fill,
17
23
  :set_source, :scale, :render_rsvg_handle, :circle, :triangle, :line_to,
18
24
  :operator=, :show_page, :clip, :transform, :mask, :create_pango_layout,
19
- :antialias=
25
+ :antialias=, :curve_to, :matrix, :matrix=, :identity_matrix
20
26
 
27
+ # :nodoc:
28
+ # @api private
21
29
  def set_source_squibcolor(arg)
22
30
  if match = arg.match(LINEAR_GRADIENT)
23
31
  x1, y1, x2, y2 = match.captures
@@ -67,7 +67,7 @@ module Squib
67
67
  # save_sheet prefix: 'sheet_', margin: 75, gap: 5, trim: 37
68
68
  #
69
69
  # @option opts [Enumerable] range (:all) the range of cards over which this will be rendered. See {file:README.md#Specifying_Ranges Specifying Ranges}
70
- # @option opts colulmns [Integer] (1) the number of columns in the grid
70
+ # @option opts colulmns [Integer] (5) the number of columns in the grid
71
71
  # @option opts rows [Integer] (:infinite) the number of rows in the grid. When set to :infinite, the sheet scales to the rows needed. If there are more cards than rows*columns, new sheets are started.
72
72
  # @option opts [String] prefix (card_) the prefix of the file name(s)
73
73
  # @option opts [String] count_format (%02d) the format string used for formatting the card count (e.g. padding zeros). Uses a Ruby format string (see the Ruby doc for Kernel::sprintf for specifics)
@@ -58,5 +58,21 @@ module Squib
58
58
  end
59
59
  end
60
60
 
61
+ # :nodoc:
62
+ # @api private
63
+ def curve(x1, y1, cx1, cy1, x2, y2, cx2, cy2, fill_color, stroke_color, stroke_width)
64
+ use_cairo do |cc|
65
+ cc.move_to(x1, y1)
66
+ cc.curve_to(cx1, cy1, cx2, cy2, x2, y2)
67
+ cc.set_line_width(stroke_width)
68
+ cc.set_source_squibcolor(stroke_color)
69
+ cc.stroke
70
+ cc.move_to(x1, y1)
71
+ cc.curve_to(cx1, cy1, cx2, cy2, x2, y2)
72
+ cc.set_source_squibcolor(fill_color)
73
+ cc.fill
74
+ end
75
+ end
76
+
61
77
  end
62
78
  end
@@ -60,6 +60,8 @@ module Squib
60
60
  return tmp_cc.target
61
61
  end
62
62
 
63
+ # :nodoc:
64
+ # @api private
63
65
  def perspective(src, scale, face_right)
64
66
  dest_cxt = Cairo::Context.new(Cairo::ImageSurface.new(src.width * scale, src.height))
65
67
  in_thickness = 1 # Take strip 1 pixel-width at a time
@@ -1,4 +1,5 @@
1
1
  require 'pango'
2
+ require 'squib/args/typographer'
2
3
 
3
4
  module Squib
4
5
  class Card
@@ -6,14 +7,14 @@ module Squib
6
7
  # :nodoc:
7
8
  # @api private
8
9
  def draw_text_hint(cc,x,y,layout, color,angle)
9
- color = @deck.text_hint if color.to_s.eql? 'off' and not @deck.text_hint.to_s.eql? 'off'
10
+ color = @deck.text_hint if color.eql? 'off' and not @deck.text_hint.to_s.eql? 'off'
10
11
  return if color.to_s.eql? 'off' or color.nil?
11
12
  # when w,h < 0, it was never set. extents[1] are ink extents
12
13
  w = layout.width / Pango::SCALE
13
14
  w = layout.extents[1].width / Pango::SCALE if w < 0
14
15
  h = layout.height / Pango::SCALE
15
16
  h = layout.extents[1].height / Pango::SCALE if h < 0
16
- cc.rounded_rectangle(x,y,w,h,0,0)
17
+ cc.rounded_rectangle(0, 0, w, h, 0, 0)
17
18
  cc.set_source_color(color)
18
19
  cc.set_line_width(2.0)
19
20
  cc.stroke
@@ -62,57 +63,138 @@ module Squib
62
63
 
63
64
  # :nodoc:
64
65
  # @api private
65
- def valign!(cc, layout, x, y, valign)
66
- if layout.height > 0
67
- ink_extents = layout.extents[1]
68
- case valign.to_s
69
- when 'middle'
70
- cc.move_to(x, y + (layout.height - ink_extents.height) / (2 * Pango::SCALE))
71
- when 'bottom'
72
- cc.move_to(x, y + (layout.height - ink_extents.height) / Pango::SCALE)
73
- end
66
+ def compute_valign(layout, valign)
67
+ return 0 unless layout.height > 0
68
+ ink_extents = layout.extents[1]
69
+ case valign.to_s.downcase
70
+ when 'middle'
71
+ Pango.pixels( (layout.height - ink_extents.height) / 2)
72
+ when 'bottom'
73
+ Pango.pixels(layout.height - ink_extents.height)
74
+ else
75
+ 0
74
76
  end
75
77
  end
76
78
 
79
+ def set_font_rendering_opts!(layout)
80
+ font_options = Cairo::FontOptions.new
81
+ font_options.antialias = ANTIALIAS_OPTS[(@deck.antialias.downcase)] || 'gray'
82
+ font_options.hint_metrics = 'on' # TODO make this configurable
83
+ font_options.hint_style = 'full' # TODO make this configurable
84
+ layout.context.font_options = font_options
85
+ end
86
+
77
87
  # :nodoc:
78
88
  # @api private
79
89
  def set_wh!(layout, width, height)
80
- layout.width = width * Pango::SCALE unless width.nil? || width == :native
90
+ layout.width = width * Pango::SCALE unless width.nil? || width == :native
81
91
  layout.height = height * Pango::SCALE unless height.nil? || height == :native
82
- layout
83
92
  end
84
93
 
85
94
  # :nodoc:
86
95
  # @api private
87
- def text(str, font, font_size, color,
96
+ def next_embed(keys, str)
97
+ ret = nil
98
+ ret_key = nil
99
+ keys.each do |key|
100
+ i = str.index(key)
101
+ ret ||= i
102
+ unless i.nil? || i > ret
103
+ ret = i
104
+ ret_key = key
105
+ end
106
+ end
107
+ ret_key
108
+ end
109
+
110
+ # When we do embedded icons, we create a 3-character string that has a font
111
+ # size of zero and a letter-spacing that fits the icon we need. That gives
112
+ # us the wrapping behavior we need but no clutter beneath. On most
113
+ # platforms, this works fine. On Linux, this creates
114
+ # a Cairo transformation matrix that
115
+ ZERO_WIDTH_CHAR_SIZE = 0 # this works on most platforms
116
+ # some platforms make this Pango pixels (1/1024), others a 1 pt font
117
+ ZERO_WIDTH_CHAR_SIZE = 1 if RbConfig::CONFIG['host_os'] === 'linux-gnu'
118
+
119
+ # :nodoc:
120
+ # @api private
121
+ def process_embeds(embed, str, layout)
122
+ return [] unless embed.rules.any?
123
+ layout.markup = str
124
+ clean_str = layout.text
125
+ draw_calls = []
126
+ searches = []
127
+ while (key = next_embed(embed.rules.keys, clean_str)) != nil
128
+ rule = embed.rules[key]
129
+ spacing = rule[:width] * Pango::SCALE
130
+ index = clean_str.index(key)
131
+ index = clean_str[0..index].bytesize #convert to byte index (bug #57)
132
+ str = str.sub(key, "<span size=\"#{ZERO_WIDTH_CHAR_SIZE}\">a<span letter_spacing=\"#{spacing.to_i}\">a</span>a</span>")
133
+ layout.markup = str
134
+ clean_str = layout.text
135
+ searches << { index: index, rule: rule }
136
+ end
137
+ searches.each do |search|
138
+ rect = layout.index_to_pos(search[:index])
139
+ x = Pango.pixels(rect.x) + search[:rule][:dx]
140
+ y = Pango.pixels(rect.y) + search[:rule][:dy]
141
+ draw_calls << {x: x, y: y, draw: search[:rule][:draw]} # defer drawing until we've valigned
142
+ end
143
+ return draw_calls
144
+ end
145
+
146
+ # :nodoc:
147
+ # @api private
148
+ def text(embed,str, font, font_size, color,
88
149
  x, y, width, height,
89
150
  markup, justify, wrap, ellipsize,
90
151
  spacing, align, valign, hint, angle)
91
152
  Squib.logger.debug {"Placing '#{str}'' with font '#{font}' @ #{x}, #{y}, color: #{color}, angle: #{angle} etc."}
92
153
  extents = nil
154
+ str = str.to_s
93
155
  use_cairo do |cc|
94
156
  cc.set_source_squibcolor(color)
95
157
  cc.translate(x,y)
96
158
  cc.rotate(angle)
97
- cc.translate(-1*x,-1*y)
98
- cc.move_to(x,y)
159
+ cc.move_to(0, 0)
99
160
 
100
- layout = cc.create_pango_layout
101
- font_desc = Pango::FontDescription.new(font)
161
+ font_desc = Pango::FontDescription.new(font)
102
162
  font_desc.size = font_size * Pango::SCALE unless font_size.nil?
163
+ layout = cc.create_pango_layout
103
164
  layout.font_description = font_desc
104
- layout.text = str.to_s
105
- layout.markup = str.to_s if markup
165
+ layout.text = str
166
+ if markup
167
+ str = Args::Typographer.new(@deck.quote_chars).process(layout.text)
168
+ layout.markup = str
169
+ end
170
+
171
+ set_font_rendering_opts!(layout)
106
172
  set_wh!(layout, width, height)
107
173
  set_wrap!(layout, wrap)
108
174
  set_ellipsize!(layout, ellipsize)
109
175
  set_align!(layout, align)
176
+
110
177
  layout.justify = justify unless justify.nil?
111
178
  layout.spacing = spacing * Pango::SCALE unless spacing.nil?
112
179
  cc.update_pango_layout(layout)
113
- valign!(cc, layout, x, y, valign)
114
- cc.update_pango_layout(layout) ; cc.show_pango_layout(layout)
115
- draw_text_hint(cc,x,y,layout,hint,angle)
180
+
181
+ embed_draws = process_embeds(embed, str, layout)
182
+
183
+ vertical_start = compute_valign(layout, valign)
184
+ cc.move_to(0, vertical_start)
185
+
186
+ cc.update_pango_layout(layout)
187
+ cc.show_pango_layout(layout)
188
+ begin
189
+ embed_draws.each { |ed| ed[:draw].call(self, ed[:x], ed[:y] + vertical_start) }
190
+ rescue Exception => e
191
+ puts "====EXCEPTION!===="
192
+ puts e
193
+ puts "If this was a non-invertible matrix error, this is a known issue with a potential workaround. Please report it at: https://github.com/andymeneely/squib/issues/55"
194
+ puts "=================="
195
+ raise e
196
+ end
197
+ draw_text_hint(cc, x, y, layout, hint, angle)
116
198
  extents = { width: layout.extents[1].width / Pango::SCALE,
117
199
  height: layout.extents[1].height / Pango::SCALE }
118
200
  end