2024-11-08 22:26:38 +11:00
|
|
|
#!/usr/bin/env python3
|
|
|
|
|
|
|
|
# GIMP plug-in for JBIG2-encoded PDF files
|
|
|
|
# Copyright (C) 2024 Lee Yingtong Li (RunasSudo)
|
|
|
|
#
|
|
|
|
# Loosely adapted from file-openraster.py - Copyright (C) 2009 by Jon Nordby <jononor@gmail.com>, licensed under the GPLv3
|
|
|
|
# In turn based on MyPaint source code by Martin Renold
|
|
|
|
#
|
|
|
|
# This program is free software: you can redistribute it and/or modify
|
|
|
|
# it under the terms of the GNU 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 General Public License for more details.
|
|
|
|
#
|
|
|
|
# You should have received a copy of the GNU General Public License
|
|
|
|
# along with this program. If not, see <https://www.gnu.org/licenses/>.
|
|
|
|
|
|
|
|
import gi
|
|
|
|
gi.require_version('Gimp', '3.0')
|
|
|
|
from gi.repository import Gimp
|
|
|
|
from gi.repository import GObject
|
|
|
|
from gi.repository import GLib
|
|
|
|
from gi.repository import Gio
|
|
|
|
|
|
|
|
import os
|
2024-11-16 19:16:40 +11:00
|
|
|
import shutil
|
2024-11-08 22:26:38 +11:00
|
|
|
import subprocess
|
|
|
|
import sys
|
|
|
|
import tempfile
|
|
|
|
|
|
|
|
def export_jbig2pdf(procedure, run_mode, image, file, options, metadata, config, data):
|
|
|
|
# Check jbig2enc version
|
2024-11-16 19:16:40 +11:00
|
|
|
if shutil.which('jbig2') is None:
|
2024-11-08 22:26:38 +11:00
|
|
|
Gimp.message('jbig2 command (from jbig2enc) not in PATH')
|
|
|
|
return Gimp.ValueArray.new_from_values([GObject.Value(Gimp.PDBStatusType, Gimp.PDBStatusType.EXECUTION_ERROR)])
|
|
|
|
|
2024-11-16 19:16:40 +11:00
|
|
|
if shutil.which('jbig2topdf.py') is None:
|
|
|
|
Gimp.message('jbig2topdf.py command (from jbig2enc) not in PATH')
|
|
|
|
return Gimp.ValueArray.new_from_values([GObject.Value(Gimp.PDBStatusType, Gimp.PDBStatusType.EXECUTION_ERROR)])
|
|
|
|
|
2024-11-08 22:26:38 +11:00
|
|
|
# jbig2enc OK!
|
|
|
|
|
|
|
|
Gimp.progress_init('Exporting PDF JBIG2 image')
|
|
|
|
tempdir = tempfile.mkdtemp('gimp-plugin-file-jbig2pdf')
|
|
|
|
|
|
|
|
def unroll_layers(layers):
|
|
|
|
for layer in layers:
|
|
|
|
if not layer.is_group():
|
|
|
|
yield layer
|
|
|
|
else:
|
|
|
|
for sublayer in unroll_layers(layer.get_children()):
|
|
|
|
yield sublayer
|
|
|
|
|
2024-11-09 03:55:47 +11:00
|
|
|
jb2s_out = []
|
2024-11-08 22:26:38 +11:00
|
|
|
|
|
|
|
# Export image layer by layer (bottom layer first)
|
|
|
|
for i, layer in enumerate(reversed(list(unroll_layers(image.get_layers())))):
|
|
|
|
png_out = os.path.join(tempdir, '{:03d}.png'.format(i))
|
|
|
|
|
|
|
|
offsets = layer.get_offsets()
|
|
|
|
|
|
|
|
# Save layer to new GimpImage
|
|
|
|
tmp_img = Gimp.Image.new(image.get_width(), image.get_height(), image.get_base_type())
|
|
|
|
tmp_layer = Gimp.Layer.new_from_drawable(layer, tmp_img)
|
|
|
|
tmp_img.insert_layer(tmp_layer, None, 0)
|
|
|
|
tmp_layer.merge_filters() # Apply non-destructive effects
|
|
|
|
tmp_layer.flatten() # Remove alpha channel as this confuses jbig2enc
|
|
|
|
tmp_img.crop(layer.get_width(), layer.get_height(), layer.get_offsets().offset_x, layer.get_offsets().offset_y) # Apply layer origin to image origin
|
|
|
|
|
|
|
|
# Export tmp_image as PNG
|
|
|
|
pdb_proc = Gimp.get_pdb().lookup_procedure('file-png-export')
|
|
|
|
pdb_config = pdb_proc.create_config()
|
|
|
|
pdb_config.set_property('run-mode', Gimp.RunMode.NONINTERACTIVE)
|
|
|
|
pdb_config.set_property('image', tmp_img)
|
|
|
|
pdb_config.set_property('file', Gio.File.new_for_path(png_out))
|
|
|
|
pdb_config.set_property('options', None)
|
|
|
|
pdb_config.set_property('interlaced', 0) # As per file-openraster.py
|
|
|
|
pdb_config.set_property('compression', 2) # As per file-openraster.py
|
|
|
|
pdb_config.set_property('bkgd', True) # Write all PNG chunks except oFFs(ets) - GIMP defaults
|
|
|
|
pdb_config.set_property('offs', False)
|
|
|
|
pdb_config.set_property('phys', True)
|
|
|
|
pdb_config.set_property('time', True)
|
|
|
|
pdb_config.set_property('save-transparent', True)
|
|
|
|
pdb_proc.run(pdb_config)
|
|
|
|
|
|
|
|
tmp_img.delete() # Clean up buffer
|
|
|
|
|
2024-11-09 03:55:47 +11:00
|
|
|
# Execute jbig2enc
|
|
|
|
jb2_out = os.path.join(tempdir, '{:03d}.jb2'.format(i))
|
|
|
|
|
|
|
|
try:
|
|
|
|
with open(jb2_out, 'wb') as jb2_file:
|
|
|
|
# Passing "-s" uses lossly JBIG2 encoding, so we do not pass this option
|
|
|
|
subprocess.run(['jbig2', '-p', '-v', png_out], cwd=tempdir, check=True, stdout=jb2_file)
|
|
|
|
except subprocess.CalledProcessError:
|
|
|
|
Gimp.message('Unknown error in jbig2enc')
|
2024-11-08 22:26:38 +11:00
|
|
|
Gimp.progress_end()
|
|
|
|
return Gimp.ValueArray.new_from_values([GObject.Value(Gimp.PDBStatusType, Gimp.PDBStatusType.EXECUTION_ERROR)])
|
2024-11-09 03:55:47 +11:00
|
|
|
|
|
|
|
os.unlink(png_out)
|
|
|
|
jb2s_out.append(jb2_out)
|
2024-11-08 22:26:38 +11:00
|
|
|
|
2024-11-09 03:55:47 +11:00
|
|
|
# Convert to PDF
|
2024-11-08 22:26:38 +11:00
|
|
|
# Use .tmpsave extension, so we don't overwrite a valid file if there is an exception
|
|
|
|
pdf_tmpout = file.peek_path() + '.tmpsave'
|
2024-11-09 03:55:47 +11:00
|
|
|
|
2024-11-08 22:26:38 +11:00
|
|
|
try:
|
|
|
|
with open(pdf_tmpout, 'wb') as pdf_file:
|
2024-11-16 19:16:40 +11:00
|
|
|
subprocess.run(['jbig2topdf.py', '-s'] + jb2s_out, check=True, stdout=pdf_file)
|
2024-11-08 22:26:38 +11:00
|
|
|
success = True
|
2024-11-16 19:16:40 +11:00
|
|
|
except subprocess.CalledProcessError:
|
2024-11-08 22:26:38 +11:00
|
|
|
success = False
|
2024-11-16 19:16:40 +11:00
|
|
|
Gimp.message('Unknown error in jbig2topdf.py')
|
2024-11-08 22:26:38 +11:00
|
|
|
Gimp.progress_end()
|
|
|
|
return Gimp.ValueArray.new_from_values([GObject.Value(Gimp.PDBStatusType, Gimp.PDBStatusType.EXECUTION_ERROR)])
|
|
|
|
|
|
|
|
# Success! - Finalise and clean up
|
|
|
|
|
|
|
|
# Remove intermediate JBIG2 files
|
2024-11-09 03:55:47 +11:00
|
|
|
for i in range(len(jb2s_out)):
|
|
|
|
os.unlink(os.path.join(tempdir, '{:03d}.jb2'.format(i)))
|
2024-11-08 22:26:38 +11:00
|
|
|
|
2024-11-09 03:55:47 +11:00
|
|
|
# Remove temp directory
|
2024-11-08 22:26:38 +11:00
|
|
|
os.rmdir(tempdir)
|
|
|
|
|
|
|
|
# Save over destination file
|
|
|
|
os.rename(pdf_tmpout, file.peek_path())
|
|
|
|
|
|
|
|
Gimp.progress_end()
|
|
|
|
return Gimp.ValueArray.new_from_values([GObject.Value(Gimp.PDBStatusType, Gimp.PDBStatusType.SUCCESS)])
|
|
|
|
|
|
|
|
class FileJBIG2PDF(Gimp.PlugIn):
|
|
|
|
## GimpPlugIn virtual methods ##
|
|
|
|
def do_set_i18n(self, procname):
|
|
|
|
return False
|
|
|
|
|
|
|
|
def do_query_procedures(self):
|
|
|
|
return ['file-jbig2pdf-export']
|
|
|
|
|
|
|
|
def do_create_procedure(self, name):
|
|
|
|
if name == 'file-jbig2pdf-export':
|
|
|
|
procedure = Gimp.ExportProcedure.new(self, name, Gimp.PDBProcType.PLUGIN, False, export_jbig2pdf, None)
|
|
|
|
procedure.set_image_types('*')
|
|
|
|
procedure.set_documentation(
|
|
|
|
'save a PDF file with JBIG2 encoding',
|
|
|
|
'save a PDF file with JBIG2 encoding',
|
|
|
|
name
|
|
|
|
)
|
|
|
|
procedure.set_menu_label('Portable Document Format (JBIG2)')
|
|
|
|
procedure.set_extensions('pdf')
|
|
|
|
else:
|
|
|
|
raise Exception('Unknown procedure')
|
|
|
|
|
|
|
|
procedure.set_attribution(
|
|
|
|
'Lee Yingtong Li (RunasSudo)', # Author
|
|
|
|
'Lee Yingtong Li (RunasSudo)', # Copyright
|
2024-11-16 19:16:40 +11:00
|
|
|
'2024' # Year
|
2024-11-08 22:26:38 +11:00
|
|
|
)
|
|
|
|
return procedure
|
|
|
|
|
|
|
|
Gimp.main(FileJBIG2PDF.__gtype__, sys.argv)
|