squib 0.11.0 → 0.12.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 (194) hide show
  1. checksums.yaml +4 -4
  2. data/.gitignore +3 -0
  3. data/.travis.yml +5 -3
  4. data/CHANGELOG.md +24 -2
  5. data/README.md +2 -4
  6. data/RELEASE TODO.md +1 -0
  7. data/Rakefile +1 -1
  8. data/docs/arrays.rst +5 -2
  9. data/docs/build_groups.rst +15 -17
  10. data/docs/colors.rst +29 -3
  11. data/docs/conf.py +3 -3
  12. data/docs/config.rst +14 -1
  13. data/docs/data.rst +8 -4
  14. data/docs/dsl/configure.rst +18 -0
  15. data/docs/dsl/csv.rst +19 -0
  16. data/docs/dsl/data_frame.rst +85 -0
  17. data/docs/dsl/disable_build.rst +1 -4
  18. data/docs/dsl/disable_build_globally.rst +46 -0
  19. data/docs/dsl/enable_build.rst +1 -1
  20. data/docs/dsl/enable_build_globally.rst +44 -0
  21. data/docs/dsl/save_pdf.rst +44 -3
  22. data/docs/dsl/showcase.rst +11 -0
  23. data/docs/dsl/text.rst +3 -5
  24. data/docs/dsl/xlsx.rst +6 -0
  25. data/docs/guides/game_icons.rst +124 -4
  26. data/docs/guides/getting-started/part_1_zero_to_game.rst +4 -3
  27. data/docs/guides/getting-started/part_2_iconography.rst +92 -16
  28. data/docs/guides/getting-started/part_3_workflows.rst +43 -1
  29. data/docs/guides/getting-started/part_4_ruby_power.rst +18 -0
  30. data/docs/guides/git.rst +2 -1
  31. data/docs/guides/guard.rst +84 -0
  32. data/docs/guides/hello_world.rst +63 -4
  33. data/docs/guides/projects.rst +35 -0
  34. data/docs/install.rst +3 -1
  35. data/docs/layouts.rst +3 -3
  36. data/docs/learning.rst +3 -1
  37. data/docs/parameters.rst +14 -6
  38. data/docs/text_feature.rst +42 -13
  39. data/docs/units.rst +9 -1
  40. data/lib/squib/api/data.rb +6 -5
  41. data/lib/squib/api/groups.rb +14 -7
  42. data/lib/squib/api/save.rb +2 -1
  43. data/lib/squib/args/sheet.rb +57 -2
  44. data/lib/squib/card.rb +8 -0
  45. data/lib/squib/conf.rb +9 -1
  46. data/lib/squib/deck.rb +2 -1
  47. data/lib/squib/graphics/save_doc.rb +0 -45
  48. data/lib/squib/graphics/save_pdf.rb +85 -0
  49. data/lib/squib/graphics/showcase.rb +1 -1
  50. data/lib/squib/import/data_frame.rb +108 -0
  51. data/lib/squib/version.rb +1 -1
  52. data/samples/autoscale_font/.gitignore +2 -0
  53. data/samples/autoscale_font/card_00_expected.png +0 -0
  54. data/samples/backend/.gitignore +1 -0
  55. data/samples/backend/_backend-config.yml +5 -0
  56. data/samples/backend/_backend.rb +2 -9
  57. data/samples/backend/backend_00_expected.png +0 -0
  58. data/samples/backend/backend_01_expected.png +0 -0
  59. data/samples/backend/backend_vectorized_expected.pdf +0 -0
  60. data/samples/backend/backend_vectors_00_expected.svg +84 -0
  61. data/samples/backend/backend_vectors_01_expected.svg +84 -0
  62. data/samples/backend/shiny-purse.png +0 -0
  63. data/samples/backend/showcase_expected.png +0 -0
  64. data/samples/backend/spanner.svg +91 -0
  65. data/samples/build_groups/.gitignore +1 -0
  66. data/samples/build_groups/Rakefile +25 -0
  67. data/samples/colors/.gitignore +1 -0
  68. data/samples/colors/color_constants_00_expected.png +0 -0
  69. data/samples/colors/colors_00_expected.png +0 -0
  70. data/samples/colors/gradient_00_expected.png +0 -0
  71. data/samples/data/.gitignore +1 -0
  72. data/samples/data/explode_quantities.xlsx +0 -0
  73. data/samples/data/quantity_explosion.csv +3 -0
  74. data/samples/data/sample.csv +3 -0
  75. data/samples/data/sample.xlsx +0 -0
  76. data/samples/data/sample_csv_00_expected.png +0 -0
  77. data/samples/data/sample_csv_01_expected.png +0 -0
  78. data/samples/data/sample_csv_qty_00_expected.png +0 -0
  79. data/samples/data/sample_excel_00_expected.png +0 -0
  80. data/samples/data/sample_excel_01_expected.png +0 -0
  81. data/samples/data/sample_excel_02_expected.png +0 -0
  82. data/samples/data/sample_excel_resources_00_expected.png +0 -0
  83. data/samples/data/sample_excel_resources_01_expected.png +0 -0
  84. data/samples/data/sample_xlsx_qty_00_expected.png +0 -0
  85. data/samples/images/.gitignore +8 -0
  86. data/samples/images/_images_00_expected.png +0 -0
  87. data/samples/images/angler-fish.png +0 -0
  88. data/samples/images/ball.png +0 -0
  89. data/samples/images/glass-heart.svg +52 -0
  90. data/samples/images/grit.png +0 -0
  91. data/samples/images/offset.svg +85 -0
  92. data/samples/images/robot-golem.svg +1 -0
  93. data/samples/images/shiny-purse.png +0 -0
  94. data/samples/images/spanner.svg +91 -0
  95. data/samples/images/sprites.png +0 -0
  96. data/samples/images/with-alpha.png +0 -0
  97. data/samples/intro/.gitignore +2 -0
  98. data/samples/intro/auto-repair.svg +1 -0
  99. data/samples/intro/crawling.svg +1 -0
  100. data/samples/intro/data.xlsx +0 -0
  101. data/samples/intro/humans.svg +1 -0
  102. data/samples/intro/ninja-mask.svg +1 -0
  103. data/samples/intro/part1_00_expected.png +0 -0
  104. data/samples/intro/part2_00_expected.png +0 -0
  105. data/samples/intro/part3_00_expected.png +0 -0
  106. data/samples/intro/part3_layout.yml +34 -0
  107. data/samples/intro/part4_00_expected.png +0 -0
  108. data/samples/intro/part4_01_expected.png +0 -0
  109. data/samples/intro/part5_00_expected.png +0 -0
  110. data/samples/intro/part5_01_expected.png +0 -0
  111. data/samples/intro/part5_02_expected.png +0 -0
  112. data/samples/intro/part5_03_expected.png +0 -0
  113. data/samples/intro/part5_hand_expected.png +0 -0
  114. data/samples/intro/part5_showcase_expected.png +0 -0
  115. data/samples/intro/pirate-skull.svg +1 -0
  116. data/samples/intro/robot-golem.svg +1 -0
  117. data/samples/project/Gemfile +6 -0
  118. data/samples/project/Guardfile +18 -0
  119. data/samples/project/Rakefile +25 -0
  120. data/samples/project/bw/robot-golem.svg +53 -0
  121. data/samples/project/color/robot-golem.svg +1 -0
  122. data/samples/project/config.yml +2 -0
  123. data/samples/project/layouts/characters.yml +3 -0
  124. data/samples/project/layouts/skills.yml +3 -0
  125. data/samples/project/src/characters.rb +8 -0
  126. data/samples/project/src/skills.rb +7 -0
  127. data/samples/{ranges.rb → ranges/_ranges.rb} +0 -0
  128. data/samples/ranges/glass-heart.svg +52 -0
  129. data/samples/ranges/ranges_00_expected.png +0 -0
  130. data/samples/saves/.gitignore +1 -0
  131. data/samples/saves/_save_pdf.rb +18 -0
  132. data/samples/saves/hand_expected.png +0 -0
  133. data/samples/saves/hand_pretty_expected.png +0 -0
  134. data/samples/saves/save-pdf-small_expected.pdf +0 -0
  135. data/samples/saves/save-pdf_expected.pdf +0 -0
  136. data/samples/saves/save_png_00_expected.png +0 -0
  137. data/samples/saves/save_png_trimmed_00_expected.png +0 -0
  138. data/samples/saves/save_sheet_00_expected.png +0 -0
  139. data/samples/saves/save_sheet_01_expected.png +0 -0
  140. data/samples/saves/save_sheet_range_00_expected.png +0 -0
  141. data/samples/saves/save_sheet_range_01_expected.png +0 -0
  142. data/samples/saves/save_single_sheet_00_expected.png +0 -0
  143. data/samples/saves/saves_notrim_01_expected.png +0 -0
  144. data/samples/saves/showcase2_expected.png +0 -0
  145. data/samples/saves/showcase_expected.png +0 -0
  146. data/samples/saves/showcase_individual_00_expected.png +0 -0
  147. data/samples/saves/showcase_individual_01_expected.png +0 -0
  148. data/samples/saves/showcase_individual_02_expected.png +0 -0
  149. data/samples/saves/showcase_individual_03_expected.png +0 -0
  150. data/samples/saves/spanner.svg +91 -0
  151. data/samples/shapes/.gitignore +1 -0
  152. data/samples/shapes/shape_00_expected.png +0 -0
  153. data/samples/text/.gitignore +2 -0
  154. data/samples/text/README.md +1 -0
  155. data/samples/text/_text_00_expected.png +0 -0
  156. data/samples/text/config.yml +2 -0
  157. data/samples/units/_units.rb +32 -0
  158. data/samples/units/units_00_expected.png +0 -0
  159. data/samples/units/using_units.yml +10 -0
  160. data/spec/api/api_data_spec.rb +18 -18
  161. data/spec/api/api_groups_spec.rb +49 -0
  162. data/spec/args/sheet_spec.rb +24 -2
  163. data/spec/conf_spec.rb +8 -0
  164. data/spec/data/conf/basic.yml +1 -0
  165. data/spec/data/samples/autoscale_font/_autoscale_font.rb.txt +3 -0
  166. data/spec/data/samples/basic.rb.txt +3 -0
  167. data/spec/data/samples/cairo_access.rb.txt +2 -0
  168. data/spec/data/samples/colors/_gradients.rb.txt +1 -0
  169. data/spec/data/samples/config_text_markup.rb.txt +2 -0
  170. data/spec/data/samples/custom_config.rb.txt +1 -0
  171. data/spec/data/samples/data/_csv.rb.txt +6 -0
  172. data/spec/data/samples/data/_excel.rb.txt +13 -0
  173. data/spec/data/samples/embed_text.rb.txt +4 -0
  174. data/spec/data/samples/hello_world.rb.txt +2 -0
  175. data/spec/data/samples/images/_more_load_images.rb.txt +1 -0
  176. data/spec/data/samples/{ranges.rb.txt → ranges/_ranges.rb.txt} +3 -0
  177. data/spec/data/samples/saves/_hand.rb.txt +8 -0
  178. data/spec/data/samples/saves/_portrait_landscape.rb.txt +2 -0
  179. data/spec/data/samples/saves/_save_pdf.rb.txt +1505 -449
  180. data/spec/data/samples/saves/_saves.rb.txt +16 -0
  181. data/spec/data/samples/saves/_showcase.rb.txt +4 -0
  182. data/spec/data/samples/shapes/_draw_shapes.rb.txt +1 -0
  183. data/spec/data/samples/text_options.rb.txt +3 -0
  184. data/spec/data/samples/tgc_proofs.rb.txt +1 -0
  185. data/spec/data/samples/{units.rb.txt → units/_units.rb.txt} +20 -15
  186. data/spec/deck_spec.rb +7 -0
  187. data/spec/graphics/graphics_save_doc_spec.rb +2 -1
  188. data/spec/import/data_frame_spec.rb +251 -0
  189. data/spec/samples/run_samples_spec.rb +1 -1
  190. data/spec/samples/samples_regression_spec.rb +3 -2
  191. data/squib.gemspec +6 -6
  192. metadata +144 -24
  193. data/.gitmodules +0 -36
  194. data/samples/units.rb +0 -28
