Типограф на PHP: кавычки-ёлочки, тире и неразрывные пробелы
Типографика ♡
В русском тексте кавычки ставят «ёлочками», тире делают длинным, а предлог «в» не оставляют одиноко висеть в конце строки (визуальный кринж, ты его точно видел). Типограф делает всё это за тебя. Сложность не в том, чтобы поставить нужные значки, а в том, чтобы при этом не зацепить остальное: даты, числа и разметку.
Главное правило: не навреди
В комментарии к классу Typograf записано: правила намеренно осторожные, лучше пропустить спорное место, чем испортить текст. Резонно. Типограф, который иногда ломает даты и код, бросают после первой же аварии, а пропущенное тире никто и не заметит.
Поэтому группы правил включаются по отдельности: кавычки, тире, пробелы, символы, пунктуация. И есть ещё два режима: «это HTML» и «заменить символы на HTML-сущности».
Как не зацепить HTML
Если на входе HTML, кавычки в атрибутах тегов менять нельзя, иначе разметка превратится в пиццу с ананасами. Разбирать её отдельным парсером слишком громоздко, так что я её прячу. Теги, комментарии и содержимое pre, code, script, style и textarea заменяются на невидимые символы из «области частного использования» Unicode, а после обработки по очереди возвращаются на место:
$text = (string) preg_replace_callback(self::TAGS, static function (array $m) use (&$held): string {
$held[] = $m[0];
return str_starts_with($m[0], '</') ? self::CLOSE : self::OPEN;
}, $text);
Открывающие и закрывающие теги получают разные символы, чтобы алгоритм кавычек понимал, где заканчивается слово рядом с тегом.
Кавычки с вложенностью
Сначала все виды кавычек приводятся к обычной прямой. Потом сканер идёт по символам и считает глубину. Кавычка считается открывающей, если перед ней начало текста, пробел, скобка или тире, а после неё не пробел и не знак препинания. Первый уровень получает «ёлочки», второй „лапки“, всё по классике.
На переносе строки счётчик обнуляется: одна забытая кавычка не должна портить весь остальной текст. Я проверила это на такой строке: вложенная цитата превращается в «Привет, „мир“», а счётчик насчитывает две пары кавычек. Красота.
Что ещё умеют правила
- Дефис между пробелами становится тире с неразрывным пробелом перед ним, а дефис в начале строки превращается в тире диалога.
- В диапазонах вроде «5-7» ставится короткое тире, а даты вида 2026-09-30 остаются в покое.
- Три точки становятся многоточием, (c) знаком ©, (tm) знаком ™, «No 5» превращается в «№ 5», а «10x20» в «10×20».
- Неразрывный пробел ставится после коротких предлогов и частиц («в», «на», «по», «ли», «бы»), между числом и единицей («5 кг», «100 руб.»), внутри числа вроде «1 000 000» и между инициалами и фамилией.
- Лишние пробелы перед запятой и внутри скобок и кавычек убираются, а после запятой добавляются.
Как не угробить даты
Самое опасное правило то, которое меняет дефис между числами на тире: «5-7» должно стать «5–7». Но тот же дефис стоит в дате «2026-09-30», и превратить её в кашу это уровень аварии на проде в пятницу вечером. Спасают проверки вокруг числа: слева и справа от дефиса не должно быть ни цифры, ни другого дефиса.
$text = (string) preg_replace('/(?<![\d\-])(\d+)-(\d+)(?![\d\-])/u', '$1–$2', $text);
Дата остаётся датой, а диапазон получает нужное тире. Такие «оглядки» назад и вперёд в регулярках и есть главный инструмент осторожности во всём типографе.
Чего типограф не обещает
В сложных случаях (дюймы, апострофы) он может неправильно понять кавычки. Правил для английского текста нет. Зато результат прозрачный: функция возвращает не только текст, но и счётчики, сколько поставлено пар кавычек, тире и неразрывных пробелов, так что видно, что именно поменялось. Но глазами человека всё равно пробежаться стоит, автоматика не заменяет здравый смысл.
Принцип «не уверена, не трогай» я применила и в минификаторе JavaScript. Пока что работает, тьфу-тьфу.
Комментарии
Буду рада вашим мыслям, идеям и вопросам!
Давайте обсуждать ♡
Делитесь
своими мыслями
— мне очень
это важно ♡