hexapdf 0.2.0 → 0.3.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 (173) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +33 -1
  3. data/CONTRIBUTERS +1 -1
  4. data/LICENSE +1 -1
  5. data/Rakefile +1 -1
  6. data/VERSION +1 -1
  7. data/lib/hexapdf.rb +1 -1
  8. data/lib/hexapdf/cli.rb +19 -52
  9. data/lib/hexapdf/cli/command.rb +251 -0
  10. data/lib/hexapdf/cli/{extract.rb → files.rb} +19 -23
  11. data/lib/hexapdf/cli/images.rb +147 -0
  12. data/lib/hexapdf/cli/info.rb +5 -5
  13. data/lib/hexapdf/cli/inspect.rb +13 -12
  14. data/lib/hexapdf/cli/merge.rb +200 -0
  15. data/lib/hexapdf/cli/modify.rb +39 -242
  16. data/lib/hexapdf/cli/optimize.rb +104 -0
  17. data/lib/hexapdf/configuration.rb +1 -1
  18. data/lib/hexapdf/content.rb +1 -1
  19. data/lib/hexapdf/content/canvas.rb +1 -1
  20. data/lib/hexapdf/content/color_space.rb +1 -1
  21. data/lib/hexapdf/content/graphic_object.rb +1 -1
  22. data/lib/hexapdf/content/graphic_object/arc.rb +1 -1
  23. data/lib/hexapdf/content/graphic_object/endpoint_arc.rb +1 -1
  24. data/lib/hexapdf/content/graphic_object/solid_arc.rb +1 -1
  25. data/lib/hexapdf/content/graphics_state.rb +1 -1
  26. data/lib/hexapdf/content/operator.rb +1 -1
  27. data/lib/hexapdf/content/parser.rb +16 -15
  28. data/lib/hexapdf/content/processor.rb +1 -1
  29. data/lib/hexapdf/content/transformation_matrix.rb +1 -1
  30. data/lib/hexapdf/data_dir.rb +1 -1
  31. data/lib/hexapdf/dictionary.rb +1 -1
  32. data/lib/hexapdf/dictionary_fields.rb +1 -1
  33. data/lib/hexapdf/document.rb +1 -1
  34. data/lib/hexapdf/document/files.rb +1 -1
  35. data/lib/hexapdf/document/fonts.rb +1 -1
  36. data/lib/hexapdf/document/images.rb +1 -1
  37. data/lib/hexapdf/document/pages.rb +1 -1
  38. data/lib/hexapdf/encryption.rb +1 -1
  39. data/lib/hexapdf/encryption/aes.rb +1 -1
  40. data/lib/hexapdf/encryption/arc4.rb +1 -1
  41. data/lib/hexapdf/encryption/fast_aes.rb +1 -1
  42. data/lib/hexapdf/encryption/fast_arc4.rb +1 -1
  43. data/lib/hexapdf/encryption/identity.rb +1 -1
  44. data/lib/hexapdf/encryption/ruby_aes.rb +1 -1
  45. data/lib/hexapdf/encryption/ruby_arc4.rb +1 -1
  46. data/lib/hexapdf/encryption/security_handler.rb +1 -1
  47. data/lib/hexapdf/encryption/standard_security_handler.rb +1 -1
  48. data/lib/hexapdf/error.rb +1 -1
  49. data/lib/hexapdf/filter.rb +1 -1
  50. data/lib/hexapdf/filter/ascii85_decode.rb +1 -1
  51. data/lib/hexapdf/filter/ascii_hex_decode.rb +1 -1
  52. data/lib/hexapdf/filter/dct_decode.rb +1 -1
  53. data/lib/hexapdf/filter/encryption.rb +1 -1
  54. data/lib/hexapdf/filter/flate_decode.rb +1 -1
  55. data/lib/hexapdf/filter/jpx_decode.rb +1 -1
  56. data/lib/hexapdf/filter/lzw_decode.rb +2 -3
  57. data/lib/hexapdf/filter/predictor.rb +11 -11
  58. data/lib/hexapdf/filter/run_length_decode.rb +1 -1
  59. data/lib/hexapdf/font/cmap.rb +1 -1
  60. data/lib/hexapdf/font/cmap/parser.rb +1 -1
  61. data/lib/hexapdf/font/cmap/writer.rb +1 -1
  62. data/lib/hexapdf/font/encoding.rb +1 -1
  63. data/lib/hexapdf/font/encoding/base.rb +1 -1
  64. data/lib/hexapdf/font/encoding/difference_encoding.rb +1 -1
  65. data/lib/hexapdf/font/encoding/glyph_list.rb +1 -1
  66. data/lib/hexapdf/font/encoding/mac_expert_encoding.rb +1 -1
  67. data/lib/hexapdf/font/encoding/mac_roman_encoding.rb +1 -1
  68. data/lib/hexapdf/font/encoding/standard_encoding.rb +1 -1
  69. data/lib/hexapdf/font/encoding/symbol_encoding.rb +1 -1
  70. data/lib/hexapdf/font/encoding/win_ansi_encoding.rb +1 -1
  71. data/lib/hexapdf/font/encoding/zapf_dingbats_encoding.rb +1 -1
  72. data/lib/hexapdf/font/true_type.rb +2 -1
  73. data/lib/hexapdf/font/true_type/font.rb +1 -1
  74. data/lib/hexapdf/font/true_type/subsetter.rb +186 -0
  75. data/lib/hexapdf/font/true_type/table.rb +8 -4
  76. data/lib/hexapdf/font/true_type/table/cmap.rb +1 -1
  77. data/lib/hexapdf/font/true_type/table/cmap_subtable.rb +1 -1
  78. data/lib/hexapdf/font/true_type/table/directory.rb +1 -1
  79. data/lib/hexapdf/font/true_type/table/glyf.rb +6 -2
  80. data/lib/hexapdf/font/true_type/table/head.rb +2 -2
  81. data/lib/hexapdf/font/true_type/table/hhea.rb +1 -1
  82. data/lib/hexapdf/font/true_type/table/hmtx.rb +1 -1
  83. data/lib/hexapdf/font/true_type/table/loca.rb +1 -1
  84. data/lib/hexapdf/font/true_type/table/maxp.rb +1 -1
  85. data/lib/hexapdf/font/true_type/table/name.rb +1 -1
  86. data/lib/hexapdf/font/true_type/table/os2.rb +1 -1
  87. data/lib/hexapdf/font/true_type/table/post.rb +1 -1
  88. data/lib/hexapdf/font/true_type_wrapper.rb +56 -8
  89. data/lib/hexapdf/font/type1.rb +1 -1
  90. data/lib/hexapdf/font/type1/afm_parser.rb +1 -1
  91. data/lib/hexapdf/font/type1/character_metrics.rb +1 -1
  92. data/lib/hexapdf/font/type1/font.rb +1 -1
  93. data/lib/hexapdf/font/type1/font_metrics.rb +1 -1
  94. data/lib/hexapdf/font/type1/pfb_parser.rb +1 -1
  95. data/lib/hexapdf/font/type1_wrapper.rb +1 -1
  96. data/lib/hexapdf/font_loader.rb +1 -1
  97. data/lib/hexapdf/font_loader/from_configuration.rb +6 -3
  98. data/lib/hexapdf/font_loader/standard14.rb +1 -1
  99. data/lib/hexapdf/image_loader.rb +1 -1
  100. data/lib/hexapdf/image_loader/jpeg.rb +1 -1
  101. data/lib/hexapdf/image_loader/pdf.rb +1 -1
  102. data/lib/hexapdf/image_loader/png.rb +1 -1
  103. data/lib/hexapdf/importer.rb +1 -1
  104. data/lib/hexapdf/name_tree_node.rb +1 -1
  105. data/lib/hexapdf/number_tree_node.rb +1 -1
  106. data/lib/hexapdf/object.rb +1 -1
  107. data/lib/hexapdf/parser.rb +1 -1
  108. data/lib/hexapdf/rectangle.rb +1 -1
  109. data/lib/hexapdf/reference.rb +1 -1
  110. data/lib/hexapdf/revision.rb +1 -1
  111. data/lib/hexapdf/revisions.rb +13 -15
  112. data/lib/hexapdf/serializer.rb +7 -3
  113. data/lib/hexapdf/stream.rb +1 -1
  114. data/lib/hexapdf/task.rb +1 -1
  115. data/lib/hexapdf/task/dereference.rb +1 -1
  116. data/lib/hexapdf/task/optimize.rb +1 -1
  117. data/lib/hexapdf/tokenizer.rb +12 -12
  118. data/lib/hexapdf/type.rb +1 -1
  119. data/lib/hexapdf/type/catalog.rb +1 -1
  120. data/lib/hexapdf/type/embedded_file.rb +1 -1
  121. data/lib/hexapdf/type/file_specification.rb +1 -1
  122. data/lib/hexapdf/type/font.rb +1 -1
  123. data/lib/hexapdf/type/font_descriptor.rb +1 -1
  124. data/lib/hexapdf/type/font_simple.rb +1 -1
  125. data/lib/hexapdf/type/font_true_type.rb +1 -1
  126. data/lib/hexapdf/type/font_type1.rb +1 -1
  127. data/lib/hexapdf/type/form.rb +1 -1
  128. data/lib/hexapdf/type/graphics_state_parameter.rb +1 -1
  129. data/lib/hexapdf/type/image.rb +187 -1
  130. data/lib/hexapdf/type/info.rb +1 -1
  131. data/lib/hexapdf/type/names.rb +1 -1
  132. data/lib/hexapdf/type/object_stream.rb +1 -1
  133. data/lib/hexapdf/type/page.rb +1 -1
  134. data/lib/hexapdf/type/page_tree_node.rb +6 -1
  135. data/lib/hexapdf/type/resources.rb +1 -1
  136. data/lib/hexapdf/type/trailer.rb +2 -2
  137. data/lib/hexapdf/type/viewer_preferences.rb +1 -1
  138. data/lib/hexapdf/type/xref_stream.rb +22 -18
  139. data/lib/hexapdf/utils/bit_field.rb +1 -1
  140. data/lib/hexapdf/utils/bit_stream.rb +16 -32
  141. data/lib/hexapdf/utils/lru_cache.rb +1 -1
  142. data/lib/hexapdf/utils/math_helpers.rb +1 -1
  143. data/lib/hexapdf/utils/object_hash.rb +1 -1
  144. data/lib/hexapdf/utils/pdf_doc_encoding.rb +1 -1
  145. data/lib/hexapdf/utils/sorted_tree_node.rb +1 -1
  146. data/lib/hexapdf/version.rb +2 -2
  147. data/lib/hexapdf/writer.rb +2 -1
  148. data/lib/hexapdf/xref_section.rb +6 -1
  149. data/man/man1/hexapdf.1 +194 -115
  150. data/test/data/images/greyscale-1bit.png +0 -0
  151. data/test/data/images/greyscale-2bit.png +0 -0
  152. data/test/data/images/greyscale-8bit.png +0 -0
  153. data/test/data/images/indexed-alpha-4bit.png +0 -0
  154. data/test/data/images/truecolour-8bit.png +0 -0
  155. data/test/hexapdf/content/test_operator.rb +8 -8
  156. data/test/hexapdf/content/test_processor.rb +1 -1
  157. data/test/hexapdf/encryption/test_security_handler.rb +1 -1
  158. data/test/hexapdf/font/test_true_type_wrapper.rb +89 -48
  159. data/test/hexapdf/font/true_type/table/test_glyf.rb +1 -0
  160. data/test/hexapdf/font/true_type/test_subsetter.rb +70 -0
  161. data/test/hexapdf/font/true_type/test_table.rb +16 -0
  162. data/test/hexapdf/font_loader/test_from_configuration.rb +7 -0
  163. data/test/hexapdf/test_document.rb +1 -1
  164. data/test/hexapdf/test_object.rb +1 -1
  165. data/test/hexapdf/test_revisions.rb +34 -8
  166. data/test/hexapdf/test_serializer.rb +3 -0
  167. data/test/hexapdf/test_writer.rb +11 -2
  168. data/test/hexapdf/test_xref_section.rb +15 -0
  169. data/test/hexapdf/type/test_image.rb +234 -0
  170. data/test/hexapdf/type/test_object_stream.rb +2 -2
  171. data/test/hexapdf/type/test_trailer.rb +4 -0
  172. data/test/hexapdf/utils/test_bit_stream.rb +69 -0
  173. metadata +14 -6
