Перейти к содержимому
ГоМастер

Поиск по урокам

Найдите урок, курс, термин или раздел

Урок 8 из 25 ~18 мин +50 XP

Печать в PDF: счета и отчёты

Бухгалтерия просит счета в PDF, а у вас готовый HTML-шаблон — это работа для chromedp pdf: команда page.PrintToPDF печатает отрисованную страницу в документ силами самого браузера. Разберём превращение html в pdf go-кодом: обёртка над протоколом, размер листа и поля, фоновые цвета, колонтитулы и место PDF рядом со скриншотом.

Содержание урока · 10 разделов

Счёт на оплату — документ, который живёт в двух мирах: витрина показывает его как 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 по умолчанию, поэтому из коробки всё работает без дополнительных флагов; если включить окно ради отладки, шаг печати придётся пропускать.

Счёт из шаблона: полная программа

GO
package 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 / WithPaperHeight8,5 и 11 дюймов — LetterA4 = 8,27 и 11,69; размеры всегда в дюймах
WithMarginTop и три соседнихоколо 0,4 дюйма (1 см)колонтитулам нужно место: увеличивайте верх и низ
WithPrintBackgroundfalse — фоны не печатаютсятёмные шапки, плашки, фирменные цвета — включать
WithLandscapefalse — портретный листширокие таблицы позиций
WithScale1уместить крупный шаблон на лист
WithPageRangesпустая строка — весь документвыборочные страницы: 1-3, 7
WithDisplayHeaderFooterfalseколонтитулы с номерами страниц и датой

Шаблон и CSS печати#

Печать применяет к странице правила печатной медиа: работают те же стили, что и на экране, плюс отдельные правила уровня печати, которые сайт или ваш шаблон объявляет в блоке print-медиа. Из этого следуют три практических привычки. Первая — контроль разрывов: длинная карточка товара, порезанная между страницами пополам, лечится правилом запрета разрыва внутри элемента, и это вёрстка шаблона, а не параметр команды. Вторая — фон: браузер по умолчанию экономит краску и не печатает фоновые цвета и картинки, поэтому тёмная шапка счёта уезжает в PDF белой, пока не включён WithPrintBackground — тема отдельного блока ниже. Третья — шрифты: печать собирает текст из тех же шрифтов, что и экран, поэтому на сервере без кириллических гарнитур русский текст в документе превратится в серые квадраты — тот же брак, что у скриншотов в контейнере, и лечится он так же, пакетами шрифтов в образе, о чём говорит урок про Docker и серверное окружение. Если шаблон ваш, тестируйте его печатью на черновом листе: правило экрана «посмотрел в браузере — работает» для печати не аргумент: окончательный ответ даёт только сам PDF-файл, открытый глазами.

Колонтитулы: headerTemplate и footerTemplate#

Верхний и нижний колонтитулы — то, что повторяется на каждом листе: дата счёта, номер страницы, адрес документа. Включаются парой WithDisplayHeaderFooter(true) и настраиваются HTML-шаблонами WithHeaderTemplate и WithFooterTemplate — это обычная разметка с инлайн-стилями, в которую браузер подставляет значения через специальные классы: date — дата печати, title — заголовок документа, url — адрес страницы, pageNumber — текущий лист, totalPages — всего листов. Шаблон не видит ни ваших стилей страницы, ни внешних файлов: только инлайн-CSS, поэтому размеры и отступы задаются прямо в нём, и задать их нужно — без явного размера шрифта колонтитул печатается микроскопическим. Под шаблоны нужно и место: увеличьте верхнее и нижнее поля, иначе колонтитул ляжет поверх содержимого. Важная связка: показывать колонтитулы и убрать их из шаблона можно независимо — с выключенным отображением шаблоны просто не рендерятся. Для счетов рабочая связка — дата в шапке, номер листа из общего числа в подвале: получатель видит, сколько страниц в документе, не листая печать.

Колонтитул с номерами страниц

GO
buf, _, 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)

Соберите печать счёта

Восстановите вызовы: команда печати протокола и опция, включающая фоновые цвета шаблона.

var buf []byte err := chromedp.Run(ctx, chromedp.WaitVisible("#invoice"), chromedp.ActionFunc(func(ctx context.Context) error { var err error buf, _, err = page.(). With(true). Do(ctx) return err }), )

Расставьте программу печати

Соберите по порядку: буфер, Run с навигацией, ожиданием и печатью, проверка ошибки.

Строки

Ваша программа

Нажимайте на строки слева, чтобы собрать программу

Карточки: печать в PDF

Термин

page.PrintToPDF()

нажмите, чтобы перевернуть
Определение

Команда протокола Page.printToPDF из cdproto/page: возвращает конструктор параметров, Do(ctx) печатает текущий документ вкладки.

1 / 6

Песочница урока

Редактируйте код и запускайте его прямо здесь — интерпретатор работает в браузере, ничего устанавливать не нужно. За каждый запуск — +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 перед печатью и посмотрите, что попадёт в документ, — и верните ожидание на место.

Личные заметкиПусто
Хранятся только в вашем браузере (Chromedp · Печать в PDF: счета и отчёты)

Вопросы и ответы по уроку

Почему в 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. Задайте явный размер шрифта: без него шаблон печатается мелким. Под колонтитул нужно место — увеличьте верхнее и нижнее поля, иначе он ляжет на содержимое.

Термины урока

Ключевые понятия из этого урока — короткие определения в глоссарии. Наведите на термин, чтобы вспомнить, откройте карточку для подробностей и ссылки на практику.

Следующая ступень учебного пути

Testcontainers25 уроков~8 чпрактика25 уроков уже доступны

Testcontainers в Go: интеграционные тесты с Docker

Курс testcontainers-go: Postgres, Redis, Kafka и Mongo в тестах через Docker, wait-стратегии, сети, параллельность, CI и итоговый стенд. 25 уроков.

  • Поднимать реальные зависимости в Docker из кода теста
  • Использовать модули Postgres, Redis, MongoDB, Kafka и RabbitMQ
  • Ждать готовность сервиса wait-стратегиями вместо sleep
  • Собирать стенды из нескольких контейнеров и своего образа