hexapdf 0.7.0 → 0.8.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (106) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +39 -1
  3. data/CONTRIBUTERS +1 -1
  4. data/LICENSE +3 -0
  5. data/README.md +2 -1
  6. data/Rakefile +3 -1
  7. data/VERSION +1 -1
  8. data/examples/{hello_world.rb → 001-hello_world.rb} +0 -0
  9. data/examples/{graphics.rb → 002-graphics.rb} +1 -1
  10. data/examples/{arc.rb → 003-arcs.rb} +2 -2
  11. data/examples/{optimizing.rb → 004-optimizing.rb} +0 -0
  12. data/examples/{merging.rb → 005-merging.rb} +0 -0
  13. data/examples/{standard_pdf_fonts.rb → 006-standard_pdf_fonts.rb} +0 -0
  14. data/examples/{truetype.rb → 007-truetype.rb} +0 -0
  15. data/examples/{show_char_bboxes.rb → 008-show_char_bboxes.rb} +0 -0
  16. data/examples/{text_layouter_alignment.rb → 009-text_layouter_alignment.rb} +3 -3
  17. data/examples/{text_layouter_inline_boxes.rb → 010-text_layouter_inline_boxes.rb} +7 -9
  18. data/examples/{text_layouter_line_wrapping.rb → 011-text_layouter_line_wrapping.rb} +6 -5
  19. data/examples/{text_layouter_styling.rb → 012-text_layouter_styling.rb} +6 -8
  20. data/examples/013-text_layouter_shapes.rb +176 -0
  21. data/examples/014-text_in_polygon.rb +60 -0
  22. data/examples/{boxes.rb → 015-boxes.rb} +29 -21
  23. data/examples/016-frame_automatic_box_placement.rb +90 -0
  24. data/examples/017-frame_text_flow.rb +60 -0
  25. data/lib/hexapdf/cli/command.rb +4 -3
  26. data/lib/hexapdf/cli/files.rb +1 -1
  27. data/lib/hexapdf/cli/inspect.rb +0 -1
  28. data/lib/hexapdf/cli/merge.rb +1 -1
  29. data/lib/hexapdf/cli/modify.rb +1 -1
  30. data/lib/hexapdf/configuration.rb +2 -0
  31. data/lib/hexapdf/content/canvas.rb +3 -3
  32. data/lib/hexapdf/content/graphic_object.rb +1 -0
  33. data/lib/hexapdf/content/graphic_object/geom2d.rb +132 -0
  34. data/lib/hexapdf/dictionary.rb +7 -1
  35. data/lib/hexapdf/dictionary_fields.rb +35 -83
  36. data/lib/hexapdf/document.rb +9 -5
  37. data/lib/hexapdf/document/fonts.rb +1 -1
  38. data/lib/hexapdf/encryption/standard_security_handler.rb +1 -1
  39. data/lib/hexapdf/filter/ascii85_decode.rb +1 -1
  40. data/lib/hexapdf/filter/ascii_hex_decode.rb +1 -1
  41. data/lib/hexapdf/font/cmap/writer.rb +2 -2
  42. data/lib/hexapdf/font/true_type/builder.rb +1 -1
  43. data/lib/hexapdf/font/true_type/table.rb +1 -1
  44. data/lib/hexapdf/font/true_type/table/cmap.rb +1 -1
  45. data/lib/hexapdf/font/true_type/table/cmap_subtable.rb +3 -3
  46. data/lib/hexapdf/font/true_type/table/kern.rb +1 -1
  47. data/lib/hexapdf/font/true_type/table/post.rb +1 -1
  48. data/lib/hexapdf/font/type1/character_metrics.rb +1 -1
  49. data/lib/hexapdf/font/type1/font_metrics.rb +1 -1
  50. data/lib/hexapdf/image_loader/jpeg.rb +1 -1
  51. data/lib/hexapdf/image_loader/png.rb +2 -2
  52. data/lib/hexapdf/layout.rb +3 -0
  53. data/lib/hexapdf/layout/box.rb +64 -46
  54. data/lib/hexapdf/layout/frame.rb +348 -0
  55. data/lib/hexapdf/layout/inline_box.rb +2 -2
  56. data/lib/hexapdf/layout/line.rb +3 -3
  57. data/lib/hexapdf/layout/style.rb +81 -14
  58. data/lib/hexapdf/layout/text_box.rb +84 -0
  59. data/lib/hexapdf/layout/text_fragment.rb +8 -8
  60. data/lib/hexapdf/layout/text_layouter.rb +278 -169
  61. data/lib/hexapdf/layout/width_from_polygon.rb +246 -0
  62. data/lib/hexapdf/rectangle.rb +9 -9
  63. data/lib/hexapdf/stream.rb +2 -2
  64. data/lib/hexapdf/type.rb +1 -0
  65. data/lib/hexapdf/type/action.rb +1 -1
  66. data/lib/hexapdf/type/annotations/markup_annotation.rb +1 -1
  67. data/lib/hexapdf/type/catalog.rb +1 -1
  68. data/lib/hexapdf/type/cid_font.rb +2 -1
  69. data/lib/hexapdf/type/font.rb +0 -1
  70. data/lib/hexapdf/type/font_descriptor.rb +1 -1
  71. data/lib/hexapdf/type/font_simple.rb +3 -3
  72. data/lib/hexapdf/type/font_true_type.rb +8 -0
  73. data/lib/hexapdf/type/font_type0.rb +2 -1
  74. data/lib/hexapdf/type/font_type1.rb +7 -1
  75. data/lib/hexapdf/type/font_type3.rb +61 -0
  76. data/lib/hexapdf/type/graphics_state_parameter.rb +8 -8
  77. data/lib/hexapdf/type/image.rb +10 -0
  78. data/lib/hexapdf/type/page.rb +83 -10
  79. data/lib/hexapdf/version.rb +1 -1
  80. data/test/hexapdf/common_tokenizer_tests.rb +2 -2
  81. data/test/hexapdf/content/graphic_object/test_geom2d.rb +79 -0
  82. data/test/hexapdf/encryption/test_standard_security_handler.rb +1 -1
  83. data/test/hexapdf/font/test_true_type_wrapper.rb +1 -1
  84. data/test/hexapdf/font/test_type1_wrapper.rb +1 -1
  85. data/test/hexapdf/font/true_type/table/test_cmap.rb +1 -1
  86. data/test/hexapdf/font/true_type/table/test_directory.rb +1 -1
  87. data/test/hexapdf/font/true_type/table/test_head.rb +7 -3
  88. data/test/hexapdf/layout/test_box.rb +57 -15
  89. data/test/hexapdf/layout/test_frame.rb +313 -0
  90. data/test/hexapdf/layout/test_inline_box.rb +1 -1
  91. data/test/hexapdf/layout/test_style.rb +74 -0
  92. data/test/hexapdf/layout/test_text_box.rb +77 -0
  93. data/test/hexapdf/layout/test_text_layouter.rb +220 -239
  94. data/test/hexapdf/layout/test_width_from_polygon.rb +108 -0
  95. data/test/hexapdf/test_dictionary_fields.rb +22 -26
  96. data/test/hexapdf/test_document.rb +3 -3
  97. data/test/hexapdf/test_reference.rb +1 -0
  98. data/test/hexapdf/test_writer.rb +2 -2
  99. data/test/hexapdf/type/test_font_true_type.rb +25 -0
  100. data/test/hexapdf/type/test_font_type1.rb +6 -0
  101. data/test/hexapdf/type/test_font_type3.rb +26 -0
  102. data/test/hexapdf/type/test_image.rb +10 -0
  103. data/test/hexapdf/type/test_page.rb +114 -0
  104. data/test/test_helper.rb +1 -1
  105. metadata +65 -17
  106. data/examples/text_layouter_shapes.rb +0 -170
