diff --git a/pipcl.py b/pipcl.py index bca1ad1b3..9f3c93538 100644 --- a/pipcl.py +++ b/pipcl.py @@ -914,10 +914,19 @@ def tag_abi(self): ''' ABI tag. ''' + Py_GIL_DISABLED = sysconfig.get_config_var('Py_GIL_DISABLED') if self.tag_abi_: return self.tag_abi_ elif self.py_limited_api: + assert Py_GIL_DISABLED != 1, \ + f'py_limited_api and Py_GIL_DISABLED are not supported together as of 2026-02-20, e.g. see PEP 803 and PEP 809.' return 'abi3' + elif Py_GIL_DISABLED == 1: + ret = '' + ret += 'cp' + ret += ''.join(platform.python_version().split('.')[:2]) + ret += 't' + return ret else: return 'none' diff --git a/src/extra.i b/src/extra.i index 8f4e79c63..cd3fce901 100644 --- a/src/extra.i +++ b/src/extra.i @@ -3424,6 +3424,7 @@ int _as_blocks(fz_stext_block *block, fz_rect tp_rect, PyObject *lines, int bloc PyObject *text = NULL; fz_rect blockrect; mupdf::FzBuffer res; + int last_char; while (block) { switch (block->type) @@ -3438,7 +3439,7 @@ int _as_blocks(fz_stext_block *block, fz_rect tp_rect, PyObject *lines, int bloc case FZ_STEXT_BLOCK_TEXT: blockrect = fz_empty_rect; res = mupdf::fz_new_buffer(1024); - int last_char; + last_char = 10; for (fz_stext_line* line = block->u.t.first_line; line; line = line->next) { fz_rect linerect = fz_empty_rect; diff --git a/tests/resources/test_4755.pdf b/tests/resources/test_4755.pdf new file mode 100644 index 000000000..61b4d3862 Binary files /dev/null and b/tests/resources/test_4755.pdf differ diff --git a/tests/test_4767.py b/tests/test_4767.py index 433df6ddc..e8a88fe51 100644 --- a/tests/test_4767.py +++ b/tests/test_4767.py @@ -4,6 +4,7 @@ import pymupdf import subprocess import sys +import sysconfig def test_4767(): @@ -66,36 +67,51 @@ def get_paths(): paths.append(path) return paths + def get_stdout(cp): + ''' + Strips free-threading warning. + ''' + stdout = cp.stdout + if sysconfig.get_config_var('Py_GIL_DISABLED') == 1: + line0, stdout = stdout.split('\n', 1) + assert 'The global interpreter lock (GIL) has been enabled to load module \'pymupdf._extra\',' in line0 + return stdout + cp = run(f'cd {testdir}/one/two && {sys.executable} -m pymupdf embed-extract {path} -name evil_entry') - print(cp.stdout) assert cp.returncode - assert cp.stdout == 'refusing to write stored name outside current directory: ../../test.txt\n' + stdout = get_stdout(cp) + print(f'{stdout=}') + assert stdout == 'refusing to write stored name outside current directory: ../../test.txt\n' assert not get_paths() cp = run(f'cd {testdir}/one/two && {sys.executable} -m pymupdf embed-extract {path} -name evil_entry -unsafe') assert cp.returncode == 0 - assert cp.stdout == "saved entry 'evil_entry' as '../../test.txt'\n" + stdout = get_stdout(cp) + assert stdout == "saved entry 'evil_entry' as '../../test.txt'\n" paths = get_paths() print(f'{paths=}') assert paths == [f'{testdir}/test.txt'] cp = run(f'cd {testdir}/one/two && {sys.executable} -m pymupdf embed-extract {path} -name evil_entry2') assert not cp.returncode - assert cp.stdout == "saved entry 'evil_entry2' as 'test2.txt'\n" + stdout = get_stdout(cp) + assert stdout == "saved entry 'evil_entry2' as 'test2.txt'\n" paths = get_paths() print(f'{paths=}') assert paths == [f'{testdir}/test.txt', f'{testdir}/one/two/test2.txt'] cp = run(f'cd {testdir}/one/two && {sys.executable} -m pymupdf embed-extract {path} -name evil_entry2') assert cp.returncode - assert cp.stdout == "refusing to overwrite existing file with stored name: test2.txt\n" + stdout = get_stdout(cp) + assert stdout == "refusing to overwrite existing file with stored name: test2.txt\n" paths = get_paths() print(f'{paths=}') assert paths == [f'{testdir}/test.txt', f'{testdir}/one/two/test2.txt'] cp = run(f'cd {testdir}/one/two && {sys.executable} -m pymupdf embed-extract {path} -name evil_entry2 -unsafe') assert not cp.returncode - assert cp.stdout == "saved entry 'evil_entry2' as 'test2.txt'\n" + stdout = get_stdout(cp) + assert stdout == "saved entry 'evil_entry2' as 'test2.txt'\n" paths = get_paths() print(f'{paths=}') assert paths == [f'{testdir}/test.txt', f'{testdir}/one/two/test2.txt'] diff --git a/tests/test_annots.py b/tests/test_annots.py index b6df366f2..122968291 100644 --- a/tests/test_annots.py +++ b/tests/test_annots.py @@ -3,6 +3,7 @@ Test PDF annotation insertions. """ +import copy import os import platform @@ -674,3 +675,46 @@ def test_4447(): path_out = os.path.normpath(f'{__file__}/../../tests/test_4447.pdf') document.save(path_out) + + +def test_4755(): + print() + path = os.path.normpath(f'{__file__}/../../tests/resources/test_4755.pdf') + path_out = os.path.normpath(f'{__file__}/../../tests/test_4755_out.pdf') + + with pymupdf.open(path) as document: + for page_i, page in enumerate(document): + print(f'{page_i=}') + caret = page.add_caret_annot((50,50)) + + colours = [ + (0, 0, 1), + (0, 1, 0), + (1, 0, 0), + ] + for annot_i, annot in enumerate(page.annots()): + print(f'{annot_i=}') + #if annot_i != 2: + # continue + before_rect = copy.deepcopy(annot.rect) + + def draw_rectangle(rect, c, w): + drect = page.add_freetext_annot(rect, str(annot_i), text_color=c) + drect.set_border(width=w) + drect.update() + + colour = colours[annot_i] + print(f'{colour=}') + draw_rectangle(annot.rect, colour, .3) + + print(f'before: {annot.rect}=') + annot.set_rect(annot.rect) + print(f' after: {annot.rect}=') + + print(f'difference: {annot.rect-before_rect=}') + + draw_rectangle(annot.rect, (0, 1, 0), .3) + print() + document.save(path_out) + print(f' {path=}.') + print(f'{path_out=}.') diff --git a/tests/test_general.py b/tests/test_general.py index 7449010c0..96d070f1b 100644 --- a/tests/test_general.py +++ b/tests/test_general.py @@ -2004,7 +2004,7 @@ def test_4392(): print('test_4392(): not running on Pyodide - cannot run child processes.') return - print() + print('', flush=1) path = os.path.normpath(f'{__file__}/../../tests/test_4392.py') with open(path, 'w') as f: f.write('import pymupdf\n') @@ -2024,15 +2024,22 @@ def test_4392(): e3 = subprocess.run(command, shell=1, check=0).returncode print(f'{e3=}') - print(f'{e1=} {e2=} {e3=}') + print(f'{e1=} {e2=} {e3=}', flush=1) - print(f'{pymupdf.swig_version=}') - print(f'{pymupdf.swig_version_tuple=}') + print(f'{pymupdf.swig_version=}', flush=1) + print(f'{pymupdf.swig_version_tuple=}', flush=1) assert e1 == 5 if pymupdf.swig_version_tuple >= (4, 4): - assert e2 == 5 - assert e3 == 0 + if sysconfig.get_config_var('Py_GIL_DISABLED') == 1: + assert e2 == 4 + else: + assert e2 == 5 + if sysconfig.get_config_var('Py_GIL_DISABLED') == 1: + # GIL warning results in failure because of -Werror. + assert e3 == 1 + else: + assert e3 == 0 else: # We get SEGV's etc with older swig. if platform.system() == 'Windows': diff --git a/tests/test_pixmap.py b/tests/test_pixmap.py index 54128e523..bfbf3c565 100644 --- a/tests/test_pixmap.py +++ b/tests/test_pixmap.py @@ -11,6 +11,7 @@ import os import platform import re +import shutil import subprocess import sys import tempfile @@ -284,6 +285,10 @@ def test_3493(): import subprocess root = os.path.abspath(f'{__file__}/../..') + + venv = f'{root}/tests/resources/test_3493_venv' + shutil.rmtree(venv, ignore_errors=1) + in_path = f'{root}/tests/resources/test_3493.epub' def run(command, check=1, stdout=None): @@ -319,7 +324,7 @@ def run_code(code, code_path, *, check=True, venv=None, venv_args='', pythonpath , f'{root}/tests/resources/test_3493_gi.py', check=0, - venv=f'{root}/tests/resources/test_3493_venv', + venv=venv, venv_args='--system-site-packages', stdout=subprocess.PIPE, )