Skip to content

Commit ccebe55

Browse files
hjosugiclaude
andcommitted
feat(overlay): font scaling mode, with a relative mode that also shrinks text
The "enlarge text in big players" checkbox becomes a select: fixed, up (the old behaviour and still the default) and relative. Relative treats the font size as the size at a 720 px player, so small windows shrink the text too (down to 0.5x). A stored scaleWithPlayer boolean still reads as up or fixed, in settings and in the engine. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
1 parent 97f32af commit ccebe55

9 files changed

Lines changed: 60 additions & 20 deletions

File tree

‎README.ja.md‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -33,7 +33,7 @@ Chrome ウェブストアで公開済みです。実装済み:
3333
- すべてのフレームからのYouTubeライブチャット抽出
3434
- プレーヤー上の拡張ステージフレームで動くキャンバス弾幕レンダラー
3535
- コメントの右クリックメニュー: ピン留めとドラッグ、そのユーザーのコメントをまとめて非表示(NGリストに保存)
36-
- 全画面ではプレーヤーに合わせて文字を拡大
36+
- プレーヤーに合わせた文字サイズ(「相対」モードでは全画面で拡大し、小さいウィンドウでは縮小)
3737
- YouTubeのチャット欄を閉じてもコメントが流れ続ける(見えないチャットフレームが代わりに読み込み、アーカイブにも対応)
3838
- コメント付きピクチャーインピクチャー(ベータ、Document Picture-in-Picture)
3939
- `extension/scoring.js`内のJavaScriptのみのローカルスコアラー

‎README.md‎

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -39,7 +39,8 @@ Published on the Chrome Web Store. Implemented:
3939
- canvas danmaku renderer in an extension stage frame over the player
4040
- right-click menu on a comment: pin and drag it, or hide everything from its
4141
author (saved to the NG list)
42-
- text that grows with the player in fullscreen
42+
- text size that can track the player (grows in fullscreen, shrinks in small
43+
windows with the "relative" font scaling mode)
4344
- comments keep flowing when YouTube's chat is closed (a hidden chat frame
4445
stands in, replays included)
4546
- picture-in-picture with comments (beta, Document Picture-in-Picture)

‎docs/PLAN.ja.md‎

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -71,6 +71,7 @@
7171

7272
- `lineHeight`(px)は読まなくなりました。保存済みの値は `lineHeightScale` の既定値に正規化されます。エンジンには引き続き px(`fontPx * scale`)を渡します。
7373
- フィルターの保存データに `mode`(`drop` / `censor` / `replace`)と `replacement` が加わりました。古いレコードは `drop` と既定の置換文字に正規化されます。
74+
- 真偽値の `scaleWithPlayer` は `fontScaleMode` セレクト(`fixed` / `up` / `relative`)になりました。保存済みの `true` は `up`、`false` は `fixed` として読みます。`relative` は `fontPx` を 720px のプレイヤーでのサイズとして扱い、小さいウィンドウでは縮小もします(下限 0.5 倍)。
7475

7576
## ストアの審査
7677

‎docs/PLAN.md‎

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -103,6 +103,10 @@ Stored settings:
103103
`lineHeightScale` default; the engine still receives px (`fontPx * scale`).
104104
- The filter store gained `mode` (`drop` / `censor` / `replace`) and
105105
`replacement`. Older records normalize to `drop` and the default replacement.
106+
- The `scaleWithPlayer` boolean became the `fontScaleMode` select (`fixed` /
107+
`up` / `relative`); a stored `true` reads as `up`, `false` as `fixed`.
108+
`relative` treats `fontPx` as the size at a 720 px player and also shrinks the
109+
text in smaller windows (down to 0.5x).
106110

107111
## Store review
108112

‎extension/_locales/en/messages.json‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -45,7 +45,7 @@
4545
"s_outlineBlur": { "message": "Outline blur" },
4646
"s_authorName": { "message": "Show author name" },
4747
"s_sizeByScore": { "message": "Vary size by score" },
48-
"s_scaleWithPlayer": { "message": "Enlarge text in big players (fullscreen)" },
48+
"s_fontScaleMode": { "message": "Font scaling" },
4949
"s_showNormal": { "message": "Show normal users" },
5050
"s_showMember": { "message": "Show members" },
5151
"s_showModerator": { "message": "Show moderators" },

‎extension/_locales/ja/messages.json‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -45,7 +45,7 @@
4545
"s_outlineBlur": { "message": "縁取りのぼかし" },
4646
"s_authorName": { "message": "投稿者名を表示" },
4747
"s_sizeByScore": { "message": "スコアで文字サイズを変える" },
48-
"s_scaleWithPlayer": { "message": "大きいプレーヤーでは文字を大きくする(全画面など)" },
48+
"s_fontScaleMode": { "message": "文字サイズの拡大縮小" },
4949
"s_showNormal": { "message": "一般ユーザーを表示" },
5050
"s_showMember": { "message": "メンバーを表示" },
5151
"s_showModerator": { "message": "モデレーターを表示" },

‎extension/danmaku.js‎

