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