Mercurial > hgrepos > Python2 > PyMuPDF
diff mupdf-source/scripts/wrap/csharp.py @ 2:b50eed0cc0ef upstream
ADD: MuPDF v1.26.7: the MuPDF source as downloaded by a default build of PyMuPDF 1.26.4.
The directory name has changed: no version number in the expanded directory now.
| author | Franz Glasner <fzglas.hg@dom66.de> |
|---|---|
| date | Mon, 15 Sep 2025 11:43:07 +0200 |
| parents | |
| children |
line wrap: on
line diff
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/mupdf-source/scripts/wrap/csharp.py Mon Sep 15 11:43:07 2025 +0200 @@ -0,0 +1,325 @@ +''' +Things for generating C#-specific output. +''' +from . import cpp +from . import parse +from . import rename +from . import state +from . import util + +import jlib + +import textwrap +import os + + +def make_outparam_helper_csharp( + tu, + cursor, + fnname, + fnname_wrapper, + generated, + main_name, + ): + ''' + Write C# code for a convenient tuple-returning wrapper for MuPDF + function that has out-params. We use the C# wrapper for our generated + {main_name}_outparams() function. + + We don't attempt to handle functions that take unsigned char* args + because these generally indicate sized binary data and cannot be handled + generically. + ''' + def write(text): + generated.swig_csharp.write(text) + + main_name = rename.ll_fn(cursor.mangled_name) + return_void = cursor.result_type.spelling == 'void' + if fnname == 'fz_buffer_extract': + # Write custom wrapper that returns the binary data as a C# bytes + # array, using the C# wrapper for buffer_extract_outparams_fn(fz_buffer + # buf, buffer_extract_outparams outparams). + # + write( + textwrap.dedent( + f''' + + // Custom C# wrapper for fz_buffer_extract(). + public static class mupdf_{rename.class_('fz_buffer')}_extract + {{ + public static byte[] {rename.method('fz_buffer', 'fz_buffer_extract')}(this mupdf.{rename.class_('fz_buffer')} buffer) + {{ + var outparams = new mupdf.{rename.ll_fn('fz_buffer_storage')}_outparams(); + uint n = mupdf.mupdf.{rename.ll_fn('fz_buffer_storage')}_outparams_fn(buffer.m_internal, outparams); + var raw1 = mupdf.SWIGTYPE_p_unsigned_char.getCPtr(outparams.datap); + System.IntPtr raw2 = System.Runtime.InteropServices.HandleRef.ToIntPtr(raw1); + byte[] ret = new byte[n]; + // Marshal.Copy() raises exception if <raw2> is null even if <n> is zero. + if (n == 0) return ret; + System.Runtime.InteropServices.Marshal.Copy(raw2, ret, 0, (int) n); + buffer.{rename.method( 'fz_buffer', 'fz_clear_buffer')}(); + buffer.{rename.method( 'fz_buffer', 'fz_trim_buffer')}(); + return ret; + }} + }} + ''') + ) + return + + # We don't attempt to generate wrappers for fns that take or return + # 'unsigned char*' - swig does not treat these as zero-terminated strings, + # and they are generally binary data so cannot be handled generically. + # + if parse.is_pointer_to(cursor.result_type, 'unsigned char'): + jlib.log(f'Cannot generate C# out-param wrapper for {cursor.mangled_name} because it returns unsigned char*.', 1) + return + for arg in parse.get_args( tu, cursor): + if parse.is_pointer_to(arg.cursor.type, 'unsigned char'): + jlib.log(f'Cannot generate C# out-param wrapper for {cursor.mangled_name} because has unsigned char* arg.', 1) + return + if parse.is_pointer_to_pointer_to(arg.cursor.type, 'unsigned char'): + jlib.log(f'Cannot generate C# out-param wrapper for {cursor.mangled_name} because has unsigned char** arg.', 1) + return + if arg.cursor.type.get_array_size() >= 0: + jlib.log(f'Cannot generate C# out-param wrapper for {cursor.mangled_name} because has array arg.', 1) + return + if arg.cursor.type.kind == state.clang.cindex.TypeKind.POINTER: + pointee = state.get_name_canonical( arg.cursor.type.get_pointee()) + if pointee.kind == state.clang.cindex.TypeKind.ENUM: + jlib.log(f'Cannot generate C# out-param wrapper for {cursor.mangled_name} because has enum out-param arg.', 1) + return + if pointee.kind == state.clang.cindex.TypeKind.FUNCTIONPROTO: + jlib.log(f'Cannot generate C# out-param wrapper for {cursor.mangled_name} because has fn-ptr arg.', 1) + return + if pointee.is_const_qualified(): + # Not an out-param. + jlib.log(f'Cannot generate C# out-param wrapper for {cursor.mangled_name} because has pointer-to-const arg.', 1) + return + if arg.cursor.type.get_pointee().spelling == 'FILE': + jlib.log(f'Cannot generate C# out-param wrapper for {cursor.mangled_name} because has FILE* arg.', 1) + return + if pointee.spelling == 'void': + jlib.log(f'Cannot generate C# out-param wrapper for {cursor.mangled_name} because has void* arg.', 1) + return + + num_return_values = 0 if return_void else 1 + for arg in parse.get_args( tu, cursor): + if arg.out_param: + num_return_values += 1 + assert num_return_values + + if num_return_values > 7: + # On linux, mono-csc can fail with: + # System.NotImplementedException: tuples > 7 + # + jlib.log(f'Cannot generate C# out-param wrapper for {cursor.mangled_name} because would require > 7-tuple.') + return + + # Write C# wrapper. + arg0, _ = parse.get_first_arg( tu, cursor) + if not arg0.alt: + return + + method_name = rename.method( arg0.alt.type.spelling, fnname) + + write(f'\n') + write(f'// Out-params extension method for C# class {rename.class_(arg0.alt.type.spelling)} (wrapper for MuPDF struct {arg0.alt.type.spelling}),\n') + write(f'// adding class method {method_name}() (wrapper for {fnname}())\n') + write(f'// which returns out-params directly.\n') + write(f'//\n') + write(f'public static class mupdf_{main_name}_outparams_helper\n') + write(f'{{\n') + write(f' public static ') + + def write_type(alt, type_): + if alt: + write(f'mupdf.{rename.class_(alt.type.spelling)}') + elif parse.is_pointer_to(type_, 'char'): + write( f'string') + else: + text = cpp.declaration_text(type_, '').strip() + if text == 'int16_t': text = 'short' + elif text == 'int64_t': text = 'long' + elif text == 'size_t': text = 'ulong' + elif text == 'unsigned int': text = 'uint' + elif text.startswith('enum '): + # This is primarily for enum pdf_zugferd_profile; C# does not + # like `enum` prefix, and we need to specify namespace name + # `mupdf`. + text = text[5:] + if text.startswith('pdf_') or text.startswith('fz_'): + text = f'{rename.namespace()}.{text}' + write(f'{text}') + + # Generate the returned tuple. + # + if num_return_values > 1: + write('(') + + sep = '' + + # Returned param, if any. + if not return_void: + return_alt = None + base_type_cursor, base_typename, extras = parse.get_extras( tu, cursor.result_type) + if extras: + if extras.opaque: + # E.g. we don't have access to definition of fz_separation, + # but it is marked in classextras with opaque=true, so + # there will be a wrapper class. + return_alt = base_type_cursor + elif base_type_cursor.kind == state.clang.cindex.CursorKind.STRUCT_DECL: + return_alt = base_type_cursor + write_type(return_alt, cursor.result_type) + sep = ', ' + + # Out-params. + for arg in parse.get_args( tu, cursor): + if arg.out_param: + write(sep) + write_type(arg.alt, arg.cursor.type.get_pointee()) + if num_return_values > 1: + write(f' {arg.name_csharp}') + sep = ', ' + + if num_return_values > 1: + write(')') + + # Generate function name and params. If first arg is a wrapper class we + # use C#'s 'this' keyword to make this a member function of the wrapper + # class. + #jlib.log('outputs fn {fnname=}: is member: {"yes" if arg0.alt else "no"}') + write(f' ') + write( method_name if arg0.alt else 'fn') + write(f'(') + if arg0.alt: write('this ') + sep = '' + for arg in parse.get_args( tu, cursor): + if arg.out_param: + continue + write(sep) + if arg.alt: + # E.g. 'Document doc'. + write(f'mupdf.{rename.class_(arg.alt.type.spelling)} {arg.name_csharp}') + elif parse.is_pointer_to(arg.cursor.type, 'char'): + write(f'string {arg.name_csharp}') + else: + text = cpp.declaration_text(arg.cursor.type, arg.name_csharp).strip() + text = util.clip(text, 'const ') + text = text.replace('int16_t ', 'short ') + text = text.replace('int64_t ', 'long ') + text = text.replace('size_t ', 'uint ') + text = text.replace('unsigned int ', 'uint ') + write(text) + sep = ', ' + write(f')\n') + + # Function body. + # + write(f' {{\n') + + # Create local outparams struct. + write(f' var outparams = new mupdf.{main_name}_outparams();\n') + write(f' ') + + # Generate function call. + # + # The C# *_outparams_fn() generated by swig is inside namespace mupdf { + # class mupdf { ... } }, so we access it using the rather clumsy prefix + # 'mupdf.mupdf.'. It will have been generated from a C++ function + # (generate by us) which is in top-level namespace mupdf, but swig + # appears to generate the same code even if the C++ function is not in + # a namespace. + # + if not return_void: + write(f'var ret = ') + write(f'mupdf.mupdf.{main_name}_outparams_fn(') + sep = '' + for arg in parse.get_args( tu, cursor): + if arg.out_param: + continue + write(f'{sep}{arg.name_csharp}') + if arg.alt: + extras = parse.get_fz_extras( tu, arg.alt.type.spelling) + assert extras.pod != 'none' \ + 'Cannot pass wrapper for {type_.spelling} as arg because pod is "none" so we cannot recover struct.' + write('.internal_()' if extras.pod == 'inline' else '.m_internal') + sep = ', ' + write(f'{sep}outparams);\n') + + # Generate return of tuple. + write(f' return ') + if num_return_values > 1: + write(f'(') + sep = '' + if not return_void: + if return_alt: + write(f'new mupdf.{rename.class_(return_alt.type.spelling)}(ret)') + else: + write(f'ret') + sep = ', ' + for arg in parse.get_args( tu, cursor): + if arg.out_param: + write(f'{sep}') + type_ = arg.cursor.type.get_pointee() + if arg.alt: + write(f'new mupdf.{rename.class_(arg.alt.type.spelling)}(outparams.{arg.name_csharp})') + elif 0 and parse.is_pointer_to(type_, 'char'): + # This was intended to convert char* to string, but swig + # will have already done that when making a C# version of + # the C++ struct, and modern csc on Windows doesn't like + # creating a string from a string for some reason. + write(f'new string(outparams.{arg.name_csharp})') + else: + write(f'outparams.{arg.name_csharp}') + sep = ', ' + if num_return_values > 1: + write(')') + write(';\n') + write(f' }}\n') + write(f'}}\n') + + +def csharp_settings(build_dirs): + ''' + Returns (csc, mono, mupdf_cs). + + csc: C# compiler. + mono: C# interpreter ("" on Windows). + mupdf_cs: MuPDF C# code. + + `mupdf_cs` will be None if `build_dirs` is false. + + E.g. on Windows `csc` can be: C:/Program Files (x86)/Microsoft Visual Studio/2019/Community/MSBuild/Current/Bin/Roslyn/csc.exe + ''' + # On linux requires: + # sudo apt install mono-devel + # + # OpenBSD: + # pkg_add mono + # but we get runtime error when exiting: + # mono:build/shared-release/libmupdfcpp.so: undefined symbol '_ZdlPv' + # which might be because of mixing gcc and clang? + # + if state.state_.windows: + import wdev + vs = wdev.WindowsVS() + jlib.log('{vs.description_ml()=}') + csc = vs.csc + jlib.log('{csc=}') + assert csc, f'Unable to find csc.exe' + mono = '' + else: + mono = 'mono' + if state.state_.linux: + csc = 'mono-csc' + elif state.state_.openbsd: + csc = 'csc' + else: + assert 0, f'Do not know where to find mono. {platform.platform()=}' + + if build_dirs: + mupdf_cs = os.path.relpath(f'{build_dirs.dir_so}/mupdf.cs') + else: + mupdf_cs = None + return csc, mono, mupdf_cs