@@ -4,7 +4,7 @@
4
4
  # This file is part of HexaPDF.
5
5
  #
6
6
  # HexaPDF - A Versatile PDF Creation and Manipulation Library For Ruby
7
- # Copyright (C) 2016 Thomas Leitner
7
+ # Copyright (C) 2014-2017 Thomas Leitner
8
8
  #
9
9
  # HexaPDF is free software: you can redistribute it and/or modify it
10
10
  # under the terms of the GNU Affero General Public License version 3 as
@@ -4,7 +4,7 @@
4
4
  # This file is part of HexaPDF.
5
5
  #
6
6
  # HexaPDF - A Versatile PDF Creation and Manipulation Library For Ruby
7
- # Copyright (C) 2016 Thomas Leitner
7
+ # Copyright (C) 2014-2017 Thomas Leitner
8
8
  #
9
9
  # HexaPDF is free software: you can redistribute it and/or modify it
10
10
  # under the terms of the GNU Affero General Public License version 3 as
@@ -4,7 +4,7 @@
4
4
  # This file is part of HexaPDF.
5
5
  #
6
6
  # HexaPDF - A Versatile PDF Creation and Manipulation Library For Ruby
7
- # Copyright (C) 2016 Thomas Leitner
7
+ # Copyright (C) 2014-2017 Thomas Leitner
8
8
  #
