# **************************************************************************** # * Software: FPDF for python * # * License: LGPL v3.0+ * # * * # * Original Author (PHP): Olivier PLATHEY 2004-12-31 * # * Ported to Python 2.4 by Max (maxpat78@yahoo.it) on 2006-05 * # * Maintainer: Mariano Reingart (reingart@gmail.com) et al since 2008 est. * # * Maintainer: David Alexander (daveankin@gmail.com) et al since 2017 est. * # * Maintainer: Lucas Cimon et al since 2021 est. * # **************************************************************************** import hashlib, io, logging, math, os, re, sys, types, warnings from collections import defaultdict from contextlib import contextmanager from datetime import datetime, timezone from functools import wraps from math import isclose from numbers import Number from os.path import splitext from pathlib import Path from typing import Callable, Iterator, NamedTuple, Optional, Union try: from endesive import signer from cryptography.hazmat.primitives.serialization import pkcs12 except ImportError: pkcs12, signer = None, None try: from PIL.Image import Image except ImportError: warnings.warn( "Pillow could not be imported - fpdf2 will not be able to add any image" ) class Image: # The class must exist for some isinstance checks below pass from .actions import URIAction from .annotations import ( AnnotationDict, PDFAnnotation, PDFEmbeddedFile, DEFAULT_ANNOT_FLAGS, ) from .bidi import BidiParagraph, auto_detect_base_direction from .deprecation import ( support_deprecated_txt_arg, get_stack_level, WarnOnDeprecatedModuleAttributes, ) from .drawing import ( convert_to_device_color, DeviceRGB, GraphicsStateDictRegistry, GraphicsStyle, DrawingContext, PaintedPath, Point, Transform, ) from .encryption import StandardSecurityHandler from .enums import ( AccessPermission, Align, Angle, AnnotationFlag, AnnotationName, CharVPos, Corner, EncryptionMethod, FileAttachmentAnnotationName, MethodReturnValue, PageLayout, PageMode, PathPaintRule, RenderStyle, TextEmphasis, TextMarkupType, TextMode, WrapMode, XPos, YPos, ) from .errors import FPDFException, FPDFPageFormatException, FPDFUnicodeEncodingException from .fonts import CoreFont, CORE_FONTS, FontFace, TTFFont from .graphics_state import GraphicsStateMixin from .html import HTML2FPDF from .image_datastructures import ( ImageCache, ImageInfo, RasterImageInfo, VectorImageInfo, ) from .image_parsing import ( SUPPORTED_IMAGE_FILTERS, get_img_info, load_image, preload_image, ) from .linearization import LinearizedOutputProducer from .line_break import Fragment, MultiLineBreak, TextLine from .outline import OutlineSection from .output import ( OutputProducer, PDFPage, ZOOM_CONFIGS, stream_content_for_raster_image, ) from .recorder import FPDFRecorder from .sign import Signature from .structure_tree import StructureTreeBuilder from .svg import Percent, SVGObject from .syntax import DestinationXYZ, PDFArray, PDFDate from .table import Table, draw_box_borders from .text_region import TextRegionMixin, TextColumns from .util import get_scale_factor, Padding # Public global variables: FPDF_VERSION = "2.7.8" PAGE_FORMATS = { "a3": (841.89, 1190.55), "a4": (595.28, 841.89), "a5": (420.94, 595.28), "letter": (612, 792), "legal": (612, 1008), } "Supported page format names & dimensions" # Private global variables: LOGGER = logging.getLogger(__name__) HERE = Path(__file__).resolve().parent FPDF_FONT_DIR = HERE / "font" LAYOUT_ALIASES = { "default": None, "single": PageLayout.SINGLE_PAGE, "continuous": PageLayout.ONE_COLUMN, "two": PageLayout.TWO_COLUMN_LEFT, } class TitleStyle(FontFace): def __init__( self, font_family: Optional[str] = None, font_style: Optional[str] = None, font_size_pt: Optional[int] = None, color: Union[int, tuple] = None, # grey scale or (red, green, blue), underline: bool = False, t_margin: Optional[int] = None, l_margin: Optional[int] = None, b_margin: Optional[int] = None, ): super().__init__( font_family, (font_style or "") + ("U" if underline else ""), font_size_pt, color, ) self.t_margin = t_margin self.l_margin = l_margin self.b_margin = b_margin class ToCPlaceholder(NamedTuple): render_function: Callable start_page: int y: int pages: int = 1 # Disabling this check due to the "format" parameter below: # pylint: disable=redefined-builtin def get_page_format(format, k=None): """Return page width and height size in points. Throws FPDFPageFormatException `format` can be either a 2-tuple or one of 'a3', 'a4', 'a5', 'letter', or 'legal'. If format is a tuple, then the return value is the tuple's values given in the units specified on this document in the constructor, multiplied by the corresponding scale factor `k`, taken from instance variable `self.k`. If format is a string, the (width, height) tuple returned is in points. For a width and height of 8.5 * 11, 72 dpi is assumed, so the value returned is (8.5 * 72, 11 * 72), or (612, 792). Additional formats can be added by adding fields to the `PAGE_FORMATS` dictionary with a case insensitive key (the name of the new format) and 2-tuple value of (width, height) in dots per inch with a 72 dpi resolution. """ if isinstance(format, str): format = format.lower() if format in PAGE_FORMATS: return PAGE_FORMATS[format] raise FPDFPageFormatException(format, unknown=True) if k is None: raise FPDFPageFormatException(format, one=True) try: return format[0] * k, format[1] * k except Exception as e: args = f"{format}, {k}" raise FPDFPageFormatException(f"Arguments must be numbers: {args}") from e def check_page(fn): """Decorator to protect drawing methods""" @wraps(fn) def wrapper(self, *args, **kwargs): if not self.page and not (kwargs.get("dry_run") or kwargs.get("split_only")): raise FPDFException("No page open, you need to call add_page() first") return fn(self, *args, **kwargs) return wrapper class FPDF(GraphicsStateMixin, TextRegionMixin): "PDF Generation class" MARKDOWN_BOLD_MARKER = "**" MARKDOWN_ITALICS_MARKER = "__" MARKDOWN_UNDERLINE_MARKER = "--" MARKDOWN_LINK_REGEX = re.compile(r"^\[([^][]+)\]\(([^()]+)\)(.*)$", re.DOTALL) MARKDOWN_LINK_COLOR = None MARKDOWN_LINK_UNDERLINE = True HTML2FPDF_CLASS = HTML2FPDF def __init__( self, orientation="portrait", unit="mm", format="A4", font_cache_dir="DEPRECATED", ): """ Args: orientation (str): possible values are "portrait" (can be abbreviated "P") or "landscape" (can be abbreviated "L"). Default to "portrait". unit (str, int, float): possible values are "pt", "mm", "cm", "in", or a number. A point equals 1/72 of an inch, that is to say about 0.35 mm (an inch being 2.54 cm). This is a very common unit in typography; font sizes are expressed in this unit. If given a number, then it will be treated as the number of points per unit. (eg. 72 = 1 in) Default to "mm". format (str): possible values are "a3", "a4", "a5", "letter", "legal" or a tuple (width, height) expressed in the given unit. Default to "a4". font_cache_dir (Path or str): [**DEPRECATED since v2.5.1**] unused """ if font_cache_dir != "DEPRECATED": warnings.warn( ( '"font_cache_dir" parameter is deprecated since v2.5.1, ' "unused and will soon be removed" ), DeprecationWarning, stacklevel=get_stack_level(), ) super().__init__() self.page = 0 # current page number self.pages = {} # array of PDFPage objects starting at index 1 self.fonts = {} # map font string keys to an instance of CoreFont or TTFFont self.links = {} # array of Destination objects starting at index 1 self.embedded_files = [] # array of PDFEmbeddedFile self.image_cache = ImageCache() self.in_footer = False # flag set while rendering footer # indicates that we are inside an .unbreakable() code block: self._in_unbreakable = False self._lasth = 0 # height of last cell printed self.alias_nb_pages() # enable alias by default self._angle = 0 # used by deprecated method: rotate() self.xmp_metadata = None # Define the compression algorithm used when embedding images: self.page_duration = 0 # optional pages display duration, cf. add_page() self.page_transition = None # optional pages transition, cf. add_page() self.allow_images_transparency = True # Do nothing by default. Allowed values: 'WARN', 'DOWNSCALE': self.oversized_images = None self.oversized_images_ratio = 2 # number of pixels per UserSpace point self.struct_builder = StructureTreeBuilder() self._toc_placeholder = None # optional ToCPlaceholder instance self._outline = [] # list of OutlineSection self._sign_key = None self.section_title_styles = {} # level -> TitleStyle self.core_fonts_encoding = "latin-1" "Font encoding, Latin-1 by default" # Replace these fonts with these core fonts self.font_aliases = { "arial": "helvetica", "couriernew": "courier", "timesnewroman": "times", } # Scale factor self.k = get_scale_factor(unit) # Graphics state variables defined as properties by GraphicsStateMixin. # We set their default values here. self.font_family = "" # current font family self.font_style = "" # current font style self.font_size_pt = 12 # current font size in points self.font_stretching = 100 # current font stretching self.char_spacing = 0 # current character spacing self.underline = False # underlining flag self.current_font = ( None # current font, None or an instance of CoreFont or TTFFont ) self.draw_color = self.DEFAULT_DRAW_COLOR self.fill_color = self.DEFAULT_FILL_COLOR self.text_color = self.DEFAULT_TEXT_COLOR self.page_background = None self.dash_pattern = dict(dash=0, gap=0, phase=0) self.line_width = 0.567 / self.k # line width (0.2 mm) self.text_mode = TextMode.FILL # end of grapics state variables self.dw_pt, self.dh_pt = get_page_format(format, self.k) self._set_orientation(orientation, self.dw_pt, self.dh_pt) self.def_orientation = self.cur_orientation # Page spacing # Page margins (1 cm) margin = (7200 / 254) / self.k self.x, self.y, self.l_margin, self.t_margin = 0, 0, 0, 0 self.set_margins(margin, margin) self.x, self.y = self.l_margin, self.t_margin self.c_margin = margin / 10.0 # Interior cell margin (1 mm) # sets self.auto_page_break, self.b_margin & self.page_break_trigger: self.set_auto_page_break(True, 2 * margin) self.set_display_mode("fullwidth") # Full width display mode self._page_mode = None self.viewer_preferences = None # optional instance of ViewerPreferences self.compress = True # switch enabling pages content compression self.pdf_version = "1.3" # Set default PDF version No. self.creation_date = datetime.now(timezone.utc) self._security_handler = None self._fallback_font_ids = [] self._fallback_font_exact_match = False self._current_draw_context = None self._drawing_graphics_state_registry = GraphicsStateDictRegistry() self._record_text_quad_points = False # page number -> array of 8 × n numbers: self._text_quad_points = defaultdict(list) # final buffer holding the PDF document in-memory - defined only after calling output(): self.buffer = None def set_encryption( self, owner_password, user_password=None, encryption_method=EncryptionMethod.RC4, permissions=AccessPermission.all(), encrypt_metadata=False, ): """ " Activate encryption of the document content. Args: owner_password (str): mandatory. The owner password allows to perform any change on the document, including removing all encryption and access permissions. user_password (str): optional. If a user password is set, the content of the document will be encrypted and a password prompt displayed when a user opens the document. The document will only be displayed after either the user or owner password is entered. encryption_method (fpdf.enums.EncryptionMethod, str): algorithm to be used to encrypt the document. Defaults to RC4. permissions (fpdf.enums.AccessPermission): specify access permissions granted when the document is opened with user access. Defaults to ALL. encrypt_metadata (bool): whether to also encrypt document metadata (author, creation date, etc.). Defaults to False. """ self._security_handler = StandardSecurityHandler( self, owner_password=owner_password, user_password=user_password, permission=permissions, encryption_method=encryption_method, encrypt_metadata=encrypt_metadata, ) def write_html(self, text, *args, **kwargs): """ Parse HTML and convert it to PDF. cf. https://py-pdf.github.io/fpdf2/HTML.html Args: text (str): HTML content to render image_map (function): an optional one-argument function that map "src" to new image URLs li_tag_indent (int): numeric indentation of
  • elements dd_tag_indent (int): numeric indentation of
    elements table_line_separators (bool): enable horizontal line separators in ul_bullet_char (str): bullet character for