Refine line-based story scrolling

This commit is contained in:
2026-05-16 21:40:36 +02:00
parent b9ae7f71c5
commit e368d252ad
10 changed files with 1238 additions and 755 deletions
+20 -25
View File
@@ -79,15 +79,14 @@ class LayoutRendererModule extends BaseModule {
layoutData.addTopSpace ? 'story-textblock-start' : '',
layoutData.dropCap ? 'story-dropcap-paragraph' : ''
].filter(Boolean).join(' ');
paragraph.style.position = 'relative';
paragraph.style.position = 'absolute';
paragraph.style.margin = '0';
paragraph.style.left = '0';
const globalLineStart = Math.max(0, Number(layoutData.lineStart || 0));
const windowOriginLine = Math.max(0, Number(layoutData.windowOriginLine || 0));
paragraph.style.top = `${(globalLineStart - windowOriginLine) * Number(lineHeightPx || 0)}px`;
if (fontSize) paragraph.style.fontSize = fontSize;
if (fontFamily) paragraph.style.fontFamily = fontFamily;
if (Array.isArray(measures) && measures.length > 0) {
paragraph.style.width = `${Math.max(...measures)}px`;
paragraph.style.maxWidth = '100%';
}
// Calculate paragraph height
const storyElement = document.getElementById('story');
if (!storyElement) {
@@ -95,30 +94,25 @@ class LayoutRendererModule extends BaseModule {
return null;
}
const pageWidth = Number(layoutData.pageWidth || storyElement.clientWidth);
paragraph.style.width = `${pageWidth}px`;
paragraph.style.maxWidth = '100%';
if (!Number.isFinite(Number(lineHeightPx)) || Number(lineHeightPx) <= 0) {
throw new Error('LayoutRenderer: Missing canonical lineHeightPx for story layout.');
}
const lineHeight = Number(lineHeightPx);
let marginLines = 0;
if (layoutData.role === 'chapter-heading') {
paragraph.style.marginTop = `${lineHeight * 2}px`;
paragraph.style.marginBottom = `${lineHeight}px`;
marginLines = 3;
} else if (layoutData.role === 'section-heading') {
paragraph.style.marginTop = `${lineHeight}px`;
paragraph.style.marginBottom = `${lineHeight}px`;
marginLines = 2;
} else if (layoutData.addTopSpace) {
paragraph.style.marginTop = `${lineHeight}px`;
marginLines = 1;
}
const contentTopLines = Math.max(0, Number(layoutData.contentTopLines || 0));
const maxLineWidth = Array.isArray(measures) && measures.length > 0
? Math.max(...measures)
: storyElement.clientWidth;
? Math.max(pageWidth, ...measures)
: pageWidth;
// Height should include all lines (breaks.length represents number of lines)
const numLines = Math.max(1, breaks.length - 1);
paragraph.style.height = `${lineHeight * numLines}px`;
paragraph.dataset.heightLines = String(numLines + marginLines);
const totalLines = Math.max(1, Number(layoutData.lineCount || (numLines + contentTopLines)));
paragraph.style.height = `${lineHeight * totalLines}px`;
paragraph.dataset.heightLines = String(totalLines);
paragraph.dataset.lineStart = String(globalLineStart);
paragraph.dataset.lineCount = String(totalLines);
console.log(`LayoutRenderer: Rendering paragraph ${id} - ${breaks.length} breaks (${numLines} lines), lineHeight: ${lineHeight}px, total height: ${lineHeight * numLines}px`);
@@ -139,6 +133,7 @@ class LayoutRendererModule extends BaseModule {
const dropCap = document.createElement('span');
dropCap.className = 'drop-cap story-drop-cap';
dropCap.textContent = layoutData.dropCapText;
dropCap.style.top = `${contentTopLines * lineHeight}px`;
paragraph.appendChild(dropCap);
}
@@ -195,7 +190,7 @@ class LayoutRendererModule extends BaseModule {
word.dataset.lineWidth = String(lineWidth);
// Calculate position with proper line and justification
const topPercent = (lineIndex * lineHeight * 100) / parseFloat(paragraph.style.height);
const topPercent = ((contentTopLines + lineIndex) * lineHeight * 100) / parseFloat(paragraph.style.height);
const leftPercent = ((lineOffset + currentLeft) * 100) / maxLineWidth;
word.style.top = `${topPercent}%`;
@@ -266,7 +261,7 @@ class LayoutRendererModule extends BaseModule {
word.dataset.line = String(lineIndex);
word.dataset.lineStart = String(lineOffset);
word.dataset.lineWidth = String(lineWidth);
const topPercent = (lineIndex * lineHeight * 100) / parseFloat(paragraph.style.height);
const topPercent = ((contentTopLines + lineIndex) * lineHeight * 100) / parseFloat(paragraph.style.height);
const leftPercent = ((lineOffset + currentLeft) * 100) / maxLineWidth;
word.style.top = `${topPercent}%`;
word.style.left = `${leftPercent}%`;