diff options
author | Julian Smith <jules@op59.net> | 2020-07-24 14:03:51 +0100 |
---|---|---|
committer | Julian Smith <jules@op59.net> | 2020-07-24 17:53:28 +0100 |
commit | f77f99702c9c8418b9516056d5f4280105beafc4 (patch) | |
tree | 8de7d49c98dac3a525c3e81920cbe513fb3a9fbf /demos/python | |
parent | 269f880585dd2c6b3f716668b05f4c911214356b (diff) | |
download | ghostpdl-f77f99702c9c8418b9516056d5f4280105beafc4.tar.gz |
demos/python/gsapi.py: improved encoding/decoding of strings.
Set global _encoding to the encoding passed to gsapi_set_arg_encoding(), and
use it where expected by the underlying C code.
gsapi_set_stdio():
Pass bytes object to stdout and stderr callbacks for convenience, and
document what expectations are.
gsapi_run_string*():
Accept str or bytes; encode the former into bytes using utf-8 encoding.
Diffstat (limited to 'demos/python')
-rwxr-xr-x | demos/python/gsapi.py | 147 |
1 files changed, 117 insertions, 30 deletions
diff --git a/demos/python/gsapi.py b/demos/python/gsapi.py index ad9fadebb..0888ee608 100755 --- a/demos/python/gsapi.py +++ b/demos/python/gsapi.py @@ -59,13 +59,15 @@ def gsapi_revision(): ''' Returns (e, r) where <r> is a gsapi_revision_t. ''' + # [unicode: we assume that underlying gsapi_revision() returns utf-8 + # strings.] _r = _gsapi_revision_t() e = _libgs.gsapi_revision(ctypes.byref(_r), ctypes.sizeof(_r)) if e: return e, None r = gsapi_revision_t( - _r.product.decode('latin-1'), - _r.copyright.decode('latin-1'), + _r.product.decode('utf-8'), + _r.copyright.decode('utf-8'), _r.revision, _r.revisiondate, ) @@ -87,9 +89,42 @@ def gsapi_delete_instance(instance): def gsapi_set_stdio(instance, stdin_fn, stdout_fn, stderr_fn): - stdin_fn2 = _stdio_fn(stdin_fn) if stdin_fn else None - stdout_fn2 = _stdio_fn(stdout_fn) if stdout_fn else None - stderr_fn2 = _stdio_fn(stderr_fn) if stderr_fn else None + ''' + stdin_fn: + If not None, will be called with (caller_handle, text, len_) + where <text> is a ctypes.LP_c_char of length <len_>. + + [todo: wrap this to be easier to use from Python?] + + stdout_fn and stderr_fn: + If not None, called with (caller_handle, text): + caller_handle: + As passed originally to gsapi_new_instance(). + text: + A Python bytes object. + Should return the number of bytes of <text> that they handled; for + convenience None is converted to len(text). + ''' + # [unicode: we do not do any encoding or decoding; stdin_fn should encode + # and stdout_fn and stderr_fn should decode. ] + def make_out(fn): + if not fn: + return None + def out(caller_handle, text, len_): + text2 = text[:len_] # converts from ctypes.LP_c_char to bytes. + ret = fn(caller_handle, text2) + if ret is None: + return len_ + return ret + return _stdio_fn(out) + def make_in(fn): + if not fn: + return None + return _stdio_fn(fn) + + stdout_fn2 = make_out(stdout_fn) + stderr_fn2 = make_out(stderr_fn) + stdin_fn2 = make_in(stdin_fn) e = _libgs.gsapi_set_stdio(instance, stdout_fn2, stdout_fn2, stdout_fn2) if not e: # Need to keep references to call-back functions. @@ -161,12 +196,11 @@ def gsapi_set_display_callback(instance, callback): def gsapi_set_default_device_list(instance, list_): + # [unicode: we assume that underlying gsapi_set_default_device_list() is + # expecting list_ to be in utf-8 encoding.] assert isinstance(list_, str) - e = _libgs.gsapi_set_default_device_list( - instance, - list_.encode('latin-1'), - len(list_), - ) + list_2 = list_.encode('utf-8') + e = _libgs.gsapi_set_default_device_list(instance, list_2, len(list_)) return e @@ -174,6 +208,8 @@ def gsapi_get_default_device_list(instance): ''' Returns (e, list) where <list> is a string. ''' + # [unicode: we assume underlying gsapi_get_default_device_list() returns + # strings encoded as latin-1.] list_ = ctypes.POINTER(ctypes.c_char)() len_ = ctypes.c_int() e = _libgs.gsapi_get_default_device_list( @@ -183,7 +219,7 @@ def gsapi_get_default_device_list(instance): ) if e: return e, '' - return e, list_[:len_.value] + return e, list_[:len_.value].decode('latin-1') GS_ARG_ENCODING_LOCAL = 0 @@ -192,16 +228,33 @@ GS_ARG_ENCODING_UTF16LE = 2 def gsapi_set_arg_encoding(instance, encoding): + assert encoding in ( + GS_ARG_ENCODING_LOCAL, + GS_ARG_ENCODING_UTF8, + GS_ARG_ENCODING_UTF16LE, + ) e = _libgs.gsapi_set_arg_encoding(instance, encoding) + if not e: + if encoding == GS_ARG_ENCODING_LOCAL: + # This is probably wrong on Windows. + _encoding = 'utf-8' + elif encoding == GS_ARG_ENCODING_UTF8: + _encoding = 'utf-8' + elif encoding == GS_ARG_ENCODING_UTF16LE: + _encoding = 'utf-16-le' return e def gsapi_init_with_args(instance, args): + # [unicode: we assume that underlying gsapi_init_with_args() + # expects strings in args[] to be encoded in encoding set by + # gsapi_set_arg_encoding().] + # Create copy of args in format expected by C. argc = len(args) argv = (_pchar * (argc + 1))() for i, arg in enumerate(args): - enc_arg = arg.encode('utf-8') + enc_arg = arg.encode(_encoding) argv[i] = ctypes.create_string_buffer(enc_arg) argv[argc] = None @@ -220,8 +273,14 @@ def gsapi_run_string_begin(instance, user_errors): def gsapi_run_string_continue(instance, str_, user_errors): ''' + <str_> should be either a python string or a bytes object. If the former, + it is converted into a bytes object using utf-8 encoding. + Returns (e, exit_code). ''' + if isinstance(str_, str): + str_ = str_.encode('utf-8') + assert isinstance(str_, bytes) pexit_code = ctypes.c_int() e = _libgs.gsapi_run_string_continue( instance, @@ -238,31 +297,43 @@ def gsapi_run_string_end(instance, user_errors): Returns (e, exit_code). ''' pexit_code = ctypes.c_int() - e = _libgs.gsapi_run_string_end(instance, user_errors, ctypes.byref(pexit_code)) + e = _libgs.gsapi_run_string_end( + instance, + user_errors, + ctypes.byref(pexit_code), + ) return e, pexit_code.value def gsapi_run_string_with_length(instance, str_, length, user_errors): ''' + <str_> should be either a python string or a bytes object. If the former, + it is converted into a bytes object using utf-8 encoding. + Returns (e, exit_code). ''' - pexit_code = ctypes.c_int() - e = _libgs.gsapi_run_string_with_length( - instance, - str_, - length, - user_errors, - ctypes.byref(pexit_code), - ) - return e, pexit_code.value + return gsapi_run_string(instance, str_[:length], user_errors) def gsapi_run_string(instance, str_, user_errors): ''' + <str_> should be either a python string or a bytes object. If the former, + it is converted into a bytes object using utf-8 encoding. + Returns (e, exit_code). ''' + if isinstance(str_, str): + str_ = str_.encode('utf-8') + assert isinstance(str_, bytes) pexit_code = ctypes.c_int() - e = _libgs.gsapi_run_string(instance, str_, user_errors, ctypes.byref(pexit_code)) + # We use gsapi_run_string_with_length() because str_ might contain zeros. + e = _libgs.gsapi_run_string_with_length( + instance, + str_, + len(str_), + user_errors, + ctypes.byref(pexit_code), + ) return e, pexit_code.value @@ -270,8 +341,11 @@ def gsapi_run_file(instance, filename, user_errors): ''' Returns (e, exit_code). ''' + # [unicode: we assume that underlying gsapi_run_file() expects <filename> + # to be encoded in encoding set by gsapi_set_arg_encoding().] pexit_code = ctypes.c_int() - e = _libgs.gsapi_run_file(instance, filename, user_errors, ctypes.byref(pexit_code)) + filename2 = filename.encode(_encoding) + e = _libgs.gsapi_run_file(instance, filename2, user_errors, ctypes.byref(pexit_code)) return e, pexit_code.value @@ -293,6 +367,8 @@ gs_spt_size_t = 8 # void * is a size_t *. def gsapi_set_param(instance, param, value): + # [unicode: we assume that underlying gsapi_set_param() expects <param> and + # string <value> to be encoded as latin-1.] param2 = param.encode('latin-1') if 0: pass elif isinstance(value, bool): @@ -321,12 +397,18 @@ GS_PERMIT_FILE_CONTROL = 2 def gsapi_add_control_path(instance, type_, path): - e = _libgs.gsapi_add_control_path(instance, type_, path) + # [unicode: we assume that underlying gsapi_add_control_path() expects + # <path> to be encoded in encoding set by gsapi_set_arg_encoding().] + path2 = path.encode(_encoding) + e = _libgs.gsapi_add_control_path(instance, type_, path2) return e def gsapi_remove_control_path(instance, type_, path): - e = _libgs.gsapi_remove_control_path(instance, type_, path) + # [unicode: we assume that underlying gsapi_remove_control_path() expects + # <path> to be encoded in encoding set by gsapi_set_arg_encoding().] + path2 = path.encode(_encoding) + e = _libgs.gsapi_remove_control_path(instance, type_, path2) return e @@ -352,6 +434,12 @@ def gsapi_is_path_control_active(instance): _libgs = ctypes.CDLL('libgs.so') +# The encoding that we use when passing strings to the underlying gsapi_*() C +# functions. Changed by gsapi_set_arg_encoding(). +# +# This default is probably incorrect on Windows. +# +_encoding = 'utf-8' class _gsapi_revision_t(ctypes.Structure): _fields_ = [ @@ -529,7 +617,7 @@ if __name__ == '__main__': assert not e - e, instance = gsapi_new_instance(0) + e, instance = gsapi_new_instance(1) print('gsapi_new_instance => e=%s: %s' % (e, instance)) assert not e @@ -537,9 +625,8 @@ if __name__ == '__main__': print('gsapi_set_arg_encoding => e=%s' % e) assert not e - def stdout_fn(caller_handle, str_, len_): - sys.stdout.write(str_[:len_].decode('latin-1').replace('\n', '\n*** ')) - return len_ + def stdout_fn(caller_handle, bytes_): + sys.stdout.write(bytes_.decode('latin-1')) e = gsapi_set_stdio(instance, None, stdout_fn, None) print('gsapi_set_stdio => e=%s' % e) assert not e |