Skip to content

Commit

Permalink
feat: overall fixes and enhancements
Browse files Browse the repository at this point in the history
- Fixes #27
- Fixes #25
- Fixes #26
  • Loading branch information
adrienbrignon committed Jun 23, 2024
1 parent d926dc6 commit 01935c2
Show file tree
Hide file tree
Showing 16 changed files with 177 additions and 83 deletions.
2 changes: 1 addition & 1 deletion .github/workflows/documentation.yml
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ jobs:
- name: Checkout repository
uses: actions/checkout@v4
with:
fetch-depth: 1
fetch-depth: 0
- name: Set up Poetry
run: |
pipx install poetry
Expand Down
5 changes: 3 additions & 2 deletions Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ ifeq ($(origin .RECIPEPREFIX), undefined)
endif

PDF = true
PDF_AGGREGATOR = true
DEBUG = false

.RECIPEPREFIX = >
Expand All @@ -17,10 +18,10 @@ browser:
> poetry run playwright install $(if $(FORCE),--force,) --with-deps

build:
>@ MKDOCS_EXPORTER_PDF=$(PDF) poetry run mkdocs build
>@ MKDOCS_EXPORTER_PDF=$(PDF) MKDOCS_EXPORTER_PDF_AGGREGATOR=$(PDF_AGGREGATOR) poetry run mkdocs build

serve:
>@ MKDOCS_EXPORTER_PDF=$(PDF) poetry run mkdocs serve
>@ MKDOCS_EXPORTER_PDF=$(PDF) MKDOCS_EXPORTER_PDF_AGGREGATOR=$(PDF_AGGREGATOR) poetry run mkdocs serve

clean:
> $(RM) -rf dist/
Expand Down
14 changes: 14 additions & 0 deletions docs/configuration/generating-pdf-documents.md
Original file line number Diff line number Diff line change
Expand Up @@ -251,4 +251,18 @@ plugins:
aggregator:
enabled: true
output: documentation.pdf
covers: all
```
#### Configuring cover pages behavior
When aggregating PDF documents, you have the flexibility to configure the behavior of cover pages to suit your needs.
There are five available options for managing cover pages:
- `all` (*default*): retains every cover page from all documents.
- `none`: removes every cover page from the aggregated document, resulting in a compilation that includes only the main content of each PDF.
- `limits`: retains the front cover of the first document and the back cover of the last document, while removing all other cover pages in between.
- `front`: preserves every front cover page from all documents but removes all back cover pages.
- `back`: preserves every back cover page from all documents but removes all front cover pages.

Choose the behavior that best aligns with your document aggregation needs to ensure the final PDF meets your requirements.
4 changes: 1 addition & 3 deletions docs/getting-started.md
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,6 @@ hide:
- navigation
---

<div class="mkdocs-exporter" style="display: none;"></div>

# Getting started

## Introduction
Expand Down Expand Up @@ -39,4 +37,4 @@ plugins:
- exporter
```
Check out the [configuration guides](configuration/generating-pdf-documents) for more details about how to use and configure the plugin.
Check out the [configuration guides](../configuration/generating-pdf-documents) for more details about how to use and configure the plugin.
10 changes: 6 additions & 4 deletions mkdocs.yml
Original file line number Diff line number Diff line change
Expand Up @@ -47,7 +47,6 @@ extra:

plugins:
- privacy:
log_level: warn
- mkdocstrings:
handlers:
python:
Expand Down Expand Up @@ -76,6 +75,9 @@ plugins:
- social:
cards_layout_options:
background_color: '#EA2027'
- git-committers:
repository: adrienbrignon/mkdocs-exporter
branch: master
- exporter:
logging:
level: debug
Expand All @@ -87,13 +89,13 @@ plugins:
- resources/stylesheets/pdf.scss
covers:
front: resources/templates/covers/front.html.j2
back: resources/templates/covers/back.html.j2
browser:
debug: false
headless: true
aggregator:
enabled: true
enabled: !ENV [MKDOCS_EXPORTER_PDF_AGGREGATOR, true]
output: documentation.pdf
covers: limits
covers: front
buttons:
- title: View as PDF
icon: material-file-move-outline
Expand Down
59 changes: 51 additions & 8 deletions mkdocs_exporter/formats/pdf/aggregator.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@

from pypdf import PdfWriter

from mkdocs_exporter.page import Page
from mkdocs_exporter.formats.pdf.renderer import Renderer
from mkdocs_exporter.formats.pdf.preprocessor import Preprocessor

Expand All @@ -12,10 +13,11 @@ class Aggregator:
"""Aggregates PDF documents together."""


def __init__(self, renderer: Renderer):
def __init__(self, renderer: Renderer, config: dict = {}) -> None:
"""The constructor."""

self.total_pages = 0
self.pages = []
self.config = config
self.renderer = renderer


Expand All @@ -25,20 +27,50 @@ def open(self, path: str) -> Aggregator:
self.path = path
self.writer = PdfWriter()

return self


def set_pages(self, pages: list[Page]) -> Aggregator:
"""Sets the pages."""

