hexapdf 0.7.0 → 0.8.0
Sign up to get free protection for your applications and to get access to all the features.
- 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)
|