from __future__ import annotations
import io
from typing import List, Iterator, Tuple, Optional, Any, TYPE_CHECKING, Callable
from ctypes import *
from datetime import datetime
from numbers import Number
from pdftools_toolbox.internal import _lib
from pdftools_toolbox.internal.utils import _string_to_utf16, _utf16_to_string
from pdftools_toolbox.internal.streams import _StreamDescriptor, _NativeStream
from pdftools_toolbox.internal.native_base import _NativeBase
from pdftools_toolbox.internal.native_object import _NativeObject
import pdftools_toolbox.internal
if TYPE_CHECKING:
from pdftools_toolbox.geometry.real.affine_transform import AffineTransform
from pdftools_toolbox.pdf.structure.node import Node
from pdftools_toolbox.pdf.content.image import Image
from pdftools_toolbox.geometry.real.rectangle import Rectangle
from pdftools_toolbox.pdf.content.image_mask import ImageMask
from pdftools_toolbox.pdf.content.paint import Paint
from pdftools_toolbox.pdf.content.path import Path
from pdftools_toolbox.pdf.content.fill import Fill
from pdftools_toolbox.pdf.content.stroke import Stroke
from pdftools_toolbox.pdf.content.text import Text
from pdftools_toolbox.pdf.content.inside_rule import InsideRule
from pdftools_toolbox.pdf.content.group import Group
from pdftools_toolbox.pdf.content.transparency import Transparency
from pdftools_toolbox.pdf.content.content_element import ContentElement
from pdftools_toolbox.pdf.content.content import Content
else:
AffineTransform = "pdftools_toolbox.geometry.real.affine_transform.AffineTransform"
Node = "pdftools_toolbox.pdf.structure.node.Node"
Image = "pdftools_toolbox.pdf.content.image.Image"
Rectangle = "pdftools_toolbox.geometry.real.rectangle.Rectangle"
ImageMask = "pdftools_toolbox.pdf.content.image_mask.ImageMask"
Paint = "pdftools_toolbox.pdf.content.paint.Paint"
Path = "pdftools_toolbox.pdf.content.path.Path"
Fill = "pdftools_toolbox.pdf.content.fill.Fill"
Stroke = "pdftools_toolbox.pdf.content.stroke.Stroke"
Text = "pdftools_toolbox.pdf.content.text.Text"
InsideRule = "pdftools_toolbox.pdf.content.inside_rule.InsideRule"
Group = "pdftools_toolbox.pdf.content.group.Group"
Transparency = "pdftools_toolbox.pdf.content.transparency.Transparency"
ContentElement = "pdftools_toolbox.pdf.content.content_element.ContentElement"
Content = "pdftools_toolbox.pdf.content.content.Content"
[docs]
class ContentGenerator(_NativeObject):
"""
"""
[docs]
def __init__(self, content: Content, prepend: bool):
"""
Create a new content generator for appending or prepending to the content of a group.
Args:
content (pdftools_toolbox.pdf.content.content.Content):
the content object of a page or group
prepend (bool):
`True` for prepending to the content (apply content to background of page), `False` for appending (apply content to foreground of page)
Raises:
ValueError:
if the document associated with `content` has already been closed
ValueError:
if the page or group associated with the `content` has already been appended or closed
"""
from pdftools_toolbox.pdf.content.content import Content
if not isinstance(content, Content):
raise TypeError(f"Expected type {Content.__name__}, but got {type(content).__name__}.")
if not isinstance(prepend, bool):
raise TypeError(f"Expected type {bool.__name__}, but got {type(prepend).__name__}.")
_lib.PtxPdfContent_ContentGenerator_New.argtypes = [c_void_p, c_bool]
_lib.PtxPdfContent_ContentGenerator_New.restype = c_void_p
ret_val = _lib.PtxPdfContent_ContentGenerator_New(content._handle, prepend)
if ret_val is None:
_NativeBase._throw_last_error(False)
super()._initialize(ret_val)
[docs]
def save(self) -> None:
"""
Save the current graphics state
The graphics state is stored on the graphics state stack.
The following properties are affected:
- The current transform matrix
- The current clip path
Raises:
StateError:
if the document associated with the content has already been closed
StateError:
if the page/group associated with the content has already been closed
StateError:
if the content object has already been closed
StateError:
if the object has already been closed
"""
_lib.PtxPdfContent_ContentGenerator_Save.argtypes = [c_void_p]
_lib.PtxPdfContent_ContentGenerator_Save.restype = c_bool
if not _lib.PtxPdfContent_ContentGenerator_Save(self._handle):
_NativeBase._throw_last_error(False)
[docs]
def restore(self) -> None:
"""
Restore the graphics state.
The most recently saved state is restored and removed from the graphics state stack.
The following properties are affected:
- The current transform matrix
- The current clip path
Raises:
StateError:
if the document associated with the content has already been closed
StateError:
if the page/group associated with the content has already been closed
StateError:
if the content object has already been closed
StateError:
if the object has already been closed
"""
_lib.PtxPdfContent_ContentGenerator_Restore.argtypes = [c_void_p]
_lib.PtxPdfContent_ContentGenerator_Restore.restype = c_bool
if not _lib.PtxPdfContent_ContentGenerator_Restore(self._handle):
_NativeBase._throw_last_error(False)
[docs]
def transform(self, transform: AffineTransform) -> None:
"""
Modify the current transform matrix by concatenating the specified matrix.
Args:
transform (pdftools_toolbox.geometry.real.affine_transform.AffineTransform):
the transform that is applied to the current transform
Raises:
StateError:
if the document associated with the content has already been closed
StateError:
if the page/group associated with the content has already been closed
StateError:
if the content object has already been closed
StateError:
if the object has already been closed
ValueError:
if the `transform` object has already been closed
ValueError:
if the `transform` is non-invertible
"""
from pdftools_toolbox.geometry.real.affine_transform import AffineTransform
if not isinstance(transform, AffineTransform):
raise TypeError(f"Expected type {AffineTransform.__name__}, but got {type(transform).__name__}.")
_lib.PtxPdfContent_ContentGenerator_Transform.argtypes = [c_void_p, POINTER(AffineTransform)]
_lib.PtxPdfContent_ContentGenerator_Transform.restype = c_bool
if not _lib.PtxPdfContent_ContentGenerator_Transform(self._handle, transform):
_NativeBase._throw_last_error(False)
[docs]
def tag_as(self, node: Node, language: Optional[str] = None) -> None:
"""
Associate content created following this call with the supplied element of the document structure tree.
Args:
node (pdftools_toolbox.pdf.structure.node.Node):
the tag to be applied to the marked content
language (Optional[str]):
The language code that specifies the language of the tagged content.
Specifying the language is highly recommended for PDF/A level A conformance.
The codes are defined in BCP 47 and ISO 3166:2013 and can
be obtained from the Internet Engineering Task Force and
the International Organization for Standardization.
If no code is set, the language will be specified as
unknown.
Examples:
- "en"
- "en-US"
- "de"
- "de-CH"
- "fr-FR"
- "zxx" (for non linguistic content)
Default is `None` (unknown)
Raises:
StateError:
if the document associated with the content has already been closed
pdftools_toolbox.unsupported_feature_error.UnsupportedFeatureError:
if trying to tag in a content generator not associated with a page
ValueError:
if the `node` has a tag value that is not allowed (not part of PDF 1.7 specification and not in RoleMap)
"""
from pdftools_toolbox.pdf.structure.node import Node
if not isinstance(node, Node):
raise TypeError(f"Expected type {Node.__name__}, but got {type(node).__name__}.")
if language is not None and not isinstance(language, str):
raise TypeError(f"Expected type {str.__name__} or None, but got {type(language).__name__}.")
_lib.PtxPdfContent_ContentGenerator_TagAsW.argtypes = [c_void_p, c_void_p, c_wchar_p]
_lib.PtxPdfContent_ContentGenerator_TagAsW.restype = c_bool
if not _lib.PtxPdfContent_ContentGenerator_TagAsW(self._handle, node._handle, _string_to_utf16(language)):
_NativeBase._throw_last_error(False)
[docs]
def stop_tagging(self) -> None:
"""
Stop tagging content.
Raises:
StateError:
if the document associated with the content has already been closed
"""
_lib.PtxPdfContent_ContentGenerator_StopTagging.argtypes = [c_void_p]
_lib.PtxPdfContent_ContentGenerator_StopTagging.restype = c_bool
if not _lib.PtxPdfContent_ContentGenerator_StopTagging(self._handle):
_NativeBase._throw_last_error(False)
[docs]
def paint_image(self, image: Image, target_rect: Rectangle) -> None:
"""
Paint an image.
Args:
image (pdftools_toolbox.pdf.content.image.Image):
the image to be painted
targetRect (pdftools_toolbox.geometry.real.rectangle.Rectangle):
the target rectangle in the current coordinate system. If targetRect is `None`, the unit rectangle *[0, 0, 1, 1]* is used.
Raises:
StateError:
if the document associated with the content has already been closed
StateError:
if the page/group associated with the content has already been closed
StateError:
if the content object has already been closed
StateError:
if the object has already been closed
ValueError:
if the document associated with `image` has already been closed
ValueError:
if the `image` is associated with a different document
"""
from pdftools_toolbox.pdf.content.image import Image
from pdftools_toolbox.geometry.real.rectangle import Rectangle
if not isinstance(image, Image):
raise TypeError(f"Expected type {Image.__name__}, but got {type(image).__name__}.")
if not isinstance(target_rect, Rectangle):
raise TypeError(f"Expected type {Rectangle.__name__}, but got {type(target_rect).__name__}.")
_lib.PtxPdfContent_ContentGenerator_PaintImage.argtypes = [c_void_p, c_void_p, POINTER(Rectangle)]
_lib.PtxPdfContent_ContentGenerator_PaintImage.restype = c_bool
if not _lib.PtxPdfContent_ContentGenerator_PaintImage(self._handle, image._handle, target_rect):
_NativeBase._throw_last_error(False)
[docs]
def paint_image_mask(self, image_mask: ImageMask, target_rect: Rectangle, paint: Paint) -> None:
"""
Paint an image (stencil) mask.
An image mask is a monochrome image, in which each sample is specified by a single bit.
However, instead of being painted in opaque black and white,
the image mask is treated as a stencil mask that is partly opaque and partly transparent.
Sample values in the image do not represent black and white pixels;
rather, they designate places on the content that should either be marked
with the given paint or masked out (not marked at all).
Areas that are masked out retain their former content.
The effect is like applying paint in the current color through a cut-out stencil,
which allows the paint to reach the page in some places and masks it out in others.
Args:
imageMask (pdftools_toolbox.pdf.content.image_mask.ImageMask):
the image (stencil) mask
targetRect (pdftools_toolbox.geometry.real.rectangle.Rectangle):
the target rectangle in the current coordinate system.
If targetRect is `None`, the unit rectangle *[0, 0, 1, 1]* is used.
paint (pdftools_toolbox.pdf.content.paint.Paint):
the paint for filling marked pixels
Raises:
StateError:
if the document associated with the content has already been closed
StateError:
if the page/group associated with the content has already been closed
StateError:
if the content object has already been closed
StateError:
if the object has already been closed
ValueError:
if the document associated with `imageMask` has already been closed
ValueError:
if the `imageMask` object is not an image mask
ValueError:
if the `imageMask` is associated with a different document
ValueError:
if the document associated with `paint` has already been closed
ValueError:
if the `paint` is associated with a different document
"""
from pdftools_toolbox.pdf.content.image_mask import ImageMask
from pdftools_toolbox.geometry.real.rectangle import Rectangle
from pdftools_toolbox.pdf.content.paint import Paint
if not isinstance(image_mask, ImageMask):
raise TypeError(f"Expected type {ImageMask.__name__}, but got {type(image_mask).__name__}.")
if not isinstance(target_rect, Rectangle):
raise TypeError(f"Expected type {Rectangle.__name__}, but got {type(target_rect).__name__}.")
if not isinstance(paint, Paint):
raise TypeError(f"Expected type {Paint.__name__}, but got {type(paint).__name__}.")
_lib.PtxPdfContent_ContentGenerator_PaintImageMask.argtypes = [c_void_p, c_void_p, POINTER(Rectangle), c_void_p]
_lib.PtxPdfContent_ContentGenerator_PaintImageMask.restype = c_bool
if not _lib.PtxPdfContent_ContentGenerator_PaintImageMask(self._handle, image_mask._handle, target_rect, paint._handle):
_NativeBase._throw_last_error(False)
[docs]
def paint_path(self, path: Path, fill: Optional[Fill], stroke: Optional[Stroke]) -> None:
"""
Paint a path.
The path is first filled and then stroked
The blend mode for filling and stroking must be the same.
Args:
path (pdftools_toolbox.pdf.content.path.Path):
the path to be painted
fill (Optional[pdftools_toolbox.pdf.content.fill.Fill]):
the fill properties or `None` if the path should not be filled
stroke (Optional[pdftools_toolbox.pdf.content.stroke.Stroke]):
the stroke properties or `None` if the path should not be stroked
Raises:
StateError:
if the document associated with the content has already been closed
StateError:
if the page/group associated with the content has already been closed
StateError:
if the content object has already been closed
StateError:
if the object has already been closed
ValueError:
if the `fill` and `stroke` arguments are both `None`.
ValueError:
if the document associated with the `fill` object has already been closed
ValueError:
if the `fill` object belongs to a different document
ValueError:
if the property :attr:`pdftools_toolbox.pdf.content.stroke.Stroke.paint` of argument `stroke` is `None`.
ValueError:
if the document associated with the property :attr:`pdftools_toolbox.pdf.content.stroke.Stroke.paint` of argument `stroke` has already been closed.
ValueError:
if the `stroke` argument belongs to a different document
OperationError:
if the :class:`pdftools_toolbox.pdf.content.paint.Paint` objects for filling and stroking use different blend modes
"""
from pdftools_toolbox.pdf.content.path import Path
from pdftools_toolbox.pdf.content.fill import Fill
from pdftools_toolbox.pdf.content.stroke import Stroke
if not isinstance(path, Path):
raise TypeError(f"Expected type {Path.__name__}, but got {type(path).__name__}.")
if fill is not None and not isinstance(fill, Fill):
raise TypeError(f"Expected type {Fill.__name__} or None, but got {type(fill).__name__}.")
if stroke is not None and not isinstance(stroke, Stroke):
raise TypeError(f"Expected type {Stroke.__name__} or None, but got {type(stroke).__name__}.")
_lib.PtxPdfContent_ContentGenerator_PaintPath.argtypes = [c_void_p, c_void_p, c_void_p, c_void_p]
_lib.PtxPdfContent_ContentGenerator_PaintPath.restype = c_bool
if not _lib.PtxPdfContent_ContentGenerator_PaintPath(self._handle, path._handle, fill._handle if fill is not None else None, stroke._handle if stroke is not None else None):
_NativeBase._throw_last_error(False)
[docs]
def paint_text(self, text: Text) -> None:
"""
Paint text.
Args:
text (pdftools_toolbox.pdf.content.text.Text):
the text to be painted
Raises:
StateError:
if the document associated with the content has already been closed
StateError:
if the page/group associated with the content has already been closed
StateError:
if the content object has already been closed
StateError:
if the object has already been closed
ValueError:
if the `text` is associated with a different document
pdftools_toolbox.generic_error.GenericError:
if for some of the requested characters to paint the font's encoding is not defined or no glyph exists in the font
"""
from pdftools_toolbox.pdf.content.text import Text
if not isinstance(text, Text):
raise TypeError(f"Expected type {Text.__name__}, but got {type(text).__name__}.")
_lib.PtxPdfContent_ContentGenerator_PaintText.argtypes = [c_void_p, c_void_p]
_lib.PtxPdfContent_ContentGenerator_PaintText.restype = c_bool
if not _lib.PtxPdfContent_ContentGenerator_PaintText(self._handle, text._handle):
_NativeBase._throw_last_error(False)
[docs]
def clip_with_path(self, path: Path, inside_rule: InsideRule) -> None:
"""
Intersect clip path with path.
Update the current clip path by intersecting with the given path.
Args:
path (pdftools_toolbox.pdf.content.path.Path):
the path to intersect with the current clip path
insideRule (pdftools_toolbox.pdf.content.inside_rule.InsideRule):
the inside rule of the path argument
Raises:
StateError:
if the document associated with the content has already been closed
StateError:
if the page/group associated with the content has already been closed
StateError:
if the content object has already been closed
StateError:
if the object has already been closed
ValueError:
if the `path` is associated with a different document
"""
from pdftools_toolbox.pdf.content.path import Path
from pdftools_toolbox.pdf.content.inside_rule import InsideRule
if not isinstance(path, Path):
raise TypeError(f"Expected type {Path.__name__}, but got {type(path).__name__}.")
if not isinstance(inside_rule, InsideRule):
raise TypeError(f"Expected type {InsideRule.__name__}, but got {type(inside_rule).__name__}.")
_lib.PtxPdfContent_ContentGenerator_ClipWithPath.argtypes = [c_void_p, c_void_p, c_int]
_lib.PtxPdfContent_ContentGenerator_ClipWithPath.restype = c_bool
if not _lib.PtxPdfContent_ContentGenerator_ClipWithPath(self._handle, path._handle, c_int(inside_rule.value)):
_NativeBase._throw_last_error(False)
[docs]
def clip_with_text(self, text: Text) -> None:
"""
Intersect clip path with text.
Update the current clip path by intersecting with the given text.
Args:
text (pdftools_toolbox.pdf.content.text.Text):
the text to intersect with the current clip path
Raises:
StateError:
if the document associated with the content has already been closed
StateError:
if the page/group associated with the content has already been closed
StateError:
if the content object has already been closed
StateError:
if the object has already been closed
ValueError:
if the document associated with the `text` object has already been closed
ValueError:
if the `text` is associated with a different document
pdftools_toolbox.generic_error.GenericError:
if for some of the requested characters to paint the font's encoding is not defined or no glyph exists in the font
"""
from pdftools_toolbox.pdf.content.text import Text
if not isinstance(text, Text):
raise TypeError(f"Expected type {Text.__name__}, but got {type(text).__name__}.")
_lib.PtxPdfContent_ContentGenerator_ClipWithText.argtypes = [c_void_p, c_void_p]
_lib.PtxPdfContent_ContentGenerator_ClipWithText.restype = c_bool
if not _lib.PtxPdfContent_ContentGenerator_ClipWithText(self._handle, text._handle):
_NativeBase._throw_last_error(False)
[docs]
def paint_group(self, group: Group, target_rect: Optional[Rectangle], transparency: Optional[Transparency]) -> None:
"""
Paint a group.
Args:
group (pdftools_toolbox.pdf.content.group.Group):
the group to be painted
targetRect (Optional[pdftools_toolbox.geometry.real.rectangle.Rectangle]):
the target rectangle in the current coordinate system.
If targetRect is `None`,
a default rectangle *[0, 0, width, height]* is used, where *width* and *height* are the dimensions of the given `group`'s :attr:`Size <pdftools_toolbox.pdf.content.group.Group.size>`
transparency (Optional[pdftools_toolbox.pdf.content.transparency.Transparency]):
the transparency to be used when painting the group.
If Transparency is `None`, then the group is painted opaquely.
Raises:
StateError:
if the document associated with the content has already been closed
StateError:
if the page/group associated with the content has already been closed
StateError:
if the content object has already been closed
StateError:
if the object has already been closed
ValueError:
if the document associated with the `group` object has already been closed
ValueError:
if the `group` is associated with a different document
ValueError:
if the `group` contains no content or the content generator has not been closed yet
ValueError:
if the `group` contains interactive elements (see :meth:`pdftools_toolbox.pdf.content.group.Group.copy_from_page` )
and it has been painted before.
ValueError:
if the `group` contains interactive elements (see :meth:`pdftools_toolbox.pdf.content.group.Group.copy_from_page` )
and the content of the content generator belongs to an annotation.
pdftools_toolbox.conformance_error.ConformanceError:
if the `transparency` argument is not `None` and has properties :attr:`pdftools_toolbox.pdf.content.transparency.Transparency.alpha` other than 1.0 or :attr:`pdftools_toolbox.pdf.content.transparency.Transparency.blend_mode` other than :attr:`pdftools_toolbox.pdf.content.blend_mode.BlendMode.NORMAL` ,
and the explicitly specified conformance does not support transparency (PDF/A-1, PDF 1.0 - 1.3).
"""
from pdftools_toolbox.pdf.content.group import Group
from pdftools_toolbox.geometry.real.rectangle import Rectangle
from pdftools_toolbox.pdf.content.transparency import Transparency
if not isinstance(group, Group):
raise TypeError(f"Expected type {Group.__name__}, but got {type(group).__name__}.")
if target_rect is not None and not isinstance(target_rect, Rectangle):
raise TypeError(f"Expected type {Rectangle.__name__} or None, but got {type(target_rect).__name__}.")
if transparency is not None and not isinstance(transparency, Transparency):
raise TypeError(f"Expected type {Transparency.__name__} or None, but got {type(transparency).__name__}.")
_lib.PtxPdfContent_ContentGenerator_PaintGroup.argtypes = [c_void_p, c_void_p, POINTER(Rectangle), c_void_p]
_lib.PtxPdfContent_ContentGenerator_PaintGroup.restype = c_bool
if not _lib.PtxPdfContent_ContentGenerator_PaintGroup(self._handle, group._handle, target_rect, transparency._handle if transparency is not None else None):
_NativeBase._throw_last_error(False)
[docs]
def append_content_element(self, content_element: ContentElement) -> None:
"""
Paint a content element
Args:
contentElement (pdftools_toolbox.pdf.content.content_element.ContentElement):
the content element to be painted
Raises:
StateError:
if the document associated with the content has already been closed
StateError:
if the `contentElement` object has already been closed
StateError:
if the content object has already been closed
ValueError:
if the `contentElement` is associated with a different document
"""
from pdftools_toolbox.pdf.content.content_element import ContentElement
if not isinstance(content_element, ContentElement):
raise TypeError(f"Expected type {ContentElement.__name__}, but got {type(content_element).__name__}.")
_lib.PtxPdfContent_ContentGenerator_AppendContentElement.argtypes = [c_void_p, c_void_p]
_lib.PtxPdfContent_ContentGenerator_AppendContentElement.restype = c_bool
if not _lib.PtxPdfContent_ContentGenerator_AppendContentElement(self._handle, content_element._handle):
_NativeBase._throw_last_error(False)
def __exit__(self, exc_type, exc_value, traceback):
_lib.PtxPdfContent_ContentGenerator_Close.argtypes = [c_void_p]
_lib.PtxPdfContent_ContentGenerator_Close.restype = c_bool
if self._handle is not None:
try:
if not _lib.PtxPdfContent_ContentGenerator_Close(self._handle):
super()._throw_last_error()
finally:
self._handle = None # Invalidate the handle
def __enter__(self):
return self
@staticmethod
def _create_dynamic_type(handle):
return ContentGenerator._from_handle(handle)
@classmethod
def _from_handle(cls, handle):
"""
Internal factory method for constructing an instance using an internal handle.
This method creates an instance of the class by bypassing the public constructor.
"""
instance = ContentGenerator.__new__(cls) # Bypass __init__
instance._initialize(handle)
return instance
def _initialize(self, handle):
super()._initialize(handle)