font_comparison.html 14 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445
  1. <!DOCTYPE html>
  2. <html lang="zh-CN">
  3. <head>
  4. <meta charset="UTF-8">
  5. <meta name="viewport" content="width=device-width, initial-scale=1.0">
  6. <title>字体对比预览</title>
  7. <style>
  8. * {
  9. margin: 0;
  10. padding: 0;
  11. box-sizing: border-box;
  12. }
  13. body {
  14. font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, 'Helvetica Neue', Arial, sans-serif;
  15. background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
  16. min-height: 100vh;
  17. padding: 20px;
  18. }
  19. .container {
  20. max-width: 1400px;
  21. margin: 0 auto;
  22. background: white;
  23. border-radius: 12px;
  24. box-shadow: 0 10px 40px rgba(0, 0, 0, 0.2);
  25. padding: 30px;
  26. }
  27. h1 {
  28. color: #333;
  29. margin-bottom: 30px;
  30. text-align: center;
  31. font-size: 28px;
  32. font-weight: 600;
  33. }
  34. .input-section {
  35. background: #f8f9fa;
  36. padding: 20px;
  37. border-radius: 8px;
  38. margin-bottom: 30px;
  39. }
  40. .form-row {
  41. display: grid;
  42. grid-template-columns: 1fr auto;
  43. gap: 15px;
  44. align-items: end;
  45. }
  46. .form-group {
  47. flex: 1;
  48. }
  49. label {
  50. display: block;
  51. margin-bottom: 8px;
  52. color: #555;
  53. font-weight: 500;
  54. font-size: 14px;
  55. }
  56. input[type="text"],
  57. input[type="number"],
  58. input[type="color"] {
  59. width: 100%;
  60. padding: 12px;
  61. border: 2px solid #e0e0e0;
  62. border-radius: 6px;
  63. font-size: 14px;
  64. transition: border-color 0.3s;
  65. }
  66. input[type="text"]:focus,
  67. input[type="number"]:focus {
  68. outline: none;
  69. border-color: #667eea;
  70. }
  71. .form-controls {
  72. display: grid;
  73. grid-template-columns: auto auto auto;
  74. gap: 10px;
  75. align-items: end;
  76. }
  77. .color-input-group {
  78. display: flex;
  79. gap: 10px;
  80. align-items: center;
  81. }
  82. .color-input-group input[type="color"] {
  83. width: 50px;
  84. height: 46px;
  85. cursor: pointer;
  86. padding: 2px;
  87. }
  88. .color-input-group input[type="text"] {
  89. flex: 1;
  90. }
  91. button {
  92. background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
  93. color: white;
  94. border: none;
  95. padding: 12px 30px;
  96. border-radius: 6px;
  97. font-size: 16px;
  98. font-weight: 500;
  99. cursor: pointer;
  100. transition: transform 0.2s, box-shadow 0.2s;
  101. white-space: nowrap;
  102. }
  103. button:hover {
  104. transform: translateY(-2px);
  105. box-shadow: 0 5px 15px rgba(102, 126, 234, 0.4);
  106. }
  107. button:active {
  108. transform: translateY(0);
  109. }
  110. button:disabled {
  111. background: #ccc;
  112. cursor: not-allowed;
  113. transform: none;
  114. }
  115. .checkbox-group {
  116. display: flex;
  117. align-items: center;
  118. gap: 8px;
  119. }
  120. .checkbox-group input[type="checkbox"] {
  121. width: 18px;
  122. height: 18px;
  123. cursor: pointer;
  124. }
  125. .preview-grid {
  126. display: grid;
  127. grid-template-columns: repeat(auto-fill, minmax(300px, 1fr));
  128. gap: 20px;
  129. margin-top: 20px;
  130. }
  131. .font-preview-card {
  132. background: white;
  133. border: 2px solid #e0e0e0;
  134. border-radius: 8px;
  135. padding: 15px;
  136. transition: all 0.3s;
  137. }
  138. .font-preview-card:hover {
  139. border-color: #667eea;
  140. box-shadow: 0 4px 12px rgba(102, 126, 234, 0.2);
  141. }
  142. .font-name {
  143. font-size: 14px;
  144. font-weight: 600;
  145. color: #333;
  146. margin-bottom: 10px;
  147. padding-bottom: 8px;
  148. border-bottom: 1px solid #e0e0e0;
  149. }
  150. .font-preview-image {
  151. width: 100%;
  152. height: auto;
  153. border-radius: 4px;
  154. background:
  155. repeating-linear-gradient(45deg, #f0f0f0, #f0f0f0 10px, #ffffff 10px, #ffffff 20px);
  156. padding: 10px;
  157. }
  158. .loading {
  159. text-align: center;
  160. padding: 60px 20px;
  161. color: #667eea;
  162. font-size: 16px;
  163. }
  164. .loading-spinner {
  165. border: 4px solid #f3f3f3;
  166. border-top: 4px solid #667eea;
  167. border-radius: 50%;
  168. width: 40px;
  169. height: 40px;
  170. animation: spin 1s linear infinite;
  171. margin: 0 auto 20px;
  172. }
  173. @keyframes spin {
  174. 0% { transform: rotate(0deg); }
  175. 100% { transform: rotate(360deg); }
  176. }
  177. .error {
  178. background: #fee;
  179. color: #c33;
  180. padding: 15px;
  181. border-radius: 6px;
  182. margin-top: 15px;
  183. border-left: 4px solid #c33;
  184. }
  185. .stats {
  186. text-align: center;
  187. color: #666;
  188. margin-bottom: 20px;
  189. font-size: 14px;
  190. }
  191. .empty-state {
  192. text-align: center;
  193. padding: 60px 20px;
  194. color: #999;
  195. }
  196. .empty-state-icon {
  197. font-size: 64px;
  198. margin-bottom: 20px;
  199. }
  200. @media (max-width: 768px) {
  201. .form-row {
  202. grid-template-columns: 1fr;
  203. }
  204. .form-controls {
  205. grid-template-columns: 1fr;
  206. }
  207. .preview-grid {
  208. grid-template-columns: 1fr;
  209. }
  210. .container {
  211. padding: 20px;
  212. }
  213. }
  214. </style>
  215. </head>
  216. <body>
  217. <div class="container">
  218. <h1>🎨 字体对比预览</h1>
  219. <div class="input-section">
  220. <div class="form-row">
  221. <div class="form-group">
  222. <label for="textInput">输入文字</label>
  223. <input type="text" id="textInput" placeholder="请输入要预览的文字" value="字体预览">
  224. </div>
  225. <div class="form-controls">
  226. <div class="form-group">
  227. <label for="fontSize">字体大小</label>
  228. <input type="number" id="fontSize" value="60" min="10" max="200">
  229. </div>
  230. <div class="form-group">
  231. <label for="textColor">文字颜色</label>
  232. <div class="color-input-group">
  233. <input type="color" id="textColor" value="#000000">
  234. <input type="text" id="textColorInput" value="#000000" placeholder="#000000">
  235. </div>
  236. </div>
  237. <button id="generateBtn" onclick="generateAllPreviews()">生成所有预览</button>
  238. </div>
  239. </div>
  240. <div style="margin-top: 15px;">
  241. <div class="checkbox-group">
  242. <input type="checkbox" id="handwritingEffect" checked>
  243. <label for="handwritingEffect" style="margin: 0;">启用手写效果(轻微位置扰动)</label>
  244. </div>
  245. </div>
  246. </div>
  247. <div id="stats" class="stats" style="display: none;"></div>
  248. <div id="previewContainer">
  249. <div class="empty-state">
  250. <div class="empty-state-icon">📝</div>
  251. <div>请输入文字并点击"生成所有预览"查看所有字体效果</div>
  252. </div>
  253. </div>
  254. </div>
  255. <script>
  256. // 自动检测环境,本地开发时不添加前缀,生产环境添加 /PdfProcessing 前缀
  257. const isLocal = window.location.hostname === 'localhost' ||
  258. window.location.hostname === '127.0.0.1' ||
  259. window.location.hostname === '';
  260. const API_BASE = window.location.origin + (isLocal ? '' : '/PdfProcessing');
  261. // 颜色输入同步
  262. document.getElementById('textColor').addEventListener('input', function(e) {
  263. document.getElementById('textColorInput').value = e.target.value;
  264. });
  265. document.getElementById('textColorInput').addEventListener('input', function(e) {
  266. const color = e.target.value;
  267. if (/^#[0-9A-F]{6}$/i.test(color)) {
  268. document.getElementById('textColor').value = color;
  269. }
  270. });
  271. // 将十六进制颜色转换为RGB数组
  272. function hexToRgb(hex) {
  273. const result = /^#?([a-f\d]{2})([a-f\d]{2})([a-f\d]{2})$/i.exec(hex);
  274. return result ? [
  275. parseInt(result[1], 16),
  276. parseInt(result[2], 16),
  277. parseInt(result[3], 16)
  278. ] : [0, 0, 0];
  279. }
  280. // 生成所有字体预览
  281. async function generateAllPreviews() {
  282. const textInput = document.getElementById('textInput');
  283. const fontSize = parseInt(document.getElementById('fontSize').value);
  284. const textColor = hexToRgb(document.getElementById('textColor').value);
  285. const handwritingEffect = document.getElementById('handwritingEffect').checked;
  286. if (!textInput.value.trim()) {
  287. alert('请输入要预览的文字');
  288. return;
  289. }
  290. const generateBtn = document.getElementById('generateBtn');
  291. const previewContainer = document.getElementById('previewContainer');
  292. const statsDiv = document.getElementById('stats');
  293. generateBtn.disabled = true;
  294. generateBtn.textContent = '生成中...';
  295. previewContainer.innerHTML = `
  296. <div class="loading">
  297. <div class="loading-spinner"></div>
  298. 正在生成所有字体预览,请稍候...
  299. </div>
  300. `;
  301. statsDiv.style.display = 'none';
  302. try {
  303. const response = await fetch(`${API_BASE}/batch_generate_preview`, {
  304. method: 'POST',
  305. headers: {
  306. 'Content-Type': 'application/json',
  307. },
  308. body: JSON.stringify({
  309. text: textInput.value,
  310. font_size: fontSize,
  311. padding: 20,
  312. background_color: [255, 255, 255, 0], // 透明背景
  313. text_color: textColor,
  314. use_handwriting_effect: handwritingEffect
  315. })
  316. });
  317. const data = await response.json();
  318. if (!data.success) {
  319. throw new Error(data.error || '生成失败');
  320. }
  321. if (!data.previews || data.previews.length === 0) {
  322. previewContainer.innerHTML = `
  323. <div class="empty-state">
  324. <div class="empty-state-icon">😕</div>
  325. <div>没有生成任何预览,请检查字体文件是否存在</div>
  326. </div>
  327. `;
  328. return;
  329. }
  330. // 显示统计信息
  331. statsDiv.textContent = `共生成 ${data.total} 个字体预览`;
  332. statsDiv.style.display = 'block';
  333. // 生成预览卡片
  334. const grid = document.createElement('div');
  335. grid.className = 'preview-grid';
  336. data.previews.forEach(preview => {
  337. const card = document.createElement('div');
  338. card.className = 'font-preview-card';
  339. const fontName = document.createElement('div');
  340. fontName.className = 'font-name';
  341. fontName.textContent = preview.font_name;
  342. const previewImg = document.createElement('img');
  343. previewImg.className = 'font-preview-image';
  344. // 确保image_base64格式正确(支持带前缀或不带前缀)
  345. let imageSrc = preview.image_base64;
  346. if (imageSrc && !imageSrc.startsWith('data:image/') && !imageSrc.startsWith('http')) {
  347. // 如果是纯base64字符串,添加前缀
  348. imageSrc = 'data:image/png;base64,' + imageSrc;
  349. }
  350. previewImg.src = imageSrc;
  351. previewImg.alt = preview.font_name;
  352. previewImg.title = '点击右键保存图片';
  353. // 添加错误处理
  354. previewImg.onerror = function() {
  355. console.error('图片加载失败:', preview.font_name, imageSrc.substring(0, 50) + '...');
  356. this.alt = '图片加载失败';
  357. this.style.border = '2px solid red';
  358. };
  359. card.appendChild(fontName);
  360. card.appendChild(previewImg);
  361. grid.appendChild(card);
  362. });
  363. previewContainer.innerHTML = '';
  364. previewContainer.appendChild(grid);
  365. } catch (error) {
  366. console.error('生成预览失败:', error);
  367. previewContainer.innerHTML = `
  368. <div class="error">
  369. 生成失败: ${error.message}
  370. </div>
  371. `;
  372. } finally {
  373. generateBtn.disabled = false;
  374. generateBtn.textContent = '生成所有预览';
  375. }
  376. }
  377. // 支持回车键生成
  378. document.getElementById('textInput').addEventListener('keypress', function(e) {
  379. if (e.key === 'Enter') {
  380. generateAllPreviews();
  381. }
  382. });
  383. </script>
  384. </body>
  385. </html>