Initial release

This commit is contained in:
Kujiu 2020-06-19 02:21:35 +02:00
parent 441ec2418e
commit 309307f1ce
Signed by: kujiu
GPG key ID: ABBB2CAC6855599F
11 changed files with 284 additions and 130 deletions

View file

@ -5,4 +5,4 @@ Changes
0.1.0 (*2020-06-10*) 0.1.0 (*2020-06-10*)
==================== ====================
- Initial release - Initial release (no theme included...)

View file

@ -49,8 +49,20 @@ for `html` builder.
text overflows the page. text overflows the page.
You can use the sphinx_weasyprint_theme installed You can use the sphinx_weasyprint_theme installed
with this plugin to start. with this plugin to start (coming soon!).
In addition, you can set these three variables:
- weasyprint_main_selector
- weasyprint_footer_selector
- weasyprint_header_selector
This builder analyzes generated HTML to extract with
BeautifulSoup a footer and a header. They are removed
from main HTML and reinjected on each page, conforming
to theme's CSS. By default, no header and no footer.
Use compatible selectors for BeautifulSoup.
Use Use
--- ---
@ -61,36 +73,6 @@ Just launch the following:
make weasyprint make weasyprint
Signing
-------
.. warning::
You need to install `pykcs11` and `endesive` to
sign PDF.
Following parameters are needed to sign:
.. code:: python
weasyprint_sign_method = 'pkcs11'
weasyprint_sign_certid = '0x..........'
weasyprint_sign_reason = 'My Company'
weasyprint_sign_location = 'Where I am'
weasyprint_sign_contact = "Yeah, it's me"
weasyprint_sign_image = 'path_to_image'
weasyprint_sign_text = __('Signed on {sign_date}\nat {sign_location}\nby {sign_contact}\nfor {sign_reason}') #default
weasyprint_sign_position = (page, x, y) #default: None
.. note::
Signing methods are really limited. Do not
hesitate to contribute !
Available: `none` (no signing), `pkcs11`
To be implemented: `p12`, `gpg`
Why an other PDF builder for Sphinx? Why an other PDF builder for Sphinx?
------------------------------------ ------------------------------------

View file

