Unit system
Geometry is stored in PDF points. Conversions live at the edges - uploads (px → pt), display (pt → CSS px), export (pt → PDF).
Every coordinate, width, and height inside the document model is stored in PDF points (1pt = 1/72 inch). Everything else - millimeters in paper-size labels, CSS pixels in the on-screen canvas, image dimensions from a file upload - is a conversion happening at the boundary. Picking one canonical unit keeps the reducer pure and the PDF output byte-stable.
Mental model
| Surface | Unit | Why |
|---|---|---|
Section.x/y/width/height | points (72 DPI) | Matches jsPDF's native coordinate system 1-to-1. |
PageSize definitions | points | Lets getPageDimensionsWithOrientation feed jsPDF without a conversion step. |
Paper-size labels (A4, LETTER) | millimeters | Human-readable ("A4 = 210×297mm") - the canonical international spec. |
| Canvas display | CSS pixels (96 DPI) | What the browser draws. Always scaled at render time. |
| Image uploads | CSS pixels (96 DPI) | What the browser gives you (naturalWidth/Height). |
The reducer never converts. Conversions live at the edges - file upload (px → pt), display (pt → CSS px), export (pt → PDF, no-op).
Why points
jsPDF's default unit is points. By keeping the document in points the editor:
- Hands geometry straight to
jspdfwith zero arithmetic. - Computes paper sizes via lookup, not multiplication.
- Stays byte-stable across PDF regeneration (no floating-point drift from repeated mm ↔ pt round-trips).
When humans want millimeters (the page-size dropdown reads "A4 (210×297mm)") or inches (an export-summary tooltip), the dimension helpers convert on the way out.
Conversion API
All helpers live in @docmosaic/core and are pure functions - no side effects, no DOM access.
import {
convertDimensions,
mmToPt,
ptToMm,
getPageDimensions,
getPageDimensionsWithOrientation,
formatDimensions,
type MeasurementUnit,
type PageDimensions,
} from '@docmosaic/core';| Function | Use case |
|---|---|
mmToPt(mm) / ptToMm(pt) | Scalar conversion for single dimensions. |
convertDimensions({ width, height }, from, to) | Convert a PageDimensions pair between 'mm', 'in', 'px'. |
getPageDimensions(pageSize, unit?) | Canonical (portrait) dimensions for a paper size in any unit. |
getPageDimensionsWithOrientation(size, orient) | Returns { width, height } in points with orientation applied. |
formatDimensions(dims, unit) | Human label - e.g. "210×297mm". |
MeasurementUnit is 'mm' | 'in' | 'px'. Points are intentionally not in the union - they're the canonical storage unit, not a display unit.
Example - getting points for the current page
import { getPageDimensionsWithOrientation } from '@docmosaic/core';
const { width, height } = getPageDimensionsWithOrientation('A4', 'landscape');
// => { width: 841.89, height: 595.28 } - pointsThat's the same shape jsPDF expects. Pass it straight to the renderer.
When you need to convert
Three real boundaries - the rest is storage.
1. File upload (px → pt)
A dropped image gives you pixel dimensions via naturalWidth / naturalHeight. Convert to points before persisting them on a Section:
const widthPt = img.naturalWidth * (72 / 96);
const heightPt = img.naturalHeight * (72 / 96);2. Rendering (pt → CSS px)
The Canvas applies a single scale to its content so child sections can be authored in points and still land in the right pixels:
finalScale = pageScale * zoom
displayedPx = pointValue * finalScaleYou never write to section.x directly from a mouse event in CSS pixels - the canvas hook divides by finalScale first, so the reducer always sees points.
3. Export (pt → PDF)
This is a no-op. jsPDF is configured with unit: 'pt', so the document's stored values flow straight through. That's the whole point.
Tradeoffs
- No
'pt'inMeasurementUnit. Points are storage, not display. If a UI ever needs to show "12pt", convert viaptToMmthen format - or build aformatPointshelper at the call site. mmToPt/ptToMmuse the constant2.83465. That's the rounded value of72 / 25.4. Errors are under1e-5over A0 dimensions - well below jsPDF's own precision.
See also
- Designer - how the canvas scales points into CSS pixels
- Guides and snap -
Editor.Ruleris the unit-aware primitive that surfaces points/mm/in directly on the canvas