MOP — единицы измерения, идентификаторы, цвета
Нормативный текст — docs/04-file-formats.md §2.12–2.14;
здесь — справочные таблицы и формулы для реализации.
1. Единицы измерения
1.1 Базовая единица длины — EMU
Все длины в пакете — целые EMU (English Metric
Unit), тип i64; 1 дюйм = 914 400 EMU. Запись в XML —
десятичное целое без суффикса.
| Единица | EMU в единице | Единиц в дюйме | Примечание |
|---|---|---|---|
дюйм (in) |
914 400 | 1 | |
сантиметр (cm) |
360 000 | 2,54 | |
миллиметр (mm) |
36 000 | 25,4 | |
| 1/100 мм | 360 | 2 540 | внутренняя единица LibreOffice Draw/Impress |
пункт (pt) |
12 700 | 72 | PostScript-пункт |
пика (pc) |
152 400 | 6 | |
| twip (1/20 pt) | 635 | 1 440 | OOXML WordprocessingML, RTF, DOC |
| полупункт (1/2 pt) | 6 350 | 144 | w:sz, w:szCs |
| 1/8 pt | 1 587,5 | 576 | толщина границ w:sz в
w:bdr/w:tcBorders — единственная
нецелая, округляется |
пиксель при 96 dpi (px) |
9 525 | 96 | CSS/HTML/SVG по умолчанию, Excel-пиксели |
Проверка делимости: 914 400 = 2⁵ · 3² · 5² · 127; 360 =
2³·3²·5, 635 = 5·127, 9 525 = 3·5²·127, 12 700 = 2²·5²·127, 36 000 =
2⁵·3²·5³ — нет, 36 000 содержит 5³, однако 914 400 / 36 000 = 25,4 не
целое: миллиметр не делит дюйм, но это и не требуется —
EMU делит и мм (36 000 EMU), и дюйм (914 400 EMU) нацело; важно лишь,
что все единицы таблицы выражаются целым числом EMU
(кроме 1/8 pt).
1.2 Пересчёт и округление
/// Длина в EMU / Length in EMU
#[derive(Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash, Default)]
pub struct Length(pub i64);
pub const EMU_PER_INCH: i64 = 914_400;
pub const EMU_PER_PT: i64 = 12_700;
pub const EMU_PER_TWIP: i64 = 635;
pub const EMU_PER_HALF_PT: i64 = 6_350;
pub const EMU_PER_MM: i64 = 36_000;
pub const EMU_PER_CM: i64 = 360_000;
pub const EMU_PER_PX96: i64 = 9_525;
pub const EMU_PER_HMM: i64 = 360; // 1/100 мм / hundredths of mm
impl Length {
// В EMU — умножение без потерь / into EMU: lossless multiplication
pub const fn from_twips(v: i64) -> Self { Length(v * EMU_PER_TWIP) }
pub const fn from_half_pt(v: i64) -> Self { Length(v * EMU_PER_HALF_PT) }
pub const fn from_hmm(v: i64) -> Self { Length(v * EMU_PER_HMM) }
/// Границы OOXML в 1/8 pt: единственный нецелый коэффициент / OOXML border eighths of a point
pub fn from_eighth_pt(v: i64) -> Self { Length((v * 12_700 + 4) / 8) } // round half away from zero для v ≥ 0
/// Дробные значения (pt из PDF/iWork, in из Visio): округление к ближайшему / fractional input
pub fn from_pt_f64(v: f64) -> Self { Length((v * EMU_PER_PT as f64).round() as i64) }
pub fn from_inch_f64(v: f64) -> Self { Length((v * EMU_PER_INCH as f64).round() as i64) }
// Из EMU — деление с округлением «половина от нуля» / out of EMU: round half away from zero
pub fn to_twips(self) -> i64 { div_round(self.0, EMU_PER_TWIP) }
pub fn to_half_pt(self) -> i64 { div_round(self.0, EMU_PER_HALF_PT) }
pub fn to_eighth_pt(self) -> i64 { div_round(self.0 * 8, EMU_PER_PT) }
pub fn to_pt_f64(self) -> f64 { self.0 as f64 / EMU_PER_PT as f64 } // для раскладки / for layout
}
/// Деление с округлением половины от нуля / integer division, round half away from zero
fn div_round(a: i64, b: i64) -> i64 {
let (q, r) = (a / b, a % b);
if 2 * r.abs() >= b.abs() { q + a.signum() * b.signum() } else { q }
}Правила: умножение (в EMU) — всегда точно; деление (из EMU) — один
раз, при экспорте; ядро никогда не хранит результаты деления. Раскладка
считает в f64 pt, переводя из EMU один раз на входе;
обратно в EMU результаты раскладки не записываются (они не часть
документа).
1.3 Другие величины
| Величина | Хранение | Пример | Соответствие |
|---|---|---|---|
| Угол | целое, 1/60 000 градуса (i32) |
rotation="5400000" = 90° |
DrawingML ST_Angle; ODF
draw:transform rotate(rad) →
rad·180/π·60000 |
| Процент | целое, 1/1000 % (i32) |
scale="50000" = 50 % |
DrawingML ST_Percentage; OOXML w:w (1/50
%?) — через пересчёт |
| Доля ширины | строка x<число>% |
width="x100%" |
w:tblW type="pct" (в 1/50 %), ODF
rel-column-width |
| Множитель интервала | строка x<число> |
line="x1.15" |
OOXML w:spacing/@line (240 = 1,0) при
lineRule=auto |
| Относительная ширина символа | строка ch:<число> |
indent-left="ch:150" |
OOXML @leftChars (1/100 символа) |
| Выравнивание вместо координаты | строка align:<…> |
x="align:center" |
wp:positionH/wp:align |
| Время/длительность | мс (i32) |
duration="700" |
анимации, переходы |
| Дата-время | ISO 8601 UTC (Z) |
date="2026-10-02T14:03:00Z" |
w:date, dc:date |
| Серийная дата (Sheets) | f64 дней от date-base |
v="46298" |
Excel/ODF serial |
| Размер шрифта | EMU | size="152400" = 12 pt |
w:sz="24" (половинки пункта), ODF
fo:font-size="12pt" |
| Кернинг/трекинг | EMU | spacing="-6350" = −0,5 pt |
w:spacing (twip), w:kern (половинки
пункта) |
| Разрешение | dpi (u32) |
dpi="300" |
Paint |
| Пиксели Paint | u32 |
width="4000" |
слои, маски, выделение |
1.4
Входной синтаксис с суффиксом (чтение и режим pretty)
length := integer ; EMU (канон)
| number unit ; суффиксная форма
unit := "pt" | "mm" | "cm" | "in" | "px" | "pc" | "twip" | "emu"
number := "-"? digits ("." digits)?
Читатель переводит суффиксную форму в EMU с округлением к ближайшему.
Писатель пакета всегда пишет целые EMU; писатель плоского формата в
режиме units="pretty" пишет суффиксную форму, только если
значение точно представимо
(emu % 12700 == 0 → pt,
emu % 36000 == 0 → mm, иначе EMU). Это
гарантирует, что pretty → emu → pretty не меняет файл.
1.5 Ширина столбцов Excel
Excel хранит ширину в «символах» максимальной цифры шрифта стиля «Обычный» (MDW, в пикселях). Пересчёт (ISO 29500 §18.3.1.13):
px = Truncate(((256 × width + Truncate(128 / MDW)) / 256) × MDW)
emu = px × 9525
width (обратно) = Truncate((px − 5) / MDW × 100 + 0.5) / 100 ; 5 px — поля ячейки
MDW для Calibri 11 при 96 dpi = 7 px (Carlito совпадает). Исходное
значение в символах кэшируется в s:col/@width-chars для
round-trip.
2. Идентификаторы
2.1 UUID v7 (RFC 9562)
0 1 2 3
0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1
+-------------------------------------------------------------+
| unix_ts_ms (48 бит) |
+---------------+---------------+-------------------------------+
| unix_ts_ms | ver = 0111 | rand_a (12 бит) |
+---+-----------+---------------+-------------------------------+
|var| rand_b (62 бит) |
+---+---------------------------------------------------------+
| rand_b |
+-------------------------------------------------------------+
unix_ts_ms— миллисекунды Unix (UTC) big-endian;ver= 7;var=10;rand_aиспользуется как 12-битный счётчик монотонности внутри одной миллисекунды (RFC 9562 §6.2, метод 1),rand_b— CSPRNG.- Текстовая форма:
xxxxxxxx-xxxx-7xxx-[89ab]xxx-xxxxxxxxxxxx, строчные hex, 36 символов; в URI —urn:uuid:…. - Генератор ядра (
meridian_kernel::id::Uuid7::new()): потокобезопасный, хранит(last_ts, counter); при переполнении счётчика (4096 за мс) ждёт следующую миллисекунду; при «часах назад» продолжает отlast_ts(монотонность важнее точности времени). - Сравнение — побайтовое; сортировка даёт порядок создания (в пределах точности часов и одного генератора).
/// Генерация UUID v7 с монотонным счётчиком / UUID v7 with a monotonic counter
pub fn next(&mut self) -> Uuid7 {
let mut ts = now_unix_ms();
if ts <= self.last_ts { ts = self.last_ts; self.counter += 1; } else { self.last_ts = ts; self.counter = random_u16() & 0x0FFF; }
if self.counter > 0x0FFF { self.last_ts += 1; ts = self.last_ts; self.counter = 0; } // переполнение → следующая мс / overflow → next ms
let mut b = [0u8; 16];
b[0..6].copy_from_slice(&ts.to_be_bytes()[2..8]);
b[6] = 0x70 | ((self.counter >> 8) as u8 & 0x0F);
b[7] = self.counter as u8;
fill_random(&mut b[8..16]);
b[8] = (b[8] & 0x3F) | 0x80; // variant 10
Uuid7(b)
}2.2 Что получает UUID
| Обязательно | Опционально (по требованию) | Никогда (адресуется иначе) |
|---|---|---|
документ (m:document/@id, постоянен между
сохранениями), секции, таблицы, листы, слайды, мастера, макеты,
фигуры/изображения/группы/коннекторы, объекты (objects/),
комментарии (@target), сноски? — нет (локальный id),
именованные диапазоны, сводные, диаграммы, страницы Notes/Draw, слои
Paint/Draw, письма/события/контакты Mail, вопросы Forms |
абзацы, прогоны — при участии в совместном редактировании, при
появлении ссылки (закладка, комментарий, перекрёстная ссылка) или при
collab.ids=all |
ячейки Sheets (координаты), строки/столбцы, точки геометрии, уровни списков |
2.3 Локальные идентификаторы
Для сущностей, видимых только внутри пакета, — короткие
ASCII-идентификаторы [A-Za-z0-9_-]{1,64}, уникальные в
своём пространстве:
| Пространство | Префикс по соглашению | Примеры |
|---|---|---|
стили (m:style/@id, t:list-style/@id) |
по смыслу / a<n> для автоматических |
Heading1, TableGrid, a17,
dxf-hot |
авторы (meta.xml) |
a<n> |
a1 |
| заметки (сноски) | n<n> |
n1 |
| комментарии | c<n>, ответы
c<n>-<k> |
c1, c1-1 |
| правки | r<n> |
r12 |
| закладки | bm-<имя> |
bm-summary |
| экземпляры списков | L<n> |
L1 |
| общие формулы Sheets | sf<n> |
sf1 |
| внешние ссылки Sheets | x<n> |
x1 |
Ссылки на локальные идентификаторы — атрибуты ref,
style, author, list,
parent, continue, shared. При
слиянии документов (вставка из другого файла) локальные идентификаторы
переименовываются писателем; UUID — сохраняются (конфликт UUID = один и
тот же объект).
2.4 Правила стабильности
- «Сохранить» не меняет ни одного существующего идентификатора; новые объекты получают новые UUID.
- «Сохранить как» / создание из шаблона → новый
m:document/@id, прежний — вmeta.xml/m:derived-from; идентификаторы объектов внутри сохраняются (ссылки между копиями документов остаются осмысленными). - Импорт из чужого формата: UUID генерируются при импорте; при
повторном импорте того же файла они будут другими (чужие форматы
стабильных идентификаторов не имеют), кроме случаев, когда исходный файл
был создан нами и несёт
meridian:idв расширениях (docxw14:paraIdне используется как источник). - Плоский формат: идентификаторы стабильны между сохранениями, чтобы диффы не «шумели».
3. Цвета
3.1 Грамматика
color := "#" HEX{6} | "#" HEX{8} ; sRGB, альфа FF по умолчанию
| "theme(" slot ("," WS* mod)* ")"
| "system(" sysname ")"
| "auto" | "none"
slot := "dk1" | "lt1" | "dk2" | "lt2" | "accent1" .. "accent6" | "hlink" | "folHlink"
mod := "tint" pct | "shade" pct | "lum" spct | "sat" spct | "hue" angle | "alpha" pct
pct := number "%" ; 0–100
spct := ("+" | "-") number "%" ; −100..+100
angle := number "deg"
sysname:= "window" | "window-text" | "highlight" | "highlight-text" | "gray-text" | "button-face" | "button-text"
Примеры: #3B5BDB, #3B5BDB80 (50 %
прозрачности), theme(accent1),
theme(accent1, tint 40%),
theme(dk2, lum +25%, sat -10%),
system(window-text), auto.
3.2 Модификаторы — формулы
Применяются слева направо к разрешённому цвету слота;
tint/shade — в линейном RGB (как DrawingML
a:tint/a:shade),
lum/sat/hue — в HSL (как
a:lumMod/a:lumOff/a:satMod/a:hue):
lin(c) = c ≤ 0.04045 ? c/12.92 : ((c + 0.055)/1.055)^2.4 ; sRGB → линейный
srgb(l) = l ≤ 0.0031308 ? 12.92·l : 1.055·l^(1/2.4) − 0.055
tint p: l' = l·(1 − p) + p по каждому каналу в линейном пространстве (p = pct/100) — осветление к белому
shade p: l' = l·(1 − p) — затемнение к чёрному
lum ±p: L' = clamp(L + p) в HSL (L ∈ [0,1])
sat ±p: S' = clamp(S·(1 + p))
hue a: H' = (H + a) mod 360
alpha p: A' = p
Для OOXML WordprocessingML w:themeTint="99" (hex 00–FF,
применяется Word в HSL по яркости — формулы отличаются от DrawingML;
уточнить по ISO 29500 §17.3.2.6) импортёр записывает
theme(slot, tint N%) с дополнительным атрибутом объекта
m:x-attrs="w:themeTint=99" для точного round-trip; при
рендере используется формула DrawingML (разница в пределах 1–2 единиц
канала на типичных значениях — проверить на корпусе).
3.3 Слоты темы и сопоставление с OOXML/ODF
| Слот MOP | OOXML DrawingML | OOXML w:themeColor (через clrSchemeMapping
по умолчанию) |
ODF |
|---|---|---|---|
dk1 |
a:dk1 |
text1, dark1 |
— (явный sRGB) |
lt1 |
a:lt1 |
background1, light1 |
— |
dk2 |
a:dk2 |
text2, dark2 |
— |
lt2 |
a:lt2 |
background2, light2 |
— |
accent1..6 |
a:accent1..6 |
accent1..6 |
— |
hlink |
a:hlink |
hyperlink |
— |
folHlink |
a:folHlink |
followedHyperlink |
— |
a:sysClr в теме → @lastClr;
a:srgbClr → hex; a:prstClr (red,
blue, …) → таблица 140 именованных цветов → hex. Экспорт в
ODF/RTF/HTML/PDF — вычисленный sRGB (#RRGGBB), прозрачность
— отдельным свойством формата (loext:opacity, CSS
rgba).
3.4 Цветовые пространства
- Все документные форматы (Write, Sheets, Slides, Draw, Notes, Forms) — sRGB; растры при вставке конвертируются в sRGB по встроенному профилю.
- Paint: рабочее пространство — профиль документа
(
pt:image/@profile, ICC v2/v4 вmedia/); слои PNG — в профиле документа; слои EXR — линейный scRGB (float), конвертация на композитинге;merged.png— в профиле документа с встроенным ICC. - Печать: управление цветом — в Meridian Print (ICC-профиль принтера/условия PDF/X — фаза 4+).
- CMYK из PSD/PDF — в sRGB через профиль источника (или «U.S. Web Coated (SWOP)» по умолчанию) с пометкой в паспорте; обратно не конвертируется.