hexapdf 0.3.0 → 0.4.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 (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