squib 0.10.0 → 0.11.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (90) hide show
  1. checksums.yaml +4 -4
  2. data/.travis.yml +6 -0
  3. data/CHANGELOG.md +24 -3
  4. data/README.md +4 -3
  5. data/RELEASE TODO.md +3 -1
  6. data/docs/_static/css/squibdocs.css +5 -0
  7. data/docs/conf.py +2 -2
  8. data/docs/config.rst +5 -0
  9. data/docs/dsl/ellipse.rst +6 -0
  10. data/docs/dsl/mm.rst +19 -0
  11. data/docs/dsl/rect.rst +4 -0
  12. data/docs/dsl/text.rst +2 -2
  13. data/docs/dsl/triangle.rst +20 -8
  14. data/docs/guides/game_icons.rst +4 -0
  15. data/docs/layouts.rst +55 -0
  16. data/docs/units.rst +1 -1
  17. data/lib/squib/api/background.rb +1 -1
  18. data/lib/squib/api/data.rb +4 -4
  19. data/lib/squib/api/groups.rb +4 -4
  20. data/lib/squib/api/image.rb +2 -2
  21. data/lib/squib/api/save.rb +6 -6
  22. data/lib/squib/api/settings.rb +4 -4
  23. data/lib/squib/api/shapes.rb +13 -11
  24. data/lib/squib/api/text.rb +1 -1
  25. data/lib/squib/api/text_embed.rb +4 -29
  26. data/lib/squib/api/units.rb +7 -2
  27. data/lib/squib/args/arg_loader.rb +2 -14
  28. data/lib/squib/args/draw.rb +1 -1
  29. data/lib/squib/args/hand_special.rb +37 -37
  30. data/lib/squib/args/unit_conversion.rb +2 -0
  31. data/lib/squib/deck.rb +1 -1
  32. data/lib/squib/graphics/embedding_utils.rb +28 -0
  33. data/lib/squib/graphics/shapes.rb +6 -3
  34. data/lib/squib/graphics/text.rb +49 -52
  35. data/lib/squib/layout_parser.rb +28 -9
  36. data/lib/squib/layouts/economy.yml +85 -85
  37. data/lib/squib/layouts/fantasy.yml +101 -101
  38. data/lib/squib/layouts/hand.yml +62 -62
  39. data/lib/squib/layouts/playing-card.yml +35 -35
  40. data/lib/squib/layouts/tuck_box.yml +45 -45
  41. data/lib/squib/version.rb +1 -1
  42. data/samples/custom-config.yml +5 -5
  43. data/samples/custom_config.rb +18 -18
  44. data/samples/embed_text.rb +27 -9
  45. data/samples/layouts/_output/.gitignore +2 -0
  46. data/samples/{layouts_builtin.rb → layouts/builtin_layouts.rb} +74 -51
  47. data/samples/{custom-layout.yml → layouts/custom-layout.yml} +0 -0
  48. data/samples/{custom-layout2.yml → layouts/custom-layout2.yml} +0 -0
  49. data/samples/layouts/expected_layouts_builtin_economy_00.png +0 -0
  50. data/samples/layouts/expected_layouts_builtin_fantasy_00.png +0 -0
  51. data/samples/layouts/expected_layouts_builtin_hand_00.png +0 -0
  52. data/samples/layouts/expected_layouts_builtin_playing_card_00.png +0 -0
  53. data/samples/layouts/expected_layouts_builtin_tuck_box_00.png +0 -0
  54. data/samples/{layouts.rb → layouts/layouts.rb} +0 -1
  55. data/samples/layouts/shiny-purse.png +0 -0
  56. data/samples/layouts/spanner.svg +91 -0
  57. data/samples/offset.svg +71 -71
  58. data/samples/shapes/_draw_shapes.rb +8 -2
  59. data/spec/api/api_units_spec.rb +37 -0
  60. data/spec/args/embed_key_spec.rb +13 -13
  61. data/spec/args/showcase_special_spec.rb +15 -15
  62. data/spec/args/unit_conversion_spec.rb +8 -1
  63. data/spec/data/csv/qty.csv +2 -2
  64. data/spec/data/csv/qty_named.csv +2 -2
  65. data/spec/data/csv/with_spaces.csv +2 -2
  66. data/spec/data/layouts/extends-units-mixed.yml +8 -0
  67. data/spec/data/layouts/extends-units.yml +8 -0
  68. data/spec/data/samples/autoscale_font/_autoscale_font.rb.txt +9 -0
  69. data/spec/data/samples/basic.rb.txt +18 -0
  70. data/spec/data/samples/cairo_access.rb.txt +6 -0
  71. data/spec/data/samples/colors/_gradients.rb.txt +6 -0
  72. data/spec/data/samples/config_text_markup.rb.txt +72 -72
  73. data/spec/data/samples/data/_csv.rb.txt +12 -0
  74. data/spec/data/samples/data/_excel.rb.txt +30 -0
  75. data/spec/data/samples/embed_text.rb.txt +70 -5
  76. data/spec/data/samples/hello_world.rb.txt +36 -36
  77. data/spec/data/samples/images/_more_load_images.rb.txt +3 -0
  78. data/spec/data/samples/ranges.rb.txt +9 -0
  79. data/spec/data/samples/saves/_hand.rb.txt +48 -0
  80. data/spec/data/samples/saves/_saves.rb.txt +48 -0
  81. data/spec/data/samples/saves/_showcase.rb.txt +12 -0
  82. data/spec/data/samples/shapes/_draw_shapes.rb.txt +41 -0
  83. data/spec/data/samples/text_options.rb.txt +1155 -1125
  84. data/spec/data/samples/tgc_proofs.rb.txt +6 -0
  85. data/spec/data/samples/units.rb.txt +9 -0
  86. data/spec/graphics/embedding_utils_spec.rb +73 -0
  87. data/spec/layout_parser_spec.rb +42 -17
  88. data/spec/spec_helper.rb +5 -1
  89. data/squib.gemspec +5 -3
  90. metadata +61 -15
