From 612c042d527650c6de3fa51edac77cccb1eda63f Mon Sep 17 00:00:00 2001 From: Andy Meneely Date: Tue, 24 Feb 2015 22:33:32 -0500 Subject: [PATCH] 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. --- lib/squib/card.rb | 3 +- lib/squib/graphics/save_doc.rb | 52 +++++--- lib/squib/graphics/shapes.rb | 2 +- samples/backend.rb | 25 ++-- spec/data/samples/custom_config.rb.txt | 7 +- spec/data/samples/draw_shapes.rb.txt | 2 +- spec/data/samples/gradients.rb.txt | 2 +- spec/data/samples/saves.rb.txt | 165 ++++++++++++++++++------ spec/graphics/graphics_save_doc_spec.rb | 27 ++-- spec/graphics/graphics_shapes_spec.rb | 2 +- spec/spec_helper.rb | 3 +- 11 files changed, 203 insertions(+), 87 deletions(-) diff --git a/lib/squib/card.rb b/lib/squib/card.rb index 868c1bb..f0c0a9e 100644 --- a/lib/squib/card.rb +++ b/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)) diff --git a/lib/squib/graphics/save_doc.rb b/lib/squib/graphics/save_doc.rb index c21149e..2f98705 100644 --- a/lib/squib/graphics/save_doc.rb +++ b/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 diff --git a/lib/squib/graphics/shapes.rb b/lib/squib/graphics/shapes.rb index 93a442f..dab31f2 100644 --- a/lib/squib/graphics/shapes.rb +++ b/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) diff --git a/samples/backend.rb b/samples/backend.rb index eef9c39..c8f57ba 100644 --- a/samples/backend.rb +++ b/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 diff --git a/spec/data/samples/custom_config.rb.txt b/spec/data/samples/custom_config.rb.txt index 988f1c7..0f260d5 100644 --- a/spec/data/samples/custom_config.rb.txt +++ b/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]) diff --git a/spec/data/samples/draw_shapes.rb.txt b/spec/data/samples/draw_shapes.rb.txt index 6c61857..9753850 100644 --- a/spec/data/samples/draw_shapes.rb.txt +++ b/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]) diff --git a/spec/data/samples/gradients.rb.txt b/spec/data/samples/gradients.rb.txt index d4f4a68..1eea627 100644 --- a/spec/data/samples/gradients.rb.txt +++ b/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]) diff --git a/spec/data/samples/saves.rb.txt b/spec/data/samples/saves.rb.txt index 771ac00..bcb1d50 100644 --- a/spec/data/samples/saves.rb.txt +++ b/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([]) diff --git a/spec/graphics/graphics_save_doc_spec.rb b/spec/graphics/graphics_save_doc_spec.rb index 48c68b6..0d5bd01 100644 --- a/spec/graphics/graphics_save_doc_spec.rb +++ b/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) diff --git a/spec/graphics/graphics_shapes_spec.rb b/spec/graphics/graphics_shapes_spec.rb index 08dff0e..1a33c6b 100644 --- a/spec/graphics/graphics_shapes_spec.rb +++ b/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 diff --git a/spec/spec_helper.rb b/spec/spec_helper.rb index b773814..f7c491c 100644 --- a/spec/spec_helper.rb +++ b/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