|
33 | 33 | topPct: 0.08, // keep top 8% clear |
34 | 34 | bottomPct: 0.14, // keep bottom 14% clear (controls) |
35 | 35 | 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) |
37 | 37 | dedup: false, // drop near-duplicates of recently shown comments |
38 | 38 | simThreshold: 3, // Hamming distance <= this => near-duplicate |
39 | 39 | recentMax: 400, |
|
88 | 88 | "textColor", |
89 | 89 | "roleColors" |
90 | 90 | ]; |
91 | | - const GEOMETRY_CONFIG_KEYS = ["dpr", "lineHeight", "topPct", "bottomPct", "scaleWithPlayer"]; |
| 91 | + const GEOMETRY_CONFIG_KEYS = ["dpr", "lineHeight", "topPct", "bottomPct", "fontScaleMode"]; |
92 | 92 |
|
93 | 93 | const COLORS = { owner: "#ffca28", moderator: "#5e9bff", member: "#7CFC8C", normal: "#ffffff" }; |
94 | 94 | const AUTHOR_BOOST = { owner: 0.40, moderator: 0.25, member: 0.10, normal: 0 }; |
95 | 95 | const TARGET_FRAME_MS = 1000 / 60; |
96 | 96 | const MIN_CAP_FRAME_MS = 50; |
97 | 97 | 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. |
100 | 102 | const SCALE_REF_HEIGHT = 720; |
| 103 | + const SCALE_MIN = 0.5; |
101 | 104 | const SCALE_MAX = 3; |
102 | 105 | const MAX_WRAP_LINES = 3; // hard cap on wrapped lines per comment |
103 | 106 | // Sprite-atlas geometry (device px). A page is 2 MB RGBA; pages are opened |
|
128 | 131 | return (sig >>> (32 - DEDUP_BUCKET_BITS)) & ((1 << DEDUP_BUCKET_BITS) - 1); |
129 | 132 | } |
130 | 133 |
|
| 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 | + |
131 | 144 | function truncateText(text, maxChars) { |
132 | 145 | const s = String(text || ""); |
133 | 146 | if (s.length <= maxChars) return text; // fast path: no spread/array for short text |
|
153 | 166 |
|
154 | 167 | class DanmakuOverlay { |
155 | 168 | constructor(cfg) { |
156 | | - this.cfg = Object.assign({}, DEFAULTS, cfg); |
| 169 | + this.cfg = Object.assign({}, DEFAULTS, normalizeScaleConfig(cfg)); |
157 | 170 | this.active = []; |
158 | 171 | this.nextActive = []; |
159 | 172 | this.activeMinHeap = []; |
|
197 | 210 | this.drag = null; // active pin-drag state |
198 | 211 | this._loop = this._loop.bind(this); |
199 | 212 | 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) |
201 | 214 | this.gap = this.cfg.gapPx; |
202 | 215 | } |
203 | 216 |
|
|
283 | 296 | } |
284 | 297 |
|
285 | 298 | setConfig(partial) { |
| 299 | + const next = normalizeScaleConfig(partial) || {}; |
286 | 300 | 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] |
288 | 302 | ); |
289 | 303 | 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] |
291 | 305 | ); |
292 | | - Object.assign(this.cfg, partial); |
| 306 | + Object.assign(this.cfg, next); |
293 | 307 | if (shouldClearRasterCache) this.cache.clear(); |
294 | 308 | if (partial.maxActive != null || partial.minActive != null) this._updateDynamicCap(); |
295 | 309 | if (this.canvas && shouldResize) this._resize(); |
|
332 | 346 | this.canvas.height = Math.round(this.h * dpr); |
333 | 347 | const usable = this.h * (1 - this.cfg.topPct - this.cfg.bottomPct); |
334 | 348 | // 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; |
338 | 356 | this.laneH = Math.round(this.cfg.lineHeight * this.scale); |
339 | 357 | this.gap = Math.round(this.cfg.gapPx * this.scale); |
340 | 358 | this.laneCount = Math.max(3, Math.floor(usable / this.laneH)); |
|
0 commit comments