squib 0.14.3.pre1 → 0.16.0.pre.preview1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (113) hide show
  1. checksums.yaml +4 -4
  2. data/.github/workflows/tests.yml +22 -0
  3. data/CHANGELOG.md +58 -2
  4. data/Dockerfile +27 -0
  5. data/Guardfile +8 -0
  6. data/README.md +24 -6
  7. data/RELEASE TODO.md +1 -0
  8. data/Rakefile +3 -0
  9. data/lib/squib.rb +3 -1
  10. data/lib/squib/args/arg_loader.rb +109 -106
  11. data/lib/squib/args/box.rb +52 -48
  12. data/lib/squib/args/card_range.rb +26 -24
  13. data/lib/squib/args/color_validator.rb +4 -9
  14. data/lib/squib/args/coords.rb +39 -25
  15. data/lib/squib/args/csv_opts.rb +13 -16
  16. data/lib/squib/args/dir_validator.rb +7 -12
  17. data/lib/squib/args/draw.rb +69 -68
  18. data/lib/squib/args/embed_adjust.rb +12 -15
  19. data/lib/squib/args/embed_key.rb +6 -11
  20. data/lib/squib/args/hand_special.rb +25 -25
  21. data/lib/squib/args/import.rb +54 -27
  22. data/lib/squib/args/input_file.rb +22 -26
  23. data/lib/squib/args/paint.rb +30 -31
  24. data/lib/squib/args/paragraph.rb +95 -93
  25. data/lib/squib/args/save_batch.rb +50 -48
  26. data/lib/squib/args/scale_box.rb +43 -39
  27. data/lib/squib/args/sheet.rb +147 -149
  28. data/lib/squib/args/showcase_special.rb +32 -29
  29. data/lib/squib/args/sprue_file.rb +30 -30
  30. data/lib/squib/args/svg_special.rb +26 -26
  31. data/lib/squib/args/transform.rb +48 -54
  32. data/lib/squib/args/typographer.rb +88 -92
  33. data/lib/squib/args/unit_conversion.rb +6 -8
  34. data/lib/squib/args/xywh_shorthands.rb +56 -0
  35. data/lib/squib/builtin/projects/advanced/config.yml +3 -6
  36. data/lib/squib/builtin/projects/basic/config.yml +3 -6
  37. data/lib/squib/commands/make_sprue.rb +2 -0
  38. data/lib/squib/conf.rb +5 -5
  39. data/lib/squib/deck.rb +34 -12
  40. data/lib/squib/dsl/background.rb +35 -0
  41. data/lib/squib/dsl/circle.rb +39 -0
  42. data/lib/squib/dsl/csv.rb +42 -0
  43. data/lib/squib/dsl/curve.rb +35 -0
  44. data/lib/squib/dsl/cut_zone.rb +47 -0
  45. data/lib/squib/dsl/ellipse.rb +37 -0
  46. data/lib/squib/dsl/grid.rb +35 -0
  47. data/lib/squib/{api → dsl}/groups.rb +0 -0
  48. data/lib/squib/dsl/hand.rb +42 -0
  49. data/lib/squib/dsl/line.rb +35 -0
  50. data/lib/squib/dsl/png.rb +56 -0
  51. data/lib/squib/dsl/polygon.rb +36 -0
  52. data/lib/squib/dsl/rect.rb +37 -0
  53. data/lib/squib/dsl/safe_zone.rb +48 -0
  54. data/lib/squib/dsl/save.rb +21 -0
  55. data/lib/squib/dsl/save_pdf.rb +50 -0
  56. data/lib/squib/dsl/save_png.rb +47 -0
  57. data/lib/squib/dsl/save_sheet.rb +53 -0
  58. data/lib/squib/dsl/showcase.rb +43 -0
  59. data/lib/squib/dsl/star.rb +37 -0
  60. data/lib/squib/dsl/svg.rb +62 -0
  61. data/lib/squib/dsl/text.rb +54 -0
  62. data/lib/squib/dsl/text_embed.rb +78 -0
  63. data/lib/squib/dsl/triangle.rb +35 -0
  64. data/lib/squib/{api → dsl}/units.rb +10 -0
  65. data/lib/squib/dsl/xlsx.rb +40 -0
  66. data/lib/squib/dsl/yaml.rb +40 -0
  67. data/lib/squib/errors_warnings/warn_unexpected_params.rb +14 -0
  68. data/lib/squib/graphics/cairo_context_wrapper.rb +2 -2
  69. data/lib/squib/graphics/image.rb +0 -6
  70. data/lib/squib/graphics/save_images.rb +3 -3
  71. data/lib/squib/graphics/save_sprue.rb +39 -12
  72. data/lib/squib/graphics/showcase.rb +1 -1
  73. data/lib/squib/graphics/text.rb +37 -9
  74. data/lib/squib/import/csv_importer.rb +45 -0
  75. data/lib/squib/import/quantity_exploder.rb +18 -0
  76. data/lib/squib/import/xlsx_importer.rb +28 -0
  77. data/lib/squib/import/yaml_importer.rb +30 -0
  78. data/lib/squib/layout_parser.rb +24 -7
  79. data/lib/squib/sprues/crop_line.rb +6 -6
  80. data/lib/squib/sprues/crop_line_dash.rb +6 -6
  81. data/lib/squib/sprues/sprue.rb +19 -14
  82. data/lib/squib/sprues/sprue_schema.rb +4 -2
  83. data/lib/squib/version.rb +1 -1
  84. data/samples/autoscale_font/_autoscale_font.rb +77 -8
  85. data/samples/colors/_colors.rb +11 -5
  86. data/samples/colors/_switch_color.rb +33 -0
  87. data/samples/data/_excel.rb +1 -1
  88. data/samples/images/_more_load_images.rb +1 -1
  89. data/samples/ranges/_ranges.rb +1 -1
  90. data/samples/saves/_save_filenames.rb +28 -0
  91. data/samples/saves/_save_pdf.rb +1 -1
  92. data/samples/saves/_saves.rb +2 -1
  93. data/samples/shapes/_draw_shapes.rb +2 -2
  94. data/samples/sprues/_advanced_sprues.rb +4 -3
  95. data/samples/sprues/_builtin_sprues.rb +1 -0
  96. data/samples/sprues/_fold_sheet.rb +4 -1
  97. data/samples/text/_text.rb +6 -1
  98. data/samples/text/_text_options.rb +2 -1
  99. data/samples/units/_cells.rb +51 -0
  100. data/samples/units/_shorthands.rb +49 -0
  101. data/samples/units/_units.rb +7 -0
  102. data/squib.gemspec +19 -13
  103. metadata +151 -61
  104. data/.travis.yml +0 -19
  105. data/appveyor.yml +0 -24
  106. data/lib/squib/api/background.rb +0 -15
  107. data/lib/squib/api/data.rb +0 -137
  108. data/lib/squib/api/image.rb +0 -49
  109. data/lib/squib/api/save.rb +0 -83
  110. data/lib/squib/api/shapes.rb +0 -124
  111. data/lib/squib/api/text.rb +0 -25
  112. data/lib/squib/api/text_embed.rb +0 -71
  113. data/samples/bug256/_bug256.rb +0 -13
