feat(pdf): roll out shared RTL layout to all templates

Introduce createRtlStyleHelpers and a single rtl flag on RenderProvider,
migrate every template page to mirrored layout styles, and rename
alignRight to alignEnd. Fix plain rich text rendering via PdfText
paragraph renderers and map legacy Times New Roman to Times-Roman.

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
Amruth Pillai
2026-05-25 16:29:50 +02:00
co-authored by Cursor
parent 86fff7237f
commit 24c882fa9f
28 changed files with 501 additions and 276 deletions
@@ -12,6 +12,7 @@ import { getTemplateMetrics } from "../shared/metrics";
import { getTemplatePageMinHeightStyle, getTemplatePageSize } from "../shared/page-size";
import { hasTemplatePicture } from "../shared/picture";
import { Heading, Icon, Link, Text } from "../shared/primitives";
import { createRtlStyleHelpers } from "../shared/rtl";
import { Section } from "../shared/sections";
import { composeStyles, headerNameLineHeight, resolvePlacementColor } from "../shared/styles";
@@ -147,9 +148,10 @@ const Header = ({ styles, colors }: { styles: PikachuStyles; colors: TemplateCol
};
const usePikachuTemplate = (): PikachuTemplate => {
const { picture, metadata } = useRender();
const { picture, metadata, rtl } = useRender();
return useMemo(() => {
const r = createRtlStyleHelpers(rtl);
const foreground = rgbaStringToHex(metadata.design.colors.text);
const background = rgbaStringToHex(metadata.design.colors.background);
const primary = rgbaStringToHex(metadata.design.colors.primary);
@@ -162,6 +164,7 @@ const usePikachuTemplate = (): PikachuTemplate => {
fontWeight: metadata.typography.body.fontWeights[0] ?? "400",
lineHeight: metadata.typography.body.lineHeight,
color: foreground,
...r.text,
} satisfies Style;
const baseStyles = StyleSheet.create({
@@ -173,6 +176,7 @@ const usePikachuTemplate = (): PikachuTemplate => {
fontFamily: metadata.typography.body.fontFamily,
fontSize: metadata.typography.body.fontSize,
lineHeight: metadata.typography.body.lineHeight,
direction: r.pageDirection,
},
text: bodyText,
heading: {
@@ -181,24 +185,25 @@ const usePikachuTemplate = (): PikachuTemplate => {
fontWeight: metadata.typography.heading.fontWeights.at(-1) ?? "600",
lineHeight: metadata.typography.heading.lineHeight,
color: foreground,
...r.text,
},
div: { rowGap: metrics.gapY(0.125), columnGap: metrics.gapX(1 / 3) },
inline: { flexDirection: "row", alignItems: "center", columnGap: metrics.gapX(1 / 3) },
inline: { flexDirection: r.row, alignItems: "center", columnGap: metrics.gapX(1 / 3) },
link: { textDecoration: "none", color: foreground },
small: { fontSize: metadata.typography.body.fontSize * 0.875 },
bold: { fontWeight: metadata.typography.body.fontWeights.at(-1) ?? "600" },
richParagraph: { margin: 0, ...bodyText },
richListItemRow: { flexDirection: "row", columnGap: metrics.gapX(1 / 3), alignItems: "flex-start" },
richListItemMarker: { width: metadata.typography.body.fontSize, textAlign: "right", ...bodyText },
richListItemMarker: { ...bodyText, width: metadata.typography.body.fontSize, textAlign: r.listMarkerTextAlign },
richListItemContent: { flex: 1, ...bodyText },
splitRow: {
flexDirection: "row",
flexDirection: r.row,
flexWrap: "wrap",
alignItems: "flex-start",
justifyContent: "space-between",
columnGap: metrics.gapX(2 / 3),
},
alignRight: { textAlign: "right", minWidth: 0, maxWidth: "100%", flexShrink: 1 },
alignEnd: { ...r.alignEnd },
section: { flexDirection: "column", rowGap: metrics.gapY(0.25) },
sectionHeading: { borderBottomWidth: 1, borderBottomColor: primary },
item: { rowGap: metrics.gapY(0.125) },
@@ -206,7 +211,7 @@ const usePikachuTemplate = (): PikachuTemplate => {
levelItem: { borderColor: primary },
levelItemActive: { backgroundColor: primary },
layout: {
flexDirection: "row",
flexDirection: r.row,
columnGap: metrics.columnGap,
},
sidebarColumn: {
@@ -217,7 +222,7 @@ const usePikachuTemplate = (): PikachuTemplate => {
flex: 1,
},
headerRow: {
flexDirection: "row",
flexDirection: r.row,
alignItems: "center",
columnGap: metrics.gapX(1),
},
@@ -237,8 +242,7 @@ const usePikachuTemplate = (): PikachuTemplate => {
paddingBottom: metrics.gapY(0.5),
},
headerIdentity: {
textAlign: "left",
alignItems: "flex-start",
...r.headerIdentity,
rowGap: metrics.gapY(0.35),
},
headerName: {
@@ -248,13 +252,13 @@ const usePikachuTemplate = (): PikachuTemplate => {
},
headerText: { color: background },
contactList: {
flexDirection: "row",
flexDirection: r.row,
flexWrap: "wrap",
rowGap: metrics.gapY(0.125),
columnGap: metrics.gapX(0.75),
},
contactItem: {
flexDirection: "row",
flexDirection: r.row,
alignItems: "center",
columnGap: metrics.gapX(1 / 6),
},
@@ -297,5 +301,5 @@ const usePikachuTemplate = (): PikachuTemplate => {
}),
} satisfies PikachuStyles,
};
}, [picture, metadata]);
}, [picture, metadata, rtl]);
};