Font related improvements
This commit is contained in:
@@ -56,6 +56,13 @@ build 把同一个 build ID 注入浏览器 bundle 与 worker,发现更新后
|
||||
metadata 的唯一文案来源。Vite 在构建时解析并注入它,Rust 则嵌入同一文件来验证允许保存的 locale。增加文案键时必须为每个 locale 提供值;增加语言时同时增加完整
|
||||
`[locales."<code>".messages]` 段。业务命令如“点歌”是 Bilibili 输入协议,不属于 UI 翻译。
|
||||
|
||||
## 字体资源
|
||||
|
||||
Noto Serif SC、ZCOOL XiaoWei 与 LXGW
|
||||
WenKai 由锁定的 Fontsource 依赖提供;漓雨手书则以固定版本的 WOFF2 文件保存在
|
||||
`public/fonts/`。浏览器从 Rust 静态服务同域加载这些字体,不依赖 OBS 设备的系统字体。每套字体的 OFL-1.1 许可证均输出到
|
||||
`/fonts/licenses/`。
|
||||
|
||||
## 格式化与构建
|
||||
|
||||
```bash
|
||||
|
||||
Generated
+30
@@ -8,6 +8,9 @@
|
||||
"name": "lxc-stream-overlay",
|
||||
"version": "0.1.0",
|
||||
"dependencies": {
|
||||
"@fontsource/lxgw-wenkai": "5.3.0",
|
||||
"@fontsource/noto-serif-sc": "5.3.0",
|
||||
"@fontsource/zcool-xiaowei": "5.3.0",
|
||||
"react": "19.1.1",
|
||||
"react-dom": "19.1.1"
|
||||
},
|
||||
@@ -745,6 +748,33 @@
|
||||
"node": ">=18"
|
||||
}
|
||||
},
|
||||
"node_modules/@fontsource/lxgw-wenkai": {
|
||||
"version": "5.3.0",
|
||||
"resolved": "https://registry.npmjs.org/@fontsource/lxgw-wenkai/-/lxgw-wenkai-5.3.0.tgz",
|
||||
"integrity": "sha512-+U5AAOXeaB7o+3G0Qm7I7Djz2Jd1hqNY+kClOZH8bTunyyGa2WxY12LZftt2AqUsxlfxwdQpd75Js1HQIt4H4g==",
|
||||
"license": "OFL-1.1",
|
||||
"funding": {
|
||||
"url": "https://github.com/sponsors/ayuhito"
|
||||
}
|
||||
},
|
||||
"node_modules/@fontsource/noto-serif-sc": {
|
||||
"version": "5.3.0",
|
||||
"resolved": "https://registry.npmjs.org/@fontsource/noto-serif-sc/-/noto-serif-sc-5.3.0.tgz",
|
||||
"integrity": "sha512-0/zaEFkidiWldE62rTeD74x8ygUsQvejiSNtO0LQxQk3qpaHnlMZ3w4C7yH80B4KTIg8VKeFP4oSgwWMchY9+g==",
|
||||
"license": "OFL-1.1",
|
||||
"funding": {
|
||||
"url": "https://github.com/sponsors/ayuhito"
|
||||
}
|
||||
},
|
||||
"node_modules/@fontsource/zcool-xiaowei": {
|
||||
"version": "5.3.0",
|
||||
"resolved": "https://registry.npmjs.org/@fontsource/zcool-xiaowei/-/zcool-xiaowei-5.3.0.tgz",
|
||||
"integrity": "sha512-iRuUPl9ONFbYzi6JcEshpE4VedINyzQ4hv0Z5XEt0cB0pMPiQeqc2Qvv1NwJCNzkPwG02VCdcO+cF2K8ylYn2A==",
|
||||
"license": "OFL-1.1",
|
||||
"funding": {
|
||||
"url": "https://github.com/sponsors/ayuhito"
|
||||
}
|
||||
},
|
||||
"node_modules/@jridgewell/gen-mapping": {
|
||||
"version": "0.3.13",
|
||||
"resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.13.tgz",
|
||||
|
||||
@@ -3,13 +3,16 @@
|
||||
"private": true,
|
||||
"version": "0.1.0",
|
||||
"scripts": {
|
||||
"build": "tsc -b && vite build",
|
||||
"build": "tsc -b && vite build && node scripts/check-font-assets.mjs",
|
||||
"dev": "vite",
|
||||
"docs:check": "node scripts/check-doc-links.mjs ../..",
|
||||
"format": "prettier --ignore-path ../../.prettierignore --write . ../server-rust/README.md ../../README.md ../../docs ../../compose.yaml",
|
||||
"format:check": "prettier --ignore-path ../../.prettierignore --check . ../server-rust/README.md ../../README.md ../../docs ../../compose.yaml"
|
||||
},
|
||||
"dependencies": {
|
||||
"@fontsource/lxgw-wenkai": "5.3.0",
|
||||
"@fontsource/noto-serif-sc": "5.3.0",
|
||||
"@fontsource/zcool-xiaowei": "5.3.0",
|
||||
"react": "19.1.1",
|
||||
"react-dom": "19.1.1"
|
||||
},
|
||||
|
||||
@@ -0,0 +1,19 @@
|
||||
# Bundled font notice
|
||||
|
||||
## Liyu Shoushu
|
||||
|
||||
`liyu-shoushu-v0.107.woff2` is a WOFF2 conversion of `LiyuShoushu.ttf` from the official
|
||||
[Liyu Shoushu v0.107 release](https://github.com/InkPantherType/LiyuShoushu/releases/tag/v0.107).
|
||||
|
||||
- Release tag commit: `219a0f42cb7b3f0d00be6e5bf9f8b5298a1f3060`
|
||||
- Original TTF SHA-256: `16554439b7e95da1fc354ed7b2c1551e83e942c3c5afb2a2e883761c6037dc22`
|
||||
- Bundled WOFF2 SHA-256: `b17f8c4fc6612a539d7a46e0eb5f57fdc732cff1d17804563b00a3f0a7eb9ab3`
|
||||
- Conversion: FontTools 4.63.0 WOFF2 compression with Brotli 1.2.0; no glyph subsetting
|
||||
- Coverage retained: 18,828 glyphs and 18,221 Unicode mappings
|
||||
- License: SIL Open Font License 1.1; see
|
||||
[`licenses/liyu-shoushu-OFL.txt`](licenses/liyu-shoushu-OFL.txt)
|
||||
|
||||
The original font credits Yuji Kataoka and the Yuji Project Authors for the base brush glyphs, LXGW
|
||||
and the LXGW ZhenKai Project Authors for punctuation and symbols, and Yuchen Tian's
|
||||
[zi2zi-JiT](https://github.com/kaonashi-tyc/zi2zi-JiT) for generated Chinese glyphs. This notice
|
||||
preserves the zi2zi-JiT attribution required by the upstream font documentation.
|
||||
@@ -0,0 +1,94 @@
|
||||
Copyright 2026 Alexander Tseng.
|
||||
Portions Copyright 2021 The Yuji Project Authors (https://github.com/Kinutafontfactory/Yuji).
|
||||
Portions Copyright LXGW and LXGWZhenKai Project Authors (https://github.com/lxgw/LxgwZhenKai).
|
||||
|
||||
This Font Software is licensed under the SIL Open Font License, Version 1.1.
|
||||
This license is copied below, and is also available with a FAQ at:
|
||||
https://scripts.sil.org/OFL
|
||||
|
||||
-----------------------------------------------------------
|
||||
SIL OPEN FONT LICENSE Version 1.1 - 26 February 2007
|
||||
-----------------------------------------------------------
|
||||
|
||||
PREAMBLE
|
||||
The goals of the Open Font License (OFL) are to stimulate worldwide
|
||||
development of collaborative font projects, to support the font creation
|
||||
efforts of academic and linguistic communities, and to provide a free and
|
||||
open framework in which fonts may be shared and improved in partnership
|
||||
with others.
|
||||
|
||||
The OFL allows the licensed fonts to be used, studied, modified and
|
||||
redistributed freely as long as they are not sold by themselves. The
|
||||
fonts, including any derivative works, can be bundled, embedded,
|
||||
redistributed and/or sold with any software provided that any reserved
|
||||
names are not used by derivative works. The fonts and derivatives,
|
||||
however, cannot be released under any other type of license. The
|
||||
requirement for fonts to remain under this license does not apply
|
||||
to any document created using the fonts or their derivatives.
|
||||
|
||||
DEFINITIONS
|
||||
"Font Software" refers to the set of files released by the Copyright
|
||||
Holder(s) under this license and clearly marked as such. This may
|
||||
include source files, build scripts and documentation.
|
||||
|
||||
"Reserved Font Name" refers to any names specified as such after the
|
||||
copyright statement(s).
|
||||
|
||||
"Original Version" refers to the collection of Font Software components as
|
||||
distributed by the Copyright Holder(s).
|
||||
|
||||
"Modified Version" refers to any derivative made by adding to, deleting,
|
||||
or substituting -- in part or in whole -- any of the components of the
|
||||
Original Version, by changing formats or by porting the Font Software to a
|
||||
new environment.
|
||||
|
||||
"Author" refers to any designer, engineer, programmer, technical
|
||||
writer or other person who contributed to the Font Software.
|
||||
|
||||
PERMISSION & CONDITIONS
|
||||
Permission is hereby granted, free of charge, to any person obtaining
|
||||
a copy of the Font Software, to use, study, copy, merge, embed, modify,
|
||||
redistribute, and sell modified and unmodified copies of the Font
|
||||
Software, subject to the following conditions:
|
||||
|
||||
1) Neither the Font Software nor any of its individual components,
|
||||
in Original or Modified Versions, may be sold by itself.
|
||||
|
||||
2) Original or Modified Versions of the Font Software may be bundled,
|
||||
redistributed and/or sold with any software, provided that each copy
|
||||
contains the above copyright notice and this license. These can be
|
||||
included either as stand-alone text files, human-readable headers or
|
||||
in the appropriate machine-readable metadata fields within text or
|
||||
binary files as long as those fields can be easily viewed by the user.
|
||||
|
||||
3) No Modified Version of the Font Software may use the Reserved Font
|
||||
Name(s) unless explicit written permission is granted by the corresponding
|
||||
Copyright Holder. This restriction only applies to the primary font name as
|
||||
presented to the users.
|
||||
|
||||
4) The name(s) of the Copyright Holder(s) or the Author(s) of the Font
|
||||
Software shall not be used to promote, endorse or advertise any
|
||||
Modified Version, except to acknowledge the contribution(s) of the
|
||||
Copyright Holder(s) and the Author(s) or with their explicit written
|
||||
permission.
|
||||
|
||||
5) The Font Software, modified or unmodified, in part or in whole,
|
||||
must be distributed entirely under this license, and must not be
|
||||
distributed under any other license. The requirement for fonts to
|
||||
remain under this license does not apply to any document created
|
||||
using the Font Software.
|
||||
|
||||
TERMINATION
|
||||
This license becomes null and void if any of the above conditions are
|
||||
not met.
|
||||
|
||||
DISCLAIMER
|
||||
THE FONT SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
||||
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO ANY WARRANTIES OF
|
||||
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT
|
||||
OF COPYRIGHT, PATENT, TRADEMARK, OR OTHER RIGHT. IN NO EVENT SHALL THE
|
||||
COPYRIGHT HOLDER BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
|
||||
INCLUDING ANY GENERAL, SPECIAL, INDIRECT, INCIDENTAL, OR CONSEQUENTIAL
|
||||
DAMAGES, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
|
||||
FROM, OUT OF THE USE OR INABILITY TO USE THE FONT SOFTWARE OR FROM
|
||||
OTHER DEALINGS IN THE FONT SOFTWARE.
|
||||
Binary file not shown.
@@ -66,6 +66,7 @@ function isControlShell(pathname) {
|
||||
function isStaticAsset(pathname) {
|
||||
return (
|
||||
pathname.startsWith('/assets/') ||
|
||||
pathname.startsWith('/fonts/') ||
|
||||
pathname.startsWith('/pwa/') ||
|
||||
pathname === '/control/manifest.webmanifest'
|
||||
)
|
||||
|
||||
@@ -0,0 +1,67 @@
|
||||
import { createHash } from 'node:crypto'
|
||||
import { readdir, readFile } from 'node:fs/promises'
|
||||
|
||||
const dist = new URL('../dist/', import.meta.url)
|
||||
|
||||
async function filesBelow(directory, prefix = '') {
|
||||
const entries = await readdir(directory, { withFileTypes: true })
|
||||
const files = []
|
||||
for (const entry of entries) {
|
||||
const relative = `${prefix}${entry.name}`
|
||||
if (entry.isDirectory()) {
|
||||
files.push(...(await filesBelow(new URL(`${entry.name}/`, directory), `${relative}/`)))
|
||||
} else {
|
||||
files.push(relative)
|
||||
}
|
||||
}
|
||||
return files
|
||||
}
|
||||
|
||||
const files = await filesBelow(dist)
|
||||
const expectedFonts = ['noto-serif-sc', 'zcool-xiaowei', 'lxgw-wenkai']
|
||||
const liyuFont = 'fonts/liyu-shoushu-v0.107.woff2'
|
||||
|
||||
for (const font of expectedFonts) {
|
||||
if (!files.some(file => file.startsWith(`assets/${font}-`) && file.endsWith('.woff2'))) {
|
||||
throw new Error(`Missing bundled WOFF2 assets for ${font}`)
|
||||
}
|
||||
if (!files.includes(`fonts/licenses/${font}-OFL.txt`)) {
|
||||
throw new Error(`Missing bundled license for ${font}`)
|
||||
}
|
||||
}
|
||||
|
||||
if (!files.includes(liyuFont)) throw new Error('Missing bundled WOFF2 asset for liyu-shoushu')
|
||||
if (!files.includes('fonts/licenses/liyu-shoushu-OFL.txt')) {
|
||||
throw new Error('Missing bundled license for liyu-shoushu')
|
||||
}
|
||||
const liyuDigest = createHash('sha256')
|
||||
.update(await readFile(new URL(liyuFont, dist)))
|
||||
.digest('hex')
|
||||
if (liyuDigest !== 'b17f8c4fc6612a539d7a46e0eb5f57fdc732cff1d17804563b00a3f0a7eb9ab3') {
|
||||
throw new Error(`Unexpected liyu-shoushu WOFF2 digest: ${liyuDigest}`)
|
||||
}
|
||||
|
||||
const legacyWoff = files.find(file => file.endsWith('.woff'))
|
||||
if (legacyWoff) throw new Error(`Unexpected legacy WOFF asset: ${legacyWoff}`)
|
||||
|
||||
const css = (
|
||||
await Promise.all(
|
||||
files.filter(file => file.endsWith('.css')).map(file => readFile(new URL(file, dist), 'utf8')),
|
||||
)
|
||||
).join('\n')
|
||||
|
||||
for (const family of ['Noto Serif SC', 'ZCOOL XiaoWei', 'LXGW WenKai', 'Liyu Shoushu']) {
|
||||
const declarations = [
|
||||
`font-family:${family}`,
|
||||
`font-family:${JSON.stringify(family)}`,
|
||||
`font-family:'${family}'`,
|
||||
]
|
||||
if (!declarations.some(declaration => css.includes(declaration))) {
|
||||
throw new Error(`Missing @font-face declaration for ${family}`)
|
||||
}
|
||||
}
|
||||
if (!css.includes('font-display:block') || css.includes('font-display:swap')) {
|
||||
throw new Error('Bundled fonts must block local fallback while loading')
|
||||
}
|
||||
|
||||
console.log('Verified same-origin WOFF2 component fonts and licenses.')
|
||||
@@ -0,0 +1,7 @@
|
||||
@font-face {
|
||||
font-family: 'Liyu Shoushu';
|
||||
font-style: normal;
|
||||
font-display: block;
|
||||
font-weight: 400;
|
||||
src: url('/fonts/liyu-shoushu-v0.107.woff2') format('woff2');
|
||||
}
|
||||
@@ -25,6 +25,10 @@ import { SongRequestOverlay } from './songOverlay'
|
||||
import { useComponentStream } from './stream'
|
||||
import { I18nProvider, translate, useI18n } from './i18n'
|
||||
import type { Session } from './types'
|
||||
import '@fontsource/noto-serif-sc/400.css'
|
||||
import '@fontsource/zcool-xiaowei/400.css'
|
||||
import '@fontsource/lxgw-wenkai/500.css'
|
||||
import './fonts.css'
|
||||
import './style.css'
|
||||
import './control.css'
|
||||
import './giftEffect.css'
|
||||
|
||||
@@ -105,6 +105,8 @@
|
||||
}
|
||||
|
||||
.gift-menu-overlay.gift-menu-theme-moonlit-water {
|
||||
height: 100%;
|
||||
align-items: center;
|
||||
padding-block: calc(clamp(8px, 1.3vmin, 16px) + var(--moonlit-edge-space));
|
||||
}
|
||||
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import type { CSSProperties } from 'react'
|
||||
|
||||
/** Stable IDs persisted by the backend; each maps to a fixed, injection-safe stack. */
|
||||
export const fontFamilyIds = ['song', 'fang-song', 'kai'] as const
|
||||
/** Stable IDs persisted by the backend; each maps to a bundled, same-origin webfont. */
|
||||
export const fontFamilyIds = ['song', 'fang-song', 'kai', 'liyu-shoushu'] as const
|
||||
|
||||
export type FontFamilyId = (typeof fontFamilyIds)[number]
|
||||
|
||||
@@ -9,12 +9,14 @@ export const fontFamilies: ReadonlyArray<{ id: FontFamilyId; nameKey: string }>
|
||||
{ id: 'song', nameKey: 'settings.font_family.song' },
|
||||
{ id: 'fang-song', nameKey: 'settings.font_family.fang_song' },
|
||||
{ id: 'kai', nameKey: 'settings.font_family.kai' },
|
||||
{ id: 'liyu-shoushu', nameKey: 'settings.font_family.liyu_shoushu' },
|
||||
]
|
||||
|
||||
const fontStacks: Record<FontFamilyId, string> = {
|
||||
song: '"Source Han Serif SC", "Noto Serif CJK SC", "Noto Serif SC", "Songti SC", STSong, SimSun, serif',
|
||||
'fang-song': 'FangSong, STFangsong, "FZShuSong-Z01", "Noto Serif SC", "Songti SC", serif',
|
||||
kai: '"Kaiti SC", STKaiti, KaiTi, "LXGW WenKai", "Noto Serif SC", serif',
|
||||
song: '"Noto Serif SC", serif',
|
||||
'fang-song': '"ZCOOL XiaoWei", serif',
|
||||
kai: '"LXGW WenKai", serif',
|
||||
'liyu-shoushu': '"Liyu Shoushu", serif',
|
||||
}
|
||||
|
||||
export function normalizeFontFamilyId(value: unknown): FontFamilyId {
|
||||
|
||||
@@ -14,6 +14,13 @@ import { parse } from 'smol-toml'
|
||||
// The waiting worker is still activated explicitly by the user in the console.
|
||||
const pwaBuildId = Date.now().toString(36)
|
||||
const serviceWorkerSource = readFileSync(new URL('./pwa/control-sw.js', import.meta.url), 'utf8')
|
||||
const bundledFontLicenses = ['noto-serif-sc', 'zcool-xiaowei', 'lxgw-wenkai'].map(font => ({
|
||||
fileName: `fonts/licenses/${font}-OFL.txt`,
|
||||
source: readFileSync(
|
||||
new URL(`./node_modules/@fontsource/${font}/LICENSE`, import.meta.url),
|
||||
'utf8',
|
||||
),
|
||||
}))
|
||||
const i18nResource = parse(
|
||||
readFileSync(new URL('../../resources/i18n.toml', import.meta.url), 'utf8'),
|
||||
) as unknown as {
|
||||
@@ -51,6 +58,16 @@ const manifest = {
|
||||
export default defineConfig({
|
||||
plugins: [
|
||||
react(),
|
||||
{
|
||||
name: 'same-origin-woff2-fonts',
|
||||
enforce: 'pre',
|
||||
transform(source, id) {
|
||||
if (!id.includes('/node_modules/@fontsource/') || !id.endsWith('.css')) return
|
||||
return source
|
||||
.replaceAll('font-display: swap', 'font-display: block')
|
||||
.replace(/,\s*url\([^)]*\.woff\) format\(['"]woff['"]\)/g, '')
|
||||
},
|
||||
},
|
||||
{
|
||||
name: 'control-pwa-assets',
|
||||
transformIndexHtml(html) {
|
||||
@@ -61,6 +78,9 @@ export default defineConfig({
|
||||
.replaceAll('__APP_DESCRIPTION__', defaultMessages['pwa.manifest.description'])
|
||||
},
|
||||
generateBundle() {
|
||||
for (const license of bundledFontLicenses) {
|
||||
this.emitFile({ type: 'asset', ...license })
|
||||
}
|
||||
this.emitFile({
|
||||
type: 'asset',
|
||||
fileName: 'control/sw.js',
|
||||
|
||||
Generated
+1
@@ -956,6 +956,7 @@ checksum = "68ab91017fe16c622486840e4c83c9a37afeff978bd239b5293d61ece587de66"
|
||||
[[package]]
|
||||
name = "libilibili"
|
||||
version = "0.1.0"
|
||||
source = "git+http://gitea.home.arpa/felis/libilibili.git#3cb2a902d4d9387c5581a1827cc19248b6000ae2"
|
||||
dependencies = [
|
||||
"base64",
|
||||
"brotli",
|
||||
|
||||
@@ -7,20 +7,30 @@ edition = "2024"
|
||||
axum = { version = "0.8", features = ["ws", "json"] }
|
||||
async-trait = "0.1"
|
||||
base64 = "0.22"
|
||||
chrono = { version = "0.4", default-features = false, features = ["clock", "serde"] }
|
||||
chrono = { version = "0.4", default-features = false, features = [
|
||||
"clock",
|
||||
"serde",
|
||||
] }
|
||||
chacha20poly1305 = "0.10"
|
||||
deadpool-postgres = "0.14"
|
||||
# Kept as a sibling checkout during development. Docker Compose supplies the
|
||||
# same directory as a named BuildKit context at `/libilibili`.
|
||||
libilibili = { path = "../../../libilibili" }
|
||||
libilibili = { git = "http://gitea.home.arpa/felis/libilibili.git" }
|
||||
rand = "0.9"
|
||||
reqwest = { version = "0.12", default-features = false, features = ["json", "rustls-tls"] }
|
||||
reqwest = { version = "0.12", default-features = false, features = [
|
||||
"json",
|
||||
"rustls-tls",
|
||||
] }
|
||||
serde = { version = "1", features = ["derive"] }
|
||||
serde_json = "1"
|
||||
sha2 = "0.10"
|
||||
subtle = "2"
|
||||
tokio = { version = "1", features = ["full"] }
|
||||
tokio-postgres = { version = "0.7", features = ["with-serde_json-1", "with-uuid-1", "with-chrono-0_4"] }
|
||||
tokio-postgres = { version = "0.7", features = [
|
||||
"with-serde_json-1",
|
||||
"with-uuid-1",
|
||||
"with-chrono-0_4",
|
||||
] }
|
||||
tokio-util = "0.7"
|
||||
toml = "0.8"
|
||||
totp-rs = { version = "5.7", features = ["gen_secret", "qr", "zeroize"] }
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
//! Shared, validated typography settings for all built-in OBS components.
|
||||
//!
|
||||
//! Font IDs map to fixed frontend font stacks rather than accepting arbitrary
|
||||
//! CSS. This keeps settings portable between OBS hosts and prevents untrusted
|
||||
//! component settings from becoming a CSS injection boundary.
|
||||
//! Font IDs map to fixed, same-origin WOFF2 assets in the frontend rather than
|
||||
//! accepting arbitrary CSS. This keeps settings portable between OBS hosts and
|
||||
//! prevents untrusted component settings from becoming a CSS injection boundary.
|
||||
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
@@ -13,6 +13,7 @@ pub enum FontFamilyId {
|
||||
#[default]
|
||||
FangSong,
|
||||
Kai,
|
||||
LiyuShoushu,
|
||||
}
|
||||
|
||||
pub const fn default_font_brightness() -> u16 {
|
||||
@@ -35,5 +36,9 @@ mod tests {
|
||||
);
|
||||
assert_eq!(sanitize_font_brightness(1), 70);
|
||||
assert_eq!(sanitize_font_brightness(u16::MAX), 180);
|
||||
assert_eq!(
|
||||
serde_json::to_string(&FontFamilyId::LiyuShoushu).unwrap(),
|
||||
"\"liyu-shoushu\""
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -12,28 +12,30 @@
|
||||
|
||||
## 设置
|
||||
|
||||
| 字段 | 范围/单位 | 行为 |
|
||||
| ------------------------ | ----------- | -------------------------- |
|
||||
| `themeId` | 主题 ID | 选择已安装的完整视觉主题 |
|
||||
| `fontFamily` | 字体 ID | `song`、`fang-song`、`kai` |
|
||||
| `fontBrightness` | 70–180% | 只调整文字亮度 |
|
||||
| `viewerColor` | `#RRGGBB` | 可选的用户昵称颜色覆盖 |
|
||||
| `danmakuColor` | `#RRGGBB` | 可选的弹幕正文颜色覆盖 |
|
||||
| `fontScale` | 50–300% | 展开与收缩字号的统一比例 |
|
||||
| `decorationLineWeight` | 50–300% | 边框、分隔线与古风边缘粗细 |
|
||||
| `maxVisible` | 1–12 | 同时保留的消息卡数量 |
|
||||
| `collapseAfterSeconds` | 2–120 秒 | 最新卡从展开态切换到紧凑态 |
|
||||
| `unfoldDurationMs` | 200–5000 ms | 横向卷轴展开动画时间 |
|
||||
| `motionIntensity` | 0–100% | 卡片、流光与焦点动画强度 |
|
||||
| `particleCount` | 0–12 | 每卡星花粒子数量 |
|
||||
| `particleSpeed` | 25–300% | 粒子动画速度 |
|
||||
| `lowPerformanceMode` | boolean | 关闭高成本动态效果 |
|
||||
| `highValueThreshold` | 千分之一元 | 高价值礼物起点 |
|
||||
| `featuredValueThreshold` | 千分之一元 | 焦点礼物起点 |
|
||||
| `show*` | boolean | 控制各事件类别订阅 |
|
||||
| 字段 | 范围/单位 | 行为 |
|
||||
| ------------------------ | ----------- | ------------------------------------------ |
|
||||
| `themeId` | 主题 ID | 选择已安装的完整视觉主题 |
|
||||
| `fontFamily` | 字体 ID | `song`、`fang-song`、`kai`、`liyu-shoushu` |
|
||||
| `fontBrightness` | 70–180% | 只调整文字亮度 |
|
||||
| `viewerColor` | `#RRGGBB` | 可选的用户昵称颜色覆盖 |
|
||||
| `danmakuColor` | `#RRGGBB` | 可选的弹幕正文颜色覆盖 |
|
||||
| `fontScale` | 50–300% | 展开与收缩字号的统一比例 |
|
||||
| `decorationLineWeight` | 50–300% | 边框、分隔线与古风边缘粗细 |
|
||||
| `maxVisible` | 1–12 | 同时保留的消息卡数量 |
|
||||
| `collapseAfterSeconds` | 2–120 秒 | 最新卡从展开态切换到紧凑态 |
|
||||
| `unfoldDurationMs` | 200–5000 ms | 横向卷轴展开动画时间 |
|
||||
| `motionIntensity` | 0–100% | 卡片、流光与焦点动画强度 |
|
||||
| `particleCount` | 0–12 | 每卡星花粒子数量 |
|
||||
| `particleSpeed` | 25–300% | 粒子动画速度 |
|
||||
| `lowPerformanceMode` | boolean | 关闭高成本动态效果 |
|
||||
| `highValueThreshold` | 千分之一元 | 高价值礼物起点 |
|
||||
| `featuredValueThreshold` | 千分之一元 | 焦点礼物起点 |
|
||||
| `show*` | boolean | 控制各事件类别订阅 |
|
||||
|
||||
后端的 `OverlaySettings::sanitize` 是最终边界。控制台 range
|
||||
input 只改善交互,不能取代服务端校验。字体 ID 只映射到前端预先注册的安全字体栈,不接受任意 CSS;OBS 设备缺少首选字体时会依次回退到可用衬线字体。颜色覆盖同样只接受六位十六进制颜色;留空时使用主题色,切换主题后会自动采用新主题的配色。
|
||||
input 只改善交互,不能取代服务端校验。字体 ID 只映射到前端预先注册并由服务同域提供的 WOFF2 字体,不接受任意 CSS,也不依赖 OBS 设备安装的字体;`song`、`fang-song`、`kai`
|
||||
与 `liyu-shoushu` 分别使用 Noto Serif SC、ZCOOL XiaoWei、LXGW
|
||||
WenKai 与漓雨手书。颜色覆盖同样只接受六位十六进制颜色;留空时使用主题色,切换主题后会自动采用新主题的配色。
|
||||
|
||||
## 主题
|
||||
|
||||
|
||||
+10
-8
@@ -142,10 +142,11 @@ name = "简体中文"
|
||||
"nav.logout" = "退出"
|
||||
"settings.theme" = "主题"
|
||||
"settings.font_family" = "中文字体"
|
||||
"settings.font_family_description" = "按 OBS 所在设备已安装字体依次匹配;缺失时自动回退到衬线字体。"
|
||||
"settings.font_family.song" = "宋体 · 端正"
|
||||
"settings.font_family.fang_song" = "仿宋 · 清雅"
|
||||
"settings.font_family.kai" = "楷体 · 书卷"
|
||||
"settings.font_family_description" = "字体随服务提供并由 OBS 同域加载,不依赖设备已安装字体。"
|
||||
"settings.font_family.song" = "思源宋体 · 端正"
|
||||
"settings.font_family.fang_song" = "小薇体 · 清雅"
|
||||
"settings.font_family.kai" = "霞鹜文楷 · 书卷"
|
||||
"settings.font_family.liyu_shoushu" = "漓雨手书 · 毛笔手写"
|
||||
"settings.font_brightness" = "文字亮度"
|
||||
"settings.font_brightness_description" = "只提亮文字,不改变透明背景、礼物图片或直播画面。"
|
||||
"settings.viewer_color" = "用户昵称颜色"
|
||||
@@ -642,10 +643,11 @@ name = "English"
|
||||
"nav.logout" = "Log out"
|
||||
"settings.theme" = "Theme"
|
||||
"settings.font_family" = "Chinese typeface"
|
||||
"settings.font_family_description" = "Uses the first installed font on the OBS device and falls back safely to a serif face."
|
||||
"settings.font_family.song" = "Song · Formal"
|
||||
"settings.font_family.fang_song" = "FangSong · Refined"
|
||||
"settings.font_family.kai" = "Kai · Calligraphic"
|
||||
"settings.font_family_description" = "Fonts are served by this service and loaded from the same origin instead of relying on fonts installed on the OBS device."
|
||||
"settings.font_family.song" = "Noto Serif SC · Formal"
|
||||
"settings.font_family.fang_song" = "ZCOOL XiaoWei · Refined"
|
||||
"settings.font_family.kai" = "LXGW WenKai · Calligraphic"
|
||||
"settings.font_family.liyu_shoushu" = "Liyu Shoushu · Brush handwriting"
|
||||
"settings.font_brightness" = "Text brightness"
|
||||
"settings.font_brightness_description" = "Brightens text only; transparency, gift artwork, and the underlying scene are unchanged."
|
||||
"settings.viewer_color" = "Viewer name color"
|
||||
|
||||
Reference in New Issue
Block a user