@@ -3,6 +3,7 @@ require 'csv'
3
3
  require_relative '../args/input_file'
4
4
  require_relative '../args/import'
5
5
  require_relative '../args/csv_opts'
6
+ require_relative '../import/data_frame'
6
7
 
7
8
  module Squib
8
9
 
@@ -12,7 +13,7 @@ module Squib
12
13
  import = Args::Import.new.load!(opts)
13
14
  s = Roo::Excelx.new(input.file[0])
14
15
  s.default_sheet = s.sheets[input.sheet[0]]
15
- data = {}
16
+ data = Squib::DataFrame.new
16
17
  s.first_column.upto(s.last_column) do |col|
17
18
  header = s.cell(s.first_row, col).to_s
18
19
  header.strip! if import.strip?
@@ -39,14 +40,14 @@ module Squib
39
40
  csv_opts = Args::CSV_Opts.new(opts)
40
41
  table = CSV.parse(data, csv_opts.to_hash)
41
42
  check_duplicate_csv_headers(table)
42
- hash = Hash.new
43
+ hash = Squib::DataFrame.new
43
44
  table.headers.each do |header|
44
45
  new_header = header.to_s
45
46
  new_header = new_header.strip if import.strip?
46
47
  hash[new_header] ||= table[header]
47
48
  end
