hexapdf 0.3.0 → 0.4.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (142) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +68 -0
  3. data/CONTRIBUTERS +1 -1
  4. data/README.md +35 -4
  5. data/Rakefile +1 -0
  6. data/VERSION +1 -1
  7. data/data/hexapdf/cmap/83pv-RKSJ-H +314 -0
  8. data/data/hexapdf/cmap/90ms-RKSJ-H +259 -0
  9. data/data/hexapdf/cmap/90ms-RKSJ-V +156 -0
  10. data/data/hexapdf/cmap/90msp-RKSJ-H +257 -0
  11. data/data/hexapdf/cmap/90msp-RKSJ-V +155 -0
  12. data/data/hexapdf/cmap/90pv-RKSJ-H +355 -0
  13. data/data/hexapdf/cmap/Add-RKSJ-H +738 -0
  14. data/data/hexapdf/cmap/Add-RKSJ-V +135 -0
  15. data/data/hexapdf/cmap/Adobe-CNS1-UCS2 +18209 -0
  16. data/data/hexapdf/cmap/Adobe-GB1-UCS2 +14267 -0
  17. data/data/hexapdf/cmap/Adobe-Japan1-UCS2 +19159 -0
  18. data/data/hexapdf/cmap/Adobe-Korea1-UCS2 +9267 -0
  19. data/data/hexapdf/cmap/B5pc-H +337 -0
  20. data/data/hexapdf/cmap/B5pc-V +90 -0
  21. data/data/hexapdf/cmap/CNS-EUC-H +490 -0
  22. data/data/hexapdf/cmap/CNS-EUC-V +538 -0
  23. data/data/hexapdf/cmap/ETen-B5-H +343 -0
  24. data/data/hexapdf/cmap/ETen-B5-V +91 -0
  25. data/data/hexapdf/cmap/ETenms-B5-H +79 -0
  26. data/data/hexapdf/cmap/ETenms-B5-V +99 -0
  27. data/data/hexapdf/cmap/EUC-H +207 -0
  28. data/data/hexapdf/cmap/EUC-V +105 -0
  29. data/data/hexapdf/cmap/Ext-RKSJ-H +768 -0
  30. data/data/hexapdf/cmap/Ext-RKSJ-V +117 -0
  31. data/data/hexapdf/cmap/GB-EUC-H +173 -0
  32. data/data/hexapdf/cmap/GB-EUC-V +98 -0
  33. data/data/hexapdf/cmap/GBK-EUC-H +4273 -0
  34. data/data/hexapdf/cmap/GBK-EUC-V +97 -0
  35. data/data/hexapdf/cmap/GBK2K-H +5325 -0
  36. data/data/hexapdf/cmap/GBK2K-V +118 -0
  37. data/data/hexapdf/cmap/GBKp-EUC-H +4272 -0
  38. data/data/hexapdf/cmap/GBKp-EUC-V +97 -0
  39. data/data/hexapdf/cmap/GBpc-EUC-H +175 -0
  40. data/data/hexapdf/cmap/GBpc-EUC-V +98 -0
  41. data/data/hexapdf/cmap/H +200 -0
  42. data/data/hexapdf/cmap/HKscs-B5-H +1331 -0
  43. data/data/hexapdf/cmap/HKscs-B5-V +90 -0
  44. data/data/hexapdf/cmap/Identity-H +339 -0
  45. data/data/hexapdf/cmap/Identity-V +73 -0
  46. data/data/hexapdf/cmap/KSC-EUC-H +562 -0
  47. data/data/hexapdf/cmap/KSC-EUC-V +94 -0
  48. data/data/hexapdf/cmap/KSCms-UHC-H +776 -0
  49. data/data/hexapdf/cmap/KSCms-UHC-HW-H +775 -0
  50. data/data/hexapdf/cmap/KSCms-UHC-HW-V +93 -0
  51. data/data/hexapdf/cmap/KSCms-UHC-V +94 -0
  52. data/data/hexapdf/cmap/KSCpc-EUC-H +608 -0
  53. data/data/hexapdf/cmap/LICENSE.txt +26 -0
  54. data/data/hexapdf/cmap/README.txt +9 -0
  55. data/data/hexapdf/cmap/UniCNS-UCS2-H +16992 -0
  56. data/data/hexapdf/cmap/UniCNS-UCS2-V +90 -0
  57. data/data/hexapdf/cmap/UniCNS-UTF16-H +19117 -0
  58. data/data/hexapdf/cmap/UniCNS-UTF16-V +94 -0
  59. data/data/hexapdf/cmap/UniGB-UCS2-H +14321 -0
  60. data/data/hexapdf/cmap/UniGB-UCS2-V +101 -0
  61. data/data/hexapdf/cmap/UniGB-UTF16-H +14381 -0
  62. data/data/hexapdf/cmap/UniGB-UTF16-V +104 -0
  63. data/data/hexapdf/cmap/UniJIS-UCS2-H +8870 -0
  64. data/data/hexapdf/cmap/UniJIS-UCS2-HW-H +81 -0
  65. data/data/hexapdf/cmap/UniJIS-UCS2-HW-V +279 -0
  66. data/data/hexapdf/cmap/UniJIS-UCS2-V +275 -0
  67. data/data/hexapdf/cmap/UniJIS-UTF16-H +14450 -0
  68. data/data/hexapdf/cmap/UniJIS-UTF16-V +299 -0
  69. data/data/hexapdf/cmap/UniKS-UCS2-H +8725 -0
  70. data/data/hexapdf/cmap/UniKS-UCS2-V +95 -0
  71. data/data/hexapdf/cmap/UniKS-UTF16-H +8895 -0
  72. data/data/hexapdf/cmap/UniKS-UTF16-V +99 -0
  73. data/data/hexapdf/cmap/V +105 -0
  74. data/examples/arc.rb +3 -3
  75. data/examples/merging.rb +4 -1
  76. data/examples/optimizing.rb +3 -0
  77. data/examples/show_char_bboxes.rb +2 -2
  78. data/examples/truetype.rb +2 -2
  79. data/lib/hexapdf/cli.rb +40 -1
  80. data/lib/hexapdf/cli/batch.rb +72 -0
  81. data/lib/hexapdf/cli/command.rb +112 -15
  82. data/lib/hexapdf/cli/files.rb +2 -2
  83. data/lib/hexapdf/cli/images.rb +14 -6
  84. data/lib/hexapdf/cli/info.rb +6 -8
  85. data/lib/hexapdf/cli/inspect.rb +5 -8
  86. data/lib/hexapdf/cli/merge.rb +13 -20
  87. data/lib/hexapdf/cli/modify.rb +4 -7
  88. data/lib/hexapdf/cli/optimize.rb +2 -5
  89. data/lib/hexapdf/configuration.rb +32 -3
  90. data/lib/hexapdf/content/canvas.rb +130 -37
  91. data/lib/hexapdf/content/parser.rb +40 -6
  92. data/lib/hexapdf/content/processor.rb +4 -4
  93. data/lib/hexapdf/document.rb +40 -10
  94. data/lib/hexapdf/document/fonts.rb +1 -0
  95. data/lib/hexapdf/encryption/security_handler.rb +8 -12
  96. data/lib/hexapdf/filter/flate_decode.rb +25 -2
  97. data/lib/hexapdf/font/cmap.rb +124 -8
  98. data/lib/hexapdf/font/cmap/parser.rb +65 -15
  99. data/lib/hexapdf/font/encoding/base.rb +2 -2
  100. data/lib/hexapdf/font/encoding/glyph_list.rb +2 -4
  101. data/lib/hexapdf/font/true_type.rb +1 -0
  102. data/lib/hexapdf/font/true_type/builder.rb +75 -0
  103. data/lib/hexapdf/font/true_type/optimizer.rb +65 -0
  104. data/lib/hexapdf/font/true_type/subsetter.rb +9 -22
  105. data/lib/hexapdf/font/true_type_wrapper.rb +9 -21
  106. data/lib/hexapdf/font_loader.rb +1 -1
  107. data/lib/hexapdf/importer.rb +1 -1
  108. data/lib/hexapdf/serializer.rb +5 -3
  109. data/lib/hexapdf/type.rb +2 -0
  110. data/lib/hexapdf/type/cid_font.rb +120 -0
  111. data/lib/hexapdf/type/font.rb +32 -12
  112. data/lib/hexapdf/type/font_simple.rb +34 -42
  113. data/lib/hexapdf/type/font_type0.rb +148 -0
  114. data/lib/hexapdf/type/form.rb +4 -4
  115. data/lib/hexapdf/type/page.rb +12 -11
  116. data/lib/hexapdf/type/resources.rb +14 -0
  117. data/lib/hexapdf/utils/graphics_helpers.rb +77 -0
  118. data/lib/hexapdf/version.rb +1 -1
  119. data/man/man1/hexapdf.1 +43 -1
  120. data/test/hexapdf/content/test_canvas.rb +76 -0
  121. data/test/hexapdf/content/test_parser.rb +20 -1
  122. data/test/hexapdf/content/test_processor.rb +11 -7
  123. data/test/hexapdf/document/test_fonts.rb +3 -1
  124. data/test/hexapdf/font/cmap/test_parser.rb +42 -7
  125. data/test/hexapdf/font/encoding/test_base.rb +1 -1
  126. data/test/hexapdf/font/encoding/test_glyph_list.rb +3 -3
  127. data/test/hexapdf/font/test_cmap.rb +104 -0
  128. data/test/hexapdf/font/test_true_type_wrapper.rb +63 -46
  129. data/test/hexapdf/font/true_type/test_builder.rb +37 -0
  130. data/test/hexapdf/font/true_type/test_optimizer.rb +27 -0
  131. data/test/hexapdf/font/true_type/test_subsetter.rb +6 -13
  132. data/test/hexapdf/test_configuration.rb +12 -7
  133. data/test/hexapdf/test_document.rb +24 -0
  134. data/test/hexapdf/test_importer.rb +9 -1
  135. data/test/hexapdf/test_writer.rb +2 -2
  136. data/test/hexapdf/type/test_cid_font.rb +61 -0
  137. data/test/hexapdf/type/test_font.rb +31 -4
  138. data/test/hexapdf/type/test_font_simple.rb +6 -21
  139. data/test/hexapdf/type/test_font_type0.rb +114 -0
  140. data/test/hexapdf/type/test_resources.rb +17 -1
  141. data/test/hexapdf/utils/test_graphics_helpers.rb +29 -0
  142. metadata +82 -3
