docs: def setup_output_document(input_doc, tmp_input_doc, metadata_info,
                                                      copy_document_catalog=True):
      """Create the output `PdfFileWriter` objects and copy over the relevant info."""
      # NOTE: Inserting pages from a PdfFileReader into multiple PdfFileWriters
      # seems to cause problems (writer can hang on write), so only one is used.
      # This is why the tmp_input_doc file was created earlier, to get copies of
      # the page objects which are independent of those in input_doc.  An ugly
      # hack for a nasty bug to track down.
  
      # NOTE: You can get the _root_object attribute (dict for the document
      # catalog) from the output document after calling cloneReaderDocumentRoot
      # or else you can just directly get it from the input_doc.trailer dict, as
      # below (which is from the code for cloneReaderDocumentRoot), but you
      # CANNOT set the full _root_object to be the _root_object attribute for the
      # actual output_doc or else only blank pages show up in acroread (whether
      # or not there is any attempt to explicitly copy the pages over).  The same
      # is true for using cloneDocumentFromReader (which just calls
      # cloneReaderDocumentRoot followed by appendPagesFromReader).  At least the
      # '/Pages' key and value in _root_object cause problems, so they are
      # skipped in the partial copy.  Probably a bug in PyPDF2.  See the original
      # code for the routines on the github pages below.
      #
      # https://github.com/mstamy2/PyPDF2/blob/master/PyPDF2/pdf.py
      # https://github.com/mstamy2/PyPDF2/blob/master/PyPDF2/generic.py
      #
      # Files still can change zoom mode on clicking outline links, but that is
      # an Adobe implementation problem, and happens even in the uncropped files:
      # https://superuser.com/questions/278302/
  
      output_doc = PdfFileWriter()
  
      def root_objects_not_indirect(input_doc, root_object):
          """This can expand some of the `IndirectObject` objects in  a root object to
          see the actual values.  Currently only used for debugging.  May mess up the
          input doc and require a temporary one."""
          if isinstance(root_object, dict):
              return {root_objects_not_indirect(input_doc, key):
                      root_objects_not_indirect(input_doc, value) for
                                                    key, value in root_object.items()}
          elif isinstance(root_object, list):
              return [root_objects_not_indirect(input_doc, item) for item in root_object]
          elif isinstance(root_object, IndirectObject):
              return input_doc.getObject(root_object)
          else:
              return root_object
  
      doc_cat_whitelist = args.docCatWhitelist.split()
      if "ALL" in doc_cat_whitelist:
          doc_cat_whitelist = ["ALL"]
  
      doc_cat_blacklist = args.docCatBlacklist.split()
      if "ALL" in doc_cat_blacklist:
          doc_cat_blacklist = ["ALL"]
  
      # Partially copy over document catalog data from input_doc to output_doc.
      if not copy_document_catalog or (
              not doc_cat_whitelist and doc_cat_blacklist == ["ALL"]):
          # Check this first, to completely skip the possibly problematic code getting
          # document catalog items when possible.  Does not print a skipped list, though.
          if args.verbose:
              print("\nNot copying any document catalog items to the cropped document.")
      else:
          try:
              root_object = input_doc.trailer["/Root"]
  
              copied_items = []
              skipped_items = []
              for key, value in root_object.items():
                  # Some possible keys can be:
                  #
                  # /Type -- required, must have value /Catalog
                  # /Pages -- required, indirect ref to page tree; skip, will change
                  # /PageMode -- set to /UseNone, /UseOutlines, /UseThumbs, /Fullscreen,
                  #              /UseOC, or /UseAttachments, with /UseNone default.
                  # /OpenAction -- action to take when document is opened, like zooming
                  # /PageLayout -- set to /SinglePage, /OneColumn, /TwoColumnLeft,
                  #                /TwoColumnRight, /TwoPageLeft, /TwoPageRight
                  # /Names -- a name dictionary to avoid having to use object numbers
                  # /Outlines -- indirect ref to document outline, i.e., bookmarks
                  # /Dests -- a dict of destinations in the PDF
                  # /ViewerPreferences -- a viewer preferences dict
                  # /MetaData -- XMP metadata, as opposed to other metadata
                  # /PageLabels -- alternate numbering for pages, only affect PDF viewers
                  if key == "/Pages":
                      skipped_items.append(key)
                      continue
                  if doc_cat_whitelist != ["ALL"] and key not in doc_cat_whitelist:
                      if doc_cat_blacklist == ["ALL"] or key in doc_cat_blacklist:
                          skipped_items.append(key)
                          continue
                  copied_items.append(key)
                  output_doc._root_object[NameObject(key)] = value
  
              if args.verbose:
                  print("\nCopied these items from the document catalog:\n   ", end="")
                  print(*copied_items)
                  print("Skipped copy of these items from the document catalog:\n   ", end="")
                  print(*skipped_items)
  
          except (KeyboardInterrupt, EOFError):
              raise
          except: # Just catch any errors here; don't know which might be raised.
              # On exception just warn and get a new PdfFileWriter object, to be safe.
              print("\nWarning: The document catalog data could not be copied to the"
                    "\nnew, cropped document.  Try fixing the PDF document using"
                    "\n'--gsFix' if you have Ghostscript installed.", file=sys.stderr)
              output_doc = PdfFileWriter()
  
      #output_doc.appendPagesFromReader(input_doc) # Works, but wait and test more.
      for page in [input_doc.getPage(i) for i in range(input_doc.getNumPages())]:
          output_doc.addPage(page)
  
      tmp_output_doc = PdfFileWriter()
      #tmp_output_doc.appendPagesFromReader(tmp_input_doc)  # Works, but test more.
      for page in [tmp_input_doc.getPage(i) for i in range(tmp_input_doc.getNumPages())]:
          tmp_output_doc.addPage(page)
  
      ##
      ## Copy the metadata from input_doc to output_doc, modifying the Producer string
      ## if this program didn't already set it.  Get bool for whether this program
      ## cropped the document already.
      ##
  
      already_cropped_by_this_program = set_cropped_metadata(input_doc, output_doc,
                                                             metadata_info)
      return output_doc, tmp_output_doc, already_cropped_by_this_program