comparison mupdf-source/scripts/wrap/cpp.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 Functions for generating source code for the C++ bindings.
3 '''
4
5 import io
6 import os
7 import pickle
8 import re
9 import textwrap
10
11 import jlib
12
13 from . import classes
14 from . import csharp
15 from . import parse
16 from . import python
17 from . import rename
18 from . import state
19 from . import util
20
21
22 def _make_top_level( text, top_level='::'):
23 if text == 'string':
24 # This is a hack; for some reason we often end up with `string` when it
25 # it should be `std::string`.
26 text = 'std::string'
27 initial_prefix = ['']
28 def handle_prefix( text, prefix):
29 if text.startswith( prefix):
30 initial_prefix[0] += prefix
31 return text[ len(prefix):]
32 return text
33 text = handle_prefix( text, 'const ')
34 text = handle_prefix( text, 'struct ')
35 if text.startswith( ('fz_', 'pdf_')):
36 text = f'{top_level}{text}'
37 text = f'{initial_prefix[0]}{text}'
38 return text
39
40
41 def declaration_text(
42 type_,
43 name,
44 nest=0,
45 name_is_simple=True,
46 verbose=False,
47 expand_typedef=True,
48 top_level='::',
49 ):
50 '''
51 Returns text for C++ declaration of <type_> called <name>.
52
53 type:
54 a clang.cindex.Type.
55 name:
56 name of type; can be empty.
57 nest:
58 for internal diagnostics.
59 name_is_simple:
60 true iff <name> is an identifier.
61
62 If name_is_simple is false, we surround <name> with (...) if type is a
63 function.
64 '''
65 # clang can give unhelpful spelling for anonymous structs.
66 assert 'struct (unnamed at ' not in type_.spelling, f'type_.spelling={type_.spelling}'
67 if verbose:
68 jlib.log( '{nest=} {name=} {type_.spelling=} {type_.get_declaration().get_usr()=}')
69 jlib.log( '{type_.kind=} {type_.get_array_size()=} {expand_typedef=}')
70
71 array_n = type_.get_array_size()
72 if verbose:
73 jlib.log( '{array_n=}')
74 if array_n >= 0 or type_.kind == state.clang.cindex.TypeKind.INCOMPLETEARRAY:
75 if verbose: jlib.log( '{array_n=}')
76 if array_n < 0:
77 array_n = ''
78 ret = declaration_text(
79 type_.get_array_element_type(),
80 f'{name}[{array_n}]',
81 nest+1,
82 name_is_simple,
83 verbose=verbose,
84 expand_typedef=expand_typedef,
85 top_level=top_level,
86 )
87 if verbose:
88 jlib.log( 'returning {ret=}')
89 return ret
90
91 pointee = type_.get_pointee()
92 if pointee and pointee.spelling:
93 if type_.kind == state.clang.cindex.TypeKind.LVALUEREFERENCE:
94 pointee_type = '&'
95 elif type_.kind == state.clang.cindex.TypeKind.POINTER:
96 pointee_type = '*'
97 else:
98 assert 0, f'Unrecognised pointer kind {type_.kind=}.'
99 if verbose: jlib.log( '{type_=} {type_.kind=} {pointee.spelling=}')
100 ret = declaration_text(
101 pointee,
102 f'{pointee_type}{name}',
103 nest+1,
104 name_is_simple=False,
105 verbose=verbose,
106 expand_typedef=expand_typedef,
107 top_level=top_level,
108 )
109 if verbose:
110 jlib.log( 'returning {ret=}')
111 return ret
112
113 if expand_typedef and type_.get_typedef_name():
114 if verbose: jlib.log( '{type_.get_typedef_name()=}')
115 const = 'const ' if type_.is_const_qualified() else ''
116 ret = f'{const}{_make_top_level(type_.get_typedef_name(), top_level)} {name}'
117 if verbose:
118 jlib.log( 'returning {ret=}')
119 return ret
120
121 # On MacOS type `size_t` returns true from get_result() and is
122 # state.clang.cindex.TypeKind.ELABORATED.
123 #
124 if ( type_.get_result().spelling
125 and type_.kind not in
126 (
127 state.clang.cindex.TypeKind.FUNCTIONNOPROTO,
128 state.clang.cindex.TypeKind.ELABORATED,
129 )
130 ):
131 # <type> is a function. We call ourselves with type=type_.get_result()
132 # and name=<name>(<args>).
133 #
134 assert type_.kind == state.clang.cindex.TypeKind.FUNCTIONPROTO, \
135 f'{type_.spelling=} {type_.kind=}'
136 ret = ''
137 sep = ''
138 for arg in type_.argument_types():
139 ret += sep
140 ret += declaration_text(
141 arg,
142 '',
143 nest+1,
144 top_level=top_level,
145 verbose=verbose,
146 expand_typedef=expand_typedef,
147 )
148 sep = ', '
149 if verbose: jlib.log( '{ret!r=}')
150 if not name_is_simple:
151 # If name isn't a simple identifier, put it inside braces, e.g.
152 # this crudely allows function pointers to work.
153 name = f'({name})'
154 ret = f'{name}({ret})'
155 if verbose: jlib.log( '{type_.get_result()=}')
156 ret = declaration_text(
157 type_.get_result(),
158 ret,
159 nest+1,
160 name_is_simple=False,
161 verbose=verbose,
162 expand_typedef=expand_typedef,
163 top_level=top_level,
164 )
165 if verbose:
166 jlib.log( 'returning {ret=}')
167 return ret
168
169 ret = f'{_make_top_level(type_.spelling, top_level)} {name}'
170 assert not 'struct (unnamed at ' in ret, f'Bad clang name for anonymous struct: {ret}'
171 if verbose: jlib.log( 'returning {ret=}')
172 return ret
173
174
175 def write_call_arg(
176 tu,
177 arg,
178 classname,
179 have_used_this,
180 out_cpp,
181 verbose=False,
182 python=False,
183 ):
184 '''
185 Write an arg of a function call, translating between raw and wrapping
186 classes as appropriate.
187
188 If the required type is a fz_ struct that we wrap, we assume that arg.name
189 is a reference to an instance of the wrapper class. If the wrapper class
190 is the same as <classname>, we use 'this->' instead of <name>. We also
191 generate slightly different code depending on whether the wrapper class is
192 pod or inline pod.
193
194 arg:
195 Arg from get_args().
196 classname:
197 Name of wrapper class available as 'this'.
198 have_used_this:
199 If true, we never use 'this->...'.
200 out_cpp:
201 .
202 python:
203 If true, we write python code, not C.
204
205 Returns True if we have used 'this->...', else return <have_used_this>.
206 '''
207 assert isinstance( arg, parse.Arg)
208 assert isinstance( arg.cursor, state.clang.cindex.Cursor)
209 if not arg.alt:
210 # Arg is a normal type; no conversion necessary.
211 if python:
212 out_cpp.write( arg.name_python)
213 else:
214 out_cpp.write( arg.name)
215 return have_used_this
216
217 if verbose:
218 jlib.log( '{=arg.name arg.alt.spelling classname}')
219 type_ = state.get_name_canonical( arg.cursor.type)
220 ptr = '*'
221 #log( '{=arg.name arg.alt.spelling classname type_.spelling}')
222 if type_.kind == state.clang.cindex.TypeKind.POINTER:
223 type_ = state.get_name_canonical( type_.get_pointee())
224 ptr = ''
225 #log( '{=arg.name arg.alt.spelling classname type_.spelling}')
226 extras = parse.get_fz_extras( tu, type_.spelling)
227 assert extras, f'No extras for type_.spelling={type_.spelling}'
228 if verbose:
229 jlib.log( 'param is fz: {type_.spelling=} {extras2.pod=}')
230 assert extras.pod != 'none' \
231 'Cannot pass wrapper for {type_.spelling} as arg because pod is "none" so we cannot recover struct.'
232 if python:
233 if extras.pod == 'inline':
234 out_cpp.write( f'{arg.name_python}.internal()')
235 elif extras.pod:
236 out_cpp.write( f'{arg.name_python}.m_internal')
237 else:
238 out_cpp.write( f'{arg.name_python}.m_internal')
239
240 elif extras.pod == 'inline':
241 # We use the address of the first class member, casting it to a pointer
242 # to the wrapped type. Not sure this is guaranteed safe, but should
243 # work in practise.
244 name_ = f'{arg.name}.'
245 if not have_used_this and rename.class_(arg.alt.type.spelling) == classname:
246 have_used_this = True
247 name_ = 'this->'
248 field0 = parse.get_field0(type_).spelling
249 out_cpp.write( f'{ptr} {name_}internal()')
250 else:
251 if verbose:
252 jlib.log( '{=arg state.get_name_canonical(arg.cursor.type).kind classname extras}')
253 if extras.pod and state.get_name_canonical( arg.cursor.type).kind == state.clang.cindex.TypeKind.POINTER:
254 out_cpp.write( '&')
255 elif not extras.pod and state.get_name_canonical( arg.cursor.type).kind != state.clang.cindex.TypeKind.POINTER:
256 out_cpp.write( '*')
257 elif arg.out_param:
258 out_cpp.write( '&')
259 if not have_used_this and rename.class_(arg.alt.type.spelling) == classname:
260 have_used_this = True
261 out_cpp.write( 'this->')
262 else:
263 out_cpp.write( f'{arg.name}.')
264 out_cpp.write( 'm_internal')
265
266 return have_used_this
267
268
269 def make_fncall( tu, cursor, return_type, fncall, out, refcheck_if, trace_if):
270 '''
271 Writes a low-level function call to <out>, using fz_context_s from
272 internal_context_get() and with fz_try...fz_catch that converts to C++
273 exceptions by calling throw_exception().
274
275 return_type:
276 Text return type of function, e.g. 'void' or 'double'.
277 fncall:
278 Text containing function call, e.g. 'function(a, b, 34)'.
279 out:
280 Stream to which we write generated code.
281 '''
282 uses_fz_context = False;
283
284 # Setting this to False is a hack to elide all fz_try/fz_catch code. This
285 # has a very small effect on mupdfpy test suite performance - e.g. reduce
286 # time from 548.1s to 543.2s.
287 #
288 use_fz_try = True
289
290 if cursor.spelling in (
291 'pdf_specifics',
292 ):
293 # This fn takes a fz_context* but never throws, so we can omit
294 # `fz_try()...fz_catch()`, which might give a small performance
295 # improvement.
296 use_fz_try = False
297 uses_fz_context = True
298 else:
299 for arg in parse.get_args( tu, cursor, include_fz_context=True):
300 if parse.is_pointer_to( arg.cursor.type, 'fz_context'):
301 uses_fz_context = True
302 break
303 if uses_fz_context:
304 context_get = rename.internal( 'context_get')
305 throw_exception = rename.internal( 'throw_exception')
306 out.write( f' fz_context* auto_ctx = {context_get}();\n')
307
308 # Output code that writes diagnostics to std::cerr if $MUPDF_trace is set.
309 #
310 def varname_enable():
311 for t in 'fz_keep_', 'fz_drop_', 'pdf_keep_', 'pdf_drop_':
312 if cursor.spelling.startswith( t):
313 return 's_trace_keepdrop'
314 return 's_trace > 1'
315
316 out.write( f' {trace_if}\n')
317 out.write( f' if ({varname_enable()}) {{\n')
318 out.write( f' std::cerr << __FILE__ << ":" << __LINE__ << ":" << __FUNCTION__ << "(): calling {cursor.spelling}():";\n')
319 for arg in parse.get_args( tu, cursor, include_fz_context=True):
320 if parse.is_pointer_to( arg.cursor.type, 'fz_context'):
321 out.write( f' if ({varname_enable()}) std::cerr << " auto_ctx=" << auto_ctx;\n')
322 elif arg.out_param:
323 out.write( f' if ({varname_enable()}) std::cerr << " {arg.name}=" << (void*) {arg.name};\n')
324 elif arg.alt:
325 # If not a pod, there will not be an operator<<, so just show
326 # the address of this arg.
327 #
328 extras = parse.get_fz_extras( tu, arg.alt.type.spelling)
329 assert extras.pod != 'none' \
330 'Cannot pass wrapper for {type_.spelling} as arg because pod is "none" so we cannot recover struct.'
331 if extras.pod:
332 out.write( f' std::cerr << " {arg.name}=" << {arg.name};\n')
333 elif arg.cursor.type.kind == state.clang.cindex.TypeKind.POINTER:
334 out.write( f' if ({varname_enable()}) std::cerr << " {arg.name}=" << {arg.name};\n')
335 elif arg.cursor.type.kind == state.clang.cindex.TypeKind.LVALUEREFERENCE:
336 out.write( f' if ({varname_enable()}) std::cerr << " &{arg.name}=" << &{arg.name};\n')
337 else:
338 out.write( f' std::cerr << " &{arg.name}=" << &{arg.name};\n')
339 elif parse.is_pointer_to(arg.cursor.type, 'char') and state.get_name_canonical( arg.cursor.type.get_pointee()).is_const_qualified():
340 # 'const char*' is assumed to be zero-terminated string. But we
341 # need to protect against trying to write nullptr because this
342 # appears to kill std::cerr on Linux.
343 out.write( f' if ({arg.name}) std::cerr << " {arg.name}=\'" << {arg.name} << "\'";\n')
344 out.write( f' else std::cerr << " {arg.name}:null";\n')
345 elif parse.is_( arg.cursor.type, 'va_list'):
346 out.write( f' std::cerr << " {arg.name}:va_list";\n')
347 elif (0
348 or parse.is_( arg.cursor.type, 'signed char')
349 or parse.is_( arg.cursor.type, 'unsigned char')
350 ):
351 # Typically used for raw data, so not safe to treat as text.
352 out.write( f' std::cerr << " {arg.name}=" << ((int) {arg.name});\n')
353 elif (0
354 or parse.is_pointer_to(arg.cursor.type, 'signed char')
355 or parse.is_pointer_to(arg.cursor.type, 'unsigned char')
356 ):
357 # Typically used for raw data, so not safe to treat as text.
358 out.write( f' std::cerr << " {arg.name}=" << ((void*) {arg.name});\n')
359 elif arg.cursor.type.kind == state.clang.cindex.TypeKind.POINTER:
360 # Don't assume non-const 'char*' is a zero-terminated string.
361 out.write( f' if ({varname_enable()}) std::cerr << " {arg.name}=" << (void*) {arg.name};\n')
362 elif arg.cursor.type.kind == state.clang.cindex.TypeKind.LVALUEREFERENCE:
363 out.write( f' if ({varname_enable()}) std::cerr << " &{arg.name}=" << &{arg.name};\n')
364 else:
365 out.write( f' std::cerr << " {arg.name}=" << {arg.name};\n')
366 out.write( f' std::cerr << "\\n";\n')
367 out.write( f' }}\n')
368 out.write( f' #endif\n')
369
370 if uses_fz_context:
371 out.write( f' {refcheck_if}\n')
372 out.write( f' long stack0;\n')
373 out.write( f' if (s_check_error_stack)\n')
374 out.write( f' {{\n')
375 out.write( f' stack0 = auto_ctx->error.top - auto_ctx->error.stack_base;\n')
376 out.write( f' }}\n')
377 out.write( f' #endif\n')
378
379 # Now output the function call.
380 #
381 if return_type != 'void':
382 out.write( f' {return_type} ret;\n')
383
384 if cursor.spelling == 'fz_warn':
385 out.write( ' va_list ap;\n')
386 out.write( ' fz_var(ap);\n')
387
388 indent = ''
389 if uses_fz_context and use_fz_try:
390 out.write( f' fz_try(auto_ctx) {{\n')
391 indent = ' '
392
393 if cursor.spelling == 'fz_warn':
394 out.write( f' {indent}va_start(ap, fmt);\n')
395 out.write( f' {indent}fz_vwarn(auto_ctx, fmt, ap);\n')
396 else:
397 if not uses_fz_context:
398 out.write( f' /* No fz_context* arg, so no need for fz_try()/fz_catch() to convert MuPDF exceptions into C++ exceptions. */\n')
399 out.write( f' {indent}')
400 if return_type != 'void':
401 out.write( f'ret = ')
402 out.write( f'{fncall};\n')
403
404 if uses_fz_context and use_fz_try:
405 out.write( f' }}\n')
406
407 if cursor.spelling == 'fz_warn':
408 if use_fz_try:
409 out.write( f' fz_always(auto_ctx) {{\n')
410 out.write( f' va_end(ap);\n')
411 out.write( f' }}\n')
412 else:
413 out.write( f' va_end(ap);\n')
414
415 if uses_fz_context and use_fz_try:
416 out.write( f' fz_catch(auto_ctx) {{\n')
417 out.write( f' {trace_if}\n')
418 out.write( f' if (s_trace_exceptions) {{\n')
419 out.write( f' std::cerr << __FILE__ << ":" << __LINE__ << ":" << __FUNCTION__ << "(): fz_catch() has caught exception.\\n";\n')
420 out.write( f' }}\n')
421 out.write( f' #endif\n')
422 out.write( f' {throw_exception}(auto_ctx);\n')
423 out.write( f' }}\n')
424
425 if uses_fz_context:
426 out.write( f' {refcheck_if}\n')
427 out.write( f' if (s_check_error_stack)\n')
428 out.write( f' {{\n')
429 out.write( f' long stack1 = auto_ctx->error.top - auto_ctx->error.stack_base;\n')
430 out.write( f' if (stack1 != stack0)\n')
431 out.write( f' std::cerr << __FILE__ << ":" << __LINE__ << ":" << __FUNCTION__ << "(): MuPDF error stack size changed by {cursor.spelling}(): " << stack0 << " -> " << stack1 << "\\n";\n')
432 out.write( f' }}\n')
433 out.write( f' #endif\n')
434
435 if return_type != 'void':
436 out.write( f' return ret;\n')
437
438
439 def to_pickle( obj, path):
440 '''
441 Pickles <obj> to file <path>.
442 '''
443 with open( path, 'wb') as f:
444 pickle.dump( obj, f)
445
446 def from_pickle( path):
447 '''
448 Returns contents of file <path> unpickled.
449 '''
450 with open( path, 'rb') as f:
451 return pickle.load( f)
452
453 class Generated:
454 '''
455 Stores information generated when we parse headers using clang.
456 '''
457 def __init__( self):
458 self.h_files = []
459 self.cpp_files = []
460 self.fn_usage_filename = None
461 self.container_classnames = []
462 self.to_string_structnames = []
463 self.fn_usage = dict()
464 self.output_param_fns = []
465 self.c_functions = []
466 self.c_globals = []
467 self.c_enums = []
468 self.c_structs = []
469 self.swig_cpp = io.StringIO()
470 self.swig_cpp_python = io.StringIO()
471 self.swig_python = io.StringIO()
472 self.swig_python_exceptions = io.StringIO()
473 self.swig_python_set_error_classes = io.StringIO()
474 self.swig_csharp = io.StringIO()
475 self.virtual_fnptrs = [] # List of extra wrapper class names with virtual fnptrs.
476 self.cppyy_extra = ''
477
478 def save( self, dirpath):
479 '''
480 Saves state to .pickle file, to be loaded later via pickle.load().
481 '''
482 to_pickle( self, f'{dirpath}/generated.pickle')
483
484
485 def make_outparam_helper(
486 tu,
487 cursor,
488 fnname,
489 fnname_wrapper,
490 generated,
491 ):
492 '''
493 Create extra C++, Python and C# code to make tuple-returning wrapper of
494 specified function.
495
496 We write Python code to generated.swig_python and C++ code to
497 generated.swig_cpp.
498 '''
499 verbose = False
500 main_name = rename.ll_fn(cursor.spelling)
501 generated.swig_cpp.write( '\n')
502
503 # Write struct.
504 generated.swig_cpp.write( 'namespace mupdf\n')
505 generated.swig_cpp.write('{\n')
506 generated.swig_cpp.write(f' /* Out-params helper class for {cursor.spelling}(). */\n')
507 generated.swig_cpp.write(f' struct {main_name}_outparams\n')
508 generated.swig_cpp.write(f' {{\n')
509 for arg in parse.get_args( tu, cursor):
510 if not arg.out_param:
511 continue
512 decl = declaration_text( arg.cursor.type, arg.name, verbose=verbose)
513 if verbose:
514 jlib.log( '{decl=}')
515 assert arg.cursor.type.kind == state.clang.cindex.TypeKind.POINTER
516
517 # We use state.get_name_canonical() here because, for example, it
518 # converts int64_t to 'long long', which seems to be handled better by
519 # swig - swig maps int64_t to mupdf.SWIGTYPE_p_int64_t which can't be
520 # treated or converted to an integer.
521 #
522 # We also value-initialise in case the underlying mupdf function also
523 # reads the supplied value - i.e. treats it as an in-parm as well as an
524 # out-param; this is particularly important for pointer out-params.
525 #
526 pointee = state.get_name_canonical( arg.cursor.type.get_pointee())
527 generated.swig_cpp.write(f' {declaration_text( pointee, arg.name)} = {{}};\n')
528 generated.swig_cpp.write(f' }};\n')
529 generated.swig_cpp.write('\n')
530
531 # Write function definition.
532 name_args = f'{main_name}_outparams_fn('
533 sep = ''
534 for arg in parse.get_args( tu, cursor):
535 if arg.out_param:
536 continue
537 name_args += sep
538 name_args += declaration_text( arg.cursor.type, arg.name, verbose=verbose)
539 sep = ', '
540 name_args += f'{sep}{main_name}_outparams* outparams'
541 name_args += ')'
542 generated.swig_cpp.write(f' /* Out-params function for {cursor.spelling}(). */\n')
543 generated.swig_cpp.write(f' {declaration_text( cursor.result_type, name_args)}\n')
544 generated.swig_cpp.write( ' {\n')
545 return_void = (cursor.result_type.spelling == 'void')
546 generated.swig_cpp.write(f' ')
547 if not return_void:
548 generated.swig_cpp.write(f'{declaration_text(cursor.result_type, "ret")} = ')
549 generated.swig_cpp.write(f'{rename.ll_fn(cursor.spelling)}(')
550 sep = ''
551 for arg in parse.get_args( tu, cursor):
552 generated.swig_cpp.write(sep)
553 if arg.out_param:
554 generated.swig_cpp.write(f'&outparams->{arg.name}')
555 else:
556 generated.swig_cpp.write(f'{arg.name}')
557 sep = ', '
558 generated.swig_cpp.write(');\n')
559 if not return_void:
560 generated.swig_cpp.write(' return ret;\n')
561 generated.swig_cpp.write(' }\n')
562 generated.swig_cpp.write('}\n')
563
564 # Write Python wrapper.
565 python.make_outparam_helper_python(tu, cursor, fnname, fnname_wrapper, generated, main_name)
566
567 # Write C# wrapper.
568 csharp.make_outparam_helper_csharp(tu, cursor, fnname, fnname_wrapper, generated, main_name)
569
570
571 def make_python_class_method_outparam_override(
572 tu,
573 cursor,
574 fnname,
575 generated,
576 structname,
577 classname,
578 return_type,
579 ):
580 '''
581 Writes Python code to `generated.swig_python` that monkey-patches Python
582 function or method to make it call the underlying MuPDF function's Python
583 wrapper, which will return out-params in a tuple.
584
585 This is necessary because C++ doesn't support out-params so the C++ API
586 supports wrapper class out-params by taking references to a dummy wrapper
587 class instances, whose m_internal is then changed to point to the out-param
588 struct (with suitable calls to keep/drop to manage the destruction of the
589 dummy instance).
590
591 In Python, we could create dummy wrapper class instances (e.g. passing
592 nullptr to constructor) and return them, but instead we make our own call
593 to the underlying MuPDF function and wrap the out-params into wrapper
594 classes.
595 '''
596 out = generated.swig_python
597 # Underlying fn.
598 main_name = rename.ll_fn(cursor.spelling)
599
600 if structname:
601 name_new = f'{classname}_{rename.method(structname, cursor.spelling)}_outparams_fn'
602 else:
603 name_new = f'{rename.fn(cursor.spelling)}_outparams_fn'
604
605 # Define an internal Python function that will become the class method.
606 #
607 out.write( f'def {name_new}(')
608 if structname:
609 out.write( ' self')
610 comma = ', '
611 else:
612 comma = ''
613 for arg in parse.get_args( tu, cursor):
614 if arg.out_param:
615 continue
616 if structname and parse.is_pointer_to( arg.cursor.type, structname):
617 continue
618 out.write(f'{comma}{arg.name_python}')
619 comma = ', '
620 out.write('):\n')
621 out.write( ' """\n')
622 if structname:
623 out.write(f' Helper for out-params of class method {structname}::{main_name}() [{cursor.spelling}()].\n')
624 else:
625 out.write(f' Class-aware helper for out-params of {fnname}() [{cursor.spelling}()].\n')
626 out.write( ' """\n')
627
628 # ret, a, b, ... = foo::bar(self.m_internal, p, q, r, ...)
629 out.write(f' ')
630 sep = ''
631 if cursor.result_type.spelling != 'void':
632 out.write( 'ret')
633 sep = ', '
634 for arg in parse.get_args( tu, cursor):
635 if not arg.out_param:
636 continue
637 out.write( f'{sep}{arg.name_python}')
638 sep = ', '
639 out.write( f' = {main_name}(')
640 sep = ''
641 if structname:
642 out.write( f' self.m_internal')
643 sep = ', '
644 for arg in parse.get_args( tu, cursor):
645 if arg.out_param:
646 continue
647 if structname and parse.is_pointer_to( arg.cursor.type, structname):
648 continue
649 out.write( sep)
650 write_call_arg( tu, arg, classname, have_used_this=False, out_cpp=out, python=True)
651 sep = ', '
652 out.write( ')\n')
653
654 # return ret, a, b.
655 #
656 # We convert returned items to wrapper classes if they are MuPDF types.
657 #
658 out.write( ' return ')
659 sep = ''
660 if cursor.result_type.spelling != 'void':
661 if return_type:
662 #out.write( f'{return_type}(ret)')
663 # Return type is a class wrapper.
664 return_ll_type = cursor.result_type
665 do_keep = False
666 if cursor.result_type.kind == state.clang.cindex.TypeKind.POINTER:
667 return_ll_type = return_ll_type.get_pointee()
668 if parse.has_refs( tu, return_ll_type):
669 return_ll_type = return_ll_type.spelling
670 return_ll_type = util.clip( return_ll_type, ('struct ', 'const '))
671 assert return_ll_type.startswith( ( 'fz_', 'pdf_'))
672 for prefix in ( 'fz_', 'pdf_'):
673 if return_ll_type.startswith( prefix):
674 break
675 else:
676 assert 0, f'Unexpected arg type: {return_ll_type}'
677 return_extra = classes.classextras.get( tu, return_ll_type)
678 if not function_name_implies_kept_references( fnname):
679 do_keep = True
680 else:
681 if 'char' in return_ll_type.spelling:
682 jlib.log('### Function returns {cursor.result_type.spelling=} -> {return_ll_type.spelling=}: {fnname}. {function_name_implies_kept_references(fnname)=}')
683 if do_keep:
684 keepfn = f'{prefix}keep_{return_ll_type[ len(prefix):]}'
685 keepfn = rename.ll_fn( keepfn)
686 out.write( f'{return_type}( {keepfn}( ret))')
687 else:
688 out.write( f'{return_type}(ret)')
689 else:
690 out.write( 'ret')
691 sep = ', '
692 for arg in parse.get_args( tu, cursor):
693 if not arg.out_param:
694 continue
695 if arg.alt:
696 name = util.clip( arg.alt.type.spelling, ('struct ', 'const '))
697 for prefix in ( 'fz_', 'pdf_'):
698 if name.startswith( prefix):
699 break
700 else:
701 assert 0, f'Unexpected arg type: {name}'
702 if function_name_implies_kept_references( fnname):
703 out.write( f'{sep}{rename.class_(name)}( {arg.name_python})')
704 else:
705 keepfn = f'{prefix}keep_{name[ len(prefix):]}'
706 keepfn = rename.ll_fn( keepfn)
707 out.write( f'{sep}{rename.class_(name)}({keepfn}( {arg.name_python}))')
708 else:
709 out.write( f'{sep}{arg.name_python}')
710 sep = ', '
711 out.write('\n')
712 out.write('\n')
713
714 # foo.bar = foo_bar_outparams_fn
715 if structname:
716 out.write(f'{classname}.{rename.method(structname, cursor.spelling)} = {name_new}\n')
717 else:
718 out.write(f'{rename.fn( cursor.spelling)} = {name_new}\n')
719 out.write('\n')
720 out.write('\n')
721
722
723 def make_wrapper_comment(
724 tu,
725 cursor,
726 fnname,
727 fnname_wrapper,
728 indent,
729 is_method,
730 is_low_level,
731 ):
732 ret = io.StringIO()
733 def write(text):
734 text = text.replace('\n', f'\n{indent}')
735 ret.write( text)
736
737 num_out_params = 0
738 for arg in parse.get_args(
739 tu,
740 cursor,
741 include_fz_context=False,
742 skip_first_alt=is_method,
743 ):
744 if arg.out_param:
745 num_out_params += 1
746
747 if is_low_level:
748 write( f'Low-level wrapper for `{rename.c_fn(cursor.spelling)}()`.')
749 else:
750 write( f'Class-aware wrapper for `{rename.c_fn(cursor.spelling)}()`.')
751 if num_out_params:
752 tuple_size = num_out_params
753 if cursor.result_type.spelling != 'void':
754 tuple_size += 1
755 write( f'\n')
756 write( f'\n')
757 write( f'This {"method" if is_method else "function"} has out-params. Python/C# wrappers look like:\n')
758 write( f' `{fnname_wrapper}(')
759 sep = ''
760 for arg in parse.get_args( tu, cursor, include_fz_context=False, skip_first_alt=is_method):
761 if arg.alt or not arg.out_param:
762 write( f'{sep}{declaration_text( arg.cursor.type, arg.name)}')
763 sep = ', '
764 write(')` => ')
765 if tuple_size > 1:
766 write( '`(')
767 sep = ''
768 if cursor.result_type.spelling != 'void':
769 write( f'{cursor.result_type.spelling}')
770 sep = ', '
771 for arg in parse.get_args( tu, cursor, include_fz_context=False, skip_first_alt=is_method):
772 if not arg.alt and arg.out_param:
773 write( f'{sep}{declaration_text( arg.cursor.type.get_pointee(), arg.name)}')
774 sep = ', '
775 if tuple_size > 1:
776 write( ')`')
777 write( f'\n')
778 else:
779 write( ' ')
780
781 return ret.getvalue()
782
783
784 def function_wrapper(
785 tu,
786 cursor,
787 fnname,
788 fnname_wrapper,
789 out_h,
790 out_cpp,
791 generated,
792 refcheck_if,
793 trace_if,
794 ):
795 '''
796 Writes low-level C++ wrapper fn, converting any fz_try..fz_catch exception
797 into a C++ exception.
798
799 cursor:
800 Clang cursor for function to wrap.
801 fnname:
802 Name of wrapped function.
803 fnname_wrapper:
804 Name of function to create.
805 out_h:
806 Stream to which we write header output.
807 out_cpp:
808 Stream to which we write cpp output.
809 generated:
810 A Generated instance.
811 refcheck_if:
812 A '#if*' statement that determines whether extra checks are compiled
813 in.
814 trace_if:
815 A '#if*' statement that determines whether runtime diagnostics are
816 compiled in.
817
818 Example generated function:
819
820 fz_band_writer * mupdf_new_band_writer_of_size(fz_context *ctx, size_t size, fz_output *out)
821 {
822 fz_band_writer * ret;
823 fz_try(ctx) {
824 ret = fz_new_band_writer_of_size(ctx, size, out);
825 }
826 fz_catch(ctx) {
827 mupdf_throw_exception(ctx);
828 }
829 return ret;
830 }
831 '''
832 assert cursor.kind == state.clang.cindex.CursorKind.FUNCTION_DECL
833 if cursor.type.is_function_variadic() and fnname != 'fz_warn':
834 jlib.log( 'Not writing low-level wrapper because variadic: {fnname=}', 1)
835 return
836
837 verbose = state.state_.show_details( fnname)
838 if verbose:
839 jlib.log( 'Wrapping {fnname}')
840 num_out_params = 0
841 for arg in parse.get_args( tu, cursor, include_fz_context=True):
842 if parse.is_pointer_to(arg.cursor.type, 'fz_context'):
843 continue
844 if arg.out_param:
845 num_out_params += 1
846
847 # Write first line: <result_type> <fnname_wrapper> (<args>...)
848 #
849 comment = make_wrapper_comment( tu, cursor, fnname, fnname_wrapper, indent='', is_method=False, is_low_level=True)
850 comment = f'/** {comment}*/\n'
851 for out in out_h, out_cpp:
852 out.write( comment)
853
854 # Copy any comment into .h file before declaration.
855 if cursor.raw_comment:
856 # On Windows, carriage returns can appear in cursor.raw_comment on
857 # due to line ending inconsistencies in our generated extra.cpp and
858 # extra.h, and can cause spurious differences in our generated C++
859 # code, which in turn causes unnecessary rebuilds.
860 #
861 # It would probably better to fix line endings in our generation of
862 # extra.*.
863 raw_comment = cursor.raw_comment.replace('\r', '')
864 out_h.write(raw_comment)
865 if not raw_comment.endswith( '\n'):
866 out_h.write( '\n')
867
868 # Write declaration and definition.
869 name_args_h = f'{fnname_wrapper}('
870 name_args_cpp = f'{fnname_wrapper}('
871 comma = ''
872 for arg in parse.get_args( tu, cursor, include_fz_context=True):
873 if verbose:
874 jlib.log( '{arg.cursor=} {arg.name=} {arg.separator=} {arg.alt=} {arg.out_param=}')
875 if parse.is_pointer_to(arg.cursor.type, 'fz_context'):
876 continue
877 decl = declaration_text( arg.cursor.type, arg.name, verbose=verbose)
878 if verbose:
879 jlib.log( '{decl=}')
880 name_args_h += f'{comma}{decl}'
881 decl = declaration_text( arg.cursor.type, arg.name)
882 name_args_cpp += f'{comma}{decl}'
883 comma = ', '
884
885 if cursor.type.is_function_variadic():
886 name_args_h += f'{comma}...'
887 name_args_cpp += f'{comma}...'
888
889 name_args_h += ')'
890 name_args_cpp += ')'
891 declaration_h = declaration_text( cursor.result_type, name_args_h, verbose=verbose)
892 declaration_cpp = declaration_text( cursor.result_type, name_args_cpp, verbose=verbose)
893 out_h.write( f'FZ_FUNCTION {declaration_h};\n')
894 out_h.write( '\n')
895
896 # Write function definition.
897 #
898 out_cpp.write( f'FZ_FUNCTION {declaration_cpp}\n')
899 out_cpp.write( '{\n')
900 return_type = cursor.result_type.spelling
901 fncall = ''
902 fncall += f'{rename.c_fn(cursor.spelling)}('
903 for arg in parse.get_args( tu, cursor, include_fz_context=True):
904 if parse.is_pointer_to( arg.cursor.type, 'fz_context'):
905 fncall += f'{arg.separator}auto_ctx'
906 else:
907 fncall += f'{arg.separator}{arg.name}'
908 fncall += ')'
909 make_fncall( tu, cursor, return_type, fncall, out_cpp, refcheck_if, trace_if)
910 out_cpp.write( '}\n')
911 out_cpp.write( '\n')
912
913 if num_out_params:
914 make_outparam_helper(
915 tu,
916 cursor,
917 fnname,
918 fnname_wrapper,
919 generated,
920 )
921
922
923 def make_namespace_open( namespace, out):
924 if namespace:
925 out.write( '\n')
926 out.write( f'namespace {namespace}\n')
927 out.write( '{\n')
928
929
930 def make_namespace_close( namespace, out):
931 if namespace:
932 out.write( '\n')
933 out.write( f'}} /* End of namespace {namespace}. */\n')
934
935
936 # libclang can't always find headers so we define our own `std::string`
937 # and `std::vector<>` that work well enough for the generation of the
938 # C++ API.
939 #
940 # We also define extra raw functions to aid SWIG-generated code. These
941 # are implemented in C++, and should be excluded from the generated
942 # windows_def file later on, otherwise we get link errors on Windows.
943 #
944 g_extra_declarations = textwrap.dedent(f'''
945
946 #ifdef MUPDF_WRAP_LIBCLANG
947
948 namespace std
949 {{
950 template<typename T>
951 struct vector
952 {{
953 }};
954
955 struct string
956 {{
957 }};
958 }}
959
960 #else
961
962 #include <string>
963 #include <vector>
964
965 #endif
966
967 #include "mupdf/fitz.h"
968 #include "mupdf/pdf.h"
969
970 /**
971 C++ alternative to `fz_lookup_metadata()` that returns a `std::string`
972 or calls `fz_throw()` if not found.
973 */
974 FZ_FUNCTION std::string fz_lookup_metadata2(fz_context* ctx, fz_document* doc, const char* key);
975
976 /**
977 C++ alternative to `pdf_lookup_metadata()` that returns a `std::string`
978 or calls `fz_throw()` if not found.
979 */
980 FZ_FUNCTION std::string pdf_lookup_metadata2(fz_context* ctx, pdf_document* doc, const char* key);
981
982 /**
983 C++ alternative to `fz_md5_pixmap()` that returns the digest by value.
984 */
985 FZ_FUNCTION std::vector<unsigned char> fz_md5_pixmap2(fz_context* ctx, fz_pixmap* pixmap);
986
987 /**
988 C++ alternative to fz_md5_final() that returns the digest by value.
989 */
990 FZ_FUNCTION std::vector<unsigned char> fz_md5_final2(fz_md5* md5);
991
992 /** */
993 FZ_FUNCTION long long fz_pixmap_samples_int(fz_context* ctx, fz_pixmap* pixmap);
994
995 /**
996 Provides simple (but slow) access to pixmap data from Python and C#.
997 */
998 FZ_FUNCTION int fz_samples_get(fz_pixmap* pixmap, int offset);
999
1000 /**
1001 Provides simple (but slow) write access to pixmap data from Python and
1002 C#.
1003 */
1004 FZ_FUNCTION void fz_samples_set(fz_pixmap* pixmap, int offset, int value);
1005
1006 /**
1007 C++ alternative to fz_highlight_selection() that returns quads in a
1008 std::vector.
1009 */
1010 FZ_FUNCTION std::vector<fz_quad> fz_highlight_selection2(fz_context* ctx, fz_stext_page* page, fz_point a, fz_point b, int max_quads);
1011
1012 struct fz_search_page2_hit
1013 {{
1014 fz_quad quad;
1015 int mark;
1016 }};
1017
1018 /**
1019 C++ alternative to fz_search_page() that returns information in a std::vector.
1020 */
1021 FZ_FUNCTION std::vector<fz_search_page2_hit> fz_search_page2(fz_context* ctx, fz_document* doc, int number, const char* needle, int hit_max);
1022
1023 /**
1024 C++ alternative to fz_string_from_text_language() that returns information in a std::string.
1025 */
1026 FZ_FUNCTION std::string fz_string_from_text_language2(fz_text_language lang);
1027
1028 /**
1029 C++ alternative to fz_get_glyph_name() that returns information in a std::string.
1030 */
1031 FZ_FUNCTION std::string fz_get_glyph_name2(fz_context* ctx, fz_font* font, int glyph);
1032
1033 /**
1034 Extra struct containing fz_install_load_system_font_funcs()'s args,
1035 which we wrap with virtual_fnptrs set to allow use from Python/C# via
1036 Swig Directors.
1037 */
1038 typedef struct fz_install_load_system_font_funcs_args
1039 {{
1040 fz_load_system_font_fn* f;
1041 fz_load_system_cjk_font_fn* f_cjk;
1042 fz_load_system_fallback_font_fn* f_fallback;
1043 }} fz_install_load_system_font_funcs_args;
1044
1045 /**
1046 Alternative to fz_install_load_system_font_funcs() that takes args in a
1047 struct, to allow use from Python/C# via Swig Directors.
1048 */
1049 FZ_FUNCTION void fz_install_load_system_font_funcs2(fz_context* ctx, fz_install_load_system_font_funcs_args* args);
1050
1051 /** Internal singleton state to allow Swig Director class to find
1052 fz_install_load_system_font_funcs_args class wrapper instance. */
1053 FZ_DATA extern void* fz_install_load_system_font_funcs2_state;
1054
1055 /** Helper for calling `fz_document_handler::open` function pointer via
1056 Swig from Python/C#. */
1057 FZ_FUNCTION fz_document* fz_document_handler_open(fz_context* ctx, const fz_document_handler *handler, fz_stream* stream, fz_stream* accel, fz_archive* dir, void* recognize_state);
1058
1059 /** Helper for calling a `fz_document_handler::recognize` function
1060 pointer via Swig from Python/C#. */
1061 FZ_FUNCTION int fz_document_handler_recognize(fz_context* ctx, const fz_document_handler *handler, const char *magic);
1062
1063 /** Swig-friendly wrapper for pdf_choice_widget_options(), returns the
1064 options directly in a vector. */
1065 FZ_FUNCTION std::vector<std::string> pdf_choice_widget_options2(fz_context* ctx, pdf_annot* tw, int exportval);
1066
1067 /** Swig-friendly wrapper for fz_new_image_from_compressed_buffer(),
1068 uses specified `decode` and `colorkey` if they are not null (in which
1069 case we assert that they have size `2*fz_colorspace_n(colorspace)`). */
1070 FZ_FUNCTION fz_image* fz_new_image_from_compressed_buffer2(
1071 fz_context* ctx,
1072 int w,
1073 int h,
1074 int bpc,
1075 fz_colorspace* colorspace,
1076 int xres,
1077 int yres,
1078 int interpolate,
1079 int imagemask,
1080 const std::vector<float>& decode,
1081 const std::vector<int>& colorkey,
1082 fz_compressed_buffer* buffer,
1083 fz_image* mask
1084 );
1085
1086 /** Swig-friendly wrapper for pdf_rearrange_pages(). */
1087 void pdf_rearrange_pages2(
1088 fz_context* ctx,
1089 pdf_document* doc,
1090 const std::vector<int>& pages,
1091 pdf_clean_options_structure structure
1092 );
1093
1094 /** Swig-friendly wrapper for pdf_subset_fonts(). */
1095 void pdf_subset_fonts2(fz_context *ctx, pdf_document *doc, const std::vector<int>& pages);
1096
1097 /** Swig-friendly and typesafe way to do fz_snprintf(fmt, value). `fmt`
1098 must end with one of 'efg' otherwise we throw an exception. */
1099 std::string fz_format_double(fz_context* ctx, const char* fmt, double value);
1100
1101 struct fz_font_ucs_gid
1102 {{
1103 unsigned long ucs;
1104 unsigned int gid;
1105 }};
1106
1107 /** SWIG-friendly wrapper for fz_enumerate_font_cmap(). */
1108 std::vector<fz_font_ucs_gid> fz_enumerate_font_cmap2(fz_context* ctx, fz_font* font);
1109
1110 /** SWIG-friendly wrapper for pdf_set_annot_callout_line(). */
1111 void pdf_set_annot_callout_line2(fz_context *ctx, pdf_annot *annot, std::vector<fz_point>& callout);
1112
1113 /** SWIG-friendly wrapper for fz_decode_barcode_from_display_list(),
1114 avoiding leak of the returned string. */
1115 std::string fz_decode_barcode_from_display_list2(fz_context *ctx, fz_barcode_type *type, fz_display_list *list, fz_rect subarea, int rotate);
1116
1117 /** SWIG-friendly wrapper for fz_decode_barcode_from_pixmap(), avoiding
1118 leak of the returned string. */
1119 std::string fz_decode_barcode_from_pixmap2(fz_context *ctx, fz_barcode_type *type, fz_pixmap *pix, int rotate);
1120
1121 /** SWIG-friendly wrapper for fz_decode_barcode_from_page(), avoiding
1122 leak of the returned string. */
1123 std::string fz_decode_barcode_from_page2(fz_context *ctx, fz_barcode_type *type, fz_page *page, fz_rect subarea, int rotate);
1124 ''')
1125
1126 g_extra_definitions = textwrap.dedent(f'''
1127
1128 FZ_FUNCTION std::string fz_lookup_metadata2( fz_context* ctx, fz_document* doc, const char* key)
1129 {{
1130 /* Find length first. */
1131 int e = fz_lookup_metadata(ctx, doc, key, NULL /*buf*/, 0 /*size*/);
1132 if (e < 0)
1133 {{
1134 fz_throw(ctx, FZ_ERROR_GENERIC, "key not found: %s", key);
1135 }}
1136 assert(e != 0);
1137 char* buf = (char*) fz_malloc(ctx, e);
1138 int e2 = fz_lookup_metadata(ctx, doc, key, buf, e);
1139 assert(e2 = e);
1140 std::string ret = buf;
1141 free(buf);
1142 return ret;
1143 }}
1144
1145 FZ_FUNCTION std::string pdf_lookup_metadata2( fz_context* ctx, pdf_document* doc, const char* key)
1146 {{
1147 /* Find length first. */
1148 int e = pdf_lookup_metadata(ctx, doc, key, NULL /*buf*/, 0 /*size*/);
1149 if (e < 0)
1150 {{
1151 fz_throw(ctx, FZ_ERROR_GENERIC, "key not found: %s", key);
1152 }}
1153 assert(e != 0);
1154 char* buf = (char*) fz_malloc(ctx, e);
1155 int e2 = pdf_lookup_metadata(ctx, doc, key, buf, e);
1156 assert(e2 = e);
1157 std::string ret = buf;
1158 free(buf);
1159 return ret;
1160 }}
1161
1162 FZ_FUNCTION std::vector<unsigned char> fz_md5_pixmap2(fz_context* ctx, fz_pixmap* pixmap)
1163 {{
1164 std::vector<unsigned char> ret(16);
1165 fz_md5_pixmap( ctx, pixmap, &ret[0]);
1166 return ret;
1167 }}
1168
1169 FZ_FUNCTION long long fz_pixmap_samples_int(fz_context* ctx, fz_pixmap* pixmap)
1170 {{
1171 long long ret = (intptr_t) pixmap->samples;
1172 return ret;
1173 }}
1174
1175 FZ_FUNCTION int fz_samples_get(fz_pixmap* pixmap, int offset)
1176 {{
1177 return pixmap->samples[offset];
1178 }}
1179
1180 FZ_FUNCTION void fz_samples_set(fz_pixmap* pixmap, int offset, int value)
1181 {{
1182 pixmap->samples[offset] = value;
1183 }}
1184
1185 FZ_FUNCTION std::vector<unsigned char> fz_md5_final2(fz_md5* md5)
1186 {{
1187 std::vector<unsigned char> ret(16);
1188 fz_md5_final( md5, &ret[0]);
1189 return ret;
1190 }}
1191
1192 FZ_FUNCTION std::vector<fz_quad> fz_highlight_selection2(fz_context* ctx, fz_stext_page* page, fz_point a, fz_point b, int max_quads)
1193 {{
1194 {{
1195 std::vector<fz_quad> ret(max_quads);
1196 int n;
1197 fz_try(ctx)
1198 {{
1199 n = fz_highlight_selection(ctx, page, a, b, &ret[0], max_quads);
1200 }}
1201 fz_catch(ctx)
1202 {{
1203 n = -1;
1204 }}
1205 if (n >= 0)
1206 {{
1207 ret.resize(n);
1208 return ret;
1209 }}
1210 }}
1211 /* We are careful to only call `fz_throw()` after `ret`'s
1212 destructor has been called. */
1213 fz_throw(ctx, FZ_ERROR_GENERIC, "fz_highlight_selection() failed");
1214 }}
1215
1216 FZ_FUNCTION std::vector<fz_search_page2_hit> fz_search_page2(fz_context* ctx, fz_document* doc, int number, const char* needle, int hit_max)
1217 {{
1218 std::vector<fz_quad> quads(hit_max);
1219 std::vector<int> marks(hit_max);
1220 int n = fz_search_page_number(ctx, doc, number, needle, &marks[0], &quads[0], hit_max);
1221 std::vector<fz_search_page2_hit> ret(n);
1222 for (int i=0; i<n; ++i)
1223 {{
1224 ret[i].quad = quads[i];
1225 ret[i].mark = marks[i];
1226 }}
1227 return ret;
1228 }}
1229
1230 FZ_FUNCTION std::string fz_string_from_text_language2(fz_text_language lang)
1231 {{
1232 char str[8];
1233 fz_string_from_text_language(str, lang);
1234 return std::string(str);
1235 }}
1236
1237 FZ_FUNCTION std::string fz_get_glyph_name2(fz_context* ctx, fz_font* font, int glyph)
1238 {{
1239 char name[32];
1240 fz_get_glyph_name(ctx, font, glyph, name, sizeof(name));
1241 return std::string(name);
1242 }}
1243
1244 void fz_install_load_system_font_funcs2(fz_context* ctx, fz_install_load_system_font_funcs_args* args)
1245 {{
1246 fz_install_load_system_font_funcs(ctx, args->f, args->f_cjk, args->f_fallback);
1247 }}
1248
1249 void* fz_install_load_system_font_funcs2_state = nullptr;
1250
1251 FZ_FUNCTION fz_document* fz_document_handler_open(fz_context* ctx, const fz_document_handler *handler, fz_stream* stream, fz_stream* accel, fz_archive* dir, void* recognize_state)
1252 {{
1253 return handler->open(ctx, handler, stream, accel, dir, recognize_state);
1254 }}
1255
1256 FZ_FUNCTION int fz_document_handler_recognize(fz_context* ctx, const fz_document_handler *handler, const char *magic)
1257 {{
1258 return handler->recognize(ctx, handler, magic);
1259 }}
1260
1261 FZ_FUNCTION std::vector<std::string> pdf_choice_widget_options2(fz_context* ctx, pdf_annot* tw, int exportval)
1262 {{
1263 int n = pdf_choice_widget_options(ctx, tw, exportval, nullptr);
1264 std::vector<const char*> opts(n);
1265 int n2 = pdf_choice_widget_options(ctx, tw, exportval, &opts[0]);
1266 assert(n2 == n);
1267 std::vector<std::string> ret(n);
1268 for (int i=0; i<n; ++i)
1269 {{
1270 ret[i] = opts[i];
1271 }}
1272 return ret;
1273 }}
1274
1275 FZ_FUNCTION fz_image* fz_new_image_from_compressed_buffer2(
1276 fz_context* ctx,
1277 int w,
1278 int h,
1279 int bpc,
1280 fz_colorspace* colorspace,
1281 int xres,
1282 int yres,
1283 int interpolate,
1284 int imagemask,
1285 const std::vector<float>& decode,
1286 const std::vector<int>& colorkey,
1287 fz_compressed_buffer* buffer,
1288 fz_image* mask
1289 )
1290 {{
1291 int n = fz_colorspace_n(ctx, colorspace);
1292 assert(decode.empty() || decode.size() == 2 * n);
1293 assert(colorkey.empty() || colorkey.size() == 2 * n);
1294 const float* decode2 = decode.empty() ? nullptr : &decode[0];
1295 const int* colorkey2 = colorkey.empty() ? nullptr : &colorkey[0];
1296 fz_image* ret = fz_new_image_from_compressed_buffer(
1297 ctx,
1298 w,
1299 h,
1300 bpc,
1301 colorspace,
1302 xres,
1303 yres,
1304 interpolate,
1305 imagemask,
1306 decode2,
1307 colorkey2,
1308 fz_keep_compressed_buffer(ctx, buffer),
1309 mask
1310 );
1311 return ret;
1312 }}
1313
1314 void pdf_rearrange_pages2(
1315 fz_context* ctx,
1316 pdf_document* doc,
1317 const std::vector<int>& pages,
1318 pdf_clean_options_structure structure
1319 )
1320 {{
1321 return pdf_rearrange_pages(ctx, doc, pages.size(), &pages[0], structure);
1322 }}
1323
1324 void pdf_subset_fonts2(fz_context *ctx, pdf_document *doc, const std::vector<int>& pages)
1325 {{
1326 return pdf_subset_fonts(ctx, doc, pages.size(), &pages[0]);
1327 }}
1328
1329 static void s_format_check(fz_context* ctx, const char* fmt, const char* specifiers)
1330 {{
1331 int length = strlen(fmt);
1332 if (!length || !strchr(specifiers, fmt[length-1]))
1333 {{
1334 fz_throw(ctx, FZ_ERROR_ARGUMENT, "Incorrect fmt '%s' should end with one of '%s'.", fmt, specifiers);
1335 }}
1336 }}
1337
1338 std::string fz_format_double(fz_context* ctx, const char* fmt, double value)
1339 {{
1340 char buffer[256];
1341 s_format_check(ctx, fmt, "efg");
1342 fz_snprintf(buffer, sizeof(buffer), fmt, value);
1343 return buffer;
1344 }}
1345
1346 static void fz_enumerate_font_cmap2_cb(fz_context* ctx, void* opaque, unsigned long ucs, unsigned int gid)
1347 {{
1348 std::vector<fz_font_ucs_gid>& ret = *(std::vector<fz_font_ucs_gid>*) opaque;
1349 fz_font_ucs_gid item = {{ucs, gid}};
1350 ret.push_back(item);
1351 }}
1352
1353 std::vector<fz_font_ucs_gid> fz_enumerate_font_cmap2(fz_context* ctx, fz_font* font)
1354 {{
1355 std::vector<fz_font_ucs_gid> ret;
1356 fz_enumerate_font_cmap(ctx, font, fz_enumerate_font_cmap2_cb, &ret);
1357 return ret;
1358 }}
1359
1360 void pdf_set_annot_callout_line2(fz_context *ctx, pdf_annot *annot, std::vector<fz_point>& callout)
1361 {{
1362 pdf_set_annot_callout_line(ctx, annot, &callout[0], callout.size());
1363 }}
1364
1365 std::string fz_decode_barcode_from_display_list2(fz_context *ctx, fz_barcode_type *type, fz_display_list *list, fz_rect subarea, int rotate)
1366 {{
1367 char* ret = fz_decode_barcode_from_display_list(ctx, type, list, subarea, rotate);
1368 std::string ret2 = ret;
1369 fz_free(ctx, ret);
1370 return ret2;
1371 }}
1372
1373 std::string fz_decode_barcode_from_pixmap2(fz_context *ctx, fz_barcode_type *type, fz_pixmap *pix, int rotate)
1374 {{
1375 char* ret = fz_decode_barcode_from_pixmap(ctx, type, pix, rotate);
1376 std::string ret2 = ret;
1377 fz_free(ctx, ret);
1378 return ret2;
1379 }}
1380
1381 std::string fz_decode_barcode_from_page2(fz_context *ctx, fz_barcode_type *type, fz_page *page, fz_rect subarea, int rotate)
1382 {{
1383 char* ret = fz_decode_barcode_from_page(ctx, type, page, subarea, rotate);
1384 std::string ret2 = ret;
1385 fz_free(ctx, ret);
1386 return ret2;
1387 }}
1388 ''')
1389
1390 def make_extra( out_extra_h, out_extra_cpp):
1391 '''
1392 We write extra abstractions here.
1393
1394 These are written in C++ but are at the same level of abstraction as MuPDF
1395 C functions, for example they take `fz_context` args. This is done so that
1396 we automatically generate wrappers as class methods as well as global
1397 functions.
1398 '''
1399 out_extra_h.write( g_extra_declarations)
1400
1401 out_extra_cpp.write( textwrap.dedent('''
1402 #include "mupdf/extra.h"
1403
1404 '''))
1405 out_extra_cpp.write( g_extra_definitions)
1406
1407
1408 def make_internal_functions( namespace, out_h, out_cpp, refcheck_if, trace_if):
1409 '''
1410 Writes internal support functions.
1411
1412 out_h:
1413 Stream to which we write C++ header text.
1414 out_cpp:
1415 Stream to which we write C++ text.
1416 '''
1417 out_h.write(
1418 textwrap.dedent(
1419 f'''
1420 #define internal_assert(expression) (expression) ? (void) 0 : internal_assert_fail(__FILE__, __LINE__, __FUNCTION__, #expression)
1421 FZ_FUNCTION void internal_assert_fail(const char* file, int line, const char* fn, const char* expression);
1422
1423 /** Internal use only. Looks at environmental variable <name>; returns 0 if unset else int value. */
1424 FZ_FUNCTION int {rename.internal('env_flag')}(const char* name);
1425
1426 /** Internal use only. Looks at environmental variable <name>; returns 0 if unset else int value. */
1427 FZ_FUNCTION int {rename.internal('env_flag_check_unset')}( const char* if_, const char* name);
1428
1429 /** Internal use only. Returns `fz_context*` for use by current thread. */
1430 FZ_FUNCTION fz_context* {rename.internal('context_get')}();
1431 '''
1432 ))
1433
1434 out_cpp.write(
1435 textwrap.dedent(
1436 '''
1437 #include "mupdf/exceptions.h"
1438 #include "mupdf/internal.h"
1439
1440 #include <iostream>
1441 #include <thread>
1442 #include <mutex>
1443
1444 #include <string.h>
1445
1446 '''))
1447
1448 make_namespace_open( namespace, out_cpp)
1449
1450 state_t = rename.internal( 'state')
1451 thread_state_t = rename.internal( 'thread_state')
1452
1453 cpp_text = textwrap.dedent(
1454 f'''
1455 FZ_FUNCTION void internal_assert_fail(const char* file, int line, const char* fn, const char* expression)
1456 {{
1457 std::cerr << file << ":" << line << ":" << fn << "(): "
1458 << "MuPDF C++ internal assert failure: " << expression
1459 << "\\n" << std::flush;
1460 abort();
1461 }}
1462
1463 FZ_FUNCTION int {rename.internal('env_flag')}(const char* name)
1464 {{
1465 const char* s = getenv( name);
1466 if (!s) return 0;
1467 return atoi( s);
1468 }}
1469
1470 FZ_FUNCTION int {rename.internal('env_flag_check_unset')}(const char* if_, const char* name)
1471 {{
1472 const char* s = getenv( name);
1473 if (s) std::cerr << __FILE__ << ":" << __LINE__ << ":" << __FUNCTION__ << "():"
1474 << " Warning: ignoring environmental variable because"
1475 << " '" << if_ << "' is false: " << name << "\\n";
1476 return false;
1477 }}
1478
1479 {trace_if}
1480 static const int s_trace = mupdf::internal_env_flag("MUPDF_trace");
1481 #else
1482 static const int s_trace = mupdf::internal_env_flag_check_unset("{trace_if}", "MUPDF_trace");
1483 #endif
1484
1485 static bool s_state_valid = false;
1486
1487 struct {rename.internal("state")}
1488 {{
1489 /* Constructor. */
1490 {rename.internal("state")}()
1491 {{
1492 m_locks.user = this;
1493 m_locks.lock = lock;
1494 m_locks.unlock = unlock;
1495 m_ctx = nullptr;
1496 bool multithreaded = true;
1497 const char* s = getenv( "MUPDF_mt_ctx");
1498 if ( s && !strcmp( s, "0")) multithreaded = false;
1499 reinit( multithreaded);
1500 s_state_valid = true;
1501 }}
1502
1503 void reinit( bool multithreaded)
1504 {{
1505 if (s_trace)
1506 {{
1507 std::cerr << __FILE__ << ":" << __LINE__ << ":" << __FUNCTION__ << "(): "
1508 << " calling fz_drop_context()\\n";
1509 }}
1510 fz_drop_context( m_ctx);
1511 m_multithreaded = multithreaded;
1512 if (s_trace)
1513 {{
1514 std::cerr << __FILE__ << ":" << __LINE__ << ":" << __FUNCTION__ << "(): "
1515 << " calling fz_new_context()\\n";
1516 }}
1517 m_ctx = fz_new_context(NULL /*alloc*/, (multithreaded) ? &m_locks : nullptr, FZ_STORE_DEFAULT);
1518 if (s_trace)
1519 {{
1520 std::cerr << __FILE__ << ":" << __LINE__ << ":" << __FUNCTION__ << "(): "
1521 << "fz_new_context() => " << m_ctx << "\\n";
1522 }}
1523 if (s_trace)
1524 {{
1525 std::cerr << __FILE__ << ":" << __LINE__ << ":" << __FUNCTION__ << "(): "
1526 << " calling fz_register_document_handlers()\\n";
1527 }}
1528 internal_assert("m_ctx = fz_new_context()" && m_ctx);
1529 fz_register_document_handlers(m_ctx);
1530 }}
1531 static void lock(void *user, int lock)
1532 {{
1533 {rename.internal("state")}* self = ({rename.internal("state")}*) user;
1534 internal_assert( self->m_multithreaded);
1535 self->m_mutexes[lock].lock();
1536 }}
1537 static void unlock(void *user, int lock)
1538 {{
1539 {rename.internal("state")}* self = ({rename.internal("state")}*) user;
1540 internal_assert( self->m_multithreaded);
1541 self->m_mutexes[lock].unlock();
1542 }}
1543 ~{rename.internal("state")}()
1544 {{
1545 if (s_trace)
1546 {{
1547 std::cerr << __FILE__ << ":" << __LINE__ << ":" << __FUNCTION__ << "(): "
1548 << " calling fz_drop_context()\\n";
1549 }}
1550 fz_drop_context(m_ctx);
1551 m_ctx = nullptr;
1552 s_state_valid = false;
1553 }}
1554
1555 bool m_multithreaded;
1556 fz_context* m_ctx;
1557 std::mutex m_mutex; /* Serialise access to m_ctx. fixme: not actually necessary. */
1558
1559 /* Provide thread support to mupdf. */
1560 std::mutex m_mutexes[FZ_LOCK_MAX];
1561 fz_locks_context m_locks;
1562 }};
1563
1564 static {rename.internal("state")} s_state;
1565
1566 struct {rename.internal("thread_state")}
1567 {{
1568 {rename.internal("thread_state")}()
1569 :
1570 m_ctx( nullptr),
1571 m_constructed( true)
1572 {{}}
1573 fz_context* get_context()
1574 {{
1575 internal_assert( s_state.m_multithreaded);
1576
1577 /* The following code checks that we are not being called after
1578 we have been destructed. This can happen if global mupdf
1579 wrapper class instances are defined - thread-local objects
1580 are destructed /before/ globals. */
1581 if (!m_constructed)
1582 {{
1583 std::cerr << __FILE__ << ":" << __LINE__ << ":" << __FUNCTION__ << ":\\n"
1584 << "*** Error - undefined behaviour.\\n"
1585 << "***\\n"
1586 << "*** Attempt to get thread-local fz_context after destruction\\n"
1587 << "*** of thread-local fz_context support instance.\\n"
1588 << "***\\n"
1589 << "*** This is undefined behaviour.\\n"
1590 << "***\\n"
1591 << "*** This can happen if mupdf wrapper class instances are\\n"
1592 << "*** created as globals, because in C++ global object\\n"
1593 << "*** destructors are run after thread_local destructors.\\n"
1594 << "***\\n"
1595 ;
1596 }}
1597 internal_assert( m_constructed);
1598 if (!m_ctx)
1599 {{
1600 /* Make a context for this thread by cloning the global
1601 context. */
1602 /* fixme: we don't actually need to take a lock here. */
1603 std::lock_guard<std::mutex> lock( s_state.m_mutex);
1604 if (s_trace)
1605 {{
1606 std::cerr << __FILE__ << ":" << __LINE__ << ":" << __FUNCTION__ << "(): "
1607 << " calling fz_clone_context()\\n";
1608 }}
1609 internal_assert(s_state_valid);
1610 m_ctx = fz_clone_context(s_state.m_ctx);
1611 if (s_trace)
1612 {{
1613 std::cerr << __FILE__ << ":" << __LINE__ << ":" << __FUNCTION__ << "(): "
1614 << "fz_clone_context(" << s_state.m_ctx << ") => " << m_ctx << "\\n";
1615 }}
1616 internal_assert("m_ctx = fz_clone_context()" && m_ctx);
1617 }}
1618 return m_ctx;
1619 }}
1620 ~{rename.internal("thread_state")}()
1621 {{
1622 if (m_ctx)
1623 {{
1624 internal_assert( s_state.m_multithreaded);
1625 if (s_trace)
1626 {{
1627 std::cerr << __FILE__ << ":" << __LINE__ << ":" << __FUNCTION__ << "(): "
1628 << " calling fz_drop_context()\\n";
1629 }}
1630 fz_drop_context( m_ctx);
1631 }}
1632
1633 /* These two statements are an attempt to get useful
1634 diagnostics in cases of undefined behaviour caused by the
1635 use of global wrapper class instances, whose destructors
1636 will be called /after/ destruction of this thread-local
1637 internal_thread_state instance. See check of m_constructed in
1638 get_context().
1639
1640 This probably only works in non-optimised builds -
1641 optimisation will simply elide both these statements. */
1642 m_ctx = nullptr;
1643 m_constructed = false;
1644 }}
1645 fz_context* m_ctx;
1646 bool m_constructed;
1647 }};
1648
1649 static thread_local {rename.internal("thread_state")} s_thread_state;
1650
1651 FZ_FUNCTION fz_context* {rename.internal("context_get")}()
1652 {{
1653 if (s_state.m_multithreaded)
1654 {{
1655 return s_thread_state.get_context();
1656 }}
1657 else
1658 {{
1659 /* This gives a small improvement in performance for
1660 single-threaded use, e.g. from 552.4s to 548.1s. */
1661 internal_assert(s_state_valid);
1662 fz_context* ret = s_state.m_ctx;
1663 internal_assert(ret);
1664 return ret;
1665 }}
1666 }}
1667
1668 FZ_FUNCTION void reinit_singlethreaded()
1669 {{
1670 if (0)
1671 {{
1672 std::cerr << __FILE__ << ":" << __LINE__ << ":" << __FUNCTION__ << "(): Reinitialising as single-threaded.\\n";
1673 }}
1674 s_state.reinit( false /*multithreaded*/);
1675 }}
1676 ''')
1677 out_cpp.write( cpp_text)
1678
1679 make_namespace_close( namespace, out_cpp)
1680
1681 # Generate code that exposes C++ operator new/delete to Memento.
1682 #
1683 # Disabled because our generated code makes very few direct calls
1684 # to operator new, and Memento ends up catching lots of (presumably
1685 # false-positive) leaks in the Python interpreter, so isn't very useful.
1686 #
1687 if 0:
1688 out_cpp.write( textwrap.dedent(
1689 '''
1690 #ifdef MEMENTO
1691
1692 void* operator new( size_t size)
1693 {
1694 return Memento_cpp_new( size);
1695 }
1696
1697 void operator delete( void* pointer)
1698 {
1699 Memento_cpp_delete( pointer);
1700 }
1701
1702 void* operator new[]( size_t size)
1703 {
1704 return Memento_cpp_new_array( size);
1705 }
1706
1707 void operator delete[]( void* pointer)
1708 {
1709 Memento_cpp_delete_array( pointer);
1710 }
1711
1712 #endif
1713 '''
1714 ))
1715
1716
1717 def make_function_wrappers(
1718 tu,
1719 namespace,
1720 out_exceptions_h,
1721 out_exceptions_cpp,
1722 out_functions_h,
1723 out_functions_cpp,
1724 out_internal_h,
1725 out_internal_cpp,
1726 out_functions_h2,
1727 out_functions_cpp2,
1728 generated,
1729 refcheck_if,
1730 trace_if,
1731 ):
1732 '''
1733 Generates C++ source code containing wrappers for all fz_*() functions.
1734
1735 We also create a function throw_exception(fz_context* ctx) that throws a
1736 C++ exception appropriate for the error in ctx.
1737
1738 If a function has first arg fz_context*, extra code is generated that
1739 converts fz_try..fz_catch exceptions into C++ exceptions by calling
1740 throw_exception().
1741
1742 We remove any fz_context* argument and the implementation calls
1743 internal_get_context() to get a suitable thread-specific fz_context* to
1744 use.
1745
1746 We generate a class for each exception type.
1747
1748 Returned source is just the raw functions text, e.g. it does not contain
1749 required #include's.
1750
1751 Args:
1752 tu:
1753 Clang translation unit.
1754 out_exceptions_h:
1755 Stream to which we write exception class definitions.
1756 out_exceptions_cpp:
1757 Stream to which we write exception class implementation.
1758 out_functions_h:
1759 Stream to which we write function declarations.
1760 out_functions_cpp:
1761 Stream to which we write function definitions.
1762 generated:
1763 A Generated instance.
1764 '''
1765 # Look for FZ_ERROR_* enums. We generate an exception class for each of
1766 # these.
1767 #
1768 error_name_prefix = 'FZ_ERROR_'
1769 fz_error_names = []
1770 fz_error_names_maxlen = 0 # Used for padding so generated code aligns.
1771
1772 for cursor in parse.get_children(tu.cursor):
1773 if cursor.kind == state.clang.cindex.CursorKind.ENUM_DECL:
1774 #log( 'enum: {cursor.spelling=})
1775 for child in parse.get_members( cursor):
1776 #log( 'child:{ child.spelling=})
1777 if child.spelling.startswith( error_name_prefix):
1778 name = child.spelling[ len(error_name_prefix):]
1779 fz_error_names.append( name)
1780 if len( name) > fz_error_names_maxlen:
1781 fz_error_names_maxlen = len( name)
1782
1783 def errors(include_error_base=False):
1784 '''
1785 Yields (enum, typename, padding) for each error.
1786 E.g.:
1787 enum=FZ_ERROR_SYSTEM
1788 typename=mupdf_error_memory
1789 padding=' '
1790 '''
1791 names = fz_error_names
1792 if include_error_base:
1793 names = ['BASE'] + names
1794 for name in names:
1795 enum = f'{error_name_prefix}{name}'
1796 typename = rename.error_class( enum)
1797 padding = (fz_error_names_maxlen - len(name)) * ' '
1798 yield enum, typename, padding
1799
1800 # Declare base exception class and define its methods.
1801 #
1802 base_name = rename.error_class('FZ_ERROR_BASE')
1803
1804 out_exceptions_h.write( textwrap.dedent(
1805 f'''
1806 /** Base class for exceptions. */
1807 struct {base_name} : std::exception
1808 {{
1809 int m_code;
1810 std::string m_text;
1811 mutable std::string m_what;
1812 FZ_FUNCTION const char* what() const throw();
1813 FZ_FUNCTION {base_name}(int code, const char* text);
1814 }};
1815 '''))
1816
1817 out_exceptions_cpp.write( textwrap.dedent(
1818 f'''
1819 FZ_FUNCTION {base_name}::{base_name}(int code, const char* text)
1820 :
1821 m_code(code),
1822 m_text(text)
1823 {{
1824 {trace_if}
1825 if (s_trace_exceptions)
1826 {{
1827 std::cerr << __FILE__ << ":" << __LINE__ << ":" << __FUNCTION__ << "(): {base_name}: " << m_text << "\\n";
1828 }}
1829 #endif
1830 }};
1831
1832 FZ_FUNCTION const char* {base_name}::what() const throw()
1833 {{
1834 m_what = "code=" + std::to_string(m_code) + ": " + m_text;
1835 return m_what.c_str();
1836 }};
1837
1838 '''))
1839
1840 # Generate SWIG Python code to allow conversion of our error class
1841 # exceptions into equivalent Python exceptions.
1842 error_classes_n = 0
1843 for enum, typename, padding in errors():
1844 error_classes_n += 1
1845
1846 error_classes_n += 1 # Extra space for FzErrorBase.
1847 generated.swig_python_exceptions.write( textwrap.dedent( f'''
1848
1849 void internal_set_error_classes(PyObject* classes);
1850
1851 %{{
1852 /* A Python list of Error classes, [FzErrorNone, FzErrorMemory, FzErrorGeneric, ...]. */
1853 static PyObject* s_error_classes[{error_classes_n}] = {{}};
1854
1855 /* Called on startup by mupdf.py, with a list of error classes
1856 to be copied into s_error_classes. This will allow us to create
1857 instances of these error classes in SWIG's `%exception ...`, so
1858 Python code will see exceptions as instances of Python error
1859 classes. */
1860 void internal_set_error_classes(PyObject* classes)
1861 {{
1862 assert(PyList_Check(classes));
1863 int n = PyList_Size(classes);
1864 assert(n == {error_classes_n});
1865 for (int i=0; i<n; ++i)
1866 {{
1867 PyObject* class_ = PyList_GetItem(classes, i);
1868 s_error_classes[i] = class_;
1869 }}
1870 }}
1871
1872 /* Sets Python exception to a new mupdf.<name> object constructed
1873 with `text`. */
1874 void set_exception(PyObject* class_, int code, const std::string& text)
1875 {{
1876 PyObject* args = Py_BuildValue("(s)", text.c_str());
1877 PyObject* instance = PyObject_CallObject(class_, args);
1878 PyErr_SetObject(class_, instance);
1879 Py_XDECREF(instance);
1880 Py_XDECREF(args);
1881 }}
1882
1883 /* Exception handler for swig-generated code. Uses internal
1884 `throw;` to recover the current C++ exception then uses
1885 `set_exception()` to set the current Python exception. Caller
1886 should do `SWIG_fail;` after we return. */
1887 void handle_exception()
1888 {{
1889 try
1890 {{
1891 throw;
1892 }}
1893 '''
1894 ))
1895
1896 # Declare exception class for each FZ_ERROR_*. Also append catch blocks for
1897 # each of these exception classes to `handle_exception()`.
1898 #
1899 for i, (enum, typename, padding) in enumerate(errors()):
1900 out_exceptions_h.write( textwrap.dedent(
1901 f'''
1902 /** For `{enum}`. */
1903 struct {typename} : {base_name}
1904 {{
1905 FZ_FUNCTION {typename}(const char* message);
1906 }};
1907
1908 '''))
1909
1910 generated.swig_python_exceptions.write( textwrap.dedent( f'''
1911 /**/
1912 catch (mupdf::{typename}& e)
1913 {{
1914 if (g_mupdf_trace_exceptions)
1915 {{
1916 std::cerr
1917 << __FILE__ << ':' << __LINE__ << ':'
1918 #ifndef _WIN32
1919 << __PRETTY_FUNCTION__ << ':'
1920 #endif
1921 << " Converting C++ std::exception mupdf::{typename} ({i=}) into Python exception:\\n"
1922 << " e.m_code: " << e.m_code << "\\n"
1923 << " e.m_text: " << e.m_text << "\\n"
1924 << " e.what(): " << e.what() << "\\n"
1925 << " typeid(e).name(): " << typeid(e).name() << "\\n"
1926 << "\\n";
1927 }}
1928 set_exception(s_error_classes[{i}], e.m_code, e.m_text);
1929
1930 }}'''))
1931
1932 # Append less specific exception handling.
1933 generated.swig_python_exceptions.write( textwrap.dedent( f'''
1934 catch (mupdf::FzErrorBase& e)
1935 {{
1936 if (g_mupdf_trace_exceptions)
1937 {{
1938 std::cerr
1939 << __FILE__ << ':' << __LINE__ << ':'
1940 #ifndef _WIN32
1941 << __PRETTY_FUNCTION__ << ':'
1942 #endif
1943 << " Converting C++ std::exception mupdf::FzErrorBase ({error_classes_n-1=}) into Python exception:\\n"
1944 << " e.m_code: " << e.m_code << "\\n"
1945 << " e.m_text: " << e.m_text << "\\n"
1946 << " e.what(): " << e.what() << "\\n"
1947 << " typeid(e).name(): " << typeid(e).name() << "\\n"
1948 << "\\n";
1949 }}
1950 PyObject* class_ = s_error_classes[{error_classes_n-1}];
1951 PyObject* args = Py_BuildValue("is", e.m_code, e.m_text.c_str());
1952 PyObject* instance = PyObject_CallObject(class_, args);
1953 PyErr_SetObject(class_, instance);
1954 Py_XDECREF(instance);
1955 Py_XDECREF(args);
1956 }}
1957 catch (std::exception& e)
1958 {{
1959 if (g_mupdf_trace_exceptions)
1960 {{
1961 std::cerr
1962 << __FILE__ << ':' << __LINE__ << ':'
1963 #ifndef _WIN32
1964 << __PRETTY_FUNCTION__ << ':'
1965 #endif
1966 << " Converting C++ std::exception into Python exception: "
1967 << e.what()
1968 << " typeid(e).name(): " << typeid(e).name() << "\\n"
1969 << "\\n";
1970 }}
1971 SWIG_Error(SWIG_RuntimeError, e.what());
1972
1973 }}
1974 catch (...)
1975 {{
1976 if (g_mupdf_trace_exceptions)
1977 {{
1978 std::cerr
1979 << __FILE__ << ':' << __LINE__ << ':'
1980 #ifndef _WIN32
1981 << __PRETTY_FUNCTION__ << ':'
1982 #endif
1983 << " Converting unknown C++ exception into Python exception."
1984 << "\\n";
1985 }}
1986 SWIG_Error(SWIG_RuntimeError, "Unknown exception");
1987 }}
1988 }}
1989
1990 %}}
1991
1992 %exception
1993 {{
1994 try
1995 {{
1996 $action
1997 }}
1998 catch (...)
1999 {{
2000 handle_exception();
2001 SWIG_fail;
2002 }}
2003 }}
2004 '''))
2005
2006 generated.swig_python_set_error_classes.write( f'# Define __str()__ for each error/exception class, to use self.what().\n')
2007 for enum, typename, padding in errors(include_error_base=1):
2008 generated.swig_python_set_error_classes.write( f'{typename}.__str__ = lambda self: self.what()\n')
2009
2010 generated.swig_python_set_error_classes.write( textwrap.dedent( f'''
2011 # This must be after the declaration of mupdf::FzError*
2012 # classes in mupdf/exceptions.h and declaration of
2013 # `internal_set_error_classes()`, otherwise generated code is
2014 # before the declaration of the Python class or similar. */
2015 internal_set_error_classes([
2016 '''))
2017 for enum, typename, padding in errors():
2018 generated.swig_python_set_error_classes.write(f' {typename},\n')
2019 generated.swig_python_set_error_classes.write( textwrap.dedent( f'''
2020 FzErrorBase,
2021 ])
2022 '''))
2023
2024 # Define constructor for each exception class.
2025 #
2026 for enum, typename, padding in errors():
2027 out_exceptions_cpp.write( textwrap.dedent(
2028 f'''
2029 FZ_FUNCTION {typename}::{typename}(const char* text)
2030 : {base_name}({enum}, text)
2031 {{
2032 {trace_if}
2033 if (s_trace_exceptions)
2034 {{
2035 std::cerr << __FILE__ << ":" << __LINE__ << ":" << __FUNCTION__ << "(): {typename} constructor, text: " << m_text << "\\n";
2036 }}
2037 #endif
2038 }}
2039
2040 '''))
2041
2042 # Generate function that throws an appropriate exception from a fz_context.
2043 #
2044 throw_exception = rename.internal( 'throw_exception')
2045 out_exceptions_h.write( textwrap.dedent(
2046 f'''
2047 /** Throw exception appropriate for error in `ctx`. */
2048 FZ_FUNCTION void {throw_exception}(fz_context* ctx);
2049
2050 '''))
2051 out_exceptions_cpp.write( textwrap.dedent(
2052 f'''
2053 FZ_FUNCTION void {throw_exception}(fz_context* ctx)
2054 {{
2055 int code;
2056 const char* text = fz_convert_error(ctx, &code);
2057 {trace_if}
2058 if (s_trace_exceptions)
2059 {{
2060 std::cerr << __FILE__ << ":" << __LINE__ << ":" << __FUNCTION__ << "(): code=" << code << "\\n";
2061 std::cerr << __FILE__ << ":" << __LINE__ << ":" << __FUNCTION__ << "(): text=" << text << "\\n";
2062 }}
2063 #endif
2064 '''))
2065 for enum, typename, padding in errors():
2066 out_exceptions_cpp.write( f' if (code == {enum}) {padding}throw {typename}{padding}(text);\n')
2067 out_exceptions_cpp.write( f' throw {base_name}(code, text);\n')
2068 out_exceptions_cpp.write( f'}}\n')
2069 out_exceptions_cpp.write( '\n')
2070
2071 make_internal_functions( namespace, out_internal_h, out_internal_cpp, refcheck_if, trace_if)
2072
2073 # Generate wrappers for each function that we find.
2074 #
2075 functions = []
2076 for fnname, cursor in state.state_.find_functions_starting_with( tu, ('fz_', 'pdf_'), method=False):
2077 assert fnname not in state.omit_fns
2078 #jlib.log( '{fnname=} {cursor.spelling=} {cursor.type.spelling=}')
2079 if ( cursor.type == state.clang.cindex.TypeKind.FUNCTIONPROTO
2080 and cursor.type.is_function_variadic()
2081 ):
2082 # We don't attempt to wrap variadic functions - would need to find
2083 # the equivalent function that takes a va_list.
2084 if 0:
2085 jlib.log( 'Variadic fn: {cursor.type.spelling=}')
2086 if fnname != 'fz_warn':
2087 continue
2088 if fnname == 'fz_push_try':
2089 # This is partof implementation of fz_try/catch so doesn't make
2090 # sense to provide a wrapper. Also it is OS-dependent so including
2091 # it makes our generated code OS-specific.
2092 continue
2093
2094 functions.append( (fnname, cursor))
2095
2096 jlib.log1( '{len(functions)=}')
2097
2098 # Sort by function-name to make output easier to read.
2099 functions.sort()
2100 for fnname, cursor in functions:
2101 if state.state_.show_details( fnname):
2102 jlib.log( 'Looking at {fnname}')
2103 fnname_wrapper = rename.ll_fn( fnname)
2104 function_wrapper(
2105 tu,
2106 cursor,
2107 fnname,
2108 fnname_wrapper,
2109 out_functions_h,
2110 out_functions_cpp,
2111 generated,
2112 refcheck_if,
2113 trace_if,
2114 )
2115 if not fnname.startswith( ( 'fz_keep_', 'fz_drop_', 'pdf_keep_', 'pdf_drop_')):
2116 function_wrapper_class_aware(
2117 tu,
2118 register_fn_use=None,
2119 struct_name=None,
2120 class_name=None,
2121 fn_cursor=cursor,
2122 refcheck_if=refcheck_if,
2123 trace_if=trace_if,
2124 fnname=fnname,
2125 out_h=out_functions_h2,
2126 out_cpp=out_functions_cpp2,
2127 generated=generated,
2128 )
2129
2130 python.cppyy_add_outparams_wrapper( tu, fnname, cursor, state.state_, generated)
2131
2132 if fnname == "pdf_load_field_name": #(fz_context *ctx, pdf_obj *field);
2133 # Output wrapper that returns std::string instead of buffer that
2134 # caller needs to free.
2135 out_functions_h.write(
2136 textwrap.dedent(
2137 f'''
2138 /** Alternative to `{rename.ll_fn('pdf_load_field_name')}()` that returns a std::string. */
2139 FZ_FUNCTION std::string {rename.ll_fn('pdf_load_field_name2')}(pdf_obj* field);
2140
2141 '''))
2142 out_functions_cpp.write(
2143 textwrap.dedent(
2144 f'''
2145 FZ_FUNCTION std::string {rename.ll_fn('pdf_load_field_name2')}(pdf_obj* field)
2146 {{
2147 char* buffer = {rename.ll_fn('pdf_load_field_name')}( field);
2148 std::string ret( buffer);
2149 {rename.ll_fn('fz_free')}( buffer);
2150 return ret;
2151 }}
2152 '''))
2153 out_functions_h2.write(
2154 textwrap.indent(
2155 textwrap.dedent(
2156 f'''
2157 /** Alternative to `{rename.fn('pdf_load_field_name')}()` that returns a std::string. */
2158 FZ_FUNCTION std::string {rename.fn('pdf_load_field_name2')}({rename.class_('pdf_obj')}& field);
2159 '''),
2160 ' ',
2161 )
2162 )
2163 out_functions_cpp2.write(
2164 textwrap.dedent(
2165 f'''
2166 FZ_FUNCTION std::string {rename.fn('pdf_load_field_name2')}({rename.class_('pdf_obj')}& field)
2167 {{
2168 return {rename.ll_fn('pdf_load_field_name2')}( field.m_internal);
2169 }}
2170 '''))
2171
2172 # Output custom wrappers for variadic pdf_dict_getl().
2173 #
2174
2175 decl = f'''FZ_FUNCTION pdf_obj* {rename.ll_fn('pdf_dict_getlv')}( pdf_obj* dict, va_list keys)'''
2176 out_functions_h.write( textwrap.dedent( f'''
2177 /* Low-level wrapper for `pdf_dict_getl()`. `keys` must be null-terminated list of `pdf_obj*`'s. */
2178 {decl};
2179 '''))
2180 out_functions_cpp.write( textwrap.dedent( f'''
2181 {decl}
2182 {{
2183 pdf_obj *key;
2184 while (dict != NULL && (key = va_arg(keys, pdf_obj *)) != NULL)
2185 {{
2186 dict = {rename.ll_fn('pdf_dict_get')}( dict, key);
2187 }}
2188 return dict;
2189 }}
2190 '''))
2191
2192 decl = f'''FZ_FUNCTION pdf_obj* {rename.ll_fn('pdf_dict_getl')}( pdf_obj* dict, ...)'''
2193 out_functions_h.write( textwrap.dedent( f'''
2194 /* Low-level wrapper for `pdf_dict_getl()`. `...` must be null-terminated list of `pdf_obj*`'s. */
2195 {decl};
2196 '''))
2197 out_functions_cpp.write( textwrap.dedent( f'''
2198 {decl}
2199 {{
2200 va_list keys;
2201 va_start(keys, dict);
2202 try
2203 {{
2204 dict = {rename.ll_fn('pdf_dict_getlv')}( dict, keys);
2205 }}
2206 catch( std::exception&)
2207 {{
2208 va_end(keys);
2209 throw;
2210 }}
2211 va_end(keys);
2212 return dict;
2213 }}
2214 '''))
2215
2216 decl = f'''FZ_FUNCTION {rename.class_('pdf_obj')} {rename.fn('pdf_dict_getlv')}( {rename.class_('pdf_obj')}& dict, va_list keys)'''
2217 out_functions_h2.write(
2218 textwrap.indent(
2219 textwrap.dedent( f'''
2220 /* Class-aware wrapper for `pdf_dict_getl()`. `keys` must be null-terminated list of
2221 `pdf_obj*`'s, not `{rename.class_('pdf_obj')}*`'s, so that conventional
2222 use with `PDF_NAME()` works. */
2223 {decl};
2224 '''),
2225 ' ',
2226 )
2227 )
2228 out_functions_cpp2.write( textwrap.dedent( f'''
2229 {decl}
2230 {{
2231 pdf_obj* ret = {rename.ll_fn('pdf_dict_getlv')}( dict.m_internal, keys);
2232 return {rename.class_('pdf_obj')}( {rename.ll_fn('pdf_keep_obj')}( ret));
2233 }}
2234 '''))
2235
2236 decl = f'''FZ_FUNCTION {rename.class_('pdf_obj')} {rename.fn('pdf_dict_getl')}( {rename.class_('pdf_obj')}* dict, ...)'''
2237 out_functions_h2.write(
2238 textwrap.indent(
2239 textwrap.dedent( f'''
2240 /* Class-aware wrapper for `pdf_dict_getl()`. `...` must be null-terminated list of
2241 `pdf_obj*`'s, not `{rename.class_('pdf_obj')}*`'s, so that conventional
2242 use with `PDF_NAME()` works. [We use pointer `dict` arg because variadic
2243 args do not with with reference args.] */
2244 {decl};
2245 '''),
2246 ' ',
2247 ),
2248 )
2249 out_functions_cpp2.write( textwrap.dedent( f'''
2250 {decl}
2251 {{
2252 va_list keys;
2253 va_start(keys, dict);
2254 try
2255 {{
2256 {rename.class_('pdf_obj')} ret = {rename.fn('pdf_dict_getlv')}( *dict, keys);
2257 va_end( keys);
2258 return ret;
2259 }}
2260 catch (std::exception&)
2261 {{
2262 va_end( keys);
2263 throw;
2264 }}
2265 }}
2266 '''))
2267
2268
2269 def class_add_iterator( tu, struct_cursor, struct_name, classname, extras, refcheck_if, trace_if):
2270 '''
2271 Add begin() and end() methods so that this generated class is iterable
2272 from C++ with:
2273
2274 for (auto i: foo) {...}
2275
2276 We modify <extras> to create an iterator class and add begin() and end()
2277 methods that each return an instance of the iterator class.
2278 '''
2279 it_begin, it_end = extras.iterator_next
2280
2281 # Figure out type of what the iterator returns by looking at type of
2282 # <it_begin>.
2283 if it_begin:
2284 c = parse.find_name( struct_cursor, it_begin)
2285 assert c.type.kind == state.clang.cindex.TypeKind.POINTER
2286 it_internal_type = state.get_name_canonical( c.type.get_pointee()).spelling
2287 it_internal_type = util.clip( it_internal_type, 'struct ')
2288 it_type = rename.class_( it_internal_type)
2289 else:
2290 # The container is also the first item in the linked list.
2291 it_internal_type = struct_name
2292 it_type = classname
2293
2294 # We add to extras.methods_extra().
2295 #
2296 check_refs = 1 if parse.has_refs( tu, struct_cursor.type) else 0
2297 extras.methods_extra.append(
2298 classes.ExtraMethod( f'{classname}Iterator', 'begin()',
2299 f'''
2300 {{
2301 auto ret = {classname}Iterator({'m_internal->'+it_begin if it_begin else '*this'});
2302 {refcheck_if}
2303 #if {check_refs}
2304 if (s_check_refs)
2305 {{
2306 s_{classname}_refs_check.check( this, __FILE__, __LINE__, __FUNCTION__);
2307 }}
2308 #endif
2309 #endif
2310 return ret;
2311 }}
2312 ''',
2313 f'/* Used for iteration over linked list of {it_type} items starting at {it_internal_type}::{it_begin}. */',
2314 ),
2315 )
2316 extras.methods_extra.append(
2317 classes.ExtraMethod( f'{classname}Iterator', 'end()',
2318 f'''
2319 {{
2320 auto ret = {classname}Iterator({it_type}());
2321 {refcheck_if}
2322 #if {check_refs}
2323 if (s_check_refs)
2324 {{
2325 s_{classname}_refs_check.check( this, __FILE__, __LINE__, __FUNCTION__);
2326 }}
2327 #endif
2328 #endif
2329 return ret;
2330 }}
2331 ''',
2332 f'/* Used for iteration over linked list of {it_type} items starting at {it_internal_type}::{it_begin}. */',
2333 ),
2334 )
2335
2336 extras.class_bottom += f'\n typedef {classname}Iterator iterator;\n'
2337
2338 extras.class_pre += f'\nstruct {classname}Iterator;\n'
2339
2340 extras.class_post += f'''
2341 struct {classname}Iterator
2342 {{
2343 FZ_FUNCTION {classname}Iterator(const {it_type}& item);
2344 FZ_FUNCTION {classname}Iterator& operator++();
2345 FZ_FUNCTION bool operator==( const {classname}Iterator& rhs);
2346 FZ_FUNCTION bool operator!=( const {classname}Iterator& rhs);
2347 FZ_FUNCTION {it_type} operator*();
2348 FZ_FUNCTION {it_type}* operator->();
2349 private:
2350 {it_type} m_item;
2351 }};
2352 '''
2353 keep_text = ''
2354 if extras.copyable and extras.copyable != 'default':
2355 # Our operator++ needs to create it_type from m_item.m_internal->next,
2356 # so we need to call fz_keep_<it_type>().
2357 #
2358 # [Perhaps life would be simpler if our generated constructors always
2359 # called fz_keep_*() as necessary? In some circumstances this would
2360 # require us to call fz_drop_*() when constructing an instance, but
2361 # that might be simpler?]
2362 #
2363 base_name = util.clip( struct_name, ('fz_', 'pdf_'))
2364 if struct_name.startswith( 'fz_'):
2365 keep_name = f'fz_keep_{base_name}'
2366 elif struct_name.startswith( 'pdf_'):
2367 keep_name = f'pdf_keep_{base_name}'
2368 keep_name = rename.ll_fn(keep_name)
2369 keep_text = f'{keep_name}(m_item.m_internal->next);'
2370
2371 extras.extra_cpp += f'''
2372 FZ_FUNCTION {classname}Iterator::{classname}Iterator(const {it_type}& item)
2373 : m_item( item)
2374 {{
2375 }}
2376 FZ_FUNCTION {classname}Iterator& {classname}Iterator::operator++()
2377 {{
2378 {keep_text}
2379 m_item = {it_type}(m_item.m_internal->next);
2380 return *this;
2381 }}
2382 FZ_FUNCTION bool {classname}Iterator::operator==( const {classname}Iterator& rhs)
2383 {{
2384 return m_item.m_internal == rhs.m_item.m_internal;
2385 }}
2386 FZ_FUNCTION bool {classname}Iterator::operator!=( const {classname}Iterator& rhs)
2387 {{
2388 return m_item.m_internal != rhs.m_item.m_internal;
2389 }}
2390 FZ_FUNCTION {it_type} {classname}Iterator::operator*()
2391 {{
2392 return m_item;
2393 }}
2394 FZ_FUNCTION {it_type}* {classname}Iterator::operator->()
2395 {{
2396 return &m_item;
2397 }}
2398
2399 '''
2400
2401
2402 def class_find_constructor_fns( tu, classname, struct_name, base_name, extras):
2403 '''
2404 Returns list of functions that could be used as constructors of the
2405 specified wrapper class.
2406
2407 For example we look for functions that return a pointer to <struct_name> or
2408 return a POD <struct_name> by value.
2409
2410 tu:
2411 .
2412 classname:
2413 Name of our wrapper class.
2414 struct_name:
2415 Name of underlying mupdf struct.
2416 base_name:
2417 Name of struct without 'fz_' prefix.
2418 extras:
2419 .
2420 '''
2421 assert struct_name == f'fz_{base_name}' or struct_name == f'pdf_{base_name}'
2422 verbose = state.state_.show_details( struct_name)
2423 constructor_fns = []
2424 if '-' not in extras.constructor_prefixes:
2425 # Add default constructor fn prefix.
2426 if struct_name.startswith( 'fz_'):
2427 extras.constructor_prefixes.insert( 0, f'fz_new_')
2428 extras.constructor_prefixes.insert( 0, f'pdf_new_')
2429 elif struct_name.startswith( 'pdf_'):
2430 extras.constructor_prefixes.insert( 0, f'pdf_new_')
2431 for fnprefix in extras.constructor_prefixes:
2432 if verbose:
2433 jlib.log('{struct_name=} {fnprefix=}')
2434 for fnname, cursor in state.state_.find_functions_starting_with( tu, fnprefix, method=True):
2435 # Check whether this has identical signature to any fn we've
2436 # already found.
2437 if verbose:
2438 jlib.log( '{struct_name=} {fnname=}')
2439 duplicate_type = None
2440 duplicate_name = False
2441 for f, c, is_duplicate in constructor_fns:
2442 if verbose:
2443 jlib.log( '{struct_name=} {cursor.spelling=} {c.type.spelling=}')
2444 if f == fnname:
2445 if verbose:
2446 jlib.log('setting duplicate_name to true')
2447 duplicate_name = True
2448 break
2449 if c.type == cursor.type:
2450 if verbose:
2451 jlib.log( '{struct_name} wrapper: ignoring candidate constructor {fnname}() because prototype is indistinguishable from {f=}()')
2452 duplicate_type = f
2453 break
2454 if duplicate_name:
2455 continue
2456 ok = False
2457
2458 arg, n = parse.get_first_arg( tu, cursor)
2459 if arg and n == 1 and parse.is_pointer_to( arg.cursor.type, struct_name):
2460 # This avoids generation of bogus copy constructor wrapping
2461 # function fz_new_pixmap_from_alpha_channel() introduced
2462 # 2021-05-07.
2463 #
2464 if verbose:
2465 jlib.log('ignoring possible constructor because looks like copy constructor: {fnname}')
2466 elif fnname in extras.constructor_excludes:
2467 if verbose:
2468 jlib.log('{fnname=} is in {extras.constructor_excludes=}')
2469 elif extras.pod and extras.pod != 'none' and state.get_name_canonical( cursor.result_type).spelling == f'{struct_name}':
2470 # Returns POD struct by value.
2471 ok = True
2472 elif not extras.pod and parse.is_pointer_to( cursor.result_type, f'{struct_name}'):
2473 # Returns pointer to struct.
2474 ok = True
2475
2476 if ok:
2477 if duplicate_type and extras.copyable:
2478 if verbose:
2479 jlib.log1( 'adding static method wrapper for {fnname}')
2480 extras.method_wrappers_static.append( fnname)
2481 else:
2482 if duplicate_type:
2483 if verbose:
2484 jlib.log( 'not able to provide static factory fn {struct_name}::{fnname} because wrapper class is not copyable.')
2485 if verbose:
2486 jlib.log( 'adding constructor wrapper for {fnname}')
2487 constructor_fns.append( (fnname, cursor, duplicate_type))
2488 else:
2489 if verbose:
2490 jlib.log( 'ignoring possible constructor for {classname=} because does not return required type: {fnname=} -> {cursor.result_type.spelling=}')
2491
2492 constructor_fns.sort()
2493 return constructor_fns
2494
2495
2496 def class_find_destructor_fns( tu, struct_name, base_name):
2497 '''
2498 Returns list of functions that could be used by destructor - must be called
2499 'fz_drop_<typename>', must take a <struct>* arg, may take a fz_context*
2500 arg.
2501 '''
2502 if struct_name.startswith( 'fz_'):
2503 destructor_prefix = f'fz_drop_{base_name}'
2504 elif struct_name.startswith( 'pdf_'):
2505 destructor_prefix = f'pdf_drop_{base_name}'
2506 destructor_fns = []
2507 for fnname, cursor in state.state_.find_functions_starting_with( tu, destructor_prefix, method=True):
2508 arg_struct = False
2509 arg_context = False
2510 args_num = 0
2511 for arg in parse.get_args( tu, cursor):
2512 if not arg_struct and parse.is_pointer_to( arg.cursor.type, struct_name):
2513 arg_struct = True
2514 elif not arg_context and parse.is_pointer_to( arg.cursor.type, 'fz_context'):
2515 arg_context = True
2516 args_num += 1
2517 if arg_struct:
2518 if args_num == 1 or (args_num == 2 and arg_context):
2519 # No params other than <struct>* and fz_context* so this is
2520 # candidate destructor.
2521 #log( 'adding candidate destructor: {fnname}')
2522 destructor_fns.append( (fnname, cursor))
2523
2524 destructor_fns.sort()
2525 return destructor_fns
2526
2527
2528 def num_instances(refcheck_if, delta, name):
2529 '''
2530 Returns C++ code to embed in a wrapper class constructor/destructor function
2531 to update the class static `s_num_instances` variable.
2532 '''
2533 ret = ''
2534 ret += f' {refcheck_if}\n'
2535 if delta == +1:
2536 ret += ' ++s_num_instances;\n'
2537 elif delta == -1:
2538 ret += ' --s_num_instances;\n'
2539 else:
2540 assert 0
2541 ret += ' #endif\n'
2542 return ret
2543
2544
2545 def class_constructor_default(
2546 tu,
2547 struct_cursor,
2548 classname,
2549 extras,
2550 out_h,
2551 out_cpp,
2552 refcheck_if,
2553 trace_if,
2554 ):
2555 '''
2556 Generates constructor that sets each member to default value.
2557 '''
2558 if extras.pod:
2559 comment = f'Default constructor, sets each member to default value.'
2560 else:
2561 comment = f'Default constructor, sets `m_internal` to null.'
2562 out_h.write( '\n')
2563 out_h.write( f' /** {comment} */\n')
2564 out_h.write( f' FZ_FUNCTION {classname}();\n')
2565
2566 out_cpp.write( f'/** {comment} */\n')
2567 out_cpp.write( f'FZ_FUNCTION {classname}::{classname}()\n')
2568 if not extras.pod:
2569 out_cpp.write( f': m_internal(nullptr)\n')
2570 out_cpp.write( f'{{\n')
2571 if extras.pod == 'none':
2572 pass
2573 elif extras.pod:
2574 for c in parse.get_members(struct_cursor):
2575 if extras.pod == 'inline':
2576 c_name = f'this->{c.spelling}'
2577 else:
2578 c_name = f'this->m_internal.{c.spelling}'
2579 if c.type.kind == state.clang.cindex.TypeKind.CONSTANTARRAY:
2580 out_cpp.write( f' memset(&{c_name}, 0, sizeof({c_name}));\n')
2581 else:
2582 out_cpp.write( f' {c_name} = {{}};\n')
2583 else:
2584 if parse.has_refs( tu, struct_cursor.type):
2585 out_cpp.write(f' {refcheck_if}\n')
2586 out_cpp.write( ' if (s_check_refs)\n')
2587 out_cpp.write( ' {\n')
2588 out_cpp.write(f' s_{classname}_refs_check.add( this, __FILE__, __LINE__, __FUNCTION__);\n')
2589 out_cpp.write( ' }\n')
2590 out_cpp.write( ' #endif\n')
2591
2592 out_cpp.write(num_instances(refcheck_if, +1, classname))
2593
2594 out_cpp.write( f'}};\n')
2595
2596
2597 def class_copy_constructor(
2598 tu,
2599 functions,
2600 struct_name,
2601 struct_cursor,
2602 base_name,
2603 classname,
2604 constructor_fns,
2605 out_h,
2606 out_cpp,
2607 refcheck_if,
2608 trace_if,
2609 ):
2610 '''
2611 Generate a copy constructor and operator= by finding a suitable fz_keep_*()
2612 function.
2613
2614 We raise an exception if we can't find one.
2615 '''
2616 if struct_name.startswith( 'fz_'):
2617 keep_name = f'fz_keep_{base_name}'
2618 drop_name = f'fz_drop_{base_name}'
2619 elif struct_name.startswith( 'pdf_'):
2620 keep_name = f'pdf_keep_{base_name}'
2621 drop_name = f'pdf_drop_{base_name}'
2622 for name in keep_name, drop_name:
2623 cursor = state.state_.find_function( tu, name, method=True)
2624 if not cursor:
2625 classextra = classes.classextras.get( tu, struct_name)
2626 if classextra.copyable:
2627 if 1 or state.state_.show_details( struct_name):
2628 jlib.log( 'changing to non-copyable because no function {name}(): {struct_name}')
2629 classextra.copyable = False
2630 return
2631 if name == keep_name:
2632 pvoid = parse.is_pointer_to( cursor.result_type, 'void')
2633 assert ( pvoid
2634 or parse.is_pointer_to( cursor.result_type, struct_name)
2635 ), (
2636 f'Function {name}(): result_type not void* or pointer to {struct_name}: {cursor.result_type.spelling}'
2637 )
2638 arg, n = parse.get_first_arg( tu, cursor)
2639 assert n == 1, f'should take exactly one arg: {cursor.spelling}()'
2640 assert parse.is_pointer_to( arg.cursor.type, struct_name), (
2641 f'arg0 is not pointer to {struct_name}: {cursor.spelling}(): {arg.cursor.spelling} {arg.name}')
2642
2643 for fnname, cursor, duplicate_type in constructor_fns:
2644 fnname2 = rename.ll_fn(fnname)
2645 if fnname2 == keep_name:
2646 jlib.log( 'not generating copy constructor with {keep_name=} because already used by a constructor.')
2647 break
2648 else:
2649 functions( keep_name)
2650 comment = f'Copy constructor using `{keep_name}()`.'
2651 out_h.write( '\n')
2652 out_h.write( f' /** {comment} */\n')
2653 out_h.write( f' FZ_FUNCTION {classname}(const {classname}& rhs);\n')
2654 out_h.write( '\n')
2655
2656 cast = ''
2657 if pvoid:
2658 # Need to cast the void* to the correct type.
2659 cast = f'(::{struct_name}*) '
2660
2661 out_cpp.write( f'/** {comment} */\n')
2662 out_cpp.write( f'FZ_FUNCTION {classname}::{classname}(const {classname}& rhs)\n')
2663 out_cpp.write( f': m_internal({cast}{rename.ll_fn(keep_name)}(rhs.m_internal))\n')
2664 out_cpp.write( '{\n')
2665
2666 # Write trace code.
2667 out_cpp.write( f' {trace_if}\n')
2668 out_cpp.write( f' if (s_trace_keepdrop) {{\n')
2669 out_cpp.write( f' std::cerr << __FILE__ << ":" << __LINE__ << ":" << __FUNCTION__ << "():"\n')
2670 out_cpp.write( f' << " have called {rename.ll_fn(keep_name)}(rhs.m_internal)\\n"\n')
2671 out_cpp.write( f' ;\n')
2672 out_cpp.write( f' }}\n')
2673 out_cpp.write( f' #endif\n')
2674
2675 if parse.has_refs( tu, struct_cursor.type):
2676 out_cpp.write(f' {refcheck_if}\n')
2677 out_cpp.write( ' if (s_check_refs)\n')
2678 out_cpp.write( ' {\n')
2679 out_cpp.write(f' s_{classname}_refs_check.add( this, __FILE__, __LINE__, __FUNCTION__);\n')
2680 out_cpp.write( ' }\n')
2681 out_cpp.write( ' #endif\n')
2682 out_cpp.write(num_instances(refcheck_if, +1, classname))
2683 out_cpp.write( '}\n')
2684 out_cpp.write( '\n')
2685
2686 # Make operator=().
2687 #
2688 comment = f'operator= using `{keep_name}()` and `{drop_name}()`.'
2689 out_h.write( f' /** {comment} */\n')
2690 out_h.write( f' FZ_FUNCTION {classname}& operator=(const {classname}& rhs);\n')
2691
2692 out_cpp.write( f'/* {comment} */\n')
2693 out_cpp.write( f'FZ_FUNCTION {classname}& {classname}::operator=(const {classname}& rhs)\n')
2694 out_cpp.write( '{\n')
2695 out_cpp.write( f' {trace_if}\n')
2696 out_cpp.write( f' if (s_trace_keepdrop) {{\n')
2697 out_cpp.write( f' std::cerr << __FILE__ << ":" << __LINE__ << ":" << __FUNCTION__ << "():"\n')
2698 out_cpp.write( f' << " calling {rename.ll_fn(drop_name)}(this->m_internal)"\n')
2699 out_cpp.write( f' << " and {rename.ll_fn(keep_name)}(rhs.m_internal)\\n"\n')
2700 out_cpp.write( f' ;\n')
2701 out_cpp.write( f' }}\n')
2702 out_cpp.write( f' #endif\n')
2703 out_cpp.write( f' {rename.ll_fn(drop_name)}(this->m_internal);\n')
2704 out_cpp.write( f' {rename.ll_fn(keep_name)}(rhs.m_internal);\n')
2705 if parse.has_refs( tu, struct_cursor.type):
2706 out_cpp.write(f' {refcheck_if}\n')
2707 out_cpp.write( ' if (s_check_refs)\n')
2708 out_cpp.write( ' {\n')
2709 out_cpp.write(f' s_{classname}_refs_check.remove( this, __FILE__, __LINE__, __FUNCTION__);\n')
2710 out_cpp.write( ' }\n')
2711 out_cpp.write( ' #endif\n')
2712 out_cpp.write( f' this->m_internal = {cast}rhs.m_internal;\n')
2713 if parse.has_refs( tu, struct_cursor.type):
2714 out_cpp.write(f' {refcheck_if}\n')
2715 out_cpp.write( ' if (s_check_refs)\n')
2716 out_cpp.write( ' {\n')
2717 out_cpp.write(f' s_{classname}_refs_check.add( this, __FILE__, __LINE__, __FUNCTION__);\n')
2718 out_cpp.write( ' }\n')
2719 out_cpp.write( ' #endif\n')
2720 out_cpp.write( f' return *this;\n')
2721 out_cpp.write( '}\n')
2722 out_cpp.write( '\n')
2723
2724 def function_name_implies_kept_references( fnname):
2725 '''
2726 Returns true if <fnname> implies the function would return kept
2727 reference(s).
2728 '''
2729 if fnname in (
2730 'pdf_page_write',
2731 'fz_decomp_image_from_stream',
2732 'fz_get_pixmap_from_image',
2733 ):
2734 return True
2735 for i in (
2736 'add',
2737 'convert',
2738 'copy',
2739 'create',
2740 'deep_copy',
2741 'find',
2742 'graft',
2743 'keep',
2744 'load',
2745 'new',
2746 'open',
2747 'parse',
2748 'read',
2749 ):
2750 if fnname.startswith(f'fz_{i}_') or fnname.startswith(f'pdf_{i}_'):
2751 if state.state_.show_details(fnname):
2752 jlib.log('Assuming that {fnname=} returns a kept reference.')
2753 return True
2754 return False
2755
2756
2757 def function_wrapper_class_aware_body(
2758 tu,
2759 fnname,
2760 out_cpp,
2761 struct_name,
2762 class_name,
2763 class_static,
2764 class_constructor,
2765 extras,
2766 struct_cursor,
2767 fn_cursor,
2768 return_cursor,
2769 wrap_return,
2770 refcheck_if,
2771 trace_if,
2772 ):
2773 '''
2774 Writes function or method body to <out_cpp> that calls a generated C++ wrapper
2775 function.
2776
2777 fnname:
2778 .
2779 out_cpp:
2780 .
2781 struct_name:
2782 If false, we write a class-aware wrapping function body. Otherwise name
2783 of struct such as 'fz_rect' and we write method body for the struct's
2784 wrapper class.
2785 class_name:
2786 class_static:
2787 If true, this is a static class method.
2788 class_constructor:
2789 If true, this is a constructor.
2790 extras:
2791 .
2792 struct_cursor:
2793 .
2794 fn_cursor:
2795 Cursor for the underlying MuPDF function.
2796 return_cursor:
2797 If not None, the cursor for definition of returned type.
2798 wrap_return:
2799 If 'pointer', the underlying function returns a pointer to a struct
2800 that we wrap.
2801
2802 If 'value' the underlying function returns, by value, a
2803 struct that we wrap, so we need to construct our wrapper from the
2804 address of this value.
2805
2806 Otherwise we don't wrap the returned value.
2807 '''
2808 verbose = state.state_.show_details( fnname)
2809 out_cpp.write( f'{{\n')
2810 return_void = (fn_cursor.result_type.spelling == 'void')
2811
2812 # Write trace code.
2813 out_cpp.write( f' {trace_if}\n')
2814 out_cpp.write( f' if (s_trace) {{\n')
2815 out_cpp.write( f' std::cerr << __FILE__ << ":" << __LINE__ << ":" << __FUNCTION__ << "():"\n')
2816 out_cpp.write( f' << " calling mupdf::{rename.ll_fn(fnname)}()\\n";\n')
2817 out_cpp.write( f' }}\n')
2818 out_cpp.write( f' #endif\n')
2819
2820 if fn_cursor.type.is_function_variadic():
2821 assert fnname == 'fz_warn', f'{fnname=}'
2822 out_cpp.write( f' va_list ap;\n')
2823 out_cpp.write( f' va_start( ap, fmt);\n')
2824 out_cpp.write( f' {rename.ll_fn("fz_vwarn")}( fmt, ap);\n')
2825 out_cpp.write( f' va_end( ap);\n')
2826
2827 elif class_constructor or not struct_name:
2828 # This code can generate a class method, but we choose to not use this,
2829 # instead method body simply calls the class-aware function (see below).
2830 def get_keep_drop(arg):
2831 name = util.clip( arg.alt.type.spelling, 'struct ')
2832 if name.startswith('fz_'):
2833 prefix = 'fz'
2834 name = name[3:]
2835 elif name.startswith('pdf_'):
2836 prefix = 'pdf'
2837 name = name[4:]
2838 else:
2839 assert 0
2840 return rename.ll_fn(f'{prefix}_keep_{name}'), rename.ll_fn(f'{prefix}_drop_{name}')
2841
2842 # Handle wrapper-class out-params - need to drop .m_internal and set to
2843 # null.
2844 #
2845 # fixme: maybe instead simply call <arg.name>'s destructor directly?
2846 #
2847 for arg in parse.get_args( tu, fn_cursor):
2848 if arg.alt and arg.out_param:
2849 if parse.has_refs(tu, arg.alt.type):
2850 keep_fn, drop_fn = get_keep_drop(arg)
2851 out_cpp.write( f' /* Out-param {arg.name}.m_internal will be overwritten. */\n')
2852 out_cpp.write( f' {drop_fn}({arg.name}.m_internal);\n')
2853 out_cpp.write( f' {arg.name}.m_internal = nullptr;\n')
2854
2855 # Write function call.
2856 if class_constructor:
2857 if extras.pod:
2858 if extras.pod == 'inline':
2859 out_cpp.write( f' *(::{struct_name}*) &this->{parse.get_field0(struct_cursor.type).spelling} = ')
2860 else:
2861 out_cpp.write( f' this->m_internal = ')
2862 if fn_cursor.result_type.kind == state.clang.cindex.TypeKind.POINTER:
2863 out_cpp.write( f'*')
2864 else:
2865 out_cpp.write( f' this->m_internal = ')
2866 if fn_cursor.result_type.kind == state.clang.cindex.TypeKind.POINTER:
2867 pass
2868 else:
2869 assert 0, 'cannot handle underlying fn returning by value when not pod.'
2870 out_cpp.write( f'{rename.ll_fn(fnname)}(')
2871 elif wrap_return == 'value':
2872 out_cpp.write( f' {_make_top_level(return_cursor.spelling)} temp = mupdf::{rename.ll_fn(fnname)}(')
2873 elif wrap_return == 'pointer':
2874 out_cpp.write( f' {_make_top_level(return_cursor.spelling)}* temp = mupdf::{rename.ll_fn(fnname)}(')
2875 elif wrap_return == 'const pointer':
2876 out_cpp.write( f' const {_make_top_level(return_cursor.spelling)}* temp = mupdf::{rename.ll_fn(fnname)}(')
2877 elif return_void:
2878 out_cpp.write( f' mupdf::{rename.ll_fn(fnname)}(')
2879 else:
2880 out_cpp.write( f' auto ret = mupdf::{rename.ll_fn(fnname)}(')
2881
2882 have_used_this = False
2883 sep = ''
2884 for arg in parse.get_args( tu, fn_cursor):
2885 arg_classname = class_name
2886 if class_static or class_constructor:
2887 arg_classname = None
2888 out_cpp.write( sep)
2889 have_used_this = write_call_arg(
2890 tu,
2891 arg,
2892 arg_classname,
2893 have_used_this,
2894 out_cpp,
2895 state.state_.show_details(fnname),
2896 )
2897 sep = ', '
2898 out_cpp.write( f');\n')
2899
2900 if state.state_.show_details(fnname):
2901 jlib.log('{=wrap_return}')
2902 refcounted_return = False
2903 if wrap_return in ('pointer', 'const pointer') and parse.has_refs( tu, return_cursor.type):
2904 refcounted_return = True
2905 refcounted_return_struct_cursor = return_cursor
2906 elif class_constructor and parse.has_refs( tu, struct_cursor.type):
2907 refcounted_return = True
2908 refcounted_return_struct_cursor = struct_cursor
2909
2910 if refcounted_return:
2911 # This MuPDF function returns pointer to a struct which uses reference
2912 # counting. If the function returns a borrowed reference, we need
2913 # to increment its reference count before passing it to our wrapper
2914 # class's constructor.
2915 #
2916 #jlib.log('Function returns pointer to {return_cursor=}')
2917 return_struct_name = util.clip( refcounted_return_struct_cursor.spelling, 'struct ')
2918 if return_struct_name.startswith('fz_'):
2919 prefix = 'fz_'
2920 elif return_struct_name.startswith('pdf_'):
2921 prefix = 'pdf_'
2922 else:
2923 prefix = None
2924 if state.state_.show_details(fnname):
2925 jlib.log('{=prefix}')
2926 if prefix:
2927 if function_name_implies_kept_references( fnname):
2928 pass
2929 #out_cpp.write( f' /* We assume that {fnname} returns a kept reference. */\n')
2930 else:
2931 if state.state_.show_details(fnname):
2932 jlib.log('{=classname fnname constructor} Assuming that {fnname=} returns a borrowed reference.')
2933 # This function returns a borrowed reference.
2934 suffix = return_struct_name[ len(prefix):]
2935 keep_fn = f'{prefix}keep_{suffix}'
2936 #jlib.log('Function assumed to return borrowed reference: {fnname=} => {return_struct_name=} {keep_fn=}')
2937 #out_cpp.write( f' /* We assume that {fnname} returns a borrowed reference. */\n')
2938 if class_constructor:
2939 out_cpp.write( f' {rename.ll_fn(keep_fn)}(this->m_internal);\n')
2940 else:
2941 out_cpp.write( f' {rename.ll_fn(keep_fn)}(temp);\n')
2942
2943 if wrap_return == 'value':
2944 out_cpp.write( f' auto ret = {rename.class_(return_cursor.spelling)}(&temp);\n')
2945 elif wrap_return in ('pointer', 'const pointer'):
2946 out_cpp.write( f' auto ret = {rename.class_(return_cursor.spelling)}(temp);\n')
2947
2948 # Handle wrapper-class out-params - need to keep arg.m_internal if
2949 # fnname implies it will be a borrowed reference.
2950 for arg in parse.get_args( tu, fn_cursor):
2951 if arg.alt and arg.out_param:
2952 if parse.has_refs(tu, arg.alt.type):
2953 if function_name_implies_kept_references( fnname):
2954 out_cpp.write( f' /* We assume that out-param {arg.name}.m_internal is a kept reference. */\n')
2955 else:
2956 keep_fn, drop_fn = get_keep_drop(arg)
2957 out_cpp.write( f' /* We assume that out-param {arg.name}.m_internal is a borrowed reference. */\n')
2958 out_cpp.write( f' {keep_fn}({arg.name}.m_internal);\n')
2959 else:
2960 # Class method simply calls the class-aware function, which will have
2961 # been generated elsewhere.
2962 out_cpp.write( ' ')
2963 if not return_void:
2964 out_cpp.write( 'auto ret = ')
2965
2966 out_cpp.write( f'mupdf::{rename.fn(fnname)}(')
2967 sep = ''
2968 for i, arg in enumerate( parse.get_args( tu, fn_cursor)):
2969 out_cpp.write( sep)
2970 if i==0 and not class_static:
2971 out_cpp.write( '*this')
2972 else:
2973 out_cpp.write( f'{arg.name}')
2974 sep = ', '
2975
2976 out_cpp.write( ');\n')
2977
2978 if struct_name and not class_static:
2979 if parse.has_refs( tu, struct_cursor.type):
2980 # Write code that does runtime checking of reference counts.
2981 out_cpp.write( f' {refcheck_if}\n')
2982 out_cpp.write( f' if (s_check_refs)\n')
2983 out_cpp.write( f' {{\n')
2984 if class_constructor:
2985 out_cpp.write( f' s_{class_name}_refs_check.add( this, __FILE__, __LINE__, __FUNCTION__);\n')
2986 else:
2987 out_cpp.write( f' s_{class_name}_refs_check.check( this, __FILE__, __LINE__, __FUNCTION__);\n')
2988 out_cpp.write( f' }}\n')
2989 out_cpp.write( f' #endif\n')
2990
2991 if class_constructor:
2992 out_cpp.write(num_instances(refcheck_if, +1, class_name))
2993
2994 if not return_void and not class_constructor:
2995 out_cpp.write( f' return ret;\n')
2996
2997 out_cpp.write( f'}}\n')
2998 out_cpp.write( f'\n')
2999
3000
3001 def function_wrapper_class_aware(
3002 tu,
3003 register_fn_use,
3004 fnname,
3005 out_h,
3006 out_cpp,
3007 struct_name,
3008 class_name,
3009 fn_cursor,
3010 refcheck_if,
3011 trace_if,
3012 class_static=False,
3013 class_constructor=False,
3014 extras=None,
3015 struct_cursor=None,
3016 duplicate_type=None,
3017 generated=None,
3018 debug=None,
3019 ):
3020 '''
3021 Writes a function or class method that calls <fnname>.
3022
3023 Also appends python and C# code to generated.swig_python and
3024 generated.swig_csharp if <generated> is not None.
3025
3026 tu
3027 .
3028 register_fn_use
3029 Callback to keep track of what fz_*() fns have been used.
3030 fnname
3031 Name of fz_*() fn to wrap, e.g. fz_concat.
3032 out_h
3033 out_cpp
3034 Where to write generated code.
3035 struct_name
3036 If false, we generate class-aware wrapping function. Otherwise name
3037 of struct such as 'fz_rect' and we create a method in the struct's
3038 wrapper class.
3039 class_name
3040 Ignored if struct_name is false.
3041
3042 Name of wrapper class, e.g. 'Rect'.
3043 class_static
3044 Ignored if struct_name is false.
3045
3046 If true, we generate a static method.
3047
3048 Otherwise we generate a normal class method, where first arg that
3049 is type <struct_name> is omitted from the generated method's
3050 prototype; in the implementation we use <this>.
3051 class_constructor
3052 If true, we write a constructor.
3053 extras
3054 None or ClassExtras instance.
3055 Only used if <constructor> is true.
3056 struct_cursor
3057 None or cursor for the struct definition.
3058 Only used if <constructor> is true.
3059 duplicate_type:
3060 If true, we have already generated a method with the same args, so
3061 this generated method will be commented-out.
3062 generated:
3063 If not None and there are one or more out-params, we write
3064 python code to generated.swig_python that overrides the default
3065 SWIG-generated method to call our *_outparams_fn() alternative.
3066 debug
3067 Show extra diagnostics.
3068 '''
3069 verbose = state.state_.show_details( fnname)
3070 if fn_cursor and fn_cursor.type.is_function_variadic() and fnname != 'fz_warn':
3071 jlib.log( 'Not writing class-aware wrapper because variadic: {fnname=}', 1)
3072 return
3073 if verbose:
3074 jlib.log( 'Writing class-aware wrapper for {fnname=}')
3075 if struct_name:
3076 assert fnname not in state.omit_methods, jlib.log_text( '{=fnname}')
3077 if debug:
3078 jlib.log( '{class_name=} {fnname=}')
3079 assert fnname.startswith( ('fz_', 'pdf_'))
3080 if not fn_cursor:
3081 fn_cursor = state.state_.find_function( tu, fnname, method=True)
3082 if not fn_cursor:
3083 jlib.log( '*** ignoring {fnname=}')
3084 return
3085
3086 if fnname.endswith('_drop'):
3087 # E.g. fz_concat_push_drop() is not safe (or necessary) for us because
3088 # we need to manage reference counts ourselves.
3089 #jlib.log('Ignoring because ends with "_drop": {fnname}')
3090 return
3091
3092 if struct_name:
3093 methodname = rename.method( struct_name, fnname)
3094 else:
3095 methodname = rename.fn( fnname)
3096
3097 if verbose:
3098 jlib.log( 'Writing class-aware wrapper for {fnname=}')
3099 # Construct prototype fnname(args).
3100 #
3101 if class_constructor:
3102 assert struct_name
3103 decl_h = f'{class_name}('
3104 decl_cpp = f'{class_name}('
3105 else:
3106 decl_h = f'{methodname}('
3107 decl_cpp = f'{methodname}('
3108 have_used_this = False
3109 num_out_params = 0
3110 num_class_wrapper_params = 0
3111 comma = ''
3112 this_is_const = False
3113 debug = state.state_.show_details( fnname)
3114
3115 for arg in parse.get_args( tu, fn_cursor):
3116 if debug:
3117 jlib.log( 'Looking at {struct_name=} {fnname=} {fnname_wrapper} {arg=}', 1)
3118 decl_h += comma
3119 decl_cpp += comma
3120 if arg.out_param:
3121 num_out_params += 1
3122 if arg.alt:
3123 # This parameter is a pointer to a struct that we wrap.
3124 num_class_wrapper_params += 1
3125 arg_extras = classes.classextras.get( tu, arg.alt.type.spelling)
3126 assert arg_extras, jlib.log_text( '{=structname fnname arg.alt.type.spelling}')
3127 const = ''
3128 if not arg.out_param and (not arg_extras.pod or arg.cursor.type.kind != state.clang.cindex.TypeKind.POINTER):
3129 const = 'const '
3130
3131 if (1
3132 and struct_name
3133 and not class_static
3134 and not class_constructor
3135 and rename.class_(util.clip( arg.alt.type.spelling, 'struct ')) == class_name
3136 and not have_used_this
3137 ):
3138 assert not arg.out_param
3139 # Omit this arg from the method's prototype - we'll use <this>
3140 # when calling the underlying fz_ function.
3141 have_used_this = True
3142 if not arg_extras.pod:
3143 this_is_const = const
3144 continue
3145
3146 if arg_extras.pod == 'none':
3147 jlib.log( 'Not wrapping because {arg=} wrapper has {extras.pod=}', 1)
3148 return
3149 text = f'{const}{rename.class_(arg.alt.type.spelling)}& {arg.name}'
3150 decl_h += text
3151 decl_cpp += text
3152 else:
3153 jlib.logx( '{arg.spelling=}')
3154 decl_text = declaration_text( arg.cursor.type, arg.name)
3155 decl_h += decl_text
3156 decl_cpp += decl_text
3157 comma = ', '
3158
3159 if fn_cursor.type.is_function_variadic():
3160 decl_h += f'{comma}...'
3161 decl_cpp += f'{comma}...'
3162
3163 decl_h += ')'
3164 decl_cpp += ')'
3165 if this_is_const:
3166 decl_h += ' const'
3167 decl_cpp += ' const'
3168
3169 if verbose:
3170 jlib.log( '{=struct_name class_constructor}')
3171 if class_constructor:
3172 comment = f'Constructor using `{fnname}()`.'
3173 else:
3174 comment = make_wrapper_comment(
3175 tu,
3176 fn_cursor,
3177 fnname,
3178 methodname,
3179 indent=' ',
3180 is_method=bool(struct_name),
3181 is_low_level=False,
3182 )
3183
3184 if struct_name and not class_static and not class_constructor:
3185 assert have_used_this, f'error: wrapper for {struct_name}: {fnname}() is not useful - does not have a {struct_name} arg.'
3186
3187 if struct_name and not duplicate_type:
3188 register_fn_use( fnname)
3189
3190 # If this is true, we explicitly construct a temporary from what the
3191 # wrapped function returns.
3192 #
3193 wrap_return = None
3194
3195 warning_not_copyable = False
3196 warning_no_raw_constructor = False
3197
3198 # Figure out return type for our generated function/method.
3199 #
3200 if verbose:
3201 jlib.log( 'Looking at return type...')
3202 return_cursor = None
3203 return_type = None
3204 return_extras = None
3205 if class_constructor:
3206 assert struct_name
3207 fn_h = f'{decl_h}'
3208 fn_cpp = f'{class_name}::{decl_cpp}'
3209 else:
3210 fn_h = declaration_text( fn_cursor.result_type, decl_h)
3211 if verbose:
3212 jlib.log( '{fn_cursor.result_type=}')
3213 if struct_name:
3214 fn_cpp = declaration_text( fn_cursor.result_type, f'{class_name}::{decl_cpp}')
3215 else:
3216 fn_cpp = declaration_text( fn_cursor.result_type, f'{decl_cpp}')
3217
3218 # See whether we can convert return type to an instance of a wrapper
3219 # class.
3220 #
3221 if verbose:
3222 jlib.log( '{fn_cursor.result_type.kind=}')
3223 if fn_cursor.result_type.kind == state.clang.cindex.TypeKind.POINTER:
3224 # Function returns a pointer.
3225 t = state.get_name_canonical( fn_cursor.result_type.get_pointee())
3226 if verbose:
3227 jlib.log( '{t.spelling=}')
3228 return_cursor = parse.find_struct( tu, t.spelling, require_definition=False)
3229 if verbose:
3230 jlib.log( '{=t.spelling return_cursor}')
3231 if return_cursor:
3232 # Function returns a pointer to a struct.
3233 return_extras = classes.classextras.get( tu, return_cursor.spelling)
3234 if return_extras:
3235 # Function returns a pointer to a struct for which we
3236 # generate a class wrapper, so change return type to be an
3237 # instance of the class wrapper.
3238 return_type = rename.class_(return_cursor.spelling)
3239 if verbose:
3240 jlib.log( '{=return_type}')
3241 if 0 and (state.state_.show_details(return_cursor.type.spelling) or state.state_.show_details(struct_name)):
3242 jlib.log('{return_cursor.type.spelling=}'
3243 ' {return_cursor.spelling=}'
3244 ' {struct_name=} {return_extras.copyable=}'
3245 ' {return_extras.constructor_raw=}'
3246 )
3247 fn_h = f'{return_type} {decl_h}'
3248 if struct_name:
3249 fn_cpp = f'{return_type} {class_name}::{decl_cpp}'
3250 else:
3251 fn_cpp = f'{return_type} {decl_cpp}'
3252 if t.is_const_qualified():
3253 wrap_return = 'const pointer'
3254 else:
3255 wrap_return = 'pointer'
3256 else:
3257 return_pointee = fn_cursor.result_type.get_pointee()
3258 if 'char' in return_pointee.spelling:
3259 if function_name_implies_kept_references(fnname):
3260 # For now we just output a diagnostic, but eventually
3261 # we might make C++ wrappers return a std::string here,
3262 # free()-ing the char* before returning.
3263 jlib.log1( 'Function name implies kept reference and returns char*:'
3264 ' {fnname}(): {fn_cursor.result_type.spelling=}'
3265 ' -> {return_pointee.spelling=}.'
3266 )
3267
3268 if verbose:
3269 jlib.log( '{=warning_not_copyable warning_no_raw_constructor}')
3270 else:
3271 # The fz_*() function returns by value. See whether we can convert
3272 # its return type to an instance of a wrapper class.
3273 #
3274 # If so, we will use constructor that takes pointer to the fz_
3275 # struct. C++ doesn't allow us to use address of temporary, so we
3276 # generate code like this:
3277 #
3278 # fz_quad_s ret = mupdf_snap_selection(...);
3279 # return Quad(&ret);
3280 #
3281 t = state.get_name_canonical( fn_cursor.result_type)
3282
3283 # 2023-02-09: parse.find_struct() will actually find any definition,
3284 # and we now prefix Fitz headers with a typedef of size_t on Linux,
3285 # so we need to avoid calling parse.find_struct() unless `t` is for
3286 # a MuPDF type.
3287 #
3288 if t.spelling.startswith( ('fz_', 'pdf_')):
3289 return_cursor = parse.find_struct( tu, t.spelling)
3290 if return_cursor:
3291 tt = state.get_name_canonical( return_cursor.type)
3292 if tt.kind == state.clang.cindex.TypeKind.ENUM:
3293 # For now, we return this type directly with no wrapping.
3294 pass
3295 else:
3296 return_extras = classes.classextras.get( tu, return_cursor.spelling)
3297 return_type = rename.class_(return_cursor.type.spelling)
3298 fn_h = f'{return_type} {decl_h}'
3299 if struct_name:
3300 fn_cpp = f'{return_type} {class_name}::{decl_cpp}'
3301 else:
3302 fn_cpp = f'{return_type} {decl_cpp}'
3303 wrap_return = 'value'
3304
3305 if return_extras:
3306 if not return_extras.copyable:
3307 out_h.write(
3308 textwrap.indent(
3309 textwrap.dedent( f'''
3310 /* Class-aware wrapper for `{fnname}()`
3311 is not available because returned wrapper class for `{return_cursor.spelling}`
3312 is non-copyable. */
3313 '''
3314 ),
3315 ' ',
3316 )
3317 )
3318 if verbose:
3319 jlib.log( 'Not creating class-aware wrapper because returned wrapper class is non-copyable: {fnname=}.')
3320 return
3321 if not return_extras.constructor_raw:
3322 out_h.write(
3323 textwrap.indent(
3324 textwrap.dedent( f'''
3325 /* Class-aware wrapper for `{fnname}()`
3326 is not available because returned wrapper class for `{return_cursor.spelling}`
3327 does not have raw constructor. */
3328 '''
3329 ),
3330 ' ',
3331 )
3332 )
3333 if verbose:
3334 jlib.log( 'Not creating class-aware wrapper because returned wrapper class does not have raw constructor: {fnname=}.')
3335 return
3336
3337 out_h.write( '\n')
3338 out_h.write( f' /** {comment} */\n')
3339
3340 # Copy any comment (indented) into class definition above method
3341 # declaration.
3342 if fn_cursor.raw_comment:
3343 raw_comment = fn_cursor.raw_comment.replace('\r', '')
3344 for line in raw_comment.split( '\n'):
3345 out_h.write( f' {line}\n')
3346
3347 if duplicate_type:
3348 out_h.write( f' /* Disabled because same args as {duplicate_type}.\n')
3349
3350 out_h.write( f' FZ_FUNCTION {"static " if class_static else ""}{fn_h};\n')
3351
3352 if duplicate_type:
3353 out_h.write( f' */\n')
3354
3355 if not struct_name:
3356 # Use extra spacing between non-class functions. Class methods are
3357 # grouped together.
3358 out_cpp.write( f'\n')
3359
3360 out_cpp.write( f'/* {comment} */\n')
3361 if duplicate_type:
3362 out_cpp.write( f'/* Disabled because same args as {duplicate_type}.\n')
3363
3364 out_cpp.write( f'FZ_FUNCTION {fn_cpp}\n')
3365
3366 function_wrapper_class_aware_body(
3367 tu,
3368 fnname,
3369 out_cpp,
3370 struct_name,
3371 class_name,
3372 class_static,
3373 class_constructor,
3374 extras,
3375 struct_cursor,
3376 fn_cursor,
3377 return_cursor,
3378 wrap_return,
3379 refcheck_if,
3380 trace_if,
3381 )
3382
3383 if struct_name:
3384 if duplicate_type:
3385 out_cpp.write( f'*/\n')
3386
3387 # fixme: the test of `struct_name` means that we don't generate outparam override for
3388 # class-aware fns which don't have any struct/class args, e.g. fz_lookup_cjk_font().
3389 #
3390
3391 if generated and num_out_params:
3392 make_python_class_method_outparam_override(
3393 tu,
3394 fn_cursor,
3395 fnname,
3396 generated,
3397 struct_name,
3398 class_name,
3399 return_type,
3400 )
3401
3402
3403 def class_custom_method(
3404 tu,
3405 register_fn_use,
3406 struct_cursor,
3407 classname,
3408 extramethod,
3409 out_h,
3410 out_cpp,
3411 refcheck_if,
3412 trace_if,
3413 ):
3414 '''
3415 Writes custom method as specified by <extramethod>.
3416
3417 tu
3418 .
3419 register_fn_use
3420 Callable taking single <fnname> arg.
3421 struct_cursor
3422 Cursor for definition of MuPDF struct.
3423 classname
3424 Name of wrapper class for <struct_cursor>.
3425 extramethod
3426 An ExtraMethod or ExtraConstructor instance.
3427 out_h
3428 out_cpp
3429 Where to write generated code.
3430 '''
3431 assert isinstance( extramethod, ( classes.ExtraMethod, classes.ExtraConstructor)), f'{type(extramethod)}'
3432 is_constructor = False
3433 is_destructor = False
3434 is_begin_end = False
3435
3436 if extramethod.return_:
3437 name_args = extramethod.name_args
3438 return_space = f'{extramethod.return_} '
3439 comment = 'Custom method.'
3440 if name_args.startswith( 'begin(') or name_args.startswith( 'end('):
3441 is_begin_end = True
3442 elif extramethod.name_args == '~()':
3443 # Destructor.
3444 name_args = f'~{classname}{extramethod.name_args[1:]}'
3445 return_space = ''
3446 comment = 'Custom destructor.'
3447 is_destructor = True
3448 elif extramethod.name_args.startswith('operator '):
3449 name_args = extramethod.name_args
3450 comment = 'Custom operator.'
3451 return_space = ''
3452 else:
3453 # Constructor.
3454 assert extramethod.name_args.startswith( '('), f'bad constructor/destructor in {classname=}: {extramethod.name_args=}'
3455 name_args = f'{classname}{extramethod.name_args}'
3456 return_space = ''
3457 comment = 'Custom constructor.'
3458 is_constructor = True
3459
3460 out_h.write( f'\n')
3461 if extramethod.comment:
3462 for i, line in enumerate( extramethod.comment.strip().split('\n')):
3463 line = line.replace( '/* ', '/** ')
3464 out_h.write( f' {line}\n')
3465 else:
3466 out_h.write( f' /** {comment} */\n')
3467 out_h.write( f' FZ_FUNCTION {return_space}{name_args};\n')
3468
3469 out_cpp.write( f'/** {comment} */\n')
3470 # Remove any default arg values from <name_args>.
3471 name_args_no_defaults = re.sub('= *[^(][^),]*', '', name_args)
3472 if name_args_no_defaults != name_args:
3473 jlib.log('have changed {name_args=} to {name_args_no_defaults=}', 1)
3474 out_cpp.write( f'FZ_FUNCTION {return_space}{classname}::{name_args_no_defaults}')
3475
3476 body = textwrap.dedent(extramethod.body)
3477
3478 end = body.rfind('}')
3479 assert end >= 0
3480 out_cpp.write( body[:end])
3481
3482 if is_constructor and parse.has_refs( tu, struct_cursor.type):
3483 # Insert ref checking code into end of custom constructor body.
3484 out_cpp.write( f' {refcheck_if}\n')
3485 out_cpp.write( f' if (s_check_refs)\n')
3486 out_cpp.write( f' {{\n')
3487 out_cpp.write( f' s_{classname}_refs_check.add( this, __FILE__, __LINE__, __FUNCTION__);\n')
3488 out_cpp.write( f' }}\n')
3489 out_cpp.write( f' #endif\n')
3490 if is_constructor:
3491 out_cpp.write( num_instances(refcheck_if, +1, classname))
3492 if is_destructor:
3493 out_cpp.write( num_instances(refcheck_if, -1, classname))
3494
3495 out_cpp.write( body[end:])
3496
3497 out_cpp.write( f'\n')
3498
3499 if 1: # lgtm [py/constant-conditional-expression]
3500 # Register calls of all fz_* functions. Not necessarily helpful - we
3501 # might only be interested in calls of fz_* functions that are directly
3502 # available to uses of class.
3503 #
3504 for fnname in re.findall( '(mupdf::[a-zA-Z0-9_]+) *[(]', extramethod.body):
3505 fnname = util.clip( fnname, 'mupdf::')
3506 if not fnname.startswith( 'pdf_'):
3507 fnname = 'fz_' + fnname
3508 #log( 'registering use of {fnname} in extramethod {classname}::{name_args}')
3509 register_fn_use( fnname)
3510
3511 return is_constructor, is_destructor, is_begin_end
3512
3513
3514 def class_raw_constructor(
3515 tu,
3516 register_fn_use,
3517 classname,
3518 struct_cursor,
3519 struct_name,
3520 base_name,
3521 extras,
3522 constructor_fns,
3523 out_h,
3524 out_cpp,
3525 refcheck_if,
3526 trace_if,
3527 ):
3528 '''
3529 Create a raw constructor - a constructor taking a pointer to underlying
3530 struct. This raw constructor assumes that it already owns the pointer so it
3531 does not call fz_keep_*(); the class's destructor will call fz_drop_*().
3532 '''
3533 #jlib.log( 'Creating raw constructor {classname=} {struct_name=} {extras.pod=} {extras.constructor_raw=} {fnname=}')
3534 comment = f'/** Constructor using raw copy of pre-existing `::{struct_name}`. */'
3535 if extras.pod:
3536 constructor_decl = f'{classname}(const ::{struct_name}* internal)'
3537 else:
3538 constructor_decl = f'{classname}(::{struct_name}* internal)'
3539 out_h.write( '\n')
3540 out_h.write( f' {comment}\n')
3541 explicit = ''
3542 if parse.has_refs( tu, struct_cursor.type):
3543 # Don't allow implicit construction from low-level struct, because our
3544 # destructor will drop it without a prior balancing keep.
3545 explicit = f'explicit '
3546 out_h.write(
3547 f' /* This constructor is marked as `explicit` because wrapper classes do not\n'
3548 f' call `keep`in constructors, but do call `drop` in destructors. So\n'
3549 f' automatic construction from a {struct_name}* will generally cause an\n'
3550 f' unbalanced `drop` resulting in errors such as SEGV. */\n'
3551 )
3552 if extras.constructor_raw == 'default':
3553 out_h.write( f' FZ_FUNCTION {explicit}{classname}(::{struct_name}* internal=NULL);\n')
3554 else:
3555 out_h.write( f' FZ_FUNCTION {explicit}{constructor_decl};\n')
3556
3557 if extras.constructor_raw != 'declaration_only':
3558 out_cpp.write( f'FZ_FUNCTION {classname}::{constructor_decl}\n')
3559 if extras.pod == 'inline':
3560 pass
3561 elif extras.pod:
3562 out_cpp.write( ': m_internal(*internal)\n')
3563 else:
3564 out_cpp.write( ': m_internal(internal)\n')
3565 out_cpp.write( '{\n')
3566 if extras.pod == 'inline':
3567 assert struct_cursor, f'cannot form raw constructor for inline pod {classname} without cursor for underlying {struct_name}'
3568 out_cpp.write( f' assert( internal);\n')
3569 for c in parse.get_members(struct_cursor):
3570 if c.type.kind == state.clang.cindex.TypeKind.CONSTANTARRAY:
3571 out_cpp.write( f' memcpy(this->{c.spelling}, internal->{c.spelling}, sizeof(this->{c.spelling}));\n')
3572 else:
3573 out_cpp.write( f' this->{c.spelling} = internal->{c.spelling};\n')
3574 if parse.has_refs( tu, struct_cursor.type):
3575 out_cpp.write( f' {refcheck_if}\n')
3576 out_cpp.write( f' if (s_check_refs)\n')
3577 out_cpp.write( f' {{\n')
3578 out_cpp.write( f' s_{classname}_refs_check.add( this, __FILE__, __LINE__, __FUNCTION__);\n')
3579 out_cpp.write( f' }}\n')
3580 out_cpp.write( f' #endif\n')
3581
3582 out_cpp.write(num_instances(refcheck_if, +1, classname))
3583
3584 out_cpp.write( '}\n')
3585 out_cpp.write( '\n')
3586
3587 if extras.pod == 'inline':
3588 # Write second constructor that takes underlying struct by value.
3589 #
3590 assert not parse.has_refs( tu, struct_cursor.type)
3591 constructor_decl = f'{classname}(const ::{struct_name} internal)'
3592 out_h.write( '\n')
3593 out_h.write( f' {comment}\n')
3594 out_h.write( f' FZ_FUNCTION {constructor_decl};\n')
3595
3596 if extras.constructor_raw != 'declaration_only':
3597 out_cpp.write( f'FZ_FUNCTION {classname}::{constructor_decl}\n')
3598 out_cpp.write( '{\n')
3599 for c in parse.get_members(struct_cursor):
3600 if c.type.kind == state.clang.cindex.TypeKind.CONSTANTARRAY:
3601 out_cpp.write( f' memcpy(this->{c.spelling}, &internal.{c.spelling}, sizeof(this->{c.spelling}));\n')
3602 else:
3603 out_cpp.write( f' this->{c.spelling} = internal.{c.spelling};\n')
3604
3605 out_cpp.write(num_instances(refcheck_if, +1, classname))
3606 out_cpp.write( '}\n')
3607 out_cpp.write( '\n')
3608
3609 # Write accessor for inline state.state_.
3610 #
3611 for const in False, True:
3612 space_const = ' const' if const else ''
3613 const_space = 'const ' if const else ''
3614 out_h.write( '\n')
3615 out_h.write( f' /** Access as underlying struct. */\n')
3616 out_h.write( f' FZ_FUNCTION {const_space}::{struct_name}* internal(){space_const};\n')
3617 out_cpp.write( f'{comment}\n')
3618 out_cpp.write( f'FZ_FUNCTION {const_space}::{struct_name}* {classname}::internal(){space_const}\n')
3619 out_cpp.write( '{\n')
3620 field0 = parse.get_field0(struct_cursor.canonical).spelling
3621 out_cpp.write( f' auto ret = ({const_space}::{struct_name}*) &this->{field0};\n')
3622 if parse.has_refs( tu, struct_cursor.type):
3623 out_cpp.write( f' {refcheck_if}\n')
3624 out_cpp.write( f' if (s_check_refs)\n')
3625 out_cpp.write( f' {{\n')
3626 out_cpp.write( f' s_{classname}_refs_check.add( this, __FILE__, __LINE__, __FUNCTION__);\n')
3627 out_cpp.write( f' }}\n')
3628 out_cpp.write( f' #endif\n')
3629
3630 out_cpp.write( ' return ret;\n')
3631 out_cpp.write( '}\n')
3632 out_cpp.write( '\n')
3633
3634
3635
3636 def class_accessors(
3637 tu,
3638 register_fn_use,
3639 classname,
3640 struct_cursor,
3641 struct_name,
3642 extras,
3643 out_h,
3644 out_cpp,
3645 ):
3646 '''
3647 Writes accessor functions for member data.
3648 '''
3649 if not extras.pod:
3650 jlib.logx( 'creating accessor for non-pod class {classname=} wrapping {struct_name}')
3651
3652 n = 0
3653
3654 for cursor in parse.get_members(struct_cursor):
3655 n += 1
3656 #jlib.log( 'accessors: {cursor.spelling=} {cursor.type.spelling=}')
3657
3658 # We set this to fz_keep_<type>() function to call, if we return a
3659 # wrapper class constructed from raw pointer to underlying fz_* struct.
3660 keep_function = None
3661
3662 # Set <decl> to be prototype with %s where the name is, e.g. 'int
3663 # %s()'; later on we use python's % operator to replace the '%s'
3664 # with the name we want.
3665 #
3666 if cursor.type.kind == state.clang.cindex.TypeKind.POINTER:
3667 decl = 'const ' + declaration_text( cursor.type, '%s()')
3668 pointee_type = state.get_name_canonical( cursor.type.get_pointee()).spelling
3669 pointee_type = util.clip( pointee_type, 'const ')
3670 pointee_type = util.clip( pointee_type, 'struct ')
3671 #if 'fz_' in pointee_type:
3672 # jlib.log( '{pointee_type=}')
3673 # We don't attempt to make accessors to function pointers.
3674 if state.get_name_canonical( cursor.type.get_pointee()).kind == state.clang.cindex.TypeKind.FUNCTIONPROTO:
3675 jlib.logx( 'ignoring {cursor.spelling=} because pointer to FUNCTIONPROTO')
3676 continue
3677 elif pointee_type.startswith( ('fz_', 'pdf_')):
3678 extras2 = parse.get_fz_extras( tu, pointee_type)
3679 if extras2:
3680 # Make this accessor return an instance of the wrapping
3681 # class by value.
3682 #
3683 classname2 = rename.class_( pointee_type)
3684 decl = f'{classname2} %s()'
3685
3686 # If there's a fz_keep_() function, we must call it on the
3687 # internal data before returning the wrapper class.
3688 pointee_type_base = util.clip( pointee_type, ('fz_', 'pdf_'))
3689 keep_function = f'{parse.prefix(pointee_type)}keep_{pointee_type_base}'
3690 if state.state_.find_function( tu, keep_function, method=False):
3691 jlib.logx( 'using {keep_function=}')
3692 else:
3693 jlib.log( 'cannot find {keep_function=}')
3694 keep_function = None
3695 elif cursor.type.kind == state.clang.cindex.TypeKind.FUNCTIONPROTO:
3696 jlib.log( 'ignoring {cursor.spelling=} because FUNCTIONPROTO')
3697 continue
3698 else:
3699 if 0 and extras.pod: # lgtm [py/unreachable-statement]
3700 # Return reference so caller can modify. Unfortunately SWIG
3701 # converts non-const references to pointers, so generated
3702 # python isn't useful.
3703 fn_args = '& %s()'
3704 else:
3705 fn_args = '%s()'
3706 if cursor.type.get_array_size() >= 0:
3707 if 0: # lgtm [py/unreachable-statement]
3708 # Return reference to the array; we need to put fn name
3709 # and args inside (...) to allow the declaration syntax
3710 # to work - we end up with things like:
3711 #
3712 # char (& media_class())[64];
3713 #
3714 # Unfortunately SWIG doesn't seem to be able to cope
3715 # with this.
3716 decl = declaration_text( cursor.type, '(%s)' % fn_args)
3717 else:
3718 # Return pointer to the first element of the array, so
3719 # that SWIG can cope.
3720 fn_args = '* %s()'
3721 type_ = cursor.type.get_array_element_type()
3722 decl = declaration_text( type_, fn_args)
3723 else:
3724 if ( cursor.type.kind == state.clang.cindex.TypeKind.TYPEDEF
3725 and cursor.type.get_typedef_name() in ('uint8_t', 'int8_t')
3726 ):
3727 # Don't let accessor return uint8_t because SWIG thinks it
3728 # is a char*, leading to memory errors. Instead return int.
3729 #
3730 jlib.logx('Changing from {cursor.type.get_typedef_name()=} {cursor.type=} to int')
3731 decl = f'int {fn_args}'
3732 else:
3733 decl = declaration_text( cursor.type, fn_args)
3734
3735 # todo: if return type is uint8_t or int8_t, maybe return as <int>
3736 # so SWIG doesn't think it is a string? This would fix errors with
3737 # fz_image::n and fz_image::bpc.
3738 out_h.write( f' FZ_FUNCTION {decl % cursor.spelling};\n')
3739 out_cpp.write( 'FZ_FUNCTION %s\n' % (decl % ( f'{classname}::{cursor.spelling}')))
3740 out_cpp.write( '{\n')
3741 if keep_function:
3742 out_cpp.write( f' {rename.ll_fn(keep_function)}(m_internal->{cursor.spelling});\n')
3743 out_cpp.write( f' return ({classname2}) m_internal->{cursor.spelling};\n')
3744 else:
3745 if extras.pod:
3746 out_cpp.write( f' return m_internal.{cursor.spelling};\n')
3747 else:
3748 out_cpp.write( f' return m_internal->{cursor.spelling};\n')
3749 out_cpp.write( '}\n')
3750 out_cpp.write( '\n')
3751 assert n, f'No fields found for {struct_cursor.spelling}.'
3752
3753
3754
3755 def class_destructor(
3756 tu,
3757 register_fn_use,
3758 classname,
3759 extras,
3760 struct_cursor,
3761 destructor_fns,
3762 out_h,
3763 out_cpp,
3764 refcheck_if,
3765 trace_if,
3766 ):
3767 if len(destructor_fns) > 1:
3768 # Use function with shortest name.
3769 if 0: # lgtm [py/unreachable-statement]
3770 jlib.log( 'Multiple possible destructor fns for {classname=}')
3771 for fnname, cursor in destructor_fns:
3772 jlib.log( ' {fnname=} {cursor.spelling=}')
3773 shortest = None
3774 for i in destructor_fns:
3775 if shortest is None or len(i[0]) < len(shortest[0]):
3776 shortest = i
3777 #jlib.log( 'Using: {shortest[0]=}')
3778 destructor_fns = [shortest]
3779 if len(destructor_fns):
3780 fnname, cursor = destructor_fns[0]
3781 register_fn_use( cursor.spelling)
3782 out_h.write( f' /** Destructor using {cursor.spelling}(). */\n')
3783 out_h.write( f' FZ_FUNCTION ~{classname}();\n')
3784
3785 out_cpp.write( f'FZ_FUNCTION {classname}::~{classname}()\n')
3786 out_cpp.write( '{\n')
3787 out_cpp.write( f' {rename.ll_fn(fnname)}(m_internal);\n')
3788 if parse.has_refs( tu, struct_cursor.type):
3789 out_cpp.write( f' {refcheck_if}\n')
3790 out_cpp.write( f' if (s_check_refs)\n')
3791 out_cpp.write( ' {\n')
3792 out_cpp.write( f' s_{classname}_refs_check.remove( this, __FILE__, __LINE__, __FUNCTION__);\n')
3793 out_cpp.write( ' }\n')
3794 out_cpp.write( f' #endif\n')
3795
3796 out_cpp.write(num_instances(refcheck_if, -1, classname))
3797
3798 out_cpp.write( '}\n')
3799 out_cpp.write( '\n')
3800 else:
3801 out_h.write(f' {refcheck_if}\n')
3802 out_h.write(f' /** Destructor only decrements s_num_instances. */\n')
3803 out_h.write(f' FZ_FUNCTION ~{classname}();\n')
3804 out_h.write( ' #else\n')
3805 out_h.write( ' /** We use default destructor. */\n')
3806 out_h.write( ' #endif\n')
3807
3808 out_cpp.write( f'{refcheck_if}\n')
3809 out_cpp.write( f'FZ_FUNCTION {classname}::~{classname}()\n')
3810 out_cpp.write( '{\n')
3811 out_cpp.write(num_instances(refcheck_if, -1, classname))
3812 out_cpp.write( '}\n')
3813 out_cpp.write( '#endif\n')
3814 out_cpp.write( '\n')
3815
3816
3817
3818 def pod_class_members(
3819 tu,
3820 classname,
3821 struct_cursor,
3822 struct_name,
3823 extras,
3824 out_h,
3825 out_cpp,
3826 ):
3827 '''
3828 Writes code for wrapper class's to_string() member function.
3829 '''
3830 out_h.write( f'\n')
3831 out_h.write( f' /** Returns string containing our members, labelled and inside (...), using operator<<. */\n')
3832 out_h.write( f' FZ_FUNCTION std::string to_string();\n')
3833
3834 out_h.write( f'\n')
3835 out_h.write( f' /** Comparison method. */\n')
3836 out_h.write( f' FZ_FUNCTION bool operator==(const {classname}& rhs);\n')
3837
3838 out_h.write( f'\n')
3839 out_h.write( f' /** Comparison method. */\n')
3840 out_h.write( f' FZ_FUNCTION bool operator!=(const {classname}& rhs);\n')
3841
3842 out_cpp.write( f'FZ_FUNCTION std::string {classname}::to_string()\n')
3843 out_cpp.write( f'{{\n')
3844 out_cpp.write( f' std::ostringstream buffer;\n')
3845 out_cpp.write( f' buffer << *this;\n')
3846 out_cpp.write( f' return buffer.str();\n')
3847 out_cpp.write( f'}}\n')
3848 out_cpp.write( f'\n')
3849
3850 out_cpp.write( f'FZ_FUNCTION bool {classname}::operator==(const {classname}& rhs)\n')
3851 out_cpp.write( f'{{\n')
3852 out_cpp.write( f' return ::operator==( *this, rhs);\n')
3853 out_cpp.write( f'}}\n')
3854 out_cpp.write( f'\n')
3855
3856 out_cpp.write( f'FZ_FUNCTION bool {classname}::operator!=(const {classname}& rhs)\n')
3857 out_cpp.write( f'{{\n')
3858 out_cpp.write( f' return ::operator!=( *this, rhs);\n')
3859 out_cpp.write( f'}}\n')
3860 out_cpp.write( f'\n')
3861
3862
3863 def struct_to_string_fns(
3864 tu,
3865 struct_cursor,
3866 struct_name,
3867 extras,
3868 out_h,
3869 out_cpp,
3870 ):
3871 '''
3872 Writes functions for text representation of struct/wrapper-class members.
3873 '''
3874 out_h.write( f'\n')
3875 out_h.write( f'/** Returns string containing a {struct_name}\'s members, labelled and inside (...), using operator<<. */\n')
3876 out_h.write( f'FZ_FUNCTION std::string to_string_{struct_name}(const ::{struct_name}& s);\n')
3877
3878 out_h.write( f'\n')
3879 out_h.write( f'/** Returns string containing a {struct_name}\'s members, labelled and inside (...), using operator<<.\n')
3880 out_h.write( f'(Convenience overload). */\n')
3881 out_h.write( f'FZ_FUNCTION std::string to_string(const ::{struct_name}& s);\n')
3882
3883 out_cpp.write( f'\n')
3884 out_cpp.write( f'FZ_FUNCTION std::string to_string_{struct_name}(const ::{struct_name}& s)\n')
3885 out_cpp.write( f'{{\n')
3886 out_cpp.write( f' std::ostringstream buffer;\n')
3887 out_cpp.write( f' buffer << s;\n')
3888 out_cpp.write( f' return buffer.str();\n')
3889 out_cpp.write( f'}}\n')
3890
3891 out_cpp.write( f'\n')
3892 out_cpp.write( f'FZ_FUNCTION std::string to_string(const ::{struct_name}& s)\n')
3893 out_cpp.write( f'{{\n')
3894 out_cpp.write( f' return to_string_{struct_name}(s);\n')
3895 out_cpp.write( f'}}\n')
3896
3897
3898 def pod_struct_fns(
3899 tu,
3900 namespace,
3901 struct_cursor,
3902 struct_name,
3903 extras,
3904 out_h,
3905 out_cpp,
3906 ):
3907 '''
3908 Writes extra fns for POD structs - operator<<(), operator==(), operator!=().
3909 '''
3910 # Write operator<< functions for streaming text representation of C struct
3911 # members. We should be at top-level in out_h and out_cpp, i.e. not inside
3912 # 'namespace mupdf {...}'.
3913 out_h.write( f'\n')
3914 out_h.write( f'/** {struct_name}: writes members, labelled and inside (...), to a stream. */\n')
3915 out_h.write( f'FZ_FUNCTION std::ostream& operator<< (std::ostream& out, const ::{struct_name}& rhs);\n')
3916
3917 out_cpp.write( f'\n')
3918 out_cpp.write( f'FZ_FUNCTION std::ostream& operator<< (std::ostream& out, const ::{struct_name}& rhs)\n')
3919 out_cpp.write( f'{{\n')
3920 i = 0
3921 out_cpp.write( f' out\n')
3922 out_cpp.write( f' << "("\n');
3923 for cursor in parse.get_members(struct_cursor):
3924 out_cpp.write( f' << ');
3925 if i:
3926 out_cpp.write( f'" {cursor.spelling}="')
3927 else:
3928 out_cpp.write( f' "{cursor.spelling}="')
3929 out_cpp.write( f' << rhs.{cursor.spelling}\n')
3930 i += 1
3931 out_cpp.write( f' << ")"\n');
3932 out_cpp.write( f' ;\n')
3933 out_cpp.write( f' return out;\n')
3934 out_cpp.write( f'}}\n')
3935
3936 # Write comparison fns.
3937 out_h.write( f'\n')
3938 out_h.write( f'/** {struct_name}: comparison function. */\n')
3939 out_h.write( f'FZ_FUNCTION bool operator==( const ::{struct_name}& lhs, const ::{struct_name}& rhs);\n')
3940 out_h.write( f'\n')
3941 out_h.write( f'/** {struct_name}: comparison function. */\n')
3942 out_h.write( f'FZ_FUNCTION bool operator!=( const ::{struct_name}& lhs, const ::{struct_name}& rhs);\n')
3943
3944 out_cpp.write( f'\n')
3945 out_cpp.write( f'FZ_FUNCTION bool operator==( const ::{struct_name}& lhs, const ::{struct_name}& rhs)\n')
3946 out_cpp.write( f'{{\n')
3947 for cursor in parse.get_members(struct_cursor):
3948 out_cpp.write( f' if (lhs.{cursor.spelling} != rhs.{cursor.spelling}) return false;\n')
3949 out_cpp.write( f' return true;\n')
3950 out_cpp.write( f'}}\n')
3951 out_cpp.write( f'FZ_FUNCTION bool operator!=( const ::{struct_name}& lhs, const ::{struct_name}& rhs)\n')
3952 out_cpp.write( f'{{\n')
3953 out_cpp.write( f' return !(lhs == rhs);\n')
3954 out_cpp.write( f'}}\n')
3955
3956
3957 def pod_class_fns(
3958 tu,
3959 classname,
3960 struct_cursor,
3961 struct_name,
3962 extras,
3963 out_h,
3964 out_cpp,
3965 ):
3966 '''
3967 Writes extra fns for wrappers for POD structs - operator<<(), operator==(),
3968 operator!=().
3969 '''
3970 # Write functions for text representation of wrapper-class members. These
3971 # functions make use of the corresponding struct functions created by
3972 # struct_to_string_fns().
3973 #
3974 assert extras.pod != 'none'
3975 classname = f'mupdf::{classname}'
3976 out_h.write( f'\n')
3977 out_h.write( f'/** {classname}: writes underlying {struct_name}\'s members, labelled and inside (...), to a stream. */\n')
3978 out_h.write( f'FZ_FUNCTION std::ostream& operator<< (std::ostream& out, const {classname}& rhs);\n')
3979
3980 out_cpp.write( f'\n')
3981 out_cpp.write( f'FZ_FUNCTION std::ostream& operator<< (std::ostream& out, const {classname}& rhs)\n')
3982 out_cpp.write( f'{{\n')
3983 if extras.pod == 'inline':
3984 out_cpp.write( f' return out << *rhs.internal();\n')
3985 elif extras.pod:
3986 out_cpp.write( f' return out << rhs.m_internal;\n')
3987 else:
3988 out_cpp.write( f' return out << " " << *rhs.m_internal;\n')
3989 out_cpp.write( f'}}\n')
3990
3991 # Write comparison fns, using comparison of underlying MuPDF struct.
3992 out_h.write( f'\n')
3993 out_h.write( f'/** {classname}: comparison function. */\n')
3994 out_h.write( f'FZ_FUNCTION bool operator==( const {classname}& lhs, const {classname}& rhs);\n')
3995 out_h.write( f'\n')
3996 out_h.write( f'/** {classname}: comparison function. */\n')
3997 out_h.write( f'FZ_FUNCTION bool operator!=( const {classname}& lhs, const {classname}& rhs);\n')
3998
3999 out_cpp.write( f'\n')
4000 out_cpp.write( f'FZ_FUNCTION bool operator==( const {classname}& lhs, const {classname}& rhs)\n')
4001 out_cpp.write( f'{{\n')
4002 if extras.pod == 'inline':
4003 out_cpp.write( f' return *lhs.internal() == *rhs.internal();\n')
4004 else:
4005 out_cpp.write( f' return lhs.m_internal == rhs.m_internal;\n')
4006 out_cpp.write( f'}}\n')
4007
4008 out_cpp.write( f'\n')
4009 out_cpp.write( f'FZ_FUNCTION bool operator!=( const {classname}& lhs, const {classname}& rhs)\n')
4010 out_cpp.write( f'{{\n')
4011 if extras.pod == 'inline':
4012 out_cpp.write( f' return *lhs.internal() != *rhs.internal();\n')
4013 else:
4014 out_cpp.write( f' return lhs.m_internal != rhs.m_internal;\n')
4015 out_cpp.write( f'}}\n')
4016
4017
4018 def get_struct_fnptrs( cursor_struct, shallow_typedef_expansion=False, verbose=False):
4019 '''
4020 Yields (cursor, fnptr_type) for function-pointer members of struct defined
4021 at cusor, where <cursor> is the cursor of the member and <fntr_type> is the
4022 type.
4023
4024 cursor_struct:
4025 Cursor for definition of struct; this can be a typedef.
4026 shallow_typedef_expansion:
4027 If true, the returned <fnptr_type> has any top-level typedefs resolved
4028 so will be a clang.cindex.TypeKind.FUNCTIONPROTO, but typedefs within
4029 the function args are not resolved, e.g. they can be size_t. This can
4030 be useful when generating code that will be compiled on different
4031 platforms with differing definitions of size_t.
4032 '''
4033 if verbose:
4034 jlib.log('Looking for fnptrs in {cursor_struct.spelling=}')
4035 for cursor in parse.get_members(cursor_struct):
4036 t = cursor.type
4037 if verbose:
4038 jlib.log('{t.kind=} {cursor.spelling=}')
4039 if t.kind == state.clang.cindex.TypeKind.POINTER:
4040 t = cursor.type.get_pointee()
4041 if t.kind in (state.clang.cindex.TypeKind.TYPEDEF, state.clang.cindex.TypeKind.ELABORATED):
4042 t_cursor = t.get_declaration()
4043 t = t_cursor.underlying_typedef_type
4044 if t.kind == state.clang.cindex.TypeKind.FUNCTIONPROTO:
4045 if shallow_typedef_expansion:
4046 if verbose:
4047 jlib.log('Not calling state.get_name_canonical() for {t.spelling=}. {cursor.spelling=}.')
4048 else:
4049 tt = state.get_name_canonical( t)
4050 if verbose:
4051 jlib.log('{tt.spelling=}')
4052 if (0
4053 or 'struct (unnamed at ' in tt.spelling
4054 or 'unnamed struct at ' in tt.spelling
4055 ):
4056
4057 # This is clang giving an unhelpful name to an
4058 # anonymous struct.
4059 if verbose:
4060 jlib.log( 'Avoiding clang anonymous struct placeholder: {tt.spelling=}')
4061 else:
4062 t = tt
4063 if verbose:
4064 jlib.log('Yielding: {cursor.spelling=} {t.spelling=}')
4065 yield cursor, t
4066
4067
4068 def class_wrapper_virtual_fnptrs(
4069 tu,
4070 struct_cursor,
4071 struct_name,
4072 classname,
4073 extras,
4074 out_h,
4075 out_cpp,
4076 out_h_end,
4077 generated,
4078 refcheck_if,
4079 trace_if,
4080 ):
4081 '''
4082 Generate extra wrapper class if struct contains function pointers, for
4083 use as a SWIG Director class so that the function pointers can be made to
4084 effectively point to Python or C# code.
4085 '''
4086 if not extras.virtual_fnptrs:
4087 return
4088 verbose = state.state_.show_details( struct_name)
4089 generated.virtual_fnptrs.append( f'{classname}2')
4090
4091 self_ = extras.virtual_fnptrs.pop( 'self_')
4092 self_n = extras.virtual_fnptrs.pop( 'self_n', 1)
4093 alloc = extras.virtual_fnptrs.pop( 'alloc')
4094 free = extras.virtual_fnptrs.pop( 'free', None)
4095 comment = extras.virtual_fnptrs.pop( 'comment', None)
4096 assert not extras.virtual_fnptrs, f'Unused items in virtual_fnptrs: {extras.virtual_fnptrs}'
4097
4098 # Class definition beginning.
4099 #
4100 out_h.write( '\n')
4101 out_h.write( f'/** Wrapper class for struct {struct_name} with virtual fns for each fnptr; this is for use as a SWIG Director class. */\n')
4102 if comment:
4103 out_h.write(comment)
4104 out_h.write( f'struct {classname}2 : {classname}\n')
4105 out_h.write( '{\n')
4106
4107 out_cpp.write( f'/* Implementation of methods for `{classname}2`, virtual_fnptrs wrapper for `{struct_name}`). */\n')
4108 out_cpp.write( '\n')
4109
4110 def get_fnptrs( shallow_typedef_expansion=False):
4111 for i in get_struct_fnptrs( struct_cursor, shallow_typedef_expansion, verbose=verbose):
4112 yield i
4113
4114 # Constructor
4115 #
4116 out_h.write( '\n')
4117 out_h.write( ' /** == Constructor. */\n')
4118 out_h.write(f' FZ_FUNCTION {classname}2();\n')
4119 out_cpp.write('\n')
4120 out_cpp.write(f'FZ_FUNCTION {classname}2::{classname}2()\n')
4121 out_cpp.write( '{\n')
4122 alloc = [''] + alloc.split('\n')
4123 alloc = '\n '.join(alloc)
4124 out_cpp.write(f'{alloc}\n')
4125 out_cpp.write(f' {trace_if}\n')
4126 out_cpp.write(f' if (s_trace_director)\n')
4127 out_cpp.write( ' {\n')
4128 out_cpp.write(f' std::cerr << __FILE__ << ":" << __LINE__ << ":" << __FUNCTION__ << ": {classname}2::{classname}2(): this=" << this << "\\n";\n')
4129 if not extras.pod:
4130 out_cpp.write(f' std::cerr << __FILE__ << ":" << __LINE__ << ":" << __FUNCTION__ << ": {classname}2::{classname}2(): m_internal=" << m_internal << "\\n";\n')
4131 out_cpp.write(f' {classname}2* self = {self_("m_internal")};\n')
4132 out_cpp.write(f' std::cerr << __FILE__ << ":" << __LINE__ << ":" << __FUNCTION__ << ": {classname}2::{classname}2(): self=" << self << "\\n";\n')
4133 out_cpp.write(' }\n')
4134 out_cpp.write(' #endif\n')
4135 out_cpp.write( '}\n')
4136
4137 # Destructor. This needs to be virtual with an empty implementation,
4138 # because instances will generally be derived classes.
4139 out_h.write( '\n')
4140 out_h.write( ' /** == Destructor. */\n')
4141 out_h.write(f' FZ_FUNCTION virtual ~{classname}2();\n')
4142 out_cpp.write('\n')
4143 out_cpp.write(f'FZ_FUNCTION {classname}2::~{classname}2()\n')
4144 out_cpp.write( '{\n')
4145 out_cpp.write(f' {trace_if}\n')
4146 out_cpp.write(f' if (s_trace_director)\n')
4147 out_cpp.write( ' {\n')
4148 out_cpp.write(f' std::cerr << __FILE__ << ":" << __LINE__ << ":" << __FUNCTION__ << ": ~{classname}2(): this=" << this << "\\n";\n')
4149 if not extras.pod:
4150 out_cpp.write( f' std::cerr << __FILE__ << ":" << __LINE__ << ":" << __FUNCTION__ << ": ~{classname}2(): m_internal=" << m_internal << "\\n";\n')
4151 out_cpp.write( ' }\n')
4152 out_cpp.write(f' #endif\n')
4153 if free:
4154 out_cpp.write(f' {free}\n')
4155 out_cpp.write( '}\n')
4156
4157 def write(text):
4158 out_h.write(text)
4159 out_cpp.write(text)
4160
4161 # Define static callback for each fnptr. It's important that these
4162 # functions do not resolve function parameter typedefs such as size_t to
4163 # the underlying types such as long int, because:
4164 #
4165 # * Our generated code can be compiled on different machines where types
4166 # such as size_t can be typedef-ed differently.
4167 #
4168 # * Elsewhere, code that we generate will assign our static callback
4169 # functions to MuPDF's function pointers (which use things like size_t).
4170 #
4171 # * These assignments will fail if the types don't match exactly.
4172 #
4173 # For example fz_output has a member:
4174 # fz_output_write_fn *write;
4175 #
4176 # This typedef is:
4177 # void (fz_output_write_fn)(fz_context *ctx, void *state, const void *data, size_t n);
4178 #
4179 # We generate a static function called Output2_s_write() and we will be
4180 # setting a fz_output's write member to point to Output2_s_write(), which
4181 # only works if the types match exactly.
4182 #
4183 # So we need to resolve the outer 'typedef fz_output_write_fn', but not
4184 # the inner 'size_t' typedef for the <n> arg. This is slightly tricky with
4185 # clang-python - it provide a Type.get_canonical() method that resolves all
4186 # typedefs, but to resolve just one level of typedefs requires a bit more
4187 # work. See get_struct_fnptrs() for details.
4188 #
4189 # [Usually our generated code deliberately resolves typedefs such as size_t
4190 # to long int etc, because SWIG-generated code for size_t etc does not
4191 # always work properly due to SWIG having its own definitions of things
4192 # like size_t in Python/C#. But in this case the generated static function
4193 # is not seen by SWIG so it's ok to make it use size_t etc.]
4194 #
4195 for cursor, fnptr_type in get_fnptrs( shallow_typedef_expansion=True):
4196
4197 # Write static callback.
4198 return_type = _make_top_level(fnptr_type.get_result().spelling)
4199 out_cpp.write(f'/* Static callback, calls self->{cursor.spelling}(). */\n')
4200 out_cpp.write(f'static {return_type} {classname}2_s_{cursor.spelling}')
4201 out_cpp.write('(')
4202 sep = ''
4203 for i, arg_type in enumerate( fnptr_type.argument_types()):
4204 name = f'arg_{i}'
4205 out_cpp.write(sep)
4206 out_cpp.write( declaration_text( arg_type, name, expand_typedef=False))
4207 sep = ', '
4208 out_cpp.write(')')
4209 out_cpp.write('\n')
4210 out_cpp.write('{\n')
4211 self_expression = self_() if self_n is None else self_( f'arg_{self_n}')
4212 out_cpp.write(f' {classname}2* self = {self_expression};\n')
4213 out_cpp.write(f' {trace_if}\n')
4214 out_cpp.write(f' if (s_trace_director)\n')
4215 out_cpp.write( ' {\n')
4216 out_cpp.write(f' std::cerr << __FILE__ << ":" << __LINE__ << ":" << __FUNCTION__ << ": {classname}2_s_{cursor.spelling}(): arg_0=" << arg_0 << " arg_1=" << arg_1 << " self=" << self << "\\n";\n')
4217 out_cpp.write( ' }\n')
4218 out_cpp.write( ' #endif\n')
4219 out_cpp.write( ' {\n')
4220 out_cpp.write( ' char error_message[256] = "";\n')
4221 out_cpp.write( ' try\n')
4222 out_cpp.write( ' {\n')
4223 out_cpp.write(f' return self->{cursor.spelling}(')
4224 sep = ''
4225 for i, arg_type in enumerate( fnptr_type.argument_types()):
4226 if i == self_n:
4227 # This is the void* from which we found `self` so ignore
4228 # here. Note that we still pass the fz_context to the virtual
4229 # fn.
4230 continue
4231 name = f'arg_{i}'
4232 out_cpp.write( f'{sep}{name}')
4233 sep = ', '
4234 out_cpp.write(');\n')
4235 out_cpp.write(' }\n')
4236
4237 # todo: catch our different exception types and map to FZ_ERROR_*.
4238 out_cpp.write( ' catch (std::exception& e)\n')
4239 out_cpp.write( ' {\n')
4240 out_cpp.write(f' {trace_if}\n')
4241 out_cpp.write( ' if (s_trace_director)\n')
4242 out_cpp.write( ' {\n')
4243 out_cpp.write(f' std::cerr << __FILE__ << ":" << __LINE__ << ":" << __FUNCTION__ << ": {classname}2_s_{cursor.spelling}(): converting std::exception to fz_throw(): " << e.what() << "\\n";\n')
4244 out_cpp.write( ' }\n')
4245 out_cpp.write( ' #endif\n')
4246 out_cpp.write( ' fz_strlcpy(error_message, e.what(), sizeof(error_message));\n')
4247 out_cpp.write( ' }\n')
4248 out_cpp.write( ' /* We defer fz_throw() to here, to ensure that `std::exception& e` has been destructed. */\n')
4249 out_cpp.write( ' fz_throw(arg_0, FZ_ERROR_GENERIC, "%s", error_message);\n')
4250 if return_type != 'void':
4251 out_cpp.write(f' /* Keep compiler happy. */\n')
4252 out_cpp.write(f' {return_type} ret;\n')
4253 out_cpp.write(f' return ret;\n')
4254 out_cpp.write( ' }\n')
4255 out_cpp.write('}\n')
4256
4257 # Define use_virtual_<name>( bool use) method for each fnptr.
4258 #
4259 out_h.write(f'\n')
4260 # Using a Doxygen-style `/**` comment prefix here can break swig with
4261 # `Error: Syntax error in input(3).` if there are no following method
4262 # declarations.
4263 out_h.write(f' /** These methods set the function pointers in *m_internal\n')
4264 out_h.write(f' to point to internal callbacks that call our virtual methods. */\n')
4265 for cursor, fnptr_type in get_fnptrs():
4266 out_h.write(f' FZ_FUNCTION void use_virtual_{cursor.spelling}( bool use=true);\n')
4267 out_cpp.write(f'FZ_FUNCTION void {classname}2::use_virtual_{cursor.spelling}( bool use)\n')
4268 out_cpp.write( '{\n')
4269
4270 out_cpp.write(f' {trace_if}\n')
4271 out_cpp.write(f' if (s_trace_director)\n')
4272 out_cpp.write( ' {\n')
4273 out_cpp.write(f' std::cerr << __FILE__ << ":" << __LINE__ << ":" << __FUNCTION__ << ": {classname}2::use_virtual_{cursor.spelling}(): this=" << this << " use=" << use << "\\n";\n')
4274 out_cpp.write( ' }\n')
4275 out_cpp.write( ' #endif\n')
4276
4277 if extras.pod == 'inline':
4278 # Fnptr (in {classname}2) and virtual function (in {classname})
4279 # have same name, so we need qualify the fnptr with {classname} to
4280 # ensure we distinguish between the two.
4281 out_cpp.write(f' {classname}::{cursor.spelling} = (use) ? {classname}2_s_{cursor.spelling} : nullptr;\n')
4282 elif extras.pod:
4283 out_cpp.write(f' m_internal.{cursor.spelling} = (use) ? {classname}2_s_{cursor.spelling} : nullptr;\n')
4284 else:
4285 out_cpp.write(f' m_internal->{cursor.spelling} = (use) ? {classname}2_s_{cursor.spelling} : nullptr;\n')
4286 out_cpp.write( '}\n')
4287
4288 # Write virtual fn default implementations.
4289 #
4290 out_h.write(f'\n')
4291
4292 # Using a Doxygen-style `/**` comment prefix here can break swig with
4293 # `Error: Syntax error in input(3).` if there are no following method
4294 # declarations.
4295 out_h.write(f' /** Default virtual method implementations; these all throw an exception. */\n')
4296 for cursor, fnptr_type in get_fnptrs():
4297
4298 out_h.write(f' FZ_FUNCTION virtual {_make_top_level(fnptr_type.get_result().spelling)} {cursor.spelling}(')
4299 out_cpp.write(f'/* Default implementation of virtual method. */\n')
4300 out_cpp.write(f'FZ_FUNCTION {_make_top_level(fnptr_type.get_result().spelling)} {classname}2::{cursor.spelling}(')
4301 sep = ''
4302 for i, arg_type in enumerate( fnptr_type.argument_types()):
4303 if i == self_n:
4304 # This is the void* from which we found `self` so ignore
4305 # here. Note that we still pass the fz_context to the virtual
4306 # fn.
4307 continue
4308 name = f'arg_{i}'
4309 write(f'{sep}')
4310 decl_text = declaration_text(arg_type, name, verbose=0)
4311 write(decl_text)
4312 sep = ', '
4313 out_h.write( ');\n')
4314 out_cpp.write( ')\n')
4315 out_cpp.write( '{\n')
4316 out_cpp.write(f' std::cerr << "Unexpected call of unimplemented virtual_fnptrs fn {classname}2::{cursor.spelling}(): this=" << this << ".\\n";\n')
4317 out_cpp.write(f' throw std::runtime_error( "Unexpected call of unimplemented virtual_fnptrs fn {classname}2::{cursor.spelling}()");\n')
4318 out_cpp.write( '}\n')
4319
4320 out_h.write( '};\n')
4321
4322
4323 def class_wrapper(
4324 tu,
4325 register_fn_use,
4326 struct_cursor,
4327 struct_name,
4328 classname,
4329 extras,
4330 out_h,
4331 out_cpp,
4332 out_h_end,
4333 out_cpp2,
4334 out_h2,
4335 generated,
4336 refcheck_if,
4337 trace_if,
4338 ):
4339 '''
4340 Creates source for a class called <classname> that wraps <struct_name>,
4341 with methods that call selected fz_*() functions. Writes to out_h and
4342 out_cpp.
4343
4344 Created source is just the per-class code, e.g. it does not contain
4345 #include's.
4346
4347 Args:
4348 tu:
4349 Clang translation unit.
4350 struct_cursor:
4351 Cursor for struct to wrap.
4352 struct_name:
4353 Name of struct to wrap.
4354 classname:
4355 Name of wrapper class to create.
4356 out_h:
4357 Stream to which we write class definition.
4358 out_cpp:
4359 Stream to which we write method implementations.
4360 out_h_end:
4361 Stream for text that should be put at the end of the generated
4362 header text.
4363 generated:
4364 We write extra python and C# code to generated.out_swig_python and
4365 generated.out_swig_csharp for use in the swig .i file.
4366
4367 Returns (is_container, has_to_string). <is_container> is true if generated
4368 class has custom begin() and end() methods; <has_to_string> is true if we
4369 have created a to_string() method.
4370 '''
4371 assert extras, f'extras is None for {struct_name}'
4372 if extras.iterator_next:
4373 class_add_iterator( tu, struct_cursor, struct_name, classname, extras, refcheck_if, trace_if)
4374
4375 if extras.class_pre:
4376 out_h.write( textwrap.dedent( extras.class_pre))
4377
4378 base_name = util.clip( struct_name, ('fz_', 'pdf_'))
4379
4380 constructor_fns = class_find_constructor_fns( tu, classname, struct_name, base_name, extras)
4381 for fnname in extras.constructors_wrappers:
4382 cursor = state.state_.find_function( tu, fnname, method=True)
4383 assert cursor, f'No cursor for constructor wrapper fnname={fnname}'
4384 constructor_fns.append( (fnname, cursor, None))
4385
4386 destructor_fns = class_find_destructor_fns( tu, struct_name, base_name)
4387
4388 # Class definition beginning.
4389 #
4390 out_h.write( '\n')
4391 if extras.copyable:
4392 out_h.write( f'/** Wrapper class for struct `{struct_name}`. */\n')
4393 else:
4394 out_h.write( f'/** Wrapper class for struct `{struct_name}`. Not copyable or assignable. */\n')
4395 if struct_cursor.raw_comment:
4396 raw_comment = struct_cursor.raw_comment.replace('\r', '')
4397 out_h.write(raw_comment)
4398 if not raw_comment.endswith( '\n'):
4399 out_h.write( '\n')
4400 out_h.write( f'struct {classname}\n{{')
4401
4402 out_cpp.write( '\n')
4403 out_cpp.write( f'/* Implementation of methods for {classname} (wrapper for {struct_name}). */\n')
4404 out_cpp.write( '\n')
4405 refs = parse.has_refs( tu, struct_cursor.type)
4406 if refs:
4407 refs_name, refs_size = refs
4408 out_cpp.write( f'{refcheck_if}\n')
4409 if isinstance(refs_name, int):
4410 # <refs_name> is offset of .refs in the struct.
4411 allow_int_this = ', true /*allow_int_this*/' if struct_name == 'pdf_obj' else ''
4412 out_cpp.write( f'static RefsCheck<::{struct_name}, {classname}{allow_int_this}> s_{classname}_refs_check({refs_name}, {refs_size});\n')
4413 else:
4414 # <refs_name> is name of .refs in the struct.
4415 out_cpp.write( f'static RefsCheck<::{struct_name}, {classname}> s_{classname}_refs_check(offsetof(::{struct_name}, {refs_name}), {refs_size});\n')
4416 out_cpp.write( f'#endif\n')
4417 out_cpp.write( '\n')
4418
4419 # Trailing text in header, e.g. typedef for iterator.
4420 #
4421 if extras.class_top:
4422 # Strip leading blank line to avoid slightly odd-looking layout.
4423 text = util.clip( extras.class_top, '\n')
4424 text = textwrap.dedent( text)
4425 text = textwrap.indent( text, ' ')
4426 out_h.write( '\n')
4427 out_h.write( text)
4428
4429 # Constructors
4430 #
4431 num_constructors = 0
4432 have_created_default_constructor = False
4433
4434 if constructor_fns:
4435 out_h.write( '\n')
4436 out_h.write( ' /** == Constructors. */\n')
4437 num_constructors += len(constructor_fns)
4438 for fnname, cursor, duplicate_type in constructor_fns:
4439 # clang-6 appears not to be able to handle fn args that are themselves
4440 # function pointers, so for now we allow function_wrapper() to fail,
4441 # so we need to use temporary buffers, otherwise out_functions_h and
4442 # out_functions_cpp can get partial text written.
4443 #
4444 assert cursor, f'No cursor for constructor function. fnname={fnname} duplicate_type={duplicate_type}'
4445 temp_out_h = io.StringIO()
4446 temp_out_cpp = io.StringIO()
4447 if state.state_.show_details(fnname):
4448 jlib.log('Creating constructor for {=classname fnname}')
4449 if parse.get_first_arg( tu, cursor) == (None, 0):
4450 have_created_default_constructor = True
4451 try:
4452 function_wrapper_class_aware(
4453 tu,
4454 register_fn_use,
4455 fnname,
4456 temp_out_h,
4457 temp_out_cpp,
4458 struct_name,
4459 classname,
4460 cursor,
4461 refcheck_if,
4462 trace_if,
4463 class_static=False,
4464 class_constructor=True,
4465 extras=extras,
4466 struct_cursor=struct_cursor,
4467 duplicate_type=duplicate_type,
4468 )
4469 except Clang6FnArgsBug as e:
4470 jlib.log( 'Unable to wrap function {fnname} because: {e}')
4471 else:
4472 out_h.write( temp_out_h.getvalue())
4473 out_cpp.write( temp_out_cpp.getvalue())
4474
4475 # Custom constructors.
4476 #
4477 for extra_constructor in extras.constructors_extra:
4478 if extra_constructor.name_args == '()':
4479 have_created_default_constructor = True
4480 class_custom_method(
4481 tu,
4482 register_fn_use,
4483 struct_cursor,
4484 classname,
4485 extra_constructor,
4486 out_h,
4487 out_cpp,
4488 refcheck_if,
4489 trace_if,
4490 )
4491 num_constructors += 1
4492
4493 # Look for function that can be used by copy constructor and operator=.
4494 #
4495 if refs:
4496 assert extras.copyable != 'default', f'Non-POD class for {struct_name=} has refs, so must not use copyable="default"'
4497
4498 if not extras.pod and extras.copyable and extras.copyable != 'default':
4499 class_copy_constructor(
4500 tu,
4501 register_fn_use,
4502 struct_name,
4503 struct_cursor,
4504 base_name,
4505 classname,
4506 constructor_fns,
4507 out_h,
4508 out_cpp,
4509 refcheck_if,
4510 trace_if,
4511 )
4512 elif extras.copyable:
4513 out_h.write( '\n')
4514 out_h.write( ' /** We use default copy constructor and operator=. */\n')
4515
4516 if extras.constructor_default:
4517 if have_created_default_constructor:
4518 if 0:
4519 jlib.log( 'Not creating default constructor because default custom constructor. {struct_name=}')
4520 elif extras.constructor_raw == 'default':
4521 if 0:
4522 jlib.log( 'Not creating default constructor because default raw constructor. {struct_name=}')
4523 else:
4524 class_constructor_default(
4525 tu,
4526 struct_cursor,
4527 classname,
4528 extras,
4529 out_h,
4530 out_cpp,
4531 refcheck_if,
4532 trace_if,
4533 )
4534 num_constructors += 1
4535
4536 # Auto-add all methods that take <struct_name> as first param, but
4537 # skip methods that are already wrapped in extras.method_wrappers or
4538 # extras.methods_extra etc.
4539 #
4540 for fnname in parse.find_wrappable_function_with_arg0_type( tu, struct_name):
4541 if state.state_.show_details(fnname):
4542 jlib.log('{struct_name=}: looking at potential method wrapping {fnname=}')
4543 if fnname in extras.method_wrappers:
4544 #log( 'auto-detected fn already in {struct_name} method_wrappers: {fnname}')
4545 # Omit this function, because there is an extra method with the
4546 # same name. (We could probably include both as they will generally
4547 # have different args so overloading will distinguish them, but
4548 # extra methods are usually defined to be used in preference.)
4549 pass
4550 elif fnname.startswith( 'fz_new_draw_device'):
4551 # fz_new_draw_device*() functions take first arg fz_matrix, but
4552 # aren't really appropriate for the fz_matrix wrapper class.
4553 #
4554 pass
4555 elif isinstance( fnname, list):
4556 assert 0
4557 else:
4558 for extramethod in extras.methods_extra:
4559 if not extramethod.overload:
4560 if extramethod.name_args.startswith( f'{rename.method( struct_name, fnname)}('):
4561 jlib.log1( 'Omitting default method because same name as extramethod: {extramethod.name_args}')
4562 break
4563 else:
4564 #log( 'adding to extras.method_wrappers: {fnname}')
4565 extras.method_wrappers.append( fnname)
4566
4567 # Extra static methods.
4568 #
4569 if extras.method_wrappers_static:
4570 out_h.write( '\n')
4571 out_h.write( ' /* == Static methods. */\n')
4572 for fnname in extras.method_wrappers_static:
4573 function_wrapper_class_aware(
4574 tu,
4575 register_fn_use,
4576 fnname,
4577 out_h,
4578 out_cpp,
4579 struct_name,
4580 classname,
4581 fn_cursor=None,
4582 refcheck_if=refcheck_if,
4583 trace_if=trace_if,
4584 class_static=True,
4585 struct_cursor=struct_cursor,
4586 generated=generated,
4587 )
4588
4589 # Extra methods that wrap fz_*() fns.
4590 #
4591 if extras.method_wrappers or extras.methods_extra:
4592 out_h.write( '\n')
4593 out_h.write( ' /* == Methods. */')
4594 out_h.write( '\n')
4595 extras.method_wrappers.sort()
4596 for fnname in extras.method_wrappers:
4597 function_wrapper_class_aware(
4598 tu,
4599 register_fn_use,
4600 fnname,
4601 out_h,
4602 out_cpp,
4603 struct_name,
4604 classname,
4605 None, #fn_cursor
4606 refcheck_if,
4607 trace_if,
4608 struct_cursor=struct_cursor,
4609 generated=generated,
4610 debug=state.state_.show_details(fnname),
4611 )
4612
4613 # Custom methods.
4614 #
4615 is_container = 0
4616 custom_destructor = False
4617 for extramethod in extras.methods_extra:
4618 is_constructor, is_destructor, is_begin_end = class_custom_method(
4619 tu,
4620 register_fn_use,
4621 struct_cursor,
4622 classname,
4623 extramethod,
4624 out_h,
4625 out_cpp,
4626 refcheck_if,
4627 trace_if,
4628 )
4629 if is_constructor:
4630 num_constructors += 1
4631 if is_destructor:
4632 custom_destructor = True
4633 if is_begin_end:
4634 is_container += 1
4635
4636 assert is_container==0 or is_container==2, f'struct_name={struct_name} is_container={is_container}' # Should be begin()+end() or neither.
4637 if is_container:
4638 pass
4639 #jlib.log( 'Generated class has begin() and end(): {classname=}')
4640
4641 if num_constructors == 0 or extras.constructor_raw:
4642 if state.state_.show_details(struct_name):
4643 jlib.log('calling class_raw_constructor(). {struct_name=}')
4644 class_raw_constructor(
4645 tu,
4646 register_fn_use,
4647 classname,
4648 struct_cursor,
4649 struct_name,
4650 base_name,
4651 extras,
4652 constructor_fns,
4653 out_h,
4654 out_cpp,
4655 refcheck_if,
4656 trace_if,
4657 )
4658
4659 # Accessor methods to POD data.
4660 #
4661 if extras.accessors and extras.pod == 'inline':
4662 jlib.log( 'ignoring {extras.accessors=} for {struct_name=} because {extras.pod=}.')
4663 elif extras.accessors:
4664 out_h.write( f'\n')
4665 out_h.write( f' /* == Accessors to members of ::{struct_name} m_internal. */\n')
4666 out_h.write( '\n')
4667 class_accessors(
4668 tu,
4669 register_fn_use,
4670 classname,
4671 struct_cursor,
4672 struct_name,
4673 extras,
4674 out_h,
4675 out_cpp,
4676 )
4677
4678 # Destructor.
4679 #
4680 if not custom_destructor:
4681 out_h.write( f'\n')
4682 class_destructor(
4683 tu,
4684 register_fn_use,
4685 classname,
4686 extras,
4687 struct_cursor,
4688 destructor_fns,
4689 out_h,
4690 out_cpp,
4691 refcheck_if,
4692 trace_if,
4693 )
4694
4695 # If class has '{structname}* m_internal;', provide access to m_iternal as
4696 # an integer, for use by python etc, and provide `operator bool()`.
4697 if not extras.pod:
4698 class_custom_method(
4699 tu,
4700 register_fn_use,
4701 struct_cursor,
4702 classname,
4703 classes.ExtraMethod(
4704 'long long',
4705 'm_internal_value()',
4706 '''
4707 {
4708 return (uintptr_t) m_internal;
4709 }
4710 ''',
4711 '/** Return numerical value of .m_internal; helps with Python debugging. */',
4712 ),
4713 out_h,
4714 out_cpp,
4715 refcheck_if,
4716 trace_if,
4717 )
4718 class_custom_method(
4719 tu,
4720 register_fn_use,
4721 struct_cursor,
4722 classname,
4723 classes.ExtraMethod(
4724 '',
4725 'operator bool()',
4726 f'''
4727 {{
4728 {trace_if}
4729 if (s_trace)
4730 {{
4731 std::cerr << __FILE__ << ":" << __LINE__ << ":"
4732 << " {classname}::operator bool() called,"
4733 << " m_internal=" << m_internal << "."
4734 << "\\n";
4735 }}
4736 #endif
4737 return m_internal ? true : false;
4738 }}
4739 ''',
4740 '/** Return true iff `m_internal` is not null. */',
4741 ),
4742 out_h,
4743 out_cpp,
4744 refcheck_if,
4745 trace_if,
4746 )
4747
4748 # Class members.
4749 #
4750 out_h.write( '\n')
4751 out_h.write( ' /* == Member data. */\n')
4752 out_h.write( '\n')
4753 if extras.pod == 'none':
4754 pass
4755 elif extras.pod == 'inline':
4756 out_h.write( f' /* These members are the same as the members of ::{struct_name}. */\n')
4757 for c in parse.get_members(struct_cursor):
4758 out_h.write( f' {declaration_text(c.type, c.spelling)};\n')
4759 elif extras.pod:
4760 out_h.write( f' ::{struct_cursor.spelling} m_internal; /** Wrapped data is held by value. */\n')
4761 else:
4762 # Putting this double-asterix comment on same line as m_internal breaks
4763 # swig-4.02 with "Error: Syntax error in input(3).".
4764 out_h.write( f' /** Pointer to wrapped data. */\n')
4765 out_h.write( f' ::{struct_name}* m_internal;\n')
4766
4767 # Declare static `num_instances` variable.
4768 out_h.write( '\n')
4769 out_h.write(f' /* Ideally this would be in `{refcheck_if}...#endif`, but Swig will\n')
4770 out_h.write(f' generate code regardless so we always need to have this available. */\n')
4771 out_h.write(f' FZ_DATA static int s_num_instances;\n')
4772
4773 out_cpp.write(f'/* Ideally this would be in `{refcheck_if}...#endif`, but Swig will\n')
4774 out_cpp.write(f'generate code regardless so we always need to have this available. */\n')
4775 out_cpp.write(f'int {classname}::s_num_instances = 0;\n')
4776 out_cpp.write(f'\n')
4777
4778 # Make operator<< (std::ostream&, ...) for POD classes.
4779 #
4780 has_to_string = False
4781 if extras.pod and extras.pod != 'none':
4782 has_to_string = True
4783 pod_class_members(
4784 tu,
4785 classname,
4786 struct_cursor,
4787 struct_name,
4788 extras,
4789 out_h,
4790 out_cpp,
4791 )
4792
4793 # Trailing text in header, e.g. typedef for iterator.
4794 #
4795 if extras.class_bottom:
4796 out_h.write( textwrap.indent( textwrap.dedent( extras.class_bottom), ' '))
4797
4798 # Private copy constructor if not copyable.
4799 #
4800 if not extras.copyable:
4801 out_h.write( '\n')
4802 out_h.write( ' private:\n')
4803 out_h.write( '\n')
4804 out_h.write( ' /** This class is not copyable or assignable. */\n')
4805 out_h.write( f' {classname}(const {classname}& rhs);\n')
4806 out_h.write( f' {classname}& operator=(const {classname}& rhs);\n')
4807
4808 # Class definition end.
4809 #
4810 out_h.write( '};\n')
4811
4812 if extras.class_post:
4813 out_h_end.write( textwrap.dedent( extras.class_post))
4814
4815 if extras.extra_cpp:
4816 out_cpp.write( f'/* .extra_cpp for {struct_name}. */\n')
4817 out_cpp.write( textwrap.dedent( extras.extra_cpp))
4818
4819 class_wrapper_virtual_fnptrs(
4820 tu,
4821 struct_cursor,
4822 struct_name,
4823 classname,
4824 extras,
4825 out_h,
4826 out_cpp,
4827 out_h_end,
4828 generated,
4829 refcheck_if,
4830 trace_if,
4831 )
4832
4833 return is_container, has_to_string
4834
4835
4836 def header_guard( name, out):
4837 '''
4838 Writes header guard for <name> to stream <out>.
4839 '''
4840 m = ''
4841 for c in name:
4842 m += c.upper() if c.isalnum() else '_'
4843 out.write( f'#ifndef {m}\n')
4844 out.write( f'#define {m}\n')
4845 out.write( '\n')
4846
4847
4848 def tabify( filename, text):
4849 '''
4850 Returns <text> with leading multiples of 4 spaces converted to tab
4851 characters.
4852 '''
4853 ret = ''
4854 linenum = 0
4855 for line in text.split( '\n'):
4856 linenum += 1
4857 i = 0
4858 while 1:
4859 if i == len(line):
4860 break
4861 if line[i] != ' ':
4862 break
4863 i += 1
4864 if i % 4:
4865 if line[ int(i/4)*4:].startswith( ' *'):
4866 # This can happen in comments.
4867 pass
4868 else:
4869 jlib.log( '*** {filename}:{linenum}: {i=} {line!r=} indentation is not a multiple of 4')
4870 num_tabs = int(i / 4)
4871 ret += num_tabs * '\t' + line[ num_tabs*4:] + '\n'
4872
4873 # We use [:-1] here because split() always returns extra last item '', so
4874 # we will have appended an extra '\n'.
4875 #
4876 return ret[:-1]
4877
4878
4879 def refcount_check_code( out, refcheck_if):
4880 '''
4881 Writes reference count checking code to <out>.
4882 '''
4883 out.write( textwrap.dedent(
4884 f'''
4885 /* Support for checking that reference counts of underlying
4886 MuPDF structs are not smaller than the number of wrapper class
4887 instances. Enable at runtime by setting environmental variable
4888 MUPDF_check_refs to "1". */
4889
4890 static const bool s_check_refs = internal_env_flag("MUPDF_check_refs");
4891
4892 /* For each MuPDF struct that has an 'int refs' member, we create
4893 a static instance of this class template with T set to our wrapper
4894 class, for example:
4895
4896 static RefsCheck<fz_document, FzDocument> s_FzDocument_refs_check;
4897
4898 Then if s_check_refs is true, each constructor function calls
4899 .add(), the destructor calls .remove() and other class functions
4900 call .check(). This ensures that we check reference counting after
4901 each class operation.
4902
4903 If <allow_int_this> is true, we allow _this->m_internal to be
4904 an invalid pointer less than 4096, in which case we don't try
4905 to check refs. This is used for pdf_obj because in Python the
4906 enums PDF_ENUM_NAME_* are converted to mupdf.PdfObj's contain
4907 .m_internal's which are the enum values cast to (for_pdf_obj*), so
4908 that they can be used directly.
4909
4910 If m_size is -1, we don't attempt any checking; this is for fz_xml
4911 which is reference counted but does not have a simple .refs member.
4912 */
4913 {refcheck_if}
4914 template<typename Struct, typename ClassWrapper, bool allow_int_this=false>
4915 struct RefsCheck
4916 {{
4917 std::mutex m_mutex;
4918 int m_offset;
4919 int m_size;
4920 std::map<Struct*, int> m_this_to_num;
4921
4922 RefsCheck(int offset, int size)
4923 : m_offset(offset), m_size(size)
4924 {{
4925 assert(offset >= 0 && offset < 1000);
4926 assert(m_size == 32 || m_size == 16 || m_size == 8 || m_size == -1);
4927 }}
4928
4929 void change( const ClassWrapper* this_, const char* file, int line, const char* fn, int delta)
4930 {{
4931 assert( s_check_refs);
4932 if (m_size == -1)
4933 {{
4934 /* No well-defined .refs member for us to check, e.g. fz_xml. */
4935 return;
4936 }}
4937 if (!this_->m_internal) return;
4938 if (allow_int_this)
4939 {{
4940 #if 0 // Historic diagnostics, might still be useful.
4941 std::cerr << __FILE__ << ":" << __LINE__
4942 << " " << file << ":" << line << ":" << fn << ":"
4943 << " this_->m_internal=" << this_->m_internal
4944 << "\\n";
4945 #endif
4946 if ((intptr_t) this_->m_internal < 4096)
4947 {{
4948 #if 0 // Historic diagnostics, might still be useful.
4949 std::cerr << __FILE__ << ":" << __LINE__
4950 << " " << file << ":" << line << ":" << fn << ":"
4951 << " Ignoring this_->m_internal=" << this_->m_internal
4952 << "\\n";
4953 #endif
4954 return;
4955 }}
4956 }}
4957 std::lock_guard< std::mutex> lock( m_mutex);
4958 /* Our lock doesn't make our access to
4959 this_->m_internal->refs thead-safe - other threads
4960 could be modifying it via fz_keep_<Struct>() or
4961 fz_drop_<Struct>(). But hopefully our read will be atomic
4962 in practise anyway? */
4963 void* refs_ptr = (char*) this_->m_internal + m_offset;
4964 int refs;
4965 if (m_size == 32) refs = *(int32_t*) refs_ptr;
4966 if (m_size == 16) refs = *(int16_t*) refs_ptr;
4967 if (m_size == 8) refs = *(int8_t* ) refs_ptr;
4968
4969 int& n = m_this_to_num[ this_->m_internal];
4970 int n_prev = n;
4971 assert( n >= 0);
4972 n += delta;
4973 #if 0 // Historic diagnostics, might still be useful.
4974 std::cerr << file << ":" << line << ":" << fn << "():"
4975 // << " " << typeid(ClassWrapper).name() << ":"
4976 << " this_=" << this_
4977 << " this_->m_internal=" << this_->m_internal
4978 << " refs=" << refs
4979 << " n: " << n_prev << " => " << n
4980 << "\\n";
4981 #endif
4982 if ( n < 0)
4983 {{
4984 #if 0 // Historic diagnostics, might still be useful.
4985 std::cerr << file << ":" << line << ":" << fn << "():"
4986 // << " " << typeid(ClassWrapper).name() << ":"
4987 << " this_=" << this_
4988 << " this_->m_internal=" << this_->m_internal
4989 << " bad n: " << n_prev << " => " << n
4990 << "\\n";
4991 #endif
4992 abort();
4993 }}
4994 if ( n && refs < n)
4995 {{
4996 #if 0 // Historic diagnostics, might still be useful.
4997 std::cerr << file << ":" << line << ":" << fn << "():"
4998 // << " " << typeid(ClassWrapper).name() << ":"
4999 << " this_=" << this_
5000 << " this_->m_internal=" << this_->m_internal
5001 << " refs=" << refs
5002 << " n: " << n_prev << " => " << n
5003 << " refs mismatch (refs<n):"
5004 << "\\n";
5005 #endif
5006 abort();
5007 }}
5008 if (n && ::abs( refs - n) > 1000)
5009 {{
5010 /* This traps case where n > 0 but underlying struct is
5011 freed and .ref is set to bogus value by fz_free() or
5012 similar. */
5013 #if 0 // Historic diagnostics, might still be useful.
5014 std::cerr << file << ":" << line << ":" << fn << "(): " << ": " << typeid(ClassWrapper).name()
5015 << " bad change to refs."
5016 << " this_=" << this_
5017 << " refs=" << refs
5018 << " n: " << n_prev << " => " << n
5019 << "\\n";
5020 #endif
5021 abort();
5022 }}
5023 if (n == 0) m_this_to_num.erase( this_->m_internal);
5024 }}
5025 void add( const ClassWrapper* this_, const char* file, int line, const char* fn)
5026 {{
5027 change( this_, file, line, fn, +1);
5028 }}
5029 void remove( const ClassWrapper* this_, const char* file, int line, const char* fn)
5030 {{
5031 change( this_, file, line, fn, -1);
5032 }}
5033 void check( const ClassWrapper* this_, const char* file, int line, const char* fn)
5034 {{
5035 change( this_, file, line, fn, 0);
5036 }}
5037 }};
5038 #endif
5039
5040 '''
5041 ))
5042
5043 def cpp_source(
5044 dir_mupdf,
5045 namespace,
5046 base,
5047 header_git,
5048 generated,
5049 check_regress,
5050 clang_info_version,
5051 refcheck_if,
5052 trace_if,
5053 debug,
5054 ):
5055 '''
5056 Generates all .h and .cpp files.
5057
5058 Args:
5059
5060 dir_mupdf:
5061 Location of mupdf checkout.
5062 namespace:
5063 C++ namespace to use.
5064 base:
5065 Directory in which all generated files are placed.
5066 header_git:
5067 If true we include git info in the file comment that is written
5068 into all generated files.
5069 generated:
5070 A Generated instance.
5071 check_regress:
5072 If true, we raise exception if generated content differs from what
5073 is in existing files.
5074 refcheck_if:
5075 `#if ... ' text for enabling reference-checking code. For example
5076 `#if 1` to always enable, `#ifndef NDEBUG` to only enable in debug
5077 builds, `#if 0` to always disable.
5078 refcheck_if:
5079 `#if ... ' text for enabling optional runtime diagnostic, for
5080 example by setting `MuPDF_trace=1` runtime. For example `#if 1` to
5081 always enable, `#ifndef NDEBUG` to only enable in debug builds,
5082 `#if 0` to always disable.
5083 debug:
5084 True if debug build.
5085
5086 Updates <generated> and returns <tu> from clang..
5087 '''
5088 assert isinstance(generated, Generated)
5089 assert not dir_mupdf.endswith( '/')
5090 assert not base.endswith( '/')
5091
5092 # Do initial setting up of generated files before parse, because we include extra.h in our parse input.
5093
5094 doit = True
5095 if doit:
5096 class File:
5097 def __init__( self, filename, tabify=True):
5098 self.filename = filename
5099 self.tabify = tabify
5100 self.file = io.StringIO()
5101 self.line_begin = True
5102 self.regressions = True
5103 self.closed = False
5104 def write( self, text, fileline=False):
5105 # Do not allow writes after .close().
5106 assert not self.closed, f'File.write() called after .close(). {self.filename=}'
5107 if fileline:
5108 # Generate #line <line> "<filename>" for our caller's
5109 # location. This makes any compiler warnings refer to their
5110 # python code rather than the generated C++ code.
5111 tb = traceback.extract_stack( None)
5112 filename, line, function, source = tb[0]
5113 if self.line_begin:
5114 self.file.write( f'#line {line} "{filename}"\n')
5115 self.file.write( text)
5116 self.line_begin = text.endswith( '\n')
5117 def close( self):
5118 if self.closed:
5119 # Allow multiple calls to .close().
5120 return
5121 self.closed = True
5122 if self.filename:
5123 # Overwrite if contents differ.
5124 text = self.get()
5125 if self.tabify:
5126 text = tabify( self.filename, text)
5127 cr = check_regress
5128 jlib.log('calling util.update_file_regress() check_regress={cr}: {self.filename=}', 1)
5129 e = util.update_file_regress( text, self.filename, check_regression=cr)
5130 jlib.log('util.update_file_regress() returned => {e}', 1)
5131 if e:
5132 jlib.log('util.update_file_regress() => {e=}', 1)
5133 self.regressions = True
5134 jlib.log(f'File updated: {os.path.relpath(self.filename)}')
5135 else:
5136 jlib.log(f'File unchanged: {os.path.relpath(self.filename)}')
5137 def get( self):
5138 return self.file.getvalue()
5139 else:
5140 class File:
5141 def __init__( self, filename):
5142 pass
5143 def write( self, text, fileline=False):
5144 pass
5145 def close( self):
5146 pass
5147
5148 class Outputs:
5149 '''
5150 A set of output files.
5151
5152 For convenience, after outputs.add( 'foo', 'foo.c'), outputs.foo is a
5153 python stream that writes to 'foo.c'.
5154 '''
5155 def __init__( self):
5156 self.items = []
5157
5158 def add( self, name, filename):
5159 '''
5160 Sets self.<name> to file opened for writing on <filename>.
5161 '''
5162 file = File( filename)
5163 self.items.append( (name, filename, file))
5164 setattr( self, name, file)
5165
5166 def get( self):
5167 '''
5168 Returns list of (name, filename, file) tuples.
5169 '''
5170 return self.items
5171
5172 def close( self):
5173 for name, filename, file in self.items:
5174 file.close()
5175
5176 out_cpps = Outputs()
5177 out_hs = Outputs()
5178 for name in (
5179 'classes',
5180 'classes2',
5181 'exceptions',
5182 'functions',
5183 'internal',
5184 'extra',
5185 ):
5186 out_hs.add( name, f'{base}/include/mupdf/{name}.h')
5187 out_cpps.add( name, f'{base}/implementation/{name}.cpp')
5188
5189 # Make text of header comment for all generated file.
5190 #
5191 header_text = textwrap.dedent(
5192 f'''
5193 /**
5194 This file was auto-generated by mupdfwrap.py.
5195 ''')
5196
5197 if header_git:
5198 git_id = jlib.get_git_id( dir_mupdf, allow_none=True)
5199 if git_id:
5200 git_id = git_id.split('\n', 1)
5201 header_text += textwrap.dedent(
5202 f'''
5203 mupdf checkout:
5204 {git_id[0]}'
5205 ''')
5206
5207 header_text += '*/\n'
5208 header_text += '\n'
5209 header_text = header_text[1:] # Strip leading \n.
5210 for _, _, file in out_cpps.get() + out_hs.get():
5211 file.write( header_text)
5212
5213 os.makedirs( f'{base}/include/mupdf', exist_ok=True)
5214 os.makedirs( f'{base}/implementation', exist_ok=True)
5215
5216 num_regressions = 0
5217 # Create extra File that writes to internal buffer rather than an actual
5218 # file, which we will append to out_h.
5219 #
5220 out_h_classes_end = File( None)
5221
5222 # Write multiple-inclusion guards into headers:
5223 #
5224 for name, filename, file in out_hs.get():
5225 prefix = f'{base}/include/'
5226 assert filename.startswith( prefix)
5227 name = filename[ len(prefix):]
5228 header_guard( name, file)
5229
5230 # We need to write to out_hs.extra here before we do the parse
5231 # because out_hs.extra will be part of the input text passed to the
5232 # clang parser.
5233 #
5234 make_extra(out_hs.extra, out_cpps.extra)
5235 out_hs.extra.write( textwrap.dedent('''
5236 #endif
5237 '''))
5238 out_hs.extra.close()
5239 out_cpps.extra.close()
5240
5241 # Now parse.
5242 #
5243 try:
5244 index = state.clang.cindex.Index.create()
5245 except Exception as e:
5246 raise Exception(f'libclang does not appear to be installed') from e
5247
5248 header = f'{dir_mupdf}/include/mupdf/fitz.h'
5249 assert os.path.isfile( header), f'header={header}'
5250
5251 # Get clang to parse mupdf/fitz.h and mupdf/pdf.h and mupdf/extra.h.
5252 #
5253 # It might be possible to use index.parse()'s <unsaved_files> arg to
5254 # specify these multiple files, but i couldn't get that to work.
5255 #
5256 # So instead we write some #include's to a temporary file and ask clang to
5257 # parse it.
5258 #
5259 temp_h = f'_mupdfwrap_temp.cpp'
5260 try:
5261 with open( temp_h, 'w') as f:
5262 if state.state_.linux or state.state_.macos:
5263 jlib.log1('Prefixing Fitz headers with `typedef unsigned long size_t;`'
5264 ' because size_t not available to clang on Linux/MacOS.')
5265 # On Linux, size_t is defined internally in gcc (e.g. not even
5266 # in /usr/include/stdint.h) and so not visible to clang.
5267 #
5268 # If we don't define it, clang complains about C99 not
5269 # supporting implicit int and appears to variously expand
5270 # size_t as different function pointers, e.g. `int (int *)` and
5271 # `int (*)(int *)`.
5272 #
5273 f.write( textwrap.dedent('''
5274 /*
5275 Workaround on Linux/MacOS. size_t is defined internally in
5276 gcc (e.g. not even in /usr/include/stdint.h) and so not visible to clang.
5277 */
5278 typedef unsigned long size_t;
5279 '''))
5280 if state.state_.macos:
5281 f.write( textwrap.dedent('''
5282 /*
5283 Workaround on MacOS: we need to define fixed-size int types
5284 and FILE and va_list, similarly as with size_t above.
5285 */
5286 typedef signed char int8_t;
5287 typedef short int16_t;
5288 typedef int int32_t;
5289 typedef long long int64_t;
5290 typedef unsigned char uint8_t;
5291 typedef unsigned short uint16_t;
5292 typedef unsigned int uint32_t;
5293 typedef unsigned long long uint64_t;
5294 typedef struct FILE FILE;
5295 typedef struct va_list va_list;
5296 '''))
5297 f.write( textwrap.dedent('''
5298 #include "mupdf/extra.h"
5299
5300 #include "mupdf/fitz.h"
5301 #include "mupdf/pdf.h"
5302 '''))
5303
5304 # libclang often doesn't have access to system headers so we define
5305 # MUPDF_WRAP_LIBCLANG so that extra.h can use dummy definition of
5306 # std::vector.
5307 #
5308 args = [
5309 '-I', f'{dir_mupdf}/include',
5310 '-I', f'{dir_mupdf}/platform/c++/include',
5311 '-D', 'MUPDF_WRAP_LIBCLANG',
5312 '-D', 'FZ_FUNCTION=',
5313 ]
5314 tu = index.parse(
5315 temp_h,
5316 args = args,
5317 options = 0
5318 | state.clang.cindex.TranslationUnit.PARSE_INCOMPLETE
5319 | state.clang.cindex.TranslationUnit.PARSE_SKIP_FUNCTION_BODIES
5320 ,
5321 )
5322
5323 # Show warnings/errors from the parse. Failure to include stddef.h
5324 # appears to be harmless on Linux, but other failures seem to cause
5325 # more problems.
5326 #
5327 def show_clang_diagnostic( diagnostic, depth=0):
5328 for diagnostic2 in diagnostic.children:
5329 show_clang_diagnostic( diagnostic2, depth + 1)
5330 jlib.log1( '{" "*4*depth}{diagnostic}')
5331 if tu.diagnostics:
5332 jlib.log1( 'tu.diagnostics():')
5333 for diagnostic in tu.diagnostics:
5334 show_clang_diagnostic(diagnostic, 1)
5335
5336 finally:
5337 if os.path.isfile( temp_h):
5338 os.remove( temp_h)
5339
5340 # Write required #includes into .h files:
5341 #
5342 out_hs.exceptions.write( textwrap.dedent(
5343 '''
5344 #include <stdexcept>
5345 #include <string>
5346
5347 #include "mupdf/fitz.h"
5348
5349 '''))
5350
5351 out_hs.internal.write( textwrap.dedent(
5352 '''
5353 #include <iostream>
5354
5355 '''))
5356
5357 out_hs.functions.write( textwrap.dedent(
5358 '''
5359 #include "mupdf/extra.h"
5360
5361 #include "mupdf/fitz.h"
5362 #include "mupdf/pdf.h"
5363
5364 #include <iostream>
5365 #include <string>
5366 #include <vector>
5367
5368 '''))
5369
5370 out_hs.classes.write( textwrap.dedent(
5371 '''
5372 #include "mupdf/fitz.h"
5373 #include "mupdf/functions.h"
5374 #include "mupdf/pdf.h"
5375
5376 #include <map>
5377 #include <string>
5378 #include <vector>
5379
5380 '''))
5381
5382 out_hs.classes2.write( textwrap.dedent(
5383 '''
5384 #include "classes.h"
5385
5386 '''))
5387
5388 # Write required #includes into .cpp files:
5389 #
5390 out_cpps.exceptions.write( textwrap.dedent(
5391 f'''
5392 #include "mupdf/exceptions.h"
5393 #include "mupdf/fitz.h"
5394 #include "mupdf/internal.h"
5395
5396 #include <iostream>
5397
5398 #include <string.h>
5399
5400 {trace_if}
5401 static const bool s_trace_exceptions = mupdf::internal_env_flag("MUPDF_trace_exceptions");
5402 #else
5403 static const bool s_trace_exceptions_dummy = mupdf::internal_env_flag_check_unset("{trace_if}", "MUPDF_trace_exceptions");
5404 #endif
5405 '''))
5406
5407 out_cpps.functions.write( textwrap.dedent(
5408 '''
5409 #include "mupdf/exceptions.h"
5410 #include "mupdf/functions.h"
5411 #include "mupdf/internal.h"
5412 #include "mupdf/extra.h"
5413
5414 #include <assert.h>
5415 #include <sstream>
5416
5417 #include <string.h>
5418
5419 '''))
5420
5421 out_cpps.classes.write(
5422 textwrap.dedent(
5423 f'''
5424 #include "mupdf/classes.h"
5425 #include "mupdf/classes2.h"
5426 #include "mupdf/exceptions.h"
5427 #include "mupdf/internal.h"
5428
5429 #include "mupdf/fitz/geometry.h"
5430
5431 #include <algorithm>
5432 #include <map>
5433 #include <mutex>
5434 #include <sstream>
5435 #include <string.h>
5436 #include <thread>
5437
5438 #include <string.h>
5439
5440 {trace_if}
5441 static const int s_trace = mupdf::internal_env_flag("MUPDF_trace");
5442 static const bool s_trace_keepdrop = mupdf::internal_env_flag("MUPDF_trace_keepdrop");
5443 static const bool s_trace_director = mupdf::internal_env_flag("MUPDF_trace_director");
5444 #else
5445 static const int s_trace = mupdf::internal_env_flag_check_unset("{trace_if}", "MUPDF_trace");
5446 static const bool s_trace_keepdrop = mupdf::internal_env_flag_check_unset("{trace_if}", "MUPDF_trace_keepdrop");
5447 static const bool s_trace_director = mupdf::internal_env_flag_check_unset("{trace_if}", "MUPDF_trace_director");
5448 #endif
5449 '''))
5450
5451 out_cpps.classes2.write(
5452 textwrap.dedent(
5453 f'''
5454 #include "mupdf/classes2.h"
5455 #include "mupdf/exceptions.h"
5456 #include "mupdf/internal.h"
5457
5458 #include "mupdf/fitz/geometry.h"
5459
5460 #include <map>
5461 #include <mutex>
5462 #include <sstream>
5463 #include <string.h>
5464 #include <thread>
5465
5466 #include <string.h>
5467
5468 {trace_if}
5469 static const int s_trace = mupdf::internal_env_flag("MUPDF_trace");
5470 #else
5471 static const int s_trace = mupdf::internal_env_flag_check_unset("{trace_if}", "MUPDF_trace");
5472 #endif
5473 '''))
5474
5475 namespace = 'mupdf'
5476 for _, _, file in out_cpps.get() + out_hs.get():
5477 if file in (out_cpps.internal, out_cpps.extra, out_hs.extra):
5478 continue
5479 make_namespace_open( namespace, file)
5480
5481 # Write reference counting check code to out_cpps.classes.
5482 refcount_check_code( out_cpps.classes, refcheck_if)
5483
5484 # Write declaration and definition for metadata_keys global.
5485 #
5486 out_hs.functions.write(
5487 textwrap.dedent(
5488 '''
5489 /*
5490 The keys that are defined for fz_lookup_metadata().
5491 */
5492 FZ_DATA extern const std::vector<std::string> metadata_keys;
5493
5494 '''))
5495 out_cpps.functions.write(
5496 textwrap.dedent(
5497 f'''
5498 FZ_FUNCTION const std::vector<std::string> metadata_keys = {{
5499 "format",
5500 "encryption",
5501 "info:Title",
5502 "info:Author",
5503 "info:Subject",
5504 "info:Keywords",
5505 "info:Creator",
5506 "info:Producer",
5507 "info:CreationDate",
5508 "info:ModDate",
5509 }};
5510
5511 {trace_if}
5512 static const int s_trace = internal_env_flag("MUPDF_trace");
5513 static const bool s_trace_keepdrop = internal_env_flag("MUPDF_trace_keepdrop");
5514 static const bool s_trace_exceptions = internal_env_flag("MUPDF_trace_exceptions");
5515 static const bool s_check_error_stack = internal_env_flag("MUPDF_check_error_stack");
5516 #else
5517 static const int s_trace = internal_env_flag_check_unset("{trace_if}", "MUPDF_trace");
5518 static const bool s_trace_keepdrop = internal_env_flag_check_unset("{trace_if}", "MUPDF_trace_keepdrop");
5519 static const bool s_trace_exceptions = internal_env_flag_check_unset("{trace_if}", "MUPDF_trace_exceptions");
5520 static const bool s_check_error_stack = internal_env_flag_check_unset("{trace_if}", "MUPDF_check_error_stack");
5521 #endif
5522
5523 '''))
5524
5525 # Write source code for exceptions and wrapper functions.
5526 #
5527 jlib.log( 'Creating wrapper functions...')
5528 make_function_wrappers(
5529 tu,
5530 namespace,
5531 out_hs.exceptions,
5532 out_cpps.exceptions,
5533 out_hs.functions,
5534 out_cpps.functions,
5535 out_hs.internal,
5536 out_cpps.internal,
5537 out_hs.classes2,
5538 out_cpps.classes2,
5539 generated,
5540 refcheck_if,
5541 trace_if,
5542 )
5543
5544 fn_usage = dict()
5545 functions_unrecognised = set()
5546
5547 for fnname, cursor in state.state_.find_functions_starting_with( tu, '', method=True):
5548 fn_usage[ fnname] = [0, cursor]
5549 generated.c_functions.append(fnname)
5550
5551 for structname, cursor in state.state_.structs[ tu].items():
5552 generated.c_structs.append( structname)
5553
5554 # Create windows_mupdf.def, containing explicit exports for all MuPDF
5555 # global data and functions. We do this instead of explicitly prefixing
5556 # everything with FZ_FUNCTION or FZ_DATA in the MuPDF header files.
5557 #
5558 windows_def_path = os.path.relpath(f'{base}/windows_mupdf.def')
5559 windows_def = ''
5560 windows_def += 'EXPORTS\n'
5561
5562 for name, cursor in state.state_.find_global_data_starting_with( tu, ('fz_', 'pdf_')):
5563 if state.state_.show_details(name):
5564 jlib.log('global: {name=}')
5565 generated.c_globals.append(name)
5566 windows_def += f' {name} DATA\n'
5567 for fnname, cursor in state.state_.find_functions_starting_with( tu, ('fz_', 'pdf_', 'FT_'), method=False):
5568 if cursor.storage_class == state.clang.cindex.StorageClass.STATIC:
5569 # These fns do not work in windows.def, probably because they are
5570 # usually inline?
5571 #
5572 jlib.log('Not adding to windows_def because static: {fnname}()', 1)
5573 elif os.path.abspath(cursor.extent.start.file.name) == os.path.abspath(out_hs.extra.filename):
5574 # Items defined in out_hs.extra are C++ so we would need to use the
5575 # mangled name if we added them to windows_def. Instead they are
5576 # explicitly prefixed with `FZ_FUNCTION`.
5577 #
5578 # (We use os.path.abspath() to avoid problems with back and forward
5579 # slashes in cursor.extent.start.file.name on Windows.)
5580 #
5581 jlib.log1('Not adding to {windows_def_path} because defined in {os.path.relpath(out_hs.extra.filename)}: {cursor.spelling}')
5582 else:
5583 windows_def += f' {fnname}\n'
5584 # Add some internal fns that PyMuPDF requires.
5585 for fnname in (
5586 'FT_Get_First_Char',
5587 'FT_Get_Next_Char',
5588 ):
5589 windows_def += f' {fnname}\n'
5590
5591 if debug:
5592 # In debug builds these are real fns, not macros, and we need to
5593 # make them exported.
5594 windows_def += f' fz_lock_debug_lock\n'
5595 windows_def += f' fz_lock_debug_unlock\n'
5596
5597 jlib.fs_update( windows_def, windows_def_path)
5598
5599 def register_fn_use( name):
5600 assert name.startswith( ('fz_', 'pdf_'))
5601 if name in fn_usage:
5602 fn_usage[ name][0] += 1
5603 else:
5604 functions_unrecognised.add( name)
5605
5606 # Write source code for wrapper classes.
5607 #
5608 jlib.log( 'Creating wrapper classes...')
5609
5610 # Find all classes that we can create.
5611 #
5612 classes_ = []
5613 for cursor in parse.get_children(tu.cursor):
5614 if not cursor.spelling.startswith( ('fz_', 'pdf_')):
5615 continue
5616 if cursor.kind != state.clang.cindex.CursorKind.TYPEDEF_DECL:
5617 continue;
5618 type_ = state.get_name_canonical( cursor.underlying_typedef_type)
5619 if type_.kind not in (state.clang.cindex.TypeKind.RECORD, state.clang.cindex.TypeKind.ELABORATED):
5620 continue
5621 if type_.kind == state.clang.cindex.TypeKind.ELABORATED:
5622 jlib.log( 'state.clang.cindex.TypeKind.ELABORATED: {type_.spelling=}')
5623
5624 if not cursor.is_definition():
5625 # Handle abstract type only if we have an ClassExtra for it.
5626 extras = classes.classextras.get( tu, cursor.spelling)
5627 if extras and extras.opaque:
5628 pass
5629 #log( 'Creating wrapper for opaque struct: {cursor.spelling=}')
5630 else:
5631 continue
5632
5633 #struct_name = type_.spelling
5634 struct_name = cursor.spelling
5635 struct_name = util.clip( struct_name, 'struct ')
5636 if cursor.spelling != struct_name:
5637 jlib.log('{type_.spelling=} {struct_name=} {cursor.spelling=}')
5638 classname = rename.class_( struct_name)
5639
5640 # For some reason after updating mupdf 2020-04-13, clang-python is
5641 # returning two locations for struct fz_buffer_s, both STRUCT_DECL. One
5642 # is 'typedef struct fz_buffer_s fz_buffer;', the other is the full
5643 # struct definition.
5644 #
5645 # No idea why this is happening. Using .canonical doesn't seem to
5646 # affect things.
5647 #
5648 for cl, cu, s in classes_:
5649 if cl == classname:
5650 jlib.logx( 'ignoring duplicate STRUCT_DECL for {struct_name=}')
5651 break
5652 else:
5653 classes_.append( (classname, cursor, struct_name))
5654
5655 classes_.sort()
5656
5657 # Write forward declarations - this is required because some class
5658 # methods take pointers to other classes.
5659 #
5660 out_hs.classes.write( '\n')
5661 out_hs.classes.write( '/* Forward declarations of all classes that we define. */\n')
5662 for classname, struct_cursor, struct_name in classes_:
5663 out_hs.classes.write( f'struct {classname};\n')
5664 out_hs.classes.write( '\n')
5665
5666 # Create each class.
5667 #
5668 for classname, struct_cursor, struct_name in classes_:
5669 #jlib.log( 'creating wrapper {classname} for {cursor.spelling}')
5670 extras = classes.classextras.get( tu, struct_name)
5671 assert extras, f'struct_name={struct_name}'
5672 if extras.pod:
5673 struct_to_string_fns(
5674 tu,
5675 struct_cursor,
5676 struct_name,
5677 extras,
5678 out_hs.functions,
5679 out_cpps.functions,
5680 )
5681
5682 with jlib.LogPrefixScope( f'{struct_name}: '):
5683 is_container, has_to_string = class_wrapper(
5684 tu,
5685 register_fn_use,
5686 struct_cursor,
5687 struct_name,
5688 classname,
5689 extras,
5690 out_hs.classes,
5691 out_cpps.classes,
5692 out_h_classes_end,
5693 out_cpps.classes2,
5694 out_hs.classes2,
5695 generated,
5696 refcheck_if,
5697 trace_if,
5698 )
5699 if is_container:
5700 generated.container_classnames.append( classname)
5701 if has_to_string:
5702 generated.to_string_structnames.append( struct_name)
5703
5704 out_hs.functions.write( textwrap.dedent( '''
5705 /** Reinitializes the MuPDF context for single-threaded use, which
5706 is slightly faster when calling code is single threaded.
5707
5708 This should be called before any other use of MuPDF.
5709 */
5710 FZ_FUNCTION void reinit_singlethreaded();
5711
5712 '''))
5713
5714 # Generate num_instances diagnostic fn.
5715 out_hs.classes.write('\n')
5716 out_hs.classes.write('/** Returns map from class name (for example FzDocument) to s_num_instances. */\n')
5717 out_hs.classes.write('FZ_FUNCTION std::map<std::string, int> num_instances();\n')
5718 out_cpps.classes.write('FZ_FUNCTION std::map<std::string, int> num_instances()\n')
5719 out_cpps.classes.write('{\n')
5720 out_cpps.classes.write(' std::map<std::string, int> ret;\n')
5721 for classname, struct_cursor, struct_name in classes_:
5722 out_cpps.classes.write(f' ret["{classname}"] = {classname}::s_num_instances;\n')
5723 out_cpps.classes.write(' \n')
5724 out_cpps.classes.write(' return ret;\n')
5725 out_cpps.classes.write('}\n')
5726
5727 # Write close of namespace.
5728 out_hs.classes.write( out_h_classes_end.get())
5729 for _, _, file in out_cpps.get() + out_hs.get():
5730 if file in (out_cpps.internal, out_cpps.extra, out_hs.extra):
5731 continue
5732 make_namespace_close( namespace, file)
5733
5734 # Write pod struct fns such as operator<<(), operator==() - these need to
5735 # be outside the namespace.
5736 #
5737 for classname, struct_cursor, struct_name in classes_:
5738 extras = classes.classextras.get( tu, struct_name)
5739 if extras.pod:
5740 # Make operator<<(), operator==(), operator!=() for POD struct.
5741 #
5742 pod_struct_fns(
5743 tu,
5744 namespace,
5745 struct_cursor,
5746 struct_name,
5747 extras,
5748 out_hs.functions,
5749 out_cpps.functions,
5750 )
5751 if extras.pod != 'none':
5752 # Make operator<<(), operator==(), operator!=() for POD class
5753 # wrappers.
5754 #
5755 pod_class_fns(
5756 tu,
5757 classname,
5758 struct_cursor,
5759 struct_name,
5760 extras,
5761 out_hs.classes,
5762 out_cpps.classes,
5763 )
5764
5765
5766 # Terminate multiple-inclusion guards in headers:
5767 #
5768 for name, _, file in out_hs.get():
5769 if name != 'extra':
5770 file.write( '\n#endif\n')
5771
5772 out_hs.close()
5773 out_cpps.close()
5774
5775 generated.h_files = [filename for _, filename, _ in out_hs.get()]
5776 generated.cpp_files = [filename for _, filename, _ in out_cpps.get()]
5777 if 0: # lgtm [py/unreachable-statement]
5778 jlib.log( 'Have created:')
5779 for filename in filenames_h + filenames_cpp:
5780 jlib.log( ' {filename}')
5781
5782
5783 # Output usage information.
5784 #
5785
5786 fn_usage_filename = f'{base}/fn_usage.txt'
5787 out_fn_usage = File( fn_usage_filename, tabify=False)
5788 functions_unused = 0
5789 functions_used = 0
5790
5791 for fnname in sorted( fn_usage.keys()):
5792 n, cursor = fn_usage[ fnname]
5793 exclude_reasons = parse.find_wrappable_function_with_arg0_type_excluded_cache.get( fnname, [])
5794 if n:
5795 functions_used += 1
5796 else:
5797 functions_unused += 1
5798 if n and not exclude_reasons:
5799 continue
5800
5801 out_fn_usage.write( f'Functions not wrapped by class methods:\n')
5802 out_fn_usage.write( '\n')
5803
5804 for fnname in sorted( fn_usage.keys()):
5805 n, cursor = fn_usage[ fnname]
5806 exclude_reasons = parse.find_wrappable_function_with_arg0_type_excluded_cache.get( fnname, [])
5807 if not exclude_reasons:
5808 continue
5809 if n:
5810 continue
5811 num_interesting_reasons = 0
5812 for t, description in exclude_reasons:
5813 if t == parse.MethodExcludeReason_FIRST_ARG_NOT_STRUCT:
5814 continue
5815 if t == parse.MethodExcludeReason_VARIADIC:
5816 continue
5817 num_interesting_reasons += 1
5818 if num_interesting_reasons:
5819 try:
5820 out_fn_usage.write( f' {declaration_text( cursor.type, cursor.spelling)}\n')
5821 except Clang6FnArgsBug as e:
5822 out_fn_usage.write( f' {cursor.spelling} [full prototype not available due to known clang-6 issue]\n')
5823 for t, description in exclude_reasons:
5824 if t == parse.MethodExcludeReason_FIRST_ARG_NOT_STRUCT:
5825 continue
5826 out_fn_usage.write( f' {description}\n')
5827 out_fn_usage.write( '\n')
5828
5829 out_fn_usage.write( f'\n')
5830 out_fn_usage.write( f'Functions used more than once:\n')
5831 for fnname in sorted( fn_usage.keys()):
5832 n, cursor = fn_usage[ fnname]
5833 if n > 1:
5834 out_fn_usage.write( f' n={n}: {declaration_text( cursor.type, cursor.spelling)}\n')
5835
5836 out_fn_usage.write( f'\n')
5837 out_fn_usage.write( f'Number of wrapped functions: {len(fn_usage)}\n')
5838 out_fn_usage.write( f'Number of wrapped functions used by wrapper classes: {functions_used}\n')
5839 out_fn_usage.write( f'Number of wrapped functions not used by wrapper classes: {functions_unused}\n')
5840
5841 out_fn_usage.close()
5842
5843 generated.c_enums = state.state_.enums[ tu]
5844
5845 if num_regressions:
5846 raise Exception( f'There were {num_regressions} regressions')
5847 return tu
5848
5849
5850 def test():
5851 '''
5852 Place to experiment with clang-python.
5853 '''
5854 text = ''
5855 if state.state_.linux:
5856 text += textwrap.dedent('''
5857 /*
5858 Workaround on Linux. size_t is defined internally in gcc. It isn't
5859 even in stdint.h.
5860 */
5861 typedef unsigned long size_t;
5862 ''')
5863
5864 text += textwrap.dedent('''
5865 #include "mupdf/fitz.h"
5866 #include "mupdf/pdf.h"
5867 ''')
5868 path = 'wrap-test.c'
5869 jlib.fs_update( text, path)
5870 index = state.clang.cindex.Index.create()
5871 tu = index.parse( path, '-I /usr/include -I include'.split(' '))
5872 path2 = 'wrap-test.c.c'
5873 tu.save(path2)
5874 jlib.log( 'Have saved to: {path2}')
5875 parse.dump_ast( tu.cursor, 'ast')
5876 for diagnostic in tu.diagnostics:
5877 jlib.log('{diagnostic=}')
5878 for cursor in parse.get_members( tu.cursor):
5879 if 'cpp_test_' in cursor.spelling:
5880 parse.dump_ast(cursor, out=jlib.log)