Engineering Journal
Ginexys
Ginexys

How to Build a CAD-Style Layout Editor on Top of Extracted HTML

2026-05-30

TLDR

If you have extracted a PDF to HTML and want users to rearrange and edit the layout in the browser, you do not need position: absolute or canvas overlays. The extracted DOM is already a structured CSS Grid box model. HTML5 drag-and-drop combined with native DOM insertBefore/appendChild calls provides free layout reflow. Selection Mode toggles contentEditable, injects drag handles, supports marquee multi-select, and lets users group regions into new grid zones.
Component StepUnderlying MechanismImplementation StrategyLayout Reflow Behavior
Selection Mode TogglecontentEditable + CSS ClassFlips contentEditable, attaches handlesBrowser Grid handles flow
Element Drag & DropHTML5 Drag API + DOM Nodestarget.before(_draggedEl) insertionsDynamic reflow (No math required)
Marquee SelectionOverlay Box + getBoundingClientRectOverlap check against .pdf-regionMulti-node bounding set
Code Editor ModalMonaco Editor in native <dialog>Deferred setValue() via requestAnimationFrameFull DOM node replacement

Absolute coordinates break responsive CSS document flow

When building visual layout tools for PDF-extracted HTML, developers often reach for absolute positioning (position: absolute) or canvas overlay frameworks (like Fabric.js or Konva).

However, forcing document elements into absolute coordinate positions breaks responsive document flow. Elements no longer reflow when text is edited, column layouts collapse, and exporting to clean HTML or Markdown becomes nearly impossible.


CSS Grid containers align extracted document flows natively

The geometry extraction pipeline outputs structured, semantic HTML:

<section class="pdf-page-content">
  <div class="pdf-zone pdf-zone--cols-2"> <!-- CSS Grid Container -->
    <div class="pdf-col pdf-col--left">
      <div class="pdf-region">
        <h3>Section Heading</h3>
      </div>
    </div>
    <div class="pdf-col pdf-col--right">
      <div class="pdf-region">
        <div class="pdf-table-wrap"><table>...</table></div>
      </div>
    </div>
  </div>
</section>

Because zones use CSS Grid (grid-template-columns: repeat(N, 1fr)), regions are standard flow children. Attempting to drag regions using absolute pixel coordinates breaks the CSS Grid contract.


Visual document editing requires targeted DOM manipulation

                              [Toggle Selection Mode]
                                         |
                                         v
                      [Inject Handles & Marquee Listeners]
                                         |
                                         v
                                 User Interaction?
                       /        /                 \         \
                      v        v                   v         v
                [Drag & Drop] [Marquee Box]    [Group Button] [Edit Code]
                      |              |                 |           |
                      v              v                 v           v
                 (Insert Node) (Intersect Check) (Zone Wrap) (Monaco Dialog)
                      \              \                 /           /
                       \              \               /           /
                        v              v             v           v
                        +----------------------------------------+
                        |      applyHtmlEverywhere(HTML)         |
                        +----------------------------------------+

Dual mode toggles switch between layout design and text entry

A single mode toggle flips between Edit Mode (inline text editing) and Selection Mode (layout manipulation):
function toggleMode() {
  isActive = !isActive;
  preview.classList.toggle('selection-mode', isActive);
  preview.contentEditable = isActive ? 'false' : 'true';
  
  if (isActive) {
    attachDragHandles();
    preview.addEventListener('mousedown', onMarqueeStart);
  } else {
    clearSelection();
    removeDragHandles();
    preview.removeEventListener('mousedown', onMarqueeStart);
  }
}

Drag and drop listeners handle relative DOM repositioning

Inject a drag handle into regions and handle drop events by mutating the DOM tree directly:
function onDrop(e) {
  e.preventDefault();
  const target = e.currentTarget;
  const rect = target.getBoundingClientRect();
  const insertAfter = e.clientY > rect.top + rect.height / 2;

if (insertAfter) { target.after(draggedEl); } else { target.before(draggedEl); }

applyHtmlEverywhere(preview.innerHTML, preview); // Synchronize state }

Overlap checks support multi-region marquee selection

To select multiple regions, create a visual marquee box on mousedown and test intersection on mouseup:
preview.querySelectorAll('.pdf-region').forEach(el => {
  const r = el.getBoundingClientRect();
  const overlaps = !(r.right < marqueeRect.left || r.left > marqueeRect.right ||
                     r.bottom < marqueeRect.top || r.top > marqueeRect.bottom);
  if (overlaps) {
    selectedSet.add(el);
    el.classList.add('sel-selected');
  }
});

Grid wrapper insertions group selected document regions

Grouping wraps all selected regions into a new single-column grid zone (div.pdf-zone.pdf-zone--cols-1):
function groupSelected() {
  const regions = [...selectedSet].filter(el => el.classList.contains('pdf-region'));
  if (regions.length < 2) return;

const firstParentZone = regions[0].closest('.pdf-zone') || regions[0].parentElement; const newZone = document.createElement('div'); newZone.className = 'pdf-zone pdf-zone--cols-1';

regions.forEach(r => newZone.appendChild(r)); firstParentZone.before(newZone); clearSelection(); applyHtmlEverywhere(preview.innerHTML, preview); }

Animation frames ensure Monaco layout initialization inside dialogs

For direct HTML editing, right-clicking an element opens a native <dialog> containing a Monaco editor. Crucially, Monaco requires container measurement after the dialog paints:
dialog.addEventListener('toggle', () => {
  if (!dialog.open) return;
  if (!editor) editor = monaco.editor.create(container, { language: 'html' });

requestAnimationFrame(() => { editor.layout(); editor.setValue(pendingHtml); editor.focus(); }); });

Rule of thumb: Use native browser layout engines (CSS Grid + DOM insertion) instead of canvas coordinate overlays when building visual document editors.
Read this post in the full Engineering Journal →