9
9
  # HexaPDF is free software: you can redistribute it and/or modify it
10
10
  # under the terms of the GNU Affero General Public License version 3 as
@@ -4,7 +4,7 @@
4
4
  # This file is part of HexaPDF.
5
5
  #
6
6
  # HexaPDF - A Versatile PDF Creation and Manipulation Library For Ruby
7
- # Copyright (C) 2016 Thomas Leitner
7
+ # Copyright (C) 2014-2017 Thomas Leitner
8
8
  #
9
9
  # HexaPDF is free software: you can redistribute it and/or modify it
10
10
  # under the terms of the GNU Affero General Public License version 3 as
@@ -4,7 +4,7 @@
4
4
  # This file is part of HexaPDF.
5
5
  #
6
6
  # HexaPDF - A Versatile PDF Creation and Manipulation Library For Ruby
7
- # Copyright (C) 2016 Thomas Leitner
7
+ # Copyright (C) 2014-2017 Thomas Leitner
8
8
  #
9
9
  # HexaPDF is free software: you can redistribute it and/or modify it
10
10
  # under the terms of the GNU Affero General Public License version 3 as
@@ -4,7 +4,7 @@
4
4
  # This file is part of HexaPDF.
5
5
  #
6
6
  # HexaPDF - A Versatile PDF Creation and Manipulation Library For Ruby
