save_pdf: add crop marks
Should satisfy #123 Needs some testing on printers but the functionality is there.dev
parent
f94f710040
commit
30395aee56
|
|
@ -1,13 +1,11 @@
|
||||||
save_pdf
|
save_pdf
|
||||||
========
|
========
|
||||||
|
|
||||||
Lays out the cards in range on a sheet and renders a PDF
|
Lays out the cards in a gride and renders a PDF.
|
||||||
|
|
||||||
Options
|
Options
|
||||||
-------
|
-------
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
file
|
file
|
||||||
default: ``'output.pdf'``
|
default: ``'output.pdf'``
|
||||||
|
|
||||||
|
|
@ -43,6 +41,45 @@ trim
|
||||||
|
|
||||||
the space around the edge of each card to trim (e.g. to cut off the bleed margin for print-and-play). Supports :doc:`/units`.
|
the space around the edge of each card to trim (e.g. to cut off the bleed margin for print-and-play). Supports :doc:`/units`.
|
||||||
|
|
||||||
|
crop_marks
|
||||||
|
default: ``false``
|
||||||
|
|
||||||
|
When ``true``, draws lines in the margins as guides for cutting. Crop marks factor in the ``trim`` (if non-zero), and can also be customized via ``crop_margin_*`` options (see below). Has no effect if ``margin`` is 0.
|
||||||
|
|
||||||
|
crop_margin_bottom
|
||||||
|
default: 0
|
||||||
|
|
||||||
|
The space between the bottom edge of the (potentially trimmed) card, and the crop mark. Supports :doc:`/units`. Has no effect if ``crop_marks`` is ``false``.
|
||||||
|
|
||||||
|
crop_margin_left
|
||||||
|
default: 0
|
||||||
|
|
||||||
|
The space between the left edge of the (potentially trimmed) card, and the crop mark. Supports :doc:`/units`. Has no effect if ``crop_marks`` is ``false``.
|
||||||
|
|
||||||
|
crop_margin_right
|
||||||
|
default: ``0``
|
||||||
|
|
||||||
|
The space between the right edge of the (potentially trimmed) card, and the crop mark. Supports :doc:`/units`. Has no effect if ``crop_marks`` is ``false``.
|
||||||
|
|
||||||
|
crop_margin_top
|
||||||
|
default: ``0``
|
||||||
|
|
||||||
|
The space between the top edge of the (potentially trimmed) card, and the crop mark. Supports :doc:`/units`. Has no effect if ``crop_marks`` is ``false``.
|
||||||
|
|
||||||
|
crop_stroke_color
|
||||||
|
default: ``:black``
|
||||||
|
|
||||||
|
The color of the crop mark lines. Has no effect if ``crop_marks`` is ``false``.
|
||||||
|
|
||||||
|
crop_stroke_dash
|
||||||
|
default: ``''``
|
||||||
|
|
||||||
|
Define a dash pattern for the crop marks. This is a special string with space-separated numbers that define the pattern of on-and-off alternating strokes, measured in pixels or units. For example, ``'0.02in 0.02in'`` will be an equal on-and-off dash pattern. Supports :doc:`/units`. Has no effect if ``crop_marks`` is ``false``.
|
||||||
|
|
||||||
|
crop_stroke_width
|
||||||
|
default: ``1.5``
|
||||||
|
|
||||||
|
Width of the crop mark lines. Has no effect if ``crop_marks`` is ``false``.
|
||||||
|
|
||||||
Examples
|
Examples
|
||||||
--------
|
--------
|
||||||
|
|
|
||||||
|
|
@ -3,6 +3,7 @@ require_relative '../args/hand_special'
|
||||||
require_relative '../args/save_batch'
|
require_relative '../args/save_batch'
|
||||||
require_relative '../args/sheet'
|
require_relative '../args/sheet'
|
||||||
require_relative '../args/showcase_special'
|
require_relative '../args/showcase_special'
|
||||||
|
require_relative '../graphics/save_pdf'
|
||||||
|
|
||||||
module Squib
|
module Squib
|
||||||
class Deck
|
class Deck
|
||||||
|
|
@ -18,7 +19,7 @@ module Squib
|
||||||
def save_pdf(opts = {})
|
def save_pdf(opts = {})
|
||||||
range = Args::CardRange.new(opts[:range], deck_size: size)
|
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)
|
sheet = Args::Sheet.new(custom_colors, { file: 'output.pdf' }).load!(opts, expand_by: size, layout: layout, dpi: dpi)
|
||||||
render_pdf(range, sheet)
|
Graphics::SavePDF.new(self).render_pdf(range, sheet)
|
||||||
end
|
end
|
||||||
|
|
||||||
# DSL method. See http://squib.readthedocs.io
|
# DSL method. See http://squib.readthedocs.io
|
||||||
|
|
|
||||||
|
|
@ -12,14 +12,23 @@ module Squib
|
||||||
include ColorValidator
|
include ColorValidator
|
||||||
include DirValidator
|
include DirValidator
|
||||||
|
|
||||||
def initialize(custom_colors = {}, dsl_method_defaults = {}, deck_size = 1)
|
def initialize(custom_colors = {}, dsl_method_defaults = {}, deck_size = 1, dpi = 300)
|
||||||
@custom_colors = custom_colors
|
@custom_colors = custom_colors
|
||||||
@dsl_method_defaults = dsl_method_defaults
|
@dsl_method_defaults = dsl_method_defaults
|
||||||
@deck_size = deck_size
|
@deck_size = deck_size
|
||||||
|
@dpi = dpi
|
||||||
end
|
end
|
||||||
|
|
||||||
def self.parameters
|
def self.parameters
|
||||||
{
|
{
|
||||||
|
crop_margin_bottom: 0,
|
||||||
|
crop_margin_left: 0,
|
||||||
|
crop_margin_right: 0,
|
||||||
|
crop_margin_top: 0,
|
||||||
|
crop_marks: false,
|
||||||
|
crop_stroke_color: :black,
|
||||||
|
crop_stroke_dash: '',
|
||||||
|
crop_stroke_width: 1.5,
|
||||||
dir: '_output',
|
dir: '_output',
|
||||||
file: 'sheet.png',
|
file: 'sheet.png',
|
||||||
fill_color: :white,
|
fill_color: :white,
|
||||||
|
|
@ -39,7 +48,16 @@ module Squib
|
||||||
end
|
end
|
||||||
|
|
||||||
def self.params_with_units
|
def self.params_with_units
|
||||||
[ :gap, :height, :margin, :trim_radius, :trim, :width ]
|
[ :crop_margin_bottom, :crop_margin_left, :crop_margin_right,
|
||||||
|
:crop_margin_top, :gap, :height, :margin, :trim_radius, :trim,
|
||||||
|
:width
|
||||||
|
]
|
||||||
|
end
|
||||||
|
|
||||||
|
def validate_crop_stroke_dash(arg)
|
||||||
|
arg.to_s.split.collect do |x|
|
||||||
|
UnitConversion.parse(x, @dpi).to_f
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
def validate_fill_color(arg)
|
def validate_fill_color(arg)
|
||||||
|
|
@ -66,6 +84,43 @@ module Squib
|
||||||
"#{dir}/#{file}"
|
"#{dir}/#{file}"
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def crop_coords(x, y, deck_w, deck_h)
|
||||||
|
[
|
||||||
|
{ # Vertical, Upper-left
|
||||||
|
x1: x + trim + crop_margin_left, y1: 0,
|
||||||
|
x2: x + trim + crop_margin_left, y2: margin - 1
|
||||||
|
},
|
||||||
|
{ # Vertical , Upper-right
|
||||||
|
x1: x + deck_w - trim - crop_margin_right, y1: 0,
|
||||||
|
x2: x + deck_w - trim - crop_margin_right, y2: margin - 1
|
||||||
|
},
|
||||||
|
{ # Vertical , Lower-left
|
||||||
|
x1: x + trim + crop_margin_left, y1: height,
|
||||||
|
x2: x + trim + crop_margin_left, y2: height - margin + 1
|
||||||
|
},
|
||||||
|
{ # Vertical , Lower-right
|
||||||
|
x1: x + deck_w - trim - crop_margin_right, y1: height,
|
||||||
|
x2: x + deck_w - trim - crop_margin_right, y2: height - margin + 1
|
||||||
|
},
|
||||||
|
{ # Horizont al, Upper-left
|
||||||
|
x1: 0 , y1: y + trim + crop_margin_top,
|
||||||
|
x2: margin - 1, y2: y + trim + crop_margin_top
|
||||||
|
},
|
||||||
|
{ # Horizontal, Upper-Right
|
||||||
|
x1: width , y1: y + trim + crop_margin_top,
|
||||||
|
x2: width - margin + 1, y2: y + trim + crop_margin_top
|
||||||
|
},
|
||||||
|
{ # Horizontal, Lower-Left
|
||||||
|
x1: 0 , y1: y + deck_h - trim - crop_margin_bottom,
|
||||||
|
x2: margin - 1, y2: y + deck_h - trim - crop_margin_bottom
|
||||||
|
},
|
||||||
|
{ # Horizontal, Lower-Right
|
||||||
|
x1: width, y1: y + deck_h - trim - crop_margin_bottom,
|
||||||
|
x2: width - margin + 1, y2: y + deck_h - trim - crop_margin_bottom
|
||||||
|
},
|
||||||
|
]
|
||||||
|
end
|
||||||
|
|
||||||
end
|
end
|
||||||
|
|
||||||
end
|
end
|
||||||
|
|
|
||||||
|
|
@ -26,7 +26,7 @@ module Squib
|
||||||
# Attributes for the width, height (in pixels) and number of cards
|
# Attributes for the width, height (in pixels) and number of cards
|
||||||
# These are expected to be immuatble for the life of Deck
|
# These are expected to be immuatble for the life of Deck
|
||||||
# @api private
|
# @api private
|
||||||
attr_reader :width, :height, :cards
|
attr_reader :width, :height, :cards, :progress_bar
|
||||||
|
|
||||||
# Delegate these configuration options to the Squib::Conf object
|
# Delegate these configuration options to the Squib::Conf object
|
||||||
def_delegators :conf, :antialias, :backend, :count_format, :custom_colors, :dir,
|
def_delegators :conf, :antialias, :backend, :count_format, :custom_colors, :dir,
|
||||||
|
|
|
||||||
|
|
@ -1,51 +1,6 @@
|
||||||
module Squib
|
module Squib
|
||||||
class Deck
|
class Deck
|
||||||
|
|
||||||
# :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 = 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(sheet.trim, sheet.trim, card_width, card_height)
|
|
||||||
cc.clip
|
|
||||||
case card.backend.downcase.to_sym
|
|
||||||
when :memory
|
|
||||||
cc.set_source(card.cairo_surface, 0, 0)
|
|
||||||
cc.paint
|
|
||||||
when :svg
|
|
||||||
card.cairo_surface.finish
|
|
||||||
cc.save
|
|
||||||
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
|
|
||||||
cc.render_rsvg_handle(RSVG::Handle.new_from_file(card.svgfile), nil)
|
|
||||||
cc.restore
|
|
||||||
else
|
|
||||||
abort "No such back end supported for save_pdf: #{backend}"
|
|
||||||
end
|
|
||||||
bar.increment
|
|
||||||
cc.reset_clip
|
|
||||||
cc.translate(-x, -y)
|
|
||||||
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 = sheet.margin, sheet.margin
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
||||||
cc.target.finish
|
|
||||||
end
|
|
||||||
|
|
||||||
# :nodoc:
|
# :nodoc:
|
||||||
# @api private
|
# @api private
|
||||||
def render_sheet(range, batch, sheet)
|
def render_sheet(range, batch, sheet)
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,85 @@
|
||||||
|
require_relative '../args/sheet'
|
||||||
|
|
||||||
|
module Squib
|
||||||
|
module Graphics
|
||||||
|
class SavePDF
|
||||||
|
|
||||||
|
def initialize(deck)
|
||||||
|
@deck = deck
|
||||||
|
end
|
||||||
|
|
||||||
|
# :nodoc:
|
||||||
|
# @api private
|
||||||
|
def render_pdf(range, sheet)
|
||||||
|
cc = init_cc(sheet)
|
||||||
|
cc.scale(72.0 / @deck.dpi, 72.0 / @deck.dpi) # for bug #62
|
||||||
|
x, y = sheet.margin, sheet.margin
|
||||||
|
card_width = @deck.width - 2 * sheet.trim
|
||||||
|
card_height = @deck.height - 2 * sheet.trim
|
||||||
|
track_progress(range, sheet) do |bar|
|
||||||
|
range.each do |i|
|
||||||
|
card = @deck.cards[i]
|
||||||
|
cc.translate(x, y)
|
||||||
|
cc.rectangle(sheet.trim, sheet.trim, card_width, card_height)
|
||||||
|
cc.clip
|
||||||
|
case card.backend.downcase.to_sym
|
||||||
|
when :memory
|
||||||
|
cc.set_source(card.cairo_surface, 0, 0)
|
||||||
|
cc.paint
|
||||||
|
when :svg
|
||||||
|
card.cairo_surface.finish
|
||||||
|
cc.save
|
||||||
|
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
|
||||||
|
cc.render_rsvg_handle(RSVG::Handle.new_from_file(card.svgfile), nil)
|
||||||
|
cc.restore
|
||||||
|
else
|
||||||
|
abort "No such back end supported for save_pdf: #{backend}"
|
||||||
|
end
|
||||||
|
bar.increment
|
||||||
|
cc.reset_clip
|
||||||
|
cc.translate(-x, -y)
|
||||||
|
draw_crop_marks(cc, x, y, sheet) if sheet.crop_marks
|
||||||
|
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 = sheet.margin, sheet.margin
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
cc.target.finish
|
||||||
|
end
|
||||||
|
|
||||||
|
private
|
||||||
|
|
||||||
|
# Initialize the Cairo Context
|
||||||
|
def init_cc(sheet)
|
||||||
|
Cairo::Context.new(Cairo::PDFSurface.new(
|
||||||
|
"#{sheet.dir}/#{sheet.file}",
|
||||||
|
sheet.width * 72.0 / @deck.dpi, #PDF thinks in 72 DPI "points"
|
||||||
|
sheet.height * 72.0 / @deck.dpi)
|
||||||
|
)
|
||||||
|
end
|
||||||
|
|
||||||
|
def track_progress(range, sheet)
|
||||||
|
msg = "Saving PDF to #{sheet.full_filename}"
|
||||||
|
@deck.progress_bar.start(msg, range.size) { |bar| yield(bar) }
|
||||||
|
end
|
||||||
|
|
||||||
|
def draw_crop_marks(cc, x, y, sheet)
|
||||||
|
sheet.crop_coords(x, y, @deck.width, @deck.height).each do |coord|
|
||||||
|
cc.move_to(coord[:x1], coord[:y1])
|
||||||
|
cc.line_to(coord[:x2], coord[:y2])
|
||||||
|
cc.set_source_color(sheet.crop_stroke_color)
|
||||||
|
cc.set_dash(sheet.crop_stroke_dash)
|
||||||
|
cc.set_line_width(sheet.crop_stroke_width)
|
||||||
|
cc.stroke
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
@ -1 +1 @@
|
||||||
Subproject commit 2b3a6498fdf4668f5d7a30aa2773e4a0f1f1c4f2
|
Subproject commit 711ec5a0596a62f6b699a202b7f557d9cd5d20fe
|
||||||
|
|
@ -50,9 +50,31 @@ describe Squib::Args::Sheet do
|
||||||
opts = { columns: :infinite }
|
opts = { columns: :infinite }
|
||||||
expect { sheet.load!(opts) }.to raise_error('columns must be an integer')
|
expect { sheet.load!(opts) }.to raise_error('columns must be an integer')
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
end
|
end
|
||||||
|
|
||||||
|
context 'crop marks' do
|
||||||
|
subject(:sheet) { Squib::Args::Sheet.new({}, {}, 4) }
|
||||||
|
|
||||||
|
it 'computes crop marks properly' do
|
||||||
|
opts = {
|
||||||
|
width: 1000,
|
||||||
|
height: 1100,
|
||||||
|
trim: 10,
|
||||||
|
crop_margin_top: 20,
|
||||||
|
crop_margin_left: 30,
|
||||||
|
crop_margin_right: 40,
|
||||||
|
crop_margin_bottom: 50,
|
||||||
|
}
|
||||||
|
expect(sheet.load!(opts).crop_coords(3,4, 300, 400)).to eq(
|
||||||
|
[ {:x1=>43, :y1=>0, :x2=>43, :y2=>74},
|
||||||
|
{:x1=>253, :y1=>0, :x2=>253, :y2=>74},
|
||||||
|
{:x1=>43, :y1=>1100, :x2=>43, :y2=>1026},
|
||||||
|
{:x1=>253, :y1=>1100, :x2=>253, :y2=>1026},
|
||||||
|
{:x1=>0, :y1=>34, :x2=>74, :y2=>34},
|
||||||
|
{:x1=>1000, :y1=>34, :x2=>926, :y2=>34},
|
||||||
|
{:x1=>0, :y1=>344, :x2=>74, :y2=>344},
|
||||||
|
{:x1=>1000, :y1=>344, :x2=>926, :y2=>344}]
|
||||||
|
)
|
||||||
|
end
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|
|
||||||
File diff suppressed because it is too large
Load Diff
|
|
@ -45,7 +45,8 @@ describe Squib::Deck, '#save_pdf' do
|
||||||
expect(cxt).to receive(:target).and_return(target)
|
expect(cxt).to receive(:target).and_return(target)
|
||||||
expect(target).to receive(:finish).once
|
expect(target).to receive(:finish).once
|
||||||
|
|
||||||
args = { file: 'foo.pdf', dir: '_out', margin: 75, gap: 5, trim: 37 }
|
args = { file: 'foo.pdf', dir: '_out', crop_marks: false,
|
||||||
|
margin: 75, gap: 5, trim: 37 }
|
||||||
deck.save_pdf(args)
|
deck.save_pdf(args)
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -48,6 +48,7 @@ describe 'Squib samples' do
|
||||||
saves/_hand.rb
|
saves/_hand.rb
|
||||||
saves/_portrait_landscape.rb
|
saves/_portrait_landscape.rb
|
||||||
saves/_saves.rb
|
saves/_saves.rb
|
||||||
|
saves/_save_pdf.rb
|
||||||
saves/_showcase.rb
|
saves/_showcase.rb
|
||||||
shapes/_draw_shapes.rb
|
shapes/_draw_shapes.rb
|
||||||
text_options.rb
|
text_options.rb
|
||||||
|
|
@ -61,7 +62,7 @@ describe 'Squib samples' do
|
||||||
Dir.chdir(File.dirname("#{samples_dir}/#{sample}")) do
|
Dir.chdir(File.dirname("#{samples_dir}/#{sample}")) do
|
||||||
load full_sample_path
|
load full_sample_path
|
||||||
end
|
end
|
||||||
# overwrite_sample(sample, log) # Use TEMPORARILY once happy with the new sample log
|
overwrite_sample(sample, log) # Use TEMPORARILY once happy with the new sample log
|
||||||
test_file_str = File.open(sample_regression_file(sample), 'r:UTF-8').read
|
test_file_str = File.open(sample_regression_file(sample), 'r:UTF-8').read
|
||||||
expect(log.string).to eq(test_file_str)
|
expect(log.string).to eq(test_file_str)
|
||||||
end
|
end
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue