hexapdf 0.43.0 → 0.45.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/CHANGELOG.md +36 -0
- data/examples/027-composer_optional_content.rb +6 -4
- data/examples/030-pdfa.rb +13 -11
- data/lib/hexapdf/composer.rb +23 -0
- data/lib/hexapdf/content/canvas.rb +3 -3
- data/lib/hexapdf/content/canvas_composer.rb +1 -0
- data/lib/hexapdf/document/files.rb +7 -2
- data/lib/hexapdf/document/layout.rb +15 -3
- data/lib/hexapdf/document/metadata.rb +12 -1
- data/lib/hexapdf/font/type1/character_metrics.rb +1 -1
- data/lib/hexapdf/font/type1/font_metrics.rb +1 -1
- data/lib/hexapdf/layout/box.rb +180 -66
- data/lib/hexapdf/layout/box_fitter.rb +1 -0
- data/lib/hexapdf/layout/column_box.rb +18 -28
- data/lib/hexapdf/layout/container_box.rb +6 -6
- data/lib/hexapdf/layout/frame.rb +13 -94
- data/lib/hexapdf/layout/image_box.rb +4 -4
- data/lib/hexapdf/layout/list_box.rb +13 -31
- data/lib/hexapdf/layout/style.rb +8 -4
- data/lib/hexapdf/layout/table_box.rb +55 -58
- data/lib/hexapdf/layout/text_box.rb +84 -71
- data/lib/hexapdf/layout/text_fragment.rb +1 -1
- data/lib/hexapdf/layout/text_layouter.rb +7 -8
- data/lib/hexapdf/parser.rb +5 -2
- data/lib/hexapdf/rectangle.rb +4 -4
- data/lib/hexapdf/type/file_specification.rb +9 -5
- data/lib/hexapdf/type/form.rb +2 -2
- data/lib/hexapdf/type/graphics_state_parameter.rb +1 -1
- data/lib/hexapdf/version.rb +1 -1
- data/test/hexapdf/content/test_canvas_composer.rb +13 -8
- data/test/hexapdf/document/test_files.rb +5 -0
- data/test/hexapdf/document/test_layout.rb +16 -0
- data/test/hexapdf/document/test_metadata.rb +21 -0
- data/test/hexapdf/layout/test_box.rb +93 -37
- data/test/hexapdf/layout/test_box_fitter.rb +7 -0
- data/test/hexapdf/layout/test_column_box.rb +7 -13
- data/test/hexapdf/layout/test_container_box.rb +1 -1
- data/test/hexapdf/layout/test_frame.rb +7 -46
- data/test/hexapdf/layout/test_image_box.rb +14 -6
- data/test/hexapdf/layout/test_list_box.rb +26 -27
- data/test/hexapdf/layout/test_table_box.rb +47 -54
- data/test/hexapdf/layout/test_text_box.rb +83 -83
- data/test/hexapdf/test_composer.rb +20 -5
- data/test/hexapdf/test_parser.rb +8 -0
- data/test/hexapdf/test_serializer.rb +1 -0
- data/test/hexapdf/type/test_file_specification.rb +2 -1
- metadata +2 -2
| @@ -75,7 +75,7 @@ describe HexaPDF::Layout::TableBox::Cell do | |
| 75 75 | 
             
              describe "fit" do
         | 
| 76 76 | 
             
                it "fits a single box" do
         | 
| 77 77 | 
             
                  cell = create_cell(children: HexaPDF::Layout::Box.create(width: 20, height: 10))
         | 
| 78 | 
            -
                  cell.fit(100, 100, @frame)
         | 
| 78 | 
            +
                  assert(cell.fit(100, 100, @frame).success?)
         | 
| 79 79 | 
             
                  assert_equal(100, cell.width)
         | 
| 80 80 | 
             
                  assert_equal(22, cell.height)
         | 
| 81 81 | 
             
                  assert_equal(32, cell.preferred_width)
         | 
| @@ -84,7 +84,7 @@ describe HexaPDF::Layout::TableBox::Cell do | |
| 84 84 |  | 
| 85 85 | 
             
                it "fits a single box with horizontal aligning not being :left" do
         | 
| 86 86 | 
             
                  cell = create_cell(children: HexaPDF::Layout::Box.create(width: 20, height: 10, align: :center))
         | 
| 87 | 
            -
                  cell.fit(100, 100, @frame)
         | 
| 87 | 
            +
                  assert(cell.fit(100, 100, @frame).success?)
         | 
| 88 88 | 
             
                  assert_equal(66, cell.preferred_width)
         | 
| 89 89 | 
             
                end
         | 
| 90 90 |  | 
| @@ -92,7 +92,7 @@ describe HexaPDF::Layout::TableBox::Cell do | |
| 92 92 | 
             
                  box1 = HexaPDF::Layout::Box.create(width: 20, height: 10)
         | 
| 93 93 | 
             
                  box2 = HexaPDF::Layout::Box.create(width: 50, height: 15)
         | 
| 94 94 | 
             
                  cell = create_cell(children: [box1, box2])
         | 
| 95 | 
            -
                  cell.fit(100, 100, @frame)
         | 
| 95 | 
            +
                  assert(cell.fit(100, 100, @frame).success?)
         | 
| 96 96 | 
             
                  assert_equal(100, cell.width)
         | 
| 97 97 | 
             
                  assert_equal(37, cell.height)
         | 
| 98 98 | 
             
                  assert_equal(62, cell.preferred_width)
         | 
| @@ -103,23 +103,30 @@ describe HexaPDF::Layout::TableBox::Cell do | |
| 103 103 | 
             
                  box1 = HexaPDF::Layout::Box.create(width: 20, height: 10, align: :center)
         | 
| 104 104 | 
             
                  box2 = HexaPDF::Layout::Box.create(width: 50, height: 15)
         | 
| 105 105 | 
             
                  cell = create_cell(children: [box1, box2])
         | 
| 106 | 
            -
                  cell.fit(100, 100, @frame)
         | 
| 106 | 
            +
                  assert(cell.fit(100, 100, @frame).success?)
         | 
