From 3f2b05746604b541b7f03663269b90c1e08a2481 Mon Sep 17 00:00:00 2001 From: Andy Meneely Date: Fri, 14 Aug 2015 23:56:03 -0400 Subject: [PATCH] Convert save_sheet and save_pdf to args classes --- lib/squib/api/save.rb | 47 +++++++++++ lib/squib/args/save_batch.rb | 8 ++ lib/squib/args/sheet.rb | 24 +++++- lib/squib/graphics/save_doc.rb | 102 ++++++++---------------- spec/args/sheet_spec.rb | 42 ++++++++++ spec/graphics/graphics_save_doc_spec.rb | 6 +- 6 files changed, 156 insertions(+), 73 deletions(-) diff --git a/lib/squib/api/save.rb b/lib/squib/api/save.rb index 96f33f6..6668035 100644 --- a/lib/squib/api/save.rb +++ b/lib/squib/api/save.rb @@ -22,6 +22,26 @@ module Squib self end + # Lays out the cards in range and renders a PDF + # + # @example + # save_pdf file: 'deck.pdf', margin: 75, gap: 5, trim: 37 + # + # @option opts file [String] the name of the PDF file to save. See {file:README.md#Specifying_Files Specifying Files} + # @option opts dir [String] (_output) the directory to save to. Created if it doesn't exist. + # @option opts width [Integer] (3300) the height of the page in pixels. Default is 11in * 300dpi. Supports unit conversion. + # @option opts height [Integer] (2550) the height of the page in pixels. Default is 8.5in * 300dpi. Supports unit conversion. + # @option opts margin [Integer] (75) the margin around the outside of the page. Supports unit conversion. + # @option opts gap [Integer] (0) the space in pixels between the cards. Supports unit conversion. + # @option opts trim [Integer] (0) the space around the edge of each card to trim (e.g. to cut off the bleed margin for print-and-play). Supports unit conversion. + # @return [nil] + # @api public + def save_pdf(opts = {}) + range = Args::CardRange.new(opts[:range], deck_size: size) + sheet = Args::Sheet.new(custom_colors, {file: 'output.pdf'}).load!(opts, expand_by: size, layout: layout, dpi: dpi) + render_pdf(range, sheet) + end + # Saves the given range of cards to a PNG # # @example @@ -47,6 +67,33 @@ module Squib end end + # Lays out the cards in range and renders a stitched PNG sheet + # + # @example + # save_sheet prefix: 'sheet_', margin: 75, gap: 5, trim: 37 + # + # @option opts [Enumerable] range (:all) the range of cards over which this will be rendered. See {file:README.md#Specifying_Ranges Specifying Ranges} + # @option opts columns [Integer] (5) the number of columns in the grid. Must be an integer + # @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. + # @option opts [String] prefix (card_) the prefix of the file name(s) + # @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) + # @option opts dir [String] (_output) the directory to save to. Created if it doesn't exist. + # @option opts margin [Integer] (0) the margin around the outside of the sheet. + # @option opts gap [Integer] (0) the space in pixels between the cards + # @option opts trim [Integer] (0) the space around the edge of each card to trim (e.g. to cut off the bleed margin for print-and-play) + # @return [nil] + # @api public + def save_sheet(opts = {}) + range = Args::CardRange.new(opts[:range], deck_size: size) + batch = Args::SaveBatch.new.load!(opts, expand_by: size, layout: layout, dpi: dpi) + sheet = Args::Sheet.new(custom_colors, {margin: 0}, size).load!(opts, expand_by: size, layout: layout, dpi: dpi) + opts = {margin: 0}.merge(opts) # overriding the non-system default + p = needs(opts, [:range, + :prefix, :count_format, :creatable_dir, + :margin, :gap, :trim, :rows, :columns]) + render_sheet(range, batch, sheet, p) + end + # Renders a range of cards in a showcase as if they are sitting in 3D on a reflective surface # See {file:samples/showcase.rb} for full example # diff --git a/lib/squib/args/save_batch.rb b/lib/squib/args/save_batch.rb index 27d3fc2..7b3ab42 100644 --- a/lib/squib/args/save_batch.rb +++ b/lib/squib/args/save_batch.rb @@ -47,6 +47,14 @@ module Squib end end + def full_filename(i) + "#{dir[i]}/#{prefix[i]}#{count_format[i] % i}.png" + end + + def summary + "#{dir[0]}/#{prefix[0]}_*" + end + end end end diff --git a/lib/squib/args/sheet.rb b/lib/squib/args/sheet.rb index 1a8d73b..c7774f3 100644 --- a/lib/squib/args/sheet.rb +++ b/lib/squib/args/sheet.rb @@ -12,19 +12,25 @@ module Squib include ColorValidator include DirValidator - def initialize(custom_colors = {}, dsl_method_defaults = {}) + def initialize(custom_colors = {}, dsl_method_defaults = {}, deck_size = 1) @custom_colors = custom_colors @dsl_method_defaults = dsl_method_defaults + @deck_size = deck_size end def self.parameters - { dir: '_output', + { + dir: '_output', file: 'sheet.png', fill_color: :white, gap: 0, + height: 2550, margin: 75, + rows: :infinite, + columns: 5, trim_radius: 38, trim: 0, + width: 3300, } end @@ -33,7 +39,7 @@ module Squib end def self.params_with_units - [ :gap, :margin, :trim_radius, :trim ] + [ :gap, :height, :margin, :trim_radius, :trim, :width ] end def validate_fill_color(arg) @@ -44,6 +50,18 @@ module Squib ensure_dir_created(arg) end + def validate_columns(arg) + raise 'columns must be an integer' unless arg.respond_to? :to_i + arg.to_i + end + + def validate_rows(arg) + raise 'columns must be an integer' unless columns.respond_to? :to_i + return 1 if @deck_size < columns + return arg if arg.respond_to? :to_i + (@deck_size.to_i / columns.to_i).ceil + end + end end diff --git a/lib/squib/graphics/save_doc.rb b/lib/squib/graphics/save_doc.rb index 89dc8c5..eefdf61 100644 --- a/lib/squib/graphics/save_doc.rb +++ b/lib/squib/graphics/save_doc.rb @@ -1,37 +1,20 @@ module Squib class Deck - # Lays out the cards in range and renders a PDF - # - # @example - # save_pdf file: 'deck.pdf', margin: 75, gap: 5, trim: 37 - # - # @option opts file [String] the name of the PDF file to save. See {file:README.md#Specifying_Files Specifying Files} - # @option opts dir [String] (_output) the directory to save to. Created if it doesn't exist. - # @option opts width [Integer] (3300) the height of the page in pixels. Default is 11in * 300dpi. Supports unit conversion. - # @option opts height [Integer] (2550) the height of the page in pixels. Default is 8.5in * 300dpi. Supports unit conversion. - # @option opts margin [Integer] (75) the margin around the outside of the page. Supports unit conversion. - # @option opts gap [Integer] (0) the space in pixels between the cards. Supports unit conversion. - # @option opts trim [Integer] (0) the space around the edge of each card to trim (e.g. to cut off the bleed margin for print-and-play). Supports unit conversion. - # @return [nil] - # @api public - def save_pdf(opts = {}) - opts = {width: 3300, height: 2550}.merge(opts) - p = needs(opts, [:range, :paper_width, :paper_height, :file_to_save, - :creatable_dir, :margin, :gap, :trim]) - paper_width = p[:width] - paper_height = p[:height] - file = "#{p[:dir]}/#{p[:file]}" - cc = Cairo::Context.new(Cairo::PDFSurface.new(file, paper_width * 72.0 / @dpi, paper_height * 72.0 / @dpi)) + # :nodoc: + # @api private + def render_pdf(range, sheet) + file = "#{sheet.dir}/#{sheet.file}" + cc = Cairo::Context.new(Cairo::PDFSurface.new(file, sheet.width * 72.0 / @dpi, sheet.height * 72.0 / @dpi)) cc.scale(72.0 / @dpi, 72.0 / @dpi) # for bug #62 - x, y = p[:margin], p[:margin] - card_width = @width - 2 * p[:trim] - card_height = @height - 2 * p[:trim] - @progress_bar.start("Saving PDF to #{file}", p[:range].size) do |bar| - p[:range].each do |i| + x, y = sheet.margin, sheet.margin + card_width = @width - 2 * sheet.trim + card_height = @height - 2 * sheet.trim + @progress_bar.start("Saving PDF to #{file}", range.size) do |bar| + range.each do |i| card = @cards[i] cc.translate(x,y) - cc.rectangle(p[:trim], p[:trim], card_width, card_height) + cc.rectangle(sheet.trim, sheet.trim, card_width, card_height) cc.clip case card.backend.downcase.to_sym when :memory @@ -49,68 +32,51 @@ module Squib bar.increment cc.reset_clip cc.translate(-x,-y) - x += card.width + p[:gap] - 2*p[:trim] - if x > (paper_width - card_width - p[:margin]) - x = p[:margin] - y += card.height + p[:gap] - 2*p[:trim] - if y > (paper_height - card_height - p[:margin]) + x += card.width + sheet.gap - 2*sheet.trim + if x > (sheet.width - card_width - sheet.margin) + x = sheet.margin + y += card.height + sheet.gap - 2*sheet.trim + if y > (sheet.height - card_height - sheet.margin) cc.show_page # next page - x,y = p[:margin],p[:margin] + x,y = sheet.margin,sheet.margin end end end end end - # Lays out the cards in range and renders a stitched PNG sheet - # - # @example - # save_sheet prefix: 'sheet_', margin: 75, gap: 5, trim: 37 - # - # @option opts [Enumerable] range (:all) the range of cards over which this will be rendered. See {file:README.md#Specifying_Ranges Specifying Ranges} - # @option opts colulmns [Integer] (5) the number of columns in the grid - # @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. - # @option opts [String] prefix (card_) the prefix of the file name(s) - # @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) - # @option opts dir [String] (_output) the directory to save to. Created if it doesn't exist. - # @option opts margin [Integer] (0) the margin around the outside of the page. - # @option opts gap [Integer] (0) the space in pixels between the cards - # @option opts trim [Integer] (0) the space around the edge of each card to trim (e.g. to cut off the bleed margin for print-and-play) - # @return [nil] - # @api public - def save_sheet(opts = {}) - opts = {margin: 0}.merge(opts) # overriding the non-system default - p = needs(opts, [:range, :prefix, :count_format, :creatable_dir, :margin, :gap, :trim, :rows, :columns]) - # EXTRACT METHOD HERE - sheet_width = (p[:columns] * (@width + 2 * p[:gap] - 2 * p[:trim])) + (2 * p[:margin]) - sheet_height = (p[:rows] * (@height + 2 * p[:gap] - 2 * p[:trim])) + (2 * p[:margin]) + # :nodoc: + # @api private + def render_sheet(range, batch, sheet, p = {}) + sheet_width = (sheet.columns * (@width + 2 * sheet.gap - 2 * sheet.trim)) + (2 * sheet.margin) + sheet_height = (sheet.rows * (@height + 2 * sheet.gap - 2 * sheet.trim)) + (2 * sheet.margin) cc = Cairo::Context.new(Cairo::ImageSurface.new(sheet_width, sheet_height)) num_this_sheet = 0 sheet_num = 0 - x, y = p[:margin], p[:margin] - @progress_bar.start("Saving PNG sheet to #{p[:dir]}/#{p[:prefix]}_*", @cards.size + 1) do |bar| - p[:range].each do |i| - if num_this_sheet >= (p[:columns] * p[:rows]) # new sheet - filename = "#{p[:dir]}/#{p[:prefix]}#{p[:count_format] % sheet_num}.png" + x, y = sheet.margin, sheet.margin + @progress_bar.start("Saving PNG sheet to #{batch.summary}", @cards.size + 1) do |bar| + range.each do |i| + if num_this_sheet >= (sheet.columns * sheet.rows) # new sheet + filename = batch.full_filename(sheet_num) cc.target.write_to_png(filename) new_sheet = false num_this_sheet = 0 sheet_num += 1 - x, y = p[:margin], p[:margin] + x, y = sheet.margin, sheet.margin cc = Cairo::Context.new(Cairo::ImageSurface.new(sheet_width, sheet_height)) end - surface = trim(@cards[i].cairo_surface, p[:trim], @width, @height) + surface = trim(@cards[i].cairo_surface, sheet.trim, @width, @height) cc.set_source(surface, x, y) cc.paint num_this_sheet += 1 - x += surface.width + p[:gap] - if num_this_sheet % p[:columns] == 0 # new row - x = p[:margin] - y += surface.height + p[:gap] + x += surface.width + sheet.gap + if num_this_sheet % sheet.columns == 0 # new row + x = sheet.margin + y += surface.height + sheet.gap end bar.increment end - cc.target.write_to_png("#{p[:dir]}/#{p[:prefix]}#{p[:count_format] % sheet_num}.png") + cc.target.write_to_png(batch.full_filename(sheet_num)) end end diff --git a/spec/args/sheet_spec.rb b/spec/args/sheet_spec.rb index ea0202a..0f35641 100644 --- a/spec/args/sheet_spec.rb +++ b/spec/args/sheet_spec.rb @@ -13,4 +13,46 @@ describe Squib::Args::Sheet do end + context 'rows and colums' do + subject(:sheet) { Squib::Args::Sheet.new({}, {}, 4) } + + it 'does nothing on a perfect fit' do + opts = { columns: 2, rows: 2 } + sheet.load! opts + expect(sheet).to have_attributes(columns: 2, rows: 2) + end + + it 'keeps both if specified' do + opts = { columns: 1, rows: 1 } + sheet.load! opts + expect(sheet).to have_attributes(columns: 1, rows: 1) + end + + it 'computes properly on non-integer' do + opts = {columns: 1, rows: :infinite} + sheet.load! opts + expect(sheet).to have_attributes( columns: 1, rows: 4 ) + end + + it 'computes properly on unspecified rows' do + opts = {columns: 1} + sheet.load! opts + expect(sheet).to have_attributes( columns: 1, rows: 4 ) + end + + it 'computes properly on unspecified, too-big column' do + opts = {} + sheet.load! opts + expect(sheet).to have_attributes( columns: 5, rows: 1 ) + end + + it 'fails on a non-integer column' do + opts = {columns: :infinite} + expect { sheet.load!(opts) }.to raise_error('columns must be an integer') + end + + + + end + end diff --git a/spec/graphics/graphics_save_doc_spec.rb b/spec/graphics/graphics_save_doc_spec.rb index 2bb79cf..567e3f8 100644 --- a/spec/graphics/graphics_save_doc_spec.rb +++ b/spec/graphics/graphics_save_doc_spec.rb @@ -26,8 +26,9 @@ describe Squib::Deck, '#save_pdf' do it 'make all the expected calls on a smoke test' do num_cards = 9 deck = Squib::Deck.new(cards: 9, width: 825, height: 1125) - expect(deck).to receive(:dirify) { |arg| arg } #don't create the dir expect(Squib.logger).to receive(:debug).at_least(:once) + expect(Squib.logger).to receive(:warn).exactly(:once) #warn about making the dir + expect(Dir).to receive(:mkdir) {} # don't actually make the dir expect(cxt).to receive(:scale).with(0.24, 0.24) expect_card_place(75, 75) @@ -50,7 +51,8 @@ describe Squib::Deck, '#save_pdf' do args = { range: 2..4, file: 'foo.pdf', dir: '_out', margin: 75, gap: 5, trim: 37 } deck = Squib::Deck.new(cards: num_cards, width: 825, height: 1125) expect(Squib.logger).to receive(:debug).at_least(:once) - expect(deck).to receive(:dirify) { |arg| arg } #don't create the dir + expect(Squib.logger).to receive(:warn).exactly(:once) #warn about making the dir + expect(Dir).to receive(:mkdir) {} # don't actually make the dir expect(cxt).to receive(:scale).with(0.24, 0.24) expect_card_place(75, 75)