diff --git a/docs/_static/PyMuPDF.ico b/docs/_static/PyMuPDF.ico index 38e08e010..de307f0f9 100644 Binary files a/docs/_static/PyMuPDF.ico and b/docs/_static/PyMuPDF.ico differ diff --git a/docs/_static/pymupdf-sidebar-logo-dark.png b/docs/_static/pymupdf-sidebar-logo-dark.png index eec2b1e77..0721a33a4 100644 Binary files a/docs/_static/pymupdf-sidebar-logo-dark.png and b/docs/_static/pymupdf-sidebar-logo-dark.png differ diff --git a/docs/_static/pymupdf-sidebar-logo-light.png b/docs/_static/pymupdf-sidebar-logo-light.png index 2efa7a2e4..416c80bb8 100644 Binary files a/docs/_static/pymupdf-sidebar-logo-light.png and b/docs/_static/pymupdf-sidebar-logo-light.png differ diff --git a/pipcl.py b/pipcl.py index 3680ca53b..cb55b9d08 100644 --- a/pipcl.py +++ b/pipcl.py @@ -2075,49 +2075,63 @@ def run_if( command, out, *prerequisites): >>> out = 'run_if_test_out' >>> if os.path.exists( out): ... os.remove( out) + >>> if os.path.exists( f'{out}.cmd'): + ... os.remove( f'{out}.cmd') >>> run_if( f'touch {out}', out) + pipcl.py: run_if(): Running command because: File does not exist: 'run_if_test_out' + pipcl.py: run(): Running: touch run_if_test_out True If we repeat, the output file will be up to date so the command is not run: >>> run_if( f'touch {out}', out) + pipcl.py: run_if(): Not running command because up to date: 'run_if_test_out' If we change the command, the command is run: >>> run_if( f'touch {out}', out) + pipcl.py: run_if(): Running command because: Command has changed + pipcl.py: run(): Running: touch run_if_test_out True If we add a prerequisite that is newer than the output, the command is run: + >>> time.sleep(1) >>> prerequisite = 'run_if_test_prerequisite' >>> run( f'touch {prerequisite}') - >>> run_if( f'touch {out}', out, prerequisite) + pipcl.py: run(): Running: touch run_if_test_prerequisite + >>> run_if( f'touch {out}', out, prerequisite) + pipcl.py: run_if(): Running command because: Prerequisite is new: 'run_if_test_prerequisite' + pipcl.py: run(): Running: touch run_if_test_out True If we repeat, the output will be newer than the prerequisite, so the command is not run: - >>> run_if( f'touch {out}', out, prerequisite) + >>> run_if( f'touch {out}', out, prerequisite) + pipcl.py: run_if(): Not running command because up to date: 'run_if_test_out' ''' doit = False + cmd_path = f'{out}.cmd' + if not doit: out_mtime = _fs_mtime( out) if out_mtime == 0: doit = f'File does not exist: {out!r}' - cmd_path = f'{out}.cmd' - if os.path.isfile( cmd_path): - with open( cmd_path) as f: - cmd = f.read() - else: - cmd = None - if command != cmd: - if cmd is None: - doit = 'No previous command stored' + if not doit: + if os.path.isfile( cmd_path): + with open( cmd_path) as f: + cmd = f.read() else: - doit = f'Command has changed' - if 0: - doit += f': {cmd!r} => {command!r}' + cmd = None + if command != cmd: + if cmd is None: + doit = 'No previous command stored' + else: + doit = f'Command has changed' + if 0: + doit += f': {cmd!r} => {command!r}' if not doit: # See whether any prerequisites are newer than target. diff --git a/scripts/gh_release.py b/scripts/gh_release.py index 7d97b7501..9bddd5bb8 100755 --- a/scripts/gh_release.py +++ b/scripts/gh_release.py @@ -248,7 +248,9 @@ def set_if_unset(name, value): set_if_unset( 'CIBW_BUILD_VERBOSITY', '3') # We exclude pp* because of `fitz_wrap.obj : error LNK2001: unresolved # external symbol PyUnicode_DecodeRawUnicodeEscape`. - set_if_unset( 'CIBW_SKIP', '"pp* *i686 cp36* cp37*"') + # 2024-06-05: musllinux on aarch64 fails because libclang cannot find + # libclang.so. + set_if_unset( 'CIBW_SKIP', '"pp* *i686 cp36* cp37* *musllinux*aarch64*"') def make_string(*items): ret = list() diff --git a/scripts/sysinstall.py b/scripts/sysinstall.py index 84c28f341..a50a5503b 100755 --- a/scripts/sysinstall.py +++ b/scripts/sysinstall.py @@ -3,8 +3,9 @@ ''' Test for Linux system install of MuPDF and PyMuPDF. -We build and install MuPDF and PyMuPDF into a root directory, then run -PyMuPDF's pytest tests with LD_PRELOAD_PATH and PYTHONPATH set. +We build and install MuPDF and PyMuPDF into a root directory, then use +scripts/test.py to run PyMuPDF's pytest tests with LD_PRELOAD_PATH and +PYTHONPATH set. PyMuPDF itself is installed using `python -m install` with a wheel created with `pip wheel`. @@ -21,6 +22,8 @@ --mupdf-dir Path of MuPDF checkout; default is 'mupdf'. + --mupdf-do 0|1 + Whether to build and install mupdf. --mupdf-git Get or update `mupdf_dir` using git. If `mupdf_dir` already exists we run `git pull` in it; otherwise we run `git @@ -43,6 +46,8 @@ Directory within `root`; default is `/usr/local`. Must start with `/`. --pymupdf-dir Path of PyMuPDF checkout; default is 'PyMuPDF'. + --pymupdf-do 0|1 + Whether to build and install pymupdf. --root Root of install directory; default is `/`. --tesseract5 0|1 @@ -57,15 +62,14 @@ If 1 (the default), we use `python -m installer` to install PyMuPDF from a generated wheel. [Otherwise we use `pip install`, which refuses to do a system install with `--root /`, referencing PEP-668.] - -m 0|1 - If 1 (the default) we build and install MuPDF, otherwise we just show - what command we would have run. - -p 0|1 - If 1 (the default) we build and install PyMuPDF, otherwise we just show - what command we would have run. - -t 0|1 - If 1 (the default) we run PyMuPDF's pytest tests, otherwise we just - show what command we would have run. + -i + Passed through to scripts/test.py. + -f + Passed through to scripts/test.py. + -p + Passed through to scripts/test.py. + -t + Passed through to scripts/test.py. To only show what commands would be run, but not actually run them, specify `-m 0 -p 0 -t 0`. @@ -75,6 +79,7 @@ import multiprocessing import os import platform +import shlex import subprocess import sys import sysconfig @@ -117,19 +122,23 @@ def main(): # Set default behaviour. # use_installer = True - mupdf = True + mupdf_do = True mupdf_dir = 'mupdf' mupdf_git = None mupdf_so_mode = None packages = True prefix = '/usr/local' - pymupdf = True + pymupdf_do = True pymupdf_dir = os.path.abspath( f'{__file__}/../..') root = 'sysinstall_test' tesseract5 = True - test = True + pytest_args = None + pytest_do = True + pytest_name = None test_venv = 'venv-pymupdf-sysinstall-test' pip = 'venv' + test_fitz = None + test_implementations = None # Parse command-line. # @@ -142,20 +151,24 @@ def main(): if arg in ('-h', '--help'): print(__doc__) return + elif arg == '--mupdf-do': mupdf_do = int(next(args)) elif arg == '--mupdf-dir': mupdf_dir = next(args) elif arg == '--mupdf-git': mupdf_git = next(args) elif arg == '--mupdf-so-mode': mupdf_so_mode = next(args) elif arg == '--packages': packages = int(next(args)) elif arg == '--prefix': prefix = next(args) + elif arg == '--pymupdf-do': pymupdf_do = int(next(args)) elif arg == '--pymupdf-dir': pymupdf_dir = next(args) elif arg == '--root': root = next(args) elif arg == '--tesseract5': tesseract5 = int(next(args)) + elif arg == '--pytest-do': pytest_do = int(next(args)) elif arg == '--test-venv': test_venv = next(args) elif arg == '--use-installer': use_installer = int(next(args)) elif arg == '--pip': pip = next(args) - elif arg == '-m': mupdf = int(next(args)) - elif arg == '-p': pymupdf = int(next(args)) - elif arg == '-t': test = int(next(args)) + elif arg == '-f': test_fitz = next(args) + elif arg == '-i': test_implementations = next(args) + elif arg == '-p': pytest_args = next(args) + elif arg == '-t': pytest_name = next(args) else: assert 0, f'Unrecognised arg: {arg!r}' @@ -169,7 +182,7 @@ def main(): if root == '/': sudo = f'sudo PATH={os.environ["PATH"]} ' def run(command): - return run_command(command, doit=mupdf) + return run_command(command, doit=mupdf_do) # Get MuPDF from git if specified. # if mupdf_git: @@ -232,7 +245,7 @@ def run(command): # print('## Build and install PyMuPDF.') def run(command): - return run_command(command, doit=pymupdf) + return run_command(command, doit=pymupdf_do) flags_freetype2 = run_command('pkg-config --cflags freetype2', capture_output=1).stdout.strip() compile_flags = f'-I {root_prefix}/include {flags_freetype2}' link_flags = f'-L {root_prefix}/lib' @@ -316,7 +329,7 @@ def run(command): # print('## Run PyMuPDF pytest tests.') def run(command): - return run_command(command, doit=test) + return run_command(command, doit=pytest_do) import gh_release if pip == 'venv': # Create venv. @@ -342,9 +355,32 @@ def run(command): run(f'ls -l {root_prefix}/bin/') # 2024-03-20: Not sure whether/where `pymupdf` binary is installed, so we # disable the test_cli* tests. - excluded_tests = 'test_color_count test_3050 test_cli test_cli_out test_pylint test_textbox3'.split() - excluded_tests = ' and not '.join(excluded_tests) - command += f' {pymupdf_dir}/scripts/test.py -p "-k \'not {excluded_tests}\'" -v 0 test' + command += f' {pymupdf_dir}/scripts/test.py' + command += f' -v 0' + if pytest_name is None: + excluded_tests = ( + 'test_color_count', + 'test_3050', + 'test_cli', + 'test_cli_out', + 'test_pylint', + 'test_textbox3', + 'test_3493', + ) + excluded_tests = ' and not '.join(excluded_tests) + if not pytest_args: + pytest_args = '' + pytest_args += f' -k \'not {excluded_tests}\'' + else: + command += f' -t {pytest_name}' + if test_fitz: + command += f' -f {test_fitz}' + if test_implementations: + command += f' -i {test_implementations}' + if pytest_args: + command += f' -p {shlex.quote(pytest_args)}' + if pytest_do: + command += ' test' run(command) diff --git a/src/__init__.py b/src/__init__.py index aa4402fe7..b00e7cc64 100644 --- a/src/__init__.py +++ b/src/__init__.py @@ -2803,12 +2803,20 @@ def __init__(self, filename=None, stream=None, filetype=None, rect=None, width=0 stream = mupdf.FzStream(filename) accel = mupdf.FzStream() archive = mupdf.FzArchive(None) - doc = mupdf.ll_fz_document_open_fn_call( - handler.open, - stream.m_internal, - accel.m_internal, - archive.m_internal, - ) + if mupdf_version_tuple >= (1, 25): + doc = mupdf.ll_fz_document_handler_open( + handler, + stream.m_internal, + accel.m_internal, + archive.m_internal, + ) + else: + doc = mupdf.ll_fz_document_open_fn_call( + handler.open, + stream.m_internal, + accel.m_internal, + archive.m_internal, + ) else: doc = mupdf.ll_fz_document_open_fn_call( handler.open, filename) except Exception as e: diff --git a/tests/resources/test_1645_expected_1.25.pdf b/tests/resources/test_1645_expected_1.25.pdf index 2f6a85827..cae1ef8a5 100644 Binary files a/tests/resources/test_1645_expected_1.25.pdf and b/tests/resources/test_1645_expected_1.25.pdf differ diff --git a/tests/test_pixmap.py b/tests/test_pixmap.py index a17cf1b03..c456f05fc 100644 --- a/tests/test_pixmap.py +++ b/tests/test_pixmap.py @@ -270,6 +270,9 @@ def test_3493(): If python3-gi is installed, we check fix for #3493, where importing gi would load an older version of libjpeg than is used in MuPDF, and break MuPDF. + + This test is excluded by default in sysinstall tests, because running + commands in a new venv does not seem to pick up pymupdf as expected. ''' if platform.system() != 'Linux': print(f'Not running because not Linux: {platform.system()=}') @@ -315,7 +318,7 @@ def run_code(code, code_path, *, check=True, venv=None, venv_args='', pythonpath return gi = r.stdout.strip() gi_pythonpath = os.path.abspath(f'{gi}/../..') - + def do(gi): # Run code that will import gi and pymupdf in different orders, and # return contents of generated .png file as a bytes. diff --git a/tests/test_textbox.py b/tests/test_textbox.py index 924b9207e..e0bda416e 100644 --- a/tests/test_textbox.py +++ b/tests/test_textbox.py @@ -260,3 +260,14 @@ def test_htmlbox3(): # lowlevel-extract inserted text to access opacity span = page.get_texttrace()[0] assert span["opacity"] == 0.5 + + +def test_3559(): + if pymupdf.mupdf_version_tuple < (1, 24, 4): + print(f'test_3559(): Not running because mupdf known to SEGV.') + return + doc = pymupdf.Document() + page = doc.new_page() + text_insert="""

""" + rect = pymupdf.Rect(100, 100, 200, 200) + page.insert_htmlbox(rect, text_insert)