refactor: 添加中文注释 + 优化模块结构
- 给全部 78 个 Swift 源文件添加详细的中文注释(文件级、类级、方法级) - 删除 LegacyRDReaderController/ 死代码目录(16 文件 4592 行) - 根目录翻页容器文件移入 ReaderView/ 目录 - Resources/ 移入 EPUBCore/Resources/(与使用者归属一致) - RDEPUBTextIndexTable.swift 移入 EPUBTextRendering/(消除反向依赖) - RDURLReaderController.swift 移入 EPUBUI/(入口控制器归入 UI 层) - 更新 podspec 资源路径
This commit is contained in:
@@ -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>
|
||||
Reference in New Issue
Block a user