Mercurial > hgrepos > Python2 > PyMuPDF
comparison mupdf-source/scripts/wrap/swig.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 Support for using SWIG to generate language bindings from the C++ bindings. | |
| 3 ''' | |
| 4 | |
| 5 import inspect | |
| 6 import io | |
| 7 import os | |
| 8 import re | |
| 9 import textwrap | |
| 10 | |
| 11 import jlib | |
| 12 | |
| 13 from . import cpp | |
| 14 from . import csharp | |
| 15 from . import rename | |
| 16 from . import state | |
| 17 from . import util | |
| 18 | |
| 19 | |
| 20 def translate_ucdn_macros( build_dirs): | |
| 21 ''' | |
| 22 Returns string containing UCDN_* macros represented as enums. | |
| 23 ''' | |
| 24 out = io.StringIO() | |
| 25 with open( f'{build_dirs.dir_mupdf}/include/mupdf/ucdn.h') as f: | |
| 26 text = f.read() | |
| 27 out.write( '\n') | |
| 28 out.write( '\n') | |
| 29 out.write( 'enum\n') | |
| 30 out.write( '{\n') | |
| 31 n = 0 | |
| 32 for m in re.finditer('\n#define (UCDN_[A-Z0-9_]+) +([^\n]+)', text): | |
| 33 out.write(f' {m.group(1)} = {m.group(2)},\n') | |
| 34 n += 1 | |
| 35 out.write( '};\n') | |
| 36 out.write( '\n') | |
| 37 assert n | |
| 38 return out.getvalue() | |
| 39 | |
| 40 def _csharp_unicode_prefix(): | |
| 41 ''' | |
| 42 Returns typemaps that automatically convert C# strings (which are utf16) | |
| 43 into utf8 when calling MuPDF, and convert strings returned by MuPDF (which | |
| 44 are utf8) into utf16. | |
| 45 | |
| 46 We return empty string if not on Windows, because Mono appears to already | |
| 47 work. | |
| 48 ''' | |
| 49 if not state.state_.windows: | |
| 50 # Mono on Linux already seems to use utf8. | |
| 51 return '' | |
| 52 | |
| 53 text = textwrap.dedent(''' | |
| 54 // This ensures that our code below overrides whatever is defined | |
| 55 // in std_string.i and any later `%include "std_string.i"` is | |
| 56 // ignored. | |
| 57 %include "std_string.i" | |
| 58 | |
| 59 // See https://github.com/swig/swig/pull/2364. We also add typemaps | |
| 60 // for `const char*`. | |
| 61 | |
| 62 %{ | |
| 63 #include <string> | |
| 64 %} | |
| 65 | |
| 66 namespace std | |
| 67 { | |
| 68 %typemap(imtype, | |
| 69 inattributes="[global::System.Runtime.InteropServices.MarshalAs(global::System.Runtime.InteropServices.UnmanagedType.LPUTF8Str)]", | |
| 70 outattributes="[return: global::System.Runtime.InteropServices.MarshalAs(global::System.Runtime.InteropServices.UnmanagedType.LPUTF8Str)]", | |
| 71 directorinattributes="[global::System.Runtime.InteropServices.MarshalAs(global::System.Runtime.InteropServices.UnmanagedType.LPUTF8Str)]", | |
| 72 directoroutattributes="[return: global::System.Runtime.InteropServices.MarshalAs(global::System.Runtime.InteropServices.UnmanagedType.LPUTF8Str)]" | |
| 73 ) string "string" | |
| 74 | |
| 75 | |
| 76 %typemap(imtype, | |
| 77 inattributes="[global::System.Runtime.InteropServices.MarshalAs(global::System.Runtime.InteropServices.UnmanagedType.LPUTF8Str)]", | |
| 78 outattributes="[return: global::System.Runtime.InteropServices.MarshalAs(global::System.Runtime.InteropServices.UnmanagedType.LPUTF8Str)]", | |
| 79 directorinattributes="[global::System.Runtime.InteropServices.MarshalAs(global::System.Runtime.InteropServices.UnmanagedType.LPUTF8Str)]", | |
| 80 directoroutattributes="[return: global::System.Runtime.InteropServices.MarshalAs(global::System.Runtime.InteropServices.UnmanagedType.LPUTF8Str)]" | |
| 81 ) const string & "string" | |
| 82 | |
| 83 %typemap(imtype, | |
| 84 inattributes="[global::System.Runtime.InteropServices.MarshalAs(global::System.Runtime.InteropServices.UnmanagedType.LPUTF8Str)]", | |
| 85 outattributes="[return: global::System.Runtime.InteropServices.MarshalAs(global::System.Runtime.InteropServices.UnmanagedType.LPUTF8Str)]", | |
| 86 directorinattributes="[global::System.Runtime.InteropServices.MarshalAs(global::System.Runtime.InteropServices.UnmanagedType.LPUTF8Str)]", | |
| 87 directoroutattributes="[return: global::System.Runtime.InteropServices.MarshalAs(global::System.Runtime.InteropServices.UnmanagedType.LPUTF8Str)]" | |
| 88 ) const char* "string" | |
| 89 } | |
| 90 ''') | |
| 91 return text | |
| 92 | |
| 93 | |
| 94 def build_swig( | |
| 95 state_: state.State, | |
| 96 build_dirs: state.BuildDirs, | |
| 97 generated, | |
| 98 language='python', | |
| 99 swig_command='swig', | |
| 100 check_regress=False, | |
| 101 force_rebuild=False, | |
| 102 ): | |
| 103 ''' | |
| 104 Builds python or C# wrappers for all mupdf_* functions and classes, by | |
| 105 creating a .i file that #include's our generated C++ header files and | |
| 106 running swig. | |
| 107 | |
| 108 build_dirs | |
| 109 A BuildDirs instance. | |
| 110 generated. | |
| 111 A Generated instance. | |
| 112 language | |
| 113 The output language, must be 'python' or 'csharp'. | |
| 114 swig | |
| 115 Location of swig binary. | |
| 116 check_regress | |
| 117 If true, we fail with error if generated .i file already exists and | |
| 118 differs from our new content. | |
| 119 ''' | |
| 120 assert isinstance( state_, state.State) | |
| 121 assert isinstance(build_dirs, state.BuildDirs), type(build_dirs) | |
| 122 assert isinstance(generated, cpp.Generated), type(generated) | |
| 123 assert language in ('python', 'csharp') | |
| 124 # Find version of swig. (We use quotes around <swig> to make things work on | |
| 125 # Windows.) | |
| 126 e, swig_location = jlib.system( f'which "{swig_command}"', raise_errors=0, out='return', verbose=0) | |
| 127 if e == 0: | |
| 128 jlib.log(f'{swig_location=}') | |
| 129 t = jlib.system( f'"{swig_command}" -version', out='return', verbose=0) | |
| 130 jlib.log1('SWIG version info:\n========\n{t}\n========') | |
| 131 m = re.search( 'SWIG Version ([0-9]+)[.]([0-9]+)[.]([0-9]+)', t) | |
| 132 assert m | |
| 133 swig_major = int( m.group(1)) | |
| 134 jlib.log(f'{m.group()}') | |
| 135 | |
| 136 # Create a .i file for SWIG. | |
| 137 # | |
| 138 common = textwrap.dedent(f''' | |
| 139 #include <stdexcept> | |
| 140 | |
| 141 #include "mupdf/functions.h" | |
| 142 #include "mupdf/classes.h" | |
| 143 #include "mupdf/classes2.h" | |
| 144 #include "mupdf/internal.h" | |
| 145 #include "mupdf/exceptions.h" | |
| 146 #include "mupdf/extra.h" | |
| 147 | |
| 148 #ifdef NDEBUG | |
| 149 static bool g_mupdf_trace_director = false; | |
| 150 static bool g_mupdf_trace_exceptions = false; | |
| 151 #else | |
| 152 static bool g_mupdf_trace_director = mupdf::internal_env_flag("MUPDF_trace_director"); | |
| 153 static bool g_mupdf_trace_exceptions = mupdf::internal_env_flag("MUPDF_trace_exceptions"); | |
| 154 #endif | |
| 155 | |
| 156 ''' | |
| 157 ) | |
| 158 if language == 'csharp': | |
| 159 common += textwrap.dedent(f''' | |
| 160 /* This is required otherwise compiling the resulting C++ code | |
| 161 fails with: | |
| 162 error: use of undeclared identifier 'SWIG_fail' | |
| 163 | |
| 164 But no idea whether it is the 'correct' thing to do; seems odd | |
| 165 that SWIG doesn't define SWIG_fail itself. | |
| 166 */ | |
| 167 #define SWIG_fail throw std::runtime_error( e.what()); | |
| 168 ''') | |
| 169 | |
| 170 if language == 'python': | |
| 171 common += textwrap.dedent(f''' | |
| 172 | |
| 173 static std::string to_stdstring(PyObject* s) | |
| 174 {{ | |
| 175 PyObject* repr_str = PyUnicode_AsEncodedString(s, "utf-8", "~E~"); | |
| 176 #ifdef Py_LIMITED_API | |
| 177 const char* repr_str_s = PyBytes_AsString(repr_str); | |
| 178 #else | |
| 179 const char* repr_str_s = PyBytes_AS_STRING(repr_str); | |
| 180 #endif | |
| 181 std::string ret = repr_str_s; | |
| 182 Py_DECREF(repr_str); | |
| 183 Py_DECREF(s); | |
| 184 return ret; | |
| 185 }} | |
| 186 | |
| 187 static std::string py_repr(PyObject* x) | |
| 188 {{ | |
| 189 if (!x) return "<C_nullptr>"; | |
| 190 PyObject* s = PyObject_Repr(x); | |
| 191 return to_stdstring(s); | |
| 192 }} | |
| 193 | |
| 194 static std::string py_str(PyObject* x) | |
| 195 {{ | |
| 196 if (!x) return "<C_nullptr>"; | |
| 197 PyObject* s = PyObject_Str(x); | |
| 198 return to_stdstring(s); | |
| 199 }} | |
| 200 | |
| 201 /* Returns a Python `bytes` containing a copy of a `fz_buffer`'s | |
| 202 data. If <clear> is true we also clear and trim the buffer. */ | |
| 203 PyObject* ll_fz_buffer_to_bytes_internal(fz_buffer* buffer, int clear) | |
| 204 {{ | |
| 205 unsigned char* c = NULL; | |
| 206 size_t len = {rename.namespace_ll_fn('fz_buffer_storage')}(buffer, &c); | |
| 207 PyObject* ret = PyBytes_FromStringAndSize((const char*) c, (Py_ssize_t) len); | |
| 208 if (clear) | |
| 209 {{ | |
| 210 /* We mimic the affects of fz_buffer_extract(), which | |
| 211 leaves the buffer with zero capacity. */ | |
| 212 {rename.namespace_ll_fn('fz_clear_buffer')}(buffer); | |
| 213 {rename.namespace_ll_fn('fz_trim_buffer')}(buffer); | |
| 214 }} | |
| 215 return ret; | |
| 216 }} | |
| 217 | |
| 218 /* Returns a Python `memoryview` for specified memory. */ | |
| 219 PyObject* python_memoryview_from_memory( void* data, size_t size, int writable) | |
| 220 {{ | |
| 221 return PyMemoryView_FromMemory( | |
| 222 (char*) data, | |
| 223 (Py_ssize_t) size, | |
| 224 writable ? PyBUF_WRITE : PyBUF_READ | |
| 225 ); | |
| 226 }} | |
| 227 | |
| 228 /* Returns a Python `memoryview` for a `fz_buffer`'s data. */ | |
| 229 PyObject* ll_fz_buffer_storage_memoryview(fz_buffer* buffer, int writable) | |
| 230 {{ | |
| 231 unsigned char* data = NULL; | |
| 232 size_t len = {rename.namespace_ll_fn('fz_buffer_storage')}(buffer, &data); | |
| 233 return python_memoryview_from_memory( data, len, writable); | |
| 234 }} | |
| 235 | |
| 236 /* Creates Python bytes from copy of raw data. */ | |
| 237 PyObject* raw_to_python_bytes(const unsigned char* c, size_t len) | |
| 238 {{ | |
| 239 return PyBytes_FromStringAndSize((const char*) c, (Py_ssize_t) len); | |
| 240 }} | |
| 241 | |
| 242 /* Creates Python bytes from copy of raw data. */ | |
| 243 PyObject* raw_to_python_bytes(const void* c, size_t len) | |
| 244 {{ | |
| 245 return PyBytes_FromStringAndSize((const char*) c, (Py_ssize_t) len); | |
| 246 }} | |
| 247 | |
| 248 /* The SWIG wrapper for this function returns a SWIG proxy for | |
| 249 a 'const unsigned char*' pointing to the raw data of a python | |
| 250 bytes. This proxy can then be passed from Python to functions | |
| 251 that take a 'const unsigned char*'. | |
| 252 | |
| 253 For example to create a MuPDF fz_buffer* from a copy of a | |
| 254 Python bytes instance: | |
| 255 bs = b'qwerty' | |
| 256 buffer_ = mupdf.fz_new_buffer_from_copied_data(mupdf.python_buffer_data(bs), len(bs)) | |
| 257 */ | |
| 258 const unsigned char* python_buffer_data( | |
| 259 const unsigned char* PYTHON_BUFFER_DATA, | |
| 260 size_t PYTHON_BUFFER_SIZE | |
| 261 ) | |
| 262 {{ | |
| 263 return PYTHON_BUFFER_DATA; | |
| 264 }} | |
| 265 | |
| 266 unsigned char* python_mutable_buffer_data( | |
| 267 unsigned char* PYTHON_BUFFER_MUTABLE_DATA, | |
| 268 size_t PYTHON_BUFFER_MUTABLE_SIZE | |
| 269 ) | |
| 270 {{ | |
| 271 return PYTHON_BUFFER_MUTABLE_DATA; | |
| 272 }} | |
| 273 | |
| 274 /* Casts an integer to a pdf_obj*. Used to convert SWIG's int | |
| 275 values for PDF_ENUM_NAME_* into {rename.class_('pdf_obj')}'s. */ | |
| 276 pdf_obj* obj_enum_to_obj(int n) | |
| 277 {{ | |
| 278 return (pdf_obj*) (intptr_t) n; | |
| 279 }} | |
| 280 | |
| 281 /* SWIG-friendly alternative to {rename.ll_fn('pdf_set_annot_color')}(). */ | |
| 282 void {rename.ll_fn('pdf_set_annot_color2')}(pdf_annot *annot, int n, float color0, float color1, float color2, float color3) | |
| 283 {{ | |
| 284 float color[] = {{ color0, color1, color2, color3 }}; | |
| 285 return {rename.namespace_ll_fn('pdf_set_annot_color')}(annot, n, color); | |
| 286 }} | |
| 287 | |
| 288 | |
| 289 /* SWIG-friendly alternative to {rename.ll_fn('pdf_set_annot_interior_color')}(). */ | |
| 290 void {rename.ll_fn('pdf_set_annot_interior_color2')}(pdf_annot *annot, int n, float color0, float color1, float color2, float color3) | |
| 291 {{ | |
| 292 float color[] = {{ color0, color1, color2, color3 }}; | |
| 293 return {rename.namespace_ll_fn('pdf_set_annot_interior_color')}(annot, n, color); | |
| 294 }} | |
| 295 | |
| 296 /* SWIG-friendly alternative to `fz_fill_text()`. */ | |
| 297 void ll_fz_fill_text2( | |
| 298 fz_device* dev, | |
| 299 const fz_text* text, | |
| 300 fz_matrix ctm, | |
| 301 fz_colorspace* colorspace, | |
| 302 float color0, | |
| 303 float color1, | |
| 304 float color2, | |
| 305 float color3, | |
| 306 float alpha, | |
| 307 fz_color_params color_params | |
| 308 ) | |
| 309 {{ | |
| 310 float color[] = {{color0, color1, color2, color3}}; | |
| 311 return {rename.namespace_ll_fn( 'fz_fill_text')}(dev, text, ctm, colorspace, color, alpha, color_params); | |
| 312 }} | |
| 313 | |
| 314 std::vector<unsigned char> {rename.fn('fz_memrnd2')}(int length) | |
| 315 {{ | |
| 316 std::vector<unsigned char> ret(length); | |
| 317 {rename.namespace_fn('fz_memrnd')}(&ret[0], length); | |
| 318 return ret; | |
| 319 }} | |
| 320 | |
| 321 /* mupdfpy optimisation for copying raw data into pixmap. `samples` must | |
| 322 have enough data to fill the pixmap. */ | |
| 323 void ll_fz_pixmap_copy_raw( fz_pixmap* pm, const void* samples) | |
| 324 {{ | |
| 325 memcpy(pm->samples, samples, pm->stride * pm->h); | |
| 326 }} | |
| 327 ''') | |
| 328 | |
| 329 common += textwrap.dedent(f''' | |
| 330 /* SWIG-friendly alternative to fz_runetochar(). */ | |
| 331 std::vector<unsigned char> {rename.fn('fz_runetochar2')}(int rune) | |
| 332 {{ | |
| 333 std::vector<unsigned char> buffer(10); | |
| 334 int n = {rename.namespace_ll_fn('fz_runetochar')}((char*) &buffer[0], rune); | |
| 335 assert(n < sizeof(buffer)); | |
| 336 buffer.resize(n); | |
| 337 return buffer; | |
| 338 }} | |
| 339 | |
| 340 /* SWIG-friendly alternatives to fz_make_bookmark() and | |
| 341 {rename.fn('fz_lookup_bookmark')}(), using long long instead of fz_bookmark | |
| 342 because SWIG appears to treat fz_bookmark as an int despite it | |
| 343 being a typedef for intptr_t, so ends up slicing. */ | |
| 344 long long unsigned {rename.ll_fn('fz_make_bookmark2')}(fz_document* doc, fz_location loc) | |
| 345 {{ | |
| 346 fz_bookmark bm = {rename.namespace_ll_fn('fz_make_bookmark')}(doc, loc); | |
| 347 return (long long unsigned) bm; | |
| 348 }} | |
| 349 | |
| 350 fz_location {rename.ll_fn('fz_lookup_bookmark2')}(fz_document *doc, long long unsigned mark) | |
| 351 {{ | |
| 352 return {rename.namespace_ll_fn('fz_lookup_bookmark')}(doc, (fz_bookmark) mark); | |
| 353 }} | |
| 354 {rename.namespace_class('fz_location')} {rename.fn('fz_lookup_bookmark2')}( {rename.namespace_class('fz_document')} doc, long long unsigned mark) | |
| 355 {{ | |
| 356 return {rename.namespace_class('fz_location')}( {rename.ll_fn('fz_lookup_bookmark2')}(doc.m_internal, mark)); | |
| 357 }} | |
| 358 | |
| 359 struct {rename.fn('fz_convert_color2_v')} | |
| 360 {{ | |
| 361 float v0; | |
| 362 float v1; | |
| 363 float v2; | |
| 364 float v3; | |
| 365 }}; | |
| 366 | |
| 367 /* SWIG-friendly alternative for | |
| 368 {rename.ll_fn('fz_convert_color')}(), taking `float* sv`. */ | |
| 369 void {rename.ll_fn('fz_convert_color2')}( | |
| 370 fz_colorspace *ss, | |
| 371 float* sv, | |
| 372 fz_colorspace *ds, | |
| 373 {rename.fn('fz_convert_color2_v')}* dv, | |
| 374 fz_colorspace *is, | |
| 375 fz_color_params params | |
| 376 ) | |
| 377 {{ | |
| 378 //float sv[] = {{ sv0, sv1, sv2, sv3}}; | |
| 379 {rename.namespace_ll_fn('fz_convert_color')}(ss, sv, ds, &dv->v0, is, params); | |
| 380 }} | |
| 381 | |
| 382 /* SWIG-friendly alternative for | |
| 383 {rename.ll_fn('fz_convert_color')}(), taking four explicit `float` | |
| 384 values for `sv`. */ | |
| 385 void {rename.ll_fn('fz_convert_color2')}( | |
| 386 fz_colorspace *ss, | |
| 387 float sv0, | |
| 388 float sv1, | |
| 389 float sv2, | |
| 390 float sv3, | |
| 391 fz_colorspace *ds, | |
| 392 {rename.fn('fz_convert_color2_v')}* dv, | |
| 393 fz_colorspace *is, | |
| 394 fz_color_params params | |
| 395 ) | |
| 396 {{ | |
| 397 float sv[] = {{ sv0, sv1, sv2, sv3}}; | |
| 398 {rename.namespace_ll_fn('fz_convert_color')}(ss, sv, ds, &dv->v0, is, params); | |
| 399 }} | |
| 400 | |
| 401 /* SWIG- Director class to allow fz_set_warning_callback() and | |
| 402 fz_set_error_callback() to be used with Python callbacks. Note that | |
| 403 we rename print() to _print() to match what SWIG does. */ | |
| 404 struct DiagnosticCallback | |
| 405 {{ | |
| 406 /* `description` must be "error" or "warning". */ | |
| 407 DiagnosticCallback(const char* description) | |
| 408 : | |
| 409 m_description(description) | |
| 410 {{ | |
| 411 #ifndef NDEBUG | |
| 412 if (g_mupdf_trace_director) | |
| 413 {{ | |
| 414 std::cerr | |
| 415 << __FILE__ << ":" << __LINE__ << ":" << __FUNCTION__ << ":" | |
| 416 << " DiagnosticCallback[" << m_description << "]() constructor." | |
| 417 << "\\n"; | |
| 418 }} | |
| 419 #endif | |
| 420 if (m_description == "warning") | |
| 421 {{ | |
| 422 mupdf::ll_fz_set_warning_callback( s_print, this); | |
| 423 }} | |
| 424 else if (m_description == "error") | |
| 425 {{ | |
| 426 mupdf::ll_fz_set_error_callback( s_print, this); | |
| 427 }} | |
| 428 else | |
| 429 {{ | |
| 430 std::cerr | |
| 431 << __FILE__ << ":" << __LINE__ << ":" << __FUNCTION__ << ":" | |
| 432 << " DiagnosticCallback() constructor" | |
| 433 << " Unrecognised description: " << m_description | |
| 434 << "\\n"; | |
| 435 assert(0); | |
| 436 }} | |
| 437 }} | |
| 438 virtual void _print( const char* message) | |
| 439 {{ | |
| 440 #ifndef NDEBUG | |
| 441 if (g_mupdf_trace_director) | |
| 442 {{ | |
| 443 std::cerr | |
| 444 << __FILE__ << ":" << __LINE__ << ":" << __FUNCTION__ << ":" | |
| 445 << " DiagnosticCallback[" << m_description << "]::_print()" | |
| 446 << " called (no derived class?)" << " message: " << message | |
| 447 << "\\n"; | |
| 448 }} | |
| 449 #endif | |
| 450 }} | |
| 451 virtual ~DiagnosticCallback() | |
| 452 {{ | |
| 453 #ifndef NDEBUG | |
| 454 if (g_mupdf_trace_director) | |
| 455 {{ | |
| 456 std::cerr | |
| 457 << __FILE__ << ":" << __LINE__ << ":" << __FUNCTION__ << ":" | |
| 458 << " ~DiagnosticCallback[" << m_description << "]() destructor called" | |
| 459 << " this=" << this | |
| 460 << "\\n"; | |
| 461 }} | |
| 462 #endif | |
| 463 }} | |
| 464 static void s_print( void* self0, const char* message) | |
| 465 {{ | |
| 466 DiagnosticCallback* self = (DiagnosticCallback*) self0; | |
| 467 try | |
| 468 {{ | |
| 469 return self->_print( message); | |
| 470 }} | |
| 471 catch (std::exception& e) | |
| 472 {{ | |
| 473 /* It's important to swallow any exception from | |
| 474 self->_print() because fz_set_warning_callback() and | |
| 475 fz_set_error_callback() specifically require that | |
| 476 the callback does not throw. But we always output a | |
| 477 diagnostic. */ | |
| 478 std::cerr | |
| 479 << "DiagnosticCallback[" << self->m_description << "]::s_print()" | |
| 480 << " ignoring exception from _print(): " | |
| 481 << e.what() | |
| 482 << "\\n"; | |
| 483 }} | |
| 484 }} | |
| 485 std::string m_description; | |
| 486 }}; | |
| 487 | |
| 488 struct StoryPositionsCallback | |
| 489 {{ | |
| 490 StoryPositionsCallback() | |
| 491 {{ | |
| 492 //printf( "StoryPositionsCallback() constructor\\n"); | |
| 493 }} | |
| 494 | |
| 495 virtual void call( const fz_story_element_position* position) = 0; | |
| 496 | |
| 497 static void s_call( fz_context* ctx, void* self0, const fz_story_element_position* position) | |
| 498 {{ | |
| 499 //printf( "StoryPositionsCallback::s_call()\\n"); | |
| 500 (void) ctx; | |
| 501 StoryPositionsCallback* self = (StoryPositionsCallback*) self0; | |
| 502 self->call( position); | |
| 503 }} | |
| 504 | |
| 505 virtual ~StoryPositionsCallback() | |
| 506 {{ | |
| 507 //printf( "StoryPositionsCallback() destructor\\n"); | |
| 508 }} | |
| 509 }}; | |
| 510 | |
| 511 void ll_fz_story_positions_director( fz_story *story, StoryPositionsCallback* cb) | |
| 512 {{ | |
| 513 //printf( "ll_fz_story_positions_director()\\n"); | |
| 514 {rename.namespace_ll_fn('fz_story_positions')}( | |
| 515 story, | |
| 516 StoryPositionsCallback::s_call, | |
| 517 cb | |
| 518 ); | |
| 519 }} | |
| 520 | |
| 521 void Pixmap_set_alpha_helper( | |
| 522 int balen, | |
| 523 int n, | |
| 524 int data_len, | |
| 525 int zero_out, | |
| 526 unsigned char* data, | |
| 527 fz_pixmap* pix, | |
| 528 int premultiply, | |
| 529 int bground, | |
| 530 const std::vector<int>& colors, | |
| 531 const std::vector<int>& bgcolor | |
| 532 ) | |
| 533 {{ | |
| 534 int i = 0; | |
| 535 int j = 0; | |
| 536 int k = 0; | |
| 537 int data_fix = 255; | |
| 538 while (i < balen) {{ | |
| 539 unsigned char alpha = data[k]; | |
| 540 if (zero_out) {{ | |
| 541 for (j = i; j < i+n; j++) {{ | |
| 542 if (pix->samples[j] != (unsigned char) colors[j - i]) {{ | |
| 543 data_fix = 255; | |
| 544 break; | |
| 545 }} else {{ | |
| 546 data_fix = 0; | |
| 547 }} | |
| 548 }} | |
| 549 }} | |
| 550 if (data_len) {{ | |
| 551 if (data_fix == 0) {{ | |
| 552 pix->samples[i+n] = 0; | |
| 553 }} else {{ | |
| 554 pix->samples[i+n] = alpha; | |
| 555 }} | |
| 556 if (premultiply && !bground) {{ | |
| 557 for (j = i; j < i+n; j++) {{ | |
| 558 pix->samples[j] = fz_mul255(pix->samples[j], alpha); | |
| 559 }} | |
| 560 }} else if (bground) {{ | |
| 561 for (j = i; j < i+n; j++) {{ | |
| 562 int m = (unsigned char) bgcolor[j - i]; | |
| 563 pix->samples[j] = m + fz_mul255((pix->samples[j] - m), alpha); | |
| 564 }} | |
| 565 }} | |
| 566 }} else {{ | |
| 567 pix->samples[i+n] = data_fix; | |
| 568 }} | |
| 569 i += n+1; | |
| 570 k += 1; | |
| 571 }} | |
| 572 }} | |
| 573 | |
| 574 void page_merge_helper( | |
| 575 {rename.namespace_class('pdf_obj')}& old_annots, | |
| 576 {rename.namespace_class('pdf_graft_map')}& graft_map, | |
| 577 {rename.namespace_class('pdf_document')}& doc_des, | |
| 578 {rename.namespace_class('pdf_obj')}& new_annots, | |
| 579 int n | |
| 580 ) | |
| 581 {{ | |
| 582 #define PDF_NAME2(X) {rename.namespace_class('pdf_obj')}(PDF_NAME(X)) | |
| 583 for ( int i=0; i<n; ++i) | |
| 584 {{ | |
| 585 {rename.namespace_class('pdf_obj')} o = {rename.namespace_fn('pdf_array_get')}( old_annots, i); | |
| 586 if ({rename.namespace_fn('pdf_dict_gets')}( o, "IRT").m_internal) | |
| 587 continue; | |
| 588 {rename.namespace_class('pdf_obj')} subtype = {rename.namespace_fn('pdf_dict_get')}( o, PDF_NAME2(Subtype)); | |
| 589 if ( {rename.namespace_fn('pdf_name_eq')}( subtype, PDF_NAME2(Link))) | |
| 590 continue; | |
| 591 if ( {rename.namespace_fn('pdf_name_eq')}( subtype, PDF_NAME2(Popup))) | |
| 592 continue; | |
| 593 if ( {rename.namespace_fn('pdf_name_eq')}( subtype, PDF_NAME2(Widget))) | |
| 594 {{ | |
| 595 /* fixme: C++ API doesn't yet wrap fz_warn() - it | |
| 596 excludes all variadic fns. */ | |
| 597 //mupdf::fz_warn( "skipping widget annotation"); | |
| 598 continue; | |
| 599 }} | |
| 600 {rename.namespace_fn('pdf_dict_del')}( o, PDF_NAME2(Popup)); | |
| 601 {rename.namespace_fn('pdf_dict_del')}( o, PDF_NAME2(P)); | |
| 602 {rename.namespace_class('pdf_obj')} copy_o = {rename.namespace_fn('pdf_graft_mapped_object')}( graft_map, o); | |
| 603 {rename.namespace_class('pdf_obj')} annot = {rename.namespace_fn('pdf_new_indirect')}( doc_des, {rename.namespace_fn('pdf_to_num')}( copy_o), 0); | |
| 604 {rename.namespace_fn('pdf_array_push')}( new_annots, annot); | |
| 605 }} | |
| 606 #undef PDF_NAME2 | |
| 607 }} | |
| 608 ''') | |
| 609 | |
| 610 common += generated.swig_cpp | |
| 611 common += translate_ucdn_macros( build_dirs) | |
| 612 | |
| 613 text = '' | |
| 614 | |
| 615 text += '%module(directors="1") mupdf\n' | |
| 616 | |
| 617 jlib.log(f'{build_dirs.Py_LIMITED_API=}') | |
| 618 | |
| 619 text += f'%begin %{{\n' | |
| 620 | |
| 621 if build_dirs.Py_LIMITED_API: # e.g. 0x03080000 | |
| 622 text += textwrap.dedent(f''' | |
| 623 /* Use Python Stable ABI with earliest Python version that we | |
| 624 support. */ | |
| 625 #define Py_LIMITED_API {build_dirs.Py_LIMITED_API} | |
| 626 | |
| 627 /* These seem to be mistakenly undefined when Py_LIMITED_API | |
| 628 is defined, so we force the values from Python.h. Also see | |
| 629 https://github.com/python/cpython/issues/98680. */ | |
| 630 #ifndef PyBUF_READ | |
| 631 #define PyBUF_READ 0x100 | |
| 632 #endif | |
| 633 #ifndef PyBUF_WRITE | |
| 634 #define PyBUF_WRITE 0x200 | |
| 635 #endif | |
| 636 ''') | |
| 637 | |
| 638 text += textwrap.dedent(f''' | |
| 639 /* This seems to be necessary on some Windows machines with | |
| 640 Py_LIMITED_API, otherwise compilation can fail because free() | |
| 641 and malloc() are not declared. */ | |
| 642 #include <stdlib.h> | |
| 643 ''') | |
| 644 | |
| 645 text += f'%}}\n' | |
| 646 | |
| 647 # https://www.mono-project.com/docs/advanced/pinvoke/ | |
| 648 # | |
| 649 # > Mono on all platforms currently uses UTF-8 encoding for all string | |
| 650 # > marshaling operations. | |
| 651 # | |
| 652 if language == 'csharp': | |
| 653 text += _csharp_unicode_prefix() | |
| 654 | |
| 655 for i in generated.virtual_fnptrs: | |
| 656 text += f'%feature("director") {i};\n' | |
| 657 | |
| 658 text += f'%feature("director") DiagnosticCallback;\n' | |
| 659 text += f'%feature("director") StoryPositionsCallback;\n' | |
| 660 | |
| 661 text += textwrap.dedent( | |
| 662 ''' | |
| 663 %feature("director:except") | |
| 664 { | |
| 665 if ($error != NULL) | |
| 666 { | |
| 667 /* | |
| 668 This is how we can end up here: | |
| 669 | |
| 670 1. Python code calls a function in the Python `mupdf` module. | |
| 671 2. - which calls SWIG C++ code. | |
| 672 3. - which calls MuPDF C++ API wrapper function. | |
| 673 4. - which calls MuPDF C code which calls an MuPDF struct's function pointer. | |
| 674 5. - which calls MuPDF C++ API Director wrapper (e.g. mupdf::FzDevice2) virtual function. | |
| 675 6. - which calls SWIG Director C++ code. | |
| 676 7. - which calls Python derived class's method, which raises a Python exception. | |
| 677 | |
| 678 The exception propagates back up the above stack, being converted | |
| 679 into different exception representations as it goes: | |
| 680 | |
| 681 6. SWIG Director C++ code (here). We raise a C++ exception. | |
| 682 5. MuPDF C++ API Director wrapper converts the C++ exception into a MuPDF fz_try/catch exception. | |
| 683 4. MuPDF C code allows the exception to propagate or catches and rethrows or throws a new fz_try/catch exception. | |
| 684 3. MuPDF C++ API wrapper function converts the fz_try/catch exception into a C++ exception. | |
| 685 2. SWIG C++ code converts the C++ exception into a Python exception. | |
| 686 1. Python code receives the Python exception. | |
| 687 | |
| 688 So the exception changes from a Python exception, to a C++ | |
| 689 exception, to a fz_try/catch exception, to a C++ exception, and | |
| 690 finally back into a Python exception. | |
| 691 | |
| 692 Each of these stages is necessary. In particular we cannot let the | |
| 693 first C++ exception propagate directly through MuPDF C code without | |
| 694 being a fz_try/catch exception, because it would mess up MuPDF C | |
| 695 code's fz_try/catch exception stack. | |
| 696 | |
| 697 Unfortuntately MuPDF fz_try/catch exception strings are limited to | |
| 698 256 characters so some or all of our detailed backtrace information | |
| 699 is lost. | |
| 700 */ | |
| 701 | |
| 702 /* Get text description of the Python exception. */ | |
| 703 PyObject* etype; | |
| 704 PyObject* obj; | |
| 705 PyObject* trace; | |
| 706 PyErr_Fetch( &etype, &obj, &trace); | |
| 707 | |
| 708 /* Looks like PyErr_GetExcInfo() fails here, returning NULL.*/ | |
| 709 /* | |
| 710 PyErr_GetExcInfo( &etype, &obj, &trace); | |
| 711 std::cerr << "PyErr_GetExcInfo(): etype: " << py_str(etype) << "\\n"; | |
| 712 std::cerr << "PyErr_GetExcInfo(): obj: " << py_str(obj) << "\\n"; | |
| 713 std::cerr << "PyErr_GetExcInfo(): trace: " << py_str(trace) << "\\n"; | |
| 714 */ | |
| 715 | |
| 716 std::string message = "Director error: " + py_str(etype) + ": " + py_str(obj) + "\\n"; | |
| 717 | |
| 718 if (g_mupdf_trace_director) | |
| 719 { | |
| 720 /* __FILE__ and __LINE__ are not useful here because SWIG makes | |
| 721 them point to the generic .i code. */ | |
| 722 std::cerr << "========\\n"; | |
| 723 std::cerr << "g_mupdf_trace_director set: Converting Python error into C++ exception:" << "\\n"; | |
| 724 #ifndef _WIN32 | |
| 725 std::cerr << " function: " << __PRETTY_FUNCTION__ << "\\n"; | |
| 726 #endif | |
| 727 std::cerr << " etype: " << py_str(etype) << "\\n"; | |
| 728 std::cerr << " obj: " << py_str(obj) << "\\n"; | |
| 729 std::cerr << " trace: " << py_str(trace) << "\\n"; | |
| 730 std::cerr << "========\\n"; | |
| 731 } | |
| 732 | |
| 733 PyObject* traceback = PyImport_ImportModule("traceback"); | |
| 734 if (traceback) | |
| 735 { | |
| 736 /* Use traceback.format_tb() to get backtrace. */ | |
| 737 if (0) | |
| 738 { | |
| 739 message += "Traceback (from traceback.format_tb()):\\n"; | |
| 740 PyObject* traceback_dict = PyModule_GetDict(traceback); | |
| 741 PyObject* format_tb = PyDict_GetItem(traceback_dict, PyString_FromString("format_tb")); | |
| 742 PyObject* ret = PyObject_CallFunctionObjArgs(format_tb, trace, NULL); | |
| 743 PyObject* iter = PyObject_GetIter(ret); | |
| 744 for(;;) | |
| 745 { | |
| 746 PyObject* item = PyIter_Next(iter); | |
| 747 if (!item) break; | |
| 748 message += py_str(item); | |
| 749 Py_DECREF(item); | |
| 750 } | |
| 751 /* `format_tb` and `traceback_dict` are borrowed references. | |
| 752 */ | |
| 753 Py_XDECREF(iter); | |
| 754 Py_XDECREF(ret); | |
| 755 Py_XDECREF(traceback); | |
| 756 } | |
| 757 | |
| 758 /* Use exception_info() (copied from mupdf/scripts/jlib.py) to get | |
| 759 detailed backtrace. */ | |
| 760 if (1) | |
| 761 { | |
| 762 PyObject* globals = PyEval_GetGlobals(); | |
| 763 PyObject* exception_info = PyDict_GetItemString(globals, "exception_info"); | |
| 764 PyObject* string_return = PyUnicode_FromString("return"); | |
| 765 PyObject* ret = PyObject_CallFunctionObjArgs( | |
| 766 exception_info, | |
| 767 trace, | |
| 768 Py_None, | |
| 769 string_return, | |
| 770 NULL | |
| 771 ); | |
| 772 Py_XDECREF(string_return); | |
| 773 message += py_str(ret); | |
| 774 Py_XDECREF(ret); | |
| 775 } | |
| 776 } | |
| 777 else | |
| 778 { | |
| 779 message += "[No backtrace available.]\\n"; | |
| 780 } | |
| 781 | |
| 782 Py_XDECREF(etype); | |
| 783 Py_XDECREF(obj); | |
| 784 Py_XDECREF(trace); | |
| 785 | |
| 786 message += "Exception was from C++/Python callback:\\n"; | |
| 787 message += " "; | |
| 788 #ifdef _WIN32 | |
| 789 message += __FUNCTION__; | |
| 790 #else | |
| 791 message += __PRETTY_FUNCTION__; | |
| 792 #endif | |
| 793 message += "\\n"; | |
| 794 | |
| 795 if (1 || g_mupdf_trace_director) | |
| 796 { | |
| 797 std::cerr << "========\\n"; | |
| 798 std::cerr << "Director exception handler, message is:\\n" << message << "\\n"; | |
| 799 std::cerr << "========\\n"; | |
| 800 } | |
| 801 | |
| 802 /* SWIG 4.1 documentation talks about throwing a | |
| 803 Swig::DirectorMethodException here, but this doesn't work for us | |
| 804 because it sets Python's error state again, which makes the | |
| 805 next SWIG call of a C/C++ function appear to fail. | |
| 806 //throw Swig::DirectorMethodException(); | |
| 807 */ | |
| 808 throw std::runtime_error( message.c_str()); | |
| 809 } | |
| 810 } | |
| 811 ''') | |
| 812 | |
| 813 # Ignore all C MuPDF functions; SWIG will still look at the C++ API in | |
| 814 # namespace mudf. | |
| 815 for fnname in generated.c_functions: | |
| 816 if fnname in ( | |
| 817 'pdf_annot_type', | |
| 818 'pdf_widget_type', | |
| 819 'pdf_zugferd_profile', | |
| 820 ): | |
| 821 # These are also enums which we don't want to ignore. SWIGing the | |
| 822 # functions is hopefully harmless. | |
| 823 pass | |
| 824 elif fnname in ('x0', 'y0', 'x1', 'y1'): | |
| 825 # Windows appears to have functions called y0() and y1() e.g. in: | |
| 826 # | |
| 827 # C:\\Program Files (x86)\\Windows Kits\\10\\Include\\10.0.18362.0\\ucrt\\corecrt_math.h | |
| 828 # | |
| 829 # If we use `%ignore` with these, e.g. `%ignore ::y0`, swig | |
| 830 # unhelpfully seems to also ignore any member variables called `y0` | |
| 831 # or `y1`. | |
| 832 # | |
| 833 jlib.log('Not ignoring {fnname=} because breaks wrapping of fz_rect.') | |
| 834 pass | |
| 835 else: | |
| 836 text += f'%ignore ::{fnname};\n' | |
| 837 | |
| 838 # Attempt to move C structs out of the way to allow wrapper classes to have | |
| 839 # the same name as the struct they wrap. Unfortunately this causes a small | |
| 840 # number of obscure errors from SWIG. | |
| 841 if 0: | |
| 842 for name in generated.c_structs: | |
| 843 text += f'%rename(lll_{name}) ::{name};\n' | |
| 844 | |
| 845 for i in ( | |
| 846 'fz_append_vprintf', | |
| 847 'fz_error_stack_slot', | |
| 848 'fz_format_string', | |
| 849 'fz_vsnprintf', | |
| 850 'fz_vthrow', | |
| 851 'fz_vwarn', | |
| 852 'fz_write_vprintf', | |
| 853 'fz_vlog_error_printf', | |
| 854 | |
| 855 'fz_utf8_from_wchar', | |
| 856 'fz_wchar_from_utf8', | |
| 857 'fz_fopen_utf8', | |
| 858 'fz_remove_utf8', | |
| 859 'fz_argv_from_wargv', | |
| 860 'fz_free_argv', | |
| 861 'fz_stdods', | |
| 862 ): | |
| 863 text += f'%ignore {i};\n' | |
| 864 text += f'%ignore {rename.method(None, i)};\n' | |
| 865 | |
| 866 text += textwrap.dedent(f''' | |
| 867 // Not implemented in mupdf.so: fz_colorspace_name_process_colorants | |
| 868 %ignore fz_colorspace_name_process_colorants; | |
| 869 %ignore fz_argv_from_wargv; | |
| 870 | |
| 871 %ignore fz_open_file_w; | |
| 872 | |
| 873 %ignore {rename.ll_fn('fz_append_vprintf')}; | |
| 874 %ignore {rename.ll_fn('fz_error_stack_slot_s')}; | |
| 875 %ignore {rename.ll_fn('fz_format_string')}; | |
| 876 %ignore {rename.ll_fn('fz_vsnprintf')}; | |
| 877 %ignore {rename.ll_fn('fz_vthrow')}; | |
| 878 %ignore {rename.ll_fn('fz_vwarn')}; | |
| 879 %ignore {rename.ll_fn('fz_write_vprintf')}; | |
| 880 %ignore {rename.ll_fn('fz_vlog_error_printf')}; | |
| 881 %ignore {rename.ll_fn('fz_open_file_w')}; | |
| 882 | |
| 883 // Ignore custom C++ variadic fns. | |
| 884 %ignore {rename.ll_fn('pdf_dict_getlv')}; | |
| 885 %ignore {rename.ll_fn('pdf_dict_getl')}; | |
| 886 %ignore {rename.fn('pdf_dict_getlv')}; | |
| 887 %ignore {rename.fn('pdf_dict_getl')}; | |
| 888 | |
| 889 // SWIG can't handle this because it uses a valist. | |
| 890 %ignore {rename.ll_fn('Memento_vasprintf')}; | |
| 891 %ignore {rename.fn('Memento_vasprintf')}; | |
| 892 | |
| 893 // These appear to be not present in Windows debug builds. | |
| 894 %ignore fz_assert_lock_held; | |
| 895 %ignore fz_assert_lock_not_held; | |
| 896 %ignore fz_lock_debug_lock; | |
| 897 %ignore fz_lock_debug_unlock; | |
| 898 | |
| 899 %ignore Memento_cpp_new; | |
| 900 %ignore Memento_cpp_delete; | |
| 901 %ignore Memento_cpp_new_array; | |
| 902 %ignore Memento_cpp_delete_array; | |
| 903 %ignore Memento_showHash; | |
| 904 | |
| 905 // asprintf() isn't available on Windows, so exclude Memento_asprintf because | |
| 906 // it is #define-d to asprintf. | |
| 907 %ignore {rename.ll_fn('Memento_asprintf')}; | |
| 908 %ignore {rename.fn('Memento_asprintf')}; | |
| 909 | |
| 910 // Might prefer to #include mupdf/exceptions.h and make the | |
| 911 // %exception block below handle all the different exception types, | |
| 912 // but swig-3 cannot parse 'throw()' in mupdf/exceptions.h. | |
| 913 // | |
| 914 // So for now we just #include <stdexcept> and handle | |
| 915 // std::exception only. | |
| 916 | |
| 917 %include "typemaps.i" | |
| 918 %include "cpointer.i" | |
| 919 | |
| 920 // This appears to allow python to call fns taking an int64_t. | |
| 921 %include "stdint.i" | |
| 922 | |
| 923 /* | |
| 924 This is only documented for Ruby, but is mentioned for Python at | |
| 925 https://sourceforge.net/p/swig/mailman/message/4867286/. | |
| 926 | |
| 927 It makes the Python wrapper for `FzErrorBase` inherit Python's | |
| 928 `Exception` instead of `object`, which in turn means it can be | |
| 929 caught in Python with `except Exception as e: ...` or similar. | |
| 930 | |
| 931 Note that while it will have the underlying C++ class's `what()` | |
| 932 method, this is not used by the `__str__()` and `__repr__()` | |
| 933 methods. Instead: | |
| 934 | |
| 935 `__str__()` appears to return a tuple of the constructor args | |
| 936 that were originally used to create the exception object with | |
| 937 `PyObject_CallObject(class_, args)`. | |
| 938 | |
| 939 `__repr__()` returns a SWIG-style string such as | |
| 940 `<texcept.MyError; proxy of <Swig Object of type 'MyError *' at | |
| 941 0xb61ebfabc00> >`. | |
| 942 | |
| 943 We explicitly overwrite `__str__()` to call `what()`. | |
| 944 */ | |
| 945 %feature("exceptionclass") FzErrorBase; | |
| 946 | |
| 947 %{{ | |
| 948 ''') | |
| 949 | |
| 950 text += common | |
| 951 | |
| 952 text += textwrap.dedent(f''' | |
| 953 %}} | |
| 954 | |
| 955 %include exception.i | |
| 956 %include std_string.i | |
| 957 %include carrays.i | |
| 958 %include cdata.i | |
| 959 %include std_vector.i | |
| 960 %include std_map.i | |
| 961 | |
| 962 {"%include argcargv.i" if language=="python" else ""} | |
| 963 | |
| 964 %array_class(unsigned char, uchar_array); | |
| 965 | |
| 966 %include <cstring.i> | |
| 967 | |
| 968 namespace std | |
| 969 {{ | |
| 970 %template(vectoruc) vector<unsigned char>; | |
| 971 %template(vectori) vector<int>; | |
| 972 %template(vectorf) vector<float>; | |
| 973 %template(vectord) vector<double>; | |
| 974 %template(vectors) vector<std::string>; | |
| 975 %template(map_string_int) map<std::string, int>; | |
| 976 %template(vectorq) vector<{rename.namespace_class("fz_quad")}>; | |
| 977 %template(vector_search_page2_hit) vector<fz_search_page2_hit>; | |
| 978 %template(vector_fz_font_ucs_gid) vector<fz_font_ucs_gid>; | |
| 979 %template(vector_fz_point) vector<fz_point>; | |
| 980 }}; | |
| 981 | |
| 982 // Make sure that operator++() gets converted to __next__(). | |
| 983 // | |
| 984 // Note that swig already seems to do: | |
| 985 // | |
| 986 // operator* => __ref__ | |
| 987 // operator== => __eq__ | |
| 988 // operator!= => __ne__ | |
| 989 // operator-> => __deref__ | |
| 990 // | |
| 991 // Just need to add this method to containers that already have | |
| 992 // begin() and end(): | |
| 993 // def __iter__( self): | |
| 994 // return CppIterator( self) | |
| 995 // | |
| 996 | |
| 997 %rename(__increment__) *::operator++; | |
| 998 | |
| 999 // Create fns that give access to arrays of some basic types, e.g. bytes_getitem(). | |
| 1000 // | |
| 1001 %array_functions(unsigned char, bytes); | |
| 1002 | |
| 1003 // Useful for fz_stroke_state::dash_list[]. | |
| 1004 %array_functions(float, floats); | |
| 1005 ''') | |
| 1006 | |
| 1007 if language == 'python': | |
| 1008 text += generated.swig_python_exceptions.getvalue() | |
| 1009 | |
| 1010 text += textwrap.dedent(f''' | |
| 1011 // Ensure SWIG handles OUTPUT params. | |
| 1012 // | |
| 1013 %include "cpointer.i" | |
| 1014 ''') | |
| 1015 | |
| 1016 if swig_major < 4: | |
| 1017 text += textwrap.dedent(f''' | |
| 1018 // SWIG version is less than 4 so swig is not able to copy | |
| 1019 // across comments from header file into generated code. The | |
| 1020 // next best thing is to use autodoc to make swig at least show | |
| 1021 // some generic information about arg types. | |
| 1022 // | |
| 1023 %feature("autodoc", "3"); | |
| 1024 ''') | |
| 1025 | |
| 1026 text += textwrap.dedent(f''' | |
| 1027 // Tell swig about pdf_clean_file()'s (int,argv)-style args: | |
| 1028 %apply (int ARGC, char **ARGV) {{ (int retainlen, char *retainlist[]) }} | |
| 1029 ''') | |
| 1030 | |
| 1031 if language == 'python': | |
| 1032 text += textwrap.dedent( ''' | |
| 1033 %include pybuffer.i | |
| 1034 | |
| 1035 /* Convert Python buffer to (const unsigned char*, size_t) pair | |
| 1036 for python_buffer_data(). */ | |
| 1037 %pybuffer_binary( | |
| 1038 const unsigned char* PYTHON_BUFFER_DATA, | |
| 1039 size_t PYTHON_BUFFER_SIZE | |
| 1040 ); | |
| 1041 /* Convert Python buffer to (unsigned char*, size_t) pair for | |
| 1042 python_mutable_bytes_data(). */ | |
| 1043 %pybuffer_mutable_binary( | |
| 1044 unsigned char* PYTHON_BUFFER_MUTABLE_DATA, | |
| 1045 size_t PYTHON_BUFFER_MUTABLE_SIZE | |
| 1046 ); | |
| 1047 ''' | |
| 1048 ) | |
| 1049 | |
| 1050 text += common | |
| 1051 | |
| 1052 if language == 'python': | |
| 1053 | |
| 1054 text += textwrap.dedent(f''' | |
| 1055 %pointer_functions(int, pint); | |
| 1056 | |
| 1057 %pythoncode %{{ | |
| 1058 | |
| 1059 import inspect | |
| 1060 import os | |
| 1061 import re | |
| 1062 import sys | |
| 1063 import traceback | |
| 1064 | |
| 1065 def log( text): | |
| 1066 print( text, file=sys.stderr) | |
| 1067 | |
| 1068 g_mupdf_trace_director = (os.environ.get('MUPDF_trace_director') == '1') | |
| 1069 | |
| 1070 def fz_lookup_metadata(document, key): | |
| 1071 """ | |
| 1072 Like fz_lookup_metadata2() but returns None on error | |
| 1073 instead of raising exception. | |
| 1074 """ | |
| 1075 try: | |
| 1076 return fz_lookup_metadata2(document, key) | |
| 1077 except Exception: | |
| 1078 return | |
| 1079 {rename.class_('fz_document')}.{rename.method('fz_document', 'fz_lookup_metadata')} \ | |
| 1080 = fz_lookup_metadata | |
| 1081 | |
| 1082 def pdf_lookup_metadata(document, key): | |
| 1083 """ | |
| 1084 Likepsd_lookup_metadata2() but returns None on error | |
| 1085 instead of raising exception. | |
| 1086 """ | |
| 1087 try: | |
| 1088 return pdf_lookup_metadata2(document, key) | |
| 1089 except Exception: | |
| 1090 return | |
| 1091 {rename.class_('pdf_document')}.{rename.method('pdf_document', 'pdf_lookup_metadata')} \ | |
| 1092 = pdf_lookup_metadata | |
| 1093 | |
| 1094 ''') | |
| 1095 | |
| 1096 exception_info_text = inspect.getsource(jlib.exception_info) | |
| 1097 text += 'import inspect\n' | |
| 1098 text += 'import io\n' | |
| 1099 text += 'import os\n' | |
| 1100 text += 'import sys\n' | |
| 1101 text += 'import traceback\n' | |
| 1102 text += 'import types\n' | |
| 1103 text += exception_info_text | |
| 1104 | |
| 1105 if language == 'python': | |
| 1106 # Make some additions to the generated Python module. | |
| 1107 # | |
| 1108 # E.g. python wrappers for functions that take out-params should return | |
| 1109 # tuples. | |
| 1110 # | |
| 1111 text += generated.swig_python | |
| 1112 text += generated.swig_python_set_error_classes.getvalue() | |
| 1113 | |
| 1114 def set_class_method(struct, fn): | |
| 1115 return f'{rename.class_(struct)}.{rename.method(struct, fn)} = {fn}' | |
| 1116 | |
| 1117 text += textwrap.dedent(f''' | |
| 1118 | |
| 1119 # Wrap fz_parse_page_range() to fix SWIG bug where a NULL return | |
| 1120 # value seems to mess up the returned list - we end up with ret | |
| 1121 # containing two elements rather than three, e.g. [0, 2]. This | |
| 1122 # occurs with SWIG-3.0; maybe fixed in SWIG-4? | |
| 1123 # | |
| 1124 ll_fz_parse_page_range_orig = ll_fz_parse_page_range | |
| 1125 def ll_fz_parse_page_range(s, n): | |
| 1126 ret = ll_fz_parse_page_range_orig(s, n) | |
| 1127 if len(ret) == 2: | |
| 1128 return None, 0, 0 | |
| 1129 else: | |
| 1130 return ret[0], ret[1], ret[2] | |
| 1131 fz_parse_page_range = ll_fz_parse_page_range | |
| 1132 | |
| 1133 # Provide native python implementation of format_output_path() (-> | |
| 1134 # fz_format_output_path). | |
| 1135 # | |
| 1136 def ll_fz_format_output_path( format, page): | |
| 1137 m = re.search( '(%[0-9]*d)', format) | |
| 1138 if m: | |
| 1139 ret = format[ :m.start(1)] + str(page) + format[ m.end(1):] | |
| 1140 else: | |
| 1141 dot = format.rfind( '.') | |
| 1142 if dot < 0: | |
| 1143 dot = len( format) | |
| 1144 ret = format[:dot] + str(page) + format[dot:] | |
| 1145 return ret | |
| 1146 fz_format_output_path = ll_fz_format_output_path | |
| 1147 | |
| 1148 class IteratorWrap: | |
| 1149 """ | |
| 1150 This is a Python iterator for containers that have C++-style | |
| 1151 begin() and end() methods that return iterators. | |
| 1152 | |
| 1153 Iterators must have the following methods: | |
| 1154 | |
| 1155 __increment__(): move to next item in the container. | |
| 1156 __ref__(): return reference to item in the container. | |
| 1157 | |
| 1158 Must also be able to compare two iterators for equality. | |
| 1159 | |
| 1160 """ | |
| 1161 def __init__( self, container): | |
| 1162 self.container = container | |
| 1163 self.pos = None | |
| 1164 self.end = container.end() | |
| 1165 def __iter__( self): | |
| 1166 return self | |
| 1167 def __next__( self): # for python2. | |
| 1168 if self.pos is None: | |
| 1169 self.pos = self.container.begin() | |
| 1170 else: | |
| 1171 self.pos.__increment__() | |
| 1172 if self.pos == self.end: | |
| 1173 raise StopIteration() | |
| 1174 return self.pos.__ref__() | |
| 1175 def next( self): # for python3. | |
| 1176 return self.__next__() | |
| 1177 | |
| 1178 # The auto-generated Python class method | |
| 1179 # {rename.class_('fz_buffer')}.{rename.method('fz_buffer', 'fz_buffer_extract')}() returns (size, data). | |
| 1180 # | |
| 1181 # But these raw values aren't particularly useful to | |
| 1182 # Python code so we change the method to return a Python | |
| 1183 # bytes instance instead, using the special C function | |
| 1184 # buffer_extract_bytes() defined above. | |
| 1185 # | |
| 1186 # The raw values for a buffer are available via | |
| 1187 # fz_buffer_storage(). | |
| 1188 | |
| 1189 def ll_fz_buffer_extract(buffer): | |
| 1190 """ | |
| 1191 Returns buffer data as a Python bytes instance, leaving the | |
| 1192 buffer empty. | |
| 1193 """ | |
| 1194 assert isinstance( buffer, fz_buffer) | |
| 1195 return ll_fz_buffer_to_bytes_internal(buffer, clear=1) | |
| 1196 def fz_buffer_extract(buffer): | |
| 1197 """ | |
| 1198 Returns buffer data as a Python bytes instance, leaving the | |
| 1199 buffer empty. | |
| 1200 """ | |
| 1201 assert isinstance( buffer, FzBuffer) | |
| 1202 return ll_fz_buffer_extract(buffer.m_internal) | |
| 1203 {set_class_method('fz_buffer', 'fz_buffer_extract')} | |
| 1204 | |
| 1205 def ll_fz_buffer_extract_copy( buffer): | |
| 1206 """ | |
| 1207 Returns buffer data as a Python bytes instance, leaving the | |
| 1208 buffer unchanged. | |
| 1209 """ | |
| 1210 assert isinstance( buffer, fz_buffer) | |
| 1211 return ll_fz_buffer_to_bytes_internal(buffer, clear=0) | |
| 1212 def fz_buffer_extract_copy( buffer): | |
| 1213 """ | |
| 1214 Returns buffer data as a Python bytes instance, leaving the | |
| 1215 buffer unchanged. | |
| 1216 """ | |
| 1217 assert isinstance( buffer, FzBuffer) | |
| 1218 return ll_fz_buffer_extract_copy(buffer.m_internal) | |
| 1219 {set_class_method('fz_buffer', 'fz_buffer_extract_copy')} | |
| 1220 | |
| 1221 # [ll_fz_buffer_storage_memoryview() is implemented in C.] | |
| 1222 def fz_buffer_storage_memoryview( buffer, writable=False): | |
| 1223 """ | |
| 1224 Returns a read-only or writable Python `memoryview` onto | |
| 1225 `fz_buffer` data. This relies on `buffer` existing and | |
| 1226 not changing size while the `memoryview` is used. | |
| 1227 """ | |
| 1228 assert isinstance( buffer, FzBuffer) | |
| 1229 return ll_fz_buffer_storage_memoryview( buffer.m_internal, writable) | |
| 1230 {set_class_method('fz_buffer', 'fz_buffer_storage_memoryview')} | |
| 1231 | |
| 1232 # Overwrite wrappers for fz_new_buffer_from_copied_data() to | |
| 1233 # take Python buffer. | |
| 1234 # | |
| 1235 ll_fz_new_buffer_from_copied_data_orig = ll_fz_new_buffer_from_copied_data | |
| 1236 def ll_fz_new_buffer_from_copied_data(data): | |
| 1237 """ | |
| 1238 Returns fz_buffer containing copy of `data`, which should | |
| 1239 be a `bytes` or similar Python buffer instance. | |
| 1240 """ | |
| 1241 buffer_ = ll_fz_new_buffer_from_copied_data_orig(python_buffer_data(data), len(data)) | |
| 1242 return buffer_ | |
| 1243 def fz_new_buffer_from_copied_data(data): | |
| 1244 """ | |
| 1245 Returns FzBuffer containing copy of `data`, which should be | |
| 1246 a `bytes` or similar Python buffer instance. | |
| 1247 """ | |
| 1248 return FzBuffer( ll_fz_new_buffer_from_copied_data( data)) | |
| 1249 {set_class_method('fz_buffer', 'fz_new_buffer_from_copied_data')} | |
| 1250 | |
| 1251 def ll_pdf_dict_getl(obj, *tail): | |
| 1252 """ | |
| 1253 Python implementation of ll_pdf_dict_getl(), because SWIG | |
| 1254 doesn't handle variadic args. Each item in `tail` should be | |
| 1255 `mupdf.pdf_obj`. | |
| 1256 """ | |
| 1257 for key in tail: | |
| 1258 if not obj: | |
| 1259 break | |
| 1260 obj = ll_pdf_dict_get(obj, key) | |
| 1261 assert isinstance(obj, pdf_obj) | |
| 1262 return obj | |
| 1263 def pdf_dict_getl(obj, *tail): | |
| 1264 """ | |
| 1265 Python implementation of pdf_dict_getl(), because SWIG | |
| 1266 doesn't handle variadic args. Each item in `tail` should be | |
| 1267 a `mupdf.PdfObj`. | |
| 1268 """ | |
| 1269 for key in tail: | |
| 1270 if not obj.m_internal: | |
| 1271 break | |
| 1272 obj = pdf_dict_get(obj, key) | |
| 1273 assert isinstance(obj, PdfObj) | |
| 1274 return obj | |
| 1275 {set_class_method('pdf_obj', 'pdf_dict_getl')} | |
| 1276 | |
| 1277 def ll_pdf_dict_putl(obj, val, *tail): | |
| 1278 """ | |
| 1279 Python implementation of ll_pdf_dict_putl() because SWIG | |
| 1280 doesn't handle variadic args. Each item in `tail` should | |
| 1281 be a SWIG wrapper for a `pdf_obj`. | |
| 1282 """ | |
| 1283 if ll_pdf_is_indirect( obj): | |
| 1284 obj = ll_pdf_resolve_indirect_chain( obj) | |
| 1285 if not pdf_is_dict( obj): | |
| 1286 raise Exception(f'not a dict: {{obj}}') | |
| 1287 if not tail: | |
| 1288 return | |
| 1289 doc = ll_pdf_get_bound_document( obj) | |
| 1290 for i, key in enumerate( tail[:-1]): | |
| 1291 assert isinstance( key, PdfObj), f'Item {{i}} in `tail` should be a pdf_obj but is a {{type(key)}}.' | |
| 1292 next_obj = ll_pdf_dict_get( obj, key) | |
| 1293 if not next_obj: | |
| 1294 # We have to create entries | |
| 1295 next_obj = ll_pdf_new_dict( doc, 1) | |
| 1296 ll_pdf_dict_put( obj, key, next_obj) | |
| 1297 obj = next_obj | |
| 1298 key = tail[-1] | |
| 1299 ll_pdf_dict_put( obj, key, val) | |
| 1300 def pdf_dict_putl(obj, val, *tail): | |
| 1301 """ | |
| 1302 Python implementation of pdf_dict_putl(fz_context *ctx, | |
| 1303 pdf_obj *obj, pdf_obj *val, ...) because SWIG doesn't | |
| 1304 handle variadic args. Each item in `tail` should | |
| 1305 be a SWIG wrapper for a `PdfObj`. | |
| 1306 """ | |
| 1307 if pdf_is_indirect( obj): | |
| 1308 obj = pdf_resolve_indirect_chain( obj) | |
| 1309 if not pdf_is_dict( obj): | |
| 1310 raise Exception(f'not a dict: {{obj}}') | |
| 1311 if not tail: | |
| 1312 return | |
| 1313 doc = pdf_get_bound_document( obj) | |
| 1314 for i, key in enumerate( tail[:-1]): | |
| 1315 assert isinstance( key, PdfObj), f'item {{i}} in `tail` should be a PdfObj but is a {{type(key)}}.' | |
| 1316 next_obj = pdf_dict_get( obj, key) | |
| 1317 if not next_obj.m_internal: | |
| 1318 # We have to create entries | |
| 1319 next_obj = pdf_new_dict( doc, 1) | |
| 1320 pdf_dict_put( obj, key, next_obj) | |
| 1321 obj = next_obj | |
| 1322 key = tail[-1] | |
| 1323 pdf_dict_put( obj, key, val) | |
| 1324 {set_class_method('pdf_obj', 'pdf_dict_putl')} | |
| 1325 | |
| 1326 def pdf_dict_putl_drop(obj, *tail): | |
| 1327 raise Exception('mupdf.pdf_dict_putl_drop() is unsupported and unnecessary in Python because reference counting is automatic. Instead use mupdf.pdf_dict_putl().') | |
| 1328 {set_class_method('pdf_obj', 'pdf_dict_putl_drop')} | |
| 1329 | |
| 1330 def ll_pdf_set_annot_color(annot, color): | |
| 1331 """ | |
| 1332 Low-level Python implementation of pdf_set_annot_color() | |
| 1333 using ll_pdf_set_annot_color2(). | |
| 1334 """ | |
| 1335 if isinstance(color, float): | |
| 1336 ll_pdf_set_annot_color2(annot, 1, color, 0, 0, 0) | |
| 1337 elif len(color) == 1: | |
| 1338 ll_pdf_set_annot_color2(annot, 1, color[0], 0, 0, 0) | |
| 1339 elif len(color) == 2: | |
| 1340 ll_pdf_set_annot_color2(annot, 2, color[0], color[1], 0, 0) | |
| 1341 elif len(color) == 3: | |
| 1342 ll_pdf_set_annot_color2(annot, 3, color[0], color[1], color[2], 0) | |
| 1343 elif len(color) == 4: | |
| 1344 ll_pdf_set_annot_color2(annot, 4, color[0], color[1], color[2], color[3]) | |
| 1345 else: | |
| 1346 raise Exception( f'Unexpected color should be float or list of 1-4 floats: {{color}}') | |
| 1347 def pdf_set_annot_color(self, color): | |
| 1348 return ll_pdf_set_annot_color(self.m_internal, color) | |
| 1349 {set_class_method('pdf_annot', 'pdf_set_annot_color')} | |
| 1350 | |
| 1351 def ll_pdf_set_annot_interior_color(annot, color): | |
| 1352 """ | |
| 1353 Low-level Python version of pdf_set_annot_color() using | |
| 1354 pdf_set_annot_color2(). | |
| 1355 """ | |
| 1356 if isinstance(color, float): | |
| 1357 ll_pdf_set_annot_interior_color2(annot, 1, color, 0, 0, 0) | |
| 1358 elif len(color) == 1: | |
| 1359 ll_pdf_set_annot_interior_color2(annot, 1, color[0], 0, 0, 0) | |
| 1360 elif len(color) == 2: | |
| 1361 ll_pdf_set_annot_interior_color2(annot, 2, color[0], color[1], 0, 0) | |
| 1362 elif len(color) == 3: | |
| 1363 ll_pdf_set_annot_interior_color2(annot, 3, color[0], color[1], color[2], 0) | |
| 1364 elif len(color) == 4: | |
| 1365 ll_pdf_set_annot_interior_color2(annot, 4, color[0], color[1], color[2], color[3]) | |
| 1366 else: | |
| 1367 raise Exception( f'Unexpected color should be float or list of 1-4 floats: {{color}}') | |
| 1368 def pdf_set_annot_interior_color(self, color): | |
| 1369 """ | |
| 1370 Python version of pdf_set_annot_color() using | |
| 1371 pdf_set_annot_color2(). | |
| 1372 """ | |
| 1373 return ll_pdf_set_annot_interior_color(self.m_internal, color) | |
| 1374 {set_class_method('pdf_annot', 'pdf_set_annot_interior_color')} | |
| 1375 | |
| 1376 def ll_fz_fill_text( dev, text, ctm, colorspace, color, alpha, color_params): | |
| 1377 """ | |
| 1378 Low-level Python version of fz_fill_text() taking list/tuple for `color`. | |
| 1379 """ | |
| 1380 color = tuple(color) + (0,) * (4-len(color)) | |
| 1381 assert len(color) == 4, f'color not len 4: len={{len(color)}}: {{color}}' | |
| 1382 return ll_fz_fill_text2(dev, text, ctm, colorspace, *color, alpha, color_params) | |
| 1383 def fz_fill_text(dev, text, ctm, colorspace, color, alpha, color_params): | |
| 1384 """ | |
| 1385 Python version of fz_fill_text() taking list/tuple for `color`. | |
| 1386 """ | |
| 1387 return ll_fz_fill_text( | |
| 1388 dev.m_internal, | |
| 1389 text.m_internal, | |
| 1390 ctm.internal(), | |
| 1391 colorspace.m_internal, | |
| 1392 color, | |
| 1393 alpha, | |
| 1394 color_params.internal(), | |
| 1395 ) | |
| 1396 {set_class_method('fz_device', 'fz_fill_text')} | |
| 1397 | |
| 1398 # Override mupdf_convert_color() to return (rgb0, rgb1, rgb2, rgb3). | |
| 1399 def ll_fz_convert_color( ss, sv, ds, is_, params): | |
| 1400 """ | |
| 1401 Low-level Python version of fz_convert_color(). | |
| 1402 | |
| 1403 `sv` should be a float or list of 1-4 floats or a SWIG | |
| 1404 representation of a float*. | |
| 1405 | |
| 1406 Returns (dv0, dv1, dv2, dv3). | |
| 1407 """ | |
| 1408 dv = fz_convert_color2_v() | |
| 1409 if isinstance( sv, float): | |
| 1410 ll_fz_convert_color2( ss, sv, 0.0, 0.0, 0.0, ds, dv, is_, params) | |
| 1411 elif isinstance( sv, (tuple, list)): | |
| 1412 sv2 = tuple(sv) + (0,) * (4-len(sv)) | |
| 1413 ll_fz_convert_color2( ss, *sv2, ds, dv, is_, params) | |
| 1414 else: | |
| 1415 # Assume `sv` is SWIG representation of a `float*`. | |
| 1416 ll_fz_convert_color2( ss, sv, ds, dv, is_, params) | |
| 1417 return dv.v0, dv.v1, dv.v2, dv.v3 | |
| 1418 def fz_convert_color( ss, sv, ds, is_, params): | |
| 1419 """ | |
| 1420 Python version of fz_convert_color(). | |
| 1421 | |
| 1422 `sv` should be a float or list of 1-4 floats or a SWIG | |
| 1423 representation of a float*. | |
| 1424 | |
| 1425 Returns (dv0, dv1, dv2, dv3). | |
| 1426 """ | |
| 1427 return ll_fz_convert_color( ss.m_internal, sv, ds.m_internal, is_.m_internal, params.internal()) | |
| 1428 {set_class_method('fz_colorspace', 'fz_convert_color')} | |
| 1429 | |
| 1430 # Override fz_set_warning_callback() and | |
| 1431 # fz_set_error_callback() to use Python classes derived from | |
| 1432 # our SWIG Director class DiagnosticCallback (defined in C), so | |
| 1433 # that fnptrs can call Python code. | |
| 1434 # | |
| 1435 | |
| 1436 # We store DiagnosticCallbackPython instances in these | |
| 1437 # globals to ensure they continue to exist after | |
| 1438 # set_diagnostic_callback() returns. | |
| 1439 # | |
| 1440 set_warning_callback_s = None | |
| 1441 set_error_callback_s = None | |
| 1442 | |
| 1443 # Override set_error_callback(). | |
| 1444 class DiagnosticCallbackPython( DiagnosticCallback): | |
| 1445 """ | |
| 1446 Overrides Director class DiagnosticCallback's virtual | |
| 1447 `_print()` method in Python. | |
| 1448 """ | |
| 1449 def __init__( self, description, printfn): | |
| 1450 super().__init__( description) | |
| 1451 self.printfn = printfn | |
| 1452 if g_mupdf_trace_director: | |
| 1453 log( f'DiagnosticCallbackPython[{{self.m_description}}].__init__() self={{self!r}} printfn={{printfn!r}}') | |
| 1454 def __del__( self): | |
| 1455 if g_mupdf_trace_director: | |
| 1456 log( f'DiagnosticCallbackPython[{{self.m_description}}].__del__() destructor called.') | |
| 1457 def _print( self, message): | |
| 1458 if g_mupdf_trace_director: | |
| 1459 log( f'DiagnosticCallbackPython[{{self.m_description}}]._print(): Calling self.printfn={{self.printfn!r}} with message={{message!r}}') | |
| 1460 try: | |
| 1461 self.printfn( message) | |
| 1462 except Exception as e: | |
| 1463 # This shouldn't happen, so always output a diagnostic. | |
| 1464 log( f'DiagnosticCallbackPython[{{self.m_description}}]._print(): Warning: exception from self.printfn={{self.printfn!r}}: e={{e!r}}') | |
| 1465 # Calling `raise` here serves to test | |
| 1466 # `DiagnosticCallback()`'s swallowing of what will | |
| 1467 # be a C++ exception. But we could swallow the | |
| 1468 # exception here instead. | |
| 1469 raise | |
| 1470 | |
| 1471 def set_diagnostic_callback( description, printfn): | |
| 1472 if g_mupdf_trace_director: | |
| 1473 log( f'set_diagnostic_callback() description={{description!r}} printfn={{printfn!r}}') | |
| 1474 if printfn: | |
| 1475 ret = DiagnosticCallbackPython( description, printfn) | |
| 1476 return ret | |
| 1477 else: | |
| 1478 if g_mupdf_trace_director: | |
| 1479 log( f'Calling ll_fz_set_{{description}}_callback() with (None, None)') | |
| 1480 if description == 'error': | |
| 1481 ll_fz_set_error_callback( None, None) | |
| 1482 elif description == 'warning': | |
| 1483 ll_fz_set_warning_callback( None, None) | |
| 1484 else: | |
| 1485 assert 0, f'Unrecognised description={{description!r}}' | |
| 1486 return None | |
| 1487 | |
| 1488 def fz_set_error_callback( printfn): | |
| 1489 global set_error_callback_s | |
| 1490 set_error_callback_s = set_diagnostic_callback( 'error', printfn) | |
| 1491 | |
| 1492 def fz_set_warning_callback( printfn): | |
| 1493 global set_warning_callback_s | |
| 1494 set_warning_callback_s = set_diagnostic_callback( 'warning', printfn) | |
| 1495 | |
| 1496 # Direct access to fz_pixmap samples. | |
| 1497 def ll_fz_pixmap_samples_memoryview( pixmap): | |
| 1498 """ | |
| 1499 Returns a writable Python `memoryview` for a `fz_pixmap`. | |
| 1500 """ | |
| 1501 assert isinstance( pixmap, fz_pixmap) | |
| 1502 ret = python_memoryview_from_memory( | |
| 1503 ll_fz_pixmap_samples( pixmap), | |
| 1504 ll_fz_pixmap_stride( pixmap) * ll_fz_pixmap_height( pixmap), | |
| 1505 1, # writable | |
| 1506 ) | |
| 1507 return ret | |
| 1508 def fz_pixmap_samples_memoryview( pixmap): | |
| 1509 """ | |
| 1510 Returns a writable Python `memoryview` for a `FzPixmap`. | |
| 1511 """ | |
| 1512 return ll_fz_pixmap_samples_memoryview( pixmap.m_internal) | |
| 1513 {set_class_method('fz_pixmap', 'fz_pixmap_samples_memoryview')} | |
| 1514 | |
| 1515 # Avoid potential unsafe use of variadic args by forcing a | |
| 1516 # single arg and escaping all '%' characters. (Passing ('%s', | |
| 1517 # text) does not work - results in "(null)" being output.) | |
| 1518 # | |
| 1519 ll_fz_warn_original = ll_fz_warn | |
| 1520 def ll_fz_warn( text): | |
| 1521 assert isinstance( text, str), f'text={{text!r}} str={{str!r}}' | |
| 1522 text = text.replace( '%', '%%') | |
| 1523 return ll_fz_warn_original( text) | |
| 1524 fz_warn = ll_fz_warn | |
| 1525 | |
| 1526 # Force use of pdf_load_field_name2() instead of | |
| 1527 # pdf_load_field_name() because the latter returns a char* | |
| 1528 # buffer that must be freed by the caller. | |
| 1529 ll_pdf_load_field_name = ll_pdf_load_field_name2 | |
| 1530 pdf_load_field_name = pdf_load_field_name2 | |
| 1531 {set_class_method('pdf_obj', 'pdf_load_field_name')} | |
| 1532 | |
| 1533 # It's important that when we create class derived | |
| 1534 # from StoryPositionsCallback, we ensure that | |
| 1535 # StoryPositionsCallback's constructor is called. Otherwise | |
| 1536 # the new instance doesn't seem to be an instance of | |
| 1537 # StoryPositionsCallback. | |
| 1538 # | |
| 1539 class StoryPositionsCallback_python( StoryPositionsCallback): | |
| 1540 def __init__( self, python_callback): | |
| 1541 super().__init__() | |
| 1542 self.python_callback = python_callback | |
| 1543 def call( self, position): | |
| 1544 self.python_callback( position) | |
| 1545 | |
| 1546 ll_fz_story_positions_orig = ll_fz_story_positions | |
| 1547 def ll_fz_story_positions( story, python_callback): | |
| 1548 """ | |
| 1549 Custom replacement for `ll_fz_story_positions()` that takes | |
| 1550 a Python callable `python_callback`. | |
| 1551 """ | |
| 1552 #log( f'll_fz_story_positions() type(story)={{type(story)!r}} type(python_callback)={{type(python_callback)!r}}') | |
| 1553 python_callback_instance = StoryPositionsCallback_python( python_callback) | |
| 1554 ll_fz_story_positions_director( story, python_callback_instance) | |
| 1555 def fz_story_positions( story, python_callback): | |
| 1556 #log( f'fz_story_positions() type(story)={{type(story)!r}} type(python_callback)={{type(python_callback)!r}}') | |
| 1557 assert isinstance( story, FzStory) | |
| 1558 assert callable( python_callback) | |
| 1559 def python_callback2( position): | |
| 1560 position2 = FzStoryElementPosition( position) | |
| 1561 python_callback( position2) | |
| 1562 ll_fz_story_positions( story.m_internal, python_callback2) | |
| 1563 {set_class_method('fz_story', 'fz_story_positions')} | |
| 1564 | |
| 1565 # Monkey-patch `FzDocumentWriter.__init__()` to set `self._out` | |
| 1566 # to any `FzOutput2` arg. This ensures that the Python part of | |
| 1567 # the derived `FzOutput2` instance is kept alive for use by the | |
| 1568 # `FzDocumentWriter`, otherwise Python can delete it, then get | |
| 1569 # a SEGV if C++ tries to call the derived Python methods. | |
| 1570 # | |
| 1571 # [We don't patch equivalent class-aware functions such | |
| 1572 # as `fz_new_pdf_writer_with_output()` because they are | |
| 1573 # not available to C++/Python, because FzDocumentWriter is | |
| 1574 # non-copyable.] | |
| 1575 # | |
| 1576 FzDocumentWriter__init__0 = FzDocumentWriter.__init__ | |
| 1577 def FzDocumentWriter__init__1(self, *args): | |
| 1578 out = None | |
| 1579 for arg in args: | |
| 1580 if isinstance( arg, FzOutput2): | |
| 1581 assert not out, "More than one FzOutput2 passed to FzDocumentWriter.__init__()" | |
| 1582 out = arg | |
| 1583 if out is not None: | |
| 1584 self._out = out | |
| 1585 return FzDocumentWriter__init__0(self, *args) | |
| 1586 FzDocumentWriter.__init__ = FzDocumentWriter__init__1 | |
| 1587 | |
| 1588 # Create class derived from | |
| 1589 # fz_install_load_system_font_funcs_args class wrapper with | |
| 1590 # overrides of the virtual functions to allow calling of Python | |
| 1591 # callbacks. | |
| 1592 # | |
| 1593 class fz_install_load_system_font_funcs_args3({rename.class_('fz_install_load_system_font_funcs_args')}2): | |
| 1594 """ | |
| 1595 Class derived from Swig Director class | |
| 1596 fz_install_load_system_font_funcs_args2, to allow | |
| 1597 implementation of fz_install_load_system_font_funcs with | |
| 1598 Python callbacks. | |
| 1599 """ | |
| 1600 def __init__(self, f=None, f_cjk=None, f_fallback=None): | |
| 1601 super().__init__() | |
| 1602 | |
| 1603 self.f3 = f | |
| 1604 self.f_cjk3 = f_cjk | |
| 1605 self.f_fallback3 = f_fallback | |
| 1606 | |
| 1607 self.use_virtual_f(True if f else False) | |
| 1608 self.use_virtual_f_cjk(True if f_cjk else False) | |
| 1609 self.use_virtual_f_fallback(True if f_fallback else False) | |
| 1610 | |
| 1611 def ret_font(self, font): | |
| 1612 if font is None: | |
| 1613 return None | |
| 1614 elif isinstance(font, {rename.class_('fz_font')}): | |
| 1615 return ll_fz_keep_font(font.m_internal) | |
| 1616 elif isinstance(font, fz_font): | |
| 1617 return font | |
| 1618 else: | |
| 1619 assert 0, f'Expected FzFont or fz_font, but fz_install_load_system_font_funcs() callback returned {{type(font)=}}' | |
| 1620 | |
| 1621 def f(self, ctx, name, bold, italic, needs_exact_metrics): | |
| 1622 font = self.f3(name, bold, italic, needs_exact_metrics) | |
| 1623 return self.ret_font(font) | |
| 1624 | |
| 1625 def f_cjk(self, ctx, name, ordering, serif): | |
| 1626 font = self.f_cjk3(name, ordering, serif) | |
| 1627 return self.ret_font(font) | |
| 1628 | |
| 1629 def f_fallback(self, ctx, script, language, serif, bold, italic): | |
| 1630 font = self.f_fallback3(script, language, serif, bold, italic) | |
| 1631 return self.ret_font(font) | |
| 1632 | |
| 1633 # We store the most recently created | |
| 1634 # fz_install_load_system_font_funcs_args in this global so that | |
| 1635 # it is not cleaned up by Python. | |
| 1636 g_fz_install_load_system_font_funcs_args = None | |
| 1637 | |
| 1638 def fz_install_load_system_font_funcs(f=None, f_cjk=None, f_fallback=None): | |
| 1639 """ | |
| 1640 Python override for MuPDF | |
| 1641 fz_install_load_system_font_funcs() using Swig Director | |
| 1642 support. Python callbacks are not passed a `ctx` arg, and | |
| 1643 can return None, a mupdf.fz_font or a mupdf.FzFont. | |
| 1644 """ | |
| 1645 global g_fz_install_load_system_font_funcs_args | |
| 1646 g_fz_install_load_system_font_funcs_args = fz_install_load_system_font_funcs_args3( | |
| 1647 f, | |
| 1648 f_cjk, | |
| 1649 f_fallback, | |
| 1650 ) | |
| 1651 fz_install_load_system_font_funcs2(g_fz_install_load_system_font_funcs_args) | |
| 1652 | |
| 1653 Py_LIMITED_API = {repr(build_dirs.Py_LIMITED_API) if build_dirs.Py_LIMITED_API else 'None'} | |
| 1654 ''') | |
| 1655 | |
| 1656 # Add __iter__() methods for all classes with begin() and end() methods. | |
| 1657 # | |
| 1658 for classname in generated.container_classnames: | |
| 1659 text += f'{classname}.__iter__ = lambda self: IteratorWrap( self)\n' | |
| 1660 | |
| 1661 # For all wrapper classes with a to_string() method, add a __str__() | |
| 1662 # method to the underlying struct's Python class, which calls | |
| 1663 # to_string_<structname>(). | |
| 1664 # | |
| 1665 # E.g. this allows Python code to print a mupdf.fz_rect instance. | |
| 1666 # | |
| 1667 # [We could instead call our generated to_string() and rely on overloading, | |
| 1668 # but this will end up switching on the type in the SWIG code.] | |
| 1669 # | |
| 1670 for struct_name in generated.to_string_structnames: | |
| 1671 text += f'{struct_name}.__str__ = lambda s: to_string_{struct_name}(s)\n' | |
| 1672 text += f'{struct_name}.__repr__ = lambda s: to_string_{struct_name}(s)\n' | |
| 1673 | |
| 1674 # For all wrapper classes with a to_string() method, add a __str__() method | |
| 1675 # to the Python wrapper class, which calls the class's to_string() method. | |
| 1676 # | |
| 1677 # E.g. this allows Python code to print a mupdf.Rect instance. | |
| 1678 # | |
| 1679 for struct_name in generated.to_string_structnames: | |
| 1680 text += f'{rename.class_(struct_name)}.__str__ = lambda self: self.to_string()\n' | |
| 1681 text += f'{rename.class_(struct_name)}.__repr__ = lambda self: self.to_string()\n' | |
| 1682 | |
| 1683 text += '%}\n' | |
| 1684 | |
| 1685 if 1: # lgtm [py/constant-conditional-expression] | |
| 1686 # This is a horrible hack to avoid swig failing because | |
| 1687 # include/mupdf/pdf/object.h defines an enum which contains a #include. | |
| 1688 # | |
| 1689 # Would like to pre-process files in advance so that swig doesn't see | |
| 1690 # the #include, but this breaks swig in a different way - swig cannot | |
| 1691 # cope with some code in system headers. | |
| 1692 # | |
| 1693 # So instead we copy include/mupdf/pdf/object.h into | |
| 1694 # {build_dirs.dir_mupdf}/platform/python/include/mupdf/pdf/object.h, | |
| 1695 # manually expanding the #include using a Python .replace() call. Then | |
| 1696 # we specify {build_dirs.dir_mupdf}/platform/python/include as the | |
| 1697 # first include path so that our modified mupdf/pdf/object.h will get | |
| 1698 # included in preference to the original. | |
| 1699 # | |
| 1700 os.makedirs(f'{build_dirs.dir_mupdf}/platform/python/include/mupdf/pdf', exist_ok=True) | |
| 1701 with open( f'{build_dirs.dir_mupdf}/include/mupdf/pdf/object.h') as f: | |
| 1702 o = f.read() | |
| 1703 with open( f'{build_dirs.dir_mupdf}/include/mupdf/pdf/name-table.h') as f: | |
| 1704 name_table_h = f.read() | |
| 1705 oo = o.replace( '#include "mupdf/pdf/name-table.h"\n', name_table_h) | |
| 1706 assert oo != o | |
| 1707 jlib.fs_update( oo, f'{build_dirs.dir_mupdf}/platform/python/include/mupdf/pdf/object.h') | |
| 1708 | |
| 1709 swig_i = build_dirs.mupdfcpp_swig_i(language) | |
| 1710 swig_cpp = build_dirs.mupdfcpp_swig_cpp(language) | |
| 1711 include1 = f'{build_dirs.dir_mupdf}/include/' | |
| 1712 include2 = f'{build_dirs.dir_mupdf}/platform/c++/include' | |
| 1713 swig_py = f'{build_dirs.dir_so}/mupdf.py' | |
| 1714 | |
| 1715 swig2_i = f'{build_dirs.dir_mupdf}/platform/{language}/mupdfcpp2_swig.i' | |
| 1716 swig2_cpp = f'{build_dirs.dir_mupdf}/platform/{language}/mupdfcpp2_swig.cpp' | |
| 1717 swig2_py = f'{build_dirs.dir_so}/mupdf2.py' | |
| 1718 | |
| 1719 os.makedirs( f'{build_dirs.dir_mupdf}/platform/{language}', exist_ok=True) | |
| 1720 os.makedirs( f'{build_dirs.dir_so}', exist_ok=True) | |
| 1721 util.update_file_regress( text, swig_i, check_regress) | |
| 1722 jlib.fs_update( '', swig2_i) | |
| 1723 | |
| 1724 # Disable some unhelpful SWIG warnings. Must not use -Wall as it overrides | |
| 1725 # all warning disables. | |
| 1726 disable_swig_warnings = [ | |
| 1727 201, # Warning 201: Unable to find 'stddef.h' | |
| 1728 314, # Warning 314: 'print' is a python keyword, renaming to '_print' | |
| 1729 302, # Warning 302: Identifier 'pdf_annot_type' redefined (ignored), | |
| 1730 312, # Warning 312: Nested union not currently supported (ignored). | |
| 1731 321, # Warning 321: 'max' conflicts with a built-in name in python | |
| 1732 322, # Warning 322: Redundant redeclaration of 'pdf_annot', | |
| 1733 362, # Warning 362: operator= ignored | |
| 1734 451, # Warning 451: Setting a const char * variable may leak memory. | |
| 1735 503, # Warning 503: Can't wrap 'operator <<' unless renamed to a valid identifier. | |
| 1736 512, # Warning 512: Overloaded method mupdf::DrawOptions::internal() const ignored, using non-const method mupdf::DrawOptions::internal() instead. | |
| 1737 509, # Warning 509: Overloaded method mupdf::FzAaContext::FzAaContext(::fz_aa_context const) effectively ignored, | |
| 1738 560, # Warning 560: Unknown Doxygen command: d. | |
| 1739 ] | |
| 1740 | |
| 1741 disable_swig_warnings = [ '-' + str( x) for x in disable_swig_warnings] | |
| 1742 disable_swig_warnings = '-w' + ','.join( disable_swig_warnings) | |
| 1743 | |
| 1744 # Preserve any existing file `swig_cpp`, so that we can restore the | |
| 1745 # mtime if SWIG produces an unchanged file. This then avoids unnecessary | |
| 1746 # recompilation. | |
| 1747 # | |
| 1748 # 2022-11-16: Disabled this, because it can result in continuous | |
| 1749 # unnecessary rebuilds, e.g. if .cpp is older than a mupdf header. | |
| 1750 # | |
| 1751 swig_cpp_old = None | |
| 1752 if 0 and os.path.exists( swig_cpp): | |
| 1753 swig_cpp_old = f'{swig_cpp}-old' | |
| 1754 jlib.fs_copy( swig_cpp, swig_cpp_old) | |
| 1755 | |
| 1756 if language == 'python': | |
| 1757 # Maybe use '^' on windows as equivalent to unix '\\' for multiline | |
| 1758 # ending? | |
| 1759 def make_command( module, cpp, swig_i): | |
| 1760 cpp = os.path.relpath( cpp) | |
| 1761 swig_i = os.path.relpath( swig_i) | |
| 1762 # We need to predefine MUPDF_FITZ_HEAP_H to disable parsing of | |
| 1763 # include/mupdf/fitz/heap.h. Otherwise swig's preprocessor seems to | |
| 1764 # ignore #undef's in include/mupdf/fitz/heap-imp.h then complains | |
| 1765 # about redefinition of macros in include/mupdf/fitz/heap.h. | |
| 1766 command = f''' | |
| 1767 "{swig_command}" | |
| 1768 {"-D_WIN32" if state_.windows else ""} | |
| 1769 -c++ | |
| 1770 {"-doxygen" if swig_major >= 4 else ""} | |
| 1771 -python | |
| 1772 -Wextra | |
| 1773 {disable_swig_warnings} | |
| 1774 -module {module} | |
| 1775 -outdir {os.path.relpath(build_dirs.dir_mupdf)}/platform/python | |
| 1776 -o {cpp} | |
| 1777 -includeall | |
| 1778 {os.environ.get('XCXXFLAGS', '')} | |
| 1779 -I{os.path.relpath(build_dirs.dir_mupdf)}/platform/python/include | |
| 1780 -I{os.path.relpath(include1)} | |
| 1781 -I{os.path.relpath(include2)} | |
| 1782 -ignoremissing | |
| 1783 -DMUPDF_FITZ_HEAP_H | |
| 1784 {swig_i} | |
| 1785 ''' | |
| 1786 return command | |
| 1787 | |
| 1788 def modify_py( path_in, path_out): | |
| 1789 with open( path_in) as f: | |
| 1790 text = f.read() | |
| 1791 | |
| 1792 # Change all our PDF_ENUM_NAME_* enums so that they are actually | |
| 1793 # PdfObj instances so that they can be used like any other PdfObj. | |
| 1794 # | |
| 1795 #jlib.log('{len(generated.c_enums)=}') | |
| 1796 for enum_type, enum_names in generated.c_enums.items(): | |
| 1797 for enum_name in enum_names: | |
| 1798 if enum_name.startswith( 'PDF_ENUM_NAME_'): | |
| 1799 text += f'{enum_name} = {rename.class_("pdf_obj")}( obj_enum_to_obj( {enum_name}))\n' | |
| 1800 | |
| 1801 # 2024-09-28: important to not include PDF_LIMIT here, because | |
| 1802 # pdf_drop_obj() treats all pdf_obj*'s as real pointers if they are | |
| 1803 # >= PDF_LIMIT. | |
| 1804 for name in ('NULL', 'TRUE', 'FALSE'): | |
| 1805 text += f'PDF_{name} = {rename.class_("pdf_obj")}( obj_enum_to_obj( PDF_ENUM_{name}))\n' | |
| 1806 | |
| 1807 jlib.fs_update(text, path_out) | |
| 1808 | |
| 1809 jlib.fs_update( '', swig2_cpp) | |
| 1810 jlib.fs_remove( swig2_py) | |
| 1811 | |
| 1812 # Make main mupdf .so. | |
| 1813 command = make_command( 'mupdf', swig_cpp, swig_i) | |
| 1814 swig_py_ = f'{build_dirs.dir_mupdf}/platform/python/mupdf.py' | |
| 1815 rebuilt = jlib.build( | |
| 1816 (swig_i, include1, include2), | |
| 1817 (swig_cpp, swig_py_), | |
| 1818 command, | |
| 1819 force_rebuild, | |
| 1820 ) | |
| 1821 jlib.log(f'swig => {rebuilt=}.') | |
| 1822 updated = modify_py( swig_py_, swig_py) | |
| 1823 jlib.log(f'modify_py() => {updated=}.') | |
| 1824 | |
| 1825 | |
| 1826 elif language == 'csharp': | |
| 1827 outdir = os.path.relpath(f'{build_dirs.dir_mupdf}/platform/csharp') | |
| 1828 os.makedirs(outdir, exist_ok=True) | |
| 1829 # Looks like swig comes up with 'mupdfcpp_swig_wrap.cxx' leafname. | |
| 1830 # | |
| 1831 # We include platform/python/include in order to pick up the modified | |
| 1832 # include/mupdf/pdf/object.h that we generate elsewhere. | |
| 1833 dllimport = 'mupdfcsharp.so' | |
| 1834 if state_.windows: | |
| 1835 # Would like to specify relative path to .dll with: | |
| 1836 # dllimport = os.path.relpath( f'{build_dirs.dir_so}/mupdfcsharp.dll') | |
| 1837 # but Windows/.NET doesn't seem to support this, despite | |
| 1838 # https://stackoverflow.com/questions/31807289 "how can i add a | |
| 1839 # swig generated c dll reference to a c sharp project". | |
| 1840 # | |
| 1841 dllimport = 'mupdfcsharp.dll' | |
| 1842 | |
| 1843 # See https://www.swig.org/Doc4.2/CSharp.html `23.3.1 Primitive types` | |
| 1844 # for description of SWIGWORDSIZE64. If we were to build on 32-bit Linux | |
| 1845 # we would have to remove the `-DSWIGWORDSIZE64` flag. | |
| 1846 command = (f''' | |
| 1847 "{swig_command}" | |
| 1848 {"-D_WIN32" if state_.windows else ""} | |
| 1849 {"-DSWIGWORDSIZE64" if state_.linux else ""} | |
| 1850 -c++ | |
| 1851 -csharp | |
| 1852 -Wextra | |
| 1853 {disable_swig_warnings} | |
| 1854 -module mupdf | |
| 1855 -namespace mupdf | |
| 1856 -dllimport {dllimport} | |
| 1857 -outdir {outdir} | |
| 1858 -outfile mupdf.cs | |
| 1859 -o {os.path.relpath(swig_cpp)} | |
| 1860 -includeall | |
| 1861 -I{os.path.relpath(build_dirs.dir_mupdf)}/platform/python/include | |
| 1862 -I{os.path.relpath(include1)} | |
| 1863 -I{os.path.relpath(include2)} | |
| 1864 -ignoremissing | |
| 1865 -DMUPDF_FITZ_HEAP_H | |
| 1866 {os.path.relpath(swig_i)} | |
| 1867 ''') | |
| 1868 | |
| 1869 rebuilt = jlib.build( | |
| 1870 (swig_i, include1, include2), | |
| 1871 (f'{outdir}/mupdf.cs', os.path.relpath(swig_cpp)), | |
| 1872 command, | |
| 1873 force_rebuild, | |
| 1874 ) | |
| 1875 # fixme: use <rebuilt> line with language=='python' to avoid multiple | |
| 1876 # modifications to unchanged mupdf.cs? | |
| 1877 # | |
| 1878 # For classes that have our to_string() method, override C#'s | |
| 1879 # ToString() to call to_string(). | |
| 1880 with open(f'{outdir}/mupdf.cs') as f: | |
| 1881 cs = f.read() | |
| 1882 cs2 = re.sub( | |
| 1883 '(( *)public string to_string[(][)])', | |
| 1884 '\\2public override string ToString() { return to_string(); }\n\\1', | |
| 1885 cs, | |
| 1886 ) | |
| 1887 jlib.log1('{len(cs)=}') | |
| 1888 jlib.log1('{len(cs2)=}') | |
| 1889 assert cs2 != cs, f'Failed to add toString() methods.' | |
| 1890 jlib.log1('{len(generated.swig_csharp)=}') | |
| 1891 assert len(generated.swig_csharp) | |
| 1892 cs2 += generated.swig_csharp | |
| 1893 jlib.log1( 'Updating cs2 => {build_dirs.dir_so}/mupdf.cs') | |
| 1894 jlib.fs_update(cs2, f'{build_dirs.dir_so}/mupdf.cs') | |
| 1895 #jlib.fs_copy(f'{outdir}/mupdf.cs', f'{build_dirs.dir_so}/mupdf.cs') | |
| 1896 jlib.log1('{rebuilt=}') | |
| 1897 | |
| 1898 else: | |
| 1899 assert 0 | |
| 1900 | |
| 1901 # Disabled; see above for explanation. | |
| 1902 if 0 and swig_cpp_old: | |
| 1903 with open( swig_cpp_old) as f: | |
| 1904 swig_cpp_contents_old = f.read() | |
| 1905 with open(swig_cpp) as f: | |
| 1906 swig_cpp_contents_new = f.read() | |
| 1907 if swig_cpp_contents_new == swig_cpp_contents_old: | |
| 1908 # File <swig_cpp> unchanged, so restore the mtime to avoid | |
| 1909 # unnecessary recompilation. | |
| 1910 jlib.log( 'File contents unchanged, copying {swig_cpp_old=} => {swig_cpp=}') | |
| 1911 jlib.fs_rename( swig_cpp_old, swig_cpp) | |
| 1912 | |
| 1913 | |
| 1914 def test_swig(): | |
| 1915 ''' | |
| 1916 For testing different swig .i constructs. | |
| 1917 ''' | |
| 1918 test_i = textwrap.dedent(''' | |
| 1919 %include argcargv.i | |
| 1920 | |
| 1921 %apply (int ARGC, char **ARGV) { (int retainlen, const char **retainlist) } | |
| 1922 %apply (int ARGC, char **ARGV) { (const char **retainlist, int retainlen) } | |
| 1923 %apply (int ARGC, char **ARGV) { (const char *retainlist[], int retainlen) } | |
| 1924 | |
| 1925 %clear double a, int ARGC, char **ARGV; | |
| 1926 %clear double a, int argc, char *argv[]; | |
| 1927 %clear int ARGC, char **ARGV; | |
| 1928 %clear (double a, int ARGC, char **ARGV); | |
| 1929 %clear (double a, int argc, char *argv[]); | |
| 1930 %clear (int ARGC, char **ARGV); | |
| 1931 %clear int retainlen, const char **retainlist; | |
| 1932 | |
| 1933 int bar( int argc, char* argv[]); | |
| 1934 int foo( double a, int argc, char* argv[]); | |
| 1935 | |
| 1936 int qwe( double a, int argc, const char** argv); | |
| 1937 | |
| 1938 void ppdf_clean_file( char *infile, char *outfile, char *password, pdf_write_options *opts, int retainlen, const char **retainlist); | |
| 1939 void ppdf_clean_file2(char *infile, char *outfile, char *password, pdf_write_options *opts, const char **retainlist, int retainlen); | |
| 1940 void ppdf_clean_file3(char *infile, char *outfile, char *password, pdf_write_options *opts, const char *retainlist[], int retainlen); | |
| 1941 | |
| 1942 ''') | |
| 1943 jlib.fs_update( test_i, 'test.i') | |
| 1944 | |
| 1945 jlib.system( textwrap.dedent( | |
| 1946 ''' | |
| 1947 swig | |
| 1948 -Wall | |
| 1949 -c++ | |
| 1950 -python | |
| 1951 -module test | |
| 1952 -outdir . | |
| 1953 -o test.cpp | |
| 1954 test.i | |
| 1955 ''').replace( '\n', ' \\\n') | |
| 1956 ) | |
| 1957 | |
| 1958 | |
| 1959 def test_swig_csharp(): | |
| 1960 ''' | |
| 1961 Checks behaviour with and without our custom string marshalling code from | |
| 1962 _csharp_unicode_prefix(). | |
| 1963 ''' | |
| 1964 test_swig_csharp_internal(fix=0) | |
| 1965 test_swig_csharp_internal(fix=1) | |
| 1966 | |
| 1967 | |
| 1968 def test_swig_csharp_internal(fix): | |
| 1969 ''' | |
| 1970 Test utf8 string handling, with/without use of _csharp_unicode_prefix(). | |
| 1971 ''' | |
| 1972 # We create C++/C# source directly from this function, and explicitly run | |
| 1973 # C++ and .NET/Mono build commands. | |
| 1974 # | |
| 1975 | |
| 1976 build_dir = f'test_swig_{fix}' | |
| 1977 os.makedirs( build_dir, exist_ok=True) | |
| 1978 | |
| 1979 print('') | |
| 1980 print(f'### test_swig_internal(): {fix=}', flush=1) | |
| 1981 | |
| 1982 # Create SWIG input file `test.i`. | |
| 1983 # | |
| 1984 test_i = '%module test\n' | |
| 1985 | |
| 1986 if fix: | |
| 1987 test_i += _csharp_unicode_prefix() | |
| 1988 | |
| 1989 test_i += textwrap.dedent(f''' | |
| 1990 %include "std_string.i" | |
| 1991 | |
| 1992 // Returns escaped representation of `text`. | |
| 1993 const char* foo1(const char* text); | |
| 1994 | |
| 1995 // Returns escaped representation of `text`. | |
| 1996 std::string foo2(const std::string& text); | |
| 1997 | |
| 1998 // Returns 4-byte string `0xf0 0x90 0x90 0xb7`, which decodes as | |
| 1999 // utf8 to a 4-byte utf16 character. | |
| 2000 const char* bar(); | |
| 2001 | |
| 2002 // Returns 4-byte string `0xf0 0x90 0x90 0xb7`, which decodes as | |
| 2003 // utf8 to a 4-byte utf16 character. | |
| 2004 std::string bar2(); | |
| 2005 | |
| 2006 %{{ | |
| 2007 // Returns string containing escaped description of `text`. | |
| 2008 std::string foo2(const std::string& text) | |
| 2009 {{ | |
| 2010 std::string ret; | |
| 2011 for (int i=0; i<text.size(); ++i) | |
| 2012 {{ | |
| 2013 char buffer[8]; | |
| 2014 snprintf(buffer, sizeof(buffer), " \\\\x%02x", (unsigned char) text[i]); | |
| 2015 ret += buffer; | |
| 2016 }} | |
| 2017 return ret; | |
| 2018 }} | |
| 2019 | |
| 2020 // Returns pointer to static buffer containing escaped | |
| 2021 // description of `text`. | |
| 2022 const char* foo1(const char* text) | |
| 2023 {{ | |
| 2024 std::string text2 = text; | |
| 2025 static std::string ret; | |
| 2026 ret = foo2(text2); | |
| 2027 return ret.c_str(); | |
| 2028 }} | |
| 2029 | |
| 2030 // Returns pointer to static buffer containing a utf8 string. | |
| 2031 const char* bar() | |
| 2032 {{ | |
| 2033 static char ret[] = | |
| 2034 {{ | |
| 2035 (char) 0xf0, | |
| 2036 (char) 0x90, | |
| 2037 (char) 0x90, | |
| 2038 (char) 0xb7, | |
| 2039 0, | |
| 2040 }}; | |
| 2041 return ret; | |
| 2042 }} | |
| 2043 | |
| 2044 // Returns a std::string containing a utf8 string. | |
| 2045 std::string bar2() | |
| 2046 {{ | |
| 2047 const char* ret = bar(); | |
| 2048 return std::string(ret); | |
| 2049 }} | |
| 2050 %}} | |
| 2051 ''') | |
| 2052 with open(f'{build_dir}/test.i', 'w') as f: | |
| 2053 f.write(test_i) | |
| 2054 | |
| 2055 # Run swig on `test.i` to generate `test.cs` and `test.cpp`. | |
| 2056 # | |
| 2057 jlib.system( | |
| 2058 f''' | |
| 2059 cd {build_dir} && swig | |
| 2060 {'-DSWIG_CSHARP_NO_STRING_HELPER=1 -DSWIG_CSHARP_NO_EXCEPTION_HELPER=1' if 0 and fix else ''} | |
| 2061 -D_WIN32 | |
| 2062 -c++ | |
| 2063 -csharp | |
| 2064 -Wextra | |
| 2065 -Wall | |
| 2066 -dllimport test.dll | |
| 2067 -outdir . | |
| 2068 -outfile test.cs | |
| 2069 -o test.cpp | |
| 2070 test.i | |
| 2071 ''') | |
| 2072 | |
| 2073 # Compile/link test.cpp to create test.dll. | |
| 2074 # | |
| 2075 if state.state_.windows: | |
| 2076 import wdev | |
| 2077 vs = wdev.WindowsVS() | |
| 2078 jlib.system( | |
| 2079 f''' | |
| 2080 cd {build_dir} && "{vs.vcvars}"&&"{vs.cl}" | |
| 2081 /nologo # | |
| 2082 /c # Compiles without linking. | |
| 2083 /EHsc # Enable "Standard C++ exception handling". | |
| 2084 /MD | |
| 2085 /Tptest.cpp # /Tp specifies C++ source file. | |
| 2086 /Fotest.cpp.obj # Output file. | |
| 2087 /permissive- # Set standard-conformance mode. | |
| 2088 /FC # Display full path of source code files passed to cl.exe in diagnostic text. | |
| 2089 /W3 # Sets which warning level to output. /W3 is IDE default. | |
| 2090 /diagnostics:caret # Controls the format of diagnostic messages. | |
| 2091 ''') | |
| 2092 | |
| 2093 jlib.system( | |
| 2094 f''' | |
| 2095 cd {build_dir} && "{vs.vcvars}"&&"{vs.link}" | |
| 2096 /nologo # | |
| 2097 /DLL | |
| 2098 /IMPLIB:test.lib # Overrides the default import library name. | |
| 2099 /OUT:test.dll # Specifies the output file name. | |
| 2100 /nologo | |
| 2101 test.cpp.obj | |
| 2102 ''') | |
| 2103 else: | |
| 2104 jlib.system( | |
| 2105 f''' | |
| 2106 cd {build_dir} && c++ | |
| 2107 -fPIC | |
| 2108 --shared | |
| 2109 -o test.dll | |
| 2110 test.cpp | |
| 2111 ''') | |
| 2112 | |
| 2113 # Create C# test programme `testfoo.cs`. | |
| 2114 # | |
| 2115 cs = textwrap.dedent(f''' | |
| 2116 public class HelloWorld | |
| 2117 {{ | |
| 2118 public static void Main(string[] args) | |
| 2119 {{ | |
| 2120 bool expect_fix = ({fix if state.state_.windows else 1} != 0); | |
| 2121 | |
| 2122 // Utf8 for our string with 4-byte utf16 character. | |
| 2123 // | |
| 2124 byte[] text_utf8 = {{ 0xf0, 0x90, 0x90, 0xb7, }}; | |
| 2125 string text = System.Text.Encoding.UTF8.GetString(text_utf8); | |
| 2126 | |
| 2127 // Escaped representation of text_utf8, as returned by | |
| 2128 // calls of test.foo1() and test.foo2() below. | |
| 2129 // | |
| 2130 string text_utf8_escaped = " \\\\xf0 \\\\x90 \\\\x90 \\\\xb7"; | |
| 2131 string incorrect_utf8_escaped = " \\\\x3f \\\\x3f"; | |
| 2132 | |
| 2133 // test.foo1()/test.foo2() return a `const | |
| 2134 // char*`/`std::string` containing an escaped | |
| 2135 // representation of the string that they were given. If | |
| 2136 // things are working correctly, this will be an escaped | |
| 2137 // representation of `text_utf8`. | |
| 2138 // | |
| 2139 | |
| 2140 string foo1 = test.foo1(text); | |
| 2141 System.Console.WriteLine("foo1: " + foo1); | |
| 2142 string foo_expected_escaped = (expect_fix) ? text_utf8_escaped : incorrect_utf8_escaped; | |
| 2143 if (foo1 != foo_expected_escaped) | |
| 2144 {{ | |
| 2145 throw new System.Exception( | |
| 2146 "foo1 incorrect: '" + foo1 + "'" | |
| 2147 + " - foo_expected_escaped: '" + foo_expected_escaped + "'" | |
| 2148 ); | |
| 2149 }} | |
| 2150 | |
| 2151 string foo2 = test.foo2(text); | |
| 2152 System.Console.WriteLine("foo2: " + foo2); | |
| 2153 if (foo2 != foo_expected_escaped) | |
| 2154 {{ | |
| 2155 throw new System.Exception( | |
| 2156 "foo2 incorrect: '" + foo2 + "'" | |
| 2157 + " - foo_expected_escaped: '" + foo_expected_escaped + "'" | |
| 2158 ); | |
| 2159 }} | |
| 2160 | |
| 2161 // test.bar1() and test.bar2() return a `const | |
| 2162 // char*`/`std::string` containing the bytes of | |
| 2163 // `text_utf8`. If things are working correctly we will see | |
| 2164 // exactly these bytes. | |
| 2165 // | |
| 2166 byte[] bar_expected_utf8_incorrect = {{ 0xc3, 0xb0, 0xc2, 0x90, 0xc2, 0x90, 0xc2, 0xb7, }}; | |
| 2167 byte[] bar_expected_utf8 = (expect_fix) ? text_utf8 : bar_expected_utf8_incorrect; | |
| 2168 | |
| 2169 string ret3 = test.bar(); | |
| 2170 byte[] ret3_utf8 = System.Text.Encoding.UTF8.GetBytes(ret3); | |
| 2171 print_bytes_as_string("ret3_utf8:", ret3_utf8); | |
| 2172 if (!equal(ret3_utf8, bar_expected_utf8)) | |
| 2173 {{ | |
| 2174 throw new System.Exception("ret3 != bar_expected_utf8"); | |
| 2175 }} | |
| 2176 | |
| 2177 string ret4 = test.bar2(); | |
| 2178 byte[] ret4_utf8 = System.Text.Encoding.UTF8.GetBytes(ret4); | |
| 2179 print_bytes_as_string("ret4_utf8:", ret4_utf8); | |
| 2180 if (!equal(ret4_utf8, bar_expected_utf8)) | |
| 2181 {{ | |
| 2182 throw new System.Exception("ret4_utf8 != bar_expected_utf8"); | |
| 2183 }} | |
| 2184 }} | |
| 2185 | |
| 2186 static bool equal(byte[] a, byte[] b) | |
| 2187 {{ | |
| 2188 if (a.Length != b.Length) return false; | |
| 2189 for (int i=0; i<a.Length; ++i) | |
| 2190 {{ | |
| 2191 if (a[i] != b[i]) return false; | |
| 2192 }} | |
| 2193 return true; | |
| 2194 }} | |
| 2195 | |
| 2196 static void print_bytes_as_string(string prefix, byte[] a) | |
| 2197 {{ | |
| 2198 System.Console.Write(prefix); | |
| 2199 System.Console.Write("["); | |
| 2200 foreach (var b in a) | |
| 2201 {{ | |
| 2202 System.Console.Write(" {{0:x2}}", b); | |
| 2203 }} | |
| 2204 System.Console.WriteLine("]"); | |
| 2205 }} | |
| 2206 }} | |
| 2207 ''') | |
| 2208 with open(f'{build_dir}/testfoo.cs', 'w') as f: | |
| 2209 f.write(cs) | |
| 2210 | |
| 2211 # Use `csc` to compile `testfoo.cs` and create `testfoo.exe`. | |
| 2212 # | |
| 2213 csc, mono, _ = csharp.csharp_settings(None) | |
| 2214 jlib.system(f'cd {build_dir} && "{csc}" -out:testfoo.exe testfoo.cs test.cs') | |
| 2215 | |
| 2216 # Run `testfoo.exe`. | |
| 2217 # | |
| 2218 jlib.system(f'cd {build_dir} && {mono} testfoo.exe') |
