Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Jules #3183

Merged
merged 4 commits into from
Feb 19, 2024
Merged

Jules #3183

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
8 changes: 7 additions & 1 deletion .github/workflows/test_sysinstall.yml
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,12 @@ jobs:
# # sees `--venv` and defers to a venv, so we currently have to force use of python 3.11.
# python-version: '3.11'

- name: sysinstall
- name: sysinstall_venv
run:
# Use venv.
python3 scripts/sysinstall.py --root / --mupdf-git '--branch master https://github.com/ArtifexSoftware/mupdf.git'

- name: sysinstall_sudo
run:
# Do not use a venv, instead install required packages with sudo.
python3 scripts/sysinstall.py --pip sudo --root / --mupdf-git '--branch master https://github.com/ArtifexSoftware/mupdf-julian.git'
147 changes: 98 additions & 49 deletions scripts/sysinstall.py
Original file line number Diff line number Diff line change
Expand Up @@ -24,28 +24,39 @@
--mupdf-git <git_args>
Get or update `mupdf_dir` using git. If `mupdf_dir` already
exists we run `git pull` in it; otherwise we run `git
clone` with `git_args`. For example:
clone` with `<git_args> <mupdf_dir>`. For example:
--mupdf-git "--branch master https://github.com/ArtifexSoftware/mupdf.git"
--pymupdf-dir <pymupdf_dir>
Path of PyMuPDF checkout; default is 'PyMuPDF'.
--mupdf-so-mode <mode>
Used with `install -m <mode> ...` when installing MuPDF. For example
`--mupdf-so-mode 744`.
--packages 0|1
If 1 (the default) we install required system packages such as
`libfreetype-dev`.
--pip 0|venv|sudo
Whether/how to install Python packages.
If '0' we assume required packages are already available.
If 'sudo' we install required Python packages using `sudo pip install
...`.
If 'venv' (the default) we install Python packages and run installer
and test commands inside venv's.
--prefix:
Directory within `root`; default is `/usr/local`. Must start with `/`.
--pymupdf-dir <pymupdf_dir>
Path of PyMuPDF checkout; default is 'PyMuPDF'.
--root <root>
Root of install directory; default is `/`.
--tesseract5 0|1
If 1 (the default), we force installation of libtesseract-dev version
5 (which is not available as a default package in Ubuntu-22.04) from
package repository ppa:alex-p/tesseract-ocr-devel.
--test-venv <venv_path>
Run tests in specified venv; the default is a hard-coded venv name. The
venv will be created and required packages installed using `pip`. If
`venv-path` is empty string, we do not create or use a venv, and will
instead attempt to install required packages with `pip` in the current
Python environment.
--test-venv <test_venv>
Set the name of the venv in which we run tests (only with `--pip
venv`); the default is a hard-coded venv name. The venv will be
created, and required packages installed using `pip`.
--use-installer 0|1
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.
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.
Expand All @@ -61,12 +72,14 @@
'''

import glob
import multiprocessing
import os
import platform
import subprocess
import sys
import sysconfig

import test as test_py

# Requirements for a system build and install:
#
Expand Down Expand Up @@ -107,13 +120,16 @@ def main():
mupdf = True
mupdf_dir = 'mupdf'
mupdf_git = None
mupdf_so_mode = None
packages = True
prefix = '/usr/local'
pymupdf = True
pymupdf_dir = os.path.abspath( f'{__file__}/../..')
root = 'sysinstall_test'
tesseract5 = True
test = True
test_venv = 'venv-pymupdf-sysinstall-test'
pip = 'venv'

# Parse command-line.
#
Expand All @@ -128,19 +144,24 @@ def main():
return
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-dir': pymupdf_dir = next(args)
elif arg == '--root': root = next(args)
elif arg == '--tesseract5': tesseract5 = 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))
else:
assert 0, f'Unrecognised arg: {arg!r}'

assert prefix.startswith('/')
pip_values = ('0', 'sudo', 'venv')
assert pip in pip_values, f'Unrecognised --pip value {pip!r} should be one of: {pip_values!r}'
root = os.path.abspath(root)
root_prefix = f'{root}{prefix}'.replace('//', '/')

Expand All @@ -161,44 +182,54 @@ def run(command):
print(f'## Clone MuPDF into {mupdf_dir}.')
run(f'git clone --recursive --depth 1 --shallow-submodules {mupdf_git} {mupdf_dir}')

# Install required system packages. We assume a Debian package system.
#
print('## Install system packages required by MuPDF.')
run(f'sudo apt update')
run(f'sudo apt install {" ".join(g_sys_packages)}')
# Ubuntu-22.04 has freeglut3-dev, not libglut-dev.
run(f'sudo apt install libglut-dev | sudo apt install freeglut3-dev')
if tesseract5:
print(f'## Force installation of libtesseract-dev version 5.')
# https://stackoverflow.com/questions/76834972/how-can-i-run-pytesseract-python-library-in-ubuntu-22-04
if packages:
# Install required system packages. We assume a Debian package system.
#
run('sudo apt install -y software-properties-common')
run('sudo add-apt-repository ppa:alex-p/tesseract-ocr-devel')
run('sudo apt update')
run('sudo apt install -y libtesseract-dev')
else:
run('sudo apt install libtesseract-dev')
print('## Install system packages required by MuPDF.')
run(f'sudo apt update')
run(f'sudo apt install {" ".join(g_sys_packages)}')
# Ubuntu-22.04 has freeglut3-dev, not libglut-dev.
run(f'sudo apt install libglut-dev | sudo apt install freeglut3-dev')
if tesseract5:
print(f'## Force installation of libtesseract-dev version 5.')
# https://stackoverflow.com/questions/76834972/how-can-i-run-pytesseract-python-library-in-ubuntu-22-04
#
run('sudo apt install -y software-properties-common')
run('sudo add-apt-repository ppa:alex-p/tesseract-ocr-devel')
run('sudo apt update')
run('sudo apt install -y libtesseract-dev')
else:
run('sudo apt install libtesseract-dev')

# Build+install MuPDF. We use mupd:Makefile's install-shared-python target.
#
if pip == 'sudo':
print('## Installing Python packages required for building MuPDF and PyMuPDF.')
run(f'sudo pip install --upgrade pip')
names = (''
+ test_py.get_pyproject_required(os.path.abspath(f'{__file__}/../../pyproject.toml'))
+ ' '
+ test_py.get_pyproject_required(os.path.abspath(f'{mupdf_dir}/pyproject.toml'))
)
run(f'sudo pip install {names}')

print('## Build and install MuPDF.')
if 1:
# Current MuPDF creates softlinks with `ln -s` which breaks if there
# was a previous build; it should do `ln -sf`. We make things work by
# deleting any existing softlinks here.
run(f'rm {root_prefix}/lib/libmupdf.so || true')
run(f'rm {root_prefix}/lib/libmupdfcpp.so || true')
command = f'cd {mupdf_dir}'
command += f' && {sudo}make'
command += f' -j {multiprocessing.cpu_count()}'
#command += f' EXE_LDFLAGS=-Wl,--trace' # Makes linker generate diagnostics as it runs.
command += f' DESTDIR={root}'
command += f' HAVE_LEPTONICA=yes'
command += f' HAVE_TESSERACT=yes'
command += f' USE_SYSTEM_LIBS=yes'
command += f' VENV_FLAG={"--venv" if pip == "venv" else ""}'
if mupdf_so_mode:
command += f' SO_INSTALL_MODE={mupdf_so_mode}'
command += f' build_prefix=system-libs-'
command += f' prefix={prefix}'
command += f' verbose=yes'
command += f' install-shared-python'
command += f' INSTALL_MODE=755'
run( command)

# Build+install PyMuPDF.
Expand All @@ -217,23 +248,34 @@ def run(command):
env += f'PYMUPDF_SETUP_IMPLEMENTATIONS=b' # Only build the rebased implementation.
if use_installer:
print(f'## Building wheel.')
if pip == 'venv':
venv_name = 'venv-pymupdf-sysinstall'
run(f'pwd')
run(f'rm dist/* || true')
run(f'{env} pip wheel -vv -w dist {os.path.abspath(pymupdf_dir)}')
if pip == 'venv':
run(f'{sys.executable} -m venv {venv_name}')
run(f'. {venv_name}/bin/activate && pip install --upgrade pip')
run(f'. {venv_name}/bin/activate && pip install --upgrade installer')
run(f'{env} {venv_name}/bin/python -m pip wheel -vv -w dist {os.path.abspath(pymupdf_dir)}')
elif pip == 'sudo':
run(f'sudo pip install --upgrade pip')
run(f'sudo pip install installer')
run(f'{env} pip wheel -vv -w dist {os.path.abspath(pymupdf_dir)}')
else:
log(f'Not installing "installer" because {pip=}.')
wheel = glob.glob(f'dist/*')
assert len(wheel) == 1, f'{wheel=}'
wheel = wheel[0]
print(f'## Installing wheel using `installer`.')
venv = 'venv-pymupdf-sysinstall'
run(f'{sys.executable} -m venv {venv}')
run(f'. {venv}/bin/activate && pip install --upgrade pip')
run(f'. {venv}/bin/activate && pip install --upgrade installer')
pv = '.'.join(platform.python_version_tuple()[:2])
p = f'{root_prefix}/lib/python{pv}'
# `python -m installer` fails to overwrite existing files.
run(f'{sudo}rm -r {p}/site-packages/fitz || true')
run(f'{sudo}rm -r {p}/site-packages/PyMuPDF-*.dist-info || true')
run(f'{sudo}{venv}/bin/python -m installer --destdir {root} --prefix {prefix} {wheel}')
if pip == 'venv':
run(f'{sudo}{venv_name}/bin/python -m installer --destdir {root} --prefix {prefix} {wheel}')
else:
run(f'{sudo}{sys.executable} -m installer --destdir {root} --prefix {prefix} {wheel}')
# It seems that MuPDF Python bindings are installed into
# `.../dist-packages` (from mupdf:Mafile's call of `$(shell python3
# -c "import sysconfig; print(sysconfig.get_path('platlib'))")` while
Expand Down Expand Up @@ -267,7 +309,7 @@ def run(command):
del sys.path[0]
pythonpath = pipcl.install_dir(root)

# Show contents of installation director. This is very slow on github,
# Show contents of installation directory. This is very slow on github,
# where /usr/local contains lots of things.
#run(f'find {root_prefix}|sort')

Expand All @@ -277,20 +319,27 @@ def run(command):
def run(command):
return run_command(command, doit=test)
import gh_release
# Create venv.
run(f'{sys.executable} -m venv {test_venv}')
# Install required packages.
command = f'. {test_venv}/bin/activate'
command += f' && pip install --upgrade pip'
command += f' && pip install --upgrade {gh_release.test_packages}'
run(command)
if pip == 'venv':
# Create venv.
run(f'{sys.executable} -m venv {test_venv}')
# Install required packages.
command = f'. {test_venv}/bin/activate'
command += f' && pip install --upgrade pip'
command += f' && pip install --upgrade {gh_release.test_packages}'
run(command)
elif pip == 'sudo':
run(f'sudo pip install --upgrade {gh_release.test_packages}')
else:
log(f'Not installing packages for testing because {pip=}.')
# Run pytest.
#
# We need to set PYTHONPATH and LD_LIBRARY_PATH. In particular we
# use pipcl.install_dir() to find where pipcl will have installed
# PyMuPDF.
command = f'. {test_venv}/bin/activate'
command += f' && LD_LIBRARY_PATH={root_prefix}/lib PYTHONPATH={pythonpath}'
command = ''
if pip == 'venv':
command += f'. {test_venv}/bin/activate &&'
command += f' LD_LIBRARY_PATH={root_prefix}/lib PYTHONPATH={pythonpath}'
command += f' pytest -k "not test_color_count and not test_3050" {pymupdf_dir}'
run(command)

Expand Down
5 changes: 3 additions & 2 deletions scripts/test.py
Original file line number Diff line number Diff line change
Expand Up @@ -410,12 +410,13 @@ def test(
log('\n' + venv_info(pytest_args=f'{pytest_options} {pytest_arg}'))


def get_pyproject_required():
def get_pyproject_required(ppt=None):
'''
Returns space-separated names of required packages in pyproject.toml. We
do not do a proper parse and rely on the packages being in a single line.
'''
ppt = os.path.abspath(f'{__file__}/../../pyproject.toml')
if ppt is None:
ppt = os.path.abspath(f'{__file__}/../../pyproject.toml')
with open(ppt) as f:
for line in f:
m = re.match('^requires = \\[(.*)\\]$', line)
Expand Down
2 changes: 1 addition & 1 deletion src/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -17437,7 +17437,7 @@ def JM_set_ocg_arrays_imp(arr, list_):
# Not found.
continue
obj = mupdf.pdf_new_indirect(pdf, xref, 0)
mupdf.pdf_array_push_drop(arr, obj)
mupdf.pdf_array_push(arr, obj)


def JM_set_resource_property(ref, name, xref):
Expand Down
65 changes: 65 additions & 0 deletions tests/test_optional_content.py
Original file line number Diff line number Diff line change
Expand Up @@ -72,3 +72,68 @@ def test_3143():
set1 = set([p["layer"] for p in page.get_drawings()])
set2 = set([b[2] for b in page.get_bboxlog(layers=True)])
assert set0 == set1 == set2


def test_3180():
doc = fitz.open()
page = doc.new_page()

# Define the items for the combo box
combo_items = ['first', 'second', 'third']

# Create a combo box field
combo_box = fitz.Widget() # create a new widget
combo_box.field_type = fitz.PDF_WIDGET_TYPE_COMBOBOX
combo_box.field_name = "myComboBox"
combo_box.field_value = combo_items[0]
combo_box.choice_values = combo_items
combo_box.rect = fitz.Rect(50, 50, 200, 75) # position of the combo box
combo_box.script_change = """
var value = event.value;
app.alert('You selected: ' + value);

//var group_id = optional_content_group_ids[value];

"""

# Insert the combo box into the page
# https://pymupdf.readthedocs.io/en/latest/page.html#Page.add_widget
page.add_widget(combo_box)

# Create optional content groups
# https://github.com/pymupdf/PyMuPDF-Utilities/blob/master/jupyter-notebooks/optional-content.ipynb


# Load images and create OCGs for each
optional_content_group_ids = {}
for i, item in enumerate(combo_items):
optional_content_group_id = doc.add_ocg(item, on=False)
optional_content_group_ids[item] = optional_content_group_id
rect = fitz.Rect(50, 100, 250, 300)
image_file_name = f'{item}.png'
# xref = page.insert_image(
# rect,
# filename=image_file_name,
# oc=optional_content_group_id,
# )


first_id = optional_content_group_ids['first']
second_id = optional_content_group_ids['second']
third_id = optional_content_group_ids['third']

# https://pymupdf.readthedocs.io/en/latest/document.html#Document.set_layer


doc.set_layer(-1, basestate="OFF")
layers = doc.get_layer()
doc.set_layer(config=-1, on=[first_id])

# https://pymupdf.readthedocs.io/en/latest/document.html#Document.set_layer_ui_config
# configs = doc.layer_ui_configs()
# doc.set_layer_ui_config(0, fitz.PDF_OC_ON)
# doc.set_layer_ui_config('third', action=2)

# Save the PDF
doc.save(os.path.abspath(f'{__file__}/../../tests/test_3180.pdf'))
doc.close()
Loading