@@ -363,38 +363,30 @@ describe HexaPDF::Layout::TextLayouter do
363
363
  @style = HexaPDF::Layout::Style.new(font: @font)
364
364
  end
365
365
 
366
- it "creates an instance from text and options" do
367
- layouter = HexaPDF::Layout::TextLayouter.create("T", font: @font, width: 100, height: 100)
368
- assert_equal(1, layouter.items.length)
369
- assert_equal(@font.decode_utf8("T"), layouter.items[0].item.items)
370
- end
371
-
372
366
  describe "initialize" do
373
367
  it "can use a Style object" do
374
368
  style = HexaPDF::Layout::Style.new(font: @font, font_size: 20)
375
- layouter = HexaPDF::Layout::TextLayouter.new(items: [], width: 10, style: style)
369
+ layouter = HexaPDF::Layout::TextLayouter.new(style)
376
370
  assert_equal(20, layouter.style.font_size)
377
371
  end
378
372
 
379
373
  it "can use a style options" do
380
- layouter = HexaPDF::Layout::TextLayouter.new(items: [], width: 10, style:
381
- {font: @font, font_size: 20})
374
+ layouter = HexaPDF::Layout::TextLayouter.new(font: @font, font_size: 20)
382
375
  assert_equal(20, layouter.style.font_size)
383
376
  end
384
377
  end
385
378
 
386
- it "doesn't run the text segmentation algorithm on already segmented items" do
387
- item = HexaPDF::Layout::InlineBox.create(width: 20) {}
388
- layouter = HexaPDF::Layout::TextLayouter.new(items: [item], width: 100, height: 100)
389
- items = layouter.items
390
- assert_equal(1, items.length)
391
- assert_box(items[0], item)
379
+ describe "fit" do
380
+ before do
381
+ @layouter = HexaPDF::Layout::TextLayouter.new(@style)
382
+ end
392
383
 
393
- layouter.items = items
394
- assert_same(items, layouter.items)
395
- end
384
+ it "does nothing if there are no items" do
385
+ result = @layouter.fit([], 100, 100)
386
+ assert_equal(:success, result.status)
387
+ assert_equal(0, result.height)
388
+ end
396
389
 
397
- describe "fit" do
398
390
  it "handles text indentation" do
399
391
  items = boxes([20, 20], [20, 20], [20, 20]) +
400
392
  [HexaPDF::Layout::TextLayouter::Penalty::MandatoryParagraphBreak] +
@@ -404,93 +396,94 @@ describe HexaPDF::Layout::TextLayouter do
404
396
  @style.text_indent = 20
405
397
 
406
398
  [60, proc { 60 }].each do |width|
407
- layouter = HexaPDF::Layout::TextLayouter.new(items: items, width: width, style: @style)
408
- rest, reason = layouter.fit
409
- assert_equal([40, 20, 40, 60, 20, 60, 20], layouter.lines.map(&:width))
410
- assert_equal([20, 0, 20, 0, 0, 0, 0], layouter.lines.map(&:x_offset))
411
- assert(rest.empty?)
412
- assert_equal(:success, reason)
399
+ result = @layouter.fit(items, width, 200)
400
+ assert_equal([40, 20, 40, 60, 20, 60, 20], result.lines.map(&:width))
401
+ assert_equal([20, 0, 20, 0, 0, 0, 0], result.lines.map(&:x_offset))
402
+ assert(result.remaining_items.empty?)
403
+ assert_equal(:success, result.status)
413
404
  end
414
405
  end
415
406
 
416
- it "fits using unlimited height" do
417
- layouter = HexaPDF::Layout::TextLayouter.new(items: boxes(*([[20, 20]] * 100)), width: 20,
418
- style: @style)
419
- rest, reason = layouter.fit
420
- assert(rest.empty?)
421
- assert_equal(:success, reason)
422
- assert_equal(20 * 100, layouter.actual_height)
423
- end
424
-
425
407
  it "fits using a limited height" do
426
- layouter = HexaPDF::Layout::TextLayouter.new(items: boxes(*([[20, 20]] * 100)), width: 20,
427
- height: 100, style: @style)
428
- rest, reason = layouter.fit
429
- assert_equal(95, rest.count)
430
- assert_equal(:height, reason)
431
- assert_equal(100, layouter.actual_height)
408
+ result = @layouter.fit(boxes(*([[20, 20]] * 100)), 20, 100)
409
+ assert_equal(95, result.remaining_items.count)
410
+ assert_equal(:height, result.status)
411
+ assert_equal(100, result.height)
432
412
  end
433
413
 
434
414
  it "takes line spacing into account when calculating the height" do
435
- layouter = HexaPDF::Layout::TextLayouter.new(items: boxes(*([[20, 20]] * 5)), width: 20,
436
- style: @style)
437
- layouter.style.line_spacing = :double
438
- rest, reason = layouter.fit
439
- assert(rest.empty?)
440
- assert_equal(:success, reason)
441
- assert_equal(20 * (5 + 4), layouter.actual_height)
415
+ @style.line_spacing = :double
416
+ result = @layouter.fit(boxes(*([[20, 20]] * 5)), 20, 200)
417
+ assert(result.remaining_items.empty?)
418
+ assert_equal(:success, result.status)
419
+ assert_equal(20 * (5 + 4), result.height)
442
420
  end
443
421
 
444
422
  it "handles empty lines" do
445
423
  items = boxes([20, 20]) + [penalty(-5000)] + boxes([30, 20]) + [penalty(-5000)] * 2 +
446
424
  boxes([20, 20]) + [penalty(-5000)] * 2
447
- layouter = HexaPDF::Layout::TextLayouter.new(items: items, width: 30, style: @style)
448
- rest, reason = layouter.fit
449
- assert(rest.empty?)
450
- assert_equal(:success, reason)
451
- assert_equal(5, layouter.lines.count)
452
- assert_equal(20 + 20 + 9 + 20 + 9, layouter.actual_height)
453
- end
454
-
455
- describe "fixed width" do
456
- it "stops if an item is wider than the available width, with unlimited height" do
457
- layouter = HexaPDF::Layout::TextLayouter.new(items: boxes([20, 20], [50, 20]), width: 30,
458
- style: @style)
459
- rest, reason = layouter.fit
460
- assert_equal(1, rest.count)
461
- assert_equal(:box, reason)
462
- assert_equal(20, layouter.actual_height)
425
+ result = @layouter.fit(items, 30, 100)
426
+ assert(result.remaining_items.empty?)
427
+ assert_equal(:success, result.status)
428
+ assert_equal(5, result.lines.count)
429
+ assert_equal(20 + 20 + 9 + 20 + 9, result.height)
430
+ end
431
+
432
+ it "handles line breaks in combination with multiple parts per line" do
433
+ items = boxes([20, 20]) + [penalty(-5000)] +
434
+ boxes([20, 20], [20, 20], [20, 20]) + [penalty(-5000)] +
435
+ [penalty(-5000)] +
436
+ boxes([20, 20])
437
+ result = @layouter.fit(items, [0, 40, 0, 40, 0, 40], 100)
438
+ assert_equal(:success, result.status)
439
+ assert_equal([20, 40, 20, 0, 20], result.lines.map(&:width))
440
+ assert_equal([20, 20, 0, 6.83, 22.17], result.lines.map(&:y_offset))
441
+ end
442
+
443
+ describe "fixed width and too wide item" do
444
+ it "single part per line" do
445
+ result = @layouter.fit(boxes([20, 20], [50, 20]), 30, 100)
446
+ assert_equal(1, result.remaining_items.count)
447
+ assert_equal(:box_too_wide, result.status)
448
+ assert_equal(20, result.height)
463
449
  end
464
450
 
465
- it "stops if a box item is wider than the available width, with limited height" do
466
- layouter = HexaPDF::Layout::TextLayouter.new(items: boxes([20, 20], [50, 20]), width: 30,
467
- height: 100, style: @style)
468
- rest, reason = layouter.fit
469
- assert_equal(1, rest.count)
470
- assert_equal(:box, reason)
471
- assert_equal(20, layouter.actual_height)
451
+ it "multiple parts per line, one fits" do
452
+ result = @layouter.fit(boxes([20, 20], [40, 20], [40, 20], [10, 20]),
453
+ [0, 30, 0, 50, 0, 30, 0, 30], 100)
454
+ assert_equal(0, result.remaining_items.count)
455
+ assert_equal([20, 40, 0, 0, 0, 50], result.lines.map(&:width))
456
+ assert_equal([0, 30, 80, 110, 0, 30], result.lines.map(&:x_offset))
457
+ assert_equal([20, 0, 0, 0, 20, 0], result.lines.map(&:y_offset))
458
+ assert_equal(:success, result.status)
459
+ assert_equal(40, result.height)
460
+ end
461
+
462
+ it "multiple parts per line, none fits" do
463
+ result = @layouter.fit(boxes([20, 20], [50, 20]), [0, 30, 0, 30, 0, 30], 100)
464
+ assert_equal(1, result.remaining_items.count)
465
+ assert_equal(:box_too_wide, result.status)
466
+ assert_equal(20, result.height)
472
467
  end
473
468
  end
474
469
 
475
- describe "variable width with limited height" do
476
- it "searches for a vertical offset if the first item is wider than the available width" do
470
+ describe "variable width" do
471
+ it "single part per line, searches for vertical offset if the first item is too wide" do
477
472
  width_block = lambda do |height, _|
478
473
  case height
479
474
  when 0..20 then 10
480
475
  else 40
481
476
  end
482
477
  end
483
- layouter = HexaPDF::Layout::TextLayouter.new(items: boxes([20, 18]), width: width_block,
484
- height: 100, style: @style)
485
- rest, reason = layouter.fit
486
- assert(rest.empty?)
487
- assert_equal(:success, reason)
488
- assert_equal(1, layouter.lines.count)
489
- assert_equal(24, layouter.lines[0].y_offset)
490
- assert_equal(42, layouter.actual_height)
478
+ result = @layouter.fit(boxes([20, 18]), width_block, 100)
479
+ assert(result.remaining_items.empty?)
480
+ assert_equal(:success, result.status)
481
+ assert_equal(1, result.lines.count)
482
+ assert_equal(42, result.lines[0].y_offset)
483
+ assert_equal(42, result.height)
491
484
  end
492
485
 
493
- it "searches for a vertical offset if an item is wider than the available width" do
486
+ it "single part per line, searches for vertical offset if an item is too wide" do
494
487
  width_block = lambda do |height, line_height|
495
488
  if (40..60).cover?(height) || (40..60).cover?(height + line_height)
496
489
  10
@@ -498,36 +491,118 @@ describe HexaPDF::Layout::TextLayouter do
498
491
  40
499
492
  end
500
493
  end
501
- layouter = HexaPDF::Layout::TextLayouter.new(items: boxes(*([[20, 18]] * 7)),
502
- width: width_block, height: 100, style: @style)
503
- rest, reason = layouter.fit
504
- assert_equal(1, rest.count)
505
- assert_equal(:height, reason)
506
- assert_equal(3, layouter.lines.count)
507
- assert_equal(0, layouter.lines[0].y_offset)
508
- assert_equal(18, layouter.lines[1].y_offset)
509
- assert_equal(48, layouter.lines[2].y_offset)
510
- assert_equal(84, layouter.actual_height)
494
+ result = @layouter.fit(boxes(*([[20, 18]] * 7)), width_block, 100)
495
+ assert_equal(1, result.remaining_items.count)
496
+ assert_equal(:height, result.status)
497
+ assert_equal(3, result.lines.count)
498
+ assert_equal(18, result.lines[0].y_offset)
499
+ assert_equal(18, result.lines[1].y_offset)
500
+ assert_equal(48, result.lines[2].y_offset)
501
+ assert_equal(84, result.height)
502
+ end
503
+
504
+ it "multiple parts per line, reset line because of line height change during first part" do
505
+ width_block = lambda do |height, line_height|
506
+ if height == 0 && line_height <= 10
507
+ [0, 40, 0, 40]
508
+ elsif height == 0 && line_height > 10
509
+ [0, 30, 0, 40]
510
+ else
511
+ [0, 60]
512
+ end
513
+ end
514
+ result = @layouter.fit(boxes([20, 10], [20, 10], [20, 18], [20, 10]), width_block, 100)
515
+ assert_equal(:success, result.status)
516
+ assert_equal(0, result.remaining_items.count)
517
+ assert_equal(3, result.lines.count)
518
+ assert_equal([20, 40, 20], result.lines.map(&:width))
519
+ assert_equal([18, 0, 10], result.lines.map(&:y_offset))
520
+ assert_equal(28, result.height)
521
+ end
522
+
523
+ it "multiple parts per line, reset line because of line height change after first part" do
524
+ width_block = lambda do |height, line_height|
525
+ if height == 0 && line_height <= 10
526
+ [0, 40, 0, 40]
527
+ elsif height == 0 && line_height > 10
528
+ [0, 30, 0, 40]
529
+ else
530
+ [0, 60]
531
+ end
532
+ end
533
+ result = @layouter.fit(boxes([20, 10], [15, 10], [10, 10], [12, 18], [20, 10]),
534
+ width_block, 100)
535
+ assert_equal(:success, result.status)
536
+ assert_equal(0, result.remaining_items.count)
537
+ assert_equal(3, result.lines.count)
538
+ assert_equal([20, 37, 20], result.lines.map(&:width))
539
+ assert_equal([18, 0, 10], result.lines.map(&:y_offset))
540
+ assert_equal(28, result.height)
511
541
  end
512
542
  end
513
543
 
514
544
  it "breaks a text fragment into parts if it is wider than the available width" do
515
- [100, nil].each do |height|
516
- str = " Thisisaverylongstring"
517
- frag = HexaPDF::Layout::TextFragment.create(str, font: @font)
518
- layouter = HexaPDF::Layout::TextLayouter.new(items: [frag], width: 20, height: height,
519
- style: @style)
520
- rest, reason = layouter.fit
521
- assert(rest.empty?)
522
- assert_equal(:success, reason)
523
- assert_equal(str.strip.length, layouter.lines.sum {|l| l.items.sum {|i| i.items.count } })
524
- assert_equal(45, layouter.actual_height)
525
-
526
- layouter = HexaPDF::Layout::TextLayouter.new(items: [frag], width: 1, height: height,
527
- style: @style)
528
- rest, reason = layouter.fit
529
- assert_equal(str.strip.length, rest.count)
530
- assert_equal(:box, reason)
545
+ str = " Thisisaverylongstring"
546
+ frag = HexaPDF::Layout::TextFragment.create(str, font: @font)
547
+ result = @layouter.fit([frag], 20, 100)
548
+ assert(result.remaining_items.empty?)
549
+ assert_equal(:success, result.status)
550
+ assert_equal(str.strip.length, result.lines.sum {|l| l.items.sum {|i| i.items.count } })
551
+ assert_equal(45, result.height)
552
+
553
+ result = @layouter.fit([frag], 1, 100)
554
+ assert_equal(str.strip.length, result.remaining_items.count)
555
+ assert_equal(:box_too_wide, result.status)
556
+ end
557
+
558
+ describe "horizontal alignment" do
559
+ before do
560
+ @items = boxes(*[[20, 20]] * 4)
561
+ end
562
+
563
+ it "aligns the contents to the left" do
564
+ @style.align = :left
565
+ result = @layouter.fit(@items, 100, 100)
566
+ assert_equal(0, result.lines[0].x_offset)
567
+ end
568
+
569
+ it "aligns the contents to the center" do
570
+ @style.align = :center
571
+ result = @layouter.fit(@items, 100, 100)
572
+ assert_equal(10, result.lines[0].x_offset)
573
+ end
574
+
575
+ it "aligns the contents to the right" do
576
+ @style.align = :right
577
+ result = @layouter.fit(@items, 100, 100)
578
+ assert_equal(20, result.lines[0].x_offset)
579
+ end
580
+ end
581
+
582
+ describe "vertical alignment" do
583
+ before do
584
+ @items = boxes(*[[20, 20]] * 4)
585
+ end
586
+
587
+ it "aligns the contents to the top" do
588
+ @style.valign = :top
589
+ result = @layouter.fit(@items, 40, 100)
590
+ assert_equal(result.lines[0].y_max, result.lines[0].y_offset)
591
+ assert_equal(40, result.height)
592
+ end
593
+
594
+ it "aligns the contents to the center" do
595
+ @style.valign = :center
596
+ result = @layouter.fit(@items, 40, 100)
597
+ assert_equal((100 - 40) / 2 + 20, result.lines[0].y_offset)
598
+ assert_equal(70, result.height)
599
+ end
600
+
601
+ it "aligns the contents to the bottom" do
602
+ @style.valign = :bottom
603
+ result = @layouter.fit(@items, 40, 100)
604
+ assert_equal(100 - 20 * 2 + 20, result.lines[0].y_offset)
605
+ assert_equal(100, result.height)
531
606
  end
532
607
  end
533
608
 
@@ -543,34 +618,20 @@ describe HexaPDF::Layout::TextLayouter do
543
618
  # Missing width: 100 - 90 = 10
544
619
  # -> Each space must be doubled!
545
620
 
546
- layouter = HexaPDF::Layout::TextLayouter.new(items: items, width: 100)
547
- layouter.style.align = :justify
548
- rest, reason = layouter.fit
549
- assert(rest.empty?)
550
- assert_equal(:success, reason)
551
- assert_equal(9, layouter.lines[0].items.count)
552
- assert_in_delta(100, layouter.lines[0].width)
553
- assert_equal(-250, layouter.lines[0].items[1].items[0])
554
- assert_equal(-250, layouter.lines[0].items[4].items[0])
555
- assert_equal(-250, layouter.lines[0].items[6].items[0])
556
- assert_equal(30, layouter.lines[1].width)
557
- end
558
-
559
- it "applies the optional horizontal offsets if set" do
560
- x_offsets = lambda {|height, line_height| height + line_height }
561
- layouter = HexaPDF::Layout::TextLayouter.new(items: boxes(*([[20, 10]] * 7)), width: 60,
562
- x_offsets: x_offsets, height: 100, style: @style)
563
- rest, reason = layouter.fit
564
- assert(rest.empty?)
565
- assert_equal(:success, reason)
566
- assert_equal(30, layouter.actual_height)
567
- assert_equal(10, layouter.lines[0].x_offset)
568
- assert_equal(20, layouter.lines[1].x_offset)
569
- assert_equal(30, layouter.lines[2].x_offset)
621
+ @style.align = :justify
622
+ result = @layouter.fit(items, 100, 100)
623
+ assert(result.remaining_items.empty?)
624
+ assert_equal(:success, result.status)
625
+ assert_equal(9, result.lines[0].items.count)
626
+ assert_in_delta(100, result.lines[0].width)
627
+ assert_equal(-250, result.lines[0].items[1].items[0])
628
+ assert_equal(-250, result.lines[0].items[4].items[0])
629
+ assert_equal(-250, result.lines[0].items[6].items[0])
630
+ assert_equal(30, result.lines[1].width)
570
631
  end
