Browse Source

SVG backend: PDF implemented, tests passing

Wrapping up the svg backend work

Still some bugs to stamp out, but this is getting close to release ready.
dev
Andy Meneely 11 years ago
parent
commit
612c042d52
  1. 3
      lib/squib/card.rb
  2. 52
      lib/squib/graphics/save_doc.rb
  3. 2
      lib/squib/graphics/shapes.rb
  4. 25
      samples/backend.rb
  5. 7
      spec/data/samples/custom_config.rb.txt
  6. 2
      spec/data/samples/draw_shapes.rb.txt
  7. 2
      spec/data/samples/gradients.rb.txt
  8. 165
      spec/data/samples/saves.rb.txt
  9. 27
      spec/graphics/graphics_save_doc_spec.rb
  10. 2
      spec/graphics/graphics_shapes_spec.rb
  11. 3
      spec/spec_helper.rb

3
lib/squib/card.rb

@ -9,7 +9,7 @@ module Squib
# :nodoc: # :nodoc:
# @api private # @api private
attr_reader :width, :height attr_reader :width, :height, :backend, :svgfile
# :nodoc: # :nodoc:
# @api private # @api private
@ -21,6 +21,7 @@ module Squib
@deck = deck @deck = deck
@width = width @width = width
@height = height @height = height
@backend = backend
@svgfile = "#{deck.dir}/#{deck.prefix}#{deck.count_format % index}.svg" @svgfile = "#{deck.dir}/#{deck.prefix}#{deck.count_format % index}.svg"
@cairo_surface = make_surface(@svgfile, backend) @cairo_surface = make_surface(@svgfile, backend)
@cairo_context = Squib::Graphics::CairoContextWrapper.new(Cairo::Context.new(@cairo_surface)) @cairo_context = Squib::Graphics::CairoContextWrapper.new(Cairo::Context.new(@cairo_surface))

52
lib/squib/graphics/save_doc.rb

@ -19,28 +19,42 @@ module Squib
opts = {width: 3300, height: 2550}.merge(opts) opts = {width: 3300, height: 2550}.merge(opts)
p = needs(opts, [:range, :paper_width, :paper_height, :file_to_save, p = needs(opts, [:range, :paper_width, :paper_height, :file_to_save,
:creatable_dir, :margin, :gap, :trim]) :creatable_dir, :margin, :gap, :trim])
width = p[:width] paper_width = p[:width]
height = p[:height] paper_height = p[:height]
file = "#{p[:dir]}/#{p[:file]}" file = "#{p[:dir]}/#{p[:file]}"
cc = Cairo::Context.new(Cairo::PDFSurface.new(file, width, height)) cc = Cairo::Context.new(Cairo::PDFSurface.new(file, paper_width, paper_height))
x = p[:margin] x, y = p[:margin], p[:margin]
y = 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| @progress_bar.start("Saving PDF to #{file}", p[:range].size) do |bar|
p[:range].each do |i| p[:range].each do |i|
surface = trim(@cards[i].cairo_surface, p[:trim], @width, @height) card = @cards[i]
cc.set_source(surface, x, y) cc.translate(x,y)
surface = @cards[i].cairo_surface cc.rectangle(p[:trim], p[:trim], card_width, card_height)
cc.set_source(surface, x, y) cc.clip
cc.paint case card.backend
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) # FIXME I don't know why I need to do this to make it look 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 bar.increment
x += surface.width + p[:gap] cc.reset_clip
if x > (width - surface.width - p[:margin]) cc.translate(-x,-y)
x += card.width + p[:gap] - 2*p[:trim]
if x > (paper_width - card_width - p[:margin])
x = p[:margin] x = p[:margin]
y += surface.height + p[:gap] y += card.height + p[:gap] - 2*p[:trim]
if y > (height - surface.height - p[:margin]) if y > (paper_height - card_height - p[:margin])
x = p[:margin] cc.show_page # next page
y = p[:margin] x,y = p[:margin],p[:margin]
cc.show_page #next page
end end
end end
end end
@ -50,7 +64,7 @@ module Squib
# Lays out the cards in range and renders a stitched PNG sheet # Lays out the cards in range and renders a stitched PNG sheet
# #
# @example # @example
# save_png_sheet prefix: 'sheet_', margin: 75, gap: 5, trim: 37 # 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 [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] (1) the number of columns in the grid # @option opts colulmns [Integer] (1) the number of columns in the grid

