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.
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
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: ffd1dd41128f1ef75e1caf26f415e908509f881bfdf1595da9b45c633c26bb59
4
- data.tar.gz: ef1ae7147fa94bcf361beae3a340acdd1f6f2a72cc3fdbad0d5e46766957c9b7
3
+ metadata.gz: 0a7cfa4a446d0ec5c87bc204de8040e8c9671e299592901644a1e1c64b5e69af
4
+ data.tar.gz: 14e0e42f0d49eb0483be23446b8507d3f3d6e0b724b2c71530ff997bc97f33b3
5
5
  SHA512:
6
- metadata.gz: dc5242710665cf697db8085fcaf0e5096fce0179308a427e4dccbba3a9067db7713a22f1e83a6732ba943b4aa120eb1df69472b32fdcbae7f430c2cfc7364250
7
- data.tar.gz: b700fb5ce735aa6ff38cb33b88f37cb4a7eece29979f0be5abaab884f8aef2f1fcf23336b47e9218ea237d04d27012a46df3fd5154f92b33b6a635d31139a180
6
+ metadata.gz: c9e582a53eb0a946287a23449a563d7c7b1d29994b7ad799a199fdd23aaca6e01a76c15e35f6c07aaad69407247e859f7ab9501df076c58e9ed515ef6333e43a
7
+ data.tar.gz: 6c0bf3a0e42a9b7228d2a8939056b3aceb037ff66540e4b6d8b917b1e4eb51d6c9213440278da9980a163067542f73ab874e6c798d0750b938a8afd8977707fd
@@ -1,3 +1,41 @@
1
+ ## 0.8.0 - 2018-10-26
2
+
3
+ ### Added
4
+
5
+ * [HexaPDF::Layout::Frame] for box positioning and easier text layouting
6
+ inside an arbitrary polygon
7
+ * [HexaPDF::Layout::TextBox] for displaying text in a rectangular and for
8
+ flowing text inside a frame
9
+ * [HexaPDF::Layout::WidthFromPolygon] for getting a width specification from
10
+ a polygon for use with the text layouting engine
11
+ * [HexaPDF::Type::Image#width] and [HexaPDF::Type::Image#height] convenience
12
+ methods
13
+ * [HexaPDF::Type::FontType3] for Type 3 font support
14
+ * [HexaPDF::Content::GraphicObject::Geom2D] for [Geom2D] object drawing support
15
+ * [HexaPDF::Type::Page#orientation] for easy determination of page orientation
16
+ * [HexaPDF::Type::Page#rotate] for rotating a page
17
+ * [HexaPDF::Layout::Style::Quad#set] for setting all values at once
18
+
19
+ ### Changed
20
+
21
+ * [HexaPDF::Document#validate] to also yield the object that failed validation
22
+ * [HexaPDF::Type::Page#box] to allow setting the value for a box
23
+ * [HexaPDF::Layout::TextLayouter#fit] to allow fitting text into arbitrarily
24
+ shaped areas
25
+ * [HexaPDF::Layout::TextLayouter] to return a new
26
+ [HexaPDF::Layout::TextLayouter::Result] structure when `#fit` is called that
27
+ includes the `#draw` method
28
+ * [HexaPDF::Layout::TextLayouter#fit] to require the height argument
29
+ * Refactored [HexaPDF::Layout::Box] to make using it a bit easier
30
+
31
+ ### Fixed
32
+
33
+ * Validation and conversion of dictionary fields with multiple possible types
34
+ * Box border drawing when border width is greater than edge length
35
+
36
+ [geom2d]: https://github.com/gettalong/geom2d
37
+
38
+
1
39
  ## 0.7.0 - 2018-06-19
2
40
 
3
41
  ### Changed
@@ -25,7 +63,7 @@
25
63
  table directory
26
64
  * [HexaPDF::Font::TrueType::Builder] to pad the table data to achieve the
27
65
  correct alignment
28
- * [HexaPDF::Filter::FlateDecode] by removing the Zlib pools since ther were
66
+ * [HexaPDF::Filter::FlateDecode] by removing the Zlib pools since they were
29
67
  not thread safe
30
68
  * All color space classes to accept the color space definition as argument to
31
69
  `::new`
@@ -1,3 +1,3 @@
1
1
  Count Name
2
2
  ======= ====
3
- 928 Thomas Leitner <t_leitner@gmx.at>
3
+ 980 Thomas Leitner <t_leitner@gmx.at>
data/LICENSE CHANGED
@@ -24,3 +24,6 @@ under Section 5 of the GNU Affero General Public License version 3.
24
24
  In accordance with Section 7(b) of the GNU Affero General Public
25
25
  License, a covered work must retain the producer line in every PDF that
26
26
  is created or manipulated using HexaPDF.
27
+
28
+ If the GNU Affero General Public License doesn't fit your need,
29
+ commercial licenses are available at <https://gettalong.at/hexapdf/>.
data/README.md CHANGED
@@ -93,7 +93,8 @@ So why use HexaPDF?
93
93
 
94
94
  ## License
95
95
 
96
- See the LICENSE file for licensing details.
96
+ AGPL - see the LICENSE file for licensing details. Commercial licenses are available at
97
+ <https://gettalong.at/hexapdf/>.
97
98
 
98
99
  Some included files have a different license:
99
100
 
data/Rakefile CHANGED
@@ -65,12 +65,14 @@ namespace :dev do
65
65
  s.executables = ['hexapdf']
66
66
  s.default_executable = 'hexapdf'
67
67
  s.add_dependency('cmdparse', '~> 3.0', '>= 3.0.3')
68
+ s.add_dependency('geom2d', '~> 0.1')
68
69
  s.add_development_dependency('kramdown', '~> 1.0', '>= 1.13.0')
70
+ s.add_development_dependency('rubocop', '~> 0.58', '>= 0.58.2')
69
71
  s.required_ruby_version = '>= 2.4'
70
72
 
71
73
  s.author = 'Thomas Leitner'
72
74
  s.email = 't_leitner@gmx.at'
73
- s.homepage = "http://hexapdf.gettalong.org"
75
+ s.homepage = "https://hexapdf.gettalong.org"
74
76
  end
75
77
 
76
78
  Gem::PackageTask.new(spec) do |pkg|
data/VERSION CHANGED
@@ -1 +1 @@
1
- 0.7.0
1
+ 0.8.0
@@ -3,7 +3,7 @@
3
3
  # This example shows many of the operations that the canvas implementation
4
4
  # allows.
5
5
  #
6
- # Note that the PDF canvas has its origin in the lower left corner of the page.
6
+ # Note that the PDF canvas has its origin in the bottom left corner of the page.
7
7
  # This means the coordinate (100, 50) is 100 PDF points from the left side and
8
8
  # 50 PDF points from the bottom. One PDF point is equal to 1/72 inch.
9
9
  #
@@ -4,7 +4,7 @@
4
4
  # draw simple pie charts.
5
5
  #
6
6
  # Usage:
7
- # : `ruby arc.rb`
7
+ # : `ruby arcs.rb`
8
8
  #
9
9
 
10
10
  require 'hexapdf'
@@ -47,4 +47,4 @@ canvas.draw(pie, start_angle: 110, end_angle: 130).fill_stroke
47
47
  canvas.fill_color('ddffdd')
48
48
  canvas.draw(pie, start_angle: 130, end_angle: 30).fill_stroke
49
49
 
50
- doc.write('arc.pdf', optimize: true)
50
+ doc.write('arcs.pdf', optimize: true)
File without changes
@@ -26,9 +26,9 @@ canvas.font("Times", size: 10, variant: :bold)
26
26
  width = 100
27
27
  height = 150
28
28
  y_base = 800
29
- tl = HexaPDF::Layout::TextLayouter.create(sample_text, width: width,
30
- height: height,
29
+ tf = HexaPDF::Layout::TextFragment.create(sample_text,
31
30
  font: doc.fonts.add("Times"))
31
+ tl = HexaPDF::Layout::TextLayouter.new
32
32
 
33
33
  [:left, :center, :right, :justify].each_with_index do |align, x_index|
34
34
  x = x_index * (width + 20) + 70
@@ -39,7 +39,7 @@ tl = HexaPDF::Layout::TextLayouter.create(sample_text, width: width,
39
39
  canvas.text(valign.to_s, at: [20, y - height / 2]) if x_index == 0
40
40
 
41
41
  tl.style.align(align).valign(valign)
42
- tl.draw(canvas, x, y, fit: true)
42
+ tl.fit([tf], width, height).draw(canvas, x, y)
43
43
  canvas.stroke_color(128, 0, 0).rectangle(x, y, width, -height).stroke
44
44
  end
45
45
  end
@@ -41,16 +41,14 @@ size = 10
41
41
  items = sample_text.split(/(:-\)|;-\))/).map do |part|
42
42
  case part
43
43
  when ':-)'
44
- style = {background_color: [162, 234, 247], padding: 2}
45
- InlineBox.create(content_width: size * 2, content_height: size * 2,
46
- style: style) do |canvas, box|
44
+ InlineBox.create(width: size * 2, height: size * 2, content_box: true,
45
+ background_color: [162, 234, 247], padding: 2) do |canvas, box|
47
46
  canvas.image(emoji_smile, at: [0, 0], width: box.content_width)
48
47
  end
49
48
  when ';-)'
50
- style = {padding: 5, margin: [0, 10],
51
- border: {width: [1, 2], color: [200, 40]}}
52
- InlineBox.create(content_width: size, content_height: size,
53
- valign: :top, style: style) do |canvas, box|
49
+ InlineBox.create(width: size, height: size, content_box: true,
50
+ valign: :top, padding: 5, margin: [0, 10],
51
+ border: {width: [1, 2], color: [200, 40]}) do |canvas, box|
54
52
  canvas.image(emoji_wink, at: [0, 0], width: box.content_width)
55
53
  end
56
54
  else
@@ -58,9 +56,9 @@ items = sample_text.split(/(:-\)|;-\))/).map do |part|
58
56
  end
59
57
  end
60
58
 
61
- layouter = TextLayouter.new(items: items, width: 500, height: 700)
59
+ layouter = TextLayouter.new
62
60
  layouter.style.align = :justify
63
61
  layouter.style.line_spacing(:proportional, 1.5)
64
- layouter.draw(doc.pages.add.canvas, 50, 800)
62
+ layouter.fit(items, 500, 700).draw(doc.pages.add.canvas, 50, 800)
65
63
 
66
64
  doc.write("text_layouter_inline_boxes.pdf")
@@ -44,13 +44,14 @@ text = "Hello! Fly-fishing\nand wand\u{00AD}ering\taround - fanta\u{200B}stic" \
44
44
 
45
45
  x = 10
46
46
  y = 220
47
+ frag = HexaPDF::Layout::TextFragment.create(text, font: doc.fonts.add("Times"))
48
+ layouter = HexaPDF::Layout::TextLayouter.new
47
49
  [30, 60, 100, 160].each do |width|
48
- layouter = HexaPDF::Layout::TextLayouter.create(text, width: width,
49
- font: doc.fonts.add("Times"))
50
- layouter.draw(canvas, x, y)
50
+ result = layouter.fit([frag], width, 400)
51
+ result.draw(canvas, x, y)
51
52
  canvas.stroke_color(255, 0, 0).line_width(0.2)
52
- canvas.rectangle(x, y, width, -layouter.actual_height).stroke
53
- y -= layouter.actual_height + 5
53
+ canvas.rectangle(x, y, width, -result.height).stroke
54
+ y -= result.height + 5
54
55
  end
55
56
 
56
57
  doc.write("text_layouter_line_wrapping.pdf", optimize: true)
@@ -30,11 +30,10 @@ end
30
30
 
31
31
  # Draws the text at the given [x, y] position onto the canvas and returns the
32
32
  # new y position.
33
- def draw_text(layouter, canvas, x, y)
34
- rest, = layouter.fit
35
- raise "Error" unless rest.empty?
36
- layouter.draw(canvas, x, y)
37
- y - layouter.actual_height
33
+ def draw_text(result, canvas, x, y)
34
+ raise "Error" unless result.remaining_items.empty?
35
+ result.draw(canvas, x, y)
36
+ y - result.height
38
37
  end
39
38
 
40
39
  doc = HexaPDF::Document.new
@@ -103,7 +102,7 @@ styles = {
103
102
  y = 800
104
103
  left = 50
105
104
  width = 500
106
-
105
+ layouter = TextLayouter.new(base_style)
107
106
  styles.each do |desc, variations|
108
107
  items = sample_text.split(/(Lorem ipsum dolor|\b\w{2,5}\b)/).map do |str|
109
108
  if str.length >= 3 && str.length <= 5
@@ -117,8 +116,7 @@ styles.each do |desc, variations|
117
116
  end
118
117
  end
119
118
  items.unshift(fragment(desc + ": ", fill_color: [255, 0, 0], **base_style))
120
- layouter = TextLayouter.new(items: items, width: width, style: base_style)
121
- y = draw_text(layouter, canvas, left, y) - 20
119
+ y = draw_text(layouter.fit(items, width, 400), canvas, left, y) - 20
122
120
  end
123
121
 
124
122
  doc.write("text_layouter_styling.pdf", optimize: true)
@@ -0,0 +1,176 @@
1
+ # ## Text Layouter - Shapes
2
+ #
3
+ # The [HexaPDF::Layout::TextLayouter] class can be used to easily lay out text,
4
+ # not limiting the area to a rectangle but any shape. There is only one
5
+ # restriction: In the case of arbitrary shapes the vertical alignment has to be
6
+ # "top".
7
+ #
8
+ # Arbitrary shapes boil down to varying line widths and horizontal offsets from
9
+ # left. Imagine a circle: If text is fit in a circle, the line widths start at
10
+ # zero, getting larger and larger until the middle of the cirle. And then they
11
+ # get smaller until zero again. The x-values of the left half circle determine
12
+ # the horizontal offsets.
13
+ #
14
+ # Both, the line widths and the horizontal offsets can be calculated given a
15
+ # certain height, and this is exactly what HexaPDF uses. If the `width` argument
16
+ # to [HexaPDF::Layout::TextLayouter#fit] is an object responding to #call (e.g.
17
+ # a lambda), it is used for determining the line widths and offsets.
18
+ #
19
+ # This example shows text layed out in various shapes, using the above mentioned
20
+ # techniques.
21
+ #
22
+ # Usage:
23
+ # : `ruby text_layouter_shapes.rb`
24
+ #
25
+
26
+ require 'hexapdf'
27
+
28
+ include HexaPDF::Layout
29
+
30
+ doc = HexaPDF::Document.new
31
+ page = doc.pages.add
32
+ canvas = page.canvas
33
+ canvas.font("Times", size: 10, variant: :bold)
34
+ canvas.stroke_color(255, 0, 0).line_width(0.2)
35
+ font = doc.fonts.add("Times")
36
+
37
+ sample_text = "Lorem ipsum dolor sit amet, con\u{00AD}sectetur
38
+ adipis\u{00AD}cing elit, sed do eiusmod tempor incididunt ut labore et
39
+ dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation
40
+ ullamco laboris nisi ut aliquip ex ea commodo consequat.
41
+ ".tr("\n", ' ') * 10
42
+
43
+ items = [TextFragment.create(sample_text, font: font)]
44
+ layouter = TextLayouter.new
45
+
46
+ ########################################################################
47
+ # Circly things on the top
48
+ radius = 100
49
+ circle_top = 840
50
+ half_circle_width = lambda do |height, line_height|
51
+ sum = height + line_height
52
+ if sum <= radius * 2
53
+ [Math.sqrt(radius**2 - (radius - height)**2),
54
+ Math.sqrt([radius**2 - (radius - sum)**2, 0].max)].min
55
+ else
56
+ 0
57
+ end
58
+ end
59
+ circle = lambda do |height, line_height|
60
+ w = half_circle_width.call(height, line_height)
61
+ [radius - w, 2 * w]
62
+ end
63
+ left_half_circle = lambda do |height, line_height|
64
+ w = half_circle_width.call(height, line_height)
65
+ [radius - w, w]
66
+ end
67
+
68
+ # Left: right half circle
69
+ result = layouter.fit(items, half_circle_width, radius * 2)
70
+ result.draw(canvas, 0, circle_top)
71
+ canvas.circle(0, circle_top - radius, radius).stroke
72
+
73
+ # Center: full circle
74
+ layouter.style.align = :justify
75
+ result = layouter.fit(items, circle, radius * 2)
76
+ result.draw(canvas, page.box(:media).width / 2.0 - radius, circle_top)
77
+ canvas.circle(page.box(:media).width / 2.0, circle_top - radius, radius).stroke
78
+
79
+ # Right: left half circle
80
+ layouter.style.align = :right
81
+ result = layouter.fit(items, left_half_circle, radius * 2)
82
+ result.draw(canvas, page.box(:media).width - radius, circle_top)
83
+ canvas.circle(page.box(:media).width, circle_top - radius, radius).stroke
84
+
85
+
86
+ ########################################################################
87
+ # Pointy, diamondy things in the middle
88
+
89
+ diamond_width = 100
90
+ diamond_top = circle_top - 2 * radius - 10
91
+ half_diamond_width = lambda do |height, line_height|
92
+ sum = height + line_height
93
+ if sum < diamond_width
94
+ height
95
+ else
96
+ [diamond_width * 2 - sum, 0].max
97
+ end
98
+ end
99
+ full_diamond = lambda do |height, line_height|
100
+ w = half_diamond_width.call(height, line_height)
101
+ [diamond_width - w, 2 * w]
102
+ end
103
+ left_half_diamond = lambda do |height, line_height|
104
+ w = half_diamond_width.call(height, line_height)
105
+ [diamond_width - w, w]
106
+ end
107
+
108
+ # Left: right half diamond
109
+ layouter.style.align = :left
110
+ result = layouter.fit(items, half_diamond_width, 2 * diamond_width)
111
+ result.draw(canvas, 0, diamond_top)
112
+ canvas.polyline(0, diamond_top, diamond_width, diamond_top - diamond_width,
113
+ 0, diamond_top - 2 * diamond_width).stroke
114
+
115
+ # Center: full diamond
116
+ layouter.style.align = :justify
117
+ result = layouter.fit(items, full_diamond, 2 * diamond_width)
118
+ left = page.box(:media).width / 2.0 - diamond_width
119
+ result.draw(canvas, left, diamond_top)
120
+ canvas.polyline(left + diamond_width, diamond_top,
121
+ left + 2 * diamond_width, diamond_top - diamond_width,
122
+ left + diamond_width, diamond_top - 2 * diamond_width,
123
+ left, diamond_top - diamond_width).close_subpath.stroke
124
+
125
+ # Right: left half diamond
126
+ layouter.style.align = :right
127
+ result = layouter.fit(items, left_half_diamond, 2 * diamond_width)
128
+ middle = page.box(:media).width
129
+ result.draw(canvas, middle - diamond_width, diamond_top)
130
+ canvas.polyline(middle, diamond_top,
131
+ middle - diamond_width, diamond_top - diamond_width,
132
+ middle, diamond_top - 2 * diamond_width).stroke
133
+
134
+
135
+ ########################################################################
136
+ # Sine wave thing next
137
+
138
+ sine_wave_height = 200.0
139
+ sine_wave_top = diamond_top - 2 * diamond_width - 10
140
+ sine_wave = lambda do |height, line_height|
141
+ offset = [40 * Math.sin(2 * Math::PI * (height / sine_wave_height)),
142
+ 40 * Math.sin(2 * Math::PI * (height + line_height) / sine_wave_height)].max
143
+ [offset, sine_wave_height + 100 + offset * -2]
144
+ end
145
+ layouter.style.align = :justify
146
+ result = layouter.fit(items, sine_wave, sine_wave_height)
147
+ middle = page.box(:media).width / 2.0
148
+ result.draw(canvas, middle - (sine_wave_height + 100) / 2, sine_wave_top)
149
+
150
+ ########################################################################
151
+ # And finally a house
152
+
153
+ house_top = sine_wave_top - sine_wave_height - 10
154
+ outer_width = 300.0
155
+ inner_width = 100.0
156
+ house = lambda do |height, line_height|
157
+ sum = height + line_height
158
+ first_part = (outer_width / 2 - inner_width / 2)
159
+ if (0..first_part).cover?(sum)
160
+ [-height, outer_width + height * 2]
161
+ elsif (first_part..(first_part + inner_width)).cover?(height) ||
162
+ (first_part..(first_part + inner_width)).cover?(sum)
163
+ [0, first_part, inner_width, first_part]
164
+ elsif sum <= outer_width
165
+ outer_width
166
+ else
167
+ 0
168
+ end
169
+ end
170
+ layouter.style.align = :justify
171
+ result = layouter.fit(items, house, 200)
172
+
173
+ middle = page.box(:media).width / 2.0
174
+ result.draw(canvas, middle - (outer_width / 2), house_top)
175
+
176
+ doc.write("text_layouter_shapes.pdf", optimize: true)
@@ -0,0 +1,60 @@
1
+ # ## Text in Polygon
2
+ #
3
+ # While creating width specifications for the [HexaPDF::Layout::TextLayouter]
4
+ # class by hand is possible, the [HexaPDF::Layout::WidthFromPolygon] class
5
+ # provides an easier way by using polygons.
6
+ #
7
+ # Most of the times text is laid out within polygonal shapes, so direct support
8
+ # for these makes text layout in HexaPDF easier.
9
+ #
10
+ # This example shows how much easier text layout is by re-doing the "house"
11
+ # example from the [Text Layouter - Shapes example](text_layouter_shapes.html).
12
+ # Additionally, there is an example using a complex polygon with a hole inside.
13
+ #
14
+ # Usage:
15
+ # : `ruby text_in_polygon.rb`
16
+ #
17
+
18
+ require 'hexapdf'
19
+ require 'geom2d'
20
+
21
+ include HexaPDF::Layout
22
+
23
+ doc = HexaPDF::Document.new
24
+ canvas = doc.pages.add.canvas
25
+
26
+ sample_text = "Lorem ipsum dolor sit amet, con\u{00AD}sectetur
27
+ adipis\u{00AD}cing elit, sed do eiusmod tempor incididunt ut labore et
28
+ dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation
29
+ ullamco laboris nisi ut aliquip ex ea commodo consequat.
30
+ ".tr("\n", ' ') * 12
31
+ items = [TextFragment.create(sample_text, font: doc.fonts.add("Times"))]
32
+ layouter = TextLayouter.new
33
+ layouter.style.align = :justify
34
+
35
+ # The house example
36
+ house = Geom2D::Polygon([100, 200], [400, 200], [500, 100], [400, 100], [400, 0],
37
+ [300, 0], [300, 100], [200, 100], [200, 0], [100, 0],
38
+ [100, 100], [0, 100])
39
+ width_spec = WidthFromPolygon.new(house)
40
+ result = layouter.fit(items, width_spec, house.bbox.height)
41
+ result.draw(canvas, 50, 750)
42
+
43
+ # A more complex example
44
+ polygon = Geom2D::PolygonSet(
45
+ Geom2D::Polygon([150, 450], [145, 198], [160, 196],
46
+ [200, 220], [200, 300], [300, 300], [400, 0],
47
+ [200, 0], [200, 100], [100, 100], [100, 0],
48
+ [-100, 0], [0, 300], [-50, 300], [100, 330]),
49
+ Geom2D::Polygon([50, 120], [250, 120], [250, 180], [50, 180]),
50
+ Geom2D::Polygon([60, 130], [240, 130], [240, 170], [60, 170])
51
+ )
52
+ width_spec = WidthFromPolygon.new(polygon)
53
+ result = layouter.fit(items, width_spec, polygon.bbox.height)
54
+ result.draw(canvas, 150, 550)
55
+ canvas.translate(150, 100).
56
+ stroke_color(255, 0, 0).opacity(stroke_alpha: 0.5).
57
+ line_width(0.5).
58
+ draw(:geom2d, object: polygon)
59
+
60
+ doc.write("text_in_polygon.pdf", optimize: true)