diff --git a/.github/ISSUE_TEMPLATE/1_pypa.yml b/.github/ISSUE_TEMPLATE/1_pypa.yml index fb75a8ff7..f3df84ce3 100644 --- a/.github/ISSUE_TEMPLATE/1_pypa.yml +++ b/.github/ISSUE_TEMPLATE/1_pypa.yml @@ -1,4 +1,4 @@ -# SPDX-FileCopyrightText: 2024 geisserml +# SPDX-FileCopyrightText: 2025 geisserml # SPDX-License-Identifier: CC-BY-4.0 # TODO add separate template for source install issues? diff --git a/.github/ISSUE_TEMPLATE/2_conda.yml b/.github/ISSUE_TEMPLATE/2_conda.yml index cff2583fa..a7e991938 100644 --- a/.github/ISSUE_TEMPLATE/2_conda.yml +++ b/.github/ISSUE_TEMPLATE/2_conda.yml @@ -1,4 +1,4 @@ -# SPDX-FileCopyrightText: 2024 geisserml +# SPDX-FileCopyrightText: 2025 geisserml # SPDX-License-Identifier: CC-BY-4.0 name: Conda install diff --git a/.github/ISSUE_TEMPLATE/3_other.yml b/.github/ISSUE_TEMPLATE/3_other.yml index 89fb65a84..9e596b9e3 100644 --- a/.github/ISSUE_TEMPLATE/3_other.yml +++ b/.github/ISSUE_TEMPLATE/3_other.yml @@ -1,4 +1,4 @@ -# SPDX-FileCopyrightText: 2024 geisserml +# SPDX-FileCopyrightText: 2025 geisserml # SPDX-License-Identifier: CC-BY-4.0 name: Other issue diff --git a/.github/ISSUE_TEMPLATE/config.yml b/.github/ISSUE_TEMPLATE/config.yml index 18c1a3149..31bfaef66 100644 --- a/.github/ISSUE_TEMPLATE/config.yml +++ b/.github/ISSUE_TEMPLATE/config.yml @@ -1,4 +1,4 @@ -# SPDX-FileCopyrightText: 2024 geisserml +# SPDX-FileCopyrightText: 2025 geisserml # SPDX-License-Identifier: CC-BY-4.0 blank_issues_enabled: false diff --git a/.github/PULL_REQUEST_TEMPLATE.md b/.github/PULL_REQUEST_TEMPLATE.md index d2160aa9a..ed63de46c 100644 --- a/.github/PULL_REQUEST_TEMPLATE.md +++ b/.github/PULL_REQUEST_TEMPLATE.md @@ -16,7 +16,7 @@ - [ ] This PR changes helpers code. - [ ] The change comes with sufficient test cases to confirm correct functionality. -- [ ] Any new test files are under a suitable license and have been registered in `reuse/dep5`. +- [ ] Any new test files are under a suitable license and have been registered in `REUSE.toml`.
Setup @@ -24,8 +24,8 @@ - [ ] This PR changes setup code (`setupsrc/`, `setup.py` etc.). - [ ] I have read through relevant doc sections [Installation -> From source][1] and [Setup Magic][2]. - [ ] I have tested the change does not break internal callers. -- [ ] I believe the change is maintainable and does not cause unreasonable complexity or code pollution. -- [ ] The change does not try to shift a maintenance burden or upstream downstream tasks. *Keep handlers generic, avoid overly downstream-specific or (for us) effectively dead code passages.* +- [ ] I believe the change is maintainable and does not cause unreasonable complexity or code fragmentation. +- [ ] The change does not shift a maintenance burden or upstream downstream tasks. *Keep handlers generic, avoid overly downstream-specific or (for us) effectively dead code passages.* - [ ] I have assessed that the targeted use case cannot reasonably be satisfied by existing means, such as [Caller-provided data files][3], or the change forms a notable improvement over possible alternatives. - [ ] I believe the targeted use case is supported by pypdfium2-team. *Note that we may not want to support esoteric or artificially restricted setup envs.* diff --git a/.github/dependabot.yml b/.github/dependabot.yml index fe4128f0e..88fecb30d 100644 --- a/.github/dependabot.yml +++ b/.github/dependabot.yml @@ -1,5 +1,5 @@ # SPDX-FileCopyrightText: 2024 GitHub -# SPDX-FileCopyrightText: 2024 geisserml +# SPDX-FileCopyrightText: 2025 geisserml # SPDX-License-Identifier: LicenseRef-FairUse OR CC-BY-4.0 # Set update schedule for GitHub Actions diff --git a/.github/workflows/conda.yaml b/.github/workflows/conda.yaml index 389d7d5e4..460b6c1f8 100644 --- a/.github/workflows/conda.yaml +++ b/.github/workflows/conda.yaml @@ -1,4 +1,4 @@ -# SPDX-FileCopyrightText: 2024 geisserml +# SPDX-FileCopyrightText: 2025 geisserml # SPDX-License-Identifier: Apache-2.0 OR BSD-3-Clause name: Conda packaging @@ -14,6 +14,10 @@ on: pdfium_ver: default: 'latest' type: string + new_only: + # only with package == "raw", ignored otherwise (actually the default should be false in that case, but I don't know if GH supports dynamic defaults depending on other inputs) + default: true + type: boolean test: default: true type: boolean @@ -62,8 +66,15 @@ jobs: git config --global user.name "geisserml" python -m pip install -U -r req/setup.txt + # TODO might be able to unify with ${{ inputs.package == 'raw' && inputs.new_only && '--new-only' || '' }} or something + - name: Build package - run: ./run craft --pdfium-ver "${{ inputs.pdfium_ver }}" conda_${{ inputs.package }} + if: ${{ inputs.package == 'helpers' || !inputs.new_only }} + run: ./run craft-conda ${{ inputs.package }} --pdfium-ver ${{ inputs.pdfium_ver }} + + - name: Build package (new only) + if: ${{ inputs.package == 'raw' && inputs.new_only }} + run: ./run craft-conda ${{ inputs.package }} --pdfium-ver ${{ inputs.pdfium_ver }} --new-only - name: Upload artifact uses: actions/upload-artifact@v4 @@ -80,8 +91,8 @@ jobs: fail-fast: false matrix: # NOTE On GH actions, macOS <=13 is Intel, whereas macOS >=14 will be ARM64 - os: ['ubuntu-latest', 'macos-13', 'macos-14', 'windows-latest'] - py: ['3.8', '3.9', '3.10', '3.11', '3.12'] + os: ['ubuntu-latest', 'ubuntu-24.04-arm', 'macos-13', 'macos-14', 'windows-latest'] + py: ['3.8', '3.9', '3.10', '3.11', '3.12', '3.13'] runs-on: ${{ matrix.os }} @@ -121,7 +132,7 @@ jobs: run: | conda install -y pytest pillow numpy conda install -y pypdfium2_${{ inputs.package }} --override-channels -c ./conda_dist/ -c pypdfium2-team -c bblanchon -c defaults - pytest tests/ tests_old/ + pytest tests/ publish: diff --git a/.github/workflows/gh_pages.yaml b/.github/workflows/gh_pages.yaml index ad71d4794..467196faf 100644 --- a/.github/workflows/gh_pages.yaml +++ b/.github/workflows/gh_pages.yaml @@ -1,4 +1,4 @@ -# SPDX-FileCopyrightText: 2024 geisserml +# SPDX-FileCopyrightText: 2025 geisserml # SPDX-License-Identifier: Apache-2.0 OR BSD-3-Clause name: Update Docs (GH Pages) diff --git a/.github/workflows/main.yaml b/.github/workflows/main.yaml index 5a71ec17a..dc4d0eba7 100644 --- a/.github/workflows/main.yaml +++ b/.github/workflows/main.yaml @@ -1,4 +1,4 @@ -# SPDX-FileCopyrightText: 2024 geisserml +# SPDX-FileCopyrightText: 2025 geisserml # SPDX-License-Identifier: Apache-2.0 OR BSD-3-Clause name: Main packaging @@ -64,13 +64,14 @@ jobs: # NOTE autorelease sets a tag, but it's just temporary for informational purpose and does not match the actually published tag # We can't push the tag at this stage because we don't know the outcome of the following jobs yet + # In the future, we might want to move away from using a git branch & tag at this stage, and instead transfer the changes as a patchfile. - name: Run autorelease script run: | git config user.email "geisserml@gmail.com" git config user.name "geisserml" git reset --hard HEAD + # autorelease.py --register automatically switches us onto the autorelease_tmp branch python3 setupsrc/pypdfium2_setup/autorelease.py --register - git checkout autorelease_tmp - name: Get new version id: get_version @@ -110,11 +111,13 @@ jobs: fail-fast: false matrix: # NOTE On GH actions, macOS <=13 is Intel, whereas macOS >=14 will be ARM64 - os: ['ubuntu-latest', 'macos-13', 'macos-14', 'windows-latest'] - py: ['3.8', '3.9', '3.10', '3.11', '3.12'] + os: ['ubuntu-latest', 'ubuntu-24.04-arm', 'macos-13', 'macos-14', 'windows-latest'] + py: ['3.8', '3.9', '3.10', '3.11', '3.12', '3.13'] include: - os: ubuntu-latest wheel: dist/*manylinux_*_x86_64*.whl + - os: ubuntu-24.04-arm + wheel: dist/*manylinux_*_aarch64*.whl - os: macos-13 wheel: dist/*macosx_*_x86_64*.whl - os: macos-14 @@ -145,8 +148,12 @@ jobs: - name: Install dependencies run: | - python3 -m pip install -U pip setuptools - python3 -m pip install -U pytest pillow numpy wheel + python3 -m pip install -U pip setuptools auditwheel + python3 -m pip install -U pytest pillow numpy + + - name: Run auditwheel (informational) + if: ${{ startsWith(matrix.os, 'ubuntu') }} + run: python3 -m auditwheel show ${{ matrix.wheel }} - name: Install pypdfium2 from artifact run: python3 -m pip install ${{ matrix.wheel }} @@ -222,6 +229,7 @@ jobs: with: packages-dir: dist/ # the default + # TODO make sure RELEASE.md is less than 125000 chars, otherwise attach it as an artifact and write a new body pointing out the attachment. We've been hit by this limit in v4.30.1, due to an excessively long pdfium commit log. - name: Publish to GitHub uses: ncipollo/release-action@v1 with: @@ -251,6 +259,7 @@ jobs: { "package": "helpers", "pdfium_ver": "latest", + "new_only": "false", "test": "true", "publish": "${{ inputs.publish }}", "py_version": "3.12" diff --git a/.github/workflows/test_release.yaml b/.github/workflows/test_release.yaml index d7e874111..7be0acc39 100644 --- a/.github/workflows/test_release.yaml +++ b/.github/workflows/test_release.yaml @@ -1,4 +1,4 @@ -# SPDX-FileCopyrightText: 2024 geisserml +# SPDX-FileCopyrightText: 2025 geisserml # SPDX-License-Identifier: Apache-2.0 OR BSD-3-Clause name: Test PyPI Release @@ -17,8 +17,8 @@ jobs: fail-fast: false matrix: # NOTE On GH actions, macOS <=13 is Intel, whereas macOS >=14 will be ARM64 - os: ['ubuntu-latest', 'macos-13', 'macos-14', 'windows-latest'] - py: ['3.8', '3.9', '3.10', '3.11', '3.12'] + os: ['ubuntu-latest', 'ubuntu-24.04-arm', 'macos-13', 'macos-14', 'windows-latest'] + py: ['3.8', '3.9', '3.10', '3.11', '3.12', '3.13'] runs-on: ${{ matrix.os }} diff --git a/.github/workflows/test_setup.yaml b/.github/workflows/test_setup.yaml index 4903e7800..f57a5b5b2 100644 --- a/.github/workflows/test_setup.yaml +++ b/.github/workflows/test_setup.yaml @@ -1,4 +1,4 @@ -# SPDX-FileCopyrightText: 2024 geisserml +# SPDX-FileCopyrightText: 2025 geisserml # SPDX-License-Identifier: Apache-2.0 OR BSD-3-Clause name: Test Setup @@ -20,8 +20,8 @@ jobs: fail-fast: false matrix: # NOTE On GH actions, macOS <=13 is Intel, whereas macOS >=14 will be ARM64 - os: ['ubuntu-latest', 'macos-13', 'macos-14', 'windows-latest'] - py: ['3.8', '3.9', '3.10', '3.11', '3.12'] + os: ['ubuntu-latest', 'ubuntu-24.04-arm', 'macos-13', 'macos-14', 'windows-latest'] + py: ['3.8', '3.9', '3.10', '3.11', '3.12', '3.13'] runs-on: ${{ matrix.os }} diff --git a/.github/workflows/test_sourcebuild.yaml b/.github/workflows/test_sourcebuild.yaml index 3bef3aec2..badf432d2 100644 --- a/.github/workflows/test_sourcebuild.yaml +++ b/.github/workflows/test_sourcebuild.yaml @@ -1,4 +1,4 @@ -# SPDX-FileCopyrightText: 2024 geisserml +# SPDX-FileCopyrightText: 2025 geisserml # SPDX-License-Identifier: Apache-2.0 OR BSD-3-Clause name: Test Sourcebuild @@ -19,11 +19,14 @@ jobs: fail-fast: false matrix: # NOTE On GH actions, macOS <=13 is Intel, whereas macOS >=14 will be ARM64 - os: [ubuntu-latest, macos-13, macos-14, windows-latest] + # FIXME Google's toolchain doesn't seem to run on Linux arm64 natively + os: ['ubuntu-latest', 'macos-13', 'macos-14', 'windows-latest'] # 'ubuntu-24.04-arm' use_syslibs: [false] include: - - os: ubuntu-latest + - os: 'ubuntu-latest' use_syslibs: [true] + # - os: 'ubuntu-24.04-arm' + # use_syslibs: [true] runs-on: ${{ matrix.os }} @@ -45,13 +48,13 @@ jobs: - name: Build PDFium (default) if: ${{ !matrix.use_syslibs }} - run: python3 ./setupsrc/pypdfium2_setup/build_pdfium.py + run: python3 ./setupsrc/pypdfium2_setup/sourcebuild.py - name: Build PDFium (with syslibs) if: ${{ matrix.use_syslibs }} run: | - sudo apt-get install -y libfreetype-dev liblcms2-dev libjpeg-dev libopenjp2-7-dev libpng-dev zlib1g-dev libicu-dev libtiff-dev - python3 ./setupsrc/pypdfium2_setup/build_pdfium.py --use-syslibs + sudo apt-get install -y libfreetype-dev liblcms2-dev libjpeg-dev libopenjp2-7-dev libpng-dev zlib1g-dev libicu-dev libtiff-dev libglib2.0-dev + python3 ./setupsrc/pypdfium2_setup/sourcebuild.py --use-syslibs - name: Install run: PDFIUM_PLATFORM="sourcebuild" python3 -m pip install . diff --git a/.github/workflows/trigger_conda_raw.yaml b/.github/workflows/trigger_conda_raw.yaml index f562a790e..33e012ead 100644 --- a/.github/workflows/trigger_conda_raw.yaml +++ b/.github/workflows/trigger_conda_raw.yaml @@ -1,4 +1,4 @@ -# SPDX-FileCopyrightText: 2024 geisserml +# SPDX-FileCopyrightText: 2025 geisserml # SPDX-License-Identifier: Apache-2.0 OR BSD-3-Clause name: Trigger conda_raw release @@ -22,6 +22,7 @@ jobs: { "package": "raw", "pdfium_ver": "latest", + "new_only": "true", "test": "true", "publish": "true", "py_version": "3.12" diff --git a/.github/workflows/trigger_main.yaml b/.github/workflows/trigger_main.yaml index f9a0c44f2..86449d7a3 100644 --- a/.github/workflows/trigger_main.yaml +++ b/.github/workflows/trigger_main.yaml @@ -1,11 +1,12 @@ -# SPDX-FileCopyrightText: 2024 geisserml +# SPDX-FileCopyrightText: 2025 geisserml # SPDX-License-Identifier: Apache-2.0 OR BSD-3-Clause # Separate trigger workflow because we can't configure inputs for scheduled workflow runs (and don't want publish enabled by default in the main workflow) name: Trigger main release on: - # NOTE temporarily commented out, awaiting merge of the v5 branch + # NOTE temporarily commented out during the beta phase + # The beta field in autorelease/config.json is automatically unset after a beta release, but we probably don't want the real release to be auto-triggered - instead it should be triggered intentionally and be supervised by the maintainer. # # https://github.com/bblanchon/pdfium-binaries/blob/master/.github/workflows/trigger.yml # # https://docs.github.com/en/actions/using-workflows/events-that-trigger-workflows#schedule # # https://crontab.guru/ diff --git a/.gitignore b/.gitignore index acc9141c6..a3aa3af8c 100644 --- a/.gitignore +++ b/.gitignore @@ -1,11 +1,10 @@ -# SPDX-FileCopyrightText: 2024 geisserml +# SPDX-FileCopyrightText: 2025 geisserml # SPDX-License-Identifier: CC-BY-4.0 build/ dist/ conda/*/out/ tests/output/ -tests_old/output/ data/ !data/.gitkeep diff --git a/.readthedocs.yaml b/.readthedocs.yaml index b9de23cb2..127bd10cf 100644 --- a/.readthedocs.yaml +++ b/.readthedocs.yaml @@ -1,4 +1,4 @@ -# SPDX-FileCopyrightText: 2024 geisserml +# SPDX-FileCopyrightText: 2025 geisserml # SPDX-License-Identifier: CC-BY-4.0 # Read the Docs configuration file diff --git a/.reuse/dep5 b/.reuse/dep5 deleted file mode 100644 index 8caad1951..000000000 --- a/.reuse/dep5 +++ /dev/null @@ -1,106 +0,0 @@ -Format: https://www.debian.org/doc/packaging-manuals/copyright-format/1.0/ -Upstream-Name: pypdfium2 -Upstream-Contact: geisserml -Source: https://github.com/pypdfium2-team/pypdfium2 - -# This file contains copyright info for data files in the project directory. -# It is shipped with our sdist packages, although not all members are actually included. -# See also .reuse/dep5-wheel regarding copyright of data files in our wheel packages. - -# Sample paragraph, commented out: -# -# Files: src/* -# Copyright: $YEAR $NAME <$CONTACT> -# License: ... - -Files: - req/*.txt - autorelease/record.json - autorelease/config.json - .github/PULL_REQUEST_TEMPLATE.md - tests/expectations/* - tests/resources/render.pdf - tests/resources/multipage.pdf - tests/resources/encrypted.pdf - tests/resources/text.pdf - tests/resources/empty.pdf -Copyright: 2024 geisserml -License: CC-BY-4.0 - -Files: autorelease/bindings.py -Copyright: - 2024 geisserml - 2024 ctypesgen developers -License: CC-BY-4.0 -Comment: - Auto-generated by ctypesgen. Probably not copyrighted. - -Files: tests/resources/toc.pdf -Copyright: 2020 Matthias Erll -License: LicenseRef-FairUse -Comment: - Obtained from: https://github.com/pikepdf/pikepdf/blob/master/tests/resources/outlines.pdf - No individual license stated for this data file. Project license is MPL-2.0. - (https://github.com/pikepdf/pikepdf/blob/master/debian/copyright says "License assumed from LICENSE.txt in project root.") - -Files: - tests/resources/toc_circular.pdf - tests/resources/toc_viewmodes.pdf - tests/resources/toc_maxdepth.pdf - tests/resources/forms.pdf - tests/resources/attachments.pdf - tests/resources/mona_lisa.jpg -Copyright: 2022 PDFium developers -License: BSD-3-Clause, Apache-2.0 -Comment: - Obtained from: - https://pdfium.googlesource.com/pdfium/+/refs/heads/main/testing/resources/bookmarks_circular.pdf - https://pdfium.googlesource.com/pdfium_tests/+/refs/heads/main/fx/other/8.2_outline.pdf - https://pdfium.googlesource.com/pdfium_tests/+/refs/heads/main/fx/FRC_8.2.2_part1/FRC_51_8.2.2_T_8.4__Count_edit_count_100.pdf - https://pdfium.googlesource.com/pdfium/+/refs/heads/main/testing/resources/listbox_form.pdf - https://pdfium.googlesource.com/pdfium/+/refs/heads/main/testing/resources/embedded_attachments.pdf - https://pdfium.googlesource.com/pdfium/+/refs/heads/main/testing/resources/mona_lisa.jpg - -Files: - tests/resources/box_fallback.in - tests/resources/box_fallback.pdf -Copyright: - 2022 PDFium developers - 2024 geisserml -License: BSD-3-Clause, Apache-2.0 - -Files: tests/resources/images.pdf -Copyright: - 2024 geisserml - 2022 Johannes Schauer Marin Rodrigues -License: LicenseRef-FairUse -Comments: - Contains `mono.png` from the img2pdf test suite. - -Files: - sourcebuild/patches/public_headers.patch - sourcebuild/patches/shared_library.patch - sourcebuild/patches/win/build.patch - sourcebuild/patches/win/pdfium.patch - sourcebuild/patches/win/resources.rc -Copyright: - 2024 Benoît Blanchon and pdfium-binaries contributors - 2024 PDFium developers -License: LicenseRef-FairUse -Comment: - Obtained from https://github.com/bblanchon/pdfium-binaries/tree/master/patches - For reuse, see https://github.com/bblanchon/pdfium-binaries/issues/55 - Unchanged/deleted code is owned by PDFium, added code by pdfium-binaries. - -Files: autorelease/record.json -Copyright: 2024 geisserml -License: LicenseRef-PdfiumThirdParty -Comment: - Dummy to prevent reuse lint from reporting LicenseRef-PdfiumThirdParty as unused. - The file is actually auto-generated and not copyrighted. - -# Files: tests/resources/NotoSans-Regular.ttf -# Copyright: 2022 Google Inc. -# License: OFL-1.1 -# Comment: -# Obtained from: https://fonts.google.com/noto/specimen/Noto+Sans diff --git a/.reuse/dep5-wheel b/.reuse/dep5-wheel deleted file mode 100644 index 5b046468b..000000000 --- a/.reuse/dep5-wheel +++ /dev/null @@ -1,29 +0,0 @@ -Format: https://www.debian.org/doc/packaging-manuals/copyright-format/1.0/ -Upstream-Name: pypdfium2 -Upstream-Contact: geisserml -Source: https://github.com/pypdfium2-team/pypdfium2 - -# This file contains copyright info for data files included in our binary wheel packages. -# See also .reuse/dep5 regarding copyright of data files in the project directory. - -Files: - pypdfium2_raw/bindings.py - pypdfium2_raw/version.json - pypdfium2_helpers/version.json -Copyright: - 2024 geisserml - 2024 ctypesgen developers -License: Apache-2.0 OR BSD-3-Clause -Comment: - Auto-generated by ctypesgen/pypdfium2. - pypdfium2_helpers/... applies only if the helpers module is included. - -Files: - pypdfium2_raw/libpdfium.so - pypdfium2_raw/libpdfium.dylib - pypdfium2_raw/pdfium.dll -Copyright: - 2024 PDFium developers - 2024 Developers of projects mentioned in PdfiumThirdParty - 2024 Benoît Blanchon and pdfium-binaries contributors -License: (BSD-3-Clause, Apache-2.0) AND LicenseRef-PdfiumThirdParty diff --git a/MANIFEST.in b/MANIFEST.in index d919fb596..a7440e0dd 100644 --- a/MANIFEST.in +++ b/MANIFEST.in @@ -1,4 +1,4 @@ -# SPDX-FileCopyrightText: 2024 geisserml +# SPDX-FileCopyrightText: 2025 geisserml # SPDX-License-Identifier: Apache-2.0 OR BSD-3-Clause # MANIFEST.in defines the files to include in an sdist. sdists are also the starting point for wheels. @@ -20,8 +20,8 @@ include src/pypdfium2_raw/__init__.py # Include all of licenses in an sdist (also wheel licenses), since we may build a wheel from the sdist recursive-include LICENSES/ * -include .reuse/dep5 -include .reuse/dep5-wheel +include REUSE.toml +include REUSE-wheel.toml # PyPA eagerly includes tests - exclude because we don't use them recursive-exclude tests * diff --git a/README.md b/README.md index 98d99ae8d..68ee2efab 100644 --- a/README.md +++ b/README.md @@ -1,4 +1,4 @@ - + # pypdfium2 @@ -27,7 +27,7 @@ pypdfium2 includes helpers to simplify common use cases, while the raw PDFium/ct * From source 🔗 * Dependencies: - - System: git, C pre-processor (gcc/clang - alternatively, specify the command to envoke via `$CPP`) + - System: git, C pre-processor (gcc/clang - alternatively, specify the command to invoke via `$CPP`) - Python: ctypesgen (pypdfium2-team fork), wheel, setuptools. Usually installed automatically. * Get the code @@ -46,36 +46,55 @@ pypdfium2 includes helpers to simplify common use cases, while the raw PDFium/ct * With self-built binary 🔗 ```bash # call build script with --help to list options - python setupsrc/pypdfium2_setup/build_pdfium.py + python setupsrc/pypdfium2_setup/sourcebuild.py PDFIUM_PLATFORM="sourcebuild" python -m pip install -v . ``` Building PDFium may take a long time, as it comes with its bundled toolchain and deps, rather than taking them from the system.[^pdfium_buildsystem] - However, we can at least provide the `--use-syslibs` option to build against system-provided runtime libraries. + However, we can at least provide the `--use-syslibs` option to build against system runtime libraries. - * With system-provided binary 🔗 + * With system-level binary 🔗 ```bash - # Substitute $PDFIUM_VER with the system pdfium's build version. + # Substitute $PDFIUM_VER with the system pdfium build's version. # For ABI safety reasons, you'll want to make sure `$PDFIUM_VER` is correct and the bindings are rebuilt whenever system pdfium is updated. PDFIUM_PLATFORM="system:$PDFIUM_VER" python -m pip install -v . ``` Link against external pdfium instead of bundling it. Note, this is basically a high-level convenience entry point to internal bindings generation, and intended for end users. Therefore it is less flexible, supporting only the "simple case" for now. For more sohpisticated use cases that need passing custom parameters to ctypesgen (e.g. runtime libdirs / headers / feature flags), consider [caller-provided data files](#install-source-caller). + + * With system-level binary (non-standard location, e.g. LibreOffice) 🔗 + ```bash + # if root rights are available and targeting /usr/local/lib is OK + sudo ln -s /usr/lib/libreoffice/program/libpdfiumlo.so /usr/local/lib/libpdfium.so + # Substitute $PDFIUM_VER with the pdfium build's version. + PDFIUM_PLATFORM="system:$PDFIUM_VER" python -m pip install -v . + ``` + + Symlink pdfium from a non-standard location (e.g. libreoffice libdir) to a directory that is on the search path, determine the version, and install with system pdfium [as described above](#install-source-system). + + Note, if elevated privileges are not available, you can target e.g. `~/.local/lib` and add it to [`LD_LIBRARY_PATH`](https://docs.python.org/3/library/ctypes.html#finding-shared-libraries) in your `~/.bashrc` file. + + Background: At this time, Linux/BSD distributions do not usually provide pdfium as an own package. However, some may ship a pdfium shared library as part of Libreoffice. + This may be helpful to get pypdfium2 installed on platforms not covered by pdfium-binaries yet (e.g. `linux ppc64le/s390x`, `freebsd`). + + Libreoffice actually uses its own build system for pdfium, so your distributor may be able to do this even for platforms not supported by Google's toolchain. + At this time, Debian/Ubuntu and FreeBSD seem to build Libreoffice with pdfium; however, Red Hat do not. - + * With caller-provided data files 🔗 (this is expected to work offline) ```bash - # Call ctypesgen (see --help or packaging_base.py::run_ctypesgen() for further options) + # Call ctypesgen (see --help or base.py::run_ctypesgen() for further options) # Reminder: you'll want to use the pypdfium2-team fork of ctypesgen ctypesgen --library pdfium --runtime-libdirs $MY_LIBDIRS --headers $MY_INCLUDE_DIR/fpdf*.h -o src/pypdfium2_raw/bindings.py [-D $MY_FLAGS] # Write the version file (fill the placeholders). - # Note, this is not a mature interface yet and might change! + # See https://pypdfium2.readthedocs.io/en/stable/python_api.html#pypdfium2.version.PDFIUM_INFO for field documentation + # Note, this is not a mature interface yet and might change any time! # major/minor/build/patch: integers forming the pdfium version being packaged # n_commits/hash: git describe like post-tag info (0/null for release commit) - # origin: a string to identify the build, consisting of binary source and package provider (e.g. "system/debian", "pdfium-binaries/debian") + # origin: a string to identify the build, in the form `$BUILDER`, `$DISTNAME/$BUILDER`, `system/$BUILDER` or `system/$DISTNAME/$BUILDER`. (Use the `$DISTNAME/$BUILDER` form if you are a distribution maintainer re-packaging another builder's binaries. Add the `system` prefix if the binary is loaded from a system path rather than bundled with pypdfium2.) # flags: a comma-delimited list of pdfium feature flag strings (e.g. "V8", "XFA") - may be empty for default build - cat >"src/pypdfium2_raw/version.json" < "src/pypdfium2_raw/version.json" < * Read the table of contents ```python - for item in pdf.get_toc(): - state = "*" if item.n_kids == 0 else "-" if item.is_closed else "+" - target = "?" if item.page_index is None else item.page_index+1 - print( - " " * item.level + - "[%s] %s -> %s # %s %s" % ( - state, item.title, target, item.view_mode, item.view_pos, - ) + import pypdfium2.internal as pdfium_i + + for bm in pdf.get_toc(max_depth=15): + count, dest = bm.get_count(), bm.get_dest() + out = " " * bm.level + out += "[%s] %s -> " % ( + f"{count:+}" if count != 0 else "*", + bm.get_title(), ) + if dest: + index, (view_mode, view_pos) = dest.get_index(), dest.get_view() + out += "%s # %s %s" % ( + index+1 if index != None else "?", + pdfium_i.ViewmodeToStr.get(view_mode), + round(view_pos, 3), + ) + else: + out += "_" + print(out) ``` * Create a new PDF with an empty A4 sized page @@ -300,7 +333,7 @@ Here are some examples of using the support model API. image = pdfium.PdfImage.new(pdf) image.load_jpeg("./tests/resources/mona_lisa.jpg") - width, height = image.get_size() + width, height = image.get_px_size() matrix = pdfium.PdfMatrix().scale(width, height) image.set_matrix(matrix) @@ -318,14 +351,14 @@ Here are some examples of using the support model API. ### Raw PDFium API -While helper classes conveniently wrap the raw PDFium API, it may still be accessed directly and is available in the namespace `pypdfium2.raw`. Lower-level helpers that may aid with using the raw API are provided in `pypdfium2.internal`. +While helper classes conveniently wrap the raw PDFium API, it may still be accessed directly and is available in the namespace `pypdfium2.raw`. Lower-level utilities that may aid with using the raw API are provided in `pypdfium2.internal`. ```python import pypdfium2.raw as pdfium_c import pypdfium2.internal as pdfium_i ``` -Since PDFium is a large library, many components are not covered by helpers yet. You may seamlessly interact with the raw API while still using helpers where available. When used as ctypes function parameter, helper objects automatically resolve to the underlying raw object (but you may still access it explicitly if desired): +Since PDFium is a large library, many components are not covered by helpers yet. However, as helpers expose their underlying raw objects, you may seamlessly integrate raw APIs while using helpers as available. When passed as ctypes function parameter, helpers automatically resolve to the raw object handle (but you may still access it explicitly if desired): ```python permission_flags = pdfium_c.FPDF_GetDocPermission(pdf.raw) # explicit permission_flags = pdfium_c.FPDF_GetDocPermission(pdf) # implicit @@ -333,14 +366,14 @@ permission_flags = pdfium_c.FPDF_GetDocPermission(pdf) # implicit For PDFium docs, please look at the comments in its [public header files](https://pdfium.googlesource.com/pdfium/+/refs/heads/main/public/).[^pdfium_docs] A large variety of examples on how to interface with the raw API using [`ctypes`](https://docs.python.org/3/library/ctypes.html) is already provided with [support model source code](src/pypdfium2/_helpers). -Nonetheless, the following guide may be helpful to get started with the raw API, especially for developers who are not familiar with `ctypes` yet. +Nonetheless, the following guide may be helpful to get started with the raw API, if you are not familiar with `ctypes` yet. [^pdfium_docs]: Unfortunately, no recent HTML-rendered docs are available for PDFium at the moment. * In general, PDFium functions can be called just like normal Python functions. - However, parameters may only be passed positionally, i. e. it is not possible to use keyword arguments. + However, parameters may only be passed positionally, i.e. it is not possible to use keyword arguments. There are no defaults, so you always need to provide a value for each argument. ```python # arguments: filepath (bytes), password (bytes|None) @@ -355,12 +388,12 @@ Nonetheless, the following guide may be helpful to get started with the raw API, FPDF_LoadDocument.argtypes = [FPDF_STRING, FPDF_BYTESTRING] FPDF_LoadDocument.restype = FPDF_DOCUMENT ``` - Python `bytes` are converted to `FPDF_STRING` by ctypes autoconversion. + Python `bytes` are converted to `FPDF_STRING` by ctypes autoconversion. This works because `FPDF_STRING` is actually an alias to `POINTER(c_char)` (i.e. `char*`), which is a primitive pointer type. When passing a string to a C function, it must always be null-terminated, as the function merely receives a pointer to the first item and then continues to read memory until it finds a null terminator. [^bindings_decl]: From the auto-generated bindings file. We maintain a reference copy at `autorelease/bindings.py`. Or if you have an editable install, there will also be `src/pypdfium2_raw/bindings.py`. -* While some functions are quite easy to use, things soon get more complex. +* While some functions are quite easy to use, things may soon get more special. First of all, function parameters are not only used for input, but also for output: ```python # Initialise an integer object (defaults to 0) @@ -392,19 +425,18 @@ Nonetheless, the following guide may be helpful to get started with the raw API, ``` * For string output parameters, callers needs to provide a sufficiently long, pre-allocated buffer. - This may work differently depending on what type the function requires, which encoding is used, whether the number of bytes or characters is returned, and whether space for a null terminator is included or not. Carefully review the documentation for the function in question to fulfill its requirements. + This may work differently depending on what type the function requires, which encoding is used, whether the number of bytes or characters is returned, and whether space for a null terminator is included or not. Carefully review the documentation of the function in question to fulfill its requirements. Example A: Getting the title string of a bookmark. ```python # (Assuming `bookmark` is an FPDF_BOOKMARK) - # First call to get the required number of bytes (not characters!), including space for a null terminator + # First call to get the required number of bytes (not units!), including space for a null terminator n_bytes = pdfium_c.FPDFBookmark_GetTitle(bookmark, None, 0) # Initialise the output buffer buffer = ctypes.create_string_buffer(n_bytes) # Second call with the actual buffer pdfium_c.FPDFBookmark_GetTitle(bookmark, buffer, n_bytes) - # Decode to string, cutting off the null terminator - # Encoding: UTF-16LE (2 bytes per character) + # Decode to string, cutting off the null terminator (encoding: UTF-16LE) title = buffer.raw[:n_bytes-2].decode("utf-16-le") ``` @@ -413,16 +445,17 @@ Nonetheless, the following guide may be helpful to get started with the raw API, # (Assuming `textpage` is an FPDF_TEXTPAGE and the boundary variables are set) # Store common arguments for the two calls args = (textpage, left, top, right, bottom) - # First call to get the required number of characters (not bytes!) - a possible null terminator is not included + # First call to get the required number of units (not bytes!) - a possible null terminator is not included n_chars = pdfium_c.FPDFText_GetBoundedText(*args, None, 0) # If no characters were found, return an empty string if n_chars <= 0: return "" - # Calculate the required number of bytes (UTF-16LE encoding again) + # Calculate the required number of bytes (encoding: UTF-16LE again) + # The function signature uses c_ushort, so 1 unit takes sizeof(c_ushort) == 2 bytes n_bytes = 2 * n_chars # Initialise the output buffer - this function can work without null terminator, so skip it buffer = ctypes.create_string_buffer(n_bytes) - # Re-interpret the type from char to unsigned short as required by the function + # Re-interpret the type from char to unsigned short* as required by the function buffer_ptr = ctypes.cast(buffer, ctypes.POINTER(ctypes.c_ushort)) # Second call with the actual buffer pdfium_c.FPDFText_GetBoundedText(*args, buffer_ptr, n_chars) @@ -432,8 +465,8 @@ Nonetheless, the following guide may be helpful to get started with the raw API, * Not only are there different ways of string output that need to be handled according to the requirements of the function in question. String input, too, can work differently depending on encoding and type. - We have already discussed `FPDF_LoadDocument()`, which takes a UTF-8 encoded string as `char *`. - A different examples is `FPDFText_FindStart()`, which needs a UTF-16LE encoded string, given as `unsigned short *`: + We have already discussed `FPDF_LoadDocument()`, which takes a UTF-8 encoded string as `char*`. + A different examples is `FPDFText_FindStart()`, which needs a UTF-16LE encoded string, given as `unsigned short*`: ```python # (Assuming `text` is a str and `textpage` an FPDF_TEXTPAGE) # Add the null terminator and encode as UTF-16LE @@ -445,7 +478,7 @@ Nonetheless, the following guide may be helpful to get started with the raw API, * Leaving strings, let's suppose you have a C memory buffer allocated by PDFium and wish to read its data. PDFium will provide you with a pointer to the first item of the byte array. - To access the data, you'll want to re-interpret the pointer using `ctypes.cast()` to encompass the whole array: + To access the data, you'll want to re-interpret the pointer with `ctypes.cast()` to encompass the whole array: ```python # (Assuming `bitmap` is an FPDF_BITMAP and `size` is the expected number of bytes in the buffer) buffer_ptr = pdfium_c.FPDFBitmap_GetBuffer(bitmap) @@ -466,7 +499,7 @@ Nonetheless, the following guide may be helpful to get started with the raw API, n_bytes = py_buffer.readinto(buffer_ptr.contents) # returns the number of bytes read ``` -* If you wish to check whether two objects returned by PDFium are the same, the `is` operator won't help because `ctypes` does not have original object return (OOR), i. e. new, equivalent Python objects are created each time, although they might represent one and the same C object.[^ctypes_no_oor] +* If you wish to check whether two objects returned by PDFium are the same, the `is` operator won't help because `ctypes` does not have original object return (OOR), i.e. new, equivalent Python objects are created each time, although they might represent one and the same C object.[^ctypes_no_oor] That's why you'll want to use `ctypes.addressof()` to get the memory addresses of the underlying C object. For instance, this is used to avoid infinite loops on circular bookmark references when iterating through the document outline: ```python @@ -490,7 +523,7 @@ Nonetheless, the following guide may be helpful to get started with the raw API, [^callback_usecases]: e. g. incremental read/write, management of progressive tasks, ... - Example: Loading a document from a Python buffer. This way, file access can be controlled in Python while the whole data does not need to be in memory at once. + Example: Loading a document from a Python buffer. This way, file access can be controlled in Python while the data does not need to be in memory at once. ```python import os @@ -500,9 +533,9 @@ Nonetheless, the following guide may be helpful to get started with the raw API, def __init__(self, py_buffer): self.py_buffer = py_buffer - def __call__(self, _, position, p_buf, size): + def __call__(self, _, position, buffer_ptr, size): # Write data from Python buffer into C buffer, as explained before - buffer_ptr = ctypes.cast(p_buf, ctypes.POINTER(ctypes.c_char * size)) + buffer_ptr = ctypes.cast(buffer_ptr, ctypes.POINTER(ctypes.c_char * size)) self.py_buffer.seek(position) self.py_buffer.readinto(buffer_ptr.contents) return 1 # non-zero return code for success @@ -528,7 +561,7 @@ Nonetheless, the following guide may be helpful to get started with the raw API, * When using the raw API, special care needs to be taken regarding object lifetime, considering that Python may garbage collect objects as soon as their reference count reaches zero. However, the interpreter has no way of magically knowing how long the underlying resources of a Python object might still be needed on the C side, so measures need to be taken to keep such objects referenced until PDFium does not depend on them anymore. - If resources need to remain valid after the time of a function call, PDFium docs usually indicate this clearly. Ignoring requirements on object lifetime will lead to memory corruption (commonly resulting in a segfault). + If resources need to remain valid after the time of a function call, PDFium docs usually indicate this clearly. Ignoring requirements on object lifetime will lead to memory corruption (commonly resulting in a segfault sooner or later). For instance, the docs on `FPDF_LoadCustomDocument()` state that > The application must keep the file resources |pFileAccess| points to valid until the returned FPDF_DOCUMENT is closed. |pFileAccess| itself does not need to outlive the FPDF_DOCUMENT. @@ -564,7 +597,7 @@ Nonetheless, the following guide may be helpful to get started with the raw API, data_holder.close() ``` -* Finally, let's finish with an example how to render the first page of a document to a `PIL` image in `RGBA` color format. +* Finally, let's finish this guide with an example how to render the first page of a document to a `PIL` image in `RGBA` color format. ```python import math import ctypes @@ -635,28 +668,27 @@ Usage should be largely self-explanatory, assuming a minimum of familiarity with ## Licensing -*Important: This is NOT LEGAL ADVICE, and there is ABSOLUTELY NO WARRANTY for any information provided in this document or elsewhere in the pypdfium2 project, including earlier revisions.* +*Disclaimer: This project is provided on an "as-is" basis. This is not legal advice, and there is ABSOLUTELY NO WARRANTY for any information provided in this document or elsewhere in the pypdfium2 project, including earlier revisions. We disclaim liability for any possible damages resulting from using this license information. It is the embedder's responsibility to check on licensing. See also [GitHub's disclaimer](https://docs.github.com/en/repositories/managing-your-repositorys-settings-and-features/customizing-your-repository/licensing-a-repository#disclaimer).* pypdfium2 itself is available by the terms and conditions of [`Apache-2.0`](LICENSES/Apache-2.0.txt) / [`BSD-3-Clause`](LICENSES/BSD-3-Clause.txt). Documentation and examples of pypdfium2 are licensed under [`CC-BY-4.0`](LICENSES/CC-BY-4.0.txt). PDFium is available under a BSD-style license that can be found in its [`LICENSE`](https://pdfium.googlesource.com/pdfium/+/refs/heads/main/LICENSE) file. Various other open-source licenses apply to dependencies bundled with PDFium. These also have to be shipped alongside binary redistributions. Copies of identified licenses are provided in [`LicenseRef-PdfiumThirdParty.txt`](LICENSES/LicenseRef-PdfiumThirdParty.txt). -There is no guarantee of completeness, and pdfium's dependencies might change over time. Please do notify us if you think this misses a relevant license. +Note that pdfium's dependencies might change over time. Although we try to keep an eye on the situation, there is no guarantee of completeness. Please notify us if you think this misses a relevant license. pypdfium2 includes [SPDX](https://spdx.org/licenses/) headers in source files. -License information for data files is provided in [`.reuse/dep5`](.reuse/dep5) as per the [`reuse` standard](https://reuse.software/spec/). +License information for data files is provided in [`REUSE.toml`](REUSE.toml) as per the [`reuse` standard](https://reuse.software/spec/). To the author's knowledge, pypdfium2 is one of the rare Python libraries that are capable of PDF rendering while not being covered by copyleft licenses (such as the `GPL`).[^liberal_pdf_renderlibs] [^liberal_pdf_renderlibs]: The only other liberal-licensed PDF rendering libraries known to the author are [`pdf.js`](https://github.com/mozilla/pdf.js/) (JavaScript) and [`Apache PDFBox`](https://github.com/apache/pdfbox) (Java), but python bindings packages don't exist yet or are unsatisfactory. However, we wrote some gists that show it'd be possible in principle: [pdfbox](https://gist.github.com/mara004/51c3216a9eabd3dcbc78a86d877a61dc) (+ [setup](https://gist.github.com/mara004/881d0c5a99b8444fd5d1d21a333b70f8)), [pdfjs](https://gist.github.com/mara004/87276da4f8be31c80c38036c6ab667d7). -## Issues +## Issues / Contributions While using pypdfium2, you might encounter bugs or missing features. -In this case, feel free to open an issue or discuss thread. If applicable, include details such as tracebacks, OS and CPU type, as well as the versions of pypdfium2 and used dependencies. -__However, please note our [response policy](#contributions).__ +In this case, feel free to open an issue or discussion thread. If applicable, include details such as tracebacks, OS and CPU type, as well as the versions of pypdfium2 and used dependencies. Roadmap: * pypdfium2 @@ -669,22 +701,27 @@ Roadmap: * [pdfium-binaries](https://github.com/bblanchon/pdfium-binaries/issues): Binary builder. * [ctypesgen](https://github.com/ctypesgen/ctypesgen/issues): Bindings generator. -### Known limitations +### Response policy + -#### Incompatibility with CPython 3.7.6 and 3.8.1 +Given this is a volunteer open-source project, it is possible you may not get a response to your issue, or it may be closed without much feedback. Conversations may be locked if we feel like our attention is getting DDOSed. We may not have time to provide usage support. -pypdfium2 built with mainstream ctypesgen cannot be used with releases 3.7.6 and 3.8.1 of the CPython interpreter due to a [regression](https://github.com/python/cpython/pull/16799#issuecomment-612353119) that [broke](https://github.com/ctypesgen/ctypesgen/issues/77) ctypesgen-created string handling code. +The same applies to Pull Requests. We will accept contributions only if we find them suitable. Do not reach out with a strong expectation to get your change merged; it is solely up to the repository owner to decide if and when a PR will be merged, and we are free to silently reject PRs we do not like. -Since version 4, pypdfium2 is built with a patched fork of ctypesgen that removes ctypesgen's problematic string code. +### Known limitations + +#### Incompatibility with Threading + +PDFium is inherently not thread-safe. See the [API docs](https://pypdfium2.readthedocs.io/en/stable/python_api.html#incompatibility-with-threading) for more information. #### Risk of unknown object lifetime violations As outlined in the raw API section, it is essential that Python-managed resources remain available as long as they are needed by PDFium. -The problem is that the Python interpreter may garbage collect objects with reference count zero at any time, so an unreferenced but still required object may either by chance stay around long enough or disappear too soon, resulting in non-deterministic memory issues that are hard to debug. +The problem is that the Python interpreter may garbage collect objects with reference count zero at any time, so it can happen that an unreferenced but still required object by chance stays around long enough before it is garbage collected. However, it could also disappear too soon and cause breakage. Such dangling objects result in non-deterministic memory issues that are hard to debug. If the timeframe between reaching reference count zero and removal is sufficiently large and roughly consistent across different runs, it is even possible that mistakes regarding object lifetime remain unnoticed for a long time. -Although we intend to develop helpers carefully, it cannot be fully excluded that unknown object lifetime violations are still lurking around somewhere, especially if unexpected requirements were not documented by the time the code was written. +Although we intend to develop helpers carefully, it cannot be fully excluded that unknown object lifetime violations might still be lurking around somewhere, especially if unexpected requirements were not documented by the time the code was written. #### Missing raw PDF access @@ -692,7 +729,7 @@ As of this writing, PDFium's public interface does not provide access to the raw #### Limitations of ABI bindings -PDFium's non-public backend would provide extended capabilities, including [raw access](#missing-raw-pdf-access), but it is not exported into the ABI and written in C++ (not pure C), so we cannot use it with `ctypes`. This means it's out of scope for this project. +PDFium's non-public backend would provide extended capabilities, including [raw access](#missing-raw-pdf-access), but it is written in C++, which (unlike pure C) does not result in a stable ABI, so we cannot use it with `ctypes`. This means it's out of scope for this project. Also, while ABI bindings tend to be more convenient, they have some technical drawbacks compared to API bindings (see e.g. [1](https://cffi.readthedocs.io/en/latest/overview.html#abi-versus-api), [2](https://github.com/ocrmypdf/OCRmyPDF/issues/541#issuecomment-1834684532)) @@ -700,17 +737,6 @@ Also, while ABI bindings tend to be more convenient, they have some technical dr ## Development -### Contributions - - -> We may accept contributions, but only if our code quality expectations are met. - -__Policy__: -* We may not respond to your issue or PR. -* We may close an issue or PR without much feedback. -* We may lock discussions or contributions if our attention is getting DDOSed. -* We may not provide much usage support. - ### Long lines The pypdfium2 codebase does not hard wrap long lines. @@ -771,9 +797,9 @@ The release process is fully automated using Python scripts and scheduled releas You may also trigger the workflow manually using the GitHub Actions panel or the [`gh`](https://cli.github.com/) command-line tool. Python release scripts are located in the folder `setupsrc/pypdfium2_setup`, along with custom setup code: -* `update_pdfium.py` downloads binaries. -* `craft_packages.py pypi` builds platform-specific wheel packages and a source distribution suitable for PyPI upload. -* `autorelease.py` takes care of versioning, changelog, release note generation and VCS checkin. +* `update.py` downloads binaries. +* `craft.py` builds platform-specific wheel packages and a source distribution suitable for PyPI upload. +* `autorelease.py` takes care of versioning, changelog, release note generation and VCS check-in. The autorelease script has some peculiarities maintainers should know about: * The changelog for the next release shall be written into `docs/devel/changelog_staging.md`. @@ -797,8 +823,8 @@ In case of necessity, you may also forego autorelease/CI and do the release manu ``` * Build the packages ```bash - python setupsrc/pypdfium2_setup/update_pdfium.py - python setupsrc/pypdfium2_setup/craft_packages.py pypi + python setupsrc/pypdfium2_setup/update.py + python setupsrc/pypdfium2_setup/craft.py ``` * Upload to PyPI ```bash @@ -827,10 +853,11 @@ git push --delete origin $TAGNAME Faulty PyPI releases may be yanked using the web interface. -## Prominent Embedders +## Popular dependents -pypdfium2 is used by prominent embedders such as +pypdfium2 is used by popular packages such as [langchain](https://github.com/langchain-ai/langchain), +[docling](https://github.com/DS4SD/docling), [nougat](https://github.com/facebookresearch/nougat), [pdfplumber](https://github.com/jsvine/pdfplumber), and [doctr](https://github.com/mindee/doctr/). @@ -840,20 +867,18 @@ This results in pypdfium2 being part of a large dependency tree. ## Thanks to[^thanks_to] - - -* [Benoît Blanchon](https://github.com/bblanchon): Author of [PDFium binaries](https://github.com/bblanchon/pdfium-binaries/) and [patches](sourcebuild/patches/). -* [Anderson Bravalheri](https://github.com/abravalheri): Help with PEP 517/518 compliance. Hint to use an environment variable rather than separate setup files. -* [Bastian Germann](https://github.com/bgermann): Help with inclusion of licenses for third-party components of PDFium. -* [Tim Head](https://github.com/betatim): Original idea for Python bindings to PDFium with ctypesgen in `wowpng`. * [Yinlin Hu](https://github.com/YinlinHu): `pypdfium` prototype and `kuafu` PDF viewer. +* [Mike Kroutikov](https://github.com/mkroutikov): Examples on how to use PDFium in `redstork`, `redstork-ui` and `pdfbrain`. +* [Tim Head](https://github.com/betatim): Original idea for Python bindings to PDFium with ctypesgen in `wowpng`. +* [Benoît Blanchon](https://github.com/bblanchon): Author of [PDFium binaries](https://github.com/bblanchon/pdfium-binaries/) and [patches](sourcebuild/patches/). * [Adam Huganir](https://github.com/adam-huganir): Help with maintenance and development decisions since the beginning of the project. * [kobaltcore](https://github.com/kobaltcore): Bug fix for `PdfDocument.save()`. -* [Mike Kroutikov](https://github.com/mkroutikov): Examples on how to use PDFium with ctypes in `redstork` and `pdfbrain`. +* [Anderson Bravalheri](https://github.com/abravalheri): Help with PEP 517/518 compliance. Hint to use an environment variable rather than separate setup files. +* [Bastian Germann](https://github.com/bgermann): Help with inclusion of licenses for third-party components of PDFium. ... and further [code contributors](https://github.com/pypdfium2-team/pypdfium2/graphs/contributors) (GitHub stats). -*If you have somehow contributed to this project but we forgot to mention you here, please let us know.* +*If you have contributed to this project but are not mentioned here yet, please let us know.* [^thanks_to]: People listed in this section may not necessarily have contributed any copyrightable code to the repository. Some have rather helped with ideas, or contributions to dependencies of pypdfium2. @@ -873,7 +898,7 @@ Inspired by *wowpng*, the first known proof of concept Python binding to PDFium *pypdfium-reboot* then added a script to automate binary deployment and bindings generation to simplify regular updates. However, it was still not platform specific. pypdfium2 is a full rewrite of *pypdfium-reboot* to build platform-specific wheels and consolidate the setup scripts. Further additions include ... -* A CI workflow to automatically release new wheels every Tuesday -* Support models that conveniently wrap the raw PDFium/ctypes API +* A CI workflow to automatically release new wheels at a defined schedule +* Convenience support models that wrap the raw PDFium/ctypes API * Test code * A script to build PDFium from source diff --git a/REUSE-wheel.toml b/REUSE-wheel.toml new file mode 100644 index 000000000..2a87663c8 --- /dev/null +++ b/REUSE-wheel.toml @@ -0,0 +1,44 @@ +# This file contains copyright info for data files included in our wheel packages. +# See also REUSE.toml regarding copyright of data files in the project directory. + +# Note, this file has been auto-converted from .reuse/dep5-wheel by `reuse convert-dep5` + +version = 1 +SPDX-PackageName = "pypdfium2" +SPDX-PackageSupplier = "geisserml " +SPDX-PackageDownloadLocation = "https://github.com/pypdfium2-team/pypdfium2" + +[[annotations]] +path = [ + "pypdfium2_raw/bindings.py", + "pypdfium2_raw/version.json", + "pypdfium2/version.json", +] +precedence = "aggregate" +SPDX-FileCopyrightText = [ + "2025 ctypesgen developers", + "2025 geisserml " +] +SPDX-License-Identifier = "Apache-2.0 OR BSD-3-Clause" +SPDX-FileComment = ''' +Auto-generated by ctypesgen/pypdfium2. +pypdfium2/version.json applies only if the helpers module is included. +''' + +[[annotations]] +path = [ + "pypdfium2_raw/libpdfium.so", + "pypdfium2_raw/libpdfium.dylib", + "pypdfium2_raw/pdfium.dll" +] +precedence = "aggregate" +SPDX-FileCopyrightText = [ + "2025 PDFium developers", + "2025 Developers of projects mentioned in PdfiumThirdParty", + "2025 Benoît Blanchon and pdfium-binaries contributors" +] +SPDX-License-Identifier = "BSD-3-Clause AND Apache-2.0 AND LicenseRef-PdfiumThirdParty" +SPDX-FileComment = ''' +pdfium's LICENSE file includes both BSD-3-Clause and Apache-2.0 texts +We're not sure if this meant as SPDX "AND" or "OR", so use the conservative (safe) assumption "AND". +''' diff --git a/REUSE.toml b/REUSE.toml new file mode 100644 index 000000000..1433a2193 --- /dev/null +++ b/REUSE.toml @@ -0,0 +1,131 @@ +# This file contains copyright info for data files in the project directory. +# It is shipped with our sdist packages, although not all members are actually included. +# See also REUSE-wheel.toml regarding copyright of data files in our wheel packages. + +# Note, this file has been auto-converted from .reuse/dep5 by `reuse convert-dep5` + +version = 1 +SPDX-PackageName = "pypdfium2" +SPDX-PackageSupplier = "geisserml " +SPDX-PackageDownloadLocation = "https://github.com/pypdfium2-team/pypdfium2" + +[[annotations]] +path = [ + "req/**.txt", + "autorelease/record.json", + "autorelease/config.json", + ".github/PULL_REQUEST_TEMPLATE.md", + "tests/expectations/**", + "tests/resources/render.pdf", + "tests/resources/multipage.pdf", + "tests/resources/encrypted.pdf", + "tests/resources/text.pdf", + "tests/resources/empty.pdf" +] +precedence = "aggregate" +SPDX-FileCopyrightText = "2025 geisserml " +SPDX-License-Identifier = "CC-BY-4.0" + +[[annotations]] +path = "autorelease/bindings.py" +precedence = "aggregate" +SPDX-FileCopyrightText = [ + "2025 ctypesgen developers", + "2025 geisserml " +] +SPDX-License-Identifier = "CC-BY-4.0" +SPDX-FileComment = "Auto-generated by ctypesgen. Probably not copyrighted." + +[[annotations]] +path = "tests/resources/toc.pdf" +precedence = "aggregate" +SPDX-FileCopyrightText = "2020 Matthias Erll" +SPDX-License-Identifier = "LicenseRef-FairUse" +SPDX-FileComment = ''' +Obtained from: https://github.com/pikepdf/pikepdf/blob/master/tests/resources/outlines.pdf +No individual license stated for this data file. Project license is MPL-2.0. +(https://github.com/pikepdf/pikepdf/blob/2662d9da649a3b354422673fb62bcfe0a1b9ba4c/REUSE.toml#L154 says "License assumed from LICENSE.txt in project root.") +''' + +[[annotations]] +path = [ + "tests/resources/toc_circular.pdf", + "tests/resources/toc_viewmodes.pdf", + "tests/resources/toc_maxdepth.pdf", + "tests/resources/forms.pdf", + "tests/resources/attachments.pdf", + "tests/resources/mona_lisa.jpg" +] +precedence = "aggregate" +SPDX-FileCopyrightText = "2022 PDFium developers" +SPDX-License-Identifier = "BSD-3-Clause AND Apache-2.0" +SPDX-FileComment = ''' +pdfium's LICENSE file includes both BSD-3-Clause and Apache-2.0 texts +We're not sure if this meant as SPDX "AND" or "OR", so use the conservative (safe) assumption "AND". +Obtained from: +https://pdfium.googlesource.com/pdfium/+/refs/heads/main/testing/resources/bookmarks_circular.pdf +https://pdfium.googlesource.com/pdfium_tests/+/refs/heads/main/fx/other/8.2_outline.pdf +https://pdfium.googlesource.com/pdfium_tests/+/refs/heads/main/fx/FRC_8.2.2_part1/FRC_51_8.2.2_T_8.4__Count_edit_count_100.pdf +https://pdfium.googlesource.com/pdfium/+/refs/heads/main/testing/resources/listbox_form.pdf +https://pdfium.googlesource.com/pdfium/+/refs/heads/main/testing/resources/embedded_attachments.pdf +https://pdfium.googlesource.com/pdfium/+/refs/heads/main/testing/resources/mona_lisa.jpg +''' + +[[annotations]] +path = [ + "tests/resources/box_fallback.in", + "tests/resources/box_fallback.pdf" +] +precedence = "aggregate" +SPDX-FileCopyrightText = [ + "2022 PDFium developers", + "2025 geisserml " +] +SPDX-License-Identifier = "BSD-3-Clause AND Apache-2.0" +SPDX-FileComment = "See above regarding pdfium's double-license." + +[[annotations]] +path = "tests/resources/images.pdf" +precedence = "aggregate" +SPDX-FileCopyrightText = [ + "2022 Johannes Schauer Marin Rodrigues ", + "2025 geisserml " +] +SPDX-License-Identifier = "LicenseRef-FairUse" + +[[annotations]] +path = [ + "sourcebuild/patches/public_headers.patch", + "sourcebuild/patches/shared_library.patch", + "sourcebuild/patches/win/build.patch", + "sourcebuild/patches/win/pdfium.patch", + "sourcebuild/patches/win/resources.rc" +] +precedence = "aggregate" +SPDX-FileCopyrightText = [ + "2025 PDFium developers", + "2025 Benoît Blanchon and pdfium-binaries contributors" +] +SPDX-License-Identifier = "LicenseRef-FairUse" +SPDX-FileComment = ''' +Obtained from https://github.com/bblanchon/pdfium-binaries/tree/master/patches +For reuse, see https://github.com/bblanchon/pdfium-binaries/issues/55 +Unchanged/deleted code is owned by PDFium, added code by pdfium-binaries. +''' + +[[annotations]] +path = "REUSE-wheel.toml" +precedence = "aggregate" +SPDX-FileCopyrightText = "2025 geisserml" +SPDX-License-Identifier = "LicenseRef-PdfiumThirdParty" +SPDX-FileComment = ''' +Pointer to prevent reuse lint from reporting LicenseRef-PdfiumThirdParty as unused. +This file is not actually copyrighted, but it mentions the data files that are. +''' + +[[annotations]] +path = ["RELEASE.md", "docs/build/.gitkeep"] +precedence = "aggregate" +SPDX-FileCopyrightText = "2025 geisserml" +SPDX-License-Identifier = "CC-BY-4.0" +SPDX-FileComment = "Other files. Listed here to make reuse lint happy on CI." diff --git a/autorelease/bindings.py b/autorelease/bindings.py index d5279ab81..45aa6297c 100644 --- a/autorelease/bindings.py +++ b/autorelease/bindings.py @@ -1,6 +1,6 @@ R""" Auto-generated by: -ctypesgen -l pdfium --runtime-libdirs . --no-load-library --no-macro-guards -D PDF_ENABLE_V8 PDF_ENABLE_XFA PDF_USE_SKIA --symbol-rules 'if_needed=\w+_$|\w+_t$|_\w+' --headers fpdf_annot.h fpdf_attachment.h fpdf_catalog.h fpdf_dataavail.h fpdf_doc.h fpdf_edit.h fpdf_ext.h fpdf_flatten.h fpdf_formfill.h fpdf_fwlevent.h fpdf_javascript.h fpdf_ppo.h fpdf_progressive.h fpdf_save.h fpdf_searchex.h fpdf_signature.h fpdf_structtree.h fpdf_sysfontinfo.h fpdf_text.h fpdf_thumbnail.h fpdf_transformpage.h fpdfview.h -o '~/work/pypdfium2/pypdfium2/data/bindings/bindings.py' +ctypesgen -l pdfium --runtime-libdirs . --no-load-library --no-macro-guards --no-srcinfo -D PDF_ENABLE_V8 PDF_ENABLE_XFA PDF_USE_SKIA --symbol-rules 'if_needed=\w+_$|\w+_t$|_\w+' --headers fpdf_annot.h fpdf_attachment.h fpdf_catalog.h fpdf_dataavail.h fpdf_doc.h fpdf_edit.h fpdf_ext.h fpdf_flatten.h fpdf_formfill.h fpdf_fwlevent.h fpdf_javascript.h fpdf_ppo.h fpdf_progressive.h fpdf_save.h fpdf_searchex.h fpdf_signature.h fpdf_structtree.h fpdf_sysfontinfo.h fpdf_text.h fpdf_thumbnail.h fpdf_transformpage.h fpdfview.h -o ../bindings.py """ import ctypes @@ -60,288 +60,203 @@ def _register_library(name, dllclass, **kwargs): # -- Begin header members -- -# ./fpdfview.h: 59 enum_anon_2 = c_int -# ./fpdfview.h: 59 FPDF_TEXTRENDERMODE_UNKNOWN = (-1) -# ./fpdfview.h: 59 FPDF_TEXTRENDERMODE_FILL = 0 -# ./fpdfview.h: 59 FPDF_TEXTRENDERMODE_STROKE = 1 -# ./fpdfview.h: 59 FPDF_TEXTRENDERMODE_FILL_STROKE = 2 -# ./fpdfview.h: 59 FPDF_TEXTRENDERMODE_INVISIBLE = 3 -# ./fpdfview.h: 59 FPDF_TEXTRENDERMODE_FILL_CLIP = 4 -# ./fpdfview.h: 59 FPDF_TEXTRENDERMODE_STROKE_CLIP = 5 -# ./fpdfview.h: 59 FPDF_TEXTRENDERMODE_FILL_STROKE_CLIP = 6 -# ./fpdfview.h: 59 FPDF_TEXTRENDERMODE_CLIP = 7 -# ./fpdfview.h: 59 FPDF_TEXTRENDERMODE_LAST = FPDF_TEXTRENDERMODE_CLIP -# ./fpdfview.h: 59 FPDF_TEXT_RENDERMODE = enum_anon_2 -# ./fpdfview.h: 62 class struct_fpdf_action_t__ (Structure): pass -# ./fpdfview.h: 62 FPDF_ACTION = POINTER(struct_fpdf_action_t__) -# ./fpdfview.h: 63 class struct_fpdf_annotation_t__ (Structure): pass -# ./fpdfview.h: 63 FPDF_ANNOTATION = POINTER(struct_fpdf_annotation_t__) -# ./fpdfview.h: 64 class struct_fpdf_attachment_t__ (Structure): pass -# ./fpdfview.h: 64 FPDF_ATTACHMENT = POINTER(struct_fpdf_attachment_t__) -# ./fpdfview.h: 65 class struct_fpdf_avail_t__ (Structure): pass -# ./fpdfview.h: 65 FPDF_AVAIL = POINTER(struct_fpdf_avail_t__) -# ./fpdfview.h: 66 class struct_fpdf_bitmap_t__ (Structure): pass -# ./fpdfview.h: 66 FPDF_BITMAP = POINTER(struct_fpdf_bitmap_t__) -# ./fpdfview.h: 67 class struct_fpdf_bookmark_t__ (Structure): pass -# ./fpdfview.h: 67 FPDF_BOOKMARK = POINTER(struct_fpdf_bookmark_t__) -# ./fpdfview.h: 68 class struct_fpdf_clippath_t__ (Structure): pass -# ./fpdfview.h: 68 FPDF_CLIPPATH = POINTER(struct_fpdf_clippath_t__) -# ./fpdfview.h: 69 class struct_fpdf_dest_t__ (Structure): pass -# ./fpdfview.h: 69 FPDF_DEST = POINTER(struct_fpdf_dest_t__) -# ./fpdfview.h: 70 class struct_fpdf_document_t__ (Structure): pass -# ./fpdfview.h: 70 FPDF_DOCUMENT = POINTER(struct_fpdf_document_t__) -# ./fpdfview.h: 71 class struct_fpdf_font_t__ (Structure): pass -# ./fpdfview.h: 71 FPDF_FONT = POINTER(struct_fpdf_font_t__) -# ./fpdfview.h: 72 class struct_fpdf_form_handle_t__ (Structure): pass -# ./fpdfview.h: 72 FPDF_FORMHANDLE = POINTER(struct_fpdf_form_handle_t__) -# ./fpdfview.h: 73 class struct_fpdf_glyphpath_t__ (Structure): pass -# ./fpdfview.h: 73 FPDF_GLYPHPATH = POINTER(struct_fpdf_glyphpath_t__) -# ./fpdfview.h: 74 class struct_fpdf_javascript_action_t (Structure): pass -# ./fpdfview.h: 74 FPDF_JAVASCRIPT_ACTION = POINTER(struct_fpdf_javascript_action_t) -# ./fpdfview.h: 75 class struct_fpdf_link_t__ (Structure): pass -# ./fpdfview.h: 75 FPDF_LINK = POINTER(struct_fpdf_link_t__) -# ./fpdfview.h: 76 class struct_fpdf_page_t__ (Structure): pass -# ./fpdfview.h: 76 FPDF_PAGE = POINTER(struct_fpdf_page_t__) -# ./fpdfview.h: 77 class struct_fpdf_pagelink_t__ (Structure): pass -# ./fpdfview.h: 77 FPDF_PAGELINK = POINTER(struct_fpdf_pagelink_t__) -# ./fpdfview.h: 78 class struct_fpdf_pageobject_t__ (Structure): pass -# ./fpdfview.h: 78 FPDF_PAGEOBJECT = POINTER(struct_fpdf_pageobject_t__) -# ./fpdfview.h: 79 class struct_fpdf_pageobjectmark_t__ (Structure): pass -# ./fpdfview.h: 79 FPDF_PAGEOBJECTMARK = POINTER(struct_fpdf_pageobjectmark_t__) -# ./fpdfview.h: 80 class struct_fpdf_pagerange_t__ (Structure): pass -# ./fpdfview.h: 80 FPDF_PAGERANGE = POINTER(struct_fpdf_pagerange_t__) -# ./fpdfview.h: 81 class struct_fpdf_pathsegment_t (Structure): pass -# ./fpdfview.h: 81 FPDF_PATHSEGMENT = POINTER(struct_fpdf_pathsegment_t) -# ./fpdfview.h: 82 class struct_fpdf_schhandle_t__ (Structure): pass -# ./fpdfview.h: 82 FPDF_SCHHANDLE = POINTER(struct_fpdf_schhandle_t__) -# ./fpdfview.h: 83 class struct_fpdf_signature_t__ (Structure): pass -# ./fpdfview.h: 83 FPDF_SIGNATURE = POINTER(struct_fpdf_signature_t__) -# ./fpdfview.h: 84 FPDF_SKIA_CANVAS = POINTER(None) -# ./fpdfview.h: 85 class struct_fpdf_structelement_t__ (Structure): pass -# ./fpdfview.h: 85 FPDF_STRUCTELEMENT = POINTER(struct_fpdf_structelement_t__) -# ./fpdfview.h: 86 class struct_fpdf_structelement_attr_t__ (Structure): pass -# ./fpdfview.h: 86 FPDF_STRUCTELEMENT_ATTR = POINTER(struct_fpdf_structelement_attr_t__) -# ./fpdfview.h: 87 class struct_fpdf_structelement_attr_value_t__ (Structure): pass -# ./fpdfview.h: 87 FPDF_STRUCTELEMENT_ATTR_VALUE = POINTER(struct_fpdf_structelement_attr_value_t__) -# ./fpdfview.h: 89 class struct_fpdf_structtree_t__ (Structure): pass -# ./fpdfview.h: 89 FPDF_STRUCTTREE = POINTER(struct_fpdf_structtree_t__) -# ./fpdfview.h: 90 class struct_fpdf_textpage_t__ (Structure): pass -# ./fpdfview.h: 90 FPDF_TEXTPAGE = POINTER(struct_fpdf_textpage_t__) -# ./fpdfview.h: 91 class struct_fpdf_widget_t__ (Structure): pass -# ./fpdfview.h: 91 FPDF_WIDGET = POINTER(struct_fpdf_widget_t__) -# ./fpdfview.h: 92 class struct_fpdf_xobject_t__ (Structure): pass -# ./fpdfview.h: 92 FPDF_XOBJECT = POINTER(struct_fpdf_xobject_t__) -# ./fpdfview.h: 95 FPDF_BOOL = c_int -# ./fpdfview.h: 96 FPDF_RESULT = c_int -# ./fpdfview.h: 97 FPDF_DWORD = c_ulong -# ./fpdfview.h: 98 FS_FLOAT = c_float -# ./fpdfview.h: 106 enum__FPDF_DUPLEXTYPE_ = c_int -# ./fpdfview.h: 106 DuplexUndefined = 0 -# ./fpdfview.h: 106 Simplex = (DuplexUndefined + 1) -# ./fpdfview.h: 106 DuplexFlipShortEdge = (Simplex + 1) -# ./fpdfview.h: 106 DuplexFlipLongEdge = (DuplexFlipShortEdge + 1) -# ./fpdfview.h: 106 FPDF_DUPLEXTYPE = enum__FPDF_DUPLEXTYPE_ -# ./fpdfview.h: 109 FPDF_WCHAR = c_ushort -# ./fpdfview.h: 115 FPDF_BYTESTRING = POINTER(c_char) -# ./fpdfview.h: 119 FPDF_WIDESTRING = POINTER(FPDF_WCHAR) -# ./fpdfview.h: 127 class struct_FPDF_BSTR_ (Structure): __slots__ = ['str', 'len'] @@ -350,13 +265,10 @@ class struct_FPDF_BSTR_ (Structure): ('len', c_int), ] -# ./fpdfview.h: 127 FPDF_BSTR = struct_FPDF_BSTR_ -# ./fpdfview.h: 136 FPDF_STRING = POINTER(c_char) -# ./fpdfview.h: 153 class struct__FS_MATRIX_ (Structure): __slots__ = ['a', 'b', 'c', 'd', 'e', 'f'] @@ -369,10 +281,8 @@ class struct__FS_MATRIX_ (Structure): ('f', c_float), ] -# ./fpdfview.h: 153 FS_MATRIX = struct__FS_MATRIX_ -# ./fpdfview.h: 156 class struct__FS_RECTF_ (Structure): __slots__ = ['left', 'top', 'right', 'bottom'] @@ -383,16 +293,12 @@ class struct__FS_RECTF_ (Structure): ('bottom', c_float), ] -# ./fpdfview.h: 165 FS_LPRECTF = POINTER(struct__FS_RECTF_) -# ./fpdfview.h: 165 FS_RECTF = struct__FS_RECTF_ -# ./fpdfview.h: 168 FS_LPCRECTF = POINTER(FS_RECTF) -# ./fpdfview.h: 171 class struct_FS_SIZEF_ (Structure): __slots__ = ['width', 'height'] @@ -401,16 +307,12 @@ class struct_FS_SIZEF_ (Structure): ('height', c_float), ] -# ./fpdfview.h: 174 FS_LPSIZEF = POINTER(struct_FS_SIZEF_) -# ./fpdfview.h: 174 FS_SIZEF = struct_FS_SIZEF_ -# ./fpdfview.h: 177 FS_LPCSIZEF = POINTER(FS_SIZEF) -# ./fpdfview.h: 180 class struct_FS_POINTF_ (Structure): __slots__ = ['x', 'y'] @@ -419,16 +321,12 @@ class struct_FS_POINTF_ (Structure): ('y', c_float), ] -# ./fpdfview.h: 183 FS_LPPOINTF = POINTER(struct_FS_POINTF_) -# ./fpdfview.h: 183 FS_POINTF = struct_FS_POINTF_ -# ./fpdfview.h: 186 FS_LPCPOINTF = POINTER(FS_POINTF) -# ./fpdfview.h: 197 class struct__FS_QUADPOINTSF (Structure): __slots__ = ['x1', 'y1', 'x2', 'y2', 'x3', 'y3', 'x4', 'y4'] @@ -443,31 +341,22 @@ class struct__FS_QUADPOINTSF (Structure): ('y4', FS_FLOAT), ] -# ./fpdfview.h: 197 FS_QUADPOINTSF = struct__FS_QUADPOINTSF -# ./fpdfview.h: 200 FPDF_ANNOTATION_SUBTYPE = c_int -# ./fpdfview.h: 201 FPDF_ANNOT_APPEARANCEMODE = c_int -# ./fpdfview.h: 204 FPDF_OBJECT_TYPE = c_int -# ./fpdfview.h: 244 enum_anon_3 = c_int -# ./fpdfview.h: 244 FPDF_RENDERERTYPE_AGG = 0 -# ./fpdfview.h: 244 FPDF_RENDERERTYPE_SKIA = 1 -# ./fpdfview.h: 244 FPDF_RENDERER_TYPE = enum_anon_3 -# ./fpdfview.h: 283 class struct_FPDF_LIBRARY_CONFIG_ (Structure): __slots__ = ['version', 'm_pUserFontPaths', 'm_pIsolate', 'm_v8EmbedderSlot', 'm_pPlatform', 'm_RendererType'] @@ -480,52 +369,43 @@ class struct_FPDF_LIBRARY_CONFIG_ (Structure): ('m_RendererType', FPDF_RENDERER_TYPE), ] -# ./fpdfview.h: 283 FPDF_LIBRARY_CONFIG = struct_FPDF_LIBRARY_CONFIG_ -# ./fpdfview.h: 295 if hasattr(_libs['pdfium'], 'FPDF_InitLibraryWithConfig'): FPDF_InitLibraryWithConfig = _libs['pdfium']['FPDF_InitLibraryWithConfig'] FPDF_InitLibraryWithConfig.argtypes = [POINTER(FPDF_LIBRARY_CONFIG)] FPDF_InitLibraryWithConfig.restype = None -# ./fpdfview.h: 308 if hasattr(_libs['pdfium'], 'FPDF_InitLibrary'): FPDF_InitLibrary = _libs['pdfium']['FPDF_InitLibrary'] FPDF_InitLibrary.argtypes = [] FPDF_InitLibrary.restype = None -# ./fpdfview.h: 324 if hasattr(_libs['pdfium'], 'FPDF_DestroyLibrary'): FPDF_DestroyLibrary = _libs['pdfium']['FPDF_DestroyLibrary'] FPDF_DestroyLibrary.argtypes = [] FPDF_DestroyLibrary.restype = None -# ./fpdfview.h: 337 if hasattr(_libs['pdfium'], 'FPDF_SetSandBoxPolicy'): FPDF_SetSandBoxPolicy = _libs['pdfium']['FPDF_SetSandBoxPolicy'] FPDF_SetSandBoxPolicy.argtypes = [FPDF_DWORD, FPDF_BOOL] FPDF_SetSandBoxPolicy.restype = None -# ./fpdfview.h: 391 if hasattr(_libs['pdfium'], 'FPDF_LoadDocument'): FPDF_LoadDocument = _libs['pdfium']['FPDF_LoadDocument'] FPDF_LoadDocument.argtypes = [FPDF_STRING, FPDF_BYTESTRING] FPDF_LoadDocument.restype = FPDF_DOCUMENT -# ./fpdfview.h: 415 if hasattr(_libs['pdfium'], 'FPDF_LoadMemDocument'): FPDF_LoadMemDocument = _libs['pdfium']['FPDF_LoadMemDocument'] FPDF_LoadMemDocument.argtypes = [POINTER(None), c_int, FPDF_BYTESTRING] FPDF_LoadMemDocument.restype = FPDF_DOCUMENT -# ./fpdfview.h: 440 if hasattr(_libs['pdfium'], 'FPDF_LoadMemDocument64'): FPDF_LoadMemDocument64 = _libs['pdfium']['FPDF_LoadMemDocument64'] FPDF_LoadMemDocument64.argtypes = [POINTER(None), c_size_t, FPDF_BYTESTRING] FPDF_LoadMemDocument64.restype = FPDF_DOCUMENT -# ./fpdfview.h: 464 class struct_anon_4 (Structure): __slots__ = ['m_FileLen', 'm_GetBlock', 'm_Param'] @@ -535,10 +415,8 @@ class struct_anon_4 (Structure): ('m_Param', POINTER(None)), ] -# ./fpdfview.h: 464 FPDF_FILEACCESS = struct_anon_4 -# ./fpdfview.h: 544 class struct_FPDF_FILEHANDLER_ (Structure): __slots__ = ['clientData', 'Release', 'GetSize', 'ReadBlock', 'WriteBlock', 'Flush', 'Truncate'] @@ -552,112 +430,93 @@ class struct_FPDF_FILEHANDLER_ (Structure): ('Truncate', CFUNCTYPE(FPDF_RESULT, POINTER(None), FPDF_DWORD)), ] -# ./fpdfview.h: 544 FPDF_FILEHANDLER = struct_FPDF_FILEHANDLER_ -# ./fpdfview.h: 567 if hasattr(_libs['pdfium'], 'FPDF_LoadCustomDocument'): FPDF_LoadCustomDocument = _libs['pdfium']['FPDF_LoadCustomDocument'] FPDF_LoadCustomDocument.argtypes = [POINTER(FPDF_FILEACCESS), FPDF_BYTESTRING] FPDF_LoadCustomDocument.restype = FPDF_DOCUMENT -# ./fpdfview.h: 580 if hasattr(_libs['pdfium'], 'FPDF_GetFileVersion'): FPDF_GetFileVersion = _libs['pdfium']['FPDF_GetFileVersion'] FPDF_GetFileVersion.argtypes = [FPDF_DOCUMENT, POINTER(c_int)] FPDF_GetFileVersion.restype = FPDF_BOOL -# ./fpdfview.h: 605 if hasattr(_libs['pdfium'], 'FPDF_GetLastError'): FPDF_GetLastError = _libs['pdfium']['FPDF_GetLastError'] FPDF_GetLastError.argtypes = [] FPDF_GetLastError.restype = c_ulong -# ./fpdfview.h: 620 if hasattr(_libs['pdfium'], 'FPDF_DocumentHasValidCrossReferenceTable'): FPDF_DocumentHasValidCrossReferenceTable = _libs['pdfium']['FPDF_DocumentHasValidCrossReferenceTable'] FPDF_DocumentHasValidCrossReferenceTable.argtypes = [FPDF_DOCUMENT] FPDF_DocumentHasValidCrossReferenceTable.restype = FPDF_BOOL -# ./fpdfview.h: 637 if hasattr(_libs['pdfium'], 'FPDF_GetTrailerEnds'): FPDF_GetTrailerEnds = _libs['pdfium']['FPDF_GetTrailerEnds'] FPDF_GetTrailerEnds.argtypes = [FPDF_DOCUMENT, POINTER(c_uint), c_ulong] FPDF_GetTrailerEnds.restype = c_ulong -# ./fpdfview.h: 650 if hasattr(_libs['pdfium'], 'FPDF_GetDocPermissions'): FPDF_GetDocPermissions = _libs['pdfium']['FPDF_GetDocPermissions'] FPDF_GetDocPermissions.argtypes = [FPDF_DOCUMENT] FPDF_GetDocPermissions.restype = c_ulong -# ./fpdfview.h: 662 if hasattr(_libs['pdfium'], 'FPDF_GetDocUserPermissions'): FPDF_GetDocUserPermissions = _libs['pdfium']['FPDF_GetDocUserPermissions'] FPDF_GetDocUserPermissions.argtypes = [FPDF_DOCUMENT] FPDF_GetDocUserPermissions.restype = c_ulong -# ./fpdfview.h: 673 if hasattr(_libs['pdfium'], 'FPDF_GetSecurityHandlerRevision'): FPDF_GetSecurityHandlerRevision = _libs['pdfium']['FPDF_GetSecurityHandlerRevision'] FPDF_GetSecurityHandlerRevision.argtypes = [FPDF_DOCUMENT] FPDF_GetSecurityHandlerRevision.restype = c_int -# ./fpdfview.h: 681 if hasattr(_libs['pdfium'], 'FPDF_GetPageCount'): FPDF_GetPageCount = _libs['pdfium']['FPDF_GetPageCount'] FPDF_GetPageCount.argtypes = [FPDF_DOCUMENT] FPDF_GetPageCount.restype = c_int -# ./fpdfview.h: 693 if hasattr(_libs['pdfium'], 'FPDF_LoadPage'): FPDF_LoadPage = _libs['pdfium']['FPDF_LoadPage'] FPDF_LoadPage.argtypes = [FPDF_DOCUMENT, c_int] FPDF_LoadPage.restype = FPDF_PAGE -# ./fpdfview.h: 704 if hasattr(_libs['pdfium'], 'FPDF_GetPageWidthF'): FPDF_GetPageWidthF = _libs['pdfium']['FPDF_GetPageWidthF'] FPDF_GetPageWidthF.argtypes = [FPDF_PAGE] FPDF_GetPageWidthF.restype = c_float -# ./fpdfview.h: 716 if hasattr(_libs['pdfium'], 'FPDF_GetPageWidth'): FPDF_GetPageWidth = _libs['pdfium']['FPDF_GetPageWidth'] FPDF_GetPageWidth.argtypes = [FPDF_PAGE] FPDF_GetPageWidth.restype = c_double -# ./fpdfview.h: 726 if hasattr(_libs['pdfium'], 'FPDF_GetPageHeightF'): FPDF_GetPageHeightF = _libs['pdfium']['FPDF_GetPageHeightF'] FPDF_GetPageHeightF.argtypes = [FPDF_PAGE] FPDF_GetPageHeightF.restype = c_float -# ./fpdfview.h: 738 if hasattr(_libs['pdfium'], 'FPDF_GetPageHeight'): FPDF_GetPageHeight = _libs['pdfium']['FPDF_GetPageHeight'] FPDF_GetPageHeight.argtypes = [FPDF_PAGE] FPDF_GetPageHeight.restype = c_double -# ./fpdfview.h: 750 if hasattr(_libs['pdfium'], 'FPDF_GetPageBoundingBox'): FPDF_GetPageBoundingBox = _libs['pdfium']['FPDF_GetPageBoundingBox'] FPDF_GetPageBoundingBox.argtypes = [FPDF_PAGE, POINTER(FS_RECTF)] FPDF_GetPageBoundingBox.restype = FPDF_BOOL -# ./fpdfview.h: 764 if hasattr(_libs['pdfium'], 'FPDF_GetPageSizeByIndexF'): FPDF_GetPageSizeByIndexF = _libs['pdfium']['FPDF_GetPageSizeByIndexF'] FPDF_GetPageSizeByIndexF.argtypes = [FPDF_DOCUMENT, c_int, POINTER(FS_SIZEF)] FPDF_GetPageSizeByIndexF.restype = FPDF_BOOL -# ./fpdfview.h: 782 if hasattr(_libs['pdfium'], 'FPDF_GetPageSizeByIndex'): FPDF_GetPageSizeByIndex = _libs['pdfium']['FPDF_GetPageSizeByIndex'] FPDF_GetPageSizeByIndex.argtypes = [FPDF_DOCUMENT, c_int, POINTER(c_double), POINTER(c_double)] FPDF_GetPageSizeByIndex.restype = c_int -# ./fpdfview.h: 830 class struct_FPDF_COLORSCHEME_ (Structure): __slots__ = ['path_fill_color', 'path_stroke_color', 'text_fill_color', 'text_stroke_color'] @@ -668,214 +527,178 @@ class struct_FPDF_COLORSCHEME_ (Structure): ('text_stroke_color', FPDF_DWORD), ] -# ./fpdfview.h: 830 FPDF_COLORSCHEME = struct_FPDF_COLORSCHEME_ -# ./fpdfview.h: 891 if hasattr(_libs['pdfium'], 'FPDF_RenderPageBitmap'): FPDF_RenderPageBitmap = _libs['pdfium']['FPDF_RenderPageBitmap'] FPDF_RenderPageBitmap.argtypes = [FPDF_BITMAP, FPDF_PAGE, c_int, c_int, c_int, c_int, c_int, c_int] FPDF_RenderPageBitmap.restype = None -# ./fpdfview.h: 919 if hasattr(_libs['pdfium'], 'FPDF_RenderPageBitmapWithMatrix'): FPDF_RenderPageBitmapWithMatrix = _libs['pdfium']['FPDF_RenderPageBitmapWithMatrix'] FPDF_RenderPageBitmapWithMatrix.argtypes = [FPDF_BITMAP, FPDF_PAGE, POINTER(FS_MATRIX), POINTER(FS_RECTF), c_int] FPDF_RenderPageBitmapWithMatrix.restype = None -# ./fpdfview.h: 936 if hasattr(_libs['pdfium'], 'FPDF_RenderPageSkia'): FPDF_RenderPageSkia = _libs['pdfium']['FPDF_RenderPageSkia'] FPDF_RenderPageSkia.argtypes = [FPDF_SKIA_CANVAS, FPDF_PAGE, c_int, c_int] FPDF_RenderPageSkia.restype = None -# ./fpdfview.h: 948 if hasattr(_libs['pdfium'], 'FPDF_ClosePage'): FPDF_ClosePage = _libs['pdfium']['FPDF_ClosePage'] FPDF_ClosePage.argtypes = [FPDF_PAGE] FPDF_ClosePage.restype = None -# ./fpdfview.h: 956 if hasattr(_libs['pdfium'], 'FPDF_CloseDocument'): FPDF_CloseDocument = _libs['pdfium']['FPDF_CloseDocument'] FPDF_CloseDocument.argtypes = [FPDF_DOCUMENT] FPDF_CloseDocument.restype = None -# ./fpdfview.h: 999 if hasattr(_libs['pdfium'], 'FPDF_DeviceToPage'): FPDF_DeviceToPage = _libs['pdfium']['FPDF_DeviceToPage'] FPDF_DeviceToPage.argtypes = [FPDF_PAGE, c_int, c_int, c_int, c_int, c_int, c_int, c_int, POINTER(c_double), POINTER(c_double)] FPDF_DeviceToPage.restype = FPDF_BOOL -# ./fpdfview.h: 1036 if hasattr(_libs['pdfium'], 'FPDF_PageToDevice'): FPDF_PageToDevice = _libs['pdfium']['FPDF_PageToDevice'] FPDF_PageToDevice.argtypes = [FPDF_PAGE, c_int, c_int, c_int, c_int, c_int, c_double, c_double, POINTER(c_int), POINTER(c_int)] FPDF_PageToDevice.restype = FPDF_BOOL -# ./fpdfview.h: 1077 if hasattr(_libs['pdfium'], 'FPDFBitmap_Create'): FPDFBitmap_Create = _libs['pdfium']['FPDFBitmap_Create'] FPDFBitmap_Create.argtypes = [c_int, c_int, c_int] FPDFBitmap_Create.restype = FPDF_BITMAP -# ./fpdfview.h: 1126 if hasattr(_libs['pdfium'], 'FPDFBitmap_CreateEx'): FPDFBitmap_CreateEx = _libs['pdfium']['FPDFBitmap_CreateEx'] FPDFBitmap_CreateEx.argtypes = [c_int, c_int, c_int, POINTER(None), c_int] FPDFBitmap_CreateEx.restype = FPDF_BITMAP -# ./fpdfview.h: 1142 if hasattr(_libs['pdfium'], 'FPDFBitmap_GetFormat'): FPDFBitmap_GetFormat = _libs['pdfium']['FPDFBitmap_GetFormat'] FPDFBitmap_GetFormat.argtypes = [FPDF_BITMAP] FPDFBitmap_GetFormat.restype = c_int -# ./fpdfview.h: 1168 if hasattr(_libs['pdfium'], 'FPDFBitmap_FillRect'): FPDFBitmap_FillRect = _libs['pdfium']['FPDFBitmap_FillRect'] FPDFBitmap_FillRect.argtypes = [FPDF_BITMAP, c_int, c_int, c_int, c_int, FPDF_DWORD] FPDFBitmap_FillRect.restype = FPDF_BOOL -# ./fpdfview.h: 1190 if hasattr(_libs['pdfium'], 'FPDFBitmap_GetBuffer'): FPDFBitmap_GetBuffer = _libs['pdfium']['FPDFBitmap_GetBuffer'] FPDFBitmap_GetBuffer.argtypes = [FPDF_BITMAP] FPDFBitmap_GetBuffer.restype = POINTER(None) -# ./fpdfview.h: 1199 if hasattr(_libs['pdfium'], 'FPDFBitmap_GetWidth'): FPDFBitmap_GetWidth = _libs['pdfium']['FPDFBitmap_GetWidth'] FPDFBitmap_GetWidth.argtypes = [FPDF_BITMAP] FPDFBitmap_GetWidth.restype = c_int -# ./fpdfview.h: 1208 if hasattr(_libs['pdfium'], 'FPDFBitmap_GetHeight'): FPDFBitmap_GetHeight = _libs['pdfium']['FPDFBitmap_GetHeight'] FPDFBitmap_GetHeight.argtypes = [FPDF_BITMAP] FPDFBitmap_GetHeight.restype = c_int -# ./fpdfview.h: 1219 if hasattr(_libs['pdfium'], 'FPDFBitmap_GetStride'): FPDFBitmap_GetStride = _libs['pdfium']['FPDFBitmap_GetStride'] FPDFBitmap_GetStride.argtypes = [FPDF_BITMAP] FPDFBitmap_GetStride.restype = c_int -# ./fpdfview.h: 1231 if hasattr(_libs['pdfium'], 'FPDFBitmap_Destroy'): FPDFBitmap_Destroy = _libs['pdfium']['FPDFBitmap_Destroy'] FPDFBitmap_Destroy.argtypes = [FPDF_BITMAP] FPDFBitmap_Destroy.restype = None -# ./fpdfview.h: 1240 if hasattr(_libs['pdfium'], 'FPDF_VIEWERREF_GetPrintScaling'): FPDF_VIEWERREF_GetPrintScaling = _libs['pdfium']['FPDF_VIEWERREF_GetPrintScaling'] FPDF_VIEWERREF_GetPrintScaling.argtypes = [FPDF_DOCUMENT] FPDF_VIEWERREF_GetPrintScaling.restype = FPDF_BOOL -# ./fpdfview.h: 1249 if hasattr(_libs['pdfium'], 'FPDF_VIEWERREF_GetNumCopies'): FPDF_VIEWERREF_GetNumCopies = _libs['pdfium']['FPDF_VIEWERREF_GetNumCopies'] FPDF_VIEWERREF_GetNumCopies.argtypes = [FPDF_DOCUMENT] FPDF_VIEWERREF_GetNumCopies.restype = c_int -# ./fpdfview.h: 1258 if hasattr(_libs['pdfium'], 'FPDF_VIEWERREF_GetPrintPageRange'): FPDF_VIEWERREF_GetPrintPageRange = _libs['pdfium']['FPDF_VIEWERREF_GetPrintPageRange'] FPDF_VIEWERREF_GetPrintPageRange.argtypes = [FPDF_DOCUMENT] FPDF_VIEWERREF_GetPrintPageRange.restype = FPDF_PAGERANGE -# ./fpdfview.h: 1268 if hasattr(_libs['pdfium'], 'FPDF_VIEWERREF_GetPrintPageRangeCount'): FPDF_VIEWERREF_GetPrintPageRangeCount = _libs['pdfium']['FPDF_VIEWERREF_GetPrintPageRangeCount'] FPDF_VIEWERREF_GetPrintPageRangeCount.argtypes = [FPDF_PAGERANGE] FPDF_VIEWERREF_GetPrintPageRangeCount.restype = c_size_t -# ./fpdfview.h: 1280 if hasattr(_libs['pdfium'], 'FPDF_VIEWERREF_GetPrintPageRangeElement'): FPDF_VIEWERREF_GetPrintPageRangeElement = _libs['pdfium']['FPDF_VIEWERREF_GetPrintPageRangeElement'] FPDF_VIEWERREF_GetPrintPageRangeElement.argtypes = [FPDF_PAGERANGE, c_size_t] FPDF_VIEWERREF_GetPrintPageRangeElement.restype = c_int -# ./fpdfview.h: 1290 if hasattr(_libs['pdfium'], 'FPDF_VIEWERREF_GetDuplex'): FPDF_VIEWERREF_GetDuplex = _libs['pdfium']['FPDF_VIEWERREF_GetDuplex'] FPDF_VIEWERREF_GetDuplex.argtypes = [FPDF_DOCUMENT] FPDF_VIEWERREF_GetDuplex.restype = FPDF_DUPLEXTYPE -# ./fpdfview.h: 1308 if hasattr(_libs['pdfium'], 'FPDF_VIEWERREF_GetName'): FPDF_VIEWERREF_GetName = _libs['pdfium']['FPDF_VIEWERREF_GetName'] FPDF_VIEWERREF_GetName.argtypes = [FPDF_DOCUMENT, FPDF_BYTESTRING, POINTER(c_char), c_ulong] FPDF_VIEWERREF_GetName.restype = c_ulong -# ./fpdfview.h: 1320 if hasattr(_libs['pdfium'], 'FPDF_CountNamedDests'): FPDF_CountNamedDests = _libs['pdfium']['FPDF_CountNamedDests'] FPDF_CountNamedDests.argtypes = [FPDF_DOCUMENT] FPDF_CountNamedDests.restype = FPDF_DWORD -# ./fpdfview.h: 1330 if hasattr(_libs['pdfium'], 'FPDF_GetNamedDestByName'): FPDF_GetNamedDestByName = _libs['pdfium']['FPDF_GetNamedDestByName'] FPDF_GetNamedDestByName.argtypes = [FPDF_DOCUMENT, FPDF_BYTESTRING] FPDF_GetNamedDestByName.restype = FPDF_DEST -# ./fpdfview.h: 1353 if hasattr(_libs['pdfium'], 'FPDF_GetNamedDest'): FPDF_GetNamedDest = _libs['pdfium']['FPDF_GetNamedDest'] FPDF_GetNamedDest.argtypes = [FPDF_DOCUMENT, c_int, POINTER(None), POINTER(c_long)] FPDF_GetNamedDest.restype = FPDF_DEST -# ./fpdfview.h: 1365 if hasattr(_libs['pdfium'], 'FPDF_GetXFAPacketCount'): FPDF_GetXFAPacketCount = _libs['pdfium']['FPDF_GetXFAPacketCount'] FPDF_GetXFAPacketCount.argtypes = [FPDF_DOCUMENT] FPDF_GetXFAPacketCount.restype = c_int -# ./fpdfview.h: 1383 if hasattr(_libs['pdfium'], 'FPDF_GetXFAPacketName'): FPDF_GetXFAPacketName = _libs['pdfium']['FPDF_GetXFAPacketName'] FPDF_GetXFAPacketName.argtypes = [FPDF_DOCUMENT, c_int, POINTER(None), c_ulong] FPDF_GetXFAPacketName.restype = c_ulong -# ./fpdfview.h: 1410 if hasattr(_libs['pdfium'], 'FPDF_GetXFAPacketContent'): FPDF_GetXFAPacketContent = _libs['pdfium']['FPDF_GetXFAPacketContent'] FPDF_GetXFAPacketContent.argtypes = [FPDF_DOCUMENT, c_int, POINTER(None), c_ulong, POINTER(c_ulong)] FPDF_GetXFAPacketContent.restype = FPDF_BOOL -# ./fpdfview.h: 1427 if hasattr(_libs['pdfium'], 'FPDF_GetRecommendedV8Flags'): FPDF_GetRecommendedV8Flags = _libs['pdfium']['FPDF_GetRecommendedV8Flags'] FPDF_GetRecommendedV8Flags.argtypes = [] FPDF_GetRecommendedV8Flags.restype = POINTER(c_char) -# ./fpdfview.h: 1445 if hasattr(_libs['pdfium'], 'FPDF_GetArrayBufferAllocatorSharedInstance'): FPDF_GetArrayBufferAllocatorSharedInstance = _libs['pdfium']['FPDF_GetArrayBufferAllocatorSharedInstance'] FPDF_GetArrayBufferAllocatorSharedInstance.argtypes = [] FPDF_GetArrayBufferAllocatorSharedInstance.restype = POINTER(None) -# ./fpdfview.h: 1451 if hasattr(_libs['pdfium'], 'FPDF_BStr_Init'): FPDF_BStr_Init = _libs['pdfium']['FPDF_BStr_Init'] FPDF_BStr_Init.argtypes = [POINTER(FPDF_BSTR)] FPDF_BStr_Init.restype = FPDF_RESULT -# ./fpdfview.h: 1455 if hasattr(_libs['pdfium'], 'FPDF_BStr_Set'): FPDF_BStr_Set = _libs['pdfium']['FPDF_BStr_Set'] FPDF_BStr_Set.argtypes = [POINTER(FPDF_BSTR), POINTER(c_char), c_int] FPDF_BStr_Set.restype = FPDF_RESULT -# ./fpdfview.h: 1461 if hasattr(_libs['pdfium'], 'FPDF_BStr_Clear'): FPDF_BStr_Clear = _libs['pdfium']['FPDF_BStr_Clear'] FPDF_BStr_Clear.argtypes = [POINTER(FPDF_BSTR)] FPDF_BStr_Clear.restype = FPDF_RESULT -# ./fpdf_formfill.h: 52 class struct__IPDF_JsPlatform (Structure): __slots__ = ['version', 'app_alert', 'app_beep', 'app_response', 'Doc_getFilePath', 'Doc_mail', 'Doc_print', 'Doc_submitForm', 'Doc_gotoPage', 'Field_browse', 'm_pFormfillinfo', 'm_isolate', 'm_v8EmbedderSlot'] @@ -895,13 +718,10 @@ class struct__IPDF_JsPlatform (Structure): ('m_v8EmbedderSlot', c_uint), ] -# ./fpdf_formfill.h: 300 IPDF_JSPLATFORM = struct__IPDF_JsPlatform -# ./fpdf_formfill.h: 316 TimerCallback = CFUNCTYPE(None, c_int) -# ./fpdf_formfill.h: 328 class struct__FPDF_SYSTEMTIME (Structure): __slots__ = ['wYear', 'wMonth', 'wDayOfWeek', 'wDay', 'wHour', 'wMinute', 'wSecond', 'wMilliseconds'] @@ -916,10 +736,8 @@ class struct__FPDF_SYSTEMTIME (Structure): ('wMilliseconds', c_ushort), ] -# ./fpdf_formfill.h: 328 FPDF_SYSTEMTIME = struct__FPDF_SYSTEMTIME -# ./fpdf_formfill.h: 350 class struct__FPDF_FORMFILLINFO (Structure): __slots__ = ['version', 'Release', 'FFI_Invalidate', 'FFI_OutputSelectedRect', 'FFI_SetCursor', 'FFI_SetTimer', 'FFI_KillTimer', 'FFI_GetLocalTime', 'FFI_OnChange', 'FFI_GetPage', 'FFI_GetCurrentPage', 'FFI_GetRotation', 'FFI_ExecuteNamedAction', 'FFI_SetTextFieldFocus', 'FFI_DoURIAction', 'FFI_DoGoToAction', 'm_pJsPlatform', 'xfa_disabled', 'FFI_DisplayCaret', 'FFI_GetCurrentPageIndex', 'FFI_SetCurrentPage', 'FFI_GotoURL', 'FFI_GetPageViewRect', 'FFI_PageEvent', 'FFI_PopupMenu', 'FFI_OpenFile', 'FFI_EmailTo', 'FFI_UploadTo', 'FFI_GetPlatform', 'FFI_GetLanguage', 'FFI_DownloadFromURL', 'FFI_PostRequestURL', 'FFI_PutRequestURL', 'FFI_OnFocusChange', 'FFI_DoURIActionWithKeyboardModifier'] @@ -961,736 +779,611 @@ class struct__FPDF_FORMFILLINFO (Structure): ('FFI_DoURIActionWithKeyboardModifier', CFUNCTYPE(None, POINTER(struct__FPDF_FORMFILLINFO), FPDF_BYTESTRING, c_int)), ] -# ./fpdf_formfill.h: 1044 FPDF_FORMFILLINFO = struct__FPDF_FORMFILLINFO -# ./fpdf_formfill.h: 1058 if hasattr(_libs['pdfium'], 'FPDFDOC_InitFormFillEnvironment'): FPDFDOC_InitFormFillEnvironment = _libs['pdfium']['FPDFDOC_InitFormFillEnvironment'] FPDFDOC_InitFormFillEnvironment.argtypes = [FPDF_DOCUMENT, POINTER(FPDF_FORMFILLINFO)] FPDFDOC_InitFormFillEnvironment.restype = FPDF_FORMHANDLE -# ./fpdf_formfill.h: 1071 if hasattr(_libs['pdfium'], 'FPDFDOC_ExitFormFillEnvironment'): FPDFDOC_ExitFormFillEnvironment = _libs['pdfium']['FPDFDOC_ExitFormFillEnvironment'] FPDFDOC_ExitFormFillEnvironment.argtypes = [FPDF_FORMHANDLE] FPDFDOC_ExitFormFillEnvironment.restype = None -# ./fpdf_formfill.h: 1082 if hasattr(_libs['pdfium'], 'FORM_OnAfterLoadPage'): FORM_OnAfterLoadPage = _libs['pdfium']['FORM_OnAfterLoadPage'] FORM_OnAfterLoadPage.argtypes = [FPDF_PAGE, FPDF_FORMHANDLE] FORM_OnAfterLoadPage.restype = None -# ./fpdf_formfill.h: 1094 if hasattr(_libs['pdfium'], 'FORM_OnBeforeClosePage'): FORM_OnBeforeClosePage = _libs['pdfium']['FORM_OnBeforeClosePage'] FORM_OnBeforeClosePage.argtypes = [FPDF_PAGE, FPDF_FORMHANDLE] FORM_OnBeforeClosePage.restype = None -# ./fpdf_formfill.h: 1110 if hasattr(_libs['pdfium'], 'FORM_DoDocumentJSAction'): FORM_DoDocumentJSAction = _libs['pdfium']['FORM_DoDocumentJSAction'] FORM_DoDocumentJSAction.argtypes = [FPDF_FORMHANDLE] FORM_DoDocumentJSAction.restype = None -# ./fpdf_formfill.h: 1124 if hasattr(_libs['pdfium'], 'FORM_DoDocumentOpenAction'): FORM_DoDocumentOpenAction = _libs['pdfium']['FORM_DoDocumentOpenAction'] FORM_DoDocumentOpenAction.argtypes = [FPDF_FORMHANDLE] FORM_DoDocumentOpenAction.restype = None -# ./fpdf_formfill.h: 1151 if hasattr(_libs['pdfium'], 'FORM_DoDocumentAAction'): FORM_DoDocumentAAction = _libs['pdfium']['FORM_DoDocumentAAction'] FORM_DoDocumentAAction.argtypes = [FPDF_FORMHANDLE, c_int] FORM_DoDocumentAAction.restype = None -# ./fpdf_formfill.h: 1174 if hasattr(_libs['pdfium'], 'FORM_DoPageAAction'): FORM_DoPageAAction = _libs['pdfium']['FORM_DoPageAAction'] FORM_DoPageAAction.argtypes = [FPDF_PAGE, FPDF_FORMHANDLE, c_int] FORM_DoPageAAction.restype = None -# ./fpdf_formfill.h: 1191 if hasattr(_libs['pdfium'], 'FORM_OnMouseMove'): FORM_OnMouseMove = _libs['pdfium']['FORM_OnMouseMove'] FORM_OnMouseMove.argtypes = [FPDF_FORMHANDLE, FPDF_PAGE, c_int, c_double, c_double] FORM_OnMouseMove.restype = FPDF_BOOL -# ./fpdf_formfill.h: 1220 if hasattr(_libs['pdfium'], 'FORM_OnMouseWheel'): FORM_OnMouseWheel = _libs['pdfium']['FORM_OnMouseWheel'] FORM_OnMouseWheel.argtypes = [FPDF_FORMHANDLE, FPDF_PAGE, c_int, POINTER(FS_POINTF), c_int, c_int] FORM_OnMouseWheel.restype = FPDF_BOOL -# ./fpdf_formfill.h: 1243 if hasattr(_libs['pdfium'], 'FORM_OnFocus'): FORM_OnFocus = _libs['pdfium']['FORM_OnFocus'] FORM_OnFocus.argtypes = [FPDF_FORMHANDLE, FPDF_PAGE, c_int, c_double, c_double] FORM_OnFocus.restype = FPDF_BOOL -# ./fpdf_formfill.h: 1263 if hasattr(_libs['pdfium'], 'FORM_OnLButtonDown'): FORM_OnLButtonDown = _libs['pdfium']['FORM_OnLButtonDown'] FORM_OnLButtonDown.argtypes = [FPDF_FORMHANDLE, FPDF_PAGE, c_int, c_double, c_double] FORM_OnLButtonDown.restype = FPDF_BOOL -# ./fpdf_formfill.h: 1274 if hasattr(_libs['pdfium'], 'FORM_OnRButtonDown'): FORM_OnRButtonDown = _libs['pdfium']['FORM_OnRButtonDown'] FORM_OnRButtonDown.argtypes = [FPDF_FORMHANDLE, FPDF_PAGE, c_int, c_double, c_double] FORM_OnRButtonDown.restype = FPDF_BOOL -# ./fpdf_formfill.h: 1291 if hasattr(_libs['pdfium'], 'FORM_OnLButtonUp'): FORM_OnLButtonUp = _libs['pdfium']['FORM_OnLButtonUp'] FORM_OnLButtonUp.argtypes = [FPDF_FORMHANDLE, FPDF_PAGE, c_int, c_double, c_double] FORM_OnLButtonUp.restype = FPDF_BOOL -# ./fpdf_formfill.h: 1302 if hasattr(_libs['pdfium'], 'FORM_OnRButtonUp'): FORM_OnRButtonUp = _libs['pdfium']['FORM_OnRButtonUp'] FORM_OnRButtonUp.argtypes = [FPDF_FORMHANDLE, FPDF_PAGE, c_int, c_double, c_double] FORM_OnRButtonUp.restype = FPDF_BOOL -# ./fpdf_formfill.h: 1323 if hasattr(_libs['pdfium'], 'FORM_OnLButtonDoubleClick'): FORM_OnLButtonDoubleClick = _libs['pdfium']['FORM_OnLButtonDoubleClick'] FORM_OnLButtonDoubleClick.argtypes = [FPDF_FORMHANDLE, FPDF_PAGE, c_int, c_double, c_double] FORM_OnLButtonDoubleClick.restype = FPDF_BOOL -# ./fpdf_formfill.h: 1341 if hasattr(_libs['pdfium'], 'FORM_OnKeyDown'): FORM_OnKeyDown = _libs['pdfium']['FORM_OnKeyDown'] FORM_OnKeyDown.argtypes = [FPDF_FORMHANDLE, FPDF_PAGE, c_int, c_int] FORM_OnKeyDown.restype = FPDF_BOOL -# ./fpdf_formfill.h: 1361 if hasattr(_libs['pdfium'], 'FORM_OnKeyUp'): FORM_OnKeyUp = _libs['pdfium']['FORM_OnKeyUp'] FORM_OnKeyUp.argtypes = [FPDF_FORMHANDLE, FPDF_PAGE, c_int, c_int] FORM_OnKeyUp.restype = FPDF_BOOL -# ./fpdf_formfill.h: 1378 if hasattr(_libs['pdfium'], 'FORM_OnChar'): FORM_OnChar = _libs['pdfium']['FORM_OnChar'] FORM_OnChar.argtypes = [FPDF_FORMHANDLE, FPDF_PAGE, c_int, c_int] FORM_OnChar.restype = FPDF_BOOL -# ./fpdf_formfill.h: 1399 if hasattr(_libs['pdfium'], 'FORM_GetFocusedText'): FORM_GetFocusedText = _libs['pdfium']['FORM_GetFocusedText'] FORM_GetFocusedText.argtypes = [FPDF_FORMHANDLE, FPDF_PAGE, POINTER(None), c_ulong] FORM_GetFocusedText.restype = c_ulong -# ./fpdf_formfill.h: 1420 if hasattr(_libs['pdfium'], 'FORM_GetSelectedText'): FORM_GetSelectedText = _libs['pdfium']['FORM_GetSelectedText'] FORM_GetSelectedText.argtypes = [FPDF_FORMHANDLE, FPDF_PAGE, POINTER(None), c_ulong] FORM_GetSelectedText.restype = c_ulong -# ./fpdf_formfill.h: 1441 if hasattr(_libs['pdfium'], 'FORM_ReplaceAndKeepSelection'): FORM_ReplaceAndKeepSelection = _libs['pdfium']['FORM_ReplaceAndKeepSelection'] FORM_ReplaceAndKeepSelection.argtypes = [FPDF_FORMHANDLE, FPDF_PAGE, FPDF_WIDESTRING] FORM_ReplaceAndKeepSelection.restype = None -# ./fpdf_formfill.h: 1459 if hasattr(_libs['pdfium'], 'FORM_ReplaceSelection'): FORM_ReplaceSelection = _libs['pdfium']['FORM_ReplaceSelection'] FORM_ReplaceSelection.argtypes = [FPDF_FORMHANDLE, FPDF_PAGE, FPDF_WIDESTRING] FORM_ReplaceSelection.restype = None -# ./fpdf_formfill.h: 1474 if hasattr(_libs['pdfium'], 'FORM_SelectAllText'): FORM_SelectAllText = _libs['pdfium']['FORM_SelectAllText'] FORM_SelectAllText.argtypes = [FPDF_FORMHANDLE, FPDF_PAGE] FORM_SelectAllText.restype = FPDF_BOOL -# ./fpdf_formfill.h: 1485 if hasattr(_libs['pdfium'], 'FORM_CanUndo'): FORM_CanUndo = _libs['pdfium']['FORM_CanUndo'] FORM_CanUndo.argtypes = [FPDF_FORMHANDLE, FPDF_PAGE] FORM_CanUndo.restype = FPDF_BOOL -# ./fpdf_formfill.h: 1497 if hasattr(_libs['pdfium'], 'FORM_CanRedo'): FORM_CanRedo = _libs['pdfium']['FORM_CanRedo'] FORM_CanRedo.argtypes = [FPDF_FORMHANDLE, FPDF_PAGE] FORM_CanRedo.restype = FPDF_BOOL -# ./fpdf_formfill.h: 1508 if hasattr(_libs['pdfium'], 'FORM_Undo'): FORM_Undo = _libs['pdfium']['FORM_Undo'] FORM_Undo.argtypes = [FPDF_FORMHANDLE, FPDF_PAGE] FORM_Undo.restype = FPDF_BOOL -# ./fpdf_formfill.h: 1519 if hasattr(_libs['pdfium'], 'FORM_Redo'): FORM_Redo = _libs['pdfium']['FORM_Redo'] FORM_Redo.argtypes = [FPDF_FORMHANDLE, FPDF_PAGE] FORM_Redo.restype = FPDF_BOOL -# ./fpdf_formfill.h: 1532 if hasattr(_libs['pdfium'], 'FORM_ForceToKillFocus'): FORM_ForceToKillFocus = _libs['pdfium']['FORM_ForceToKillFocus'] FORM_ForceToKillFocus.argtypes = [FPDF_FORMHANDLE] FORM_ForceToKillFocus.restype = FPDF_BOOL -# ./fpdf_formfill.h: 1555 if hasattr(_libs['pdfium'], 'FORM_GetFocusedAnnot'): FORM_GetFocusedAnnot = _libs['pdfium']['FORM_GetFocusedAnnot'] FORM_GetFocusedAnnot.argtypes = [FPDF_FORMHANDLE, POINTER(c_int), POINTER(FPDF_ANNOTATION)] FORM_GetFocusedAnnot.restype = FPDF_BOOL -# ./fpdf_formfill.h: 1572 if hasattr(_libs['pdfium'], 'FORM_SetFocusedAnnot'): FORM_SetFocusedAnnot = _libs['pdfium']['FORM_SetFocusedAnnot'] FORM_SetFocusedAnnot.argtypes = [FPDF_FORMHANDLE, FPDF_ANNOTATION] FORM_SetFocusedAnnot.restype = FPDF_BOOL -# ./fpdf_formfill.h: 1626 if hasattr(_libs['pdfium'], 'FPDFPage_HasFormFieldAtPoint'): FPDFPage_HasFormFieldAtPoint = _libs['pdfium']['FPDFPage_HasFormFieldAtPoint'] FPDFPage_HasFormFieldAtPoint.argtypes = [FPDF_FORMHANDLE, FPDF_PAGE, c_double, c_double] FPDFPage_HasFormFieldAtPoint.restype = c_int -# ./fpdf_formfill.h: 1643 if hasattr(_libs['pdfium'], 'FPDFPage_FormFieldZOrderAtPoint'): FPDFPage_FormFieldZOrderAtPoint = _libs['pdfium']['FPDFPage_FormFieldZOrderAtPoint'] FPDFPage_FormFieldZOrderAtPoint.argtypes = [FPDF_FORMHANDLE, FPDF_PAGE, c_double, c_double] FPDFPage_FormFieldZOrderAtPoint.restype = c_int -# ./fpdf_formfill.h: 1669 if hasattr(_libs['pdfium'], 'FPDF_SetFormFieldHighlightColor'): FPDF_SetFormFieldHighlightColor = _libs['pdfium']['FPDF_SetFormFieldHighlightColor'] FPDF_SetFormFieldHighlightColor.argtypes = [FPDF_FORMHANDLE, c_int, c_ulong] FPDF_SetFormFieldHighlightColor.restype = None -# ./fpdf_formfill.h: 1686 if hasattr(_libs['pdfium'], 'FPDF_SetFormFieldHighlightAlpha'): FPDF_SetFormFieldHighlightAlpha = _libs['pdfium']['FPDF_SetFormFieldHighlightAlpha'] FPDF_SetFormFieldHighlightAlpha.argtypes = [FPDF_FORMHANDLE, c_ubyte] FPDF_SetFormFieldHighlightAlpha.restype = None -# ./fpdf_formfill.h: 1699 if hasattr(_libs['pdfium'], 'FPDF_RemoveFormFieldHighlight'): FPDF_RemoveFormFieldHighlight = _libs['pdfium']['FPDF_RemoveFormFieldHighlight'] FPDF_RemoveFormFieldHighlight.argtypes = [FPDF_FORMHANDLE] FPDF_RemoveFormFieldHighlight.restype = None -# ./fpdf_formfill.h: 1736 if hasattr(_libs['pdfium'], 'FPDF_FFLDraw'): FPDF_FFLDraw = _libs['pdfium']['FPDF_FFLDraw'] FPDF_FFLDraw.argtypes = [FPDF_FORMHANDLE, FPDF_BITMAP, FPDF_PAGE, c_int, c_int, c_int, c_int, c_int, c_int] FPDF_FFLDraw.restype = None -# ./fpdf_formfill.h: 1747 if hasattr(_libs['pdfium'], 'FPDF_FFLDrawSkia'): FPDF_FFLDrawSkia = _libs['pdfium']['FPDF_FFLDrawSkia'] FPDF_FFLDrawSkia.argtypes = [FPDF_FORMHANDLE, FPDF_SKIA_CANVAS, FPDF_PAGE, c_int, c_int, c_int, c_int, c_int, c_int] FPDF_FFLDrawSkia.restype = None -# ./fpdf_formfill.h: 1767 if hasattr(_libs['pdfium'], 'FPDF_GetFormType'): FPDF_GetFormType = _libs['pdfium']['FPDF_GetFormType'] FPDF_GetFormType.argtypes = [FPDF_DOCUMENT] FPDF_GetFormType.restype = c_int -# ./fpdf_formfill.h: 1791 if hasattr(_libs['pdfium'], 'FORM_SetIndexSelected'): FORM_SetIndexSelected = _libs['pdfium']['FORM_SetIndexSelected'] FORM_SetIndexSelected.argtypes = [FPDF_FORMHANDLE, FPDF_PAGE, c_int, FPDF_BOOL] FORM_SetIndexSelected.restype = FPDF_BOOL -# ./fpdf_formfill.h: 1814 if hasattr(_libs['pdfium'], 'FORM_IsIndexSelected'): FORM_IsIndexSelected = _libs['pdfium']['FORM_IsIndexSelected'] FORM_IsIndexSelected.argtypes = [FPDF_FORMHANDLE, FPDF_PAGE, c_int] FORM_IsIndexSelected.restype = FPDF_BOOL -# ./fpdf_formfill.h: 1824 if hasattr(_libs['pdfium'], 'FPDF_LoadXFA'): FPDF_LoadXFA = _libs['pdfium']['FPDF_LoadXFA'] FPDF_LoadXFA.argtypes = [FPDF_DOCUMENT] FPDF_LoadXFA.restype = FPDF_BOOL -# ./fpdf_annot.h: 98 enum_FPDFANNOT_COLORTYPE = c_int -# ./fpdf_annot.h: 98 FPDFANNOT_COLORTYPE_Color = 0 -# ./fpdf_annot.h: 98 FPDFANNOT_COLORTYPE_InteriorColor = (FPDFANNOT_COLORTYPE_Color + 1) -# ./fpdf_annot.h: 98 FPDFANNOT_COLORTYPE = enum_FPDFANNOT_COLORTYPE -# ./fpdf_annot.h: 121 if hasattr(_libs['pdfium'], 'FPDFAnnot_IsSupportedSubtype'): FPDFAnnot_IsSupportedSubtype = _libs['pdfium']['FPDFAnnot_IsSupportedSubtype'] FPDFAnnot_IsSupportedSubtype.argtypes = [FPDF_ANNOTATION_SUBTYPE] FPDFAnnot_IsSupportedSubtype.restype = FPDF_BOOL -# ./fpdf_annot.h: 134 if hasattr(_libs['pdfium'], 'FPDFPage_CreateAnnot'): FPDFPage_CreateAnnot = _libs['pdfium']['FPDFPage_CreateAnnot'] FPDFPage_CreateAnnot.argtypes = [FPDF_PAGE, FPDF_ANNOTATION_SUBTYPE] FPDFPage_CreateAnnot.restype = FPDF_ANNOTATION -# ./fpdf_annot.h: 142 if hasattr(_libs['pdfium'], 'FPDFPage_GetAnnotCount'): FPDFPage_GetAnnotCount = _libs['pdfium']['FPDFPage_GetAnnotCount'] FPDFPage_GetAnnotCount.argtypes = [FPDF_PAGE] FPDFPage_GetAnnotCount.restype = c_int -# ./fpdf_annot.h: 152 if hasattr(_libs['pdfium'], 'FPDFPage_GetAnnot'): FPDFPage_GetAnnot = _libs['pdfium']['FPDFPage_GetAnnot'] FPDFPage_GetAnnot.argtypes = [FPDF_PAGE, c_int] FPDFPage_GetAnnot.restype = FPDF_ANNOTATION -# ./fpdf_annot.h: 163 if hasattr(_libs['pdfium'], 'FPDFPage_GetAnnotIndex'): FPDFPage_GetAnnotIndex = _libs['pdfium']['FPDFPage_GetAnnotIndex'] FPDFPage_GetAnnotIndex.argtypes = [FPDF_PAGE, FPDF_ANNOTATION] FPDFPage_GetAnnotIndex.restype = c_int -# ./fpdf_annot.h: 172 if hasattr(_libs['pdfium'], 'FPDFPage_CloseAnnot'): FPDFPage_CloseAnnot = _libs['pdfium']['FPDFPage_CloseAnnot'] FPDFPage_CloseAnnot.argtypes = [FPDF_ANNOTATION] FPDFPage_CloseAnnot.restype = None -# ./fpdf_annot.h: 181 if hasattr(_libs['pdfium'], 'FPDFPage_RemoveAnnot'): FPDFPage_RemoveAnnot = _libs['pdfium']['FPDFPage_RemoveAnnot'] FPDFPage_RemoveAnnot.argtypes = [FPDF_PAGE, c_int] FPDFPage_RemoveAnnot.restype = FPDF_BOOL -# ./fpdf_annot.h: 191 if hasattr(_libs['pdfium'], 'FPDFAnnot_GetSubtype'): FPDFAnnot_GetSubtype = _libs['pdfium']['FPDFAnnot_GetSubtype'] FPDFAnnot_GetSubtype.argtypes = [FPDF_ANNOTATION] FPDFAnnot_GetSubtype.restype = FPDF_ANNOTATION_SUBTYPE -# ./fpdf_annot.h: 202 if hasattr(_libs['pdfium'], 'FPDFAnnot_IsObjectSupportedSubtype'): FPDFAnnot_IsObjectSupportedSubtype = _libs['pdfium']['FPDFAnnot_IsObjectSupportedSubtype'] FPDFAnnot_IsObjectSupportedSubtype.argtypes = [FPDF_ANNOTATION_SUBTYPE] FPDFAnnot_IsObjectSupportedSubtype.restype = FPDF_BOOL -# ./fpdf_annot.h: 216 if hasattr(_libs['pdfium'], 'FPDFAnnot_UpdateObject'): FPDFAnnot_UpdateObject = _libs['pdfium']['FPDFAnnot_UpdateObject'] FPDFAnnot_UpdateObject.argtypes = [FPDF_ANNOTATION, FPDF_PAGEOBJECT] FPDFAnnot_UpdateObject.restype = FPDF_BOOL -# ./fpdf_annot.h: 231 if hasattr(_libs['pdfium'], 'FPDFAnnot_AddInkStroke'): FPDFAnnot_AddInkStroke = _libs['pdfium']['FPDFAnnot_AddInkStroke'] FPDFAnnot_AddInkStroke.argtypes = [FPDF_ANNOTATION, POINTER(FS_POINTF), c_size_t] FPDFAnnot_AddInkStroke.restype = c_int -# ./fpdf_annot.h: 244 if hasattr(_libs['pdfium'], 'FPDFAnnot_RemoveInkList'): FPDFAnnot_RemoveInkList = _libs['pdfium']['FPDFAnnot_RemoveInkList'] FPDFAnnot_RemoveInkList.argtypes = [FPDF_ANNOTATION] FPDFAnnot_RemoveInkList.restype = FPDF_BOOL -# ./fpdf_annot.h: 258 if hasattr(_libs['pdfium'], 'FPDFAnnot_AppendObject'): FPDFAnnot_AppendObject = _libs['pdfium']['FPDFAnnot_AppendObject'] FPDFAnnot_AppendObject.argtypes = [FPDF_ANNOTATION, FPDF_PAGEOBJECT] FPDFAnnot_AppendObject.restype = FPDF_BOOL -# ./fpdf_annot.h: 267 if hasattr(_libs['pdfium'], 'FPDFAnnot_GetObjectCount'): FPDFAnnot_GetObjectCount = _libs['pdfium']['FPDFAnnot_GetObjectCount'] FPDFAnnot_GetObjectCount.argtypes = [FPDF_ANNOTATION] FPDFAnnot_GetObjectCount.restype = c_int -# ./fpdf_annot.h: 277 if hasattr(_libs['pdfium'], 'FPDFAnnot_GetObject'): FPDFAnnot_GetObject = _libs['pdfium']['FPDFAnnot_GetObject'] FPDFAnnot_GetObject.argtypes = [FPDF_ANNOTATION, c_int] FPDFAnnot_GetObject.restype = FPDF_PAGEOBJECT -# ./fpdf_annot.h: 287 if hasattr(_libs['pdfium'], 'FPDFAnnot_RemoveObject'): FPDFAnnot_RemoveObject = _libs['pdfium']['FPDFAnnot_RemoveObject'] FPDFAnnot_RemoveObject.argtypes = [FPDF_ANNOTATION, c_int] FPDFAnnot_RemoveObject.restype = FPDF_BOOL -# ./fpdf_annot.h: 300 if hasattr(_libs['pdfium'], 'FPDFAnnot_SetColor'): FPDFAnnot_SetColor = _libs['pdfium']['FPDFAnnot_SetColor'] FPDFAnnot_SetColor.argtypes = [FPDF_ANNOTATION, FPDFANNOT_COLORTYPE, c_uint, c_uint, c_uint, c_uint] FPDFAnnot_SetColor.restype = FPDF_BOOL -# ./fpdf_annot.h: 319 if hasattr(_libs['pdfium'], 'FPDFAnnot_GetColor'): FPDFAnnot_GetColor = _libs['pdfium']['FPDFAnnot_GetColor'] FPDFAnnot_GetColor.argtypes = [FPDF_ANNOTATION, FPDFANNOT_COLORTYPE, POINTER(c_uint), POINTER(c_uint), POINTER(c_uint), POINTER(c_uint)] FPDFAnnot_GetColor.restype = FPDF_BOOL -# ./fpdf_annot.h: 339 if hasattr(_libs['pdfium'], 'FPDFAnnot_HasAttachmentPoints'): FPDFAnnot_HasAttachmentPoints = _libs['pdfium']['FPDFAnnot_HasAttachmentPoints'] FPDFAnnot_HasAttachmentPoints.argtypes = [FPDF_ANNOTATION] FPDFAnnot_HasAttachmentPoints.restype = FPDF_BOOL -# ./fpdf_annot.h: 355 if hasattr(_libs['pdfium'], 'FPDFAnnot_SetAttachmentPoints'): FPDFAnnot_SetAttachmentPoints = _libs['pdfium']['FPDFAnnot_SetAttachmentPoints'] FPDFAnnot_SetAttachmentPoints.argtypes = [FPDF_ANNOTATION, c_size_t, POINTER(FS_QUADPOINTSF)] FPDFAnnot_SetAttachmentPoints.restype = FPDF_BOOL -# ./fpdf_annot.h: 370 if hasattr(_libs['pdfium'], 'FPDFAnnot_AppendAttachmentPoints'): FPDFAnnot_AppendAttachmentPoints = _libs['pdfium']['FPDFAnnot_AppendAttachmentPoints'] FPDFAnnot_AppendAttachmentPoints.argtypes = [FPDF_ANNOTATION, POINTER(FS_QUADPOINTSF)] FPDFAnnot_AppendAttachmentPoints.restype = FPDF_BOOL -# ./fpdf_annot.h: 380 if hasattr(_libs['pdfium'], 'FPDFAnnot_CountAttachmentPoints'): FPDFAnnot_CountAttachmentPoints = _libs['pdfium']['FPDFAnnot_CountAttachmentPoints'] FPDFAnnot_CountAttachmentPoints.argtypes = [FPDF_ANNOTATION] FPDFAnnot_CountAttachmentPoints.restype = c_size_t -# ./fpdf_annot.h: 391 if hasattr(_libs['pdfium'], 'FPDFAnnot_GetAttachmentPoints'): FPDFAnnot_GetAttachmentPoints = _libs['pdfium']['FPDFAnnot_GetAttachmentPoints'] FPDFAnnot_GetAttachmentPoints.argtypes = [FPDF_ANNOTATION, c_size_t, POINTER(FS_QUADPOINTSF)] FPDFAnnot_GetAttachmentPoints.restype = FPDF_BOOL -# ./fpdf_annot.h: 405 if hasattr(_libs['pdfium'], 'FPDFAnnot_SetRect'): FPDFAnnot_SetRect = _libs['pdfium']['FPDFAnnot_SetRect'] FPDFAnnot_SetRect.argtypes = [FPDF_ANNOTATION, POINTER(FS_RECTF)] FPDFAnnot_SetRect.restype = FPDF_BOOL -# ./fpdf_annot.h: 415 if hasattr(_libs['pdfium'], 'FPDFAnnot_GetRect'): FPDFAnnot_GetRect = _libs['pdfium']['FPDFAnnot_GetRect'] FPDFAnnot_GetRect.argtypes = [FPDF_ANNOTATION, POINTER(FS_RECTF)] FPDFAnnot_GetRect.restype = FPDF_BOOL -# ./fpdf_annot.h: 430 if hasattr(_libs['pdfium'], 'FPDFAnnot_GetVertices'): FPDFAnnot_GetVertices = _libs['pdfium']['FPDFAnnot_GetVertices'] FPDFAnnot_GetVertices.argtypes = [FPDF_ANNOTATION, POINTER(FS_POINTF), c_ulong] FPDFAnnot_GetVertices.restype = c_ulong -# ./fpdf_annot.h: 442 if hasattr(_libs['pdfium'], 'FPDFAnnot_GetInkListCount'): FPDFAnnot_GetInkListCount = _libs['pdfium']['FPDFAnnot_GetInkListCount'] FPDFAnnot_GetInkListCount.argtypes = [FPDF_ANNOTATION] FPDFAnnot_GetInkListCount.restype = c_ulong -# ./fpdf_annot.h: 457 if hasattr(_libs['pdfium'], 'FPDFAnnot_GetInkListPath'): FPDFAnnot_GetInkListPath = _libs['pdfium']['FPDFAnnot_GetInkListPath'] FPDFAnnot_GetInkListPath.argtypes = [FPDF_ANNOTATION, c_ulong, POINTER(FS_POINTF), c_ulong] FPDFAnnot_GetInkListPath.restype = c_ulong -# ./fpdf_annot.h: 471 if hasattr(_libs['pdfium'], 'FPDFAnnot_GetLine'): FPDFAnnot_GetLine = _libs['pdfium']['FPDFAnnot_GetLine'] FPDFAnnot_GetLine.argtypes = [FPDF_ANNOTATION, POINTER(FS_POINTF), POINTER(FS_POINTF)] FPDFAnnot_GetLine.restype = FPDF_BOOL -# ./fpdf_annot.h: 487 if hasattr(_libs['pdfium'], 'FPDFAnnot_SetBorder'): FPDFAnnot_SetBorder = _libs['pdfium']['FPDFAnnot_SetBorder'] FPDFAnnot_SetBorder.argtypes = [FPDF_ANNOTATION, c_float, c_float, c_float] FPDFAnnot_SetBorder.restype = FPDF_BOOL -# ./fpdf_annot.h: 503 if hasattr(_libs['pdfium'], 'FPDFAnnot_GetBorder'): FPDFAnnot_GetBorder = _libs['pdfium']['FPDFAnnot_GetBorder'] FPDFAnnot_GetBorder.argtypes = [FPDF_ANNOTATION, POINTER(c_float), POINTER(c_float), POINTER(c_float)] FPDFAnnot_GetBorder.restype = FPDF_BOOL -# ./fpdf_annot.h: 527 if hasattr(_libs['pdfium'], 'FPDFAnnot_GetFormAdditionalActionJavaScript'): FPDFAnnot_GetFormAdditionalActionJavaScript = _libs['pdfium']['FPDFAnnot_GetFormAdditionalActionJavaScript'] FPDFAnnot_GetFormAdditionalActionJavaScript.argtypes = [FPDF_FORMHANDLE, FPDF_ANNOTATION, c_int, POINTER(FPDF_WCHAR), c_ulong] FPDFAnnot_GetFormAdditionalActionJavaScript.restype = c_ulong -# ./fpdf_annot.h: 540 if hasattr(_libs['pdfium'], 'FPDFAnnot_HasKey'): FPDFAnnot_HasKey = _libs['pdfium']['FPDFAnnot_HasKey'] FPDFAnnot_HasKey.argtypes = [FPDF_ANNOTATION, FPDF_BYTESTRING] FPDFAnnot_HasKey.restype = FPDF_BOOL -# ./fpdf_annot.h: 551 if hasattr(_libs['pdfium'], 'FPDFAnnot_GetValueType'): FPDFAnnot_GetValueType = _libs['pdfium']['FPDFAnnot_GetValueType'] FPDFAnnot_GetValueType.argtypes = [FPDF_ANNOTATION, FPDF_BYTESTRING] FPDFAnnot_GetValueType.restype = FPDF_OBJECT_TYPE -# ./fpdf_annot.h: 564 if hasattr(_libs['pdfium'], 'FPDFAnnot_SetStringValue'): FPDFAnnot_SetStringValue = _libs['pdfium']['FPDFAnnot_SetStringValue'] FPDFAnnot_SetStringValue.argtypes = [FPDF_ANNOTATION, FPDF_BYTESTRING, FPDF_WIDESTRING] FPDFAnnot_SetStringValue.restype = FPDF_BOOL -# ./fpdf_annot.h: 584 if hasattr(_libs['pdfium'], 'FPDFAnnot_GetStringValue'): FPDFAnnot_GetStringValue = _libs['pdfium']['FPDFAnnot_GetStringValue'] FPDFAnnot_GetStringValue.argtypes = [FPDF_ANNOTATION, FPDF_BYTESTRING, POINTER(FPDF_WCHAR), c_ulong] FPDFAnnot_GetStringValue.restype = c_ulong -# ./fpdf_annot.h: 601 if hasattr(_libs['pdfium'], 'FPDFAnnot_GetNumberValue'): FPDFAnnot_GetNumberValue = _libs['pdfium']['FPDFAnnot_GetNumberValue'] FPDFAnnot_GetNumberValue.argtypes = [FPDF_ANNOTATION, FPDF_BYTESTRING, POINTER(c_float)] FPDFAnnot_GetNumberValue.restype = FPDF_BOOL -# ./fpdf_annot.h: 618 if hasattr(_libs['pdfium'], 'FPDFAnnot_SetAP'): FPDFAnnot_SetAP = _libs['pdfium']['FPDFAnnot_SetAP'] FPDFAnnot_SetAP.argtypes = [FPDF_ANNOTATION, FPDF_ANNOT_APPEARANCEMODE, FPDF_WIDESTRING] FPDFAnnot_SetAP.restype = FPDF_BOOL -# ./fpdf_annot.h: 640 if hasattr(_libs['pdfium'], 'FPDFAnnot_GetAP'): FPDFAnnot_GetAP = _libs['pdfium']['FPDFAnnot_GetAP'] FPDFAnnot_GetAP.argtypes = [FPDF_ANNOTATION, FPDF_ANNOT_APPEARANCEMODE, POINTER(FPDF_WCHAR), c_ulong] FPDFAnnot_GetAP.restype = c_ulong -# ./fpdf_annot.h: 656 if hasattr(_libs['pdfium'], 'FPDFAnnot_GetLinkedAnnot'): FPDFAnnot_GetLinkedAnnot = _libs['pdfium']['FPDFAnnot_GetLinkedAnnot'] FPDFAnnot_GetLinkedAnnot.argtypes = [FPDF_ANNOTATION, FPDF_BYTESTRING] FPDFAnnot_GetLinkedAnnot.restype = FPDF_ANNOTATION -# ./fpdf_annot.h: 664 if hasattr(_libs['pdfium'], 'FPDFAnnot_GetFlags'): FPDFAnnot_GetFlags = _libs['pdfium']['FPDFAnnot_GetFlags'] FPDFAnnot_GetFlags.argtypes = [FPDF_ANNOTATION] FPDFAnnot_GetFlags.restype = c_int -# ./fpdf_annot.h: 673 if hasattr(_libs['pdfium'], 'FPDFAnnot_SetFlags'): FPDFAnnot_SetFlags = _libs['pdfium']['FPDFAnnot_SetFlags'] FPDFAnnot_SetFlags.argtypes = [FPDF_ANNOTATION, c_int] FPDFAnnot_SetFlags.restype = FPDF_BOOL -# ./fpdf_annot.h: 685 if hasattr(_libs['pdfium'], 'FPDFAnnot_GetFormFieldFlags'): FPDFAnnot_GetFormFieldFlags = _libs['pdfium']['FPDFAnnot_GetFormFieldFlags'] FPDFAnnot_GetFormFieldFlags.argtypes = [FPDF_FORMHANDLE, FPDF_ANNOTATION] FPDFAnnot_GetFormFieldFlags.restype = c_int -# ./fpdf_annot.h: 702 if hasattr(_libs['pdfium'], 'FPDFAnnot_GetFormFieldAtPoint'): FPDFAnnot_GetFormFieldAtPoint = _libs['pdfium']['FPDFAnnot_GetFormFieldAtPoint'] FPDFAnnot_GetFormFieldAtPoint.argtypes = [FPDF_FORMHANDLE, FPDF_PAGE, POINTER(FS_POINTF)] FPDFAnnot_GetFormFieldAtPoint.restype = FPDF_ANNOTATION -# ./fpdf_annot.h: 720 if hasattr(_libs['pdfium'], 'FPDFAnnot_GetFormFieldName'): FPDFAnnot_GetFormFieldName = _libs['pdfium']['FPDFAnnot_GetFormFieldName'] FPDFAnnot_GetFormFieldName.argtypes = [FPDF_FORMHANDLE, FPDF_ANNOTATION, POINTER(FPDF_WCHAR), c_ulong] FPDFAnnot_GetFormFieldName.restype = c_ulong -# ./fpdf_annot.h: 740 if hasattr(_libs['pdfium'], 'FPDFAnnot_GetFormFieldAlternateName'): FPDFAnnot_GetFormFieldAlternateName = _libs['pdfium']['FPDFAnnot_GetFormFieldAlternateName'] FPDFAnnot_GetFormFieldAlternateName.argtypes = [FPDF_FORMHANDLE, FPDF_ANNOTATION, POINTER(FPDF_WCHAR), c_ulong] FPDFAnnot_GetFormFieldAlternateName.restype = c_ulong -# ./fpdf_annot.h: 756 if hasattr(_libs['pdfium'], 'FPDFAnnot_GetFormFieldType'): FPDFAnnot_GetFormFieldType = _libs['pdfium']['FPDFAnnot_GetFormFieldType'] FPDFAnnot_GetFormFieldType.argtypes = [FPDF_FORMHANDLE, FPDF_ANNOTATION] FPDFAnnot_GetFormFieldType.restype = c_int -# ./fpdf_annot.h: 772 if hasattr(_libs['pdfium'], 'FPDFAnnot_GetFormFieldValue'): FPDFAnnot_GetFormFieldValue = _libs['pdfium']['FPDFAnnot_GetFormFieldValue'] FPDFAnnot_GetFormFieldValue.argtypes = [FPDF_FORMHANDLE, FPDF_ANNOTATION, POINTER(FPDF_WCHAR), c_ulong] FPDFAnnot_GetFormFieldValue.restype = c_ulong -# ./fpdf_annot.h: 787 if hasattr(_libs['pdfium'], 'FPDFAnnot_GetOptionCount'): FPDFAnnot_GetOptionCount = _libs['pdfium']['FPDFAnnot_GetOptionCount'] FPDFAnnot_GetOptionCount.argtypes = [FPDF_FORMHANDLE, FPDF_ANNOTATION] FPDFAnnot_GetOptionCount.restype = c_int -# ./fpdf_annot.h: 809 if hasattr(_libs['pdfium'], 'FPDFAnnot_GetOptionLabel'): FPDFAnnot_GetOptionLabel = _libs['pdfium']['FPDFAnnot_GetOptionLabel'] FPDFAnnot_GetOptionLabel.argtypes = [FPDF_FORMHANDLE, FPDF_ANNOTATION, c_int, POINTER(FPDF_WCHAR), c_ulong] FPDFAnnot_GetOptionLabel.restype = c_ulong -# ./fpdf_annot.h: 827 if hasattr(_libs['pdfium'], 'FPDFAnnot_IsOptionSelected'): FPDFAnnot_IsOptionSelected = _libs['pdfium']['FPDFAnnot_IsOptionSelected'] FPDFAnnot_IsOptionSelected.argtypes = [FPDF_FORMHANDLE, FPDF_ANNOTATION, c_int] FPDFAnnot_IsOptionSelected.restype = FPDF_BOOL -# ./fpdf_annot.h: 844 if hasattr(_libs['pdfium'], 'FPDFAnnot_GetFontSize'): FPDFAnnot_GetFontSize = _libs['pdfium']['FPDFAnnot_GetFontSize'] FPDFAnnot_GetFontSize.argtypes = [FPDF_FORMHANDLE, FPDF_ANNOTATION, POINTER(c_float)] FPDFAnnot_GetFontSize.restype = FPDF_BOOL -# ./fpdf_annot.h: 859 if hasattr(_libs['pdfium'], 'FPDFAnnot_GetFontColor'): FPDFAnnot_GetFontColor = _libs['pdfium']['FPDFAnnot_GetFontColor'] FPDFAnnot_GetFontColor.argtypes = [FPDF_FORMHANDLE, FPDF_ANNOTATION, POINTER(c_uint), POINTER(c_uint), POINTER(c_uint)] FPDFAnnot_GetFontColor.restype = FPDF_BOOL -# ./fpdf_annot.h: 874 if hasattr(_libs['pdfium'], 'FPDFAnnot_IsChecked'): FPDFAnnot_IsChecked = _libs['pdfium']['FPDFAnnot_IsChecked'] FPDFAnnot_IsChecked.argtypes = [FPDF_FORMHANDLE, FPDF_ANNOTATION] FPDFAnnot_IsChecked.restype = FPDF_BOOL -# ./fpdf_annot.h: 889 if hasattr(_libs['pdfium'], 'FPDFAnnot_SetFocusableSubtypes'): FPDFAnnot_SetFocusableSubtypes = _libs['pdfium']['FPDFAnnot_SetFocusableSubtypes'] FPDFAnnot_SetFocusableSubtypes.argtypes = [FPDF_FORMHANDLE, POINTER(FPDF_ANNOTATION_SUBTYPE), c_size_t] FPDFAnnot_SetFocusableSubtypes.restype = FPDF_BOOL -# ./fpdf_annot.h: 902 if hasattr(_libs['pdfium'], 'FPDFAnnot_GetFocusableSubtypesCount'): FPDFAnnot_GetFocusableSubtypesCount = _libs['pdfium']['FPDFAnnot_GetFocusableSubtypesCount'] FPDFAnnot_GetFocusableSubtypesCount.argtypes = [FPDF_FORMHANDLE] FPDFAnnot_GetFocusableSubtypesCount.restype = c_int -# ./fpdf_annot.h: 918 if hasattr(_libs['pdfium'], 'FPDFAnnot_GetFocusableSubtypes'): FPDFAnnot_GetFocusableSubtypes = _libs['pdfium']['FPDFAnnot_GetFocusableSubtypes'] FPDFAnnot_GetFocusableSubtypes.argtypes = [FPDF_FORMHANDLE, POINTER(FPDF_ANNOTATION_SUBTYPE), c_size_t] FPDFAnnot_GetFocusableSubtypes.restype = FPDF_BOOL -# ./fpdf_annot.h: 929 if hasattr(_libs['pdfium'], 'FPDFAnnot_GetLink'): FPDFAnnot_GetLink = _libs['pdfium']['FPDFAnnot_GetLink'] FPDFAnnot_GetLink.argtypes = [FPDF_ANNOTATION] FPDFAnnot_GetLink.restype = FPDF_LINK -# ./fpdf_annot.h: 943 if hasattr(_libs['pdfium'], 'FPDFAnnot_GetFormControlCount'): FPDFAnnot_GetFormControlCount = _libs['pdfium']['FPDFAnnot_GetFormControlCount'] FPDFAnnot_GetFormControlCount.argtypes = [FPDF_FORMHANDLE, FPDF_ANNOTATION] FPDFAnnot_GetFormControlCount.restype = c_int -# ./fpdf_annot.h: 957 if hasattr(_libs['pdfium'], 'FPDFAnnot_GetFormControlIndex'): FPDFAnnot_GetFormControlIndex = _libs['pdfium']['FPDFAnnot_GetFormControlIndex'] FPDFAnnot_GetFormControlIndex.argtypes = [FPDF_FORMHANDLE, FPDF_ANNOTATION] FPDFAnnot_GetFormControlIndex.restype = c_int -# ./fpdf_annot.h: 974 if hasattr(_libs['pdfium'], 'FPDFAnnot_GetFormFieldExportValue'): FPDFAnnot_GetFormFieldExportValue = _libs['pdfium']['FPDFAnnot_GetFormFieldExportValue'] FPDFAnnot_GetFormFieldExportValue.argtypes = [FPDF_FORMHANDLE, FPDF_ANNOTATION, POINTER(FPDF_WCHAR), c_ulong] FPDFAnnot_GetFormFieldExportValue.restype = c_ulong -# ./fpdf_annot.h: 986 if hasattr(_libs['pdfium'], 'FPDFAnnot_SetURI'): FPDFAnnot_SetURI = _libs['pdfium']['FPDFAnnot_SetURI'] FPDFAnnot_SetURI.argtypes = [FPDF_ANNOTATION, POINTER(c_char)] FPDFAnnot_SetURI.restype = FPDF_BOOL -# ./fpdf_annot.h: 996 if hasattr(_libs['pdfium'], 'FPDFAnnot_GetFileAttachment'): FPDFAnnot_GetFileAttachment = _libs['pdfium']['FPDFAnnot_GetFileAttachment'] FPDFAnnot_GetFileAttachment.argtypes = [FPDF_ANNOTATION] FPDFAnnot_GetFileAttachment.restype = FPDF_ATTACHMENT -# ./fpdf_annot.h: 1006 if hasattr(_libs['pdfium'], 'FPDFAnnot_AddFileAttachment'): FPDFAnnot_AddFileAttachment = _libs['pdfium']['FPDFAnnot_AddFileAttachment'] FPDFAnnot_AddFileAttachment.argtypes = [FPDF_ANNOTATION, FPDF_WIDESTRING] FPDFAnnot_AddFileAttachment.restype = FPDF_ATTACHMENT -# ./fpdf_attachment.h: 22 if hasattr(_libs['pdfium'], 'FPDFDoc_GetAttachmentCount'): FPDFDoc_GetAttachmentCount = _libs['pdfium']['FPDFDoc_GetAttachmentCount'] FPDFDoc_GetAttachmentCount.argtypes = [FPDF_DOCUMENT] FPDFDoc_GetAttachmentCount.restype = c_int -# ./fpdf_attachment.h: 35 if hasattr(_libs['pdfium'], 'FPDFDoc_AddAttachment'): FPDFDoc_AddAttachment = _libs['pdfium']['FPDFDoc_AddAttachment'] FPDFDoc_AddAttachment.argtypes = [FPDF_DOCUMENT, FPDF_WIDESTRING] FPDFDoc_AddAttachment.restype = FPDF_ATTACHMENT -# ./fpdf_attachment.h: 46 if hasattr(_libs['pdfium'], 'FPDFDoc_GetAttachment'): FPDFDoc_GetAttachment = _libs['pdfium']['FPDFDoc_GetAttachment'] FPDFDoc_GetAttachment.argtypes = [FPDF_DOCUMENT, c_int] FPDFDoc_GetAttachment.restype = FPDF_ATTACHMENT -# ./fpdf_attachment.h: 59 if hasattr(_libs['pdfium'], 'FPDFDoc_DeleteAttachment'): FPDFDoc_DeleteAttachment = _libs['pdfium']['FPDFDoc_DeleteAttachment'] FPDFDoc_DeleteAttachment.argtypes = [FPDF_DOCUMENT, c_int] FPDFDoc_DeleteAttachment.restype = FPDF_BOOL -# ./fpdf_attachment.h: 72 if hasattr(_libs['pdfium'], 'FPDFAttachment_GetName'): FPDFAttachment_GetName = _libs['pdfium']['FPDFAttachment_GetName'] FPDFAttachment_GetName.argtypes = [FPDF_ATTACHMENT, POINTER(FPDF_WCHAR), c_ulong] FPDFAttachment_GetName.restype = c_ulong -# ./fpdf_attachment.h: 84 if hasattr(_libs['pdfium'], 'FPDFAttachment_HasKey'): FPDFAttachment_HasKey = _libs['pdfium']['FPDFAttachment_HasKey'] FPDFAttachment_HasKey.argtypes = [FPDF_ATTACHMENT, FPDF_BYTESTRING] FPDFAttachment_HasKey.restype = FPDF_BOOL -# ./fpdf_attachment.h: 95 if hasattr(_libs['pdfium'], 'FPDFAttachment_GetValueType'): FPDFAttachment_GetValueType = _libs['pdfium']['FPDFAttachment_GetValueType'] FPDFAttachment_GetValueType.argtypes = [FPDF_ATTACHMENT, FPDF_BYTESTRING] FPDFAttachment_GetValueType.restype = FPDF_OBJECT_TYPE -# ./fpdf_attachment.h: 108 if hasattr(_libs['pdfium'], 'FPDFAttachment_SetStringValue'): FPDFAttachment_SetStringValue = _libs['pdfium']['FPDFAttachment_SetStringValue'] FPDFAttachment_SetStringValue.argtypes = [FPDF_ATTACHMENT, FPDF_BYTESTRING, FPDF_WIDESTRING] FPDFAttachment_SetStringValue.restype = FPDF_BOOL -# ./fpdf_attachment.h: 129 if hasattr(_libs['pdfium'], 'FPDFAttachment_GetStringValue'): FPDFAttachment_GetStringValue = _libs['pdfium']['FPDFAttachment_GetStringValue'] FPDFAttachment_GetStringValue.argtypes = [FPDF_ATTACHMENT, FPDF_BYTESTRING, POINTER(FPDF_WCHAR), c_ulong] FPDFAttachment_GetStringValue.restype = c_ulong -# ./fpdf_attachment.h: 146 if hasattr(_libs['pdfium'], 'FPDFAttachment_SetFile'): FPDFAttachment_SetFile = _libs['pdfium']['FPDFAttachment_SetFile'] FPDFAttachment_SetFile.argtypes = [FPDF_ATTACHMENT, FPDF_DOCUMENT, POINTER(None), c_ulong] FPDFAttachment_SetFile.restype = FPDF_BOOL -# ./fpdf_attachment.h: 170 if hasattr(_libs['pdfium'], 'FPDFAttachment_GetFile'): FPDFAttachment_GetFile = _libs['pdfium']['FPDFAttachment_GetFile'] FPDFAttachment_GetFile.argtypes = [FPDF_ATTACHMENT, POINTER(None), c_ulong, POINTER(c_ulong)] FPDFAttachment_GetFile.restype = FPDF_BOOL -# ./fpdf_catalog.h: 26 if hasattr(_libs['pdfium'], 'FPDFCatalog_IsTagged'): FPDFCatalog_IsTagged = _libs['pdfium']['FPDFCatalog_IsTagged'] FPDFCatalog_IsTagged.argtypes = [FPDF_DOCUMENT] FPDFCatalog_IsTagged.restype = FPDF_BOOL -# ./fpdf_catalog.h: 36 if hasattr(_libs['pdfium'], 'FPDFCatalog_SetLanguage'): FPDFCatalog_SetLanguage = _libs['pdfium']['FPDFCatalog_SetLanguage'] FPDFCatalog_SetLanguage.argtypes = [FPDF_DOCUMENT, FPDF_BYTESTRING] FPDFCatalog_SetLanguage.restype = FPDF_BOOL -# ./fpdf_dataavail.h: 33 class struct__FX_FILEAVAIL (Structure): __slots__ = ['version', 'IsDataAvail'] @@ -1699,22 +1392,18 @@ class struct__FX_FILEAVAIL (Structure): ('IsDataAvail', CFUNCTYPE(FPDF_BOOL, POINTER(struct__FX_FILEAVAIL), c_size_t, c_size_t)), ] -# ./fpdf_dataavail.h: 52 FX_FILEAVAIL = struct__FX_FILEAVAIL -# ./fpdf_dataavail.h: 62 if hasattr(_libs['pdfium'], 'FPDFAvail_Create'): FPDFAvail_Create = _libs['pdfium']['FPDFAvail_Create'] FPDFAvail_Create.argtypes = [POINTER(FX_FILEAVAIL), POINTER(FPDF_FILEACCESS)] FPDFAvail_Create.restype = FPDF_AVAIL -# ./fpdf_dataavail.h: 68 if hasattr(_libs['pdfium'], 'FPDFAvail_Destroy'): FPDFAvail_Destroy = _libs['pdfium']['FPDFAvail_Destroy'] FPDFAvail_Destroy.argtypes = [FPDF_AVAIL] FPDFAvail_Destroy.restype = None -# ./fpdf_dataavail.h: 71 class struct__FX_DOWNLOADHINTS (Structure): __slots__ = ['version', 'AddSegment'] @@ -1723,241 +1412,195 @@ class struct__FX_DOWNLOADHINTS (Structure): ('AddSegment', CFUNCTYPE(None, POINTER(struct__FX_DOWNLOADHINTS), c_size_t, c_size_t)), ] -# ./fpdf_dataavail.h: 90 FX_DOWNLOADHINTS = struct__FX_DOWNLOADHINTS -# ./fpdf_dataavail.h: 109 if hasattr(_libs['pdfium'], 'FPDFAvail_IsDocAvail'): FPDFAvail_IsDocAvail = _libs['pdfium']['FPDFAvail_IsDocAvail'] FPDFAvail_IsDocAvail.argtypes = [FPDF_AVAIL, POINTER(FX_DOWNLOADHINTS)] FPDFAvail_IsDocAvail.restype = c_int -# ./fpdf_dataavail.h: 124 if hasattr(_libs['pdfium'], 'FPDFAvail_GetDocument'): FPDFAvail_GetDocument = _libs['pdfium']['FPDFAvail_GetDocument'] FPDFAvail_GetDocument.argtypes = [FPDF_AVAIL, FPDF_BYTESTRING] FPDFAvail_GetDocument.restype = FPDF_DOCUMENT -# ./fpdf_dataavail.h: 135 if hasattr(_libs['pdfium'], 'FPDFAvail_GetFirstPageNum'): FPDFAvail_GetFirstPageNum = _libs['pdfium']['FPDFAvail_GetFirstPageNum'] FPDFAvail_GetFirstPageNum.argtypes = [FPDF_DOCUMENT] FPDFAvail_GetFirstPageNum.restype = c_int -# ./fpdf_dataavail.h: 157 if hasattr(_libs['pdfium'], 'FPDFAvail_IsPageAvail'): FPDFAvail_IsPageAvail = _libs['pdfium']['FPDFAvail_IsPageAvail'] FPDFAvail_IsPageAvail.argtypes = [FPDF_AVAIL, c_int, POINTER(FX_DOWNLOADHINTS)] FPDFAvail_IsPageAvail.restype = c_int -# ./fpdf_dataavail.h: 182 if hasattr(_libs['pdfium'], 'FPDFAvail_IsFormAvail'): FPDFAvail_IsFormAvail = _libs['pdfium']['FPDFAvail_IsFormAvail'] FPDFAvail_IsFormAvail.argtypes = [FPDF_AVAIL, POINTER(FX_DOWNLOADHINTS)] FPDFAvail_IsFormAvail.restype = c_int -# ./fpdf_dataavail.h: 198 if hasattr(_libs['pdfium'], 'FPDFAvail_IsLinearized'): FPDFAvail_IsLinearized = _libs['pdfium']['FPDFAvail_IsLinearized'] FPDFAvail_IsLinearized.argtypes = [FPDF_AVAIL] FPDFAvail_IsLinearized.restype = c_int -# ./fpdf_doc.h: 46 enum_anon_5 = c_int -# ./fpdf_doc.h: 46 FILEIDTYPE_PERMANENT = 0 -# ./fpdf_doc.h: 46 FILEIDTYPE_CHANGING = 1 -# ./fpdf_doc.h: 46 FPDF_FILEIDTYPE = enum_anon_5 -# ./fpdf_doc.h: 59 if hasattr(_libs['pdfium'], 'FPDFBookmark_GetFirstChild'): FPDFBookmark_GetFirstChild = _libs['pdfium']['FPDFBookmark_GetFirstChild'] FPDFBookmark_GetFirstChild.argtypes = [FPDF_DOCUMENT, FPDF_BOOKMARK] FPDFBookmark_GetFirstChild.restype = FPDF_BOOKMARK -# ./fpdf_doc.h: 72 if hasattr(_libs['pdfium'], 'FPDFBookmark_GetNextSibling'): FPDFBookmark_GetNextSibling = _libs['pdfium']['FPDFBookmark_GetNextSibling'] FPDFBookmark_GetNextSibling.argtypes = [FPDF_DOCUMENT, FPDF_BOOKMARK] FPDFBookmark_GetNextSibling.restype = FPDF_BOOKMARK -# ./fpdf_doc.h: 88 if hasattr(_libs['pdfium'], 'FPDFBookmark_GetTitle'): FPDFBookmark_GetTitle = _libs['pdfium']['FPDFBookmark_GetTitle'] FPDFBookmark_GetTitle.argtypes = [FPDF_BOOKMARK, POINTER(None), c_ulong] FPDFBookmark_GetTitle.restype = c_ulong -# ./fpdf_doc.h: 102 if hasattr(_libs['pdfium'], 'FPDFBookmark_GetCount'): FPDFBookmark_GetCount = _libs['pdfium']['FPDFBookmark_GetCount'] FPDFBookmark_GetCount.argtypes = [FPDF_BOOKMARK] FPDFBookmark_GetCount.restype = c_int -# ./fpdf_doc.h: 114 if hasattr(_libs['pdfium'], 'FPDFBookmark_Find'): FPDFBookmark_Find = _libs['pdfium']['FPDFBookmark_Find'] FPDFBookmark_Find.argtypes = [FPDF_DOCUMENT, FPDF_WIDESTRING] FPDFBookmark_Find.restype = FPDF_BOOKMARK -# ./fpdf_doc.h: 124 if hasattr(_libs['pdfium'], 'FPDFBookmark_GetDest'): FPDFBookmark_GetDest = _libs['pdfium']['FPDFBookmark_GetDest'] FPDFBookmark_GetDest.argtypes = [FPDF_DOCUMENT, FPDF_BOOKMARK] FPDFBookmark_GetDest.restype = FPDF_DEST -# ./fpdf_doc.h: 137 if hasattr(_libs['pdfium'], 'FPDFBookmark_GetAction'): FPDFBookmark_GetAction = _libs['pdfium']['FPDFBookmark_GetAction'] FPDFBookmark_GetAction.argtypes = [FPDF_BOOKMARK] FPDFBookmark_GetAction.restype = FPDF_ACTION -# ./fpdf_doc.h: 149 if hasattr(_libs['pdfium'], 'FPDFAction_GetType'): FPDFAction_GetType = _libs['pdfium']['FPDFAction_GetType'] FPDFAction_GetType.argtypes = [FPDF_ACTION] FPDFAction_GetType.restype = c_ulong -# ./fpdf_doc.h: 163 if hasattr(_libs['pdfium'], 'FPDFAction_GetDest'): FPDFAction_GetDest = _libs['pdfium']['FPDFAction_GetDest'] FPDFAction_GetDest.argtypes = [FPDF_DOCUMENT, FPDF_ACTION] FPDFAction_GetDest.restype = FPDF_DEST -# ./fpdf_doc.h: 181 if hasattr(_libs['pdfium'], 'FPDFAction_GetFilePath'): FPDFAction_GetFilePath = _libs['pdfium']['FPDFAction_GetFilePath'] FPDFAction_GetFilePath.argtypes = [FPDF_ACTION, POINTER(None), c_ulong] FPDFAction_GetFilePath.restype = c_ulong -# ./fpdf_doc.h: 207 if hasattr(_libs['pdfium'], 'FPDFAction_GetURIPath'): FPDFAction_GetURIPath = _libs['pdfium']['FPDFAction_GetURIPath'] FPDFAction_GetURIPath.argtypes = [FPDF_DOCUMENT, FPDF_ACTION, POINTER(None), c_ulong] FPDFAction_GetURIPath.restype = c_ulong -# ./fpdf_doc.h: 218 if hasattr(_libs['pdfium'], 'FPDFDest_GetDestPageIndex'): FPDFDest_GetDestPageIndex = _libs['pdfium']['FPDFDest_GetDestPageIndex'] FPDFDest_GetDestPageIndex.argtypes = [FPDF_DOCUMENT, FPDF_DEST] FPDFDest_GetDestPageIndex.restype = c_int -# ./fpdf_doc.h: 231 if hasattr(_libs['pdfium'], 'FPDFDest_GetView'): FPDFDest_GetView = _libs['pdfium']['FPDFDest_GetView'] FPDFDest_GetView.argtypes = [FPDF_DEST, POINTER(c_ulong), POINTER(FS_FLOAT)] FPDFDest_GetView.restype = c_ulong -# ./fpdf_doc.h: 248 if hasattr(_libs['pdfium'], 'FPDFDest_GetLocationInPage'): FPDFDest_GetLocationInPage = _libs['pdfium']['FPDFDest_GetLocationInPage'] FPDFDest_GetLocationInPage.argtypes = [FPDF_DEST, POINTER(FPDF_BOOL), POINTER(FPDF_BOOL), POINTER(FPDF_BOOL), POINTER(FS_FLOAT), POINTER(FS_FLOAT), POINTER(FS_FLOAT)] FPDFDest_GetLocationInPage.restype = FPDF_BOOL -# ./fpdf_doc.h: 266 if hasattr(_libs['pdfium'], 'FPDFLink_GetLinkAtPoint'): FPDFLink_GetLinkAtPoint = _libs['pdfium']['FPDFLink_GetLinkAtPoint'] FPDFLink_GetLinkAtPoint.argtypes = [FPDF_PAGE, c_double, c_double] FPDFLink_GetLinkAtPoint.restype = FPDF_LINK -# ./fpdf_doc.h: 281 if hasattr(_libs['pdfium'], 'FPDFLink_GetLinkZOrderAtPoint'): FPDFLink_GetLinkZOrderAtPoint = _libs['pdfium']['FPDFLink_GetLinkZOrderAtPoint'] FPDFLink_GetLinkZOrderAtPoint.argtypes = [FPDF_PAGE, c_double, c_double] FPDFLink_GetLinkZOrderAtPoint.restype = c_int -# ./fpdf_doc.h: 293 if hasattr(_libs['pdfium'], 'FPDFLink_GetDest'): FPDFLink_GetDest = _libs['pdfium']['FPDFLink_GetDest'] FPDFLink_GetDest.argtypes = [FPDF_DOCUMENT, FPDF_LINK] FPDFLink_GetDest.restype = FPDF_DEST -# ./fpdf_doc.h: 303 if hasattr(_libs['pdfium'], 'FPDFLink_GetAction'): FPDFLink_GetAction = _libs['pdfium']['FPDFLink_GetAction'] FPDFLink_GetAction.argtypes = [FPDF_LINK] FPDFLink_GetAction.restype = FPDF_ACTION -# ./fpdf_doc.h: 313 if hasattr(_libs['pdfium'], 'FPDFLink_Enumerate'): FPDFLink_Enumerate = _libs['pdfium']['FPDFLink_Enumerate'] FPDFLink_Enumerate.argtypes = [FPDF_PAGE, POINTER(c_int), POINTER(FPDF_LINK)] FPDFLink_Enumerate.restype = FPDF_BOOL -# ./fpdf_doc.h: 326 if hasattr(_libs['pdfium'], 'FPDFLink_GetAnnot'): FPDFLink_GetAnnot = _libs['pdfium']['FPDFLink_GetAnnot'] FPDFLink_GetAnnot.argtypes = [FPDF_PAGE, FPDF_LINK] FPDFLink_GetAnnot.restype = FPDF_ANNOTATION -# ./fpdf_doc.h: 334 if hasattr(_libs['pdfium'], 'FPDFLink_GetAnnotRect'): FPDFLink_GetAnnotRect = _libs['pdfium']['FPDFLink_GetAnnotRect'] FPDFLink_GetAnnotRect.argtypes = [FPDF_LINK, POINTER(FS_RECTF)] FPDFLink_GetAnnotRect.restype = FPDF_BOOL -# ./fpdf_doc.h: 342 if hasattr(_libs['pdfium'], 'FPDFLink_CountQuadPoints'): FPDFLink_CountQuadPoints = _libs['pdfium']['FPDFLink_CountQuadPoints'] FPDFLink_CountQuadPoints.argtypes = [FPDF_LINK] FPDFLink_CountQuadPoints.restype = c_int -# ./fpdf_doc.h: 352 if hasattr(_libs['pdfium'], 'FPDFLink_GetQuadPoints'): FPDFLink_GetQuadPoints = _libs['pdfium']['FPDFLink_GetQuadPoints'] FPDFLink_GetQuadPoints.argtypes = [FPDF_LINK, c_int, POINTER(FS_QUADPOINTSF)] FPDFLink_GetQuadPoints.restype = FPDF_BOOL -# ./fpdf_doc.h: 367 if hasattr(_libs['pdfium'], 'FPDF_GetPageAAction'): FPDF_GetPageAAction = _libs['pdfium']['FPDF_GetPageAAction'] FPDF_GetPageAAction.argtypes = [FPDF_PAGE, c_int] FPDF_GetPageAAction.restype = FPDF_ACTION -# ./fpdf_doc.h: 385 if hasattr(_libs['pdfium'], 'FPDF_GetFileIdentifier'): FPDF_GetFileIdentifier = _libs['pdfium']['FPDF_GetFileIdentifier'] FPDF_GetFileIdentifier.argtypes = [FPDF_DOCUMENT, FPDF_FILEIDTYPE, POINTER(None), c_ulong] FPDF_GetFileIdentifier.restype = c_ulong -# ./fpdf_doc.h: 411 if hasattr(_libs['pdfium'], 'FPDF_GetMetaText'): FPDF_GetMetaText = _libs['pdfium']['FPDF_GetMetaText'] FPDF_GetMetaText.argtypes = [FPDF_DOCUMENT, FPDF_BYTESTRING, POINTER(None), c_ulong] FPDF_GetMetaText.restype = c_ulong -# ./fpdf_doc.h: 429 if hasattr(_libs['pdfium'], 'FPDF_GetPageLabel'): FPDF_GetPageLabel = _libs['pdfium']['FPDF_GetPageLabel'] FPDF_GetPageLabel.argtypes = [FPDF_DOCUMENT, c_int, POINTER(None), c_ulong] FPDF_GetPageLabel.restype = c_ulong -# /usr/include/x86_64-linux-gnu/bits/types.h: 38 __uint8_t = c_ubyte -# /usr/include/x86_64-linux-gnu/bits/types.h: 40 __uint16_t = c_ushort -# /usr/include/x86_64-linux-gnu/bits/types.h: 42 __uint32_t = c_uint -# /usr/include/x86_64-linux-gnu/bits/types.h: 160 __time_t = c_long -# /usr/include/x86_64-linux-gnu/bits/stdint-uintn.h: 24 uint8_t = __uint8_t -# /usr/include/x86_64-linux-gnu/bits/stdint-uintn.h: 25 uint16_t = __uint16_t -# /usr/include/x86_64-linux-gnu/bits/stdint-uintn.h: 26 uint32_t = __uint32_t -# ./fpdf_edit.h: 93 class struct_FPDF_IMAGEOBJ_METADATA (Structure): __slots__ = ['width', 'height', 'horizontal_dpi', 'vertical_dpi', 'bits_per_pixel', 'colorspace', 'marked_content_id'] @@ -1971,685 +1614,570 @@ class struct_FPDF_IMAGEOBJ_METADATA (Structure): ('marked_content_id', c_int), ] -# ./fpdf_edit.h: 93 FPDF_IMAGEOBJ_METADATA = struct_FPDF_IMAGEOBJ_METADATA -# ./fpdf_edit.h: 102 if hasattr(_libs['pdfium'], 'FPDF_CreateNewDocument'): FPDF_CreateNewDocument = _libs['pdfium']['FPDF_CreateNewDocument'] FPDF_CreateNewDocument.argtypes = [] FPDF_CreateNewDocument.restype = FPDF_DOCUMENT -# ./fpdf_edit.h: 117 if hasattr(_libs['pdfium'], 'FPDFPage_New'): FPDFPage_New = _libs['pdfium']['FPDFPage_New'] FPDFPage_New.argtypes = [FPDF_DOCUMENT, c_int, c_double, c_double] FPDFPage_New.restype = FPDF_PAGE -# ./fpdf_edit.h: 126 if hasattr(_libs['pdfium'], 'FPDFPage_Delete'): FPDFPage_Delete = _libs['pdfium']['FPDFPage_Delete'] FPDFPage_Delete.argtypes = [FPDF_DOCUMENT, c_int] FPDFPage_Delete.restype = None -# ./fpdf_edit.h: 156 if hasattr(_libs['pdfium'], 'FPDF_MovePages'): FPDF_MovePages = _libs['pdfium']['FPDF_MovePages'] FPDF_MovePages.argtypes = [FPDF_DOCUMENT, POINTER(c_int), c_ulong, c_int] FPDF_MovePages.restype = FPDF_BOOL -# ./fpdf_edit.h: 170 if hasattr(_libs['pdfium'], 'FPDFPage_GetRotation'): FPDFPage_GetRotation = _libs['pdfium']['FPDFPage_GetRotation'] FPDFPage_GetRotation.argtypes = [FPDF_PAGE] FPDFPage_GetRotation.restype = c_int -# ./fpdf_edit.h: 180 if hasattr(_libs['pdfium'], 'FPDFPage_SetRotation'): FPDFPage_SetRotation = _libs['pdfium']['FPDFPage_SetRotation'] FPDFPage_SetRotation.argtypes = [FPDF_PAGE, c_int] FPDFPage_SetRotation.restype = None -# ./fpdf_edit.h: 188 if hasattr(_libs['pdfium'], 'FPDFPage_InsertObject'): FPDFPage_InsertObject = _libs['pdfium']['FPDFPage_InsertObject'] FPDFPage_InsertObject.argtypes = [FPDF_PAGE, FPDF_PAGEOBJECT] FPDFPage_InsertObject.restype = None -# ./fpdf_edit.h: 203 if hasattr(_libs['pdfium'], 'FPDFPage_RemoveObject'): FPDFPage_RemoveObject = _libs['pdfium']['FPDFPage_RemoveObject'] FPDFPage_RemoveObject.argtypes = [FPDF_PAGE, FPDF_PAGEOBJECT] FPDFPage_RemoveObject.restype = FPDF_BOOL -# ./fpdf_edit.h: 210 if hasattr(_libs['pdfium'], 'FPDFPage_CountObjects'): FPDFPage_CountObjects = _libs['pdfium']['FPDFPage_CountObjects'] FPDFPage_CountObjects.argtypes = [FPDF_PAGE] FPDFPage_CountObjects.restype = c_int -# ./fpdf_edit.h: 218 if hasattr(_libs['pdfium'], 'FPDFPage_GetObject'): FPDFPage_GetObject = _libs['pdfium']['FPDFPage_GetObject'] FPDFPage_GetObject.argtypes = [FPDF_PAGE, c_int] FPDFPage_GetObject.restype = FPDF_PAGEOBJECT -# ./fpdf_edit.h: 226 if hasattr(_libs['pdfium'], 'FPDFPage_HasTransparency'): FPDFPage_HasTransparency = _libs['pdfium']['FPDFPage_HasTransparency'] FPDFPage_HasTransparency.argtypes = [FPDF_PAGE] FPDFPage_HasTransparency.restype = FPDF_BOOL -# ./fpdf_edit.h: 236 if hasattr(_libs['pdfium'], 'FPDFPage_GenerateContent'): FPDFPage_GenerateContent = _libs['pdfium']['FPDFPage_GenerateContent'] FPDFPage_GenerateContent.argtypes = [FPDF_PAGE] FPDFPage_GenerateContent.restype = FPDF_BOOL -# ./fpdf_edit.h: 245 if hasattr(_libs['pdfium'], 'FPDFPageObj_Destroy'): FPDFPageObj_Destroy = _libs['pdfium']['FPDFPageObj_Destroy'] FPDFPageObj_Destroy.argtypes = [FPDF_PAGEOBJECT] FPDFPageObj_Destroy.restype = None -# ./fpdf_edit.h: 253 if hasattr(_libs['pdfium'], 'FPDFPageObj_HasTransparency'): FPDFPageObj_HasTransparency = _libs['pdfium']['FPDFPageObj_HasTransparency'] FPDFPageObj_HasTransparency.argtypes = [FPDF_PAGEOBJECT] FPDFPageObj_HasTransparency.restype = FPDF_BOOL -# ./fpdf_edit.h: 261 if hasattr(_libs['pdfium'], 'FPDFPageObj_GetType'): FPDFPageObj_GetType = _libs['pdfium']['FPDFPageObj_GetType'] FPDFPageObj_GetType.argtypes = [FPDF_PAGEOBJECT] FPDFPageObj_GetType.restype = c_int -# ./fpdf_edit.h: 277 if hasattr(_libs['pdfium'], 'FPDFPageObj_GetIsActive'): FPDFPageObj_GetIsActive = _libs['pdfium']['FPDFPageObj_GetIsActive'] FPDFPageObj_GetIsActive.argtypes = [FPDF_PAGEOBJECT, POINTER(FPDF_BOOL)] FPDFPageObj_GetIsActive.restype = FPDF_BOOL -# ./fpdf_edit.h: 293 if hasattr(_libs['pdfium'], 'FPDFPageObj_SetIsActive'): FPDFPageObj_SetIsActive = _libs['pdfium']['FPDFPageObj_SetIsActive'] FPDFPageObj_SetIsActive.argtypes = [FPDF_PAGEOBJECT, FPDF_BOOL] FPDFPageObj_SetIsActive.restype = FPDF_BOOL -# ./fpdf_edit.h: 310 if hasattr(_libs['pdfium'], 'FPDFPageObj_Transform'): FPDFPageObj_Transform = _libs['pdfium']['FPDFPageObj_Transform'] FPDFPageObj_Transform.argtypes = [FPDF_PAGEOBJECT, c_double, c_double, c_double, c_double, c_double, c_double] FPDFPageObj_Transform.restype = None -# ./fpdf_edit.h: 331 if hasattr(_libs['pdfium'], 'FPDFPageObj_TransformF'): FPDFPageObj_TransformF = _libs['pdfium']['FPDFPageObj_TransformF'] FPDFPageObj_TransformF.argtypes = [FPDF_PAGEOBJECT, POINTER(FS_MATRIX)] FPDFPageObj_TransformF.restype = FPDF_BOOL -# ./fpdf_edit.h: 351 if hasattr(_libs['pdfium'], 'FPDFPageObj_GetMatrix'): FPDFPageObj_GetMatrix = _libs['pdfium']['FPDFPageObj_GetMatrix'] FPDFPageObj_GetMatrix.argtypes = [FPDF_PAGEOBJECT, POINTER(FS_MATRIX)] FPDFPageObj_GetMatrix.restype = FPDF_BOOL -# ./fpdf_edit.h: 366 if hasattr(_libs['pdfium'], 'FPDFPageObj_SetMatrix'): FPDFPageObj_SetMatrix = _libs['pdfium']['FPDFPageObj_SetMatrix'] FPDFPageObj_SetMatrix.argtypes = [FPDF_PAGEOBJECT, POINTER(FS_MATRIX)] FPDFPageObj_SetMatrix.restype = FPDF_BOOL -# ./fpdf_edit.h: 382 if hasattr(_libs['pdfium'], 'FPDFPage_TransformAnnots'): FPDFPage_TransformAnnots = _libs['pdfium']['FPDFPage_TransformAnnots'] FPDFPage_TransformAnnots.argtypes = [FPDF_PAGE, c_double, c_double, c_double, c_double, c_double, c_double] FPDFPage_TransformAnnots.restype = None -# ./fpdf_edit.h: 396 if hasattr(_libs['pdfium'], 'FPDFPageObj_NewImageObj'): FPDFPageObj_NewImageObj = _libs['pdfium']['FPDFPageObj_NewImageObj'] FPDFPageObj_NewImageObj.argtypes = [FPDF_DOCUMENT] FPDFPageObj_NewImageObj.restype = FPDF_PAGEOBJECT -# ./fpdf_edit.h: 405 if hasattr(_libs['pdfium'], 'FPDFPageObj_GetMarkedContentID'): FPDFPageObj_GetMarkedContentID = _libs['pdfium']['FPDFPageObj_GetMarkedContentID'] FPDFPageObj_GetMarkedContentID.argtypes = [FPDF_PAGEOBJECT] FPDFPageObj_GetMarkedContentID.restype = c_int -# ./fpdf_edit.h: 415 if hasattr(_libs['pdfium'], 'FPDFPageObj_CountMarks'): FPDFPageObj_CountMarks = _libs['pdfium']['FPDFPageObj_CountMarks'] FPDFPageObj_CountMarks.argtypes = [FPDF_PAGEOBJECT] FPDFPageObj_CountMarks.restype = c_int -# ./fpdf_edit.h: 428 if hasattr(_libs['pdfium'], 'FPDFPageObj_GetMark'): FPDFPageObj_GetMark = _libs['pdfium']['FPDFPageObj_GetMark'] FPDFPageObj_GetMark.argtypes = [FPDF_PAGEOBJECT, c_ulong] FPDFPageObj_GetMark.restype = FPDF_PAGEOBJECTMARK -# ./fpdf_edit.h: 441 if hasattr(_libs['pdfium'], 'FPDFPageObj_AddMark'): FPDFPageObj_AddMark = _libs['pdfium']['FPDFPageObj_AddMark'] FPDFPageObj_AddMark.argtypes = [FPDF_PAGEOBJECT, FPDF_BYTESTRING] FPDFPageObj_AddMark.restype = FPDF_PAGEOBJECTMARK -# ./fpdf_edit.h: 452 if hasattr(_libs['pdfium'], 'FPDFPageObj_RemoveMark'): FPDFPageObj_RemoveMark = _libs['pdfium']['FPDFPageObj_RemoveMark'] FPDFPageObj_RemoveMark.argtypes = [FPDF_PAGEOBJECT, FPDF_PAGEOBJECTMARK] FPDFPageObj_RemoveMark.restype = FPDF_BOOL -# ./fpdf_edit.h: 469 if hasattr(_libs['pdfium'], 'FPDFPageObjMark_GetName'): FPDFPageObjMark_GetName = _libs['pdfium']['FPDFPageObjMark_GetName'] FPDFPageObjMark_GetName.argtypes = [FPDF_PAGEOBJECTMARK, POINTER(FPDF_WCHAR), c_ulong, POINTER(c_ulong)] FPDFPageObjMark_GetName.restype = FPDF_BOOL -# ./fpdf_edit.h: 482 if hasattr(_libs['pdfium'], 'FPDFPageObjMark_CountParams'): FPDFPageObjMark_CountParams = _libs['pdfium']['FPDFPageObjMark_CountParams'] FPDFPageObjMark_CountParams.argtypes = [FPDF_PAGEOBJECTMARK] FPDFPageObjMark_CountParams.restype = c_int -# ./fpdf_edit.h: 500 if hasattr(_libs['pdfium'], 'FPDFPageObjMark_GetParamKey'): FPDFPageObjMark_GetParamKey = _libs['pdfium']['FPDFPageObjMark_GetParamKey'] FPDFPageObjMark_GetParamKey.argtypes = [FPDF_PAGEOBJECTMARK, c_ulong, POINTER(FPDF_WCHAR), c_ulong, POINTER(c_ulong)] FPDFPageObjMark_GetParamKey.restype = FPDF_BOOL -# ./fpdf_edit.h: 514 if hasattr(_libs['pdfium'], 'FPDFPageObjMark_GetParamValueType'): FPDFPageObjMark_GetParamValueType = _libs['pdfium']['FPDFPageObjMark_GetParamValueType'] FPDFPageObjMark_GetParamValueType.argtypes = [FPDF_PAGEOBJECTMARK, FPDF_BYTESTRING] FPDFPageObjMark_GetParamValueType.restype = FPDF_OBJECT_TYPE -# ./fpdf_edit.h: 529 if hasattr(_libs['pdfium'], 'FPDFPageObjMark_GetParamIntValue'): FPDFPageObjMark_GetParamIntValue = _libs['pdfium']['FPDFPageObjMark_GetParamIntValue'] FPDFPageObjMark_GetParamIntValue.argtypes = [FPDF_PAGEOBJECTMARK, FPDF_BYTESTRING, POINTER(c_int)] FPDFPageObjMark_GetParamIntValue.restype = FPDF_BOOL -# ./fpdf_edit.h: 549 if hasattr(_libs['pdfium'], 'FPDFPageObjMark_GetParamStringValue'): FPDFPageObjMark_GetParamStringValue = _libs['pdfium']['FPDFPageObjMark_GetParamStringValue'] FPDFPageObjMark_GetParamStringValue.argtypes = [FPDF_PAGEOBJECTMARK, FPDF_BYTESTRING, POINTER(FPDF_WCHAR), c_ulong, POINTER(c_ulong)] FPDFPageObjMark_GetParamStringValue.restype = FPDF_BOOL -# ./fpdf_edit.h: 571 if hasattr(_libs['pdfium'], 'FPDFPageObjMark_GetParamBlobValue'): FPDFPageObjMark_GetParamBlobValue = _libs['pdfium']['FPDFPageObjMark_GetParamBlobValue'] FPDFPageObjMark_GetParamBlobValue.argtypes = [FPDF_PAGEOBJECTMARK, FPDF_BYTESTRING, POINTER(c_ubyte), c_ulong, POINTER(c_ulong)] FPDFPageObjMark_GetParamBlobValue.restype = FPDF_BOOL -# ./fpdf_edit.h: 590 if hasattr(_libs['pdfium'], 'FPDFPageObjMark_SetIntParam'): FPDFPageObjMark_SetIntParam = _libs['pdfium']['FPDFPageObjMark_SetIntParam'] FPDFPageObjMark_SetIntParam.argtypes = [FPDF_DOCUMENT, FPDF_PAGEOBJECT, FPDF_PAGEOBJECTMARK, FPDF_BYTESTRING, c_int] FPDFPageObjMark_SetIntParam.restype = FPDF_BOOL -# ./fpdf_edit.h: 609 if hasattr(_libs['pdfium'], 'FPDFPageObjMark_SetStringParam'): FPDFPageObjMark_SetStringParam = _libs['pdfium']['FPDFPageObjMark_SetStringParam'] FPDFPageObjMark_SetStringParam.argtypes = [FPDF_DOCUMENT, FPDF_PAGEOBJECT, FPDF_PAGEOBJECTMARK, FPDF_BYTESTRING, FPDF_BYTESTRING] FPDFPageObjMark_SetStringParam.restype = FPDF_BOOL -# ./fpdf_edit.h: 629 if hasattr(_libs['pdfium'], 'FPDFPageObjMark_SetBlobParam'): FPDFPageObjMark_SetBlobParam = _libs['pdfium']['FPDFPageObjMark_SetBlobParam'] FPDFPageObjMark_SetBlobParam.argtypes = [FPDF_DOCUMENT, FPDF_PAGEOBJECT, FPDF_PAGEOBJECTMARK, FPDF_BYTESTRING, POINTER(c_ubyte), c_ulong] FPDFPageObjMark_SetBlobParam.restype = FPDF_BOOL -# ./fpdf_edit.h: 645 if hasattr(_libs['pdfium'], 'FPDFPageObjMark_RemoveParam'): FPDFPageObjMark_RemoveParam = _libs['pdfium']['FPDFPageObjMark_RemoveParam'] FPDFPageObjMark_RemoveParam.argtypes = [FPDF_PAGEOBJECT, FPDF_PAGEOBJECTMARK, FPDF_BYTESTRING] FPDFPageObjMark_RemoveParam.restype = FPDF_BOOL -# ./fpdf_edit.h: 664 if hasattr(_libs['pdfium'], 'FPDFImageObj_LoadJpegFile'): FPDFImageObj_LoadJpegFile = _libs['pdfium']['FPDFImageObj_LoadJpegFile'] FPDFImageObj_LoadJpegFile.argtypes = [POINTER(FPDF_PAGE), c_int, FPDF_PAGEOBJECT, POINTER(FPDF_FILEACCESS)] FPDFImageObj_LoadJpegFile.restype = FPDF_BOOL -# ./fpdf_edit.h: 686 if hasattr(_libs['pdfium'], 'FPDFImageObj_LoadJpegFileInline'): FPDFImageObj_LoadJpegFileInline = _libs['pdfium']['FPDFImageObj_LoadJpegFileInline'] FPDFImageObj_LoadJpegFileInline.argtypes = [POINTER(FPDF_PAGE), c_int, FPDF_PAGEOBJECT, POINTER(FPDF_FILEACCESS)] FPDFImageObj_LoadJpegFileInline.restype = FPDF_BOOL -# ./fpdf_edit.h: 710 if hasattr(_libs['pdfium'], 'FPDFImageObj_SetMatrix'): FPDFImageObj_SetMatrix = _libs['pdfium']['FPDFImageObj_SetMatrix'] FPDFImageObj_SetMatrix.argtypes = [FPDF_PAGEOBJECT, c_double, c_double, c_double, c_double, c_double, c_double] FPDFImageObj_SetMatrix.restype = FPDF_BOOL -# ./fpdf_edit.h: 727 if hasattr(_libs['pdfium'], 'FPDFImageObj_SetBitmap'): FPDFImageObj_SetBitmap = _libs['pdfium']['FPDFImageObj_SetBitmap'] FPDFImageObj_SetBitmap.argtypes = [POINTER(FPDF_PAGE), c_int, FPDF_PAGEOBJECT, FPDF_BITMAP] FPDFImageObj_SetBitmap.restype = FPDF_BOOL -# ./fpdf_edit.h: 742 if hasattr(_libs['pdfium'], 'FPDFImageObj_GetBitmap'): FPDFImageObj_GetBitmap = _libs['pdfium']['FPDFImageObj_GetBitmap'] FPDFImageObj_GetBitmap.argtypes = [FPDF_PAGEOBJECT] FPDFImageObj_GetBitmap.restype = FPDF_BITMAP -# ./fpdf_edit.h: 758 if hasattr(_libs['pdfium'], 'FPDFImageObj_GetRenderedBitmap'): FPDFImageObj_GetRenderedBitmap = _libs['pdfium']['FPDFImageObj_GetRenderedBitmap'] FPDFImageObj_GetRenderedBitmap.argtypes = [FPDF_DOCUMENT, FPDF_PAGE, FPDF_PAGEOBJECT] FPDFImageObj_GetRenderedBitmap.restype = FPDF_BITMAP -# ./fpdf_edit.h: 773 if hasattr(_libs['pdfium'], 'FPDFImageObj_GetImageDataDecoded'): FPDFImageObj_GetImageDataDecoded = _libs['pdfium']['FPDFImageObj_GetImageDataDecoded'] FPDFImageObj_GetImageDataDecoded.argtypes = [FPDF_PAGEOBJECT, POINTER(None), c_ulong] FPDFImageObj_GetImageDataDecoded.restype = c_ulong -# ./fpdf_edit.h: 787 if hasattr(_libs['pdfium'], 'FPDFImageObj_GetImageDataRaw'): FPDFImageObj_GetImageDataRaw = _libs['pdfium']['FPDFImageObj_GetImageDataRaw'] FPDFImageObj_GetImageDataRaw.argtypes = [FPDF_PAGEOBJECT, POINTER(None), c_ulong] FPDFImageObj_GetImageDataRaw.restype = c_ulong -# ./fpdf_edit.h: 797 if hasattr(_libs['pdfium'], 'FPDFImageObj_GetImageFilterCount'): FPDFImageObj_GetImageFilterCount = _libs['pdfium']['FPDFImageObj_GetImageFilterCount'] FPDFImageObj_GetImageFilterCount.argtypes = [FPDF_PAGEOBJECT] FPDFImageObj_GetImageFilterCount.restype = c_int -# ./fpdf_edit.h: 811 if hasattr(_libs['pdfium'], 'FPDFImageObj_GetImageFilter'): FPDFImageObj_GetImageFilter = _libs['pdfium']['FPDFImageObj_GetImageFilter'] FPDFImageObj_GetImageFilter.argtypes = [FPDF_PAGEOBJECT, c_int, POINTER(None), c_ulong] FPDFImageObj_GetImageFilter.restype = c_ulong -# ./fpdf_edit.h: 828 if hasattr(_libs['pdfium'], 'FPDFImageObj_GetImageMetadata'): FPDFImageObj_GetImageMetadata = _libs['pdfium']['FPDFImageObj_GetImageMetadata'] FPDFImageObj_GetImageMetadata.argtypes = [FPDF_PAGEOBJECT, FPDF_PAGE, POINTER(FPDF_IMAGEOBJ_METADATA)] FPDFImageObj_GetImageMetadata.restype = FPDF_BOOL -# ./fpdf_edit.h: 841 if hasattr(_libs['pdfium'], 'FPDFImageObj_GetImagePixelSize'): FPDFImageObj_GetImagePixelSize = _libs['pdfium']['FPDFImageObj_GetImagePixelSize'] FPDFImageObj_GetImagePixelSize.argtypes = [FPDF_PAGEOBJECT, POINTER(c_uint), POINTER(c_uint)] FPDFImageObj_GetImagePixelSize.restype = FPDF_BOOL -# ./fpdf_edit.h: 864 if hasattr(_libs['pdfium'], 'FPDFImageObj_GetIccProfileDataDecoded'): FPDFImageObj_GetIccProfileDataDecoded = _libs['pdfium']['FPDFImageObj_GetIccProfileDataDecoded'] FPDFImageObj_GetIccProfileDataDecoded.argtypes = [FPDF_PAGEOBJECT, FPDF_PAGE, POINTER(uint8_t), c_size_t, POINTER(c_size_t)] FPDFImageObj_GetIccProfileDataDecoded.restype = FPDF_BOOL -# ./fpdf_edit.h: 876 if hasattr(_libs['pdfium'], 'FPDFPageObj_CreateNewPath'): FPDFPageObj_CreateNewPath = _libs['pdfium']['FPDFPageObj_CreateNewPath'] FPDFPageObj_CreateNewPath.argtypes = [c_float, c_float] FPDFPageObj_CreateNewPath.restype = FPDF_PAGEOBJECT -# ./fpdf_edit.h: 887 if hasattr(_libs['pdfium'], 'FPDFPageObj_CreateNewRect'): FPDFPageObj_CreateNewRect = _libs['pdfium']['FPDFPageObj_CreateNewRect'] FPDFPageObj_CreateNewRect.argtypes = [c_float, c_float, c_float, c_float] FPDFPageObj_CreateNewRect.restype = FPDF_PAGEOBJECT -# ./fpdf_edit.h: 902 if hasattr(_libs['pdfium'], 'FPDFPageObj_GetBounds'): FPDFPageObj_GetBounds = _libs['pdfium']['FPDFPageObj_GetBounds'] FPDFPageObj_GetBounds.argtypes = [FPDF_PAGEOBJECT, POINTER(c_float), POINTER(c_float), POINTER(c_float), POINTER(c_float)] FPDFPageObj_GetBounds.restype = FPDF_BOOL -# ./fpdf_edit.h: 924 if hasattr(_libs['pdfium'], 'FPDFPageObj_GetRotatedBounds'): FPDFPageObj_GetRotatedBounds = _libs['pdfium']['FPDFPageObj_GetRotatedBounds'] FPDFPageObj_GetRotatedBounds.argtypes = [FPDF_PAGEOBJECT, POINTER(FS_QUADPOINTSF)] FPDFPageObj_GetRotatedBounds.restype = FPDF_BOOL -# ./fpdf_edit.h: 936 if hasattr(_libs['pdfium'], 'FPDFPageObj_SetBlendMode'): FPDFPageObj_SetBlendMode = _libs['pdfium']['FPDFPageObj_SetBlendMode'] FPDFPageObj_SetBlendMode.argtypes = [FPDF_PAGEOBJECT, FPDF_BYTESTRING] FPDFPageObj_SetBlendMode.restype = None -# ./fpdf_edit.h: 949 if hasattr(_libs['pdfium'], 'FPDFPageObj_SetStrokeColor'): FPDFPageObj_SetStrokeColor = _libs['pdfium']['FPDFPageObj_SetStrokeColor'] FPDFPageObj_SetStrokeColor.argtypes = [FPDF_PAGEOBJECT, c_uint, c_uint, c_uint, c_uint] FPDFPageObj_SetStrokeColor.restype = FPDF_BOOL -# ./fpdf_edit.h: 965 if hasattr(_libs['pdfium'], 'FPDFPageObj_GetStrokeColor'): FPDFPageObj_GetStrokeColor = _libs['pdfium']['FPDFPageObj_GetStrokeColor'] FPDFPageObj_GetStrokeColor.argtypes = [FPDF_PAGEOBJECT, POINTER(c_uint), POINTER(c_uint), POINTER(c_uint), POINTER(c_uint)] FPDFPageObj_GetStrokeColor.restype = FPDF_BOOL -# ./fpdf_edit.h: 978 if hasattr(_libs['pdfium'], 'FPDFPageObj_SetStrokeWidth'): FPDFPageObj_SetStrokeWidth = _libs['pdfium']['FPDFPageObj_SetStrokeWidth'] FPDFPageObj_SetStrokeWidth.argtypes = [FPDF_PAGEOBJECT, c_float] FPDFPageObj_SetStrokeWidth.restype = FPDF_BOOL -# ./fpdf_edit.h: 987 if hasattr(_libs['pdfium'], 'FPDFPageObj_GetStrokeWidth'): FPDFPageObj_GetStrokeWidth = _libs['pdfium']['FPDFPageObj_GetStrokeWidth'] FPDFPageObj_GetStrokeWidth.argtypes = [FPDF_PAGEOBJECT, POINTER(c_float)] FPDFPageObj_GetStrokeWidth.restype = FPDF_BOOL -# ./fpdf_edit.h: 997 if hasattr(_libs['pdfium'], 'FPDFPageObj_GetLineJoin'): FPDFPageObj_GetLineJoin = _libs['pdfium']['FPDFPageObj_GetLineJoin'] FPDFPageObj_GetLineJoin.argtypes = [FPDF_PAGEOBJECT] FPDFPageObj_GetLineJoin.restype = c_int -# ./fpdf_edit.h: 1007 if hasattr(_libs['pdfium'], 'FPDFPageObj_SetLineJoin'): FPDFPageObj_SetLineJoin = _libs['pdfium']['FPDFPageObj_SetLineJoin'] FPDFPageObj_SetLineJoin.argtypes = [FPDF_PAGEOBJECT, c_int] FPDFPageObj_SetLineJoin.restype = FPDF_BOOL -# ./fpdf_edit.h: 1017 if hasattr(_libs['pdfium'], 'FPDFPageObj_GetLineCap'): FPDFPageObj_GetLineCap = _libs['pdfium']['FPDFPageObj_GetLineCap'] FPDFPageObj_GetLineCap.argtypes = [FPDF_PAGEOBJECT] FPDFPageObj_GetLineCap.restype = c_int -# ./fpdf_edit.h: 1027 if hasattr(_libs['pdfium'], 'FPDFPageObj_SetLineCap'): FPDFPageObj_SetLineCap = _libs['pdfium']['FPDFPageObj_SetLineCap'] FPDFPageObj_SetLineCap.argtypes = [FPDF_PAGEOBJECT, c_int] FPDFPageObj_SetLineCap.restype = FPDF_BOOL -# ./fpdf_edit.h: 1039 if hasattr(_libs['pdfium'], 'FPDFPageObj_SetFillColor'): FPDFPageObj_SetFillColor = _libs['pdfium']['FPDFPageObj_SetFillColor'] FPDFPageObj_SetFillColor.argtypes = [FPDF_PAGEOBJECT, c_uint, c_uint, c_uint, c_uint] FPDFPageObj_SetFillColor.restype = FPDF_BOOL -# ./fpdf_edit.h: 1055 if hasattr(_libs['pdfium'], 'FPDFPageObj_GetFillColor'): FPDFPageObj_GetFillColor = _libs['pdfium']['FPDFPageObj_GetFillColor'] FPDFPageObj_GetFillColor.argtypes = [FPDF_PAGEOBJECT, POINTER(c_uint), POINTER(c_uint), POINTER(c_uint), POINTER(c_uint)] FPDFPageObj_GetFillColor.restype = FPDF_BOOL -# ./fpdf_edit.h: 1069 if hasattr(_libs['pdfium'], 'FPDFPageObj_GetDashPhase'): FPDFPageObj_GetDashPhase = _libs['pdfium']['FPDFPageObj_GetDashPhase'] FPDFPageObj_GetDashPhase.argtypes = [FPDF_PAGEOBJECT, POINTER(c_float)] FPDFPageObj_GetDashPhase.restype = FPDF_BOOL -# ./fpdf_edit.h: 1079 if hasattr(_libs['pdfium'], 'FPDFPageObj_SetDashPhase'): FPDFPageObj_SetDashPhase = _libs['pdfium']['FPDFPageObj_SetDashPhase'] FPDFPageObj_SetDashPhase.argtypes = [FPDF_PAGEOBJECT, c_float] FPDFPageObj_SetDashPhase.restype = FPDF_BOOL -# ./fpdf_edit.h: 1088 if hasattr(_libs['pdfium'], 'FPDFPageObj_GetDashCount'): FPDFPageObj_GetDashCount = _libs['pdfium']['FPDFPageObj_GetDashCount'] FPDFPageObj_GetDashCount.argtypes = [FPDF_PAGEOBJECT] FPDFPageObj_GetDashCount.restype = c_int -# ./fpdf_edit.h: 1099 if hasattr(_libs['pdfium'], 'FPDFPageObj_GetDashArray'): FPDFPageObj_GetDashArray = _libs['pdfium']['FPDFPageObj_GetDashArray'] FPDFPageObj_GetDashArray.argtypes = [FPDF_PAGEOBJECT, POINTER(c_float), c_size_t] FPDFPageObj_GetDashArray.restype = FPDF_BOOL -# ./fpdf_edit.h: 1113 if hasattr(_libs['pdfium'], 'FPDFPageObj_SetDashArray'): FPDFPageObj_SetDashArray = _libs['pdfium']['FPDFPageObj_SetDashArray'] FPDFPageObj_SetDashArray.argtypes = [FPDF_PAGEOBJECT, POINTER(c_float), c_size_t, c_float] FPDFPageObj_SetDashArray.restype = FPDF_BOOL -# ./fpdf_edit.h: 1126 if hasattr(_libs['pdfium'], 'FPDFPath_CountSegments'): FPDFPath_CountSegments = _libs['pdfium']['FPDFPath_CountSegments'] FPDFPath_CountSegments.argtypes = [FPDF_PAGEOBJECT] FPDFPath_CountSegments.restype = c_int -# ./fpdf_edit.h: 1135 if hasattr(_libs['pdfium'], 'FPDFPath_GetPathSegment'): FPDFPath_GetPathSegment = _libs['pdfium']['FPDFPath_GetPathSegment'] FPDFPath_GetPathSegment.argtypes = [FPDF_PAGEOBJECT, c_int] FPDFPath_GetPathSegment.restype = FPDF_PATHSEGMENT -# ./fpdf_edit.h: 1145 if hasattr(_libs['pdfium'], 'FPDFPathSegment_GetPoint'): FPDFPathSegment_GetPoint = _libs['pdfium']['FPDFPathSegment_GetPoint'] FPDFPathSegment_GetPoint.argtypes = [FPDF_PATHSEGMENT, POINTER(c_float), POINTER(c_float)] FPDFPathSegment_GetPoint.restype = FPDF_BOOL -# ./fpdf_edit.h: 1153 if hasattr(_libs['pdfium'], 'FPDFPathSegment_GetType'): FPDFPathSegment_GetType = _libs['pdfium']['FPDFPathSegment_GetType'] FPDFPathSegment_GetType.argtypes = [FPDF_PATHSEGMENT] FPDFPathSegment_GetType.restype = c_int -# ./fpdf_edit.h: 1161 if hasattr(_libs['pdfium'], 'FPDFPathSegment_GetClose'): FPDFPathSegment_GetClose = _libs['pdfium']['FPDFPathSegment_GetClose'] FPDFPathSegment_GetClose.argtypes = [FPDF_PATHSEGMENT] FPDFPathSegment_GetClose.restype = FPDF_BOOL -# ./fpdf_edit.h: 1173 if hasattr(_libs['pdfium'], 'FPDFPath_MoveTo'): FPDFPath_MoveTo = _libs['pdfium']['FPDFPath_MoveTo'] FPDFPath_MoveTo.argtypes = [FPDF_PAGEOBJECT, c_float, c_float] FPDFPath_MoveTo.restype = FPDF_BOOL -# ./fpdf_edit.h: 1186 if hasattr(_libs['pdfium'], 'FPDFPath_LineTo'): FPDFPath_LineTo = _libs['pdfium']['FPDFPath_LineTo'] FPDFPath_LineTo.argtypes = [FPDF_PAGEOBJECT, c_float, c_float] FPDFPath_LineTo.restype = FPDF_BOOL -# ./fpdf_edit.h: 1201 if hasattr(_libs['pdfium'], 'FPDFPath_BezierTo'): FPDFPath_BezierTo = _libs['pdfium']['FPDFPath_BezierTo'] FPDFPath_BezierTo.argtypes = [FPDF_PAGEOBJECT, c_float, c_float, c_float, c_float, c_float, c_float] FPDFPath_BezierTo.restype = FPDF_BOOL -# ./fpdf_edit.h: 1217 if hasattr(_libs['pdfium'], 'FPDFPath_Close'): FPDFPath_Close = _libs['pdfium']['FPDFPath_Close'] FPDFPath_Close.argtypes = [FPDF_PAGEOBJECT] FPDFPath_Close.restype = FPDF_BOOL -# ./fpdf_edit.h: 1226 if hasattr(_libs['pdfium'], 'FPDFPath_SetDrawMode'): FPDFPath_SetDrawMode = _libs['pdfium']['FPDFPath_SetDrawMode'] FPDFPath_SetDrawMode.argtypes = [FPDF_PAGEOBJECT, c_int, FPDF_BOOL] FPDFPath_SetDrawMode.restype = FPDF_BOOL -# ./fpdf_edit.h: 1237 if hasattr(_libs['pdfium'], 'FPDFPath_GetDrawMode'): FPDFPath_GetDrawMode = _libs['pdfium']['FPDFPath_GetDrawMode'] FPDFPath_GetDrawMode.argtypes = [FPDF_PAGEOBJECT, POINTER(c_int), POINTER(FPDF_BOOL)] FPDFPath_GetDrawMode.restype = FPDF_BOOL -# ./fpdf_edit.h: 1249 if hasattr(_libs['pdfium'], 'FPDFPageObj_NewTextObj'): FPDFPageObj_NewTextObj = _libs['pdfium']['FPDFPageObj_NewTextObj'] FPDFPageObj_NewTextObj.argtypes = [FPDF_DOCUMENT, FPDF_BYTESTRING, c_float] FPDFPageObj_NewTextObj.restype = FPDF_PAGEOBJECT -# ./fpdf_edit.h: 1260 if hasattr(_libs['pdfium'], 'FPDFText_SetText'): FPDFText_SetText = _libs['pdfium']['FPDFText_SetText'] FPDFText_SetText.argtypes = [FPDF_PAGEOBJECT, FPDF_WIDESTRING] FPDFText_SetText.restype = FPDF_BOOL -# ./fpdf_edit.h: 1272 if hasattr(_libs['pdfium'], 'FPDFText_SetCharcodes'): FPDFText_SetCharcodes = _libs['pdfium']['FPDFText_SetCharcodes'] FPDFText_SetCharcodes.argtypes = [FPDF_PAGEOBJECT, POINTER(uint32_t), c_size_t] FPDFText_SetCharcodes.restype = FPDF_BOOL -# ./fpdf_edit.h: 1289 if hasattr(_libs['pdfium'], 'FPDFText_LoadFont'): FPDFText_LoadFont = _libs['pdfium']['FPDFText_LoadFont'] FPDFText_LoadFont.argtypes = [FPDF_DOCUMENT, POINTER(uint8_t), uint32_t, c_int, FPDF_BOOL] FPDFText_LoadFont.restype = FPDF_FONT -# ./fpdf_edit.h: 1307 if hasattr(_libs['pdfium'], 'FPDFText_LoadStandardFont'): FPDFText_LoadStandardFont = _libs['pdfium']['FPDFText_LoadStandardFont'] FPDFText_LoadStandardFont.argtypes = [FPDF_DOCUMENT, FPDF_BYTESTRING] FPDFText_LoadStandardFont.restype = FPDF_FONT -# ./fpdf_edit.h: 1326 if hasattr(_libs['pdfium'], 'FPDFText_LoadCidType2Font'): FPDFText_LoadCidType2Font = _libs['pdfium']['FPDFText_LoadCidType2Font'] FPDFText_LoadCidType2Font.argtypes = [FPDF_DOCUMENT, POINTER(uint8_t), uint32_t, FPDF_BYTESTRING, POINTER(uint8_t), uint32_t] FPDFText_LoadCidType2Font.restype = FPDF_FONT -# ./fpdf_edit.h: 1341 if hasattr(_libs['pdfium'], 'FPDFTextObj_GetFontSize'): FPDFTextObj_GetFontSize = _libs['pdfium']['FPDFTextObj_GetFontSize'] FPDFTextObj_GetFontSize.argtypes = [FPDF_PAGEOBJECT, POINTER(c_float)] FPDFTextObj_GetFontSize.restype = FPDF_BOOL -# ./fpdf_edit.h: 1346 if hasattr(_libs['pdfium'], 'FPDFFont_Close'): FPDFFont_Close = _libs['pdfium']['FPDFFont_Close'] FPDFFont_Close.argtypes = [FPDF_FONT] FPDFFont_Close.restype = None -# ./fpdf_edit.h: 1356 if hasattr(_libs['pdfium'], 'FPDFPageObj_CreateTextObj'): FPDFPageObj_CreateTextObj = _libs['pdfium']['FPDFPageObj_CreateTextObj'] FPDFPageObj_CreateTextObj.argtypes = [FPDF_DOCUMENT, FPDF_FONT, c_float] FPDFPageObj_CreateTextObj.restype = FPDF_PAGEOBJECT -# ./fpdf_edit.h: 1367 if hasattr(_libs['pdfium'], 'FPDFTextObj_GetTextRenderMode'): FPDFTextObj_GetTextRenderMode = _libs['pdfium']['FPDFTextObj_GetTextRenderMode'] FPDFTextObj_GetTextRenderMode.argtypes = [FPDF_PAGEOBJECT] FPDFTextObj_GetTextRenderMode.restype = FPDF_TEXT_RENDERMODE -# ./fpdf_edit.h: 1378 if hasattr(_libs['pdfium'], 'FPDFTextObj_SetTextRenderMode'): FPDFTextObj_SetTextRenderMode = _libs['pdfium']['FPDFTextObj_SetTextRenderMode'] FPDFTextObj_SetTextRenderMode.argtypes = [FPDF_PAGEOBJECT, FPDF_TEXT_RENDERMODE] FPDFTextObj_SetTextRenderMode.restype = FPDF_BOOL -# ./fpdf_edit.h: 1395 if hasattr(_libs['pdfium'], 'FPDFTextObj_GetText'): FPDFTextObj_GetText = _libs['pdfium']['FPDFTextObj_GetText'] FPDFTextObj_GetText.argtypes = [FPDF_PAGEOBJECT, FPDF_TEXTPAGE, POINTER(FPDF_WCHAR), c_ulong] FPDFTextObj_GetText.restype = c_ulong -# ./fpdf_edit.h: 1414 if hasattr(_libs['pdfium'], 'FPDFTextObj_GetRenderedBitmap'): FPDFTextObj_GetRenderedBitmap = _libs['pdfium']['FPDFTextObj_GetRenderedBitmap'] FPDFTextObj_GetRenderedBitmap.argtypes = [FPDF_DOCUMENT, FPDF_PAGE, FPDF_PAGEOBJECT, c_float] FPDFTextObj_GetRenderedBitmap.restype = FPDF_BITMAP -# ./fpdf_edit.h: 1425 if hasattr(_libs['pdfium'], 'FPDFTextObj_GetFont'): FPDFTextObj_GetFont = _libs['pdfium']['FPDFTextObj_GetFont'] FPDFTextObj_GetFont.argtypes = [FPDF_PAGEOBJECT] FPDFTextObj_GetFont.restype = FPDF_FONT -# ./fpdf_edit.h: 1441 if hasattr(_libs['pdfium'], 'FPDFFont_GetBaseFontName'): FPDFFont_GetBaseFontName = _libs['pdfium']['FPDFFont_GetBaseFontName'] FPDFFont_GetBaseFontName.argtypes = [FPDF_FONT, POINTER(c_char), c_size_t] FPDFFont_GetBaseFontName.restype = c_size_t -# ./fpdf_edit.h: 1458 if hasattr(_libs['pdfium'], 'FPDFFont_GetFamilyName'): FPDFFont_GetFamilyName = _libs['pdfium']['FPDFFont_GetFamilyName'] FPDFFont_GetFamilyName.argtypes = [FPDF_FONT, POINTER(c_char), c_size_t] FPDFFont_GetFamilyName.restype = c_size_t -# ./fpdf_edit.h: 1481 if hasattr(_libs['pdfium'], 'FPDFFont_GetFontData'): FPDFFont_GetFontData = _libs['pdfium']['FPDFFont_GetFontData'] FPDFFont_GetFontData.argtypes = [FPDF_FONT, POINTER(uint8_t), c_size_t, POINTER(c_size_t)] FPDFFont_GetFontData.restype = FPDF_BOOL -# ./fpdf_edit.h: 1492 if hasattr(_libs['pdfium'], 'FPDFFont_GetIsEmbedded'): FPDFFont_GetIsEmbedded = _libs['pdfium']['FPDFFont_GetIsEmbedded'] FPDFFont_GetIsEmbedded.argtypes = [FPDF_FONT] FPDFFont_GetIsEmbedded.restype = c_int -# ./fpdf_edit.h: 1501 if hasattr(_libs['pdfium'], 'FPDFFont_GetFlags'): FPDFFont_GetFlags = _libs['pdfium']['FPDFFont_GetFlags'] FPDFFont_GetFlags.argtypes = [FPDF_FONT] FPDFFont_GetFlags.restype = c_int -# ./fpdf_edit.h: 1510 if hasattr(_libs['pdfium'], 'FPDFFont_GetWeight'): FPDFFont_GetWeight = _libs['pdfium']['FPDFFont_GetWeight'] FPDFFont_GetWeight.argtypes = [FPDF_FONT] FPDFFont_GetWeight.restype = c_int -# ./fpdf_edit.h: 1522 if hasattr(_libs['pdfium'], 'FPDFFont_GetItalicAngle'): FPDFFont_GetItalicAngle = _libs['pdfium']['FPDFFont_GetItalicAngle'] FPDFFont_GetItalicAngle.argtypes = [FPDF_FONT, POINTER(c_int)] FPDFFont_GetItalicAngle.restype = FPDF_BOOL -# ./fpdf_edit.h: 1536 if hasattr(_libs['pdfium'], 'FPDFFont_GetAscent'): FPDFFont_GetAscent = _libs['pdfium']['FPDFFont_GetAscent'] FPDFFont_GetAscent.argtypes = [FPDF_FONT, c_float, POINTER(c_float)] FPDFFont_GetAscent.restype = FPDF_BOOL -# ./fpdf_edit.h: 1551 if hasattr(_libs['pdfium'], 'FPDFFont_GetDescent'): FPDFFont_GetDescent = _libs['pdfium']['FPDFFont_GetDescent'] FPDFFont_GetDescent.argtypes = [FPDF_FONT, c_float, POINTER(c_float)] FPDFFont_GetDescent.restype = FPDF_BOOL -# ./fpdf_edit.h: 1567 if hasattr(_libs['pdfium'], 'FPDFFont_GetGlyphWidth'): FPDFFont_GetGlyphWidth = _libs['pdfium']['FPDFFont_GetGlyphWidth'] FPDFFont_GetGlyphWidth.argtypes = [FPDF_FONT, uint32_t, c_float, POINTER(c_float)] FPDFFont_GetGlyphWidth.restype = FPDF_BOOL -# ./fpdf_edit.h: 1580 if hasattr(_libs['pdfium'], 'FPDFFont_GetGlyphPath'): FPDFFont_GetGlyphPath = _libs['pdfium']['FPDFFont_GetGlyphPath'] FPDFFont_GetGlyphPath.argtypes = [FPDF_FONT, uint32_t, c_float] FPDFFont_GetGlyphPath.restype = FPDF_GLYPHPATH -# ./fpdf_edit.h: 1591 if hasattr(_libs['pdfium'], 'FPDFGlyphPath_CountGlyphSegments'): FPDFGlyphPath_CountGlyphSegments = _libs['pdfium']['FPDFGlyphPath_CountGlyphSegments'] FPDFGlyphPath_CountGlyphSegments.argtypes = [FPDF_GLYPHPATH] FPDFGlyphPath_CountGlyphSegments.restype = c_int -# ./fpdf_edit.h: 1601 if hasattr(_libs['pdfium'], 'FPDFGlyphPath_GetGlyphPathSegment'): FPDFGlyphPath_GetGlyphPathSegment = _libs['pdfium']['FPDFGlyphPath_GetGlyphPathSegment'] FPDFGlyphPath_GetGlyphPathSegment.argtypes = [FPDF_GLYPHPATH, c_int] FPDFGlyphPath_GetGlyphPathSegment.restype = FPDF_PATHSEGMENT -# ./fpdf_edit.h: 1609 if hasattr(_libs['pdfium'], 'FPDFFormObj_CountObjects'): FPDFFormObj_CountObjects = _libs['pdfium']['FPDFFormObj_CountObjects'] FPDFFormObj_CountObjects.argtypes = [FPDF_PAGEOBJECT] FPDFFormObj_CountObjects.restype = c_int -# ./fpdf_edit.h: 1618 if hasattr(_libs['pdfium'], 'FPDFFormObj_GetObject'): FPDFFormObj_GetObject = _libs['pdfium']['FPDFFormObj_GetObject'] FPDFFormObj_GetObject.argtypes = [FPDF_PAGEOBJECT, c_ulong] FPDFFormObj_GetObject.restype = FPDF_PAGEOBJECT -# /usr/include/x86_64-linux-gnu/bits/types/time_t.h: 10 time_t = __time_t -# /usr/include/x86_64-linux-gnu/bits/types/struct_tm.h: 7 class struct_tm (Structure): __slots__ = ['tm_sec', 'tm_min', 'tm_hour', 'tm_mday', 'tm_mon', 'tm_year', 'tm_wday', 'tm_yday', 'tm_isdst', 'tm_gmtoff', 'tm_zone'] @@ -2667,7 +2195,6 @@ class struct_tm (Structure): ('tm_zone', POINTER(c_char)), ] -# ./fpdf_ext.h: 51 class struct__UNSUPPORT_INFO (Structure): __slots__ = ['version', 'FSDK_UnSupport_Handler'] @@ -2676,658 +2203,457 @@ class struct__UNSUPPORT_INFO (Structure): ('FSDK_UnSupport_Handler', CFUNCTYPE(None, POINTER(struct__UNSUPPORT_INFO), c_int)), ] -# ./fpdf_ext.h: 62 UNSUPPORT_INFO = struct__UNSUPPORT_INFO -# ./fpdf_ext.h: 70 if hasattr(_libs['pdfium'], 'FSDK_SetUnSpObjProcessHandler'): FSDK_SetUnSpObjProcessHandler = _libs['pdfium']['FSDK_SetUnSpObjProcessHandler'] FSDK_SetUnSpObjProcessHandler.argtypes = [POINTER(UNSUPPORT_INFO)] FSDK_SetUnSpObjProcessHandler.restype = FPDF_BOOL -# ./fpdf_ext.h: 79 if hasattr(_libs['pdfium'], 'FSDK_SetTimeFunction'): FSDK_SetTimeFunction = _libs['pdfium']['FSDK_SetTimeFunction'] FSDK_SetTimeFunction.argtypes = [CFUNCTYPE(time_t, )] FSDK_SetTimeFunction.restype = None -# ./fpdf_ext.h: 89 if hasattr(_libs['pdfium'], 'FSDK_SetLocaltimeFunction'): FSDK_SetLocaltimeFunction = _libs['pdfium']['FSDK_SetLocaltimeFunction'] FSDK_SetLocaltimeFunction.argtypes = [CFUNCTYPE(POINTER(struct_tm), POINTER(time_t))] FSDK_SetLocaltimeFunction.restype = None -# ./fpdf_ext.h: 113 if hasattr(_libs['pdfium'], 'FPDFDoc_GetPageMode'): FPDFDoc_GetPageMode = _libs['pdfium']['FPDFDoc_GetPageMode'] FPDFDoc_GetPageMode.argtypes = [FPDF_DOCUMENT] FPDFDoc_GetPageMode.restype = c_int -# ./fpdf_flatten.h: 38 if hasattr(_libs['pdfium'], 'FPDFPage_Flatten'): FPDFPage_Flatten = _libs['pdfium']['FPDFPage_Flatten'] FPDFPage_Flatten.argtypes = [FPDF_PAGE, c_int] FPDFPage_Flatten.restype = c_int -# ./fpdf_fwlevent.h: 28 enum_anon_7 = c_int -# ./fpdf_fwlevent.h: 28 FWL_EVENTFLAG_ShiftKey = (1 << 0) -# ./fpdf_fwlevent.h: 28 FWL_EVENTFLAG_ControlKey = (1 << 1) -# ./fpdf_fwlevent.h: 28 FWL_EVENTFLAG_AltKey = (1 << 2) -# ./fpdf_fwlevent.h: 28 FWL_EVENTFLAG_MetaKey = (1 << 3) -# ./fpdf_fwlevent.h: 28 FWL_EVENTFLAG_KeyPad = (1 << 4) -# ./fpdf_fwlevent.h: 28 FWL_EVENTFLAG_AutoRepeat = (1 << 5) -# ./fpdf_fwlevent.h: 28 FWL_EVENTFLAG_LeftButtonDown = (1 << 6) -# ./fpdf_fwlevent.h: 28 FWL_EVENTFLAG_MiddleButtonDown = (1 << 7) -# ./fpdf_fwlevent.h: 28 FWL_EVENTFLAG_RightButtonDown = (1 << 8) -# ./fpdf_fwlevent.h: 28 FWL_EVENTFLAG = enum_anon_7 -# ./fpdf_fwlevent.h: 201 enum_anon_8 = c_int -# ./fpdf_fwlevent.h: 201 FWL_VKEY_Back = 0x08 -# ./fpdf_fwlevent.h: 201 FWL_VKEY_Tab = 0x09 -# ./fpdf_fwlevent.h: 201 FWL_VKEY_NewLine = 0x0A -# ./fpdf_fwlevent.h: 201 FWL_VKEY_Clear = 0x0C -# ./fpdf_fwlevent.h: 201 FWL_VKEY_Return = 0x0D -# ./fpdf_fwlevent.h: 201 FWL_VKEY_Shift = 0x10 -# ./fpdf_fwlevent.h: 201 FWL_VKEY_Control = 0x11 -# ./fpdf_fwlevent.h: 201 FWL_VKEY_Menu = 0x12 -# ./fpdf_fwlevent.h: 201 FWL_VKEY_Pause = 0x13 -# ./fpdf_fwlevent.h: 201 FWL_VKEY_Capital = 0x14 -# ./fpdf_fwlevent.h: 201 FWL_VKEY_Kana = 0x15 -# ./fpdf_fwlevent.h: 201 FWL_VKEY_Hangul = 0x15 -# ./fpdf_fwlevent.h: 201 FWL_VKEY_Junja = 0x17 -# ./fpdf_fwlevent.h: 201 FWL_VKEY_Final = 0x18 -# ./fpdf_fwlevent.h: 201 FWL_VKEY_Hanja = 0x19 -# ./fpdf_fwlevent.h: 201 FWL_VKEY_Kanji = 0x19 -# ./fpdf_fwlevent.h: 201 FWL_VKEY_Escape = 0x1B -# ./fpdf_fwlevent.h: 201 FWL_VKEY_Convert = 0x1C -# ./fpdf_fwlevent.h: 201 FWL_VKEY_NonConvert = 0x1D -# ./fpdf_fwlevent.h: 201 FWL_VKEY_Accept = 0x1E -# ./fpdf_fwlevent.h: 201 FWL_VKEY_ModeChange = 0x1F -# ./fpdf_fwlevent.h: 201 FWL_VKEY_Space = 0x20 -# ./fpdf_fwlevent.h: 201 FWL_VKEY_Prior = 0x21 -# ./fpdf_fwlevent.h: 201 FWL_VKEY_Next = 0x22 -# ./fpdf_fwlevent.h: 201 FWL_VKEY_End = 0x23 -# ./fpdf_fwlevent.h: 201 FWL_VKEY_Home = 0x24 -# ./fpdf_fwlevent.h: 201 FWL_VKEY_Left = 0x25 -# ./fpdf_fwlevent.h: 201 FWL_VKEY_Up = 0x26 -# ./fpdf_fwlevent.h: 201 FWL_VKEY_Right = 0x27 -# ./fpdf_fwlevent.h: 201 FWL_VKEY_Down = 0x28 -# ./fpdf_fwlevent.h: 201 FWL_VKEY_Select = 0x29 -# ./fpdf_fwlevent.h: 201 FWL_VKEY_Print = 0x2A -# ./fpdf_fwlevent.h: 201 FWL_VKEY_Execute = 0x2B -# ./fpdf_fwlevent.h: 201 FWL_VKEY_Snapshot = 0x2C -# ./fpdf_fwlevent.h: 201 FWL_VKEY_Insert = 0x2D -# ./fpdf_fwlevent.h: 201 FWL_VKEY_Delete = 0x2E -# ./fpdf_fwlevent.h: 201 FWL_VKEY_Help = 0x2F -# ./fpdf_fwlevent.h: 201 FWL_VKEY_0 = 0x30 -# ./fpdf_fwlevent.h: 201 FWL_VKEY_1 = 0x31 -# ./fpdf_fwlevent.h: 201 FWL_VKEY_2 = 0x32 -# ./fpdf_fwlevent.h: 201 FWL_VKEY_3 = 0x33 -# ./fpdf_fwlevent.h: 201 FWL_VKEY_4 = 0x34 -# ./fpdf_fwlevent.h: 201 FWL_VKEY_5 = 0x35 -# ./fpdf_fwlevent.h: 201 FWL_VKEY_6 = 0x36 -# ./fpdf_fwlevent.h: 201 FWL_VKEY_7 = 0x37 -# ./fpdf_fwlevent.h: 201 FWL_VKEY_8 = 0x38 -# ./fpdf_fwlevent.h: 201 FWL_VKEY_9 = 0x39 -# ./fpdf_fwlevent.h: 201 FWL_VKEY_A = 0x41 -# ./fpdf_fwlevent.h: 201 FWL_VKEY_B = 0x42 -# ./fpdf_fwlevent.h: 201 FWL_VKEY_C = 0x43 -# ./fpdf_fwlevent.h: 201 FWL_VKEY_D = 0x44 -# ./fpdf_fwlevent.h: 201 FWL_VKEY_E = 0x45 -# ./fpdf_fwlevent.h: 201 FWL_VKEY_F = 0x46 -# ./fpdf_fwlevent.h: 201 FWL_VKEY_G = 0x47 -# ./fpdf_fwlevent.h: 201 FWL_VKEY_H = 0x48 -# ./fpdf_fwlevent.h: 201 FWL_VKEY_I = 0x49 -# ./fpdf_fwlevent.h: 201 FWL_VKEY_J = 0x4A -# ./fpdf_fwlevent.h: 201 FWL_VKEY_K = 0x4B -# ./fpdf_fwlevent.h: 201 FWL_VKEY_L = 0x4C -# ./fpdf_fwlevent.h: 201 FWL_VKEY_M = 0x4D -# ./fpdf_fwlevent.h: 201 FWL_VKEY_N = 0x4E -# ./fpdf_fwlevent.h: 201 FWL_VKEY_O = 0x4F -# ./fpdf_fwlevent.h: 201 FWL_VKEY_P = 0x50 -# ./fpdf_fwlevent.h: 201 FWL_VKEY_Q = 0x51 -# ./fpdf_fwlevent.h: 201 FWL_VKEY_R = 0x52 -# ./fpdf_fwlevent.h: 201 FWL_VKEY_S = 0x53 -# ./fpdf_fwlevent.h: 201 FWL_VKEY_T = 0x54 -# ./fpdf_fwlevent.h: 201 FWL_VKEY_U = 0x55 -# ./fpdf_fwlevent.h: 201 FWL_VKEY_V = 0x56 -# ./fpdf_fwlevent.h: 201 FWL_VKEY_W = 0x57 -# ./fpdf_fwlevent.h: 201 FWL_VKEY_X = 0x58 -# ./fpdf_fwlevent.h: 201 FWL_VKEY_Y = 0x59 -# ./fpdf_fwlevent.h: 201 FWL_VKEY_Z = 0x5A -# ./fpdf_fwlevent.h: 201 FWL_VKEY_LWin = 0x5B -# ./fpdf_fwlevent.h: 201 FWL_VKEY_Command = 0x5B -# ./fpdf_fwlevent.h: 201 FWL_VKEY_RWin = 0x5C -# ./fpdf_fwlevent.h: 201 FWL_VKEY_Apps = 0x5D -# ./fpdf_fwlevent.h: 201 FWL_VKEY_Sleep = 0x5F -# ./fpdf_fwlevent.h: 201 FWL_VKEY_NumPad0 = 0x60 -# ./fpdf_fwlevent.h: 201 FWL_VKEY_NumPad1 = 0x61 -# ./fpdf_fwlevent.h: 201 FWL_VKEY_NumPad2 = 0x62 -# ./fpdf_fwlevent.h: 201 FWL_VKEY_NumPad3 = 0x63 -# ./fpdf_fwlevent.h: 201 FWL_VKEY_NumPad4 = 0x64 -# ./fpdf_fwlevent.h: 201 FWL_VKEY_NumPad5 = 0x65 -# ./fpdf_fwlevent.h: 201 FWL_VKEY_NumPad6 = 0x66 -# ./fpdf_fwlevent.h: 201 FWL_VKEY_NumPad7 = 0x67 -# ./fpdf_fwlevent.h: 201 FWL_VKEY_NumPad8 = 0x68 -# ./fpdf_fwlevent.h: 201 FWL_VKEY_NumPad9 = 0x69 -# ./fpdf_fwlevent.h: 201 FWL_VKEY_Multiply = 0x6A -# ./fpdf_fwlevent.h: 201 FWL_VKEY_Add = 0x6B -# ./fpdf_fwlevent.h: 201 FWL_VKEY_Separator = 0x6C -# ./fpdf_fwlevent.h: 201 FWL_VKEY_Subtract = 0x6D -# ./fpdf_fwlevent.h: 201 FWL_VKEY_Decimal = 0x6E -# ./fpdf_fwlevent.h: 201 FWL_VKEY_Divide = 0x6F -# ./fpdf_fwlevent.h: 201 FWL_VKEY_F1 = 0x70 -# ./fpdf_fwlevent.h: 201 FWL_VKEY_F2 = 0x71 -# ./fpdf_fwlevent.h: 201 FWL_VKEY_F3 = 0x72 -# ./fpdf_fwlevent.h: 201 FWL_VKEY_F4 = 0x73 -# ./fpdf_fwlevent.h: 201 FWL_VKEY_F5 = 0x74 -# ./fpdf_fwlevent.h: 201 FWL_VKEY_F6 = 0x75 -# ./fpdf_fwlevent.h: 201 FWL_VKEY_F7 = 0x76 -# ./fpdf_fwlevent.h: 201 FWL_VKEY_F8 = 0x77 -# ./fpdf_fwlevent.h: 201 FWL_VKEY_F9 = 0x78 -# ./fpdf_fwlevent.h: 201 FWL_VKEY_F10 = 0x79 -# ./fpdf_fwlevent.h: 201 FWL_VKEY_F11 = 0x7A -# ./fpdf_fwlevent.h: 201 FWL_VKEY_F12 = 0x7B -# ./fpdf_fwlevent.h: 201 FWL_VKEY_F13 = 0x7C -# ./fpdf_fwlevent.h: 201 FWL_VKEY_F14 = 0x7D -# ./fpdf_fwlevent.h: 201 FWL_VKEY_F15 = 0x7E -# ./fpdf_fwlevent.h: 201 FWL_VKEY_F16 = 0x7F -# ./fpdf_fwlevent.h: 201 FWL_VKEY_F17 = 0x80 -# ./fpdf_fwlevent.h: 201 FWL_VKEY_F18 = 0x81 -# ./fpdf_fwlevent.h: 201 FWL_VKEY_F19 = 0x82 -# ./fpdf_fwlevent.h: 201 FWL_VKEY_F20 = 0x83 -# ./fpdf_fwlevent.h: 201 FWL_VKEY_F21 = 0x84 -# ./fpdf_fwlevent.h: 201 FWL_VKEY_F22 = 0x85 -# ./fpdf_fwlevent.h: 201 FWL_VKEY_F23 = 0x86 -# ./fpdf_fwlevent.h: 201 FWL_VKEY_F24 = 0x87 -# ./fpdf_fwlevent.h: 201 FWL_VKEY_NunLock = 0x90 -# ./fpdf_fwlevent.h: 201 FWL_VKEY_Scroll = 0x91 -# ./fpdf_fwlevent.h: 201 FWL_VKEY_LShift = 0xA0 -# ./fpdf_fwlevent.h: 201 FWL_VKEY_RShift = 0xA1 -# ./fpdf_fwlevent.h: 201 FWL_VKEY_LControl = 0xA2 -# ./fpdf_fwlevent.h: 201 FWL_VKEY_RControl = 0xA3 -# ./fpdf_fwlevent.h: 201 FWL_VKEY_LMenu = 0xA4 -# ./fpdf_fwlevent.h: 201 FWL_VKEY_RMenu = 0xA5 -# ./fpdf_fwlevent.h: 201 FWL_VKEY_BROWSER_Back = 0xA6 -# ./fpdf_fwlevent.h: 201 FWL_VKEY_BROWSER_Forward = 0xA7 -# ./fpdf_fwlevent.h: 201 FWL_VKEY_BROWSER_Refresh = 0xA8 -# ./fpdf_fwlevent.h: 201 FWL_VKEY_BROWSER_Stop = 0xA9 -# ./fpdf_fwlevent.h: 201 FWL_VKEY_BROWSER_Search = 0xAA -# ./fpdf_fwlevent.h: 201 FWL_VKEY_BROWSER_Favorites = 0xAB -# ./fpdf_fwlevent.h: 201 FWL_VKEY_BROWSER_Home = 0xAC -# ./fpdf_fwlevent.h: 201 FWL_VKEY_VOLUME_Mute = 0xAD -# ./fpdf_fwlevent.h: 201 FWL_VKEY_VOLUME_Down = 0xAE -# ./fpdf_fwlevent.h: 201 FWL_VKEY_VOLUME_Up = 0xAF -# ./fpdf_fwlevent.h: 201 FWL_VKEY_MEDIA_NEXT_Track = 0xB0 -# ./fpdf_fwlevent.h: 201 FWL_VKEY_MEDIA_PREV_Track = 0xB1 -# ./fpdf_fwlevent.h: 201 FWL_VKEY_MEDIA_Stop = 0xB2 -# ./fpdf_fwlevent.h: 201 FWL_VKEY_MEDIA_PLAY_Pause = 0xB3 -# ./fpdf_fwlevent.h: 201 FWL_VKEY_MEDIA_LAUNCH_Mail = 0xB4 -# ./fpdf_fwlevent.h: 201 FWL_VKEY_MEDIA_LAUNCH_MEDIA_Select = 0xB5 -# ./fpdf_fwlevent.h: 201 FWL_VKEY_MEDIA_LAUNCH_APP1 = 0xB6 -# ./fpdf_fwlevent.h: 201 FWL_VKEY_MEDIA_LAUNCH_APP2 = 0xB7 -# ./fpdf_fwlevent.h: 201 FWL_VKEY_OEM_1 = 0xBA -# ./fpdf_fwlevent.h: 201 FWL_VKEY_OEM_Plus = 0xBB -# ./fpdf_fwlevent.h: 201 FWL_VKEY_OEM_Comma = 0xBC -# ./fpdf_fwlevent.h: 201 FWL_VKEY_OEM_Minus = 0xBD -# ./fpdf_fwlevent.h: 201 FWL_VKEY_OEM_Period = 0xBE -# ./fpdf_fwlevent.h: 201 FWL_VKEY_OEM_2 = 0xBF -# ./fpdf_fwlevent.h: 201 FWL_VKEY_OEM_3 = 0xC0 -# ./fpdf_fwlevent.h: 201 FWL_VKEY_OEM_4 = 0xDB -# ./fpdf_fwlevent.h: 201 FWL_VKEY_OEM_5 = 0xDC -# ./fpdf_fwlevent.h: 201 FWL_VKEY_OEM_6 = 0xDD -# ./fpdf_fwlevent.h: 201 FWL_VKEY_OEM_7 = 0xDE -# ./fpdf_fwlevent.h: 201 FWL_VKEY_OEM_8 = 0xDF -# ./fpdf_fwlevent.h: 201 FWL_VKEY_OEM_102 = 0xE2 -# ./fpdf_fwlevent.h: 201 FWL_VKEY_ProcessKey = 0xE5 -# ./fpdf_fwlevent.h: 201 FWL_VKEY_Packet = 0xE7 -# ./fpdf_fwlevent.h: 201 FWL_VKEY_Attn = 0xF6 -# ./fpdf_fwlevent.h: 201 FWL_VKEY_Crsel = 0xF7 -# ./fpdf_fwlevent.h: 201 FWL_VKEY_Exsel = 0xF8 -# ./fpdf_fwlevent.h: 201 FWL_VKEY_Ereof = 0xF9 -# ./fpdf_fwlevent.h: 201 FWL_VKEY_Play = 0xFA -# ./fpdf_fwlevent.h: 201 FWL_VKEY_Zoom = 0xFB -# ./fpdf_fwlevent.h: 201 FWL_VKEY_NoName = 0xFC -# ./fpdf_fwlevent.h: 201 FWL_VKEY_PA1 = 0xFD -# ./fpdf_fwlevent.h: 201 FWL_VKEY_OEM_Clear = 0xFE -# ./fpdf_fwlevent.h: 201 FWL_VKEY_Unknown = 0 -# ./fpdf_fwlevent.h: 201 FWL_VKEYCODE = enum_anon_8 -# ./fpdf_javascript.h: 22 if hasattr(_libs['pdfium'], 'FPDFDoc_GetJavaScriptActionCount'): FPDFDoc_GetJavaScriptActionCount = _libs['pdfium']['FPDFDoc_GetJavaScriptActionCount'] FPDFDoc_GetJavaScriptActionCount.argtypes = [FPDF_DOCUMENT] FPDFDoc_GetJavaScriptActionCount.restype = c_int -# ./fpdf_javascript.h: 34 if hasattr(_libs['pdfium'], 'FPDFDoc_GetJavaScriptAction'): FPDFDoc_GetJavaScriptAction = _libs['pdfium']['FPDFDoc_GetJavaScriptAction'] FPDFDoc_GetJavaScriptAction.argtypes = [FPDF_DOCUMENT, c_int] FPDFDoc_GetJavaScriptAction.restype = FPDF_JAVASCRIPT_ACTION -# ./fpdf_javascript.h: 41 if hasattr(_libs['pdfium'], 'FPDFDoc_CloseJavaScriptAction'): FPDFDoc_CloseJavaScriptAction = _libs['pdfium']['FPDFDoc_CloseJavaScriptAction'] FPDFDoc_CloseJavaScriptAction.argtypes = [FPDF_JAVASCRIPT_ACTION] FPDFDoc_CloseJavaScriptAction.restype = None -# ./fpdf_javascript.h: 54 if hasattr(_libs['pdfium'], 'FPDFJavaScriptAction_GetName'): FPDFJavaScriptAction_GetName = _libs['pdfium']['FPDFJavaScriptAction_GetName'] FPDFJavaScriptAction_GetName.argtypes = [FPDF_JAVASCRIPT_ACTION, POINTER(FPDF_WCHAR), c_ulong] FPDFJavaScriptAction_GetName.restype = c_ulong -# ./fpdf_javascript.h: 69 if hasattr(_libs['pdfium'], 'FPDFJavaScriptAction_GetScript'): FPDFJavaScriptAction_GetScript = _libs['pdfium']['FPDFJavaScriptAction_GetScript'] FPDFJavaScriptAction_GetScript.argtypes = [FPDF_JAVASCRIPT_ACTION, POINTER(FPDF_WCHAR), c_ulong] FPDFJavaScriptAction_GetScript.restype = c_ulong -# ./fpdf_ppo.h: 32 if hasattr(_libs['pdfium'], 'FPDF_ImportPagesByIndex'): FPDF_ImportPagesByIndex = _libs['pdfium']['FPDF_ImportPagesByIndex'] FPDF_ImportPagesByIndex.argtypes = [FPDF_DOCUMENT, FPDF_DOCUMENT, POINTER(c_int), c_ulong, c_int] FPDF_ImportPagesByIndex.restype = FPDF_BOOL -# ./fpdf_ppo.h: 49 if hasattr(_libs['pdfium'], 'FPDF_ImportPages'): FPDF_ImportPages = _libs['pdfium']['FPDF_ImportPages'] FPDF_ImportPages.argtypes = [FPDF_DOCUMENT, FPDF_DOCUMENT, FPDF_BYTESTRING, c_int] FPDF_ImportPages.restype = FPDF_BOOL -# ./fpdf_ppo.h: 72 if hasattr(_libs['pdfium'], 'FPDF_ImportNPagesToOne'): FPDF_ImportNPagesToOne = _libs['pdfium']['FPDF_ImportNPagesToOne'] FPDF_ImportNPagesToOne.argtypes = [FPDF_DOCUMENT, c_float, c_float, c_size_t, c_size_t] FPDF_ImportNPagesToOne.restype = FPDF_DOCUMENT -# ./fpdf_ppo.h: 85 if hasattr(_libs['pdfium'], 'FPDF_NewXObjectFromPage'): FPDF_NewXObjectFromPage = _libs['pdfium']['FPDF_NewXObjectFromPage'] FPDF_NewXObjectFromPage.argtypes = [FPDF_DOCUMENT, FPDF_DOCUMENT, c_int] FPDF_NewXObjectFromPage.restype = FPDF_XOBJECT -# ./fpdf_ppo.h: 92 if hasattr(_libs['pdfium'], 'FPDF_CloseXObject'): FPDF_CloseXObject = _libs['pdfium']['FPDF_CloseXObject'] FPDF_CloseXObject.argtypes = [FPDF_XOBJECT] FPDF_CloseXObject.restype = None -# ./fpdf_ppo.h: 100 if hasattr(_libs['pdfium'], 'FPDF_NewFormObjectFromXObject'): FPDF_NewFormObjectFromXObject = _libs['pdfium']['FPDF_NewFormObjectFromXObject'] FPDF_NewFormObjectFromXObject.argtypes = [FPDF_XOBJECT] FPDF_NewFormObjectFromXObject.restype = FPDF_PAGEOBJECT -# ./fpdf_ppo.h: 109 if hasattr(_libs['pdfium'], 'FPDF_CopyViewerPreferences'): FPDF_CopyViewerPreferences = _libs['pdfium']['FPDF_CopyViewerPreferences'] FPDF_CopyViewerPreferences.argtypes = [FPDF_DOCUMENT, FPDF_DOCUMENT] FPDF_CopyViewerPreferences.restype = FPDF_BOOL -# ./fpdf_progressive.h: 25 class struct__IFSDK_PAUSE (Structure): __slots__ = ['version', 'NeedToPauseNow', 'user'] @@ -3337,34 +2663,28 @@ class struct__IFSDK_PAUSE (Structure): ('user', POINTER(None)), ] -# ./fpdf_progressive.h: 43 IFSDK_PAUSE = struct__IFSDK_PAUSE -# ./fpdf_progressive.h: 79 if hasattr(_libs['pdfium'], 'FPDF_RenderPageBitmapWithColorScheme_Start'): FPDF_RenderPageBitmapWithColorScheme_Start = _libs['pdfium']['FPDF_RenderPageBitmapWithColorScheme_Start'] FPDF_RenderPageBitmapWithColorScheme_Start.argtypes = [FPDF_BITMAP, FPDF_PAGE, c_int, c_int, c_int, c_int, c_int, c_int, POINTER(FPDF_COLORSCHEME), POINTER(IFSDK_PAUSE)] FPDF_RenderPageBitmapWithColorScheme_Start.restype = c_int -# ./fpdf_progressive.h: 117 if hasattr(_libs['pdfium'], 'FPDF_RenderPageBitmap_Start'): FPDF_RenderPageBitmap_Start = _libs['pdfium']['FPDF_RenderPageBitmap_Start'] FPDF_RenderPageBitmap_Start.argtypes = [FPDF_BITMAP, FPDF_PAGE, c_int, c_int, c_int, c_int, c_int, c_int, POINTER(IFSDK_PAUSE)] FPDF_RenderPageBitmap_Start.restype = c_int -# ./fpdf_progressive.h: 138 if hasattr(_libs['pdfium'], 'FPDF_RenderPage_Continue'): FPDF_RenderPage_Continue = _libs['pdfium']['FPDF_RenderPage_Continue'] FPDF_RenderPage_Continue.argtypes = [FPDF_PAGE, POINTER(IFSDK_PAUSE)] FPDF_RenderPage_Continue.restype = c_int -# ./fpdf_progressive.h: 149 if hasattr(_libs['pdfium'], 'FPDF_RenderPage_Close'): FPDF_RenderPage_Close = _libs['pdfium']['FPDF_RenderPage_Close'] FPDF_RenderPage_Close.argtypes = [FPDF_PAGE] FPDF_RenderPage_Close.restype = None -# ./fpdf_save.h: 19 class struct_FPDF_FILEWRITE_ (Structure): __slots__ = ['version', 'WriteBlock'] @@ -3373,268 +2693,223 @@ class struct_FPDF_FILEWRITE_ (Structure): ('WriteBlock', CFUNCTYPE(c_int, POINTER(struct_FPDF_FILEWRITE_), POINTER(None), c_ulong)), ] -# ./fpdf_save.h: 42 FPDF_FILEWRITE = struct_FPDF_FILEWRITE_ -# ./fpdf_save.h: 59 if hasattr(_libs['pdfium'], 'FPDF_SaveAsCopy'): FPDF_SaveAsCopy = _libs['pdfium']['FPDF_SaveAsCopy'] FPDF_SaveAsCopy.argtypes = [FPDF_DOCUMENT, POINTER(FPDF_FILEWRITE), FPDF_DWORD] FPDF_SaveAsCopy.restype = FPDF_BOOL -# ./fpdf_save.h: 76 if hasattr(_libs['pdfium'], 'FPDF_SaveWithVersion'): FPDF_SaveWithVersion = _libs['pdfium']['FPDF_SaveWithVersion'] FPDF_SaveWithVersion.argtypes = [FPDF_DOCUMENT, POINTER(FPDF_FILEWRITE), FPDF_DWORD, c_int] FPDF_SaveWithVersion.restype = FPDF_BOOL -# ./fpdf_searchex.h: 24 if hasattr(_libs['pdfium'], 'FPDFText_GetCharIndexFromTextIndex'): FPDFText_GetCharIndexFromTextIndex = _libs['pdfium']['FPDFText_GetCharIndexFromTextIndex'] FPDFText_GetCharIndexFromTextIndex.argtypes = [FPDF_TEXTPAGE, c_int] FPDFText_GetCharIndexFromTextIndex.restype = c_int -# ./fpdf_searchex.h: 33 if hasattr(_libs['pdfium'], 'FPDFText_GetTextIndexFromCharIndex'): FPDFText_GetTextIndexFromCharIndex = _libs['pdfium']['FPDFText_GetTextIndexFromCharIndex'] FPDFText_GetTextIndexFromCharIndex.argtypes = [FPDF_TEXTPAGE, c_int] FPDFText_GetTextIndexFromCharIndex.restype = c_int -# ./fpdf_signature.h: 22 if hasattr(_libs['pdfium'], 'FPDF_GetSignatureCount'): FPDF_GetSignatureCount = _libs['pdfium']['FPDF_GetSignatureCount'] FPDF_GetSignatureCount.argtypes = [FPDF_DOCUMENT] FPDF_GetSignatureCount.restype = c_int -# ./fpdf_signature.h: 35 if hasattr(_libs['pdfium'], 'FPDF_GetSignatureObject'): FPDF_GetSignatureObject = _libs['pdfium']['FPDF_GetSignatureObject'] FPDF_GetSignatureObject.argtypes = [FPDF_DOCUMENT, c_int] FPDF_GetSignatureObject.restype = FPDF_SIGNATURE -# ./fpdf_signature.h: 52 if hasattr(_libs['pdfium'], 'FPDFSignatureObj_GetContents'): FPDFSignatureObj_GetContents = _libs['pdfium']['FPDFSignatureObj_GetContents'] FPDFSignatureObj_GetContents.argtypes = [FPDF_SIGNATURE, POINTER(None), c_ulong] FPDFSignatureObj_GetContents.restype = c_ulong -# ./fpdf_signature.h: 74 if hasattr(_libs['pdfium'], 'FPDFSignatureObj_GetByteRange'): FPDFSignatureObj_GetByteRange = _libs['pdfium']['FPDFSignatureObj_GetByteRange'] FPDFSignatureObj_GetByteRange.argtypes = [FPDF_SIGNATURE, POINTER(c_int), c_ulong] FPDFSignatureObj_GetByteRange.restype = c_ulong -# ./fpdf_signature.h: 93 if hasattr(_libs['pdfium'], 'FPDFSignatureObj_GetSubFilter'): FPDFSignatureObj_GetSubFilter = _libs['pdfium']['FPDFSignatureObj_GetSubFilter'] FPDFSignatureObj_GetSubFilter.argtypes = [FPDF_SIGNATURE, POINTER(c_char), c_ulong] FPDFSignatureObj_GetSubFilter.restype = c_ulong -# ./fpdf_signature.h: 112 if hasattr(_libs['pdfium'], 'FPDFSignatureObj_GetReason'): FPDFSignatureObj_GetReason = _libs['pdfium']['FPDFSignatureObj_GetReason'] FPDFSignatureObj_GetReason.argtypes = [FPDF_SIGNATURE, POINTER(None), c_ulong] FPDFSignatureObj_GetReason.restype = c_ulong -# ./fpdf_signature.h: 136 if hasattr(_libs['pdfium'], 'FPDFSignatureObj_GetTime'): FPDFSignatureObj_GetTime = _libs['pdfium']['FPDFSignatureObj_GetTime'] FPDFSignatureObj_GetTime.argtypes = [FPDF_SIGNATURE, POINTER(c_char), c_ulong] FPDFSignatureObj_GetTime.restype = c_ulong -# ./fpdf_signature.h: 149 if hasattr(_libs['pdfium'], 'FPDFSignatureObj_GetDocMDPPermission'): FPDFSignatureObj_GetDocMDPPermission = _libs['pdfium']['FPDFSignatureObj_GetDocMDPPermission'] FPDFSignatureObj_GetDocMDPPermission.argtypes = [FPDF_SIGNATURE] FPDFSignatureObj_GetDocMDPPermission.restype = c_uint -# ./fpdf_structtree.h: 27 if hasattr(_libs['pdfium'], 'FPDF_StructTree_GetForPage'): FPDF_StructTree_GetForPage = _libs['pdfium']['FPDF_StructTree_GetForPage'] FPDF_StructTree_GetForPage.argtypes = [FPDF_PAGE] FPDF_StructTree_GetForPage.restype = FPDF_STRUCTTREE -# ./fpdf_structtree.h: 37 if hasattr(_libs['pdfium'], 'FPDF_StructTree_Close'): FPDF_StructTree_Close = _libs['pdfium']['FPDF_StructTree_Close'] FPDF_StructTree_Close.argtypes = [FPDF_STRUCTTREE] FPDF_StructTree_Close.restype = None -# ./fpdf_structtree.h: 47 if hasattr(_libs['pdfium'], 'FPDF_StructTree_CountChildren'): FPDF_StructTree_CountChildren = _libs['pdfium']['FPDF_StructTree_CountChildren'] FPDF_StructTree_CountChildren.argtypes = [FPDF_STRUCTTREE] FPDF_StructTree_CountChildren.restype = c_int -# ./fpdf_structtree.h: 63 if hasattr(_libs['pdfium'], 'FPDF_StructTree_GetChildAtIndex'): FPDF_StructTree_GetChildAtIndex = _libs['pdfium']['FPDF_StructTree_GetChildAtIndex'] FPDF_StructTree_GetChildAtIndex.argtypes = [FPDF_STRUCTTREE, c_int] FPDF_StructTree_GetChildAtIndex.restype = FPDF_STRUCTELEMENT -# ./fpdf_structtree.h: 81 if hasattr(_libs['pdfium'], 'FPDF_StructElement_GetAltText'): FPDF_StructElement_GetAltText = _libs['pdfium']['FPDF_StructElement_GetAltText'] FPDF_StructElement_GetAltText.argtypes = [FPDF_STRUCTELEMENT, POINTER(None), c_ulong] FPDF_StructElement_GetAltText.restype = c_ulong -# ./fpdf_structtree.h: 102 if hasattr(_libs['pdfium'], 'FPDF_StructElement_GetActualText'): FPDF_StructElement_GetActualText = _libs['pdfium']['FPDF_StructElement_GetActualText'] FPDF_StructElement_GetActualText.argtypes = [FPDF_STRUCTELEMENT, POINTER(None), c_ulong] FPDF_StructElement_GetActualText.restype = c_ulong -# ./fpdf_structtree.h: 122 if hasattr(_libs['pdfium'], 'FPDF_StructElement_GetID'): FPDF_StructElement_GetID = _libs['pdfium']['FPDF_StructElement_GetID'] FPDF_StructElement_GetID.argtypes = [FPDF_STRUCTELEMENT, POINTER(None), c_ulong] FPDF_StructElement_GetID.restype = c_ulong -# ./fpdf_structtree.h: 143 if hasattr(_libs['pdfium'], 'FPDF_StructElement_GetLang'): FPDF_StructElement_GetLang = _libs['pdfium']['FPDF_StructElement_GetLang'] FPDF_StructElement_GetLang.argtypes = [FPDF_STRUCTELEMENT, POINTER(None), c_ulong] FPDF_StructElement_GetLang.restype = c_ulong -# ./fpdf_structtree.h: 165 if hasattr(_libs['pdfium'], 'FPDF_StructElement_GetStringAttribute'): FPDF_StructElement_GetStringAttribute = _libs['pdfium']['FPDF_StructElement_GetStringAttribute'] FPDF_StructElement_GetStringAttribute.argtypes = [FPDF_STRUCTELEMENT, FPDF_BYTESTRING, POINTER(None), c_ulong] FPDF_StructElement_GetStringAttribute.restype = c_ulong -# ./fpdf_structtree.h: 182 if hasattr(_libs['pdfium'], 'FPDF_StructElement_GetMarkedContentID'): FPDF_StructElement_GetMarkedContentID = _libs['pdfium']['FPDF_StructElement_GetMarkedContentID'] FPDF_StructElement_GetMarkedContentID.argtypes = [FPDF_STRUCTELEMENT] FPDF_StructElement_GetMarkedContentID.restype = c_int -# ./fpdf_structtree.h: 200 if hasattr(_libs['pdfium'], 'FPDF_StructElement_GetType'): FPDF_StructElement_GetType = _libs['pdfium']['FPDF_StructElement_GetType'] FPDF_StructElement_GetType.argtypes = [FPDF_STRUCTELEMENT, POINTER(None), c_ulong] FPDF_StructElement_GetType.restype = c_ulong -# ./fpdf_structtree.h: 221 if hasattr(_libs['pdfium'], 'FPDF_StructElement_GetObjType'): FPDF_StructElement_GetObjType = _libs['pdfium']['FPDF_StructElement_GetObjType'] FPDF_StructElement_GetObjType.argtypes = [FPDF_STRUCTELEMENT, POINTER(None), c_ulong] FPDF_StructElement_GetObjType.restype = c_ulong -# ./fpdf_structtree.h: 241 if hasattr(_libs['pdfium'], 'FPDF_StructElement_GetTitle'): FPDF_StructElement_GetTitle = _libs['pdfium']['FPDF_StructElement_GetTitle'] FPDF_StructElement_GetTitle.argtypes = [FPDF_STRUCTELEMENT, POINTER(None), c_ulong] FPDF_StructElement_GetTitle.restype = c_ulong -# ./fpdf_structtree.h: 252 if hasattr(_libs['pdfium'], 'FPDF_StructElement_CountChildren'): FPDF_StructElement_CountChildren = _libs['pdfium']['FPDF_StructElement_CountChildren'] FPDF_StructElement_CountChildren.argtypes = [FPDF_STRUCTELEMENT] FPDF_StructElement_CountChildren.restype = c_int -# ./fpdf_structtree.h: 267 if hasattr(_libs['pdfium'], 'FPDF_StructElement_GetChildAtIndex'): FPDF_StructElement_GetChildAtIndex = _libs['pdfium']['FPDF_StructElement_GetChildAtIndex'] FPDF_StructElement_GetChildAtIndex.argtypes = [FPDF_STRUCTELEMENT, c_int] FPDF_StructElement_GetChildAtIndex.restype = FPDF_STRUCTELEMENT -# ./fpdf_structtree.h: 286 if hasattr(_libs['pdfium'], 'FPDF_StructElement_GetChildMarkedContentID'): FPDF_StructElement_GetChildMarkedContentID = _libs['pdfium']['FPDF_StructElement_GetChildMarkedContentID'] FPDF_StructElement_GetChildMarkedContentID.argtypes = [FPDF_STRUCTELEMENT, c_int] FPDF_StructElement_GetChildMarkedContentID.restype = c_int -# ./fpdf_structtree.h: 300 if hasattr(_libs['pdfium'], 'FPDF_StructElement_GetParent'): FPDF_StructElement_GetParent = _libs['pdfium']['FPDF_StructElement_GetParent'] FPDF_StructElement_GetParent.argtypes = [FPDF_STRUCTELEMENT] FPDF_StructElement_GetParent.restype = FPDF_STRUCTELEMENT -# ./fpdf_structtree.h: 309 if hasattr(_libs['pdfium'], 'FPDF_StructElement_GetAttributeCount'): FPDF_StructElement_GetAttributeCount = _libs['pdfium']['FPDF_StructElement_GetAttributeCount'] FPDF_StructElement_GetAttributeCount.argtypes = [FPDF_STRUCTELEMENT] FPDF_StructElement_GetAttributeCount.restype = c_int -# ./fpdf_structtree.h: 327 if hasattr(_libs['pdfium'], 'FPDF_StructElement_GetAttributeAtIndex'): FPDF_StructElement_GetAttributeAtIndex = _libs['pdfium']['FPDF_StructElement_GetAttributeAtIndex'] FPDF_StructElement_GetAttributeAtIndex.argtypes = [FPDF_STRUCTELEMENT, c_int] FPDF_StructElement_GetAttributeAtIndex.restype = FPDF_STRUCTELEMENT_ATTR -# ./fpdf_structtree.h: 337 if hasattr(_libs['pdfium'], 'FPDF_StructElement_Attr_GetCount'): FPDF_StructElement_Attr_GetCount = _libs['pdfium']['FPDF_StructElement_Attr_GetCount'] FPDF_StructElement_Attr_GetCount.argtypes = [FPDF_STRUCTELEMENT_ATTR] FPDF_StructElement_Attr_GetCount.restype = c_int -# ./fpdf_structtree.h: 357 if hasattr(_libs['pdfium'], 'FPDF_StructElement_Attr_GetName'): FPDF_StructElement_Attr_GetName = _libs['pdfium']['FPDF_StructElement_Attr_GetName'] FPDF_StructElement_Attr_GetName.argtypes = [FPDF_STRUCTELEMENT_ATTR, c_int, POINTER(None), c_ulong, POINTER(c_ulong)] FPDF_StructElement_Attr_GetName.restype = FPDF_BOOL -# ./fpdf_structtree.h: 375 if hasattr(_libs['pdfium'], 'FPDF_StructElement_Attr_GetValue'): FPDF_StructElement_Attr_GetValue = _libs['pdfium']['FPDF_StructElement_Attr_GetValue'] FPDF_StructElement_Attr_GetValue.argtypes = [FPDF_STRUCTELEMENT_ATTR, FPDF_BYTESTRING] FPDF_StructElement_Attr_GetValue.restype = FPDF_STRUCTELEMENT_ATTR_VALUE -# ./fpdf_structtree.h: 388 if hasattr(_libs['pdfium'], 'FPDF_StructElement_Attr_GetType'): FPDF_StructElement_Attr_GetType = _libs['pdfium']['FPDF_StructElement_Attr_GetType'] FPDF_StructElement_Attr_GetType.argtypes = [FPDF_STRUCTELEMENT_ATTR_VALUE] FPDF_StructElement_Attr_GetType.restype = FPDF_OBJECT_TYPE -# ./fpdf_structtree.h: 403 if hasattr(_libs['pdfium'], 'FPDF_StructElement_Attr_GetBooleanValue'): FPDF_StructElement_Attr_GetBooleanValue = _libs['pdfium']['FPDF_StructElement_Attr_GetBooleanValue'] FPDF_StructElement_Attr_GetBooleanValue.argtypes = [FPDF_STRUCTELEMENT_ATTR_VALUE, POINTER(FPDF_BOOL)] FPDF_StructElement_Attr_GetBooleanValue.restype = FPDF_BOOL -# ./fpdf_structtree.h: 419 if hasattr(_libs['pdfium'], 'FPDF_StructElement_Attr_GetNumberValue'): FPDF_StructElement_Attr_GetNumberValue = _libs['pdfium']['FPDF_StructElement_Attr_GetNumberValue'] FPDF_StructElement_Attr_GetNumberValue.argtypes = [FPDF_STRUCTELEMENT_ATTR_VALUE, POINTER(c_float)] FPDF_StructElement_Attr_GetNumberValue.restype = FPDF_BOOL -# ./fpdf_structtree.h: 441 if hasattr(_libs['pdfium'], 'FPDF_StructElement_Attr_GetStringValue'): FPDF_StructElement_Attr_GetStringValue = _libs['pdfium']['FPDF_StructElement_Attr_GetStringValue'] FPDF_StructElement_Attr_GetStringValue.argtypes = [FPDF_STRUCTELEMENT_ATTR_VALUE, POINTER(None), c_ulong, POINTER(c_ulong)] FPDF_StructElement_Attr_GetStringValue.restype = FPDF_BOOL -# ./fpdf_structtree.h: 463 if hasattr(_libs['pdfium'], 'FPDF_StructElement_Attr_GetBlobValue'): FPDF_StructElement_Attr_GetBlobValue = _libs['pdfium']['FPDF_StructElement_Attr_GetBlobValue'] FPDF_StructElement_Attr_GetBlobValue.argtypes = [FPDF_STRUCTELEMENT_ATTR_VALUE, POINTER(None), c_ulong, POINTER(c_ulong)] FPDF_StructElement_Attr_GetBlobValue.restype = FPDF_BOOL -# ./fpdf_structtree.h: 476 if hasattr(_libs['pdfium'], 'FPDF_StructElement_Attr_CountChildren'): FPDF_StructElement_Attr_CountChildren = _libs['pdfium']['FPDF_StructElement_Attr_CountChildren'] FPDF_StructElement_Attr_CountChildren.argtypes = [FPDF_STRUCTELEMENT_ATTR_VALUE] FPDF_StructElement_Attr_CountChildren.restype = c_int -# ./fpdf_structtree.h: 490 if hasattr(_libs['pdfium'], 'FPDF_StructElement_Attr_GetChildAtIndex'): FPDF_StructElement_Attr_GetChildAtIndex = _libs['pdfium']['FPDF_StructElement_Attr_GetChildAtIndex'] FPDF_StructElement_Attr_GetChildAtIndex.argtypes = [FPDF_STRUCTELEMENT_ATTR_VALUE, c_int] FPDF_StructElement_Attr_GetChildAtIndex.restype = FPDF_STRUCTELEMENT_ATTR_VALUE -# ./fpdf_structtree.h: 501 if hasattr(_libs['pdfium'], 'FPDF_StructElement_GetMarkedContentIdCount'): FPDF_StructElement_GetMarkedContentIdCount = _libs['pdfium']['FPDF_StructElement_GetMarkedContentIdCount'] FPDF_StructElement_GetMarkedContentIdCount.argtypes = [FPDF_STRUCTELEMENT] FPDF_StructElement_GetMarkedContentIdCount.restype = c_int -# ./fpdf_structtree.h: 517 if hasattr(_libs['pdfium'], 'FPDF_StructElement_GetMarkedContentIdAtIndex'): FPDF_StructElement_GetMarkedContentIdAtIndex = _libs['pdfium']['FPDF_StructElement_GetMarkedContentIdAtIndex'] FPDF_StructElement_GetMarkedContentIdAtIndex.argtypes = [FPDF_STRUCTELEMENT, c_int] FPDF_StructElement_GetMarkedContentIdAtIndex.restype = c_int -# ./fpdf_sysfontinfo.h: 48 class struct__FPDF_SYSFONTINFO (Structure): __slots__ = ['version', 'Release', 'EnumFonts', 'MapFont', 'GetFont', 'GetFontData', 'GetFaceName', 'GetFontCharset', 'DeleteFont'] @@ -3650,10 +2925,8 @@ class struct__FPDF_SYSFONTINFO (Structure): ('DeleteFont', CFUNCTYPE(None, POINTER(struct__FPDF_SYSFONTINFO), POINTER(None))), ] -# ./fpdf_sysfontinfo.h: 209 FPDF_SYSFONTINFO = struct__FPDF_SYSFONTINFO -# ./fpdf_sysfontinfo.h: 216 class struct_FPDF_CharsetFontMap_ (Structure): __slots__ = ['charset', 'fontname'] @@ -3662,1273 +2935,917 @@ class struct_FPDF_CharsetFontMap_ (Structure): ('fontname', POINTER(c_char)), ] -# ./fpdf_sysfontinfo.h: 216 FPDF_CharsetFontMap = struct_FPDF_CharsetFontMap_ -# ./fpdf_sysfontinfo.h: 230 if hasattr(_libs['pdfium'], 'FPDF_GetDefaultTTFMap'): FPDF_GetDefaultTTFMap = _libs['pdfium']['FPDF_GetDefaultTTFMap'] FPDF_GetDefaultTTFMap.argtypes = [] FPDF_GetDefaultTTFMap.restype = POINTER(FPDF_CharsetFontMap) -# ./fpdf_sysfontinfo.h: 241 if hasattr(_libs['pdfium'], 'FPDF_GetDefaultTTFMapCount'): FPDF_GetDefaultTTFMapCount = _libs['pdfium']['FPDF_GetDefaultTTFMapCount'] FPDF_GetDefaultTTFMapCount.argtypes = [] FPDF_GetDefaultTTFMapCount.restype = c_size_t -# ./fpdf_sysfontinfo.h: 252 if hasattr(_libs['pdfium'], 'FPDF_GetDefaultTTFMapEntry'): FPDF_GetDefaultTTFMapEntry = _libs['pdfium']['FPDF_GetDefaultTTFMapEntry'] FPDF_GetDefaultTTFMapEntry.argtypes = [c_size_t] FPDF_GetDefaultTTFMapEntry.restype = POINTER(FPDF_CharsetFontMap) -# ./fpdf_sysfontinfo.h: 266 if hasattr(_libs['pdfium'], 'FPDF_AddInstalledFont'): FPDF_AddInstalledFont = _libs['pdfium']['FPDF_AddInstalledFont'] FPDF_AddInstalledFont.argtypes = [POINTER(None), POINTER(c_char), c_int] FPDF_AddInstalledFont.restype = None -# ./fpdf_sysfontinfo.h: 284 if hasattr(_libs['pdfium'], 'FPDF_SetSystemFontInfo'): FPDF_SetSystemFontInfo = _libs['pdfium']['FPDF_SetSystemFontInfo'] FPDF_SetSystemFontInfo.argtypes = [POINTER(FPDF_SYSFONTINFO)] FPDF_SetSystemFontInfo.restype = None -# ./fpdf_sysfontinfo.h: 299 if hasattr(_libs['pdfium'], 'FPDF_GetDefaultSystemFontInfo'): FPDF_GetDefaultSystemFontInfo = _libs['pdfium']['FPDF_GetDefaultSystemFontInfo'] FPDF_GetDefaultSystemFontInfo.argtypes = [] FPDF_GetDefaultSystemFontInfo.restype = POINTER(FPDF_SYSFONTINFO) -# ./fpdf_sysfontinfo.h: 311 if hasattr(_libs['pdfium'], 'FPDF_FreeDefaultSystemFontInfo'): FPDF_FreeDefaultSystemFontInfo = _libs['pdfium']['FPDF_FreeDefaultSystemFontInfo'] FPDF_FreeDefaultSystemFontInfo.argtypes = [POINTER(FPDF_SYSFONTINFO)] FPDF_FreeDefaultSystemFontInfo.restype = None -# ./fpdf_text.h: 31 if hasattr(_libs['pdfium'], 'FPDFText_LoadPage'): FPDFText_LoadPage = _libs['pdfium']['FPDFText_LoadPage'] FPDFText_LoadPage.argtypes = [FPDF_PAGE] FPDFText_LoadPage.restype = FPDF_TEXTPAGE -# ./fpdf_text.h: 42 if hasattr(_libs['pdfium'], 'FPDFText_ClosePage'): FPDFText_ClosePage = _libs['pdfium']['FPDFText_ClosePage'] FPDFText_ClosePage.argtypes = [FPDF_TEXTPAGE] FPDFText_ClosePage.restype = None -# ./fpdf_text.h: 60 if hasattr(_libs['pdfium'], 'FPDFText_CountChars'): FPDFText_CountChars = _libs['pdfium']['FPDFText_CountChars'] FPDFText_CountChars.argtypes = [FPDF_TEXTPAGE] FPDFText_CountChars.restype = c_int -# ./fpdf_text.h: 75 if hasattr(_libs['pdfium'], 'FPDFText_GetUnicode'): FPDFText_GetUnicode = _libs['pdfium']['FPDFText_GetUnicode'] FPDFText_GetUnicode.argtypes = [FPDF_TEXTPAGE, c_int] FPDFText_GetUnicode.restype = c_uint -# ./fpdf_text.h: 90 if hasattr(_libs['pdfium'], 'FPDFText_GetTextObject'): FPDFText_GetTextObject = _libs['pdfium']['FPDFText_GetTextObject'] FPDFText_GetTextObject.argtypes = [FPDF_TEXTPAGE, c_int] FPDFText_GetTextObject.restype = FPDF_PAGEOBJECT -# ./fpdf_text.h: 105 if hasattr(_libs['pdfium'], 'FPDFText_IsGenerated'): FPDFText_IsGenerated = _libs['pdfium']['FPDFText_IsGenerated'] FPDFText_IsGenerated.argtypes = [FPDF_TEXTPAGE, c_int] FPDFText_IsGenerated.restype = c_int -# ./fpdf_text.h: 120 if hasattr(_libs['pdfium'], 'FPDFText_IsHyphen'): FPDFText_IsHyphen = _libs['pdfium']['FPDFText_IsHyphen'] FPDFText_IsHyphen.argtypes = [FPDF_TEXTPAGE, c_int] FPDFText_IsHyphen.restype = c_int -# ./fpdf_text.h: 135 if hasattr(_libs['pdfium'], 'FPDFText_HasUnicodeMapError'): FPDFText_HasUnicodeMapError = _libs['pdfium']['FPDFText_HasUnicodeMapError'] FPDFText_HasUnicodeMapError.argtypes = [FPDF_TEXTPAGE, c_int] FPDFText_HasUnicodeMapError.restype = c_int -# ./fpdf_text.h: 148 if hasattr(_libs['pdfium'], 'FPDFText_GetFontSize'): FPDFText_GetFontSize = _libs['pdfium']['FPDFText_GetFontSize'] FPDFText_GetFontSize.argtypes = [FPDF_TEXTPAGE, c_int] FPDFText_GetFontSize.restype = c_double -# ./fpdf_text.h: 171 if hasattr(_libs['pdfium'], 'FPDFText_GetFontInfo'): FPDFText_GetFontInfo = _libs['pdfium']['FPDFText_GetFontInfo'] FPDFText_GetFontInfo.argtypes = [FPDF_TEXTPAGE, c_int, POINTER(None), c_ulong, POINTER(c_int)] FPDFText_GetFontInfo.restype = c_ulong -# ./fpdf_text.h: 189 if hasattr(_libs['pdfium'], 'FPDFText_GetFontWeight'): FPDFText_GetFontWeight = _libs['pdfium']['FPDFText_GetFontWeight'] FPDFText_GetFontWeight.argtypes = [FPDF_TEXTPAGE, c_int] FPDFText_GetFontWeight.restype = c_int -# ./fpdf_text.h: 212 if hasattr(_libs['pdfium'], 'FPDFText_GetFillColor'): FPDFText_GetFillColor = _libs['pdfium']['FPDFText_GetFillColor'] FPDFText_GetFillColor.argtypes = [FPDF_TEXTPAGE, c_int, POINTER(c_uint), POINTER(c_uint), POINTER(c_uint), POINTER(c_uint)] FPDFText_GetFillColor.restype = FPDF_BOOL -# ./fpdf_text.h: 239 if hasattr(_libs['pdfium'], 'FPDFText_GetStrokeColor'): FPDFText_GetStrokeColor = _libs['pdfium']['FPDFText_GetStrokeColor'] FPDFText_GetStrokeColor.argtypes = [FPDF_TEXTPAGE, c_int, POINTER(c_uint), POINTER(c_uint), POINTER(c_uint), POINTER(c_uint)] FPDFText_GetStrokeColor.restype = FPDF_BOOL -# ./fpdf_text.h: 258 if hasattr(_libs['pdfium'], 'FPDFText_GetCharAngle'): FPDFText_GetCharAngle = _libs['pdfium']['FPDFText_GetCharAngle'] FPDFText_GetCharAngle.argtypes = [FPDF_TEXTPAGE, c_int] FPDFText_GetCharAngle.restype = c_float -# ./fpdf_text.h: 282 if hasattr(_libs['pdfium'], 'FPDFText_GetCharBox'): FPDFText_GetCharBox = _libs['pdfium']['FPDFText_GetCharBox'] FPDFText_GetCharBox.argtypes = [FPDF_TEXTPAGE, c_int, POINTER(c_double), POINTER(c_double), POINTER(c_double), POINTER(c_double)] FPDFText_GetCharBox.restype = FPDF_BOOL -# ./fpdf_text.h: 307 if hasattr(_libs['pdfium'], 'FPDFText_GetLooseCharBox'): FPDFText_GetLooseCharBox = _libs['pdfium']['FPDFText_GetLooseCharBox'] FPDFText_GetLooseCharBox.argtypes = [FPDF_TEXTPAGE, c_int, POINTER(FS_RECTF)] FPDFText_GetLooseCharBox.restype = FPDF_BOOL -# ./fpdf_text.h: 323 if hasattr(_libs['pdfium'], 'FPDFText_GetMatrix'): FPDFText_GetMatrix = _libs['pdfium']['FPDFText_GetMatrix'] FPDFText_GetMatrix.argtypes = [FPDF_TEXTPAGE, c_int, POINTER(FS_MATRIX)] FPDFText_GetMatrix.restype = FPDF_BOOL -# ./fpdf_text.h: 343 if hasattr(_libs['pdfium'], 'FPDFText_GetCharOrigin'): FPDFText_GetCharOrigin = _libs['pdfium']['FPDFText_GetCharOrigin'] FPDFText_GetCharOrigin.argtypes = [FPDF_TEXTPAGE, c_int, POINTER(c_double), POINTER(c_double)] FPDFText_GetCharOrigin.restype = FPDF_BOOL -# ./fpdf_text.h: 366 if hasattr(_libs['pdfium'], 'FPDFText_GetCharIndexAtPos'): FPDFText_GetCharIndexAtPos = _libs['pdfium']['FPDFText_GetCharIndexAtPos'] FPDFText_GetCharIndexAtPos.argtypes = [FPDF_TEXTPAGE, c_double, c_double, c_double, c_double] FPDFText_GetCharIndexAtPos.restype = c_int -# ./fpdf_text.h: 392 if hasattr(_libs['pdfium'], 'FPDFText_GetText'): FPDFText_GetText = _libs['pdfium']['FPDFText_GetText'] FPDFText_GetText.argtypes = [FPDF_TEXTPAGE, c_int, c_int, POINTER(c_ushort)] FPDFText_GetText.restype = c_int -# ./fpdf_text.h: 415 if hasattr(_libs['pdfium'], 'FPDFText_CountRects'): FPDFText_CountRects = _libs['pdfium']['FPDFText_CountRects'] FPDFText_CountRects.argtypes = [FPDF_TEXTPAGE, c_int, c_int] FPDFText_CountRects.restype = c_int -# ./fpdf_text.h: 441 if hasattr(_libs['pdfium'], 'FPDFText_GetRect'): FPDFText_GetRect = _libs['pdfium']['FPDFText_GetRect'] FPDFText_GetRect.argtypes = [FPDF_TEXTPAGE, c_int, POINTER(c_double), POINTER(c_double), POINTER(c_double), POINTER(c_double)] FPDFText_GetRect.restype = FPDF_BOOL -# ./fpdf_text.h: 472 if hasattr(_libs['pdfium'], 'FPDFText_GetBoundedText'): FPDFText_GetBoundedText = _libs['pdfium']['FPDFText_GetBoundedText'] FPDFText_GetBoundedText.argtypes = [FPDF_TEXTPAGE, c_double, c_double, c_double, c_double, POINTER(c_ushort), c_int] FPDFText_GetBoundedText.restype = c_int -# ./fpdf_text.h: 502 if hasattr(_libs['pdfium'], 'FPDFText_FindStart'): FPDFText_FindStart = _libs['pdfium']['FPDFText_FindStart'] FPDFText_FindStart.argtypes = [FPDF_TEXTPAGE, FPDF_WIDESTRING, c_ulong, c_int] FPDFText_FindStart.restype = FPDF_SCHHANDLE -# ./fpdf_text.h: 515 if hasattr(_libs['pdfium'], 'FPDFText_FindNext'): FPDFText_FindNext = _libs['pdfium']['FPDFText_FindNext'] FPDFText_FindNext.argtypes = [FPDF_SCHHANDLE] FPDFText_FindNext.restype = FPDF_BOOL -# ./fpdf_text.h: 525 if hasattr(_libs['pdfium'], 'FPDFText_FindPrev'): FPDFText_FindPrev = _libs['pdfium']['FPDFText_FindPrev'] FPDFText_FindPrev.argtypes = [FPDF_SCHHANDLE] FPDFText_FindPrev.restype = FPDF_BOOL -# ./fpdf_text.h: 535 if hasattr(_libs['pdfium'], 'FPDFText_GetSchResultIndex'): FPDFText_GetSchResultIndex = _libs['pdfium']['FPDFText_GetSchResultIndex'] FPDFText_GetSchResultIndex.argtypes = [FPDF_SCHHANDLE] FPDFText_GetSchResultIndex.restype = c_int -# ./fpdf_text.h: 545 if hasattr(_libs['pdfium'], 'FPDFText_GetSchCount'): FPDFText_GetSchCount = _libs['pdfium']['FPDFText_GetSchCount'] FPDFText_GetSchCount.argtypes = [FPDF_SCHHANDLE] FPDFText_GetSchCount.restype = c_int -# ./fpdf_text.h: 555 if hasattr(_libs['pdfium'], 'FPDFText_FindClose'): FPDFText_FindClose = _libs['pdfium']['FPDFText_FindClose'] FPDFText_FindClose.argtypes = [FPDF_SCHHANDLE] FPDFText_FindClose.restype = None -# ./fpdf_text.h: 577 if hasattr(_libs['pdfium'], 'FPDFLink_LoadWebLinks'): FPDFLink_LoadWebLinks = _libs['pdfium']['FPDFLink_LoadWebLinks'] FPDFLink_LoadWebLinks.argtypes = [FPDF_TEXTPAGE] FPDFLink_LoadWebLinks.restype = FPDF_PAGELINK -# ./fpdf_text.h: 586 if hasattr(_libs['pdfium'], 'FPDFLink_CountWebLinks'): FPDFLink_CountWebLinks = _libs['pdfium']['FPDFLink_CountWebLinks'] FPDFLink_CountWebLinks.argtypes = [FPDF_PAGELINK] FPDFLink_CountWebLinks.restype = c_int -# ./fpdf_text.h: 607 if hasattr(_libs['pdfium'], 'FPDFLink_GetURL'): FPDFLink_GetURL = _libs['pdfium']['FPDFLink_GetURL'] FPDFLink_GetURL.argtypes = [FPDF_PAGELINK, c_int, POINTER(c_ushort), c_int] FPDFLink_GetURL.restype = c_int -# ./fpdf_text.h: 621 if hasattr(_libs['pdfium'], 'FPDFLink_CountRects'): FPDFLink_CountRects = _libs['pdfium']['FPDFLink_CountRects'] FPDFLink_CountRects.argtypes = [FPDF_PAGELINK, c_int] FPDFLink_CountRects.restype = c_int -# ./fpdf_text.h: 644 if hasattr(_libs['pdfium'], 'FPDFLink_GetRect'): FPDFLink_GetRect = _libs['pdfium']['FPDFLink_GetRect'] FPDFLink_GetRect.argtypes = [FPDF_PAGELINK, c_int, c_int, POINTER(c_double), POINTER(c_double), POINTER(c_double), POINTER(c_double)] FPDFLink_GetRect.restype = FPDF_BOOL -# ./fpdf_text.h: 667 if hasattr(_libs['pdfium'], 'FPDFLink_GetTextRange'): FPDFLink_GetTextRange = _libs['pdfium']['FPDFLink_GetTextRange'] FPDFLink_GetTextRange.argtypes = [FPDF_PAGELINK, c_int, POINTER(c_int), POINTER(c_int)] FPDFLink_GetTextRange.restype = FPDF_BOOL -# ./fpdf_text.h: 679 if hasattr(_libs['pdfium'], 'FPDFLink_CloseWebLinks'): FPDFLink_CloseWebLinks = _libs['pdfium']['FPDFLink_CloseWebLinks'] FPDFLink_CloseWebLinks.argtypes = [FPDF_PAGELINK] FPDFLink_CloseWebLinks.restype = None -# ./fpdf_thumbnail.h: 28 if hasattr(_libs['pdfium'], 'FPDFPage_GetDecodedThumbnailData'): FPDFPage_GetDecodedThumbnailData = _libs['pdfium']['FPDFPage_GetDecodedThumbnailData'] FPDFPage_GetDecodedThumbnailData.argtypes = [FPDF_PAGE, POINTER(None), c_ulong] FPDFPage_GetDecodedThumbnailData.restype = c_ulong -# ./fpdf_thumbnail.h: 43 if hasattr(_libs['pdfium'], 'FPDFPage_GetRawThumbnailData'): FPDFPage_GetRawThumbnailData = _libs['pdfium']['FPDFPage_GetRawThumbnailData'] FPDFPage_GetRawThumbnailData.argtypes = [FPDF_PAGE, POINTER(None), c_ulong] FPDFPage_GetRawThumbnailData.restype = c_ulong -# ./fpdf_thumbnail.h: 53 if hasattr(_libs['pdfium'], 'FPDFPage_GetThumbnailAsBitmap'): FPDFPage_GetThumbnailAsBitmap = _libs['pdfium']['FPDFPage_GetThumbnailAsBitmap'] FPDFPage_GetThumbnailAsBitmap.argtypes = [FPDF_PAGE] FPDFPage_GetThumbnailAsBitmap.restype = FPDF_BITMAP -# ./fpdf_transformpage.h: 24 if hasattr(_libs['pdfium'], 'FPDFPage_SetMediaBox'): FPDFPage_SetMediaBox = _libs['pdfium']['FPDFPage_SetMediaBox'] FPDFPage_SetMediaBox.argtypes = [FPDF_PAGE, c_float, c_float, c_float, c_float] FPDFPage_SetMediaBox.restype = None -# ./fpdf_transformpage.h: 37 if hasattr(_libs['pdfium'], 'FPDFPage_SetCropBox'): FPDFPage_SetCropBox = _libs['pdfium']['FPDFPage_SetCropBox'] FPDFPage_SetCropBox.argtypes = [FPDF_PAGE, c_float, c_float, c_float, c_float] FPDFPage_SetCropBox.restype = None -# ./fpdf_transformpage.h: 50 if hasattr(_libs['pdfium'], 'FPDFPage_SetBleedBox'): FPDFPage_SetBleedBox = _libs['pdfium']['FPDFPage_SetBleedBox'] FPDFPage_SetBleedBox.argtypes = [FPDF_PAGE, c_float, c_float, c_float, c_float] FPDFPage_SetBleedBox.restype = None -# ./fpdf_transformpage.h: 63 if hasattr(_libs['pdfium'], 'FPDFPage_SetTrimBox'): FPDFPage_SetTrimBox = _libs['pdfium']['FPDFPage_SetTrimBox'] FPDFPage_SetTrimBox.argtypes = [FPDF_PAGE, c_float, c_float, c_float, c_float] FPDFPage_SetTrimBox.restype = None -# ./fpdf_transformpage.h: 76 if hasattr(_libs['pdfium'], 'FPDFPage_SetArtBox'): FPDFPage_SetArtBox = _libs['pdfium']['FPDFPage_SetArtBox'] FPDFPage_SetArtBox.argtypes = [FPDF_PAGE, c_float, c_float, c_float, c_float] FPDFPage_SetArtBox.restype = None -# ./fpdf_transformpage.h: 92 if hasattr(_libs['pdfium'], 'FPDFPage_GetMediaBox'): FPDFPage_GetMediaBox = _libs['pdfium']['FPDFPage_GetMediaBox'] FPDFPage_GetMediaBox.argtypes = [FPDF_PAGE, POINTER(c_float), POINTER(c_float), POINTER(c_float), POINTER(c_float)] FPDFPage_GetMediaBox.restype = FPDF_BOOL -# ./fpdf_transformpage.h: 108 if hasattr(_libs['pdfium'], 'FPDFPage_GetCropBox'): FPDFPage_GetCropBox = _libs['pdfium']['FPDFPage_GetCropBox'] FPDFPage_GetCropBox.argtypes = [FPDF_PAGE, POINTER(c_float), POINTER(c_float), POINTER(c_float), POINTER(c_float)] FPDFPage_GetCropBox.restype = FPDF_BOOL -# ./fpdf_transformpage.h: 124 if hasattr(_libs['pdfium'], 'FPDFPage_GetBleedBox'): FPDFPage_GetBleedBox = _libs['pdfium']['FPDFPage_GetBleedBox'] FPDFPage_GetBleedBox.argtypes = [FPDF_PAGE, POINTER(c_float), POINTER(c_float), POINTER(c_float), POINTER(c_float)] FPDFPage_GetBleedBox.restype = FPDF_BOOL -# ./fpdf_transformpage.h: 140 if hasattr(_libs['pdfium'], 'FPDFPage_GetTrimBox'): FPDFPage_GetTrimBox = _libs['pdfium']['FPDFPage_GetTrimBox'] FPDFPage_GetTrimBox.argtypes = [FPDF_PAGE, POINTER(c_float), POINTER(c_float), POINTER(c_float), POINTER(c_float)] FPDFPage_GetTrimBox.restype = FPDF_BOOL -# ./fpdf_transformpage.h: 156 if hasattr(_libs['pdfium'], 'FPDFPage_GetArtBox'): FPDFPage_GetArtBox = _libs['pdfium']['FPDFPage_GetArtBox'] FPDFPage_GetArtBox.argtypes = [FPDF_PAGE, POINTER(c_float), POINTER(c_float), POINTER(c_float), POINTER(c_float)] FPDFPage_GetArtBox.restype = FPDF_BOOL -# ./fpdf_transformpage.h: 176 if hasattr(_libs['pdfium'], 'FPDFPage_TransFormWithClip'): FPDFPage_TransFormWithClip = _libs['pdfium']['FPDFPage_TransFormWithClip'] FPDFPage_TransFormWithClip.argtypes = [FPDF_PAGE, POINTER(FS_MATRIX), POINTER(FS_RECTF)] FPDFPage_TransFormWithClip.restype = FPDF_BOOL -# ./fpdf_transformpage.h: 191 if hasattr(_libs['pdfium'], 'FPDFPageObj_TransformClipPath'): FPDFPageObj_TransformClipPath = _libs['pdfium']['FPDFPageObj_TransformClipPath'] FPDFPageObj_TransformClipPath.argtypes = [FPDF_PAGEOBJECT, c_double, c_double, c_double, c_double, c_double, c_double] FPDFPageObj_TransformClipPath.restype = None -# ./fpdf_transformpage.h: 209 if hasattr(_libs['pdfium'], 'FPDFPageObj_GetClipPath'): FPDFPageObj_GetClipPath = _libs['pdfium']['FPDFPageObj_GetClipPath'] FPDFPageObj_GetClipPath.argtypes = [FPDF_PAGEOBJECT] FPDFPageObj_GetClipPath.restype = FPDF_CLIPPATH -# ./fpdf_transformpage.h: 217 if hasattr(_libs['pdfium'], 'FPDFClipPath_CountPaths'): FPDFClipPath_CountPaths = _libs['pdfium']['FPDFClipPath_CountPaths'] FPDFClipPath_CountPaths.argtypes = [FPDF_CLIPPATH] FPDFClipPath_CountPaths.restype = c_int -# ./fpdf_transformpage.h: 227 if hasattr(_libs['pdfium'], 'FPDFClipPath_CountPathSegments'): FPDFClipPath_CountPathSegments = _libs['pdfium']['FPDFClipPath_CountPathSegments'] FPDFClipPath_CountPathSegments.argtypes = [FPDF_CLIPPATH, c_int] FPDFClipPath_CountPathSegments.restype = c_int -# ./fpdf_transformpage.h: 240 if hasattr(_libs['pdfium'], 'FPDFClipPath_GetPathSegment'): FPDFClipPath_GetPathSegment = _libs['pdfium']['FPDFClipPath_GetPathSegment'] FPDFClipPath_GetPathSegment.argtypes = [FPDF_CLIPPATH, c_int, c_int] FPDFClipPath_GetPathSegment.restype = FPDF_PATHSEGMENT -# ./fpdf_transformpage.h: 253 if hasattr(_libs['pdfium'], 'FPDF_CreateClipPath'): FPDF_CreateClipPath = _libs['pdfium']['FPDF_CreateClipPath'] FPDF_CreateClipPath.argtypes = [c_float, c_float, c_float, c_float] FPDF_CreateClipPath.restype = FPDF_CLIPPATH -# ./fpdf_transformpage.h: 261 if hasattr(_libs['pdfium'], 'FPDF_DestroyClipPath'): FPDF_DestroyClipPath = _libs['pdfium']['FPDF_DestroyClipPath'] FPDF_DestroyClipPath.argtypes = [FPDF_CLIPPATH] FPDF_DestroyClipPath.restype = None -# ./fpdf_transformpage.h: 271 if hasattr(_libs['pdfium'], 'FPDFPage_InsertClipPath'): FPDFPage_InsertClipPath = _libs['pdfium']['FPDFPage_InsertClipPath'] FPDFPage_InsertClipPath.argtypes = [FPDF_PAGE, FPDF_CLIPPATH] FPDFPage_InsertClipPath.restype = None -# ./fpdfview.h: 36 FPDF_OBJECT_UNKNOWN = 0 -# ./fpdfview.h: 37 FPDF_OBJECT_BOOLEAN = 1 -# ./fpdfview.h: 38 FPDF_OBJECT_NUMBER = 2 -# ./fpdfview.h: 39 FPDF_OBJECT_STRING = 3 -# ./fpdfview.h: 40 FPDF_OBJECT_NAME = 4 -# ./fpdfview.h: 41 FPDF_OBJECT_ARRAY = 5 -# ./fpdfview.h: 42 FPDF_OBJECT_DICTIONARY = 6 -# ./fpdfview.h: 43 FPDF_OBJECT_STREAM = 7 -# ./fpdfview.h: 44 FPDF_OBJECT_NULLOBJ = 8 -# ./fpdfview.h: 45 FPDF_OBJECT_REFERENCE = 9 -# ./fpdfview.h: 327 FPDF_POLICY_MACHINETIME_ACCESS = 0 -# ./fpdfview.h: 583 FPDF_ERR_SUCCESS = 0 -# ./fpdfview.h: 584 FPDF_ERR_UNKNOWN = 1 -# ./fpdfview.h: 585 FPDF_ERR_FILE = 2 -# ./fpdfview.h: 586 FPDF_ERR_FORMAT = 3 -# ./fpdfview.h: 587 FPDF_ERR_PASSWORD = 4 -# ./fpdfview.h: 588 FPDF_ERR_SECURITY = 5 -# ./fpdfview.h: 589 FPDF_ERR_PAGE = 6 -# ./fpdfview.h: 591 FPDF_ERR_XFALOAD = 7 -# ./fpdfview.h: 592 FPDF_ERR_XFALAYOUT = 8 -# ./fpdfview.h: 790 FPDF_ANNOT = 0x01 -# ./fpdfview.h: 793 FPDF_LCD_TEXT = 0x02 -# ./fpdfview.h: 795 FPDF_NO_NATIVETEXT = 0x04 -# ./fpdfview.h: 797 FPDF_GRAYSCALE = 0x08 -# ./fpdfview.h: 799 FPDF_DEBUG_INFO = 0x80 -# ./fpdfview.h: 801 FPDF_NO_CATCH = 0x100 -# ./fpdfview.h: 803 FPDF_RENDER_LIMITEDIMAGECACHE = 0x200 -# ./fpdfview.h: 805 FPDF_RENDER_FORCEHALFTONE = 0x400 -# ./fpdfview.h: 807 FPDF_PRINTING = 0x800 -# ./fpdfview.h: 810 FPDF_RENDER_NO_SMOOTHTEXT = 0x1000 -# ./fpdfview.h: 812 FPDF_RENDER_NO_SMOOTHIMAGE = 0x2000 -# ./fpdfview.h: 814 FPDF_RENDER_NO_SMOOTHPATH = 0x4000 -# ./fpdfview.h: 817 FPDF_REVERSE_BYTE_ORDER = 0x10 -# ./fpdfview.h: 821 FPDF_CONVERT_FILL_TO_STROKE = 0x20 -# ./fpdfview.h: 1083 FPDFBitmap_Unknown = 0 -# ./fpdfview.h: 1085 FPDFBitmap_Gray = 1 -# ./fpdfview.h: 1087 FPDFBitmap_BGR = 2 -# ./fpdfview.h: 1089 FPDFBitmap_BGRx = 3 -# ./fpdfview.h: 1091 FPDFBitmap_BGRA = 4 -# ./fpdf_formfill.h: 16 FORMTYPE_NONE = 0 -# ./fpdf_formfill.h: 17 FORMTYPE_ACRO_FORM = 1 -# ./fpdf_formfill.h: 18 FORMTYPE_XFA_FULL = 2 -# ./fpdf_formfill.h: 19 FORMTYPE_XFA_FOREGROUND = 3 -# ./fpdf_formfill.h: 21 FORMTYPE_COUNT = 4 -# ./fpdf_formfill.h: 23 JSPLATFORM_ALERT_BUTTON_OK = 0 -# ./fpdf_formfill.h: 24 JSPLATFORM_ALERT_BUTTON_OKCANCEL = 1 -# ./fpdf_formfill.h: 25 JSPLATFORM_ALERT_BUTTON_YESNO = 2 -# ./fpdf_formfill.h: 26 JSPLATFORM_ALERT_BUTTON_YESNOCANCEL = 3 -# ./fpdf_formfill.h: 27 JSPLATFORM_ALERT_BUTTON_DEFAULT = JSPLATFORM_ALERT_BUTTON_OK -# ./fpdf_formfill.h: 29 JSPLATFORM_ALERT_ICON_ERROR = 0 -# ./fpdf_formfill.h: 30 JSPLATFORM_ALERT_ICON_WARNING = 1 -# ./fpdf_formfill.h: 31 JSPLATFORM_ALERT_ICON_QUESTION = 2 -# ./fpdf_formfill.h: 32 JSPLATFORM_ALERT_ICON_STATUS = 3 -# ./fpdf_formfill.h: 33 JSPLATFORM_ALERT_ICON_ASTERISK = 4 -# ./fpdf_formfill.h: 34 JSPLATFORM_ALERT_ICON_DEFAULT = JSPLATFORM_ALERT_ICON_ERROR -# ./fpdf_formfill.h: 36 JSPLATFORM_ALERT_RETURN_OK = 1 -# ./fpdf_formfill.h: 37 JSPLATFORM_ALERT_RETURN_CANCEL = 2 -# ./fpdf_formfill.h: 38 JSPLATFORM_ALERT_RETURN_NO = 3 -# ./fpdf_formfill.h: 39 JSPLATFORM_ALERT_RETURN_YES = 4 -# ./fpdf_formfill.h: 41 JSPLATFORM_BEEP_ERROR = 0 -# ./fpdf_formfill.h: 42 JSPLATFORM_BEEP_WARNING = 1 -# ./fpdf_formfill.h: 43 JSPLATFORM_BEEP_QUESTION = 2 -# ./fpdf_formfill.h: 44 JSPLATFORM_BEEP_STATUS = 3 -# ./fpdf_formfill.h: 45 JSPLATFORM_BEEP_DEFAULT = 4 -# ./fpdf_formfill.h: 303 FXCT_ARROW = 0 -# ./fpdf_formfill.h: 304 FXCT_NESW = 1 -# ./fpdf_formfill.h: 305 FXCT_NWSE = 2 -# ./fpdf_formfill.h: 306 FXCT_VBEAM = 3 -# ./fpdf_formfill.h: 307 FXCT_HBEAM = 4 -# ./fpdf_formfill.h: 308 FXCT_HAND = 5 -# ./fpdf_formfill.h: 333 FXFA_PAGEVIEWEVENT_POSTADDED = 1 -# ./fpdf_formfill.h: 334 FXFA_PAGEVIEWEVENT_POSTREMOVED = 3 -# ./fpdf_formfill.h: 337 FXFA_MENU_COPY = 1 -# ./fpdf_formfill.h: 338 FXFA_MENU_CUT = 2 -# ./fpdf_formfill.h: 339 FXFA_MENU_SELECTALL = 4 -# ./fpdf_formfill.h: 340 FXFA_MENU_UNDO = 8 -# ./fpdf_formfill.h: 341 FXFA_MENU_REDO = 16 -# ./fpdf_formfill.h: 342 FXFA_MENU_PASTE = 32 -# ./fpdf_formfill.h: 345 FXFA_SAVEAS_XML = 1 -# ./fpdf_formfill.h: 346 FXFA_SAVEAS_XDP = 2 -# ./fpdf_formfill.h: 1132 FPDFDOC_AACTION_WC = 0x10 -# ./fpdf_formfill.h: 1133 FPDFDOC_AACTION_WS = 0x11 -# ./fpdf_formfill.h: 1134 FPDFDOC_AACTION_DS = 0x12 -# ./fpdf_formfill.h: 1135 FPDFDOC_AACTION_WP = 0x13 -# ./fpdf_formfill.h: 1136 FPDFDOC_AACTION_DP = 0x14 -# ./fpdf_formfill.h: 1157 FPDFPAGE_AACTION_OPEN = 0 -# ./fpdf_formfill.h: 1158 FPDFPAGE_AACTION_CLOSE = 1 -# ./fpdf_formfill.h: 1577 FPDF_FORMFIELD_UNKNOWN = 0 -# ./fpdf_formfill.h: 1578 FPDF_FORMFIELD_PUSHBUTTON = 1 -# ./fpdf_formfill.h: 1579 FPDF_FORMFIELD_CHECKBOX = 2 -# ./fpdf_formfill.h: 1580 FPDF_FORMFIELD_RADIOBUTTON = 3 -# ./fpdf_formfill.h: 1581 FPDF_FORMFIELD_COMBOBOX = 4 -# ./fpdf_formfill.h: 1582 FPDF_FORMFIELD_LISTBOX = 5 -# ./fpdf_formfill.h: 1583 FPDF_FORMFIELD_TEXTFIELD = 6 -# ./fpdf_formfill.h: 1584 FPDF_FORMFIELD_SIGNATURE = 7 -# ./fpdf_formfill.h: 1586 FPDF_FORMFIELD_XFA = 8 -# ./fpdf_formfill.h: 1587 FPDF_FORMFIELD_XFA_CHECKBOX = 9 -# ./fpdf_formfill.h: 1588 FPDF_FORMFIELD_XFA_COMBOBOX = 10 -# ./fpdf_formfill.h: 1589 FPDF_FORMFIELD_XFA_IMAGEFIELD = 11 -# ./fpdf_formfill.h: 1590 FPDF_FORMFIELD_XFA_LISTBOX = 12 -# ./fpdf_formfill.h: 1591 FPDF_FORMFIELD_XFA_PUSHBUTTON = 13 -# ./fpdf_formfill.h: 1592 FPDF_FORMFIELD_XFA_SIGNATURE = 14 -# ./fpdf_formfill.h: 1593 FPDF_FORMFIELD_XFA_TEXTFIELD = 15 -# ./fpdf_formfill.h: 1597 FPDF_FORMFIELD_COUNT = 16 -# ./fpdf_formfill.h: 1603 def IS_XFA_FORMFIELD(type): return ((((((((type == FPDF_FORMFIELD_XFA) or (type == FPDF_FORMFIELD_XFA_CHECKBOX)) or (type == FPDF_FORMFIELD_XFA_COMBOBOX)) or (type == FPDF_FORMFIELD_XFA_IMAGEFIELD)) or (type == FPDF_FORMFIELD_XFA_LISTBOX)) or (type == FPDF_FORMFIELD_XFA_PUSHBUTTON)) or (type == FPDF_FORMFIELD_XFA_SIGNATURE)) or (type == FPDF_FORMFIELD_XFA_TEXTFIELD)) -# ./fpdf_annot.h: 20 FPDF_ANNOT_UNKNOWN = 0 -# ./fpdf_annot.h: 21 FPDF_ANNOT_TEXT = 1 -# ./fpdf_annot.h: 22 FPDF_ANNOT_LINK = 2 -# ./fpdf_annot.h: 23 FPDF_ANNOT_FREETEXT = 3 -# ./fpdf_annot.h: 24 FPDF_ANNOT_LINE = 4 -# ./fpdf_annot.h: 25 FPDF_ANNOT_SQUARE = 5 -# ./fpdf_annot.h: 26 FPDF_ANNOT_CIRCLE = 6 -# ./fpdf_annot.h: 27 FPDF_ANNOT_POLYGON = 7 -# ./fpdf_annot.h: 28 FPDF_ANNOT_POLYLINE = 8 -# ./fpdf_annot.h: 29 FPDF_ANNOT_HIGHLIGHT = 9 -# ./fpdf_annot.h: 30 FPDF_ANNOT_UNDERLINE = 10 -# ./fpdf_annot.h: 31 FPDF_ANNOT_SQUIGGLY = 11 -# ./fpdf_annot.h: 32 FPDF_ANNOT_STRIKEOUT = 12 -# ./fpdf_annot.h: 33 FPDF_ANNOT_STAMP = 13 -# ./fpdf_annot.h: 34 FPDF_ANNOT_CARET = 14 -# ./fpdf_annot.h: 35 FPDF_ANNOT_INK = 15 -# ./fpdf_annot.h: 36 FPDF_ANNOT_POPUP = 16 -# ./fpdf_annot.h: 37 FPDF_ANNOT_FILEATTACHMENT = 17 -# ./fpdf_annot.h: 38 FPDF_ANNOT_SOUND = 18 -# ./fpdf_annot.h: 39 FPDF_ANNOT_MOVIE = 19 -# ./fpdf_annot.h: 40 FPDF_ANNOT_WIDGET = 20 -# ./fpdf_annot.h: 41 FPDF_ANNOT_SCREEN = 21 -# ./fpdf_annot.h: 42 FPDF_ANNOT_PRINTERMARK = 22 -# ./fpdf_annot.h: 43 FPDF_ANNOT_TRAPNET = 23 -# ./fpdf_annot.h: 44 FPDF_ANNOT_WATERMARK = 24 -# ./fpdf_annot.h: 45 FPDF_ANNOT_THREED = 25 -# ./fpdf_annot.h: 46 FPDF_ANNOT_RICHMEDIA = 26 -# ./fpdf_annot.h: 47 FPDF_ANNOT_XFAWIDGET = 27 -# ./fpdf_annot.h: 48 FPDF_ANNOT_REDACT = 28 -# ./fpdf_annot.h: 51 FPDF_ANNOT_FLAG_NONE = 0 -# ./fpdf_annot.h: 52 FPDF_ANNOT_FLAG_INVISIBLE = (1 << 0) -# ./fpdf_annot.h: 53 FPDF_ANNOT_FLAG_HIDDEN = (1 << 1) -# ./fpdf_annot.h: 54 FPDF_ANNOT_FLAG_PRINT = (1 << 2) -# ./fpdf_annot.h: 55 FPDF_ANNOT_FLAG_NOZOOM = (1 << 3) -# ./fpdf_annot.h: 56 FPDF_ANNOT_FLAG_NOROTATE = (1 << 4) -# ./fpdf_annot.h: 57 FPDF_ANNOT_FLAG_NOVIEW = (1 << 5) -# ./fpdf_annot.h: 58 FPDF_ANNOT_FLAG_READONLY = (1 << 6) -# ./fpdf_annot.h: 59 FPDF_ANNOT_FLAG_LOCKED = (1 << 7) -# ./fpdf_annot.h: 60 FPDF_ANNOT_FLAG_TOGGLENOVIEW = (1 << 8) -# ./fpdf_annot.h: 62 FPDF_ANNOT_APPEARANCEMODE_NORMAL = 0 -# ./fpdf_annot.h: 63 FPDF_ANNOT_APPEARANCEMODE_ROLLOVER = 1 -# ./fpdf_annot.h: 64 FPDF_ANNOT_APPEARANCEMODE_DOWN = 2 -# ./fpdf_annot.h: 65 FPDF_ANNOT_APPEARANCEMODE_COUNT = 3 -# ./fpdf_annot.h: 69 FPDF_FORMFLAG_NONE = 0 -# ./fpdf_annot.h: 70 FPDF_FORMFLAG_READONLY = (1 << 0) -# ./fpdf_annot.h: 71 FPDF_FORMFLAG_REQUIRED = (1 << 1) -# ./fpdf_annot.h: 72 FPDF_FORMFLAG_NOEXPORT = (1 << 2) -# ./fpdf_annot.h: 76 FPDF_FORMFLAG_TEXT_MULTILINE = (1 << 12) -# ./fpdf_annot.h: 77 FPDF_FORMFLAG_TEXT_PASSWORD = (1 << 13) -# ./fpdf_annot.h: 81 FPDF_FORMFLAG_CHOICE_COMBO = (1 << 17) -# ./fpdf_annot.h: 82 FPDF_FORMFLAG_CHOICE_EDIT = (1 << 18) -# ./fpdf_annot.h: 83 FPDF_FORMFLAG_CHOICE_MULTI_SELECT = (1 << 21) -# ./fpdf_annot.h: 90 FPDF_ANNOT_AACTION_KEY_STROKE = 12 -# ./fpdf_annot.h: 91 FPDF_ANNOT_AACTION_FORMAT = 13 -# ./fpdf_annot.h: 92 FPDF_ANNOT_AACTION_VALIDATE = 14 -# ./fpdf_annot.h: 93 FPDF_ANNOT_AACTION_CALCULATE = 15 -# ./fpdf_dataavail.h: 15 PDF_LINEARIZATION_UNKNOWN = (-1) -# ./fpdf_dataavail.h: 16 PDF_NOT_LINEARIZED = 0 -# ./fpdf_dataavail.h: 17 PDF_LINEARIZED = 1 -# ./fpdf_dataavail.h: 19 PDF_DATA_ERROR = (-1) -# ./fpdf_dataavail.h: 20 PDF_DATA_NOTAVAIL = 0 -# ./fpdf_dataavail.h: 21 PDF_DATA_AVAIL = 1 -# ./fpdf_dataavail.h: 23 PDF_FORM_ERROR = (-1) -# ./fpdf_dataavail.h: 24 PDF_FORM_NOTAVAIL = 0 -# ./fpdf_dataavail.h: 25 PDF_FORM_AVAIL = 1 -# ./fpdf_dataavail.h: 26 PDF_FORM_NOTEXIST = 2 -# ./fpdf_doc.h: 18 PDFACTION_UNSUPPORTED = 0 -# ./fpdf_doc.h: 20 PDFACTION_GOTO = 1 -# ./fpdf_doc.h: 22 PDFACTION_REMOTEGOTO = 2 -# ./fpdf_doc.h: 24 PDFACTION_URI = 3 -# ./fpdf_doc.h: 26 PDFACTION_LAUNCH = 4 -# ./fpdf_doc.h: 28 PDFACTION_EMBEDDEDGOTO = 5 -# ./fpdf_doc.h: 31 PDFDEST_VIEW_UNKNOWN_MODE = 0 -# ./fpdf_doc.h: 32 PDFDEST_VIEW_XYZ = 1 -# ./fpdf_doc.h: 33 PDFDEST_VIEW_FIT = 2 -# ./fpdf_doc.h: 34 PDFDEST_VIEW_FITH = 3 -# ./fpdf_doc.h: 35 PDFDEST_VIEW_FITV = 4 -# ./fpdf_doc.h: 36 PDFDEST_VIEW_FITR = 5 -# ./fpdf_doc.h: 37 PDFDEST_VIEW_FITB = 6 -# ./fpdf_doc.h: 38 PDFDEST_VIEW_FITBH = 7 -# ./fpdf_doc.h: 39 PDFDEST_VIEW_FITBV = 8 -# ./fpdf_edit.h: 15 def FPDF_ARGB(a, r, g, b): return uint32_t(((((uint32_t(b).value & 0xff) | ((uint32_t(g).value & 0xff) << 8)) | ((uint32_t(r).value & 0xff) << 16)) | ((uint32_t(a).value & 0xff) << 24))).value -# ./fpdf_edit.h: 18 def FPDF_GetBValue(argb): return uint8_t(argb).value -# ./fpdf_edit.h: 19 def FPDF_GetGValue(argb): return uint8_t((uint16_t(argb).value >> 8)).value -# ./fpdf_edit.h: 20 def FPDF_GetRValue(argb): return uint8_t((argb >> 16)).value -# ./fpdf_edit.h: 21 def FPDF_GetAValue(argb): return uint8_t((argb >> 24)).value -# ./fpdf_edit.h: 24 FPDF_COLORSPACE_UNKNOWN = 0 -# ./fpdf_edit.h: 25 FPDF_COLORSPACE_DEVICEGRAY = 1 -# ./fpdf_edit.h: 26 FPDF_COLORSPACE_DEVICERGB = 2 -# ./fpdf_edit.h: 27 FPDF_COLORSPACE_DEVICECMYK = 3 -# ./fpdf_edit.h: 28 FPDF_COLORSPACE_CALGRAY = 4 -# ./fpdf_edit.h: 29 FPDF_COLORSPACE_CALRGB = 5 -# ./fpdf_edit.h: 30 FPDF_COLORSPACE_LAB = 6 -# ./fpdf_edit.h: 31 FPDF_COLORSPACE_ICCBASED = 7 -# ./fpdf_edit.h: 32 FPDF_COLORSPACE_SEPARATION = 8 -# ./fpdf_edit.h: 33 FPDF_COLORSPACE_DEVICEN = 9 -# ./fpdf_edit.h: 34 FPDF_COLORSPACE_INDEXED = 10 -# ./fpdf_edit.h: 35 FPDF_COLORSPACE_PATTERN = 11 -# ./fpdf_edit.h: 38 FPDF_PAGEOBJ_UNKNOWN = 0 -# ./fpdf_edit.h: 39 FPDF_PAGEOBJ_TEXT = 1 -# ./fpdf_edit.h: 40 FPDF_PAGEOBJ_PATH = 2 -# ./fpdf_edit.h: 41 FPDF_PAGEOBJ_IMAGE = 3 -# ./fpdf_edit.h: 42 FPDF_PAGEOBJ_SHADING = 4 -# ./fpdf_edit.h: 43 FPDF_PAGEOBJ_FORM = 5 -# ./fpdf_edit.h: 46 FPDF_SEGMENT_UNKNOWN = (-1) -# ./fpdf_edit.h: 47 FPDF_SEGMENT_LINETO = 0 -# ./fpdf_edit.h: 48 FPDF_SEGMENT_BEZIERTO = 1 -# ./fpdf_edit.h: 49 FPDF_SEGMENT_MOVETO = 2 -# ./fpdf_edit.h: 51 FPDF_FILLMODE_NONE = 0 -# ./fpdf_edit.h: 52 FPDF_FILLMODE_ALTERNATE = 1 -# ./fpdf_edit.h: 53 FPDF_FILLMODE_WINDING = 2 -# ./fpdf_edit.h: 55 FPDF_FONT_TYPE1 = 1 -# ./fpdf_edit.h: 56 FPDF_FONT_TRUETYPE = 2 -# ./fpdf_edit.h: 58 FPDF_LINECAP_BUTT = 0 -# ./fpdf_edit.h: 59 FPDF_LINECAP_ROUND = 1 -# ./fpdf_edit.h: 60 FPDF_LINECAP_PROJECTING_SQUARE = 2 -# ./fpdf_edit.h: 62 FPDF_LINEJOIN_MITER = 0 -# ./fpdf_edit.h: 63 FPDF_LINEJOIN_ROUND = 1 -# ./fpdf_edit.h: 64 FPDF_LINEJOIN_BEVEL = 2 -# ./fpdf_edit.h: 67 FPDF_PRINTMODE_EMF = 0 -# ./fpdf_edit.h: 68 FPDF_PRINTMODE_TEXTONLY = 1 -# ./fpdf_edit.h: 69 FPDF_PRINTMODE_POSTSCRIPT2 = 2 -# ./fpdf_edit.h: 70 FPDF_PRINTMODE_POSTSCRIPT3 = 3 -# ./fpdf_edit.h: 71 FPDF_PRINTMODE_POSTSCRIPT2_PASSTHROUGH = 4 -# ./fpdf_edit.h: 72 FPDF_PRINTMODE_POSTSCRIPT3_PASSTHROUGH = 5 -# ./fpdf_edit.h: 73 FPDF_PRINTMODE_EMF_IMAGE_MASKS = 6 -# ./fpdf_edit.h: 74 FPDF_PRINTMODE_POSTSCRIPT3_TYPE42 = 7 -# ./fpdf_edit.h: 75 FPDF_PRINTMODE_POSTSCRIPT3_TYPE42_PASSTHROUGH = 8 -# ./fpdf_ext.h: 20 FPDF_UNSP_DOC_XFAFORM = 1 -# ./fpdf_ext.h: 22 FPDF_UNSP_DOC_PORTABLECOLLECTION = 2 -# ./fpdf_ext.h: 24 FPDF_UNSP_DOC_ATTACHMENT = 3 -# ./fpdf_ext.h: 26 FPDF_UNSP_DOC_SECURITY = 4 -# ./fpdf_ext.h: 28 FPDF_UNSP_DOC_SHAREDREVIEW = 5 -# ./fpdf_ext.h: 30 FPDF_UNSP_DOC_SHAREDFORM_ACROBAT = 6 -# ./fpdf_ext.h: 32 FPDF_UNSP_DOC_SHAREDFORM_FILESYSTEM = 7 -# ./fpdf_ext.h: 34 FPDF_UNSP_DOC_SHAREDFORM_EMAIL = 8 -# ./fpdf_ext.h: 36 FPDF_UNSP_ANNOT_3DANNOT = 11 -# ./fpdf_ext.h: 38 FPDF_UNSP_ANNOT_MOVIE = 12 -# ./fpdf_ext.h: 40 FPDF_UNSP_ANNOT_SOUND = 13 -# ./fpdf_ext.h: 42 FPDF_UNSP_ANNOT_SCREEN_MEDIA = 14 -# ./fpdf_ext.h: 44 FPDF_UNSP_ANNOT_SCREEN_RICHMEDIA = 15 -# ./fpdf_ext.h: 46 FPDF_UNSP_ANNOT_ATTACHMENT = 16 -# ./fpdf_ext.h: 48 FPDF_UNSP_ANNOT_SIG = 17 -# ./fpdf_ext.h: 92 PAGEMODE_UNKNOWN = (-1) -# ./fpdf_ext.h: 94 PAGEMODE_USENONE = 0 -# ./fpdf_ext.h: 96 PAGEMODE_USEOUTLINES = 1 -# ./fpdf_ext.h: 98 PAGEMODE_USETHUMBS = 2 -# ./fpdf_ext.h: 100 PAGEMODE_FULLSCREEN = 3 -# ./fpdf_ext.h: 102 PAGEMODE_USEOC = 4 -# ./fpdf_ext.h: 104 PAGEMODE_USEATTACHMENTS = 5 -# ./fpdf_flatten.h: 14 FLATTEN_FAIL = 0 -# ./fpdf_flatten.h: 16 FLATTEN_SUCCESS = 1 -# ./fpdf_flatten.h: 18 FLATTEN_NOTHINGTODO = 2 -# ./fpdf_flatten.h: 21 FLAT_NORMALDISPLAY = 0 -# ./fpdf_flatten.h: 23 FLAT_PRINT = 1 -# ./fpdf_progressive.h: 15 FPDF_RENDER_READY = 0 -# ./fpdf_progressive.h: 16 FPDF_RENDER_TOBECONTINUED = 1 -# ./fpdf_progressive.h: 17 FPDF_RENDER_DONE = 2 -# ./fpdf_progressive.h: 18 FPDF_RENDER_FAILED = 3 -# ./fpdf_save.h: 45 FPDF_INCREMENTAL = 1 -# ./fpdf_save.h: 46 FPDF_NO_INCREMENTAL = 2 -# ./fpdf_save.h: 47 FPDF_REMOVE_SECURITY = 3 -# ./fpdf_sysfontinfo.h: 17 FXFONT_ANSI_CHARSET = 0 -# ./fpdf_sysfontinfo.h: 18 FXFONT_DEFAULT_CHARSET = 1 -# ./fpdf_sysfontinfo.h: 19 FXFONT_SYMBOL_CHARSET = 2 -# ./fpdf_sysfontinfo.h: 20 FXFONT_SHIFTJIS_CHARSET = 128 -# ./fpdf_sysfontinfo.h: 21 FXFONT_HANGEUL_CHARSET = 129 -# ./fpdf_sysfontinfo.h: 22 FXFONT_GB2312_CHARSET = 134 -# ./fpdf_sysfontinfo.h: 23 FXFONT_CHINESEBIG5_CHARSET = 136 -# ./fpdf_sysfontinfo.h: 24 FXFONT_GREEK_CHARSET = 161 -# ./fpdf_sysfontinfo.h: 25 FXFONT_VIETNAMESE_CHARSET = 163 -# ./fpdf_sysfontinfo.h: 26 FXFONT_HEBREW_CHARSET = 177 -# ./fpdf_sysfontinfo.h: 27 FXFONT_ARABIC_CHARSET = 178 -# ./fpdf_sysfontinfo.h: 28 FXFONT_CYRILLIC_CHARSET = 204 -# ./fpdf_sysfontinfo.h: 29 FXFONT_THAI_CHARSET = 222 -# ./fpdf_sysfontinfo.h: 30 FXFONT_EASTERNEUROPEAN_CHARSET = 238 -# ./fpdf_sysfontinfo.h: 33 FXFONT_FF_FIXEDPITCH = (1 << 0) -# ./fpdf_sysfontinfo.h: 34 FXFONT_FF_ROMAN = (1 << 4) -# ./fpdf_sysfontinfo.h: 35 FXFONT_FF_SCRIPT = (4 << 4) -# ./fpdf_sysfontinfo.h: 38 FXFONT_FW_NORMAL = 400 -# ./fpdf_sysfontinfo.h: 39 FXFONT_FW_BOLD = 700 -# ./fpdf_text.h: 483 FPDF_MATCHCASE = 0x00000001 -# ./fpdf_text.h: 485 FPDF_MATCHWHOLEWORD = 0x00000002 -# ./fpdf_text.h: 487 FPDF_CONSECUTIVE = 0x00000004 -# ./fpdf_edit.h: 93 FPDF_IMAGEOBJ_METADATA = struct_FPDF_IMAGEOBJ_METADATA # -- End header members -- diff --git a/autorelease/config.json b/autorelease/config.json index d6a9c8477..86eb3c8e2 100644 --- a/autorelease/config.json +++ b/autorelease/config.json @@ -1,5 +1,5 @@ { - "beta": false, - "major": false, + "beta": true, + "major": true, "humble": null -} \ No newline at end of file +} diff --git a/conda/craft_conda_pkgs.py b/conda/craft_conda_pkgs.py new file mode 100644 index 000000000..1bf92616d --- /dev/null +++ b/conda/craft_conda_pkgs.py @@ -0,0 +1,177 @@ +# SPDX-FileCopyrightText: 2025 geisserml +# SPDX-License-Identifier: Apache-2.0 OR BSD-3-Clause + +import sys +import argparse +from pathlib import Path +from functools import partial + +sys.path.insert(0, str(Path(__file__).parents[1]/"setupsrc")) +from pypdfium2_setup.base import * +from pypdfium2_setup.emplace import prepare_setup +from pypdfium2_setup.craft import ArtifactStash + +CondaDir = ProjectDir / "conda" +CondaRaw_BuildNumF = CondaDir / "raw" / "build_num.txt" + +T_RAW = "raw" +T_HELPERS = "helpers" + + +def main(): + parser = argparse.ArgumentParser( + description = "Craft conda packages for pypdfium2" + ) + parser.add_argument( + "type", + choices = (T_RAW, T_HELPERS), + help = "The package type to build (raw or helpers)", + ) + parser.add_argument("--pdfium-ver", default=None) + parser.add_argument("--new-only", action="store_true") + + args = parser.parse_args() + if args.type == T_RAW: + with ArtifactStash(): + main_conda_raw(args) + elif args.type == T_HELPERS: + assert not args.new_only, "--new-only / buildnum handling not implemented for helpers package" + main_conda_helpers(args) + else: + assert False # unreached, handled by argparse + + +def _handle_ver(args, get_latest): + if not args.pdfium_ver or args.pdfium_ver == "latest": + args.pdfium_ver = get_latest() + else: + args.pdfium_ver = int(args.pdfium_ver) + + +def main_conda_raw(args): + + _handle_ver(args, CondaPkgVer.get_latest_pdfium) + os.environ["PDFIUM_SHORT"] = str(args.pdfium_ver) + os.environ["PDFIUM_FULL"] = ".".join([str(v) for v in PdfiumVer.to_full(args.pdfium_ver)]) + os.environ["BUILD_NUM"] = str(_get_build_num(args)) + + emplace_func = partial(prepare_setup, ExtPlats.system, args.pdfium_ver, use_v8=None) + with CondaExtPlatfiles(emplace_func): + run_conda_build(CondaDir/"raw", CondaDir/"raw"/"out", args=["--override-channels", "-c", "bblanchon", "-c", "defaults"]) + + +def main_conda_helpers(args): + + _handle_ver(args, CondaPkgVer.get_latest_bindings) + helpers_info = parse_git_tag() + os.environ["M_HELPERS_VER"] = merge_tag(helpers_info, "py") + + # Set the current pdfium version as upper boundary, for inherent API safety. + # pdfium does not do semantic versioning, so upward flexibility is difficult. + os.environ["PDFIUM_MAX"] = str(args.pdfium_ver) + + # NOTE To build with a local pypdfium2_raw, add the args below for the source dir, and remove the pypdfium2-team prefix from the helpers recipe's run requirements + # args=["-c", CondaDir/"raw"/"out"] + run_conda_build(CondaDir/"helpers", CondaDir/"helpers"/"out", args=["--override-channels", "-c", "pypdfium2-team", "-c", "bblanchon", "-c", "defaults"]) + + +def run_conda_build(recipe_dir, out_dir, args=()): + with TmpCommitCtx(): + run_cmd(["conda", "build", recipe_dir, "--output-folder", out_dir, *args], cwd=ProjectDir, env=os.environ) + + +@functools.lru_cache(maxsize=2) +def run_conda_search(package, channel): + output = run_cmd(["conda", "search", "--json", package, "--override-channels", "-c", channel], cwd=None, capture=True) + return json.loads(output)[package] + + +class CondaPkgVer: + + @staticmethod + @functools.lru_cache(maxsize=2) + def _get_latest_for(package, channel, v_func): + search = run_conda_search(package, channel) + search = sorted(search, key=lambda d: v_func(d["version"]), reverse=True) + result = v_func(search[0]["version"]) + print(f"Resolved latest {channel}::{package} to {result}", file=sys.stderr) + return result + + @staticmethod + def get_latest_pdfium(): + return CondaPkgVer._get_latest_for( + "pdfium-binaries", "bblanchon", lambda v: int(v.split(".")[2]) + ) + + @staticmethod + def get_latest_bindings(): + return CondaPkgVer._get_latest_for( + "pypdfium2_raw", "pypdfium2-team", lambda v: int(v) + ) + + +def _get_build_num(args): + + # parse existing releases to automatically handle arbitrary version builds + # TODO expand to pypdfium2_helpers as well, so we could rebuild with different pdfium bounds in a workflow + + search = reversed(run_conda_search("pypdfium2_raw", "pypdfium2-team")) + + if args.new_only: + # or `args.pdfium_ver not in {...}` to allow new builds of older versions + assert args.pdfium_ver > max(int(d["version"]) for d in search), f"--new-only given, but {args.pdfium_ver} already has a build" + + # determine build number + build_num = max((d["build_number"] for d in search if int(d["version"]) == args.pdfium_ver), default=None) + build_num = 0 if build_num is None else build_num+1 + + return build_num + + +class TmpCommitCtx: + + # Work around local conda `git_url` not including uncommitted changes + # In particular, this is used to transfer data files, so we can generate them externally and don't have to conda package ctypesgen. + + # use a tmp control file so we can also undo the commit in conda's isolated clone + FILE = CondaDir / "with_tmp_commit.txt" + + def __enter__(self): + # determine if there are any modified or new files + out = run_cmd(["git", "status", "--porcelain"], capture=True, cwd=ProjectDir) + self.have_mods = bool(out) + if self.have_mods: # make tmp commit + self.FILE.touch() + run_cmd(["git", "add", "."], cwd=ProjectDir) + run_cmd(["git", "commit", "-m", "!!! tmp commit for conda-build", "-m", "make conda-build include uncommitted changes"], cwd=ProjectDir) + + @classmethod + def undo(cls): + # assuming FILE exists (promised by callers) + run_cmd(["git", "reset", "--soft", "HEAD^"], cwd=ProjectDir) + run_cmd(["git", "reset", cls.FILE], cwd=ProjectDir) + cls.FILE.unlink() + + def __exit__(self, *_): + if self.have_mods: # pop tmp commit, if any + self.undo() + + +class CondaExtPlatfiles: + + def __init__(self, emplace_func): + self.emplace_func = emplace_func + + def __enter__(self): + self.platfiles = self.emplace_func() + self.platfiles = [ModuleDir_Raw/f for f in self.platfiles] + run_cmd(["git", "add", "-f"] + [str(f) for f in self.platfiles], cwd=ProjectDir) + + def __exit__(self, *_): + run_cmd(["git", "reset"] + [str(f) for f in self.platfiles], cwd=ProjectDir) + for fp in self.platfiles: + fp.unlink() + + +if __name__ == "__main__": + main() diff --git a/conda/helpers/recipe/meta.yaml b/conda/helpers/recipe/meta.yaml index 06ae182b1..5852906cc 100644 --- a/conda/helpers/recipe/meta.yaml +++ b/conda/helpers/recipe/meta.yaml @@ -1,4 +1,4 @@ -# SPDX-FileCopyrightText: 2024 geisserml +# SPDX-FileCopyrightText: 2025 geisserml # SPDX-License-Identifier: CC-BY-4.0 {% set pdfium_max = environ["PDFIUM_MAX"] %} @@ -31,11 +31,12 @@ requirements: - wheel !=0.38.0,!=0.38.1 run: # -- Reasons for pdfium version bounds -- - # >6164 : API-breaking changes to ctypesgen + # >6635 (effectively >=6638) : new pdfium errchecks + # (before that: >6164 : API-breaking changes to ctypesgen) # !=6219 : Blacklisted due to https://crbug.com/pdfium/2112 (6205 is also affected, but not on conda due to monthly schedule) # <={{ pdfium_max }} : Prevents future versions for API safety reasons - python - - pypdfium2-team::pypdfium2_raw >6164,!=6219,<={{ pdfium_max }} + - pypdfium2-team::pypdfium2_raw >6635,!=6219,<={{ pdfium_max }} test: requires: diff --git a/conda/prepare_script.py b/conda/prepare_script.py index 513263d39..f34912e7a 100644 --- a/conda/prepare_script.py +++ b/conda/prepare_script.py @@ -1,11 +1,11 @@ -# SPDX-FileCopyrightText: 2024 geisserml +# SPDX-FileCopyrightText: 2025 geisserml # SPDX-License-Identifier: Apache-2.0 OR BSD-3-Clause import sys from pathlib import Path -sys.path.insert(0, str(Path(__file__).parents[1] / "setupsrc")) -from pypdfium2_setup.craft_packages import TmpCommitCtx +sys.path.insert(0, str(Path(__file__).parents[1] / "conda")) +from craft_conda_pkgs import TmpCommitCtx def main(): if TmpCommitCtx.FILE.exists(): diff --git a/conda/raw/minitest.py b/conda/raw/minitest.py index b86cbae87..80e508dea 100644 --- a/conda/raw/minitest.py +++ b/conda/raw/minitest.py @@ -1,4 +1,4 @@ -# SPDX-FileCopyrightText: 2024 geisserml +# SPDX-FileCopyrightText: 2025 geisserml # SPDX-License-Identifier: CC-BY-4.0 OR Apache-2.0 OR BSD-3-Clause # minimal test confirming we can call the library diff --git a/conda/raw/recipe/meta.yaml b/conda/raw/recipe/meta.yaml index 89626dc8f..5314cd28b 100644 --- a/conda/raw/recipe/meta.yaml +++ b/conda/raw/recipe/meta.yaml @@ -1,4 +1,4 @@ -# SPDX-FileCopyrightText: 2024 geisserml +# SPDX-FileCopyrightText: 2025 geisserml # SPDX-License-Identifier: CC-BY-4.0 {% set pdfium_short = environ["PDFIUM_SHORT"] %} @@ -51,7 +51,8 @@ about: summary: Python bindings to PDFium (raw, external binary) description: | This package provides raw ctypes bindings to pdfium. - Important: DO NOT PIN to an exact version, as pypdfium2_raw itself pins pdfium-binaries to achieve ABI safety. + Note, dependents should not pin this package to an exact version, as pypdfium2_raw itself pins pdfium-binaries for ABI safety reasons. + In general, pypdfium2_helpers is the canonical package (i.e. roughly equivalent to pypdfium2 on PyPI), so it is recommended to depend on that rather than on pypdfium2_raw directly. license: BSD-3-Clause, Apache-2.0 license_file: - LICENSES/BSD-3-Clause.txt diff --git a/docs/devel/changelog.md b/docs/devel/changelog.md index 27156d70c..bbd2bbc03 100644 --- a/docs/devel/changelog.md +++ b/docs/devel/changelog.md @@ -1,4 +1,4 @@ - + @@ -55,7 +55,7 @@ ## 4.26.0 (2024-01-10) - Updated PDFium from `6164` to `6233`. -- Pin ctypesgen in sdist to prevent reoccurrence of {issue}`264` / {issue}`286`. As a drawback, the pin is never committed, so the sdist is not simply reproducible at this time due to dependence on the latest commit hash of the ctypesgen fork at build time. +- Pin ctypesgen in sdist to prevent re-occurrence of {issue}`264` / {issue}`286`. As a drawback, the pin is never committed, so the sdist is not simply reproducible at this time due to dependence on the latest commit hash of the ctypesgen fork at build time. - Wheel tags: Added back `manylinux2014` in addition to `manylinux_{glibc_ver}` to be on the safe side. Suspected relation to the above issues. @@ -75,10 +75,10 @@ #### Rationale for `PdfDocument.render()` deprecation - The parallel rendering API unfortunately was an inherent design mistake: Multiprocessing is not meant to transfer large amounts of pixel data from workers to the main process. -- This was such a heavy drawback that it basically outweighed the parallelization, so there was no real performance advantage, only higher memory load. -- As a related problem, the worker pool produces bitmaps at an indepedent speed, regardless of where the receiving iteration might be, so bitmaps could queue up in memory, possibly causing an enormeous rise in memory consumption over time. This effect was pronounced e.g. with PNG saving via PIL, as exhibited in Facebook's `nougat` project. +- Bitmap transfer is so expensive that it essentially outweighed parallelization, so there was no real performance advantage, only higher memory load. +- As a related problem, the worker pool produces bitmaps at an independent speed, regardless of where the receiving iteration might be, so bitmaps could queue up in memory, possibly causing an enormeous rise in memory consumption over time. This effect was pronounced e.g. with PNG saving via PIL, as seen in Facebook's `nougat` project. - Instead, each bitmap should be processed (e.g. saved) in the job which created it. Only a minimal, final result should be sent back to the main process (e.g. a file path). -- This means we cannot reasonably provide a generic parallel renderer, instead it needs to be implemented by callers. +- This means we cannot reasonably provide a generic parallel renderer; instead it needs to be implemented by callers. - Historically, note that there had been even more faults in the implementation: * Prior to `4.22.0`, the pool was always initialized with `os.cpu_count()` processes by default, even when rendering less pages. * Prior to `4.20.0`, a full-scale input transfer was conducted on each job (rendering it unusable with bytes input). However, this can and should be done only once on process creation. diff --git a/docs/devel/changelog_staging.md b/docs/devel/changelog_staging.md index e41edd85a..25ce725aa 100644 --- a/docs/devel/changelog_staging.md +++ b/docs/devel/changelog_staging.md @@ -1,6 +1,58 @@ - + # Changelog for next release + +*API changes* +- Rendering / Bitmap + * Removed `PdfDocument.render()` (see deprecation rationale in v4.25 changelog). Instead, use `PdfPage.render()` with a loop or process pool. + * Removed `PdfBitmap.get_info()` and `PdfBitmapInfo`, which existed mainly on behalf of data transfer with `PdfDocument.render()`. Instead, take the info from the `PdfBitmap` object directly. (If using an adapter that copies, you may want to store the relevant info in variables to avoid holding a reference to the original buffer.) + * `PdfBitmap.fill_rect()`: Changed argument order. The `color` parameter now goes first. + * `PdfBitmap.to_numpy()`: If the bitmap is single-channel (grayscale), use a 2d shape to avoid needlessly wrapping each pixel value in a list. + * `PdfBitmap.from_pil()`: Removed `recopy` parameter. +- Pageobjects + * Renamed `PdfObject.get_pos()` to `.get_bounds()`. + * Renamed `PdfImage.get_size()` to `.get_px_size()`. + * `PdfImage.extract()`: Removed `fb_render` option because it does not fit in this API. If the image's rendered bitmap is desired, use `.get_bitmap(render=True)` in the first place. +- `PdfDocument.get_toc()`: Replaced `PdfOutlineItem` namedtuple with method-oriented wrapper classes `PdfBookmark` and `PdfDest`, so callers may retrieve only the properties they actually need. This is closer to pdfium's original API and exposes the underlying raw objects. Provides signed count as-is rather than splitting in `n_kids` and `is_closed`. Also distinguishes between `dest is None` and a dest with unknown mode. +- Renamed misleading `PdfMatrix.mirror()` parameters `v, h` to `invert_x, invert_y`, as the terms horizontal/vertical flip commonly refer to the transformation applied, not the axis around which is being flipped (i.e. the previous `v` meant flipping around the Y axis, which is vertical, but the resulting transform is inverting the X coordinates and thus actually horizontal). No behavior change if you did not use keyword arguments. +- `get_text_range()`: Removed implicit translation of default calls to `get_text_bounded()`, as pdfium reverted `FPDFText_GetText()` to UCS-2, which resolves the allocation concern. However, callers are encouraged to explicitly use `get_text_bounded()` for full Unicode support. +- Removed legacy version flags. + +*Improvements and new features* +- Added `PdfPosConv` and `PdfBitmap.get_posconv(page)` helper for bidirectional translation between page and bitmap coordinates. +- Added `PdfObject.get_quad_points()` to get the corner points of an image or text object. +- Exposed `PdfPage.flatten()` (previously semi-private `_flatten()`), after having found out how to correctly use it. Added check and updated docs accordingly. +- With `PdfImage.get_bitmap(render=True)`, added `scale_to_original` option (defaults to True) to temporarily scale the image to its pixel size. Thanks to Lei Zhang for the suggestion. +- Added context manager support to `PdfDocument`, so it can be used in a `with`-statement, because opening from a file path binds a file descriptor (usually on the C side), which should be released explicitly, given OS limits. +- If document loading failed, `err_code` is now assigned to the `PdfiumError` instance so callers may programmatically handle the error subtype. +- In `PdfPage.render()`, added a new option `use_bgra_on_transparency`. If there is page content with transparency, using BGR(x) may slow down PDFium. Therefore, it is recommended to set this option to True if dynamic (page-dependent) pixel format selection is acceptable. Alternatively, you might want to use only BGRA via `force_bitmap_format=pypdfium2.raw.FPDFBitmap_BGRA` (at the cost of occupying more memory compared to BGR). +- In `PdfBitmap.new_*()` methods, avoid use of `.from_raw()`, and instead call the constructor directly, as most parameters are already known on the caller side when creating a bitmap. +- In the rendering CLI, added `--invert-lightness --exclude-images` post-processing options to render with selective lightness inversion. This may be useful to achieve a "dark theme" for light PDFs while preserving different colors, but goes at the cost of performance. (PDFium also provides a color scheme option, but this only allows you to set colors for certain object types, which are then forced on all instances of the type in question. This may flatten different colors into one, leading to a loss of visual information.) +- Corrected some null pointer checks: we have to use `bool(ptr)` rather than `ptr is None`. +- Improved startup performance by deferring imports of optional dependencies to the point where they are actually needed, to avoid overhead if you do not use them. +- Simplified version classes (no API change expected). + +*Platforms* +- Experimental Android support added (cf. PEP 738). We are now packaging `android_21_arm64_v8a` wheels. Other Android targets (`armeabi_v7a`, `x86_64`, `x86`) are handled in setup as well and should implicitly download the right binaries, but we don't currently build wheels for these, due to lower relevance (`x86_64` and `x86` are emulators, i.e. only relevant to developers), and `armeabi_v7a`, `x86` not being officially supported by Python. Note, this is provided on a best effort basis, and largely untested (only arm64 Termux prior to PEP 738 has been tested on the author's phone). Please report success or failure. +- Experimental iOS support added as well (cf. PEP 730). `arm64` device and simulator, and `x86_64` simulator are now handled and should implicitly download the right binaries. However, this is untested and may not be enough to get all the way through. In particular, the PEP hints that the binary needs to be moved to a Frameworks location, in which case you'd also need to change the library search path. No iOS wheels will be provided at this time. However, if there are testers and an actual demand, iOS arm64 wheels may be enabled in the future. + +*Setup* +- Avoid needlessly calling `_get_libc_ver()`. Instead, call it only on Linux. A negative side effect of calling this unconditionally is that, on non-Linux platforms, an empty string may be returned, in which case the musllinux handler would be reached, which uses non-public API and isn't meant to be called on other platforms (though it seems to have passed). +- If packaging with `PDFIUM_PLATFORM=sourcebuild`, forward the platform tag determined by `bdist_wheel`'s wrapper, rather than using the underlying `sysconfig.get_platform()` directly. This may provide more accurate results, e.g. on macOS. + +*Project* +- Made the runfile fail fast and propagate errors via bash `-eu`. This is actually quite important to avoid potentially continuing on a broken state in CI. +- CI: Added Linux aarch64 (GH now provides free runners) and Python 3.13 to the test matrix. +- Merged `tests_old/` back into `tests/`. +- Migrated from deprecated `.reuse/dep5`/`.reuse/dep5-wheel` to more visible `REUSE.toml`/`REUSE-wheel.toml`. +- Docs: Improved logic when to include the unreleased version warning and upcoming changelog. +- Bumped minimum pdfium requirement in conda recipe to `>6635` (effectively `>=6638`), due to new errchecks that are not version-guarded. +- Cleanly split out conda packaging into an own file, and confined it to the `conda/` directory, to avoid polluting the main setup code. + + diff --git a/docs/source/changelog.rst b/docs/source/changelog.rst index b1ca25645..96041f70b 100644 --- a/docs/source/changelog.rst +++ b/docs/source/changelog.rst @@ -1,10 +1,10 @@ -.. SPDX-FileCopyrightText: 2024 geisserml +.. SPDX-FileCopyrightText: 2025 geisserml .. SPDX-License-Identifier: CC-BY-4.0 Changelog ========= -.. ifconfig:: build_type == 'latest' +.. ifconfig:: have_changes .. warning:: This is a documentation build for an unreleased version of pypdfium2, so it is possible that new changes are not logged yet. diff --git a/docs/source/conf.py b/docs/source/conf.py index 18a452957..1c3159b23 100644 --- a/docs/source/conf.py +++ b/docs/source/conf.py @@ -1,4 +1,4 @@ -# SPDX-FileCopyrightText: 2024 geisserml +# SPDX-FileCopyrightText: 2025 geisserml # SPDX-License-Identifier: CC-BY-4.0 # Configuration file for the Sphinx documentation builder. @@ -8,32 +8,21 @@ import os import sys import time -import collections +# import collections from pathlib import Path sys.path.insert(0, str(Path(__file__).parents[2] / "setupsrc")) -from pypdfium2_setup.packaging_base import ( - run_cmd, - ProjectDir, +from pypdfium2_setup.base import ( + parse_git_tag, + get_next_changelog, ) - -def _get_build_type(): - - # RTD uses git checkout --force origin/... which results in a detached HEAD state, so we cannot easily get the branch name - # Thus query for an RTD-specific environment variable instead - rtd_vn = os.environ.get("READTHEDOCS_VERSION_NAME", None) - if rtd_vn: - return rtd_vn - - branch = run_cmd(["git", "branch", "--show-current"], cwd=ProjectDir, capture=True) - if branch == "main": - return "latest" - else: - return branch - - -build_type = _get_build_type() +# RTD modifies conf.py, so we have to ignore dirty state if on RTD +is_rtd = os.environ.get("READTHEDOCS", "").lower() == "true" +tag_info = parse_git_tag() +have_changes = tag_info["n_commits"] > 0 or (tag_info["dirty"] and not is_rtd) +if get_next_changelog(): + assert have_changes project = "pypdfium2" author = "pypdfium2-team" @@ -70,7 +59,6 @@ def _get_build_type(): "members": True, "undoc-members": True, "show-inheritance": True, - # "inherited-members": True, "member-order": "bysource", } intersphinx_mapping = { @@ -81,21 +69,16 @@ def _get_build_type(): # https://www.sphinx-doc.org/en/master/usage/configuration.html#confval-rst_prolog # .. |br| raw:: html - #
-rst_prolog = """ -.. |build_type| replace:: %(build_type)s -""" % dict( - build_type = build_type, -) - - -def remove_namedtuple_aliases(app, what, name, obj, skip, options): - if type(obj) is collections._tuplegetter: - return True - return skip +rst_prolog = f""" +.. |have_changes| replace:: {have_changes} +""" +# def remove_namedtuple_aliases(app, what, name, obj, skip, options): +# if type(obj) is collections._tuplegetter: +# return True +# return skip def setup(app): - app.connect('autodoc-skip-member', remove_namedtuple_aliases) - app.add_config_value("build_type", "latest", "env") + # app.connect('autodoc-skip-member', remove_namedtuple_aliases) + app.add_config_value("have_changes", True, "env") diff --git a/docs/source/index.rst b/docs/source/index.rst index b67e35815..1668a968f 100644 --- a/docs/source/index.rst +++ b/docs/source/index.rst @@ -1,10 +1,10 @@ -.. SPDX-FileCopyrightText: 2024 geisserml +.. SPDX-FileCopyrightText: 2025 geisserml .. SPDX-License-Identifier: CC-BY-4.0 pypdfium2 ========= -Welcome to the documentation for the support model of pypdfium2 (|build_type| build). +Welcome to the documentation for the support model of pypdfium2. .. toctree:: :maxdepth: 2 @@ -16,9 +16,8 @@ Welcome to the documentation for the support model of pypdfium2 (|build_type| bu .. toctree:: :maxdepth: 1 - :caption: Progress + :caption: Release Notes - planned_changes changelog diff --git a/docs/source/planned_changes.md b/docs/source/planned_changes.md deleted file mode 100644 index 4a6ea384c..000000000 --- a/docs/source/planned_changes.md +++ /dev/null @@ -1,10 +0,0 @@ - - - - - -# Planned Changes - -To find out about possible planned changes, you can ... -* Search the codebase for `TODO(apibreak)`. -* Check if there is a development branch. If so, take a look at its changelog (`docs/devel/changelog_staging.md`). diff --git a/docs/source/python_api.rst b/docs/source/python_api.rst index 4a63522c7..7d386441f 100644 --- a/docs/source/python_api.rst +++ b/docs/source/python_api.rst @@ -1,4 +1,4 @@ -.. SPDX-FileCopyrightText: 2024 geisserml +.. SPDX-FileCopyrightText: 2025 geisserml .. SPDX-License-Identifier: CC-BY-4.0 Python API @@ -8,10 +8,10 @@ Python API Preface ******* -Thread incompatibility ----------------------- +Incompatibility with Threading +------------------------------ -PDFium is not thread-safe. It is not allowed to call pdfium functions simultaneously across different threads, not even with different documents. [#illegal_threading]_ +PDFium is inherently not thread-safe. It is not allowed to call pdfium functions simultaneously across different threads, not even with different documents. [#illegal_threading]_ However, you may still use pdfium in a threaded context if it is ensured that only a single pdfium call can be made at a time (e.g. via mutex). It is fine to do pdfium work in one thread and other work in other threads. @@ -76,9 +76,6 @@ Version .. automodule:: pypdfium2.version -.. deprecated:: 4.22 - The legacy members ``V_PYPDFIUM2, V_LIBPDFIUM, V_BUILDNAME, V_PDFIUM_IS_V8, V_LIBPDFIUM_FULL`` will be removed in version 5. - Document ******** .. automodule:: pypdfium2._helpers.document @@ -87,8 +84,8 @@ Page **** .. automodule:: pypdfium2._helpers.page -Page Objects -************ +Pageobjects +*********** .. automodule:: pypdfium2._helpers.pageobjects Text Page diff --git a/docs/source/readme.md b/docs/source/readme.md index 78c38a066..4323809c8 100644 --- a/docs/source/readme.md +++ b/docs/source/readme.md @@ -1,4 +1,4 @@ - + diff --git a/docs/source/shell_api.rst b/docs/source/shell_api.rst index adaba15a8..edea56b7a 100644 --- a/docs/source/shell_api.rst +++ b/docs/source/shell_api.rst @@ -1,4 +1,4 @@ -.. SPDX-FileCopyrightText: 2024 geisserml +.. SPDX-FileCopyrightText: 2025 geisserml .. SPDX-License-Identifier: CC-BY-4.0 Shell API @@ -46,8 +46,8 @@ Image Converter .. command-output:: pypdfium2 imgtopdf --help -Page Objects Info -***************** +Pageobjects Info +**************** .. command-output:: pypdfium2 pageobjects --help diff --git a/pyproject.toml b/pyproject.toml index b438203a8..d9f87ce3c 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,4 +1,4 @@ -# SPDX-FileCopyrightText: 2024 geisserml +# SPDX-FileCopyrightText: 2025 geisserml # SPDX-License-Identifier: Apache-2.0 OR BSD-3-Clause [build-system] diff --git a/req/converters.txt b/req/converters.txt index f1c7e2688..551d15c55 100644 --- a/req/converters.txt +++ b/req/converters.txt @@ -1,3 +1,3 @@ -# NOTE In order to use numpy, the rendering CLI further needs `opencv-python`, but we don't currently cover that internally. As the import is guarded, we don't have to require it here. +# NOTE In order to use numpy, the rendering CLI further needs `opencv-python[-headless]`, but we don't currently cover that internally. As the import is guarded, we don't have to require it here. pillow numpy diff --git a/run b/run index 61eb2feac..0ab220d11 100755 --- a/run +++ b/run @@ -1,45 +1,54 @@ -#!/usr/bin/env bash -# SPDX-FileCopyrightText: 2024 geisserml +#!/usr/bin/env -S bash -eu +# SPDX-FileCopyrightText: 2025 geisserml # SPDX-License-Identifier: Apache-2.0 OR BSD-3-Clause -# FIXME can we support running multiple commands at once ? -# FIXME auto-completion +# TODO: auto-completion?, running multiple commands at once? args="${@:2}" function check() { - autoflake src/ setupsrc/ tests/ tests_old/ setup.py docs/source/conf.py --recursive --remove-all-unused-imports --ignore-pass-statements --ignore-init-module-imports - codespell --skip="./docs/build,./tests/resources,./tests/output,./tests_old/output,./data,./sourcebuild,./dist,./.git,__pycache__,.mypy_cache,.hypothesis" -L "tabe,splitted,fith,flate" + autoflake src/ setupsrc/ tests/ setup.py docs/source/conf.py --recursive --remove-all-unused-imports --ignore-pass-statements --ignore-init-module-imports + codespell --skip="./docs/build,./tests/resources,./tests/output,./data,./sourcebuild,./dist,./LICENSES/*,./RELEASE.md,./.git,__pycache__,.mypy_cache,.hypothesis" -L "FitH,flate" reuse lint } function clean() { - rm -rf pypdfium2*.egg-info/ src/pypdfium2*.egg-info/ build/ dist/ data/* tests/output/* tests_old/output/* conda/bundle/out/ conda/helpers/out/ conda/raw/out/ + rm -rf pypdfium2*.egg-info/ src/pypdfium2*.egg-info/ build/ dist/ data/* tests/output/* conda/bundle/out/ conda/helpers/out/ conda/raw/out/ } function packaging_pypi() { + # Note, in the future, we might want to add some `|| true` to avoid failing the release workflow just because of an autoflake/codespell/reuse complaint. clean check - # calling update_pdfium is not strictly necessary, but may improve performance because downloads are done in parallel, rather than linear with each package - python3 setupsrc/pypdfium2_setup/update_pdfium.py - python3 setupsrc/pypdfium2_setup/craft_packages.py pypi + # calling update.py is not strictly necessary, but may improve performance because downloads are done in parallel, rather than linear with each package + python3 setupsrc/pypdfium2_setup/update.py + python3 setupsrc/pypdfium2_setup/craft.py twine check dist/* # ignore W002: erroneous detection of __init__.py files as duplicates check-wheel-contents dist/*.whl --ignore W002 --toplevel "pypdfium2,pypdfium2_raw" } +function coverage_impl() { + python3 -m coverage run --omit "$OMISSIONS" -m pytest tests/ $args + python3 -m coverage report +} + set -x case $1 in test) - python3 -m pytest tests/ tests_old/ $args;; + python3 -m pytest tests/ $args;; coverage) - python3 -m coverage run --omit "tests/*,tests_old/*,src/pypdfium2_raw/bindings.py,setupsrc/*" -m pytest tests/ tests_old/ $args - python3 -m coverage report;; + OMISSIONS="src/pypdfium2_raw/bindings.py,tests/*,setupsrc/*" + coverage_impl;; + +coverage-core) + OMISSIONS="src/pypdfium2/__main__.py,src/pypdfium2/_cli/*,src/pypdfium2_raw/bindings.py,tests/*,setupsrc/*" + coverage_impl;; docs-build) python3 -m sphinx -b html docs/source docs/build/html $args;; @@ -57,13 +66,16 @@ packaging_pypi) packaging_pypi;; update) - python3 setupsrc/pypdfium2_setup/update_pdfium.py $args;; + python3 setupsrc/pypdfium2_setup/update.py $args;; craft) - python3 setupsrc/pypdfium2_setup/craft_packages.py $args;; + python3 setupsrc/pypdfium2_setup/craft.py $args;; + +craft-conda) + python3 conda/craft_conda_pkgs.py $args;; build) - python3 setupsrc/pypdfium2_setup/build_pdfium.py $args;; + python3 setupsrc/pypdfium2_setup/sourcebuild.py $args;; emplace) python3 setupsrc/pypdfium2_setup/emplace.py $args;; diff --git a/setup.cfg b/setup.cfg index bfafcaf77..3c1c6526a 100644 --- a/setup.cfg +++ b/setup.cfg @@ -1,4 +1,4 @@ -# SPDX-FileCopyrightText: 2024 geisserml +# SPDX-FileCopyrightText: 2025 geisserml # SPDX-License-Identifier: Apache-2.0 OR BSD-3-Clause # NOTE We have to use setup.cfg rather than pyproject.toml to declare static metadata because the latter does not permit a dynamic project name. diff --git a/setup.py b/setup.py index ed12dfe0f..7ebf2a79a 100644 --- a/setup.py +++ b/setup.py @@ -1,25 +1,24 @@ -# SPDX-FileCopyrightText: 2024 geisserml +# SPDX-FileCopyrightText: 2025 geisserml # SPDX-License-Identifier: Apache-2.0 OR BSD-3-Clause -# NOTE Unfortunately, pip may run setup.py multiple times with different commands (dist_info, bdist_wheel). -# However, guarding code depending on command is tricky. You'd need to be careful not to cause inconsistent data. Also, it's hard to tell which command runs first because other tools (e.g. build) may run just bdist_wheel. +# See also https://stackoverflow.com/questions/45150304/how-to-force-a-python-wheel-to-be-platform-specific-when-building-it and https://github.com/innodatalabs/redstork/blob/master/setup.py import os import sys from pathlib import Path import setuptools +from setuptools.command.build_py import build_py as build_py_orig try: from setuptools.command.bdist_wheel import bdist_wheel except ImportError: from wheel.bdist_wheel import bdist_wheel -from setuptools.command.build_py import build_py as build_py_orig sys.path.insert(0, str(Path(__file__).parent / "setupsrc")) +from pypdfium2_setup.base import * from pypdfium2_setup.emplace import prepare_setup -from pypdfium2_setup.packaging_base import * -# Use a custom distclass declaring we have a binary extension, to prevent modules from being nested in a purelib/ subdirectory in wheels. This also sets `Root-Is-Purelib: false` in the WHEEL file. +# Use a custom distclass declaring we have a binary extension, to prevent modules from being nested in a purelib/ subdirectory in wheels. This will also set `Root-Is-Purelib: false` in the WHEEL file, and make the wheel tag platform specific by default. class BinaryDistribution (setuptools.Distribution): @@ -33,9 +32,17 @@ class pypdfium_bdist (bdist_wheel): def finalize_options(self, *args, **kws): bdist_wheel.finalize_options(self, *args, **kws) + # should be handled by the distclass already, but set it again to be on the safe side + self.root_is_pure = False def get_tag(self, *args, **kws): - return "py3", "none", get_wheel_tag(pl_name) + if pl_name == ExtPlats.sourcebuild: + # if using the sourcebuild target, forward the native tag + # alternatively, the sourcebuild clause in get_wheel_tag() should be roughly equivalent (it uses sysconfig.get_platform() directly) + _py, _abi, plat_tag = bdist_wheel.get_tag(self, *args, **kws) + else: + plat_tag = get_wheel_tag(pl_name) + return "py3", "none", plat_tag return pypdfium_bdist @@ -43,14 +50,12 @@ def get_tag(self, *args, **kws): class pypdfium_build_py (build_py_orig): def run(self, *args, **kwargs): - if hasattr(self, "editable_mode"): helpers_info = read_json(ModuleDir_Helpers/VersionFN) helpers_info["is_editable"] = bool(self.editable_mode) write_json(ModuleDir_Helpers/VersionFN, helpers_info) else: - print("!!! Warning: cmdclass does not provide `editable_mode` attribute. Please file a bug report.") - + print("!!! Warning: cmdclass does not provide `editable_mode` attribute.") build_py_orig.run(self, *args, **kwargs) @@ -63,14 +68,14 @@ def run(self, *args, **kwargs): ) LICENSES_WHEEL = ( "LICENSES/LicenseRef-PdfiumThirdParty.txt", - ".reuse/dep5-wheel", + "REUSE-wheel.toml", ) LICENSES_SDIST = ( "LICENSES/LicenseRef-FairUse.txt", - ".reuse/dep5", + "REUSE.toml", ) -PLATFILES_GLOB = [BindingsFN, VersionFN, *LibnameForSystem.values()] +PLATFILES_GLOB = [BindingsFN, VersionFN, *AllLibnames] def assert_exists(dir, data_files): @@ -93,8 +98,7 @@ def run_setup(modnames, pl_name, pdfium_ver): install_requires = [], ) - if modnames == [ModuleHelpers] and pl_name != ExtPlats.sdist: - # do not do this for sdist (none) + if modnames == [ModuleHelpers]: kwargs["name"] += "_helpers" kwargs["description"] += " (helpers module)" kwargs["install_requires"] += ["pypdfium2_raw"] @@ -107,7 +111,7 @@ def run_setup(modnames, pl_name, pdfium_ver): helpers_info = get_helpers_info() if pl_name == ExtPlats.sdist: if helpers_info["dirty"]: - # ignore dirty state due to craft_packages::tmp_ctypesgen_pin() + # ignore dirty state due to craft.py::tmp_ctypesgen_pin() if int(os.environ.get("SDIST_IGNORE_DIRTY", 0)): helpers_info["dirty"] = False else: @@ -131,10 +135,10 @@ def run_setup(modnames, pl_name, pdfium_ver): kwargs["package_data"]["pypdfium2_raw"] = [VersionFN, BindingsFN] else: sys_name = plat_to_system(pl_name) - libname = LibnameForSystem[sys_name] + libname = libname_for_system(sys_name) kwargs["package_data"]["pypdfium2_raw"] = [VersionFN, BindingsFN, libname] - kwargs["cmdclass"]["bdist_wheel"] = bdist_factory(pl_name) kwargs["distclass"] = BinaryDistribution + kwargs["cmdclass"]["bdist_wheel"] = bdist_factory(pl_name) kwargs["license_files"] += LICENSES_WHEEL if "pypdfium2" in kwargs["package_data"]: @@ -150,12 +154,22 @@ def main(): pl_spec = os.environ.get(PlatSpec_EnvVar, "") modspec = os.environ.get(ModulesSpec_EnvVar, "") - # NOTE in principle, it may be possible to achieve the same as `prepared!` by just filling the data/ cache manually, but this is more explicit, formally disabling the generating code paths - with_prepare, pl_name, pdfium_ver, use_v8 = parse_pl_spec(pl_spec) - modnames = parse_modspec(modspec) + parsed_spec = parse_pl_spec(pl_spec) + if parsed_spec is None: + # TODO if we're on a unixoid system, check if it provides libreoffice with pdfium + print(f"No pre-built binaries available for this host. You may place custom binaries & bindings in data/sourcebuild/ and install with `{PlatSpec_EnvVar}=sourcebuild`.", file=sys.stderr) + raise Host._exc + + else: + + do_prepare, pl_name, pdfium_ver, use_v8 = parsed_spec + modnames = parse_modspec(modspec) + if pl_name == ExtPlats.sdist and modnames != ModulesAll: + raise ValueError(f"Partial sdist does not make sense - unset {ModulesSpec_EnvVar}.") + + if ModuleRaw in modnames and do_prepare and pl_name != ExtPlats.sdist: + prepare_setup(pl_name, pdfium_ver, use_v8) - if ModuleRaw in modnames and with_prepare and pl_name != ExtPlats.sdist: - prepare_setup(pl_name, pdfium_ver, use_v8) run_setup(modnames, pl_name, pdfium_ver) diff --git a/setupsrc/pypdfium2_setup/__init__.py b/setupsrc/pypdfium2_setup/__init__.py index b4fb1b2f0..e719c7581 100644 --- a/setupsrc/pypdfium2_setup/__init__.py +++ b/setupsrc/pypdfium2_setup/__init__.py @@ -1,2 +1,2 @@ -# SPDX-FileCopyrightText: 2024 geisserml +# SPDX-FileCopyrightText: 2025 geisserml # SPDX-License-Identifier: Apache-2.0 OR BSD-3-Clause diff --git a/setupsrc/pypdfium2_setup/autorelease.py b/setupsrc/pypdfium2_setup/autorelease.py index 5a8d3a537..3be88762b 100644 --- a/setupsrc/pypdfium2_setup/autorelease.py +++ b/setupsrc/pypdfium2_setup/autorelease.py @@ -1,5 +1,5 @@ #! /usr/bin/env python3 -# SPDX-FileCopyrightText: 2024 geisserml +# SPDX-FileCopyrightText: 2025 geisserml # SPDX-License-Identifier: Apache-2.0 OR BSD-3-Clause import sys @@ -11,8 +11,7 @@ from copy import deepcopy sys.path.insert(0, str(Path(__file__).parents[1])) -# TODO consider dotted access? -from pypdfium2_setup.packaging_base import * +from pypdfium2_setup.base import * PlacesToRegister = (AutoreleaseDir, Changelog, ChangelogStaging, RefBindingsFile) @@ -22,8 +21,17 @@ def run_local(*args, **kws): def update_refbindings(version): + + # We endeavor to make the reference bindings as universal and robust as possible, thus the symbol guards, flags, runtime libdir ["."] + system search. + # Also, skip symbol source info for cleaner diffs, so one can see pdfium API changes at a glance. + + # REFBINDINGS_FLAGS: + # Given the symbol guards, we can define all standalone feature flags. + # We don't currently define flags that depend on external headers, though it should be possible in principle by adding them to $CPATH (or equivalent). + # Note that Skia is currently a standalone flag because pdfium only provides a typedef void* for a Skia canvas and casts internally + RefBindingsFile.unlink() - build_pdfium_bindings(version, guard_symbols=True, flags=REFBINDINGS_FLAGS, allow_system_despite_libdirs=True) + build_pdfium_bindings(version, guard_symbols=True, flags=REFBINDINGS_FLAGS, search_sys_despite_libdirs=True, no_srcinfo=True) shutil.copyfile(DataDir_Bindings/BindingsFN, RefBindingsFile) assert RefBindingsFile.exists() @@ -36,7 +44,6 @@ def do_versioning(config, record, prev_helpers, new_pdfium): if prev_helpers["dirty"]: print("Warning: dirty state. This should not happen in CI.", file=sys.stderr) - # TODO actually, we care only about updates to src/ py_updates = prev_helpers["n_commits"] > 0 c_updates = record["pdfium"] < new_pdfium @@ -80,7 +87,7 @@ def do_versioning(config, record, prev_helpers, new_pdfium): return (c_updates, new_pdfium), (py_updates, new_helpers) -def log_changes(summary, prev_pdfium, new_pdfium, new_tag, beta): +def log_changes(summary, prev_pdfium, new_pdfium, new_tag, is_beta): pdfium_msg = f"## {new_tag} ({time.strftime('%Y-%m-%d')})\n\n" if prev_pdfium != new_pdfium: @@ -93,7 +100,9 @@ def log_changes(summary, prev_pdfium, new_pdfium, new_tag, beta): part_a = content[:pos].strip() + "\n" part_b = content[pos:].strip() + "\n" content = part_a + "\n\n" + pdfium_msg + "\n" - if beta is None: + if is_beta: + content += f"- See the beta release notes on GitHub [here](https://github.com/pypdfium2-team/pypdfium2/releases/tag/{new_tag})\n" + else: content += summary content += "\n\n" + part_b Changelog.write_text(content) @@ -107,22 +116,23 @@ def register_changes(new_tag): run_local(["git", "tag", "-a", new_tag, "-m", "Autorelease"]) -def _get_log(name, url, cwd, ver_a, ver_b, prefix_ver, prefix_commit, prefix_tag): - # known issue: log fails if args.register is False +def _get_log(name, url, cwd, ver_a, ver_b, prefix_ver, prefix_commit, prefix_tag, target_known): log = "" log += "\n
\n" log += f" {name} commit log\n\n" log += f"Commits between [`{ver_a}`]({url+prefix_ver+ver_a}) and [`{ver_b}`]({url+prefix_ver+ver_b})" log += " (latest commit first):\n\n" + ref_a = prefix_tag+ver_a + ref_b = prefix_tag+ver_b if target_known else "HEAD" log += run_cmd( - ["git", "log", f"{prefix_tag+ver_a}..{prefix_tag+ver_b}", f"--pretty=format:* [`%h`]({url+prefix_commit}%H) %s"], + ["git", "log", f"{ref_a}..{ref_b}", f"--pretty=format:* [`%h`]({url+prefix_commit}%H) %s"], capture=True, check=True, cwd=cwd, ) log += "\n\n
\n" return log -def make_releasenotes(summary, prev_pdfium, new_pdfium, prev_tag, new_tag, c_updates): +def make_releasenotes(summary, prev_pdfium, new_pdfium, prev_tag, new_tag, c_updates, register): # TODO specifically show changes to public/ ? @@ -137,37 +147,25 @@ def make_releasenotes(summary, prev_pdfium, new_pdfium, prev_tag, new_tag, c_upd "pypdfium2", RepositoryURL, ProjectDir, prev_tag, new_tag, "/tree/", "/commit/", "", + target_known=register ) relnotes += "\n" if c_updates: with tempfile.TemporaryDirectory() as tmpdir: tmpdir = Path(tmpdir) + # FIXME seems to take rather long - possibility to limit history size? run_cmd(["git", "clone", "--filter=blob:none", "--no-checkout", PdfiumURL, "pdfium_history"], cwd=tmpdir) relnotes += _get_log( "PDFium", PdfiumURL, tmpdir/"pdfium_history", str(prev_pdfium), str(new_pdfium), "/+/refs/heads/chromium/", "/+/", "origin/chromium/", + target_known=True ) (ProjectDir/"RELEASE.md").write_text(relnotes) -def get_changelog_staging(beta): - - content = ChangelogStaging.read_text() - pos = content.index("\n", content.index("# Changelog")) + 1 - header = content[:pos].strip() + "\n" - devel_msg = content[pos:].strip() - if devel_msg: - devel_msg += "\n" - - if beta is None: # flush - ChangelogStaging.write_text(header) - - return devel_msg - - def main(): parser = argparse.ArgumentParser( @@ -195,17 +193,18 @@ def main(): write_json(AR_RecordFile, dict(pdfium=new_pdfium, tag=new_tag)) update_refbindings(latest_pdfium) - summary = get_changelog_staging(new_helpers["beta"]) - log_changes(summary, record["pdfium"], new_pdfium, new_tag, new_helpers["beta"]) + is_beta = new_helpers["beta"] is not None + summary = get_next_changelog(flush=(not is_beta)) + log_changes(summary, record["pdfium"], new_pdfium, new_tag, is_beta) if args.register: register_changes(new_tag) parsed_helpers = parse_git_tag() if new_helpers != parsed_helpers: print( - "Warning: Written and parsed helpers do not match. This should not happen in CI.\n" + + "Warning: Written and parsed helpers do not match. This should not happen in CI.\n" f"In: {new_helpers}\n" + f"Out: {parsed_helpers}" ) - make_releasenotes(summary, record["pdfium"], new_pdfium, prev_tag, new_tag, c_updates) + make_releasenotes(summary, record["pdfium"], new_pdfium, prev_tag, new_tag, c_updates, args.register) if __name__ == "__main__": diff --git a/setupsrc/pypdfium2_setup/packaging_base.py b/setupsrc/pypdfium2_setup/base.py similarity index 62% rename from setupsrc/pypdfium2_setup/packaging_base.py rename to setupsrc/pypdfium2_setup/base.py index 0fa1a6ed6..de15ac803 100644 --- a/setupsrc/pypdfium2_setup/packaging_base.py +++ b/setupsrc/pypdfium2_setup/base.py @@ -1,9 +1,6 @@ -# SPDX-FileCopyrightText: 2024 geisserml +# SPDX-FileCopyrightText: 2025 geisserml # SPDX-License-Identifier: Apache-2.0 OR BSD-3-Clause -# No external dependencies shall be imported in this file -# TODO improve consistency of variable names; think about variables to move in/out - import os import re import sys @@ -13,14 +10,12 @@ import platform import functools import sysconfig -import traceback import subprocess import contextlib from pathlib import Path from collections import namedtuple import urllib.request as url_request -# TODO(apibreak) consider renaming PDFIUM_PLATFORM to PDFIUM_BINARY ? PlatSpec_EnvVar = "PDFIUM_PLATFORM" PlatSpec_VerSep = ":" PlatSpec_V8Sym = "-v8" @@ -34,7 +29,6 @@ ModuleHelpers = "helpers" ModulesAll = (ModuleRaw, ModuleHelpers) -# NOTE if renaming BindingsFN, also rename `bindings/$FILE` BindingsFN = "bindings.py" VersionFN = "version.json" @@ -53,9 +47,6 @@ AR_ConfigFile = AutoreleaseDir / "config.json" RefBindingsFile = AutoreleaseDir / BindingsFN -CondaDir = ProjectDir / "conda" -CondaRaw_BuildNumF = CondaDir / "raw" / "build_num.txt" - RepositoryURL = "https://github.com/pypdfium2-team/pypdfium2" PdfiumURL = "https://pdfium.googlesource.com/pdfium" DepotToolsURL = "https://chromium.googlesource.com/chromium/tools/depot_tools.git" @@ -63,30 +54,32 @@ ReleaseURL = ReleaseRepo + "/releases/download/chromium%2F" ReleaseInfoURL = ReleaseURL.replace("github.com/", "api.github.com/repos/").replace("download/", "tags/") - -# We endeavor to make the reference bindings as universal and robust as possible. -# Thanks to symbol guards, we can define all standalone feature flags. -# We don't currently define flags that depend on external headers, though it should be possible in principle by adding them to $CPATH (or equivalent). -# Note that Skia is currently a standalone flag because pdfium only provides a typedef void* for a Skia canvas and casts internally -REFBINDINGS_FLAGS = ["V8", "XFA", "SKIA"] +REFBINDINGS_FLAGS = ("V8", "XFA", "SKIA") -# TODO make SysNames/ExtPlats/PlatNames iterable, consider StrEnum or something +# TODO consider StrEnum or something class SysNames: - linux = "linux" darwin = "darwin" windows = "windows" + linux = "linux" + android = "android" + ios = "ios" class ExtPlats: sourcebuild = "sourcebuild" - system = "system" - sdist = "sdist" + system = "system" + sdist = "sdist" -# TODO align with either python or google platform names? class PlatNames: # - Attribute names and values are expected to match # - Platform names are expected to start with the corresponding system name + darwin_x64 = SysNames.darwin + "_x64" + darwin_arm64 = SysNames.darwin + "_arm64" + darwin_universal = SysNames.darwin + "_universal" + windows_x64 = SysNames.windows + "_x64" + windows_x86 = SysNames.windows + "_x86" + windows_arm64 = SysNames.windows + "_arm64" linux_x64 = SysNames.linux + "_x64" linux_x86 = SysNames.linux + "_x86" linux_arm64 = SysNames.linux + "_arm64" @@ -94,42 +87,70 @@ class PlatNames: linux_musl_x64 = SysNames.linux + "_musl_x64" linux_musl_x86 = SysNames.linux + "_musl_x86" linux_musl_arm64 = SysNames.linux + "_musl_arm64" - darwin_x64 = SysNames.darwin + "_x64" - darwin_arm64 = SysNames.darwin + "_arm64" - windows_x64 = SysNames.windows + "_x64" - windows_x86 = SysNames.windows + "_x86" - windows_arm64 = SysNames.windows + "_arm64" - - -ReleaseNames = { - PlatNames.darwin_x64 : "mac-x64", - PlatNames.darwin_arm64 : "mac-arm64", - PlatNames.linux_x64 : "linux-x64", - PlatNames.linux_x86 : "linux-x86", - PlatNames.linux_arm64 : "linux-arm64", - PlatNames.linux_arm32 : "linux-arm", - PlatNames.linux_musl_x64 : "linux-musl-x64", - PlatNames.linux_musl_x86 : "linux-musl-x86", - PlatNames.linux_musl_arm64 : "linux-musl-arm64", - PlatNames.windows_x64 : "win-x64", - PlatNames.windows_x86 : "win-x86", - PlatNames.windows_arm64 : "win-arm64", + android_arm64 = SysNames.android + "_arm64" # device + android_arm32 = SysNames.android + "_arm32" # device + android_x64 = SysNames.android + "_x64" # emulator + android_x86 = SysNames.android + "_x86" # emulator + ios_arm64_dev = SysNames.ios + "_arm64_dev" # device + ios_arm64_simu = SysNames.ios + "_arm64_simu" # simulator + ios_x64_simu = SysNames.ios + "_x64_simu" # simulator + +# Map platform names to the package names used by pdfium-binaries/google. +PdfiumBinariesMap = { + PlatNames.darwin_x64: "mac-x64", + PlatNames.darwin_arm64: "mac-arm64", + PlatNames.windows_x64: "win-x64", + PlatNames.windows_x86: "win-x86", + PlatNames.windows_arm64: "win-arm64", + PlatNames.linux_x64: "linux-x64", + PlatNames.linux_x86: "linux-x86", + PlatNames.linux_arm64: "linux-arm64", + PlatNames.linux_arm32: "linux-arm", + PlatNames.linux_musl_x64: "linux-musl-x64", + PlatNames.linux_musl_x86: "linux-musl-x86", + PlatNames.linux_musl_arm64: "linux-musl-arm64", + PlatNames.android_arm64: "android-arm64", } -LibnameForSystem = { - SysNames.linux: "libpdfium.so", - SysNames.darwin: "libpdfium.dylib", - SysNames.windows: "pdfium.dll", -} +# Capture the platforms we build wheels for +WheelPlatforms = list(PdfiumBinariesMap.keys()) + +# Additional platforms we don't currently build wheels for in craft.py +# To package these manually, you can do e.g. (in bash): +# export PLATFORMS=(darwin_universal android_arm32 android_x64 android_x86 ios_arm64_dev ios_arm64_simu ios_x64_simu) +# for PLAT in ${PLATFORMS[@]}; do echo $PLAT; ./run emplace $PLAT; PDFIUM_PLATFORM=$PLAT python3 -m build -wxn; done +PdfiumBinariesMap.update({ + PlatNames.darwin_universal: "mac-univ", + PlatNames.android_arm32: "android-arm", + PlatNames.android_x64: "android-x64", + PlatNames.android_x86: "android-x86", + PlatNames.ios_arm64_dev: "ios-device-arm64", + PlatNames.ios_arm64_simu: "ios-simulator-arm64", + PlatNames.ios_x64_simu: "ios-simulator-x64", +}) + + +# Map system to pdfium shared library name +def libname_for_system(system): + if system == SysNames.windows: + return "pdfium.dll" + elif system in (SysNames.darwin, SysNames.ios): + return "libpdfium.dylib" + elif system in (SysNames.linux, SysNames.android): + return "libpdfium.so" + else: + # TODO fallback logic if system is None? + raise ValueError(f"Unhandled system {system!r}") -BinaryPlatforms = list(ReleaseNames.keys()) -BinarySystems = list(LibnameForSystem.keys()) +AllLibnames = ["pdfium.dll", "libpdfium.dylib", "libpdfium.so"] -@functools.lru_cache(maxsize=2) -def run_conda_search(package, channel): - output = run_cmd(["conda", "search", "--json", package, "--override-channels", "-c", channel], cwd=None, capture=True) - return json.loads(output)[package] +if sys.version_info < (3, 8): + # NOTE alternatively, we could write our own cached property backport with python's descriptor protocol + def cached_property(func): + return property( functools.lru_cache(maxsize=1)(func) ) +else: + cached_property = functools.cached_property class PdfiumVer: @@ -144,25 +165,6 @@ def get_latest(): tag = git_ls.split("\t")[-1] return int( tag.split("/")[-1] ) - @staticmethod - @functools.lru_cache(maxsize=2) - def _get_latest_conda_for(package, channel, v_func): - search = run_conda_search(package, channel) - search = sorted(search, key=lambda d: v_func(d["version"]), reverse=True) - result = v_func(search[0]["version"]) - print(f"Resolved latest {channel}::{package} to {result}", file=sys.stderr) - return result - - def get_latest_conda_pdfium(): - return PdfiumVer._get_latest_conda_for( - "pdfium-binaries", "bblanchon", lambda v: int(v.split(".")[2]) - ) - - def get_latest_conda_bindings(): - return PdfiumVer._get_latest_conda_for( - "pypdfium2_raw", "pypdfium2-team", lambda v: int(v) - ) - @classmethod def to_full(cls, v_short): @@ -202,8 +204,8 @@ def write_json(fp, data, indent=2): return json.dump(data, buf, indent=indent) -def write_pdfium_info(dir, build, origin, flags=[], n_commits=0, hash=None): - info = dict(**PdfiumVer.to_full(build)._asdict(), n_commits=n_commits, hash=hash, origin=origin, flags=flags) +def write_pdfium_info(dir, build, origin, flags=(), n_commits=0, hash=None): + info = dict(**PdfiumVer.to_full(build)._asdict(), n_commits=n_commits, hash=hash, origin=origin, flags=list(flags)) write_json(dir/VersionFN, info) return info @@ -212,7 +214,7 @@ def parse_given_tag(full_tag): info = dict() - # NOTE `git describe --dirty` does not account for new unregistered files + # note, `git describe --dirty` ignores new unregistered files tag = full_tag dirty = tag.endswith("-dirty") if dirty: @@ -242,7 +244,7 @@ def parse_git_tag(): def merge_tag(info, mode): - # FIXME some duplication with src/pypdfium2/version.py + # some duplication with src/pypdfium2/version.py ... tag = ".".join([str(info[k]) for k in ("major", "minor", "patch")]) if info['beta'] is not None: @@ -267,15 +269,15 @@ def merge_tag(info, mode): def plat_to_system(pl_name): if pl_name == ExtPlats.sourcebuild: - # FIXME If doing a sourcebuild on an unknown host system, this returns None, which will cause binary detection code to fail (we need to know the platform-specific binary name) - handle this downsteam with fallback value? + # FIXME If doing a sourcebuild on an unknown host system, this returns None, which will cause binary detection code to fail (we need to know the platform-specific binary name) - handle this downstream with fallback value? return Host.system - # NOTE other ExtPlats not supported here + # other ExtPlats intentionally not handled here return getattr(SysNames, pl_name.split("_", maxsplit=1)[0]) # platform.libc_ver() currently returns an empty string for musl, so use the packaging module to confirm. # See https://github.com/python/cpython/issues/87414 and https://github.com/pypa/packaging/blob/f13c298f0a623f3f7e01cc8395956b718d21503a/src/packaging/_musllinux.py#L32 -# NOTE could consider packaging.tags.sys_tags() as a possible public-API alternative - see https://packaging.pypa.io/en/stable/tags.html#packaging.tags.sys_tags or https://stackoverflow.com/a/75172415/15547292 +# (could consider packaging.tags.sys_tags() as a possible public-API alternative - see https://packaging.pypa.io/en/stable/tags.html#packaging.tags.sys_tags or https://stackoverflow.com/a/75172415/15547292) def _get_libc_info(): @@ -284,13 +286,20 @@ def _get_libc_info(): # try to be future proof in case libc_ver() gets musl support but uses "muslc" rather than just "musl" name = "musl" elif name == "": - # TODO add test ensuring this continues to work import packaging._musllinux musl_ver = packaging._musllinux._get_musl_version(sys.executable) if musl_ver: name, ver = "musl", f"{musl_ver.major}.{musl_ver.minor}" - return name, ver + return name.lower(), ver + + +def _android_api(): + try: + # this is available since python 3.7 (i.e. earlier than PEP 738) + return sys.getandroidapilevel() + except AttributeError: + return None class _host_platform: @@ -302,14 +311,24 @@ def __init__(self): self._system_name = platform.system().lower() self._machine_name = platform.machine().lower() - # If we are on Linux, check if we have glibc or musl - self._libc_name, self._libc_ver = _get_libc_info() - - # TODO consider cached property for platform and system - self.platform = self._get_platform() - self.system = None + # empty slots + self._libc_name, self._libc_ver = "", "" + self._exc = None + + @cached_property + def platform(self): + try: + return self._get_platform() + except Exception as e: + self._exc = e + return None + + @cached_property + def system(self): if self.platform is not None: - self.system = plat_to_system(self.platform) + return plat_to_system(self.platform) + else: + return None def __repr__(self): info = f"{self._system_name} {self._machine_name}" @@ -317,31 +336,75 @@ def __repr__(self): info += f", {self._libc_name} {self._libc_ver}" return f"" - def _is_plat(self, system, machine): - return self._system_name.startswith(system) and self._machine_name.startswith(machine) + def _handle_linux(self, archid): + if self._libc_name == "glibc": + return getattr(PlatNames, f"linux_{archid}") + elif self._libc_name == "musl": + return getattr(PlatNames, f"linux_musl_{archid}") + elif _android_api(): # seems to imply self._libc_name == "libc" + print("Android prior to PEP 738 (e.g. Termux)", file=sys.stderr) + return getattr(PlatNames, f"android_{archid}") + else: + raise RuntimeError(f"Linux with unhandled libc {self._libc_name!r}.") def _get_platform(self): - # some machine names are merely "qualified guesses", mistakes can't be fully excluded for platforms we don't have access to - if self._is_plat("darwin", "x86_64"): - return PlatNames.darwin_x64 - elif self._is_plat("darwin", "arm64"): - return PlatNames.darwin_arm64 - elif self._is_plat("linux", "x86_64"): - return PlatNames.linux_x64 if self._libc_name != "musl" else PlatNames.linux_musl_x64 - elif self._is_plat("linux", "i686"): - return PlatNames.linux_x86 if self._libc_name != "musl" else PlatNames.linux_musl_x86 - elif self._is_plat("linux", "aarch64"): - return PlatNames.linux_arm64 if self._libc_name != "musl" else PlatNames.linux_musl_arm64 - elif self._is_plat("linux", "armv7l"): - return PlatNames.linux_arm32 - elif self._is_plat("windows", "amd64"): - return PlatNames.windows_x64 - elif self._is_plat("windows", "arm64"): - return PlatNames.windows_arm64 - elif self._is_plat("windows", "x86"): - return PlatNames.windows_x86 - else: - return None + + # TODO 32-bit interpreters running on 64-bit machines? + + if self._system_name == "darwin": + # platform.machine() is the actual architecture. sysconfig.get_platform() may return universal2, but by default we only use the arch-specific binaries. + print(f"macOS {self._machine_name} {platform.mac_ver()}", file=sys.stderr) + if self._machine_name == "x86_64": + return PlatNames.darwin_x64 + elif self._machine_name == "arm64": + return PlatNames.darwin_arm64 + + elif self._system_name == "windows": + print(f"windows {self._machine_name} {platform.win32_ver()}", file=sys.stderr) + if self._machine_name == "amd64": + return PlatNames.windows_x64 + elif self._machine_name == "x86": + return PlatNames.windows_x86 + elif self._machine_name == "arm64": + return PlatNames.windows_arm64 + + elif self._system_name == "linux": + self._libc_name, self._libc_ver = _get_libc_info() + print(f"linux {self._machine_name} {self._libc_name, self._libc_ver}", file=sys.stderr) + if self._machine_name == "x86_64": + return self._handle_linux("x64") + elif self._machine_name == "i686": + return self._handle_linux("x86") + elif self._machine_name == "aarch64": + return self._handle_linux("arm64") + elif self._machine_name == "armv7l": + if self._libc_name == "musl": + raise RuntimeError(f"armv7l: musl not supported at this time.") + return self._handle_linux("arm32") + + elif self._system_name == "android": # PEP 738 + # The PEP isn't too explicit about the machine names, but based on related CPython PRs, it looks like platform.machine() retains the raw uname values as on Linux, whereas sysconfig.get_platform() will map to the wheel tags + print(f"android {self._machine_name} {sys.getandroidapilevel()} {platform.android_ver()}", file=sys.stderr) + if self._machine_name == "aarch64": + return PlatNames.android_arm64 + elif self._machine_name == "armv7l": + return PlatNames.android_arm32 + elif self._machine_name == "x86_64": + return PlatNames.android_x64 + elif self._machine_name == "i686": + return PlatNames.android_x86 + + elif self._system_name in ("ios", "ipados"): # PEP 730 + # This is currently untested. We don't have access to an iOS device, so this is basically guessed from what the PEP mentions. + ios_ver = platform.ios_ver() + print(f"{self._system_name} {self._machine_name} {ios_ver}", file=sys.stderr) + if self._machine_name == "arm64": + return PlatNames.ios_arm64_simu if ios_ver.is_simulator else PlatNames.ios_arm64_dev + elif self._machine_name == "x86_64": + assert ios_ver.is_simulator, "iOS x86_64 can only be simulator" + return PlatNames.ios_x64_simu + + raise RuntimeError(f"Unhandled platform: {self!r}") Host = _host_platform() @@ -351,12 +414,25 @@ def _manylinux_tag(arch, glibc="2_17"): return f"manylinux_{glibc}_{arch}.manylinux2014_{arch}" def get_wheel_tag(pl_name): + if pl_name == PlatNames.darwin_x64: # pdfium-binaries/steps/05-configure.sh defines `mac_deployment_target = "10.13.0"` + # "intel" instead of "x86_64" might work too, but I think it's considered legacy return "macosx_10_13_x86_64" elif pl_name == PlatNames.darwin_arm64: # macOS 11 is the first version available on arm64 return "macosx_11_0_arm64" + elif pl_name == PlatNames.darwin_universal: + # universal binary format (combo of x64 and arm64) - we prefer arch-specific wheels, but allow callers to build a universal wheel if they want to + return "macosx_10_13_universal2" + + elif pl_name == PlatNames.windows_x64: + return "win_amd64" + elif pl_name == PlatNames.windows_arm64: + return "win_arm64" + elif pl_name == PlatNames.windows_x86: + return "win32" + elif pl_name == PlatNames.linux_x64: return _manylinux_tag("x86_64") elif pl_name == PlatNames.linux_x86: @@ -365,28 +441,46 @@ def get_wheel_tag(pl_name): return _manylinux_tag("aarch64") elif pl_name == PlatNames.linux_arm32: return _manylinux_tag("armv7l") + elif pl_name == PlatNames.linux_musl_x64: return "musllinux_1_1_x86_64" elif pl_name == PlatNames.linux_musl_x86: return "musllinux_1_1_i686" elif pl_name == PlatNames.linux_musl_arm64: return "musllinux_1_1_aarch64" - elif pl_name == PlatNames.windows_x64: - return "win_amd64" - elif pl_name == PlatNames.windows_arm64: - return "win_arm64" - elif pl_name == PlatNames.windows_x86: - return "win32" + + # Android - see PEP 738 # Packaging + # At this time, we only build wheels for android_arm64, but handle the others as well so the code is ready if we want to in the future (or in case callers want to build their own wheels) + elif pl_name == PlatNames.android_arm64: + return "android_21_arm64_v8a" + elif pl_name == PlatNames.android_arm32: + return "android_21_armeabi_v7a" + elif pl_name == PlatNames.android_x64: + return "android_21_x86_64" + elif pl_name == PlatNames.android_x86: + return "android_21_x86" + + # iOS - see PEP 730 # Packaging + # We do not currently build wheels for iOS, but again, add the handlers so it could be done on demand. Bear in mind that the resulting iOS packages are currently completely untested. In particular, the PEP says + # "These wheels can include binary modules in-situ (i.e., co-located with the Python source, in the same way as wheels for a desktop platform); however, they will need to be post-processed as binary modules need to be moved into the “Frameworks” location for distribution. This can be automated with an Xcode build step." + # I take it this means you may need to change the library search path to that Frameworks location. + elif pl_name == PlatNames.ios_arm64_dev: + return "ios_12_0_arm64_iphoneos" + elif pl_name == PlatNames.ios_arm64_simu: + return "ios_12_0_arm64_iphonesimulator" + elif pl_name == PlatNames.ios_x64_simu: + return "ios_12_0_x86_64_iphonesimulator" + + # The sourcebuild clause is currently inactive; setup.py will simply forward the tag determined by bdist_wheel. Anyway, this should be roughly equivalent. elif pl_name == ExtPlats.sourcebuild: - # sysconfig.get_platform() may return universal2 on macOS. However, the binaries built here should be considered architecture-specific. - # The reason why we don't simply do `if Host.platform: return get_wheel_tag(Host.platform) else ...` is that version info for pdfium-binaries does not have to match the sourcebuild host. - # NOTE On Linux, this just returns f"linux_{arch}" (which is a valid wheel tag). Leave it as-is since we don't know the build's lowest compatible libc. The caller may re-tag using the wheel module's CLI. tag = sysconfig.get_platform().replace("-", "_").replace(".", "_") + # sysconfig.get_platform() may return universal2 on macOS. However, the binaries built here should be considered architecture-specific. if tag.startswith("macosx") and tag.endswith("universal2"): tag = tag[:-len("universal2")] + Host._machine_name return tag + else: - raise ValueError(f"Unknown platform name {pl_name}") + raise ValueError(f"Unhandled platform name {pl_name}") def run_cmd(command, cwd, capture=False, check=True, str_cast=True, **kwargs): @@ -428,7 +522,7 @@ def tmp_cwd_context(tmp_cwd): os.chdir(orig_cwd) -def run_ctypesgen(target_dir, headers_dir, flags=[], guard_symbols=False, compile_lds=[], run_lds=["."], allow_system_despite_libdirs=False): +def run_ctypesgen(target_dir, headers_dir, flags=(), compile_lds=(), run_lds=(".", ), search_sys_despite_libdirs=False, guard_symbols=False, no_srcinfo=False): # Import ctypesgen only in this function so it does not have to be available for other setup tasks import ctypesgen assert getattr(ctypesgen, "PYPDFIUM2_SPECIFIC", False), "pypdfium2 requires fork of ctypesgen" @@ -438,7 +532,7 @@ def run_ctypesgen(target_dir, headers_dir, flags=[], guard_symbols=False, compil args = ["-l", "pdfium"] if run_lds: args += ["--runtime-libdirs", *run_lds] - if not allow_system_despite_libdirs: + if not search_sys_despite_libdirs: args += ["--no-system-libsearch"] if compile_lds: args += ["--compile-libdirs", *compile_lds] @@ -449,6 +543,8 @@ def run_ctypesgen(target_dir, headers_dir, flags=[], guard_symbols=False, compil args += ["--no-macro-guards"] if not guard_symbols: args += ["--no-symbol-guards"] + if no_srcinfo: + args += ["--no-srcinfo"] # pre-processor - if not given, pypdfium2-ctypesgen will try to auto-select as available (gcc/clang) c_preproc = os.environ.get("CPP", None) @@ -473,7 +569,7 @@ def run_ctypesgen(target_dir, headers_dir, flags=[], guard_symbols=False, compil def build_pdfium_bindings(version, headers_dir=None, **kwargs): - defaults = dict(flags=[], run_lds=["."], guard_symbols=False) + defaults = dict(flags=(), run_lds=(".", ), guard_symbols=False) for k, v in defaults.items(): kwargs.setdefault(k, v) @@ -541,7 +637,7 @@ def clean_platfiles(): ModuleDir_Raw / BindingsFN, ModuleDir_Raw / VersionFN, ] - deletables += [ModuleDir_Raw / fn for fn in LibnameForSystem.values()] + deletables += [ModuleDir_Raw / fn for fn in AllLibnames] for fp in deletables: if fp.is_file(): @@ -552,15 +648,15 @@ def clean_platfiles(): def get_helpers_info(): - # TODO consider adding some checks against record + # TODO add some checks against record? have_git_describe = False if HAVE_GIT_REPO: try: helpers_info = parse_git_tag() - except subprocess.CalledProcessError: + except subprocess.CalledProcessError as e: + print(str(e), file=sys.stderr) print("Version uncertain: git describe failure - possibly a shallow checkout", file=sys.stderr) - traceback.print_exc() else: have_git_describe = True helpers_info["data_source"] = "git" @@ -586,16 +682,16 @@ def build_pl_suffix(version, use_v8): return (PlatSpec_V8Sym if use_v8 else "") + PlatSpec_VerSep + str(version) -def parse_pl_spec(pl_spec, with_prepare=True): +def parse_pl_spec(pl_spec): # TODOs # - targets integration is inflexible, need to restructure # - add way to pass through package provider with system target? - # - variable name with_prepare is confusing + do_prepare = True if pl_spec.startswith("prepared!"): - _, pl_spec = pl_spec.split("!", maxsplit=1) - return parse_pl_spec(pl_spec, with_prepare=False) + pl_spec = pl_spec[len("prepared!"):] # removeprefix + do_prepare = False req_ver = None use_v8 = False @@ -608,7 +704,8 @@ def parse_pl_spec(pl_spec, with_prepare=True): if not pl_spec or pl_spec == "auto": pl_name = Host.platform if pl_name is None: - raise RuntimeError(f"No pre-built binaries available for {Host}. You may place custom binaries & bindings in data/sourcebuild and install with `{PlatSpec_EnvVar}=sourcebuild`.") + # delegate handling of unknown platforms to the caller (setup.py) + return None elif hasattr(ExtPlats, pl_spec): pl_name = getattr(ExtPlats, pl_spec) elif hasattr(PlatNames, pl_spec): @@ -620,10 +717,10 @@ def parse_pl_spec(pl_spec, with_prepare=True): assert req_ver.isnumeric() req_ver = int(req_ver) else: - assert pl_name != ExtPlats.system and with_prepare, "Version must be given explicitly for system or prepared!... targets" + assert pl_name != ExtPlats.system and do_prepare, "Version must be given explicitly for system or prepared!... targets" req_ver = PdfiumVer.get_latest() - return with_prepare, pl_name, req_ver, use_v8 + return do_prepare, pl_name, req_ver, use_v8 def parse_modspec(modspec): @@ -634,3 +731,18 @@ def parse_modspec(modspec): else: modnames = ModulesAll return modnames + + +def get_next_changelog(flush=False): + + content = ChangelogStaging.read_text() + pos = content.index("\n", content.index("# Changelog")) + 1 + header = content[:pos].strip() + "\n" + devel_msg = content[pos:].strip() + if devel_msg: + devel_msg += "\n" + + if flush: + ChangelogStaging.write_text(header) + + return devel_msg diff --git a/setupsrc/pypdfium2_setup/craft.py b/setupsrc/pypdfium2_setup/craft.py new file mode 100644 index 000000000..76e15e2c8 --- /dev/null +++ b/setupsrc/pypdfium2_setup/craft.py @@ -0,0 +1,131 @@ +# SPDX-FileCopyrightText: 2025 geisserml +# SPDX-License-Identifier: Apache-2.0 OR BSD-3-Clause + +import os +import sys +import json +import shutil +import argparse +import tempfile +import contextlib +import urllib.request as url_request +from pathlib import Path + +sys.path.insert(0, str(Path(__file__).parents[1])) +from pypdfium2_setup.base import * + +try: + import build.__main__ as build_module +except ImportError: + build_module = None + + +def main(): + + parser = argparse.ArgumentParser( + description = "Craft PyPI packages for pypdfium2" + ) + parser.add_argument("--pdfium-ver", default=None) + parser.add_argument("--use-v8", action="store_true") + parser.add_argument("--wheels", action="store_true") + parser.add_argument("--sdist", action="store_true") + + args = parser.parse_args() + if not (args.wheels or args.sdist): + args.wheels, args.sdist = True, True + if not args.pdfium_ver or args.pdfium_ver == "latest": + args.pdfium_ver = PdfiumVer.get_latest() + else: + args.pdfium_ver = int(args.pdfium_ver) + + with ArtifactStash(): + main_pypi(args) + + +def main_pypi(args): + + assert args.sdist or args.wheels + + if args.sdist: + os.environ[PlatSpec_EnvVar] = ExtPlats.sdist + helpers_info = get_helpers_info() + with tmp_ctypesgen_pin(): + if not helpers_info["dirty"]: + os.environ["SDIST_IGNORE_DIRTY"] = "1" + _run_pypi_build(["--sdist"]) + + if args.wheels: + suffix = build_pl_suffix(args.pdfium_ver, args.use_v8) + for plat in WheelPlatforms: + os.environ[PlatSpec_EnvVar] = plat + suffix + _run_pypi_build(["--wheel"]) + clean_platfiles() + + +def _run_pypi_build(caller_args): + # -nx: --no-isolation --skip-dependency-check + assert build_module, "Module 'build' is not importable. Cannot craft PyPI packages." + with tmp_cwd_context(ProjectDir): + build_module.main([str(ProjectDir), "-nx", *caller_args]) + + +class ArtifactStash: + + # Preserve in-tree artifacts from editable install + + def __enter__(self): + + self.tmpdir = None + + file_names = [VersionFN, BindingsFN, libname_for_system(Host.system)] + self.files = [fp for fp in [ModuleDir_Raw / fn for fn in file_names] if fp.exists()] + if len(self.files) == 0: + return + + self.tmpdir = tempfile.TemporaryDirectory(prefix="pypdfium2_artifact_stash_") + self.tmpdir_path = Path(self.tmpdir.name) + for fp in self.files: + shutil.move(fp, self.tmpdir_path) + + def __exit__(self, *_): + if self.tmpdir is None: + return + for fp in self.files: + shutil.move(self.tmpdir_path / fp.name, ModuleDir_Raw) + self.tmpdir.cleanup() + + +@contextlib.contextmanager +def tmp_replace_ctx(fp, orig, tmp): + orig_txt = fp.read_text() + assert orig_txt.count(orig) == 1 + tmp_txt = orig_txt.replace(orig, tmp) + fp.write_text(tmp_txt) + try: + yield + finally: + fp.write_text(orig_txt) + + +@contextlib.contextmanager +def tmp_ctypesgen_pin(): + + pin = os.environ.get("CTYPESGEN_PIN", None) + if not pin: + head_url = "https://api.github.com/repos/pypdfium2-team/ctypesgen/git/refs/heads/pypdfium2" + with url_request.urlopen(head_url) as rq: + content = rq.read().decode() + content = json.loads(content) + pin = content["object"]["sha"] + print(f"Resolved pypdfium2 ctypesgen HEAD to SHA {pin}", file=sys.stderr) + + base_txt = "ctypesgen @ git+https://github.com/pypdfium2-team/ctypesgen@" + ctx = tmp_replace_ctx(ProjectDir/"pyproject.toml", base_txt+"pypdfium2", base_txt+pin) + with ctx: + print(f"Wrote temporary pyproject.toml with ctypesgen pin", file=sys.stderr) + yield + print(f"Reset pyproject.toml", file=sys.stderr) + + +if __name__ == '__main__': + main() diff --git a/setupsrc/pypdfium2_setup/craft_packages.py b/setupsrc/pypdfium2_setup/craft_packages.py deleted file mode 100644 index de42bc421..000000000 --- a/setupsrc/pypdfium2_setup/craft_packages.py +++ /dev/null @@ -1,259 +0,0 @@ -# SPDX-FileCopyrightText: 2024 geisserml -# SPDX-License-Identifier: Apache-2.0 OR BSD-3-Clause - -# TODO split in pypi and conda? - -import os -import sys -import json -import shutil -import argparse -import tempfile -import contextlib -import urllib.request as url_request -from pathlib import Path -from functools import partial - -sys.path.insert(0, str(Path(__file__).parents[1])) -# TODO consider dotted access? -from pypdfium2_setup.packaging_base import * -from pypdfium2_setup.emplace import prepare_setup - -try: - import build.__main__ as build_module -except ImportError: - build_module = None - - -P_PYPI = "pypi" -P_CONDA_RAW = "conda_raw" -P_CONDA_HELPERS = "conda_helpers" - -def parse_args(): - - root_parser = argparse.ArgumentParser( - description = "Craft PyPI or conda packages for pypdfium2.", - ) - root_parser.add_argument( - "--pdfium-ver", - default = None, - ) - subparsers = root_parser.add_subparsers(dest="parser") - pypi = subparsers.add_parser(P_PYPI) - pypi.add_argument("--use-v8", action="store_true") - pypi.add_argument("--wheels", action="store_true") - pypi.add_argument("--sdist", action="store_true") - conda_raw = subparsers.add_parser(P_CONDA_RAW) - conda_helpers = subparsers.add_parser(P_CONDA_HELPERS) - - args = root_parser.parse_args() - - if args.parser == P_PYPI: - if not (args.wheels or args.sdist): - args.wheels, args.sdist = True, True - - args.is_literal_latest = args.pdfium_ver == "latest" - if not args.pdfium_ver or args.is_literal_latest: - if args.parser == P_CONDA_RAW: - args.pdfium_ver = PdfiumVer.get_latest_conda_pdfium() - elif args.parser == P_CONDA_HELPERS: - args.pdfium_ver = PdfiumVer.get_latest_conda_bindings() - else: - args.pdfium_ver = PdfiumVer.get_latest() - else: - args.pdfium_ver = int(args.pdfium_ver) - - return args - - -def main(): - - args = parse_args() - - with ArtifactStash(): - if args.parser == P_PYPI: - main_pypi(args) - elif args.parser == P_CONDA_RAW: - main_conda_raw(args) - elif args.parser == P_CONDA_HELPERS: - helpers_info = parse_git_tag() - os.environ["M_HELPERS_VER"] = merge_tag(helpers_info, "py") - main_conda_helpers(args) - else: - assert False - - -def _run_pypi_build(caller_args): - # -nx: --no-isolation --skip-dependency-check - assert build_module, "Module 'build' is not importable. Cannot craft PyPI packages." - with tmp_cwd_context(ProjectDir): - build_module.main([str(ProjectDir), "-nx", *caller_args]) - - -def main_pypi(args): - - assert args.sdist or args.wheels - - if args.sdist: - os.environ[PlatSpec_EnvVar] = ExtPlats.sdist - helpers_info = get_helpers_info() - with tmp_ctypesgen_pin(): - if not helpers_info["dirty"]: - os.environ["SDIST_IGNORE_DIRTY"] = "1" - _run_pypi_build(["--sdist"]) - - if args.wheels: - suffix = build_pl_suffix(args.pdfium_ver, args.use_v8) - for plat in ReleaseNames.keys(): - os.environ[PlatSpec_EnvVar] = plat + suffix - _run_pypi_build(["--wheel"]) - clean_platfiles() - - -def run_conda_build(recipe_dir, out_dir, args=[]): - with TmpCommitCtx(): - run_cmd(["conda", "build", recipe_dir, "--output-folder", out_dir, *args], cwd=ProjectDir, env=os.environ) - - -def _get_build_num(args): - - # parse existing releases to automatically handle arbitrary version builds - # TODO expand to pypdfium2_helpers as well, so we could rebuild with different pdfium bounds in a workflow - - search = reversed(run_conda_search("pypdfium2_raw", "pypdfium2-team")) - - if args.is_literal_latest: - assert args.pdfium_ver > max([int(d["version"]) for d in search]), "Literal latest must resolve to a new version. This is done to avoid rebuilds without new version in scheduled releases. If you want to rebuild, omit --pdfium-ver or pass the resolved value." - - # determine build number - build_num = max([d["build_number"] for d in search if int(d["version"]) == args.pdfium_ver], default=None) - build_num = 0 if build_num is None else build_num+1 - - return build_num - - -def main_conda_raw(args): - os.environ["PDFIUM_SHORT"] = str(args.pdfium_ver) - os.environ["PDFIUM_FULL"] = ".".join([str(v) for v in PdfiumVer.to_full(args.pdfium_ver)]) - os.environ["BUILD_NUM"] = str(_get_build_num(args)) - emplace_func = partial(prepare_setup, ExtPlats.system, args.pdfium_ver, use_v8=None) - with CondaExtPlatfiles(emplace_func): - run_conda_build(CondaDir/"raw", CondaDir/"raw"/"out", args=["--override-channels", "-c", "bblanchon", "-c", "defaults"]) - - -def main_conda_helpers(args): - - # Set the current pdfium version as upper boundary, for inherent API safety. - # pdfium does not do semantic versioning, so upward flexibility is difficult. - os.environ["PDFIUM_MAX"] = str(args.pdfium_ver) - - # NOTE To build with a local pypdfium2_raw, add the args below for the source dir, and remove the pypdfium2-team prefix from the helpers recipe's run requirements - # args=["-c", CondaDir/"raw"/"out"] - run_conda_build(CondaDir/"helpers", CondaDir/"helpers"/"out", args=["--override-channels", "-c", "pypdfium2-team", "-c", "bblanchon", "-c", "defaults"]) - - -class ArtifactStash: - - # Preserve in-tree aftefacts from editable install - - def __enter__(self): - - self.tmpdir = None - - file_names = [VersionFN, BindingsFN, LibnameForSystem[Host.system]] - self.files = [fp for fp in [ModuleDir_Raw / fn for fn in file_names] if fp.exists()] - if len(self.files) == 0: - return - - self.tmpdir = tempfile.TemporaryDirectory(prefix="pypdfium2_artifact_stash_") - self.tmpdir_path = Path(self.tmpdir.name) - for fp in self.files: - shutil.move(fp, self.tmpdir_path) - - def __exit__(self, *_): - if self.tmpdir is None: - return - for fp in self.files: - shutil.move(self.tmpdir_path / fp.name, ModuleDir_Raw) - self.tmpdir.cleanup() - - -@contextlib.contextmanager -def tmp_replace_ctx(fp, orig, tmp): - orig_txt = fp.read_text() - assert orig_txt.count(orig) == 1 - tmp_txt = orig_txt.replace(orig, tmp) - fp.write_text(tmp_txt) - try: - yield - finally: - fp.write_text(orig_txt) - - -@contextlib.contextmanager -def tmp_ctypesgen_pin(): - - pin = os.environ.get("CTYPESGEN_PIN", None) - if not pin: - head_url = "https://api.github.com/repos/pypdfium2-team/ctypesgen/git/refs/heads/pypdfium2" - with url_request.urlopen(head_url) as rq: - content = rq.read().decode() - content = json.loads(content) - pin = content["object"]["sha"] - print(f"Resolved pypdfium2 ctypesgen HEAD to SHA {pin}", file=sys.stderr) - - base_txt = "ctypesgen @ git+https://github.com/pypdfium2-team/ctypesgen@" - ctx = tmp_replace_ctx(ProjectDir/"pyproject.toml", base_txt+"pypdfium2", base_txt+pin) - with ctx: - print(f"Wrote temporary pyproject.toml with ctypesgen pin", file=sys.stderr) - yield - print(f"Reset pyproject.toml", file=sys.stderr) - - -class TmpCommitCtx: - - # Work around local conda `git_url` not including uncommitted changes - # In particular, this is used to transfer data files, so we can generate them externally and don't have to conda package ctypesgen. - - # use a tmp control file so we can also undo the commit in conda's isolated clone - FILE = CondaDir / "with_tmp_commit.txt" - - def __enter__(self): - # determine if there are any modified or new files - out = run_cmd(["git", "status", "--porcelain"], capture=True, cwd=ProjectDir) - self.have_mods = bool(out) - if self.have_mods: # make tmp commit - self.FILE.touch() - run_cmd(["git", "add", "."], cwd=ProjectDir) - run_cmd(["git", "commit", "-m", "!!! tmp commit for conda-build", "-m", "make conda-build include uncommitted changes"], cwd=ProjectDir) - - @classmethod - def undo(cls): - # assuming FILE exists (promised by callers) - run_cmd(["git", "reset", "--soft", "HEAD^"], cwd=ProjectDir) - run_cmd(["git", "reset", cls.FILE], cwd=ProjectDir) - cls.FILE.unlink() - - def __exit__(self, *_): - if self.have_mods: # pop tmp commit, if any - self.undo() - - -class CondaExtPlatfiles: - - def __init__(self, emplace_func): - self.emplace_func = emplace_func - - def __enter__(self): - self.platfiles = self.emplace_func() - self.platfiles = [ModuleDir_Raw/f for f in self.platfiles] - run_cmd(["git", "add", "-f"] + [str(f) for f in self.platfiles], cwd=ProjectDir) - - def __exit__(self, *_): - run_cmd(["git", "reset"] + [str(f) for f in self.platfiles], cwd=ProjectDir) - for fp in self.platfiles: - fp.unlink() - - -if __name__ == '__main__': - main() diff --git a/setupsrc/pypdfium2_setup/emplace.py b/setupsrc/pypdfium2_setup/emplace.py index fe15a5fcf..c796d5ed7 100644 --- a/setupsrc/pypdfium2_setup/emplace.py +++ b/setupsrc/pypdfium2_setup/emplace.py @@ -1,30 +1,26 @@ #! /usr/bin/env python3 -# SPDX-FileCopyrightText: 2024 geisserml +# SPDX-FileCopyrightText: 2025 geisserml # SPDX-License-Identifier: Apache-2.0 OR BSD-3-Clause import os +import os.path import sys import argparse from pathlib import Path sys.path.insert(0, str(Path(__file__).parents[1])) -from pypdfium2_setup import update_pdfium -# TODO consider dotted access? -from pypdfium2_setup.packaging_base import * +from pypdfium2_setup.base import * +from pypdfium2_setup import update as update_pdfium -# CONSIDER Linux/macOS: check that minimum OS version requirements are fulfilled - def _repr_info(version, flags): - return str(version) + (":{%s}" % ','.join(flags) if flags else "") + return str(version) + (f":{','.join(flags)}" if flags else "") def _get_pdfium_with_cache(pl_name, req_ver, req_flags, use_v8): - # TODO inline binary cache logic into update_pdfium ? - system = plat_to_system(pl_name) pl_dir = DataDir / pl_name - binary = pl_dir / LibnameForSystem[system] + binary = pl_dir / libname_for_system(system) binary_ver = pl_dir / VersionFN if all(f.exists() for f in (binary, binary_ver)): @@ -50,26 +46,29 @@ def prepare_setup(pl_name, pdfium_ver, use_v8): clean_platfiles() flags = ["V8", "XFA"] if use_v8 else [] + # TODO for PDFIUM_PLATFORM=system, add option for caller to pass in custom headers_dir, run_lds and flags? this might cause more fragmentation, though + # also want to consider accepting a full version for offline setup + if pl_name == ExtPlats.system: - # TODO add option for caller to pass in custom headers_dir, run_lds and flags? unfortunately it's not straightforward how to integrate this - # also want to consider accepting a full version for offline setup - build_pdfium_bindings(pdfium_ver, flags=flags, guard_symbols=True, run_lds=[]) + build_pdfium_bindings(pdfium_ver, flags=flags, guard_symbols=True, run_lds=()) shutil.copyfile(DataDir_Bindings/BindingsFN, ModuleDir_Raw/BindingsFN) write_pdfium_info(ModuleDir_Raw, pdfium_ver, origin="system", flags=flags) return [BindingsFN, VersionFN] + else: + platfiles = [] pl_dir = DataDir/pl_name system = plat_to_system(pl_name) if pl_name == ExtPlats.sourcebuild: - # sourcebuild bindings are captured once and can't really be re-generated, hence keep them in the platform directory so they are not overwritten + # sourcebuild bindings are kept in the platform directory platfiles += [pl_dir/BindingsFN] else: platfiles += [DataDir_Bindings/BindingsFN] _get_pdfium_with_cache(pl_name, pdfium_ver, flags, use_v8) - platfiles += [pl_dir/LibnameForSystem[system], pl_dir/VersionFN] + platfiles += [pl_dir/libname_for_system(system), pl_dir/VersionFN] for fp in platfiles: shutil.copyfile(fp, ModuleDir_Raw/fp.name) @@ -78,7 +77,6 @@ def prepare_setup(pl_name, pdfium_ver, use_v8): def main(): - # TODO add option to force rebuild parser = argparse.ArgumentParser( description = "Manage in-tree artifacts from an editable install.", ) @@ -95,8 +93,11 @@ def main(): clean_platfiles() return - with_prepare, *pl_info = parse_pl_spec(args.plat_spec) - assert with_prepare, "Can't use prepared target with emplace, would be no-op." + parsed_spec = parse_pl_spec(args.plat_spec) + if parsed_spec is None: + raise Host._exc + do_prepare, *pl_info = parsed_spec + assert do_prepare, "Can't use already-prepared target with emplace, would be no-op." prepare_setup(*pl_info) diff --git a/setupsrc/pypdfium2_setup/build_pdfium.py b/setupsrc/pypdfium2_setup/sourcebuild.py similarity index 94% rename from setupsrc/pypdfium2_setup/build_pdfium.py rename to setupsrc/pypdfium2_setup/sourcebuild.py index 036669b4b..5a563cfdf 100755 --- a/setupsrc/pypdfium2_setup/build_pdfium.py +++ b/setupsrc/pypdfium2_setup/sourcebuild.py @@ -1,8 +1,9 @@ #! /usr/bin/env python3 -# SPDX-FileCopyrightText: 2024 geisserml +# SPDX-FileCopyrightText: 2025 geisserml # SPDX-License-Identifier: Apache-2.0 OR BSD-3-Clause -# NOTE Works on Linux/macOS/Windows (that is, at least on GitHub Actions) +# This script has been tested on Linux/macOS/Windows x86_64 on GH Actions CI +# However, it does not currently work on Linux aarch64 natively, since Google's toolchain doesn't seem to support that. Cross-compilation (by setting target_cpu in config) should work, though. import os import sys @@ -12,8 +13,7 @@ from pathlib import Path, WindowsPath sys.path.insert(0, str(Path(__file__).parents[1])) -# TODO consider dotted access? -from pypdfium2_setup.packaging_base import * +from pypdfium2_setup.base import * SBDir = SourcebuildDir # local alias for convenience PatchDir = SBDir / "patches" @@ -87,20 +87,18 @@ def dl_depottools(do_update): def dl_pdfium(GClient, do_update, revision): - is_sync = True - if PDFiumDir.exists(): if do_update: print("PDFium: Revert / Sync ...") run_cmd([GClient, "revert"], cwd=SBDir) else: - is_sync = False print("PDFium: Using existing repository as-is.") else: print("PDFium: Download ...") + do_update = True run_cmd([GClient, "config", "--custom-var", "checkout_configuration=minimal", "--unmanaged", PdfiumURL], cwd=SBDir) - if is_sync: + if do_update: # TODO consider passing -D ? run_cmd([GClient, "sync", "--revision", f"origin/{revision}", "--no-history", "--shallow"], cwd=SBDir) # quick & dirty fix to make a versioned commit available (pdfium gets tagged frequently, so this should be more than enough in practice) @@ -108,7 +106,7 @@ def dl_pdfium(GClient, do_update, revision): run_cmd(["git", "fetch", "--depth=100"], cwd=PDFiumDir) run_cmd(["git", "fetch", "--depth=100"], cwd=PDFiumDir) - return is_sync + return do_update def _dl_unbundler(): @@ -183,7 +181,7 @@ def pack(v_short, v_post): dest_dir = DataDir / ExtPlats.sourcebuild dest_dir.mkdir(parents=True, exist_ok=True) - libname = LibnameForSystem[Host.system] + libname = libname_for_system(Host.system) shutil.copy(PDFiumBuildDir/libname, dest_dir/libname) write_pdfium_info(dest_dir, v_short, origin="sourcebuild", **v_post) @@ -244,21 +242,19 @@ def main( GN = get_tool("gn") Ninja = get_tool("ninja") - pdfium_dl_done = dl_pdfium(GClient, b_update, b_revision) + did_pdfium_sync = dl_pdfium(GClient, b_update, b_revision) v_short, v_post = identify_pdfium() print(f"Version {v_short} {v_post}", file=sys.stderr) - if pdfium_dl_done: + if did_pdfium_sync: patch_pdfium(v_short) - if b_use_syslibs: - _dl_unbundler() - - if b_use_syslibs: - run_cmd(["python3", "build/linux/unbundle/replace_gn_files.py", "--system-libraries", "icu"], cwd=PDFiumDir) config_dict = DefaultConfig.copy() if b_use_syslibs: + _dl_unbundler() + run_cmd(["python3", "build/linux/unbundle/replace_gn_files.py", "--system-libraries", "icu"], cwd=PDFiumDir) config_dict.update(SyslibsConfig) + config_str = serialise_config(config_dict) print(f"\nBuild configuration:\n{config_str}\n") diff --git a/setupsrc/pypdfium2_setup/update_pdfium.py b/setupsrc/pypdfium2_setup/update.py similarity index 74% rename from setupsrc/pypdfium2_setup/update_pdfium.py rename to setupsrc/pypdfium2_setup/update.py index 60266edd3..4312a1e1a 100755 --- a/setupsrc/pypdfium2_setup/update_pdfium.py +++ b/setupsrc/pypdfium2_setup/update.py @@ -1,20 +1,18 @@ #! /usr/bin/env python3 -# SPDX-FileCopyrightText: 2024 geisserml +# SPDX-FileCopyrightText: 2025 geisserml # SPDX-License-Identifier: Apache-2.0 OR BSD-3-Clause import sys import shutil import tarfile import argparse -import traceback import functools from pathlib import Path import urllib.request as url_request from concurrent.futures import ThreadPoolExecutor sys.path.insert(0, str(Path(__file__).parents[1])) -# TODO consider dotted access? -from pypdfium2_setup.packaging_base import * +from pypdfium2_setup.base import * def clear_data(download_files): @@ -33,16 +31,16 @@ def _get_package(pl_name, version, robust, use_v8): if use_v8: prefix += "v8-" - fn = prefix + f"{ReleaseNames[pl_name]}.tgz" + fn = prefix + f"{PdfiumBinariesMap[pl_name]}.tgz" fu = f"{ReleaseURL}{version}/{fn}" fp = pl_dir / fn print(f"'{fu}' -> '{fp}'") try: url_request.urlretrieve(fu, fp) - except Exception: + except Exception as e: if robust: - traceback.print_exc() + print(str(e), file=sys.stderr) return None, None else: raise @@ -72,7 +70,7 @@ def extract(archives, version, flags): with tarfile.open(arc_path) as tar: pl_dir = DataDir/pl_name system = plat_to_system(pl_name) - libname = LibnameForSystem[system] + libname = libname_for_system(system) tar_libdir = "lib" if system != SysNames.windows else "bin" tar_extract_file(tar, f"{tar_libdir}/{libname}", pl_dir/libname) write_pdfium_info(pl_dir, version, origin="pdfium-binaries", flags=flags) @@ -80,14 +78,24 @@ def extract(archives, version, flags): arc_path.unlink() -BinaryPlatforms = list(ReleaseNames.keys()) +def postprocess_android(platforms): + # see https://wiki.termux.com/wiki/FAQ#Why_does_a_compiled_program_show_warnings + if Host.system == SysNames.android and Host.platform in platforms: + elf_cleaner = shutil.which("termux-elf-cleaner") + if elf_cleaner: + print("Invoking termux-elf-cleaner to clean up possible linker warnings...", file=sys.stderr) + libpath = DataDir / Host.platform / libname_for_system(Host.system) + run_cmd([elf_cleaner, str(libpath)], cwd=None) + else: + print("If you are on Termux, consider installing termux-elf-cleaner to clean up possible linker warnings.", file=sys.stderr) + def main(platforms, version=None, robust=False, max_workers=None, use_v8=False): if not version: version = PdfiumVer.get_latest() if not platforms: - platforms = BinaryPlatforms + platforms = WheelPlatforms if len(platforms) != len(set(platforms)): raise ValueError("Duplicate platforms not allowed.") @@ -96,21 +104,22 @@ def main(platforms, version=None, robust=False, max_workers=None, use_v8=False): clear_data(platforms) archives = download(platforms, version, use_v8, max_workers, robust) extract(archives, version, flags) + postprocess_android(platforms) # low-level CLI interface for testing - users should go with higher-level emplace.py or setup.py def parse_args(argv): + platform_choices = list(PdfiumBinariesMap.keys()) parser = argparse.ArgumentParser( description = "Download pre-built PDFium packages.", ) parser.add_argument( - # FIXME with metavar, choices are not visible in help by default - without it, the long choices list is repeated 4 times due to 2 flags and nargs="+" "--platforms", "-p", nargs = "+", metavar = "ID", - choices = BinaryPlatforms, - help = f"The platform(s) to include. Choices: {BinaryPlatforms}", + choices = platform_choices, + help = f"The platform(s) to include. Defaults to the platforms we build wheels for. Choices: {platform_choices}", ) parser.add_argument( "--use-v8", diff --git a/src/pypdfium2/__init__.py b/src/pypdfium2/__init__.py index c60d44310..e3aa37ecc 100644 --- a/src/pypdfium2/__init__.py +++ b/src/pypdfium2/__init__.py @@ -1,4 +1,4 @@ -# SPDX-FileCopyrightText: 2024 geisserml +# SPDX-FileCopyrightText: 2025 geisserml # SPDX-License-Identifier: Apache-2.0 OR BSD-3-Clause import pypdfium2._library_scope diff --git a/src/pypdfium2/__main__.py b/src/pypdfium2/__main__.py index 55e5f1826..5e388c133 100644 --- a/src/pypdfium2/__main__.py +++ b/src/pypdfium2/__main__.py @@ -1,4 +1,4 @@ -# SPDX-FileCopyrightText: 2024 geisserml +# SPDX-FileCopyrightText: 2025 geisserml # SPDX-License-Identifier: Apache-2.0 OR BSD-3-Clause import sys @@ -7,6 +7,7 @@ from pypdfium2.version import PYPDFIUM_INFO, PDFIUM_INFO from pypdfium2._cli._parsers import setup_logging +# Note, this requires the pypdfium2-team fork of ctypesgen. With oldschool ctypesgen (or even older versions of the fork), this will fail. from pypdfium2_raw.bindings import _libs_info pdfium_path = _libs_info["pdfium"]["path"] @@ -16,7 +17,7 @@ "extract-images": "extract images", "extract-text": "extract text", "imgtopdf": "convert images to PDF", - "pageobjects": "print info on page objects", + "pageobjects": "print info on pageobjects", "pdfinfo": "print info on document and pages", "render": "rasterize pages", "tile": "tile pages (N-up)", diff --git a/src/pypdfium2/_cli/__init__.py b/src/pypdfium2/_cli/__init__.py index b4fb1b2f0..e719c7581 100644 --- a/src/pypdfium2/_cli/__init__.py +++ b/src/pypdfium2/_cli/__init__.py @@ -1,2 +1,2 @@ -# SPDX-FileCopyrightText: 2024 geisserml +# SPDX-FileCopyrightText: 2025 geisserml # SPDX-License-Identifier: Apache-2.0 OR BSD-3-Clause diff --git a/src/pypdfium2/_cli/_parsers.py b/src/pypdfium2/_cli/_parsers.py index abffe4e5d..6af7890cf 100644 --- a/src/pypdfium2/_cli/_parsers.py +++ b/src/pypdfium2/_cli/_parsers.py @@ -1,10 +1,10 @@ -# SPDX-FileCopyrightText: 2024 geisserml +# SPDX-FileCopyrightText: 2025 geisserml # SPDX-License-Identifier: Apache-2.0 OR BSD-3-Clause import os import sys -import argparse import logging +import argparse from pathlib import Path import pypdfium2._helpers as pdfium import pypdfium2.internal as pdfium_i @@ -12,19 +12,19 @@ def setup_logging(): - pdfium_i.DEBUG_AUTOCLOSE.value = bool(int( os.environ.get("DEBUG_AUTOCLOSE", 0) )) + # could also pass through the log level by parameter, but using an env var seemed easiest for now + debug_autoclose = bool(int( os.environ.get("DEBUG_AUTOCLOSE", 0) )) + loglevel = getattr(logging, os.environ.get("PYPDFIUM_LOGLEVEL", "debug").upper()) + pdfium_i.DEBUG_AUTOCLOSE.value = debug_autoclose lib_logger = logging.getLogger("pypdfium2") lib_logger.addHandler(logging.StreamHandler()) - lib_logger.setLevel(logging.DEBUG) - + lib_logger.setLevel(loglevel) pdfium.PdfUnspHandler().setup() def parse_numtext(numtext): - # TODO enhancement: take count and verify page numbers - if not numtext: return None indices = [] @@ -88,9 +88,24 @@ def get_input(args, init_forms=False, **kwargs): pdf.init_forms() if "pages" in args and not args.pages: args.pages = [i for i in range(len(pdf))] + # TODO else validate pages, as seen in ./render.py return pdf +# dummy more_itertools.peekable().__bool__ alternative + +def _postpeek_generator(value, iterator): + yield value; yield from iterator + +def iterator_hasvalue(iterator): + try: + first_value = next(iterator) + except StopIteration: + return False, None + else: + return True, _postpeek_generator(first_value, iterator) + + if sys.version_info >= (3, 9): from argparse import BooleanOptionalAction diff --git a/src/pypdfium2/_cli/arrange.py b/src/pypdfium2/_cli/arrange.py index ec7108447..0dafb5e7a 100644 --- a/src/pypdfium2/_cli/arrange.py +++ b/src/pypdfium2/_cli/arrange.py @@ -1,8 +1,7 @@ -# SPDX-FileCopyrightText: 2024 geisserml +# SPDX-FileCopyrightText: 2025 geisserml # SPDX-License-Identifier: Apache-2.0 OR BSD-3-Clause import pypdfium2._helpers as pdfium -# TODO? consider dotted access from pypdfium2._cli._parsers import parse_numtext @@ -41,11 +40,9 @@ def main(args): args.passwords.append(None) dest_pdf = pdfium.PdfDocument.new() - index = 0 for in_path, pages, password in zip(args.inputs, args.pages, args.passwords): - src_pdf = pdfium.PdfDocument(in_path, password=password) - dest_pdf.import_pages(src_pdf, pages=pages) - index += len(src_pdf) + with pdfium.PdfDocument(in_path, password=password) as src_pdf: + dest_pdf.import_pages(src_pdf, pages=pages) dest_pdf.save(args.output) diff --git a/src/pypdfium2/_cli/attachments.py b/src/pypdfium2/_cli/attachments.py index 039c58cd4..9e405ecd4 100644 --- a/src/pypdfium2/_cli/attachments.py +++ b/src/pypdfium2/_cli/attachments.py @@ -1,8 +1,7 @@ -# SPDX-FileCopyrightText: 2024 geisserml +# SPDX-FileCopyrightText: 2025 geisserml # SPDX-License-Identifier: Apache-2.0 OR BSD-3-Clause from pathlib import Path -# TODO? consider dotted access from pypdfium2._cli._parsers import ( add_input, get_input, parse_numtext, diff --git a/src/pypdfium2/_cli/extract_images.py b/src/pypdfium2/_cli/extract_images.py index 873f62756..538d6f3bb 100644 --- a/src/pypdfium2/_cli/extract_images.py +++ b/src/pypdfium2/_cli/extract_images.py @@ -1,14 +1,19 @@ -# SPDX-FileCopyrightText: 2024 geisserml +# SPDX-FileCopyrightText: 2025 geisserml # SPDX-License-Identifier: Apache-2.0 OR BSD-3-Clause # TODO test-cover use_bitmap, format and render +import logging import traceback from pathlib import Path import pypdfium2.raw as pdfium_c import pypdfium2._helpers as pdfium -# TODO? consider dotted access -from pypdfium2._cli._parsers import add_input, get_input +from pypdfium2._cli._parsers import ( + add_input, get_input, + BooleanOptionalAction, +) + +logger = logging.getLogger(__name__) def attach(parser): @@ -22,8 +27,8 @@ def attach(parser): parser.add_argument( "--max-depth", type = int, - default = 2, - help = "Maximum recursion depth to consider when looking for page objects.", + default = 15, + help = "Maximum recursion depth to consider when looking for pageobjects.", ) parser.add_argument( "--use-bitmap", @@ -37,7 +42,13 @@ def attach(parser): parser.add_argument( "--render", action = "store_true", - help = "Whether to get rendered bitmaps, taking masks and transform matrices into account. (Fallback if doing smart extraction.)", + help = "When --use-bitmap is given, whether to get rendered bitmaps, taking masks and transform matrices into account.", + ) + parser.add_argument( + "--scale-to-original", + action = BooleanOptionalAction, + default = True, + help = "When --use-bitmap --render is given, whether to scale the image so it is rendered at its native resolution, or close to that. This should improve output quality. The default is True, but you may opt out.", ) @@ -64,14 +75,15 @@ def main(args): n_idigits = len(str( len(images) )) for j, image in enumerate(images): - stem = "%s_%0*d_%0*d" % (args.input.stem, n_pdigits, i+1, n_idigits, j+1) - prefix = args.output_dir / stem + tag = "%0*d_%0*d" % (n_pdigits, i+1, n_idigits, j+1) + prefix = args.output_dir / f"{args.input.stem}_{tag}" + # logger.debug("\n"+tag) try: if args.use_bitmap: - pil_image = image.get_bitmap(render=args.render).to_pil() + pil_image = image.get_bitmap(render=args.render, scale_to_original=args.scale_to_original).to_pil() pil_image.save(f"{prefix}.{args.format}") else: - image.extract(prefix, fb_format=args.format, fb_render=args.render) + image.extract(prefix, fb_format=args.format) except pdfium.PdfiumError: traceback.print_exc() image.close() diff --git a/src/pypdfium2/_cli/extract_text.py b/src/pypdfium2/_cli/extract_text.py index f212b6a6f..c4a177255 100644 --- a/src/pypdfium2/_cli/extract_text.py +++ b/src/pypdfium2/_cli/extract_text.py @@ -1,7 +1,6 @@ -# SPDX-FileCopyrightText: 2024 geisserml +# SPDX-FileCopyrightText: 2025 geisserml # SPDX-License-Identifier: Apache-2.0 OR BSD-3-Clause -# TODO? consider dotted access from pypdfium2._cli._parsers import add_input, get_input EXTRACT_RANGE = "range" @@ -30,7 +29,7 @@ def main(args): # TODO let caller pass in possible range/boundary parameters if args.strategy == EXTRACT_RANGE: - text = textpage.get_text_range(force_this=True) + text = textpage.get_text_range() elif args.strategy == EXTRACT_BOUNDED: text = textpage.get_text_bounded() else: diff --git a/src/pypdfium2/_cli/imgtopdf.py b/src/pypdfium2/_cli/imgtopdf.py index 7fcf83cd5..b8c95e148 100644 --- a/src/pypdfium2/_cli/imgtopdf.py +++ b/src/pypdfium2/_cli/imgtopdf.py @@ -1,16 +1,11 @@ -# SPDX-FileCopyrightText: 2024 geisserml +# SPDX-FileCopyrightText: 2025 geisserml # SPDX-License-Identifier: Apache-2.0 OR BSD-3-Clause # TODO test-cover converting non-jpeg format from pathlib import Path import pypdfium2._helpers as pdfium - -try: - import PIL.Image -except ImportError: - PIL = None - +from pypdfium2._deferred import PIL_Image def attach(parser): parser.add_argument( @@ -38,8 +33,6 @@ def main(args): # Due to limitations in PDFium's public API, this function may be inefficient/lossy for non-JPEG input. # The technically best available open-source tool for image to PDF conversion is probably img2pdf (although its code style can be regarded as displeasing). - # Development note: We are closing objects explicitly because loading JPEGs non-inline binds file handles to the PDF, which need to be released as soon as possible. Without this, we have already run into "OSError: Too many open files" while testing. - pdf = pdfium.PdfDocument.new() for fp in args.images: @@ -50,13 +43,13 @@ def main(args): if fp.suffix.lower() in (".jpg", ".jpeg"): image_obj.load_jpeg(fp, inline=args.inline) else: - pil_image = PIL.Image.open(fp) + pil_image = PIL_Image.open(fp) bitmap = pdfium.PdfBitmap.from_pil(pil_image) pil_image.close() image_obj.set_bitmap(bitmap) bitmap.close() - w, h = image_obj.get_size() + w, h = image_obj.get_px_size() image_obj.set_matrix( pdfium.PdfMatrix().scale(w, h) ) page = pdf.new_page(w, h) page.insert_obj(image_obj) diff --git a/src/pypdfium2/_cli/pageobjects.py b/src/pypdfium2/_cli/pageobjects.py index 9e9aee531..0320114a3 100644 --- a/src/pypdfium2/_cli/pageobjects.py +++ b/src/pypdfium2/_cli/pageobjects.py @@ -1,24 +1,24 @@ -# SPDX-FileCopyrightText: 2024 geisserml +# SPDX-FileCopyrightText: 2025 geisserml # SPDX-License-Identifier: Apache-2.0 OR BSD-3-Clause # TODO test-confirm filter and info params -from enum import Enum +from collections import OrderedDict import pypdfium2._helpers as pdfium -import pypdfium2.raw as pdfium_c import pypdfium2.internal as pdfium_i -# TODO? consider dotted access +import pypdfium2.raw as pdfium_c from pypdfium2._cli._parsers import ( add_input, add_n_digits, get_input, round_list, + iterator_hasvalue, ) -class InfoParams (Enum): - pos = 0 - imageinfo = 1 +PARAM_POS = "pos" +PARAM_IMGINFO = "imginfo" +INFO_PARAMS = (PARAM_POS, PARAM_IMGINFO) def attach(parser): @@ -43,21 +43,29 @@ def attach(parser): ) parser.add_argument( "--info", - nargs = "*", - type = lambda s: InfoParams[s.lower()], - default = (InfoParams.pos, InfoParams.imageinfo), - help = "Object details to show (pos, imageinfo).", + nargs = "+", + type = str.lower, + choices = INFO_PARAMS, + default = INFO_PARAMS, + help = "Object details to show.", ) -def print_img_metadata(metadata, pad=""): - for attr in pdfium_c.FPDF_IMAGEOBJ_METADATA.__slots__: - value = getattr(metadata, attr) - if attr == "colorspace": - value = pdfium_i.ColorspaceToStr.get(value) - elif attr == "marked_content_id" and value == -1: - continue - print(pad + f"{attr}: {value}\n", end="") +def print_img_metadata(m, n_digits, pad=""): + + members = OrderedDict( + width = m.width, + height = m.height, + horizontal_dpi = round(m.horizontal_dpi, n_digits), + vertical_dpi = round(m.vertical_dpi, n_digits), + bits_per_pixel = m.bits_per_pixel, + colorspace = pdfium_i.ColorspaceToStr.get(m.colorspace), + ) + if m.marked_content_id != -1: + members["marked_content_id"] = m.marked_content_id + + for key, value in members.items(): + print(pad + f"{key}: {value}") def main(args): @@ -68,38 +76,40 @@ def main(args): if args.filter: args.filter = [pdfium_i.ObjectTypeToConst[t] for t in args.filter] - show_pos = (InfoParams.pos in args.info) - show_imageinfo = (InfoParams.imageinfo in args.info) - total_count = 0 + show_pos = PARAM_POS in args.info + show_imginfo = PARAM_IMGINFO in args.info + assert show_pos or show_imginfo + total_count = 0 for i in args.pages: page = pdf[i] - obj_searcher = page.get_objects( - filter = args.filter, - max_depth = args.max_depth, - ) - preamble = f"# Page {i+1}\n" + hasvalue, obj_searcher = iterator_hasvalue( page.get_objects(args.filter, max_depth=args.max_depth) ) + if not hasvalue: continue + + print(f"# Page {i+1}") count = 0 for obj in obj_searcher: pad_0 = " " * obj.level pad_1 = pad_0 + " " - print(preamble + pad_0 + pdfium_i.ObjectTypeToStr.get(obj.type)) + print(pad_0 + pdfium_i.ObjectTypeToStr.get(obj.type)) if show_pos: - pos = round_list(obj.get_pos(), args.n_digits) - print(pad_1 + f"Position: {pos}") + bounds = round_list(obj.get_bounds(), args.n_digits) + print(pad_1 + f"Bounding Box: {bounds}") + if obj.type in (pdfium_c.FPDF_PAGEOBJ_IMAGE, pdfium_c.FPDF_PAGEOBJ_TEXT): + quad_bounds = obj.get_quad_points() + print(pad_1 + f"Quad Points: {[round_list(p, args.n_digits) for p in quad_bounds]}") - # TODO? also call get_size() for coverage - if show_imageinfo and isinstance(obj, pdfium.PdfImage): + if show_imginfo and isinstance(obj, pdfium.PdfImage): print(pad_1 + f"Filters: {obj.get_filters()}") metadata = obj.get_metadata() - print_img_metadata(metadata, pad=pad_1) + assert (metadata.width, metadata.height) == obj.get_px_size() + print_img_metadata(metadata, args.n_digits, pad=pad_1) count += 1 - preamble = "" if count > 0: print(f"-> Count: {count}\n") diff --git a/src/pypdfium2/_cli/pdfinfo.py b/src/pypdfium2/_cli/pdfinfo.py index f4daffc03..085f2a03e 100644 --- a/src/pypdfium2/_cli/pdfinfo.py +++ b/src/pypdfium2/_cli/pdfinfo.py @@ -1,9 +1,8 @@ -# SPDX-FileCopyrightText: 2024 geisserml +# SPDX-FileCopyrightText: 2025 geisserml # SPDX-License-Identifier: Apache-2.0 OR BSD-3-Clause import pypdfium2.raw as pdfium_c import pypdfium2.internal as pdfium_i -# TODO? consider dotted access from pypdfium2._cli._parsers import ( add_input, add_n_digits, diff --git a/src/pypdfium2/_cli/render.py b/src/pypdfium2/_cli/render.py index 6ee7d12e4..f3eb046bd 100644 --- a/src/pypdfium2/_cli/render.py +++ b/src/pypdfium2/_cli/render.py @@ -1,36 +1,36 @@ -# SPDX-FileCopyrightText: 2024 geisserml +# SPDX-FileCopyrightText: 2025 geisserml # SPDX-License-Identifier: Apache-2.0 OR BSD-3-Clause import os import math +import types import logging +import colorsys import functools from pathlib import Path import multiprocessing as mp import concurrent.futures as ft - -try: - import cv2 -except ImportError: - cv2 = None +from importlib.util import find_spec import pypdfium2._helpers as pdfium import pypdfium2.internal as pdfium_i -import pypdfium2.raw as pdfium_r -# TODO? consider dotted access +import pypdfium2.raw as pdfium_c from pypdfium2._cli._parsers import ( add_input, get_input, setup_logging, + iterator_hasvalue, BooleanOptionalAction, ) +have_pil = find_spec("PIL") is not None +have_cv2 = find_spec("cv2") is not None logger = logging.getLogger(__name__) def _bitmap_wrapper_foreign_simple(width, height, format, *args, **kwargs): - if format == pdfium_r.FPDFBitmap_BGRx: + if format == pdfium_c.FPDFBitmap_BGRx: use_alpha = False - elif format == pdfium_r.FPDFBitmap_BGRA: + elif format == pdfium_c.FPDFBitmap_BGRA: use_alpha = True else: raise RuntimeError(f"Cannot create foreign_simple bitmap with bitmap type {pdfium_i.BitmapTypeToStr[format]}.") @@ -43,10 +43,10 @@ def _bitmap_wrapper_foreign_simple(width, height, format, *args, **kwargs): foreign_simple = _bitmap_wrapper_foreign_simple, ) -CsFields = ("path_fill", "path_stroke", "text_fill", "text_stroke") +ColorSchemeFields = ("path_fill", "path_stroke", "text_fill", "text_stroke") ColorOpts = dict(metavar="C", nargs=4, type=int) SampleTheme = dict( - # TODO improve colors - currently it's just some random ones to distinguish the different drawings + # TODO improve colors - currently it's just some random colors to distinguish the different drawings path_fill = (170, 100, 0, 255), # dark orange path_stroke = (0, 150, 255, 255), # sky blue text_fill = (255, 255, 255, 255), # white @@ -57,7 +57,7 @@ def attach(parser): add_input(parser, pages=True) parser.add_argument( "--output", "-o", - type = Path, + type = lambda p: Path(p).expanduser().resolve(), required = True, help = "Output directory where the serially numbered images shall be placed.", ) @@ -67,15 +67,15 @@ def attach(parser): ) parser.add_argument( "--format", "-f", - default = "jpg", type = str.lower, - help = "The image format to use.", + help = "The image format to use (default: conditional).", ) + engines_map = {"pil": PILEngine, "numpy+pil": NumpyPILEngine, "numpy+cv2": NumpyCV2Engine} parser.add_argument( "--engine", dest = "engine_cls", - type = lambda k: {"pil": PILEngine, "numpy+cv2": NumpyCV2Engine}[k.lower()], - help = "The saver engine to use (pil, numpy+cv2)", + type = lambda k: engines_map[k.lower()], + help = f"The saver engine to use {tuple(engines_map.keys())}", ) parser.add_argument( "--scale", @@ -92,7 +92,7 @@ def attach(parser): ) parser.add_argument( "--fill-color", - help = "Color the bitmap will be filled with before rendering. It shall be given in RGBA format as a sequence of integers ranging from 0 to 255. Defaults to white.", + help = "Color the bitmap will be filled with before rendering. Shall be given in RGBA format as a sequence of integers ranging from 0 to 255. Defaults to white.", **ColorOpts, ) parser.add_argument( @@ -102,8 +102,7 @@ def attach(parser): ) parser.add_argument( "--crop", - nargs = 4, - type = float, + metavar="C", nargs=4, type=float, default = (0, 0, 0, 0), help = "Amount to crop from (left, bottom, right, top).", ) @@ -161,6 +160,13 @@ def attach(parser): action = BooleanOptionalAction, help = "Whether to prefer BGRx/RGBx over BGR/RGB (default: conditional).", ) + bitmap.add_argument( + "--bgra-on-transparency", + dest="use_bgra_on_transparency", + action = BooleanOptionalAction, + help = "Whether to use BGRA if there is page content that has transparency. Note, this makes format selection page-dependent. As this behavior can be confusing, it is not currently the default, but recommended for performance in these cases.", + ) + # TODO expose force_bitmap_format parallel = parser.add_argument_group( title = "Parallelization", @@ -171,7 +177,7 @@ def attach(parser): nargs = "?", type = int, const = math.inf, - help = "Render non-parallel if page count is less or equal to the specified value (default is conditional). If this flag is given without a value, then render linear regardless of document length.", + help = "Render non-parallel if page count is less or equal to the specified value (default: 4). If this flag is given without a value, then render linear regardless of document length.", ) parallel.add_argument( "--processes", @@ -200,67 +206,175 @@ def attach(parser): ) color_scheme = parser.add_argument_group( - title = "Forced color scheme", - description = "Options for using pdfium's forced color scheme renderer. Deprecated, considered not useful.", + title = "Flat color scheme", + description = "Options for using pdfium's color scheme renderer. Note that this may flatten different colors into one, so the usability of this is limited. Alternatively, consider post-processing with lightness inversion (see below).", ) color_scheme.add_argument( "--sample-theme", action = "store_true", help = "Use a dark background sample theme as base. Explicit color params override selectively." ) + color_scheme.add_argument("--path-fill", **ColorOpts) + color_scheme.add_argument("--path-stroke", **ColorOpts) + color_scheme.add_argument("--text-fill", **ColorOpts) + color_scheme.add_argument("--text-stroke", **ColorOpts) color_scheme.add_argument( - "--path-fill", - **ColorOpts - ) - color_scheme.add_argument( - "--path-stroke", - **ColorOpts + "--fill-to-stroke", + action = "store_true", + help = "When rendering with custom color scheme, only draw borders around fill areas using the `path_stroke` color, instead of filling with the `path_fill` color. This is actually recommended, since with a single fill color for paths the boundaries of adjacent fill paths are less visible.", ) - color_scheme.add_argument( - "--text-fill", - **ColorOpts + + postproc = parser.add_argument_group( + title = "Post processing", + description = "Options to post-process rendered images. Note, this may have a strongly negative impact on performance.", ) - color_scheme.add_argument( - "--text-stroke", - **ColorOpts + postproc.add_argument( + "--invert-lightness", + action = "store_true", + help = "Invert lightness using the HLS color space (e.g. white<->black, dark_blue<->light_blue). The intent is to achieve a dark theme for documents with light background, while providing better visual results than classical color inversion or a flat pdfium color scheme. However, note that --optimize-mode lcd is not recommendable when inverting lightness.", ) - color_scheme.add_argument( - "--fill-to-stroke", + postproc.add_argument( + "--exclude-images", action = "store_true", - help = "Only draw borders around fill areas using the `path_stroke` color, instead of filling with the `path_fill` color.", + help = "Whether to exclude PDF images from lightness inversion.", ) class SavingEngine: - def __init__(self, path_parts): - self._path_parts = path_parts + def __init__(self, saver_args, postproc_kwargs): + self.args = saver_args + self.postproc_kwargs = postproc_kwargs - def _get_path(self, i): - output_dir, prefix, n_digits, format = self._path_parts - return output_dir / f"{prefix}{i+1:0{n_digits}d}.{format}" + def _get_path(self, i, ext): + args = self.args + return args.output_dir / f"{args.prefix}{i+1:0{args.n_digits}d}.{ext}" - def __call__(self, bitmap, i): - out_path = self._get_path(i) - self._saving_hook(out_path, bitmap) + def __call__(self, i, bitmap, page): + if self.args.use_bgra_on_transparency and self.args.format in ("jpg", "jpeg") and pdfium_c.FPDFPage_HasTransparency(page): + # alternatively, we could perhaps convert to RGB + logger.info("Page has transparency - overriding output format to PNG.") + ext = "png" + else: + ext = self.args.format + out_path = self._get_path(i, ext) + self._saving_hook(out_path, bitmap, page, self.postproc_kwargs) logger.info(f"Wrote page {i+1} as {out_path.name}") class PILEngine (SavingEngine): - def _saving_hook(self, out_path, bitmap): - bitmap.to_pil().save(out_path) + + def do_imports(self): + if not self.postproc_kwargs["invert_lightness"]: + return + logger.debug("PIL engine imports for post-processing") + global PIL + import PIL.Image + import PIL.ImageOps + import PIL.ImageFilter + import PIL.ImageDraw + + def _to_pil_hook(self, bitmap): + return bitmap.to_pil() + + def _saving_hook(self, out_path, bitmap, page, postproc_kwargs): + posconv = bitmap.get_posconv(page) + pil_image = self._to_pil_hook(bitmap) + pil_image = self.postprocess(pil_image, page, posconv, **postproc_kwargs) + pil_image.save(out_path) + + @staticmethod + def _invert_px_lightness(r, g, b): + h, l, s = colorsys.rgb_to_hls(r, g, b) + l = 1 - l + return colorsys.hls_to_rgb(h, l, s) + + LINV_LUT_SIZE = 17 + + @classmethod + @functools.lru_cache(maxsize=1) + def _get_linv_lut(cls): + return PIL.ImageFilter.Color3DLUT.generate(cls.LINV_LUT_SIZE, cls._invert_px_lightness) + + @classmethod + def postprocess(cls, src_image, page, posconv, invert_lightness, exclude_images): + dst_image = src_image + if invert_lightness: + if src_image.mode == "L": + dst_image = PIL.ImageOps.invert(src_image) + else: + dst_image = dst_image.filter(cls._get_linv_lut()) + if exclude_images: + # FIXME pdfium does not seem to provide APIs to translate XObject to page coordinates, so not sure how to handle images nested in XObjects. + # FIXME we'd also like to take alpha masks into account, but this may be difficult as long as pdfium does not expose them directly. + have_images, obj_walker = iterator_hasvalue( page.get_objects([pdfium_c.FPDF_PAGEOBJ_IMAGE], max_depth=1) ) + if have_images: + mask = PIL.Image.new("1", src_image.size) + draw = PIL.ImageDraw.Draw(mask) + for obj in obj_walker: + qpoints = [posconv.to_bitmap(x, y) for x, y in obj.get_quad_points()] + draw.polygon(qpoints, fill=1) + dst_image.paste(src_image, mask=mask) + return dst_image -class NumpyCV2Engine (SavingEngine): - def _saving_hook(self, out_path, bitmap): - cv2.imwrite(str(out_path), bitmap.to_numpy()) + +class NumpyPILEngine (PILEngine): + + def do_imports(self): + logger.debug("NumPy+PIL engine imports") + global PIL + import PIL.Image + super().do_imports() + + def _to_pil_hook(self, bitmap): + return PIL.Image.fromarray(bitmap.to_numpy(), bitmap.mode) -def _render_parallel_init(extra_init, input, password, may_init_forms, kwargs, engine): +class NumpyCV2Engine (SavingEngine): + + def do_imports(self): + logger.debug("NumPy+cv2 engine imports") + global cv2, np + import cv2 + if self.postproc_kwargs["exclude_images"]: + import numpy as np + + def _saving_hook(self, out_path, bitmap, page, postproc_kwargs): + np_array = bitmap.to_numpy() + np_array = self.postprocess(np_array, bitmap, page, **postproc_kwargs) + cv2.imwrite(str(out_path), np_array) - if extra_init: - extra_init() + @classmethod + def postprocess(cls, src_image, bitmap, page, invert_lightness, exclude_images): + dst_image = src_image + if invert_lightness: + if bitmap.format == pdfium_c.FPDFBitmap_Gray: + dst_image = ~src_image + else: + convert_to, convert_from = (cv2.COLOR_RGB2HLS, cv2.COLOR_HLS2RGB) if bitmap.rev_byteorder else (cv2.COLOR_BGR2HLS, cv2.COLOR_HLS2BGR) + dst_image = cv2.cvtColor(dst_image, convert_to) + h, l, s = cv2.split(dst_image) + l = ~l + dst_image = cv2.merge([h, l, s]) + dst_image = cv2.cvtColor(dst_image, convert_from) + if exclude_images: + assert bitmap.format != pdfium_c.FPDFBitmap_BGRx, "Not sure how to paste with mask on {RGB,BGR}X image using cv2" # FIXME? + posconv = bitmap.get_posconv(page) + have_images, obj_walker = iterator_hasvalue( page.get_objects([pdfium_c.FPDF_PAGEOBJ_IMAGE], max_depth=1) ) + if have_images: + mask = np.zeros((bitmap.height, bitmap.width, 1), np.uint8) + for obj in obj_walker: + qpoints = np.array([posconv.to_bitmap(x, y) for x, y in obj.get_quad_points()], np.int32) + cv2.fillPoly(mask, [qpoints], 1) + dst_image = cv2.copyTo(src_image, mask=mask, dst=dst_image) + return dst_image + + +def _render_parallel_init(logging_init, engine_init, input, password, may_init_forms, kwargs, engine): + logging_init() logger.info(f"Initializing data for process {os.getpid()}") + engine_init() pdf = pdfium.PdfDocument(input, password=password, autoclose=True) if may_init_forms: @@ -274,19 +388,23 @@ def _render_job(i, pdf, kwargs, engine): # logger.info(f"Started page {i+1} ...") page = pdf[i] bitmap = page.render(**kwargs) - engine(bitmap, i) + engine(i, bitmap, page) def _render_parallel_job(i): - global ProcObjs; _render_job(i, *ProcObjs) + global ProcObjs + _render_job(i, *ProcObjs) +def _do_nothing(): pass + +# TODO turn into a python-usable API yielding output paths as they are written def main(args): - # TODO turn into a python-usable API yielding output paths as they are written + if not args.output.is_dir(): + # make sure the output directory exists (PIL throws an error if it doesn't, but cv2 may silently skip) + raise ValueError(f"Output path is not an existing directory: {args.output!r}") pdf = get_input(args, init_forms=args.draw_forms) - - # TODO move to parsers? pdf_len = len(pdf) if not all(0 <= i < pdf_len for i in args.pages): raise ValueError("Out-of-bounds page indices are prohibited.") @@ -297,28 +415,32 @@ def main(args): args.prefix = f"{args.input.stem}_" if args.fill_color is None: args.fill_color = (0, 0, 0, 255) if args.sample_theme else (255, 255, 255, 255) + if args.format is None: + # can't use jpeg with transparency rsp. when there is an alpha channel + args.format = "jpg" if args.fill_color[3] == 255 else "png" if args.linear is None: - args.linear = 6 if args.format == "jpg" else 3 + args.linear = 4 # numpy+cv2 is much faster for PNG, and PIL faster for JPG, but this might simply be due to different encoding defaults if args.engine_cls is None: - if cv2 != None and args.format == "png": + assert have_pil or have_cv2, "Either pillow or numpy+cv2 must be installed for rendering CLI." + if (not have_pil) or (have_cv2 and args.format == "png"): args.engine_cls = NumpyCV2Engine else: args.engine_cls = PILEngine # PIL is faster with rev_byteorder and prefer_bgrx = True, as this achieves a natively supported pixel format. For numpy+cv2 there doesn't seem to be a difference. if args.rev_byteorder is None: - args.rev_byteorder = args.engine_cls is PILEngine + args.rev_byteorder = issubclass(args.engine_cls, PILEngine) if args.prefer_bgrx is None: # PIL can't save BGRX as PNG - args.prefer_bgrx = args.engine_cls is PILEngine and args.format != "png" + args.prefer_bgrx = issubclass(args.engine_cls, PILEngine) and args.format != "png" cs_kwargs = dict() if args.sample_theme: cs_kwargs.update(**SampleTheme) - cs_kwargs.update(**{f: getattr(args, f) for f in CsFields if getattr(args, f)}) - cs = pdfium.PdfColorScheme(**cs_kwargs) if len(cs_kwargs) > 0 else None + cs_kwargs.update(**{f: getattr(args, f) for f in ColorSchemeFields if getattr(args, f)}) + color_scheme = pdfium.PdfColorScheme(**cs_kwargs) if cs_kwargs else None kwargs = dict( scale = args.scale, @@ -326,29 +448,48 @@ def main(args): crop = args.crop, grayscale = args.grayscale, fill_color = args.fill_color, - color_scheme = cs, - fill_to_stroke = args.fill_to_stroke, optimize_mode = args.optimize_mode, draw_annots = args.draw_annots, may_draw_forms = args.draw_forms, force_halftone = args.force_halftone, rev_byteorder = args.rev_byteorder, prefer_bgrx = args.prefer_bgrx, + use_bgra_on_transparency = args.use_bgra_on_transparency, bitmap_maker = BitmapMakers[args.bitmap_maker], + color_scheme = color_scheme, + fill_to_stroke = args.fill_to_stroke, ) for type in args.no_antialias: kwargs[f"no_smooth{type}"] = True - # TODO dump all args except password? - logger.info(f"{args.engine_cls.__name__}, Format: {args.format}, rev_byteorder: {args.rev_byteorder}, prefer_bgrx {args.prefer_bgrx}") + saver_args = types.SimpleNamespace( + output_dir = args.output, + prefix = args.prefix, + n_digits = len(str(pdf_len)), + format = args.format, + use_bgra_on_transparency = args.use_bgra_on_transparency, + ) + postproc_kwargs = dict( + invert_lightness = args.invert_lightness, + exclude_images = args.exclude_images, + ) + if args.invert_lightness and args.optimize_mode == "lcd": + logger.warning("LCD optimization clashes with lightness inversion, as post-processing colors defeats the idea of subpixel rendering.") + + print_args = vars(args).copy() + del print_args["subcommand"], print_args["pages"] + if print_args["password"]: + print_args["password"] = "" + logger.debug(f"{print_args}") # TODO prettier? + if color_scheme: + logger.debug(f"{color_scheme}") - n_digits = len(str(pdf_len)) - path_parts = (args.output, args.prefix, n_digits, args.format) - engine = args.engine_cls(path_parts) + engine = args.engine_cls(saver_args, postproc_kwargs) if len(args.pages) <= args.linear: logger.info("Linear rendering ...") + engine.do_imports() for i in args.pages: _render_job(i, pdf, kwargs, engine) @@ -357,7 +498,6 @@ def main(args): logger.info("Parallel rendering ...") ctx = mp.get_context(args.parallel_strategy) - # TODO unify using mp.pool.Pool(context=...) ? pool_backends = dict( mp = (ctx.Pool, "imap"), ft = (functools.partial(ft.ProcessPoolExecutor, mp_context=ctx), "map"), @@ -366,10 +506,15 @@ def main(args): if args.parallel_map: map_attr = args.parallel_map - extra_init = (setup_logging if args.parallel_strategy in ("spawn", "forkserver") else None) + if args.parallel_strategy == "fork": + logging_init, engine_init = _do_nothing, _do_nothing + engine.do_imports() + else: + logging_init, engine_init = setup_logging, engine.do_imports + pool_kwargs = dict( initializer = _render_parallel_init, - initargs = (extra_init, pdf._input, args.password, args.draw_forms, kwargs, engine), + initargs = (logging_init, engine_init, pdf._input, args.password, args.draw_forms, kwargs, engine), ) n_procs = min(args.processes, len(args.pages)) diff --git a/src/pypdfium2/_cli/tile.py b/src/pypdfium2/_cli/tile.py index 82d2c1e67..3b92c41a1 100644 --- a/src/pypdfium2/_cli/tile.py +++ b/src/pypdfium2/_cli/tile.py @@ -1,11 +1,10 @@ -# SPDX-FileCopyrightText: 2024 geisserml +# SPDX-FileCopyrightText: 2025 geisserml # SPDX-License-Identifier: Apache-2.0 OR BSD-3-Clause from enum import Enum from pathlib import Path import pypdfium2.raw as pdfium_c import pypdfium2._helpers as pdfium -# TODO? consider dotted access from pypdfium2._cli._parsers import add_input, get_input @@ -49,7 +48,7 @@ def attach(parser): required = True, help = "Number of columns (vertical tiles)", ) - # NOTE no short aliases for width and height since -h would confilct with argparse help + # NOTE no short aliases for width and height since -h would conflict with argparse help parser.add_argument( "--width", type = float, diff --git a/src/pypdfium2/_cli/toc.py b/src/pypdfium2/_cli/toc.py index 6921c2af8..143b3e2f0 100644 --- a/src/pypdfium2/_cli/toc.py +++ b/src/pypdfium2/_cli/toc.py @@ -1,8 +1,7 @@ -# SPDX-FileCopyrightText: 2024 geisserml +# SPDX-FileCopyrightText: 2025 geisserml # SPDX-License-Identifier: Apache-2.0 OR BSD-3-Clause import pypdfium2.internal as pdfium_i -# TODO? consider dotted access from pypdfium2._cli._parsers import ( add_input, add_n_digits, @@ -25,18 +24,23 @@ def attach(parser): def main(args): pdf = get_input(args) - toc = pdf.get_toc( - max_depth = args.max_depth, - ) + toc = pdf.get_toc(max_depth=args.max_depth) - for item in toc: - state = "*" if item.n_kids == 0 else "-" if item.is_closed else "+" - target = "?" if item.page_index is None else item.page_index+1 - print( - " " * item.level + - "[%s] %s -> %s # %s %s" % ( - state, item.title, target, - pdfium_i.ViewmodeToStr.get(item.view_mode), - round_list(item.view_pos, args.n_digits), - ) + for bm in toc: + count, dest = bm.get_count(), bm.get_dest() + out = " " * bm.level + out += "[%s] %s -> " % ( + f"{count:+}" if count != 0 else "*", + bm.get_title(), ) + # distinguish between "dest == None" and "dest with unknown mode" while keeping the output machine readable + if dest: + index, (view_mode, view_pos) = dest.get_index(), dest.get_view() + out += "%s # %s %s" % ( + index+1 if index != None else "?", + pdfium_i.ViewmodeToStr.get(view_mode), + round_list(view_pos, args.n_digits), + ) + else: + out += "_" + print(out) diff --git a/src/pypdfium2/_deferred.py b/src/pypdfium2/_deferred.py new file mode 100644 index 000000000..ad71d3406 --- /dev/null +++ b/src/pypdfium2/_deferred.py @@ -0,0 +1,48 @@ +# SPDX-FileCopyrightText: 2025 geisserml +# SPDX-License-Identifier: Apache-2.0 OR BSD-3-Clause + +# see https://gist.github.com/mara004/6915e904797916b961e9c53b4fc874ec for alternative approaches to deferred imports + +__all__ = ("PIL_Image", "numpy") + +import sys +import logging +import importlib +import functools + +logger = logging.getLogger(__name__) + +if sys.version_info < (3, 8): # pragma: no cover + # NOTE alternatively, we could write our own cached property backport with python's descriptor protocol + def cached_property(func): + return property( functools.lru_cache(maxsize=1)(func) ) +else: + cached_property = functools.cached_property + + +class _DeferredModule: + # This is a simple deferred object proxy. + # TODO: use the lazy_object_proxy module, if installed? + + def __init__(self, modpath): + self._modpath = modpath + + def __repr__(self): + return f"" + + @cached_property + def _module(self): + logger.debug(f"Evaluating deferred import {self._modpath}") + return importlib.import_module(self._modpath) + + def __getattr__(self, k): + return getattr(self._module, k) + + +PIL_Image = _DeferredModule("PIL.Image") +numpy = _DeferredModule("numpy") + +# add setattr after we have initialized the modpaths +def _setattr_impl(self, k, v): + return setattr(self._module, k, v) +_DeferredModule.__setattr__ = _setattr_impl diff --git a/src/pypdfium2/_helpers/__init__.py b/src/pypdfium2/_helpers/__init__.py index cbe65ccc4..6566781ae 100644 --- a/src/pypdfium2/_helpers/__init__.py +++ b/src/pypdfium2/_helpers/__init__.py @@ -1,4 +1,4 @@ -# SPDX-FileCopyrightText: 2024 geisserml +# SPDX-FileCopyrightText: 2025 geisserml # SPDX-License-Identifier: Apache-2.0 OR BSD-3-Clause from pypdfium2._helpers.unsupported import * diff --git a/src/pypdfium2/_helpers/attachment.py b/src/pypdfium2/_helpers/attachment.py index 5b69e62b6..db17b7cce 100644 --- a/src/pypdfium2/_helpers/attachment.py +++ b/src/pypdfium2/_helpers/attachment.py @@ -1,4 +1,4 @@ -# SPDX-FileCopyrightText: 2024 geisserml +# SPDX-FileCopyrightText: 2025 geisserml # SPDX-License-Identifier: Apache-2.0 OR BSD-3-Clause __all__ = ("PdfAttachment", ) @@ -36,7 +36,8 @@ class PdfAttachment (pdfium_i.AutoCastable): def __init__(self, raw, pdf): - self.raw, self.pdf = raw, pdf + self.raw = raw + self.pdf = pdf def get_name(self): diff --git a/src/pypdfium2/_helpers/bitmap.py b/src/pypdfium2/_helpers/bitmap.py index 3fe4c1e30..2c8791711 100644 --- a/src/pypdfium2/_helpers/bitmap.py +++ b/src/pypdfium2/_helpers/bitmap.py @@ -1,59 +1,68 @@ -# SPDX-FileCopyrightText: 2024 geisserml +# SPDX-FileCopyrightText: 2025 geisserml # SPDX-License-Identifier: Apache-2.0 OR BSD-3-Clause -__all__ = ("PdfBitmap", "PdfBitmapInfo") +__all__ = ("PdfBitmap", "PdfPosConv") import ctypes import logging -import weakref -from collections import namedtuple import pypdfium2.raw as pdfium_c import pypdfium2.internal as pdfium_i from pypdfium2._helpers.misc import PdfiumError +from pypdfium2._deferred import PIL_Image, numpy logger = logging.getLogger(__name__) -try: - import PIL.Image -except ImportError: - PIL = None - -try: - import numpy -except ImportError: - numpy = None - class PdfBitmap (pdfium_i.AutoCloseable): """ Bitmap helper class. - Hint: - This class provides built-in converters (e. g. :meth:`.to_pil`, :meth:`.to_numpy`) that may be used to create a different representation of the bitmap. - Converters can be applied on :class:`.PdfBitmap` objects either as bound method (``bitmap.to_*()``), or as function (``PdfBitmap.to_*(bitmap)``) - The second pattern is useful for API methods that need to apply a caller-provided converter (e. g. :meth:`.PdfDocument.render`) - .. _PIL Modes: https://pillow.readthedocs.io/en/stable/handbook/concepts.html#concept-modes - Note: - All attributes of :class:`.PdfBitmapInfo` are available in this class as well. - Warning: ``bitmap.close()``, which frees the buffer of foreign bitmaps, is not validated for safety. - A bitmap must not be closed when other objects still depend on its buffer! + A bitmap must not be closed while other objects still depend on its buffer! Attributes: raw (FPDF_BITMAP): The underlying PDFium bitmap handle. buffer (~ctypes.c_ubyte): A ctypes array representation of the pixel data (each item is an unsigned byte, i. e. a number ranging from 0 to 255). + width (int): + Width of the bitmap (horizontal size). + height (int): + Height of the bitmap (vertical size). + stride (int): + Number of bytes per line in the bitmap buffer. + Depending on how the bitmap was created, there may be a padding of unused bytes at the end of each line, so this value can be greater than ``width * n_channels``. + format (int): + PDFium bitmap format constant (:attr:`FPDFBitmap_*`) + rev_byteorder (bool): + Whether the bitmap is using reverse byte order. + n_channels (int): + Number of channels per pixel. + mode (str): + The bitmap format as string (see `PIL Modes`_). """ def __init__(self, raw, buffer, width, height, stride, format, rev_byteorder, needs_free): - self.raw, self.buffer, self.width, self.height = raw, buffer, width, height - self.stride, self.format, self.rev_byteorder = stride, format, rev_byteorder + + self.raw = raw + self.buffer = buffer + self.width = width + self.height = height + self.stride = stride + self.format = format + self.rev_byteorder = rev_byteorder self.n_channels = pdfium_i.BitmapTypeToNChannels[self.format] - self.mode = (pdfium_i.BitmapTypeToStrReverse if self.rev_byteorder else pdfium_i.BitmapTypeToStr)[self.format] + self.mode = ( + pdfium_i.BitmapTypeToStrReverse if self.rev_byteorder else \ + pdfium_i.BitmapTypeToStr + )[self.format] + + # slot to store arguments for PdfPosConv, set on page rendering + self._pos_args = None + super().__init__(pdfium_c.FPDFBitmap_Destroy, needs_free=needs_free, obj=self.buffer) @@ -62,15 +71,22 @@ def parent(self): # AutoCloseable hook return None - def get_info(self): - """ - Returns: - PdfBitmapInfo: A namedtuple describing the bitmap. - """ - return PdfBitmapInfo( - width=self.width, height=self.height, stride=self.stride, format=self.format, - rev_byteorder=self.rev_byteorder, n_channels=self.n_channels, mode=self.mode, - ) + # NOTE To test all bitmap creation strategies through the CLI: + # MAKERS=(native foreign foreign_packed foreign_simple) + # DOCPATH="..." # ideally use a short one or pass e.g. --pages "1-3" + # for MAKER in ${MAKERS[@]}; do echo "$MAKER"; mkdir -p out/$MAKER; pypdfium2 render "$DOCPATH" -o out/$MAKER --bitmap-maker $MAKER; done + + # To test .from_raw(): + # pypdfium2 extract-images "$DOCPATH" -o out/ --use-bitmap + + + @classmethod + def _get_buffer(cls, raw, stride, height): + buffer_ptr = pdfium_c.FPDFBitmap_GetBuffer(raw) + if not buffer_ptr: + raise PdfiumError("Failed to get bitmap buffer (null pointer returned)") + buffer = ctypes.cast(buffer_ptr, ctypes.POINTER(ctypes.c_ubyte * (stride * height))).contents + return buffer @classmethod @@ -78,6 +94,9 @@ def from_raw(cls, raw, rev_byteorder=False, ex_buffer=None): """ Construct a :class:`.PdfBitmap` wrapper around a raw PDFium bitmap handle. + Note: + This method is primarily meant for bitmaps provided by pdfium (as in :meth:`.PdfImage.get_bitmap`). For bitmaps created by the caller, where the parameters are already known, it may be preferable to call the :class:`.PdfBitmap` constructor directly. + Parameters: raw (FPDF_BITMAP): PDFium bitmap handle. @@ -89,70 +108,88 @@ def from_raw(cls, raw, rev_byteorder=False, ex_buffer=None): width = pdfium_c.FPDFBitmap_GetWidth(raw) height = pdfium_c.FPDFBitmap_GetHeight(raw) - format = pdfium_c.FPDFBitmap_GetFormat(raw) stride = pdfium_c.FPDFBitmap_GetStride(raw) + format = pdfium_c.FPDFBitmap_GetFormat(raw) if ex_buffer is None: - needs_free = True - buffer_ptr = pdfium_c.FPDFBitmap_GetBuffer(raw) - if buffer_ptr is None: - raise PdfiumError("Failed to get bitmap buffer (null pointer returned)") - buffer = ctypes.cast(buffer_ptr, ctypes.POINTER(ctypes.c_ubyte * (stride * height))).contents + needs_free, buffer = True, cls._get_buffer(raw, stride, height) else: - needs_free = False - buffer = ex_buffer + needs_free, buffer = False, ex_buffer - return cls( - raw=raw, buffer=buffer, width=width, height=height, stride=stride, - format=format, rev_byteorder=rev_byteorder, needs_free=needs_free, - ) + return cls(raw, buffer, width, height, stride, format, rev_byteorder, needs_free) - # TODO support setting stride if external buffer is provided @classmethod - def new_native(cls, width, height, format, rev_byteorder=False, buffer=None): + def new_native(cls, width, height, format, rev_byteorder=False, buffer=None, stride=None): """ - Create a new bitmap using :func:`FPDFBitmap_CreateEx`, with a buffer allocated by Python/ctypes. - Bitmaps created by this function are always packed (no unused bytes at line end). + Create a new bitmap using :func:`FPDFBitmap_CreateEx`, with a buffer allocated by Python/ctypes, or provided by the caller. + + * If buffer and stride are None, a packed buffer is created. + * If a custom buffer is given but no stride, the buffer is assumed to be packed. + * If a custom stride is given but no buffer, a stride-agnostic buffer is created. + * If both custom buffer and stride are given, they are used as-is. + + Caller-provided buffer/stride are subject to a logical validation. """ - stride = width * pdfium_i.BitmapTypeToNChannels[format] + bpc = pdfium_i.BitmapTypeToNChannels[format] + if stride is None: + stride = width * bpc + else: + assert stride >= width * bpc + if buffer is None: buffer = (ctypes.c_ubyte * (stride * height))() + else: + assert len(buffer) >= stride * height + raw = pdfium_c.FPDFBitmap_CreateEx(width, height, format, buffer, stride) + return cls(raw, buffer, width, height, stride, format, rev_byteorder, needs_free=False) - # alternatively, we could call the constructor directly with the information from above - return cls.from_raw(raw, rev_byteorder, buffer) + # Alternatively, we could do: + # return cls.from_raw(raw, rev_byteorder, buffer) + # This implies some (technically unnecessary) API calls. Note, for a short time, there was a bug in pdfium where retrieving the params of a caller-created bitmap through the FPDFBitmap_Get*() APIs didn't work correctly, so better avoid doing this if we can help it. @classmethod def new_foreign(cls, width, height, format, rev_byteorder=False, force_packed=False): """ Create a new bitmap using :func:`FPDFBitmap_CreateEx`, with a buffer allocated by PDFium. + There may be a padding of unused bytes at line end, unless *force_packed=True* is given. - Using this method is discouraged. Prefer :meth:`.new_native` instead. + Note, the recommended default bitmap creation strategy is :meth:`.new_native`. """ stride = width * pdfium_i.BitmapTypeToNChannels[format] if force_packed else 0 raw = pdfium_c.FPDFBitmap_CreateEx(width, height, format, None, stride) - return cls.from_raw(raw, rev_byteorder) + # Retrieve stride set by pdfium, if we passed in 0. Otherwise, trust in pdfium to use the requested stride. + if not force_packed: # stride == 0 + stride = pdfium_c.FPDFBitmap_GetStride(raw) + buffer = cls._get_buffer(raw, stride, height) + return cls(raw, buffer, width, height, stride, format, rev_byteorder, needs_free=True) @classmethod def new_foreign_simple(cls, width, height, use_alpha, rev_byteorder=False): """ - Create a new bitmap using :func:`FPDFBitmap_Create`. The buffer is allocated by PDFium. - The resulting bitmap is supposed to be packed (i. e. no gap of unused bytes between lines). + Create a new bitmap using :func:`FPDFBitmap_Create`. The buffer is allocated by PDFium. + + PDFium docs specify that each line uses width * 4 bytes, with no gap between adjacent lines, i.e. the resulting buffer should be packed. - Using this method is discouraged. Prefer :meth:`.new_native` instead. + Contrary to the other ``PdfBitmap.new_*()`` methods, this method does not take a format constant, but a *use_alpha* boolean. If True, the format will be :attr:`FPDFBitmap_BGRA`, :attr:`FPFBitmap_BGRx` otherwise. Other bitmap formats cannot be used with this method. + + Note, the recommended default bitmap creation strategy is :meth:`.new_native`. """ raw = pdfium_c.FPDFBitmap_Create(width, height, use_alpha) - return cls.from_raw(raw, rev_byteorder) + stride = width * 4 # see above + buffer = cls._get_buffer(raw, stride, height) + format = pdfium_c.FPDFBitmap_BGRA if use_alpha else pdfium_c.FPDFBitmap_BGRx + return cls(raw, buffer, width, height, stride, format, rev_byteorder, needs_free=True) - def fill_rect(self, left, top, width, height, color): + def fill_rect(self, color, left, top, width, height): """ Fill a rectangle on the bitmap with the given color. - The coordinate system starts at the top left corner of the image. + The coordinate system's origin is the top left corner of the image. Note: This function replaces the color values in the given rectangle. It does not perform alpha compositing. @@ -162,7 +199,10 @@ def fill_rect(self, left, top, width, height, color): RGBA fill color (a tuple of 4 integers ranging from 0 to 255). """ c_color = pdfium_i.color_tohex(color, self.rev_byteorder) - pdfium_c.FPDFBitmap_FillRect(self, left, top, width, height, c_color) + ok = pdfium_c.FPDFBitmap_FillRect(self, left, top, width, height, c_color) + # Assuming pdfium >= 6635 (first tag to include commit ae9dbb6). With lower pdfium versions, this would always return None and fail. + if not ok: + raise PdfiumError("Failed to fill bitmap rectangle.") # Requirement: If the result is a view of the buffer (not a copy), it keeps the referenced memory valid. @@ -184,7 +224,7 @@ def to_numpy(self): The array contains as many rows as the bitmap is high. Each row contains as many pixels as the bitmap is wide. - The length of each pixel corresponds to the number of channels. + Each pixel will be an array holding the channel values, or just a value if there is only one channel (see :attr:`.n_channels` and :attr:`.format`). The resulting array is supposed to share memory with the original bitmap buffer, so changes to the buffer should be reflected in the array, and vice versa. @@ -197,11 +237,11 @@ def to_numpy(self): array = numpy.ndarray( # layout: row major - shape = (self.height, self.width, self.n_channels), + shape = (self.height, self.width, self.n_channels) if self.n_channels > 1 else (self.height, self.width), dtype = ctypes.c_ubyte, buffer = self.buffer, - # number of bytes per item for each nesting level (outer->inner, i. e. row, pixel, value) - strides = (self.stride, self.n_channels, 1), + # number of bytes per item for each nesting level (outer->inner: row, pixel, value - or row, value for a single-channel bitmap) + strides = (self.stride, self.n_channels, 1) if self.n_channels > 1 else (self.stride, 1), ) return array @@ -211,21 +251,19 @@ def to_pil(self): """ Convert the bitmap to a :mod:`PIL` image, using :func:`PIL.Image.frombuffer`. - For ``RGBA``, ``RGBX`` and ``L`` buffers, PIL is supposed to share memory with - the original bitmap buffer, so changes to the buffer should be reflected in the image, and vice versa. + For ``RGBA``, ``RGBX`` and ``L`` bitmaps, PIL is supposed to share memory with + the original buffer, so changes to the buffer should be reflected in the image, and vice versa. Otherwise, PIL will make a copy of the data. Returns: PIL.Image.Image: PIL image (representation or copy of the bitmap buffer). - - .. versionchanged:: 4.16 Set ``image.readonly = False`` so that changes to the image are also reflected in the buffer. """ # https://pillow.readthedocs.io/en/stable/reference/Image.html#PIL.Image.frombuffer # https://pillow.readthedocs.io/en/stable/handbook/writing-your-own-image-plugin.html#the-raw-decoder dest_mode = pdfium_i.BitmapTypeToStrReverse[self.format] - image = PIL.Image.frombuffer( + image = PIL_Image.frombuffer( dest_mode, # target color format (self.width, self.height), # size self.buffer, # buffer @@ -234,54 +272,56 @@ def to_pil(self): self.stride, # bytes per line 1, # orientation (top->bottom) ) + # set `readonly = False` so changes to the image are reflected in the buffer, if the original buffer is used image.readonly = False return image @classmethod - def from_pil(cls, pil_image, recopy=False): + def from_pil(cls, pil_image): """ Convert a :mod:`PIL` image to a PDFium bitmap. - Due to the restricted number of color formats and bit depths supported by PDFium's - bitmap implementation, this may be a lossy operation. + Due to the limited number of color formats and bit depths supported by :attr:`FPDF_BITMAP`, this may be a lossy operation. - Bitmaps returned by this function should be treated as immutable (i.e. don't call :meth:`.fill_rect`). + Bitmaps returned by this function should be treated as immutable. Parameters: pil_image (PIL.Image.Image): The image. Returns: PdfBitmap: PDFium bitmap (with a copy of the PIL image's data). - - .. deprecated:: 4.25 - The *recopy* parameter has been deprecated. """ + # FIXME possibility to get mutable buffer from PIL image? + if pil_image.mode in pdfium_i.BitmapStrToConst: - # PIL always seems to represent BGR(A/X) input as RGB(A/X), so this code passage is probably only hit for L + # PIL always seems to represent BGR(A/X) input as RGB(A/X), so this code passage would only be reached for L format = pdfium_i.BitmapStrToConst[pil_image.mode] else: pil_image = _pil_convert_for_pdfium(pil_image) format = pdfium_i.BitmapStrReverseToConst[pil_image.mode] - py_buffer = pil_image.tobytes() - if recopy: - buffer = (ctypes.c_ubyte * len(py_buffer)).from_buffer_copy(py_buffer) - else: - buffer = py_buffer - w, h = pil_image.size - return cls.new_native(w, h, format, rev_byteorder=False, buffer=buffer) + return cls.new_native(w, h, format, rev_byteorder=False, buffer=pil_image.tobytes()) - # TODO implement from_numpy() + def get_posconv(self, page): + """ + Acquire a :class:`.PdfPosConv` object to translate between coordinates on the bitmap and the page it was rendered from. + + This method requires passing in the page explicitly, to avoid holding a strong reference, so that bitmap and page can be independently freed by finalizer. + """ + # if the bitmap was rendered from a page, resolve the weakref and check identity + # before that, make sure *page* isn't None because that's what the weakref may resolve to if the referenced object is not alive anymore. + assert page, "Page must be non-null" + if not self._pos_args or self._pos_args[0]() is not page: + raise RuntimeError("This bitmap does not belong to the given page.") + return PdfPosConv(page, self._pos_args[1:]) def _pil_convert_for_pdfium(pil_image): - # FIXME? convoluted / hard to understand; improve control flow - if pil_image.mode == "1": pil_image = pil_image.convert("L") elif pil_image.mode.startswith("RGB"): @@ -294,34 +334,54 @@ def _pil_convert_for_pdfium(pil_image): # convert RGB(A/X) to BGR(A) for PDFium if pil_image.mode == "RGB": r, g, b = pil_image.split() - pil_image = PIL.Image.merge("RGB", (b, g, r)) + pil_image = PIL_Image.merge("RGB", (b, g, r)) elif pil_image.mode == "RGBA": r, g, b, a = pil_image.split() - pil_image = PIL.Image.merge("RGBA", (b, g, r, a)) + pil_image = PIL_Image.merge("RGBA", (b, g, r, a)) elif pil_image.mode == "RGBX": # technically the x channel may be unnecessary, but preserve what the caller passes in r, g, b, x = pil_image.split() - pil_image = PIL.Image.merge("RGBX", (b, g, r, x)) + pil_image = PIL_Image.merge("RGBX", (b, g, r, x)) return pil_image -PdfBitmapInfo = namedtuple("PdfBitmapInfo", "width height stride format rev_byteorder n_channels mode") -""" -Attributes: - width (int): - Width of the bitmap (horizontal size). - height (int): - Height of the bitmap (vertical size). - stride (int): - Number of bytes per line in the bitmap buffer. - Depending on how the bitmap was created, there may be a padding of unused bytes at the end of each line, so this value can be greater than ``width * n_channels``. - format (int): - PDFium bitmap format constant (:attr:`FPDFBitmap_*`) - rev_byteorder (bool): - Whether the bitmap is using reverse byte order. - n_channels (int): - Number of channels per pixel. - mode (str): - The bitmap format as string (see `PIL Modes`_). -""" +class PdfPosConv: + """ + Pdf coordinate translator. + + Hint: + You may want to use :meth:`.PdfBitmap.get_posconv` to obtain an instance of this class. + + Parameters: + page (PdfPage): + Handle to the page. + pos_args (tuple[int*5]): + pdfium canvas args (start_x, start_y, size_x, size_y, rotate), as in ``FPDF_RenderPageBitmap()`` etc. + """ + + # FIXME do we have to do overflow checking against too large sizes? + + def __init__(self, page, pos_args): + self.page = page + self.pos_args = pos_args + + def to_page(self, bitmap_x, bitmap_y): + """ + Translate coordinates from bitmap to page. + """ + page_x, page_y = ctypes.c_double(), ctypes.c_double() + ok = pdfium_c.FPDF_DeviceToPage(self.page, *self.pos_args, bitmap_x, bitmap_y, page_x, page_y) + if not ok: + raise PdfiumError("Failed to translate to page coordinates.") + return (page_x.value, page_y.value) + + def to_bitmap(self, page_x, page_y): + """ + Translate coordinates from page to bitmap. + """ + bitmap_x, bitmap_y = ctypes.c_int(), ctypes.c_int() + ok = pdfium_c.FPDF_PageToDevice(self.page, *self.pos_args, page_x, page_y, bitmap_x, bitmap_y) + if not ok: + raise PdfiumError("Failed to translate to bitmap coordinates.") + return (bitmap_x.value, bitmap_y.value) diff --git a/src/pypdfium2/_helpers/document.py b/src/pypdfium2/_helpers/document.py index 2fba017f8..11abbbf30 100644 --- a/src/pypdfium2/_helpers/document.py +++ b/src/pypdfium2/_helpers/document.py @@ -1,16 +1,11 @@ -# SPDX-FileCopyrightText: 2024 geisserml +# SPDX-FileCopyrightText: 2025 geisserml # SPDX-License-Identifier: Apache-2.0 OR BSD-3-Clause -__all__ = ("PdfDocument", "PdfFormEnv", "PdfXObject", "PdfOutlineItem") +__all__ = ("PdfDocument", "PdfFormEnv", "PdfXObject", "PdfBookmark", "PdfDest") -import os import ctypes import logging -import inspect -import warnings from pathlib import Path -from collections import namedtuple -import multiprocessing as mp import pypdfium2.raw as pdfium_c import pypdfium2.internal as pdfium_i @@ -29,22 +24,24 @@ class PdfDocument (pdfium_i.AutoCloseable): Parameters: input_data (str | pathlib.Path | bytes | ctypes.Array | typing.BinaryIO | FPDF_DOCUMENT): - The input PDF given as file path, bytes, ctypes array, byte buffer, or raw PDFium document handle. - A byte buffer is defined as an object that implements ``seek() tell() read() readinto()``. + The input PDF given as file path, bytes, ctypes array, byte stream, or raw PDFium document handle. + A byte stream is defined as an object that implements ``seek() tell() read() readinto()``. password (str | None): A password to unlock the PDF, if encrypted. Otherwise, None or an empty string may be passed. If a password is given but the PDF is not encrypted, it will be ignored (as of PDFium 5418). autoclose (bool): - Whether byte buffer input should be automatically closed on finalization. + Whether byte stream input should be automatically closed on finalization. Raises: - PdfiumError: Raised if the document failed to load. The exception message is annotated with the reason reported by PDFium. + PdfiumError: Raised if the document failed to load. The exception is annotated with the reason reported by PDFium (via message and :attr:`~.PdfiumError.err_code`). FileNotFoundError: Raised if an invalid or non-existent file path was given. Hint: + * Documents may be used in a ``with``-block, closing the document on context manager exit. + This is recommended when *input_data* is a file path, to safely and immediately release the bound file handle. * :func:`len` may be called to get a document's number of pages. - * Looping over a document will yield its pages from beginning to end. * Pages may be loaded using list index access. + * Looping over a document will yield its pages from beginning to end. * The ``del`` keyword and list index access may be used to delete pages. Attributes: @@ -68,8 +65,6 @@ def __init__(self, input, password=None, autoclose=False): self._autoclose = autoclose self._data_holder = [] self._data_closer = [] - - # question: can we make attributes like formenv effectively immutable for the caller? self.formenv = None if isinstance(self._input, pdfium_c.FPDF_DOCUMENT): @@ -82,6 +77,16 @@ def __init__(self, input, password=None, autoclose=False): super().__init__(PdfDocument._close_impl, self._data_holder, self._data_closer) + # Support using PdfDocument in a with-block + # Note that pdfium objects should be closed in hierarchial order, but this is managed by our parents/kids system, so callers don't need to mind that. + + def __enter__(self): + return self + + def __exit__(self, *_): + self.close() + + def __repr__(self): if isinstance(self._input, Path): input_r = repr( str(self._input) ) @@ -140,7 +145,7 @@ def init_forms(self, config=None): See the :attr:`formenv` attribute. Attention: - If form rendering is desired, this method shall be called immediately after document construction, before getting document length or page handles. + If form rendering is desired, this method shall be called right after document construction, before getting document length or page handles. Parameters: config (FPDF_FORMFILLINFO | None): @@ -153,11 +158,11 @@ def init_forms(self, config=None): # safety check for older binaries to prevent a segfault (could be removed at some point) # https://github.com/bblanchon/pdfium-binaries/issues/105 - if "V8" in PDFIUM_INFO.flags and PDFIUM_INFO.origin != "sourcebuild" and PDFIUM_INFO.build <= 5677: + if "V8" in PDFIUM_INFO.flags and "pdfium-binaries" in PDFIUM_INFO.origin and PDFIUM_INFO.build <= 5677: # pragma: no cover raise RuntimeError("V8 enabled pdfium-binaries builds <= 5677 crash on init_forms().") if not config: - if "XFA" in PDFIUM_INFO.flags: + if "XFA" in PDFIUM_INFO.flags: # pragma: no cover js_platform = pdfium_c.IPDF_JSPLATFORM(version=3) config = pdfium_c.FPDF_FORMFILLINFO(version=2, xfa_disabled=False, m_pJsPlatform=ctypes.pointer(js_platform)) else: @@ -166,13 +171,14 @@ def init_forms(self, config=None): raw = pdfium_c.FPDFDOC_InitFormFillEnvironment(self, config) if not raw: raise PdfiumError(f"Initializing form env failed for document {self}.") - self.formenv = PdfFormEnv(raw, config, self) + self.formenv = PdfFormEnv(raw, self, config) self._add_kid(self.formenv) if formtype in (pdfium_c.FORMTYPE_XFA_FULL, pdfium_c.FORMTYPE_XFA_FOREGROUND): - if "XFA" in PDFIUM_INFO.flags: + if "XFA" in PDFIUM_INFO.flags: # pragma: no cover ok = pdfium_c.FPDF_LoadXFA(self) if not ok: + # FIXME ability to propagate an optional exception with error code info? err = pdfium_c.FPDF_GetLastError() logger.warning(f"FPDF_LoadXFA() failed with {pdfium_i.XFAErrorToStr.get(err)}") else: @@ -182,7 +188,6 @@ def init_forms(self, config=None): ) - # TODO?(v5) consider cached property def get_formtype(self): """ Returns: @@ -192,7 +197,6 @@ def get_formtype(self): return pdfium_c.FPDF_GetFormType(self) - # TODO?(v5) consider cached property def get_pagemode(self): """ Returns: @@ -201,7 +205,6 @@ def get_pagemode(self): return pdfium_c.FPDFDoc_GetPageMode(self) - # TODO?(v5) consider cached property def is_tagged(self): """ Returns: @@ -216,7 +219,7 @@ def save(self, dest, version=None, flags=pdfium_c.FPDF_NO_INCREMENTAL): Parameters: dest (str | pathlib.Path | io.BytesIO): - File path or byte buffer the document shall be written to. + File path or byte stream the document shall be written to. version (int | None): The PDF version to use, given as an integer (14 for 1.4, 15 for 1.5, ...). If None (the default), PDFium will set a version automatically. @@ -314,7 +317,7 @@ def count_attachments(self): def get_attachment(self, index): """ Returns: - PdfAttachment: The attachment at *index* (zero-based). + PdfAttachment: The attachment at given index (zero-based). """ raw_attachment = pdfium_c.FPDFDoc_GetAttachment(self, index) if not raw_attachment: @@ -342,7 +345,7 @@ def new_attachment(self, name): def del_attachment(self, index): """ - Unlink the attachment at *index* (zero-based). + Unlink the attachment at given index (zero-based). It will be hidden from the viewer, but is still present in the file (as of PDFium 5418). Following attachments shift one slot to the left in the array representation used by PDFium's API. @@ -354,14 +357,13 @@ def del_attachment(self, index): raise PdfiumError(f"Failed to delete attachment at index {index}.") - # TODO deprecate in favour of index access? def get_page(self, index): """ Returns: - PdfPage: The page at *index* (zero-based). + PdfPage: The page at given index (zero-based). Note: This calls ``FORM_OnAfterLoadPage()`` if the document has an active form env. - The form env must not be closed before the page is closed! + In that case, note that closing the formenv would implicitly close the page. """ raw_page = pdfium_c.FPDF_LoadPage(self, index) @@ -397,16 +399,17 @@ def new_page(self, width, height, index=None): index = len(self) raw_page = pdfium_c.FPDFPage_New(self, index, width, height) page = PdfPage(raw_page, self, None) - # not doing formenv calls for new pages as we don't see the point + # not doing formenv calls for new pages self._add_kid(page) return page def del_page(self, index): """ - Remove the page at *index* (zero-based). + Remove the page at given index (zero-based). + It is recommended to close any open handles to the page before calling this method. """ - # FIXME what if the caller still has a handle to the page? + # FIXME not sure how pdfium would behave if the caller tries to access a handle to a deleted page... pdfium_c.FPDFPage_Delete(self, index) @@ -444,7 +447,7 @@ def import_pages(self, pdf, pages=None, index=None): def get_page_size(self, index): """ Returns: - (float, float): Width and height in PDF canvas units of the page at *index* (zero-based). + (float, float): Width and height of the page at given index (zero-based), in PDF canvas units. """ size = pdfium_c.FS_SIZEF() ok = pdfium_c.FPDF_GetPageSizeByIndexF(self, index, size) @@ -456,7 +459,7 @@ def get_page_size(self, index): def get_page_label(self, index): """ Returns: - str: Label of the page at *index* (zero-based). + str: Label of the page at given index (zero-based). (A page label is essentially an alias that may be displayed instead of the page number.) """ n_bytes = pdfium_c.FPDF_GetPageLabel(self, index, None, 0) @@ -478,49 +481,13 @@ def page_as_xobject(self, index, dest_pdf): PdfXObject: The page as XObject. """ raw_xobject = pdfium_c.FPDF_NewXObjectFromPage(dest_pdf, self, index) - if raw_xobject is None: + if not raw_xobject: raise PdfiumError(f"Failed to capture page at index {index} as FPDF_XOBJECT.") xobject = PdfXObject(raw=raw_xobject, pdf=dest_pdf) self._add_kid(xobject) return xobject - # TODO(apibreak) consider switching to a wrapper class around the raw bookmark - # (either with getter methods, or possibly cached properties) - def _get_bookmark(self, bookmark, level): - - n_bytes = pdfium_c.FPDFBookmark_GetTitle(bookmark, None, 0) - buffer = ctypes.create_string_buffer(n_bytes) - pdfium_c.FPDFBookmark_GetTitle(bookmark, buffer, n_bytes) - title = buffer.raw[:n_bytes-2].decode('utf-16-le') - - # TODO(apibreak) just expose count as-is rather than using two variables and doing extra work - count = pdfium_c.FPDFBookmark_GetCount(bookmark) - is_closed = True if count < 0 else None if count == 0 else False - n_kids = abs(count) - - dest = pdfium_c.FPDFBookmark_GetDest(self, bookmark) - page_index = pdfium_c.FPDFDest_GetDestPageIndex(self, dest) - if page_index == -1: - page_index = None - - n_params = ctypes.c_ulong() - view_pos = (pdfium_c.FS_FLOAT * 4)() - view_mode = pdfium_c.FPDFDest_GetView(dest, n_params, view_pos) - view_pos = list(view_pos)[:n_params.value] - - return PdfOutlineItem( - level = level, - title = title, - is_closed = is_closed, - n_kids = n_kids, - page_index = page_index, - view_mode = view_mode, - view_pos = view_pos, - ) - - - # TODO(apibreak) change outline API (see above) def get_toc( self, max_depth = 15, @@ -529,70 +496,64 @@ def get_toc( seen = None, ): """ - Iterate through the bookmarks in the document's table of contents. + Iterate through the bookmarks in the document's table of contents (TOC). Parameters: max_depth (int): Maximum recursion depth to consider. Yields: - :class:`.PdfOutlineItem`: Bookmark information. + :class:`.PdfBookmark` """ if seen is None: seen = set() - bookmark = pdfium_c.FPDFBookmark_GetFirstChild(self, parent) + bm_ptr = pdfium_c.FPDFBookmark_GetFirstChild(self, parent) - while bookmark: + # NOTE We need bool(ptr) here to handle null pointers (where accessing .contents would raise an exception). Don't use ptr != None, it's always true. + while bm_ptr: - address = ctypes.addressof(bookmark.contents) + address = ctypes.addressof(bm_ptr.contents) if address in seen: - logger.warning("A circular bookmark reference was detected whilst parsing the table of contents.") + logger.warning("A circular bookmark reference was detected while traversing the table of contents.") break else: seen.add(address) - yield self._get_bookmark(bookmark, level) + yield PdfBookmark(bm_ptr, self, level) if level < max_depth-1: - yield from self.get_toc( - max_depth = max_depth, - parent = bookmark, - level = level + 1, - seen = seen, - ) + yield from self.get_toc(max_depth=max_depth, parent=bm_ptr, level=level+1, seen=seen) + elif pdfium_c.FPDFBookmark_GetFirstChild(self, bm_ptr): + # Warn only if there actually is a subtree. If level == max_depth but the tree ends there, it's fine as no info is skipped. + logger.warning(f"Maximum recursion depth {max_depth} reached (subtree skipped).") - bookmark = pdfium_c.FPDFBookmark_GetNextSibling(self, bookmark) + bm_ptr = pdfium_c.FPDFBookmark_GetNextSibling(self, bm_ptr) + + +def _open_pdf(input_data, password, autoclose): + to_hold, to_close = (), () + if password is not None: + password = (password+"\x00").encode("utf-8") - def render( - self, - converter, - renderer = PdfPage.render, - page_indices = None, - pass_info = False, - n_processes = None, # ignored, retained for compat - mk_formconfig = None, # ignored, retained for compat - **kwargs - ): - """ - .. deprecated:: 4.19 - This method will be removed with the next major release due to serious issues rooted in the original API design. Use :meth:`.PdfPage.render()` instead. - *Note that the CLI provides parallel rendering using a proper caller-side process pool with inline saving in rendering jobs.* - - .. versionchanged:: 4.25 - Removed the original process pool implementation and turned this into a wrapper for linear rendering, due to the serious conceptual issues and possible memory load escalation, especially with expensive receiving code (e.g. PNG encoding) or long documents. See the changelog for more info - """ - - warnings.warn("The document-level pdf.render() API is deprecated and uncored due to serious issues in the original concept. Use page.render() and a caller-side loop or process pool instead.", category=DeprecationWarning) - - if not page_indices: - page_indices = [i for i in range(len(self))] - for i in page_indices: - bitmap = renderer(self[i], **kwargs) - if pass_info: - yield (converter(bitmap), bitmap.get_info()) - else: - yield converter(bitmap) + if isinstance(input_data, Path): + pdf = pdfium_c.FPDF_LoadDocument((str(input_data)+"\x00").encode("utf-8"), password) + elif isinstance(input_data, (bytes, ctypes.Array)): + pdf = pdfium_c.FPDF_LoadMemDocument64(input_data, len(input_data), password) + to_hold = (input_data, ) + elif pdfium_i.is_buffer(input_data, "r"): + bufaccess, to_hold = pdfium_i.get_bufreader(input_data) + if autoclose: + to_close = (input_data, ) + pdf = pdfium_c.FPDF_LoadCustomDocument(bufaccess, password) + else: + raise TypeError(f"Invalid input type '{type(input_data).__name__}'") + + if pdfium_c.FPDF_GetPageCount(pdf) < 1: + err_code = pdfium_c.FPDF_GetLastError() + raise PdfiumError(f"Failed to load document (PDFium: {pdfium_i.ErrorToStr.get(err_code)}).", err_code=err_code) + + return pdf, to_hold, to_close class PdfFormEnv (pdfium_i.AutoCloseable): @@ -608,8 +569,10 @@ class PdfFormEnv (pdfium_i.AutoCloseable): Parent document this form env belongs to. """ - def __init__(self, raw, config, pdf): - self.raw, self.config, self.pdf = raw, config, pdf + def __init__(self, raw, pdf, config): + self.raw = raw + self.pdf = pdf + self.config = config super().__init__(PdfFormEnv._close_impl, self.config, self.pdf) @property @@ -633,7 +596,8 @@ class PdfXObject (pdfium_i.AutoCloseable): """ def __init__(self, raw, pdf): - self.raw, self.pdf = raw, pdf + self.raw = raw + self.pdf = pdf super().__init__(pdfium_c.FPDF_CloseXObject) @property @@ -643,65 +607,94 @@ def parent(self): # AutoCloseable hook def as_pageobject(self): """ Returns: - PdfObject: An independent page object representation of the XObject. - If multiple page objects are created from one XObject, they share resources. - Page objects created from an XObject remain valid after the XObject is closed. + PdfObject: An independent pageobject representation of the XObject. + If multiple pageobjects are created from an XObject, they share resources. + Returned pageobjects remain valid after the XObject is closed. """ raw_pageobj = pdfium_c.FPDF_NewFormObjectFromXObject(self) - return PdfObject( # not a child object (see above) - raw = raw_pageobj, - pdf = self.pdf, - ) + # not a child object (see above) + return PdfObject(raw=raw_pageobj, pdf=self.pdf) -def _open_pdf(input_data, password, autoclose): +class PdfBookmark (pdfium_i.AutoCastable): + """ + Bookmark helper class. - to_hold, to_close = (), () - if password is not None: - password = (password+"\x00").encode("utf-8") + Attributes: + raw (FPDF_BOOKMARK): + The underlying PDFium bookmark handle. + pdf (PdfDocument): + Reference to the document this bookmark belongs to. + level (int): + The bookmark's nesting level in the TOC tree (zero-based). Corresponds to the number of parent bookmarks. + """ - if isinstance(input_data, Path): - pdf = pdfium_c.FPDF_LoadDocument((str(input_data)+"\x00").encode("utf-8"), password) - elif isinstance(input_data, (bytes, ctypes.Array)): - pdf = pdfium_c.FPDF_LoadMemDocument64(input_data, len(input_data), password) - to_hold = (input_data, ) - elif pdfium_i.is_buffer(input_data, "r"): - bufaccess, to_hold = pdfium_i.get_bufreader(input_data) - if autoclose: - to_close = (input_data, ) - pdf = pdfium_c.FPDF_LoadCustomDocument(bufaccess, password) - else: - raise TypeError(f"Invalid input type '{type(input_data).__name__}'") + def __init__(self, raw, pdf, level): + self.raw = raw + self.pdf = pdf + self.level = level - if pdfium_c.FPDF_GetPageCount(pdf) < 1: - err_code = pdfium_c.FPDF_GetLastError() - raise PdfiumError(f"Failed to load document (PDFium: {pdfium_i.ErrorToStr.get(err_code)}).") + def get_title(self): + """ + Returns: + str: The bookmark's title string. + """ + n_bytes = pdfium_c.FPDFBookmark_GetTitle(self, None, 0) + buffer = ctypes.create_string_buffer(n_bytes) + pdfium_c.FPDFBookmark_GetTitle(self, buffer, n_bytes) + return buffer.raw[:n_bytes-2].decode("utf-16-le") - return pdf, to_hold, to_close - + def get_count(self): + """ + Returns: + int: Signed number of child bookmarks that would be visible if the bookmark were open (i.e. recursively counting children of open children). + The bookmark's initial state is open (expanded) if the number is positive, closed (collapsed) if negative. + Zero if the bookmark has no descendants. + """ + return pdfium_c.FPDFBookmark_GetCount(self) + + def get_dest(self): + """ + Returns: + PdfDest | None: The bookmark's destination (an object providing page index and viewport), or None on failure. + """ + raw_dest = pdfium_c.FPDFBookmark_GetDest(self.pdf, self) + if not raw_dest: + return None + return PdfDest(raw_dest, pdf=self.pdf) -# TODO(apibreak) change outline API (see above) -PdfOutlineItem = namedtuple("PdfOutlineItem", "level title is_closed n_kids page_index view_mode view_pos") -""" -Bookmark information. -Parameters: - level (int): - Number of parent items. - title (str): - Title string of the bookmark. - is_closed (bool): - True if child items shall be collapsed, False if they shall be expanded. - None if the item has no descendants (i. e. ``n_kids == 0``). - n_kids (int): - Absolute number of child items, according to the PDF. - page_index (int | None): - Zero-based index of the page the bookmark points to. - May be None if the bookmark has no target page (or it could not be determined). - view_mode (int): - A view mode constant (:data:`PDFDEST_VIEW_*`) defining how the coordinates of *view_pos* shall be interpreted. - view_pos (list[float]): - Target position on the page the viewport should jump to when the bookmark is clicked. - It is a sequence of :class:`float` values in PDF canvas units. - Depending on *view_mode*, it may contain between 0 and 4 coordinates. -""" +class PdfDest (pdfium_i.AutoCastable): + """ + Destination helper class. + + Attributes: + raw (FPDF_DEST): The underlying PDFium destination handle. + pdf (PdfDocument): Reference to the document this dest belongs to. + """ + + def __init__(self, raw, pdf): + self.raw = raw + self.pdf = pdf + + def get_index(self): + """ + Returns: + int | None: Zero-based index of the page the dest points to, or None on failure. + """ + val = pdfium_c.FPDFDest_GetDestPageIndex(self.pdf, self) + return val if val >= 0 else None + + def get_view(self): + """ + Returns: + (int, list[float]): A tuple of (view_mode, view_pos). + *view_mode* is a constant (one of :data:`PDFDEST_VIEW_*`) defining how *view_pos* shall be interpreted. + *view_pos* is the target position on the page the dest points to. + It may contain between 0 to 4 float coordinates, depending on the view mode. + """ + n_params = ctypes.c_ulong() + pos = (pdfium_c.FS_FLOAT * 4)() + mode = pdfium_c.FPDFDest_GetView(self, n_params, pos) + pos = list(pos)[:n_params.value] + return mode, pos diff --git a/src/pypdfium2/_helpers/matrix.py b/src/pypdfium2/_helpers/matrix.py index 4d9aff402..d10348898 100644 --- a/src/pypdfium2/_helpers/matrix.py +++ b/src/pypdfium2/_helpers/matrix.py @@ -1,4 +1,4 @@ -# SPDX-FileCopyrightText: 2024 geisserml +# SPDX-FileCopyrightText: 2025 geisserml # SPDX-License-Identifier: Apache-2.0 OR BSD-3-Clause __all__ = ("PdfMatrix", ) @@ -7,10 +7,8 @@ import ctypes import pypdfium2.raw as pdfium_c - -# TODO consider adding PdfRectangle support model to calculate size and corner points -# NOTE the code below was written by a non-mathematician - might contain mistakes! - +# Note, the code below was written by a non-mathematician - might contain mistakes! +# In the future, we may want to consider adding a PdfRectangle support model to calculate size and corner points. class PdfMatrix: """ @@ -21,7 +19,7 @@ class PdfMatrix: Note: * The PDF format uses row vectors. * Transformations operate from the origin of the coordinate system - (PDF coordinates: bottom left corner, Device coordinates: top left corner). + (PDF coordinates: commonly bottom left, but can be any corner in principle. Device coordinates: top left). * Matrix calculations are implemented independently in Python. * Matrix objects are immutable, so transforming methods return a new matrix. * Matrix objects implement ctypes auto-conversion to ``FS_MATRIX`` for easy use as C function parameter. @@ -40,16 +38,13 @@ class PdfMatrix: def __init__(self, a=1, b=0, c=0, d=1, e=0, f=0): self.a, self.b, self.c, self.d, self.e, self.f = a, b, c, d, e, f - def __repr__(self): return f"PdfMatrix{self.get()}" - def __eq__(self, other): if type(self) is not type(other): return False - return (self.get() == other.get()) - + return self.get() == other.get() @property def _as_parameter_(self): @@ -91,6 +86,7 @@ def multiply(self, other): b = self.a*other.b + self.b*other.d, c = self.c*other.a + self.d*other.c, d = self.c*other.b + self.d*other.d, + # corresponds to: e, f = other.on_point(self.e, self.f) - transforms X/Y translation e = self.e*other.a + self.f*other.c + other.e, f = self.e*other.b + self.f*other.d + other.f, ) @@ -129,13 +125,15 @@ def rotate(self, angle, ccw=False, rad=False): return self.multiply( PdfMatrix(c, s, -s, c) if ccw else PdfMatrix(c, -s, s, c) ) - def mirror(self, v, h): + def mirror(self, invert_x, invert_y): """ Parameters: - v (bool): Whether to mirror vertically (at the Y axis). - h (bool): Whether to mirror horizontall (at the X axis). + invert_x (bool): If True, invert X coordinates (horizontal transform). Corresponds to flipping around the Y axis. + invert_y (bool): If True, invert Y coordinates (vertical transform). Corresponds to flipping around the X axis. + Note: + Flipping around a vertical axis leads to a horizontal transform, and vice versa. """ - return self.scale(x=(-1 if v else 1), y=(-1 if h else 1)) + return self.scale(x=(-1 if invert_x else 1), y=(-1 if invert_y else 1)) def skew(self, x_angle, y_angle, rad=False): @@ -174,7 +172,6 @@ def on_rect(self, left, bottom, right, top): self.on_point(right, top), self.on_point(right, bottom), ) - # NOTE maybe a single loop with min/max x/y vars and comparisons would be more efficient... return ( # new rect min(p[0] for p in points), # left min(p[1] for p in points), # bottom diff --git a/src/pypdfium2/_helpers/misc.py b/src/pypdfium2/_helpers/misc.py index 370522141..2b2a2a54b 100644 --- a/src/pypdfium2/_helpers/misc.py +++ b/src/pypdfium2/_helpers/misc.py @@ -1,9 +1,17 @@ -# SPDX-FileCopyrightText: 2024 geisserml +# SPDX-FileCopyrightText: 2025 geisserml # SPDX-License-Identifier: Apache-2.0 OR BSD-3-Clause __all__ = ("PdfiumError", ) class PdfiumError (RuntimeError): - """ An exception from the PDFium library, detected by function return code. """ - pass + """ + An exception from the PDFium library, detected by function return code. + + Attributes: + err_code (int | None): PDFium error code, for programmatic handling of error subtypes, if provided by the API in question (e.g. document loading). None otherwise. + """ + + def __init__(self, msg, err_code=None): + super().__init__(msg) + self.err_code = err_code diff --git a/src/pypdfium2/_helpers/page.py b/src/pypdfium2/_helpers/page.py index bff335f5e..b7a4023ed 100644 --- a/src/pypdfium2/_helpers/page.py +++ b/src/pypdfium2/_helpers/page.py @@ -1,4 +1,4 @@ -# SPDX-FileCopyrightText: 2024 geisserml +# SPDX-FileCopyrightText: 2025 geisserml # SPDX-License-Identifier: Apache-2.0 OR BSD-3-Clause __all__ = ("PdfPage", "PdfColorScheme") @@ -6,6 +6,7 @@ import math import ctypes import logging +import weakref import pypdfium2.raw as pdfium_c import pypdfium2.internal as pdfium_i from pypdfium2._helpers.misc import PdfiumError @@ -22,12 +23,18 @@ class PdfPage (pdfium_i.AutoCloseable): Page helper class. Attributes: - raw (FPDF_PAGE): The underlying PDFium page handle. - pdf (PdfDocument): Reference to the document this page belongs to. + raw (FPDF_PAGE): + The underlying PDFium page handle. + pdf (PdfDocument): + Reference to the document this page belongs to. + formenv (PdfFormEnv | None): + Formenv handle, if the parent pdf had an active formenv at the time of page retrieval. None otherwise. """ def __init__(self, raw, pdf, formenv): - self.raw, self.pdf, self.formenv = raw, pdf, formenv + self.raw = raw + self.pdf = pdf + self.formenv = formenv super().__init__(PdfPage._close_impl, self.formenv) @@ -40,7 +47,7 @@ def _close_impl(raw, formenv): @property def parent(self): # AutoCloseable hook - # Might want to have this point to the direct parent, i. e. (self.pdf if formenv is None else self.formenv), but this might confuse callers expecting that parent be always pdf for pages. + # Might want to have this point to the nearest dependency, i.e. (self.pdf if formenv is None else self.formenv), but this would confuse callers expecting that parent be always pdf for pages. return self.pdf @@ -97,9 +104,9 @@ def get_mediabox(self, fallback_ok=True): (float, float, float, float) | None: The page MediaBox in PDF canvas units, consisting of four coordinates (usually x0, y0, x1, y1). If MediaBox is not defined, returns ANSI A (0, 0, 612, 792) if ``fallback_ok=True``, None otherwise. - Note: - Due to quirks in PDFium's public API, all ``get_*box()`` functions except :meth:`.get_bbox` - do not inherit from parent nodes in the page tree (as of PDFium 5418). + + .. admonition:: Known issue\n + Due to quirks in PDFium, all ``get_*box()`` functions except :meth:`.get_bbox` do not inherit from parent nodes in the page tree (as of PDFium 5418). """ # https://crbug.com/pdfium/1786 return self._get_box(pdfium_c.FPDFPage_GetMediaBox, lambda: (0, 0, 612, 792), fallback_ok) @@ -193,15 +200,15 @@ def get_textpage(self): def insert_obj(self, pageobj): """ - Insert a page object into the page. + Insert a pageobject into the page. - The page object must not belong to a page yet. If it belongs to a PDF, this page must be part of the PDF. + The pageobject must not belong to a page yet. If it belongs to a PDF, the target page must be part of that PDF. Position and form are defined by the object's matrix. If it is the identity matrix, the object will appear as-is on the bottom left corner of the page. Parameters: - pageobj (PdfObject): The page object to insert. + pageobj (PdfObject): The pageobject to insert. """ if pageobj.page: @@ -217,16 +224,21 @@ def insert_obj(self, pageobj): def remove_obj(self, pageobj): """ - Remove a page object from the page. - As of PDFium 5692, detached page objects may be only re-inserted into existing pages of the same document. - If the page object is not re-inserted into a page, its ``close()`` method may be called. + Remove a pageobject from the page. + As of PDFium 5692, detached pageobjects may be only re-inserted into existing pages of the same document. + If the pageobject is not re-inserted into a page, its ``close()`` method may be called. + + Note: + If the object's :attr:`~.PdfObject.type` is :data:`FPDF_PAGEOBJ_TEXT`, any :class:`.PdfTextPage` handles to the page should be closed before removing the object. Parameters: - pageobj (PdfObject): The page object to remove. + pageobj (PdfObject): The pageobject to remove. """ + # note https://pdfium-review.googlesource.com/c/pdfium/+/118914 + if pageobj.page is not self: - raise ValueError("The page object you attempted to remove is not part of this page.") + raise ValueError("The pageobject you attempted to remove is not part of this page.") ok = pdfium_c.FPDFPage_RemoveObject(self, pageobj) if not ok: @@ -237,7 +249,7 @@ def remove_obj(self, pageobj): def gen_content(self): """ - Generate page content to apply additions, removals or modifications of page objects. + Generate page content to apply additions, removals or modifications of pageobjects. If page content was changed, this function should be called once before saving the document or re-loading the page. """ @@ -246,24 +258,22 @@ def gen_content(self): raise PdfiumError("Failed to generate page content.") - def get_objects(self, filter=None, max_depth=2, form=None, level=0): + def get_objects(self, filter=None, max_depth=15, form=None, level=0): """ - Iterate through the page objects on this page. + Iterate through the pageobjects on this page. Parameters: filter (list[int] | None): - An optional list of page object types to filter (:attr:`FPDF_PAGEOBJ_*`). + An optional list of pageobject types to filter (:attr:`FPDF_PAGEOBJ_*`). Any objects whose type is not contained will be skipped. If None or empty, all objects will be provided, regardless of their type. max_depth (int): Maximum recursion depth to consider when descending into Form XObjects. Yields: - :class:`.PdfObject`: A page object. + :class:`.PdfObject`: A pageobject. """ - # TODO? close skipped objects explicitly ? - if form: count_objects = pdfium_c.FPDFFormObj_CountObjects get_object = pdfium_c.FPDFFormObj_GetObject @@ -275,15 +285,15 @@ def get_objects(self, filter=None, max_depth=2, form=None, level=0): n_objects = count_objects(parent) if n_objects < 0: - raise PdfiumError("Failed to get number of page objects.") + raise PdfiumError("Failed to get number of pageobjects.") for i in range(n_objects): raw_obj = get_object(parent, i) - if raw_obj is None: - raise PdfiumError("Failed to get page object.") + if not raw_obj: + raise PdfiumError("Failed to get pageobject.") - # Not a child object, because the lifetime of pageobjects that are part of a page is managed by pdfium. The .page reference is enough to keep the parent alive, unless the caller explicitly closes it (which may not merit storing countless of weakrefs). + # Don't register as child object, because the lifetime of pageobjects that are part of a page is managed by pdfium. The parent page should remain alive while a pageobject is used, but it seems unjustified to store countless of weakrefs just to lock pageobjects when the parent page is closed. helper_obj = PdfObject(raw_obj, page=self, pdf=self.pdf, level=level) if not filter or helper_obj.type in filter: yield helper_obj @@ -297,16 +307,21 @@ def get_objects(self, filter=None, max_depth=2, form=None, level=0): ) - # non-public because it doesn't really work (returns success but does nothing on all samples we tried) - def _flatten(self, flag=pdfium_c.FLAT_NORMALDISPLAY): + def flatten(self, flag=pdfium_c.FLAT_NORMALDISPLAY): """ - Attempt to flatten annotations and form fields into the page contents. + Flatten form fields and annotations into page contents. + + Attention: + * :meth:`~.PdfDocument.init_forms` must have been called on the parent pdf, before the page was retrieved, for this method to work. In other words, :attr:`.PdfPage.formenv` must be non-null. + * Flattening may invalidate existing handles to the page, so you may want to re-initialize these afterwards. Parameters: flag (int): PDFium flattening target (:attr:`FLAT_*`) Returns: int: PDFium flattening status (:attr:`FLATTEN_*`). :attr:`FLATTEN_FAIL` is handled internally. """ + if not self.formenv: + raise RuntimeError("page.flatten() requires previous pdf.init_forms() before page retrieval.") rc = pdfium_c.FPDFPage_Flatten(self, flag) if rc == pdfium_c.FLATTEN_FAIL: raise PdfiumError("Failed to flatten annotations / form fields.") @@ -318,7 +333,6 @@ def _flatten(self, flag=pdfium_c.FLAT_NORMALDISPLAY): # - add lower-level renderer that takes a caller-provided bitmap # e. g. render(), render_ex(), render_matrix(), render_matrix_ex() - def render( self, scale = 1, @@ -338,70 +352,88 @@ def render( scale (float): A factor scaling the number of pixels per PDF canvas unit. This defines the resolution of the image. To convert a DPI value to a scale factor, multiply it by the size of 1 canvas unit in inches (usually 1/72in). [#user_unit]_ - + rotation (int): Additional rotation in degrees (0, 90, 180, or 270). - + crop (tuple[float, float, float, float]): Amount in PDF canvas units to cut off from page borders (left, bottom, right, top). Crop is applied after rotation. - + may_draw_forms (bool): - If True, render form fields (provided the document has forms and :meth:`~PdfDocument.init_forms` was called). + If True, render form fields (provided the document has forms and :meth:`~.PdfDocument.init_forms` was called). bitmap_maker (typing.Callable): Callback function used to create the :class:`.PdfBitmap`. - - color_scheme (PdfColorScheme | None): - An optional, custom rendering color scheme. - - fill_to_stroke (bool): - If True and rendering with custom color scheme, fill paths will be stroked. - + fill_color (tuple[int, int, int, int]): - Color the bitmap will be filled with before rendering (RGBA values from 0 to 255). - + Color the bitmap will be filled with before rendering. This uses RGBA syntax regardless of the pixel format used, with values from 0 to 255. + If the fill color is not opaque (i.e. has transparency), ``{BGR,RGB}A`` will be used. + grayscale (bool): If True, render in grayscale mode. - + optimize_mode (None | str): Page rendering optimization mode (None, "lcd", "print"). - + draw_annots (bool): If True, render page annotations. - + no_smoothtext (bool): If True, disable text anti-aliasing. Overrides ``optimize_mode="lcd"``. - + no_smoothimage (bool): If True, disable image anti-aliasing. - + no_smoothpath (bool): If True, disable path anti-aliasing. - + force_halftone (bool): If True, always use halftone for image stretching. - + limit_image_cache (bool): If True, limit image cache size. - + rev_byteorder (bool): - If True, render with reverse byte order, leading to ``RGB(A/X)`` output instead of ``BGR(A/X)``. + If True, render with reverse byte order, leading to ``RGB{A/x}`` output rather than ``BGR{A/x}``. Other pixel formats are not affected. - + prefer_bgrx (bool): - If True, prefer four-channel over three-channel pixel formats, even if the alpha byte is unused. + If True, use 4-byte ``{BGR/RGB}x`` rather than 3-byte ``{BGR/RGB}`` (i.e. add an unused byte). Other pixel formats are not affected. - + + use_bgra_on_transparency (bool): + If True, use a pixel format with alpha channel (i.e. ``{BGR/RGB}A``) if page content has transparency. + This is recommended for performance in these cases, but as page-dependent format selection is somewhat unexpected, it is not enabled by default. + force_bitmap_format (int | None): - If given, override automatic pixel format selection and enforce use of the given format (one of the :attr:`FPDFBitmap_*` constants). - + If given, override automatic pixel format selection and enforce use of the given format (one of the :attr:`FPDFBitmap_*` constants). In this case, you should not pass any other format selection options, except potentially *rev_byteorder*. + extra_flags (int): Additional PDFium rendering flags. May be combined with bitwise OR (``|`` operator). + + color_scheme (PdfColorScheme | None): + A custom pdfium color scheme. Note that this may flatten different colors into one, so the usability of this is limited. + + fill_to_stroke (bool): + If a *color_scheme* is given, whether to only draw borders around fill areas using the `path_stroke` color, instead of filling with the `path_fill` color. Returns: PdfBitmap: Bitmap of the rendered page. - .. [#user_unit] Since PDF 1.6, pages may define an additional user unit factor. In this case, 1 canvas unit is equivalent to ``user_unit * (1/72)`` inches. PDFium currently does not have an API to get the user unit, so this is not taken into account. + .. admonition:: Format selection + + This is the format selection hierarchy used by :meth:`.render`, from lowest to highest priority: + + * default: ``BGR`` + * ``prefer_bgrx=True``: ``BGRx`` + * ``grayscale=True``: ``L`` + * ``prefer_bgra_on_transparency=True``: ``BGRA`` if the page has transparency, else the format selected otherwise + * ``fill_color[3] < 255``: ``BGRA`` (background color with transparency) + * ``force_bitmap_format=...`` -> any supported by pdfium + + Additionally, *rev_byteorder* will swap ``BGR{A/x}`` to ``RGB{A/x}`` if applicable. + + .. [#user_unit] Since PDF 1.6, pages may define an additional user unit factor. In this case, 1 canvas unit is equivalent to ``user_unit * (1/72)`` inches. PDFium does not currently provide an API to get the user unit, so this is not taken into account. """ src_width = math.ceil(self.get_width() * scale) @@ -415,22 +447,21 @@ def render( if any(d < 1 for d in (width, height)): raise ValueError("Crop exceeds page dimensions") - cl_format, rev_byteorder, fill_color, flags = _parse_renderopts(**kwargs) + cl_format, rev_byteorder, fill_color, flags = _parse_renderopts(self, **kwargs) if (color_scheme is not None) and fill_to_stroke: flags |= pdfium_c.FPDF_CONVERT_FILL_TO_STROKE bitmap = bitmap_maker(width, height, format=cl_format, rev_byteorder=rev_byteorder) - bitmap.fill_rect(0, 0, width, height, fill_color) + bitmap.fill_rect(fill_color, 0, 0, width, height) - render_args = (bitmap, self, -crop[0], -crop[3], src_width, src_height, pdfium_i.RotationToConst[rotation], flags) + pos_args = (-crop[0], -crop[3], src_width, src_height, pdfium_i.RotationToConst[rotation]) + render_args = (bitmap, self, *pos_args, flags) if color_scheme is None: pdfium_c.FPDF_RenderPageBitmap(*render_args) else: - pause = pdfium_c.IFSDK_PAUSE(version=1) pdfium_i.set_callback(pause, "NeedToPauseNow", lambda _: False) - fpdf_cs = color_scheme.convert(rev_byteorder) status = pdfium_c.FPDF_RenderPageBitmapWithColorScheme_Start(*render_args, fpdf_cs, pause) assert status == pdfium_c.FPDF_RENDER_DONE @@ -439,13 +470,14 @@ def render( if may_draw_forms and self.formenv: pdfium_c.FPDF_FFLDraw(self.formenv, *render_args) + bitmap._pos_args = (weakref.ref(self), *pos_args) return bitmap -def _auto_bitmap_format(fill_color, grayscale, prefer_bgrx): - # TODO(apibreak) we'd like to also use BGRA if FPDFPage_HasTransparency(page) is True for performance reasons (see [1]), but this may break caller format expectations, and would make format selection document-dependent - # [1]: https://chromium.googlesource.com/chromium/src/+/21e456b92bfadc625c947c718a6c4c5bf0c4c61b - if (fill_color[3] < 255): +def _auto_bitmap_format(page, fill_color, grayscale, prefer_bgrx, use_bgra_on_transparency): + # regarding use_bgra_on_transparency, see + # https://chromium.googlesource.com/chromium/src/+/21e456b92bfadc625c947c718a6c4c5bf0c4c61b + if fill_color[3] < 255 or (use_bgra_on_transparency and pdfium_c.FPDFPage_HasTransparency(page)): return pdfium_c.FPDFBitmap_BGRA elif grayscale: return pdfium_c.FPDFBitmap_Gray @@ -456,6 +488,7 @@ def _auto_bitmap_format(fill_color, grayscale, prefer_bgrx): def _parse_renderopts( + page, fill_color = (255, 255, 255, 255), grayscale = False, optimize_mode = None, @@ -467,12 +500,13 @@ def _parse_renderopts( limit_image_cache = False, rev_byteorder = False, prefer_bgrx = False, + use_bgra_on_transparency = False, force_bitmap_format = None, extra_flags = 0, ): if force_bitmap_format is None: - cl_format = _auto_bitmap_format(fill_color, grayscale, prefer_bgrx) + cl_format = _auto_bitmap_format(page, fill_color, grayscale, prefer_bgrx, use_bgra_on_transparency) else: cl_format = force_bitmap_format @@ -522,6 +556,9 @@ def __init__(self, path_fill, path_stroke, text_fill, text_stroke): text_fill_color=text_fill, text_stroke_color=text_stroke, ) + def __repr__(self): + return f"{type(self).__name__}(**{self.colors})" + def convert(self, rev_byteorder): """ Returns: diff --git a/src/pypdfium2/_helpers/pageobjects.py b/src/pypdfium2/_helpers/pageobjects.py index 555ab447f..98b108850 100644 --- a/src/pypdfium2/_helpers/pageobjects.py +++ b/src/pypdfium2/_helpers/pageobjects.py @@ -1,10 +1,11 @@ -# SPDX-FileCopyrightText: 2024 geisserml +# SPDX-FileCopyrightText: 2025 geisserml # SPDX-License-Identifier: Apache-2.0 OR BSD-3-Clause __all__ = ("PdfObject", "PdfImage") import ctypes from ctypes import c_uint, c_float +import logging from pathlib import Path from collections import namedtuple import pypdfium2.raw as pdfium_c @@ -12,19 +13,20 @@ from pypdfium2._helpers.misc import PdfiumError from pypdfium2._helpers.matrix import PdfMatrix from pypdfium2._helpers.bitmap import PdfBitmap +from pypdfium2._deferred import PIL_Image -try: - import PIL.Image -except ImportError: - PIL = None +logger = logging.getLogger(__name__) class PdfObject (pdfium_i.AutoCloseable): """ - Page object helper class. + Pageobject helper class. - When constructing a :class:`.PdfObject`, an instance of a more specific subclass may be returned instead, - depending on the object's :attr:`.type` (e. g. :class:`.PdfImage`). + When constructing a :class:`.PdfObject`, an instance of a more specific subclass may be returned instead, depending on the object's :attr:`.type` (e. g. :class:`.PdfImage`). + + Note: + :meth:`.PdfObject.close` only takes effect on loose pageobjects. + It is a no-op otherwise, because pageobjects that are part of a page are owned by pdfium, not the caller. Attributes: raw (FPDF_PAGEOBJECT): @@ -32,7 +34,7 @@ class PdfObject (pdfium_i.AutoCloseable): type (int): The object's type (:data:`FPDF_PAGEOBJ_*`). page (PdfPage): - Reference to the page this pageobject belongs to. May be None if it does not belong to a page yet. + Reference to the page this pageobject belongs to. May be None if not part of a page (e.g. new or detached object). pdf (PdfDocument): Reference to the document this pageobject belongs to. May be None if the object does not belong to a document yet. This attribute is always set if :attr:`.page` is set. @@ -56,7 +58,10 @@ def __new__(cls, raw, *args, **kwargs): def __init__(self, raw, page=None, pdf=None, level=0): - self.raw, self.page, self.pdf, self.level = raw, page, pdf, level + self.raw = raw + self.page = page + self.pdf = pdf + self.level = level if page is not None: if self.pdf is None: @@ -73,15 +78,15 @@ def parent(self): # AutoCloseable hook return self.pdf if self.page is None else self.page - def get_pos(self): + def get_bounds(self): """ - Get the position of the object on the page. + Get the bounds of the object on the page. Returns: - A tuple of four :class:`float` coordinates for left, bottom, right, and top. + tuple[float * 4]: Left, bottom, right and top, in PDF page coordinates. """ if self.page is None: - raise RuntimeError("Must not call get_pos() on a loose pageobject.") + raise RuntimeError("Must not call get_bounds() on a loose pageobject.") l, b, r, t = c_float(), c_float(), c_float(), c_float() ok = pdfium_c.FPDFPageObj_GetBounds(self, l, b, r, t) @@ -91,6 +96,30 @@ def get_pos(self): return (l.value, b.value, r.value, t.value) + def get_quad_points(self): + """ + Get the object's quadriliteral points (i.e. the positions of its corners). + For transformed objects, this may provide tighter bounds than a rectangle (e.g. rotation by a non-multiple of 90°, shear). + + Note: + This function only supports image and text objects. + + Returns: + tuple[tuple[float*2] * 4]: Corner positions as (x, y) tuples, counter-clockwise from origin, i.e. bottom-left, bottom-right, top-right, top-left, in PDF page coordinates. + """ + + if self.type not in (pdfium_c.FPDF_PAGEOBJ_IMAGE, pdfium_c.FPDF_PAGEOBJ_TEXT): + # as of pdfium 5921 + raise RuntimeError("Quad points only supported for image and text objects.") + + q = pdfium_c.FS_QUADPOINTSF() + ok = pdfium_c.FPDFPageObj_GetRotatedBounds(self, q) + if not ok: + raise PdfiumError("Failed to get quad points.") + + return (q.x1, q.y1), (q.x2, q.y2), (q.x3, q.y3), (q.x4, q.y4) + + def get_matrix(self): """ Returns: @@ -116,20 +145,20 @@ def set_matrix(self, matrix): def transform(self, matrix): """ Parameters: - matrix (PdfMatrix): Multiply the page object's current transform matrix by this matrix. + matrix (PdfMatrix): Multiply the pageobject's current transform matrix by this matrix. """ - pdfium_c.FPDFPageObj_Transform(self, *matrix.get()) - + ok = pdfium_c.FPDFPageObj_TransformF(self, matrix) + if not ok: + raise PdfiumError("Failed to transform pageobject with matrix.") -# In principle, we would like to move PdfImage to a separate file, but it's not that easy because of the two-fold connection with PdfObject, which would run us into a circular import. (However, what we could do is externalize the class under a different name and turn PdfImage into a wrapper which merely inherits from that class.) class PdfImage (PdfObject): """ - Image object helper class (specific kind of page object). + Image object helper class (specific kind of pageobject). """ # cf. https://crbug.com/pdfium/1203 - #: Filters applied by :func:`FPDFImageObj_GetImageDataDecoded`. Hereafter referred to as "simple filters", while non-simple filters will be called "complex filters". + #: Filters applied by :func:`FPDFImageObj_GetImageDataDecoded`, referred to as "simple filters". Other filters are considered "complex filters". SIMPLE_FILTERS = ("ASCIIHexDecode", "ASCII85Decode", "RunLengthDecode", "FlateDecode", "LZWDecode") @@ -141,7 +170,7 @@ def new(cls, pdf): Returns: PdfImage: Handle to a new, empty image. Note that position and size of the image are defined by its matrix, which defaults to the identity matrix. - This means that new images will appear as a tiny square of 1x1 units on the bottom left corner of the page. + This means that new images will appear as a tiny square of 1x1 canvas units on the bottom left corner of the page. Use :class:`.PdfMatrix` and :meth:`.set_matrix` to adjust size and position. """ raw_img = pdfium_c.FPDFPageObj_NewImageObj(pdf) @@ -155,7 +184,7 @@ def get_metadata(self): Note: * The DPI values signify the resolution of the image on the PDF page, not the DPI metadata embedded in the image file. - * Due to issues in PDFium, this function can be slow. If you only need image size, prefer the faster :meth:`.get_size` instead. + * Due to issues in pdfium, this function might be slow on some kinds of images. If you only need size, prefer :meth:`.get_px_size` instead. Returns: FPDF_IMAGEOBJ_METADATA: Image metadata structure @@ -168,10 +197,8 @@ def get_metadata(self): return metadata - def get_size(self): + def get_px_size(self): """ - .. versionadded:: 4.8/5731 - Returns: (int, int): Image dimensions as a tuple of (width, height). """ @@ -189,7 +216,7 @@ def load_jpeg(self, source, pages=None, inline=False, autoclose=True): Parameters: source (str | pathlib.Path | typing.BinaryIO): - Input JPEG, given as file path or readable byte buffer. + Input JPEG, given as file path or readable byte stream. pages (list[PdfPage] | None): If replacing an image, pass in a list of loaded pages that might contain it, to update their cache. (The same image may be shown multiple times in different transforms across a PDF.) @@ -207,10 +234,11 @@ def load_jpeg(self, source, pages=None, inline=False, autoclose=True): elif pdfium_i.is_buffer(source, "r"): buffer = source else: - raise ValueError(f"Cannot load JPEG from {source} - not a file path or byte buffer.") + raise ValueError(f"Cannot load JPEG from {source} - not a file path or byte stream.") bufaccess, to_hold = pdfium_i.get_bufreader(buffer) - loader = pdfium_c.FPDFImageObj_LoadJpegFileInline if inline else pdfium_c.FPDFImageObj_LoadJpegFile + loader = pdfium_c.FPDFImageObj_LoadJpegFileInline if inline else \ + pdfium_c.FPDFImageObj_LoadJpegFile c_pages, page_count = pdfium_i.pages_c_array(pages) ok = loader(c_pages, page_count, self, bufaccess) @@ -245,40 +273,77 @@ def set_bitmap(self, bitmap, pages=None): raise PdfiumError("Failed to set image to bitmap.") - def get_bitmap(self, render=False): + def get_bitmap(self, render=False, scale_to_original=True): """ Get a bitmap rasterization of the image. Parameters: render (bool): Whether the image should be rendered, thereby applying possible transform matrices and alpha masks. + scale_to_original (bool): + If *render* is True, whether to temporarily scale the image to its native resolution, or close to that (defaults to True). This should improve output quality. Ignored if *render* is False. Returns: PdfBitmap: Image bitmap (with a buffer allocated by PDFium). """ if render: if self.pdf is None: - raise RuntimeError("Cannot get rendered bitmap of loose page object.") - raw_bitmap = pdfium_c.FPDFImageObj_GetRenderedBitmap(self.pdf, self.page, self) + raise RuntimeError("Cannot get rendered bitmap of loose pageobject.") + + if scale_to_original: + # Suggested by pdfium dev Lei Zhang in https://groups.google.com/g/pdfium/c/2czGFBcWHHQ/m/g0wzOJR-BAAJ + + px_w, px_h = self.get_px_size() + l, b, r, t = self.get_bounds() + content_w, content_h = r-l, t-b + + # align pixel and content width/height relation if swapped due to rotation (e.g. 90°, 270°) + swap = (px_w < px_h) != (content_w < content_h) + if swap: + px_w, px_h = px_h, px_w + + # if the image is squashed/stretched, prefer partial upscaling over partial downscaling (not using separate x/y scaling, so the image will look as in the PDF) + scale_factor = max(px_w/content_w, px_h/content_h) + orig_mat = self.get_matrix() + scaled_mat = orig_mat.scale(scale_factor, scale_factor) + self.set_matrix(scaled_mat) + # logger.debug( + # f"Pixel size: {px_w}, {px_h} (did swap? {swap})\n" + # f"Size in page coords: {content_w}, {content_h}\n" + # f"Scale: {scale_factor}\n" + # f"Current matrix: {orig_mat}\n" + # f"Scaled matrix: {scaled_mat}" + # ) + + try: + raw_bitmap = pdfium_c.FPDFImageObj_GetRenderedBitmap(self.pdf, self.page, self) + finally: + if scale_to_original: + self.set_matrix(orig_mat) else: raw_bitmap = pdfium_c.FPDFImageObj_GetBitmap(self) - if raw_bitmap is None: + if not raw_bitmap: raise PdfiumError(f"Failed to get bitmap of image {self}.") - return PdfBitmap.from_raw(raw_bitmap) + bitmap = PdfBitmap.from_raw(raw_bitmap) + if render and scale_to_original: + logger.debug(f"Extracted size: {bitmap.width}, {bitmap.height}") + + return bitmap def get_data(self, decode_simple=False): """ Parameters: decode_simple (bool): - If True, apply simple filters, resulting in semi-decoded data (see :attr:`.SIMPLE_FILTERS`). - Otherwise, the raw data will be returned. + If True, decode simple filters (see :attr:`.SIMPLE_FILTERS`), so only complex filters will remain, if any. If there are no complex filters, this provides the decoded pixel data. + If False, the raw stream data will be returned instead. Returns: ctypes.Array: The data of the image stream (as :class:`~ctypes.c_ubyte` array). """ - func = pdfium_c.FPDFImageObj_GetImageDataDecoded if decode_simple else pdfium_c.FPDFImageObj_GetImageDataRaw + func = pdfium_c.FPDFImageObj_GetImageDataDecoded if decode_simple else \ + pdfium_c.FPDFImageObj_GetImageDataRaw n_bytes = func(self, None, 0) buffer = (ctypes.c_ubyte * n_bytes)() func(self, buffer, n_bytes) @@ -302,31 +367,33 @@ def get_filters(self, skip_simple=False): buffer = ctypes.create_string_buffer(length) pdfium_c.FPDFImageObj_GetImageFilter(self, i, buffer, length) f = buffer.value.decode("utf-8") - if skip_simple and f in self.SIMPLE_FILTERS: - continue filters.append(f) + if skip_simple: + filters = [f for f in filters if f not in self.SIMPLE_FILTERS] + return filters def extract(self, dest, *args, **kwargs): - # TODO rewrite/simplify docstring """ - Extract the image into an independently usable file or byte buffer. - Where possible within PDFium's limited public API, it will be attempted to transfer the image data directly, - avoiding an unnecessary layer of decoding and re-encoding. - Otherwise, the fully decoded data will be retrieved and (re-)encoded using :mod:`PIL`. + Extract the image into an independently usable file or byte stream, attempting to avoid re-encoding or quality loss, as far as pdfium's limited API permits. + + This method can only extract DCTDecode (JPEG) and JPXDecode (JPEG 2000) images directly. + Otherwise, the pixel data is decoded and re-encoded using :mod:`PIL`, which is slower and loses the original encoding. + For images with simple filters only, ``get_data(decode_simple=True)`` is used to preserve higher bit depth or special color formats not supported by ``FPDF_BITMAP``. + For images with complex filters other than those extracted directly, we have to resort to :meth:`.get_bitmap`. + + Note, this method is not able to account for alpha masks, and potentially other data stored separately of the main image stream, which might lead to incorrect representation of the image. - As PDFium does not expose all required information, only DCTDecode (JPEG) and JPXDecode (JPEG 2000) images can be extracted directly. - For images with complex filters, the bitmap data is used. Otherwise, ``get_data(decode_simple=True)`` is used, which avoids lossy conversion for images whose bit depth or colour format is not supported by PDFium's bitmap implementation. + Tip: + The ``pikepdf`` library is capable of preserving the original encoding in many cases where this method is not. Parameters: - dest (str | io.BytesIO): - File prefix or byte buffer to which the image shall be written. + dest (str | pathlib.Path | io.BytesIO): + File path prefix or byte stream to which the image shall be written. fb_format (str): The image format to use in case it is necessary to (re-)encode the data. - fb_render (bool): - Whether the image should be rendered if falling back to bitmap-based extraction. """ # https://crbug.com/pdfium/1930 @@ -350,36 +417,33 @@ class ImageNotExtractableError (Exception): pass -def _get_pil_mode(colorspace, bpp): - # In theory, indexed (palettized) and ICC-based color spaces could be handled as well, but PDFium currently does not provide access to the palette or the ICC profile - if colorspace == pdfium_c.FPDF_COLORSPACE_DEVICEGRAY: - if bpp == 1: - return "1" - else: - return "L" - elif colorspace == pdfium_c.FPDF_COLORSPACE_DEVICERGB: +def _get_pil_mode(cs, bpp): + # As of Jan 2025, pdfium does not provide access to the palette, so we cannot handle indexed (palettized) color space. + # TODO handle ICC-based color spaces (pdfium now provides access to the ICC profile via FPDFImageObj_GetIccProfileDataDecoded(), see commit edd7c5cf) + if cs == pdfium_c.FPDF_COLORSPACE_DEVICEGRAY: + return "1" if bpp == 1 else "L" + elif cs == pdfium_c.FPDF_COLORSPACE_DEVICERGB: return "RGB" - elif colorspace == pdfium_c.FPDF_COLORSPACE_DEVICECMYK: + elif cs == pdfium_c.FPDF_COLORSPACE_DEVICECMYK: return "CMYK" else: return None -def _extract_smart(image_obj, fb_format=None, fb_render=False): - - # FIXME somewhat hard to read... +def _extract_smart(image_obj, fb_format=None): try: + # TODO can we change PdfImage.get_data() to take an mmap, so the data could be written directly into a file rather than an in-memory array? data, info = _extract_direct(image_obj) except ImageNotExtractableError: - # TODO? log reason why the image cannot be extracted directly - pil_image = image_obj.get_bitmap(render=fb_render).to_pil() + # TODO log reason why the image cannot be extracted directly? + pil_image = image_obj.get_bitmap(render=False).to_pil() else: pil_image = None format = info.format if format == "raw": metadata = info.metadata - pil_image = PIL.Image.frombuffer( + pil_image = PIL_Image.frombuffer( info.mode, (metadata.width, metadata.height), image_obj.get_data(decode_simple=True), @@ -387,7 +451,9 @@ def _extract_smart(image_obj, fb_format=None, fb_render=False): ) if pil_image: - format = fb_format if fb_format else "tiff" if pil_image.mode == "CMYK" else "png" + format = fb_format + if not format: + format = {"CMYK": "tiff"}.get(pil_image.mode, "png") buffer = yield format pil_image.save(buffer, format=format) if pil_image else buffer.write(data) diff --git a/src/pypdfium2/_helpers/textpage.py b/src/pypdfium2/_helpers/textpage.py index dc03fe345..c13a129ea 100644 --- a/src/pypdfium2/_helpers/textpage.py +++ b/src/pypdfium2/_helpers/textpage.py @@ -1,11 +1,10 @@ -# SPDX-FileCopyrightText: 2024 geisserml +# SPDX-FileCopyrightText: 2025 geisserml # SPDX-License-Identifier: Apache-2.0 OR BSD-3-Clause __all__ = ("PdfTextPage", "PdfTextSearcher") import ctypes import logging -import warnings import pypdfium2.raw as pdfium_c import pypdfium2.internal as pdfium_i from pypdfium2._helpers.misc import PdfiumError @@ -20,9 +19,15 @@ class PdfTextPage (pdfium_i.AutoCloseable): """ Text page helper class. + Hint: + (py)pdfium itself does not implement layout analysis, such as detecting words/lines/paragraphs. + However, there may be third-party extensions for this job, e.g.: https://github.com/VikParuchuri/pdftext + Attributes: - raw (FPDF_TEXTPAGE): The underlying PDFium textpage handle. - page (PdfPage): Reference to the page this textpage belongs to. + raw (FPDF_TEXTPAGE): + The underlying PDFium textpage handle. + page (PdfPage): + Reference to the page this textpage belongs to. """ def __init__(self, raw, page): @@ -35,6 +40,38 @@ def parent(self): # AutoCloseable hook return self.page + def get_text_bounded(self, left=None, bottom=None, right=None, top=None, errors="ignore"): + """ + Extract text from given boundaries, in PDF canvas units. + If a boundary value is None, it defaults to the corresponding value of :meth:`.PdfPage.get_bbox`. + + Parameters: + errors (str): Error treatment when decoding the data (see :meth:`bytes.decode`). + Returns: + str: The text on the page area in question, or an empty string if no text was found. + """ + + bbox = self.page.get_bbox() + if left is None: + left = bbox[0] + if bottom is None: + bottom = bbox[1] + if right is None: + right = bbox[2] + if top is None: + top = bbox[3] + + args = (self, left, top, right, bottom) + n_chars = pdfium_c.FPDFText_GetBoundedText(*args, None, 0) + if n_chars <= 0: + return "" + + buffer = ctypes.create_string_buffer(n_chars * 2) + buffer_ptr = ctypes.cast(buffer, ctypes.POINTER(ctypes.c_ushort)) + pdfium_c.FPDFText_GetBoundedText(*args, buffer_ptr, n_chars) + return buffer.raw.decode("utf-16-le", errors=errors) + + def _get_active_text_range(self, c_start, c_end, l_passive=0, r_passive=0): if c_start > c_end: @@ -51,14 +88,13 @@ def _get_active_text_range(self, c_start, c_end, l_passive=0, r_passive=0): return t_start, t_end, l_passive, r_passive - def get_text_range(self, index=0, count=-1, errors="ignore", force_this=False): + def get_text_range(self, index=0, count=-1, errors="ignore"): """ - Warning: - .. versionchanged:: 4.28 - For various reasons, calling this method with default params now implicitly translates to :meth:`.get_text_bounded` (pass ``force_this=True`` to circumvent). - Extract text from a given range. + Warning: + This method is limited to UCS-2, whereas :meth:`.get_text_bounded` provides full Unicode support. + Parameters: index (int): Index of the first char to include. count (int): Number of chars to cover, relative to the internal char list. Defaults to -1 for all remaining chars after *index*. @@ -74,12 +110,6 @@ def get_text_range(self, index=0, count=-1, errors="ignore", force_this=False): * In case of leading/trailing excluded characters, pypdfium2 modifies *index* and *count* accordingly to prevent pdfium from unexpectedly reading beyond ``range(index, index+count)``. """ - # https://github.com/pypdfium2-team/pypdfium2/issues/298 - # https://crbug.com/pdfium/2133 - if (index, count) == (0, -1) and not force_this: - warnings.warn("get_text_range() call with default params will be implicitly redirected to get_text_bounded()") - return self.get_text_bounded(errors=errors) - if count == -1: count = self.count_chars() - index @@ -95,10 +125,11 @@ def get_text_range(self, index=0, count=-1, errors="ignore", force_this=False): count -= l_passive + r_passive in_count = t_end+1 - t_start - # pdfium fea01fa9e2 (>6167) to d6a4b27d80 (<6415) requires assuming 4 bytes per character + # pdfium builds from fea01fa9e2 (>6167) to d6a4b27d80 (<6415) require assuming 4 bytes per character # https://github.com/pypdfium2-team/pypdfium2/issues/298 # https://crbug.com/pdfium/2133 - if 6167 < PDFIUM_INFO.build < 6415: + if 6167 < PDFIUM_INFO.build < 6415: # pragma: no cover + logger.warning(f"Due to API issues, pdfium builds between 6167 and 6415 (you have {PDFIUM_INFO.build}) require allocating twice the amount of memory in get_text_range().") in_count *= 2 in_count += 1 # null terminator @@ -110,38 +141,6 @@ def get_text_range(self, index=0, count=-1, errors="ignore", force_this=False): return buffer.raw[:(out_count-1)*2].decode("utf-16-le", errors=errors) - def get_text_bounded(self, left=None, bottom=None, right=None, top=None, errors="ignore"): - """ - Extract text from given boundaries in PDF coordinates. - If a boundary value is None, it defaults to the corresponding value of :meth:`.PdfPage.get_bbox`. - - Parameters: - errors (str): Error treatment when decoding the data (see :meth:`bytes.decode`). - Returns: - str: The text on the page area in question, or an empty string if no text was found. - """ - - bbox = self.page.get_bbox() - if left is None: - left = bbox[0] - if bottom is None: - bottom = bbox[1] - if right is None: - right = bbox[2] - if top is None: - top = bbox[3] - - args = (self, left, top, right, bottom) - n_chars = pdfium_c.FPDFText_GetBoundedText(*args, None, 0) - if n_chars <= 0: - return "" - - buffer = ctypes.create_string_buffer(n_chars * 2) - buffer_ptr = ctypes.cast(buffer, ctypes.POINTER(ctypes.c_ushort)) - pdfium_c.FPDFText_GetBoundedText(*args, buffer_ptr, n_chars) - return buffer.raw.decode("utf-16-le", errors=errors) - - def count_chars(self): """ Returns: @@ -178,11 +177,14 @@ def get_index(self, x, y, x_tol, y_tol): y_tol (float): Vertical tolerance. Returns: int | None: The index of the character at or nearby the point (x, y). - May be None if there is no character or an error occurred. + May be None if there is no character. If an internal error occurred, an exception will be raised. """ index = pdfium_c.FPDFText_GetCharIndexAtPos(self, x, y, x_tol, y_tol) - if index < 0: + if index == -1: return None + elif index == -3: + raise PdfiumError("An error occurred on attempt to get char index by pos.") + assert index >= 0, "Negative return is not permitted (unhandled error code?)" return index @@ -217,8 +219,9 @@ def get_charbox(self, index, loose=False): def get_rect(self, index): """ Get the bounding box of a text rectangle at the given index. - Note that :meth:`.count_rects` must be called once with default parameters - before subsequent :meth:`.get_rect` calls for this function to work (due to PDFium's API). + + Attention: + :meth:`.count_rects` must be called once with default params before subsequent :meth:`.get_rect` calls for this function to work. Returns: Float values for left, bottom, right and top in PDF canvas units. @@ -230,7 +233,7 @@ def get_rect(self, index): return (l.value, b.value, r.value, t.value) - def search(self, text, index=0, match_case=False, match_whole_word=False, consecutive=False): + def search(self, text, index=0, match_case=False, match_whole_word=False, consecutive=False, flags=0): """ Locate text on the page. @@ -246,6 +249,8 @@ def search(self, text, index=0, match_case=False, match_whole_word=False, consec consecutive (bool): If False (the default), :meth:`.search` will skip past the current match to look for the next match. If True, parts of the previous match may be caught again (e. g. searching for `aa` in `aaaa` would match 3 rather than 2 times). + flags (int): + Passthrough of raw pdfium searching flags. Note that you may want to use the boolean options instead. Returns: PdfTextSearcher: A helper object to search text. """ @@ -253,7 +258,6 @@ def search(self, text, index=0, match_case=False, match_whole_word=False, consec if len(text) == 0: raise ValueError("Text length must be greater than 0.") - flags = 0 if match_case: flags |= pdfium_c.FPDF_MATCHCASE if match_whole_word: @@ -299,15 +303,13 @@ def _get_occurrence(self, find_func): def get_next(self): """ Returns: - (int, int): Start character index and count of the next occurrence, - or None if the last occurrence was passed. + (int, int) | None: Start character index and count of the next occurrence, or None if the last occurrence was passed. """ return self._get_occurrence(pdfium_c.FPDFText_FindNext) def get_prev(self): """ Returns: - (int, int): Start character index and count of the previous occurrence (i. e. the one before the last valid occurrence), - or None if the last occurrence was passed. + (int, int) | None: Start character index and count of the previous occurrence (i. e. the one before the last valid occurrence), or None if the last occurrence was passed. """ return self._get_occurrence(pdfium_c.FPDFText_FindPrev) diff --git a/src/pypdfium2/_helpers/unsupported.py b/src/pypdfium2/_helpers/unsupported.py index 52a98b050..8f67ffcfc 100644 --- a/src/pypdfium2/_helpers/unsupported.py +++ b/src/pypdfium2/_helpers/unsupported.py @@ -1,4 +1,4 @@ -# SPDX-FileCopyrightText: 2024 geisserml +# SPDX-FileCopyrightText: 2025 geisserml # SPDX-License-Identifier: Apache-2.0 OR BSD-3-Clause __all__ = ("PdfUnspHandler", ) diff --git a/src/pypdfium2/_library_scope.py b/src/pypdfium2/_library_scope.py index d66daf21e..d1c95613c 100644 --- a/src/pypdfium2/_library_scope.py +++ b/src/pypdfium2/_library_scope.py @@ -1,16 +1,17 @@ -# SPDX-FileCopyrightText: 2024 geisserml +# SPDX-FileCopyrightText: 2025 geisserml # SPDX-License-Identifier: Apache-2.0 OR BSD-3-Clause +import sys import atexit -import os, sys import pypdfium2.raw as pdfium_c import pypdfium2.internal as pdfium_i def init_lib(): assert not pdfium_i.LIBRARY_AVAILABLE - if pdfium_i.DEBUG_AUTOCLOSE: - print("Initialize PDFium (auto)", file=sys.stderr) + if pdfium_i.DEBUG_AUTOCLOSE: # pragma: no cover + # FIXME not shown on auto-init, because DEBUG_AUTOCLOSE can only be set on the caller side after pypdfium2 has been imported... + print("Initialize PDFium", file=sys.stderr) # PDFium init API may change in the future: https://crbug.com/pdfium/1446 # NOTE Technically, FPDF_InitLibrary() would be sufficient for our purposes, but since it's formally marked for deprecation, don't use it to be on the safe side. Also, avoid experimental config versions that might not be promoted to stable. @@ -27,11 +28,10 @@ def init_lib(): pdfium_i.LIBRARY_AVAILABLE.value = True -def destroy_lib(): +def destroy_lib(): # pragma: no cover assert pdfium_i.LIBRARY_AVAILABLE if pdfium_i.DEBUG_AUTOCLOSE: - # use os.write() rather than print() to avoid "reentrant call" exceptions on shutdown (see https://stackoverflow.com/q/75367828/15547292) - os.write(sys.stderr.fileno(), b"Destroy PDFium (auto)\n") + pdfium_i._safe_debug("Destroy PDFium") pdfium_c.FPDF_DestroyLibrary() pdfium_i.LIBRARY_AVAILABLE.value = False diff --git a/src/pypdfium2/internal/__init__.py b/src/pypdfium2/internal/__init__.py index 6c7cf0123..f8e48670c 100644 --- a/src/pypdfium2/internal/__init__.py +++ b/src/pypdfium2/internal/__init__.py @@ -1,4 +1,4 @@ -# SPDX-FileCopyrightText: 2024 geisserml +# SPDX-FileCopyrightText: 2025 geisserml # SPDX-License-Identifier: Apache-2.0 OR BSD-3-Clause from pypdfium2.internal.bases import * diff --git a/src/pypdfium2/internal/bases.py b/src/pypdfium2/internal/bases.py index 010e11112..8de74359e 100644 --- a/src/pypdfium2/internal/bases.py +++ b/src/pypdfium2/internal/bases.py @@ -1,46 +1,70 @@ -# SPDX-FileCopyrightText: 2024 geisserml +# SPDX-FileCopyrightText: 2025 geisserml # SPDX-License-Identifier: Apache-2.0 OR BSD-3-Clause -__all__ = ("AutoCastable", "AutoCloseable", "DEBUG_AUTOCLOSE", "LIBRARY_AVAILABLE") +__all__ = ("AutoCastable", "AutoCloseable", "DEBUG_AUTOCLOSE", "LIBRARY_AVAILABLE", "_safe_debug") import os import sys -import ctypes +import enum +import uuid import weakref import logging -import uuid logger = logging.getLogger(__name__) -# mutable bools -DEBUG_AUTOCLOSE = ctypes.c_bool(False) -LIBRARY_AVAILABLE = ctypes.c_bool(False) # set to true on library init -STATE_INVALID = -1 -STATE_AUTO = 0 -STATE_EXPLICIT = 1 -STATE_BYPARENT = 2 +def _safe_debug(msg): # pragma: no cover + # try to use os.write() rather than print() to avoid "reentrant call" exceptions on shutdown (see https://stackoverflow.com/q/75367828/15547292) + try: + os.write(sys.stderr.fileno(), (msg+"\n").encode()) + except Exception: # e.g. io.UnsupportedOperation + print(msg, file=sys.stderr) + + +class _Mutable: + + def __init__(self, value): + self.value = value + + def __repr__(self): + return f"_Mutable({self.value})" + + def __bool__(self): + return bool(self.value) + + +DEBUG_AUTOCLOSE = _Mutable(False) +LIBRARY_AVAILABLE = _Mutable(False) # set to true on library init + + +class _STATE (enum.Enum): + INVALID = -1 + AUTO = 0 + EXPLICIT = 1 + BYPARENT = 2 class AutoCastable: @property def _as_parameter_(self): + # trust in the caller not to invoke APIs on an object after .close() + # if not self.raw: + # raise RuntimeError("bool(obj.raw) must evaluate to True for use as C function parameter") return self.raw def _close_template(close_func, raw, obj_repr, state, parent, *args, **kwargs): - if DEBUG_AUTOCLOSE: - desc = {STATE_AUTO: "auto", STATE_EXPLICIT: "explicit", STATE_BYPARENT: "by parent"}[state.value] - # use os.write() rather than print() to avoid "reentrant call" exceptions on shutdown (see https://stackoverflow.com/q/75367828/15547292) - os.write(sys.stderr.fileno(), f"Close ({desc}) {obj_repr}\n".encode()) + if DEBUG_AUTOCLOSE: # pragma: no cover + _safe_debug(f"Close ({state.value.name.lower()}) {obj_repr}") - if not LIBRARY_AVAILABLE: - os.write(sys.stderr.fileno(), f"-> Cannot close object, library is destroyed. This may cause a memory leak!\n".encode()) + if not LIBRARY_AVAILABLE: # pragma: no cover + _safe_debug(f"-> Cannot close object; pdfium library is destroyed. This may cause a memory leak!") return - assert (parent is None) or not parent._tree_closed() + assert state.value != _STATE.INVALID + assert parent is None or not parent._tree_closed() close_func(raw, *args, **kwargs) @@ -48,15 +72,15 @@ class AutoCloseable (AutoCastable): def __init__(self, close_func, *args, obj=None, needs_free=True, **kwargs): - # NOTE proactively prevent accidental double initialization + # proactively prevent accidental double initialization assert not hasattr(self, "_finalizer") self._close_func = close_func self._obj = self if obj is None else obj - self._uuid = uuid.uuid4() self._ex_args = args self._ex_kwargs = kwargs - self._autoclose_state = ctypes.c_int8(STATE_AUTO) # mutable int + self._autoclose_state = _Mutable(_STATE.AUTO) + self._uuid = uuid.uuid4() if DEBUG_AUTOCLOSE else None self._finalizer = None self._kids = [] @@ -65,11 +89,12 @@ def __init__(self, close_func, *args, obj=None, needs_free=True, **kwargs): def __repr__(self): - return f"<{type(self).__name__} uuid:{str(self._uuid)[:8]}>" + identifier = hex(id(self)) if self._uuid is None else self._uuid.hex[:14] + return f"<{type(self).__name__} {identifier}>" def _attach_finalizer(self): - # NOTE this function captures the value of the `parent` property at finalizer installation time - if it changes, detach the old finalizer and create a new one + # NOTE this function captures the value of the `parent` property at finalizer installation time assert self._finalizer is None self._finalizer = weakref.finalize(self._obj, _close_template, self._close_func, self.raw, repr(self), self._autoclose_state, self.parent, *self._ex_args, **self._ex_kwargs) @@ -80,7 +105,7 @@ def _detach_finalizer(self): def _tree_closed(self): if self.raw is None: return True - if (self.parent is not None) and self.parent._tree_closed(): + if self.parent != None and self.parent._tree_closed(): return True return False @@ -90,7 +115,12 @@ def _add_kid(self, k): def close(self, _by_parent=False): - if not self.raw or not self._finalizer: + # TODO remove object from parent's kids cache on finalization to avoid unnecessary accumulation + + if not self.raw: + return False + if not self._finalizer: + self.raw = None return False for k_ref in self._kids: @@ -98,9 +128,9 @@ def close(self, _by_parent=False): if k and k.raw: k.close(_by_parent=True) - self._autoclose_state.value = STATE_BYPARENT if _by_parent else STATE_EXPLICIT + self._autoclose_state.value = _STATE.BYPARENT if _by_parent else _STATE.EXPLICIT self._finalizer() - self._autoclose_state.value = STATE_INVALID + self._autoclose_state.value = _STATE.INVALID self.raw = None self._finalizer = None self._kids.clear() diff --git a/src/pypdfium2/internal/consts.py b/src/pypdfium2/internal/consts.py index 28c050c36..c18244fe4 100644 --- a/src/pypdfium2/internal/consts.py +++ b/src/pypdfium2/internal/consts.py @@ -1,4 +1,4 @@ -# SPDX-FileCopyrightText: 2024 geisserml +# SPDX-FileCopyrightText: 2025 geisserml # SPDX-License-Identifier: Apache-2.0 OR BSD-3-Clause import pypdfium2.raw as pdfium_c @@ -126,9 +126,8 @@ def get(self, key, default_prefix="Unhandled constant"): }) -# known implication: causes eager evaluation of pdfium version -if "XFA" in PDFIUM_INFO.flags: - #: [V8/XFA builds only] Convert a PDFium XFA error constant (:attr:`FPDF_ERR_XFA*`) to string. +if "XFA" in PDFIUM_INFO.flags: # pragma: no cover + #: [XFA builds only] Convert a PDFium XFA error constant (:attr:`FPDF_ERR_XFA*`) to string. XFAErrorToStr = _fallback_dict({ pdfium_c.FPDF_ERR_XFALOAD: "Load error", pdfium_c.FPDF_ERR_XFALAYOUT: "Layout error", diff --git a/src/pypdfium2/internal/utils.py b/src/pypdfium2/internal/utils.py index ef952e137..c3105da31 100644 --- a/src/pypdfium2/internal/utils.py +++ b/src/pypdfium2/internal/utils.py @@ -1,4 +1,4 @@ -# SPDX-FileCopyrightText: 2024 geisserml +# SPDX-FileCopyrightText: 2025 geisserml # SPDX-License-Identifier: Apache-2.0 OR BSD-3-Clause import ctypes @@ -44,10 +44,10 @@ class _buffer_reader: def __init__(self, buffer): self.buffer = buffer - def __call__(self, _, position, p_buf, size): - c_buf = ctypes.cast(p_buf, ctypes.POINTER(ctypes.c_char * size)) + def __call__(self, _, position, p_buf_first, size): + p_buf = ctypes.cast(p_buf_first, ctypes.POINTER(ctypes.c_char * size)) self.buffer.seek(position) - self.buffer.readinto(c_buf.contents) + self.buffer.readinto(p_buf.contents) return 1 @@ -56,9 +56,9 @@ class _buffer_writer: def __init__(self, buffer): self.buffer = buffer - def __call__(self, _, data, size): - block = ctypes.cast(data, ctypes.POINTER(ctypes.c_ubyte * size)) - self.buffer.write(block.contents) + def __call__(self, _, p_data_first, size): + p_data = ctypes.cast(p_data_first, ctypes.POINTER(ctypes.c_ubyte * size)) + self.buffer.write(p_data.contents) return 1 diff --git a/src/pypdfium2/raw.py b/src/pypdfium2/raw.py index aa423aca1..9197f8780 100644 --- a/src/pypdfium2/raw.py +++ b/src/pypdfium2/raw.py @@ -1,5 +1,5 @@ -# SPDX-FileCopyrightText: 2024 geisserml +# SPDX-FileCopyrightText: 2025 geisserml # SPDX-License-Identifier: Apache-2.0 OR BSD-3-Clause # platform files are managed in an own pypdfium2_raw module, cleanly separating raw bindings and helpers -from pypdfium2_raw.bindings import * +from pypdfium2_raw.bindings import * # noqa diff --git a/src/pypdfium2/version.py b/src/pypdfium2/version.py index 7dd604a25..e5a7acbdd 100644 --- a/src/pypdfium2/version.py +++ b/src/pypdfium2/version.py @@ -1,148 +1,82 @@ -# SPDX-FileCopyrightText: 2024 geisserml +# SPDX-FileCopyrightText: 2025 geisserml # SPDX-License-Identifier: Apache-2.0 OR BSD-3-Clause -__all__ = [] +# TODO(future) add bindings info (e.g. ctypesgen version, reference/generated, runtime libdirs) + +__all__ = ("PYPDFIUM_INFO", "PDFIUM_INFO") -import sys import json -import functools from pathlib import Path -from types import MappingProxyType import pypdfium2_raw -# TODO move to shared compat file -if sys.version_info < (3, 8): - def cached_property(func): - return property( functools.lru_cache(maxsize=1)(func) ) -else: - cached_property = functools.cached_property - - -class _abc_version: +class _version_interface: - @cached_property - def _data(self): + def __init__(self): with open(self._FILE, "r") as buf: data = json.load(buf) - self._process_data(data) - return MappingProxyType(data) - - def _process_data(self, data): - pass - - def __getattr__(self, attr): - return self._data[attr] - - def __setattr__(self, name, value): - raise AttributeError(f"Version class is immutable - assignment '{name} = {value}' not allowed") + for k, v in data.items(): + setattr(self, k, v) + self.api_tag = tuple(data[k] for k in self._TAG_FIELDS) + self._hook() + self.version = self.tag + self.desc def __repr__(self): return self.version - @cached_property - def api_tag(self): - return tuple(self._data[k] for k in self._TAG_FIELDS) - def _craft_tag(self): return ".".join(str(v) for v in self.api_tag) - def _craft_desc(self, extra=[]): + def _craft_desc(self, *suffixes): local_ver = [] if self.n_commits > 0: local_ver += [str(self.n_commits), str(self.hash)] - local_ver += extra + local_ver += suffixes desc = "" if local_ver: desc += "+" + ".".join(local_ver) return desc - - @cached_property - def version(self): - return self.tag + self.desc -class _version_pypdfium2 (_abc_version): +class _version_pypdfium2 (_version_interface): _FILE = Path(__file__).parent / "version.json" _TAG_FIELDS = ("major", "minor", "patch") - @cached_property - def tag(self): - tag = self._craft_tag() - if self.beta is not None: - tag += f"b{self.beta}" - return tag - - @cached_property - def desc(self): + def _hook(self): - extra = [] - if self.dirty: - extra += ["dirty"] + self.tag = self._craft_tag() + if self.beta is not None: + self.tag += f"b{self.beta}" - desc = self._craft_desc(extra) + suffixes = ["dirty"] if self.dirty else [] + self.desc = self._craft_desc(*suffixes) if self.data_source != "git": - desc += f":{self.data_source}" + self.desc += f":{self.data_source}" if self.is_editable: - desc += "@editable" - - return desc + self.desc += "@editable" -class _version_pdfium (_abc_version): +class _version_pdfium (_version_interface): _FILE = Path(pypdfium2_raw.__file__).parent / "version.json" _TAG_FIELDS = ("major", "minor", "build", "patch") - def _process_data(self, data): - data["flags"] = tuple(data["flags"]) - - @cached_property - def tag(self): - return self._craft_tag() - - @cached_property - def desc(self): - desc = self._craft_desc() + def _hook(self): + + self.flags = tuple(self.flags) + self.tag = self._craft_tag() + + self.desc = self._craft_desc() if self.flags: - desc += ":{%s}" % ",".join(self.flags) + self.desc += f":{','.join(self.flags)}" if self.origin != "pdfium-binaries": - desc += f"@{self.origin}" - return desc - -# TODO(future) add bindings info (e.g. ctypesgen version, reference/generated, runtime libdirs) - + self.desc += f"@{self.origin}" -# Current API PYPDFIUM_INFO = _version_pypdfium2() -PDFIUM_INFO = _version_pdfium() - -__all__ += ["PYPDFIUM_INFO", "PDFIUM_INFO"] - -# ----- - - -# Deprecated API, to be removed with v5 -# Known issue: causes eager evaluation of the new API's theoretically deferred properties. - -V_PYPDFIUM2 = PYPDFIUM_INFO.version -V_LIBPDFIUM = str(PDFIUM_INFO.build) -V_BUILDNAME = PDFIUM_INFO.origin -V_PDFIUM_IS_V8 = "V8" in PDFIUM_INFO.flags # implies XFA -V_LIBPDFIUM_FULL = PDFIUM_INFO.version - -__all__ += ["V_PYPDFIUM2", "V_LIBPDFIUM", "V_LIBPDFIUM_FULL", "V_BUILDNAME", "V_PDFIUM_IS_V8"] - -# ----- - - -# Docs - -PYPDFIUM_INFO = PYPDFIUM_INFO """ pypdfium2 helpers version. @@ -178,11 +112,11 @@ def desc(self): - ``record``: Parsed from autorelease record. Implies that possible changes after tag are unknown. is_editable (bool | None): True for editable install, False otherwise. None if unknown.\n - If True, the version info is the one captured at install time. An arbitrary number of forward or reverse changes may have happened since. The actual current state is unknown. + If True, the version info is the one captured at install time. An arbitrary number of forward or reverse changes may have happened since. """ -PDFIUM_INFO = PDFIUM_INFO +PDFIUM_INFO = _version_pdfium() """ PDFium version. @@ -192,18 +126,18 @@ def desc(self): version (str): Joined tag and desc, forming the full version. tag (str): - Version ciphers joined as str. + Version ciphers joined as string. desc (str): - Descriptors (origin, flags) represented as str. + Descriptors (origin, flags) as string. api_tag (tuple[int]): - Version ciphers joined as tuple. + Version ciphers grouped as tuple. major (int): Chromium major cipher. minor (int): Chromium minor cipher. build (int): Chromium/pdfium build cipher. - This value allows to uniquely identify the pdfium sources the binary was built from. + This value uniquely identifies the pdfium version. patch (int): Chromium patch cipher. n_commits (int): @@ -211,12 +145,12 @@ def desc(self): hash (str | None): Hash of head commit if n_commits > 0, None otherwise. origin (str): - The pdfium binary's origin. Possible values:\n - - ``pdfium-binaries``: Compiled by bblanchon/pdfium-binaries, and bundled into pypdfium2. - - ``sourcebuild``: Provided by the caller (commonly compiled using pypdfium2's integrated build script), and bundled into pypdfium2. - - ``system``: Loaded from a standard system location using :func:`ctypes.util.find_library()`, or an explicit directory provided at setup time. + The pdfium binary's origin. flags (tuple[str]): Tuple of pdfium feature flags. Empty for default build. (V8, XFA) for pdfium-binaries V8 build. """ -# ----- +# Freeze the base class after we have constructed the instance objects +def _frozen_setattr(self, name, value): + raise AttributeError(f"Version class is read-only - assignment '{name} = {value}' not allowed") +_version_interface.__setattr__ = _frozen_setattr diff --git a/tests/__init__.py b/tests/__init__.py index 88e9891f3..79b789075 100644 --- a/tests/__init__.py +++ b/tests/__init__.py @@ -1,4 +1,4 @@ -# SPDX-FileCopyrightText: 2024 geisserml +# SPDX-FileCopyrightText: 2025 geisserml # SPDX-License-Identifier: Apache-2.0 OR BSD-3-Clause # This file is necessary for relative imports diff --git a/tests/conftest.py b/tests/conftest.py index 5a311a54f..4e4e3ed38 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -1,11 +1,14 @@ -# SPDX-FileCopyrightText: 2024 geisserml +# SPDX-FileCopyrightText: 2025 geisserml # SPDX-License-Identifier: Apache-2.0 OR BSD-3-Clause import sys +import pytest from pathlib import Path from argparse import Namespace import pypdfium2.__main__ as pdfium_cli +PyVersion = (sys.version_info.major, sys.version_info.minor) + pdfium_cli.setup_logging() @@ -27,6 +30,46 @@ def _gather_resources(dir, skip_exts=[".in"]): setattr(test_files, path.stem, (dir / path.name)) return test_files - -TestResources = _gather_resources(ResourceDir) +TestFiles = _gather_resources(ResourceDir) TestExpectations = _gather_resources(ExpectationsDir) + + +def get_members(cls): + members = [] + for attr in dir(cls): + if attr.startswith("_"): + continue + members.append( getattr(cls, attr) ) + return members + + +def compare_n2(data, exp_data, approx_abs=1): + assert len(data) == len(exp_data) + for d, exp_d in zip(data, exp_data): + assert pytest.approx(d, abs=approx_abs) == exp_d + + +ExpRenderPixels = ( + ( (0, 0 ), (255, 255, 255) ), + ( (150, 180), (129, 212, 26 ) ), + ( (150, 390), (42, 96, 153) ), + ( (150, 570), (128, 0, 128) ), +) + + +# def iterate_testfiles(skip_encrypted=True): +# encrypted = (TestFiles.encrypted, ) +# for attr_name in dir(TestFiles): +# if attr_name.startswith("_"): +# continue +# member = getattr(TestFiles, attr_name) +# if skip_encrypted and member in encrypted: +# continue +# yield member +# +# +# def test_testpaths(): +# for dirpath in (TestDir, ProjectDir, ResourceDir, OutputDir): +# assert dirpath.is_dir() +# for filepath in iterate_testfiles(False): +# assert filepath.is_file() diff --git a/tests/expectations/attachments_list.txt b/tests/expectations/attachments_list.txt index 539fe1d54..92641d8d9 100644 --- a/tests/expectations/attachments_list.txt +++ b/tests/expectations/attachments_list.txt @@ -1,2 +1,3 @@ +Unsupported PDF feature: Attachment (incomplete support) [1] 1.txt [2] attached.pdf diff --git a/tests/expectations/pageobjects_images.txt b/tests/expectations/pageobjects_images.txt index 7888c0542..e67029267 100644 --- a/tests/expectations/pageobjects_images.txt +++ b/tests/expectations/pageobjects_images.txt @@ -1,33 +1,37 @@ # Page 1 text - Position: (58.692, 759.975, 127.024, 779.335) + Bounding Box: (58.692, 759.975, 127.024, 779.335) + Quad Points: [(58.692, 759.975), (127.024, 759.975), (127.024, 779.335), (58.692, 779.335)] image - Position: (132.7, 459.189, 349.5, 549.689) + Bounding Box: (132.7, 459.189, 349.5, 549.689) + Quad Points: [(132.7, 459.189), (349.5, 459.189), (349.5, 549.689), (132.7, 549.689)] Filters: ['CCITTFaxDecode'] width: 115 height: 48 - horizontal_dpi: 38.19187927246094 - vertical_dpi: 38.18785858154297 + horizontal_dpi: 38.1919 + vertical_dpi: 38.1879 bits_per_pixel: 1 colorspace: DeviceGray marked_content_id: 1 image - Position: (47.65, 652.239, 162.6, 700.239) + Bounding Box: (47.65, 652.239, 162.6, 700.239) + Quad Points: [(47.65, 652.239), (162.6, 652.239), (162.6, 700.239), (47.65, 700.239)] Filters: ['CCITTFaxDecode'] width: 115 height: 48 - horizontal_dpi: 72.03131103515625 + horizontal_dpi: 72.0313 vertical_dpi: 72.0 bits_per_pixel: 1 colorspace: DeviceGray marked_content_id: 2 image - Position: (203.55, 204.089, 577.2, 360.039) + Bounding Box: (203.55, 204.089, 577.2, 360.039) + Quad Points: [(203.55, 204.089), (577.2, 204.089), (577.2, 360.039), (203.55, 360.039)] Filters: ['CCITTFaxDecode'] width: 115 height: 48 - horizontal_dpi: 22.159772872924805 - vertical_dpi: 22.16094970703125 + horizontal_dpi: 22.1598 + vertical_dpi: 22.1609 bits_per_pixel: 1 colorspace: DeviceGray marked_content_id: 3 diff --git a/tests/expectations/pdfinfo_attachments.txt b/tests/expectations/pdfinfo_attachments.txt index 898f35b9c..1bfbeb373 100644 --- a/tests/expectations/pdfinfo_attachments.txt +++ b/tests/expectations/pdfinfo_attachments.txt @@ -1,3 +1,4 @@ +Unsupported PDF feature: Attachment (incomplete support) Page Count: 1 PDF Version: 1.6 ID (permanent): b'\xd8\x89\xebk\x9a\xdf\x88\xe5\xed\xa7\xdc\x08\xfe\x85\x97' diff --git a/tests/expectations/toc.txt b/tests/expectations/toc.txt index b635d42a4..bd6aa6b50 100644 --- a/tests/expectations/toc.txt +++ b/tests/expectations/toc.txt @@ -1,9 +1,9 @@ -[-] One -> 1 # XYZ [89.29, 757.7, 0.0] +[-2] One -> 1 # XYZ [89.29, 757.7, 0.0] [*] One-A -> 1 # XYZ [89.29, 706.86, 0.0] - [-] One-B -> 1 # XYZ [89.29, 657.03, 0.0] + [-2] One-B -> 1 # XYZ [89.29, 657.03, 0.0] [*] One-B-I -> 1 # XYZ [89.29, 607.2, 0.0] [*] One-B-II -> 1 # XYZ [89.29, 557.76, 0.0] [*] Two -> 1 # XYZ [89.29, 507.16, 0.0] -[-] Three -> 2 # XYZ [89.29, 757.7, 0.0] +[-2] Three -> 2 # XYZ [89.29, 757.7, 0.0] [*] Three-A -> 2 # XYZ [89.29, 706.98, 0.0] [*] Three-B -> 2 # XYZ [89.29, 657.15, 0.0] diff --git a/tests/expectations/toc_circular.txt b/tests/expectations/toc_circular.txt index 15142248c..984920d7f 100644 --- a/tests/expectations/toc_circular.txt +++ b/tests/expectations/toc_circular.txt @@ -1,2 +1,3 @@ -[*] A Good Beginning -> ? # ? [] -[*] A Good Ending -> ? # ? [] +[*] A Good Beginning -> _ +[*] A Good Ending -> _ +A circular bookmark reference was detected while traversing the table of contents. diff --git a/tests/expectations/toc_maxdepth.txt b/tests/expectations/toc_maxdepth.txt index beeacb932..889fe0075 100644 --- a/tests/expectations/toc_maxdepth.txt +++ b/tests/expectations/toc_maxdepth.txt @@ -1,20 +1,21 @@ -[+] 1.outline -> 1 # FitH [746.439] - [+] 1.1.outline -> 1 # FitH [700.878] - [+] 1.1.1.outline -> 1 # FitH [632.537] - [+] 1.1.1.1.outline -> 1 # FitH [632.946] - [+] 1.1.1.1.1.outline -> 1 # FitH [597.304] - [+] 1.1.1.1.1.1outline -> 1 # FitH [632.946] - [+] 1.1.1.1.1.1.outline -> 1 # FitH [632.946] - [+] 1.1.1.1.1.1.1.outline -> 1 # FitH [632.946] - [+] 1.1.1.1.1.1.1.1.outline -> 1 # FitH [632.946] - [+] 1.1.1.1.1.1.1.1.1.outline -> 1 # FitH [632.946] - [+] 1.1.1.1.1.1.1.1.1.1.outline -> 1 # FitH [632.946] - [+] 1.1.1.1.1.1.1.1.1.1.1.outline -> 1 # FitH [632.946] - [+] 1.1.1.1.1.1.1.1.1.1.1.1.outline -> 1 # FitH [632.946] - [+] 1.1.1.1.1.1.1.1.1.1.1.1.1.outline -> 1 # FitH [632.946] - [+] 1.1.1.1.1.1.1.1.1.1.1.1.1.1.outline -> 1 # FitH [632.946] -[+] 2.outline -> 2 # FitH [749.477] - [+] 2.1.outline -> 2 # FitH [699.36] - [+] 2.1.1.outline -> 2 # FitH [628.74] +[+100] 1.outline -> 1 # FitH [746.439] + [+100] 1.1.outline -> 1 # FitH [700.878] + [+1] 1.1.1.outline -> 1 # FitH [632.537] + [+1] 1.1.1.1.outline -> 1 # FitH [632.946] + [+1] 1.1.1.1.1.outline -> 1 # FitH [597.304] + [+1] 1.1.1.1.1.1outline -> 1 # FitH [632.946] + [+1] 1.1.1.1.1.1.outline -> 1 # FitH [632.946] + [+1] 1.1.1.1.1.1.1.outline -> 1 # FitH [632.946] + [+1] 1.1.1.1.1.1.1.1.outline -> 1 # FitH [632.946] + [+1] 1.1.1.1.1.1.1.1.1.outline -> 1 # FitH [632.946] + [+1] 1.1.1.1.1.1.1.1.1.1.outline -> 1 # FitH [632.946] + [+1] 1.1.1.1.1.1.1.1.1.1.1.outline -> 1 # FitH [632.946] + [+1] 1.1.1.1.1.1.1.1.1.1.1.1.outline -> 1 # FitH [632.946] + [+1] 1.1.1.1.1.1.1.1.1.1.1.1.1.outline -> 1 # FitH [632.946] + [+1] 1.1.1.1.1.1.1.1.1.1.1.1.1.1.outline -> 1 # FitH [632.946] +Maximum recursion depth 15 reached (subtree skipped). +[+100] 2.outline -> 2 # FitH [749.477] + [+100] 2.1.outline -> 2 # FitH [699.36] + [+100] 2.1.1.outline -> 2 # FitH [628.74] [*] 2.1.1.1.outline -> 2 # FitH [583.179] [*] 2.2 outline -> 2 # FitH [515.218] diff --git a/tests/test_attachments.py b/tests/test_attachments.py index 2af663960..aa67a0fd7 100644 --- a/tests/test_attachments.py +++ b/tests/test_attachments.py @@ -1,4 +1,4 @@ -# SPDX-FileCopyrightText: 2024 geisserml +# SPDX-FileCopyrightText: 2025 geisserml # SPDX-License-Identifier: Apache-2.0 OR BSD-3-Clause import re @@ -7,12 +7,12 @@ import hashlib import pypdfium2 as pdfium import pypdfium2.raw as pdfium_c -from .conftest import TestResources, OutputDir +from .conftest import TestFiles, OutputDir def test_attachment(): - pdf = pdfium.PdfDocument(TestResources.attachments) + pdf = pdfium.PdfDocument(TestFiles.attachments) assert pdf.count_attachments() == 2 attachment_a = pdf.get_attachment(0) @@ -69,7 +69,7 @@ def test_attachment(): with pytest.raises(pdfium.PdfiumError, match=re.escape("Failed to extract attachment (buffer length 0).")): attachment_c.get_data() - data_c = TestResources.mona_lisa.read_bytes() + data_c = TestFiles.mona_lisa.read_bytes() attachment_c.set_data(data_c) assert attachment_c.get_data().raw == data_c diff --git a/tests/test_bitmap.py b/tests/test_bitmap.py deleted file mode 100644 index b4fb1b2f0..000000000 --- a/tests/test_bitmap.py +++ /dev/null @@ -1,2 +0,0 @@ -# SPDX-FileCopyrightText: 2024 geisserml -# SPDX-License-Identifier: Apache-2.0 OR BSD-3-Clause diff --git a/tests/test_cli.py b/tests/test_cli.py index 2b64dfd93..aa4ffcb61 100644 --- a/tests/test_cli.py +++ b/tests/test_cli.py @@ -1,7 +1,9 @@ -# SPDX-FileCopyrightText: 2024 geisserml +# SPDX-FileCopyrightText: 2025 geisserml # SPDX-License-Identifier: Apache-2.0 OR BSD-3-Clause import io +import sys +import logging import filecmp import contextlib from pathlib import Path @@ -9,30 +11,69 @@ import pypdfium2 as pdfium import pypdfium2.raw as pdfium_c import pypdfium2.__main__ as pdfium_cli -from .conftest import TestResources, TestExpectations +from .conftest import TestFiles, TestExpectations +lib_logger = logging.getLogger("pypdfium2") -def run_cli(argv, exp_stdout=None, normalize_lfs=False): +@contextlib.contextmanager +def logging_capture_handler(buf): + orig_handlers = lib_logger.handlers + lib_logger.handlers = [] + handler = logging.StreamHandler(buf) + lib_logger.addHandler(handler) + yield + lib_logger.removeHandler(handler) + lib_logger.handlers = orig_handlers + + +@contextlib.contextmanager +def joined_ctx(ctxes): + with contextlib.ExitStack() as stack: + for ctx in ctxes: stack.enter_context(ctx) + yield + + +class StringIOWithFileno (io.StringIO): + + def __init__(self, orig_stderr): + super().__init__() + self._orig_stderr = orig_stderr + + def fileno(self): + return self._orig_stderr.fileno() + + +def run_cli(argv, exp_output=None, capture=("out", "err", "log"), normalize_lfs=False): argv = [str(a) for a in argv] - if exp_stdout is None: + if exp_output is None: pdfium_cli.api_main(argv) else: - stdout_buf = io.StringIO() - with contextlib.redirect_stdout(stdout_buf): + output = StringIOWithFileno(sys.stderr) + ctxes = [] + assert isinstance(capture, (tuple, list)) + if "out" in capture: + ctxes += [contextlib.redirect_stdout(output)] + if "err" in capture: + ctxes += [contextlib.redirect_stderr(output)] + # for some reason, logging doesn't seem to go the usual stdout/stderr path, so explicitly install a stream handler to capture + if "log" in capture: + ctxes += [logging_capture_handler(output)] + assert len(ctxes) >= 1 + with joined_ctx(ctxes): pdfium_cli.api_main(argv) - if isinstance(exp_stdout, Path): - exp_stdout = exp_stdout.read_text() + if isinstance(exp_output, Path): + exp_output = exp_output.read_text() - stdout = stdout_buf.getvalue() + output = output.getvalue() if normalize_lfs: - stdout = stdout.replace("\r\n", "\n") + output = output.replace("\r\n", "\n") - assert stdout == exp_stdout + assert output == exp_output def _get_files(dir): @@ -45,19 +86,19 @@ def _get_text(pdf, index): @pytest.mark.parametrize("resource", ["toc", "toc_viewmodes", "toc_circular", "toc_maxdepth"]) def test_toc(resource): - run_cli(["toc", getattr(TestResources, resource)], getattr(TestExpectations, resource)) + run_cli(["toc", getattr(TestFiles, resource)], getattr(TestExpectations, resource)) def test_attachments(tmp_path): - run_cli(["attachments", TestResources.attachments, "list"], TestExpectations.attachments_list) + run_cli(["attachments", TestFiles.attachments, "list"], TestExpectations.attachments_list) - run_cli(["attachments", TestResources.attachments, "extract", "-o", tmp_path]) + run_cli(["attachments", TestFiles.attachments, "extract", "-o", tmp_path]) assert _get_files(tmp_path) == ["1_1.txt", "2_attached.pdf"] edited_pdf = tmp_path / "edited.pdf" - run_cli(["attachments", TestResources.attachments, "edit", "--del-numbers", "1,2", "--add-files", TestResources.mona_lisa, "-o", edited_pdf]) - run_cli(["attachments", edited_pdf, "list"], "[1] mona_lisa.jpg\n") + run_cli(["attachments", TestFiles.attachments, "edit", "--del-numbers", "1,2", "--add-files", TestFiles.mona_lisa, "-o", edited_pdf]) + run_cli(["attachments", edited_pdf, "list"], "[1] mona_lisa.jpg\n", capture=["out"]) def test_images(tmp_path): @@ -66,33 +107,33 @@ def test_images(tmp_path): output_dir = tmp_path / "out" output_dir.mkdir() - run_cli(["imgtopdf", TestResources.mona_lisa, "-o", img_pdf]) + run_cli(["imgtopdf", TestFiles.mona_lisa, "-o", img_pdf]) run_cli(["extract-images", img_pdf, "-o", output_dir]) output_name = "img_pdf_1_1.jpg" assert _get_files(output_dir) == [output_name] - assert filecmp.cmp(TestResources.mona_lisa, output_dir/output_name) + assert filecmp.cmp(TestFiles.mona_lisa, output_dir/output_name) @pytest.mark.parametrize("strategy", ["range", "bounded"]) def test_extract_text(strategy): - run_cli(["extract-text", TestResources.text, "--strategy", strategy], TestExpectations.text_extract, normalize_lfs=True) + run_cli(["extract-text", TestFiles.text, "--strategy", strategy], TestExpectations.text_extract, normalize_lfs=True) @pytest.mark.parametrize("resource", ["multipage", "attachments", "forms"]) def test_pdfinfo(resource): - run_cli(["pdfinfo", getattr(TestResources, resource)], getattr(TestExpectations, "pdfinfo_%s" % resource)) + run_cli(["pdfinfo", getattr(TestFiles, resource)], getattr(TestExpectations, "pdfinfo_%s" % resource)) @pytest.mark.parametrize("resource", ["images"]) def test_pageobjects(resource): - run_cli(["pageobjects", getattr(TestResources, resource)], getattr(TestExpectations, "pageobjects_%s" % resource)) + run_cli(["pageobjects", getattr(TestFiles, resource)], getattr(TestExpectations, "pageobjects_%s" % resource)) def test_arrange(tmp_path): out = tmp_path / "out.pdf" - run_cli(["arrange", TestResources.multipage, TestResources.encrypted, TestResources.empty, "--pages", "1,3", "--passwords", "_", "test_user", "-o", out]) + run_cli(["arrange", TestFiles.multipage, TestFiles.encrypted, TestFiles.empty, "--pages", "1,3", "--passwords", "_", "test_user", "-o", out]) pdf = pdfium.PdfDocument(out) assert len(pdf) == 4 @@ -104,7 +145,7 @@ def test_arrange(tmp_path): def test_tile(tmp_path): out = tmp_path / "out.pdf" - run_cli(["tile", TestResources.multipage, "-r", 2, "-c", 2, "--width", 21.0, "--height", 29.7, "-u", "cm", "-o", out]) + run_cli(["tile", TestFiles.multipage, "-r", 2, "-c", 2, "--width", 21.0, "--height", 29.7, "-u", "cm", "-o", out]) pdf = pdfium.PdfDocument(out) assert len(pdf) == 1 @@ -119,7 +160,7 @@ def test_render_multipage(tmp_path): out_dir = tmp_path / "out" out_dir.mkdir() - run_cli(["render", TestResources.multipage, "-o", out_dir, "--scale", 0.2, "-f", "jpg"]) + run_cli(["render", TestFiles.multipage, "-o", out_dir, "--scale", 0.2, "-f", "jpg"]) out_files = list(out_dir.iterdir()) assert sorted([f.name for f in out_files]) == ["multipage_1.jpg", "multipage_2.jpg", "multipage_3.jpg"] diff --git a/tests/test_document.py b/tests/test_document.py index 8eb2a3f55..569c4d6f7 100644 --- a/tests/test_document.py +++ b/tests/test_document.py @@ -1,17 +1,19 @@ -# SPDX-FileCopyrightText: 2024 geisserml +# SPDX-FileCopyrightText: 2025 geisserml # SPDX-License-Identifier: Apache-2.0 OR BSD-3-Clause +# TODO test formenv and page deletion + import re import ctypes import pathlib import pytest -from .conftest import TestResources +from .conftest import TestFiles import pypdfium2 as pdfium import pypdfium2.raw as pdfium_c -parametrize_opener_files = pytest.mark.parametrize("file", [TestResources.empty]) +parametrize_opener_files = pytest.mark.parametrize("file", [TestFiles.empty]) def _check_pdf(pdf): @@ -84,7 +86,7 @@ def test_open_ctypes_array(file): def test_open_raw(): # not meant for embedders, but works for testing all the same - pdf = pdfium.PdfDocument(TestResources.empty) + pdf = pdfium.PdfDocument(TestFiles.empty) pdf._finalizer.detach() input = pdf.raw assert isinstance(input, pdfium_c.FPDF_DOCUMENT) @@ -115,7 +117,7 @@ def _make_encryption_cases(file, passwords): @pytest.mark.parametrize( ["input", "password"], - _make_encryption_cases(TestResources.encrypted, ["test_user", "test_owner"]), + _make_encryption_cases(TestFiles.encrypted, ["test_user", "test_owner"]), ) def test_open_encrypted(input, password): pdf = pdfium.PdfDocument(input, password, autoclose=True) @@ -124,7 +126,7 @@ def test_open_encrypted(input, password): @pytest.mark.parametrize( ["input", "password"], - _make_encryption_cases(TestResources.empty, ["superfluous"]), + _make_encryption_cases(TestFiles.empty, ["superfluous"]), ) def test_open_with_excessive_password(input, password): pdf = pdfium.PdfDocument(input, password, autoclose=True) @@ -136,12 +138,14 @@ def test_open_invalid(): pdf = pdfium.PdfDocument(123) with pytest.raises(FileNotFoundError): pdf = pdfium.PdfDocument("invalid/path") - with pytest.raises(pdfium.PdfiumError, match=re.escape("Failed to load document (PDFium: Incorrect password error).")): - pdf = pdfium.PdfDocument(TestResources.encrypted, password="wrong_password") + with pytest.raises(pdfium.PdfiumError, match=re.escape("Failed to load document (PDFium: Incorrect password error).")) as e: + pdf = pdfium.PdfDocument(TestFiles.encrypted, password="wrong_password") + e = e.value + assert e.err_code == pdfium_c.FPDF_ERR_PASSWORD def test_misc(): - pdf = pdfium.PdfDocument(TestResources.empty) + pdf = pdfium.PdfDocument(TestFiles.empty) assert pdf.get_formtype() == pdfium_c.FORMTYPE_NONE assert pdf.get_version() == 15 assert pdf.get_identifier(pdfium_c.FILEIDTYPE_PERMANENT) == b"\xec\xe5!\x04\xd6\x1b(R\x1a\x89f\x85\n\xbe\xa4" @@ -154,7 +158,7 @@ def test_misc(): def test_page_labels(): # incidentally, it happens that this TOC test file also has page labels - pdf = pdfium.PdfDocument(TestResources.toc_viewmodes) + pdf = pdfium.PdfDocument(TestFiles.toc_viewmodes) exp_labels = ["i", "ii", "appendix-C", "appendix-D", "appendix-E", "appendix-F", "appendix-G", "appendix-H"] assert exp_labels == [pdf.get_page_label(i) for i in range(len(pdf))] @@ -173,7 +177,7 @@ def _compare_metadata(pdf, metadata, exp_metadata): def test_metadata_dict(): - pdf = pdfium.PdfDocument(TestResources.empty) + pdf = pdfium.PdfDocument(TestFiles.empty) metadata = pdf.get_metadata_dict() exp_metadata = { "Producer": "LibreOffice 6.4", @@ -203,23 +207,19 @@ def test_new_page_on_new_pdf(new_pages): ] ) def test_new_page_on_existing_pdf(new_pages): - pdf = pdfium.PdfDocument(TestResources.multipage) + pdf = pdfium.PdfDocument(TestFiles.multipage) for index, size in new_pages: page = pdf.new_page(*size, index=index) if index is None: index = len(pdf) - 1 assert page.get_size() == pdf.get_page_size(index) == size - - -def test_del_page(): - pass ImportTestSequence = [ - (TestResources.empty, None, None, 1), - (TestResources.empty, "", 0, 1), - (TestResources.multipage, [1, 0, 1, 2, 1], 1, 5), - (TestResources.multipage, "2,1-3, 2", 4, 5), + (TestFiles.empty, None, None, 1), + (TestFiles.empty, "", 0, 1), + (TestFiles.multipage, [1, 0, 1, 2, 1], 1, 5), + (TestFiles.multipage, "2,1-3, 2", 4, 5), ] @pytest.mark.parametrize("sequence", [ImportTestSequence]) @@ -234,13 +234,9 @@ def test_import_pages(sequence): assert len(dest_pdf) == exp_len -def test_formenv(): - pass - - def test_closing_parent_closes_kids(): - pdf = pdfium.PdfDocument(TestResources.multipage) + pdf = pdfium.PdfDocument(TestFiles.multipage) pages = list(pdf) assert len(pages) == 3 pdf.close() @@ -251,7 +247,7 @@ def test_closing_parent_closes_kids(): def test_post_close(): - pdf = pdfium.PdfDocument(TestResources.empty) + pdf = pdfium.PdfDocument(TestFiles.empty) pdf.close() with pytest.raises(ctypes.ArgumentError): pdf.get_version() diff --git a/tests/test_matrix.py b/tests/test_matrix.py deleted file mode 100644 index b4fb1b2f0..000000000 --- a/tests/test_matrix.py +++ /dev/null @@ -1,2 +0,0 @@ -# SPDX-FileCopyrightText: 2024 geisserml -# SPDX-License-Identifier: Apache-2.0 OR BSD-3-Clause diff --git a/tests/test_misc.py b/tests/test_misc.py index 739fe9e98..309c3e812 100644 --- a/tests/test_misc.py +++ b/tests/test_misc.py @@ -1,10 +1,15 @@ -# SPDX-FileCopyrightText: 2024 geisserml +# SPDX-FileCopyrightText: 2025 geisserml # SPDX-License-Identifier: Apache-2.0 OR BSD-3-Clause +import sys import pytest +import platform +import warnings +import pypdfium2 as pdfium import pypdfium2.raw as pdfium_c import pypdfium2.internal as pdfium_i from pypdfium2.version import PDFIUM_INFO +# from .conftest import OutputDir @pytest.mark.parametrize( ["color_in", "rev_byteorder", "exp_color"], @@ -27,7 +32,7 @@ def test_color_tohex(color_in, rev_byteorder, exp_color): assert pdfium_c.FPDF_GetBValue(exp_color) == channels[3] -def _filter(prefix, skips=[], type=int): +def _filter(prefix, skips=(), type=int): items = [] for attr in dir(pdfium_c): value = getattr(pdfium_c, attr) @@ -40,6 +45,7 @@ def _filter(prefix, skips=[], type=int): BitmapNsp = _filter("FPDFBitmap_", [pdfium_c.FPDFBitmap_Unknown]) PageObjNsp = _filter("FPDF_PAGEOBJ_") ErrorMapping = pdfium_i.ErrorToStr +# FIXME this will cause an erroneous test failure when using the reference bindings with a non-XFA build if "XFA" in PDFIUM_INFO.flags: ErrorMapping.update(pdfium_i.XFAErrorToStr) @@ -83,3 +89,79 @@ def test_const_converters(mapping, use_keys, items): def test_const_converters_rotation(degrees, const): assert pdfium_i.RotationToConst[degrees] == const assert pdfium_i.RotationToDegrees[const] == degrees + + +@pytest.mark.parametrize( + "mode_str, rev_byteorder", + [ + ("BGR", False), + ("BGR", True), + ("BGRA", False), + ("BGRA", True), + ("BGRX", False), + ("BGRX", True), + ("L", False), + ] +) +def test_bitmap_makers_to_images(mode_str, rev_byteorder): + + # Exercise the various bitmap maker strategies, and confirm we can add them all as images to a PDF. + # Admittedly, this is a bit off-practice, as the bitmap maker are mainly for rendering. When embedding an existing image, you probably have a native buffer on the caller side, and don't want to create a new foreign buffer. + + w, h = 10, 10 + rect = 0, 0, w, h + mode_constant = pdfium_i.BitmapStrToConst[mode_str] + + pdf = pdfium.PdfDocument.new() + page = pdf.new_page(w*2, h*2) + common_mat = pdfium.PdfMatrix().scale(w, h) + + def _add_bitmap_at_pos(bitmap, x, y): + img = pdfium.PdfImage.new(pdf) + img.set_bitmap(bitmap) + img.set_matrix(common_mat.translate(x, y)) + page.insert_obj(img) + + # with rev_byteorder, red and blue swap place, while green and black are unaffected + + native = pdfium.PdfBitmap.new_native(w, h, mode_constant, rev_byteorder=rev_byteorder) + native.fill_rect((255, 0, 0, 255), *rect) # red/blue + _add_bitmap_at_pos(native, 0, 10) # top left + + foreign = pdfium.PdfBitmap.new_foreign(w, h, mode_constant, rev_byteorder=rev_byteorder) + foreign.fill_rect((0, 255, 0, 255), *rect) # green + _add_bitmap_at_pos(foreign, 10, 10) # top right + + foreign_packed = pdfium.PdfBitmap.new_foreign(w, h, mode_constant, force_packed=True, rev_byteorder=rev_byteorder) + foreign_packed.fill_rect((0, 0, 255, 255), *rect) # blue/red + _add_bitmap_at_pos(foreign_packed, 0, 0) # bottom left + + map_to_use_alpha = {pdfium_c.FPDFBitmap_BGRx: False, pdfium_c.FPDFBitmap_BGRA: True} + if mode_constant in map_to_use_alpha: + use_alpha = map_to_use_alpha[mode_constant] + foreign_simple = pdfium.PdfBitmap.new_foreign_simple(w, h, use_alpha=use_alpha, rev_byteorder=rev_byteorder) + foreign_simple.fill_rect((0, 0, 0, 255), *rect) # black + _add_bitmap_at_pos(foreign_simple, 10, 0) # bottom right + + page.gen_content() + # pdf.save(OutputDir / f"bitmap_makers_imgs_{mode_str}_{rev_byteorder}.pdf") + + +@pytest.mark.skipif(not sys.platform.startswith("linux"), reason="libc_ver/musl test only makes sense on linux") +def test_musllinux_api_available(): + + # Test availability of the non-public API we use to detect musllinux on setup. + # `packaging` is a pretty fundamental package so expect it to be installed + + import packaging._musllinux + assert hasattr(packaging._musllinux, "_get_musl_version") + + libc_name, libc_ver = platform.libc_ver() + musl_ver = packaging._musllinux._get_musl_version(sys.executable) + + if libc_name in ("glibc", "libc"): + assert not musl_ver + else: + assert musl_ver, "Not glibc or android libc, expected musl" + if libc_name: + warnings.warn(f"platform.libc_ver() now returns {(libc_name, libc_ver)} for musl") diff --git a/tests/test_nup.py b/tests/test_nup.py index 0d505cc8e..67b2bf76a 100644 --- a/tests/test_nup.py +++ b/tests/test_nup.py @@ -1,17 +1,17 @@ -# SPDX-FileCopyrightText: 2024 geisserml +# SPDX-FileCopyrightText: 2025 geisserml # SPDX-License-Identifier: Apache-2.0 OR BSD-3-Clause import pytest import pypdfium2 as pdfium import pypdfium2.raw as pdfium_c -from .conftest import TestResources, OutputDir +from .conftest import TestFiles, OutputDir def test_xobject_placement(): # basic test to at least run through the code - src_pdf = pdfium.PdfDocument(TestResources.multipage) + src_pdf = pdfium.PdfDocument(TestFiles.multipage) dest_pdf = pdfium.PdfDocument.new() xobject = src_pdf.page_as_xobject(0, dest_pdf) @@ -32,26 +32,26 @@ def test_xobject_placement(): dest_page_1.insert_obj(po) assert po.pdf is dest_pdf assert po.page is dest_page_1 - pos_a = po.get_pos() + pos_a = po.get_bounds() # xfail with pdfium < 5370, https://crbug.com/pdfium/1905 assert pytest.approx(pos_a, abs=0.5) == (19, 440, 279, 823) po = xobject.as_pageobject() - matrix = base_matrix.mirror(v=True, h=False).translate(w, 0).translate(w, h) + matrix = base_matrix.mirror(invert_x=True, invert_y=False).translate(w, 0).translate(w, h) assert matrix == pdfium.PdfMatrix(-0.5, 0, 0, 0.5, 2*w, h) po.transform(matrix) dest_page_1.insert_obj(po) po = xobject.as_pageobject() assert po.get_matrix() == pdfium.PdfMatrix() - matrix = base_matrix.mirror(v=False, h=True).translate(0, h).translate(w, 0) + matrix = base_matrix.mirror(invert_x=False, invert_y=True).translate(0, h).translate(w, 0) assert matrix == pdfium.PdfMatrix(0.5, 0, 0, -0.5, w, h) po.set_matrix(matrix) assert po.get_matrix() == matrix dest_page_1.insert_obj(po) po = xobject.as_pageobject() - matrix = base_matrix.mirror(v=True, h=True).translate(w, h) + matrix = base_matrix.mirror(invert_x=True, invert_y=True).translate(w, h) assert matrix == pdfium.PdfMatrix(-0.5, 0, 0, -0.5, w, h) po.set_matrix(matrix) dest_page_1.insert_obj(po) diff --git a/tests_old/test_opener.py b/tests/test_opener.py similarity index 98% rename from tests_old/test_opener.py rename to tests/test_opener.py index 4e0c0b186..cc6d1be7b 100644 --- a/tests_old/test_opener.py +++ b/tests/test_opener.py @@ -1,6 +1,8 @@ -# SPDX-FileCopyrightText: 2024 geisserml +# SPDX-FileCopyrightText: 2025 geisserml # SPDX-License-Identifier: Apache-2.0 OR BSD-3-Clause +# FIXME merge with test_document and deduplicate + import re import shutil import tempfile diff --git a/tests/test_page.py b/tests/test_page.py index b4fb1b2f0..26b050d6d 100644 --- a/tests/test_page.py +++ b/tests/test_page.py @@ -1,2 +1,80 @@ -# SPDX-FileCopyrightText: 2024 geisserml +# SPDX-FileCopyrightText: 2025 geisserml # SPDX-License-Identifier: Apache-2.0 OR BSD-3-Clause + +import pytest +import pypdfium2 as pdfium +import pypdfium2.raw as pdfium_c +from .conftest import TestFiles, OutputDir + + +def test_boxes(): + + pdf = pdfium.PdfDocument(TestFiles.render) + index = 0 + page = pdf[index] + assert page.get_size() == pdf.get_page_size(index) == (595, 842) + assert page.get_mediabox() == (0, 0, 595, 842) + assert isinstance(page, pdfium.PdfPage) + + test_cases = [ + ("media", (0, 0, 612, 792)), + ("media", (0, 0, 595, 842)), + ("crop", (10, 10, 585, 832)), + ("bleed", (20, 20, 575, 822)), + ("trim", (30, 30, 565, 812)), + ("art", (40, 40, 555, 802)), + ] + + for mn, exp_box in test_cases: + getattr(page, f"set_{mn}box")(*exp_box) + box = getattr(page, f"get_{mn}box")() + assert pytest.approx(box) == exp_box + + +def test_mediabox_fallback(): + pdf = pdfium.PdfDocument(TestFiles.box_fallback) + page = pdf[0] + assert page.get_mediabox() == (0, 0, 612, 792) + + +def test_rotation(): + pdf = pdfium.PdfDocument.new() + page = pdf.new_page(500, 800) + for r in (90, 180, 270, 0): + page.set_rotation(r) + assert page.get_rotation() == r + + +def test_page_labels(): + # incidentally, it happens that this TOC test file also has page labels + pdf = pdfium.PdfDocument(TestFiles.toc_viewmodes) + exp_labels = ["i", "ii", "appendix-C", "appendix-D", "appendix-E", "appendix-F", "appendix-G", "appendix-H"] + assert exp_labels == [pdf.get_page_label(i) for i in range(len(pdf))] + + +def test_flatten(): + pdf = pdfium.PdfDocument(TestFiles.forms) + pdf.init_forms() + page = pdf[0] + rc = page.flatten() + assert rc == pdfium_c.FLATTEN_SUCCESS + pdf.save(OutputDir / "flattened.pdf") + + +def test_posconv(): + pdf = pdfium.PdfDocument.new() + W, H = 100, 150 + page = pdf.new_page(W, H) + posconv = pdfium.PdfPosConv(page, (0, 0, W, H, 0)) + page_corners = [ + (0, 0), # bottom left == origin + (W, 0), # bottom right + (0, H), # top left + (W, H), # top right + ] + # bitmaps use top-left as origin + bmp_corners = [posconv.to_bitmap(x, y) for x, y in page_corners] + exp_bmp_corners = [(x, H-y) for x, y in page_corners] + assert bmp_corners == exp_bmp_corners + reverse_page_corners = [posconv.to_page(x, y) for x, y in bmp_corners] + assert reverse_page_corners == page_corners diff --git a/tests/test_pageobjects.py b/tests/test_pageobjects.py index b4fb1b2f0..59bb1a83d 100644 --- a/tests/test_pageobjects.py +++ b/tests/test_pageobjects.py @@ -1,2 +1,262 @@ -# SPDX-FileCopyrightText: 2024 geisserml +# SPDX-FileCopyrightText: 2025 geisserml # SPDX-License-Identifier: Apache-2.0 OR BSD-3-Clause + +import io +import re +import pytest +import PIL.Image +import pypdfium2 as pdfium +import pypdfium2.raw as pdfium_c +from .conftest import TestFiles, OutputDir, compare_n2 + + +def test_image_objects(): + pdf = pdfium.PdfDocument(TestFiles.images) + page = pdf[0] + assert page.pdf is pdf + + images = list( page.get_objects(filter=[pdfium_c.FPDF_PAGEOBJ_IMAGE]) ) + assert len(images) == 3 + + img_0 = images[0] + assert isinstance(img_0, pdfium.PdfObject) + assert type(img_0) is pdfium.PdfImage + assert img_0.type == pdfium_c.FPDF_PAGEOBJ_IMAGE + assert isinstance(img_0.raw, pdfium_c.FPDF_PAGEOBJECT) + assert img_0.level == 0 + assert img_0.page is page + assert img_0.pdf is pdf + + positions = [img.get_bounds() for img in images] + exp_positions = [ + (133, 459, 350, 550), + (48, 652, 163, 700), + (204, 204, 577, 360), + ] + compare_n2(positions, exp_positions) + + compare_n2( + img_0.get_quad_points(), + ((132.7, 459.2), (349.5, 459.2), (349.5, 549.7), (132.7, 549.7)) + ) + + +def test_misc_objects(): + + pdf = pdfium.PdfDocument(TestFiles.render) + page = pdf[0] + assert page.pdf is pdf + + for obj in page.get_objects(): + assert type(obj) is pdfium.PdfObject + assert isinstance(obj.raw, pdfium_c.FPDF_PAGEOBJECT) + assert obj.type in (pdfium_c.FPDF_PAGEOBJ_TEXT, pdfium_c.FPDF_PAGEOBJ_PATH) + assert obj.level == 0 + assert obj.page is page + assert obj.pdf is pdf + pos = obj.get_bounds() + assert len(pos) == 4 + + text_obj = next(obj for obj in page.get_objects() if obj.type == pdfium_c.FPDF_PAGEOBJ_TEXT) + path_obj = next(obj for obj in page.get_objects() if obj.type == pdfium_c.FPDF_PAGEOBJ_PATH) + + compare_n2( + text_obj.get_quad_points(), + ((57.3, 767.4), (124.2, 767.4), (124.2, 780.9), (57.3, 780.9)) + ) + + with pytest.raises(RuntimeError, match=re.escape("Quad points only supported for image and text objects.")): + path_obj.get_quad_points() + + +def test_new_image_from_jpeg(): + + pdf = pdfium.PdfDocument.new() + page = pdf.new_page(240, 120) + + image_a = pdfium.PdfImage.new(pdf) + buffer = open(TestFiles.mona_lisa, "rb") + image_a.load_jpeg(buffer, autoclose=True) + width, height = image_a.get_px_size() + page.insert_obj(image_a) + + assert len(pdf._data_holder) == 1 + assert pdf._data_closer == [buffer] + + assert image_a.get_matrix() == pdfium.PdfMatrix() + image_a.set_matrix( pdfium.PdfMatrix().scale(width, height) ) + assert image_a.get_matrix() == pdfium.PdfMatrix(width, 0, 0, height, 0, 0) + + pil_image_1 = PIL.Image.open(TestFiles.mona_lisa) + bitmap = image_a.get_bitmap() + pil_image_2 = bitmap.to_pil() + assert (120, 120) == pil_image_1.size == pil_image_2.size == (bitmap.width, bitmap.height) + assert "RGB" == pil_image_1.mode == pil_image_2.mode + + in_data = TestFiles.mona_lisa.read_bytes() + out_buffer = io.BytesIO() + image_a.extract(out_buffer) + out_buffer.seek(0) + out_data = out_buffer.read() + assert in_data == out_data + + metadata = image_a.get_metadata() + assert isinstance(metadata, pdfium_c.FPDF_IMAGEOBJ_METADATA) + assert metadata.bits_per_pixel == 24 # 3 channels, 8 bits each + assert metadata.colorspace == pdfium_c.FPDF_COLORSPACE_DEVICERGB + assert metadata.height == height == 120 + assert metadata.width == width == 120 + assert metadata.horizontal_dpi == 72 + assert metadata.vertical_dpi == 72 + + image_b = pdfium.PdfImage.new(pdf) + with open(TestFiles.mona_lisa, "rb") as buffer: + image_b.load_jpeg(buffer, inline=True, autoclose=False) + + assert image_b.get_matrix() == pdfium.PdfMatrix() + image_b.set_matrix( pdfium.PdfMatrix().scale(width, height).translate(width, 0) ) + image_b.get_matrix() == pdfium.PdfMatrix(width, 0, 0, height, width, 0) + page.insert_obj(image_b) + + page.gen_content() + out_path = OutputDir / "image_jpeg.pdf" + pdf.save(out_path) + assert out_path.exists() + + page._finalizer() + pdf._finalizer() + assert buffer.closed is True + + +def test_new_image_from_bitmap(): + + src_pdf = pdfium.PdfDocument(TestFiles.render) + src_page = src_pdf[0] + dst_pdf = pdfium.PdfDocument.new() + image_a = pdfium.PdfImage.new(dst_pdf) + + bitmap = src_page.render() + w, h = bitmap.width, bitmap.height + image_a.set_bitmap(bitmap) + image_a.set_matrix( pdfium.PdfMatrix().scale(w, h) ) + + pil_image = PIL.Image.open(TestFiles.mona_lisa) + bitmap = pdfium.PdfBitmap.from_pil(pil_image) + image_b = pdfium.PdfImage.new(dst_pdf) + image_b.set_matrix( pdfium.PdfMatrix().scale(bitmap.width, bitmap.height) ) + image_b.set_bitmap(bitmap) + + dst_page = dst_pdf.new_page(w, h) + dst_page.insert_obj(image_a) + dst_page.insert_obj(image_b) + dst_page.gen_content() + + out_path = OutputDir / "image_bitmap.pdf" + dst_pdf.save(out_path) + + reopened_pdf = pdfium.PdfDocument(out_path) + reopened_page = reopened_pdf[0] + reopened_image = next( reopened_page.get_objects(filter=[pdfium_c.FPDF_PAGEOBJ_IMAGE]) ) + assert reopened_image.get_filters() == ["FlateDecode"] + + +def test_replace_image_with_jpeg(): + + pdf = pdfium.PdfDocument(TestFiles.images) + page = pdf[0] + + images = list( page.get_objects(filter=[pdfium_c.FPDF_PAGEOBJ_IMAGE]) ) + matrices = [img.get_matrix() for img in images] + assert len(images) == 3 + image_1 = images[0] + + image_1.load_jpeg(TestFiles.mona_lisa, pages=[page]) + width, height = image_1.get_px_size() + assert matrices == [img.get_matrix() for img in images] + + # preserve the aspect ratio + # this strategy only works if the matrix was just used for size/position + for image, matrix in zip(images, matrices): + w_scale = matrix.a / width + h_scale = matrix.d / height + scale = min(w_scale, h_scale) + new_matrix = pdfium.PdfMatrix(width*scale, 0, 0, height*scale, matrix.e, matrix.f) + image.set_matrix(new_matrix) + assert image.get_matrix() == new_matrix + + page.gen_content() + output_path = OutputDir / "replace_images.pdf" + pdf.save(output_path) + assert output_path.exists() + + +@pytest.mark.parametrize( + "render, scale_to_original", + [(False, None), (True, False), (True, True)] +) +def test_image_get_bitmap(render, scale_to_original): + + pdf = pdfium.PdfDocument(TestFiles.images) + page = pdf[0] + image = next( page.get_objects(filter=[pdfium_c.FPDF_PAGEOBJ_IMAGE]) ) + + metadata = image.get_metadata() + assert metadata.width == 115 + assert metadata.height == 48 + assert round(metadata.horizontal_dpi) == 38 + assert round(metadata.vertical_dpi) == 38 + assert metadata.colorspace == pdfium_c.FPDF_COLORSPACE_DEVICEGRAY + assert metadata.marked_content_id == 1 + assert metadata.bits_per_pixel == 1 + + kwargs = dict(render=render) + if scale_to_original is not None: + kwargs["scale_to_original"] = scale_to_original + bitmap = image.get_bitmap(**kwargs) + assert isinstance(bitmap, pdfium.PdfBitmap) + + if render: + assert bitmap.format == pdfium_c.FPDFBitmap_BGRA + assert bitmap.n_channels == 4 + # Somewhere between pdfium 6462 and 6899, size/stride expectation changed here + if scale_to_original: + assert (bitmap.width, bitmap.height, bitmap.stride) == (115, 49, 460) + else: + assert (bitmap.width, bitmap.height, bitmap.stride) == (217, 91, 868) + assert bitmap.rev_byteorder is False + output_path = OutputDir / "extract_rendered.png" + else: + # NOTE fails with pdfium >= 1e1e173 (6015), < b5bc2e9 (6029), which returns RGB + assert bitmap.format == pdfium_c.FPDFBitmap_Gray + assert bitmap.n_channels == 1 + assert (bitmap.width, bitmap.height, bitmap.stride) == (115, 48, 116) + assert bitmap.rev_byteorder is False + output_path = OutputDir / "extract.png" + + pil_image = bitmap.to_pil() + assert isinstance(pil_image, PIL.Image.Image) + pil_image.save(output_path) + assert output_path.exists() + + +def test_remove_image(): + + pdf = pdfium.PdfDocument(TestFiles.images) + page_1 = pdf[0] + + # TODO order images by position + images = list( page_1.get_objects(filter=[pdfium_c.FPDF_PAGEOBJ_IMAGE]) ) + assert len(images) == 3 + + # drop an image + page_1.remove_obj(images[0]) + + # delete and re-insert an image in place + page_1.remove_obj(images[1]) + page_1.insert_obj(images[1]) + + page_1.gen_content() + + output_path = OutputDir / "test_remove_objects.pdf" + pdf.save(output_path) + assert output_path.exists() diff --git a/tests/test_rendering.py b/tests/test_rendering.py index b4fb1b2f0..a0650151d 100644 --- a/tests/test_rendering.py +++ b/tests/test_rendering.py @@ -1,2 +1,384 @@ -# SPDX-FileCopyrightText: 2024 geisserml +# SPDX-FileCopyrightText: 2025 geisserml # SPDX-License-Identifier: Apache-2.0 OR BSD-3-Clause + +import math +import numpy +import warnings +import PIL.Image +import PIL.ImageDraw +import pytest +import pypdfium2 as pdfium +import pypdfium2.raw as pdfium_c +from .conftest import ( + TestFiles, + PyVersion, + OutputDir, + ExpRenderPixels, + compare_n2, +) + +# TODO assert that bitmap and info are consistent + + +@pytest.fixture +def sample_page(): + pdf = pdfium.PdfDocument(TestFiles.render) + page = pdf[0] + yield page + +@pytest.fixture +def multipage_doc(): + pdf = pdfium.PdfDocument(TestFiles.multipage) + yield pdf + +def _check_pixels(pil_image, pixels): + for pos, value in pixels: + assert pil_image.getpixel(pos) == value + + +@pytest.mark.parametrize( + ("name", "crop", "scale", "rotation"), + [ + ["01_r0", (0, 0, 0, 0 ), 0.25, 0, ], + ["02_r90", (0, 0, 0, 0 ), 0.5, 90, ], + ["03_r180", (0, 0, 0, 0 ), 0.75, 180, ], + ["04_r270", (0, 0, 0, 0 ), 1, 270, ], + ["05_cl", (100, 0, 0, 0 ), 0.5, 0, ], + ["06_cb", (0, 100, 0, 0 ), 0.5, 0, ], + ["07_cr", (0, 0, 100, 0 ), 0.5, 0, ], + ["08_ct", (0, 0, 0, 100), 0.5, 0, ], + ["09_r90_cb", (0, 100, 0, 0 ), 0.5, 90, ], + ["10_r180_cr", (0, 0, 100, 0 ), 0.5, 180, ], + ["11_r270_ct", (0, 0, 0, 100), 0.5, 270, ], + ] +) +def test_render_page_transform(sample_page, name, crop, scale, rotation): + pil_image = sample_page.render( + crop = crop, + scale = scale, + rotation = rotation, + ).to_pil() + pil_image.save(OutputDir / ("%s.png" % name)) + assert pil_image.mode == "RGB" + + c_left, c_bottom, c_right, c_top = [math.ceil(c*scale) for c in crop] + w = math.ceil(595*scale) + h = math.ceil(842*scale) + if rotation in (90, 270): + w, h = h, w + + c_w = w - c_left - c_right + c_h = h - c_bottom - c_top + assert pil_image.size == (c_w, c_h) + + pixels = [] + for (x, y), value in ExpRenderPixels: + x, y = round(x*scale), round(y*scale) + if rotation in (90, 270): + x, y = y, x + if rotation == 90: + x = w-1 - x + elif rotation == 180: + x = w-1 - x + y = h-1 - y + elif rotation == 270: + y = h-1 - y + x -= c_left + y -= c_top + if 0 <= x < c_w and 0 <= y < c_h: + pixels.append( ((x, y), value) ) + + _check_pixels(pil_image, pixels) + + +@pytest.mark.parametrize( + "rev_byteorder", [False, True] +) +def test_render_page_bgrx(rev_byteorder, sample_page): + pil_image = sample_page.render( + prefer_bgrx = True, + rev_byteorder = rev_byteorder, + ).to_pil() + assert pil_image.mode == "RGBX" + exp_pixels = [(pos, (*value, 255)) for pos, value in ExpRenderPixels] + _check_pixels(pil_image, exp_pixels) + + +def test_render_page_alpha(sample_page): + + pixels = [ + [(0, 0 ), (0, 0, 0, 0 )], + [(62, 66 ), (0, 0, 0, 186)], + [(150, 180), (129, 212, 26, 255)], + [(150, 390), (42, 96, 153, 255)], + [(150, 570), (128, 0, 128, 255)], + ] + kwargs = dict( + fill_color = (0, 0, 0, 0), + ) + image = sample_page.render(**kwargs).to_pil() + image_rev = sample_page.render(**kwargs, rev_byteorder=True).to_pil() + + if PyVersion > (3, 6): + assert image == image_rev + assert image.mode == "RGBA" + assert image.size == (595, 842) + for pos, exp_value in pixels: + assert image.getpixel(pos) == exp_value + + image.save(OutputDir / "colored_alpha.png") + + +def test_render_page_grey(sample_page): + kwargs = dict( + grayscale = True, + scale = 0.5, + ) + image = sample_page.render(**kwargs).to_pil() + image_rev = sample_page.render(**kwargs, rev_byteorder=True).to_pil() + assert image == image_rev + assert image.size == (298, 421) + assert image.mode == "L" + image.save(OutputDir / "grayscale.png") + + +@pytest.mark.parametrize( + "fill_color", + [ + (255, 255, 255, 255), + (60, 70, 80, 100), + (255, 255, 255, 255), + (0, 255, 255, 255), + (255, 0, 255, 255), + (255, 255, 0, 255), + ] +) +def test_render_page_fill_color(fill_color, sample_page): + kwargs = dict( + fill_color = fill_color, + scale = 0.5, + ) + image = sample_page.render(**kwargs).to_pil() + image_rev = sample_page.render(**kwargs, rev_byteorder=True).to_pil() + + if PyVersion > (3, 6): + assert image == image_rev + + bg_pixel = image.getpixel( (0, 0) ) + if fill_color[3] == 255: + fill_color = fill_color[:-1] + assert image.size == (298, 421) + assert bg_pixel == fill_color + + +@pytest.mark.parametrize("fill_to_stroke", [False, True]) +def test_render_page_colorscheme(fill_to_stroke): + pdf = pdfium.PdfDocument(TestFiles.render) + page = pdf[0] + # from _cli/render.py::SampleTheme + color_scheme = pdfium.PdfColorScheme( + path_fill = (170, 100, 0, 255), # dark orange + path_stroke = (0, 150, 255, 255), # sky blue + text_fill = (255, 255, 255, 255), # white + text_stroke = (150, 255, 0, 255), # green + ) + image = page.render( + fill_color = (0, 0, 0, 255), + color_scheme = color_scheme, + fill_to_stroke = fill_to_stroke, + ).to_pil() + assert image.mode == "RGB" + image.save(OutputDir / f"render_colorscheme_{int(fill_to_stroke)}.png") + + +@pytest.mark.parametrize("rev_byteorder", [False, True]) +def test_render_page_tonumpy(rev_byteorder, sample_page): + + bitmap = sample_page.render( + rev_byteorder = rev_byteorder, + ) + mode = bitmap.mode + array = bitmap.to_numpy() + + assert isinstance(array, numpy.ndarray) + assert mode == ("RGB" if rev_byteorder else "BGR") + + for (x, y), value in ExpRenderPixels: + if rev_byteorder: + assert tuple(array[y][x]) == value + else: + assert tuple(array[y][x]) == tuple(reversed(value)) + + +@pytest.mark.parametrize("mode", [None, "lcd", "print"]) +def test_render_page_optimization(sample_page, mode): + pil_image = sample_page.render( + optimize_mode = mode, + scale = 0.5, + ).to_pil() + assert isinstance(pil_image, PIL.Image.Image) + + +def test_render_page_noantialias(sample_page): + pil_image = sample_page.render( + no_smoothtext = True, + no_smoothimage = True, + no_smoothpath = True, + scale = 0.5, + ).to_pil() + assert isinstance(pil_image, PIL.Image.Image) + + +def test_render_pages(multipage_doc): + for page in multipage_doc: + image = page.render( + scale = 0.5, + grayscale = True, + ).to_pil() + assert isinstance(image, PIL.Image.Image) + + +def test_render_pil_numpy_eq(multipage_doc): + for page in multipage_doc: + # render at formats supported by PIL natively (i.e. without copying) so that pil_image and np_array are backed by the same memory + bitmap = page.render(scale=0.5, rev_byteorder=True, prefer_bgrx=True) + pil_image = bitmap.to_pil() + np_array = bitmap.to_numpy() + assert isinstance(pil_image, PIL.Image.Image) + assert isinstance(np_array, numpy.ndarray) + pil_image_from_array = PIL.Image.fromarray(np_array, mode="RGBX") + assert pil_image == pil_image_from_array + + +def test_render_new(): + pdf = pdfium.PdfDocument.new() + page = pdf.new_page(50, 100) + bitmap = page.render() + + +def test_render_with_stream_read(): + stream = open(TestFiles.multipage, "rb") + pdf = pdfium.PdfDocument(stream) + page = pdf[0] + bitmap = page.render() + + +@pytest.mark.parametrize( + ("with_forms", "exp_color"), + [ + (False, (255, 255, 255)), + (True, (0, 51, 113)), + ] +) +def test_render_form(with_forms, exp_color): + + pdf = pdfium.PdfDocument(TestFiles.forms) + if with_forms: + pdf.init_forms() + + if with_forms: + assert isinstance(pdf.formenv, pdfium.PdfFormEnv) + else: + assert pdf.formenv is None + + page = pdf[0] + image = page.render( + may_draw_forms = with_forms, + ).to_pil() + + assert image.getpixel( (190, 190) ) == exp_color + assert image.getpixel( (190, 430) ) == exp_color + assert image.getpixel( (190, 480) ) == exp_color + + +def test_numpy_nocopy(sample_page): + bitmap = sample_page.render(scale=0.1) + array = bitmap.to_numpy() + assert (bitmap.width, bitmap.height) == (60, 85) + val_a, val_b = 255, 123 + assert array[0][0][0] == val_a + bitmap.buffer[0] = val_b + assert array[0][0][0] == val_b + array[0][0][0] = val_a + assert bitmap.buffer[0] == val_a + + +@pytest.mark.parametrize( + ("bitmap_format", "rev_byteorder", "is_referenced"), + [ + (pdfium_c.FPDFBitmap_BGR, False, False), + (pdfium_c.FPDFBitmap_BGR, True, False), + (pdfium_c.FPDFBitmap_BGRA, False, False), + (pdfium_c.FPDFBitmap_BGRA, True, True), + (pdfium_c.FPDFBitmap_BGRx, False, False), + (pdfium_c.FPDFBitmap_BGRx, True, True), + (pdfium_c.FPDFBitmap_Gray, False, True), + ] +) +def test_pil_nocopy_where_possible(bitmap_format, rev_byteorder, is_referenced, sample_page): + + bitmap = sample_page.render( + scale = 0.1, + rev_byteorder = rev_byteorder, + force_bitmap_format = bitmap_format, + ) + pil_image = bitmap.to_pil() + assert pil_image.size == (60, 85) + + val_a, val_b = 255, 123 + if bitmap.n_channels == 4: + pixel_a = (val_a, 255, 255, 255) + pixel_b = (val_b, 255, 255, 255) + elif bitmap.n_channels == 3: + pixel_a = (val_a, 255, 255) + pixel_b = (val_b, 255, 255) + elif bitmap.n_channels == 1: + pixel_a = val_a + pixel_b = val_b + else: + assert False + + assert pil_image.getpixel((0, 0)) == pixel_a + bitmap.buffer[0] = val_b + + if is_referenced: + + # changes to the buffer are reflected in the image + assert pil_image.getpixel((0, 0)) == pixel_b + + # changes to the image are reflected in the buffer, since we set `.readonly = False` on after image init + pil_image.putpixel((0, 0), pixel_a) + assert pil_image.getpixel((0, 0)) == pixel_a + assert bitmap.buffer[0] == val_a + + else: + if pil_image.getpixel((0, 0)) == pixel_b: + warnings.warn(f"PIL now references {bitmap.mode} mode bitmaps.") + else: + assert pil_image.getpixel((0, 0)) == pixel_a + + +def test_draw_image_borders(): + # this demonstrates posconv functionality in the form of an embedder test + # see test_page::test_posconv for a more unittest-like example + + pdf = pdfium.PdfDocument(TestFiles.images) + page = pdf[0] + images = list( page.get_objects(filter=[pdfium_c.FPDF_PAGEOBJ_IMAGE]) ) + pdf_qpl = [i.get_quad_points() for i in images] + + bitmap = page.render(scale=1) + posconv = bitmap.get_posconv(page) + pil_image = bitmap.to_pil() + bitmap_qpl = [[posconv.to_bitmap(x, y) for x, y in qps] for qps in pdf_qpl] + + reverse_qpl = [[posconv.to_page(x, y) for x, y in qps] for qps in bitmap_qpl] + for qps_a, qps_b in zip(pdf_qpl, reverse_qpl): + compare_n2(qps_a, qps_b) + + draw = PIL.ImageDraw.Draw(pil_image) + GREEN = (50, 200, 10) + for qps in bitmap_qpl: + draw.polygon(qps, outline=GREEN, width=3) + + pil_image.save(OutputDir/"image_borders.png") diff --git a/tests/test_saving.py b/tests/test_saving.py index cfea22008..e0c010aaf 100644 --- a/tests/test_saving.py +++ b/tests/test_saving.py @@ -1,13 +1,14 @@ -# SPDX-FileCopyrightText: 2024 geisserml +# SPDX-FileCopyrightText: 2025 geisserml # SPDX-License-Identifier: Apache-2.0 OR BSD-3-Clause import io import pytest import pypdfium2 as pdfium -from .conftest import TestResources +import pypdfium2.raw as pdfium_c +from .conftest import TestFiles, OutputDir -def _get_saving_handler(version): +def _new_pdf_saving_handler(version): pdf = pdfium.PdfDocument.new() size = (612, 792) @@ -26,12 +27,12 @@ def _get_saving_handler(version): yield -def _save_tofile(version, tmp_path, use_str): +def _save_to_file(version, tmp_path, use_str): - handler = _get_saving_handler(version) + handler = _new_pdf_saving_handler(version) pdf, kwargs = next(handler) - path = tmp_path / "test_save_tofile.pdf" + path = tmp_path / "test_save_to_file.pdf" dest = str(path) if use_str else path pdf.save(dest, **kwargs) @@ -44,20 +45,17 @@ def _save_tofile(version, tmp_path, use_str): parametrize_saving_version = pytest.mark.parametrize("version", [None, 14, 17]) - -def test_save_to_strpath(tmp_path): - _save_tofile(15, tmp_path, use_str=True) - +def test_save_new_to_strpath(tmp_path): + _save_to_file(15, tmp_path, use_str=True) @parametrize_saving_version -def test_save_to_path(version, tmp_path): - _save_tofile(version, tmp_path, use_str=False) - +def test_save_new_to_path(version, tmp_path): + _save_to_file(version, tmp_path, use_str=False) @parametrize_saving_version -def test_save_tobuffer(version): +def test_save_new_to_buffer(version): - handler = _get_saving_handler(version) + handler = _new_pdf_saving_handler(version) pdf, kwargs = next(handler) out_buffer = io.BytesIO() @@ -69,23 +67,72 @@ def test_save_tobuffer(version): handler.send(saved_pdf) -def test_save_deletion(): - +def test_save_tiled(): + + src_pdf = pdfium.PdfDocument(TestFiles.multipage) + new_pdf_raw = pdfium_c.FPDF_ImportNPagesToOne( + src_pdf.raw, + 595, 842, + 2, 2, + ) + + new_pdf = pdfium.PdfDocument(new_pdf_raw) + assert len(new_pdf) == 1 + page = new_pdf[0] + assert page.get_size() == (595, 842) + + output_file = OutputDir / "tiling.pdf" + new_pdf.save(output_file) + assert output_file.exists() + + +def test_save_with_deletion(): + # Regression test for BUG(96): # Make sure page deletions take effect when saving a document - - pdf = pdfium.PdfDocument(TestResources.multipage) + + pdf = pdfium.PdfDocument(TestFiles.multipage) assert len(pdf) == 3 pdf.del_page(0) assert len(pdf) == 2 - + buffer = io.BytesIO() pdf.save(buffer) buffer.seek(0) - + saved_pdf = pdfium.PdfDocument(buffer, autoclose=True) assert len(saved_pdf) == 2 - + page = saved_pdf[0] textpage = page.get_textpage() assert textpage.get_text_bounded() == "Page\r\n2" + + +def test_save_and_check_id(): # includes deletion, versioned save, and raw data start/end check + + pdf = pdfium.PdfDocument(TestFiles.multipage) + pre_id_p = pdf.get_identifier(pdfium_c.FILEIDTYPE_PERMANENT) + pre_id_c = pdf.get_identifier(pdfium_c.FILEIDTYPE_CHANGING) + assert isinstance(pre_id_p, bytes) + pdf.del_page(1) + + buffer = io.BytesIO() + pdf.save(buffer, version=17) + + buffer.seek(0) + data = buffer.read() + buffer.seek(0) + + exp_start = b"%PDF-1.7" + exp_end = b"%EOF\r\n" + assert data[:len(exp_start)] == exp_start + assert data[-len(exp_end):] == exp_end + + reopened_pdf = pdfium.PdfDocument(buffer, autoclose=True) + assert len(reopened_pdf) == 2 + assert reopened_pdf.get_version() == 17 + + post_id_p = reopened_pdf.get_identifier(pdfium_c.FILEIDTYPE_PERMANENT) + post_id_c = reopened_pdf.get_identifier(pdfium_c.FILEIDTYPE_CHANGING) + assert pre_id_p == post_id_p + assert pre_id_c != post_id_c diff --git a/tests/test_textpage.py b/tests/test_textpage.py index 4bacf49b5..b0e09afb5 100644 --- a/tests/test_textpage.py +++ b/tests/test_textpage.py @@ -1,8 +1,142 @@ -# SPDX-FileCopyrightText: 2024 geisserml +# SPDX-FileCopyrightText: 2025 geisserml # SPDX-License-Identifier: Apache-2.0 OR BSD-3-Clause +import re +import pytest import pypdfium2 as pdfium -from .conftest import TestResources +from .conftest import TestFiles + + +@pytest.fixture +def text_pdf(): + pdf = pdfium.PdfDocument(TestFiles.text) + yield pdf + + +@pytest.fixture +def textpage(text_pdf): + page = text_pdf[0] + textpage = page.get_textpage() + assert isinstance(textpage, pdfium.PdfTextPage) + yield textpage + + +def test_gettext(textpage): + text_a = textpage.get_text_bounded() + text_b = textpage.get_text_range() + assert text_a == text_b + assert len(text_a) == 438 + exp_start = "Lorem ipsum dolor sit amet,\r\n" + exp_end = "\r\nofficia deserunt mollit anim id est laborum." + assert text_a.startswith(exp_start) + assert text_a.endswith(exp_end) + text_start = textpage.get_text_range(0, len(exp_start)) + text_end_a = textpage.get_text_range(len(text_a)-len(exp_end)) # count=-1 + text_end_b = textpage.get_text_range(len(text_a)-len(exp_end), len(exp_end)) + assert text_start == exp_start + assert text_end_a == text_end_b == exp_end + + +@pytest.mark.parametrize("loose", [False, True]) +def test_getcharbox(textpage, loose): + for index in range(textpage.count_chars()): + box = textpage.get_charbox(index, loose=loose) + assert all( isinstance(val, (int, float)) for val in box ) + assert box[0] <= box[2] and box[1] <= box[3] + + +def test_getrectboxes(textpage): + n_rects = textpage.count_rects() + rects = [textpage.get_rect(i) for i in range(n_rects)] + assert len(rects) == 10 + + first_rect = rects[0] + assert pytest.approx(first_rect, abs=1) == (58, 767, 258, 782) + first_text = textpage.get_text_bounded(*first_rect) + assert first_text == "Lorem ipsum dolor sit amet," + assert textpage.get_text_range(0, len(first_text)) == first_text + + for rect in rects: + assert len(rect) == 4 + assert 56 < rect[0] < 59 + text = textpage.get_text_bounded(*rect) + assert isinstance(text, str) + assert len(text) <= 66 + + assert text == "officia deserunt mollit anim id est laborum." + assert textpage.get_text_range(textpage.count_chars()-len(text)) # count=-1 + + +def _get_rects(textpage, search_result): + # TODO add helper? + if search_result is None: + return [] + c_index, c_count = search_result + r_index = textpage.count_rects(0, c_index) - 1 + r_count = textpage.count_rects(c_index, c_count) + textpage.count_rects() + rects = [textpage.get_rect(i) for i in range(r_index, r_index+r_count)] + return rects + + +def test_search_text(textpage): + searcher = textpage.search("labor") + + occ_1a = searcher.get_next() + occ_2a = searcher.get_next() + occ_3a = searcher.get_next() + occ_4x = searcher.get_next() + occ_2b = searcher.get_prev() + occ_1b = searcher.get_prev() + + assert occ_1a == (89, 5) + assert occ_2a == (181, 5) + assert occ_3a == (430, 5) + assert occ_4x is None + assert occ_1a == occ_1b and occ_2a == occ_2b + + occs = (occ_1a, occ_2a, occ_3a) + exp_rectlists = [ + [ (57, 675, 511, 690) ], + [ (58, 638, 537, 653) ], + [ (58, 549, 367, 561) ], + ] + + for occ, exp_rects in zip(occs, exp_rectlists): + rects = _get_rects(textpage, occ) + assert [pytest.approx(r, abs=0.5) for r in rects] == exp_rects + + +def test_get_index(textpage): + + x, y = (60, textpage.page.get_height()-66) + + index = textpage.get_index(x, y, 5, 5) + assert index < textpage.count_chars() and index == 0 + + charbox = textpage.get_charbox(index) + char = textpage.get_text_bounded(*charbox) + assert char == "L" + + +def test_textpage_empty(): + pdf = pdfium.PdfDocument(TestFiles.empty) + page = pdf[0] + textpage = page.get_textpage() + + assert textpage.get_text_bounded() == "" + assert textpage.get_text_range() == "" + assert textpage.count_chars() == 0 + assert textpage.count_rects() == 0 + assert textpage.get_index(0, 0, 0, 0) is None + + searcher = textpage.search("a") + assert searcher.get_next() is None + + with pytest.raises(pdfium.PdfiumError, match=re.escape("Failed to get charbox.")): + textpage.get_charbox(0) + with pytest.raises(ValueError, match=re.escape("Text length must be greater than 0.")): + textpage.search("") def test_get_text_bounded_defaults_with_rotation(): @@ -10,7 +144,7 @@ def test_get_text_bounded_defaults_with_rotation(): # Regression test for BUG(149): # Make sure defaults use native PDF coordinates instead of normalized page size - pdf = pdfium.PdfDocument(TestResources.text) + pdf = pdfium.PdfDocument(TestFiles.text) page = pdf[0] page.set_rotation(90) textpage = page.get_textpage() diff --git a/tests/test_toc.py b/tests/test_toc.py index a1b54a1db..90130ca89 100644 --- a/tests/test_toc.py +++ b/tests/test_toc.py @@ -1,22 +1,31 @@ -# SPDX-FileCopyrightText: 2024 geisserml +# SPDX-FileCopyrightText: 2025 geisserml # SPDX-License-Identifier: Apache-2.0 OR BSD-3-Clause import pytest import logging import pypdfium2 as pdfium import pypdfium2.raw as pdfium_c -from .conftest import TestResources +from .conftest import TestFiles -def _compare_bookmark(bookmark, view_pos, **kwargs): - for name, exp_value in kwargs.items(): - assert exp_value == getattr(bookmark, name) - assert pytest.approx(bookmark.view_pos, abs=1) == view_pos +def _compare_bookmark(bm, **kwargs): + assert isinstance(bm, pdfium.PdfBookmark) + assert kwargs["title"] == bm.get_title() + assert kwargs["count"] == bm.get_count() + dest = bm.get_dest() + if dest is None: + assert kwargs["dest"] is None + else: + assert isinstance(dest, pdfium.PdfDest) + assert kwargs["page_index"] == dest.get_index() + view_mode, view_pos = dest.get_view() + assert kwargs["view_mode"] == view_mode + assert kwargs["view_pos"] == pytest.approx(view_pos, abs=1) def test_gettoc(): - pdf = pdfium.PdfDocument(TestResources.toc) + pdf = pdfium.PdfDocument(TestFiles.toc) toc = pdf.get_toc() # check first bookmark @@ -26,50 +35,43 @@ def test_gettoc(): page_index = 0, view_mode = pdfium_c.PDFDEST_VIEW_XYZ, view_pos = (89, 758, 0), - is_closed = True, - n_kids = 2, + count = -2, ) # check common values - for bookmark in toc: - assert isinstance(bookmark, pdfium.PdfOutlineItem) - assert bookmark.view_mode is pdfium_c.PDFDEST_VIEW_XYZ - assert round(bookmark.view_pos[0]) == 89 + for bm in toc: + dest = bm.get_dest() + view_mode, view_pos = dest.get_view() + assert view_mode == pdfium_c.PDFDEST_VIEW_XYZ + assert round(view_pos[0]) == 89 # check last bookmark _compare_bookmark( - bookmark, + bm, title = "Three-B", page_index = 1, view_mode = pdfium_c.PDFDEST_VIEW_XYZ, view_pos = (89, 657, 0), - is_closed = None, - n_kids = 0, + count = 0, ) def test_gettoc_circular(caplog): - pdf = pdfium.PdfDocument(TestResources.toc_circular) + pdf = pdfium.PdfDocument(TestFiles.toc_circular) toc = pdf.get_toc() _compare_bookmark( next(toc), title = "A Good Beginning", - page_index = None, - view_mode = pdfium_c.PDFDEST_VIEW_UNKNOWN_MODE, - view_pos = [], - is_closed = None, - n_kids = 0, + dest = None, + count = 0, ) _compare_bookmark( next(toc), title = "A Good Ending", - page_index = None, - view_mode = pdfium_c.PDFDEST_VIEW_UNKNOWN_MODE, - view_pos = [], - is_closed = None, - n_kids = 0, + dest = None, + count = 0, ) with caplog.at_level(logging.WARNING): for other in toc: pass diff --git a/tests_old/__init__.py b/tests_old/__init__.py deleted file mode 100644 index b4fb1b2f0..000000000 --- a/tests_old/__init__.py +++ /dev/null @@ -1,2 +0,0 @@ -# SPDX-FileCopyrightText: 2024 geisserml -# SPDX-License-Identifier: Apache-2.0 OR BSD-3-Clause diff --git a/tests_old/conftest.py b/tests_old/conftest.py deleted file mode 100644 index 03eb520a6..000000000 --- a/tests_old/conftest.py +++ /dev/null @@ -1,67 +0,0 @@ -# SPDX-FileCopyrightText: 2024 geisserml -# SPDX-License-Identifier: Apache-2.0 OR BSD-3-Clause - -import sys -from pathlib import Path -# import pypdfium2.__main__ as pdfium_cli - -# if tests/ and tests_old/ are run together as usual, this would initialize logging twice -# pdfium_cli.setup_logging() - -PyVersion = (sys.version_info.major, sys.version_info.minor) - -TestDir = Path(__file__).absolute().parent -ProjectDir = TestDir.parent -ResourceDir = TestDir / "resources" -OutputDir = TestDir / "output" - -class TestFiles: - render = ResourceDir / "render.pdf" - encrypted = ResourceDir / "encrypted.pdf" - multipage = ResourceDir / "multipage.pdf" - toc = ResourceDir / "toc.pdf" - toc_viewmodes = ResourceDir / "toc_viewmodes.pdf" - toc_maxdepth = ResourceDir / "toc_maxdepth.pdf" - toc_circular = ResourceDir / "toc_circular.pdf" - box_fallback = ResourceDir / "box_fallback.pdf" - text = ResourceDir / "text.pdf" - empty = ResourceDir / "empty.pdf" - images = ResourceDir / "images.pdf" - form = ResourceDir / "forms.pdf" - attachments = ResourceDir / "attachments.pdf" - mona_lisa = ResourceDir / "mona_lisa.jpg" - - -ExpRenderPixels = ( - ( (0, 0 ), (255, 255, 255) ), - ( (150, 180), (129, 212, 26 ) ), - ( (150, 390), (42, 96, 153) ), - ( (150, 570), (128, 0, 128) ), -) - - -def iterate_testfiles(skip_encrypted=True): - encrypted = (TestFiles.encrypted, ) - for attr_name in dir(TestFiles): - if attr_name.startswith("_"): - continue - member = getattr(TestFiles, attr_name) - if skip_encrypted and member in encrypted: - continue - yield member - - -def get_members(cls): - members = [] - for attr in dir(cls): - if attr.startswith("_"): - continue - members.append( getattr(cls, attr) ) - return members - - -def test_testpaths(): - for dirpath in (TestDir, ProjectDir, ResourceDir, OutputDir): - assert dirpath.is_dir() - for filepath in iterate_testfiles(False): - assert filepath.is_file() diff --git a/tests_old/output/.gitkeep b/tests_old/output/.gitkeep deleted file mode 100644 index 8d1c8b69c..000000000 --- a/tests_old/output/.gitkeep +++ /dev/null @@ -1 +0,0 @@ - diff --git a/tests_old/resources b/tests_old/resources deleted file mode 120000 index 23a8f00b2..000000000 --- a/tests_old/resources +++ /dev/null @@ -1 +0,0 @@ -../tests/resources/ \ No newline at end of file diff --git a/tests_old/test_page.py b/tests_old/test_page.py deleted file mode 100644 index 9bde27c0a..000000000 --- a/tests_old/test_page.py +++ /dev/null @@ -1,64 +0,0 @@ -# SPDX-FileCopyrightText: 2024 geisserml -# SPDX-License-Identifier: Apache-2.0 OR BSD-3-Clause - -import pytest -import pypdfium2 as pdfium -# import pypdfium2.raw as pdfium_c -from .conftest import TestFiles - - -def test_boxes(): - - pdf = pdfium.PdfDocument(TestFiles.render) - index = 0 - page = pdf[index] - assert page.get_size() == pdf.get_page_size(index) == (595, 842) - assert page.get_mediabox() == (0, 0, 595, 842) - assert isinstance(page, pdfium.PdfPage) - - test_cases = [ - ("media", (0, 0, 612, 792)), - ("media", (0, 0, 595, 842)), - ("crop", (10, 10, 585, 832)), - ("bleed", (20, 20, 575, 822)), - ("trim", (30, 30, 565, 812)), - ("art", (40, 40, 555, 802)), - ] - - for meth_name, exp_box in test_cases: - getattr(page, "set_%sbox" % meth_name)(*exp_box) - box = getattr(page, "get_%sbox" % meth_name)() - assert pytest.approx(box) == exp_box - - -def test_mediabox_fallback(): - pdf = pdfium.PdfDocument(TestFiles.box_fallback) - page = pdf[0] - assert page.get_mediabox() == (0, 0, 612, 792) - - -def test_rotation(): - pdf = pdfium.PdfDocument.new() - page = pdf.new_page(500, 800) - for r in (90, 180, 270, 0): - page.set_rotation(r) - assert page.get_rotation() == r - - -def test_page_labels(): - # incidentally, it happens that this TOC test file also has page labels - pdf = pdfium.PdfDocument(TestFiles.toc_viewmodes) - exp_labels = ["i", "ii", "appendix-C", "appendix-D", "appendix-E", "appendix-F", "appendix-G", "appendix-H"] - assert exp_labels == [pdf.get_page_label(i) for i in range(len(pdf))] - - -# # disabled because flattening takes no effect -# def test_flatten(): - -# pdf = pdfium.PdfDocument(TestFiles.form) -# page = pdf[0] - -# rc = page._flatten() -# assert rc == pdfium_c.FLATTEN_SUCCESS - -# # pdf.save(OutputDir / "flattened.pdf") diff --git a/tests_old/test_pageobject.py b/tests_old/test_pageobject.py deleted file mode 100644 index 27f093ca7..000000000 --- a/tests_old/test_pageobject.py +++ /dev/null @@ -1,246 +0,0 @@ -# SPDX-FileCopyrightText: 2024 geisserml -# SPDX-License-Identifier: Apache-2.0 OR BSD-3-Clause - -import io -import pytest -import PIL.Image -import pypdfium2 as pdfium -import pypdfium2.raw as pdfium_c -from .conftest import TestFiles, OutputDir - - -def test_image_objects(): - pdf = pdfium.PdfDocument(TestFiles.images) - page = pdf[0] - assert page.pdf is pdf - - images = list( page.get_objects(filter=[pdfium_c.FPDF_PAGEOBJ_IMAGE]) ) - assert len(images) == 3 - - obj = images[0] - assert isinstance(obj, pdfium.PdfObject) - assert type(obj) is pdfium.PdfImage - assert obj.type == pdfium_c.FPDF_PAGEOBJ_IMAGE - assert isinstance(obj.raw, pdfium_c.FPDF_PAGEOBJECT) - assert obj.level == 0 - assert obj.page is page - assert obj.pdf is pdf - - positions = [img.get_pos() for img in images] - exp_positions = [ - (133, 459, 350, 550), - (48, 652, 163, 700), - (204, 204, 577, 360), - ] - assert len(positions) == len(exp_positions) - for pos, exp_pos in zip(positions, exp_positions): - assert pytest.approx(pos, abs=1) == exp_pos - - -def test_misc_objects(): - - pdf = pdfium.PdfDocument(TestFiles.render) - page = pdf[0] - assert page.pdf is pdf - - for obj in page.get_objects(): - assert type(obj) is pdfium.PdfObject - assert isinstance(obj.raw, pdfium_c.FPDF_PAGEOBJECT) - assert obj.type in (pdfium_c.FPDF_PAGEOBJ_TEXT, pdfium_c.FPDF_PAGEOBJ_PATH) - assert obj.level == 0 - assert obj.page is page - assert obj.pdf is pdf - pos = obj.get_pos() - assert len(pos) == 4 - - -def test_new_image_from_jpeg(): - - pdf = pdfium.PdfDocument.new() - page = pdf.new_page(240, 120) - - image_a = pdfium.PdfImage.new(pdf) - buffer = open(TestFiles.mona_lisa, "rb") - image_a.load_jpeg(buffer, autoclose=True) - width, height = image_a.get_size() - page.insert_obj(image_a) - - assert len(pdf._data_holder) == 1 - assert pdf._data_closer == [buffer] - - assert image_a.get_matrix() == pdfium.PdfMatrix() - image_a.set_matrix( pdfium.PdfMatrix().scale(width, height) ) - assert image_a.get_matrix() == pdfium.PdfMatrix(width, 0, 0, height, 0, 0) - - pil_image_1 = PIL.Image.open(TestFiles.mona_lisa) - bitmap = image_a.get_bitmap() - pil_image_2 = bitmap.to_pil() - assert (120, 120) == pil_image_1.size == pil_image_2.size == (bitmap.width, bitmap.height) - assert "RGB" == pil_image_1.mode == pil_image_2.mode - - in_data = TestFiles.mona_lisa.read_bytes() - out_buffer = io.BytesIO() - image_a.extract(out_buffer) - out_buffer.seek(0) - out_data = out_buffer.read() - assert in_data == out_data - - metadata = image_a.get_metadata() - assert isinstance(metadata, pdfium_c.FPDF_IMAGEOBJ_METADATA) - assert metadata.bits_per_pixel == 24 # 3 channels, 8 bits each - assert metadata.colorspace == pdfium_c.FPDF_COLORSPACE_DEVICERGB - assert metadata.height == height == 120 - assert metadata.width == width == 120 - assert metadata.horizontal_dpi == 72 - assert metadata.vertical_dpi == 72 - - image_b = pdfium.PdfImage.new(pdf) - with open(TestFiles.mona_lisa, "rb") as buffer: - image_b.load_jpeg(buffer, inline=True, autoclose=False) - - assert image_b.get_matrix() == pdfium.PdfMatrix() - image_b.set_matrix( pdfium.PdfMatrix().scale(width, height).translate(width, 0) ) - image_b.get_matrix() == pdfium.PdfMatrix(width, 0, 0, height, width, 0) - page.insert_obj(image_b) - - page.gen_content() - out_path = OutputDir / "image_jpeg.pdf" - pdf.save(out_path) - assert out_path.exists() - - page._finalizer() - pdf._finalizer() - assert buffer.closed is True - - -def test_new_image_from_bitmap(): - - src_pdf = pdfium.PdfDocument(TestFiles.render) - src_page = src_pdf[0] - dst_pdf = pdfium.PdfDocument.new() - image_a = pdfium.PdfImage.new(dst_pdf) - - bitmap = src_page.render() - w, h = bitmap.width, bitmap.height - image_a.set_bitmap(bitmap) - image_a.set_matrix( pdfium.PdfMatrix().scale(w, h) ) - - pil_image = PIL.Image.open(TestFiles.mona_lisa) - bitmap = pdfium.PdfBitmap.from_pil(pil_image) - image_b = pdfium.PdfImage.new(dst_pdf) - image_b.set_matrix( pdfium.PdfMatrix().scale(bitmap.width, bitmap.height) ) - image_b.set_bitmap(bitmap) - - dst_page = dst_pdf.new_page(w, h) - dst_page.insert_obj(image_a) - dst_page.insert_obj(image_b) - dst_page.gen_content() - - out_path = OutputDir / "image_bitmap.pdf" - dst_pdf.save(out_path) - - reopened_pdf = pdfium.PdfDocument(out_path) - reopened_page = reopened_pdf[0] - reopened_image = next( reopened_page.get_objects(filter=[pdfium_c.FPDF_PAGEOBJ_IMAGE]) ) - assert reopened_image.get_filters() == ["FlateDecode"] - - -def test_replace_image_with_jpeg(): - - pdf = pdfium.PdfDocument(TestFiles.images) - page = pdf[0] - - images = list( page.get_objects(filter=[pdfium_c.FPDF_PAGEOBJ_IMAGE]) ) - matrices = [img.get_matrix() for img in images] - assert len(images) == 3 - image_1 = images[0] - - image_1.load_jpeg(TestFiles.mona_lisa, pages=[page]) - width, height = image_1.get_size() - assert matrices == [img.get_matrix() for img in images] - - # preserve the aspect ratio - # this strategy only works if the matrix was just used for size/position - for image, matrix in zip(images, matrices): - w_scale = matrix.a / width - h_scale = matrix.d / height - scale = min(w_scale, h_scale) - new_matrix = pdfium.PdfMatrix(width*scale, 0, 0, height*scale, matrix.e, matrix.f) - image.set_matrix(new_matrix) - assert image.get_matrix() == new_matrix - - page.gen_content() - output_path = OutputDir / "replace_images.pdf" - pdf.save(output_path) - assert output_path.exists() - - -@pytest.mark.parametrize( - "render", [False, True] -) -def test_image_get_bitmap(render): - - pdf = pdfium.PdfDocument(TestFiles.images) - page = pdf[0] - - all_images = list( page.get_objects(filter=[pdfium_c.FPDF_PAGEOBJ_IMAGE]) ) - image = all_images[0] - - metadata = image.get_metadata() - assert metadata.width == 115 - assert metadata.height == 48 - assert round(metadata.horizontal_dpi) == 38 - assert round(metadata.vertical_dpi) == 38 - assert metadata.colorspace == pdfium_c.FPDF_COLORSPACE_DEVICEGRAY - assert metadata.marked_content_id == 1 - assert metadata.bits_per_pixel == 1 - - bitmap = image.get_bitmap(render=render) - assert isinstance(bitmap, pdfium.PdfBitmap) - - if render: - assert bitmap.format == pdfium_c.FPDFBitmap_BGRA - assert bitmap.n_channels == 4 - # Somewhere between pdfium 6462 and 6899, size/stride expectation changed here - assert bitmap.width == 217 - assert bitmap.height == 91 - assert bitmap.stride == 868 - assert bitmap.rev_byteorder is False - output_path = OutputDir / "extract_rendered.png" - else: - # NOTE fails with pdfium >= 1e1e173 (6015), < b5bc2e9 (6029), which returns RGB - assert bitmap.format == pdfium_c.FPDFBitmap_Gray - assert bitmap.n_channels == 1 - assert bitmap.width == 115 - assert bitmap.height == 48 - assert bitmap.stride == 116 - assert bitmap.rev_byteorder is False - output_path = OutputDir / "extract.png" - - pil_image = bitmap.to_pil() - assert isinstance(pil_image, PIL.Image.Image) - pil_image.save(output_path) - assert output_path.exists() - - -def test_remove_image(): - - pdf = pdfium.PdfDocument(TestFiles.images) - page_1 = pdf[0] - - # TODO order images by position - images = list( page_1.get_objects(filter=[pdfium_c.FPDF_PAGEOBJ_IMAGE]) ) - assert len(images) == 3 - - # drop an image - page_1.remove_obj(images[0]) - - # delete and re-insert an image in place - page_1.remove_obj(images[1]) - page_1.insert_obj(images[1]) - - page_1.gen_content() - - output_path = OutputDir / "test_remove_objects.pdf" - pdf.save(output_path) - assert output_path.exists() diff --git a/tests_old/test_renderer.py b/tests_old/test_renderer.py deleted file mode 100644 index 288b1e6fc..000000000 --- a/tests_old/test_renderer.py +++ /dev/null @@ -1,401 +0,0 @@ -# SPDX-FileCopyrightText: 2024 geisserml -# SPDX-License-Identifier: Apache-2.0 OR BSD-3-Clause - -import math -import numpy -import warnings -import PIL.Image -import pytest -import pypdfium2 as pdfium -import pypdfium2.raw as pdfium_c -from .conftest import ( - TestFiles, - PyVersion, - OutputDir, - ExpRenderPixels -) - -# TODO assert that bitmap and info are consistent - - -@pytest.fixture -def sample_page(): - pdf = pdfium.PdfDocument(TestFiles.render) - page = pdf[0] - yield page - - -@pytest.fixture -def multipage_doc(): - pdf = pdfium.PdfDocument(TestFiles.multipage) - yield pdf - - -def _check_pixels(pil_image, pixels): - for pos, value in pixels: - assert pil_image.getpixel(pos) == value - - -@pytest.mark.parametrize( - ("name", "crop", "scale", "rotation"), - [ - ["01_r0", (0, 0, 0, 0 ), 0.25, 0, ], - ["02_r90", (0, 0, 0, 0 ), 0.5, 90, ], - ["03_r180", (0, 0, 0, 0 ), 0.75, 180, ], - ["04_r270", (0, 0, 0, 0 ), 1, 270, ], - ["05_cl", (100, 0, 0, 0 ), 0.5, 0, ], - ["06_cb", (0, 100, 0, 0 ), 0.5, 0, ], - ["07_cr", (0, 0, 100, 0 ), 0.5, 0, ], - ["08_ct", (0, 0, 0, 100), 0.5, 0, ], - ["09_r90_cb", (0, 100, 0, 0 ), 0.5, 90, ], - ["10_r180_cr", (0, 0, 100, 0 ), 0.5, 180, ], - ["11_r270_ct", (0, 0, 0, 100), 0.5, 270, ], - ] -) -def test_render_page_transform(sample_page, name, crop, scale, rotation): - pil_image = sample_page.render( - crop = crop, - scale = scale, - rotation = rotation, - ).to_pil() - pil_image.save(OutputDir / ("%s.png" % name)) - assert pil_image.mode == "RGB" - - c_left, c_bottom, c_right, c_top = [math.ceil(c*scale) for c in crop] - w = math.ceil(595*scale) - h = math.ceil(842*scale) - if rotation in (90, 270): - w, h = h, w - - c_w = w - c_left - c_right - c_h = h - c_bottom - c_top - assert pil_image.size == (c_w, c_h) - - pixels = [] - for (x, y), value in ExpRenderPixels: - x, y = round(x*scale), round(y*scale) - if rotation in (90, 270): - x, y = y, x - if rotation == 90: - x = w-1 - x - elif rotation == 180: - x = w-1 - x - y = h-1 - y - elif rotation == 270: - y = h-1 - y - x -= c_left - y -= c_top - if 0 <= x < c_w and 0 <= y < c_h: - pixels.append( ((x, y), value) ) - - _check_pixels(pil_image, pixels) - - -@pytest.mark.parametrize( - "rev_byteorder", [False, True] -) -def test_render_page_bgrx(rev_byteorder, sample_page): - pil_image = sample_page.render( - prefer_bgrx = True, - rev_byteorder = rev_byteorder, - ).to_pil() - assert pil_image.mode == "RGBX" - exp_pixels = [(pos, (*value, 255)) for pos, value in ExpRenderPixels] - _check_pixels(pil_image, exp_pixels) - - -def test_render_page_alpha(sample_page): - - pixels = [ - [(0, 0 ), (0, 0, 0, 0 )], - [(62, 66 ), (0, 0, 0, 186)], - [(150, 180), (129, 212, 26, 255)], - [(150, 390), (42, 96, 153, 255)], - [(150, 570), (128, 0, 128, 255)], - ] - kwargs = dict( - fill_color = (0, 0, 0, 0), - ) - image = sample_page.render(**kwargs).to_pil() - image_rev = sample_page.render(**kwargs, rev_byteorder=True).to_pil() - - if PyVersion > (3, 6): - assert image == image_rev - assert image.mode == "RGBA" - assert image.size == (595, 842) - for pos, exp_value in pixels: - assert image.getpixel(pos) == exp_value - - image.save(OutputDir / "colored_alpha.png") - - -def test_render_page_grey(sample_page): - kwargs = dict( - grayscale = True, - scale = 0.5, - ) - image = sample_page.render(**kwargs).to_pil() - image_rev = sample_page.render(**kwargs, rev_byteorder=True).to_pil() - assert image == image_rev - assert image.size == (298, 421) - assert image.mode == "L" - image.save(OutputDir / "grayscale.png") - - -@pytest.mark.parametrize( - "fill_color", - [ - (255, 255, 255, 255), - (60, 70, 80, 100), - (255, 255, 255, 255), - (0, 255, 255, 255), - (255, 0, 255, 255), - (255, 255, 0, 255), - ] -) -def test_render_page_fill_color(fill_color, sample_page): - kwargs = dict( - fill_color = fill_color, - scale = 0.5, - ) - image = sample_page.render(**kwargs).to_pil() - image_rev = sample_page.render(**kwargs, rev_byteorder=True).to_pil() - - if PyVersion > (3, 6): - assert image == image_rev - - bg_pixel = image.getpixel( (0, 0) ) - if fill_color[3] == 255: - fill_color = fill_color[:-1] - assert image.size == (298, 421) - assert bg_pixel == fill_color - - -def test_render_page_colorscheme(): - pdf = pdfium.PdfDocument(TestFiles.text) - page = pdf[0] - color_scheme = pdfium.PdfColorScheme( - path_fill = (15, 15, 15, 255), - path_stroke = (255, 255, 255, 255), - text_fill = (255, 255, 255, 255), - text_stroke = (255, 255, 255, 255), - ) - image = page.render( - grayscale = True, - fill_color = (0, 0, 0, 255), - color_scheme = color_scheme, - ).to_pil() - assert image.mode == "L" - image.save(OutputDir / "render_colorscheme.png") - - -@pytest.mark.parametrize( - "rev_byteorder", [False, True] -) -def test_render_page_tonumpy(rev_byteorder, sample_page): - - bitmap = sample_page.render( - rev_byteorder = rev_byteorder, - ) - info, array = bitmap.get_info(), bitmap.to_numpy() - assert isinstance(array, numpy.ndarray) - assert isinstance(info, pdfium.PdfBitmapInfo) - if rev_byteorder: - assert info.mode == "RGB" - else: - assert info.mode == "BGR" - - for (x, y), value in ExpRenderPixels: - if rev_byteorder: - assert tuple(array[y][x]) == value - else: - assert tuple(array[y][x]) == tuple(reversed(value)) - - -@pytest.mark.parametrize("mode", [None, "lcd", "print"]) -def test_render_page_optimization(sample_page, mode): - pil_image = sample_page.render( - optimize_mode = mode, - scale = 0.5, - ).to_pil() - assert isinstance(pil_image, PIL.Image.Image) - - -def test_render_page_noantialias(sample_page): - pil_image = sample_page.render( - no_smoothtext = True, - no_smoothimage = True, - no_smoothpath = True, - scale = 0.5, - ).to_pil() - assert isinstance(pil_image, PIL.Image.Image) - - -def test_render_pages_no_concurrency(multipage_doc): - for page in multipage_doc: - image = page.render( - scale = 0.5, - grayscale = True, - ).to_pil() - assert isinstance(image, PIL.Image.Image) - - -@pytest.fixture -def render_pdffile_topil(multipage_doc): - - renderer = multipage_doc.render( - pdfium.PdfBitmap.to_pil, - scale = 0.5, - ) - imgs = [] - - for image in renderer: - assert isinstance(image, PIL.Image.Image) - assert image.mode == "RGB" - imgs.append(image) - - assert len(imgs) == 3 - yield imgs - - -@pytest.fixture -def render_pdffile_tonumpy(multipage_doc): - - renderer = multipage_doc.render( - pdfium.PdfBitmap.to_numpy, - scale = 0.5, - rev_byteorder = True, - pass_info = True, - ) - imgs = [] - - for array, info in renderer: - assert info.mode == "RGB" - assert isinstance(array, numpy.ndarray) - pil_image = PIL.Image.fromarray(array, mode=info.mode) - imgs.append(pil_image) - - # for i, img in enumerate(imgs): - # img.save(OutputDir / ("numpy_%s.png" % i)) - - assert len(imgs) == 3 - yield imgs - - -def test_render_pdffile(render_pdffile_topil, render_pdffile_tonumpy): - for a, b in zip(render_pdffile_topil, render_pdffile_tonumpy): - assert a == b - - -def test_render_pdf_new(): - - # two pages to actually reach the process pool and not just the single-page shortcut - pdf = pdfium.PdfDocument.new() - page_1 = pdf.new_page(50, 100) - page_2 = pdf.new_page(50, 100) - renderer = pdf.render(pdfium.PdfBitmap.to_pil) - bitmap_p1 = next(renderer) - - -def test_render_pdfbuffer(): - - buffer = open(TestFiles.multipage, "rb") - pdf = pdfium.PdfDocument(buffer) - - renderer = pdf.render(pdfium.PdfBitmap.to_pil) - bitmap_p1 = next(renderer) - - -@pytest.mark.parametrize( - ("with_forms", "exp_color"), - [ - (False, (255, 255, 255)), - (True, (0, 51, 113)), - ] -) -def test_render_form(with_forms, exp_color): - - pdf = pdfium.PdfDocument(TestFiles.form) - if with_forms: - pdf.init_forms() - - if with_forms: - assert isinstance(pdf.formenv, pdfium.PdfFormEnv) - else: - assert pdf.formenv is None - - page = pdf[0] - image = page.render( - may_draw_forms = with_forms, - ).to_pil() - - assert image.getpixel( (190, 190) ) == exp_color - assert image.getpixel( (190, 430) ) == exp_color - assert image.getpixel( (190, 480) ) == exp_color - - -def test_numpy_nocopy(sample_page): - bitmap = sample_page.render(scale=0.1) - array = bitmap.to_numpy() - assert (bitmap.width, bitmap.height) == (60, 85) - val_a, val_b = 255, 123 - assert array[0][0][0] == val_a - bitmap.buffer[0] = val_b - assert array[0][0][0] == val_b - array[0][0][0] = val_a - assert bitmap.buffer[0] == val_a - - -@pytest.mark.parametrize( - ("bitmap_format", "rev_byteorder", "is_referenced"), - [ - (pdfium_c.FPDFBitmap_BGR, False, False), - (pdfium_c.FPDFBitmap_BGR, True, False), - (pdfium_c.FPDFBitmap_BGRA, False, False), - (pdfium_c.FPDFBitmap_BGRA, True, True), - (pdfium_c.FPDFBitmap_BGRx, False, False), - (pdfium_c.FPDFBitmap_BGRx, True, True), - (pdfium_c.FPDFBitmap_Gray, False, True), - ] -) -def test_pil_nocopy_where_possible(bitmap_format, rev_byteorder, is_referenced, sample_page): - - bitmap = sample_page.render( - scale = 0.1, - rev_byteorder = rev_byteorder, - force_bitmap_format = bitmap_format, - ) - pil_image = bitmap.to_pil() - assert pil_image.size == (60, 85) - - val_a, val_b = 255, 123 - if bitmap.n_channels == 4: - pixel_a = (val_a, 255, 255, 255) - pixel_b = (val_b, 255, 255, 255) - elif bitmap.n_channels == 3: - pixel_a = (val_a, 255, 255) - pixel_b = (val_b, 255, 255) - elif bitmap.n_channels == 1: - pixel_a = val_a - pixel_b = val_b - else: - assert False - - assert pil_image.getpixel((0, 0)) == pixel_a - bitmap.buffer[0] = val_b - - if is_referenced: - - # changes to the buffer are reflected in the image - assert pil_image.getpixel((0, 0)) == pixel_b - - # changes to the image are reflected in the buffer, since we set `.readonly = False` on after image init - pil_image.putpixel((0, 0), pixel_a) - assert pil_image.getpixel((0, 0)) == pixel_a - assert bitmap.buffer[0] == val_a - - else: - if pil_image.getpixel((0, 0)) == pixel_b: - warnings.warn(f"PIL now references {bitmap.mode} mode bitmaps.") - else: - assert pil_image.getpixel((0, 0)) == pixel_a diff --git a/tests_old/test_saver.py b/tests_old/test_saver.py deleted file mode 100644 index 331aa7918..000000000 --- a/tests_old/test_saver.py +++ /dev/null @@ -1,55 +0,0 @@ -# SPDX-FileCopyrightText: 2024 geisserml -# SPDX-License-Identifier: Apache-2.0 OR BSD-3-Clause - -import io -import pypdfium2 as pdfium -import pypdfium2.raw as pdfium_c -from .conftest import TestFiles, OutputDir - - -def test_save(): - - src_pdf = pdfium.PdfDocument(TestFiles.multipage) - new_pdf_raw = pdfium_c.FPDF_ImportNPagesToOne( - src_pdf.raw, - 595, 842, - 2, 2, - ) - - new_pdf = pdfium.PdfDocument(new_pdf_raw) - assert len(new_pdf) == 1 - page = new_pdf[0] - assert page.get_size() == (595, 842) - - output_file = OutputDir / "tiling.pdf" - new_pdf.save(output_file) - assert output_file.exists() - - -def test_save_withversion(): - - pdf = pdfium.PdfDocument(TestFiles.multipage) - pre_id_p = pdf.get_identifier(pdfium_c.FILEIDTYPE_PERMANENT) - pre_id_c = pdf.get_identifier(pdfium_c.FILEIDTYPE_CHANGING) - assert isinstance(pre_id_p, bytes) - pdf.del_page(1) - - buffer = io.BytesIO() - pdf.save(buffer, version=17) - - buffer.seek(0) - data = buffer.read() - buffer.seek(0) - - exp_start = b"%PDF-1.7" - exp_end = b"%EOF\r\n" - assert data[:len(exp_start)] == exp_start - assert data[-len(exp_end):] == exp_end - - reopened_pdf = pdfium.PdfDocument(buffer, autoclose=True) - assert len(reopened_pdf) == 2 - - post_id_p = reopened_pdf.get_identifier(pdfium_c.FILEIDTYPE_PERMANENT) - post_id_c = reopened_pdf.get_identifier(pdfium_c.FILEIDTYPE_CHANGING) - assert pre_id_p == post_id_p - assert pre_id_c != post_id_c diff --git a/tests_old/test_text.py b/tests_old/test_text.py deleted file mode 100644 index cb9bf086b..000000000 --- a/tests_old/test_text.py +++ /dev/null @@ -1,146 +0,0 @@ -# SPDX-FileCopyrightText: 2024 geisserml -# SPDX-License-Identifier: Apache-2.0 OR BSD-3-Clause - -import re -import pytest -import pypdfium2 as pdfium -from .conftest import TestFiles - - -@pytest.fixture -def text_pdf(): - pdf = pdfium.PdfDocument(TestFiles.text) - yield pdf - - -@pytest.fixture -def textpage(text_pdf): - page = text_pdf[0] - textpage = page.get_textpage() - assert isinstance(textpage, pdfium.PdfTextPage) - yield textpage - - -@pytest.fixture -def linkpage(text_pdf): - page = text_pdf[1] - linkpage = page.get_textpage() - yield linkpage - - -def test_gettext(textpage): - text_a = textpage.get_text_bounded() - text_b = textpage.get_text_range() - assert text_a == text_b - assert len(text_a) == 438 - exp_start = "Lorem ipsum dolor sit amet,\r\n" - exp_end = "\r\nofficia deserunt mollit anim id est laborum." - assert text_a.startswith(exp_start) - assert text_a.endswith(exp_end) - text_start = textpage.get_text_range(0, len(exp_start)) - text_end_a = textpage.get_text_range(len(text_a)-len(exp_end)) # count=-1 - text_end_b = textpage.get_text_range(len(text_a)-len(exp_end), len(exp_end)) - assert text_start == exp_start - assert text_end_a == text_end_b == exp_end - - -@pytest.mark.parametrize("loose", [False, True]) -def test_getcharbox(textpage, loose): - for index in range(textpage.count_chars()): - box = textpage.get_charbox(index, loose=loose) - assert all( isinstance(val, (int, float)) for val in box ) - assert box[0] <= box[2] and box[1] <= box[3] - - -def test_getrectboxes(textpage): - n_rects = textpage.count_rects() - rects = [textpage.get_rect(i) for i in range(n_rects)] - assert len(rects) == 10 - - first_rect = rects[0] - assert pytest.approx(first_rect, abs=1) == (58, 767, 258, 782) - first_text = textpage.get_text_bounded(*first_rect) - assert first_text == "Lorem ipsum dolor sit amet," - assert textpage.get_text_range(0, len(first_text)) == first_text - - for rect in rects: - assert len(rect) == 4 - assert 56 < rect[0] < 59 - text = textpage.get_text_bounded(*rect) - assert isinstance(text, str) - assert len(text) <= 66 - - assert text == "officia deserunt mollit anim id est laborum." - assert textpage.get_text_range(textpage.count_chars()-len(text)) # count=-1 - - -def _get_rects(textpage, search_result): - # TODO add helper? - if search_result is None: - return [] - c_index, c_count = search_result - r_index = textpage.count_rects(0, c_index) - 1 - r_count = textpage.count_rects(c_index, c_count) - textpage.count_rects() - rects = [textpage.get_rect(i) for i in range(r_index, r_index+r_count)] - return rects - - -def test_search_text(textpage): - searcher = textpage.search("labor") - - occ_1a = searcher.get_next() - occ_2a = searcher.get_next() - occ_3a = searcher.get_next() - occ_4x = searcher.get_next() - occ_2b = searcher.get_prev() - occ_1b = searcher.get_prev() - - assert occ_1a == (89, 5) - assert occ_2a == (181, 5) - assert occ_3a == (430, 5) - assert occ_4x is None - assert occ_1a == occ_1b and occ_2a == occ_2b - - occs = (occ_1a, occ_2a, occ_3a) - exp_rectlists = [ - [ (57, 675, 511, 690) ], - [ (58, 638, 537, 653) ], - [ (58, 549, 367, 561) ], - ] - - for occ, exp_rects in zip(occs, exp_rectlists): - rects = _get_rects(textpage, occ) - assert [pytest.approx(r, abs=0.5) for r in rects] == exp_rects - - -def test_get_index(textpage): - - x, y = (60, textpage.page.get_height()-66) - - index = textpage.get_index(x, y, 5, 5) - assert index < textpage.count_chars() and index == 0 - - charbox = textpage.get_charbox(index) - char = textpage.get_text_bounded(*charbox) - assert char == "L" - - -def test_textpage_empty(): - pdf = pdfium.PdfDocument(TestFiles.empty) - page = pdf[0] - textpage = page.get_textpage() - - assert textpage.get_text_bounded() == "" - assert textpage.get_text_range() == "" - assert textpage.count_chars() == 0 - assert textpage.count_rects() == 0 - assert textpage.get_index(0, 0, 0, 0) is None - - searcher = textpage.search("a") - assert searcher.get_next() is None - - with pytest.raises(pdfium.PdfiumError, match=re.escape("Failed to get charbox.")): - textpage.get_charbox(0) - with pytest.raises(ValueError, match=re.escape("Text length must be greater than 0.")): - textpage.search("")