Skip to content

Commit d54519c

Browse files
artem-harbourArtem Nistuley
andauthored
fix: sdt field style (#2958)
Co-authored-by: Artem Nistuley <artem@superdoc.dev>
1 parent d3f7309 commit d54519c

2 files changed

Lines changed: 194 additions & 0 deletions

File tree

packages/layout-engine/painters/dom/src/index.test.ts

Lines changed: 179 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2345,6 +2345,185 @@ describe('DomPainter', () => {
23452345
expect(wrapper.textContent).toContain('controlled text');
23462346
});
23472347

2348+
it('keeps inline SDT wrapper font-size in sync when run font-size changes', () => {
2349+
const block: FlowBlock = {
2350+
kind: 'paragraph',
2351+
id: 'inline-sdt-font-sync',
2352+
runs: [
2353+
{
2354+
text: 'Company Address',
2355+
fontFamily: 'Arial, sans-serif',
2356+
fontSize: 14.6667,
2357+
pmStart: 2044,
2358+
pmEnd: 2059,
2359+
sdt: {
2360+
type: 'structuredContent',
2361+
scope: 'inline',
2362+
id: '574416526',
2363+
tag: 'inline_text_sdt',
2364+
alias: 'Company Address',
2365+
lockMode: 'unlocked',
2366+
},
2367+
},
2368+
],
2369+
attrs: {},
2370+
};
2371+
2372+
const measure: Measure = {
2373+
kind: 'paragraph',
2374+
lines: [
2375+
{
2376+
fromRun: 0,
2377+
fromChar: 0,
2378+
toRun: 0,
2379+
toChar: 15,
2380+
width: 180,
2381+
ascent: 12,
2382+
descent: 4,
2383+
lineHeight: 20,
2384+
},
2385+
],
2386+
totalHeight: 20,
2387+
};
2388+
2389+
const layout: Layout = {
2390+
pageSize: { w: 612, h: 792 },
2391+
pages: [
2392+
{
2393+
number: 1,
2394+
fragments: [
2395+
{
2396+
kind: 'para',
2397+
blockId: 'inline-sdt-font-sync',
2398+
fromLine: 0,
2399+
toLine: 1,
2400+
x: 30,
2401+
y: 40,
2402+
width: 552,
2403+
pmStart: 2044,
2404+
pmEnd: 2059,
2405+
},
2406+
],
2407+
},
2408+
],
2409+
};
2410+
2411+
const painter = createTestPainter({ blocks: [block], measures: [measure] });
2412+
painter.paint(layout, mount);
2413+
2414+
const getInlineSdtWrapper = () =>
2415+
mount.querySelector('.superdoc-structured-content-inline[data-sdt-id="574416526"]') as HTMLElement | null;
2416+
2417+
const wrapperBefore = getInlineSdtWrapper();
2418+
expect(wrapperBefore).toBeTruthy();
2419+
expect(wrapperBefore?.style.fontSize).toBe('14.6667px');
2420+
2421+
const updatedBlock: FlowBlock = {
2422+
...block,
2423+
runs: [
2424+
{
2425+
...block.runs[0],
2426+
fontSize: 10,
2427+
},
2428+
],
2429+
};
2430+
2431+
painter.setData([updatedBlock], [measure]);
2432+
painter.paint(layout, mount);
2433+
2434+
const wrapperAfter = getInlineSdtWrapper();
2435+
expect(wrapperAfter).toBeTruthy();
2436+
expect(wrapperAfter?.style.fontSize).toBe('10px');
2437+
});
2438+
2439+
it('uses first run font-size for inline SDT wrapper when a field has mixed run sizes', () => {
2440+
const mixedSizeBlock: FlowBlock = {
2441+
kind: 'paragraph',
2442+
id: 'inline-sdt-mixed-font-size',
2443+
runs: [
2444+
{
2445+
text: 'Big',
2446+
fontFamily: 'Arial, sans-serif',
2447+
fontSize: 36,
2448+
pmStart: 100,
2449+
pmEnd: 103,
2450+
sdt: {
2451+
type: 'structuredContent',
2452+
scope: 'inline',
2453+
id: 'mixed-size-sdt',
2454+
tag: 'inline_text_sdt',
2455+
alias: 'Mixed Size Field',
2456+
lockMode: 'unlocked',
2457+
},
2458+
},
2459+
{
2460+
text: ' small',
2461+
fontFamily: 'Arial, sans-serif',
2462+
fontSize: 10,
2463+
pmStart: 103,
2464+
pmEnd: 109,
2465+
sdt: {
2466+
type: 'structuredContent',
2467+
scope: 'inline',
2468+
id: 'mixed-size-sdt',
2469+
tag: 'inline_text_sdt',
2470+
alias: 'Mixed Size Field',
2471+
lockMode: 'unlocked',
2472+
},
2473+
},
2474+
],
2475+
attrs: {},
2476+
};
2477+
2478+
const mixedSizeMeasure: Measure = {
2479+
kind: 'paragraph',
2480+
lines: [
2481+
{
2482+
fromRun: 0,
2483+
fromChar: 0,
2484+
toRun: 1,
2485+
toChar: 6,
2486+
width: 180,
2487+
ascent: 12,
2488+
descent: 4,
2489+
lineHeight: 20,
2490+
},
2491+
],
2492+
totalHeight: 20,
2493+
};
2494+
2495+
const mixedSizeLayout: Layout = {
2496+
pageSize: { w: 612, h: 792 },
2497+
pages: [
2498+
{
2499+
number: 1,
2500+
fragments: [
2501+
{
2502+
kind: 'para',
2503+
blockId: 'inline-sdt-mixed-font-size',
2504+
fromLine: 0,
2505+
toLine: 1,
2506+
x: 30,
2507+
y: 40,
2508+
width: 552,
2509+
pmStart: 100,
2510+
pmEnd: 109,
2511+
},
2512+
],
2513+
},
2514+
],
2515+
};
2516+
2517+
const painter = createTestPainter({ blocks: [mixedSizeBlock], measures: [mixedSizeMeasure] });
2518+
painter.paint(mixedSizeLayout, mount);
2519+
2520+
const wrapper = mount.querySelector(
2521+
'.superdoc-structured-content-inline[data-sdt-id="mixed-size-sdt"]',
2522+
) as HTMLElement | null;
2523+
expect(wrapper).toBeTruthy();
2524+
expect(wrapper?.style.fontSize).toBe('36px');
2525+
});
2526+
23482527
it('positions word-layout markers relative to the text start', () => {
23492528
const markerBlock: FlowBlock = {
23502529
kind: 'paragraph',

packages/layout-engine/painters/dom/src/renderer.ts

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6364,6 +6364,7 @@ export class DomPainter {
63646364
geoSdtWrapper.style.top = '0px';
63656365
geoSdtWrapper.style.height = `${line.lineHeight}px`;
63666366
}
6367+
this.syncInlineSdtWrapperTypography(geoSdtWrapper, runForSdt);
63676368
elem.style.left = `${elemLeftPx - geoSdtWrapperLeft}px`;
63686369
geoSdtMaxRight = Math.max(geoSdtMaxRight, elemLeftPx + elemWidthPx);
63696370
this.expandSdtWrapperPmRange(geoSdtWrapper, (runForSdt as TextRun).pmStart, (runForSdt as TextRun).pmEnd);
@@ -6651,8 +6652,11 @@ export class DomPainter {
66516652
if (resolved && this.doc) {
66526653
if (!currentInlineSdtWrapper) {
66536654
currentInlineSdtWrapper = this.createInlineSdtWrapper(resolved.sdt);
6655+
this.syncInlineSdtWrapperTypography(currentInlineSdtWrapper, run);
66546656
currentInlineSdtId = runSdtId;
66556657
}
6658+
// Typography is set when wrapper is created from the first run.
6659+
// Follow-up (SD-2744): define a deterministic mixed-typography rule.
66566660
this.expandSdtWrapperPmRange(currentInlineSdtWrapper, run.pmStart, run.pmEnd);
66576661
currentInlineSdtWrapper.appendChild(elem);
66586662
} else {
@@ -7053,6 +7057,17 @@ export class DomPainter {
70537057
return wrapper;
70547058
}
70557059

7060+
private syncInlineSdtWrapperTypography(wrapper: HTMLElement, runForSizing?: Run): void {
7061+
// The line container sets fontSize:0 (strut fix). Keep wrapper typography
7062+
// synced with the current run so border height tracks text-size edits.
7063+
const runFontSize =
7064+
runForSizing && 'fontSize' in runForSizing && typeof runForSizing.fontSize === 'number'
7065+
? `${runForSizing.fontSize}px`
7066+
: BROWSER_DEFAULT_FONT_SIZE;
7067+
wrapper.style.fontSize = runFontSize;
7068+
wrapper.style.lineHeight = 'normal';
7069+
}
7070+
70567071
/**
70577072
* Expand the PM position range tracked on an SDT wrapper to include a new run's range.
70587073
*/

0 commit comments

Comments
 (0)