hexapdf 0.1.0 → 0.2.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.
Files changed (115) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +56 -0
  3. data/CONTRIBUTERS +1 -1
  4. data/Rakefile +3 -3
  5. data/VERSION +1 -1
  6. data/examples/arc.rb +1 -1
  7. data/examples/graphics.rb +1 -1
  8. data/examples/hello_world.rb +1 -1
  9. data/examples/merging.rb +1 -1
  10. data/examples/show_char_bboxes.rb +1 -1
  11. data/examples/standard_pdf_fonts.rb +1 -1
  12. data/examples/truetype.rb +1 -1
  13. data/lib/hexapdf/cli.rb +14 -7
  14. data/lib/hexapdf/cli/extract.rb +1 -1
  15. data/lib/hexapdf/cli/info.rb +2 -2
  16. data/lib/hexapdf/cli/inspect.rb +4 -4
  17. data/lib/hexapdf/cli/modify.rb +151 -51
  18. data/lib/hexapdf/configuration.rb +1 -1
  19. data/lib/hexapdf/content/canvas.rb +1 -1
  20. data/lib/hexapdf/content/processor.rb +1 -1
  21. data/lib/hexapdf/dictionary.rb +6 -19
  22. data/lib/hexapdf/dictionary_fields.rb +1 -1
  23. data/lib/hexapdf/document.rb +23 -16
  24. data/lib/hexapdf/document/files.rb +130 -0
  25. data/lib/hexapdf/{font_utils.rb → document/fonts.rb} +40 -38
  26. data/lib/hexapdf/document/images.rb +117 -0
  27. data/lib/hexapdf/document/pages.rb +125 -0
  28. data/lib/hexapdf/encryption/aes.rb +1 -1
  29. data/lib/hexapdf/encryption/ruby_aes.rb +10 -10
  30. data/lib/hexapdf/encryption/standard_security_handler.rb +11 -8
  31. data/lib/hexapdf/filter/ascii85_decode.rb +1 -1
  32. data/lib/hexapdf/filter/ascii_hex_decode.rb +1 -6
  33. data/lib/hexapdf/font/cmap/writer.rb +5 -7
  34. data/lib/hexapdf/font/true_type.rb +4 -1
  35. data/lib/hexapdf/font/true_type/font.rb +8 -16
  36. data/lib/hexapdf/font/true_type/table.rb +5 -16
  37. data/lib/hexapdf/font/true_type/table/cmap.rb +2 -7
  38. data/lib/hexapdf/font/true_type/table/cmap_subtable.rb +2 -6
  39. data/lib/hexapdf/font/true_type/table/directory.rb +0 -5
  40. data/lib/hexapdf/font/true_type/table/glyf.rb +3 -11
  41. data/lib/hexapdf/font/true_type/table/head.rb +0 -12
  42. data/lib/hexapdf/font/true_type/table/hhea.rb +0 -7
  43. data/lib/hexapdf/font/true_type/table/hmtx.rb +1 -5
  44. data/lib/hexapdf/font/true_type/table/loca.rb +0 -4
  45. data/lib/hexapdf/font/true_type/table/maxp.rb +0 -8
  46. data/lib/hexapdf/font/true_type/table/name.rb +3 -17
  47. data/lib/hexapdf/font/true_type/table/os2.rb +0 -14
  48. data/lib/hexapdf/font/true_type/table/post.rb +0 -8
  49. data/lib/hexapdf/font/true_type_wrapper.rb +1 -1
  50. data/lib/hexapdf/font/type1.rb +2 -2
  51. data/lib/hexapdf/font/type1/font.rb +2 -1
  52. data/lib/hexapdf/font/type1/font_metrics.rb +10 -1
  53. data/lib/hexapdf/font/type1_wrapper.rb +2 -1
  54. data/lib/hexapdf/font_loader/from_configuration.rb +1 -1
  55. data/lib/hexapdf/font_loader/standard14.rb +1 -1
  56. data/lib/hexapdf/image_loader/jpeg.rb +1 -1
  57. data/lib/hexapdf/image_loader/pdf.rb +1 -1
  58. data/lib/hexapdf/image_loader/png.rb +2 -2
  59. data/lib/hexapdf/object.rb +18 -5
  60. data/lib/hexapdf/rectangle.rb +8 -1
  61. data/lib/hexapdf/revisions.rb +4 -2
  62. data/lib/hexapdf/serializer.rb +3 -3
  63. data/lib/hexapdf/stream.rb +3 -2
  64. data/lib/hexapdf/task/dereference.rb +4 -5
  65. data/lib/hexapdf/task/optimize.rb +6 -3
  66. data/lib/hexapdf/tokenizer.rb +3 -3
  67. data/lib/hexapdf/type/file_specification.rb +2 -2
  68. data/lib/hexapdf/type/form.rb +19 -0
  69. data/lib/hexapdf/type/page.rb +21 -6
  70. data/lib/hexapdf/type/page_tree_node.rb +27 -34
  71. data/lib/hexapdf/utils/bit_stream.rb +1 -1
  72. data/lib/hexapdf/utils/pdf_doc_encoding.rb +1 -1
  73. data/lib/hexapdf/version.rb +1 -1
  74. data/man/man1/hexapdf.1 +259 -187
  75. data/test/hexapdf/content/graphic_object/test_arc.rb +1 -1
  76. data/test/hexapdf/content/graphic_object/test_endpoint_arc.rb +1 -1
  77. data/test/hexapdf/content/graphic_object/test_solid_arc.rb +1 -1
  78. data/test/hexapdf/content/test_canvas.rb +1 -1
  79. data/test/hexapdf/document/test_files.rb +71 -0
  80. data/test/hexapdf/{test_font_utils.rb → document/test_fonts.rb} +1 -2
  81. data/test/hexapdf/document/test_images.rb +78 -0
  82. data/test/hexapdf/document/test_pages.rb +114 -0
  83. data/test/hexapdf/encryption/test_standard_security_handler.rb +26 -5
  84. data/test/hexapdf/font/test_true_type_wrapper.rb +1 -1
  85. data/test/hexapdf/font/true_type/common.rb +0 -4
  86. data/test/hexapdf/font/true_type/table/test_cmap.rb +0 -6
  87. data/test/hexapdf/font/true_type/table/test_directory.rb +0 -5
  88. data/test/hexapdf/font/true_type/table/test_glyf.rb +5 -8
  89. data/test/hexapdf/font/true_type/table/test_head.rb +0 -20
  90. data/test/hexapdf/font/true_type/table/test_hhea.rb +0 -7
  91. data/test/hexapdf/font/true_type/table/test_hmtx.rb +2 -7
  92. data/test/hexapdf/font/true_type/table/test_loca.rb +4 -8
  93. data/test/hexapdf/font/true_type/table/test_maxp.rb +0 -7
  94. data/test/hexapdf/font/true_type/table/test_name.rb +0 -19
  95. data/test/hexapdf/font/true_type/table/test_os2.rb +0 -8
  96. data/test/hexapdf/font/true_type/table/test_post.rb +0 -13
  97. data/test/hexapdf/font/true_type/test_font.rb +14 -38
  98. data/test/hexapdf/font/true_type/test_table.rb +0 -9
  99. data/test/hexapdf/font/type1/test_font_metrics.rb +22 -0
  100. data/test/hexapdf/task/test_dereference.rb +5 -1
  101. data/test/hexapdf/task/test_optimize.rb +1 -1
  102. data/test/hexapdf/test_dictionary.rb +4 -0
  103. data/test/hexapdf/test_document.rb +0 -7
  104. data/test/hexapdf/test_importer.rb +4 -4
  105. data/test/hexapdf/test_object.rb +31 -9
  106. data/test/hexapdf/test_rectangle.rb +18 -0
  107. data/test/hexapdf/test_revisions.rb +7 -0
  108. data/test/hexapdf/test_serializer.rb +6 -0
  109. data/test/hexapdf/test_writer.rb +2 -2
  110. data/test/hexapdf/type/test_form.rb +12 -0
  111. data/test/hexapdf/type/test_page.rb +39 -20
  112. data/test/hexapdf/type/test_page_tree_node.rb +28 -21
  113. metadata +21 -9
  114. data/lib/hexapdf/document_utils.rb +0 -209
  115. data/test/hexapdf/test_document_utils.rb +0 -144
