Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions .pre-commit-config.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -35,3 +35,6 @@ repos:
hooks:
- id: mypy
files: ^pypdf/.*
additional_dependencies:
- cryptography
- pycryptodome
2 changes: 1 addition & 1 deletion Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@ benchmark:
pytest tests/bench.py

mypy:
mypy pypdf --ignore-missing-imports --check-untyped --strict
mypy pypdf

ruff:
ruff check pypdf tests make_release.py
4 changes: 2 additions & 2 deletions pypdf/_doc_common.py
Original file line number Diff line number Diff line change
Expand Up @@ -92,7 +92,7 @@ def convert_to_int(d: bytes, size: int) -> Union[int, tuple[Any, ...]]:
raise PdfReadError("Invalid size in convert_to_int")
d = b"\x00\x00\x00\x00\x00\x00\x00\x00" + d
d = d[-8:]
return struct.unpack(">q", d)[0]
return cast(int, struct.unpack(">q", d)[0])


class DocumentInformation(DictionaryObject):
Expand Down Expand Up @@ -382,7 +382,7 @@ def recursive_call(
node: DictionaryObject, mi: int
) -> tuple[Optional[PdfObject], int]:
ma = cast(int, node.get("/Count", 1)) # default 1 for /Page types
if node["/Type"] == "/Page":
if node["/Type"] == "/Page": # type: ignore[comparison-overlap]
if page_number == mi:
return node, -1
return None, mi + 1
Expand Down
14 changes: 7 additions & 7 deletions pypdf/_page.py
Original file line number Diff line number Diff line change
Expand Up @@ -540,7 +540,7 @@ def user_unit(self) -> float:
space unit is 1/72 inch, and a value of 3 means that a user
space unit is 3/72 inch.
"""
return self.get(PG.USER_UNIT, 1)
return cast(float, self.get(PG.USER_UNIT, 1))

@staticmethod
def create_blank_page(
Expand Down Expand Up @@ -947,15 +947,15 @@ def _add_transformation_matrix(
ctm: CompressedTransformationMatrix,
) -> ContentStream:
"""Add transformation matrix at the beginning of the given contents stream."""
contents = ContentStream(contents, pdf)
contents.operations.insert(
content_stream = ContentStream(contents, pdf)
content_stream.operations.insert(
0,
[
(
Comment thread
stefan6419846 marked this conversation as resolved.
[FloatObject(x) for x in ctm],
b"cm",
],
),
)
return contents
return content_stream

def _get_contents_as_bytes(self) -> Optional[bytes]:
"""
Expand Down Expand Up @@ -1627,7 +1627,7 @@ def page_number(self) -> Optional[int]:
return None
try:
lst = self.indirect_reference.pdf.pages
return lst.index(self)
return int(lst.index(self))
except ValueError:
return None

Expand Down
2 changes: 1 addition & 1 deletion pypdf/_page_labels.py
Original file line number Diff line number Diff line change
Expand Up @@ -156,7 +156,7 @@ def get_label_from_nums(dictionary_object: DictionaryObject, index: int) -> str:
if not isinstance(value, dict):
return str(index + 1) # Fallback
start = value.get("/St", 1)
prefix = value.get("/P", "")
prefix = cast(str, value.get("/P", ""))
mapping_function = m[value.get("/S")]
return prefix + mapping_function(index - start_index + start)

Expand Down
4 changes: 2 additions & 2 deletions pypdf/_reader.py
Original file line number Diff line number Diff line change
Expand Up @@ -730,7 +730,7 @@ def _basic_validation(self, stream: StreamType) -> None:
f"PDF starts with '{header_byte.decode('utf8')}', "
"but '%PDF-' expected"
)
logger_warning(f"invalid pdf header: {header_byte}", __name__)
logger_warning(f"invalid pdf header: {header_byte!r}", __name__)
stream.seek(0, os.SEEK_END)

def _find_eof_marker(self, stream: StreamType) -> None:
Expand Down Expand Up @@ -1005,7 +1005,7 @@ def _read_xref(self, stream: StreamType) -> Optional[int]:
)
stream.seek(p, 0)
if "/Prev" in new_trailer:
return new_trailer["/Prev"]
return cast(int, new_trailer["/Prev"])
return None

def _read_xref_other_error(
Expand Down
4 changes: 2 additions & 2 deletions pypdf/_text_extraction/_layout_mode/_text_state_manager.py
Original file line number Diff line number Diff line change
Expand Up @@ -132,7 +132,7 @@ def raw_transform(
_d: float = 1.0,
_e: float = 0.0,
_f: float = 0.0,
) -> dict[int, float]:
) -> TextStateManagerDictType:
"""Only a/b/c/d/e/f matrix params"""
return dict(zip(range(6), map(float, (_a, _b, _c, _d, _e, _f))))

Expand All @@ -148,7 +148,7 @@ def new_transform(
is_render: bool = False,
) -> TextStateManagerDictType:
"""Standard a/b/c/d/e/f matrix params + 'is_text' and 'is_render' keys"""
result: Any = TextStateManager.raw_transform(_a, _b, _c, _d, _e, _f)
result = TextStateManager.raw_transform(_a, _b, _c, _d, _e, _f)
result.update({"is_text": is_text, "is_render": is_render})
return result

Expand Down
5 changes: 3 additions & 2 deletions pypdf/_utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -72,6 +72,7 @@
]

StreamType = IO[Any]
BinaryStreamType = IO[bytes]
StrByteType = Union[str, StreamType]


Expand Down Expand Up @@ -181,7 +182,7 @@ def read_until_whitespace(stream: StreamType, maxchars: Optional[int] = None) ->
return txt


def read_non_whitespace(stream: StreamType) -> bytes:
def read_non_whitespace(stream: BinaryStreamType) -> bytes:
"""
Find and read the next non-whitespace character (ignores whitespace).

Expand Down Expand Up @@ -282,7 +283,7 @@ def read_until_regex(stream: StreamType, regex: Pattern[bytes]) -> bytes:
return b"".join(parts)


def read_block_backwards(stream: StreamType, to_read: int) -> bytes:
def read_block_backwards(stream: BinaryStreamType, to_read: int) -> bytes:
"""
Given a stream at position X, read a block of size to_read ending at position X.

Expand Down
4 changes: 2 additions & 2 deletions pypdf/_writer.py
Original file line number Diff line number Diff line change
Expand Up @@ -3007,9 +3007,9 @@ def _insert_filtered_annotations(
for an in annots:
ano = cast("DictionaryObject", an.get_object())
if (
ano["/Subtype"] != "/Link"
ano["/Subtype"] != "/Link" # type: ignore[comparison-overlap]
or "/A" not in ano
or cast("DictionaryObject", ano["/A"])["/S"] != "/GoTo"
or cast("DictionaryObject", ano["/A"])["/S"] != "/GoTo" # type: ignore[comparison-overlap]
or "/Dest" in ano
):
if "/Dest" not in ano:
Expand Down
2 changes: 1 addition & 1 deletion pypdf/annotations/_base.py
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ def __init__(self) -> None:

@property
def flags(self) -> AnnotationFlag:
return self.get(NameObject("/F"), AnnotationFlag(0))
return AnnotationFlag(self.get(NameObject("/F"), 0))

@flags.setter
def flags(self, value: AnnotationFlag) -> None:
Expand Down
8 changes: 4 additions & 4 deletions pypdf/filters.py
Original file line number Diff line number Diff line change
Expand Up @@ -216,11 +216,11 @@ def decode(
if predictor == 2:
row_length -= 1 # remove the predictor byte
bpp = row_length // columns
str_data = bytearray(str_data)
for i in range(len(str_data)):
str_data_mut = bytearray(str_data)
for i in range(len(str_data_mut)):
if i % row_length >= bpp:
str_data[i] = (str_data[i] + str_data[i - bpp]) % 256
str_data = bytes(str_data)
str_data_mut[i] = (str_data_mut[i] + str_data_mut[i - bpp]) % 256
str_data = bytes(str_data_mut)
# PNG prediction:
elif 10 <= predictor <= 15:
str_data = FlateDecode._decode_png_prediction(
Expand Down
21 changes: 14 additions & 7 deletions pypdf/generic/_base.py
Original file line number Diff line number Diff line change
Expand Up @@ -136,8 +136,8 @@ def clone(
)

def _reference_clone(
self, clone: Any, pdf_dest: PdfWriterProtocol, force_duplicate: bool = False
) -> PdfObjectProtocol:
self, clone: "PdfObject", pdf_dest: PdfWriterProtocol, force_duplicate: bool = False
) -> "PdfObject":
"""
Reference the object within the _objects of pdf_dest only if
indirect_reference attribute exists (which means the objects was
Expand All @@ -153,7 +153,11 @@ def _reference_clone(

"""
try:
if not force_duplicate and clone.indirect_reference.pdf == pdf_dest:
if (
not force_duplicate
and clone.indirect_reference is not None
and clone.indirect_reference.pdf == pdf_dest
):
return clone
except Exception:
pass
Expand Down Expand Up @@ -182,7 +186,7 @@ def _reference_clone(
obj = pdf_dest.get_object(
pdf_dest._id_translated[id(ind.pdf)][ind.idnum]
)
assert obj is not None
assert isinstance(obj, PdfObject), "mypy"
return obj
pdf_dest._id_translated[id(ind.pdf)][ind.idnum] = i
try:
Expand Down Expand Up @@ -252,6 +256,8 @@ def __hash__(self) -> int:


class BooleanObject(PdfObject):
value: bool

def __init__(self, value: Any) -> None:
self.value = value

Expand Down Expand Up @@ -370,7 +376,7 @@ def clone(
dup = pdf_dest._add_object(
obj.clone(pdf_dest, force_duplicate, ignore_fields)
)
assert dup is not None, "mypy"
assert isinstance(dup, PdfObject), "mypy"
assert dup.indirect_reference is not None, "mypy"
return dup.indirect_reference

Expand All @@ -379,7 +385,8 @@ def indirect_reference(self) -> "IndirectObject": # type: ignore[override]
return self

def get_object(self) -> Optional["PdfObject"]:
return self.pdf.get_object(self)
obj: Optional[PdfObject] = self.pdf.get_object(self)
Comment thread
stefan6419846 marked this conversation as resolved.
return obj

def __deepcopy__(self, memo: Any) -> "IndirectObject":
return IndirectObject(self.idnum, self.generation, self.pdf)
Expand Down Expand Up @@ -517,7 +524,7 @@ def hash_bin(self) -> int:
return hash((self.__class__, self.as_numeric))

def myrepr(self) -> str:
if self == 0:
if self == 0: # type: ignore[comparison-overlap]
return "0.0"
nb = FLOAT_WRITE_PRECISION - int(log10(abs(self)))
return f"{self:.{max(1, nb)}f}".rstrip("0").rstrip(".")
Expand Down
50 changes: 27 additions & 23 deletions pypdf/generic/_data_structures.py
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,7 @@
from .._protocols import PdfReaderProtocol, PdfWriterProtocol, XmpInformationProtocol
from .._utils import (
WHITESPACES,
BinaryStreamType,
StreamType,
deprecation_no_replacement,
logger_warning,
Expand Down Expand Up @@ -165,20 +166,21 @@ def items(self) -> Iterable[Any]:

def _to_lst(self, lst: Any) -> list[Any]:
# Convert to list, internal
result: list[Any]
if isinstance(lst, (list, tuple, set)):
pass
result = list(lst)
elif isinstance(lst, PdfObject):
lst = [lst]
result = [lst]
elif isinstance(lst, str):
if lst[0] == "/":
lst = [NameObject(lst)]
result = [NameObject(lst)]
else:
lst = [TextStringObject(lst)]
result = [TextStringObject(lst)]
elif isinstance(lst, bytes):
lst = [ByteStringObject(lst)]
result = [ByteStringObject(lst)]
else: # for numbers,...
lst = [lst]
return lst
result = [lst]
return result

def __add__(self, lst: Any) -> "ArrayObject":
"""
Expand Down Expand Up @@ -476,7 +478,7 @@ def setdefault(self, key: Any, value: Optional[Any] = None) -> Any:
return dict.setdefault(self, key, value)

def __getitem__(self, key: Any) -> PdfObject:
return dict.__getitem__(self, key).get_object()
return cast(PdfObject, dict.__getitem__(self, key).get_object())

@property
def xmp_metadata(self) -> Optional[XmpInformationProtocol]:
Expand Down Expand Up @@ -532,7 +534,7 @@ def _get_next_object_position(

@classmethod
def _read_unsized_from_stream(
cls, stream: StreamType, pdf: PdfReaderProtocol
cls, stream: BinaryStreamType, pdf: PdfReaderProtocol
) -> bytes:
object_position = cls._get_next_object_position(
position_before=stream.tell(), position_end=2 ** 32, generations=list(pdf.xref), pdf=pdf
Expand Down Expand Up @@ -768,45 +770,46 @@ def insert_child(
if inc_parent_counter is None:
inc_parent_counter = self.inc_parent_counter_default
child_obj = child.get_object()
child = child.indirect_reference # get_reference(child_obj)
assert child.indirect_reference is not None, "mypy"
child_reference: IndirectObject = child.indirect_reference

prev: Optional[DictionaryObject]
if "/First" not in self: # no child yet
self[NameObject("/First")] = child
self[NameObject("/First")] = child_reference
self[NameObject("/Count")] = NumberObject(0)
self[NameObject("/Last")] = child
self[NameObject("/Last")] = child_reference
child_obj[NameObject("/Parent")] = self.indirect_reference
inc_parent_counter(self, child_obj.get("/Count", 1))
if "/Next" in child_obj:
del child_obj["/Next"]
if "/Prev" in child_obj:
del child_obj["/Prev"]
return child
return child_reference
prev = cast("DictionaryObject", self["/Last"])

while prev.indirect_reference != before:
if "/Next" in prev:
prev = cast("TreeObject", prev["/Next"])
else: # append at the end
prev[NameObject("/Next")] = cast("TreeObject", child)
prev[NameObject("/Next")] = cast("TreeObject", child_reference)
child_obj[NameObject("/Prev")] = prev.indirect_reference
child_obj[NameObject("/Parent")] = self.indirect_reference
if "/Next" in child_obj:
del child_obj["/Next"]
self[NameObject("/Last")] = child
self[NameObject("/Last")] = child_reference
inc_parent_counter(self, child_obj.get("/Count", 1))
return child
return child_reference
try: # insert as first or in the middle
assert isinstance(prev["/Prev"], DictionaryObject)
prev["/Prev"][NameObject("/Next")] = child
prev["/Prev"][NameObject("/Next")] = child_reference
child_obj[NameObject("/Prev")] = prev["/Prev"]
except Exception: # it means we are inserting in first position
del child_obj["/Next"]
child_obj[NameObject("/Next")] = prev
prev[NameObject("/Prev")] = child
prev[NameObject("/Prev")] = child_reference
child_obj[NameObject("/Parent")] = self.indirect_reference
inc_parent_counter(self, child_obj.get("/Count", 1))
return child
return child_reference

def _remove_node_from_tree(
self, prev: Any, prev_ref: Any, cur: Any, last: Any
Expand Down Expand Up @@ -1211,7 +1214,7 @@ def __init__(
f"{MAX_ARRAY_BASED_STREAM_OUTPUT_LENGTH} output bytes."
)
data += new_data
if len(data) == 0 or data[-1] != b"\n":
if len(data) == 0 or data[-1:] != b"\n":
# There should be no direct need to check for a change of one byte.
length += 1
data += b"\n"
Expand Down Expand Up @@ -1775,8 +1778,9 @@ def bottom(self) -> Optional[FloatObject]:
@property
def color(self) -> Optional["ArrayObject"]:
"""Read-only property accessing the color in (R, G, B) with values 0.0-1.0."""
return self.get(
"/C", ArrayObject([FloatObject(0), FloatObject(0), FloatObject(0)])
return cast(
"ArrayObject",
self.get("/C", ArrayObject([FloatObject(0), FloatObject(0), FloatObject(0)])),
)

@property
Expand All @@ -1786,7 +1790,7 @@ def font_format(self) -> Optional[OutlineFontFlag]:

1=italic, 2=bold, 3=both
"""
return self.get("/F", 0)
return OutlineFontFlag(self.get("/F", 0))

@property
def outline_count(self) -> Optional[int]:
Expand Down
Loading
Loading