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
63 changes: 63 additions & 0 deletions packages/layout-engine/layout-engine/src/layout-drawing.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -778,5 +778,68 @@ describe('layoutDrawingBlock', () => {
expect(fragment.width).toBe(600);
expect(fragment.height).toBeCloseTo(333 * expectedScale, 10); // Allow floating point precision
});

it('should center inline shapeGroup drawings using paragraph alignment metadata', () => {
const context = createMockContext(
{
drawingKind: 'shapeGroup',
attrs: {
pmStart: 10,
pmEnd: 11,
wrap: { type: 'Inline' },
inlineParagraphAlignment: 'center',
},
},
{ width: 200, height: 150 },
);
const state = context.ensurePage();

layoutDrawingBlock(context);

const fragment = state.page.fragments[0] as DrawingFragment;
expect(fragment.x).toBe(200);
});

it('should right-align inline shapeGroup drawings using paragraph alignment metadata', () => {
const context = createMockContext(
{
drawingKind: 'shapeGroup',
attrs: {
pmStart: 10,
pmEnd: 11,
wrap: { type: 'Inline' },
inlineParagraphAlignment: 'right',
},
},
{ width: 200, height: 150 },
);
const state = context.ensurePage();

layoutDrawingBlock(context);

const fragment = state.page.fragments[0] as DrawingFragment;
expect(fragment.x).toBe(400);
});

