---
title: "Печать в PDF: счета и отчёты"
description: "chromedp pdf: page.PrintToPDF печатает HTML-шаблон в документ — лист, поля, фоны, колонтитулы, кириллица. Счёт и отчёт из html в pdf go-кодом."
course: "Chromedp в Go: автоматизация Chrome без Selenium"
url: "https://golangmaster.ru/courses/chromedp/lessons/08-pdf-dokumenty/"
published: "2026-09-19"
updated: "2026-09-21"
minutes: 18
---

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

*Курс «Chromedp в Go: автоматизация Chrome без Selenium», урок 8 из 25. ~18 минут. Обновлено 21.09.2026. Источник: https://golangmaster.ru/courses/chromedp/lessons/08-pdf-dokumenty/*

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

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

```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 / 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 и серверное окружение](/courses/chromedp/lessons/23-docker-server/). Если шаблон ваш, тестируйте его печатью на черновом листе: правило экрана «посмотрел в браузере — работает» для печати не аргумент: окончательный ответ даёт только сам PDF-файл, открытый глазами.

> **Живое место: счёт уехал белым.** Классика первого прогона: на витрине счёт выглядит идеально — тёмная шапка, серые строки таблицы, фирменная плашка итога. PDF приходит весь белый, кроме текста. Сломалась не печать, а предположение, что вид на экране равен виду на бумаге: сессия печати по умолчанию не рисует фоны, параметр WithPrintBackground false стоит у команды незаметно для глаза. Лечение одной опцией, но привычка дороже: первый документ каждой новой вёрстки открывать глазами и сравнивать с экраном. Условие, при котором фон всё равно не появится, одно: в шаблоне фон задан не цветом, а внешней картинкой, которая не доехала до момента печати — тогда сперва чинят ожидание загрузки.

### Колонтитулы: 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: выбор артефакта

Седьмой урок оставил вас с тремя командами съёмки — [скриншотами страниц и элементов](/courses/chromedp/lessons/07-skrisnty/), и возникает законный вопрос, когда брать что. Скриншот — это экран: пиксель-в-пиксель то, что видел пользователь, один кадр фиксированной высоты окна или всей страницы. PDF — это бумага: документ режется на листы сам, по своим правилам разрывов, и читается вне браузера — в просмотрщике, в печати, в бухгалтерской системе. Отсюда простое распределение: подтверждение для алерта и сравнение вёрстки — скриншот; счёт, акт, отчёт на несколько страниц, накладная — PDF. Отдельный случай — документы для печати на бумаге: PDF уходит в печать с точными размерами листа и полями, а скриншот при печати режется на куски, потому что его пиксели не знают про лист. У артефактов много общего, и это тоже стоит видеть: оба — байты, которые пишутся на диск одним вызовом; оба снимают текущее состояние рендера и ничего не дожидаются сами — если снять до готовности, в документ попадёт спиннер, поэтому перед печатью стоят те же WaitVisible, а сложные условия готовности решает урок про [умные ожидания](/courses/chromedp/lessons/09-ozhidaniya-elementov/); оба зависят от шрифтов и размера окна. И тот же критерий качества: как и кадр, документ после первого прогона открывают глазами — брак печати узнаётся быстрее, чем в числах.

### Чек-лист документа, который не придётся перепечатывать

- дождаться рендера: WaitVisible на смысловом узле перед печатью, как перед скриншотом
- задать лист и поля явно: A4 — это 8,27 на 11,69 дюйма, а не дефолтный Letter
- включить WithPrintBackground, если в шаблоне есть фоновые цвета и плашки
- проверить кириллицу в серверном окружении: шрифты должны жить в образе
- колонтитулам — место: увеличенные поля и инлайн-стили в шаблоне
- имя файла из данных: номер счёта и дата в имени, а не page.pdf
- первый документ новой вёрстки открыть глазами до отправки получателю

Что дальше: печать закрывает тему артефактов рендера, и дальше курс возвращается к данным — девятый урок про [умные ожидания](/courses/chromedp/lessons/09-ozhidaniya-elementov/) научит ждать условия, а не узлы, десятый — исполнять JavaScript и забирать значения одним обменом. Практика по этому уроку — только локально: понадобится Go, Chrome и шаблон документа, песочница сайта браузерную печать не повторяет, её интерактивы проверяют порядок вызовов и параметры. Совет для первого проекта: напечатайте свой шаблон счёта дважды — портретным и альбомным листом — и посмотрите, где разорвётся таблица; эта одна проверка даёт больше, чем три перечитанных параметра.

### Проверка: результат печати

**Вопрос:** Что именно вернёт page.PrintToPDF().Do(ctx)?

- Строку base64, которую нужно декодировать самому
- ✅ Готовые байты PDF, идентификатор потока и ошибку
- Имя файла на диске браузера
- HTML-разметку напечатанной страницы

Do возвращает три значения: срез байтов документа, уже декодированный из base64, идентификатор потока и ошибку. Поэтому в примерах пишут buf, _, err — поток нужен только при передаче результата стримом, а файл пишется одним os.WriteFile.

### Проверка: колонтитулы

**Вопрос:** Вы задали WithFooterTemplate, но подвал не появился в документе. Что забыто?

- WithPageRanges со списком страниц
- ✅ WithDisplayHeaderFooter(true) — без него шаблоны не рендерятся
- Отдельная навигация на страницу с колонтитулом
- Ничего: шаблоны работают только в альбомной ориентации

Шаблоны шапки и подвала включаются флагом WithDisplayHeaderFooter: без него печать идёт без колонтитулов, и шаблоны просто не читаются. Внутри шаблона доступны классы date, title, url, pageNumber и totalPages, а под текст нужно место — увеличенные поля.

### Что окажется в файле

```go
// buf, _, err = page.PrintToPDF().
//         WithLandscape(true).
//         Do(ctx)
// os.WriteFile("out.pdf", buf, 0o644)
```

**Что выведет программа?**

- Файл с текстом base64 — придётся декодировать
- ✅ Корректный PDF: Do уже вернул декодированные байты
- Ошибка: ориентацию задаёт CSS, а не параметр
- Пустой файл: буфер заполняет отдельное действие

Do декодирует base64 сам и отдаёт готовые байты документа — файл открывается любым просмотрщиком. Ориентация листа — честный параметр WithLandscape команды печати, а не вопрос CSS, а буфер заполняется тем же действием внутри Run.

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

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

```go
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
        }),
)
```

Подсказка: Команда живёт в пакете page, а фоны печатает опция PrintBackground — обе части имени совпадают с тем, что они делают.

Правильные ответы в пропусках: `PrintToPDF`, `PrintBackground`

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

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

Правильный порядок строк:

1. `var buf []byte`
2. `err := chromedp.Run(ctx,`
3. `  chromedp.Navigate(url),`
4. `  chromedp.WaitVisible("#invoice"),`
5. `  chromedp.ActionFunc(func(ctx context.Context) error {`
6. `    buf, _, err = page.PrintToPDF().WithPrintBackground(true).Do(ctx)`
7. `    return err`
8. `  }),`
9. `)`
10. `if err != nil {`
11. `  panic(err)`
12. `}`

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

- **page.PrintToPDF()** — Команда протокола Page.printToPDF из cdproto/page: возвращает конструктор параметров, Do(ctx) печатает текущий документ вкладки.
- **Do(ctx) у печати** — Возвращает декодированные байты PDF, идентификатор потока и ошибку. Файл пишется на диск одним os.WriteFile без конвертаций.
- **WithPrintBackground** — Включает печать фоновых цветов и картинок. По умолчанию false — тёмные шапки уезжают в PDF белыми.
- **Размер листа** — Дюймы: дефолт Letter 8,5 на 11; A4 — 8,27 на 11,69. Поля — четыре отдельные опции, дефолт около 0,4 дюйма.
- **Классы колонтитулов** — date, title, url, pageNumber, totalPages — подставляются в HTML-шаблоны шапки и подвала при включённом WithDisplayHeaderFooter.
- **PDF или скриншот** — Скриншот — экран и один кадр; PDF — бумага, разрывы листов и многостраничность. Оба ждут рендера и зависят от шрифтов.

> **Проверяйте документ программно.** Перед отправкой получателю стоит дешёвая проверка в самом коде: буфер не пуст, начинается с подписи формата PDF и весит правдоподобно — пустой или оборванный файл ловится двумя строками, а не звонком бухгалтера. Такая проверка не отменяет взгляд на первый документ, но ловит пустые и оборванные файлы до отправки. Имя файла собирайте из данных — номер счёта и дата; в архиве за месяц имена вида out.pdf затирают друг друга так же, как у скриншотов.

## Коротко: что запомнить

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