48
49
  if import.strip?
49
- new_hash = Hash.new
50
+ new_hash = Squib::DataFrame.new
50
51
  hash.each do |header, col|
51
52
  new_hash[header] = col.map do |str|
52
53
  str = str.strip if str.respond_to?(:strip)
@@ -78,9 +79,9 @@ module Squib
78
79
 
79
80
  # @api private
80
81
  def explode_quantities(data, qty)
81
- return data unless data.key? qty.to_s.strip
82
+ return data unless data.col? qty.to_s.strip
82
83
  qtys = data[qty]
83
- new_data = {}
84
+ new_data = Squib::DataFrame.new
84
85
  data.each do |col, arr|
85
86
  new_data[col] = []
86
87
  qtys.each_with_index do |qty, index|
@@ -2,13 +2,20 @@ require 'set'
2
2
 
3
3
  module Squib
4
4
 
5
- # An idea for later...
6
- # def enable_group_env group
7
- # ENV['SQUIB_BUILD'] ||= ''
8
- # ENV['SQUIB_BUILD'] += ','
9
- # ENV['SQUIB_BUILD'] += group
10
- # end
11
- # module_function :enable_group_env
5
+ # DSL method. See http://squib.readthedocs.io
6
+ def enable_build_globally group
7
+ groups = (ENV['SQUIB_BUILD'] ||= '').split(',')
8
+ ENV['SQUIB_BUILD'] = (groups << group).uniq.join(',')
9
+ end
10
+ module_function :enable_build_globally
11
+
12
+ # DSL method. See http://squib.readthedocs.io
13
+ def disable_build_globally group
14
+ groups = (ENV['SQUIB_BUILD'] ||= '').split(',')
15
+ groups.delete(group.to_s)
16
+ ENV['SQUIB_BUILD'] = groups.uniq.join(',')
17
+ end
18
+ module_function :disable_build_globally
12
19
 