@@ -1,20 +1,20 @@
1
1
  module Squib
2
2
  class Deck
3
3
 
4
- # DSL method. See http://squib.readthedocs.org
4
+ # DSL method. See http://squib.readthedocs.io
5
5
  def hint(text: :off)
6
6
  conf.text_hint = text
7
7
  end
8
8
 
9
- # DSL method. See http://squib.readthedocs.org
9
+ # DSL method. See http://squib.readthedocs.io
10
10
  def set(opts = {})
11
11
  raise 'DEPRECATED: As of v0.7 img_dir is no longer supported in "set". Use config.yml instead.' if opts.key? :img_dir
12
12
  @font = (opts[:font] == :default) ? Squib::DEFAULT_FONT : opts[:font]
13
13
  end
14
14
 
15
- # DSL method. See http://squib.readthedocs.org
15
+ # DSL method. See http://squib.readthedocs.io
16
16
  def use_layout(file: 'layout.yml')
17
- @layout = LayoutParser.load_layout(file, @layout)
17
+ @layout = LayoutParser.new(@dpi).load_layout(file, @layout)
18
18
  end
19
19
 
20
20
  end
@@ -7,15 +7,16 @@ require_relative '../args/coords'
7
7
  module Squib
8
8
  class Deck
9
9
 
10
- # DSL method. See http://squib.readthedocs.org
10
+ # DSL method. See http://squib.readthedocs.io
11
11
  def rect(opts = {})
12
12
  range = Args::CardRange.new(opts[:range], deck_size: size)
13
13
  box = Args::Box.new(self).load!(opts, expand_by: size, layout: layout, dpi: dpi)
14
14
  draw = Args::Draw.new(custom_colors).load!(opts, expand_by: size, layout: layout, dpi: dpi)
15
- range.each { |i| @cards[i].rect(box[i], draw[i]) }
15
+ trans = Args::Transform.new.load!(opts, expand_by: size, layout: layout, dpi: dpi)
16
+ range.each { |i| @cards[i].rect(box[i], draw[i], trans[i]) }
16
17
  end
17
18
 
18
- # DSL method. See http://squib.readthedocs.org
19
+ # DSL method. See http://squib.readthedocs.io
19
20
  def circle(opts = {})
20
21
  range = Args::CardRange.new(opts[:range], deck_size: size)
