hexapdf 0.43.0 → 0.45.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (48) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +36 -0
  3. data/examples/027-composer_optional_content.rb +6 -4
  4. data/examples/030-pdfa.rb +13 -11
  5. data/lib/hexapdf/composer.rb +23 -0
  6. data/lib/hexapdf/content/canvas.rb +3 -3
  7. data/lib/hexapdf/content/canvas_composer.rb +1 -0
  8. data/lib/hexapdf/document/files.rb +7 -2
  9. data/lib/hexapdf/document/layout.rb +15 -3
  10. data/lib/hexapdf/document/metadata.rb +12 -1
  11. data/lib/hexapdf/font/type1/character_metrics.rb +1 -1
  12. data/lib/hexapdf/font/type1/font_metrics.rb +1 -1
  13. data/lib/hexapdf/layout/box.rb +180 -66
  14. data/lib/hexapdf/layout/box_fitter.rb +1 -0
  15. data/lib/hexapdf/layout/column_box.rb +18 -28
  16. data/lib/hexapdf/layout/container_box.rb +6 -6
  17. data/lib/hexapdf/layout/frame.rb +13 -94
  18. data/lib/hexapdf/layout/image_box.rb +4 -4
  19. data/lib/hexapdf/layout/list_box.rb +13 -31
  20. data/lib/hexapdf/layout/style.rb +8 -4
  21. data/lib/hexapdf/layout/table_box.rb +55 -58
  22. data/lib/hexapdf/layout/text_box.rb +84 -71
  23. data/lib/hexapdf/layout/text_fragment.rb +1 -1
  24. data/lib/hexapdf/layout/text_layouter.rb +7 -8
  25. data/lib/hexapdf/parser.rb +5 -2
  26. data/lib/hexapdf/rectangle.rb +4 -4
  27. data/lib/hexapdf/type/file_specification.rb +9 -5
  28. data/lib/hexapdf/type/form.rb +2 -2
  29. data/lib/hexapdf/type/graphics_state_parameter.rb +1 -1
  30. data/lib/hexapdf/version.rb +1 -1
  31. data/test/hexapdf/content/test_canvas_composer.rb +13 -8
  32. data/test/hexapdf/document/test_files.rb +5 -0
  33. data/test/hexapdf/document/test_layout.rb +16 -0
  34. data/test/hexapdf/document/test_metadata.rb +21 -0
  35. data/test/hexapdf/layout/test_box.rb +93 -37
  36. data/test/hexapdf/layout/test_box_fitter.rb +7 -0
  37. data/test/hexapdf/layout/test_column_box.rb +7 -13
  38. data/test/hexapdf/layout/test_container_box.rb +1 -1
  39. data/test/hexapdf/layout/test_frame.rb +7 -46
  40. data/test/hexapdf/layout/test_image_box.rb +14 -6
  41. data/test/hexapdf/layout/test_list_box.rb +26 -27
  42. data/test/hexapdf/layout/test_table_box.rb +47 -54
  43. data/test/hexapdf/layout/test_text_box.rb +83 -83
  44. data/test/hexapdf/test_composer.rb +20 -5
  45. data/test/hexapdf/test_parser.rb +8 -0
  46. data/test/hexapdf/test_serializer.rb +1 -0
  47. data/test/hexapdf/type/test_file_specification.rb +2 -1
  48. metadata +2 -2
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 24f6839e903fd945678915625b1e2ef6a12221f29a82cf1c7a6d8bcea38af288
4
- data.tar.gz: 2f8309e2ef2406dd279e00643bf7fbf73ded63a1076c0067684a356fea8ee89e
3
+ metadata.gz: cd31e769d2a906198da2a4194085cb2a884fa3879442b75add168d7f81d67d8b
4
+ data.tar.gz: 43c2b4ba4df2f997d470835995f2581aa306c639c63ddc892e648d94605f3b22
5
5
  SHA512:
