hexapdf 0.1.0 → 0.2.0

Sign up to get free protection for your applications and to get access to all the features.
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