weasyprint 67.0__py3-none-any.whl → 68.1__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 +35 -103
- weasyprint/__main__.py +107 -80
- weasyprint/css/__init__.py +7 -14
- weasyprint/css/functions.py +5 -0
- weasyprint/css/html5_ua.css +1 -1
- weasyprint/css/tokens.py +4 -1
- weasyprint/css/validation/properties.py +4 -4
- weasyprint/document.py +4 -64
- weasyprint/draw/text.py +12 -10
- weasyprint/formatting_structure/boxes.py +4 -1
- weasyprint/formatting_structure/build.py +111 -37
- weasyprint/images.py +27 -32
- weasyprint/layout/__init__.py +2 -1
- weasyprint/layout/block.py +22 -16
- weasyprint/layout/grid.py +25 -14
- weasyprint/layout/page.py +4 -4
- weasyprint/layout/preferred.py +63 -24
- weasyprint/pdf/__init__.py +12 -1
- weasyprint/pdf/anchors.py +10 -16
- weasyprint/pdf/fonts.py +12 -3
- weasyprint/pdf/metadata.py +153 -98
- weasyprint/pdf/pdfa.py +1 -3
- weasyprint/pdf/pdfua.py +1 -3
- weasyprint/pdf/pdfx.py +1 -3
- weasyprint/svg/__init__.py +52 -32
- weasyprint/svg/css.py +21 -4
- weasyprint/svg/defs.py +5 -9
- weasyprint/svg/text.py +4 -3
- weasyprint/text/fonts.py +2 -3
- weasyprint/text/line_break.py +4 -5
- weasyprint/urls.py +290 -97
- {weasyprint-67.0.dist-info → weasyprint-68.1.dist-info}/METADATA +2 -1
- {weasyprint-67.0.dist-info → weasyprint-68.1.dist-info}/RECORD +36 -36
- {weasyprint-67.0.dist-info → weasyprint-68.1.dist-info}/WHEEL +0 -0
- {weasyprint-67.0.dist-info → weasyprint-68.1.dist-info}/entry_points.txt +0 -0
- {weasyprint-67.0.dist-info → weasyprint-68.1.dist-info}/licenses/LICENSE +0 -0
weasyprint/urls.py
CHANGED
|
@@ -6,11 +6,14 @@ import os.path
|
|
|
6
6
|
import re
|
|
7
7
|
import sys
|
|
8
8
|
import traceback
|
|
9
|
+
import warnings
|
|
9
10
|
import zlib
|
|
11
|
+
from email.message import EmailMessage
|
|
10
12
|
from gzip import GzipFile
|
|
13
|
+
from io import BytesIO, StringIO
|
|
11
14
|
from pathlib import Path
|
|
15
|
+
from urllib import request
|
|
12
16
|
from urllib.parse import quote, unquote, urljoin, urlsplit
|
|
13
|
-
from urllib.request import Request, pathname2url, url2pathname, urlopen
|
|
14
17
|
|
|
15
18
|
from . import __version__
|
|
16
19
|
from .logger import LOGGER
|
|
@@ -55,8 +58,7 @@ def iri_to_uri(url):
|
|
|
55
58
|
# Data URIs can be huge, but don’t need this anyway.
|
|
56
59
|
return url
|
|
57
60
|
# Use UTF-8 as per RFC 3987 (IRI), except for file://
|
|
58
|
-
url = url.encode(
|
|
59
|
-
FILESYSTEM_ENCODING if url.startswith('file:') else 'utf-8')
|
|
61
|
+
url = url.encode(FILESYSTEM_ENCODING if url.startswith('file:') else 'utf-8')
|
|
60
62
|
# This is a full URI, not just a component. Only %-encode characters
|
|
61
63
|
# that are not allowed at all in URIs. Everthing else is "safe":
|
|
62
64
|
# * Reserved characters: /:?#[]@!$&'()*+,;=
|
|
@@ -85,7 +87,7 @@ def path2url(path):
|
|
|
85
87
|
# Otherwise relative URIs are resolved from the parent directory.
|
|
86
88
|
path += os.path.sep
|
|
87
89
|
wants_trailing_slash = True
|
|
88
|
-
path = pathname2url(path)
|
|
90
|
+
path = request.pathname2url(path)
|
|
89
91
|
# On Windows pathname2url cuts off trailing slash
|
|
90
92
|
if wants_trailing_slash and not path.endswith('/'):
|
|
91
93
|
path += '/' # pragma: no cover
|
|
@@ -191,114 +193,305 @@ def default_url_fetcher(url, timeout=10, ssl_context=None, http_headers=None,
|
|
|
191
193
|
allowed_protocols=None):
|
|
192
194
|
"""Fetch an external resource such as an image or stylesheet.
|
|
193
195
|
|
|
194
|
-
|
|
195
|
-
``url_fetcher`` argument to :class:`HTML` or :class:`CSS`.
|
|
196
|
-
(See :ref:`URL Fetchers`.)
|
|
197
|
-
|
|
198
|
-
:param str url:
|
|
199
|
-
The URL of the resource to fetch.
|
|
200
|
-
:param int timeout:
|
|
201
|
-
The number of seconds before HTTP requests are dropped.
|
|
202
|
-
:param ssl.SSLContext ssl_context:
|
|
203
|
-
An SSL context used for HTTP requests.
|
|
204
|
-
:param dict http_headers:
|
|
205
|
-
Additional HTTP headers used for HTTP requests.
|
|
206
|
-
:param set allowed_protocols:
|
|
207
|
-
A set of authorized protocols.
|
|
208
|
-
:raises: An exception indicating failure, e.g. :obj:`ValueError` on
|
|
209
|
-
syntactically invalid URL.
|
|
210
|
-
:returns: A :obj:`dict` with the following keys:
|
|
211
|
-
|
|
212
|
-
* One of ``string`` (a :obj:`bytestring <bytes>`) or ``file_obj``
|
|
213
|
-
(a :term:`file object`).
|
|
214
|
-
* Optionally: ``mime_type``, a MIME type extracted e.g. from a
|
|
215
|
-
*Content-Type* header. If not provided, the type is guessed from the
|
|
216
|
-
file extension in the URL.
|
|
217
|
-
* Optionally: ``encoding``, a character encoding extracted e.g. from a
|
|
218
|
-
*charset* parameter in a *Content-Type* header
|
|
219
|
-
* Optionally: ``redirected_url``, the actual URL of the resource
|
|
220
|
-
if there were e.g. HTTP redirects.
|
|
221
|
-
* Optionally: ``filename``, the filename of the resource. Usually
|
|
222
|
-
derived from the *filename* parameter in a *Content-Disposition*
|
|
223
|
-
header.
|
|
224
|
-
* Optionally: ``path``, the path of the resource if it is stored on the
|
|
225
|
-
local filesystem.
|
|
226
|
-
|
|
227
|
-
If a ``file_obj`` key is given, it is the caller’s responsibility
|
|
228
|
-
to call ``file_obj.close()``. The default function used internally to
|
|
229
|
-
fetch data in WeasyPrint tries to close the file object after
|
|
230
|
-
retreiving; but if this URL fetcher is used elsewhere, the file object
|
|
231
|
-
has to be closed manually.
|
|
196
|
+
This function is deprecated, use ``URLFetcher`` instead.
|
|
232
197
|
|
|
233
198
|
"""
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
199
|
+
warnings.warn(
|
|
200
|
+
"default_url_fetcher is deprecated and will be removed in WeasyPrint 69.0, "
|
|
201
|
+
"please use URLFetcher instead. For security reasons, HTTP redirects are not "
|
|
202
|
+
"supported anymore with default_url_fetcher, but are with URLFetcher.\n\nSee "
|
|
203
|
+
"https://doc.courtbouillon.org/weasyprint/stable/first_steps.html#url-fetchers",
|
|
204
|
+
category=DeprecationWarning)
|
|
205
|
+
fetcher = URLFetcher(
|
|
206
|
+
timeout, ssl_context, http_headers, allowed_protocols, allow_redirects=False)
|
|
207
|
+
return fetcher.fetch(url)
|
|
208
|
+
|
|
209
|
+
|
|
210
|
+
@contextlib.contextmanager
|
|
211
|
+
def select_source(guess=None, filename=None, url=None, file_obj=None, string=None,
|
|
212
|
+
base_url=None, url_fetcher=None, check_css_mime_type=False):
|
|
213
|
+
"""If only one input is given, return it.
|
|
214
|
+
|
|
215
|
+
Yield a file object, the base url, the protocol encoding and the protocol mime-type.
|
|
216
|
+
|
|
217
|
+
"""
|
|
218
|
+
if base_url is not None:
|
|
219
|
+
base_url = ensure_url(base_url)
|
|
220
|
+
if url_fetcher is None:
|
|
221
|
+
url_fetcher = URLFetcher()
|
|
222
|
+
|
|
223
|
+
selected_params = [
|
|
224
|
+
param for param in (guess, filename, url, file_obj, string) if
|
|
225
|
+
param is not None]
|
|
226
|
+
if len(selected_params) != 1:
|
|
227
|
+
source = ', '.join(selected_params) or 'nothing'
|
|
228
|
+
raise TypeError(f'Expected exactly one source, got {source}')
|
|
229
|
+
elif guess is not None:
|
|
230
|
+
kwargs = {
|
|
231
|
+
'base_url': base_url,
|
|
232
|
+
'url_fetcher': url_fetcher,
|
|
233
|
+
'check_css_mime_type': check_css_mime_type,
|
|
234
|
+
}
|
|
235
|
+
if hasattr(guess, 'read'):
|
|
236
|
+
kwargs['file_obj'] = guess
|
|
237
|
+
elif isinstance(guess, Path):
|
|
238
|
+
kwargs['filename'] = guess
|
|
239
|
+
elif url_is_absolute(guess):
|
|
240
|
+
kwargs['url'] = guess
|
|
241
|
+
else:
|
|
242
|
+
kwargs['filename'] = guess
|
|
243
|
+
result = select_source(**kwargs)
|
|
244
|
+
with result as result:
|
|
245
|
+
yield result
|
|
246
|
+
elif filename is not None:
|
|
247
|
+
if base_url is None:
|
|
248
|
+
base_url = path2url(filename)
|
|
249
|
+
with open(filename, 'rb') as file_obj:
|
|
250
|
+
yield file_obj, base_url, None, None
|
|
251
|
+
elif url is not None:
|
|
252
|
+
with fetch(url_fetcher, url) as response:
|
|
253
|
+
if check_css_mime_type and response.content_type != 'text/css':
|
|
254
|
+
LOGGER.error(
|
|
255
|
+
f'Unsupported stylesheet type {response.content_type} '
|
|
256
|
+
f'for {response.url}')
|
|
257
|
+
yield StringIO(''), base_url, None, None
|
|
258
|
+
else:
|
|
259
|
+
if base_url is None:
|
|
260
|
+
base_url = response.url
|
|
261
|
+
yield response, base_url, response.charset, response.content_type
|
|
262
|
+
elif file_obj is not None:
|
|
263
|
+
if base_url is None:
|
|
264
|
+
# filesystem file-like objects have a 'name' attribute.
|
|
265
|
+
name = getattr(file_obj, 'name', None)
|
|
266
|
+
# Some streams have a .name like '<stdin>', not a filename.
|
|
267
|
+
if name and not name.startswith('<'):
|
|
268
|
+
base_url = ensure_url(name)
|
|
269
|
+
yield file_obj, base_url, None, None
|
|
270
|
+
else:
|
|
271
|
+
if isinstance(string, str):
|
|
272
|
+
yield StringIO(string), base_url, None, None
|
|
273
|
+
else:
|
|
274
|
+
yield BytesIO(string), base_url, None, None
|
|
275
|
+
|
|
276
|
+
|
|
277
|
+
class URLFetchingError(IOError):
|
|
278
|
+
"""Some error happened when fetching an URL."""
|
|
279
|
+
|
|
280
|
+
|
|
281
|
+
class FatalURLFetchingError(BaseException):
|
|
282
|
+
"""Some error happened when fetching an URL and must stop the rendering."""
|
|
283
|
+
|
|
284
|
+
|
|
285
|
+
class URLFetcher(request.OpenerDirector):
|
|
286
|
+
"""Fetcher of external resources such as images or stylesheets.
|
|
287
|
+
|
|
288
|
+
:param int timeout: The number of seconds before HTTP requests are dropped.
|
|
289
|
+
:param ssl.SSLContext ssl_context: An SSL context used for HTTPS requests.
|
|
290
|
+
:param dict http_headers: Additional HTTP headers used for HTTP requests.
|
|
291
|
+
:type allowed_protocols: :term:`sequence`
|
|
292
|
+
:param allowed_protocols: A set of authorized protocols, :obj:`None` means all.
|
|
293
|
+
:param bool allow_redirects: Whether HTTP redirects must be followed.
|
|
294
|
+
:param bool fail_on_errors: Whether HTTP errors should stop the rendering.
|
|
295
|
+
|
|
296
|
+
Another class inheriting from this class, with a ``fetch`` method that has a
|
|
297
|
+
compatible signature, can be given as the ``url_fetcher`` argument to
|
|
298
|
+
:class:`weasyprint.HTML` or :class:`weasyprint.CSS`.
|
|
299
|
+
|
|
300
|
+
See :ref:`URL Fetchers` for more information and examples.
|
|
301
|
+
|
|
302
|
+
"""
|
|
303
|
+
|
|
304
|
+
def __init__(self, timeout=10, ssl_context=None, http_headers=None,
|
|
305
|
+
allowed_protocols=None, allow_redirects=True, fail_on_errors=False,
|
|
306
|
+
**kwargs):
|
|
307
|
+
super().__init__()
|
|
308
|
+
handlers = [
|
|
309
|
+
request.ProxyHandler(), request.UnknownHandler(), request.HTTPHandler(),
|
|
310
|
+
request.HTTPDefaultErrorHandler(), request.FTPHandler(),
|
|
311
|
+
request.FileHandler(), request.HTTPErrorProcessor(), request.DataHandler(),
|
|
312
|
+
request.HTTPSHandler(context=ssl_context)]
|
|
313
|
+
if allow_redirects:
|
|
314
|
+
handlers.append(request.HTTPRedirectHandler())
|
|
315
|
+
for handler in handlers:
|
|
316
|
+
self.add_handler(handler)
|
|
317
|
+
|
|
318
|
+
self._timeout = timeout
|
|
319
|
+
self._http_headers = {**HTTP_HEADERS, **(http_headers or {})}
|
|
320
|
+
self._allowed_protocols = allowed_protocols
|
|
321
|
+
self._fail_on_errors = fail_on_errors
|
|
322
|
+
|
|
323
|
+
def fetch(self, url, headers=None):
|
|
324
|
+
"""Fetch a given URL.
|
|
325
|
+
|
|
326
|
+
:returns: A :obj:`URLFetcherResponse` instance.
|
|
327
|
+
:raises: An exception indicating failure, e.g. :obj:`ValueError` on
|
|
328
|
+
syntactically invalid URL. All exceptions are catched internally by
|
|
329
|
+
WeasyPrint, except when they inherit from :obj:`FatalURLFetchingError`.
|
|
330
|
+
|
|
331
|
+
"""
|
|
332
|
+
# Discard URLs with no or invalid protocol.
|
|
333
|
+
if not (match := UNICODE_SCHEME_RE.match(url)): # pragma: no cover
|
|
334
|
+
raise ValueError(f'Not an absolute URI: {url}')
|
|
335
|
+
scheme = match[1].lower()
|
|
336
|
+
|
|
337
|
+
# Discard URLs with forbidden protocol.
|
|
338
|
+
if self._allowed_protocols is not None:
|
|
339
|
+
if scheme not in self._allowed_protocols:
|
|
237
340
|
raise ValueError(f'URI uses disallowed protocol: {url}')
|
|
238
341
|
|
|
239
|
-
#
|
|
240
|
-
|
|
342
|
+
# Remove query and fragment parts from file URLs.
|
|
343
|
+
# See https://bugs.python.org/issue34702.
|
|
344
|
+
if scheme == 'file':
|
|
241
345
|
url = url.split('?')[0]
|
|
242
|
-
path = url2pathname(url.removeprefix('file:'))
|
|
243
|
-
else:
|
|
244
|
-
path = None
|
|
245
346
|
|
|
347
|
+
# Transform Unicode IRI to ASCII URI.
|
|
246
348
|
url = iri_to_uri(url)
|
|
247
|
-
|
|
248
|
-
|
|
349
|
+
|
|
350
|
+
# Open URL.
|
|
351
|
+
headers = {**self._http_headers, **(headers or {})}
|
|
352
|
+
http_request = request.Request(url, headers=headers)
|
|
353
|
+
response = super().open(http_request, timeout=self._timeout)
|
|
354
|
+
|
|
355
|
+
# Decompress response.
|
|
356
|
+
body = response
|
|
357
|
+
if 'Content-Encoding' in response.headers:
|
|
358
|
+
content_encoding = response.headers['Content-Encoding']
|
|
359
|
+
del response.headers['Content-Encoding']
|
|
360
|
+
if content_encoding == 'gzip':
|
|
361
|
+
body = StreamingGzipFile(fileobj=response)
|
|
362
|
+
elif content_encoding == 'deflate':
|
|
363
|
+
data = response.read()
|
|
364
|
+
try:
|
|
365
|
+
body = zlib.decompress(data)
|
|
366
|
+
except zlib.error:
|
|
367
|
+
# Try without zlib header or checksum.
|
|
368
|
+
body = zlib.decompress(data, -15)
|
|
369
|
+
|
|
370
|
+
return URLFetcherResponse(response.url, body, response.headers, response.status)
|
|
371
|
+
|
|
372
|
+
def open(self, url, data=None, timeout=None):
|
|
373
|
+
if isinstance(url, request.Request):
|
|
374
|
+
return self.fetch(url.full_url, url.headers)
|
|
375
|
+
return self.fetch(url)
|
|
376
|
+
|
|
377
|
+
def __call__(self, url):
|
|
378
|
+
return self.fetch(url)
|
|
379
|
+
|
|
380
|
+
|
|
381
|
+
class URLFetcherResponse:
|
|
382
|
+
"""The HTTP response of an URL fetcher.
|
|
383
|
+
|
|
384
|
+
:param str url: The URL of the HTTP response.
|
|
385
|
+
:type body: :class:`str`, :class:`bytes` or :term:`file object`
|
|
386
|
+
:param body: The body of the HTTP response.
|
|
387
|
+
:type headers: dict or email.message.EmailMessage
|
|
388
|
+
:param headers: The headers of the HTTP response.
|
|
389
|
+
:param int status: The status of the HTTP response.
|
|
390
|
+
|
|
391
|
+
Has the same interface as :class:`urllib.response.addinfourl`.
|
|
392
|
+
|
|
393
|
+
If a :term:`file object` is given for the body, it is the caller’s responsibility to
|
|
394
|
+
call ``close()`` on it. The default function used internally to fetch data in
|
|
395
|
+
WeasyPrint tries to close the file object after retreiving; but if this URL fetcher
|
|
396
|
+
is used elsewhere, the file object has to be closed manually.
|
|
397
|
+
|
|
398
|
+
"""
|
|
399
|
+
def __init__(self, url, body=None, headers=None, status=200, **kwargs):
|
|
400
|
+
self.url = url
|
|
401
|
+
self.status = status
|
|
402
|
+
|
|
403
|
+
if isinstance(headers, EmailMessage):
|
|
404
|
+
self.headers = headers
|
|
249
405
|
else:
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
if content_encoding == 'gzip':
|
|
263
|
-
result['file_obj'] = StreamingGzipFile(fileobj=response)
|
|
264
|
-
elif content_encoding == 'deflate':
|
|
265
|
-
data = response.read()
|
|
266
|
-
try:
|
|
267
|
-
result['string'] = zlib.decompress(data)
|
|
268
|
-
except zlib.error:
|
|
269
|
-
# Try without zlib header or checksum
|
|
270
|
-
result['string'] = zlib.decompress(data, -15)
|
|
406
|
+
self.headers = EmailMessage()
|
|
407
|
+
for key, value in (headers or {}).items():
|
|
408
|
+
try:
|
|
409
|
+
self.headers[key] = value
|
|
410
|
+
except ValueError:
|
|
411
|
+
pass # Ignore forbidden duplicated headers.
|
|
412
|
+
|
|
413
|
+
if hasattr(body, 'read'):
|
|
414
|
+
self._file_obj = body
|
|
415
|
+
elif isinstance(body, str):
|
|
416
|
+
self.headers.set_param('charset', 'utf-8')
|
|
417
|
+
self._file_obj = BytesIO(body.encode('utf-8'))
|
|
271
418
|
else:
|
|
272
|
-
|
|
273
|
-
return result
|
|
274
|
-
else: # pragma: no cover
|
|
275
|
-
raise ValueError(f'Not an absolute URI: {url}')
|
|
419
|
+
self._file_obj = BytesIO(body)
|
|
276
420
|
|
|
421
|
+
def read(self, *args, **kwargs):
|
|
422
|
+
return self._file_obj.read(*args, **kwargs)
|
|
277
423
|
|
|
278
|
-
|
|
279
|
-
|
|
424
|
+
def close(self):
|
|
425
|
+
try:
|
|
426
|
+
self._file_obj.close()
|
|
427
|
+
except Exception: # pragma: no cover
|
|
428
|
+
# May already be closed or something.
|
|
429
|
+
# This is just cleanup anyway: log but make it non-fatal.
|
|
430
|
+
LOGGER.warning(
|
|
431
|
+
'Error when closing stream for %s:\n%s',
|
|
432
|
+
self.url, traceback.format_exc())
|
|
433
|
+
|
|
434
|
+
@property
|
|
435
|
+
def path(self):
|
|
436
|
+
if self.url.startswith('file:'):
|
|
437
|
+
return request.url2pathname(self.url.split('?')[0].removeprefix('file:'))
|
|
438
|
+
|
|
439
|
+
@property
|
|
440
|
+
def content_type(self):
|
|
441
|
+
return self.headers.get_content_type()
|
|
442
|
+
|
|
443
|
+
@property
|
|
444
|
+
def charset(self):
|
|
445
|
+
return self.headers.get_param('charset')
|
|
446
|
+
|
|
447
|
+
def geturl(self):
|
|
448
|
+
return self.url
|
|
449
|
+
|
|
450
|
+
def info(self):
|
|
451
|
+
return self.headers
|
|
452
|
+
|
|
453
|
+
@property
|
|
454
|
+
def code(self):
|
|
455
|
+
return self.status
|
|
456
|
+
|
|
457
|
+
def getcode(self):
|
|
458
|
+
return self.status
|
|
280
459
|
|
|
281
460
|
|
|
282
461
|
@contextlib.contextmanager
|
|
283
462
|
def fetch(url_fetcher, url):
|
|
284
|
-
"""
|
|
463
|
+
"""Fetch an ``url`` with ```url_fetcher``, fill in optional data, and clean up.
|
|
464
|
+
|
|
465
|
+
Fatal errors must raise a ``FatalURLFetchingError`` that stops the rendering. All
|
|
466
|
+
other exceptions are catched and raise an ``URLFetchingError``, that is usually
|
|
467
|
+
catched by the code that fetches the resource and emits a warning.
|
|
468
|
+
|
|
469
|
+
"""
|
|
285
470
|
try:
|
|
286
|
-
|
|
471
|
+
resource = url_fetcher(url)
|
|
287
472
|
except Exception as exception:
|
|
473
|
+
if getattr(url_fetcher, '_fail_on_errors', False):
|
|
474
|
+
raise FatalURLFetchingError(f'Error fetching "{url}"') from exception
|
|
288
475
|
raise URLFetchingError(f'{type(exception).__name__}: {exception}')
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
476
|
+
|
|
477
|
+
if isinstance(resource, dict):
|
|
478
|
+
warnings.warn(
|
|
479
|
+
"Returning dicts in URL fetchers is deprecated and will be removed "
|
|
480
|
+
"in WeasyPrint 69.0, please return URLFetcherResponse instead.",
|
|
481
|
+
category=DeprecationWarning)
|
|
482
|
+
if 'url' not in resource:
|
|
483
|
+
resource['url'] = resource.get('redirected_url', url)
|
|
484
|
+
resource['body'] = resource.get('file_obj', resource.get('string'))
|
|
485
|
+
content_type = resource.get('mime_type', 'application/octet-stream')
|
|
486
|
+
if charset := resource.get('encoding'):
|
|
487
|
+
content_type += f'; charset={charset}'
|
|
488
|
+
resource['headers'] = {'Content-Type': content_type}
|
|
489
|
+
resource = URLFetcherResponse(**resource)
|
|
490
|
+
|
|
491
|
+
assert isinstance(resource, URLFetcherResponse), (
|
|
492
|
+
'URL fetcher must return either a dict or a URLFetcherResponse instance')
|
|
493
|
+
|
|
494
|
+
try:
|
|
495
|
+
yield resource
|
|
496
|
+
finally:
|
|
497
|
+
resource.close()
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: weasyprint
|
|
3
|
-
Version:
|
|
3
|
+
Version: 68.1
|
|
4
4
|
Summary: The Awesome Document Factory
|
|
5
5
|
Keywords: html,css,pdf,converter
|
|
6
6
|
Author-email: Simon Sapin <simon.sapin@exyr.org>
|
|
@@ -37,6 +37,7 @@ Requires-Dist: sphinx ; extra == "doc"
|
|
|
37
37
|
Requires-Dist: furo ; extra == "doc"
|
|
38
38
|
Requires-Dist: pytest ; extra == "test"
|
|
39
39
|
Requires-Dist: ruff ; extra == "test"
|
|
40
|
+
Requires-Dist: Pillow >=12.1.0 ; extra == "test"
|
|
40
41
|
Project-URL: Changelog, https://github.com/Kozea/WeasyPrint/releases
|
|
41
42
|
Project-URL: Code, https://github.com/Kozea/WeasyPrint
|
|
42
43
|
Project-URL: Documentation, https://doc.courtbouillon.org/weasyprint/
|
|
@@ -1,77 +1,77 @@
|
|
|
1
|
-
weasyprint/__init__.py,sha256=
|
|
2
|
-
weasyprint/__main__.py,sha256=
|
|
1
|
+
weasyprint/__init__.py,sha256=Z8RQi3arBn0J5Wlpm56inGnfXYuen9NRT2WyNqxS5q8,14949
|
|
2
|
+
weasyprint/__main__.py,sha256=L6pqmXo68pHGPkXth8obc9DlUHYdc3YXwDG5bCky4Pc,9374
|
|
3
3
|
weasyprint/anchors.py,sha256=fytc05l3TO4AWuyPJx532RBEbZe8L7UlnbYYJpB-bGA,6472
|
|
4
|
-
weasyprint/document.py,sha256=
|
|
4
|
+
weasyprint/document.py,sha256=W_pmP05WT_bucoU7eTwkYuYMGG6h-19ZpdVZZcbxpAc,13681
|
|
5
5
|
weasyprint/html.py,sha256=om7dvhx12ecunTaVclmWejkr5nPQF1BZmeH7jqQ_4GM,11590
|
|
6
|
-
weasyprint/images.py,sha256=
|
|
6
|
+
weasyprint/images.py,sha256=nh1RXq7-8M2dzuOX7gLRwmHDm7LVwulTGcgOsgB_Fr8,36372
|
|
7
7
|
weasyprint/logger.py,sha256=z1q548fX5shfAyLoMLeM9ozWGKgoBTKQsdlTtfRE_9U,1824
|
|
8
8
|
weasyprint/matrix.py,sha256=v1BPtyn_-S_4TrAUgzOOR-viUXgdqsABKRndCEprkPc,1909
|
|
9
9
|
weasyprint/stacking.py,sha256=6c6eZ_BxtcYvlEbH6JQdqaKwYBkuaqUwGGSs3OfkpS8,5697
|
|
10
|
-
weasyprint/urls.py,sha256=
|
|
11
|
-
weasyprint/css/__init__.py,sha256=
|
|
10
|
+
weasyprint/urls.py,sha256=6ZsVvV5TKHH1lyXvAkeV0qIgMavWKWKVjLvS7aMagRE,18287
|
|
11
|
+
weasyprint/css/__init__.py,sha256=rCwc157BawGI6j1z_ymYmjT-_-0jEUDVw1DYdJPW9OM,77514
|
|
12
12
|
weasyprint/css/computed_values.py,sha256=WPySY5DFXusCxa7wC5mUv4uzwBi2c7ACMLI3_iHqDnI,24187
|
|
13
13
|
weasyprint/css/counters.py,sha256=RAUuZGOLZj5oKrZexbkDUYPncTFWzwtT870xTKYkQmI,11374
|
|
14
|
-
weasyprint/css/functions.py,sha256=
|
|
14
|
+
weasyprint/css/functions.py,sha256=PXPZX0EJKI1FL_ydPGLueICMTHPxwiZwQr74vWpEaMY,6779
|
|
15
15
|
weasyprint/css/html5_ph.css,sha256=l8t4ZN3KoevKx0UEfNw3_vgVjArcII6y5DZXZolWaw0,4629
|
|
16
|
-
weasyprint/css/html5_ua.css,sha256=
|
|
16
|
+
weasyprint/css/html5_ua.css,sha256=0HGk0se0ej4-hEVK6u4E2JVW6u-x0ZE7K3gb0TgozmY,18399
|
|
17
17
|
weasyprint/css/html5_ua_form.css,sha256=O0L78oQN8ZuQcXlfe7XVTy5kM3nVF6-rfO3xALbYTy8,310
|
|
18
18
|
weasyprint/css/media_queries.py,sha256=Mh1JlSupMNjVJ4CKW3Yd7zR4W6NGNXp21y_4hajDDUY,1179
|
|
19
19
|
weasyprint/css/properties.py,sha256=64TpsS87--Xj_al7x0vREkbCDpyYQqHJx9H7vYxjKjY,11727
|
|
20
20
|
weasyprint/css/targets.py,sha256=5Ofw1RrmPsfQjDuZ1FCgktspGUai3wJmNa03MbT2sOI,8853
|
|
21
|
-
weasyprint/css/tokens.py,sha256=
|
|
21
|
+
weasyprint/css/tokens.py,sha256=r1A3lUA0xEQ8KnqILdhi_vCOZv_sQ-Y9ZdQRZcPH-JM,23546
|
|
22
22
|
weasyprint/css/units.py,sha256=loUL8k7y4OYxbNrowbUgsRuqKj-uFKfhZmKUjc6t3GQ,2901
|
|
23
23
|
weasyprint/css/validation/__init__.py,sha256=bt0Rqn5Jt2KLr6jFXau8QnosNIkz83DxSlca1t064hU,8469
|
|
24
24
|
weasyprint/css/validation/descriptors.py,sha256=7LN_0ed9wZWgYT3_4UrEE-R6mzpINGs-VM9lglV49P8,11839
|
|
25
25
|
weasyprint/css/validation/expanders.py,sha256=hqVJKiK5G-C8oNsCMTJ_X964qFip2j5hO4r912dnJX8,39233
|
|
26
|
-
weasyprint/css/validation/properties.py,sha256=
|
|
26
|
+
weasyprint/css/validation/properties.py,sha256=DYjW8TZl_y_cedVkleY6kTVw6G3nUlDspE0QMjqOsU4,66866
|
|
27
27
|
weasyprint/draw/__init__.py,sha256=l4q_L6k93tIobB8hHui_PSElstO5MI5FTHCBTISb_ew,22715
|
|
28
28
|
weasyprint/draw/border.py,sha256=5s-FZyw3MN085cmmAJHp8Yms5gM90j-XzInpwldgPFs,30373
|
|
29
29
|
weasyprint/draw/color.py,sha256=xoqq6LmkyN1xdubX0Qm-MKy7xijzT3Zd7kF2MSaqZiQ,1449
|
|
30
|
-
weasyprint/draw/text.py,sha256=
|
|
31
|
-
weasyprint/formatting_structure/boxes.py,sha256=
|
|
32
|
-
weasyprint/formatting_structure/build.py,sha256=
|
|
33
|
-
weasyprint/layout/__init__.py,sha256=
|
|
30
|
+
weasyprint/draw/text.py,sha256=vHzgHNGiTB8TGJw1GOOAVCzj4ZOnREQ2enbRJL0kVzM,12444
|
|
31
|
+
weasyprint/formatting_structure/boxes.py,sha256=QDX3FRmSY7pG6QjWTuhANlSMrePNRPm1iX5-CJ8tU98,27199
|
|
32
|
+
weasyprint/formatting_structure/build.py,sha256=j_pKsHxsNt-ErNj-TT12QczQa6RszPOWjDztqzH5yMo,58491
|
|
33
|
+
weasyprint/layout/__init__.py,sha256=mUlsoqBIamjdIqtINS9W0njkrgn8guyYORu3UCedon0,16767
|
|
34
34
|
weasyprint/layout/absolute.py,sha256=JA4mjOweZt2h-Hrsa91nHnvzZDnF-gneMpZfcXiTugc,14005
|
|
35
35
|
weasyprint/layout/background.py,sha256=IfcmSZ-E4_NES-RSeKi5DGbmrloGdu9zVz5zZ87m9gU,10023
|
|
36
|
-
weasyprint/layout/block.py,sha256=
|
|
36
|
+
weasyprint/layout/block.py,sha256=I5E7-aTPbkEqIHLcm8PJNXCfq32XbDmu6a9jv8czUBA,48990
|
|
37
37
|
weasyprint/layout/column.py,sha256=g64aPoNZYpPlrma4F1nyrW7_ji5DBYoamA92YrbFHZM,17209
|
|
38
38
|
weasyprint/layout/flex.py,sha256=KZNFElr2QDT5FYI6JlZGpXxJHYFvMMN8QGylLx98PUs,44148
|
|
39
39
|
weasyprint/layout/float.py,sha256=5iBDHg1KSexArW1SfbnMShuILzkkEH8DW7n1bdk5OU4,9488
|
|
40
|
-
weasyprint/layout/grid.py,sha256=
|
|
40
|
+
weasyprint/layout/grid.py,sha256=fqSt4_71FblPJxwG5irqvm3CKqbp1brNJWYNJ2WujJs,64522
|
|
41
41
|
weasyprint/layout/inline.py,sha256=Iw3h9_N6a0rly9VGZ35LD0Pp8RDDGK7cYVKjZy17BZk,50447
|
|
42
42
|
weasyprint/layout/leader.py,sha256=wklI0aLyTx0VJhqU7D_FxtJpfe7dXswcN-VApAusM-Q,2825
|
|
43
43
|
weasyprint/layout/min_max.py,sha256=JdXJG9ISO_RsfeHua_-3g477a16I-NrnYuwH_tQwq4o,1527
|
|
44
|
-
weasyprint/layout/page.py,sha256=
|
|
44
|
+
weasyprint/layout/page.py,sha256=m4z24NqI6bc08j1GwXsRCpyMkKxC93XE6--QMHUaKxM,41565
|
|
45
45
|
weasyprint/layout/percent.py,sha256=F2kNcNz7b6to70GwiFFu-oWWpYs011TM021COH7feEc,5829
|
|
46
|
-
weasyprint/layout/preferred.py,sha256=
|
|
46
|
+
weasyprint/layout/preferred.py,sha256=ZZAc7tbn5Rdl9DjgXgnT4J9sBBljNXvY5qj47HjUXGM,35288
|
|
47
47
|
weasyprint/layout/replaced.py,sha256=7-foaAPIAVC329QEnUN_1u2U6VTrEbQ0Qx254BlrLCo,11357
|
|
48
48
|
weasyprint/layout/table.py,sha256=KyFOg9aiMl_wO31_0kQ7WrCVeHtGJY0Ir-hdub2qSSg,47778
|
|
49
|
-
weasyprint/pdf/__init__.py,sha256=
|
|
50
|
-
weasyprint/pdf/anchors.py,sha256=
|
|
49
|
+
weasyprint/pdf/__init__.py,sha256=5OG1Tgdyk1XPhcren50OY2S9ZLT4SseIajlEhDl7PSw,14303
|
|
50
|
+
weasyprint/pdf/anchors.py,sha256=IG7mimMqf0j2lurfX5DF3UkUZ4HdMiTMqpupue6BEvI,17164
|
|
51
51
|
weasyprint/pdf/debug.py,sha256=reLw6U6hK94FOVNYW8psdt_SFN11iIe1rhYkr6sURF4,1407
|
|
52
|
-
weasyprint/pdf/fonts.py,sha256=
|
|
53
|
-
weasyprint/pdf/metadata.py,sha256=
|
|
54
|
-
weasyprint/pdf/pdfa.py,sha256=
|
|
55
|
-
weasyprint/pdf/pdfua.py,sha256=
|
|
56
|
-
weasyprint/pdf/pdfx.py,sha256=
|
|
52
|
+
weasyprint/pdf/fonts.py,sha256=UMGV2P4-y3mh1p3YFzVaoKekdx2B5nExsZvIBZg9kTM,28159
|
|
53
|
+
weasyprint/pdf/metadata.py,sha256=enBUUChb5vTHXZH7B2v9Cl_WO7hXL0TrS3XcfSv4x9M,9207
|
|
54
|
+
weasyprint/pdf/pdfa.py,sha256=JDKtJ3-v-i2CMWScTbesZi3YQ8C-5PYOheYAYM5Y1iw,4239
|
|
55
|
+
weasyprint/pdf/pdfua.py,sha256=RrV7qky5Djc_tP5ipdDUDC5BqOi6T_LDZpVy8cyXRGc,486
|
|
56
|
+
weasyprint/pdf/pdfx.py,sha256=1DB9afper1oZpmoktSxBvRTpUuxHj7TBy3dhXdD2qGc,2629
|
|
57
57
|
weasyprint/pdf/sRGB2014.icc,sha256=OEuDLeNBIGZ0O1KnXukGtvufuNngnpNvwsQyI4Fcbgo,3024
|
|
58
58
|
weasyprint/pdf/stream.py,sha256=7L7EcGhxwDp06VSqgVWVP2ZgpIdJ35LPLE6IAN4sQzQ,11615
|
|
59
59
|
weasyprint/pdf/tags.py,sha256=914AozTYgFOMpFSP2UtEZCVzsPIKueHWjI9DA77w28M,11789
|
|
60
|
-
weasyprint/svg/__init__.py,sha256=
|
|
60
|
+
weasyprint/svg/__init__.py,sha256=96DOr-LUCMiEOL81QbgpnPL96Bw9ItPDMb9AUvxygJU,32181
|
|
61
61
|
weasyprint/svg/bounding_box.py,sha256=auXs-vD2nvOx3cplHLGXFzy7X_f_IY4hg_IzKlUTXjM,13129
|
|
62
|
-
weasyprint/svg/css.py,sha256=
|
|
63
|
-
weasyprint/svg/defs.py,sha256=
|
|
62
|
+
weasyprint/svg/css.py,sha256=MohmaDCX6oDIIxJPrn6bn1fKq4la0oPfxvMYrfL5vbI,4871
|
|
63
|
+
weasyprint/svg/defs.py,sha256=kkuNKYp9ymb1hK-w45_ClV9iGWULyEGmXhazmAUnXF8,20774
|
|
64
64
|
weasyprint/svg/images.py,sha256=msVOn7_DgKFVB6Pz25SDLXU22-p7H5y0fJGKBpFDjrc,3333
|
|
65
65
|
weasyprint/svg/path.py,sha256=Z-T6kbUU3pyHhzVV0JSBgO--XaCGXLsH-cS9iAsITMM,10064
|
|
66
66
|
weasyprint/svg/shapes.py,sha256=NDo0KMnwrm0hj3BOmfrKjRZo4iJF9o-MeUhZ5avANco,3845
|
|
67
|
-
weasyprint/svg/text.py,sha256=
|
|
67
|
+
weasyprint/svg/text.py,sha256=RST3ss4HEGxrq87lX6z67T9K55nL5qBntz0dDqH0bfs,6772
|
|
68
68
|
weasyprint/svg/utils.py,sha256=BEJvyOtxo4tAgLL-RORaEMIBAgmIZzwBNp2YuN1u3NM,7289
|
|
69
69
|
weasyprint/text/constants.py,sha256=gtC92Hbzci0976gVTct3roVKLcQjNWIQM43zuEBqIuY,14189
|
|
70
70
|
weasyprint/text/ffi.py,sha256=13hLmV19RiTspKHna_RlZMar6CpTIHwPDPSGV1FmhNc,18418
|
|
71
|
-
weasyprint/text/fonts.py,sha256=
|
|
72
|
-
weasyprint/text/line_break.py,sha256=
|
|
73
|
-
weasyprint-
|
|
74
|
-
weasyprint-
|
|
75
|
-
weasyprint-
|
|
76
|
-
weasyprint-
|
|
77
|
-
weasyprint-
|
|
71
|
+
weasyprint/text/fonts.py,sha256=P2Zi7cHwoR4WZsOfm3Ki0lQ3aRYu6YWyFsKvvd0DEqE,17568
|
|
72
|
+
weasyprint/text/line_break.py,sha256=02vHDXwy_yuWSlG19CxDi-sC3rnh8wj6SBY63sskuTM,28091
|
|
73
|
+
weasyprint-68.1.dist-info/entry_points.txt,sha256=wgDp3XXzFywdYgI5vUWMp1zAwx1sZXXH0FTUQbFOq6A,55
|
|
74
|
+
weasyprint-68.1.dist-info/licenses/LICENSE,sha256=v9FOzPphAFdUYOaFVWsYM5nUvTNZBOPJUhsBFtIcVNo,1534
|
|
75
|
+
weasyprint-68.1.dist-info/WHEEL,sha256=G2gURzTEtmeR8nrdXUJfNiB3VYVxigPQ-bEQujpNiNs,82
|
|
76
|
+
weasyprint-68.1.dist-info/METADATA,sha256=QsG5YIcitase__6kIbSxKZ2KsJYdm-LaIxguuOv9BVE,3709
|
|
77
|
+
weasyprint-68.1.dist-info/RECORD,,
|
|
File without changes
|
|
File without changes
|
|
File without changes
|