hexapdf 0.4.0 → 0.5.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (90) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +46 -0
  3. data/CONTRIBUTERS +1 -1
  4. data/README.md +5 -5
  5. data/VERSION +1 -1
  6. data/examples/emoji-smile.png +0 -0
  7. data/examples/emoji-wink.png +0 -0
  8. data/examples/graphics.rb +9 -8
  9. data/examples/standard_pdf_fonts.rb +2 -1
  10. data/examples/text_box_alignment.rb +47 -0
  11. data/examples/text_box_inline_boxes.rb +56 -0
  12. data/examples/text_box_line_wrapping.rb +57 -0
  13. data/examples/text_box_shapes.rb +166 -0
  14. data/examples/text_box_styling.rb +72 -0
  15. data/examples/truetype.rb +3 -4
  16. data/lib/hexapdf/cli/optimize.rb +2 -2
  17. data/lib/hexapdf/configuration.rb +8 -6
  18. data/lib/hexapdf/content/canvas.rb +8 -5
  19. data/lib/hexapdf/content/parser.rb +3 -2
  20. data/lib/hexapdf/content/processor.rb +14 -3
  21. data/lib/hexapdf/document.rb +1 -0
  22. data/lib/hexapdf/document/fonts.rb +2 -1
  23. data/lib/hexapdf/document/pages.rb +23 -0
  24. data/lib/hexapdf/font/invalid_glyph.rb +78 -0
  25. data/lib/hexapdf/font/true_type/font.rb +14 -3
  26. data/lib/hexapdf/font/true_type/table.rb +1 -0
  27. data/lib/hexapdf/font/true_type/table/cmap.rb +1 -1
  28. data/lib/hexapdf/font/true_type/table/cmap_subtable.rb +1 -0
  29. data/lib/hexapdf/font/true_type/table/glyf.rb +4 -0
  30. data/lib/hexapdf/font/true_type/table/kern.rb +170 -0
  31. data/lib/hexapdf/font/true_type/table/post.rb +5 -1
  32. data/lib/hexapdf/font/true_type_wrapper.rb +71 -24
  33. data/lib/hexapdf/font/type1/afm_parser.rb +3 -2
  34. data/lib/hexapdf/font/type1/character_metrics.rb +0 -9
  35. data/lib/hexapdf/font/type1/font.rb +11 -0
  36. data/lib/hexapdf/font/type1/font_metrics.rb +6 -1
  37. data/lib/hexapdf/font/type1_wrapper.rb +51 -7
  38. data/lib/hexapdf/font_loader/standard14.rb +1 -1
  39. data/lib/hexapdf/layout.rb +51 -0
  40. data/lib/hexapdf/layout/inline_box.rb +95 -0
  41. data/lib/hexapdf/layout/line_fragment.rb +333 -0
  42. data/lib/hexapdf/layout/numeric_refinements.rb +56 -0
  43. data/lib/hexapdf/layout/style.rb +365 -0
  44. data/lib/hexapdf/layout/text_box.rb +727 -0
  45. data/lib/hexapdf/layout/text_fragment.rb +206 -0
  46. data/lib/hexapdf/layout/text_shaper.rb +155 -0
  47. data/lib/hexapdf/task.rb +0 -1
  48. data/lib/hexapdf/task/dereference.rb +1 -1
  49. data/lib/hexapdf/tokenizer.rb +3 -2
  50. data/lib/hexapdf/type/font_descriptor.rb +2 -1
  51. data/lib/hexapdf/type/font_type0.rb +3 -1
  52. data/lib/hexapdf/type/form.rb +12 -4
  53. data/lib/hexapdf/version.rb +1 -1
  54. data/test/hexapdf/common_tokenizer_tests.rb +7 -0
  55. data/test/hexapdf/content/common.rb +8 -0
  56. data/test/hexapdf/content/test_canvas.rb +10 -22
  57. data/test/hexapdf/content/test_processor.rb +4 -1
  58. data/test/hexapdf/document/test_pages.rb +16 -0
  59. data/test/hexapdf/font/test_invalid_glyph.rb +34 -0
  60. data/test/hexapdf/font/test_true_type_wrapper.rb +25 -11
  61. data/test/hexapdf/font/test_type1_wrapper.rb +26 -10
  62. data/test/hexapdf/font/true_type/table/common.rb +27 -0
  63. data/test/hexapdf/font/true_type/table/test_cmap.rb +14 -20
  64. data/test/hexapdf/font/true_type/table/test_cmap_subtable.rb +7 -0
  65. data/test/hexapdf/font/true_type/table/test_glyf.rb +8 -6
  66. data/test/hexapdf/font/true_type/table/test_head.rb +9 -13
  67. data/test/hexapdf/font/true_type/table/test_hhea.rb +16 -23
  68. data/test/hexapdf/font/true_type/table/test_hmtx.rb +4 -7
  69. data/test/hexapdf/font/true_type/table/test_kern.rb +61 -0
  70. data/test/hexapdf/font/true_type/table/test_loca.rb +7 -13
  71. data/test/hexapdf/font/true_type/table/test_maxp.rb +4 -9
  72. data/test/hexapdf/font/true_type/table/test_name.rb +14 -17
  73. data/test/hexapdf/font/true_type/table/test_os2.rb +3 -5
  74. data/test/hexapdf/font/true_type/table/test_post.rb +21 -19
  75. data/test/hexapdf/font/true_type/test_font.rb +4 -0
  76. data/test/hexapdf/font/type1/common.rb +6 -0
  77. data/test/hexapdf/font/type1/test_afm_parser.rb +9 -0
  78. data/test/hexapdf/font/type1/test_font.rb +6 -0
  79. data/test/hexapdf/layout/test_inline_box.rb +40 -0
  80. data/test/hexapdf/layout/test_line_fragment.rb +206 -0
  81. data/test/hexapdf/layout/test_style.rb +143 -0
  82. data/test/hexapdf/layout/test_text_box.rb +640 -0
  83. data/test/hexapdf/layout/test_text_fragment.rb +208 -0
  84. data/test/hexapdf/layout/test_text_shaper.rb +64 -0
  85. data/test/hexapdf/task/test_dereference.rb +1 -0
  86. data/test/hexapdf/test_writer.rb +2 -2
  87. data/test/hexapdf/type/test_font_descriptor.rb +4 -2
  88. data/test/hexapdf/type/test_font_type0.rb +7 -0
  89. data/test/hexapdf/type/test_form.rb +12 -0
  90. metadata +29 -2
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: 506ca50e79972288ec42dcfb6b31b436500da5c7
4
- data.tar.gz: ea88dc0891a1ce15cf40a6d8cc4e6a15e9a05583
3
+ metadata.gz: 3d9818c9cdb9673d2349c392e35b0b1460841a9d
4
+ data.tar.gz: cef36589c13850c4b3c1fd6fa976fb605a536bff
5
5
  SHA512:
6
- metadata.gz: b9a9fd668aa6a2ad42a93a42c7cd6d1e2864a5841cf21bd1191815916a8cca4a229d3a1ed5657ece44edbadd1b82975f974c2dc10ae10bd8ca9a720649ce4dcf
7
- data.tar.gz: e2bf96c4158ca5cb440bebe9fb85b3d1a61e7b8caa53a13908ab1ac4b9638488593ff6875382d799477e1fedcea4620f248e71f9970fd6478b1ce94b06a9ed61
6
+ metadata.gz: e174684958b97a165844e15fdb458172c095cc4c8c1d3a2cfce9a73bd9a3bc1d367e34195d8fbe729cb61b1b71a2ab0dca0fbd91e19cc408510107fafa5c0518
7
+ data.tar.gz: 27a927ffbd911851187867ae59afbb31b0edabfb2edb6912b682e5af9236711aa51edc683304b0a4b6cdbfba6dd736fee8b6c11766afd8e1b631dc35c04cce30
@@ -1,3 +1,49 @@
1
+ ## 0.5.0 - 2017-06-24
2
+
3
+ ### Added
4
+
5
+ * [HexaPDF::Layout::TextBox] for easy positioning and layouting of text
6
+ * [HexaPDF::Layout::LineFragment] for single text line layout calculations
7
+ * [HexaPDF::Layout::TextShaper] for text shaping functionality
8
+ * [HexaPDF::Layout::TextFragment] for basic text metrics calculations
9
+ * [HexaPDF::Layout::InlineBox] for fixed size inline graphics
10
+ * [HexaPDF::Layout::Style] as container for text and graphics styling properties
11
+ * Support for kerning of TrueType fonts via the 'kern' table
12
+ * Support for determining the features provided by a font
13
+
14
+ ### Changed
15
+
16
+ * Handling of invalid glyphs is done using the special
17
+ [HexaPDF::Font::InvalidGlyph] class
18
+ * Configuration option 'font.on_missing_glyph'; returns an invalid glyph
19
+ instead of raising an error
20
+ * Bounding box of TrueType glyphs without contours is set to `[0, 0, 0, 0]`
21
+ * Ligature pairs for AFM fonts are stored like kerning pairs
22
+ * Use TrueType configuration option 'font.true_type.unknown_format' in all
23
+ places where applicable
24
+ * Allow passing a font object to [HexaPDF::Content::Canvas#font]
25
+ * Handle invalid entry in TrueType format 4 cmap subtable encountered in the
26
+ wild gracefully
27
+ * Invalid positive descent values in font descriptors are now changed into
28
+ negative ones by the validation feature
29
+ * Allow specifying the page media box or a page format when adding a new page
30
+ through [HexaPDF::Document::Pages#add]
31
+
32
+ ### Fixed
33
+
34
+ * [HexaPDF::Task::Dereference] to work correctly when encountering invalid
35
+ references
36
+ * [HexaPDF::Tokenizer] and HexaPDF::Content::Tokenizer to parse a solitary
37
+ plus sign
38
+ * Usage of Strings instead of Symbols for AFM font kerning and ligature pairs
39
+ * Processing the contents of form XObjects in case they don't have a resources
40
+ dictionary
41
+ * Deletion of valid page node when optimizing page trees with the `hexapdf
42
+ optimize` command
43
+ * [HexaPDF::Type::FontType0] to always wrap the descendant font even if it is a
44
+ direct object
45
+
46
+
1
47
  ## 0.4.0 - 2017-03-19
2
48
 
3
49
  ### Added
@@ -1,3 +1,3 @@
1
1
  Count Name
2
2
  ======= ====
3
- 741 Thomas Leitner <t_leitner@gmx.at>
3
+ 802 Thomas Leitner <t_leitner@gmx.at>
data/README.md CHANGED
@@ -43,8 +43,8 @@ documentation, example code and more.
43
43
  ## Requirements and Installation
44
44
 
45
45
  Since HexaPDF is written in Ruby, a working Ruby installation is needed - see the
46
- [official installation documentation][rbinstall] for details. Note that you need Ruby version 2.3 or
47
- higher as prior versions are not (officially) supported!
46
+ [official installation documentation][rbinstall] for details. Note that you need Ruby version 2.4 or
47
+ higher as prior versions are not supported!
48
48
 
49
49
  Apart from Ruby itself the HexaPDF library has no external dependencies. The `hexapdf` application
50
50
  has a dependency on `cmdparse`, a command line parsing library.
@@ -69,9 +69,9 @@ Prawn has no such functionality. There is basic support for using a PDF as a tem
69
69
  featureful API when it comes to creating content, for individual pages as well as across pages.
70
70
 
71
71
  Such functionality will be incorporated into HexaPDF in the near future. The main functionality for
72
- providing such a feature is already available in HexaPDF (the [page canvas API]). What's missing
73
- (and this is quite big chunk) is automatic box and page layout so that one can say: There is a box
74
- on this page with these dimensions at this position; show this text in the box.
72
+ providing such a feature is already available in HexaPDF (the [page canvas API]). Additionally,
73
+ laying out text inside a box with line wrapping and such is also supported. What's missing (and this
74
+ is still quite a big chunk) is support for advanced features like tables, page breaking and so on.
75
75
 
76
76
  So why use HexaPDF?
77
77
 
data/VERSION CHANGED
@@ -1 +1 @@
1
- 0.4.0
1
+ 0.5.0
Binary file
Binary file
@@ -102,9 +102,9 @@ canvas.translate(0, 550) do
102
102
  canvas.translate(490, 0) do
103
103
  canvas.line_width(1)
104
104
  [[[1, 1]],
105
- [[3, 1]],
106
- [[3, 3]],
107
- [[5, 1, 1, 1, 1, 1]],
105
+ [[3, 1]],
106
+ [[3, 3]],
107
+ [[5, 1, 1, 1, 1, 1]],
108
108
  [[3, 5], 6]].each_with_index do |(value, phase), index|
109
109
  canvas.line_dash_pattern(value, phase || 0)
110
110
  canvas.line(20 * index, 0, 20 * index, 100)
@@ -140,7 +140,7 @@ canvas.translate(0, 320) do
140
140
  canvas.arc(380, 50, a: 40, b: 20, start_angle: -60, end_angle: 115, inclination: 45)
141
141
  canvas.fill
142
142
 
143
- arc = canvas.graphic_object(:arc, cx: 450, cy: 50, a: 30, b: 30,
143
+ arc = canvas.graphic_object(:arc, cx: 450, cy: 50, a: 30, b: 30,
144
144
  start_angle: -30, end_angle: 105)
145
145
  canvas.fill_color(0.4, 0.3, 0.4)
146
146
  canvas.move_to(450, 50)
@@ -196,10 +196,11 @@ end
196
196
  canvas.translate(0, 190) do
197
197
  canvas.fill_color(0.3, 0.7, 0.7)
198
198
 
199
- [[:stroke], [:close_stroke], [:fill, :nonzero], [:fill, :even_odd],
200
- [:fill_stroke, :nonzero], [:fill_stroke, :even_odd],
201
- [:close_fill_stroke, :nonzero], [:close_fill_stroke, :even_odd],
202
- ].each_with_index do |op, index|
199
+ [
200
+ [:stroke], [:close_stroke], [:fill, :nonzero], [:fill, :even_odd],
201
+ [:fill_stroke, :nonzero], [:fill_stroke, :even_odd],
202
+ [:close_fill_stroke, :nonzero], [:close_fill_stroke, :even_odd]
203
+ ].each_with_index do |op, index|
203
204
  row = (1 - (index / 4))
204
205
  column = index % 4
205
206
  x = 50 + 80 * column
@@ -42,7 +42,8 @@ HexaPDF::FontLoader::Standard14::MAPPING.each do |font_name, mapping|
42
42
  data = []
43
43
  (0..15).each do |x|
44
44
  code = y * 16 + x
45
- glyph = font.glyph(encoding.name(code)) rescue font.glyph(:space)
45
+ glyph = font.glyph(encoding.name(code))
46
+ glyph = font.glyph(:space) if glyph.id == font.wrapped_font.missing_glyph_id
46
47
  used_glyphs << glyph.name
47
48
  data << glyph << -(2000 - glyph.width)
48
49
  end
@@ -0,0 +1,47 @@
1
+ # ## Text Box Alignment
2
+ #
3
+ # The [HexaPDF::Layout::TextBox] class can be used to easily lay out text inside
4
+ # a rectangular area, with various horizontal and vertical alignment options.
5
+ #
6
+ # The text inside the box can be aligned horizontally by setting
7
+ # [HexaPDF::Layout::Style#align] and vertically by
8
+ # [HexaPDF::Layout::Style#valign]. In this example, a sample text is laid out in
9
+ # all possible combinations.
10
+ #
11
+ # Usage:
12
+ # : `ruby text_box_alignment.rb`
13
+ #
14
+
15
+ require 'hexapdf'
16
+
17
+ sample_text = "Lorem ipsum dolor sit amet, consectetur adipiscing elit,
18
+ sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut
19
+ enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut
20
+ aliquip ex ea commodo consequat. at".tr("\n", ' ')
21
+
22
+ doc = HexaPDF::Document.new
23
+ canvas = doc.pages.add.canvas
24
+ canvas.font("Times", size: 10, variant: :bold)
25
+
26
+ width = 100
27
+ height = 150
28
+ y_base = 800
29
+ box = HexaPDF::Layout::TextBox.create(sample_text, width: width,
30
+ height: height,
31
+ font: doc.fonts.load("Times"))
32
+
33
+ [:left, :center, :right, :justify].each_with_index do |align, x_index|
34
+ x = x_index * (width + 20) + 70
35
+ canvas.text(align.to_s, at: [x + 40, y_base + 15])
36
+
37
+ [:top, :center, :bottom].each_with_index do |valign, y_index|
38
+ y = y_base - (height + 30) * y_index
39
+ canvas.text(valign.to_s, at: [20, y - height / 2]) if x_index == 0
40
+
41
+ box.style.align(align).valign(valign)
42
+ box.draw(canvas, x, y, fit: true)
43
+ canvas.stroke_color(128, 0, 0).rectangle(x, y, width, -height).stroke
44
+ end
45
+ end
46
+
47
+ doc.write("text_box_alignment.pdf", optimize: true)
@@ -0,0 +1,56 @@
1
+ # ## Text Box with Inline Boxes
2
+ #
3
+ # The [HexaPDF::Layout::TextBox] class can be used to easily lay out text mixed
4
+ # with inline boxes.
5
+ #
6
+ # Inline boxes are used for showing graphics that follow the flow of the text.
7
+ # This means that their horizontal and their general vertical position is
8
+ # determined by the text layout functionality. However, inline boxes may be
9
+ # vertically aligned to various positions, like the baseline, the top/bottom of
10
+ # the text and the top/bottom of the line.
11
+ #
12
+ # This example shows some text containing emoticons that are replaced with their
13
+ # graphical representation, with normal smileys being aligned to the baseline
14
+ # and winking smileys to the top of the line.
15
+ #
16
+ # Usage:
17
+ # : `ruby text_box_inline_boxes.rb`
18
+ #
19
+
20
+ require 'hexapdf'
21
+
22
+ include HexaPDF::Layout
23
+
24
+ sample_text = "Lorem ipsum :-) dolor sit amet, consectetur adipiscing
25
+ ;-) elit, sed do eiusmod tempor incididunt :-) ut labore et dolore magna
26
+ aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco
27
+ laboris nisi ut aliquip ex ea commodo consequat ;-). Duis aute irure
28
+ dolor in reprehenderit in voluptate velit esse cillum :-) dolore eu
29
+ fugiat nulla pariatur. ".tr("\n", ' ') * 4
30
+
31
+ doc = HexaPDF::Document.new
32
+ emoji_smile = doc.images.add(File.join(__dir__, "emoji-smile.png"))
33
+ emoji_wink = doc.images.add(File.join(__dir__, "emoji-wink.png"))
34
+ size = 10
35
+
36
+ items = sample_text.split(/(:-\)|;-\))/).map do |part|
37
+ case part
38
+ when ':-)'
39
+ InlineBox.new(size * 2, size * 2, valign: :baseline) do |box, canvas|
40
+ canvas.image(emoji_smile, at: [0, 0], width: box.width)
41
+ end
42
+ when ';-)'
43
+ InlineBox.new(size, size, valign: :top) do |box, canvas|
44
+ canvas.image(emoji_wink, at: [0, 0], width: box.width)
45
+ end
46
+ else
47
+ TextFragment.create(part, font: doc.fonts.load("Times"), font_size: 18)
48
+ end
49
+ end
50
+
51
+ box = TextBox.new(items: items, width: 500, height: 700)
52
+ box.style.align = :justify
53
+ box.style.line_spacing(:proportional, 1.5)
54
+ box.draw(doc.pages.add.canvas, 50, 800)
55
+
56
+ doc.write("text_box_inline_boxes.pdf")
@@ -0,0 +1,57 @@
1
+ # ## Text Box Line Wrapping
2
+ #
3
+ # The [HexaPDF::Layout::TextBox] class can be used to easily lay out text,
4
+ # automatically wrapping it appropriately.
5
+ #
6
+ # Text is broken only at certain characters:
7
+ #
8
+ # * The most important break points are **spaces**.
9
+ #
10
+ # * Lines can be broken at **tabulators** which represent eight spaces.
11
+ #
12
+ # * **Newline characters** are respected when wrapping and introduce a line
13
+ # break. They have to be removed beforehand if this is not wanted. All Unicode
14
+ # newline separators are recognized.
15
+ #
16
+ # * **Hyphens** are used as break points, possibly breaking just after them.
17
+ #
18
+ # * In addition to hyphens, **soft-hyphens** can be used to indicate break
19
+ # points. In contrast to hyphens, soft-hyphens won't be visible unless a line
20
+ # is broken at its position.
21
+ #
22
+ # * **Zero-width spaces** can be used to indicate break points at any position.
23
+ #
24
+ # * **Non-breaking spaces** can be used to prohibit a break between two words.
25
+ # It has the same appearance as a space in the PDF.
26
+ #
27
+ # This example shows all these specially handled characters in action, e.g. a
28
+ # hard line break after "Fly-fishing", soft-hyphen in "wandering", tabulator
29
+ # instead of space after "wandering", zero-width space in "fantastic" and
30
+ # non-breaking spaces in "1 0 1".
31
+ #
32
+ # Usage:
33
+ # : `ruby text_box_line_wrapping.rb`
34
+ #
35
+
36
+ require 'hexapdf'
37
+
38
+ doc = HexaPDF::Document.new
39
+ canvas = doc.pages.add([0, 0, 180, 230]).canvas
40
+ canvas.font("Times", size: 10, variant: :bold)
41
+
42
+ text = "Hello! Fly-fishing\nand wand\u{00AD}ering\taround - fanta\u{200B}stic" \
43
+ " 1\u{00A0}0\u{00A0}1"
44
+
45
+ x = 10
46
+ y = 220
47
+ [30, 60, 100, 160].each do |width|
48
+ box = HexaPDF::Layout::TextBox.create(text, width: width,
49
+ font: doc.fonts.load("Times"))
50
+ _, height = box.fit
51
+ box.draw(canvas, x, y)
52
+ canvas.stroke_color(255, 0, 0).line_width(0.2)
53
+ canvas.rectangle(x, y, width, -height).stroke
54
+ y -= height + 5
55
+ end
56
+
57
+ doc.write("text_box_line_wrapping.pdf", optimize: true)
@@ -0,0 +1,166 @@
1
+ # ## Text Box Shapes
2
+ #
3
+ # The [HexaPDF::Layout::TextBox] class can be used to easily lay out text, not
4
+ # limiting the area to a rectangle but any shape. There is only one restriction:
5
+ # In the case of arbitrary shapes the vertical alignment has to be "top".
6
+ #
7
+ # Arbitrary shapes boil down to varying line widths and horizontal offsets from
8
+ # left. Imagine a circle: If text is fit in a circle, the line widths start at
9
+ # zero, getting larger and larger until the middle of the cirle. And then they
10
+ # get smaller until zero again. The x-values of the left half circle determine
11
+ # the horizontal offsets.
12
+ #
13
+ # Both, the line widths and the horizontal offsets can be calculated given a
14
+ # certain height, and this is exactly what HexaPDF uses. If the `width` argument
15
+ # to [HexaPDF::Layout::TextBox::new] is an object responding to #call (e.g. a
16
+ # lambda), it is used for determining the line widths. And the `x_offsets`
17
+ # argument can be used in a similar way for the horizontal offsets.
18
+ #
19
+ # This example shows text layed out in various shapes, using the above mentioned
20
+ # techniques.
21
+ #
22
+ # Usage:
23
+ # : `ruby text_box_shapes.rb`
24
+ #
25
+
26
+ require 'hexapdf'
27
+
28
+ doc = HexaPDF::Document.new
29
+ page = doc.pages.add
30
+ canvas = page.canvas
31
+ canvas.font("Times", size: 10, variant: :bold)
32
+ canvas.stroke_color(255, 0, 0).line_width(0.2)
33
+
34
+ sample_text = "Lorem ipsum dolor sit amet, con\u{00AD}sectetur
35
+ adipis\u{00AD}cing elit, sed do eiusmod tempor incididunt ut labore et
36
+ dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation
37
+ ullamco laboris nisi ut aliquip ex ea commodo consequat.
38
+ ".tr("\n", ' ') * 10
39
+
40
+ ########################################################################
41
+ # Circly things on the top
42
+ radius = 100
43
+ circle_top = 800
44
+ half_circle_widths = lambda do |height, line_height|
45
+ sum = height + line_height
46
+ if sum <= radius * 2
47
+ [Math.sqrt(radius**2 - (radius - height)**2),
48
+ Math.sqrt([radius**2 - (radius - sum)**2, 0].max)].min
49
+ else
50
+ 0
51
+ end
52
+ end
53
+ circle_widths = lambda do |height, line_height|
54
+ 2 * half_circle_widths.call(height, line_height)
55
+ end
56
+ left_half_circle_offsets = lambda do |height, line_height|
57
+ radius - half_circle_widths.call(height, line_height)
58
+ end
59
+
60
+ # Left: right half circle
61
+ box = HexaPDF::Layout::TextBox.create(sample_text,
62
+ width: half_circle_widths,
63
+ height: radius * 2,
64
+ font: doc.fonts.load("Times"))
65
+ box.draw(canvas, 0, circle_top)
66
+ canvas.circle(0, circle_top - radius, radius).stroke
67
+
68
+ # Center: full circle
69
+ box = HexaPDF::Layout::TextBox.create(sample_text,
70
+ width: circle_widths,
71
+ x_offsets: left_half_circle_offsets,
72
+ height: radius * 2,
73
+ font: doc.fonts.load("Times"),
74
+ align: :justify)
75
+ box.draw(canvas, page.box(:media).width / 2.0 - radius, circle_top)
76
+ canvas.circle(page.box(:media).width / 2.0, circle_top - radius, radius).stroke
77
+
78
+ # Right: left half circle
79
+ box = HexaPDF::Layout::TextBox.create(sample_text,
80
+ width: half_circle_widths,
81
+ x_offsets: left_half_circle_offsets,
82
+ height: radius * 2,
83
+ font: doc.fonts.load("Times"),
84
+ align: :right)
85
+ box.draw(canvas, page.box(:media).width - radius, circle_top)
86
+ canvas.circle(page.box(:media).width, circle_top - radius, radius).stroke
87
+
88
+
89
+ ########################################################################
90
+ # Pointy, diamondy things in the middle
91
+
92
+ diamond_width = 100
93
+ diamond_top = circle_top - 2 * radius - 50
94
+ half_diamond_widths = lambda do |height, line_height|
95
+ sum = height + line_height
96
+ if sum < diamond_width
97
+ height
98
+ else
99
+ [diamond_width * 2 - sum, 0].max
100
+ end
101
+ end
102
+ full_diamond_widths = lambda do |height, line_height|
103
+ 2 * half_diamond_widths.call(height, line_height)
104
+ end
105
+ left_half_diamond_offsets = lambda do |height, line_height|
106
+ diamond_width - half_diamond_widths.call(height, line_height)
107
+ end
108
+
109
+ # Left: right half diamond
110
+ box = HexaPDF::Layout::TextBox.create(sample_text,
111
+ width: half_diamond_widths,
112
+ height: 2 * diamond_width,
113
+ font: doc.fonts.load("Times"))
114
+ box.draw(canvas, 0, diamond_top)
115
+ canvas.polyline(0, diamond_top, diamond_width, diamond_top - diamond_width,
116
+ 0, diamond_top - 2 * diamond_width).stroke
117
+
118
+ # Center: full diamond
119
+ box = HexaPDF::Layout::TextBox.create(sample_text,
120
+ width: full_diamond_widths,
121
+ x_offsets: left_half_diamond_offsets,
122
+ height: 2 * diamond_width,
123
+ font: doc.fonts.load("Times"),
124
+ align: :justify)
125
+ left = page.box(:media).width / 2.0 - diamond_width
126
+ box.draw(canvas, left, diamond_top)
127
+ canvas.polyline(left + diamond_width, diamond_top,
128
+ left + 2 * diamond_width, diamond_top - diamond_width,
129
+ left + diamond_width, diamond_top - 2 * diamond_width,
130
+ left, diamond_top - diamond_width).close_subpath.stroke
131
+
132
+ # Right: left half diamond
133
+ box = HexaPDF::Layout::TextBox.create(sample_text,
134
+ width: half_diamond_widths,
135
+ x_offsets: left_half_diamond_offsets,
136
+ height: 2 * diamond_width,
137
+ font: doc.fonts.load("Times"),
138
+ align: :right)
139
+ middle = page.box(:media).width
140
+ box.draw(canvas, middle - diamond_width, diamond_top)
141
+ canvas.polyline(middle, diamond_top,
142
+ middle - diamond_width, diamond_top - diamond_width,
143
+ middle, diamond_top - 2 * diamond_width).stroke
144
+
145
+ ########################################################################
146
+ # Sine wave thing at the bottom
147
+
148
+ sine_wave_height = 200.0
149
+ sine_wave_top = diamond_top - 2 * diamond_width - 50
150
+ sine_wave_offsets = lambda do |height, line_height|
151
+ [40 * Math.sin(2 * Math::PI * (height / sine_wave_height)),
152
+ 40 * Math.sin(2 * Math::PI * (height + line_height) / sine_wave_height)].max
153
+ end
154
+ sine_wave_widths = lambda do |height, line_height|
155
+ sine_wave_height + 100 + sine_wave_offsets.call(height, line_height) * -2
156
+ end
157
+ box = HexaPDF::Layout::TextBox.create(sample_text,
158
+ width: sine_wave_widths,
159
+ x_offsets: sine_wave_offsets,
160
+ height: sine_wave_height,
161
+ font: doc.fonts.load("Times"),
162
+ align: :justify)
163
+ middle = page.box(:media).width / 2.0
164
+ box.draw(canvas, middle - (sine_wave_height + 100) / 2, sine_wave_top)
165
+
166
+ doc.write("text_box_shapes.pdf", optimize: true)