Skip to content

Commit 1bc65ac

Browse files
committed
fix: cursor placement polish in footnote
1 parent 6f0ba58 commit 1bc65ac

3 files changed

Lines changed: 40 additions & 23 deletions

File tree

packages/layout-engine/layout-bridge/src/index.ts

Lines changed: 4 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -210,31 +210,16 @@ const logClickStage = (_level: 'log' | 'warn' | 'error', _stage: string, _payloa
210210
// No-op in production. Enable for debugging click-to-position mapping.
211211
};
212212

213-
const readSelectionDebugEnabled = (): boolean => {
214-
if (typeof globalThis === 'undefined') return false;
215-
return (globalThis as { __sdSelectionDebug?: boolean }).__sdSelectionDebug === true;
216-
};
217-
218213
const SELECTION_DEBUG_ENABLED = false;
219214
const logSelectionDebug = (payload: Record<string, unknown>): void => {
220-
const enabled = SELECTION_DEBUG_ENABLED || readSelectionDebugEnabled();
221-
if (!enabled) return;
215+
if (!SELECTION_DEBUG_ENABLED) return;
222216
try {
223217
console.log('[SELECTION-DEBUG]', JSON.stringify(payload));
224218
} catch {
225219
console.log('[SELECTION-DEBUG]', payload);
226220
}
227221
};
228222

229-
const pushSelectionDebugSnapshot = (payload: Record<string, unknown>): void => {
230-
if (typeof globalThis === 'undefined') return;
231-
const target = globalThis as { __sdSelectionDebugLog?: Record<string, unknown>[] };
232-
if (!Array.isArray(target.__sdSelectionDebugLog)) {
233-
target.__sdSelectionDebugLog = [];
234-
}
235-
target.__sdSelectionDebugLog.push(payload);
236-
};
237-
238223
/**
239224
* Debug flag for DOM and geometry position mapping.
240225
* Set to true to enable detailed logging of click-to-position operations.
@@ -651,8 +636,7 @@ export function selectionToRects(
651636
pageIndex,
652637
});
653638

654-
const selectionDebugEnabled = SELECTION_DEBUG_ENABLED || readSelectionDebugEnabled();
655-
if (selectionDebugEnabled) {
639+
if (SELECTION_DEBUG_ENABLED) {
656640
const runs = block.runs.slice(line.fromRun, line.toRun + 1).map((run: Run, idx: number) => {
657641
const isAtomic =
658642
'src' in run ||
@@ -673,7 +657,7 @@ export function selectionToRects(
673657
};
674658
});
675659

676-
const debugEntry = {
660+
debugEntries.push({
677661
pageIndex,
678662
blockId: block.id,
679663
lineIndex: index,
@@ -715,9 +699,7 @@ export function selectionToRects(
715699
lineSpaceCount: (line as { spaceCount?: unknown }).spaceCount,
716700
lineNaturalWidth: (line as { naturalWidth?: unknown }).naturalWidth,
717701
lineMaxWidth: (line as { maxWidth?: unknown }).maxWidth,
718-
};
719-
debugEntries.push(debugEntry);
720-
pushSelectionDebugSnapshot(debugEntry);
702+
});
721703
}
722704
});
723705
return;

packages/layout-engine/layout-bridge/src/text-measurement.ts

Lines changed: 17 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,21 @@ let measurementCtx: CanvasRenderingContext2D | null = null;
2020

2121
const TAB_CHAR_LENGTH = 1;
2222

23+
const getRunCharacterLength = (run: Run | undefined): number => {
24+
if (!run) return 0;
25+
if (isTabRun(run)) return TAB_CHAR_LENGTH;
26+
if (
27+
'src' in run ||
28+
run.kind === 'lineBreak' ||
29+
run.kind === 'break' ||
30+
run.kind === 'fieldAnnotation' ||
31+
run.kind === 'math'
32+
) {
33+
return 0;
34+
}
35+
return run.text?.length ?? 0;
36+
};
37+
2338
/**
2439
* Characters considered as spaces for justify alignment calculations.
2540
* Only includes regular space (U+0020) and non-breaking space (U+00A0).
@@ -224,7 +239,8 @@ const getJustifyAdjustment = (
224239
// This ensures measurement matches rendering even when callers don't pass these flags.
225240
const lastRunIndex = block.runs.length - 1;
226241
const lastRun = block.runs[lastRunIndex];
227-
const derivedIsLastLine = line.toRun >= lastRunIndex;
242+
const lastRunLength = getRunCharacterLength(lastRun);
243+
const derivedIsLastLine = line.toRun > lastRunIndex || (line.toRun === lastRunIndex && line.toChar >= lastRunLength);
228244
const derivedEndsWithLineBreak = lastRun ? lastRun.kind === 'lineBreak' : false;
229245
// Determine if justify should be applied using shared logic
230246
const shouldJustify = shouldApplyJustify({

packages/layout-engine/layout-bridge/test/text-measurement.test.ts

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -571,6 +571,25 @@ describe('text measurement utility', () => {
571571
expect(lastX).toBe(lastXNormal);
572572
});
573573

574+
it('applies justify spacing to wrapped non-last lines within a single text run', () => {
575+
const block = createBlock([{ text: 'A B C D E F', fontFamily: 'Arial', fontSize: 16 }]);
576+
(block as any).attrs = { alignment: 'justify' };
577+
578+
const line = baseLine({
579+
fromRun: 0,
580+
toRun: 0,
581+
fromChar: 0,
582+
toChar: 9, // Wrapped line consumes only part of the single text run
583+
width: 90,
584+
maxWidth: 120,
585+
});
586+
587+
const xWithNaturalWidth = measureCharacterX(block, line, 7, 90);
588+
const xWithSlack = measureCharacterX(block, line, 7, 120);
589+
590+
expect(xWithSlack).toBeGreaterThan(xWithNaturalWidth);
591+
});
592+
574593
it('skips justify spacing for manual tabs without explicit segments', () => {
575594
const trailingText = 'Item body';
576595
const tabWidth = 48;

0 commit comments

Comments
 (0)