@@ -0,0 +1,14 @@
1
+ require 'rainbow/refinement'
2
+
3
+ module Squib::WarnUnexpectedParams
4
+ using Rainbow # we can colorize strings now!
5
+
6
+ def warn_if_unexpected(opts, uplevel: 5)
7
+ accepted_params = self.class.accepted_params
8
+ unexpected = opts.keys - accepted_params
9
+ unexpected.each do |key|
10
+ warn "Unexpected parameter '#{key.to_s.yellow}:' to #{dsl_method.to_s.cyan}(). Accepted parameters: #{accepted_params}",
11
+ uplevel: uplevel
12
+ end
13
+ end
14
+ end
@@ -20,11 +20,11 @@ module Squib
20
20
 
21
21
  def_delegators :cairo_cxt, :save, :set_source_color, :paint, :restore,
22
22
  :translate, :rotate, :move_to, :update_pango_layout, :width, :height,
23
- :show_pango_layout, :rounded_rectangle, :set_line_width, :stroke, :fill,
23
+ :show_pango_layout, :rectangle, :rounded_rectangle, :set_line_width, :stroke, :fill,
24
24
  :set_source, :scale, :render_rsvg_handle, :circle, :triangle, :line_to,
25
25
  :operator=, :show_page, :clip, :transform, :mask, :create_pango_layout,
