hexapdf 0.7.0 → 0.8.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 +39 -1
- data/CONTRIBUTERS +1 -1
- data/LICENSE +3 -0
- data/README.md +2 -1
- data/Rakefile +3 -1
- data/VERSION +1 -1
- data/examples/{hello_world.rb → 001-hello_world.rb} +0 -0
- data/examples/{graphics.rb → 002-graphics.rb} +1 -1
- data/examples/{arc.rb → 003-arcs.rb} +2 -2
- data/examples/{optimizing.rb → 004-optimizing.rb} +0 -0
- data/examples/{merging.rb → 005-merging.rb} +0 -0
- data/examples/{standard_pdf_fonts.rb → 006-standard_pdf_fonts.rb} +0 -0
- data/examples/{truetype.rb → 007-truetype.rb} +0 -0
- data/examples/{show_char_bboxes.rb → 008-show_char_bboxes.rb} +0 -0
- data/examples/{text_layouter_alignment.rb → 009-text_layouter_alignment.rb} +3 -3
- data/examples/{text_layouter_inline_boxes.rb → 010-text_layouter_inline_boxes.rb} +7 -9
- data/examples/{text_layouter_line_wrapping.rb → 011-text_layouter_line_wrapping.rb} +6 -5
- data/examples/{text_layouter_styling.rb → 012-text_layouter_styling.rb} +6 -8
- data/examples/013-text_layouter_shapes.rb +176 -0
- data/examples/014-text_in_polygon.rb +60 -0
- data/examples/{boxes.rb → 015-boxes.rb} +29 -21
- data/examples/016-frame_automatic_box_placement.rb +90 -0
- data/examples/017-frame_text_flow.rb +60 -0
- data/lib/hexapdf/cli/command.rb +4 -3
- data/lib/hexapdf/cli/files.rb +1 -1
- data/lib/hexapdf/cli/inspect.rb +0 -1
- data/lib/hexapdf/cli/merge.rb +1 -1
- data/lib/hexapdf/cli/modify.rb +1 -1
- data/lib/hexapdf/configuration.rb +2 -0
- data/lib/hexapdf/content/canvas.rb +3 -3
- data/lib/hexapdf/content/graphic_object.rb +1 -0
- data/lib/hexapdf/content/graphic_object/geom2d.rb +132 -0
- data/lib/hexapdf/dictionary.rb +7 -1
- data/lib/hexapdf/dictionary_fields.rb +35 -83
- data/lib/hexapdf/document.rb +9 -5
- data/lib/hexapdf/document/fonts.rb +1 -1
- data/lib/hexapdf/encryption/standard_security_handler.rb +1 -1
- data/lib/hexapdf/filter/ascii85_decode.rb +1 -1
- data/lib/hexapdf/filter/ascii_hex_decode.rb +1 -1
- data/lib/hexapdf/font/cmap/writer.rb +2 -2
- data/lib/hexapdf/font/true_type/builder.rb +1 -1
- data/lib/hexapdf/font/true_type/table.rb +1 -1
- data/lib/hexapdf/font/true_type/table/cmap.rb +1 -1
- data/lib/hexapdf/font/true_type/table/cmap_subtable.rb +3 -3
- data/lib/hexapdf/font/true_type/table/kern.rb +1 -1
- data/lib/hexapdf/font/true_type/table/post.rb +1 -1
- data/lib/hexapdf/font/type1/character_metrics.rb +1 -1
- data/lib/hexapdf/font/type1/font_metrics.rb +1 -1
- data/lib/hexapdf/image_loader/jpeg.rb +1 -1
- data/lib/hexapdf/image_loader/png.rb +2 -2
- data/lib/hexapdf/layout.rb +3 -0
- data/lib/hexapdf/layout/box.rb +64 -46
- data/lib/hexapdf/layout/frame.rb +348 -0
- data/lib/hexapdf/layout/inline_box.rb +2 -2
- data/lib/hexapdf/layout/line.rb +3 -3
- data/lib/hexapdf/layout/style.rb +81 -14
- data/lib/hexapdf/layout/text_box.rb +84 -0
- data/lib/hexapdf/layout/text_fragment.rb +8 -8
- data/lib/hexapdf/layout/text_layouter.rb +278 -169
- data/lib/hexapdf/layout/width_from_polygon.rb +246 -0
- data/lib/hexapdf/rectangle.rb +9 -9
- data/lib/hexapdf/stream.rb +2 -2
- data/lib/hexapdf/type.rb +1 -0
- data/lib/hexapdf/type/action.rb +1 -1
- data/lib/hexapdf/type/annotations/markup_annotation.rb +1 -1
- data/lib/hexapdf/type/catalog.rb +1 -1
- data/lib/hexapdf/type/cid_font.rb +2 -1
- data/lib/hexapdf/type/font.rb +0 -1
- data/lib/hexapdf/type/font_descriptor.rb +1 -1
- data/lib/hexapdf/type/font_simple.rb +3 -3
- data/lib/hexapdf/type/font_true_type.rb +8 -0
- data/lib/hexapdf/type/font_type0.rb +2 -1
- data/lib/hexapdf/type/font_type1.rb +7 -1
- data/lib/hexapdf/type/font_type3.rb +61 -0
- data/lib/hexapdf/type/graphics_state_parameter.rb +8 -8
- data/lib/hexapdf/type/image.rb +10 -0
- data/lib/hexapdf/type/page.rb +83 -10
- data/lib/hexapdf/version.rb +1 -1
- data/test/hexapdf/common_tokenizer_tests.rb +2 -2
- data/test/hexapdf/content/graphic_object/test_geom2d.rb +79 -0
- data/test/hexapdf/encryption/test_standard_security_handler.rb +1 -1
- data/test/hexapdf/font/test_true_type_wrapper.rb +1 -1
- data/test/hexapdf/font/test_type1_wrapper.rb +1 -1
- data/test/hexapdf/font/true_type/table/test_cmap.rb +1 -1
- data/test/hexapdf/font/true_type/table/test_directory.rb +1 -1
- data/test/hexapdf/font/true_type/table/test_head.rb +7 -3
- data/test/hexapdf/layout/test_box.rb +57 -15
- data/test/hexapdf/layout/test_frame.rb +313 -0
- data/test/hexapdf/layout/test_inline_box.rb +1 -1
- data/test/hexapdf/layout/test_style.rb +74 -0
- data/test/hexapdf/layout/test_text_box.rb +77 -0
- data/test/hexapdf/layout/test_text_layouter.rb +220 -239
- data/test/hexapdf/layout/test_width_from_polygon.rb +108 -0
- data/test/hexapdf/test_dictionary_fields.rb +22 -26
- data/test/hexapdf/test_document.rb +3 -3
- data/test/hexapdf/test_reference.rb +1 -0
- data/test/hexapdf/test_writer.rb +2 -2
- data/test/hexapdf/type/test_font_true_type.rb +25 -0
- data/test/hexapdf/type/test_font_type1.rb +6 -0
- data/test/hexapdf/type/test_font_type3.rb +26 -0
- data/test/hexapdf/type/test_image.rb +10 -0
- data/test/hexapdf/type/test_page.rb +114 -0
- data/test/test_helper.rb +1 -1
- metadata +65 -17
- 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(
|
|
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(
|
|
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
|
-
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
|
|
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
|
-
|
|
394
|
-
|
|
395
|
-
|
|
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
|
-
|
|
408
|
-
|
|
409
|
-
assert_equal([
|
|
410
|
-
|
|
411
|
-
|
|
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
|
-
|
|
427
|
-
|
|
428
|
-
|
|
429
|
-
assert_equal(
|
|
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
|
-
|
|
436
|
-
|
|
437
|
-
|
|
438
|
-
|
|
439
|
-
|
|
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
|
-
|
|
448
|
-
|
|
449
|
-
|
|
450
|
-
assert_equal(
|
|
451
|
-
assert_equal(
|
|
452
|
-
|
|
453
|
-
|
|
454
|
-
|
|
455
|
-
|
|
456
|
-
|
|
457
|
-
|
|
458
|
-
|
|
459
|
-
|
|
460
|
-
|
|
461
|
-
|
|
462
|
-
|
|
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 "
|
|
466
|
-
|
|
467
|
-
|
|
468
|
-
|
|
469
|
-
assert_equal(
|
|
470
|
-
assert_equal(
|
|
471
|
-
assert_equal(20,
|
|
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
|
|
476
|
-
it "searches for
|
|
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
|
-
|
|
484
|
-
|
|
485
|
-
|
|
486
|
-
|
|
487
|
-
assert_equal(
|
|
488
|
-
assert_equal(
|
|
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
|
|
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
|
-
|
|
502
|
-
|
|
503
|
-
|
|
504
|
-
assert_equal(
|
|
505
|
-
assert_equal(
|
|
506
|
-
assert_equal(
|
|
507
|
-
assert_equal(
|
|
508
|
-
assert_equal(
|
|
509
|
-
|
|
510
|
-
|
|
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
|
-
|
|
516
|
-
|
|
517
|
-
|
|
518
|
-
|
|
519
|
-
|
|
520
|
-
|
|
521
|
-
|
|
522
|
-
|
|
523
|
-
|
|
524
|
-
|
|
525
|
-
|
|
526
|
-
|
|
527
|
-
|
|
528
|
-
|
|
529
|
-
|
|
530
|
-
|
|
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
|
-
|
|
547
|
-
layouter.
|
|
548
|
-
|
|
549
|
-
|
|
550
|
-
assert_equal(
|
|
551
|
-
|
|
552
|
-
|
|
553
|
-
assert_equal(-250,
|
|
554
|
-
assert_equal(-250,
|
|
555
|
-
assert_equal(
|
|
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
|
|
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 "
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
718
|
-
assert_raises(HexaPDF::Error) {
|
|
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
|
|
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],
|