diff options
Diffstat (limited to 'pydyf')
-rwxr-xr-x | pydyf/__init__.py | 114 |
1 files changed, 58 insertions, 56 deletions
diff --git a/pydyf/__init__.py b/pydyf/__init__.py index d8e1d7b..86d321d 100755 --- a/pydyf/__init__.py +++ b/pydyf/__init__.py @@ -9,8 +9,9 @@ import zlib from codecs import BOM_UTF16_BE from hashlib import md5 from math import ceil, log +from warnings import warn -VERSION = __version__ = '0.9.0' +VERSION = __version__ = '0.10.0' def _to_bytes(item): @@ -203,6 +204,37 @@ class Stream(Object): """ self.stream.append(b'b*' if even_odd else b'b') + 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. + + """ + data = zlib.compress(raw_data) if self.compress else raw_data + a85_data = base64.a85encode(data) + b'~>' + 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' + _to_bytes(color_space), + b'/F', + b'[/A85 /Fl]' if self.compress else b'/A85', + b'/L', _to_bytes(len(a85_data)), + b'ID', + a85_data, + b'EI', + ))) + def line_to(self, x, y): """Add line from current point to point ``(x, y)``.""" self.stream.append(b' '.join((_to_bytes(x), _to_bytes(y), b'l'))) @@ -365,44 +397,6 @@ 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) @@ -450,17 +444,16 @@ class Array(Object, list): class PDF: """PDF document.""" - def __init__(self, version=b'1.7', identifier=None): - """Create a PDF document. - - :param bytes version: PDF version. - :param bytes identifier: PDF file identifier. - - """ - #: PDF version, as :obj:`bytes`. - self.version = _to_bytes(version) - #: PDF file identifier. - self.identifier = identifier + def __init__(self, version=None, identifier=None): + """Create a PDF document.""" + if version or identifier: # to be removed in next version + warn( + "PDF objects don’t take version or identifier during initialization " + "anymore. These properties are now stored but ignored, and will be " + "removed and rejected in next version of pydyf. Please pass these " + "properties to the PDF.write() method instead.", DeprecationWarning) + self.version = _to_bytes(version) if version else b'1.7' # to be removed + self.identifier = identifier # to be removed #: Python :obj:`list` containing the PDF’s objects. self.objects = [] @@ -528,18 +521,23 @@ class PDF: self.current_position += len(content) + 1 output.write(content + b'\n') - def write(self, output, version=None, identifier=None, compress=False): + def write(self, output, version=b'1.7', identifier=False, compress=False): """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. + :param identifier: PDF file identifier. Default is :obj:`False` + to include no identifier, can be set to :obj:`True` to generate an + automatic identifier. + :type identifier: :obj:`bytes` or :obj:`bool` :param bool compress: whether the PDF uses a compressed object stream. """ - version = self.version if version is None else _to_bytes(version) - identifier = self.identifier if identifier is None else identifier + # Convert version and identifier to bytes + version = _to_bytes(version or b'1.7') # Force 1.7 when None + if identifier not in (False, True, None): + identifier = _to_bytes(identifier) # Write header self.write_line(b'%PDF-' + version, output) @@ -607,10 +605,12 @@ class PDF: 'Root': self.catalog.reference, 'Info': self.info.reference, } - if identifier is not None: + if identifier: data = b''.join( obj.data for obj in self.objects if obj.free != 'f') data_hash = md5(data).hexdigest().encode() + if identifier is True: + identifier = data_hash extra['ID'] = Array(( String(identifier).data, String(data_hash).data)) dict_stream = Stream([xref_stream], extra, compress) @@ -640,10 +640,12 @@ 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: + if identifier: data = b''.join( obj.data for obj in self.objects if obj.free != 'f') data_hash = md5(data).hexdigest().encode() + if identifier is True: + identifier = data_hash self.write_line( b'/ID [' + String(identifier).data + b' ' + String(data_hash).data + b']', output) |