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:
# @api private
attr_reader :width, :height
attr_reader :width, :height, :backend, :svgfile
# :nodoc:
# @api private
@ -21,6 +21,7 @@ module Squib
@deck = deck
@width = width
@height = height
@backend = backend
@svgfile = "#{deck.dir}/#{deck.prefix}#{deck.count_format % index}.svg"
@cairo_surface = make_surface(@svgfile, backend)
@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)
p = needs(opts, [:range, :paper_width, :paper_height, :file_to_save,
:creatable_dir, :margin, :gap, :trim])
width = p[:width]
height = p[:height]
file = "#{p[:dir]}/#{p[:file]}"
cc = Cairo::Context.new(Cairo::PDFSurface.new(file, width, height))
x = p[:margin]
y = p[:margin]
paper_width = p[:width]
paper_height = p[:height]
file = "#{p[:dir]}/#{p[:file]}"
cc = Cairo::Context.new(Cairo::PDFSurface.new(file, paper_width, paper_height))
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|
surface = trim(@cards[i].cairo_surface, p[:trim], @width, @height)
cc.set_source(surface, x, y)
surface = @cards[i].cairo_surface
cc.set_source(surface, x, y)
cc.paint
card = @cards[i]
cc.translate(x,y)
cc.rectangle(p[:trim], p[:trim], card_width, card_height)
cc.clip
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
x += surface.width + p[:gap]
if x > (width - surface.width - p[:margin])
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 += surface.height + p[:gap]
if y > (height - surface.height - p[:margin])
x = p[:margin]
y = p[:margin]
cc.show_page #next page
y += card.height + p[:gap] - 2*p[:trim]
if y > (paper_height - card_height - p[:margin])
cc.show_page # next page
x,y = p[:margin],p[:margin]
end
end
end
@ -50,7 +64,7 @@ module Squib
# Lays out the cards in range and renders a stitched PNG sheet
#
# @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 colulmns [Integer] (1) the number of columns in the grid

2
lib/squib/graphics/shapes.rb

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

25
samples/backend.rb

@ -1,25 +1,26 @@
require 'squib'
# 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
background color: :white
background color: :gray
text str: "Hello, world!", y: 500, width: 825, font: 'Sans bold 72', align: :center
rect x: 10, y: 10, width: 20, height: 20
circle x: 40, y: 40, radius: 25
triangle x1: 50, y1: 15, x2: 60, y2: 25, x3: 75, y3: 25
rect x: 38, y: 38, width: 750, height: 1050, x_radius: 38, y_radius: 38
circle x: 100, y: 400, radius: 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
svg file: 'spanner.svg', x: 100, y: 20
svg file: 'glass-heart.svg', x: 100, y: 200, width: 100, height: 100, mask: :sangria
png file: 'shiny-purse.png', x: 250, y: 20
png file: 'shiny-purse.png', x: 250, y: 200, mask: :red
svg file: 'spanner.svg', x: 100, y: 75
png file: 'shiny-purse.png', x: 250, y: 75 # raster can still be used too
png file: 'shiny-purse.png', x: 250, y: 250, mask: :red # still renders as raster
# We can still rasterize whenever we want
save_png prefix: 'backend_'
# And our PDFs will be vectorized.
save_pdf file: 'backend_vectorized.pdf'
# And our PDFs will be vectorized .
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
# 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: restore([])
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: 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: restore([])
cairo: save([])
cairo: move_to([600, 600])
cairo: move_to([675, 600])
cairo: circle([600, 600, 75])
cairo: set_source_color([:green])
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: restore([])
cairo: save([])
cairo: move_to([415, 415])
cairo: move_to([515, 415])
cairo: circle([415, 415, 100])
cairo: set_source_color(["#0000"])
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: show_pango_layout([MockDouble])
cairo: restore([])
cairo: set_source([MockDouble, 75, 75])
cairo: paint([])
cairo: set_source([MockDouble, 75, 75])
cairo: paint([])
cairo: set_source([MockDouble, 75, 75])
cairo: paint([])
cairo: set_source([MockDouble, 75, 75])
cairo: paint([])
cairo: set_source([MockDouble, 75, 75])
cairo: paint([])
cairo: set_source([MockDouble, 75, 75])
cairo: paint([])
cairo: set_source([MockDouble, 75, 75])
cairo: paint([])
cairo: set_source([MockDouble, 75, 75])
cairo: paint([])
cairo: set_source([MockDouble, 75, 75])
cairo: paint([])
cairo: set_source([MockDouble, 75, 75])
cairo: paint([])
cairo: set_source([MockDouble, 75, 75])
cairo: paint([])
cairo: set_source([MockDouble, 75, 75])
cairo: paint([])
cairo: set_source([MockDouble, 75, 75])
cairo: paint([])
cairo: set_source([MockDouble, 75, 75])
cairo: paint([])
cairo: set_source([MockDouble, 75, 75])
cairo: paint([])
cairo: set_source([MockDouble, 75, 75])
cairo: paint([])
cairo: set_source([MockDouble, 75, 75])
cairo: paint([])
cairo: set_source([MockDouble, 75, 75])
cairo: paint([])
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([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"])
cairo: set_source([MockDouble, -37, -37])
cairo: paint([])

27
spec/graphics/graphics_save_doc_spec.rb

@ -2,29 +2,30 @@ require 'spec_helper'
require 'squib'
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
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
allow(Cairo::PDFSurface).to receive(:new).and_return(nil) #don't create the file
allow(Cairo::Context).to receive(:new).and_return(cxt)
end
it 'make all the expected calls on a smoke test' do
num_cards = 9
args = { 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: 9, width: 825, height: 1125)
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_card_place(75, 75)
@ -38,6 +39,7 @@ describe Squib::Deck, '#save_pdf' do
expect(cxt).to receive(:show_page).once
expect_card_place(75, 75)
args = { file: 'foo.pdf', dir: '_out', margin: 75, gap: 5, trim: 37 }
deck.save_pdf(args)
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 }
deck = Squib::Deck.new(cards: num_cards, width: 825, height: 1125)
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_card_place(75, 75)

2
spec/graphics/graphics_shapes_spec.rb

@ -37,7 +37,7 @@ describe Squib::Card do
context 'circle' do
it 'make all the expected calls on a smoke test' do
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_stroke('#fff', '#f00', 2.0)
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(:extents).and_return([Pango::Rectangle.new(0,0,0,0)]*2)
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
update_pango_layout width height show_pango_layout rounded_rectangle
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") }
end

Loading…
Cancel
Save