def functions_cache_populate(self, tu): if tu in self.functions_cache: return fns = dict() global_data = dict() enums = dict() for cursor in tu.cursor.get_children(): if cursor.kind == clang.cindex.CursorKind.ENUM_DECL: #jlib.log('ENUM_DECL: {cursor.spelling=}') enum_values = list() for cursor2 in cursor.get_children(): #jlib.log(' {cursor2.spelling=}') name = cursor2.spelling #if name.startswith('PDF_ENUM_NAME_'): enum_values.append(name) enums[cursor.type.get_canonical().spelling] = enum_values if (cursor.linkage == clang.cindex.LinkageKind.EXTERNAL or cursor.is_definition() # Picks up static inline functions. ): if cursor.kind == clang.cindex.CursorKind.FUNCTION_DECL: fnname = cursor.mangled_name if self.show_details(fnname): jlib.log('Looking at {fnname=}') if fnname not in omit_fns: fns[fnname] = cursor else: global_data[cursor.mangled_name] = cursor self.functions_cache[tu] = fns self.global_data[tu] = global_data self.enums[tu] = enums jlib.log('Have populated fns and global_data. {len(enums)=}')
def main(): argv = sys.argv if len( sys.argv) < 2: # Use test args. for zlib_pdf in ( os.path.expanduser( '~/mupdf/thirdparty/zlib/zlib.3.pdf'), os.path.expanduser( '~/artifex/mupdf/thirdparty/zlib/zlib.3.pdf'), ): if os.path.isfile( zlib_pdf): break else: raise Exception( 'cannot find zlib.3.pdf') for command in [ f'trace {zlib_pdf}', f'convert -o zlib.3.pdf-%d.png {zlib_pdf}', f'draw -o zlib.3.pdf-%d.png -s tmf -v -y l -w 150 -R 30 -h 200 {zlib_pdf}', f'draw -o zlib.png -R 10 {zlib_pdf}', ]: if 0: # This breaks - looks like <colorspace> gets dropped and *m_internal is freed? main2( [None] + command.split()) else: jlib.log( 'running with test command: {command}') command = f'{argv[0]} {command}' jlib.log( f'running: {command}') e = os.system( f'{command}') assert not e, f'command failed: {command}' else: main2( sys.argv)
def set_dir_so(self, dir_so): ''' Sets self.dir_so and also updates self.cpp_flags etc. ''' dir_so = abspath(dir_so) self.dir_so = dir_so if 0: pass # lgtm [py/unreachable-statement] elif '-debug' in dir_so: self.cpp_flags = '-g' elif '-release' in dir_so: self.cpp_flags = '-O2 -DNDEBUG' elif '-memento' in dir_so: self.cpp_flags = '-g -DMEMENTO' else: self.cpp_flags = None jlib.log( 'Warning: unrecognised {dir_so=}, so cannot determine cpp_flags' ) # Set self.cpu and self.python_version. if state_.windows: # Infer from self.dir_so. m = re.match('shared-([a-z]+)(-(x[0-9]+))?(-py([0-9.]+))?$', os.path.basename(self.dir_so)) #log(f'self.dir_so={self.dir_so} {os.path.basename(self.dir_so)} m={m}') assert m, f'Failed to parse dir_so={self.dir_so!r} - should be *-x32|x64-pyA.B' self.cpu = Cpu(m.group(3)) self.python_version = m.group(5) #log('{self.cpu=} {self.python_version=} {dir_so=}') else: # Use Python we are running under. self.cpu = Cpu(cpu_name()) self.python_version = python_version()
def dump_ast(cursor, depth=0): indent = depth * 4 * ' ' for cursor2 in cursor.get_children(): jlib.log( indent * ' ' + '{cursor2.kind=} {cursor2.mangled_name=} {cursor2.displayname=} {cursor2.spelling=}' ) dump_ast(cursor2, depth + 1)
def main2( argv): arg1 = argv[1] fn = getattr( sys.modules[__name__], arg1, None) if not fn: print( f'cannot find {arg1}') usage() sys.exit(1) fn( argv[2:]) jlib.log( 'finished')
def method( self, structname, fnname): if structname.startswith( 'fz_'): ret = clip( fnname, ('fz_', 'pdf_')) if ret in ('stdin', 'stdout', 'stderr'): jlib.log( 'appending underscore to {ret=}') ret += '_' return ret if structname.startswith( 'pdf_'): return clip( fnname, ('fz_', 'pdf_')) assert 0, f'unrecognised structname={structname}'
def test(test_command, package_name, wheels, abis, pypi, pypi_test, py): ''' If on Windows and <py> is false, we run test() with a python for each wheel in <wheels>. test_command: . package_name: Can be None if we are using <wheels>. wheels: List of wheels, used if pypi is false. pypi: If true, install from pypi.org or test.pypi.org if pypi_test is true. pypi_test: If true, pypi installs from test.pypi.org. py: Python command. If None, on Windows we test ABI from each wheel, otherwise we use native python. ''' if not py and windows(): if wheels: # Test with each wheel. log('Testing for python implied by each wheel.') for wheel in wheels: name, version, py, none, cpu = parse_wheel(wheel) pyv = f'{py[2]}.{py[3]}' cpu_bits = 64 if cpu == 'win_amd64' else 32 py = f'py -{pyv}-{cpu_bits}' if package_name is None: package_name = name if pypi: test_pypi(test_command, package_name, pypi_test, py) else: test_local(test_command, wheels, py) else: assert pypi, f'No wheels specified; need to use *pypi.org.' assert package_name, f'No wheels specified; need package_name.' # Test with each ABI. for abi in abis: cpu, python_version, py = windows_python_from_abi(abi) jlib.log('Testing with {package_name=} {pypi_test=} {py=}') test_pypi(test_command, package_name, pypi_test, py) else: if not py: if windows(): log('Using default python "py".') py = 'py' else: log('Using default python {sys.executable=}.') py = sys.executable if pypi: test_pypi(test_command, package_name, pypi_test, py) else: test_local(test_command, wheels, py)
def find_wrappable_function_with_arg0_type(tu, structname): ''' Return list of fz_*() function names which could be wrapped as a method of our wrapper class for <structname>. The functions whose names we return, satisfy all of the following: First non-context param is <structname> (by reference, pointer or value). If return type is a fz_* struc (by reference, pointer or value), the corresponding wrapper class has a raw constructor. ''' find_wrappable_function_with_arg0_type_cache_populate(tu) ret = find_wrappable_function_with_arg0_type_cache.get(structname, []) if state.state_.show_details(structname): jlib.log('{structname=}: {len(ret)=}:') for i in ret: jlib.log(' {i}') return ret
def update_file_regress(text, filename, check_regression): ''' Behaves like jlib.update_file(), but if check_regression is true and <filename> already exists with different content from <text>, we show a diff and raise an exception. ''' text_old = jlib.update_file(text, filename, check_regression) if text_old: jlib.log( 'jlib.update_file() => {len(text_old)=}. {filename=} {check_regression}' ) if check_regression: if text_old is not None: # Existing content differs and <check_regression> is true. with open(f'{filename}-2', 'w') as f: f.write(text) jlib.log('Output would have changed: {filename}') jlib.system(f'diff -u {filename} {filename}-2', verbose=True, raise_errors=False, prefix=f'diff {os.path.relpath(filename)}: ', out='log') return Exception(f'Output would have changed: {filename}') else: jlib.log('Generated file unchanged: {filename}')
def test_internal(test_command, package_name, pip_install_arg, py): if test_command == '.': test_command = '' jlib.log('Testing {package_name=} {pip_install_arg=} {py=}') if not test_command: test_command = 'pypackage_test.py' code = (f'import {package_name}\n' f'print("Successfully imported {package_name}")\n') log(f'Testing default code for package_name={package_name}:\n{code}') with open(test_command, 'w') as f: f.write(code) venv_run( [ f'pip install {pip_install_arg}', f'python {test_command}', ], venv='pypackage-venv-test', py=py, clean=True, )
def is_pointer_to(type_, destination, verbose=False): ''' Returns true if <type> is a pointer to <destination>. We do this using text for <destination>, rather than a clang.cindex.Type or clang.cindex.Cursor, so that we can represent base types such as int or char without having clang parse system headers. This involves stripping any initial 'struct ' text. Also, clang's representation of mupdf's varying use of typedef, struct and forward-declarations is rather difficult to work with directly. type_: A clang.cindex.Type. destination: Text typename. ''' # Use cache - reduces time from 0.6s to 0.2. # key = type_.spelling, destination ret = is_pointer_to_cache.get(key) if ret is None: assert isinstance(type_, clang.cindex.Type) ret = None destination = util.clip(destination, 'struct ') if verbose: jlib.log('{type_.kind=}') if type_.kind == clang.cindex.TypeKind.POINTER: pointee = type_.get_pointee().get_canonical() d = cpp.declaration_text(pointee, '') d = util.clip(d, 'const ') d = util.clip(d, 'struct ') if verbose: jlib.log('{destination!r=} {d!r=}') ret = d == f'{destination} ' or d == f'const {destination} ' is_pointer_to_cache[key] = ret return ret
def trace( argv): password = '' layout_w = mupdf.FZ_DEFAULT_LAYOUT_W layout_h = mupdf.FZ_DEFAULT_LAYOUT_H layout_em = mupdf.FZ_DEFAULT_LAYOUT_EM layout_css = None layout_use_doc_css = 1 use_display_list = 0 argv_i = 0 while 1: jlib.log( '{argv_i=} {argv[ argv_i]=}') arg = argv[ argv_i] if arg == '-p': password = next( opt) elif arg == '-W': argv_i += 1 layout_w = float( argv[argv_i]) elif arg == '-H': argv_i += 1 layout_h = float( argv[argv_i]) elif arg == '-S': argv_i += 1 layout_em = float( argv[argv_i]) elif arg == '-U': argv_i += 1 layout_css = argv[argv_i] elif arg == '-X': layout_use_doc_css = 0 elif arg == '-d': use_display_list = 1 else: break argv_i += 1 #jlib.log( '{argv_i=} {len( argv)=}') if argv_i == len( argv): trace_usage() if layout_css: buffer_ = mupdf.Buffer( layout_css) mupdf.mupdf_set_user_css( buffer_.string_from_buffer()) mupdf.set_use_document_css( layout_use_doc_css) for argv_i in range( argv_i, len( argv)): arg = argv[ argv_i] doc = mupdf.Document( arg) if doc.needs_password(): doc.authenticate_password( password) doc.layout_document( layout_w, layout_h, layout_em) print( f'<document filename="{arg}">') count = doc.count_pages() if argv_i + 1 < len( argv) and mupdf.is_page_range( argv[ argv_i+1]): argv_i += 1 trace_runrange( use_display_list, doc, count, argv[ argv_i]) else: trace_runrange( use_display_list, doc, count, '1-N') print( '<document>')
def build_swig( state_, build_dirs, generated, language='python', swig_command='swig', check_regress=False, force_rebuild=False, ): ''' Builds python or C# wrappers for all mupdf_* functions and classes, by creating a .i file that #include's our generated C++ header files and running swig. build_dirs A BuildDirs instance. generated. A Generated instance. language The output language, must be 'python' or 'csharp'. swig Location of swig binary. check_regress If true, we fail with error if generated .i file already exists and differs from our new content. ''' assert isinstance(state_, state.State) jlib.log('{=build_dirs type(build_dirs)}') assert isinstance(build_dirs, state.BuildDirs), type(build_dirs) assert isinstance(generated, cpp.Generated), type(generated) assert language in ('python', 'csharp') # Find version of swig. (We use quotes around <swig> to make things work on # Windows.) try: t = jlib.system(f'"{swig_command}" -version', out='return') except Exception as e: if state_.windows: raise Exception( 'swig failed; on Windows swig can be auto-installed with: --swig-windows-auto' ) from e else: raise m = re.search('SWIG Version ([0-9]+)[.]([0-9]+)[.]([0-9]+)', t) assert m swig_major = int(m.group(1)) # Create a .i file for SWIG. # common = f''' #include <stdexcept> #include "mupdf/functions.h" #include "mupdf/classes.h" #include "mupdf/classes2.h" ''' if language == 'csharp': common += textwrap.dedent(f''' /* This is required otherwise compiling the resulting C++ code fails with: error: use of undeclared identifier 'SWIG_fail' But no idea whether it is the 'correct' thing to do; seems odd that SWIG doesn't define SWIG_fail itself. */ #define SWIG_fail throw std::runtime_error( e.what()); ''') if language == 'python': common += textwrap.dedent(f''' /* Support for extracting buffer data into a Python bytes. */ PyObject* buffer_extract_bytes(fz_buffer* buffer) {{ unsigned char* c = NULL; /* We mimic the affects of fz_buffer_extract(), which leaves the buffer with zero capacity. */ size_t len = mupdf::buffer_storage(buffer, &c); PyObject* ret = PyBytes_FromStringAndSize((const char*) c, (Py_ssize_t) len); if (ret) {{ mupdf::clear_buffer(buffer); mupdf::trim_buffer(buffer); }} return ret; }} /* Creates Python bytes from copy of raw data. */ PyObject* raw_to_python_bytes(const unsigned char* c, size_t len) {{ return PyBytes_FromStringAndSize((const char*) c, (Py_ssize_t) len); }} /* Creates Python bytes from copy of raw data. */ PyObject* raw_to_python_bytes(const void* c, size_t len) {{ return PyBytes_FromStringAndSize((const char*) c, (Py_ssize_t) len); }} /* The SWIG wrapper for this function returns a SWIG proxy for a 'const unsigned char*' pointing to the raw data of a python bytes. This proxy can then be passed from Python to functions that take a 'const unsigned char*'. For example to create a MuPDF fz_buffer* from a copy of a Python bytes instance: bs = b'qwerty' buffer_ = mupdf.new_buffer_from_copied_data(mupdf.python_bytes_data(bs), len(bs)) */ const unsigned char* python_bytes_data(const unsigned char* PYTHON_BYTES_DATA, size_t PYTHON_BYTES_SIZE) {{ return PYTHON_BYTES_DATA; }} /* Casts an integer to a pdf_obj*. Used to convert SWIG's int values for PDF_ENUM_NAME_* into PdfObj's. */ pdf_obj* obj_enum_to_obj(int n) {{ return (pdf_obj*) (intptr_t) n; }} /* SWIG-friendly alternative to ppdf_set_annot_color(). */ void ppdf_set_annot_color2(pdf_annot *annot, int n, float color0, float color1, float color2, float color3) {{ float color[] = {{ color0, color1, color2, color3 }}; return mupdf::ppdf_set_annot_color(annot, n, color); }} /* SWIG-friendly alternative to ppdf_set_annot_color(). */ void ppdf_set_annot_interior_color2(pdf_annot *annot, int n, float color0, float color1, float color2, float color3) {{ float color[] = {{ color0, color1, color2, color3 }}; return mupdf::ppdf_set_annot_color(annot, n, color); }} /* SWIG-friendly alternative to mfz_fill_text(). */ void mfz_fill_text2( mupdf::Device& dev, const mupdf::Text& text, mupdf::Matrix& ctm, const mupdf::Colorspace& colorspace, float color0, float color1, float color2, float color3, float alpha, mupdf::ColorParams& color_params ) {{ float color[] = {{color0, color1, color2, color3}}; return mfz_fill_text(dev, text, ctm, colorspace, color, alpha, color_params); }} std::vector<unsigned char> mfz_memrnd2(int length) {{ std::vector<unsigned char> ret(length); mupdf::mfz_memrnd(&ret[0], length); return ret; }} ''') common += textwrap.dedent(f''' /* SWIG-friendly alternative to fz_runetochar(). */ std::vector<unsigned char> runetochar2(int rune) {{ std::vector<unsigned char> buffer(10); int n = mupdf::runetochar((char*) &buffer[0], rune); assert(n < sizeof(buffer)); buffer.resize(n); return buffer; }} /* SWIG-friendly alternatives to fz_make_bookmark() and fz_lookup_bookmark(), using long long instead of fz_bookmark because SWIG appears to treat fz_bookmark as an int despite it being a typedef for intptr_t, so ends up slicing. */ long long unsigned make_bookmark2(fz_document* doc, fz_location loc) {{ fz_bookmark bm = mupdf::make_bookmark(doc, loc); return (long long unsigned) bm; }} long long unsigned mfz_make_bookmark2(fz_document* doc, fz_location loc) {{ return make_bookmark2(doc, loc); }} fz_location lookup_bookmark2(fz_document *doc, long long unsigned mark) {{ return mupdf::lookup_bookmark(doc, (fz_bookmark) mark); }} fz_location mfz_lookup_bookmark2(fz_document *doc, long long unsigned mark) {{ return lookup_bookmark2(doc, mark); }} struct convert_color2_dv {{ float dv0; float dv1; float dv2; float dv3; }}; /* SWIG-friendly alternative for fz_convert_color(). */ void convert_color2( fz_colorspace *ss, const float *sv, fz_colorspace *ds, convert_color2_dv* dv, fz_colorspace *is, fz_color_params params ) {{ mupdf::convert_color(ss, sv, ds, &dv->dv0, is, params); }} /* SWIG-friendly support for fz_set_warning_callback() and fz_set_error_callback(). */ struct SetWarningCallback {{ SetWarningCallback( void* user=NULL) {{ this->user = user; mupdf::set_warning_callback( s_print, this); }} virtual void print( const char* message) {{ }} static void s_print( void* self0, const char* message) {{ SetWarningCallback* self = (SetWarningCallback*) self0; return self->print( message); }} void* user; }}; struct SetErrorCallback {{ SetErrorCallback( void* user=NULL) {{ this->user = user; mupdf::set_error_callback( s_print, this); }} virtual void print( const char* message) {{ }} static void s_print( void* self0, const char* message) {{ SetErrorCallback* self = (SetErrorCallback*) self0; return self->print( message); }} void* user; }}; ''') common += generated.swig_cpp common += translate_ucdn_macros(build_dirs) text = '' if state_.windows: # 2022-02-24: Director classes break Windows builds at the moment. pass else: text += '%module(directors="1") mupdf\n' for i in generated.virtual_fnptrs: text += f'%feature("director") {i};\n' text += f'%feature("director") SetWarningCallback;\n' text += f'%feature("director") SetErrorCallback;\n' text += textwrap.dedent(''' %feature("director:except") { if ($error != NULL) { throw Swig::DirectorMethodException(); } } ''') for fnname in generated.c_functions: if fnname in ('pdf_annot_type', 'pdf_widget_type'): # These are also enums which we don't want to ignore. SWIGing the # functions is hopefully harmless. pass elif 0 and fnname == 'pdf_string_from_annot_type': # causes duplicate symbol with classes2.cpp and python. pass else: text += f'%ignore {fnname};\n' for i in ( 'fz_append_vprintf', 'fz_error_stack_slot', 'fz_format_string', 'fz_vsnprintf', 'fz_vthrow', 'fz_vwarn', 'fz_write_vprintf', ): text += f'%ignore {i};\n' text += f'%ignore m{i};\n' text += textwrap.dedent(f''' // Not implemented in mupdf.so: fz_colorspace_name_process_colorants %ignore fz_colorspace_name_process_colorants; %ignore fz_open_file_w; %ignore {util.rename.function('fz_append_vprintf')}; %ignore {util.rename.function('fz_error_stack_slot_s')}; %ignore {util.rename.function('fz_format_string')}; %ignore {util.rename.function('fz_vsnprintf')}; %ignore {util.rename.function('fz_vthrow')}; %ignore {util.rename.function('fz_vwarn')}; %ignore {util.rename.function('fz_write_vprintf')}; %ignore {util.rename.function('fz_vsnprintf')}; %ignore {util.rename.function('fz_vthrow')}; %ignore {util.rename.function('fz_vwarn')}; %ignore {util.rename.function('fz_append_vprintf')}; %ignore {util.rename.function('fz_write_vprintf')}; %ignore {util.rename.function('fz_format_string')}; %ignore {util.rename.function('fz_open_file_w')}; // SWIG can't handle this because it uses a valist. %ignore {util.rename.function('Memento_vasprintf')}; // asprintf() isn't available on windows, so exclude Memento_asprintf because // it is #define-d to asprintf. %ignore {util.rename.function('Memento_asprintf')}; // Might prefer to #include mupdf/exceptions.h and make the // %exception block below handle all the different exception types, // but swig-3 cannot parse 'throw()' in mupdf/exceptions.h. // // So for now we just #include <stdexcept> and handle // std::exception only. %include "typemaps.i" %include "cpointer.i" // This appears to allow python to call fns taking an int64_t. %include "stdint.i" %{{ {common} %}} %include exception.i %include std_string.i %include carrays.i %include cdata.i %include std_vector.i {"%include argcargv.i" if language=="python" else ""} %array_class(unsigned char, uchar_array); %include <cstring.i> %cstring_output_allocate(char **OUTPUT, free($1)); namespace std {{ %template(vectoruc) vector<unsigned char>; %template(vectori) vector<int>; %template(vectors) vector<std::string>; %template(vectorq) vector<mupdf::{util.rename.class_("fz_quad")}>; }}; // Make sure that operator++() gets converted to __next__(). // // Note that swig already seems to do: // // operator* => __ref__ // operator== => __eq__ // operator!= => __ne__ // operator-> => __deref__ // // Just need to add this method to containers that already have // begin() and end(): // def __iter__( self): // return CppIterator( self) // %rename(__increment__) *::operator++; %array_functions(unsigned char, bytes); ''') text += textwrap.dedent(f''' %exception {{ try {{ $action }} ''') if not state_.windows: # Directors not currently supported on Windows. text += textwrap.dedent(f''' catch (Swig::DirectorException &e) {{ SWIG_fail; }} ''') text += textwrap.dedent(f''' catch(std::exception& e) {{ SWIG_exception(SWIG_RuntimeError, e.what()); }} catch(...) {{ SWIG_exception(SWIG_RuntimeError, "Unknown exception"); }} }} ''') text += textwrap.dedent(f''' // Ensure SWIG handles OUTPUT params. // %include "cpointer.i" // Don't wrap raw fz_*() functions. %rename("$ignore", regexmatch$name="^fz_", %$isfunction, %$not %$ismember) ""; ''') if swig_major < 4: text += textwrap.dedent(f''' // SWIG version is less than 4 so swig is not able to copy // across comments from header file into generated code. The // next best thing is to use autodoc to make swig at least show // some generic information about arg types. // %feature("autodoc", "3"); ''') text += textwrap.dedent(f''' // Tell swig about pdf_clean_file()'s (int,argv)-style args: %apply (int ARGC, char **ARGV) {{ (int retainlen, char *retainlist[]) }} ''') if language == 'python': text += textwrap.dedent(''' %include pybuffer.i /* Convert Python bytes to (const unsigned char*, size_t) pair for python_bytes_data(). */ %pybuffer_binary(const unsigned char* PYTHON_BYTES_DATA, size_t PYTHON_BYTES_SIZE); ''') text += common if language == 'python': text += textwrap.dedent(f''' %pointer_functions(int, pint); %pythoncode %{{ def Document_lookup_metadata(self, key): """ Python implementation override of Document.lookup_metadata(). Returns string or None if not found. """ e = new_pint() ret = lookup_metadata(self.m_internal, key, e) e = pint_value(e) if e < 0: return None return ret Document.lookup_metadata = Document_lookup_metadata def PdfDocument_lookup_metadata(self, key): """ Python implementation override of PdfDocument.lookup_metadata(). Returns string or None if not found. """ e = new_pint() ret = ppdf_lookup_metadata(self.m_internal, key, e) e = pint_value(e) if e < 0: return None return ret PdfDocument.lookup_metadata = PdfDocument_lookup_metadata ''') if language == 'python': # Make some additions to the generated Python module. # # E.g. python wrappers for functions that take out-params should return # tuples. # text += generated.swig_python text += textwrap.dedent(''' import re # Wrap parse_page_range() to fix SWIG bug where a NULL return # value seems to mess up the returned list - we end up with ret # containing two elements rather than three, e.g. [0, 2]. This # occurs with SWIG-3.0; maybe fixed in SWIG-4? # w_parse_page_range = parse_page_range def parse_page_range(s, n): ret = w_parse_page_range(s, n) if len(ret) == 2: return None, 0, 0 else: return ret[0], ret[1], ret[2] # Provide native python implementation of format_output_path() (-> # fz_format_output_path). # def format_output_path( format, page): m = re.search( '(%[0-9]*d)', format) if m: ret = format[ :m.start(1)] + str(page) + format[ m.end(1):] else: dot = format.rfind( '.') if dot < 0: dot = len( format) ret = format[:dot] + str(page) + format[dot:] return ret class IteratorWrap: """ This is a Python iterator for containers that have C++-style begin() and end() methods that return iterators. Iterators must have the following methods: __increment__(): move to next item in the container. __ref__(): return reference to item in the container. Must also be able to compare two iterators for equality. """ def __init__( self, container): self.container = container self.pos = None self.end = container.end() def __iter__( self): return self def __next__( self): # for python2. if self.pos is None: self.pos = self.container.begin() else: self.pos.__increment__() if self.pos == self.end: raise StopIteration() return self.pos.__ref__() def next( self): # for python3. return self.__next__() # The auto-generated Python class method Buffer.buffer_extract() # returns (size, data). # # But these raw values aren't particularly useful to Python code so # we change the method to return a Python bytes instance instead, # using the special C function buffer_storage_bytes() defined # above. # # We make the original method available as # Buffer.buffer_extract_raw(); this can be used to create a # mupdf.Stream by passing the raw values back to C++ with: # # data, size = buffer_.buffer_extract_raw() # stream = mupdf.Stream(data, size)) # # We don't provide a similar wrapper for Buffer.buffer_storage() # because we can't create a Python bytes object that # points into the buffer's storage. We still provide # Buffer.buffer_storage_raw() just in case there is a need for # Python code that can pass the raw (data, size) back in to C. # Buffer.buffer_extract_raw = Buffer.buffer_extract def Buffer_buffer_extract(self): """ Returns buffer data as a Python bytes instance, leaving the buffer empty. Note that this will make a copy of the underlying data. """ return buffer_extract_bytes(self.m_internal) Buffer.buffer_extract = Buffer_buffer_extract Buffer.buffer_storage_raw = Buffer.buffer_storage #delattr(Buffer, 'buffer_storage') def Buffer_buffer_storage(self): raise Exception("Buffer.buffer_storage() is not available; use Buffer.buffer_storage_raw() to get (size, data) where <data> is SWIG wrapper for buffer's 'unsigned char*' storage") Buffer.buffer_storage = Buffer_buffer_storage # Overwrite Buffer.new_buffer_from_copied_data() to take Python Bytes instance. # def Buffer_new_buffer_from_copied_data(bytes_): buffer_ = new_buffer_from_copied_data(python_bytes_data(bytes_), len(bytes_)) return Buffer(buffer_) Buffer.new_buffer_from_copied_data = Buffer_new_buffer_from_copied_data def mpdf_dict_getl(obj, *tail): """ Python implementation of pdf_dict_getl(fz_context *ctx, pdf_obj *obj, ...), because SWIG doesn't handle variadic args. """ for key in tail: if not obj.m_internal: break obj = obj.dict_get(key) assert isinstance(obj, PdfObj) return obj PdfObj.dict_getl = mpdf_dict_getl def mpdf_dict_putl(obj, val, *tail): """ Python implementation of pdf_dict_putl(fz_context *ctx, pdf_obj *obj, pdf_obj *val, ...) because SWIG doesn't handle variadic args. """ if obj.is_indirect(): obj = obj.resolve_indirect_chain() if not obj.is_dict(): raise Exception(f'not a dict: {obj}') if not tail: return doc = obj.get_bound_document() for key in tail[:-1]: next_obj = obj.dict_get(key) if not next_obj.m_internal: # We have to create entries next_obj = doc.new_dict(1) obj.dict_put(key, next_obj) obj = next_obj key = tail[-1] obj.dict_put(key, val) PdfObj.dict_putl = mpdf_dict_putl def mpdf_dict_putl_drop(obj, *tail): raise Exception('mupdf.PdfObj.dict_putl_drop() is unsupported and unnecessary in Python because reference counting is automatic. Instead use mupdf.PdfObj.dict_putl()') PdfObj.dict_putl_drop = mpdf_dict_putl_drop def ppdf_set_annot_color(annot, color): """ Python implementation of pdf_set_annot_color() using ppdf_set_annot_color2(). """ if isinstance(color, float): ppdf_set_annot_color2(annot, 1, color, 0, 0, 0) elif len(color) == 1: ppdf_set_annot_color2(annot, 1, color[0], 0, 0, 0) elif len(color) == 2: ppdf_set_annot_color2(annot, 2, color[0], color[1], 0, 0) elif len(color) == 3: ppdf_set_annot_color2(annot, 3, color[0], color[1], color[2], 0) elif len(color) == 4: ppdf_set_annot_color2(annot, 4, color[0], color[1], color[2], color[3]) else: raise Exception( f'Unexpected color should be float or list of 1-4 floats: {color}') # Override PdfAnnot.set_annot_color() to use the above. def mpdf_set_annot_color(self, color): return ppdf_set_annot_color(self.m_internal, color) PdfAnnot.set_annot_color = mpdf_set_annot_color def ppdf_set_annot_interior_color(annot, color): """ Python version of pdf_set_annot_color() using ppdf_set_annot_color2(). """ if isinstance(color, float): ppdf_set_annot_interior_color2(annot, 1, color, 0, 0, 0) elif len(color) == 1: ppdf_set_annot_interior_color2(annot, 1, color[0], 0, 0, 0) elif len(color) == 2: ppdf_set_annot_interior_color2(annot, 2, color[0], color[1], 0, 0) elif len(color) == 3: ppdf_set_annot_interior_color2(annot, 3, color[0], color[1], color[2], 0) elif len(color) == 4: ppdf_set_annot_interior_color2(annot, 4, color[0], color[1], color[2], color[3]) else: raise Exception( f'Unexpected color should be float or list of 1-4 floats: {color}') # Override PdfAnnot.set_interiorannot_color() to use the above. def mpdf_set_annot_interior_color(self, color): return ppdf_set_annot_interior_color(self.m_internal, color) PdfAnnot.set_annot_interior_color = mpdf_set_annot_interior_color # Override mfz_fill_text() to handle color as a Python tuple/list. def mfz_fill_text(dev, text, ctm, colorspace, color, alpha, color_params): """ Python version of mfz_fill_text() using mfz_fill_text2(). """ color = tuple(color) + (0,) * (4-len(color)) assert len(color) == 4, f'color not len 4: len={len(color)}: {color}' return mfz_fill_text2(dev, text, ctm, colorspace, *color, alpha, color_params) Device.fill_text = mfz_fill_text # Override set_warning_callback() and set_error_callback() to # use Python classes derived from our SWIG Director classes # SetWarningCallback and SetErrorCallback (defined in C), so # that fnptrs can call Python code. # set_warning_callback_s = None set_error_callback_s = None def set_warning_callback2( printfn): class Callback( SetWarningCallback): def print( self, message): printfn( message) global set_warning_callback_s set_warning_callback_s = Callback() # Override set_error_callback(). def set_error_callback2( printfn): class Callback( SetErrorCallback): def print( self, message): printfn( message) global set_error_callback_s set_error_callback_s = Callback() set_warning_callback = set_warning_callback2 set_error_callback = set_error_callback2 ''') # Add __iter__() methods for all classes with begin() and end() methods. # for classname in generated.container_classnames: text += f'{classname}.__iter__ = lambda self: IteratorWrap( self)\n' # For all wrapper classes with a to_string() method, add a __str__() # method to the underlying struct's Python class, which calls # to_string_<structname>(). # # E.g. this allows Python code to print a mupdf.fz_rect instance. # # [We could instead call our generated to_string() and rely on overloading, # but this will end up switching on the type in the SWIG code.] # for struct_name in generated.to_string_structnames: text += f'{struct_name}.__str__ = lambda s: to_string_{struct_name}(s)\n' # For all wrapper classes with a to_string() method, add a __str__() method # to the Python wrapper class, which calls the class's to_string() method. # # E.g. this allows Python code to print a mupdf.Rect instance. # for struct_name in generated.to_string_structnames: text += f'{util.rename.class_(struct_name)}.__str__ = lambda self: self.to_string()\n' text += '%}\n' if 1: # lgtm [py/constant-conditional-expression] # This is a horrible hack to avoid swig failing because # include/mupdf/pdf/object.h defines an enum which contains a #include. # # Would like to pre-process files in advance so that swig doesn't see # the #include, but this breaks swig in a different way - swig cannot # cope with some code in system headers. # # So instead we copy include/mupdf/pdf/object.h into # {build_dirs.dir_mupdf}/platform/python/include/mupdf/pdf/object.h, # manually expanding the #include using a Python .replace() call. Then # we specify {build_dirs.dir_mupdf}/platform/python/include as the # first include path so that our modified mupdf/pdf/object.h will get # included in preference to the original. # os.makedirs( f'{build_dirs.dir_mupdf}/platform/python/include/mupdf/pdf', exist_ok=True) with open(f'{build_dirs.dir_mupdf}/include/mupdf/pdf/object.h') as f: o = f.read() with open( f'{build_dirs.dir_mupdf}/include/mupdf/pdf/name-table.h') as f: name_table_h = f.read() oo = o.replace('#include "mupdf/pdf/name-table.h"\n', name_table_h) assert oo != o jlib.update_file( oo, f'{build_dirs.dir_mupdf}/platform/python/include/mupdf/pdf/object.h' ) swig_i = f'{build_dirs.dir_mupdf}/platform/{language}/mupdfcpp_swig.i' include1 = f'{build_dirs.dir_mupdf}/include/' include2 = f'{build_dirs.dir_mupdf}/platform/c++/include' swig_cpp = f'{build_dirs.dir_mupdf}/platform/{language}/mupdfcpp_swig.cpp' swig_py = f'{build_dirs.dir_so}/mupdf.py' os.makedirs(f'{build_dirs.dir_mupdf}/platform/{language}', exist_ok=True) os.makedirs(f'{build_dirs.dir_so}', exist_ok=True) util.update_file_regress(text, swig_i, check_regress) # Try to disable some unhelpful SWIG warnings;. unfortunately this doesn't # seem to have any effect. disable_swig_warnings = [ 201, # Warning 201: Unable to find 'stddef.h' 314, # Warning 314: 'print' is a python keyword, renaming to '_print' 312, # Warning 312: Nested union not currently supported (ignored). 321, # Warning 321: 'max' conflicts with a built-in name in python 362, # Warning 362: operator= ignored 451, # Warning 451: Setting a const char * variable may leak memory. 503, # Warning 503: Can't wrap 'operator <<' unless renamed to a valid identifier. 512, # Warning 512: Overloaded method mupdf::DrawOptions::internal() const ignored, using non-const method mupdf::DrawOptions::internal() instead. ] disable_swig_warnings = map(str, disable_swig_warnings) disable_swig_warnings = '-w' + ','.join(disable_swig_warnings) if language == 'python': # Need -D_WIN32 on Windows because as of 2022-03-17, C++ code for # SWIG Directors support doesn't work on Windows so is inside #ifndef # _WIN32...#endif. # # Maybe use '^' on windows as equivalent to unix '\\' for multiline # ending? command = (textwrap.dedent(f''' "{swig_command}" {"-D_WIN32" if state_.windows else ""} -Wall -c++ {"-doxygen" if swig_major >= 4 else ""} -python {disable_swig_warnings} -module mupdf -outdir {os.path.relpath(build_dirs.dir_so)} -o {os.path.relpath(swig_cpp)} -includeall -I{os.path.relpath(build_dirs.dir_mupdf)}/platform/python/include -I{os.path.relpath(include1)} -I{os.path.relpath(include2)} -ignoremissing {os.path.relpath(swig_i)} ''').strip().replace('\n', "" if state_.windows else "\\\n")) rebuilt = jlib.build( (swig_i, include1, include2), (swig_cpp, swig_py), command, force_rebuild, ) jlib.log('{rebuilt=}') if rebuilt: swig_py_tmp = f'{swig_py}-' jlib.remove(swig_py_tmp) os.rename(swig_py, swig_py_tmp) with open(swig_py_tmp) as f: swig_py_content = f.read() if state_.openbsd: # Write Python code that will automatically load the required # .so's when mupdf.py is imported. Unfortunately this doesn't # work on Linux. prefix = textwrap.dedent(f''' import ctypes import os import importlib # The required .so's are in the same directory as this # Python file. On OpenBSD we can explicitly load these # .so's here using ctypes.cdll.LoadLibrary(), which # avoids the need for LD_LIBRARY_PATH to be defined. # # Unfortunately this doesn't work on Linux. # for leaf in ('libmupdf.so', 'libmupdfcpp.so', '_mupdf.so'): path = os.path.abspath(f'{{__file__}}/../{{leaf}}') #print(f'path={{path}}') #print(f'exists={{os.path.exists(path)}}') ctypes.cdll.LoadLibrary( path) #print(f'have loaded {{path}}') ''') swig_py_content = prefix + swig_py_content elif state_.windows: jlib.log('Adding prefix to {swig_cpp=}') prefix = '' postfix = '' with open(swig_cpp) as f: swig_py_content = prefix + swig_py_content + postfix # Change all our PDF_ENUM_NAME_* enums so that they are actually # PdfObj instances so that they can be used like any other PdfObj. # jlib.log('{len(generated.c_enums)=}') for enum_type, enum_names in generated.c_enums.items(): for enum_name in enum_names: if enum_name.startswith('PDF_ENUM_NAME_'): swig_py_content += f'{enum_name} = PdfObj( obj_enum_to_obj( {enum_name}))\n' with open(swig_py_tmp, 'w') as f: f.write(swig_py_content) os.rename(swig_py_tmp, swig_py) elif language == 'csharp': outdir = os.path.relpath(f'{build_dirs.dir_mupdf}/platform/csharp') os.makedirs(outdir, exist_ok=True) # Looks like swig comes up with 'mupdfcpp_swig_wrap.cxx' leafname. # # We include platform/python/include in order to pick up the modified # include/mupdf/pdf/object.h that we generate elsewhere. dllimport = 'mupdfcsharp.so' if state_.windows: # Would like to specify relative path to .dll with: # dllimport = os.path.relpath( f'{build_dirs.dir_so}/mupdfcsharp.dll') # but Windows/.NET doesn't seem to support this, despite # https://stackoverflow.com/questions/31807289 "how can i add a # swig generated c dll reference to a c sharp project". # dllimport = 'mupdfcsharp.dll' command = (textwrap.dedent(f''' "{swig_command}" {"-D_WIN32" if state_.windows else ""} -Wall -c++ -csharp {disable_swig_warnings} -module mupdf -namespace mupdf -dllimport {dllimport} -outdir {outdir} -outfile mupdf.cs -o {os.path.relpath(swig_cpp)} -includeall -I{os.path.relpath(build_dirs.dir_mupdf)}/platform/python/include -I{os.path.relpath(include1)} -I{os.path.relpath(include2)} -ignoremissing {os.path.relpath(swig_i)} ''').strip().replace('\n', "" if state_.windows else "\\\n")) rebuilt = jlib.build( (swig_i, include1, include2), (f'{outdir}/mupdf.cs', os.path.relpath(swig_cpp)), command, force_rebuild, ) # fixme: use <rebuilt> line with language=='python' to avoid multiple # modifications to unchanged mupdf.cs? # # For classes that have our to_string() method, override C#'s # ToString() to call to_string(). with open(f'{outdir}/mupdf.cs') as f: cs = f.read() cs2 = re.sub( '(( *)public string to_string[(][)])', '\\2public override string ToString() { return to_string(); }\n\\1', cs, ) jlib.log('{len(cs)=}') jlib.log('{len(cs2)=}') assert cs2 != cs, f'Failed to add toString() methods.' jlib.log('{len(generated.swig_csharp)=}') assert len(generated.swig_csharp) cs2 += generated.swig_csharp jlib.update_file(cs2, f'{build_dirs.dir_so}/mupdf.cs') #jlib.copy(f'{outdir}/mupdf.cs', f'{build_dirs.dir_so}/mupdf.cs') jlib.log('{rebuilt=}') else: assert 0
def main(): sdist = None wheels = [] pypi_test = 1 abis = None outdir = 'pypackage-out' remote = None if windows(): abis = ['x32-38', 'x32-39', 'x64-38', 'x64-39'] else: abis = ['37', '38', '39'] manylinux_container_name = None manylinux_install_docker = False manylinux_docker_image = None manylinux_pull_docker_image = None parser = jlib.Arg( '', required=1, help=__doc__, subargs=[ jlib.Arg( 'abis <abis>', help=f''' Set ABIs to build, comma-separated. Default is {abis}. ''', ), jlib.Arg( 'build', help='Build wheels.', multi=True, subargs=[ jlib.Arg( '-r <uri>', help=''' Build on specified remote machine [<user>@]<host>:[<directory>] and copy them back to local machine. ''', ), jlib.Arg( '-a <abis>', help='Set ABIs to build remotely, comma-separated.'), jlib.Arg( '-t', help=''' Run basic "test ." import test. On Windows this is is done for each wheel we have built by running "py <abi>"; otherwise we only test with native python. ''', ), ], ), jlib.Arg( 'pypi-test <test>', help='Whether to use test.pypi.org.', ), jlib.Arg( 'remote', multi=True, help=''' Sync to and run pypackage.py on remote machine. ''', subargs=[ jlib.Arg( '-s <files>', help=''' Comma-separated files to sync to remote machine. (pypackage.py is always synced.) ''', ), jlib.Arg( '-a <args>', help=''' Args to pass to pypackage.py on remote machine. ''', ), jlib.Arg( '<uri>', required=1, help=''' The remote machine: [<user>@]<host>:[<directory>] ''', ), ], ), jlib.Arg( 'sdist <path>', help=''' Name of preexisting sdist file to use or Python package directory (e.g. containing setup.py) in which to build a new sdist. ''', ), jlib.Arg('tag', help='Internal use only.'), jlib.Arg( 'test', help=''' Run test programme. If <command> is '.' or '', we instead run a temporary test programme that imports the package. Uses list of wheels from "wheels <pattern>" or "build ...", otherwise uses ABIs (default or as specified with 'abis ...'). ''', multi=True, subargs=[ jlib.Arg( '-p <python>', help=''' Set python to run. If not specified, on Windows we test with each wheel/abi's matching python, otherwise we use default python. ''', ), jlib.Arg( '--pypi <package-name>', help=''' Install specified package from pypi before running test; otherwise we install from local wheels. ''', ), jlib.Arg( '<command>', required=1, help='The test command to run.', ), ], ), jlib.Arg('upload', help='Upload sdist and wheels to pypi.'), jlib.Arg( 'wheels <pattern>', help=''' Specify pre-existing wheels using glob pattern. A "*" is appended if does not end with ".whl". ''', ), ], ) args = parser.parse(sys.argv[1:]) # Need to handle args in particular order because some depend on others. # if args.abis: abis_prev = abis abis = args.abis.split(',') log(f'Changing abis from {abis_prev} to {abis}') if args.sdist: if os.path.isfile(args.sdist.path): parse_sdist(args.sdist.path) sdist = args.sdist.path else: package_directory = args.sdist.path sdist = make_sdist(package_directory, outdir) if args.pypi_test: pypi_test = int(args.pypi_test.test) for build in args.build: assert sdist, f'build requires sdist' if build.r: # Remote build. user, host, directory = parse_remote(build.r.uri) local_dir = os.path.dirname(__file__) command = ( '' f'rsync -aP {local_dir}/pypackage.py {local_dir}/jlib.py {sdist} {user}{host}:{directory}' f' && echo rsync finished' f' && ssh {user}{host} ' f'"' f'{"cd "+directory if directory else "true"}' f' && ./pypackage.py sdist {os.path.basename(sdist)}') if build.a: command += f' abis {build.a.abis}' command += (f' build') if build.t: # Also run basic import test. command += " -t" # Add a command to rsync remote wheels back to local machine. # #log( '{sdist=}') sdist_prefix = os.path.basename(sdist) sdist_suffix = '.tar.gz' assert sdist_prefix.endswith(sdist_suffix) sdist_prefix = sdist_prefix[:-len(sdist_suffix)] command += ( f'"' f' && rsync -ai \'{user}{host}:{directory}pypackage-out/{sdist_prefix}*\' {outdir}/' ) system(command, prefix=f'{user}{host}:{directory}: ') else: # Local build. if windows(): wheels = make_windows(sdist, abis, outdir) else: wheels = make_unix( sdist, abis, outdir, test_direct_install=False, install_docker=manylinux_install_docker, docker_image=manylinux_docker_image, pull_docker_image=manylinux_pull_docker_image, container_name=manylinux_container_name, ) if build.t: # Run basic import test. package_name, _ = parse_sdist(sdist) test('', package_name, wheels, abis, pypi=False, pypi_test=None, py=None) log(f'sdist: {sdist}') for wheel in wheels: log(f' wheel: {wheel}') if args.tag: print(f'tag: {make_tag()}') if args.wheels: pattern = args.wheels.pattern if not pattern.endswith('.whl'): pattern += '*' log(f'Have appended "*" to get pattern={pattern!r}') wheels_raw = glob.glob(pattern) if not wheels_raw: log(f'Warning: no matches found for wheels pattern {pattern!r}.') wheels = [] for wheel in wheels_raw: if wheel.endswith('.whl'): wheels.append(wheel) log(f'Found {len(wheels)} wheels with pattern {pattern}:') for wheel in wheels: log(f' {wheel}') if args.test: for test_ in args.test: pypi = False package_name = None python = test_.p.python if test_.p else None if test_.pypi: pypi = True package_name = test_.pypi.package_name test(test_.command, package_name, wheels, abis, pypi, pypi_test, python) if args.upload: assert sdist, f'Cannot upload because no sdist specified; use "sdist ...".' wheels = wheels_for_sdist(sdist, outdir) log(f'Uploading wheels ({len(wheels)} for sdist: {sdist!r}') for wheel in wheels: log(f' {wheel}') # We repeated on error, in case user enters the wrong password. while 1: try: venv_run( [ f'pip install twine', f'python -m twine upload --disable-progress-bar {"--repository testpypi" if pypi_test else ""} {sdist} {" ".join(wheels)}', ], bufsize=0, # So we see login/password prompts. raise_errors=True, ) except Exception as e: jlib.log('Failed to upload: {e=}') input(jlib.log_text('Press <enter> to retry... ').strip()) else: break for remote in args.remote: user, host, directory = parse_remote(remote.uri) local_dir = os.path.dirname(__file__) sync_files = f'{local_dir}/pypackage.py,{local_dir}/jlib.py' if remote.s: sync_files += f',{remote.s.files}' system( f'rsync -ai {sync_files.replace(",", " ")} {sdist if sdist else ""} {user}{host}:{directory}', prefix=f'rsync to {user}{host}:{directory}: ', ) if remote.a: remote_args = f'pypi-test {pypi_test} {remote.a.args}' system( f'ssh {user}{host} ' f'"' f'{"cd "+directory+" && " if directory else ""}' f'./pypackage.py {remote_args}' f'"', prefix=f'{user}{host}:{directory}: ', )
def _try_init_clang(self, version): if state_.openbsd: clang_bin = glob.glob(f'/usr/local/bin/clang-{version}') if not clang_bin: jlib.log('Cannot find {clang_bin=}', 1) return clang_bin = clang_bin[0] self.clang_version = version libclang_so = glob.glob(f'/usr/local/lib/libclang.so*') assert len(libclang_so) == 1 self.libclang_so = libclang_so[0] self.resource_dir = jlib.system( f'{clang_bin} -print-resource-dir', out='return', ).strip() self.include_path = os.path.join(self.resource_dir, 'include') #logx('{self.libclang_so=} {self.resource_dir=} {self.include_path=}') if os.environ.get('VIRTUAL_ENV'): clang.cindex.Config.set_library_file(self.libclang_so) return True for p in os.environ.get('PATH').split(':'): clang_bins = glob.glob(os.path.join(p, f'clang-{version}*')) if not clang_bins: continue clang_bins.sort() for clang_bin in clang_bins: e, clang_search_dirs = jlib.system( f'{clang_bin} -print-search-dirs', #verbose=log, out='return', raise_errors=False, ) if e: jlib.log('[could not find {clang_bin}: {e=}]') return if version == 10: m = re.search('\nlibraries: =(.+)\n', clang_search_dirs) assert m clang_search_dirs = m.group(1) clang_search_dirs = clang_search_dirs.strip().split(':') for i in ['/usr/lib', '/usr/local/lib'] + clang_search_dirs: for leaf in f'libclang-{version}.*so*', f'libclang.so.{version}.*': p = os.path.join(i, leaf) p = os.path.abspath(p) jlib.log('{p=}') libclang_so = glob.glob(p) if not libclang_so: continue # We have found libclang.so. self.libclang_so = libclang_so[0] jlib.log('Using {self.libclang_so=}') clang.cindex.Config.set_library_file(self.libclang_so) self.resource_dir = jlib.system( f'{clang_bin} -print-resource-dir', out='return', ).strip() self.include_path = os.path.join( self.resource_dir, 'include') self.clang_version = version return True
def draw(argv): global alphabits_graphics global alphabits_text global band_height global colorspace global files global fir global format_ global gamma_value global height global ignore_errors global invert global layer_config global layout_css global layout_em global layout_h global layout_use_doc_css global layout_w global lowmemory global min_line_width global no_icc global num_workers global num_workers global out global out_cs global output global output_file_per_page global output_format global password global proof_filename global quiet global resolution global res_specified global rotation global showfeatures global showmd5 global showmemory global showtime global spots global uselist global width info = trace_info() quiet = 0 items, argv = getopt.getopt( argv, 'qp:o:F:R:r:w:h:fB:c:e:G:Is:A:DiW:H:S:T:U:XLvPl:y:NO:') for option, value in items: if 0: pass elif option == '-q': quiet = 1 elif option == '-p': password = value elif option == '-o': output = value elif option == '-F': format_ = value elif option == '-R': rotation = float(value) elif option == '-r': resolution = float(output) res_specified = 1 elif option == '-w': width = float(value) elif option == '-h': height = float(value) elif option == '-f': fir = 1 elif option == '-B': band_height = int(value) elif option == '-c': out_cs = parse_colorspace(value) elif option == '-e': proof_filename = value elif option == '-G': gamma_value = float(value) elif option == '-I': invert += 1 elif option == '-W': layout_w = float(value) elif option == '-H': layout_h = float(value) elif option == '-S': layout_em = float(value) elif option == '-U': layout_css = value elif option == '-X': layout_use_doc_css = 0 elif option == '-O': spots = float(value) if not mupdf.FZ_ENABLE_SPOT_RENDERING: jlib.log( 'Spot rendering/Overprint/Overprint simulation not enabled in this build' ) spots = SPOTS_NONE elif option == '-s': if 't' in value: showtime += 1 if 'm' in value: showmemory += 1 if 'f' in value: showfeatures += 1 if '5' in value: showmd5 += 1 elif option == '-A': alphabits_graphics = int(value) sep = value.find('/') if sep >= 0: alphabits_text = int(value[sep + 1:]) else: alphabits_text = alphabits_graphics elif option == '-D': uselist = 0 elif option == '-l': min_line_width = float(value) elif option == '-i': ignore_errors = 1 elif option == '-N': no_icc = 1 elif option == '-T': num_workers = int(value) elif option == '-L': lowmemory = 1 elif option == '-P': bgprint.active = 1 elif option == '-y': layer_config = value elif option == '-v': print(f'mudraw version {mupdf.FZ_VERSION}') if not argv: usage() if num_workers > 0: if uselist == 0: printf('cannot use multiple threads without using display list') sys.exit(1) if band_height == 0: printf('Using multiple threads without banding is pointless') if bgprint.active: if uselist == 0: printf('cannot bgprint without using display list') sys.exit(1) if proof_filename: proof_buffer = mupdf.Buffer(proof_filename) proof_cs = mupdf.Colorspace(FZ_COLORSPACE_NONE, 0, None, proof_buffer) mupdf.set_text_aa_level(alphabits_text) mupdf.set_graphics_aa_level(alphabits_graphics) mupdf.set_graphics_min_line_width(min_line_width) if no_icc: mupdf.disable_icc() else: mupdf.enable_icc() if layout_css: buf = mupdf.Buffer(layout_css) mupdf.set_user_css(buf.string_from_buffer()) mupdf.set_use_document_css(layout_use_doc_css) # Determine output type if band_height < 0: print('Bandheight must be > 0') sys.exit(1) output_format = OUT_PNG if format_: jlib.log('{format=}') for i in range(len(suffix_table)): if format_ == suffix_table[i].suffix[1:]: output_format = suffix_table[i].format if spots == SPOTS_FULL and suffix_table[i].spots == 0: print( f'Output format {suffix_table[i].suffix[1:]} does not support spot rendering.' ) print('Doing overprint simulation instead.') spots = SPOTS_OVERPRINT_SIM break else: print(f'Unknown output format {format}') sys.exit(1) elif output: suffix = output i = 0 while 1: if i == len(suffix_table): break s = suffix.find(suffix_table[i].suffix) if s != -1: suffix = suffix_table[i].suffix[s + 1:] output_format = suffix_table[i].format_ if spots == SPOTS_FULL and suffix_table[i].spots == 0: print( 'Output format {suffix_table[i].suffix[1:]} does not support spot rendering' ) print('Doing overprint simulation instead.') spots = SPOTS_OVERPRINT_SIM i = 0 else: i += 1 if band_height: if output_format not in (OUT_PAM, OUT_PGM, OUT_PPM, OUT_PNM, OUT_PNG, OUT_PBM, OUT_PKM, OUT_PCL, OUT_PCLM, OUT_PS, OUT_PSD): print( 'Banded operation only possible with PxM, PCL, PCLM, PS, PSD, and PNG outputs' ) sys.exit(1) if showmd5: print('Banded operation not compatible with MD5') sys.exit(1) for i in range(len(format_cs_table)): if format_cs_table[i].format_ == output_format: if out_cs == CS_UNSET: out_cs = format_cs_table[i].default_cs for j in range(len(format_cs_table[i].permitted_cs)): if format_cs_table[i].permitted_cs[j] == out_cs: break else: print('Unsupported colorspace for this format') sys.exit(1) alpha = 1 if out_cs in (CS_MONO, CS_GRAY, CS_GRAY_ALPHA): colorspace = mupdf.Colorspace(mupdf.Colorspace.Fixed_GRAY) alpha = (out_cs == CS_GRAY_ALPHA) elif out_cs in (CS_RGB, CS_RGB_ALPHA): colorspace = mupdf.Colorspace(mupdf.Colorspace.Fixed_RGB) alpha = (out_cs == CS_RGB_ALPHA) elif out_cs in (CS_CMYK, CS_CMYK_ALPHA): colorspace = mupdf.Colorspace(mupdf.Colorspace.Fixed_CMYK) alpha = (out_cs == CS_CMYK_ALPHA) elif out_cs == CS_ICC: icc_buffer = mupdf.Buffer(icc_filename) colorspace = Colorspace(mupdf.FZ_COLORSPACE_NONE, 0, None, icc_buffer) alpha = 0 else: print('Unknown colorspace!') sys.exit(1) if out_cs != CS_ICC: colorspace = mupdf.Colorspace(colorspace) else: # Check to make sure this icc profile is ok with the output format */ okay = 0 for i in range(len(format_cs_table)): if format_cs_table[i].format == output_format: for j in range(len(format_cs_table[i].permitted_cs)): x = format_cs_table[i].permitted_cs[j] if x in (CS_MONO, CS_GRAY, CS_GRAY_ALPHA): if colorspace.colorspace_is_gray(): okay = 1 elif x in (CS_RGB, CS_RGB_ALPHA): if colorspace.colorspace_is_rgb(): okay = 1 elif x in (CS_CMYK, CS_CMYK_ALPHA): if colorspace.colorspace_is_cmyk(): okay = 1 if not okay: print( 'ICC profile uses a colorspace that cannot be used for this format' ) sys.exit(1) if output_format == OUT_SVG: # SVG files are always opened for each page. Do not open "output". pass elif output and (not output.startswith('-') or len(output) >= 2) and len(output) >= 1: if has_percent_d(output): output_file_per_page = 1 else: out = mupdf.Output(output, 0) else: quiet = 1 # automatically be quiet if printing to stdout if 0: # Windows specific code to make stdout binary. if output_format not in (OUT_TEXT, OUT_STEXT, OUT_HTML, OUT_XHTML, OUT_TRACE): setmode(fileno(stdout), O_BINARY) out = mupdf.Output(mupdf.Output.Fixed_STDOUT) filename = argv[0] if not output_file_per_page: file_level_headers() timing.count = 0 timing.total = 0 timing.min = 1 << 30 timing.max = 0 timing.mininterp = 1 << 30 timing.maxinterp = 0 timing.minpage = 0 timing.maxpage = 0 timing.minfilename = "" timing.maxfilename = "" if showtime and bgprint.active: timing.total = gettime() fz_optind = 0 while fz_optind < len(argv): filename = argv[fz_optind] fz_optind += 1 files += 1 doc = mupdf.Document(filename) if doc.needs_password(): if not doc.authenticate_password(password): raise Exception(f'cannot authenticate password: {filename}') # Once document is open check for output intent colorspace oi = doc.document_output_intent() if oi: # todo: fz_document_output_intent() can return NULL, and we don't handle this. # See if we had explicitly set a profile to render if out_cs != CS_ICC: # In this case, we want to render to the output intent # color space if the number of channels is the same if oi.colorspace_n() == colorspace.colorspace_n(): colorspace = oi doc.layout_document(layout_w, layout_h, layout_em) if layer_config: apply_layer_config(doc, layer_config) if fz_optind == len(argv) or not mupdf.is_page_range(argv[fz_optind]): drawrange(doc, "1-N") if fz_optind < len(argv) and mupdf.is_page_range(argv[fz_optind]): drawrange(doc, argv[fz_optind]) fz_optind += 1 bgprint_flush() if not output_file_per_page: file_level_trailers() if out: out.close_output() out = None if showtime and timing.count > 0: if bgprint.active: timing.total = gettime() - timing.total if files == 1: print( f'total {timing.total}ms / {timing.count} pages for an average of {timing.total / timing.count}ms' ) if bgprint.active: printf( f'fastest page {timing.minpage}: {timing.mininterp}ms (interpretation) {timing.min - timing.mininterp}ms (rendering) {timing.min}ms(total)' ) print( f'slowest page {timing.maxpage}: {timing.maxinterp}ms (interpretation) {timing.max - timing.maxinterp}ms (rendering) {timing.max}ms(total)' ) else: print(f'fastest page {timing.minpage}: {timing.min}ms') print(f'slowest page {timing.maxpage}: {timing.max}ms') else: print( f'total {timing.total}ms / {timing.count} pages for an average of {timing.total / timing.count}ms in {files} files' ) print( f'fastest page {timing.minpage}: {timing.min}ms ({timing.minfilename})' ) print( f'slowest page {timing.maxpage}: {timing.max}ms ({timing.maxfilename})' ) if showmemory: print( f'Memory use total={info.total} peak={info.peak} current={info.current}' ) return errored != 0
def has_refs(tu, type_): ''' Returns (offset, bits) if <type_> has a 'refs' member, otherwise False. offset: Byte offset of 'refs' or name of 'refs' for use with offsetof(), e.g. 'super.refs'. bits: Size of 'refs' in bits. ''' type0 = type_ type_ = type_.get_canonical() key = type_.spelling key = util.clip(key, 'struct ') ret = has_refs_cache.get(key, None) if ret is None: ret = False #jlib.log( 'Analysing {type0.spelling=} {type_.spelling=} {key=}') for prefix in ( 'fz_', 'pdf_', ): #jlib.log( '{type_.spelling=} {prefix=}') if key.startswith(prefix): #jlib.log( 'Type is a fz_ or pdf_ struct: {key=}') keep_name = f'{prefix}keep_{key[len(prefix):]}' keep_fn_cursor = state.state_.find_function(tu, keep_name, method=False) #jlib.log( '{keep_name=} {keep_fn_cursor=}') if keep_fn_cursor: #jlib.log( 'There is a keep() fn for this type so it uses reference counting: {keep_name=}') base_type_cursor = get_base_type(type_).get_declaration() if base_type_cursor.is_definition(): #jlib.log( 'Type definition is available so we look for .refs member: {key=}') for cursor in type_.get_fields(): name = cursor.spelling type2 = cursor.type.get_canonical() #jlib.log( '{name=} {type2.spelling=}') if name == 'refs' and type2.spelling == 'int': ret = 'refs', 32 break if name == 'storable' and type2.spelling == 'struct fz_storable': ret = 'storable.refs', 32 break else: #jlib.log('Definition is not available for {key=}') pass if not ret: if 0: jlib.log( 'Cannot find .refs member or we only have forward' ' declaration, so have to hard-code the size and offset' ' of the refs member.') if base_type_cursor.is_definition(): if key == 'pdf_document': ret = 'super.refs', 32 elif key == 'fz_pixmap': ret = 'storable.refs', 32 elif key in ( 'fz_colorspace', 'fz_image', ): return 'key_storable.storable.refs', 32 elif key == 'pdf_cmap': return 'storable.refs', 32 else: #jlib.log( 'No defintion available, i.e. forward decl only.') if key == 'pdf_obj': ret = 0, 16 elif key == 'fz_path': ret = 0, 8 elif key in ( 'fz_separations', 'fz_halftone', 'pdf_annot', 'pdf_graft_map', ): # Forward decl, first member is 'int regs;'. return 0, 32 elif key in ( 'fz_display_list', 'fz_glyph', 'fz_jbig2_globals', 'pdf_function', ): # Forward decl, first member is 'fz_storable storable;'. return 0, 32 if ret is None: # Need to hard-code info for this type. assert 0, jlib.expand_nv( '{key=} has {keep_name}() fn but is forward decl or we cannot find .regs,' ' and we have no hard-coded info about size and offset of .regs.' ' {type0.spelling=} {type_.spelling=} {base_type_cursor.spelling}' ) assert ret, f'{key} has {keep_name}() but have not found size/location of .refs member.' if type_.spelling in ( 'struct fz_document', 'struct fz_buffer', ): assert ret #jlib.log('Populating has_refs_cache with {key=} {ret=}') has_refs_cache[key] = ret return ret
try: import clang.cindex except ModuleNotFoundError as e: # On devuan, clang-python isn't on python3's path, but python2's # clang-python works fine with python3, so we deviously get the path by # running some python 2. # e, clang_path = jlib.system( 'python2 -c "import clang; print clang.__path__[0]"', out='return', raise_errors=0) if e == 0: jlib.log( 'Retrying import of clang using info from python2 {clang_path=}' ) sys.path.append(os.path.dirname(clang_path)) import clang.cindex else: raise except Exception as e: jlib.log( 'Warning: failed to import clang.cindex: {e=}\n' f'We need Clang Python to build MuPDF python.\n' f'Install with "pip install libclang" or use the --venv option, or:\n' f' OpenBSD: pkg_add py3-llvm\n' f' Linux:debian/devuan: apt install python-clang\n') clang = None
def get_args(tu, cursor, include_fz_context=False, skip_first_alt=False, verbose=False): ''' Yields Arg instance for each arg of the function at <cursor>. Args: tu: A clang.cindex.TranslationUnit instance. cursor: Clang cursor for the function. include_fz_context: If false, we skip args that are 'struct fz_context*' skip_first_alt: If true, we skip the first arg with .alt set. verbose: . ''' # We are called a few times for each function, and the calculations we do # are slow, so we cache the returned items. E.g. this reduces total time of # --build 0 from 3.5s to 2.1s. # key = tu, cursor.location.file, cursor.location.line, include_fz_context, skip_first_alt ret = get_args_cache.get(key) if not verbose and state.state_.show_details(cursor.spelling): verbose = True #jlib.log('Verbose because {cursor.spelling=}') if ret is None: ret = [] i = 0 i_alt = 0 separator = '' for arg_cursor in cursor.get_arguments(): assert arg_cursor.kind == clang.cindex.CursorKind.PARM_DECL if not include_fz_context and is_pointer_to( arg_cursor.type, 'fz_context'): # Omit this arg because our generated mupdf_*() wrapping functions # use internalContextGet() to get a context. continue name = arg_cursor.mangled_name or f'arg_{i}' if 0 and name == 'stmofsp': verbose = True alt = None out_param = False base_type_cursor, base_typename, extras = get_extras( tu, arg_cursor.type) if verbose: jlib.log('Looking at arg. {extras=}') if extras: if verbose: jlib.log( '{extras.opaque=} {base_type_cursor.kind=} {base_type_cursor.is_definition()=}' ) if extras.opaque: # E.g. we don't have access to defintion of fz_separation, # but it is marked in classes.classextras with opaque=true, # so there will be a wrapper class. alt = base_type_cursor elif (1 and base_type_cursor.kind == clang.cindex.CursorKind.STRUCT_DECL #and base_type_cursor.is_definition() ): alt = base_type_cursor if verbose: jlib.log( '{arg_cursor.type.spelling=} {base_typename=} {arg_cursor.type.kind=} {get_base_typename(arg_cursor.type)=}' ) if alt: if is_double_pointer(arg_cursor.type): out_param = True elif get_base_typename( arg_cursor.type) in ('char', 'unsigned char', 'signed char', 'void', 'FILE'): if is_double_pointer(arg_cursor.type): if verbose: jlib.log( 'setting outparam: {cursor.spelling=} {arg_cursor.type=}' ) if cursor.spelling == 'pdf_clean_file': # Don't mark char** argv as out-param, which will also # allow us to tell swig to convert python lists into # (argc,char**) pair. pass else: if verbose: jlib.log('setting out_param to true') out_param = True elif base_typename.startswith(('fz_', 'pdf_')): # Pointer to fz_ struct is not usually an out-param. if verbose: jlib.log( 'not out-param because arg is: {arg_cursor.displayname=} {base_type.spelling=} {extras}' ) elif arg_cursor.type.kind == clang.cindex.TypeKind.POINTER: pointee = arg_cursor.type.get_pointee() if verbose: jlib.log('clang.cindex.TypeKind.POINTER') if pointee.get_canonical( ).kind == clang.cindex.TypeKind.FUNCTIONPROTO: # Don't mark function-pointer args as out-params. if verbose: jlib.log('clang.cindex.TypeKind.FUNCTIONPROTO') elif pointee.is_const_qualified(): if verbose: jlib.log('is_const_qualified()') elif pointee.spelling == 'FILE': pass else: if verbose: jlib.log('setting out_param = True') out_param = True if alt: i_alt += 1 i += 1 if alt and skip_first_alt and i_alt == 1: continue arg = Arg(arg_cursor, name, separator, alt, out_param) ret.append(arg) if verbose: jlib.log('Appending {arg=}') separator = ', ' get_args_cache[key] = ret for arg in ret: yield arg
def make_outparam_helper_csharp( tu, cursor, fnname, fnname_wrapper, generated, main_name, ): ''' Write C# code for a convenient tuple-returning wrapper for MuPDF function that has out-params. We use the C# wrapper for our generated {main_name}_outparams() function. We don't attempt to handle functions that take unsigned char* args because these generally indicate sized binary data and cannot be handled generically. ''' def write(text): generated.swig_csharp.write(text) main_name = util.rename.function(cursor.mangled_name) return_void = cursor.result_type.spelling == 'void' if fnname == 'fz_buffer_extract': # Write custom wrapper that returns the binary data as a C# bytes # array, using the C# wrapper for buffer_extract_outparams_fn(fz_buffer # buf, buffer_extract_outparams outparams). # write('\n') write('// Custom C# wrapper for fz_buffer_extract().\n') write('public static class mupdf_Buffer_extract\n') write('{\n') write( ' public static byte[] buffer_extract(this mupdf.Buffer buffer)\n' ) write(' {\n') write( ' var outparams = new mupdf.buffer_storage_outparams();\n') write( ' uint n = mupdf.mupdf.buffer_storage_outparams_fn(buffer.m_internal, outparams);\n' ) write( ' var raw1 = mupdf.SWIGTYPE_p_unsigned_char.getCPtr(outparams.datap);\n' ) write( ' System.IntPtr raw2 = System.Runtime.InteropServices.HandleRef.ToIntPtr(raw1);\n' ) write(' byte[] ret = new byte[n];\n') write( ' // Marshal.Copy() raises exception if <raw2> is null even if <n> is zero.\n' ) write(' if (n == 0) return ret;\n') write( ' System.Runtime.InteropServices.Marshal.Copy(raw2, ret, 0, (int) n);\n' ) write(' buffer.clear_buffer();\n') write(' buffer.trim_buffer();\n') write(' return ret;\n') write(' }\n') write('}\n') write('\n') return # We don't attempt to generate wrappers for fns that take or return # 'unsigned char*' - swig does not treat these as zero-terminated strings, # and they are generally binary data so cannot be handled generically. # if parse.is_pointer_to(cursor.result_type, 'unsigned char'): jlib.log( f'Cannot generate C# out-param wrapper for {cursor.mangled_name} because it returns unsigned char*.', 1) return for arg in parse.get_args(tu, cursor): if parse.is_pointer_to(arg.cursor.type, 'unsigned char'): jlib.log( f'Cannot generate C# out-param wrapper for {cursor.mangled_name} because has unsigned char* arg.', 1) return if parse.is_pointer_to_pointer_to(arg.cursor.type, 'unsigned char'): jlib.log( f'Cannot generate C# out-param wrapper for {cursor.mangled_name} because has unsigned char** arg.', 1) return if arg.cursor.type.get_array_size() >= 0: jlib.log( f'Cannot generate C# out-param wrapper for {cursor.mangled_name} because has array arg.', 1) return if arg.cursor.type.kind == state.clang.cindex.TypeKind.POINTER: pointee = arg.cursor.type.get_pointee().get_canonical() if pointee.kind == state.clang.cindex.TypeKind.ENUM: jlib.log( f'Cannot generate C# out-param wrapper for {cursor.mangled_name} because has enum out-param arg.', 1) return if pointee.kind == state.clang.cindex.TypeKind.FUNCTIONPROTO: jlib.log( f'Cannot generate C# out-param wrapper for {cursor.mangled_name} because has fn-ptr arg.', 1) return if pointee.is_const_qualified(): # Not an out-param. jlib.log( f'Cannot generate C# out-param wrapper for {cursor.mangled_name} because has pointer-to-const arg.', 1) return if arg.cursor.type.get_pointee().spelling == 'FILE': jlib.log( f'Cannot generate C# out-param wrapper for {cursor.mangled_name} because has FILE* arg.', 1) return if pointee.spelling == 'void': jlib.log( f'Cannot generate C# out-param wrapper for {cursor.mangled_name} because has void* arg.', 1) return num_return_values = 0 if return_void else 1 for arg in parse.get_args(tu, cursor): if arg.out_param: num_return_values += 1 assert num_return_values if num_return_values > 7: # On linux, mono-csc can fail with: # System.NotImplementedException: tuples > 7 # jlib.log( f'Cannot generate C# out-param wrapper for {cursor.mangled_name} because would require > 7-tuple.' ) return # Write C# wrapper. arg0, _ = parse.get_first_arg(tu, cursor) if not arg0.alt: return write(f'\n') write( f'// Out-params extension method for C# class {util.rename.class_(arg0.alt.type.spelling)} (wrapper for MuPDF {arg0.alt.type.spelling}),\n' ) write( f'// adding class method {fnname_wrapper}() (wrapper for {fnname}())\n' ) write(f'// which returns out-params directly.\n') write(f'//\n') write(f'public static class mupdf_{main_name}_outparams_helper\n') write(f'{{\n') write(f' public static ') def write_type(alt, type_): if alt: write(f'mupdf.{util.rename.class_(alt.type.spelling)}') elif parse.is_pointer_to(type_, 'char'): write(f'string') else: text = cpp.declaration_text(type_, '').strip() if text == 'int16_t': text = 'short' elif text == 'int64_t': text = 'long' elif text == 'size_t': text = 'ulong' elif text == 'unsigned int': text = 'uint' write(f'{text}') # Generate the returned tuple. # if num_return_values > 1: write('(') sep = '' # Returned param, if any. if not return_void: return_alt = None base_type_cursor, base_typename, extras = parse.get_extras( tu, cursor.result_type) if extras: if extras.opaque: # E.g. we don't have access to defintion of fz_separation, # but it is marked in classextras with opaque=true, so # there will be a wrapper class. return_alt = base_type_cursor elif base_type_cursor.kind == state.clang.cindex.CursorKind.STRUCT_DECL: return_alt = base_type_cursor write_type(return_alt, cursor.result_type) sep = ', ' # Out-params. for arg in parse.get_args(tu, cursor): if arg.out_param: write(sep) write_type(arg.alt, arg.cursor.type.get_pointee()) if num_return_values > 1: write(f' {arg.name_csharp}') sep = ', ' if num_return_values > 1: write(')') # Generate function name and params. If first arg is a wrapper class we # use C#'s 'this' keyword to make this a member function of the wrapper # class. #jlib.log('outputs fn {fnname=}: is member: {"yes" if arg0.alt else "no"}') write(f' ') write(fnname_wrapper if arg0.alt else 'fn') write(f'(') if arg0.alt: write('this ') sep = '' for arg in parse.get_args(tu, cursor): if arg.out_param: continue write(sep) if arg.alt: # E.g. 'Document doc'. write( f'mupdf.{util.rename.class_(arg.alt.type.spelling)} {arg.name_csharp}' ) elif parse.is_pointer_to(arg.cursor.type, 'char'): write(f'string {arg.name_csharp}') else: text = cpp.declaration_text(arg.cursor.type, arg.name_csharp).strip() text = util.clip(text, 'const ') text = text.replace('int16_t ', 'short ') text = text.replace('int64_t ', 'long ') text = text.replace('size_t ', 'uint ') text = text.replace('unsigned int ', 'uint ') write(text) sep = ', ' write(f')\n') # Function body. # write(f' {{\n') # Create local outparams struct. write(f' var outparams = new mupdf.{main_name}_outparams();\n') write(f' ') # Generate function call. # # The C# *_outparams_fn() generated by swig is inside namespace mupdf { # class mupdf { ... } }, so we access it using the rather clumsy prefix # 'mupdf.mupdf.'. It will have been generated from a C++ function # (generate by us) which is in top-level namespace mupdf, but swig # appears to generate the same code even if the C++ function is not in # a namespace. # if not return_void: write(f'var ret = ') write(f'mupdf.mupdf.{main_name}_outparams_fn(') sep = '' for arg in parse.get_args(tu, cursor): if arg.out_param: continue write(f'{sep}{arg.name_csharp}') if arg.alt: extras = parse.get_fz_extras(tu, arg.alt.type.spelling) assert extras.pod != 'none' \ 'Cannot pass wrapper for {type_.spelling} as arg because pod is "none" so we cannot recover struct.' write('.internal_()' if extras.pod else '.m_internal') sep = ', ' write(f'{sep}outparams);\n') # Generate return of tuple. write(f' return ') if num_return_values > 1: write(f'(') sep = '' if not return_void: if return_alt: write( f'new mupdf.{util.rename.class_(return_alt.type.spelling)}(ret)' ) else: write(f'ret') sep = ', ' for arg in parse.get_args(tu, cursor): if arg.out_param: write(f'{sep}') type_ = arg.cursor.type.get_pointee() if arg.alt: write( f'new mupdf.{util.rename.class_(arg.alt.type.spelling)}(outparams.{arg.name_csharp})' ) elif 0 and parse.is_pointer_to(type_, 'char'): # This was intended to convert char* to string, but swig # will have already done that when making a C# version of # the C++ struct, and modern csc on Windows doesn't like # creating a string from a string for some reason. write(f'new string(outparams.{arg.name_csharp})') else: write(f'outparams.{arg.name_csharp}') sep = ', ' if num_return_values > 1: write(')') write(';\n') write(f' }}\n') write(f'}}\n')
def find_wrappable_function_with_arg0_type_cache_populate(tu): ''' Populates caches with wrappable functions. ''' global find_wrappable_function_with_arg0_type_cache global find_wrappable_function_with_arg0_type_excluded_cache if find_wrappable_function_with_arg0_type_cache: return t0 = time.time() find_wrappable_function_with_arg0_type_cache = dict() find_wrappable_function_with_arg0_type_excluded_cache = dict() for fnname, cursor in state.state_.find_functions_starting_with( tu, ('fz_', 'pdf_'), method=True): exclude_reasons = [] if fnname.startswith('fz_drop_') or fnname.startswith('fz_keep_'): continue if fnname.startswith('pdf_drop_') or fnname.startswith('pdf_keep_'): continue if cursor.type.is_function_variadic(): exclude_reasons.append(( MethodExcludeReason_VARIADIC, 'function is variadic', )) # Look at resulttype. # result_type = cursor.type.get_result().get_canonical() if result_type.kind == clang.cindex.TypeKind.POINTER: result_type = result_type.get_pointee().get_canonical() result_type = util.clip(result_type.spelling, 'struct ') if result_type.startswith(('fz_', 'pdf_')): result_type_extras = get_fz_extras(tu, result_type) if not result_type_extras: exclude_reasons.append(( MethodExcludeReason_NO_EXTRAS, f'no extras defined for result_type={result_type}', )) else: if not result_type_extras.constructor_raw: exclude_reasons.append(( MethodExcludeReason_NO_RAW_CONSTRUCTOR, f'wrapper for result_type={result_type} does not have raw constructor.', )) if not result_type_extras.copyable: exclude_reasons.append(( MethodExcludeReason_NOT_COPYABLE, f'wrapper for result_type={result_type} is not copyable.', )) # Look at args # i = 0 arg0_cursor = None for arg in get_args(tu, cursor): base_typename = get_base_typename(arg.cursor.type) if not arg.alt and base_typename.startswith(('fz_', 'pdf_')): if arg.cursor.type.get_canonical( ).kind == clang.cindex.TypeKind.ENUM: # We don't (yet) wrap fz_* enums, but for now at least we # still wrap functions that take fz_* enum parameters - # callers will have to use the fz_* type. # # For example this is required by mutool_draw.py because # mudraw.c calls fz_set_separation_behavior(). # jlib.logx( 'not excluding {fnname=} with enum fz_ param : {arg.cursor.spelling=} {arg.cursor.type.kind} {arg.cursor.type.get_canonical().kind=}' ) else: exclude_reasons.append(( MethodExcludeReason_NO_WRAPPER_CLASS, f'no wrapper class for arg i={i}: {arg.cursor.type.get_canonical().spelling} {arg.cursor.type.get_canonical().kind}', )) if i == 0: if arg.alt: arg0_cursor = arg.alt else: exclude_reasons.append(( MethodExcludeReason_FIRST_ARG_NOT_STRUCT, 'first arg is not fz_* struct', )) i += 1 if exclude_reasons: find_wrappable_function_with_arg0_type_excluded_cache[ fnname] = exclude_reasons #if fnname == 'fz_load_outline': # lgtm [py/unreachable-statement] if state.state_.show_details(fnname): jlib.log( 'Excluding {fnname=} from possible class methods because:') for i in exclude_reasons: jlib.log(' {i}') else: if i > 0: # <fnname> is ok to wrap. arg0 = arg0_cursor.type.get_canonical().spelling arg0 = util.clip(arg0, 'struct ') items = find_wrappable_function_with_arg0_type_cache.setdefault( arg0, []) items.append(fnname) jlib.logx( f'populating find_wrappable_function_with_arg0_type_cache took {time.time()-t0}s' )