Mercurial > hgrepos > Python2 > PyMuPDF
comparison 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 |
comparison
equal
deleted
inserted
replaced
| 1:1d09e1dec1d9 | 2:b50eed0cc0ef |
|---|---|
| 1 ''' | |
| 2 Things for generating C#-specific output. | |
| 3 ''' | |
| 4 from . import cpp | |
| 5 from . import parse | |
| 6 from . import rename | |
| 7 from . import state | |
| 8 from . import util | |
| 9 | |
| 10 import jlib | |
| 11 | |
| 12 import textwrap | |
| 13 import os | |
| 14 | |
| 15 | |
| 16 def make_outparam_helper_csharp( | |
| 17 tu, | |
| 18 cursor, | |
| 19 fnname, | |
| 20 fnname_wrapper, | |
| 21 generated, | |
| 22 main_name, | |
| 23 ): | |
| 24 ''' | |
| 25 Write C# code for a convenient tuple-returning wrapper for MuPDF | |
| 26 function that has out-params. We use the C# wrapper for our generated | |
| 27 {main_name}_outparams() function. | |
| 28 | |
| 29 We don't attempt to handle functions that take unsigned char* args | |
| 30 because these generally indicate sized binary data and cannot be handled | |
| 31 generically. | |
| 32 ''' | |
| 33 def write(text): | |
| 34 generated.swig_csharp.write(text) | |
| 35 | |
| 36 main_name = rename.ll_fn(cursor.mangled_name) | |
| 37 return_void = cursor.result_type.spelling == 'void' | |
| 38 if fnname == 'fz_buffer_extract': | |
| 39 # Write custom wrapper that returns the binary data as a C# bytes | |
| 40 # array, using the C# wrapper for buffer_extract_outparams_fn(fz_buffer | |
| 41 # buf, buffer_extract_outparams outparams). | |
| 42 # | |
| 43 write( | |
| 44 textwrap.dedent( | |
| 45 f''' | |
| 46 | |
| 47 // Custom C# wrapper for fz_buffer_extract(). | |
| 48 public static class mupdf_{rename.class_('fz_buffer')}_extract | |
| 49 {{ | |
| 50 public static byte[] {rename.method('fz_buffer', 'fz_buffer_extract')}(this mupdf.{rename.class_('fz_buffer')} buffer) | |
| 51 {{ | |
| 52 var outparams = new mupdf.{rename.ll_fn('fz_buffer_storage')}_outparams(); | |
| 53 uint n = mupdf.mupdf.{rename.ll_fn('fz_buffer_storage')}_outparams_fn(buffer.m_internal, outparams); | |
| 54 var raw1 = mupdf.SWIGTYPE_p_unsigned_char.getCPtr(outparams.datap); | |
| 55 System.IntPtr raw2 = System.Runtime.InteropServices.HandleRef.ToIntPtr(raw1); | |
| 56 byte[] ret = new byte[n]; | |
| 57 // Marshal.Copy() raises exception if <raw2> is null even if <n> is zero. | |
| 58 if (n == 0) return ret; | |
| 59 System.Runtime.InteropServices.Marshal.Copy(raw2, ret, 0, (int) n); | |
| 60 buffer.{rename.method( 'fz_buffer', 'fz_clear_buffer')}(); | |
| 61 buffer.{rename.method( 'fz_buffer', 'fz_trim_buffer')}(); | |
| 62 return ret; | |
| 63 }} | |
| 64 }} | |
| 65 ''') | |
| 66 ) | |
| 67 return | |
| 68 | |
| 69 # We don't attempt to generate wrappers for fns that take or return | |
| 70 # 'unsigned char*' - swig does not treat these as zero-terminated strings, | |
| 71 # and they are generally binary data so cannot be handled generically. | |
| 72 # | |
| 73 if parse.is_pointer_to(cursor.result_type, 'unsigned char'): | |
| 74 jlib.log(f'Cannot generate C# out-param wrapper for {cursor.mangled_name} because it returns unsigned char*.', 1) | |
| 75 return | |
| 76 for arg in parse.get_args( tu, cursor): | |
| 77 if parse.is_pointer_to(arg.cursor.type, 'unsigned char'): | |
| 78 jlib.log(f'Cannot generate C# out-param wrapper for {cursor.mangled_name} because has unsigned char* arg.', 1) | |
| 79 return | |
| 80 if parse.is_pointer_to_pointer_to(arg.cursor.type, 'unsigned char'): | |
| 81 jlib.log(f'Cannot generate C# out-param wrapper for {cursor.mangled_name} because has unsigned char** arg.', 1) | |
| 82 return | |
| 83 if arg.cursor.type.get_array_size() >= 0: | |
| 84 jlib.log(f'Cannot generate C# out-param wrapper for {cursor.mangled_name} because has array arg.', 1) | |
| 85 return | |
| 86 if arg.cursor.type.kind == state.clang.cindex.TypeKind.POINTER: | |
| 87 pointee = state.get_name_canonical( arg.cursor.type.get_pointee()) | |
| 88 if pointee.kind == state.clang.cindex.TypeKind.ENUM: | |
| 89 jlib.log(f'Cannot generate C# out-param wrapper for {cursor.mangled_name} because has enum out-param arg.', 1) | |
| 90 return | |
| 91 if pointee.kind == state.clang.cindex.TypeKind.FUNCTIONPROTO: | |
| 92 jlib.log(f'Cannot generate C# out-param wrapper for {cursor.mangled_name} because has fn-ptr arg.', 1) | |
| 93 return | |
| 94 if pointee.is_const_qualified(): | |
| 95 # Not an out-param. | |
| 96 jlib.log(f'Cannot generate C# out-param wrapper for {cursor.mangled_name} because has pointer-to-const arg.', 1) | |
| 97 return | |
| 98 if arg.cursor.type.get_pointee().spelling == 'FILE': | |
| 99 jlib.log(f'Cannot generate C# out-param wrapper for {cursor.mangled_name} because has FILE* arg.', 1) | |
| 100 return | |
| 101 if pointee.spelling == 'void': | |
| 102 jlib.log(f'Cannot generate C# out-param wrapper for {cursor.mangled_name} because has void* arg.', 1) | |
| 103 return | |
| 104 | |
| 105 num_return_values = 0 if return_void else 1 | |
| 106 for arg in parse.get_args( tu, cursor): | |
| 107 if arg.out_param: | |
| 108 num_return_values += 1 | |
| 109 assert num_return_values | |
| 110 | |
| 111 if num_return_values > 7: | |
| 112 # On linux, mono-csc can fail with: | |
| 113 # System.NotImplementedException: tuples > 7 | |
| 114 # | |
| 115 jlib.log(f'Cannot generate C# out-param wrapper for {cursor.mangled_name} because would require > 7-tuple.') | |
| 116 return | |
| 117 | |
| 118 # Write C# wrapper. | |
| 119 arg0, _ = parse.get_first_arg( tu, cursor) | |
| 120 if not arg0.alt: | |
| 121 return | |
| 122 | |
| 123 method_name = rename.method( arg0.alt.type.spelling, fnname) | |
| 124 | |
| 125 write(f'\n') | |
| 126 write(f'// Out-params extension method for C# class {rename.class_(arg0.alt.type.spelling)} (wrapper for MuPDF struct {arg0.alt.type.spelling}),\n') | |
| 127 write(f'// adding class method {method_name}() (wrapper for {fnname}())\n') | |
| 128 write(f'// which returns out-params directly.\n') | |
| 129 write(f'//\n') | |
| 130 write(f'public static class mupdf_{main_name}_outparams_helper\n') | |
| 131 write(f'{{\n') | |
| 132 write(f' public static ') | |
| 133 | |
| 134 def write_type(alt, type_): | |
| 135 if alt: | |
| 136 write(f'mupdf.{rename.class_(alt.type.spelling)}') | |
| 137 elif parse.is_pointer_to(type_, 'char'): | |
| 138 write( f'string') | |
| 139 else: | |
| 140 text = cpp.declaration_text(type_, '').strip() | |
| 141 if text == 'int16_t': text = 'short' | |
| 142 elif text == 'int64_t': text = 'long' | |
| 143 elif text == 'size_t': text = 'ulong' | |
| 144 elif text == 'unsigned int': text = 'uint' | |
| 145 elif text.startswith('enum '): | |
| 146 # This is primarily for enum pdf_zugferd_profile; C# does not | |
| 147 # like `enum` prefix, and we need to specify namespace name | |
| 148 # `mupdf`. | |
| 149 text = text[5:] | |
| 150 if text.startswith('pdf_') or text.startswith('fz_'): | |
| 151 text = f'{rename.namespace()}.{text}' | |
| 152 write(f'{text}') | |
| 153 | |
| 154 # Generate the returned tuple. | |
| 155 # | |
| 156 if num_return_values > 1: | |
| 157 write('(') | |
| 158 | |
| 159 sep = '' | |
| 160 | |
| 161 # Returned param, if any. | |
| 162 if not return_void: | |
| 163 return_alt = None | |
| 164 base_type_cursor, base_typename, extras = parse.get_extras( tu, cursor.result_type) | |
| 165 if extras: | |
| 166 if extras.opaque: | |
| 167 # E.g. we don't have access to definition of fz_separation, | |
| 168 # but it is marked in classextras with opaque=true, so | |
| 169 # there will be a wrapper class. | |
| 170 return_alt = base_type_cursor | |
| 171 elif base_type_cursor.kind == state.clang.cindex.CursorKind.STRUCT_DECL: | |
| 172 return_alt = base_type_cursor | |
| 173 write_type(return_alt, cursor.result_type) | |
| 174 sep = ', ' | |
| 175 | |
| 176 # Out-params. | |
| 177 for arg in parse.get_args( tu, cursor): | |
| 178 if arg.out_param: | |
| 179 write(sep) | |
| 180 write_type(arg.alt, arg.cursor.type.get_pointee()) | |
| 181 if num_return_values > 1: | |
| 182 write(f' {arg.name_csharp}') | |
| 183 sep = ', ' | |
| 184 | |
| 185 if num_return_values > 1: | |
| 186 write(')') | |
| 187 | |
| 188 # Generate function name and params. If first arg is a wrapper class we | |
| 189 # use C#'s 'this' keyword to make this a member function of the wrapper | |
| 190 # class. | |
| 191 #jlib.log('outputs fn {fnname=}: is member: {"yes" if arg0.alt else "no"}') | |
| 192 write(f' ') | |
| 193 write( method_name if arg0.alt else 'fn') | |
| 194 write(f'(') | |
| 195 if arg0.alt: write('this ') | |
| 196 sep = '' | |
| 197 for arg in parse.get_args( tu, cursor): | |
| 198 if arg.out_param: | |
| 199 continue | |
| 200 write(sep) | |
| 201 if arg.alt: | |
| 202 # E.g. 'Document doc'. | |
| 203 write(f'mupdf.{rename.class_(arg.alt.type.spelling)} {arg.name_csharp}') | |
| 204 elif parse.is_pointer_to(arg.cursor.type, 'char'): | |
| 205 write(f'string {arg.name_csharp}') | |
| 206 else: | |
| 207 text = cpp.declaration_text(arg.cursor.type, arg.name_csharp).strip() | |
| 208 text = util.clip(text, 'const ') | |
| 209 text = text.replace('int16_t ', 'short ') | |
| 210 text = text.replace('int64_t ', 'long ') | |
| 211 text = text.replace('size_t ', 'uint ') | |
| 212 text = text.replace('unsigned int ', 'uint ') | |
| 213 write(text) | |
| 214 sep = ', ' | |
| 215 write(f')\n') | |
| 216 | |
| 217 # Function body. | |
| 218 # | |
| 219 write(f' {{\n') | |
| 220 | |
| 221 # Create local outparams struct. | |
| 222 write(f' var outparams = new mupdf.{main_name}_outparams();\n') | |
| 223 write(f' ') | |
| 224 | |
| 225 # Generate function call. | |
| 226 # | |
| 227 # The C# *_outparams_fn() generated by swig is inside namespace mupdf { | |
| 228 # class mupdf { ... } }, so we access it using the rather clumsy prefix | |
| 229 # 'mupdf.mupdf.'. It will have been generated from a C++ function | |
| 230 # (generate by us) which is in top-level namespace mupdf, but swig | |
| 231 # appears to generate the same code even if the C++ function is not in | |
| 232 # a namespace. | |
| 233 # | |
| 234 if not return_void: | |
| 235 write(f'var ret = ') | |
| 236 write(f'mupdf.mupdf.{main_name}_outparams_fn(') | |
| 237 sep = '' | |
| 238 for arg in parse.get_args( tu, cursor): | |
| 239 if arg.out_param: | |
| 240 continue | |
| 241 write(f'{sep}{arg.name_csharp}') | |
| 242 if arg.alt: | |
| 243 extras = parse.get_fz_extras( tu, arg.alt.type.spelling) | |
| 244 assert extras.pod != 'none' \ | |
| 245 'Cannot pass wrapper for {type_.spelling} as arg because pod is "none" so we cannot recover struct.' | |
| 246 write('.internal_()' if extras.pod == 'inline' else '.m_internal') | |
| 247 sep = ', ' | |
| 248 write(f'{sep}outparams);\n') | |
| 249 | |
| 250 # Generate return of tuple. | |
| 251 write(f' return ') | |
| 252 if num_return_values > 1: | |
| 253 write(f'(') | |
| 254 sep = '' | |
| 255 if not return_void: | |
| 256 if return_alt: | |
| 257 write(f'new mupdf.{rename.class_(return_alt.type.spelling)}(ret)') | |
| 258 else: | |
| 259 write(f'ret') | |
| 260 sep = ', ' | |
| 261 for arg in parse.get_args( tu, cursor): | |
| 262 if arg.out_param: | |
| 263 write(f'{sep}') | |
| 264 type_ = arg.cursor.type.get_pointee() | |
| 265 if arg.alt: | |
| 266 write(f'new mupdf.{rename.class_(arg.alt.type.spelling)}(outparams.{arg.name_csharp})') | |
| 267 elif 0 and parse.is_pointer_to(type_, 'char'): | |
| 268 # This was intended to convert char* to string, but swig | |
| 269 # will have already done that when making a C# version of | |
| 270 # the C++ struct, and modern csc on Windows doesn't like | |
| 271 # creating a string from a string for some reason. | |
| 272 write(f'new string(outparams.{arg.name_csharp})') | |
| 273 else: | |
| 274 write(f'outparams.{arg.name_csharp}') | |
| 275 sep = ', ' | |
| 276 if num_return_values > 1: | |
| 277 write(')') | |
| 278 write(';\n') | |
| 279 write(f' }}\n') | |
| 280 write(f'}}\n') | |
| 281 | |
| 282 | |
| 283 def csharp_settings(build_dirs): | |
| 284 ''' | |
| 285 Returns (csc, mono, mupdf_cs). | |
| 286 | |
| 287 csc: C# compiler. | |
| 288 mono: C# interpreter ("" on Windows). | |
| 289 mupdf_cs: MuPDF C# code. | |
| 290 | |
| 291 `mupdf_cs` will be None if `build_dirs` is false. | |
| 292 | |
| 293 E.g. on Windows `csc` can be: C:/Program Files (x86)/Microsoft Visual Studio/2019/Community/MSBuild/Current/Bin/Roslyn/csc.exe | |
| 294 ''' | |
| 295 # On linux requires: | |
| 296 # sudo apt install mono-devel | |
| 297 # | |
| 298 # OpenBSD: | |
| 299 # pkg_add mono | |
| 300 # but we get runtime error when exiting: | |
| 301 # mono:build/shared-release/libmupdfcpp.so: undefined symbol '_ZdlPv' | |
| 302 # which might be because of mixing gcc and clang? | |
| 303 # | |
| 304 if state.state_.windows: | |
| 305 import wdev | |
| 306 vs = wdev.WindowsVS() | |
| 307 jlib.log('{vs.description_ml()=}') | |
| 308 csc = vs.csc | |
| 309 jlib.log('{csc=}') | |
| 310 assert csc, f'Unable to find csc.exe' | |
| 311 mono = '' | |
| 312 else: | |
| 313 mono = 'mono' | |
| 314 if state.state_.linux: | |
| 315 csc = 'mono-csc' | |
| 316 elif state.state_.openbsd: | |
| 317 csc = 'csc' | |
| 318 else: | |
| 319 assert 0, f'Do not know where to find mono. {platform.platform()=}' | |
| 320 | |
| 321 if build_dirs: | |
| 322 mupdf_cs = os.path.relpath(f'{build_dirs.dir_so}/mupdf.cs') | |
| 323 else: | |
| 324 mupdf_cs = None | |
| 325 return csc, mono, mupdf_cs |