@@ -51,28 +51,48 @@ module HexaPDF
51
51
  true
52
52
  end
53
53
 
54
- # Returns the UTF-8 string for the given character code, or an empty string if no mapping was
55
- # found.
54
+ # Returns the UTF-8 string for the given character code, or calls the configuration option
55
+ # 'font.on_missing_unicode_mapping' if no mapping was found.
56
56
  def to_utf8(code)
57
- if to_unicode_cmap
58
- to_unicode_cmap.to_unicode(code)
57
+ to_unicode_cmap && to_unicode_cmap.to_unicode(code) || missing_unicode_mapping(code)
58
+ end
59
+
60
+ # Returns the bounding box of the font or +nil+ if it is not found.
61
+ def bounding_box
62
+ if key?(:FontDescriptor) && self[:FontDescriptor].key?(:FontBBox)
63
+ self[:FontDescriptor][:FontBBox].value
59
64
  else
60
- ''.freeze
65
+ nil
61
66
  end
62
67
  end
63
68
 
69
+ # Returns +true+ if the font is embedded.
70
+ def embedded?
71
+ dict = self[:FontDescriptor]
72
+ dict && (dict[:FontFile] || dict[:FontFile2] || dict[:FontFile3])
73
+ end
74
+
75
+ # Returns the embeeded font file object or +nil+ if the font is not embedded.
76
+ def font_file
77
+ embedded?
78
+ end
79
+
64
80
  private
