hexapdf 0.37.2 → 0.38.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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 7633d38801df5768a809a8491de34a33ff90069c1ea55eed29a75d0ccd4e5c67
4
- data.tar.gz: f74d51319f639d5e218e27cb264231e370943fa365b4b4c1ef9b0c47df123dfb
3
+ metadata.gz: b6df8bc8bdf189f60287c3dc0a0eccb1e1bca2a8b4310c1d5c2bb5d6661bae11
4
+ data.tar.gz: a28fc9d3319f596774bda74476fbc77fe40ba8e29a2dcd1e588c18f0928e8538
5
5
  SHA512:
6
- metadata.gz: cfc63a537dacfece92e4df2116362b8a270b9f291e1350604368820a8e2e02ebc2e47b462391dcd74cf9a16ab498d9b3b72ab1a8ff063eab816ed65636405379
7
- data.tar.gz: e90d2be37bc9150c9e040b06678fe98ea601c20dab39b75e689559418f043b63c0967784f0e5f0b0d11f1cce5ee10557d65a4e996099301bd27fb4962dcbe961
6
+ metadata.gz: 11a382eceaa702705fe15484314e8d8b21c26ce2bf50d082be5e4ae27749251562474bfe1f4d410e53008120b37b6f43b42b1f9ecaca0ce123a672a6868a0515
7
+ data.tar.gz: ef98043f0d6460bbfd4819ba34fd2fab4d9e7426b04d0005f1f65ae88d813ff92db6f9cb89cb3e88997c8b68dea7e7631ceb873f5091a6c1b8bd6aa72075ab32
data/CHANGELOG.md CHANGED
@@ -1,3 +1,26 @@
1
+ ## 0.38.0 - 2024-03-10
2
+
3
+ ### Added
4
+
5
+ * [HexaPDF::Task::PDFA] for creating PDF/A conforming PDF files
6
+ * [HexaPDF::Type::OutputIntent] for defining output intents
7
+ * [HexaPDF::Document::Metadata#delete] for deleting metadata properties
8
+ * PDF/A metadata properties definitions
9
+ * Added a /Name entry to the default optional content configuration dictionary
10
+ (needed by PDF/A)
11
+
12
+ ### Changed
13
+
14
+ * Default language for XMP metadata from English to 'x-default'
15
+ * [HexaPDF::Layout::ListBox] to use the style's font for drawing markers and to
16
+ fall back to Times and ZapfDingbats if necessary
17
+ * [HexaPDF::Document::Layout#table_box] to merge the `:cell` keys that define
18
+ the cell style instead of using the last one
19
+ * [HexaPDF::Document::Layout] style retrieval to fall back to using the font of
20
+ the `:base` style and only if that doesn't exist to 'Times'
21
+ * XMP metadata stream contents to satisfy more PDF/A validators
22
+
23
+
1
24
  ## 0.37.2 - 2024-02-27
2
25
 
3
26
  ### Fixed
data/README.md CHANGED
@@ -47,19 +47,19 @@ section](#License) for details.
47
47
  * [`hexapdf` binary][hp] for most common PDF manipulation tasks
48
48
 
49
49
 
50
- [canvas API]: https://hexapdf.gettalong.org/documentation/reference/api/HexaPDF/Content/Canvas.html
51
- [document composition engine]: https://hexapdf.gettalong.org/documentation/key-topics/document-layout.html
50
+ [canvas API]: https://hexapdf.gettalong.org/documentation/api/HexaPDF/Content/Canvas.html
51
+ [document composition engine]: https://hexapdf.gettalong.org/documentation/document-creation/document-layout.html
52
52
  [flowing text]: https://hexapdf.gettalong.org/examples/frame_text_flow.html
53
- [styles]: https://hexapdf.gettalong.org/documentation/reference/api/HexaPDF/Layout/Style/index.html
54
- [(un)ordered lists]: https://hexapdf.gettalong.org/documentation/reference/api/HexaPDF/Layout/ListBox.html
55
- [multi-column layout]: https://hexapdf.gettalong.org/documentation/reference/api/HexaPDF/Layout/ColumnBox.html
56
- [PDF forms]: https://hexapdf.gettalong.org/documentation/key-topics/forms.html
57
- [Document outline]: https://hexapdf.gettalong.org/documentation/reference/api/HexaPDF/Type/Outline.html
58
- [attaching files]: https://hexapdf.gettalong.org/documentation/reference/api/HexaPDF/Document/Files.html
59
- [Encryption]: https://hexapdf.gettalong.org/documentation/key-topics/encryption.html
60
- [Digital Signatures]: https://hexapdf.gettalong.org/documentation/key-topics/digital-signatures.html
53
+ [styles]: https://hexapdf.gettalong.org/documentation/api/HexaPDF/Layout/Style/index.html
54
+ [(un)ordered lists]: https://hexapdf.gettalong.org/documentation/api/HexaPDF/Layout/ListBox.html
55
+ [multi-column layout]: https://hexapdf.gettalong.org/documentation/api/HexaPDF/Layout/ColumnBox.html
56
+ [PDF forms]: https://hexapdf.gettalong.org/documentation/interactive-forms/index.html
57
+ [Document outline]: https://hexapdf.gettalong.org/documentation/outline/index.html
58
+ [attaching files]: https://hexapdf.gettalong.org/documentation/api/HexaPDF/Document/Files.html
59
+ [Encryption]: https://hexapdf.gettalong.org/documentation/encryption/index.html
60
+ [Digital Signatures]: https://hexapdf.gettalong.org/documentation/digital-signatures/index.html
61
61
  [File size optimization]: https://hexapdf.gettalong.org/documentation/benchmarks/optimization.html
62
- [hp]: https://hexapdf.gettalong.org/documentation/reference/hexapdf.1.html
62
+ [hp]: https://hexapdf.gettalong.org/documentation/hexapdf.1.html
63
63
 
64
64
 
65
65
  ## Usage
@@ -126,7 +126,7 @@ featureful API when it comes to creating content, for individual pages as well a
126
126
  If you want to migrate from Prawn to HexaPDF, there is the [migration guide] with detailed
127
127
  information and examples, comparing the Prawn API to HexaPDF's equivalents.
128
128
 
129
- [migration guide]: https://hexapdf.gettalong.org/documentation/howtos/migrating-from-prawn.html
129
+ [migration guide]: https://hexapdf.gettalong.org/documentation/document-creation/migrating-from-prawn.html
130
130
 
131
131
  Why use HexaPDF?
132
132
 
@@ -145,9 +145,10 @@ Why use HexaPDF?
145
145
  manipulating PDFs. This tool is intended to be a replacement for tools like `pdftk` and the
146
146
  various Poppler-based tools like `pdfinfo`, `pdfimages`, ...
147
147
 
148
- [Prawn]: http://prawnpdf.org
148
+ [Prawn]: https://prawnpdf.org
149
149
  [page canvas API]: https://hexapdf.gettalong.org/api/HexaPDF/Content/Canvas.html
150
150
 
151
+
151
152
  ## Development
152
153
 
153
154
  Clone the repository and then run `rake dev:setup`. This will install the needed Rubygem
@@ -178,6 +179,9 @@ Some included files have a different license:
178
179
  * The AES test vector files in `test/data/aes-test-vectors` have been created using the test vector
179
180
  file available from <http://csrc.nist.gov/groups/STM/cavp/block-ciphers.html#test-vectors>.
180
181
 
182
+ * The license of the file `data/hexapdf/sRGB2014.icc` is available in the
183
+ `data/hexapdf/sRGB2014.icc.LICENSE` file.
184
+
181
185
 
182
186
  ## Contributing
183
187
 
Binary file
@@ -0,0 +1,7 @@
1
+ Copyright (c) 2015 International Color Consortium
2
+
3
+ This profile is made available by the International Color Consortium,
4
+ and may be copied, distributed, embedded, made, used, and sold without
5
+ restriction. Altered versions of this profile shall have the original
6
+ identification and copyright information removed and shall not be
7
+ misrepresented as the original profile.
@@ -0,0 +1,89 @@
1
+ # # PDF/A Compliance
2
+ #
3
+ # This example shows how to create a PDF file that is PDF/A compliant.
4
+ #
5
+ # In this case we are creating a simple invoice, with multiple line
6
+ # items that break across the page boundary.
7
+ #
8
+ # Usage:
9
+ # : `ruby pdfa.rb`
10
+ #
11
+ require 'hexapdf'
12
+
13
+ HexaPDF::Composer.create('pdfa.pdf') do |composer|
14
+ composer.document.task(:pdfa)
15
+ composer.document.config['font.map'] = {
16
+ 'Lato' => {
17
+ none: '/usr/share/fonts/truetype/lato/Lato-Regular.ttf',
18
+ bold: '/usr/share/fonts/truetype/lato/Lato-Bold.ttf',
19
+ italic: '/usr/share/fonts/truetype/lato/Lato-Italic.ttf',
20
+ bold_italic: '/usr/share/fonts/truetype/lato/Lato-BoldItalic.ttf',
21
+ },
22
+ }
23
+
24
+ company = {
25
+ name: 'Sample Corp Limited',
26
+ address: ["Example Avenue 1", "12345 Runway"],
27
+ }
28
+
29
+ # 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', variant: :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', variant: :bold],
39
+ font_size: 8, padding: [0, 0, 8])
40
+ composer.style(:footer_text, font_size: 8, fill_color: "darkgrey")
41
+
42
+ # Top part
43
+ composer.box(:container, style: :top_box) do |container|
44
+ container.formatted_text([{text: company[:name], font: ['Lato', variant: :bold]},
45
+ " - " + company[:address].join(' - ')], style: :top)
46
+ end
47
+ composer.text("Mega Client\nSmall Lane 5\n67890 Noonestown", mask_mode: :box)
48
+ cells = [["Invoice number:", "2024/01"],
49
+ ["Invoice date", "2024-03-10"],
50
+ ["Service date:", "2024-02-01"]]
51
+ composer.table(cells, column_widths: [150, 80], style: {align: :right}) do |args|
52
+ args[] = {cell: {border: {width: 0}, padding: 2}, text_align: :right}
53
+ args[0..-1, 0] = {font: ['Lato', variant: :bold]}
54
+ end
55
+
56
+ # Middle part
57
+ composer.text("Invoice - 2024/01", style: :header)
58
+ composer.text("Thank you for your order. Following are the items you purchased:")
59
+
60
+ cells = [["Description", "Price", "Amount", "Total"]]
61
+ max = 40
62
+ 1.upto(max) do |index|
63
+ cells << ["Sample Item E.g. #{index}", "€ 250,00", index, "€ #{250 * index},00"]
64
+ end
65
+ cells << [nil, nil, nil, "€ #{250 * max * (max + 1) / 2},00"]
66
+ composer.table(cells, column_widths: [250, 80], style: :line_items) do |args|
67
+ args[] = {cell: {border: {width: 0}, padding: 8}, style: :line_item_cell}
68
+ args[0] = {cell: {background_color: "eee"}, font: ["Lato", variant: :bold]}
69
+ args[-1] = {cell: {background_color: "eee", border: {width: [2, 0, 0]}},
70
+ font: ["Lato", variant: :bold]}
71
+ args[0..-1, 1..-1] = {text_align: :right}
72
+ end
73
+
74
+ composer.text("Please transfer the total amount via SEPA transfer to the bank " \
75
+ "account below immediately after receiving the invoice - thank you.")
76
+
77
+ # Bottom part
78
+ l = composer.document.layout
79
+ cells = [
80
+ [l.text(company[:name], style: :footer_heading),
81
+ l.text(company[:address].join("\n"), style: :footer_text)],
82
+ [l.text('Contact', style: :footer_heading),
83
+ l.text("owner@samplecorp.com\nOwner: Me, Myself, And I", style: :footer_text)],
84
+ [l.text('Bank Account', style: :footer_heading),
85
+ l.text("Sample Corp Bank\nIBAN: SC01 2345 6789 0123 4567\nBIC: SACOZZB123",
86
+ style: :footer_text)],
87
+ ]
88
+ composer.table([cells], cell_style: {border: {width: 0}}, style: :footer)
89
+ end
@@ -568,6 +568,7 @@ module HexaPDF
568
568
  'task.map' => {
569
569
  optimize: 'HexaPDF::Task::Optimize',
570
570
  dereference: 'HexaPDF::Task::Dereference',
571
+ pdfa: 'HexaPDF::Task::PDFA',
571
572
  })
