# -*- encoding: utf-8 -*- require 'test_helper' require 'hexapdf/document' require 'hexapdf/type/page_tree_node' describe HexaPDF::Type::PageTreeNode do before do @doc = HexaPDF::Document.new @root = @doc.catalog[:Pages] = @doc.add({Type: :Pages}) end # Defines the following page tree: # # @root # @kid1 # @kid11 # @pages[0] # @pages[1] # @kid12 # @pages[2] # @pages[3] # @pages[4] # @pages[5] # @kid2 # @pages[6] # @pages[7] def define_multilevel_page_tree @pages = Array.new(8) { @doc.add({Type: :Page}) } @kid1 = @doc.add({Type: :Pages, Parent: @root, Count: 5}) @kid11 = @doc.add({Type: :Pages, Parent: @kid1}) @kid11.add_page(@pages[0]) @kid11.add_page(@pages[1]) @kid12 = @doc.add({Type: :Pages, Parent: @kid1}) @kid12.add_page(@pages[2]) @kid12.add_page(@pages[3]) @kid12.add_page(@pages[4]) @kid1[:Kids] << @kid11 << @kid12 @root[:Kids] << @kid1 @pages[5][:Parent] = @root @root[:Kids] << @pages[5] @kid2 = @doc.add({Type: :Pages, Parent: @root}) @kid2.add_page(@pages[6]) @kid2.add_page(@pages[7]) @root[:Kids] << @kid2 @root[:Count] = 8 end it "must always be indirect" do pages = @doc.add({Type: :Pages}) pages.must_be_indirect = false assert(pages.must_be_indirect?) end describe "page" do before do define_multilevel_page_tree end it "returns the page for a given index" do assert_equal(@pages[0], @root.page(0)) assert_equal(@pages[3], @root.page(3)) assert_equal(@pages[5], @root.page(5)) assert_equal(@pages[7], @root.page(7)) end it "works with negative indicies counting backwards from the end" do assert_equal(@pages[0], @root.page(-8)) assert_equal(@pages[3], @root.page(-5)) assert_equal(@pages[5], @root.page(-3)) assert_equal(@pages[7], @root.page(-1)) end it "returns nil for bad indices" do assert_nil(@root.page(20)) assert_nil(@root.page(-20)) end end describe "insert_page" do it "uses an empty new page when none is provided, respecting the set configuration options" do @doc.config['page.default_media_box'] = :A4 @doc.config['page.default_media_orientation'] = :landscape page = @root.insert_page(3) assert_equal([page], @root[:Kids].value) assert_equal(1, @root.page_count) assert_equal(:Page, page[:Type]) assert_equal(@root, page[:Parent]) assert_kind_of(HexaPDF::Rectangle, page[:MediaBox]) assert_equal([0, 0, 842, 595], page[:MediaBox].value) assert_equal({}, page[:Resources].value) refute(@root.value.key?(:Parent)) end it "doesn't create a /Resources entry if an inherited one exists" do @root[:Resources] = {Font: {F1: nil}} page = @root.insert_page(3) assert_equal(@root[:Resources], page[:Resources]) end it "inserts the provided page at the given index" do page = @doc.wrap({Type: :Page}) assert_equal(page, @root.insert_page(3, page)) assert_equal([page], @root[:Kids].value) assert_equal(@root, page[:Parent]) refute(@root.value.key?(:Parent)) end it "inserts multiple pages correctly in an empty root node" do page3 = @root.insert_page(5) page1 = @root.insert_page(0) page2 = @root.insert_page(1) assert_equal([page1, page2, page3], @root[:Kids].value) assert_equal(3, @root.page_count) end it "inserts multiple pages correctly in a multilevel page tree" do define_multilevel_page_tree page = @root.insert_page(2) assert_equal([@pages[0], @pages[1], page], @kid11[:Kids].value) assert_equal(3, @kid11.page_count) assert_equal(6, @kid1.page_count) assert_equal(9, @root.page_count) page = @root.insert_page(4) assert_equal([@pages[2], page, @pages[3], @pages[4]], @kid12[:Kids].value) assert_equal(4, @kid12.page_count) assert_equal(7, @kid1.page_count) assert_equal(10, @root.page_count) page = @root.insert_page(8) assert_equal([@kid1, @pages[5], page, @kid2], @root[:Kids].value) assert_equal(11, @root.page_count) page = @root.insert_page(100) assert_equal([@kid1, @pages[5], @root[:Kids][2], @kid2, page], @root[:Kids].value) assert_equal(12, @root.page_count) end it "allows negative indices to be specified" do define_multilevel_page_tree page = @root.insert_page(-1) assert_equal(page, @root[:Kids][-1]) page = @root.insert_page(-4) assert_equal(page, @root[:Kids][2]) end end describe "delete_page" do before do define_multilevel_page_tree end it "deletes the correct page by index" do @root.delete_page(2) assert_equal(2, @kid12.page_count) assert_equal(4, @kid1.page_count) assert_equal(7, @root.page_count) assert(@pages[2].null?) @root.delete_page(4) assert_equal(6, @root.page_count) assert(@pages[5].null?) end it "deletes the given page" do @root.delete_page(@pages[2]) assert(@pages[2].null?) @root.delete_page(@pages[5]) assert(@pages[5].null?) end it "allows deleting a page from an intermediary node" do @kid1.delete_page(@pages[2]) assert_equal(7, @root.page_count) end it "does nothing if the page index is not valid" do @root.delete_page(20) @root.delete_page(-20) assert_equal(8, @root.page_count) end it "does nothing if the page has already been deleted" do @root.delete_page(@pages[2]) @root.delete_page(@pages[2]) assert_equal(7, @root.page_count) end it "fails if the page is not in its parent's /Kids array" do @kid12[:Kids].delete_at(0) assert_raises(HexaPDF::Error) { @root.delete_page(@pages[2]) } assert_equal(8, @root.page_count) end it "does nothing if the page is not part of the page tree" do pages = @doc.add({Type: :Pages, Count: 1}) page = @doc.add({Type: :Page, Parent: pages}) pages[:Kids] << page @root.delete_page(page) assert_equal(8, @root.page_count) end end describe "move_page" do before do define_multilevel_page_tree end it "moves the page to the first place" do @root.move_page(@pages[1], 0) assert_equal([@pages[1], @pages[0], *@pages[2..-1]], @root.each_page.to_a) assert(@root.validate) end it "works if the location stays the same" do @root.move_page(3, 3) assert_equal(@pages, @root.each_page.to_a) assert(@root.validate) @root.move_page(-2, -2) assert_equal(@pages, @root.each_page.to_a) assert(@root.validate) end it "moves the page to the correct location with a positive index" do @root.move_page(1, 3) assert_equal([@pages[0], @pages[2], @pages[3], @pages[1], *@pages[4..-1]], @root.each_page.to_a) assert(@root.validate) end it "moves the page to the last place" do @root.move_page(1, -1) assert_equal([@pages[0], *@pages[2..-1], @pages[1]], @root.each_page.to_a) assert(@root.validate) end it "moves the page to the correct location within the same parent node" do @root.move_page(2, 4) assert_equal([@pages[0], @pages[1], @pages[3], @pages[4], @pages[2], *@pages[5..-1]], @root.each_page.to_a) assert(@root.validate) @root.move_page(4, 3) assert_equal([@pages[0], @pages[1], @pages[3], @pages[2], @pages[4], *@pages[5..-1]], @root.each_page.to_a) assert(@root.validate) end it "fails if the index to the moving page is invalid" do assert_raises(HexaPDF::Error) { @root.move_page(10, 0) } end it "fails if the moving page was deleted/is null" do @doc.delete(@pages[0]) assert_raises(HexaPDF::Error) { @root.move_page(@pages[0], 3) } end it "fails if the page was not yet added to a page tree" do page = @doc.add({Type: :Page}) assert_raises(HexaPDF::Error) { @root.move_page(page, 3) } end it "fails if the page is not part of the page tree" do assert_raises(HexaPDF::Error) { @kid1.move_page(@pages[6], 3) } end end describe "each_page" do before do define_multilevel_page_tree end it "iterates over a simple, one-level page tree" do assert_equal([@pages[2], @pages[3], @pages[4]], @kid12.each_page.to_a) end it "iterates over a multilevel page tree" do assert_equal(@pages, @root.each_page.to_a) end end describe "validation" do it "only does validation on the document's root node" do @doc.catalog.delete(:Pages) assert(@root.validate) assert_equal(0, @root.page_count) end it "corrects faulty /Count entries" do define_multilevel_page_tree root_count = @root.page_count @root[:Count] = -5 kid_count = @kid12.page_count @kid12[:Count] = 100 called_msg = '' refute(@root.validate(auto_correct: false) {|msg, _| called_msg = msg }) assert_match(/Count.*invalid/, called_msg) assert(@root.validate) assert_equal(root_count, @root.page_count) assert_equal(kid_count, @kid12.page_count) end it "corrects faulty /Parent entries" do define_multilevel_page_tree @kid12.delete(:Parent) @kid2.delete(:Parent) called_msg = '' refute(@root.validate(auto_correct: false) {|msg, _| called_msg = msg }) assert_match(/Parent.*invalid/, called_msg) assert(@root.validate) assert_equal(@kid1, @kid12[:Parent]) assert_equal(@root, @kid2[:Parent]) end it "removes invalid objects from the page tree (like null objects)" do define_multilevel_page_tree assert(@root.validate(auto_correct: false) {|m, _| p m }) @doc.delete(@pages[3]) refute(@root.validate(auto_correct: false)) do |msg, _| assert_match(/invalid object/i, msg) end assert(@root.validate) assert_equal(2, @kid12[:Count]) assert_equal([@pages[2], @pages[4]], @kid12[:Kids].value) end it "needs at least one page node" do refute(@root.validate(auto_correct: false)) assert(@root.validate) assert_equal(1, @root.page_count) end end end