65
81
 
66
82
  # Parses and caches the ToUnicode CMap.
67
83
  def to_unicode_cmap
68
- unless defined?(@to_unicode_cmap)
69
- @to_unicode_cmap = if key?(:ToUnicode)
70
- HexaPDF::Font::CMap.parse(self[:ToUnicode].stream)
71
- else
72
- nil
73
- end
84
+ document.cache(@data, :to_unicode_cmap) do
85
+ if key?(:ToUnicode)
86
+ HexaPDF::Font::CMap.parse(self[:ToUnicode].stream)
87
+ else
88
+ nil
89
+ end
74
90
  end
75
- @to_unicode_cmap
91
+ end
92
+
93
+ # Calls the configured proc for handling missing unicode mappings.
94
+ def missing_unicode_mapping(code)
95
+ @document.config['font.on_missing_unicode_mapping'].call(code, self)
76
96
  end
77
97
 
78
98
  end
@@ -54,31 +54,30 @@ module HexaPDF
54
54
  #
55
55
  # Note that the encoding is cached internally when accessed the first time.
56
56
  def encoding
57
- @encoding ||=
58
- begin
59
- case (val = self[:Encoding])
60
- when Symbol
61
- encoding = HexaPDF::Font::Encoding.for_name(val)
62
- encoding = encoding_from_font if encoding.nil?
63
- encoding
64
- when HexaPDF::Dictionary, Hash
65
- encoding = val[:BaseEncoding]
66
- encoding = HexaPDF::Font::Encoding.for_name(encoding) if encoding
67
- unless encoding
68
- if embedded? || symbolic?
69
- encoding = encoding_from_font
70
- else
71
- encoding = HexaPDF::Font::Encoding.for_name(:StandardEncoding)
72
- end
57
+ document.cache(@data, :encoding) do
58
+ case (val = self[:Encoding])
59
+ when Symbol
60
+ encoding = HexaPDF::Font::Encoding.for_name(val)
61
+ encoding = encoding_from_font if encoding.nil?
62
+ encoding
63
+ when HexaPDF::Dictionary, Hash
64
+ encoding = val[:BaseEncoding]
65
+ encoding = HexaPDF::Font::Encoding.for_name(encoding) if encoding
66
+ unless encoding
67
+ if embedded? || symbolic?
68
+ encoding = encoding_from_font
69
+ else
70
+ encoding = HexaPDF::Font::Encoding.for_name(:StandardEncoding)
73
71
  end
