93 lines
3.3 KiB
Python
93 lines
3.3 KiB
Python
# pdf-segmented: Generate PDFs using separate compression for foreground and background
|
|
# Copyright (C) 2025 Lee Yingtong Li
|
|
#
|
|
# This program is free software: you can redistribute it and/or modify
|
|
# it under the terms of the GNU Affero General Public License as published by
|
|
# the Free Software Foundation, either version 3 of the License, or
|
|
# (at your option) any later version.
|
|
#
|
|
# This program 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 this program. If not, see <https://www.gnu.org/licenses/>.
|
|
|
|
from . import InputPages
|
|
from ..util import assert_has_imagemagick
|
|
|
|
from PIL import Image
|
|
|
|
import io
|
|
import subprocess
|
|
import sys
|
|
from typing import Generator, List
|
|
|
|
def xcf_get_pages(input_file: str) -> InputPages:
|
|
# Check ImageMagick support
|
|
assert_has_imagemagick('XCF support requires ImageMagick')
|
|
|
|
# Init metadata
|
|
num_layers = 0
|
|
dpi = None
|
|
width = None
|
|
height = None
|
|
|
|
# Read metadata
|
|
proc = subprocess.run(['magick', 'identify', '-verbose', input_file], capture_output=True, encoding='utf-8', check=True)
|
|
for line in proc.stdout.splitlines():
|
|
if line == 'Image:':
|
|
num_layers += 1
|
|
elif line.startswith(' Geometry: '):
|
|
layer_width = float(line[len(' Geometry: '):line.index('x')])
|
|
layer_height = float(line[line.index('x')+1:line.index('+')])
|
|
if (width is not None and layer_width != width) or (height is not None and layer_height != height):
|
|
print('Error: Image with variable-dimension layers is not supported ({}x{} vs {}x{})'.format(layer_width, layer_height, width, height))
|
|
sys.exit(1)
|
|
width = layer_width
|
|
height = layer_height
|
|
elif line.startswith(' Resolution: '):
|
|
resolution_x = float(line[len(' Resolution: '):line.index('x')])
|
|
resolution_y = float(line[line.index('x')+1:])
|
|
if resolution_x != resolution_y:
|
|
raise Exception('Unexpected non-square DPI ({}x{})'.format(resolution_x, resolution_y))
|
|
if dpi is not None and resolution_x != dpi:
|
|
raise Exception('Unexpected variable DPI image ({} vs {})'.format(resolution_x, dpi))
|
|
dpi = resolution_x
|
|
elif line.startswith(' Units: '):
|
|
if line != ' Units: PixelsPerInch':
|
|
raise Exception('Unexpected Units (expected PixelsPerInch, got {})'.format(line[len(' Units: '):]))
|
|
|
|
if num_layers == 0:
|
|
raise Exception('Unexpected 0 layers')
|
|
if dpi is None:
|
|
raise Exception('Unexpected no DPI information')
|
|
if width is None:
|
|
raise Exception('Unexpected no width information')
|
|
if height is None:
|
|
raise Exception('Unexpected no height information')
|
|
|
|
return InputPages(
|
|
file_name=input_file,
|
|
num_pages=num_layers,
|
|
width=width,
|
|
height=height,
|
|
dpi=dpi,
|
|
pages=_do_get_pages(input_file, num_layers)
|
|
)
|
|
|
|
def _do_get_pages(input_file: str, num_layers: int) -> Generator[Image]:
|
|
for layer_num in range(num_layers):
|
|
# Extract layer as PNG (to proc.stdout)
|
|
proc = subprocess.run(['magick', '{}[{}]'.format(input_file, layer_num), 'png:-'], capture_output=True)
|
|
|
|
if proc.returncode != 0:
|
|
raise Exception('ImageMagick error')
|
|
|
|
# Read into PIL Image
|
|
png_data = io.BytesIO(proc.stdout)
|
|
image = Image.open(png_data)
|
|
|
|
yield image
|