- Updated class names for paragraph style and font family select triggers to improve styling consistency. - Added new CSS rules for hover and focus states on select triggers, enhancing user interaction feedback. - Implemented logic to drop trailing empty pages in document metrics calculations, optimizing page count accuracy. - Enhanced tests to verify new page count behavior when content fits within existing layouts.
99 lines
3.0 KiB
TypeScript
99 lines
3.0 KiB
TypeScript
import type { DocPageSetup } from "./doc-page-setup.ts"
|
|
import { DOCS_PAGE_GAP_PX } from "./docs-page-layout-constants.ts"
|
|
import { mmToPx } from "./doc-page-setup.ts"
|
|
|
|
export type DocsPageMetrics = {
|
|
pageWidth: number
|
|
pageHeight: number
|
|
margins: { top: number; right: number; bottom: number; left: number }
|
|
headerMarginPx: number
|
|
footerMarginPx: number
|
|
bodyAreaHeight: number
|
|
interPageSpacer: number
|
|
}
|
|
|
|
export function computePageMetrics(pageLayout: {
|
|
widthPx: number
|
|
heightPx: number
|
|
marginsPx: { top: number; right: number; bottom: number; left: number }
|
|
headerMarginMm?: number
|
|
footerMarginMm?: number
|
|
effectiveMarginsPx?: { top: number; right: number; bottom: number; left: number }
|
|
}): DocsPageMetrics {
|
|
const margins = pageLayout.effectiveMarginsPx ?? pageLayout.marginsPx
|
|
const headerMarginPx =
|
|
pageLayout.headerMarginMm != null
|
|
? mmToPx(pageLayout.headerMarginMm)
|
|
: pageLayout.marginsPx.top
|
|
const footerMarginPx =
|
|
pageLayout.footerMarginMm != null
|
|
? mmToPx(pageLayout.footerMarginMm)
|
|
: pageLayout.marginsPx.bottom
|
|
|
|
const bodyAreaHeight = pageLayout.heightPx - margins.top - margins.bottom
|
|
const interPageSpacer =
|
|
margins.bottom + DOCS_PAGE_GAP_PX + margins.top
|
|
|
|
return {
|
|
pageWidth: pageLayout.widthPx,
|
|
pageHeight: pageLayout.heightPx,
|
|
margins,
|
|
headerMarginPx,
|
|
footerMarginPx,
|
|
bodyAreaHeight: Math.max(1, bodyAreaHeight),
|
|
interPageSpacer,
|
|
}
|
|
}
|
|
|
|
/** Page count from prose content height (excludes outer surface padding). */
|
|
export function computePageCount(contentHeight: number, metrics: DocsPageMetrics): number {
|
|
if (contentHeight <= 0) return 1
|
|
const { bodyAreaHeight, interPageSpacer } = metrics
|
|
let remaining = contentHeight
|
|
let pages = 1
|
|
while (remaining > bodyAreaHeight) {
|
|
remaining -= bodyAreaHeight
|
|
if (remaining <= 0) break
|
|
remaining -= interPageSpacer
|
|
pages += 1
|
|
}
|
|
|
|
// Drop a trailing page that would be entirely empty (simulated height overshoot).
|
|
while (pages > 1 && contentHeight <= computeProseMinHeight(pages - 1, metrics)) {
|
|
pages -= 1
|
|
}
|
|
|
|
return pages
|
|
}
|
|
|
|
export function computeStackHeight(pageCount: number, pageHeight: number): number {
|
|
return pageCount * pageHeight + Math.max(0, pageCount - 1) * DOCS_PAGE_GAP_PX
|
|
}
|
|
|
|
/** Minimum prose height to align with visual page stack including inter-page spacers. */
|
|
export function computeProseMinHeight(pageCount: number, metrics: DocsPageMetrics): number {
|
|
const { bodyAreaHeight, interPageSpacer } = metrics
|
|
return (
|
|
pageCount * bodyAreaHeight + Math.max(0, pageCount - 1) * interPageSpacer
|
|
)
|
|
}
|
|
|
|
export function emptyRegionDoc() {
|
|
return { type: "doc", content: [{ type: "paragraph" }] }
|
|
}
|
|
|
|
export function pageSetupFromMetrics(
|
|
setup: DocPageSetup | null | undefined,
|
|
patch: Partial<DocPageSetup>
|
|
): DocPageSetup {
|
|
return { ...(setup ?? defaultMinimalSetup()), ...patch }
|
|
}
|
|
|
|
function defaultMinimalSetup(): DocPageSetup {
|
|
return {
|
|
widthMm: 210,
|
|
heightMm: 297,
|
|
marginsMm: { top: 25.4, right: 25.4, bottom: 25.4, left: 25.4 },
|
|
}
|
|
}
|