# -*- encoding: utf-8; frozen_string_literal: true -*-
#
#--
# This file is part of HexaPDF.
#
# HexaPDF - A Versatile PDF Creation and Manipulation Library For Ruby
# Copyright (C) 2014-2022 Thomas Leitner
#
# HexaPDF is free software: you can redistribute it and/or modify it
# under the terms of the GNU Affero General Public License version 3 as
# published by the Free Software Foundation with the addition of the
# following permission added to Section 15 as permitted in Section 7(a):
# FOR ANY PART OF THE COVERED WORK IN WHICH THE COPYRIGHT IS OWNED BY
# THOMAS LEITNER, THOMAS LEITNER DISCLAIMS THE WARRANTY OF NON
# INFRINGEMENT OF THIRD PARTY RIGHTS.
#
# HexaPDF is distributed in the hope that it will be useful, but WITHOUT
# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
# FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public
# License for more details.
#
# You should have received a copy of the GNU Affero General Public License
# along with HexaPDF. If not, see .
#
# The interactive user interfaces in modified source and object code
# versions of HexaPDF must display Appropriate Legal Notices, as required
# under Section 5 of the GNU Affero General Public License version 3.
#
# In accordance with Section 7(b) of the GNU Affero General Public
# License, a covered work must retain the producer line in every PDF that
# is created or manipulated using HexaPDF.
#
# If the GNU Affero General Public License doesn't fit your need,
# commercial licenses are available at .
#++
require 'ostruct'
require 'hexapdf/cli/command'
module HexaPDF
module CLI
# Modifies a PDF file:
#
# * Decrypts or encrypts the resulting output PDF file.
# * Generates or deletes object and cross-reference streams.
# * Optimizes the output PDF by merging the revisions of a PDF file and removes unused entries.
#
# See: HexaPDF::Task::Optimize
class Modify < Command
def initialize #:nodoc:
super('modify', takes_commands: false)
short_desc("Modify a PDF file")
long_desc(<<~EOF)
This command modifies a PDF file. It can be used, for example, to select pages that should
appear in the output file and/or rotate them. The output file can also be
encrypted/decrypted and optimized in various ways.
EOF
@password = nil
@pages = '1-e'
@embed_files = []
@annotation_mode = nil
options.on("--password PASSWORD", "-p", String,
"The password for decryption. Use - for reading from standard input.") do |pwd|
@password = (pwd == '-' ? read_password : pwd)
end
options.on("-i", "--pages PAGES", "The pages of the input file that should be used " \
"(default: 1-e)") do |pages|
@pages = pages
end
options.on("-e", "--embed FILE", String, "Embed the file into the output file (can be " \
"used multiple times)") do |file|
@embed_files << file
end
options.on("--annotations MODE", [:remove, :flatten], "Handling of annotations (either " \
"remove or flatten)") do |mode|
@annotation_mode = mode
end
define_optimization_options
define_encryption_options
end
def execute(in_file, out_file) #:nodoc:
maybe_raise_on_existing_file(out_file)
with_document(in_file, password: @password, out_file: out_file) do |doc|
arrange_pages(doc) unless @pages == '1-e'
handle_annotations(doc)
@embed_files.each {|file| doc.files.add(file, embed: true) }
apply_encryption_options(doc)
apply_optimization_options(doc)
end
end
private
# Arranges the pages of the document as specified with the --pages option.
def arrange_pages(doc)
all_pages = doc.pages.to_a
new_page_tree = doc.add({Type: :Pages})
parse_pages_specification(@pages, all_pages.length).each do |index, rotation|
page = all_pages[index]
page.value.update(page.copy_inherited_values)
if rotation == :none
page.delete(:Rotate)
elsif rotation.kind_of?(Integer)
page.rotate(rotation)
end
new_page_tree.add_page(page)
end
doc.catalog[:Pages] = new_page_tree
remove_unused_pages(doc)
doc.pages.add unless doc.pages.count > 0
end
# Handles the annotations of all selected pages by doing nothing, removing them or flattening
# them.
def handle_annotations(doc)
return unless @annotation_mode
doc.pages.each do |page|
if @annotation_mode == :remove
page.delete(:Annots)
else
page.flatten_annotations
end
end
end
end
end
end