571
632
  end
572
633
 
573
- describe "draw" do
634
+ describe "Result#draw" do
574
635
  def assert_positions(content, positions)
575
636
  processor = TestHelper::OperatorRecorder.new
576
637
  HexaPDF::Content::Parser.new.parse(content, processor)
@@ -596,133 +657,53 @@ describe HexaPDF::Layout::TextLayouter do
596
657
  @frag = HexaPDF::Layout::TextFragment.create("This is some more text.\n" \
597
658
  "This is some more text.", font: @font)
598
659
  @width = HexaPDF::Layout::TextFragment.create("This is some ", font: @font).width
599
- @layouter = HexaPDF::Layout::TextLayouter.new(items: [@frag], width: @width)
660
+ @layouter = HexaPDF::Layout::TextLayouter.new
600
661
  @canvas = @doc.pages.add.canvas
601
662
 
602
663
  @line1w = HexaPDF::Layout::TextFragment.create("This is some", font: @font).width
603
664
  @line2w = HexaPDF::Layout::TextFragment.create("more text.", font: @font).width
604
665
  end
605
666
 
606
- it "returns the result of #fit if #fit needs to be run" do
607
- rest, reason = @layouter.draw(@canvas, 0, 0)
608
- assert(rest.empty?)
609
- assert_equal(:success, reason)
610
-
611
- @layouter.items = [HexaPDF::Layout::InlineBox.create(width: @width + 10) {}]
612
- rest, reason = @layouter.draw(@canvas, 0, 0)
613
- assert_equal(1, rest.size)
614
- assert_equal(:box, reason)
615
-
616
- assert_nil(@layouter.draw(@canvas, 0, 0))
617
- end
618
-
619
- it "can horizontally align the contents to the left" do
620
- top = 100
621
- @layouter.style.align = :left
622
- @layouter.draw(@canvas, 5, top)
623
- assert_positions(@canvas.contents,
624
- [[5, top - @frag.y_max],
625
- [5, top - @frag.y_max - @frag.height],
626
- [5, top - @frag.y_max - @frag.height * 2],
627
- [5, top - @frag.y_max - @frag.height * 3]])
628
- end
629
-
630
- it "can horizontally align the contents to the center" do
631
- top = 100
632
- @layouter.style.align = :center
633
- @layouter.draw(@canvas, 5, top)
634
- assert_positions(@canvas.contents,
635
- [[5 + (@width - @line1w) / 2, top - @frag.y_max],
636
- [5 + (@width - @line2w) / 2, top - @frag.y_max - @frag.height],
637
- [5 + (@width - @line1w) / 2, top - @frag.y_max - @frag.height * 2],
638
- [5 + (@width - @line2w) / 2, top - @frag.y_max - @frag.height * 3]])
639
- end
640
-
641
- it "can horizontally align the contents to the right" do
642
- top = 100
643
- @layouter.style.align = :right
644
- @layouter.draw(@canvas, 5, top)
645
- assert_positions(@canvas.contents,
646
- [[5 + @width - @line1w, top - @frag.y_max],
647
- [5 + @width - @line2w, top - @frag.y_max - @frag.height],
648
- [5 + @width - @line1w, top - @frag.y_max - @frag.height * 2],
649
- [5 + @width - @line2w, top - @frag.y_max - @frag.height * 3]])
650
- end
651
-
652
- it "can justify the contents" do
653
- top = 100
654
- @layouter.style.align = :justify
655
- @layouter.draw(@canvas, 5, top)
656
- assert_positions(@canvas.contents,
657
- [[5, top - @frag.y_max],
658
- [5, top - @frag.y_max - @frag.height],
659
- [5, top - @frag.y_max - @frag.height * 2],
660
- [5, top - @frag.y_max - @frag.height * 3]])
661
- assert_in_delta(@width, @layouter.lines[0].width, 0.0001)
662
- assert_in_delta(@width, @layouter.lines[2].width, 0.0001)
663
- end
664
-
665
- it "doesn't justify lines ending in a mandatory break or the last line" do
666
- @layouter.style.align = :justify
667
- @layouter.draw(@canvas, 5, 100)
668
- assert_equal(@line2w, @layouter.lines[1].width, 0.0001)
669
- assert_equal(@line2w, @layouter.lines[3].width, 0.0001)
670
- end
671
-
672
- it "can vertically align the contents in the center" do
667
+ it "respects the x- and y-offsets" do
673
668
  top = 100
