| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902 |
- import jlib
- import textwrap
- def make_cppyy(
- state_,
- build_dirs,
- generated,
- ):
- path = f'{build_dirs.dir_so}/mupdf_cppyy.py'
- jlib.log( 'Updating {path}')
- text = ''
- text += textwrap.dedent( """
- '''
- MuPDF Python bindings using cppyy: https://cppyy.readthedocs.io
- Cppyy generates bindings at runtime, so we don't need to build a .so like SWIG.
- However we still need the mupdf.so (MuPDF C API) and mupdfcpp.so (MuPDF C++
- API) libraries to be present and accessible via LD_LIBRARY_PATH.
- Usage:
- import mupdf_cppyy
- mupdf = mupdf_cppyy.cppyy.gbl.mupdf
- document = mupdf.Document(...)
- Requirements:
- Install cppyy; for example:
- python -m pip install cppyy
- '''
- import ctypes
- import inspect
- import os
- import re
- import sys
- import cppyy
- import cppyy.ll
- try:
- import jlib
- except ModuleNotFoundError:
- class jlib:
- @staticmethod
- def log( text):
- sys.stderr.write( f'{text}\\n')
- mupdf_dir = os.path.abspath( f'{__file__}/../../..')
- # pdf_annot_type is both an enum and a function (that returns
- # the enum type!).
- with open( f'{mupdf_dir}/include/mupdf/pdf/annot.h') as f:
- text = f.read()
- text, n = re.subn(
- '(enum pdf_annot_type pdf_annot_type[(]fz_context [*]ctx, pdf_annot [*]annot[)];)',
- '/*\\1*/',
- text,
- )
- assert n == 1, f'n={n}'
- # libmupdf and libmupdf.so also work here.
- if 0:
- print( f'$LD_LIBRARY_PATH={os.environ["LD_LIBRARY_PATH"]}', file=sys.stderr)
- ret = cppyy.load_library('mupdf')
- #jlib.log( 'after loading "mupdf": ret={ret=}')
- cppyy.load_library('mupdfcpp')
- cppyy.add_include_path( f'{mupdf_dir}/include')
- cppyy.add_include_path( f'{mupdf_dir}/platform/c++/include')
- # pdf_annot_type is both an enum and a function (that returns
- # the enum type!).
- with open( f'{mupdf_dir}/include/mupdf/pdf/annot.h') as f:
- text1 = f.read()
- text1, n = re.subn(
- '(enum pdf_annot_type pdf_annot_type[(]fz_context [*]ctx, pdf_annot [*]annot[)];)',
- '/* \\\\1 */',
- text1,
- )
- assert n == 1, f'n={n}'
- with open( 'foo_text1.h', 'w') as f:
- f.write( text1)
- # pdf_widget_type is both an enum and a function (that returns
- # the enum type!).
- with open( f'{mupdf_dir}/include/mupdf/pdf/form.h') as f:
- text2 = f.read()
- text2, n = re.subn(
- '(enum pdf_widget_type pdf_widget_type[(]fz_context [*]ctx, pdf_annot [*]widget[)];)',
- '/* \\\\1 */',
- text2,
- )
- assert n == 1, f'n={n}'
- with open( 'foo_text2.h', 'w') as f:
- f.write( text2)
- # Not sure why we need '#define FZ_ENABLE_ICC 1', but
- # otherwise classes.h doesn't see a definition of
- # fz_icc_profile. Presumably something to do with us manually
- # including our modified copy of include/mupdf/pdf/annot.h.
- #
- cppyy.cppdef( f'''
- #undef NDEBUG
- #define FZ_ENABLE_ICC 1
- {text1}
- {text2}
- #ifndef MUPDF_PDF_ANNOT_H
- #error MUPDF_PDF_ANNOT_H not defined
- #endif
- #include "mupdf/fitz/version.h"
- #include "mupdf/classes.h"
- #include "mupdf/classes2.h"
- #include "mupdf/functions.h"
- #include "mupdf/fitz.h"
- #include "mupdf/pdf.h"
- ''')
- cppyy.cppdef( f'''
- #ifndef MUPDF_PDF_ANNOT_H
- #error MUPDF_PDF_ANNOT_H not defined
- #endif
- ''')
- if os.environ.get( 'MUPDF_cppyy_sig_exceptions') == '1':
- jlib.log( 'calling cppyy.ll.set_signals_as_exception(True)')
- cppyy.ll.set_signals_as_exception(True)
- if 0:
- # Do some checks.
- try:
- cppyy.gbl.abort()
- except Exception as e:
- print( f'Ignoring test exception from abort(): {e}', file=sys.stderr)
- else:
- assert 0, 'No exception from cppyy.gbl.abort()'
- cppyy.cppdef('''
- void mupdf_check_assert()
- {
- assert( 0);
- }
- ''')
- cppyy.ll.set_signals_as_exception(True)
- print( 'Testing assert failure', file=sys.stderr)
- try:
- cppyy.gbl.mupdf_check_assert()
- except Exception as e:
- print( f'Ignoring test exception from assert(0): {e}', file=sys.stderr)
- print( 'Testing rect creation from null', file=sys.stderr)
- try:
- r = cppyy.gbl.mupdf.Rect( 0)
- except Exception as e:
- print( f'Ignoring exception from test rect creation from null e={e}', file=sys.stderr)
- except:
- print( '*** Non-Exception exception', file=sys.stderr)
- traceback.print_exc()
- else:
- print( f'*** No exception from test rect creation from null', file=sys.stderr)
- print( 'Finished testing rect creation from null', file=sys.stderr)
- #try:
- # cppyy.gbl.raise( SIGABRT)
- #except:
- # traceback.print_exc()
- #
- # Would be convenient to do:
- #
- # from cppyy.gbl.mupdf import *
- #
- # - but unfortunately this is not possible, e.g. see:
- #
- # https://cppyy.readthedocs.io/en/latest/misc.html#reduced-typing
- #
- # So instead it is suggested that users of this api do:
- #
- # import mupdf
- # mupdf = mupdf.cppyy.gbl.mupdf
- #
- # If access to mupdf.cppyy.gbl is required (e.g. to see globals that are not in
- # the C++ mupdf namespace), caller can additionally do:
- #
- # import cppyy
- # cppyy.gbl.std...
- #
- # We make various modifications of cppyy.gbl.mupdf to simplify usage.
- #
- #print( f'len(dir(cppyy.gbl))={len(dir(cppyy.gbl))}')
- #print( f'len(dir(cppyy.gbl.mupdf))={len(dir(cppyy.gbl.mupdf))}')
- # Find macros and import into cppyy.gbl.mupdf.
- paths = (
- f'{mupdf_dir}/include/mupdf/fitz/version.h',
- f'{mupdf_dir}/include/mupdf/ucdn.h',
- f'{mupdf_dir}/pdf/object.h',
- )
- for path in (
- f'{mupdf_dir}/include/mupdf/fitz/version.h',
- f'{mupdf_dir}/include/mupdf/ucdn.h',
- ):
- with open( path) as f:
- for line in f:
- m = re.match('^#define\\\\s([a-zA-Z_][a-zA-Z_0-9]+)\\\\s+([^\\\\s]*)\\\\s*$', line)
- if m:
- name = m.group(1)
- value = m.group(2)
- if value == '':
- value = 1
- else:
- value = eval( value)
- #print( f'mupdf_cppyy.py: Setting {name}={value!r}')
- setattr( cppyy.gbl.mupdf, name, value)
- # MuPDF enums are defined as C so are not in the mupdf
- # namespace. To mimic the SWIG mupdf bindings, we explicitly
- # copy them into cppyy.gbl.mupdf.
- #
- """)
- # Copy enums into mupdf namespace. We use generated.c_enums for this
- # because cppyy has a bug where enums are not visible for iteration in a
- # namespace - see: https://github.com/wlav/cppyy/issues/45
- #
- for enum_type, enum_names in generated.c_enums.items():
- for enum_name in enum_names:
- text += f'cppyy.gbl.mupdf.{enum_name} = cppyy.gbl.{enum_name}\n'
- # Add code for converting small integers into MuPDF's special pdf_obj*
- # values, and add these special enums to the mupdf namespace.
- text += textwrap.dedent( """
- cppyy.cppdef('''
- #include "mupdf/fitz.h"
- /* 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;
- }
- ''')
- """)
- for enum_type, enum_names in generated.c_enums.items():
- for enum_name in enum_names:
- if enum_name.startswith( 'PDF_ENUM_NAME_'):
- text += f'cppyy.gbl.mupdf.{enum_name} = cppyy.gbl.mupdf.PdfObj( cppyy.gbl.obj_enum_to_obj( cppyy.gbl.mupdf.{enum_name}))\n'
- # Auto-generated out-param wrappers.
- text += generated.cppyy_extra
- # Misc processing can be done directly in Python code.
- #
- text += textwrap.dedent( """
- # Import selected basic types into mupdf namespace.
- #
- cppyy.gbl.mupdf.fz_point = cppyy.gbl.fz_point
- cppyy.gbl.mupdf.fz_rect = cppyy.gbl.fz_rect
- cppyy.gbl.mupdf.fz_matrix = cppyy.gbl.fz_matrix
- cppyy.gbl.mupdf.fz_font_flags_t = cppyy.gbl.fz_font_flags_t
- cppyy.gbl.mupdf.fz_default_color_params = cppyy.gbl.fz_default_color_params
- # Override various functions so that, for example, functions with
- # out-parameters instead return tuples.
- #
- # cppyy doesn't like interpreting char name[32] as a string?
- cppyy.cppdef('''
- std::string mupdf_font_name(fz_font* font)
- {
- //std::cerr << __FUNCTION__ << ": font=" << font << " font->name=" << font->name << "\\\\n";
- return font->name;
- }
- ''')
- class getattr_path_raise: pass
- def getattr_path( path, default=getattr_path_raise):
- '''
- Like getattr() but resolves string path, splitting at '.'
- characters.
- '''
- if isinstance( path, str):
- path = path.split( '.')
- # Maybe we should use out caller's module?
- ret = sys.modules[ __name__]
- try:
- for subname in path:
- ret = getattr( ret, subname)
- except AttributeError:
- if default is getattr_path_raise:
- raise
- return default
- return ret
- def setattr_path( path, value):
- '''
- Like getattr() but resolves string path, splitting at '.'
- characters.
- '''
- if isinstance( path, str):
- path = path.split( '.')
- ns = getattr_path( path[:-1])
- setattr( ns, path[-1], value)
- assert getattr_path( 'cppyy') == cppyy
- assert getattr_path( 'cppyy.gbl.mupdf') == cppyy.gbl.mupdf
- def insert( *paths):
- '''
- Returns a decorator that copies the function into the specified
- name(s). We assert that each item in <path> does not already
- exist.
- '''
- class Anon: pass
- for path in paths:
- assert getattr_path( path, Anon) is Anon, f'path={path} already exists.'
- def decorator( fn):
- for path in paths:
- setattr_path( path, fn)
- return decorator
- def replace( *paths):
- '''
- Decorator that inserts a function into namespace(s), replacing
- the existing function(s). We assert that the namespace(s)
- already contains the specified name,
- '''
- def decorator( fn):
- class Anon: pass
- for path in paths:
- assert getattr_path( path, Anon) is not Anon, f'path does not exist: {path}'
- for path in paths:
- setattr_path( path, fn)
- return decorator
- def override( path, *paths_extra):
- '''
- Returns a decorator for <fn> which sets <path> and each item
- in <paths_extra> to <fn>. When <fn> is called, it is passed an
- additional <_original> arg set to the original <path>.
- '''
- def decorator( fn):
- fn_original = getattr_path( path)
- def fn2( *args, **kwargs):
- '''
- Call <fn>, passing additional <_original> arg.
- '''
- assert '_original' not in kwargs
- kwargs[ '_original'] = fn_original
- return fn( *args, **kwargs)
- setattr_path( path, fn2)
- for p in paths_extra:
- setattr_path( p, fn2)
- return fn2
- return decorator
- # A C++ fn that returns fz_buffer::data; our returned value seems
- # to work better than direct access in Python.
- #
- cppyy.cppdef(f'''
- namespace mupdf
- {{
- void* Buffer_data( fz_buffer* buffer)
- {{
- return buffer->data;
- }}
- }}
- ''')
- @replace( 'cppyy.gbl.mupdf.Buffer.buffer_storage', 'cppyy.gbl.mupdf.mfz_buffer_storage')
- def _( buffer):
- assert isinstance( buffer, cppyy.gbl.mupdf.Buffer)
- assert buffer.m_internal
- # Getting buffer.m_internal.data via Buffer_data() appears
- # to work better than using buffer.m_internal.data
- # directly. E.g. the latter fails when passed to
- # mfz_recognize_image_format().
- #
- d = cppyy.gbl.mupdf.Buffer_data( buffer.m_internal)
- return buffer.m_internal.len, d
- cppyy.cppdef('''
- std::string mupdf_raw_to_python_bytes( void* data, size_t size)
- {
- return std::string( (char*) data, size);
- }
- ''')
- @insert( 'cppyy.gbl.mupdf.raw_to_python_bytes')
- def _( data, size):
- '''
- Need to explicitly convert cppyy's std::string wrapper into
- a bytes, otherwise it defaults to a Python str.
- '''
- ret = cppyy.gbl.mupdf_raw_to_python_bytes( data, size)
- ret = bytes( ret)
- return ret
- # Support for converting a fz_buffer's contents into a Python
- # bytes.
- #
- # We do this by creating a std::string in C++, then in Python
- # converting the resulting class cppyy.gbl.std.string into a bytes.
- #
- # Not sure whether this conversion to bytes involves a second copy
- # of the data.
- #
- cppyy.cppdef( f'''
- namespace mupdf
- {{
- /* Returns std::string containing copy of buffer contents. */
- std::string buffer_to_string( const Buffer& buffer, bool clear)
- {{
- unsigned char* datap;
- size_t len = mupdf::mfz_buffer_storage( buffer, &datap);
- std::string ret = std::string( (char*) datap, len);
- if (clear)
- {{
- mupdf::mfz_clear_buffer(buffer);
- mupdf::mfz_trim_buffer(buffer);
- }}
- return ret;
- }}
- }}
- ''')
- @replace( 'cppyy.gbl.mupdf.mfz_buffer_extract', 'cppyy.gbl.mupdf.Buffer.buffer_extract')
- def _( buffer):
- s = cppyy.gbl.mupdf.buffer_to_string( buffer, clear=True)
- b = bytes( s)
- return b
- @insert( 'cppyy.gbl.mupdf.mfz_buffer_extract_copy', 'cppyy.gbl.mupdf.Buffer.buffer_extract_copy')
- def _( buffer):
- s = cppyy.gbl.mupdf.buffer_to_string( buffer, clear=False)
- b = bytes( s)
- return b
- # Python-friendly mfz_new_buffer_from_copied_data() taking a str.
- #
- cppyy.cppdef('''
- namespace mupdf
- {
- Buffer mfz_new_buffer_from_copied_data( const std::string& data)
- {
- /* Constructing a mupdf::Buffer from a char* ends
- up using fz_new_buffer_from_base64(). We want to
- use fz_new_buffer_from_data() which can be done by
- passing an unsigned char*. */
- return mupdf::mfz_new_buffer_from_copied_data(
- (const unsigned char*) data.c_str(),
- data.size()
- );
- }
- }
- ''')
- cppyy.gbl.mupdf.Buffer.new_buffer_from_copied_data = cppyy.gbl.mupdf.mfz_new_buffer_from_copied_data
- # Python-friendly alternative to ppdf_set_annot_color(), taking up
- # to 4 explicit color args.
- #
- cppyy.cppdef('''
- void mupdf_pdf_set_annot_color(
- mupdf::PdfAnnot& self,
- int n,
- float color0,
- float color1,
- float color2,
- float color3
- )
- {
- float color[] = { color0, color1, color2, color3 };
- return self.set_annot_color( n, color);
- }
- void mupdf_pdf_set_annot_interior_color(
- mupdf::PdfAnnot& self,
- int n,
- float color0,
- float color1,
- float color2,
- float color3
- )
- {
- float color[] = { color0, color1, color2, color3 };
- self.set_annot_interior_color( n, color);
- }
- void mupdf_mfz_fill_text(
- const 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 mupdf::mfz_fill_text( dev, text, ctm, colorspace, color, alpha, color_params);
- }
- ''')
- def mupdf_make_colors( color):
- '''
- Returns (n, colors) where <colors> is a tuple with 4 items,
- the first <n> of which are from <color> and the rest are
- zero.
- '''
- if isinstance(color, float):
- color = color,
- assert isinstance( color, ( tuple, list))
- n = len( color)
- ret = tuple(color) + (4-n)*(0,)
- assert len( ret) == 4
- return n, ret
- @replace( 'cppyy.gbl.mupdf.mpdf_set_annot_color', 'cppyy.gbl.mupdf.PdfAnnot.set_annot_color')
- def _( pdf_annot, color):
- n, colors = mupdf_make_colors( color)
- return cppyy.gbl.mupdf_pdf_set_annot_color( pdf_annot, n, *colors)
- @replace( 'cppyy.gbl.mupdf.mpdf_set_annot_interior_color', 'cppyy.gbl.mupdf.PdfAnnot.set_annot_interior_color')
- def _( pdf_annot, color):
- n, colors = mupdf_make_colors( color)
- cppyy.gbl.mupdf_pdf_set_annot_interior_color( pdf_annot, n, *colors)
- @replace( 'cppyy.gbl.mupdf.mfz_fill_text', 'cppyy.gbl.mupdf.Device.fill_text')
- def _( dev, text, ctm, colorspace, color, alpha, color_params):
- _, colors = mupdf_make_colors( color)
- return cppyy.gbl.mupdf_mfz_fill_text( dev, text, ctm, colorspace, *colors, alpha, color_params)
- # Override cppyy.gbl.mupdf.Document.lookup_metadata() to return a
- # string or None if not found.
- #
- @override( 'cppyy.gbl.mupdf.lookup_metadata', 'cppyy.gbl.mupdf.Document.lookup_metadata')
- def _(self, key, _original):
- e = ctypes.c_int(0)
- ret = _original(self.m_internal, key, e)
- e = e.value
- if e < 0:
- return None
- # <ret> will be a cppyy.gbl.std.string, for which str()
- # returns something that looks like a 'bytes', so
- # explicitly convert to 'str'.
- ret = str( ret)
- return ret
- # Override cppyy.gbl.mupdf.parse_page_range() to distinguish
- # between returned const char* being null or empty string
- # - cppyy converts both to an empty string, which means
- # we can't distinguish between the last range (where
- # fz_parse_page_range() returns '') and beyond the last range
- # (where fz_parse_page_range() returns null).
- #
- # fz_parse_page_range() leaves the out-params unchanged when it
- # returns null, so we can detect whether null was returned by
- # initializing the out-params with special values that would never
- # be ordinarily be returned.
- #
- @override( 'cppyy.gbl.mupdf.parse_page_range', 'cppyy.gbl.mupdf.mfz_parse_page_range')
- def _(s, n, _original):
- a = ctypes.c_int(-1)
- b = ctypes.c_int(-1)
- s = _original(s, a, b, n)
- if a.value == -1 and b.value == -1:
- s = None
- return s, a.value, b.value
- # Provide native python implementation of cppyy.gbl.mupdf.format_output_path()
- # (-> fz_format_output_path). (The underlying C/C++ functions take a fixed-size
- # buffer for the output string so isn't useful for Python code.)
- #
- @replace( 'cppyy.gbl.mupdf.format_output_path', 'cppyy.gbl.mupdf.mfz_format_output_path')
- def _(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
- # Override cppyy.gbl.mupdf.Pixmap.n and cppyy.gbl.mupdf.Pixmap.alpha so
- # that they return int. (The underlying C++ functions return unsigned char
- # so cppyy's default bindings end up returning a python string which isn't
- # useful.)
- #
- @override( 'cppyy.gbl.mupdf.Pixmap.n')
- def _( self, _original):
- return ord( _original( self))
- @override( 'cppyy.gbl.mupdf.Pixmap.alpha')
- def _(self, _original):
- return ord( _original( self))
- # Override cppyy.gbl.mupdf.ppdf_clean_file() so that it takes a Python
- # container instead of (argc, argv).
- #
- @override( 'cppyy.gbl.mupdf.ppdf_clean_file', 'cppyy.gbl.mupdf.mpdf_clean_file')
- def _(infile, outfile, password, opts, argv, _original):
- a = 0
- if argv:
- a = (ctypes.c_char_p * len(argv))(*argv)
- a = ctypes.pointer(a)
- _original(infile, outfile, password, opts, len(argv), a)
- # Add cppyy.gbl.mupdf.mpdf_dict_getl() with Python variadic args.
- #
- @insert( 'cppyy.gbl.mupdf.mpdf_dict_getl', 'cppyy.gbl.mupdf.PdfObj.dict_getl')
- def _(obj, *tail):
- for key in tail:
- if not obj.m_internal:
- break
- obj = obj.dict_get(key)
- assert isinstance(obj, cppyy.gbl.mupdf.PdfObj)
- return obj
- # Add cppyy.gbl.mupdf.mpdf_dict_getl() with Python variadic args.
- #
- @insert( 'cppyy.gbl.mupdf.mpdf_dict_putl', 'cppyy.gbl.mupdf.PdfObj.dict_putl')
- def _(obj, val, *tail):
- 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)
- # Raise exception if an attempt is made to call mpdf_dict_putl_drop.
- #
- @insert( 'cppyy.gbl.mpdf_dict_putl_drop', 'cppyy.gbl.mupdf.PdfObj.dict_putl_drop')
- def _(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()'
- )
- 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}')
- # Python-friendly alternative to fz_runetochar().
- #
- cppyy.cppdef(f'''
- std::vector<unsigned char> mupdf_runetochar2(int rune)
- {{
- std::vector<unsigned char> buffer(10);
- int n = mupdf::runetochar((char*) &buffer[0], rune);
- assert(n < sizeof(buffer));
- buffer.resize(n);
- if (0)
- {{
- std::cerr << __FUNCTION__ << ": rune=" << rune << ":";
- for (auto i: buffer)
- {{
- std::cerr << ' ' << (int) i;
- }}
- std::cerr << "\\\\n";
- }}
- return buffer;
- }}
- ''')
- @insert( 'cppyy.gbl.mupdf.runetochar2', 'cppyy.gbl.mupdf.mfz_runetochar2')
- def mupdf_runetochar2( rune):
- vuc = cppyy.gbl.mupdf_runetochar2( rune)
- ret = bytearray()
- #jlib.log( '{vuc!r=}')
- for uc in vuc:
- #jlib.log( '{uc!r=}')
- ret.append( ord( uc))
- #jlib.log( '{ret!r=}')
- return ret
- # Patch mfz_text_language_from_string() to treat str=None as nullptr.
- #
- @override( 'cppyy.gbl.mupdf.mfz_text_language_from_string')
- def _( s, _original):
- if s is None:
- s = ctypes.c_char_p()
- return _original( s)
- # Python-friendly versions of fz_convert_color(), returning (dv0,
- # dv1, dv2, dv3).
- #
- cppyy.cppdef(f'''
- struct mupdf_convert_color2_v
- {{
- float v0;
- float v1;
- float v2;
- float v3;
- }};
- void mupdf_convert_color2(
- fz_colorspace* ss,
- const float* sv,
- fz_colorspace* ds,
- mupdf_convert_color2_v* dv,
- fz_colorspace* is,
- fz_color_params params
- )
- {{
- mupdf::convert_color(ss, sv, ds, &dv->v0, is, params);
- }}
- ''')
- @replace( 'cppyy.gbl.mupdf.convert_color')
- def _convert_color( ss, sv, ds, is_, params):
- # Note that <sv> should be a cppyy representation of a float*.
- dv = cppyy.gbl.mupdf_convert_color2_v()
- if is_ is None:
- is_ = cppyy.ll.cast[ 'fz_colorspace*']( 0)
- cppyy.gbl.mupdf_convert_color2( ss, sv, ds, dv, is_, params)
- return dv.v0, dv.v1, dv.v2, dv.v3
- cppyy.cppdef(f'''
- namespace mupdf
- {{
- std::vector<int> mfz_memrnd2(int length)
- {{
- std::vector<unsigned char> ret(length);
- mupdf::mfz_memrnd(&ret[0], length);
- /* Unlike SWIG, cppyy converts
- std::vector<unsigned char> into a string, not a
- list of integers. */
- std::vector<int> ret2( ret.begin(), ret.end());
- return ret2;
- }}
- }}
- ''')
- # Provide an overload for mfz_recognize_image_format(), because
- # the default unsigned char p[8] causes problems.
- #
- cppyy.cppdef(f'''
- namespace mupdf
- {{
- int mfz_recognize_image_format(const void* p)
- {{
- int ret = mfz_recognize_image_format( (unsigned char*) p);
- return ret;
- }}
- }}
- ''')
- # Wrap mupdf::Pixmap::md5_pixmap() and mupdf::Md5::md5_final2
- # to make them return a Python 'bytes' instance. The
- # C++ code returns std::vector<unsigned char> which
- # SWIG converts into something that can be trivially
- # converted to a Python 'bytes', but with cppyy it is a
- # cppyy.gbl.std.vector['unsigned char'] which gives error
- # "TypeError: 'str' object cannot be interpreted as an integer"
- # if used to construct a Python 'bytes'.
- #
- @override( 'cppyy.gbl.mupdf.Pixmap.md5_pixmap')
- def _( pixmap, _original):
- r = _original( pixmap)
- assert isinstance(r, cppyy.gbl.std.vector['unsigned char'])
- r = bytes( str( r), 'latin')
- return r
- @override( 'cppyy.gbl.mupdf.Md5.md5_final2')
- def _( md5, _original):
- r = _original( md5)
- assert isinstance(r, cppyy.gbl.std.vector['unsigned char'])
- r = bytes( str( r), 'latin')
- return r
- # Allow cppyy.gbl.mupdf.mfz_md5_update() to be called with a buffer.
- def mupdf_mfz_md5_update_buffer( md5, buffer):
- len_, data = buffer.buffer_storage()
- # <data> will be a void*.
- data = cppyy.ll.cast[ 'const unsigned char*']( data)
- return cppyy.gbl.mupdf.mfz_md5_update( md5, data, len_)
- cppyy.gbl.mupdf.mfz_md5_update_buffer = mupdf_mfz_md5_update_buffer
- # Make a version of mpdf_to_name() that returns a std::string
- # so that cppyy can wrap it, and make Python wrappers for
- # mupdf::mpdf_to_name() and mupdf::PdfObj::to_name() use this
- # new version.
- #
- # Otherwise cppyy fails with curious error "TypeError: function
- # takes exactly 5 arguments (1 given)".
- #
- cppyy.cppdef(f'''
- namespace mupdf
- {{
- std::string mpdf_to_name2(const PdfObj& obj)
- {{
- /* Convert const char* to std::string. */
- return mpdf_to_name( obj);
- }}
- }}
- ''')
- def mpdf_to_name( obj):
- return str( cppyy.gbl.mupdf.mpdf_to_name2( obj))
- cppyy.gbl.mupdf.mpdf_to_name = mpdf_to_name
- cppyy.gbl.mupdf.PdfObj.to_name = mpdf_to_name
- # Wrap mfz_new_font_from_*() to convert name=None to name=(const
- # char*) nullptr.
- #
- @override( 'cppyy.gbl.mupdf.mfz_new_font_from_buffer')
- def _( name, fontfile, index, use_glyph_bbox, _original):
- if name is None:
- name = ctypes.c_char_p()
- return _original( name, fontfile, index, use_glyph_bbox)
- @override( 'cppyy.gbl.mupdf.mfz_new_font_from_file')
- def _( name, fontfile, index, use_glyph_bbox, _original):
- if name is None:
- name = ctypes.c_char_p()
- return _original( name, fontfile, index, use_glyph_bbox)
- @override( 'cppyy.gbl.mupdf.mfz_new_font_from_memory')
- def _( name, data, len, index, use_glyph_bbox, _original):
- if name is None:
- name = ctypes.c_char_p()
- return _original( name, data, len, index, use_glyph_bbox)
- # String representation of a fz_font_flags_t, for debugging.
- #
- cppyy.cppdef(f'''
- std::string mupdf_mfz_font_flags_string( const fz_font_flags_t& ff)
- {{
- std::stringstream out;
- out << "{{"
- << " is_mono=" << ff.is_mono
- << " is_serif=" << ff.is_serif
- << " is_bold=" << ff.is_bold
- << " is_italic=" << ff.is_italic
- << " ft_substitute=" << ff.ft_substitute
- << " ft_stretch=" << ff.ft_stretch
- << " fake_bold=" << ff.fake_bold
- << " fake_italic=" << ff.fake_italic
- << " has_opentype=" << ff.has_opentype
- << " invalid_bbox=" << ff.invalid_bbox
- << " cjk=" << ff.cjk
- << " cjk_lang=" << ff.cjk_lang
- << "}}";
- return out.str();
- }}
- ''')
- # Direct access to fz_font_flags_t::ft_substitute for mupdfpy,
- # while cppyy doesn't handle bitfields correctly.
- #
- cppyy.cppdef(f'''
- int mupdf_mfz_font_flags_ft_substitute( const fz_font_flags_t& ff)
- {{
- return ff.ft_substitute;
- }}
- ''')
- # Allow mupdfpy to work - requires make_bookmark2() due to SWIG weirdness.
- #
- cppyy.gbl.mupdf.make_bookmark2 = cppyy.gbl.mupdf.make_bookmark
- cppyy.gbl.mupdf.lookup_bookmark2 = cppyy.gbl.mupdf.lookup_bookmark
- """)
- # Add auto-generate out-param wrappers - these modify fn wrappers to return
- # out-params as tuples.
- #
- #text += generated.cppyy_extra
- jlib.fs_ensure_parent_dir( path)
- jlib.fs_update( text, path)
|