update at 2026-02-07 12:55:25
1
.gitignore
vendored
@@ -25,3 +25,4 @@ dist-ssr
|
|||||||
*.sw?
|
*.sw?
|
||||||
|
|
||||||
*.ttf
|
*.ttf
|
||||||
|
vite.config.ts
|
||||||
|
|||||||
@@ -6,7 +6,7 @@
|
|||||||
|
|
||||||
## 界面快照
|
## 界面快照
|
||||||
|
|
||||||

|

|
||||||
|
|
||||||
## 当前功能
|
## 当前功能
|
||||||
|
|
||||||
|
|||||||
@@ -2,7 +2,7 @@
|
|||||||
<html lang="zh-CN">
|
<html lang="zh-CN">
|
||||||
<head>
|
<head>
|
||||||
<meta charset="UTF-8" />
|
<meta charset="UTF-8" />
|
||||||
<link rel="icon" type="image/svg+xml" href="/assets/webicon.svg" />
|
<link rel="icon" type="image/svg+xml" href="/favicon.svg" />
|
||||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||||
<title>Font2SVG - 字体转SVG工具</title>
|
<title>Font2SVG - 字体转SVG工具</title>
|
||||||
</head>
|
</head>
|
||||||
|
|||||||
|
Before Width: | Height: | Size: 170 KiB After Width: | Height: | Size: 170 KiB |
@@ -62,9 +62,15 @@ async function handleExport(format: ExportFormat) {
|
|||||||
if (selectedItems.length === 1) {
|
if (selectedItems.length === 1) {
|
||||||
// 单个字体,直接下载 SVG
|
// 单个字体,直接下载 SVG
|
||||||
const item = selectedItems[0]
|
const item = selectedItems[0]
|
||||||
|
if (!item?.fontInfo.font) {
|
||||||
|
alert('选中字体未加载完成,请稍后重试')
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
const font = item.fontInfo.font
|
||||||
const svgResult = await generateSvg({
|
const svgResult = await generateSvg({
|
||||||
text: inputText,
|
text: inputText,
|
||||||
font: item.fontInfo.font,
|
font,
|
||||||
fontSize: uiStore.fontSize,
|
fontSize: uiStore.fontSize,
|
||||||
fillColor: uiStore.textColor,
|
fillColor: uiStore.textColor,
|
||||||
letterSpacing: 0
|
letterSpacing: 0
|
||||||
@@ -87,9 +93,15 @@ async function handleExport(format: ExportFormat) {
|
|||||||
|
|
||||||
for (const item of selectedItems) {
|
for (const item of selectedItems) {
|
||||||
try {
|
try {
|
||||||
|
const font = item.fontInfo.font
|
||||||
|
if (!font) {
|
||||||
|
console.warn(`字体 ${item.fontInfo.name} 尚未加载,已跳过导出`)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
const svgResult = await generateSvg({
|
const svgResult = await generateSvg({
|
||||||
text: inputText,
|
text: inputText,
|
||||||
font: item.fontInfo.font,
|
font,
|
||||||
fontSize: uiStore.fontSize,
|
fontSize: uiStore.fontSize,
|
||||||
fillColor: uiStore.textColor,
|
fillColor: uiStore.textColor,
|
||||||
letterSpacing: 0
|
letterSpacing: 0
|
||||||
@@ -161,12 +173,12 @@ console.log('App.vue: script setup completed')
|
|||||||
<div class="flex gap-2 items-center shrink-0 h-24 px-2 py-1">
|
<div class="flex gap-2 items-center shrink-0 h-24 px-2 py-1">
|
||||||
<!-- webicon - 48x48 -->
|
<!-- webicon - 48x48 -->
|
||||||
<div class="w-12 h-12 rounded-xl overflow-hidden shrink-0">
|
<div class="w-12 h-12 rounded-xl overflow-hidden shrink-0">
|
||||||
<img src="/assets/webicon.svg" alt="logo" class="w-full h-full object-cover" />
|
<img src="./assets/webicon.svg" alt="logo" class="w-full h-full object-cover" />
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- 星程字体转换 - 弹性宽度 -->
|
<!-- 星程字体转换 - 弹性宽度 -->
|
||||||
<div class="shrink-0 max-w-[225px] min-w-[120px]" style="height: 72px;">
|
<div class="shrink-0 max-w-[225px] min-w-[120px]" style="height: 72px;">
|
||||||
<img src="/assets/icons/星程字体转换.svg" alt="星程SVG文字生成 TEXT to SVG" class="w-full h-full object-contain" />
|
<img src="./assets/icons/星程字体转换.svg" alt="星程SVG文字生成 TEXT to SVG" class="w-full h-full object-contain" />
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- slider - 增加宽度 -->
|
<!-- slider - 增加宽度 -->
|
||||||
@@ -176,7 +188,7 @@ console.log('App.vue: script setup completed')
|
|||||||
class="w-4 h-4 shrink-0 cursor-pointer hover:opacity-70 transition-opacity flex items-center justify-center p-0 border-0 bg-transparent"
|
class="w-4 h-4 shrink-0 cursor-pointer hover:opacity-70 transition-opacity flex items-center justify-center p-0 border-0 bg-transparent"
|
||||||
title="减小字体"
|
title="减小字体"
|
||||||
>
|
>
|
||||||
<img src="/assets/icons/icons_idx%20_38.svg" alt="A-" class="w-4 h-4 object-contain" />
|
<img src="./assets/icons/icons_idx%20_38.svg" alt="A-" class="w-4 h-4 object-contain" />
|
||||||
</button>
|
</button>
|
||||||
<div class="flex-1 h-6 flex items-center relative">
|
<div class="flex-1 h-6 flex items-center relative">
|
||||||
<input
|
<input
|
||||||
@@ -204,14 +216,14 @@ console.log('App.vue: script setup completed')
|
|||||||
class="w-6 h-6 shrink-0 cursor-pointer hover:opacity-70 transition-opacity flex items-center justify-center p-0 border-0 bg-transparent"
|
class="w-6 h-6 shrink-0 cursor-pointer hover:opacity-70 transition-opacity flex items-center justify-center p-0 border-0 bg-transparent"
|
||||||
title="增大字体"
|
title="增大字体"
|
||||||
>
|
>
|
||||||
<img src="/assets/icons/icons_idx%20_33.svg" alt="A+" class="w-6 h-6 object-contain" />
|
<img src="./assets/icons/icons_idx%20_33.svg" alt="A+" class="w-6 h-6 object-contain" />
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- 文字颜色选择 -->
|
<!-- 文字颜色选择 -->
|
||||||
<div class="shrink-0 relative w-9 h-9">
|
<div class="shrink-0 relative w-9 h-9">
|
||||||
<label class="w-full h-full flex items-center justify-center cursor-pointer">
|
<label class="w-full h-full flex items-center justify-center cursor-pointer">
|
||||||
<img src="/assets/icons/choose-color.svg" alt="颜色" class="w-9 h-9 object-contain" />
|
<img src="./assets/icons/choose-color.svg" alt="颜色" class="w-9 h-9 object-contain" />
|
||||||
<input
|
<input
|
||||||
type="color"
|
type="color"
|
||||||
:value="uiStore.textColor"
|
:value="uiStore.textColor"
|
||||||
@@ -235,7 +247,7 @@ console.log('App.vue: script setup completed')
|
|||||||
<!-- Export Group -->
|
<!-- Export Group -->
|
||||||
<div class="flex items-center gap-1 shrink-0 border border-[#8552A1] rounded-lg px-1 py-1 bg-[#f7f8fa] shadow-sm">
|
<div class="flex items-center gap-1 shrink-0 border border-[#8552A1] rounded-lg px-1 py-1 bg-[#f7f8fa] shadow-sm">
|
||||||
<div class="w-[18px] h-[42px] shrink-0 pointer-events-none">
|
<div class="w-[18px] h-[42px] shrink-0 pointer-events-none">
|
||||||
<img src="/assets/icons/export.svg" alt="导出" class="w-full h-full object-contain" />
|
<img src="./assets/icons/export.svg" alt="导出" class="w-full h-full object-contain" />
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<button
|
<button
|
||||||
@@ -243,7 +255,7 @@ console.log('App.vue: script setup completed')
|
|||||||
class="w-12 h-12 shrink-0 cursor-pointer hover:opacity-85 transition-opacity flex items-center justify-center p-0 border-0 bg-transparent"
|
class="w-12 h-12 shrink-0 cursor-pointer hover:opacity-85 transition-opacity flex items-center justify-center p-0 border-0 bg-transparent"
|
||||||
title="导出 SVG"
|
title="导出 SVG"
|
||||||
>
|
>
|
||||||
<img src="/assets/icons/export-svg.svg" alt="导出SVG" class="w-12 h-12 object-contain" />
|
<img src="./assets/icons/export-svg.svg" alt="导出SVG" class="w-12 h-12 object-contain" />
|
||||||
</button>
|
</button>
|
||||||
|
|
||||||
<button
|
<button
|
||||||
@@ -251,7 +263,7 @@ console.log('App.vue: script setup completed')
|
|||||||
class="w-12 h-12 shrink-0 cursor-pointer hover:opacity-85 transition-opacity flex items-center justify-center p-0 border-0 bg-transparent"
|
class="w-12 h-12 shrink-0 cursor-pointer hover:opacity-85 transition-opacity flex items-center justify-center p-0 border-0 bg-transparent"
|
||||||
title="导出 PNG"
|
title="导出 PNG"
|
||||||
>
|
>
|
||||||
<img src="/assets/icons/export-png.svg" alt="导出PNG" class="w-12 h-12 object-contain" />
|
<img src="./assets/icons/export-png.svg" alt="导出PNG" class="w-12 h-12 object-contain" />
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
|
Before Width: | Height: | Size: 989 B After Width: | Height: | Size: 989 B |
|
Before Width: | Height: | Size: 488 B After Width: | Height: | Size: 488 B |
|
Before Width: | Height: | Size: 3.9 KiB After Width: | Height: | Size: 3.9 KiB |
|
Before Width: | Height: | Size: 330 B After Width: | Height: | Size: 330 B |
|
Before Width: | Height: | Size: 1.3 KiB After Width: | Height: | Size: 1.3 KiB |
|
Before Width: | Height: | Size: 1.3 KiB After Width: | Height: | Size: 1.3 KiB |
|
Before Width: | Height: | Size: 2.1 KiB After Width: | Height: | Size: 2.1 KiB |
|
Before Width: | Height: | Size: 749 B After Width: | Height: | Size: 749 B |
|
Before Width: | Height: | Size: 403 B After Width: | Height: | Size: 403 B |
|
Before Width: | Height: | Size: 404 B After Width: | Height: | Size: 404 B |
|
Before Width: | Height: | Size: 304 B After Width: | Height: | Size: 304 B |
|
Before Width: | Height: | Size: 755 B After Width: | Height: | Size: 755 B |
|
Before Width: | Height: | Size: 585 B After Width: | Height: | Size: 585 B |
|
Before Width: | Height: | Size: 304 B After Width: | Height: | Size: 304 B |
|
Before Width: | Height: | Size: 749 B After Width: | Height: | Size: 749 B |
|
Before Width: | Height: | Size: 437 B After Width: | Height: | Size: 437 B |
|
Before Width: | Height: | Size: 392 B After Width: | Height: | Size: 392 B |
|
Before Width: | Height: | Size: 198 B After Width: | Height: | Size: 198 B |
|
Before Width: | Height: | Size: 392 B After Width: | Height: | Size: 392 B |
|
Before Width: | Height: | Size: 392 B After Width: | Height: | Size: 392 B |
|
Before Width: | Height: | Size: 383 B After Width: | Height: | Size: 383 B |
|
Before Width: | Height: | Size: 279 B After Width: | Height: | Size: 279 B |
|
Before Width: | Height: | Size: 41 KiB After Width: | Height: | Size: 41 KiB |
|
Before Width: | Height: | Size: 3.2 KiB After Width: | Height: | Size: 3.2 KiB |
|
Before Width: | Height: | Size: 473 KiB After Width: | Height: | Size: 473 KiB |
|
Before Width: | Height: | Size: 496 B After Width: | Height: | Size: 496 B |
|
Before Width: | Height: | Size: 128 KiB After Width: | Height: | Size: 128 KiB |
9
frontend/src/assets/webicon.svg
Normal file
|
After Width: | Height: | Size: 170 KiB |
@@ -38,7 +38,7 @@ function isInPreview(fontId: string): boolean {
|
|||||||
>
|
>
|
||||||
<!-- 字体图标 -->
|
<!-- 字体图标 -->
|
||||||
<div class="w-4 h-4 shrink-0">
|
<div class="w-4 h-4 shrink-0">
|
||||||
<img src="/assets/icons/icons_idx%20_18.svg" alt="font" class="w-full h-full" />
|
<img src="../assets/icons/icons_idx%20_18.svg" alt="font" class="w-full h-full" />
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- 字体名称 -->
|
<!-- 字体名称 -->
|
||||||
@@ -52,7 +52,7 @@ function isInPreview(fontId: string): boolean {
|
|||||||
class="w-[18px] h-[18px] shrink-0 border rounded-full flex items-center justify-center p-0 bg-transparent"
|
class="w-[18px] h-[18px] shrink-0 border rounded-full flex items-center justify-center p-0 bg-transparent"
|
||||||
:class="isInPreview(font.id) ? 'bg-[#9b6bc2] border-[#9b6bc2]' : 'border-[#c9cdd4]'"
|
:class="isInPreview(font.id) ? 'bg-[#9b6bc2] border-[#9b6bc2]' : 'border-[#c9cdd4]'"
|
||||||
>
|
>
|
||||||
<img v-if="isInPreview(font.id)" src="/assets/icons/checkbox.svg" alt="选中" class="w-[11px] h-[9px]" />
|
<img v-if="isInPreview(font.id)" src="../assets/icons/checkbox.svg" alt="选中" class="w-[11px] h-[9px]" />
|
||||||
</button>
|
</button>
|
||||||
|
|
||||||
<!-- 收藏按钮 -->
|
<!-- 收藏按钮 -->
|
||||||
@@ -61,7 +61,7 @@ function isInPreview(fontId: string): boolean {
|
|||||||
class="w-[18px] h-[17px] shrink-0 p-0 border-0 bg-transparent"
|
class="w-[18px] h-[17px] shrink-0 p-0 border-0 bg-transparent"
|
||||||
>
|
>
|
||||||
<img
|
<img
|
||||||
src="/assets/icons/icons_idx%20_19.svg"
|
src="../assets/icons/icons_idx%20_19.svg"
|
||||||
alt="收藏"
|
alt="收藏"
|
||||||
class="w-full h-full"
|
class="w-full h-full"
|
||||||
:class="isFavorite(font.id) ? 'favorite-active' : ''"
|
:class="isFavorite(font.id) ? 'favorite-active' : ''"
|
||||||
|
|||||||
@@ -51,13 +51,13 @@ function isInPreview(node: FontTreeNode): boolean {
|
|||||||
>
|
>
|
||||||
<img
|
<img
|
||||||
v-if="node.expanded"
|
v-if="node.expanded"
|
||||||
src="/assets/icons/zhedie.svg"
|
src="../assets/icons/zhedie.svg"
|
||||||
alt="收起"
|
alt="收起"
|
||||||
class="w-[15px] h-[15px]"
|
class="w-[15px] h-[15px]"
|
||||||
/>
|
/>
|
||||||
<img
|
<img
|
||||||
v-else
|
v-else
|
||||||
src="/assets/icons/icons_idx%20_12.svg"
|
src="../assets/icons/icons_idx%20_12.svg"
|
||||||
alt="展开"
|
alt="展开"
|
||||||
class="w-[15px] h-[15px]"
|
class="w-[15px] h-[15px]"
|
||||||
/>
|
/>
|
||||||
@@ -79,7 +79,7 @@ function isInPreview(node: FontTreeNode): boolean {
|
|||||||
<!-- 字体列表 -->
|
<!-- 字体列表 -->
|
||||||
<div v-if="node.expanded && node.children" class="flex flex-col gap-3 mt-3">
|
<div v-if="node.expanded && node.children" class="flex flex-col gap-3 mt-3">
|
||||||
<div
|
<div
|
||||||
v-for="(child, index) in node.children"
|
v-for="child in node.children"
|
||||||
:key="child.name"
|
:key="child.name"
|
||||||
class="flex items-center gap-2 border-b border-[#c9cdd4] pb-2 relative"
|
class="flex items-center gap-2 border-b border-[#c9cdd4] pb-2 relative"
|
||||||
>
|
>
|
||||||
@@ -88,7 +88,7 @@ function isInPreview(node: FontTreeNode): boolean {
|
|||||||
|
|
||||||
<!-- 字体图标 -->
|
<!-- 字体图标 -->
|
||||||
<div class="w-4 h-4 shrink-0 ml-[17px]">
|
<div class="w-4 h-4 shrink-0 ml-[17px]">
|
||||||
<img src="/assets/icons/icons_idx%20_18.svg" alt="font" class="w-full h-full" />
|
<img src="../assets/icons/icons_idx%20_18.svg" alt="font" class="w-full h-full" />
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- 字体名称 -->
|
<!-- 字体名称 -->
|
||||||
@@ -102,7 +102,7 @@ function isInPreview(node: FontTreeNode): boolean {
|
|||||||
class="w-[18px] h-[18px] shrink-0 border rounded-full flex items-center justify-center p-0 bg-transparent"
|
class="w-[18px] h-[18px] shrink-0 border rounded-full flex items-center justify-center p-0 bg-transparent"
|
||||||
:class="isInPreview(child) ? 'bg-[#9b6bc2] border-[#9b6bc2]' : 'border-[#c9cdd4]'"
|
:class="isInPreview(child) ? 'bg-[#9b6bc2] border-[#9b6bc2]' : 'border-[#c9cdd4]'"
|
||||||
>
|
>
|
||||||
<img v-if="isInPreview(child)" src="/assets/icons/checkbox.svg" alt="选中" class="w-[11px] h-[9px]" />
|
<img v-if="isInPreview(child)" src="../assets/icons/checkbox.svg" alt="选中" class="w-[11px] h-[9px]" />
|
||||||
</button>
|
</button>
|
||||||
|
|
||||||
<!-- 收藏按钮 -->
|
<!-- 收藏按钮 -->
|
||||||
@@ -111,7 +111,7 @@ function isInPreview(node: FontTreeNode): boolean {
|
|||||||
class="w-[18px] h-[17px] shrink-0 p-0 border-0 bg-transparent"
|
class="w-[18px] h-[17px] shrink-0 p-0 border-0 bg-transparent"
|
||||||
>
|
>
|
||||||
<img
|
<img
|
||||||
src="/assets/icons/icons_idx%20_19.svg"
|
src="../assets/icons/icons_idx%20_19.svg"
|
||||||
alt="收藏"
|
alt="收藏"
|
||||||
class="w-full h-full"
|
class="w-full h-full"
|
||||||
:class="isFavorite(child) ? 'favorite-active' : ''"
|
:class="isFavorite(child) ? 'favorite-active' : ''"
|
||||||
|
|||||||
@@ -97,7 +97,7 @@ function toggleSelectItem(item: PreviewItemType) {
|
|||||||
>
|
>
|
||||||
<div class="flex items-center gap-[8px] border-b border-[#c9cdd4] pb-[8px] pr-[8px]">
|
<div class="flex items-center gap-[8px] border-b border-[#c9cdd4] pb-[8px] pr-[8px]">
|
||||||
<div class="w-[24px] h-[24px] shrink-0">
|
<div class="w-[24px] h-[24px] shrink-0">
|
||||||
<img src="/assets/icons/icons_idx%20_32.svg" alt="字体" class="w-full h-full" />
|
<img src="../assets/icons/icons_idx%20_32.svg" alt="字体" class="w-full h-full" />
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="flex-1 text-xs text-[#86909c]">
|
<div class="flex-1 text-xs text-[#86909c]">
|
||||||
@@ -109,7 +109,7 @@ function toggleSelectItem(item: PreviewItemType) {
|
|||||||
class="w-[18px] h-[18px] shrink-0 border rounded-full flex items-center justify-center p-0 bg-transparent"
|
class="w-[18px] h-[18px] shrink-0 border rounded-full flex items-center justify-center p-0 bg-transparent"
|
||||||
:class="item.selected ? 'bg-[#9b6bc2] border-[#9b6bc2]' : 'border-[#c9cdd4]'"
|
:class="item.selected ? 'bg-[#9b6bc2] border-[#9b6bc2]' : 'border-[#c9cdd4]'"
|
||||||
>
|
>
|
||||||
<img v-if="item.selected" src="/assets/icons/checkbox.svg" alt="选中" class="w-[11px] h-[9px]" />
|
<img v-if="item.selected" src="../assets/icons/checkbox.svg" alt="选中" class="w-[11px] h-[9px]" />
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
|||||||
12
frontend/src/types/modules.d.ts
vendored
@@ -48,4 +48,14 @@ declare module 'opentype.js' {
|
|||||||
|
|
||||||
declare module 'harfbuzzjs' {
|
declare module 'harfbuzzjs' {
|
||||||
export default function (): Promise<any>
|
export default function (): Promise<any>
|
||||||
}
|
}
|
||||||
|
|
||||||
|
declare module 'harfbuzzjs/hb.js' {
|
||||||
|
const createHarfBuzz: any
|
||||||
|
export default createHarfBuzz
|
||||||
|
}
|
||||||
|
|
||||||
|
declare module 'harfbuzzjs/hbjs.js' {
|
||||||
|
const bindHarfBuzz: any
|
||||||
|
export default bindHarfBuzz
|
||||||
|
}
|
||||||
|
|||||||
@@ -2,9 +2,6 @@ import { defineConfig } from 'vite'
|
|||||||
import vue from '@vitejs/plugin-vue'
|
import vue from '@vitejs/plugin-vue'
|
||||||
import UnoCSS from 'unocss/vite'
|
import UnoCSS from 'unocss/vite'
|
||||||
import wasm from 'vite-plugin-wasm'
|
import wasm from 'vite-plugin-wasm'
|
||||||
import fs from 'fs'
|
|
||||||
import path from 'path'
|
|
||||||
import { homedir } from 'os'
|
|
||||||
|
|
||||||
// https://vite.dev/config/
|
// https://vite.dev/config/
|
||||||
export default defineConfig({
|
export default defineConfig({
|
||||||
@@ -18,9 +15,5 @@ export default defineConfig({
|
|||||||
server: {
|
server: {
|
||||||
host: '0.0.0.0',
|
host: '0.0.0.0',
|
||||||
port: 5174,
|
port: 5174,
|
||||||
https: {
|
|
||||||
key: fs.readFileSync(path.join(homedir(), 'mac.biboer.cn_ecc/mac.biboer.cn.key')),
|
|
||||||
cert: fs.readFileSync(path.join(homedir(), 'mac.biboer.cn_ecc/fullchain.cer'))
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|||||||