def increment_total_pages(self, total_pages: int) -> Aggregator:
"""Increments the total pages count."""
self.pages = pages
covers = self.config.get('covers', [])

self.total_pages = self.total_pages + total_pages
for index, page in enumerate(self.pages):
if covers == 'none':
self._skip(page, ['front', 'back'])
elif covers == 'front':
self._skip(page, ['back'])
elif covers == 'back':
self._skip(page, ['front'])
elif covers == 'limits':
if index == 0:
self._skip(page, ['back'])
elif index == (len(self.pages) - 1):
self._skip(page, ['front'])
else:
self._skip(page, ['front', 'back'])

return self


def preprocess(self, html: str, page_number: int = 1) -> str:
def preprocess(self, page: Page) -> str:
"""Preprocesses the page."""

preprocessor = Preprocessor()

preprocessor.preprocess(html)
preprocessor.metadata({'page': page_number, 'pages': self.total_pages})
preprocessor.preprocess(self.renderer.preprocess(page, disable=['teleport']))

if 'front' not in page.formats['pdf']['covers']:
preprocessor.remove('div.mkdocs-exporter-front-cover')
if 'back' not in page.formats['pdf']['covers']:
preprocessor.remove('div.mkdocs-exporter-back-cover')

preprocessor.teleport()
preprocessor.metadata({
'page': sum(page.formats['pdf']['pages'] - page.formats['pdf']['skipped_pages'] for page in self.pages[:page.index]),
'pages': sum(page.formats['pdf']['pages'] - page.formats['pdf']['skipped_pages'] for page in self.pages),
})

return preprocessor.done()

Expand All @@ -63,3 +95,14 @@ def save(self, metadata={}) -> Aggregator:
self.writer = None

return self


def _skip(self, page: Page, covers: list[str]) -> Aggregator:
"""Skip cover pages."""

for cover in covers:
if cover in page.formats['pdf']['covers']:
page.formats['pdf']['covers'].remove(cover)
page.formats['pdf']['skipped_pages'] = page.formats['pdf']['skipped_pages'] + 1

return self
4 changes: 3 additions & 1 deletion mkdocs_exporter/formats/pdf/browser.py
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,8 @@ class Browser:
args = [
'--disable-background-timer-throttling',
'--disable-renderer-backgrounding',
'--allow-file-access-from-files'
'--allow-file-access-from-files',
'--font-render-hinting=none'
]
"""The browser's arguments..."""

