273 lines
7.9 KiB
Python
273 lines
7.9 KiB
Python
#!/usr/bin/env python
|
|
# -*- coding: utf-8 -*-
|
|
"""
|
|
ghostscript._gsprint - A low-level interface to the Ghostscript C-API using ctypes
|
|
"""
|
|
#
|
|
# Copyright 2010-2018 by Hartmut Goebel <h.goebel@crazy-compilers.com>
|
|
#
|
|
# Display_callback Structure by Lasse Fister <commander@graphicore.de> in 2013
|
|
#
|
|
# 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 <http://www.gnu.org/licenses/>.
|
|
#
|
|
|
|
from __future__ import absolute_import
|
|
|
|
__author__ = "Hartmut Goebel <h.goebel@crazy-compilers.com>"
|
|
__copyright__ = "Copyright 2010-2018 by Hartmut Goebel <h.goebel@crazy-compilers.com>"
|
|
__licence__ = "GNU General Public License version 3 (GPL v3)"
|
|
__version__ = "0.6"
|
|
|
|
import sys
|
|
from ctypes import *
|
|
|
|
from ._errors import e_Quit, e_Info
|
|
|
|
|
|
MAX_STRING_LENGTH = 65535
|
|
|
|
gs_main_instance = c_void_p
|
|
display_callback = c_void_p
|
|
|
|
|
|
class Revision(Structure):
|
|
_fields_ = [
|
|
("product", c_char_p),
|
|
("copyright", c_char_p),
|
|
("revision", c_long),
|
|
("revisiondate", c_long)
|
|
]
|
|
|
|
|
|
class GhostscriptError(Exception):
|
|
def __init__(self, ecode):
|
|
# :todo:
|
|
Exception.__init__(self, error2name(ecode))
|
|
self.code = ecode
|
|
|
|
|
|
def revision():
|
|
"""
|
|
Get version numbers and strings.
|
|
|
|
This is safe to call at any time.
|
|
You should call this first to make sure that the correct version
|
|
of the Ghostscript is being used.
|
|
|
|
Returns a Revision instance
|
|
"""
|
|
revision = Revision()
|
|
rc = libgs.gsapi_revision(pointer(revision), sizeof(revision))
|
|
if rc:
|
|
raise ArgumentError("Revision structure size is incorrect, "
|
|
"requires %s bytes" % rc)
|
|
return revision
|
|
|
|
|
|
def new_instance(): # display_callback=None):
|
|
"""
|
|
Create a new instance of Ghostscript
|
|
|
|
This instance is passed to most other API functions.
|
|
"""
|
|
# :todo: The caller_handle will be provided to callback functions.
|
|
display_callback = None
|
|
instance = gs_main_instance()
|
|
rc = libgs.gsapi_new_instance(pointer(instance), display_callback)
|
|
if rc != 0:
|
|
raise GhostscriptError(rc)
|
|
return instance
|
|
|
|
|
|
def delete_instance(instance):
|
|
"""
|
|
Destroy an instance of Ghostscript
|
|
|
|
Before you call this, Ghostscript must have finished.
|
|
If Ghostscript has been initialised, you must call exit()
|
|
before delete_instance()
|
|
"""
|
|
return libgs.gsapi_delete_instance(instance)
|
|
|
|
|
|
c_stdstream_call_t = CFUNCTYPE(c_int, gs_main_instance, POINTER(c_char), c_int)
|
|
|
|
|
|
def _wrap_stdin(infp):
|
|
"""
|
|
Wrap a filehandle into a C function to be used as `stdin` callback
|
|
for ``set_stdio``. The filehandle has to support the readline() method.
|
|
"""
|
|
|
|
def _wrap(instance, dest, count):
|
|
try:
|
|
data = infp.readline(count)
|
|
except:
|
|
count = -1
|
|
else:
|
|
if not data:
|
|
count = 0
|
|
else:
|
|
count = len(data)
|
|
memmove(dest, c_char_p(data), count)
|
|
return count
|
|
|
|
return c_stdstream_call_t(_wrap)
|
|
|
|
|
|
def _wrap_stdout(outfp):
|
|
"""
|
|
Wrap a filehandle into a C function to be used as `stdout` or
|
|
`stderr` callback for ``set_stdio``. The filehandle has to support the
|
|
write() and flush() methods.
|
|
"""
|
|
|
|
def _wrap(instance, str, count):
|
|
outfp.write(str[:count])
|
|
outfp.flush()
|
|
return count
|
|
|
|
return c_stdstream_call_t(_wrap)
|
|
|
|
|
|
_wrap_stderr = _wrap_stdout
|
|
|
|
|
|
def set_stdio(instance, stdin, stdout, stderr):
|
|
"""
|
|
Set the callback functions for stdio.
|
|
|
|
``stdin``, ``stdout`` and ``stderr`` have to be ``ctypes``
|
|
callback functions matching the ``_gsprint.c_stdstream_call_t``
|
|
prototype. You may want to use _wrap_* to wrap file handles.
|
|
|
|
Note 1: This function only changes stdio of the Postscript
|
|
interpreter, not that of the devices.
|
|
|
|
Note 2: Make sure you keep references to C function objects
|
|
as long as they are used from C code. Otherwise they may be
|
|
garbage collected, crashing your program when a callback is made.
|
|
|
|
The ``stdin`` callback function should return the number of
|
|
characters read, `0` for EOF, or `-1` for error. The `stdout` and
|
|
`stderr` callback functions should return the number of characters
|
|
written.
|
|
|
|
You may pass ``None`` for any of stdin, stdout or stderr , in which
|
|
case the system stdin, stdout resp. stderr will be used.
|
|
"""
|
|
rc = libgs.gsapi_set_stdio(instance, stdin, stdout, stderr)
|
|
if rc not in (0, e_Quit, e_Info):
|
|
raise GhostscriptError(rc)
|
|
return rc
|
|
|
|
|
|
# :todo: set_poll (instance, int(*poll_fn)(void *caller_handle));
|
|
# :todo: set_display_callback(instance, callback):
|
|
|
|
|
|
def init_with_args(instance, argv):
|
|
"""
|
|
Initialise the interpreter.
|
|
|
|
1. If quit or EOF occur during init_with_args(), the return value
|
|
will be e_Quit. This is not an error. You must call exit() and
|
|
must not call any other functions.
|
|
|
|
2. If usage info should be displayed, the return value will be
|
|
e_Info which is not an error. Do not call exit().
|
|
|
|
3. Under normal conditions this returns 0. You would then call one
|
|
or more run_*() functions and then finish with exit()
|
|
"""
|
|
ArgArray = c_char_p * len(argv)
|
|
c_argv = ArgArray(*argv)
|
|
rc = libgs.gsapi_init_with_args(instance, len(argv), c_argv)
|
|
if rc not in (0, e_Quit, e_Info):
|
|
raise GhostscriptError(rc)
|
|
return rc
|
|
|
|
|
|
def exit(instance):
|
|
"""
|
|
Exit the interpreter
|
|
|
|
This must be called on shutdown if init_with_args() has been
|
|
called, and just before delete_instance()
|
|
"""
|
|
rc = libgs.gsapi_exit(instance)
|
|
if rc != 0:
|
|
raise GhostscriptError(rc)
|
|
return rc
|
|
|
|
|
|
def __win32_finddll():
|
|
try:
|
|
import winreg
|
|
except ImportError:
|
|
# assume Python 2
|
|
from _winreg import OpenKey, CloseKey, EnumKey, QueryValueEx, \
|
|
QueryInfoKey, HKEY_LOCAL_MACHINE
|
|
else:
|
|
from winreg import OpenKey, CloseKey, EnumKey, QueryValueEx, \
|
|
QueryInfoKey, HKEY_LOCAL_MACHINE
|
|
|
|
from distutils.version import LooseVersion
|
|
import os
|
|
|
|
dlls = []
|
|
# Look up different variants of Ghostscript and take the highest
|
|
# version for which the DLL is to be found in the filesystem.
|
|
for key_name in ('AFPL Ghostscript', 'Aladdin Ghostscript',
|
|
'GPL Ghostscript', 'GNU Ghostscript'):
|
|
try:
|
|
k1 = OpenKey(HKEY_LOCAL_MACHINE, "Software\\%s" % key_name)
|
|
for num in range(0, QueryInfoKey(k1)[0]):
|
|
version = EnumKey(k1, num)
|
|
try:
|
|
k2 = OpenKey(k1, version)
|
|
dll_path = QueryValueEx(k2, 'GS_DLL')[0]
|
|
CloseKey(k2)
|
|
if os.path.exists(dll_path):
|
|
dlls.append((LooseVersion(version), dll_path))
|
|
except WindowsError:
|
|
pass
|
|
CloseKey(k1)
|
|
except WindowsError:
|
|
pass
|
|
if dlls:
|
|
dlls.sort()
|
|
return dlls[-1][-1]
|
|
else:
|
|
return None
|
|
|
|
|
|
if sys.platform == 'win32':
|
|
libgs = __win32_finddll()
|
|
if not libgs:
|
|
raise RuntimeError('Can not find Ghostscript DLL in registry')
|
|
libgs = windll.LoadLibrary(libgs)
|
|
else:
|
|
try:
|
|
libgs = cdll.LoadLibrary("libgs.so")
|
|
except OSError:
|
|
# shared object file not found
|
|
import ctypes.util
|
|
libgs = ctypes.util.find_library('gs')
|
|
if not libgs:
|
|
raise RuntimeError('Can not find Ghostscript library (libgs)')
|
|
libgs = cdll.LoadLibrary(libgs)
|
|
|
|
del __win32_finddll
|