26
26
  :antialias=, :curve_to, :matrix, :matrix=, :identity_matrix, :pango_layout_path,
27
- :stroke_preserve, :target, :new_path, :fill_preserve, :close_path,
27
+ :stroke_preserve, :target, :new_path, :new_sub_path, :reset_clip, :fill_preserve, :close_path,
28
28
  :set_line_join, :set_line_cap, :set_dash, :arc, :arc_negative
29
29
 
30
30
  # :nodoc:
@@ -81,12 +81,6 @@ module Squib
81
81
  Squib.logger.debug {"Rendering: #{file}, id: #{id} @#{x},#{y} #{width}x#{height}, alpha: #{alpha}, blend: #{blend}, angle: #{angle}, mask: #{mask}"}
82
82
  Squib.logger.warn 'Both an SVG file and SVG data were specified' unless file.to_s.empty? || svg_args.data.to_s.empty?
83
83
  return if (file.nil? or file.eql? '') and svg_args.data.nil? # nothing specified TODO Move this out to arg validator
84
- if(box.width == 0 || box.height == 0)
85
- if @deck.conf.warn_zero_size_image?
86
- Squib.logger.warn "svg: zero-sized width or height detected for #{file}. SVG not drawn."
87
- end
88
- return
89
- end
90
84
  svg_args.data = File.read(file) if svg_args.data.to_s.empty?
91
85
  begin
92
86
  svg = Rsvg::Handle.new_from_data(svg_args.data)
@@ -10,7 +10,7 @@ module Squib
10
10
  else
11
11
  @cairo_surface
12
12
  end
13
- write_png(surface, index, batch.dir, batch.prefix, batch.count_format)
13
+ write_png(surface, index, batch)
14
14
  end
15
15
 
16
16
  # :nodoc:
@@ -44,8 +44,8 @@ module Squib
44
44
  return new_cc.target
45
45
  end
46
46
 
47
- def write_png(surface, i, dir, prefix, count_format)
48
- surface.write_to_png("#{dir}/#{prefix}#{count_format % i}.png")
47
+ def write_png(surface, i, b)
48
+ surface.write_to_png("#{b.dir}/#{b.prefix}#{b.count_format % i}#{b.suffix}.png")
49
49
  end
50
50
 
51
51
  end
@@ -5,7 +5,7 @@ module Squib
5
5
  def initialize(deck, tmpl, sheet_args)
6
6
  @deck = deck
7
7
  @tmpl = tmpl
8
- @page_number = 1
8
+ @page_number = 0
9
9
  @sheet_args = sheet_args # might be Args::Sheet or Args::SaveBatch
10
10
  @overlay_lines = @tmpl.crop_lines.select do |line|
11
11
  line['overlay_on_cards']
@@ -30,15 +30,16 @@ module Squib
30
30
  slot = slots[i % per_sheet]
31
31
 
32
32
  draw_card cc, card,
33
- slot['x'], slot['y'],
33
+ slot['x'] - @sheet_args.trim,
34
+ slot['y'] - @sheet_args.trim,
34
35
  slot['rotate'],
36
+ slot['flip_vertical'], slot['flip_horizontal'],
35
37
  @sheet_args.trim, @sheet_args.trim_radius
36
-
37
38
  bar.increment
38
39
  end
39
40
 
40
41
  draw_overlay_above_cards cc
41
- cc.target.finish
42
+ draw_final_page cc # See bug #320
42
43
  end
43
44
  end
44
45
 
@@ -128,7 +129,7 @@ module Squib
128
129
  (@deck.height - 2.0 * @sheet_args.trim) > @tmpl.card_height
129
130
  end
130
131
 
131
- def draw_card(cc, card, x, y, angle, trim, trim_radius)
132
+ def draw_card(cc, card, x, y, angle, flip_v, flip_h, trim, trim_radius)
132
133
  # Compute the true size of the card after trimming
133
134
  w = @deck.width - 2.0 * trim
134
135
  h = @deck.height - 2.0 * trim
@@ -142,6 +143,7 @@ module Squib
142
143
  mat = cc.matrix # Save the transformation matrix to revert later
143
144
  cc.translate x, y
144
145
  cc.translate @deck.width / 2.0, @deck.height / 2.0
