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.
- checksums.yaml +4 -4
- data/CHANGELOG.md +1024 -0
- data/LICENSE +29 -0
- data/README.md +129 -0
- data/Rakefile +109 -0
- data/agpl-3.0.txt +661 -0
- data/examples/001-hello_world.rb +16 -0
- data/examples/002-graphics.rb +275 -0
- data/examples/003-arcs.rb +50 -0
- data/examples/004-optimizing.rb +23 -0
- data/examples/005-merging.rb +27 -0
- data/examples/006-standard_pdf_fonts.rb +73 -0
- data/examples/007-truetype.rb +42 -0
- data/examples/008-show_char_bboxes.rb +55 -0
- data/examples/009-text_layouter_alignment.rb +47 -0
- data/examples/010-text_layouter_inline_boxes.rb +64 -0
- data/examples/011-text_layouter_line_wrapping.rb +57 -0
- data/examples/012-text_layouter_styling.rb +122 -0
- data/examples/013-text_layouter_shapes.rb +176 -0
- data/examples/014-text_in_polygon.rb +60 -0
- data/examples/015-boxes.rb +76 -0
- data/examples/016-frame_automatic_box_placement.rb +90 -0
- data/examples/017-frame_text_flow.rb +60 -0
- data/examples/018-composer.rb +44 -0
- data/examples/019-acro_form.rb +88 -0
- data/examples/emoji-smile.png +0 -0
- data/examples/emoji-wink.png +0 -0
- data/examples/machupicchu.jpg +0 -0
- data/lib/hexapdf/content/graphic_object/endpoint_arc.rb +66 -0
- data/lib/hexapdf/content/graphic_object/geom2d.rb +13 -0
- data/lib/hexapdf/version.rb +1 -1
- data/test/data/aes-test-vectors/CBCGFSbox-128-decrypt.data.gz +0 -0
- data/test/data/aes-test-vectors/CBCGFSbox-128-encrypt.data.gz +0 -0
- data/test/data/aes-test-vectors/CBCGFSbox-192-decrypt.data.gz +0 -0
- data/test/data/aes-test-vectors/CBCGFSbox-192-encrypt.data.gz +0 -0
- data/test/data/aes-test-vectors/CBCGFSbox-256-decrypt.data.gz +0 -0
- data/test/data/aes-test-vectors/CBCGFSbox-256-encrypt.data.gz +0 -0
- data/test/data/aes-test-vectors/CBCKeySbox-128-decrypt.data.gz +0 -0
- data/test/data/aes-test-vectors/CBCKeySbox-128-encrypt.data.gz +0 -0
- data/test/data/aes-test-vectors/CBCKeySbox-192-decrypt.data.gz +0 -0
- data/test/data/aes-test-vectors/CBCKeySbox-192-encrypt.data.gz +0 -0
- data/test/data/aes-test-vectors/CBCKeySbox-256-decrypt.data.gz +0 -0
- data/test/data/aes-test-vectors/CBCKeySbox-256-encrypt.data.gz +0 -0
- data/test/data/aes-test-vectors/CBCVarKey-128-decrypt.data.gz +0 -0
- data/test/data/aes-test-vectors/CBCVarKey-128-encrypt.data.gz +0 -0
- data/test/data/aes-test-vectors/CBCVarKey-192-decrypt.data.gz +0 -0
- data/test/data/aes-test-vectors/CBCVarKey-192-encrypt.data.gz +0 -0
- data/test/data/aes-test-vectors/CBCVarKey-256-decrypt.data.gz +0 -0
- data/test/data/aes-test-vectors/CBCVarKey-256-encrypt.data.gz +0 -0
- data/test/data/aes-test-vectors/CBCVarTxt-128-decrypt.data.gz +0 -0
- data/test/data/aes-test-vectors/CBCVarTxt-128-encrypt.data.gz +0 -0
- data/test/data/aes-test-vectors/CBCVarTxt-192-decrypt.data.gz +0 -0
- data/test/data/aes-test-vectors/CBCVarTxt-192-encrypt.data.gz +0 -0
- data/test/data/aes-test-vectors/CBCVarTxt-256-decrypt.data.gz +0 -0
- data/test/data/aes-test-vectors/CBCVarTxt-256-encrypt.data.gz +0 -0
- data/test/data/fonts/Ubuntu-Title.ttf +0 -0
- data/test/data/images/cmyk.jpg +0 -0
- data/test/data/images/fillbytes.jpg +0 -0
- data/test/data/images/gray.jpg +0 -0
- data/test/data/images/greyscale-1bit.png +0 -0
- data/test/data/images/greyscale-2bit.png +0 -0
- data/test/data/images/greyscale-4bit.png +0 -0
- data/test/data/images/greyscale-8bit.png +0 -0
- data/test/data/images/greyscale-alpha-8bit.png +0 -0
- data/test/data/images/greyscale-trns-8bit.png +0 -0
- data/test/data/images/greyscale-with-gamma1.0.png +0 -0
- data/test/data/images/greyscale-with-gamma1.5.png +0 -0
- data/test/data/images/indexed-1bit.png +0 -0
- data/test/data/images/indexed-2bit.png +0 -0
- data/test/data/images/indexed-4bit.png +0 -0
- data/test/data/images/indexed-8bit.png +0 -0
- data/test/data/images/indexed-alpha-4bit.png +0 -0
- data/test/data/images/indexed-alpha-8bit.png +0 -0
- data/test/data/images/rgb.jpg +0 -0
- data/test/data/images/truecolour-8bit.png +0 -0
- data/test/data/images/truecolour-alpha-8bit.png +0 -0
- data/test/data/images/truecolour-gama-chrm-8bit.png +0 -0
- data/test/data/images/truecolour-srgb-8bit.png +0 -0
- data/test/data/images/ycck.jpg +0 -0
- data/test/data/minimal.pdf +44 -0
- data/test/data/standard-security-handler/README +9 -0
- data/test/data/standard-security-handler/bothpwd-aes-128bit-V4.pdf +44 -0
- data/test/data/standard-security-handler/bothpwd-aes-256bit-V5.pdf +0 -0
- data/test/data/standard-security-handler/bothpwd-arc4-128bit-V2.pdf +43 -0
- data/test/data/standard-security-handler/bothpwd-arc4-128bit-V4.pdf +43 -0
- data/test/data/standard-security-handler/bothpwd-arc4-40bit-V1.pdf +0 -0
- data/test/data/standard-security-handler/nopwd-aes-128bit-V4.pdf +43 -0
- data/test/data/standard-security-handler/nopwd-aes-256bit-V5.pdf +0 -0
- data/test/data/standard-security-handler/nopwd-arc4-128bit-V2.pdf +43 -0
- data/test/data/standard-security-handler/nopwd-arc4-128bit-V4.pdf +43 -0
- data/test/data/standard-security-handler/nopwd-arc4-40bit-V1.pdf +43 -0
- data/test/data/standard-security-handler/ownerpwd-aes-128bit-V4.pdf +0 -0
- data/test/data/standard-security-handler/ownerpwd-aes-256bit-V5.pdf +43 -0
- data/test/data/standard-security-handler/ownerpwd-arc4-128bit-V2.pdf +43 -0
- data/test/data/standard-security-handler/ownerpwd-arc4-128bit-V4.pdf +43 -0
- data/test/data/standard-security-handler/ownerpwd-arc4-40bit-V1.pdf +43 -0
- data/test/data/standard-security-handler/userpwd-aes-128bit-V4.pdf +43 -0
- data/test/data/standard-security-handler/userpwd-aes-256bit-V5.pdf +43 -0
- data/test/data/standard-security-handler/userpwd-arc4-128bit-V2.pdf +0 -0
- data/test/data/standard-security-handler/userpwd-arc4-128bit-V4.pdf +0 -0
- data/test/data/standard-security-handler/userpwd-arc4-40bit-V1.pdf +43 -0
- data/test/hexapdf/common_tokenizer_tests.rb +236 -0
- data/test/hexapdf/content/common.rb +39 -0
- data/test/hexapdf/content/graphic_object/test_arc.rb +102 -0
- data/test/hexapdf/content/graphic_object/test_endpoint_arc.rb +90 -0
- data/test/hexapdf/content/graphic_object/test_geom2d.rb +79 -0
- data/test/hexapdf/content/graphic_object/test_solid_arc.rb +86 -0
- data/test/hexapdf/content/test_canvas.rb +1279 -0
- data/test/hexapdf/content/test_color_space.rb +176 -0
- data/test/hexapdf/content/test_graphics_state.rb +151 -0
- data/test/hexapdf/content/test_operator.rb +619 -0
- data/test/hexapdf/content/test_parser.rb +99 -0
- data/test/hexapdf/content/test_processor.rb +163 -0
- data/test/hexapdf/content/test_transformation_matrix.rb +64 -0
- data/test/hexapdf/document/test_files.rb +72 -0
- data/test/hexapdf/document/test_fonts.rb +60 -0
- data/test/hexapdf/document/test_images.rb +72 -0
- data/test/hexapdf/document/test_pages.rb +130 -0
- data/test/hexapdf/encryption/common.rb +87 -0
- data/test/hexapdf/encryption/test_aes.rb +129 -0
- data/test/hexapdf/encryption/test_arc4.rb +39 -0
- data/test/hexapdf/encryption/test_fast_aes.rb +17 -0
- data/test/hexapdf/encryption/test_fast_arc4.rb +12 -0
- data/test/hexapdf/encryption/test_identity.rb +21 -0
- data/test/hexapdf/encryption/test_ruby_aes.rb +23 -0
- data/test/hexapdf/encryption/test_ruby_arc4.rb +20 -0
- data/test/hexapdf/encryption/test_security_handler.rb +380 -0
- data/test/hexapdf/encryption/test_standard_security_handler.rb +322 -0
- data/test/hexapdf/filter/common.rb +53 -0
- data/test/hexapdf/filter/test_ascii85_decode.rb +59 -0
- data/test/hexapdf/filter/test_ascii_hex_decode.rb +38 -0
- data/test/hexapdf/filter/test_crypt.rb +21 -0
- data/test/hexapdf/filter/test_encryption.rb +24 -0
- data/test/hexapdf/filter/test_flate_decode.rb +44 -0
- data/test/hexapdf/filter/test_lzw_decode.rb +52 -0
- data/test/hexapdf/filter/test_predictor.rb +219 -0
- data/test/hexapdf/filter/test_run_length_decode.rb +32 -0
- data/test/hexapdf/font/cmap/test_parser.rb +102 -0
- data/test/hexapdf/font/cmap/test_writer.rb +66 -0
- data/test/hexapdf/font/encoding/test_base.rb +45 -0
- data/test/hexapdf/font/encoding/test_difference_encoding.rb +29 -0
- data/test/hexapdf/font/encoding/test_glyph_list.rb +59 -0
- data/test/hexapdf/font/encoding/test_zapf_dingbats_encoding.rb +16 -0
- data/test/hexapdf/font/test_cmap.rb +104 -0
- data/test/hexapdf/font/test_encoding.rb +27 -0
- data/test/hexapdf/font/test_invalid_glyph.rb +34 -0
- data/test/hexapdf/font/test_true_type_wrapper.rb +186 -0
- data/test/hexapdf/font/test_type1_wrapper.rb +107 -0
- data/test/hexapdf/font/true_type/common.rb +17 -0
- data/test/hexapdf/font/true_type/table/common.rb +27 -0
- data/test/hexapdf/font/true_type/table/test_cmap.rb +47 -0
- data/test/hexapdf/font/true_type/table/test_cmap_subtable.rb +141 -0
- data/test/hexapdf/font/true_type/table/test_directory.rb +30 -0
- data/test/hexapdf/font/true_type/table/test_glyf.rb +58 -0
- data/test/hexapdf/font/true_type/table/test_head.rb +56 -0
- data/test/hexapdf/font/true_type/table/test_hhea.rb +26 -0
- data/test/hexapdf/font/true_type/table/test_hmtx.rb +30 -0
- data/test/hexapdf/font/true_type/table/test_kern.rb +61 -0
- data/test/hexapdf/font/true_type/table/test_loca.rb +33 -0
- data/test/hexapdf/font/true_type/table/test_maxp.rb +50 -0
- data/test/hexapdf/font/true_type/table/test_name.rb +76 -0
- data/test/hexapdf/font/true_type/table/test_os2.rb +55 -0
- data/test/hexapdf/font/true_type/table/test_post.rb +78 -0
- data/test/hexapdf/font/true_type/test_builder.rb +42 -0
- data/test/hexapdf/font/true_type/test_font.rb +116 -0
- data/test/hexapdf/font/true_type/test_optimizer.rb +26 -0
- data/test/hexapdf/font/true_type/test_subsetter.rb +73 -0
- data/test/hexapdf/font/true_type/test_table.rb +48 -0
- data/test/hexapdf/font/type1/common.rb +6 -0
- data/test/hexapdf/font/type1/test_afm_parser.rb +65 -0
- data/test/hexapdf/font/type1/test_font.rb +104 -0
- data/test/hexapdf/font/type1/test_font_metrics.rb +22 -0
- data/test/hexapdf/font/type1/test_pfb_parser.rb +37 -0
- data/test/hexapdf/font_loader/test_from_configuration.rb +43 -0
- data/test/hexapdf/font_loader/test_from_file.rb +36 -0
- data/test/hexapdf/font_loader/test_standard14.rb +33 -0
- data/test/hexapdf/image_loader/test_jpeg.rb +93 -0
- data/test/hexapdf/image_loader/test_pdf.rb +47 -0
- data/test/hexapdf/image_loader/test_png.rb +259 -0
- data/test/hexapdf/layout/test_box.rb +154 -0
- data/test/hexapdf/layout/test_frame.rb +350 -0
- data/test/hexapdf/layout/test_image_box.rb +73 -0
- data/test/hexapdf/layout/test_inline_box.rb +71 -0
- data/test/hexapdf/layout/test_line.rb +206 -0
- data/test/hexapdf/layout/test_style.rb +790 -0
- data/test/hexapdf/layout/test_text_box.rb +140 -0
- data/test/hexapdf/layout/test_text_fragment.rb +375 -0
- data/test/hexapdf/layout/test_text_layouter.rb +758 -0
- data/test/hexapdf/layout/test_text_shaper.rb +62 -0
- data/test/hexapdf/layout/test_width_from_polygon.rb +109 -0
- data/test/hexapdf/task/test_dereference.rb +51 -0
- data/test/hexapdf/task/test_optimize.rb +162 -0
- data/test/hexapdf/test_composer.rb +258 -0
- data/test/hexapdf/test_configuration.rb +93 -0
- data/test/hexapdf/test_data_dir.rb +32 -0
- data/test/hexapdf/test_dictionary.rb +340 -0
- data/test/hexapdf/test_dictionary_fields.rb +269 -0
- data/test/hexapdf/test_document.rb +641 -0
- data/test/hexapdf/test_filter.rb +100 -0
- data/test/hexapdf/test_importer.rb +106 -0
- data/test/hexapdf/test_object.rb +258 -0
- data/test/hexapdf/test_parser.rb +645 -0
- data/test/hexapdf/test_pdf_array.rb +169 -0
- data/test/hexapdf/test_rectangle.rb +73 -0
- data/test/hexapdf/test_reference.rb +50 -0
- data/test/hexapdf/test_revision.rb +188 -0
- data/test/hexapdf/test_revisions.rb +196 -0
- data/test/hexapdf/test_serializer.rb +195 -0
- data/test/hexapdf/test_stream.rb +274 -0
- data/test/hexapdf/test_tokenizer.rb +80 -0
- data/test/hexapdf/test_type.rb +18 -0
- data/test/hexapdf/test_writer.rb +140 -0
- data/test/hexapdf/test_xref_section.rb +61 -0
- data/test/hexapdf/type/acro_form/test_appearance_generator.rb +795 -0
- data/test/hexapdf/type/acro_form/test_button_field.rb +308 -0
- data/test/hexapdf/type/acro_form/test_choice_field.rb +220 -0
- data/test/hexapdf/type/acro_form/test_field.rb +259 -0
- data/test/hexapdf/type/acro_form/test_form.rb +357 -0
- data/test/hexapdf/type/acro_form/test_signature_field.rb +38 -0
- data/test/hexapdf/type/acro_form/test_text_field.rb +201 -0
- data/test/hexapdf/type/acro_form/test_variable_text_field.rb +88 -0
- data/test/hexapdf/type/actions/test_launch.rb +24 -0
- data/test/hexapdf/type/actions/test_uri.rb +23 -0
- data/test/hexapdf/type/annotations/test_markup_annotation.rb +22 -0
- data/test/hexapdf/type/annotations/test_text.rb +34 -0
- data/test/hexapdf/type/annotations/test_widget.rb +225 -0
- data/test/hexapdf/type/test_annotation.rb +97 -0
- data/test/hexapdf/type/test_catalog.rb +48 -0
- data/test/hexapdf/type/test_cid_font.rb +61 -0
- data/test/hexapdf/type/test_file_specification.rb +141 -0
- data/test/hexapdf/type/test_font.rb +67 -0
- data/test/hexapdf/type/test_font_descriptor.rb +61 -0
- data/test/hexapdf/type/test_font_simple.rb +176 -0
- data/test/hexapdf/type/test_font_true_type.rb +31 -0
- data/test/hexapdf/type/test_font_type0.rb +120 -0
- data/test/hexapdf/type/test_font_type1.rb +142 -0
- data/test/hexapdf/type/test_font_type3.rb +26 -0
- data/test/hexapdf/type/test_form.rb +120 -0
- data/test/hexapdf/type/test_image.rb +261 -0
- data/test/hexapdf/type/test_info.rb +9 -0
- data/test/hexapdf/type/test_object_stream.rb +117 -0
- data/test/hexapdf/type/test_page.rb +598 -0
- data/test/hexapdf/type/test_page_tree_node.rb +315 -0
- data/test/hexapdf/type/test_resources.rb +209 -0
- data/test/hexapdf/type/test_trailer.rb +116 -0
- data/test/hexapdf/type/test_xref_stream.rb +143 -0
- data/test/hexapdf/utils/test_bit_field.rb +63 -0
- data/test/hexapdf/utils/test_bit_stream.rb +69 -0
- data/test/hexapdf/utils/test_graphics_helpers.rb +37 -0
- data/test/hexapdf/utils/test_lru_cache.rb +22 -0
- data/test/hexapdf/utils/test_object_hash.rb +120 -0
- data/test/hexapdf/utils/test_pdf_doc_encoding.rb +18 -0
- data/test/hexapdf/utils/test_sorted_tree_node.rb +239 -0
- data/test/test_helper.rb +58 -0
- 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
|