74
- encoding = difference_encoding(encoding, val[:Differences]) if val.key?(:Differences)
75
- encoding
76
- when nil
77
- encoding_from_font
78
- else
79
- raise HexaPDF::Error, "Unknown value for font's encoding: #{self[:Encoding]}"
80
72
  end
73
+ encoding = difference_encoding(encoding, val[:Differences]) if val.key?(:Differences)
74
+ encoding
75
+ when nil
76
+ encoding_from_font
77
+ else
78
+ raise HexaPDF::Error, "Unknown value for font's encoding: #{self[:Encoding]}"
81
79
  end
80
+ end
82
81
  end
83
82
 
84
83
  # Decodes the given string into an array of character codes.
@@ -86,12 +85,11 @@ module HexaPDF
86
85
  string.bytes
87
86
  end
88
87
 
89
- # Returns the UTF-8 string for the given character code, or an empty string if no mapping was
90
- # found.
88
+ # Returns the UTF-8 string for the given character code, or calls the configuration option
89
+ # 'font.on_missing_unicode_mapping' if no mapping was found.
91
90
  def to_utf8(code)
92
- str = super
93
- str = encoding.unicode(code) if str.empty?
94
- str
91
+ (to_unicode_cmap && to_unicode_cmap.to_unicode(code)) ||
92
+ encoding.unicode(code) || missing_unicode_mapping(code)
95
93
  end
96
94
 
97
95
  # Returns the unscaled width of the given code point in glyph units, or 0 if the width for
@@ -110,32 +108,26 @@ module HexaPDF
110
108
  end
111
109
  end
112
110
 
113
- # Returns the bounding box of the font or +nil+ if it is not found.
114
- def bounding_box
115
- if key?(:FontDescriptor) && self[:FontDescriptor].key?(:FontBBox)
116
- self[:FontDescriptor][:FontBBox].value
117
- else
118
- nil
119
- end
120
- end
121
-
122
111
  # Returns the writing mode which is always :horizontal for simple fonts like Type1.
123
112
  def writing_mode
124
113
  :horizontal
125
114
  end
126
115
 
