hexapdf 0.34.0 → 0.35.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (72) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +74 -0
  3. data/examples/009-text_layouter_alignment.rb +7 -7
  4. data/examples/010-text_layouter_inline_boxes.rb +1 -1
  5. data/examples/011-text_layouter_line_wrapping.rb +2 -4
  6. data/examples/013-text_layouter_shapes.rb +9 -11
  7. data/examples/014-text_in_polygon.rb +2 -2
  8. data/examples/016-frame_automatic_box_placement.rb +6 -7
  9. data/examples/017-frame_text_flow.rb +2 -2
  10. data/examples/018-composer.rb +5 -6
  11. data/examples/020-column_box.rb +2 -2
  12. data/examples/021-list_box.rb +1 -1
  13. data/examples/027-composer_optional_content.rb +5 -5
  14. data/examples/028-frame_mask_mode.rb +23 -0
  15. data/examples/029-composer_fallback_fonts.rb +22 -0
  16. data/lib/hexapdf/cli/info.rb +1 -0
  17. data/lib/hexapdf/cli/inspect.rb +55 -2
  18. data/lib/hexapdf/composer.rb +3 -3
  19. data/lib/hexapdf/configuration.rb +61 -1
  20. data/lib/hexapdf/content/canvas.rb +63 -0
  21. data/lib/hexapdf/content/canvas_composer.rb +142 -0
  22. data/lib/hexapdf/content/operator.rb +1 -1
  23. data/lib/hexapdf/content.rb +1 -0
  24. data/lib/hexapdf/dictionary.rb +14 -3
  25. data/lib/hexapdf/document/layout.rb +35 -13
  26. data/lib/hexapdf/encryption/standard_security_handler.rb +15 -0
  27. data/lib/hexapdf/error.rb +2 -1
  28. data/lib/hexapdf/font/invalid_glyph.rb +22 -6
  29. data/lib/hexapdf/font/true_type_wrapper.rb +48 -20
  30. data/lib/hexapdf/font/type1_wrapper.rb +48 -24
  31. data/lib/hexapdf/layout/box.rb +11 -8
  32. data/lib/hexapdf/layout/column_box.rb +5 -3
  33. data/lib/hexapdf/layout/frame.rb +77 -39
  34. data/lib/hexapdf/layout/image_box.rb +3 -3
  35. data/lib/hexapdf/layout/list_box.rb +20 -19
  36. data/lib/hexapdf/layout/style.rb +173 -68
  37. data/lib/hexapdf/layout/table_box.rb +3 -3
  38. data/lib/hexapdf/layout/text_box.rb +5 -5
  39. data/lib/hexapdf/layout/text_fragment.rb +50 -0
  40. data/lib/hexapdf/layout/text_layouter.rb +7 -6
  41. data/lib/hexapdf/object.rb +5 -2
  42. data/lib/hexapdf/pdf_array.rb +5 -0
  43. data/lib/hexapdf/type/acro_form/appearance_generator.rb +16 -11
  44. data/lib/hexapdf/type/page.rb +13 -5
  45. data/lib/hexapdf/utils/sorted_tree_node.rb +0 -10
  46. data/lib/hexapdf/version.rb +1 -1
  47. data/test/hexapdf/content/test_canvas.rb +37 -0
  48. data/test/hexapdf/content/test_canvas_composer.rb +112 -0
  49. data/test/hexapdf/content/test_operator.rb +3 -1
  50. data/test/hexapdf/document/test_layout.rb +40 -12
  51. data/test/hexapdf/encryption/test_standard_security_handler.rb +43 -0
  52. data/test/hexapdf/font/test_invalid_glyph.rb +13 -1
  53. data/test/hexapdf/font/test_true_type_wrapper.rb +15 -2
  54. data/test/hexapdf/font/test_type1_wrapper.rb +21 -2
  55. data/test/hexapdf/layout/test_column_box.rb +14 -0
  56. data/test/hexapdf/layout/test_frame.rb +181 -95
  57. data/test/hexapdf/layout/test_list_box.rb +7 -7
  58. data/test/hexapdf/layout/test_style.rb +14 -10
  59. data/test/hexapdf/layout/test_table_box.rb +3 -3
  60. data/test/hexapdf/layout/test_text_box.rb +2 -2
  61. data/test/hexapdf/layout/test_text_fragment.rb +37 -0
  62. data/test/hexapdf/layout/test_text_layouter.rb +10 -10
  63. data/test/hexapdf/test_composer.rb +20 -0
  64. data/test/hexapdf/test_configuration.rb +49 -0
  65. data/test/hexapdf/test_dictionary.rb +1 -1
  66. data/test/hexapdf/test_object.rb +13 -12
  67. data/test/hexapdf/test_pdf_array.rb +9 -0
  68. data/test/hexapdf/test_writer.rb +3 -3
  69. data/test/hexapdf/type/acro_form/test_appearance_generator.rb +41 -13
  70. data/test/hexapdf/type/test_page.rb +5 -3
  71. data/test/hexapdf/utils/test_sorted_tree_node.rb +1 -1
  72. metadata +7 -3
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 00aaef323df26b1a17c6e4ee39787c92037e8ae887713b28c3674962fcb6ac26
4
- data.tar.gz: a46ecda2514bc5c044d115370f66402fc22ad3f8c8dfa1741e057b6849a6b952
3
+ metadata.gz: 85a526812bcfbeae9fbe103513c3fac73ac2d736b2ac965305c8c1164fc976b7
4
+ data.tar.gz: f999566105d0ac42c5037bf642256fd6c9a0c515bffb2329ef32456009b46dbd
5
5
  SHA512:
6
- metadata.gz: a15e468bdc852fd4c16286ba0e4829fcd3fb3be64aa48c429246216fbfe1c856cb8e155bfefa83c47b99246e7161f20a1de54769aa222dbcdfdb25f8be777643
7
- data.tar.gz: d75048ff9d77601d93893cceb0c73673410695e3ea381e17d27de8c92d7de3d29d2ed467e44df6362dd28d3ee374a8e5710e5f1323393ee2e4104cdcecef1112
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 [HexaPDF::Layout::Style#align]
8
- # and vertically by [HexaPDF::Layout::Style#valign]. In this example, a sample
9
- # text is laid out in all possible combinations.
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 = HexaPDF::Layout::TextFragment.create(sample_text,
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.align(align).valign(valign)
42
- tl.fit([tf], width, height).draw(canvas, x, y)
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.align = :justify
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 = HexaPDF::Layout::TextFragment.create(text, font: doc.fonts.add("Times"))
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([frag], width, 400)
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 = [TextFragment.create(sample_text, font: font)]
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.align = :justify
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.align = :right
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.align = :left
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.align = :justify
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.align = :right
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.align = :justify
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.align = :justify
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 = [TextFragment.create(sample_text, font: doc.fonts.add("Times"))]
31
+ items = doc.layout.text_fragments(sample_text, font: doc.fonts.add("Times"))
32
32
  layouter = TextLayouter.new
33
- layouter.style.align = :justify
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: :absolute, margin: 10,
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, position_hint: :left)
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, position_hint: :right)
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, position_hint: :left,
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, position_hint: :center)
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, position_hint: :right)
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, position_hint: :right,
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, align: :justify)
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
@@ -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 `:basic`. These properties are reused by every box and can
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 `:basic` style.
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, align: :justify)
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: :absolute,
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, align: :center, padding: 15)
42
+ font_size: 15, text_align: :center, padding: 15)
44
43
  end
@@ -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, position_hint: :right,
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, align: :justify)
34
+ column.lorem_ipsum(count: 2, position: :flow, text_align: :justify)
35
35
  end
36
36
  end
37
37
  result = frame.fit(columns)
@@ -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(item_type: :decimal) do |sub_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', align: :center, margin: [0, 0, 24],
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(item_type: :decimal, item_spacing: 32, style: :question) do |listing|
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(item_type: :decimal) do |answers|
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(item_type: :decimal) do |answers|
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
- position_hint: :right, padding: 2, overlays: [[:link, action: action]])
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
@@ -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)
@@ -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
- if command.start_with?('ps')
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"],
@@ -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, align: :center)
82
- # pdf.text("Hello World", valign: :center)
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.wrapped_font, char)
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} " \