146
+ cc.flip(flip_v, flip_h, 0, 0)
145
147
  cc.rotate angle
146
148
  cc.translate -@deck.width / 2.0, -@deck.height / 2.0
147
149
  cc.rounded_rectangle(trim, trim, w, h, trim_radius, trim_radius) # clip
@@ -158,13 +160,25 @@ module Squib
158
160
  def init_cc
159
161
  ratio = 72.0 / @deck.dpi
160
162
 
161
- surface = Cairo::PDFSurface.new(
162
- full_filename,
163
- @tmpl.sheet_width * ratio,
164
- @tmpl.sheet_height * ratio
165
- )
163
+ slots = @tmpl.cards
164
+ per_sheet = slots.size
165
+
166
+ surface = if per_sheet == 1
167
+ Cairo::PDFSurface.new(
168
+ full_filename,
169
+ (@tmpl.sheet_width - 2 * @sheet_args.trim) * ratio,
170
+ (@tmpl.sheet_height - 2 *@sheet_args.trim) * ratio
171
+ )
172
+ else
173
+ Cairo::PDFSurface.new(
174
+ full_filename,
175
+ @tmpl.sheet_width * ratio,
176
+ @tmpl.sheet_height * ratio
177
+ )
178
+ end
166
179
 
167
- cc = Cairo::Context.new(surface)
180
+ cc = CairoContextWrapper.new(Cairo::Context.new(surface))
181
+ # cc = Cairo::Context.new(surface)
168
182
  cc.scale(72.0 / @deck.dpi, 72.0 / @deck.dpi) # make it like pixels
169
183
  cc
170
184
  end
@@ -176,6 +190,11 @@ module Squib
176
190
  cc
177
191
  end
178
192
 
193
+ def draw_final_page(cc)
194
+ # PDF doesn't need to create a last page. See bug #320
195
+ cc.target.finish
196
+ end
197
+
179
198
  def full_filename
180
199
  @sheet_args.full_filename
181
200
  end
@@ -185,7 +204,8 @@ module Squib
185
204
  class SaveSpruePNG < SaveSprue
186
205
  def init_cc
187
206
  surface = Cairo::ImageSurface.new @tmpl.sheet_width, @tmpl.sheet_height
188
- Cairo::Context.new(surface)
207
+ CairoContextWrapper.new(Cairo::Context.new(surface))
208
+ # Cairo::Context.new(surface)
189
209
  end
190
210
 
191
211
  def draw_page(cc)
@@ -196,6 +216,13 @@ module Squib
196
216
  cc
197
217
  end
198
218
 
219
+ # The last page always gets written out for PNGs because they are separate
220
+ # files and don't get "flushed" automatically. See bug #320.
221
+ def draw_final_page(cc)
222
+ draw_page cc
223
+ cc.target.finish
224
+ end
225
+
199
226
  def full_filename
200
227
  @sheet_args.full_filename @page_number
201
228
  end
@@ -21,7 +21,7 @@ module Squib
21
21
 
22
22
  cards = range.collect { |i| @cards[i] }
23
23
  cards.each_with_index do |card, i|
24
- trimmed = trim_rounded(card.cairo_surface, sheet.trim, sheet.trim_radius)
24
+ trimmed = trim_rounded(card.cairo_surface, showcase.trim, showcase.trim_radius)
25
25
  reflected = reflect(trimmed, showcase.reflect_offset, showcase.reflect_percent, showcase.reflect_strength)
26
26
  perspectived = perspective(reflected, showcase.scale, showcase.face_right?)
27
27
  out_cc.set_source(perspectived, sheet.margin + i * perspectived.width * showcase.offset, sheet.margin)
@@ -76,7 +76,7 @@ module Squib
76
76
 
77
77
  # # :nodoc:
78
78
  # # @api private
79
- def embed_images!(embed, str, layout, valign)
79
+ def embed_images!(embed, str, layout, valign, scale)
80
80
  return [] unless embed.rules.any?
81
81
  layout.markup = str
82
82
  clean_str = layout.text
@@ -84,7 +84,7 @@ module Squib
84
84
  EmbeddingUtils.indices(clean_str, embed.rules.keys).each do |key, ranges|
85
85
  rule = embed.rules[key]
86
86
  ranges.each do |range|