2
lib/squib/graphics/shapes.rb

@ -21,7 +21,7 @@ module Squib
# @api private # @api private
def circle(x, y, radius, fill_color, stroke_color, stroke_width) def circle(x, y, radius, fill_color, stroke_color, stroke_width)
use_cairo do |cc| use_cairo do |cc|
cc.move_to(x,y) cc.move_to(x + radius, y)
cc.circle(x, y, radius) cc.circle(x, y, radius)
cc.set_source_squibcolor(stroke_color) cc.set_source_squibcolor(stroke_color)
cc.set_line_width(stroke_width) cc.set_line_width(stroke_width)

25
samples/backend.rb

@ -1,25 +1,26 @@
require 'squib' require 'squib'
# Our SVGs are auto-saved after each step using the configuration parameters # Our SVGs are auto-saved after each step using the configuration parameters
Squib::Deck.new(config: 'backend-config.yml') do Squib::Deck.new(cards: 2, config: 'backend-config.yml') do
# These are all supported by the SVG backend # These are all supported by the SVG backend
background color: :white background color: :gray
text str: "Hello, world!", y: 500, width: 825, font: 'Sans bold 72', align: :center text str: "Hello, world!", y: 500, width: 825, font: 'Sans bold 72', align: :center
rect x: 10, y: 10, width: 20, height: 20 rect x: 38, y: 38, width: 750, height: 1050, x_radius: 38, y_radius: 38
circle x: 40, y: 40, radius: 25 circle x: 100, y: 400, radius: 25
triangle x1: 50, y1: 15, x2: 60, y2: 25, x3: 75, y3: 25 triangle x1: 100, y1: 425, x2: 125, y2: 475, x3: 75, y3: 475
line x1: 100, y1: 620, x2: 720, y2: 620, stroke_width: 15.0 line x1: 100, y1: 620, x2: 720, y2: 620, stroke_width: 15.0
svg file: 'spanner.svg', x: 100, y: 20 svg file: 'spanner.svg', x: 100, y: 75
svg file: 'glass-heart.svg', x: 100, y: 200, width: 100, height: 100, mask: :sangria png file: 'shiny-purse.png', x: 250, y: 75 # raster can still be used too
png file: 'shiny-purse.png', x: 250, y: 20 png file: 'shiny-purse.png', x: 250, y: 250, mask: :red # still renders as raster
png file: 'shiny-purse.png', x: 250, y: 200, mask: :red
# We can still rasterize whenever we want # We can still rasterize whenever we want
save_png prefix: 'backend_' save_png prefix: 'backend_'
# And our PDFs will be vectorized. # And our PDFs will be vectorized .
save_pdf file: 'backend_vectorized.pdf' save_pdf file: 'backend_vectorized.pdf', gap: 5
# This one is a known issue. Masking an SVG onto an SVG backend is still buggy.
# svg file: 'glass-heart.svg', x: 100, y: 200, width: 100, height: 100, mask: :sangria
# This one is, unfortunately, not possible with svg back ends # This one is, unfortunately, not possible with svg back ends
# Cairo lacks a perspective transform (currently), so we have to # Cairo lacks a perspective transform (currently), so we have to

7
spec/data/samples/custom_config.rb.txt

@ -38,5 +38,10 @@ cairo: scale([1.0, 1.0])
cairo: render_rsvg_handle([RSVG::Handle, nil]) cairo: render_rsvg_handle([RSVG::Handle, nil])
cairo: restore([]) cairo: restore([])
surface: write_to_png(["_output/custom-config_00.png"]) surface: write_to_png(["_output/custom-config_00.png"])
cairo: set_source([MockDouble, 75, 75]) cairo: translate([75, 75])
cairo: rectangle([0, 0, 825, 1125])
cairo: clip([])
cairo: set_source([MockDouble, 0, 0])
cairo: paint([]) cairo: paint([])
cairo: reset_clip([])
cairo: translate([-75, -75])

2
spec/data/samples/draw_shapes.rb.txt

@ -8,7 +8,7 @@ cairo: set_source_color([:blue])
cairo: fill([]) cairo: fill([])
cairo: restore([]) cairo: restore([])
cairo: save([]) cairo: save([])
cairo: move_to([600, 600]) cairo: move_to([675, 600])
cairo: circle([600, 600, 75]) cairo: circle([600, 600, 75])
cairo: set_source_color([:green]) cairo: set_source_color([:green])
cairo: set_line_width([8.0]) cairo: set_line_width([8.0])

