# frozen_string_literal: true require 'spec_helper' describe Prawn::Document::BoundingBox do let(:pdf) { create_pdf } describe 'attributes' do let(:box) do described_class.new( nil, nil, [100, 125], width: 50, height: 75 ) end it 'has an anchor at (x, y - height)' do expect(box.anchor).to eq([100, 50]) end it 'has a left boundary of 0' do expect(box.left).to eq(0) end it 'has a right boundary equal to the width' do expect(box.right).to eq(50) end it 'has a top boundary of height' do expect(box.top).to eq(75) end it 'has a bottom boundary of 0' do expect(box.bottom).to eq(0) end it 'has a top-left of [0, height]' do expect(box.top_left).to eq([0, 75]) end it 'has a top-right of [width, height]' do expect(box.top_right).to eq([50, 75]) end it 'has a bottom-left of [0, 0]' do expect(box.bottom_left).to eq([0, 0]) end it 'has a bottom-right of [width ,0]' do expect(box.bottom_right).to eq([50, 0]) end it 'has an absolute left boundary of x' do expect(box.absolute_left).to eq(100) end it 'has an absolute right boundary of x + width' do expect(box.absolute_right).to eq(150) end it 'has an absolute top boundary of y' do expect(box.absolute_top).to eq(125) end it 'has an absolute bottom boundary of y - height' do expect(box.absolute_bottom).to eq(50) end it 'has an absolute bottom-left of [x, y - height]' do expect(box.absolute_bottom_left).to eq([100, 50]) end it 'has an absolute bottom-right of [x + width, y - height]' do expect(box.absolute_bottom_right).to eq([150, 50]) end it 'has an absolute top-left of [x, y]' do expect(box.absolute_top_left).to eq([100, 125]) end it 'has an absolute top-right of [x + width, y]' do expect(box.absolute_top_right).to eq([150, 125]) end end describe 'validations' do it 'requires width to be set' do expect do described_class.new(nil, nil, [100, 100]) end.to raise_error(ArgumentError) end it 'raise_errors an ArgumentError if a block is not passed' do pdf = Prawn::Document.new expect do pdf.bounding_box([0, 0], width: 200) end.to raise_error(ArgumentError) end end describe 'drawing' do it 'does not stomp on the arguments to bounding_box' do pdf = Prawn::Document.new x = [100, 500] pdf.bounding_box x, width: 100 do pdf.text 'bork-bork-bork' end expect(x).to eq([100, 500]) end it 'restores Document#bounds to the correct margin box on exit' do pdf = Prawn::Document.new(margin: 200) # add a multi-page bounding box pdf.bounding_box([100, pdf.bounds.top], width: 400) do pdf.text "The rain in spain falls mainly in the plains.\n" * 30 end pdf.start_new_page(margin: 0) x_min, y_min, x_max, y_max = pdf.page.dimensions expect(pdf.bounds.absolute_top_left).to eq([x_min, y_max]) expect(pdf.bounds.absolute_bottom_right).to eq([x_max, y_min]) end it 'restores the parent bounding box when calls are nested' do pdf.bounding_box [100, 500], width: 300, height: 300 do expect(pdf.bounds.absolute_top) .to eq(500 + pdf.margin_box.absolute_bottom) expect(pdf.bounds.absolute_left) .to eq(100 + pdf.margin_box.absolute_left) parent_box = pdf.bounds pdf.bounding_box [50, 200], width: 100, height: 100 do expect(pdf.bounds.absolute_top) .to eq(200 + parent_box.absolute_bottom) expect(pdf.bounds.absolute_left).to eq(50 + parent_box.absolute_left) end expect(pdf.bounds.absolute_top) .to eq(500 + pdf.margin_box.absolute_bottom) expect(pdf.bounds.absolute_left) .to eq(100 + pdf.margin_box.absolute_left) end end it 'calculates a height if none is specified' do pdf.bounding_box([100, 500], width: 100) do pdf.text 'The rain in Spain falls mainly on the plains.' end expect(pdf.y).to be_within(0.001).of(458.384) end it 'keeps track of the max height the box was stretched to' do box = pdf.bounding_box(pdf.bounds.top_left, width: 100) do pdf.move_down 100 pdf.move_up 15 end expect(box.height).to eq(100) end it 'advances the y-position by bbox.height by default' do orig_y = pdf.y pdf.bounding_box [0, pdf.cursor], width: pdf.bounds.width, height: 30 do pdf.text 'hello' end expect(pdf.y).to be_within(0.001).of(orig_y - 30) end it 'does not advance y-position if passed :hold_position => true' do orig_y = pdf.y pdf.bounding_box( [0, pdf.cursor], width: pdf.bounds.width, hold_position: true ) do pdf.text 'hello' end # y only advances by height of one line ("hello") expect(pdf.y).to be_within(0.001).of(orig_y - pdf.height_of('hello')) end it 'does not advance y-position of a stretchy bbox if it would stretch '\ 'the bbox further' do bottom = pdf.y = pdf.margin_box.absolute_bottom pdf.bounding_box [0, pdf.margin_box.top], width: pdf.bounds.width do pdf.y = bottom pdf.text 'hello' # starts a new page end expect(pdf.page_count).to eq(2) # Restoring the position (to the absolute bottom) would stretch the bbox # to the bottom of the page, which we don't want. This should be # equivalent to a bbox with :hold_position => true, where we only advance # by the amount that was actually drawn. expect(pdf.y).to be_within(0.001).of( pdf.margin_box.absolute_top - pdf.height_of('hello') ) end end describe 'Indentation' do it 'temporarilies shift the x coordinate and width' do pdf.bounding_box([100, 100], width: 200) do pdf.indent(20) do expect(pdf.bounds.absolute_left).to eq(120) expect(pdf.bounds.width).to eq(180) end end end it 'restores the x coordinate and width after block exits' do pdf.bounding_box([100, 100], width: 200) do pdf.indent(20) do # no-op end expect(pdf.bounds.absolute_left).to eq(100) expect(pdf.bounds.width).to eq(200) end end it 'restores the x coordinate and width on error' do pdf.bounding_box([100, 100], width: 200) do pdf.indent(20) { raise } rescue StandardError expect(pdf.bounds.absolute_left).to eq(100) expect(pdf.bounds.width).to eq(200) end end it 'maintains left indentation across a page break' do original_left = pdf.bounds.absolute_left pdf.indent(20) do expect(pdf.bounds.absolute_left).to eq(original_left + 20) pdf.start_new_page expect(pdf.bounds.absolute_left).to eq(original_left + 20) end expect(pdf.bounds.absolute_left).to eq(original_left) end it 'maintains right indentation across a page break' do original_width = pdf.bounds.width pdf.indent(0, 20) do expect(pdf.bounds.width).to eq(original_width - 20) pdf.start_new_page expect(pdf.bounds.width).to eq(original_width - 20) end expect(pdf.bounds.width).to eq(original_width) end it 'optionally allows adjustment of the right bound as well' do pdf.bounding_box([100, 100], width: 200) do pdf.indent(20, 30) do expect(pdf.bounds.absolute_left).to eq(120) expect(pdf.bounds.width).to eq(150) end expect(pdf.bounds.absolute_left).to eq(100) expect(pdf.bounds.width).to eq(200) end end describe 'in a ColumnBox' do it 'subtracts the given indentation from the available width' do pdf.column_box( [0, pdf.cursor], width: pdf.bounds.width, height: 200, columns: 2, spacer: 20 ) do width = pdf.bounds.width pdf.indent(20) do expect(pdf.bounds.width).to be_within(0.01).of(width - 20) end end end it 'subtracts right padding from the available width' do pdf.column_box( [0, pdf.cursor], width: pdf.bounds.width, height: 200, columns: 2, spacer: 20 ) do width = pdf.bounds.width pdf.indent(20, 30) do expect(pdf.bounds.width).to be_within(0.01).of(width - 50) end end end it 'maintains the same left indentation across column breaks' do pdf.column_box( [0, pdf.cursor], width: pdf.bounds.width, columns: 3, spacer: 15 ) do 3.times do |_column| x = pdf.bounds.left_side pdf.indent(20) do expect(pdf.bounds.left_side).to eq(x + 20) end pdf.bounds.move_past_bottom end end end it 'does not change the right margin if only left indentation is '\ 'requested' do pdf.column_box( [0, pdf.cursor], width: pdf.bounds.width, columns: 3, spacer: 15 ) do 3.times do |_column| x = pdf.bounds.right_side pdf.indent(20) do expect(pdf.bounds.right_side).to eq(x) end pdf.bounds.move_past_bottom end end end it 'maintains the same right indentation across columns' do pdf.column_box( [0, pdf.cursor], width: pdf.bounds.width, columns: 3, spacer: 15 ) do 3.times do |_column| x = pdf.bounds.right_side pdf.indent(20, 10) do expect(pdf.bounds.right_side).to eq(x - 10) end pdf.bounds.move_past_bottom end end end it 'keeps the right indentation after nesting indents' do pdf.column_box( [0, pdf.cursor], width: pdf.bounds.width, columns: 3, spacer: 15 ) do 3.times do |_column| # I am giving a right indent of 10... pdf.indent(20, 10) do x = pdf.bounds.right_side # ...and no right indent here... pdf.indent(20) do # right indent is inherited from the parent! expect(pdf.bounds.right_side).to eq(x) end end pdf.bounds.move_past_bottom end end end it 'reverts the right indentation if negative indent is given in '\ 'nested indent' do pdf.column_box( [0, pdf.cursor], width: pdf.bounds.width, columns: 3, spacer: 15 ) do 3.times do |_column| x = pdf.bounds.right_side pdf.indent(20, 10) do # requesting a negative right-indent of equivalent size... pdf.indent(20, -10) do # ...resets the right margin to that of the column! expect(pdf.bounds.right_side).to eq(x) end end pdf.bounds.move_past_bottom end end end it 'reduces the available column width by the sum of ' \ 'all nested indents' do pdf.column_box( [0, pdf.cursor], width: pdf.bounds.width, columns: 3, spacer: 15 ) do 3.times do |_column| w = pdf.bounds.width pdf.indent(20, 10) do pdf.indent(20, 10) do expect(pdf.bounds.width).to eq(w - 60) end end pdf.bounds.move_past_bottom end end end end end describe 'A canvas' do it 'uses whatever the last set y position is' do pdf.canvas do pdf.bounding_box([100, 500], width: 200) { pdf.move_down 50 } end expect(pdf.y).to eq(450) end it 'restores the original ypos after execution', issue: 523 do doc = Prawn::Document.new(skip_page_creation: true) doc.start_new_page original_ypos = doc.y doc.canvas do # draw end expect(doc.y).to eq(original_ypos) end end describe 'Deep-copying' do it 'creates a new object that does not copy @document' do Prawn::Document.new do |pdf| orig = pdf.bounds copy = orig.deep_copy expect(copy).to_not eq(pdf.bounds) expect(copy.document).to be_nil end end it 'deep-copies parent bounds' do Prawn::Document.new do |pdf| outside = pdf.bounds pdf.bounding_box [100, 100], width: 100 do copy = pdf.bounds.deep_copy # the parent bounds should have the same parameters expect(copy.parent.width).to eq(outside.width) expect(copy.parent.height).to eq(outside.height) # but should not be the same object expect(copy.parent).to_not eq(outside) end end end end describe 'Prawn::Document#reference_bounds' do it 'returns self for non-stretchy bounds' do pdf.bounding_box([0, pdf.cursor], width: 100, height: 100) do expect(pdf.reference_bounds).to eq(pdf.bounds) end end it 'returns the parent bounds if in a stretchy box' do pdf.bounding_box([0, pdf.cursor], width: 100, height: 100) do correct_bounds = pdf.bounds pdf.bounding_box([0, pdf.cursor], width: 100) do expect(pdf.reference_bounds).to eq(correct_bounds) end end end it 'finds the non-stretchy box through 2 levels' do pdf.bounding_box([0, pdf.cursor], width: 100, height: 100) do correct_bounds = pdf.bounds pdf.bounding_box([0, pdf.cursor], width: 100) do pdf.bounding_box([0, pdf.cursor], width: 100) do expect(pdf.reference_bounds).to eq(correct_bounds) end end end end it "returns the margin box if there's no explicit bbox" do expect(pdf.reference_bounds).to eq(pdf.margin_box) pdf.bounding_box([0, pdf.cursor], width: 100) do expect(pdf.reference_bounds).to eq(pdf.margin_box) end end it "returns the canvas box if we're in a canvas" do pdf.canvas do canvas_box = pdf.bounds expect(pdf.reference_bounds).to eq(canvas_box) pdf.bounding_box([0, pdf.cursor], width: 100) do expect(pdf.reference_bounds).to eq(canvas_box) end end end end describe '#move_past_bottom' do it 'ordinarilies start a new page' do pdf.bounds.move_past_bottom pdf.text 'Foo' pages = PDF::Inspector::Page.analyze(pdf.render).pages expect(pages.size).to eq(2) expect(pages[0][:strings]).to eq([]) expect(pages[1][:strings]).to eq(['Foo']) end it 'moves to the top of the next page if it exists already' do # save away the y-position at the top of a page top_y = pdf.y # create a blank page but go to the page before it pdf.start_new_page pdf.go_to_page 1 pdf.text 'Foo' pdf.bounds.move_past_bottom expect(pdf.y).to be_within(0.001).of(top_y) pdf.text 'Bar' pages = PDF::Inspector::Page.analyze(pdf.render).pages expect(pages.size).to eq(2) expect(pages[0][:strings]).to eq(['Foo']) expect(pages[1][:strings]).to eq(['Bar']) end end end