diff --git a/packages/pdf/src/templates/shared/section-heading-color.test.tsx b/packages/pdf/src/templates/shared/section-heading-color.test.tsx index 9044342c9..55cd2b84f 100644 --- a/packages/pdf/src/templates/shared/section-heading-color.test.tsx +++ b/packages/pdf/src/templates/shared/section-heading-color.test.tsx @@ -31,21 +31,24 @@ const renderHeading = async (css: string, hideSectionIcons = false) => { const page = await document.getPage(1); const operators = await page.getOperatorList(); let fill = ""; + let size = 0; const text: { value: string; fill: string }[] = []; + const sizes: { value: string; size: number }[] = []; const colors: string[] = []; for (const [index, fn] of operators.fnArray.entries()) { const args = operators.argsArray[index]; if (fn === OPS.setFillRGBColor) fill = args[0]; + if (fn === OPS.setFont) size = args[1]; if (fn === OPS.setFillRGBColor || fn === OPS.setStrokeRGBColor) colors.push(args[0]); - if (fn === OPS.showText) - text.push({ - value: args[0] - .map((glyph: { unicode?: string } | number) => (typeof glyph === "number" ? "" : (glyph.unicode ?? ""))) - .join(""), - fill, - }); + if (fn === OPS.showText) { + const value = args[0] + .map((glyph: { unicode?: string } | number) => (typeof glyph === "number" ? "" : (glyph.unicode ?? ""))) + .join(""); + text.push({ value, fill }); + sizes.push({ value, size }); + } } - return { text, colors }; + return { text, colors, sizes }; } finally { await loadingTask.destroy(); } @@ -72,4 +75,8 @@ describe("Semantic section heading colors (#3348)", () => { expect(text).toContainEqual({ value: "HEADING", fill: "#000000" }); expect(colors).toContain("#dc2626"); }); + it.each([false, true])("gives heading text its text styles with hideSectionIcons=%s", async (hidden) => { + const { sizes } = await renderHeading("section-heading { font-size: 21pt; text-transform: lowercase; }", hidden); + expect(sizes).toContainEqual({ value: "heading", size: 21 }); + }); }); diff --git a/packages/pdf/src/templates/shared/sections.tsx b/packages/pdf/src/templates/shared/sections.tsx index 9e5ee1c27..a5a01ea84 100644 --- a/packages/pdf/src/templates/shared/sections.tsx +++ b/packages/pdf/src/templates/shared/sections.tsx @@ -185,6 +185,34 @@ const defaultSectionHeadingContainerStyle = { columnGap: 4, } satisfies Style; +const sectionHeadingTextProperties = new Set([ + "color", + "direction", + "fontSize", + "fontStyle", + "fontWeight", + "letterSpacing", + "lineHeight", + "textAlign", + "textDecoration", + "textDecorationColor", + "textDecorationStyle", + "textIndent", + "textTransform", +]); + +/** + * With an icon, the heading node is the icon row and its title is a separate text that carries the template's own + * heading font, so the heading's text styles have to be handed to that text or they never reach it. + */ +const sectionHeadingTextOf = (style: Style | undefined): Style | undefined => + style && + (Object.fromEntries( + Object.entries(style).filter( + ([property, value]) => value !== undefined && sectionHeadingTextProperties.has(property), + ), + ) as Style); + export const getSectionHeadingTextStyle = (...styles: StyleInput[]): Style[] => { const textStyles = composeStyles(...styles).map( ({ @@ -380,9 +408,7 @@ const SectionShell = ({ sectionId, title, showHeading = true, children }: Sectio bindSemanticNode={false} style={getSectionHeadingTextStyle( sectionHeadingStyle, - sectionHeadingResolved.style?.color === undefined - ? undefined - : { color: sectionHeadingResolved.style.color }, + sectionHeadingTextOf(sectionHeadingResolved.style), )} > {sectionTitle}