diff --git a/apps/live/src/lib/pdf/node-renderers.tsx b/apps/live/src/lib/pdf/node-renderers.tsx
index 003d21f552a..7abd063ddd1 100644
--- a/apps/live/src/lib/pdf/node-renderers.tsx
+++ b/apps/live/src/lib/pdf/node-renderers.tsx
@@ -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 => (
{children}
@@ -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 (
- {children}
+ {children}
);
},
@@ -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 (
- {children}
+ {children}
);
},
diff --git a/apps/live/src/lib/pdf/plane-pdf-exporter.tsx b/apps/live/src/lib/pdf/plane-pdf-exporter.tsx
index f6c6b599c90..743eb62ed2b 100644
--- a/apps/live/src/lib/pdf/plane-pdf-exporter.tsx
+++ b/apps/live/src/lib/pdf/plane-pdf-exporter.tsx
@@ -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";
@@ -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;
diff --git a/apps/web/core/components/editor/pdf/document.tsx b/apps/web/core/components/editor/pdf/document.tsx
index 1c439bfae14..e516ae1fbc6 100644
--- a/apps/web/core/components/editor/pdf/document.tsx
+++ b/apps/web/core/components/editor/pdf/document.tsx
@@ -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";
@@ -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"];
diff --git a/apps/web/core/constants/editor.ts b/apps/web/core/constants/editor.ts
index 5cd8b929cda..de446b52ea4 100644
--- a/apps/web/core/constants/editor.ts
+++ b/apps/web/core/constants/editor.ts
@@ -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 = {