127
- # Returns +true+ if the font is embedded.
128
- def embedded?
129
- dict = self[:FontDescriptor]
130
- dict && (dict[:FontFile] || dict[:FontFile2] || dict[:FontFile3])
131
- end
132
-
133
116
  # Returns +true+ if the font is a symbolic font, +false+ if it is not, and +nil+ if it is
134
117
  # not known.
135
118
  def symbolic?
136
119
  self[:FontDescriptor] && self[:FontDescriptor].flagged?(:symbolic) || nil
137
120
  end
138
121
 
122
+ # Returns whether word spacing is applicable when using this font.
123
+ #
124
+ # Always returns +true+ for simple fonts.
125
+ #
126
+ # See: PDF1.7 s9.3.3
127
+ def word_spacing_applicable?
128
+ true
129
+ end
130
+
139
131
  private
140
132
 
141
133
  # Tries to read the encoding from the embedded font.
@@ -0,0 +1,148 @@
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) 2014-2017 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/type/font'
35
+ require 'hexapdf/stream'
36
+ require 'hexapdf/font/cmap'
37
+
38
+ module HexaPDF
39
+ module Type
40
+
41
+ # Represents a composite PDF font.
42
+ #
43
+ # Composites fonts wrap a descendant CIDFont and use CIDs to identify glyphs. A CID can be
44
+ # encoded in one or more bytes and an associated CMap specifies how this encoding is done.
45
+ # Composite fonts also allow for vertical writing mode and support TrueType as well as OpenType
46
+ # fonts.
47
+ #
48
+ # See: PDF1.7 s9.7
49
+ class FontType0 < Font
50
+
51
+ define_field :Subtype, type: Symbol, required: true, default: :Type0
52
+ define_field :Encoding, type: [Symbol, Stream], required: true
53
+ define_field :DescendantFonts, type: Array, required: true
54
+
55
+ # Returns the CID font of this type 0 font.
56
+ def descendant_font
57
+ document.cache(@data, :descendant_font) { document.deref(self[:DescendantFonts][0]) }
58
+ end
59
+
60
+ # Returns the writing mode which is either :horizontal or :vertical.
61
+ def writing_mode
62
+ cmap.wmode == 0 ? :horizontal : :vertical
63
+ end
64
+
65
+ # Decodes the given string into an array of CIDs.
66
+ def decode(string)
67
+ cmap.read_codes(string)
68
+ end
69
+
70
+ # Returns the UTF-8 string for the given code, or calls the configuration option
71
+ # 'font.on_missing_unicode_mapping' if no mapping was found.
72
+ def to_utf8(code)
73
+ (to_unicode_cmap && to_unicode_cmap.to_unicode(code)) ||
74
+ (ucs2_cmap && ucs2_cmap.to_unicode(code)) || missing_unicode_mapping(code)
75
+ end
76
+
77
+ # Returns the unscaled width of the given CID in glyph units, or 0 if the width for the code
78
+ # point is missing.
79
+ def width(code)
80
+ descendant_font.width(cmap.to_cid(code))
81
+ end
82
+
83
+ # Returns the bounding box of the font or +nil+ if it is not found.
84
+ def bounding_box
85
+ descendant_font.bounding_box
86
+ end
87
+
88
+ # Returns +true+ if the font is embedded.
89
+ def embedded?
90
+ descendant_font.embedded?
91
+ end
92
+
93
+ # Returns the embeeded font file object or +nil+ if the font is not embedded.
94
+ def font_file
95
+ descendant_font.font_file
96
+ end
97
+
98
+ # Returns whether word spacing is applicable when using this font.
99
+ #
100
+ # Note that the return value is cached when accessed the first time.
101
+ #
102
+ # See: PDF1.7 s9.3.3
103
+ def word_spacing_applicable?
104
+ @word_spacing_applicable ||= ((cmap.read_codes("\x20".freeze) && true) rescue false)
105
+ end
106
+
107
+ private
108
+
109
+ # Returns the CMap used for decoding strings for this font.
110
+ #
111
+ # Note that the CMap is cached internally when accessed the first time.
112
+ def cmap
113
+ document.cache(@data, :cmap) do
114
+ val = self[:Encoding]
115
+ if val.kind_of?(Symbol)
116
+ HexaPDF::Font::CMap.for_name(val.to_s)
117
+ elsif val.kind_of?(HexaPDF::Stream)
118
+ HexaPDF::Font::CMap.parse(val.stream)
119
+ else
120
+ raise HexaPDF::Error, "Unknown value for font's encoding: #{self[:Encoding]}"
121
+ end
122
+ end
123
+ end
124
+
125
+ # Returns the UCS-2 CMap used for extracting text when no ToUnicode CMap is available, or
126
+ # +nil+ if the UCS-2 CMap could not be determined.
127
+ #
128
+ # Note that the CMap is cached internally when accessed the first time.
129
+ #
130
+ # See: PDF1.7 s9.10.2
131
+ def ucs2_cmap
132
+ document.cache(@data, :ucs2_cmap) do
133
+ encoding = self[:Encoding]
134
+ system_info = descendant_font[:CIDSystemInfo]
135
+ registry = system_info[:Registry]
136
+ ordering = system_info[:Ordering]
137
+ if (encoding.kind_of?(Symbol) && HexaPDF::Font::CMap.predefined?(encoding.to_s) &&
138
+ encoding != :"Identity-H" && encoding != :"Identity-V") ||
139
+ (registry == "Adobe" && ['GB1', 'CNS1', 'Japan1', 'Korea1'].include?(ordering))
140
+ HexaPDF::Font::CMap.for_name("#{registry}-#{ordering}-UCS2")
141
+ end
142
+ end
143
+ end
144
+
145
+ end
146
+
147
+ end
148
+ end
@@ -105,15 +105,15 @@ module HexaPDF
105
105
  #