6
- metadata.gz: 8c6ddd8ec6f19d39daa9a6422fdb3595d255b528fb93ca7907ef12dab0eca23c74de9fcfc27d02c15b3a9d3c24d47c7f7db6ea472f4c1ed34c2b7c06d4a8a351
7
- data.tar.gz: 1d77c2da70d9048cbef6935afacde40fb6d4041414fce571a331a4bee33b0e3ee2ff59bb28eee02e515798c3c156a57f42f139356e946f64e878ea962756a1d8
6
+ metadata.gz: f17a303dba8104974564a213c72c0f0862c520964a3255b575544489ccb5a17a468d093d3970817903c4eb798e888592410db18447d3264d535f3a01a4a94c82
7
+ data.tar.gz: d5727e2f2bfb5ed827ac92eecc8cd9e0ba73044150ba07d03623dccde43dc29db1cddc2fbce24bfd63268522e7965519e188271c2bc9c00162b1a660fc468b74
data/CHANGELOG.md CHANGED
@@ -1,3 +1,39 @@
1
+ ## 0.45.0 - 2024-06-18
2
+
3
+ ### Added
4
+
5
+ * [HexaPDF::Document::Layout#styles] and [HexaPDF::Composer#styles] for defining
6
+ multiple styles at once
7
+
8
+ ### Changed
9
+
10
+ * [HexaPDF::Layout::Box#fit] to set width/height correctly for boxes with
11
+ position `:flow`
12
+
13
+ ### Fixed
14
+
15
+ * Regression in [HexaPDF::Layout::ListBox] that leads to missing markers
16
+ * [HexaPDF::Content::CanvasComposer#draw_box] to handle truncated boxes
17
+ * [HexaPDF::Layout::TableBox::Cell] to handle too-big content in all cases
18
+
19
+
20
+ ## 0.44.0 - 2024-06-05
21
+
22
+ ### Added
23
+
24
+ * Support for specifying the MIME type when embedding files
25
+ * Support for adding custom XMP metadata
26
+
27
+ ### Changed
28
+
29
+ * **Breaking change**: Refactored the box implementation of the document layout
30
+ system
31
+
32
+ ### Fixed
33
+
34
+ * Parsing of invalid files with garbage bytes at the end
35
+
36
+
1
37
  ## 0.43.0 - 2024-05-26
2
38
 
3
39
  ### Added
@@ -16,8 +16,10 @@
16
16
  require 'hexapdf'
17
17
 
18
18
  HexaPDF::Composer.create('composer_optional_content.pdf') do |composer|
19
- composer.style(:question, font_size: 16, margin: [0, 0, 16], fill_color: 'hp-blue')
20
- composer.style(:answer, font: 'ZapfDingbats', fill_color: "green")
19
+ composer.styles(
20
+ question: {font_size: 16, margin: [0, 0, 16], fill_color: 'hp-blue'},
21
+ answer: {font: 'ZapfDingbats', fill_color: "green"},
22
+ )
21
23
 
22
24
  all = composer.document.optional_content.ocg('All answers')
23
25
  a1 = composer.document.optional_content.ocg('Answer 1')
@@ -38,7 +40,7 @@ HexaPDF::Composer.create('composer_optional_content.pdf') do |composer|
38
40
  answers.text('Guido van Rossum')
39
41
  answers.multiple do |answer|
40
42
  answer.text('Yukihiro “Matz” Matsumoto', position: :float)
41
- answer.text("\u{a0}\u{a0}4", style: :answer,
43
+ answer.text("\u{a0}\u{a0}", style: :answer,
42
44
  properties: {'optional_content' => a1})
43
45
  end
44
46
  answers.text('Rob Pike')
@@ -54,7 +56,7 @@ HexaPDF::Composer.create('composer_optional_content.pdf') do |composer|
54
56
  answers.text('1992')
55
57
  answers.multiple do |answer|
56
58
  answer.text('1993', position: :float)
57
- answer.text("\u{a0}\u{a0}4", style: :answer,
59
+ answer.text("\u{a0}\u{a0}", style: :answer,
58
60
  properties: {'optional_content' => a2})
59
61
  end
60
62
  end
data/examples/030-pdfa.rb CHANGED
@@ -8,6 +8,7 @@
8
8
  # Usage:
9
9
  # : `ruby pdfa.rb`
10
10
  #
11
+
11
12
  require 'hexapdf'
12
13
 
13
14
  HexaPDF::Composer.create('pdfa.pdf') do |composer|
@@ -27,17 +28,18 @@ HexaPDF::Composer.create('pdfa.pdf') do |composer|
27
28
  }
28
29
 
29
30
  # Define all styles
30
- composer.style(:base, font: 'Lato', font_size: 10, line_spacing: 1.3)
31
- composer.style(:top, font_size: 8)
32
- composer.style(:top_box, padding: [100, 0, 0], margin: [0, 0, 10], border: {width: [0, 0, 1]})
33
- composer.style(:header, font: 'Lato bold', font_size: 20, margin: [50, 0, 20])
34
- composer.style(:line_items, border: {width: 1, color: "eee"}, margin: [20, 0])
35
- composer.style(:line_item_cell, font_size: 8)
36
- composer.style(:footer, border: {width: [1, 0, 0], color: "darkgrey"},
37
- padding: [5, 0, 0], valign: :bottom)
38
- composer.style(:footer_heading, font: 'Lato bold',
39
- font_size: 8, padding: [0, 0, 8])
40
- composer.style(:footer_text, font_size: 8, fill_color: "darkgrey")
31
+ composer.styles(
32
+ base: {font: 'Lato', font_size: 10, line_spacing: 1.3},
33
+ top: {font_size: 8},
34
+ top_box: {padding: [100, 0, 0], margin: [0, 0, 10], border: {width: [0, 0, 1]}},
35
+ header: {font: 'Lato bold', font_size: 20, margin: [50, 0, 20]},
36
+ line_items: {border: {width: 1, color: "eee"}, margin: [20, 0]},
37
+ line_item_cell: {font_size: 8},
38
+ footer: {border: {width: [1, 0, 0], color: "darkgrey"}, padding: [5, 0, 0],
39
+ valign: :bottom},
40
+ footer_heading: {font: 'Lato bold', font_size: 8, padding: [0, 0, 8]},
41
+ footer_text: {font_size: 8, fill_color: "darkgrey"},
42
+ )
41
43
 
42
44
  # Top part
43
45
  composer.box(:container, style: :top_box) do |container|
@@ -254,6 +254,28 @@ module HexaPDF
254
254
  @document.layout.style(name, base: base, **properties)
255
255
  end
256
256
 
257
+ # :call-seq:
258
+ # composer.styles -> styles
259
+ # composer.styles(**mapping) -> styles
260
+ #
261
+ # Creates multiple named styles at once if +mapping+ is provided, and returns the style mapping.
262
+ #
263
+ # See HexaPDF::Document::Layout#styles for details; this method is just a thin wrapper around
264
+ # that method.
265
+ #
266
+ # Example:
267
+ #
268
+ # composer.styles(
269
+ # base: {font_size: 12, leading: 1.2},
270
+ # header: {font: 'Helvetica', fill_color: "008"},
271
+ # header1: {base: :header, font_size: 30}
272
+ # )
273
+ #
274
+ # See: HexaPDF::Layout::Style
275
+ def styles(**mapping)
276
+ @document.layout.styles(**mapping)
277
+ end
278
+
257
279
  # :call-seq:
258
280
  # composer.page_style(name) -> page_style
259
281
  # composer.page_style(name, **attributes, &template_block) -> page_style
@@ -425,6 +447,7 @@ module HexaPDF
425
447
  if draw_box
426
448
  @frame.draw(@canvas, result)
427
449
  drawn_on_page = true
450
+ (box = draw_box; break) unless box
428
451
  elsif !@frame.find_next_region
429
452
  unless drawn_on_page
430
453
  raise HexaPDF::Error, "Box doesn't fit on empty page"
@@ -1127,7 +1127,7 @@ module HexaPDF
1127
1127
  # canvas.rectangle(x, y, width, height, radius: 0) => canvas
1128
1128
  #
1129
1129
  # Appends a rectangle to the current path as a complete subpath (drawn in counterclockwise
1130
- # direction), with the bottom left corner specified by +x+ and +y+ and the given +width+ and
1130
+ # direction), with the bottom-left corner specified by +x+ and +y+ and the given +width+ and
1131
1131
  # +height+. Returns +self+.
1132
1132
  #
1133
1133
  # If +radius+ is greater than 0, the corners are rounded with the given radius.
@@ -1137,7 +1137,7 @@ module HexaPDF
1137
1137
  #
1138
1138
  # If there is no current path when the method is invoked, a new path is automatically begun.
1139
1139
  #
1140
- # The current point is set to the bottom left corner if +radius+ is zero, otherwise it is set
1140
+ # The current point is set to the bottom-left corner if +radius+ is zero, otherwise it is set
1141
1141
  # to (x, y + radius).
1142
1142
  #
1143
1143
  # Examples:
@@ -1720,7 +1720,7 @@ module HexaPDF
1720
1720
  # If the filename or the IO specifies a PDF file, the first page of this file is used to
1721
1721
  # create a form XObject which is then drawn.
1722
1722
  #
1723
- # The +at+ argument has to be an array containing two numbers specifying the bottom left
1723
+ # The +at+ argument has to be an array containing two numbers specifying the bottom-left
1724
1724
  # corner at which to draw the XObject.
1725
1725
  #
1726
1726
  # If +width+ and +height+ are specified, the drawn XObject will have exactly these
@@ -96,6 +96,7 @@ module HexaPDF
96
96
  draw_box, box = @frame.split(result)
97
97
  if draw_box
98
98
  @frame.draw(@canvas, result)
99
+ (box = draw_box; break) unless box
99
100
  elsif !@frame.find_next_region
100
101
  raise HexaPDF::Error, "Frame for canvas composer is full and box doesn't fit anymore"
101
102
  end
@@ -70,6 +70,9 @@ module HexaPDF
70
70
  # description::
71
71
  # A description of the file.
72
72
  #
73
+ # mime_type::
74
+ # The MIME type that should be set for embedded files (so only used if +embed+ is +true+).
75
+ #
73
76
  # embed::
74
77
  # When an IO object is given, it is always embedded and this option is ignored.
75
78
  #
@@ -77,7 +80,7 @@ module HexaPDF
77
80
  # only a reference to it is stored.
78
81
  #
79
82
  # See: HexaPDF::Type::FileSpecification
80
- def add(file_or_io, name: nil, description: nil, embed: true)
83
+ def add(file_or_io, name: nil, description: nil, mime_type: nil, embed: true)
81
84
  name ||= File.basename(file_or_io) if file_or_io.kind_of?(String)
82
85
  if name.nil?
83
86
  raise ArgumentError, "The name argument is mandatory when given an IO object"
@@ -86,7 +89,9 @@ module HexaPDF
86
89
  spec = @document.add({Type: :Filespec})
87
90
  spec.path = name
88
91
  spec[:Desc] = description if description
89
- spec.embed(file_or_io, name: name, register: true) if embed || !file_or_io.kind_of?(String)
92
+ if embed || !file_or_io.kind_of?(String)
93
+ spec.embed(file_or_io, name: name, mime_type: mime_type, register: true)
94
+ end
90
95
  spec
91
96
  end
92
97
 
@@ -175,9 +175,6 @@ module HexaPDF
175
175
 
176
176
  end
177
177
 
178
- # The mapping of style name (a Symbol) to Layout::Style instance.
179
- attr_reader :styles
180
-
181
178
  # Creates a new Layout object for the given PDF document.
182
179
  def initialize(document)
183
180
  @document = document
@@ -219,6 +216,21 @@ module HexaPDF
219
216
  style
220
217
  end
221
218
 
219
+ # :call-seq:
220
+ # layout.styles -> styles
221
+ # layout.styles(**mapping) -> styles
222
+ #
223
+ # Returns the mapping of style names to Layout::Style instances. If +mapping+ is provided,
224
+ # also defines the given styles using #style.
225
+ #
226
+ # The argument +mapping+ needs to be a hash mapping a style name (a Symbol) to style
227
+ # properties. The special key +:base+ can be used to define the base style. For details see
228
+ # #style.
229
+ def styles(**mapping)
230
+ mapping.each {|name, properties| style(name, **properties) } unless mapping.empty?
231
+ @styles
232
+ end
233
+
222
234
  # Creates an inline box for use together with text fragments.
223
235
  #
224
236
  # The +valign+ argument ist used to specify the vertical alignment of the box within the text
@@ -161,6 +161,7 @@ module HexaPDF
161
161
  @properties = PREDEFINED_PROPERTIES.transform_values(&:dup)
162
162
  @default_language = document.catalog[:Lang] || 'x-default'
163
163
  @metadata = Hash.new {|h, k| h[k] = {} }
164
+ @custom_metadata = []
164
165
  write_info_dict(true)
165
166
  write_metadata_stream(true)
166
167
  @document.register_listener(:complete_objects, &method(:write_metadata))
@@ -248,6 +249,16 @@ module HexaPDF
248
249
  end
249
250
  end
250
251
 
252
+ # Adds the given +data+ string as custom metadata to the XMP document.
253
+ #
254
+ # The +data+ string must contain a fully valid 'rdf:Description' element.
255
+ #
256
+ # Using this method allows adding metadata like PDF/A schema definitions for which there is no
257
+ # direct support by HexaPDF.
258
+ def custom_metadata(data)
259
+ @custom_metadata << data
260
+ end
261
+
251
262
  # :call-seq:
252
263
  # metadata.delete
253
264
  # metadata.delete(ns_prefix)
@@ -469,7 +480,7 @@ module HexaPDF
469
480
  <?xpacket begin="\u{FEFF}" id="#{SecureRandom.uuid.tr('-', '')}"?>
470
481
  <x:xmpmeta xmlns:x="adobe:ns:meta/">
471
482
  <rdf:RDF xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#">
472
- #{data}
483
+ #{data}#{@custom_metadata.empty? ? '' : "\n#{@custom_metadata.join("\n")}"}
473
484
  </rdf:RDF>
474
485
  </x:xmpmeta>
475
486
  <?xpacket end="r"?>
@@ -51,7 +51,7 @@ module HexaPDF
51
51
  attr_accessor :name
52
52
 
53
53
  # Character bounding box as array of four numbers, specifying the x- and y-coordinates of
54
- # the bottom left corner and the x- and y-coordinates of the top right corner.
54
+ # the bottom-left corner and the x- and y-coordinates of the top-right corner.
55
55
  attr_accessor :bbox
56
56
 
57
57
  end
@@ -63,7 +63,7 @@ module HexaPDF
63
63
  attr_accessor :weight
64
64
 
65
65
  # The font bounding box as array of four numbers, specifying the x- and y-coordinates of the
66
- # bottom left corner and the x- and y-coordinates of the top right corner.
66
+ # bottom-left corner and the x- and y-coordinates of the top-right corner.
67
67
  attr_accessor :bounding_box
68
68
 
69
69
  # The y-value of the top of the capital H (or 0 or nil if the font doesn't contain a capital