@@ -142,7 +142,7 @@ module HexaPDF
142
142
  # {"font_name": {variant: file_name, variant2: file_name2, ...}, ...}
143
143
  #
144
144
  # Once a font is registered in this way, the font name together with a variant name can be used
145
- # with the HexaPDF::FontUtils#load method to load the font.
145
+ # with the HexaPDF::Document::Fonts#load method to load the font.
146
146
  #
147
147
  # For best compatibility, the following variant names should be used:
148
148
  #
@@ -1258,7 +1258,7 @@ module HexaPDF
1258
1258
  # See: PDF1.7 s8.8, s.8.10.1
1259
1259
  def xobject(obj, at:, width: nil, height: nil)
1260
1260
  unless obj.kind_of?(HexaPDF::Stream)
1261
- obj = context.document.utils.add_image(obj)
1261
+ obj = context.document.images.add(obj)
1262
1262
  end
1263
1263
 
1264
1264
  if obj[:Subtype] == :Image
@@ -295,7 +295,7 @@ module HexaPDF
295
295
  EMC: :end_marked_content,
296
296
  BX: :begin_compatibility_section,
297
297
  EX: :end_compatibility_section,
298
- }
298
+ }.freeze
299
299
 
300
300
  # Mapping from operator name (Symbol) to a callable object.
