freetype.cr
freetype.cr — ftgrays + ttinterp на Crystal (этапы B1–B2 из PLAN.md)
Порт растеризатора FreeType ftgrays.c и интерпретатора TrueType-байткода ttinterp.c (снапшот 2.14.3, ~/freetype) на Crystal — самодостаточный, без зависимостей от остального FreeType. Цель: unhinted- и hinted-шрифты растеризуются пиксель-в-пиксель как системная libfreetype, без самой библиотеки.
Статус B1: выполнен. Оракул-диф (PLAN.md §7): 32 000 растеризаций глифов по 16 unhinted-шрифтам (Roboto всех начертаний, Noto Sans/Serif/ Mono; размеры 12/13/16/24/37) — диф 0 пикселей, включая размеры битмапов и смещения. Синтетический smoke — 5/5.
Статус B2: выполнен. Полный конвейер TTF → контур → хинтинг → растеризация целиком на Crystal. Приёмка: 198 790 hinted-глифов (Liberation, DejaVu, Verdana, Times и др.; 12/13/16/24/37 px) — контур+битмап диф 0, плюс 80 997 unhinted-глифов — диф 0. Зарегистрировано 2 «версионно-чувствительных» глифа (LiberationMono, 12/13 px), где системная libfreetype 2.13.3 расходится с мастером FT — наш вывод совпадает с мастером (проверено локальной сборкой).
Состав
src/ftgrays.cr— ядро:Ftgrays::Raster+Ftgrays::Outline(точки 26.6, y вверх). Портgray_render_line(64-битный вариант), коник через DDA (gray_render_conic), кубик через бисекцию (gray_render_cubic), клетки покрытия и свип с fill-rule. Битовая точность перенесена явно: wrapping-арифметика (&+ &- &*), арифметические сдвиги, C-делениеtdiv, точная репликаFT_UDIV. Пул клеток/биссекция полос заменены динамическими массивами (интегрирование площадей от разбиения на полосы не зависит).src/ftrender.cr— клейftsmooth.c/ftobjs.cдля FT_RENDER_MODE_NORMAL: control-box → пиксельный bbox (floor/ceil как вft_glyphslot_preset_bitmap) → трансляция → растеризация. Результат —Ftrender::GlyphBitmap(top-down 8-bit coverage).
Состав B2 (хинтинг)
src/fttrigon.cr— портfttrigon.c(тригонометрия на fixed-point:FT_Vector_Rotate,FT_Vector_Norm_Lenи т.п. — нужен для проекций на произвольные оси в байткоде).src/tt/sfnt.cr— минимальный парсер SFNT: head, maxp, hhea, hmtx, cmap (формат 4), loca, glyf, cvt, fpgm, prep, gasp. Только то, что нужно для загрузки контуров и хинтинга.src/tt/loader.cr— порт hint-glue изttgload.c/ttobjs.c: масштабирование,exec.run_fpgm/run_prep(cvt в 26.6, как в C),backward_compatibility, загрузка простых и композитных глифов, grid-fit advance (ft_glyphslot_grid_fit_metrics), write-back точек, scan-conversion флаг в tags[0].src/tt/ttinterp.cr(~4 600 строк) — порт VMttinterp.c: стек, зоны/точки, CVT, storage, функции, ~190 опкодов; wrapping-арифметика (&+ &- &*), C-семантика деления и сдвигов. Встроен трассер (TT_TRACE=1— пооператорный дамп VM, нулевая стоимость при выключенном флаге).
Важная деталь: FT отдаёт шрифты без байткода (fpgm пуст, prep ≤ 7 байт) автохинтеру (ftobjs.c:1016-1020) — поэтому unhinted-корпус (например, Roboto) в hinted-приёмке не участвует и покрывается отдельной unhinted-спекой.
Тесты
Важная деталь, найденная отладкой: FreeType применяет FT_Outline_Translate до декомпозиции контура, поэтому середины коник (v_start/v_middle, C-деление /2 с усечением к нулю) округляются на сдвинутых координатах — порт применяет сдвиг при чтении точек в decompose, а не при апскейле.
Тесты
crystal run spec/ftgrays_smoke.cr— синтетика: квадрат, треугольник, круг из коник, кубик, even-odd.crystal run --release spec/ftgrays_diff.cr [-- шрифты...]— приёмочный тест B1: диф против системной libfreetype (FFI-оракул, PLAN.md §7). Шрифты авто-классифицируются (unhinted = контуры с хинтингом и без совпадают); hinted пропускаются с пометкой (им нужны B2/C).DUMP_DIFFS=n— ASCII-дамп первых n расхождений.crystal run --release spec/tt_unhinted_diff.cr [-- шрифты...]— полный конвейер B1+B2 без хинтинга, диф контуров/битмапов/advanсов против FT_LOAD_NO_HINTING (80 997 глифов, диф 0).crystal run --release spec/tt_hinted_diff.cr [-- шрифты...]— приёмочный тест B2: hinted-глифы, сравнение контуров (точки+теги с маской 0xE7 — TOUCH-биты являются внутренностями VM и между версиями FT расходятся), битмапов и advanсов против FT_LOAD_DEFAULT (198 790 глифов, диф 0).spec/interp_probe.cr— ручные пробы VM на синтетике;tmp_c/— standalone-сборкаftgrays.cиз снапшота (второй оракул для бисектов: вход — текстовый дамп контура, выход — coverage). NB: собирается сftint64_shim.h(макросFT_INT64), иначе компилируется старый до-2.13 путь растеризации.spec/bisect_dbg.cr— трёхсторонний бисект глифа (наш / C-ftgrays / системный FT);spec/ffi_dbg.cr— проверка FFI-зеркала структур;spec/circle_dbg.cr— сверка синтетики с C-оракулом.
Дальше (по PLAN.md)
B1: растеризатор + спека-диф (диф == 0)✓B2: порт✓ttinterp+ sfnt-loader, спека-диф на hinted (диф == 0)- Включить в
LightHintedFonts(egui.cr) вместо FFI/эвристики, суперсэмплер иfreetype.cr— в отставку (FFI остаётся как debug-оракул в спеках) - libfreetype удаляется из зависимостей egui.cr
freetype.cr
- 0
- 0
- 0
- 0
- 0
- about 2 hours ago
- September 29, 2026
Tue, 29 Sep 2026 20:54:48 GMT