Решили издать каталог подразделений, проектов и людей достаточно крупной организации — и встал вопрос, в чем именно готовить макет: InDesign, TeX или typst. При выборе инструмента хотелось учесть:
1) удобство работы каждого участника,
2) удобство совместной работы,
3) удобство внесения правок в последний момент.
Третий пункт даже был самым важным, посколько было очевидно, что первоначальные данные весьма грязные, будут правки не только орфографические, но и в масштабе плюс/минус подраздел.
InDesign — старый добрый друг, в котором есть работа со стилями, скрипты типа DoTextOK, генерация оглавлений и прочее. Но я пока не освоил систему совместной работы в InDesign, плюс планировалась верстка с выводом данных сеткой — а это означало бы, что при добавлении или удалении одного элемента немалую часть работы пришлось бы делать заново (опасения о таких подковырках оправдались). Пугала еще одна рутинная процедура — вставка вручную QR-кодов, которых в итоге оказалось 170 штук. Напутать их было бы проще простого.
TeX для меня — тоже старый добрый друг, о сопоставлении его с InDesign я уже писал на Хабре. Совместную работу можно было организовать в git, устойчивость к внесению правок в последний момент вполне надежная. QR-коды умеет генерировать сам.
Но на горизонте появился typst, который работает по принципу TeX'а (гибрид текста и команд + картинки --> pdf), но в несколько раз быстрее (особенно если TeX надо прогонять несколько раз для выставления перекрестных ссылок). Еще больше порадовал typst тем, что можно прямо в режиме реального времени видеть результат всех вносимых изменений (Visual Studio Code + плагины для typst), а также прыгать из превьюшки на нужное место кода, а из кода — на нужное место превьюшки.
И окончательное решение в сторону typst было принято, когда стало понятно, что в нем можно удобно представить исходные данные по типу JSON, конкретно у меня — как список словарей
#let names = ((name: "Иван", year: "2000"), (name: "Петр", year: "2010"))
а далее сортировать, фильтровать, заниматься арифметическими вычислениями. Ну и он умеет делать QR-коды.
Итак, был выбран typst, а для изящной типографики (и опять же, для избавления от прогонки скриптами) вот такая постепенно сформировалась преамбула.
Размеры страницы, поля, нижний колонтитул с выравниванием по внешнему краю. У нас не было картинок с вылетом за обрез, так что типография приняла макет в обрезном формате, но в typst можно задать и обрезной отступ параметром outset.
#set page( width: 170mm, height: 225mm, margin: (inside: 21mm, outside: 14mm, top: 19mm, bottom: 28mm), numbering: "1", footer: context{ set text(size: 16pt, number-type: "old-style", number-width: "proportional") let (n,) = counter(page).get() set align(if calc.even(n) { left } else { right }) counter(page).display("1") } )
Основной шрифт задан был так:
#set text(font: "Ladoga", lang: "ru", size: 12pt, fill: cmyk(0%,0%,0%,100%))
Последняя строка появилась уже при проверке макета в Adobe Acrobat Pro: оказалось, что основной текст идет не чистым черным, а составным из 4 красок — на экране это, конечно, не заметишь, а вот при офсетной печати в типографии на такой подход ругались бы знатно.
С русским языком typst работает хорошо, неадекватных переносов замечено не было (в отличие от InDesign, в который правильно ставить модуль переносов Батова).
Абзацы равняем по ширине, выставляем интерлиньяж (spacing) и абзацный отступ (я сторонник убирать «красную строку» после заголовков, так что all: false).
#set par(justify: true, spacing: 0.65em, first-line-indent: (amount: 1em, all: false), )
Дальше идет ряд директив, указывающих, как обращаться со знаками процента, номера, однобуквенными словами (считаю, что лепить их к следующему слову надо только в случае заглавной буквы). Тире не должно отрываться от предыдущего слова, а отступы вокруг него хочется сжать до 30%. Сокращения в адресах не должны отрываться от имен населенных пунктов и от чисел.
// знак процента #let percents = regex("(\d+)\s*\%") #show percents: it => { let (d, ) = it.text.match(percents).captures [#box([#d\u{2009}%])] } // знак номера #let numero = regex("№\s*(\d)") #show numero: it => { let (d, ) = it.text.match(numero).captures [№\u{2009}#d] } // однобуквенными слова в начале предложения #let aviko = regex("(\b[АВИКОСУЯ])\s+") #show aviko: it => { let (d, ) = it.text.match(aviko).captures [#d~] } // инициалы и фамилия #let fio = regex("(\b[А-ЯЁ]\.)\s*([А-ЯЁ]\.)\s*([А-ЯЁ][а-яё])") #show fio: it => { let (i, o, f) = it.text.match(fio).captures [#i\u{202F}#o\u{202F}#f] } // тире #let tire = regex("\s+(—)\s+") #show tire: it => { let (d, ) = it.text.match(tire).captures [#text(spacing: 30%)[~#d ]] } // сокращения в адресах #let gorod_selo = regex("\b((г|с|пос|дер|д|ул|пер|п|кв)\.)\s+") #show gorod_selo: it => { let d = it.text.match(gorod_selo).captures.first() [#d~] }
Отдельная заморочка была с телефонами. Убираем все лишние символы, определяем типичные 5-циферные коды регионов, выводим красиво и единообразно, без скобок, с пробелами и маленькими центральными точками, отделяющими последние две пары цифр, типа +7 910 123⋅45⋅67. Местные номера — по типу +7 48532 1⋅23⋅45. Если эстетическое чувство захочет изменить подачу, меняем всё несколькими нажатиями клавиш.
#let format_phone(p) = { let psplit = p.split(" доб. ") if psplit.len() > 1 { return format_phone(psplit.at(0)) + " доб." + sym.space.nobreak.narrow + psplit.at(1) } let digits = p.replace(regex("[^0-9]"), "") if digits.len() != 11 {return p} let formatted = "+7" let space = " " let space_nobreak = sym.space.nobreak // let space = sym.space.narrow // let space_nobreak = sym.space.nobreak.narrow let open_bracket = "" let close_bracket = "" // let open_bracket = "(" // let close_bracket = ")" // let separator = "-" let separator = sym.space.hair + sym.dot.op + sym.space.hair if digits.slice(1,3) == "48" { formatted += space_nobreak + open_bracket + digits.slice(1,6) + close_bracket + space + digits.at(6) + separator + digits.slice(7,9) + separator + digits.slice(9,11)} else { formatted += space_nobreak + open_bracket + digits.slice(1,4) + close_bracket + space + digits.slice(4,7) + separator + digits.slice(7,9) + separator + digits.slice(9,11)} return formatted }
С форматированием электронных адресов всё проще, просто убираем заглавные:
#let format_email(e) = {lower(e)}
Счастье, которое было у меня от генерации QR-кодов командами qrcode("habr.com", 2cm), почти рухнуло на этапе препресса �� Adobe Acrobat выдал ошибку, что все QR-коды не чисто чёрные, а 4-цветные. Прописывание cmyk-цвета в качестве параметра вызова не приносило нужного результата. Связано это было с тем, что генерация QR-кода происходит через промежуточный этап в SVG, а формат SVG хранит информацию о цвете только RGB, но не CMYK. Тыканье мэтров на форумах не принесло результатов. Пришлось самому написать код, который разбирает текст SVG и отрисовывает. Оказалось не слишком сложно. Там QR-код записан как набор прямоугольников, заданных командами типа "M1 2h3v4h-3Z": сдвинься в точку (1,2), рисуй направо 3 клетки, вверх 4 клетки, налево 3 клетки, замкни контур. Парсим, получаем массив координат и размеров, рисуем. Правда, это решение было придумано на следующий день после того, как макет ушел в печать — быстрее оказалось прощелкать все 170 QR кодов в Acrobat'е и преобразовать их в чистый CMYK-черный.
#let black_cmyk_qrcode(text, width: 1.5cm, color: cmyk(0%, 0%, 0%, 100%)) = { let svg-bytes = qrcode(text).source let qr_path = str(svg-bytes).match(regex("<path d=\"([^\"]*)")).captures.at(0) // get the path part of code from SVG let qr_rectangles_text = qr_path.matches(regex("M(\d+) (\d+)h(\d+)v(\d+)h-\d+Z")) let qr_rectangles_coord = () for qr in qr_rectangles_text { qr_rectangles_coord.push(("x", "y", "width", "height").zip(qr.captures.map(x => int(x))).to-dict()) } let tiles = calc.max(..qr_rectangles_coord.map(x => (x.x + x.width))) let tile_size = width/tiles rect(width:width, height: width, inset: 0mm, stroke: 0pt, { for r in qr_rectangles_coord { place(dx: r.x*tile_size, dy: r.y*tile_size, rect(height: r.height*tile_size, width: r.width*tile_size, fill: color)) }} ) }
Итоговый макет на 240 страниц содержал 480 картинок, 170 QR-кодов и весил 2.2Gb. С таким размером он уже переставал показывать превью в Visual Studio Code.
Сжатый вариант макета весом 6Mb можно посмотреть/скачать тут.
Главный вывод, который я сделал для себя — typst удобен, быстр и перспективен, буду дальше пользовать его и для книг, и для рутины с договорами, и для разминки мозгов.
