fix: a header/footer band is as tall as its own font, not the body's
The zone is one paragraph, so its mark sizes every line of the band. Runs that agree on a font now set that strut (applyUniformRunFont, as the body already does), the zone schema carries blockFontSize so it renders, and hfReachPx measures a line by the family's natural height instead of a flat 1.15. A 10pt footer reserved 12pt lines and sat 1.6mm high on every page.
This commit is contained in:
1 parent
853762a774
commit
e2ee55cf35
5 files changed
+25
-9
No files matched your search
@@ -28,7 +28,7 @@
|
||||
import { findBookmark } from '../editor/extensions/bookmark';
|
||||
import { recordTransaction, resetHistoryLog } from '../utils/historyLog.svelte';
|
||||
import { wheelZoomFactor } from '../utils/zoom';
|
||||
import { styleCss } from '../styles/styleSheet';
|
||||
import { styleCss, singleLineHeight } from '../styles/styleSheet';
|
||||
import { styleSheet } from '../styles/sheet.svelte';
|
||||
import { t } from '../i18n/i18n.svelte';
|
||||
import { withShortcut } from '../i18n/shortcut';
|
||||
@@ -103,19 +103,23 @@
|
||||
const HF_LINE_PX = 16 * 1.15;
|
||||
function hfReachPx(doc: HfDoc, distPx: number, footer = false): number {
|
||||
if (!doc || hfIsEmpty(doc)) return 0;
|
||||
type Run = { type?: string; attrs?: { height?: number; wrap?: string }; marks?: { type?: string; attrs?: { fontSize?: string } }[] };
|
||||
const para = doc.content?.[0] as { content?: Run[]; attrs?: { spaceBefore?: number; spaceAfter?: number } } | undefined;
|
||||
type Run = { type?: string; attrs?: { height?: number; wrap?: string }; marks?: { type?: string; attrs?: { fontSize?: string; fontFamily?: string } }[] };
|
||||
const para = doc.content?.[0] as { content?: Run[]; attrs?: { spaceBefore?: number; spaceAfter?: number; fontSize?: string; fontFamily?: string } } | undefined;
|
||||
const inline = para?.content ?? [];
|
||||
// The zone's biggest text run sizes its lines: LibreOffice grows the band to hold them
|
||||
// when fo:min-height is smaller (probed, a 3-line header), and a zone set throughout in
|
||||
// 10pt reserves 10pt — the body default only stands in for a run that declares none.
|
||||
let linePx = 0;
|
||||
// A line is its font's own natural height, which is what the band renders at.
|
||||
const line = (size?: string, family?: string) =>
|
||||
size ? (parseFloat(size) * 96) / 72 * singleLineHeight(family) : HF_LINE_PX;
|
||||
// The paragraph mark is the strut every run that declares no size of its own takes.
|
||||
const base = line(para?.attrs?.fontSize, para?.attrs?.fontFamily);
|
||||
let linePx = base;
|
||||
for (const n of inline) {
|
||||
if (n.type === 'image') continue;
|
||||
const size = n.marks?.find((m) => m.type === 'textStyle')?.attrs?.fontSize;
|
||||
linePx = Math.max(linePx, size ? (parseFloat(size) * 96) / 72 * 1.15 : HF_LINE_PX);
|
||||
const ts = n.marks?.find((m) => m.type === 'textStyle')?.attrs;
|
||||
linePx = Math.max(linePx, ts?.fontSize ? line(ts.fontSize, ts.fontFamily) : base);
|
||||
}
|
||||
if (!linePx) linePx = HF_LINE_PX;
|
||||
// Per line, since an as-character image (a letterhead logo) makes its own line
|
||||
// as tall as it is; the others are one text line each. A positioned frame is out
|
||||
// of flow — a page-sized background would otherwise reserve the whole page.
|
||||
|
||||
@@ -21,6 +21,7 @@ import { Image } from './image';
|
||||
import { Shortcuts } from './shortcuts';
|
||||
import { TabStops } from './tabStops';
|
||||
import { ParagraphSpacing } from './paragraphSpacing';
|
||||
import { BlockFontSize } from './blockFontSize';
|
||||
|
||||
// Schema for the header/footer mini editors: exactly one paragraph of formatted
|
||||
// runs, line breaks, page fields, and inline (as-character) images. Marks mirror
|
||||
@@ -65,6 +66,9 @@ export function hfExtensions(placeholder = '') {
|
||||
// The zone style's own margins: what the band has to be tall enough for
|
||||
// (Editor.svelte's hfReachPx), not a gap the bottom-anchored footer moves by.
|
||||
ParagraphSpacing.configure({ types: ['paragraph'] }),
|
||||
// The zone is one paragraph, so its mark's font is the band's line height: a 10pt
|
||||
// footer reserves 10pt lines, not the body's 12pt.
|
||||
BlockFontSize.configure({ types: ['paragraph'] }),
|
||||
CharacterStyle,
|
||||
Highlight.configure({ multicolor: true }),
|
||||
HfHardBreak,
|
||||
|
||||
@@ -2022,6 +2022,9 @@ function convertHfPart(relId: string | null, ctx: Ctx): HfDoc {
|
||||
|
||||
const para: Node = { type: 'paragraph', content: inline };
|
||||
const attrs: Record<string, string> = {};
|
||||
// The zone is one paragraph here, so its strut is the whole band's line height —
|
||||
// runs that agree on a size must set it, or a 10pt footer reserves 12pt lines.
|
||||
applyUniformRunFont(attrs, inline);
|
||||
if (textAlign) attrs.textAlign = textAlign;
|
||||
if (stops) attrs.tabStops = stops;
|
||||
Object.assign(attrs, box);
|
||||
|
||||
@@ -736,6 +736,9 @@ function convertHfZone(zoneEl: Element, ctx: Ctx): HfDoc {
|
||||
|
||||
const para: Node = { type: 'paragraph', content };
|
||||
const attrs: Record<string, string | number> = {};
|
||||
// The zone is one paragraph here, so its strut is the whole band's line height —
|
||||
// runs that agree on a size must set it, or a 10pt footer reserves 12pt lines.
|
||||
applyUniformRunFont(attrs, content);
|
||||
if (textAlign) attrs.textAlign = textAlign;
|
||||
if (stops) attrs.tabStops = stops;
|
||||
if (spaceBefore) attrs.spaceBefore = spaceBefore;
|
||||
|
||||
@@ -932,10 +932,12 @@ describe('Leg 3a: different first page header/footer → buildOdt → importOdt'
|
||||
// Arial 10pt on the runs AND the page-field atoms, so the field digits keep the font.
|
||||
const arial = [{ type: 'textStyle', attrs: { fontFamily: 'Arial', fontSize: '10pt' } }];
|
||||
const header: N = { type: 'doc', content: [{ type: 'paragraph', content: [{ type: 'text', text: 'Default header' }] }] };
|
||||
const footer: N = { type: 'doc', content: [{ type: 'paragraph', attrs: { textAlign: 'center' }, content: [
|
||||
// The strut follows runs that agree on a font (applyUniformRunFont), so the zone
|
||||
// paragraph carries it either way — stating it keeps the round trip an identity.
|
||||
const footer: N = { type: 'doc', content: [{ type: 'paragraph', attrs: { textAlign: 'center', fontFamily: 'Arial', fontSize: '10pt' }, content: [
|
||||
{ type: 'text', text: 'Seite ', marks: arial }, { type: 'pageNumber', marks: arial }, { type: 'text', text: ' von ', marks: arial }, { type: 'pageCount', marks: arial },
|
||||
] }] };
|
||||
const headerFirst: N = { type: 'doc', content: [{ type: 'paragraph', content: [
|
||||
const headerFirst: N = { type: 'doc', content: [{ type: 'paragraph', attrs: { fontFamily: 'Arial', fontSize: '10pt' }, content: [
|
||||
{ type: 'text', text: 'Cover', marks: [{ type: 'bold' }, ...arial] },
|
||||
] }] };
|
||||
const footerFirst: N = { type: 'doc', content: [{ type: 'paragraph', content: [
|
||||
|
||||
Reference in new issue
Block a user