ultisuite-client/lib/drive/docs-header-footer-layout.ts
R3D347HR4Y 2a7c153748
Some checks are pending
E2E / Playwright e2e (push) Waiting to run
wrap page
2026-06-10 12:48:27 +02:00

204 lines
6.4 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

import type { DocPageHeaderFooter, DocPageLayout, DocPageSetup } from "./doc-page-setup.ts"
import { mmToPx, pxToMm } from "./doc-page-setup.ts"
export type DocsHeaderFooterRegion = "header" | "footer"
/** Height of the Google Docsstyle header/footer chrome bar (px). */
export const DOCS_HF_CHROME_BAR_PX = 28
export function headerOffsetPx(pageLayout: DocPageLayout): number {
return pageLayout.headerMarginMm != null
? mmToPx(pageLayout.headerMarginMm)
: 0
}
export function footerOffsetPx(pageLayout: DocPageLayout): number {
return pageLayout.footerMarginMm != null
? mmToPx(pageLayout.footerMarginMm)
: 0
}
export function defaultHeaderZoneHeightPx(pageLayout: DocPageLayout): number {
return Math.max(24, pageLayout.marginsPx.top - headerOffsetPx(pageLayout))
}
export function defaultFooterZoneHeightPx(pageLayout: DocPageLayout): number {
return Math.max(24, pageLayout.marginsPx.bottom - footerOffsetPx(pageLayout))
}
export function regionZoneHeightPx(
region: DocPageHeaderFooter | null | undefined,
defaultPx: number
): number {
if (region?.heightMm != null && region.heightMm > 0) {
return Math.max(defaultPx, mmToPx(region.heightMm))
}
return defaultPx
}
/** Header/footer content shown on a given page index. */
export function resolveRegionForPage(
pageLayout: DocPageLayout,
region: DocsHeaderFooterRegion,
pageIndex: number
): DocPageHeaderFooter | null | undefined {
const differentFirst = pageLayout.headerFooterDifferentFirstPage ?? false
if (region === "header") {
if (pageIndex === 0 && differentFirst) return pageLayout.headerFirstPage ?? null
return pageLayout.header
}
if (pageIndex === 0 && differentFirst) return pageLayout.footerFirstPage ?? null
return pageLayout.footer
}
export function effectiveTopMarginPx(pageLayout: DocPageLayout): number {
const offset = headerOffsetPx(pageLayout)
const defaultZone = defaultHeaderZoneHeightPx(pageLayout)
const sharedH = regionZoneHeightPx(pageLayout.header, defaultZone)
const firstH = pageLayout.headerFooterDifferentFirstPage
? regionZoneHeightPx(pageLayout.headerFirstPage, defaultZone)
: sharedH
return Math.max(pageLayout.marginsPx.top, offset + Math.max(sharedH, firstH))
}
export function effectiveBottomMarginPx(pageLayout: DocPageLayout): number {
const offset = footerOffsetPx(pageLayout)
const defaultZone = defaultFooterZoneHeightPx(pageLayout)
const sharedH = regionZoneHeightPx(pageLayout.footer, defaultZone)
const firstH = pageLayout.headerFooterDifferentFirstPage
? regionZoneHeightPx(pageLayout.footerFirstPage, defaultZone)
: sharedH
return Math.max(pageLayout.marginsPx.bottom, offset + Math.max(sharedH, firstH))
}
export function effectiveMarginsPx(
pageLayout: DocPageLayout,
livePreview?: { region: DocsHeaderFooterRegion; heightPx: number } | null,
measuredHeights?: Partial<Record<DocsHeaderFooterRegion, number>>
) {
let top = effectiveTopMarginPx(pageLayout)
let bottom = effectiveBottomMarginPx(pageLayout)
if (measuredHeights?.header != null) {
const offset = headerOffsetPx(pageLayout)
top = Math.max(pageLayout.marginsPx.top, offset + measuredHeights.header)
}
if (measuredHeights?.footer != null) {
const offset = footerOffsetPx(pageLayout)
bottom = Math.max(pageLayout.marginsPx.bottom, offset + measuredHeights.footer)
}
if (livePreview) {
const { region, heightPx } = livePreview
if (region === "header") {
const offset = headerOffsetPx(pageLayout)
top = Math.max(pageLayout.marginsPx.top, offset + heightPx)
} else {
const offset = footerOffsetPx(pageLayout)
bottom = Math.max(pageLayout.marginsPx.bottom, offset + heightPx)
}
}
return {
top,
right: pageLayout.marginsPx.right,
bottom,
left: pageLayout.marginsPx.left,
}
}
export function pageRegionZoneHeightPx(
pageLayout: DocPageLayout,
region: DocsHeaderFooterRegion,
pageIndex: number
): number {
const data = resolveRegionForPage(pageLayout, region, pageIndex)
const defaultPx =
region === "header"
? defaultHeaderZoneHeightPx(pageLayout)
: defaultFooterZoneHeightPx(pageLayout)
return regionZoneHeightPx(data, defaultPx)
}
export function pageHeaderGeometry(
pageLayout: DocPageLayout,
pageTop: number,
pageIndex: number
) {
const offset = headerOffsetPx(pageLayout)
const zoneHeight = pageRegionZoneHeightPx(pageLayout, "header", pageIndex)
const zoneTop = pageTop + offset
return {
zoneTop,
zoneHeight,
bottomLine: zoneTop + zoneHeight,
offset,
}
}
export function pageFooterGeometry(
pageLayout: DocPageLayout,
pageTop: number,
pageHeight: number,
pageIndex: number
) {
const offset = footerOffsetPx(pageLayout)
const zoneHeight = pageRegionZoneHeightPx(pageLayout, "footer", pageIndex)
const zoneBottom = pageTop + pageHeight - offset
return {
zoneTop: zoneBottom - zoneHeight,
zoneHeight,
zoneBottom,
topLine: zoneBottom - zoneHeight,
offset,
}
}
export function regionStorageKey(
region: DocsHeaderFooterRegion,
pageIndex: number,
differentFirstPage: boolean
): keyof DocPageSetup {
if (pageIndex === 0 && differentFirstPage) {
return region === "header" ? "headerFirstPage" : "footerFirstPage"
}
return region
}
export function buildRegionPatch(
setup: DocPageSetup,
region: DocsHeaderFooterRegion,
pageIndex: number,
content: Record<string, unknown>,
contentHeightPx: number
): Partial<DocPageSetup> {
const differentFirst = setup.headerFooterDifferentFirstPage ?? false
const key = regionStorageKey(region, pageIndex, differentFirst)
const topPx = mmToPx(setup.marginsMm.top)
const bottomPx = mmToPx(setup.marginsMm.bottom)
const headerOff =
setup.headerMarginMm != null ? mmToPx(setup.headerMarginMm) : 0
const footerOff =
setup.footerMarginMm != null ? mmToPx(setup.footerMarginMm) : 0
const defaultZonePx =
region === "header" ? Math.max(24, topPx - headerOff) : Math.max(24, bottomPx - footerOff)
const heightMm = pxToMm(Math.max(defaultZonePx, contentHeightPx))
return {
[key]: { content, heightMm },
}
}
export function toggleDifferentFirstPage(
setup: DocPageSetup,
enabled: boolean
): Partial<DocPageSetup> {
if (enabled) {
return {
headerFooterDifferentFirstPage: true,
headerFirstPage: setup.headerFirstPage ?? setup.header ?? null,
footerFirstPage: setup.footerFirstPage ?? setup.footer ?? null,
}
}
return { headerFooterDifferentFirstPage: false }
}