Создание и настройка разделов документа в Python для работы со страницами
Работа со страницами документа Word с использованием python-docx
Библиотека python-docx позволяет программно управлять структурой документа, включая разбивку на страницы, разделы, поля и нумерацию. Основной механизм для настройки страниц - это разделы (sections). Каждый раздел может иметь собственные параметры: ориентацию, размеры, поля, колонтитулы и начальный номер страницы. Разрывы страниц (page breaks) используются для принудительного перехода на новую страницу внутри раздела.
Основное решение: работа с разделами и разрывами
Лучший способ управлять макетом страниц - создавать новые разделы с помощью document.add_section() и настраивать их свойства. Для принудительного разрыва страницы используется document.add_page_break() или добавление разрыва в ран.
from docx import Document
from docx.enum.section import WD_ORIENT
from docx.shared import Cm, Pt
doc = Document()
# Настройка первого раздела (по умолчанию)
section = doc.sections[0]
section.orientation = WD_ORIENT.PORTRAIT
section.page_width = Cm(21)
section.page_height = Cm(29.7)
section.margins.left = Cm(2.5)
section.margins.right = Cm(2.5)
section.margins.top = Cm(2)
section.margins.bottom = Cm(2)
# Добавление содержимого
doc.add_paragraph('Первый раздел (портретный)')
# Принудительный разрыв страницы
doc.add_page_break()
doc.add_paragraph('Эта строка на новой странице, но в том же разделе')
# Создание нового раздела с альбомной ориентацией
new_section = doc.add_section(WD_ORIENT.LANDSCAPE)
new_section.page_width = Cm(29.7)
new_section.page_height = Cm(21)
doc.add_paragraph('Второй раздел (альбомный)')
doc.save('sections_example.docx')
Python docx страница (работа со страницами в docx)
Это решение покрывает большинство задач по управлению страницами: разные ориентации, поля, размеры листов.
Как добавить разрыв страницы в произвольном месте?
Простой способ - вызов document.add_page_break() вставляет разрыв после текущего абзаца. Альтернативно можно использовать run.add_break(docx.enum.text.WD_BREAK.PAGE) для вставки разрыва внутри абзаца.
from docx import Document
from docx.enum.text import WD_BREAK
doc = Document()
p = doc.add_paragraph('Текст до разрыва')
run = p.add_run()
run.add_break(WD_BREAK.PAGE)
p.add_run('Текст после разрыва на новой странице')
doc.save('break_in_run.docx')
Типичная ошибка: забыть установить курсор в нужное место. При вызове add_page_break() разрыв вставляется после текущего текста. Для вставки перед содержимым следует сначала добавить пустой абзац или переместить курсор.
Как создать новый раздел с отличными от предыдущего параметрами?
Используйте document.add_section(start_type), где start_type определяет, с чего начинается раздел (новая страница, новый столбец и т.д.). По умолчанию - WD_SECTION_START.NEW_PAGE.
from docx import Document
from docx.enum.section import WD_ORIENT, WD_SECTION_START
from docx.shared import Cm
doc = Document()
doc.add_paragraph('Первый раздел по умолчанию')
new_section = doc.add_section(WD_SECTION_START.NEW_PAGE)
new_section.orientation = WD_ORIENT.LANDSCAPE
new_section.margins.top = Cm(1)
new_section.margins.bottom = Cm(1)
doc.add_paragraph('Второй раздел с альбомной ориентацией и узкими полями')
doc.save('new_section.docx')
Проблема: если не указать start_type, раздел может начаться не там, где ожидалось. Также важно помнить, что после создания раздела все его параметры сбрасываются к значениям по умолчанию, если их не переопределить.
Как настроить нумерацию страниц?
Python-docx не имеет встроенного метода для номера страницы, но можно вставить поле PAGE в колонтитул через низкоуровневый XML. Рекомендуется использовать библиотеку docx.oxml.
from docx import Document
from docx.oxml.ns import qn
doc = Document()
section = doc.sections[0]
header = section.header
paragraph = header.paragraphs[0]
run = paragraph.add_run()
# Создание элемента fldChar для поля PAGE
fld_char_begin = run._r.makeelement(qn('w:fldChar'), {qn('w:fldCharType'): 'begin'})
run._r.append(fld_char_begin)
instr = run._r.makeelement(qn('w:instrText'), {})
instr.text = ' PAGE '
run._r.append(instr)
fld_char_separate = run._r.makeelement(qn('w:fldChar'), {qn('w:fldCharType'): 'separate'})
run._r.append(fld_char_separate)
fld_char_end = run._r.makeelement(qn('w:fldChar'), {qn('w:fldCharType'): 'end'})
run._r.append(fld_char_end)
# Теперь также можно добавить номер раздела и т.д.
doc.save('page_number.docx')
Возможные ошибки: пропуск пространства имен (w:) или неправильный порядок элементов fldChar. При открытии в Word поле может не обновиться автоматически; нажмите Ctrl+A, затем F9.
Как сделать разные колонтитулы для первого листа или для разных разделов?
Используйте свойство section.different_first_page_header_footer для отдельного верхнего/нижнего колонтитула на первой странице раздела, а также section.header.is_linked_to_previous для отключения связи с предыдущим разделом.
from docx import Document
doc = Document()
section = doc.sections[0]
section.different_first_page_header_footer = True
# Заголовок для первой страницы раздела
first_header = section.first_page_header
first_header.paragraphs[0].text = 'Первый лист'
# Заголовок для остальных страниц
header = section.header
header.paragraphs[0].text = 'Последующие страницы'
# Отключаем связь с предыдущим разделом (если он есть)
section.header.is_linked_to_previous = False
doc.save('headers.docx')
Ошибка: если не сбросить is_linked_to_previous, изменения в колонтитуле раздела будут игнорироваться. Также не забывайте, что колонтитулы хранятся отдельно для каждого раздела.
Расширенные примеры работы со страницами в python-docx
Пример 1. Создание документа с несколькими разделами: разная ориентация и поля
В этом примере создаётся документ из трёх разделов. Первый - портретный с обычными полями, второй - портретный с широкими полями, третий - альбомный с узкими полями. Для каждого раздела добавляется содержимое.
from docx import Document
from docx.enum.section import WD_ORIENT, WD_SECTION_START
from docx.shared import Cm
doc = Document()
# Раздел 1: портретный, поля 2.5 см
sec1 = doc.sections[0]
sec1.orientation = WD_ORIENT.PORTRAIT
sec1.page_width = Cm(21)
sec1.page_height = Cm(29.7)
sec1.margins.left = Cm(2.5)
sec1.margins.right = Cm(2.5)
sec1.margins.top = Cm(2.5)
sec1.margins.bottom = Cm(2.5)
doc.add_paragraph('Первый раздел (портрет, поля 2.5 см)')
# Добавляем разрыв страницы внутри раздела
doc.add_page_break()
doc.add_paragraph('Ещё один абзац в первом разделе')
# Раздел 2: портретный, но поля 4 см
sec2 = doc.add_section(WD_SECTION_START.NEW_PAGE)
sec2.orientation = WD_ORIENT.PORTRAIT
sec2.page_width = Cm(21)
sec2.page_height = Cm(29.7)
sec2.margins.left = Cm(4)
sec2.margins.right = Cm(4)
sec2.margins.top = Cm(2)
sec2.margins.bottom = Cm(2)
doc.add_paragraph('Второй раздел (портрет, увеличенные боковые поля)')
# Раздел 3: альбомный, узкие поля
sec3 = doc.add_section(WD_SECTION_START.NEW_PAGE)
sec3.orientation = WD_ORIENT.LANDSCAPE
sec3.page_width = Cm(29.7)
sec3.page_height = Cm(21)
sec3.margins.left = Cm(1)
sec3.margins.right = Cm(1)
sec3.margins.top = Cm(1)
sec3.margins.bottom = Cm(1)
doc.add_paragraph('Третий раздел (альбом, поля 1 см)')
doc.save('multi_sections.docx')
Результат: файл будет содержать три раздела с заданными параметрами. При открытии в Word можно увидеть разные ориентации и поля на каждой странице-разделе.
Пример 2. Вставка номеров страниц в нижний колонтитул с разным форматом для разных разделов
Используя XML, вставляем поле PAGE в нижний колонтитул первого раздела, а во втором разделе добавляем ещё и номер раздела.
from docx import Document
from docx.oxml.ns import qn
from copy import deepcopy
def add_page_number_to_footer(section):
footer = section.footer
paragraph = footer.paragraphs[0]
run = paragraph.add_run()
# Создаём поле PAGE
fld_char_begin = run._r.makeelement(qn('w:fldChar'), {qn('w:fldCharType'): 'begin'})
run._r.append(fld_char_begin)
instr = run._r.makeelement(qn('w:instrText'), {})
instr.text = ' PAGE '
run._r.append(instr)
fld_char_separate = run._r.makeelement(qn('w:fldChar'), {qn('w:fldCharType'): 'separate'})
run._r.append(fld_char_separate)
# Можно добавить текст-заполнитель
run = paragraph.add_run(' ')
fld_char_end = run._r.makeelement(qn('w:fldChar'), {qn('w:fldCharType'): 'end'})
run._r.append(fld_char_end)
def add_section_number_to_footer(section):
footer = section.footer
paragraph = footer.paragraphs[0]
run = paragraph.add_run()
# Поле SECTION
fld_char_begin = run._r.makeelement(qn('w:fldChar'), {qn('w:fldCharType'): 'begin'})
run._r.append(fld_char_begin)
instr = run._r.makeelement(qn('w:instrText'), {})
instr.text = ' SECTION '
run._r.append(instr)
fld_char_separate = run._r.makeelement(qn('w:fldChar'), {qn('w:fldCharType'): 'separate'})
run._r.append(fld_char_separate)
run = paragraph.add_run(' ')
fld_char_end = run._r.makeelement(qn('w:fldChar'), {qn('w:fldCharType'): 'end'})
run._r.append(fld_char_end)
doc = Document()
# Добавляем два раздела
sec1 = doc.sections[0]
add_page_number_to_footer(sec1)
doc.add_paragraph('Первый раздел. Номер страницы внизу.')
sec2 = doc.add_section()
sec2.header.is_linked_to_previous = False # отключаем связь
add_page_number_to_footer(sec2)
add_section_number_to_footer(sec2) # добавим ещё и номер раздела
doc.add_paragraph('Второй раздел. Внизу номер страницы и номер раздела.')
doc.save('page_and_section_numbers.docx')
Результат: при открытии документа в Word в нижнем колонтитуле первого раздела отобразится номер страницы, во втором - номер страницы и номер раздела. Поля обновятся при загрузке или по нажатию Ctrl+F9.
Пример 3. Добавление разрыва страницы при создании списка элементов
Предположим, нужно сгенерировать отчёт, где каждый новый элемент начинается с новой страницы. Можно использовать цикл с doc.add_page_break().
from docx import Document
from docx.shared import Pt
doc = Document()
elements = [
('Отчёт 1', 'Текст первого отчёта'),
('Отчёт 2', 'Текст второго отчёта'),
('Отчёт 3', 'Текст третьего отчёта'),
]
for i, (title, text) in enumerate(elements):
if i > 0:
doc.add_page_break() # новая страница для каждого, кроме первого
doc.add_heading(title, level=1)
doc.add_paragraph(text)
doc.save('reports.docx')
Результат: документ с тремя страницами, на каждой - заголовок и абзац текста.
Пример 4. Изменение начального номера страницы в разделе
Иногда требуется начать нумерацию страниц с определённого числа. Для этого используется свойство section.page_numbering_start.
from docx import Document
doc = Document()
# Первый раздел: номера не показываем (не вставляли поле), но можно задать старт
sec1 = doc.sections[0]
sec1.page_numbering_start = 5 # начать нумерацию с 5
# Второй раздел: сбросить нумерацию
sec2 = doc.add_section()
sec2.page_numbering_start = 1 # начать заново с 1
doc.add_paragraph('Первый раздел (номера с 5)')
doc.add_paragraph('Текст')
doc.add_page_break()
doc.add_paragraph('Второй раздел (номера с 1)')
doc.save('page_start.docx')
Результат: Если вставить поля PAGE в колонтитулы, то в первом разделе страницы будут нумероваться с 5, во втором - с 1.