pdf-slide-divider スライドが1ページに複数面割り付けられている(例えば3×3面とか2×2面とか)PDFファイルを1ページ1面ごとに分割する

2020/06/19 1:05
例えば何かの資料として、PowerPointで作成したとおぼしきスライドを1ページに複数面割り付けたPDFファイルを受け取ることがあります。
このままだと製本やファイリングしにくいので、1面1枚ずつに分割したいときがあります。
そのときのためのツールです。
「2 in 1 印刷」や「冊子印刷」に対応した形式でも出力できます。

処理のイメージ
divide_image.png



Perlで書いています。Imagemagick が要ります。

ウェブ上で使える、CGI版も用意しました。
https://satomichan.jp/cgi/pdf-slide-divider/


ソースは
#!/usr/bin/perl -w

use strict;
use utf8;

use File::Basename;

our $magick_convert_cmd = '/usr/bin/convert';
our $magick_mogrify_cmd = '/usr/bin/mogrify';
our $identify_cmd       = '/usr/bin/identify';
our $rm_cmd             = '/bin/rm';

our $temp_dir = (-d './temp' ? './temp' : '.');


#ARGV[0] オリジナルPDFファイル名
#ARGV[1] 分割後のPDFファイル名
#ARGV[2] オリジナル1ページあたりの、ヨコに割り付けられているページ数
#ARGV[3] オリジナル1ページあたりの、タテに割り付けられているページ数
#ARGV[4] オリジナルPDFファイル 余白率(%)
#ARGV[5] 生成後ページから余白除去するか (1=する/0=しない)
#ARGV[6] 2in1 出力するか (1=する/0=しない;1枚ごと)
#ARGV[7] 冊子出力するか (1=する/0=しない)
#ARGV[8] A4縦にリサイズするか (1=する/0=しない)
#ARGV[9] density 解像度 (Default=400)
# ex) ./pdfdivider.pl in.pdf out.pdf 3 3 6 1 1 1 1 200

my($org_pdf, $divided_pdf, $x_pages, $y_pages, $space, $is_trim, $is_2in1, $is_booklet, $is_resize_a4v, $density) = @ARGV;
die unless ($org_pdf && $divided_pdf);

#一時ファイル用ディレクトリを変更
my $new_temp_dir = "${temp_dir}/". basename($org_pdf);
mkdir($new_temp_dir) or die;
$temp_dir = $new_temp_dir;

#オリジナルPDFのページ数取得
my $org_pages;
open(my $PAGES, "$identify_cmd $org_pdf | /usr/bin/wc -l |");
my $line = <$PAGES>;
if($line =~ /(\d+)/){
	$org_pages = $1;
}else{
	die "Can't get number of pages.";
}
close($PAGES);

#
my $x_percent = 100 / $x_pages;
my $y_percent = 100 / $y_pages;


my @splitted_pages;

foreach (my $page = 0; $page < $org_pages; $page++){
	my $temp_basefn_a_page = "temp_". basename($org_pdf). "_${page}";
	system("$magick_convert_cmd -density $density +profile icc ${org_pdf}[${page}] ${temp_dir}/${temp_basefn_a_page}.bmp");

	my($x, $y) = get_image_size("${temp_dir}/${temp_basefn_a_page}.bmp");
	my $x_center = $x - int( $x * $space / 100 );
	my $y_center = $y - int( $y * $space / 100 );
	system("$magick_mogrify_cmd -gravity center -crop ${x_center}x${y_center}+0+0 ${temp_dir}/${temp_basefn_a_page}.bmp");
	
	system("$magick_convert_cmd ${temp_dir}/${temp_basefn_a_page}.bmp +profile icc -crop ${x_percent}%x${y_percent}% ${temp_dir}/${temp_basefn_a_page}.bmp");
	
	for my $p (0..($x_pages * $y_pages - 1) ){
		#生成後ページから余白除去
		if($is_trim){
			system("$magick_mogrify_cmd -trim -fuzz 1% +repage ${temp_dir}/${temp_basefn_a_page}-${p}.bmp");
		}
		my($x_trimed, $y_trimed) = &get_image_size("${temp_dir}/${temp_basefn_a_page}-${p}.bmp");
		push(@splitted_pages, "${temp_basefn_a_page}-${p}.bmp") unless ($x_trimed == 1 && $y_trimed == 1);
	}
}


#2枚ずつ縦に結合
if($is_2in1){
	my @joined_pages;
	while(my $up = shift @splitted_pages){
		my $down = shift @splitted_pages;
		
		if(!defined $down){
			my($up_size_x, $up_size_y) = &get_image_size("${temp_dir}/$up");
			$down = basename($org_pdf). "_whitepage.bmp";
			system("$magick_convert_cmd -size ${up_size_x}x${up_size_y} xc:white ${temp_dir}/$down");
		}
		
		my $joined = "temp_joined_". $up. "_and_". $down;
		system("$magick_convert_cmd -append ${temp_dir}/$up ${temp_dir}/$down ${temp_dir}/$joined");
		push(@joined_pages, $joined);
	}

	@splitted_pages = @joined_pages;
}


#冊子出力
if($is_booklet){
	@splitted_pages = &make_booklet(@splitted_pages);
}


#強制A4縦リサイズ
if($is_resize_a4v){
	resize_a4v($density, @splitted_pages);
}


my @splitted_pages2;
foreach my $a_p (@splitted_pages){
	push(@splitted_pages2, "${temp_dir}/$a_p");
}

system("$magick_convert_cmd -density $density -units PixelsPerInch -compress zip ". join(" ", @splitted_pages2). " $divided_pdf");

