Files
Reactive-Resume/packages/pdf/src/semantic/adapter.ts
T
2026-07-30 12:39:15 +02:00

87 lines
3.4 KiB
TypeScript

import type { Style } from "@react-pdf/types";
import type { ResolvedNodeStyle, ResolvedPageSize } from "@reactive-resume/resume/stylesheet";
type ResolvedPdfPageSize = ResolvedPageSize;
export type ResolvedPdfNodePresentation = {
style?: Style;
size?: ResolvedPdfPageSize;
break?: boolean;
wrap?: boolean;
fixed?: boolean;
minPresenceAhead?: number;
orphans?: number;
widows?: number;
};
export type ResolvedPdfFlowProps = Omit<ResolvedPdfNodePresentation, "style" | "size" | "orphans" | "widows">;
export type ResolvedPdfTextProps = Omit<ResolvedPdfNodePresentation, "style" | "size">;
export const resolvedPdfFlowProps = ({
break: breakBefore,
wrap,
fixed,
minPresenceAhead,
}: ResolvedPdfNodePresentation): ResolvedPdfFlowProps => ({
...(breakBefore === undefined ? {} : { break: breakBefore }),
...(wrap === undefined ? {} : { wrap }),
...(fixed === undefined ? {} : { fixed }),
...(minPresenceAhead === undefined ? {} : { minPresenceAhead }),
});
export const resolvedPdfTextProps = ({
orphans,
widows,
...presentation
}: ResolvedPdfNodePresentation): ResolvedPdfTextProps => ({
...resolvedPdfFlowProps(presentation),
...(orphans === undefined ? {} : { orphans }),
...(widows === undefined ? {} : { widows }),
});
const toReactPdfProperty = (property: string) => {
if (property === "-resume-shadow-color") return "shadowColor";
if (property === "-resume-shadow-width") return "shadowWidth";
return property.replace(/-([a-z])/g, (_match, letter: string) => letter.toUpperCase());
};
const styleDelta = (resolved: ResolvedNodeStyle, base: ResolvedNodeStyle["style"] | undefined): Style | undefined => {
const specified = new Set(resolved.specifiedStyleProperties);
const hostBase = new Set(resolved.hostBaseStyleProperties);
const entries: [string, string | number | undefined][] =
base === undefined
? Object.entries(resolved.style)
: Object.entries(resolved.style).filter(([property]) => !hostBase.has(property) && specified.has(property));
for (const property of specified) {
if (!hostBase.has(property) && !(property in resolved.style)) entries.push([property, undefined]);
}
if (entries.length === 0) return undefined;
return Object.freeze(
Object.fromEntries(entries.map(([property, value]) => [toReactPdfProperty(property), value])),
) as Style;
};
export function adaptResolvedPdfNode(
resolved: ResolvedNodeStyle,
base?: ResolvedNodeStyle,
): ResolvedPdfNodePresentation {
const style = styleDelta(resolved, base?.style);
const { structural } = resolved;
const breakBefore =
structural.breakBefore === "page" ? true : base?.structural.breakBefore === "page" ? false : undefined;
const wrap = structural.breakInside === "avoid" ? false : base?.structural.breakInside === "avoid" ? true : undefined;
const presentation = {
...(style ? { style } : {}),
...(structural.pageSize === undefined ? {} : { size: structural.pageSize }),
...(breakBefore === undefined ? {} : { break: breakBefore }),
...(wrap === undefined ? {} : { wrap }),
...(structural.fixed === undefined ? {} : { fixed: structural.fixed }),
...(structural.minPresenceAhead === undefined ? {} : { minPresenceAhead: structural.minPresenceAhead }),
...(structural.orphans === undefined ? {} : { orphans: structural.orphans }),
...(structural.widows === undefined ? {} : { widows: structural.widows }),
} satisfies ResolvedPdfNodePresentation;
return Object.freeze(presentation);
}