Skip to main content

Units & measurements

Which units does Ydesign use internally?

Ydesign's canvas uses CSS pixels (px) throughout, with 1pt = 1px and 72 dpi as the default baseline. Every size-related value you see in storewidth, height, fontSize, left, top, … — is in pixels.

This matches Fabric.js v6's coordinate system and keeps browser-native APIs (getBoundingClientRect, canvas dimensions, etc.) seamless.


Change the unit shown in the UI

While values are stored in pixels, you can tell the UI which unit to display and accept as input via store.setUnit (rulers, the size panel, print bleed, etc. follow along):

store.setUnit({
unit: 'mm', // 'px' | 'pt' | 'mm' | 'cm' | 'in'
dpi: 300,
});

After the switch:

  • store.unit'mm'
  • store.dpi300
  • The ruler labels in millimeters
  • The size panel inputs accept millimeter values
  • store.width / store.height stay as pixels (internal storage is unchanged, only the UI converts)

📌 Supported units: px, pt, mm, cm, in. Currently pt and px are treated 1:1.


Display units in custom UI

Ydesign exposes a unit conversion helper set. All built-in panels use the same functions — reuse them in your own components to avoid drift:

import {
pxToUnit,
pxToUnitRounded,
pxToUnitString,
unitToPx,
} from '@ydesign/react-editor/utils/unit';

pxToUnit — pixels → number

pxToUnit({ px: 100, unit: 'mm', dpi: 300 });  // => 8.466...
pxToUnit({ px: 300, unit: 'in', dpi: 300 }); // => 1

pxToUnitRounded — pixels → rounded number

For UI display (avoid long decimals):

pxToUnitRounded({ px: 100, unit: 'mm', dpi: 300 });              // => 8.47
pxToUnitRounded({ px: 100, unit: 'mm', dpi: 300, precious: 0 }); // => 8

pxToUnitString — pixels → string with unit suffix

Drop directly into labels:

pxToUnitString({ px: 2480, unit: 'mm', dpi: 300 });  // => '210mm'
pxToUnitString({ px: 300, unit: 'in', dpi: 300 }); // => '1in'
pxToUnitString({ px: 300, unit: 'px', dpi: 72 }); // => '300px'

unitToPx — number → pixels

Use this when accepting user input (e.g. "A4 width = 210mm" → canvas needs how many pixels?):

const widthPx = unitToPx({ unitVal: 210, unit: 'mm', dpi: 300 });
// => 2480.31...

store.setSize({
width: Math.round(widthPx),
height: Math.round(unitToPx({ unitVal: 297, unit: 'mm', dpi: 300 })),
});

Full example: a size input with unit switching

import { observer } from 'mobx-react-lite';
import { InputNumber, Select } from 'antd';
import { pxToUnitRounded, unitToPx } from '@ydesign/react-editor/utils/unit';

export const SizeInput = observer(({ store }) => {
const widthInCurrentUnit = pxToUnitRounded({
px: store.width,
unit: store.unit as any,
dpi: store.dpi,
precious: 2,
});

const handleChangeWidth = (val: number | null) => {
if (val == null) return;
const px = unitToPx({ unitVal: val, unit: store.unit, dpi: store.dpi });
store.setSize({ width: Math.round(px), height: store.height });
};

return (
<div style={{ display: 'flex', gap: 8 }}>
<InputNumber value={widthInCurrentUnit} onChange={handleChangeWidth} />
<Select
value={store.unit}
onChange={unit => store.setUnit({ unit, dpi: store.dpi })}
style={{ width: 80 }}
options={[
{ value: 'px', label: 'px' },
{ value: 'mm', label: 'mm' },
{ value: 'cm', label: 'cm' },
{ value: 'in', label: 'in' },
]}
/>
</div>
);
});

DPI on export

Client export (store.toDataURL / toBlob / saveAsImage)

Client-side export doesn't read store.dpi. Use the multiplier parameter to control output resolution:

// 1920×1080 canvas with multiplier=2 → 3840×2160 output
await store.saveAsImage({
multiplier: 2,
format: 'png',
});

Two common strategies:

GoalRecommended
High-res on-screen previewmultiplier: 2
Print-grade 300 dpimultiplier: 3 ~ 4
Scale by the device pixel ratioenableRetinaScaling: true

Cloud render (/api/render/image)

Cloud Render API uses the same multiplier but supports up to 8× (client-side is typically capped at 4× by browser memory). For print:

{
"json": { /* ... */ },
"format": "png",
"multiplier": 4
}

Two approaches to "high-res export"

When your product needs both on-screen previews (72 dpi) and print deliverables (300 dpi), two patterns work:

  • store.setSize({ width: 2480, height: 3508 }) (A4 @ 300 dpi in pixels)
  • Screen preview: multiplier: 1
  • Print export: multiplier: 1 with format: 'png' (the canvas is already at 300 dpi)

Pro: pixel positions match exactly between preview and print — no drift.

Approach B: 72 dpi canvas + scale up at export

  • store.setSize({ width: 595, height: 842 }) (A4 @ 72 dpi)
  • Preview: multiplier: 1
  • Print: multiplier: 4 (≈ 288 dpi)

Pro: smaller canvas = faster renders + lighter JSON. Con: small text / fine strokes may drift by a pixel or two when scaled.

Pick whichever fits your product.


See also