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