21
22
  coords = Args::Coords.new.load!(opts, expand_by: size, layout: layout, dpi: dpi)
@@ -23,15 +24,16 @@ module Squib
23
24
  range.each { |i| @cards[i].circle(coords[i], draw[i]) }
24
25
  end
25
26
 
26
- # DSL method. See http://squib.readthedocs.org
27
+ # DSL method. See http://squib.readthedocs.io
27
28
  def ellipse(opts = {})
28
29
  range = Args::CardRange.new(opts[:range], deck_size: size)
29
30
  draw = Args::Draw.new(custom_colors).load!(opts, expand_by: size, layout: layout, dpi: dpi)
30
31
  box = Args::Box.new(self, { width: '0.25in', height: '0.25in' }).load!(opts, expand_by: size, layout: layout, dpi: dpi)
31
- range.each { |i| @cards[i].ellipse(box[i], draw[i]) }
32
+ trans = Args::Transform.new.load!(opts, expand_by: size, layout: layout, dpi: dpi)
33
+ range.each { |i| @cards[i].ellipse(box[i], draw[i], trans[i]) }
32
34
  end
33
35
 
34
- # DSL method. See http://squib.readthedocs.org
36
+ # DSL method. See http://squib.readthedocs.io
35
37
  def grid(opts = {})
36
38
  range = Args::CardRange.new(opts[:range], deck_size: size)
37
39
  draw = Args::Draw.new(custom_colors).load!(opts, expand_by: size, layout: layout, dpi: dpi)
@@ -39,7 +41,7 @@ module Squib
39
41
  range.each { |i| @cards[i].grid(box[i], draw[i]) }
40
42
  end
41
43
 
42
- # DSL method. See http://squib.readthedocs.org
44
+ # DSL method. See http://squib.readthedocs.io
43
45
  def triangle(opts = {})
44
46
  range = Args::CardRange.new(opts[:range], deck_size: size)
45
47
  draw = Args::Draw.new(custom_colors).load!(opts, expand_by: size, layout: layout, dpi: dpi)
@@ -47,7 +49,7 @@ module Squib
47
49
  range.each { |i| @cards[i].triangle(coords[i], draw[i]) }
48
50
  end
49
51
 
50
- # DSL method. See http://squib.readthedocs.org
52
+ # DSL method. See http://squib.readthedocs.io
51
53
  def line(opts = {})
52
54
  range = Args::CardRange.new(opts[:range], deck_size: size)
53
55
  draw = Args::Draw.new(custom_colors).load!(opts, expand_by: size, layout: layout, dpi: dpi)
@@ -55,7 +57,7 @@ module Squib
55
57
  range.each { |i| @cards[i].line(coords[i], draw[i]) }
56
58
  end
57
59
 
58
- # DSL method. See http://squib.readthedocs.org
60
+ # DSL method. See http://squib.readthedocs.io
59
61
  def curve(opts = {})
60
62
  range = Args::CardRange.new(opts[:range], deck_size: size)
61
63
  draw = Args::Draw.new(custom_colors).load!(opts, expand_by: size, layout: layout, dpi: dpi)
@@ -63,7 +65,7 @@ module Squib
63
65
  range.each { |i| @cards[i].curve(coords[i], draw[i]) }
64
66
  end
65
67
 
66
- # DSL method. See http://squib.readthedocs.org
68
+ # DSL method. See http://squib.readthedocs.io
67
69
  def star(opts = {})
68
70
  range = Args::CardRange.new(opts[:range], deck_size: size)
69
71
  draw = Args::Draw.new(custom_colors).load!(opts, expand_by: size, layout: layout, dpi: dpi)
@@ -72,7 +74,7 @@ module Squib
72
74
  range.each { |i| @cards[i].star(coords[i], trans[i], draw[i]) }
73
75
  end
74
76
 
75
- # DSL method. See http://squib.readthedocs.org
77
+ # DSL method. See http://squib.readthedocs.io
76
78
  def polygon(opts = {})
77
79
  range = Args::CardRange.new(opts[:range], deck_size: size)
78
80
  draw = Args::Draw.new(custom_colors).load!(opts, expand_by: size, layout: layout, dpi: dpi)
@@ -7,7 +7,7 @@ require_relative '../args/paragraph'
7
7
  module Squib
