Epub阅读器0.0.1
This commit is contained in:
@@ -0,0 +1,204 @@
|
||||
import Foundation
|
||||
|
||||
public enum RDEPUBStyleSheetBuilder {
|
||||
public static func injectPaginationCSS(
|
||||
into html: String,
|
||||
presentation: RDEPUBPresentationStyle
|
||||
) -> String {
|
||||
let styleTag = "<style id=\"ss-reader-pagination\">\(measurementCSS(for: presentation))</style>"
|
||||
if html.range(of: "</head>", options: .caseInsensitive) != nil {
|
||||
return html.replacingOccurrences(of: "</head>", with: styleTag + "</head>", options: .caseInsensitive)
|
||||
}
|
||||
if html.range(of: "<body", options: .caseInsensitive) != nil {
|
||||
return html.replacingOccurrences(of: "<body", with: styleTag + "<body", options: .caseInsensitive)
|
||||
}
|
||||
return styleTag + html
|
||||
}
|
||||
|
||||
public static func renderCSS(for presentation: RDEPUBPresentationStyle) -> String {
|
||||
let values = cssValues(for: presentation)
|
||||
let backgroundCSS = presentation.themeBackgroundColor.map { "background: \($0) !important;" } ?? ""
|
||||
let textCSS = presentation.themeTextColor.map { "color: \($0) !important;" } ?? ""
|
||||
|
||||
return """
|
||||
html {
|
||||
margin: 0 !important;
|
||||
padding: 0 !important;
|
||||
width: \(values.viewportWidth)px !important;
|
||||
height: \(values.viewportHeight)px !important;
|
||||
min-height: \(values.viewportHeight)px !important;
|
||||
overflow-x: hidden !important;
|
||||
overflow-y: hidden !important;
|
||||
}
|
||||
body {
|
||||
margin: 0 !important;
|
||||
padding: 0 !important;
|
||||
box-sizing: border-box !important;
|
||||
position: relative !important;
|
||||
width: \(values.viewportWidth)px !important;
|
||||
height: \(values.viewportHeight)px !important;
|
||||
min-height: \(values.viewportHeight)px !important;
|
||||
overflow: hidden !important;
|
||||
background: transparent !important;
|
||||
}
|
||||
#ss-reader-viewport {
|
||||
position: relative !important;
|
||||
width: \(values.viewportWidth)px !important;
|
||||
height: \(values.viewportHeight)px !important;
|
||||
min-height: \(values.viewportHeight)px !important;
|
||||
overflow: hidden !important;
|
||||
background: transparent !important;
|
||||
}
|
||||
#ss-reader-content {
|
||||
position: relative !important;
|
||||
box-sizing: border-box !important;
|
||||
width: \(values.viewportWidth)px !important;
|
||||
height: \(values.viewportHeight)px !important;
|
||||
min-height: \(values.viewportHeight)px !important;
|
||||
overflow: visible !important;
|
||||
padding: \(values.paddingTop)px \(values.paddingRight)px \(values.paddingBottom)px \(values.paddingLeft)px !important;
|
||||
font-size: \(values.fontSize)px !important;
|
||||
line-height: \(values.lineHeight) !important;
|
||||
-webkit-column-width: \(values.contentWidth)px !important;
|
||||
column-width: \(values.contentWidth)px !important;
|
||||
-webkit-column-gap: \(values.columnGap)px !important;
|
||||
column-gap: \(values.columnGap)px !important;
|
||||
-webkit-column-fill: auto !important;
|
||||
column-fill: auto !important;
|
||||
transform-origin: top left !important;
|
||||
will-change: transform !important;
|
||||
\(backgroundCSS)
|
||||
\(textCSS)
|
||||
}
|
||||
#ss-reader-content img, #ss-reader-content svg, #ss-reader-content video, #ss-reader-content canvas, #ss-reader-content iframe {
|
||||
max-width: 100% !important;
|
||||
height: auto !important;
|
||||
break-inside: avoid-column !important;
|
||||
-webkit-column-break-inside: avoid !important;
|
||||
}
|
||||
#ss-reader-content mark.ss-reader-highlight {
|
||||
padding: 0;
|
||||
border-radius: 2px;
|
||||
color: inherit !important;
|
||||
}
|
||||
#ss-reader-content mark.ss-reader-highlight-highlight {
|
||||
text-decoration: none !important;
|
||||
color: inherit !important;
|
||||
}
|
||||
#ss-reader-content mark.ss-reader-highlight-underline {
|
||||
background: transparent !important;
|
||||
color: inherit !important;
|
||||
text-decoration-line: underline !important;
|
||||
text-decoration-thickness: 2px !important;
|
||||
text-decoration-skip-ink: auto !important;
|
||||
}
|
||||
"""
|
||||
}
|
||||
|
||||
public static func measurementCSS(for presentation: RDEPUBPresentationStyle) -> String {
|
||||
let values = cssValues(for: presentation)
|
||||
let backgroundCSS = presentation.themeBackgroundColor.map { "background: \($0) !important;" } ?? ""
|
||||
let textCSS = presentation.themeTextColor.map { "color: \($0) !important;" } ?? ""
|
||||
|
||||
return """
|
||||
html {
|
||||
margin: 0 !important;
|
||||
padding: 0 !important;
|
||||
width: \(values.viewportWidth)px !important;
|
||||
height: \(values.viewportHeight)px !important;
|
||||
min-height: \(values.viewportHeight)px !important;
|
||||
overflow: hidden !important;
|
||||
}
|
||||
body {
|
||||
margin: 0 !important;
|
||||
box-sizing: border-box !important;
|
||||
position: relative !important;
|
||||
width: \(values.viewportWidth)px !important;
|
||||
height: \(values.viewportHeight)px !important;
|
||||
min-height: \(values.viewportHeight)px !important;
|
||||
overflow: visible !important;
|
||||
padding: \(values.paddingTop)px \(values.paddingRight)px \(values.paddingBottom)px \(values.paddingLeft)px !important;
|
||||
font-size: \(values.fontSize)px !important;
|
||||
line-height: \(values.lineHeight) !important;
|
||||
-webkit-column-width: \(values.contentWidth)px !important;
|
||||
column-width: \(values.contentWidth)px !important;
|
||||
-webkit-column-gap: \(values.columnGap)px !important;
|
||||
column-gap: \(values.columnGap)px !important;
|
||||
-webkit-column-fill: auto !important;
|
||||
column-fill: auto !important;
|
||||
transform-origin: top left !important;
|
||||
\(backgroundCSS)
|
||||
\(textCSS)
|
||||
}
|
||||
img, svg, video, canvas, iframe {
|
||||
max-width: 100% !important;
|
||||
height: auto !important;
|
||||
break-inside: avoid-column !important;
|
||||
-webkit-column-break-inside: avoid !important;
|
||||
}
|
||||
"""
|
||||
}
|
||||
|
||||
public static func measurementScript(for presentation: RDEPUBPresentationStyle) -> String {
|
||||
let style = measurementCSS(for: presentation)
|
||||
.replacingOccurrences(of: "\\", with: "\\\\")
|
||||
.replacingOccurrences(of: "`", with: "\\`")
|
||||
|
||||
let viewportWidth = Double(max(1, presentation.viewportSize.width))
|
||||
let viewportWidthJS = String(format: "%.3f", viewportWidth)
|
||||
|
||||
return """
|
||||
(function() {
|
||||
var style = document.getElementById('ss-reader-pagination');
|
||||
if (!style) {
|
||||
style = document.createElement('style');
|
||||
style.id = 'ss-reader-pagination';
|
||||
document.head.appendChild(style);
|
||||
}
|
||||
style.textContent = `\(style)`;
|
||||
var scrollingElement = document.scrollingElement || document.documentElement || document.body;
|
||||
var totalWidth = Math.max(
|
||||
scrollingElement ? scrollingElement.scrollWidth : 0,
|
||||
scrollingElement ? scrollingElement.offsetWidth : 0,
|
||||
document.body ? document.body.scrollWidth : 0,
|
||||
document.body ? document.body.offsetWidth : 0,
|
||||
document.documentElement ? document.documentElement.scrollWidth : 0,
|
||||
document.documentElement ? document.documentElement.offsetWidth : 0
|
||||
);
|
||||
return Math.max(1, Math.ceil(totalWidth / \(viewportWidthJS)));
|
||||
})();
|
||||
"""
|
||||
}
|
||||
|
||||
private static func cssValues(for presentation: RDEPUBPresentationStyle) -> (
|
||||
viewportWidth: String,
|
||||
viewportHeight: String,
|
||||
contentWidth: String,
|
||||
columnGap: String,
|
||||
paddingTop: String,
|
||||
paddingRight: String,
|
||||
paddingBottom: String,
|
||||
paddingLeft: String,
|
||||
fontSize: String,
|
||||
lineHeight: String
|
||||
) {
|
||||
let viewportWidth = max(1, presentation.viewportSize.width)
|
||||
let viewportHeight = max(1, presentation.viewportSize.height)
|
||||
let contentInsets = presentation.contentInsets
|
||||
let contentWidth = max(1, viewportWidth - contentInsets.left - contentInsets.right)
|
||||
let columnGap = max(0, contentInsets.left + contentInsets.right)
|
||||
|
||||
return (
|
||||
viewportWidth: String(format: "%.3f", viewportWidth),
|
||||
viewportHeight: String(format: "%.3f", viewportHeight),
|
||||
contentWidth: String(format: "%.3f", contentWidth),
|
||||
columnGap: String(format: "%.3f", columnGap),
|
||||
paddingTop: String(format: "%.3f", contentInsets.top),
|
||||
paddingRight: String(format: "%.3f", contentInsets.right),
|
||||
paddingBottom: String(format: "%.3f", contentInsets.bottom),
|
||||
paddingLeft: String(format: "%.3f", contentInsets.left),
|
||||
fontSize: String(format: "%.3f", presentation.fontSize),
|
||||
lineHeight: String(format: "%.3f", max(1, presentation.lineHeightMultiple))
|
||||
)
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user