Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
25 changes: 19 additions & 6 deletions apps/live/src/lib/pdf/node-renderers.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -88,6 +88,11 @@ const getFlexAlignStyle = (textAlign: string | null | undefined): Style => {
return {};
};

const getRtlStyle = (dir: string | null | undefined): Style => {
if (dir !== "rtl") return {};
return { fontFamily: "Vazirmatn" };
};

export const nodeRenderers: NodeRendererRegistry = {
doc: (_node: TipTapNode, children: ReactElement[], ctx: PDFRenderContext): ReactElement => (
<View key={ctx.getKey()}>{children}</View>
Expand All @@ -98,16 +103,20 @@ export const nodeRenderers: NodeRendererRegistry = {

paragraph: (node: TipTapNode, children: ReactElement[], ctx: PDFRenderContext): ReactElement => {
const textAlign = node.attrs?.textAlign as string | null;
const dir = node.attrs?.dir as string | null | undefined;
// For RTL paragraphs with no explicit alignment, default to right-aligned
const effectiveTextAlign = textAlign ?? (dir === "rtl" ? "right" : null);
const background = node.attrs?.backgroundColor as string | undefined;
const alignStyle = getTextAlignStyle(textAlign);
const flexStyle = getFlexAlignStyle(textAlign);
const alignStyle = getTextAlignStyle(effectiveTextAlign);
const flexStyle = getFlexAlignStyle(effectiveTextAlign);
const rtlStyle = getRtlStyle(dir);
const resolvedBgColor =
background && background !== "default" ? resolveColorForPdf(background, "background") : null;
const bgStyle = resolvedBgColor ? { backgroundColor: resolvedBgColor } : {};

return (
<View key={ctx.getKey()} style={[pdfStyles.paragraphWrapper, flexStyle, bgStyle]}>
<Text style={[pdfStyles.paragraph, alignStyle, bgStyle]}>{children}</Text>
<Text style={[pdfStyles.paragraph, alignStyle, rtlStyle, bgStyle]}>{children}</Text>
</View>
);
},
Expand All @@ -117,12 +126,16 @@ export const nodeRenderers: NodeRendererRegistry = {
const styleKey = `heading${level}` as keyof typeof pdfStyles;
const style = pdfStyles[styleKey] || pdfStyles.heading1;
const textAlign = node.attrs?.textAlign as string | null;
const alignStyle = getTextAlignStyle(textAlign);
const flexStyle = getFlexAlignStyle(textAlign);
const dir = node.attrs?.dir as string | null | undefined;
// For RTL headings with no explicit alignment, default to right-aligned
const effectiveTextAlign = textAlign ?? (dir === "rtl" ? "right" : null);
const alignStyle = getTextAlignStyle(effectiveTextAlign);
const flexStyle = getFlexAlignStyle(effectiveTextAlign);
const rtlStyle = getRtlStyle(dir);

return (
<View key={ctx.getKey()} style={flexStyle}>
<Text style={[style, alignStyle]}>{children}</Text>
<Text style={[style, alignStyle, rtlStyle]}>{children}</Text>
</View>
);
},
Expand Down
21 changes: 21 additions & 0 deletions apps/live/src/lib/pdf/plane-pdf-exporter.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@

import { createRequire } from "module";
import path from "path";
import { fileURLToPath } from "url";
import { Document, Font, Page, pdf, Text } from "@react-pdf/renderer";
import { createKeyGenerator, renderNode } from "./node-renderers";
import { pdfStyles } from "./styles";
Expand Down Expand Up @@ -50,6 +51,26 @@ Font.register({
],
});

// Resolve Vazirmatn font files relative to the compiled bundle (dist/start.js),
// so the path is stable regardless of where the process is started from.
// Place the woff files at apps/live/fonts/vazirmatn/ before starting the server.
// Download from: https://github.com/rastikerdar/vazirmatn/releases
const vazirmatnFontDir = path.resolve(path.dirname(fileURLToPath(import.meta.url)), "../fonts/vazirmatn");

Font.register({
family: "Vazirmatn",
fonts: [
{
src: path.join(vazirmatnFontDir, "vazirmatn-regular.woff"),
fontWeight: "normal",
},
{
src: path.join(vazirmatnFontDir, "vazirmatn-bold.woff"),
fontWeight: "bold",
},
],
});

export const createPdfDocument = (doc: TipTapDocument, options: PDFExportOptions = {}) => {
const { title, author, subject, pageSize = "A4", pageOrientation = "portrait", metadata, noAssets } = options;

Expand Down
13 changes: 13 additions & 0 deletions apps/web/core/components/editor/pdf/document.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,11 @@ import interSemibold from "@/app/assets/fonts/inter/semibold.ttf?url";
import interThin from "@/app/assets/fonts/inter/thin.ttf?url";
import interUltraBold from "@/app/assets/fonts/inter/ultrabold.ttf?url";
import interUltraLight from "@/app/assets/fonts/inter/ultralight.ttf?url";
// Vazirmatn — Persian/Arabic font for RTL content.
// Place font files at apps/web/app/assets/fonts/vazirmatn/ before building.
// Download from: https://github.com/rastikerdar/vazirmatn/releases
import vazirmatnBold from "@/app/assets/fonts/vazirmatn/bold.ttf?url";
import vazirmatnRegular from "@/app/assets/fonts/vazirmatn/regular.ttf?url";
// constants
import { EDITOR_PDF_DOCUMENT_STYLESHEET } from "@/constants/editor";

Expand Down Expand Up @@ -44,6 +49,14 @@ Font.register({
],
});

Font.register({
family: "Vazirmatn",
fonts: [
{ src: vazirmatnRegular, fontWeight: "normal" },
{ src: vazirmatnBold, fontWeight: "bold" },
],
});

type Props = {
content: string;
pageFormat: PageProps["size"];
Expand Down
6 changes: 6 additions & 0 deletions apps/web/core/constants/editor.ts
Original file line number Diff line number Diff line change
Expand Up @@ -236,6 +236,12 @@ const EDITOR_PDF_FONT_FAMILY_STYLES: Styles = {
".courier-bold": {
fontFamily: "Courier-Bold",
},
// RTL content (Persian, Arabic, Hebrew, etc.) — use a font that carries
// the required Unicode shaping tables so letters connect correctly.
"[dir='rtl']": {
fontFamily: "Vazirmatn",
textAlign: "right",
},
};

const EDITOR_PDF_TYPOGRAPHY_STYLES: Styles = {
Expand Down