674
- @layouter = HexaPDF::Layout::TextLayouter.new(items: [@frag], width: @width, height: top)
675
669
  @layouter.style.valign = :center
670
+ @layouter.style.align = :center
676
671
 
677
- @layouter.fit
678
- initial_baseline = top - ((top - @layouter.actual_height) / 2) - @frag.y_max
679
- @layouter.draw(@canvas, 5, top)
680
- assert_positions(@canvas.contents,
681
- [[5, initial_baseline],
682
- [5, initial_baseline - @frag.height],
683
- [5, initial_baseline - @frag.height * 2],
684
- [5, initial_baseline - @frag.height * 3]])
685
- end
686
-
687
- it "can vertically align the contents to the bottom" do
688
- top = 100
689
- @layouter = HexaPDF::Layout::TextLayouter.new(items: [@frag], width: @width, height: top)
690
- @layouter.style.valign = :bottom
672
+ result = @layouter.fit([@frag], @width, top)
673
+ result.draw(@canvas, 5, top)
691
674
 
692
- @layouter.fit
693
- initial_baseline = @layouter.actual_height - @frag.y_max
694
- @layouter.draw(@canvas, 5, top)
675
+ initial_baseline = top - result.lines.first.y_offset
695
676
  assert_positions(@canvas.contents,
696
- [[5, initial_baseline],
697
- [5, initial_baseline - @frag.height],
698
- [5, initial_baseline - @frag.height * 2],
699
- [5, initial_baseline - @frag.height * 3]])
700
- end
701
-
702
- it "raises an error if vertical alignment is :center/:bottom and an unlimited height is used" do
703
- @layouter = HexaPDF::Layout::TextLayouter.new(items: [@frag], width: @width)
704
- assert_raises(HexaPDF::Error) do
705
- @layouter.style.valign = :center
706
- @layouter.draw(@canvas, 0, 0)
707
- end
708
- assert_raises(HexaPDF::Error) do
709
- @layouter.style.valign = :bottom
710
- @layouter.draw(@canvas, 0, 0)
711
- end
677
+ [[5 + (@width - @line1w) / 2, initial_baseline],
678
+ [5 + (@width - @line2w) / 2, initial_baseline - @frag.height],
679
+ [5 + (@width - @line1w) / 2, initial_baseline - @frag.height * 2],
680
+ [5 + (@width - @line2w) / 2, initial_baseline - @frag.height * 3]])
712
681
  end
713
682
 
714
683
  it "makes sure that text fragments don't pollute the graphics state for inline boxes" do
715
- frag = HexaPDF::Layout::TextFragment.create("Demo", font: @font)
716
684
  inline_box = HexaPDF::Layout::InlineBox.create(width: 10, height: 10) {|c, _| c.text("A") }
717
- layouter = HexaPDF::Layout::TextLayouter.new(items: [frag, inline_box], width: 200)
718
- assert_raises(HexaPDF::Error) { layouter.draw(@canvas, 0, 0) }
685
+ result = @layouter.fit([@frag, inline_box], 200, 100)
686
+ assert_raises(HexaPDF::Error) { result.draw(@canvas, 0, 0) } # bc font should be reset to nil
687
+ end
688
+
689
+ it "doesn't do unnecessary work for consecutive text fragments with same style" do
690
+ @layouter.fit([@frag], 200, 100).draw(@canvas, 0, 0)
691
+ assert_operators(@canvas.contents, [[:save_graphics_state],
692
+ [:set_leading, [9.0]],
693
+ [:set_font_and_size, [:F1, 10]],
694
+ [:begin_text],
695
+ [:move_text, [0, -6.83]],
696
+ [:show_text, ["This is some more text."]],
697
+ [:move_text_next_line],
698
+ [:show_text, ["This is some more text."]],
699
+ [:end_text],
700
+ [:restore_graphics_state]])
719
701
  end
720
702
 
721
703
  it "doesn't do unnecessary work for placeholder boxes" do
722
704
  box1 = HexaPDF::Layout::InlineBox.create(width: 10, height: 20)
723
705
  box2 = HexaPDF::Layout::InlineBox.create(width: 30, height: 40) { @canvas.line_width(2) }
724
- layouter = HexaPDF::Layout::TextLayouter.new(items: [box1, box2], width: 200)
725
- layouter.draw(@canvas, 0, 0)
706
+ @layouter.fit([box1, box2], 200, 100).draw(@canvas, 0, 0)
726
707
  assert_operators(@canvas.contents, [[:save_graphics_state],
727
708
  [:restore_graphics_state],
728
709
  [:save_graphics_state],