Skip to content
Merged
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
Original file line number Diff line number Diff line change
Expand Up @@ -114,9 +114,9 @@ describe('normalizeAlignment', () => {
expect(normalizeAlignment('end', true)).toBe('left');
});

it('does not flip explicit left/right/center/justify in RTL', () => {
expect(normalizeAlignment('left', true)).toBe('left');
expect(normalizeAlignment('right', true)).toBe('right');
it('maps explicit left/right to logical start/end in RTL', () => {
expect(normalizeAlignment('left', true)).toBe('right');
expect(normalizeAlignment('right', true)).toBe('left');
expect(normalizeAlignment('center', true)).toBe('center');
expect(normalizeAlignment('justify', true)).toBe('justify');
});
Expand All @@ -127,6 +127,22 @@ describe('normalizeAlignment', () => {
expect(normalizeAlignment('highKashida')).toBe('justify');
});

// SD-3093: both/distribute/numTab/thaiDistribute collapse to justify regardless
// of direction. They must not flip under RTL like `left`/`right` do.
it('maps both/distribute/numTab/thaiDistribute to justify in LTR', () => {
expect(normalizeAlignment('both', false)).toBe('justify');
expect(normalizeAlignment('distribute', false)).toBe('justify');
expect(normalizeAlignment('numTab', false)).toBe('justify');
expect(normalizeAlignment('thaiDistribute', false)).toBe('justify');
});

it('maps both/distribute/numTab/thaiDistribute to justify in RTL (no flip)', () => {
expect(normalizeAlignment('both', true)).toBe('justify');
expect(normalizeAlignment('distribute', true)).toBe('justify');
expect(normalizeAlignment('numTab', true)).toBe('justify');
expect(normalizeAlignment('thaiDistribute', true)).toBe('justify');
});

it('returns undefined for invalid values', () => {
expect(normalizeAlignment('unknown')).toBeUndefined();
expect(normalizeAlignment(123)).toBeUndefined();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -43,10 +43,12 @@ const AUTO_SPACING_LINE_DEFAULT = 240; // Default OOXML auto line spacing in twi
export const normalizeAlignment = (value: unknown, isRtl = false): ParagraphAttrs['alignment'] => {
switch (value) {
case 'center':
case 'right':
case 'justify':
case 'left':
return value;
case 'left':
return isRtl ? 'right' : 'left';
case 'right':
return isRtl ? 'left' : 'right';
Comment thread
artem-harbour marked this conversation as resolved.
case 'both':
case 'distribute':
case 'numTab':
Expand Down
56 changes: 55 additions & 1 deletion packages/layout-engine/pm-adapter/src/index.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4655,7 +4655,7 @@ describe('toFlowBlocks', () => {
});
});

it('preserves explicit left alignment on RTL paragraphs', () => {
it('maps explicit left alignment to right on RTL paragraphs', () => {
const pmDoc = {
type: 'doc',
content: [
Expand All @@ -4680,6 +4680,37 @@ describe('toFlowBlocks', () => {

const { blocks } = toFlowBlocks(pmDoc);

expect(blocks).toHaveLength(1);
expect(blocks[0].attrs?.direction).toBe('rtl');
expect(blocks[0].attrs).toMatchObject({
alignment: 'right',
});
});

it('maps explicit right alignment to left on RTL paragraphs', () => {
const pmDoc = {
type: 'doc',
content: [
{
type: 'paragraph',
attrs: {
paragraphProperties: {
rightToLeft: true,
justification: 'right',
},
},
content: [
{
type: 'text',
text: 'مرحبا بالعالم',
},
],
},
],
};

const { blocks } = toFlowBlocks(pmDoc);

expect(blocks).toHaveLength(1);
expect(blocks[0].attrs?.direction).toBe('rtl');
expect(blocks[0].attrs).toMatchObject({
Expand Down Expand Up @@ -4726,6 +4757,29 @@ describe('toFlowBlocks', () => {
expect(blocksStart[0].attrs?.alignment).toBe('right');
expect(blocksEnd[0].attrs?.alignment).toBe('left');
});

// SD-3093: justify-family values must collapse to 'justify' without flipping
// in RTL. Regression guard against accidentally extending the mirror logic.
it('maps both/distribute/numTab/thaiDistribute to justify on RTL paragraphs', () => {
const makeDoc = (jc: string) => ({
type: 'doc',
content: [
{
type: 'paragraph',
attrs: {
paragraphProperties: { rightToLeft: true, justification: jc },
},
content: [{ type: 'text', text: 'مرحبا' }],
},
],
});

for (const jc of ['both', 'distribute', 'numTab', 'thaiDistribute']) {
const { blocks } = toFlowBlocks(makeDoc(jc));
expect(blocks[0].attrs?.direction).toBe('rtl');
expect(blocks[0].attrs).toMatchObject({ alignment: 'justify' });
}
});
});

describe('documentSection SDT metadata propagation', () => {
Expand Down
Binary file not shown.
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
import path from 'node:path';
import { fileURLToPath } from 'node:url';
import { test, expect } from '../../fixtures/superdoc.js';

const __dirname = path.dirname(fileURLToPath(import.meta.url));
const DOC_PATH = path.resolve(__dirname, 'fixtures/rtl-paragraph-alignment.docx');

// SD-3093: When a Word doc has `w:bidi` + explicit `w:jc="left"`/"right"/"center",
// ECMA-376 §17.3.1.13 says left = leading edge, right = trailing edge. In an RTL
// paragraph that resolves to visual right / visual left / center respectively.
// This spec loads a Word-authored fixture exercising all three to guard the
// import + render path that PR #3235 fixes.
test('RTL paragraph w:jc=left renders text-align: right', async ({ superdoc }) => {
await superdoc.loadDocument(DOC_PATH);
await superdoc.waitForStable();

const lines = superdoc.page.locator('.superdoc-page .superdoc-fragment .superdoc-line');
const jcLeftLine = lines.filter({ hasText: 'jc=left' }).first();
await expect(jcLeftLine).toBeVisible();
const textAlign = await jcLeftLine.evaluate((el) => window.getComputedStyle(el).textAlign);
expect(textAlign).toBe('right');
});

test('RTL paragraph w:jc=right renders text-align: left', async ({ superdoc }) => {
await superdoc.loadDocument(DOC_PATH);
await superdoc.waitForStable();

const lines = superdoc.page.locator('.superdoc-page .superdoc-fragment .superdoc-line');
const jcRightLine = lines.filter({ hasText: 'jc=right' }).first();
await expect(jcRightLine).toBeVisible();
const textAlign = await jcRightLine.evaluate((el) => window.getComputedStyle(el).textAlign);
expect(textAlign).toBe('left');
});

test('RTL paragraph w:jc=center renders text-align: center', async ({ superdoc }) => {
await superdoc.loadDocument(DOC_PATH);
await superdoc.waitForStable();

const lines = superdoc.page.locator('.superdoc-page .superdoc-fragment .superdoc-line');
const jcCenterLine = lines.filter({ hasText: 'jc=center' }).first();
await expect(jcCenterLine).toBeVisible();
const textAlign = await jcCenterLine.evaluate((el) => window.getComputedStyle(el).textAlign);
expect(textAlign).toBe('center');
});
Loading