From 5af1e7262d704ec79cc40ecc2f61bcd8d1dd02d7 Mon Sep 17 00:00:00 2001 From: Daniel Baumann Date: Sun, 19 Jun 2022 09:06:23 +0200 Subject: Merging upstream version 0.2.0. Signed-off-by: Daniel Baumann --- .github/FUNDING.yml | 1 + .github/workflows/doconfly.yml | 29 +++++++++++++++++++++ .github/workflows/tests.yml | 35 ++++++++++++++++++++++++++ .gitignore | 4 +++ PKG-INFO | 57 ------------------------------------------ README.rst | 2 +- docs/changelog.rst | 43 +++++++++++++++++++++++++++++++ docs/conf.py | 6 ----- docs/going_further.rst | 21 ++++++++-------- pydyf/__init__.py | 18 ++++++++++--- pyproject.toml | 8 +++--- setup.py | 31 ----------------------- tests/test_pydyf.py | 20 +++++++++++++-- 13 files changed, 159 insertions(+), 116 deletions(-) create mode 100644 .github/FUNDING.yml create mode 100644 .github/workflows/doconfly.yml create mode 100644 .github/workflows/tests.yml create mode 100644 .gitignore delete mode 100644 PKG-INFO delete mode 100644 setup.py diff --git a/.github/FUNDING.yml b/.github/FUNDING.yml new file mode 100644 index 0000000..709eb98 --- /dev/null +++ b/.github/FUNDING.yml @@ -0,0 +1 @@ +open_collective: courtbouillon diff --git a/.github/workflows/doconfly.yml b/.github/workflows/doconfly.yml new file mode 100644 index 0000000..cf2c1ca --- /dev/null +++ b/.github/workflows/doconfly.yml @@ -0,0 +1,29 @@ +name: doconfly +on: + push: + branches: + - master + tags: + - "*" + +jobs: + doconfly: + name: doconfly job + runs-on: ubuntu-latest + env: + PORT: ${{ secrets.PORT }} + SSH_PRIVATE_KEY: ${{ secrets.SSH_PRIVATE_KEY }} + TAKOYAKI: ${{ secrets.TAKOYAKI }} + USER: ${{ secrets.USER }} + DOCUMENTATION_PATH: ${{ secrets.DOCUMENTATION_PATH }} + DOCUMENTATION_URL: ${{ secrets.DOCUMENTATION_URL }} + steps: + - run: | + which ssh-agent || ( apt-get update -y && apt-get install openssh-client -y ) + eval $(ssh-agent -s) + echo "$SSH_PRIVATE_KEY" | tr -d '\r' | ssh-add - + mkdir -p ~/.ssh + chmod 700 ~/.ssh + ssh-keyscan -p $PORT $TAKOYAKI >> ~/.ssh/known_hosts + chmod 644 ~/.ssh/known_hosts + ssh $USER@$TAKOYAKI -p $PORT "doconfly/doconfly.sh $GITHUB_REPOSITORY $GITHUB_REF $DOCUMENTATION_PATH $DOCUMENTATION_URL" diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml new file mode 100644 index 0000000..b69256e --- /dev/null +++ b/.github/workflows/tests.yml @@ -0,0 +1,35 @@ +name: pydyf's tests +on: [push, pull_request] + +jobs: + tests: + name: ${{ matrix.os }} - ${{ matrix.python-version }} + runs-on: ${{ matrix.os }} + strategy: + fail-fast: false + matrix: + os: [ubuntu-latest, macos-latest, windows-latest] + python-version: ['3.7', '3.8', '3.9', '3.10', 'pypy-3.8'] + steps: + - uses: actions/checkout@v2 + - uses: actions/setup-python@v2 + with: + python-version: ${{ matrix.python-version }} + - name: Install Ghostscript (Ubuntu) + if: matrix.os == 'ubuntu-latest' + run: sudo apt-get update -y && sudo apt-get install ghostscript -y + - name: Install Ghostscript (macOS) + if: matrix.os == 'macos-latest' + run: brew install ghostscript + - name: Install Ghostscript (Windows) + if: matrix.os == 'windows-latest' + run: | + C:\msys64\usr\bin\bash -lc 'pacman -S mingw-w64-x86_64-ghostscript --noconfirm' + echo "C:\msys64\mingw64\bin" | Out-File -FilePath $env:GITHUB_PATH + rm C:\msys64\mingw64\bin\python.exe + - name: Upgrade pip and setuptools + run: python -m pip install --upgrade pip setuptools + - name: Install tests’ requirements + run: python -m pip install .[test] + - name: Launch tests + run: python -m pytest diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..95a1136 --- /dev/null +++ b/.gitignore @@ -0,0 +1,4 @@ +/docs/_build +/tests/results +__pycache__ +.coverage diff --git a/PKG-INFO b/PKG-INFO deleted file mode 100644 index d6c6777..0000000 --- a/PKG-INFO +++ /dev/null @@ -1,57 +0,0 @@ -Metadata-Version: 2.1 -Name: pydyf -Version: 0.1.2 -Summary: A low-level PDF generator. -Keywords: pdf,generator -Author-email: CourtBouillon -Maintainer-email: CourtBouillon -Requires-Python: >=3.6 -Description-Content-Type: text/x-rst -Classifier: Development Status :: 4 - Beta -Classifier: Intended Audience :: Developers -Classifier: License :: OSI Approved :: BSD License -Classifier: Operating System :: OS Independent -Classifier: Programming Language :: Python -Classifier: Programming Language :: Python :: 3 -Classifier: Programming Language :: Python :: 3 :: Only -Classifier: Programming Language :: Python :: 3.6 -Classifier: Programming Language :: Python :: 3.7 -Classifier: Programming Language :: Python :: 3.8 -Classifier: Programming Language :: Python :: 3.9 -Classifier: Programming Language :: Python :: Implementation :: CPython -Classifier: Programming Language :: Python :: Implementation :: PyPy -Requires-Dist: sphinx ; extra == "doc" -Requires-Dist: sphinx_rtd_theme ; extra == "doc" -Requires-Dist: pytest ; extra == "test" -Requires-Dist: pytest-cov ; extra == "test" -Requires-Dist: pytest-flake8 ; extra == "test" -Requires-Dist: pytest-isort ; extra == "test" -Requires-Dist: coverage[toml] ; extra == "test" -Requires-Dist: pillow ; extra == "test" -Project-URL: Changelog, https://github.com/CourtBouillon/pydyf/releases -Project-URL: Code, https://github.com/CourtBouillon/pydyf -Project-URL: Documentation, https://doc.courtbouillon.org/pydyf/ -Project-URL: Donation, https://opencollective.com/courtbouillon -Project-URL: Homepage, https://www.courtbouillon.org/pydyf -Project-URL: Issues, https://github.com/CourtBouillon/pydyf/issues -Provides-Extra: doc -Provides-Extra: test - -pydyf is a low-level PDF generator written in Python and based on PDF -specification 1.7. - -* Free software: BSD license -* For Python 3.6+, tested on CPython and PyPy -* Documentation: https://doc.courtbouillon.org/pydyf -* Changelog: https://github.com/CourtBouillon/pydyf/releases -* Code, issues, tests: https://github.com/CourtBouillon/pydyf -* Code of conduct: https://www.courtbouillon.org/code-of-conduct -* Professional support: https://www.courtbouillon.org -* Donation: https://opencollective.com/courtbouillon - -Copyrights are retained by their contributors, no copyright assignment is -required to contribute to pydyf. Unless explicitly stated otherwise, any -contribution intentionally submitted for inclusion is licensed under the BSD -3-clause license, without any additional terms or conditions. For full -authorship information, see the version control history. - diff --git a/README.rst b/README.rst index 6c37a4a..f76eccb 100644 --- a/README.rst +++ b/README.rst @@ -2,7 +2,7 @@ pydyf is a low-level PDF generator written in Python and based on PDF specification 1.7. * Free software: BSD license -* For Python 3.6+, tested on CPython and PyPy +* For Python 3.7+, tested on CPython and PyPy * Documentation: https://doc.courtbouillon.org/pydyf * Changelog: https://github.com/CourtBouillon/pydyf/releases * Code, issues, tests: https://github.com/CourtBouillon/pydyf diff --git a/docs/changelog.rst b/docs/changelog.rst index acbf3c3..5243edc 100644 --- a/docs/changelog.rst +++ b/docs/changelog.rst @@ -2,6 +2,49 @@ Changelog ========= +Version 0.2.0 +------------- + +Released on 2022-05-23. + +Dependencies: + +* Python 3.7+ is now needed, Python 3.6 is not supported anymore + + New features: + +* `d0be36b `_: + Allow to set PDF version +* `879261c `_: + Allow to set PDF identifier + +Contributors: + +* Guillaume Ayoub + +Backers and sponsors: + +* Grip Angebotssoftware +* Manuel Barkhau +* Crisp BV +* SimonSoft +* Menutech +* Spacinov +* KontextWork +* René Fritz +* Kobalt +* NCC Group +* Des images et des mots +* Nathalie Gutton +* Andreas Zettl +* Tom Pohl +* Moritz Mahringer +* Florian Demmer +* Yanal-Yvez Fargialla +* Gábor +* Piotr Horzycki + + Version 0.1.2 ------------- diff --git a/docs/conf.py b/docs/conf.py index 585afd0..a08c175 100644 --- a/docs/conf.py +++ b/docs/conf.py @@ -1,13 +1,7 @@ # pydyf documentation build configuration file. -import sys -from pathlib import Path - import pydyf -# Add current path for css_diagram_role -sys.path.append(str(Path(__file__).parent)) - # Add any Sphinx extension module names here, as strings. They can be # extensions coming with Sphinx (named 'sphinx.ext.*') or your custom ones. extensions = [ diff --git a/docs/going_further.rst b/docs/going_further.rst index ce9a8f3..c66f6c2 100644 --- a/docs/going_further.rst +++ b/docs/going_further.rst @@ -5,12 +5,11 @@ Going Further Why pydyf? ------------- -pydyf has been created to replace Cairo PDF generation in WeasyPrint_. +pydyf has been created to replace Cairo_ PDF generation in WeasyPrint_. -Indeed, there are some bugs in WeasyPrint caused by Cairo_ and Cairo has some -difficulties to make releases. -Also there are features which will be easier to implement while having more -control on the PDF generation. +Indeed, there were some bugs in WeasyPrint caused by Cairo, and new versions of +Cairo can take a long time to be released. There are also many features that +are easier to implement with more control on the PDF generation. So we created pydyf. @@ -20,13 +19,13 @@ So we created pydyf. Why Python? ----------- -Python is a really good language to design a small, OS-agnostic parser. As it -is object-oriented, it gives the possibility to follow the specification with -high-level classes and a small amount of very simple code. +Python is a really good language to design a small, OS-agnostic library. As it +is object-oriented, it gives the possibility to follow the PDF specification +with high-level classes and a small amount of very simple code. And of course, WeasyPrint is written in Python too, giving an obvious reason for this choice. -Speed is not pydyf’s main goal. Code simplicity, maintainability and -flexibility are more important goals for this library, as they give the -ability to stay really close to the specification and to fix bugs easily. +Speed is not pydyf’s main goal. Code simplicity, maintainability and +flexibility are more important goals for this library, as they give the ability +to stay really close to the specification and to fix bugs easily. diff --git a/pydyf/__init__.py b/pydyf/__init__.py index 05dccf6..a627a13 100755 --- a/pydyf/__init__.py +++ b/pydyf/__init__.py @@ -6,8 +6,9 @@ A low-level PDF generator. import re import zlib from codecs import BOM_UTF16_BE +from hashlib import md5 -VERSION = __version__ = '0.1.2' +VERSION = __version__ = '0.2.0' def _to_bytes(item): @@ -425,7 +426,7 @@ class PDF: }) self.add_object(self.pages) - #: PDF :class:`Dictionary` containing the PDF’s metadata. + #: PDF :class:`Dictionary` containing the PDF’s metadata. self.info = Dictionary({}) self.add_object(self.info) @@ -469,15 +470,17 @@ class PDF: self.current_position += len(content) + 1 output.write(content + b'\n') - def write(self, output): + def write(self, output, version=b'1.7', identifier=None): """Write PDF to output. :param output: Output stream. :type output: binary :term:`file object` + :param bytes version: PDF version. + :param bytes identifier: PDF file identifier. """ # Write header - self.write_line(b'%PDF-1.7', output) + self.write_line(b'%PDF-' + version, output) self.write_line(b'%\xf0\x9f\x96\xa4', output) # Write all non-free PDF objects @@ -502,6 +505,13 @@ class PDF: self.write_line(f'/Size {len(self.objects)}'.encode(), output) self.write_line(b'/Root ' + self.catalog.reference, output) self.write_line(b'/Info ' + self.info.reference, output) + if identifier is not None: + data = b''.join( + obj.data for obj in self.objects if obj.free != 'f') + data_hash = md5(data).hexdigest().encode() + self.write_line( + b'/ID [' + String(identifier).data + b' ' + + String(data_hash).data + b']', output) self.write_line(b'>>', output) self.write_line(b'startxref', output) self.write_line(f'{self.xref_position}'.encode(), output) diff --git a/pyproject.toml b/pyproject.toml index 5a88369..a89cea0 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -8,7 +8,7 @@ description = 'A low-level PDF generator.' keywords = ['pdf', 'generator'] authors = [{name = 'CourtBouillon', email = 'contact@courtbouillon.org'}] maintainers = [{name = 'CourtBouillon', email = 'contact@courtbouillon.org'}] -requires-python = '>=3.6' +requires-python = '>=3.7' readme = {file = 'README.rst', content-type = 'text/x-rst'} license = {file = 'LICENSE'} classifiers = [ @@ -19,10 +19,10 @@ classifiers = [ 'Programming Language :: Python', 'Programming Language :: Python :: 3', 'Programming Language :: Python :: 3 :: Only', - 'Programming Language :: Python :: 3.6', 'Programming Language :: Python :: 3.7', 'Programming Language :: Python :: 3.8', 'Programming Language :: Python :: 3.9', + 'Programming Language :: Python :: 3.10', 'Programming Language :: Python :: Implementation :: CPython', 'Programming Language :: Python :: Implementation :: PyPy', ] @@ -38,13 +38,13 @@ Donation = 'https://opencollective.com/courtbouillon' [project.optional-dependencies] doc = ['sphinx', 'sphinx_rtd_theme'] -test = ['pytest', 'pytest-cov', 'pytest-flake8', 'pytest-isort', 'coverage[toml]', 'pillow'] +test = ['pytest', 'pytest-xdist', 'pytest-flake8', 'pytest-isort', 'pytest-cov', 'coverage[toml]', 'pillow'] [tool.flit.sdist] exclude = ['.*'] [tool.pytest.ini_options] -addopts = '--isort --flake8 --cov --no-cov-on-fail' +addopts = '--isort --flake8 --numprocesses=auto' [tool.coverage.run] branch = true diff --git a/setup.py b/setup.py deleted file mode 100644 index 40b63c0..0000000 --- a/setup.py +++ /dev/null @@ -1,31 +0,0 @@ -#!/usr/bin/env python -# setup.py generated by flit for tools that don't yet use PEP 517 - -from distutils.core import setup - -packages = \ -['pydyf'] - -package_data = \ -{'': ['*']} - -extras_require = \ -{'doc': ['sphinx', 'sphinx_rtd_theme'], - 'test': ['pytest', - 'pytest-cov', - 'pytest-flake8', - 'pytest-isort', - 'coverage[toml]', - 'pillow']} - -setup(name='pydyf', - version='0.1.2', - description='A low-level PDF generator.', - author=None, - author_email='CourtBouillon ', - url=None, - packages=packages, - package_data=package_data, - extras_require=extras_require, - python_requires='>=3.6', - ) diff --git a/tests/test_pydyf.py b/tests/test_pydyf.py index 1e758a5..83c260c 100644 --- a/tests/test_pydyf.py +++ b/tests/test_pydyf.py @@ -1,3 +1,5 @@ +import io + import pydyf from . import assert_pixels @@ -283,10 +285,10 @@ def test_transform(): document = pydyf.PDF() draw = pydyf.Stream() + draw.transform(1, 0, 0, 1, 1, 1) draw.move_to(2, 2) draw.set_line_width(2) draw.line_to(2, 5) - draw.transform(1, 0, 0, 1, 1, 1) draw.stroke() document.add_object(draw) @@ -694,7 +696,7 @@ def test_text(): KKKKKKKKKK KKKKKKKKKK KKKKKKKKKK - __________ + zzzzzzzzzz __________ __________ __________ @@ -702,6 +704,20 @@ def test_text(): ''') +def test_identifier(): + document = pydyf.PDF() + pdf = io.BytesIO() + document.write(pdf, identifier=b'abc') + assert b'abc' in pdf.getvalue() + + +def test_version(): + document = pydyf.PDF() + pdf = io.BytesIO() + document.write(pdf, version=b'2.0') + assert b'2.0' in pdf.getvalue() + + def test_string_encoding(): assert pydyf.String('abc').data == b'(abc)' assert pydyf.String('déf').data == b'' -- cgit v1.2.3