7
- # Copyright (C) 2016 Thomas Leitner
7
+ # Copyright (C) 2014-2017 Thomas Leitner
8
8
  #
9
9
  # HexaPDF is free software: you can redistribute it and/or modify it
10
10
  # under the terms of the GNU Affero General Public License version 3 as
@@ -62,24 +62,22 @@ module HexaPDF
62
62
 
63
63
  parser = Parser.new(io, document)
64
64
  object_loader = lambda {|xref_entry| parser.load_object(xref_entry)}
65
- revision_loader = lambda do |offset|
66
- xref_section, trailer = parser.load_revision(offset)
67
- Revision.new(document.wrap(trailer, type: :XXTrailer), xref_section: xref_section,
68
- loader: object_loader)
69
- end
70
65
 
71
- revisions = [revision_loader.call(parser.startxref_offset)]
66
+ revisions = []
67
+ xref_section, trailer = parser.load_revision(parser.startxref_offset)
68
+ revisions << Revision.new(document.wrap(trailer, type: :XXTrailer),
69
+ xref_section: xref_section, loader: object_loader)
70
+
72
71
 
73
- i = revisions.length - 1
74
- while i >= 0
72
+ while (prev = revisions[0].trailer.value[:Prev])
75
73
  # PDF1.7 s7.5.5 states that :Prev needs to be indirect, Adobe's reference 3.4.4 says it
76
74
  # should be direct. Adobe's POV is followed here. Same with :XRefStm.
77
- xrefstm = revisions[i].trailer.value[:XRefStm]
78
- prev = revisions[i].trailer.value[:Prev]
79
- new_revisions = [(revision_loader.call(prev) if prev),
80
- (revision_loader.call(xrefstm) if xrefstm)].compact
81
- revisions.insert(i, *new_revisions)
82
- i += new_revisions.length - 1
75
+ xref_section, trailer = parser.load_revision(prev)
76
+ stm = revisions[0].trailer.value[:XRefStm]
77
+ stm_xref_section, = parser.load_revision(stm) if stm
78
+ xref_section.merge!(stm_xref_section) if stm
79
+ revisions.unshift(Revision.new(document.wrap(trailer, type: :XXTrailer),
80
+ xref_section: xref_section, loader: object_loader))
83
81
  end
84
82
 
85
83
  document.version = parser.file_header_version
@@ -4,7 +4,7 @@
4
4
  # This file is part of HexaPDF.
5
5
  #
6
6
  # HexaPDF - A Versatile PDF Creation and Manipulation Library For Ruby
7
- # Copyright (C) 2016 Thomas Leitner
7
+ # Copyright (C) 2014-2017 Thomas Leitner
8
8
  #
9
9
  # HexaPDF is free software: you can redistribute it and/or modify it
10
10
  # under the terms of the GNU Affero General Public License version 3 as
@@ -96,6 +96,7 @@ module HexaPDF
96
96
  @encrypter = false
97
97
  @io = nil
98
98
  @object = nil
99
+ @in_object = false
99
100
  end
100
101
 
101
102
  # Returns the serialized form of the given object.
@@ -267,10 +268,13 @@ module HexaPDF
267
268
  # Uses #serialize_hexapdf_reference if it is an indirect object, otherwise just serializes
268
269
  # the objects value.
269
270
  def serialize_hexapdf_object(obj)
270
- if obj.indirect? && obj != @object
271
+ if obj.indirect? && (obj != @object || @in_object)
271
272
  serialize_hexapdf_reference(obj)
272
273
  else
273
- __serialize(obj.value)
274
+ @in_object ||= (obj == @object)
275
+ str = __serialize(obj.value)
276
+ @in_object = false if obj == @object
277
+ str
274
278
  end
275
279
  end
276
280
 
@@ -4,7 +4,7 @@
4
4
  # This file is part of HexaPDF.
