hexapdf 0.7.0 → 0.8.0
Sign up to get free protection for your applications and to get access to all the features.
- 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],
|