572
573
 
573
574
  # The global configuration object, providing the following options:
@@ -688,6 +689,8 @@ module HexaPDF
688
689
  XXCIDSystemInfo: 'HexaPDF::Type::CIDFont::CIDSystemInfo',
689
690
  Group: 'HexaPDF::Type::Form::Group',
690
691
  Metadata: 'HexaPDF::Type::Metadata',
692
+ OutputIntent: 'HexaPDF::Type::OutputIntent',
693
+ XXDestOutputProfileRef: 'HexaPDF::Type::OutputIntent::DestOutputProfileRef',
691
694
  },
692
695
  'object.subtype_map' => {
693
696
  nil => {
@@ -707,6 +710,9 @@ module HexaPDF
707
710
  Link: 'HexaPDF::Type::Annotations::Link',
708
711
  Widget: 'HexaPDF::Type::Annotations::Widget',
709
712
  XML: 'HexaPDF::Type::Metadata',
713
+ GTS_PDFX: 'HexaPDF::Type::OutputIntent',
714
+ GTS_PDFA1: 'HexaPDF::Type::OutputIntent',
715
+ ISO_PDFE1: 'HexaPDF::Type::OutputIntent',
710
716
  },
711
717
  XObject: {
712
718
  Image: 'HexaPDF::Type::Image',
@@ -738,6 +744,11 @@ module HexaPDF
738
744
  Ch: 'HexaPDF::Type::AcroForm::ChoiceField',
739
745
  Sig: 'HexaPDF::Type::AcroForm::SignatureField',
740
746
  },
747
+ OutputIntent: {
748
+ GTS_PDFX: 'HexaPDF::Type::OutputIntent',
749
+ GTS_PDFA1: 'HexaPDF::Type::OutputIntent',
750
+ ISO_PDFE1: 'HexaPDF::Type::OutputIntent',
751
+ },
741
752
  })