301
301
  #
@@ -88,7 +88,7 @@ module HexaPDF
88
88
  #
89
89
  # version:: Specifies the minimum version of the PDF specification needed for this value.
90
90
  def self.define_field(name, type:, required: false, default: nil, indirect: nil,
91
- version: '1.2')
91
+ version: '1.2')
92
92
  @fields ||= {}
93
93
  @fields[name] = Field.new(type, required, default, indirect, version)
94
94
  end
@@ -141,7 +141,9 @@ module HexaPDF
141
141
  value[name] = field.default
142
142
  end
143
143
  value[name] = data = document.deref(data) if data.kind_of?(HexaPDF::Reference)
144
- data = data.value if data.class == HexaPDF::Object
144
+ if data.class == HexaPDF::Object || (data.kind_of?(HexaPDF::Object) && data.value.nil?)
145
+ data = data.value
146
+ end
145
147
  self[name] = data = field.convert(data, document) if field && field.convert?(data)
146
148
  data
147
149
  end
@@ -242,12 +244,8 @@ module HexaPDF
242
244
  each_set_key_or_required_field do |name, field|
243
245
  obj = key?(name) && self[name] || nil
244
246
 
245
- # Validate direct PDF objects and those possibly nested within a hash
246
- if obj.kind_of?(HexaPDF::Object) && !obj.indirect?
247
- obj.validate(&block)
248
- elsif obj.kind_of?(Hash)
249
- validate_hash(obj, &block)
250
- end
247
+ # Validate nested objects
248
+ validate_nested(obj, &block)
251
249
 
252
250
  # The checks below need a valid field definition
253
251
  next if field.nil?
@@ -287,17 +285,6 @@ module HexaPDF
287
285
  end
288
286
  end
289
287
 
290
- # Validates all nested values of the given hash.
291
- def validate_hash(hash, &block)
292
- hash.each_value do |obj|
293
- if obj.kind_of?(HexaPDF::Object) && !obj.indirect?
294
- obj.validate(&block)
295
- elsif obj.kind_of?(Hash)
296
- validate_hash(obj, &block)
297
- end
298
- end
299
- end
300
-
301
288
  end
302
289
 
303
290
  end
@@ -65,7 +65,7 @@ module HexaPDF
65
65
  module DictionaryFields
66
66
 
67
67
  # This constant should *always* be used for boolean fields.
68
- Boolean = [TrueClass, FalseClass]
68
+ Boolean = [TrueClass, FalseClass].freeze
69
69
 
70
70
  # PDFByteString is used for defining fields with strings in binary encoding.
71
71
  PDFByteString = Class.new { private_class_method :new }
@@ -45,9 +45,7 @@ require 'hexapdf/encryption'
45
45
  require 'hexapdf/writer'
46
46
  require 'hexapdf/importer'
47
47
  require 'hexapdf/image_loader'
48
- require 'hexapdf/document_utils'
49
- require 'hexapdf/font_utils'
50
-
48
+ require 'hexapdf/font_loader'
51
49
 
52
50
  # == HexaPDF API Documentation
53
51
  #
@@ -69,6 +67,11 @@ module HexaPDF
69
67
  # that there are no convenience methods for higher PDF functionality whatsoever.
70
68
  class Document
