hexapdf 0.10.0 → 0.11.0

Sign up to get free protection for your applications and to get access to all the features.
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)