| 107 107 | 
             
                  assert_equal(66, cell.preferred_width)
         | 
| 108 108 | 
             
                end
         | 
| 109 109 |  | 
| 110 110 | 
             
                it "fits the cell even if it has no content" do
         | 
| 111 111 | 
             
                  cell = create_cell(children: nil)
         | 
| 112 | 
            -
                  cell.fit(100, 100, @frame)
         | 
| 112 | 
            +
                  assert(cell.fit(100, 100, @frame).success?)
         | 
| 113 113 | 
             
                  assert_equal(100, cell.width)
         | 
| 114 114 | 
             
                  assert_equal(12, cell.height)
         | 
| 115 115 | 
             
                  assert_equal(12, cell.preferred_width)
         | 
| 116 116 | 
             
                  assert_equal(12, cell.preferred_height)
         | 
| 117 117 | 
             
                end
         | 
| 118 118 |  | 
| 119 | 
            -
                it "doesn't fit  | 
| 119 | 
            +
                it "doesn't fit children that are too big" do
         | 
| 120 | 
            +
                  cell = create_cell(children: HexaPDF::Layout::Box.create(width: 300, height: 20))
         | 
| 121 | 
            +
                  assert(cell.fit(100, 100, @frame).failure?)
         | 
| 122 | 
            +
                  cell = create_cell(children: [HexaPDF::Layout::Box.create(width: 300, height: 20)])
         | 
| 123 | 
            +
                  assert(cell.fit(100, 100, @frame).failure?)
         | 
| 124 | 
            +
                end
         | 
| 125 | 
            +
             | 
| 126 | 
            +
                it "doesn't fit anything if the available width or height are too small even if there are no children" do
         | 
| 120 127 | 
             
                  cell = create_cell(children: nil)
         | 
| 121 | 
            -
                   | 
| 122 | 
            -
                   | 
| 128 | 
            +
                  assert(cell.fit(10, 100, @frame).failure?)
         | 
| 129 | 
            +
                  assert(cell.fit(100, 10, @frame).failure?)
         | 
| 123 130 | 
             
                end
         | 
| 124 131 | 
             
              end
         | 
| 125 132 |  | 
| @@ -370,8 +377,8 @@ describe HexaPDF::Layout::TableBox do | |
| 370 377 | 
             
                HexaPDF::Layout::TableBox.new(cells: [@fixed_size_boxes[0, 2], @fixed_size_boxes[2, 2]], **kwargs)
         | 
| 371 378 | 
             
              end
         | 