71
69
 
70
+ autoload(:Pages, 'hexapdf/document/pages')
71
+ autoload(:Fonts, 'hexapdf/document/fonts')
72
+ autoload(:Images, 'hexapdf/document/images')
73
+ autoload(:Files, 'hexapdf/document/files')
74
+
72
75
  # :call-seq:
73
76
  # Document.open(filename, **docargs) -> doc
74
77
  # Document.open(filename, **docargs) {|doc| block} -> obj
@@ -415,15 +418,26 @@ module HexaPDF
415
418
  @listeners[name] && @listeners[name].each {|obj| obj.call(*args)}
416
419
  end
417
420
 
418
- # Returns a DocumentUtils object that provides convenience methods for often used
419
- # functionality like adding images.
420
- def utils
421
- @utils ||= DocumentUtils.new(self)
421
+ # Returns the Pages object that provides convenience methods for working with pages.
422
+ #
423
+ # Also see: HexaPDF::Type::PageTreeNode
424
+ def pages
425
+ @pages ||= Pages.new(self)
426
+ end
427
+
428
+ # Returns the Images object that provides convenience methods for working with images.
429
+ def images
430
+ @images ||= Images.new(self)
422
431
  end
423
432
 
424
- # Returns the FontUtils object that provides convenience methods for working with fonts.
433
+ # Returns the Files object that provides convenience methods for working with files.
434
+ def files
435
+ @files ||= Files.new(self)
436
+ end
437
+
438
+ # Returns the Fonts object that provides convenience methods for working with fonts.
425
439
  def fonts
426
- @font_utils ||= FontUtils.new(self)
440
+ @fonts ||= Fonts.new(self)
427
441
  end
428
442
 
429
443
  # Executes the given task and returns its result.
@@ -449,13 +463,6 @@ module HexaPDF
449
463
  trailer.catalog
450
464
  end
451
465
 
452
- # Returns the root node of the document's page tree.
453
- #
454
- # See: HexaPDF::Type::PageTreeNode
455
- def pages
456
- catalog.pages
457
- end
458
-
459
466
  # Returns the PDF document's version as string (e.g. '1.4').
460
467
  #
461
468
  # This method takes the file header version and the catalog's /Version key into account. If a
@@ -0,0 +1,130 @@
1
+ # -*- encoding: utf-8 -*-
2
+ #
3
+ #--
4
+ # This file is part of HexaPDF.
5
+ #
6
+ # HexaPDF - A Versatile PDF Creation and Manipulation Library For Ruby
7
+ # Copyright (C) 2016 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
+
34
+ module HexaPDF
35
+ class Document
36
+
37
+ # This class provides methods for managing file specifications of a PDF file.
38
+ #
39
+ # Note that for a given PDF file not all file specifications may be found, e.g. when a file
40
+ # specification is only a string. Therefore this module can only handle those file
41
+ # specifications that are indirect file specification dictionaries with the /Type key set.
42
+ class Files
43
+
44
+ include Enumerable
45
+
46
+ # Creates a new Files object for the given PDF document.
47
+ def initialize(document)
48
+ @document = document
49
+ end
50
+
51
+ # :call-seq:
52
+ # files.add(filename, name: File.basename(filename), description: nil, embed: true) -> file_spec
53
+ # files.add(io, name:, description: nil) -> file_spec
54
+ #
55
+ # Adds the file or IO to the PDF document and returns the corresponding file specification
56
+ # object.
57
+ #
58
+ # Options:
59
+ #
60
+ # name::
61
+ # The name that should be used for the file path. This name is also for registering the
62
+ # file in the EmbeddedFiles name tree.
63
+ #
64
+ # description::
65
+ # A description of the file.
66
+ #
67
+ # embed::
68
+ # When an IO object is given, it is always embedded and this option is ignored.
69
+ #
70
+ # When a filename is given and this option is +true+, then the file is embedded. Otherwise
71
+ # only a reference to it is stored.
72
+ #
73
+ # See: HexaPDF::Type::FileSpecification
74
+ def add(file_or_io, name: nil, description: nil, embed: true)
75
+ name ||= File.basename(file_or_io) if file_or_io.kind_of?(String)
76
+ if name.nil?
77
+ raise ArgumentError, "The name argument is mandatory when given an IO object"
78
+ end
79
+
80
+ spec = @document.add(Type: :Filespec)
81
+ spec.path = name
82
+ spec[:Desc] = description if description
83
+ spec.embed(file_or_io, name: name, register: true) if embed || !file_or_io.kind_of?(String)
84
+ spec
85
+ end
86
+
87
+ # :call-seq:
88
+ # files.each(search: false) {|file_spec| block } -> files
89
+ # files.each(search: false) -> Enumerator
90
+ #
91
+ # Iterates over indirect file specification dictionaries of the PDF.
92
+ #
93
+ # By default, only the file specifications in their standard locations, namely in the
94
+ # EmbeddedFiles name tree and in the page annotations, are returned. If the +search+ option is
95
+ # +true+, then all indirect objects are searched for file specification dictionaries which can
96
+ # be much slower.
97
+ def each(search: false)
98
+ return to_enum(__method__, search: search) unless block_given?
99
+
100
+ if search
101
+ @document.each(current: false) do |obj|
102
+ yield(obj) if obj.type == :Filespec
103
+ end
104
+ else
105
+ seen = {}
106
+ tree = @document.catalog[:Names] && @document.catalog[:Names][:EmbeddedFiles]
107
+ tree.each_entry do |_, spec|
108
+ seen[spec] = true
109
+ yield(spec)
110
+ end if tree
111
+
112
+ @document.pages.each do |page|
113
+ next unless page[:Annots]
114
+ page[:Annots].each do |annot|
115
+ annot = @document.deref(annot)
116
+ next unless annot[:Subtype] == :FileAttachment
117
+ spec = @document.deref(annot[:FS])
118
+ yield(spec) unless seen.key?(spec)
119
+ seen[spec] = true
120
+ end
121
+ end
122
+ end
123
+
124
+ self
125
+ end
126
+
127
+ end
128
+
129
+ end
130
+ end
@@ -35,55 +35,57 @@ require 'hexapdf/configuration'
35
35
  require 'hexapdf/font_loader'