Lines changed: 30 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -33,7 +33,7 @@
3333
topPct: 0.08, // keep top 8% clear
3434
bottomPct: 0.14, // keep bottom 14% clear (controls)
3535
gapPx: 28, // min horizontal gap between same-lane comments
36-
scaleWithPlayer: true, // enlarge text in players taller than SCALE_REF_HEIGHT (fullscreen)
36+
fontScaleMode: "up", // fixed | up (enlarge in big players) | relative (scale both ways)
3737
dedup: false, // drop near-duplicates of recently shown comments
3838
simThreshold: 3, // Hamming distance <= this => near-duplicate
3939
recentMax: 400,
@@ -88,16 +88,19 @@
8888
"textColor",
8989
"roleColors"
9090
];
91-
const GEOMETRY_CONFIG_KEYS = ["dpr", "lineHeight", "topPct", "bottomPct", "scaleWithPlayer"];
91+
const GEOMETRY_CONFIG_KEYS = ["dpr", "lineHeight", "topPct", "bottomPct", "fontScaleMode"];
9292

9393
const COLORS = { owner: "#ffca28", moderator: "#5e9bff", member: "#7CFC8C", normal: "#ffffff" };
9494
const AUTHOR_BOOST = { owner: 0.40, moderator: 0.25, member: 0.10, normal: 0 };
9595
const TARGET_FRAME_MS = 1000 / 60;
9696
const MIN_CAP_FRAME_MS = 50;
9797
const LONG_GAP_MS = 500; // frame gap above this is a pause, not a hitch
98-
// With scaleWithPlayer, sizes are as set up to this player height and grow
99-
// with it beyond (fullscreen); normal and theater players look unchanged.
98+
// Font scaling relative to this player height. `up` keeps the configured size
99+
// up to this height and only grows beyond (fullscreen). `relative` treats the
100+
// configured size as the size at this reference, so a small window shrinks the
101+
// text too (down to SCALE_MIN) instead of letting it dominate the player.
100102
const SCALE_REF_HEIGHT = 720;
103+
const SCALE_MIN = 0.5;
101104
const SCALE_MAX = 3;
102105
const MAX_WRAP_LINES = 3; // hard cap on wrapped lines per comment
103106
// Sprite-atlas geometry (device px). A page is 2 MB RGBA; pages are opened
@@ -128,6 +131,16 @@
128131
return (sig >>> (32 - DEDUP_BUCKET_BITS)) & ((1 << DEDUP_BUCKET_BITS) - 1);
129132
}
130133