742
753
 
743
754
  end
@@ -486,10 +486,14 @@ module HexaPDF
486
486
 
487
487
  # Retrieves the merged keyword arguments for the cell in +row+ and +col+.
488
488
  #
489
- # Earlier defined arguments are overridden by later ones.
489
+ # Earlier defined arguments are overridden by later ones, except for the +:cell+ key which
490
+ # is merged.
490
491
  def retrieve_arguments_for(row, col)
491
492
  @argument_infos.each_with_object({}) do |arg_info, result|
492
493
  next unless arg_info.rows.cover?(row) && arg_info.cols.cover?(col)
494
+ if arg_info.args[:cell]
495
+ arg_info.args[:cell] = (result[:cell] || {}).merge(arg_info.args[:cell])
496
+ end
493
497
  result.update(arg_info.args)
494
498
  end
495
499
  end
@@ -635,15 +639,15 @@ module HexaPDF
635
639
  # If the +properties+ hash is not empty, the retrieved style is duplicated and the properties
636
640
  # hash is applied to it.
637
641
  #
638
- # Finally, a default font is set if necessary to ensure that the style object works in all
639
- # cases.
642
+ # Finally, a default font (the one from the :base style or otherwise 'Times') is set if
643
+ # necessary to ensure that the style object works in all cases.
640
644
  def retrieve_style(style, properties = nil)
641
645
  if style.kind_of?(Symbol) && !@styles.key?(style)
642
646
  raise HexaPDF::Error, "Style #{style} not defined"
643
647
  end
644
648
  style = HexaPDF::Layout::Style.create(@styles[style] || style || @styles[:base])
645
649
  style = style.dup.update(**properties) unless properties.nil? || properties.empty?
646
- style.font('Times') unless style.font?
650
+ style.font(@styles[:base].font? && @styles[:base].font || 'Times') unless style.font?
647
651
  unless style.font.respond_to?(:pdf_object)
648
652
  name, options = *style.font
649
653
  style.font(@document.fonts.add(name, **(options || {})))
@@ -80,6 +80,10 @@ module HexaPDF
80
80
  # String::
81
81
  # Maps to the XMP simple string value. Values need to be of type String.
82
82
  #
83
+ # Integer::
84
+ # Maps to the XMP integer core value type and gets formatted as string. Values need to be of
85
+ # type Integer.
86
+ #
83
87
  # Date::
84
88
  # Maps to the XMP simple string value, correctly formatted. Values need to be of type Time,
85
89
  # Date, or DateTime
@@ -123,6 +127,7 @@ module HexaPDF
123
127
  "pdf" => "http://ns.adobe.com/pdf/1.3/",
124
128
  "dc" => "http://purl.org/dc/elements/1.1/",
125
129
  "x" => "adobe:ns:meta/",
130
+ "pdfaid" => "http://www.aiim.org/pdfa/ns/id/",
126
131
  }.freeze
127
132
 
128
133
  # Contains a mapping of predefined XMP properties to their types, i.e. from namespace to
@@ -143,6 +148,10 @@ module HexaPDF
143
148
  'description' => 'LanguageArray',
144
149
  'title' => 'LanguageArray',
145
150
  }.freeze,
151
+ "http://www.aiim.org/pdfa/ns/id/" => {
152
+ 'part' => 'Integer',
153
+ 'conformance' => 'String',
154
+ }.freeze,
146
155
  }.freeze
147
156
 
148
157
  # Creates a new Metadata object for the given PDF document.
@@ -150,7 +159,7 @@ module HexaPDF
150
159
  @document = document
151
160
  @namespaces = PREDEFINED_NAMESPACES.dup
152
161
  @properties = PREDEFINED_PROPERTIES.transform_values(&:dup)
153
- @default_language = document.catalog[:Lang] || 'en'
162
+ @default_language = document.catalog[:Lang] || 'x-default'
154
163
  @metadata = Hash.new {|h, k| h[k] = {} }
155
164
  write_info_dict(true)
156
165
  write_metadata_stream(true)
@@ -166,7 +175,7 @@ module HexaPDF
166
175
  # is given. Otherwise sets the default language to the given language.
167
176
  #
168
177
  # The initial default lanuage is taken from the document catalog's /Lang entry. If that is not
169
- # set, the default language is assumed to be English ('en').
178
+ # set, the default language is assumed to be default language ('x-default').
170
179
  def default_language(value = :UNSET)
171
180
  if value == :UNSET
172
181
  @default_language
@@ -213,8 +222,8 @@ module HexaPDF
213
222
 
214
223
  # Registers the +property+ for the namespace specified via +prefix+ as the given +type+.
215
224
  #
216
- # The argument +type+ has to be one of the following: 'String', 'Date', 'URI', 'Boolean',
217
- # 'OrderedArray', 'UnorderedArray', or 'LanguageArray'.
225
+ # The argument +type+ has to be one of the following: 'String', 'Integer', 'Date', 'URI',
226
+ # 'Boolean', 'OrderedArray', 'UnorderedArray', or 'LanguageArray'.
218
227
  def register_property_type(prefix, property, type)
219
228
  (@properties[namespace(prefix)] ||= {})[property] = type
220
229
  end
@@ -240,13 +249,31 @@ module HexaPDF
240
249
  end
241
250
 
242
251
  # :call-seq:
243
- # metadata.title -> title or nil
244
- # metadata.title(value -> value
252
+ # metadata.delete
253
+ # metadata.delete(ns_prefix)
254
+ # metadata.delete(ns_prefix, name)
255
+ #
256
+ # Deletes either all metadata properties, only the ones from a specific namespace, or a
257
+ # specific one.
258
+ def delete(ns = nil, property = nil)
259
+ if ns.nil? && property.nil?
260
+ @metadata.clear
261
+ elsif property.nil?
262
+ @metadata.delete(namespace(ns))
263
+ else
264
+ @metadata[namespace(ns)].delete(property)
265
+ end
266
+ end
267
+
268
+ # :call-seq:
269
+ # metadata.title -> title or nil
270
+ # metadata.title(value) -> value
245
271
  #
