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
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)