update at 2026-02-07 12:55:25

This commit is contained in:
douboer
2026-02-07 12:55:25 +08:00
parent eb2ad134d3
commit 5036daea3e
38 changed files with 56 additions and 31 deletions

1
.gitignore vendored
View File

@@ -25,3 +25,4 @@ dist-ssr
*.sw? *.sw?
*.ttf *.ttf
vite.config.ts

View File

@@ -6,7 +6,7 @@
## 界面快照 ## 界面快照
![snapshot](frontend/assets/snapshot.png) ![snapshot](frontend/src/assets/snapshot.png)
## 当前功能 ## 当前功能

View File

@@ -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>

View File

Before

Width:  |  Height:  |  Size: 170 KiB

After

Width:  |  Height:  |  Size: 170 KiB

View File

@@ -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>

View File

Before

Width:  |  Height:  |  Size: 989 B

After

Width:  |  Height:  |  Size: 989 B

View File

Before

Width:  |  Height:  |  Size: 488 B

After

Width:  |  Height:  |  Size: 488 B

View File

Before

Width:  |  Height:  |  Size: 3.9 KiB

After

Width:  |  Height:  |  Size: 3.9 KiB

View File

Before

Width:  |  Height:  |  Size: 330 B

After

Width:  |  Height:  |  Size: 330 B

View File

Before

Width:  |  Height:  |  Size: 1.3 KiB

After

Width:  |  Height:  |  Size: 1.3 KiB

View File

Before

Width:  |  Height:  |  Size: 1.3 KiB

After

Width:  |  Height:  |  Size: 1.3 KiB

View File

Before

Width:  |  Height:  |  Size: 2.1 KiB

After

Width:  |  Height:  |  Size: 2.1 KiB

View File

Before

Width:  |  Height:  |  Size: 749 B

After

Width:  |  Height:  |  Size: 749 B

View File

Before

Width:  |  Height:  |  Size: 403 B

After

Width:  |  Height:  |  Size: 403 B

View File

Before

Width:  |  Height:  |  Size: 404 B

After

Width:  |  Height:  |  Size: 404 B

View File

Before

Width:  |  Height:  |  Size: 304 B

After

Width:  |  Height:  |  Size: 304 B

View File

Before

Width:  |  Height:  |  Size: 755 B

After

Width:  |  Height:  |  Size: 755 B

View File

Before

Width:  |  Height:  |  Size: 585 B

After

Width:  |  Height:  |  Size: 585 B

View File

Before

Width:  |  Height:  |  Size: 304 B

After

Width:  |  Height:  |  Size: 304 B

View File

Before

Width:  |  Height:  |  Size: 749 B

After

Width:  |  Height:  |  Size: 749 B

View File

Before

Width:  |  Height:  |  Size: 437 B

After

Width:  |  Height:  |  Size: 437 B

View File

Before

Width:  |  Height:  |  Size: 392 B

After

Width:  |  Height:  |  Size: 392 B

View File

Before

Width:  |  Height:  |  Size: 198 B

After

Width:  |  Height:  |  Size: 198 B

View File

Before

Width:  |  Height:  |  Size: 392 B

After

Width:  |  Height:  |  Size: 392 B

View File

Before

Width:  |  Height:  |  Size: 392 B

After

Width:  |  Height:  |  Size: 392 B

View File

Before

Width:  |  Height:  |  Size: 383 B

After

Width:  |  Height:  |  Size: 383 B

View File

Before

Width:  |  Height:  |  Size: 279 B

After

Width:  |  Height:  |  Size: 279 B

View File

Before

Width:  |  Height:  |  Size: 41 KiB

After

Width:  |  Height:  |  Size: 41 KiB

View File

Before

Width:  |  Height:  |  Size: 3.2 KiB

After

Width:  |  Height:  |  Size: 3.2 KiB

View File

Before

Width:  |  Height:  |  Size: 473 KiB

After

Width:  |  Height:  |  Size: 473 KiB

View File

Before

Width:  |  Height:  |  Size: 496 B

After

Width:  |  Height:  |  Size: 496 B

View File

Before

Width:  |  Height:  |  Size: 128 KiB

After

Width:  |  Height:  |  Size: 128 KiB

File diff suppressed because one or more lines are too long

After

Width:  |  Height:  |  Size: 170 KiB

View File

@@ -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' : ''"

View File

@@ -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' : ''"

View File

@@ -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>

View File

@@ -49,3 +49,13 @@ 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
}

View File

@@ -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'))
}
} }
}) })