修复字体加载:name 表解析真实字族名、先本机后 webfont、加载失败可见提示
This commit is contained in:
@@ -2,16 +2,22 @@
|
||||
import { computed, ref, watch } from "vue";
|
||||
import { Shadow, Textbox } from "fabric";
|
||||
import { useEditor } from "@/editor/useEditor";
|
||||
import { ensureFontLoaded, fonts, listFonts, loadingFonts } from "@/fonts/useFonts";
|
||||
import {
|
||||
ensureFontLoaded,
|
||||
fonts,
|
||||
listFonts,
|
||||
loadingFonts,
|
||||
styleOfSubfamily,
|
||||
} from "@/fonts/useFonts";
|
||||
|
||||
/**
|
||||
* 【T2 — 文字面板】选中 Textbox 时显示。
|
||||
*
|
||||
* 字体下拉列出 `useFonts` 里的本机字体,选中即挂载并应用到当前文本的 `fontFamily`;
|
||||
* 字号、颜色、对齐、行高、描边(stroke/strokeWidth)与阴影(shadow)。
|
||||
* 描边和阴影是刚需:现有封面全靠它们保证白字压在图上还读得清。
|
||||
* 字体下拉列出本机字体(family + subfamily),选中后把 fontFamily / fontWeight /
|
||||
* fontStyle 应用到当前文本,再交给 ensureFontLoaded 走「先本机、后 webfont」;
|
||||
* 加载失败要在这里给出可见提示,不能静默。
|
||||
*
|
||||
* 画布选中对象是 Fabric 实例,不是响应式的,所以这里用一批本地 ref 做 UI 状态:
|
||||
* 画布选中对象是 Fabric 实例,不是响应式的,所以用一批本地 ref 做 UI 状态:
|
||||
* 选中对象变化时从对象同步进来,用户改动时写回对象并 `requestRenderAll()`。
|
||||
*/
|
||||
const { activeObject, getCanvas } = useEditor();
|
||||
@@ -33,6 +39,35 @@ const shadowBlur = ref(0);
|
||||
const shadowOffsetX = ref(0);
|
||||
const shadowOffsetY = ref(0);
|
||||
|
||||
// 字体选择状态。
|
||||
const selectedId = ref("");
|
||||
const fontError = ref("");
|
||||
|
||||
/** 把 Fabric 的 fontWeight("normal" / "bold" / 数字)归一成数字字符串再比对。 */
|
||||
function normWeight(w: string | number | null | undefined): string {
|
||||
if (w === "normal" || w === null || w === undefined || w === "") return "400";
|
||||
if (w === "bold") return "700";
|
||||
return String(w);
|
||||
}
|
||||
|
||||
/** 用对象上的 fontFamily + weight/style 反查出下拉里对应的字体 id。 */
|
||||
function matchEntryId(): string {
|
||||
const obj = textObj.value;
|
||||
if (!obj) return "";
|
||||
const fam = obj.fontFamily;
|
||||
if (!fam) return "";
|
||||
const candidates = fonts.value.filter((f) => f.family === fam);
|
||||
if (candidates.length === 0) return "";
|
||||
if (candidates.length === 1) return candidates[0].id;
|
||||
const w = normWeight(obj.fontWeight);
|
||||
const st = obj.fontStyle ?? "normal";
|
||||
const exact = candidates.find((f) => {
|
||||
const s = styleOfSubfamily(f.subfamily);
|
||||
return String(s.fontWeight) === w && s.fontStyle === st;
|
||||
});
|
||||
return exact?.id ?? candidates[0].id;
|
||||
}
|
||||
|
||||
/** 选中对象变化 → 把对象上的值同步进 UI。 */
|
||||
function syncFromObject() {
|
||||
const obj = textObj.value;
|
||||
@@ -49,9 +84,13 @@ function syncFromObject() {
|
||||
shadowBlur.value = typeof s?.blur === "number" ? s.blur : 0;
|
||||
shadowOffsetX.value = typeof s?.offsetX === "number" ? s.offsetX : 0;
|
||||
shadowOffsetY.value = typeof s?.offsetY === "number" ? s.offsetY : 0;
|
||||
selectedId.value = matchEntryId();
|
||||
}
|
||||
|
||||
watch(textObj, syncFromObject, { immediate: true });
|
||||
watch(fonts, () => {
|
||||
selectedId.value = matchEntryId();
|
||||
});
|
||||
|
||||
/** 用户改动 → 写回对象并重绘。 */
|
||||
function applyToObject() {
|
||||
@@ -106,20 +145,22 @@ watch(
|
||||
applyToObject,
|
||||
);
|
||||
|
||||
/** 当前文本的 fontFamily 对应到列表里的 id;找不到(如默认 Times New Roman)就留空。 */
|
||||
const selectedFontId = computed(() => {
|
||||
const fam = textObj.value?.fontFamily;
|
||||
if (!fam) return "";
|
||||
return fonts.value.find((f) => f.family === fam)?.id ?? "";
|
||||
});
|
||||
|
||||
async function pickFont(id: string) {
|
||||
const entry = fonts.value.find((f) => f.id === id);
|
||||
const obj = textObj.value;
|
||||
if (!entry || !obj) return;
|
||||
obj.set("fontFamily", entry.family);
|
||||
|
||||
selectedId.value = id;
|
||||
const { fontWeight, fontStyle } = styleOfSubfamily(entry.subfamily);
|
||||
obj.set({ fontFamily: entry.family, fontWeight, fontStyle });
|
||||
getCanvas().requestRenderAll();
|
||||
await ensureFontLoaded(entry);
|
||||
|
||||
try {
|
||||
await ensureFontLoaded(entry);
|
||||
fontError.value = "";
|
||||
} catch {
|
||||
fontError.value = "这款字体用不了:本机解析不到,浏览器也加载不了,文字暂时用默认字体。";
|
||||
}
|
||||
}
|
||||
|
||||
listFonts();
|
||||
@@ -129,13 +170,14 @@ listFonts();
|
||||
<div class="text-panel">
|
||||
<div class="field">
|
||||
<label>字体</label>
|
||||
<select :value="selectedFontId" @change="pickFont(($event.target as HTMLSelectElement).value)">
|
||||
<select :value="selectedId" @change="pickFont(($event.target as HTMLSelectElement).value)">
|
||||
<option value="" disabled>— 选择本机字体 —</option>
|
||||
<option v-for="f in fonts" :key="f.id" :value="f.id" :disabled="f.needsExtraction">
|
||||
{{ f.family }}{{ f.needsExtraction ? "(.ttc 合集,暂不支持)" : "" }}
|
||||
{{ f.family }}{{ f.subfamily ? " " + f.subfamily : "" }}{{ f.needsExtraction ? "(.ttc 合集,暂不支持)" : "" }}
|
||||
</option>
|
||||
</select>
|
||||
<p v-if="loadingFonts" class="meta">正在读取本机字体…</p>
|
||||
<p v-if="fontError" class="meta error">{{ fontError }}</p>
|
||||
<p v-else-if="loadingFonts" class="meta">正在读取本机字体…</p>
|
||||
<p v-else-if="fonts.length === 0" class="meta">未找到可用字体(本机需装 .ttf / .otf 字体)。</p>
|
||||
</div>
|
||||
|
||||
@@ -227,6 +269,10 @@ listFonts();
|
||||
line-height: 1.6;
|
||||
}
|
||||
|
||||
.meta.error {
|
||||
color: var(--accent);
|
||||
}
|
||||
|
||||
.group {
|
||||
border: 1px solid var(--line);
|
||||
border-radius: var(--radius);
|
||||
|
||||
Reference in New Issue
Block a user