File: image_datastructures.py

package info (click to toggle)
fpdf2 2.8.7-2
  • links: PTS, VCS
  • area: main
  • in suites: forky, sid
  • size: 114,352 kB
  • sloc: python: 50,410; sh: 133; makefile: 12
file content (101 lines) | stat: -rw-r--r-- 3,115 bytes parent folder | download
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
# pyright: reportUnknownVariableType=false
from dataclasses import dataclass, field
from typing import Literal, TypeAlias, cast

ImageFilter: TypeAlias = Literal[
    "AUTO",
    "FlateDecode",
    "DCTDecode",
    "JPXDecode",
    "LZWDecode",
    "CCITTFaxDecode",
]


class ImageInfo(dict[str, object]):
    """Information about an image used in the PDF document (base class).
    We subclass this to distinguish between raster and vector images."""

    @property
    def width(self) -> float:
        "Intrinsic image width"
        return cast(float, self["w"])

    @property
    def height(self) -> float:
        "Intrinsic image height"
        return cast(float, self["h"])

    @property
    def rendered_width(self) -> float:
        "Only available if the image has been placed on the document"
        return cast(float, self["rendered_width"])

    @property
    def rendered_height(self) -> float:
        "Only available if the image has been placed on the document"
        return cast(float, self["rendered_height"])

    def __str__(self) -> str:
        d = {
            k: ("..." if k in ("data", "iccp", "smask") else v) for k, v in self.items()
        }
        return f"self.__class__.__name__({d})"

    def scale_inside_box(
        self, x: float, y: float, w: float, h: float
    ) -> tuple[float, float, float, float]:
        """
        Make an image fit within a bounding box, maintaining its proportions.
        In the reduced dimension it will be centered within the available space.
        """
        img_w: float = self["w"]  # type: ignore
        img_h: float = self["h"]  # type: ignore
        ratio = img_w / img_h
        if h * ratio < w:
            new_w = h * ratio
            new_h = h
            x += (w - new_w) / 2
        else:  # => too wide, limiting width:
            new_h = w / ratio
            new_w = w
            y += (h - new_h) / 2
        return x, y, new_w, new_h


class RasterImageInfo(ImageInfo):
    "Information about a raster image used in the PDF document"

    def size_in_document_units(
        self, w: float, h: float, scale: float = 1
    ) -> tuple[float, float]:
        img_w: float = self["w"]  # type: ignore
        img_h: float = self["h"]  # type: ignore
        if w == 0 and h == 0:  # Put image at 72 dpi
            w = img_w / scale
            h = img_h / scale
        elif w == 0:
            w = h * img_w / img_h
        elif h == 0:
            h = w * img_h / img_w
        return w, h


class VectorImageInfo(ImageInfo):
    "Information about a vector image used in the PDF document"

    # pass


@dataclass
class ImageCache:
    # Map image identifiers to dicts describing raster or vector images
    images: dict[str, RasterImageInfo | VectorImageInfo] = field(default_factory=dict)
    # Map icc profiles (bytes) to their index (number)
    icc_profiles: dict[bytes, int] = field(default_factory=dict)
    # Must be one of SUPPORTED_IMAGE_FILTERS values
    image_filter: ImageFilter = "AUTO"

    def reset_usages(self) -> None:
        for img in self.images.values():
            img["usages"] = 0