font_preview.html 14 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433
  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: 1200px;
  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. .form-section {
  35. margin-bottom: 25px;
  36. }
  37. label {
  38. display: block;
  39. margin-bottom: 8px;
  40. color: #555;
  41. font-weight: 500;
  42. font-size: 14px;
  43. }
  44. input[type="text"],
  45. select {
  46. width: 100%;
  47. padding: 12px;
  48. border: 2px solid #e0e0e0;
  49. border-radius: 6px;
  50. font-size: 14px;
  51. transition: border-color 0.3s;
  52. }
  53. input[type="text"]:focus,
  54. select:focus {
  55. outline: none;
  56. border-color: #667eea;
  57. }
  58. input[type="number"],
  59. input[type="color"] {
  60. padding: 8px;
  61. border: 2px solid #e0e0e0;
  62. border-radius: 6px;
  63. font-size: 14px;
  64. transition: border-color 0.3s;
  65. }
  66. input[type="number"]:focus {
  67. outline: none;
  68. border-color: #667eea;
  69. }
  70. .form-row {
  71. display: grid;
  72. grid-template-columns: 1fr 1fr;
  73. gap: 15px;
  74. margin-bottom: 15px;
  75. }
  76. .form-row.full-width {
  77. grid-template-columns: 1fr;
  78. }
  79. .form-group {
  80. display: flex;
  81. flex-direction: column;
  82. }
  83. .form-group label {
  84. margin-bottom: 5px;
  85. }
  86. button {
  87. background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
  88. color: white;
  89. border: none;
  90. padding: 12px 30px;
  91. border-radius: 6px;
  92. font-size: 16px;
  93. font-weight: 500;
  94. cursor: pointer;
  95. transition: transform 0.2s, box-shadow 0.2s;
  96. width: 100%;
  97. }
  98. button:hover {
  99. transform: translateY(-2px);
  100. box-shadow: 0 5px 15px rgba(102, 126, 234, 0.4);
  101. }
  102. button:active {
  103. transform: translateY(0);
  104. }
  105. button:disabled {
  106. background: #ccc;
  107. cursor: not-allowed;
  108. transform: none;
  109. }
  110. .preview-section {
  111. margin-top: 30px;
  112. padding: 25px;
  113. background: #f8f9fa;
  114. border-radius: 8px;
  115. min-height: 200px;
  116. }
  117. .preview-title {
  118. font-size: 16px;
  119. font-weight: 600;
  120. color: #333;
  121. margin-bottom: 15px;
  122. }
  123. .preview-image {
  124. max-width: 100%;
  125. height: auto;
  126. border-radius: 6px;
  127. box-shadow: 0 4px 12px rgba(0, 0, 0, 0.1);
  128. background:
  129. repeating-linear-gradient(45deg, #f0f0f0, #f0f0f0 10px, #ffffff 10px, #ffffff 20px);
  130. padding: 20px;
  131. display: block;
  132. margin: 0 auto;
  133. }
  134. .loading {
  135. text-align: center;
  136. padding: 40px;
  137. color: #667eea;
  138. font-size: 16px;
  139. }
  140. .error {
  141. background: #fee;
  142. color: #c33;
  143. padding: 15px;
  144. border-radius: 6px;
  145. margin-top: 15px;
  146. border-left: 4px solid #c33;
  147. }
  148. .success {
  149. background: #efe;
  150. color: #3c3;
  151. padding: 15px;
  152. border-radius: 6px;
  153. margin-top: 15px;
  154. border-left: 4px solid #3c3;
  155. }
  156. .checkbox-group {
  157. display: flex;
  158. align-items: center;
  159. gap: 8px;
  160. }
  161. .checkbox-group input[type="checkbox"] {
  162. width: 18px;
  163. height: 18px;
  164. cursor: pointer;
  165. }
  166. .color-input-group {
  167. display: flex;
  168. gap: 10px;
  169. align-items: center;
  170. }
  171. .color-input-group input[type="color"] {
  172. width: 50px;
  173. height: 40px;
  174. cursor: pointer;
  175. }
  176. .color-input-group input[type="text"] {
  177. flex: 1;
  178. }
  179. select {
  180. cursor: pointer;
  181. }
  182. @media (max-width: 768px) {
  183. .form-row {
  184. grid-template-columns: 1fr;
  185. }
  186. .container {
  187. padding: 20px;
  188. }
  189. }
  190. </style>
  191. </head>
  192. <body>
  193. <div class="container">
  194. <h1>🎨 字体预览工具</h1>
  195. <div class="form-section">
  196. <label for="fontSelect">选择字体</label>
  197. <select id="fontSelect">
  198. <option value="">加载中...</option>
  199. </select>
  200. </div>
  201. <div class="form-section">
  202. <label for="textInput">输入文字</label>
  203. <input type="text" id="textInput" placeholder="请输入要预览的文字" value="字体预览">
  204. </div>
  205. <div class="form-row">
  206. <div class="form-group">
  207. <label for="fontSize">字体大小</label>
  208. <input type="number" id="fontSize" value="60" min="10" max="200">
  209. </div>
  210. <div class="form-group">
  211. <label for="padding">内边距</label>
  212. <input type="number" id="padding" value="20" min="0" max="100">
  213. </div>
  214. </div>
  215. <div class="form-row">
  216. <div class="form-group">
  217. <label for="textColor">文字颜色</label>
  218. <div class="color-input-group">
  219. <input type="color" id="textColor" value="#000000">
  220. <input type="text" id="textColorInput" value="#000000" placeholder="#000000">
  221. </div>
  222. </div>
  223. <div class="form-group">
  224. <label for="backgroundColor">背景颜色(透明)</label>
  225. <div class="color-input-group">
  226. <input type="color" id="backgroundColor" value="#ffffff" disabled>
  227. <input type="text" id="backgroundColorInput" value="透明" placeholder="透明背景" disabled>
  228. </div>
  229. </div>
  230. </div>
  231. <div class="form-section">
  232. <div class="checkbox-group">
  233. <input type="checkbox" id="handwritingEffect" checked>
  234. <label for="handwritingEffect" style="margin: 0;">启用手写效果(轻微位置扰动)</label>
  235. </div>
  236. </div>
  237. <button id="generateBtn" onclick="generatePreview()">生成预览</button>
  238. <div class="preview-section">
  239. <div class="preview-title">预览效果</div>
  240. <div id="previewContainer">
  241. <div class="loading">请选择字体并输入文字,然后点击"生成预览"</div>
  242. </div>
  243. </div>
  244. </div>
  245. <script>
  246. // 自动检测环境,本地开发时不添加前缀,生产环境添加 /PdfProcessing 前缀
  247. const isLocal = window.location.hostname === 'localhost' ||
  248. window.location.hostname === '127.0.0.1' ||
  249. window.location.hostname === '';
  250. const API_BASE = window.location.origin + (isLocal ? '' : '/PdfProcessing');
  251. let fonts = [];
  252. // 加载字体列表
  253. async function loadFonts() {
  254. try {
  255. const response = await fetch(`${API_BASE}/get_font_list`);
  256. const data = await response.json();
  257. if (data.success && data.fonts) {
  258. fonts = data.fonts;
  259. const fontSelect = document.getElementById('fontSelect');
  260. fontSelect.innerHTML = '<option value="">请选择字体</option>';
  261. fonts.forEach(font => {
  262. const option = document.createElement('option');
  263. option.value = font.path;
  264. option.textContent = font.name;
  265. fontSelect.appendChild(option);
  266. });
  267. } else {
  268. throw new Error(data.error || '加载字体列表失败');
  269. }
  270. } catch (error) {
  271. console.error('加载字体列表失败:', error);
  272. document.getElementById('fontSelect').innerHTML =
  273. '<option value="">加载失败,请刷新页面重试</option>';
  274. }
  275. }
  276. // 颜色输入同步
  277. document.getElementById('textColor').addEventListener('input', function(e) {
  278. document.getElementById('textColorInput').value = e.target.value;
  279. });
  280. document.getElementById('textColorInput').addEventListener('input', function(e) {
  281. const color = e.target.value;
  282. if (/^#[0-9A-F]{6}$/i.test(color)) {
  283. document.getElementById('textColor').value = color;
  284. }
  285. });
  286. // 将十六进制颜色转换为RGB数组
  287. function hexToRgb(hex) {
  288. const result = /^#?([a-f\d]{2})([a-f\d]{2})([a-f\d]{2})$/i.exec(hex);
  289. return result ? [
  290. parseInt(result[1], 16),
  291. parseInt(result[2], 16),
  292. parseInt(result[3], 16)
  293. ] : [0, 0, 0];
  294. }
  295. // 生成预览
  296. async function generatePreview() {
  297. const fontSelect = document.getElementById('fontSelect');
  298. const textInput = document.getElementById('textInput');
  299. const fontSize = parseInt(document.getElementById('fontSize').value);
  300. const padding = parseInt(document.getElementById('padding').value);
  301. const textColor = hexToRgb(document.getElementById('textColor').value);
  302. const handwritingEffect = document.getElementById('handwritingEffect').checked;
  303. if (!fontSelect.value) {
  304. alert('请先选择字体');
  305. return;
  306. }
  307. if (!textInput.value.trim()) {
  308. alert('请输入要预览的文字');
  309. return;
  310. }
  311. const generateBtn = document.getElementById('generateBtn');
  312. const previewContainer = document.getElementById('previewContainer');
  313. generateBtn.disabled = true;
  314. generateBtn.textContent = '生成中...';
  315. previewContainer.innerHTML = '<div class="loading">正在生成预览...</div>';
  316. try {
  317. const response = await fetch(`${API_BASE}/generate_handwriting`, {
  318. method: 'POST',
  319. headers: {
  320. 'Content-Type': 'application/json',
  321. },
  322. body: JSON.stringify({
  323. text: textInput.value,
  324. font_size: fontSize,
  325. padding: padding,
  326. background_color: [255, 255, 255, 0], // 透明背景
  327. text_color: textColor,
  328. font_path: fontSelect.value,
  329. use_handwriting_effect: handwritingEffect
  330. })
  331. });
  332. if (!response.ok) {
  333. // 如果响应不是图片,尝试解析为JSON错误信息
  334. const errorData = await response.json().catch(() => null);
  335. throw new Error(errorData?.error || `HTTP错误: ${response.status}`);
  336. }
  337. // 检查响应类型是否为图片
  338. const contentType = response.headers.get('content-type');
  339. if (contentType && contentType.startsWith('image/')) {
  340. // 创建图片URL(使用Blob)
  341. const blob = await response.blob();
  342. const imageUrl = URL.createObjectURL(blob);
  343. previewContainer.innerHTML = `
  344. <img src="${imageUrl}" alt="字体预览" class="preview-image">
  345. <div class="success" style="margin-top: 15px;">
  346. 生成成功!点击图片可以右键保存。
  347. </div>
  348. `;
  349. } else {
  350. // 如果不是图片,尝试解析为JSON
  351. const data = await response.json();
  352. throw new Error(data.error || '生成失败');
  353. }
  354. } catch (error) {
  355. console.error('生成预览失败:', error);
  356. previewContainer.innerHTML = `
  357. <div class="error">
  358. 生成失败: ${error.message}
  359. </div>
  360. `;
  361. } finally {
  362. generateBtn.disabled = false;
  363. generateBtn.textContent = '生成预览';
  364. }
  365. }
  366. // 页面加载时获取字体列表
  367. window.addEventListener('DOMContentLoaded', loadFonts);
  368. // 支持回车键生成
  369. document.getElementById('textInput').addEventListener('keypress', function(e) {
  370. if (e.key === 'Enter') {
  371. generatePreview();
  372. }
  373. });
  374. </script>
  375. </body>
  376. </html>