Coverage for /pythoncovmergedfiles/medio/medio/usr/local/lib/python3.11/site-packages/pikepdf/_cpphelpers.py: 22%
Shortcuts on this page
r m x toggle line displays
j k next/prev highlighted chunk
0 (zero) top of page
1 (one) first highlighted chunk
Shortcuts on this page
r m x toggle line displays
j k next/prev highlighted chunk
0 (zero) top of page
1 (one) first highlighted chunk
1# SPDX-FileCopyrightText: 2022 James R. Barlow
2# SPDX-License-Identifier: MPL-2.0
4"""Support functions called by the C++ library binding layer.
6Not intended to be called from Python, and subject to change at any time.
7"""
9from __future__ import annotations
11from collections.abc import Callable
12from typing import TYPE_CHECKING
13from warnings import warn
15from pikepdf.exceptions import PdfError
16from pikepdf.objects import Name
18if TYPE_CHECKING:
19 from pikepdf._core import Pdf
20 from pikepdf.objects import Dictionary
23def update_xmp_pdfversion(pdf: Pdf, version: str) -> None:
24 """Update XMP metadata to specified PDF version."""
25 if Name.Metadata not in pdf.Root:
26 return # Don't create an empty XMP object just to store the version
28 try:
29 with pdf.open_metadata(
30 set_pikepdf_as_editor=False, update_docinfo=False
31 ) as meta:
32 if 'pdf:PDFVersion' in meta:
33 meta['pdf:PDFVersion'] = version
34 except Exception as e:
35 raise PdfError("While trying to update XMP metadata, an error occurred") from e
38def _alpha(n: int) -> str:
39 """Excel-style column numbering A..Z, AA..AZ..BA..ZZ.., AAA."""
40 if n < 1:
41 raise ValueError(f"Can't represent {n} in alphabetic numbering")
42 p = []
43 while n > 0:
44 n, r = divmod(n - 1, 26)
45 p.append(r)
46 base = ord('A')
47 ords = [(base + v) for v in reversed(p)]
48 return ''.join(chr(o) for o in ords)
51def _roman(n: int) -> str:
52 """Convert integer n to Roman numeral representation as a string."""
53 if not (1 <= n <= 5000):
54 raise ValueError(f"Can't represent {n} in Roman numerals")
55 roman_numerals = (
56 (1000, 'M'),
57 (900, 'CM'),
58 (500, 'D'),
59 (400, 'CD'),
60 (100, 'C'),
61 (90, 'XC'),
62 (50, 'L'),
63 (40, 'XL'),
64 (10, 'X'),
65 (9, 'IX'),
66 (5, 'V'),
67 (4, 'IV'),
68 (1, 'I'),
69 )
70 roman = ""
71 for value, numeral in roman_numerals:
72 while n >= value:
73 roman += numeral
74 n -= value
75 return roman
78LABEL_STYLE_MAP: dict[str, Callable[[int], str]] = {
79 "D": str,
80 "A": _alpha,
81 "a": lambda x: _alpha(x).lower(),
82 "R": _roman,
83 "r": lambda x: _roman(x).lower(),
84}
87def label_from_label_dict(label_dict: int | Dictionary) -> str:
88 """Convert a label dictionary returned by qpdf into a text string."""
89 if isinstance(label_dict, int):
90 return str(label_dict)
92 label = ''
93 if Name.P in label_dict:
94 prefix = label_dict[Name.P]
95 label += str(prefix)
97 # If there is no S, return only the P portion
98 if Name.S in label_dict:
99 # St defaults to 1
100 numeric_value = label_dict[Name.St] if Name.St in label_dict else 1
101 if not isinstance(numeric_value, int):
102 warn(
103 "Page label dictionary has invalid non-integer start value", UserWarning
104 )
105 numeric_value = 1
107 style = label_dict[Name.S]
108 if isinstance(style, Name):
109 style_fn = LABEL_STYLE_MAP[str(style)[1:]]
110 value = style_fn(numeric_value)
111 label += value
112 else:
113 warn("Page label dictionary has invalid page label style", UserWarning)
115 return label