8
8
  class Deck
9
9
 
10
- # DSL method. See http://squib.readthedocs.org
10
+ # DSL method. See http://squib.readthedocs.io
11
11
  def text(opts = {})
12
12
  range = Args::CardRange.new(opts[:range], deck_size: size)
13
13
  para = Args::Paragraph.new(font).load!(opts, expand_by: size, layout: layout)
@@ -23,21 +23,7 @@ module Squib
23
23
  @rules = {} # store an array of options for later usage
24
24
  end
25
25
 
26
- # Context object for embedding an svg icon within text
27
- #
28
- # @option opts key [String] ('*') the string to replace with the graphic. Can be multiple letters, e.g. ':tool:'
29
- # @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}
30
- # @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}
31
- # @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}
32
- # @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}
33
- # @option opts width [Integer] (:native) the width of the image rendered.
34
- # @option opts height [Integer] (:native) the height the height of the image rendered.
35
- # @option opts dx [Integer] (0) "delta x", or adjust the icon horizontally by x pixels
36
- # @option opts dy [Integer] (0) "delta y", or adjust the icon vertically by y pixels
37
- # @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}
38
- # @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}
39
- # @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}
40
- # @api public
26
+ # DSL method. See http://squib.readthedocs.io
41
27
  def svg(opts = {})
42
28
  key = Args::EmbedKey.new.validate_key(opts[:key])
43
29
  range = Args::CardRange.new(opts[:range], deck_size: @deck_size)
@@ -47,7 +33,8 @@ module Squib
47
33
  trans = Args::Transform.new.load!(opts, expand_by: @deck_size, layout: @layout, dpi: @dpi)
48
34
  ifile = Args::InputFile.new.load!(opts, expand_by: @deck_size, layout: @layout, dpi: @dpi)
49
35
  svg_args = Args::SvgSpecial.new.load!(opts, expand_by: @deck_size, layout: @layout, dpi: @dpi)
50
- rule = { type: :png, file: ifile, box: box, paint: paint, trans: trans, adjust: adjust }
36
+ rule = { type: :svg, file: ifile, box: box, paint: paint, trans: trans,
37
+ adjust: adjust, svg_args: svg_args }
51
38
  rule[:draw] = Proc.new do |card, x, y|
52
39
  i = card.index
53
40
  b = box[i]
@@ -59,19 +46,7 @@ module Squib
59
46
  @rules[key] = rule
60
47
  end
61
48
 
62
- # Context object for embedding a png within text
63
- #
64
- # @option opts key [String] ('*') the string to replace with the graphic. Can be multiple letters, e.g. ':tool:'
65
- # @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}
66
- # @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}
67
- # @option opts width [Fixnum] (:native) the width of the image rendered
68
- # @option opts height [Fixnum] (:native) the height of the image rendered
69
- # @option opts dx [Integer] (0) "delta x", or adjust the icon horizontally by x pixels
70
- # @option opts dy [Integer] (0) "delta y", or adjust the icon vertically by y pixels
71
- # @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}
72
- # @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}
73
- # @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}
74
- # @api public
49
+ # DSL method. See http://squib.readthedocs.io
75
50
  def png(opts = {})
76
51
  key = Args::EmbedKey.new.validate_key(opts[:key])
77
52
  range = Args::CardRange.new(opts[:range], deck_size: @deck_size)
@@ -3,15 +3,20 @@ require_relative '../constants'
3
3
  module Squib
4
4
  class Deck
5
5
 
6
- # DSL method. See http://squib.readthedocs.org
6
+ # DSL method. See http://squib.readthedocs.io
7
7
  def inches(n)
8
8
  @dpi * n.to_f
9
9
  end
10
10
 
11
- # DSL method. See http://squib.readthedocs.org
11
+ # DSL method. See http://squib.readthedocs.io
12
12
  def cm(n)
13
13
  @dpi * Squib::INCHES_IN_CM * n.to_f
14
14
  end
15
15
 
16
+ # DSL method. See http://squib.readthedocs.io
17
+ def mm(n)
18
+ @dpi * Squib::INCHES_IN_CM * n.to_f / 10.0
19
+ end
20
+
16
21
  end
