From 60e8b5a52ad9e5da5c85b75f81eed427edcdf193 Mon Sep 17 00:00:00 2001 From: "Panagiotis H.M. Issaris" Date: Fri, 2 Feb 2024 20:58:22 +0100 Subject: [PATCH 1/9] Add basic inline image support, #19 --- pydyf/__init__.py | 36 ++++++++++++++++++++++++++++++++++++ 1 file changed, 36 insertions(+) diff --git a/pydyf/__init__.py b/pydyf/__init__.py index 2ee8385..d1522e3 100755 --- a/pydyf/__init__.py +++ b/pydyf/__init__.py @@ -3,6 +3,7 @@ """ +import base64 import re import zlib from codecs import BOM_UTF16_BE @@ -364,6 +365,41 @@ def transform(self, a, b, c, d, e, f): _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, 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 width: :obj:`int` + :param bpc: The bits per component. 1 for BW, 8 for grayscale. + :type width: :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).decode("ascii") + self.stream.append( + " ".join( + ( + "BI", + f"/W {width}", + f"/H {height}", + f"/BPC {bpc}", + "/CS", + "/DeviceGray", + "/F", + "[/A85 /Fl]" if self.compress else "/A85", + "ID", + enc_data, + "EI", + ) + ) + ) + @property def data(self): stream = b'\n'.join(_to_bytes(item) for item in self.stream) From 27788ea09b6421eccdc3d75e168b2f9edf4af92c Mon Sep 17 00:00:00 2001 From: "Panagiotis H.M. Issaris" Date: Fri, 2 Feb 2024 23:11:18 +0100 Subject: [PATCH 2/9] Add an example of inline image usage, #19 --- docs/common_use_cases.rst | 50 +++++++++++++++++++++++++++++++++++++++ 1 file changed, 50 insertions(+) diff --git a/docs/common_use_cases.rst b/docs/common_use_cases.rst index e213b83..c32d19c 100644 --- a/docs/common_use_cases.rst +++ b/docs/common_use_cases.rst @@ -213,3 +213,53 @@ Add metadata # 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, 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) + From 9971cd39c63501e25a7311ef843c621170e3cd08 Mon Sep 17 00:00:00 2001 From: "Panagiotis H.M. Issaris" Date: Fri, 16 Feb 2024 16:37:14 +0100 Subject: [PATCH 3/9] Add the missing ASCII85 end-of-data marker, #19 This fixes this warning: An EOD code was missing in an ASCII85 stream. The operator has an invalid number of operands. The document does not conform to the requested standard. --- pydyf/__init__.py | 1 + 1 file changed, 1 insertion(+) diff --git a/pydyf/__init__.py b/pydyf/__init__.py index d1522e3..f5d7ed3 100755 --- a/pydyf/__init__.py +++ b/pydyf/__init__.py @@ -395,6 +395,7 @@ def inline_image(self, width, height, bpc, raw_data): "[/A85 /Fl]" if self.compress else "/A85", "ID", enc_data, + "~>", "EI", ) ) From 5b0b88ed7c383d6a12512b6d3d540c9c4c47d765 Mon Sep 17 00:00:00 2001 From: "Panagiotis H.M. Issaris" Date: Fri, 16 Feb 2024 22:57:40 +0100 Subject: [PATCH 4/9] Use single quotes for consistency with the rest of the code base, #19 Suggested by Lucie Anglade. --- pydyf/__init__.py | 26 +++++++++++++------------- 1 file changed, 13 insertions(+), 13 deletions(-) diff --git a/pydyf/__init__.py b/pydyf/__init__.py index f5d7ed3..8f20828 100755 --- a/pydyf/__init__.py +++ b/pydyf/__init__.py @@ -381,22 +381,22 @@ def inline_image(self, width, height, bpc, raw_data): data = zlib.compress(raw_data) else: data = raw_data - enc_data = base64.a85encode(data).decode("ascii") + enc_data = base64.a85encode(data).decode('ascii') self.stream.append( - " ".join( + ' '.join( ( - "BI", - f"/W {width}", - f"/H {height}", - f"/BPC {bpc}", - "/CS", - "/DeviceGray", - "/F", - "[/A85 /Fl]" if self.compress else "/A85", - "ID", + 'BI', + f'/W {width}', + f'/H {height}', + f'/BPC {bpc}', + '/CS', + '/DeviceGray', + '/F', + '[/A85 /Fl]' if self.compress else '/A85', + 'ID', enc_data, - "~>", - "EI", + '~>', + 'EI', ) ) ) From 3460b0babcb84c305d17afd63a08b8a0e566087a Mon Sep 17 00:00:00 2001 From: "Panagiotis H.M. Issaris" Date: Fri, 16 Feb 2024 22:59:02 +0100 Subject: [PATCH 5/9] Use bytes, #19 Suggested by Lucie Anglade. --- pydyf/__init__.py | 26 +++++++++++++------------- 1 file changed, 13 insertions(+), 13 deletions(-) diff --git a/pydyf/__init__.py b/pydyf/__init__.py index 8f20828..349721b 100755 --- a/pydyf/__init__.py +++ b/pydyf/__init__.py @@ -381,22 +381,22 @@ def inline_image(self, width, height, bpc, raw_data): data = zlib.compress(raw_data) else: data = raw_data - enc_data = base64.a85encode(data).decode('ascii') + enc_data = base64.a85encode(data) self.stream.append( - ' '.join( + b' '.join( ( - 'BI', - f'/W {width}', - f'/H {height}', - f'/BPC {bpc}', - '/CS', - '/DeviceGray', - '/F', - '[/A85 /Fl]' if self.compress else '/A85', - 'ID', + b'BI', + b'/W', _to_bytes(width), + b'/H', _to_bytes(height), + b'/BPC', _to_bytes(bpc), + b'/CS', + b'/DeviceGray', + b'/F', + b'[/A85 /Fl]' if self.compress else b'/A85', + b'ID', enc_data, - '~>', - 'EI', + b'~>', + b'EI', ) ) ) From ad1efa5b849da628558f6f8d4ccd3df5d76edd31 Mon Sep 17 00:00:00 2001 From: "Panagiotis H.M. Issaris" Date: Fri, 16 Feb 2024 23:11:42 +0100 Subject: [PATCH 6/9] Remove the space between the encoded string and the end-of-data marker, #19 Suggested by Lucie Anglade. --- pydyf/__init__.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/pydyf/__init__.py b/pydyf/__init__.py index 349721b..a1985a2 100755 --- a/pydyf/__init__.py +++ b/pydyf/__init__.py @@ -394,8 +394,7 @@ def inline_image(self, width, height, bpc, raw_data): b'/F', b'[/A85 /Fl]' if self.compress else b'/A85', b'ID', - enc_data, - b'~>', + enc_data + b'~>', b'EI', ) ) From eb72935e1c4b35df193b0021c878deeafdee4241 Mon Sep 17 00:00:00 2001 From: "Panagiotis H.M. Issaris" Date: Fri, 16 Feb 2024 23:21:35 +0100 Subject: [PATCH 7/9] Add the Length key mandatory since PDF 2.0, #19 Suggested by Lucie Anglade. --- pydyf/__init__.py | 1 + 1 file changed, 1 insertion(+) diff --git a/pydyf/__init__.py b/pydyf/__init__.py index a1985a2..9dc3171 100755 --- a/pydyf/__init__.py +++ b/pydyf/__init__.py @@ -393,6 +393,7 @@ def inline_image(self, width, height, bpc, raw_data): b'/DeviceGray', 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', From 30aeb6d4eb7f6a8848ef7368d21928c2d8eb67cc Mon Sep 17 00:00:00 2001 From: "Panagiotis H.M. Issaris" Date: Fri, 16 Feb 2024 23:27:11 +0100 Subject: [PATCH 8/9] Correct docstring, #19 --- pydyf/__init__.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/pydyf/__init__.py b/pydyf/__init__.py index 9dc3171..acf21bf 100755 --- a/pydyf/__init__.py +++ b/pydyf/__init__.py @@ -371,9 +371,9 @@ def inline_image(self, width, height, bpc, raw_data): :param width: The width of the image. :type width: :obj:`int` :param height: The height of the image. - :type width: :obj:`int` + :type height: :obj:`int` :param bpc: The bits per component. 1 for BW, 8 for grayscale. - :type width: :obj:`int` + :type bpc: :obj:`int` :param raw_data: The raw pixel data. """ From 4a640765e22115d2e56f5c8a3b67cc0dc58e30f5 Mon Sep 17 00:00:00 2001 From: "Panagiotis H.M. Issaris" Date: Fri, 16 Feb 2024 23:31:57 +0100 Subject: [PATCH 9/9] Allow specifying the color space using a parameter, #19 Suggested by Lucie Anglade. --- docs/common_use_cases.rst | 2 +- pydyf/__init__.py | 6 ++++-- 2 files changed, 5 insertions(+), 3 deletions(-) diff --git a/docs/common_use_cases.rst b/docs/common_use_cases.rst index c32d19c..6d44cf5 100644 --- a/docs/common_use_cases.rst +++ b/docs/common_use_cases.rst @@ -236,7 +236,7 @@ Display inline QR-code image 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, 1, raw_data) + stream.inline_image(width, height, 'Gray', 1, raw_data) stream.pop_state() document.add_object(stream) diff --git a/pydyf/__init__.py b/pydyf/__init__.py index acf21bf..6858ba9 100755 --- a/pydyf/__init__.py +++ b/pydyf/__init__.py @@ -365,13 +365,15 @@ def transform(self, a, b, c, d, e, f): _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, bpc, raw_data): + 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. @@ -390,7 +392,7 @@ def inline_image(self, width, height, bpc, raw_data): b'/H', _to_bytes(height), b'/BPC', _to_bytes(bpc), b'/CS', - b'/DeviceGray', + b'/Device' + color_space.encode(), b'/F', b'[/A85 /Fl]' if self.compress else b'/A85', b'/L', _to_bytes(len(enc_data) + 2),