106
106
  # *Note* that a canvas can only be retrieved for initially empty form XObjects!
107
107
  def canvas
108
- unless defined?(@canvas)
108
+ document.cache(@data, :canvas) do
109
109
  unless stream.empty?
110
110
  raise HexaPDF::Error, "Cannot create a canvas for a form XObjects with contents"
111
111
  end
112
- @canvas = Content::Canvas.new(self)
113
- self.stream = @canvas.stream_data
112
+ canvas = Content::Canvas.new(self)
113
+ self.stream = canvas.stream_data
114
114
  set_filter(:FlateDecode)
115
+ canvas
115
116
  end
116
- @canvas
117
117
  end
118
118
 
119
119
  end
@@ -135,7 +135,7 @@ module HexaPDF
135
135
  define_field :TemplateInstantiated, type: Symbol, version: '1.5'
136
136
  define_field :PresSteps, type: Dictionary, version: '1.5'
137
137
  define_field :UserUnit, type: Numeric, version: '1.6'
138
- define_field :VP, type: Dictionary, version: '1.6'
138
+ define_field :VP, type: Array, version: '1.6'
139
139
 
140
140
  # Returns +true+ since page objects must always be indirect.
141
141
  def must_be_indirect?
@@ -234,7 +234,8 @@ module HexaPDF
234
234
  end
235
235
  end
236
236
 
237
- # Returns the resource dictionary which is automatically created if it doesn't exist.
237
+ # Returns the possibly inherited resource dictionary which is automatically created if it
238
+ # doesn't exist.
238
239
  def resources
239
240
  self[:Resources] ||= document.wrap({}, type: :XXResources)
240
241
  end
@@ -277,8 +278,8 @@ module HexaPDF
277
278
  unless [:page, :overlay, :underlay].include?(type)
278
279
  raise ArgumentError, "Invalid value for 'type', expected: :page, :underlay or :overlay"
279
280
  end
280
- @canvas_cache ||= {}
281
- return @canvas_cache[type] if @canvas_cache.key?(type)
281
+ cache_key = "#{type}_canvas".intern
282
+ return document.cache(@data, cache_key) if document.cached?(@data, cache_key)
282
283
 
283
284
  if type == :page && key?(:Contents)
284
285
  raise HexaPDF::Error, "Cannot get the canvas for a page with contents"
@@ -286,18 +287,18 @@ module HexaPDF
286
287
 
287
288
  contents = self[:Contents]
288
289
  if contents.nil?
289
- @canvas_cache[:page] = Content::Canvas.new(self)
290
+ page_canvas = document.cache(@data, :page_canvas, Content::Canvas.new(self))
290
291
  self[:Contents] = document.add({Filter: :FlateDecode},
291
- stream: @canvas_cache[:page].stream_data)
292
+ stream: page_canvas.stream_data)
292
293
  end