17
22
  end
@@ -111,27 +111,15 @@ module Squib
111
111
  p_str = "@#{p}"
112
112
  p_val = instance_variable_get(p_str)
113
113
  if p_val.respond_to? :each
114
- arr = p_val.map { |x| convert_unit(x, dpi) }
114
+ arr = p_val.map { |x| UnitConversion.parse(x, dpi) }
115
115
  instance_variable_set p_str, arr
116
116
  else
117
- instance_variable_set p_str, convert_unit(p_val, dpi)
117
+ instance_variable_set p_str, UnitConversion.parse(p_val, dpi)
118
118
  end
119
119
  end
120
120
  self
121
121
  end
122
122
 
123
- def convert_unit(arg, dpi)
124
- case arg.to_s.rstrip
125
- when /in$/ # ends with "in"
126
- arg.rstrip[0..-2].to_f * dpi
127
- when /cm$/ # ends with "cm"
128
- arg.rstrip[0..-2].to_f * dpi * INCHES_IN_CM
129
- else
130
- arg
131
- end
132
- end
133
- module_function :convert_unit
134
-
135
123
  end
136
124
 
137
125
  end
@@ -59,7 +59,7 @@ module Squib
59
59
 
60
60
  def validate_dash(arg, _i)
61
61
  arg.to_s.split.collect do |x|
62
- convert_unit(x, @dpi).to_f
62
+ UnitConversion.parse(x, @dpi).to_f
63
63
  end
64
64
  end
65
65
 
@@ -1,37 +1,37 @@
1
- require 'cairo'
2
-
3
- module Squib
4
- # @api private
5
- module Args
6
-
7
- class HandSpecial
8
- include ArgLoader
9
-
10
- def initialize(card_height)
11
- @card_height = card_height
12
- end
13
-
14
- def self.parameters
15
- {
16
- angle_range: (Math::PI / -4.0)..(Math::PI / 4),
17
- radius: :auto
18
- }
19
- end
20
-
21
- def self.expanding_parameters
22
- [] # none of them
23
- end
24
-
25
- def self.params_with_units
26
- [ :radius ]
27
- end
28
-
29
- def validate_radius(arg)
30
- return 0.3 * @card_height if arg.to_s.downcase.strip == 'auto'
31
- arg
32
- end
33
-
34
- end
35
-
36
- end
37
- end
1
+ require 'cairo'
2
+
3
+ module Squib
4
+ # @api private
5
+ module Args
6
+
7
+ class HandSpecial
8
+ include ArgLoader
9
+
10
+ def initialize(card_height)
11
+ @card_height = card_height
12
+ end
13
+
14
+ def self.parameters
15
+ {
16
+ angle_range: (Math::PI / -4.0)..(Math::PI / 4),
17
+ radius: :auto
18
+ }
19
+ end
20
+
21
+ def self.expanding_parameters
22
+ [] # none of them
23
+ end
24
+
25
+ def self.params_with_units
26
+ [ :radius ]
27
+ end
28
+
29
+ def validate_radius(arg)
30
+ return 0.3 * @card_height if arg.to_s.downcase.strip == 'auto'
31
+ arg
32
+ end
33
+
34
+ end
35
+
36
+ end
37
+ end
@@ -15,6 +15,8 @@ module Squib
15
15
  arg.rstrip[0..-2].to_f * dpi
16
16
  when /cm$/ # ends with "cm"
17
17
  arg.rstrip[0..-2].to_f * dpi * INCHES_IN_CM
18
+ when /mm$/ # ends with "mm"
19
+ arg.rstrip[0..-2].to_f * dpi * INCHES_IN_CM / 10.0
18
20
  else
19
21
  arg
20
22
  end
data/lib/squib/deck.rb CHANGED
@@ -68,7 +68,7 @@ module Squib
68
68
  @width = Args::UnitConversion.parse width, dpi
69
69
  @height = Args::UnitConversion.parse height, dpi
70
70
  cards.times{ |i| @cards << Squib::Card.new(self, @width, @height, i) }
71
- @layout = LayoutParser.load_layout(layout)
71
+ @layout = LayoutParser.new(dpi).load_layout(layout)
72
72
  enable_groups_from_env!
