hexapdf 0.17.1 → 0.17.2

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 (255) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +1024 -0
  3. data/LICENSE +29 -0
  4. data/README.md +129 -0
  5. data/Rakefile +109 -0
  6. data/agpl-3.0.txt +661 -0
  7. data/examples/001-hello_world.rb +16 -0
  8. data/examples/002-graphics.rb +275 -0
  9. data/examples/003-arcs.rb +50 -0
  10. data/examples/004-optimizing.rb +23 -0
  11. data/examples/005-merging.rb +27 -0
  12. data/examples/006-standard_pdf_fonts.rb +73 -0
  13. data/examples/007-truetype.rb +42 -0
  14. data/examples/008-show_char_bboxes.rb +55 -0
  15. data/examples/009-text_layouter_alignment.rb +47 -0
  16. data/examples/010-text_layouter_inline_boxes.rb +64 -0
  17. data/examples/011-text_layouter_line_wrapping.rb +57 -0
  18. data/examples/012-text_layouter_styling.rb +122 -0
  19. data/examples/013-text_layouter_shapes.rb +176 -0
  20. data/examples/014-text_in_polygon.rb +60 -0
  21. data/examples/015-boxes.rb +76 -0
  22. data/examples/016-frame_automatic_box_placement.rb +90 -0
  23. data/examples/017-frame_text_flow.rb +60 -0
  24. data/examples/018-composer.rb +44 -0
  25. data/examples/019-acro_form.rb +88 -0
  26. data/examples/emoji-smile.png +0 -0
  27. data/examples/emoji-wink.png +0 -0
  28. data/examples/machupicchu.jpg +0 -0
  29. data/lib/hexapdf/content/graphic_object/endpoint_arc.rb +66 -0
  30. data/lib/hexapdf/content/graphic_object/geom2d.rb +13 -0
  31. data/lib/hexapdf/version.rb +1 -1
  32. data/test/data/aes-test-vectors/CBCGFSbox-128-decrypt.data.gz +0 -0
  33. data/test/data/aes-test-vectors/CBCGFSbox-128-encrypt.data.gz +0 -0
  34. data/test/data/aes-test-vectors/CBCGFSbox-192-decrypt.data.gz +0 -0
  35. data/test/data/aes-test-vectors/CBCGFSbox-192-encrypt.data.gz +0 -0
  36. data/test/data/aes-test-vectors/CBCGFSbox-256-decrypt.data.gz +0 -0
  37. data/test/data/aes-test-vectors/CBCGFSbox-256-encrypt.data.gz +0 -0
  38. data/test/data/aes-test-vectors/CBCKeySbox-128-decrypt.data.gz +0 -0
  39. data/test/data/aes-test-vectors/CBCKeySbox-128-encrypt.data.gz +0 -0
  40. data/test/data/aes-test-vectors/CBCKeySbox-192-decrypt.data.gz +0 -0
  41. data/test/data/aes-test-vectors/CBCKeySbox-192-encrypt.data.gz +0 -0
  42. data/test/data/aes-test-vectors/CBCKeySbox-256-decrypt.data.gz +0 -0
  43. data/test/data/aes-test-vectors/CBCKeySbox-256-encrypt.data.gz +0 -0
  44. data/test/data/aes-test-vectors/CBCVarKey-128-decrypt.data.gz +0 -0
  45. data/test/data/aes-test-vectors/CBCVarKey-128-encrypt.data.gz +0 -0
  46. data/test/data/aes-test-vectors/CBCVarKey-192-decrypt.data.gz +0 -0
  47. data/test/data/aes-test-vectors/CBCVarKey-192-encrypt.data.gz +0 -0
  48. data/test/data/aes-test-vectors/CBCVarKey-256-decrypt.data.gz +0 -0
  49. data/test/data/aes-test-vectors/CBCVarKey-256-encrypt.data.gz +0 -0
  50. data/test/data/aes-test-vectors/CBCVarTxt-128-decrypt.data.gz +0 -0
  51. data/test/data/aes-test-vectors/CBCVarTxt-128-encrypt.data.gz +0 -0
  52. data/test/data/aes-test-vectors/CBCVarTxt-192-decrypt.data.gz +0 -0
  53. data/test/data/aes-test-vectors/CBCVarTxt-192-encrypt.data.gz +0 -0
  54. data/test/data/aes-test-vectors/CBCVarTxt-256-decrypt.data.gz +0 -0
  55. data/test/data/aes-test-vectors/CBCVarTxt-256-encrypt.data.gz +0 -0
  56. data/test/data/fonts/Ubuntu-Title.ttf +0 -0
  57. data/test/data/images/cmyk.jpg +0 -0
  58. data/test/data/images/fillbytes.jpg +0 -0
  59. data/test/data/images/gray.jpg +0 -0
  60. data/test/data/images/greyscale-1bit.png +0 -0
  61. data/test/data/images/greyscale-2bit.png +0 -0
  62. data/test/data/images/greyscale-4bit.png +0 -0
  63. data/test/data/images/greyscale-8bit.png +0 -0
  64. data/test/data/images/greyscale-alpha-8bit.png +0 -0
  65. data/test/data/images/greyscale-trns-8bit.png +0 -0
  66. data/test/data/images/greyscale-with-gamma1.0.png +0 -0
  67. data/test/data/images/greyscale-with-gamma1.5.png +0 -0
  68. data/test/data/images/indexed-1bit.png +0 -0
  69. data/test/data/images/indexed-2bit.png +0 -0
  70. data/test/data/images/indexed-4bit.png +0 -0
  71. data/test/data/images/indexed-8bit.png +0 -0
  72. data/test/data/images/indexed-alpha-4bit.png +0 -0
  73. data/test/data/images/indexed-alpha-8bit.png +0 -0
  74. data/test/data/images/rgb.jpg +0 -0
  75. data/test/data/images/truecolour-8bit.png +0 -0
  76. data/test/data/images/truecolour-alpha-8bit.png +0 -0
  77. data/test/data/images/truecolour-gama-chrm-8bit.png +0 -0
  78. data/test/data/images/truecolour-srgb-8bit.png +0 -0
  79. data/test/data/images/ycck.jpg +0 -0
  80. data/test/data/minimal.pdf +44 -0
  81. data/test/data/standard-security-handler/README +9 -0
  82. data/test/data/standard-security-handler/bothpwd-aes-128bit-V4.pdf +44 -0
  83. data/test/data/standard-security-handler/bothpwd-aes-256bit-V5.pdf +0 -0
  84. data/test/data/standard-security-handler/bothpwd-arc4-128bit-V2.pdf +43 -0
  85. data/test/data/standard-security-handler/bothpwd-arc4-128bit-V4.pdf +43 -0
  86. data/test/data/standard-security-handler/bothpwd-arc4-40bit-V1.pdf +0 -0
  87. data/test/data/standard-security-handler/nopwd-aes-128bit-V4.pdf +43 -0
  88. data/test/data/standard-security-handler/nopwd-aes-256bit-V5.pdf +0 -0
  89. data/test/data/standard-security-handler/nopwd-arc4-128bit-V2.pdf +43 -0
  90. data/test/data/standard-security-handler/nopwd-arc4-128bit-V4.pdf +43 -0
  91. data/test/data/standard-security-handler/nopwd-arc4-40bit-V1.pdf +43 -0
  92. data/test/data/standard-security-handler/ownerpwd-aes-128bit-V4.pdf +0 -0
  93. data/test/data/standard-security-handler/ownerpwd-aes-256bit-V5.pdf +43 -0
  94. data/test/data/standard-security-handler/ownerpwd-arc4-128bit-V2.pdf +43 -0
  95. data/test/data/standard-security-handler/ownerpwd-arc4-128bit-V4.pdf +43 -0
  96. data/test/data/standard-security-handler/ownerpwd-arc4-40bit-V1.pdf +43 -0
  97. data/test/data/standard-security-handler/userpwd-aes-128bit-V4.pdf +43 -0
  98. data/test/data/standard-security-handler/userpwd-aes-256bit-V5.pdf +43 -0
  99. data/test/data/standard-security-handler/userpwd-arc4-128bit-V2.pdf +0 -0
  100. data/test/data/standard-security-handler/userpwd-arc4-128bit-V4.pdf +0 -0
  101. data/test/data/standard-security-handler/userpwd-arc4-40bit-V1.pdf +43 -0
  102. data/test/hexapdf/common_tokenizer_tests.rb +236 -0
  103. data/test/hexapdf/content/common.rb +39 -0
  104. data/test/hexapdf/content/graphic_object/test_arc.rb +102 -0
  105. data/test/hexapdf/content/graphic_object/test_endpoint_arc.rb +90 -0
  106. data/test/hexapdf/content/graphic_object/test_geom2d.rb +79 -0
  107. data/test/hexapdf/content/graphic_object/test_solid_arc.rb +86 -0
  108. data/test/hexapdf/content/test_canvas.rb +1279 -0
  109. data/test/hexapdf/content/test_color_space.rb +176 -0
  110. data/test/hexapdf/content/test_graphics_state.rb +151 -0
  111. data/test/hexapdf/content/test_operator.rb +619 -0
  112. data/test/hexapdf/content/test_parser.rb +99 -0
  113. data/test/hexapdf/content/test_processor.rb +163 -0
  114. data/test/hexapdf/content/test_transformation_matrix.rb +64 -0
  115. data/test/hexapdf/document/test_files.rb +72 -0
  116. data/test/hexapdf/document/test_fonts.rb +60 -0
  117. data/test/hexapdf/document/test_images.rb +72 -0
  118. data/test/hexapdf/document/test_pages.rb +130 -0
  119. data/test/hexapdf/encryption/common.rb +87 -0
  120. data/test/hexapdf/encryption/test_aes.rb +129 -0
  121. data/test/hexapdf/encryption/test_arc4.rb +39 -0
  122. data/test/hexapdf/encryption/test_fast_aes.rb +17 -0
  123. data/test/hexapdf/encryption/test_fast_arc4.rb +12 -0
  124. data/test/hexapdf/encryption/test_identity.rb +21 -0
  125. data/test/hexapdf/encryption/test_ruby_aes.rb +23 -0
  126. data/test/hexapdf/encryption/test_ruby_arc4.rb +20 -0
  127. data/test/hexapdf/encryption/test_security_handler.rb +380 -0
  128. data/test/hexapdf/encryption/test_standard_security_handler.rb +322 -0
  129. data/test/hexapdf/filter/common.rb +53 -0
  130. data/test/hexapdf/filter/test_ascii85_decode.rb +59 -0
  131. data/test/hexapdf/filter/test_ascii_hex_decode.rb +38 -0
  132. data/test/hexapdf/filter/test_crypt.rb +21 -0
  133. data/test/hexapdf/filter/test_encryption.rb +24 -0
  134. data/test/hexapdf/filter/test_flate_decode.rb +44 -0
  135. data/test/hexapdf/filter/test_lzw_decode.rb +52 -0
  136. data/test/hexapdf/filter/test_predictor.rb +219 -0
  137. data/test/hexapdf/filter/test_run_length_decode.rb +32 -0
  138. data/test/hexapdf/font/cmap/test_parser.rb +102 -0
  139. data/test/hexapdf/font/cmap/test_writer.rb +66 -0
  140. data/test/hexapdf/font/encoding/test_base.rb +45 -0
  141. data/test/hexapdf/font/encoding/test_difference_encoding.rb +29 -0
  142. data/test/hexapdf/font/encoding/test_glyph_list.rb +59 -0
  143. data/test/hexapdf/font/encoding/test_zapf_dingbats_encoding.rb +16 -0
  144. data/test/hexapdf/font/test_cmap.rb +104 -0
  145. data/test/hexapdf/font/test_encoding.rb +27 -0
  146. data/test/hexapdf/font/test_invalid_glyph.rb +34 -0
  147. data/test/hexapdf/font/test_true_type_wrapper.rb +186 -0
  148. data/test/hexapdf/font/test_type1_wrapper.rb +107 -0
  149. data/test/hexapdf/font/true_type/common.rb +17 -0
  150. data/test/hexapdf/font/true_type/table/common.rb +27 -0
  151. data/test/hexapdf/font/true_type/table/test_cmap.rb +47 -0
  152. data/test/hexapdf/font/true_type/table/test_cmap_subtable.rb +141 -0
  153. data/test/hexapdf/font/true_type/table/test_directory.rb +30 -0
  154. data/test/hexapdf/font/true_type/table/test_glyf.rb +58 -0
  155. data/test/hexapdf/font/true_type/table/test_head.rb +56 -0
  156. data/test/hexapdf/font/true_type/table/test_hhea.rb +26 -0
  157. data/test/hexapdf/font/true_type/table/test_hmtx.rb +30 -0
  158. data/test/hexapdf/font/true_type/table/test_kern.rb +61 -0
  159. data/test/hexapdf/font/true_type/table/test_loca.rb +33 -0
  160. data/test/hexapdf/font/true_type/table/test_maxp.rb +50 -0
  161. data/test/hexapdf/font/true_type/table/test_name.rb +76 -0
  162. data/test/hexapdf/font/true_type/table/test_os2.rb +55 -0
  163. data/test/hexapdf/font/true_type/table/test_post.rb +78 -0
  164. data/test/hexapdf/font/true_type/test_builder.rb +42 -0
  165. data/test/hexapdf/font/true_type/test_font.rb +116 -0
  166. data/test/hexapdf/font/true_type/test_optimizer.rb +26 -0
  167. data/test/hexapdf/font/true_type/test_subsetter.rb +73 -0
  168. data/test/hexapdf/font/true_type/test_table.rb +48 -0
  169. data/test/hexapdf/font/type1/common.rb +6 -0
  170. data/test/hexapdf/font/type1/test_afm_parser.rb +65 -0
  171. data/test/hexapdf/font/type1/test_font.rb +104 -0
  172. data/test/hexapdf/font/type1/test_font_metrics.rb +22 -0
  173. data/test/hexapdf/font/type1/test_pfb_parser.rb +37 -0
  174. data/test/hexapdf/font_loader/test_from_configuration.rb +43 -0
  175. data/test/hexapdf/font_loader/test_from_file.rb +36 -0
  176. data/test/hexapdf/font_loader/test_standard14.rb +33 -0
  177. data/test/hexapdf/image_loader/test_jpeg.rb +93 -0
  178. data/test/hexapdf/image_loader/test_pdf.rb +47 -0
  179. data/test/hexapdf/image_loader/test_png.rb +259 -0
  180. data/test/hexapdf/layout/test_box.rb +154 -0
  181. data/test/hexapdf/layout/test_frame.rb +350 -0
  182. data/test/hexapdf/layout/test_image_box.rb +73 -0
  183. data/test/hexapdf/layout/test_inline_box.rb +71 -0
  184. data/test/hexapdf/layout/test_line.rb +206 -0
  185. data/test/hexapdf/layout/test_style.rb +790 -0
  186. data/test/hexapdf/layout/test_text_box.rb +140 -0
  187. data/test/hexapdf/layout/test_text_fragment.rb +375 -0
  188. data/test/hexapdf/layout/test_text_layouter.rb +758 -0
  189. data/test/hexapdf/layout/test_text_shaper.rb +62 -0
  190. data/test/hexapdf/layout/test_width_from_polygon.rb +109 -0
  191. data/test/hexapdf/task/test_dereference.rb +51 -0
  192. data/test/hexapdf/task/test_optimize.rb +162 -0
  193. data/test/hexapdf/test_composer.rb +258 -0
  194. data/test/hexapdf/test_configuration.rb +93 -0
  195. data/test/hexapdf/test_data_dir.rb +32 -0
  196. data/test/hexapdf/test_dictionary.rb +340 -0
  197. data/test/hexapdf/test_dictionary_fields.rb +269 -0
  198. data/test/hexapdf/test_document.rb +641 -0
  199. data/test/hexapdf/test_filter.rb +100 -0
  200. data/test/hexapdf/test_importer.rb +106 -0
  201. data/test/hexapdf/test_object.rb +258 -0
  202. data/test/hexapdf/test_parser.rb +645 -0
  203. data/test/hexapdf/test_pdf_array.rb +169 -0
  204. data/test/hexapdf/test_rectangle.rb +73 -0
  205. data/test/hexapdf/test_reference.rb +50 -0
  206. data/test/hexapdf/test_revision.rb +188 -0
  207. data/test/hexapdf/test_revisions.rb +196 -0
  208. data/test/hexapdf/test_serializer.rb +195 -0
  209. data/test/hexapdf/test_stream.rb +274 -0
  210. data/test/hexapdf/test_tokenizer.rb +80 -0
  211. data/test/hexapdf/test_type.rb +18 -0
  212. data/test/hexapdf/test_writer.rb +140 -0
  213. data/test/hexapdf/test_xref_section.rb +61 -0
  214. data/test/hexapdf/type/acro_form/test_appearance_generator.rb +795 -0
  215. data/test/hexapdf/type/acro_form/test_button_field.rb +308 -0
  216. data/test/hexapdf/type/acro_form/test_choice_field.rb +220 -0
  217. data/test/hexapdf/type/acro_form/test_field.rb +259 -0
  218. data/test/hexapdf/type/acro_form/test_form.rb +357 -0
  219. data/test/hexapdf/type/acro_form/test_signature_field.rb +38 -0
  220. data/test/hexapdf/type/acro_form/test_text_field.rb +201 -0
  221. data/test/hexapdf/type/acro_form/test_variable_text_field.rb +88 -0
  222. data/test/hexapdf/type/actions/test_launch.rb +24 -0
  223. data/test/hexapdf/type/actions/test_uri.rb +23 -0
  224. data/test/hexapdf/type/annotations/test_markup_annotation.rb +22 -0
  225. data/test/hexapdf/type/annotations/test_text.rb +34 -0
  226. data/test/hexapdf/type/annotations/test_widget.rb +225 -0
  227. data/test/hexapdf/type/test_annotation.rb +97 -0
  228. data/test/hexapdf/type/test_catalog.rb +48 -0
  229. data/test/hexapdf/type/test_cid_font.rb +61 -0
  230. data/test/hexapdf/type/test_file_specification.rb +141 -0
  231. data/test/hexapdf/type/test_font.rb +67 -0
  232. data/test/hexapdf/type/test_font_descriptor.rb +61 -0
  233. data/test/hexapdf/type/test_font_simple.rb +176 -0
  234. data/test/hexapdf/type/test_font_true_type.rb +31 -0
  235. data/test/hexapdf/type/test_font_type0.rb +120 -0
  236. data/test/hexapdf/type/test_font_type1.rb +142 -0
  237. data/test/hexapdf/type/test_font_type3.rb +26 -0
  238. data/test/hexapdf/type/test_form.rb +120 -0
  239. data/test/hexapdf/type/test_image.rb +261 -0
  240. data/test/hexapdf/type/test_info.rb +9 -0
  241. data/test/hexapdf/type/test_object_stream.rb +117 -0
  242. data/test/hexapdf/type/test_page.rb +598 -0
  243. data/test/hexapdf/type/test_page_tree_node.rb +315 -0
  244. data/test/hexapdf/type/test_resources.rb +209 -0
  245. data/test/hexapdf/type/test_trailer.rb +116 -0
  246. data/test/hexapdf/type/test_xref_stream.rb +143 -0
  247. data/test/hexapdf/utils/test_bit_field.rb +63 -0
  248. data/test/hexapdf/utils/test_bit_stream.rb +69 -0
  249. data/test/hexapdf/utils/test_graphics_helpers.rb +37 -0
  250. data/test/hexapdf/utils/test_lru_cache.rb +22 -0
  251. data/test/hexapdf/utils/test_object_hash.rb +120 -0
  252. data/test/hexapdf/utils/test_pdf_doc_encoding.rb +18 -0
  253. data/test/hexapdf/utils/test_sorted_tree_node.rb +239 -0
  254. data/test/test_helper.rb +58 -0
  255. metadata +263 -3
