dsh-fonttune 0.7.7 → 0.7.8
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.en.md +9 -0
- package/CHANGELOG.md +9 -0
- package/README.en.md +1 -1
- package/README.md +1 -1
- package/lib/client.js +113 -3
- package/lib/shared.cjs +35 -0
- package/package.json +5 -15
package/CHANGELOG.en.md
CHANGED
|
@@ -2,6 +2,15 @@
|
|
|
2
2
|
|
|
3
3
|
All notable changes to **dsh-fonttune** are documented here. Chinese version: [CHANGELOG.md](CHANGELOG.md).
|
|
4
4
|
|
|
5
|
+
## [0.7.8] - 2026-11-05
|
|
6
|
+
|
|
7
|
+
### Fixed
|
|
8
|
+
- **The font picker opened slowly**: the list now builds one screenful, and the names are no longer drawn in each family's own font, so opening and scrolling stay quick on a machine with many installed fonts.
|
|
9
|
+
- **A pause when switching presets**: a preset remembers the notches that were measured for it, so switching uses them directly instead of measuring again at that moment.
|
|
10
|
+
|
|
11
|
+
### Changed
|
|
12
|
+
- **Only verified host versions are declared**: 0.2.0-rc.1 through 0.2.2-rc.1 are listed one by one; older or untested versions are no longer claimed.
|
|
13
|
+
|
|
5
14
|
## [0.7.7] - 2026-11-05
|
|
6
15
|
|
|
7
16
|
### Fixed
|
package/CHANGELOG.md
CHANGED
|
@@ -2,6 +2,15 @@
|
|
|
2
2
|
|
|
3
3
|
**dsh-fonttune** 的重要变更都记录在这里。英文版见 [CHANGELOG.en.md](CHANGELOG.en.md)。
|
|
4
4
|
|
|
5
|
+
## [0.7.8] - 2026-11-05
|
|
6
|
+
|
|
7
|
+
### 修复
|
|
8
|
+
- **字体选择列表打开较慢**:列表现在只列出首屏,并且不再逐个用各自字体显示名称;机器上安装的字体很多时,打开与滚动都更快。
|
|
9
|
+
- **切换配置方案时的停顿**:方案会记住已测好的档位,切换时直接使用,切换的那一瞬间不再重新测量。
|
|
10
|
+
|
|
11
|
+
### 变更
|
|
12
|
+
- **兼容范围只保留实测过的版本**:从 0.2.0-rc.1 到 0.2.2-rc.1 逐个列出,更早或未验证的版本不再声明支持。
|
|
13
|
+
|
|
5
14
|
## [0.7.7] - 2026-11-05
|
|
6
15
|
|
|
7
16
|
### 修复
|
package/README.en.md
CHANGED
|
@@ -39,7 +39,7 @@ The floating tuning panel (left, with its "in force right now" readout) and the
|
|
|
39
39
|
|
|
40
40
|
| Plugin version | Supported DSH versions |
|
|
41
41
|
| --- | --- |
|
|
42
|
-
| **0.7.x** (latest, 0.7.7) | 0.
|
|
42
|
+
| **0.7.x** (latest, 0.7.7) | 0.2.0-rc.1, 0.2.0-rc.2, 0.2.1-rc.1, 0.2.1-rc.2, 0.2.1-rc.3, 0.2.1-rc.4, 0.2.1-rc.5, 0.2.2-rc.1|
|
|
43
43
|
| 0.6.x (0.6.2) | 0.1.7-alpha.1, 0.1.7-alpha.2, 0.1.7-rc.1, 0.1.7-rc.2, 0.2.0-rc.1, 0.2.0-rc.2, 0.2.1-alpha.1, 0.2.1-alpha.2, 0.2.1-alpha.3, 0.2.1-alpha.4, 0.2.1-alpha.5, 0.2.1-rc.1, 0.2.1-rc.2 |
|
|
44
44
|
| 0.5.x (0.5.0) | 0.1.7-alpha.1, 0.1.7-alpha.2, 0.1.7-rc.1, 0.1.7-rc.2, 0.2.0-rc.1, 0.2.0-rc.2, 0.2.1-alpha.1, 0.2.1-alpha.2 |
|
|
45
45
|
| 0.4.x (0.4.2) | 0.1.7-alpha.1, 0.1.7-alpha.2, 0.1.7-rc.1, 0.1.7-rc.2, 0.2.0-rc.1, 0.2.0-rc.2, 0.2.1-alpha.1, 0.2.1-alpha.2 |
|
package/README.md
CHANGED
|
@@ -39,7 +39,7 @@
|
|
|
39
39
|
|
|
40
40
|
| 插件版本 | 支持的 DSH 版本 |
|
|
41
41
|
| --- | --- |
|
|
42
|
-
| **0.7.x**(最新,0.7.7) | 0.
|
|
42
|
+
| **0.7.x**(最新,0.7.7) | 0.2.0-rc.1、0.2.0-rc.2、0.2.1-rc.1、0.2.1-rc.2、0.2.1-rc.3、0.2.1-rc.4、0.2.1-rc.5、0.2.2-rc.1|
|
|
43
43
|
| 0.6.x(0.6.2) | 0.1.7-alpha.1、0.1.7-alpha.2、0.1.7-rc.1、0.1.7-rc.2、0.2.0-rc.1、0.2.0-rc.2、0.2.1-alpha.1、0.2.1-alpha.2、0.2.1-alpha.3、0.2.1-alpha.4、0.2.1-alpha.5、0.2.1-rc.1、0.2.1-rc.2 |
|
|
44
44
|
| 0.5.x(0.5.0) | 0.1.7-alpha.1、0.1.7-alpha.2、0.1.7-rc.1、0.1.7-rc.2、0.2.0-rc.1、0.2.0-rc.2、0.2.1-alpha.1、0.2.1-alpha.2 |
|
|
45
45
|
| 0.4.x(0.4.2) | 0.1.7-alpha.1、0.1.7-alpha.2、0.1.7-rc.1、0.1.7-rc.2、0.2.0-rc.1、0.2.0-rc.2、0.2.1-alpha.1、0.2.1-alpha.2 |
|
package/lib/client.js
CHANGED
|
@@ -5000,6 +5000,40 @@ function catalogRefreshDue(cache, now, retryMs) {
|
|
|
5000
5000
|
return now - (cache.at || 0) >= retryMs;
|
|
5001
5001
|
}
|
|
5002
5002
|
|
|
5003
|
+
/**
|
|
5004
|
+
* The rows one picker group actually renders.
|
|
5005
|
+
*
|
|
5006
|
+
* A machine with a huge font collection — or a Super TTC, which exposes every
|
|
5007
|
+
* weight and variant as its own family — can enumerate hundreds of names, and
|
|
5008
|
+
* building a row for each one is a visible stall when the list opens (issue #3:
|
|
5009
|
+
* the reporter's second symptom). The list therefore renders a screenful unless
|
|
5010
|
+
* the reader is searching, and a family that is already selected is always
|
|
5011
|
+
* rendered so its check mark never falls outside the window.
|
|
5012
|
+
* @param {string[]} families - the group's families, in display order.
|
|
5013
|
+
* @param {string} query - the search text (empty means "browse the whole list").
|
|
5014
|
+
* @param {Object<string, boolean>} selectedLower - lower-cased names already picked.
|
|
5015
|
+
* @param {number} [limit] - how many rows a browse renders.
|
|
5016
|
+
* @returns {string[]} the families to render.
|
|
5017
|
+
*/
|
|
5018
|
+
function pickerRows(families, query, selectedLower, limit) {
|
|
5019
|
+
var list = Array.isArray(families) ? families : [];
|
|
5020
|
+
var searching = typeof query === "string" && query.trim() !== "";
|
|
5021
|
+
if (searching) return list.slice();
|
|
5022
|
+
var cap = typeof limit === "number" && limit > 0 ? Math.floor(limit) : 60;
|
|
5023
|
+
var picked = selectedLower !== null && typeof selectedLower === "object" ? selectedLower : {};
|
|
5024
|
+
var rows = [];
|
|
5025
|
+
var shown = 0;
|
|
5026
|
+
for (var index = 0; index < list.length; index += 1) {
|
|
5027
|
+
var name = list[index];
|
|
5028
|
+
var isSelected = picked[String(name).toLowerCase()] === true;
|
|
5029
|
+
if (isSelected || shown < cap) {
|
|
5030
|
+
rows.push(name);
|
|
5031
|
+
shown += 1;
|
|
5032
|
+
}
|
|
5033
|
+
}
|
|
5034
|
+
return rows;
|
|
5035
|
+
}
|
|
5036
|
+
|
|
5003
5037
|
var shared = {
|
|
5004
5038
|
NAMESPACE: NAMESPACE,
|
|
5005
5039
|
SANS_FIELD: SANS_FIELD,
|
|
@@ -5147,6 +5181,7 @@ var shared = {
|
|
|
5147
5181
|
PRESETS: PRESETS,
|
|
5148
5182
|
PRESET_GROUPS: PRESET_GROUPS,
|
|
5149
5183
|
MAX_LOCAL_FONTS: MAX_LOCAL_FONTS,
|
|
5184
|
+
pickerRows: pickerRows,
|
|
5150
5185
|
pickerGroups: pickerGroups,
|
|
5151
5186
|
catalogRefreshDue: catalogRefreshDue,
|
|
5152
5187
|
MARKDOWN_SELECTOR: MARKDOWN_SELECTOR,
|
|
@@ -7354,9 +7389,21 @@ function deferWeightWarm(job) {
|
|
|
7354
7389
|
*/
|
|
7355
7390
|
var warmSignature = null;
|
|
7356
7391
|
|
|
7392
|
+
/** When a preset was applied: its warm-up waits so the switch itself feels instant. */
|
|
7393
|
+
var presetSwitchAt = 0;
|
|
7394
|
+
|
|
7357
7395
|
/** The code stack the deferred slice was last scheduled for (scheduled once). */
|
|
7358
7396
|
var codeWarmSignature = null;
|
|
7359
7397
|
var scheduleWeightWarm = function (editing) {
|
|
7398
|
+
// A preset switch changes several stacks at once. Hydrated shapes make the rows
|
|
7399
|
+
// correct immediately, so the sweep those stacks would trigger can wait: measuring
|
|
7400
|
+
// inside the switch is what made switching stutter on a slow font.
|
|
7401
|
+
if (Date.now() - presetSwitchAt < 1200) {
|
|
7402
|
+
globalThis.setTimeout(function () {
|
|
7403
|
+
scheduleWeightWarm(editing);
|
|
7404
|
+
}, 1200);
|
|
7405
|
+
return;
|
|
7406
|
+
}
|
|
7360
7407
|
var stacks = weightAxisStacks(editing);
|
|
7361
7408
|
var signature = stacks.dialog + "\u0000" + stacks.ui + "\u0000" + stacks.code;
|
|
7362
7409
|
if (signature === warmSignature) return;
|
|
@@ -8547,11 +8594,18 @@ function StackPicker(props) {
|
|
|
8547
8594
|
var panel = null;
|
|
8548
8595
|
if (open) {
|
|
8549
8596
|
var rows = [];
|
|
8597
|
+
// A machine with a huge font collection (or a Super TTC, which exposes every
|
|
8598
|
+
// weight and variant as its own family) can enumerate hundreds of names. Only
|
|
8599
|
+
// the first screenful is built unless the reader is searching, and a selected
|
|
8600
|
+
// family is always among them — drawing every row was a second of layout on
|
|
8601
|
+
// such a machine. The subsetting itself is pure and covered offline.
|
|
8602
|
+
var RENDER_LIMIT = 60;
|
|
8550
8603
|
for (var groupRender = 0; groupRender < options.length; groupRender += 1) {
|
|
8551
8604
|
var group = options[groupRender];
|
|
8552
8605
|
rows.push(h("div", { className: "dfp-group", key: "g:" + group.label }, t(group.label)));
|
|
8553
|
-
|
|
8554
|
-
|
|
8606
|
+
var visible = shared.pickerRows(group.families, typed, selectedLower, RENDER_LIMIT);
|
|
8607
|
+
for (var rowIndex = 0; rowIndex < visible.length; rowIndex += 1) {
|
|
8608
|
+
var name = visible[rowIndex];
|
|
8555
8609
|
var isSelected = selectedLower[name.toLowerCase()] === true;
|
|
8556
8610
|
rows.push(
|
|
8557
8611
|
h(
|
|
@@ -8560,12 +8614,13 @@ function StackPicker(props) {
|
|
|
8560
8614
|
type: "button",
|
|
8561
8615
|
className: "dfp-option",
|
|
8562
8616
|
key: "o:" + group.label + ":" + name,
|
|
8563
|
-
style: { fontFamily: quoteFamily(name) },
|
|
8564
8617
|
"data-family": name,
|
|
8565
8618
|
onClick: function (event) {
|
|
8566
8619
|
toggle(event.currentTarget.dataset.family);
|
|
8567
8620
|
},
|
|
8568
8621
|
},
|
|
8622
|
+
// The rows are NOT drawn in their own family: on a large collection that
|
|
8623
|
+
// loaded one font per row, which is exactly the stall this list is about.
|
|
8569
8624
|
h("span", { className: "dfp-optionLabel" }, name),
|
|
8570
8625
|
isSelected ? h("span", { className: "dfp-optionCheck" }, "✓") : null
|
|
8571
8626
|
)
|
|
@@ -9345,7 +9400,9 @@ function FontCard(props) {
|
|
|
9345
9400
|
var next = presetList.slice();
|
|
9346
9401
|
for (var index = 0; index < next.length; index += 1) {
|
|
9347
9402
|
if (next[index].name === entry.name) {
|
|
9403
|
+
var shapes = shapeSnapshotFor(values);
|
|
9348
9404
|
next[index] = { name: entry.name, values: values, savedAt: Date.now() };
|
|
9405
|
+
if (Object.keys(shapes).length > 0) next[index].shapes = shapes;
|
|
9349
9406
|
}
|
|
9350
9407
|
}
|
|
9351
9408
|
return { op: shared.PENDING_SET, path: [PRESETS_FIELD], value: JSON.stringify(next) };
|
|
@@ -10189,10 +10246,63 @@ function FontCard(props) {
|
|
|
10189
10246
|
* is exact.
|
|
10190
10247
|
* @param {{name: string, values: object}} entry - the picked preset.
|
|
10191
10248
|
*/
|
|
10249
|
+
/**
|
|
10250
|
+
* The measured shapes a preset can carry.
|
|
10251
|
+
*
|
|
10252
|
+
* Switching presets changes the font stacks, which invalidates the slider shapes:
|
|
10253
|
+
* the new stacks would be measured again (a second of canvas work on a huge font
|
|
10254
|
+
* collection) the moment they are applied. A preset therefore remembers the
|
|
10255
|
+
* shapes it was saved with — three small records — and applying it restores them
|
|
10256
|
+
* instead of measuring (issue #3 follow-up: "switching presets stutters").
|
|
10257
|
+
* @param {object} values - the preset's value set.
|
|
10258
|
+
* @returns {object} cache key -> shape, empty when nothing is known yet.
|
|
10259
|
+
*/
|
|
10260
|
+
var shapeSnapshotFor = function (values) {
|
|
10261
|
+
var stacks = weightAxisStacks(values);
|
|
10262
|
+
var pairs = [
|
|
10263
|
+
[stacks.dialog, shared.WEIGHT_LADDER_STRONG],
|
|
10264
|
+
[stacks.ui, shared.WEIGHT_LADDER_STRONG],
|
|
10265
|
+
[stacks.code, shared.WEIGHT_BASE],
|
|
10266
|
+
];
|
|
10267
|
+
var snapshot = {};
|
|
10268
|
+
for (var index = 0; index < pairs.length; index += 1) {
|
|
10269
|
+
var stack = pairs[index][0];
|
|
10270
|
+
var base = pairs[index][1];
|
|
10271
|
+
var key = String(stack) + "\u0000" + base + "\u0000stepped";
|
|
10272
|
+
if (Object.prototype.hasOwnProperty.call(weightProfileCache, key)) {
|
|
10273
|
+
snapshot[key] = weightProfileCache[key];
|
|
10274
|
+
} else {
|
|
10275
|
+
var stored = storedWeightShape(key);
|
|
10276
|
+
if (stored !== null) snapshot[key] = stored;
|
|
10277
|
+
}
|
|
10278
|
+
}
|
|
10279
|
+
return snapshot;
|
|
10280
|
+
};
|
|
10281
|
+
|
|
10282
|
+
/**
|
|
10283
|
+
* Put a preset's remembered shapes back into the cache.
|
|
10284
|
+
* @param {object} shapes - what `shapeSnapshotFor` recorded.
|
|
10285
|
+
*/
|
|
10286
|
+
var hydrateShapes = function (shapes) {
|
|
10287
|
+
if (shapes === null || typeof shapes !== "object") return;
|
|
10288
|
+
for (var key in shapes) {
|
|
10289
|
+
if (!Object.prototype.hasOwnProperty.call(shapes, key)) continue;
|
|
10290
|
+
var shape = shapes[key];
|
|
10291
|
+
if (shape === null || typeof shape !== "object") continue;
|
|
10292
|
+
if (!isFinite(shape.min) || !isFinite(shape.max) || !isFinite(shape.step)) continue;
|
|
10293
|
+
if (Object.prototype.hasOwnProperty.call(weightProfileCache, key)) continue;
|
|
10294
|
+
weightProfileCache[key] = { min: shape.min, max: shape.max, step: shape.step };
|
|
10295
|
+
}
|
|
10296
|
+
};
|
|
10297
|
+
|
|
10192
10298
|
var switchPreset = function (entry) {
|
|
10193
10299
|
if (!writable || entry.name === activeName) return;
|
|
10300
|
+
// Stamp the switch BEFORE anything is written: the sync that follows it would
|
|
10301
|
+
// otherwise start measuring the new stacks in the same task.
|
|
10302
|
+
presetSwitchAt = Date.now();
|
|
10194
10303
|
applyingRef.current = true;
|
|
10195
10304
|
try {
|
|
10305
|
+
hydrateShapes(entry.shapes);
|
|
10196
10306
|
for (var index = 0; index < VALUE_FIELDS.length; index += 1) {
|
|
10197
10307
|
var field = VALUE_FIELDS[index];
|
|
10198
10308
|
var value = Object.prototype.hasOwnProperty.call(entry.values, field)
|
package/lib/shared.cjs
CHANGED
|
@@ -4991,6 +4991,40 @@ function catalogRefreshDue(cache, now, retryMs) {
|
|
|
4991
4991
|
return now - (cache.at || 0) >= retryMs;
|
|
4992
4992
|
}
|
|
4993
4993
|
|
|
4994
|
+
/**
|
|
4995
|
+
* The rows one picker group actually renders.
|
|
4996
|
+
*
|
|
4997
|
+
* A machine with a huge font collection — or a Super TTC, which exposes every
|
|
4998
|
+
* weight and variant as its own family — can enumerate hundreds of names, and
|
|
4999
|
+
* building a row for each one is a visible stall when the list opens (issue #3:
|
|
5000
|
+
* the reporter's second symptom). The list therefore renders a screenful unless
|
|
5001
|
+
* the reader is searching, and a family that is already selected is always
|
|
5002
|
+
* rendered so its check mark never falls outside the window.
|
|
5003
|
+
* @param {string[]} families - the group's families, in display order.
|
|
5004
|
+
* @param {string} query - the search text (empty means "browse the whole list").
|
|
5005
|
+
* @param {Object<string, boolean>} selectedLower - lower-cased names already picked.
|
|
5006
|
+
* @param {number} [limit] - how many rows a browse renders.
|
|
5007
|
+
* @returns {string[]} the families to render.
|
|
5008
|
+
*/
|
|
5009
|
+
function pickerRows(families, query, selectedLower, limit) {
|
|
5010
|
+
var list = Array.isArray(families) ? families : [];
|
|
5011
|
+
var searching = typeof query === "string" && query.trim() !== "";
|
|
5012
|
+
if (searching) return list.slice();
|
|
5013
|
+
var cap = typeof limit === "number" && limit > 0 ? Math.floor(limit) : 60;
|
|
5014
|
+
var picked = selectedLower !== null && typeof selectedLower === "object" ? selectedLower : {};
|
|
5015
|
+
var rows = [];
|
|
5016
|
+
var shown = 0;
|
|
5017
|
+
for (var index = 0; index < list.length; index += 1) {
|
|
5018
|
+
var name = list[index];
|
|
5019
|
+
var isSelected = picked[String(name).toLowerCase()] === true;
|
|
5020
|
+
if (isSelected || shown < cap) {
|
|
5021
|
+
rows.push(name);
|
|
5022
|
+
shown += 1;
|
|
5023
|
+
}
|
|
5024
|
+
}
|
|
5025
|
+
return rows;
|
|
5026
|
+
}
|
|
5027
|
+
|
|
4994
5028
|
var shared = {
|
|
4995
5029
|
NAMESPACE: NAMESPACE,
|
|
4996
5030
|
SANS_FIELD: SANS_FIELD,
|
|
@@ -5138,6 +5172,7 @@ var shared = {
|
|
|
5138
5172
|
PRESETS: PRESETS,
|
|
5139
5173
|
PRESET_GROUPS: PRESET_GROUPS,
|
|
5140
5174
|
MAX_LOCAL_FONTS: MAX_LOCAL_FONTS,
|
|
5175
|
+
pickerRows: pickerRows,
|
|
5141
5176
|
pickerGroups: pickerGroups,
|
|
5142
5177
|
catalogRefreshDue: catalogRefreshDue,
|
|
5143
5178
|
MARKDOWN_SELECTOR: MARKDOWN_SELECTOR,
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "dsh-fonttune",
|
|
3
|
-
"version": "0.7.
|
|
3
|
+
"version": "0.7.8",
|
|
4
4
|
"description": "Font plugin for the DeepSeek Harness Web GUI: the conversation gets its own font, size, line height and weight, the interface font and weight follow it, code keeps its own axis with ligature control, and whole setups save as presets.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"icon": "icon.svg",
|
|
@@ -25,7 +25,7 @@
|
|
|
25
25
|
"docs/card-shaping.jpg"
|
|
26
26
|
],
|
|
27
27
|
"engines": {
|
|
28
|
-
"dsh": ">=0.
|
|
28
|
+
"dsh": ">=0.2.0-rc.1 <0.2.2-rc.2"
|
|
29
29
|
},
|
|
30
30
|
"dsh": {
|
|
31
31
|
"bundle": {
|
|
@@ -36,24 +36,14 @@
|
|
|
36
36
|
},
|
|
37
37
|
"compatibility": {
|
|
38
38
|
"dshReleases": {
|
|
39
|
-
"0.1.7-alpha.1": "compatible",
|
|
40
|
-
"0.1.7-alpha.2": "compatible",
|
|
41
|
-
"0.1.7-rc.1": "compatible",
|
|
42
|
-
"0.1.7-rc.2": "compatible",
|
|
43
39
|
"0.2.0-rc.1": "compatible",
|
|
44
40
|
"0.2.0-rc.2": "compatible",
|
|
45
|
-
"0.2.1-alpha.1": "compatible",
|
|
46
|
-
"0.2.1-alpha.2": "compatible",
|
|
47
|
-
"0.2.1-alpha.3": "compatible",
|
|
48
|
-
"0.2.1-alpha.4": "compatible",
|
|
49
|
-
"0.2.1-alpha.5": "compatible",
|
|
50
41
|
"0.2.1-rc.1": "compatible",
|
|
51
42
|
"0.2.1-rc.2": "compatible",
|
|
52
|
-
"0.2.1-rc.4": "compatible",
|
|
53
43
|
"0.2.1-rc.3": "compatible",
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
44
|
+
"0.2.1-rc.4": "compatible",
|
|
45
|
+
"0.2.1-rc.5": "compatible",
|
|
46
|
+
"0.2.2-rc.1": "compatible"
|
|
57
47
|
}
|
|
58
48
|
}
|
|
59
49
|
},
|