diff --git a/src/assets/fonts/irancell/style.css b/src/assets/fonts/irancell/style.css index 7f72117..d9980ca 100644 --- a/src/assets/fonts/irancell/style.css +++ b/src/assets/fonts/irancell/style.css @@ -1,6 +1,6 @@ @font-face { font-family: "irancell"; - font-style: thin; + font-style: normal; font-weight: 200; src: url("./irancell-extralight.ttf"); } @@ -12,7 +12,13 @@ } @font-face { font-family: "irancell"; - font-style: thin; + font-style: normal; font-weight: 600; src: url("./irancell-bold.ttf"); } +@font-face { + font-family: "irancell"; + font-style: normal; + font-weight: 700; + src: url("./irancell-bold.ttf"); +} diff --git a/src/pages/editor/components/tools/TextShape.tsx b/src/pages/editor/components/tools/TextShape.tsx index 6ca3b76..22f0f7b 100644 --- a/src/pages/editor/components/tools/TextShape.tsx +++ b/src/pages/editor/components/tools/TextShape.tsx @@ -2,14 +2,7 @@ import { useEffect, useLayoutEffect, useRef, useMemo, useState } from "react"; import { Text, Group } from "react-konva"; import Konva from "konva"; import type { TextShapeProps } from "./types"; - -const getFontFamily = (fontFamily?: string): string => { - const fontMap: Record = { - "1": "irancell", - "2": "irancell", - }; - return fontMap[fontFamily || "1"] || "irancell"; -}; +import { getFontFamily } from "@/pages/editor/utils/fontFamily"; const getFontWeight = (fontWeight?: string): string => { if (!fontWeight) return "normal"; diff --git a/src/pages/editor/utils/fontFamily.ts b/src/pages/editor/utils/fontFamily.ts new file mode 100644 index 0000000..1e915f2 --- /dev/null +++ b/src/pages/editor/utils/fontFamily.ts @@ -0,0 +1,9 @@ +const FONT_FAMILY_MAP: Record = { + "1": "irancell", + "2": "irancell", +}; + +/** Maps editor font option ids to CSS font-family names. */ +export const getFontFamily = (fontFamily?: string): string => { + return FONT_FAMILY_MAP[fontFamily || "1"] || fontFamily || "irancell"; +}; diff --git a/src/pages/editor/utils/textStyle.ts b/src/pages/editor/utils/textStyle.ts new file mode 100644 index 0000000..514d569 --- /dev/null +++ b/src/pages/editor/utils/textStyle.ts @@ -0,0 +1,27 @@ +/** CSS font-weight values that match irancell @font-face declarations. */ +export const getCssFontWeight = (fontWeight?: string): number => { + if (!fontWeight || fontWeight === "normal") return 300; + if (fontWeight === "bold" || fontWeight === "bolder") return 600; + + const n = parseInt(fontWeight, 10); + if (!Number.isNaN(n)) { + if (n >= 600) return 600; + if (n <= 200) return 200; + return 300; + } + + return 300; +}; + +export const isSingleLineText = (text?: string): boolean => !text?.includes("\n"); + +/** Scaled width for viewer text; adds slack so CSS does not wrap tighter than Konva. */ +export const getScaledTextWidth = ( + width: number | undefined, + scale: number, + singleLine: boolean, +): number | undefined => { + if (!width) return undefined; + const scaled = Math.ceil(width * scale); + return singleLine ? scaled + 2 : scaled; +}; diff --git a/src/pages/viewer/components/BookPage.tsx b/src/pages/viewer/components/BookPage.tsx index 38fb2a3..a010334 100644 --- a/src/pages/viewer/components/BookPage.tsx +++ b/src/pages/viewer/components/BookPage.tsx @@ -2,6 +2,8 @@ import { forwardRef, useCallback } from 'react'; import { type PageData } from '../types'; import type { EditorObject } from '@/pages/editor/store/editorStore'; import { toCssLinearGradient } from '@/pages/editor/utils/gradient'; +import { getFontFamily } from '@/pages/editor/utils/fontFamily'; +import { getCssFontWeight, getScaledTextWidth, isSingleLineText } from '@/pages/editor/utils/textStyle'; import '@/pages/viewer/styles/entranceAnimations.css'; import { mergeEntranceAnimationStyle } from '@/pages/viewer/utils/entranceAnimationStyle'; import { useBookPageEntranceSequence } from '@/pages/viewer/hooks/useBookPageEntranceSequence'; @@ -122,8 +124,9 @@ const BookPage = forwardRef( // برای text، opacity در رنگ اعمال می‌شود، پس باید opacity را از baseStyle حذف کنیم // eslint-disable-next-line @typescript-eslint/no-unused-vars const { opacity, ...textBaseStyle } = baseStyle; - const textWidth = obj.width ? obj.width * scale : undefined; - const textLeft = ((obj.x || 0) - (obj.width || 0)) * scale; + const singleLine = isSingleLineText(obj.text); + const textWidth = getScaledTextWidth(obj.width, scale, singleLine); + const textLeft = Math.round(((obj.x || 0) - (obj.width || 0)) * scale); return (
( left: `${textLeft}px`, width: textWidth ? `${textWidth}px` : undefined, fontSize: `${(obj.fontSize || 16) * scale}px`, - fontFamily: obj.fontFamily || 'irancell, sans-serif', - fontWeight: obj.fontWeight || 'normal', + fontFamily: getFontFamily(obj.fontFamily), + fontWeight: getCssFontWeight(obj.fontWeight), lineHeight: obj.lineHeight ?? 1.2, textAlign: obj.textAlign ?? 'right', direction: 'rtl', color: getColorWithOpacity(obj.fill, obj.opacity), - whiteSpace: 'pre-wrap', - wordBreak: 'break-word', + whiteSpace: singleLine ? 'nowrap' : 'pre-wrap', letterSpacing: obj.letterSpacing ? `${obj.letterSpacing * scale}px` : undefined, + boxSizing: 'content-box', }, obj, index, @@ -210,7 +213,8 @@ const BookPage = forwardRef( height: obj.height ? `${obj.height * scale}px` : 'auto', color: obj.fill || '#3b82f6', fontSize: `${(obj.fontSize || 16) * scale}px`, - fontFamily: obj.fontFamily || 'irancell, sans-serif', + fontFamily: getFontFamily(obj.fontFamily), + fontWeight: getCssFontWeight(obj.fontWeight), textDecoration: 'underline', display: 'flex', alignItems: 'center',