hexapdf 0.10.0 → 0.11.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 (139) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +33 -0
  3. data/CONTRIBUTERS +1 -1
  4. data/Rakefile +35 -50
  5. data/VERSION +1 -1
  6. data/lib/hexapdf/cli.rb +4 -0
  7. data/lib/hexapdf/cli/command.rb +6 -2
  8. data/lib/hexapdf/cli/image2pdf.rb +141 -0
  9. data/lib/hexapdf/cli/info.rb +1 -1
  10. data/lib/hexapdf/cli/inspect.rb +32 -2
  11. data/lib/hexapdf/cli/modify.rb +1 -1
  12. data/lib/hexapdf/cli/optimize.rb +1 -1
  13. data/lib/hexapdf/cli/watermark.rb +130 -0
  14. data/lib/hexapdf/composer.rb +2 -2
  15. data/lib/hexapdf/configuration.rb +7 -1
  16. data/lib/hexapdf/content/canvas.rb +2 -2
  17. data/lib/hexapdf/content/graphic_object/arc.rb +2 -2
  18. data/lib/hexapdf/content/graphic_object/endpoint_arc.rb +2 -2
  19. data/lib/hexapdf/content/graphic_object/geom2d.rb +1 -1
  20. data/lib/hexapdf/content/graphic_object/solid_arc.rb +1 -1
  21. data/lib/hexapdf/dictionary.rb +11 -3
  22. data/lib/hexapdf/dictionary_fields.rb +32 -3
  23. data/lib/hexapdf/document.rb +7 -3
  24. data/lib/hexapdf/document/files.rb +1 -1
  25. data/lib/hexapdf/document/fonts.rb +21 -1
  26. data/lib/hexapdf/document/pages.rb +2 -2
  27. data/lib/hexapdf/encryption/standard_security_handler.rb +2 -2
  28. data/lib/hexapdf/font/cmap/parser.rb +1 -1
  29. data/lib/hexapdf/font/true_type/table/head.rb +2 -2
  30. data/lib/hexapdf/font/true_type/table/os2.rb +4 -4
  31. data/lib/hexapdf/font/true_type_wrapper.rb +16 -16
  32. data/lib/hexapdf/font/type1_wrapper.rb +16 -16
  33. data/lib/hexapdf/font_loader.rb +2 -0
  34. data/lib/hexapdf/font_loader/from_configuration.rb +5 -0
  35. data/lib/hexapdf/font_loader/standard14.rb +5 -0
  36. data/lib/hexapdf/image_loader/png.rb +1 -1
  37. data/lib/hexapdf/layout/box.rb +2 -2
  38. data/lib/hexapdf/layout/image_box.rb +1 -1
  39. data/lib/hexapdf/layout/style.rb +50 -24
  40. data/lib/hexapdf/layout/text_box.rb +1 -1
  41. data/lib/hexapdf/layout/text_fragment.rb +2 -2
  42. data/lib/hexapdf/layout/text_layouter.rb +14 -10
  43. data/lib/hexapdf/name_tree_node.rb +3 -3
  44. data/lib/hexapdf/number_tree_node.rb +3 -3
  45. data/lib/hexapdf/pdf_array.rb +207 -0
  46. data/lib/hexapdf/rectangle.rb +12 -12
  47. data/lib/hexapdf/serializer.rb +1 -1
  48. data/lib/hexapdf/stream.rb +6 -4
  49. data/lib/hexapdf/task/optimize.rb +3 -3
  50. data/lib/hexapdf/type.rb +2 -0
  51. data/lib/hexapdf/type/acro_form.rb +51 -0
  52. data/lib/hexapdf/type/acro_form/field.rb +129 -0
  53. data/lib/hexapdf/type/acro_form/form.rb +124 -0
  54. data/lib/hexapdf/type/action.rb +1 -1
  55. data/lib/hexapdf/type/actions/go_to.rb +1 -1
  56. data/lib/hexapdf/type/actions/go_to_r.rb +1 -1
  57. data/lib/hexapdf/type/actions/launch.rb +1 -1
  58. data/lib/hexapdf/type/annotation.rb +2 -2
  59. data/lib/hexapdf/type/annotations.rb +1 -0
  60. data/lib/hexapdf/type/annotations/link.rb +4 -15
  61. data/lib/hexapdf/type/annotations/markup_annotation.rb +2 -1
  62. data/lib/hexapdf/type/annotations/text.rb +3 -6
  63. data/lib/hexapdf/type/annotations/widget.rb +90 -0
  64. data/lib/hexapdf/type/catalog.rb +12 -9
  65. data/lib/hexapdf/type/cid_font.rb +3 -3
  66. data/lib/hexapdf/type/file_specification.rb +2 -2
  67. data/lib/hexapdf/type/font_descriptor.rb +5 -2
  68. data/lib/hexapdf/type/font_simple.rb +1 -1
  69. data/lib/hexapdf/type/font_type0.rb +1 -1
  70. data/lib/hexapdf/type/font_type3.rb +1 -1
  71. data/lib/hexapdf/type/form.rb +2 -2
  72. data/lib/hexapdf/type/graphics_state_parameter.rb +11 -6
  73. data/lib/hexapdf/type/icon_fit.rb +58 -0
  74. data/lib/hexapdf/type/image.rb +14 -8
  75. data/lib/hexapdf/type/info.rb +2 -1
  76. data/lib/hexapdf/type/page.rb +4 -4
  77. data/lib/hexapdf/type/page_tree_node.rb +3 -7
  78. data/lib/hexapdf/type/resources.rb +1 -1
  79. data/lib/hexapdf/type/trailer.rb +4 -4
  80. data/lib/hexapdf/type/viewer_preferences.rb +7 -4
  81. data/lib/hexapdf/type/xref_stream.rb +2 -2
  82. data/lib/hexapdf/utils/sorted_tree_node.rb +1 -1
  83. data/lib/hexapdf/version.rb +1 -1
  84. data/man/man1/hexapdf.1 +77 -8
  85. data/test/hexapdf/content/test_canvas.rb +2 -2
  86. data/test/hexapdf/content/test_processor.rb +3 -3
  87. data/test/hexapdf/document/test_files.rb +4 -4
  88. data/test/hexapdf/document/test_fonts.rb +13 -1
  89. data/test/hexapdf/document/test_images.rb +6 -6
  90. data/test/hexapdf/document/test_pages.rb +8 -8
  91. data/test/hexapdf/encryption/test_security_handler.rb +7 -7
  92. data/test/hexapdf/encryption/test_standard_security_handler.rb +5 -5
  93. data/test/hexapdf/font/test_true_type_wrapper.rb +2 -2
  94. data/test/hexapdf/font_loader/test_from_configuration.rb +4 -0
  95. data/test/hexapdf/font_loader/test_standard14.rb +10 -0
  96. data/test/hexapdf/image_loader/test_jpeg.rb +1 -1
  97. data/test/hexapdf/image_loader/test_png.rb +3 -3
  98. data/test/hexapdf/layout/test_box.rb +2 -2
  99. data/test/hexapdf/layout/test_frame.rb +1 -1
  100. data/test/hexapdf/layout/test_image_box.rb +1 -1
  101. data/test/hexapdf/layout/test_style.rb +18 -13
  102. data/test/hexapdf/layout/test_text_box.rb +1 -1
  103. data/test/hexapdf/layout/test_text_layouter.rb +11 -6
  104. data/test/hexapdf/task/test_dereference.rb +2 -2
  105. data/test/hexapdf/task/test_optimize.rb +11 -11
  106. data/test/hexapdf/test_composer.rb +1 -1
  107. data/test/hexapdf/test_dictionary.rb +10 -2
  108. data/test/hexapdf/test_dictionary_fields.rb +27 -3
  109. data/test/hexapdf/test_document.rb +16 -15
  110. data/test/hexapdf/test_importer.rb +4 -4
  111. data/test/hexapdf/test_object.rb +1 -1
  112. data/test/hexapdf/test_pdf_array.rb +162 -0
  113. data/test/hexapdf/test_rectangle.rb +3 -5
  114. data/test/hexapdf/test_serializer.rb +1 -1
  115. data/test/hexapdf/test_stream.rb +1 -0
  116. data/test/hexapdf/test_writer.rb +3 -3
  117. data/test/hexapdf/type/acro_form/test_field.rb +85 -0
  118. data/test/hexapdf/type/acro_form/test_form.rb +69 -0
  119. data/test/hexapdf/type/annotations/test_text.rb +2 -6
  120. data/test/hexapdf/type/annotations/test_widget.rb +24 -0
  121. data/test/hexapdf/type/test_annotation.rb +1 -1
  122. data/test/hexapdf/type/test_catalog.rb +1 -1
  123. data/test/hexapdf/type/test_cid_font.rb +3 -3
  124. data/test/hexapdf/type/test_font.rb +2 -2
  125. data/test/hexapdf/type/test_font_descriptor.rb +2 -1
  126. data/test/hexapdf/type/test_font_simple.rb +3 -3
  127. data/test/hexapdf/type/test_font_true_type.rb +6 -6
  128. data/test/hexapdf/type/test_font_type0.rb +5 -5
  129. data/test/hexapdf/type/test_font_type1.rb +8 -8
  130. data/test/hexapdf/type/test_font_type3.rb +4 -4
  131. data/test/hexapdf/type/test_image.rb +16 -12
  132. data/test/hexapdf/type/test_page.rb +11 -11
  133. data/test/hexapdf/type/test_page_tree_node.rb +20 -20
  134. data/test/hexapdf/type/test_resources.rb +6 -6
  135. data/test/hexapdf/type/test_trailer.rb +5 -2
  136. data/test/hexapdf/type/test_xref_stream.rb +1 -0
  137. data/test/hexapdf/utils/test_sorted_tree_node.rb +35 -35
  138. metadata +23 -7
  139. data/test/hexapdf/type/annotations/test_link.rb +0 -19
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: e7d800154c0525b724aa0b1a91e360aff2d443bd56e260b5b051a06d605cdb9c
4
- data.tar.gz: d658fa5ff09f12a169156ee091f66b189874ae4b847cb4effa8d2c60b5f836b1
3
+ metadata.gz: d9c9409476d9ff9efaf2a8c0fb2b2623f660676c19e0d920659f6d719a783ee1
4
+ data.tar.gz: cb266922c5cdb6749e7a9954141e2b8b2094fcd44968bfa384e62478a184161c
5
5
  SHA512:
6
- metadata.gz: b600fb0c600a0093e08d5ed3a4d16c2bfee637b0d338783c663f664aa7b4eb5384b20f064137c26d83f4269ed213b5d46c390421946d747f16be9d7e5600f381
7
- data.tar.gz: 31f7c75f80db9b467f0fe8582616a9d136b9da86995cd6c57c76678a4e916fef0e73a4ad22ef53d8e8c36ce7ab620e303d6d05268d3ce6d551bbc33b67c7f959
6
+ metadata.gz: 7c7d5fb9e817d5a4a8fc7ad3f26deb075afa9dd504fda9dd7801dabbb76bcf819734cb86918ecb1c553e767125edfd47672cfeacdd648afbda81ab198e02f96f
7
+ data.tar.gz: bafff7b14203d534cacf94fdfaf493657c05255fb1caf431fd0cc8b7ebdcfb4470d49a0d02a5d72c96fea51f0c4bc0fa6b9aaca89caf0a190e6879be1b49fbb3
@@ -1,3 +1,36 @@
1
+ ## 0.11.0 - 2019-11-19
2
+
3
+ ### Added
4
+
5
+ * [HexaPDF::PDFArray] to wrap arrays and allow automatic resolution of
6
+ references like with [HexaPDF::Dictionary] - MAY BREAK THINGS!
7
+ * CLI command `hexapdf watermark` to apply a watermark PDF as background or
8
+ stamp onto another PDF file
9
+ * CLI command `hexapdf image2pdf` to convert images into a PDF file
10
+ * [HexaPDF::DictionaryFields::Field#allowed_values] to allow constraining a
11
+ field to certain allowed values
12
+ * [HexaPDF::Document::Fonts#configured_fonts] to return all font variants that
13
+ are configured and available for adding to a document
14
+ * [HexaPDF::Type::Annotations::Widget] and associated classes
15
+ * [HexaPDF::Type::AcroForm::Form] and [HexaPDF::Type::AcroForm::Field] for basic
16
+ AcroForm support
17
+
18
+ ### Changed
19
+
20
+ * Use Reline for interactive mode of `hexapdf inspect` if available
21
+ * [HexaPDF::DictionaryFields::Field::new] to use keyword arguments
22
+ * Update the field information for implemented PDF types to include the allowed
23
+ values where possible
24
+ * Interface of font loader objects to allow another method `available_fonts` for
25
+ returning all available fonts
26
+ * [HexaPDF::Layout::Style] to check for valid values where possible
27
+
28
+ ### Fixed
29
+
30
+ * Line spacing of empty lines for [HexaPDF::Layout::TextLayouter]
31
+ * Handling of `/DecodeParms` when exporting to PNG images
32
+
33
+
1
34
  ## 0.10.0 - 2019-10-02
2
35
 
3
36
  ### Added
@@ -1,5 +1,5 @@
1
1
  Count Name
2
2
  ======= ====
3
- 1069 Thomas Leitner <t_leitner@gmx.at>
3
+ 1089 Thomas Leitner <t_leitner@gmx.at>
4
4
  1 Stanislav (Stas) Katkov <sk@skylup.com>
5
5
  1 Daniel Kraus <bovender@bovender.de>
data/Rakefile CHANGED
@@ -1,9 +1,9 @@
1
+ # -*- coding: utf-8 -*-
1
2
  require 'rake/testtask'
2
3
  require 'rake/clean'
3
4
  require 'rubygems/package_task'
4
5
 
5
- $:.unshift('lib')
6
- require 'hexapdf'
6
+ require_relative 'lib/hexapdf/version'
7
7
 
8
8
  Rake::TestTask.new do |t|
9
9
  t.libs << 'test'
@@ -13,19 +13,6 @@ Rake::TestTask.new do |t|
13
13
  end
14
14
 
15
15
  namespace :dev do
16
- PKG_FILES = FileList.new([
17
- 'Rakefile',
18
- 'LICENSE', 'agpl-3.0.txt',
19
- 'README.md', 'CHANGELOG.md',
20
- 'VERSION', 'CONTRIBUTERS',
21
- 'bin/*',
22
- 'lib/**/*.rb',
23
- 'man/man1/hexapdf.1',
24
- 'data/**/*',
25
- 'examples/*',
26
- 'test/**/*'
27
- ])
28
-
29
16
  CLOBBER << "man/man1/hexapdf.1"
30
17
  file 'man/man1/hexapdf.1' => ['man/man1/hexapdf.1.md'] do
31
18
  puts "Generating hexapdf man page"
@@ -35,7 +22,7 @@ namespace :dev do
35
22
  CLOBBER << "VERSION"
36
23
  file 'VERSION' do
37
24
  puts "Generating VERSION file"
38
- File.open('VERSION', 'w+') {|file| file.write(HexaPDF::VERSION + "\n")}
25
+ File.open('VERSION', 'w+') {|file| file.write(HexaPDF::VERSION + "\n") }
39
26
  end
40
27
 
41
28
  CLOBBER << 'CONTRIBUTERS'
@@ -46,35 +33,8 @@ namespace :dev do
46
33
  `git log | grep ^Author: | sed 's/^Author: //' | sort | uniq -c | sort -nr >> CONTRIBUTERS`
47
34
  end
48
35
 
49
- spec = Gem::Specification.new do |s|
50
- s.name = 'hexapdf'
51
- s.version = HexaPDF::VERSION
52
- s.summary = "HexaPDF - A Versatile PDF Creation and Manipulation Library For Ruby"
53
- s.description = "HexaPDF is a pure Ruby library with an accompanying application for " \
54
- "working with PDF files.\n\nIn short, it allows creating new PDF files, manipulating " \
55
- "existing PDF files, merging multiple PDF files into one, extracting meta information, " \
56
- "text, images and files from PDF files, securing PDF files by encrypting them and " \
57
- "optimizing PDF files for smaller file size or other criteria.\n\nHexaPDF was designed " \
58
- "with ease of use and performance in mind. It uses lazy loading and lazy computing when " \
59
- "possible and tries to produce small PDF files by default."
60
- s.license = 'AGPL-3.0'
61
-
62
- s.files = PKG_FILES.to_a
63
-
64
- s.require_path = 'lib'
65
- s.executables = ['hexapdf']
66
- s.add_dependency('cmdparse', '~> 3.0', '>= 3.0.3')
67
- s.add_dependency('geom2d', '~> 0.2')
68
- s.add_development_dependency('kramdown', '~> 1.0', '>= 1.13.0')
69
- s.add_development_dependency('rubocop', '~> 0.58', '>= 0.58.2')
70
- s.required_ruby_version = '>= 2.4'
71
-
72
- s.author = 'Thomas Leitner'
73
- s.email = 't_leitner@gmx.at'
74
- s.homepage = "https://hexapdf.gettalong.org"
75
- end
76
-
77
- Gem::PackageTask.new(spec) do |pkg|
36
+ ENV['REAL_GEM'] = "true"
37
+ Gem::PackageTask.new(eval(File.read('hexapdf.gemspec'), binding, 'hexapdf.gemspec')) do |pkg|
78
38
  pkg.need_zip = true
79
39
  pkg.need_tar = true
80
40
  end
@@ -88,11 +48,36 @@ namespace :dev do
88
48
  desc 'Release HexaPDF version ' + HexaPDF::VERSION
89
49
  task release: [:clobber, :package, :publish_files]
90
50
 
91
- CLOBBER << 'hexapdf.gemspec'
92
- task :gemspec do
93
- puts "Generating Gemspec"
94
- contents = spec.to_ruby
95
- File.open("hexapdf.gemspec", 'w+') {|f| f.puts(contents)}
51
+ desc "Set-up everything for development"
52
+ task :setup do
53
+ puts "Installing required runtime and development gems:"
54
+ resolver = Gem::Resolver.for_current_gems(spec.dependencies)
55
+ resolver.ignore_dependencies = true
56
+ resolver.soft_missing = true
57
+ resolver.resolve
58
+ spec.dependencies.each do |dependency|
59
+ if resolver.missing.find {|dep_request| dep_request.dependency == dependency }
60
+ print "✗ #{dependency.name} - installing it..."
61
+ Gem.install(dependency.name, dependency.requirement, prerelease: true)
62
+ puts " done"
63
+ else
64
+ puts "✓ #{dependency.name}"
65
+ end
66
+ end
67
+
68
+ puts
69
+ puts "The following binaries are needed for the tests:"
70
+ {
71
+ 'pngtopnm' => 'sudo apt install netpbm',
72
+ 'pngcheck' => 'sudo apt install pngcheck',
73
+ }.each do |name, install_command|
74
+ `which #{name} 2>&1`
75
+ if $?.exitstatus == 0
76
+ puts "✓ #{name}"
77
+ else
78
+ puts "✗ #{name} (#{install_command})"
79
+ end
80
+ end
96
81
  end
97
82
 
98
83
  CODING_LINE = "# -*- encoding: utf-8; frozen_string_literal: true -*-\n"
data/VERSION CHANGED
@@ -1 +1 @@
1
- 0.10.0
1
+ 0.11.0
@@ -44,6 +44,8 @@ require 'hexapdf/cli/optimize'
44
44
  require 'hexapdf/cli/images'
45
45
  require 'hexapdf/cli/batch'
46
46
  require 'hexapdf/cli/split'
47
+ require 'hexapdf/cli/watermark'
48
+ require 'hexapdf/cli/image2pdf'
47
49
  require 'hexapdf/version'
48
50
  require 'hexapdf/document'
49
51
 
@@ -95,6 +97,8 @@ module HexaPDF
95
97
  add_command(HexaPDF::CLI::Merge.new)
96
98
  add_command(HexaPDF::CLI::Batch.new)
97
99
  add_command(HexaPDF::CLI::Split.new)
100
+ add_command(HexaPDF::CLI::Watermark.new)
101
+ add_command(HexaPDF::CLI::Image2PDF.new)
98
102
  add_command(CmdParse::HelpCommand.new)
99
103
  version_command = CmdParse::VersionCommand.new(add_switches: false)
100
104
  add_command(version_command)
@@ -57,7 +57,7 @@ module HexaPDF
57
57
 
58
58
  include Extensions
59
59
 
60
- def initialize(*args, &block) #:nodoc:
60
+ def initialize(*args, **kwargs, &block) #:nodoc:
61
61
  super
62
62
  @out_options = OpenStruct.new
63
63
  @out_options.compact = true
@@ -82,7 +82,7 @@ module HexaPDF
82
82
  # If +out_file+ is given, the document is written to it after yielding.
83
83
  def with_document(file, password: nil, out_file: nil) #:yield: document
84
84
  if file == out_file
85
- doc = HexaPDF::Document.open(file, pdf_options(password))
85
+ doc = HexaPDF::Document.open(file, **pdf_options(password))
86
86
  else
87
87
  file_io = File.open(file, 'rb')
88
88
  doc = HexaPDF::Document.new(io: file_io, **pdf_options(password))
@@ -143,6 +143,8 @@ module HexaPDF
143
143
  #
144
144
  # See: #out_options, #apply_optimization_options
145
145
  def define_optimization_options
146
+ options.separator("")
147
+ options.separator("Optimization options:")
146
148
  options.on("--[no-]compact", "Delete unnecessary PDF objects (default: " \
147
149
  "#{@out_options.compact})") do |c|
148
150
  @out_options.compact = c
@@ -176,6 +178,8 @@ module HexaPDF
176
178
  #
177
179
  # See: #out_options, #apply_encryption_options
178
180
  def define_encryption_options
181
+ options.separator("")
182
+ options.separator("Encryption options:")
179
183
  options.on("--decrypt", "Remove any encryption") do
180
184
  @out_options.encryption = :remove
181
185
  end
@@ -0,0 +1,141 @@
1
+ # -*- encoding: utf-8; frozen_string_literal: true -*-
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-2019 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
+ # If the GNU Affero General Public License doesn't fit your need,
34
+ # commercial licenses are available at <https://gettalong.at/hexapdf/>.
35
+ #++
36
+
37
+ require 'hexapdf/cli/command'
38
+
39
+ module HexaPDF
40
+ module CLI
41
+
42
+ # Converts one or more images into a PDF file.
43
+ class Image2PDF < Command
44
+
45
+ def initialize #:nodoc:
46
+ super('image2pdf', takes_commands: false)
47
+ short_desc("Convert one or more images into a PDF file")
48
+ long_desc(<<~EOF)
49
+ This command converts one or more images into a single PDF file. The various options allow
50
+ setting a page size, scaling the images and defining margins.
51
+ EOF
52
+
53
+ options.on("-p", "--page-size SIZE", "The PDF page size. Either auto which chooses a " \
54
+ "size based on the image size or a valid page size like A4, A4-landscape " \
55
+ "or 595x842. Default: auto") do |page_size|
56
+ @media_box = case page_size
57
+ when 'auto'
58
+ :auto
59
+ when /(\d+(?:\.\d+)?)x(\d+(?:\.\d+)?)/
60
+ [0, 0, $1.to_f, $2.to_f]
61
+ else
62
+ orientation = :portrait
63
+ if page_size.end_with?('-landscape')
64
+ orientation = :landscape
65
+ page_size.delete_suffix!('-landscape')
66
+ end
67
+ HexaPDF::Type::Page.media_box(page_size.to_sym, orientation: orientation)
68
+ end
69
+ end
70
+ options.on("--[no-]auto-rotate", "Automatically rotate pages based on image dimesions. " \
71
+ "Default: true") do |auto_rotate|
72
+ @auto_rotate = auto_rotate
73
+ end
74
+ options.on("-s", "--scale SCALE", Integer, "Defines how the images should be scaled. " \
75
+ "Either fit to fit the image to the page size or a number specifying the " \
76
+ "minimum pixels per inch. Default: fit") do |scale|
77
+ @scale = case scale
78
+ when 'fit' then :fit
79
+ else scale.to_f
80
+ end
81
+ end
82
+ options.on("-m", "--margins MARGINS", Array, "Defines the margins around the image, " \
83
+ "either with a single number or four numbers (top, right, bottom, left) " \
84
+ "separated by commas. Default: 0") do |margins|
85
+ @margins = case margins.size
86
+ when 1 then margins.map!(&:to_f) * 4
87
+ when 4 then margins.map!(&:to_f)
88
+ else
89
+ raise OptionParser::InvalidArgument, "#{margins.join(',')} (1 or 4 " \
90
+ "numbers needed)"
91
+ end
92
+ end
93
+ define_optimization_options
94
+ define_encryption_options
95
+
96
+ @media_box = :auto
97
+ @auto_rotate = true
98
+ @scale = :fit
99
+ @margins = [0, 0, 0, 0]
100
+ end
101
+
102
+ def execute(out_file, *images) #:nodoc:
103
+ maybe_raise_on_existing_file(out_file)
104
+
105
+ out = HexaPDF::Document.new
106
+
107
+ images.each do |image_file|
108
+ image = out.images.add(image_file)
109
+ iw = image.info.width.to_f
110
+ ih = image.info.height.to_f
111
+ if @scale != :fit
112
+ iw *= 72 / @scale
113
+ ih *= 72 / @scale
114
+ end
115
+
116
+ media_box = (@media_box == :auto ? [0, 0, iw, ih] : @media_box.dup)
117
+ if @auto_rotate && (ih > iw) != (media_box[3] > media_box[2]) &&
118
+ (iw > media_box[2] || ih > media_box[3])
119
+ media_box[2], media_box[3] = media_box[3], media_box[2]
120
+ end
121
+ page = out.pages.add(media_box)
122
+
123
+ pw = page.box(:media).width.to_f - @margins[1] - @margins[3]
124
+ ph = page.box(:media).height.to_f - @margins[0] - @margins[2]
125
+ if @scale == :fit || iw > pw || ih > ph
126
+ ratio = [pw / iw, ph / ih].min
127
+ iw, ih = iw * ratio, ih * ratio
128
+ end
129
+ x, y = @margins[3] + (pw - iw) / 2, @margins[2] + (ph - ih) / 2
130
+ page.canvas.image(image, at: [x, y], width: iw, height: ih)
131
+ end
132
+
133
+ apply_encryption_options(out)
134
+ apply_optimization_options(out)
135
+ write_document(out, out_file)
136
+ end
137
+
138
+ end
139
+
140
+ end
141
+ end
@@ -78,7 +78,7 @@ module HexaPDF
78
78
  def output_info(file) # :nodoc:
79
79
  options = pdf_options(@password)
80
80
  options[:config]['document.auto_decrypt'] = @auto_decrypt
81
- HexaPDF::Document.open(file, options) do |doc|
81
+ HexaPDF::Document.open(file, **options) do |doc|
82
82
  output_line("File name", file)
83
83
  output_line("File size", File.stat(file).size.to_s + " bytes")
84
84
  @auto_decrypt && INFO_KEYS.each do |name|
@@ -70,9 +70,17 @@ module HexaPDF
70
70
  with_document(file, password: @password) do |doc|
71
71
  @doc = doc
72
72
  if commands.empty?
73
+ begin
74
+ require 'reline'
75
+ Reline.completion_proc = RELINE_COMPLETION_PROC
76
+ Reline.completion_append_character = " "
77
+ rescue LoadError
78
+ if command_parser.verbosity_info?
79
+ $stderr.puts("Library reline not available, history and line editing not available")
80
+ end
81
+ end
73
82
  while true
74
- print "cmd> "
75
- input = $stdin.gets
83
+ input = read_input
76
84
  (puts; break) unless input
77
85
  commands = input.scan(/(["'])(.+?)\1|(\S+)/).map {|a| a[1] || a[2] }
78
86
  break if execute_commands(commands)
@@ -85,6 +93,28 @@ module HexaPDF
85
93
 
86
94
  private
87
95
 
96
+ # :nodoc:
97
+ COMMAND_LIST = %w[object recursive stream raw-stream xref catalog trailer pages
98
+ page-count search quit help]
99
+ # :nodoc:
100
+ RELINE_COMPLETION_PROC = proc do |s|
101
+ if s.empty?
102
+ COMMAND_DESCRIPTIONS.map {|cmd, desc| cmd.ljust(35) << desc }
103
+ else
104
+ COMMAND_LIST.grep(/^#{Regexp.escape(s)}/)
105
+ end
106
+ end
107
+
108
+ # Returns one line of input, using Reline if available.
109
+ def read_input
110
+ if Object.const_defined?("Reline")
111
+ Reline.readline("cmd> ", true)
112
+ else
113
+ print "cmd> "
114
+ $stdin.gets
115
+ end
116
+ end
117
+
88
118
  def execute_commands(data) #:nodoc:
89
119
  data.map! {|item| item == ";" ? nil : item }
90
120
  until data.empty?
@@ -93,7 +93,7 @@ module HexaPDF
93
93
  # Arranges the pages of the document as specified with the --pages option.
94
94
  def arrange_pages(doc)
95
95
  all_pages = doc.pages.to_a
96
- new_page_tree = doc.add(Type: :Pages)
96
+ new_page_tree = doc.add({Type: :Pages})
97
97
  parse_pages_specification(@pages, all_pages.length).each do |index, rotation|
98
98
  page = all_pages[index]
99
99
  page.value.update(page.copy_inherited_values)
@@ -81,7 +81,7 @@ module HexaPDF
81
81
 
82
82
  # Optimizes the page tree by flattening it and deleting unsed objects.
83
83
  def optimize_page_tree(doc)
84
- page_tree = doc.add(Type: :Pages)
84
+ page_tree = doc.add({Type: :Pages})
85
85
  retained = {page_tree.data => true}
86
86
  doc.pages.each do |page|
87
87
  page.value.update(page.copy_inherited_values)