# -*- 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-2023 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 'hexapdf/cli/command' module HexaPDF module CLI # Converts one or more images into a PDF file. class Image2PDF < Command def initialize #:nodoc: super('image2pdf', takes_commands: false) short_desc("Convert one or more images into a PDF file") long_desc(<<~EOF) This command converts one or more images into a single PDF file. The various options allow setting a page size, scaling the images and defining margins. EOF options.on("-p", "--page-size SIZE", "The PDF page size. Either auto which chooses a " \ "size based on the image size or a valid page size like A4, A4-landscape " \ "or 595x842. Default: auto") do |page_size| @media_box = case page_size when 'auto' :auto when /(\d+(?:\.\d+)?)x(\d+(?:\.\d+)?)/ [0, 0, $1.to_f, $2.to_f] else orientation = :portrait if page_size.end_with?('-landscape') orientation = :landscape page_size.delete_suffix!('-landscape') end page_size = page_size.capitalize.to_sym HexaPDF::Type::Page.media_box(page_size, orientation: orientation) end end options.on("--[no-]auto-rotate", "Automatically rotate pages based on image dimesions. " \ "Default: true") do |auto_rotate| @auto_rotate = auto_rotate end options.on("-s", "--scale SCALE", Integer, "Defines how the images should be scaled. " \ "Either fit to fit the image to the page size or a number specifying the " \ "minimum pixels per inch. Default: fit") do |scale| @scale = case scale when 'fit' then :fit else scale.to_f end end options.on("-m", "--margins MARGINS", Array, "Defines the margins around the image, " \ "either with a single number or four numbers (top, right, bottom, left) " \ "separated by commas. Default: 0") do |margins| @margins = case margins.size when 1 then margins.map!(&:to_f) * 4 when 4 then margins.map!(&:to_f) else raise OptionParser::InvalidArgument, "#{margins.join(',')} (1 or 4 " \ "numbers needed)" end end define_optimization_options define_encryption_options @media_box = :auto @auto_rotate = true @scale = :fit @margins = [0, 0, 0, 0] end def execute(*images, out_file) #:nodoc: maybe_raise_on_existing_file(out_file) out = HexaPDF::Document.new images.each do |image_file| image = out.images.add(image_file) iw = image.width.to_f ih = image.height.to_f if @scale != :fit iw *= 72 / @scale ih *= 72 / @scale end media_box = (@media_box == :auto ? [0, 0, iw, ih] : @media_box.dup) if @auto_rotate && (ih > iw) != (media_box[3] > media_box[2]) && (iw > media_box[2] || ih > media_box[3]) media_box[2], media_box[3] = media_box[3], media_box[2] end page = out.pages.add(media_box) pw = page.box(:media).width.to_f - @margins[1] - @margins[3] ph = page.box(:media).height.to_f - @margins[0] - @margins[2] if @scale == :fit || iw > pw || ih > ph ratio = [pw / iw, ph / ih].min iw, ih = iw * ratio, ih * ratio end x, y = @margins[3] + (pw - iw) / 2, @margins[2] + (ph - ih) / 2 page.canvas.image(image, at: [x, y], width: iw, height: ih) end apply_encryption_options(out) apply_optimization_options(out) write_document(out, out_file) end end end end