viewer.htmlbak25621 85 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059106010611062106310641065106610671068106910701071107210731074107510761077107810791080108110821083108410851086108710881089109010911092109310941095109610971098109911001101110211031104110511061107110811091110111111121113111411151116111711181119112011211122112311241125112611271128112911301131113211331134113511361137113811391140114111421143114411451146114711481149115011511152115311541155115611571158115911601161116211631164116511661167116811691170117111721173117411751176117711781179118011811182118311841185118611871188118911901191119211931194119511961197119811991200120112021203120412051206120712081209121012111212121312141215121612171218121912201221122212231224122512261227122812291230123112321233123412351236123712381239124012411242124312441245124612471248124912501251125212531254125512561257125812591260126112621263126412651266126712681269127012711272127312741275127612771278127912801281128212831284128512861287128812891290129112921293129412951296129712981299130013011302130313041305130613071308130913101311131213131314131513161317131813191320132113221323132413251326132713281329133013311332133313341335133613371338133913401341134213431344134513461347134813491350135113521353135413551356135713581359136013611362136313641365136613671368136913701371137213731374137513761377137813791380138113821383138413851386138713881389139013911392139313941395139613971398139914001401140214031404140514061407140814091410
  1. <!DOCTYPE html>
  2. <html dir="ltr" mozdisallowselectionprint>
  3. <head>
  4. <meta charset="utf-8">
  5. <meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=1">
  6. <meta name="google" content="notranslate">
  7. <title>PDF.js viewer</title>
  8. <!-- This snippet is used in production (included from viewer.html) -->
  9. <link rel="resource" type="application/l10n" href="locale/locale.json">
  10. <script src="../build/pdf.mjs" type="module"></script>
  11. <link rel="stylesheet" href="viewer.css">
  12. <script src="viewer.mjs" type="module"></script>
  13. <style>
  14. /* 模态窗口样式 */
  15. .modal {
  16. display: none;
  17. position: fixed;
  18. z-index: 10002;
  19. left: 50%;
  20. top: 50%;
  21. transform: translate(-50%, -50%);
  22. width: 500px;
  23. background-color: white;
  24. border: 1px solid #ccc;
  25. border-radius: 5px;
  26. padding: 20px;
  27. box-shadow: 0 5px 15px rgba(0, 0, 0, 0.3);
  28. }
  29. .modal-overlay {
  30. display: none;
  31. position: fixed;
  32. top: 0;
  33. left: 0;
  34. width: 100%;
  35. height: 100%;
  36. background-color: rgba(0, 0, 0, 0.5);
  37. z-index: 10001;
  38. }
  39. .button {
  40. padding: 10px 20px;
  41. background-color: #007bff;
  42. color: white;
  43. border: none;
  44. border-radius: 5px;
  45. cursor: pointer;
  46. }
  47. .button-secondary {
  48. background-color: #6c757d;
  49. }
  50. .button-success {
  51. background-color: #28a745;
  52. }
  53. .button-light {
  54. background-color: #ded520;
  55. }
  56. .button-danger {
  57. background-color: #dc3545;
  58. }
  59. </style>
  60. </head>
  61. <body tabindex="0">
  62. <div id="loading-indicator"
  63. style="display: none; position: fixed; top: 20px; left: 20px; background: rgba(0,0,0,0.7); color: #fff; padding: 10px; border-radius: 5px;">
  64. 正在生成语音,请稍候...
  65. </div>
  66. <audio id="audio-player" controls
  67. style="position: fixed; width: 190px; height: 50px; bottom: 10px; left: 10px; z-index: 1000;"></audio>
  68. <button id="uploadButton" style="
  69. position: fixed;
  70. bottom: 20px;
  71. right: 20px;
  72. z-index: 1000;
  73. padding: 10px 20px;
  74. background-color: #007bff;
  75. color: white;
  76. border: none;
  77. border-radius: 5px;
  78. cursor: pointer;">
  79. 上传 PDF
  80. </button>
  81. <input type="file" id="pdfInput" accept="application/pdf" style="display: none;" />
  82. <div id="filenameModal" class="modal">
  83. <h3>输入保存的文件名</h3>
  84. <input type="text" id="customFileName" placeholder="不含扩展名"
  85. style="width: 100%; padding: 5px; margin-top: 10px;" />
  86. <div style="margin-top: 20px; text-align: right;">
  87. <button id="cancelUpload" class="button button-secondary">取消</button>
  88. <button id="confirmUpload" class="button button-success" style="margin-left: 10px;">确认</button>
  89. </div>
  90. </div>
  91. <div id="modalOverlay" class="modal-overlay"></div>
  92. <button id="openDirectoryButton" class="button" style="
  93. position: fixed;
  94. bottom: 70px;
  95. right: 20px;
  96. z-index: 1000;">
  97. 打开目录
  98. </button>
  99. <button id="select-all-text" class="button button-light" style="
  100. position: fixed;
  101. bottom: 170px;
  102. right: 20px;
  103. z-index: 1000;
  104. color: black;">
  105. 阅读整页
  106. </button>
  107. <button id="toggle-text-select" class="button button-success" style="
  108. position: fixed;
  109. bottom: 120px;
  110. right: 20px;
  111. z-index: 1000;">
  112. 文本选择
  113. </button>
  114. <div id="directoryModal" class="modal">
  115. <h2>已上传的 PDF 文档</h2>
  116. <ul id="pdfList" style="list-style-type: none; padding: 0;"></ul>
  117. <button id="closeDirectoryButton" class="button button-danger"
  118. style="margin-top: 20px; width: 100%;">关闭</button>
  119. </div>
  120. <div id="outerContainer">
  121. <div id="outerContainer">
  122. <div id="sidebarContainer">
  123. <div id="toolbarSidebar" class="toolbarHorizontalGroup">
  124. <div id="toolbarSidebarLeft">
  125. <div id="sidebarViewButtons" class="toolbarHorizontalGroup toggled" role="radiogroup">
  126. <button id="viewThumbnail" class="toolbarButton toggled" type="button"
  127. title="Show Thumbnails" tabindex="0" data-l10n-id="pdfjs-thumbs-button" role="radio"
  128. aria-checked="true" aria-controls="thumbnailView">
  129. <span data-l10n-id="pdfjs-thumbs-button-label">Thumbnails</span>
  130. </button>
  131. <button id="viewOutline" class="toolbarButton" type="button"
  132. title="Show Document Outline (double-click to expand/collapse all items)" tabindex="0"
  133. data-l10n-id="pdfjs-document-outline-button" role="radio" aria-checked="false"
  134. aria-controls="outlineView">
  135. <span data-l10n-id="pdfjs-document-outline-button-label">Document Outline</span>
  136. </button>
  137. <button id="viewAttachments" class="toolbarButton" type="button" title="Show Attachments"
  138. tabindex="0" data-l10n-id="pdfjs-attachments-button" role="radio" aria-checked="false"
  139. aria-controls="attachmentsView">
  140. <span data-l10n-id="pdfjs-attachments-button-label">Attachments</span>
  141. </button>
  142. <button id="viewLayers" class="toolbarButton" type="button"
  143. title="Show Layers (double-click to reset all layers to the default state)" tabindex="0"
  144. data-l10n-id="pdfjs-layers-button" role="radio" aria-checked="false"
  145. aria-controls="layersView">
  146. <span data-l10n-id="pdfjs-layers-button-label">Layers</span>
  147. </button>
  148. </div>
  149. </div>
  150. <div id="toolbarSidebarRight">
  151. <div id="outlineOptionsContainer" class="toolbarHorizontalGroup">
  152. <div class="verticalToolbarSeparator"></div>
  153. <button id="currentOutlineItem" class="toolbarButton" type="button" disabled="disabled"
  154. title="Find Current Outline Item" tabindex="0"
  155. data-l10n-id="pdfjs-current-outline-item-button">
  156. <span data-l10n-id="pdfjs-current-outline-item-button-label">Current Outline Item</span>
  157. </button>
  158. </div>
  159. </div>
  160. </div>
  161. <div id="sidebarContent">
  162. <div id="thumbnailView">
  163. </div>
  164. <div id="outlineView" class="hidden">
  165. </div>
  166. <div id="attachmentsView" class="hidden">
  167. </div>
  168. <div id="layersView" class="hidden">
  169. </div>
  170. </div>
  171. <div id="sidebarResizer"></div>
  172. </div> <!-- sidebarContainer -->
  173. <div id="mainContainer">
  174. <div class="toolbar">
  175. <div id="toolbarContainer">
  176. <div id="toolbarViewer" class="toolbarHorizontalGroup">
  177. <div id="toolbarViewerLeft" class="toolbarHorizontalGroup">
  178. <button id="sidebarToggleButton" class="toolbarButton" type="button"
  179. title="Toggle Sidebar" tabindex="0" data-l10n-id="pdfjs-toggle-sidebar-button"
  180. aria-expanded="false" aria-haspopup="true" aria-controls="sidebarContainer">
  181. <span data-l10n-id="pdfjs-toggle-sidebar-button-label">Toggle Sidebar</span>
  182. </button>
  183. <div class="toolbarButtonSpacer"></div>
  184. <div class="toolbarButtonWithContainer">
  185. <button id="viewFindButton" class="toolbarButton" type="button"
  186. title="Find in Document" tabindex="0" data-l10n-id="pdfjs-findbar-button"
  187. aria-expanded="false" aria-controls="findbar">
  188. <span data-l10n-id="pdfjs-findbar-button-label">Find</span>
  189. </button>
  190. <div class="hidden doorHanger toolbarHorizontalGroup" id="findbar">
  191. <div id="findInputContainer" class="toolbarHorizontalGroup">
  192. <span class="loadingInput end toolbarHorizontalGroup">
  193. <input id="findInput" class="toolbarField" title="Find"
  194. placeholder="Find in document…" tabindex="0"
  195. data-l10n-id="pdfjs-find-input" aria-invalid="false">
  196. </span>
  197. <div class="toolbarHorizontalGroup">
  198. <button id="findPreviousButton" class="toolbarButton" type="button"
  199. title="Find the previous occurrence of the phrase" tabindex="0"
  200. data-l10n-id="pdfjs-find-previous-button">
  201. <span
  202. data-l10n-id="pdfjs-find-previous-button-label">Previous</span>
  203. </button>
  204. <div class="splitToolbarButtonSeparator"></div>
  205. <button id="findNextButton" class="toolbarButton" type="button"
  206. title="Find the next occurrence of the phrase" tabindex="0"
  207. data-l10n-id="pdfjs-find-next-button">
  208. <span data-l10n-id="pdfjs-find-next-button-label">Next</span>
  209. </button>
  210. </div>
  211. </div>
  212. <div id="findbarOptionsOneContainer" class="toolbarHorizontalGroup">
  213. <div class="toggleButton toolbarLabel">
  214. <input type="checkbox" id="findHighlightAll" tabindex="0" />
  215. <label for="findHighlightAll"
  216. data-l10n-id="pdfjs-find-highlight-checkbox">Highlight All</label>
  217. </div>
  218. <div class="toggleButton toolbarLabel">
  219. <input type="checkbox" id="findMatchCase" tabindex="0" />
  220. <label for="findMatchCase"
  221. data-l10n-id="pdfjs-find-match-case-checkbox-label">Match
  222. Case</label>
  223. </div>
  224. </div>
  225. <div id="findbarOptionsTwoContainer" class="toolbarHorizontalGroup">
  226. <div class="toggleButton toolbarLabel">
  227. <input type="checkbox" id="findMatchDiacritics" tabindex="0" />
  228. <label for="findMatchDiacritics"
  229. data-l10n-id="pdfjs-find-match-diacritics-checkbox-label">Match
  230. Diacritics</label>
  231. </div>
  232. <div class="toggleButton toolbarLabel">
  233. <input type="checkbox" id="findEntireWord" tabindex="0" />
  234. <label for="findEntireWord"
  235. data-l10n-id="pdfjs-find-entire-word-checkbox-label">Whole
  236. Words</label>
  237. </div>
  238. </div>
  239. <div id="findbarMessageContainer" class="toolbarHorizontalGroup"
  240. aria-live="polite">
  241. <span id="findResultsCount" class="toolbarLabel"></span>
  242. <span id="findMsg" class="toolbarLabel"></span>
  243. </div>
  244. </div> <!-- findbar -->
  245. </div>
  246. <div class="toolbarHorizontalGroup hiddenSmallView">
  247. <button class="toolbarButton" title="Previous Page" type="button" id="previous"
  248. tabindex="0" data-l10n-id="pdfjs-previous-button">
  249. <span data-l10n-id="pdfjs-previous-button-label">Previous</span>
  250. </button>
  251. <div class="splitToolbarButtonSeparator"></div>
  252. <button class="toolbarButton" type="button" title="Next Page" id="next" tabindex="0"
  253. data-l10n-id="pdfjs-next-button">
  254. <span data-l10n-id="pdfjs-next-button-label">Next</span>
  255. </button>
  256. </div>
  257. <div class="toolbarHorizontalGroup">
  258. <span class="loadingInput start toolbarHorizontalGroup">
  259. <input type="number" id="pageNumber" class="toolbarField" title="Page" value="1"
  260. min="1" tabindex="0" data-l10n-id="pdfjs-page-input" autocomplete="off">
  261. </span>
  262. <span id="numPages" class="toolbarLabel"></span>
  263. </div>
  264. </div>
  265. <div id="toolbarViewerMiddle" class="toolbarHorizontalGroup">
  266. <div class="toolbarHorizontalGroup">
  267. <button id="zoomOutButton" class="toolbarButton" type="button" title="Zoom Out"
  268. tabindex="0" data-l10n-id="pdfjs-zoom-out-button">
  269. <span data-l10n-id="pdfjs-zoom-out-button-label">Zoom Out</span>
  270. </button>
  271. <div class="splitToolbarButtonSeparator"></div>
  272. <button id="zoomInButton" class="toolbarButton" type="button" title="Zoom In"
  273. tabindex="0" data-l10n-id="pdfjs-zoom-in-button">
  274. <span data-l10n-id="pdfjs-zoom-in-button-label">Zoom In</span>
  275. </button>
  276. </div>
  277. <span id="scaleSelectContainer" class="dropdownToolbarButton">
  278. <select id="scaleSelect" title="Zoom" tabindex="0" data-l10n-id="pdfjs-zoom-select">
  279. <option id="pageAutoOption" title="" value="auto" selected="selected"
  280. data-l10n-id="pdfjs-page-scale-auto">Automatic Zoom</option>
  281. <option id="pageActualOption" title="" value="page-actual"
  282. data-l10n-id="pdfjs-page-scale-actual">
  283. Actual Size</option>
  284. <option id="pageFitOption" title="" value="page-fit"
  285. data-l10n-id="pdfjs-page-scale-fit">Page Fit
  286. </option>
  287. <option id="pageWidthOption" title="" value="page-width"
  288. data-l10n-id="pdfjs-page-scale-width">Page
  289. Width</option>
  290. <option id="customScaleOption" title="" value="custom" disabled="disabled"
  291. hidden="true" data-l10n-id="pdfjs-page-scale-percent"
  292. data-l10n-args='{ "scale": 0 }'>0%</option>
  293. <option title="" value="0.5" data-l10n-id="pdfjs-page-scale-percent"
  294. data-l10n-args='{ "scale": 50 }'>
  295. 50%</option>
  296. <option title="" value="0.75" data-l10n-id="pdfjs-page-scale-percent"
  297. data-l10n-args='{ "scale": 75 }'>
  298. 75%</option>
  299. <option title="" value="1" data-l10n-id="pdfjs-page-scale-percent"
  300. data-l10n-args='{ "scale": 100 }'>
  301. 100%</option>
  302. <option title="" value="1.25" data-l10n-id="pdfjs-page-scale-percent"
  303. data-l10n-args='{ "scale": 125 }'>
  304. 125%</option>
  305. <option title="" value="1.5" data-l10n-id="pdfjs-page-scale-percent"
  306. data-l10n-args='{ "scale": 150 }'>
  307. 150%</option>
  308. <option title="" value="2" data-l10n-id="pdfjs-page-scale-percent"
  309. data-l10n-args='{ "scale": 200 }'>
  310. 200%</option>
  311. <option title="" value="3" data-l10n-id="pdfjs-page-scale-percent"
  312. data-l10n-args='{ "scale": 300 }'>
  313. 300%</option>
  314. <option title="" value="4" data-l10n-id="pdfjs-page-scale-percent"
  315. data-l10n-args='{ "scale": 400 }'>
  316. 400%</option>
  317. </select>
  318. </span>
  319. </div>
  320. <div id="toolbarViewerRight" class="toolbarHorizontalGroup">
  321. <div id="editorModeButtons" class="toolbarHorizontalGroup" role="radiogroup">
  322. <div id="editorHighlight" class="toolbarButtonWithContainer">
  323. <button id="editorHighlightButton" class="toolbarButton" type="button"
  324. disabled="disabled" title="Highlight" role="radio" aria-expanded="false"
  325. aria-haspopup="true" aria-controls="editorHighlightParamsToolbar"
  326. tabindex="0" data-l10n-id="pdfjs-editor-highlight-button">
  327. <span data-l10n-id="pdfjs-editor-highlight-button-label">Highlight</span>
  328. </button>
  329. <div class="editorParamsToolbar hidden doorHangerRight"
  330. id="editorHighlightParamsToolbar">
  331. <div id="highlightParamsToolbarContainer"
  332. class="editorParamsToolbarContainer">
  333. <div id="editorHighlightColorPicker" class="colorPicker">
  334. <span id="highlightColorPickerLabel" class="editorParamsLabel"
  335. data-l10n-id="pdfjs-editor-highlight-colorpicker-label">Highlight
  336. color</span>
  337. </div>
  338. <div id="editorHighlightThickness">
  339. <label for="editorFreeHighlightThickness" class="editorParamsLabel"
  340. data-l10n-id="pdfjs-editor-free-highlight-thickness-input">Thickness</label>
  341. <div class="thicknessPicker">
  342. <input type="range" id="editorFreeHighlightThickness"
  343. class="editorParamsSlider"
  344. data-l10n-id="pdfjs-editor-free-highlight-thickness-title"
  345. value="12" min="8" max="24" step="1" tabindex="0">
  346. </div>
  347. </div>
  348. <div id="editorHighlightVisibility">
  349. <div class="divider"></div>
  350. <div class="toggler">
  351. <label for="editorHighlightShowAll" class="editorParamsLabel"
  352. data-l10n-id="pdfjs-editor-highlight-show-all-button-label">Show
  353. all</label>
  354. <button id="editorHighlightShowAll" class="toggle-button"
  355. type="button"
  356. data-l10n-id="pdfjs-editor-highlight-show-all-button"
  357. aria-pressed="true" tabindex="0"></button>
  358. </div>
  359. </div>
  360. </div>
  361. </div>
  362. </div>
  363. <div id="editorFreeText" class="toolbarButtonWithContainer">
  364. <button id="editorFreeTextButton" class="toolbarButton" type="button"
  365. disabled="disabled" title="Text" role="radio" aria-expanded="false"
  366. aria-haspopup="true" aria-controls="editorFreeTextParamsToolbar"
  367. tabindex="0" data-l10n-id="pdfjs-editor-free-text-button">
  368. <span data-l10n-id="pdfjs-editor-free-text-button-label">Text</span>
  369. </button>
  370. <div class="editorParamsToolbar hidden doorHangerRight"
  371. id="editorFreeTextParamsToolbar">
  372. <div class="editorParamsToolbarContainer">
  373. <div class="editorParamsSetter">
  374. <label for="editorFreeTextColor" class="editorParamsLabel"
  375. data-l10n-id="pdfjs-editor-free-text-color-input">Color</label>
  376. <input type="color" id="editorFreeTextColor"
  377. class="editorParamsColor" tabindex="0">
  378. </div>
  379. <div class="editorParamsSetter">
  380. <label for="editorFreeTextFontSize" class="editorParamsLabel"
  381. data-l10n-id="pdfjs-editor-free-text-size-input">Size</label>
  382. <input type="range" id="editorFreeTextFontSize"
  383. class="editorParamsSlider" value="10" min="5" max="100" step="1"
  384. tabindex="0">
  385. </div>
  386. </div>
  387. </div>
  388. </div>
  389. <div id="editorInk" class="toolbarButtonWithContainer">
  390. <button id="editorInkButton" class="toolbarButton" type="button"
  391. disabled="disabled" title="Draw" role="radio" aria-expanded="false"
  392. aria-haspopup="true" aria-controls="editorInkParamsToolbar" tabindex="0"
  393. data-l10n-id="pdfjs-editor-ink-button">
  394. <span data-l10n-id="pdfjs-editor-ink-button-label">Draw</span>
  395. </button>
  396. <div class="editorParamsToolbar hidden doorHangerRight"
  397. id="editorInkParamsToolbar">
  398. <div class="editorParamsToolbarContainer">
  399. <div class="editorParamsSetter">
  400. <label for="editorInkColor" class="editorParamsLabel"
  401. data-l10n-id="pdfjs-editor-ink-color-input">Color</label>
  402. <input type="color" id="editorInkColor" class="editorParamsColor"
  403. tabindex="0">
  404. </div>
  405. <div class="editorParamsSetter">
  406. <label for="editorInkThickness" class="editorParamsLabel"
  407. data-l10n-id="pdfjs-editor-ink-thickness-input">Thickness</label>
  408. <input type="range" id="editorInkThickness"
  409. class="editorParamsSlider" value="1" min="1" max="20" step="1"
  410. tabindex="0">
  411. </div>
  412. <div class="editorParamsSetter">
  413. <label for="editorInkOpacity" class="editorParamsLabel"
  414. data-l10n-id="pdfjs-editor-ink-opacity-input">Opacity</label>
  415. <input type="range" id="editorInkOpacity" class="editorParamsSlider"
  416. value="100" min="1" max="100" step="1" tabindex="0">
  417. </div>
  418. </div>
  419. </div>
  420. </div>
  421. <div id="editorStamp" class="toolbarButtonWithContainer">
  422. <button id="editorStampButton" class="toolbarButton" type="button"
  423. disabled="disabled" title="Add or edit images" role="radio"
  424. aria-expanded="false" aria-haspopup="true"
  425. aria-controls="editorStampParamsToolbar" tabindex="0"
  426. data-l10n-id="pdfjs-editor-stamp-button">
  427. <span data-l10n-id="pdfjs-editor-stamp-button-label">Add or edit
  428. images</span>
  429. </button>
  430. <div class="editorParamsToolbar hidden doorHangerRight menu"
  431. id="editorStampParamsToolbar">
  432. <div class="menuContainer">
  433. <button id="editorStampAddImage" class="toolbarButton labeled"
  434. type="button" title="Add image" tabindex="0"
  435. data-l10n-id="pdfjs-editor-stamp-add-image-button">
  436. <span class="editorParamsLabel"
  437. data-l10n-id="pdfjs-editor-stamp-add-image-button-label">Add
  438. image</span>
  439. </button>
  440. </div>
  441. </div>
  442. </div>
  443. </div>
  444. <div id="editorModeSeparator" class="verticalToolbarSeparator"></div>
  445. <div class="toolbarHorizontalGroup hiddenMediumView">
  446. <button id="printButton" class="toolbarButton" type="button" title="Print"
  447. tabindex="0" data-l10n-id="pdfjs-print-button">
  448. <span data-l10n-id="pdfjs-print-button-label">Print</span>
  449. </button>
  450. <button id="downloadButton" class="toolbarButton" type="button" title="Save"
  451. tabindex="0" data-l10n-id="pdfjs-save-button">
  452. <span data-l10n-id="pdfjs-save-button-label">Save</span>
  453. </button>
  454. </div>
  455. <div class="verticalToolbarSeparator hiddenMediumView"></div>
  456. <div id="secondaryToolbarToggle" class="toolbarButtonWithContainer">
  457. <button id="secondaryToolbarToggleButton" class="toolbarButton" type="button"
  458. title="Tools" tabindex="0" data-l10n-id="pdfjs-tools-button"
  459. aria-expanded="false" aria-haspopup="true" aria-controls="secondaryToolbar">
  460. <span data-l10n-id="pdfjs-tools-button-label">Tools</span>
  461. </button>
  462. <div id="secondaryToolbar" class="hidden doorHangerRight menu">
  463. <div id="secondaryToolbarButtonContainer" class="menuContainer">
  464. <button id="secondaryOpenFile" class="toolbarButton labeled" type="button"
  465. title="Open File" tabindex="0" data-l10n-id="pdfjs-open-file-button">
  466. <span data-l10n-id="pdfjs-open-file-button-label">Open</span>
  467. </button>
  468. <div class="visibleMediumView">
  469. <button id="secondaryPrint" class="toolbarButton labeled" type="button"
  470. title="Print" tabindex="0" data-l10n-id="pdfjs-print-button">
  471. <span data-l10n-id="pdfjs-print-button-label">Print</span>
  472. </button>
  473. <button id="secondaryDownload" class="toolbarButton labeled"
  474. type="button" title="Save" tabindex="0"
  475. data-l10n-id="pdfjs-save-button">
  476. <span data-l10n-id="pdfjs-save-button-label">Save</span>
  477. </button>
  478. </div>
  479. <div class="horizontalToolbarSeparator"></div>
  480. <button id="presentationMode" class="toolbarButton labeled" type="button"
  481. title="Switch to Presentation Mode" tabindex="0"
  482. data-l10n-id="pdfjs-presentation-mode-button">
  483. <span data-l10n-id="pdfjs-presentation-mode-button-label">Presentation
  484. Mode</span>
  485. </button>
  486. <a href="#" id="viewBookmark" class="toolbarButton labeled"
  487. title="Current Page (View URL from Current Page)" tabindex="0"
  488. data-l10n-id="pdfjs-bookmark-button">
  489. <span data-l10n-id="pdfjs-bookmark-button-label">Current Page</span>
  490. </a>
  491. <div id="viewBookmarkSeparator" class="horizontalToolbarSeparator"></div>
  492. <button id="firstPage" class="toolbarButton labeled" type="button"
  493. title="Go to First Page" tabindex="0"
  494. data-l10n-id="pdfjs-first-page-button">
  495. <span data-l10n-id="pdfjs-first-page-button-label">Go to First
  496. Page</span>
  497. </button>
  498. <button id="lastPage" class="toolbarButton labeled" type="button"
  499. title="Go to Last Page" tabindex="0"
  500. data-l10n-id="pdfjs-last-page-button">
  501. <span data-l10n-id="pdfjs-last-page-button-label">Go to Last Page</span>
  502. </button>
  503. <div class="horizontalToolbarSeparator"></div>
  504. <button id="pageRotateCw" class="toolbarButton labeled" type="button"
  505. title="Rotate Clockwise" tabindex="0"
  506. data-l10n-id="pdfjs-page-rotate-cw-button">
  507. <span data-l10n-id="pdfjs-page-rotate-cw-button-label">Rotate
  508. Clockwise</span>
  509. </button>
  510. <button id="pageRotateCcw" class="toolbarButton labeled" type="button"
  511. title="Rotate Counterclockwise" tabindex="0"
  512. data-l10n-id="pdfjs-page-rotate-ccw-button">
  513. <span data-l10n-id="pdfjs-page-rotate-ccw-button-label">Rotate
  514. Counterclockwise</span>
  515. </button>
  516. <div class="horizontalToolbarSeparator"></div>
  517. <div id="cursorToolButtons" role="radiogroup">
  518. <button id="cursorSelectTool" class="toolbarButton labeled toggled"
  519. type="button" title="Enable Text Selection Tool" tabindex="0"
  520. data-l10n-id="pdfjs-cursor-text-select-tool-button" role="radio"
  521. aria-checked="true">
  522. <span data-l10n-id="pdfjs-cursor-text-select-tool-button-label">Text
  523. Selection Tool</span>
  524. </button>
  525. <button id="cursorHandTool" class="toolbarButton labeled" type="button"
  526. title="Enable Hand Tool" tabindex="0"
  527. data-l10n-id="pdfjs-cursor-hand-tool-button" role="radio"
  528. aria-checked="false">
  529. <span data-l10n-id="pdfjs-cursor-hand-tool-button-label">Hand
  530. Tool</span>
  531. </button>
  532. </div>
  533. <div class="horizontalToolbarSeparator"></div>
  534. <div id="scrollModeButtons" role="radiogroup">
  535. <button id="scrollPage" class="toolbarButton labeled" type="button"
  536. title="Use Page Scrolling" tabindex="0"
  537. data-l10n-id="pdfjs-scroll-page-button" role="radio"
  538. aria-checked="false">
  539. <span data-l10n-id="pdfjs-scroll-page-button-label">Page
  540. Scrolling</span>
  541. </button>
  542. <button id="scrollVertical" class="toolbarButton labeled toggled"
  543. type="button" title="Use Vertical Scrolling" tabindex="0"
  544. data-l10n-id="pdfjs-scroll-vertical-button" role="radio"
  545. aria-checked="true">
  546. <span data-l10n-id="pdfjs-scroll-vertical-button-label">Vertical
  547. Scrolling</span>
  548. </button>
  549. <button id="scrollHorizontal" class="toolbarButton labeled"
  550. type="button" title="Use Horizontal Scrolling" tabindex="0"
  551. data-l10n-id="pdfjs-scroll-horizontal-button" role="radio"
  552. aria-checked="false">
  553. <span data-l10n-id="pdfjs-scroll-horizontal-button-label">Horizontal
  554. Scrolling</span>
  555. </button>
  556. <button id="scrollWrapped" class="toolbarButton labeled" type="button"
  557. title="Use Wrapped Scrolling" tabindex="0"
  558. data-l10n-id="pdfjs-scroll-wrapped-button" role="radio"
  559. aria-checked="false">
  560. <span data-l10n-id="pdfjs-scroll-wrapped-button-label">Wrapped
  561. Scrolling</span>
  562. </button>
  563. </div>
  564. <div class="horizontalToolbarSeparator"></div>
  565. <div id="spreadModeButtons" role="radiogroup">
  566. <button id="spreadNone" class="toolbarButton labeled toggled"
  567. type="button" title="Do not join page spreads" tabindex="0"
  568. data-l10n-id="pdfjs-spread-none-button" role="radio"
  569. aria-checked="true">
  570. <span data-l10n-id="pdfjs-spread-none-button-label">No
  571. Spreads</span>
  572. </button>
  573. <button id="spreadOdd" class="toolbarButton labeled" type="button"
  574. title="Join page spreads starting with odd-numbered pages"
  575. tabindex="0" data-l10n-id="pdfjs-spread-odd-button" role="radio"
  576. aria-checked="false">
  577. <span data-l10n-id="pdfjs-spread-odd-button-label">Odd
  578. Spreads</span>
  579. </button>
  580. <button id="spreadEven" class="toolbarButton labeled" type="button"
  581. title="Join page spreads starting with even-numbered pages"
  582. tabindex="0" data-l10n-id="pdfjs-spread-even-button" role="radio"
  583. aria-checked="false">
  584. <span data-l10n-id="pdfjs-spread-even-button-label">Even
  585. Spreads</span>
  586. </button>
  587. </div>
  588. <div id="imageAltTextSettingsSeparator"
  589. class="horizontalToolbarSeparator hidden"></div>
  590. <button id="imageAltTextSettings" type="button"
  591. class="toolbarButton labeled hidden" title="Image alt text settings"
  592. tabindex="0" data-l10n-id="pdfjs-image-alt-text-settings-button"
  593. aria-controls="altTextSettingsDialog">
  594. <span data-l10n-id="pdfjs-image-alt-text-settings-button-label">Image
  595. alt text settings</span>
  596. </button>
  597. <div class="horizontalToolbarSeparator"></div>
  598. <button id="documentProperties" class="toolbarButton labeled" type="button"
  599. title="Document Properties…" tabindex="0"
  600. data-l10n-id="pdfjs-document-properties-button"
  601. aria-controls="documentPropertiesDialog">
  602. <span data-l10n-id="pdfjs-document-properties-button-label">Document
  603. Properties…</span>
  604. </button>
  605. </div>
  606. </div> <!-- secondaryToolbar -->
  607. </div>
  608. </div>
  609. </div>
  610. <div id="loadingBar">
  611. <div class="progress">
  612. <div class="glimmer">
  613. </div>
  614. </div>
  615. </div>
  616. </div>
  617. </div>
  618. <div id="viewerContainer" tabindex="0">
  619. <div id="viewer" class="pdfViewer"></div>
  620. </div>
  621. </div> <!-- mainContainer -->
  622. <div id="dialogContainer">
  623. <dialog id="passwordDialog">
  624. <div class="row">
  625. <label for="password" id="passwordText" data-l10n-id="pdfjs-password-label">Enter the password
  626. to open this
  627. PDF file:</label>
  628. </div>
  629. <div class="row">
  630. <input type="password" id="password" class="toolbarField">
  631. </div>
  632. <div class="buttonRow">
  633. <button id="passwordCancel" class="dialogButton" type="button"><span
  634. data-l10n-id="pdfjs-password-cancel-button">Cancel</span></button>
  635. <button id="passwordSubmit" class="dialogButton" type="button"><span
  636. data-l10n-id="pdfjs-password-ok-button">OK</span></button>
  637. </div>
  638. </dialog>
  639. <dialog id="documentPropertiesDialog">
  640. <div class="row">
  641. <span id="fileNameLabel" data-l10n-id="pdfjs-document-properties-file-name">File name:</span>
  642. <p id="fileNameField" aria-labelledby="fileNameLabel">-</p>
  643. </div>
  644. <div class="row">
  645. <span id="fileSizeLabel" data-l10n-id="pdfjs-document-properties-file-size">File size:</span>
  646. <p id="fileSizeField" aria-labelledby="fileSizeLabel">-</p>
  647. </div>
  648. <div class="separator"></div>
  649. <div class="row">
  650. <span id="titleLabel" data-l10n-id="pdfjs-document-properties-title">Title:</span>
  651. <p id="titleField" aria-labelledby="titleLabel">-</p>
  652. </div>
  653. <div class="row">
  654. <span id="authorLabel" data-l10n-id="pdfjs-document-properties-author">Author:</span>
  655. <p id="authorField" aria-labelledby="authorLabel">-</p>
  656. </div>
  657. <div class="row">
  658. <span id="subjectLabel" data-l10n-id="pdfjs-document-properties-subject">Subject:</span>
  659. <p id="subjectField" aria-labelledby="subjectLabel">-</p>
  660. </div>
  661. <div class="row">
  662. <span id="keywordsLabel" data-l10n-id="pdfjs-document-properties-keywords">Keywords:</span>
  663. <p id="keywordsField" aria-labelledby="keywordsLabel">-</p>
  664. </div>
  665. <div class="row">
  666. <span id="creationDateLabel" data-l10n-id="pdfjs-document-properties-creation-date">Creation
  667. Date:</span>
  668. <p id="creationDateField" aria-labelledby="creationDateLabel">-</p>
  669. </div>
  670. <div class="row">
  671. <span id="modificationDateLabel"
  672. data-l10n-id="pdfjs-document-properties-modification-date">Modification
  673. Date:</span>
  674. <p id="modificationDateField" aria-labelledby="modificationDateLabel">-</p>
  675. </div>
  676. <div class="row">
  677. <span id="creatorLabel" data-l10n-id="pdfjs-document-properties-creator">Creator:</span>
  678. <p id="creatorField" aria-labelledby="creatorLabel">-</p>
  679. </div>
  680. <div class="separator"></div>
  681. <div class="row">
  682. <span id="producerLabel" data-l10n-id="pdfjs-document-properties-producer">PDF Producer:</span>
  683. <p id="producerField" aria-labelledby="producerLabel">-</p>
  684. </div>
  685. <div class="row">
  686. <span id="versionLabel" data-l10n-id="pdfjs-document-properties-version">PDF Version:</span>
  687. <p id="versionField" aria-labelledby="versionLabel">-</p>
  688. </div>
  689. <div class="row">
  690. <span id="pageCountLabel" data-l10n-id="pdfjs-document-properties-page-count">Page Count:</span>
  691. <p id="pageCountField" aria-labelledby="pageCountLabel">-</p>
  692. </div>
  693. <div class="row">
  694. <span id="pageSizeLabel" data-l10n-id="pdfjs-document-properties-page-size">Page Size:</span>
  695. <p id="pageSizeField" aria-labelledby="pageSizeLabel">-</p>
  696. </div>
  697. <div class="separator"></div>
  698. <div class="row">
  699. <span id="linearizedLabel" data-l10n-id="pdfjs-document-properties-linearized">Fast Web
  700. View:</span>
  701. <p id="linearizedField" aria-labelledby="linearizedLabel">-</p>
  702. </div>
  703. <div class="buttonRow">
  704. <button id="documentPropertiesClose" class="dialogButton" type="button"><span
  705. data-l10n-id="pdfjs-document-properties-close-button">Close</span></button>
  706. </div>
  707. </dialog>
  708. <dialog class="dialog altText" id="altTextDialog" aria-labelledby="dialogLabel"
  709. aria-describedby="dialogDescription">
  710. <div id="altTextContainer" class="mainContainer">
  711. <div id="overallDescription">
  712. <span id="dialogLabel" data-l10n-id="pdfjs-editor-alt-text-dialog-label"
  713. class="title">Choose an
  714. option</span>
  715. <span id="dialogDescription" data-l10n-id="pdfjs-editor-alt-text-dialog-description">
  716. Alt text (alternative text) helps when people can’t see the image or when it doesn’t
  717. load.
  718. </span>
  719. </div>
  720. <div id="addDescription">
  721. <div class="radio">
  722. <div class="radioButton">
  723. <input type="radio" id="descriptionButton" name="altTextOption" tabindex="0"
  724. aria-describedby="descriptionAreaLabel" checked>
  725. <label for="descriptionButton"
  726. data-l10n-id="pdfjs-editor-alt-text-add-description-label">Add a
  727. description</label>
  728. </div>
  729. <div class="radioLabel">
  730. <span id="descriptionAreaLabel"
  731. data-l10n-id="pdfjs-editor-alt-text-add-description-description">
  732. Aim for 1-2 sentences that describe the subject, setting, or actions.
  733. </span>
  734. </div>
  735. </div>
  736. <div class="descriptionArea">
  737. <textarea id="descriptionTextarea"
  738. placeholder="For example, “A young man sits down at a table to eat a meal”"
  739. aria-labelledby="descriptionAreaLabel" data-l10n-id="pdfjs-editor-alt-text-textarea"
  740. tabindex="0"></textarea>
  741. </div>
  742. </div>
  743. <div id="markAsDecorative">
  744. <div class="radio">
  745. <div class="radioButton">
  746. <input type="radio" id="decorativeButton" name="altTextOption"
  747. aria-describedby="decorativeLabel">
  748. <label for="decorativeButton"
  749. data-l10n-id="pdfjs-editor-alt-text-mark-decorative-label">Mark as
  750. decorative</label>
  751. </div>
  752. <div class="radioLabel">
  753. <span id="decorativeLabel"
  754. data-l10n-id="pdfjs-editor-alt-text-mark-decorative-description">
  755. This is used for ornamental images, like borders or watermarks.
  756. </span>
  757. </div>
  758. </div>
  759. </div>
  760. <div id="buttons">
  761. <button id="altTextCancel" class="secondaryButton" type="button" tabindex="0"><span
  762. data-l10n-id="pdfjs-editor-alt-text-cancel-button">Cancel</span></button>
  763. <button id="altTextSave" class="primaryButton" type="button" tabindex="0"><span
  764. data-l10n-id="pdfjs-editor-alt-text-save-button">Save</span></button>
  765. </div>
  766. </div>
  767. </dialog>
  768. <dialog class="dialog newAltText" id="newAltTextDialog" aria-labelledby="newAltTextTitle"
  769. aria-describedby="newAltTextDescription" tabindex="0">
  770. <div id="newAltTextContainer" class="mainContainer">
  771. <div class="title">
  772. <span id="newAltTextTitle" data-l10n-id="pdfjs-editor-new-alt-text-dialog-edit-label"
  773. role="sectionhead" tabindex="0">Edit alt text (image description)</span>
  774. </div>
  775. <div id="mainContent">
  776. <div id="descriptionAndSettings">
  777. <div id="descriptionInstruction">
  778. <div id="newAltTextDescriptionContainer">
  779. <div class="altTextSpinner" role="status" aria-live="polite"></div>
  780. <textarea id="newAltTextDescriptionTextarea"
  781. placeholder="Write your description here…"
  782. aria-labelledby="descriptionAreaLabel"
  783. data-l10n-id="pdfjs-editor-new-alt-text-textarea" tabindex="0"></textarea>
  784. </div>
  785. <span id="newAltTextDescription" role="note"
  786. data-l10n-id="pdfjs-editor-new-alt-text-description">Short
  787. description for people who can’t see the image or when the image doesn’t
  788. load.</span>
  789. <div id="newAltTextDisclaimer" role="note">
  790. <div><span data-l10n-id="pdfjs-editor-new-alt-text-disclaimer1">This alt text
  791. was created
  792. automatically and may be inaccurate.</span> <a
  793. href="https://support.mozilla.org/en-US/kb/pdf-alt-text" target="_blank"
  794. rel="noopener noreferrer" id="newAltTextLearnMore"
  795. data-l10n-id="pdfjs-editor-new-alt-text-disclaimer-learn-more-url"
  796. tabindex="0">Learn more</a></div>
  797. </div>
  798. </div>
  799. <div id="newAltTextCreateAutomatically" class="toggler">
  800. <button id="newAltTextCreateAutomaticallyButton" class="toggle-button" type="button"
  801. aria-pressed="true" tabindex="0"></button>
  802. <label for="newAltTextCreateAutomaticallyButton" class="togglerLabel"
  803. data-l10n-id="pdfjs-editor-new-alt-text-create-automatically-button-label">Create
  804. alt text
  805. automatically</label>
  806. </div>
  807. <div id="newAltTextDownloadModel" class="hidden">
  808. <span id="newAltTextDownloadModelDescription"
  809. data-l10n-id="pdfjs-editor-new-alt-text-ai-model-downloading-progress"
  810. aria-valuemin="0"
  811. data-l10n-args='{ "totalSize": 0, "downloadedSize": 0 }'>Downloading alt text AI
  812. model (0 of 0
  813. MB)</span>
  814. </div>
  815. </div>
  816. <div id="newAltTextImagePreview"></div>
  817. </div>
  818. <div id="newAltTextError" class="messageBar">
  819. <div>
  820. <div>
  821. <span class="title" data-l10n-id="pdfjs-editor-new-alt-text-error-title">Couldn’t
  822. create alt text
  823. automatically</span>
  824. <span class="description"
  825. data-l10n-id="pdfjs-editor-new-alt-text-error-description">Please write your
  826. own alt text or try again later.</span>
  827. </div>
  828. <button id="newAltTextCloseButton" class="closeButton" type="button" tabindex="0"
  829. title="Close"><span
  830. data-l10n-id="pdfjs-editor-new-alt-text-error-close-button">Close</span></button>
  831. </div>
  832. </div>
  833. <div id="newAltTextButtons" class="dialogButtonsGroup">
  834. <button id="newAltTextCancel" type="button" class="secondaryButton hidden"
  835. tabindex="0"><span
  836. data-l10n-id="pdfjs-editor-alt-text-cancel-button">Cancel</span></button>
  837. <button id="newAltTextNotNow" type="button" class="secondaryButton" tabindex="0"><span
  838. data-l10n-id="pdfjs-editor-new-alt-text-not-now-button">Not now</span></button>
  839. <button id="newAltTextSave" type="button" class="primaryButton" tabindex="0"><span
  840. data-l10n-id="pdfjs-editor-alt-text-save-button">Save</span></button>
  841. </div>
  842. </div>
  843. </dialog>
  844. <dialog class="dialog" id="altTextSettingsDialog" aria-labelledby="altTextSettingsTitle">
  845. <div id="altTextSettingsContainer" class="mainContainer">
  846. <div class="title">
  847. <span id="altTextSettingsTitle" data-l10n-id="pdfjs-editor-alt-text-settings-dialog-label"
  848. role="sectionhead" tabindex="0" class="title">Image alt text settings</span>
  849. </div>
  850. <div id="automaticAltText">
  851. <span data-l10n-id="pdfjs-editor-alt-text-settings-automatic-title">Automatic alt
  852. text</span>
  853. <div id="automaticSettings">
  854. <div id="createModelSetting">
  855. <div class="toggler">
  856. <button id="createModelButton" type="button" class="toggle-button"
  857. aria-pressed="true" tabindex="0"></button>
  858. <label for="createModelButton" class="togglerLabel"
  859. data-l10n-id="pdfjs-editor-alt-text-settings-create-model-button-label">Create
  860. alt text
  861. automatically</label>
  862. </div>
  863. <div id="createModelDescription" class="description">
  864. <span
  865. data-l10n-id="pdfjs-editor-alt-text-settings-create-model-description">Suggests
  866. descriptions to
  867. help people who can’t see the image or when the image doesn’t load.</span>
  868. <a href="https://support.mozilla.org/en-US/kb/pdf-alt-text" target="_blank"
  869. rel="noopener noreferrer" id="altTextSettingsLearnMore"
  870. data-l10n-id="pdfjs-editor-new-alt-text-disclaimer-learn-more-url"
  871. tabindex="0">Learn more</a>
  872. </div>
  873. </div>
  874. <div id="aiModelSettings">
  875. <div>
  876. <span data-l10n-id="pdfjs-editor-alt-text-settings-download-model-label"
  877. data-l10n-args='{ "totalSize": 180 }'>Alt text AI model (180MB)</span>
  878. <div id="aiModelDescription" class="description">
  879. <span
  880. data-l10n-id="pdfjs-editor-alt-text-settings-ai-model-description">Runs
  881. locally on your device
  882. so your data stays private. Required for automatic alt text.</span>
  883. </div>
  884. </div>
  885. <button id="deleteModelButton" type="button" class="secondaryButton"
  886. tabindex="0"><span
  887. data-l10n-id="pdfjs-editor-alt-text-settings-delete-model-button">Delete</span></button>
  888. <button id="downloadModelButton" type="button" class="secondaryButton"
  889. tabindex="0"><span
  890. data-l10n-id="pdfjs-editor-alt-text-settings-download-model-button">Download</span></button>
  891. </div>
  892. </div>
  893. </div>
  894. <div class="dialogSeparator"></div>
  895. <div id="altTextEditor">
  896. <span data-l10n-id="pdfjs-editor-alt-text-settings-editor-title">Alt text editor</span>
  897. <div id="showAltTextEditor">
  898. <div class="toggler">
  899. <button id="showAltTextDialogButton" type="button" class="toggle-button"
  900. aria-pressed="true" tabindex="0"></button>
  901. <label for="showAltTextDialogButton" class="togglerLabel"
  902. data-l10n-id="pdfjs-editor-alt-text-settings-show-dialog-button-label">Show alt
  903. text editor right away
  904. when adding an image</label>
  905. </div>
  906. <div id="showAltTextDialogDescription" class="description">
  907. <span data-l10n-id="pdfjs-editor-alt-text-settings-show-dialog-description">Helps
  908. you make sure all your
  909. images have alt text.</span>
  910. </div>
  911. </div>
  912. </div>
  913. <div id="buttons" class="dialogButtonsGroup">
  914. <button id="altTextSettingsCloseButton" type="button" class="primaryButton"
  915. tabindex="0"><span
  916. data-l10n-id="pdfjs-editor-alt-text-settings-close-button">Close</span></button>
  917. </div>
  918. </div>
  919. </dialog>
  920. <dialog id="printServiceDialog" style="min-width: 200px;">
  921. <div class="row">
  922. <span data-l10n-id="pdfjs-print-progress-message">Preparing document for printing…</span>
  923. </div>
  924. <div class="row">
  925. <progress value="0" max="100"></progress>
  926. <span data-l10n-id="pdfjs-print-progress-percent" data-l10n-args='{ "progress": 0 }'
  927. class="relative-progress">0%</span>
  928. </div>
  929. <div class="buttonRow">
  930. <button id="printCancel" class="dialogButton" type="button"><span
  931. data-l10n-id="pdfjs-print-progress-close-button">Cancel</span></button>
  932. </div>
  933. </dialog>
  934. </div> <!-- dialogContainer -->
  935. </div> <!-- outerContainer -->
  936. <div id="printContainer"></div>
  937. <script>
  938. const uploadButton = document.getElementById('uploadButton');
  939. const pdfInput = document.getElementById('pdfInput');
  940. const filenameModal = document.getElementById('filenameModal');
  941. const modalOverlay = document.getElementById('modalOverlay');
  942. const confirmUploadButton = document.getElementById('confirmUpload');
  943. const cancelUploadButton = document.getElementById('cancelUpload');
  944. const customFileNameInput = document.getElementById('customFileName');
  945. const openDirectoryButton = document.getElementById('openDirectoryButton');
  946. const directoryModal = document.getElementById('directoryModal');
  947. const closeDirectoryButton = document.getElementById('closeDirectoryButton');
  948. const pdfList = document.getElementById('pdfList');
  949. let selectedFile = null;
  950. // 上传按钮点击事件
  951. uploadButton.addEventListener('click', function () {
  952. pdfInput.click();
  953. });
  954. // 文件选择事件
  955. pdfInput.addEventListener('change', function (event) {
  956. const file = event.target.files[0];
  957. if (file && file.type === 'application/pdf') {
  958. selectedFile = file;
  959. modalOverlay.style.display = 'block';
  960. filenameModal.style.display = 'block';
  961. customFileNameInput.value = "";
  962. customFileNameInput.focus();
  963. } else {
  964. alert('请选择一个 PDF 文件');
  965. }
  966. });
  967. // 取消上传
  968. cancelUploadButton.addEventListener('click', function () {
  969. filenameModal.style.display = 'none';
  970. modalOverlay.style.display = 'none';
  971. pdfInput.value = "";
  972. selectedFile = null;
  973. });
  974. // 确认上传
  975. confirmUploadButton.addEventListener('click', function () {
  976. const customFileName = customFileNameInput.value.trim();
  977. if (customFileName === "") {
  978. alert('请输入保存的文件名');
  979. customFileNameInput.focus();
  980. return;
  981. }
  982. const invalidChars = /[\\/:"*?<>|]+/;
  983. if (invalidChars.test(customFileName)) {
  984. alert('文件名包含非法字符:\\ / : " * ? < > |');
  985. customFileNameInput.focus();
  986. return;
  987. }
  988. if (!selectedFile) {
  989. alert('没有选择文件');
  990. return;
  991. }
  992. const formData = new FormData();
  993. formData.append('file', selectedFile);
  994. formData.append('custom_name', customFileName);
  995. fetch('http://117.50.195.224:8005/upload-pdf', {
  996. method: 'POST',
  997. body: formData
  998. })
  999. .then(response => response.json())
  1000. .then(data => {
  1001. if (data.success) {
  1002. const uploadedFilePath = data.file_path;
  1003. const redirectUrl = `viewer.html?file=${encodeURIComponent(uploadedFilePath)}`;
  1004. window.location.href = redirectUrl;
  1005. } else {
  1006. alert('上传失败: ' + data.error);
  1007. }
  1008. })
  1009. .catch(error => {
  1010. console.error('Error:', error);
  1011. alert('上传过程中发生错误');
  1012. })
  1013. .finally(() => {
  1014. filenameModal.style.display = 'none';
  1015. modalOverlay.style.display = 'none';
  1016. pdfInput.value = "";
  1017. selectedFile = null;
  1018. });
  1019. });
  1020. // 打开目录
  1021. openDirectoryButton.addEventListener('click', function () {
  1022. modalOverlay.style.display = 'block';
  1023. directoryModal.style.display = 'block';
  1024. pdfList.innerHTML = '';
  1025. fetch('http://117.50.195.224:8005/list-pdfs')
  1026. .then(response => response.json())
  1027. .then(data => {
  1028. if (data.success) {
  1029. const files = data.files;
  1030. if (files.length === 0) {
  1031. const listItem = document.createElement('li');
  1032. listItem.textContent = '没有已上传的 PDF 文档。';
  1033. pdfList.appendChild(listItem);
  1034. } else {
  1035. files.forEach(file => {
  1036. const listItem = document.createElement('li');
  1037. listItem.style.marginBottom = '10px';
  1038. const link = document.createElement('a');
  1039. link.href = `viewer.html?file=${file.url}`;
  1040. link.textContent = file.name;
  1041. link.style.textDecoration = 'none';
  1042. link.style.color = '#007bff';
  1043. link.addEventListener('click', function (event) {
  1044. event.preventDefault();
  1045. window.location.href = link.getAttribute('href');
  1046. });
  1047. listItem.appendChild(link);
  1048. pdfList.appendChild(listItem);
  1049. });
  1050. }
  1051. } else {
  1052. alert('无法获取文件列表: ' + data.error);
  1053. }
  1054. })
  1055. .catch(error => {
  1056. console.error('Error:', error);
  1057. alert('获取文件列表时发生错误');
  1058. });
  1059. });
  1060. // 关闭目录
  1061. closeDirectoryButton.addEventListener('click', function () {
  1062. directoryModal.style.display = 'none';
  1063. modalOverlay.style.display = 'none';
  1064. });
  1065. // 点击遮罩关闭弹出层
  1066. modalOverlay.addEventListener('click', function () {
  1067. if (filenameModal.style.display === 'block') {
  1068. filenameModal.style.display = 'none';
  1069. uploadButton.focus();
  1070. }
  1071. if (directoryModal.style.display === 'block') {
  1072. directoryModal.style.display = 'none';
  1073. }
  1074. modalOverlay.style.display = 'none';
  1075. });
  1076. // 语音控制
  1077. let isListening = false;
  1078. let audioContext = null;
  1079. // 初始化 AudioContext
  1080. function initAudioContext() {
  1081. if (!audioContext) {
  1082. audioContext = new (window.AudioContext || window.webkitAudioContext)();
  1083. }
  1084. }
  1085. // 获取播放权限
  1086. async function getPlayPermission() {
  1087. return new Promise((resolve) => {
  1088. // const handleUserInteraction = () => {
  1089. // document.removeEventListener('click', handleUserInteraction);
  1090. // document.removeEventListener('keydown', handleUserInteraction);
  1091. // resolve();
  1092. // };
  1093. const dummyAudio = new Audio('data:audio/wav;base64,UklGRl9vT19XQVZFZm10IBAAAAABAAEAQB8AAEAfAAABAAgAZGF0YU');
  1094. dummyAudio.play()
  1095. .then(() => {
  1096. dummyAudio.pause();
  1097. dummyAudio.remove();
  1098. resolve();
  1099. })
  1100. .catch((error) => {
  1101. console.warn('Audio play permission denied:', error);
  1102. // 静默处理权限错误,假设后续交互会自动触发权限
  1103. resolve(); // 直接解析以继续流程
  1104. // document.getElementById('loading-indicator').textContent = '请点击任意位置以启用音频...';
  1105. // document.getElementById('loading-indicator').style.display = 'block';
  1106. // document.addEventListener('click', handleUserInteraction);
  1107. // document.addEventListener('keydown', handleUserInteraction);
  1108. });
  1109. });
  1110. }
  1111. // 切换文本选择监听
  1112. document.getElementById('toggle-text-select').addEventListener('click', () => {
  1113. isListening = !isListening;
  1114. const button = document.getElementById('toggle-text-select');
  1115. button.textContent = isListening ? "关闭监听" : "开启监听";
  1116. button.classList.toggle('button-danger', isListening);
  1117. });
  1118. // 播放音频队列
  1119. async function playAudioSequentially(queue, signal) {
  1120. const audioElements = new Map();
  1121. const loadingIndicator = document.getElementById('loading-indicator');
  1122. try {
  1123. while (true) {
  1124. if (signal.aborted) break;
  1125. const chunk = queue.shift();
  1126. if (!chunk) {
  1127. if (queue.includes(null)) break;
  1128. await new Promise(resolve => setTimeout(resolve, 100));
  1129. continue;
  1130. }
  1131. if (audioContext.state !== 'running') {
  1132. await audioContext.resume();
  1133. }
  1134. const audio = new Audio(chunk.audio);
  1135. audioElements.set(chunk.index, audio);
  1136. try {
  1137. loadingIndicator.textContent = `正在播放第 ${chunk.index + 1} 句`;
  1138. loadingIndicator.style.display = 'block';
  1139. await playAudio(audio);
  1140. } catch (e) {
  1141. console.warn(`播放失败: ${e.message}`);
  1142. } finally {
  1143. audioElements.delete(chunk.index);
  1144. }
  1145. }
  1146. loadingIndicator.textContent = '播放完成';
  1147. setTimeout(() => {
  1148. loadingIndicator.style.display = 'none';
  1149. }, 1000);
  1150. } catch (error) {
  1151. console.error(error);
  1152. loadingIndicator.textContent = `错误: ${error.message}`;
  1153. loadingIndicator.style.display = 'block';
  1154. } finally {
  1155. audioElements.forEach(audio => {
  1156. audio.pause();
  1157. audio.remove();
  1158. });
  1159. }
  1160. }
  1161. // 播放单个音频
  1162. function playAudio(audio) {
  1163. return new Promise((resolve, reject) => {
  1164. audio.play()
  1165. .then(() => {
  1166. audio.onended = resolve;
  1167. audio.onerror = reject;
  1168. })
  1169. .catch(reject);
  1170. });
  1171. }
  1172. // 解析流数据
  1173. function parseStreamData(line) {
  1174. try {
  1175. return JSON.parse(line);
  1176. } catch (e) {
  1177. console.error('解析错误:', e, '原始数据:', line);
  1178. return { error: e.message };
  1179. }
  1180. }
  1181. // 创建音频片段
  1182. function createAudioChunk(data) {
  1183. return {
  1184. ...data,
  1185. audio: data.audio.startsWith('data:') ?
  1186. data.audio :
  1187. `data:audio/wav;base64,${data.audio}`,
  1188. timestamp: Date.now()
  1189. };
  1190. }
  1191. // 按顺序插入队列
  1192. function insertInOrder(queue, chunk) {
  1193. let index = 0;
  1194. while (index < queue.length && queue[index].index < chunk.index) {
  1195. index++;
  1196. }
  1197. queue.splice(index, 0, chunk);
  1198. }
  1199. // 开始转换
  1200. async function startConversion(sentences) {
  1201. const loadingIndicator = document.getElementById('loading-indicator');
  1202. try {
  1203. const fullText = sentences.map(s => s.text).join(' ');
  1204. const response = await fetch('http://117.50.195.224:8028/generate', {
  1205. method: 'POST',
  1206. headers: { 'Content-Type': 'application/json' },
  1207. body: JSON.stringify({ text: fullText, voice: "af_heart", speed: 1.0 })
  1208. });
  1209. if (!response.ok) throw new Error(`服务器错误: ${response.status}`);
  1210. const reader = response.body.getReader();
  1211. const decoder = new TextDecoder();
  1212. let buffer = '';
  1213. const audioQueue = [];
  1214. let expectedIndex = 0;
  1215. const playController = new AbortController();
  1216. const playPromise = playAudioSequentially(audioQueue, playController.signal);
  1217. while (true) {
  1218. const { done, value } = await reader.read();
  1219. if (done) break;
  1220. buffer += decoder.decode(value, { stream: true });
  1221. const lines = buffer.split('\n');
  1222. buffer = lines.pop() || '';
  1223. for (const line of lines) {
  1224. if (!line.trim()) continue;
  1225. const data = parseStreamData(line);
  1226. if (data.audio) {
  1227. const sentence = sentences[data.index];
  1228. insertInOrder(audioQueue, createAudioChunk({
  1229. ...data,
  1230. xpath: sentence?.xpath
  1231. }));
  1232. }
  1233. }
  1234. }
  1235. while (buffer.trim()) {
  1236. const data = parseStreamData(buffer.trim());
  1237. if (data.audio) {
  1238. audioQueue.push(createAudioChunk(data));
  1239. }
  1240. buffer = '';
  1241. }
  1242. audioQueue.push(null);
  1243. await playPromise;
  1244. } catch (error) {
  1245. console.error(error);
  1246. loadingIndicator.textContent = `错误: ${error.message}`;
  1247. loadingIndicator.style.display = 'block';
  1248. }
  1249. }
  1250. // 监听文本选择
  1251. document.addEventListener('DOMContentLoaded', () => {
  1252. const loadingIndicator = document.getElementById('loading-indicator');
  1253. document.addEventListener('mouseup', async () => {
  1254. if (!isListening) return;
  1255. const selection = window.getSelection();
  1256. const selectedText = selection.toString().trim();
  1257. if (selectedText.length === 0) return;
  1258. selection.removeAllRanges();
  1259. try {
  1260. initAudioContext();
  1261. await getPlayPermission();
  1262. loadingIndicator.textContent = '正在转换选中文本...';
  1263. loadingIndicator.style.display = 'block';
  1264. const sentences = selectedText.split(/[.!?]+/).filter(s => s.trim()).map((text, index) => ({
  1265. text: text.trim(),
  1266. xpath: null,
  1267. index: index
  1268. }));
  1269. await startConversion(sentences);
  1270. } catch (error) {
  1271. console.error(error);
  1272. loadingIndicator.textContent = `错误: ${error.message}`;
  1273. loadingIndicator.style.display = 'block';
  1274. }
  1275. });
  1276. });
  1277. // 阅读整页
  1278. document.getElementById('select-all-text').addEventListener('click', async function () {
  1279. const { pdfViewer } = PDFViewerApplication;
  1280. const currentPage = pdfViewer.currentPageNumber;
  1281. const loadingIndicator = document.getElementById('loading-indicator');
  1282. try {
  1283. initAudioContext();
  1284. await getPlayPermission();
  1285. loadingIndicator.textContent = '正在提取页面文本...';
  1286. loadingIndicator.style.display = 'block';
  1287. PDFViewerApplication.pdfDocument.getPage(currentPage).then(function (page) {
  1288. page.getTextContent().then(async function (textContent) {
  1289. const fullText = textContent.items.map(item => item.str).join(' ');
  1290. const sentences = fullText.split(/[.!?]+/).filter(s => s.trim()).map((text, index) => ({
  1291. text: text.trim(),
  1292. xpath: null,
  1293. index: index
  1294. }));
  1295. await startConversion(sentences);
  1296. });
  1297. });
  1298. } catch (error) {
  1299. console.error(error);
  1300. loadingIndicator.textContent = `错误: ${error.message}`;
  1301. loadingIndicator.style.display = 'block';
  1302. }
  1303. });
  1304. </script>
  1305. </body>
  1306. </html>