13
20
  class Deck
14
21
 
@@ -3,6 +3,7 @@ require_relative '../args/hand_special'
3
3
  require_relative '../args/save_batch'
4
4
  require_relative '../args/sheet'
5
5
  require_relative '../args/showcase_special'
6
+ require_relative '../graphics/save_pdf'
6
7
 
7
8
  module Squib
8
9
  class Deck
@@ -18,7 +19,7 @@ module Squib
18
19
  def save_pdf(opts = {})
19
20
  range = Args::CardRange.new(opts[:range], deck_size: size)
20
21
  sheet = Args::Sheet.new(custom_colors, { file: 'output.pdf' }).load!(opts, expand_by: size, layout: layout, dpi: dpi)
21
- render_pdf(range, sheet)
22
+ Graphics::SavePDF.new(self).render_pdf(range, sheet)
22
23
  end
23
24
 
24
25
  # DSL method. See http://squib.readthedocs.io
@@ -12,14 +12,23 @@ module Squib
12
12
  include ColorValidator
13
13
  include DirValidator
14
14
 
15
- def initialize(custom_colors = {}, dsl_method_defaults = {}, deck_size = 1)
15
+ def initialize(custom_colors = {}, dsl_method_defaults = {}, deck_size = 1, dpi = 300)
16
16
  @custom_colors = custom_colors