2
spec/data/samples/gradients.rb.txt

@ -10,7 +10,7 @@ cairo: set_line_width([15])
cairo: stroke([]) cairo: stroke([])
cairo: restore([]) cairo: restore([])
cairo: save([]) cairo: save([])
cairo: move_to([415, 415]) cairo: move_to([515, 415])
cairo: circle([415, 415, 100]) cairo: circle([415, 415, 100])
cairo: set_source_color(["#0000"]) cairo: set_source_color(["#0000"])
cairo: set_line_width([2.0]) cairo: set_line_width([2.0])

165
spec/data/samples/saves.rb.txt

@ -478,42 +478,135 @@ cairo: update_pango_layout([MockDouble])
cairo: update_pango_layout([MockDouble]) cairo: update_pango_layout([MockDouble])
cairo: show_pango_layout([MockDouble]) cairo: show_pango_layout([MockDouble])
cairo: restore([]) cairo: restore([])
cairo: set_source([MockDouble, 75, 75]) cairo: translate([75, 75])
cairo: paint([]) cairo: rectangle([37, 37, 751, 1051])
cairo: set_source([MockDouble, 75, 75]) cairo: clip([])
cairo: paint([]) cairo: set_source([MockDouble, 0, 0])
cairo: set_source([MockDouble, 75, 75]) cairo: paint([])
cairo: paint([]) cairo: reset_clip([])
cairo: set_source([MockDouble, 75, 75]) cairo: translate([-75, -75])
cairo: paint([]) cairo: translate([831, 75])
cairo: set_source([MockDouble, 75, 75]) cairo: rectangle([37, 37, 751, 1051])
cairo: paint([]) cairo: clip([])
cairo: set_source([MockDouble, 75, 75]) cairo: set_source([MockDouble, 0, 0])
cairo: paint([]) cairo: paint([])
cairo: set_source([MockDouble, 75, 75]) cairo: reset_clip([])
cairo: paint([]) cairo: translate([-831, -75])
cairo: set_source([MockDouble, 75, 75]) cairo: translate([1587, 75])
cairo: paint([]) cairo: rectangle([37, 37, 751, 1051])
cairo: set_source([MockDouble, 75, 75]) cairo: clip([])
cairo: paint([]) cairo: set_source([MockDouble, 0, 0])
cairo: set_source([MockDouble, 75, 75]) cairo: paint([])
cairo: paint([]) cairo: reset_clip([])
cairo: set_source([MockDouble, 75, 75]) cairo: translate([-1587, -75])
cairo: paint([]) cairo: translate([2343, 75])
cairo: set_source([MockDouble, 75, 75]) cairo: rectangle([37, 37, 751, 1051])
cairo: paint([]) cairo: clip([])
cairo: set_source([MockDouble, 75, 75]) cairo: set_source([MockDouble, 0, 0])
cairo: paint([]) cairo: paint([])
cairo: set_source([MockDouble, 75, 75]) cairo: reset_clip([])
cairo: paint([]) cairo: translate([-2343, -75])
cairo: set_source([MockDouble, 75, 75]) cairo: translate([75, 1131])
cairo: paint([]) cairo: rectangle([37, 37, 751, 1051])
cairo: set_source([MockDouble, 75, 75]) cairo: clip([])
cairo: paint([]) cairo: set_source([MockDouble, 0, 0])
cairo: set_source([MockDouble, 75, 75]) cairo: paint([])
cairo: paint([]) cairo: reset_clip([])
cairo: set_source([MockDouble, 75, 75]) cairo: translate([-75, -1131])
cairo: paint([]) cairo: translate([831, 1131])
cairo: rectangle([37, 37, 751, 1051])
cairo: clip([])
cairo: set_source([MockDouble, 0, 0])
cairo: paint([])
cairo: reset_clip([])
cairo: translate([-831, -1131])
cairo: translate([1587, 1131])
cairo: rectangle([37, 37, 751, 1051])
cairo: clip([])
cairo: set_source([MockDouble, 0, 0])
cairo: paint([])
cairo: reset_clip([])
cairo: translate([-1587, -1131])
cairo: translate([2343, 1131])
cairo: rectangle([37, 37, 751, 1051])
cairo: clip([])
cairo: set_source([MockDouble, 0, 0])
cairo: paint([])
cairo: reset_clip([])
cairo: translate([-2343, -1131])
cairo: show_page([])
cairo: translate([75, 75])
cairo: rectangle([37, 37, 751, 1051])
cairo: clip([])
cairo: set_source([MockDouble, 0, 0])
cairo: paint([])
cairo: reset_clip([])
cairo: translate([-75, -75])
cairo: translate([831, 75])
cairo: rectangle([37, 37, 751, 1051])
cairo: clip([])
cairo: set_source([MockDouble, 0, 0])
cairo: paint([])
cairo: reset_clip([])
cairo: translate([-831, -75])
cairo: translate([1587, 75])
cairo: rectangle([37, 37, 751, 1051])
cairo: clip([])
cairo: set_source([MockDouble, 0, 0])
cairo: paint([])
cairo: reset_clip([])
cairo: translate([-1587, -75])
cairo: translate([2343, 75])
cairo: rectangle([37, 37, 751, 1051])
cairo: clip([])
cairo: set_source([MockDouble, 0, 0])
cairo: paint([])
cairo: reset_clip([])
cairo: translate([-2343, -75])
cairo: translate([75, 1131])
cairo: rectangle([37, 37, 751, 1051])
cairo: clip([])
cairo: set_source([MockDouble, 0, 0])
cairo: paint([])
cairo: reset_clip([])
cairo: translate([-75, -1131])
cairo: translate([831, 1131])
cairo: rectangle([37, 37, 751, 1051])
cairo: clip([])
cairo: set_source([MockDouble, 0, 0])
cairo: paint([])
cairo: reset_clip([])
cairo: translate([-831, -1131])
cairo: translate([1587, 1131])
cairo: rectangle([37, 37, 751, 1051])
cairo: clip([])
cairo: set_source([MockDouble, 0, 0])
cairo: paint([])
cairo: reset_clip([])
cairo: translate([-1587, -1131])
cairo: translate([2343, 1131])
cairo: rectangle([37, 37, 751, 1051])
cairo: clip([])
cairo: set_source([MockDouble, 0, 0])
cairo: paint([])
cairo: reset_clip([])
cairo: translate([-2343, -1131])
cairo: show_page([])
cairo: translate([75, 75])
cairo: rectangle([0, 0, 825, 1125])
cairo: clip([])
cairo: set_source([MockDouble, 0, 0])
cairo: paint([])
cairo: reset_clip([])
cairo: translate([-75, -75])
cairo: translate([900, 75])
cairo: rectangle([0, 0, 825, 1125])
cairo: clip([])
cairo: set_source([MockDouble, 0, 0])
cairo: paint([])
cairo: reset_clip([])
cairo: translate([-900, -75])
cairo: show_page([])
surface: write_to_png(["_output/saves_notrim_01.png"]) surface: write_to_png(["_output/saves_notrim_01.png"])
cairo: set_source([MockDouble, -37, -37]) cairo: set_source([MockDouble, -37, -37])
cairo: paint([]) cairo: paint([])

