module Squib module Graphics # Helper class to generate templated sheet. class SaveSprue def initialize(deck, tmpl, sheet_args) @deck = deck @tmpl = tmpl @page_number = 0 @sheet_args = sheet_args # might be Args::Sheet or Args::SaveBatch @overlay_lines = @tmpl.crop_lines.select do |line| line['overlay_on_cards'] end end def render_sheet(range) cc = init_cc cc.set_source_color(:white) # white backdrop TODO make option cc.paint slots = @tmpl.cards per_sheet = slots.size check_oversized_card draw_overlay_below_cards cc if range.size track_progress(range) do |bar| range.each do |i| cc = next_page_if_needed(cc, i, per_sheet) card = @deck.cards[i] slot = slots[i % per_sheet] draw_card cc, card, slot['x'] - @sheet_args.trim, slot['y'] - @sheet_args.trim, slot['rotate'], slot['flip_vertical'], slot['flip_horizontal'], @sheet_args.trim, @sheet_args.trim_radius bar.increment end draw_overlay_above_cards cc draw_final_page cc # See bug #320 end end protected # Initialize the Cairo Context def init_cc raise NotImplementedError end def draw_page(cc) raise NotImplementedError end def full_filename raise NotImplementedError end private def next_page_if_needed(cc, i, per_sheet) return cc unless (i != 0) && (i % per_sheet) == 0 draw_overlay_above_cards cc cc = draw_page cc draw_overlay_below_cards cc @page_number += 1 cc end def track_progress(range) msg = "Saving templated sheet to #{full_filename}" @deck.progress_bar.start(msg, range.size) { |bar| yield(bar) } end def draw_overlay_below_cards(cc) if @tmpl.crop_line_overlay == :on_margin add_margin_overlay_clip_mask cc cc.clip draw_crop_line cc, @tmpl.crop_lines cc.reset_clip elsif @tmpl.crop_line_overlay == :beneath_cards draw_crop_line cc, @tmpl.crop_lines end end def draw_overlay_above_cards(cc) if @tmpl.crop_line_overlay == :overlay_on_cards draw_crop_line cc, @tmpl.crop_lines else draw_crop_line cc, @overlay_lines end end def add_margin_overlay_clip_mask(cc) margin = @tmpl.margin cc.new_path cc.rectangle( margin[:left], margin[:top], margin[:right] - margin[:left], margin[:bottom] - margin[:top] ) cc.new_sub_path cc.move_to @tmpl.sheet_width, 0 cc.line_to 0, 0 cc.line_to 0, @tmpl.sheet_height cc.line_to @tmpl.sheet_width, @tmpl.sheet_height cc.close_path end def draw_crop_line(cc, crop_lines) crop_lines.each do |line| cc.move_to line['line'].x1, line['line'].y1 cc.line_to line['line'].x2, line['line'].y2 cc.set_source_color line['color'] cc.set_line_width line['width'] cc.set_dash(line['style'].pattern) if line['style'].pattern cc.stroke end end def check_oversized_card Squib.logger.warn { "Card size is larger than sprue's expected card size "\ "of #{@tmpl.card_width}x#{@tmpl.card_height}. Cards may overlap." } if (@deck.width - 2.0 * @sheet_args.trim) > @tmpl.card_width || (@deck.height - 2.0 * @sheet_args.trim) > @tmpl.card_height end def draw_card(cc, card, x, y, angle, flip_v, flip_h, trim, trim_radius) # Compute the true size of the card after trimming w = @deck.width - 2.0 * trim h = @deck.height - 2.0 * trim # Normalize the angles first # TODO do this in the args class angle = angle % (2 * Math::PI) angle = 2 * Math::PI - angle if angle < 0 # Perform the actual rotation and drawing mat = cc.matrix # Save the transformation matrix to revert later cc.translate x, y cc.translate @deck.width / 2.0, @deck.height / 2.0 cc.flip(flip_v, flip_h, 0, 0) cc.rotate angle cc.translate -@deck.width / 2.0, -@deck.height / 2.0 cc.rounded_rectangle(trim, trim, w, h, trim_radius, trim_radius) # clip cc.clip cc.set_source card.cairo_surface, 0, 0 cc.matrix = mat cc.paint cc.reset_clip end end # Templated sheet renderer in PDF format. class SaveSpruePDF < SaveSprue def init_cc ratio = 72.0 / @deck.dpi slots = @tmpl.cards per_sheet = slots.size surface = if per_sheet == 1 Cairo::PDFSurface.new( full_filename, (@tmpl.sheet_width - 2 * @sheet_args.trim) * ratio, (@tmpl.sheet_height - 2 *@sheet_args.trim) * ratio ) else Cairo::PDFSurface.new( full_filename, @tmpl.sheet_width * ratio, @tmpl.sheet_height * ratio ) end cc = CairoContextWrapper.new(Cairo::Context.new(surface)) # cc = Cairo::Context.new(surface) cc.scale(72.0 / @deck.dpi, 72.0 / @deck.dpi) # make it like pixels cc end def draw_page(cc) cc.show_page cc.set_source_color(:white) # white backdrop TODO make option cc.paint cc end def draw_final_page(cc) # PDF doesn't need to create a last page. See bug #320 cc.target.finish end def full_filename @sheet_args.full_filename end end # Templated sheet renderer in PNG format. class SaveSpruePNG < SaveSprue def init_cc surface = Cairo::ImageSurface.new @tmpl.sheet_width, @tmpl.sheet_height CairoContextWrapper.new(Cairo::Context.new(surface)) # Cairo::Context.new(surface) end def draw_page(cc) cc.target.write_to_png(full_filename) init_cc cc.set_source_color(:white) # white backdrop TODO make option cc.paint cc end # The last page always gets written out for PNGs because they are separate # files and don't get "flushed" automatically. See bug #320. def draw_final_page(cc) draw_page cc cc.target.finish end def full_filename @sheet_args.full_filename @page_number end end end end