1# Copyright (c) 2023, Pubpub-ZZ
2#
3# All rights reserved.
4#
5# Redistribution and use in source and binary forms, with or without
6# modification, are permitted provided that the following conditions are
7# met:
8#
9# * Redistributions of source code must retain the above copyright notice,
10# this list of conditions and the following disclaimer.
11# * Redistributions in binary form must reproduce the above copyright notice,
12# this list of conditions and the following disclaimer in the documentation
13# and/or other materials provided with the distribution.
14# * The name of the author may not be used to endorse or promote products
15# derived from this software without specific prior written permission.
16#
17# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
18# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
19# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
20# ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
21# LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
22# CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
23# SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
24# INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
25# CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
26# ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
27# POSSIBILITY OF SUCH DAMAGE.
28
29from typing import (
30 Any,
31 Optional,
32 cast,
33)
34
35from ..constants import PageAttributes
36from ._base import BooleanObject, NameObject, NumberObject, is_null_or_none
37from ._data_structures import ArrayObject, DictionaryObject
38
39f_obj = BooleanObject(False)
40
41#: The page boundaries a viewer preference may name, per Table 147 in the 2.0
42#: reference.
43BOX_NAMES = [
44 PageAttributes.MEDIABOX,
45 PageAttributes.CROPBOX,
46 PageAttributes.BLEEDBOX,
47 PageAttributes.TRIMBOX,
48 PageAttributes.ARTBOX,
49]
50
51
52class ViewerPreferences(DictionaryObject):
53 def __init__(self, obj: Optional[DictionaryObject] = None) -> None:
54 super().__init__(self)
55 if not is_null_or_none(obj):
56 self.update(obj.items()) # type: ignore[union-attr]
57 try:
58 self.indirect_reference = obj.indirect_reference # type: ignore[union-attr]
59 except AttributeError:
60 pass
61
62 def _get_bool(self, key: str, default: Optional[BooleanObject]) -> Optional[BooleanObject]:
63 return self.get(key, default)
64
65 def _set_bool(self, key: str, v: bool) -> None:
66 self[NameObject(key)] = BooleanObject(v is True)
67
68 def _get_name(self, key: str, default: Optional[NameObject]) -> Optional[NameObject]:
69 return self.get(key, default)
70
71 def _set_name(self, key: str, lst: list[str], v: NameObject) -> None:
72 if v[0] != "/":
73 raise ValueError(f"{v} does not start with '/'")
74 if lst != [] and v not in lst:
75 raise ValueError(f"{v} is an unacceptable value")
76 self[NameObject(key)] = NameObject(v)
77
78 def _get_arr(self, key: str, default: Optional[list[Any]]) -> Optional[ArrayObject]:
79 return self.get(key, None if default is None else ArrayObject(default))
80
81 def _set_arr(self, key: str, v: Optional[ArrayObject]) -> None:
82 if v is None:
83 try:
84 del self[NameObject(key)]
85 except KeyError:
86 pass
87 return
88 if not isinstance(v, ArrayObject):
89 raise ValueError("ArrayObject is expected")
90 if key == "/PrintPageRange" and len(v) % 2:
91 # The array holds first/last page pairs, so an odd length leaves a
92 # range without its end.
93 raise ValueError(
94 f"/PrintPageRange holds page pairs, got {len(v)} entries: {v}"
95 )
96 self[NameObject(key)] = v
97
98 def _get_int(self, key: str, default: Optional[NumberObject]) -> Optional[NumberObject]:
99 return self.get(key, default)
100
101 def _set_int(self, key: str, v: int) -> None:
102 if v < 0:
103 raise ValueError(f"{v} is an unacceptable value for {key}")
104 self[NameObject(key)] = NumberObject(v)
105
106 @property
107 def PRINT_SCALING(self) -> NameObject:
108 return NameObject("/PrintScaling")
109
110 def __new__(cls: Any, value: Any = None) -> "ViewerPreferences": # noqa: PYI034
111 def _add_prop_bool(key: str, default: Optional[BooleanObject]) -> property:
112 return property(
113 lambda self: self._get_bool(key, default),
114 lambda self, v: self._set_bool(key, v),
115 None,
116 f"""
117 Returns/Modify the status of {key}, Returns {default} if not defined
118 """,
119 )
120
121 def _add_prop_name(
122 key: str, lst: list[str], default: Optional[NameObject]
123 ) -> property:
124 return property(
125 lambda self: self._get_name(key, default),
126 lambda self, v: self._set_name(key, lst, v),
127 None,
128 f"""
129 Returns/Modify the status of {key}, Returns {default} if not defined.
130 Acceptable values: {lst}
131 """,
132 )
133
134 def _add_prop_arr(key: str, default: Optional[ArrayObject]) -> property:
135 return property(
136 lambda self: self._get_arr(key, default),
137 lambda self, v: self._set_arr(key, v),
138 None,
139 f"""
140 Returns/Modify the status of {key}, Returns {default} if not defined
141 """,
142 )
143
144 def _add_prop_int(key: str, default: Optional[int]) -> property:
145 return property(
146 lambda self: self._get_int(key, default),
147 lambda self, v: self._set_int(key, v),
148 None,
149 f"""
150 Returns/Modify the status of {key}, Returns {default} if not defined
151 """,
152 )
153
154 cls.hide_toolbar = _add_prop_bool("/HideToolbar", f_obj)
155 cls.hide_menubar = _add_prop_bool("/HideMenubar", f_obj)
156 cls.hide_windowui = _add_prop_bool("/HideWindowUI", f_obj)
157 cls.fit_window = _add_prop_bool("/FitWindow", f_obj)
158 cls.center_window = _add_prop_bool("/CenterWindow", f_obj)
159 cls.display_doctitle = _add_prop_bool("/DisplayDocTitle", f_obj)
160
161 cls.non_fullscreen_pagemode = _add_prop_name(
162 "/NonFullScreenPageMode",
163 ["/UseNone", "/UseOutlines", "/UseThumbs", "/UseOC"],
164 NameObject("/UseNone"),
165 )
166 cls.direction = _add_prop_name(
167 "/Direction", ["/L2R", "/R2L"], NameObject("/L2R")
168 )
169 cls.view_area = _add_prop_name("/ViewArea", BOX_NAMES, None)
170 cls.view_clip = _add_prop_name("/ViewClip", BOX_NAMES, None)
171 cls.print_area = _add_prop_name("/PrintArea", BOX_NAMES, None)
172 cls.print_clip = _add_prop_name("/PrintClip", BOX_NAMES, None)
173 cls.print_scaling = _add_prop_name(
174 "/PrintScaling", ["/None", "/AppDefault"], None
175 )
176 cls.duplex = _add_prop_name(
177 "/Duplex", ["/Simplex", "/DuplexFlipShortEdge", "/DuplexFlipLongEdge"], None
178 )
179 cls.pick_tray_by_pdfsize = _add_prop_bool("/PickTrayByPDFSize", None)
180 cls.print_pagerange = _add_prop_arr("/PrintPageRange", None)
181 cls.num_copies = _add_prop_int("/NumCopies", None)
182
183 cls.enforce = _add_prop_arr("/Enforce", ArrayObject())
184
185 return cast("ViewerPreferences", DictionaryObject.__new__(cls))