5
5
  #
6
6
  # HexaPDF - A Versatile PDF Creation and Manipulation Library For Ruby
7
- # Copyright (C) 2016 Thomas Leitner
7
+ # Copyright (C) 2014-2017 Thomas Leitner
8
8
  #
9
9
  # HexaPDF is free software: you can redistribute it and/or modify it
10
10
  # under the terms of the GNU Affero General Public License version 3 as
@@ -4,7 +4,7 @@
4
4
  # This file is part of HexaPDF.
5
5
  #
6
6
  # HexaPDF - A Versatile PDF Creation and Manipulation Library For Ruby
7
- # Copyright (C) 2016 Thomas Leitner
7
+ # Copyright (C) 2014-2017 Thomas Leitner
8
8
  #
9
9
  # HexaPDF is free software: you can redistribute it and/or modify it
10
10
  # under the terms of the GNU Affero General Public License version 3 as
@@ -4,7 +4,7 @@
4
4
  # This file is part of HexaPDF.
5
5
  #
6
6
  # HexaPDF - A Versatile PDF Creation and Manipulation Library For Ruby
7
- # Copyright (C) 2016 Thomas Leitner
7
+ # Copyright (C) 2014-2017 Thomas Leitner
8
8
  #
9
9
  # HexaPDF is free software: you can redistribute it and/or modify it
10
10
  # under the terms of the GNU Affero General Public License version 3 as
@@ -4,7 +4,7 @@
4
4
  # This file is part of HexaPDF.
5
5
  #
6
6
  # HexaPDF - A Versatile PDF Creation and Manipulation Library For Ruby
7
- # Copyright (C) 2016 Thomas Leitner
7
+ # Copyright (C) 2014-2017 Thomas Leitner
8
8
  #
9
9
  # HexaPDF is free software: you can redistribute it and/or modify it
10
10
  # under the terms of the GNU Affero General Public License version 3 as
@@ -4,7 +4,7 @@
4
4
  # This file is part of HexaPDF.
5
5
  #
6
6
  # HexaPDF - A Versatile PDF Creation and Manipulation Library For Ruby
7
- # Copyright (C) 2016 Thomas Leitner
7
+ # Copyright (C) 2014-2017 Thomas Leitner
8
8
  #
9
9
  # HexaPDF is free software: you can redistribute it and/or modify it
10
10
  # under the terms of the GNU Affero General Public License version 3 as
@@ -108,40 +108,40 @@ module HexaPDF
108
108
  def next_token
109
109
  prepare_string_scanner(20)
110
110
  prepare_string_scanner(20) while @ss.skip(WHITESPACE_MULTI_RE)
111
- case (@ss.eos? ? -1 : @ss.string.getbyte(@ss.pos))
112
- when 43, 45, 46, 48..57 # + - . 0..9
111
+ byte = @ss.string.getbyte(@ss.pos) || -1
112
+ if (48 <= byte && byte <= 57) || byte == 45 || byte == 43 || byte == 46 # 0..9 - + .
113
113
  parse_number
114
- when 47 # /
114
+ elsif byte == 47 # /
115
115
  parse_name
116
- when 40 # (
116
+ elsif byte == 40 # (
117
117
  parse_literal_string
118
- when 60 # <
118
+ elsif byte == 60 # <
119
119
  if @ss.string.getbyte(@ss.pos + 1) != 60
120
120
  parse_hex_string
121
121
  else
122
122
  @ss.pos += 2
123
123
  TOKEN_DICT_START
124
124
  end
125
- when 62 # >
125
+ elsif byte == 62 # >
126
126
  unless @ss.string.getbyte(@ss.pos + 1) == 62
127
127
  raise HexaPDF::MalformedPDFError.new("Delimiter '>' found at invalid position", pos: pos)
128
128
  end
129
129
  @ss.pos += 2
130
130
  TOKEN_DICT_END
131
- when 91 # [
131
+ elsif byte == 91 # [
132
132
  @ss.pos += 1
133
133
  TOKEN_ARRAY_START
134
- when 93 # ]
134
+ elsif byte == 93 # ]
135
135
  @ss.pos += 1
136
136
  TOKEN_ARRAY_END
137
- when 123, 125 # { }
137
+ elsif byte == 123 || byte == 125 # { }
138
138
  Token.new(@ss.get_byte)
139
- when 37 # %
139
+ elsif byte == 37 # %
140
140
  until @ss.skip_until(/(?=[\r\n])/)
141
141
  return NO_MORE_TOKENS unless prepare_string_scanner
142
142
  end
143
143
  next_token
144
- when -1 # we reached the end of the file
144
+ elsif byte == -1 # we reached the end of the file
145
145
  NO_MORE_TOKENS
146
146
  else # everything else consisting of regular characters
147
147
  parse_keyword