87
- carve = Pango::Rectangle.new(0, 0, compute_carve(rule, range), 0)
87
+ carve = Pango::Rectangle.new(0, 0, compute_carve(rule, range) * scale, 0)
88
88
  att = Pango::AttrShape.new(carve, carve, rule)
89
89
  att.start_index = range.first
90
90
  att.end_index = range.last
@@ -100,7 +100,7 @@ module Squib
100
100
  y = Pango.pixels(layout.index_to_pos(att.start_index).y) +
101
101
  rule[:adjust].dy[@index] +
102
102
  compute_valign(layout, valign, rule[:box].height[@index])
103
- rule[:draw].call(self, x, y)
103
+ rule[:draw].call(self, x, y, scale)
104
104
  cxt.reset_clip
105
105
  [cxt, att, do_path]
106
106
  end
@@ -121,19 +121,46 @@ module Squib
121
121
  end
122
122
  end
123
123
 
124
- # :nodoc:
125
124
  # @api private
126
125
  def text(embed, para, box, trans, draw, dpi)
126
+ font_desc = Pango::FontDescription.new(para.font)
127
+ font_desc.size = para.font_size * Pango::SCALE if para.font_size.is_a? Numeric
128
+ orig_font_size = font_desc.size
129
+
130
+ # If text autoscaling is enabled, find the largest text size (smaller or equal to the set text size) that fits
131
+ if para.ellipsize == :autoscale
132
+ para.ellipsize = Pango::EllipsizeMode::END
133
+ sizes = sizes = (1 .. font_desc.size).to_a.reverse
134
+
135
+ # Dummy render to an area outside the card with decreasing font sizes until text no longer ellipsizes
136
+ max_fitting_size = sizes.bsearch{ |sz|
137
+ font_desc.size = sz
138
+ extents = render_text(embed, para, box, trans, draw, dpi, font_desc, orig_font_size, true)
139
+ !extents[:ellipsized]
140
+ }
141
+
142
+ if max_fitting_size.nil?
143
+ max_fitting_size = sizes.last
144
+ Squib.logger.warn{"Could not autosize for Card \##{@index} as minimum specified size #{max_fitting_size} still ellipsizes."}
145
+ end
146
+ font_desc.size = max_fitting_size
147
+ end
148
+
149
+ render_text(embed, para, box, trans, draw, dpi, font_desc, orig_font_size, false)
150
+ end
151
+
152
+ # :nodoc:
153
+ # @api private
154
+ def render_text(embed, para, box, trans, draw, dpi, font_desc, orig_font_size, dummy_draw)
127
155
  Squib.logger.debug {"Rendering text with: \n#{para} \nat:\n #{box} \ndraw:\n #{draw} \ntransform: #{trans}"}
128
156
  extents = nil
129
157
  use_cairo do |cc|
130
158
  cc.set_source_squibcolor(draw.color)
131
159
  cc.translate(box.x, box.y)
160
+ cc.translate(-10000, -10000) if dummy_draw
132
161
  cc.rotate(trans.angle)
133
162
  cc.move_to(0, 0)
134
163
 
135
- font_desc = Pango::FontDescription.new(para.font)
136
- font_desc.size = para.font_size * Pango::SCALE unless para.font_size.nil?
137
164
  layout = cc.create_pango_layout
138
165
  layout.font_description = font_desc
139
166
  layout.text = para.str.to_s
@@ -152,7 +179,7 @@ module Squib
152
179
  layout.justify = para.justify unless para.justify.nil?
153
180
  layout.spacing = para.spacing unless para.spacing.nil?
154
181
 
155
- embed_images!(embed, para.str, layout, para.valign)
182
+ embed_images!(embed, para.str, layout, para.valign, font_desc.size / orig_font_size.to_f)
156
183
 
157
184
  vertical_start = compute_valign(layout, para.valign, 0)
158
185
  cc.move_to(0, vertical_start)
@@ -164,8 +191,9 @@ module Squib
164
191
  stroke_outline!(cc, layout, draw) if draw.stroke_strategy == :fill_first
165
192
  draw_text_hint(cc, box.x, box.y, layout, para.hint)
166
193
  extents = { width: layout.extents[1].width / Pango::SCALE,
167
- height: layout.extents[1].height / Pango::SCALE }
168
- warn_if_ellipsized layout
194
+ height: layout.extents[1].height / Pango::SCALE,
195
+ ellipsized: layout.ellipsized?}
196
+ warn_if_ellipsized layout unless dummy_draw
169
197
  end
