class_docstr: Manages grafting text-only PDFs onto regular PDFs.
class_docstr_tok: ["Manages","grafting","text-only","PDFs","onto","regular","PDFs","."]
docstr: Insert the text layer from text page 0 on to pdf_base at page_num.
docstr_tok: ["Insert","the","text","layer","from","text","page","0","on","to","pdf_base","at","page_num","."]
code: def _graft_text_layer(
      self,
      *,
      page_num: int,
      textpdf: Path,
      font: Dictionary,
      font_key: Name,
      text_rotation: int,
      strip_old_text: bool,
  ):
      """Insert the text layer from text page 0 on to pdf_base at page_num."""
      # pylint: disable=invalid-name
  
      log.debug("Grafting")
      if Path(textpdf).stat().st_size == 0:
          return
  
      # This is a pointer indicating a specific page in the base file
      with Pdf.open(textpdf) as pdf_text:
          pdf_text_contents = pdf_text.pages[0].Contents.read_bytes()
  
          base_page = self.pdf_base.pages.p(page_num)
  
          # The text page always will be oriented up by this stage but the original
          # content may have a rotation applied. Wrap the text stream with a rotation
          # so it will be oriented the same way as the rest of the page content.
          # (Previous versions OCRmyPDF rotated the content layer to match the text.)
          mediabox = pdf_text.pages[0].mediabox
          wt, ht = mediabox[2] - mediabox[0], mediabox[3] - mediabox[1]
  
          mediabox = base_page.mediabox
          wp, hp = mediabox[2] - mediabox[0], mediabox[3] - mediabox[1]
  
          translate = Matrix().translated(-wt / 2, -ht / 2)
          untranslate = Matrix().translated(wp / 2, hp / 2)
          corner = Matrix().translated(mediabox[0], mediabox[1])
          # -rotation because the input is a clockwise angle and this formula
          # uses CCW
          text_rotation = -text_rotation % 360
          rotate = Matrix().rotated(text_rotation)
  
          # Because of rounding of DPI, we might get a text layer that is not
          # identically sized to the target page. Scale to adjust. Normally this
          # is within 0.998.
          if text_rotation in (90, 270):
              wt, ht = ht, wt
          scale_x = wp / wt
          scale_y = hp / ht
  
          # log.debug('%r', scale_x, scale_y)
          scale = Matrix().scaled(scale_x, scale_y)
  
          # Translate the text so it is centered at (0, 0), rotate it there, adjust
          # for a size different between initial and text PDF, then untranslate, and
          # finally move the lower left corner to match the mediabox. All transforms
          # must be premultiplied so they are applied in reverse order here.
          ctm = corner @ untranslate @ scale @ rotate @ translate
          log.debug("Grafting with ctm %r", ctm)
  
          base_resources = _ensure_dictionary(
              base_page.obj, Name.Resources
          )
          base_xobjs = _ensure_dictionary(base_resources, Name.XObject)
          text_xobj_name = Name.random(prefix="OCR-")
          xobj = self.pdf_base.make_stream(pdf_text_contents)
          base_xobjs[text_xobj_name] = xobj
          xobj.Type = Name.XObject
          xobj.Subtype = Name.Form
          xobj.FormType = 1
          xobj.BBox = mediabox
          _update_resources(obj=xobj, font=font, font_key=font_key)
  
          pdf_draw_xobj = (
              (b"q %s cm\n" % ctm.encode())
              + (b"%s Do\n" % text_xobj_name)
              + b"\nQ\n"
          )
          new_text_layer = Stream(self.pdf_base, pdf_draw_xobj)
  
          if strip_old_text:
              strip_invisible_text(self.pdf_base, base_page)
          base_page.contents_coalesce()
          if self.render_mode == RenderMode.ON_TOP:
              # Add q/Q to ensure content we append is drawn correctly
              # Strictly speaking this needs to trace the whole q/Q stack in case
              # stack is not balanced.
              original = base_page.Contents.read_bytes()
              base_page.Contents.write(b"q\n" + original + b"\nQ\n")
          base_page.contents_add(
              new_text_layer,
              prepend=self.render_mode == RenderMode.UNDERNEATH,
          )
          base_page.contents_coalesce()
  
          _update_resources(
              obj=base_page.obj, font=font, font_key=font_key
          )
  
file_path: OCRmyPDF/src/ocrmypdf/_graft.py
imports: from __future__ import annotations
  import logging
  from contextlib import suppress
  from enum import Enum
  from pathlib import Path
  from pikepdf import Dictionary, Matrix, Name, Operator, Page, Pdf, PdfError, Stream, parse_content_stream, unparse_content_stream
  from ocrmypdf._jobcontext import PdfContext
label_desc: Use image node_id 6 for calling the OcrGrafter obj's underlying member method code with example usage: obj._graft_text_layer() without return types