27
spec/graphics/graphics_save_doc_spec.rb

@ -2,29 +2,30 @@ require 'spec_helper'
require 'squib' require 'squib'
describe Squib::Deck, '#save_pdf' do describe Squib::Deck, '#save_pdf' do
def expect_card_place(x, y)
expect(cxt).to receive(:set_source)
.with(instance_of(Cairo::ImageSurface), -37, -37)
.once # trim the card
expect(cxt).to receive(:paint).once # paint trimmed card
expect(cxt).to receive(:set_source) # place the card
.with(instance_of(Cairo::ImageSurface), x, y).at_least(1).times
expect(cxt).to receive(:paint).once # paint placed card
end
context 'typical inputs' do context 'typical inputs' do
let(:cxt) { double(Cairo::Context) } let(:cxt) { double(Cairo::Context) }
def expect_card_place(x, y)
expect(cxt).to receive(:translate).with(x, y).once
expect(cxt).to receive(:rectangle).once
expect(cxt).to receive(:clip).once
expect(cxt).to receive(:set_source) # place the card
.with(instance_of(Cairo::ImageSurface), 0, 0).once
expect(cxt).to receive(:paint).once # paint placed card
expect(cxt).to receive(:translate).with(-x,-y).once
expect(cxt).to receive(:reset_clip).once
end
before(:each) do before(:each) do
allow(Cairo::PDFSurface).to receive(:new).and_return(nil) #don't create the file allow(Cairo::PDFSurface).to receive(:new).and_return(nil) #don't create the file
allow(Cairo::Context).to receive(:new).and_return(cxt)
end end
it 'make all the expected calls on a smoke test' do it 'make all the expected calls on a smoke test' do
num_cards = 9 num_cards = 9
args = { file: 'foo.pdf', dir: '_out', margin: 75, gap: 5, trim: 37 } deck = Squib::Deck.new(cards: 9, width: 825, height: 1125)
deck = Squib::Deck.new(cards: num_cards, width: 825, height: 1125)
expect(Squib.logger).to receive(:debug).at_least(:once) expect(Squib.logger).to receive(:debug).at_least(:once)
expect(Cairo::Context).to receive(:new).and_return(cxt).at_least(num_cards + 1).times
expect(deck).to receive(:dirify) { |arg| arg } #don't create the dir expect(deck).to receive(:dirify) { |arg| arg } #don't create the dir
expect_card_place(75, 75) expect_card_place(75, 75)
@ -38,6 +39,7 @@ describe Squib::Deck, '#save_pdf' do
expect(cxt).to receive(:show_page).once expect(cxt).to receive(:show_page).once
expect_card_place(75, 75) expect_card_place(75, 75)
args = { file: 'foo.pdf', dir: '_out', margin: 75, gap: 5, trim: 37 }
deck.save_pdf(args) deck.save_pdf(args)
end end
@ -46,7 +48,6 @@ describe Squib::Deck, '#save_pdf' do
args = { range: 2..4, file: 'foo.pdf', dir: '_out', margin: 75, gap: 5, trim: 37 } 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) deck = Squib::Deck.new(cards: num_cards, width: 825, height: 1125)
expect(Squib.logger).to receive(:debug).at_least(:once) expect(Squib.logger).to receive(:debug).at_least(:once)
expect(Cairo::Context).to receive(:new).and_return(cxt).exactly(4).times
expect(deck).to receive(:dirify) { |arg| arg } #don't create the dir expect(deck).to receive(:dirify) { |arg| arg } #don't create the dir
expect_card_place(75, 75) expect_card_place(75, 75)