17
17
  @dsl_method_defaults = dsl_method_defaults
18
18
  @deck_size = deck_size
19
+ @dpi = dpi
19
20
  end
20
21
 
21
22
  def self.parameters
22
23
  {
24
+ crop_margin_bottom: 0,
25
+ crop_margin_left: 0,
26
+ crop_margin_right: 0,
27
+ crop_margin_top: 0,
28
+ crop_marks: false,
29
+ crop_stroke_color: :black,
30
+ crop_stroke_dash: '',
31
+ crop_stroke_width: 1.5,
23
32
  dir: '_output',
24
33
  file: 'sheet.png',
25
34
  fill_color: :white,
@@ -39,7 +48,16 @@ module Squib
39
48
  end
40
49
 
41
50
  def self.params_with_units
42
- [ :gap, :height, :margin, :trim_radius, :trim, :width ]
51
+ [ :crop_margin_bottom, :crop_margin_left, :crop_margin_right,
52
+ :crop_margin_top, :gap, :height, :margin, :trim_radius, :trim,
53
+ :width
54
+ ]
55
+ end
56
+
57
+ def validate_crop_stroke_dash(arg)
58
+ arg.to_s.split.collect do |x|
59
+ UnitConversion.parse(x, @dpi).to_f
60
+ end
43
61
  end
44
62
 
45
63
  def validate_fill_color(arg)
@@ -66,6 +84,43 @@ module Squib
66
84
  "#{dir}/#{file}"
67
85
  end
68
86
 
87
+ def crop_coords(x, y, deck_w, deck_h)
88
+ [
89
+ { # Vertical, Upper-left
90
+ x1: x + trim + crop_margin_left, y1: 0,
91
+ x2: x + trim + crop_margin_left, y2: margin - 1
92
+ },
93
+ { # Vertical , Upper-right
94
+ x1: x + deck_w - trim - crop_margin_right, y1: 0,
95
+ x2: x + deck_w - trim - crop_margin_right, y2: margin - 1
96
+ },
97
+ { # Vertical , Lower-left
98
+ x1: x + trim + crop_margin_left, y1: height,
99
+ x2: x + trim + crop_margin_left, y2: height - margin + 1
100
+ },
101
+ { # Vertical , Lower-right
102
+ x1: x + deck_w - trim - crop_margin_right, y1: height,
103
+ x2: x + deck_w - trim - crop_margin_right, y2: height - margin + 1
104
+ },
105
+ { # Horizont al, Upper-left
106
+ x1: 0 , y1: y + trim + crop_margin_top,
107
+ x2: margin - 1, y2: y + trim + crop_margin_top
108
+ },
109
+ { # Horizontal, Upper-Right
110
+ x1: width , y1: y + trim + crop_margin_top,
111
+ x2: width - margin + 1, y2: y + trim + crop_margin_top
112
+ },
113
+ { # Horizontal, Lower-Left
114
+ x1: 0 , y1: y + deck_h - trim - crop_margin_bottom,
115
+ x2: margin - 1, y2: y + deck_h - trim - crop_margin_bottom
116
+ },
117
+ { # Horizontal, Lower-Right
118
+ x1: width, y1: y + deck_h - trim - crop_margin_bottom,
119
+ x2: width - margin + 1, y2: y + deck_h - trim - crop_margin_bottom
120
+ },
121
+ ]
122
+ end
123
+
69
124
  end
70
125
 
71
126
  end
@@ -51,6 +51,14 @@ module Squib
51
51
  @cairo_context.restore
52
52
  end
53
53
 
54
+ def finish!
55
+ begin
56
+ @cairo_surface.finish
57
+ rescue Cairo::SurfaceFinishedError
58
+ # do nothin - if it's already finished that's fine
59
+ end
60
+ end
61
+
54
62
  ########################
55
63
  ### BACKEND GRAPHICS ###
56
64
  ########################
@@ -3,6 +3,14 @@ require 'yaml'
3
3
  require_relative 'args/typographer'
4
4
 
5
5
  module Squib
6
+ USER_CONFIG = {}
7
+
8
+ def configure(opts)
9
+ str_hash = opts.inject({}) { |h, (k, v)| h[k.to_s] = v; h }
10
+ USER_CONFIG.merge! str_hash
11
+ end
12
+ module_function :configure
13
+
6
14
  # @api private
7
15
  class Conf
8
16
 
@@ -40,7 +48,7 @@ module Squib
40
48
  }