@@ -4,7 +4,7 @@
4
4
  # This file is part of HexaPDF.
5
5
  #
6
6
  # HexaPDF - A Versatile PDF Creation and Manipulation Library For Ruby
7
- # Copyright (C) 2016 Thomas Leitner
7
+ # Copyright (C) 2014-2017 Thomas Leitner
8
8
  #
9
9
  # HexaPDF is free software: you can redistribute it and/or modify it
10
10
  # under the terms of the GNU Affero General Public License version 3 as
@@ -4,7 +4,7 @@
4
4
  # This file is part of HexaPDF.
5
5
  #
6
6
  # HexaPDF - A Versatile PDF Creation and Manipulation Library For Ruby
7
- # Copyright (C) 2016 Thomas Leitner
7
+ # Copyright (C) 2014-2017 Thomas Leitner
8
8
  #
9
9
  # HexaPDF is free software: you can redistribute it and/or modify it
10
10
  # under the terms of the GNU Affero General Public License version 3 as
@@ -4,7 +4,7 @@
4
4
  # This file is part of HexaPDF.
5
5
  #
6
6
  # HexaPDF - A Versatile PDF Creation and Manipulation Library For Ruby
7
- # Copyright (C) 2016 Thomas Leitner
7
+ # Copyright (C) 2014-2017 Thomas Leitner
8
8
  #
9
9
  # HexaPDF is free software: you can redistribute it and/or modify it
10
10
  # under the terms of the GNU Affero General Public License version 3 as
@@ -4,7 +4,7 @@
4
4
  # This file is part of HexaPDF.
5
5
  #
6
6
  # HexaPDF - A Versatile PDF Creation and Manipulation Library For Ruby
7
- # Copyright (C) 2016 Thomas Leitner
7
+ # Copyright (C) 2014-2017 Thomas Leitner
8
8
  #
9
9
  # HexaPDF is free software: you can redistribute it and/or modify it
10
10
  # under the terms of the GNU Affero General Public License version 3 as
@@ -4,7 +4,7 @@
4
4
  # This file is part of HexaPDF.
5
5
  #
6
6
  # HexaPDF - A Versatile PDF Creation and Manipulation Library For Ruby
7
- # Copyright (C) 2016 Thomas Leitner
7
+ # Copyright (C) 2014-2017 Thomas Leitner
8
8
  #
9
9
  # HexaPDF is free software: you can redistribute it and/or modify it
10
10
  # under the terms of the GNU Affero General Public License version 3 as
@@ -4,7 +4,7 @@
4
4
  # This file is part of HexaPDF.
5
5
  #
6
6
  # HexaPDF - A Versatile PDF Creation and Manipulation Library For Ruby
7
- # Copyright (C) 2016 Thomas Leitner
7
+ # Copyright (C) 2014-2017 Thomas Leitner
8
8
  #
9
9
  # HexaPDF is free software: you can redistribute it and/or modify it
10
10
  # under the terms of the GNU Affero General Public License version 3 as
@@ -4,7 +4,7 @@
4
4
  # This file is part of HexaPDF.
5
5
  #
6
6
  # HexaPDF - A Versatile PDF Creation and Manipulation Library For Ruby
7
- # Copyright (C) 2016 Thomas Leitner
7
+ # Copyright (C) 2014-2017 Thomas Leitner
8
8
  #
9
9
  # HexaPDF is free software: you can redistribute it and/or modify it
10
10
  # under the terms of the GNU Affero General Public License version 3 as
@@ -4,7 +4,7 @@
4
4
  # This file is part of HexaPDF.
5
5
  #
6
6
  # HexaPDF - A Versatile PDF Creation and Manipulation Library For Ruby
7
- # Copyright (C) 2016 Thomas Leitner
7
+ # Copyright (C) 2014-2017 Thomas Leitner
8
8
  #
9
9
  # HexaPDF is free software: you can redistribute it and/or modify it
10
10
  # under the terms of the GNU Affero General Public License version 3 as
@@ -4,7 +4,7 @@
4
4
  # This file is part of HexaPDF.
5
5
  #
6
6
  # HexaPDF - A Versatile PDF Creation and Manipulation Library For Ruby
7
- # Copyright (C) 2016 Thomas Leitner
7
+ # Copyright (C) 2014-2017 Thomas Leitner
8
8
  #
9
9
  # HexaPDF is free software: you can redistribute it and/or modify it
10
10
  # under the terms of the GNU Affero General Public License version 3 as
@@ -4,7 +4,7 @@
4
4
  # This file is part of HexaPDF.
5
5
  #
6
6
  # HexaPDF - A Versatile PDF Creation and Manipulation Library For Ruby
7
- # Copyright (C) 2016 Thomas Leitner
7
+ # Copyright (C) 2014-2017 Thomas Leitner
8
8
  #