134+
// Accept the legacy boolean `scaleWithPlayer` from older configs/callers and
135+
// express it as the current `fontScaleMode` select (extension/settings.js).
136+
function normalizeScaleConfig(cfg) {
137+
if (!cfg || typeof cfg !== "object") return cfg;
138+
if (cfg.fontScaleMode == null && cfg.scaleWithPlayer != null) {
139+
return Object.assign({}, cfg, { fontScaleMode: cfg.scaleWithPlayer ? "up" : "fixed" });
140+
}
141+
return cfg;
142+
}
143+
131144
function truncateText(text, maxChars) {
132145
const s = String(text || "");
133146
if (s.length <= maxChars) return text; // fast path: no spread/array for short text
@@ -153,7 +166,7 @@
153166

154167
class DanmakuOverlay {
155168
constructor(cfg) {
156-
this.cfg = Object.assign({}, DEFAULTS, cfg);
169+
this.cfg = Object.assign({}, DEFAULTS, normalizeScaleConfig(cfg));
157170
this.active = [];
158171
this.nextActive = [];
159172
this.activeMinHeap = [];
@@ -197,7 +210,7 @@
197210
this.drag = null; // active pin-drag state
198211
this._loop = this._loop.bind(this);
199212
this.w = 1; this.h = 1; this.laneCount = 1; this.laneTop = 0; this.laneH = this.cfg.lineHeight;
200-
this.scale = 1; // text scale for the player size (scaleWithPlayer)
213+
this.scale = 1; // text scale for the player size (fontScaleMode)
201214
this.gap = this.cfg.gapPx;
202215
}
203216

@@ -283,13 +296,14 @@
283296
}
284297

285298
setConfig(partial) {
299+
const next = normalizeScaleConfig(partial) || {};
286300
const shouldClearRasterCache = RASTER_CONFIG_KEYS.some((key) =>
287-
partial[key] != null && partial[key] !== this.cfg[key]
301+
next[key] != null && next[key] !== this.cfg[key]
288302
);
289303
const shouldResize = GEOMETRY_CONFIG_KEYS.some((key) =>
290-
partial[key] != null && partial[key] !== this.cfg[key]
304+
next[key] != null && next[key] !== this.cfg[key]
291305
);
292-
Object.assign(this.cfg, partial);
306+
Object.assign(this.cfg, next);
293307
if (shouldClearRasterCache) this.cache.clear();
294308
if (partial.maxActive != null || partial.minActive != null) this._updateDynamicCap();
295309
if (this.canvas && shouldResize) this._resize();
@@ -332,9 +346,13 @@
332346
this.canvas.height = Math.round(this.h * dpr);
333347
const usable = this.h * (1 - this.cfg.topPct - this.cfg.bottomPct);
334348
// Steps of 5% so a window being dragged does not re-rasterize every pixel.
335-
this.scale = this.cfg.scaleWithPlayer
336-
? Math.round(clamp(1, SCALE_MAX, this.h / SCALE_REF_HEIGHT) * 20) / 20
337-
: 1;
349+
const ratio = this.h / SCALE_REF_HEIGHT;
350+
const target = this.cfg.fontScaleMode === "relative"
351+
? clamp(SCALE_MIN, SCALE_MAX, ratio)
352+
: this.cfg.fontScaleMode === "fixed"
353+
? 1
354+
: clamp(1, SCALE_MAX, ratio);
355+
this.scale = Math.round(target * 20) / 20;
338356
this.laneH = Math.round(this.cfg.lineHeight * this.scale);
339357
this.gap = Math.round(this.cfg.gapPx * this.scale);
340358
this.laneCount = Math.max(3, Math.floor(usable / this.laneH));

‎extension/settings.js‎

Lines changed: 11 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -49,7 +49,12 @@
4949
{ value: "always", label: "Always" }
5050
] },
5151
{ key: "sizeByScore", group: "Display", label: "Vary size by score", type: "bool", default: true },
52-
{ key: "scaleWithPlayer", group: "Display", label: "Enlarge text in big players (fullscreen)", type: "bool", default: true },
52+
{ key: "fontScaleMode", group: "Display", label: "Font scaling", type: "select", default: "up",
53+
options: [
54+
{ value: "fixed", label: "Fixed size (same in every player)" },
55+
{ value: "up", label: "Enlarge in big players (fullscreen)" },
56+
{ value: "relative", label: "Scale with player size (relative)" }
57+
] },
5358
{ key: "pinComments", group: "Behavior", label: "Right-click menu on comments (pin, hide user)", type: "bool", default: true },
5459
{ key: "translateTo", group: "Behavior", label: "Translate comments (on-device)", type: "select", default: "",
5560
options: [
@@ -178,7 +183,10 @@
178183
const clean = {};
179184
for (const spec of SCHEMA) {
180185
const value = input[spec.key];
181-
if (spec.type === "bool") clean[spec.key] = normalizeBool(value, spec.default);
186+
// v0.3.x stored a boolean `scaleWithPlayer`; migrate it to the select.
187+
if (spec.key === "fontScaleMode" && value == null && input.scaleWithPlayer != null) {
188+
clean[spec.key] = normalizeBool(input.scaleWithPlayer, true) ? "up" : "fixed";
189+
} else if (spec.type === "bool") clean[spec.key] = normalizeBool(value, spec.default);
182190
else if (spec.type === "range") clean[spec.key] = normalizeRange(spec, value);
183191
else if (spec.type === "color") clean[spec.key] = normalizeColor(value, spec.default);
184192
else if (spec.type === "text") clean[spec.key] = normalizeText(value, spec.default, spec.maxLength);
@@ -219,7 +227,7 @@
219227
outlineBlur: safe.outlineBlur,
220228
authorName: safe.authorName,
221229
sizeByScore: safe.sizeByScore,
222-
scaleWithPlayer: safe.scaleWithPlayer,
230+
fontScaleMode: safe.fontScaleMode,
223231
pinComments: safe.pinComments,
224232
showNormal: safe.showNormal,
225233
showMember: safe.showMember,

‎extension/test/settings-filter.mjs‎

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -54,6 +54,14 @@ assert.equal(settings.PROFILE.surface, "extension")
5454
assert.equal(settings.PROFILE.target, "desktop Chrome extension")
5555
assert.match(settings.PROFILE.rationale, /Desktop/)
5656
assert.equal(settings.DEFAULTS.fontPx, 24)
57+
assert.equal(settings.DEFAULTS.fontScaleMode, "up")
58+
assert.equal(settings.normalize({ fontScaleMode: "relative" }).fontScaleMode, "relative")
59+
assert.equal(settings.normalize({ fontScaleMode: "bogus" }).fontScaleMode, "up")
60+
// v0.3.x boolean migrates onto the select.
61+
assert.equal(settings.normalize({ scaleWithPlayer: false }).fontScaleMode, "fixed")
62+
assert.equal(settings.normalize({ scaleWithPlayer: true }).fontScaleMode, "up")
63+
assert.equal(settings.toEngineConfig({ scaleWithPlayer: false }).fontScaleMode, "fixed")
64+
assert.equal(settings.toEngineConfig(settings.DEFAULTS).fontScaleMode, "up")
5765
assert.equal(settings.DEFAULTS.maxActive, 2000)
5866
assert.equal(settings.DEFAULTS.maxQueue, 2400)
5967
assert.equal(settings.DEFAULTS.spawnPerFrame, 10)
@@ -115,4 +123,4 @@ assert.equal(filter.apply("ALICE", "spam spam").drop, true)
115123
assert.equal((await reducedSettings.load()).enabled, true)
116124
}
117125

118-
console.log("settings-filter ok (41 assertions)")
126+
console.log("settings-filter ok (48 assertions)")

0 commit comments

Comments
 (0)