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.
- checksums.yaml +4 -4
- data/CHANGELOG.md +56 -0
- data/CONTRIBUTERS +1 -1
- data/Rakefile +3 -3
- data/VERSION +1 -1
- data/examples/arc.rb +1 -1
- data/examples/graphics.rb +1 -1
- data/examples/hello_world.rb +1 -1
- data/examples/merging.rb +1 -1
- data/examples/show_char_bboxes.rb +1 -1
- data/examples/standard_pdf_fonts.rb +1 -1
- data/examples/truetype.rb +1 -1
- data/lib/hexapdf/cli.rb +14 -7
- data/lib/hexapdf/cli/extract.rb +1 -1
- data/lib/hexapdf/cli/info.rb +2 -2
- data/lib/hexapdf/cli/inspect.rb +4 -4
- data/lib/hexapdf/cli/modify.rb +151 -51
- data/lib/hexapdf/configuration.rb +1 -1
- data/lib/hexapdf/content/canvas.rb +1 -1
- data/lib/hexapdf/content/processor.rb +1 -1
- data/lib/hexapdf/dictionary.rb +6 -19
- data/lib/hexapdf/dictionary_fields.rb +1 -1
- data/lib/hexapdf/document.rb +23 -16
- data/lib/hexapdf/document/files.rb +130 -0
- data/lib/hexapdf/{font_utils.rb → document/fonts.rb} +40 -38
- data/lib/hexapdf/document/images.rb +117 -0
- data/lib/hexapdf/document/pages.rb +125 -0
- data/lib/hexapdf/encryption/aes.rb +1 -1
- data/lib/hexapdf/encryption/ruby_aes.rb +10 -10
- data/lib/hexapdf/encryption/standard_security_handler.rb +11 -8
- data/lib/hexapdf/filter/ascii85_decode.rb +1 -1
- data/lib/hexapdf/filter/ascii_hex_decode.rb +1 -6
- data/lib/hexapdf/font/cmap/writer.rb +5 -7
- data/lib/hexapdf/font/true_type.rb +4 -1
- data/lib/hexapdf/font/true_type/font.rb +8 -16
- data/lib/hexapdf/font/true_type/table.rb +5 -16
- data/lib/hexapdf/font/true_type/table/cmap.rb +2 -7
- data/lib/hexapdf/font/true_type/table/cmap_subtable.rb +2 -6
- data/lib/hexapdf/font/true_type/table/directory.rb +0 -5
- data/lib/hexapdf/font/true_type/table/glyf.rb +3 -11
- data/lib/hexapdf/font/true_type/table/head.rb +0 -12
- data/lib/hexapdf/font/true_type/table/hhea.rb +0 -7
- data/lib/hexapdf/font/true_type/table/hmtx.rb +1 -5
- data/lib/hexapdf/font/true_type/table/loca.rb +0 -4
- data/lib/hexapdf/font/true_type/table/maxp.rb +0 -8
- data/lib/hexapdf/font/true_type/table/name.rb +3 -17
- data/lib/hexapdf/font/true_type/table/os2.rb +0 -14
- data/lib/hexapdf/font/true_type/table/post.rb +0 -8
- data/lib/hexapdf/font/true_type_wrapper.rb +1 -1
- data/lib/hexapdf/font/type1.rb +2 -2
- data/lib/hexapdf/font/type1/font.rb +2 -1
- data/lib/hexapdf/font/type1/font_metrics.rb +10 -1
- data/lib/hexapdf/font/type1_wrapper.rb +2 -1
- data/lib/hexapdf/font_loader/from_configuration.rb +1 -1
- data/lib/hexapdf/font_loader/standard14.rb +1 -1
- data/lib/hexapdf/image_loader/jpeg.rb +1 -1
- data/lib/hexapdf/image_loader/pdf.rb +1 -1
- data/lib/hexapdf/image_loader/png.rb +2 -2
- data/lib/hexapdf/object.rb +18 -5
- data/lib/hexapdf/rectangle.rb +8 -1
- data/lib/hexapdf/revisions.rb +4 -2
- data/lib/hexapdf/serializer.rb +3 -3
- data/lib/hexapdf/stream.rb +3 -2
- data/lib/hexapdf/task/dereference.rb +4 -5
- data/lib/hexapdf/task/optimize.rb +6 -3
- data/lib/hexapdf/tokenizer.rb +3 -3
- data/lib/hexapdf/type/file_specification.rb +2 -2
- data/lib/hexapdf/type/form.rb +19 -0
- data/lib/hexapdf/type/page.rb +21 -6
- data/lib/hexapdf/type/page_tree_node.rb +27 -34
- data/lib/hexapdf/utils/bit_stream.rb +1 -1
- data/lib/hexapdf/utils/pdf_doc_encoding.rb +1 -1
- data/lib/hexapdf/version.rb +1 -1
- data/man/man1/hexapdf.1 +259 -187
- data/test/hexapdf/content/graphic_object/test_arc.rb +1 -1
- data/test/hexapdf/content/graphic_object/test_endpoint_arc.rb +1 -1
- data/test/hexapdf/content/graphic_object/test_solid_arc.rb +1 -1
- data/test/hexapdf/content/test_canvas.rb +1 -1
- data/test/hexapdf/document/test_files.rb +71 -0
- data/test/hexapdf/{test_font_utils.rb → document/test_fonts.rb} +1 -2
- data/test/hexapdf/document/test_images.rb +78 -0
- data/test/hexapdf/document/test_pages.rb +114 -0
- data/test/hexapdf/encryption/test_standard_security_handler.rb +26 -5
- data/test/hexapdf/font/test_true_type_wrapper.rb +1 -1
- data/test/hexapdf/font/true_type/common.rb +0 -4
- data/test/hexapdf/font/true_type/table/test_cmap.rb +0 -6
- data/test/hexapdf/font/true_type/table/test_directory.rb +0 -5
- data/test/hexapdf/font/true_type/table/test_glyf.rb +5 -8
- data/test/hexapdf/font/true_type/table/test_head.rb +0 -20
- data/test/hexapdf/font/true_type/table/test_hhea.rb +0 -7
- data/test/hexapdf/font/true_type/table/test_hmtx.rb +2 -7
- data/test/hexapdf/font/true_type/table/test_loca.rb +4 -8
- data/test/hexapdf/font/true_type/table/test_maxp.rb +0 -7
- data/test/hexapdf/font/true_type/table/test_name.rb +0 -19
- data/test/hexapdf/font/true_type/table/test_os2.rb +0 -8
- data/test/hexapdf/font/true_type/table/test_post.rb +0 -13
- data/test/hexapdf/font/true_type/test_font.rb +14 -38
- data/test/hexapdf/font/true_type/test_table.rb +0 -9
- data/test/hexapdf/font/type1/test_font_metrics.rb +22 -0
- data/test/hexapdf/task/test_dereference.rb +5 -1
- data/test/hexapdf/task/test_optimize.rb +1 -1
- data/test/hexapdf/test_dictionary.rb +4 -0
- data/test/hexapdf/test_document.rb +0 -7
- data/test/hexapdf/test_importer.rb +4 -4
- data/test/hexapdf/test_object.rb +31 -9
- data/test/hexapdf/test_rectangle.rb +18 -0
- data/test/hexapdf/test_revisions.rb +7 -0
- data/test/hexapdf/test_serializer.rb +6 -0
- data/test/hexapdf/test_writer.rb +2 -2
- data/test/hexapdf/type/test_form.rb +12 -0
- data/test/hexapdf/type/test_page.rb +39 -20
- data/test/hexapdf/type/test_page_tree_node.rb +28 -21
- metadata +21 -9
- data/lib/hexapdf/document_utils.rb +0 -209
- 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::
|
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.
|
1261
|
+
obj = context.document.images.add(obj)
|
1262
1262
|
end
|
1263
1263
|
|
1264
1264
|
if obj[:Subtype] == :Image
|
data/lib/hexapdf/dictionary.rb
CHANGED
@@ -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
|
-
|
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
|
-
|
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
|
246
|
-
|
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 }
|
data/lib/hexapdf/document.rb
CHANGED
@@ -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/
|
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
|
419
|
-
#
|
420
|
-
|
421
|
-
|
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
|
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
|
-
@
|
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
|
-
|
40
|
-
|
41
|
-
|
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
|
-
|
44
|
-
|
45
|
-
|
46
|
-
|
47
|
-
|
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
|
-
|
50
|
-
|
51
|
-
|
52
|
-
|
53
|
-
|
54
|
-
|
55
|
-
|
56
|
-
|
57
|
-
|
58
|
-
|
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
|
-
|
61
|
-
|
62
|
-
|
63
|
-
|
61
|
+
each_font_loader do |loader|
|
62
|
+
font = loader.call(@document, name, **options)
|
63
|
+
break if font
|
64
|
+
end
|
64
65
|
|
65
|
-
|
66
|
-
|
67
|
-
|
68
|
-
|
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
|
-
|
73
|
+
private
|
73
74
|
|
74
|
-
|
75
|
-
|
76
|
-
|
77
|
-
|
78
|
-
|
79
|
-
|
80
|
-
|
81
|
-
|
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
|