170
198
  return extents
171
199
  end
@@ -0,0 +1,45 @@
1
+ require 'csv'
2
+ require_relative 'quantity_exploder'
3
+
4
+ module Squib::Import
5
+ class CsvImporter
6
+ include Squib::Import::QuantityExploder
7
+ def import_to_dataframe(import, csv_opts, &block)
8
+ data = import.data.nil? ? File.read(import.file) : import.data
9
+ table = CSV.parse(data, **csv_opts.to_hash)
10
+ check_duplicate_csv_headers(table)
11
+ hash = Squib::DataFrame.new
12
+ table.headers.each do |header|
13
+ new_header = header.to_s
14
+ new_header = new_header.strip if import.strip?
15
+ hash[new_header] ||= table[header]
16
+ end
17
+ if import.strip?
18
+ new_hash = Squib::DataFrame.new
19
+ hash.each do |header, col|
20
+ new_hash[header] = col.map do |str|
21
+ str = str.strip if str.respond_to?(:strip)
22
+ str
23
+ end
24
+ end
25
+ hash = new_hash
26
+ end
27
+ unless block.nil?
28
+ hash.each do |header, col|
29
+ col.map! do |val|
30
+ yield(header, val)
31
+ end
32
+ end
33
+ end
34
+ return explode_quantities(hash, import.explode)
35
+ end
36
+
37
+ def check_duplicate_csv_headers(table)
38
+ if table.headers.size != table.headers.uniq.size
39
+ dups = table.headers.select{|e| table.headers.count(e) > 1 }
40
+ Squib.logger.warn "CSV duplicated the following column keys: #{dups.join(',')}"
41
+ end
42
+ end
43
+ end
44
+ end
45
+
@@ -0,0 +1,18 @@
1
+ module Squib
2
+ module Import
3
+ module QuantityExploder
4
+ def explode_quantities(data, qty)
5
+ return data unless data.col? qty.to_s.strip
6
+ qtys = data[qty]
7
+ new_data = Squib::DataFrame.new
8
+ data.each do |col, arr|
9
+ new_data[col] = []
10
+ qtys.each_with_index do |qty, index|
11
+ qty.to_i.times { new_data[col] << arr[index] }
12
+ end
13
+ end
14
+ return new_data
15
+ end
16
+ end
17
+ end
18
+ end
@@ -0,0 +1,28 @@
1
+ require 'roo'
2
+ require_relative 'quantity_exploder'
3
+
4
+ module Squib::Import
5
+ class XlsxImporter
6
+ include Squib::Import::QuantityExploder
7
+ def import_to_dataframe(import, &block)
8
+ s = Roo::Excelx.new(import.file)
9
+ s.default_sheet = s.sheets[import.sheet]
10
+ data = Squib::DataFrame.new
11
+ s.first_column.upto(s.last_column) do |col|
12
+ header = s.cell(s.first_row, col).to_s
13
+ header.strip! if import.strip?
14
+ data[header] = []
15
+ (s.first_row + 1).upto(s.last_row) do |row|
16
+ cell = s.cell(row, col)
17
+ # Roo hack for avoiding unnecessary .0's on whole integers (https://github.com/roo-rb/roo/issues/139)
18
+ cell = s.excelx_value(row, col) if s.excelx_type(row, col) == [:numeric_or_formula, 'General']
19
+ cell.strip! if cell.respond_to?(:strip) && import.strip?
20
+ cell = block.yield(header, cell) unless block.nil?
21
+ data[header] << cell
22
+ end# row
23
+ end# col
24
+ explode_quantities(data, import.explode)
25
+ end
26
+ end
27
+ end
28
+
@@ -0,0 +1,30 @@
1
+ require 'yaml'
2
+ require_relative 'data_frame'
3
+ require_relative 'quantity_exploder'
4
+
5
+ module Squib::Import
6
+ class YamlImporter
7
+ include Squib::Import::QuantityExploder
8
+ def import_to_dataframe(import, &block)
9
+ data = import.data.nil? ? File.read(import.file) : import.data
10
+ yml = YAML.load(data)
11
+ data = Squib::DataFrame.new
12
+ # Get a universal list of keys to ensure everything is covered.
13
+ keys = yml.map { |c| c.keys}.flatten.uniq
14
+ keys.each { |k| data[k] = [] } #init arrays
15
+ yml.each do |card|
16
+ # nil value if key isn't set.
17
+ keys.each { |k| data[k] << card[k] }
18
+ end
19
+ unless block.nil?
20
+ data.each do |header, col|
21
+ col.map! do |val|
22
+ block.yield(header, val)
23
+ end
24
+ end
25
+ end
26
+ explode_quantities(data, import.explode)
27
+ end
28
+ end
29
+ end
30
+
@@ -1,12 +1,15 @@
1
1
  require 'yaml'
