@@ -282,9 +282,7 @@ class PdfReader:
282282 @property
283283 def viewer_preferences (self ) -> Optional [ViewerPreferences ]:
284284 """Returns the existing ViewerPreferences as an overloaded dictionary."""
285- o = cast (DictionaryObject , self .trailer ["/Root" ]).get (
286- CD .VIEWER_PREFERENCES , None
287- )
285+ o = self .root_object .get (CD .VIEWER_PREFERENCES , None )
288286 if o is None :
289287 return None
290288 o = o .get_object ()
@@ -344,6 +342,33 @@ def __init__(
344342 elif password is not None :
345343 raise PdfReadError ("Not encrypted file" )
346344
345+ @property
346+ def root_object (self ) -> DictionaryObject :
347+ """Provide access to "/Root". standardized with PdfWriter."""
348+ return cast (DictionaryObject , self .trailer [TK .ROOT ].get_object ())
349+
350+ @property
351+ def _info (self ) -> Optional [DictionaryObject ]:
352+ """
353+ Provide access to "/Info". standardized with PdfWriter.
354+
355+ Returns:
356+ /Info Dictionary ; None if the entry does not exists
357+ """
358+ info = self .trailer .get (TK .INFO , None )
359+ return None if info is None else cast (DictionaryObject , info .get_object ())
360+
361+ @property
362+ def _ID (self ) -> Optional [ArrayObject ]:
363+ """
364+ Provide access to "/ID". standardized with PdfWriter.
365+
366+ Returns:
367+ /ID array ; None if the entry does not exists
368+ """
369+ id = self .trailer .get (TK .ID , None )
370+ return None if id is None else cast (ArrayObject , id .get_object ())
371+
347372 def _repr_mimebundle_ (
348373 self ,
349374 include : Union [None , Iterable [str ]] = None ,
@@ -400,21 +425,20 @@ def metadata(self) -> Optional[DocumentInformation]:
400425 """
401426 if TK .INFO not in self .trailer :
402427 return None
403- obj = self .trailer [TK .INFO ]
404428 retval = DocumentInformation ()
405- if isinstance (obj , type (None )):
429+ if isinstance (self . _info , type (None )):
406430 raise PdfReadError (
407431 "trailer not found or does not point to document information directory"
408432 )
409- retval .update (obj ) # type: ignore
433+ retval .update (self . _info ) # type: ignore
410434 return retval
411435
412436 @property
413437 def xmp_metadata (self ) -> Optional [XmpInformation ]:
414438 """XMP (Extensible Metadata Platform) data."""
415439 try :
416440 self ._override_encryption = True
417- return self .trailer [ TK . ROOT ] .xmp_metadata # type: ignore
441+ return self .root_object .xmp_metadata # type: ignore
418442 finally :
419443 self ._override_encryption = False
420444
@@ -433,7 +457,7 @@ def _get_num_pages(self) -> int:
433457 # the PDF file's page count is used in this case. Otherwise,
434458 # the original method (flattened page count) is used.
435459 if self .is_encrypted :
436- return self .trailer [ TK . ROOT ] ["/Pages" ]["/Count" ] # type: ignore
460+ return self .root_object ["/Pages" ]["/Count" ] # type: ignore
437461 else :
438462 if self .flattened_pages is None :
439463 self ._flatten ()
@@ -493,7 +517,7 @@ def get_fields(
493517 field_attributes .update (CheckboxRadioButtonAttributes .attributes_dict ())
494518 if retval is None :
495519 retval = {}
496- catalog = cast ( DictionaryObject , self .trailer [ TK . ROOT ])
520+ catalog = self .root_object
497521 # get the AcroForm tree
498522 if CD .ACRO_FORM in catalog :
499523 tree = cast (Optional [TreeObject ], catalog [CD .ACRO_FORM ])
@@ -755,7 +779,7 @@ def _get_named_destinations(
755779 """
756780 if retval is None :
757781 retval = {}
758- catalog = cast ( DictionaryObject , self .trailer [ TK . ROOT ])
782+ catalog = self .root_object
759783
760784 # get the name tree
761785 if CA .DESTS in catalog :
@@ -822,7 +846,7 @@ def _get_outline(
822846 ) -> OutlineType :
823847 if outline is None :
824848 outline = []
825- catalog = cast ( DictionaryObject , self .trailer [ TK . ROOT ])
849+ catalog = self .root_object
826850
827851 # get the outline dictionary and named destinations
828852 if CO .OUTLINES in catalog :
@@ -868,7 +892,7 @@ def threads(self) -> Optional[ArrayObject]:
868892 It's an array of dictionaries with "/F" and "/I" properties or
869893 None if there are no articles.
870894 """
871- catalog = cast ( DictionaryObject , self .trailer [ TK . ROOT ])
895+ catalog = self .root_object
872896 if CO .THREADS in catalog :
873897 return cast ("ArrayObject" , catalog [CO .THREADS ])
874898 else :
@@ -1071,9 +1095,8 @@ def page_layout(self) -> Optional[str]:
10711095 * - /TwoPageRight
10721096 - Show two pages at a time, odd-numbered pages on the right
10731097 """
1074- trailer = cast (DictionaryObject , self .trailer [TK .ROOT ])
1075- if CD .PAGE_LAYOUT in trailer :
1076- return cast (NameObject , trailer [CD .PAGE_LAYOUT ])
1098+ if CD .PAGE_LAYOUT in self .root_object :
1099+ return cast (NameObject , self .root_object [CD .PAGE_LAYOUT ])
10771100 return None
10781101
10791102 @property
@@ -1098,7 +1121,7 @@ def page_mode(self) -> Optional[PagemodeType]:
10981121 - Show attachments panel
10991122 """
11001123 try :
1101- return self .trailer [ TK . ROOT ] ["/PageMode" ] # type: ignore
1124+ return self .root_object ["/PageMode" ] # type: ignore
11021125 except KeyError :
11031126 return None
11041127
@@ -1119,12 +1142,12 @@ def _flatten(
11191142 if pages is None :
11201143 # Fix issue 327: set flattened_pages attribute only for
11211144 # decrypted file
1122- catalog = self .trailer [ TK . ROOT ]. get_object ()
1123- pages = catalog ["/Pages" ].get_object () # type: ignore
1145+ catalog = self .root_object
1146+ pages = cast ( DictionaryObject , catalog ["/Pages" ].get_object ())
11241147 self .flattened_pages = []
11251148
11261149 if PA .TYPE in pages :
1127- t = pages [PA .TYPE ]
1150+ t = cast ( str , pages [PA .TYPE ])
11281151 # if pdf has no type, considered as a page if /Kids is missing
11291152 elif PA .KIDS not in pages :
11301153 t = "/Page"
@@ -1925,7 +1948,7 @@ def is_encrypted(self) -> bool:
19251948 def xfa (self ) -> Optional [Dict [str , Any ]]:
19261949 tree : Optional [TreeObject ] = None
19271950 retval : Dict [str , Any ] = {}
1928- catalog = cast ( DictionaryObject , self .trailer [ TK . ROOT ])
1951+ catalog = self .root_object
19291952
19301953 if "/AcroForm" not in catalog or not catalog ["/AcroForm" ]:
19311954 return None
@@ -1955,7 +1978,7 @@ def add_form_topname(self, name: str) -> Optional[DictionaryObject]:
19551978 Returns:
19561979 The created object. ``None`` means no object was created.
19571980 """
1958- catalog = cast ( DictionaryObject , self .trailer [ TK . ROOT ])
1981+ catalog = self .root_object
19591982
19601983 if "/AcroForm" not in catalog or not isinstance (
19611984 catalog ["/AcroForm" ], DictionaryObject
@@ -1997,7 +2020,7 @@ def rename_form_topname(self, name: str) -> Optional[DictionaryObject]:
19972020 Returns:
19982021 The modified object. ``None`` means no object was modified.
19992022 """
2000- catalog = cast ( DictionaryObject , self .trailer [ TK . ROOT ])
2023+ catalog = self .root_object
20012024
20022025 if "/AcroForm" not in catalog or not isinstance (
20032026 catalog ["/AcroForm" ], DictionaryObject
@@ -2030,7 +2053,7 @@ def _list_attachments(self) -> List[str]:
20302053 Returns:
20312054 list of filenames
20322055 """
2033- catalog = cast ( DictionaryObject , self .trailer [ "/Root" ])
2056+ catalog = self .root_object
20342057 # From the catalog get the embedded file names
20352058 try :
20362059 filenames = cast (
@@ -2068,7 +2091,7 @@ def _get_attachments(
20682091 dictionary of filename -> Union[bytestring or List[ByteString]]
20692092 if the filename exists multiple times a List of the different version will be provided
20702093 """
2071- catalog = cast ( DictionaryObject , self .trailer [ "/Root" ])
2094+ catalog = self .root_object
20722095 # From the catalog get the embedded file names
20732096 try :
20742097 filenames = cast (
0 commit comments