246
272
  # Returns the document's title if no argument is given. Otherwise sets the document's title to
247
273
  # the given value.
248
274
  #
249
- # The language for the title is specified via #default_language.
275
+ # If the +value+ is a LocalizedString, the language for the title is taken from it. Otherwise
276
+ # the language specified via #default_language is used.
250
277
  #
251
278
  # The value +nil+ is returned if the property is not set. And by using +nil+ as +value+ the
252
279
  # property is deleted from the metadata.
@@ -278,7 +305,8 @@ module HexaPDF
278
305
  # Returns the subject of the document if no argument is given. Otherwise sets the subject to
279
306
  # the given value.
280
307
  #
281
- # The language for the subject is specified via #default_language.
308
+ # If the +value+ is a LocalizedString, the language for the subject is taken from it.
309
+ # Otherwise the language specified via #default_language is used.
282
310
  #
283
311
  # The value +nil+ is returned if the property ist not set. And by using +nil+ as +value+ the
284
312
  # property is deleted from the metadata.
@@ -406,23 +434,30 @@ module HexaPDF
406
434
  ns_xmp = namespace('xmp')
407
435
  ns_pdf = namespace('pdf')
408
436
 
437
+ producer("HexaPDF version #{HexaPDF::VERSION}")
438
+
409
439
  if write_info_dict?
410
440
  info_dict = @document.trailer.info
411
441
  info_dict[:Title] = Array(@metadata[ns_dc]['title']).first
412
- info_dict[:Author] = Array(@metadata[ns_dc]['creator']).join(', ')
442
+ if @metadata[ns_dc].key?('creator')
443
+ info_dict[:Author] = Array(@metadata[ns_dc]['creator']).join(', ')
444
+ end
413
445
  info_dict[:Subject] = Array(@metadata[ns_dc]['description']).first
414
446
  info_dict[:Creator] = @metadata[ns_xmp]['CreatorTool']
415
447
  info_dict[:CreationDate] = @metadata[ns_xmp]['CreateDate']
416
448
  info_dict[:ModDate] = @metadata[ns_xmp]['ModifyDate']
417
449
  info_dict[:Keywords] = @metadata[ns_pdf]['Keywords']
418
450
  info_dict[:Producer] = @metadata[ns_pdf]['Producer']
419
- info_dict[:Trapped] = @metadata[ns_pdf]['Trapped'] ? :True : :False
451
+ if @metadata[ns_pdf].key?('Trapped')
452
+ info_dict[:Trapped] = @metadata[ns_pdf]['Trapped'] ? :True : :False
453
+ end
420
454
  end
421
455
 
422
456
  if write_metadata_stream?
423
457
  descriptions = @metadata.map do |namespace, values|
458
+ next if values.empty?
424
459
  xmp_description(@namespaces.key(namespace), values)
425
- end.join("\n")
460
+ end.compact.join("\n")
426
461
  obj = @document.catalog[:Metadata] ||= @document.add({Type: :Metadata, Subtype: :XML})
427
462
  obj.stream = xmp_packet(descriptions)
428
463
  end
@@ -432,9 +467,11 @@ module HexaPDF
432
467
  def xmp_packet(data)
433
468
  <<~XMP
434
469
  <?xpacket begin="\u{FEFF}" id="#{SecureRandom.uuid.tr('-', '')}"?>
470
+ <x:xmpmeta xmlns:x="adobe:ns:meta/">
435
471
  <rdf:RDF xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#">
436
472
  #{data}
437
473
  </rdf:RDF>
474
+ </x:xmpmeta>
438
475
  <?xpacket end="r"?>
439
476
  XMP
440
477
  end
@@ -444,8 +481,8 @@ module HexaPDF
444
481
  values = values.map do |name, value|
445
482
  str = +"<#{ns_prefix}:#{name}"
446
483
  case (property_type = @properties[namespace(ns_prefix)][name])
447
- when 'String'
448
- str << ">#{xmp_escape(value)}</#{ns_prefix}:#{name}>"
484
+ when 'String', 'Integer'
485
+ str << ">#{xmp_escape(value.to_s)}</#{ns_prefix}:#{name}>"
449
486
  when 'Date'
450
487
  str << ">#{xmp_date(value)}</#{ns_prefix}:#{name}>"
451
488
  when 'URI'
@@ -325,27 +325,30 @@ module HexaPDF
325
325
  return @marker_type.call(document, self, index) if @marker_type.kind_of?(Proc)
326
326
  return @item_marker_box if defined?(@item_marker_box)
327
327
 
328
+ marker_style = {
329
+ font: style.font? ? style.font : document.fonts.add("Times"),
330
+ font_size: style.font_size || 10, fill_color: style.fill_color
331
+ }
328
332
  fragment = case @marker_type
329
333
  when :disc
330
- TextFragment.create("•", font: document.fonts.add("Times"),
331
- font_size: style.font_size, fill_color: style.fill_color)
334
+ TextFragment.create("•", marker_style)
332
335
  when :circle
