241 lines
7.0 KiB
JavaScript
241 lines
7.0 KiB
JavaScript
const { request, downloadFile, readFile } = require('./wx-promisify')
|
|
const { ensureRouteReady, checkRouteOnFailure } = require('./route-manager')
|
|
|
|
const localFonts = require('../../assets/fonts')
|
|
const localDefaultConfig = require('../../assets/default')
|
|
|
|
const fontBufferCache = new Map()
|
|
const MAX_FONT_CACHE = 4
|
|
|
|
function normalizePath(path, baseUrl) {
|
|
if (!path) {
|
|
return ''
|
|
}
|
|
if (/^https?:\/\//i.test(path)) {
|
|
return path
|
|
}
|
|
if (path.startsWith('//')) {
|
|
return `https:${path}`
|
|
}
|
|
if (path.startsWith('/')) {
|
|
return `${baseUrl}${path}`
|
|
}
|
|
return `${baseUrl}/${path}`
|
|
}
|
|
|
|
function normalizeFontItem(item, baseUrl) {
|
|
const path = item.path || item.url || ''
|
|
const normalizedPath = normalizePath(path, baseUrl)
|
|
const filename = item.filename || normalizedPath.split('/').pop() || `${item.name || 'font'}.ttf`
|
|
return {
|
|
id: item.id || `${item.category || '默认'}/${item.name || filename}`,
|
|
name: item.name || filename.replace(/\.[^.]+$/, ''),
|
|
category: item.category || '默认',
|
|
filename,
|
|
path,
|
|
url: normalizedPath,
|
|
}
|
|
}
|
|
|
|
function normalizeManifest(fonts, baseUrl) {
|
|
if (!Array.isArray(fonts)) {
|
|
return []
|
|
}
|
|
return fonts
|
|
.map((item) => normalizeFontItem(item, baseUrl))
|
|
.filter((item) => item.url)
|
|
}
|
|
|
|
function normalizeDefaultConfig(config) {
|
|
const payload = config && typeof config === 'object' ? config : {}
|
|
const selectedRaw = Array.isArray(payload.selectedFontIds)
|
|
? payload.selectedFontIds
|
|
: (Array.isArray(payload.selectedFonts) ? payload.selectedFonts : [])
|
|
const favoriteRaw = Array.isArray(payload.favoriteFontIds)
|
|
? payload.favoriteFontIds
|
|
: (Array.isArray(payload.favoriteFonts) ? payload.favoriteFonts : [])
|
|
|
|
const selectedFontIds = Array.from(
|
|
new Set(
|
|
selectedRaw
|
|
.map((item) => String(item || '').trim())
|
|
.filter(Boolean),
|
|
),
|
|
)
|
|
const favoriteFontIds = Array.from(
|
|
new Set(
|
|
favoriteRaw
|
|
.map((item) => String(item || '').trim())
|
|
.filter(Boolean),
|
|
),
|
|
)
|
|
|
|
const result = {
|
|
selectedFontIds,
|
|
favoriteFontIds,
|
|
}
|
|
|
|
if (typeof payload.inputText === 'string') {
|
|
result.inputText = payload.inputText
|
|
}
|
|
if (typeof payload.textColor === 'string') {
|
|
result.textColor = payload.textColor
|
|
}
|
|
if (payload.fontSize !== undefined && payload.fontSize !== null && payload.fontSize !== '') {
|
|
const fontSize = Number(payload.fontSize)
|
|
if (Number.isFinite(fontSize)) {
|
|
result.fontSize = fontSize
|
|
}
|
|
}
|
|
if (payload.letterSpacing !== undefined && payload.letterSpacing !== null && payload.letterSpacing !== '') {
|
|
const letterSpacing = Number(payload.letterSpacing)
|
|
if (Number.isFinite(letterSpacing)) {
|
|
result.letterSpacing = letterSpacing
|
|
}
|
|
}
|
|
|
|
return result
|
|
}
|
|
|
|
function buildDefaultConfigUrl(manifestUrl, baseUrl) {
|
|
const manifest = String(manifestUrl || '').trim()
|
|
if (manifest) {
|
|
const withoutHash = manifest.split('#')[0]
|
|
const [pathPart] = withoutHash.split('?')
|
|
const slashIndex = pathPart.lastIndexOf('/')
|
|
if (slashIndex >= 0) {
|
|
return `${pathPart.slice(0, slashIndex + 1)}default.json`
|
|
}
|
|
}
|
|
return normalizePath('/miniprogram/assets/default.json', baseUrl)
|
|
}
|
|
|
|
async function loadFontsManifest(options = {}) {
|
|
const app = getApp()
|
|
await ensureRouteReady(app)
|
|
const manifestUrl = options.manifestUrl || app.globalData.fontsManifestUrl
|
|
const baseUrl = options.baseUrl || app.globalData.fontsBaseUrl
|
|
|
|
if (Array.isArray(app.globalData.fonts) && app.globalData.fonts.length > 0) {
|
|
return app.globalData.fonts
|
|
}
|
|
|
|
try {
|
|
const response = await request({
|
|
url: manifestUrl,
|
|
method: 'GET',
|
|
timeout: 10000,
|
|
})
|
|
|
|
if (response.statusCode < 200 || response.statusCode >= 300) {
|
|
throw new Error(`获取字体清单失败,状态码: ${response.statusCode}`)
|
|
}
|
|
|
|
const fonts = normalizeManifest(response.data, baseUrl)
|
|
if (!fonts.length) {
|
|
throw new Error('字体清单为空')
|
|
}
|
|
|
|
app.globalData.fonts = fonts
|
|
return fonts
|
|
} catch (error) {
|
|
console.warn('远程字体清单加载失败,回退到本地清单:', error)
|
|
await checkRouteOnFailure(app).catch((routeError) => {
|
|
console.warn('字体清单失败后的路由检查失败:', routeError)
|
|
})
|
|
const effectiveBaseUrl = app.globalData.fontsBaseUrl || baseUrl
|
|
const fallbackFonts = normalizeManifest(localFonts, effectiveBaseUrl)
|
|
app.globalData.fonts = fallbackFonts
|
|
return fallbackFonts
|
|
}
|
|
}
|
|
|
|
async function loadDefaultConfig(options = {}) {
|
|
const app = getApp()
|
|
await ensureRouteReady(app)
|
|
const manifestUrl = options.manifestUrl || app.globalData.fontsManifestUrl
|
|
const baseUrl = options.baseUrl || app.globalData.fontsBaseUrl
|
|
const defaultConfigUrl = options.defaultConfigUrl ||
|
|
app.globalData.defaultConfigUrl ||
|
|
buildDefaultConfigUrl(manifestUrl, baseUrl)
|
|
|
|
if (app.globalData.defaultConfig && typeof app.globalData.defaultConfig === 'object') {
|
|
return app.globalData.defaultConfig
|
|
}
|
|
|
|
try {
|
|
const response = await request({
|
|
url: defaultConfigUrl,
|
|
method: 'GET',
|
|
timeout: 10000,
|
|
})
|
|
|
|
if (response.statusCode < 200 || response.statusCode >= 300) {
|
|
throw new Error(`获取默认配置失败,状态码: ${response.statusCode}`)
|
|
}
|
|
|
|
const remoteConfig = normalizeDefaultConfig(response.data)
|
|
app.globalData.defaultConfig = remoteConfig
|
|
return remoteConfig
|
|
} catch (error) {
|
|
console.warn('远程 default.json 加载失败,回退到本地默认配置:', error)
|
|
await checkRouteOnFailure(app).catch((routeError) => {
|
|
console.warn('default 配置失败后的路由检查失败:', routeError)
|
|
})
|
|
const fallbackConfig = normalizeDefaultConfig(localDefaultConfig)
|
|
app.globalData.defaultConfig = fallbackConfig
|
|
return fallbackConfig
|
|
}
|
|
}
|
|
|
|
function setLruCache(key, value) {
|
|
if (fontBufferCache.has(key)) {
|
|
fontBufferCache.delete(key)
|
|
}
|
|
fontBufferCache.set(key, value)
|
|
|
|
while (fontBufferCache.size > MAX_FONT_CACHE) {
|
|
const firstKey = fontBufferCache.keys().next().value
|
|
fontBufferCache.delete(firstKey)
|
|
}
|
|
}
|
|
|
|
async function loadFontBuffer(fontItem) {
|
|
const cacheKey = fontItem.id
|
|
if (fontBufferCache.has(cacheKey)) {
|
|
const cached = fontBufferCache.get(cacheKey)
|
|
setLruCache(cacheKey, cached)
|
|
return cached
|
|
}
|
|
|
|
if (!fontItem.url) {
|
|
throw new Error('字体地址为空')
|
|
}
|
|
|
|
const downloadRes = await downloadFile({ url: fontItem.url })
|
|
if (downloadRes.statusCode < 200 || downloadRes.statusCode >= 300) {
|
|
throw new Error(`字体下载失败,状态码: ${downloadRes.statusCode}`)
|
|
}
|
|
|
|
const readRes = await readFile(downloadRes.tempFilePath)
|
|
const result = {
|
|
tempFilePath: downloadRes.tempFilePath,
|
|
buffer: readRes.data,
|
|
}
|
|
|
|
setLruCache(cacheKey, result)
|
|
return result
|
|
}
|
|
|
|
function listCategories(fonts) {
|
|
const set = new Set(fonts.map((font) => font.category || '默认'))
|
|
return ['全部', '收藏', ...Array.from(set)]
|
|
}
|
|
|
|
module.exports = {
|
|
loadFontsManifest,
|
|
loadDefaultConfig,
|
|
loadFontBuffer,
|
|
listCategories,
|
|
}
|