dsh-fonttune 0.3.4 → 0.3.5
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 +3 -3
- package/README.md +3 -3
- package/lib/client.js +4246 -4071
- package/lib/index.js +6 -0
- package/lib/shared.cjs +198 -60
- package/package.json +1 -1
package/lib/index.js
CHANGED
|
@@ -217,6 +217,12 @@ export const Config = volatileWhenSupported(z.object({
|
|
|
217
217
|
.description(
|
|
218
218
|
`Code font-weight offset; added to each code surface's own weight and independent of the interface weight (the slider spans whatever weights the chosen font can render), ${shared.WEIGHT_UNSET} keeps DSH's own weights. A legacy absolute weight (${shared.WEIGHT_MIN}..${shared.WEIGHT_MAX}) is accepted and converted to its offset.`
|
|
219
219
|
),
|
|
220
|
+
[shared.WEIGHT_OFFSETS_FIELD]: z
|
|
221
|
+
.boolean()
|
|
222
|
+
.default(false)
|
|
223
|
+
.description(
|
|
224
|
+
"Scale marker: the three weight fields hold offsets, not legacy absolute weights (set automatically the first time the card writes a weight)"
|
|
225
|
+
),
|
|
220
226
|
[LINE_HEIGHT_FIELD]: z
|
|
221
227
|
.number()
|
|
222
228
|
.min(shared.LINE_HEIGHT_MIN)
|
package/lib/shared.cjs
CHANGED
|
@@ -77,6 +77,23 @@ var WEIGHT_DIALOG_FIELD = "weightDialog";
|
|
|
77
77
|
/** Field carrying the code font weight (0 = leave DSH alone). */
|
|
78
78
|
var CODE_WEIGHT_FIELD = "weightCode";
|
|
79
79
|
|
|
80
|
+
/**
|
|
81
|
+
* Marks a document whose weight fields are OFFSETS, not absolute weights.
|
|
82
|
+
*
|
|
83
|
+
* Before 0.2.8 the three weight axes stored an absolute CSS weight (`300…600`,
|
|
84
|
+
* `400` = normal) and `clampWeightDelta` converted one on sight. That inference
|
|
85
|
+
* only works while offsets never land inside the absolute window, which capped
|
|
86
|
+
* the offset range at +200 — and +200 is not enough room for a step the eye can
|
|
87
|
+
* see on a family with few faces (measured: one 200-weight step is a +30% change
|
|
88
|
+
* on Noto Serif SC, one 100-weight step only +16%).
|
|
89
|
+
*
|
|
90
|
+
* So the document says which scale it is on, and the inference is borrowed for
|
|
91
|
+
* exactly one write: the first time 0.3.5+ writes a weight, the card converts any
|
|
92
|
+
* remaining absolute value, converts the saved presets the same way, and sets
|
|
93
|
+
* this field. From then on a stored weight is an offset, whatever its size.
|
|
94
|
+
*/
|
|
95
|
+
var WEIGHT_OFFSETS_FIELD = "weightOffsets";
|
|
96
|
+
|
|
80
97
|
/**
|
|
81
98
|
* Field carrying the interface line-height ratio in percent (100 = untouched).
|
|
82
99
|
* A ratio rather than a px offset: interface line heights form a design ladder
|
|
@@ -208,17 +225,18 @@ var WEIGHT_LADDER_TOP = DIALOG_WEIGHT_GROUPS.reduce(function (top, group) {
|
|
|
208
225
|
/**
|
|
209
226
|
* How far above its base an offset may reach at all.
|
|
210
227
|
*
|
|
211
|
-
*
|
|
212
|
-
*
|
|
213
|
-
*
|
|
214
|
-
*
|
|
228
|
+
* The ceiling comes from the layer a notch MUST move: body text sits at
|
|
229
|
+
* `WEIGHT_BASE`, so the range runs as far as the text a reader is reading can
|
|
230
|
+
* still change — measured up to +400 on a family whose faces reach 900. Bold text
|
|
231
|
+
* stops moving at +300 and headings at +200; that is accepted, because a heading
|
|
232
|
+
* already at its heaviest face stays there (see `WEIGHT_LADDER_STRONG`).
|
|
215
233
|
*
|
|
216
|
-
*
|
|
217
|
-
* `WEIGHT_MIN…WEIGHT_MAX`, the
|
|
218
|
-
* document occupies
|
|
219
|
-
*
|
|
234
|
+
* It used to stop at `WEIGHT_CSS_MAX - WEIGHT_LADDER_TOP` (= +200) so an offset
|
|
235
|
+
* could never land inside `WEIGHT_MIN…WEIGHT_MAX`, the window an ABSOLUTE weight
|
|
236
|
+
* from a pre-relative document occupies. The document's own marker resolves that
|
|
237
|
+
* window now (`WEIGHT_OFFSETS_FIELD`), so the range is free to grow.
|
|
220
238
|
*/
|
|
221
|
-
var WEIGHT_DELTA_UP_LIMIT = WEIGHT_CSS_MAX -
|
|
239
|
+
var WEIGHT_DELTA_UP_LIMIT = WEIGHT_CSS_MAX - WEIGHT_BASE;
|
|
222
240
|
|
|
223
241
|
/**
|
|
224
242
|
* The heaviest layer a notch still has to move: bold text.
|
|
@@ -314,9 +332,12 @@ var VALUE_FIELDS = [
|
|
|
314
332
|
var RETIRED_FIELDS = [SIZE_FIELD, LINE_HEIGHT_FIELD];
|
|
315
333
|
|
|
316
334
|
/**
|
|
317
|
-
* Every durable
|
|
335
|
+
* Every durable field normalization covers: the live axes, the retired pair, and
|
|
336
|
+
* the scale marker (`WEIGHT_OFFSETS_FIELD`, which is deliberately NOT a value
|
|
337
|
+
* axis — a preset must never carry it, or applying that preset would re-label a
|
|
338
|
+
* document still holding absolute weights).
|
|
318
339
|
*/
|
|
319
|
-
var DURABLE_FIELDS = VALUE_FIELDS.concat(RETIRED_FIELDS);
|
|
340
|
+
var DURABLE_FIELDS = VALUE_FIELDS.concat(RETIRED_FIELDS, [WEIGHT_OFFSETS_FIELD]);
|
|
320
341
|
|
|
321
342
|
/**
|
|
322
343
|
* Longest accepted font stack, in characters (mirrored by the host schema).
|
|
@@ -435,7 +456,7 @@ var FEATURES_UNSAFE = /[^a-zA-Z0-9"' ,]/g;
|
|
|
435
456
|
* ------------------------------------------------------------------ */
|
|
436
457
|
|
|
437
458
|
/** Clamp/validate one field by name (shared by config and value sets). */
|
|
438
|
-
function normalizeField(field, value) {
|
|
459
|
+
function normalizeField(field, value, relative) {
|
|
439
460
|
switch (field) {
|
|
440
461
|
case SANS_FIELD:
|
|
441
462
|
case STACK_DIALOG_FIELD:
|
|
@@ -448,7 +469,7 @@ function normalizeField(field, value) {
|
|
|
448
469
|
case WEIGHT_FIELD:
|
|
449
470
|
case WEIGHT_DIALOG_FIELD:
|
|
450
471
|
case CODE_WEIGHT_FIELD:
|
|
451
|
-
return clampWeightDelta(value);
|
|
472
|
+
return clampWeightDelta(value, relative);
|
|
452
473
|
case LINE_HEIGHT_FIELD:
|
|
453
474
|
case LINE_HEIGHT_DIALOG_FIELD:
|
|
454
475
|
return clampRatio(value);
|
|
@@ -456,6 +477,9 @@ function normalizeField(field, value) {
|
|
|
456
477
|
return clampLineOffset(value);
|
|
457
478
|
case LIGATURES_FIELD:
|
|
458
479
|
return clampLigatures(value);
|
|
480
|
+
case WEIGHT_OFFSETS_FIELD:
|
|
481
|
+
// The document's own scale marker: false/absent = the pre-relative scale.
|
|
482
|
+
return toBool(value);
|
|
459
483
|
case FEATURES_FIELD:
|
|
460
484
|
return sanitizeFeatures(value);
|
|
461
485
|
case NO_SYNTHETIC_ITALIC_FIELD:
|
|
@@ -479,14 +503,17 @@ function normalizeField(field, value) {
|
|
|
479
503
|
*/
|
|
480
504
|
function normalizeConfig(value) {
|
|
481
505
|
var source = value !== null && typeof value === "object" ? value : {};
|
|
506
|
+
// The scale marker is read BEFORE the axes: it decides whether a weight inside
|
|
507
|
+
// `300…600` is a legacy absolute value or an offset (see `clampWeightDelta`).
|
|
508
|
+
var relative = toBool(source[WEIGHT_OFFSETS_FIELD]);
|
|
482
509
|
var config = {};
|
|
483
510
|
for (var index = 0; index < DURABLE_FIELDS.length; index += 1) {
|
|
484
511
|
var field = DURABLE_FIELDS[index];
|
|
485
|
-
config[field] = normalizeField(field, source[field]);
|
|
512
|
+
config[field] = normalizeField(field, source[field], relative);
|
|
486
513
|
}
|
|
487
514
|
config[PER_THEME_FIELD] = toBool(source[PER_THEME_FIELD]);
|
|
488
|
-
config[DARK_VALUES_FIELD] = normalizeDarkValues(source[DARK_VALUES_FIELD]);
|
|
489
|
-
config[PRESETS_FIELD] = normalizePresets(source[PRESETS_FIELD]);
|
|
515
|
+
config[DARK_VALUES_FIELD] = normalizeDarkValues(source[DARK_VALUES_FIELD], relative);
|
|
516
|
+
config[PRESETS_FIELD] = normalizePresets(source[PRESETS_FIELD], relative);
|
|
490
517
|
config[ACTIVE_PRESET_FIELD] = sanitize(source[ACTIVE_PRESET_FIELD]).slice(0, MAX_PRESET_NAME);
|
|
491
518
|
return config;
|
|
492
519
|
}
|
|
@@ -524,21 +551,21 @@ function clampWeight(value) {
|
|
|
524
551
|
* Clamp one RELATIVE weight to its allowed range, migrating an absolute value.
|
|
525
552
|
*
|
|
526
553
|
* A document written before the axes became relative stores an absolute weight
|
|
527
|
-
* (`300…600`, `400` = normal)
|
|
528
|
-
*
|
|
529
|
-
*
|
|
530
|
-
*
|
|
531
|
-
*
|
|
532
|
-
* instead of jumping to the top of the new scale.
|
|
554
|
+
* (`300…600`, `400` = normal) and has no marker, so a value inside `300…600` is
|
|
555
|
+
* converted to its distance from normal (`400 → 0`, `560 → +160`, `300 → −100`).
|
|
556
|
+
* A marked document is on the offset scale already and is never converted — that
|
|
557
|
+
* is what lets an offset grow past `+200` without being mistaken for one of those
|
|
558
|
+
* absolute values (the two windows overlap; see `WEIGHT_OFFSETS_FIELD`).
|
|
533
559
|
*
|
|
534
560
|
* @param {unknown} value - candidate offset, or a pre-0.2.8 absolute weight.
|
|
561
|
+
* @param {boolean} [relative] - true when the document carries the offset marker.
|
|
535
562
|
* @returns {number} the offset the stylesheet may use.
|
|
536
563
|
*/
|
|
537
|
-
function clampWeightDelta(value) {
|
|
564
|
+
function clampWeightDelta(value, relative) {
|
|
538
565
|
var delta = Number(value);
|
|
539
566
|
if (!isFinite(delta)) return WEIGHT_UNSET;
|
|
540
567
|
delta = Math.round(delta);
|
|
541
|
-
if (delta >= WEIGHT_MIN && delta <= WEIGHT_MAX) delta -= WEIGHT_BASE;
|
|
568
|
+
if (relative !== true && delta >= WEIGHT_MIN && delta <= WEIGHT_MAX) delta -= WEIGHT_BASE;
|
|
542
569
|
// The slider's own range comes from the font (see `weightProfileFrom`) and can
|
|
543
570
|
// reach further than the nominal one, so the hard bounds are the widest range
|
|
544
571
|
// any font-derived slider may use.
|
|
@@ -586,13 +613,13 @@ function clampLigatures(value) {
|
|
|
586
613
|
* @param {unknown} values - candidate map.
|
|
587
614
|
* @returns {Record<string, string|number|boolean>} the safe subset.
|
|
588
615
|
*/
|
|
589
|
-
function normalizeValueSet(values) {
|
|
616
|
+
function normalizeValueSet(values, relative) {
|
|
590
617
|
var out = {};
|
|
591
618
|
if (values === null || typeof values !== "object" || Array.isArray(values)) return out;
|
|
592
619
|
for (var index = 0; index < VALUE_FIELDS.length; index += 1) {
|
|
593
620
|
var field = VALUE_FIELDS[index];
|
|
594
621
|
if (!Object.prototype.hasOwnProperty.call(values, field)) continue;
|
|
595
|
-
out[field] = normalizeField(field, values[field]);
|
|
622
|
+
out[field] = normalizeField(field, values[field], relative);
|
|
596
623
|
}
|
|
597
624
|
return out;
|
|
598
625
|
}
|
|
@@ -602,7 +629,7 @@ function normalizeValueSet(values) {
|
|
|
602
629
|
* @param {unknown} value - candidate JSON text or object.
|
|
603
630
|
* @returns {Record<string, string|number|boolean>} the sparse override map.
|
|
604
631
|
*/
|
|
605
|
-
function normalizeDarkValues(value) {
|
|
632
|
+
function normalizeDarkValues(value, relative) {
|
|
606
633
|
if (typeof value === "string") {
|
|
607
634
|
if (value.trim() === "") return {};
|
|
608
635
|
try {
|
|
@@ -611,7 +638,70 @@ function normalizeDarkValues(value) {
|
|
|
611
638
|
return {};
|
|
612
639
|
}
|
|
613
640
|
}
|
|
614
|
-
return normalizeValueSet(value);
|
|
641
|
+
return normalizeValueSet(value, relative);
|
|
642
|
+
}
|
|
643
|
+
|
|
644
|
+
/** The three axes that share the weight scale (and its marker). */
|
|
645
|
+
var WEIGHT_FIELDS = [WEIGHT_FIELD, WEIGHT_DIALOG_FIELD, CODE_WEIGHT_FIELD];
|
|
646
|
+
|
|
647
|
+
/**
|
|
648
|
+
* Convert the weight fields of saved preset snapshots to offsets.
|
|
649
|
+
*
|
|
650
|
+
* Runs exactly once per document, right before the card sets
|
|
651
|
+
* `WEIGHT_OFFSETS_FIELD`: a snapshot saved before 0.2.8 holds an absolute weight,
|
|
652
|
+
* and once the document is marked a stored weight is an offset, so applying that
|
|
653
|
+
* snapshot later would read `450` as +450 instead of +50. Converting the snapshots
|
|
654
|
+
* in the same breath as the document keeps the two on one scale.
|
|
655
|
+
*
|
|
656
|
+
* Only values inside the legacy absolute window are touched, so a snapshot that
|
|
657
|
+
* already holds offsets (any 0.3.x one) comes back unchanged and `changed` stays
|
|
658
|
+
* false — the caller then writes nothing.
|
|
659
|
+
*
|
|
660
|
+
* @param {unknown} presets - normalized presets, or anything shaped like them.
|
|
661
|
+
* @returns {{presets: object[], changed: boolean}} the converted list.
|
|
662
|
+
*/
|
|
663
|
+
function migratePresetWeights(presets) {
|
|
664
|
+
if (!Array.isArray(presets)) return { presets: [], changed: false };
|
|
665
|
+
var changed = false;
|
|
666
|
+
var out = [];
|
|
667
|
+
for (var index = 0; index < presets.length; index += 1) {
|
|
668
|
+
var entry = presets[index];
|
|
669
|
+
if (entry === null || typeof entry !== "object" || entry.values === null) {
|
|
670
|
+
out.push(entry);
|
|
671
|
+
continue;
|
|
672
|
+
}
|
|
673
|
+
var values = entry.values;
|
|
674
|
+
if (typeof values !== "object") {
|
|
675
|
+
out.push(entry);
|
|
676
|
+
continue;
|
|
677
|
+
}
|
|
678
|
+
var converted = null;
|
|
679
|
+
for (var axis = 0; axis < WEIGHT_FIELDS.length; axis += 1) {
|
|
680
|
+
var field = WEIGHT_FIELDS[axis];
|
|
681
|
+
if (!Object.prototype.hasOwnProperty.call(values, field)) continue;
|
|
682
|
+
var stored = Number(values[field]);
|
|
683
|
+
if (!isFinite(stored) || stored < WEIGHT_MIN || stored > WEIGHT_MAX) continue;
|
|
684
|
+
if (converted === null) {
|
|
685
|
+
converted = {};
|
|
686
|
+
for (var key in values) {
|
|
687
|
+
if (Object.prototype.hasOwnProperty.call(values, key)) converted[key] = values[key];
|
|
688
|
+
}
|
|
689
|
+
}
|
|
690
|
+
converted[field] = stored - WEIGHT_BASE;
|
|
691
|
+
}
|
|
692
|
+
if (converted === null) {
|
|
693
|
+
out.push(entry);
|
|
694
|
+
continue;
|
|
695
|
+
}
|
|
696
|
+
changed = true;
|
|
697
|
+
var copy = {};
|
|
698
|
+
for (var name in entry) {
|
|
699
|
+
if (Object.prototype.hasOwnProperty.call(entry, name)) copy[name] = entry[name];
|
|
700
|
+
}
|
|
701
|
+
copy.values = converted;
|
|
702
|
+
out.push(copy);
|
|
703
|
+
}
|
|
704
|
+
return { presets: out, changed: changed };
|
|
615
705
|
}
|
|
616
706
|
|
|
617
707
|
/**
|
|
@@ -619,7 +709,7 @@ function normalizeDarkValues(value) {
|
|
|
619
709
|
* @param {unknown} value - candidate JSON text or array.
|
|
620
710
|
* @returns {{name: string, values: object, savedAt: number}[]}
|
|
621
711
|
*/
|
|
622
|
-
function normalizePresets(value) {
|
|
712
|
+
function normalizePresets(value, relative) {
|
|
623
713
|
var list = value;
|
|
624
714
|
if (typeof value === "string") {
|
|
625
715
|
if (value.trim() === "") return [];
|
|
@@ -639,7 +729,7 @@ function normalizePresets(value) {
|
|
|
639
729
|
var stamp = Math.round(Number(entry.savedAt));
|
|
640
730
|
out.push({
|
|
641
731
|
name: name,
|
|
642
|
-
values: normalizeValueSet(entry.values),
|
|
732
|
+
values: normalizeValueSet(entry.values, relative),
|
|
643
733
|
savedAt: isFinite(stamp) && stamp > 0 ? stamp : 0,
|
|
644
734
|
});
|
|
645
735
|
}
|
|
@@ -1692,6 +1782,17 @@ function resolveAxes(config) {
|
|
|
1692
1782
|
*/
|
|
1693
1783
|
var WEIGHT_STEP_CANDIDATES = [1, 5, 10, 25, 50, 100, 200, 300];
|
|
1694
1784
|
|
|
1785
|
+
/**
|
|
1786
|
+
* How many positions a slider must still offer before a coarser step is refused.
|
|
1787
|
+
*
|
|
1788
|
+
* Coarseness is the point (see `weightProfileFrom`), but a control with two
|
|
1789
|
+
* positions is not a slider. Four is the smallest count that still lets a reader
|
|
1790
|
+
* go one notch either side of the neutral position and one further up — and on
|
|
1791
|
+
* the measured seven-face family it lands on a 200-weight step, where one notch is
|
|
1792
|
+
* a +30% change in ink, against +16% at 100 (which is why 100 was not enough).
|
|
1793
|
+
*/
|
|
1794
|
+
var WEIGHT_MIN_POSITIONS = 4;
|
|
1795
|
+
|
|
1695
1796
|
/** The weight grid the profile is measured on. */
|
|
1696
1797
|
var WEIGHT_PROBE_LOW = 100;
|
|
1697
1798
|
var WEIGHT_PROBE_HIGH = 900;
|
|
@@ -1733,19 +1834,28 @@ function everyPositionDiffers(sample, bases, min, max, step) {
|
|
|
1733
1834
|
/**
|
|
1734
1835
|
* The slider a family deserves: how far it can go, and by how much.
|
|
1735
1836
|
*
|
|
1736
|
-
* A font
|
|
1737
|
-
*
|
|
1738
|
-
* fine step and a wide range. Measured on a weight grid:
|
|
1837
|
+
* A font can be made lighter or heavier only as far as its faces reach, and only
|
|
1838
|
+
* in jumps between them. Measured on a weight grid:
|
|
1739
1839
|
* - the downward reach is the highest weight that still renders as light as the
|
|
1740
1840
|
* family can (asking for less cannot change anything);
|
|
1741
|
-
* - the upward reach is the LOWEST weight at or above
|
|
1742
|
-
*
|
|
1743
|
-
*
|
|
1744
|
-
*
|
|
1745
|
-
*
|
|
1746
|
-
*
|
|
1747
|
-
*
|
|
1748
|
-
*
|
|
1841
|
+
* - the upward reach is the LOWEST weight at or above body text that already
|
|
1842
|
+
* renders as heavy as the family can, so the range ends where the text a reader
|
|
1843
|
+
* is reading stops changing (see `WEIGHT_DELTA_UP_LIMIT` for the ceiling);
|
|
1844
|
+
* - the step is the COARSEST candidate that still leaves `WEIGHT_MIN_POSITIONS`
|
|
1845
|
+
* positions with body text changing at every one of them. Coarse is the point:
|
|
1846
|
+
* a finer step splits one face into several positions that all render the same,
|
|
1847
|
+
* so the control feels dead (measured on Noto Serif SC, whose faces are
|
|
1848
|
+
* 100-250 / 300 / 400 / 500 / 600 / 700 / 800-900: one 100-weight step is a
|
|
1849
|
+
* +16% change in ink, one 200-weight step +30%).
|
|
1850
|
+
*
|
|
1851
|
+
* Bold text and headings are deliberately NOT required to change at every
|
|
1852
|
+
* position: the heaviest ladder layer sits at 600 and reaches the ceiling at +300,
|
|
1853
|
+
* so no range can move it further. Body text is what a reader reads, so it carries
|
|
1854
|
+
* the requirement, and a heading already at its boldest face stays there.
|
|
1855
|
+
*
|
|
1856
|
+
* Every position is a whole number of steps from 0, so the card's step counts map
|
|
1857
|
+
* back onto exactly the offsets the document stores.
|
|
1858
|
+
*
|
|
1749
1859
|
* @param {(weight: number) => string|number} sample - renders a probe at one
|
|
1750
1860
|
* weight and returns a signature; equal signatures mean identical rendering.
|
|
1751
1861
|
* @param {{candidates?: number[], floor?: number, ceil?: number, top?: number}}
|
|
@@ -1793,27 +1903,49 @@ function weightProfileFrom(sample, options) {
|
|
|
1793
1903
|
}
|
|
1794
1904
|
var reachUp = Math.min(ceil, WEIGHT_DELTA_UP_LIMIT, headroom);
|
|
1795
1905
|
var reachDown = Math.max(floor, Math.min(0, lightest - WEIGHT_BASE));
|
|
1796
|
-
var
|
|
1797
|
-
|
|
1906
|
+
var cap = Math.min(reachUp, WEIGHT_DELTA_UP_LIMIT);
|
|
1907
|
+
/**
|
|
1908
|
+
* The shape one candidate step describes.
|
|
1909
|
+
*
|
|
1910
|
+
* Every position is a whole number of steps from 0, so the card's step counts
|
|
1911
|
+
* multiply back into exactly the offsets the document stores. The reach is
|
|
1912
|
+
* snapped to that grid without ever passing the hard floor or the ceiling —
|
|
1913
|
+
* rounding the top UP (which the first version did) put a position past the
|
|
1914
|
+
* widest allowed offset, and rounding the bottom DOWN could pass the floor.
|
|
1915
|
+
*/
|
|
1916
|
+
var shapeAt = function (step) {
|
|
1917
|
+
var down = Math.min(ceilTo(-reachDown, step), floorTo(-floor, step));
|
|
1918
|
+
return { min: -down, max: floorTo(cap, step), step: step };
|
|
1919
|
+
};
|
|
1920
|
+
var best = null;
|
|
1921
|
+
var bestCount = 0;
|
|
1922
|
+
// Coarsest candidate first: the coarsest step that still leaves enough visible
|
|
1923
|
+
// positions is the one a reader can actually work with.
|
|
1924
|
+
for (var index = candidates.length - 1; index >= 0; index -= 1) {
|
|
1798
1925
|
var step = candidates[index];
|
|
1799
1926
|
if (!(step > 0)) continue;
|
|
1800
|
-
var
|
|
1801
|
-
|
|
1802
|
-
|
|
1803
|
-
var
|
|
1804
|
-
|
|
1805
|
-
if (
|
|
1806
|
-
|
|
1807
|
-
|
|
1927
|
+
var shape = shapeAt(step);
|
|
1928
|
+
if (shape.max < shape.min) continue;
|
|
1929
|
+
if (!everyPositionDiffers(sample, [WEIGHT_BASE], shape.min, shape.max, step)) continue;
|
|
1930
|
+
var count = (shape.max - shape.min) / step + 1;
|
|
1931
|
+
if (count >= WEIGHT_MIN_POSITIONS) return shape;
|
|
1932
|
+
if (count > bestCount) {
|
|
1933
|
+
best = shape;
|
|
1934
|
+
bestCount = count;
|
|
1808
1935
|
}
|
|
1809
1936
|
}
|
|
1937
|
+
if (best !== null) {
|
|
1938
|
+
// Fewer positions than asked for is still usable; one position is the whole
|
|
1939
|
+
// range, and any step describes it, so use the finest and keep the unit small.
|
|
1940
|
+
if (best.max === best.min) return { min: best.min, max: best.max, step: 1 };
|
|
1941
|
+
return best;
|
|
1942
|
+
}
|
|
1943
|
+
// No step kept body text alive: the coarsest one that still fits is the honest
|
|
1944
|
+
// answer, never a step the control cannot reach.
|
|
1810
1945
|
var coarsest = candidates[candidates.length - 1];
|
|
1811
|
-
var
|
|
1812
|
-
var
|
|
1813
|
-
|
|
1814
|
-
// honest answer, never a step the control cannot reach.
|
|
1815
|
-
var span = max - min;
|
|
1816
|
-
return { min: min, max: max, step: span > 0 ? Math.min(coarsest, span) : 1 };
|
|
1946
|
+
var plain = shapeAt(coarsest);
|
|
1947
|
+
var span = plain.max - plain.min;
|
|
1948
|
+
return { min: plain.min, max: plain.max, step: span > 0 ? Math.min(coarsest, span) : 1 };
|
|
1817
1949
|
}
|
|
1818
1950
|
|
|
1819
1951
|
/**
|
|
@@ -1896,9 +2028,11 @@ function isDormant(config) {
|
|
|
1896
2028
|
sanitize(axis[MONO_FIELD]) === "" &&
|
|
1897
2029
|
clampOffset(axis[SIZE_DIALOG_FIELD]) === 0 &&
|
|
1898
2030
|
clampOffset(axis[CODE_SIZE_FIELD]) === 0 &&
|
|
1899
|
-
|
|
1900
|
-
|
|
1901
|
-
clampWeightDelta(axis[
|
|
2031
|
+
// These arrive normalized, so they are offsets already: converting them again
|
|
2032
|
+
// would rescale an offset that legitimately sits inside `300…600`.
|
|
2033
|
+
clampWeightDelta(axis[WEIGHT_FIELD], true) === WEIGHT_UNSET &&
|
|
2034
|
+
clampWeightDelta(axis[WEIGHT_DIALOG_FIELD], true) === WEIGHT_UNSET &&
|
|
2035
|
+
clampWeightDelta(axis[CODE_WEIGHT_FIELD], true) === WEIGHT_UNSET &&
|
|
1902
2036
|
clampRatio(axis[LINE_HEIGHT_DIALOG_FIELD]) === LINE_HEIGHT_MIN &&
|
|
1903
2037
|
clampLineOffset(axis[CODE_LINE_HEIGHT_FIELD]) === 0 &&
|
|
1904
2038
|
clampLigatures(axis[LIGATURES_FIELD]) === LIGATURES_DEFAULT &&
|
|
@@ -2778,6 +2912,9 @@ var shared = {
|
|
|
2778
2912
|
WEIGHT_FIELD: WEIGHT_FIELD,
|
|
2779
2913
|
WEIGHT_DIALOG_FIELD: WEIGHT_DIALOG_FIELD,
|
|
2780
2914
|
CODE_WEIGHT_FIELD: CODE_WEIGHT_FIELD,
|
|
2915
|
+
WEIGHT_OFFSETS_FIELD: WEIGHT_OFFSETS_FIELD,
|
|
2916
|
+
WEIGHT_FIELDS: WEIGHT_FIELDS,
|
|
2917
|
+
migratePresetWeights: migratePresetWeights,
|
|
2781
2918
|
LINE_HEIGHT_FIELD: LINE_HEIGHT_FIELD,
|
|
2782
2919
|
LINE_HEIGHT_DIALOG_FIELD: LINE_HEIGHT_DIALOG_FIELD,
|
|
2783
2920
|
CODE_LINE_HEIGHT_FIELD: CODE_LINE_HEIGHT_FIELD,
|
|
@@ -2872,6 +3009,7 @@ var shared = {
|
|
|
2872
3009
|
WEIGHT_STEP_CANDIDATES: WEIGHT_STEP_CANDIDATES,
|
|
2873
3010
|
WEIGHT_DELTA_FLOOR: WEIGHT_DELTA_FLOOR,
|
|
2874
3011
|
WEIGHT_DELTA_CEIL: WEIGHT_DELTA_CEIL,
|
|
3012
|
+
WEIGHT_MIN_POSITIONS: WEIGHT_MIN_POSITIONS,
|
|
2875
3013
|
WEIGHT_LADDER_TOP: WEIGHT_LADDER_TOP,
|
|
2876
3014
|
WEIGHT_LADDER_STRONG: WEIGHT_LADDER_STRONG,
|
|
2877
3015
|
WEIGHT_DELTA_UP_LIMIT: WEIGHT_DELTA_UP_LIMIT,
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "dsh-fonttune",
|
|
3
|
-
"version": "0.3.
|
|
3
|
+
"version": "0.3.5",
|
|
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
|
"main": "lib/index.js",
|