diff --git a/stubs/fpdf2/@tests/stubtest_allowlist.txt b/stubs/fpdf2/@tests/stubtest_allowlist.txt index 7675d7912..5a86f1936 100644 --- a/stubs/fpdf2/@tests/stubtest_allowlist.txt +++ b/stubs/fpdf2/@tests/stubtest_allowlist.txt @@ -5,6 +5,9 @@ fpdf.fpdf.FPDF.set_creation_date # fonttools shims since we can't import it fpdf._fonttools_shims +# Only present if harfbuzz is installed +fpdf.fonts.HarfBuzzFont + # Checking the following function crashes stubtest 0.991, but seems to be # fixed in later versions. fpdf.FPDF.set_encryption diff --git a/stubs/fpdf2/METADATA.toml b/stubs/fpdf2/METADATA.toml index 8207ba527..52b0219da 100644 --- a/stubs/fpdf2/METADATA.toml +++ b/stubs/fpdf2/METADATA.toml @@ -1,4 +1,4 @@ -version = "2.7.7" +version = "2.7.8" upstream_repository = "https://github.com/PyFPDF/fpdf2" requires = ["types-Pillow>=9.2.0"] diff --git a/stubs/fpdf2/fpdf/bidi.pyi b/stubs/fpdf2/fpdf/bidi.pyi new file mode 100644 index 000000000..eb314edf8 --- /dev/null +++ b/stubs/fpdf2/fpdf/bidi.pyi @@ -0,0 +1,62 @@ +from _typeshed import Incomplete +from collections.abc import Sequence +from dataclasses import dataclass +from typing import Final, Literal, TypedDict, type_check_only + +MAX_DEPTH: Final = 125 + +@type_check_only +class _BracketInfo(TypedDict): + pair: str + type: Literal["o", "c"] + +BIDI_BRACKETS: Final[dict[str, _BracketInfo]] + +class BidiCharacter: + character_index: int + character: str + bidi_class: str + original_bidi_class: str + embedding_level: str + direction: Incomplete | None + + def __init__(self, character_index: int, character: str, embedding_level: str, debug: bool) -> None: ... + def get_direction_from_level(self) -> Literal["L", "R"]: ... + def set_class(self, cls: str) -> None: ... + +@dataclass +class DirectionalStatus: + embedding_level: int # between 0 and MAX_DEPTH + directional_override_status: Literal["N", "L", "R"] + directional_isolate_status: bool + +class IsolatingRun: + characters: list[BidiCharacter] + previous_direction: str + next_direction: str + def __init__(self, characters: list[BidiCharacter], sos: str, eos: str) -> None: ... + def resolve_weak_types(self) -> None: ... + def pair_brackets(self) -> list[tuple[int, int]]: ... + def resolve_neutral_types(self) -> None: ... + def resolve_implicit_levels(self) -> None: ... + +def auto_detect_base_direction(string: str, stop_at_pdi: bool = False, debug: bool = False) -> Literal["L", "R"]: ... +def calculate_isolate_runs(paragraph: Sequence[BidiCharacter]) -> list[IsolatingRun]: ... + +class BidiParagraph: + text: str + base_direction: Literal["L", "R"] + debug: bool + base_embedding_level: int + characters: list[BidiCharacter] + + def __init__(self, text: str, base_direction: Literal["L", "R"] | None = None, debug: bool = False) -> None: ... + def get_characters(self) -> list[BidiCharacter]: ... + def get_characters_with_embedding_level(self) -> list[BidiCharacter]: ... + def get_reordered_characters(self) -> list[BidiCharacter]: ... + def get_all(self) -> tuple[list[BidiCharacter], tuple[BidiCharacter, ...]]: ... + def get_reordered_string(self) -> str: ... + def get_bidi_fragments(self) -> tuple[tuple[str, Literal["L", "R"]], ...]: ... + def get_bidi_characters(self) -> None: ... + def split_bidi_fragments(self) -> tuple[tuple[str, Literal["L", "R"]], ...]: ... + def reorder_resolved_levels(self) -> tuple[BidiCharacter, ...]: ... diff --git a/stubs/fpdf2/fpdf/enums.pyi b/stubs/fpdf2/fpdf/enums.pyi index f67ea17e0..d1fa0fd75 100644 --- a/stubs/fpdf2/fpdf/enums.pyi +++ b/stubs/fpdf2/fpdf/enums.pyi @@ -73,6 +73,10 @@ class TableCellFillMode(CoerciveEnum): def should_fill_cell(self, i: int, j: int) -> bool: ... +class TableSpan(CoerciveEnum): + ROW: Literal["ROW"] + COL: Literal["COL"] + class RenderStyle(CoerciveEnum): D: str F: str diff --git a/stubs/fpdf2/fpdf/fonts.pyi b/stubs/fpdf2/fpdf/fonts.pyi index 1c0a51155..f4e7722d8 100644 --- a/stubs/fpdf2/fpdf/fonts.pyi +++ b/stubs/fpdf2/fpdf/fonts.pyi @@ -3,11 +3,16 @@ from _typeshed import Incomplete from collections.abc import Generator from dataclasses import dataclass from typing import overload +from typing_extensions import Self from .drawing import DeviceGray, DeviceRGB, Number from .enums import TextEmphasis from .syntax import PDFObject +# Only defined if harfbuzz is installed. +class HarfBuzzFont(Incomplete): # derives from uharfbuzz.Font + def __deepcopy__(self, _memo: object) -> Self: ... + @dataclass class FontFace: family: str | None @@ -58,7 +63,7 @@ class TTFFont(_FontMixin): glyph_ids: Incomplete missing_glyphs: Incomplete subset: Incomplete - hbfont: Incomplete + hbfont: HarfBuzzFont | None # Not always defined. def __init__(self, fpdf, font_file_path, fontkey: str, style: int) -> None: ... def close(self) -> None: ... def get_text_width(self, text: str, font_size_pt: int, text_shaping_parms): ... diff --git a/stubs/fpdf2/fpdf/fpdf.pyi b/stubs/fpdf2/fpdf/fpdf.pyi index 94517be77..80142a885 100644 --- a/stubs/fpdf2/fpdf/fpdf.pyi +++ b/stubs/fpdf2/fpdf/fpdf.pyi @@ -156,8 +156,6 @@ class FPDF(GraphicsStateMixin): w: float h: float - text_shaping: dict[str, Incomplete] | None # TODO: TypedDict - def __init__( self, orientation: _Orientation = "portrait", @@ -212,7 +210,7 @@ class FPDF(GraphicsStateMixin): direction: Literal["ltr", "rtl"] | None = None, script: str | None = None, language: str | None = None, - ): ... + ) -> None: ... def set_compression(self, compress: bool) -> None: ... title: str def set_title(self, title: str) -> None: ... diff --git a/stubs/fpdf2/fpdf/graphics_state.pyi b/stubs/fpdf2/fpdf/graphics_state.pyi index fec06f18e..cbc0048ab 100644 --- a/stubs/fpdf2/fpdf/graphics_state.pyi +++ b/stubs/fpdf2/fpdf/graphics_state.pyi @@ -1,9 +1,18 @@ -from typing import Any, ClassVar - -from fpdf.fonts import FontFace +from typing import Any, ClassVar, Literal, TypedDict, type_check_only from .drawing import DeviceGray, DeviceRGB from .enums import TextMode +from .fonts import FontFace + +@type_check_only +class _TextShaping(TypedDict): + use_shaping_engine: bool + features: dict[str, bool] + direction: Literal["ltr", "rtl"] + script: str | None + language: str | None + fragment_direction: Literal["L", "R"] | None + paragraph_direction: Literal["L", "R"] | None class GraphicsStateMixin: DEFAULT_DRAW_COLOR: ClassVar[DeviceGray] @@ -103,7 +112,7 @@ class GraphicsStateMixin: @denom_lift.setter def denom_lift(self, v) -> None: ... @property - def text_shaping(self): ... + def text_shaping(self) -> _TextShaping | None: ... @text_shaping.setter - def text_shaping(self, v) -> None: ... + def text_shaping(self, v: _TextShaping | None) -> None: ... def font_face(self) -> FontFace: ... diff --git a/stubs/fpdf2/fpdf/line_break.pyi b/stubs/fpdf2/fpdf/line_break.pyi index 52b130b50..ace56d7e3 100644 --- a/stubs/fpdf2/fpdf/line_break.pyi +++ b/stubs/fpdf2/fpdf/line_break.pyi @@ -1,6 +1,6 @@ from _typeshed import Incomplete from collections.abc import Callable, Sequence -from typing import Final, NamedTuple +from typing import Final, Literal, NamedTuple from .enums import Align, WrapMode @@ -54,18 +54,26 @@ class Fragment: @property def lift(self): ... @property - def string(self): ... - def trim(self, index: int): ... + def string(self) -> str: ... + @property + def width(self) -> float: ... + @property + def text_shaping_parameters(self): ... + @property + def paragraph_direction(self) -> Literal["L", "R"]: ... + @property + def fragment_direction(self) -> Literal["L", "R"]: ... + def trim(self, index: int) -> None: ... def __eq__(self, other: Fragment) -> bool: ... # type: ignore[override] - def get_width(self, start: int = 0, end: int | None = None, chars: str | None = None, initial_cs: bool = True): ... + def get_width(self, start: int = 0, end: int | None = None, chars: str | None = None, initial_cs: bool = True) -> float: ... def get_character_width(self, character: str, print_sh: bool = False, initial_cs: bool = True): ... def render_pdf_text(self, frag_ws, current_ws, word_spacing, adjust_x, adjust_y, h): ... def render_pdf_text_ttf(self, frag_ws, word_spacing): ... - def render_with_text_shaping(self, pos_x, pos_y, h, word_spacing, text_shaping_parms): ... + def render_with_text_shaping(self, pos_x: float, pos_y: float, h: float, word_spacing: float) -> str: ... def render_pdf_text_core(self, frag_ws, current_ws): ... class TextLine(NamedTuple): - fragments: tuple[Incomplete, ...] + fragments: tuple[Fragment, ...] text_width: float number_of_spaces: int align: Align @@ -73,6 +81,7 @@ class TextLine(NamedTuple): max_width: float trailing_nl: bool = False trailing_form_feed: bool = False + def get_ordered_fragments(self) -> tuple[Fragment, ...]: ... class SpaceHint(NamedTuple): original_fragment_index: int @@ -97,13 +106,14 @@ class HyphenHint(NamedTuple): class CurrentLine: max_width: float print_sh: Incomplete - fragments: Incomplete - width: int + fragments: list[Fragment] height: int number_of_spaces: int space_break_hint: Incomplete hyphen_break_hint: Incomplete def __init__(self, max_width: float, print_sh: bool = False) -> None: ... + @property + def width(self) -> float: ... def add_character( self, character: str, diff --git a/stubs/fpdf2/fpdf/svg.pyi b/stubs/fpdf2/fpdf/svg.pyi index d619f2c56..520060697 100644 --- a/stubs/fpdf2/fpdf/svg.pyi +++ b/stubs/fpdf2/fpdf/svg.pyi @@ -29,6 +29,7 @@ def resolve_length(length_str, default_unit: str = "pt"): ... def resolve_angle(angle_str, default_unit: str = "deg"): ... def xmlns(space, name): ... def xmlns_lookup(space, *names): ... +def without_ns(qualified_tag: str) -> str: ... shape_tags: Incomplete diff --git a/stubs/fpdf2/fpdf/table.pyi b/stubs/fpdf2/fpdf/table.pyi index 563c18a37..28008484b 100644 --- a/stubs/fpdf2/fpdf/table.pyi +++ b/stubs/fpdf2/fpdf/table.pyi @@ -1,13 +1,13 @@ -from _typeshed import Incomplete +from _typeshed import Incomplete, SupportsItems from collections.abc import Iterable from dataclasses import dataclass from io import BytesIO -from typing import Literal +from typing import Literal, overload from PIL import Image from .drawing import DeviceGray, DeviceRGB -from .enums import Align, TableBordersLayout, TableCellFillMode, VAlign, WrapMode +from .enums import Align, TableBordersLayout, TableCellFillMode, TableSpan, VAlign, WrapMode from .fonts import FontFace from .fpdf import FPDF from .util import Padding @@ -43,7 +43,7 @@ class Table: ) -> None: ... def row(self, cells: Iterable[str] = (), style: FontFace | None = None) -> Row: ... def render(self) -> None: ... - def get_cell_border(self, i, j) -> str | Literal[0, 1]: ... + def get_cell_border(self, i: int, j: int, cell: Cell) -> str | Literal[0, 1]: ... class Row: cells: list[Cell] @@ -52,7 +52,9 @@ class Row: @property def cols_count(self) -> int: ... @property - def column_indices(self): ... + def max_rowspan(self) -> int: ... + def convert_spans(self, active_rowspans: SupportsItems[int, int]) -> tuple[dict[int, int], list[int]]: ... + @overload def cell( self, text: str = "", @@ -62,9 +64,24 @@ class Row: img: str | Image.Image | BytesIO | None = None, img_fill_width: bool = False, colspan: int = 1, + rowspan: int = 1, padding: tuple[float, ...] | None = None, link: str | int | None = None, - ) -> Cell: ... + ) -> str: ... + @overload + def cell( + self, + text: TableSpan, + align: str | Align | None = None, + v_align: str | VAlign | None = None, + style: FontFace | None = None, + img: str | Image.Image | BytesIO | None = None, + img_fill_width: bool = False, + colspan: int = 1, + rowspan: int = 1, + padding: tuple[float, ...] | None = None, + link: str | int | None = None, + ) -> TableSpan: ... @dataclass class Cell: @@ -75,6 +92,7 @@ class Cell: img: str | None img_fill_width: bool colspan: int + rowspan: int padding: int | tuple[float, ...] | None link: str | int | None @@ -83,7 +101,17 @@ class Cell: @dataclass(frozen=True) class RowLayoutInfo: height: int - triggers_page_jump: bool + pagebreak_height: float rendered_height: dict[Incomplete, Incomplete] + merged_heights: list[Incomplete] + +@dataclass(frozen=True) +class RowSpanLayoutInfo: + column: int + start: int + length: int + contents_height: float + + def row_range(self) -> range: ... def draw_box_borders(pdf: FPDF, x1, y1, x2, y2, border: str | Literal[0, 1], fill_color: Incomplete | None = None) -> None: ...