Browse Source

语言在线pdf阅读

root 1 day ago
commit
607282ebc2
100 changed files with 46394 additions and 0 deletions
  1. 7 0
      .gitignore
  2. 531 0
      main.py
  3. 244 0
      main2-old.py
  4. 253 0
      main_kokoro.py
  5. 299 0
      main_server.py
  6. 1 0
      start.sh
  7. 25344 0
      static/build/pdf.mjs
  8. 0 0
      static/build/pdf.mjs.map
  9. 3638 0
      static/build/pdf.sandbox.mjs
  10. 0 0
      static/build/pdf.sandbox.mjs.map
  11. 8016 0
      static/build/pdf.worker.mjs
  12. 0 0
      static/build/pdf.worker.mjs.map
  13. 1111 0
      static/web/bak/viewer.html.bak
  14. 1135 0
      static/web/bak/viewer.html.bak24-11-7
  15. 1490 0
      static/web/bak/viewer.html1
  16. 1410 0
      static/web/bak/viewer.htmlbak25621
  17. 1086 0
      static/web/bak/viewer_copy.html
  18. 1196 0
      static/web/bak/viewer_old.html
  19. 623 0
      static/web/bak/viewernov.html
  20. BIN
      static/web/cmaps/78-EUC-H.bcmap
  21. BIN
      static/web/cmaps/78-EUC-V.bcmap
  22. BIN
      static/web/cmaps/78-H.bcmap
  23. BIN
      static/web/cmaps/78-RKSJ-H.bcmap
  24. BIN
      static/web/cmaps/78-RKSJ-V.bcmap
  25. BIN
      static/web/cmaps/78-V.bcmap
  26. BIN
      static/web/cmaps/78ms-RKSJ-H.bcmap
  27. BIN
      static/web/cmaps/78ms-RKSJ-V.bcmap
  28. BIN
      static/web/cmaps/83pv-RKSJ-H.bcmap
  29. BIN
      static/web/cmaps/90ms-RKSJ-H.bcmap
  30. BIN
      static/web/cmaps/90ms-RKSJ-V.bcmap
  31. BIN
      static/web/cmaps/90msp-RKSJ-H.bcmap
  32. BIN
      static/web/cmaps/90msp-RKSJ-V.bcmap
  33. BIN
      static/web/cmaps/90pv-RKSJ-H.bcmap
  34. BIN
      static/web/cmaps/90pv-RKSJ-V.bcmap
  35. BIN
      static/web/cmaps/Add-H.bcmap
  36. BIN
      static/web/cmaps/Add-RKSJ-H.bcmap
  37. BIN
      static/web/cmaps/Add-RKSJ-V.bcmap
  38. BIN
      static/web/cmaps/Add-V.bcmap
  39. BIN
      static/web/cmaps/Adobe-CNS1-0.bcmap
  40. BIN
      static/web/cmaps/Adobe-CNS1-1.bcmap
  41. BIN
      static/web/cmaps/Adobe-CNS1-2.bcmap
  42. BIN
      static/web/cmaps/Adobe-CNS1-3.bcmap
  43. BIN
      static/web/cmaps/Adobe-CNS1-4.bcmap
  44. BIN
      static/web/cmaps/Adobe-CNS1-5.bcmap
  45. BIN
      static/web/cmaps/Adobe-CNS1-6.bcmap
  46. BIN
      static/web/cmaps/Adobe-CNS1-UCS2.bcmap
  47. BIN
      static/web/cmaps/Adobe-GB1-0.bcmap
  48. BIN
      static/web/cmaps/Adobe-GB1-1.bcmap
  49. BIN
      static/web/cmaps/Adobe-GB1-2.bcmap
  50. BIN
      static/web/cmaps/Adobe-GB1-3.bcmap
  51. BIN
      static/web/cmaps/Adobe-GB1-4.bcmap
  52. BIN
      static/web/cmaps/Adobe-GB1-5.bcmap
  53. BIN
      static/web/cmaps/Adobe-GB1-UCS2.bcmap
  54. BIN
      static/web/cmaps/Adobe-Japan1-0.bcmap
  55. BIN
      static/web/cmaps/Adobe-Japan1-1.bcmap
  56. BIN
      static/web/cmaps/Adobe-Japan1-2.bcmap
  57. BIN
      static/web/cmaps/Adobe-Japan1-3.bcmap
  58. BIN
      static/web/cmaps/Adobe-Japan1-4.bcmap
  59. BIN
      static/web/cmaps/Adobe-Japan1-5.bcmap
  60. BIN
      static/web/cmaps/Adobe-Japan1-6.bcmap
  61. BIN
      static/web/cmaps/Adobe-Japan1-UCS2.bcmap
  62. BIN
      static/web/cmaps/Adobe-Korea1-0.bcmap
  63. BIN
      static/web/cmaps/Adobe-Korea1-1.bcmap
  64. BIN
      static/web/cmaps/Adobe-Korea1-2.bcmap
  65. BIN
      static/web/cmaps/Adobe-Korea1-UCS2.bcmap
  66. BIN
      static/web/cmaps/B5-H.bcmap
  67. BIN
      static/web/cmaps/B5-V.bcmap
  68. BIN
      static/web/cmaps/B5pc-H.bcmap
  69. BIN
      static/web/cmaps/B5pc-V.bcmap
  70. BIN
      static/web/cmaps/CNS-EUC-H.bcmap
  71. BIN
      static/web/cmaps/CNS-EUC-V.bcmap
  72. BIN
      static/web/cmaps/CNS1-H.bcmap
  73. BIN
      static/web/cmaps/CNS1-V.bcmap
  74. BIN
      static/web/cmaps/CNS2-H.bcmap
  75. 3 0
      static/web/cmaps/CNS2-V.bcmap
  76. BIN
      static/web/cmaps/ETHK-B5-H.bcmap
  77. BIN
      static/web/cmaps/ETHK-B5-V.bcmap
  78. BIN
      static/web/cmaps/ETen-B5-H.bcmap
  79. BIN
      static/web/cmaps/ETen-B5-V.bcmap
  80. 3 0
      static/web/cmaps/ETenms-B5-H.bcmap
  81. BIN
      static/web/cmaps/ETenms-B5-V.bcmap
  82. BIN
      static/web/cmaps/EUC-H.bcmap
  83. BIN
      static/web/cmaps/EUC-V.bcmap
  84. BIN
      static/web/cmaps/Ext-H.bcmap
  85. BIN
      static/web/cmaps/Ext-RKSJ-H.bcmap
  86. BIN
      static/web/cmaps/Ext-RKSJ-V.bcmap
  87. BIN
      static/web/cmaps/Ext-V.bcmap
  88. BIN
      static/web/cmaps/GB-EUC-H.bcmap
  89. BIN
      static/web/cmaps/GB-EUC-V.bcmap
  90. 4 0
      static/web/cmaps/GB-H.bcmap
  91. BIN
      static/web/cmaps/GB-V.bcmap
  92. BIN
      static/web/cmaps/GBK-EUC-H.bcmap
  93. BIN
      static/web/cmaps/GBK-EUC-V.bcmap
  94. BIN
      static/web/cmaps/GBK2K-H.bcmap
  95. BIN
      static/web/cmaps/GBK2K-V.bcmap
  96. BIN
      static/web/cmaps/GBKp-EUC-H.bcmap
  97. BIN
      static/web/cmaps/GBKp-EUC-V.bcmap
  98. BIN
      static/web/cmaps/GBT-EUC-H.bcmap
  99. BIN
      static/web/cmaps/GBT-EUC-V.bcmap
  100. BIN
      static/web/cmaps/GBT-H.bcmap

+ 7 - 0
.gitignore

@@ -0,0 +1,7 @@
+__pycache__/
+*.py[cod]
+.*
+!.gitignore
+/static/files/
+/audio_cache/
+nohup.out

+ 531 - 0
main.py

@@ -0,0 +1,531 @@
+# from fastapi import FastAPI, Request, File, UploadFile, HTTPException, Form, Response
+# from fastapi.middleware.cors import CORSMiddleware
+# from fastapi.responses import JSONResponse, RedirectResponse, StreamingResponse
+# from fastapi.staticfiles import StaticFiles
+# import os
+# import shutil
+# import uuid
+# from pydantic import BaseModel
+# import hashlib
+# import asyncio
+# from typing import AsyncGenerator
+# import soundfile as sf
+# import io
+# import logging
+# from kokoro import KPipeline  # Assuming kokoro is installed and available
+
+# # Set up logging
+# logging.basicConfig(level=logging.INFO)
+# logger = logging.getLogger(__name__)
+
+# # Initialize FastAPI app
+# app = FastAPI()
+
+# # Configure CORS
+# origins = ["*"]
+# app.add_middleware(
+#     CORSMiddleware,
+#     allow_origins=origins,
+#     allow_credentials=True,
+#     allow_methods=["*"],
+#     allow_headers=["*"],
+# )
+
+# # Directory for uploaded files
+# UPLOAD_DIRECTORY = "static/files"
+# if not os.path.exists(UPLOAD_DIRECTORY):
+#     os.makedirs(UPLOAD_DIRECTORY)
+
+# # Mount static files
+# app.mount("/static/files", StaticFiles(directory=UPLOAD_DIRECTORY), name="static_files")
+# app.mount("/static/web", StaticFiles(directory="static/web"), name="static_web")
+# app.mount("/static", StaticFiles(directory="static"), name="static")
+
+# # Audio cache directory
+# CACHE_DIR = "audio_cache"
+# os.makedirs(CACHE_DIR, exist_ok=True)
+
+# # Root redirect to PDF viewer
+# @app.get("/")
+# def root():
+#     return RedirectResponse(url="/static/web/viewer.html?file=/static/files/compress.pdf")
+
+# # Sanitize filename
+# def sanitize_filename(name: str) -> str:
+#     return "".join(c for c in name if c.isalnum() or c in (' ', '.', '_', '-')).rstrip()
+
+# # PDF upload endpoint
+# @app.post("/upload-pdf")
+# async def upload_pdf(file: UploadFile = File(...), custom_name: str = Form(...)):
+#     if file.content_type != 'application/pdf':
+#         raise HTTPException(status_code=400, detail="文件类型必须是 PDF")
+#     sanitized_name = sanitize_filename(custom_name)
+#     if not sanitized_name:
+#         return JSONResponse(status_code=400, content={"success": False, "error": "无效的文件名"})
+    
+#     unique_filename = f"{sanitized_name}.pdf"
+#     file_path = os.path.join(UPLOAD_DIRECTORY, unique_filename)
+
+#     if os.path.exists(file_path):
+#         return JSONResponse(status_code=400, content={"success": False, "error": "文件名已存在,请使用其他名称"})
+
+#     try:
+#         with open(file_path, "wb") as buffer:
+#             shutil.copyfileobj(file.file, buffer)
+#     except Exception as e:
+#         raise HTTPException(status_code=500, detail="上传过程中出错")
+#     finally:
+#         file.file.close()
+
+#     file_relative_path = f"/static/files/{unique_filename}"
+#     return JSONResponse(content={"success": True, "file_path": file_relative_path})
+
+# # List PDFs endpoint
+# @app.get("/list-pdfs")
+# async def list_pdfs():
+#     try:
+#         files = os.listdir(UPLOAD_DIRECTORY)
+#         pdf_files = [
+#             {"name": file, "url": f"/static/files/{file}"}
+#             for file in files if file.lower().endswith(".pdf")
+#         ]
+#         return JSONResponse(content={"success": True, "files": pdf_files})
+#     except Exception as e:
+#         raise HTTPException(status_code=500, detail="无法获取文件列表")
+
+# # TTS Server with Kokoro
+# class TextToSpeechServer:
+#     def __init__(self):
+#         self.pipeline = None
+        
+#     def load_model(self, lang_code='a'):
+#         try:
+#             logger.info("Loading KPipeline model...")
+#             self.pipeline = KPipeline(lang_code=lang_code)
+#             logger.info("Model loaded successfully")
+#         except Exception as e:
+#             logger.error(f"Failed to load model: {str(e)}")
+#             raise
+
+# # Initialize TTS server
+# tts_server = TextToSpeechServer()
+
+# # Startup event to load Kokoro model
+# @app.on_event("startup")
+# async def startup_event():
+#     tts_server.load_model()
+
+# # Request models
+# class TextToSpeechRequest(BaseModel):
+#     user_input: str
+#     voice: str = 'af_heart'  # Default voice for Kokoro
+#     speed: float = 1.0       # Default speed
+
+# # Text-to-speech endpoint (streaming)
+# @app.post("/text-to-speech/")
+# async def text_to_speech(request: TextToSpeechRequest):
+#     user_input = request.user_input.strip()
+#     if not user_input:
+#         raise HTTPException(status_code=400, detail="输入文本为空")
+    
+#     text_hash = hashlib.md5(user_input.encode('utf-8')).hexdigest()
+#     audio_path = os.path.join(CACHE_DIR, f"{text_hash}.wav")
+
+#     if os.path.exists(audio_path):
+#         with open(audio_path, "rb") as f:
+#             return Response(content=f.read(), media_type="audio/wav")
+    
+#     async def audio_generator() -> AsyncGenerator[bytes, None]:
+#         try:
+#             if not tts_server.pipeline:
+#                 raise HTTPException(status_code=503, detail="Model not initialized")
+#             print(user_input)
+#             generator = tts_server.pipeline(
+#                 text=user_input,
+#                 voice=request.voice,
+#                 speed=request.speed,
+#                 split_pattern=r'\n+'
+#             )
+            
+#             # 用于拼接所有音频数据的 NumPy 数组
+#             full_audio_data = []
+#             for i, (gs, ps, audio) in enumerate(generator):
+#                 print(f"Generating segment {i}")
+#                 full_audio_data.append(audio)  # 假设 audio 是 NumPy 数组
+            
+#             # 将所有音频片段拼接成一个完整的音频
+#             import numpy as np
+#             concatenated_audio = np.concatenate(full_audio_data)
+            
+#             # 将拼接后的音频写入 WAV 文件
+#             buffer = io.BytesIO()
+#             sf.write(buffer, concatenated_audio, 24000, format='WAV')
+#             buffer.seek(0)
+#             audio_data = buffer.getvalue()
+            
+#             # 流式传输整个音频
+#             yield audio_data
+            
+#             # 保存到缓存
+#             with open(audio_path, "wb") as f:
+#                 f.write(audio_data)
+        
+#         except Exception as e:
+#             logger.error(f"TTS error: {str(e)}")
+#             raise HTTPException(status_code=500, detail=str(e))
+
+#     return StreamingResponse(audio_generator(), media_type="audio/wav")
+
+
+# # Page-to-speech endpoint (chunked streaming)
+# MAX_CHUNK_SIZE = 200
+
+# def split_text_into_chunks(text: str, max_chunk_size: int = MAX_CHUNK_SIZE) -> list:
+#     import re
+#     sentences = re.split('(?<=[.!?]) +', text)
+#     chunks = []
+#     current_chunk = ""
+#     for sentence in sentences:
+#         if len(current_chunk) + len(sentence) + 1 <= max_chunk_size:
+#             current_chunk += " " + sentence if current_chunk else sentence
+#         else:
+#             if current_chunk:
+#                 chunks.append(current_chunk)
+#             if len(sentence) > max_chunk_size:
+#                 for i in range(0, len(sentence), max_chunk_size):
+#                     chunks.append(sentence[i:i + max_chunk_size])
+#                 current_chunk = ""
+#             else:
+#                 current_chunk = sentence
+#     if current_chunk:
+#         chunks.append(current_chunk)
+#     return chunks
+
+# async def generate_kokoro_audio(chunk: str, voice: str, speed: float) -> AsyncGenerator[bytes, None]:
+#     text_hash = hashlib.md5(chunk.encode('utf-8')).hexdigest()
+#     audio_path = os.path.join(CACHE_DIR, f"{text_hash}.wav")
+    
+#     if os.path.exists(audio_path):
+#         with open(audio_path, "rb") as f:
+#             yield f.read()
+#     else:
+#         try:
+#             if not tts_server.pipeline:
+#                 raise HTTPException(status_code=503, detail="Model not initialized")
+            
+#             generator = tts_server.pipeline(
+#                 text=chunk,
+#                 voice=voice,
+#                 speed=speed,
+#                 split_pattern=r'\n+'
+#             )
+            
+#             full_audio_buffer = io.BytesIO()  # For caching
+#             for i, (gs, ps, audio) in enumerate(generator):
+#                 buffer = io.BytesIO()
+#                 sf.write(buffer, audio, 24000, format='WAV')
+#                 buffer.seek(0)
+#                 audio_data = buffer.getvalue()
+#                 yield audio_data  # Stream immediately
+#                 full_audio_buffer.write(audio_data)
+#                 break  # Take first segment (adjust if multiple segments needed)
+            
+#             # Cache the chunk
+#             full_audio_buffer.seek(0)
+#             with open(audio_path, "wb") as f:
+#                 f.write(full_audio_buffer.getvalue())
+#         except Exception as e:
+#             raise HTTPException(status_code=500, detail=f"TTS生成失败: {str(e)}")
+
+# @app.post("/page-to-speech/")
+# async def page_to_speech(request: TextToSpeechRequest):
+#     user_input = request.user_input.strip()
+#     if not user_input:
+#         raise HTTPException(status_code=400, detail="输入文本为空")
+    
+#     full_text_hash = hashlib.md5(user_input.encode('utf-8')).hexdigest()
+#     full_audio_path = os.path.join(CACHE_DIR, f"{full_text_hash}_full.wav")
+    
+#     if os.path.exists(full_audio_path):
+#         return StreamingResponse(open(full_audio_path, "rb"), media_type="audio/wav")
+    
+#     chunks = split_text_into_chunks(user_input)
+
+#     async def audio_generator() -> AsyncGenerator[bytes, None]:
+#         full_audio_buffer = io.BytesIO()  # For caching full audio
+#         for chunk in chunks:
+#             async for audio_data in generate_kokoro_audio(chunk, request.voice, request.speed):
+#                 yield audio_data  # Stream each chunk's audio
+#                 full_audio_buffer.write(audio_data)
+#             await asyncio.sleep(0)  # Yield control to event loop
+        
+#         # Save the full audio to cache
+#         full_audio_buffer.seek(0)
+#         with open(full_audio_path, "wb") as f:
+#             f.write(full_audio_buffer.getvalue())
+
+#     return StreamingResponse(audio_generator(), media_type="audio/wav")
+
+# # Health check
+# @app.get("/health")
+# async def health_check():
+#     return {"status": "healthy" if tts_server.pipeline else "model_not_loaded"}
+
+# if __name__ == "__main__":
+#     import uvicorn
+#     uvicorn.run(app, host="0.0.0.0", port=8005)
+
+
+from fastapi import FastAPI, Request, File, UploadFile, HTTPException, Form, Response
+from fastapi.middleware.cors import CORSMiddleware
+from fastapi.responses import JSONResponse, RedirectResponse, StreamingResponse
+from fastapi.staticfiles import StaticFiles
+import os
+import shutil
+import uuid
+from pydantic import BaseModel
+import hashlib
+import asyncio
+from typing import AsyncGenerator
+import soundfile as sf
+import io
+import logging
+import numpy as np
+import re
+from kokoro import KPipeline  # 假设 kokoro 已安装并可用
+
+# 设置日志
+logging.basicConfig(level=logging.INFO)
+logger = logging.getLogger(__name__)
+
+# 初始化 FastAPI 应用
+app = FastAPI()
+
+# 配置 CORS
+origins = ["*"]
+app.add_middleware(
+    CORSMiddleware,
+    allow_origins=origins,
+    allow_credentials=True,
+    allow_methods=["*"],
+    allow_headers=["*"],
+)
+
+# 上传文件目录
+UPLOAD_DIRECTORY = "static/files"
+if not os.path.exists(UPLOAD_DIRECTORY):
+    os.makedirs(UPLOAD_DIRECTORY)
+
+# 挂载静态文件
+app.mount("/static/files", StaticFiles(directory=UPLOAD_DIRECTORY), name="static_files")
+app.mount("/static/web", StaticFiles(directory="static/web"), name="static_web")
+app.mount("/static", StaticFiles(directory="static"), name="static")
+
+# 音频缓存目录
+CACHE_DIR = "audio_cache"
+os.makedirs(CACHE_DIR, exist_ok=True)
+
+# 根路径重定向到 PDF 查看器
+@app.get("/")
+def root():
+    return RedirectResponse(url="/static/web/viewer.html?file=/static/files/compress.pdf")
+
+# 清理文件名
+def sanitize_filename(name: str) -> str:
+    return "".join(c for c in name if c.isalnum() or c in (' ', '.', '_', '-')).rstrip()
+
+# PDF 上传端点
+@app.post("/upload-pdf")
+async def upload_pdf(file: UploadFile = File(...), custom_name: str = Form(...)):
+    if file.content_type != 'application/pdf':
+        raise HTTPException(status_code=400, detail="文件类型必须是 PDF")
+    sanitized_name = sanitize_filename(custom_name)
+    if not sanitized_name:
+        return JSONResponse(status_code=400, content={"success": False, "error": "无效的文件名"})
+
+    unique_filename = f"{sanitized_name}.pdf"
+    file_path = os.path.join(UPLOAD_DIRECTORY, unique_filename)
+
+    if os.path.exists(file_path):
+        return JSONResponse(status_code=400, content={"success": False, "error": "文件名已存在,请使用其他名称"})
+
+    try:
+        with open(file_path, "wb") as buffer:
+            shutil.copyfileobj(file.file, buffer)
+    except Exception as e:
+        raise HTTPException(status_code=500, detail="上传过程中出错")
+    finally:
+        file.file.close()
+
+    file_relative_path = f"/static/files/{unique_filename}"
+    return JSONResponse(content={"success": True, "file_path": file_relative_path})
+
+# 列出 PDF 文件端点
+@app.get("/list-pdfs")
+async def list_pdfs():
+    try:
+        files = os.listdir(UPLOAD_DIRECTORY)
+        pdf_files = [
+            {"name": file, "url": f"/static/files/{file}"}
+            for file in files if file.lower().endswith(".pdf")
+        ]
+        return JSONResponse(content={"success": True, "files": pdf_files})
+    except Exception as e:
+        raise HTTPException(status_code=500, detail="无法获取文件列表")
+
+# TTS 服务类
+class TextToSpeechServer:
+    def __init__(self):
+        self.pipeline = None
+
+    def load_model(self, lang_code='a'):
+        try:
+            logger.info("加载 KPipeline 模型...")
+            self.pipeline = KPipeline(lang_code=lang_code)
+            logger.info("模型加载成功")
+        except Exception as e:
+            logger.error(f"模型加载失败: {str(e)}")
+            raise
+
+# 初始化 TTS 服务
+tts_server = TextToSpeechServer()
+
+# 应用启动时加载 Kokoro 模型
+@app.on_event("startup")
+async def startup_event():
+    tts_server.load_model()
+
+# 请求模型
+class TextToSpeechRequest(BaseModel):
+    user_input: str
+    voice: str = 'af_heart'  # 默认语音
+    speed: float = 1.0       # 默认速度
+
+# 文本转语音端点(流式)
+@app.post("/text-to-speech/")
+async def text_to_speech(request: TextToSpeechRequest):
+    user_input = request.user_input.strip()
+    if not user_input:
+        raise HTTPException(status_code=400, detail="输入文本为空")
+
+    text_hash = hashlib.md5(user_input.encode('utf-8')).hexdigest()
+    audio_path = os.path.join(CACHE_DIR, f"{text_hash}.wav")
+
+    if os.path.exists(audio_path):
+        with open(audio_path, "rb") as f:
+            return Response(content=f.read(), media_type="audio/wav")
+
+    async def audio_generator() -> AsyncGenerator[bytes, None]:
+        try:
+            if not tts_server.pipeline:
+                raise HTTPException(status_code=503, detail="模型未初始化")
+            generator = tts_server.pipeline(
+                text=user_input,
+                voice=request.voice,
+                speed=request.speed,
+                split_pattern=r'\n+'
+            )
+
+            full_audio_data = []
+            for i, (gs, ps, audio) in enumerate(generator):
+                full_audio_data.append(audio)
+
+            concatenated_audio = np.concatenate(full_audio_data)
+            buffer = io.BytesIO()
+            sf.write(buffer, concatenated_audio, 24000, format='WAV')
+            buffer.seek(0)
+            audio_data = buffer.getvalue()
+
+            yield audio_data
+            with open(audio_path, "wb") as f:
+                f.write(audio_data)
+
+        except Exception as e:
+            logger.error(f"TTS 错误: {str(e)}")
+            raise HTTPException(status_code=500, detail=str(e))
+
+    return StreamingResponse(audio_generator(), media_type="audio/wav")
+
+# 按句子分割文本
+def split_text_into_sentences(text: str) -> list:
+    # 使用正则表达式按句号、问号、感叹号分割句子
+    sentences = re.split(r'(?<=[.!?])\s+', text.strip())
+    return [s.strip() for s in sentences if s.strip()]
+
+# 生成单句音频
+async def generate_kokoro_audio(chunk: str, voice: str, speed: float) -> AsyncGenerator[bytes, None]:
+    text_hash = hashlib.md5(chunk.encode('utf-8')).hexdigest()
+    audio_path = os.path.join(CACHE_DIR, f"{text_hash}.wav")
+
+    if os.path.exists(audio_path):
+        with open(audio_path, "rb") as f:
+            yield f.read()
+    else:
+        try:
+            if not tts_server.pipeline:
+                raise HTTPException(status_code=503, detail="模型未初始化")
+
+            generator = tts_server.pipeline(
+                text=chunk,
+                voice=voice,
+                speed=speed,
+                split_pattern=r'\n+'
+            )
+
+            full_audio_buffer = io.BytesIO()
+            for i, (gs, ps, audio) in enumerate(generator):
+                buffer = io.BytesIO()
+                sf.write(buffer, audio, 24000, format='WAV')
+                buffer.seek(0)
+                audio_data = buffer.getvalue()
+                yield audio_data
+                full_audio_buffer.write(audio_data)
+                break  # 仅取第一个片段
+
+            full_audio_buffer.seek(0)
+            with open(audio_path, "wb") as f:
+                f.write(full_audio_buffer.getvalue())
+        except Exception as e:
+            raise HTTPException(status_code=500, detail=f"TTS生成失败: {str(e)}")
+
+# 页面转语音端点(按句子逐句转换并播放)
+@app.post("/page-to-speech/")
+async def page_to_speech(request: TextToSpeechRequest):
+    user_input = request.user_input.strip()
+    if not user_input:
+        raise HTTPException(status_code=400, detail="输入文本为空")
+
+    full_text_hash = hashlib.md5(user_input.encode('utf-8')).hexdigest()
+    full_audio_path = os.path.join(CACHE_DIR, f"{full_text_hash}_full.wav")
+
+    if os.path.exists(full_audio_path):
+        return StreamingResponse(open(full_audio_path, "rb"), media_type="audio/wav")
+
+    sentences = split_text_into_sentences(user_input)
+    if not sentences:
+        raise HTTPException(status_code=400, detail="没有有效的句子")
+
+    async def audio_generator() -> AsyncGenerator[bytes, None]:
+        full_audio_buffer = io.BytesIO()  # 用于缓存完整音频
+        for sentence in sentences:
+            logger.info(f"处理句子: {sentence}")
+            async for audio_data in generate_kokoro_audio(sentence, request.voice, request.speed):
+                yield audio_data  # 立即流式传输当前句子的音频
+                full_audio_buffer.write(audio_data)
+            await asyncio.sleep(0)  # 让出控制权给事件循环
+
+        # 保存完整音频到缓存
+        full_audio_buffer.seek(0)
+        with open(full_audio_path, "wb") as f:
+            f.write(full_audio_buffer.getvalue())
+
+    return StreamingResponse(audio_generator(), media_type="audio/wav")
+
+# 健康检查
+@app.get("/health")
+async def health_check():
+    return {"status": "healthy" if tts_server.pipeline else "model_not_loaded"}
+
+if __name__ == "__main__":
+    import uvicorn
+    uvicorn.run(app, host="0.0.0.0", port=8005)

+ 244 - 0
main2-old.py

@@ -0,0 +1,244 @@
+from fastapi import FastAPI, Request,File, UploadFile, HTTPException,Form, Response
+from fastapi.middleware.cors import CORSMiddleware
+from fastapi.responses import JSONResponse
+from fastapi.responses import  RedirectResponse
+from fastapi.responses import StreamingResponse
+from fastapi.staticfiles import StaticFiles
+import os
+import shutil
+import uuid
+from openai import OpenAI
+from pydantic import BaseModel
+import hashlib
+import asyncio
+from typing import AsyncGenerator
+from pydub import AudioSegment  # 用于音频处理
+
+
+app = FastAPI()
+# 配置允许的跨域源,* 表示允许所有
+origins = [
+    "*",
+    # 若要限制特定域名,可以在这里添加,例如:
+    # "http://localhost",
+    # "http://localhost:8000",
+]
+app.add_middleware(
+    CORSMiddleware,
+    allow_origins=origins,  # 允许的来源
+    allow_credentials=True,
+    allow_methods=["*"],    # 允许的方法
+    allow_headers=["*"],    # 允许的请求头
+)
+
+# 指定上传文件保存的目录
+UPLOAD_DIRECTORY = "static/files"
+
+if not os.path.exists(UPLOAD_DIRECTORY):
+    os.makedirs(UPLOAD_DIRECTORY)
+
+# 配置静态文件服务,使上传的PDF可以通过URL访问
+app.mount("/static/files", StaticFiles(directory=UPLOAD_DIRECTORY), name="static_files")
+app.mount("/static/web", StaticFiles(directory="static/web"), name="static_web")  # 假设 viewer.html 在 static/web
+
+# 挂载静态文件
+app.mount("/static", StaticFiles(directory="static"), name="static")
+# 根路径重定向到 PDF.js viewer
+@app.get("/")
+def root():
+    return RedirectResponse(url="/static/web/viewer.html?file=/static/files/compress.pdf")
+# 如果需要自定义 PDF 文件上传或动态渲染,可以在此添加更多路由
+
+def sanitize_filename(name: str) -> str:
+    return "".join(c for c in name if c.isalnum() or c in (' ', '.', '_', '-')).rstrip()
+
+@app.post("/upload-pdf")
+async def upload_pdf(file: UploadFile = File(...), custom_name: str = Form(...)):
+    if file.content_type != 'application/pdf':
+        raise HTTPException(status_code=400, detail="文件类型必须是 PDF")
+    # 清理文件名
+    sanitized_name = sanitize_filename(custom_name)
+    if not sanitized_name:
+        return JSONResponse(status_code=400, content={"success": False, "error": "无效的文件名"})
+    
+    # 添加 .pdf 扩展名
+    unique_filename = f"{sanitized_name}.pdf"
+    file_path = os.path.join(UPLOAD_DIRECTORY, unique_filename)
+
+    # 如果文件已存在,添加 UUID 以确保唯一性
+    if os.path.exists(file_path):
+        return JSONResponse(status_code=400, content={"success": False, "error": "文件名已存在,请使用其他名称"})
+
+    try:
+        with open(file_path, "wb") as buffer:
+            shutil.copyfileobj(file.file, buffer)
+    except Exception as e:
+        raise HTTPException(status_code=500, detail="上传过程中出错")
+    finally:
+        file.file.close()
+
+    # 构建文件的相对路径
+    file_relative_path = f"/static/files/{unique_filename}"
+
+    return JSONResponse(content={"success": True, "file_path": file_relative_path})
+
+@app.get("/list-pdfs")
+async def list_pdfs():
+    try:
+        files = os.listdir(UPLOAD_DIRECTORY)
+        # 过滤出PDF文件并构建可访问的URL
+        pdf_files = [
+            {
+                "name": file,
+                "url": f"/static/files/{file}"
+            }
+            for file in files if file.lower().endswith(".pdf")
+        ]
+        return JSONResponse(content={"success": True, "files": pdf_files})
+    except Exception as e:
+        raise HTTPException(status_code=500, detail="无法获取文件列表")
+
+class TextToSpeechRequest(BaseModel):
+    user_input: str
+
+
+# 配置OpenAI客户端
+api_key = "sk-bpaahUHgzoriWpjV24524eC7BbBf47D5A4Ce59EbFdB57f35"  # 请确保使用环境变量存储API密钥
+client = OpenAI(
+    base_url="https://api.wlai.vip/v1",
+    api_key=api_key
+)
+
+# 音频缓存目录
+CACHE_DIR = "audio_cache"
+os.makedirs(CACHE_DIR, exist_ok=True)
+
+@app.post("/text-to-speech/")
+async def text_to_speech(request: TextToSpeechRequest):
+    user_input = request.user_input
+    try:
+        # 生成文本的hash值作为缓存文件名
+        # print(user_input)
+        text_hash = hashlib.md5(user_input.encode('utf-8')).hexdigest()
+        audio_path = os.path.join(CACHE_DIR, f"{text_hash}.mp3")
+
+        if os.path.exists(audio_path):
+            # 如果缓存存在,直接返回缓存的音频
+            # print("have")
+            with open(audio_path, "rb") as f:
+                audio_data = f.read()
+            return Response(content=audio_data, media_type="audio/mpeg")
+        else:
+            # 如果缓存不存在,调用OpenAI API生成音频
+            with client.audio.speech.with_streaming_response.create(
+                model="tts-1",
+                voice="nova", 
+                input=user_input,
+            ) as response:
+                response.stream_to_file(audio_path)
+
+            with open(audio_path, "rb") as f:
+                audio_data = f.read()
+            return Response(content=audio_data, media_type="audio/mpeg")
+    except Exception as e:
+        raise HTTPException(status_code=500, detail=str(e))
+
+# 整页阅读,分块
+# 最大字符数,以根据需求调整
+MAX_CHUNK_SIZE = 200  # 每个块的最大字符数
+
+def split_text_into_chunks(text: str, max_chunk_size: int = MAX_CHUNK_SIZE) -> list:
+    """
+    将文本分割成不超过 max_chunk_size 个字符的块。
+    尝试在句号、感叹号或问号处断开,以避免中间断句。
+    """
+    import re
+
+    sentences = re.split('(?<=[.!?]) +', text)
+    chunks = []
+    current_chunk = ""
+
+    for sentence in sentences:
+        if len(current_chunk) + len(sentence) + 1 <= max_chunk_size:
+            current_chunk += " " + sentence if current_chunk else sentence
+        else:
+            if current_chunk:
+                chunks.append(current_chunk)
+            if len(sentence) > max_chunk_size:
+                # 如果单个句子超过最大长度,强制分割
+                for i in range(0, len(sentence), max_chunk_size):
+                    chunks.append(sentence[i:i + max_chunk_size])
+                current_chunk = ""
+            else:
+                current_chunk = sentence
+
+    if current_chunk:
+        chunks.append(current_chunk)
+
+    return chunks
+
+async def generate_tts_audio(chunk: str) -> str:
+    """
+    生成给定文本块的语音音频,并缓存到文件系统中。
+    返回音频文件的路径。
+    """
+    text_hash = hashlib.md5(chunk.encode('utf-8')).hexdigest()
+    audio_path = os.path.join(CACHE_DIR, f"{text_hash}.mp3")
+
+    if not os.path.exists(audio_path):
+        try:
+            with client.audio.speech.with_streaming_response.create(
+                model="tts-1",
+                voice="nova",
+                input=chunk,
+            ) as response:
+                response.stream_to_file(audio_path)
+        except Exception as e:
+            raise HTTPException(status_code=500, detail=f"TTS生成失败: {str(e)}")
+
+    return audio_path
+
+def concatenate_audios(audio_paths: list, output_path: str) -> None:
+    """
+    将多个音频文件按顺序拼接成一个音频文件。
+    """
+    combined = AudioSegment.empty()
+    for path in audio_paths:
+        audio = AudioSegment.from_mp3(path)
+        combined += audio
+    combined.export(output_path, format="mp3")
+
+@app.post("/page-to-speech/")
+async def page_to_speech(request: TextToSpeechRequest):
+    user_input = request.user_input.strip()
+    if not user_input:
+        raise HTTPException(status_code=400, detail="输入文本为空。")
+
+    # 生成整个文本的hash值作为整体缓存文件名
+    full_text_hash = hashlib.md5(user_input.encode('utf-8')).hexdigest()
+    full_audio_path = os.path.join(CACHE_DIR, f"{full_text_hash}_full.mp3")
+
+    if os.path.exists(full_audio_path):
+        # 如果整体缓存存在,直接返回
+        return StreamingResponse(open(full_audio_path, "rb"), media_type="audio/mpeg")
+
+    # 分割文本为多个块
+    chunks = split_text_into_chunks(user_input)
+    audio_paths = []
+
+    async def audio_generator() -> AsyncGenerator[bytes, None]:
+        for chunk in chunks:
+            audio_path = await generate_tts_audio(chunk)
+            audio_paths.append(audio_path)
+            with open(audio_path, "rb") as f:
+                yield f.read()
+            await asyncio.sleep(0)  # 让事件循环有机会处理其它任务
+
+    # 异步生成并缓存整体音频
+    async def create_full_audio():
+        await asyncio.gather(*(generate_tts_audio(chunk) for chunk in chunks))
+        concatenate_audios(audio_paths, full_audio_path)
+
+    asyncio.create_task(create_full_audio())
+
+    return StreamingResponse(audio_generator(), media_type="audio/mpeg")

+ 253 - 0
main_kokoro.py

@@ -0,0 +1,253 @@
+from fastapi import FastAPI, Request, File, UploadFile, HTTPException, Form, Response
+from fastapi.middleware.cors import CORSMiddleware
+from fastapi.responses import JSONResponse, RedirectResponse, StreamingResponse
+from fastapi.staticfiles import StaticFiles
+import os
+import shutil
+import uuid
+from pydantic import BaseModel
+import hashlib
+import asyncio
+from typing import AsyncGenerator
+import soundfile as sf
+import io
+import logging
+import numpy as np
+import re
+from kokoro import KPipeline  # 假设 kokoro 已安装并可用
+
+# 设置日志
+logging.basicConfig(level=logging.INFO)
+logger = logging.getLogger(__name__)
+
+# 初始化 FastAPI 应用
+app = FastAPI()
+
+# 配置 CORS
+origins = ["*"]
+app.add_middleware(
+    CORSMiddleware,
+    allow_origins=origins,
+    allow_credentials=True,
+    allow_methods=["*"],
+    allow_headers=["*"],
+)
+
+# 上传文件目录
+UPLOAD_DIRECTORY = "static/files"
+if not os.path.exists(UPLOAD_DIRECTORY):
+    os.makedirs(UPLOAD_DIRECTORY)
+
+# 挂载静态文件
+app.mount("/static/files", StaticFiles(directory=UPLOAD_DIRECTORY), name="static_files")
+app.mount("/static/web", StaticFiles(directory="static/web"), name="static_web")
+app.mount("/static", StaticFiles(directory="static"), name="static")
+
+# 音频缓存目录
+CACHE_DIR = "audio_cache"
+os.makedirs(CACHE_DIR, exist_ok=True)
+
+# 根路径重定向到 PDF 查看器
+@app.get("/")
+def root():
+    return RedirectResponse(url="/static/web/viewer.html?file=/static/files/compress.pdf")
+
+# 清理文件名
+def sanitize_filename(name: str) -> str:
+    return "".join(c for c in name if c.isalnum() or c in (' ', '.', '_', '-')).rstrip()
+
+# PDF 上传端点
+@app.post("/upload-pdf")
+async def upload_pdf(file: UploadFile = File(...), custom_name: str = Form(...)):
+    if file.content_type != 'application/pdf':
+        raise HTTPException(status_code=400, detail="文件类型必须是 PDF")
+    sanitized_name = sanitize_filename(custom_name)
+    if not sanitized_name:
+        return JSONResponse(status_code=400, content={"success": False, "error": "无效的文件名"})
+
+    unique_filename = f"{sanitized_name}.pdf"
+    file_path = os.path.join(UPLOAD_DIRECTORY, unique_filename)
+
+    if os.path.exists(file_path):
+        return JSONResponse(status_code=400, content={"success": False, "error": "文件名已存在,请使用其他名称"})
+
+    try:
+        with open(file_path, "wb") as buffer:
+            shutil.copyfileobj(file.file, buffer)
+    except Exception as e:
+        raise HTTPException(status_code=500, detail="上传过程中出错")
+    finally:
+        file.file.close()
+
+    file_relative_path = f"/static/files/{unique_filename}"
+    return JSONResponse(content={"success": True, "file_path": file_relative_path})
+
+# 列出 PDF 文件端点
+@app.get("/list-pdfs")
+async def list_pdfs():
+    try:
+        files = os.listdir(UPLOAD_DIRECTORY)
+        pdf_files = [
+            {"name": file, "url": f"/static/files/{file}"}
+            for file in files if file.lower().endswith(".pdf")
+        ]
+        return JSONResponse(content={"success": True, "files": pdf_files})
+    except Exception as e:
+        raise HTTPException(status_code=500, detail="无法获取文件列表")
+
+# TTS 服务类
+class TextToSpeechServer:
+    def __init__(self):
+        self.pipeline = None
+
+    def load_model(self, lang_code='a'):
+        try:
+            logger.info("加载 KPipeline 模型...")
+            self.pipeline = KPipeline(lang_code=lang_code)
+            logger.info("模型加载成功")
+        except Exception as e:
+            logger.error(f"模型加载失败: {str(e)}")
+            raise
+
+# 初始化 TTS 服务
+tts_server = TextToSpeechServer()
+
+# 应用启动时加载 Kokoro 模型
+@app.on_event("startup")
+async def startup_event():
+    tts_server.load_model()
+
+# 请求模型
+class TextToSpeechRequest(BaseModel):
+    user_input: str
+    voice: str = 'af_heart'  # 默认语音
+    speed: float = 1.0       # 默认速度
+
+# 文本转语音端点(流式)
+@app.post("/text-to-speech/")
+async def text_to_speech(request: TextToSpeechRequest):
+    user_input = request.user_input.strip()
+    if not user_input:
+        raise HTTPException(status_code=400, detail="输入文本为空")
+
+    text_hash = hashlib.md5(user_input.encode('utf-8')).hexdigest()
+    audio_path = os.path.join(CACHE_DIR, f"{text_hash}.wav")
+
+    if os.path.exists(audio_path):
+        with open(audio_path, "rb") as f:
+            return Response(content=f.read(), media_type="audio/wav")
+
+    async def audio_generator() -> AsyncGenerator[bytes, None]:
+        try:
+            if not tts_server.pipeline:
+                raise HTTPException(status_code=503, detail="模型未初始化")
+            generator = tts_server.pipeline(
+                text=user_input,
+                voice=request.voice,
+                speed=request.speed,
+                split_pattern=r'\n+'
+            )
+
+            full_audio_data = []
+            for i, (gs, ps, audio) in enumerate(generator):
+                full_audio_data.append(audio)
+
+            concatenated_audio = np.concatenate(full_audio_data)
+            buffer = io.BytesIO()
+            sf.write(buffer, concatenated_audio, 24000, format='WAV')
+            buffer.seek(0)
+            audio_data = buffer.getvalue()
+
+            yield audio_data
+            with open(audio_path, "wb") as f:
+                f.write(audio_data)
+
+        except Exception as e:
+            logger.error(f"TTS 错误: {str(e)}")
+            raise HTTPException(status_code=500, detail=str(e))
+
+    return StreamingResponse(audio_generator(), media_type="audio/wav")
+
+# 按句子分割文本
+def split_text_into_sentences(text: str) -> list:
+    # 使用正则表达式按句号、问号、感叹号分割句子
+    sentences = re.split(r'(?<=[.!?])\s+', text.strip())
+    return [s.strip() for s in sentences if s.strip()]
+
+# 生成单句音频
+async def generate_kokoro_audio(chunk: str, voice: str, speed: float) -> AsyncGenerator[bytes, None]:
+    text_hash = hashlib.md5(chunk.encode('utf-8')).hexdigest()
+    audio_path = os.path.join(CACHE_DIR, f"{text_hash}.wav")
+
+    if os.path.exists(audio_path):
+        with open(audio_path, "rb") as f:
+            yield f.read()
+    else:
+        try:
+            if not tts_server.pipeline:
+                raise HTTPException(status_code=503, detail="模型未初始化")
+
+            generator = tts_server.pipeline(
+                text=chunk,
+                voice=voice,
+                speed=speed,
+                split_pattern=r'\n+'
+            )
+
+            full_audio_buffer = io.BytesIO()
+            for i, (gs, ps, audio) in enumerate(generator):
+                buffer = io.BytesIO()
+                sf.write(buffer, audio, 24000, format='WAV')
+                buffer.seek(0)
+                audio_data = buffer.getvalue()
+                yield audio_data
+                full_audio_buffer.write(audio_data)
+                break  # 仅取第一个片段
+
+            full_audio_buffer.seek(0)
+            with open(audio_path, "wb") as f:
+                f.write(full_audio_buffer.getvalue())
+        except Exception as e:
+            raise HTTPException(status_code=500, detail=f"TTS生成失败: {str(e)}")
+
+# 页面转语音端点(按句子逐句转换并播放)
+@app.post("/page-to-speech/")
+async def page_to_speech(request: TextToSpeechRequest):
+    user_input = request.user_input.strip()
+    if not user_input:
+        raise HTTPException(status_code=400, detail="输入文本为空")
+
+    full_text_hash = hashlib.md5(user_input.encode('utf-8')).hexdigest()
+    full_audio_path = os.path.join(CACHE_DIR, f"{full_text_hash}_full.wav")
+
+    if os.path.exists(full_audio_path):
+        return StreamingResponse(open(full_audio_path, "rb"), media_type="audio/wav")
+
+    sentences = split_text_into_sentences(user_input)
+    if not sentences:
+        raise HTTPException(status_code=400, detail="没有有效的句子")
+
+    async def audio_generator() -> AsyncGenerator[bytes, None]:
+        full_audio_buffer = io.BytesIO()  # 用于缓存完整音频
+        for sentence in sentences:
+            logger.info(f"处理句子: {sentence}")
+            async for audio_data in generate_kokoro_audio(sentence, request.voice, request.speed):
+                yield audio_data  # 立即流式传输当前句子的音频
+                full_audio_buffer.write(audio_data)
+            await asyncio.sleep(0)  # 让出控制权给事件循环
+
+        # 保存完整音频到缓存
+        full_audio_buffer.seek(0)
+        with open(full_audio_path, "wb") as f:
+            f.write(full_audio_buffer.getvalue())
+
+    return StreamingResponse(audio_generator(), media_type="audio/wav")
+
+# 健康检查
+@app.get("/health")
+async def health_check():
+    return {"status": "healthy" if tts_server.pipeline else "model_not_loaded"}
+
+if __name__ == "__main__":
+    import uvicorn
+    uvicorn.run(app, host="0.0.0.0", port=8005)

+ 299 - 0
main_server.py

@@ -0,0 +1,299 @@
+from fastapi import FastAPI, Request, File, UploadFile, HTTPException, Form, Response
+from fastapi.middleware.cors import CORSMiddleware
+from fastapi.responses import JSONResponse, RedirectResponse, StreamingResponse
+from fastapi.staticfiles import StaticFiles
+import os
+import shutil
+import hashlib
+import asyncio
+from typing import AsyncGenerator
+import aiohttp
+import io
+import logging
+import base64
+import json
+
+# Set up logging
+logging.basicConfig(level=logging.INFO)
+logger = logging.getLogger(__name__)
+
+# Initialize FastAPI app
+app = FastAPI()
+
+# Configure CORS
+origins = ["*"]
+app.add_middleware(
+    CORSMiddleware,
+    allow_origins=origins,
+    allow_credentials=True,
+    allow_methods=["*"],
+    allow_headers=["*"],
+)
+
+# Directory for uploaded files
+UPLOAD_DIRECTORY = "static/files"
+if not os.path.exists(UPLOAD_DIRECTORY):
+    os.makedirs(UPLOAD_DIRECTORY)
+
+# Mount static files
+app.mount("/static/files", StaticFiles(directory=UPLOAD_DIRECTORY), name="static_files")
+app.mount("/static/web", StaticFiles(directory="static/web"), name="static_web")
+app.mount("/static", StaticFiles(directory="static"), name="static")
+
+# Audio cache directory
+CACHE_DIR = "audio_cache"
+os.makedirs(CACHE_DIR, exist_ok=True)
+
+# Root redirect to PDF viewer
+@app.get("/")
+def root():
+    return RedirectResponse(url="/static/web/viewer.html?file=/static/files/compress.pdf")
+
+# Sanitize filename
+def sanitize_filename(name: str) -> str:
+    return "".join(c for c in name if c.isalnum() or c in (' ', '.', '_', '-')).rstrip()
+
+# PDF upload endpoint
+@app.post("/upload-pdf")
+async def upload_pdf(file: UploadFile = File(...), custom_name: str = Form(...)):
+    if file.content_type != 'application/pdf':
+        raise HTTPException(status_code=400, detail="文件类型必须是 PDF")
+    sanitized_name = sanitize_filename(custom_name)
+    if not sanitized_name:
+        return JSONResponse(status_code=400, content={"success": False, "error": "无效的文件名"})
+    
+    unique_filename = f"{sanitized_name}.pdf"
+    file_path = os.path.join(UPLOAD_DIRECTORY, unique_filename)
+
+    if os.path.exists(file_path):
+        return JSONResponse(status_code=400, content={"success": False, "error": "文件名已存在,请使用其他名称"})
+
+    try:
+        with open(file_path, "wb") as buffer:
+            shutil.copyfileobj(file.file, buffer)
+    except Exception as e:
+        raise HTTPException(status_code=500, detail="上传过程中出错")
+    finally:
+        file.file.close()
+
+    file_relative_path = f"/static/files/{unique_filename}"
+    return JSONResponse(content={"success": True, "file_path": file_relative_path})
+
+# List PDFs endpoint
+@app.get("/list-pdfs")
+async def list_pdfs():
+    try:
+        files = os.listdir(UPLOAD_DIRECTORY)
+        pdf_files = [
+            {"name": file, "url": f"/static/files/{file}"}
+            for file in files if file.lower().endswith(".pdf")
+        ]
+        return JSONResponse(content={"success": True, "files": pdf_files})
+    except Exception as e:
+        raise HTTPException(status_code=500, detail="无法获取文件列表")
+
+# Request models
+from pydantic import BaseModel
+class TextToSpeechRequest(BaseModel):
+    user_input: str
+    voice: str = 'af_heart'  # Default voice
+    speed: float = 1.0       # Default speed
+
+# Text-to-speech endpoint (streaming)
+@app.post("/text-to-speech/")
+async def text_to_speech(request: TextToSpeechRequest):
+    user_input = request.user_input.strip()
+    if not user_input:
+        raise HTTPException(status_code=400, detail="输入文本为空")
+    
+    text_hash = hashlib.md5(user_input.encode('utf-8')).hexdigest()
+    audio_path = os.path.join(CACHE_DIR, f"{text_hash}.wav")
+
+    if os.path.exists(audio_path):
+        with open(audio_path, "rb") as f:
+            return Response(content=f.read(), media_type="audio/wav")
+    
+    async def audio_generator() -> AsyncGenerator[bytes, None]:
+        try:
+            async with aiohttp.ClientSession() as session:
+                async with session.post(
+                    'http://141.140.15.30:8028/generate',
+                    headers={'Content-Type': 'application/json'},
+                    json={
+                        'text': user_input,
+                        'voice': request.voice,
+                        'speed': request.speed
+                    }
+                ) as response:
+                    if response.status != 200:
+                        raise HTTPException(status_code=500, detail="TTS API 请求失败")
+                    
+                    # Read NDJSON response
+                    buffer = ""
+                    full_audio = io.BytesIO()
+                    async for chunk in response.content.iter_any():
+                        buffer += chunk.decode('utf-8')
+                        lines = buffer.split('\n')
+                        buffer = lines[-1]  # Keep incomplete line
+                        
+                        for line in lines[:-1]:
+                            if not line.strip():
+                                continue
+                            try:
+                                data = json.loads(line)
+                                if data.get('error'):
+                                    raise HTTPException(status_code=500, detail=data['error'])
+                                audio_b64 = data.get('audio')
+                                if audio_b64:
+                                    audio_bytes = base64.b64decode(audio_b64)
+                                    full_audio.write(audio_bytes)
+                                    yield audio_bytes
+                            except json.JSONDecodeError as e:
+                                logger.error(f"JSON decode error: {str(e)}")
+                                continue
+                    
+                    # Handle final buffer
+                    if buffer.strip():
+                        try:
+                            data = json.loads(buffer)
+                            if data.get('audio'):
+                                audio_bytes = base64.b64decode(data['audio'])
+                                full_audio.write(audio_bytes)
+                                yield audio_bytes
+                        except json.JSONDecodeError:
+                            pass
+                    
+                    # Save to cache
+                    full_audio.seek(0)
+                    with open(audio_path, "wb") as f:
+                        f.write(full_audio.getvalue())
+        
+        except Exception as e:
+            logger.error(f"TTS error: {str(e)}")
+            raise HTTPException(status_code=500, detail=str(e))
+
+    return StreamingResponse(audio_generator(), media_type="audio/wav")
+
+# Page-to-speech endpoint (chunked streaming)
+MAX_CHUNK_SIZE = 200
+
+def split_text_into_chunks(text: str, max_chunk_size: int = MAX_CHUNK_SIZE) -> list:
+    import re
+    sentences = re.split('(?<=[.!?]) +', text)
+    chunks = []
+    current_chunk = ""
+    for sentence in sentences:
+        if len(current_chunk) + len(sentence) + 1 <= max_chunk_size:
+            current_chunk += " " + sentence if current_chunk else sentence
+        else:
+            if current_chunk:
+                chunks.append(current_chunk)
+            if len(sentence) > max_chunk_size:
+                for i in range(0, len(sentence), max_chunk_size):
+                    chunks.append(sentence[i:i + max_chunk_size])
+                current_chunk = ""
+            else:
+                current_chunk = sentence
+    if current_chunk:
+        chunks.append(current_chunk)
+    return chunks
+
+async def generate_api_audio(chunk: str, voice: str, speed: float) -> AsyncGenerator[bytes, None]:
+    text_hash = hashlib.md5(chunk.encode('utf-8')).hexdigest()
+    audio_path = os.path.join(CACHE_DIR, f"{text_hash}.wav")
+    
+    if os.path.exists(audio_path):
+        with open(audio_path, "rb") as f:
+            yield f.read()
+    else:
+        try:
+            async with aiohttp.ClientSession() as session:
+                async with session.post(
+                    'http://141.140.15.30:8028/generate',
+                    headers={'Content-Type': 'application/json'},
+                    json={
+                        'text': chunk,
+                        'voice': voice,
+                        'speed': speed
+                    }
+                ) as response:
+                    if response.status != 200:
+                        raise HTTPException(status_code=500, detail="TTS API 请求失败")
+                    
+                    # Read NDJSON response
+                    buffer = ""
+                    async for chunk in response.content.iter_any():
+                        buffer += chunk.decode('utf-8')
+                        lines = buffer.split('\n')
+                        buffer = lines[-1]
+                        
+                        for line in lines[:-1]:
+                            if not line.strip():
+                                continue
+                            try:
+                                data = json.loads(line)
+                                if data.get('error'):
+                                    raise HTTPException(status_code=500, detail=data['error'])
+                                audio_b64 = data.get('audio')
+                                if audio_b64:
+                                    audio_bytes = base64.b64decode(audio_b64)
+                                    yield audio_bytes
+                                    # Cache the chunk
+                                    with open(audio_path, "wb") as f:
+                                        f.write(audio_bytes)
+                            except json.JSONDecodeError as e:
+                                logger.error(f"JSON decode error: {str(e)}")
+                                continue
+                    
+                    # Handle final buffer
+                    if buffer.strip():
+                        try:
+                            data = json.loads(buffer)
+                            if data.get('audio'):
+                                audio_bytes = base64.b64decode(data['audio'])
+                                yield audio_bytes
+                                with open(audio_path, "wb") as f:
+                                    f.write(audio_bytes)
+                        except json.JSONDecodeError:
+                            pass
+        
+        except Exception as e:
+            raise HTTPException(status_code=500, detail=f"TTS生成失败: {str(e)}")
+
+@app.post("/page-to-speech/")
+async def page_to_speech(request: TextToSpeechRequest):
+    user_input = request.user_input.strip()
+    if not user_input:
+        raise HTTPException(status_code=400, detail="输入文本为空")
+    
+    full_text_hash = hashlib.md5(user_input.encode('utf-8')).hexdigest()
+    full_audio_path = os.path.join(CACHE_DIR, f"{full_text_hash}_full.wav")
+    
+    if os.path.exists(full_audio_path):
+        return StreamingResponse(open(full_audio_path, "rb"), media_type="audio/wav")
+    
+    chunks = split_text_into_chunks(user_input)
+
+    async def audio_generator() -> AsyncGenerator[bytes, None]:
+        full_audio_buffer = io.BytesIO()  # For caching full audio
+        for chunk in chunks:
+            async for audio_data in generate_api_audio(chunk, request.voice, request.speed):
+                yield audio_data  # Stream each chunk's audio
+                full_audio_buffer.write(audio_data)
+            await asyncio.sleep(0)  # Yield control to event loop
+        
+        # Save the full audio to cache
+        full_audio_buffer.seek(0)
+        with open(full_audio_path, "wb") as f:
+            f.write(full_audio_buffer.getvalue())
+
+    return StreamingResponse(audio_generator(), media_type="audio/wav")
+
+# Health check
+@app.get("/health")
+async def health_check():
+    return {"status": "healthy"}
+
+if __name__ == "__main__":
+    import uvicorn
+    uvicorn.run(app, host="0.0.0.0", port=8005)

+ 1 - 0
start.sh

@@ -0,0 +1 @@
+nohup uvicorn main_server:app --host 0.0.0.0 --port 8005  &

File diff suppressed because it is too large
+ 25344 - 0
static/build/pdf.mjs


File diff suppressed because it is too large
+ 0 - 0
static/build/pdf.mjs.map


File diff suppressed because it is too large
+ 3638 - 0
static/build/pdf.sandbox.mjs


File diff suppressed because it is too large
+ 0 - 0
static/build/pdf.sandbox.mjs.map


File diff suppressed because it is too large
+ 8016 - 0
static/build/pdf.worker.mjs


File diff suppressed because it is too large
+ 0 - 0
static/build/pdf.worker.mjs.map


+ 1111 - 0
static/web/bak/viewer.html.bak

@@ -0,0 +1,1111 @@
+<!DOCTYPE html>
+
+<html dir="ltr" mozdisallowselectionprint>
+
+<head>
+  <meta charset="utf-8">
+  <meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=1">
+  <meta name="google" content="notranslate">
+  <title>PDF.js viewer</title>
+
+  <!-- This snippet is used in production (included from viewer.html) -->
+  <link rel="resource" type="application/l10n" href="locale/locale.json">
+  <script src="../build/pdf.mjs" type="module"></script>
+
+  <link rel="stylesheet" href="viewer.css">
+
+  <script src="viewer.mjs" type="module"></script>
+
+  <style>
+    /* 模态窗口样式 */
+    .modal {
+      display: none;
+      /* 默认隐藏 */
+      position: fixed;
+      z-index: 1002;
+      left: 50%;
+      top: 50%;
+      transform: translate(-50%, -50%);
+      width: 500px;
+      background-color: white;
+      border: 1px solid #ccc;
+      border-radius: 5px;
+      padding: 20px;
+      box-shadow: 0 5px 15px rgba(0, 0, 0, 0.3);
+    }
+
+    /* 遮罩层样式 */
+    .modal-overlay {
+      display: none;
+      /* 默认隐藏 */
+      position: fixed;
+      top: 0;
+      left: 0;
+      width: 100%;
+      height: 100%;
+      background-color: rgba(0, 0, 0, 0.5);
+      z-index: 1001;
+    }
+
+    /* 按钮样式 */
+    .button {
+      padding: 10px 20px;
+      background-color: #007bff;
+      color: white;
+      border: none;
+      border-radius: 5px;
+      cursor: pointer;
+    }
+
+    .button-secondary {
+      background-color: #6c757d;
+    }
+
+    .button-success {
+      background-color: #28a745;
+    }
+
+    .button-danger {
+      background-color: #dc3545;
+    }
+  </style>
+
+</head>
+
+<body tabindex="0">
+  <audio id="audio-player" controls style="position: fixed; width: 190px;height: 50px; bottom: 10px; left: 10px; z-index: 1000;"></audio>
+  <!-- 上传按钮定位在页面底部 -->
+  <button id="uploadButton" style="
+    position: fixed;
+    bottom: 20px;
+    right: 20px;
+    z-index: 1000;
+    padding: 10px 20px;
+    background-color: #007bff;
+    color: white;
+    border: none;
+    border-radius: 5px;
+    cursor: pointer;">
+    上传 PDF
+  </button>
+
+
+  <!-- 隐藏的文件输入 -->
+  <input type="file" id="pdfInput" accept="application/pdf" style="display: none;" />
+
+  <!-- 模态窗口 -->
+  <div id="filenameModal" class="modal">
+    <h3>输入保存的文件名</h3>
+    <input type="text" id="customFileName" placeholder="不含扩展名" style="width: 100%; padding: 5px; margin-top: 10px;" />
+    <div style="margin-top: 20px; text-align: right;">
+      <button id="cancelUpload" class="button button-secondary">取消</button>
+      <button id="confirmUpload" class="button button-success" style="margin-left: 10px;">确认</button>
+    </div>
+  </div>
+
+  <!-- 页面遮罩 -->
+  <div id="modalOverlay" class="modal-overlay"></div>
+
+  <!-- 打开目录按钮 -->
+  <button id="openDirectoryButton" class="button button-success" style="
+      position: fixed;
+      bottom: 20px;
+      right: 130px;  
+      z-index: 1000;
+      ">打开目录
+  </button>
+
+  <!-- 目录弹出层 -->
+  <div id="directoryModal" class="modal">
+    <h2>已上传的 PDF 文档</h2>
+    <ul id="pdfList" style="list-style-type: none; padding: 0;"></ul>
+    <button id="closeDirectoryButton" class="button button-danger" style="margin-top: 20px; width: 100%;">关闭</button>
+  </div>
+
+
+  <div id="sidebarContainer">
+    <div id="toolbarSidebar" class="toolbarHorizontalGroup">
+      <div id="toolbarSidebarLeft">
+        <div id="sidebarViewButtons" class="toolbarHorizontalGroup toggled" role="radiogroup">
+          <button id="viewThumbnail" class="toolbarButton toggled" type="button" title="Show Thumbnails" tabindex="0"
+            data-l10n-id="pdfjs-thumbs-button" role="radio" aria-checked="true" aria-controls="thumbnailView">
+            <span data-l10n-id="pdfjs-thumbs-button-label">Thumbnails</span>
+          </button>
+          <button id="viewOutline" class="toolbarButton" type="button"
+            title="Show Document Outline (double-click to expand/collapse all items)" tabindex="0"
+            data-l10n-id="pdfjs-document-outline-button" role="radio" aria-checked="false" aria-controls="outlineView">
+            <span data-l10n-id="pdfjs-document-outline-button-label">Document Outline</span>
+          </button>
+          <button id="viewAttachments" class="toolbarButton" type="button" title="Show Attachments" tabindex="0"
+            data-l10n-id="pdfjs-attachments-button" role="radio" aria-checked="false" aria-controls="attachmentsView">
+            <span data-l10n-id="pdfjs-attachments-button-label">Attachments</span>
+          </button>
+          <button id="viewLayers" class="toolbarButton" type="button"
+            title="Show Layers (double-click to reset all layers to the default state)" tabindex="0"
+            data-l10n-id="pdfjs-layers-button" role="radio" aria-checked="false" aria-controls="layersView">
+            <span data-l10n-id="pdfjs-layers-button-label">Layers</span>
+          </button>
+        </div>
+      </div>
+
+      <div id="toolbarSidebarRight">
+        <div id="outlineOptionsContainer" class="toolbarHorizontalGroup">
+          <div class="verticalToolbarSeparator"></div>
+
+          <button id="currentOutlineItem" class="toolbarButton" type="button" disabled="disabled"
+            title="Find Current Outline Item" tabindex="0" data-l10n-id="pdfjs-current-outline-item-button">
+            <span data-l10n-id="pdfjs-current-outline-item-button-label">Current Outline Item</span>
+          </button>
+        </div>
+      </div>
+    </div>
+    <div id="sidebarContent">
+      <div id="thumbnailView">
+      </div>
+      <div id="outlineView" class="hidden">
+      </div>
+      <div id="attachmentsView" class="hidden">
+      </div>
+      <div id="layersView" class="hidden">
+      </div>
+    </div>
+    <div id="sidebarResizer"></div>
+  </div> <!-- sidebarContainer -->
+
+  <div id="mainContainer">
+    <div class="toolbar">
+      <div id="toolbarContainer">
+        <div id="toolbarViewer" class="toolbarHorizontalGroup">
+          <div id="toolbarViewerLeft" class="toolbarHorizontalGroup">
+            <button id="sidebarToggleButton" class="toolbarButton" type="button" title="Toggle Sidebar" tabindex="0"
+              data-l10n-id="pdfjs-toggle-sidebar-button" aria-expanded="false" aria-haspopup="true"
+              aria-controls="sidebarContainer">
+              <span data-l10n-id="pdfjs-toggle-sidebar-button-label">Toggle Sidebar</span>
+            </button>
+            <div class="toolbarButtonSpacer"></div>
+            <div class="toolbarButtonWithContainer">
+              <button id="viewFindButton" class="toolbarButton" type="button" title="Find in Document" tabindex="0"
+                data-l10n-id="pdfjs-findbar-button" aria-expanded="false" aria-controls="findbar">
+                <span data-l10n-id="pdfjs-findbar-button-label">Find</span>
+              </button>
+              <div class="hidden doorHanger toolbarHorizontalGroup" id="findbar">
+                <div id="findInputContainer" class="toolbarHorizontalGroup">
+                  <span class="loadingInput end toolbarHorizontalGroup">
+                    <input id="findInput" class="toolbarField" title="Find" placeholder="Find in document…" tabindex="0"
+                      data-l10n-id="pdfjs-find-input" aria-invalid="false">
+                  </span>
+                  <div class="toolbarHorizontalGroup">
+                    <button id="findPreviousButton" class="toolbarButton" type="button"
+                      title="Find the previous occurrence of the phrase" tabindex="0"
+                      data-l10n-id="pdfjs-find-previous-button">
+                      <span data-l10n-id="pdfjs-find-previous-button-label">Previous</span>
+                    </button>
+                    <div class="splitToolbarButtonSeparator"></div>
+                    <button id="findNextButton" class="toolbarButton" type="button"
+                      title="Find the next occurrence of the phrase" tabindex="0" data-l10n-id="pdfjs-find-next-button">
+                      <span data-l10n-id="pdfjs-find-next-button-label">Next</span>
+                    </button>
+                  </div>
+                </div>
+
+                <div id="findbarOptionsOneContainer" class="toolbarHorizontalGroup">
+                  <div class="toggleButton toolbarLabel">
+                    <input type="checkbox" id="findHighlightAll" tabindex="0" />
+                    <label for="findHighlightAll" data-l10n-id="pdfjs-find-highlight-checkbox">Highlight All</label>
+                  </div>
+                  <div class="toggleButton toolbarLabel">
+                    <input type="checkbox" id="findMatchCase" tabindex="0" />
+                    <label for="findMatchCase" data-l10n-id="pdfjs-find-match-case-checkbox-label">Match Case</label>
+                  </div>
+                </div>
+                <div id="findbarOptionsTwoContainer" class="toolbarHorizontalGroup">
+                  <div class="toggleButton toolbarLabel">
+                    <input type="checkbox" id="findMatchDiacritics" tabindex="0" />
+                    <label for="findMatchDiacritics" data-l10n-id="pdfjs-find-match-diacritics-checkbox-label">Match
+                      Diacritics</label>
+                  </div>
+                  <div class="toggleButton toolbarLabel">
+                    <input type="checkbox" id="findEntireWord" tabindex="0" />
+                    <label for="findEntireWord" data-l10n-id="pdfjs-find-entire-word-checkbox-label">Whole
+                      Words</label>
+                  </div>
+                </div>
+
+                <div id="findbarMessageContainer" class="toolbarHorizontalGroup" aria-live="polite">
+                  <span id="findResultsCount" class="toolbarLabel"></span>
+                  <span id="findMsg" class="toolbarLabel"></span>
+                </div>
+              </div> <!-- findbar -->
+            </div>
+            <div class="toolbarHorizontalGroup hiddenSmallView">
+              <button class="toolbarButton" title="Previous Page" type="button" id="previous" tabindex="0"
+                data-l10n-id="pdfjs-previous-button">
+                <span data-l10n-id="pdfjs-previous-button-label">Previous</span>
+              </button>
+              <div class="splitToolbarButtonSeparator"></div>
+              <button class="toolbarButton" type="button" title="Next Page" id="next" tabindex="0"
+                data-l10n-id="pdfjs-next-button">
+                <span data-l10n-id="pdfjs-next-button-label">Next</span>
+              </button>
+            </div>
+            <div class="toolbarHorizontalGroup">
+              <span class="loadingInput start toolbarHorizontalGroup">
+                <input type="number" id="pageNumber" class="toolbarField" title="Page" value="1" min="1" tabindex="0"
+                  data-l10n-id="pdfjs-page-input" autocomplete="off">
+              </span>
+              <span id="numPages" class="toolbarLabel"></span>
+            </div>
+          </div>
+          <div id="toolbarViewerMiddle" class="toolbarHorizontalGroup">
+            <div class="toolbarHorizontalGroup">
+              <button id="zoomOutButton" class="toolbarButton" type="button" title="Zoom Out" tabindex="0"
+                data-l10n-id="pdfjs-zoom-out-button">
+                <span data-l10n-id="pdfjs-zoom-out-button-label">Zoom Out</span>
+              </button>
+              <div class="splitToolbarButtonSeparator"></div>
+              <button id="zoomInButton" class="toolbarButton" type="button" title="Zoom In" tabindex="0"
+                data-l10n-id="pdfjs-zoom-in-button">
+                <span data-l10n-id="pdfjs-zoom-in-button-label">Zoom In</span>
+              </button>
+            </div>
+            <span id="scaleSelectContainer" class="dropdownToolbarButton">
+              <select id="scaleSelect" title="Zoom" tabindex="0" data-l10n-id="pdfjs-zoom-select">
+                <option id="pageAutoOption" title="" value="auto" selected="selected"
+                  data-l10n-id="pdfjs-page-scale-auto">Automatic Zoom</option>
+                <option id="pageActualOption" title="" value="page-actual" data-l10n-id="pdfjs-page-scale-actual">
+                  Actual Size</option>
+                <option id="pageFitOption" title="" value="page-fit" data-l10n-id="pdfjs-page-scale-fit">Page Fit
+                </option>
+                <option id="pageWidthOption" title="" value="page-width" data-l10n-id="pdfjs-page-scale-width">Page
+                  Width</option>
+                <option id="customScaleOption" title="" value="custom" disabled="disabled" hidden="true"
+                  data-l10n-id="pdfjs-page-scale-percent" data-l10n-args='{ "scale": 0 }'>0%</option>
+                <option title="" value="0.5" data-l10n-id="pdfjs-page-scale-percent" data-l10n-args='{ "scale": 50 }'>
+                  50%</option>
+                <option title="" value="0.75" data-l10n-id="pdfjs-page-scale-percent" data-l10n-args='{ "scale": 75 }'>
+                  75%</option>
+                <option title="" value="1" data-l10n-id="pdfjs-page-scale-percent" data-l10n-args='{ "scale": 100 }'>
+                  100%</option>
+                <option title="" value="1.25" data-l10n-id="pdfjs-page-scale-percent" data-l10n-args='{ "scale": 125 }'>
+                  125%</option>
+                <option title="" value="1.5" data-l10n-id="pdfjs-page-scale-percent" data-l10n-args='{ "scale": 150 }'>
+                  150%</option>
+                <option title="" value="2" data-l10n-id="pdfjs-page-scale-percent" data-l10n-args='{ "scale": 200 }'>
+                  200%</option>
+                <option title="" value="3" data-l10n-id="pdfjs-page-scale-percent" data-l10n-args='{ "scale": 300 }'>
+                  300%</option>
+                <option title="" value="4" data-l10n-id="pdfjs-page-scale-percent" data-l10n-args='{ "scale": 400 }'>
+                  400%</option>
+              </select>
+            </span>
+          </div>
+          <div id="toolbarViewerRight" class="toolbarHorizontalGroup">
+            <div id="editorModeButtons" class="toolbarHorizontalGroup" role="radiogroup">
+              <div id="editorHighlight" class="toolbarButtonWithContainer">
+                <button id="editorHighlightButton" class="toolbarButton" type="button" disabled="disabled"
+                  title="Highlight" role="radio" aria-expanded="false" aria-haspopup="true"
+                  aria-controls="editorHighlightParamsToolbar" tabindex="0"
+                  data-l10n-id="pdfjs-editor-highlight-button">
+                  <span data-l10n-id="pdfjs-editor-highlight-button-label">Highlight</span>
+                </button>
+                <div class="editorParamsToolbar hidden doorHangerRight" id="editorHighlightParamsToolbar">
+                  <div id="highlightParamsToolbarContainer" class="editorParamsToolbarContainer">
+                    <div id="editorHighlightColorPicker" class="colorPicker">
+                      <span id="highlightColorPickerLabel" class="editorParamsLabel"
+                        data-l10n-id="pdfjs-editor-highlight-colorpicker-label">Highlight color</span>
+                    </div>
+                    <div id="editorHighlightThickness">
+                      <label for="editorFreeHighlightThickness" class="editorParamsLabel"
+                        data-l10n-id="pdfjs-editor-free-highlight-thickness-input">Thickness</label>
+                      <div class="thicknessPicker">
+                        <input type="range" id="editorFreeHighlightThickness" class="editorParamsSlider"
+                          data-l10n-id="pdfjs-editor-free-highlight-thickness-title" value="12" min="8" max="24"
+                          step="1" tabindex="0">
+                      </div>
+                    </div>
+                    <div id="editorHighlightVisibility">
+                      <div class="divider"></div>
+                      <div class="toggler">
+                        <label for="editorHighlightShowAll" class="editorParamsLabel"
+                          data-l10n-id="pdfjs-editor-highlight-show-all-button-label">Show all</label>
+                        <button id="editorHighlightShowAll" class="toggle-button" type="button"
+                          data-l10n-id="pdfjs-editor-highlight-show-all-button" aria-pressed="true"
+                          tabindex="0"></button>
+                      </div>
+                    </div>
+                  </div>
+                </div>
+              </div>
+              <div id="editorFreeText" class="toolbarButtonWithContainer">
+                <button id="editorFreeTextButton" class="toolbarButton" type="button" disabled="disabled" title="Text"
+                  role="radio" aria-expanded="false" aria-haspopup="true" aria-controls="editorFreeTextParamsToolbar"
+                  tabindex="0" data-l10n-id="pdfjs-editor-free-text-button">
+                  <span data-l10n-id="pdfjs-editor-free-text-button-label">Text</span>
+                </button>
+                <div class="editorParamsToolbar hidden doorHangerRight" id="editorFreeTextParamsToolbar">
+                  <div class="editorParamsToolbarContainer">
+                    <div class="editorParamsSetter">
+                      <label for="editorFreeTextColor" class="editorParamsLabel"
+                        data-l10n-id="pdfjs-editor-free-text-color-input">Color</label>
+                      <input type="color" id="editorFreeTextColor" class="editorParamsColor" tabindex="0">
+                    </div>
+                    <div class="editorParamsSetter">
+                      <label for="editorFreeTextFontSize" class="editorParamsLabel"
+                        data-l10n-id="pdfjs-editor-free-text-size-input">Size</label>
+                      <input type="range" id="editorFreeTextFontSize" class="editorParamsSlider" value="10" min="5"
+                        max="100" step="1" tabindex="0">
+                    </div>
+                  </div>
+                </div>
+              </div>
+              <div id="editorInk" class="toolbarButtonWithContainer">
+                <button id="editorInkButton" class="toolbarButton" type="button" disabled="disabled" title="Draw"
+                  role="radio" aria-expanded="false" aria-haspopup="true" aria-controls="editorInkParamsToolbar"
+                  tabindex="0" data-l10n-id="pdfjs-editor-ink-button">
+                  <span data-l10n-id="pdfjs-editor-ink-button-label">Draw</span>
+                </button>
+                <div class="editorParamsToolbar hidden doorHangerRight" id="editorInkParamsToolbar">
+                  <div class="editorParamsToolbarContainer">
+                    <div class="editorParamsSetter">
+                      <label for="editorInkColor" class="editorParamsLabel"
+                        data-l10n-id="pdfjs-editor-ink-color-input">Color</label>
+                      <input type="color" id="editorInkColor" class="editorParamsColor" tabindex="0">
+                    </div>
+                    <div class="editorParamsSetter">
+                      <label for="editorInkThickness" class="editorParamsLabel"
+                        data-l10n-id="pdfjs-editor-ink-thickness-input">Thickness</label>
+                      <input type="range" id="editorInkThickness" class="editorParamsSlider" value="1" min="1" max="20"
+                        step="1" tabindex="0">
+                    </div>
+                    <div class="editorParamsSetter">
+                      <label for="editorInkOpacity" class="editorParamsLabel"
+                        data-l10n-id="pdfjs-editor-ink-opacity-input">Opacity</label>
+                      <input type="range" id="editorInkOpacity" class="editorParamsSlider" value="100" min="1" max="100"
+                        step="1" tabindex="0">
+                    </div>
+                  </div>
+                </div>
+              </div>
+              <div id="editorStamp" class="toolbarButtonWithContainer">
+                <button id="editorStampButton" class="toolbarButton" type="button" disabled="disabled"
+                  title="Add or edit images" role="radio" aria-expanded="false" aria-haspopup="true"
+                  aria-controls="editorStampParamsToolbar" tabindex="0" data-l10n-id="pdfjs-editor-stamp-button">
+                  <span data-l10n-id="pdfjs-editor-stamp-button-label">Add or edit images</span>
+                </button>
+                <div class="editorParamsToolbar hidden doorHangerRight menu" id="editorStampParamsToolbar">
+                  <div class="menuContainer">
+                    <button id="editorStampAddImage" class="toolbarButton labeled" type="button" title="Add image"
+                      tabindex="0" data-l10n-id="pdfjs-editor-stamp-add-image-button">
+                      <span class="editorParamsLabel" data-l10n-id="pdfjs-editor-stamp-add-image-button-label">Add
+                        image</span>
+                    </button>
+                  </div>
+                </div>
+              </div>
+            </div>
+
+            <div id="editorModeSeparator" class="verticalToolbarSeparator"></div>
+
+            <div class="toolbarHorizontalGroup hiddenMediumView">
+              <button id="printButton" class="toolbarButton" type="button" title="Print" tabindex="0"
+                data-l10n-id="pdfjs-print-button">
+                <span data-l10n-id="pdfjs-print-button-label">Print</span>
+              </button>
+
+              <button id="downloadButton" class="toolbarButton" type="button" title="Save" tabindex="0"
+                data-l10n-id="pdfjs-save-button">
+                <span data-l10n-id="pdfjs-save-button-label">Save</span>
+              </button>
+            </div>
+
+            <div class="verticalToolbarSeparator hiddenMediumView"></div>
+
+            <div id="secondaryToolbarToggle" class="toolbarButtonWithContainer">
+              <button id="secondaryToolbarToggleButton" class="toolbarButton" type="button" title="Tools" tabindex="0"
+                data-l10n-id="pdfjs-tools-button" aria-expanded="false" aria-haspopup="true"
+                aria-controls="secondaryToolbar">
+                <span data-l10n-id="pdfjs-tools-button-label">Tools</span>
+              </button>
+              <div id="secondaryToolbar" class="hidden doorHangerRight menu">
+                <div id="secondaryToolbarButtonContainer" class="menuContainer">
+                  <button id="secondaryOpenFile" class="toolbarButton labeled" type="button" title="Open File"
+                    tabindex="0" data-l10n-id="pdfjs-open-file-button">
+                    <span data-l10n-id="pdfjs-open-file-button-label">Open</span>
+                  </button>
+
+                  <div class="visibleMediumView">
+                    <button id="secondaryPrint" class="toolbarButton labeled" type="button" title="Print" tabindex="0"
+                      data-l10n-id="pdfjs-print-button">
+                      <span data-l10n-id="pdfjs-print-button-label">Print</span>
+                    </button>
+
+                    <button id="secondaryDownload" class="toolbarButton labeled" type="button" title="Save" tabindex="0"
+                      data-l10n-id="pdfjs-save-button">
+                      <span data-l10n-id="pdfjs-save-button-label">Save</span>
+                    </button>
+
+                  </div>
+
+                  <div class="horizontalToolbarSeparator"></div>
+
+                  <button id="presentationMode" class="toolbarButton labeled" type="button"
+                    title="Switch to Presentation Mode" tabindex="0" data-l10n-id="pdfjs-presentation-mode-button">
+                    <span data-l10n-id="pdfjs-presentation-mode-button-label">Presentation Mode</span>
+                  </button>
+
+                  <a href="#" id="viewBookmark" class="toolbarButton labeled"
+                    title="Current Page (View URL from Current Page)" tabindex="0" data-l10n-id="pdfjs-bookmark-button">
+                    <span data-l10n-id="pdfjs-bookmark-button-label">Current Page</span>
+                  </a>
+
+                  <div id="viewBookmarkSeparator" class="horizontalToolbarSeparator"></div>
+
+                  <button id="firstPage" class="toolbarButton labeled" type="button" title="Go to First Page"
+                    tabindex="0" data-l10n-id="pdfjs-first-page-button">
+                    <span data-l10n-id="pdfjs-first-page-button-label">Go to First Page</span>
+                  </button>
+                  <button id="lastPage" class="toolbarButton labeled" type="button" title="Go to Last Page" tabindex="0"
+                    data-l10n-id="pdfjs-last-page-button">
+                    <span data-l10n-id="pdfjs-last-page-button-label">Go to Last Page</span>
+                  </button>
+
+                  <div class="horizontalToolbarSeparator"></div>
+
+                  <button id="pageRotateCw" class="toolbarButton labeled" type="button" title="Rotate Clockwise"
+                    tabindex="0" data-l10n-id="pdfjs-page-rotate-cw-button">
+                    <span data-l10n-id="pdfjs-page-rotate-cw-button-label">Rotate Clockwise</span>
+                  </button>
+                  <button id="pageRotateCcw" class="toolbarButton labeled" type="button" title="Rotate Counterclockwise"
+                    tabindex="0" data-l10n-id="pdfjs-page-rotate-ccw-button">
+                    <span data-l10n-id="pdfjs-page-rotate-ccw-button-label">Rotate Counterclockwise</span>
+                  </button>
+
+                  <div class="horizontalToolbarSeparator"></div>
+
+                  <div id="cursorToolButtons" role="radiogroup">
+                    <button id="cursorSelectTool" class="toolbarButton labeled toggled" type="button"
+                      title="Enable Text Selection Tool" tabindex="0"
+                      data-l10n-id="pdfjs-cursor-text-select-tool-button" role="radio" aria-checked="true">
+                      <span data-l10n-id="pdfjs-cursor-text-select-tool-button-label">Text Selection Tool</span>
+                    </button>
+                    <button id="cursorHandTool" class="toolbarButton labeled" type="button" title="Enable Hand Tool"
+                      tabindex="0" data-l10n-id="pdfjs-cursor-hand-tool-button" role="radio" aria-checked="false">
+                      <span data-l10n-id="pdfjs-cursor-hand-tool-button-label">Hand Tool</span>
+                    </button>
+                  </div>
+
+                  <div class="horizontalToolbarSeparator"></div>
+
+                  <div id="scrollModeButtons" role="radiogroup">
+                    <button id="scrollPage" class="toolbarButton labeled" type="button" title="Use Page Scrolling"
+                      tabindex="0" data-l10n-id="pdfjs-scroll-page-button" role="radio" aria-checked="false">
+                      <span data-l10n-id="pdfjs-scroll-page-button-label">Page Scrolling</span>
+                    </button>
+                    <button id="scrollVertical" class="toolbarButton labeled toggled" type="button"
+                      title="Use Vertical Scrolling" tabindex="0" data-l10n-id="pdfjs-scroll-vertical-button"
+                      role="radio" aria-checked="true">
+                      <span data-l10n-id="pdfjs-scroll-vertical-button-label">Vertical Scrolling</span>
+                    </button>
+                    <button id="scrollHorizontal" class="toolbarButton labeled" type="button"
+                      title="Use Horizontal Scrolling" tabindex="0" data-l10n-id="pdfjs-scroll-horizontal-button"
+                      role="radio" aria-checked="false">
+                      <span data-l10n-id="pdfjs-scroll-horizontal-button-label">Horizontal Scrolling</span>
+                    </button>
+                    <button id="scrollWrapped" class="toolbarButton labeled" type="button" title="Use Wrapped Scrolling"
+                      tabindex="0" data-l10n-id="pdfjs-scroll-wrapped-button" role="radio" aria-checked="false">
+                      <span data-l10n-id="pdfjs-scroll-wrapped-button-label">Wrapped Scrolling</span>
+                    </button>
+                  </div>
+
+                  <div class="horizontalToolbarSeparator"></div>
+
+                  <div id="spreadModeButtons" role="radiogroup">
+                    <button id="spreadNone" class="toolbarButton labeled toggled" type="button"
+                      title="Do not join page spreads" tabindex="0" data-l10n-id="pdfjs-spread-none-button" role="radio"
+                      aria-checked="true">
+                      <span data-l10n-id="pdfjs-spread-none-button-label">No Spreads</span>
+                    </button>
+                    <button id="spreadOdd" class="toolbarButton labeled" type="button"
+                      title="Join page spreads starting with odd-numbered pages" tabindex="0"
+                      data-l10n-id="pdfjs-spread-odd-button" role="radio" aria-checked="false">
+                      <span data-l10n-id="pdfjs-spread-odd-button-label">Odd Spreads</span>
+                    </button>
+                    <button id="spreadEven" class="toolbarButton labeled" type="button"
+                      title="Join page spreads starting with even-numbered pages" tabindex="0"
+                      data-l10n-id="pdfjs-spread-even-button" role="radio" aria-checked="false">
+                      <span data-l10n-id="pdfjs-spread-even-button-label">Even Spreads</span>
+                    </button>
+                  </div>
+
+                  <div id="imageAltTextSettingsSeparator" class="horizontalToolbarSeparator hidden"></div>
+                  <button id="imageAltTextSettings" type="button" class="toolbarButton labeled hidden"
+                    title="Image alt text settings" tabindex="0" data-l10n-id="pdfjs-image-alt-text-settings-button"
+                    aria-controls="altTextSettingsDialog">
+                    <span data-l10n-id="pdfjs-image-alt-text-settings-button-label">Image alt text settings</span>
+                  </button>
+
+                  <div class="horizontalToolbarSeparator"></div>
+
+                  <button id="documentProperties" class="toolbarButton labeled" type="button"
+                    title="Document Properties…" tabindex="0" data-l10n-id="pdfjs-document-properties-button"
+                    aria-controls="documentPropertiesDialog">
+                    <span data-l10n-id="pdfjs-document-properties-button-label">Document Properties…</span>
+                  </button>
+                </div>
+              </div> <!-- secondaryToolbar -->
+            </div>
+          </div>
+        </div>
+        <div id="loadingBar">
+          <div class="progress">
+            <div class="glimmer">
+            </div>
+          </div>
+        </div>
+      </div>
+    </div>
+
+    <div id="viewerContainer" tabindex="0">
+      <div id="viewer" class="pdfViewer"></div>
+    </div>
+  </div> <!-- mainContainer -->
+
+  <div id="dialogContainer">
+    <dialog id="passwordDialog">
+      <div class="row">
+        <label for="password" id="passwordText" data-l10n-id="pdfjs-password-label">Enter the password to open this
+          PDF file:</label>
+      </div>
+      <div class="row">
+        <input type="password" id="password" class="toolbarField">
+      </div>
+      <div class="buttonRow">
+        <button id="passwordCancel" class="dialogButton" type="button"><span
+            data-l10n-id="pdfjs-password-cancel-button">Cancel</span></button>
+        <button id="passwordSubmit" class="dialogButton" type="button"><span
+            data-l10n-id="pdfjs-password-ok-button">OK</span></button>
+      </div>
+    </dialog>
+    <dialog id="documentPropertiesDialog">
+      <div class="row">
+        <span id="fileNameLabel" data-l10n-id="pdfjs-document-properties-file-name">File name:</span>
+        <p id="fileNameField" aria-labelledby="fileNameLabel">-</p>
+      </div>
+      <div class="row">
+        <span id="fileSizeLabel" data-l10n-id="pdfjs-document-properties-file-size">File size:</span>
+        <p id="fileSizeField" aria-labelledby="fileSizeLabel">-</p>
+      </div>
+      <div class="separator"></div>
+      <div class="row">
+        <span id="titleLabel" data-l10n-id="pdfjs-document-properties-title">Title:</span>
+        <p id="titleField" aria-labelledby="titleLabel">-</p>
+      </div>
+      <div class="row">
+        <span id="authorLabel" data-l10n-id="pdfjs-document-properties-author">Author:</span>
+        <p id="authorField" aria-labelledby="authorLabel">-</p>
+      </div>
+      <div class="row">
+        <span id="subjectLabel" data-l10n-id="pdfjs-document-properties-subject">Subject:</span>
+        <p id="subjectField" aria-labelledby="subjectLabel">-</p>
+      </div>
+      <div class="row">
+        <span id="keywordsLabel" data-l10n-id="pdfjs-document-properties-keywords">Keywords:</span>
+        <p id="keywordsField" aria-labelledby="keywordsLabel">-</p>
+      </div>
+      <div class="row">
+        <span id="creationDateLabel" data-l10n-id="pdfjs-document-properties-creation-date">Creation Date:</span>
+        <p id="creationDateField" aria-labelledby="creationDateLabel">-</p>
+      </div>
+      <div class="row">
+        <span id="modificationDateLabel" data-l10n-id="pdfjs-document-properties-modification-date">Modification
+          Date:</span>
+        <p id="modificationDateField" aria-labelledby="modificationDateLabel">-</p>
+      </div>
+      <div class="row">
+        <span id="creatorLabel" data-l10n-id="pdfjs-document-properties-creator">Creator:</span>
+        <p id="creatorField" aria-labelledby="creatorLabel">-</p>
+      </div>
+      <div class="separator"></div>
+      <div class="row">
+        <span id="producerLabel" data-l10n-id="pdfjs-document-properties-producer">PDF Producer:</span>
+        <p id="producerField" aria-labelledby="producerLabel">-</p>
+      </div>
+      <div class="row">
+        <span id="versionLabel" data-l10n-id="pdfjs-document-properties-version">PDF Version:</span>
+        <p id="versionField" aria-labelledby="versionLabel">-</p>
+      </div>
+      <div class="row">
+        <span id="pageCountLabel" data-l10n-id="pdfjs-document-properties-page-count">Page Count:</span>
+        <p id="pageCountField" aria-labelledby="pageCountLabel">-</p>
+      </div>
+      <div class="row">
+        <span id="pageSizeLabel" data-l10n-id="pdfjs-document-properties-page-size">Page Size:</span>
+        <p id="pageSizeField" aria-labelledby="pageSizeLabel">-</p>
+      </div>
+      <div class="separator"></div>
+      <div class="row">
+        <span id="linearizedLabel" data-l10n-id="pdfjs-document-properties-linearized">Fast Web View:</span>
+        <p id="linearizedField" aria-labelledby="linearizedLabel">-</p>
+      </div>
+      <div class="buttonRow">
+        <button id="documentPropertiesClose" class="dialogButton" type="button"><span
+            data-l10n-id="pdfjs-document-properties-close-button">Close</span></button>
+      </div>
+    </dialog>
+    <dialog class="dialog altText" id="altTextDialog" aria-labelledby="dialogLabel"
+      aria-describedby="dialogDescription">
+      <div id="altTextContainer" class="mainContainer">
+        <div id="overallDescription">
+          <span id="dialogLabel" data-l10n-id="pdfjs-editor-alt-text-dialog-label" class="title">Choose an
+            option</span>
+          <span id="dialogDescription" data-l10n-id="pdfjs-editor-alt-text-dialog-description">
+            Alt text (alternative text) helps when people can’t see the image or when it doesn’t load.
+          </span>
+        </div>
+        <div id="addDescription">
+          <div class="radio">
+            <div class="radioButton">
+              <input type="radio" id="descriptionButton" name="altTextOption" tabindex="0"
+                aria-describedby="descriptionAreaLabel" checked>
+              <label for="descriptionButton" data-l10n-id="pdfjs-editor-alt-text-add-description-label">Add a
+                description</label>
+            </div>
+            <div class="radioLabel">
+              <span id="descriptionAreaLabel" data-l10n-id="pdfjs-editor-alt-text-add-description-description">
+                Aim for 1-2 sentences that describe the subject, setting, or actions.
+              </span>
+            </div>
+          </div>
+          <div class="descriptionArea">
+            <textarea id="descriptionTextarea"
+              placeholder="For example, “A young man sits down at a table to eat a meal”"
+              aria-labelledby="descriptionAreaLabel" data-l10n-id="pdfjs-editor-alt-text-textarea"
+              tabindex="0"></textarea>
+          </div>
+        </div>
+        <div id="markAsDecorative">
+          <div class="radio">
+            <div class="radioButton">
+              <input type="radio" id="decorativeButton" name="altTextOption" aria-describedby="decorativeLabel">
+              <label for="decorativeButton" data-l10n-id="pdfjs-editor-alt-text-mark-decorative-label">Mark as
+                decorative</label>
+            </div>
+            <div class="radioLabel">
+              <span id="decorativeLabel" data-l10n-id="pdfjs-editor-alt-text-mark-decorative-description">
+                This is used for ornamental images, like borders or watermarks.
+              </span>
+            </div>
+          </div>
+        </div>
+        <div id="buttons">
+          <button id="altTextCancel" class="secondaryButton" type="button" tabindex="0"><span
+              data-l10n-id="pdfjs-editor-alt-text-cancel-button">Cancel</span></button>
+          <button id="altTextSave" class="primaryButton" type="button" tabindex="0"><span
+              data-l10n-id="pdfjs-editor-alt-text-save-button">Save</span></button>
+        </div>
+      </div>
+    </dialog>
+    <dialog class="dialog newAltText" id="newAltTextDialog" aria-labelledby="newAltTextTitle"
+      aria-describedby="newAltTextDescription" tabindex="0">
+      <div id="newAltTextContainer" class="mainContainer">
+        <div class="title">
+          <span id="newAltTextTitle" data-l10n-id="pdfjs-editor-new-alt-text-dialog-edit-label" role="sectionhead"
+            tabindex="0">Edit alt text (image description)</span>
+        </div>
+        <div id="mainContent">
+          <div id="descriptionAndSettings">
+            <div id="descriptionInstruction">
+              <div id="newAltTextDescriptionContainer">
+                <div class="altTextSpinner" role="status" aria-live="polite"></div>
+                <textarea id="newAltTextDescriptionTextarea" placeholder="Write your description here…"
+                  aria-labelledby="descriptionAreaLabel" data-l10n-id="pdfjs-editor-new-alt-text-textarea"
+                  tabindex="0"></textarea>
+              </div>
+              <span id="newAltTextDescription" role="note" data-l10n-id="pdfjs-editor-new-alt-text-description">Short
+                description for people who can’t see the image or when the image doesn’t load.</span>
+              <div id="newAltTextDisclaimer" role="note">
+                <div><span data-l10n-id="pdfjs-editor-new-alt-text-disclaimer1">This alt text was created
+                    automatically and may be inaccurate.</span> <a
+                    href="https://support.mozilla.org/en-US/kb/pdf-alt-text" target="_blank" rel="noopener noreferrer"
+                    id="newAltTextLearnMore" data-l10n-id="pdfjs-editor-new-alt-text-disclaimer-learn-more-url"
+                    tabindex="0">Learn more</a></div>
+              </div>
+            </div>
+            <div id="newAltTextCreateAutomatically" class="toggler">
+              <button id="newAltTextCreateAutomaticallyButton" class="toggle-button" type="button" aria-pressed="true"
+                tabindex="0"></button>
+              <label for="newAltTextCreateAutomaticallyButton" class="togglerLabel"
+                data-l10n-id="pdfjs-editor-new-alt-text-create-automatically-button-label">Create alt text
+                automatically</label>
+            </div>
+            <div id="newAltTextDownloadModel" class="hidden">
+              <span id="newAltTextDownloadModelDescription"
+                data-l10n-id="pdfjs-editor-new-alt-text-ai-model-downloading-progress" aria-valuemin="0"
+                data-l10n-args='{ "totalSize": 0, "downloadedSize": 0 }'>Downloading alt text AI model (0 of 0
+                MB)</span>
+            </div>
+          </div>
+          <div id="newAltTextImagePreview"></div>
+        </div>
+        <div id="newAltTextError" class="messageBar">
+          <div>
+            <div>
+              <span class="title" data-l10n-id="pdfjs-editor-new-alt-text-error-title">Couldn’t create alt text
+                automatically</span>
+              <span class="description" data-l10n-id="pdfjs-editor-new-alt-text-error-description">Please write your
+                own alt text or try again later.</span>
+            </div>
+            <button id="newAltTextCloseButton" class="closeButton" type="button" tabindex="0" title="Close"><span
+                data-l10n-id="pdfjs-editor-new-alt-text-error-close-button">Close</span></button>
+          </div>
+        </div>
+        <div id="newAltTextButtons" class="dialogButtonsGroup">
+          <button id="newAltTextCancel" type="button" class="secondaryButton hidden" tabindex="0"><span
+              data-l10n-id="pdfjs-editor-alt-text-cancel-button">Cancel</span></button>
+          <button id="newAltTextNotNow" type="button" class="secondaryButton" tabindex="0"><span
+              data-l10n-id="pdfjs-editor-new-alt-text-not-now-button">Not now</span></button>
+          <button id="newAltTextSave" type="button" class="primaryButton" tabindex="0"><span
+              data-l10n-id="pdfjs-editor-alt-text-save-button">Save</span></button>
+        </div>
+      </div>
+    </dialog>
+
+    <dialog class="dialog" id="altTextSettingsDialog" aria-labelledby="altTextSettingsTitle">
+      <div id="altTextSettingsContainer" class="mainContainer">
+        <div class="title">
+          <span id="altTextSettingsTitle" data-l10n-id="pdfjs-editor-alt-text-settings-dialog-label" role="sectionhead"
+            tabindex="0" class="title">Image alt text settings</span>
+        </div>
+        <div id="automaticAltText">
+          <span data-l10n-id="pdfjs-editor-alt-text-settings-automatic-title">Automatic alt text</span>
+          <div id="automaticSettings">
+            <div id="createModelSetting">
+              <div class="toggler">
+                <button id="createModelButton" type="button" class="toggle-button" aria-pressed="true"
+                  tabindex="0"></button>
+                <label for="createModelButton" class="togglerLabel"
+                  data-l10n-id="pdfjs-editor-alt-text-settings-create-model-button-label">Create alt text
+                  automatically</label>
+              </div>
+              <div id="createModelDescription" class="description">
+                <span data-l10n-id="pdfjs-editor-alt-text-settings-create-model-description">Suggests descriptions to
+                  help people who can’t see the image or when the image doesn’t load.</span> <a
+                  href="https://support.mozilla.org/en-US/kb/pdf-alt-text" target="_blank" rel="noopener noreferrer"
+                  id="altTextSettingsLearnMore" data-l10n-id="pdfjs-editor-new-alt-text-disclaimer-learn-more-url"
+                  tabindex="0">Learn more</a>
+              </div>
+            </div>
+            <div id="aiModelSettings">
+              <div>
+                <span data-l10n-id="pdfjs-editor-alt-text-settings-download-model-label"
+                  data-l10n-args='{ "totalSize": 180 }'>Alt text AI model (180MB)</span>
+                <div id="aiModelDescription" class="description">
+                  <span data-l10n-id="pdfjs-editor-alt-text-settings-ai-model-description">Runs locally on your device
+                    so your data stays private. Required for automatic alt text.</span>
+                </div>
+              </div>
+              <button id="deleteModelButton" type="button" class="secondaryButton" tabindex="0"><span
+                  data-l10n-id="pdfjs-editor-alt-text-settings-delete-model-button">Delete</span></button>
+              <button id="downloadModelButton" type="button" class="secondaryButton" tabindex="0"><span
+                  data-l10n-id="pdfjs-editor-alt-text-settings-download-model-button">Download</span></button>
+            </div>
+          </div>
+        </div>
+        <div class="dialogSeparator"></div>
+        <div id="altTextEditor">
+          <span data-l10n-id="pdfjs-editor-alt-text-settings-editor-title">Alt text editor</span>
+          <div id="showAltTextEditor">
+            <div class="toggler">
+              <button id="showAltTextDialogButton" type="button" class="toggle-button" aria-pressed="true"
+                tabindex="0"></button>
+              <label for="showAltTextDialogButton" class="togglerLabel"
+                data-l10n-id="pdfjs-editor-alt-text-settings-show-dialog-button-label">Show alt text editor right away
+                when adding an image</label>
+            </div>
+            <div id="showAltTextDialogDescription" class="description">
+              <span data-l10n-id="pdfjs-editor-alt-text-settings-show-dialog-description">Helps you make sure all your
+                images have alt text.</span>
+            </div>
+          </div>
+        </div>
+        <div id="buttons" class="dialogButtonsGroup">
+          <button id="altTextSettingsCloseButton" type="button" class="primaryButton" tabindex="0"><span
+              data-l10n-id="pdfjs-editor-alt-text-settings-close-button">Close</span></button>
+        </div>
+      </div>
+    </dialog>
+    <dialog id="printServiceDialog" style="min-width: 200px;">
+      <div class="row">
+        <span data-l10n-id="pdfjs-print-progress-message">Preparing document for printing…</span>
+      </div>
+      <div class="row">
+        <progress value="0" max="100"></progress>
+        <span data-l10n-id="pdfjs-print-progress-percent" data-l10n-args='{ "progress": 0 }'
+          class="relative-progress">0%</span>
+      </div>
+      <div class="buttonRow">
+        <button id="printCancel" class="dialogButton" type="button"><span
+            data-l10n-id="pdfjs-print-progress-close-button">Cancel</span></button>
+      </div>
+    </dialog>
+  </div> <!-- dialogContainer -->
+
+  </div> <!-- outerContainer -->
+  <div id="printContainer"></div>
+  <!-- <script>
+      document.getElementById('uploadButton').addEventListener('click', function() {
+          // 触发隐藏的文件输入
+          document.getElementById('pdfInput').click();
+      });
+      
+      document.getElementById('pdfInput').addEventListener('change', function(event) {
+          const file = event.target.files[0];
+          if (file && file.type === 'application/pdf') {
+              const formData = new FormData();
+              formData.append('file', file);
+      
+              // 调用 FastAPI 上传接口
+              fetch('http://106.14.113.12:8005/upload-pdf', {  // 根据实际部署情况调整URL
+                  method: 'POST',
+                  body: formData
+              })
+              .then(response => response.json())
+              .then(data => {
+                  if (data.success) {
+                      const uploadedUrl = data.url;
+                      // 加载上传后的 PDF
+                      PDFViewerApplication.open(uploadedUrl);
+                  } else {
+                      alert('上传失败: ' + data.error);
+                  }
+              })
+              .catch(error => {
+                  console.error('Error:', error);
+                  alert('上传过程中发生错误');
+              });
+          } else {
+              alert('请选择一个 PDF 文件');
+          }
+      });
+      </script> -->
+  <script>
+    const uploadButton = document.getElementById('uploadButton');
+    const pdfInput = document.getElementById('pdfInput');
+    const filenameModal = document.getElementById('filenameModal');
+    const modalOverlay = document.getElementById('modalOverlay');
+    const confirmUploadButton = document.getElementById('confirmUpload');
+    const cancelUploadButton = document.getElementById('cancelUpload');
+    const customFileNameInput = document.getElementById('customFileName');
+    const openDirectoryButton = document.getElementById('openDirectoryButton');
+    const directoryModal = document.getElementById('directoryModal');
+    const closeDirectoryButton = document.getElementById('closeDirectoryButton');
+    const pdfList = document.getElementById('pdfList');
+
+    let selectedFile = null;
+
+    // 上传按钮点击事件
+    uploadButton.addEventListener('click', function () {
+      // 触发隐藏的文件输入
+      pdfInput.click();
+    });
+
+    // 文件选择事件
+    pdfInput.addEventListener('change', function (event) {
+      const file = event.target.files[0];
+      if (file && file.type === 'application/pdf') {
+        selectedFile = file;
+        // 显示模态窗口
+        modalOverlay.style.display = 'block';
+        filenameModal.style.display = 'block';
+        customFileNameInput.value = ""; // 清空之前的输入
+        customFileNameInput.focus();
+      } else {
+        alert('请选择一个 PDF 文件');
+      }
+    });
+
+    // 取消上传
+    cancelUploadButton.addEventListener('click', function () {
+      // 隐藏模态窗口和遮罩
+      filenameModal.style.display = 'none';
+      modalOverlay.style.display = 'none';
+      // 清空文件输入
+      pdfInput.value = "";
+      selectedFile = null;
+    });
+
+    // 确认上传
+    confirmUploadButton.addEventListener('click', function () {
+      const customFileName = customFileNameInput.value.trim();
+      if (customFileName === "") {
+        alert('请输入保存的文件名');
+        customFileNameInput.focus();
+        return;
+      }
+
+      // 检查文件名是否包含非法字符
+      const invalidChars = /[\\/:"*?<>|]+/;
+      if (invalidChars.test(customFileName)) {
+        alert('文件名包含非法字符:\\ / : " * ? < > |');
+        customFileNameInput.focus();
+        return;
+      }
+
+      if (!selectedFile) {
+        alert('没有选择文件');
+        return;
+      }
+
+      // 进行上传
+      const formData = new FormData();
+      formData.append('file', selectedFile);
+      formData.append('custom_name', customFileName); // 添加自定义文件名
+
+      // 可以根据需要显示上传进度
+      fetch('http://106.14.113.12:8005/upload-pdf', {  // 根据实际部署情况调整URL
+        method: 'POST',
+        body: formData
+      })
+        .then(response => response.json())
+        .then(data => {
+          if (data.success) {
+            const uploadedFilePath = data.file_path; // 如 "/static/files/XXXX.pdf"
+            const redirectUrl = `viewer.html?file=${encodeURIComponent(uploadedFilePath)}`;
+            window.location.href = redirectUrl;
+          } else {
+            alert('上传失败: ' + data.error);
+          }
+        })
+        .catch(error => {
+          console.error('Error:', error);
+          alert('上传过程中发生错误');
+        })
+        .finally(() => {
+          // 隐藏模态窗口和遮罩
+          filenameModal.style.display = 'none';
+          modalOverlay.style.display = 'none';
+          // 清空文件输入
+          pdfInput.value = "";
+          selectedFile = null;
+        });
+    });
+
+    // 处理“打开目录”按钮点击事件
+    openDirectoryButton.addEventListener('click', function () {
+      // 显示遮罩和弹出层
+      modalOverlay.style.display = 'block';
+      directoryModal.style.display = 'block';
+
+      // 清空现有的列表
+      pdfList.innerHTML = '';
+
+      // 调用 FastAPI 获取文件列表
+      fetch('http://106.14.113.12:8005/list-pdfs')  // 根据实际部署情况调整URL
+        .then(response => response.json())
+        .then(data => {
+          if (data.success) {
+            const files = data.files;
+            if (files.length === 0) {
+              const listItem = document.createElement('li');
+              listItem.textContent = '没有已上传的 PDF 文档。';
+              pdfList.appendChild(listItem);
+            } else {
+              files.forEach(file => {
+                const listItem = document.createElement('li');
+                listItem.style.marginBottom = '10px';
+
+                const link = document.createElement('a');
+                link.href = `viewer.html?file=${file.url}`;  // 确保 URL 正确
+                link.textContent = file.name;
+                link.style.textDecoration = 'none';
+                link.style.color = '#007bff';
+
+                // 添加点击事件,在当前页面打开
+                link.addEventListener('click', function (event) {
+                  event.preventDefault(); // 防止默认的跳转行为
+                  const fileUrl = link.getAttribute('href');
+                  const redirectUrl = `${fileUrl}`;
+                  window.location.href = redirectUrl; // 在当前页面跳转
+                });
+
+                listItem.appendChild(link);
+                pdfList.appendChild(listItem);
+              });
+            }
+          } else {
+            alert('无法获取文件列表: ' + data.error);
+          }
+        })
+        .catch(error => {
+          console.error('Error:', error);
+          alert('获取文件列表时发生错误');
+        });
+    });
+
+    // 处理“关闭”按钮点击事件
+    closeDirectoryButton.addEventListener('click', function () {
+      directoryModal.style.display = 'none';
+      modalOverlay.style.display = 'none';
+    });
+
+    // 点击遮罩关闭弹出层
+    modalOverlay.addEventListener('click', function () {
+      if (filenameModal.style.display === 'block') {
+        filenameModal.style.display = 'none';
+        uploadButton.focus();
+      }
+      if (directoryModal.style.display === 'block') {
+        directoryModal.style.display = 'none';
+      }
+      modalOverlay.style.display = 'none';
+    });
+
+    //语音控制
+    document.addEventListener('DOMContentLoaded', () => {
+          const audioPlayer = document.getElementById('audio-player');
+  
+          // 函数:发送选中的文本到后端进行 TTS 处理
+          function sendTextToTTS(selectedText) {
+              fetch('http://106.14.113.12:8005/text-to-speech/', {
+                  method: 'POST',
+                  headers: {
+                      'Content-Type': 'application/json'
+                  },
+                  body: JSON.stringify({ user_input: selectedText })
+              })
+              .then(response => {
+                  if (!response.ok) {
+                      throw new Error('语音生成失败');
+                  }
+                  return response.blob();
+              })
+              .then(blob => {
+                  const audioURL = URL.createObjectURL(blob);
+                  audioPlayer.src = audioURL;
+                  audioPlayer.play();
+              })
+              .catch(err => {
+                  alert('生成语音失败');
+                  console.error(err);
+              });
+          }
+  
+          // 监听文本选择事件
+          document.addEventListener('mouseup', () => {
+              const selection = window.getSelection();
+              const selectedText = selection.toString().trim();
+              if (selectedText.length === 0) return;
+  
+              // 清除选择
+              selection.removeAllRanges();
+  
+              // 发送选中的文本到后端
+              // alert(selectedText);
+              sendTextToTTS(selectedText);
+          });
+      });
+  </script>
+
+</body>
+
+</html>

+ 1135 - 0
static/web/bak/viewer.html.bak24-11-7

@@ -0,0 +1,1135 @@
+<!DOCTYPE html>
+
+<html dir="ltr" mozdisallowselectionprint>
+
+<head>
+  <meta charset="utf-8">
+  <meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=1">
+  <meta name="google" content="notranslate">
+  <title>PDF.js viewer</title>
+
+  <!-- This snippet is used in production (included from viewer.html) -->
+  <link rel="resource" type="application/l10n" href="locale/locale.json">
+  <script src="../build/pdf.mjs" type="module"></script>
+
+  <link rel="stylesheet" href="viewer.css">
+
+  <script src="viewer.mjs" type="module"></script>
+
+  <style>
+    /* 模态窗口样式 */
+    .modal {
+      display: none;
+      /* 默认隐藏 */
+      position: fixed;
+      z-index: 10002;
+      left: 50%;
+      top: 50%;
+      transform: translate(-50%, -50%);
+      width: 500px;
+      background-color: white;
+      border: 1px solid #ccc;
+      border-radius: 5px;
+      padding: 20px;
+      box-shadow: 0 5px 15px rgba(0, 0, 0, 0.3);
+    }
+
+    /* 遮罩层样式 */
+    .modal-overlay {
+      display: none;
+      /* 默认隐藏 */
+      position: fixed;
+      top: 0;
+      left: 0;
+      width: 100%;
+      height: 100%;
+      background-color: rgba(0, 0, 0, 0.5);
+      z-index: 10001;
+    }
+
+    /* 按钮样式 */
+    .button {
+      padding: 10px 20px;
+      background-color: #007bff;
+      color: white;
+      border: none;
+      border-radius: 5px;
+      cursor: pointer;
+    }
+
+    .button-secondary {
+      background-color: #6c757d;
+    }
+
+    .button-success {
+      background-color: #28a745;
+    }
+
+    .button-danger {
+      background-color: #dc3545;
+    }
+  </style>
+
+</head>
+
+<body tabindex="0">
+  <!-- 等待指示器 -->
+  <div id="loading-indicator"
+    style="display: none; position: fixed; top: 20px; left: 20px; background: rgba(0,0,0,0.7); color: #fff; padding: 10px; border-radius: 5px;">
+    正在生成语音,请稍候...
+  </div>
+
+  <audio id="audio-player" controls
+    style="position: fixed; width: 190px;height: 50px; bottom: 10px; left: 10px; z-index: 1000;"></audio>
+  <!-- 上传按钮定位在页面底部 -->
+  <button id="uploadButton" style="
+  position: fixed;
+  bottom: 20px;
+  right: 20px;
+  z-index: 1000;
+  padding: 10px 20px;
+  background-color: #007bff;
+  color: white;
+  border: none;
+  border-radius: 5px;
+  cursor: pointer;">
+    上传 PDF
+  </button>
+
+
+  <!-- 隐藏的文件输入 -->
+  <input type="file" id="pdfInput" accept="application/pdf" style="display: none;" />
+
+  <!-- 模态窗口 -->
+  <div id="filenameModal" class="modal">
+    <h3>输入保存的文件名</h3>
+    <input type="text" id="customFileName" placeholder="不含扩展名" style="width: 100%; padding: 5px; margin-top: 10px;" />
+    <div style="margin-top: 20px; text-align: right;">
+      <button id="cancelUpload" class="button button-secondary">取消</button>
+      <button id="confirmUpload" class="button button-success" style="margin-left: 10px;">确认</button>
+    </div>
+  </div>
+
+  <!-- 页面遮罩 -->
+  <div id="modalOverlay" class="modal-overlay"></div>
+
+  <!-- 打开目录按钮 -->
+  <button id="openDirectoryButton" class="button button-success" style="
+    position: fixed;
+    bottom: 20px;
+    right: 130px;  
+    z-index: 1000;
+    ">打开目录
+  </button>
+
+  <!-- 目录弹出层 -->
+  <div id="directoryModal" class="modal">
+    <h2>已上传的 PDF 文档</h2>
+    <ul id="pdfList" style="list-style-type: none; padding: 0;"></ul>
+    <button id="closeDirectoryButton" class="button button-danger" style="margin-top: 20px; width: 100%;">关闭</button>
+  </div>
+
+  <div id="outerContainer">
+    <div id="sidebarContainer">
+      <div id="toolbarSidebar" class="toolbarHorizontalGroup">
+        <div id="toolbarSidebarLeft">
+          <div id="sidebarViewButtons" class="toolbarHorizontalGroup toggled" role="radiogroup">
+            <button id="viewThumbnail" class="toolbarButton toggled" type="button" title="Show Thumbnails" tabindex="0"
+              data-l10n-id="pdfjs-thumbs-button" role="radio" aria-checked="true" aria-controls="thumbnailView">
+              <span data-l10n-id="pdfjs-thumbs-button-label">Thumbnails</span>
+            </button>
+            <button id="viewOutline" class="toolbarButton" type="button"
+              title="Show Document Outline (double-click to expand/collapse all items)" tabindex="0"
+              data-l10n-id="pdfjs-document-outline-button" role="radio" aria-checked="false"
+              aria-controls="outlineView">
+              <span data-l10n-id="pdfjs-document-outline-button-label">Document Outline</span>
+            </button>
+            <button id="viewAttachments" class="toolbarButton" type="button" title="Show Attachments" tabindex="0"
+              data-l10n-id="pdfjs-attachments-button" role="radio" aria-checked="false" aria-controls="attachmentsView">
+              <span data-l10n-id="pdfjs-attachments-button-label">Attachments</span>
+            </button>
+            <button id="viewLayers" class="toolbarButton" type="button"
+              title="Show Layers (double-click to reset all layers to the default state)" tabindex="0"
+              data-l10n-id="pdfjs-layers-button" role="radio" aria-checked="false" aria-controls="layersView">
+              <span data-l10n-id="pdfjs-layers-button-label">Layers</span>
+            </button>
+          </div>
+        </div>
+
+        <div id="toolbarSidebarRight">
+          <div id="outlineOptionsContainer" class="toolbarHorizontalGroup">
+            <div class="verticalToolbarSeparator"></div>
+
+            <button id="currentOutlineItem" class="toolbarButton" type="button" disabled="disabled"
+              title="Find Current Outline Item" tabindex="0" data-l10n-id="pdfjs-current-outline-item-button">
+              <span data-l10n-id="pdfjs-current-outline-item-button-label">Current Outline Item</span>
+            </button>
+          </div>
+        </div>
+      </div>
+      <div id="sidebarContent">
+        <div id="thumbnailView">
+        </div>
+        <div id="outlineView" class="hidden">
+        </div>
+        <div id="attachmentsView" class="hidden">
+        </div>
+        <div id="layersView" class="hidden">
+        </div>
+      </div>
+      <div id="sidebarResizer"></div>
+    </div> <!-- sidebarContainer -->
+
+    <div id="mainContainer">
+      <div class="toolbar">
+        <div id="toolbarContainer">
+          <div id="toolbarViewer" class="toolbarHorizontalGroup">
+            <div id="toolbarViewerLeft" class="toolbarHorizontalGroup">
+              <button id="sidebarToggleButton" class="toolbarButton" type="button" title="Toggle Sidebar" tabindex="0"
+                data-l10n-id="pdfjs-toggle-sidebar-button" aria-expanded="false" aria-haspopup="true"
+                aria-controls="sidebarContainer">
+                <span data-l10n-id="pdfjs-toggle-sidebar-button-label">Toggle Sidebar</span>
+              </button>
+              <div class="toolbarButtonSpacer"></div>
+              <div class="toolbarButtonWithContainer">
+                <button id="viewFindButton" class="toolbarButton" type="button" title="Find in Document" tabindex="0"
+                  data-l10n-id="pdfjs-findbar-button" aria-expanded="false" aria-controls="findbar">
+                  <span data-l10n-id="pdfjs-findbar-button-label">Find</span>
+                </button>
+                <div class="hidden doorHanger toolbarHorizontalGroup" id="findbar">
+                  <div id="findInputContainer" class="toolbarHorizontalGroup">
+                    <span class="loadingInput end toolbarHorizontalGroup">
+                      <input id="findInput" class="toolbarField" title="Find" placeholder="Find in document…"
+                        tabindex="0" data-l10n-id="pdfjs-find-input" aria-invalid="false">
+                    </span>
+                    <div class="toolbarHorizontalGroup">
+                      <button id="findPreviousButton" class="toolbarButton" type="button"
+                        title="Find the previous occurrence of the phrase" tabindex="0"
+                        data-l10n-id="pdfjs-find-previous-button">
+                        <span data-l10n-id="pdfjs-find-previous-button-label">Previous</span>
+                      </button>
+                      <div class="splitToolbarButtonSeparator"></div>
+                      <button id="findNextButton" class="toolbarButton" type="button"
+                        title="Find the next occurrence of the phrase" tabindex="0"
+                        data-l10n-id="pdfjs-find-next-button">
+                        <span data-l10n-id="pdfjs-find-next-button-label">Next</span>
+                      </button>
+                    </div>
+                  </div>
+
+                  <div id="findbarOptionsOneContainer" class="toolbarHorizontalGroup">
+                    <div class="toggleButton toolbarLabel">
+                      <input type="checkbox" id="findHighlightAll" tabindex="0" />
+                      <label for="findHighlightAll" data-l10n-id="pdfjs-find-highlight-checkbox">Highlight All</label>
+                    </div>
+                    <div class="toggleButton toolbarLabel">
+                      <input type="checkbox" id="findMatchCase" tabindex="0" />
+                      <label for="findMatchCase" data-l10n-id="pdfjs-find-match-case-checkbox-label">Match Case</label>
+                    </div>
+                  </div>
+                  <div id="findbarOptionsTwoContainer" class="toolbarHorizontalGroup">
+                    <div class="toggleButton toolbarLabel">
+                      <input type="checkbox" id="findMatchDiacritics" tabindex="0" />
+                      <label for="findMatchDiacritics" data-l10n-id="pdfjs-find-match-diacritics-checkbox-label">Match
+                        Diacritics</label>
+                    </div>
+                    <div class="toggleButton toolbarLabel">
+                      <input type="checkbox" id="findEntireWord" tabindex="0" />
+                      <label for="findEntireWord" data-l10n-id="pdfjs-find-entire-word-checkbox-label">Whole
+                        Words</label>
+                    </div>
+                  </div>
+
+                  <div id="findbarMessageContainer" class="toolbarHorizontalGroup" aria-live="polite">
+                    <span id="findResultsCount" class="toolbarLabel"></span>
+                    <span id="findMsg" class="toolbarLabel"></span>
+                  </div>
+                </div> <!-- findbar -->
+              </div>
+              <div class="toolbarHorizontalGroup hiddenSmallView">
+                <button class="toolbarButton" title="Previous Page" type="button" id="previous" tabindex="0"
+                  data-l10n-id="pdfjs-previous-button">
+                  <span data-l10n-id="pdfjs-previous-button-label">Previous</span>
+                </button>
+                <div class="splitToolbarButtonSeparator"></div>
+                <button class="toolbarButton" type="button" title="Next Page" id="next" tabindex="0"
+                  data-l10n-id="pdfjs-next-button">
+                  <span data-l10n-id="pdfjs-next-button-label">Next</span>
+                </button>
+              </div>
+              <div class="toolbarHorizontalGroup">
+                <span class="loadingInput start toolbarHorizontalGroup">
+                  <input type="number" id="pageNumber" class="toolbarField" title="Page" value="1" min="1" tabindex="0"
+                    data-l10n-id="pdfjs-page-input" autocomplete="off">
+                </span>
+                <span id="numPages" class="toolbarLabel"></span>
+              </div>
+            </div>
+            <div id="toolbarViewerMiddle" class="toolbarHorizontalGroup">
+              <div class="toolbarHorizontalGroup">
+                <button id="zoomOutButton" class="toolbarButton" type="button" title="Zoom Out" tabindex="0"
+                  data-l10n-id="pdfjs-zoom-out-button">
+                  <span data-l10n-id="pdfjs-zoom-out-button-label">Zoom Out</span>
+                </button>
+                <div class="splitToolbarButtonSeparator"></div>
+                <button id="zoomInButton" class="toolbarButton" type="button" title="Zoom In" tabindex="0"
+                  data-l10n-id="pdfjs-zoom-in-button">
+                  <span data-l10n-id="pdfjs-zoom-in-button-label">Zoom In</span>
+                </button>
+              </div>
+              <span id="scaleSelectContainer" class="dropdownToolbarButton">
+                <select id="scaleSelect" title="Zoom" tabindex="0" data-l10n-id="pdfjs-zoom-select">
+                  <option id="pageAutoOption" title="" value="auto" selected="selected"
+                    data-l10n-id="pdfjs-page-scale-auto">Automatic Zoom</option>
+                  <option id="pageActualOption" title="" value="page-actual" data-l10n-id="pdfjs-page-scale-actual">
+                    Actual Size</option>
+                  <option id="pageFitOption" title="" value="page-fit" data-l10n-id="pdfjs-page-scale-fit">Page Fit
+                  </option>
+                  <option id="pageWidthOption" title="" value="page-width" data-l10n-id="pdfjs-page-scale-width">Page
+                    Width</option>
+                  <option id="customScaleOption" title="" value="custom" disabled="disabled" hidden="true"
+                    data-l10n-id="pdfjs-page-scale-percent" data-l10n-args='{ "scale": 0 }'>0%</option>
+                  <option title="" value="0.5" data-l10n-id="pdfjs-page-scale-percent" data-l10n-args='{ "scale": 50 }'>
+                    50%</option>
+                  <option title="" value="0.75" data-l10n-id="pdfjs-page-scale-percent"
+                    data-l10n-args='{ "scale": 75 }'>
+                    75%</option>
+                  <option title="" value="1" data-l10n-id="pdfjs-page-scale-percent" data-l10n-args='{ "scale": 100 }'>
+                    100%</option>
+                  <option title="" value="1.25" data-l10n-id="pdfjs-page-scale-percent"
+                    data-l10n-args='{ "scale": 125 }'>
+                    125%</option>
+                  <option title="" value="1.5" data-l10n-id="pdfjs-page-scale-percent"
+                    data-l10n-args='{ "scale": 150 }'>
+                    150%</option>
+                  <option title="" value="2" data-l10n-id="pdfjs-page-scale-percent" data-l10n-args='{ "scale": 200 }'>
+                    200%</option>
+                  <option title="" value="3" data-l10n-id="pdfjs-page-scale-percent" data-l10n-args='{ "scale": 300 }'>
+                    300%</option>
+                  <option title="" value="4" data-l10n-id="pdfjs-page-scale-percent" data-l10n-args='{ "scale": 400 }'>
+                    400%</option>
+                </select>
+              </span>
+            </div>
+            <div id="toolbarViewerRight" class="toolbarHorizontalGroup">
+              <div id="editorModeButtons" class="toolbarHorizontalGroup" role="radiogroup">
+                <div id="editorHighlight" class="toolbarButtonWithContainer">
+                  <button id="editorHighlightButton" class="toolbarButton" type="button" disabled="disabled"
+                    title="Highlight" role="radio" aria-expanded="false" aria-haspopup="true"
+                    aria-controls="editorHighlightParamsToolbar" tabindex="0"
+                    data-l10n-id="pdfjs-editor-highlight-button">
+                    <span data-l10n-id="pdfjs-editor-highlight-button-label">Highlight</span>
+                  </button>
+                  <div class="editorParamsToolbar hidden doorHangerRight" id="editorHighlightParamsToolbar">
+                    <div id="highlightParamsToolbarContainer" class="editorParamsToolbarContainer">
+                      <div id="editorHighlightColorPicker" class="colorPicker">
+                        <span id="highlightColorPickerLabel" class="editorParamsLabel"
+                          data-l10n-id="pdfjs-editor-highlight-colorpicker-label">Highlight color</span>
+                      </div>
+                      <div id="editorHighlightThickness">
+                        <label for="editorFreeHighlightThickness" class="editorParamsLabel"
+                          data-l10n-id="pdfjs-editor-free-highlight-thickness-input">Thickness</label>
+                        <div class="thicknessPicker">
+                          <input type="range" id="editorFreeHighlightThickness" class="editorParamsSlider"
+                            data-l10n-id="pdfjs-editor-free-highlight-thickness-title" value="12" min="8" max="24"
+                            step="1" tabindex="0">
+                        </div>
+                      </div>
+                      <div id="editorHighlightVisibility">
+                        <div class="divider"></div>
+                        <div class="toggler">
+                          <label for="editorHighlightShowAll" class="editorParamsLabel"
+                            data-l10n-id="pdfjs-editor-highlight-show-all-button-label">Show all</label>
+                          <button id="editorHighlightShowAll" class="toggle-button" type="button"
+                            data-l10n-id="pdfjs-editor-highlight-show-all-button" aria-pressed="true"
+                            tabindex="0"></button>
+                        </div>
+                      </div>
+                    </div>
+                  </div>
+                </div>
+                <div id="editorFreeText" class="toolbarButtonWithContainer">
+                  <button id="editorFreeTextButton" class="toolbarButton" type="button" disabled="disabled" title="Text"
+                    role="radio" aria-expanded="false" aria-haspopup="true" aria-controls="editorFreeTextParamsToolbar"
+                    tabindex="0" data-l10n-id="pdfjs-editor-free-text-button">
+                    <span data-l10n-id="pdfjs-editor-free-text-button-label">Text</span>
+                  </button>
+                  <div class="editorParamsToolbar hidden doorHangerRight" id="editorFreeTextParamsToolbar">
+                    <div class="editorParamsToolbarContainer">
+                      <div class="editorParamsSetter">
+                        <label for="editorFreeTextColor" class="editorParamsLabel"
+                          data-l10n-id="pdfjs-editor-free-text-color-input">Color</label>
+                        <input type="color" id="editorFreeTextColor" class="editorParamsColor" tabindex="0">
+                      </div>
+                      <div class="editorParamsSetter">
+                        <label for="editorFreeTextFontSize" class="editorParamsLabel"
+                          data-l10n-id="pdfjs-editor-free-text-size-input">Size</label>
+                        <input type="range" id="editorFreeTextFontSize" class="editorParamsSlider" value="10" min="5"
+                          max="100" step="1" tabindex="0">
+                      </div>
+                    </div>
+                  </div>
+                </div>
+                <div id="editorInk" class="toolbarButtonWithContainer">
+                  <button id="editorInkButton" class="toolbarButton" type="button" disabled="disabled" title="Draw"
+                    role="radio" aria-expanded="false" aria-haspopup="true" aria-controls="editorInkParamsToolbar"
+                    tabindex="0" data-l10n-id="pdfjs-editor-ink-button">
+                    <span data-l10n-id="pdfjs-editor-ink-button-label">Draw</span>
+                  </button>
+                  <div class="editorParamsToolbar hidden doorHangerRight" id="editorInkParamsToolbar">
+                    <div class="editorParamsToolbarContainer">
+                      <div class="editorParamsSetter">
+                        <label for="editorInkColor" class="editorParamsLabel"
+                          data-l10n-id="pdfjs-editor-ink-color-input">Color</label>
+                        <input type="color" id="editorInkColor" class="editorParamsColor" tabindex="0">
+                      </div>
+                      <div class="editorParamsSetter">
+                        <label for="editorInkThickness" class="editorParamsLabel"
+                          data-l10n-id="pdfjs-editor-ink-thickness-input">Thickness</label>
+                        <input type="range" id="editorInkThickness" class="editorParamsSlider" value="1" min="1"
+                          max="20" step="1" tabindex="0">
+                      </div>
+                      <div class="editorParamsSetter">
+                        <label for="editorInkOpacity" class="editorParamsLabel"
+                          data-l10n-id="pdfjs-editor-ink-opacity-input">Opacity</label>
+                        <input type="range" id="editorInkOpacity" class="editorParamsSlider" value="100" min="1"
+                          max="100" step="1" tabindex="0">
+                      </div>
+                    </div>
+                  </div>
+                </div>
+                <div id="editorStamp" class="toolbarButtonWithContainer">
+                  <button id="editorStampButton" class="toolbarButton" type="button" disabled="disabled"
+                    title="Add or edit images" role="radio" aria-expanded="false" aria-haspopup="true"
+                    aria-controls="editorStampParamsToolbar" tabindex="0" data-l10n-id="pdfjs-editor-stamp-button">
+                    <span data-l10n-id="pdfjs-editor-stamp-button-label">Add or edit images</span>
+                  </button>
+                  <div class="editorParamsToolbar hidden doorHangerRight menu" id="editorStampParamsToolbar">
+                    <div class="menuContainer">
+                      <button id="editorStampAddImage" class="toolbarButton labeled" type="button" title="Add image"
+                        tabindex="0" data-l10n-id="pdfjs-editor-stamp-add-image-button">
+                        <span class="editorParamsLabel" data-l10n-id="pdfjs-editor-stamp-add-image-button-label">Add
+                          image</span>
+                      </button>
+                    </div>
+                  </div>
+                </div>
+              </div>
+
+              <div id="editorModeSeparator" class="verticalToolbarSeparator"></div>
+
+              <div class="toolbarHorizontalGroup hiddenMediumView">
+                <button id="printButton" class="toolbarButton" type="button" title="Print" tabindex="0"
+                  data-l10n-id="pdfjs-print-button">
+                  <span data-l10n-id="pdfjs-print-button-label">Print</span>
+                </button>
+
+                <button id="downloadButton" class="toolbarButton" type="button" title="Save" tabindex="0"
+                  data-l10n-id="pdfjs-save-button">
+                  <span data-l10n-id="pdfjs-save-button-label">Save</span>
+                </button>
+              </div>
+
+              <div class="verticalToolbarSeparator hiddenMediumView"></div>
+
+              <div id="secondaryToolbarToggle" class="toolbarButtonWithContainer">
+                <button id="secondaryToolbarToggleButton" class="toolbarButton" type="button" title="Tools" tabindex="0"
+                  data-l10n-id="pdfjs-tools-button" aria-expanded="false" aria-haspopup="true"
+                  aria-controls="secondaryToolbar">
+                  <span data-l10n-id="pdfjs-tools-button-label">Tools</span>
+                </button>
+                <div id="secondaryToolbar" class="hidden doorHangerRight menu">
+                  <div id="secondaryToolbarButtonContainer" class="menuContainer">
+                    <button id="secondaryOpenFile" class="toolbarButton labeled" type="button" title="Open File"
+                      tabindex="0" data-l10n-id="pdfjs-open-file-button">
+                      <span data-l10n-id="pdfjs-open-file-button-label">Open</span>
+                    </button>
+
+                    <div class="visibleMediumView">
+                      <button id="secondaryPrint" class="toolbarButton labeled" type="button" title="Print" tabindex="0"
+                        data-l10n-id="pdfjs-print-button">
+                        <span data-l10n-id="pdfjs-print-button-label">Print</span>
+                      </button>
+
+                      <button id="secondaryDownload" class="toolbarButton labeled" type="button" title="Save"
+                        tabindex="0" data-l10n-id="pdfjs-save-button">
+                        <span data-l10n-id="pdfjs-save-button-label">Save</span>
+                      </button>
+
+                    </div>
+
+                    <div class="horizontalToolbarSeparator"></div>
+
+                    <button id="presentationMode" class="toolbarButton labeled" type="button"
+                      title="Switch to Presentation Mode" tabindex="0" data-l10n-id="pdfjs-presentation-mode-button">
+                      <span data-l10n-id="pdfjs-presentation-mode-button-label">Presentation Mode</span>
+                    </button>
+
+                    <a href="#" id="viewBookmark" class="toolbarButton labeled"
+                      title="Current Page (View URL from Current Page)" tabindex="0"
+                      data-l10n-id="pdfjs-bookmark-button">
+                      <span data-l10n-id="pdfjs-bookmark-button-label">Current Page</span>
+                    </a>
+
+                    <div id="viewBookmarkSeparator" class="horizontalToolbarSeparator"></div>
+
+                    <button id="firstPage" class="toolbarButton labeled" type="button" title="Go to First Page"
+                      tabindex="0" data-l10n-id="pdfjs-first-page-button">
+                      <span data-l10n-id="pdfjs-first-page-button-label">Go to First Page</span>
+                    </button>
+                    <button id="lastPage" class="toolbarButton labeled" type="button" title="Go to Last Page"
+                      tabindex="0" data-l10n-id="pdfjs-last-page-button">
+                      <span data-l10n-id="pdfjs-last-page-button-label">Go to Last Page</span>
+                    </button>
+
+                    <div class="horizontalToolbarSeparator"></div>
+
+                    <button id="pageRotateCw" class="toolbarButton labeled" type="button" title="Rotate Clockwise"
+                      tabindex="0" data-l10n-id="pdfjs-page-rotate-cw-button">
+                      <span data-l10n-id="pdfjs-page-rotate-cw-button-label">Rotate Clockwise</span>
+                    </button>
+                    <button id="pageRotateCcw" class="toolbarButton labeled" type="button"
+                      title="Rotate Counterclockwise" tabindex="0" data-l10n-id="pdfjs-page-rotate-ccw-button">
+                      <span data-l10n-id="pdfjs-page-rotate-ccw-button-label">Rotate Counterclockwise</span>
+                    </button>
+
+                    <div class="horizontalToolbarSeparator"></div>
+
+                    <div id="cursorToolButtons" role="radiogroup">
+                      <button id="cursorSelectTool" class="toolbarButton labeled toggled" type="button"
+                        title="Enable Text Selection Tool" tabindex="0"
+                        data-l10n-id="pdfjs-cursor-text-select-tool-button" role="radio" aria-checked="true">
+                        <span data-l10n-id="pdfjs-cursor-text-select-tool-button-label">Text Selection Tool</span>
+                      </button>
+                      <button id="cursorHandTool" class="toolbarButton labeled" type="button" title="Enable Hand Tool"
+                        tabindex="0" data-l10n-id="pdfjs-cursor-hand-tool-button" role="radio" aria-checked="false">
+                        <span data-l10n-id="pdfjs-cursor-hand-tool-button-label">Hand Tool</span>
+                      </button>
+                    </div>
+
+                    <div class="horizontalToolbarSeparator"></div>
+
+                    <div id="scrollModeButtons" role="radiogroup">
+                      <button id="scrollPage" class="toolbarButton labeled" type="button" title="Use Page Scrolling"
+                        tabindex="0" data-l10n-id="pdfjs-scroll-page-button" role="radio" aria-checked="false">
+                        <span data-l10n-id="pdfjs-scroll-page-button-label">Page Scrolling</span>
+                      </button>
+                      <button id="scrollVertical" class="toolbarButton labeled toggled" type="button"
+                        title="Use Vertical Scrolling" tabindex="0" data-l10n-id="pdfjs-scroll-vertical-button"
+                        role="radio" aria-checked="true">
+                        <span data-l10n-id="pdfjs-scroll-vertical-button-label">Vertical Scrolling</span>
+                      </button>
+                      <button id="scrollHorizontal" class="toolbarButton labeled" type="button"
+                        title="Use Horizontal Scrolling" tabindex="0" data-l10n-id="pdfjs-scroll-horizontal-button"
+                        role="radio" aria-checked="false">
+                        <span data-l10n-id="pdfjs-scroll-horizontal-button-label">Horizontal Scrolling</span>
+                      </button>
+                      <button id="scrollWrapped" class="toolbarButton labeled" type="button"
+                        title="Use Wrapped Scrolling" tabindex="0" data-l10n-id="pdfjs-scroll-wrapped-button"
+                        role="radio" aria-checked="false">
+                        <span data-l10n-id="pdfjs-scroll-wrapped-button-label">Wrapped Scrolling</span>
+                      </button>
+                    </div>
+
+                    <div class="horizontalToolbarSeparator"></div>
+
+                    <div id="spreadModeButtons" role="radiogroup">
+                      <button id="spreadNone" class="toolbarButton labeled toggled" type="button"
+                        title="Do not join page spreads" tabindex="0" data-l10n-id="pdfjs-spread-none-button"
+                        role="radio" aria-checked="true">
+                        <span data-l10n-id="pdfjs-spread-none-button-label">No Spreads</span>
+                      </button>
+                      <button id="spreadOdd" class="toolbarButton labeled" type="button"
+                        title="Join page spreads starting with odd-numbered pages" tabindex="0"
+                        data-l10n-id="pdfjs-spread-odd-button" role="radio" aria-checked="false">
+                        <span data-l10n-id="pdfjs-spread-odd-button-label">Odd Spreads</span>
+                      </button>
+                      <button id="spreadEven" class="toolbarButton labeled" type="button"
+                        title="Join page spreads starting with even-numbered pages" tabindex="0"
+                        data-l10n-id="pdfjs-spread-even-button" role="radio" aria-checked="false">
+                        <span data-l10n-id="pdfjs-spread-even-button-label">Even Spreads</span>
+                      </button>
+                    </div>
+
+                    <div id="imageAltTextSettingsSeparator" class="horizontalToolbarSeparator hidden"></div>
+                    <button id="imageAltTextSettings" type="button" class="toolbarButton labeled hidden"
+                      title="Image alt text settings" tabindex="0" data-l10n-id="pdfjs-image-alt-text-settings-button"
+                      aria-controls="altTextSettingsDialog">
+                      <span data-l10n-id="pdfjs-image-alt-text-settings-button-label">Image alt text settings</span>
+                    </button>
+
+                    <div class="horizontalToolbarSeparator"></div>
+
+                    <button id="documentProperties" class="toolbarButton labeled" type="button"
+                      title="Document Properties…" tabindex="0" data-l10n-id="pdfjs-document-properties-button"
+                      aria-controls="documentPropertiesDialog">
+                      <span data-l10n-id="pdfjs-document-properties-button-label">Document Properties…</span>
+                    </button>
+                  </div>
+                </div> <!-- secondaryToolbar -->
+              </div>
+            </div>
+          </div>
+          <div id="loadingBar">
+            <div class="progress">
+              <div class="glimmer">
+              </div>
+            </div>
+          </div>
+        </div>
+      </div>
+
+      <div id="viewerContainer" tabindex="0">
+        <div id="viewer" class="pdfViewer"></div>
+      </div>
+    </div> <!-- mainContainer -->
+
+    <div id="dialogContainer">
+      <dialog id="passwordDialog">
+        <div class="row">
+          <label for="password" id="passwordText" data-l10n-id="pdfjs-password-label">Enter the password to open this
+            PDF file:</label>
+        </div>
+        <div class="row">
+          <input type="password" id="password" class="toolbarField">
+        </div>
+        <div class="buttonRow">
+          <button id="passwordCancel" class="dialogButton" type="button"><span
+              data-l10n-id="pdfjs-password-cancel-button">Cancel</span></button>
+          <button id="passwordSubmit" class="dialogButton" type="button"><span
+              data-l10n-id="pdfjs-password-ok-button">OK</span></button>
+        </div>
+      </dialog>
+      <dialog id="documentPropertiesDialog">
+        <div class="row">
+          <span id="fileNameLabel" data-l10n-id="pdfjs-document-properties-file-name">File name:</span>
+          <p id="fileNameField" aria-labelledby="fileNameLabel">-</p>
+        </div>
+        <div class="row">
+          <span id="fileSizeLabel" data-l10n-id="pdfjs-document-properties-file-size">File size:</span>
+          <p id="fileSizeField" aria-labelledby="fileSizeLabel">-</p>
+        </div>
+        <div class="separator"></div>
+        <div class="row">
+          <span id="titleLabel" data-l10n-id="pdfjs-document-properties-title">Title:</span>
+          <p id="titleField" aria-labelledby="titleLabel">-</p>
+        </div>
+        <div class="row">
+          <span id="authorLabel" data-l10n-id="pdfjs-document-properties-author">Author:</span>
+          <p id="authorField" aria-labelledby="authorLabel">-</p>
+        </div>
+        <div class="row">
+          <span id="subjectLabel" data-l10n-id="pdfjs-document-properties-subject">Subject:</span>
+          <p id="subjectField" aria-labelledby="subjectLabel">-</p>
+        </div>
+        <div class="row">
+          <span id="keywordsLabel" data-l10n-id="pdfjs-document-properties-keywords">Keywords:</span>
+          <p id="keywordsField" aria-labelledby="keywordsLabel">-</p>
+        </div>
+        <div class="row">
+          <span id="creationDateLabel" data-l10n-id="pdfjs-document-properties-creation-date">Creation Date:</span>
+          <p id="creationDateField" aria-labelledby="creationDateLabel">-</p>
+        </div>
+        <div class="row">
+          <span id="modificationDateLabel" data-l10n-id="pdfjs-document-properties-modification-date">Modification
+            Date:</span>
+          <p id="modificationDateField" aria-labelledby="modificationDateLabel">-</p>
+        </div>
+        <div class="row">
+          <span id="creatorLabel" data-l10n-id="pdfjs-document-properties-creator">Creator:</span>
+          <p id="creatorField" aria-labelledby="creatorLabel">-</p>
+        </div>
+        <div class="separator"></div>
+        <div class="row">
+          <span id="producerLabel" data-l10n-id="pdfjs-document-properties-producer">PDF Producer:</span>
+          <p id="producerField" aria-labelledby="producerLabel">-</p>
+        </div>
+        <div class="row">
+          <span id="versionLabel" data-l10n-id="pdfjs-document-properties-version">PDF Version:</span>
+          <p id="versionField" aria-labelledby="versionLabel">-</p>
+        </div>
+        <div class="row">
+          <span id="pageCountLabel" data-l10n-id="pdfjs-document-properties-page-count">Page Count:</span>
+          <p id="pageCountField" aria-labelledby="pageCountLabel">-</p>
+        </div>
+        <div class="row">
+          <span id="pageSizeLabel" data-l10n-id="pdfjs-document-properties-page-size">Page Size:</span>
+          <p id="pageSizeField" aria-labelledby="pageSizeLabel">-</p>
+        </div>
+        <div class="separator"></div>
+        <div class="row">
+          <span id="linearizedLabel" data-l10n-id="pdfjs-document-properties-linearized">Fast Web View:</span>
+          <p id="linearizedField" aria-labelledby="linearizedLabel">-</p>
+        </div>
+        <div class="buttonRow">
+          <button id="documentPropertiesClose" class="dialogButton" type="button"><span
+              data-l10n-id="pdfjs-document-properties-close-button">Close</span></button>
+        </div>
+      </dialog>
+      <dialog class="dialog altText" id="altTextDialog" aria-labelledby="dialogLabel"
+        aria-describedby="dialogDescription">
+        <div id="altTextContainer" class="mainContainer">
+          <div id="overallDescription">
+            <span id="dialogLabel" data-l10n-id="pdfjs-editor-alt-text-dialog-label" class="title">Choose an
+              option</span>
+            <span id="dialogDescription" data-l10n-id="pdfjs-editor-alt-text-dialog-description">
+              Alt text (alternative text) helps when people can’t see the image or when it doesn’t load.
+            </span>
+          </div>
+          <div id="addDescription">
+            <div class="radio">
+              <div class="radioButton">
+                <input type="radio" id="descriptionButton" name="altTextOption" tabindex="0"
+                  aria-describedby="descriptionAreaLabel" checked>
+                <label for="descriptionButton" data-l10n-id="pdfjs-editor-alt-text-add-description-label">Add a
+                  description</label>
+              </div>
+              <div class="radioLabel">
+                <span id="descriptionAreaLabel" data-l10n-id="pdfjs-editor-alt-text-add-description-description">
+                  Aim for 1-2 sentences that describe the subject, setting, or actions.
+                </span>
+              </div>
+            </div>
+            <div class="descriptionArea">
+              <textarea id="descriptionTextarea"
+                placeholder="For example, “A young man sits down at a table to eat a meal”"
+                aria-labelledby="descriptionAreaLabel" data-l10n-id="pdfjs-editor-alt-text-textarea"
+                tabindex="0"></textarea>
+            </div>
+          </div>
+          <div id="markAsDecorative">
+            <div class="radio">
+              <div class="radioButton">
+                <input type="radio" id="decorativeButton" name="altTextOption" aria-describedby="decorativeLabel">
+                <label for="decorativeButton" data-l10n-id="pdfjs-editor-alt-text-mark-decorative-label">Mark as
+                  decorative</label>
+              </div>
+              <div class="radioLabel">
+                <span id="decorativeLabel" data-l10n-id="pdfjs-editor-alt-text-mark-decorative-description">
+                  This is used for ornamental images, like borders or watermarks.
+                </span>
+              </div>
+            </div>
+          </div>
+          <div id="buttons">
+            <button id="altTextCancel" class="secondaryButton" type="button" tabindex="0"><span
+                data-l10n-id="pdfjs-editor-alt-text-cancel-button">Cancel</span></button>
+            <button id="altTextSave" class="primaryButton" type="button" tabindex="0"><span
+                data-l10n-id="pdfjs-editor-alt-text-save-button">Save</span></button>
+          </div>
+        </div>
+      </dialog>
+      <dialog class="dialog newAltText" id="newAltTextDialog" aria-labelledby="newAltTextTitle"
+        aria-describedby="newAltTextDescription" tabindex="0">
+        <div id="newAltTextContainer" class="mainContainer">
+          <div class="title">
+            <span id="newAltTextTitle" data-l10n-id="pdfjs-editor-new-alt-text-dialog-edit-label" role="sectionhead"
+              tabindex="0">Edit alt text (image description)</span>
+          </div>
+          <div id="mainContent">
+            <div id="descriptionAndSettings">
+              <div id="descriptionInstruction">
+                <div id="newAltTextDescriptionContainer">
+                  <div class="altTextSpinner" role="status" aria-live="polite"></div>
+                  <textarea id="newAltTextDescriptionTextarea" placeholder="Write your description here…"
+                    aria-labelledby="descriptionAreaLabel" data-l10n-id="pdfjs-editor-new-alt-text-textarea"
+                    tabindex="0"></textarea>
+                </div>
+                <span id="newAltTextDescription" role="note" data-l10n-id="pdfjs-editor-new-alt-text-description">Short
+                  description for people who can’t see the image or when the image doesn’t load.</span>
+                <div id="newAltTextDisclaimer" role="note">
+                  <div><span data-l10n-id="pdfjs-editor-new-alt-text-disclaimer1">This alt text was created
+                      automatically and may be inaccurate.</span> <a
+                      href="https://support.mozilla.org/en-US/kb/pdf-alt-text" target="_blank" rel="noopener noreferrer"
+                      id="newAltTextLearnMore" data-l10n-id="pdfjs-editor-new-alt-text-disclaimer-learn-more-url"
+                      tabindex="0">Learn more</a></div>
+                </div>
+              </div>
+              <div id="newAltTextCreateAutomatically" class="toggler">
+                <button id="newAltTextCreateAutomaticallyButton" class="toggle-button" type="button" aria-pressed="true"
+                  tabindex="0"></button>
+                <label for="newAltTextCreateAutomaticallyButton" class="togglerLabel"
+                  data-l10n-id="pdfjs-editor-new-alt-text-create-automatically-button-label">Create alt text
+                  automatically</label>
+              </div>
+              <div id="newAltTextDownloadModel" class="hidden">
+                <span id="newAltTextDownloadModelDescription"
+                  data-l10n-id="pdfjs-editor-new-alt-text-ai-model-downloading-progress" aria-valuemin="0"
+                  data-l10n-args='{ "totalSize": 0, "downloadedSize": 0 }'>Downloading alt text AI model (0 of 0
+                  MB)</span>
+              </div>
+            </div>
+            <div id="newAltTextImagePreview"></div>
+          </div>
+          <div id="newAltTextError" class="messageBar">
+            <div>
+              <div>
+                <span class="title" data-l10n-id="pdfjs-editor-new-alt-text-error-title">Couldn’t create alt text
+                  automatically</span>
+                <span class="description" data-l10n-id="pdfjs-editor-new-alt-text-error-description">Please write your
+                  own alt text or try again later.</span>
+              </div>
+              <button id="newAltTextCloseButton" class="closeButton" type="button" tabindex="0" title="Close"><span
+                  data-l10n-id="pdfjs-editor-new-alt-text-error-close-button">Close</span></button>
+            </div>
+          </div>
+          <div id="newAltTextButtons" class="dialogButtonsGroup">
+            <button id="newAltTextCancel" type="button" class="secondaryButton hidden" tabindex="0"><span
+                data-l10n-id="pdfjs-editor-alt-text-cancel-button">Cancel</span></button>
+            <button id="newAltTextNotNow" type="button" class="secondaryButton" tabindex="0"><span
+                data-l10n-id="pdfjs-editor-new-alt-text-not-now-button">Not now</span></button>
+            <button id="newAltTextSave" type="button" class="primaryButton" tabindex="0"><span
+                data-l10n-id="pdfjs-editor-alt-text-save-button">Save</span></button>
+          </div>
+        </div>
+      </dialog>
+
+      <dialog class="dialog" id="altTextSettingsDialog" aria-labelledby="altTextSettingsTitle">
+        <div id="altTextSettingsContainer" class="mainContainer">
+          <div class="title">
+            <span id="altTextSettingsTitle" data-l10n-id="pdfjs-editor-alt-text-settings-dialog-label"
+              role="sectionhead" tabindex="0" class="title">Image alt text settings</span>
+          </div>
+          <div id="automaticAltText">
+            <span data-l10n-id="pdfjs-editor-alt-text-settings-automatic-title">Automatic alt text</span>
+            <div id="automaticSettings">
+              <div id="createModelSetting">
+                <div class="toggler">
+                  <button id="createModelButton" type="button" class="toggle-button" aria-pressed="true"
+                    tabindex="0"></button>
+                  <label for="createModelButton" class="togglerLabel"
+                    data-l10n-id="pdfjs-editor-alt-text-settings-create-model-button-label">Create alt text
+                    automatically</label>
+                </div>
+                <div id="createModelDescription" class="description">
+                  <span data-l10n-id="pdfjs-editor-alt-text-settings-create-model-description">Suggests descriptions to
+                    help people who can’t see the image or when the image doesn’t load.</span> <a
+                    href="https://support.mozilla.org/en-US/kb/pdf-alt-text" target="_blank" rel="noopener noreferrer"
+                    id="altTextSettingsLearnMore" data-l10n-id="pdfjs-editor-new-alt-text-disclaimer-learn-more-url"
+                    tabindex="0">Learn more</a>
+                </div>
+              </div>
+              <div id="aiModelSettings">
+                <div>
+                  <span data-l10n-id="pdfjs-editor-alt-text-settings-download-model-label"
+                    data-l10n-args='{ "totalSize": 180 }'>Alt text AI model (180MB)</span>
+                  <div id="aiModelDescription" class="description">
+                    <span data-l10n-id="pdfjs-editor-alt-text-settings-ai-model-description">Runs locally on your device
+                      so your data stays private. Required for automatic alt text.</span>
+                  </div>
+                </div>
+                <button id="deleteModelButton" type="button" class="secondaryButton" tabindex="0"><span
+                    data-l10n-id="pdfjs-editor-alt-text-settings-delete-model-button">Delete</span></button>
+                <button id="downloadModelButton" type="button" class="secondaryButton" tabindex="0"><span
+                    data-l10n-id="pdfjs-editor-alt-text-settings-download-model-button">Download</span></button>
+              </div>
+            </div>
+          </div>
+          <div class="dialogSeparator"></div>
+          <div id="altTextEditor">
+            <span data-l10n-id="pdfjs-editor-alt-text-settings-editor-title">Alt text editor</span>
+            <div id="showAltTextEditor">
+              <div class="toggler">
+                <button id="showAltTextDialogButton" type="button" class="toggle-button" aria-pressed="true"
+                  tabindex="0"></button>
+                <label for="showAltTextDialogButton" class="togglerLabel"
+                  data-l10n-id="pdfjs-editor-alt-text-settings-show-dialog-button-label">Show alt text editor right away
+                  when adding an image</label>
+              </div>
+              <div id="showAltTextDialogDescription" class="description">
+                <span data-l10n-id="pdfjs-editor-alt-text-settings-show-dialog-description">Helps you make sure all your
+                  images have alt text.</span>
+              </div>
+            </div>
+          </div>
+          <div id="buttons" class="dialogButtonsGroup">
+            <button id="altTextSettingsCloseButton" type="button" class="primaryButton" tabindex="0"><span
+                data-l10n-id="pdfjs-editor-alt-text-settings-close-button">Close</span></button>
+          </div>
+        </div>
+      </dialog>
+      <dialog id="printServiceDialog" style="min-width: 200px;">
+        <div class="row">
+          <span data-l10n-id="pdfjs-print-progress-message">Preparing document for printing…</span>
+        </div>
+        <div class="row">
+          <progress value="0" max="100"></progress>
+          <span data-l10n-id="pdfjs-print-progress-percent" data-l10n-args='{ "progress": 0 }'
+            class="relative-progress">0%</span>
+        </div>
+        <div class="buttonRow">
+          <button id="printCancel" class="dialogButton" type="button"><span
+              data-l10n-id="pdfjs-print-progress-close-button">Cancel</span></button>
+        </div>
+      </dialog>
+    </div> <!-- dialogContainer -->
+
+  </div> <!-- outerContainer -->
+  <div id="printContainer"></div>
+  <!-- <script>
+      document.getElementById('uploadButton').addEventListener('click', function() {
+          // 触发隐藏的文件输入
+          document.getElementById('pdfInput').click();
+      });
+      
+      document.getElementById('pdfInput').addEventListener('change', function(event) {
+          const file = event.target.files[0];
+          if (file && file.type === 'application/pdf') {
+              const formData = new FormData();
+              formData.append('file', file);
+      
+              // 调用 FastAPI 上传接口
+              fetch('http://106.14.113.12:8005/upload-pdf', {  // 根据实际部署情况调整URL
+                  method: 'POST',
+                  body: formData
+              })
+              .then(response => response.json())
+              .then(data => {
+                  if (data.success) {
+                      const uploadedUrl = data.url;
+                      // 加载上传后的 PDF
+                      PDFViewerApplication.open(uploadedUrl);
+                  } else {
+                      alert('上传失败: ' + data.error);
+                  }
+              })
+              .catch(error => {
+                  console.error('Error:', error);
+                  alert('上传过程中发生错误');
+              });
+          } else {
+              alert('请选择一个 PDF 文件');
+          }
+      });
+      </script> -->
+
+
+
+
+  <script>
+    const uploadButton = document.getElementById('uploadButton');
+    const pdfInput = document.getElementById('pdfInput');
+    const filenameModal = document.getElementById('filenameModal');
+    const modalOverlay = document.getElementById('modalOverlay');
+    const confirmUploadButton = document.getElementById('confirmUpload');
+    const cancelUploadButton = document.getElementById('cancelUpload');
+    const customFileNameInput = document.getElementById('customFileName');
+    const openDirectoryButton = document.getElementById('openDirectoryButton');
+    const directoryModal = document.getElementById('directoryModal');
+    const closeDirectoryButton = document.getElementById('closeDirectoryButton');
+    const pdfList = document.getElementById('pdfList');
+
+    let selectedFile = null;
+
+    // 上传按钮点击事件
+    uploadButton.addEventListener('click', function () {
+      // 触发隐藏的文件输入
+      pdfInput.click();
+    });
+
+    // 文件选择事件
+    pdfInput.addEventListener('change', function (event) {
+      const file = event.target.files[0];
+      if (file && file.type === 'application/pdf') {
+        selectedFile = file;
+        // 显示模态窗口
+        modalOverlay.style.display = 'block';
+        filenameModal.style.display = 'block';
+        customFileNameInput.value = ""; // 清空之前的输入
+        customFileNameInput.focus();
+      } else {
+        alert('请选择一个 PDF 文件');
+      }
+    });
+
+    // 取消上传
+    cancelUploadButton.addEventListener('click', function () {
+      // 隐藏模态窗口和遮罩
+      filenameModal.style.display = 'none';
+      modalOverlay.style.display = 'none';
+      // 清空文件输入
+      pdfInput.value = "";
+      selectedFile = null;
+    });
+
+    // 确认上传
+    confirmUploadButton.addEventListener('click', function () {
+      const customFileName = customFileNameInput.value.trim();
+      if (customFileName === "") {
+        alert('请输入保存的文件名');
+        customFileNameInput.focus();
+        return;
+      }
+
+      // 检查文件名是否包含非法字符
+      const invalidChars = /[\\/:"*?<>|]+/;
+      if (invalidChars.test(customFileName)) {
+        alert('文件名包含非法字符:\\ / : " * ? < > |');
+        customFileNameInput.focus();
+        return;
+      }
+
+      if (!selectedFile) {
+        alert('没有选择文件');
+        return;
+      }
+
+      // 进行上传
+      const formData = new FormData();
+      formData.append('file', selectedFile);
+      formData.append('custom_name', customFileName); // 添加自定义文件名
+
+      // 可以根据需要显示上传进度
+      fetch('http://106.14.113.12:8005/upload-pdf', {  // 根据实际部署情况调整URL
+        method: 'POST',
+        body: formData
+      })
+        .then(response => response.json())
+        .then(data => {
+          if (data.success) {
+            const uploadedFilePath = data.file_path; // 如 "/static/files/XXXX.pdf"
+            const redirectUrl = `viewer.html?file=${encodeURIComponent(uploadedFilePath)}`;
+            window.location.href = redirectUrl;
+          } else {
+            alert('上传失败: ' + data.error);
+          }
+        })
+        .catch(error => {
+          console.error('Error:', error);
+          alert('上传过程中发生错误');
+        })
+        .finally(() => {
+          // 隐藏模态窗口和遮罩
+          filenameModal.style.display = 'none';
+          modalOverlay.style.display = 'none';
+          // 清空文件输入
+          pdfInput.value = "";
+          selectedFile = null;
+        });
+    });
+
+    // 处理“打开目录”按钮点击事件
+    openDirectoryButton.addEventListener('click', function () {
+      // 显示遮罩和弹出层
+      modalOverlay.style.display = 'block';
+      directoryModal.style.display = 'block';
+
+      // 清空现有的列表
+      pdfList.innerHTML = '';
+
+      // 调用 FastAPI 获取文件列表
+      fetch('http://106.14.113.12:8005/list-pdfs')  // 根据实际部署情况调整URL
+        .then(response => response.json())
+        .then(data => {
+          if (data.success) {
+            const files = data.files;
+            if (files.length === 0) {
+              const listItem = document.createElement('li');
+              listItem.textContent = '没有已上传的 PDF 文档。';
+              pdfList.appendChild(listItem);
+            } else {
+              files.forEach(file => {
+                const listItem = document.createElement('li');
+                listItem.style.marginBottom = '10px';
+
+                const link = document.createElement('a');
+                link.href = `viewer.html?file=${file.url}`;  // 确保 URL 正确
+                link.textContent = file.name;
+                link.style.textDecoration = 'none';
+                link.style.color = '#007bff';
+
+                // 添加点击事件,在当前页面打开
+                link.addEventListener('click', function (event) {
+                  event.preventDefault(); // 防止默认的跳转行为
+                  const fileUrl = link.getAttribute('href');
+                  const redirectUrl = `${fileUrl}`;
+                  window.location.href = redirectUrl; // 在当前页面跳转
+                });
+
+                listItem.appendChild(link);
+                pdfList.appendChild(listItem);
+              });
+            }
+          } else {
+            alert('无法获取文件列表: ' + data.error);
+          }
+        })
+        .catch(error => {
+          console.error('Error:', error);
+          alert('获取文件列表时发生错误');
+        });
+    });
+
+    // 处理“关闭”按钮点击事件
+    closeDirectoryButton.addEventListener('click', function () {
+      directoryModal.style.display = 'none';
+      modalOverlay.style.display = 'none';
+    });
+
+    // 点击遮罩关闭弹出层
+    modalOverlay.addEventListener('click', function () {
+      if (filenameModal.style.display === 'block') {
+        filenameModal.style.display = 'none';
+        uploadButton.focus();
+      }
+      if (directoryModal.style.display === 'block') {
+        directoryModal.style.display = 'none';
+      }
+      modalOverlay.style.display = 'none';
+    });
+
+    //语音控制
+    document.addEventListener('DOMContentLoaded', () => {
+      const audioPlayer = document.getElementById('audio-player');
+      const loadingIndicator = document.getElementById('loading-indicator');
+
+      // 函数:发送选中的文本到后端进行 TTS 处理
+      function sendTextToTTS(selectedText) {
+        loadingIndicator.style.display = 'block';
+        fetch('http://106.14.113.12:8005/text-to-speech/', {
+          method: 'POST',
+          headers: {
+            'Content-Type': 'application/json'
+          },
+          body: JSON.stringify({ user_input: selectedText })
+        })
+          .then(response => {
+            if (!response.ok) {
+              throw new Error('语音生成失败');
+            }
+            return response.blob();
+          })
+          .then(blob => {
+            const audioURL = URL.createObjectURL(blob);
+            audioPlayer.src = audioURL;
+            audioPlayer.play();
+          })
+          .catch(err => {
+            alert('生成语音失败');
+            console.error(err);
+          })
+          .finally(() => {
+            // 隐藏等待指示器
+            loadingIndicator.style.display = 'none';
+          });
+      }
+
+      // 监听文本选择事件
+      document.addEventListener('mouseup', () => {
+        const selection = window.getSelection();
+        const selectedText = selection.toString().trim();
+        if (selectedText.length === 0) return;
+
+        // 清除选择
+        selection.removeAllRanges();
+
+        // 发送选中的文本到后端
+        // alert(selectedText);
+        sendTextToTTS(selectedText);
+      });
+    });
+  </script>
+
+</body>
+
+</html>

+ 1490 - 0
static/web/bak/viewer.html1

@@ -0,0 +1,1490 @@
+<!DOCTYPE html>
+<html dir="ltr" mozdisallowselectionprint>
+<head>
+    <meta charset="utf-8">
+    <meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=1">
+    <meta name="google" content="notranslate">
+    <title>PDF.js viewer</title>
+
+    <link rel="resource" type="application/l10n" href="locale/locale.json">
+    <script src="../build/pdf.mjs" type="module"></script>
+    <link rel="stylesheet" href="viewer.css">
+    <script src="viewer.mjs" type="module"></script>
+
+    <style>
+        /* 模态窗口样式 */
+        .modal {
+            display: none;
+            position: fixed;
+            z-index: 10002;
+            left: 50%;
+            top: 50%;
+            transform: translate(-50%, -50%);
+            width: 90%;
+            max-width: 500px;
+            background-color: white;
+            border: 1px solid #ccc;
+            border-radius: 5px;
+            padding: 20px;
+            box-shadow: 0 5px 15px rgba(0, 0, 0, 0.3);
+            max-height: 80vh;
+            overflow-y: auto;
+        }
+
+        .modal-overlay {
+            display: none;
+            position: fixed;
+            top: 0;
+            left: 0;
+            width: 100%;
+            height: 100%;
+            background-color: rgba(0, 0, 0, 0.5);
+            z-index: 10001;
+        }
+
+        .button {
+            padding: 6px 12px;
+            background-color: #007bff;
+            color: white;
+            border: none;
+            border-radius: 5px;
+            cursor: pointer;
+            font-size: 12px;
+            margin: 0 4px;
+            flex: 1;
+            text-align: center;
+        }
+
+        .button-secondary {
+            background-color: #6c757d;
+        }
+
+        .button-success {
+            background-color: #28a745;
+        }
+
+        .button-light {
+            background-color: #ded520;
+            color: black;
+        }
+
+        .button-danger {
+            background-color: #dc3545;
+        }
+
+        .button-pause {
+            background-color: #ffc107;
+            color: black;
+        }
+
+        /* 底部按钮栏 */
+        .bottom-bar {
+            position: fixed;
+            bottom: 0;
+            margin: auto;
+            width: 60%;
+            left: 50%;
+            transform: translateX(-50%);
+            background-color: rgba(255, 255, 255, 0.0);
+            display: flex;
+            justify-content: space-around;
+            padding: 8px 0;
+            box-shadow: 0 -2px 5px rgba(0, 0, 0, 0.1);
+            z-index: 1000;
+        }
+
+        /* 顶部状态条 */
+        #loading-indicator {
+            display: none;
+            position: fixed;
+            top: 0;
+            left: 0;
+            width: 100%;
+            background: rgba(0, 0, 0, 0.7);
+            color: #fff;
+            padding: 5px 10px;
+            font-size: 12px;
+            z-index: 1000;
+            text-align: center;
+        }
+
+        /* 音频播放器 */
+        #audio-player {
+            display: none; /* 隐藏默认音频控件,改用自定义按钮 */
+        }
+
+        /* 目录分页样式 */
+        #pdfList {
+            list-style-type: none;
+            padding: 0;
+            margin: 0;
+        }
+
+        #pdfList li {
+            margin-bottom: 10px;
+            word-break: break-all;
+        }
+
+        #pdfList a {
+            text-decoration: none;
+            color: #007bff;
+            display: block;
+            padding: 5px;
+        }
+
+        .pagination {
+            display: flex;
+            justify-content: space-between;
+            margin-top: 20px;
+        }
+
+        .pagination button {
+            padding: 5px 10px;
+            background-color: #007bff;
+            color: white;
+            border: none;
+            border-radius: 5px;
+            cursor: pointer;
+            font-size: 14px;
+        }
+
+        .pagination button:disabled {
+            background-color: #6c757d;
+            cursor: not-allowed;
+        }
+
+        /* 手机端优化 */
+        @media (max-width: 768px) {
+            .modal {
+                width: 95%;
+                padding: 10px;
+            }
+
+            .button {
+                font-size: 12px;
+                padding: 6px 8px;
+                margin: 0 2px;
+            }
+
+            .bottom-bar {
+                padding: 6px 0;
+            }
+
+            #loading-indicator {
+                font-size: 10px;
+                padding: 3px 5px;
+            }
+        }
+    </style>
+</head>
+
+<body tabindex="0">
+    <div id="loading-indicator"></div>
+
+    <audio id="audio-player" controls></audio>
+
+    <div class="bottom-bar">
+        <button id="uploadButton" class="button">上传 PDF</button>
+        <button id="openDirectoryButton" class="button">打开目录</button>
+        <button id="select-all-text" class="button button-light">阅读整页</button>
+        <button id="toggle-text-select" class="button button-success">文本选择</button>
+        <button id="pause-play-button" class="button button-pause">暂停播放</button>
+    </div>
+
+    <input type="file" id="pdfInput" accept="application/pdf" style="display: none;" />
+
+    <div id="filenameModal" class="modal">
+        <h3>输入保存的文件名</h3>
+        <input type="text" id="customFileName" placeholder="不含扩展名"
+            style="width: 100%; padding: 5px; margin-top: 10px;" />
+        <div style="margin-top: 20px; text-align: right;">
+            <button id="cancelUpload" class="button button-secondary">取消</button>
+            <button id="confirmUpload" class="button button-success" style="margin-left: 10px;">确认</button>
+        </div>
+    </div>
+
+    <div id="directoryModal" class="modal">
+        <h2>已上传的 PDF 文档</h2>
+        <ul id="pdfList"></ul>
+        <div class="pagination">
+            <button id="prevPage" disabled>上一页</button>
+            <span id="pageInfo"></span>
+            <button id="nextPage" disabled>下一页</button>
+        </div>
+        <button id="closeDirectoryButton" class="button button-danger"
+            style="margin-top: 20px; width: 100%;">关闭</button>
+    </div>
+
+    <div id="modalOverlay" class="modal-overlay"></div>
+
+    <div id="outerContainer">
+        <div id="sidebarContainer">
+            <div id="toolbarSidebar" class="toolbarHorizontalGroup">
+                <div id="toolbarSidebarLeft">
+                    <div id="sidebarViewButtons" class="toolbarHorizontalGroup toggled" role="radiogroup">
+                        <button id="viewThumbnail" class="toolbarButton toggled" type="button"
+                            title="Show Thumbnails" tabindex="0" data-l10n-id="pdfjs-thumbs-button" role="radio"
+                            aria-checked="true" aria-controls="thumbnailView">
+                            <span data-l10n-id="pdfjs-thumbs-button-label">Thumbnails</span>
+                        </button>
+                        <button id="viewOutline" class="toolbarButton" type="button"
+                            title="Show Document Outline (double-click to expand/collapse all items)" tabindex="0"
+                            data-l10n-id="pdfjs-document-outline-button" role="radio" aria-checked="false"
+                            aria-controls="outlineView">
+                            <span data-l10n-id="pdfjs-document-outline-button-label">Document Outline</span>
+                        </button>
+                        <button id="viewAttachments" class="toolbarButton" type="button" title="Show Attachments"
+                            tabindex="0" data-l10n-id="pdfjs-attachments-button" role="radio" aria-checked="false"
+                            aria-controls="attachmentsView">
+                            <span data-l10n-id="pdfjs-attachments-button-label">Attachments</span>
+                        </button>
+                        <button id="viewLayers" class="toolbarButton" type="button"
+                            title="Show Layers (double-click to reset all layers to the default state)" tabindex="0"
+                            data-l10n-id="pdfjs-layers-button" role="radio" aria-checked="false"
+                            aria-controls="layersView">
+                            <span data-l10n-id="pdfjs-layers-button-label">Layers</span>
+                        </button>
+                    </div>
+                </div>
+                <div id="toolbarSidebarRight">
+                    <div id="outlineOptionsContainer" class="toolbarHorizontalGroup">
+                        <div class="verticalToolbarSeparator"></div>
+                        <button id="currentOutlineItem" class="toolbarButton" type="button" disabled="disabled"
+                            title="Find Current Outline Item" tabindex="0"
+                            data-l10n-id="pdfjs-current-outline-item-button">
+                            <span data-l10n-id="pdfjs-current-outline-item-button-label">Current Outline Item</span>
+                        </button>
+                    </div>
+                </div>
+            </div>
+            <div id="sidebarContent">
+                <div id="thumbnailView"></div>
+                <div id="outlineView" class="hidden"></div>
+                <div id="attachmentsView" class="hidden"></div>
+                <div id="layersView" class="hidden"></div>
+            </div>
+            <div id="sidebarResizer"></div>
+        </div>
+
+        <div id="mainContainer">
+            <div class="toolbar">
+                <div id="toolbarContainer">
+                    <div id="toolbarViewer" class="toolbarHorizontalGroup">
+                        <div id="toolbarViewerLeft" class="toolbarHorizontalGroup">
+                            <button id="sidebarToggleButton" class="toolbarButton" type="button"
+                                title="Toggle Sidebar" tabindex="0" data-l10n-id="pdfjs-toggle-sidebar-button"
+                                aria-expanded="false" aria-haspopup="true" aria-controls="sidebarContainer">
+                                <span data-l10n-id="pdfjs-toggle-sidebar-button-label">Toggle Sidebar</span>
+                            </button>
+                            <div class="toolbarButtonSpacer"></div>
+                            <div class="toolbarButtonWithContainer">
+                                <button id="viewFindButton" class="toolbarButton" type="button"
+                                    title="Find in Document" tabindex="0" data-l10n-id="pdfjs-findbar-button"
+                                    aria-expanded="false" aria-controls="findbar">
+                                    <span data-l10n-id="pdfjs-findbar-button-label">Find</span>
+                                </button>
+                                <div class="hidden doorHanger toolbarHorizontalGroup" id="findbar">
+                                    <div id="findInputContainer" class="toolbarHorizontalGroup">
+                                        <span class="loadingInput end toolbarHorizontalGroup">
+                                            <input id="findInput" class="toolbarField" title="Find"
+                                                placeholder="Find in document…" tabindex="0"
+                                                data-l10n-id="pdfjs-find-input" aria-invalid="false">
+                                        </span>
+                                        <div class="toolbarHorizontalGroup">
+                                            <button id="findPreviousButton" class="toolbarButton" type="button"
+                                                title="Find the previous occurrence of the phrase" tabindex="0"
+                                                data-l10n-id="pdfjs-find-previous-button">
+                                                <span
+                                                    data-l10n-id="pdfjs-find-previous-button-label">Previous</span>
+                                            </button>
+                                            <div class="splitToolbarButtonSeparator"></div>
+                                            <button id="findNextButton" class="toolbarButton" type="button"
+                                                title="Find the next occurrence of the phrase" tabindex="0"
+                                                data-l10n-id="pdfjs-find-next-button">
+                                                <span data-l10n-id="pdfjs-find-next-button-label">Next</span>
+                                            </button>
+                                        </div>
+                                    </div>
+                                    <div id="findbarOptionsOneContainer" class="toolbarHorizontalGroup">
+                                        <div class="toggleButton toolbarLabel">
+                                            <input type="checkbox" id="findHighlightAll" tabindex="0" />
+                                            <label for="findHighlightAll"
+                                                data-l10n-id="pdfjs-find-highlight-checkbox">Highlight All</label>
+                                        </div>
+                                        <div class="toggleButton toolbarLabel">
+                                            <input type="checkbox" id="findMatchCase" tabindex="0" />
+                                            <label for="findMatchCase"
+                                                data-l10n-id="pdfjs-find-match-case-checkbox-label">Match
+                                                Case</label>
+                                        </div>
+                                    </div>
+                                    <div id="findbarOptionsTwoContainer" class="toolbarHorizontalGroup">
+                                        <div class="toggleButton toolbarLabel">
+                                            <input type="checkbox" id="findMatchDiacritics" tabindex="0" />
+                                            <label for="findMatchDiacritics"
+                                                data-l10n-id="pdfjs-find-match-diacritics-checkbox-label">Match
+                                                Diacritics</label>
+                                        </div>
+                                        <div class="toggleButton toolbarLabel">
+                                            <input type="checkbox" id="findEntireWord" tabindex="0" />
+                                            <label for="findEntireWord"
+                                                data-l10n-id="pdfjs-find-entire-word-checkbox-label">Whole
+                                                Words</label>
+                                        </div>
+                                    </div>
+                                    <div id="findbarMessageContainer" class="toolbarHorizontalGroup"
+                                        aria-live="polite">
+                                        <span id="findResultsCount" class="toolbarLabel"></span>
+                                        <span id="findMsg" class="toolbarLabel"></span>
+                                    </div>
+                                </div>
+                            </div>
+                            <div class="toolbarHorizontalGroup hiddenSmallView">
+                                <button class="toolbarButton" title="Previous Page" type="button" id="previous"
+                                    tabindex="0" data-l10n-id="pdfjs-previous-button">
+                                    <span data-l10n-id="pdfjs-previous-button-label">Previous</span>
+                                </button>
+                                <div class="splitToolbarButtonSeparator"></div>
+                                <button class="toolbarButton" type="button" title="Next Page" id="next" tabindex="0"
+                                    data-l10n-id="pdfjs-next-button">
+                                    <span data-l10n-id="pdfjs-next-button-label">Next</span>
+                                </button>
+                            </div>
+                            <div class="toolbarHorizontalGroup">
+                                <span class="loadingInput start toolbarHorizontalGroup">
+                                    <input type="number" id="pageNumber" class="toolbarField" title="Page" value="1"
+                                        min="1" tabindex="0" data-l10n-id="pdfjs-page-input" autocomplete="off">
+                                </span>
+                                <span id="numPages" class="toolbarLabel"></span>
+                            </div>
+                        </div>
+                        <div id="toolbarViewerMiddle" class="toolbarHorizontalGroup">
+                            <div class="toolbarHorizontalGroup">
+                                <button id="zoomOutButton" class="toolbarButton" type="button" title="Zoom Out"
+                                    tabindex="0" data-l10n-id="pdfjs-zoom-out-button">
+                                    <span data-l10n-id="pdfjs-zoom-out-button-label">Zoom Out</span>
+                                </button>
+                                <div class="splitToolbarButtonSeparator"></div>
+                                <button id="zoomInButton" class="toolbarButton" type="button" title="Zoom In"
+                                    tabindex="0" data-l10n-id="pdfjs-zoom-in-button">
+                                    <span data-l10n-id="pdfjs-zoom-in-button-label">Zoom In</span>
+                                </button>
+                            </div>
+                            <span id="scaleSelectContainer" class="dropdownToolbarButton">
+                                <select id="scaleSelect" title="Zoom" tabindex="0" data-l10n-id="pdfjs-zoom-select">
+                                    <option id="pageAutoOption" title="" value="auto" selected="selected"
+                                        data-l10n-id="pdfjs-page-scale-auto">Automatic Zoom</option>
+                                    <option id="pageActualOption" title="" value="page-actual"
+                                        data-l10n-id="pdfjs-page-scale-actual">
+                                        Actual Size</option>
+                                    <option id="pageFitOption" title="" value="page-fit"
+                                        data-l10n-id="pdfjs-page-scale-fit">Page Fit
+                                    </option>
+                                    <option id="pageWidthOption" title="" value="page-width"
+                                        data-l10n-id="pdfjs-page-scale-width">Page
+                                        Width</option>
+                                    <option id="customScaleOption" title="" value="custom" disabled="disabled"
+                                        hidden="true" data-l10n-id="pdfjs-page-scale-percent"
+                                        data-l10n-args='{ "scale": 0 }'>0%</option>
+                                    <option title="" value="0.5" data-l10n-id="pdfjs-page-scale-percent"
+                                        data-l10n-args='{ "scale": 50 }'>
+                                        50%</option>
+                                    <option title="" value="0.75" data-l10n-id="pdfjs-page-scale-percent"
+                                        data-l10n-args='{ "scale": 75 }'>
+                                        75%</option>
+                                    <option title="" value="1" data-l10n-id="pdfjs-page-scale-percent"
+                                        data-l10n-args='{ "scale": 100 }'>
+                                        100%</option>
+                                    <option title="" value="1.25" data-l10n-id="pdfjs-page-scale-percent"
+                                        data-l10n-args='{ "scale": 125 }'>
+                                        125%</option>
+                                    <option title="" value="1.5" data-l10n-id="pdfjs-page-scale-percent"
+                                        data-l10n-args='{ "scale": 150 }'>
+                                        150%</option>
+                                    <option title="" value="2" data-l10n-id="pdfjs-page-scale-percent"
+                                        data-l10n-args='{ "scale": 200 }'>
+                                        200%</option>
+                                    <option title="" value="3" data-l10n-id="pdfjs-page-scale-percent"
+                                        data-l10n-args='{ "scale": 300 }'>
+                                        300%</option>
+                                    <option title="" value="4" data-l10n-id="pdfjs-page-scale-percent"
+                                        data-l10n-args='{ "scale": 400 }'>
+                                        400%</option>
+                                </select>
+                            </span>
+                        </div>
+                        <div id="toolbarViewerRight" class="toolbarHorizontalGroup">
+                            <div id="editorModeButtons" class="toolbarHorizontalGroup" role="radiogroup">
+                                <div id="editorHighlight" class="toolbarButtonWithContainer">
+                                    <button id="editorHighlightButton" class="toolbarButton" type="button"
+                                        disabled="disabled" title="Highlight" role="radio" aria-expanded="false"
+                                        aria-haspopup="true" aria-controls="editorHighlightParamsToolbar"
+                                        tabindex="0" data-l10n-id="pdfjs-editor-highlight-button">
+                                        <span data-l10n-id="pdfjs-editor-highlight-button-label">Highlight</span>
+                                    </button>
+                                    <div class="editorParamsToolbar hidden doorHangerRight"
+                                        id="editorHighlightParamsToolbar">
+                                        <div id="highlightParamsToolbarContainer"
+                                            class="editorParamsToolbarContainer">
+                                            <div id="editorHighlightColorPicker" class="colorPicker">
+                                                <span id="highlightColorPickerLabel" class="editorParamsLabel"
+                                                    data-l10n-id="pdfjs-editor-highlight-colorpicker-label">Highlight
+                                                    color</span>
+                                            </div>
+                                            <div id="editorHighlightThickness">
+                                                <label for="editorFreeHighlightThickness" class="editorParamsLabel"
+                                                    data-l10n-id="pdfjs-editor-free-highlight-thickness-input">Thickness</label>
+                                                <div class="thicknessPicker">
+                                                    <input type="range" id="editorFreeHighlightThickness"
+                                                        class="editorParamsSlider"
+                                                        data-l10n-id="pdfjs-editor-free-highlight-thickness-title"
+                                                        value="12" min="8" max="24" step="1" tabindex="0">
+                                                </div>
+                                            </div>
+                                            <div id="editorHighlightVisibility">
+                                                <div class="divider"></div>
+                                                <div class="toggler">
+                                                    <label for="editorHighlightShowAll" class="editorParamsLabel"
+                                                        data-l10n-id="pdfjs-editor-highlight-show-all-button-label">Show
+                                                        all</label>
+                                                    <button id="editorHighlightShowAll" class="toggle-button"
+                                                        type="button"
+                                                        data-l10n-id="pdfjs-editor-highlight-show-all-button"
+                                                        aria-pressed="true" tabindex="0"></button>
+                                                </div>
+                                            </div>
+                                        </div>
+                                    </div>
+                                </div>
+                                <div id="editorFreeText" class="toolbarButtonWithContainer">
+                                    <button id="editorFreeTextButton" class="toolbarButton" type="button"
+                                        disabled="disabled" title="Text" role="radio" aria-expanded="false"
+                                        aria-haspopup="true" aria-controls="editorFreeTextParamsToolbar"
+                                        tabindex="0" data-l10n-id="pdfjs-editor-free-text-button">
+                                        <span data-l10n-id="pdfjs-editor-free-text-button-label">Text</span>
+                                    </button>
+                                    <div class="editorParamsToolbar hidden doorHangerRight"
+                                        id="editorFreeTextParamsToolbar">
+                                        <div class="editorParamsToolbarContainer">
+                                            <div class="editorParamsSetter">
+                                                <label for="editorFreeTextColor" class="editorParamsLabel"
+                                                    data-l10n-id="pdfjs-editor-free-text-color-input">Color</label>
+                                                <input type="color" id="editorFreeTextColor"
+                                                    class="editorParamsColor" tabindex="0">
+                                            </div>
+                                            <div class="editorParamsSetter">
+                                                <label for="editorFreeTextFontSize" class="editorParamsLabel"
+                                                    data-l10n-id="pdfjs-editor-free-text-size-input">Size</label>
+                                                <input type="range" id="editorFreeTextFontSize"
+                                                    class="editorParamsSlider" value="10" min="5" max="100" step="1"
+                                                    tabindex="0">
+                                            </div>
+                                        </div>
+                                    </div>
+                                </div>
+                                <div id="editorInk" class="toolbarButtonWithContainer">
+                                    <button id="editorInkButton" class="toolbarButton" type="button"
+                                        disabled="disabled" title="Draw" role="radio" aria-expanded="false"
+                                        aria-haspopup="true" aria-controls="editorInkParamsToolbar" tabindex="0"
+                                        data-l10n-id="pdfjs-editor-ink-button">
+                                        <span data-l10n-id="pdfjs-editor-ink-button-label">Draw</span>
+                                    </button>
+                                    <div class="editorParamsToolbar hidden doorHangerRight"
+                                        id="editorInkParamsToolbar">
+                                        <div class="editorParamsToolbarContainer">
+                                            <div class="editorParamsSetter">
+                                                <label for="editorInkColor" class="editorParamsLabel"
+                                                    data-l10n-id="pdfjs-editor-ink-color-input">Color</label>
+                                                <input type="color" id="editorInkColor" class="editorParamsColor"
+                                                    tabindex="0">
+                                            </div>
+                                            <div class="editorParamsSetter">
+                                                <label for="editorInkThickness" class="editorParamsLabel"
+                                                    data-l10n-id="pdfjs-editor-ink-thickness-input">Thickness</label>
+                                                <input type="range" id="editorInkThickness"
+                                                    class="editorParamsSlider" value="1" min="1" max="20" step="1"
+                                                    tabindex="0">
+                                            </div>
+                                            <div class="editorParamsSetter">
+                                                <label for="editorInkOpacity" class="editorParamsLabel"
+                                                    data-l10n-id="pdfjs-editor-ink-opacity-input">Opacity</label>
+                                                <input type="range" id="editorInkOpacity" class="editorParamsSlider"
+                                                    value="100" min="1" max="100" step="1" tabindex="0">
+                                            </div>
+                                        </div>
+                                    </div>
+                                </div>
+                                <div id="editorStamp" class="toolbarButtonWithContainer">
+                                    <button id="editorStampButton" class="toolbarButton" type="button"
+                                        disabled="disabled" title="Add or edit images" role="radio"
+                                        aria-expanded="false" aria-haspopup="true"
+                                        aria-controls="editorStampParamsToolbar" tabindex="0"
+                                        data-l10n-id="pdfjs-editor-stamp-button">
+                                        <span data-l10n-id="pdfjs-editor-stamp-button">Add or edit images</span>
+                                    </button>
+                                    <div class="editorParamsToolbar hidden doorHangerRight menu"
+                                        id="editorStampParamsToolbar">
+                                        <div class="menuContainer">
+                                            <button id="editorStampAddImage" class="toolbarButton labeled"
+                                                type="button" title="Add image" tabindex="0"
+                                                data-l10n-id="editor-stamp-add-image-button">
+                                                <span class="editorParamsLabel"
+                                                    data-l10n-id="editor-stamp-add-image-button-label">Add image</span>
+                                            </button>
+                                        </div>
+                                    </div>
+                                </div>
+                            </div>
+                            <div id="editorModeSeparator" class="verticalToolbarSeparator"></div>
+                            <div class="toolbarHorizontalGroup hiddenMediumView">
+                                <button id="printButton" class="toolbarButton" type="button" title="Print"
+                                    tabindex="0" data-l10n-id="pdfjs-print-button">
+                                    <span data-l10n-id="pdfjs-print-button-label">Print</span>
+                                </button>
+                                <button id="downloadButton" class="toolbarButton" type="button" title="Save"
+                                    tabindex="0" data-l10n-id="pdfjs-save-button">
+                                    <span data-l10n-id="pdfjs-save-button-label">Save</span>
+                                </button>
+                            </div>
+                            <div class="verticalToolbarSeparator hiddenMediumView"></div>
+                            <div id="secondaryToolbarToggle" class="toolbarButtonWithContainer">
+                                <button id="secondaryToolbarToggleButton" class="toolbarButton" type="button"
+                                    title="Tools" tabindex="0" data-l10n-id="pdfjs-tools-button"
+                                    aria-expanded="false" aria-haspopup="true" aria-controls="secondaryToolbar">
+                                    <span data-l10n-id="pdfjs-tools-button-label">Tools</span>
+                                </button>
+                                <div id="secondaryToolbar" class="hidden doorHangerRight menu">
+                                    <div id="secondaryToolbarButtonContainer" class="menuContainer">
+                                        <button id="secondaryOpenFile" class="toolbarButton labeled" type="button"
+                                            title="Open File" tabindex="0" data-l10n-id="pdfjs-open-file-button">
+                                            <span data-l10n-id="pdfjs-open-file-button-label">Open</span>
+                                        </button>
+                                        <div class="visibleMediumView">
+                                            <button id="secondaryPrint" class="toolbarButton labeled" type="button"
+                                                title="Print" tabindex="0" data-l10n-id="pdfjs-print-button">
+                                                <span data-l10n-id="pdfjs-print-button-label">Print</span>
+                                            </button>
+                                            <button id="secondaryDownload" class="toolbarButton labeled"
+                                                type="button" title="Save" tabindex="0"
+                                                data-l10n-id="pdfjs-save-button">
+                                                <span data-l10n-id="pdfjs-save-button-label">Save</span>
+                                            </button>
+                                        </div>
+                                        <div class="horizontalToolbarSeparator"></div>
+                                        <button id="presentationMode" class="toolbarButton labeled" type="button"
+                                            title="Switch to Presentation Mode" tabindex="0"
+                                            data-l10n-id="pdfjs-presentation-mode-button">
+                                            <span data-l10n-id="pdfjs-presentation-mode-button-label">Presentation
+                                                Mode</span>
+                                        </button>
+                                        <a href="#" id="viewBookmark" class="toolbarButton labeled"
+                                            title="Current Page (View URL from Current Page)" tabindex="0"
+                                            data-l10n-id="pdfjs-bookmark-button">
+                                            <span data-l10n-id="pdfjs-bookmark-button-label">Current Page</span>
+                                        </a>
+                                        <div id="viewBookmarkSeparator" class="horizontalToolbarSeparator"></div>
+                                        <button id="firstPage" class="toolbarButton labeled" type="button"
+                                            title="Go to First Page" tabindex="0"
+                                            data-l10n-id="pdfjs-first-page-button">
+                                            <span data-l10n-id="pdfjs-first-page-button-label">Go to First
+                                                Page</span>
+                                        </button>
+                                        <button id="lastPage" class="toolbarButton labeled" type="button"
+                                            title="Go to Last Page" tabindex="0"
+                                            data-l10n-id="pdfjs-last-page-button">
+                                            <span data-l10n-id="pdfjs-last-page-button-label">Go to Last Page</span>
+                                        </button>
+                                        <div class="horizontalToolbarSeparator"></div>
+                                        <button id="pageRotateCw" class="toolbarButton labeled" type="button"
+                                            title="Rotate Clockwise" tabindex="0"
+                                            data-l10n-id="pdfjs-page-rotate-cw-button">
+                                            <span data-l10n-id="pdfjs-page-rotate-cw-button-label">Rotate
+                                                Clockwise</span>
+                                        </button>
+                                        <button id="pageRotateCcw" class="toolbarButton labeled" type="button"
+                                            title="Rotate Counterclockwise" tabindex="0"
+                                            data-l10n-id="pdfjs-page-rotate-ccw-button">
+                                            <span data-l10n-id="pdfjs-page-rotate-ccw-button-label">Rotate
+                                                Counterclockwise</span>
+                                        </button>
+                                        <div class="horizontalToolbarSeparator"></div>
+                                        <div id="cursorToolButtons" role="radiogroup">
+                                            <button id="cursorSelectTool" class="toolbarButton labeled toggled"
+                                                type="button" title="Enable Text Selection Tool" tabindex="0"
+                                                data-l10n-id="pdfjs-cursor-text-select-tool-button" role="radio"
+                                                aria-checked="true">
+                                                <span data-l10n-id="pdfjs-cursor-text-select-tool-button-label">Text
+                                                    Selection Tool</span>
+                                            </button>
+                                            <button id="cursorHandTool" class="toolbarButton labeled" type="button"
+                                                title="Enable Hand Tool" tabindex="0"
+                                                data-l10n-id="pdfjs-cursor-hand-tool-button" role="radio"
+                                                aria-checked="false">
+                                                <span data-l10n-id="pdfjs-cursor-hand-tool-button-label">Hand
+                                                    Tool</span>
+                                            </button>
+                                        </div>
+                                        <div class="horizontalToolbarSeparator"></div>
+                                        <div id="scrollModeButtons" role="radiogroup">
+                                            <button id="scrollPage" class="toolbarButton labeled" type="button"
+                                                title="Use Page Scrolling" tabindex="0"
+                                                data-l10n-id="pdfjs-scroll-page-button" role="radio"
+                                                aria-checked="false">
+                                                <span data-l10n-id="pdfjs-scroll-page-button-label">Page
+                                                    Scrolling</span>
+                                            </button>
+                                            <button id="scrollVertical" class="toolbarButton labeled toggled"
+                                                type="button" title="Use Vertical Scrolling" tabindex="0"
+                                                data-l10n-id="pdfjs-scroll-vertical-button" role="radio"
+                                                aria-checked="true">
+                                                <span data-l10n-id="pdfjs-scroll-vertical-button-label">Vertical
+                                                    Scrolling</span>
+                                            </button>
+                                            <button id="scrollHorizontal" class="toolbarButton labeled"
+                                                type="button" title="Use Horizontal Scrolling" tabindex="0"
+                                                data-l10n-id="pdfjs-scroll-horizontal-button" role="radio"
+                                                aria-checked="false">
+                                                <span data-l10n-id="pdfjs-scroll-horizontal-button-label">Horizontal
+                                                    Scrolling</span>
+                                            </button>
+                                            <button id="scrollWrapped" class="toolbarButton labeled" type="button"
+                                                title="Use Wrapped Scrolling" tabindex="0"
+                                                data-l10n-id="pdfjs-scroll-wrapped-button" role="radio"
+                                                aria-checked="false">
+                                                <span data-l10n-id="pdfjs-scroll-wrapped-button-label">Wrapped
+                                                    Scrolling</span>
+                                            </button>
+                                        </div>
+                                        <div class="horizontalToolbarSeparator"></div>
+                                        <div id="spreadModeButtons" role="radiogroup">
+                                            <button id="spreadNone" class="toolbarButton labeled toggled"
+                                                type="button" title="Do not join page spreads" tabindex="0"
+                                                data-l10n-id="pdfjs-spread-none-button" role="radio"
+                                                aria-checked="true">
+                                                <span data-l10n-id="pdfjs-spread-none-button-label">No
+                                                    Spreads</span>
+                                            </button>
+                                            <button id="spreadOdd" class="toolbarButton labeled" type="button"
+                                                title="Join page spreads starting with odd-numbered pages"
+                                                tabindex="0" data-l10n-id="pdfjs-spread-odd-button" role="radio"
+                                                aria-checked="false">
+                                                <span data-l10n-id="pdfjs-spread-odd-button-label">Odd
+                                                    Spreads</span>
+                                            </button>
+                                            <button id="spreadEven" class="toolbarButton labeled" type="button"
+                                                title="Join page spreads starting with even-numbered pages"
+                                                tabindex="0" data-l10n-id="pdfjs-spread-even-button" role="radio"
+                                                aria-checked="false">
+                                                <span data-l10n-id="pdfjs-spread-even-button-label">Even
+                                                    Spreads</span>
+                                            </button>
+                                        </div>
+                                        <div id="imageAltTextSettingsSeparator"
+                                            class="horizontalToolbarSeparator hidden"></div>
+                                        <button id="imageAltTextSettings" type="button"
+                                            class="toolbarButton labeled hidden" title="Image alt text settings"
+                                            tabindex="0" data-l10n-id="pdfjs-image-alt-text-settings-button"
+                                            aria-controls="altTextSettingsDialog">
+                                            <span data-l10n-id="pdfjs-image-alt-text-settings-button-label">Image
+                                                alt text settings</span>
+                                        </button>
+                                        <div class="horizontalToolbarSeparator"></div>
+                                        <button id="documentProperties" class="toolbarButton labeled" type="button"
+                                            title="Document Properties…" tabindex="0"
+                                            data-l10n-id="pdfjs-document-properties-button"
+                                            aria-controls="documentPropertiesDialog">
+                                            <span data-l10n-id="pdfjs-document-properties-button-label">Document
+                                                Properties…</span>
+                                        </button>
+                                    </div>
+                                </div>
+                            </div>
+                        </div>
+                        <div id="loadingBar">
+                            <div class="progress">
+                                <div class="glimmer"></div>
+                            </div>
+                        </div>
+                    </div>
+                </div>
+                <div id="viewerContainer" tabindex="0">
+                    <div id="viewer" class="pdfViewer"></div>
+                </div>
+            </div>
+
+            <div id="dialogContainer">
+                <dialog id="passwordDialog">
+                    <div class="row">
+                        <label for="password" id="passwordText" data-l10n-id="pdfjs-password-label">Enter the password
+                            to open this PDF file:</label>
+                    </div>
+                    <div class="row">
+                        <input type="password" id="password" class="toolbarField">
+                    </div>
+                    <div class="buttonRow">
+                        <button id="passwordCancel" class="dialogButton" type="button"><span
+                                data-l10n-id="pdfjs-password-cancel-button">Cancel</span></button>
+                        <button id="passwordSubmit" class="dialogButton" type="button"><span
+                            data-l10n-id="pdfjs-password-ok-button">OK</span></button>
+                    </div>
+                </dialog>
+                <dialog id="documentPropertiesDialog">
+                    <div class="row">
+                        <span id="fileNameLabel" data-l10n-id="pdfjs-document-properties-file-name">File name:</span>
+                        <p id="fileNameField" aria-labelledby="fileNameLabel">-</p>
+                    </div>
+                    <div class="row">
+                        <span id="fileSizeLabel" data-l10n-id="pdfjs-document-properties-file-size">File size:</span>
+                        <p id="fileSizeField" aria-label="fileSizeLabel">-</p>
+                    </div>
+                    <div class="separator"></div>
+                    <div class="row">
+                        <span id="titleLabel" data-l10n-id="pdfjs-document-properties-title">Title:</span>
+                        <p id="titleField" aria-labelledby="titleLabel">-</p>
+                    </div>
+                    <div class="row">
+                        <span id="authorLabel" data-l10n-id="pdfjs-document-properties-author">Author:</span>
+                        <p id="authorField" aria-labelledby="authorLabel">-</p>
+                    </div>
+                    <div class="row">
+                        <span id="subjectLabel" data-l10n-id="pdfjs-document-properties-subject">Subject:</span>
+                        <p id="subjectField" aria-labelledby="subjectLabel">-</p>
+                    </div>
+                    <div class="row">
+                        <span id="keywordsLabel" data-l10n-id="pdfjs-document-properties-keywords">Keywords:</span>
+                        <p id="keywordsField" aria-labelledby="keywordsLabel">-</p>
+                    </div>
+                    <div class="row">
+                        <span id="creationDateLabel" data-l10n-id="pdfjs-document-properties-creation-date">Creation
+                            Date:</span>
+                        <p id="creationDateField" aria-labelledby="creationDateLabel">-</p>
+                    </div>
+                    <div class="row">
+                        <span id="modificationDateLabel"
+                            data-l10n-id="pdfjs-document-properties-modification-date">Modification
+                            Date:</span>
+                        <p id="modificationDateField" aria-labelledby="modificationDateLabel">-</p>
+                    </div>
+                    <div class="row">
+                        <span id="creatorLabel" data-l10n-id="pdfjs-document-properties-creator">Creator:</span>
+                        <p id="creatorField" aria-labelledby="creatorLabel">-</p>
+                    </div>
+                    <div class="separator"></div>
+                    <div class="row">
+                        <span id="producerLabel" data-l10n-id="pdfjs-document-properties-producer">PDF Producer:</span>
+                        <p id="producerField" aria-labelledby="producerLabel">-</p>
+                    </div>
+                    <div class="row">
+                        <span id="versionLabel" data-l10n-id="pdfjs-document-properties-version">PDF Version:</span>
+                        <p id="versionField" aria-labelledby="versionLabel">-</p>
+                    </div>
+                    <div class="row">
+                        <span id="pageCountLabel" data-l10n-id="pdfjs-document-properties-page-count">Page Count:</span>
+                        <p id="pageCountField" aria-labelledby="pageCountLabel">-</p>
+                    </div>
+                    <div class="row">
+                        <span id="pageSizeLabel" data-l10n-id="pdfjs-document-properties-page-size">Page Size:</span>
+                        <p id="pageSizeField" aria-labelledby="pageSizeLabel">-</p>
+                    </div>
+                    <div class="separator"></div>
+                    <div class="row">
+                        <span id="linearizedLabel" data-l10n-id="pdfjs-document-properties-linearized">Fast Web
+                            View:</span>
+                        <p id="linearizedField" aria-labelledby="linearizedLabel">-</p>
+                    </div>
+                    <div class="buttonRow">
+                        <button id="documentPropertiesClose" class="dialogButton" type="button"><span
+                                data-l10n-id="pdfjs-document-properties-close-button">Close</span></button>
+                    </div>
+                </dialog>
+                <dialog class="dialog altText" id="altTextDialog" aria-labelledby="dialogLabel"
+                    aria-describedby="dialogDescription">
+                    <div id="altTextContainer" class="mainContainer">
+                        <div id="overallDescription">
+                            <span id="dialogLabel" data-l10n-id="pdfjs-editor-alt-text-dialog-label"
+                                class="title">Choose an
+                                option</span>
+                            <span id="dialogDescription" data-l10n-id="pdfjs-editor-alt-text-dialog-description">
+                                Alt text (alternative text) helps when people can’t see the image or when it doesn’t
+                                load.
+                            </span>
+                        </div>
+                        <div id="addDescription">
+                            <div class="radio">
+                                <div class="radioButton">
+                                    <input type="radio" id="descriptionButton" name="altTextOption" tabindex="0"
+                                        aria-describedby="descriptionAreaLabel" checked>
+                                    <label for="descriptionButton"
+                                        data-l10n-id="pdfjs-editor-alt-text-add-description-label">Add a
+                                        description</label>
+                                </div>
+                                <div class="radioLabel">
+                                    <span id="descriptionAreaLabel"
+                                        data-l10n-id="pdfjs-editor-alt-text-add-description-description">
+                                        Aim for 1-2 sentences that describe the subject, setting, or actions.
+                                    </span>
+                                </div>
+                            </div>
+                            <div class="descriptionArea">
+                                <textarea id="descriptionTextarea"
+                                    placeholder="For example, “A young man sits down at a table to eat a meal”"
+                                    aria-labelledby="descriptionAreaLabel" data-l10n-id="pdfjs-editor-alt-text-textarea"
+                                    tabindex="0"></textarea>
+                            </div>
+                        </div>
+                        <div id="markAsDecorative">
+                            <div class="radio">
+                                <div class="radioButton">
+                                    <input type="radio" id="decorativeButton" name="altTextOption"
+                                        aria-describedby="decorativeLabel">
+                                    <label for="decorativeButton"
+                                        data-l10n-id="pdfjs-editor-alt-text-mark-decorative-label">Mark as
+                                        decorative</label>
+                                </div>
+                                <div class="radioLabel">
+                                    <span id="decorativeLabel"
+                                        data-l10n-id="pdfjs-editor-alt-text-mark-decorative-description">
+                                        This is used for ornamental images, like borders or watermarks.
+                                    </span>
+                                </div>
+                            </div>
+                        </div>
+                        <div id="buttons">
+                            <button id="altTextCancel" class="secondaryButton" type="button" tabindex="0"><span
+                                    data-l10n-id="pdfjs-editor-alt-text-cancel-button">Cancel</span></button>
+                            <button id="altTextSave" class="primaryButton" type="button" tabindex="0"><span
+                                    data-l10n-id="pdfjs-editor-alt-text-save-button">Save</span></button>
+                        </div>
+                    </div>
+                </dialog>
+                <dialog class="dialog newAltText" id="newAltTextDialog" aria-labelledby="newAltTextTitle"
+                    aria-describedby="newAltTextDescription" tabindex="0">
+                    <div id="newAltTextContainer" class="mainContainer">
+                        <div class="title">
+                            <span id="newAltTextTitle" data-l10n-id="pdfjs-editor-new-alt-text-dialog-edit-label"
+                                role="sectionhead" tabindex="0">Edit alt text (image description)</span>
+                        </div>
+                        <div id="mainContent">
+                            <div id="descriptionAndSettings">
+                                <div id="descriptionInstruction">
+                                    <div id="newAltTextDescriptionContainer">
+                                        <div class="altTextSpinner" role="status" aria-live="polite"></div>
+                                        <textarea id="newAltTextDescriptionTextarea"
+                                            placeholder="Write your description here…"
+                                            aria-labelledby="descriptionAreaLabel"
+                                            data-l10n-id="pdfjs-editor-new-alt-text-textarea" tabindex="0"></textarea>
+                                    </div>
+                                    <span id="newAltTextDescription" role="note"
+                                        data-l10n-id="pdfjs-editor-new-alt-text-description">Short
+                                        description for people who can’t see the image or when the image doesn’t
+                                        load.</span>
+                                    <div id="newAltTextDisclaimer" role="note">
+                                        <div><span data-l10n-id="pdfjs-editor-new-alt-text-disclaimer1">This alt text
+                                                was created
+                                                automatically and may be inaccurate.</span> <a
+                                                href="https://support.mozilla.org/en-US/kb/pdf-alt-text" target="_blank"
+                                                rel="noopener noreferrer" id="newAltTextLearnMore"
+                                                data-l10n-id="pdfjs-editor-new-alt-text-disclaimer-learn-more-url"
+                                                tabindex="0">Learn more</a></div>
+                                    </div>
+                                </div>
+                                <div id="newAltTextCreateAutomatically" class="toggler">
+                                    <button id="newAltTextCreateAutomaticallyButton" class="toggle-button" type="button"
+                                        aria-pressed="true" tabindex="0"></button>
+                                    <label for="newAltTextCreateAutomaticallyButton" class="togglerLabel"
+                                        data-l10n-id="pdfjs-editor-new-alt-text-create-automatically-button-label">Create
+                                        alt text
+                                        automatically</label>
+                                </div>
+                                <div id="newAltTextDownloadModel" class="hidden">
+                                    <span id="newAltTextDownloadModelDescription"
+                                        data-l10n-id="pdfjs-editor-new-alt-text-ai-model-downloading-progress"
+                                        aria-valuemin="0"
+                                        data-l10n-args='{ "totalSize": 0, "downloadedSize": 0 }'>Downloading alt text AI
+                                        model (0 of 0
+                                        MB)</span>
+                                </div>
+                            </div>
+                            <div id="newAltTextImagePreview"></div>
+                        </div>
+                        <div id="newAltTextError" class="messageBar">
+                            <div>
+                                <div>
+                                    <span class="title" data-l10n-id="pdfjs-editor-new-alt-text-error-title">Couldn’t
+                                        create alt text
+                                        automatically</span>
+                                    <span class="description"
+                                        data-l10n-id="pdfjs-editor-new-alt-text-error-description">Please write your
+                                        own alt text or try again later.</span>
+                                </div>
+                                <button id="newAltTextCloseButton" class="closeButton" type="button" tabindex="0"
+                                    title="Close"><span
+                                        data-l10n-id="pdfjs-editor-new-alt-text-error-close-button">Close</span></button>
+                            </div>
+                        </div>
+                        <div id="newAltTextButtons" class="dialogButtonsGroup">
+                            <button id="newAltTextCancel" type="button" class="secondaryButton hidden"
+                                tabindex="0"><span
+                                    data-l10n-id="pdfjs-editor-alt-text-cancel-button">Cancel</span></button>
+                            <button id="newAltTextNotNow" type="button" class="secondaryButton" tabindex="0"><span
+                                    data-l10n-id="pdfjs-editor-new-alt-text-not-now-button">Not now</span></button>
+                            <button id="newAltTextSave" type="button" class="primaryButton" tabindex="0"><span
+                                    data-l10n-id="pdfjs-editor-alt-text-save-button">Save</span></button>
+                        </div>
+                    </div>
+                </dialog>
+                <dialog class="dialog" id="altTextSettingsDialog" aria-labelledby="altTextSettingsTitle">
+                    <div id="altTextSettingsContainer" class="mainContainer">
+                        <div class="title">
+                            <span id="altTextSettingsTitle" data-l10n-id="pdfjs-editor-alt-text-settings-dialog-label"
+                                role="sectionhead" tabindex="0" class="title">Image alt text settings</span>
+                        </div>
+                        <div id="automaticAltText">
+                            <span data-l10n-id="pdfjs-editor-alt-text-settings-automatic-title">Automatic alt
+                                text</span>
+                            <div id="automaticSettings">
+                                <div id="createModelSetting">
+                                    <div class="toggler">
+                                        <button id="createModelButton" type="button" class="toggle-button"
+                                            aria-pressed="true" tabindex="0"></button>
+                                        <label for="createModelButton" class="togglerLabel"
+                                            data-l10n-id="pdfjs-editor-alt-text-settings-create-model-button-label">Create
+                                            alt text
+                                            automatically</label>
+                                    </div>
+                                    <div id="createModelDescription" class="description">
+                                        <span
+                                            data-l10n-id="pdfjs-editor-alt-text-settings-create-model-description">Suggests
+                                            descriptions to
+                                            help people who can’t see the image or when the image doesn’t load.</span>
+                                        <a href="https://support.mozilla.org/en-US/kb/pdf-alt-text" target="_blank"
+                                            rel="noopener noreferrer" id="altTextSettingsLearnMore"
+                                            data-l10n-id="pdfjs-editor-new-alt-text-disclaimer-learn-more-url"
+                                            tabindex="0">Learn more</a>
+                                    </div>
+                                </div>
+                                <div id="aiModelSettings">
+                                    <div>
+                                        <span data-l10n-id="pdfjs-editor-alt-text-settings-download-model-label"
+                                            data-l10n-args='{ "totalSize": 180 }'>Alt text AI model (180MB)</span>
+                                        <div id="aiModelDescription" class="description">
+                                            <span
+                                                data-l10n-id="pdfjs-editor-alt-text-settings-ai-model-description">Runs
+                                                locally on your device
+                                                so your data stays private. Required for automatic alt text.</span>
+                                        </div>
+                                    </div>
+                                    <button id="deleteModelButton" type="button" class="secondaryButton"
+                                        tabindex="0"><span
+                                            data-l10n-id="pdfjs-editor-alt-text-settings-delete-model-button">Delete</span></button>
+                                    <button id="downloadModelButton" type="button" class="secondaryButton"
+                                        tabindex="0"><span
+                                            data-l10n-id="pdfjs-editor-alt-text-settings-download-model-button">Download</span></button>
+                                </div>
+                            </div>
+                        </div>
+                        <div class="dialogSeparator"></div>
+                        <div id="altTextEditor">
+                            <span data-l10n-id="pdfjs-editor-alt-text-settings-editor-title">Alt text editor</span>
+                            <div id="showAltTextEditor">
+                                <div class="toggler">
+                                    <button id="showAltTextDialogButton" type="button" class="toggle-button"
+                                        aria-pressed="true" tabindex="0"></button>
+                                    <label for="showAltTextDialogButton" class="togglerLabel"
+                                        data-l10n-id="pdfjs-editor-alt-text-settings-show-dialog-button-label">Show alt
+                                        text editor right away
+                                        when adding an image</label>
+                                </div>
+                                <div id="showAltTextDialogDescription" class="description">
+                                    <span data-l10n-id="pdfjs-editor-alt-text-settings-show-dialog-description">Helps
+                                        you make sure all your
+                                        images have alt text.</span>
+                                </div>
+                            </div>
+                        </div>
+                        <div id="buttons" class="dialogButtonsGroup">
+                            <button id="altTextSettingsCloseButton" type="button" class="primaryButton"
+                                tabindex="0"><span
+                                    data-l10n-id="pdfjs-editor-alt-text-settings-close-button">Close</span></button>
+                        </div>
+                    </div>
+                </dialog>
+                <dialog id="printServiceDialog" style="min-width: 200px;">
+                    <div class="row">
+                        <span data-l10n-id="pdfjs-print-progress-message">Preparing document for printing…</span>
+                    </div>
+                    <div class="row">
+                        <progress value="0" max="100"></progress>
+                        <span data-l10n-id="pdfjs-print-progress-percent" data-l10n-args='{ "progress": 0 }'
+                            class="relative-progress">0%</span>
+                    </div>
+                    <div class="buttonRow">
+                        <button id="printCancel" class="dialogButton" type="button"><span
+                                data-l10n-id="pdfjs-print-progress-close-button">Cancel</span></button>
+                    </div>
+                </dialog>
+            </div>
+
+            <div id="printContainer"></div>
+
+            <script>
+                const uploadButton = document.getElementById('uploadButton');
+                const pdfInput = document.getElementById('pdfInput');
+                const filenameModal = document.getElementById('filenameModal');
+                const modalOverlay = document.getElementById('modalOverlay');
+                const confirmUploadButton = document.getElementById('confirmUpload');
+                const cancelUploadButton = document.getElementById('cancelUpload');
+                const customFileNameInput = document.getElementById('customFileName');
+                const openDirectoryButton = document.getElementById('openDirectoryButton');
+                const directoryModal = document.getElementById('directoryModal');
+                const closeDirectoryButton = document.getElementById('closeDirectoryButton');
+                const pdfList = document.getElementById('pdfList');
+                const prevPageButton = document.getElementById('prevPage');
+                const nextPageButton = document.getElementById('nextPage');
+                const pageInfo = document.getElementById('pageInfo');
+                const pausePlayButton = document.getElementById('pause-play-button');
+
+                let selectedFile = null;
+                let currentPage = 1;
+                const itemsPerPage = 10;
+                let allFiles = [];
+
+                // 上传按钮点击事件
+                uploadButton.addEventListener('click', function () {
+                    pdfInput.click();
+                });
+
+                // 文件选择事件
+                pdfInput.addEventListener('change', function (event) {
+                    const file = event.target.files[0];
+                    if (file && file.type === 'application/pdf') {
+                        selectedFile = file;
+                        modalOverlay.style.display = 'block';
+                        filenameModal.style.display = 'block';
+                        customFileNameInput.value = "";
+                        customFileNameInput.focus();
+                    } else {
+                        alert('请选择一个 PDF 文件');
+                    }
+                });
+
+                // 取消上传
+                cancelUploadButton.addEventListener('click', function () {
+                    filenameModal.style.display = 'none';
+                    modalOverlay.style.display = 'none';
+                    pdfInput.value = "";
+                    selectedFile = null;
+                });
+
+                // 确认上传
+                confirmUploadButton.addEventListener('click', function () {
+                    const customFileName = customFileNameInput.value.trim();
+                    if (customFileName === "") {
+                        alert('请输入保存的文件名');
+                        customFileNameInput.focus();
+                        return;
+                    }
+
+                    const invalidChars = /[\\/:"*?<>|]+/;
+                    if (invalidChars.test(customFileName)) {
+                        alert('文件名包含非法字符:\\ / : " * ? < > |');
+                        customFileNameInput.focus();
+                        return;
+                    }
+
+                    if (!selectedFile) {
+                        alert('没有选择文件');
+                        return;
+                    }
+
+                    const formData = new FormData();
+                    formData.append('file', selectedFile);
+                    formData.append('custom_name', customFileName);
+
+                    fetch('http://117.50.195.224:8005/upload-pdf', {
+                        method: 'POST',
+                        body: formData
+                    })
+                        .then(response => response.json())
+                        .then(data => {
+                            if (data.success) {
+                                const uploadedFilePath = data.file_path;
+                                const redirectUrl = `viewer.html?file=${encodeURIComponent(uploadedFilePath)}`;
+                                window.location.href = redirectUrl;
+                            } else {
+                                alert('上传失败: ' + data.error);
+                            }
+                        })
+                        .catch(error => {
+                            console.error('Error:', error);
+                            alert('上传过程中发生错误');
+                        })
+                        .finally(() => {
+                            filenameModal.style.display = 'none';
+                            modalOverlay.style.display = 'none';
+                            pdfInput.value = "";
+                            selectedFile = null;
+                        });
+                });
+
+                // 显示目录分页
+                function displayDirectoryPage(page) {
+                    pdfList.innerHTML = '';
+                    const start = (page - 1) * itemsPerPage;
+                    const end = start + itemsPerPage;
+                    const pageFiles = allFiles.slice(start, end);
+
+                    if (pageFiles.length === 0) {
+                        const listItem = document.createElement('li');
+                        listItem.textContent = '没有已上传的 PDF 文档。';
+                        pdfList.appendChild(listItem);
+                    } else {
+                        pageFiles.forEach(file => {
+                            const listItem = document.createElement('li');
+                            const link = document.createElement('a');
+                            link.href = `viewer.html?file=${file.url}`;
+                            link.textContent = file.name;
+                            link.addEventListener('click', function (event) {
+                                event.preventDefault();
+                                window.location.href = link.getAttribute('href');
+                            });
+                            listItem.appendChild(link);
+                            pdfList.appendChild(listItem);
+                        });
+                    }
+
+                    pageInfo.textContent = `第 ${page} 页 / 共 ${Math.ceil(allFiles.length / itemsPerPage)} 页`;
+                    prevPageButton.disabled = page === 1;
+                    nextPageButton.disabled = end >= allFiles.length;
+                }
+
+                // 打开目录
+                openDirectoryButton.addEventListener('click', function () {
+                    modalOverlay.style.display = 'block';
+                    directoryModal.style.display = 'block';
+                    currentPage = 1;
+
+                    fetch('http://117.50.195.224:8005/list-pdfs')
+                        .then(response => response.json())
+                        .then(data => {
+                            if (data.success) {
+                                allFiles = data.files;
+                                displayDirectoryPage(currentPage);
+                            } else {
+                                alert('无法获取文件列表: ' + data.error);
+                            }
+                        })
+                        .catch(error => {
+                            console.error('Error:', error);
+                            alert('获取文件列表时发生错误');
+                        });
+                });
+
+                // 上一页
+                prevPageButton.addEventListener('click', function () {
+                    if (currentPage > 1) {
+                        currentPage--;
+                        displayDirectoryPage(currentPage);
+                    }
+                });
+
+                // 下一页
+                nextPageButton.addEventListener('click', function () {
+                    if (currentPage < Math.ceil(allFiles.length / itemsPerPage)) {
+                        currentPage++;
+                        displayDirectoryPage(currentPage);
+                    }
+                });
+
+                // 关闭目录
+                closeDirectoryButton.addEventListener('click', function () {
+                    directoryModal.style.display = 'none';
+                    modalOverlay.style.display = 'none';
+                });
+
+                // 点击遮罩关闭弹出层
+                modalOverlay.addEventListener('click', function () {
+                    if (filenameModal.style.display === 'block') {
+                        filenameModal.style.display = 'none';
+                        uploadButton.focus();
+                    }
+                    if (directoryModal.style.display === 'block') {
+                        directoryModal.style.display = 'none';
+                    }
+                    modalOverlay.style.display = 'none';
+                });
+
+                // 语音控制
+                let isListening = false;
+                let audioContext = null;
+                let isPaused = false;
+                let currentAudio = null;
+
+                // 初始化 AudioContext
+                function initAudioContext() {
+                    if (!audioContext) {
+                        audioContext = new (window.AudioContext || window.webkitAudioContext)();
+                    }
+                }
+
+                // 获取播放权限
+                async function getPlayPermission() {
+                    return new Promise((resolve) => {
+                        const dummyAudio = new Audio('data:audio/wav;base64,UklGRl9vT19XQVZFZm10IBAAAAABAAEAQB8AAEAfAAABAAgAZGF0YU');
+                        dummyAudio.play()
+                            .then(() => {
+                                dummyAudio.pause();
+                                dummyAudio.remove();
+                                resolve();
+                            })
+                            .catch((error) => {
+                                console.warn('Audio play permission denied:', error);
+                                resolve(); // 静默处理权限错误
+                            });
+                    });
+                }
+
+                // 切换文本选择监听
+                document.getElementById('toggle-text-select').addEventListener('click', () => {
+                    isListening = !isListening;
+                    const button = document.getElementById('toggle-text-select');
+                    button.textContent = isListening ? "关闭监听" : "开启监听";
+                    button.classList.toggle('button-danger', isListening);
+                });
+
+                // 暂停/继续播放
+                pausePlayButton.addEventListener('click', () => {
+                    isPaused = !isPaused;
+                    pausePlayButton.textContent = isPaused ? "继续播放" : "暂停播放";
+                    if (isPaused && currentAudio) {
+                        currentAudio.pause();
+                        audioContext.suspend();
+                    } else if (!isPaused && currentAudio) {
+                        currentAudio.play();
+                        audioContext.resume();
+                    }
+                });
+
+                // 播放音频队列
+                async function playAudioSequentially(queue, signal) {
+                    const audioElements = new Map();
+                    const loadingIndicator = document.getElementById('loading-indicator');
+                    try {
+                        while (true) {
+                            if (signal.aborted) break;
+
+                            const chunk = queue.shift();
+                            if (!chunk) {
+                                if (queue.includes(null)) break;
+                                await new Promise(resolve => setTimeout(resolve, 100));
+                                continue;
+                            }
+
+                            if (audioContext.state !== 'running') {
+                                await audioContext.resume();
+                            }
+
+                            const audio = new Audio(chunk.audio);
+                            audioElements.set(chunk.index, audio);
+                            currentAudio = audio;
+
+                            try {
+                                loadingIndicator.textContent = `正在播放第 ${chunk.index + 1} 句`;
+                                loadingIndicator.style.display = 'block';
+                                await playAudio(audio);
+                            } catch (e) {
+                                console.warn(`播放失败: ${e.message}`);
+                            } finally {
+                                audioElements.delete(chunk.index);
+                                currentAudio = null;
+                            }
+                        }
+                        loadingIndicator.textContent = '播放完成';
+                        setTimeout(() => {
+                            loadingIndicator.style.display = 'none';
+                        }, 1000);
+                    } catch (error) {
+                        console.error(error);
+                        loadingIndicator.textContent = `错误: ${error.message}`;
+                        loadingIndicator.style.display = 'block';
+                    } finally {
+                        audioElements.forEach(audio => {
+                            audio.pause();
+                            audio.remove();
+                        });
+                        currentAudio = null;
+                    }
+                }
+
+                // 播放单个音频
+                function playAudio(audio) {
+                    return new Promise((resolve, reject) => {
+                        audio.play()
+                            .then(() => {
+                                audio.onended = () => {
+                                    if (!isPaused) resolve();
+                                };
+                                audio.onerror = reject;
+                            })
+                            .catch(reject);
+                    });
+                }
+
+                // 解析流数据
+                function parseStreamData(line) {
+                    try {
+                        return JSON.parse(line);
+                    } catch (e) {
+                        console.error('解析错误:', e, '原始数据:', line);
+                        return { error: e.message };
+                    }
+                }
+
+                // 创建音频片段
+                function createAudioChunk(data) {
+                    return {
+                        ...data,
+                        audio: data.audio.startsWith('data:') ?
+                            data.audio :
+                            `data:audio/wav;base64,${data.audio}`,
+                        timestamp: Date.now()
+                    };
+                }
+
+                // 按顺序插入队列
+                function insertInOrder(queue, chunk) {
+                    let index = 0;
+                    while (index < queue.length && queue[index].index < chunk.index) {
+                        index++;
+                    }
+                    queue.splice(index, 0, chunk);
+                }
+
+                // 开始转换
+                async function startConversion(sentences) {
+                    const loadingIndicator = document.getElementById('loading-indicator');
+                    try {
+                        const fullText = sentences.map(s => s.text).join(' ');
+                        const response = await fetch('http://141.140.15.30:8028/generate', {
+                            method: 'POST',
+                            headers: { 'Content-Type': 'application/json' },
+                            body: JSON.stringify({ text: fullText, voice: "af_heart", speed: 1.0 })
+                        });
+
+                        if (!response.ok) throw new Error(`服务器错误: ${response.status}`);
+
+                        const reader = response.body.getReader();
+                        const decoder = new TextDecoder();
+                        let buffer = '';
+                        const audioQueue = [];
+                        let expectedIndex = 0;
+
+                        const playController = new AbortController();
+                        const playPromise = playAudioSequentially(audioQueue, playController.signal);
+
+                        while (true) {
+                            const { done, value } = await reader.read();
+                            if (done) break;
+
+                            buffer += decoder.decode(value, { stream: true });
+                            const lines = buffer.split('\n');
+                            buffer = lines.pop() || '';
+
+                            for (const line of lines) {
+                                if (!line.trim()) continue;
+                                const data = parseStreamData(line);
+                                if (data.audio) {
+                                    const sentence = sentences[data.index];
+                                    insertInOrder(audioQueue, createAudioChunk({
+                                        ...data,
+                                        xpath: sentence?.xpath
+                                    }));
+                                }
+                            }
+                        }
+
+                        while (buffer.trim()) {
+                            const data = parseStreamData(buffer.trim());
+                            if (data.audio) {
+                                audioQueue.push(createAudioChunk(data));
+                            }
+                            buffer = '';
+                        }
+
+                        audioQueue.push(null);
+                        await playPromise;
+
+                    } catch (error) {
+                        console.error(error);
+                        loadingIndicator.textContent = `错误: ${error.message}`;
+                        loadingIndicator.style.display = 'block';
+                    }
+                }
+
+                // 监听文本选择
+                document.addEventListener('DOMContentLoaded', () => {
+                    const loadingIndicator = document.getElementById('loading-indicator');
+
+                    document.addEventListener('mouseup', async () => {
+                        if (!isListening) return;
+
+                        const selection = window.getSelection();
+                        const selectedText = selection.toString().trim();
+                        if (selectedText.length === 0) return;
+
+                        selection.removeAllRanges();
+
+                        try {
+                            initAudioContext();
+                            await getPlayPermission();
+                            loadingIndicator.textContent = '正在转换选中文本...';
+                            loadingIndicator.style.display = 'block';
+
+                            const sentences = selectedText.split(/[.!?]+/).filter(s => s.trim()).map((text, index) => ({
+                                text: text.trim(),
+                                xpath: null,
+                                index: index
+                            }));
+
+                            await startConversion(sentences);
+                        } catch (error) {
+                            console.error(error);
+                            loadingIndicator.textContent = `错误: ${error.message}`;
+                            loadingIndicator.style.display = 'block';
+                        }
+                    });
+                });
+
+                // 阅读整页
+                document.getElementById('select-all-text').addEventListener('click', async function () {
+                    const { pdfViewer } = PDFViewerApplication;
+                    const currentPage = pdfViewer.currentPageNumber;
+                    const loadingIndicator = document.getElementById('loading-indicator');
+
+                    try {
+                        initAudioContext();
+                        await getPlayPermission();
+                        loadingIndicator.textContent = '正在提取页面文本...';
+                        loadingIndicator.style.display = 'block';
+
+                        PDFViewerApplication.pdfDocument.getPage(currentPage).then(function (page) {
+                            page.getTextContent().then(async function (textContent) {
+                                const fullText = textContent.items.map(item => item.str).join(' ');
+                                const sentences = fullText.split(/[.!?]+/).filter(s => s.trim()).map((text, index) => ({
+                                    text: text.trim(),
+                                    xpath: null,
+                                    index: index
+                                }));
+
+                                await startConversion(sentences);
+                            });
+                        });
+                    } catch (error) {
+                        console.error(error);
+                        loadingIndicator.textContent = `错误: ${error.message}`;
+                        loadingIndicator.style.display = 'block';
+                    }
+                });
+            </script>
+</body>
+</html>

+ 1410 - 0
static/web/bak/viewer.htmlbak25621

@@ -0,0 +1,1410 @@
+<!DOCTYPE html>
+<html dir="ltr" mozdisallowselectionprint>
+
+<head>
+    <meta charset="utf-8">
+    <meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=1">
+    <meta name="google" content="notranslate">
+    <title>PDF.js viewer</title>
+
+    <!-- This snippet is used in production (included from viewer.html) -->
+    <link rel="resource" type="application/l10n" href="locale/locale.json">
+    <script src="../build/pdf.mjs" type="module"></script>
+    <link rel="stylesheet" href="viewer.css">
+    <script src="viewer.mjs" type="module"></script>
+
+    <style>
+        /* 模态窗口样式 */
+        .modal {
+            display: none;
+            position: fixed;
+            z-index: 10002;
+            left: 50%;
+            top: 50%;
+            transform: translate(-50%, -50%);
+            width: 500px;
+            background-color: white;
+            border: 1px solid #ccc;
+            border-radius: 5px;
+            padding: 20px;
+            box-shadow: 0 5px 15px rgba(0, 0, 0, 0.3);
+        }
+
+        .modal-overlay {
+            display: none;
+            position: fixed;
+            top: 0;
+            left: 0;
+            width: 100%;
+            height: 100%;
+            background-color: rgba(0, 0, 0, 0.5);
+            z-index: 10001;
+        }
+
+        .button {
+            padding: 10px 20px;
+            background-color: #007bff;
+            color: white;
+            border: none;
+            border-radius: 5px;
+            cursor: pointer;
+        }
+
+        .button-secondary {
+            background-color: #6c757d;
+        }
+
+        .button-success {
+            background-color: #28a745;
+        }
+
+        .button-light {
+            background-color: #ded520;
+        }
+
+        .button-danger {
+            background-color: #dc3545;
+        }
+    </style>
+</head>
+
+<body tabindex="0">
+    <div id="loading-indicator"
+        style="display: none; position: fixed; top: 20px; left: 20px; background: rgba(0,0,0,0.7); color: #fff; padding: 10px; border-radius: 5px;">
+        正在生成语音,请稍候...
+    </div>
+
+    <audio id="audio-player" controls
+        style="position: fixed; width: 190px; height: 50px; bottom: 10px; left: 10px; z-index: 1000;"></audio>
+
+    <button id="uploadButton" style="
+    position: fixed;
+    bottom: 20px;
+    right: 20px;
+    z-index: 1000;
+    padding: 10px 20px;
+    background-color: #007bff;
+    color: white;
+    border: none;
+    border-radius: 5px;
+    cursor: pointer;">
+        上传 PDF
+    </button>
+
+    <input type="file" id="pdfInput" accept="application/pdf" style="display: none;" />
+
+    <div id="filenameModal" class="modal">
+        <h3>输入保存的文件名</h3>
+        <input type="text" id="customFileName" placeholder="不含扩展名"
+            style="width: 100%; padding: 5px; margin-top: 10px;" />
+        <div style="margin-top: 20px; text-align: right;">
+            <button id="cancelUpload" class="button button-secondary">取消</button>
+            <button id="confirmUpload" class="button button-success" style="margin-left: 10px;">确认</button>
+        </div>
+    </div>
+
+    <div id="modalOverlay" class="modal-overlay"></div>
+
+    <button id="openDirectoryButton" class="button" style="
+    position: fixed;
+    bottom: 70px;
+    right: 20px;  
+    z-index: 1000;">
+        打开目录
+    </button>
+
+    <button id="select-all-text" class="button button-light" style="
+    position: fixed;
+    bottom: 170px;
+    right: 20px;  
+    z-index: 1000;
+    color: black;">
+        阅读整页
+    </button>
+
+    <button id="toggle-text-select" class="button button-success" style="
+    position: fixed;
+    bottom: 120px;
+    right: 20px;  
+    z-index: 1000;">
+        文本选择
+    </button>
+
+    <div id="directoryModal" class="modal">
+        <h2>已上传的 PDF 文档</h2>
+        <ul id="pdfList" style="list-style-type: none; padding: 0;"></ul>
+        <button id="closeDirectoryButton" class="button button-danger"
+            style="margin-top: 20px; width: 100%;">关闭</button>
+    </div>
+
+    <div id="outerContainer">
+        <div id="outerContainer">
+            <div id="sidebarContainer">
+                <div id="toolbarSidebar" class="toolbarHorizontalGroup">
+                    <div id="toolbarSidebarLeft">
+                        <div id="sidebarViewButtons" class="toolbarHorizontalGroup toggled" role="radiogroup">
+                            <button id="viewThumbnail" class="toolbarButton toggled" type="button"
+                                title="Show Thumbnails" tabindex="0" data-l10n-id="pdfjs-thumbs-button" role="radio"
+                                aria-checked="true" aria-controls="thumbnailView">
+                                <span data-l10n-id="pdfjs-thumbs-button-label">Thumbnails</span>
+                            </button>
+                            <button id="viewOutline" class="toolbarButton" type="button"
+                                title="Show Document Outline (double-click to expand/collapse all items)" tabindex="0"
+                                data-l10n-id="pdfjs-document-outline-button" role="radio" aria-checked="false"
+                                aria-controls="outlineView">
+                                <span data-l10n-id="pdfjs-document-outline-button-label">Document Outline</span>
+                            </button>
+                            <button id="viewAttachments" class="toolbarButton" type="button" title="Show Attachments"
+                                tabindex="0" data-l10n-id="pdfjs-attachments-button" role="radio" aria-checked="false"
+                                aria-controls="attachmentsView">
+                                <span data-l10n-id="pdfjs-attachments-button-label">Attachments</span>
+                            </button>
+                            <button id="viewLayers" class="toolbarButton" type="button"
+                                title="Show Layers (double-click to reset all layers to the default state)" tabindex="0"
+                                data-l10n-id="pdfjs-layers-button" role="radio" aria-checked="false"
+                                aria-controls="layersView">
+                                <span data-l10n-id="pdfjs-layers-button-label">Layers</span>
+                            </button>
+                        </div>
+                    </div>
+
+                    <div id="toolbarSidebarRight">
+                        <div id="outlineOptionsContainer" class="toolbarHorizontalGroup">
+                            <div class="verticalToolbarSeparator"></div>
+
+                            <button id="currentOutlineItem" class="toolbarButton" type="button" disabled="disabled"
+                                title="Find Current Outline Item" tabindex="0"
+                                data-l10n-id="pdfjs-current-outline-item-button">
+                                <span data-l10n-id="pdfjs-current-outline-item-button-label">Current Outline Item</span>
+                            </button>
+                        </div>
+                    </div>
+                </div>
+                <div id="sidebarContent">
+                    <div id="thumbnailView">
+                    </div>
+                    <div id="outlineView" class="hidden">
+                    </div>
+                    <div id="attachmentsView" class="hidden">
+                    </div>
+                    <div id="layersView" class="hidden">
+                    </div>
+                </div>
+                <div id="sidebarResizer"></div>
+            </div> <!-- sidebarContainer -->
+
+            <div id="mainContainer">
+                <div class="toolbar">
+                    <div id="toolbarContainer">
+                        <div id="toolbarViewer" class="toolbarHorizontalGroup">
+                            <div id="toolbarViewerLeft" class="toolbarHorizontalGroup">
+                                <button id="sidebarToggleButton" class="toolbarButton" type="button"
+                                    title="Toggle Sidebar" tabindex="0" data-l10n-id="pdfjs-toggle-sidebar-button"
+                                    aria-expanded="false" aria-haspopup="true" aria-controls="sidebarContainer">
+                                    <span data-l10n-id="pdfjs-toggle-sidebar-button-label">Toggle Sidebar</span>
+                                </button>
+                                <div class="toolbarButtonSpacer"></div>
+                                <div class="toolbarButtonWithContainer">
+                                    <button id="viewFindButton" class="toolbarButton" type="button"
+                                        title="Find in Document" tabindex="0" data-l10n-id="pdfjs-findbar-button"
+                                        aria-expanded="false" aria-controls="findbar">
+                                        <span data-l10n-id="pdfjs-findbar-button-label">Find</span>
+                                    </button>
+                                    <div class="hidden doorHanger toolbarHorizontalGroup" id="findbar">
+                                        <div id="findInputContainer" class="toolbarHorizontalGroup">
+                                            <span class="loadingInput end toolbarHorizontalGroup">
+                                                <input id="findInput" class="toolbarField" title="Find"
+                                                    placeholder="Find in document…" tabindex="0"
+                                                    data-l10n-id="pdfjs-find-input" aria-invalid="false">
+                                            </span>
+                                            <div class="toolbarHorizontalGroup">
+                                                <button id="findPreviousButton" class="toolbarButton" type="button"
+                                                    title="Find the previous occurrence of the phrase" tabindex="0"
+                                                    data-l10n-id="pdfjs-find-previous-button">
+                                                    <span
+                                                        data-l10n-id="pdfjs-find-previous-button-label">Previous</span>
+                                                </button>
+                                                <div class="splitToolbarButtonSeparator"></div>
+                                                <button id="findNextButton" class="toolbarButton" type="button"
+                                                    title="Find the next occurrence of the phrase" tabindex="0"
+                                                    data-l10n-id="pdfjs-find-next-button">
+                                                    <span data-l10n-id="pdfjs-find-next-button-label">Next</span>
+                                                </button>
+                                            </div>
+                                        </div>
+
+                                        <div id="findbarOptionsOneContainer" class="toolbarHorizontalGroup">
+                                            <div class="toggleButton toolbarLabel">
+                                                <input type="checkbox" id="findHighlightAll" tabindex="0" />
+                                                <label for="findHighlightAll"
+                                                    data-l10n-id="pdfjs-find-highlight-checkbox">Highlight All</label>
+                                            </div>
+                                            <div class="toggleButton toolbarLabel">
+                                                <input type="checkbox" id="findMatchCase" tabindex="0" />
+                                                <label for="findMatchCase"
+                                                    data-l10n-id="pdfjs-find-match-case-checkbox-label">Match
+                                                    Case</label>
+                                            </div>
+                                        </div>
+                                        <div id="findbarOptionsTwoContainer" class="toolbarHorizontalGroup">
+                                            <div class="toggleButton toolbarLabel">
+                                                <input type="checkbox" id="findMatchDiacritics" tabindex="0" />
+                                                <label for="findMatchDiacritics"
+                                                    data-l10n-id="pdfjs-find-match-diacritics-checkbox-label">Match
+                                                    Diacritics</label>
+                                            </div>
+                                            <div class="toggleButton toolbarLabel">
+                                                <input type="checkbox" id="findEntireWord" tabindex="0" />
+                                                <label for="findEntireWord"
+                                                    data-l10n-id="pdfjs-find-entire-word-checkbox-label">Whole
+                                                    Words</label>
+                                            </div>
+                                        </div>
+
+                                        <div id="findbarMessageContainer" class="toolbarHorizontalGroup"
+                                            aria-live="polite">
+                                            <span id="findResultsCount" class="toolbarLabel"></span>
+                                            <span id="findMsg" class="toolbarLabel"></span>
+                                        </div>
+                                    </div> <!-- findbar -->
+                                </div>
+                                <div class="toolbarHorizontalGroup hiddenSmallView">
+                                    <button class="toolbarButton" title="Previous Page" type="button" id="previous"
+                                        tabindex="0" data-l10n-id="pdfjs-previous-button">
+                                        <span data-l10n-id="pdfjs-previous-button-label">Previous</span>
+                                    </button>
+                                    <div class="splitToolbarButtonSeparator"></div>
+                                    <button class="toolbarButton" type="button" title="Next Page" id="next" tabindex="0"
+                                        data-l10n-id="pdfjs-next-button">
+                                        <span data-l10n-id="pdfjs-next-button-label">Next</span>
+                                    </button>
+                                </div>
+                                <div class="toolbarHorizontalGroup">
+                                    <span class="loadingInput start toolbarHorizontalGroup">
+                                        <input type="number" id="pageNumber" class="toolbarField" title="Page" value="1"
+                                            min="1" tabindex="0" data-l10n-id="pdfjs-page-input" autocomplete="off">
+                                    </span>
+                                    <span id="numPages" class="toolbarLabel"></span>
+                                </div>
+                            </div>
+                            <div id="toolbarViewerMiddle" class="toolbarHorizontalGroup">
+                                <div class="toolbarHorizontalGroup">
+                                    <button id="zoomOutButton" class="toolbarButton" type="button" title="Zoom Out"
+                                        tabindex="0" data-l10n-id="pdfjs-zoom-out-button">
+                                        <span data-l10n-id="pdfjs-zoom-out-button-label">Zoom Out</span>
+                                    </button>
+                                    <div class="splitToolbarButtonSeparator"></div>
+                                    <button id="zoomInButton" class="toolbarButton" type="button" title="Zoom In"
+                                        tabindex="0" data-l10n-id="pdfjs-zoom-in-button">
+                                        <span data-l10n-id="pdfjs-zoom-in-button-label">Zoom In</span>
+                                    </button>
+                                </div>
+                                <span id="scaleSelectContainer" class="dropdownToolbarButton">
+                                    <select id="scaleSelect" title="Zoom" tabindex="0" data-l10n-id="pdfjs-zoom-select">
+                                        <option id="pageAutoOption" title="" value="auto" selected="selected"
+                                            data-l10n-id="pdfjs-page-scale-auto">Automatic Zoom</option>
+                                        <option id="pageActualOption" title="" value="page-actual"
+                                            data-l10n-id="pdfjs-page-scale-actual">
+                                            Actual Size</option>
+                                        <option id="pageFitOption" title="" value="page-fit"
+                                            data-l10n-id="pdfjs-page-scale-fit">Page Fit
+                                        </option>
+                                        <option id="pageWidthOption" title="" value="page-width"
+                                            data-l10n-id="pdfjs-page-scale-width">Page
+                                            Width</option>
+                                        <option id="customScaleOption" title="" value="custom" disabled="disabled"
+                                            hidden="true" data-l10n-id="pdfjs-page-scale-percent"
+                                            data-l10n-args='{ "scale": 0 }'>0%</option>
+                                        <option title="" value="0.5" data-l10n-id="pdfjs-page-scale-percent"
+                                            data-l10n-args='{ "scale": 50 }'>
+                                            50%</option>
+                                        <option title="" value="0.75" data-l10n-id="pdfjs-page-scale-percent"
+                                            data-l10n-args='{ "scale": 75 }'>
+                                            75%</option>
+                                        <option title="" value="1" data-l10n-id="pdfjs-page-scale-percent"
+                                            data-l10n-args='{ "scale": 100 }'>
+                                            100%</option>
+                                        <option title="" value="1.25" data-l10n-id="pdfjs-page-scale-percent"
+                                            data-l10n-args='{ "scale": 125 }'>
+                                            125%</option>
+                                        <option title="" value="1.5" data-l10n-id="pdfjs-page-scale-percent"
+                                            data-l10n-args='{ "scale": 150 }'>
+                                            150%</option>
+                                        <option title="" value="2" data-l10n-id="pdfjs-page-scale-percent"
+                                            data-l10n-args='{ "scale": 200 }'>
+                                            200%</option>
+                                        <option title="" value="3" data-l10n-id="pdfjs-page-scale-percent"
+                                            data-l10n-args='{ "scale": 300 }'>
+                                            300%</option>
+                                        <option title="" value="4" data-l10n-id="pdfjs-page-scale-percent"
+                                            data-l10n-args='{ "scale": 400 }'>
+                                            400%</option>
+                                    </select>
+                                </span>
+                            </div>
+                            <div id="toolbarViewerRight" class="toolbarHorizontalGroup">
+                                <div id="editorModeButtons" class="toolbarHorizontalGroup" role="radiogroup">
+                                    <div id="editorHighlight" class="toolbarButtonWithContainer">
+                                        <button id="editorHighlightButton" class="toolbarButton" type="button"
+                                            disabled="disabled" title="Highlight" role="radio" aria-expanded="false"
+                                            aria-haspopup="true" aria-controls="editorHighlightParamsToolbar"
+                                            tabindex="0" data-l10n-id="pdfjs-editor-highlight-button">
+                                            <span data-l10n-id="pdfjs-editor-highlight-button-label">Highlight</span>
+                                        </button>
+                                        <div class="editorParamsToolbar hidden doorHangerRight"
+                                            id="editorHighlightParamsToolbar">
+                                            <div id="highlightParamsToolbarContainer"
+                                                class="editorParamsToolbarContainer">
+                                                <div id="editorHighlightColorPicker" class="colorPicker">
+                                                    <span id="highlightColorPickerLabel" class="editorParamsLabel"
+                                                        data-l10n-id="pdfjs-editor-highlight-colorpicker-label">Highlight
+                                                        color</span>
+                                                </div>
+                                                <div id="editorHighlightThickness">
+                                                    <label for="editorFreeHighlightThickness" class="editorParamsLabel"
+                                                        data-l10n-id="pdfjs-editor-free-highlight-thickness-input">Thickness</label>
+                                                    <div class="thicknessPicker">
+                                                        <input type="range" id="editorFreeHighlightThickness"
+                                                            class="editorParamsSlider"
+                                                            data-l10n-id="pdfjs-editor-free-highlight-thickness-title"
+                                                            value="12" min="8" max="24" step="1" tabindex="0">
+                                                    </div>
+                                                </div>
+                                                <div id="editorHighlightVisibility">
+                                                    <div class="divider"></div>
+                                                    <div class="toggler">
+                                                        <label for="editorHighlightShowAll" class="editorParamsLabel"
+                                                            data-l10n-id="pdfjs-editor-highlight-show-all-button-label">Show
+                                                            all</label>
+                                                        <button id="editorHighlightShowAll" class="toggle-button"
+                                                            type="button"
+                                                            data-l10n-id="pdfjs-editor-highlight-show-all-button"
+                                                            aria-pressed="true" tabindex="0"></button>
+                                                    </div>
+                                                </div>
+                                            </div>
+                                        </div>
+                                    </div>
+                                    <div id="editorFreeText" class="toolbarButtonWithContainer">
+                                        <button id="editorFreeTextButton" class="toolbarButton" type="button"
+                                            disabled="disabled" title="Text" role="radio" aria-expanded="false"
+                                            aria-haspopup="true" aria-controls="editorFreeTextParamsToolbar"
+                                            tabindex="0" data-l10n-id="pdfjs-editor-free-text-button">
+                                            <span data-l10n-id="pdfjs-editor-free-text-button-label">Text</span>
+                                        </button>
+                                        <div class="editorParamsToolbar hidden doorHangerRight"
+                                            id="editorFreeTextParamsToolbar">
+                                            <div class="editorParamsToolbarContainer">
+                                                <div class="editorParamsSetter">
+                                                    <label for="editorFreeTextColor" class="editorParamsLabel"
+                                                        data-l10n-id="pdfjs-editor-free-text-color-input">Color</label>
+                                                    <input type="color" id="editorFreeTextColor"
+                                                        class="editorParamsColor" tabindex="0">
+                                                </div>
+                                                <div class="editorParamsSetter">
+                                                    <label for="editorFreeTextFontSize" class="editorParamsLabel"
+                                                        data-l10n-id="pdfjs-editor-free-text-size-input">Size</label>
+                                                    <input type="range" id="editorFreeTextFontSize"
+                                                        class="editorParamsSlider" value="10" min="5" max="100" step="1"
+                                                        tabindex="0">
+                                                </div>
+                                            </div>
+                                        </div>
+                                    </div>
+                                    <div id="editorInk" class="toolbarButtonWithContainer">
+                                        <button id="editorInkButton" class="toolbarButton" type="button"
+                                            disabled="disabled" title="Draw" role="radio" aria-expanded="false"
+                                            aria-haspopup="true" aria-controls="editorInkParamsToolbar" tabindex="0"
+                                            data-l10n-id="pdfjs-editor-ink-button">
+                                            <span data-l10n-id="pdfjs-editor-ink-button-label">Draw</span>
+                                        </button>
+                                        <div class="editorParamsToolbar hidden doorHangerRight"
+                                            id="editorInkParamsToolbar">
+                                            <div class="editorParamsToolbarContainer">
+                                                <div class="editorParamsSetter">
+                                                    <label for="editorInkColor" class="editorParamsLabel"
+                                                        data-l10n-id="pdfjs-editor-ink-color-input">Color</label>
+                                                    <input type="color" id="editorInkColor" class="editorParamsColor"
+                                                        tabindex="0">
+                                                </div>
+                                                <div class="editorParamsSetter">
+                                                    <label for="editorInkThickness" class="editorParamsLabel"
+                                                        data-l10n-id="pdfjs-editor-ink-thickness-input">Thickness</label>
+                                                    <input type="range" id="editorInkThickness"
+                                                        class="editorParamsSlider" value="1" min="1" max="20" step="1"
+                                                        tabindex="0">
+                                                </div>
+                                                <div class="editorParamsSetter">
+                                                    <label for="editorInkOpacity" class="editorParamsLabel"
+                                                        data-l10n-id="pdfjs-editor-ink-opacity-input">Opacity</label>
+                                                    <input type="range" id="editorInkOpacity" class="editorParamsSlider"
+                                                        value="100" min="1" max="100" step="1" tabindex="0">
+                                                </div>
+                                            </div>
+                                        </div>
+                                    </div>
+                                    <div id="editorStamp" class="toolbarButtonWithContainer">
+                                        <button id="editorStampButton" class="toolbarButton" type="button"
+                                            disabled="disabled" title="Add or edit images" role="radio"
+                                            aria-expanded="false" aria-haspopup="true"
+                                            aria-controls="editorStampParamsToolbar" tabindex="0"
+                                            data-l10n-id="pdfjs-editor-stamp-button">
+                                            <span data-l10n-id="pdfjs-editor-stamp-button-label">Add or edit
+                                                images</span>
+                                        </button>
+                                        <div class="editorParamsToolbar hidden doorHangerRight menu"
+                                            id="editorStampParamsToolbar">
+                                            <div class="menuContainer">
+                                                <button id="editorStampAddImage" class="toolbarButton labeled"
+                                                    type="button" title="Add image" tabindex="0"
+                                                    data-l10n-id="pdfjs-editor-stamp-add-image-button">
+                                                    <span class="editorParamsLabel"
+                                                        data-l10n-id="pdfjs-editor-stamp-add-image-button-label">Add
+                                                        image</span>
+                                                </button>
+                                            </div>
+                                        </div>
+                                    </div>
+                                </div>
+
+                                <div id="editorModeSeparator" class="verticalToolbarSeparator"></div>
+
+                                <div class="toolbarHorizontalGroup hiddenMediumView">
+                                    <button id="printButton" class="toolbarButton" type="button" title="Print"
+                                        tabindex="0" data-l10n-id="pdfjs-print-button">
+                                        <span data-l10n-id="pdfjs-print-button-label">Print</span>
+                                    </button>
+
+                                    <button id="downloadButton" class="toolbarButton" type="button" title="Save"
+                                        tabindex="0" data-l10n-id="pdfjs-save-button">
+                                        <span data-l10n-id="pdfjs-save-button-label">Save</span>
+                                    </button>
+                                </div>
+
+                                <div class="verticalToolbarSeparator hiddenMediumView"></div>
+
+                                <div id="secondaryToolbarToggle" class="toolbarButtonWithContainer">
+                                    <button id="secondaryToolbarToggleButton" class="toolbarButton" type="button"
+                                        title="Tools" tabindex="0" data-l10n-id="pdfjs-tools-button"
+                                        aria-expanded="false" aria-haspopup="true" aria-controls="secondaryToolbar">
+                                        <span data-l10n-id="pdfjs-tools-button-label">Tools</span>
+                                    </button>
+                                    <div id="secondaryToolbar" class="hidden doorHangerRight menu">
+                                        <div id="secondaryToolbarButtonContainer" class="menuContainer">
+                                            <button id="secondaryOpenFile" class="toolbarButton labeled" type="button"
+                                                title="Open File" tabindex="0" data-l10n-id="pdfjs-open-file-button">
+                                                <span data-l10n-id="pdfjs-open-file-button-label">Open</span>
+                                            </button>
+
+                                            <div class="visibleMediumView">
+                                                <button id="secondaryPrint" class="toolbarButton labeled" type="button"
+                                                    title="Print" tabindex="0" data-l10n-id="pdfjs-print-button">
+                                                    <span data-l10n-id="pdfjs-print-button-label">Print</span>
+                                                </button>
+
+                                                <button id="secondaryDownload" class="toolbarButton labeled"
+                                                    type="button" title="Save" tabindex="0"
+                                                    data-l10n-id="pdfjs-save-button">
+                                                    <span data-l10n-id="pdfjs-save-button-label">Save</span>
+                                                </button>
+
+                                            </div>
+
+                                            <div class="horizontalToolbarSeparator"></div>
+
+                                            <button id="presentationMode" class="toolbarButton labeled" type="button"
+                                                title="Switch to Presentation Mode" tabindex="0"
+                                                data-l10n-id="pdfjs-presentation-mode-button">
+                                                <span data-l10n-id="pdfjs-presentation-mode-button-label">Presentation
+                                                    Mode</span>
+                                            </button>
+
+                                            <a href="#" id="viewBookmark" class="toolbarButton labeled"
+                                                title="Current Page (View URL from Current Page)" tabindex="0"
+                                                data-l10n-id="pdfjs-bookmark-button">
+                                                <span data-l10n-id="pdfjs-bookmark-button-label">Current Page</span>
+                                            </a>
+
+                                            <div id="viewBookmarkSeparator" class="horizontalToolbarSeparator"></div>
+
+                                            <button id="firstPage" class="toolbarButton labeled" type="button"
+                                                title="Go to First Page" tabindex="0"
+                                                data-l10n-id="pdfjs-first-page-button">
+                                                <span data-l10n-id="pdfjs-first-page-button-label">Go to First
+                                                    Page</span>
+                                            </button>
+                                            <button id="lastPage" class="toolbarButton labeled" type="button"
+                                                title="Go to Last Page" tabindex="0"
+                                                data-l10n-id="pdfjs-last-page-button">
+                                                <span data-l10n-id="pdfjs-last-page-button-label">Go to Last Page</span>
+                                            </button>
+
+                                            <div class="horizontalToolbarSeparator"></div>
+
+                                            <button id="pageRotateCw" class="toolbarButton labeled" type="button"
+                                                title="Rotate Clockwise" tabindex="0"
+                                                data-l10n-id="pdfjs-page-rotate-cw-button">
+                                                <span data-l10n-id="pdfjs-page-rotate-cw-button-label">Rotate
+                                                    Clockwise</span>
+                                            </button>
+                                            <button id="pageRotateCcw" class="toolbarButton labeled" type="button"
+                                                title="Rotate Counterclockwise" tabindex="0"
+                                                data-l10n-id="pdfjs-page-rotate-ccw-button">
+                                                <span data-l10n-id="pdfjs-page-rotate-ccw-button-label">Rotate
+                                                    Counterclockwise</span>
+                                            </button>
+
+                                            <div class="horizontalToolbarSeparator"></div>
+
+                                            <div id="cursorToolButtons" role="radiogroup">
+                                                <button id="cursorSelectTool" class="toolbarButton labeled toggled"
+                                                    type="button" title="Enable Text Selection Tool" tabindex="0"
+                                                    data-l10n-id="pdfjs-cursor-text-select-tool-button" role="radio"
+                                                    aria-checked="true">
+                                                    <span data-l10n-id="pdfjs-cursor-text-select-tool-button-label">Text
+                                                        Selection Tool</span>
+                                                </button>
+                                                <button id="cursorHandTool" class="toolbarButton labeled" type="button"
+                                                    title="Enable Hand Tool" tabindex="0"
+                                                    data-l10n-id="pdfjs-cursor-hand-tool-button" role="radio"
+                                                    aria-checked="false">
+                                                    <span data-l10n-id="pdfjs-cursor-hand-tool-button-label">Hand
+                                                        Tool</span>
+                                                </button>
+                                            </div>
+
+                                            <div class="horizontalToolbarSeparator"></div>
+
+                                            <div id="scrollModeButtons" role="radiogroup">
+                                                <button id="scrollPage" class="toolbarButton labeled" type="button"
+                                                    title="Use Page Scrolling" tabindex="0"
+                                                    data-l10n-id="pdfjs-scroll-page-button" role="radio"
+                                                    aria-checked="false">
+                                                    <span data-l10n-id="pdfjs-scroll-page-button-label">Page
+                                                        Scrolling</span>
+                                                </button>
+                                                <button id="scrollVertical" class="toolbarButton labeled toggled"
+                                                    type="button" title="Use Vertical Scrolling" tabindex="0"
+                                                    data-l10n-id="pdfjs-scroll-vertical-button" role="radio"
+                                                    aria-checked="true">
+                                                    <span data-l10n-id="pdfjs-scroll-vertical-button-label">Vertical
+                                                        Scrolling</span>
+                                                </button>
+                                                <button id="scrollHorizontal" class="toolbarButton labeled"
+                                                    type="button" title="Use Horizontal Scrolling" tabindex="0"
+                                                    data-l10n-id="pdfjs-scroll-horizontal-button" role="radio"
+                                                    aria-checked="false">
+                                                    <span data-l10n-id="pdfjs-scroll-horizontal-button-label">Horizontal
+                                                        Scrolling</span>
+                                                </button>
+                                                <button id="scrollWrapped" class="toolbarButton labeled" type="button"
+                                                    title="Use Wrapped Scrolling" tabindex="0"
+                                                    data-l10n-id="pdfjs-scroll-wrapped-button" role="radio"
+                                                    aria-checked="false">
+                                                    <span data-l10n-id="pdfjs-scroll-wrapped-button-label">Wrapped
+                                                        Scrolling</span>
+                                                </button>
+                                            </div>
+
+                                            <div class="horizontalToolbarSeparator"></div>
+
+                                            <div id="spreadModeButtons" role="radiogroup">
+                                                <button id="spreadNone" class="toolbarButton labeled toggled"
+                                                    type="button" title="Do not join page spreads" tabindex="0"
+                                                    data-l10n-id="pdfjs-spread-none-button" role="radio"
+                                                    aria-checked="true">
+                                                    <span data-l10n-id="pdfjs-spread-none-button-label">No
+                                                        Spreads</span>
+                                                </button>
+                                                <button id="spreadOdd" class="toolbarButton labeled" type="button"
+                                                    title="Join page spreads starting with odd-numbered pages"
+                                                    tabindex="0" data-l10n-id="pdfjs-spread-odd-button" role="radio"
+                                                    aria-checked="false">
+                                                    <span data-l10n-id="pdfjs-spread-odd-button-label">Odd
+                                                        Spreads</span>
+                                                </button>
+                                                <button id="spreadEven" class="toolbarButton labeled" type="button"
+                                                    title="Join page spreads starting with even-numbered pages"
+                                                    tabindex="0" data-l10n-id="pdfjs-spread-even-button" role="radio"
+                                                    aria-checked="false">
+                                                    <span data-l10n-id="pdfjs-spread-even-button-label">Even
+                                                        Spreads</span>
+                                                </button>
+                                            </div>
+
+                                            <div id="imageAltTextSettingsSeparator"
+                                                class="horizontalToolbarSeparator hidden"></div>
+                                            <button id="imageAltTextSettings" type="button"
+                                                class="toolbarButton labeled hidden" title="Image alt text settings"
+                                                tabindex="0" data-l10n-id="pdfjs-image-alt-text-settings-button"
+                                                aria-controls="altTextSettingsDialog">
+                                                <span data-l10n-id="pdfjs-image-alt-text-settings-button-label">Image
+                                                    alt text settings</span>
+                                            </button>
+
+                                            <div class="horizontalToolbarSeparator"></div>
+
+                                            <button id="documentProperties" class="toolbarButton labeled" type="button"
+                                                title="Document Properties…" tabindex="0"
+                                                data-l10n-id="pdfjs-document-properties-button"
+                                                aria-controls="documentPropertiesDialog">
+                                                <span data-l10n-id="pdfjs-document-properties-button-label">Document
+                                                    Properties…</span>
+                                            </button>
+                                        </div>
+                                    </div> <!-- secondaryToolbar -->
+                                </div>
+                            </div>
+                        </div>
+                        <div id="loadingBar">
+                            <div class="progress">
+                                <div class="glimmer">
+                                </div>
+                            </div>
+                        </div>
+                    </div>
+                </div>
+
+                <div id="viewerContainer" tabindex="0">
+                    <div id="viewer" class="pdfViewer"></div>
+                </div>
+            </div> <!-- mainContainer -->
+
+            <div id="dialogContainer">
+                <dialog id="passwordDialog">
+                    <div class="row">
+                        <label for="password" id="passwordText" data-l10n-id="pdfjs-password-label">Enter the password
+                            to open this
+                            PDF file:</label>
+                    </div>
+                    <div class="row">
+                        <input type="password" id="password" class="toolbarField">
+                    </div>
+                    <div class="buttonRow">
+                        <button id="passwordCancel" class="dialogButton" type="button"><span
+                                data-l10n-id="pdfjs-password-cancel-button">Cancel</span></button>
+                        <button id="passwordSubmit" class="dialogButton" type="button"><span
+                                data-l10n-id="pdfjs-password-ok-button">OK</span></button>
+                    </div>
+                </dialog>
+                <dialog id="documentPropertiesDialog">
+                    <div class="row">
+                        <span id="fileNameLabel" data-l10n-id="pdfjs-document-properties-file-name">File name:</span>
+                        <p id="fileNameField" aria-labelledby="fileNameLabel">-</p>
+                    </div>
+                    <div class="row">
+                        <span id="fileSizeLabel" data-l10n-id="pdfjs-document-properties-file-size">File size:</span>
+                        <p id="fileSizeField" aria-labelledby="fileSizeLabel">-</p>
+                    </div>
+                    <div class="separator"></div>
+                    <div class="row">
+                        <span id="titleLabel" data-l10n-id="pdfjs-document-properties-title">Title:</span>
+                        <p id="titleField" aria-labelledby="titleLabel">-</p>
+                    </div>
+                    <div class="row">
+                        <span id="authorLabel" data-l10n-id="pdfjs-document-properties-author">Author:</span>
+                        <p id="authorField" aria-labelledby="authorLabel">-</p>
+                    </div>
+                    <div class="row">
+                        <span id="subjectLabel" data-l10n-id="pdfjs-document-properties-subject">Subject:</span>
+                        <p id="subjectField" aria-labelledby="subjectLabel">-</p>
+                    </div>
+                    <div class="row">
+                        <span id="keywordsLabel" data-l10n-id="pdfjs-document-properties-keywords">Keywords:</span>
+                        <p id="keywordsField" aria-labelledby="keywordsLabel">-</p>
+                    </div>
+                    <div class="row">
+                        <span id="creationDateLabel" data-l10n-id="pdfjs-document-properties-creation-date">Creation
+                            Date:</span>
+                        <p id="creationDateField" aria-labelledby="creationDateLabel">-</p>
+                    </div>
+                    <div class="row">
+                        <span id="modificationDateLabel"
+                            data-l10n-id="pdfjs-document-properties-modification-date">Modification
+                            Date:</span>
+                        <p id="modificationDateField" aria-labelledby="modificationDateLabel">-</p>
+                    </div>
+                    <div class="row">
+                        <span id="creatorLabel" data-l10n-id="pdfjs-document-properties-creator">Creator:</span>
+                        <p id="creatorField" aria-labelledby="creatorLabel">-</p>
+                    </div>
+                    <div class="separator"></div>
+                    <div class="row">
+                        <span id="producerLabel" data-l10n-id="pdfjs-document-properties-producer">PDF Producer:</span>
+                        <p id="producerField" aria-labelledby="producerLabel">-</p>
+                    </div>
+                    <div class="row">
+                        <span id="versionLabel" data-l10n-id="pdfjs-document-properties-version">PDF Version:</span>
+                        <p id="versionField" aria-labelledby="versionLabel">-</p>
+                    </div>
+                    <div class="row">
+                        <span id="pageCountLabel" data-l10n-id="pdfjs-document-properties-page-count">Page Count:</span>
+                        <p id="pageCountField" aria-labelledby="pageCountLabel">-</p>
+                    </div>
+                    <div class="row">
+                        <span id="pageSizeLabel" data-l10n-id="pdfjs-document-properties-page-size">Page Size:</span>
+                        <p id="pageSizeField" aria-labelledby="pageSizeLabel">-</p>
+                    </div>
+                    <div class="separator"></div>
+                    <div class="row">
+                        <span id="linearizedLabel" data-l10n-id="pdfjs-document-properties-linearized">Fast Web
+                            View:</span>
+                        <p id="linearizedField" aria-labelledby="linearizedLabel">-</p>
+                    </div>
+                    <div class="buttonRow">
+                        <button id="documentPropertiesClose" class="dialogButton" type="button"><span
+                                data-l10n-id="pdfjs-document-properties-close-button">Close</span></button>
+                    </div>
+                </dialog>
+                <dialog class="dialog altText" id="altTextDialog" aria-labelledby="dialogLabel"
+                    aria-describedby="dialogDescription">
+                    <div id="altTextContainer" class="mainContainer">
+                        <div id="overallDescription">
+                            <span id="dialogLabel" data-l10n-id="pdfjs-editor-alt-text-dialog-label"
+                                class="title">Choose an
+                                option</span>
+                            <span id="dialogDescription" data-l10n-id="pdfjs-editor-alt-text-dialog-description">
+                                Alt text (alternative text) helps when people can’t see the image or when it doesn’t
+                                load.
+                            </span>
+                        </div>
+                        <div id="addDescription">
+                            <div class="radio">
+                                <div class="radioButton">
+                                    <input type="radio" id="descriptionButton" name="altTextOption" tabindex="0"
+                                        aria-describedby="descriptionAreaLabel" checked>
+                                    <label for="descriptionButton"
+                                        data-l10n-id="pdfjs-editor-alt-text-add-description-label">Add a
+                                        description</label>
+                                </div>
+                                <div class="radioLabel">
+                                    <span id="descriptionAreaLabel"
+                                        data-l10n-id="pdfjs-editor-alt-text-add-description-description">
+                                        Aim for 1-2 sentences that describe the subject, setting, or actions.
+                                    </span>
+                                </div>
+                            </div>
+                            <div class="descriptionArea">
+                                <textarea id="descriptionTextarea"
+                                    placeholder="For example, “A young man sits down at a table to eat a meal”"
+                                    aria-labelledby="descriptionAreaLabel" data-l10n-id="pdfjs-editor-alt-text-textarea"
+                                    tabindex="0"></textarea>
+                            </div>
+                        </div>
+                        <div id="markAsDecorative">
+                            <div class="radio">
+                                <div class="radioButton">
+                                    <input type="radio" id="decorativeButton" name="altTextOption"
+                                        aria-describedby="decorativeLabel">
+                                    <label for="decorativeButton"
+                                        data-l10n-id="pdfjs-editor-alt-text-mark-decorative-label">Mark as
+                                        decorative</label>
+                                </div>
+                                <div class="radioLabel">
+                                    <span id="decorativeLabel"
+                                        data-l10n-id="pdfjs-editor-alt-text-mark-decorative-description">
+                                        This is used for ornamental images, like borders or watermarks.
+                                    </span>
+                                </div>
+                            </div>
+                        </div>
+                        <div id="buttons">
+                            <button id="altTextCancel" class="secondaryButton" type="button" tabindex="0"><span
+                                    data-l10n-id="pdfjs-editor-alt-text-cancel-button">Cancel</span></button>
+                            <button id="altTextSave" class="primaryButton" type="button" tabindex="0"><span
+                                    data-l10n-id="pdfjs-editor-alt-text-save-button">Save</span></button>
+                        </div>
+                    </div>
+                </dialog>
+                <dialog class="dialog newAltText" id="newAltTextDialog" aria-labelledby="newAltTextTitle"
+                    aria-describedby="newAltTextDescription" tabindex="0">
+                    <div id="newAltTextContainer" class="mainContainer">
+                        <div class="title">
+                            <span id="newAltTextTitle" data-l10n-id="pdfjs-editor-new-alt-text-dialog-edit-label"
+                                role="sectionhead" tabindex="0">Edit alt text (image description)</span>
+                        </div>
+                        <div id="mainContent">
+                            <div id="descriptionAndSettings">
+                                <div id="descriptionInstruction">
+                                    <div id="newAltTextDescriptionContainer">
+                                        <div class="altTextSpinner" role="status" aria-live="polite"></div>
+                                        <textarea id="newAltTextDescriptionTextarea"
+                                            placeholder="Write your description here…"
+                                            aria-labelledby="descriptionAreaLabel"
+                                            data-l10n-id="pdfjs-editor-new-alt-text-textarea" tabindex="0"></textarea>
+                                    </div>
+                                    <span id="newAltTextDescription" role="note"
+                                        data-l10n-id="pdfjs-editor-new-alt-text-description">Short
+                                        description for people who can’t see the image or when the image doesn’t
+                                        load.</span>
+                                    <div id="newAltTextDisclaimer" role="note">
+                                        <div><span data-l10n-id="pdfjs-editor-new-alt-text-disclaimer1">This alt text
+                                                was created
+                                                automatically and may be inaccurate.</span> <a
+                                                href="https://support.mozilla.org/en-US/kb/pdf-alt-text" target="_blank"
+                                                rel="noopener noreferrer" id="newAltTextLearnMore"
+                                                data-l10n-id="pdfjs-editor-new-alt-text-disclaimer-learn-more-url"
+                                                tabindex="0">Learn more</a></div>
+                                    </div>
+                                </div>
+                                <div id="newAltTextCreateAutomatically" class="toggler">
+                                    <button id="newAltTextCreateAutomaticallyButton" class="toggle-button" type="button"
+                                        aria-pressed="true" tabindex="0"></button>
+                                    <label for="newAltTextCreateAutomaticallyButton" class="togglerLabel"
+                                        data-l10n-id="pdfjs-editor-new-alt-text-create-automatically-button-label">Create
+                                        alt text
+                                        automatically</label>
+                                </div>
+                                <div id="newAltTextDownloadModel" class="hidden">
+                                    <span id="newAltTextDownloadModelDescription"
+                                        data-l10n-id="pdfjs-editor-new-alt-text-ai-model-downloading-progress"
+                                        aria-valuemin="0"
+                                        data-l10n-args='{ "totalSize": 0, "downloadedSize": 0 }'>Downloading alt text AI
+                                        model (0 of 0
+                                        MB)</span>
+                                </div>
+                            </div>
+                            <div id="newAltTextImagePreview"></div>
+                        </div>
+                        <div id="newAltTextError" class="messageBar">
+                            <div>
+                                <div>
+                                    <span class="title" data-l10n-id="pdfjs-editor-new-alt-text-error-title">Couldn’t
+                                        create alt text
+                                        automatically</span>
+                                    <span class="description"
+                                        data-l10n-id="pdfjs-editor-new-alt-text-error-description">Please write your
+                                        own alt text or try again later.</span>
+                                </div>
+                                <button id="newAltTextCloseButton" class="closeButton" type="button" tabindex="0"
+                                    title="Close"><span
+                                        data-l10n-id="pdfjs-editor-new-alt-text-error-close-button">Close</span></button>
+                            </div>
+                        </div>
+                        <div id="newAltTextButtons" class="dialogButtonsGroup">
+                            <button id="newAltTextCancel" type="button" class="secondaryButton hidden"
+                                tabindex="0"><span
+                                    data-l10n-id="pdfjs-editor-alt-text-cancel-button">Cancel</span></button>
+                            <button id="newAltTextNotNow" type="button" class="secondaryButton" tabindex="0"><span
+                                    data-l10n-id="pdfjs-editor-new-alt-text-not-now-button">Not now</span></button>
+                            <button id="newAltTextSave" type="button" class="primaryButton" tabindex="0"><span
+                                    data-l10n-id="pdfjs-editor-alt-text-save-button">Save</span></button>
+                        </div>
+                    </div>
+                </dialog>
+
+                <dialog class="dialog" id="altTextSettingsDialog" aria-labelledby="altTextSettingsTitle">
+                    <div id="altTextSettingsContainer" class="mainContainer">
+                        <div class="title">
+                            <span id="altTextSettingsTitle" data-l10n-id="pdfjs-editor-alt-text-settings-dialog-label"
+                                role="sectionhead" tabindex="0" class="title">Image alt text settings</span>
+                        </div>
+                        <div id="automaticAltText">
+                            <span data-l10n-id="pdfjs-editor-alt-text-settings-automatic-title">Automatic alt
+                                text</span>
+                            <div id="automaticSettings">
+                                <div id="createModelSetting">
+                                    <div class="toggler">
+                                        <button id="createModelButton" type="button" class="toggle-button"
+                                            aria-pressed="true" tabindex="0"></button>
+                                        <label for="createModelButton" class="togglerLabel"
+                                            data-l10n-id="pdfjs-editor-alt-text-settings-create-model-button-label">Create
+                                            alt text
+                                            automatically</label>
+                                    </div>
+                                    <div id="createModelDescription" class="description">
+                                        <span
+                                            data-l10n-id="pdfjs-editor-alt-text-settings-create-model-description">Suggests
+                                            descriptions to
+                                            help people who can’t see the image or when the image doesn’t load.</span>
+                                        <a href="https://support.mozilla.org/en-US/kb/pdf-alt-text" target="_blank"
+                                            rel="noopener noreferrer" id="altTextSettingsLearnMore"
+                                            data-l10n-id="pdfjs-editor-new-alt-text-disclaimer-learn-more-url"
+                                            tabindex="0">Learn more</a>
+                                    </div>
+                                </div>
+                                <div id="aiModelSettings">
+                                    <div>
+                                        <span data-l10n-id="pdfjs-editor-alt-text-settings-download-model-label"
+                                            data-l10n-args='{ "totalSize": 180 }'>Alt text AI model (180MB)</span>
+                                        <div id="aiModelDescription" class="description">
+                                            <span
+                                                data-l10n-id="pdfjs-editor-alt-text-settings-ai-model-description">Runs
+                                                locally on your device
+                                                so your data stays private. Required for automatic alt text.</span>
+                                        </div>
+                                    </div>
+                                    <button id="deleteModelButton" type="button" class="secondaryButton"
+                                        tabindex="0"><span
+                                            data-l10n-id="pdfjs-editor-alt-text-settings-delete-model-button">Delete</span></button>
+                                    <button id="downloadModelButton" type="button" class="secondaryButton"
+                                        tabindex="0"><span
+                                            data-l10n-id="pdfjs-editor-alt-text-settings-download-model-button">Download</span></button>
+                                </div>
+                            </div>
+                        </div>
+                        <div class="dialogSeparator"></div>
+                        <div id="altTextEditor">
+                            <span data-l10n-id="pdfjs-editor-alt-text-settings-editor-title">Alt text editor</span>
+                            <div id="showAltTextEditor">
+                                <div class="toggler">
+                                    <button id="showAltTextDialogButton" type="button" class="toggle-button"
+                                        aria-pressed="true" tabindex="0"></button>
+                                    <label for="showAltTextDialogButton" class="togglerLabel"
+                                        data-l10n-id="pdfjs-editor-alt-text-settings-show-dialog-button-label">Show alt
+                                        text editor right away
+                                        when adding an image</label>
+                                </div>
+                                <div id="showAltTextDialogDescription" class="description">
+                                    <span data-l10n-id="pdfjs-editor-alt-text-settings-show-dialog-description">Helps
+                                        you make sure all your
+                                        images have alt text.</span>
+                                </div>
+                            </div>
+                        </div>
+                        <div id="buttons" class="dialogButtonsGroup">
+                            <button id="altTextSettingsCloseButton" type="button" class="primaryButton"
+                                tabindex="0"><span
+                                    data-l10n-id="pdfjs-editor-alt-text-settings-close-button">Close</span></button>
+                        </div>
+                    </div>
+                </dialog>
+                <dialog id="printServiceDialog" style="min-width: 200px;">
+                    <div class="row">
+                        <span data-l10n-id="pdfjs-print-progress-message">Preparing document for printing…</span>
+                    </div>
+                    <div class="row">
+                        <progress value="0" max="100"></progress>
+                        <span data-l10n-id="pdfjs-print-progress-percent" data-l10n-args='{ "progress": 0 }'
+                            class="relative-progress">0%</span>
+                    </div>
+                    <div class="buttonRow">
+                        <button id="printCancel" class="dialogButton" type="button"><span
+                                data-l10n-id="pdfjs-print-progress-close-button">Cancel</span></button>
+                    </div>
+                </dialog>
+            </div> <!-- dialogContainer -->
+
+        </div> <!-- outerContainer -->
+
+        <div id="printContainer"></div>
+
+        <script>
+            const uploadButton = document.getElementById('uploadButton');
+            const pdfInput = document.getElementById('pdfInput');
+            const filenameModal = document.getElementById('filenameModal');
+            const modalOverlay = document.getElementById('modalOverlay');
+            const confirmUploadButton = document.getElementById('confirmUpload');
+            const cancelUploadButton = document.getElementById('cancelUpload');
+            const customFileNameInput = document.getElementById('customFileName');
+            const openDirectoryButton = document.getElementById('openDirectoryButton');
+            const directoryModal = document.getElementById('directoryModal');
+            const closeDirectoryButton = document.getElementById('closeDirectoryButton');
+            const pdfList = document.getElementById('pdfList');
+
+            let selectedFile = null;
+
+            // 上传按钮点击事件
+            uploadButton.addEventListener('click', function () {
+                pdfInput.click();
+            });
+
+            // 文件选择事件
+            pdfInput.addEventListener('change', function (event) {
+                const file = event.target.files[0];
+                if (file && file.type === 'application/pdf') {
+                    selectedFile = file;
+                    modalOverlay.style.display = 'block';
+                    filenameModal.style.display = 'block';
+                    customFileNameInput.value = "";
+                    customFileNameInput.focus();
+                } else {
+                    alert('请选择一个 PDF 文件');
+                }
+            });
+
+            // 取消上传
+            cancelUploadButton.addEventListener('click', function () {
+                filenameModal.style.display = 'none';
+                modalOverlay.style.display = 'none';
+                pdfInput.value = "";
+                selectedFile = null;
+            });
+
+            // 确认上传
+            confirmUploadButton.addEventListener('click', function () {
+                const customFileName = customFileNameInput.value.trim();
+                if (customFileName === "") {
+                    alert('请输入保存的文件名');
+                    customFileNameInput.focus();
+                    return;
+                }
+
+                const invalidChars = /[\\/:"*?<>|]+/;
+                if (invalidChars.test(customFileName)) {
+                    alert('文件名包含非法字符:\\ / : " * ? < > |');
+                    customFileNameInput.focus();
+                    return;
+                }
+
+                if (!selectedFile) {
+                    alert('没有选择文件');
+                    return;
+                }
+
+                const formData = new FormData();
+                formData.append('file', selectedFile);
+                formData.append('custom_name', customFileName);
+
+                fetch('http://117.50.195.224:8005/upload-pdf', {
+                    method: 'POST',
+                    body: formData
+                })
+                    .then(response => response.json())
+                    .then(data => {
+                        if (data.success) {
+                            const uploadedFilePath = data.file_path;
+                            const redirectUrl = `viewer.html?file=${encodeURIComponent(uploadedFilePath)}`;
+                            window.location.href = redirectUrl;
+                        } else {
+                            alert('上传失败: ' + data.error);
+                        }
+                    })
+                    .catch(error => {
+                        console.error('Error:', error);
+                        alert('上传过程中发生错误');
+                    })
+                    .finally(() => {
+                        filenameModal.style.display = 'none';
+                        modalOverlay.style.display = 'none';
+                        pdfInput.value = "";
+                        selectedFile = null;
+                    });
+            });
+
+            // 打开目录
+            openDirectoryButton.addEventListener('click', function () {
+                modalOverlay.style.display = 'block';
+                directoryModal.style.display = 'block';
+                pdfList.innerHTML = '';
+
+                fetch('http://117.50.195.224:8005/list-pdfs')
+                    .then(response => response.json())
+                    .then(data => {
+                        if (data.success) {
+                            const files = data.files;
+                            if (files.length === 0) {
+                                const listItem = document.createElement('li');
+                                listItem.textContent = '没有已上传的 PDF 文档。';
+                                pdfList.appendChild(listItem);
+                            } else {
+                                files.forEach(file => {
+                                    const listItem = document.createElement('li');
+                                    listItem.style.marginBottom = '10px';
+                                    const link = document.createElement('a');
+                                    link.href = `viewer.html?file=${file.url}`;
+                                    link.textContent = file.name;
+                                    link.style.textDecoration = 'none';
+                                    link.style.color = '#007bff';
+                                    link.addEventListener('click', function (event) {
+                                        event.preventDefault();
+                                        window.location.href = link.getAttribute('href');
+                                    });
+                                    listItem.appendChild(link);
+                                    pdfList.appendChild(listItem);
+                                });
+                            }
+                        } else {
+                            alert('无法获取文件列表: ' + data.error);
+                        }
+                    })
+                    .catch(error => {
+                        console.error('Error:', error);
+                        alert('获取文件列表时发生错误');
+                    });
+            });
+
+            // 关闭目录
+            closeDirectoryButton.addEventListener('click', function () {
+                directoryModal.style.display = 'none';
+                modalOverlay.style.display = 'none';
+            });
+
+            // 点击遮罩关闭弹出层
+            modalOverlay.addEventListener('click', function () {
+                if (filenameModal.style.display === 'block') {
+                    filenameModal.style.display = 'none';
+                    uploadButton.focus();
+                }
+                if (directoryModal.style.display === 'block') {
+                    directoryModal.style.display = 'none';
+                }
+                modalOverlay.style.display = 'none';
+            });
+
+            // 语音控制
+            let isListening = false;
+            let audioContext = null;
+
+            // 初始化 AudioContext
+            function initAudioContext() {
+                if (!audioContext) {
+                    audioContext = new (window.AudioContext || window.webkitAudioContext)();
+                }
+            }
+
+            // 获取播放权限
+            async function getPlayPermission() {
+                return new Promise((resolve) => {
+                    // const handleUserInteraction = () => {
+                    //     document.removeEventListener('click', handleUserInteraction);
+                    //     document.removeEventListener('keydown', handleUserInteraction);
+                    //     resolve();
+                    // };
+
+                    const dummyAudio = new Audio('data:audio/wav;base64,UklGRl9vT19XQVZFZm10IBAAAAABAAEAQB8AAEAfAAABAAgAZGF0YU');
+                    dummyAudio.play()
+                        .then(() => {
+                            dummyAudio.pause();
+                            dummyAudio.remove();
+                            resolve();
+                        })
+                        .catch((error) => {
+                            console.warn('Audio play permission denied:', error);
+                            // 静默处理权限错误,假设后续交互会自动触发权限
+                            resolve(); // 直接解析以继续流程
+                            // document.getElementById('loading-indicator').textContent = '请点击任意位置以启用音频...';
+                            // document.getElementById('loading-indicator').style.display = 'block';
+                            // document.addEventListener('click', handleUserInteraction);
+                            // document.addEventListener('keydown', handleUserInteraction);
+                        });
+                });
+            }
+
+            // 切换文本选择监听
+            document.getElementById('toggle-text-select').addEventListener('click', () => {
+                isListening = !isListening;
+                const button = document.getElementById('toggle-text-select');
+                button.textContent = isListening ? "关闭监听" : "开启监听";
+                button.classList.toggle('button-danger', isListening);
+            });
+
+            // 播放音频队列
+            async function playAudioSequentially(queue, signal) {
+                const audioElements = new Map();
+                const loadingIndicator = document.getElementById('loading-indicator');
+                try {
+                    while (true) {
+                        if (signal.aborted) break;
+
+                        const chunk = queue.shift();
+                        if (!chunk) {
+                            if (queue.includes(null)) break;
+                            await new Promise(resolve => setTimeout(resolve, 100));
+                            continue;
+                        }
+
+                        if (audioContext.state !== 'running') {
+                            await audioContext.resume();
+                        }
+
+                        const audio = new Audio(chunk.audio);
+                        audioElements.set(chunk.index, audio);
+
+                        try {
+                            loadingIndicator.textContent = `正在播放第 ${chunk.index + 1} 句`;
+                            loadingIndicator.style.display = 'block';
+                            await playAudio(audio);
+                        } catch (e) {
+                            console.warn(`播放失败: ${e.message}`);
+                        } finally {
+                            audioElements.delete(chunk.index);
+                        }
+                    }
+                    loadingIndicator.textContent = '播放完成';
+                    setTimeout(() => {
+                        loadingIndicator.style.display = 'none';
+                    }, 1000);
+                } catch (error) {
+                    console.error(error);
+                    loadingIndicator.textContent = `错误: ${error.message}`;
+                    loadingIndicator.style.display = 'block';
+                } finally {
+                    audioElements.forEach(audio => {
+                        audio.pause();
+                        audio.remove();
+                    });
+                }
+            }
+
+            // 播放单个音频
+            function playAudio(audio) {
+                return new Promise((resolve, reject) => {
+                    audio.play()
+                        .then(() => {
+                            audio.onended = resolve;
+                            audio.onerror = reject;
+                        })
+                        .catch(reject);
+                });
+            }
+
+            // 解析流数据
+            function parseStreamData(line) {
+                try {
+                    return JSON.parse(line);
+                } catch (e) {
+                    console.error('解析错误:', e, '原始数据:', line);
+                    return { error: e.message };
+                }
+            }
+
+            // 创建音频片段
+            function createAudioChunk(data) {
+                return {
+                    ...data,
+                    audio: data.audio.startsWith('data:') ?
+                        data.audio :
+                        `data:audio/wav;base64,${data.audio}`,
+                    timestamp: Date.now()
+                };
+            }
+
+            // 按顺序插入队列
+            function insertInOrder(queue, chunk) {
+                let index = 0;
+                while (index < queue.length && queue[index].index < chunk.index) {
+                    index++;
+                }
+                queue.splice(index, 0, chunk);
+            }
+
+            // 开始转换
+            async function startConversion(sentences) {
+                const loadingIndicator = document.getElementById('loading-indicator');
+                try {
+                    const fullText = sentences.map(s => s.text).join(' ');
+                    const response = await fetch('http://117.50.195.224:8028/generate', {
+                        method: 'POST',
+                        headers: { 'Content-Type': 'application/json' },
+                        body: JSON.stringify({ text: fullText, voice: "af_heart", speed: 1.0 })
+                    });
+
+                    if (!response.ok) throw new Error(`服务器错误: ${response.status}`);
+
+                    const reader = response.body.getReader();
+                    const decoder = new TextDecoder();
+                    let buffer = '';
+                    const audioQueue = [];
+                    let expectedIndex = 0;
+
+                    const playController = new AbortController();
+                    const playPromise = playAudioSequentially(audioQueue, playController.signal);
+
+                    while (true) {
+                        const { done, value } = await reader.read();
+                        if (done) break;
+
+                        buffer += decoder.decode(value, { stream: true });
+                        const lines = buffer.split('\n');
+                        buffer = lines.pop() || '';
+
+                        for (const line of lines) {
+                            if (!line.trim()) continue;
+                            const data = parseStreamData(line);
+                            if (data.audio) {
+                                const sentence = sentences[data.index];
+                                insertInOrder(audioQueue, createAudioChunk({
+                                    ...data,
+                                    xpath: sentence?.xpath
+                                }));
+                            }
+                        }
+                    }
+
+                    while (buffer.trim()) {
+                        const data = parseStreamData(buffer.trim());
+                        if (data.audio) {
+                            audioQueue.push(createAudioChunk(data));
+                        }
+                        buffer = '';
+                    }
+
+                    audioQueue.push(null);
+                    await playPromise;
+
+                } catch (error) {
+                    console.error(error);
+                    loadingIndicator.textContent = `错误: ${error.message}`;
+                    loadingIndicator.style.display = 'block';
+                }
+            }
+
+            // 监听文本选择
+            document.addEventListener('DOMContentLoaded', () => {
+                const loadingIndicator = document.getElementById('loading-indicator');
+
+                document.addEventListener('mouseup', async () => {
+                    if (!isListening) return;
+
+                    const selection = window.getSelection();
+                    const selectedText = selection.toString().trim();
+                    if (selectedText.length === 0) return;
+
+                    selection.removeAllRanges();
+
+                    try {
+                        initAudioContext();
+                        await getPlayPermission();
+                        loadingIndicator.textContent = '正在转换选中文本...';
+                        loadingIndicator.style.display = 'block';
+
+                        const sentences = selectedText.split(/[.!?]+/).filter(s => s.trim()).map((text, index) => ({
+                            text: text.trim(),
+                            xpath: null,
+                            index: index
+                        }));
+
+                        await startConversion(sentences);
+                    } catch (error) {
+                        console.error(error);
+                        loadingIndicator.textContent = `错误: ${error.message}`;
+                        loadingIndicator.style.display = 'block';
+                    }
+                });
+            });
+
+            // 阅读整页
+            document.getElementById('select-all-text').addEventListener('click', async function () {
+                const { pdfViewer } = PDFViewerApplication;
+                const currentPage = pdfViewer.currentPageNumber;
+                const loadingIndicator = document.getElementById('loading-indicator');
+
+                try {
+                    initAudioContext();
+                    await getPlayPermission();
+                    loadingIndicator.textContent = '正在提取页面文本...';
+                    loadingIndicator.style.display = 'block';
+
+                    PDFViewerApplication.pdfDocument.getPage(currentPage).then(function (page) {
+                        page.getTextContent().then(async function (textContent) {
+                            const fullText = textContent.items.map(item => item.str).join(' ');
+                            const sentences = fullText.split(/[.!?]+/).filter(s => s.trim()).map((text, index) => ({
+                                text: text.trim(),
+                                xpath: null,
+                                index: index
+                            }));
+
+                            await startConversion(sentences);
+                        });
+                    });
+                } catch (error) {
+                    console.error(error);
+                    loadingIndicator.textContent = `错误: ${error.message}`;
+                    loadingIndicator.style.display = 'block';
+                }
+            });
+        </script>
+</body>
+
+</html>

+ 1086 - 0
static/web/bak/viewer_copy.html

@@ -0,0 +1,1086 @@
+<!DOCTYPE html>
+<!--
+Copyright 2012 Mozilla Foundation
+
+Licensed under the Apache License, Version 2.0 (the "License");
+you may not use this file except in compliance with the License.
+You may obtain a copy of the License at
+
+    http://www.apache.org/licenses/LICENSE-2.0
+
+Unless required by applicable law or agreed to in writing, software
+distributed under the License is distributed on an "AS IS" BASIS,
+WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+See the License for the specific language governing permissions and
+limitations under the License.
+
+Adobe CMap resources are covered by their own copyright but the same license:
+
+    Copyright 1990-2015 Adobe Systems Incorporated.
+
+See https://github.com/adobe-type-tools/cmap-resources
+-->
+<html dir="ltr" mozdisallowselectionprint>
+
+<head>
+  <meta charset="utf-8">
+  <meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=1">
+  <meta name="google" content="notranslate">
+  <title>PDF.js viewer</title>
+
+  <!-- This snippet is used in production (included from viewer.html) -->
+  <link rel="resource" type="application/l10n" href="locale/locale.json">
+  <script src="../build/pdf.mjs" type="module"></script>
+
+  <link rel="stylesheet" href="viewer.css">
+
+  <script src="viewer.mjs" type="module"></script>
+
+  <style>
+    /* 模态窗口样式 */
+    .modal {
+        display: none; /* 默认隐藏 */
+        position: fixed;
+        z-index: 1002;
+        left: 50%;
+        top: 50%;
+        transform: translate(-50%, -50%);
+        width: 300px;
+        background-color: white;
+        border: 1px solid #ccc;
+        border-radius: 5px;
+        padding: 20px;
+        box-shadow: 0 5px 15px rgba(0,0,0,0.3);
+    }
+
+    /* 遮罩层样式 */
+    .modal-overlay {
+        display: none; /* 默认隐藏 */
+        position: fixed;
+        top: 0; 
+        left: 0; 
+        width: 100%; 
+        height: 100%; 
+        background-color: rgba(0, 0, 0, 0.5); 
+        z-index: 1001;
+    }
+
+    /* 按钮样式 */
+    .button {
+        padding: 10px 20px;
+        background-color: #007bff;
+        color: white;
+        border: none;
+        border-radius: 5px;
+        cursor: pointer;
+    }
+
+    .button-secondary {
+        background-color: #6c757d;
+    }
+
+    .button-success {
+        background-color: #28a745;
+    }
+
+    .button-danger {
+        background-color: #dc3545;
+    }
+</style>
+
+</head>
+
+<body tabindex="0">
+  <!-- 上传按钮定位在页面底部 -->
+  <button id="uploadButton" style="
+    position: fixed;
+    bottom: 20px;
+    right: 20px;
+    z-index: 1000;
+    padding: 10px 20px;
+    background-color: #007bff;
+    color: white;
+    border: none;
+    border-radius: 5px;
+    cursor: pointer;">
+    上传 PDF
+  </button>
+
+  
+  <!-- <input type="file" id="pdfInput" accept="application/pdf" style="display: none;" />
+  <div id="outerContainer"> -->
+
+  <!-- 上传按钮和文件名输入 -->
+  <!-- <div style="position: fixed; bottom: 20px; right: 20px; z-index: 1000;">
+    <input type="text" id="customFileName" placeholder="输入保存的文件名(不含扩展名)"
+      style="margin-bottom: 10px; padding: 5px; width: 250px;" />
+    <button id="uploadButton" style="
+      padding: 10px 20px;
+      background-color: #007bff;
+      color: white;
+      border: none;
+      border-radius: 5px;
+      cursor: pointer;">
+      上传 PDF
+    </button>
+  </div> -->
+
+  <!-- 隐藏的文件输入 -->
+  <input type="file" id="pdfInput" accept="application/pdf" style="display: none;" />
+
+      <!-- 模态窗口 -->
+      <div id="filenameModal" class="modal">
+        <h3>输入保存的文件名</h3>
+        <input type="text" id="customFileName" placeholder="不含扩展名" style="width: 100%; padding: 5px; margin-top: 10px;" />
+        <div style="margin-top: 20px; text-align: right;">
+            <button id="cancelUpload" class="button button-secondary">取消</button>
+            <button id="confirmUpload" class="button button-success" style="margin-left: 10px;">确认</button>
+        </div>
+    </div>
+
+    <!-- 页面遮罩 -->
+    <div id="modalOverlay" class="modal-overlay"></div>
+
+  <!-- 打开目录按钮 -->
+  <button id="openDirectoryButton" style="
+    position: fixed;
+    bottom: 20px;
+    right: 120px;  /* 与上传按钮的距离,可以根据需要调整 */
+    z-index: 1000;
+    padding: 10px 20px;
+    background-color: #28a745;
+    color: white;
+    border: none;
+    border-radius: 5px;
+    cursor: pointer;">
+    打开目录
+  </button>
+
+  <!-- 目录弹出层 -->
+  <div id="directoryModal" style="
+    display: none; 
+    position: fixed; 
+    top: 50%; 
+    left: 50%; 
+    transform: translate(-50%, -50%); 
+    background-color: white; 
+    border: 1px solid #ccc; 
+    border-radius: 5px; 
+    padding: 20px; 
+    z-index: 1001;
+    width: 80%;
+    max-height: 80%;
+    overflow-y: auto;">
+
+    <h2>已上传的 PDF 文档</h2>
+    <ul id="pdfList" style="list-style-type: none; padding: 0;"></ul>
+    <button id="closeDirectoryButton" style="
+        margin-top: 20px;
+        padding: 10px 20px;
+        background-color: #dc3545;
+        color: white;
+        border: none;
+        border-radius: 5px;
+        cursor: pointer;">
+      关闭
+    </button>
+  </div>
+
+  <!-- 页面遮罩 -->
+  <div id="modalOverlay" style="
+    display: none;
+    position: fixed;
+    top: 0; 
+    left: 0; 
+    width: 100%; 
+    height: 100%; 
+    background-color: rgba(0, 0, 0, 0.5); 
+    z-index: 1000;"></div>
+
+  <div id="sidebarContainer">
+    <div id="toolbarSidebar" class="toolbarHorizontalGroup">
+      <div id="toolbarSidebarLeft">
+        <div id="sidebarViewButtons" class="toolbarHorizontalGroup toggled" role="radiogroup">
+          <button id="viewThumbnail" class="toolbarButton toggled" type="button" title="Show Thumbnails" tabindex="0"
+            data-l10n-id="pdfjs-thumbs-button" role="radio" aria-checked="true" aria-controls="thumbnailView">
+            <span data-l10n-id="pdfjs-thumbs-button-label">Thumbnails</span>
+          </button>
+          <button id="viewOutline" class="toolbarButton" type="button"
+            title="Show Document Outline (double-click to expand/collapse all items)" tabindex="0"
+            data-l10n-id="pdfjs-document-outline-button" role="radio" aria-checked="false" aria-controls="outlineView">
+            <span data-l10n-id="pdfjs-document-outline-button-label">Document Outline</span>
+          </button>
+          <button id="viewAttachments" class="toolbarButton" type="button" title="Show Attachments" tabindex="0"
+            data-l10n-id="pdfjs-attachments-button" role="radio" aria-checked="false" aria-controls="attachmentsView">
+            <span data-l10n-id="pdfjs-attachments-button-label">Attachments</span>
+          </button>
+          <button id="viewLayers" class="toolbarButton" type="button"
+            title="Show Layers (double-click to reset all layers to the default state)" tabindex="0"
+            data-l10n-id="pdfjs-layers-button" role="radio" aria-checked="false" aria-controls="layersView">
+            <span data-l10n-id="pdfjs-layers-button-label">Layers</span>
+          </button>
+        </div>
+      </div>
+
+      <div id="toolbarSidebarRight">
+        <div id="outlineOptionsContainer" class="toolbarHorizontalGroup">
+          <div class="verticalToolbarSeparator"></div>
+
+          <button id="currentOutlineItem" class="toolbarButton" type="button" disabled="disabled"
+            title="Find Current Outline Item" tabindex="0" data-l10n-id="pdfjs-current-outline-item-button">
+            <span data-l10n-id="pdfjs-current-outline-item-button-label">Current Outline Item</span>
+          </button>
+        </div>
+      </div>
+    </div>
+    <div id="sidebarContent">
+      <div id="thumbnailView">
+      </div>
+      <div id="outlineView" class="hidden">
+      </div>
+      <div id="attachmentsView" class="hidden">
+      </div>
+      <div id="layersView" class="hidden">
+      </div>
+    </div>
+    <div id="sidebarResizer"></div>
+  </div> <!-- sidebarContainer -->
+
+  <div id="mainContainer">
+    <div class="toolbar">
+      <div id="toolbarContainer">
+        <div id="toolbarViewer" class="toolbarHorizontalGroup">
+          <div id="toolbarViewerLeft" class="toolbarHorizontalGroup">
+            <button id="sidebarToggleButton" class="toolbarButton" type="button" title="Toggle Sidebar" tabindex="0"
+              data-l10n-id="pdfjs-toggle-sidebar-button" aria-expanded="false" aria-haspopup="true"
+              aria-controls="sidebarContainer">
+              <span data-l10n-id="pdfjs-toggle-sidebar-button-label">Toggle Sidebar</span>
+            </button>
+            <div class="toolbarButtonSpacer"></div>
+            <div class="toolbarButtonWithContainer">
+              <button id="viewFindButton" class="toolbarButton" type="button" title="Find in Document" tabindex="0"
+                data-l10n-id="pdfjs-findbar-button" aria-expanded="false" aria-controls="findbar">
+                <span data-l10n-id="pdfjs-findbar-button-label">Find</span>
+              </button>
+              <div class="hidden doorHanger toolbarHorizontalGroup" id="findbar">
+                <div id="findInputContainer" class="toolbarHorizontalGroup">
+                  <span class="loadingInput end toolbarHorizontalGroup">
+                    <input id="findInput" class="toolbarField" title="Find" placeholder="Find in document…" tabindex="0"
+                      data-l10n-id="pdfjs-find-input" aria-invalid="false">
+                  </span>
+                  <div class="toolbarHorizontalGroup">
+                    <button id="findPreviousButton" class="toolbarButton" type="button"
+                      title="Find the previous occurrence of the phrase" tabindex="0"
+                      data-l10n-id="pdfjs-find-previous-button">
+                      <span data-l10n-id="pdfjs-find-previous-button-label">Previous</span>
+                    </button>
+                    <div class="splitToolbarButtonSeparator"></div>
+                    <button id="findNextButton" class="toolbarButton" type="button"
+                      title="Find the next occurrence of the phrase" tabindex="0" data-l10n-id="pdfjs-find-next-button">
+                      <span data-l10n-id="pdfjs-find-next-button-label">Next</span>
+                    </button>
+                  </div>
+                </div>
+
+                <div id="findbarOptionsOneContainer" class="toolbarHorizontalGroup">
+                  <div class="toggleButton toolbarLabel">
+                    <input type="checkbox" id="findHighlightAll" tabindex="0" />
+                    <label for="findHighlightAll" data-l10n-id="pdfjs-find-highlight-checkbox">Highlight All</label>
+                  </div>
+                  <div class="toggleButton toolbarLabel">
+                    <input type="checkbox" id="findMatchCase" tabindex="0" />
+                    <label for="findMatchCase" data-l10n-id="pdfjs-find-match-case-checkbox-label">Match Case</label>
+                  </div>
+                </div>
+                <div id="findbarOptionsTwoContainer" class="toolbarHorizontalGroup">
+                  <div class="toggleButton toolbarLabel">
+                    <input type="checkbox" id="findMatchDiacritics" tabindex="0" />
+                    <label for="findMatchDiacritics" data-l10n-id="pdfjs-find-match-diacritics-checkbox-label">Match
+                      Diacritics</label>
+                  </div>
+                  <div class="toggleButton toolbarLabel">
+                    <input type="checkbox" id="findEntireWord" tabindex="0" />
+                    <label for="findEntireWord" data-l10n-id="pdfjs-find-entire-word-checkbox-label">Whole
+                      Words</label>
+                  </div>
+                </div>
+
+                <div id="findbarMessageContainer" class="toolbarHorizontalGroup" aria-live="polite">
+                  <span id="findResultsCount" class="toolbarLabel"></span>
+                  <span id="findMsg" class="toolbarLabel"></span>
+                </div>
+              </div> <!-- findbar -->
+            </div>
+            <div class="toolbarHorizontalGroup hiddenSmallView">
+              <button class="toolbarButton" title="Previous Page" type="button" id="previous" tabindex="0"
+                data-l10n-id="pdfjs-previous-button">
+                <span data-l10n-id="pdfjs-previous-button-label">Previous</span>
+              </button>
+              <div class="splitToolbarButtonSeparator"></div>
+              <button class="toolbarButton" type="button" title="Next Page" id="next" tabindex="0"
+                data-l10n-id="pdfjs-next-button">
+                <span data-l10n-id="pdfjs-next-button-label">Next</span>
+              </button>
+            </div>
+            <div class="toolbarHorizontalGroup">
+              <span class="loadingInput start toolbarHorizontalGroup">
+                <input type="number" id="pageNumber" class="toolbarField" title="Page" value="1" min="1" tabindex="0"
+                  data-l10n-id="pdfjs-page-input" autocomplete="off">
+              </span>
+              <span id="numPages" class="toolbarLabel"></span>
+            </div>
+          </div>
+          <div id="toolbarViewerMiddle" class="toolbarHorizontalGroup">
+            <div class="toolbarHorizontalGroup">
+              <button id="zoomOutButton" class="toolbarButton" type="button" title="Zoom Out" tabindex="0"
+                data-l10n-id="pdfjs-zoom-out-button">
+                <span data-l10n-id="pdfjs-zoom-out-button-label">Zoom Out</span>
+              </button>
+              <div class="splitToolbarButtonSeparator"></div>
+              <button id="zoomInButton" class="toolbarButton" type="button" title="Zoom In" tabindex="0"
+                data-l10n-id="pdfjs-zoom-in-button">
+                <span data-l10n-id="pdfjs-zoom-in-button-label">Zoom In</span>
+              </button>
+            </div>
+            <span id="scaleSelectContainer" class="dropdownToolbarButton">
+              <select id="scaleSelect" title="Zoom" tabindex="0" data-l10n-id="pdfjs-zoom-select">
+                <option id="pageAutoOption" title="" value="auto" selected="selected"
+                  data-l10n-id="pdfjs-page-scale-auto">Automatic Zoom</option>
+                <option id="pageActualOption" title="" value="page-actual" data-l10n-id="pdfjs-page-scale-actual">
+                  Actual Size</option>
+                <option id="pageFitOption" title="" value="page-fit" data-l10n-id="pdfjs-page-scale-fit">Page Fit
+                </option>
+                <option id="pageWidthOption" title="" value="page-width" data-l10n-id="pdfjs-page-scale-width">Page
+                  Width</option>
+                <option id="customScaleOption" title="" value="custom" disabled="disabled" hidden="true"
+                  data-l10n-id="pdfjs-page-scale-percent" data-l10n-args='{ "scale": 0 }'>0%</option>
+                <option title="" value="0.5" data-l10n-id="pdfjs-page-scale-percent" data-l10n-args='{ "scale": 50 }'>
+                  50%</option>
+                <option title="" value="0.75" data-l10n-id="pdfjs-page-scale-percent" data-l10n-args='{ "scale": 75 }'>
+                  75%</option>
+                <option title="" value="1" data-l10n-id="pdfjs-page-scale-percent" data-l10n-args='{ "scale": 100 }'>
+                  100%</option>
+                <option title="" value="1.25" data-l10n-id="pdfjs-page-scale-percent" data-l10n-args='{ "scale": 125 }'>
+                  125%</option>
+                <option title="" value="1.5" data-l10n-id="pdfjs-page-scale-percent" data-l10n-args='{ "scale": 150 }'>
+                  150%</option>
+                <option title="" value="2" data-l10n-id="pdfjs-page-scale-percent" data-l10n-args='{ "scale": 200 }'>
+                  200%</option>
+                <option title="" value="3" data-l10n-id="pdfjs-page-scale-percent" data-l10n-args='{ "scale": 300 }'>
+                  300%</option>
+                <option title="" value="4" data-l10n-id="pdfjs-page-scale-percent" data-l10n-args='{ "scale": 400 }'>
+                  400%</option>
+              </select>
+            </span>
+          </div>
+          <div id="toolbarViewerRight" class="toolbarHorizontalGroup">
+            <div id="editorModeButtons" class="toolbarHorizontalGroup" role="radiogroup">
+              <div id="editorHighlight" class="toolbarButtonWithContainer">
+                <button id="editorHighlightButton" class="toolbarButton" type="button" disabled="disabled"
+                  title="Highlight" role="radio" aria-expanded="false" aria-haspopup="true"
+                  aria-controls="editorHighlightParamsToolbar" tabindex="0"
+                  data-l10n-id="pdfjs-editor-highlight-button">
+                  <span data-l10n-id="pdfjs-editor-highlight-button-label">Highlight</span>
+                </button>
+                <div class="editorParamsToolbar hidden doorHangerRight" id="editorHighlightParamsToolbar">
+                  <div id="highlightParamsToolbarContainer" class="editorParamsToolbarContainer">
+                    <div id="editorHighlightColorPicker" class="colorPicker">
+                      <span id="highlightColorPickerLabel" class="editorParamsLabel"
+                        data-l10n-id="pdfjs-editor-highlight-colorpicker-label">Highlight color</span>
+                    </div>
+                    <div id="editorHighlightThickness">
+                      <label for="editorFreeHighlightThickness" class="editorParamsLabel"
+                        data-l10n-id="pdfjs-editor-free-highlight-thickness-input">Thickness</label>
+                      <div class="thicknessPicker">
+                        <input type="range" id="editorFreeHighlightThickness" class="editorParamsSlider"
+                          data-l10n-id="pdfjs-editor-free-highlight-thickness-title" value="12" min="8" max="24"
+                          step="1" tabindex="0">
+                      </div>
+                    </div>
+                    <div id="editorHighlightVisibility">
+                      <div class="divider"></div>
+                      <div class="toggler">
+                        <label for="editorHighlightShowAll" class="editorParamsLabel"
+                          data-l10n-id="pdfjs-editor-highlight-show-all-button-label">Show all</label>
+                        <button id="editorHighlightShowAll" class="toggle-button" type="button"
+                          data-l10n-id="pdfjs-editor-highlight-show-all-button" aria-pressed="true"
+                          tabindex="0"></button>
+                      </div>
+                    </div>
+                  </div>
+                </div>
+              </div>
+              <div id="editorFreeText" class="toolbarButtonWithContainer">
+                <button id="editorFreeTextButton" class="toolbarButton" type="button" disabled="disabled" title="Text"
+                  role="radio" aria-expanded="false" aria-haspopup="true" aria-controls="editorFreeTextParamsToolbar"
+                  tabindex="0" data-l10n-id="pdfjs-editor-free-text-button">
+                  <span data-l10n-id="pdfjs-editor-free-text-button-label">Text</span>
+                </button>
+                <div class="editorParamsToolbar hidden doorHangerRight" id="editorFreeTextParamsToolbar">
+                  <div class="editorParamsToolbarContainer">
+                    <div class="editorParamsSetter">
+                      <label for="editorFreeTextColor" class="editorParamsLabel"
+                        data-l10n-id="pdfjs-editor-free-text-color-input">Color</label>
+                      <input type="color" id="editorFreeTextColor" class="editorParamsColor" tabindex="0">
+                    </div>
+                    <div class="editorParamsSetter">
+                      <label for="editorFreeTextFontSize" class="editorParamsLabel"
+                        data-l10n-id="pdfjs-editor-free-text-size-input">Size</label>
+                      <input type="range" id="editorFreeTextFontSize" class="editorParamsSlider" value="10" min="5"
+                        max="100" step="1" tabindex="0">
+                    </div>
+                  </div>
+                </div>
+              </div>
+              <div id="editorInk" class="toolbarButtonWithContainer">
+                <button id="editorInkButton" class="toolbarButton" type="button" disabled="disabled" title="Draw"
+                  role="radio" aria-expanded="false" aria-haspopup="true" aria-controls="editorInkParamsToolbar"
+                  tabindex="0" data-l10n-id="pdfjs-editor-ink-button">
+                  <span data-l10n-id="pdfjs-editor-ink-button-label">Draw</span>
+                </button>
+                <div class="editorParamsToolbar hidden doorHangerRight" id="editorInkParamsToolbar">
+                  <div class="editorParamsToolbarContainer">
+                    <div class="editorParamsSetter">
+                      <label for="editorInkColor" class="editorParamsLabel"
+                        data-l10n-id="pdfjs-editor-ink-color-input">Color</label>
+                      <input type="color" id="editorInkColor" class="editorParamsColor" tabindex="0">
+                    </div>
+                    <div class="editorParamsSetter">
+                      <label for="editorInkThickness" class="editorParamsLabel"
+                        data-l10n-id="pdfjs-editor-ink-thickness-input">Thickness</label>
+                      <input type="range" id="editorInkThickness" class="editorParamsSlider" value="1" min="1" max="20"
+                        step="1" tabindex="0">
+                    </div>
+                    <div class="editorParamsSetter">
+                      <label for="editorInkOpacity" class="editorParamsLabel"
+                        data-l10n-id="pdfjs-editor-ink-opacity-input">Opacity</label>
+                      <input type="range" id="editorInkOpacity" class="editorParamsSlider" value="100" min="1" max="100"
+                        step="1" tabindex="0">
+                    </div>
+                  </div>
+                </div>
+              </div>
+              <div id="editorStamp" class="toolbarButtonWithContainer">
+                <button id="editorStampButton" class="toolbarButton" type="button" disabled="disabled"
+                  title="Add or edit images" role="radio" aria-expanded="false" aria-haspopup="true"
+                  aria-controls="editorStampParamsToolbar" tabindex="0" data-l10n-id="pdfjs-editor-stamp-button">
+                  <span data-l10n-id="pdfjs-editor-stamp-button-label">Add or edit images</span>
+                </button>
+                <div class="editorParamsToolbar hidden doorHangerRight menu" id="editorStampParamsToolbar">
+                  <div class="menuContainer">
+                    <button id="editorStampAddImage" class="toolbarButton labeled" type="button" title="Add image"
+                      tabindex="0" data-l10n-id="pdfjs-editor-stamp-add-image-button">
+                      <span class="editorParamsLabel" data-l10n-id="pdfjs-editor-stamp-add-image-button-label">Add
+                        image</span>
+                    </button>
+                  </div>
+                </div>
+              </div>
+            </div>
+
+            <div id="editorModeSeparator" class="verticalToolbarSeparator"></div>
+
+            <div class="toolbarHorizontalGroup hiddenMediumView">
+              <button id="printButton" class="toolbarButton" type="button" title="Print" tabindex="0"
+                data-l10n-id="pdfjs-print-button">
+                <span data-l10n-id="pdfjs-print-button-label">Print</span>
+              </button>
+
+              <button id="downloadButton" class="toolbarButton" type="button" title="Save" tabindex="0"
+                data-l10n-id="pdfjs-save-button">
+                <span data-l10n-id="pdfjs-save-button-label">Save</span>
+              </button>
+            </div>
+
+            <div class="verticalToolbarSeparator hiddenMediumView"></div>
+
+            <div id="secondaryToolbarToggle" class="toolbarButtonWithContainer">
+              <button id="secondaryToolbarToggleButton" class="toolbarButton" type="button" title="Tools" tabindex="0"
+                data-l10n-id="pdfjs-tools-button" aria-expanded="false" aria-haspopup="true"
+                aria-controls="secondaryToolbar">
+                <span data-l10n-id="pdfjs-tools-button-label">Tools</span>
+              </button>
+              <div id="secondaryToolbar" class="hidden doorHangerRight menu">
+                <div id="secondaryToolbarButtonContainer" class="menuContainer">
+                  <button id="secondaryOpenFile" class="toolbarButton labeled" type="button" title="Open File"
+                    tabindex="0" data-l10n-id="pdfjs-open-file-button">
+                    <span data-l10n-id="pdfjs-open-file-button-label">Open</span>
+                  </button>
+
+                  <div class="visibleMediumView">
+                    <button id="secondaryPrint" class="toolbarButton labeled" type="button" title="Print" tabindex="0"
+                      data-l10n-id="pdfjs-print-button">
+                      <span data-l10n-id="pdfjs-print-button-label">Print</span>
+                    </button>
+
+                    <button id="secondaryDownload" class="toolbarButton labeled" type="button" title="Save" tabindex="0"
+                      data-l10n-id="pdfjs-save-button">
+                      <span data-l10n-id="pdfjs-save-button-label">Save</span>
+                    </button>
+
+                  </div>
+
+                  <div class="horizontalToolbarSeparator"></div>
+
+                  <button id="presentationMode" class="toolbarButton labeled" type="button"
+                    title="Switch to Presentation Mode" tabindex="0" data-l10n-id="pdfjs-presentation-mode-button">
+                    <span data-l10n-id="pdfjs-presentation-mode-button-label">Presentation Mode</span>
+                  </button>
+
+                  <a href="#" id="viewBookmark" class="toolbarButton labeled"
+                    title="Current Page (View URL from Current Page)" tabindex="0" data-l10n-id="pdfjs-bookmark-button">
+                    <span data-l10n-id="pdfjs-bookmark-button-label">Current Page</span>
+                  </a>
+
+                  <div id="viewBookmarkSeparator" class="horizontalToolbarSeparator"></div>
+
+                  <button id="firstPage" class="toolbarButton labeled" type="button" title="Go to First Page"
+                    tabindex="0" data-l10n-id="pdfjs-first-page-button">
+                    <span data-l10n-id="pdfjs-first-page-button-label">Go to First Page</span>
+                  </button>
+                  <button id="lastPage" class="toolbarButton labeled" type="button" title="Go to Last Page" tabindex="0"
+                    data-l10n-id="pdfjs-last-page-button">
+                    <span data-l10n-id="pdfjs-last-page-button-label">Go to Last Page</span>
+                  </button>
+
+                  <div class="horizontalToolbarSeparator"></div>
+
+                  <button id="pageRotateCw" class="toolbarButton labeled" type="button" title="Rotate Clockwise"
+                    tabindex="0" data-l10n-id="pdfjs-page-rotate-cw-button">
+                    <span data-l10n-id="pdfjs-page-rotate-cw-button-label">Rotate Clockwise</span>
+                  </button>
+                  <button id="pageRotateCcw" class="toolbarButton labeled" type="button" title="Rotate Counterclockwise"
+                    tabindex="0" data-l10n-id="pdfjs-page-rotate-ccw-button">
+                    <span data-l10n-id="pdfjs-page-rotate-ccw-button-label">Rotate Counterclockwise</span>
+                  </button>
+
+                  <div class="horizontalToolbarSeparator"></div>
+
+                  <div id="cursorToolButtons" role="radiogroup">
+                    <button id="cursorSelectTool" class="toolbarButton labeled toggled" type="button"
+                      title="Enable Text Selection Tool" tabindex="0"
+                      data-l10n-id="pdfjs-cursor-text-select-tool-button" role="radio" aria-checked="true">
+                      <span data-l10n-id="pdfjs-cursor-text-select-tool-button-label">Text Selection Tool</span>
+                    </button>
+                    <button id="cursorHandTool" class="toolbarButton labeled" type="button" title="Enable Hand Tool"
+                      tabindex="0" data-l10n-id="pdfjs-cursor-hand-tool-button" role="radio" aria-checked="false">
+                      <span data-l10n-id="pdfjs-cursor-hand-tool-button-label">Hand Tool</span>
+                    </button>
+                  </div>
+
+                  <div class="horizontalToolbarSeparator"></div>
+
+                  <div id="scrollModeButtons" role="radiogroup">
+                    <button id="scrollPage" class="toolbarButton labeled" type="button" title="Use Page Scrolling"
+                      tabindex="0" data-l10n-id="pdfjs-scroll-page-button" role="radio" aria-checked="false">
+                      <span data-l10n-id="pdfjs-scroll-page-button-label">Page Scrolling</span>
+                    </button>
+                    <button id="scrollVertical" class="toolbarButton labeled toggled" type="button"
+                      title="Use Vertical Scrolling" tabindex="0" data-l10n-id="pdfjs-scroll-vertical-button"
+                      role="radio" aria-checked="true">
+                      <span data-l10n-id="pdfjs-scroll-vertical-button-label">Vertical Scrolling</span>
+                    </button>
+                    <button id="scrollHorizontal" class="toolbarButton labeled" type="button"
+                      title="Use Horizontal Scrolling" tabindex="0" data-l10n-id="pdfjs-scroll-horizontal-button"
+                      role="radio" aria-checked="false">
+                      <span data-l10n-id="pdfjs-scroll-horizontal-button-label">Horizontal Scrolling</span>
+                    </button>
+                    <button id="scrollWrapped" class="toolbarButton labeled" type="button" title="Use Wrapped Scrolling"
+                      tabindex="0" data-l10n-id="pdfjs-scroll-wrapped-button" role="radio" aria-checked="false">
+                      <span data-l10n-id="pdfjs-scroll-wrapped-button-label">Wrapped Scrolling</span>
+                    </button>
+                  </div>
+
+                  <div class="horizontalToolbarSeparator"></div>
+
+                  <div id="spreadModeButtons" role="radiogroup">
+                    <button id="spreadNone" class="toolbarButton labeled toggled" type="button"
+                      title="Do not join page spreads" tabindex="0" data-l10n-id="pdfjs-spread-none-button" role="radio"
+                      aria-checked="true">
+                      <span data-l10n-id="pdfjs-spread-none-button-label">No Spreads</span>
+                    </button>
+                    <button id="spreadOdd" class="toolbarButton labeled" type="button"
+                      title="Join page spreads starting with odd-numbered pages" tabindex="0"
+                      data-l10n-id="pdfjs-spread-odd-button" role="radio" aria-checked="false">
+                      <span data-l10n-id="pdfjs-spread-odd-button-label">Odd Spreads</span>
+                    </button>
+                    <button id="spreadEven" class="toolbarButton labeled" type="button"
+                      title="Join page spreads starting with even-numbered pages" tabindex="0"
+                      data-l10n-id="pdfjs-spread-even-button" role="radio" aria-checked="false">
+                      <span data-l10n-id="pdfjs-spread-even-button-label">Even Spreads</span>
+                    </button>
+                  </div>
+
+                  <div id="imageAltTextSettingsSeparator" class="horizontalToolbarSeparator hidden"></div>
+                  <button id="imageAltTextSettings" type="button" class="toolbarButton labeled hidden"
+                    title="Image alt text settings" tabindex="0" data-l10n-id="pdfjs-image-alt-text-settings-button"
+                    aria-controls="altTextSettingsDialog">
+                    <span data-l10n-id="pdfjs-image-alt-text-settings-button-label">Image alt text settings</span>
+                  </button>
+
+                  <div class="horizontalToolbarSeparator"></div>
+
+                  <button id="documentProperties" class="toolbarButton labeled" type="button"
+                    title="Document Properties…" tabindex="0" data-l10n-id="pdfjs-document-properties-button"
+                    aria-controls="documentPropertiesDialog">
+                    <span data-l10n-id="pdfjs-document-properties-button-label">Document Properties…</span>
+                  </button>
+                </div>
+              </div> <!-- secondaryToolbar -->
+            </div>
+          </div>
+        </div>
+        <div id="loadingBar">
+          <div class="progress">
+            <div class="glimmer">
+            </div>
+          </div>
+        </div>
+      </div>
+    </div>
+
+    <div id="viewerContainer" tabindex="0">
+      <div id="viewer" class="pdfViewer"></div>
+    </div>
+  </div> <!-- mainContainer -->
+
+  <div id="dialogContainer">
+    <dialog id="passwordDialog">
+      <div class="row">
+        <label for="password" id="passwordText" data-l10n-id="pdfjs-password-label">Enter the password to open this
+          PDF file:</label>
+      </div>
+      <div class="row">
+        <input type="password" id="password" class="toolbarField">
+      </div>
+      <div class="buttonRow">
+        <button id="passwordCancel" class="dialogButton" type="button"><span
+            data-l10n-id="pdfjs-password-cancel-button">Cancel</span></button>
+        <button id="passwordSubmit" class="dialogButton" type="button"><span
+            data-l10n-id="pdfjs-password-ok-button">OK</span></button>
+      </div>
+    </dialog>
+    <dialog id="documentPropertiesDialog">
+      <div class="row">
+        <span id="fileNameLabel" data-l10n-id="pdfjs-document-properties-file-name">File name:</span>
+        <p id="fileNameField" aria-labelledby="fileNameLabel">-</p>
+      </div>
+      <div class="row">
+        <span id="fileSizeLabel" data-l10n-id="pdfjs-document-properties-file-size">File size:</span>
+        <p id="fileSizeField" aria-labelledby="fileSizeLabel">-</p>
+      </div>
+      <div class="separator"></div>
+      <div class="row">
+        <span id="titleLabel" data-l10n-id="pdfjs-document-properties-title">Title:</span>
+        <p id="titleField" aria-labelledby="titleLabel">-</p>
+      </div>
+      <div class="row">
+        <span id="authorLabel" data-l10n-id="pdfjs-document-properties-author">Author:</span>
+        <p id="authorField" aria-labelledby="authorLabel">-</p>
+      </div>
+      <div class="row">
+        <span id="subjectLabel" data-l10n-id="pdfjs-document-properties-subject">Subject:</span>
+        <p id="subjectField" aria-labelledby="subjectLabel">-</p>
+      </div>
+      <div class="row">
+        <span id="keywordsLabel" data-l10n-id="pdfjs-document-properties-keywords">Keywords:</span>
+        <p id="keywordsField" aria-labelledby="keywordsLabel">-</p>
+      </div>
+      <div class="row">
+        <span id="creationDateLabel" data-l10n-id="pdfjs-document-properties-creation-date">Creation Date:</span>
+        <p id="creationDateField" aria-labelledby="creationDateLabel">-</p>
+      </div>
+      <div class="row">
+        <span id="modificationDateLabel" data-l10n-id="pdfjs-document-properties-modification-date">Modification
+          Date:</span>
+        <p id="modificationDateField" aria-labelledby="modificationDateLabel">-</p>
+      </div>
+      <div class="row">
+        <span id="creatorLabel" data-l10n-id="pdfjs-document-properties-creator">Creator:</span>
+        <p id="creatorField" aria-labelledby="creatorLabel">-</p>
+      </div>
+      <div class="separator"></div>
+      <div class="row">
+        <span id="producerLabel" data-l10n-id="pdfjs-document-properties-producer">PDF Producer:</span>
+        <p id="producerField" aria-labelledby="producerLabel">-</p>
+      </div>
+      <div class="row">
+        <span id="versionLabel" data-l10n-id="pdfjs-document-properties-version">PDF Version:</span>
+        <p id="versionField" aria-labelledby="versionLabel">-</p>
+      </div>
+      <div class="row">
+        <span id="pageCountLabel" data-l10n-id="pdfjs-document-properties-page-count">Page Count:</span>
+        <p id="pageCountField" aria-labelledby="pageCountLabel">-</p>
+      </div>
+      <div class="row">
+        <span id="pageSizeLabel" data-l10n-id="pdfjs-document-properties-page-size">Page Size:</span>
+        <p id="pageSizeField" aria-labelledby="pageSizeLabel">-</p>
+      </div>
+      <div class="separator"></div>
+      <div class="row">
+        <span id="linearizedLabel" data-l10n-id="pdfjs-document-properties-linearized">Fast Web View:</span>
+        <p id="linearizedField" aria-labelledby="linearizedLabel">-</p>
+      </div>
+      <div class="buttonRow">
+        <button id="documentPropertiesClose" class="dialogButton" type="button"><span
+            data-l10n-id="pdfjs-document-properties-close-button">Close</span></button>
+      </div>
+    </dialog>
+    <dialog class="dialog altText" id="altTextDialog" aria-labelledby="dialogLabel"
+      aria-describedby="dialogDescription">
+      <div id="altTextContainer" class="mainContainer">
+        <div id="overallDescription">
+          <span id="dialogLabel" data-l10n-id="pdfjs-editor-alt-text-dialog-label" class="title">Choose an
+            option</span>
+          <span id="dialogDescription" data-l10n-id="pdfjs-editor-alt-text-dialog-description">
+            Alt text (alternative text) helps when people can’t see the image or when it doesn’t load.
+          </span>
+        </div>
+        <div id="addDescription">
+          <div class="radio">
+            <div class="radioButton">
+              <input type="radio" id="descriptionButton" name="altTextOption" tabindex="0"
+                aria-describedby="descriptionAreaLabel" checked>
+              <label for="descriptionButton" data-l10n-id="pdfjs-editor-alt-text-add-description-label">Add a
+                description</label>
+            </div>
+            <div class="radioLabel">
+              <span id="descriptionAreaLabel" data-l10n-id="pdfjs-editor-alt-text-add-description-description">
+                Aim for 1-2 sentences that describe the subject, setting, or actions.
+              </span>
+            </div>
+          </div>
+          <div class="descriptionArea">
+            <textarea id="descriptionTextarea"
+              placeholder="For example, “A young man sits down at a table to eat a meal”"
+              aria-labelledby="descriptionAreaLabel" data-l10n-id="pdfjs-editor-alt-text-textarea"
+              tabindex="0"></textarea>
+          </div>
+        </div>
+        <div id="markAsDecorative">
+          <div class="radio">
+            <div class="radioButton">
+              <input type="radio" id="decorativeButton" name="altTextOption" aria-describedby="decorativeLabel">
+              <label for="decorativeButton" data-l10n-id="pdfjs-editor-alt-text-mark-decorative-label">Mark as
+                decorative</label>
+            </div>
+            <div class="radioLabel">
+              <span id="decorativeLabel" data-l10n-id="pdfjs-editor-alt-text-mark-decorative-description">
+                This is used for ornamental images, like borders or watermarks.
+              </span>
+            </div>
+          </div>
+        </div>
+        <div id="buttons">
+          <button id="altTextCancel" class="secondaryButton" type="button" tabindex="0"><span
+              data-l10n-id="pdfjs-editor-alt-text-cancel-button">Cancel</span></button>
+          <button id="altTextSave" class="primaryButton" type="button" tabindex="0"><span
+              data-l10n-id="pdfjs-editor-alt-text-save-button">Save</span></button>
+        </div>
+      </div>
+    </dialog>
+    <dialog class="dialog newAltText" id="newAltTextDialog" aria-labelledby="newAltTextTitle"
+      aria-describedby="newAltTextDescription" tabindex="0">
+      <div id="newAltTextContainer" class="mainContainer">
+        <div class="title">
+          <span id="newAltTextTitle" data-l10n-id="pdfjs-editor-new-alt-text-dialog-edit-label" role="sectionhead"
+            tabindex="0">Edit alt text (image description)</span>
+        </div>
+        <div id="mainContent">
+          <div id="descriptionAndSettings">
+            <div id="descriptionInstruction">
+              <div id="newAltTextDescriptionContainer">
+                <div class="altTextSpinner" role="status" aria-live="polite"></div>
+                <textarea id="newAltTextDescriptionTextarea" placeholder="Write your description here…"
+                  aria-labelledby="descriptionAreaLabel" data-l10n-id="pdfjs-editor-new-alt-text-textarea"
+                  tabindex="0"></textarea>
+              </div>
+              <span id="newAltTextDescription" role="note" data-l10n-id="pdfjs-editor-new-alt-text-description">Short
+                description for people who can’t see the image or when the image doesn’t load.</span>
+              <div id="newAltTextDisclaimer" role="note">
+                <div><span data-l10n-id="pdfjs-editor-new-alt-text-disclaimer1">This alt text was created
+                    automatically and may be inaccurate.</span> <a
+                    href="https://support.mozilla.org/en-US/kb/pdf-alt-text" target="_blank" rel="noopener noreferrer"
+                    id="newAltTextLearnMore" data-l10n-id="pdfjs-editor-new-alt-text-disclaimer-learn-more-url"
+                    tabindex="0">Learn more</a></div>
+              </div>
+            </div>
+            <div id="newAltTextCreateAutomatically" class="toggler">
+              <button id="newAltTextCreateAutomaticallyButton" class="toggle-button" type="button" aria-pressed="true"
+                tabindex="0"></button>
+              <label for="newAltTextCreateAutomaticallyButton" class="togglerLabel"
+                data-l10n-id="pdfjs-editor-new-alt-text-create-automatically-button-label">Create alt text
+                automatically</label>
+            </div>
+            <div id="newAltTextDownloadModel" class="hidden">
+              <span id="newAltTextDownloadModelDescription"
+                data-l10n-id="pdfjs-editor-new-alt-text-ai-model-downloading-progress" aria-valuemin="0"
+                data-l10n-args='{ "totalSize": 0, "downloadedSize": 0 }'>Downloading alt text AI model (0 of 0
+                MB)</span>
+            </div>
+          </div>
+          <div id="newAltTextImagePreview"></div>
+        </div>
+        <div id="newAltTextError" class="messageBar">
+          <div>
+            <div>
+              <span class="title" data-l10n-id="pdfjs-editor-new-alt-text-error-title">Couldn’t create alt text
+                automatically</span>
+              <span class="description" data-l10n-id="pdfjs-editor-new-alt-text-error-description">Please write your
+                own alt text or try again later.</span>
+            </div>
+            <button id="newAltTextCloseButton" class="closeButton" type="button" tabindex="0" title="Close"><span
+                data-l10n-id="pdfjs-editor-new-alt-text-error-close-button">Close</span></button>
+          </div>
+        </div>
+        <div id="newAltTextButtons" class="dialogButtonsGroup">
+          <button id="newAltTextCancel" type="button" class="secondaryButton hidden" tabindex="0"><span
+              data-l10n-id="pdfjs-editor-alt-text-cancel-button">Cancel</span></button>
+          <button id="newAltTextNotNow" type="button" class="secondaryButton" tabindex="0"><span
+              data-l10n-id="pdfjs-editor-new-alt-text-not-now-button">Not now</span></button>
+          <button id="newAltTextSave" type="button" class="primaryButton" tabindex="0"><span
+              data-l10n-id="pdfjs-editor-alt-text-save-button">Save</span></button>
+        </div>
+      </div>
+    </dialog>
+
+    <dialog class="dialog" id="altTextSettingsDialog" aria-labelledby="altTextSettingsTitle">
+      <div id="altTextSettingsContainer" class="mainContainer">
+        <div class="title">
+          <span id="altTextSettingsTitle" data-l10n-id="pdfjs-editor-alt-text-settings-dialog-label" role="sectionhead"
+            tabindex="0" class="title">Image alt text settings</span>
+        </div>
+        <div id="automaticAltText">
+          <span data-l10n-id="pdfjs-editor-alt-text-settings-automatic-title">Automatic alt text</span>
+          <div id="automaticSettings">
+            <div id="createModelSetting">
+              <div class="toggler">
+                <button id="createModelButton" type="button" class="toggle-button" aria-pressed="true"
+                  tabindex="0"></button>
+                <label for="createModelButton" class="togglerLabel"
+                  data-l10n-id="pdfjs-editor-alt-text-settings-create-model-button-label">Create alt text
+                  automatically</label>
+              </div>
+              <div id="createModelDescription" class="description">
+                <span data-l10n-id="pdfjs-editor-alt-text-settings-create-model-description">Suggests descriptions to
+                  help people who can’t see the image or when the image doesn’t load.</span> <a
+                  href="https://support.mozilla.org/en-US/kb/pdf-alt-text" target="_blank" rel="noopener noreferrer"
+                  id="altTextSettingsLearnMore" data-l10n-id="pdfjs-editor-new-alt-text-disclaimer-learn-more-url"
+                  tabindex="0">Learn more</a>
+              </div>
+            </div>
+            <div id="aiModelSettings">
+              <div>
+                <span data-l10n-id="pdfjs-editor-alt-text-settings-download-model-label"
+                  data-l10n-args='{ "totalSize": 180 }'>Alt text AI model (180MB)</span>
+                <div id="aiModelDescription" class="description">
+                  <span data-l10n-id="pdfjs-editor-alt-text-settings-ai-model-description">Runs locally on your device
+                    so your data stays private. Required for automatic alt text.</span>
+                </div>
+              </div>
+              <button id="deleteModelButton" type="button" class="secondaryButton" tabindex="0"><span
+                  data-l10n-id="pdfjs-editor-alt-text-settings-delete-model-button">Delete</span></button>
+              <button id="downloadModelButton" type="button" class="secondaryButton" tabindex="0"><span
+                  data-l10n-id="pdfjs-editor-alt-text-settings-download-model-button">Download</span></button>
+            </div>
+          </div>
+        </div>
+        <div class="dialogSeparator"></div>
+        <div id="altTextEditor">
+          <span data-l10n-id="pdfjs-editor-alt-text-settings-editor-title">Alt text editor</span>
+          <div id="showAltTextEditor">
+            <div class="toggler">
+              <button id="showAltTextDialogButton" type="button" class="toggle-button" aria-pressed="true"
+                tabindex="0"></button>
+              <label for="showAltTextDialogButton" class="togglerLabel"
+                data-l10n-id="pdfjs-editor-alt-text-settings-show-dialog-button-label">Show alt text editor right away
+                when adding an image</label>
+            </div>
+            <div id="showAltTextDialogDescription" class="description">
+              <span data-l10n-id="pdfjs-editor-alt-text-settings-show-dialog-description">Helps you make sure all your
+                images have alt text.</span>
+            </div>
+          </div>
+        </div>
+        <div id="buttons" class="dialogButtonsGroup">
+          <button id="altTextSettingsCloseButton" type="button" class="primaryButton" tabindex="0"><span
+              data-l10n-id="pdfjs-editor-alt-text-settings-close-button">Close</span></button>
+        </div>
+      </div>
+    </dialog>
+    <dialog id="printServiceDialog" style="min-width: 200px;">
+      <div class="row">
+        <span data-l10n-id="pdfjs-print-progress-message">Preparing document for printing…</span>
+      </div>
+      <div class="row">
+        <progress value="0" max="100"></progress>
+        <span data-l10n-id="pdfjs-print-progress-percent" data-l10n-args='{ "progress": 0 }'
+          class="relative-progress">0%</span>
+      </div>
+      <div class="buttonRow">
+        <button id="printCancel" class="dialogButton" type="button"><span
+            data-l10n-id="pdfjs-print-progress-close-button">Cancel</span></button>
+      </div>
+    </dialog>
+  </div> <!-- dialogContainer -->
+
+  </div> <!-- outerContainer -->
+  <div id="printContainer"></div>
+  <!-- <script>
+      document.getElementById('uploadButton').addEventListener('click', function() {
+          // 触发隐藏的文件输入
+          document.getElementById('pdfInput').click();
+      });
+      
+      document.getElementById('pdfInput').addEventListener('change', function(event) {
+          const file = event.target.files[0];
+          if (file && file.type === 'application/pdf') {
+              const formData = new FormData();
+              formData.append('file', file);
+      
+              // 调用 FastAPI 上传接口
+              fetch('http://106.14.113.12:8005/upload-pdf', {  // 根据实际部署情况调整URL
+                  method: 'POST',
+                  body: formData
+              })
+              .then(response => response.json())
+              .then(data => {
+                  if (data.success) {
+                      const uploadedUrl = data.url;
+                      // 加载上传后的 PDF
+                      PDFViewerApplication.open(uploadedUrl);
+                  } else {
+                      alert('上传失败: ' + data.error);
+                  }
+              })
+              .catch(error => {
+                  console.error('Error:', error);
+                  alert('上传过程中发生错误');
+              });
+          } else {
+              alert('请选择一个 PDF 文件');
+          }
+      });
+      </script> -->
+  <script>
+    document.getElementById('uploadButton').addEventListener('click', function () {
+      const customFileName = document.getElementById('customFileName').value.trim();
+    if (customFileName === "") {
+        alert('请输入保存的文件名');
+        return;
+    }
+
+    // 检查文件名是否包含非法字符
+    const invalidChars = /[\\/:"*?<>|]+/;
+    if (invalidChars.test(customFileName)) {
+        alert('文件名包含非法字符:\\ / : " * ? < > |');
+        return;
+    }
+      // 触发隐藏的文件输入
+      document.getElementById('pdfInput').click();
+    });
+
+    document.getElementById('pdfInput').addEventListener('change', function (event) {
+      const file = event.target.files[0];
+      const customFileName = document.getElementById('customFileName').value.trim();
+
+      if (file && file.type === 'application/pdf') {
+        const formData = new FormData();
+        formData.append('file', file);
+        formData.append('custom_name', customFileName); // 添加自定义文件名
+
+        // 调用 FastAPI 上传接口
+        fetch('http://106.14.113.12:8005/upload-pdf', {  // 根据实际部署情况调整URL
+          method: 'POST',
+          body: formData
+        })
+          .then(response => response.json())
+          .then(data => {
+            if (data.success) {
+              const uploadedFilePath = data.file_path; // 假设后端返回的是文件相对路径
+              // 构建重定向的URL
+              const redirectUrl = `http://106.14.113.12:8005/static/web/viewer.html?file=${uploadedFilePath}`;
+              // 执行页面跳转
+              window.location.href = redirectUrl;
+            } else {
+              alert('上传失败: ' + data.error);
+            }
+          })
+          .catch(error => {
+            console.error('Error:', error);
+            alert('上传过程中发生错误');
+          });
+      } else {
+        alert('请选择一个 PDF 文件');
+      }
+    });
+
+
+    // 处理“打开目录”按钮点击事件
+    document.getElementById('openDirectoryButton').addEventListener('click', function () {
+      // 显示遮罩和弹出层
+      document.getElementById('modalOverlay').style.display = 'block';
+      document.getElementById('directoryModal').style.display = 'block';
+
+      // 清空现有的列表
+      const pdfList = document.getElementById('pdfList');
+      pdfList.innerHTML = '';
+
+      // 调用 FastAPI 获取文件列表
+      fetch('http://106.14.113.12:8005/list-pdfs')  // 根据实际部署情况调整URL
+        .then(response => response.json())
+        .then(data => {
+          if (data.success) {
+            const files = data.files;
+            if (files.length === 0) {
+              const listItem = document.createElement('li');
+              listItem.textContent = '没有已上传的 PDF 文档。';
+              pdfList.appendChild(listItem);
+            } else {
+              files.forEach(file => {
+                const listItem = document.createElement('li');
+                listItem.style.marginBottom = '10px';
+
+                const link = document.createElement('a');
+                link.href = `viewer.html?file=${file.url}`;  // 确保 URL 正确
+                link.textContent = file.name;
+                // link.target = '_blank';  // 在新标签页中打开
+
+                //在当前页打开
+                link.addEventListener('click', function (event) {
+                  event.preventDefault(); // 防止默认的跳转行为
+                  const fileUrl = link.getAttribute('href');
+                  // const redirectUrl = `viewer.html?file=${fileUrl}`;
+                  const redirectUrl = `${fileUrl}`;
+                  window.location.href = redirectUrl; // 在当前页面跳转
+                });
+
+                listItem.appendChild(link);
+                pdfList.appendChild(listItem);
+              });
+            }
+          } else {
+            alert('无法获取文件列表: ' + data.error);
+          }
+        })
+        .catch(error => {
+          console.error('Error:', error);
+          alert('获取文件列表时发生错误');
+        });
+    });
+
+    // 处理“关闭”按钮点击事件
+    document.getElementById('closeDirectoryButton').addEventListener('click', function () {
+      document.getElementById('modalOverlay').style.display = 'none';
+      document.getElementById('directoryModal').style.display = 'none';
+    });
+
+    // 处理点击遮罩关闭弹出层
+    document.getElementById('modalOverlay').addEventListener('click', function () {
+      document.getElementById('modalOverlay').style.display = 'none';
+      document.getElementById('directoryModal').style.display = 'none';
+    });
+  </script>
+</body>
+
+</html>

+ 1196 - 0
static/web/bak/viewer_old.html

@@ -0,0 +1,1196 @@
+<!DOCTYPE html>
+
+<html dir="ltr" mozdisallowselectionprint>
+
+<head>
+  <meta charset="utf-8">
+  <meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=1">
+  <meta name="google" content="notranslate">
+  <title>PDF.js viewer</title>
+
+  <!-- This snippet is used in production (included from viewer.html) -->
+  <link rel="resource" type="application/l10n" href="locale/locale.json">
+  <script src="../build/pdf.mjs" type="module"></script>
+
+  <link rel="stylesheet" href="viewer.css">
+
+  <script src="viewer.mjs" type="module"></script>
+
+  <style>
+    /* 模态窗口样式 */
+    .modal {
+      display: none;
+      /* 默认隐藏 */
+      position: fixed;
+      z-index: 10002;
+      left: 50%;
+      top: 50%;
+      transform: translate(-50%, -50%);
+      width: 500px;
+      background-color: white;
+      border: 1px solid #ccc;
+      border-radius: 5px;
+      padding: 20px;
+      box-shadow: 0 5px 15px rgba(0, 0, 0, 0.3);
+    }
+
+    /* 遮罩层样式 */
+    .modal-overlay {
+      display: none;
+      /* 默认隐藏 */
+      position: fixed;
+      top: 0;
+      left: 0;
+      width: 100%;
+      height: 100%;
+      background-color: rgba(0, 0, 0, 0.5);
+      z-index: 10001;
+    }
+
+    /* 按钮样式 */
+    .button {
+      padding: 10px 20px;
+      background-color: #007bff;
+      color: white;
+      border: none;
+      border-radius: 5px;
+      cursor: pointer;
+    }
+
+    .button-secondary {
+      background-color: #6c757d;
+    }
+
+    .button-success {
+      background-color: #28a745;
+    }
+
+    .button-light {
+      background-color: #ded520;
+    }
+
+    .button-danger {
+      background-color: #dc3545;
+    }
+  </style>
+
+</head>
+
+<body tabindex="0">
+  <!-- 等待指示器 -->
+  <div id="loading-indicator"
+    style="display: none; position: fixed; top: 20px; left: 20px; background: rgba(0,0,0,0.7); color: #fff; padding: 10px; border-radius: 5px;">
+    正在生成语音,请稍候...
+  </div>
+
+  <audio id="audio-player" controls
+    style="position: fixed; width: 190px;height: 50px; bottom: 10px; left: 10px; z-index: 1000;"></audio>
+  <!-- 上传按钮定位在页面底部 -->
+  <button id="uploadButton" style="
+  position: fixed;
+  bottom: 20px;
+  right: 20px;
+  z-index: 1000;
+  padding: 10px 20px;
+  background-color: #007bff;
+  color: white;
+  border: none;
+  border-radius: 5px;
+  cursor: pointer;">
+    上传 PDF
+  </button>
+
+
+  <!-- 隐藏的文件输入 -->
+  <input type="file" id="pdfInput" accept="application/pdf" style="display: none;" />
+
+  <!-- 模态窗口 -->
+  <div id="filenameModal" class="modal">
+    <h3>输入保存的文件名</h3>
+    <input type="text" id="customFileName" placeholder="不含扩展名" style="width: 100%; padding: 5px; margin-top: 10px;" />
+    <div style="margin-top: 20px; text-align: right;">
+      <button id="cancelUpload" class="button button-secondary">取消</button>
+      <button id="confirmUpload" class="button button-success" style="margin-left: 10px;">确认</button>
+    </div>
+  </div>
+
+  <!-- 页面遮罩 -->
+  <div id="modalOverlay" class="modal-overlay"></div>
+
+  <!-- 打开目录按钮 -->
+  <button id="openDirectoryButton" class="button" style="
+    position: fixed;
+    bottom: 70px;
+    right: 20px;  
+    z-index: 1000;
+    ">打开目录
+  </button>
+
+  <button id="select-all-text" class="button button-light" style="
+  position: fixed;
+  bottom: 170px;
+  right: 20px;  
+  z-index: 1000;
+  color:black;
+  ">阅读整页</button>
+
+  <button id="toggle-text-select" class="button button-success" style="
+  position: fixed;
+  bottom: 120px;
+  right: 20px;  
+  z-index: 1000;
+  ">文本选择</button>
+
+  <!-- 目录弹出层 -->
+  <div id="directoryModal" class="modal">
+    <h2>已上传的 PDF 文档</h2>
+    <ul id="pdfList" style="list-style-type: none; padding: 0;"></ul>
+    <button id="closeDirectoryButton" class="button button-danger" style="margin-top: 20px; width: 100%;">关闭</button>
+  </div>
+
+  <div id="outerContainer">
+    <div id="sidebarContainer">
+      <div id="toolbarSidebar" class="toolbarHorizontalGroup">
+        <div id="toolbarSidebarLeft">
+          <div id="sidebarViewButtons" class="toolbarHorizontalGroup toggled" role="radiogroup">
+            <button id="viewThumbnail" class="toolbarButton toggled" type="button" title="Show Thumbnails" tabindex="0"
+              data-l10n-id="pdfjs-thumbs-button" role="radio" aria-checked="true" aria-controls="thumbnailView">
+              <span data-l10n-id="pdfjs-thumbs-button-label">Thumbnails</span>
+            </button>
+            <button id="viewOutline" class="toolbarButton" type="button"
+              title="Show Document Outline (double-click to expand/collapse all items)" tabindex="0"
+              data-l10n-id="pdfjs-document-outline-button" role="radio" aria-checked="false"
+              aria-controls="outlineView">
+              <span data-l10n-id="pdfjs-document-outline-button-label">Document Outline</span>
+            </button>
+            <button id="viewAttachments" class="toolbarButton" type="button" title="Show Attachments" tabindex="0"
+              data-l10n-id="pdfjs-attachments-button" role="radio" aria-checked="false" aria-controls="attachmentsView">
+              <span data-l10n-id="pdfjs-attachments-button-label">Attachments</span>
+            </button>
+            <button id="viewLayers" class="toolbarButton" type="button"
+              title="Show Layers (double-click to reset all layers to the default state)" tabindex="0"
+              data-l10n-id="pdfjs-layers-button" role="radio" aria-checked="false" aria-controls="layersView">
+              <span data-l10n-id="pdfjs-layers-button-label">Layers</span>
+            </button>
+          </div>
+        </div>
+
+        <div id="toolbarSidebarRight">
+          <div id="outlineOptionsContainer" class="toolbarHorizontalGroup">
+            <div class="verticalToolbarSeparator"></div>
+
+            <button id="currentOutlineItem" class="toolbarButton" type="button" disabled="disabled"
+              title="Find Current Outline Item" tabindex="0" data-l10n-id="pdfjs-current-outline-item-button">
+              <span data-l10n-id="pdfjs-current-outline-item-button-label">Current Outline Item</span>
+            </button>
+          </div>
+        </div>
+      </div>
+      <div id="sidebarContent">
+        <div id="thumbnailView">
+        </div>
+        <div id="outlineView" class="hidden">
+        </div>
+        <div id="attachmentsView" class="hidden">
+        </div>
+        <div id="layersView" class="hidden">
+        </div>
+      </div>
+      <div id="sidebarResizer"></div>
+    </div> <!-- sidebarContainer -->
+
+    <div id="mainContainer">
+      <div class="toolbar">
+        <div id="toolbarContainer">
+          <div id="toolbarViewer" class="toolbarHorizontalGroup">
+            <div id="toolbarViewerLeft" class="toolbarHorizontalGroup">
+              <button id="sidebarToggleButton" class="toolbarButton" type="button" title="Toggle Sidebar" tabindex="0"
+                data-l10n-id="pdfjs-toggle-sidebar-button" aria-expanded="false" aria-haspopup="true"
+                aria-controls="sidebarContainer">
+                <span data-l10n-id="pdfjs-toggle-sidebar-button-label">Toggle Sidebar</span>
+              </button>
+              <div class="toolbarButtonSpacer"></div>
+              <div class="toolbarButtonWithContainer">
+                <button id="viewFindButton" class="toolbarButton" type="button" title="Find in Document" tabindex="0"
+                  data-l10n-id="pdfjs-findbar-button" aria-expanded="false" aria-controls="findbar">
+                  <span data-l10n-id="pdfjs-findbar-button-label">Find</span>
+                </button>
+                <div class="hidden doorHanger toolbarHorizontalGroup" id="findbar">
+                  <div id="findInputContainer" class="toolbarHorizontalGroup">
+                    <span class="loadingInput end toolbarHorizontalGroup">
+                      <input id="findInput" class="toolbarField" title="Find" placeholder="Find in document…"
+                        tabindex="0" data-l10n-id="pdfjs-find-input" aria-invalid="false">
+                    </span>
+                    <div class="toolbarHorizontalGroup">
+                      <button id="findPreviousButton" class="toolbarButton" type="button"
+                        title="Find the previous occurrence of the phrase" tabindex="0"
+                        data-l10n-id="pdfjs-find-previous-button">
+                        <span data-l10n-id="pdfjs-find-previous-button-label">Previous</span>
+                      </button>
+                      <div class="splitToolbarButtonSeparator"></div>
+                      <button id="findNextButton" class="toolbarButton" type="button"
+                        title="Find the next occurrence of the phrase" tabindex="0"
+                        data-l10n-id="pdfjs-find-next-button">
+                        <span data-l10n-id="pdfjs-find-next-button-label">Next</span>
+                      </button>
+                    </div>
+                  </div>
+
+                  <div id="findbarOptionsOneContainer" class="toolbarHorizontalGroup">
+                    <div class="toggleButton toolbarLabel">
+                      <input type="checkbox" id="findHighlightAll" tabindex="0" />
+                      <label for="findHighlightAll" data-l10n-id="pdfjs-find-highlight-checkbox">Highlight All</label>
+                    </div>
+                    <div class="toggleButton toolbarLabel">
+                      <input type="checkbox" id="findMatchCase" tabindex="0" />
+                      <label for="findMatchCase" data-l10n-id="pdfjs-find-match-case-checkbox-label">Match Case</label>
+                    </div>
+                  </div>
+                  <div id="findbarOptionsTwoContainer" class="toolbarHorizontalGroup">
+                    <div class="toggleButton toolbarLabel">
+                      <input type="checkbox" id="findMatchDiacritics" tabindex="0" />
+                      <label for="findMatchDiacritics" data-l10n-id="pdfjs-find-match-diacritics-checkbox-label">Match
+                        Diacritics</label>
+                    </div>
+                    <div class="toggleButton toolbarLabel">
+                      <input type="checkbox" id="findEntireWord" tabindex="0" />
+                      <label for="findEntireWord" data-l10n-id="pdfjs-find-entire-word-checkbox-label">Whole
+                        Words</label>
+                    </div>
+                  </div>
+
+                  <div id="findbarMessageContainer" class="toolbarHorizontalGroup" aria-live="polite">
+                    <span id="findResultsCount" class="toolbarLabel"></span>
+                    <span id="findMsg" class="toolbarLabel"></span>
+                  </div>
+                </div> <!-- findbar -->
+              </div>
+              <div class="toolbarHorizontalGroup hiddenSmallView">
+                <button class="toolbarButton" title="Previous Page" type="button" id="previous" tabindex="0"
+                  data-l10n-id="pdfjs-previous-button">
+                  <span data-l10n-id="pdfjs-previous-button-label">Previous</span>
+                </button>
+                <div class="splitToolbarButtonSeparator"></div>
+                <button class="toolbarButton" type="button" title="Next Page" id="next" tabindex="0"
+                  data-l10n-id="pdfjs-next-button">
+                  <span data-l10n-id="pdfjs-next-button-label">Next</span>
+                </button>
+              </div>
+              <div class="toolbarHorizontalGroup">
+                <span class="loadingInput start toolbarHorizontalGroup">
+                  <input type="number" id="pageNumber" class="toolbarField" title="Page" value="1" min="1" tabindex="0"
+                    data-l10n-id="pdfjs-page-input" autocomplete="off">
+                </span>
+                <span id="numPages" class="toolbarLabel"></span>
+              </div>
+            </div>
+            <div id="toolbarViewerMiddle" class="toolbarHorizontalGroup">
+              <div class="toolbarHorizontalGroup">
+                <button id="zoomOutButton" class="toolbarButton" type="button" title="Zoom Out" tabindex="0"
+                  data-l10n-id="pdfjs-zoom-out-button">
+                  <span data-l10n-id="pdfjs-zoom-out-button-label">Zoom Out</span>
+                </button>
+                <div class="splitToolbarButtonSeparator"></div>
+                <button id="zoomInButton" class="toolbarButton" type="button" title="Zoom In" tabindex="0"
+                  data-l10n-id="pdfjs-zoom-in-button">
+                  <span data-l10n-id="pdfjs-zoom-in-button-label">Zoom In</span>
+                </button>
+              </div>
+              <span id="scaleSelectContainer" class="dropdownToolbarButton">
+                <select id="scaleSelect" title="Zoom" tabindex="0" data-l10n-id="pdfjs-zoom-select">
+                  <option id="pageAutoOption" title="" value="auto" selected="selected"
+                    data-l10n-id="pdfjs-page-scale-auto">Automatic Zoom</option>
+                  <option id="pageActualOption" title="" value="page-actual" data-l10n-id="pdfjs-page-scale-actual">
+                    Actual Size</option>
+                  <option id="pageFitOption" title="" value="page-fit" data-l10n-id="pdfjs-page-scale-fit">Page Fit
+                  </option>
+                  <option id="pageWidthOption" title="" value="page-width" data-l10n-id="pdfjs-page-scale-width">Page
+                    Width</option>
+                  <option id="customScaleOption" title="" value="custom" disabled="disabled" hidden="true"
+                    data-l10n-id="pdfjs-page-scale-percent" data-l10n-args='{ "scale": 0 }'>0%</option>
+                  <option title="" value="0.5" data-l10n-id="pdfjs-page-scale-percent" data-l10n-args='{ "scale": 50 }'>
+                    50%</option>
+                  <option title="" value="0.75" data-l10n-id="pdfjs-page-scale-percent"
+                    data-l10n-args='{ "scale": 75 }'>
+                    75%</option>
+                  <option title="" value="1" data-l10n-id="pdfjs-page-scale-percent" data-l10n-args='{ "scale": 100 }'>
+                    100%</option>
+                  <option title="" value="1.25" data-l10n-id="pdfjs-page-scale-percent"
+                    data-l10n-args='{ "scale": 125 }'>
+                    125%</option>
+                  <option title="" value="1.5" data-l10n-id="pdfjs-page-scale-percent"
+                    data-l10n-args='{ "scale": 150 }'>
+                    150%</option>
+                  <option title="" value="2" data-l10n-id="pdfjs-page-scale-percent" data-l10n-args='{ "scale": 200 }'>
+                    200%</option>
+                  <option title="" value="3" data-l10n-id="pdfjs-page-scale-percent" data-l10n-args='{ "scale": 300 }'>
+                    300%</option>
+                  <option title="" value="4" data-l10n-id="pdfjs-page-scale-percent" data-l10n-args='{ "scale": 400 }'>
+                    400%</option>
+                </select>
+              </span>
+            </div>
+            <div id="toolbarViewerRight" class="toolbarHorizontalGroup">
+              <div id="editorModeButtons" class="toolbarHorizontalGroup" role="radiogroup">
+                <div id="editorHighlight" class="toolbarButtonWithContainer">
+                  <button id="editorHighlightButton" class="toolbarButton" type="button" disabled="disabled"
+                    title="Highlight" role="radio" aria-expanded="false" aria-haspopup="true"
+                    aria-controls="editorHighlightParamsToolbar" tabindex="0"
+                    data-l10n-id="pdfjs-editor-highlight-button">
+                    <span data-l10n-id="pdfjs-editor-highlight-button-label">Highlight</span>
+                  </button>
+                  <div class="editorParamsToolbar hidden doorHangerRight" id="editorHighlightParamsToolbar">
+                    <div id="highlightParamsToolbarContainer" class="editorParamsToolbarContainer">
+                      <div id="editorHighlightColorPicker" class="colorPicker">
+                        <span id="highlightColorPickerLabel" class="editorParamsLabel"
+                          data-l10n-id="pdfjs-editor-highlight-colorpicker-label">Highlight color</span>
+                      </div>
+                      <div id="editorHighlightThickness">
+                        <label for="editorFreeHighlightThickness" class="editorParamsLabel"
+                          data-l10n-id="pdfjs-editor-free-highlight-thickness-input">Thickness</label>
+                        <div class="thicknessPicker">
+                          <input type="range" id="editorFreeHighlightThickness" class="editorParamsSlider"
+                            data-l10n-id="pdfjs-editor-free-highlight-thickness-title" value="12" min="8" max="24"
+                            step="1" tabindex="0">
+                        </div>
+                      </div>
+                      <div id="editorHighlightVisibility">
+                        <div class="divider"></div>
+                        <div class="toggler">
+                          <label for="editorHighlightShowAll" class="editorParamsLabel"
+                            data-l10n-id="pdfjs-editor-highlight-show-all-button-label">Show all</label>
+                          <button id="editorHighlightShowAll" class="toggle-button" type="button"
+                            data-l10n-id="pdfjs-editor-highlight-show-all-button" aria-pressed="true"
+                            tabindex="0"></button>
+                        </div>
+                      </div>
+                    </div>
+                  </div>
+                </div>
+                <div id="editorFreeText" class="toolbarButtonWithContainer">
+                  <button id="editorFreeTextButton" class="toolbarButton" type="button" disabled="disabled" title="Text"
+                    role="radio" aria-expanded="false" aria-haspopup="true" aria-controls="editorFreeTextParamsToolbar"
+                    tabindex="0" data-l10n-id="pdfjs-editor-free-text-button">
+                    <span data-l10n-id="pdfjs-editor-free-text-button-label">Text</span>
+                  </button>
+                  <div class="editorParamsToolbar hidden doorHangerRight" id="editorFreeTextParamsToolbar">
+                    <div class="editorParamsToolbarContainer">
+                      <div class="editorParamsSetter">
+                        <label for="editorFreeTextColor" class="editorParamsLabel"
+                          data-l10n-id="pdfjs-editor-free-text-color-input">Color</label>
+                        <input type="color" id="editorFreeTextColor" class="editorParamsColor" tabindex="0">
+                      </div>
+                      <div class="editorParamsSetter">
+                        <label for="editorFreeTextFontSize" class="editorParamsLabel"
+                          data-l10n-id="pdfjs-editor-free-text-size-input">Size</label>
+                        <input type="range" id="editorFreeTextFontSize" class="editorParamsSlider" value="10" min="5"
+                          max="100" step="1" tabindex="0">
+                      </div>
+                    </div>
+                  </div>
+                </div>
+                <div id="editorInk" class="toolbarButtonWithContainer">
+                  <button id="editorInkButton" class="toolbarButton" type="button" disabled="disabled" title="Draw"
+                    role="radio" aria-expanded="false" aria-haspopup="true" aria-controls="editorInkParamsToolbar"
+                    tabindex="0" data-l10n-id="pdfjs-editor-ink-button">
+                    <span data-l10n-id="pdfjs-editor-ink-button-label">Draw</span>
+                  </button>
+                  <div class="editorParamsToolbar hidden doorHangerRight" id="editorInkParamsToolbar">
+                    <div class="editorParamsToolbarContainer">
+                      <div class="editorParamsSetter">
+                        <label for="editorInkColor" class="editorParamsLabel"
+                          data-l10n-id="pdfjs-editor-ink-color-input">Color</label>
+                        <input type="color" id="editorInkColor" class="editorParamsColor" tabindex="0">
+                      </div>
+                      <div class="editorParamsSetter">
+                        <label for="editorInkThickness" class="editorParamsLabel"
+                          data-l10n-id="pdfjs-editor-ink-thickness-input">Thickness</label>
+                        <input type="range" id="editorInkThickness" class="editorParamsSlider" value="1" min="1"
+                          max="20" step="1" tabindex="0">
+                      </div>
+                      <div class="editorParamsSetter">
+                        <label for="editorInkOpacity" class="editorParamsLabel"
+                          data-l10n-id="pdfjs-editor-ink-opacity-input">Opacity</label>
+                        <input type="range" id="editorInkOpacity" class="editorParamsSlider" value="100" min="1"
+                          max="100" step="1" tabindex="0">
+                      </div>
+                    </div>
+                  </div>
+                </div>
+                <div id="editorStamp" class="toolbarButtonWithContainer">
+                  <button id="editorStampButton" class="toolbarButton" type="button" disabled="disabled"
+                    title="Add or edit images" role="radio" aria-expanded="false" aria-haspopup="true"
+                    aria-controls="editorStampParamsToolbar" tabindex="0" data-l10n-id="pdfjs-editor-stamp-button">
+                    <span data-l10n-id="pdfjs-editor-stamp-button-label">Add or edit images</span>
+                  </button>
+                  <div class="editorParamsToolbar hidden doorHangerRight menu" id="editorStampParamsToolbar">
+                    <div class="menuContainer">
+                      <button id="editorStampAddImage" class="toolbarButton labeled" type="button" title="Add image"
+                        tabindex="0" data-l10n-id="pdfjs-editor-stamp-add-image-button">
+                        <span class="editorParamsLabel" data-l10n-id="pdfjs-editor-stamp-add-image-button-label">Add
+                          image</span>
+                      </button>
+                    </div>
+                  </div>
+                </div>
+              </div>
+
+              <div id="editorModeSeparator" class="verticalToolbarSeparator"></div>
+
+              <div class="toolbarHorizontalGroup hiddenMediumView">
+                <button id="printButton" class="toolbarButton" type="button" title="Print" tabindex="0"
+                  data-l10n-id="pdfjs-print-button">
+                  <span data-l10n-id="pdfjs-print-button-label">Print</span>
+                </button>
+
+                <button id="downloadButton" class="toolbarButton" type="button" title="Save" tabindex="0"
+                  data-l10n-id="pdfjs-save-button">
+                  <span data-l10n-id="pdfjs-save-button-label">Save</span>
+                </button>
+              </div>
+
+              <div class="verticalToolbarSeparator hiddenMediumView"></div>
+
+              <div id="secondaryToolbarToggle" class="toolbarButtonWithContainer">
+                <button id="secondaryToolbarToggleButton" class="toolbarButton" type="button" title="Tools" tabindex="0"
+                  data-l10n-id="pdfjs-tools-button" aria-expanded="false" aria-haspopup="true"
+                  aria-controls="secondaryToolbar">
+                  <span data-l10n-id="pdfjs-tools-button-label">Tools</span>
+                </button>
+                <div id="secondaryToolbar" class="hidden doorHangerRight menu">
+                  <div id="secondaryToolbarButtonContainer" class="menuContainer">
+                    <button id="secondaryOpenFile" class="toolbarButton labeled" type="button" title="Open File"
+                      tabindex="0" data-l10n-id="pdfjs-open-file-button">
+                      <span data-l10n-id="pdfjs-open-file-button-label">Open</span>
+                    </button>
+
+                    <div class="visibleMediumView">
+                      <button id="secondaryPrint" class="toolbarButton labeled" type="button" title="Print" tabindex="0"
+                        data-l10n-id="pdfjs-print-button">
+                        <span data-l10n-id="pdfjs-print-button-label">Print</span>
+                      </button>
+
+                      <button id="secondaryDownload" class="toolbarButton labeled" type="button" title="Save"
+                        tabindex="0" data-l10n-id="pdfjs-save-button">
+                        <span data-l10n-id="pdfjs-save-button-label">Save</span>
+                      </button>
+
+                    </div>
+
+                    <div class="horizontalToolbarSeparator"></div>
+
+                    <button id="presentationMode" class="toolbarButton labeled" type="button"
+                      title="Switch to Presentation Mode" tabindex="0" data-l10n-id="pdfjs-presentation-mode-button">
+                      <span data-l10n-id="pdfjs-presentation-mode-button-label">Presentation Mode</span>
+                    </button>
+
+                    <a href="#" id="viewBookmark" class="toolbarButton labeled"
+                      title="Current Page (View URL from Current Page)" tabindex="0"
+                      data-l10n-id="pdfjs-bookmark-button">
+                      <span data-l10n-id="pdfjs-bookmark-button-label">Current Page</span>
+                    </a>
+
+                    <div id="viewBookmarkSeparator" class="horizontalToolbarSeparator"></div>
+
+                    <button id="firstPage" class="toolbarButton labeled" type="button" title="Go to First Page"
+                      tabindex="0" data-l10n-id="pdfjs-first-page-button">
+                      <span data-l10n-id="pdfjs-first-page-button-label">Go to First Page</span>
+                    </button>
+                    <button id="lastPage" class="toolbarButton labeled" type="button" title="Go to Last Page"
+                      tabindex="0" data-l10n-id="pdfjs-last-page-button">
+                      <span data-l10n-id="pdfjs-last-page-button-label">Go to Last Page</span>
+                    </button>
+
+                    <div class="horizontalToolbarSeparator"></div>
+
+                    <button id="pageRotateCw" class="toolbarButton labeled" type="button" title="Rotate Clockwise"
+                      tabindex="0" data-l10n-id="pdfjs-page-rotate-cw-button">
+                      <span data-l10n-id="pdfjs-page-rotate-cw-button-label">Rotate Clockwise</span>
+                    </button>
+                    <button id="pageRotateCcw" class="toolbarButton labeled" type="button"
+                      title="Rotate Counterclockwise" tabindex="0" data-l10n-id="pdfjs-page-rotate-ccw-button">
+                      <span data-l10n-id="pdfjs-page-rotate-ccw-button-label">Rotate Counterclockwise</span>
+                    </button>
+
+                    <div class="horizontalToolbarSeparator"></div>
+
+                    <div id="cursorToolButtons" role="radiogroup">
+                      <button id="cursorSelectTool" class="toolbarButton labeled toggled" type="button"
+                        title="Enable Text Selection Tool" tabindex="0"
+                        data-l10n-id="pdfjs-cursor-text-select-tool-button" role="radio" aria-checked="true">
+                        <span data-l10n-id="pdfjs-cursor-text-select-tool-button-label">Text Selection Tool</span>
+                      </button>
+                      <button id="cursorHandTool" class="toolbarButton labeled" type="button" title="Enable Hand Tool"
+                        tabindex="0" data-l10n-id="pdfjs-cursor-hand-tool-button" role="radio" aria-checked="false">
+                        <span data-l10n-id="pdfjs-cursor-hand-tool-button-label">Hand Tool</span>
+                      </button>
+                    </div>
+
+                    <div class="horizontalToolbarSeparator"></div>
+
+                    <div id="scrollModeButtons" role="radiogroup">
+                      <button id="scrollPage" class="toolbarButton labeled" type="button" title="Use Page Scrolling"
+                        tabindex="0" data-l10n-id="pdfjs-scroll-page-button" role="radio" aria-checked="false">
+                        <span data-l10n-id="pdfjs-scroll-page-button-label">Page Scrolling</span>
+                      </button>
+                      <button id="scrollVertical" class="toolbarButton labeled toggled" type="button"
+                        title="Use Vertical Scrolling" tabindex="0" data-l10n-id="pdfjs-scroll-vertical-button"
+                        role="radio" aria-checked="true">
+                        <span data-l10n-id="pdfjs-scroll-vertical-button-label">Vertical Scrolling</span>
+                      </button>
+                      <button id="scrollHorizontal" class="toolbarButton labeled" type="button"
+                        title="Use Horizontal Scrolling" tabindex="0" data-l10n-id="pdfjs-scroll-horizontal-button"
+                        role="radio" aria-checked="false">
+                        <span data-l10n-id="pdfjs-scroll-horizontal-button-label">Horizontal Scrolling</span>
+                      </button>
+                      <button id="scrollWrapped" class="toolbarButton labeled" type="button"
+                        title="Use Wrapped Scrolling" tabindex="0" data-l10n-id="pdfjs-scroll-wrapped-button"
+                        role="radio" aria-checked="false">
+                        <span data-l10n-id="pdfjs-scroll-wrapped-button-label">Wrapped Scrolling</span>
+                      </button>
+                    </div>
+
+                    <div class="horizontalToolbarSeparator"></div>
+
+                    <div id="spreadModeButtons" role="radiogroup">
+                      <button id="spreadNone" class="toolbarButton labeled toggled" type="button"
+                        title="Do not join page spreads" tabindex="0" data-l10n-id="pdfjs-spread-none-button"
+                        role="radio" aria-checked="true">
+                        <span data-l10n-id="pdfjs-spread-none-button-label">No Spreads</span>
+                      </button>
+                      <button id="spreadOdd" class="toolbarButton labeled" type="button"
+                        title="Join page spreads starting with odd-numbered pages" tabindex="0"
+                        data-l10n-id="pdfjs-spread-odd-button" role="radio" aria-checked="false">
+                        <span data-l10n-id="pdfjs-spread-odd-button-label">Odd Spreads</span>
+                      </button>
+                      <button id="spreadEven" class="toolbarButton labeled" type="button"
+                        title="Join page spreads starting with even-numbered pages" tabindex="0"
+                        data-l10n-id="pdfjs-spread-even-button" role="radio" aria-checked="false">
+                        <span data-l10n-id="pdfjs-spread-even-button-label">Even Spreads</span>
+                      </button>
+                    </div>
+
+                    <div id="imageAltTextSettingsSeparator" class="horizontalToolbarSeparator hidden"></div>
+                    <button id="imageAltTextSettings" type="button" class="toolbarButton labeled hidden"
+                      title="Image alt text settings" tabindex="0" data-l10n-id="pdfjs-image-alt-text-settings-button"
+                      aria-controls="altTextSettingsDialog">
+                      <span data-l10n-id="pdfjs-image-alt-text-settings-button-label">Image alt text settings</span>
+                    </button>
+
+                    <div class="horizontalToolbarSeparator"></div>
+
+                    <button id="documentProperties" class="toolbarButton labeled" type="button"
+                      title="Document Properties…" tabindex="0" data-l10n-id="pdfjs-document-properties-button"
+                      aria-controls="documentPropertiesDialog">
+                      <span data-l10n-id="pdfjs-document-properties-button-label">Document Properties…</span>
+                    </button>
+                  </div>
+                </div> <!-- secondaryToolbar -->
+              </div>
+            </div>
+          </div>
+          <div id="loadingBar">
+            <div class="progress">
+              <div class="glimmer">
+              </div>
+            </div>
+          </div>
+        </div>
+      </div>
+
+      <div id="viewerContainer" tabindex="0">
+        <div id="viewer" class="pdfViewer"></div>
+      </div>
+    </div> <!-- mainContainer -->
+
+    <div id="dialogContainer">
+      <dialog id="passwordDialog">
+        <div class="row">
+          <label for="password" id="passwordText" data-l10n-id="pdfjs-password-label">Enter the password to open this
+            PDF file:</label>
+        </div>
+        <div class="row">
+          <input type="password" id="password" class="toolbarField">
+        </div>
+        <div class="buttonRow">
+          <button id="passwordCancel" class="dialogButton" type="button"><span
+              data-l10n-id="pdfjs-password-cancel-button">Cancel</span></button>
+          <button id="passwordSubmit" class="dialogButton" type="button"><span
+              data-l10n-id="pdfjs-password-ok-button">OK</span></button>
+        </div>
+      </dialog>
+      <dialog id="documentPropertiesDialog">
+        <div class="row">
+          <span id="fileNameLabel" data-l10n-id="pdfjs-document-properties-file-name">File name:</span>
+          <p id="fileNameField" aria-labelledby="fileNameLabel">-</p>
+        </div>
+        <div class="row">
+          <span id="fileSizeLabel" data-l10n-id="pdfjs-document-properties-file-size">File size:</span>
+          <p id="fileSizeField" aria-labelledby="fileSizeLabel">-</p>
+        </div>
+        <div class="separator"></div>
+        <div class="row">
+          <span id="titleLabel" data-l10n-id="pdfjs-document-properties-title">Title:</span>
+          <p id="titleField" aria-labelledby="titleLabel">-</p>
+        </div>
+        <div class="row">
+          <span id="authorLabel" data-l10n-id="pdfjs-document-properties-author">Author:</span>
+          <p id="authorField" aria-labelledby="authorLabel">-</p>
+        </div>
+        <div class="row">
+          <span id="subjectLabel" data-l10n-id="pdfjs-document-properties-subject">Subject:</span>
+          <p id="subjectField" aria-labelledby="subjectLabel">-</p>
+        </div>
+        <div class="row">
+          <span id="keywordsLabel" data-l10n-id="pdfjs-document-properties-keywords">Keywords:</span>
+          <p id="keywordsField" aria-labelledby="keywordsLabel">-</p>
+        </div>
+        <div class="row">
+          <span id="creationDateLabel" data-l10n-id="pdfjs-document-properties-creation-date">Creation Date:</span>
+          <p id="creationDateField" aria-labelledby="creationDateLabel">-</p>
+        </div>
+        <div class="row">
+          <span id="modificationDateLabel" data-l10n-id="pdfjs-document-properties-modification-date">Modification
+            Date:</span>
+          <p id="modificationDateField" aria-labelledby="modificationDateLabel">-</p>
+        </div>
+        <div class="row">
+          <span id="creatorLabel" data-l10n-id="pdfjs-document-properties-creator">Creator:</span>
+          <p id="creatorField" aria-labelledby="creatorLabel">-</p>
+        </div>
+        <div class="separator"></div>
+        <div class="row">
+          <span id="producerLabel" data-l10n-id="pdfjs-document-properties-producer">PDF Producer:</span>
+          <p id="producerField" aria-labelledby="producerLabel">-</p>
+        </div>
+        <div class="row">
+          <span id="versionLabel" data-l10n-id="pdfjs-document-properties-version">PDF Version:</span>
+          <p id="versionField" aria-labelledby="versionLabel">-</p>
+        </div>
+        <div class="row">
+          <span id="pageCountLabel" data-l10n-id="pdfjs-document-properties-page-count">Page Count:</span>
+          <p id="pageCountField" aria-labelledby="pageCountLabel">-</p>
+        </div>
+        <div class="row">
+          <span id="pageSizeLabel" data-l10n-id="pdfjs-document-properties-page-size">Page Size:</span>
+          <p id="pageSizeField" aria-labelledby="pageSizeLabel">-</p>
+        </div>
+        <div class="separator"></div>
+        <div class="row">
+          <span id="linearizedLabel" data-l10n-id="pdfjs-document-properties-linearized">Fast Web View:</span>
+          <p id="linearizedField" aria-labelledby="linearizedLabel">-</p>
+        </div>
+        <div class="buttonRow">
+          <button id="documentPropertiesClose" class="dialogButton" type="button"><span
+              data-l10n-id="pdfjs-document-properties-close-button">Close</span></button>
+        </div>
+      </dialog>
+      <dialog class="dialog altText" id="altTextDialog" aria-labelledby="dialogLabel"
+        aria-describedby="dialogDescription">
+        <div id="altTextContainer" class="mainContainer">
+          <div id="overallDescription">
+            <span id="dialogLabel" data-l10n-id="pdfjs-editor-alt-text-dialog-label" class="title">Choose an
+              option</span>
+            <span id="dialogDescription" data-l10n-id="pdfjs-editor-alt-text-dialog-description">
+              Alt text (alternative text) helps when people can’t see the image or when it doesn’t load.
+            </span>
+          </div>
+          <div id="addDescription">
+            <div class="radio">
+              <div class="radioButton">
+                <input type="radio" id="descriptionButton" name="altTextOption" tabindex="0"
+                  aria-describedby="descriptionAreaLabel" checked>
+                <label for="descriptionButton" data-l10n-id="pdfjs-editor-alt-text-add-description-label">Add a
+                  description</label>
+              </div>
+              <div class="radioLabel">
+                <span id="descriptionAreaLabel" data-l10n-id="pdfjs-editor-alt-text-add-description-description">
+                  Aim for 1-2 sentences that describe the subject, setting, or actions.
+                </span>
+              </div>
+            </div>
+            <div class="descriptionArea">
+              <textarea id="descriptionTextarea"
+                placeholder="For example, “A young man sits down at a table to eat a meal”"
+                aria-labelledby="descriptionAreaLabel" data-l10n-id="pdfjs-editor-alt-text-textarea"
+                tabindex="0"></textarea>
+            </div>
+          </div>
+          <div id="markAsDecorative">
+            <div class="radio">
+              <div class="radioButton">
+                <input type="radio" id="decorativeButton" name="altTextOption" aria-describedby="decorativeLabel">
+                <label for="decorativeButton" data-l10n-id="pdfjs-editor-alt-text-mark-decorative-label">Mark as
+                  decorative</label>
+              </div>
+              <div class="radioLabel">
+                <span id="decorativeLabel" data-l10n-id="pdfjs-editor-alt-text-mark-decorative-description">
+                  This is used for ornamental images, like borders or watermarks.
+                </span>
+              </div>
+            </div>
+          </div>
+          <div id="buttons">
+            <button id="altTextCancel" class="secondaryButton" type="button" tabindex="0"><span
+                data-l10n-id="pdfjs-editor-alt-text-cancel-button">Cancel</span></button>
+            <button id="altTextSave" class="primaryButton" type="button" tabindex="0"><span
+                data-l10n-id="pdfjs-editor-alt-text-save-button">Save</span></button>
+          </div>
+        </div>
+      </dialog>
+      <dialog class="dialog newAltText" id="newAltTextDialog" aria-labelledby="newAltTextTitle"
+        aria-describedby="newAltTextDescription" tabindex="0">
+        <div id="newAltTextContainer" class="mainContainer">
+          <div class="title">
+            <span id="newAltTextTitle" data-l10n-id="pdfjs-editor-new-alt-text-dialog-edit-label" role="sectionhead"
+              tabindex="0">Edit alt text (image description)</span>
+          </div>
+          <div id="mainContent">
+            <div id="descriptionAndSettings">
+              <div id="descriptionInstruction">
+                <div id="newAltTextDescriptionContainer">
+                  <div class="altTextSpinner" role="status" aria-live="polite"></div>
+                  <textarea id="newAltTextDescriptionTextarea" placeholder="Write your description here…"
+                    aria-labelledby="descriptionAreaLabel" data-l10n-id="pdfjs-editor-new-alt-text-textarea"
+                    tabindex="0"></textarea>
+                </div>
+                <span id="newAltTextDescription" role="note" data-l10n-id="pdfjs-editor-new-alt-text-description">Short
+                  description for people who can’t see the image or when the image doesn’t load.</span>
+                <div id="newAltTextDisclaimer" role="note">
+                  <div><span data-l10n-id="pdfjs-editor-new-alt-text-disclaimer1">This alt text was created
+                      automatically and may be inaccurate.</span> <a
+                      href="https://support.mozilla.org/en-US/kb/pdf-alt-text" target="_blank" rel="noopener noreferrer"
+                      id="newAltTextLearnMore" data-l10n-id="pdfjs-editor-new-alt-text-disclaimer-learn-more-url"
+                      tabindex="0">Learn more</a></div>
+                </div>
+              </div>
+              <div id="newAltTextCreateAutomatically" class="toggler">
+                <button id="newAltTextCreateAutomaticallyButton" class="toggle-button" type="button" aria-pressed="true"
+                  tabindex="0"></button>
+                <label for="newAltTextCreateAutomaticallyButton" class="togglerLabel"
+                  data-l10n-id="pdfjs-editor-new-alt-text-create-automatically-button-label">Create alt text
+                  automatically</label>
+              </div>
+              <div id="newAltTextDownloadModel" class="hidden">
+                <span id="newAltTextDownloadModelDescription"
+                  data-l10n-id="pdfjs-editor-new-alt-text-ai-model-downloading-progress" aria-valuemin="0"
+                  data-l10n-args='{ "totalSize": 0, "downloadedSize": 0 }'>Downloading alt text AI model (0 of 0
+                  MB)</span>
+              </div>
+            </div>
+            <div id="newAltTextImagePreview"></div>
+          </div>
+          <div id="newAltTextError" class="messageBar">
+            <div>
+              <div>
+                <span class="title" data-l10n-id="pdfjs-editor-new-alt-text-error-title">Couldn’t create alt text
+                  automatically</span>
+                <span class="description" data-l10n-id="pdfjs-editor-new-alt-text-error-description">Please write your
+                  own alt text or try again later.</span>
+              </div>
+              <button id="newAltTextCloseButton" class="closeButton" type="button" tabindex="0" title="Close"><span
+                  data-l10n-id="pdfjs-editor-new-alt-text-error-close-button">Close</span></button>
+            </div>
+          </div>
+          <div id="newAltTextButtons" class="dialogButtonsGroup">
+            <button id="newAltTextCancel" type="button" class="secondaryButton hidden" tabindex="0"><span
+                data-l10n-id="pdfjs-editor-alt-text-cancel-button">Cancel</span></button>
+            <button id="newAltTextNotNow" type="button" class="secondaryButton" tabindex="0"><span
+                data-l10n-id="pdfjs-editor-new-alt-text-not-now-button">Not now</span></button>
+            <button id="newAltTextSave" type="button" class="primaryButton" tabindex="0"><span
+                data-l10n-id="pdfjs-editor-alt-text-save-button">Save</span></button>
+          </div>
+        </div>
+      </dialog>
+
+      <dialog class="dialog" id="altTextSettingsDialog" aria-labelledby="altTextSettingsTitle">
+        <div id="altTextSettingsContainer" class="mainContainer">
+          <div class="title">
+            <span id="altTextSettingsTitle" data-l10n-id="pdfjs-editor-alt-text-settings-dialog-label"
+              role="sectionhead" tabindex="0" class="title">Image alt text settings</span>
+          </div>
+          <div id="automaticAltText">
+            <span data-l10n-id="pdfjs-editor-alt-text-settings-automatic-title">Automatic alt text</span>
+            <div id="automaticSettings">
+              <div id="createModelSetting">
+                <div class="toggler">
+                  <button id="createModelButton" type="button" class="toggle-button" aria-pressed="true"
+                    tabindex="0"></button>
+                  <label for="createModelButton" class="togglerLabel"
+                    data-l10n-id="pdfjs-editor-alt-text-settings-create-model-button-label">Create alt text
+                    automatically</label>
+                </div>
+                <div id="createModelDescription" class="description">
+                  <span data-l10n-id="pdfjs-editor-alt-text-settings-create-model-description">Suggests descriptions to
+                    help people who can’t see the image or when the image doesn’t load.</span> <a
+                    href="https://support.mozilla.org/en-US/kb/pdf-alt-text" target="_blank" rel="noopener noreferrer"
+                    id="altTextSettingsLearnMore" data-l10n-id="pdfjs-editor-new-alt-text-disclaimer-learn-more-url"
+                    tabindex="0">Learn more</a>
+                </div>
+              </div>
+              <div id="aiModelSettings">
+                <div>
+                  <span data-l10n-id="pdfjs-editor-alt-text-settings-download-model-label"
+                    data-l10n-args='{ "totalSize": 180 }'>Alt text AI model (180MB)</span>
+                  <div id="aiModelDescription" class="description">
+                    <span data-l10n-id="pdfjs-editor-alt-text-settings-ai-model-description">Runs locally on your device
+                      so your data stays private. Required for automatic alt text.</span>
+                  </div>
+                </div>
+                <button id="deleteModelButton" type="button" class="secondaryButton" tabindex="0"><span
+                    data-l10n-id="pdfjs-editor-alt-text-settings-delete-model-button">Delete</span></button>
+                <button id="downloadModelButton" type="button" class="secondaryButton" tabindex="0"><span
+                    data-l10n-id="pdfjs-editor-alt-text-settings-download-model-button">Download</span></button>
+              </div>
+            </div>
+          </div>
+          <div class="dialogSeparator"></div>
+          <div id="altTextEditor">
+            <span data-l10n-id="pdfjs-editor-alt-text-settings-editor-title">Alt text editor</span>
+            <div id="showAltTextEditor">
+              <div class="toggler">
+                <button id="showAltTextDialogButton" type="button" class="toggle-button" aria-pressed="true"
+                  tabindex="0"></button>
+                <label for="showAltTextDialogButton" class="togglerLabel"
+                  data-l10n-id="pdfjs-editor-alt-text-settings-show-dialog-button-label">Show alt text editor right away
+                  when adding an image</label>
+              </div>
+              <div id="showAltTextDialogDescription" class="description">
+                <span data-l10n-id="pdfjs-editor-alt-text-settings-show-dialog-description">Helps you make sure all your
+                  images have alt text.</span>
+              </div>
+            </div>
+          </div>
+          <div id="buttons" class="dialogButtonsGroup">
+            <button id="altTextSettingsCloseButton" type="button" class="primaryButton" tabindex="0"><span
+                data-l10n-id="pdfjs-editor-alt-text-settings-close-button">Close</span></button>
+          </div>
+        </div>
+      </dialog>
+      <dialog id="printServiceDialog" style="min-width: 200px;">
+        <div class="row">
+          <span data-l10n-id="pdfjs-print-progress-message">Preparing document for printing…</span>
+        </div>
+        <div class="row">
+          <progress value="0" max="100"></progress>
+          <span data-l10n-id="pdfjs-print-progress-percent" data-l10n-args='{ "progress": 0 }'
+            class="relative-progress">0%</span>
+        </div>
+        <div class="buttonRow">
+          <button id="printCancel" class="dialogButton" type="button"><span
+              data-l10n-id="pdfjs-print-progress-close-button">Cancel</span></button>
+        </div>
+      </dialog>
+    </div> <!-- dialogContainer -->
+
+  </div> <!-- outerContainer -->
+  <div id="printContainer"></div>
+
+<script>
+    const uploadButton = document.getElementById('uploadButton');
+    const pdfInput = document.getElementById('pdfInput');
+    const filenameModal = document.getElementById('filenameModal');
+    const modalOverlay = document.getElementById('modalOverlay');
+    const confirmUploadButton = document.getElementById('confirmUpload');
+    const cancelUploadButton = document.getElementById('cancelUpload');
+    const customFileNameInput = document.getElementById('customFileName');
+    const openDirectoryButton = document.getElementById('openDirectoryButton');
+    const directoryModal = document.getElementById('directoryModal');
+    const closeDirectoryButton = document.getElementById('closeDirectoryButton');
+    const pdfList = document.getElementById('pdfList');
+
+    let selectedFile = null;
+
+    // 上传按钮点击事件
+    uploadButton.addEventListener('click', function () {
+      // 触发隐藏的文件输入
+      pdfInput.click();
+    });
+
+    // 文件选择事件
+    pdfInput.addEventListener('change', function (event) {
+      const file = event.target.files[0];
+      if (file && file.type === 'application/pdf') {
+        selectedFile = file;
+        // 显示模态窗口
+        modalOverlay.style.display = 'block';
+        filenameModal.style.display = 'block';
+        customFileNameInput.value = ""; // 清空之前的输入
+        customFileNameInput.focus();
+      } else {
+        alert('请选择一个 PDF 文件');
+      }
+    });
+
+    // 取消上传
+    cancelUploadButton.addEventListener('click', function () {
+      // 隐藏模态窗口和遮罩
+      filenameModal.style.display = 'none';
+      modalOverlay.style.display = 'none';
+      // 清空文件输入
+      pdfInput.value = "";
+      selectedFile = null;
+    });
+
+    // 确认上传
+    confirmUploadButton.addEventListener('click', function () {
+      const customFileName = customFileNameInput.value.trim();
+      if (customFileName === "") {
+        alert('请输入保存的文件名');
+        customFileNameInput.focus();
+        return;
+      }
+
+      // 检查文件名是否包含非法字符
+      const invalidChars = /[\\/:"*?<>|]+/;
+      if (invalidChars.test(customFileName)) {
+        alert('文件名包含非法字符:\\ / : " * ? < > |');
+        customFileNameInput.focus();
+        return;
+      }
+
+      if (!selectedFile) {
+        alert('没有选择文件');
+        return;
+      }
+
+      // 进行上传
+      const formData = new FormData();
+      formData.append('file', selectedFile);
+      formData.append('custom_name', customFileName); // 添加自定义文件名
+
+      // 可以根据需要显示上传进度
+      fetch('http://117.50.195.224:8005/upload-pdf', {  // 根据实际部署情况调整URL
+        method: 'POST',
+        body: formData
+      })
+        .then(response => response.json())
+        .then(data => {
+          if (data.success) {
+            const uploadedFilePath = data.file_path; // 如 "/static/files/XXXX.pdf"
+            const redirectUrl = `viewer.html?file=${encodeURIComponent(uploadedFilePath)}`;
+            window.location.href = redirectUrl;
+          } else {
+            alert('上传失败: ' + data.error);
+          }
+        })
+        .catch(error => {
+          console.error('Error:', error);
+          alert('上传过程中发生错误');
+        })
+        .finally(() => {
+          // 隐藏模态窗口和遮罩
+          filenameModal.style.display = 'none';
+          modalOverlay.style.display = 'none';
+          // 清空文件输入
+          pdfInput.value = "";
+          selectedFile = null;
+        });
+    });
+
+    // 处理“打开目录”按钮点击事件
+    openDirectoryButton.addEventListener('click', function () {
+      // 显示遮罩和弹出层
+      modalOverlay.style.display = 'block';
+      directoryModal.style.display = 'block';
+
+      // 清空现有的列表
+      pdfList.innerHTML = '';
+
+      // 调用 FastAPI 获取文件列表
+      fetch('http://117.50.195.224:8005/list-pdfs')  // 根据实际部署情况调整URL
+        .then(response => response.json())
+        .then(data => {
+          if (data.success) {
+            const files = data.files;
+            if (files.length === 0) {
+              const listItem = document.createElement('li');
+              listItem.textContent = '没有已上传的 PDF 文档。';
+              pdfList.appendChild(listItem);
+            } else {
+              files.forEach(file => {
+                const listItem = document.createElement('li');
+                listItem.style.marginBottom = '10px';
+
+                const link = document.createElement('a');
+                link.href = `viewer.html?file=${file.url}`;  // 确保 URL 正确
+                link.textContent = file.name;
+                link.style.textDecoration = 'none';
+                link.style.color = '#007bff';
+
+                // 添加点击事件,在当前页面打开
+                link.addEventListener('click', function (event) {
+                  event.preventDefault(); // 防止默认的跳转行为
+                  const fileUrl = link.getAttribute('href');
+                  const redirectUrl = `${fileUrl}`;
+                  window.location.href = redirectUrl; // 在当前页面跳转
+                });
+
+                listItem.appendChild(link);
+                pdfList.appendChild(listItem);
+              });
+            }
+          } else {
+            alert('无法获取文件列表: ' + data.error);
+          }
+        })
+        .catch(error => {
+          console.error('Error:', error);
+          alert('获取文件列表时发生错误');
+        });
+    });
+
+    // 处理“关闭”按钮点击事件
+    closeDirectoryButton.addEventListener('click', function () {
+      directoryModal.style.display = 'none';
+      modalOverlay.style.display = 'none';
+    });
+
+    // 点击遮罩关闭弹出层
+    modalOverlay.addEventListener('click', function () {
+      if (filenameModal.style.display === 'block') {
+        filenameModal.style.display = 'none';
+        uploadButton.focus();
+      }
+      if (directoryModal.style.display === 'block') {
+        directoryModal.style.display = 'none';
+      }
+      modalOverlay.style.display = 'none';
+    });
+
+
+  // 语音控制
+let isListening = false;
+
+// 点击按钮开关文本选择监听
+document.getElementById('toggle-text-select').addEventListener('click', () => {
+    isListening = !isListening; // 切换状态
+    const button = document.getElementById('toggle-text-select');
+    button.textContent = isListening ? "关闭监听" : "开启监听";
+    
+    // 切换按钮的样式
+    if (isListening) {
+        button.classList.add('button-danger');
+    } else {
+        button.classList.remove('button-danger');
+    }
+});
+
+document.addEventListener('DOMContentLoaded', () => {
+    const audioPlayer = document.getElementById('audio-player');
+    const loadingIndicator = document.getElementById('loading-indicator');
+
+    // 函数:发送选中的文本到后端进行 TTS 处理
+    function sendTextToTTS(selectedText) {
+        loadingIndicator.style.display = 'block';
+        fetch('http://117.50.195.224:8005/text-to-speech/', {
+            method: 'POST',
+            headers: {
+                'Content-Type': 'application/json'
+            },
+            body: JSON.stringify({ user_input: selectedText })
+        })
+            .then(response => {
+                if (!response.ok) {
+                    throw new Error('语音生成失败');
+                }
+                return response.blob();
+            })
+            .then(blob => {
+                const audioURL = URL.createObjectURL(blob);
+                audioPlayer.src = audioURL;
+                audioPlayer.play();
+            })
+            .catch(err => {
+                alert('生成语音失败');
+                console.error(err);
+            })
+            .finally(() => {
+                // 隐藏等待指示器
+                loadingIndicator.style.display = 'none';
+            });
+    }
+
+    // 监听文本选择事件
+    document.addEventListener('mouseup', () => {
+        if (!isListening) return; // 若未开启监听,直接返回
+
+        const selection = window.getSelection();
+        const selectedText = selection.toString().trim();
+        if (selectedText.length === 0) return;
+
+        // 清除选择
+        selection.removeAllRanges();
+
+        // 发送选中的文本到后端
+        sendTextToTTS(selectedText);
+    });
+});
+
+// 在其他 JavaScript 代码里保持不变
+
+
+
+document.getElementById('select-all-text').addEventListener('click', function() {
+    const { pdfViewer } = PDFViewerApplication;
+    const currentPage = pdfViewer.currentPageNumber;
+    const audioPlayer = document.getElementById('audio-player');
+    const loadingIndicator = document.getElementById('loading-indicator');
+
+    // 函数:发送选中的文本到后端进行 TTS 处理
+  function sendPageToTTS(selectedText) {
+        loadingIndicator.style.display = 'block';
+        fetch('http://117.50.195.224:8005/text-to-speech/', {
+          method: 'POST',
+          headers: {
+            'Content-Type': 'application/json'
+          },
+          body: JSON.stringify({ user_input: selectedText })
+        })
+          .then(response => {
+            if (!response.ok) {
+              throw new Error('语音生成失败');
+            }
+            return response.blob();
+          })
+          .then(blob => {
+            const audioURL = URL.createObjectURL(blob);
+            audioPlayer.src = audioURL;
+            audioPlayer.play();
+          })
+          .catch(err => {
+            alert('生成语音失败');
+            console.error(err);
+          })
+          .finally(() => {
+            // 隐藏等待指示器
+            loadingIndicator.style.display = 'none';
+          });
+      }
+
+    PDFViewerApplication.pdfDocument.getPage(currentPage).then(function(page) {
+        page.getTextContent().then(function(textContent) {
+            // 创建一个临时的、不可见的 div 来容纳文本
+            const tempDiv = document.createElement('div');
+            tempDiv.style.position = 'fixed';
+            tempDiv.style.top = '0';
+            tempDiv.style.left = '0';
+            tempDiv.style.width = '100%';
+            tempDiv.style.height = '100%';
+            tempDiv.style.opacity = '0';
+            tempDiv.style.pointerEvents = 'none';
+            tempDiv.contentEditable = true; // 使其可选中
+
+            // 将所有文本合并为一个字符串
+            const fullText = textContent.items.map(item => item.str).join(' ');
+            // alert(fullText);
+            sendPageToTTS(fullText);
+
+        });
+    });
+});
+</script>
+
+</body>
+
+</html>

+ 623 - 0
static/web/bak/viewernov.html

@@ -0,0 +1,623 @@
+<!DOCTYPE html>
+<!--
+Copyright 2012 Mozilla Foundation
+
+Licensed under the Apache License, Version 2.0 (the "License");
+you may not use this file except in compliance with the License.
+You may obtain a copy of the License at
+
+    http://www.apache.org/licenses/LICENSE-2.0
+
+Unless required by applicable law or agreed to in writing, software
+distributed under the License is distributed on an "AS IS" BASIS,
+WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+See the License for the specific language governing permissions and
+limitations under the License.
+
+Adobe CMap resources are covered by their own copyright but the same license:
+
+    Copyright 1990-2015 Adobe Systems Incorporated.
+
+See https://github.com/adobe-type-tools/cmap-resources
+-->
+<html dir="ltr" mozdisallowselectionprint>
+  <head>
+    <meta charset="utf-8">
+    <meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=1">
+    <meta name="google" content="notranslate">
+    <title>PDF.js viewer</title>
+
+<!-- This snippet is used in production (included from viewer.html) -->
+<link rel="resource" type="application/l10n" href="locale/locale.json">
+<script src="../build/pdf.mjs" type="module"></script>
+
+    <link rel="stylesheet" href="viewer.css">
+
+  <script src="viewer.mjs" type="module"></script>
+  </head>
+
+  <body tabindex="0">
+    <div id="outerContainer">
+
+      <div id="sidebarContainer">
+        <div id="toolbarSidebar" class="toolbarHorizontalGroup">
+          <div id="toolbarSidebarLeft">
+            <div id="sidebarViewButtons" class="toolbarHorizontalGroup toggled" role="radiogroup">
+              <button id="viewThumbnail" class="toolbarButton toggled" type="button" title="Show Thumbnails" tabindex="0" data-l10n-id="pdfjs-thumbs-button" role="radio" aria-checked="true" aria-controls="thumbnailView">
+                 <span data-l10n-id="pdfjs-thumbs-button-label">Thumbnails</span>
+              </button>
+              <button id="viewOutline" class="toolbarButton" type="button" title="Show Document Outline (double-click to expand/collapse all items)" tabindex="0" data-l10n-id="pdfjs-document-outline-button" role="radio" aria-checked="false" aria-controls="outlineView">
+                 <span data-l10n-id="pdfjs-document-outline-button-label">Document Outline</span>
+              </button>
+              <button id="viewAttachments" class="toolbarButton" type="button" title="Show Attachments" tabindex="0" data-l10n-id="pdfjs-attachments-button" role="radio" aria-checked="false" aria-controls="attachmentsView">
+                 <span data-l10n-id="pdfjs-attachments-button-label">Attachments</span>
+              </button>
+              <button id="viewLayers" class="toolbarButton" type="button" title="Show Layers (double-click to reset all layers to the default state)" tabindex="0" data-l10n-id="pdfjs-layers-button" role="radio" aria-checked="false" aria-controls="layersView">
+                 <span data-l10n-id="pdfjs-layers-button-label">Layers</span>
+              </button>
+            </div>
+          </div>
+
+          <div id="toolbarSidebarRight">
+            <div id="outlineOptionsContainer" class="toolbarHorizontalGroup">
+              <div class="verticalToolbarSeparator"></div>
+
+              <button id="currentOutlineItem" class="toolbarButton" type="button" disabled="disabled" title="Find Current Outline Item" tabindex="0" data-l10n-id="pdfjs-current-outline-item-button">
+                <span data-l10n-id="pdfjs-current-outline-item-button-label">Current Outline Item</span>
+              </button>
+            </div>
+          </div>
+        </div>
+        <div id="sidebarContent">
+          <div id="thumbnailView">
+          </div>
+          <div id="outlineView" class="hidden">
+          </div>
+          <div id="attachmentsView" class="hidden">
+          </div>
+          <div id="layersView" class="hidden">
+          </div>
+        </div>
+        <div id="sidebarResizer"></div>
+      </div>  <!-- sidebarContainer -->
+
+      <div id="mainContainer">
+        <div class="toolbar">
+          <div id="toolbarContainer">
+            <div id="toolbarViewer" class="toolbarHorizontalGroup">
+              <div id="toolbarViewerLeft" class="toolbarHorizontalGroup">
+                <button id="sidebarToggleButton" class="toolbarButton" type="button" title="Toggle Sidebar" tabindex="0" data-l10n-id="pdfjs-toggle-sidebar-button" aria-expanded="false" aria-haspopup="true" aria-controls="sidebarContainer">
+                  <span data-l10n-id="pdfjs-toggle-sidebar-button-label">Toggle Sidebar</span>
+                </button>
+                <div class="toolbarButtonSpacer"></div>
+                <div class="toolbarButtonWithContainer">
+                  <button id="viewFindButton" class="toolbarButton" type="button" title="Find in Document" tabindex="0" data-l10n-id="pdfjs-findbar-button" aria-expanded="false" aria-controls="findbar">
+                    <span data-l10n-id="pdfjs-findbar-button-label">Find</span>
+                  </button>
+                  <div class="hidden doorHanger toolbarHorizontalGroup" id="findbar">
+                    <div id="findInputContainer" class="toolbarHorizontalGroup">
+                      <span class="loadingInput end toolbarHorizontalGroup">
+                        <input id="findInput" class="toolbarField" title="Find" placeholder="Find in document…" tabindex="0" data-l10n-id="pdfjs-find-input" aria-invalid="false">
+                      </span>
+                      <div class="toolbarHorizontalGroup">
+                        <button id="findPreviousButton" class="toolbarButton" type="button" title="Find the previous occurrence of the phrase" tabindex="0" data-l10n-id="pdfjs-find-previous-button">
+                          <span data-l10n-id="pdfjs-find-previous-button-label">Previous</span>
+                        </button>
+                        <div class="splitToolbarButtonSeparator"></div>
+                        <button id="findNextButton" class="toolbarButton" type="button" title="Find the next occurrence of the phrase" tabindex="0" data-l10n-id="pdfjs-find-next-button">
+                          <span data-l10n-id="pdfjs-find-next-button-label">Next</span>
+                        </button>
+                      </div>
+                    </div>
+
+                    <div id="findbarOptionsOneContainer" class="toolbarHorizontalGroup">
+                      <div class="toggleButton toolbarLabel">
+                        <input type="checkbox" id="findHighlightAll" tabindex="0" />
+                        <label for="findHighlightAll" data-l10n-id="pdfjs-find-highlight-checkbox">Highlight All</label>
+                      </div>
+                      <div class="toggleButton toolbarLabel">
+                        <input type="checkbox" id="findMatchCase" tabindex="0" />
+                        <label for="findMatchCase" data-l10n-id="pdfjs-find-match-case-checkbox-label">Match Case</label>
+                      </div>
+                    </div>
+                    <div id="findbarOptionsTwoContainer" class="toolbarHorizontalGroup">
+                      <div class="toggleButton toolbarLabel">
+                        <input type="checkbox" id="findMatchDiacritics" tabindex="0" />
+                        <label for="findMatchDiacritics" data-l10n-id="pdfjs-find-match-diacritics-checkbox-label">Match Diacritics</label>
+                      </div>
+                      <div class="toggleButton toolbarLabel">
+                        <input type="checkbox" id="findEntireWord" tabindex="0" />
+                        <label for="findEntireWord" data-l10n-id="pdfjs-find-entire-word-checkbox-label">Whole Words</label>
+                      </div>
+                    </div>
+
+                    <div id="findbarMessageContainer" class="toolbarHorizontalGroup" aria-live="polite">
+                      <span id="findResultsCount" class="toolbarLabel"></span>
+                      <span id="findMsg" class="toolbarLabel"></span>
+                    </div>
+                  </div>  <!-- findbar -->
+                </div>
+                <div class="toolbarHorizontalGroup hiddenSmallView">
+                  <button class="toolbarButton" title="Previous Page" type="button" id="previous" tabindex="0" data-l10n-id="pdfjs-previous-button">
+                    <span data-l10n-id="pdfjs-previous-button-label">Previous</span>
+                  </button>
+                  <div class="splitToolbarButtonSeparator"></div>
+                  <button class="toolbarButton" type="button" title="Next Page" id="next" tabindex="0" data-l10n-id="pdfjs-next-button">
+                    <span data-l10n-id="pdfjs-next-button-label">Next</span>
+                  </button>
+                </div>
+                <div class="toolbarHorizontalGroup">
+                  <span class="loadingInput start toolbarHorizontalGroup">
+                    <input type="number" id="pageNumber" class="toolbarField" title="Page" value="1" min="1" tabindex="0" data-l10n-id="pdfjs-page-input" autocomplete="off">
+                  </span>
+                  <span id="numPages" class="toolbarLabel"></span>
+                </div>
+              </div>
+              <div id="toolbarViewerMiddle" class="toolbarHorizontalGroup">
+                <div class="toolbarHorizontalGroup">
+                  <button id="zoomOutButton" class="toolbarButton" type="button" title="Zoom Out" tabindex="0" data-l10n-id="pdfjs-zoom-out-button">
+                    <span data-l10n-id="pdfjs-zoom-out-button-label">Zoom Out</span>
+                  </button>
+                  <div class="splitToolbarButtonSeparator"></div>
+                  <button id="zoomInButton" class="toolbarButton" type="button" title="Zoom In" tabindex="0" data-l10n-id="pdfjs-zoom-in-button">
+                    <span data-l10n-id="pdfjs-zoom-in-button-label">Zoom In</span>
+                  </button>
+                </div>
+                <span id="scaleSelectContainer" class="dropdownToolbarButton">
+                  <select id="scaleSelect" title="Zoom" tabindex="0" data-l10n-id="pdfjs-zoom-select">
+                    <option id="pageAutoOption" title="" value="auto" selected="selected" data-l10n-id="pdfjs-page-scale-auto">Automatic Zoom</option>
+                    <option id="pageActualOption" title="" value="page-actual" data-l10n-id="pdfjs-page-scale-actual">Actual Size</option>
+                    <option id="pageFitOption" title="" value="page-fit" data-l10n-id="pdfjs-page-scale-fit">Page Fit</option>
+                    <option id="pageWidthOption" title="" value="page-width" data-l10n-id="pdfjs-page-scale-width">Page Width</option>
+                    <option id="customScaleOption" title="" value="custom" disabled="disabled" hidden="true" data-l10n-id="pdfjs-page-scale-percent" data-l10n-args='{ "scale": 0 }'>0%</option>
+                    <option title="" value="0.5" data-l10n-id="pdfjs-page-scale-percent" data-l10n-args='{ "scale": 50 }'>50%</option>
+                    <option title="" value="0.75" data-l10n-id="pdfjs-page-scale-percent" data-l10n-args='{ "scale": 75 }'>75%</option>
+                    <option title="" value="1" data-l10n-id="pdfjs-page-scale-percent" data-l10n-args='{ "scale": 100 }'>100%</option>
+                    <option title="" value="1.25" data-l10n-id="pdfjs-page-scale-percent" data-l10n-args='{ "scale": 125 }'>125%</option>
+                    <option title="" value="1.5" data-l10n-id="pdfjs-page-scale-percent" data-l10n-args='{ "scale": 150 }'>150%</option>
+                    <option title="" value="2" data-l10n-id="pdfjs-page-scale-percent" data-l10n-args='{ "scale": 200 }'>200%</option>
+                    <option title="" value="3" data-l10n-id="pdfjs-page-scale-percent" data-l10n-args='{ "scale": 300 }'>300%</option>
+                    <option title="" value="4" data-l10n-id="pdfjs-page-scale-percent" data-l10n-args='{ "scale": 400 }'>400%</option>
+                  </select>
+                </span>
+              </div>
+              <div id="toolbarViewerRight" class="toolbarHorizontalGroup">
+                <div id="editorModeButtons" class="toolbarHorizontalGroup" role="radiogroup">
+                  <div id="editorHighlight" class="toolbarButtonWithContainer">
+                    <button id="editorHighlightButton" class="toolbarButton" type="button" disabled="disabled" title="Highlight" role="radio" aria-expanded="false" aria-haspopup="true" aria-controls="editorHighlightParamsToolbar" tabindex="0" data-l10n-id="pdfjs-editor-highlight-button">
+                      <span data-l10n-id="pdfjs-editor-highlight-button-label">Highlight</span>
+                    </button>
+                    <div class="editorParamsToolbar hidden doorHangerRight" id="editorHighlightParamsToolbar">
+                      <div id="highlightParamsToolbarContainer" class="editorParamsToolbarContainer">
+                        <div id="editorHighlightColorPicker" class="colorPicker">
+                          <span id="highlightColorPickerLabel" class="editorParamsLabel" data-l10n-id="pdfjs-editor-highlight-colorpicker-label">Highlight color</span>
+                        </div>
+                        <div id="editorHighlightThickness">
+                          <label for="editorFreeHighlightThickness" class="editorParamsLabel" data-l10n-id="pdfjs-editor-free-highlight-thickness-input">Thickness</label>
+                          <div class="thicknessPicker">
+                            <input type="range" id="editorFreeHighlightThickness" class="editorParamsSlider" data-l10n-id="pdfjs-editor-free-highlight-thickness-title" value="12" min="8" max="24" step="1" tabindex="0">
+                          </div>
+                        </div>
+                        <div id="editorHighlightVisibility">
+                          <div class="divider"></div>
+                          <div class="toggler">
+                            <label for="editorHighlightShowAll" class="editorParamsLabel" data-l10n-id="pdfjs-editor-highlight-show-all-button-label">Show all</label>
+                            <button id="editorHighlightShowAll" class="toggle-button" type="button" data-l10n-id="pdfjs-editor-highlight-show-all-button" aria-pressed="true" tabindex="0"></button>
+                          </div>
+                        </div>
+                      </div>
+                    </div>
+                  </div>
+                  <div id="editorFreeText" class="toolbarButtonWithContainer">
+                    <button id="editorFreeTextButton" class="toolbarButton" type="button" disabled="disabled" title="Text" role="radio" aria-expanded="false" aria-haspopup="true" aria-controls="editorFreeTextParamsToolbar" tabindex="0" data-l10n-id="pdfjs-editor-free-text-button">
+                      <span data-l10n-id="pdfjs-editor-free-text-button-label">Text</span>
+                    </button>
+                    <div class="editorParamsToolbar hidden doorHangerRight" id="editorFreeTextParamsToolbar">
+                      <div class="editorParamsToolbarContainer">
+                        <div class="editorParamsSetter">
+                          <label for="editorFreeTextColor" class="editorParamsLabel" data-l10n-id="pdfjs-editor-free-text-color-input">Color</label>
+                          <input type="color" id="editorFreeTextColor" class="editorParamsColor" tabindex="0">
+                        </div>
+                        <div class="editorParamsSetter">
+                          <label for="editorFreeTextFontSize" class="editorParamsLabel" data-l10n-id="pdfjs-editor-free-text-size-input">Size</label>
+                          <input type="range" id="editorFreeTextFontSize" class="editorParamsSlider" value="10" min="5" max="100" step="1" tabindex="0">
+                        </div>
+                      </div>
+                    </div>
+                  </div>
+                  <div id="editorInk" class="toolbarButtonWithContainer">
+                    <button id="editorInkButton" class="toolbarButton" type="button" disabled="disabled" title="Draw" role="radio" aria-expanded="false" aria-haspopup="true" aria-controls="editorInkParamsToolbar" tabindex="0" data-l10n-id="pdfjs-editor-ink-button">
+                      <span data-l10n-id="pdfjs-editor-ink-button-label">Draw</span>
+                    </button>
+                    <div class="editorParamsToolbar hidden doorHangerRight" id="editorInkParamsToolbar">
+                      <div class="editorParamsToolbarContainer">
+                        <div class="editorParamsSetter">
+                          <label for="editorInkColor" class="editorParamsLabel" data-l10n-id="pdfjs-editor-ink-color-input">Color</label>
+                          <input type="color" id="editorInkColor" class="editorParamsColor" tabindex="0">
+                        </div>
+                        <div class="editorParamsSetter">
+                          <label for="editorInkThickness" class="editorParamsLabel" data-l10n-id="pdfjs-editor-ink-thickness-input">Thickness</label>
+                          <input type="range" id="editorInkThickness" class="editorParamsSlider" value="1" min="1" max="20" step="1" tabindex="0">
+                        </div>
+                        <div class="editorParamsSetter">
+                          <label for="editorInkOpacity" class="editorParamsLabel" data-l10n-id="pdfjs-editor-ink-opacity-input">Opacity</label>
+                          <input type="range" id="editorInkOpacity" class="editorParamsSlider" value="100" min="1" max="100" step="1" tabindex="0">
+                        </div>
+                      </div>
+                    </div>
+                  </div>
+                  <div id="editorStamp" class="toolbarButtonWithContainer">
+                    <button id="editorStampButton" class="toolbarButton" type="button" disabled="disabled" title="Add or edit images" role="radio" aria-expanded="false" aria-haspopup="true" aria-controls="editorStampParamsToolbar" tabindex="0" data-l10n-id="pdfjs-editor-stamp-button">
+                      <span data-l10n-id="pdfjs-editor-stamp-button-label">Add or edit images</span>
+                    </button>
+                    <div class="editorParamsToolbar hidden doorHangerRight menu" id="editorStampParamsToolbar">
+                      <div class="menuContainer">
+                        <button id="editorStampAddImage" class="toolbarButton labeled" type="button" title="Add image" tabindex="0" data-l10n-id="pdfjs-editor-stamp-add-image-button">
+                          <span class="editorParamsLabel" data-l10n-id="pdfjs-editor-stamp-add-image-button-label">Add image</span>
+                        </button>
+                      </div>
+                    </div>
+                  </div>
+                </div>
+
+                <div id="editorModeSeparator" class="verticalToolbarSeparator"></div>
+
+                <div class="toolbarHorizontalGroup hiddenMediumView">
+                  <button id="printButton" class="toolbarButton" type="button" title="Print" tabindex="0" data-l10n-id="pdfjs-print-button">
+                    <span data-l10n-id="pdfjs-print-button-label">Print</span>
+                  </button>
+
+                  <button id="downloadButton" class="toolbarButton" type="button" title="Save" tabindex="0" data-l10n-id="pdfjs-save-button">
+                    <span data-l10n-id="pdfjs-save-button-label">Save</span>
+                  </button>
+                </div>
+
+                <div class="verticalToolbarSeparator hiddenMediumView"></div>
+
+                <div id="secondaryToolbarToggle" class="toolbarButtonWithContainer">
+                  <button id="secondaryToolbarToggleButton" class="toolbarButton" type="button" title="Tools" tabindex="0" data-l10n-id="pdfjs-tools-button" aria-expanded="false" aria-haspopup="true" aria-controls="secondaryToolbar">
+                    <span data-l10n-id="pdfjs-tools-button-label">Tools</span>
+                  </button>
+                  <div id="secondaryToolbar" class="hidden doorHangerRight menu">
+                    <div id="secondaryToolbarButtonContainer" class="menuContainer">
+                      <button id="secondaryOpenFile" class="toolbarButton labeled" type="button" title="Open File" tabindex="0" data-l10n-id="pdfjs-open-file-button">
+                        <span data-l10n-id="pdfjs-open-file-button-label">Open</span>
+                      </button>
+
+                      <div class="visibleMediumView">
+                        <button id="secondaryPrint" class="toolbarButton labeled" type="button" title="Print" tabindex="0" data-l10n-id="pdfjs-print-button">
+                          <span data-l10n-id="pdfjs-print-button-label">Print</span>
+                        </button>
+
+                        <button id="secondaryDownload" class="toolbarButton labeled" type="button" title="Save" tabindex="0" data-l10n-id="pdfjs-save-button">
+                          <span data-l10n-id="pdfjs-save-button-label">Save</span>
+                        </button>
+
+                      </div>
+
+                      <div class="horizontalToolbarSeparator"></div>
+
+                      <button id="presentationMode" class="toolbarButton labeled" type="button" title="Switch to Presentation Mode" tabindex="0" data-l10n-id="pdfjs-presentation-mode-button">
+                        <span data-l10n-id="pdfjs-presentation-mode-button-label">Presentation Mode</span>
+                      </button>
+
+                      <a href="#" id="viewBookmark" class="toolbarButton labeled" title="Current Page (View URL from Current Page)" tabindex="0" data-l10n-id="pdfjs-bookmark-button">
+                        <span data-l10n-id="pdfjs-bookmark-button-label">Current Page</span>
+                      </a>
+
+                      <div id="viewBookmarkSeparator" class="horizontalToolbarSeparator"></div>
+
+                      <button id="firstPage" class="toolbarButton labeled" type="button" title="Go to First Page" tabindex="0" data-l10n-id="pdfjs-first-page-button">
+                        <span data-l10n-id="pdfjs-first-page-button-label">Go to First Page</span>
+                      </button>
+                      <button id="lastPage" class="toolbarButton labeled" type="button" title="Go to Last Page" tabindex="0" data-l10n-id="pdfjs-last-page-button">
+                        <span data-l10n-id="pdfjs-last-page-button-label">Go to Last Page</span>
+                      </button>
+
+                      <div class="horizontalToolbarSeparator"></div>
+
+                      <button id="pageRotateCw" class="toolbarButton labeled" type="button" title="Rotate Clockwise" tabindex="0" data-l10n-id="pdfjs-page-rotate-cw-button">
+                        <span data-l10n-id="pdfjs-page-rotate-cw-button-label">Rotate Clockwise</span>
+                      </button>
+                      <button id="pageRotateCcw" class="toolbarButton labeled" type="button" title="Rotate Counterclockwise" tabindex="0" data-l10n-id="pdfjs-page-rotate-ccw-button">
+                        <span data-l10n-id="pdfjs-page-rotate-ccw-button-label">Rotate Counterclockwise</span>
+                      </button>
+
+                      <div class="horizontalToolbarSeparator"></div>
+
+                      <div id="cursorToolButtons" role="radiogroup">
+                        <button id="cursorSelectTool" class="toolbarButton labeled toggled" type="button" title="Enable Text Selection Tool" tabindex="0" data-l10n-id="pdfjs-cursor-text-select-tool-button" role="radio" aria-checked="true">
+                          <span data-l10n-id="pdfjs-cursor-text-select-tool-button-label">Text Selection Tool</span>
+                        </button>
+                        <button id="cursorHandTool" class="toolbarButton labeled" type="button" title="Enable Hand Tool" tabindex="0" data-l10n-id="pdfjs-cursor-hand-tool-button" role="radio" aria-checked="false">
+                          <span data-l10n-id="pdfjs-cursor-hand-tool-button-label">Hand Tool</span>
+                        </button>
+                      </div>
+
+                      <div class="horizontalToolbarSeparator"></div>
+
+                      <div id="scrollModeButtons" role="radiogroup">
+                        <button id="scrollPage" class="toolbarButton labeled" type="button" title="Use Page Scrolling" tabindex="0" data-l10n-id="pdfjs-scroll-page-button" role="radio" aria-checked="false">
+                          <span data-l10n-id="pdfjs-scroll-page-button-label">Page Scrolling</span>
+                        </button>
+                        <button id="scrollVertical" class="toolbarButton labeled toggled" type="button" title="Use Vertical Scrolling" tabindex="0" data-l10n-id="pdfjs-scroll-vertical-button" role="radio" aria-checked="true">
+                          <span data-l10n-id="pdfjs-scroll-vertical-button-label">Vertical Scrolling</span>
+                        </button>
+                        <button id="scrollHorizontal" class="toolbarButton labeled" type="button" title="Use Horizontal Scrolling" tabindex="0" data-l10n-id="pdfjs-scroll-horizontal-button" role="radio" aria-checked="false">
+                          <span data-l10n-id="pdfjs-scroll-horizontal-button-label">Horizontal Scrolling</span>
+                        </button>
+                        <button id="scrollWrapped" class="toolbarButton labeled" type="button" title="Use Wrapped Scrolling" tabindex="0" data-l10n-id="pdfjs-scroll-wrapped-button" role="radio" aria-checked="false">
+                          <span data-l10n-id="pdfjs-scroll-wrapped-button-label">Wrapped Scrolling</span>
+                        </button>
+                      </div>
+
+                      <div class="horizontalToolbarSeparator"></div>
+
+                      <div id="spreadModeButtons" role="radiogroup">
+                        <button id="spreadNone" class="toolbarButton labeled toggled" type="button" title="Do not join page spreads" tabindex="0" data-l10n-id="pdfjs-spread-none-button" role="radio" aria-checked="true">
+                          <span data-l10n-id="pdfjs-spread-none-button-label">No Spreads</span>
+                        </button>
+                        <button id="spreadOdd" class="toolbarButton labeled" type="button" title="Join page spreads starting with odd-numbered pages" tabindex="0" data-l10n-id="pdfjs-spread-odd-button" role="radio" aria-checked="false">
+                          <span data-l10n-id="pdfjs-spread-odd-button-label">Odd Spreads</span>
+                        </button>
+                        <button id="spreadEven" class="toolbarButton labeled" type="button" title="Join page spreads starting with even-numbered pages" tabindex="0" data-l10n-id="pdfjs-spread-even-button" role="radio" aria-checked="false">
+                          <span data-l10n-id="pdfjs-spread-even-button-label">Even Spreads</span>
+                        </button>
+                      </div>
+
+                      <div id="imageAltTextSettingsSeparator" class="horizontalToolbarSeparator hidden"></div>
+                      <button id="imageAltTextSettings" type="button" class="toolbarButton labeled hidden" title="Image alt text settings" tabindex="0" data-l10n-id="pdfjs-image-alt-text-settings-button" aria-controls="altTextSettingsDialog">
+                        <span data-l10n-id="pdfjs-image-alt-text-settings-button-label">Image alt text settings</span>
+                      </button>
+
+                      <div class="horizontalToolbarSeparator"></div>
+
+                      <button id="documentProperties" class="toolbarButton labeled" type="button" title="Document Properties…" tabindex="0" data-l10n-id="pdfjs-document-properties-button" aria-controls="documentPropertiesDialog">
+                        <span data-l10n-id="pdfjs-document-properties-button-label">Document Properties…</span>
+                      </button>
+                    </div>
+                  </div>  <!-- secondaryToolbar -->
+                </div>
+              </div>
+            </div>
+            <div id="loadingBar">
+              <div class="progress">
+                <div class="glimmer">
+                </div>
+              </div>
+            </div>
+          </div>
+        </div>
+
+        <div id="viewerContainer" tabindex="0">
+          <div id="viewer" class="pdfViewer"></div>
+        </div>
+      </div> <!-- mainContainer -->
+
+      <div id="dialogContainer">
+        <dialog id="passwordDialog">
+          <div class="row">
+            <label for="password" id="passwordText" data-l10n-id="pdfjs-password-label">Enter the password to open this PDF file:</label>
+          </div>
+          <div class="row">
+            <input type="password" id="password" class="toolbarField">
+          </div>
+          <div class="buttonRow">
+            <button id="passwordCancel" class="dialogButton" type="button"><span data-l10n-id="pdfjs-password-cancel-button">Cancel</span></button>
+            <button id="passwordSubmit" class="dialogButton" type="button"><span data-l10n-id="pdfjs-password-ok-button">OK</span></button>
+          </div>
+        </dialog>
+        <dialog id="documentPropertiesDialog">
+          <div class="row">
+            <span id="fileNameLabel" data-l10n-id="pdfjs-document-properties-file-name">File name:</span>
+            <p id="fileNameField" aria-labelledby="fileNameLabel">-</p>
+          </div>
+          <div class="row">
+            <span id="fileSizeLabel" data-l10n-id="pdfjs-document-properties-file-size">File size:</span>
+            <p id="fileSizeField" aria-labelledby="fileSizeLabel">-</p>
+          </div>
+          <div class="separator"></div>
+          <div class="row">
+            <span id="titleLabel" data-l10n-id="pdfjs-document-properties-title">Title:</span>
+            <p id="titleField" aria-labelledby="titleLabel">-</p>
+          </div>
+          <div class="row">
+            <span id="authorLabel" data-l10n-id="pdfjs-document-properties-author">Author:</span>
+            <p id="authorField" aria-labelledby="authorLabel">-</p>
+          </div>
+          <div class="row">
+            <span id="subjectLabel" data-l10n-id="pdfjs-document-properties-subject">Subject:</span>
+            <p id="subjectField" aria-labelledby="subjectLabel">-</p>
+          </div>
+          <div class="row">
+            <span id="keywordsLabel" data-l10n-id="pdfjs-document-properties-keywords">Keywords:</span>
+            <p id="keywordsField" aria-labelledby="keywordsLabel">-</p>
+          </div>
+          <div class="row">
+            <span id="creationDateLabel" data-l10n-id="pdfjs-document-properties-creation-date">Creation Date:</span>
+            <p id="creationDateField" aria-labelledby="creationDateLabel">-</p>
+          </div>
+          <div class="row">
+            <span id="modificationDateLabel" data-l10n-id="pdfjs-document-properties-modification-date">Modification Date:</span>
+            <p id="modificationDateField" aria-labelledby="modificationDateLabel">-</p>
+          </div>
+          <div class="row">
+            <span id="creatorLabel" data-l10n-id="pdfjs-document-properties-creator">Creator:</span>
+            <p id="creatorField" aria-labelledby="creatorLabel">-</p>
+          </div>
+          <div class="separator"></div>
+          <div class="row">
+            <span id="producerLabel" data-l10n-id="pdfjs-document-properties-producer">PDF Producer:</span>
+            <p id="producerField" aria-labelledby="producerLabel">-</p>
+          </div>
+          <div class="row">
+            <span id="versionLabel" data-l10n-id="pdfjs-document-properties-version">PDF Version:</span>
+            <p id="versionField" aria-labelledby="versionLabel">-</p>
+          </div>
+          <div class="row">
+            <span id="pageCountLabel" data-l10n-id="pdfjs-document-properties-page-count">Page Count:</span>
+            <p id="pageCountField" aria-labelledby="pageCountLabel">-</p>
+          </div>
+          <div class="row">
+            <span id="pageSizeLabel" data-l10n-id="pdfjs-document-properties-page-size">Page Size:</span>
+            <p id="pageSizeField" aria-labelledby="pageSizeLabel">-</p>
+          </div>
+          <div class="separator"></div>
+          <div class="row">
+            <span id="linearizedLabel" data-l10n-id="pdfjs-document-properties-linearized">Fast Web View:</span>
+            <p id="linearizedField" aria-labelledby="linearizedLabel">-</p>
+          </div>
+          <div class="buttonRow">
+            <button id="documentPropertiesClose" class="dialogButton" type="button"><span data-l10n-id="pdfjs-document-properties-close-button">Close</span></button>
+          </div>
+        </dialog>
+        <dialog class="dialog altText" id="altTextDialog" aria-labelledby="dialogLabel" aria-describedby="dialogDescription">
+          <div id="altTextContainer" class="mainContainer">
+            <div id="overallDescription">
+              <span id="dialogLabel" data-l10n-id="pdfjs-editor-alt-text-dialog-label" class="title">Choose an option</span>
+              <span id="dialogDescription" data-l10n-id="pdfjs-editor-alt-text-dialog-description">
+                Alt text (alternative text) helps when people can’t see the image or when it doesn’t load.
+              </span>
+            </div>
+            <div id="addDescription">
+              <div class="radio">
+                <div class="radioButton">
+                  <input type="radio" id="descriptionButton" name="altTextOption" tabindex="0" aria-describedby="descriptionAreaLabel" checked>
+                  <label for="descriptionButton" data-l10n-id="pdfjs-editor-alt-text-add-description-label">Add a description</label>
+                </div>
+                <div class="radioLabel">
+                  <span id="descriptionAreaLabel" data-l10n-id="pdfjs-editor-alt-text-add-description-description">
+                    Aim for 1-2 sentences that describe the subject, setting, or actions.
+                  </span>
+                </div>
+              </div>
+              <div class="descriptionArea">
+                <textarea id="descriptionTextarea" placeholder="For example, “A young man sits down at a table to eat a meal”" aria-labelledby="descriptionAreaLabel" data-l10n-id="pdfjs-editor-alt-text-textarea" tabindex="0"></textarea>
+              </div>
+            </div>
+            <div id="markAsDecorative">
+              <div class="radio">
+                <div class="radioButton">
+                  <input type="radio" id="decorativeButton" name="altTextOption" aria-describedby="decorativeLabel">
+                  <label for="decorativeButton" data-l10n-id="pdfjs-editor-alt-text-mark-decorative-label">Mark as decorative</label>
+                </div>
+                <div class="radioLabel">
+                  <span id="decorativeLabel" data-l10n-id="pdfjs-editor-alt-text-mark-decorative-description">
+                    This is used for ornamental images, like borders or watermarks.
+                  </span>
+                </div>
+              </div>
+            </div>
+            <div id="buttons">
+              <button id="altTextCancel" class="secondaryButton" type="button" tabindex="0"><span data-l10n-id="pdfjs-editor-alt-text-cancel-button">Cancel</span></button>
+              <button id="altTextSave" class="primaryButton" type="button" tabindex="0"><span data-l10n-id="pdfjs-editor-alt-text-save-button">Save</span></button>
+            </div>
+          </div>
+        </dialog>
+        <dialog class="dialog newAltText" id="newAltTextDialog" aria-labelledby="newAltTextTitle" aria-describedby="newAltTextDescription" tabindex="0">
+          <div id="newAltTextContainer" class="mainContainer">
+            <div class="title">
+              <span id="newAltTextTitle" data-l10n-id="pdfjs-editor-new-alt-text-dialog-edit-label" role="sectionhead" tabindex="0">Edit alt text (image description)</span>
+            </div>
+            <div id="mainContent">
+              <div id="descriptionAndSettings">
+                <div id="descriptionInstruction">
+                  <div id="newAltTextDescriptionContainer">
+                    <div class="altTextSpinner" role="status" aria-live="polite"></div>
+                    <textarea id="newAltTextDescriptionTextarea" placeholder="Write your description here…" aria-labelledby="descriptionAreaLabel" data-l10n-id="pdfjs-editor-new-alt-text-textarea" tabindex="0"></textarea>
+                  </div>
+                  <span id="newAltTextDescription" role="note" data-l10n-id="pdfjs-editor-new-alt-text-description">Short description for people who can’t see the image or when the image doesn’t load.</span>
+                  <div id="newAltTextDisclaimer" role="note"><div><span data-l10n-id="pdfjs-editor-new-alt-text-disclaimer1">This alt text was created automatically and may be inaccurate.</span> <a href="https://support.mozilla.org/en-US/kb/pdf-alt-text" target="_blank" rel="noopener noreferrer" id="newAltTextLearnMore" data-l10n-id="pdfjs-editor-new-alt-text-disclaimer-learn-more-url" tabindex="0">Learn more</a></div></div>
+                </div>
+                <div id="newAltTextCreateAutomatically" class="toggler">
+                  <button id="newAltTextCreateAutomaticallyButton" class="toggle-button" type="button" aria-pressed="true" tabindex="0"></button>
+                  <label for="newAltTextCreateAutomaticallyButton" class="togglerLabel" data-l10n-id="pdfjs-editor-new-alt-text-create-automatically-button-label">Create alt text automatically</label>
+                </div>
+                <div id="newAltTextDownloadModel" class="hidden">
+                  <span id="newAltTextDownloadModelDescription" data-l10n-id="pdfjs-editor-new-alt-text-ai-model-downloading-progress" aria-valuemin="0" data-l10n-args='{ "totalSize": 0, "downloadedSize": 0 }'>Downloading alt text AI model (0 of 0 MB)</span>
+                </div>
+              </div>
+              <div id="newAltTextImagePreview"></div>
+            </div>
+            <div id="newAltTextError" class="messageBar">
+              <div>
+                <div>
+                  <span class="title" data-l10n-id="pdfjs-editor-new-alt-text-error-title">Couldn’t create alt text automatically</span>
+                  <span  class="description" data-l10n-id="pdfjs-editor-new-alt-text-error-description">Please write your own alt text or try again later.</span>
+                </div>
+                <button id="newAltTextCloseButton" class="closeButton" type="button" tabindex="0" title="Close"><span data-l10n-id="pdfjs-editor-new-alt-text-error-close-button">Close</span></button>
+              </div>
+            </div>
+            <div id="newAltTextButtons" class="dialogButtonsGroup">
+              <button id="newAltTextCancel" type="button" class="secondaryButton hidden" tabindex="0"><span data-l10n-id="pdfjs-editor-alt-text-cancel-button">Cancel</span></button>
+              <button id="newAltTextNotNow" type="button" class="secondaryButton" tabindex="0"><span data-l10n-id="pdfjs-editor-new-alt-text-not-now-button">Not now</span></button>
+              <button id="newAltTextSave" type="button" class="primaryButton" tabindex="0"><span data-l10n-id="pdfjs-editor-alt-text-save-button">Save</span></button>
+            </div>
+          </div>
+        </dialog>
+
+        <dialog class="dialog" id="altTextSettingsDialog" aria-labelledby="altTextSettingsTitle">
+          <div id="altTextSettingsContainer" class="mainContainer">
+            <div class="title">
+              <span id="altTextSettingsTitle" data-l10n-id="pdfjs-editor-alt-text-settings-dialog-label" role="sectionhead" tabindex="0" class="title">Image alt text settings</span>
+            </div>
+            <div id="automaticAltText">
+              <span data-l10n-id="pdfjs-editor-alt-text-settings-automatic-title">Automatic alt text</span>
+              <div id="automaticSettings">
+                <div id="createModelSetting">
+                  <div class="toggler">
+                    <button id="createModelButton" type="button" class="toggle-button" aria-pressed="true" tabindex="0"></button>
+                    <label for="createModelButton" class="togglerLabel" data-l10n-id="pdfjs-editor-alt-text-settings-create-model-button-label">Create alt text automatically</label>
+                  </div>
+                  <div id="createModelDescription" class="description">
+                    <span data-l10n-id="pdfjs-editor-alt-text-settings-create-model-description">Suggests descriptions to help people who can’t see the image or when the image doesn’t load.</span> <a href="https://support.mozilla.org/en-US/kb/pdf-alt-text" target="_blank" rel="noopener noreferrer" id="altTextSettingsLearnMore" data-l10n-id="pdfjs-editor-new-alt-text-disclaimer-learn-more-url" tabindex="0">Learn more</a>
+                  </div>
+                </div>
+                <div id="aiModelSettings">
+                  <div>
+                    <span data-l10n-id="pdfjs-editor-alt-text-settings-download-model-label" data-l10n-args='{ "totalSize": 180 }'>Alt text AI model (180MB)</span>
+                    <div id="aiModelDescription" class="description">
+                      <span data-l10n-id="pdfjs-editor-alt-text-settings-ai-model-description">Runs locally on your device so your data stays private. Required for automatic alt text.</span>
+                    </div>
+                  </div>
+                  <button id="deleteModelButton" type="button" class="secondaryButton" tabindex="0"><span data-l10n-id="pdfjs-editor-alt-text-settings-delete-model-button">Delete</span></button>
+                  <button id="downloadModelButton" type="button" class="secondaryButton" tabindex="0"><span data-l10n-id="pdfjs-editor-alt-text-settings-download-model-button">Download</span></button>
+                </div>
+              </div>
+            </div>
+            <div class="dialogSeparator"></div>
+            <div id="altTextEditor">
+              <span data-l10n-id="pdfjs-editor-alt-text-settings-editor-title">Alt text editor</span>
+              <div id="showAltTextEditor">
+                <div class="toggler">
+                  <button id="showAltTextDialogButton" type="button" class="toggle-button" aria-pressed="true" tabindex="0"></button>
+                  <label for="showAltTextDialogButton" class="togglerLabel" data-l10n-id="pdfjs-editor-alt-text-settings-show-dialog-button-label">Show alt text editor right away when adding an image</label>
+                </div>
+                <div id="showAltTextDialogDescription" class="description">
+                  <span data-l10n-id="pdfjs-editor-alt-text-settings-show-dialog-description">Helps you make sure all your images have alt text.</span>
+                </div>
+              </div>
+            </div>
+            <div id="buttons" class="dialogButtonsGroup">
+              <button id="altTextSettingsCloseButton" type="button" class="primaryButton" tabindex="0"><span data-l10n-id="pdfjs-editor-alt-text-settings-close-button">Close</span></button>
+            </div>
+          </div>
+        </dialog>
+        <dialog id="printServiceDialog" style="min-width: 200px;">
+          <div class="row">
+            <span data-l10n-id="pdfjs-print-progress-message">Preparing document for printing…</span>
+          </div>
+          <div class="row">
+            <progress value="0" max="100"></progress>
+            <span data-l10n-id="pdfjs-print-progress-percent" data-l10n-args='{ "progress": 0 }' class="relative-progress">0%</span>
+          </div>
+          <div class="buttonRow">
+            <button id="printCancel" class="dialogButton" type="button"><span data-l10n-id="pdfjs-print-progress-close-button">Cancel</span></button>
+          </div>
+        </dialog>
+      </div>  <!-- dialogContainer -->
+
+    </div> <!-- outerContainer -->
+    <div id="printContainer"></div>
+  </body>
+</html>

BIN
static/web/cmaps/78-EUC-H.bcmap


BIN
static/web/cmaps/78-EUC-V.bcmap


BIN
static/web/cmaps/78-H.bcmap


BIN
static/web/cmaps/78-RKSJ-H.bcmap


BIN
static/web/cmaps/78-RKSJ-V.bcmap


BIN
static/web/cmaps/78-V.bcmap


BIN
static/web/cmaps/78ms-RKSJ-H.bcmap


BIN
static/web/cmaps/78ms-RKSJ-V.bcmap


BIN
static/web/cmaps/83pv-RKSJ-H.bcmap


BIN
static/web/cmaps/90ms-RKSJ-H.bcmap


BIN
static/web/cmaps/90ms-RKSJ-V.bcmap


BIN
static/web/cmaps/90msp-RKSJ-H.bcmap


BIN
static/web/cmaps/90msp-RKSJ-V.bcmap


BIN
static/web/cmaps/90pv-RKSJ-H.bcmap


BIN
static/web/cmaps/90pv-RKSJ-V.bcmap


BIN
static/web/cmaps/Add-H.bcmap


BIN
static/web/cmaps/Add-RKSJ-H.bcmap


BIN
static/web/cmaps/Add-RKSJ-V.bcmap


BIN
static/web/cmaps/Add-V.bcmap


BIN
static/web/cmaps/Adobe-CNS1-0.bcmap


BIN
static/web/cmaps/Adobe-CNS1-1.bcmap


BIN
static/web/cmaps/Adobe-CNS1-2.bcmap


BIN
static/web/cmaps/Adobe-CNS1-3.bcmap


BIN
static/web/cmaps/Adobe-CNS1-4.bcmap


BIN
static/web/cmaps/Adobe-CNS1-5.bcmap


BIN
static/web/cmaps/Adobe-CNS1-6.bcmap


BIN
static/web/cmaps/Adobe-CNS1-UCS2.bcmap


BIN
static/web/cmaps/Adobe-GB1-0.bcmap


BIN
static/web/cmaps/Adobe-GB1-1.bcmap


BIN
static/web/cmaps/Adobe-GB1-2.bcmap


BIN
static/web/cmaps/Adobe-GB1-3.bcmap


BIN
static/web/cmaps/Adobe-GB1-4.bcmap


BIN
static/web/cmaps/Adobe-GB1-5.bcmap


BIN
static/web/cmaps/Adobe-GB1-UCS2.bcmap


BIN
static/web/cmaps/Adobe-Japan1-0.bcmap


BIN
static/web/cmaps/Adobe-Japan1-1.bcmap


BIN
static/web/cmaps/Adobe-Japan1-2.bcmap


BIN
static/web/cmaps/Adobe-Japan1-3.bcmap


BIN
static/web/cmaps/Adobe-Japan1-4.bcmap


BIN
static/web/cmaps/Adobe-Japan1-5.bcmap


BIN
static/web/cmaps/Adobe-Japan1-6.bcmap


BIN
static/web/cmaps/Adobe-Japan1-UCS2.bcmap


BIN
static/web/cmaps/Adobe-Korea1-0.bcmap


BIN
static/web/cmaps/Adobe-Korea1-1.bcmap


BIN
static/web/cmaps/Adobe-Korea1-2.bcmap


BIN
static/web/cmaps/Adobe-Korea1-UCS2.bcmap


BIN
static/web/cmaps/B5-H.bcmap


BIN
static/web/cmaps/B5-V.bcmap


BIN
static/web/cmaps/B5pc-H.bcmap


BIN
static/web/cmaps/B5pc-V.bcmap


BIN
static/web/cmaps/CNS-EUC-H.bcmap


BIN
static/web/cmaps/CNS-EUC-V.bcmap


BIN
static/web/cmaps/CNS1-H.bcmap


BIN
static/web/cmaps/CNS1-V.bcmap


BIN
static/web/cmaps/CNS2-H.bcmap


+ 3 - 0
static/web/cmaps/CNS2-V.bcmap

@@ -0,0 +1,3 @@
+àRCopyright 1990-2009 Adobe Systems Incorporated.
+All rights reserved.
+See ./LICENSEáCNS2-H

BIN
static/web/cmaps/ETHK-B5-H.bcmap


BIN
static/web/cmaps/ETHK-B5-V.bcmap


BIN
static/web/cmaps/ETen-B5-H.bcmap


BIN
static/web/cmaps/ETen-B5-V.bcmap


+ 3 - 0
static/web/cmaps/ETenms-B5-H.bcmap

@@ -0,0 +1,3 @@
+àRCopyright 1990-2009 Adobe Systems Incorporated.
+All rights reserved.
+See ./LICENSEá	ETen-B5-H` ^

BIN
static/web/cmaps/ETenms-B5-V.bcmap


BIN
static/web/cmaps/EUC-H.bcmap


BIN
static/web/cmaps/EUC-V.bcmap


BIN
static/web/cmaps/Ext-H.bcmap


BIN
static/web/cmaps/Ext-RKSJ-H.bcmap


BIN
static/web/cmaps/Ext-RKSJ-V.bcmap


BIN
static/web/cmaps/Ext-V.bcmap


BIN
static/web/cmaps/GB-EUC-H.bcmap


BIN
static/web/cmaps/GB-EUC-V.bcmap


+ 4 - 0
static/web/cmaps/GB-H.bcmap

@@ -0,0 +1,4 @@
+àRCopyright 1990-2009 Adobe Systems Incorporated.
+All rights reserved.
+See ./LICENSE!!�º]aX!!]`�21�>	�p�z�$]‚�"R‚d�-Uƒ7�*„
4„%�+ „Z „{�/…%…<�9K…b�1]†.�"‡‰`]‡,�"]ˆ
+�"]ˆh�"]‰F�"]Š$�"]‹�"]‹`�"]Œ>�"]��"]�z�"]ŽX�"]�6�"]��"]�r�"]‘P�"]’.�"]“�"]“j�"]”H�"]•&�"]–�"]–b�"]—@�"]˜�"]˜|�"]™Z�"]š8�"]›�"]›t�"]œR�"]�0�"]ž�"]žl�"]ŸJ�"] (�"]¡�"]¡d�"]¢B�"]£ �"X£~�']¤W�"]¥5�"]¦�"]¦q�"]§O�"]¨-�"]©�"]©i�"]ªG�"]«%�"]¬�"]¬a�"]­?�"]®�"]®{�"]¯Y�"]°7�"]±�"]±s�"]²Q�"]³/�"]´
�"]´k�"]µI�"]¶'�"]·�"]·c�"]¸A�"]¹�"]¹}�"]º[�"]»9

BIN
static/web/cmaps/GB-V.bcmap


BIN
static/web/cmaps/GBK-EUC-H.bcmap


BIN
static/web/cmaps/GBK-EUC-V.bcmap


BIN
static/web/cmaps/GBK2K-H.bcmap


BIN
static/web/cmaps/GBK2K-V.bcmap


BIN
static/web/cmaps/GBKp-EUC-H.bcmap


BIN
static/web/cmaps/GBKp-EUC-V.bcmap


BIN
static/web/cmaps/GBT-EUC-H.bcmap


BIN
static/web/cmaps/GBT-EUC-V.bcmap


BIN
static/web/cmaps/GBT-H.bcmap


Some files were not shown because too many files changed in this diff