swig.py 96 KB

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