system("$rm_cmd ${temp_dir}/*");
system("$rm_cmd -r ${temp_dir}");

exit;



sub make_booklet {
	my(@org_pages) = @_;
	
	my @booklet_pages;

	#4の倍数になっていないとき
	my $r = ($#org_pages + 1) % 4;
	foreach my $i (1.. 4 - $r){
		my $t_page = $org_pages[$#org_pages -$i +1];
		my($t_x, $t_y) = &get_image_size("${temp_dir}/${t_page}");
		my $white_page = "booklet_white_${i}.bmp";
		system("$magick_convert_cmd -size ${t_x}x${t_y} xc:white ${temp_dir}/${white_page}");
		push(@org_pages, $white_page);
	
	}
	
	while(1){
		#A面
		push(@booklet_pages, &make_booklet_half(   pop(@org_pages) ,   shift(@org_pages)   ));
		#B面
		push(@booklet_pages, &make_booklet_half(   shift(@org_pages) , pop(@org_pages)     ));
		
		last if(@org_pages == 0);
	}
	
	return @booklet_pages;
}



sub make_booklet_half {
	my($left, $right) = @_;

	my($l_x, $l_y) = &get_image_size("${temp_dir}/$left");
	my($r_x, $r_y) = &get_image_size("${temp_dir}/$right");

	#左右の幅xが違ったら、そろえる
	my $diff = 0;
	if($l_x > $r_x){
		#左が幅広→右に空白を付加
		$diff = $l_x - $r_x;
		system("$magick_convert_cmd -size ${diff}x5 xc:white ${temp_dir}/diff_white.bmp");
		system("$magick_convert_cmd ${temp_dir}/diff_white.bmp ${temp_dir}/$right +append ${temp_dir}/appended.bmp");
		rename("${temp_dir}/appended.bmp", "${temp_dir}/$right");
	}elsif($l_x < $r_x){
		#右が幅広→左に空白を付加
		$diff = $r_x - $l_x;
		system("$magick_convert_cmd -size ${diff}x5 xc:white ${temp_dir}/diff_white.bmp");
		system("$magick_convert_cmd ${temp_dir}/$left ${temp_dir}/diff_white.bmp +append ${temp_dir}/appended.bmp");
		rename("${temp_dir}/appended.bmp", "${temp_dir}/$left");
	}
	
	#パンチ穴よけマージン生成
	my $m = int(  ( 0.13 * ($l_x + $r_x + $diff) ) / (1-0.13)  );
	#my $m = $density * 1.5;
	system("$magick_convert_cmd -size ${m}x5 xc:white ${temp_dir}/center_white.bmp");
	
	#連結
	my $ret = "${left}_${right}_joined.bmp";
	system("$magick_convert_cmd ${temp_dir}/$left ${temp_dir}/center_white.bmp ${temp_dir}/$right +append ${temp_dir}/${ret}");

	return $ret;
}



sub get_image_size {
	my($image_filename) = @_;
	
	my $x_size = -1;
	my $y_size = -1;

	open(my $INFO, "$identify_cmd $image_filename |");
	my $line = <$INFO>;
	if($line =~ / (\d+)x(\d+) /){
		$x_size = $1;
		$y_size = $2;
	}
	close($INFO);

	return ($x_size, $y_size);
}



sub resize_a4v {
	my($density, @pages) = @_;
	
	my $a4x = int( $density *  8.27 + 0.5);
	my $a4y = int( $density * 11.69 + 0.5);
	
	foreach my $a_page (@pages){
	
		my($p_x, $p_y) = &get_image_size("${temp_dir}/${a_page}");

		if($p_x > $p_y){
			system("$magick_mogrify_cmd -rotate -90 ${temp_dir}/${a_page}");
		}

		system("$magick_mogrify_cmd -resize ${a4x}x${a4y} ${temp_dir}/${a_page}");
		my($resized_x, $resized_y) = &get_image_size("${temp_dir}/${a_page}");
		
		if($a4x > $resized_x){
			my $diff_x = $a4x - $resized_x;
			my $diff_l = int( $diff_x /2);
			my $diff_r = $diff_x - $diff_l;
		
			system("$magick_convert_cmd -size ${diff_l}x${a4y} xc:white ${temp_dir}/diff_white_l.bmp");
			system("$magick_convert_cmd -size ${diff_r}x${a4y} xc:white ${temp_dir}/diff_white_r.bmp");
			system("$magick_convert_cmd ${temp_dir}/diff_white_l.bmp ${temp_dir}/${a_page}".
			       " ${temp_dir}/diff_white_r.bmp +append ${temp_dir}/appended_a4v.bmp");
			rename("${temp_dir}/appended_a4v.bmp", "${temp_dir}/${a_page}");
		}elsif($a4y > $resized_y){
			my $diff_y = $a4y - $resized_y;
			my $diff_t = int( $diff_y /2);
			my $diff_b = $diff_y - $diff_t;
		
			system("$magick_convert_cmd -size ${a4x}x${diff_t} xc:white ${temp_dir}/diff_white_t.bmp");
			system("$magick_convert_cmd -size ${a4x}x${diff_b} xc:white ${temp_dir}/diff_white_b.bmp");
			system("$magick_convert_cmd ${temp_dir}/diff_white_t.bmp ${temp_dir}/${a_page}".
			       " ${temp_dir}/diff_white_b.bmp -append ${temp_dir}/appended_a4v.bmp");
			rename("${temp_dir}/appended_a4v.bmp", "${temp_dir}/${a_page}");
		}

	}

}