Typographer 0.1.0
dotnet add package Typographer --version 0.1.0
NuGet\Install-Package Typographer -Version 0.1.0
<PackageReference Include="Typographer" Version="0.1.0" />
<PackageVersion Include="Typographer" Version="0.1.0" />
<PackageReference Include="Typographer" />
paket add Typographer --version 0.1.0
#r "nuget: Typographer, 0.1.0"
#:package Typographer@0.1.0
#addin nuget:?package=Typographer&version=0.1.0
#tool nuget:?package=Typographer&version=0.1.0
Typographer
Документация: https://gberikov.github.io/Typographer/ · справочник правил
Типограф для русского языка на .NET: кавычки-ёлочки, тире, неразрывные пробелы, работает и с обычным текстом, и с HTML-фрагментами.
Статус: реестр закрыт — 107 правил, шесть фаз конвейера, пять пакетов. Не реализованы три правила, которые не могут быть реализованы без нарушения гарантий (
common/punctuation/quoteLink,common/html/stripTags,common/html/processingAttrs), и отложеноru/typo/switchingKeyboardLayout. Публикации в NuGet ещё не было.
Использование
Быстрый старт — статический фасад с настройками по умолчанию:
using Typographer;
string html = Typographer.Html("Он сказал: \"Привет!\" - и махнул рукой.");
string text = Typographer.PlainText("Он сказал: \"Привет!\" - и махнул рукой.");
Настраиваемый вариант — свой набор правил, кодирование сущностей, перенос строк и абзацы:
using Typographer;
using Typographer.Rules;
var typographer = new HtmlTypographer(new HtmlOptions
{
Rules = RuleSet.Default.Without(RuleId.Ru.Nbsp.Initials),
Entities = EntityMode.Named,
UseBr = true,
MaxNobr = 3,
});
string html = typographer.Process("Он сказал: \"Привет!\" - и махнул рукой.\nВторая строка.");
Для обычного текста — TextTypographer и TextOptions (без Entities, UseBr, UseP,
MaxNobr: они имеют смысл только в HTML, поэтому в TextOptions их физически нет):
var typographer = new TextTypographer(new TextOptions { Rules = RuleSet.Minimal });
string text = typographer.Process("Он сказал: \"Привет!\" - и махнул рукой.");
Обе точки входа умеют писать результат прямо в приёмник без промежуточной строки:
using System.Buffers;
var writer = new ArrayBufferWriter<char>();
HtmlTypographer.Default.Process("Он сказал: \"Привет!\"".AsSpan(), writer);
Наборы правил
RuleSet — иммутабельное множество включённых правил с готовыми пресетами:
| Пресет | Смысл |
|---|---|
RuleSet.Default |
безопасная типографика — пресет по умолчанию |
RuleSet.Minimal |
только кавычки, тире и многоточие |
RuleSet.All |
все зарегистрированные правила, включая ещё не реализованные |
RuleSet.None |
ничего не менять |
RuleSet.Lebedev, RuleSet.Gost, RuleSet.Typograf |
пока совпадают с Default — правила, которые должны их различать, ещё не реализованы (см. XML-комментарии на этих пресетах) |
RuleSet rules = RuleSet.Default.With(RuleId.Ru.Dash.Years).Without(RuleId.Ru.Nbsp.Abbr);
bool enabled = rules.Contains(RuleId.Ru.Dash.Years);
Идемпотентность и разметка
Правила RuleSet идемпотентны: повторный прогон ничего не меняет. Исключение — опции
UseBr и MaxNobr: они рассчитаны на однократное применение к исходному тексту. Прогон
по СОБСТВЕННОМУ ВЫВОДУ типографа с той же опцией вложит разметку в саму себя
(<nobr><nobr>текст</nobr></nobr>) — типограф не распознаёт свой прошлый вывод, это
намеренное решение (см. docs/spec.md, гарантия 5).
UseP в это исключение не входит: абзацы размечаются по документу целиком, а не по
каждому текстовому узлу, и во входе с готовой блочной разметкой (<p>, <ul>, <div>…)
опция не применяется — границы абзацев там задаёт сама разметка.
Незакрытые теги и защищённые области также отключают UseP, чтобы добавленный </p>
не оказался внутри атрибута, комментария или скрипта. UseBr обрабатывает только текст
вне тегов и защищённых областей.
BOM (U+FEFF) удаляется только в начале документа. Внутри текста этот символ сохраняется:
его удаление могло бы превратить текст в HTML-тег или сущность.
Целевые платформы
| TFM | Зачем |
|---|---|
netstandard2.0 |
.NET Framework 4.6.1+, Unity, Xamarin, старые библиотеки |
net8.0 |
текущая LTS |
net10.0 |
актуальная LTS, Span/SearchValues, AOT |
Пакеты
| Пакет | Зачем | Зависимости | Платформы |
|---|---|---|---|
Typographer |
ядро: HTML и обычный текст, правила, пресеты | нет на net8.0 и net10.0; System.Memory на netstandard2.0 |
netstandard2.0, net8.0, net10.0 |
Typographer.DependencyInjection |
AddTypographer() |
Microsoft.Extensions.DependencyInjection.Abstractions |
netstandard2.0, net8.0, net10.0 |
Typographer.AspNetCore |
тег-хелпер <typographer> и IHtmlContent |
ASP.NET Core | net8.0, net10.0 |
Typographer.Markdig |
типографика Markdown | Markdig |
netstandard2.0, net8.0, net10.0 |
dotnet-typographer |
утилита командной строки | ядро | net10.0 |
Контейнер
services.AddTypographer(); // настройки по умолчанию
services.AddTypographer(new HtmlOptions { Entities = EntityMode.Named });
Регистрируются одиночками HtmlTypographer и TextTypographer. Своя регистрация, сделанная
раньше, побеждает: внутри TryAddSingleton.
ASP.NET Core
@addTagHelper *, Typographer.AspNetCore
<typographer><p>Он - человек и "цитата"</p></typographer>
Тег-хелпер типографирует содержимое и исчезает сам. Требует services.AddTypographer().
Там, где удобнее вызов, а не элемент:
@inject HtmlTypographer Typographer
@Typographer.ToHtmlContent(Model.Text)
Markdown
MarkdownPipeline pipeline = new MarkdownPipelineBuilder().UseTypographer().Build();
string html = Markdown.ToHtml(source, pipeline);
Правки вносятся в дерево документа после разбора, а не в готовый HTML: рендерер кодирует
прямую кавычку в ", и в отрендеренном HTML ёлочки уже не появились бы. Поэтому код,
адреса ссылок и встроенный HTML остаются нетронутыми, а кавычки вокруг разметки —
"**слово**" — смотрят в разные стороны.
Командная строка
dotnet tool install --global dotnet-typographer
echo 'Он - человек' | dotnet-typographer
dotnet-typographer --entities named --in-place статья.html
dotnet-typographer --help
Разработка
dotnet build
dotnet test
Четыре уровня проверки, три из них в CI:
| Уровень | Что проверяет | Команда |
|---|---|---|
| Unit и property | правила поштучно, гарантии, каждое правило реестра в одиночку | dotnet test |
| Golden-корпус | tests/Typographer.Corpus — пары «вход — эталон» |
dotnet test |
| Снимок оракула | наши расхождения с typograf.artlebedev.ru (без сети) |
dotnet test |
| Живая сверка | не устарел ли сам снимок (с сетью, вне CI) | TYPOGRAPHER_ORACLE=1 dotnet test tests/Typographer.Oracle |
| Фаззинг | конвейер не падает ни на каком входе | workflow Fuzz, по расписанию |
Эталоны корпуса и список расхождений перегенерируются переменными
TYPOGRAPHER_UPDATE_CORPUS=1 и TYPOGRAPHER_UPDATE_ORACLE=1. Тест при этом падает
намеренно: перезапись эталона — не проверка.
Производительность
Бенчмарки — bench/Typographer.Bench (BenchmarkDotNet).
Запуск: dotnet run -c Release --project bench/Typographer.Bench.
Последний замер — docs/perf.md. Коротко, на входе в 22 200 символов:
| Сценарий | Пропускная способность | Аллокации |
|---|---|---|
Html(string) |
28,0 млн симв./с | сама возвращаемая строка |
Html(span, IBufferWriter) |
27,3 млн симв./с | 0 байт |
Обещание про ноль аллокаций выполнено. Обещание про 50 млн символов в секунду — нет:
получается 28. Разрыв записан и объяснён в docs/perf.md; там же сравнение с замером
плана 1, когда правил было четырнадцать вместо ста семи.
Ветвление — git flow
master— релизы, тег вида1.2.3на каждый релиз (версия пакета — MinVer);develop— интеграционная ветка;feature/*,release/*,hotfix/*— как в git flow.
master, develop и теги *.*.* защищены правилами репозитория: только PR, без force-push и удаления.
Релиз: тег вида 0.1.0 (без префикса v) на master запускает workflow Release —
он собирает, прогоняет тесты, упаковывает пять пакетов, публикует их в NuGet и создаёт
релиз на GitHub. Для публикации нужен секрет репозитория NUGET_API_KEY; без него
workflow останавливается с понятной ошибкой, а не публикует половину.
Лицензия
MIT
| Product | Versions Compatible and additional computed target framework versions. |
|---|---|
| .NET | net5.0 was computed. net5.0-windows was computed. net6.0 was computed. net6.0-android was computed. net6.0-ios was computed. net6.0-maccatalyst was computed. net6.0-macos was computed. net6.0-tvos was computed. net6.0-windows was computed. net7.0 was computed. net7.0-android was computed. net7.0-ios was computed. net7.0-maccatalyst was computed. net7.0-macos was computed. net7.0-tvos was computed. net7.0-windows was computed. net8.0 is compatible. net8.0-android was computed. net8.0-browser was computed. net8.0-ios was computed. net8.0-maccatalyst was computed. net8.0-macos was computed. net8.0-tvos was computed. net8.0-windows was computed. net9.0 was computed. net9.0-android was computed. net9.0-browser was computed. net9.0-ios was computed. net9.0-maccatalyst was computed. net9.0-macos was computed. net9.0-tvos was computed. net9.0-windows was computed. net10.0 is compatible. net10.0-android was computed. net10.0-browser was computed. net10.0-ios was computed. net10.0-maccatalyst was computed. net10.0-macos was computed. net10.0-tvos was computed. net10.0-windows was computed. |
| .NET Core | netcoreapp2.0 was computed. netcoreapp2.1 was computed. netcoreapp2.2 was computed. netcoreapp3.0 was computed. netcoreapp3.1 was computed. |
| .NET Standard | netstandard2.0 is compatible. netstandard2.1 was computed. |
| .NET Framework | net461 was computed. net462 was computed. net463 was computed. net47 was computed. net471 was computed. net472 was computed. net48 was computed. net481 was computed. |
| MonoAndroid | monoandroid was computed. |
| MonoMac | monomac was computed. |
| MonoTouch | monotouch was computed. |
| Tizen | tizen40 was computed. tizen60 was computed. |
| Xamarin.iOS | xamarinios was computed. |
| Xamarin.Mac | xamarinmac was computed. |
| Xamarin.TVOS | xamarintvos was computed. |
| Xamarin.WatchOS | xamarinwatchos was computed. |
-
.NETStandard 2.0
- System.Memory (>= 4.6.0)
-
net10.0
- No dependencies.
-
net8.0
- No dependencies.
NuGet packages (2)
Showing the top 2 NuGet packages that depend on Typographer:
| Package | Downloads |
|---|---|
|
Typographer.DependencyInjection
Регистрация типографа в контейнере Microsoft.Extensions.DependencyInjection. |
|
|
Typographer.Markdig
Типографика Markdown: расширение конвейера Markdig. |
GitHub repositories
This package is not used by any popular GitHub repositories.
| Version | Downloads | Last Updated |
|---|---|---|
| 0.1.0 | 188 | 9/10/2026 |