DocMosaicdocs
Concepts

Guides and snap

Editor.Ruler, Editor.Guides, and the snap engine share one source of truth - the same SNAP_THRESHOLD (5pt).

Editor.Ruler, Editor.Guides, and the snap engine share one source of truth - the same SNAP_THRESHOLD (5pt) controls both the multi-select group drag and ruler-dropped guide lines. Coordinates live in PDF points; the conversion to/from CSS pixels happens at the canvas boundary via finalScale.

Layers

LayerSource of truthRole
Editor.RuleruseEditorCanvas().finalScale + page dimsPaints tick marks. Pure display surface, no document writes.
Editor.GuidesPage.guides on the current pageDrag from a ruler to drop a guide; click the × badge to remove.
computeSnap*snap.ts in @docmosaic/reactPure math. Mixes page margins + section edges + page guides.
useEditorSectiongroup drag handlersCalls computeSnap on every move and updates ui.activeSnapGuides.
Editor.SnapGuidesui.activeSnapGuidesPaints the matched alignment lines while a drag is live.

Rulers and guides are opt-in: pass showRuler (and optionally showMinimap) on Editor.Root. The canvas reserves a 24px gutter on each axis when rulers are visible and auto-mounts Editor.Ruler + Editor.Guides inside the canvas overlay slot.

Storage shape

import type { PageGuides } from '@docmosaic/core';

interface PageGuides {
    vertical: number[]; // x positions in PDF points
    horizontal: number[]; // y positions in PDF points
}

Page.guides is optional - legacy documents have no guides, and the renderer treats undefined as "no guides". The PDF generator never reads the field; the byte-diff gate locks that contract: rendering a document with guides set must produce exactly the same bytes as the same document with guides omitted.

Snap targets

computeSnapTargets returns the same six page candidates it always has - page-left, page-right, page-center, page-top, page-bottom, page-middle - plus six per non-selected section, plus one per user-placed guide tagged source: 'guide'. The hook in useEditorSection passes pagesRef.current[currentPage - 1]?.guides through on every drag start so guides added during the same session immediately participate.

import { computeSnap, computeSnapTargets } from '@docmosaic/react';

const targets = computeSnapTargets(sections, selectedIds, pageDimensions, page.guides);
const { dx, dy, matched } = computeSnap(bbox, proposedDx, proposedDy, targets);

matched carries the targets the group's edges currently sit on - Editor.SnapGuides paints one accent-colored line per match.

Reducer actions

Two actions on the @docmosaic/core reducer write Page.guides. Both fail closed: out-of-range pageIndex returns the previous state untouched, and ADD_GUIDE skips exact duplicates so repeated drags onto the same value don't bloat the array.

import { reducer } from '@docmosaic/core';

const next = reducer(state, {
    type: 'ADD_GUIDE',
    pageIndex: 0,
    axis: 'vertical',
    position: 120,
});

reducer(next, {
    type: 'REMOVE_GUIDE',
    pageIndex: 0,
    axis: 'vertical',
    position: 120,
});

The React layer wraps both through actions.addGuide(pageIndex, axis, position) and actions.removeGuide(pageIndex, axis, position) - available in both uncontrolled (history-aware, undo/redo intact) and controlled modes.

See also

  • Unit system - why guides are stored in PDF points alongside section geometry