hexapdf 0.34.1 → 0.35.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 +59 -0
- data/examples/009-text_layouter_alignment.rb +7 -7
- data/examples/010-text_layouter_inline_boxes.rb +1 -1
- data/examples/011-text_layouter_line_wrapping.rb +2 -4
- data/examples/013-text_layouter_shapes.rb +9 -11
- data/examples/014-text_in_polygon.rb +2 -2
- data/examples/016-frame_automatic_box_placement.rb +6 -7
- data/examples/017-frame_text_flow.rb +2 -2
- data/examples/018-composer.rb +5 -6
- data/examples/020-column_box.rb +2 -2
- data/examples/021-list_box.rb +1 -1
- data/examples/027-composer_optional_content.rb +5 -5
- data/examples/028-frame_mask_mode.rb +23 -0
- data/examples/029-composer_fallback_fonts.rb +22 -0
- data/lib/hexapdf/cli/info.rb +1 -0
- data/lib/hexapdf/cli/inspect.rb +55 -2
- data/lib/hexapdf/composer.rb +2 -2
- data/lib/hexapdf/configuration.rb +61 -1
- data/lib/hexapdf/content/canvas.rb +63 -0
- data/lib/hexapdf/content/canvas_composer.rb +142 -0
- data/lib/hexapdf/content.rb +1 -0
- data/lib/hexapdf/dictionary.rb +14 -3
- data/lib/hexapdf/document/layout.rb +35 -13
- data/lib/hexapdf/encryption/standard_security_handler.rb +15 -0
- data/lib/hexapdf/error.rb +2 -1
- data/lib/hexapdf/font/invalid_glyph.rb +22 -6
- data/lib/hexapdf/font/true_type_wrapper.rb +48 -20
- data/lib/hexapdf/font/type1_wrapper.rb +48 -24
- data/lib/hexapdf/layout/box.rb +11 -8
- data/lib/hexapdf/layout/column_box.rb +5 -3
- data/lib/hexapdf/layout/frame.rb +77 -39
- data/lib/hexapdf/layout/image_box.rb +3 -3
- data/lib/hexapdf/layout/list_box.rb +20 -19
- data/lib/hexapdf/layout/style.rb +173 -68
- data/lib/hexapdf/layout/table_box.rb +3 -3
- data/lib/hexapdf/layout/text_box.rb +5 -5
- data/lib/hexapdf/layout/text_fragment.rb +50 -0
- data/lib/hexapdf/layout/text_layouter.rb +7 -6
- data/lib/hexapdf/object.rb +5 -2
- data/lib/hexapdf/pdf_array.rb +5 -0
- data/lib/hexapdf/type/acro_form/appearance_generator.rb +16 -11
- data/lib/hexapdf/utils/sorted_tree_node.rb +0 -10
- data/lib/hexapdf/version.rb +1 -1
- data/test/hexapdf/content/test_canvas.rb +37 -0
- data/test/hexapdf/content/test_canvas_composer.rb +112 -0
- data/test/hexapdf/document/test_layout.rb +40 -12
- data/test/hexapdf/encryption/test_standard_security_handler.rb +43 -0
- data/test/hexapdf/font/test_invalid_glyph.rb +13 -1
- data/test/hexapdf/font/test_true_type_wrapper.rb +15 -2
- data/test/hexapdf/font/test_type1_wrapper.rb +21 -2
- data/test/hexapdf/layout/test_column_box.rb +14 -0
- data/test/hexapdf/layout/test_frame.rb +181 -95
- data/test/hexapdf/layout/test_list_box.rb +7 -7
- data/test/hexapdf/layout/test_style.rb +14 -10
- data/test/hexapdf/layout/test_table_box.rb +3 -3
- data/test/hexapdf/layout/test_text_box.rb +2 -2
- data/test/hexapdf/layout/test_text_fragment.rb +37 -0
- data/test/hexapdf/layout/test_text_layouter.rb +10 -10
- data/test/hexapdf/test_configuration.rb +49 -0
- data/test/hexapdf/test_dictionary.rb +1 -1
- data/test/hexapdf/test_object.rb +13 -12
- data/test/hexapdf/test_pdf_array.rb +9 -0
- data/test/hexapdf/test_writer.rb +3 -3
- data/test/hexapdf/type/acro_form/test_appearance_generator.rb +41 -13
- data/test/hexapdf/utils/test_sorted_tree_node.rb +1 -1
- metadata +7 -3
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 85a526812bcfbeae9fbe103513c3fac73ac2d736b2ac965305c8c1164fc976b7
|
|
4
|
+
data.tar.gz: f999566105d0ac42c5037bf642256fd6c9a0c515bffb2329ef32456009b46dbd
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 77d31b7c95a8ffd8c1189b062aac8dd7adc75a78ca3f1d8c91033401a1311b446a387c8c1b52932d5573d7721b5a6d4210433ac44d500fa3389dd17daf1be865
|
|
7
|
+
data.tar.gz: 686e6e041a2723a54f1ff5acf0e46c9f7231b4ee5220f4dfe127579549c28c093f28c60ba4470d4be9fba0ccad0202905a57132f1e64596412308ab49b3ab1ed
|
data/CHANGELOG.md
CHANGED
|
@@ -1,3 +1,62 @@
|
|
|
1
|
+
## 0.35.0 - 2024-01-06
|
|
2
|
+
|
|
3
|
+
### Added
|
|
4
|
+
|
|
5
|
+
* Command 'psd' for CLI `hexapdf inspect` to show a decoded content stream
|
|
6
|
+
* Style property 'mask_mode' for more control over the region that gets removed
|
|
7
|
+
from a frame after placing a box
|
|
8
|
+
* Style property 'valign' for vertically centering a box in a frame
|
|
9
|
+
* [HexaPDF::Content::Canvas#form] for creating reusable Form XObjects
|
|
10
|
+
* Method `#valid?` to all Glyph classes
|
|
11
|
+
* [HexaPDF::Font::InvalidGlyph#control_char?] for detecting invalid glyphs that
|
|
12
|
+
represent a control character (like a newline)
|
|
13
|
+
* [HexaPDF::Font::Type1Wrapper#decode_codepoint] and
|
|
14
|
+
[HexaPDF::Font::TrueTypeWrapper#decode_codepoint] for decoding a single
|
|
15
|
+
Unicode codepoint into a glyph
|
|
16
|
+
* [HexaPDF::Layout::TextFragment::create_with_fallback_glyphs] for creating an
|
|
17
|
+
array of text fragments with support for fallback glyphs
|
|
18
|
+
* Configuration option 'font.on_invalid_glyph' for use together with the new
|
|
19
|
+
method for creating text fragments with fallback glyphs
|
|
20
|
+
* Configuration option 'font.fallback' which is used by the default
|
|
21
|
+
implementation of 'font.on_invalid_glyph'
|
|
22
|
+
* [HexaPDF::Document::Layout#text_fragments] for creating text fragments with
|
|
23
|
+
support for fallback glyphs via 'font.on_invalid_glyph'
|
|
24
|
+
* [HexaPDF::Content::CanvasComposer] for using high-level layout functionality
|
|
25
|
+
on a single canvas
|
|
26
|
+
* [HexaPDF::Content::Canvas#composer] for easily creating a canvas composer
|
|
27
|
+
* [HexaPDF::Font::TrueTypeWrapper#bold?] and [HexaPDF::Font::Type1Wrapper#bold?]
|
|
28
|
+
for determining whether a font is bold
|
|
29
|
+
* [HexaPDF::Font::TrueTypeWrapper#italic?] and
|
|
30
|
+
[HexaPDF::Font::Type1Wrapper#italic?] for determining whether a font is italic
|
|
31
|
+
* [HexaPDF::Encryption::StandardSecurityHandler#decryption_password_type] for
|
|
32
|
+
information on the type of password used for decryption
|
|
33
|
+
|
|
34
|
+
### Changed
|
|
35
|
+
|
|
36
|
+
* **Breaking change**: Style property 'align' is now called 'text_align' and
|
|
37
|
+
'valign' is 'text_valign'
|
|
38
|
+
* **Breaking change**: Style property 'position' now takes the absolute position
|
|
39
|
+
directly as value instead of in the 'position_hint' property
|
|
40
|
+
* **Breaking change**: Style property 'position_hint' is now called 'align'
|
|
41
|
+
* **Breaking change**: Glyph objects now take the font wrapper instead of the
|
|
42
|
+
font on creation
|
|
43
|
+
* **Breaking change**: The item marker type of a [HexaPDF::Layout::ListBox] item
|
|
44
|
+
is now set via `#marker_type` instead of `#item_type`
|
|
45
|
+
* [HexaPDF::Object#validate] to catch exceptions and provided an appropriate
|
|
46
|
+
validation message
|
|
47
|
+
|
|
48
|
+
### Fixed
|
|
49
|
+
|
|
50
|
+
* [HexaPDF::Layout::ColumnBox#fit] to correctly take initial height into account
|
|
51
|
+
* [HexaPDF::Layout::ColumnBox#fit] to ensure correct results in case the
|
|
52
|
+
requested dimensions are larger than the current region
|
|
53
|
+
* [HexaPDF::Document::Layout#formatted_text_box] to correctly handle properties
|
|
54
|
+
* [HexaPDF::Layout::Frame#fit] to raise an error if an invalid value for the
|
|
55
|
+
style property 'position' is used
|
|
56
|
+
* Validation of PDF arrays and dictionaries by making sure only processed values
|
|
57
|
+
are used
|
|
58
|
+
|
|
59
|
+
|
|
1
60
|
## 0.34.1 - 2023-11-01
|
|
2
61
|
|
|
3
62
|
### Added
|
|
@@ -4,9 +4,10 @@
|
|
|
4
4
|
# inside a rectangular area, with various horizontal and vertical alignment
|
|
5
5
|
# options.
|
|
6
6
|
#
|
|
7
|
-
# The text can be aligned horizontally by setting
|
|
8
|
-
#
|
|
9
|
-
#
|
|
7
|
+
# The text can be aligned horizontally by setting
|
|
8
|
+
# [HexaPDF::Layout::Style#text_align] and vertically by
|
|
9
|
+
# [HexaPDF::Layout::Style#text_valign]. In this example, a sample text is laid
|
|
10
|
+
# out in all possible combinations.
|
|
10
11
|
#
|
|
11
12
|
# Usage:
|
|
12
13
|
# : `ruby text_layouter_alignment.rb`
|
|
@@ -26,8 +27,7 @@ canvas.font("Times", size: 10, variant: :bold)
|
|
|
26
27
|
width = 100
|
|
27
28
|
height = 150
|
|
28
29
|
y_base = 800
|
|
29
|
-
tf =
|
|
30
|
-
font: doc.fonts.add("Times"))
|
|
30
|
+
tf = doc.layout.text_fragments(sample_text, font: doc.fonts.add("Times"))
|
|
31
31
|
tl = HexaPDF::Layout::TextLayouter.new
|
|
32
32
|
|
|
33
33
|
[:left, :center, :right, :justify].each_with_index do |align, x_index|
|
|
@@ -38,8 +38,8 @@ tl = HexaPDF::Layout::TextLayouter.new
|
|
|
38
38
|
y = y_base - (height + 30) * y_index
|
|
39
39
|
canvas.text(valign.to_s, at: [20, y - height / 2]) if x_index == 0
|
|
40
40
|
|
|
41
|
-
tl.style.
|
|
42
|
-
tl.fit(
|
|
41
|
+
tl.style.text_align(align).text_valign(valign)
|
|
42
|
+
tl.fit(tf, width, height).draw(canvas, x, y)
|
|
43
43
|
canvas.stroke_color("hp-blue-dark").rectangle(x, y, width, -height).stroke
|
|
44
44
|
end
|
|
45
45
|
end
|
|
@@ -57,7 +57,7 @@ items = sample_text.split(/(:-\)|;-\))/).map do |part|
|
|
|
57
57
|
end
|
|
58
58
|
|
|
59
59
|
layouter = TextLayouter.new
|
|
60
|
-
layouter.style.
|
|
60
|
+
layouter.style.text_align = :justify
|
|
61
61
|
layouter.style.line_spacing(:proportional, 1.5)
|
|
62
62
|
layouter.fit(items, 500, 700).draw(doc.pages.add.canvas, 50, 800)
|
|
63
63
|
|
|
@@ -37,17 +37,15 @@ require 'hexapdf'
|
|
|
37
37
|
|
|
38
38
|
doc = HexaPDF::Document.new
|
|
39
39
|
canvas = doc.pages.add([0, 0, 180, 230]).canvas
|
|
40
|
-
canvas.font("Times", size: 10, variant: :bold)
|
|
41
|
-
|
|
42
40
|
text = "Hello! Fly-fishing\nand wand\u{00AD}ering\taround - fanta\u{200B}stic" \
|
|
43
41
|
" 1\u{00A0}0\u{00A0}1"
|
|
44
42
|
|
|
45
43
|
x = 10
|
|
46
44
|
y = 220
|
|
47
|
-
frag =
|
|
45
|
+
frag = doc.layout.text_fragments(text, font: doc.fonts.add("Times"))
|
|
48
46
|
layouter = HexaPDF::Layout::TextLayouter.new
|
|
49
47
|
[30, 60, 100, 160].each do |width|
|
|
50
|
-
result = layouter.fit(
|
|
48
|
+
result = layouter.fit(frag, width, 400)
|
|
51
49
|
result.draw(canvas, x, y)
|
|
52
50
|
canvas.stroke_color("hp-blue-dark").line_width(0.2)
|
|
53
51
|
canvas.rectangle(x, y, width, -result.height).stroke
|
|
@@ -25,8 +25,6 @@
|
|
|
25
25
|
|
|
26
26
|
require 'hexapdf'
|
|
27
27
|
|
|
28
|
-
include HexaPDF::Layout
|
|
29
|
-
|
|
30
28
|
doc = HexaPDF::Document.new
|
|
31
29
|
page = doc.pages.add
|
|
32
30
|
canvas = page.canvas
|
|
@@ -40,8 +38,8 @@ dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation
|
|
|
40
38
|
ullamco laboris nisi ut aliquip ex ea commodo consequat.
|
|
41
39
|
".tr("\n", ' ') * 10
|
|
42
40
|
|
|
43
|
-
items =
|
|
44
|
-
layouter = TextLayouter.new
|
|
41
|
+
items = doc.layout.text_fragments(sample_text, font: font)
|
|
42
|
+
layouter = HexaPDF::Layout::TextLayouter.new
|
|
45
43
|
|
|
46
44
|
########################################################################
|
|
47
45
|
# Circly things on the top
|
|
@@ -71,13 +69,13 @@ result.draw(canvas, 0, circle_top)
|
|
|
71
69
|
canvas.circle(0, circle_top - radius, radius).stroke
|
|
72
70
|
|
|
73
71
|
# Center: full circle
|
|
74
|
-
layouter.style.
|
|
72
|
+
layouter.style.text_align = :justify
|
|
75
73
|
result = layouter.fit(items, circle, radius * 2)
|
|
76
74
|
result.draw(canvas, page.box.width / 2.0 - radius, circle_top)
|
|
77
75
|
canvas.circle(page.box.width / 2.0, circle_top - radius, radius).stroke
|
|
78
76
|
|
|
79
77
|
# Right: left half circle
|
|
80
|
-
layouter.style.
|
|
78
|
+
layouter.style.text_align = :right
|
|
81
79
|
result = layouter.fit(items, left_half_circle, radius * 2)
|
|
82
80
|
result.draw(canvas, page.box.width - radius, circle_top)
|
|
83
81
|
canvas.circle(page.box.width, circle_top - radius, radius).stroke
|
|
@@ -106,14 +104,14 @@ left_half_diamond = lambda do |height, line_height|
|
|
|
106
104
|
end
|
|
107
105
|
|
|
108
106
|
# Left: right half diamond
|
|
109
|
-
layouter.style.
|
|
107
|
+
layouter.style.text_align = :left
|
|
110
108
|
result = layouter.fit(items, half_diamond_width, 2 * diamond_width)
|
|
111
109
|
result.draw(canvas, 0, diamond_top)
|
|
112
110
|
canvas.polyline(0, diamond_top, diamond_width, diamond_top - diamond_width,
|
|
113
111
|
0, diamond_top - 2 * diamond_width).stroke
|
|
114
112
|
|
|
115
113
|
# Center: full diamond
|
|
116
|
-
layouter.style.
|
|
114
|
+
layouter.style.text_align = :justify
|
|
117
115
|
result = layouter.fit(items, full_diamond, 2 * diamond_width)
|
|
118
116
|
left = page.box.width / 2.0 - diamond_width
|
|
119
117
|
result.draw(canvas, left, diamond_top)
|
|
@@ -123,7 +121,7 @@ canvas.polyline(left + diamond_width, diamond_top,
|
|
|
123
121
|
left, diamond_top - diamond_width).close_subpath.stroke
|
|
124
122
|
|
|
125
123
|
# Right: left half diamond
|
|
126
|
-
layouter.style.
|
|
124
|
+
layouter.style.text_align = :right
|
|
127
125
|
result = layouter.fit(items, left_half_diamond, 2 * diamond_width)
|
|
128
126
|
middle = page.box.width
|
|
129
127
|
result.draw(canvas, middle - diamond_width, diamond_top)
|
|
@@ -142,7 +140,7 @@ sine_wave = lambda do |height, line_height|
|
|
|
142
140
|
40 * Math.sin(2 * Math::PI * (height + line_height) / sine_wave_height)].max
|
|
143
141
|
[offset, sine_wave_height + 100 + offset * -2]
|
|
144
142
|
end
|
|
145
|
-
layouter.style.
|
|
143
|
+
layouter.style.text_align = :justify
|
|
146
144
|
result = layouter.fit(items, sine_wave, sine_wave_height)
|
|
147
145
|
middle = page.box.width / 2.0
|
|
148
146
|
result.draw(canvas, middle - (sine_wave_height + 100) / 2, sine_wave_top)
|
|
@@ -167,7 +165,7 @@ house = lambda do |height, line_height|
|
|
|
167
165
|
0
|
|
168
166
|
end
|
|
169
167
|
end
|
|
170
|
-
layouter.style.
|
|
168
|
+
layouter.style.text_align = :justify
|
|
171
169
|
result = layouter.fit(items, house, 200)
|
|
172
170
|
|
|
173
171
|
middle = page.box.width / 2.0
|
|
@@ -28,9 +28,9 @@ adipis\u{00AD}cing elit, sed do eiusmod tempor incididunt ut labore et
|
|
|
28
28
|
dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation
|
|
29
29
|
ullamco laboris nisi ut aliquip ex ea commodo consequat.
|
|
30
30
|
".tr("\n", ' ') * 12
|
|
31
|
-
items =
|
|
31
|
+
items = doc.layout.text_fragments(sample_text, font: doc.fonts.add("Times"))
|
|
32
32
|
layouter = TextLayouter.new
|
|
33
|
-
layouter.style.
|
|
33
|
+
layouter.style.text_align = :justify
|
|
34
34
|
|
|
35
35
|
# The house example
|
|
36
36
|
house = Geom2D::Polygon([100, 200], [400, 200], [500, 100], [400, 100], [400, 0],
|
|
@@ -57,8 +57,7 @@ draw_box = lambda do |**args|
|
|
|
57
57
|
end
|
|
58
58
|
|
|
59
59
|
# Absolutely positioned box with margin
|
|
60
|
-
draw_box.call(width: 100, height: 100, position:
|
|
61
|
-
position_hint: [250, 250])
|
|
60
|
+
draw_box.call(width: 100, height: 100, position: [250, 250], margin: 10)
|
|
62
61
|
|
|
63
62
|
# Fixed sized box with automatic width
|
|
64
63
|
draw_box.call(height: 100)
|
|
@@ -71,23 +70,23 @@ draw_box.call(width: 100, height: 100)
|
|
|
71
70
|
draw_box.call(width: 100, height: 100)
|
|
72
71
|
|
|
73
72
|
# Fixed sized floating box, space to the right can be used
|
|
74
|
-
draw_box.call(width: 100, height: 100, position: :float,
|
|
73
|
+
draw_box.call(width: 100, height: 100, position: :float, align: :left)
|
|
75
74
|
|
|
76
75
|
# Fixed sized floating box again, floating to the right
|
|
77
|
-
draw_box.call(width: 100, height: 100, position: :float,
|
|
76
|
+
draw_box.call(width: 100, height: 100, position: :float, align: :right)
|
|
78
77
|
|
|
79
78
|
# Fixed sized floating box again, floating to the left with margin
|
|
80
|
-
draw_box.call(width: 100, height: 100, position: :float,
|
|
79
|
+
draw_box.call(width: 100, height: 100, position: :float, align: :left,
|
|
81
80
|
margin: [0, 10])
|
|
82
81
|
|
|
83
82
|
# Fixed sized box, no floating
|
|
84
83
|
draw_box.call(width: 100, height: 100)
|
|
85
84
|
|
|
86
85
|
# Fixed sized box, center aligned in the available space
|
|
87
|
-
draw_box.call(width: 100, height: 100,
|
|
86
|
+
draw_box.call(width: 100, height: 100, align: :center)
|
|
88
87
|
|
|
89
88
|
# Fixed sized box, right aligned in the available space
|
|
90
|
-
draw_box.call(width: 100, height: 100,
|
|
89
|
+
draw_box.call(width: 100, height: 100, align: :right)
|
|
91
90
|
|
|
92
91
|
# Fixed sized box, consuming the whole remaining available space
|
|
93
92
|
draw_box.call
|
|
@@ -28,10 +28,10 @@ boxes = []
|
|
|
28
28
|
boxes << doc.layout.image_box(File.join(__dir__, 'machupicchu.jpg'),
|
|
29
29
|
width: 100, margin: [10, 30], position: :float)
|
|
30
30
|
boxes << Box.create(width: 50, height: 50, margin: 20,
|
|
31
|
-
position: :float,
|
|
31
|
+
position: :float, align: :right,
|
|
32
32
|
background_color: "hp-blue-light2",
|
|
33
33
|
border: {width: 1, color: "hp-blue-dark"})
|
|
34
|
-
boxes << doc.layout.lorem_ipsum_box(count: 3, position: :flow,
|
|
34
|
+
boxes << doc.layout.lorem_ipsum_box(count: 3, position: :flow, text_align: :justify)
|
|
35
35
|
|
|
36
36
|
i = 0
|
|
37
37
|
frame_filled = false
|
data/examples/018-composer.rb
CHANGED
|
@@ -4,9 +4,9 @@
|
|
|
4
4
|
# documents by providing a high-level interface to the box layouting engine.
|
|
5
5
|
#
|
|
6
6
|
# Basic style properties can be set using the [HexaPDF::Composer#style] method
|
|
7
|
-
# and the style name `:
|
|
7
|
+
# and the style name `:base`. These properties are reused by every box and can
|
|
8
8
|
# be adjusted on a box-by-box basis. Newly defined styles also inherit the
|
|
9
|
-
# properties from the `:
|
|
9
|
+
# properties from the `:base` style.
|
|
10
10
|
#
|
|
11
11
|
# Various methods allow the easy creation of boxes, for example, text and image
|
|
12
12
|
# boxes. All these boxes are automatically drawn on the page. If the page has
|
|
@@ -25,20 +25,19 @@ dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exer\u{00AD}citation
|
|
|
25
25
|
ullamco laboris nisi ut aliquip ex ea commodo consequat. ".tr("\n", " ")
|
|
26
26
|
|
|
27
27
|
HexaPDF::Composer.create('composer.pdf') do |pdf|
|
|
28
|
-
pdf.style(:base, line_spacing: 1.5, last_line_gap: true,
|
|
28
|
+
pdf.style(:base, line_spacing: 1.5, last_line_gap: true, text_align: :justify)
|
|
29
29
|
pdf.style(:image, border: {width: 1}, padding: 5, margin: 10)
|
|
30
30
|
pdf.style(:link, fill_color: "hp-blue-dark", underline: true)
|
|
31
31
|
image = File.join(__dir__, 'machupicchu.jpg')
|
|
32
32
|
|
|
33
33
|
pdf.text(lorem_ipsum * 2)
|
|
34
34
|
pdf.image(image, style: :image, width: 200, position: :float)
|
|
35
|
-
pdf.image(image, style: :image, width: 200, position:
|
|
36
|
-
position_hint: [200, 300])
|
|
35
|
+
pdf.image(image, style: :image, width: 200, position: [200, 300])
|
|
37
36
|
pdf.text(lorem_ipsum * 20, position: :flow)
|
|
38
37
|
|
|
39
38
|
pdf.formatted_text(["Produced by ",
|
|
40
39
|
{link: "https://hexapdf.gettalong.org", text: "HexaPDF",
|
|
41
40
|
style: :link},
|
|
42
41
|
" via HexaPDF::Composer"],
|
|
43
|
-
font_size: 15,
|
|
42
|
+
font_size: 15, text_align: :center, padding: 15)
|
|
44
43
|
end
|
data/examples/020-column_box.rb
CHANGED
|
@@ -28,10 +28,10 @@ columns = doc.layout.column(columns: 2, style: {position: :flow}) do |column|
|
|
|
28
28
|
column.image(File.join(__dir__, 'machupicchu.jpg'), width: 100,
|
|
29
29
|
style: {margin: [10, 30], position: :float})
|
|
30
30
|
column.box(:base, width: 50, height: 50,
|
|
31
|
-
style: {margin: 20, position: :float,
|
|
31
|
+
style: {margin: 20, position: :float, align: :right,
|
|
32
32
|
background_color: "hp-blue-light2",
|
|
33
33
|
border: {width: 1, color: "hp-blue-dark"}})
|
|
34
|
-
column.lorem_ipsum(count: 2, position: :flow,
|
|
34
|
+
column.lorem_ipsum(count: 2, position: :flow, text_align: :justify)
|
|
35
35
|
end
|
|
36
36
|
end
|
|
37
37
|
result = frame.fit(columns)
|
data/examples/021-list_box.rb
CHANGED
|
@@ -16,7 +16,7 @@ HexaPDF::Composer.create("list_box.pdf") do |composer|
|
|
|
16
16
|
composer.list(content_indentation: 40, item_spacing: 20) do |list|
|
|
17
17
|
list.lorem_ipsum
|
|
18
18
|
list.image(File.join(__dir__, 'machupicchu.jpg'), height: 100)
|
|
19
|
-
list.list(
|
|
19
|
+
list.list(marker_type: :decimal) do |sub_list|
|
|
20
20
|
1.upto(10) {|i| sub_list.text("Item #{i}") }
|
|
21
21
|
end
|
|
22
22
|
list.column do |column|
|
|
@@ -27,14 +27,14 @@ HexaPDF::Composer.create('composer_optional_content.pdf') do |composer|
|
|
|
27
27
|
a3 = composer.document.optional_content.ocg('Answer 3')
|
|
28
28
|
a3m = composer.document.optional_content.create_ocmd([a3, all], policy: :any_on)
|
|
29
29
|
|
|
30
|
-
composer.text('The Great Ruby Quiz',
|
|
30
|
+
composer.text('The Great Ruby Quiz', text_align: :center, margin: [0, 0, 24],
|
|
31
31
|
font: ['Helvetica', variant: :bold], font_size: 24)
|
|
32
32
|
|
|
33
|
-
composer.list(
|
|
33
|
+
composer.list(marker_type: :decimal, item_spacing: 32, style: :question) do |listing|
|
|
34
34
|
listing.multiple do |item|
|
|
35
35
|
item.text('Who created Ruby?', style: :question)
|
|
36
36
|
item.column(columns: 3, gaps: 5) do |cols|
|
|
37
|
-
cols.list(
|
|
37
|
+
cols.list(marker_type: :decimal) do |answers|
|
|
38
38
|
answers.text('Guido van Rossum')
|
|
39
39
|
answers.multiple do |answer|
|
|
40
40
|
answer.text('Yukihiro “Matz” Matsumoto', position: :float)
|
|
@@ -49,7 +49,7 @@ HexaPDF::Composer.create('composer_optional_content.pdf') do |composer|
|
|
|
49
49
|
listing.multiple do |item|
|
|
50
50
|
item.text('When was Ruby created?', style: :question)
|
|
51
51
|
item.column(columns: 3, gaps: 5) do |cols|
|
|
52
|
-
cols.list(
|
|
52
|
+
cols.list(marker_type: :decimal) do |answers|
|
|
53
53
|
answers.text('1991')
|
|
54
54
|
answers.text('1992')
|
|
55
55
|
answers.multiple do |answer|
|
|
@@ -74,7 +74,7 @@ HexaPDF::Composer.create('composer_optional_content.pdf') do |composer|
|
|
|
74
74
|
action = composer.document.wrap({Type: :Action, S: :SetOCGState})
|
|
75
75
|
action.add_state_change(:toggle, [a1, a2, a3])
|
|
76
76
|
composer.text("Click to toggle answers", border: {width: 1, color: "red"},
|
|
77
|
-
|
|
77
|
+
align: :right, padding: 2, overlays: [[:link, action: action]])
|
|
78
78
|
|
|
79
79
|
composer.document.optional_content.default_configuration(
|
|
80
80
|
BaseState: :OFF,
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
# # Frame - Mask Mode
|
|
2
|
+
#
|
|
3
|
+
# This example shows how to use the style property 'mask_mode' to achieve
|
|
4
|
+
# certain effects like overlaying boxes on each other or using multiple
|
|
5
|
+
# horizontal alignments on one line.
|
|
6
|
+
#
|
|
7
|
+
# Usage:
|
|
8
|
+
# : `ruby frame_mask_mode.rb`
|
|
9
|
+
#
|
|
10
|
+
require 'hexapdf'
|
|
11
|
+
|
|
12
|
+
HexaPDF::Composer.create('frame_mask_mode.pdf') do |composer|
|
|
13
|
+
box = composer.image(File.join(__dir__, 'machupicchu.jpg'),
|
|
14
|
+
border: {width: 1}, mask_mode: :none)
|
|
15
|
+
composer.text('Text overlaid over image', height: box.height, text_align: :center,
|
|
16
|
+
font_size: 50, text_valign: :center, text_rendering_mode: :fill_stroke,
|
|
17
|
+
fill_color: 'white', stroke_color: 'hp-blue', margin: [0, 0, 10])
|
|
18
|
+
composer.column(columns: 1, style: {border: {width: 1}, padding: 10}) do |col|
|
|
19
|
+
col.text('Center', mask_mode: :box, align: :center)
|
|
20
|
+
col.text('Left', mask_mode: :fill_horizontal)
|
|
21
|
+
col.text('Right', align: :right)
|
|
22
|
+
end
|
|
23
|
+
end
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
# # Composer - Fallback Fonts
|
|
2
|
+
#
|
|
3
|
+
# This example shows how to use the fallback font support of HexaPDF to replace
|
|
4
|
+
# invalid glyphs with ones from other fonts.
|
|
5
|
+
#
|
|
6
|
+
# While the examples shows the usage of a single fallback font, it can easily be
|
|
7
|
+
# generalized to support multiple fallback fonts.
|
|
8
|
+
#
|
|
9
|
+
# Usage:
|
|
10
|
+
# : `ruby composer_fallback_fonts.rb`
|
|
11
|
+
#
|
|
12
|
+
|
|
13
|
+
require 'hexapdf'
|
|
14
|
+
|
|
15
|
+
HexaPDF::Composer.create('composer_fallback_fonts.pdf') do |composer|
|
|
16
|
+
zapf_dingbats = composer.document.fonts.add('ZapfDingbats')
|
|
17
|
+
composer.document.config['font.fallback'] = ['ZapfDingbats']
|
|
18
|
+
composer.text('This text contains the scissors symbol ✂ which is not available in ' \
|
|
19
|
+
'the default font Times but available in the set ZapfDingbats fallback ' \
|
|
20
|
+
'font. Other symbols from ZapfDingbats like ✐ and ✈ can also be used.' \
|
|
21
|
+
"\n\n❤ HexaPDF")
|
|
22
|
+
end
|
data/lib/hexapdf/cli/info.rb
CHANGED
|
@@ -128,6 +128,7 @@ module HexaPDF
|
|
|
128
128
|
details = doc.security_handler.encryption_details
|
|
129
129
|
data = "yes (version: #{details[:version]}, key length: #{details[:key_length]}bits)"
|
|
130
130
|
output_line("Encrypted", data)
|
|
131
|
+
output_line(" Used Password", doc.security_handler.decryption_password_type)
|
|
131
132
|
output_line(" String algorithm", details[:string_algorithm].to_s)
|
|
132
133
|
output_line(" Stream algorithm", details[:stream_algorithm].to_s)
|
|
133
134
|
output_line(" EFF algorithm", details[:embedded_file_algorithm].to_s)
|
data/lib/hexapdf/cli/inspect.rb
CHANGED
|
@@ -42,6 +42,55 @@ module HexaPDF
|
|
|
42
42
|
# Shows the internal structure of a PDF file.
|
|
43
43
|
class Inspect < Command
|
|
44
44
|
|
|
45
|
+
# Outputs the content of a page in a nicer form.
|
|
46
|
+
class ContentProcessor < HexaPDF::Content::Processor
|
|
47
|
+
|
|
48
|
+
# :nodoc:
|
|
49
|
+
def initialize(*)
|
|
50
|
+
super
|
|
51
|
+
@indent = 0
|
|
52
|
+
@serializer = HexaPDF::Serializer.new
|
|
53
|
+
end
|
|
54
|
+
|
|
55
|
+
# :nodoc:
|
|
56
|
+
def paint_xobject(name)
|
|
57
|
+
puts "#{indent}paint_xobject #{@serializer.serialize(name)}"
|
|
58
|
+
@indent += 1
|
|
59
|
+
super
|
|
60
|
+
@indent -= 1
|
|
61
|
+
end
|
|
62
|
+
|
|
63
|
+
# :nodoc:
|
|
64
|
+
def method_missing(operator, *operands)
|
|
65
|
+
case operator
|
|
66
|
+
when :save_graphics_state, :begin_text, :begin_marked_content
|
|
67
|
+
puts "#{indent}#{operator}"
|
|
68
|
+
@indent += 1
|
|
69
|
+
when :restore_graphics_state, :end_text, :end_marked_content
|
|
70
|
+
@indent -= 1
|
|
71
|
+
puts "#{indent}#{operator}"
|
|
72
|
+
when :show_text, :show_text_with_positioning
|
|
73
|
+
puts "#{indent}text> #{decode_text(*operands)}"
|
|
74
|
+
else
|
|
75
|
+
puts "#{indent}#{operator} #{operands.map {|op| @serializer.serialize(op) }.join(' ')}"
|
|
76
|
+
@indent += 1 if operator == :begin_marked_content_with_property_list
|
|
77
|
+
end
|
|
78
|
+
end
|
|
79
|
+
|
|
80
|
+
# :nodoc:
|
|
81
|
+
def respond_to_missing?(*)
|
|
82
|
+
true
|
|
83
|
+
end
|
|
84
|
+
|
|
85
|
+
private
|
|
86
|
+
|
|
87
|
+
# Returns the current indentation string.
|
|
88
|
+
def indent
|
|
89
|
+
' ' * @indent
|
|
90
|
+
end
|
|
91
|
+
|
|
92
|
+
end
|
|
93
|
+
|
|
45
94
|
def initialize #:nodoc:
|
|
46
95
|
super('inspect', takes_commands: false)
|
|
47
96
|
short_desc("Dig into the internal structure of a PDF file")
|
|
@@ -176,7 +225,7 @@ module HexaPDF
|
|
|
176
225
|
puts str
|
|
177
226
|
end
|
|
178
227
|
|
|
179
|
-
when 'po', 'ps'
|
|
228
|
+
when 'po', 'ps', 'psd'
|
|
180
229
|
page_number_str = data.shift
|
|
181
230
|
unless page_number_str
|
|
182
231
|
$stderr.puts("Error: Missing PAGE argument to #{command}")
|
|
@@ -188,8 +237,11 @@ module HexaPDF
|
|
|
188
237
|
next
|
|
189
238
|
end
|
|
190
239
|
page = @doc.pages[page_number]
|
|
191
|
-
|
|
240
|
+
case command
|
|
241
|
+
when 'ps'
|
|
192
242
|
$stdout.write(page.contents)
|
|
243
|
+
when 'psd'
|
|
244
|
+
page.process_contents(ContentProcessor.new)
|
|
193
245
|
else
|
|
194
246
|
puts "#{page.oid} #{page.gen} obj"
|
|
195
247
|
serialize(page.value, recursive: false)
|
|
@@ -375,6 +427,7 @@ module HexaPDF
|
|
|
375
427
|
["p[ages] [RANGE]", "Print information about pages"],
|
|
376
428
|
["po PAGE", "Print the page object"],
|
|
377
429
|
["ps PAGE", "Print the content stream of the page"],
|
|
430
|
+
["psd PAGE", "Print the decoded content stream of the page"],
|
|
378
431
|
["pc | page-count", "Print the number of pages"],
|
|
379
432
|
["search REGEXP", "Print objects matching the pattern"],
|
|
380
433
|
["h[elp]", "Show the help"],
|
data/lib/hexapdf/composer.rb
CHANGED
|
@@ -78,8 +78,8 @@ module HexaPDF
|
|
|
78
78
|
#
|
|
79
79
|
# #>pdf-full
|
|
80
80
|
# HexaPDF::Composer.create('out.pdf', page_size: :A6, margin: 36) do |pdf|
|
|
81
|
-
# pdf.style(:base, font_size: 20,
|
|
82
|
-
# pdf.text("Hello World",
|
|
81
|
+
# pdf.style(:base, font_size: 20, text_align: :center)
|
|
82
|
+
# pdf.text("Hello World", text_valign: :center)
|
|
83
83
|
# end
|
|
84
84
|
#
|
|
85
85
|
# See: HexaPDF::Document::Layout, HexaPDF::Layout::Frame, HexaPDF::Layout::Box
|
|
@@ -138,6 +138,30 @@ module HexaPDF
|
|
|
138
138
|
|
|
139
139
|
end
|
|
140
140
|
|
|
141
|
+
# Provides the default implementation for the configuration option 'font.on_invalid_glyph'.
|
|
142
|
+
#
|
|
143
|
+
# It uses the first font in the list provided by the 'font.fallback' configuration option that
|
|
144
|
+
# contains a glyph for the +codepoint+ (taking the font variant into account). If no fallback font
|
|
145
|
+
# contains such a glyph, +invalid_glyph+ is used.
|
|
146
|
+
def self.font_on_invalid_glyph(codepoint, invalid_glyph)
|
|
147
|
+
font_wrapper = invalid_glyph.font_wrapper
|
|
148
|
+
document = font_wrapper.pdf_object.document
|
|
149
|
+
variant = case
|
|
150
|
+
when font_wrapper.italic? && font_wrapper.bold? then :bold_italic
|
|
151
|
+
when font_wrapper.bold? then :bold
|
|
152
|
+
when font_wrapper.italic? then :italic
|
|
153
|
+
else :none
|
|
154
|
+
end
|
|
155
|
+
document.config['font.fallback'].each do |font_name|
|
|
156
|
+
font = document.fonts.add(font_name, variant: variant) rescue document.fonts.add(font_name)
|
|
157
|
+
glyph = font.decode_codepoint(codepoint)
|
|
158
|
+
unless glyph.kind_of?(HexaPDF::Font::InvalidGlyph)
|
|
159
|
+
return [glyph]
|
|
160
|
+
end
|
|
161
|
+
end
|
|
162
|
+
[invalid_glyph]
|
|
163
|
+
end
|
|
164
|
+
|
|
141
165
|
# The default document specific configuration object.
|
|
142
166
|
#
|
|
143
167
|
# Modify this object if you want to globally change document specific options or if you want to
|
|
@@ -247,6 +271,14 @@ module HexaPDF
|
|
|
247
271
|
#
|
|
248
272
|
# See PDF2.0 s7.4.1, ADB sH.3 3.3
|
|
249
273
|
#
|
|
274
|
+
# font.fallback::
|
|
275
|
+
# An array of fallback font names to be used when replacing invalid glyphs.
|
|
276
|
+
#
|
|
277
|
+
# The values can be anything that can be passed to Document::Fonts#add. Note that the +variant+
|
|
278
|
+
# of a font is determined by looking at the font for which a invalid glyph should be replaced.
|
|
279
|
+
#
|
|
280
|
+
# The default value consists of the built-in fonts ZapfDingbats and Symbol.
|
|
281
|
+
#
|
|
250
282
|
# font.map::
|
|
251
283
|
# Defines a mapping from font names and variants to font files.
|
|
252
284
|
#
|
|
@@ -263,6 +295,32 @@ module HexaPDF
|
|
|
263
295
|
# [italic] For the italic or oblique variant of the font
|
|
264
296
|
# [bold_italic] For the bold and italic/oblique variant of the font
|
|
265
297
|
#
|
|
298
|
+
# font.on_invalid_glyph::
|
|
299
|
+
# Callback hook when a character cannot be mapped to a glyph and one or more glyphs from a
|
|
300
|
+
# different font should be used. Only applies when using high-level text creation facilities.
|
|
301
|
+
#
|
|
302
|
+
# The value needs to be an object that responds to \#call(codepoint, invalid_glyph) where
|
|
303
|
+
# +codepoint+ is the Unicode codepoint that cannot be mapped to a valid glyph. The
|
|
304
|
+
# +invalid_glyph+ argument is the HexaPDF::Font::InvalidGlyph object that was the result of the
|
|
305
|
+
# initial mapping. The return value has to be an array of glyph objects which can be from any
|
|
306
|
+
# font but all need to be from the same one.
|
|
307
|
+
#
|
|
308
|
+
# The default implementation is provided by ::font_on_invalid_glyph and uses the
|
|
309
|
+
# 'font.fallback' configuration option. It is usually not necessary to change this
|
|
310
|
+
# configuration option or the 'font.on_missing_glyph' one.
|
|
311
|
+
#
|
|
312
|
+
# Note: The 'font.on_missing_glyph' configuration option does something similar but is
|
|
313
|
+
# restricted to returning a single glyph from the same font. Whenever a glyph is not found,
|
|
314
|
+
# 'font.on_missing_glyph' is invoked first and if an invalid glyph instance is returned, this
|
|
315
|
+
# callback hook is invoked when using the layout engine.
|
|
316
|
+
#
|
|
317
|
+
# A typical implementation would use one or more fallback fonts (probably choosing one in the
|
|
318
|
+
# correct font variant) for providing the necessary glyph(s):
|
|
319
|
+
#
|
|
320
|
+
# doc.config['font.on_invalid_glyph'] = lambda do |codepoint, glyph|
|
|
321
|
+
# [other_font.decode_codepoint(codepoint)]
|
|
322
|
+
# end
|
|
323
|
+
#
|
|
266
324
|
# font.on_missing_glyph::
|
|
267
325
|
# Callback hook when an UTF-8 character cannot be mapped to a glyph of a font.
|
|
268
326
|
#
|
|
@@ -451,9 +509,11 @@ module HexaPDF
|
|
|
451
509
|
Crypt: 'HexaPDF::Filter::Crypt',
|
|
452
510
|
Encryption: 'HexaPDF::Filter::Encryption',
|
|
453
511
|
},
|
|
512
|
+
'font.fallback' => ['ZapfDingbats', 'Symbol'],
|
|
454
513
|
'font.map' => {},
|
|
514
|
+
'font.on_invalid_glyph' => method(:font_on_invalid_glyph),
|
|
455
515
|
'font.on_missing_glyph' => proc do |char, font_wrapper|
|
|
456
|
-
HexaPDF::Font::InvalidGlyph.new(font_wrapper
|
|
516
|
+
HexaPDF::Font::InvalidGlyph.new(font_wrapper, char)
|
|
457
517
|
end,
|
|
458
518
|
'font.on_missing_unicode_mapping' => proc do |code_point, font|
|
|
459
519
|
raise HexaPDF::Error, "No Unicode mapping for code point #{code_point} " \
|