马一丁

Embedding Third-Party Libraries in HTML

@@ -146,6 +146,78 @@ class HTMLRenderer: @@ -146,6 +146,78 @@ class HTMLRenderer:
146 self._pdf_font_base64 = "" 146 self._pdf_font_base64 = ""
147 return self._pdf_font_base64 147 return self._pdf_font_base64
148 148
  149 + def _build_script_with_fallback(
  150 + self,
  151 + inline_code: str,
  152 + cdn_url: str,
  153 + check_expression: str,
  154 + lib_name: str,
  155 + is_defer: bool = False
  156 + ) -> str:
  157 + """
  158 + 构建带有CDN fallback机制的script标签
  159 +
  160 + 策略:
  161 + 1. 优先嵌入本地库代码
  162 + 2. 添加检测脚本,验证库是否成功加载
  163 + 3. 如果检测失败,动态加载CDN版本作为备用
  164 +
  165 + 参数:
  166 + inline_code: 本地库的JavaScript代码内容
  167 + cdn_url: CDN备用链接
  168 + check_expression: JavaScript表达式,用于检测库是否加载成功
  169 + lib_name: 库名称(用于日志输出)
  170 + is_defer: 是否使用defer属性
  171 +
  172 + 返回:
  173 + str: 完整的script标签HTML
  174 + """
  175 + defer_attr = ' defer' if is_defer else ''
  176 +
  177 + if inline_code:
  178 + # 嵌入本地库代码,并添加fallback检测
  179 + return f"""
  180 + <script{defer_attr}>
  181 + // {lib_name} - 嵌入式版本
  182 + try {{
  183 + {inline_code}
  184 + }} catch (e) {{
  185 + console.error('{lib_name}嵌入式加载失败:', e);
  186 + }}
  187 + </script>
  188 + <script{defer_attr}>
  189 + // {lib_name} - CDN Fallback检测
  190 + (function() {{
  191 + var checkLib = function() {{
  192 + if (!({check_expression})) {{
  193 + console.warn('{lib_name}本地版本加载失败,正在从CDN加载备用版本...');
  194 + var script = document.createElement('script');
  195 + script.src = '{cdn_url}';
  196 + script.onerror = function() {{
  197 + console.error('{lib_name} CDN备用加载也失败了');
  198 + }};
  199 + script.onload = function() {{
  200 + console.log('{lib_name} CDN备用版本加载成功');
  201 + }};
  202 + document.head.appendChild(script);
  203 + }}
  204 + }};
  205 +
  206 + // 延迟检测,确保嵌入代码有时间执行
  207 + if (document.readyState === 'loading') {{
  208 + document.addEventListener('DOMContentLoaded', function() {{
  209 + setTimeout(checkLib, 100);
  210 + }});
  211 + }} else {{
  212 + setTimeout(checkLib, 100);
  213 + }}
  214 + }})();
  215 + </script>""".strip()
  216 + else:
  217 + # 本地文件读取失败,直接使用CDN
  218 + logger.warning(f"{lib_name}本地文件未找到或读取失败,将直接使用CDN")
  219 + return f' <script{defer_attr} src="{cdn_url}"></script>'
  220 +
149 # ====== 公共入口 ====== 221 # ====== 公共入口 ======
150 222
151 def render(self, document_ir: Dict[str, Any]) -> str: 223 def render(self, document_ir: Dict[str, Any]) -> str:
@@ -252,12 +324,47 @@ class HTMLRenderer: @@ -252,12 +324,47 @@ class HTMLRenderer:
252 jspdf = self._load_lib("jspdf.umd.min.js") 324 jspdf = self._load_lib("jspdf.umd.min.js")
253 mathjax = self._load_lib("mathjax.js") 325 mathjax = self._load_lib("mathjax.js")
254 326
255 - # 如果库文件加载失败,使用CDN备用链接  
256 - chartjs_tag = f"<script>{chartjs}</script>" if chartjs else '<script src="https://cdn.jsdelivr.net/npm/chart.js"></script>'  
257 - sankey_tag = f"<script>{chartjs_sankey}</script>" if chartjs_sankey else '<script src="https://cdn.jsdelivr.net/npm/chartjs-chart-sankey@4"></script>'  
258 - html2canvas_tag = f"<script>{html2canvas}</script>" if html2canvas else '<script src="https://cdnjs.cloudflare.com/ajax/libs/html2canvas/1.4.1/html2canvas.min.js"></script>'  
259 - jspdf_tag = f"<script>{jspdf}</script>" if jspdf else '<script src="https://cdnjs.cloudflare.com/ajax/libs/jspdf/2.5.1/jspdf.umd.min.js"></script>'  
260 - mathjax_tag = f"<script defer>{mathjax}</script>" if mathjax else '<script defer src="https://cdn.jsdelivr.net/npm/mathjax@3/es5/tex-mml-chtml.js"></script>' 327 + # 生成嵌入式script标签,并为每个库添加CDN fallback机制
  328 + # Chart.js - 主要图表库
  329 + chartjs_tag = self._build_script_with_fallback(
  330 + inline_code=chartjs,
  331 + cdn_url="https://cdn.jsdelivr.net/npm/chart.js",
  332 + check_expression="typeof Chart !== 'undefined'",
  333 + lib_name="Chart.js"
  334 + )
  335 +
  336 + # Chart.js Sankey插件
  337 + sankey_tag = self._build_script_with_fallback(
  338 + inline_code=chartjs_sankey,
  339 + cdn_url="https://cdn.jsdelivr.net/npm/chartjs-chart-sankey@4",
  340 + check_expression="typeof Chart !== 'undefined' && Chart.controllers && Chart.controllers.sankey",
  341 + lib_name="chartjs-chart-sankey"
  342 + )
  343 +
  344 + # html2canvas - 用于截图
  345 + html2canvas_tag = self._build_script_with_fallback(
  346 + inline_code=html2canvas,
  347 + cdn_url="https://cdnjs.cloudflare.com/ajax/libs/html2canvas/1.4.1/html2canvas.min.js",
  348 + check_expression="typeof html2canvas !== 'undefined'",
  349 + lib_name="html2canvas"
  350 + )
  351 +
  352 + # jsPDF - 用于PDF导出
  353 + jspdf_tag = self._build_script_with_fallback(
  354 + inline_code=jspdf,
  355 + cdn_url="https://cdnjs.cloudflare.com/ajax/libs/jspdf/2.5.1/jspdf.umd.min.js",
  356 + check_expression="typeof jspdf !== 'undefined'",
  357 + lib_name="jsPDF"
  358 + )
  359 +
  360 + # MathJax - 数学公式渲染
  361 + mathjax_tag = self._build_script_with_fallback(
  362 + inline_code=mathjax,
  363 + cdn_url="https://cdn.jsdelivr.net/npm/mathjax@3/es5/tex-mml-chtml.js",
  364 + check_expression="typeof MathJax !== 'undefined'",
  365 + lib_name="MathJax",
  366 + is_defer=True
  367 + )
261 368
262 # PDF字体数据不再嵌入HTML,减小文件体积 369 # PDF字体数据不再嵌入HTML,减小文件体积
263 pdf_font_script = "" 370 pdf_font_script = ""