Skip to content

Print SVG


tsx
import { createRoot } from 'react-dom/client'

type SvgInput = React.FC | SVGSVGElement | string // string = URL

const delay = (ms: number) =>
  new Promise<void>((resolve) => {
    setTimeout(resolve, ms)
  })

export async function printInvoice(
  input: SvgInput,
  map?: Record<string, string>
) {
  // Create a container to render the SVG component
  const container = document.createElement('div')
  document.body.appendChild(container)

  let svg: SVGSVGElement | null = null

  if (typeof input === 'function') {
    // Case: React Component
    const root = createRoot(container)
    root.render(<input />)

    await delay(100) // Wait for render

    svg = container.querySelector('svg')

    // Unmount component and cleanup after cloning
    root.unmount()
    document.body.removeChild(container)
  } else if (typeof input === 'string') {
    // Case: URL to SVG
    const localContainer = container // freeze reference before await

    const response = await fetch(input)
    const svgText = await response.text()
    localContainer.innerHTML = svgText
    svg = localContainer.querySelector('svg')
    document.body.removeChild(localContainer)
  } else if (input instanceof SVGSVGElement) {
    // Case: Already an SVG element
    svg = input.cloneNode(true) as SVGSVGElement
    document.body.removeChild(container)
  }

  if (!svg) {
    // eslint-disable-next-line no-console
    console.error('SVG not found or failed to load')
    return
  }

  if (map) {
    // Replace text inside elements with matching IDs
    for (const [id, value] of Object.entries(map)) {
      const element = svg.querySelector(`#${id}`)
      if (element) {
        element.textContent = value
      }
    }
  }

  const fontCss = `
    /* Regular Weight */
    @font-face {
      font-family: 'PingARLT';
      font-style: normal;
      font-weight: 400; /* 'normal' or '400' */
      src: url('${window.location.origin}/app/assets/fonts/PingARLT-Regular.woff2') format('woff2');
    }

    /* Medium Weight */
    @font-face {
      font-family: 'PingARLT';
      font-style: normal;
      font-weight: 500; /* 'medium' or '500' */
      src: url('${window.location.origin}/app/assets/fonts/PingARLT-Medium.woff2') format('woff2');
    }

    /* Bold Weight */
    @font-face {
      font-family: 'PingARLT';
      font-style: normal;
      font-weight: 700; /* 'bold' or '700' */
      src: url('${window.location.origin}/app/assets/fonts/PingARLT-Bold.woff2') format('woff2');
    }

    /* --- General Styling --- */

    @media print {
      @page { margin: 0; size: auto; }
      body { margin: 0; }
    }

    body {
      /* Set the default font for the entire page */
      font-family: 'PingARLT', sans-serif;
    }
    
    svg { 
        max-width: 100%; 
        max-height: 100vh;
        /* Ensure the font is also applied inside the SVG */
        font-family: 'PingARLT', sans-serif;
    }

    /* 
      THE FIX: Use the universal selector (*) with !important.
      This forces every single element to use your desired font,
      overriding any inline styles or classes from the SVG file.
    */
    * {
      font-family: 'PingARLT', sans-serif !important;
    }
  `

  const printWindow = window.open('', '_blank')
  if (printWindow) {
    // 1. Write a basic HTML structure to the new window.
    printWindow.document.write(`
      <html>
        <head>
          <title>Print</title>
          <style>
            ${fontCss}
          </style>
        </head>
        <body>
        </body>
      </html>
    `)

    // 2. Inject the SVG into the body of the new window.
    printWindow.document.body.innerHTML = svg.outerHTML
    printWindow.document.close() // Important: End the document writing stream.

    // 3. Set the event handler from the parent window. This cannot be overwritten by scripts in the SVG.
    // printWindow.onafterprint = () => {
    //   printWindow.close()
    // }

    // 4. Call print. We wrap it in a timeout to ensure the content has had a moment to render.
    setTimeout(() => {
      printWindow.focus() // Focus the new window before printing
      printWindow.print()
    }, 250) // A small delay can help ensure rendering is complete.
  }
}