it('should not apply paragraph alignment metadata when shapeGroup is not inline', () => {
const context = createMockContext(
{
drawingKind: 'shapeGroup',
attrs: {
pmStart: 10,
pmEnd: 11,
wrap: { type: 'Square' },
inlineParagraphAlignment: 'center',
},
},
{ width: 200, height: 150 },
);
const state = context.ensurePage();

layoutDrawingBlock(context);

const fragment = state.page.fragments[0] as DrawingFragment;
expect(fragment.x).toBe(0);
});
});
});
14 changes: 13 additions & 1 deletion packages/layout-engine/layout-engine/src/layout-drawing.ts
Original file line number Diff line number Diff line change
Expand Up @@ -85,6 +85,12 @@ export function layoutDrawingBlock({
const indentRight = typeof attrs?.hrIndentRight === 'number' ? attrs.hrIndentRight : 0;
const maxWidthForBlock =
attrs?.isFullWidth === true && maxWidth > 0 ? Math.max(1, maxWidth - indentLeft - indentRight) : maxWidth;
const rawWrap = attrs?.wrap as { type?: unknown } | undefined;
const isInlineShapeGroup = block.drawingKind === 'shapeGroup' && rawWrap?.type === 'Inline';
const inlineParagraphAlignment =
attrs?.inlineParagraphAlignment === 'center' || attrs?.inlineParagraphAlignment === 'right'
? attrs.inlineParagraphAlignment
: undefined;

if (width > maxWidthForBlock && maxWidthForBlock > 0) {
const scale = maxWidthForBlock / width;
Expand All @@ -107,12 +113,18 @@ export function layoutDrawingBlock({
}

const pmRange = extractBlockPmRange(block);
let x = columnX(state.columnIndex) + marginLeft + indentLeft;
if (isInlineShapeGroup && inlineParagraphAlignment === 'center') {
x += Math.max(0, maxWidthForBlock - width) / 2;
} else if (isInlineShapeGroup && inlineParagraphAlignment === 'right') {
x += Math.max(0, maxWidthForBlock - width);
}

const fragment: DrawingFragment = {
kind: 'drawing',
blockId: block.id,
drawingKind: block.drawingKind,
x: columnX(state.columnIndex) + marginLeft + indentLeft,
x,
y: state.cursorY + marginTop,
width,
height,
Expand Down
83 changes: 83 additions & 0 deletions packages/layout-engine/pm-adapter/src/converters/paragraph.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2144,6 +2144,89 @@ describe('paragraph converters', () => {
expect(shapeGroupNodeToDrawingBlock).toHaveBeenCalledWith(shapeNode, nextBlockId, positions);
});

it('should attach inline paragraph alignment to inline shapeGroup drawings', () => {
const shapeNode: PMNode = { type: 'shapeGroup' };

vi.mocked(computeParagraphAttrs).mockReturnValue({
paragraphAttrs: {
alignment: 'center',
},
resolvedParagraphProperties: {},
} as never);

vi.mocked(shapeGroupNodeToDrawingBlock).mockReturnValue({
kind: 'drawing',
id: 'drawing-0',
drawingKind: 'shapeGroup',
wrap: { type: 'Inline' },
attrs: {
wrap: { type: 'Inline' },
},
shapes: [],
} as never);

const blocks = paragraphToFlowBlocks(
{
type: 'paragraph',
content: [shapeNode],
},
nextBlockId,
positions,
'Arial',
16,
undefined,
undefined,
undefined,
undefined,
);

const drawingBlock = blocks.find((block) => block.kind === 'drawing') as FlowBlock & {
attrs?: Record<string, unknown>;
};
expect(drawingBlock?.attrs?.inlineParagraphAlignment).toBe('center');
});

it('should not attach inline paragraph alignment to non-inline shapeGroup drawings', () => {
const shapeNode: PMNode = { type: 'shapeGroup' };

vi.mocked(computeParagraphAttrs).mockReturnValue({
paragraphAttrs: {
alignment: 'center',
},
resolvedParagraphProperties: {},
} as never);

vi.mocked(shapeGroupNodeToDrawingBlock).mockReturnValue({
kind: 'drawing',
id: 'drawing-0',
drawingKind: 'shapeGroup',
attrs: {
wrap: { type: 'Square' },
},
shapes: [],
} as never);

const blocks = paragraphToFlowBlocks(
{
type: 'paragraph',
content: [shapeNode],
},
nextBlockId,
positions,
'Arial',
16,
undefined,
undefined,
undefined,
undefined,
);

const drawingBlock = blocks.find((block) => block.kind === 'drawing') as FlowBlock & {
attrs?: Record<string, unknown>;
};
expect(drawingBlock?.attrs?.inlineParagraphAlignment).toBeUndefined();
});

it('should handle shapeContainer node', () => {
const shapeNode: PMNode = { type: 'shapeContainer' };

Expand Down
23 changes: 23 additions & 0 deletions packages/layout-engine/pm-adapter/src/converters/paragraph.ts
Original file line number Diff line number Diff line change
Expand Up @@ -665,6 +665,26 @@ export function paragraphToFlowBlocks({
blockWithAttrs.attrs.anchorParagraphId = anchorParagraphId;
return blockWithAttrs;
};
const attachInlineShapeGroupAlignment = <T extends FlowBlock>(block: T): T => {
if (block.kind !== 'drawing') {
return block;
}
const drawingBlock = block as T & {
drawingKind?: string;
attrs?: Record<string, unknown>;
};
const rawWrap = drawingBlock.attrs?.wrap as { type?: unknown } | undefined;
if (drawingBlock.drawingKind !== 'shapeGroup' || rawWrap?.type !== 'Inline') {
return block;
}
if (!drawingBlock.attrs) {
drawingBlock.attrs = {};
}
if (paragraphAttrs.alignment === 'center' || paragraphAttrs.alignment === 'right') {
drawingBlock.attrs.inlineParagraphAlignment = paragraphAttrs.alignment;
}
return block;
};

const flushParagraph = () => {
if (currentRuns.length === 0) {
Expand Down Expand Up @@ -757,11 +777,13 @@ export function paragraphToFlowBlocks({
const block = blockConverter(node, { ...blockOptions, blocks: newBlocks });
if (block) {
attachAnchorParagraphId(block, anchorParagraphId);
attachInlineShapeGroupAlignment(block);
blocks.push(block);
} else if (newBlocks.length > 0) {
// Some block converters may push multiple blocks to the provided array
newBlocks.forEach((b) => {
attachAnchorParagraphId(b, anchorParagraphId);
attachInlineShapeGroupAlignment(b);
blocks.push(b);
});
}
Expand All @@ -779,6 +801,7 @@ export function paragraphToFlowBlocks({
const converter = SHAPE_CONVERTERS_REGISTRY[node.type];
const drawingBlock = converter(node, stableNextBlockId, positions);
if (drawingBlock) {
attachInlineShapeGroupAlignment(drawingBlock);
blocks.push(attachAnchorParagraphId(drawingBlock, anchorParagraphId));
}
return;
Expand Down
Loading