Содержание урока · 10 разделов
- chromedp pdf: команда протокола PrintToPDF
- html в pdf go: размер листа и параметры
- Параметры page.PrintToPDF
- Шаблон и CSS печати
- Колонтитулы: headerTemplate и footerTemplate
- Скриншот или PDF: выбор артефакта
- Чек-лист документа, который не придётся перепечатывать
- Песочница урока
- Коротко: что запомнить
- Вопросы и ответы
Счёт на оплату — документ, который живёт в двух мирах: витрина показывает его как HTML-страницу, бухгалтерия принимает в PDF. Писать генератор PDF руками — значит перерисовывать вёрстку второй раз на другом движке, и рано или поздно версии расходятся: в HTML цена синяя, в PDF чёрная, в HTML таблица шире, в PDF переносится. Печать из браузера устраняет саму постановку проблемы: Chrome умеет печатать страницы в PDF с незапамятных времён, а chromedp просто вызывает эту способность из кода. Типовой конвейер хорошо ложится на обычный Go: данные из базы наполняют HTML-шаблон средствами text/template, готовая страница открывается в браузере — по адресу собственного эндпоинта или из файла — и печатается в буфер, а буфер уезжает на диск или в хранилище. Документ печатается так, как его отрисовал движок: шрифты, стили, таблицы и кириллица собираются тем же механизмом, что и на экране. Это второй артефакт рендера в курсе после скриншотов из седьмого урока: как и кадр, PDF нельзя получить HTTP-запросом — страница должна быть собрана и отрисована. Отличие в том, что печать идёт по правилам печатной страницы: лист, поля, разрывы и колонтитулы, — и эти правила задаются параметрами команды, а не вёрсткой. Печатаемый документ — не всегда витрина сайта: часто это локальный шаблон, который ваша же программа наполняет данными и отдаёт браузеру; механика печати от источника HTML не меняется.
chromedp pdf: команда протокола PrintToPDF#
Первое, что стоит знать: в chromedp нет обёртки вида chromedp.PrintToPDF. Печать — команда протокола Page.printToPDF, и живёт она в пакете cdproto/page, который ставится вместе с библиотекой. Вызов устроен в два слоя: page.PrintToPDF() возвращает конструктор параметров, у которого цепочкой настраиваются опции — WithLandscape, WithPrintBackground, WithPaperWidth и другие, — а метод Do(ctx) исполняет команду и возвращает три значения: срез байтов готового документа, идентификатор потока и ошибку. Байты приезжают уже декодированными из base64, поэтому результат пишется на диск одним os.WriteFile — конвертация на вашей стороне не нужна. Так как это не привычное Query-действие, вызов оборачивается в chromedp.ActionFunc внутри обычного Run — приём, который встречается во всех официальных примерах библиотеки. Внутри ActionFunc доступен тот же контекст, и ошибка команды возвращается как обычная Go-ошибка: файл не напечатался — Run вернёт её наверх. Печатается текущий документ вкладки, поэтому перед печатью программа навигируется и дожидается рендера теми же WaitVisible, что и перед скриншотом. Генерация PDF в Chrome работает в headless-режиме — chromedp запускает headless по умолчанию, поэтому из коробки всё работает без дополнительных флагов; если включить окно ради отладки, шаг печати придётся пропускать.
Счёт из шаблона: полная программа
GOpackage main
import (
"context"
"os"
"github.com/chromedp/chromedp"
"github.com/chromedp/cdproto/page"
)
func main() {
ctx, cancel := chromedp.NewContext(context.Background())
defer cancel()
var buf []byte
err := chromedp.Run(ctx,
chromedp.Navigate("https://example.com/invoice/1042"),
chromedp.WaitVisible("#invoice"),
chromedp.ActionFunc(func(ctx context.Context) error {
var err error
buf, _, err = page.PrintToPDF().
WithPrintBackground(true).
WithPaperWidth(8.27).
WithPaperHeight(11.69).
Do(ctx)
return err
}),
)
if err != nil {
panic(err)
}
os.WriteFile("invoice-1042.pdf", buf, 0o644)
}ActionFunc оборачивает печать в обычное действие для Run: page.PrintToPDF() собирает параметры, цепочка With задаёт фон и размер листа A4 в дюймах, Do исполняет команду. Do возвращает три значения — байты документа, идентификатор потока и ошибку; поток здесь не нужен, поэтому второй результат уходит в подчёркивание. Готовый буфер пишется на диск с правами 0o644.
html в pdf go: размер листа и параметры#
Размеры бумаги в протоколе задаются в дюймах — единицах печатного мира, а не веба. Дефолт команды — лист 8,5 на 11 дюймов, американский Letter; для российской бухгалтерии нужен A4, и это 8,27 на 11,69 дюйма — размеры из плоского листа 210 на 297 миллиметров. Поля по умолчанию — около 0,4 дюйма с каждой стороны, примерно сантиметр; они настраиваются четырьмя отдельными опциями WithMarginTop, WithMarginBottom, WithMarginLeft и WithMarginRight. Масштаб WithScale сжимает или растягивает содержимое относительно единицы: 0,5 ужимает вдвое, 1,2 растягивает. Опция WithPageRanges печатает не весь документ, а диапазоны в формате 1-5, 8, 11-13; номера, попавшие за конец документа, тихо отбрасываются, а диапазон, в котором начало больше конца, даёт ошибку — это удобно для выборочных выгрузок вроде одной страницы договора. WithPreferCSSPageSize передаёт управление размером листа самой странице: если в шаблоне есть правило уровня страницы со своим размером, браузер возьмёт его. Наконец, WithLandscape кладёт лист набок — для широких таблиц позиций это часто единственный способ уместить колонки. Для очень больших документов есть и режим передачи стримом: WithTransferMode просит браузер отдавать документ потоком, и тогда второй результат Do — идентификатор потока — перестаёт быть лишним, читать его нужно по частям. Все опции независимы: можно печатать альбомный A4 с нулевыми полями и диапазоном страниц одной цепочкой.
Параметры page.PrintToPDF#
| Опция | По умолчанию | Когда менять |
|---|---|---|
| WithPaperWidth / WithPaperHeight | 8,5 и 11 дюймов — Letter | A4 = 8,27 и 11,69; размеры всегда в дюймах |
| WithMarginTop и три соседних | около 0,4 дюйма (1 см) | колонтитулам нужно место: увеличивайте верх и низ |
| WithPrintBackground | false — фоны не печатаются | тёмные шапки, плашки, фирменные цвета — включать |
| WithLandscape | false — портретный лист | широкие таблицы позиций |
| WithScale | 1 | уместить крупный шаблон на лист |
| WithPageRanges | пустая строка — весь документ | выборочные страницы: 1-3, 7 |
| WithDisplayHeaderFooter | false | колонтитулы с номерами страниц и датой |
Шаблон и CSS печати#
Печать применяет к странице правила печатной медиа: работают те же стили, что и на экране, плюс отдельные правила уровня печати, которые сайт или ваш шаблон объявляет в блоке print-медиа. Из этого следуют три практических привычки. Первая — контроль разрывов: длинная карточка товара, порезанная между страницами пополам, лечится правилом запрета разрыва внутри элемента, и это вёрстка шаблона, а не параметр команды. Вторая — фон: браузер по умолчанию экономит краску и не печатает фоновые цвета и картинки, поэтому тёмная шапка счёта уезжает в PDF белой, пока не включён WithPrintBackground — тема отдельного блока ниже. Третья — шрифты: печать собирает текст из тех же шрифтов, что и экран, поэтому на сервере без кириллических гарнитур русский текст в документе превратится в серые квадраты — тот же брак, что у скриншотов в контейнере, и лечится он так же, пакетами шрифтов в образе, о чём говорит урок про Docker и серверное окружение. Если шаблон ваш, тестируйте его печатью на черновом листе: правило экрана «посмотрел в браузере — работает» для печати не аргумент: окончательный ответ даёт только сам PDF-файл, открытый глазами.
Классика первого прогона: на витрине счёт выглядит идеально — тёмная шапка, серые строки таблицы, фирменная плашка итога. PDF приходит весь белый, кроме текста. Сломалась не печать, а предположение, что вид на экране равен виду на бумаге: сессия печати по умолчанию не рисует фоны, параметр WithPrintBackground false стоит у команды незаметно для глаза. Лечение одной опцией, но привычка дороже: первый документ каждой новой вёрстки открывать глазами и сравнивать с экраном. Условие, при котором фон всё равно не появится, одно: в шаблоне фон задан не цветом, а внешней картинкой, которая не доехала до момента печати — тогда сперва чинят ожидание загрузки.
Колонтитулы: headerTemplate и footerTemplate#
Верхний и нижний колонтитулы — то, что повторяется на каждом листе: дата счёта, номер страницы, адрес документа. Включаются парой WithDisplayHeaderFooter(true) и настраиваются HTML-шаблонами WithHeaderTemplate и WithFooterTemplate — это обычная разметка с инлайн-стилями, в которую браузер подставляет значения через специальные классы: date — дата печати, title — заголовок документа, url — адрес страницы, pageNumber — текущий лист, totalPages — всего листов. Шаблон не видит ни ваших стилей страницы, ни внешних файлов: только инлайн-CSS, поэтому размеры и отступы задаются прямо в нём, и задать их нужно — без явного размера шрифта колонтитул печатается микроскопическим. Под шаблоны нужно и место: увеличьте верхнее и нижнее поля, иначе колонтитул ляжет поверх содержимого. Важная связка: показывать колонтитулы и убрать их из шаблона можно независимо — с выключенным отображением шаблоны просто не рендерятся. Для счетов рабочая связка — дата в шапке, номер листа из общего числа в подвале: получатель видит, сколько страниц в документе, не листая печать.
Колонтитул с номерами страниц
GObuf, _, err = page.PrintToPDF().
WithDisplayHeaderFooter(true).
WithHeaderTemplate(`<div style="font-size:8px; width:100%;
padding:0 12mm;">Счёт 1042 от <span class="date"></span></div>`).
WithFooterTemplate(`<div style="font-size:8px; width:100%;
text-align:center;">Стр. <span class="pageNumber"></span>
из <span class="totalPages"></span></div>`).
WithMarginTop(0.6).
WithMarginBottom(0.6).
Do(ctx)Шаблоны — обычный HTML с инлайн-стилями: браузер подставит дату печати вместо span с классом date и номера листов вместо pageNumber и totalPages. Явный размер шрифта обязателен, без него колонтитул печатается мелким. Поля увеличены, чтобы верхнему и нижнему колонтитулам было где жить вне содержимого документа.
Скриншот или PDF: выбор артефакта#
Седьмой урок оставил вас с тремя командами съёмки — скриншотами страниц и элементов, и возникает законный вопрос, когда брать что. Скриншот — это экран: пиксель-в-пиксель то, что видел пользователь, один кадр фиксированной высоты окна или всей страницы. PDF — это бумага: документ режется на листы сам, по своим правилам разрывов, и читается вне браузера — в просмотрщике, в печати, в бухгалтерской системе. Отсюда простое распределение: подтверждение для алерта и сравнение вёрстки — скриншот; счёт, акт, отчёт на несколько страниц, накладная — PDF. Отдельный случай — документы для печати на бумаге: PDF уходит в печать с точными размерами листа и полями, а скриншот при печати режется на куски, потому что его пиксели не знают про лист. У артефактов много общего, и это тоже стоит видеть: оба — байты, которые пишутся на диск одним вызовом; оба снимают текущее состояние рендера и ничего не дожидаются сами — если снять до готовности, в документ попадёт спиннер, поэтому перед печатью стоят те же WaitVisible, а сложные условия готовности решает урок про умные ожидания; оба зависят от шрифтов и размера окна. И тот же критерий качества: как и кадр, документ после первого прогона открывают глазами — брак печати узнаётся быстрее, чем в числах.
Чек-лист документа, который не придётся перепечатывать#
- дождаться рендера: WaitVisible на смысловом узле перед печатью, как перед скриншотом
- задать лист и поля явно: A4 — это 8,27 на 11,69 дюйма, а не дефолтный Letter
- включить WithPrintBackground, если в шаблоне есть фоновые цвета и плашки
- проверить кириллицу в серверном окружении: шрифты должны жить в образе
- колонтитулам — место: увеличенные поля и инлайн-стили в шаблоне
- имя файла из данных: номер счёта и дата в имени, а не page.pdf
- первый документ новой вёрстки открыть глазами до отправки получателю
Что дальше: печать закрывает тему артефактов рендера, и дальше курс возвращается к данным — девятый урок про умные ожидания научит ждать условия, а не узлы, десятый — исполнять JavaScript и забирать значения одним обменом. Практика по этому уроку — только локально: понадобится Go, Chrome и шаблон документа, песочница сайта браузерную печать не повторяет, её интерактивы проверяют порядок вызовов и параметры. Совет для первого проекта: напечатайте свой шаблон счёта дважды — портретным и альбомным листом — и посмотрите, где разорвётся таблица; эта одна проверка даёт больше, чем три перечитанных параметра.
Проверка: результат печати
Что именно вернёт page.PrintToPDF().Do(ctx)?
Проверка: колонтитулы
Вы задали WithFooterTemplate, но подвал не появился в документе. Что забыто?
Что окажется в файле
Прочитайте код и выберите, что он выведет:
// buf, _, err = page.PrintToPDF().
// WithLandscape(true).
// Do(ctx)
// os.WriteFile("out.pdf", buf, 0o644)Соберите печать счёта
Восстановите вызовы: команда печати протокола и опция, включающая фоновые цвета шаблона.
Расставьте программу печати
Соберите по порядку: буфер, Run с навигацией, ожиданием и печатью, проверка ошибки.
Строки
Ваша программа
Нажимайте на строки слева, чтобы собрать программу
Карточки: печать в PDF
Перед отправкой получателю стоит дешёвая проверка в самом коде: буфер не пуст, начинается с подписи формата PDF и весит правдоподобно — пустой или оборванный файл ловится двумя строками, а не звонком бухгалтера. Такая проверка не отменяет взгляд на первый документ, но ловит пустые и оборванные файлы до отправки. Имя файла собирайте из данных — номер счёта и дата; в архиве за месяц имена вида out.pdf затирают друг друга так же, как у скриншотов.
Песочница урока
Редактируйте код и запускайте его прямо здесь — интерпретатор работает в браузере, ничего устанавливать не нужно. За каждый запуск — +2 XP.
Запустите код, чтобы увидеть вывод.
Коротко: что запомнить
- Обёртки chromedp.PrintToPDF нет: печать — команда протокола page.PrintToPDF из cdproto/page, оборачивается в ActionFunc внутри Run.
- Do(ctx) возвращает готовые байты документа, идентификатор потока и ошибку — base64 декодируется за вас.
- Размеры листа и поля — в дюймах: A4 это 8,27 на 11,69, дефолт команды — Letter.
- WithPrintBackground по умолчанию false: фоновые цвета и плашки без него в документ не попадают.
- Колонтитулы — HTML-шаблоны с классами date, title, url, pageNumber, totalPages и обязательным WithDisplayHeaderFooter(true).
- Печать идёт по правилам печатной медиа: разрывы и фон — вёрстка шаблона, кириллица — шрифты в образе.
- Скриншот — экран и один кадр, PDF — бумага и листы; оба артефакта ждут рендера и открываются глазами после первого прогона.
Домашнее задание
Возьмите HTML-шаблон счёта (или соберите простой: шапка, таблица позиций, итог) и напечатайте его трижды: портретным Letter по умолчанию, A4 и альбомным A4. Включите фоновые цвета и добавьте колонтитул с номерами страниц через pageNumber и totalPages. Сравните три файла: где порезалась таблица, где потерялся фон, хватило ли полей. Затем уберите WaitVisible перед печатью и посмотрите, что попадёт в документ, — и верните ожидание на место.
Личные заметкиПусто
Вопросы и ответы по уроку
Почему в PDF нет фоновых цветов, хотя на экране они есть?
Сессия печати по умолчанию не рисует фон — так браузер экономит краску на реальных принтерах. Включите WithPrintBackground(true) в цепочке параметров, и плашки, шапки и полосы вернутся. Если фона всё равно нет, проверьте, что он задан цветом или заливкой, а не внешней картинкой, которая не успела загрузиться к моменту печати.
Как задать размер A4 и какие единицы у параметров печати?
Все размеры — в дюймах: WithPaperWidth(8.27) и WithPaperHeight(11.69) дают лист A4, поля — четыре опции WithMargin с теми же единицами, масштаб WithScale — множитель. Дефолт команды — американский Letter 8,5 на 11, поэтому без явных размеров европейский документ печатается на чужом листе. Если размер уже объявлен в CSS шаблона, есть WithPreferCSSPageSize.
Куда делись номера страниц и как добавить их в подвал?
Включите WithDisplayHeaderFooter(true) и передайте WithFooterTemplate — HTML с инлайн-стилями, в котором номер листа подставляется в span с классом pageNumber, а общее число — в totalPages. Задайте явный размер шрифта: без него шаблон печатается мелким. Под колонтитул нужно место — увеличьте верхнее и нижнее поля, иначе он ляжет на содержимое.
Термины урока
Ключевые понятия из этого урока — короткие определения в глоссарии. Наведите на термин, чтобы вспомнить, откройте карточку для подробностей и ссылки на практику.
Изучайте дальше
Следующая ступень учебного пути
Testcontainers в Go: интеграционные тесты с Docker
Курс testcontainers-go: Postgres, Redis, Kafka и Mongo в тестах через Docker, wait-стратегии, сети, параллельность, CI и итоговый стенд. 25 уроков.
- Поднимать реальные зависимости в Docker из кода теста
- Использовать модули Postgres, Redis, MongoDB, Kafka и RabbitMQ
- Ждать готовность сервиса wait-стратегиями вместо sleep
- Собирать стенды из нескольких контейнеров и своего образа