| 372 379 |  | 
| 373 | 
            -
              def check_box(box,  | 
| 374 | 
            -
                 | 
| 380 | 
            +
              def check_box(box, fit_status, width, height, cell_data = nil)
         | 
| 381 | 
            +
                assert_equal(fit_status, box.fit(@frame.available_width, @frame.available_height, @frame).status)
         | 
| 375 382 | 
             
                assert_equal(width, box.width, "box width")
         | 
| 376 383 | 
             
                assert_equal(height, box.height, "box height")
         | 
| 377 384 | 
             
                if cell_data
         | 
| @@ -431,8 +438,8 @@ describe HexaPDF::Layout::TableBox do | |
| 431 438 | 
             
                  footer = lambda {|_| [[nil]] }
         | 
| 432 439 | 
             
                  box = create_box(header: header, footer: footer, cells: [[nil], [nil]],
         | 
| 433 440 | 
             
                                   cell_style: {background_color: 'black'})
         | 
| 434 | 
            -
                   | 
| 435 | 
            -
                  box_a, box_b = box.split | 
| 441 | 
            +
                  assert(box.fit(100, 40, @frame).overflow?)
         | 
| 442 | 
            +
                  box_a, box_b = box.split
         | 
| 436 443 | 
             
                  assert_same(box_a, box)
         | 
| 437 444 | 
             
                  assert_equal('black', box_b.header_cells[0, 0].style.background_color)
         | 
| 438 445 | 
             
                  assert_equal('black', box_b.footer_cells[0, 0].style.background_color)
         | 
| @@ -482,23 +489,13 @@ describe HexaPDF::Layout::TableBox do | |
| 482 489 | 
             
                  assert_equal(61, box.height)
         | 
| 483 490 | 
             
                end
         | 
| 484 491 |  | 
| 485 | 
            -
                it "cannot fit the table if the  | 
| 486 | 
            -
                  box = create_box(width: 200)
         | 
| 487 | 
            -
                  refute(box.fit(@frame.available_width, @frame.available_height, @frame))
         | 
| 488 | 
            -
                end
         | 
| 489 | 
            -
             | 
| 490 | 
            -
                it "cannot fit the table if the available height smaller than the initial height" do
         | 
| 491 | 
            -
                  box = create_box(height: 200)
         | 
| 492 | 
            -
                  refute(box.fit(@frame.available_width, @frame.available_height, @frame))
         | 
| 493 | 
            -
                end
         | 
| 494 | 
            -
             | 
| 495 | 
            -
                it "cannot fit the table if the specified column widths are smaller than the available width" do
         | 
| 492 | 
            +
                it "cannot fit the table if the specified column widths are greater than the available width" do
         | 
| 496 493 | 
             
                  box = create_box(column_widths: [200])
         | 
| 497 | 
            -
                   | 
| 494 | 
            +
                  assert(box.fit(@frame.available_width, @frame.available_height, @frame).failure?)
         | 
| 498 495 | 
             
                end
         | 
| 499 496 |  | 
| 500 497 | 
             
                it "fits a simple table" do
         | 
| 501 | 
            -
                  check_box(create_box,  | 
| 498 | 
            +
                  check_box(create_box, :success, 160, 45,
         | 
| 502 499 | 
             
                            [[0, 0, 79.5, 22], [79.5, 0, 79.5, 22], [0, 22, 79.5, 22], [79.5, 22, 79.5, 22]])
         | 
| 503 500 | 
             
                end
         | 
| 504 501 |  | 
| @@ -507,7 +504,7 @@ describe HexaPDF::Layout::TableBox do | |
| 507 504 | 
             
                           [{col_span: 2, row_span: 2, content: @fixed_size_boxes[3]}, *@fixed_size_boxes[4, 2]],
         | 
| 508 505 | 
             
                           [{row_span: 2, content: @fixed_size_boxes[6]}, @fixed_size_boxes[7]],
         | 
| 509 506 | 
             
                           @fixed_size_boxes[8, 3]]
         | 
| 510 | 
            -
                  check_box(create_box(cells: cells),  | 
| 507 | 
            +
                  check_box(create_box(cells: cells), :success, 160, 89,
         | 
| 511 508 | 
             
                            [[0, 0, 39.75, 22], [39.75, 0, 79.5, 22], [39.75, 0, 79.50, 22], [119.25, 0, 39.75, 22],
         | 
| 512 509 | 
             
                             [0, 22, 79.5, 44], [0, 22, 79.5, 44], [79.5, 22, 39.75, 22], [119.25, 22, 39.75, 22],
         | 
| 513 510 | 
             
                             [0, 22, 79.5, 44], [0, 22, 79.5, 44], [79.5, 44, 39.75, 44], [119.25, 44, 39.75, 22],
         | 
| @@ -518,7 +515,7 @@ describe HexaPDF::Layout::TableBox do | |
| 518 515 | 
             
                  result = [[0, 0, 80, 10], [80, 0, 80, 10], [0, 10, 80, 10], [80, 10, 80, 10]]
         | 
| 519 516 | 
             
                  header = lambda {|_| [@fixed_size_boxes[10, 2], @fixed_size_boxes[12, 2]] }
         | 
| 520 517 | 
             
                  box = create_box(header: header, cell_style: {padding: 0, border: {width: 0}})
         | 
| 521 | 
            -
                  box = check_box(box,  | 
| 518 | 
            +
                  box = check_box(box, :success, 160, 40, result)
         | 
| 522 519 | 
             
                  assert_equal(result, cell_infos(box.header_cells))
         | 
| 523 520 | 
             
                end
         | 
| 524 521 |  | 
| @@ -526,7 +523,7 @@ describe HexaPDF::Layout::TableBox do | |
| 526 523 | 
             
                  result = [[0, 0, 80, 10], [80, 0, 80, 10], [0, 10, 80, 10], [80, 10, 80, 10]]
         | 
| 527 524 | 
             
                  footer = lambda {|_| [@fixed_size_boxes[10, 2], @fixed_size_boxes[12, 2]] }
         | 
| 528 525 | 
             
                  box = create_box(footer: footer, cell_style: {padding: 0, border: {width: 0}})
         | 
| 529 | 
            -
                  box = check_box(box,  | 
| 526 | 
            +
                  box = check_box(box, :success, 160, 40, result)
         | 
| 530 527 | 
             
                  assert_equal(result, cell_infos(box.footer_cells))
         | 
| 531 528 | 
             
                end
         | 
| 532 529 |  | 
| @@ -535,14 +532,22 @@ describe HexaPDF::Layout::TableBox do | |
| 535 532 | 
             
                  cell_creator = lambda {|_| [@fixed_size_boxes[10, 2], @fixed_size_boxes[12, 2]] }
         | 
| 536 533 | 
             
                  box = create_box(header: cell_creator, footer: cell_creator,
         | 
| 537 534 | 
             
                                   cell_style: {padding: 0, border: {width: 0}})
         | 
| 538 | 
            -
                  box = check_box(box,  | 
| 535 | 
            +
                  box = check_box(box, :success, 160, 60, result)
         | 
| 539 536 | 
             
                  assert_equal(result, cell_infos(box.header_cells))
         | 
| 540 537 | 
             
                  assert_equal(result, cell_infos(box.footer_cells))
         | 
| 541 538 | 
             
                end
         | 
| 542 539 |  | 
| 540 | 
            +
                it "fails if the header or footer rows don't fit" do
         | 
| 541 | 
            +
                  cells_creator = lambda {|_| [@fixed_size_boxes[10, 2]] }
         | 
| 542 | 
            +
                  [{header: cells_creator}, {footer: cells_creator}].each do |args|
         | 
| 543 | 
            +
                    box = create_box(**args, cell_style: {padding: 0, border: {width: 0}})
         | 
| 544 | 
            +
                    assert(box.fit(100, 15, @frame).failure?)
         | 
| 545 | 
            +
                  end
         | 
| 546 | 
            +
                end
         | 
| 547 | 
            +
             | 
| 543 548 | 
             
                it "partially fits a table if not enough height is available" do
         | 
| 544 549 | 
             
                  box = create_box(height: 10, cell_style: {padding: 0, border: {width: 0}})
         | 
| 545 | 
            -
                  check_box(box,  | 
| 550 | 
            +
                  check_box(box, :overflow, 160, 10,
         | 
| 546 551 | 
             
                            [[0, 0, 80, 10], [80, 0, 80, 10], [nil, nil, 80, 0], [nil, nil, 0, 0]])
         | 
| 547 552 | 
             
                end
         | 
| 548 553 | 
             
              end
         | 
| @@ -550,8 +555,8 @@ describe HexaPDF::Layout::TableBox do | |
| 550 555 | 
             
              describe "split" do
         | 
| 551 556 | 
             
                it "splits the table if some rows could not be fit into the available region" do
         | 
| 552 557 | 
             
                  box = create_box
         | 
| 553 | 
            -
                   | 
| 554 | 
            -
                  box_a, box_b = box.split | 
| 558 | 
            +
                  assert(box.fit(100, 25, @frame).overflow?)
         | 
| 559 | 
            +
                  box_a, box_b = box.split
         | 
| 555 560 | 
             
                  assert_same(box_a, box)
         | 
| 556 561 | 
             
                  assert(box_b.split_box?)
         | 
| 557 562 |  | 
| @@ -567,8 +572,8 @@ describe HexaPDF::Layout::TableBox do | |
| 567 572 | 
             
                           [HexaPDF::Layout::Box.new(width: 20, height: 150, &@draw_block)]]
         | 
| 568 573 | 
             
                  box = create_box(cells: cells)
         | 
| 569 574 |  | 
| 570 | 
            -
                   | 
| 571 | 
            -
                  box_a, box_b = box.split | 
| 575 | 
            +
                  assert(box.fit(100, 100, @frame).overflow?)
         | 
| 576 | 
            +
                  box_a, box_b = box.split
         | 
| 572 577 | 
             
                  assert_same(box_a, box)
         | 
| 573 578 | 
             
                  assert(box_b.split_box?)
         | 
| 574 579 | 
             
                  assert_equal(0, box_a.start_row_index)
         | 
| @@ -576,8 +581,8 @@ describe HexaPDF::Layout::TableBox do | |
| 576 581 | 
             
                  assert_equal(1, box_b.start_row_index)
         | 
| 577 582 | 
             
                  assert_equal(-1, box_b.last_fitted_row_index)
         | 
| 578 583 |  | 
| 579 | 
            -
                   | 
| 580 | 
            -
                  box_c, box_d = box_b.split | 
| 584 | 
            +
                  assert(box_b.fit(100, 100, @frame).failure?)
         | 
| 585 | 
            +
                  box_c, box_d = box_b.split
         | 
| 581 586 | 
             
                  assert_nil(box_c)
         | 
| 582 587 | 
             
                  assert_same(box_d, box_b)
         | 
| 583 588 | 
             
                  assert(box_d.split_box?)
         | 
| @@ -585,24 +590,12 @@ describe HexaPDF::Layout::TableBox do | |
| 585 590 | 
             
                  assert_equal(-1, box_d.last_fitted_row_index)
         | 
| 586 591 | 
             
                end
         | 
| 587 592 |  | 
| 588 | 
            -
                it "splits the table if the header or footer rows don't fit" do
         | 
| 589 | 
            -
                  cells_creator = lambda {|_| [@fixed_size_boxes[10, 2]] }
         | 
| 590 | 
            -
                  [{header: cells_creator}, {footer: cells_creator}].each do |args|
         | 
| 591 | 
            -
                    box = create_box(**args)
         | 
| 592 | 
            -
                    box.cells.style(padding: 0, border: {width: 0})
         | 
| 593 | 
            -
                    refute(box.fit(100, 25, @frame))
         | 
| 594 | 
            -
                    box_a, box_b = box.split(100, 25, @frame)
         | 
| 595 | 
            -
                    assert_nil(box_a)
         | 
| 596 | 
            -
                    assert_same(box_b, box)
         | 
| 597 | 
            -
                  end
         | 
| 598 | 
            -
                end
         | 
| 599 | 
            -
             | 
| 600 593 | 
             
                it "splits a table with a header or a footer" do
         | 
| 601 594 | 
             
                  cells_creator = lambda {|_| [@fixed_size_boxes[10, 2]] }
         | 
| 602 595 | 
             
                  [{header: cells_creator}, {footer: cells_creator}].each do |args|
         | 
| 603 596 | 
             
                    box = create_box(**args)
         | 
| 604 | 
            -
                     | 
| 605 | 
            -
                    box_a, box_b = box.split | 
| 597 | 
            +
                    assert(box.fit(100, 50, @frame).overflow?)
         | 
| 598 | 
            +
                    box_a, box_b = box.split
         | 
| 606 599 | 
             
                    assert_same(box_a, box)
         | 
| 607 600 |  | 
| 608 601 | 
             
                    assert_equal(0, box_a.start_row_index)
         | 
| @@ -681,9 +674,9 @@ describe HexaPDF::Layout::TableBox do | |
| 681 674 |  | 
| 682 675 | 
             
                it "correctly works for split boxes" do
         | 
| 683 676 | 
             
                  box = create_box(cell_style: {padding: 0, border: {width: 0}})
         | 
| 684 | 
            -
                   | 
| 685 | 
            -
                  _, split_box = box.split | 
| 686 | 
            -
                  assert(split_box.fit(100, 100, @frame))
         | 
| 677 | 
            +
                  assert(box.fit(100, 10, @frame).overflow?)
         | 
| 678 | 
            +
                  _, split_box = box.split
         | 
| 679 | 
            +
                  assert(split_box.fit(100, 100, @frame).success?)
         | 
| 687 680 |  | 
| 688 681 | 
             
                  box.draw(@canvas, 20, 10)
         | 
| 689 682 | 
             
                  split_box.draw(@canvas, 0, 50)
         | 
| @@ -714,7 +707,7 @@ describe HexaPDF::Layout::TableBox do | |
| 714 707 | 
             
                  box = create_box(header: lambda {|_| [@fixed_size_boxes[10, 1]] },
         | 
| 715 708 | 
             
                                   footer: lambda {|_| [@fixed_size_boxes[12, 1]] },
         | 
| 716 709 | 
             
                                   cell_style: {padding: 0, border: {width: 0}})
         | 
| 717 | 
            -
                  box.fit(100, 100, @frame)
         | 
| 710 | 
            +
                  assert(box.fit(100, 100, @frame).success?)
         | 
| 718 711 | 
             
                  box.draw(@canvas, 20, 10)
         | 
| 719 712 | 
             
                  operators = [[:save_graphics_state],
         | 
| 720 713 | 
             
                               [:concatenate_matrix, [1, 0, 0, 1, 20, 40]],
         | 
| @@ -36,39 +36,40 @@ describe HexaPDF::Layout::TextBox do | |
| 36 36 | 
             
              describe "fit" do
         | 
| 37 37 | 
             
                it "fits into a rectangular area" do
         | 
| 38 38 | 
             
                  box = create_box([@inline_box] * 5, style: {padding: 10})
         | 
| 39 | 
            -
                  assert(box.fit(100, 100, @frame))
         | 
| 39 | 
            +
                  assert(box.fit(100, 100, @frame).success?)
         | 
| 40 40 | 
             
                  assert_equal(70, box.width)
         | 
| 41 41 | 
             
                  assert_equal(30, box.height)
         | 
| 42 42 | 
             
                end
         | 
| 43 43 |  | 
| 44 44 | 
             
                it "respects the set width and height" do
         | 
| 45 | 
            -
                  box = create_box([@inline_box], width:  | 
| 46 | 
            -
             | 
| 47 | 
            -
                   | 
| 45 | 
            +
                  box = create_box([@inline_box] * 5, width: 44, height: 50,
         | 
| 46 | 
            +
                                   style: {padding: 10, text_align: :right, text_valign: :bottom})
         | 
| 47 | 
            +
                  assert(box.fit(100, 100, @frame).success?)
         | 
| 48 | 
            +
                  assert_equal(44, box.width)
         | 
| 48 49 | 
             
                  assert_equal(50, box.height)
         | 
| 49 | 
            -
                  assert_equal([10], box.instance_variable_get(:@result).lines.map(&:width))
         | 
| 50 | 
            +
                  assert_equal([20, 20, 10], box.instance_variable_get(:@result).lines.map(&:width))
         | 
| 50 51 | 
             
                end
         | 
| 51 52 |  | 
| 52 | 
            -
                 | 
| 53 | 
            -
                   | 
| 54 | 
            -
             | 
| 55 | 
            -
             | 
| 56 | 
            -
             | 
| 57 | 
            -
             | 
| 58 | 
            -
                   | 
| 59 | 
            -
                end
         | 
| 53 | 
            +
                describe "style option last_line_gap" do
         | 
| 54 | 
            +
                  it "is taken into account" do
         | 
| 55 | 
            +
                    box = create_box([@inline_box] * 5, style: {last_line_gap: true, line_spacing: :double})
         | 
| 56 | 
            +
                    assert(box.fit(100, 100, @frame).success?)
         | 
| 57 | 
            +
                    assert_equal(50, box.width)
         | 
| 58 | 
            +
                    assert_equal(20, box.height)
         | 
| 59 | 
            +
                  end
         | 
| 60 60 |  | 
| 61 | 
            -
             | 
| 62 | 
            -
             | 
| 63 | 
            -
             | 
| 64 | 
            -
             | 
| 65 | 
            -
             | 
| 61 | 
            +
                  it "will have no effect for fixed-height boxes" do
         | 
| 62 | 
            +
                    box = create_box([@inline_box] * 5, height: 40, style: {last_line_gap: true, line_spacing: :double})
         | 
| 63 | 
            +
                    assert(box.fit(100, 100, @frame).success?)
         | 
| 64 | 
            +
                    assert_equal(50, box.width)
         | 
| 65 | 
            +
                    assert_equal(40, box.height)
         | 
| 66 | 
            +
                  end
         | 
| 66 67 | 
             
                end
         | 
| 67 68 |  | 
| 68 | 
            -
                it "uses the whole available width when aligning to the center or  | 
| 69 | 
            -
                  [:center, :right].each do |align|
         | 
| 69 | 
            +
                it "uses the whole available width when aligning to the center, right or justified" do
         | 
| 70 | 
            +
                  [:center, :right, :justify].each do |align|
         | 
| 70 71 | 
             
                    box = create_box([@inline_box], style: {text_align: align})
         | 
| 71 | 
            -
                    assert(box.fit(100, 100, @frame))
         | 
| 72 | 
            +
                    assert(box.fit(100, 100, @frame).success?)
         | 
| 72 73 | 
             
                    assert_equal(100, box.width)
         | 
| 73 74 | 
             
                  end
         | 
| 74 75 | 
             
                end
         | 
| @@ -76,91 +77,89 @@ describe HexaPDF::Layout::TextBox do | |
| 76 77 | 
             
                it "uses the whole available height when vertically aligning to the center or bottom" do
         | 
| 77 78 | 
             
                  [:center, :bottom].each do |valign|
         | 
| 78 79 | 
             
                    box = create_box([@inline_box], style: {text_valign: valign})
         | 
| 79 | 
            -
                    assert(box.fit(100, 100, @frame))
         | 
| 80 | 
            +
                    assert(box.fit(100, 100, @frame).success?)
         | 
| 80 81 | 
             
                    assert_equal(100, box.height)
         | 
| 81 82 | 
             
                  end
         | 
| 82 83 | 
             
                end
         | 
| 83 84 |  | 
| 84 | 
            -
                it " | 
| 85 | 
            +
                it "can fit part of the box" do
         | 
| 85 86 | 
             
                  box = create_box([@inline_box] * 20, height: 15)
         | 
| 86 | 
            -
                   | 
| 87 | 
            -
                  box.style.overflow = :truncate
         | 
| 88 | 
            -
                  assert(box.fit(100, 100, @frame))
         | 
| 89 | 
            -
             | 
| 90 | 
            -
                  box = create_box([@inline_box] * 20, style: {overflow: :truncate})
         | 
| 91 | 
            -
                  refute(box.fit(100, 15, @frame))
         | 
| 92 | 
            -
                end
         | 
| 93 | 
            -
             | 
| 94 | 
            -
                it "can't fit the text box if the set width is bigger than the available width" do
         | 
| 95 | 
            -
                  box = create_box([@inline_box], width: 101)
         | 
| 96 | 
            -
                  refute(box.fit(100, 100, @frame))
         | 
| 87 | 
            +
                  assert(box.fit(100, 100, @frame).overflow?)
         | 
| 97 88 | 
             
                end
         | 
| 98 89 |  | 
| 99 | 
            -
                it " | 
| 100 | 
            -
                   | 
| 101 | 
            -
             | 
| 90 | 
            +
                it "correctly handles text indentation for split boxes" do
         | 
| 91 | 
            +
                  [{}, {position: :flow}].each do |styles|
         | 
| 92 | 
            +
                    box = create_box([@inline_box] * 202, style: {text_indent: 50, **styles})
         | 
| 93 | 
            +
                    assert(box.fit(100, 100, @frame).overflow?)
         | 
| 94 | 
            +
                    _, box_b = box.split
         | 
| 95 | 
            +
                    assert_equal(107, box_b.instance_variable_get(:@items).length)
         | 
| 96 | 
            +
                    assert(box_b.fit(100, 100, @frame).overflow?)
         | 
| 97 | 
            +
                    _, box_b = box_b.split
         | 
| 98 | 
            +
                    assert_equal(7, box_b.instance_variable_get(:@items).length)
         | 
| 99 | 
            +
                  end
         | 
| 102 100 | 
             
                end
         | 
| 103 | 
            -
              end
         | 
| 104 101 |  | 
| 105 | 
            -
             | 
| 106 | 
            -
                it "works for an empty text box" do
         | 
| 102 | 
            +
                it "fits an empty text box" do
         | 
| 107 103 | 
             
                  box = create_box([])
         | 
| 108 | 
            -
                   | 
| 104 | 
            +
                  assert(box.fit(100, 100, @frame).success?)
         | 
| 109 105 | 
             
                end
         | 
| 110 106 |  | 
| 111 | 
            -
                 | 
| 112 | 
            -
                   | 
| 113 | 
            -
             | 
| 114 | 
            -
             | 
| 107 | 
            +
                describe "position :flow" do
         | 
| 108 | 
            +
                  it "fits into the frame's outline" do
         | 
| 109 | 
            +
                    @frame.remove_area(Geom2D::Rectangle(0, 80, 20, 20))
         | 
| 110 | 
            +
                    @frame.remove_area(Geom2D::Rectangle(80, 70, 20, 20))
         | 
| 111 | 
            +
                    box = create_box([@inline_box] * 20, style: {position: :flow})
         | 
| 112 | 
            +
                    assert(box.fit(100, 100, @frame).success?)
         | 
| 113 | 
            +
                    assert_equal(100, box.width)
         | 
| 114 | 
            +
                    assert_equal(30, box.height)
         | 
| 115 | 
            +
                  end
         | 
| 115 116 |  | 
| 116 | 
            -
             | 
| 117 | 
            -
             | 
| 118 | 
            -
             | 
| 119 | 
            -
             | 
| 117 | 
            +
                  it "respects a set initial height" do
         | 
| 118 | 
            +
                    box = create_box([@inline_box] * 20, height: 13, style: {position: :flow})
         | 
| 119 | 
            +
                    assert(box.fit(100, 100, @frame).overflow?)
         | 
| 120 | 
            +
                    assert_equal(100, box.width)
         | 
| 121 | 
            +
                    assert_equal(13, box.height)
         | 
| 122 | 
            +
                  end
         | 
| 120 123 |  | 
| 121 | 
            -
             | 
| 122 | 
            -
             | 
| 123 | 
            -
             | 
| 124 | 
            +
                  it "respects top/bottom padding/border" do
         | 
| 125 | 
            +
                    @frame.remove_area(Geom2D::Rectangle(0, 80, 20, 20))
         | 
| 126 | 
            +
                    box = create_box([@inline_box] * 20, style: {position: :flow, padding: 10, border: {width: 2}})
         | 
| 127 | 
            +
                    assert(box.fit(100, 100, @frame).success?)
         | 
| 128 | 
            +
                    assert_equal(124, box.width)
         | 
| 129 | 
            +
                    assert_equal(54, box.height)
         | 
| 130 | 
            +
                    assert_equal([80, 100, 20], box.instance_variable_get(:@result).lines.map(&:width))
         | 
| 131 | 
            +
                  end
         | 
| 132 | 
            +
                end
         | 
| 124 133 |  | 
| 125 | 
            -
             | 
| 126 | 
            -
                   | 
| 134 | 
            +
                it "fails if no item of the text box fits due to the width" do
         | 
| 135 | 
            +
                  box = create_box([@inline_box])
         | 
| 136 | 
            +
                  assert(box.fit(5, 20, @frame).failure?)
         | 
| 127 137 | 
             
                end
         | 
| 128 138 |  | 
| 129 | 
            -
                it " | 
| 130 | 
            -
                  box = create_box([@inline_box] | 
| 131 | 
            -
                  box.fit( | 
| 132 | 
            -
                  box.instance_variable_set(:@width, 50.00000000006)
         | 
| 133 | 
            -
                  box.instance_variable_set(:@height, 10.00000000003)
         | 
| 134 | 
            -
                  assert_equal([box], box.split(50, 10, @frame))
         | 
| 139 | 
            +
                it "fails if no item of the text box fits due to the height" do
         | 
| 140 | 
            +
                  box = create_box([@inline_box])
         | 
| 141 | 
            +
                  assert(box.fit(20, 5, @frame).failure?)
         | 
| 135 142 | 
             
                end
         | 
| 143 | 
            +
              end
         | 
| 136 144 |  | 
| 145 | 
            +
              describe "split" do
         | 
| 137 146 | 
             
                it "splits the box if necessary when using non-flowing text" do
         | 
| 138 147 | 
             
                  box = create_box([@inline_box] * 10)
         | 
| 139 | 
            -
                   | 
| 140 | 
            -
                   | 
| 141 | 
            -
                   | 
| 142 | 
            -
                  refute( | 
| 143 | 
            -
                  assert( | 
| 144 | 
            -
                  assert_equal(5,  | 
| 148 | 
            +
                  box.fit(50, 10, @frame)
         | 
| 149 | 
            +
                  box_a, box_b = box.split
         | 
| 150 | 
            +
                  assert_same(box, box_a)
         | 
| 151 | 
            +
                  refute(box_a.split_box?)
         | 
| 152 | 
            +
                  assert(box_b.split_box?)
         | 
| 153 | 
            +
                  assert_equal(5, box_b.instance_variable_get(:@items).length)
         | 
| 145 154 | 
             
                end
         | 
| 146 155 |  | 
| 147 156 | 
             
                it "splits the box if necessary when using flowing text that results in a wider box" do
         | 
| 148 157 | 
             
                  @frame.remove_area(Geom2D::Polygon.new([[0, 100], [50, 100], [50, 10], [0, 10]]))
         | 
| 149 158 | 
             
                  box = create_box([@inline_box] * 60, style: {position: :flow})
         | 
| 150 | 
            -
                   | 
| 151 | 
            -
                   | 
| 152 | 
            -
                   | 
| 153 | 
            -
                  assert_equal(5,  | 
| 154 | 
            -
                end
         | 
| 155 | 
            -
             | 
| 156 | 
            -
                it "correctly handles text indentation for split boxes" do
         | 
| 157 | 
            -
                  [{}, {position: :flow}].each do |styles|
         | 
| 158 | 
            -
                    box = create_box([@inline_box] * 202, style: {text_indent: 50, **styles})
         | 
| 159 | 
            -
                    boxes = box.split(100, 100, @frame)
         | 
| 160 | 
            -
                    assert_equal(107, boxes[1].instance_variable_get(:@items).length)
         | 
| 161 | 
            -
                    boxes = boxes[1].split(100, 100, @frame)
         | 
| 162 | 
            -
                    assert_equal(7, boxes[1].instance_variable_get(:@items).length)
         | 
| 163 | 
            -
                  end
         | 
| 159 | 
            +
                  box.fit(50, 100, @frame)
         | 
| 160 | 
            +
                  box_a, box_b = box.split
         | 
| 161 | 
            +
                  assert_same(box, box_a)
         | 
| 162 | 
            +
                  assert_equal(5, box_b.instance_variable_get(:@items).length)
         | 
| 164 163 | 
             
                end
         | 
| 165 164 | 
             
              end
         | 
| 166 165 |  | 
| @@ -196,12 +195,12 @@ describe HexaPDF::Layout::TextBox do | |
| 196 195 | 
             
                  @frame.remove_area(Geom2D::Rectangle(0, 0, 40, 100))
         | 
| 197 196 | 
             
                  box = create_box([@inline_box], style: {position: :flow, border: {width: 1}})
         | 
| 198 197 | 
             
                  box.fit(60, 100, @frame)
         | 
| 199 | 
            -
                  box.draw(@canvas, 0,  | 
| 198 | 
            +
                  box.draw(@canvas, 0, 88)
         | 
| 200 199 | 
             
                  assert_operators(@canvas.contents, [[:save_graphics_state],
         | 
| 201 | 
            -
                                                      [:append_rectangle, [40,  | 
| 200 | 
            +
                                                      [:append_rectangle, [40, 88, 12, 12]],
         | 
| 202 201 | 
             
                                                      [:clip_path_non_zero],
         | 
| 203 202 | 
             
                                                      [:end_path],
         | 
| 204 | 
            -
                                                      [:append_rectangle, [40.5,  | 
| 203 | 
            +
                                                      [:append_rectangle, [40.5, 88.5, 11.0, 11.0]],
         | 
| 205 204 | 
             
                                                      [:stroke_path],
         | 
| 206 205 | 
             
                                                      [:restore_graphics_state],
         | 
| 207 206 | 
             
                                                      [:save_graphics_state],
         | 
| @@ -218,6 +217,7 @@ describe HexaPDF::Layout::TextBox do | |
| 218 217 |  | 
| 219 218 | 
             
                it "draws nothing onto the canvas if the box is empty" do
         | 
| 220 219 | 
             
                  box = create_box([])
         | 
| 220 | 
            +
                  box.fit(100, 100, @frame)
         | 
| 221 221 | 
             
                  box.draw(@canvas, 5, 5)
         | 
| 222 222 | 
             
                  assert_operators(@canvas.contents, [])
         | 
| 223 223 | 
             
                end
         | 
| @@ -119,6 +119,13 @@ describe HexaPDF::Composer do | |
| 119 119 | 
             
                end
         | 
| 120 120 | 
             
              end
         | 
| 121 121 |  | 
| 122 | 
            +
              describe "styles" do
         | 
| 123 | 
            +
                it "delegates to layout.styles" do
         | 
| 124 | 
            +
                  @composer.styles(base: {font_size: 30}, other: {font_size: 40})
         | 
| 125 | 
            +
                  assert_equal([:base, :other], @composer.document.layout.styles.keys)
         | 
| 126 | 
            +
                end
         | 
| 127 | 
            +
              end
         | 
| 128 | 
            +
             | 
| 122 129 | 
             
              describe "page_style" do
         | 
| 123 130 | 
             
                it "returns the page style if no argument or block is given" do
         | 
| 124 131 | 
             
                  page_style = @composer.page_style(:default)
         | 
| @@ -225,8 +232,9 @@ describe HexaPDF::Composer do | |
| 225 232 | 
             
                  first_page_contents = @composer.canvas.contents
         | 
| 226 233 | 
             
                  @composer.draw_box(create_box(height: 400))
         | 
| 227 234 |  | 
| 228 | 
            -
                  box = create_box | 
| 229 | 
            -
                  box.define_singleton_method(: | 
| 235 | 
            +
                  box = create_box
         | 
| 236 | 
            +
                  box.define_singleton_method(:fit_content) {|*| fit_result.overflow! }
         | 
| 237 | 
            +
                  box.define_singleton_method(:split_content) do |*|
         | 
| 230 238 | 
             
                    [box, HexaPDF::Layout::Box.new(height: 100) {}]
         | 
| 231 239 | 
             
                  end
         | 
| 232 240 | 
             
                  @composer.draw_box(box)
         | 
| @@ -235,7 +243,7 @@ describe HexaPDF::Composer do | |
| 235 243 | 
             
                                    [:concatenate_matrix, [1, 0, 0, 1, 36, 405.889764]],
         | 
| 236 244 | 
             
                                    [:restore_graphics_state],
         | 
| 237 245 | 
             
                                    [:save_graphics_state],
         | 
| 238 | 
            -
                                    [:concatenate_matrix, [1, 0, 0, 1, 36,  | 
| 246 | 
            +
                                    [:concatenate_matrix, [1, 0, 0, 1, 36, 36]],
         | 
| 239 247 | 
             
                                    [:restore_graphics_state]])
         | 
| 240 248 | 
             
                  assert_operators(@composer.canvas.contents,
         | 
| 241 249 | 
             
                                   [[:save_graphics_state],
         | 
| @@ -263,13 +271,20 @@ describe HexaPDF::Composer do | |
| 263 271 | 
             
                                    [:restore_graphics_state]])
         | 
| 264 272 | 
             
                end
         | 
| 265 273 |  | 
| 274 | 
            +
                it "handles truncated boxes correctly" do
         | 
| 275 | 
            +
                  box = create_box(height: 400, style: {overflow: :truncate})
         | 
| 276 | 
            +
                  box.define_singleton_method(:fit_content) {|*| fit_result.overflow! }
         | 
| 277 | 
            +
                  assert_same(box, @composer.draw_box(box))
         | 
| 278 | 
            +
                end
         | 
| 279 | 
            +
             | 
| 266 280 | 
             
                it "returns the last drawn box" do
         | 
| 267 281 | 
             
                  box = create_box(height: 400)
         | 
| 268 282 | 
             
                  assert_same(box, @composer.draw_box(box))
         | 
| 269 283 |  | 
| 270 | 
            -
                  box = create_box(height: 400)
         | 
| 271 284 | 
             
                  split_box = create_box(height: 100)
         | 
| 272 | 
            -
                  box | 
| 285 | 
            +
                  box = create_box
         | 
| 286 | 
            +
                  box.define_singleton_method(:fit_content) {|*| fit_result.overflow! }
         | 
| 287 | 
            +
                  box.define_singleton_method(:split_content) {|*| [box, split_box] }
         | 
| 273 288 | 
             
                  assert_same(split_box, @composer.draw_box(box))
         | 
| 274 289 | 
             
                end
         | 
| 275 290 |  | 
    
        data/test/hexapdf/test_parser.rb
    CHANGED
    
    | @@ -358,6 +358,14 @@ describe HexaPDF::Parser do | |
| 358 358 | 
             
                it "finds the startxref anywhere in file" do
         | 
| 359 359 | 
             
                  create_parser("startxref\n5\n%%EOF" << "\nhallo" * 5000)
         | 
| 360 360 | 
             
                  assert_equal(5, @parser.startxref_offset)
         | 
| 361 | 
            +
                end
         | 
| 362 | 
            +
             | 
| 363 | 
            +
                it "handles the case where %%EOF is the on the 1. line of the 1024 byte search block" do
         | 
| 364 | 
            +
                  create_parser("startxref\n5\n%%EOF\n" << "h" * 1018)
         | 
| 365 | 
            +
                  assert_equal(5, @parser.startxref_offset)
         | 
| 366 | 
            +
                end
         | 
| 367 | 
            +
             | 
| 368 | 
            +
                it "handles the case where %%EOF is the on the 2. line of the 1024 byte search block" do
         | 
| 361 369 | 
             
                  create_parser("startxref\n5\n%%EOF\n" << "h" * 1017)
         | 
| 362 370 | 
             
                  assert_equal(5, @parser.startxref_offset)
         | 
| 363 371 | 
             
                end
         | 
| @@ -89,6 +89,7 @@ describe HexaPDF::Serializer do | |
| 89 89 | 
             
                assert_serialized('/ ', :"")
         | 
| 90 90 | 
             
                assert_serialized('/H#c3#b6#c3#9fgang', :Hößgang)
         | 
| 91 91 | 
             
                assert_serialized('/H#e8lp', "H\xE8lp".force_encoding('BINARY').intern)
         | 
| 92 | 
            +
                assert_serialized('/#00#09#0a#0c#0d#20', :"\x00\t\n\f\r ")
         | 
| 92 93 | 
             
              end
         | 
| 93 94 |  | 
| 94 95 | 
             
              it "serializes arrays" do
         | 
| @@ -97,10 +97,11 @@ describe HexaPDF::Type::FileSpecification do | |
| 97 97 | 
             
                end
         | 
| 98 98 |  | 
| 99 99 | 
             
                it "embeds the given file and registers it with the global name registry" do
         | 
| 100 | 
            -
                  stream = @obj.embed(@file.path)
         | 
| 100 | 
            +
                  stream = @obj.embed(@file.path, mime_type: 'application/xml')
         | 
| 101 101 | 
             
                  assert_equal(stream, @obj[:EF][:UF])
         | 
| 102 102 | 
             
                  assert_equal(stream, @obj[:EF][:F])
         | 
| 103 103 | 
             
                  assert_equal(File.basename(@file.path), @obj.path)
         | 
| 104 | 
            +
                  assert_equal(:'application/xml', stream[:Subtype])
         | 
| 104 105 | 
             
                  assert_equal(@obj, @doc.catalog[:Names][:EmbeddedFiles].find_entry(@obj.path))
         | 
| 105 106 | 
             
                  assert_equal(:FlateDecode, stream[:Filter])
         | 
| 106 107 | 
             
                  assert_equal('embed-test', stream.stream)
         | 
    
        metadata
    CHANGED
    
    | @@ -1,14 +1,14 @@ | |
| 1 1 | 
             
            --- !ruby/object:Gem::Specification
         | 
| 2 2 | 
             
            name: hexapdf
         | 
| 3 3 | 
             
            version: !ruby/object:Gem::Version
         | 
| 4 | 
            -
              version: 0. | 
| 4 | 
            +
              version: 0.45.0
         | 
| 5 5 | 
             
            platform: ruby
         | 
| 6 6 | 
             
            authors:
         | 
| 7 7 | 
             
            - Thomas Leitner
         | 
| 8 8 | 
             
            autorequire:
         | 
| 9 9 | 
             
            bindir: bin
         | 
| 10 10 | 
             
            cert_chain: []
         | 
| 11 | 
            -
            date: 2024- | 
| 11 | 
            +
            date: 2024-06-18 00:00:00.000000000 Z
         | 
| 12 12 | 
             
            dependencies:
         | 
| 13 13 | 
             
            - !ruby/object:Gem::Dependency
         | 
| 14 14 | 
             
              name: cmdparse
         |