Мінікарта малює справжні літери, а не прямокутники
All checks were successful
CI / hygiene (push) Successful in 8s
CI / web (push) Successful in 1m7s
CI / server (push) Successful in 1m39s
CI / agent (push) Successful in 59s

Скільки не підбирай товщину, колір і щільність — прямокутник не стає
літерою. Арифметика: щоб текст читався, символу треба 5-6 пікселів;
при 120 символах у смузі 180 px виходило 1.4. Читабельним він не міг
стати за жодних налаштувань, і три попередні спроби це лікували
симптоматично.

Тепер canvas друкує ПОЧАТОК рядка шрифтом 7 px — найменшим, на якому
моноширинний ще лишається літерами (на 6 зникають внутрішні просвіти в
e, a, o). Ширина символа вимірюється, а не рахується формулою: у різних
системах підставляється різний моноширинний шрифт.

Для конфігу початок рядка й несе структуру: відступ і ключове слово
стоять зліва, а хвіст з адресами не читався б однаково. Табуляція
розгортається в пробіли — у canvas вона не має ширини, і блоки JunOS
з їхали б до лівого краю.

Рядків у вікні стало близько вісімдесяти замість двохсот. Це обмін, а
не втрата: двісті нечитабельних показують силует файла, вісімдесят
читабельних показують, ДЕ ти в ньому.

Запасний шлях прямокутниками лишився для порівняння версій, де рядки
беруться з різниці, а не з файла.
This commit is contained in:
byrsapty 2026-08-27 23:18:22 +03:00
parent 3a52d4a870
commit 68fd47ff19

View file

@ -79,7 +79,22 @@ export const MAP_W = 180
* Розмін свідомий: мініатюра, на якій нічого не розібрати, не стає * Розмін свідомий: мініатюра, на якій нічого не розібрати, не стає
* кориснішою від того, що вміщає більше. * кориснішою від того, що вміщає більше.
*/ */
const ROW_PX = 3 const ROW_PX = 8
/**
* Розмір шрифту мініатюри.
*
* Сім пікселів найменше, на чому моноширинний шрифт ще лишається
* ЛІТЕРАМИ, а не сірою крупою. Менше пробували: на шести зникають
* внутрішні просвіти в «e», «a», «o», і слово перетворюється на пляму
* тієї самої довжини.
*
* Звідси й ROW_PX = 8: сім на літери, один на просвіт між рядками.
* Рядків у вікні стає близько вісімдесяти замість двохсот і це не
* втрата, а обмін. Двісті нечитабельних рядків показують силует файла;
* вісімдесят читабельних показують, ДЕ ти в ньому. Друге і просили.
*/
const FONT_PX = 7
/** /**
* Товщина штриха рядка. Один піксель, а не два і це не косметика. * Товщина штриха рядка. Один піксель, а не два і це не косметика.
@ -125,21 +140,6 @@ const GUTTER_W = 6
*/ */
const MAX_COLS = 120 const MAX_COLS = 120
/**
* Скільки слів у рядку малюємо окремо.
*
* Саме проміжки між словами роблять смужку схожою на текст: суцільна
* риса від відступу до кінця рядка читається як штрих, а не як рядок.
*
* Стеля потрібна, бо в конфігах трапляються рядки на сотні коротких
* лексем (списки VLAN, префікс-листи): малювати їх усі це тисячі
* прямокутників заради ділянки в кілька пікселів завширшки.
*/
const MAX_SEGS = 40
const SP = ' '
const TAB = ' '
/** /**
* Форма тексту: довжина й відступ кожного рядка. * Форма тексту: довжина й відступ кожного рядка.
* *
@ -384,6 +384,25 @@ export function Minimap({
ctx.clearRect(0, 0, MAP_W, h) ctx.clearRect(0, 0, MAP_W, h)
const left = fits ? 3 : GUTTER_W + 3 const left = fits ? 3 : GUTTER_W + 3
// Справжній текст замість штрихів.
//
// Попередні версії малювали слова прямокутниками — і скільки не
// підбирай товщину, колір і щільність, прямокутник не стає літерою.
// Арифметика проста: щоб текст читався, символу треба 56 пікселів;
// при 120 символах у смузі 180 px виходило 1.4, тобто читабельним
// він не міг стати за жодних налаштувань.
//
// Тому показуємо ПОЧАТОК рядка справжніми літерами. Для конфігу це
// саме те, що несе структуру: відступ і ключове слово стоять зліва,
// а хвіст із адресами й масками не читався б однаково.
ctx.font = FONT_PX + 'px ui-monospace, SFMono-Regular, Menlo, Consolas, monospace'
ctx.textBaseline = 'top'
// Ширину символа беремо вимірюванням, а не формулою: у різних
// системах підставляється різний моноширинний шрифт, і константа
// тут рано чи пізно розійшлася б із дійсністю мовчки.
const charW = ctx.measureText('0').width || FONT_PX * 0.6
const maxChars = Math.max(1, Math.floor((MAP_W - left - 3) / charW))
const scale = (MAP_W - left - 3) / cols const scale = (MAP_W - left - 3) / cols
// У режимі «влазить цілком» рядку дістається стільки пікселів, // У режимі «влазить цілком» рядку дістається стільки пікселів,
@ -411,24 +430,19 @@ export function Minimap({
ctx.fillStyle = color ctx.fillStyle = color
// Без доступу до самого тексту лишається суцільна риса від // Без доступу до самого тексту лишається суцільна риса від
// відступу до кінця рядка. Працює, але читається гірше. // відступу до кінця рядка. Це запасний шлях: ним іде порівняння
// версій, де рядки беруться не з файла, а з різниці.
const text = lineAt?.(i) const text = lineAt?.(i)
if (!text) { if (!text) {
bar(shape.indent[i], L, y) bar(shape.indent[i], L, y)
return return
} }
let j = 0 // Табуляція в canvas не має ширини — розгортаємо в пробіли, інакше
let segs = 0 // блоки JunOS з'їхали б до лівого краю й структура зникла саме
while (j < text.length && segs < MAX_SEGS) { // там, де вона найпомітніша.
while (j < text.length && (text[j] === SP || text[j] === TAB)) j++ const flat = text.length > maxChars * 2 ? text.slice(0, maxChars * 2) : text
const a = j ctx.fillText(flat.replace(/ /g, ' ').slice(0, maxChars), left, y)
while (j < text.length && text[j] !== SP && text[j] !== TAB) j++
if (j > a) {
bar(a, j, y)
segs++
}
}
} }
if (fits) { if (fits) {