175 lines
5.7 KiB
JavaScript
175 lines
5.7 KiB
JavaScript
/**
|
|
* Book Page Format Module
|
|
* Defines the canonical page geometry used by the WebGL book renderer.
|
|
*/
|
|
import { BaseModule } from './base-module.js';
|
|
import { calculateProceduralBookThickness, snapProceduralPageCount } from './procedural-book-model.js?v=20260607-webgl-reveal-clock';
|
|
|
|
export const BOOK_TEXTURE_WIDTH = 3072;
|
|
|
|
class BookPageFormatModule extends BaseModule {
|
|
constructor() {
|
|
super('book-page-format', 'Book Page Format');
|
|
this.dependencies = [];
|
|
this.format = Object.freeze({
|
|
id: 'us-mass-market-paperback',
|
|
trim: Object.freeze({
|
|
widthIn: 4.25,
|
|
heightIn: 6.87
|
|
}),
|
|
margins: Object.freeze({
|
|
topIn: 0.46,
|
|
bottomIn: 0.58,
|
|
innerBaseIn: 0.42,
|
|
innerMinIn: 0.48,
|
|
innerMaxIn: 0.74,
|
|
innerThicknessFactor: 0.32,
|
|
outerBaseIn: 0.36,
|
|
outerThicknessFactor: 0.02,
|
|
outerMaxIn: 0.42
|
|
}),
|
|
typography: Object.freeze({
|
|
fontFamily: '"EB Garamond", "EB Garamond 12", serif',
|
|
linesPerPage: 25,
|
|
bodyLineRatio: 1.5,
|
|
headingScale: 1,
|
|
dropCapLines: 2
|
|
})
|
|
});
|
|
this.pageCount = snapProceduralPageCount(window.WebGLBookInitialState?.pageCount ?? 300);
|
|
|
|
this.bindMethods([
|
|
'getFormat',
|
|
'getAspectRatio',
|
|
'getTextureWidth',
|
|
'getTextureMetrics',
|
|
'setPageCount',
|
|
'getPageCount',
|
|
'getDynamicMargins',
|
|
'inchesToTexture'
|
|
]);
|
|
}
|
|
|
|
async initialize() {
|
|
this.addEventListener(document, 'webgl-book:page-count-changed', (event) => {
|
|
this.setPageCount(event.detail?.pageCount);
|
|
});
|
|
this.addEventListener(document, 'preference-updated', (event) => {
|
|
const detail = event.detail || {};
|
|
if (detail.category === 'webgl' && detail.key === 'bookPageCount') this.setPageCount(detail.value);
|
|
});
|
|
this.reportProgress(100, 'Book page format ready');
|
|
return true;
|
|
}
|
|
|
|
getFormat() {
|
|
return this.format;
|
|
}
|
|
|
|
getAspectRatio() {
|
|
return this.format.trim.widthIn / this.format.trim.heightIn;
|
|
}
|
|
|
|
getTextureWidth() {
|
|
return BOOK_TEXTURE_WIDTH;
|
|
}
|
|
|
|
inchesToTexture(valueIn, textureHeight) {
|
|
return (Number(valueIn) / this.format.trim.heightIn) * textureHeight;
|
|
}
|
|
|
|
setPageCount(value) {
|
|
const nextPageCount = snapProceduralPageCount(value ?? this.pageCount);
|
|
if (nextPageCount === this.pageCount) return this.pageCount;
|
|
this.pageCount = nextPageCount;
|
|
return this.pageCount;
|
|
}
|
|
|
|
getPageCount() {
|
|
return this.pageCount;
|
|
}
|
|
|
|
getDynamicMargins(pageCount = this.pageCount) {
|
|
const marginConfig = this.format.margins;
|
|
const thickness = calculateProceduralBookThickness(pageCount);
|
|
const innerIn = Math.min(
|
|
marginConfig.innerMaxIn,
|
|
Math.max(
|
|
marginConfig.innerMinIn,
|
|
marginConfig.innerBaseIn + thickness.textBlockThicknessIn * marginConfig.innerThicknessFactor
|
|
)
|
|
);
|
|
const outerIn = Math.min(
|
|
marginConfig.outerMaxIn,
|
|
marginConfig.outerBaseIn + thickness.textBlockThicknessIn * marginConfig.outerThicknessFactor
|
|
);
|
|
return {
|
|
topIn: 0.46,
|
|
bottomIn: 0.58,
|
|
innerIn,
|
|
outerIn,
|
|
thickness
|
|
};
|
|
}
|
|
|
|
getTextureMetrics(textureWidth = BOOK_TEXTURE_WIDTH, pageCount = this.pageCount) {
|
|
const width = Math.max(1, Math.round(Number(textureWidth) || 1280));
|
|
const height = Math.round(width / this.getAspectRatio());
|
|
const dynamicMargins = this.getDynamicMargins(pageCount);
|
|
const margins = {
|
|
top: this.inchesToTexture(dynamicMargins.topIn, height),
|
|
bottom: this.inchesToTexture(dynamicMargins.bottomIn, height),
|
|
inner: this.inchesToTexture(dynamicMargins.innerIn, height),
|
|
outer: this.inchesToTexture(dynamicMargins.outerIn, height)
|
|
};
|
|
const content = {
|
|
x: margins.outer,
|
|
y: margins.top,
|
|
width: Math.max(1, width - margins.outer - margins.inner),
|
|
height: Math.max(1, height - margins.top - margins.bottom)
|
|
};
|
|
const contentBySide = {
|
|
left: {
|
|
...content,
|
|
x: margins.outer
|
|
},
|
|
right: {
|
|
...content,
|
|
x: margins.inner
|
|
}
|
|
};
|
|
const linesPerPage = Math.max(1, Number(this.format.typography.linesPerPage || 25));
|
|
const typographyLineHeightPx = content.height / linesPerPage;
|
|
const bodyFontSizePx = typographyLineHeightPx / Math.max(1, Number(this.format.typography.bodyLineRatio || 1.5));
|
|
return {
|
|
width,
|
|
height,
|
|
aspectRatio: this.getAspectRatio(),
|
|
margins,
|
|
content,
|
|
contentBySide,
|
|
marginsIn: {
|
|
top: dynamicMargins.topIn,
|
|
bottom: dynamicMargins.bottomIn,
|
|
inner: dynamicMargins.innerIn,
|
|
outer: dynamicMargins.outerIn
|
|
},
|
|
thickness: dynamicMargins.thickness,
|
|
linesPerPage,
|
|
bodyFontSizePx,
|
|
typographyLineHeightPx,
|
|
typography: this.format.typography
|
|
};
|
|
}
|
|
}
|
|
|
|
const bookPageFormat = new BookPageFormatModule();
|
|
|
|
export { bookPageFormat as BookPageFormat };
|
|
|
|
if (window.moduleRegistry) {
|
|
window.moduleRegistry.register(bookPageFormat);
|
|
}
|
|
|
|
window.BookPageFormat = bookPageFormat;
|