hexapdf 0.34.0 → 0.35.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 +74 -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 +3 -3
- 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/operator.rb +1 -1
- 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/type/page.rb +13 -5
- 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/content/test_operator.rb +3 -1
- 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_composer.rb +20 -0
- 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/type/test_page.rb +5 -3
- 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,77 @@
|
|
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
|
+
|
60
|
+
## 0.34.1 - 2023-11-01
|
61
|
+
|
62
|
+
### Added
|
63
|
+
|
64
|
+
* Setting of /SMask key in graphics state parameters operator
|
65
|
+
|
66
|
+
### Fixed
|
67
|
+
|
68
|
+
* [HexaPDF::Composer#page_style] to set a page style when no attributes are
|
69
|
+
given but a block is
|
70
|
+
* [HexaPDF::Type::Page#each_annotation] and
|
71
|
+
[HexaPDF::Type::Page#flatten_annotations] to process certain invalid /Annot
|
72
|
+
keys without errors
|
73
|
+
|
74
|
+
|
1
75
|
## 0.34.0 - 2023-10-22
|
2
76
|
|
3
77
|
### 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
|
@@ -281,7 +281,7 @@ module HexaPDF
|
|
281
281
|
#
|
282
282
|
# See: HexaPDF::Layout::PageStyle
|
283
283
|
def page_style(name, **attributes, &block)
|
284
|
-
if attributes.empty?
|
284
|
+
if attributes.empty? && block.nil?
|
285
285
|
@page_styles[name]
|
286
286
|
else
|
287
287
|
@page_styles[name] = HexaPDF::Layout::PageStyle.new(**attributes, &block)
|
@@ -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} " \
|