333
- TextFragment.create("❍", font: document.fonts.add("ZapfDingbats"),
336
+ unless marker_style[:font].decode_codepoint("❍".ord).valid?
337
+ marker_style[:font] = document.fonts.add("ZapfDingbats")
338
+ end
339
+ TextFragment.create("❍", **marker_style,
334
340
  font_size: style.font_size / 2.0,
335
- fill_color: style.fill_color,
336
341
  text_rise: -style.font_size / 1.8)
337
342
  when :square
338
- TextFragment.create("■", font: document.fonts.add("ZapfDingbats"),
343
+ unless marker_style[:font].decode_codepoint("■".ord).valid?
344
+ marker_style[:font] = document.fonts.add("ZapfDingbats")
345
+ end
346
+ TextFragment.create("■", **marker_style,
339
347
  font_size: style.font_size / 2.0,
340
- fill_color: style.fill_color,
341
348
  text_rise: -style.font_size / 1.8)
342
349
  when :decimal
343
350
  text = (@start_number + index).to_s << "."
344
- decimal_style = {
345
- font: (style.font? ? style.font : document.fonts.add("Times")),
346
- font_size: style.font_size || 10, fill_color: style.fill_color
347
- }
348
- TextFragment.create(text, decimal_style)
351
+ TextFragment.create(text, marker_style)
349
352
  else
350
353
  raise HexaPDF::Error, "Unknown list marker type #{@marker_type.inspect}"
351
354
  end
@@ -0,0 +1,87 @@
1
+ # -*- encoding: utf-8; frozen_string_literal: true -*-
2
+ #
3
+ #--
4
+ # This file is part of HexaPDF.
5
+ #
6
+ # HexaPDF - A Versatile PDF Creation and Manipulation Library For Ruby
7
+ # Copyright (C) 2014-2024 Thomas Leitner
8
+ #
9
+ # HexaPDF is free software: you can redistribute it and/or modify it
10
+ # under the terms of the GNU Affero General Public License version 3 as
11
+ # published by the Free Software Foundation with the addition of the
12
+ # following permission added to Section 15 as permitted in Section 7(a):
13
+ # FOR ANY PART OF THE COVERED WORK IN WHICH THE COPYRIGHT IS OWNED BY
14
+ # THOMAS LEITNER, THOMAS LEITNER DISCLAIMS THE WARRANTY OF NON
15
+ # INFRINGEMENT OF THIRD PARTY RIGHTS.
16
+ #
17
+ # HexaPDF is distributed in the hope that it will be useful, but WITHOUT
18
+ # ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
19
+ # FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public
20
+ # License for more details.
21
+ #
22
+ # You should have received a copy of the GNU Affero General Public License
23
+ # along with HexaPDF. If not, see <http://www.gnu.org/licenses/>.
24
+ #
25
+ # The interactive user interfaces in modified source and object code
26
+ # versions of HexaPDF must display Appropriate Legal Notices, as required
27
+ # under Section 5 of the GNU Affero General Public License version 3.
28
+ #
29
+ # In accordance with Section 7(b) of the GNU Affero General Public
30
+ # License, a covered work must retain the producer line in every PDF that
31
+ # is created or manipulated using HexaPDF.
32
+ #
33
+ # If the GNU Affero General Public License doesn't fit your need,
34
+ # commercial licenses are available at <https://gettalong.at/hexapdf/>.
35
+ #++
36
+
37
+ require 'set'
38
+ require 'hexapdf/serializer'
39
+ require 'hexapdf/content/parser'
40
+ require 'hexapdf/content/operator'
41
+ require 'hexapdf/type/xref_stream'
42
+ require 'hexapdf/type/object_stream'
43
+
44
+ module HexaPDF
45
+ module Task
46
+
47
+ # Task for creating a PDF/A compliant document.
48
+ #
49
+ # It automatically
50
+ #
51
+ # * prevents the Standard 14 PDF fonts to be used.
52
+ # * adds an appropriate output intent if none is set.
53
+ # * adds the necessary PDF/A metadata properties.
54
+ module PDFA
55
+
56
+ # Performs the necessary tasks to make the document PDF/A compatible.
57
+ #
58
+ # +level+::
59
+ # Specifies the PDF/A conformance level that should be used. Can be one of the following
60
+ # strings: 2b, 2u, 3b, 3u.
61
+ def self.call(doc, level: '3u')
62
+ unless level.match?(/\A[23][bu]\z/)
63
+ raise ArgumentError, "The given PDF/A conformance level '#{level}' is not supported"
64
+ end
65
+ doc.config['font_loader'].delete('HexaPDF::FontLoader::Standard14')
66
+ doc.register_listener(:complete_objects) do
67
+ part, conformance = level.chars
68
+ doc.metadata.property('pdfaid', 'part', part)
69
+ doc.metadata.property('pdfaid', 'conformance', conformance.upcase)
70
+ add_srgb_icc_output_intent(doc) unless doc.catalog.key?(:OutputIntents)
71
+ end
72
+ end
73
+
74
+ SRGB_ICC = 'sRGB2014.icc' # :nodoc:
75
+
76
+ def self.add_srgb_icc_output_intent(doc) # :nodoc:
77
+ icc = doc.add({N: 3}, stream: File.binread(File.join(HexaPDF.data_dir, SRGB_ICC)))
78
+ doc.catalog[:OutputIntents] = [
79
+ doc.add({S: :GTS_PDFA1, OutputConditionIdentifier: SRGB_ICC, Info: SRGB_ICC,
80
+ RegistryName: 'https://www.color.org', DestOutputProfile: icc}),
81
+ ]
82
+ end
83
+
84
+ end
85
+
86
+ end
87
+ end
data/lib/hexapdf/task.rb CHANGED
@@ -64,6 +64,7 @@ module HexaPDF
64
64
 
65
65
  autoload(:Optimize, 'hexapdf/task/optimize')
66
66
  autoload(:Dereference, 'hexapdf/task/dereference')
67
+ autoload(:PDFA, 'hexapdf/task/pdfa')
67
68
 
68
69
  end
69
70
 
@@ -136,7 +136,7 @@ module HexaPDF
136
136
  if hash
137
137
  self[:D] = hash
138
138
  else
139
- self[:D] ||= {Creator: 'HexaPDF'}
139
+ self[:D] ||= {Name: 'Default', Creator: 'HexaPDF'}
140
140
  end
141
141
  self[:D]
142
142
  end
@@ -146,7 +146,7 @@ module HexaPDF
146
146
  def perform_validation(&block) # :nodoc:
147
147
  unless key?(:D)
148
148
  yield('The OptionalContentProperties dictionary needs a default configuration', true)
149
- self[:D] = {Creator: 'HexaPDF'}
149
+ self[:D] = {Name: 'Default', Creator: 'HexaPDF'}
150
150
  end
151
151
  super
152
152
  end
@@ -0,0 +1,85 @@
1
+ # -*- encoding: utf-8; frozen_string_literal: true -*-
2
+ #
3
+ #--
4
+ # This file is part of HexaPDF.
5
+ #
6
+ # HexaPDF - A Versatile PDF Creation and Manipulation Library For Ruby
7
+ # Copyright (C) 2014-2024 Thomas Leitner
8
+ #
9
+ # HexaPDF is free software: you can redistribute it and/or modify it
10
+ # under the terms of the GNU Affero General Public License version 3 as
11
+ # published by the Free Software Foundation with the addition of the
12
+ # following permission added to Section 15 as permitted in Section 7(a):
13
+ # FOR ANY PART OF THE COVERED WORK IN WHICH THE COPYRIGHT IS OWNED BY
14
+ # THOMAS LEITNER, THOMAS LEITNER DISCLAIMS THE WARRANTY OF NON
15
+ # INFRINGEMENT OF THIRD PARTY RIGHTS.
16
+ #
17
+ # HexaPDF is distributed in the hope that it will be useful, but WITHOUT
18
+ # ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
19
+ # FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public
20
+ # License for more details.
21
+ #
22
+ # You should have received a copy of the GNU Affero General Public License
23
+ # along with HexaPDF. If not, see <http://www.gnu.org/licenses/>.
24
+ #
25
+ # The interactive user interfaces in modified source and object code
26
+ # versions of HexaPDF must display Appropriate Legal Notices, as required
27
+ # under Section 5 of the GNU Affero General Public License version 3.
28
+ #
29
+ # In accordance with Section 7(b) of the GNU Affero General Public
30
+ # License, a covered work must retain the producer line in every PDF that
31
+ # is created or manipulated using HexaPDF.
32
+ #
33
+ # If the GNU Affero General Public License doesn't fit your need,
34
+ # commercial licenses are available at <https://gettalong.at/hexapdf/>.
35
+ #++
36
+
37
+ require 'hexapdf/dictionary'
38
+
39
+ module HexaPDF
40
+ module Type
41
+
42
+ # Represents an output intent dictionary.
43
+ #
44
+ # Such a dictionary may be referenced from the catalog's /OutputIntents entry or from the
45
+ # /OutputIntents entry of a page object.
46
+ #
47
+ # See: PDF2.0 s14.11.5, Catalog
48
+ class OutputIntent < Dictionary
49
+
50
+ # Represents a destination output profile reference dictionary.
51
+ #
52
+ # Such a dictionary is referenced from the /DestOutputProfileRef entry of an OutputIntent
53
+ # dictionary.
54
+ #
55
+ # See: PDF2.0 s14.11.5
56
+ class DestOutputProfileRef < Dictionary
57
+
58
+ define_type :XXDestOutputProfileRef
59
+
60
+ define_field :CheckSum, type: String, version: "2.0"
61
+ define_field :ColorantTable, type: PDFArray, version: "2.0"
62
+ define_field :ICCVersion, type: String, version: "2.0"
63
+ define_field :ProfileCS, type: String, version: "2.0"
64
+ define_field :ProfileName, type: String, version: "2.0"
65
+ define_field :URLs, type: PDFArray, version: "2.0"
66
+
67
+ end
68
+
69
+ define_type :OutputIntent
70
+
71
+ define_field :Type, type: Symbol, required: false, default: type
72
+ define_field :S, type: Symbol, required: true
73
+ define_field :OutputCondition, type: String
74
+ define_field :OutputConditionIdentifier, type: String, required: true
75
+ define_field :RegistryName, type: String
76
+ define_field :Info, type: String
77
+ define_field :DestOutputProfile, type: Stream
78
+ define_field :DestOutputProfileRef, type: :XXDestOutputProfileRef, version: "2.0"
79
+ define_field :MixingHints, type: Dictionary, version: "2.0"
80
+ define_field :SpectralData, type: Dictionary, version: "2.0"
81
+
82
+ end
83
+
84
+ end
85
+ end
data/lib/hexapdf/type.rb CHANGED
@@ -81,6 +81,7 @@ module HexaPDF
81
81
  autoload(:OptionalContentProperties, 'hexapdf/type/optional_content_properties')
82
82
  autoload(:OptionalContentConfiguration, 'hexapdf/type/optional_content_configuration')
83
83
  autoload(:Metadata, 'hexapdf/type/metadata')
84
+ autoload(:OutputIntent, 'hexapdf/type/output_intent')
84
85
 
85
86
  end
86
87
 
@@ -37,6 +37,6 @@
37
37
  module HexaPDF
38
38
 
39
39
  # The version of HexaPDF.
40
- VERSION = '0.37.2'
40
+ VERSION = '0.38.0'
41
41
 
42
42
  end
@@ -104,6 +104,13 @@ describe HexaPDF::Document::Layout::CellArgumentCollector do
104
104
  @args[5, 6] = {e: :f}
105
105
  assert_equal({key: :value, a: :c, e: :f}, @args.retrieve_arguments_for(5, 6))
106
106
  end
107
+
108
+ it "deep merges the :cell keys" do
109
+ @args[] = {cell: {a: :b, c: :d}}
110
+ @args[3..7] = {cell: {a: :y, e: :f}}
111
+ @args[5, 6] = {cell: {a: :z}}
112
+ assert_equal({cell: {a: :z, c: :d, e: :f}}, @args.retrieve_arguments_for(5, 6))
113
+ end
107
114
  end
108
115
  end
109
116
 
@@ -229,6 +236,12 @@ describe HexaPDF::Document::Layout do
229
236
  assert_equal(20, box.style.font_size)
230
237
 
231
238
  box = @layout.text_box("Test", style: {font_size: 20})
239
+ assert_same(@doc.fonts.add("Times"), box.style.font)
240
+ assert_equal(20, box.style.font_size)
241
+
242
+ @layout.style(:base, font: ['Times', {variant: :bold}])
243
+ box = @layout.text_box("Test", style: {font_size: 20})
244
+ assert_same(@doc.fonts.add("Times", variant: :bold), box.style.font)
232
245
  assert_equal(20, box.style.font_size)
233
246
 
234
247
  @layout.style(:named, font_size: 20)
@@ -27,8 +27,8 @@ describe HexaPDF::Document::Metadata do
27
27
  assert_equal("de", HexaPDF::Document::Metadata.new(@doc).default_language)
28
28
  end
29
29
 
30
- it "falls back to English if the document doesn't have a default language set" do
31
- assert_equal('en', @metadata.default_language)
30
+ it "falls back to the default language if the document doesn't have a default language set" do
31
+ assert_equal('x-default', @metadata.default_language)
32
32
  end
33
33
 
34
34
  it "allows changing the default language" do
@@ -80,6 +80,25 @@ describe HexaPDF::Document::Metadata do
80
80
  refute(@metadata.instance_variable_get(:@metadata)[@metadata.namespace('dc')].key?('title'))
81
81
  end
82
82
 
83
+ describe "delete" do
84
+ it "deletes all properties" do
85
+ @metadata.delete
86
+ assert(@metadata.instance_variable_get(:@metadata).empty?)
87
+ end
88
+
89
+ it "deletes all properties of a single namespace" do
90
+ @metadata.creator('Test')
91
+ @metadata.delete('dc')
92
+ assert_equal('Test', @metadata.creator)
93
+ refute(@metadata.instance_variable_get(:@metadata).key?(@metadata.namespace('dc')))
94
+ end
95
+
96
+ it "deletes a specific property" do
97
+ @metadata.delete('dc', 'title')
98
+ assert_nil(@metadata.title)
99
+ end
100
+ end
101
+
83
102
  it "allows reading and setting all info dictionary properties" do
84
103
  [['title', 'dc', 'title'], ['author', 'dc', 'creator'], ['subject', 'dc', 'description'],
85
104
  ['keywords', 'pdf', 'Keywords'], ['creator', 'xmp', 'CreatorTool'],
@@ -120,6 +139,17 @@ describe HexaPDF::Document::Metadata do
120
139
  assert_equal(:True, info[:Trapped])
121
140
  end
122
141
 
142
+ it "omits values in the info dictionary that are not set" do
143
+ @metadata.delete('pdf', 'Trapped')
144
+ @metadata.delete('dc', 'title')
145
+ @metadata.delete('dc', 'creator')
146
+ @doc.write(StringIO.new, update_fields: false)
147
+ info = @doc.trailer.info
148
+ refute(info.key?(:Title))
149
+ refute(info.key?(:Author))
150
+ refute(info.key?(:Trapped))
151
+ end
152
+
123
153
  it "uses a correctly updated modification date if set so by Document#write" do
124
154
  info = @doc.trailer.info
125
155
  sleep(0.1)
@@ -140,6 +170,23 @@ describe HexaPDF::Document::Metadata do
140
170
  assert_equal('Subject', info[:Subject])
141
171
  end
142
172
 
173
+ it "omits rdf:Description elements without values" do
174
+ @metadata.delete
175
+ @doc.write(StringIO.new, update_fields: false)
176
+ metadata = <<~XMP
177
+ <?xpacket begin="" id=""?>
178
+ <x:xmpmeta xmlns:x="adobe:ns:meta/">
179
+ <rdf:RDF xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#">
180
+ <rdf:Description rdf:about="" xmlns:pdf="http://ns.adobe.com/pdf/1.3/">
181
+ <pdf:Producer>HexaPDF version #{HexaPDF::VERSION}</pdf:Producer>
182
+ </rdf:Description>
183
+ </rdf:RDF>
184
+ </x:xmpmeta>
185
+ <?xpacket end="r"?>
186
+ XMP
187
+ assert_equal(metadata, @doc.catalog[:Metadata].stream.sub(/(?<=id=")\w+/, ''))
188
+ end
189
+
143
190
  it "writes the XMP metadata" do
144
191
  title = HexaPDF::Document::Metadata::LocalizedString.new('Der Titel')
145
192
  title.language = 'de'
@@ -147,13 +194,16 @@ describe HexaPDF::Document::Metadata do
147
194
  @metadata.author(['Author 1', 'Author 2'])
148
195
  @metadata.register_property_type('dc', 'other', 'URI')
149
196
  @metadata.property('dc', 'other', 'https://test.org/example')
197
+ @metadata.property('pdfaid', 'part', 3)
198
+ @metadata.property('pdfaid', 'conformance', 'b')
150
199
  @doc.write(StringIO.new, update_fields: false)
151
200
  metadata = <<~XMP
152
201
  <?xpacket begin="" id=""?>
202
+ <x:xmpmeta xmlns:x="adobe:ns:meta/">
153
203
  <rdf:RDF xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#">
154
204
  <rdf:Description rdf:about="" xmlns:dc="http://purl.org/dc/elements/1.1/">
155
205
  <dc:title><rdf:Alt>
156
- <rdf:li xml:lang="en">Title</rdf:li>
206
+ <rdf:li xml:lang="x-default">Title</rdf:li>
157
207
  <rdf:li xml:lang="de">Der Titel</rdf:li>
158
208
  </rdf:Alt></dc:title>
159
209
  <dc:creator><rdf:Seq>
@@ -161,13 +211,13 @@ describe HexaPDF::Document::Metadata do
161
211
  <rdf:li>Author 2</rdf:li>
162
212
  </rdf:Seq></dc:creator>
163
213
  <dc:description><rdf:Alt>
164
- <rdf:li xml:lang="en">Subject</rdf:li>
214
+ <rdf:li xml:lang="x-default">Subject</rdf:li>
165
215
  </rdf:Alt></dc:description>
166
216
  <dc:other rdf:resource="https://test.org/example" />
167
217
  </rdf:Description>
168
218
  <rdf:Description rdf:about="" xmlns:pdf="http://ns.adobe.com/pdf/1.3/">
169
219
  <pdf:Keywords>Keywords</pdf:Keywords>
170
- <pdf:Producer>Producer</pdf:Producer>
220
+ <pdf:Producer>HexaPDF version #{HexaPDF::VERSION}</pdf:Producer>
171
221
  <pdf:Trapped>True</pdf:Trapped>
172
222
  </rdf:Description>
173
223
  <rdf:Description rdf:about="" xmlns:xmp="http://ns.adobe.com/xap/1.0/">
@@ -175,7 +225,12 @@ describe HexaPDF::Document::Metadata do
175
225
  <xmp:CreateDate>#{@metadata.send(:xmp_date, @time)}</xmp:CreateDate>
176
226
  <xmp:ModifyDate>#{@metadata.send(:xmp_date, @time)}</xmp:ModifyDate>
177
227
  </rdf:Description>
228
+ <rdf:Description rdf:about="" xmlns:pdfaid="http://www.aiim.org/pdfa/ns/id/">
229
+ <pdfaid:part>3</pdfaid:part>
230
+ <pdfaid:conformance>b</pdfaid:conformance>
231
+ </rdf:Description>
178
232
  </rdf:RDF>
233
+ </x:xmpmeta>
179
234
  <?xpacket end="r"?>
180
235
  XMP
181
236
  assert_equal(metadata, @doc.catalog[:Metadata].stream.sub(/(?<=id=")\w+/, ''))
@@ -143,6 +143,7 @@ describe HexaPDF::Layout::ListBox do
143
143
  @canvas = @page.canvas
144
144
  draw_block = lambda {|canvas, box| }
145
145
  @fixed_size_boxes = 5.times.map { HexaPDF::Layout::Box.new(width: 20, height: 10, &draw_block) }
146
+ @helvetica = @doc.fonts.add('Helvetica')
146
147
  end
147
148
 
148
149
  it "draws the result" do
@@ -295,5 +296,24 @@ describe HexaPDF::Layout::ListBox do
295
296
  ]
296
297
  assert_operators(@canvas.contents, operators)
297
298
  end
299
+
300
+ it "uses the font set on the list box for the marker" do
301
+ box = create_box(children: @fixed_size_boxes[0, 1],
302
+ style: {font: @helvetica, font_size: 12})
303
+ box.fit(100, 100, @frame)
304
+ box.draw(@canvas, 0, 100 - box.height)
305
+ assert_operators(@canvas.contents, [:set_font_and_size, [:F1, 12]], range: 1)
306
+ assert_equal(:Helvetica, @canvas.resources.font(:F1)[:BaseFont])
307
+ end
308
+
309
+ it "falls back to ZapfDingbats if the set font doesn't contain the necessary symbol" do
310
+ box = create_box(children: @fixed_size_boxes[0, 1], marker_type: :circle,
311
+ style: {font: @helvetica})
312
+ box.fit(100, 100, @frame)
313
+ box.draw(@canvas, 0, 100 - box.height)
314
+ assert_operators(@canvas.contents, [:set_font_and_size, [:F1, 5]], range: 1)
315
+ assert_equal(:ZapfDingbats, @canvas.resources.font(:F1)[:BaseFont])
316
+ end
317
+
298
318
  end
299
319
  end
@@ -0,0 +1,41 @@
1
+ # -*- encoding: utf-8 -*-
2
+
3
+ require 'stringio'
4
+ require 'test_helper'
5
+ require 'hexapdf/document'
6
+
7
+ describe HexaPDF::Task::PDFA do
8
+ before do
9
+ @doc = HexaPDF::Document.new
10
+ end
11
+
12
+ it "fails if the given PDF/A level is invalid" do
13
+ assert_raises(ArgumentError) { @doc.task(:pdfa, level: '1a') }
14
+ assert_raises(ArgumentError) { @doc.task(:pdfa, level: '2a') }
15
+ assert_raises(ArgumentError) { @doc.task(:pdfa, level: '3a') }
16
+ assert_raises(ArgumentError) { @doc.task(:pdfa, level: '4e') }
17
+ assert_raises(ArgumentError) { @doc.task(:pdfa, level: 'something') }
18
+ end
19
+
20
+ it "removes the standard 14 PDF font loader" do
21
+ @doc.task(:pdfa)
22
+ assert_raises(HexaPDF::Error) { @doc.fonts.add('Helvetia') }
23
+ end
24
+
25
+ it "adds the necessary XMP metadata entries before the document is written" do
26
+ @doc.task(:pdfa, level: '3b')
27
+ @doc.write(StringIO.new)
28
+ assert_equal('3', @doc.metadata.property('pdfaid', 'part'))
29
+ assert_equal('B', @doc.metadata.property('pdfaid', 'conformance'))
30
+ end
31
+
32
+ it "adds an RGB output intent before the document is written" do
33
+ @doc.task(:pdfa)
34
+ @doc.write(StringIO.new)
35
+ oi = @doc.catalog[:OutputIntents].first
36
+ assert_equal(:GTS_PDFA1, oi[:S])
37
+ assert_equal('sRGB2014.icc', oi[:OutputConditionIdentifier])
38
+ assert_equal('sRGB2014.icc', oi[:Info])
39
+ assert_kind_of(HexaPDF::Stream, oi[:DestOutputProfile])
40
+ end
41
+ end
@@ -40,7 +40,7 @@ describe HexaPDF::Writer do
40
40
  219
41
41
  %%EOF
42
42
  3 0 obj
43
- <</Producer(HexaPDF version 0.37.2)>>
43
+ <</Producer(HexaPDF version #{HexaPDF::VERSION})>>
44
44
  endobj
45
45
  xref
46
46
  3 1
@@ -72,7 +72,7 @@ describe HexaPDF::Writer do
72
72
  141
73
73
  %%EOF
74
74
  6 0 obj
75
- <</Producer(HexaPDF version 0.37.2)>>
75
+ <</Producer(HexaPDF version #{HexaPDF::VERSION})>>
76
76
  endobj
77
77
  2 0 obj
78
78
  <</Length 10>>stream
@@ -85,7 +85,7 @@ describe HexaPDF::Type::OptionalContentProperties do
85
85
 
86
86
  it "sets and returns a default configuration dictionary if none is set" do
87
87
  @oc.delete(:D)
88
- assert_equal({Creator: 'HexaPDF'}, @oc.default_configuration.value)
88
+ assert_equal({Name: 'Default', Creator: 'HexaPDF'}, @oc.default_configuration.value)
89
89
  end
90
90
 
91
91
  it "sets the default configuration dictionary to the given value" do
@@ -103,7 +103,7 @@ describe HexaPDF::Type::OptionalContentProperties do
103
103
  refute(@oc.validate(auto_correct: false))
104
104
  refute(@oc.key?(:D))
105
105
  assert(@oc.validate(auto_correct: true))
106
- assert_equal({Creator: 'HexaPDF'}, @oc[:D].value)
106
+ assert_equal({Name: 'Default', Creator: 'HexaPDF'}, @oc[:D].value)
107
107
  end
108
108
  end
109
109
  end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: hexapdf
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.37.2
4
+ version: 0.38.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Thomas Leitner
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2024-02-27 00:00:00.000000000 Z
11
+ date: 2024-03-10 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: cmdparse
@@ -274,6 +274,8 @@ files:
274
274
  - data/hexapdf/cmap/V
275
275
  - data/hexapdf/encoding/glyphlist.txt
276
276
  - data/hexapdf/encoding/zapfdingbats.txt
277
+ - data/hexapdf/sRGB2014.icc
278
+ - data/hexapdf/sRGB2014.icc.LICENSE
277
279
  - examples/001-hello_world.rb
278
280
  - examples/002-graphics.rb
279
281
  - examples/003-arcs.rb
@@ -303,6 +305,7 @@ files:
303
305
  - examples/027-composer_optional_content.rb
304
306
  - examples/028-frame_mask_mode.rb
305
307
  - examples/029-composer_fallback_fonts.rb
308
+ - examples/030-pdfa.rb
306
309
  - examples/emoji-smile.png
307
310
  - examples/emoji-wink.png
308
311
  - examples/machupicchu.jpg
@@ -464,6 +467,7 @@ files:
464
467
  - lib/hexapdf/task.rb
465
468
  - lib/hexapdf/task/dereference.rb
466
469
  - lib/hexapdf/task/optimize.rb
470
+ - lib/hexapdf/task/pdfa.rb
467
471
  - lib/hexapdf/test_utils.rb
468
472
  - lib/hexapdf/tokenizer.rb
469
473
  - lib/hexapdf/type.rb
@@ -515,6 +519,7 @@ files:
515
519
  - lib/hexapdf/type/optional_content_properties.rb
516
520
  - lib/hexapdf/type/outline.rb
517
521
  - lib/hexapdf/type/outline_item.rb
522
+ - lib/hexapdf/type/output_intent.rb
518
523
  - lib/hexapdf/type/page.rb
519
524
  - lib/hexapdf/type/page_label.rb
520
525
  - lib/hexapdf/type/page_tree_node.rb
@@ -715,6 +720,7 @@ files:
715
720
  - test/hexapdf/layout/test_width_from_polygon.rb
716
721
  - test/hexapdf/task/test_dereference.rb
717
722
  - test/hexapdf/task/test_optimize.rb
723
+ - test/hexapdf/task/test_pdfa.rb
718
724
  - test/hexapdf/test_composer.rb
719
725
  - test/hexapdf/test_configuration.rb
720
726
  - test/hexapdf/test_data_dir.rb