Skip to content

feat: export docs to pdf

Artur Sabirov requested to merge asabirov/export_docs_to_pdf into main

Описание

Необходимо экспортировать контент сайта документации в PDF

Решение — плагин mkdocs-with-pdf для MkDocs

Результат

>>> document-fixed.pdf

Артефакты устранены. Проблему с битыми svg-изображениями решил заменой на png-изображения

>>> document.pdf

Генерируется при выполнении команды mkdocs build

Замечания:

  • проблемы с svg-изображениями и отсутствие части текста в первом разделе
  • крупные EBNF-диаграммы некорректно масштабируются
  • весит 20.6 МБ
>>> document_from_html.pdf

Напечатан в PDF из файла for_pdf_print.html, который получен при выполнении команды mkdocs build > for_pdf_print.html

Замечания:

  • проблемы в первом разделе отсутствуют
  • крупные EBNF-диаграммы некорректно масштабируются
  • весит 5.6 МБ
  • есть пустые страницы — их можно исключить при настройке печати в PDF
  • слишком большие отступы до и после svg-изображений
  • в файле for_pdf_print.html проще контролировать стили

Заметки

1. Запуск `mkdocs-with-pdf`
Запуск mkdocs-with-pdf

При запуске плагина mkdocs-with-pdf возникает ошибка типа AttributeError. Для обхода ошибки я исправляю код ./weasyprint/pdf/fonts.py:

        else:
            # Store width and Unicode map for all glyphs
            font_widths, cmap = {}, {}
+           try:
                for letter, key in font.ttfont.getBestCmap().items():
                    glyph = font.ttfont.getGlyphID(key)
                    if glyph not in cmap:
                        cmap[glyph] = chr(letter)
                    width = font.ttfont.getGlyphSet()[key].width
                    font_widths[glyph] = width * 1000 / font.upem
+           except:
+               continue

        max_x = max(font_widths.values()) if font_widths else 0
        bbox = (0, font.descent, max_x, font.ascent)
Контекст ошибки `mkdocs-with-pdf`
Traceback (most recent call last):
  File "/home/asabirov/.local/share/virtualenvs/docs-bOyF4WXG/bin/mkdocs", line 8, in <module>
    sys.exit(cli())
             ^^^^^
  File "/home/asabirov/.local/share/virtualenvs/docs-bOyF4WXG/lib/python3.12/site-packages/click/core.py", line 1157, in __call__
    return self.main(*args, **kwargs)
           ^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/home/asabirov/.local/share/virtualenvs/docs-bOyF4WXG/lib/python3.12/site-packages/click/core.py", line 1078, in main
    rv = self.invoke(ctx)
         ^^^^^^^^^^^^^^^^
  File "/home/asabirov/.local/share/virtualenvs/docs-bOyF4WXG/lib/python3.12/site-packages/click/core.py", line 1688, in invoke
    return _process_result(sub_ctx.command.invoke(sub_ctx))
                           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/home/asabirov/.local/share/virtualenvs/docs-bOyF4WXG/lib/python3.12/site-packages/click/core.py", line 1434, in invoke
    return ctx.invoke(self.callback, **ctx.params)
           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/home/asabirov/.local/share/virtualenvs/docs-bOyF4WXG/lib/python3.12/site-packages/click/core.py", line 783, in invoke
    return __callback(*args, **kwargs)
           ^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/home/asabirov/.local/share/virtualenvs/docs-bOyF4WXG/lib/python3.12/site-packages/mkdocs/__main__.py", line 286, in build_command
    build.build(cfg, dirty=not clean)
  File "/home/asabirov/.local/share/virtualenvs/docs-bOyF4WXG/lib/python3.12/site-packages/mkdocs/commands/build.py", line 354, in build
    config.plugins.on_post_build(config=config)
  File "/home/asabirov/.local/share/virtualenvs/docs-bOyF4WXG/lib/python3.12/site-packages/mkdocs/plugins.py", line 542, in on_post_build
    return self.run_event('post_build', config=config)
           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/home/asabirov/.local/share/virtualenvs/docs-bOyF4WXG/lib/python3.12/site-packages/mkdocs/plugins.py", line 509, in run_event
    result = method(**kwargs)
             ^^^^^^^^^^^^^^^^
  File "/home/asabirov/.local/share/virtualenvs/docs-bOyF4WXG/lib/python3.12/site-packages/mkdocs_with_pdf/plugin.py", line 135, in on_post_build
    self.generator.on_post_build(config, self.config['output_path'])
  File "/home/asabirov/.local/share/virtualenvs/docs-bOyF4WXG/lib/python3.12/site-packages/mkdocs_with_pdf/generator.py", line 157, in on_post_build
    render.write_pdf(abs_pdf_path)
  File "/home/asabirov/.local/share/virtualenvs/docs-bOyF4WXG/lib/python3.12/site-packages/weasyprint/document.py", line 390, in write_pdf
    pdf = generate_pdf(self, target, zoom, **options)
          ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/home/asabirov/.local/share/virtualenvs/docs-bOyF4WXG/lib/python3.12/site-packages/weasyprint/pdf/__init__.py", line 273, in generate_pdf
    pdf_fonts = build_fonts_dictionary(
                ^^^^^^^^^^^^^^^^^^^^^^^
  File "/home/asabirov/.local/share/virtualenvs/docs-bOyF4WXG/lib/python3.12/site-packages/weasyprint/pdf/fonts.py", line 47, in build_fonts_dictionary
    for letter, key in font.ttfont.getBestCmap().items():
                       ^^^^^^^^^^^^^^^^^^^^^^^
AttributeError: 'NoneType' object has no attribute 'getBestCmap'
2. Артефакты в pdf-файле
Артефакты в pdf-файле

В сгенерированном pdf-файле присутствуют артефакты рендеринга:

  1. В первом разделе:
    • большинство svg-изображений побились или отсутствуют
    • отсутствуют значительные куски текста
  2. Крупные EBNF-диаграммы некорректно масштабируются
  3. В выносках:
    • иконки типов выносок перекрыли заголовки
    • некоторые заголовки прилипли к левому краю
  4. Заголовки h4–h6 расположились левее заголовков h1–h3

Некоторые из этих артефактов устранены с помощью добавлений в файл docs/extra/style.css


Edited by Artur Sabirov

Merge request reports