9
9
  # HexaPDF is free software: you can redistribute it and/or modify it
10
10
  # under the terms of the GNU Affero General Public License version 3 as
@@ -4,7 +4,7 @@
4
4
  # This file is part of HexaPDF.
5
5
  #
6
6
  # HexaPDF - A Versatile PDF Creation and Manipulation Library For Ruby
7
- # Copyright (C) 2016 Thomas Leitner
7
+ # Copyright (C) 2014-2017 Thomas Leitner
8
8
  #
9
9
  # HexaPDF is free software: you can redistribute it and/or modify it
10
10
  # under the terms of the GNU Affero General Public License version 3 as
@@ -4,7 +4,7 @@
4
4
  # This file is part of HexaPDF.
5
5
  #
6
6
  # HexaPDF - A Versatile PDF Creation and Manipulation Library For Ruby
7
- # Copyright (C) 2016 Thomas Leitner
7
+ # Copyright (C) 2014-2017 Thomas Leitner
8
8
  #
9
9
  # HexaPDF is free software: you can redistribute it and/or modify it
10
10
  # under the terms of the GNU Affero General Public License version 3 as
@@ -31,7 +31,10 @@
31
31
  # is created or manipulated using HexaPDF.
32
32
  #++
33
33
 
34
+ require 'zlib'
35
+ require 'hexapdf/error'
34
36
  require 'hexapdf/stream'
37
+ require 'hexapdf/image_loader'
35
38
 
36
39
  module HexaPDF
37
40
  module Type
@@ -41,6 +44,10 @@ module HexaPDF
41
44
  # See: PDF1.7 s8.8
42
45
  class Image < Stream
43
46
 
47
+ # The structure that is returned by the Image#info method.
48
+ Info = Struct.new(:type, :width, :height, :color_space, :indexed, :components,
49
+ :bits_per_component, :writable, :extension)
50
+
44
51
  define_field :Type, type: Symbol, default: :XObject
45
52
  define_field :Subtype, type: Symbol, required: true, default: :Image
46
53
  define_field :Width, type: Integer, required: true
@@ -67,6 +74,185 @@ module HexaPDF
67
74
  # facility and not when the image is part of a loaded PDF file.
68
75
  attr_accessor :source_path
69
76
 
