summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--.github/workflows/doconfly.yml2
-rw-r--r--.github/workflows/tests.yml8
-rw-r--r--docs/changelog.rst82
-rw-r--r--docs/common_use_cases.rst84
-rw-r--r--docs/conf.py3
-rwxr-xr-xpydyf/__init__.py53
-rw-r--r--pyproject.toml2
7 files changed, 222 insertions, 12 deletions
diff --git a/.github/workflows/doconfly.yml b/.github/workflows/doconfly.yml
index a720a66..c256435 100644
--- a/.github/workflows/doconfly.yml
+++ b/.github/workflows/doconfly.yml
@@ -2,7 +2,7 @@ name: doconfly
on:
push:
branches:
- - master
+ - main
tags:
- "*"
diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml
index 89003af..8de9881 100644
--- a/.github/workflows/tests.yml
+++ b/.github/workflows/tests.yml
@@ -8,15 +8,15 @@ jobs:
strategy:
matrix:
os: [ubuntu-latest, macos-latest, windows-latest]
- python-version: ['3.11']
+ python-version: ['3.12']
include:
- os: ubuntu-latest
- python-version: '3.7'
+ python-version: '3.8'
- os: ubuntu-latest
python-version: 'pypy-3.8'
steps:
- - uses: actions/checkout@v3
- - uses: actions/setup-python@v2
+ - uses: actions/checkout@v4
+ - uses: actions/setup-python@v5
with:
python-version: ${{ matrix.python-version }}
- name: Install Ghostscript (Ubuntu)
diff --git a/docs/changelog.rst b/docs/changelog.rst
index e116ac8..661a696 100644
--- a/docs/changelog.rst
+++ b/docs/changelog.rst
@@ -2,6 +2,88 @@ Changelog
=========
+Version 0.9.0
+-------------
+
+Released on 2024-02-26.
+
+Dependencies:
+
+* Python 3.12 is supported and tested
+* Python 3.8+ is now needed, Python 3.7 is not supported anymore
+
+New features:
+
+* Add inline images support
+
+Performance:
+
+* Simplify `_to_bytes()`
+
+Documentation:
+
+* Add sample to create a PDF with metadata
+
+Contributors:
+
+* Panagiotis H.M. Issaris
+* Guillaume Ayoub
+* Lucie Anglade
+
+Backers and sponsors:
+
+* Spacinov
+* Kobalt
+* Grip Angebotssoftware
+* Manuel Barkhau
+* SimonSoft
+* Menutech
+* KontextWork
+* René Fritz
+* Simon Sapin
+* Arcanite
+* TrainingSparkle
+* Healthchecks.io
+* Hammerbacher
+* Docraptor
+* Yanal-Yvez Fargialla
+* Morntag
+* NBCO
+
+
+Version 0.8.0
+-------------
+
+Released on 2023-09-25.
+
+New features:
+
+* Add text rise operator
+
+Backers and sponsors:
+
+* Spacinov
+* Kobalt
+* Grip Angebotssoftware
+* Manuel Barkhau
+* SimonSoft
+* Menutech
+* KontextWork
+* NCC Group
+* René Fritz
+* Nicola Auchmuty
+* Syslifters
+* Hammerbacher
+* TrainingSparkle
+* Daniel Kucharski
+* Healthchecks.io
+* Yanal-Yvez Fargialla
+* WakaTime
+* Paheko
+* Synapsium
+* DocRaptor
+
+
Version 0.7.0
-------------
diff --git a/docs/common_use_cases.rst b/docs/common_use_cases.rst
index b045fa6..d8343b3 100644
--- a/docs/common_use_cases.rst
+++ b/docs/common_use_cases.rst
@@ -179,3 +179,87 @@ Display text
with open('document.pdf', 'wb') as f:
document.write(f)
+
+
+Add metadata
+------------
+
+.. code-block:: python
+
+ import datetime
+
+ import pydyf
+
+ document = pydyf.PDF()
+ document.info['Author'] = pydyf.String('Jane Doe')
+ document.info['Creator'] = pydyf.String('pydyf')
+ document.info['Keywords'] = pydyf.String('some keywords')
+ document.info['Producer'] = pydyf.String('The producer')
+ document.info['Subject'] = pydyf.String('An example PDF')
+ document.info['Title'] = pydyf.String('A PDF containing metadata')
+ now = datetime.datetime.now()
+ document.info['CreationDate'] = pydyf.String(now.strftime('D:%Y%m%d%H%M%S'))
+
+ document.add_page(
+ pydyf.Dictionary(
+ {
+ 'Type': '/Page',
+ 'Parent': document.pages.reference,
+ 'MediaBox': pydyf.Array([0, 0, 200, 200]),
+ }
+ )
+ )
+
+ # 550 bytes PDF
+ with open('metadata.pdf', 'wb') as f:
+ document.write(f)
+
+
+Display inline QR-code image
+----------------------------
+
+.. code-block:: python
+
+ import pydyf
+ import qrcode
+
+ # Create a QR code image
+ image = qrcode.make('Some data here')
+ raw_data = image.tobytes()
+ width = image.size[0]
+ height = image.size[1]
+
+ document = pydyf.PDF()
+ stream = pydyf.Stream(compress=True)
+ stream.push_state()
+ x = 0
+ y = 0
+ stream.transform(width, 0, 0, height, x, y)
+ # Add the 1-bit grayscale image inline in the PDF
+ stream.inline_image(width, height, 'Gray', 1, raw_data)
+ stream.pop_state()
+ document.add_object(stream)
+
+ # Put the image in the resources of the PDF
+ document.add_page(
+ pydyf.Dictionary(
+ {
+ 'Type': '/Page',
+ 'Parent': document.pages.reference,
+ 'MediaBox': pydyf.Array([0, 0, 400, 400]),
+ 'Resources': pydyf.Dictionary(
+ {
+ 'ProcSet': pydyf.Array(
+ ['/PDF', '/ImageB', '/ImageC', '/ImageI']
+ ),
+ }
+ ),
+ 'Contents': stream.reference,
+ }
+ )
+ )
+
+ # 909 bytes PDF
+ with open('qrcode.pdf', 'wb') as f:
+ document.write(f, compress=True)
+
diff --git a/docs/conf.py b/docs/conf.py
index a08c175..c868316 100644
--- a/docs/conf.py
+++ b/docs/conf.py
@@ -46,6 +46,9 @@ html_theme_options = {
'collapse_navigation': False,
}
+# Favicon URL
+html_favicon = 'https://www.courtbouillon.org/static/images/favicon.png'
+
# Add any paths that contain custom static files (such as style sheets) here,
# relative to this directory. They are copied after the builtin static files,
# so a file named "default.css" will overwrite the builtin "default.css".
diff --git a/pydyf/__init__.py b/pydyf/__init__.py
index 3d0b122..d8e1d7b 100755
--- a/pydyf/__init__.py
+++ b/pydyf/__init__.py
@@ -3,28 +3,27 @@ A low-level PDF generator.
"""
+import base64
import re
import zlib
from codecs import BOM_UTF16_BE
from hashlib import md5
from math import ceil, log
-VERSION = __version__ = '0.7.0'
+VERSION = __version__ = '0.9.0'
def _to_bytes(item):
"""Convert item to bytes."""
if isinstance(item, bytes):
return item
- elif isinstance(item, Object):
- return item.data
elif isinstance(item, float):
if item.is_integer():
- return f'{int(item):d}'.encode('ascii')
+ return str(int(item)).encode('ascii')
else:
return f'{item:f}'.rstrip('0').encode('ascii')
- elif isinstance(item, int):
- return f'{item:d}'.encode('ascii')
+ elif isinstance(item, Object):
+ return item.data
return str(item).encode('ascii')
@@ -280,6 +279,10 @@ class Stream(Object):
"""Set text rendering mode."""
self.stream.append(_to_bytes(mode) + b' Tr')
+ def set_text_rise(self, height):
+ """Set text rise."""
+ self.stream.append(_to_bytes(height) + b' Ts')
+
def set_line_cap(self, line_cap):
"""Set line cap style."""
self.stream.append(_to_bytes(line_cap) + b' J')
@@ -362,6 +365,44 @@ class Stream(Object):
_to_bytes(a), _to_bytes(b), _to_bytes(c),
_to_bytes(d), _to_bytes(e), _to_bytes(f), b'cm')))
+ def inline_image(self, width, height, color_space, bpc, raw_data):
+ """Add an inline image.
+
+ :param width: The width of the image.
+ :type width: :obj:`int`
+ :param height: The height of the image.
+ :type height: :obj:`int`
+ :param colorspace: The color space of the image, f.e. RGB, Gray.
+ :type colorspace: :obj:`str`
+ :param bpc: The bits per component. 1 for BW, 8 for grayscale.
+ :type bpc: :obj:`int`
+ :param raw_data: The raw pixel data.
+
+ """
+ if self.compress:
+ data = zlib.compress(raw_data)
+ else:
+ data = raw_data
+ enc_data = base64.a85encode(data)
+ self.stream.append(
+ b' '.join(
+ (
+ b'BI',
+ b'/W', _to_bytes(width),
+ b'/H', _to_bytes(height),
+ b'/BPC', _to_bytes(bpc),
+ b'/CS',
+ b'/Device' + color_space.encode(),
+ b'/F',
+ b'[/A85 /Fl]' if self.compress else b'/A85',
+ b'/L', _to_bytes(len(enc_data) + 2),
+ b'ID',
+ enc_data + b'~>',
+ b'EI',
+ )
+ )
+ )
+
@property
def data(self):
stream = b'\n'.join(_to_bytes(item) for item in self.stream)
diff --git a/pyproject.toml b/pyproject.toml
index e26cbfe..2c17097 100644
--- a/pyproject.toml
+++ b/pyproject.toml
@@ -19,11 +19,11 @@ classifiers = [
'Programming Language :: Python',
'Programming Language :: Python :: 3',
'Programming Language :: Python :: 3 :: Only',
- 'Programming Language :: Python :: 3.7',
'Programming Language :: Python :: 3.8',
'Programming Language :: Python :: 3.9',
'Programming Language :: Python :: 3.10',
'Programming Language :: Python :: 3.11',
+ 'Programming Language :: Python :: 3.12',
'Programming Language :: Python :: Implementation :: CPython',
'Programming Language :: Python :: Implementation :: PyPy',
]