fix: scale a named style's line spacing by the font's natural line height

A proportional line spacing multiplies the font's own single line height in
Word and LibreOffice, CSS multiplies the font size. The node attr already
scaled by Liberation Serif's 1.15; a style's line-height went out raw, so an
imported 1.08 rendered ~20% tighter than the file asks for and the drift
accumulated down the page.
This commit is contained in:
Steffen Becker committed 2026-08-08 08:30:04 +00:00
1 parent be96ee626c
commit 3a71491b58
2 files changed
+18 -7

No files matched your search

+4 -4
View File
@@ -1,9 +1,9 @@
import { Extension } from '@tiptap/core';
import { DEFAULT_SINGLE_LINE_HEIGHT } from '../../styles/styleSheet';
// ODF line spacing multiplies the font's natural line height (Liberation Serif
// ≈1.15× em); CSS unitless line-height multiplies the font size. Scale by this
// ratio so the on-screen box matches LibreOffice. Keep in sync with editor.css.
const LINE_HEIGHT_RATIO = 1.15;
// ODF line spacing multiplies the font's natural line height, CSS unitless
// line-height the font size. A node attr knows no family, so it takes the default.
const LINE_HEIGHT_RATIO = DEFAULT_SINGLE_LINE_HEIGHT;
declare module '@tiptap/core' {
interface Commands<ReturnType> {
+14 -3
View File
@@ -198,8 +198,9 @@ export function cssFontFamily(name: string): string {
}
// Single spacing is the font's *natural* line height, so it differs per family.
// editor.css covers Liberation Serif's 1.15; only the bundled families that deviate
// are listed, measured against LibreOffice at 12pt.
// Liberation Serif's 1.15 is the default (editor.css); only the bundled families that
// deviate are listed, measured against LibreOffice at 12pt.
export const DEFAULT_SINGLE_LINE_HEIGHT = 1.15;
const SINGLE_LINE_HEIGHT: Record<string, number> = {
Calibri: 1.2208,
Carlito: 1.2208,
@@ -207,6 +208,12 @@ const SINGLE_LINE_HEIGHT: Record<string, number> = {
'Liberation Mono': 1.1333,
};
// A proportional line spacing multiplies the font's natural line height, while CSS
// multiplies the font size — so the stored factor is scaled by the family's own.
export function singleLineHeight(fontFamily?: string): number {
return (fontFamily && SINGLE_LINE_HEIGHT[fontFamily]) || DEFAULT_SINGLE_LINE_HEIGHT;
}
// The text half of a rule, shared with the table-style family (tableStyles.ts).
export function textDeclarations(t: TextProps): string[] {
const out: string[] = [];
@@ -230,7 +237,11 @@ function declarations(r: ResolvedStyle): string[] {
const { para: p } = r;
const out = textDeclarations(r.text);
if (p.textAlign) out.push(`text-align: ${p.textAlign}`);
if (p.lineHeight) out.push(`line-height: ${p.lineHeight}`);
if (p.lineHeight) {
const n = parseFloat(p.lineHeight);
const scaled = Math.round(n * singleLineHeight(r.text.fontFamily) * 1000) / 1000;
out.push(`line-height: ${isNaN(n) ? p.lineHeight : scaled}`);
}
if (p.spaceBefore != null) out.push(`margin-top: ${p.spaceBefore}pt`);
if (p.spaceAfter != null) out.push(`margin-bottom: ${p.spaceAfter}pt`);
if (p.indent != null) out.push(`margin-left: ${p.indent}cm`);