refactor: 添加中文注释 + 优化模块结构

- 给全部 78 个 Swift 源文件添加详细的中文注释(文件级、类级、方法级)
- 删除 LegacyRDReaderController/ 死代码目录(16 文件 4592 行)
- 根目录翻页容器文件移入 ReaderView/ 目录
- Resources/ 移入 EPUBCore/Resources/(与使用者归属一致)
- RDEPUBTextIndexTable.swift 移入 EPUBTextRendering/(消除反向依赖)
- RDURLReaderController.swift 移入 EPUBUI/(入口控制器归入 UI 层)
- 更新 podspec 资源路径
This commit is contained in:
shen
2026-05-25 10:19:14 +08:00
parent 5af90c1148
commit 54798ba578
88 changed files with 2492 additions and 4469 deletions
@@ -0,0 +1,297 @@
(function() {
if (window.RDReaderBridge) { return; }
window.addEventListener('error', function(event) {
try {
var message = event && (event.message || (event.error && event.error.message)) || 'Unknown JavaScript error';
window.webkit.messageHandlers.{{JAVASCRIPT_ERROR_MESSAGE}}.postMessage(message);
} catch (error) {
}
});
function nodePath(node) {
var path = [];
var current = node;
while (current && current !== document) {
var parent = current.parentNode;
if (!parent) { break; }
var index = Array.prototype.indexOf.call(parent.childNodes, current);
path.unshift(index);
current = parent;
}
return path;
}
function nodeFromPath(path) {
var current = document;
for (var i = 0; i < path.length; i += 1) {
if (!current || !current.childNodes || current.childNodes.length <= path[i]) { return null; }
current = current.childNodes[path[i]];
}
return current;
}
function serializeRange(range) {
return JSON.stringify({
kind: 'dom-range',
startPath: nodePath(range.startContainer),
startOffset: range.startOffset,
endPath: nodePath(range.endContainer),
endOffset: range.endOffset
});
}
function rangeFromInfo(rangeInfo) {
try {
var payload = typeof rangeInfo === 'string' ? JSON.parse(rangeInfo) : rangeInfo;
var startNode = nodeFromPath(payload.startPath || []);
var endNode = nodeFromPath(payload.endPath || []);
if (!startNode || !endNode) { return null; }
var range = document.createRange();
range.setStart(startNode, payload.startOffset || 0);
range.setEnd(endNode, payload.endOffset || 0);
return range;
} catch (error) {
return null;
}
}
var visiblePageIndex = 0;
var configuredPageCount = 1;
var configuredPageStride = 1;
var relayoutTimer = null;
var fixedLayoutDocument = false;
function isFixedLayoutDocument() {
if (fixedLayoutDocument) { return true; }
fixedLayoutDocument = !!document.getElementById('ss-fixed-root') || !!document.querySelector('.ss-fixed-page');
return fixedLayoutDocument;
}
function ensurePaginationStructure() {
if (isFixedLayoutDocument()) {
return null;
}
var viewport = document.getElementById('ss-reader-viewport');
var content = document.getElementById('ss-reader-content');
if (viewport && content) {
return content;
}
viewport = document.createElement('div');
viewport.id = 'ss-reader-viewport';
content = document.createElement('div');
content.id = 'ss-reader-content';
while (document.body.firstChild) {
content.appendChild(document.body.firstChild);
}
viewport.appendChild(content);
document.body.appendChild(viewport);
return content;
}
function paginationContent() {
if (isFixedLayoutDocument()) {
return null;
}
return ensurePaginationStructure();
}
function effectivePageStride() {
return Math.max(1, configuredPageStride || 1);
}
function measuredPageCount() {
if (isFixedLayoutDocument()) {
return 1;
}
var content = paginationContent();
var scrollingElement = document.scrollingElement || document.documentElement || document.body;
var scrollWidth = Math.max(
content ? content.scrollWidth : 0,
content ? content.offsetWidth : 0,
scrollingElement ? scrollingElement.scrollWidth : 0,
document.documentElement ? document.documentElement.scrollWidth : 0,
document.body ? document.body.scrollWidth : 0,
effectivePageStride()
);
return Math.max(1, Math.ceil(scrollWidth / effectivePageStride()));
}
function totalPageCount() {
return Math.max(configuredPageCount, measuredPageCount());
}
function currentPageOffset() {
return Math.round(visiblePageIndex * effectivePageStride());
}
function reportProgression(fragment) {
var pageCount = totalPageCount();
var progression = pageCount <= 1 ? 0 : visiblePageIndex / Math.max(pageCount - 1, 1);
var lastProgression = progression;
window.webkit.messageHandlers.{{PROGRESSION_CHANGED_MESSAGE}}.postMessage({
progression: progression,
lastProgression: lastProgression,
fragment: fragment || null
});
}
function setVisiblePageIndex(pageIndex) {
if (isFixedLayoutDocument()) {
visiblePageIndex = 0;
return;
}
var pageCount = totalPageCount();
var maxPageIndex = Math.max(0, pageCount - 1);
var safePageIndex = Math.max(0, Math.min(maxPageIndex, Math.round(pageIndex || 0)));
visiblePageIndex = safePageIndex;
var content = paginationContent();
if (content) {
content.style.transform = 'translate3d(' + (-currentPageOffset()) + 'px, 0, 0)';
}
}
function scheduleRelayout() {
if (isFixedLayoutDocument()) { return; }
clearTimeout(relayoutTimer);
relayoutTimer = setTimeout(function() {
setVisiblePageIndex(visiblePageIndex);
reportProgression(null);
}, 60);
}
function pageIndexForElement(target) {
if (isFixedLayoutDocument()) { return 0; }
if (!target) { return 0; }
var rect = target.getBoundingClientRect();
var absoluteLeft = Math.max(0, rect.left + currentPageOffset());
return Math.max(0, Math.floor(absoluteLeft / effectivePageStride()));
}
function unwrapHighlights() {
document.querySelectorAll('mark.ss-reader-highlight').forEach(function(mark) {
var parent = mark.parentNode;
if (!parent) { return; }
while (mark.firstChild) {
parent.insertBefore(mark.firstChild, mark);
}
parent.removeChild(mark);
});
}
function setHighlights(items) {
unwrapHighlights();
if (!Array.isArray(items)) { return; }
items.forEach(function(item) {
if (!item || !item.rangeInfo) { return; }
var range = rangeFromInfo(item.rangeInfo);
if (!range || range.collapsed) { return; }
try {
var mark = document.createElement('mark');
var style = item.style || 'highlight';
mark.className = 'ss-reader-highlight ss-reader-highlight-' + style;
mark.style.color = 'inherit';
if (style === 'underline') {
mark.style.background = 'transparent';
mark.style.textDecorationLine = 'underline';
mark.style.textDecorationStyle = 'solid';
mark.style.textDecorationThickness = '2px';
mark.style.textDecorationColor = item.color || '#F8E16C';
} else {
mark.style.background = item.color || '#F8E16C';
}
mark.dataset.highlightId = item.id || '';
var fragment = range.extractContents();
mark.appendChild(fragment);
range.insertNode(mark);
if (mark.parentNode && mark.parentNode.normalize) {
mark.parentNode.normalize();
}
} catch (error) {
}
});
}
function selectionPayload() {
var selection = window.getSelection();
if (!selection || selection.rangeCount === 0 || selection.isCollapsed) {
return null;
}
var range = selection.getRangeAt(0);
var text = selection.toString();
if (!text || !text.trim()) { return null; }
var pageCount = totalPageCount();
var progression = pageCount <= 1 ? 0 : visiblePageIndex / Math.max(pageCount - 1, 1);
var lastProgression = progression;
return {
text: text,
rangeInfo: serializeRange(range),
progression: progression,
lastProgression: lastProgression
};
}
var selectionTimer = null;
document.addEventListener('selectionchange', function() {
clearTimeout(selectionTimer);
selectionTimer = setTimeout(function() {
var payload = selectionPayload();
window.webkit.messageHandlers.{{SELECTION_CHANGED_MESSAGE}}.postMessage(payload);
}, 120);
});
window.addEventListener('resize', function() {
if (isFixedLayoutDocument()) { return; }
setVisiblePageIndex(visiblePageIndex);
reportProgression(null);
}, { passive: true });
window.addEventListener('load', scheduleRelayout, { passive: true });
if (!isFixedLayoutDocument() && document.fonts && document.fonts.ready) {
document.fonts.ready.then(scheduleRelayout).catch(function() {});
}
if (!isFixedLayoutDocument()) {
Array.prototype.forEach.call(document.querySelectorAll('img, iframe, video'), function(node) {
node.addEventListener('load', scheduleRelayout, { passive: true });
node.addEventListener('error', scheduleRelayout, { passive: true });
});
}
document.addEventListener('click', function(event) {
var anchor = event.target.closest ? event.target.closest('a[href]') : null;
if (!anchor) { return; }
var href = anchor.getAttribute('href');
if (!href) { return; }
if (/^(https?:|mailto:|tel:)/i.test(href)) {
event.preventDefault();
window.webkit.messageHandlers.{{EXTERNAL_LINK_MESSAGE}}.postMessage({ url: href });
return;
}
event.preventDefault();
window.webkit.messageHandlers.{{INTERNAL_LINK_MESSAGE}}.postMessage({ href: href });
}, true);
window.RDReaderBridge = {
applyPagination: function(styleText) {
if (isFixedLayoutDocument()) { return; }
var style = document.getElementById('ss-reader-pagination');
if (!style) {
style = document.createElement('style');
style.id = 'ss-reader-pagination';
document.head.appendChild(style);
}
style.textContent = styleText;
ensurePaginationStructure();
},
setPageMetrics: function(pageCount, pageStride) {
if (isFixedLayoutDocument()) { return; }
configuredPageCount = Math.max(1, Math.round(pageCount || 1));
configuredPageStride = Math.max(1, Number(pageStride) || configuredPageStride || 1);
setVisiblePageIndex(visiblePageIndex);
scheduleRelayout();
},
scrollToPage: function(pageIndex) {
if (isFixedLayoutDocument()) { return; }
setVisiblePageIndex(pageIndex);
reportProgression(null);
},
scrollToLocation: function(location, fallbackPageIndex) {
if (location && location.fragment) {
var target = document.getElementById(location.fragment) || document.querySelector('[name="' + location.fragment.replace(/"/g, '\\"') + '"]');
if (target) {
setVisiblePageIndex(pageIndexForElement(target));
reportProgression(location.fragment);
return;
}
}
var pageCount = totalPageCount();
var progression = location && typeof location.progression === 'number' ? location.progression : 0;
var derivedPageIndex = progression > 0 && pageCount > 1
? Math.round(progression * Math.max(pageCount - 1, 0))
: (fallbackPageIndex || 0);
this.scrollToPage(derivedPageIndex);
},
setHighlights: setHighlights,
clearHighlights: unwrapHighlights,
reportProgression: reportProgression,
selectionPayload: selectionPayload
};
})();
@@ -0,0 +1,207 @@
<!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 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) ||
parsePageSizeFromEmbeddedImage(iframe) ||
pageViewportSize(iframe);
layoutPage(iframe);
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>