Fix portrait image flow and drop-cap spacing

This commit is contained in:
2026-05-18 03:08:23 +02:00
parent d7bb175167
commit 4f6300042c
3 changed files with 112 additions and 16 deletions
+89 -6
View File
@@ -123,12 +123,6 @@ class LayoutRendererModule extends BaseModule {
}
});
// Position words according to layout with proper justification
let wordCount = 0;
let lastChild = null;
let syllable = "";
const stack = [paragraph];
if (layoutData.dropCap && layoutData.dropCapText) {
const dropCap = document.createElement('span');
dropCap.className = 'drop-cap story-drop-cap';
@@ -137,6 +131,26 @@ class LayoutRendererModule extends BaseModule {
paragraph.appendChild(dropCap);
}
if (Array.isArray(layoutData.lines) && layoutData.lines.length > 0) {
layoutData.lines.forEach((line, lineIndex) => {
this.renderLine({
paragraph,
line,
lineIndex,
contentTopLines,
lineHeight,
maxLineWidth
});
});
return paragraph;
}
// Position words according to layout with proper justification
let wordCount = 0;
let lastChild = null;
let syllable = "";
const stack = [paragraph];
for (let i = 1; i < breaks.length; i++) {
const lineIndex = i - 1;
const lineWidth = measures[Math.min(lineIndex, measures.length - 1)];
@@ -277,6 +291,75 @@ class LayoutRendererModule extends BaseModule {
return paragraph;
}
renderLine({ paragraph, line, lineIndex, contentTopLines, lineHeight, maxLineWidth }) {
const lineWidth = Number(line.measure || maxLineWidth);
const lineOffset = Number(line.offset || 0);
const ratio = line.isFinal ? 0 : Number(line.ratio || 0);
const stack = [paragraph];
let currentLeft = 0;
let lastChild = null;
let syllable = '';
for (let j = 0; j < line.nodes.length; j += 1) {
const node = line.nodes[j];
if (!node) continue;
if (node.type === 'box' && node.value !== '') {
const followsGlue = j > 0 && line.nodes[j - 1].type === 'glue';
const isTrailingPunctuation = /^[,.;:!?…)]$/.test(node.value) && !followsGlue;
if (lastChild && isTrailingPunctuation) {
syllable += node.value;
lastChild.innerHTML = syllable;
currentLeft += node.width || 0;
continue;
}
const word = document.createElement('span');
word.className = ['word', line.styleClass || ''].filter(Boolean).join(' ');
word.style.position = 'absolute';
word.style.display = 'inline-block';
word.style.whiteSpace = 'nowrap';
word.dataset.line = String(lineIndex);
word.dataset.lineStart = String(lineOffset);
word.dataset.lineWidth = String(lineWidth);
word.style.top = `${((contentTopLines + lineIndex) * lineHeight * 100) / parseFloat(paragraph.style.height)}%`;
word.style.left = `${((lineOffset + currentLeft) * 100) / maxLineWidth}%`;
word.style.opacity = '0';
word.style.visibility = 'hidden';
word.style.clipPath = 'inset(0 100% 0 0)';
syllable = node.value;
word.innerHTML = syllable;
stack[stack.length - 1].appendChild(word);
lastChild = word;
currentLeft += node.width || 0;
} else if (node.type === 'tag') {
if (String(node.value || '').startsWith('</')) {
if (stack.length > 1) stack.pop();
} else {
const template = document.createElement('div');
template.innerHTML = node.value;
const tag = template.firstChild;
if (tag) {
tag.style.display = 'contents';
stack[stack.length - 1].appendChild(tag);
stack.push(tag);
}
}
} else if (node.type === 'glue' && node.width !== 0) {
let adjustedWidth = node.width || 0;
if (ratio > 0) {
adjustedWidth += (node.stretch || 0) * ratio;
} else if (ratio < 0) {
adjustedWidth += (node.shrink || 0) * ratio;
}
currentLeft += adjustedWidth;
} else if (node.type === 'penalty' && node.penalty === 100 && line.hyphenated && j === line.nodes.length - 1 && lastChild) {
lastChild.innerHTML = `${lastChild.innerHTML}-`;
}
}
}
measureNaturalLineWidth(nodes, startPosition, endPosition) {
let width = 0;
for (let j = startPosition; j <= endPosition; j++) {