73
73
  if block_given?
74
74
  instance_eval(&block) # here we go. wheeeee!
@@ -0,0 +1,28 @@
1
+ module Squib
2
+ class EmbeddingUtils
3
+
4
+ # Given a string and a bunch of keys, give us back a mapping of those keys
5
+ # to where those keys start, and where they end (in ranges)
6
+ #
7
+ # See the spec for expected outputs
8
+ def self.indices(str, keys)
9
+ map = {}
10
+ keys.each do |key|
11
+ map[key] ||= []
12
+ start = 0
13
+ while true
14
+ idx = str.index(key, start)
15
+ if idx.nil?
16
+ break; # done searching
17
+ else
18
+ idx_bytes = str[0..idx].bytesize - 1
19
+ map[key] << (idx_bytes..(idx_bytes + key.size))
20
+ start = idx + 1
21
+ end
22
+ end
23
+ end
24
+ return map
25
+ end
26
+
27
+ end
28
+ end
@@ -4,9 +4,11 @@ module Squib
4
4
 
5
5
  # :nodoc:
6
6
  # @api private
7
- def rect(box, draw)
7
+ def rect(box, draw, trans)
8
8
  use_cairo do |cc|
9
- cc.rounded_rectangle(box.x, box.y, box.width, box.height, box.x_radius, box.y_radius)
9
+ cc.rotate_about(box.x, box.y, trans.angle)
10
+ cc.rounded_rectangle(box.x, box.y, box.width, box.height,
11
+ box.x_radius, box.y_radius)
10
12
  cc.fill_n_stroke(draw)
11
13
  end
12
14
  end
@@ -27,9 +29,10 @@ module Squib
27
29
  # of the rectangle. Control points are at 1/4 and 3/4 of the side.
28
30
  # :nodoc:
29
31
  # @api private
30
- def ellipse(box, draw)
32
+ def ellipse(box, draw, trans)
31
33
  x, y, w, h = box.x, box.y, box.width, box.height
32
34
  use_cairo do |cc|
35
+ cc.rotate_about(box.x, box.y, trans.angle)
33
36
  cc.move_to(x, y + 0.5 * h) # start west
