JSFiddle - React, Tailwind, and code Playground

HTML

<main>
    <div class="toolbar">
      <p>Select the handwriting to copy it as clean text.</p>
      <button id="toggle" type="button" aria-pressed="false">Show OCR layer</button>
    </div>
    <figure id="page">
      <svg viewBox="0 0 1536 1024" role="img" aria-labelledby="title description">
        <title id="title">A Hacker News comment handwritten on lined paper</title>
        <desc id="description">From pbronez, 27 minutes ago: The medium is the message! Well written.</desc>
        <image width="1536" height="1024"...

CSS

:root { color-scheme: light; }
    * { box-sizing: border-box; }
    html, body { margin: 0; min-height: 100%; }
    body {
      display: grid;
      place-items: center;
      padding: clamp(16px, 4vw, 48px);
      background: #e9e5dc;
      color: #292721;
      font: 15px/1.45 system-ui, -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif;
    }
    main { width: min(100%, 1100px); }
    .toolbar {
      display: flex;
      align-items: center;
      justify-content: space-between;
      gap: 16px;
      margin-bottom: 10px;
      color: #625f57;
    }
    .toolbar p { margin: 0; }
    button {
      flex: none;
      border: 1px solid #aaa397;
      border-radius: 999px;
      padding: 7px 12px;
      background: #f8f6f0;
      color: #36332d;
      font: inherit;
      cursor: pointer;
    }
    button:hover { background: #fff; }
    button:focus-visible { outline: 3px solid #d66b42; outline-offset: 2px; }
    figure {
      position: relative;
      margin: 0;
      overflow: hidden;
      border-radius: 4px;
      background: #ddd6c7;
      box-shadow: 0 16px 42px rgba(58, 51, 39, .18);
    }
    svg { display: block; width: 100%; height: auto; }
    .ocr {
      fill: rgba(0, 0, 0, .003);
      stroke: none;
      font-family: "Arial Narrow", "Liberation Sans Narrow", sans-serif;
      font-style: italic;
      user-select: text;
      -webkit-user-select: text;
    }
    .ocr.meta { font-size: 58px; letter-spacing: 2px; }
    .ocr.comment { font-size: 74px; letter-spacing: 1px; }
    .ocr.reply { font-size: 52px; text-decoration: underline; }
    .ocr.arrow { font-size: 62px; font-style: normal; }
    figure.show-ocr .ocr {
      fill: rgba(235, 55, 34, .88);
      stroke: rgba(255, 255, 255, .75);
      stroke-width: 1px;
      paint-order: stroke fill;
    }
    .sr-only {
      position: absolute;
      width: 1px;
      height: 1px;
      padding: 0;
      margin: -1px;
     ...

JavaScript

const page = document.querySelector('#page');
const toggle = document.querySelector('#toggle');

toggle.addEventListener('click', () => {
  const visible = page.classList.toggle('show-ocr');
  toggle.setAttribute('aria-pressed', String(visible));
  toggle.textContent = visible ? 'Hide OCR layer' : 'Show OCR layer';
});

document.addEventListener('copy', (event) => {
  const selection = document.getSelection();
  if (!selection || selection.isCollapsed) return;
  const range = selection.getRangeAt(0);
  const overlay = document.querySelector('g[aria-label="Selectable text transcription"]');
  if (!overlay.contains(range.commonAncestorContainer) &&
      !overlay.contains(range.startContainer) &&
      !overlay.contains(range.endContainer)) return;

  const selectedText = selection.toString()
    .replace(/\u2006/g, '')
    .replace(/[ \t]+/g, ' ')
    .replace(/ *\n */g, '\n')
    .trim();

  if (!selectedText) return;
  event.preventDefault();
  event.clipboardData.setData('text/plain', selectedText);
});