36
36
 
37
37
  module HexaPDF
38
+ class Document
38
39
 
39
- # This class provides utility functions for working with fonts. It is available through the
40
- # HexaPDF::Document#fonts method.
41
- class FontUtils
40
+ # This class provides utility functions for working with fonts. It is available through the
41
+ # HexaPDF::Document#fonts method.
42
+ class Fonts
42
43
 
43
- # Creates a new FontUtils object for the given PDF document.
44
- def initialize(document)
45
- @document = document
46
- @loaded_fonts_cache = {}
47
- end
44
+ # Creates a new Fonts object for the given PDF document.
45
+ def initialize(document)
46
+ @document = document
47
+ @loaded_fonts_cache = {}
48
+ end
48
49
 
49
- # :call-seq:
50
- # fonts.load(name, **options) -> font
51
- #
52
- # Loads and returns the font (using the loaders specified with the configuration option
53
- # 'font_loaders').
54
- #
55
- # If a font with the same parameters has been loaded before, the cached font object is used.
56
- def load(name, **options)
57
- font = @loaded_fonts_cache[[name, options]]
58
- return font if font
50
+ # :call-seq:
51
+ # fonts.load(name, **options) -> font
52
+ #
53
+ # Loads and returns the font (using the loaders specified with the configuration option
54
+ # 'font_loaders').
55
+ #
56
+ # If a font with the same parameters has been loaded before, the cached font object is used.
57
+ def load(name, **options)
58
+ font = @loaded_fonts_cache[[name, options]]
59
+ return font if font
59
60
 
60
- each_font_loader do |loader|
61
- font = loader.call(@document, name, **options)
62
- break if font
63
- end
61
+ each_font_loader do |loader|
62
+ font = loader.call(@document, name, **options)
63
+ break if font
64
+ end
64
65
 
65
- if font
66
- @loaded_fonts_cache[[name, options]] = font
67
- else
68
- raise HexaPDF::Error, "The requested font '#{name}' couldn't be found"
66
+ if font
67
+ @loaded_fonts_cache[[name, options]] = font
68
+ else
69
+ raise HexaPDF::Error, "The requested font '#{name}' couldn't be found"
70
+ end
69
71
  end
70
- end
71
72
 
72
- private
73
+ private
73
74
 
