Add texture drop cap pagination
This commit is contained in:
@@ -21,6 +21,8 @@ class BookPaginationModule extends BaseModule {
|
|||||||
'refreshFromHistory',
|
'refreshFromHistory',
|
||||||
'buildSpreads',
|
'buildSpreads',
|
||||||
'layoutTextBlock',
|
'layoutTextBlock',
|
||||||
|
'getDropCapText',
|
||||||
|
'extractDropCapText',
|
||||||
'extractLines',
|
'extractLines',
|
||||||
'getLineGeometry',
|
'getLineGeometry',
|
||||||
'getSpread',
|
'getSpread',
|
||||||
@@ -80,7 +82,7 @@ class BookPaginationModule extends BaseModule {
|
|||||||
let blockWordCursor = 0;
|
let blockWordCursor = 0;
|
||||||
cursorLine += layout.topSpaceLines;
|
cursorLine += layout.topSpaceLines;
|
||||||
|
|
||||||
layout.lines.forEach((line) => {
|
layout.lines.forEach((line, layoutLineIndex) => {
|
||||||
const geometry = this.getLineGeometry(cursorLine);
|
const geometry = this.getLineGeometry(cursorLine);
|
||||||
const lineWordCount = line.nodes.filter(node => node?.type === 'box' && node.value).length;
|
const lineWordCount = line.nodes.filter(node => node?.type === 'box' && node.value).length;
|
||||||
if (!spreads[geometry.spreadIndex]) {
|
if (!spreads[geometry.spreadIndex]) {
|
||||||
@@ -97,7 +99,8 @@ class BookPaginationModule extends BaseModule {
|
|||||||
fontPx: layout.fontPx,
|
fontPx: layout.fontPx,
|
||||||
lineHeightPx: layout.lineHeightPx,
|
lineHeightPx: layout.lineHeightPx,
|
||||||
fontStyle: layout.fontStyle,
|
fontStyle: layout.fontStyle,
|
||||||
blockWordStart: blockWordCursor
|
blockWordStart: blockWordCursor,
|
||||||
|
dropCapText: layoutLineIndex === 0 ? layout.dropCapText : ''
|
||||||
});
|
});
|
||||||
blockWordCursor += lineWordCount;
|
blockWordCursor += lineWordCount;
|
||||||
cursorLine += 1;
|
cursorLine += 1;
|
||||||
@@ -109,7 +112,10 @@ class BookPaginationModule extends BaseModule {
|
|||||||
}
|
}
|
||||||
|
|
||||||
layoutTextBlock(block = {}, type = 'paragraph') {
|
layoutTextBlock(block = {}, type = 'paragraph') {
|
||||||
const text = String(block.layoutText || block.text || '').trim();
|
const sourceText = String(block.layoutText || block.text || '').trim();
|
||||||
|
const dropCap = Boolean(block.dropCap || block.metadata?.dropCap);
|
||||||
|
const dropCapText = dropCap ? this.getDropCapText(sourceText) : '';
|
||||||
|
const text = dropCap ? this.extractDropCapText(sourceText) : sourceText;
|
||||||
if (!text || !this.paragraphLayout) return null;
|
if (!text || !this.paragraphLayout) return null;
|
||||||
|
|
||||||
const typography = this.metrics.typography;
|
const typography = this.metrics.typography;
|
||||||
@@ -119,13 +125,16 @@ class BookPaginationModule extends BaseModule {
|
|||||||
const bottomSpaceLines = role === 'chapter-heading' || role === 'section-heading' ? 1 : 0;
|
const bottomSpaceLines = role === 'chapter-heading' || role === 'section-heading' ? 1 : 0;
|
||||||
const lineHeightPx = Math.max(1, Number(this.metrics.typographyLineHeightPx || 1));
|
const lineHeightPx = Math.max(1, Number(this.metrics.typographyLineHeightPx || 1));
|
||||||
const fontPx = Math.max(1, Number(this.metrics.bodyFontSizePx || lineHeightPx / 1.5));
|
const fontPx = Math.max(1, Number(this.metrics.bodyFontSizePx || lineHeightPx / 1.5));
|
||||||
|
const dropCapWidth = dropCap ? lineHeightPx * 1.58 : 0;
|
||||||
const indent = (isHeading || block.isFirstParagraphInChapter || block.metadata?.isFirstParagraphInChapter || block.addTopSpace)
|
const indent = (isHeading || block.isFirstParagraphInChapter || block.metadata?.isFirstParagraphInChapter || block.addTopSpace)
|
||||||
? 0
|
? 0
|
||||||
: lineHeightPx * 1.5;
|
: lineHeightPx * 1.5;
|
||||||
const measures = isHeading
|
const measures = isHeading
|
||||||
? [this.metrics.content.width]
|
? [this.metrics.content.width]
|
||||||
|
: dropCap
|
||||||
|
? [Math.max(120, this.metrics.content.width - dropCapWidth), Math.max(120, this.metrics.content.width - dropCapWidth), this.metrics.content.width]
|
||||||
: [Math.max(120, this.metrics.content.width - indent), this.metrics.content.width, this.metrics.content.width];
|
: [Math.max(120, this.metrics.content.width - indent), this.metrics.content.width, this.metrics.content.width];
|
||||||
const lineOffsets = isHeading ? [0] : [indent, 0, 0];
|
const lineOffsets = isHeading ? [0] : dropCap ? [dropCapWidth, dropCapWidth, 0] : [indent, 0, 0];
|
||||||
|
|
||||||
const layout = this.paragraphLayout.calculateLayout(text, {
|
const layout = this.paragraphLayout.calculateLayout(text, {
|
||||||
measures,
|
measures,
|
||||||
@@ -144,6 +153,8 @@ class BookPaginationModule extends BaseModule {
|
|||||||
fontStyle: isHeading ? 'italic' : 'normal',
|
fontStyle: isHeading ? 'italic' : 'normal',
|
||||||
topSpaceLines,
|
topSpaceLines,
|
||||||
bottomSpaceLines,
|
bottomSpaceLines,
|
||||||
|
dropCapText,
|
||||||
|
dropCap,
|
||||||
lines: this.extractLines(layout, {
|
lines: this.extractLines(layout, {
|
||||||
measures,
|
measures,
|
||||||
lineOffsets,
|
lineOffsets,
|
||||||
@@ -152,6 +163,16 @@ class BookPaginationModule extends BaseModule {
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
getDropCapText(text) {
|
||||||
|
return String(text || '').trimStart().match(/\S/u)?.[0] || '';
|
||||||
|
}
|
||||||
|
|
||||||
|
extractDropCapText(text) {
|
||||||
|
const dropCap = this.getDropCapText(text);
|
||||||
|
if (!dropCap) return String(text || '');
|
||||||
|
return String(text || '').replace(dropCap, '').trimStart();
|
||||||
|
}
|
||||||
|
|
||||||
extractLines(layout, options = {}) {
|
extractLines(layout, options = {}) {
|
||||||
const lines = [];
|
const lines = [];
|
||||||
const breaks = Array.isArray(layout.breaks) ? layout.breaks : [];
|
const breaks = Array.isArray(layout.breaks) ? layout.breaks : [];
|
||||||
|
|||||||
@@ -159,6 +159,18 @@ class BookTextureRendererModule extends BaseModule {
|
|||||||
let wordIndex = 0;
|
let wordIndex = 0;
|
||||||
|
|
||||||
ctx.font = `${fontStyle}${fontPx}px ${metrics.typography.fontFamily}`;
|
ctx.font = `${fontStyle}${fontPx}px ${metrics.typography.fontFamily}`;
|
||||||
|
if (lineRecord.dropCapText) {
|
||||||
|
ctx.save();
|
||||||
|
ctx.font = `${Math.round(lineHeightPx * 2.08)}px "EB Garamond Initials", ${metrics.typography.fontFamily}`;
|
||||||
|
ctx.textBaseline = 'top';
|
||||||
|
ctx.fillText(
|
||||||
|
String(lineRecord.dropCapText),
|
||||||
|
metrics.content.x,
|
||||||
|
metrics.content.y + (Number(lineRecord.pageLine || 0) * lineHeightPx) - (lineHeightPx * 0.08)
|
||||||
|
);
|
||||||
|
ctx.restore();
|
||||||
|
ctx.font = `${fontStyle}${fontPx}px ${metrics.typography.fontFamily}`;
|
||||||
|
}
|
||||||
nodes.forEach((node, index) => {
|
nodes.forEach((node, index) => {
|
||||||
if (!node) return;
|
if (!node) return;
|
||||||
if (node.type === 'box' && node.value) {
|
if (node.type === 'box' && node.value) {
|
||||||
|
|||||||
+1
-1
@@ -24,7 +24,7 @@ const ModuleState = {
|
|||||||
ERROR: 'ERROR'
|
ERROR: 'ERROR'
|
||||||
};
|
};
|
||||||
|
|
||||||
const MODULE_CACHE_BUSTER = '20260606-webgl-fps-texture-animation';
|
const MODULE_CACHE_BUSTER = '20260606-webgl-texture-dropcap-animation';
|
||||||
window.MODULE_CACHE_BUSTER = MODULE_CACHE_BUSTER;
|
window.MODULE_CACHE_BUSTER = MODULE_CACHE_BUSTER;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
@@ -4,7 +4,7 @@ import { RenderPass } from 'https://esm.sh/three@0.165.0/examples/jsm/postproces
|
|||||||
import { SSAOPass } from 'https://esm.sh/three@0.165.0/examples/jsm/postprocessing/SSAOPass.js';
|
import { SSAOPass } from 'https://esm.sh/three@0.165.0/examples/jsm/postprocessing/SSAOPass.js';
|
||||||
import { SMAAPass } from 'https://esm.sh/three@0.165.0/examples/jsm/postprocessing/SMAAPass.js';
|
import { SMAAPass } from 'https://esm.sh/three@0.165.0/examples/jsm/postprocessing/SMAAPass.js';
|
||||||
import { OutputPass } from 'https://esm.sh/three@0.165.0/examples/jsm/postprocessing/OutputPass.js';
|
import { OutputPass } from 'https://esm.sh/three@0.165.0/examples/jsm/postprocessing/OutputPass.js';
|
||||||
import { PROCEDURAL_BOOK, createProceduralBookModel, snapProceduralPageCount } from './procedural-book-model.js?v=20260606-webgl-fps-texture-animation';
|
import { PROCEDURAL_BOOK, createProceduralBookModel, snapProceduralPageCount } from './procedural-book-model.js?v=20260606-webgl-texture-dropcap-animation';
|
||||||
|
|
||||||
const canvas = document.getElementById('scene');
|
const canvas = document.getElementById('scene');
|
||||||
canvas.style.cursor = 'grab';
|
canvas.style.cursor = 'grab';
|
||||||
|
|||||||
Reference in New Issue
Block a user