ReadViewSDK/Sources/RDReaderView/EPUBCore/Resources/epub-fixed-layout.html
shenlei c65c190b71 feat: EPUB阅读器搜索、注释、CFI模块及大书远距跳转优化
- 实现EPUB阅读器搜索功能及选中注释功能
- 优化CFI模块,修复代码审查发现的11个问题
- 实现大书远距目录跳转与后台补全优化方案
- 优化设置面板与章节运行时联动
- 重构及大量改进优化
2026-06-22 20:26:34 +08:00

267 lines
8.6 KiB
HTML

<!doctype html>
<html>
<head>
<meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=no">
<style>
html, body { margin: 0; padding: 0; width: 100%; height: 100%; overflow: hidden; background: {{BACKGROUND}}; }
body { position: relative; }
#ss-fixed-root {
position: absolute;
top: {{INSET_TOP}}px;
right: {{INSET_RIGHT}}px;
bottom: {{INSET_BOTTOM}}px;
left: {{INSET_LEFT}}px;
display: flex;
flex-direction: row;
align-items: stretch;
justify-content: center;
overflow: hidden;
}
.ss-fixed-viewport {
position: relative;
flex: 1 1 0;
min-width: 0;
min-height: 0;
overflow: hidden;
}
.ss-fixed-page {
position: absolute;
top: 50%;
width: 0;
height: 0;
border: 0;
background: transparent;
transform-origin: top left;
}
.ss-fixed-page[data-page-type="single"],
.ss-fixed-page[data-page-type="center"] {
left: 50%;
transform: translate(-50%, -50%);
}
.ss-fixed-page[data-page-type="left"] {
right: 0;
transform: translateY(-50%);
transform-origin: top right;
}
.ss-fixed-page[data-page-type="right"] {
left: 0;
transform: translateY(-50%);
transform-origin: top left;
}
</style>
</head>
<body>
<div id="ss-fixed-root">{{PANES}}</div>
<script>
(function() {
var Fit = { AUTO: 'auto', PAGE: 'page', WIDTH: 'width' };
var safeAreaInsets = { top: {{INSET_TOP}}, right: {{INSET_RIGHT}}, bottom: {{INSET_BOTTOM}}, left: {{INSET_LEFT}} };
var viewportSize = { width: {{VIEWPORT_WIDTH}}, height: {{VIEWPORT_HEIGHT}} };
var fit = '{{FIT_MODE}}';
var pendingFrames = 0;
var readySent = false;
function notifyReady() {
if (readySent) { return; }
readySent = true;
try {
window.webkit.messageHandlers.{{FIXED_LAYOUT_READY_MESSAGE}}.postMessage({ loaded: true });
} catch (error) {
}
}
function parsePageSizeFromViewportMetaTag(iframe) {
try {
var viewport = iframe.contentWindow.document.querySelector('meta[name="viewport"]');
if (!viewport) { return null; }
var regex = /(\w+) *= *([^\s,]+)/g;
var properties = {};
var match;
while ((match = regex.exec(viewport.content))) {
properties[match[1]] = match[2];
}
var width = Number.parseFloat(properties.width);
var height = Number.parseFloat(properties.height);
if (!width || !height) { return null; }
return { width: width, height: height };
} catch (error) {
return null;
}
}
function parseCssPixelValue(value) {
if (!value) { return 0; }
var match = String(value).match(/([0-9]+(?:\.[0-9]+)?)px?/);
return match ? Number.parseFloat(match[1]) : 0;
}
function sizeFromElement(element) {
if (!element) { return null; }
var width = parseCssPixelValue(element.style && element.style.width);
var height = parseCssPixelValue(element.style && element.style.height);
if (!width || !height) {
width = parseCssPixelValue(element.getAttribute && element.getAttribute('width'));
height = parseCssPixelValue(element.getAttribute && element.getAttribute('height'));
}
if (!width || !height) {
var rect = element.getBoundingClientRect ? element.getBoundingClientRect() : null;
if (rect) {
width = width || rect.width;
height = height || rect.height;
}
}
if (width > 1 && height > 1) {
return { width: width, height: height };
}
return null;
}
function parsePageSizeFromDocumentBox(iframe) {
try {
var doc = iframe.contentWindow.document;
return sizeFromElement(doc.body) ||
sizeFromElement(doc.documentElement) ||
sizeFromElement(doc.querySelector('[id$="_hype_container"]')) ||
sizeFromElement(doc.querySelector('iframe')) ||
null;
} catch (error) {
return null;
}
}
function parsePageSizeFromNestedFrame(iframe) {
try {
var nestedFrame = iframe.contentWindow.document.querySelector('iframe');
if (!nestedFrame || !nestedFrame.contentWindow || !nestedFrame.contentWindow.document) {
return null;
}
return parsePageSizeFromViewportMetaTag(nestedFrame) ||
parsePageSizeFromDocumentBox(nestedFrame) ||
parsePageSizeFromEmbeddedImage(nestedFrame);
} catch (error) {
return null;
}
}
function parsePageSizeFromEmbeddedImage(iframe) {
try {
var img = iframe.contentWindow.document.querySelector('img');
if (!img || !img.naturalWidth || !img.naturalHeight) { return null; }
return { width: img.naturalWidth, height: img.naturalHeight };
} catch (error) {
return null;
}
}
function pageViewportSize(iframe) {
var viewport = iframe.closest('.ss-fixed-viewport');
if (!viewport) { return viewportSize; }
var width = Math.max(1, viewport.clientWidth);
var height = Math.max(1, viewport.clientHeight);
if (width <= 1 || height <= 1) {
return viewportSize;
}
return {
width: width,
height: height
};
}
function usesWidthFit(pageSize, localViewport) {
if (fit === Fit.WIDTH) {
return true;
}
if (fit !== Fit.AUTO) {
return false;
}
var widthRatio = localViewport.width / pageSize.width;
var heightRatio = localViewport.height / pageSize.height;
return widthRatio <= heightRatio;
}
function layoutPage(iframe) {
var pageSize = iframe.__ssPageSize;
if (!pageSize) { return; }
var pageType = iframe.dataset.pageType || 'single';
var localViewport = pageViewportSize(iframe);
iframe.style.width = pageSize.width + 'px';
iframe.style.height = pageSize.height + 'px';
var widthRatio = localViewport.width / pageSize.width;
var heightRatio = localViewport.height / pageSize.height;
var widthFit = usesWidthFit(pageSize, localViewport);
var scale = widthFit ? widthRatio : Math.min(widthRatio, heightRatio);
var scaledWidth = pageSize.width * scale;
var scaledHeight = pageSize.height * scale;
var offsetX, offsetY;
if (pageType === 'left') {
offsetX = localViewport.width - scaledWidth;
} else if (pageType === 'right') {
offsetX = 0;
} else {
offsetX = (localViewport.width - scaledWidth) / 2;
}
if (widthFit && scaledHeight > localViewport.height) {
offsetY = safeAreaInsets.top;
} else {
offsetY = (localViewport.height - scaledHeight) / 2;
offsetY += (safeAreaInsets.top - safeAreaInsets.bottom) / 2;
}
iframe.style.left = offsetX + 'px';
iframe.style.top = offsetY + 'px';
iframe.style.transform = 'scale(' + scale + ')';
}
function preparePage(iframe) {
pendingFrames += 1;
function finalizeLoad() {
pendingFrames = Math.max(0, pendingFrames - 1);
if (pendingFrames === 0) {
notifyReady();
}
}
function onLoad() {
iframe.__ssPageSize =
parsePageSizeFromViewportMetaTag(iframe) ||
parsePageSizeFromDocumentBox(iframe) ||
parsePageSizeFromNestedFrame(iframe) ||
parsePageSizeFromEmbeddedImage(iframe) ||
pageViewportSize(iframe);
layoutPage(iframe);
window.setTimeout(function() {
layoutPage(iframe);
}, 250);
finalizeLoad();
}
iframe.addEventListener('load', function() {
window.setTimeout(onLoad, 100);
});
iframe.addEventListener('error', function() {
iframe.__ssPageSize = pageViewportSize(iframe);
layoutPage(iframe);
finalizeLoad();
});
}
var pages = document.querySelectorAll('.ss-fixed-page');
Array.prototype.forEach.call(pages, preparePage);
if (pages.length === 0) {
notifyReady();
}
window.addEventListener('resize', function() {
Array.prototype.forEach.call(document.querySelectorAll('.ss-fixed-page'), layoutPage);
});
})();
</script>
</body>
</html>