@ -4,7 +4,6 @@ from .weasyprint_builder import WeasyPrintPDFBuilder
from typing import Dict, Any from typing import Dict, Any
from sphinx.application import Sphinx from sphinx.application import Sphinx
from sphinx.util.osutil import make_filename from sphinx.util.osutil import make_filename
from sphinx.locale import __
version = (0, 1, 0) version = (0, 1, 0)
@ -65,47 +64,21 @@ def setup(app: Sphinx) -> Dict[str, Any]:
True, True,
'weasyprint' 'weasyprint'
) )
app.add_config_value( app.add_config_value(
'weasyprint_sign_method', 'weasyprint_main_selector',
'none',
'weasyprint'
)
app.add_config_value(
'weasyprint_sign_certid',
'', '',
'weasyprint' 'weasyprint'
) )
app.add_config_value( app.add_config_value(
'weasyprint_sign_reason', 'weasyprint_footer_selector',
'', '',
'weasyprint' 'weasyprint'
) )
app.add_config_value( app.add_config_value(
'weasyprint_sign_contact', 'weasyprint_header_selector',
'', '',
'weasyprint' 'weasyprint'
) )
app.add_config_value(
'weasyprint_sign_location',
'',
'weasyprint'
)
app.add_config_value(
'weasyprint_sign_image',
None,
'weasyprint'
)
app.add_config_value(
'weasyprint_sign_position',
None,
'weasyprint'
)
app.add_config_value(
'weasyprint_sign_text',
__('Signed on {sign_date}\nat {sign_location}\nby {sign_contact}\nfor {sign_reason}'),
'weasyprint'
)
return { return {
'version': version, 'version': version,

View file

@ -1,15 +0,0 @@
#!/usr/bin/env python3
def sign_pdf_pkcs11(filename, certid, reason, contact, location, image, text):
pass
def sign_pdf_p12(filename, certid, reason, contact, location, image, text):
pass
def sign_pdf_gpg(filename, certid, reason, contact, location, image, text):
pass

View file

@ -2,19 +2,142 @@
import os import os
from typing import Dict, Set, Tuple from typing import Dict, Set, Tuple
from copy import deepcopy
from sphinx.builders.singlehtml import SingleFileHTMLBuilder from sphinx.builders.singlehtml import SingleFileHTMLBuilder
from sphinx.util import progress_message from sphinx.util import progress_message
from sphinx.util.osutil import os_path from sphinx.util.osutil import os_path
from sphinx.locale import __ from sphinx.locale import __
from sphinx.util import logging
from bs4 import BeautifulSoup
import weasyprint import weasyprint
from .loghandler import init_wpsphinx_log from .loghandler import init_wpsphinx_log
logger = logging.getLogger('weasyprint')
init_wpsphinx_log() init_wpsphinx_log()
def extract(soup, selector: str) -> None:
elements = soup.select(selector)
for element in elements:
element.extract()
class PdfGenerator:
"""
From WeasyPrint 47 documentation Tips & Tricks
"""
def __init__(
self, base_url: str,
main_selector: str,
footer_selector: str,
header_selector: str
) -> None:
self.base_url = base_url
with open(self.base_url) as htmlfile:
self.main_html = htmlfile.read()
self.footer_html = None
self.header_html = None
if not (footer_selector or header_selector):
return
if not main_selector:
logger.error(
__('You must define a selector for content if you set selector for footer or header.'))
return
main_soup = BeautifulSoup(self.main_html, 'html.parser')
if footer_selector:
footer_soup = deepcopy(main_soup)
if header_selector:
extract(footer_soup, header_selector)
extract(footer_soup, main_selector)
if header_selector:
header_soup = deepcopy(main_soup)
if footer_selector:
extract(header_soup, footer_selector)
extract(header_soup, main_selector)
extract(main_soup, header_selector)
if footer_selector:
extract(main_soup, footer_selector)
self.main_html = str(main_soup)
if footer_soup:
self.footer_html = str(footer_soup)
if header_soup:
self.header_html = str(header_soup)
def _compute_overlay_element(self, element: str) -> weasyprint.HTML:
html = weasyprint.HTML(
string=getattr(self, f'{element}_html'),
base_url=self.base_url,
)
element_doc = html.render()
element_page = element_doc.pages[0]
element_body = PdfGenerator.get_element(
element_page._page_box.all_children(), 'body'
)
element_body = element_body.copy_with_children(
element_body.all_children()
)
return element_body
def _apply_overlay_on_main(
self, main_doc, header_body=None, footer_body=None
) -> None:
for page in main_doc.pages:
page_body = PdfGenerator.get_element(
page._page_box.all_children(), 'body'
)
if header_body:
page_body.children += header_body.all_children()
if footer_body:
page_body.children += footer_body.all_children()
def write_pdf(self, target: str) -> None:
if self.header_html:
header_body = self._compute_overlay_element(
'header'
)
else:
header_body = None
if self.footer_html:
footer_body = self._compute_overlay_element(
'footer'
)
else:
footer_body = None
html = weasyprint.HTML(
string=self.main_html,
base_url=self.base_url,
)
main_doc = html.render()
if self.header_html or self.footer_html:
self._apply_overlay_on_main(main_doc, header_body, footer_body)
main_doc.write_pdf(target)
@staticmethod
def get_element(boxes, element):
for box in boxes:
if box.element_tag == element:
return box
return PdfGenerator.get_element(box.all_children(), element)
class WeasyPrintPDFBuilder(SingleFileHTMLBuilder): class WeasyPrintPDFBuilder(SingleFileHTMLBuilder):
name = 'weasyprint' name = 'weasyprint'
epilog = __('The PDF file has been saved in %(outdir)s.') epilog = __('The PDF file has been saved in %(outdir)s.')
@ -57,7 +180,8 @@ class WeasyPrintPDFBuilder(SingleFileHTMLBuilder):
self.globalcontext['use_opensearch'] = False self.globalcontext['use_opensearch'] = False
self.globalcontext['docstitle'] = self.config.weasyprint_title self.globalcontext['docstitle'] = self.config.weasyprint_title
self.globalcontext['shorttitle'] = self.config.weasyprint_short_title self.globalcontext['shorttitle'] = self.config.weasyprint_short_title
self.globalcontext['show_copyright'] = self.config.weasyprint_show_copyright self.globalcontext['show_copyright'] = \
self.config.weasyprint_show_copyright
self.globalcontext['show_sphinx'] = self.config.weasyprint_show_sphinx self.globalcontext['show_sphinx'] = self.config.weasyprint_show_sphinx
self.globalcontext['style'] = stylename self.globalcontext['style'] = stylename
self.globalcontext['favicon'] = None self.globalcontext['favicon'] = None
@ -73,6 +197,9 @@ class WeasyPrintPDFBuilder(SingleFileHTMLBuilder):
self.outdir, self.outdir,
self.config.weasyprint_basename + '.pdf' self.config.weasyprint_basename + '.pdf'
) )
weasy_html = weasyprint.HTML(infile) generator = PdfGenerator(
weasy_html.write_pdf(outfile) infile,
# progress_message('Signing PDF') self.config.weasyprint_main_selector,
self.config.weasyprint_footer_selector,
self.config.weasyprint_header_selector)
generator.write_pdf(outfile)