41
49
 
42
50
  def initialize(config_hash = DEFAULTS)
43
- @config_hash = config_hash
51
+ @config_hash = config_hash.merge USER_CONFIG # programmatic overrides yml
44
52
  @typographer = Args::Typographer.new(config_hash)
45
53
  normalize_antialias
46
54
  end
@@ -26,7 +26,7 @@ module Squib
26
26
  # Attributes for the width, height (in pixels) and number of cards
27
27
  # These are expected to be immuatble for the life of Deck
28
28
  # @api private
29
- attr_reader :width, :height, :cards
29
+ attr_reader :width, :height, :cards, :progress_bar
30
30
 
31
31
  # Delegate these configuration options to the Squib::Conf object
32
32
  def_delegators :conf, :antialias, :backend, :count_format, :custom_colors, :dir,
@@ -73,6 +73,7 @@ module Squib
73
73
  if block_given?
74
74
  instance_eval(&block) # here we go. wheeeee!
75
75
  end
76
+ @cards.each { |c| c.finish! }
76
77
  end
77
78
 
78
79
  # Directly accesses the array of cards in the deck
@@ -1,51 +1,6 @@
1
1
  module Squib
2
2
  class Deck
3
3
 
4
- # :nodoc:
5
- # @api private
6
- def render_pdf(range, sheet)
7
- file = "#{sheet.dir}/#{sheet.file}"
8
- cc = Cairo::Context.new(Cairo::PDFSurface.new(file, sheet.width * 72.0 / @dpi, sheet.height * 72.0 / @dpi))
9
- cc.scale(72.0 / @dpi, 72.0 / @dpi) # for bug #62
10
- x, y = sheet.margin, sheet.margin
11
- card_width = @width - 2 * sheet.trim
12
- card_height = @height - 2 * sheet.trim
13
- @progress_bar.start("Saving PDF to #{file}", range.size) do |bar|
14
- range.each do |i|
15
- card = @cards[i]
16
- cc.translate(x, y)
17
- cc.rectangle(sheet.trim, sheet.trim, card_width, card_height)
18
- cc.clip
19
- case card.backend.downcase.to_sym
20
- when :memory
21
- cc.set_source(card.cairo_surface, 0, 0)
22
- cc.paint
23
- when :svg
24
- card.cairo_surface.finish
25
- cc.save
26
- cc.scale(0.8, 0.8) # I really don't know why I needed to do this at all. But 0.8 is the magic number to get this to scale right
27
- cc.render_rsvg_handle(RSVG::Handle.new_from_file(card.svgfile), nil)
28
- cc.restore
29
- else
30
- abort "No such back end supported for save_pdf: #{backend}"
31
- end
32
- bar.increment
33
- cc.reset_clip
34
- cc.translate(-x, -y)
35
- x += card.width + sheet.gap - 2 * sheet.trim
36
- if x > (sheet.width - card_width - sheet.margin)
37
- x = sheet.margin
38
- y += card.height + sheet.gap - 2 * sheet.trim
39
- if y > (sheet.height - card_height - sheet.margin)
40
- cc.show_page # next page
41
- x, y = sheet.margin, sheet.margin
42
- end
43
- end
44
- end
45
- end
46
- cc.target.finish
47
- end
48
-
49
4
  # :nodoc:
50
5
  # @api private
51
6
  def render_sheet(range, batch, sheet)
@@ -0,0 +1,85 @@
1
+ require_relative '../args/sheet'
2
+
3
+ module Squib
4
+ module Graphics
5
+ class SavePDF
6
+
7
+ def initialize(deck)
8
+ @deck = deck
9
+ end
10
+
11
+ # :nodoc:
12
+ # @api private
13
+ def render_pdf(range, sheet)
14
+ cc = init_cc(sheet)
15
+ cc.scale(72.0 / @deck.dpi, 72.0 / @deck.dpi) # for bug #62
16
+ x, y = sheet.margin, sheet.margin
17
+ card_width = @deck.width - 2 * sheet.trim
18
+ card_height = @deck.height - 2 * sheet.trim
19
+ track_progress(range, sheet) do |bar|
20
+ range.each do |i|
21
+ card = @deck.cards[i]
22
+ cc.translate(x, y)
23
+ cc.rectangle(sheet.trim, sheet.trim, card_width, card_height)
24
+ cc.clip
25
+ case card.backend.downcase.to_sym
26
+ when :memory
27
+ cc.set_source(card.cairo_surface, 0, 0)
28
+ cc.paint
29
+ when :svg
30
+ card.cairo_surface.finish
31
+ cc.save
32
+ cc.scale(0.8, 0.8) # I really don't know why I needed to do this at all. But 0.8 is the magic number to get this to scale right
33
+ cc.render_rsvg_handle(RSVG::Handle.new_from_file(card.svgfile), nil)
34
+ cc.restore
35
+ else
36
+ abort "No such back end supported for save_pdf: #{backend}"
37
+ end
38
+ bar.increment
39
+ cc.reset_clip
40
+ cc.translate(-x, -y)
41
+ draw_crop_marks(cc, x, y, sheet) if sheet.crop_marks
42
+ x += card.width + sheet.gap - 2 * sheet.trim
43
+ if x > (sheet.width - card_width - sheet.margin)
44
+ x = sheet.margin
45
+ y += card.height + sheet.gap - 2 * sheet.trim
46
+ if y > (sheet.height - card_height - sheet.margin)
47
+ cc.show_page # next page
48
+ x, y = sheet.margin, sheet.margin
49
+ end
50
+ end
51
+ end
52
+ end
53
+ cc.target.finish
54
+ end
55
+
56
+ private
57
+
58
+ # Initialize the Cairo Context
59
+ def init_cc(sheet)
60
+ Cairo::Context.new(Cairo::PDFSurface.new(
61
+ "#{sheet.dir}/#{sheet.file}",
62
+ sheet.width * 72.0 / @deck.dpi, #PDF thinks in 72 DPI "points"
63
+ sheet.height * 72.0 / @deck.dpi)
64
+ )
65
+ end
66
+
67
+ def track_progress(range, sheet)
68
+ msg = "Saving PDF to #{sheet.full_filename}"
69
+ @deck.progress_bar.start(msg, range.size) { |bar| yield(bar) }
70
+ end
71
+
72
+ def draw_crop_marks(cc, x, y, sheet)
73
+ sheet.crop_coords(x, y, @deck.width, @deck.height).each do |coord|
74
+ cc.move_to(coord[:x1], coord[:y1])
75
+ cc.line_to(coord[:x2], coord[:y2])
76
+ cc.set_source_color(sheet.crop_stroke_color)
77
+ cc.set_dash(sheet.crop_stroke_dash)
78
+ cc.set_line_width(sheet.crop_stroke_width)
79
+ cc.stroke
80
+ end
81
+ end
82
+
83
+ end
84
+ end
85
+ end
@@ -33,7 +33,7 @@ module Squib
33
33
  # :nodoc:
34
34
  # @api private
35
35
  def trim_rounded(src, trim, radius)
36
- trim_cc = Cairo::Context.new(Cairo::ImageSurface.new(src.width - 2.0 * trim, src.height - 2.0 * trim))
36
+ trim_cc = Cairo::Context.new(Cairo::ImageSurface.new(@width - 2.0 * trim, @height - 2.0 * trim))
37
37
  trim_cc.rounded_rectangle(0, 0, trim_cc.target.width, trim_cc.target.height, radius, radius)
38
38
  trim_cc.set_source(src, -1 * trim, -1 * trim)
39
39
  trim_cc.clip
@@ -0,0 +1,108 @@
1
+ # encoding: UTF-8
2
+
3
+ require 'json'
4
+ require 'forwardable'
5
+
6
+ module Squib
7
+ class DataFrame
8
+ include Enumerable
9
+
10
+ def initialize(hash = {}, def_columns = true)
11
+ @hash = hash
12
+ columns.each { |col| def_column(col) } if def_columns
13
+ end
14
+
15
+ def each(&block)
16
+ @hash.each(&block)
17
+ end
18
+
19
+ def [](i)
20
+ @hash[i]
21
+ end
22
+
23
+ def []=(col, v)
24
+ @hash[col] = v
25
+ def_column(col)
26
+ return v
27
+ end
28
+
29
+ def columns
30
+ @hash.keys
31
+ end
32
+
33
+ def ncolumns
34
+ @hash.keys.size
35
+ end
36
+
37
+ def col?(col)
38
+ @hash.key? col
39
+ end
40
+
41
+ def row(i)
42
+ @hash.inject(Hash.new) { |ret, (name, arr)| ret[name] = arr[i]; ret }
43
+ end
44
+
45
+ def nrows
46
+ @hash.inject(0) { |max, (_n, col)| col.size > max ? col.size : max }
47
+ end
48
+
49
+ def to_json
50
+ @hash.to_json
51
+ end
52
+
53
+ def to_pretty_json
54
+ JSON.pretty_generate(@hash)
55
+ end
56
+
57
+ def to_h
58
+ @hash
59
+ end
60
+
61
+ def to_pretty_text
62
+ max_col = columns.inject(0) { |max, c | c.length > max ? c.length : max }
63
+ top = " ╭#{'-' * 36}╮\n"
64
+ bottom = " ╰#{'-' * 36}╯\n"
65
+ str = ''
66
+ 0.upto(nrows - 1) do | i |
67
+ str += (' ' * max_col) + top
68
+ row(i).each do |col, data|
69
+ str += "#{col.rjust(max_col)} #{wrap_n_pad(data, max_col)}"
70
+ end
71
+ str += (' ' * max_col) + bottom
72
+ end
73
+ return str
74
+ end
75
+
76
+ private
77
+
78
+ def snake_case(str)
79
+ str.to_s.
80
+ strip.
81
+ gsub(/\s+/,'_').
82
+ gsub(/([A-Z]+)([A-Z][a-z])/,'\1_\2').
83
+ gsub(/([a-z]+)([A-Z])/,'\1_\2').
84
+ downcase.
85
+ to_sym
86
+ end
87
+
88
+ def wrap_n_pad(str, max_col)
89
+ str.to_s.
90
+ concat(' '). # handle nil & empty strings
91
+ scan(/.{1,34}/).
92
+ map { |s| (' ' * max_col) + " | " + s.ljust(34) }.
93
+ join(" |\n").
94
+ lstrip. # initially no whitespace next to key
95
+ concat(" |\n")
96
+ end
97
+
98
+ def def_column(col)
99
+ raise "Column #{col} - does not exist" unless @hash.key? col
100
+ method_name = snake_case(col)
101
+ return if self.class.method_defined?(method_name) #warn people? or skip?
102
+ define_singleton_method method_name do
103
+ @hash[col]
104
+ end
105
+ end
106
+
107
+ end
108
+ end