@@ -0,0 +1,57 @@
1
+ # # Text Layouter - Line Wrapping
2
+ #
3
+ # The [HexaPDF::Layout::TextLayouter] 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_layout_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
+ frag = HexaPDF::Layout::TextFragment.create(text, font: doc.fonts.add("Times"))
48
+ layouter = HexaPDF::Layout::TextLayouter.new
49
+ [30, 60, 100, 160].each do |width|
50
+ result = layouter.fit([frag], width, 400)
51
+ result.draw(canvas, x, y)
52
+ canvas.stroke_color(255, 0, 0).line_width(0.2)
53
+ canvas.rectangle(x, y, width, -result.height).stroke
54
+ y -= result.height + 5
55
+ end
56
+
57
+ doc.write("text_layouter_line_wrapping.pdf", optimize: true)
@@ -0,0 +1,122 @@
1
+ # # Text Layouter - Styling
2
+ #
3
+ # The text used as part of a [HexaPDF::Layout::TextLayouter] class can be styled
4
+ # using [HexaPDF::Layout::Style]. To do this [HexaPDF::Layout::TextFragment]
5
+ # objects have to be created with the needed styling and then added to a text
6
+ # layout object. In addition the style objects can be used for customizing the
7
+ # text layouts themselves.
8
+ #
9
+ # This example shows how to do this and shows off the various styling option,
10
+ # including using callbacks to further customize the appearance.
11
+ #
12
+ # Usage:
13
+ # : `ruby text_layouter_styling.rb [FONT_FILE]`
14
+ #
15
+
16
+ require 'hexapdf'
17
+
18
+ include HexaPDF::Layout
19
+
20
+ sample_text = "Lorem ipsum dolor sit amet, consectetur adipiscing elit,
21
+ sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut
22
+ enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut
23
+ aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit
24
+ in voluptate velit esse cillum dolore eu fugiat nulla pariatur.".tr("\n", ' ')
25
+
26
+ # Wraps the text in a TextFragment using the given style.
27
+ def fragment(text, style)
28
+ TextFragment.create(text, style)
29
+ end
30
+
31
+ # Draws the text at the given [x, y] position onto the canvas and returns the
32
+ # new y position.
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
37
+ end
38
+
39
+ doc = HexaPDF::Document.new
40
+ canvas = doc.pages.add.canvas
41
+
42
+ base_font = doc.fonts.add(ARGV[0] || "Times")
43
+ base_style = {font: base_font, font_size: 12, text_indent: 20}
44
+ styles = {
45
+ "Fonts | Font Sizes | Colors" => [
46
+ {font: doc.fonts.add("Times", variant: :italic),
47
+ font_size: 12, fill_color: [0, 0, 255]},
48
+ {font: doc.fonts.add("Courier"), font_size: 14,
49
+ fill_color: [0, 255, 0]},
50
+ {font: doc.fonts.add("Helvetica", variant: :bold),
51
+ font_size: 20, fill_alpha: 0.5},
52
+ ],
53
+ "Character Spacing | Word Spacing | Horizontal Scaling" => [
54
+ {**base_style, character_spacing: 3},
55
+ {**base_style, horizontal_scaling: 150},
56
+ {**base_style, word_spacing: 15},
57
+ ],
58
+ "Text Rise" => [
59
+ {**base_style, text_rise: 5},
60
+ {**base_style, text_rise: -3},
61
+ ],
62
+ "Subscript | Superscript" => [
63
+ {**base_style, font_size: 15, subscript: true},
64
+ {**base_style, font_size: 15, superscript: true},
65
+ ],
66
+ "Underline | Strikeout" => [
67
+ {**base_style, underline: true, strikeout: true},
68
+ {**base_style, underline: true, strikeout: true, text_rise: 5},
69
+ {**base_style, underline: true, strikeout: true, subscript: true},
70
+ ],
71
+ "Text Rendering Mode" => [
72
+ {**base_style, text_rendering_mode: :stroke,
73
+ stroke_width: 0.1},
74
+ {**base_style, font_size: 20, text_rendering_mode: :fill_stroke,
75
+ stroke_color: [0, 255, 0], stroke_width: 0.7,
76
+ stroke_dash_pattern: [0.5, 1, 1.5], stroke_cap_style: :round},
77
+ ],
78
+ "Underlays | Overlays" => [
79
+ {**base_style, underlays: [lambda do |canv, box|
80
+ canv.fill_color(240, 240, 0).opacity(fill_alpha: 0.5).
81
+ rectangle(0, 0, box.width, box.height).fill
82
+ end]},
83
+ {**base_style, overlays: [lambda do |canv, box|
84
+ canv.line_width(1).stroke_color([0, 255, 0]).
85
+ line(0, -box.y_min, box.width, box.y_max - box.y_min).stroke
86
+ end]},
87
+ ],
88
+ "Links" => [
89
+ {**base_style, overlays: [
90
+ [:link, dest: [canvas.context, :FitR, 100, 300, 200, 400]],
91
+ ]},
92
+ {**base_style, overlays: [
93
+ [:link, uri: "https://hexapdf.gettalong.org",
94
+ border: [0, 0, 2, [3, 3]], border_color: [89, 150, 220]],
95
+ ]},
96
+ {**base_style, overlays: [
97
+ [:link, file: "text_layouter_styling.pdf", border: true],
98
+ ]},
99
+ ],
100
+ }
101
+
102
+ y = 800
103
+ left = 50
104
+ width = 500
105
+ layouter = TextLayouter.new(base_style)
106
+ styles.each do |desc, variations|
107
+ items = sample_text.split(/(Lorem ipsum dolor|\b\w{2,5}\b)/).map do |str|
108
+ if str.length >= 3 && str.length <= 5
109
+ fragment(str, variations[str.length % variations.length])
110
+ elsif str.length == 2
111
+ fragment(str, variations.first)
112
+ elsif str =~ /Lorem/
113
+ fragment(str, variations.last)
114
+ else
115
+ fragment(str, base_style)
116
+ end
117
+ end
118
+ items.unshift(fragment(desc + ": ", fill_color: [255, 0, 0], **base_style))
119
+ y = draw_text(layouter.fit(items, width, 400), canvas, left, y) - 20
120
+ end
121
+
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)
@@ -0,0 +1,76 @@
1
+ # # Boxes
2
+ #
3
+ # The [HexaPDF::Layout::Box] class is used as the basis for all document layout
4
+ # features.
5
+ #
6
+ # This example shows the basic properties that are available for all boxes, like
7
+ # paddings, borders and and background color. It is also possible to use the
8
+ # underlay and overlay callbacks with boxes.
9
+ #
10
+ # Usage:
11
+ # : `ruby boxes.rb`
12
+ #
13
+
14
+ require 'hexapdf'
15
+
16
+ doc = HexaPDF::Document.new
17
+
18
+ annotate_box = lambda do |canvas, box|
19
+ text = ""
20
+ canvas.font("Times", size: 6).leading(7)
21
+
22
+ if (data = box.style.padding)
23
+ text << "Padding (TRBL): #{data.top}, #{data.right}, #{data.bottom}, #{data.left}\n"
24
+ end
25
+ unless box.style.border.none?
26
+ data = box.style.border.width
27
+ text << "Border Width (TRBL): #{data.top}, #{data.right}, #{data.bottom}, #{data.left}\n"
28
+ data = box.style.border.color
29
+ text << "Border Color (TRBL):\n* #{data.top}\n* #{data.right}\n* #{data.bottom}\n* #{data.left}\n"
30
+ data = box.style.border.style
31
+ text << "Border Style (TRBL):\n* #{data.top}\n* #{data.right}\n* #{data.bottom}\n* #{data.left}\n"
32
+ end
33
+
34
+ canvas.line_width(0.1).rectangle(0, 0, box.content_width, box.content_height).stroke
35
+ canvas.text(text, at: [0, box.content_height - 10])
36
+ end
37
+
38
+ canvas = doc.pages.add.canvas
39
+
40
+ [[1, 140], [5, 190], [15, 240]].each_with_index do |(width, red), row|
41
+ [[:solid, 140], [:dashed, 177], [:dashed_round, 207],
42
+ [:dotted, 240]].each_with_index do |(style, green), column|
43
+ box = HexaPDF::Layout::Box.create(
44
+ width: 100, height: 100, content_box: true,
45
+ border: {width: width, style: style},
46
+ background_color: [red, green, 0],
47
+ &annotate_box)
48
+ box.draw(canvas, 20 + 140 * column, 700 - 150 * row)
49
+ end
50
+ end
51
+
52
+ # The whole kitchen sink
53
+ box = HexaPDF::Layout::Box.create(
54
+ width: 470, height: 200, content_box: true,
55
+ padding: [20, 5, 10, 15],
56
+ border: {width: [20, 40, 30, 15],
57
+ color: [[46, 185, 206], [206, 199, 46], [188, 46, 206], [59, 206, 46]],
58
+ style: [:solid, :dashed, :dashed_round, :dotted]},
59
+ background_color: [255, 255, 180],
60
+ underlays: [
61
+ lambda do |canv, _|
62
+ canv.stroke_color([255, 0, 0]).line_width(10).line_cap_style(:butt).
63
+ line(0, 0, box.width, box.height).line(0, box.height, box.width, 0).
64
+ stroke
65
+ end
66
+ ],
67
+ overlays: [
68
+ lambda do |canv, _|
69
+ canv.stroke_color([0, 0, 255]).line_width(5).
70
+ rectangle(10, 10, box.width - 20, box.height - 20).stroke
71
+ end
72
+ ],
73
+ &annotate_box)
74
+ box.draw(canvas, 20, 100)
75
+
76
+ doc.write("boxes.pdf", optimize: true)
@@ -0,0 +1,90 @@
1
+ # # Frame - Automatic Box Placement
2
+ #
3
+ # The [HexaPDF::Layout::Frame] class is used for placing rectangular boxes.
4
+ #
5
+ # This example shows how to create a frame and how different box styles can be
6
+ # used to specify where a box should be placed. After each box is drawn, the
7
+ # frame's shape is drawn and then a new page is started. This is done to easily
8
+ # compare the changes after each added box.
9
+ #
10
+ # Note how the absolutely positioned box cuts a hole into the frame's shape and
11
+ # how that influences the positioning.
12
+ #
13
+ # Usage:
14
+ # : `ruby frame_automatic_box_placement.rb`
15
+ #
16
+
17
+ require 'hexapdf'
18
+
19
+ include HexaPDF::Layout
20
+
21
+ doc = HexaPDF::Document.new
22
+ page = doc.pages.add
23
+ media_box = page.box(:media)
24
+ canvas = page.canvas
25
+
26
+ frame = Frame.new(media_box.left + 20, media_box.bottom + 20,
27
+ media_box.width - 40, media_box.height - 40)
28
+
29
+ box_counter = 1
30
+ draw_box = lambda do |**args|
31
+ b = Box.create(**args, border: {width: 1, color: [[255, 0, 0]]}) do |canv, box|
32
+ canv.save_graphics_state do
33
+ canv.stroke_color(255, 0, 0)
34
+ canv.line(0, 0, box.content_width, box.content_height).
35
+ line(0, box.content_height, box.content_width, 0).
36
+ stroke
37
+ end
38
+ text = box_counter.to_s << "\n" + args.map {|k, v| "#{k}: #{v}"}.join("\n")
39
+ canv.font("Times", size: 15).leading(15).
40
+ text(text, at: [10, box.content_height - 20])
41
+ box_counter += 1
42
+ end
43
+
44
+ drawn = false
45
+ until drawn
46
+ drawn = frame.draw(canvas, b)
47
+ frame.find_next_region unless drawn
48
+ end
49
+
50
+ canvas.line_width(3).draw(:geom2d, object: frame.shape)
51
+ canvas = doc.pages.add.canvas
52
+ end
53
+
54
+ # Absolutely positioned box with margin
55
+ draw_box.call(width: 100, height: 100, position: :absolute, margin: 10,
56
+ position_hint: [250, 250])
57
+
58
+ # Fixed sized box with automatic width
59
+ draw_box.call(height: 100)
60
+
61
+ # Fixed sized box
62
+ draw_box.call(width: 100, height: 100)
63
+
64
+ # Fixed sized box, placed below the other because the space to the right can't
65
+ # be used
66
+ draw_box.call(width: 100, height: 100)
67
+
68
+ # Fixed sized floating box, space to the right can be used
69
+ draw_box.call(width: 100, height: 100, position: :float, position_hint: :left)
70
+
71
+ # Fixed sized floating box again, floating to the right
72
+ draw_box.call(width: 100, height: 100, position: :float, position_hint: :right)
73
+
74
+ # Fixed sized floating box again, floating to the left with margin
75
+ draw_box.call(width: 100, height: 100, position: :float, position_hint: :left,
76
+ margin: [0, 10])
77
+
78
+ # Fixed sized box, no floating
79
+ draw_box.call(width: 100, height: 100)
80
+
81
+ # Fixed sized box, center aligned in the available space
82
+ draw_box.call(width: 100, height: 100, position_hint: :center)
83
+
84
+ # Fixed sized box, right aligned in the available space
85
+ draw_box.call(width: 100, height: 100, position_hint: :right)
86
+
87
+ # Fixed sized box, consuming the whole remaining available space
88
+ draw_box.call
89
+
90
+ doc.write("frame_automatic_box_placement.pdf", optimize: true)
@@ -0,0 +1,60 @@
1
+ # # Frame - Text Flow
2
+ #
3
+ # This example shows how [HexaPDF::Layout::Frame] and [HexaPDF::Layout::TextBox]
4
+ # can be used to flow text around objects.
5
+ #
6
+ # Three boxes are placed repeatedly onto the frame until it is filled: two
7
+ # floating boxes (one left, one right) and a text box. The text box is styled to
8
+ # flow its content around the other two boxes.
9
+ #
10
+ # Usage:
11
+ # : `ruby frame_text_flow.rb`
12
+ #
13
+
14
+ require 'hexapdf'
15
+ require 'hexapdf/utils/graphics_helpers'
16
+
17
+ include HexaPDF::Layout
18
+ include HexaPDF::Utils::GraphicsHelpers
19
+
20
+ doc = HexaPDF::Document.new
21
+
22
+ sample_text = "Lorem ipsum dolor sit amet, con\u{00AD}sectetur
23
+ adipis\u{00AD}cing elit, sed do eiusmod tempor incididunt ut labore et
24
+ dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation
25
+ ullamco laboris nisi ut aliquip ex ea commodo consequat.
26
+ ".tr("\n", ' ') * 10
27
+ items = [TextFragment.create(sample_text, font: doc.fonts.add("Times"))]
28
+
29
+ page = doc.pages.add
30
+ media_box = page.box(:media)
31
+ canvas = page.canvas
32
+ frame = Frame.new(media_box.left + 20, media_box.bottom + 20,
33
+ media_box.width - 40, media_box.height - 40)
34
+
35
+ image = doc.images.add(File.join(__dir__, 'machupicchu.jpg'))
36
+ iw, ih = calculate_dimensions(image.width, image.height, rwidth: 100)
37
+
38
+ boxes = []
39
+ boxes << Box.create(width: iw, height: ih,
40
+ margin: [10, 30], position: :float) do |canv, box|
41
+ canv.image(image, at: [0, 0], width: 100)
42
+ end
43
+ boxes << Box.create(width: 50, height: 50, margin: 20,
44
+ position: :float, position_hint: :right,
45
+ border: {width: 1, color: [[255, 0, 0]]})
46
+ boxes << TextBox.new(items, style: {position: :flow, align: :justify})
47
+
48
+ i = 0
49
+ frame_filled = false
50
+ until frame_filled
51
+ box = boxes[i]
52
+ drawn = false
53
+ until drawn || frame_filled
54
+ drawn = frame.draw(canvas, box)
55
+ frame_filled = !frame.find_next_region unless drawn
56
+ end
57
+ i = (i + 1) % boxes.length
58
+ end
59
+
60
+ doc.write("frame_text_flow.pdf", optimize: true)
@@ -0,0 +1,44 @@
1
+ # # Composer
2
+ #
3
+ # This example shows how [HexaPDF::Composer] simplifies the creation of PDF
4
+ # documents by providing a high-level interface to the box layouting engine.
5
+ #
6
+ # Basic style properties can be set on the [HexaPDF::Composer#base_style] style.
7
+ # These properties are reused by every box and can be adjusted on a box-by-box
8
+ # basis.
9
+ #
10
+ # Various methods allow the easy creation of boxes, for example, text and image
11
+ # boxes. All these boxes are automatically drawn on the page. If the page has
12
+ # not enough room left for a box, the box is split across pages (which are
13
+ # automatically created) if possible or just drawn on the new page.
14
+ #
15
+ # Usage:
16
+ # : `ruby composer.rb`
17
+ #
18
+
19
+ require 'hexapdf'
20
+
21
+ lorem_ipsum = "Lorem ipsum dolor sit amet, con\u{00AD}sectetur
22
+ adipis\u{00AD}cing elit, sed do eiusmod tempor incidi\u{00AD}dunt ut labore et
23
+ dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exer\u{00AD}citation
24
+ ullamco laboris nisi ut aliquip ex ea commodo consequat. ".tr("\n", " ")
25
+
26
+ HexaPDF::Composer.create('composer.pdf') do |pdf|
27
+ pdf.base_style.update(line_spacing: {type: :proportional, value: 1.5},
28
+ last_line_gap: true, align: :justify)
29
+ image_style = pdf.base_style.dup.update(border: {width: 1}, padding: 5, margin: 10)
30
+ link_style = pdf.base_style.dup.update(fill_color: [6, 158, 224], underline: true)
31
+ image = File.join(__dir__, 'machupicchu.jpg')
32
+
33
+ pdf.text(lorem_ipsum * 2)
34
+ pdf.image(image, style: image_style, width: 200, position: :float)
35
+ pdf.image(image, style: image_style, width: 200, position: :absolute,
36
+ position_hint: [200, 300])
37
+ pdf.text(lorem_ipsum * 20, position: :flow)
38
+
39
+ pdf.formatted_text(["Produced by ",
40
+ {link: "https://hexapdf.gettalong.org", text: "HexaPDF",
41
+ style: link_style},
42
+ " via HexaPDF::Composer"],
43
+ font_size: 15, align: :center, padding: 15)
44
+ end