2
+ require_relative 'args/xywh_shorthands'
2
3
 
3
4
  module Squib
4
5
  # Internal class for handling layouts
5
6
  # @api private
6
7
  class LayoutParser
8
+ include Args::XYWHShorthands
7
9
 
8
- def initialize(dpi = 300)
10
+ def initialize(dpi = 300, cell_px = 37.5)
9
11
  @dpi = dpi
12
+ @cell_px = cell_px
10
13
  end
11
14
 
12
15
  # Load the layout file(s), if exists
@@ -67,6 +70,9 @@ module Squib
67
70
  end
68
71
 
69
72
  def handle_relative_operators(parent_val, child_val)
73
+ if uses_operator?(child_val) && !has_digits?(parent_val) && !has_digits?(child_val)
74
+ raise "Layout parse error: can't combine #{parent_val} and #{child_val}"
75
+ end
70
76
  if child_val.to_s.strip.start_with?('+=')
71
77
  add_parent_child(parent_val, child_val)
72
78
  elsif child_val.to_s.strip.start_with?('-=')
@@ -81,29 +87,40 @@ module Squib
81
87
  end
82
88
 
83
89
  def add_parent_child(parent, child)
84
- parent_pixels = Args::UnitConversion.parse(parent, @dpi).to_f
85
- child_pixels = Args::UnitConversion.parse(child.sub('+=', ''), @dpi).to_f
90
+ parent_pixels = Args::UnitConversion.parse(parent, @dpi, @cell_px).to_f
91
+ child_pixels = Args::UnitConversion.parse(child.sub('+=', ''), @dpi, @cell_px).to_f
86
92
  parent_pixels + child_pixels
87
93
  end
88
94
 
89
95
  def sub_parent_child(parent, child)
90
- parent_pixels = Args::UnitConversion.parse(parent, @dpi).to_f
91
- child_pixels = Args::UnitConversion.parse(child.sub('-=', ''), @dpi).to_f
96
+ parent_pixels = Args::UnitConversion.parse(parent, @dpi, @cell_px).to_f
97
+ child_pixels = Args::UnitConversion.parse(child.sub('-=', ''), @dpi, @cell_px).to_f
92
98
  parent_pixels - child_pixels
93
99
  end
94
100
 
95
101
  def mul_parent_child(parent, child)
96
- parent_pixels = Args::UnitConversion.parse(parent, @dpi).to_f
102
+ parent_pixels = Args::UnitConversion.parse(parent, @dpi, @cell_px).to_f
97
103
  child_float = child.sub('*=', '').to_f
98
104
  parent_pixels * child_float
99
105
  end
100
106
 
101
107
  def div_parent_child(parent, child)
102
- parent_pixels = Args::UnitConversion.parse(parent, @dpi).to_f
108
+ parent_pixels = Args::UnitConversion.parse(parent, @dpi, @cell_px).to_f
103
109
  child_float = child.sub('/=', '').to_f
104
110
  parent_pixels / child_float
105
111
  end
106
112
 
113
+ # For relative operators, it's difficult for us to handle
114
+ # some of the shorthands - so let's just freak out if you're trying to use
115
+ # relative operators with words, e.g. "middle += 0.5in"
116
+ def has_digits?(arg)
117
+ /.*\d.*/ === arg.to_s
118
+ end
119
+
120
+ def uses_operator?(arg)
121
+ /^[+=|\-=|\/=|*=].*/ === arg.to_s
122
+ end
123
+
107
124
  # Does this layout entry have an extends field?
108
125
  # i.e. is it a base-case or will it need recursion?
109
126
  # :nodoc: