1# SPDX-FileCopyrightText: 2022 James R. Barlow
2# SPDX-License-Identifier: MPL-2.0
3
4"""DocumentInfo dictionary access with type-safe operations."""
5
6from __future__ import annotations
7
8from collections.abc import Iterator
9from typing import TYPE_CHECKING
10
11from pikepdf.models.metadata._constants import clean
12from pikepdf.objects import Name
13
14if TYPE_CHECKING: # pragma: no cover
15 from pikepdf import Pdf
16
17
18class DocinfoStore:
19 """Wrapper for PDF DocumentInfo dictionary operations.
20
21 Handles reading and writing values with proper encoding (ASCII/UTF-16)
22 and character cleaning.
23 """
24
25 def __init__(self, pdf: Pdf):
26 """Initialize with PDF reference.
27
28 Args:
29 pdf: The PDF document to manage DocumentInfo for.
30 """
31 self._pdf = pdf
32
33 def get(self, name: Name) -> str | None:
34 """Get DocumentInfo value.
35
36 Args:
37 name: The DocumentInfo key (e.g., Name.Title).
38
39 Returns:
40 The value as string, or None if not present.
41 """
42 if name not in self._pdf.docinfo:
43 return None
44 val = self._pdf.docinfo[name]
45 return str(val) if val is not None else None
46
47 def set(self, name: Name, value: str) -> None:
48 """Set DocumentInfo value with proper encoding.
49
50 Values that can be encoded as ASCII are stored as ASCII,
51 otherwise stored as UTF-16 with BOM.
52
53 Args:
54 name: The DocumentInfo key (e.g., Name.Title).
55 value: The string value to set.
56 """
57 # Ensure docinfo exists
58 self._pdf.docinfo # pylint: disable=pointless-statement
59 value = clean(value)
60 try:
61 # Try to save pure ASCII
62 self._pdf.docinfo[name] = value.encode('ascii')
63 except UnicodeEncodeError:
64 # qpdf will serialize this as a UTF-16 with BOM string
65 self._pdf.docinfo[name] = value
66
67 def delete(self, name: Name) -> bool:
68 """Delete DocumentInfo key.
69
70 Args:
71 name: The DocumentInfo key to delete.
72
73 Returns:
74 True if key was present and deleted, False if not present.
75 """
76 if name in self._pdf.docinfo:
77 del self._pdf.docinfo[name]
78 return True
79 return False
80
81 def __contains__(self, name: Name) -> bool:
82 """Check if key exists in DocumentInfo."""
83 return name in self._pdf.docinfo
84
85 def keys(self) -> Iterator[Name]:
86 """Iterate DocumentInfo keys."""
87 yield from self._pdf.docinfo.keys() # type: ignore[misc]