From bd3cf600e7a766c2fd980fbc01924e59664184b8 Mon Sep 17 00:00:00 2001 From: Antonella Sgarlatta Date: Mon, 18 May 2026 23:59:23 -0300 Subject: [PATCH] fix: Fixes Super pdf export code block formatting issues --- .../Utils/PDFExport/CodeHighlightColors.ts | 33 ++++++++++++ .../Lexical/Utils/PDFExport/PDFExport.tsx | 53 ++++++++++++++----- .../Utils/PDFExport/PDFLayoutConstants.ts | 2 + 3 files changed, 74 insertions(+), 14 deletions(-) create mode 100644 packages/web/src/javascripts/Components/SuperEditor/Lexical/Utils/PDFExport/CodeHighlightColors.ts diff --git a/packages/web/src/javascripts/Components/SuperEditor/Lexical/Utils/PDFExport/CodeHighlightColors.ts b/packages/web/src/javascripts/Components/SuperEditor/Lexical/Utils/PDFExport/CodeHighlightColors.ts new file mode 100644 index 000000000..6db3ea222 --- /dev/null +++ b/packages/web/src/javascripts/Components/SuperEditor/Lexical/Utils/PDFExport/CodeHighlightColors.ts @@ -0,0 +1,33 @@ +import BlocksEditorTheme from '../../Theme/Theme' + +const TOKEN_CLASS_COLORS: Record = { + Lexical__tokenComment: 'slategray', + Lexical__tokenPunctuation: '#999', + Lexical__tokenProperty: '#905', + Lexical__tokenSelector: '#690', + Lexical__tokenOperator: '#9a6e3a', + Lexical__tokenAttr: '#07a', + Lexical__tokenVariable: '#e90', + Lexical__tokenFunction: '#dd4a68', +} + +const HIGHLIGHT_TYPE_TO_COLOR: Record = {} + +const codeHighlightTheme = BlocksEditorTheme.codeHighlight + +if (codeHighlightTheme) { + for (const [highlightType, tokenClass] of Object.entries(codeHighlightTheme)) { + const color = TOKEN_CLASS_COLORS[tokenClass] + if (color) { + HIGHLIGHT_TYPE_TO_COLOR[highlightType] = color + } + } +} + +export const getPDFColorForCodeHighlight = (highlightType: string | null | undefined): string | undefined => { + if (!highlightType) { + return undefined + } + + return HIGHLIGHT_TYPE_TO_COLOR[highlightType] +} diff --git a/packages/web/src/javascripts/Components/SuperEditor/Lexical/Utils/PDFExport/PDFExport.tsx b/packages/web/src/javascripts/Components/SuperEditor/Lexical/Utils/PDFExport/PDFExport.tsx index 36380066d..8212d10a4 100644 --- a/packages/web/src/javascripts/Components/SuperEditor/Lexical/Utils/PDFExport/PDFExport.tsx +++ b/packages/web/src/javascripts/Components/SuperEditor/Lexical/Utils/PDFExport/PDFExport.tsx @@ -4,6 +4,7 @@ import { $isElementNode, $isParagraphNode, $isLineBreakNode, + $isTabNode, $isTextNode, LexicalEditor, LexicalNode, @@ -15,7 +16,7 @@ import { $isHeadingNode, type HeadingNode, $isQuoteNode } from '@lexical/rich-te import { $isListNode, $isListItemNode, ListType } from '@lexical/list' import { $isHorizontalRuleNode } from '@lexical/react/LexicalHorizontalRuleNode' import { $isTableNode, $isTableRowNode, $isTableCellNode } from '@lexical/table' -import { $isCodeNode } from '@lexical/code' +import { $isCodeHighlightNode, $isCodeNode } from '@lexical/code' import { $isInlineFileNode } from '../../../Plugins/InlineFilePlugin/InlineFileNode' import { $isRemoteImageNode } from '../../../Plugins/RemoteImagePlugin/RemoteImageNode' import { $isCollapsibleContainerNode } from '../../../Plugins/CollapsiblePlugin/CollapsibleContainerNode' @@ -33,9 +34,12 @@ import { MONOSPACE_FONT_FAMILY, getFontFamiliesFromLexicalNode, } from './FontConfig' +import { getPDFColorForCodeHighlight } from './CodeHighlightColors' import { PDF_BASE_FONT_SIZE, PDF_BLOCK_GAP, + PDF_CODE_BLOCK_FONT_SIZE, + PDF_CODE_TAB_SIZE, PDF_LINE_HEIGHT_MULTIPLIER, PDF_LIST_ITEM_GAP, PDF_QUOTE_INNER_GAP, @@ -154,6 +158,14 @@ const getFontSizeForHeading = (heading: HeadingNode) => { return (HEADING_FONT_SIZES[level - 1] ?? 1) * PDF_BASE_FONT_SIZE } +const getPDFTextContent = (node: TextNode, isCodeNodeText: boolean): string => { + if ($isTabNode(node) && isCodeNodeText) { + return ' '.repeat(PDF_CODE_TAB_SIZE) + } + + return node.getTextContent() +} + const getPDFTextFontFamily = ( node: TextNode, fontFamilies: FontFamily[], @@ -161,7 +173,7 @@ const getPDFTextFontFamily = ( isInlineCode: boolean, isCodeNodeText: boolean, ): FontFamily | FontFamily[] => { - if (isInlineCode && isCodeNodeText) { + if (isCodeNodeText || isInlineCode) { return MONOSPACE_FONT_FAMILY } @@ -181,7 +193,7 @@ const getPDFTextFontSize = ( isSubscript: boolean, headingFontSize: number | undefined, ): number | undefined => { - const baseFontSize = isInlineCode || isCodeNodeText ? 11 : undefined + const baseFontSize = isInlineCode || isCodeNodeText ? PDF_CODE_BLOCK_FONT_SIZE : undefined if (isSuperscript || isSubscript) { if (headingFontSize) { @@ -202,6 +214,14 @@ const getPDFTextLineHeight = (isHeading: boolean, headingFontSize: number | unde return `${size}px` } +const getPDFTextColor = (node: TextNode, parent: LexicalNode | null): string | undefined => { + if ($isCodeHighlightNode(node) && $isCodeNode(parent)) { + return getPDFColorForCodeHighlight(node.getHighlightType()) + } + + return undefined +} + const getPDFTextDecoration = (node: TextNode): 'underline' | 'line-through' | undefined => { if (node.hasFormat('underline')) { return 'underline' @@ -227,13 +247,14 @@ const getPDFTextDataNodeFromLexicalTextNode = ( return { type: 'Text', - children: node.getTextContent(), + children: getPDFTextContent(node, isCodeNodeText), style: { fontFamily: getPDFTextFontFamily(node, fontFamilies, useCustomFonts, isInlineCode, isCodeNodeText), fontWeight: node.hasFormat('bold') || isHeading ? 'bold' : 'normal', fontStyle: node.hasFormat('italic') ? 'italic' : 'normal', textDecoration: getPDFTextDecoration(node), backgroundColor: isInlineCode ? '#f1f1f1' : node.hasFormat('highlight') ? 'rgb(255,255,0)' : undefined, + color: getPDFTextColor(node, parent), fontSize: getPDFTextFontSize(isInlineCode, isCodeNodeText, isSuperscript, isSubscript, headingFontSize), verticalAlign: isSuperscript ? 'super' : isSubscript ? 'sub' : undefined, lineHeight: getPDFTextLineHeight(isHeading, headingFontSize), @@ -270,6 +291,11 @@ const getNodeDirection = (node: ElementNode) => { return direction ?? 'ltr' } +const getPDFVerticalSpacer = (height: number = PDF_BASE_FONT_SIZE * PDF_LINE_HEIGHT_MULTIPLIER): PDFDataNode => ({ + type: 'View', + style: { height }, +}) + const getPDFDataNodeFromLexicalNode = ( node: LexicalNode, fontFamilies: FontFamily[], @@ -311,16 +337,20 @@ const getPDFDataNodeFromLexicalNode = ( backgroundColor: 'rgba(0,0,0,0.05)', padding: 12, borderRadius: 6, - fontFamily: 'Courier', + fontFamily: MONOSPACE_FONT_FAMILY, + fontSize: PDF_CODE_BLOCK_FONT_SIZE, }, ], children: lines.map((line) => { return { type: 'View', style: [styles.row, styles.wrap], - children: line.map((child) => { - return getPDFDataNodeFromLexicalNode(child, fontFamilies, useCustomFonts) - }), + children: + line.length === 0 + ? [getPDFVerticalSpacer(PDF_CODE_BLOCK_FONT_SIZE * PDF_LINE_HEIGHT_MULTIPLIER)] + : line + .map((child) => getPDFDataNodeFromLexicalNode(child, fontFamilies, useCustomFonts)) + .filter((child): child is PDFDataNode => child != null), } }), } @@ -433,12 +463,7 @@ const getPDFDataNodeFromLexicalNode = ( } if ($isParagraphNode(node) && node.getTextContent().length === 0) { - return { - type: 'View', - style: { - height: PDF_BASE_FONT_SIZE * 1.5, - }, - } + return getPDFVerticalSpacer() } if ($isTableCellNode(node)) { diff --git a/packages/web/src/javascripts/Components/SuperEditor/Lexical/Utils/PDFExport/PDFLayoutConstants.ts b/packages/web/src/javascripts/Components/SuperEditor/Lexical/Utils/PDFExport/PDFLayoutConstants.ts index 2c87b81f0..ec53539a7 100644 --- a/packages/web/src/javascripts/Components/SuperEditor/Lexical/Utils/PDFExport/PDFLayoutConstants.ts +++ b/packages/web/src/javascripts/Components/SuperEditor/Lexical/Utils/PDFExport/PDFLayoutConstants.ts @@ -1,5 +1,7 @@ export const PDF_BASE_FONT_SIZE = 12 export const PDF_LINE_HEIGHT_MULTIPLIER = 1.5 +export const PDF_CODE_BLOCK_FONT_SIZE = 11 +export const PDF_CODE_TAB_SIZE = 2 export const PDF_BLOCK_GAP = 4 export const PDF_LIST_ITEM_GAP = 0 export const PDF_QUOTE_INNER_GAP = 4