fix text align

This commit is contained in:
Hamid
2026-05-17 01:26:34 -07:00
parent 5bbd677f41
commit 642155661c
2 changed files with 83 additions and 30 deletions
+66 -1
View File
@@ -144,7 +144,7 @@ export const getEffectiveTextWidth = (
return Math.max(1, base + SINGLE_LINE_WIDTH_SLACK_PX); return Math.max(1, base + SINGLE_LINE_WIDTH_SLACK_PX);
}; };
/** Scaled width for viewer text (wrapped / multi-line layout only). */ /** Scaled width for viewer text boxes (includes slack to avoid tighter CSS wrapping than Konva). */
export const getScaledTextWidth = ( export const getScaledTextWidth = (
width: number | undefined, width: number | undefined,
scale: number, scale: number,
@@ -152,3 +152,68 @@ export const getScaledTextWidth = (
if (!width) return undefined; if (!width) return undefined;
return Math.ceil(width * scale) + Math.ceil(SINGLE_LINE_WIDTH_SLACK_PX * scale); return Math.ceil(width * scale) + Math.ceil(SINGLE_LINE_WIDTH_SLACK_PX * scale);
}; };
export type TextAlignValue = "left" | "center" | "right";
export type ViewerTextLayout = {
leftPx: number;
widthPx: number | undefined;
textAlign: TextAlignValue;
transform: string | undefined;
transformOrigin: "top right";
};
/**
* Viewer HTML layout matching Konva TextShape: (x, y) is the top-right anchor of the text box.
* Text alignment is applied inside a fixed-width box that extends left from the anchor.
*/
export function getViewerTextLayout(args: {
x: number;
width?: number;
text?: string;
textAlign?: TextAlignValue;
rotation?: number;
scale: number;
measureOpts: TextMeasureOptions;
wrapped: boolean;
}): ViewerTextLayout {
const {
x,
width: storedWidth,
text,
textAlign = "right",
rotation = 0,
scale,
measureOpts,
wrapped,
} = args;
const layoutWidth = getEffectiveTextWidth(
storedWidth,
text,
measureOpts,
!wrapped,
);
const widthPx = getScaledTextWidth(layoutWidth, scale);
const slackPx =
widthPx !== undefined ? Math.ceil(SINGLE_LINE_WIDTH_SLACK_PX * scale) : 0;
const rightPx = x * scale;
const boxWidthUnscaled = layoutWidth ?? storedWidth ?? 0;
let leftPx: number;
if (widthPx !== undefined && boxWidthUnscaled > 0) {
leftPx = Math.round(rightPx - boxWidthUnscaled * scale) - slackPx;
} else {
leftPx = Math.round(rightPx);
}
const rotationTransform = rotation ? `rotate(${rotation}deg)` : undefined;
return {
leftPx,
widthPx,
textAlign,
transform: rotationTransform,
transformOrigin: "top right",
};
}
+17 -29
View File
@@ -5,10 +5,8 @@ import { toCssLinearGradient } from '@/pages/editor/utils/gradient';
import { getFontFamily } from '@/pages/editor/utils/fontFamily'; import { getFontFamily } from '@/pages/editor/utils/fontFamily';
import { import {
getCssFontWeight, getCssFontWeight,
getEffectiveTextWidth, getViewerTextLayout,
getScaledTextWidth,
usesWrappedLayout, usesWrappedLayout,
SINGLE_LINE_WIDTH_SLACK_PX,
} from '@/pages/editor/utils/textStyle'; } from '@/pages/editor/utils/textStyle';
import '@/pages/viewer/styles/entranceAnimations.css'; import '@/pages/viewer/styles/entranceAnimations.css';
import { mergeEntranceAnimationStyle } from '@/pages/viewer/utils/entranceAnimationStyle'; import { mergeEntranceAnimationStyle } from '@/pages/viewer/utils/entranceAnimationStyle';
@@ -131,51 +129,41 @@ const BookPage = forwardRef<HTMLDivElement, BookPageProps>(
letterSpacing: obj.letterSpacing ?? 0, letterSpacing: obj.letterSpacing ?? 0,
lineHeight, lineHeight,
}; };
const layoutWidth = wrapped const textLayout = getViewerTextLayout({
? getEffectiveTextWidth(obj.width, obj.text, measureOpts, false) x: obj.x || 0,
: undefined; width: obj.width,
const textWidth = wrapped ? getScaledTextWidth(layoutWidth, scale) : undefined; text: obj.text,
// getScaledTextWidth adds SINGLE_LINE_WIDTH_SLACK_PX to prevent unwanted CSS wrapping. textAlign: obj.textAlign,
// Compensate by shifting textLeft left by the same amount so the right edge stays at obj.x * scale. rotation,
const textSlackPx = textWidth !== undefined ? Math.ceil(SINGLE_LINE_WIDTH_SLACK_PX * scale) : 0; scale,
const textLeft = wrapped measureOpts,
? Math.round(((obj.x || 0) - (layoutWidth || obj.width || 0)) * scale) - textSlackPx wrapped,
: Math.round((obj.x || 0) * scale); });
const textTransform = wrapped
? rotation
? `rotate(${rotation}deg)`
: undefined
: rotation
? `rotate(${rotation}deg) translateX(-100%)`
: 'translateX(-100%)';
return ( return (
<div <div
key={obj.id || index} key={obj.id || index}
style={withEntrance( style={withEntrance(
{ {
...textBaseStyle, ...textBaseStyle,
left: `${textLeft}px`, left: `${textLayout.leftPx}px`,
width: wrapped ? (textWidth ? `${textWidth}px` : undefined) : 'max-content', width: textLayout.widthPx !== undefined ? `${textLayout.widthPx}px` : 'max-content',
maxWidth: wrapped ? undefined : 'none',
fontSize: `${fontSize * scale}px`, fontSize: `${fontSize * scale}px`,
fontFamily: getFontFamily(obj.fontFamily), fontFamily: getFontFamily(obj.fontFamily),
fontWeight: getCssFontWeight(obj.fontWeight), fontWeight: getCssFontWeight(obj.fontWeight),
lineHeight, lineHeight,
textAlign: obj.textAlign ?? 'right', textAlign: textLayout.textAlign,
direction: 'rtl', direction: 'rtl',
color: getColorWithOpacity(obj.fill, obj.opacity), color: getColorWithOpacity(obj.fill, obj.opacity),
whiteSpace: wrapped ? 'pre-wrap' : 'nowrap', whiteSpace: wrapped ? 'pre-wrap' : 'nowrap',
overflowWrap: wrapped ? 'break-word' : 'normal', overflowWrap: wrapped ? 'break-word' : 'normal',
letterSpacing: obj.letterSpacing ? `${obj.letterSpacing * scale}px` : undefined, letterSpacing: obj.letterSpacing ? `${obj.letterSpacing * scale}px` : undefined,
boxSizing: 'content-box', boxSizing: 'content-box',
transform: textTransform, transform: textLayout.transform,
transformOrigin: wrapped ? 'top left' : 'top right', transformOrigin: textLayout.transformOrigin,
}, },
obj, obj,
index, index,
wrapped { rotationDeg: rotation, transformOrigin: textLayout.transformOrigin },
? { rotationDeg: rotation, transformOrigin: 'top left' }
: { rotationDeg: rotation, transformOrigin: 'top right' },
)} )}
> >
{obj.text || ''} {obj.text || ''}