34
37
  cc.curve_to(x, y + 0.25 * h, # west to north
35
38
  x + 0.25 * w, y,
@@ -1,5 +1,6 @@
1
1
  require 'pango'
2
2
  require_relative '../args/typographer'
3
+ require_relative 'embedding_utils'
3
4
 
4
5
  module Squib
5
6
  class Card
@@ -51,55 +52,59 @@ module Squib
51
52
  layout.height = height * Pango::SCALE unless height.nil? || height == :auto
52
53
  end
53
54
 
54
- def max_embed_height(embed_draws)
55
- embed_draws.inject(0) do |max, ed|
56
- ed[:h] > max ? ed[:h] : max
57
- end
58
- end
59
-
60
- # :nodoc:
61
- # @api private
62
- def next_embed(keys, str)
63
- ret = nil
64
- ret_key = nil
65
- keys.each do |key|
66
- i = str.index(key)
67
- ret ||= i
68
- unless i.nil? || i > ret
69
- ret = i
70
- ret_key = key
55
+ # Compute the width of the carve that we need
56
+ def compute_carve(rule, range)
57
+ w = rule[:box].width[@index]
58
+ if w == :native
59
+ file = rule[:file][@index].file
60
+ case rule[:type]
61
+ when :png
62
+ Squib.cache_load_image(file).width.to_f / (range.size - 1)
63
+ when :svg
64
+ svg_data = rule[:svg_args].data[@index]
65
+ unless file.to_s.empty? || svg_data.to_s.empty?
66
+ Squib.logger.warn 'Both an SVG file and SVG data were specified'
67
+ end
68
+ return 0 if (file.nil? or file.eql? '') and svg_data.nil?
69
+ svg_data = File.read(file) if svg_data.to_s.empty?
70
+ RSVG::Handle.new_from_data(svg_data).width
71
71
  end
72
+ else
73
+ rule[:box].width[@index] * Pango::SCALE / (range.size - 1)
72
74
  end
73
- ret_key
74
75
  end
75
76
 
76
- # :nodoc:
77
- # @api private
78
- def process_embeds(embed, str, layout)
77
+ # # :nodoc:
78
+ # # @api private
79
+ def embed_images!(embed, str, layout, valign)
79
80
  return [] unless embed.rules.any?
80
81
  layout.markup = str
81
82
  clean_str = layout.text
82
- draw_calls = []
83
- searches = []
84
- while (key = next_embed(embed.rules.keys, clean_str)) != nil
85
- rule = embed.rules[key]
86
- spacing = rule[:box].width[@index] * Pango::SCALE
87
- kindex = clean_str.index(key)
88
- kindex = clean_str[0..kindex].bytesize # byte index (bug #57)
89
- str = str.sub(key, "\u0091<span letter_spacing=\"#{spacing.to_i}\">\u0091</span>\u0091")
90
- layout.markup = str
91
- clean_str = layout.text
92
- searches << { index: kindex, rule: rule }
83
+ attrs = layout.attributes || Pango::AttrList.new
84
+ EmbeddingUtils.indices(clean_str, embed.rules.keys).each do |key, ranges|
85
+ rule = embed.rules[key]
86
+ ranges.each do |range|
87
+ carve = Pango::Rectangle.new(0, 0, compute_carve(rule, range), 0)
88
+ att = Pango::AttrShape.new(carve, carve, rule)
89
+ att.start_index = range.first
90
+ att.end_index = range.last
91
+ attrs.insert(att)
92
+ end
93
93
  end
94
- searches.each do |search|
95
- rect = layout.index_to_pos(search[:index])
96
- x = Pango.pixels(rect.x) + search[:rule][:adjust].dx[@index]
97
- y = Pango.pixels(rect.y) + search[:rule][:adjust].dy[@index]
98
- h = rule[:box].height[@index]
99
- draw_calls << { x: x, y: y, h: h, # defer drawing until we've valigned
100
- draw: search[:rule][:draw] }
94
+ layout.attributes = attrs
95
+ layout.context.set_shape_renderer do |cxt, att, do_path|
96
+ unless do_path # when stroking the text
97
+ rule = att.data
98
+ x = Pango.pixels(layout.index_to_pos(att.start_index).x) +
99
+ rule[:adjust].dx[@index]
100
+ y = Pango.pixels(layout.index_to_pos(att.start_index).y) +
101
+ rule[:adjust].dy[@index] +
102
+ compute_valign(layout, valign, rule[:box].height[@index])
103
+ rule[:draw].call(self, x, y)
104
+ cxt.reset_clip
105
+ [cxt, att, do_path]
106
+ end
101
107
  end
102
- return draw_calls
103
108
  end
104
109
 
105
110
  def stroke_outline!(cc, layout, draw)
@@ -146,24 +151,16 @@ module Squib
146
151
  layout.justify = para.justify unless para.justify.nil?
147
152
  layout.spacing = para.spacing unless para.spacing.nil?
148
153
 
149
- embed_draws = process_embeds(embed, para.str, layout)
154
+ embed_images!(embed, para.str, layout, para.valign)
150
155
 
151
- vertical_start = compute_valign(layout, para.valign, max_embed_height(embed_draws))
152
- cc.move_to(0, vertical_start) # TODO clean this up a bit
156
+ vertical_start = compute_valign(layout, para.valign, 0)
157
+ cc.move_to(0, vertical_start)
153
158
 
154
159
  stroke_outline!(cc, layout, draw) if draw.stroke_strategy == :stroke_first
155
160
  cc.move_to(0, vertical_start)
161
+
156
162
  cc.show_pango_layout(layout)
157
163
  stroke_outline!(cc, layout, draw) if draw.stroke_strategy == :fill_first
158
- begin
159
- embed_draws.each { |ed| ed[:draw].call(self, ed[:x], ed[:y] + vertical_start) }
160
- rescue Exception => e
161
- puts '====EXCEPTION!===='
162
- puts e
163
- 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'
164
- puts '=================='
165
- raise e
166
- end
167
164
  draw_text_hint(cc, box.x, box.y, layout, para.hint)
168
165
  extents = { width: layout.extents[1].width / Pango::SCALE,
169
166
  height: layout.extents[1].height / Pango::SCALE }