# 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 . 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