77
+ # Returns an Info structure with information about the image.
78
+ #
79
+ # Available accessors:
80
+ #
81
+ # type::
82
+ # The type of the image. Either :jpeg, :jp2, :jbig2, :ccitt or :png.
83
+ # width::
84
+ # The width of the image.
85
+ # height::
86
+ # The height of the image.
87
+ # color_space::
88
+ # The color space the image uses. Either :rgb, :cmyk, :gray or :other.
89
+ # indexed::
90
+ # Whether the image uses an indexed color space or not.
91
+ # components::
92
+ # The number of color components of the color space, or -1 if the number couldn't be
93
+ # determined.
94
+ # bits_per_component::
95
+ # The number of bits per color component.
96
+ # writable::
97
+ # Whether the image can be written by HexaPDF.
98
+ # extension::
99
+ # The file extension that would be used when writing the file. Either jpg, jpx or png. Only
100
+ # meaningful when writable is true.
101
+ def info
102
+ result = Info.new
103
+ result.width = self[:Width]
104
+ result.height = self[:Height]
105
+ result.bits_per_component = self[:BitsPerComponent]
106
+ result.indexed = false
107
+ result.writable = true
108
+
109
+ filter, rest = *self[:Filter]
110
+ case filter
111
+ when :DCTDecode
112
+ result.type = :jpeg
113
+ result.extension = 'jpg'.freeze
114
+ when :JPXDecode
115
+ result.type = :jp2
116
+ result.extension = 'jpx'.freeze
117
+ when :JBIG2Decode
118
+ result.type = :jbig2
119
+ when :CCITTFaxDecode
120
+ result.type = :ccitt
121
+ else
122
+ result.type = :png
123
+ result.extension = 'png'.freeze
124
+ end
125
+
126
+ if rest || ![:FlateDecode, :DCTDecode, :JPXDecode, nil].include?(filter)
127
+ result.writable = false
128
+ end
129
+
130
+ color_space, = *self[:ColorSpace]
131
+ if color_space == :Indexed
132
+ result.indexed = true
133
+ color_space, = *document.deref(self[:ColorSpace][1])
134
+ end
135
+ case color_space
136
+ when :DeviceRGB, :CalRGB
137
+ result.color_space = :rgb
138
+ result.components = 3
139
+ when :DeviceGray, :CalGray
140
+ result.color_space = :gray
141
+ result.components = 1
142
+ when :DeviceCMYK
143
+ result.color_space = :cmyk
144
+ result.components = 4
145
+ result.writable = false if result.type == :png
146
+ else
147
+ result.color_space = :other
148
+ result.components = -1
149
+ result.writable = false if result.type == :png
150
+ end
151
+
152
+ result
153
+ end
154
+
155
+ # :call-seq:
156
+ # image.write(basename)
157
+ # image.write(io)
158
+ #
159
+ # Saves this image XObject to the file with the given name and appends the correct extension
160
+ # (if the name already contains this extension, the name is used as is), or the given IO
161
+ # object.
162
+ #
163
+ # Raises an error if the image format is not supported.
164
+ #
165
+ # The output format and extension depends on the image type as returned by the #info method:
166
+ #
167
+ # :jpeg:: Saved as a JPEG file with the extension '.jpg'
168
+ # :jp2:: Saved as a JPEG2000 file with the extension '.jpx'
169
+ # :png:: Saved as a PNG file with the extension '.png'
170
+ def write(name_or_io)
171
+ info = self.info
172
+
173
+ unless info.writable
174
+ raise HexaPDF::Error, "PDF image format not supported for writing"
175
+ end
176
+
177
+ io = if name_or_io.kind_of?(String)
178
+ File.open(name_or_io.sub(/\.#{info.extension}\z/, '') + "." + info.extension, "wb")
179
+ else
180
+ name_or_io
181
+ end
182
+
183
+ if info.type == :jpeg || info.type == :jp2
184
+ source = stream_source
185
+ while source.alive? && (chunk = source.resume)
186
+ io << chunk
187
+ end
188
+ else
189
+ write_png(io, info)
190
+ end
191
+ ensure
192
+ io.close if io && name_or_io.kind_of?(String)
193
+ end
194
+
195
+ private
196
+
197
+ # Writes the image as PNG to the given IO stream.
198
+ def write_png(io, info)
199
+ io << ImageLoader::PNG::MAGIC_FILE_MARKER
200
+
201
+ color_type = if info.indexed
202
+ ImageLoader::PNG::INDEXED
203
+ elsif info.color_space == :rgb
204
+ ImageLoader::PNG::TRUECOLOR
205
+ else
206
+ ImageLoader::PNG::GREYSCALE
207
+ end
208
+
209
+ io << png_chunk('IHDR', [info.width, info.height, info.bits_per_component,
210
+ color_type, 0, 0, 0].pack('N2C5'))
211
+
212
+ if key?(:Intent)
213
+ # PNG s11.3.3.5
214
+ intent = ImageLoader::PNG::RENDERING_INTENT_MAP.rassoc(self[:Intent]).first
215
+ io << png_chunk('sRGB', intent.chr) <<
216
+ png_chunk('gAMA', [45455].pack('N')) <<
217
+ png_chunk('cHRM', [31270, 32900, 64000, 33000, 30000, 60000, 15000, 6000].pack('N8'))
218
+ end
219
+
220
+ if color_type == ImageLoader::PNG::INDEXED
221
+ palette_data = document.deref(self[:ColorSpace][3])
222
+ palette_data = palette_data.stream unless palette_data.kind_of?(String)
223
+ palette = ''.b
224
+ if info.color_space == :rgb
225
+ palette = palette_data[0, palette_data.length - palette_data.length % 3]
226
+ else
227
+ palette_data.each_byte {|byte| palette << byte << byte << byte}
228
+ end
229
+ io << png_chunk('PLTE', palette)
230
+ end
231
+
232
+ if self[:Mask].kind_of?(Array) && self[:Mask].each_slice(2).all? {|a, b| a == b} &&
233
+ (color_type == ImageLoader::PNG::TRUECOLOR || color_type == ImageLoader::PNG::GREYSCALE)
234
+ io << png_chunk('tRNS', self[:Mask].each_slice(2).map {|a, _| a}.pack('n*'))
235
+ end
236
+
237
+ filter, = *self[:Filter]
238
+ if filter == :FlateDecode && self[:DecodeParms] && self[:DecodeParms][:Predictor].to_i >= 10
239
+ data = stream_source
240
+ else
241
+ flate_decode = GlobalConfiguration.constantize('filter.map', :FlateDecode)
242
+ data = flate_decode.encoder(stream_decoder, Predictor: 15, Colors: 1, Columns: info.width,
243
+ BitsPerComponent: info.bits_per_component)
244
+ end
245
+ io << png_chunk('IDAT', Filter.string_from_source(data))
246
+
247
+ io << png_chunk('IEND', '')
248
+ end
249
+
250
+ # Returns the binary representation of the PNG chunk for the given chunk type and data.
251
+ def png_chunk(type, data = nil)
252
+ [data.to_s.length].pack("N") << type << data.to_s <<
253
+ [Zlib.crc32(data, Zlib.crc32(type))].pack("N")
254
+ end
255
+
70
256
  end
71
257
 
72
258
  end