weasyprint 66.0__py3-none-any.whl → 68.0__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- weasyprint/__init__.py +47 -108
- weasyprint/__main__.py +120 -84
- weasyprint/anchors.py +4 -4
- weasyprint/css/__init__.py +719 -68
- weasyprint/css/computed_values.py +64 -175
- weasyprint/css/counters.py +1 -1
- weasyprint/css/functions.py +211 -0
- weasyprint/css/html5_ua.css +2 -1
- weasyprint/css/html5_ua_form.css +1 -1
- weasyprint/css/media_queries.py +3 -1
- weasyprint/css/properties.py +6 -2
- weasyprint/css/{utils.py → tokens.py} +310 -398
- weasyprint/css/units.py +91 -0
- weasyprint/css/validation/__init__.py +1 -1
- weasyprint/css/validation/descriptors.py +47 -19
- weasyprint/css/validation/expanders.py +7 -8
- weasyprint/css/validation/properties.py +343 -359
- weasyprint/document.py +22 -73
- weasyprint/draw/__init__.py +6 -7
- weasyprint/draw/border.py +3 -5
- weasyprint/draw/color.py +1 -1
- weasyprint/draw/text.py +62 -40
- weasyprint/formatting_structure/boxes.py +24 -3
- weasyprint/formatting_structure/build.py +113 -41
- weasyprint/images.py +94 -78
- weasyprint/layout/__init__.py +29 -25
- weasyprint/layout/absolute.py +3 -5
- weasyprint/layout/background.py +7 -7
- weasyprint/layout/block.py +140 -128
- weasyprint/layout/column.py +18 -24
- weasyprint/layout/flex.py +13 -5
- weasyprint/layout/float.py +4 -6
- weasyprint/layout/grid.py +304 -99
- weasyprint/layout/inline.py +114 -60
- weasyprint/layout/page.py +27 -16
- weasyprint/layout/percent.py +14 -10
- weasyprint/layout/preferred.py +79 -31
- weasyprint/layout/replaced.py +9 -6
- weasyprint/layout/table.py +8 -5
- weasyprint/pdf/__init__.py +58 -14
- weasyprint/pdf/anchors.py +11 -18
- weasyprint/pdf/fonts.py +135 -69
- weasyprint/pdf/metadata.py +155 -68
- weasyprint/pdf/pdfa.py +20 -6
- weasyprint/pdf/pdfua.py +1 -3
- weasyprint/pdf/pdfx.py +81 -0
- weasyprint/pdf/stream.py +18 -3
- weasyprint/pdf/tags.py +6 -4
- weasyprint/svg/__init__.py +85 -48
- weasyprint/svg/css.py +21 -4
- weasyprint/svg/defs.py +5 -3
- weasyprint/svg/images.py +11 -3
- weasyprint/svg/text.py +11 -2
- weasyprint/svg/utils.py +6 -3
- weasyprint/text/constants.py +1 -1
- weasyprint/text/ffi.py +4 -3
- weasyprint/text/fonts.py +14 -7
- weasyprint/text/line_break.py +101 -17
- weasyprint/urls.py +288 -95
- {weasyprint-66.0.dist-info → weasyprint-68.0.dist-info}/METADATA +6 -6
- weasyprint-68.0.dist-info/RECORD +77 -0
- weasyprint-66.0.dist-info/RECORD +0 -74
- {weasyprint-66.0.dist-info → weasyprint-68.0.dist-info}/WHEEL +0 -0
- {weasyprint-66.0.dist-info → weasyprint-68.0.dist-info}/entry_points.txt +0 -0
- {weasyprint-66.0.dist-info → weasyprint-68.0.dist-info}/licenses/LICENSE +0 -0
weasyprint/__init__.py
CHANGED
|
@@ -5,7 +5,6 @@ importing sub-modules.
|
|
|
5
5
|
|
|
6
6
|
"""
|
|
7
7
|
|
|
8
|
-
import contextlib
|
|
9
8
|
from datetime import datetime
|
|
10
9
|
from os.path import getctime, getmtime
|
|
11
10
|
from pathlib import Path
|
|
@@ -15,9 +14,9 @@ import cssselect2
|
|
|
15
14
|
import tinycss2
|
|
16
15
|
import tinyhtml5
|
|
17
16
|
|
|
18
|
-
VERSION = __version__ = '
|
|
17
|
+
VERSION = __version__ = '68.0'
|
|
19
18
|
|
|
20
|
-
#: Default values for command-line and Python API options. See
|
|
19
|
+
#: Default values for command-line and Python API rendering options. See
|
|
21
20
|
#: :func:`__main__.main` to learn more about specific options for
|
|
22
21
|
#: command-line.
|
|
23
22
|
#:
|
|
@@ -67,14 +66,15 @@ VERSION = __version__ = '66.0'
|
|
|
67
66
|
#: images are temporarily stored.
|
|
68
67
|
DEFAULT_OPTIONS = {
|
|
69
68
|
'stylesheets': None,
|
|
70
|
-
'media_type': 'print',
|
|
71
69
|
'attachments': None,
|
|
70
|
+
'attachment_relationships': None,
|
|
72
71
|
'pdf_identifier': None,
|
|
73
72
|
'pdf_variant': None,
|
|
74
73
|
'pdf_version': None,
|
|
75
74
|
'pdf_forms': None,
|
|
76
75
|
'pdf_tags': False,
|
|
77
76
|
'uncompressed_pdf': False,
|
|
77
|
+
'xmp_metadata': None,
|
|
78
78
|
'custom_metadata': False,
|
|
79
79
|
'presentational_hints': False,
|
|
80
80
|
'srgb': False,
|
|
@@ -92,8 +92,7 @@ __all__ = [
|
|
|
92
92
|
|
|
93
93
|
|
|
94
94
|
# Import after setting the version, as the version is used in other modules
|
|
95
|
-
from .urls import
|
|
96
|
-
fetch, default_url_fetcher, path2url, ensure_url, url_is_absolute)
|
|
95
|
+
from .urls import URLFetcher, default_url_fetcher, select_source # noqa: I001, E402
|
|
97
96
|
from .logger import LOGGER, PROGRESS_LOGGER # noqa: E402
|
|
98
97
|
# Some imports are at the end of the file (after the CSS class)
|
|
99
98
|
# to work around circular imports.
|
|
@@ -150,9 +149,7 @@ class HTML:
|
|
|
150
149
|
:term:`file objects <file object>`.
|
|
151
150
|
:type url_fetcher: :term:`callable`
|
|
152
151
|
:param url_fetcher:
|
|
153
|
-
|
|
154
|
-
:func:`default_url_fetcher` called to fetch external resources such as
|
|
155
|
-
stylesheets and images. (See :ref:`URL Fetchers`.)
|
|
152
|
+
An instance of :class:`urls.URLFetcher`. (See :ref:`URL Fetchers`.)
|
|
156
153
|
:param str media_type:
|
|
157
154
|
The media type to use for ``@media``. Defaults to ``'print'``.
|
|
158
155
|
**Note:** In some cases like ``HTML(string=foo)`` relative URLs will be
|
|
@@ -161,25 +158,24 @@ class HTML:
|
|
|
161
158
|
"""
|
|
162
159
|
def __init__(self, guess=None, filename=None, url=None, file_obj=None,
|
|
163
160
|
string=None, encoding=None, base_url=None,
|
|
164
|
-
url_fetcher=
|
|
161
|
+
url_fetcher=None, media_type='print'):
|
|
165
162
|
PROGRESS_LOGGER.info(
|
|
166
163
|
'Step 1 - Fetching and parsing HTML - %s',
|
|
167
164
|
guess or filename or url or
|
|
168
165
|
getattr(file_obj, 'name', 'HTML string'))
|
|
169
166
|
if isinstance(base_url, Path):
|
|
170
167
|
base_url = str(base_url)
|
|
171
|
-
|
|
168
|
+
if url_fetcher is None:
|
|
169
|
+
url_fetcher = URLFetcher()
|
|
170
|
+
result = select_source(
|
|
172
171
|
guess, filename, url, file_obj, string, base_url, url_fetcher)
|
|
173
|
-
with result as (
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
if encoding is not None:
|
|
181
|
-
kwargs['override_encoding'] = encoding
|
|
182
|
-
result = tinyhtml5.parse(source, **kwargs)
|
|
172
|
+
with result as (file_obj, base_url, protocol_encoding, _):
|
|
173
|
+
kwargs = {'namespace_html_elements': False}
|
|
174
|
+
if protocol_encoding is not None:
|
|
175
|
+
kwargs['transport_encoding'] = protocol_encoding
|
|
176
|
+
if encoding is not None:
|
|
177
|
+
kwargs['override_encoding'] = encoding
|
|
178
|
+
result = tinyhtml5.parse(file_obj, **kwargs)
|
|
183
179
|
self.base_url = _find_base_url(result, base_url)
|
|
184
180
|
self.url_fetcher = url_fetcher
|
|
185
181
|
self.media_type = media_type
|
|
@@ -198,7 +194,8 @@ class HTML:
|
|
|
198
194
|
def _ph_stylesheets(self):
|
|
199
195
|
return [HTML5_PH_STYLESHEET]
|
|
200
196
|
|
|
201
|
-
def render(self, font_config=None, counter_style=None,
|
|
197
|
+
def render(self, font_config=None, counter_style=None, color_profiles=None,
|
|
198
|
+
**options):
|
|
202
199
|
"""Lay out and paginate the document, but do not (yet) export it.
|
|
203
200
|
|
|
204
201
|
This returns a :class:`document.Document` object which provides
|
|
@@ -222,10 +219,11 @@ class HTML:
|
|
|
222
219
|
new_options = DEFAULT_OPTIONS.copy()
|
|
223
220
|
new_options.update(options)
|
|
224
221
|
options = new_options
|
|
225
|
-
return Document._render(
|
|
222
|
+
return Document._render(
|
|
223
|
+
self, font_config, counter_style, color_profiles, options)
|
|
226
224
|
|
|
227
225
|
def write_pdf(self, target=None, zoom=1, finisher=None,
|
|
228
|
-
font_config=None, counter_style=None, **options):
|
|
226
|
+
font_config=None, counter_style=None, color_profiles=None, **options):
|
|
229
227
|
"""Render the document to a PDF file.
|
|
230
228
|
|
|
231
229
|
This is a shortcut for calling :meth:`render`, then
|
|
@@ -265,7 +263,7 @@ class HTML:
|
|
|
265
263
|
new_options.update(options)
|
|
266
264
|
options = new_options
|
|
267
265
|
return (
|
|
268
|
-
self.render(font_config, counter_style, **options)
|
|
266
|
+
self.render(font_config, counter_style, color_profiles, **options)
|
|
269
267
|
.write_pdf(target, zoom, finisher, **options))
|
|
270
268
|
|
|
271
269
|
|
|
@@ -284,35 +282,37 @@ class CSS:
|
|
|
284
282
|
of :class:`HTML` objects.
|
|
285
283
|
|
|
286
284
|
"""
|
|
287
|
-
def __init__(self, guess=None, filename=None, url=None, file_obj=None,
|
|
288
|
-
|
|
289
|
-
url_fetcher=default_url_fetcher, _check_mime_type=False,
|
|
285
|
+
def __init__(self, guess=None, filename=None, url=None, file_obj=None, string=None,
|
|
286
|
+
encoding=None, base_url=None, url_fetcher=None, _check_mime_type=False,
|
|
290
287
|
media_type='print', font_config=None, counter_style=None,
|
|
291
|
-
matcher=None, page_rules=None
|
|
288
|
+
color_profiles=None, matcher=None, page_rules=None, layers=None,
|
|
289
|
+
layer=None):
|
|
292
290
|
PROGRESS_LOGGER.info(
|
|
293
291
|
'Step 2 - Fetching and parsing CSS - %s',
|
|
294
292
|
filename or url or getattr(file_obj, 'name', 'CSS string'))
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
if isinstance(
|
|
303
|
-
|
|
304
|
-
stylesheet = tinycss2.parse_stylesheet(source)
|
|
293
|
+
if url_fetcher is None:
|
|
294
|
+
url_fetcher = URLFetcher()
|
|
295
|
+
result = select_source(
|
|
296
|
+
guess, filename, url, file_obj, string, base_url=base_url,
|
|
297
|
+
url_fetcher=url_fetcher, check_css_mime_type=_check_mime_type)
|
|
298
|
+
with result as (file_obj, base_url, protocol_encoding, mime_type):
|
|
299
|
+
css = file_obj.read()
|
|
300
|
+
if isinstance(css, str):
|
|
301
|
+
stylesheet = tinycss2.parse_stylesheet(css)
|
|
305
302
|
else:
|
|
306
|
-
stylesheet,
|
|
307
|
-
|
|
303
|
+
stylesheet, _ = tinycss2.parse_stylesheet_bytes(
|
|
304
|
+
css, environment_encoding=encoding,
|
|
308
305
|
protocol_encoding=protocol_encoding)
|
|
309
306
|
self.base_url = base_url
|
|
310
307
|
self.matcher = matcher or cssselect2.Matcher()
|
|
311
308
|
self.page_rules = [] if page_rules is None else page_rules
|
|
309
|
+
self.layers = [] if layers is None else layers
|
|
312
310
|
counter_style = {} if counter_style is None else counter_style
|
|
311
|
+
color_profiles = {} if color_profiles is None else color_profiles
|
|
313
312
|
preprocess_stylesheet(
|
|
314
313
|
media_type, base_url, stylesheet, url_fetcher, self.matcher,
|
|
315
|
-
self.page_rules, font_config, counter_style
|
|
314
|
+
self.page_rules, self.layers, font_config, counter_style, color_profiles,
|
|
315
|
+
layer=layer)
|
|
316
316
|
|
|
317
317
|
|
|
318
318
|
class Attachment:
|
|
@@ -341,10 +341,12 @@ class Attachment:
|
|
|
341
341
|
|
|
342
342
|
"""
|
|
343
343
|
def __init__(self, guess=None, filename=None, url=None, file_obj=None,
|
|
344
|
-
string=None, base_url=None, url_fetcher=
|
|
345
|
-
|
|
344
|
+
string=None, base_url=None, url_fetcher=None, name=None,
|
|
345
|
+
description=None, created=None, modified=None,
|
|
346
346
|
relationship='Unspecified'):
|
|
347
|
-
|
|
347
|
+
if url_fetcher is None:
|
|
348
|
+
url_fetcher = URLFetcher()
|
|
349
|
+
self.source = select_source(
|
|
348
350
|
guess, filename, url, file_obj, string, base_url=base_url,
|
|
349
351
|
url_fetcher=url_fetcher)
|
|
350
352
|
self.name = name
|
|
@@ -366,69 +368,6 @@ class Attachment:
|
|
|
366
368
|
self.modified = modified
|
|
367
369
|
|
|
368
370
|
|
|
369
|
-
@contextlib.contextmanager
|
|
370
|
-
def _select_source(guess=None, filename=None, url=None, file_obj=None,
|
|
371
|
-
string=None, base_url=None, url_fetcher=default_url_fetcher,
|
|
372
|
-
check_css_mime_type=False):
|
|
373
|
-
"""If only one input is given, return it with normalized ``base_url``."""
|
|
374
|
-
if base_url is not None:
|
|
375
|
-
base_url = ensure_url(base_url)
|
|
376
|
-
|
|
377
|
-
selected_params = [
|
|
378
|
-
param for param in (guess, filename, url, file_obj, string) if
|
|
379
|
-
param is not None]
|
|
380
|
-
if len(selected_params) != 1:
|
|
381
|
-
source = ', '.join(selected_params) or 'nothing'
|
|
382
|
-
raise TypeError(f'Expected exactly one source, got {source}')
|
|
383
|
-
elif guess is not None:
|
|
384
|
-
if hasattr(guess, 'read'):
|
|
385
|
-
type_ = 'file_obj'
|
|
386
|
-
elif isinstance(guess, Path):
|
|
387
|
-
type_ = 'filename'
|
|
388
|
-
elif url_is_absolute(guess):
|
|
389
|
-
type_ = 'url'
|
|
390
|
-
else:
|
|
391
|
-
type_ = 'filename'
|
|
392
|
-
result = _select_source(
|
|
393
|
-
base_url=base_url, url_fetcher=url_fetcher,
|
|
394
|
-
check_css_mime_type=check_css_mime_type,
|
|
395
|
-
**{type_: guess})
|
|
396
|
-
with result as result:
|
|
397
|
-
yield result
|
|
398
|
-
elif filename is not None:
|
|
399
|
-
if base_url is None:
|
|
400
|
-
base_url = path2url(filename)
|
|
401
|
-
with open(filename, 'rb') as file_obj:
|
|
402
|
-
yield 'file_obj', file_obj, base_url, None
|
|
403
|
-
elif url is not None:
|
|
404
|
-
with fetch(url_fetcher, url) as result:
|
|
405
|
-
if check_css_mime_type and result['mime_type'] != 'text/css':
|
|
406
|
-
LOGGER.error(
|
|
407
|
-
'Unsupported stylesheet type %s for %s',
|
|
408
|
-
result['mime_type'], result['redirected_url'])
|
|
409
|
-
yield 'string', '', base_url, None
|
|
410
|
-
else:
|
|
411
|
-
proto_encoding = result.get('encoding')
|
|
412
|
-
if base_url is None:
|
|
413
|
-
base_url = result.get('redirected_url', url)
|
|
414
|
-
if 'string' in result:
|
|
415
|
-
yield 'string', result['string'], base_url, proto_encoding
|
|
416
|
-
else:
|
|
417
|
-
yield (
|
|
418
|
-
'file_obj', result['file_obj'], base_url,
|
|
419
|
-
proto_encoding)
|
|
420
|
-
elif file_obj is not None:
|
|
421
|
-
if base_url is None:
|
|
422
|
-
# filesystem file-like objects have a 'name' attribute.
|
|
423
|
-
name = getattr(file_obj, 'name', None)
|
|
424
|
-
# Some streams have a .name like '<stdin>', not a filename.
|
|
425
|
-
if name and not name.startswith('<'):
|
|
426
|
-
base_url = ensure_url(name)
|
|
427
|
-
yield 'file_obj', file_obj, base_url, None
|
|
428
|
-
else:
|
|
429
|
-
assert string is not None
|
|
430
|
-
yield 'string', string, base_url, None
|
|
431
|
-
|
|
432
371
|
# Work around circular imports.
|
|
433
372
|
from .css import preprocess_stylesheet # noqa: I001, E402
|
|
434
373
|
from .html import ( # noqa: E402
|
weasyprint/__main__.py
CHANGED
|
@@ -4,141 +4,169 @@ import argparse
|
|
|
4
4
|
import logging
|
|
5
5
|
import platform
|
|
6
6
|
import sys
|
|
7
|
-
from functools import partial
|
|
8
7
|
|
|
9
8
|
import pydyf
|
|
10
9
|
|
|
11
10
|
from . import DEFAULT_OPTIONS, HTML, LOGGER, __version__
|
|
12
11
|
from .pdf import VARIANTS
|
|
13
12
|
from .text.ffi import pango
|
|
14
|
-
from .urls import
|
|
13
|
+
from .urls import URLFetcher
|
|
15
14
|
|
|
16
15
|
|
|
17
16
|
class PrintInfo(argparse.Action):
|
|
18
17
|
def __call__(*_, **__):
|
|
18
|
+
# TODO: ignore check at block-level when available.
|
|
19
|
+
# https://github.com/astral-sh/ruff/issues/3711
|
|
19
20
|
uname = platform.uname()
|
|
20
|
-
print('System:', uname.system)
|
|
21
|
-
print('Machine:', uname.machine)
|
|
22
|
-
print('Version:', uname.version)
|
|
23
|
-
print('Release:', uname.release)
|
|
24
|
-
print()
|
|
25
|
-
print('WeasyPrint version:', __version__)
|
|
26
|
-
print('Python version:', sys.version.split()[0])
|
|
27
|
-
print('Pydyf version:', pydyf.__version__)
|
|
28
|
-
print('Pango version:', pango.pango_version())
|
|
21
|
+
print('System:', uname.system) # noqa: T201
|
|
22
|
+
print('Machine:', uname.machine) # noqa: T201
|
|
23
|
+
print('Version:', uname.version) # noqa: T201
|
|
24
|
+
print('Release:', uname.release) # noqa: T201
|
|
25
|
+
print() # noqa: T201
|
|
26
|
+
print('WeasyPrint version:', __version__) # noqa: T201
|
|
27
|
+
print('Python version:', sys.version.split()[0]) # noqa: T201
|
|
28
|
+
print('Pydyf version:', pydyf.__version__) # noqa: T201
|
|
29
|
+
print('Pango version:', pango.pango_version()) # noqa: T201
|
|
29
30
|
sys.exit()
|
|
30
31
|
|
|
31
32
|
|
|
32
33
|
class Parser(argparse.ArgumentParser):
|
|
33
34
|
def __init__(self, *args, **kwargs):
|
|
34
|
-
self.
|
|
35
|
+
self._groups = {None: {}}
|
|
35
36
|
super().__init__(*args, **kwargs)
|
|
36
37
|
|
|
37
|
-
def add_argument(self, *args, **kwargs):
|
|
38
|
-
|
|
38
|
+
def add_argument(self, *args, _group_name=None, **kwargs):
|
|
39
|
+
if _group_name is None:
|
|
40
|
+
super().add_argument(*args, **kwargs)
|
|
39
41
|
key = args[-1].lstrip('-')
|
|
40
42
|
kwargs['flags'] = args
|
|
41
43
|
kwargs['positional'] = args[-1][0] != '-'
|
|
42
|
-
self.
|
|
44
|
+
self._groups[_group_name][key] = kwargs
|
|
45
|
+
|
|
46
|
+
def add_argument_group(self, name, *args, **kwargs):
|
|
47
|
+
group = super().add_argument_group(name, *args, **kwargs)
|
|
48
|
+
self._groups[name] = {}
|
|
49
|
+
def add_argument(*args, **kwargs):
|
|
50
|
+
group._add_argument(*args, **kwargs)
|
|
51
|
+
self.add_argument(*args, _group_name=name, **kwargs)
|
|
52
|
+
group._add_argument = group.add_argument
|
|
53
|
+
group.add_argument = add_argument
|
|
54
|
+
return group
|
|
43
55
|
|
|
44
56
|
@property
|
|
45
57
|
def docstring(self):
|
|
46
|
-
self.
|
|
58
|
+
self._groups[None].pop('help')
|
|
47
59
|
data = []
|
|
48
|
-
for
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
data.append(
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
data.append('
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
data
|
|
60
|
+
for group, arguments in self._groups.items():
|
|
61
|
+
if not arguments:
|
|
62
|
+
continue
|
|
63
|
+
if group:
|
|
64
|
+
data.append(f'{group[0].title()}{group[1:]}\n')
|
|
65
|
+
data.append(f'{"~" * len(group)}\n\n')
|
|
66
|
+
for key, args in arguments.items():
|
|
67
|
+
data.append('.. option:: ')
|
|
68
|
+
action = args.get('action', 'store')
|
|
69
|
+
for flag in args['flags']:
|
|
70
|
+
data.append(flag)
|
|
71
|
+
if not args['positional'] and action in ('store', 'append'):
|
|
72
|
+
data.append(f' <{key}>')
|
|
73
|
+
data.append(', ')
|
|
74
|
+
data[-1] = '\n\n'
|
|
75
|
+
data.append(f' {args["help"][0].upper()}{args["help"][1:]}.\n\n')
|
|
76
|
+
if 'choices' in args:
|
|
77
|
+
choices = ", ".join(args['choices'])
|
|
78
|
+
data.append(f' Possible choices: {choices}.\n\n')
|
|
79
|
+
if action == 'append':
|
|
80
|
+
data.append(' This option can be passed multiple times.\n\n')
|
|
63
81
|
return ''.join(data)
|
|
64
82
|
|
|
65
83
|
|
|
66
84
|
PARSER = Parser(prog='weasyprint', description='Render web pages to PDF.')
|
|
85
|
+
PARSER.add_argument('input', help='URL or filename of the HTML input, or - for stdin')
|
|
86
|
+
PARSER.add_argument('output', help='filename where output is written, or - for stdout')
|
|
67
87
|
PARSER.add_argument(
|
|
68
|
-
'
|
|
69
|
-
PARSER.add_argument(
|
|
70
|
-
'output', help='filename where output is written, or - for stdout')
|
|
71
|
-
PARSER.add_argument(
|
|
72
|
-
'-e', '--encoding', help='force the input character encoding')
|
|
88
|
+
'-i', '--info', action=PrintInfo, nargs=0, help='print system information and exit')
|
|
73
89
|
PARSER.add_argument(
|
|
90
|
+
'--version', action='version', version=f'WeasyPrint version {__version__}',
|
|
91
|
+
help='print WeasyPrint’s version number and exit')
|
|
92
|
+
|
|
93
|
+
group = PARSER.add_argument_group('rendering options')
|
|
94
|
+
group.add_argument(
|
|
74
95
|
'-s', '--stylesheet', action='append', dest='stylesheets',
|
|
75
96
|
help='URL or filename for a user CSS stylesheet')
|
|
76
|
-
|
|
77
|
-
'-m', '--media-type',
|
|
78
|
-
help='media type to use for @media, defaults to print')
|
|
79
|
-
PARSER.add_argument(
|
|
80
|
-
'-u', '--base-url',
|
|
81
|
-
help='base for relative URLs in the HTML input, defaults to the '
|
|
82
|
-
'input’s own filename or URL or the current directory for stdin')
|
|
83
|
-
PARSER.add_argument(
|
|
97
|
+
group.add_argument(
|
|
84
98
|
'-a', '--attachment', action='append', dest='attachments',
|
|
85
99
|
help='URL or filename of a file to attach to the PDF document')
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
100
|
+
group.add_argument(
|
|
101
|
+
'--attachment-relationship', action='append', dest='attachment_relationships',
|
|
102
|
+
help='Relationship of the attachment file to attach to the PDF')
|
|
103
|
+
group.add_argument('--pdf-identifier', help='PDF file identifier')
|
|
104
|
+
group.add_argument('--pdf-variant', choices=VARIANTS, help='PDF variant to generate')
|
|
105
|
+
group.add_argument('--pdf-version', help='PDF version number')
|
|
106
|
+
group.add_argument('--pdf-forms', action='store_true', help='include PDF forms')
|
|
107
|
+
group.add_argument('--pdf-tags', action='store_true', help='tag PDF for accessibility')
|
|
108
|
+
group.add_argument(
|
|
95
109
|
'--uncompressed-pdf', action='store_true',
|
|
96
110
|
help='do not compress PDF content, mainly for debugging purpose')
|
|
97
|
-
|
|
111
|
+
group.add_argument(
|
|
112
|
+
'--xmp-metadata', action='append',
|
|
113
|
+
help='URL or filename of a file to include into the XMP metadata')
|
|
114
|
+
group.add_argument(
|
|
98
115
|
'--custom-metadata', action='store_true',
|
|
99
116
|
help='include custom HTML meta tags in PDF metadata')
|
|
100
|
-
|
|
117
|
+
group.add_argument(
|
|
101
118
|
'-p', '--presentational-hints', action='store_true',
|
|
102
119
|
help='follow HTML presentational hints')
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
help='include sRGB color profile')
|
|
106
|
-
PARSER.add_argument(
|
|
120
|
+
group.add_argument('--srgb', action='store_true', help='include sRGB color profile')
|
|
121
|
+
group.add_argument(
|
|
107
122
|
'--optimize-images', action='store_true',
|
|
108
123
|
help='optimize size of embedded images with no quality loss')
|
|
109
|
-
|
|
124
|
+
group.add_argument(
|
|
110
125
|
'-j', '--jpeg-quality', type=int,
|
|
111
126
|
help='JPEG quality between 0 (worst) to 95 (best)')
|
|
112
|
-
|
|
127
|
+
group.add_argument(
|
|
128
|
+
'-D', '--dpi', type=int,
|
|
129
|
+
help='set maximum resolution of images embedded in the PDF')
|
|
130
|
+
group.add_argument(
|
|
113
131
|
'--full-fonts', action='store_true',
|
|
114
132
|
help='embed unmodified font files when possible')
|
|
115
|
-
|
|
116
|
-
'--hinting', action='store_true',
|
|
117
|
-
|
|
118
|
-
PARSER.add_argument(
|
|
133
|
+
group.add_argument(
|
|
134
|
+
'--hinting', action='store_true', help='keep hinting information in embedded fonts')
|
|
135
|
+
group.add_argument(
|
|
119
136
|
'-c', '--cache-folder', dest='cache',
|
|
120
137
|
help='store cache on disk instead of memory, folder is '
|
|
121
138
|
'created if needed and cleaned after the PDF is generated')
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
139
|
+
|
|
140
|
+
group = PARSER.add_argument_group('HTML options')
|
|
141
|
+
group.add_argument('-e', '--encoding', help='force the input character encoding')
|
|
142
|
+
group.add_argument(
|
|
143
|
+
'-m', '--media-type', help='media type to use for @media, defaults to print',
|
|
144
|
+
default='print')
|
|
145
|
+
group.add_argument(
|
|
146
|
+
'-u', '--base-url',
|
|
147
|
+
help='base for relative URLs in the HTML input, defaults to the '
|
|
148
|
+
'input’s own filename or URL or the current directory for stdin')
|
|
149
|
+
|
|
150
|
+
group = PARSER.add_argument_group('URL fetcher options')
|
|
151
|
+
group.add_argument(
|
|
152
|
+
'-t', '--timeout', type=int, help='set timeout in seconds for HTTP requests')
|
|
153
|
+
group.add_argument(
|
|
154
|
+
'--allowed-protocols', dest='allowed_protocols',
|
|
155
|
+
help='only authorize comma-separated list of protocols for fetching URLs')
|
|
156
|
+
group.add_argument(
|
|
157
|
+
'--no-http-redirects', action='store_true', help='do not follow HTTP redirects')
|
|
158
|
+
group.add_argument(
|
|
159
|
+
'--fail-on-http-errors', action='store_true',
|
|
160
|
+
help='abort document rendering on any HTTP error')
|
|
161
|
+
|
|
162
|
+
group = PARSER.add_argument_group('command-line logging options')
|
|
163
|
+
group.add_argument(
|
|
126
164
|
'-v', '--verbose', action='store_true',
|
|
127
165
|
help='show warnings and information messages')
|
|
128
|
-
|
|
166
|
+
group.add_argument(
|
|
129
167
|
'-d', '--debug', action='store_true', help='show debugging messages')
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
PARSER.add_argument(
|
|
133
|
-
'--version', action='version',
|
|
134
|
-
version=f'WeasyPrint version {__version__}',
|
|
135
|
-
help='print WeasyPrint’s version number and exit')
|
|
136
|
-
PARSER.add_argument(
|
|
137
|
-
'-i', '--info', action=PrintInfo, nargs=0,
|
|
138
|
-
help='print system information and exit')
|
|
139
|
-
PARSER.add_argument(
|
|
140
|
-
'-t', '--timeout', type=int,
|
|
141
|
-
help='Set timeout in seconds for HTTP requests')
|
|
168
|
+
group.add_argument('-q', '--quiet', action='store_true', help='hide logging messages')
|
|
169
|
+
|
|
142
170
|
PARSER.set_defaults(**DEFAULT_OPTIONS)
|
|
143
171
|
|
|
144
172
|
|
|
@@ -166,9 +194,17 @@ def main(argv=None, stdout=None, stdin=None, HTML=HTML): # noqa: N803
|
|
|
166
194
|
else:
|
|
167
195
|
output = args.output
|
|
168
196
|
|
|
169
|
-
|
|
197
|
+
fetcher_args = {}
|
|
170
198
|
if args.timeout is not None:
|
|
171
|
-
|
|
199
|
+
fetcher_args['timeout'] = args.timeout
|
|
200
|
+
if args.allowed_protocols is not None:
|
|
201
|
+
fetcher_args['allowed_protocols'] = {
|
|
202
|
+
protocol.strip().lower() for protocol in args.allowed_protocols.split(',')}
|
|
203
|
+
if args.no_http_redirects:
|
|
204
|
+
fetcher_args['allow_redirects'] = False
|
|
205
|
+
if args.fail_on_http_errors:
|
|
206
|
+
fetcher_args['fail_on_errors'] = True
|
|
207
|
+
url_fetcher = URLFetcher(**fetcher_args)
|
|
172
208
|
|
|
173
209
|
options = {
|
|
174
210
|
key: value for key, value in vars(args).items() if key in DEFAULT_OPTIONS}
|
weasyprint/anchors.py
CHANGED
|
@@ -43,8 +43,8 @@ def gather_anchors(box, anchors, links, bookmarks, forms, parent_matrix=None,
|
|
|
43
43
|
border_width = box.border_width()
|
|
44
44
|
border_height = box.border_height()
|
|
45
45
|
origin_x, origin_y = box.style['transform_origin']
|
|
46
|
-
offset_x = percentage(origin_x, border_width)
|
|
47
|
-
offset_y = percentage(origin_y, border_height)
|
|
46
|
+
offset_x = percentage(origin_x, box.style, border_width)
|
|
47
|
+
offset_y = percentage(origin_y, box.style, border_height)
|
|
48
48
|
origin_x = box.border_box_x() + offset_x
|
|
49
49
|
origin_y = box.border_box_y() + offset_y
|
|
50
50
|
|
|
@@ -58,8 +58,8 @@ def gather_anchors(box, anchors, links, bookmarks, forms, parent_matrix=None,
|
|
|
58
58
|
b = math.sin(args)
|
|
59
59
|
c = -b
|
|
60
60
|
elif name == 'translate':
|
|
61
|
-
e = percentage(args[0], border_width)
|
|
62
|
-
f = percentage(args[1], border_height)
|
|
61
|
+
e = percentage(args[0], box.style, border_width)
|
|
62
|
+
f = percentage(args[1], box.style, border_height)
|
|
63
63
|
elif name == 'skew':
|
|
64
64
|
b, c = math.tan(args[1]), math.tan(args[0])
|
|
65
65
|
else:
|