Coverage for /pythoncovmergedfiles/medio/medio/usr/local/lib/python3.11/site-packages/pikepdf/_cpphelpers.py: 20%

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

59 statements  

1# SPDX-FileCopyrightText: 2022 James R. Barlow 

2# SPDX-License-Identifier: MPL-2.0 

3 

4"""Support functions called by the C++ library binding layer. 

5 

6Not intended to be called from Python, and subject to change at any time. 

7""" 

8 

9from __future__ import annotations 

10 

11from typing import TYPE_CHECKING, Callable 

12from warnings import warn 

13 

14from pikepdf.exceptions import PdfError 

15from pikepdf.objects import Name 

16 

17if TYPE_CHECKING: 

18 from pikepdf._core import Pdf 

19 from pikepdf.objects import Dictionary 

20 

21 

22def update_xmp_pdfversion(pdf: Pdf, version: str) -> None: 

23 """Update XMP metadata to specified PDF version.""" 

24 if Name.Metadata not in pdf.Root: 

25 return # Don't create an empty XMP object just to store the version 

26 

27 try: 

28 with pdf.open_metadata( 

29 set_pikepdf_as_editor=False, update_docinfo=False 

30 ) as meta: 

31 if 'pdf:PDFVersion' in meta: 

32 meta['pdf:PDFVersion'] = version 

33 except Exception as e: 

34 raise PdfError("While trying to update XMP metadata, an error occurred") from e 

35 

36 

37def _alpha(n: int) -> str: 

38 """Excel-style column numbering A..Z, AA..AZ..BA..ZZ.., AAA.""" 

39 if n < 1: 

40 raise ValueError(f"Can't represent {n} in alphabetic numbering") 

41 p = [] 

42 while n > 0: 

43 n, r = divmod(n - 1, 26) 

44 p.append(r) 

45 base = ord('A') 

46 ords = [(base + v) for v in reversed(p)] 

47 return ''.join(chr(o) for o in ords) 

48 

49 

50def _roman(n: int) -> str: 

51 """Convert integer n to Roman numeral representation as a string.""" 

52 if not (1 <= n <= 5000): 

53 raise ValueError(f"Can't represent {n} in Roman numerals") 

54 roman_numerals = ( 

55 (1000, 'M'), 

56 (900, 'CM'), 

57 (500, 'D'), 

58 (400, 'CD'), 

59 (100, 'C'), 

60 (90, 'XC'), 

61 (50, 'L'), 

62 (40, 'XL'), 

63 (10, 'X'), 

64 (9, 'IX'), 

65 (5, 'V'), 

66 (4, 'IV'), 

67 (1, 'I'), 

68 ) 

69 roman = "" 

70 for value, numeral in roman_numerals: 

71 while n >= value: 

72 roman += numeral 

73 n -= value 

74 return roman 

75 

76 

77LABEL_STYLE_MAP: dict[str, Callable[[int], str]] = { 

78 "D": str, 

79 "A": _alpha, 

80 "a": lambda x: _alpha(x).lower(), 

81 "R": _roman, 

82 "r": lambda x: _roman(x).lower(), 

83} 

84 

85 

86def label_from_label_dict(label_dict: int | Dictionary) -> str: 

87 """Convert a label dictionary returned by qpdf into a text string.""" 

88 if isinstance(label_dict, int): 

89 return str(label_dict) 

90 

91 label = '' 

92 if Name.P in label_dict: 

93 prefix = label_dict[Name.P] 

94 label += str(prefix) 

95 

96 # If there is no S, return only the P portion 

97 if Name.S in label_dict: 

98 # St defaults to 1 

99 numeric_value = label_dict[Name.St] if Name.St in label_dict else 1 

100 if not isinstance(numeric_value, int): 

101 warn( 

102 "Page label dictionary has invalid non-integer start value", UserWarning 

103 ) 

104 numeric_value = 1 

105 

106 style = label_dict[Name.S] 

107 if isinstance(style, Name): 

108 style_fn = LABEL_STYLE_MAP[str(style)[1:]] 

109 value = style_fn(numeric_value) 

110 label += value 

111 else: 

112 warn("Page label dictionary has invalid page label style", UserWarning) 

113 

114 return label