2
spec/graphics/graphics_shapes_spec.rb

@ -37,7 +37,7 @@ describe Squib::Card do
context 'circle' do context 'circle' do
it 'make all the expected calls on a smoke test' do it 'make all the expected calls on a smoke test' do
expect(@context).to receive(:save).once expect(@context).to receive(:save).once
expect(@context).to receive(:move_to).with(37, 38) expect(@context).to receive(:move_to).with(137, 38)
expect(@context).to receive(:circle).with(37, 38, 100).twice expect(@context).to receive(:circle).with(37, 38, 100).twice
expect_stroke('#fff', '#f00', 2.0) expect_stroke('#fff', '#f00', 2.0)
expect(@context).to receive(:restore).once expect(@context).to receive(:restore).once

3
spec/spec_helper.rb

@ -71,11 +71,12 @@ def mock_cairo(strio)
allow(pango).to receive(:width).and_return(25) allow(pango).to receive(:width).and_return(25)
allow(pango).to receive(:extents).and_return([Pango::Rectangle.new(0,0,0,0)]*2) allow(pango).to receive(:extents).and_return([Pango::Rectangle.new(0,0,0,0)]*2)
allow(Pango::FontDescription).to receive(:new).and_return(font) allow(Pango::FontDescription).to receive(:new).and_return(font)
allow(Cairo::PDFSurface).to receive(:new).and_return(nil)
%w(save set_source_color paint restore translate rotate move_to %w(save set_source_color paint restore translate rotate move_to
update_pango_layout width height show_pango_layout rounded_rectangle update_pango_layout width height show_pango_layout rounded_rectangle
set_line_width stroke fill set_source scale render_rsvg_handle circle set_line_width stroke fill set_source scale render_rsvg_handle circle
triangle line_to operator= show_page clip transform mask).each do |m| triangle line_to operator= show_page clip transform mask rectangle reset_clip).each do |m|
allow(cxt).to receive(m) { |*args| strio << scrub_hex("cairo: #{m}(#{args})\n") } allow(cxt).to receive(m) { |*args| strio << scrub_hex("cairo: #{m}(#{args})\n") }
end end

Loading…
Cancel
Save