293
294
 
294
295
  if type == :overlay || type == :underlay
295
- @canvas_cache[:overlay] = Content::Canvas.new(self)
296
- @canvas_cache[:underlay] = Content::Canvas.new(self)
296
+ underlay_canvas = document.cache(@data, :underlay_canvas, Content::Canvas.new(self))
297
+ overlay_canvas = document.cache(@data, :overlay_canvas, Content::Canvas.new(self))
297
298
 
298
299
  stream = HexaPDF::StreamData.new do
299
300
  Fiber.yield(" q ")
300
- fiber = @canvas_cache[:underlay].stream_data.fiber
301
+ fiber = underlay_canvas.stream_data.fiber
301
302
  while fiber.alive? && (data = fiber.resume)
302
303
  Fiber.yield(data)
303
304
  end
@@ -307,7 +308,7 @@ module HexaPDF
307
308
 
308
309
  stream = HexaPDF::StreamData.new do
309
310
  Fiber.yield(" Q ")
310
- fiber = @canvas_cache[:overlay].stream_data.fiber
311
+ fiber = overlay_canvas.stream_data.fiber
311
312
  while fiber.alive? && (data = fiber.resume)
312
313
  Fiber.yield(data)
313
314
  end
@@ -317,7 +318,7 @@ module HexaPDF
317
318
  self[:Contents] = [underlay, *self[:Contents], overlay]
318
319
  end
319
320
 
320
- @canvas_cache[type]
321
+ document.cache(@data, cache_key)
321
322
  end
322
323
 
323
324
  # Creates a Form XObject from the page's dictionary and contents for the given PDF document.
@@ -154,6 +154,20 @@ module HexaPDF
154
154
  object_setter(:Font, 'F'.freeze, object)
155
155
  end
156
156
 
157
+ # Returns the property list stored under the given name.
158
+ #
159
+ # If the property list is not found, an error is raised.
160
+ def property_list(name)
161
+ object_getter(:Properties, name)
162
+ end
163
+
164
+ # Adds the property list to the resources and returns the name under which it is stored.
165
+ #
166
+ # If there already exists a name for the given property list, it is just returned.
167
+ def add_property_list(dict)
168
+ object_setter(:Properties, 'P'.freeze, dict)
169
+ end
170
+
157
171
  private
158
172
 
159
173
  # Helper method for returning an entry of a subdictionary.
@@ -0,0 +1,77 @@
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) 2014-2017 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
+ module Utils
36
+
37
+ # This module provides some helper functions for graphics.
38
+ module GraphicsHelpers
39
+
40
+ module_function
41
+
42
+ # Calculates and returns the requested dimensions for the rectangular object with the given
43
+ # +width+ and +height+ based on the following: options:
44
+ #
45
+ # +rwidth+::
46
+ # The requested width. If +rheight+ is not specified, it is chosen so that the aspect
47
+ # ratio is maintained
48
+ #
49
+ # +rheight+::
50
+ # The requested height. If +rwidth+ is not specified, it is chosen so that the aspect
51
+ # ratio is maintained
52
+ def calculate_dimensions(width, height, rwidth: nil, rheight: nil)
53
+ if rwidth && rheight
54
+ [rwidth, rheight]
55
+ elsif rwidth
56
+ [rwidth, height * rwidth / width.to_f]
57
+ elsif rheight
58
+ [width * rheight / height.to_f, rheight]
59
+ else
60
+ [width, height]
61
+ end
62
+ end
63
+
64
+ # Given two points p0 = (x0, y0) and p1 = (x1, y1), returns the point on the line through
65
+ # these points that is +distance+ units away from p0.
66
+ #
67
+ # v = p1 - p0
68
+ # result = p0 + distance * v/norm(v)
69
+ def point_on_line(x0, y0, x1, y1, distance:)
70
+ norm = Math.sqrt((x1 - x0)**2 + (y1 - y0)**2)
71
+ [x0 + distance / norm * (x1 - x0), y0 + distance / norm * (y1 - y0)]
72
+ end
73
+
74
+ end
75
+
76
+ end
77
+ end