// RDEPUBFixedLayoutTemplate.swift // 固定版式(Fixed Layout)HTML 模板生成器 // 根据渲染请求和 Publication 信息,将 iframe 拼装到 HTML 模板中, // 生成适配视口尺寸和内边距的固定版式页面 HTML。 import Foundation /// 固定版式模板生成器,负责将 spine 资源渲染为 iframe 嵌套的 HTML 页面 enum RDEPUBFixedLayoutTemplate { /// 根据渲染请求和 Publication 生成固定版式页面的完整 HTML /// - Parameters: /// - request: 固定版式渲染请求(含 spread、视口尺寸、内边距、适配模式等) /// - publication: EPUB 出版物,用于解析资源 URL /// - Returns: 完整的 HTML 字符串 static func html(for request: RDEPUBFixedRenderRequest, publication: RDEPUBPublication) -> String { // 遍历 spread 中的每个资源,生成对应的 iframe pane HTML let panes = request.spread.resources.enumerated().compactMap { index, resource -> String? in guard let url = publication.resourceResolver.resourceURL(forRelativePath: resource.href)?.absoluteString else { return nil } // 根据 spread 中的资源数量判断页面类型:单页/左页/右页/居中 let pageType: String if request.spread.resources.count == 1 { pageType = "single" } else if request.spread.resources.count == 2 { pageType = index == 0 ? "left" : "right" } else { pageType = "center" } return """
""" }.joined() // 计算视口区域(去除内边距后的可用区域) let background = request.backgroundColorCSS ?? "#FFFFFF" let viewportWidth = max(1, Int((request.viewportSize.width - request.contentInset.left - request.contentInset.right).rounded(.down))) let viewportHeight = max(1, Int((request.viewportSize.height - request.contentInset.top - request.contentInset.bottom).rounded(.down))) let insetTop = Int(request.contentInset.top.rounded(.down)) let insetRight = Int(request.contentInset.right.rounded(.down)) let insetBottom = Int(request.contentInset.bottom.rounded(.down)) let insetLeft = Int(request.contentInset.left.rounded(.down)) // 从资源仓库加载 HTML 模板并替换占位符 return RDEPUBAssetRepository.string( for: .fixedLayoutTemplate, replacements: [ "BACKGROUND": background, "INSET_TOP": String(insetTop), "INSET_RIGHT": String(insetRight), "INSET_BOTTOM": String(insetBottom), "INSET_LEFT": String(insetLeft), "VIEWPORT_WIDTH": String(viewportWidth), "VIEWPORT_HEIGHT": String(viewportHeight), "PANES": panes, "FIT_MODE": request.fit.rawValue, "FIXED_LAYOUT_READY_MESSAGE": RDEPUBJavaScriptBridgeMessage.fixedLayoutReady.rawValue ] ) } }