Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 9 additions & 0 deletions pipcl.py
Original file line number Diff line number Diff line change
Expand Up @@ -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'

Expand Down
3 changes: 2 additions & 1 deletion src/extra.i
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand All @@ -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;
Expand Down
Binary file added tests/resources/test_4755.pdf
Binary file not shown.
28 changes: 22 additions & 6 deletions tests/test_4767.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
import pymupdf
import subprocess
import sys
import sysconfig


def test_4767():
Expand Down Expand Up @@ -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']
44 changes: 44 additions & 0 deletions tests/test_annots.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
Test PDF annotation insertions.
"""

import copy
import os
import platform

Expand Down Expand Up @@ -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=}.')
19 changes: 13 additions & 6 deletions tests/test_general.py
Original file line number Diff line number Diff line change
Expand Up @@ -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')
Expand All @@ -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':
Expand Down
7 changes: 6 additions & 1 deletion tests/test_pixmap.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@
import os
import platform
import re
import shutil
import subprocess
import sys
import tempfile
Expand Down Expand Up @@ -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):
Expand Down Expand Up @@ -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,
)
Expand Down