74
- # :call-seq:
75
- # fonts.each_font_loader {|loader| block}
76
- #
77
- # Iterates over all configured font loaders.
78
- def each_font_loader
79
- @document.config['font_loader'].each_index do |index|
80
- loader = @document.config.constantize('font_loader', index) do
81
- raise HexaPDF::Error, "Couldn't retrieve font loader ##{index} from configuration"
75
+ # :call-seq:
76
+ # fonts.each_font_loader {|loader| block}
77
+ #
78
+ # Iterates over all configured font loaders.
79
+ def each_font_loader
80
+ @document.config['font_loader'].each_index do |index|
81
+ loader = @document.config.constantize('font_loader', index) do
82
+ raise HexaPDF::Error, "Couldn't retrieve font loader ##{index} from configuration"
83
+ end
84
+ yield(loader)
82
85
  end
83
- yield(loader)
84
86
  end
87
+
85
88
  end
86
89
 
87
90
  end
88
-
89
91
  end
@@ -0,0 +1,117 @@
1
+ # -*- encoding: utf-8 -*-
2
+ #
3
+ #--
4
+ # This file is part of HexaPDF.
5
+ #
6
+ # HexaPDF - A Versatile PDF Creation and Manipulation Library For Ruby
7
+ # Copyright (C) 2016 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
+
34
+ require 'hexapdf/configuration'
35
+
36
+ module HexaPDF
37
+ class Document
38
+
39
+ # This class provides methods for managing the images embedded in a PDF file. It is available
40
+ # through the HexaPDF::Document#images method.
41
+ #
42
+ # Images themselves are represented by the HexaPDF::Type::Image class.Since an image can be used
43
+ # as a mask for another image, not all image objects found in a PDF are really used as images.
44
+ # Such cases are all handled by this class automatically.
45
+ class Images
46
+
47
+ include Enumerable
48
+
49
+ # Creates a new Images object for the given PDF document.
50
+ def initialize(document)
51
+ @document = document
52
+ end
53
+
54
+ # :call-seq:
55
+ # images.add(file) -> image
56
+ # images.add(io) -> image
57
+ #
58
+ # Adds the image from the given file or IO to the PDF document and returns the image object.
59
+ #
60
+ # If the image has been added to the PDF before (i.e. if there is an image object with the
61
+ # same path name), the already existing image object is returned.
62
+ def add(file_or_io)
63
+ name = if file_or_io.kind_of?(String)
64
+ file_or_io
65
+ elsif file_or_io.respond_to?(:to_path)
66
+ file_or_io.to_path
67
+ end
68
+ if name
69
+ name = File.absolute_path(name)
70
+ image = find {|im| im.source_path == name}
71
+ end
72
+ unless image
73
+ image = image_loader_for(file_or_io).load(@document, file_or_io)
74
+ image.source_path = name
75
+ end
76
+ image
77
+ end
78
+
79
+ # :call-seq:
80
+ # images.each {|image| block } -> images
81
+ # images.each -> Enumerator
82
+ #
83
+ # Iterates over all images in the PDF document.
84
+ #
85
+ # Note that only real images are yielded which means, for example, that images used as soft
86
+ # mask are not.
87
+ def each(&block)
88
+ images = @document.each(current: false).select do |obj|
89
+ next unless obj.kind_of?(HexaPDF::Dictionary)
90
+ obj[:Subtype] == :Image && !obj[:ImageMask]
91
+ end
92
+ masks = images.each_with_object([]) do |image, temp|
93
+ temp << image[:Mask] if image[:Mask].kind_of?(Stream)
94
+ temp << image[:SMask] if image[:SMask].kind_of?(Stream)
95
+ end
96
+ (images - masks).each(&block)
97
+ end
98
+
99
+ private
100
+
101
+ # Returns the image loader (see HexaPDF::ImageLoader) for the given file or IO stream or
102
+ # raises an error if no suitable image loader is found.
103
+ def image_loader_for(file_or_io)
104
+ GlobalConfiguration['image_loader'].each_index do |index|
105
+ loader = GlobalConfiguration.constantize('image_loader', index) do
106
+ raise HexaPDF::Error, "Couldn't retrieve image loader from configuration"
107
+ end
108
+ return loader if loader.handles?(file_or_io)
109
+ end
110
+
111
+ raise HexaPDF::Error, "Couldn't find suitable image loader"
112
+ end
113
+
114
+ end
115
+
116
+ end
117
+ end