View file

@ -2,3 +2,4 @@ Sphinx>=3.0.0
WeasyPrint WeasyPrint
endesive endesive
pykcs11 pykcs11
bs4

View file

@ -50,6 +50,7 @@ setup(
install_requires=[ install_requires=[
"sphinx>=3.0.0", "sphinx>=3.0.0",
"WeasyPrint", "WeasyPrint",
"bs4",
], ],
classifiers=[ classifiers=[
"Framework :: Sphinx", "Framework :: Sphinx",

View file

@ -4,7 +4,6 @@ from .weasyprint_builder import WeasyPrintPDFBuilder
from typing import Dict, Any from typing import Dict, Any
from sphinx.application import Sphinx from sphinx.application import Sphinx
from sphinx.util.osutil import make_filename from sphinx.util.osutil import make_filename
from sphinx.locale import __
version = (0, 1, 0) version = (0, 1, 0)
@ -65,47 +64,21 @@ def setup(app: Sphinx) -> Dict[str, Any]:
True, True,
'weasyprint' 'weasyprint'
) )
app.add_config_value( app.add_config_value(
'weasyprint_sign_method', 'weasyprint_main_selector',
'none',
'weasyprint'
)
app.add_config_value(
'weasyprint_sign_certid',
'', '',
'weasyprint' 'weasyprint'
) )
app.add_config_value( app.add_config_value(
'weasyprint_sign_reason', 'weasyprint_footer_selector',
'', '',
'weasyprint' 'weasyprint'
) )
app.add_config_value( app.add_config_value(
'weasyprint_sign_contact', 'weasyprint_header_selector',
'', '',
'weasyprint' 'weasyprint'
) )
app.add_config_value(
'weasyprint_sign_location',
'',
'weasyprint'
)
app.add_config_value(
'weasyprint_sign_image',
None,
'weasyprint'
)
app.add_config_value(
'weasyprint_sign_position',
None,
'weasyprint'
)
app.add_config_value(
'weasyprint_sign_text',
__('Signed on {sign_date}\nat {sign_location}\nby {sign_contact}\nfor {sign_reason}'),
'weasyprint'
)
return { return {
'version': version, 'version': version,

View file

@ -1,15 +0,0 @@
#!/usr/bin/env python3
def sign_pdf_pkcs11(filename, certid, reason, contact, location, image, text):
pass
def sign_pdf_p12(filename, certid, reason, contact, location, image, text):
pass
def sign_pdf_gpg(filename, certid, reason, contact, location, image, text):
pass

View file

@ -2,19 +2,142 @@
import os import os
from typing import Dict, Set, Tuple from typing import Dict, Set, Tuple
from copy import deepcopy
from sphinx.builders.singlehtml import SingleFileHTMLBuilder from sphinx.builders.singlehtml import SingleFileHTMLBuilder
from sphinx.util import progress_message from sphinx.util import progress_message
from sphinx.util.osutil import os_path from sphinx.util.osutil import os_path
from sphinx.locale import __ from sphinx.locale import __
from sphinx.util import logging
from bs4 import BeautifulSoup
import weasyprint import weasyprint
from .loghandler import init_wpsphinx_log from .loghandler import init_wpsphinx_log
logger = logging.getLogger('weasyprint')
init_wpsphinx_log() init_wpsphinx_log()
def extract(soup, selector: str) -> None:
elements = soup.select(selector)
for element in elements:
element.extract()
class PdfGenerator:
"""
From WeasyPrint 47 documentation Tips & Tricks
"""
def __init__(
self, base_url: str,
main_selector: str,
footer_selector: str,
header_selector: str
) -> None:
self.base_url = base_url
with open(self.base_url) as htmlfile:
self.main_html = htmlfile.read()
self.footer_html = None
self.header_html = None
if not (footer_selector or header_selector):
return
if not main_selector:
logger.error(
__('You must define a selector for content if you set selector for footer or header.'))
return
main_soup = BeautifulSoup(self.main_html, 'html.parser')
if footer_selector:
footer_soup = deepcopy(main_soup)
if header_selector:
extract(footer_soup, header_selector)
extract(footer_soup, main_selector)
if header_selector:
header_soup = deepcopy(main_soup)
if footer_selector:
extract(header_soup, footer_selector)
extract(header_soup, main_selector)
extract(main_soup, header_selector)
if footer_selector:
extract(main_soup, footer_selector)
self.main_html = str(main_soup)
if footer_soup:
self.footer_html = str(footer_soup)
if header_soup:
self.header_html = str(header_soup)
def _compute_overlay_element(self, element: str) -> weasyprint.HTML:
html = weasyprint.HTML(
string=getattr(self, f'{element}_html'),
base_url=self.base_url,
)
element_doc = html.render()
element_page = element_doc.pages[0]
element_body = PdfGenerator.get_element(
element_page._page_box.all_children(), 'body'
)
element_body = element_body.copy_with_children(
element_body.all_children()
)
return element_body
def _apply_overlay_on_main(
self, main_doc, header_body=None, footer_body=None
) -> None:
for page in main_doc.pages:
page_body = PdfGenerator.get_element(
page._page_box.all_children(), 'body'
)
if header_body:
page_body.children += header_body.all_children()
if footer_body:
page_body.children += footer_body.all_children()
def write_pdf(self, target: str) -> None:
if self.header_html:
header_body = self._compute_overlay_element(
'header'
)
else:
header_body = None
if self.footer_html:
footer_body = self._compute_overlay_element(
'footer'
)
else:
footer_body = None
html = weasyprint.HTML(
string=self.main_html,
base_url=self.base_url,
)
main_doc = html.render()
if self.header_html or self.footer_html:
self._apply_overlay_on_main(main_doc, header_body, footer_body)
main_doc.write_pdf(target)
@staticmethod
def get_element(boxes, element):
for box in boxes:
if box.element_tag == element:
return box
return PdfGenerator.get_element(box.all_children(), element)
class WeasyPrintPDFBuilder(SingleFileHTMLBuilder): class WeasyPrintPDFBuilder(SingleFileHTMLBuilder):
name = 'weasyprint' name = 'weasyprint'
epilog = __('The PDF file has been saved in %(outdir)s.') epilog = __('The PDF file has been saved in %(outdir)s.')
@ -57,7 +180,8 @@ class WeasyPrintPDFBuilder(SingleFileHTMLBuilder):
self.globalcontext['use_opensearch'] = False self.globalcontext['use_opensearch'] = False
self.globalcontext['docstitle'] = self.config.weasyprint_title self.globalcontext['docstitle'] = self.config.weasyprint_title
self.globalcontext['shorttitle'] = self.config.weasyprint_short_title self.globalcontext['shorttitle'] = self.config.weasyprint_short_title
self.globalcontext['show_copyright'] = self.config.weasyprint_show_copyright self.globalcontext['show_copyright'] = \
self.config.weasyprint_show_copyright
self.globalcontext['show_sphinx'] = self.config.weasyprint_show_sphinx self.globalcontext['show_sphinx'] = self.config.weasyprint_show_sphinx
self.globalcontext['style'] = stylename self.globalcontext['style'] = stylename
self.globalcontext['favicon'] = None self.globalcontext['favicon'] = None
@ -73,6 +197,9 @@ class WeasyPrintPDFBuilder(SingleFileHTMLBuilder):
self.outdir, self.outdir,
self.config.weasyprint_basename + '.pdf' self.config.weasyprint_basename + '.pdf'
) )
weasy_html = weasyprint.HTML(infile) generator = PdfGenerator(
weasy_html.write_pdf(outfile) infile,
# progress_message('Signing PDF') self.config.weasyprint_main_selector,
self.config.weasyprint_footer_selector,
self.config.weasyprint_header_selector)
generator.write_pdf(outfile)