Expand All @@ -38,6 +39,7 @@ def __init__(self, options: dict = {}):
self.debug = options.get('debug', False)
self.headless = options.get('headless', True)
self.timeout = options.get('timeout', 60_000)
self.args = self.args + options.get('args', [])
self.levels = {
'warn': 'warning',
'error': 'error',
Expand Down
7 changes: 7 additions & 0 deletions mkdocs_exporter/formats/pdf/config.py
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,9 @@ class BrowserConfig(BaseConfig):
timeout = c.Type(int, default=60_000)
"""The timeout when waiting for the PDF to render."""

args = c.ListOfItems(c.Type(str), default=[])
"""Extra arguments to pass to the browser."""


class CoversConfig(BaseConfig):
"""The cover's configuration."""
Expand All @@ -26,6 +29,7 @@ class CoversConfig(BaseConfig):


class AggregatorConfig(BaseConfig):
"""The aggregator's configuration."""

enabled = c.Type(bool, default=False)
"""Is the aggregator enabled?"""
Expand All @@ -36,6 +40,9 @@ class AggregatorConfig(BaseConfig):
metadata = c.Type(dict, default={})
"""Some metadata to append to the PDF document."""

covers = c.Choice(['all', 'none', 'limits', 'front', 'back'], default='all')
"""The behavior of cover pages."""


class Config(BaseConfig):
"""The plugin's configuration."""
Expand Down
41 changes: 25 additions & 16 deletions mkdocs_exporter/formats/pdf/plugin.py
Original file line number Diff line number Diff line change
Expand Up @@ -63,15 +63,20 @@ def on_page_markdown(self, markdown: str, page: Page, config: Config, **kwargs)
content = markdown
covers = {**self.config.covers, **{k: os.path.join(os.path.dirname(config['config_file_path']), v) for k, v in page.meta.get('covers', {}).items()}}

for path in [path for path in covers.values() if path is not None]:
self.watch.append(path)

if covers.get('front'):
page.formats['pdf']['covers'].append('front')

with open(covers['front'], 'r', encoding='utf-8') as file:
content = self.renderer.cover(file.read()) + content
content = self.renderer.cover(file.read(), 'front') + content

if covers.get('back'):
with open(covers['back'], 'r', encoding='utf-8') as file:
content = content + self.renderer.cover(file.read())
page.formats['pdf']['covers'].append('back')

for path in [path for path in covers.values() if path is not None]:
self.watch.append(path)
with open(covers['back'], 'r', encoding='utf-8') as file:
content = content + self.renderer.cover(file.read(), 'back')

return content

Expand All @@ -85,11 +90,10 @@ def on_pre_build(self, **kwargs) -> None:
self.loop = asyncio.new_event_loop()
self.renderer = Renderer(options=self.config)

if self.config.aggregator.get('enabled'):
self.aggregator = Aggregator(renderer=self.renderer)

asyncio.set_event_loop(self.loop)

if self.config.aggregator.get('enabled'):
self.aggregator = Aggregator(renderer=self.renderer, config=self.config.get('aggregator'))
for stylesheet in self.config.stylesheets:
self.renderer.add_stylesheet(stylesheet)
for script in self.config.scripts:
Expand All @@ -108,7 +112,10 @@ def on_pre_page(self, page: Page, config: dict, **kwargs):
fullpath = os.path.join(directory, filename)

page.formats['pdf'] = {
'pages': 0,
'covers': [],
'path': fullpath,
'skipped_pages': 0,
'url': os.path.relpath(fullpath, config['site_dir'])
}

Expand All @@ -132,9 +139,6 @@ async def render(page: Page) -> None:
with open(page.formats['pdf']['path'], 'wb+') as file:
file.write(pdf)

if self.aggregator:
self.aggregator.increment_total_pages(pages)

self.tasks.append(render(page))

return page.html
Expand All @@ -160,17 +164,19 @@ def _on_post_build_2(self, config: dict, **kwargs) -> None:
output = self.config['aggregator']['output']
self.pages = [page for page in self.pages if 'pdf' in page.formats]

logger.info("[mkdocs-exporter.pdf] Aggregating %d pages from %d documents together as '%s'...", self.aggregator.total_pages, len(self.pages), output)
self.aggregator.set_pages(self.pages)

async def render(page: Page, page_number: int) -> None:
html = self.aggregator.preprocess(self.renderer.preprocess(page), page_number=page_number)
logger.info("[mkdocs-exporter.pdf] Aggregating pages to '%s'...", output)

async def render(page: Page) -> None:
html = self.aggregator.preprocess(page)
pdf, _ = await self.renderer.render(html)

with open(page.formats['pdf']['path'] + '.aggregate', 'wb+') as file:
file.write(pdf)

for n, page in enumerate(self.pages):
self.tasks.append(render(page, page_number=sum(page.formats['pdf']['pages'] for page in self.pages[:n])))
for page in self.pages:
self.tasks.append(render(page))
while self.tasks:
self.loop.run_until_complete(asyncio.gather(*concurrently(self.tasks, max(1, self.config.concurrency or 1))))

Expand Down Expand Up @@ -217,6 +223,9 @@ def flatten(items):

self.pages = flatten(nav)

for index, page in enumerate(self.pages):
page.index = index


def _enabled(self, page: Page = None) -> bool:
"""Is the plugin enabled for this page?"""
Expand Down
10 changes: 6 additions & 4 deletions mkdocs_exporter/formats/pdf/renderer.py
Original file line number Diff line number Diff line change
Expand Up @@ -41,15 +41,15 @@ def add_script(self, path: str) -> Renderer:
return self


def cover(self, template: str) -> Renderer:
def cover(self, template: str, location: str) -> Renderer:
"""Renders a cover."""

content = template.strip('\n')

return f'<div data-decompose="true">{content}</div>' + '\n'
return f'<div class="mkdocs-exporter-{location}-cover" data-decompose="true">{content}</div>' + '\n'


def preprocess(self, page: Page) -> str:
def preprocess(self, page: Page, disable: list = []) -> str:
"""Preprocesses a page, returning HTML that can be printed."""

preprocessor = Preprocessor(theme=page.theme)
Expand All @@ -67,9 +67,11 @@ def preprocess(self, page: Page) -> str:
with open(script, 'r', encoding='utf-8') as file:
preprocessor.script(file.read(), path=stylesheet)

if 'teleport' not in disable:
preprocessor.teleport()

preprocessor.script(importlib.resources.files(js).joinpath('pdf.js').read_text(encoding='utf-8'))
preprocessor.script(importlib.resources.files(js).joinpath('pagedjs.min.js').read_text(encoding='utf-8'))
preprocessor.teleport()
preprocessor.update_links(base, root)

if self.options.get('url'):
Expand Down
1 change: 1 addition & 0 deletions mkdocs_exporter/formats/pdf/resources/js/pdf.js
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ window.PagedConfig = {
/**
* The settings.
*/
auto: true,
settings: {
maxChars: 1e32
},
Expand Down
3 changes: 3 additions & 0 deletions mkdocs_exporter/page.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,9 @@
class Page(BasePage):
"""A page."""

index: int
"""The page's index."""

html: Optional[str] = None
"""The page's HTML content."""

Expand Down
6 changes: 6 additions & 0 deletions mkdocs_exporter/resources/css/material.css
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
/**
* @see https://github.com/adrienbrignon/mkdocs-exporter/issues/26
*/
.highlight span.filename+pre {
margin-top: 0 !important;
}
Loading

0 comments on commit 01935c2

Please sign in to comment.