mirror of
https://github.com/Kozea/WeasyPrint.git
synced 2024-10-05 16:37:47 +03:00
Use ruff to replace flake8 and isort
This commit is contained in:
parent
f59974298d
commit
29597ab893
4
.github/workflows/tests.yml
vendored
4
.github/workflows/tests.yml
vendored
@ -44,6 +44,4 @@ jobs:
|
||||
env:
|
||||
DYLD_FALLBACK_LIBRARY_PATH: /opt/homebrew/lib
|
||||
- name: Check coding style
|
||||
run: python -m flake8
|
||||
- name: Check imports order
|
||||
run: python -m isort . --check --diff
|
||||
run: python -m ruff check
|
||||
|
@ -51,17 +51,14 @@ You can launch tests using the following command::
|
||||
|
||||
venv/bin/python -m pytest
|
||||
|
||||
WeasyPrint also uses isort_ to check imports and flake8_ to check the coding
|
||||
style::
|
||||
WeasyPrint also uses ruff_ to check the coding style::
|
||||
|
||||
venv/bin/python -m isort . --check --diff
|
||||
venv/bin/python -m flake8
|
||||
venv/bin/python -m ruff check
|
||||
|
||||
.. _pytest: https://docs.pytest.org/
|
||||
.. _Ghostscript: https://www.ghostscript.com/
|
||||
.. _DejaVu fonts: https://dejavu-fonts.github.io/
|
||||
.. _isort: https://pycqa.github.io/isort/
|
||||
.. _flake8: https://flake8.pycqa.org/
|
||||
.. _ruff: https://docs.astral.sh/ruff/
|
||||
|
||||
|
||||
Documentation
|
||||
|
@ -52,7 +52,7 @@ Donation = 'https://opencollective.com/courtbouillon'
|
||||
|
||||
[project.optional-dependencies]
|
||||
doc = ['sphinx', 'sphinx_rtd_theme']
|
||||
test = ['pytest', 'isort', 'flake8']
|
||||
test = ['pytest', 'ruff']
|
||||
|
||||
[project.scripts]
|
||||
weasyprint = 'weasyprint.__main__:main'
|
||||
@ -68,6 +68,6 @@ include = ['tests/*', 'weasyprint/*']
|
||||
exclude_lines = ['pragma: no cover', 'def __repr__', 'raise NotImplementedError']
|
||||
omit = ['.*']
|
||||
|
||||
[tool.isort]
|
||||
default_section = 'FIRSTPARTY'
|
||||
multi_line_output = 4
|
||||
[tool.ruff.lint]
|
||||
select = ['E', 'W', 'F', 'I', 'N', 'RUF']
|
||||
ignore = ['RUF001', 'RUF002', 'RUF003']
|
||||
|
@ -14,6 +14,7 @@ from tempfile import NamedTemporaryFile
|
||||
|
||||
import pytest
|
||||
from PIL import Image
|
||||
|
||||
from weasyprint import HTML
|
||||
from weasyprint.document import Document
|
||||
|
||||
|
@ -1,12 +1,12 @@
|
||||
"""Test the CSS parsing, cascade, inherited and computed values."""
|
||||
|
||||
|
||||
import pytest
|
||||
|
||||
from weasyprint import CSS, default_url_fetcher
|
||||
from weasyprint.css import find_stylesheets, get_all_computed_styles
|
||||
from weasyprint.urls import path2url
|
||||
|
||||
from ..testing_utils import (
|
||||
from ..testing_utils import ( # isort:skip
|
||||
BASE_URL, FakeHTML, assert_no_logs, capture_logs, resource_path)
|
||||
|
||||
|
||||
|
@ -2,7 +2,7 @@
|
||||
|
||||
import pytest
|
||||
|
||||
from ..testing_utils import (
|
||||
from ..testing_utils import ( # isort:skip
|
||||
FakeHTML, assert_no_logs, assert_tree, parse_all, render_pages)
|
||||
|
||||
RENDER = FakeHTML(string='')._ua_counter_style()[0].render_value
|
||||
|
@ -2,6 +2,7 @@
|
||||
|
||||
import pytest
|
||||
import tinycss2
|
||||
|
||||
from weasyprint.css import preprocess_stylesheet
|
||||
from weasyprint.css.validation.descriptors import preprocess_descriptors
|
||||
|
||||
|
@ -1,6 +1,7 @@
|
||||
"""Test CSS errors and warnings."""
|
||||
|
||||
import pytest
|
||||
|
||||
from weasyprint import CSS
|
||||
|
||||
from ..testing_utils import assert_no_logs, capture_logs, render_pages
|
||||
|
@ -3,6 +3,7 @@
|
||||
import pytest
|
||||
import tinycss2
|
||||
from tinycss2.color3 import parse_color
|
||||
|
||||
from weasyprint.css import preprocess_declarations
|
||||
from weasyprint.css.properties import INITIAL_VALUES, ZERO_PIXELS
|
||||
from weasyprint.css.validation.expanders import EXPANDERS
|
||||
|
@ -3,6 +3,7 @@
|
||||
from math import isclose
|
||||
|
||||
import pytest
|
||||
|
||||
from weasyprint import CSS
|
||||
from weasyprint.css import get_all_computed_styles
|
||||
from weasyprint.css.computed_values import strut_layout
|
||||
|
@ -2,13 +2,12 @@
|
||||
|
||||
import pytest
|
||||
import tinycss2
|
||||
|
||||
from weasyprint import CSS
|
||||
from weasyprint.css import (
|
||||
PageType, get_all_computed_styles, parse_page_selectors)
|
||||
from weasyprint.css import PageType, get_all_computed_styles, parse_page_selectors
|
||||
from weasyprint.layout.page import set_page_type_computed_styles
|
||||
|
||||
from ..testing_utils import (
|
||||
FakeHTML, assert_no_logs, render_pages, resource_path)
|
||||
from ..testing_utils import FakeHTML, assert_no_logs, render_pages, resource_path
|
||||
|
||||
|
||||
@assert_no_logs
|
||||
|
@ -4,6 +4,7 @@ from math import pi
|
||||
|
||||
import pytest
|
||||
import tinycss2
|
||||
|
||||
from weasyprint.css import preprocess_declarations
|
||||
from weasyprint.css.validation.properties import PROPERTIES
|
||||
from weasyprint.images import LinearGradient, RadialGradient
|
||||
@ -249,19 +250,21 @@ def test_background_image_invalid(rule):
|
||||
('bottom 3px left 10%', (('left', (10, '%'), 'bottom', (3, 'px')),)),
|
||||
('right 10% top 3px', (('right', (10, '%'), 'top', (3, 'px')),)),
|
||||
('top 3px right 10%', (('right', (10, '%'), 'top', (3, 'px')),)),
|
||||
) + tuple(
|
||||
(css_x, (('left', val_x, 'top', (50, '%')),))
|
||||
for css_x, val_x in (
|
||||
('left', (0, '%')), ('center', (50, '%')), ('right', (100, '%')),
|
||||
('4.5%', (4.5, '%')), ('12px', (12, 'px')))
|
||||
) + tuple(
|
||||
(f'{css_x} {css_y}', (('left', val_x, 'top', val_y),))
|
||||
for css_x, val_x in (
|
||||
('left', (0, '%')), ('center', (50, '%')), ('right', (100, '%')),
|
||||
('4.5%', (4.5, '%')), ('12px', (12, 'px')))
|
||||
for css_y, val_y in (
|
||||
('top', (0, '%')), ('center', (50, '%')), ('bottom', (100, '%')),
|
||||
('7%', (7, '%')), ('1.5px', (1.5, 'px')))
|
||||
*tuple(
|
||||
(css_x, (('left', val_x, 'top', (50, '%')),))
|
||||
for css_x, val_x in (
|
||||
('left', (0, '%')), ('center', (50, '%')), ('right', (100, '%')),
|
||||
('4.5%', (4.5, '%')), ('12px', (12, 'px')))
|
||||
),
|
||||
*tuple(
|
||||
(f'{css_x} {css_y}', (('left', val_x, 'top', val_y),))
|
||||
for css_x, val_x in (
|
||||
('left', (0, '%')), ('center', (50, '%')), ('right', (100, '%')),
|
||||
('4.5%', (4.5, '%')), ('12px', (12, 'px')))
|
||||
for css_y, val_y in (
|
||||
('top', (0, '%')), ('center', (50, '%')), ('bottom', (100, '%')),
|
||||
('7%', (7, '%')), ('1.5px', (1.5, 'px')))
|
||||
),
|
||||
))
|
||||
def test_background_position(rule, value):
|
||||
assert get_value(f'background-position: {rule}') == value
|
||||
|
@ -1,6 +1,7 @@
|
||||
"""Test CSS custom properties, also known as CSS variables."""
|
||||
|
||||
import pytest
|
||||
|
||||
from weasyprint.css.properties import KNOWN_PROPERTIES
|
||||
|
||||
from ..testing_utils import assert_no_logs, capture_logs, render_pages
|
||||
|
@ -1,6 +1,7 @@
|
||||
"""Test how images are drawn in SVG."""
|
||||
|
||||
import pytest
|
||||
|
||||
from weasyprint.urls import path2url
|
||||
|
||||
from ...testing_utils import assert_no_logs, resource_path
|
||||
|
@ -4,7 +4,7 @@ from ...testing_utils import assert_no_logs
|
||||
|
||||
|
||||
@assert_no_logs
|
||||
def test_path_Hh(assert_pixels):
|
||||
def test_path_h(assert_pixels):
|
||||
assert_pixels('''
|
||||
BBBBBBBB__
|
||||
BBBBBBBB__
|
||||
@ -35,7 +35,7 @@ def test_path_Hh(assert_pixels):
|
||||
|
||||
|
||||
@assert_no_logs
|
||||
def test_path_Vv(assert_pixels):
|
||||
def test_path_v(assert_pixels):
|
||||
assert_pixels('''
|
||||
BB____GG__
|
||||
BB____GG__
|
||||
@ -66,7 +66,7 @@ def test_path_Vv(assert_pixels):
|
||||
|
||||
|
||||
@assert_no_logs
|
||||
def test_path_Ll(assert_pixels):
|
||||
def test_path_l(assert_pixels):
|
||||
assert_pixels('''
|
||||
______RR__
|
||||
______RR__
|
||||
@ -93,7 +93,7 @@ def test_path_Ll(assert_pixels):
|
||||
|
||||
|
||||
@assert_no_logs
|
||||
def test_path_Zz(assert_pixels):
|
||||
def test_path_z(assert_pixels):
|
||||
assert_pixels('''
|
||||
BBBBBBB___
|
||||
BBBBBBB___
|
||||
@ -120,7 +120,7 @@ def test_path_Zz(assert_pixels):
|
||||
|
||||
|
||||
@assert_no_logs
|
||||
def test_path_Zz_fill(assert_pixels):
|
||||
def test_path_z_fill(assert_pixels):
|
||||
assert_pixels('''
|
||||
BBBBBBB___
|
||||
BBBBBBB___
|
||||
@ -147,7 +147,7 @@ def test_path_Zz_fill(assert_pixels):
|
||||
|
||||
|
||||
@assert_no_logs
|
||||
def test_path_Cc(assert_pixels):
|
||||
def test_path_c(assert_pixels):
|
||||
assert_pixels('''
|
||||
__________
|
||||
__________
|
||||
@ -174,7 +174,7 @@ def test_path_Cc(assert_pixels):
|
||||
|
||||
|
||||
@assert_no_logs
|
||||
def test_path_Ss(assert_pixels):
|
||||
def test_path_s(assert_pixels):
|
||||
assert_pixels('''
|
||||
__________
|
||||
__________
|
||||
@ -201,7 +201,7 @@ def test_path_Ss(assert_pixels):
|
||||
|
||||
|
||||
@assert_no_logs
|
||||
def test_path_CcSs(assert_pixels):
|
||||
def test_path_cs(assert_pixels):
|
||||
assert_pixels('''
|
||||
__BBBBBB__
|
||||
__BBBBBBB_
|
||||
@ -234,7 +234,7 @@ def test_path_CcSs(assert_pixels):
|
||||
|
||||
|
||||
@assert_no_logs
|
||||
def test_path_Qq(assert_pixels):
|
||||
def test_path_q(assert_pixels):
|
||||
assert_pixels('''
|
||||
__________
|
||||
__________
|
||||
@ -261,7 +261,7 @@ def test_path_Qq(assert_pixels):
|
||||
|
||||
|
||||
@assert_no_logs
|
||||
def test_path_Tt(assert_pixels):
|
||||
def test_path_t(assert_pixels):
|
||||
assert_pixels('''
|
||||
__________
|
||||
__________
|
||||
@ -288,7 +288,7 @@ def test_path_Tt(assert_pixels):
|
||||
|
||||
|
||||
@assert_no_logs
|
||||
def test_path_QqTt(assert_pixels):
|
||||
def test_path_qt(assert_pixels):
|
||||
assert_pixels('''
|
||||
_BBBB_______
|
||||
BBBBBBB_____
|
||||
@ -317,7 +317,7 @@ def test_path_QqTt(assert_pixels):
|
||||
|
||||
|
||||
@assert_no_logs
|
||||
def test_path_QqTt2(assert_pixels):
|
||||
def test_path_qt2(assert_pixels):
|
||||
assert_pixels('''
|
||||
_BBBB_______
|
||||
BBBBBBB_____
|
||||
@ -346,7 +346,7 @@ def test_path_QqTt2(assert_pixels):
|
||||
|
||||
|
||||
@assert_no_logs
|
||||
def test_path_Aa(assert_pixels):
|
||||
def test_path_a(assert_pixels):
|
||||
assert_pixels('''
|
||||
__BBBB______
|
||||
_BBBBB______
|
||||
@ -375,7 +375,7 @@ def test_path_Aa(assert_pixels):
|
||||
|
||||
|
||||
@assert_no_logs
|
||||
def test_path_Aa2(assert_pixels):
|
||||
def test_path_a2(assert_pixels):
|
||||
assert_pixels('''
|
||||
______GGGG__
|
||||
______GGGGG_
|
||||
@ -402,7 +402,7 @@ def test_path_Aa2(assert_pixels):
|
||||
|
||||
|
||||
@assert_no_logs
|
||||
def test_path_Aa3(assert_pixels):
|
||||
def test_path_a3(assert_pixels):
|
||||
assert_pixels('''
|
||||
______GGGG__
|
||||
______GGGGG_
|
||||
@ -429,7 +429,7 @@ def test_path_Aa3(assert_pixels):
|
||||
|
||||
|
||||
@assert_no_logs
|
||||
def test_path_Aa4(assert_pixels):
|
||||
def test_path_a4(assert_pixels):
|
||||
assert_pixels('''
|
||||
____________
|
||||
____BBB_____
|
||||
@ -458,7 +458,7 @@ def test_path_Aa4(assert_pixels):
|
||||
|
||||
|
||||
@assert_no_logs
|
||||
def test_path_Aa5(assert_pixels):
|
||||
def test_path_a5(assert_pixels):
|
||||
assert_pixels('''
|
||||
__BBBBBBBB__
|
||||
_BBBBBBBBBB_
|
||||
@ -485,7 +485,7 @@ def test_path_Aa5(assert_pixels):
|
||||
|
||||
|
||||
@assert_no_logs
|
||||
def test_path_Aa6(assert_pixels):
|
||||
def test_path_a6(assert_pixels):
|
||||
assert_pixels('''
|
||||
__BBBBBBBB__
|
||||
_BBBBBBBBBB_
|
||||
@ -512,7 +512,7 @@ def test_path_Aa6(assert_pixels):
|
||||
|
||||
|
||||
@assert_no_logs
|
||||
def test_path_Aa7(assert_pixels):
|
||||
def test_path_a7(assert_pixels):
|
||||
assert_pixels('''
|
||||
____________
|
||||
____________
|
||||
|
@ -3,6 +3,7 @@
|
||||
import itertools
|
||||
|
||||
import pytest
|
||||
|
||||
from weasyprint import HTML
|
||||
|
||||
from ..testing_utils import assert_no_logs
|
||||
|
@ -2,8 +2,7 @@
|
||||
|
||||
import pytest
|
||||
|
||||
from ..testing_utils import (
|
||||
FakeHTML, assert_no_logs, capture_logs, resource_path)
|
||||
from ..testing_utils import FakeHTML, assert_no_logs, capture_logs, resource_path
|
||||
|
||||
centered_image = '''
|
||||
________
|
||||
|
@ -1,6 +1,7 @@
|
||||
"""Tests for blocks layout."""
|
||||
|
||||
import pytest
|
||||
|
||||
from weasyprint.formatting_structure import boxes
|
||||
|
||||
from ..testing_utils import assert_no_logs, render_pages
|
||||
|
@ -1,6 +1,7 @@
|
||||
"""Tests for floating boxes layout."""
|
||||
|
||||
import pytest
|
||||
|
||||
from weasyprint.formatting_structure import boxes
|
||||
|
||||
from ..testing_utils import assert_no_logs, render_pages
|
||||
|
@ -1,6 +1,7 @@
|
||||
"""Tests for images layout."""
|
||||
|
||||
import pytest
|
||||
|
||||
from weasyprint.formatting_structure import boxes
|
||||
|
||||
from ..testing_utils import assert_no_logs, capture_logs, render_pages
|
||||
|
@ -1,6 +1,7 @@
|
||||
"""Tests for inlines layout."""
|
||||
|
||||
import pytest
|
||||
|
||||
from weasyprint.formatting_structure import boxes
|
||||
|
||||
from ..testing_utils import SANS_FONTS, assert_no_logs, render_pages
|
||||
|
@ -1,6 +1,7 @@
|
||||
"""Tests for pages layout."""
|
||||
|
||||
import pytest
|
||||
|
||||
from weasyprint.formatting_structure import boxes
|
||||
|
||||
from ..testing_utils import assert_no_logs, render_pages
|
||||
|
@ -1,11 +1,11 @@
|
||||
"""Tests for layout of tables."""
|
||||
|
||||
import pytest
|
||||
|
||||
from weasyprint.formatting_structure import boxes
|
||||
from weasyprint.layout.table import collapse_table_borders
|
||||
|
||||
from ..testing_utils import (
|
||||
assert_no_logs, capture_logs, parse_all, render_pages)
|
||||
from ..testing_utils import assert_no_logs, capture_logs, parse_all, render_pages
|
||||
|
||||
|
||||
def _get_grid(html, grid_width, grid_height):
|
||||
@ -1961,7 +1961,7 @@ def test_table_vertical_align(assert_pixels):
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
||||
''') # noqa
|
||||
''')
|
||||
|
||||
|
||||
@assert_no_logs
|
||||
|
@ -3,6 +3,7 @@
|
||||
import io
|
||||
|
||||
from PIL import Image
|
||||
|
||||
from weasyprint import CSS, HTML
|
||||
|
||||
from .testing_utils import assert_no_logs, capture_logs, resource_path
|
||||
|
@ -1,10 +1,13 @@
|
||||
"""Test the public API."""
|
||||
|
||||
import contextlib
|
||||
import gzip
|
||||
import io
|
||||
import os
|
||||
import sys
|
||||
import threading
|
||||
import unicodedata
|
||||
import wsgiref.simple_server
|
||||
import zlib
|
||||
from functools import partial
|
||||
from pathlib import Path
|
||||
@ -12,13 +15,13 @@ from urllib.parse import urljoin, uses_relative
|
||||
|
||||
import pytest
|
||||
from PIL import Image
|
||||
|
||||
from weasyprint import CSS, HTML, __main__, default_url_fetcher
|
||||
from weasyprint.pdf.anchors import resolve_links
|
||||
from weasyprint.urls import path2url
|
||||
|
||||
from .draw import parse_pixels
|
||||
from .testing_utils import (
|
||||
FakeHTML, assert_no_logs, capture_logs, http_server, resource_path)
|
||||
from .testing_utils import FakeHTML, assert_no_logs, capture_logs, resource_path
|
||||
|
||||
try:
|
||||
# Available in Python 3.11+
|
||||
@ -27,7 +30,7 @@ except ImportError:
|
||||
# Backported from Python 3.11
|
||||
from contextlib import AbstractContextManager
|
||||
|
||||
class chdir(AbstractContextManager):
|
||||
class chdir(AbstractContextManager): # noqa: N801
|
||||
def __init__(self, path):
|
||||
self.path = path
|
||||
self._old_cwd = []
|
||||
@ -99,12 +102,12 @@ def _check_doc1(html, has_base_url=True):
|
||||
def _run(args, stdin=b''):
|
||||
stdin = io.BytesIO(stdin)
|
||||
stdout = io.BytesIO()
|
||||
HTML = partial(FakeHTML, force_uncompressed_pdf=False)
|
||||
HTML = partial(FakeHTML, force_uncompressed_pdf=False) # noqa: N806
|
||||
__main__.main(args.split(), stdin=stdin, stdout=stdout, HTML=HTML)
|
||||
return stdout.getvalue()
|
||||
|
||||
|
||||
class _fake_file:
|
||||
class FakeFile:
|
||||
def __init__(self):
|
||||
self.chunks = []
|
||||
|
||||
@ -261,10 +264,10 @@ def test_python_render(assert_pixels_equal, tmp_path):
|
||||
assert pdf_bytes.startswith(b'%PDF')
|
||||
check_png_pattern(assert_pixels_equal, png_bytes)
|
||||
|
||||
png_file = _fake_file()
|
||||
png_file = FakeFile()
|
||||
html.write_png(png_file, stylesheets=[css])
|
||||
assert png_file.getvalue() == png_bytes
|
||||
pdf_file = _fake_file()
|
||||
pdf_file = FakeFile()
|
||||
html.write_pdf(pdf_file, stylesheets=[css])
|
||||
assert pdf_file.getvalue().startswith(b'%PDF')
|
||||
|
||||
@ -1185,6 +1188,33 @@ def test_http():
|
||||
gzip_file.close()
|
||||
return file_obj.getvalue()
|
||||
|
||||
@contextlib.contextmanager
|
||||
def http_server(handlers):
|
||||
def wsgi_app(environ, start_response):
|
||||
handler = handlers.get(environ['PATH_INFO'])
|
||||
if handler:
|
||||
status = str('200 OK')
|
||||
response, headers = handler(environ)
|
||||
headers = [(str(name), str(value)) for name, value in headers]
|
||||
else: # pragma: no cover
|
||||
status = str('404 Not Found')
|
||||
response = b''
|
||||
headers = []
|
||||
start_response(status, headers)
|
||||
return [response]
|
||||
|
||||
# Port 0: let the OS pick an available port number
|
||||
# https://stackoverflow.com/a/1365284/1162888
|
||||
server = wsgiref.simple_server.make_server('127.0.0.1', 0, wsgi_app)
|
||||
_host, port = server.socket.getsockname()
|
||||
thread = threading.Thread(target=server.serve_forever)
|
||||
thread.start()
|
||||
try:
|
||||
yield f'http://127.0.0.1:{port}'
|
||||
finally:
|
||||
server.shutdown()
|
||||
thread.join()
|
||||
|
||||
with http_server({
|
||||
'/gzip': lambda env: (
|
||||
(gzip_compress(b'<html test=ok>'), [('Content-Encoding', 'gzip')])
|
||||
|
@ -1,11 +1,12 @@
|
||||
"""Test that the "before layout" box tree is correctly constructed."""
|
||||
|
||||
import pytest
|
||||
|
||||
from weasyprint.css import PageType, get_all_computed_styles
|
||||
from weasyprint.formatting_structure import boxes, build
|
||||
from weasyprint.layout.page import set_page_type_computed_styles
|
||||
|
||||
from .testing_utils import (
|
||||
from .testing_utils import ( # isort:skip
|
||||
FakeHTML, assert_no_logs, assert_tree, capture_logs, parse, parse_all,
|
||||
render_pages)
|
||||
|
||||
|
@ -6,13 +6,13 @@ import re
|
||||
from codecs import BOM_UTF16_BE
|
||||
|
||||
import pytest
|
||||
|
||||
from weasyprint import Attachment
|
||||
from weasyprint.document import Document, DocumentMetadata
|
||||
from weasyprint.text.fonts import FontConfiguration
|
||||
from weasyprint.urls import path2url
|
||||
|
||||
from .testing_utils import (
|
||||
FakeHTML, assert_no_logs, capture_logs, resource_path)
|
||||
from .testing_utils import FakeHTML, assert_no_logs, capture_logs, resource_path
|
||||
|
||||
# Top and right positions in points, rounded to the default float precision of
|
||||
# 6 digits, a rendered by pydyf
|
||||
|
@ -1,6 +1,7 @@
|
||||
"""Test CSS stacking contexts."""
|
||||
|
||||
import pytest
|
||||
|
||||
from weasyprint.stacking import StackingContext
|
||||
|
||||
from .testing_utils import assert_no_logs, render_pages, serialize
|
||||
|
@ -1,6 +1,7 @@
|
||||
"""Test the text layout."""
|
||||
|
||||
import pytest
|
||||
|
||||
from weasyprint.css.properties import INITIAL_VALUES
|
||||
from weasyprint.formatting_structure.build import capitalize
|
||||
from weasyprint.text.line_break import split_first_line
|
||||
@ -946,8 +947,8 @@ def test_overflow_wrap(wrap, text, test, full_text):
|
||||
lines = []
|
||||
for line in body.children:
|
||||
box, = line.children
|
||||
textBox, = box.children
|
||||
lines.append(textBox.text)
|
||||
text_box, = box.children
|
||||
lines.append(text_box.text)
|
||||
lines_full_text = ''.join(line for line in lines)
|
||||
assert test(len(lines))
|
||||
assert full_text == lines_full_text
|
||||
|
@ -4,8 +4,6 @@ import contextlib
|
||||
import functools
|
||||
import logging
|
||||
import sys
|
||||
import threading
|
||||
import wsgiref.simple_server
|
||||
from pathlib import Path
|
||||
|
||||
from weasyprint import CSS, DEFAULT_OPTIONS, HTML, images
|
||||
@ -123,34 +121,6 @@ def assert_no_logs(function):
|
||||
return wrapper
|
||||
|
||||
|
||||
@contextlib.contextmanager
|
||||
def http_server(handlers):
|
||||
def wsgi_app(environ, start_response):
|
||||
handler = handlers.get(environ['PATH_INFO'])
|
||||
if handler:
|
||||
status = str('200 OK')
|
||||
response, headers = handler(environ)
|
||||
headers = [(str(name), str(value)) for name, value in headers]
|
||||
else: # pragma: no cover
|
||||
status = str('404 Not Found')
|
||||
response = b''
|
||||
headers = []
|
||||
start_response(status, headers)
|
||||
return [response]
|
||||
|
||||
# Port 0: let the OS pick an available port number
|
||||
# https://stackoverflow.com/a/1365284/1162888
|
||||
server = wsgiref.simple_server.make_server('127.0.0.1', 0, wsgi_app)
|
||||
_host, port = server.socket.getsockname()
|
||||
thread = threading.Thread(target=server.serve_forever)
|
||||
thread.start()
|
||||
try:
|
||||
yield f'http://127.0.0.1:{port}'
|
||||
finally:
|
||||
server.shutdown()
|
||||
thread.join()
|
||||
|
||||
|
||||
def serialize(box_list):
|
||||
"""Transform a box list into a structure easier to compare for testing."""
|
||||
return [(
|
||||
@ -182,7 +152,7 @@ def tree_position(box_list, matcher):
|
||||
elif hasattr(box, 'children'):
|
||||
position = tree_position(box.children, matcher)
|
||||
if position:
|
||||
return [i] + position
|
||||
return [i, *position]
|
||||
|
||||
|
||||
def _parse_base(html_content, base_url=BASE_URL):
|
||||
|
@ -85,9 +85,9 @@ __all__ = [
|
||||
|
||||
|
||||
# Import after setting the version, as the version is used in other modules
|
||||
from .urls import ( # noqa isort:skip
|
||||
from .urls import ( # noqa: I001, E402
|
||||
fetch, default_url_fetcher, path2url, ensure_url, url_is_absolute)
|
||||
from .logger import LOGGER, PROGRESS_LOGGER # noqa isort:skip
|
||||
from .logger import LOGGER, PROGRESS_LOGGER # noqa: E402
|
||||
# Some imports are at the end of the file (after the CSS class)
|
||||
# to work around circular imports.
|
||||
|
||||
@ -416,8 +416,8 @@ def _select_source(guess=None, filename=None, url=None, file_obj=None,
|
||||
yield 'string', string, base_url, None
|
||||
|
||||
# Work around circular imports.
|
||||
from .css import preprocess_stylesheet # noqa isort:skip
|
||||
from .html import ( # noqa isort:skip
|
||||
from .css import preprocess_stylesheet # noqa: I001, E402
|
||||
from .html import ( # noqa: E402
|
||||
HTML5_UA_COUNTER_STYLE, HTML5_UA_STYLESHEET, HTML5_UA_FORM_STYLESHEET,
|
||||
HTML5_PH_STYLESHEET)
|
||||
from .document import Document, Page # noqa isort:skip
|
||||
from .document import Document, Page # noqa: E402
|
||||
|
@ -137,7 +137,7 @@ PARSER.add_argument(
|
||||
PARSER.set_defaults(**DEFAULT_OPTIONS)
|
||||
|
||||
|
||||
def main(argv=None, stdout=None, stdin=None, HTML=HTML):
|
||||
def main(argv=None, stdout=None, stdin=None, HTML=HTML): # noqa: N803
|
||||
"""The ``weasyprint`` program takes at least two arguments:
|
||||
|
||||
.. code-block:: sh
|
||||
|
@ -26,14 +26,14 @@ from ..logger import LOGGER, PROGRESS_LOGGER
|
||||
from ..urls import URLFetchingError, get_url_attribute, url_join
|
||||
from . import counters, media_queries
|
||||
from .computed_values import COMPUTER_FUNCTIONS
|
||||
from .properties import (
|
||||
INHERITED, INITIAL_NOT_COMPUTED, INITIAL_VALUES, ZERO_PIXELS)
|
||||
from .utils import (
|
||||
InvalidValues, Pending, check_var_function, get_url, parse_function,
|
||||
remove_whitespace)
|
||||
from .properties import INHERITED, INITIAL_NOT_COMPUTED, INITIAL_VALUES, ZERO_PIXELS
|
||||
from .validation import preprocess_declarations
|
||||
from .validation.descriptors import preprocess_descriptors
|
||||
|
||||
from .utils import ( # isort:skip
|
||||
InvalidValues, Pending, check_var_function, get_url, parse_function,
|
||||
remove_whitespace)
|
||||
|
||||
# Reject anything not in here:
|
||||
PSEUDO_ELEMENTS = (
|
||||
None, 'before', 'after', 'marker', 'first-line', 'first-letter',
|
||||
|
@ -10,8 +10,7 @@ from ..text.ffi import ffi, pango, units_to_double
|
||||
from ..text.line_break import Layout, first_line_metrics
|
||||
from ..urls import get_link_attribute
|
||||
from .properties import INITIAL_VALUES, ZERO_PIXELS, Dimension
|
||||
from .utils import (
|
||||
ANGLE_TO_RADIANS, LENGTH_UNITS, LENGTHS_TO_PIXELS, safe_urljoin)
|
||||
from .utils import ANGLE_TO_RADIANS, LENGTH_UNITS, LENGTHS_TO_PIXELS, safe_urljoin
|
||||
|
||||
# Value in pixels of font-size for <absolute-size> keywords: 12pt (16px) for
|
||||
# medium, and scaling factors given in CSS3 for others:
|
||||
|
@ -87,7 +87,7 @@ for unit in ANGLE_TO_RADIANS:
|
||||
ATTR_FALLBACKS[unit] = ('angle', Dimension('0', unit))
|
||||
|
||||
|
||||
class InvalidValues(ValueError):
|
||||
class InvalidValues(ValueError): # noqa: N818
|
||||
"""Invalid or unsupported values for a known CSS property."""
|
||||
|
||||
|
||||
|
@ -1,10 +1,8 @@
|
||||
"""Validate properties, expanders and descriptors."""
|
||||
|
||||
|
||||
from cssselect2 import SelectorError, compile_selector_list
|
||||
from tinycss2 import parse_blocks_contents, serialize
|
||||
from tinycss2.ast import (
|
||||
FunctionBlock, IdentToken, LiteralToken, WhitespaceToken)
|
||||
from tinycss2.ast import FunctionBlock, IdentToken, LiteralToken, WhitespaceToken
|
||||
|
||||
from ... import LOGGER
|
||||
from ..utils import InvalidValues, remove_whitespace
|
||||
|
@ -5,11 +5,12 @@ from math import inf
|
||||
import tinycss2
|
||||
|
||||
from ...logger import LOGGER
|
||||
from ..utils import (
|
||||
from . import properties
|
||||
|
||||
from ..utils import ( # isort:skip
|
||||
InvalidValues, comma_separated_list, get_custom_ident, get_keyword,
|
||||
get_single_keyword, get_url, remove_whitespace, single_keyword,
|
||||
single_token, split_on_comma)
|
||||
from . import properties
|
||||
|
||||
DESCRIPTORS = {
|
||||
'font-face': {},
|
||||
|
@ -6,11 +6,12 @@ from tinycss2.ast import DimensionToken, IdentToken, NumberToken
|
||||
from tinycss2.color3 import parse_color
|
||||
|
||||
from ..properties import INITIAL_VALUES
|
||||
from ..utils import (
|
||||
InvalidValues, Pending, check_var_function, get_keyword,
|
||||
get_single_keyword, split_on_comma)
|
||||
from .descriptors import expand_font_variant
|
||||
from .properties import (
|
||||
|
||||
from ..utils import ( # isort:skip
|
||||
InvalidValues, Pending, check_var_function, get_keyword, get_single_keyword,
|
||||
split_on_comma)
|
||||
from .properties import ( # isort:skip
|
||||
background_attachment, background_image, background_position,
|
||||
background_repeat, background_size, block_ellipsis, border_style,
|
||||
border_width, box, column_count, column_width, flex_basis, flex_direction,
|
||||
|
@ -10,7 +10,8 @@ from tinycss2.color3 import parse_color
|
||||
|
||||
from .. import computed_values
|
||||
from ..properties import KNOWN_PROPERTIES, ZERO_PIXELS, Dimension
|
||||
from ..utils import (
|
||||
|
||||
from ..utils import ( # isort:skip
|
||||
InvalidValues, Pending, check_var_function, comma_separated_list,
|
||||
get_angle, get_content_list, get_content_list_token, get_custom_ident,
|
||||
get_image, get_keyword, get_length, get_resolution, get_single_keyword,
|
||||
@ -712,8 +713,8 @@ def font_variant_ligatures(tokens):
|
||||
if token.type != 'ident':
|
||||
return None
|
||||
if token.value in all_values:
|
||||
concurrent_values = [
|
||||
couple for couple in couples if token.value in couple][0]
|
||||
concurrent_values = next(
|
||||
couple for couple in couples if token.value in couple)
|
||||
if any(value in values for value in concurrent_values):
|
||||
return None
|
||||
else:
|
||||
@ -757,8 +758,8 @@ def font_variant_numeric(tokens):
|
||||
if token.type != 'ident':
|
||||
return None
|
||||
if token.value in all_values:
|
||||
concurrent_values = [
|
||||
couple for couple in couples if token.value in couple][0]
|
||||
concurrent_values = next(
|
||||
couple for couple in couples if token.value in couple)
|
||||
if any(value in values for value in concurrent_values):
|
||||
return None
|
||||
else:
|
||||
@ -827,8 +828,8 @@ def font_variant_east_asian(tokens):
|
||||
if token.type != 'ident':
|
||||
return None
|
||||
if token.value in all_values:
|
||||
concurrent_values = [
|
||||
couple for couple in couples if token.value in couple][0]
|
||||
concurrent_values = next(
|
||||
couple for couple in couples if token.value in couple)
|
||||
if any(value in values for value in concurrent_values):
|
||||
return None
|
||||
else:
|
||||
|
@ -41,7 +41,7 @@ def darken(color):
|
||||
hue, saturation, value = rgb_to_hsv(color.red, color.green, color.blue)
|
||||
value /= 1.5
|
||||
saturation /= 1.25
|
||||
return hsv_to_rgb(hue, saturation, value) + (color.alpha,)
|
||||
return (*hsv_to_rgb(hue, saturation, value), color.alpha)
|
||||
|
||||
|
||||
def lighten(color):
|
||||
@ -50,7 +50,7 @@ def lighten(color):
|
||||
value = 1 - (1 - value) / 1.5
|
||||
if saturation:
|
||||
saturation = 1 - (1 - saturation) / 1.25
|
||||
return hsv_to_rgb(hue, saturation, value) + (color.alpha,)
|
||||
return (*hsv_to_rgb(hue, saturation, value), color.alpha)
|
||||
|
||||
|
||||
def draw_page(page, stream):
|
||||
@ -150,7 +150,7 @@ def draw_stacking_context(stream, stacking_context):
|
||||
draw_inline_level(stream, stacking_context.page, box)
|
||||
|
||||
# Point 7
|
||||
for block in [box] + stacking_context.blocks_and_cells:
|
||||
for block in (box, *stacking_context.blocks_and_cells):
|
||||
if isinstance(block, boxes.ReplacedBox):
|
||||
draw_replacedbox(stream, block)
|
||||
elif block.children:
|
||||
|
@ -619,9 +619,9 @@ class LinearGradient(Gradient):
|
||||
for i in range(len(positions) - 1)]
|
||||
|
||||
# Create cycles used to add colors
|
||||
next_steps = cycle([0] + position_steps)
|
||||
next_steps = cycle((0, *position_steps))
|
||||
next_colors = cycle(colors)
|
||||
previous_steps = cycle([0] + position_steps[::-1])
|
||||
previous_steps = cycle((0, *position_steps[::-1]))
|
||||
previous_colors = cycle(colors[::-1])
|
||||
|
||||
# Add colors after last step
|
||||
@ -820,8 +820,7 @@ class RadialGradient(Gradient):
|
||||
new_positions = [
|
||||
position - 1 - full_repeat for position
|
||||
in original_positions[-(i - 1):]]
|
||||
positions = (
|
||||
[ratio - 1 - full_repeat] + new_positions + positions)
|
||||
positions = (ratio - 1 - full_repeat, *new_positions, *positions)
|
||||
return points, positions, colors
|
||||
|
||||
def _resolve_size(self, width, height, center_x, center_y):
|
||||
|
@ -48,8 +48,7 @@ def block_level_layout(context, box, bottom_space, skip_stack,
|
||||
if not context.forced_break:
|
||||
box.margin_top = 0
|
||||
|
||||
collapsed_margin = collapse_margin(
|
||||
adjoining_margins + [box.margin_top])
|
||||
collapsed_margin = collapse_margin([*adjoining_margins, box.margin_top])
|
||||
box.clearance = get_clearance(context, box, collapsed_margin)
|
||||
if box.clearance is not None:
|
||||
top_border_edge = box.position_y + collapsed_margin + box.clearance
|
||||
@ -84,7 +83,7 @@ def block_level_layout_switch(context, box, bottom_space, skip_stack,
|
||||
page_is_empty, absolute_boxes, fixed_boxes)
|
||||
else: # pragma: no cover
|
||||
raise TypeError(f'Layout for {type(box).__name__} not handled yet')
|
||||
return result + (None,)
|
||||
return (*result, None)
|
||||
|
||||
|
||||
def block_box_layout(context, box, bottom_space, skip_stack,
|
||||
@ -111,7 +110,7 @@ def block_box_layout(context, box, bottom_space, skip_stack,
|
||||
context, box, bottom_space, skip_stack,
|
||||
containing_block, page_is_empty, absolute_boxes,
|
||||
fixed_boxes, adjoining_margins)
|
||||
return result + (None,)
|
||||
return (*result, None)
|
||||
elif box.is_table_wrapper:
|
||||
table_wrapper_width(
|
||||
context, box, (containing_block.width, containing_block.height))
|
||||
@ -469,7 +468,7 @@ def _in_flow_layout(context, box, index, child, new_children, page_is_empty,
|
||||
if not context.forced_break:
|
||||
child_margin_top = 0
|
||||
new_collapsed_margin = collapse_margin(
|
||||
adjoining_margins + [child_margin_top])
|
||||
[*adjoining_margins, child_margin_top])
|
||||
collapsed_margin_difference = (
|
||||
new_collapsed_margin - old_collapsed_margin)
|
||||
for previous_new_child in new_children:
|
||||
@ -1013,7 +1012,7 @@ def find_earlier_page_break(context, children, absolute_boxes, fixed_boxes):
|
||||
if result:
|
||||
new_grand_children, resume_at = result
|
||||
new_child = child.copy_with_children(new_grand_children)
|
||||
new_children = list(children[:index]) + [new_child]
|
||||
new_children = [*children[:index], new_child]
|
||||
|
||||
# Re-add footer at the end of split table
|
||||
# TODO: fix table height and footer position
|
||||
|
@ -10,7 +10,7 @@ def columns_layout(context, box, bottom_space, skip_stack, containing_block,
|
||||
page_is_empty, absolute_boxes, fixed_boxes,
|
||||
adjoining_margins):
|
||||
"""Lay out a multi-column ``box``."""
|
||||
from .block import (
|
||||
from .block import ( # isort:skip
|
||||
block_box_layout, block_level_layout, block_level_width,
|
||||
collapse_margin, remove_placeholders)
|
||||
|
||||
|
@ -13,8 +13,7 @@ from .float import avoid_collisions, float_layout
|
||||
from .leader import handle_leader
|
||||
from .min_max import handle_min_max_width
|
||||
from .percent import resolve_one_percentage, resolve_percentages
|
||||
from .preferred import (
|
||||
inline_min_content_width, shrink_to_fit, trailing_whitespace_size)
|
||||
from .preferred import inline_min_content_width, shrink_to_fit, trailing_whitespace_size
|
||||
from .replaced import inline_replaced_box_layout
|
||||
from .table import find_in_flow_baseline, table_wrapper_width
|
||||
|
||||
@ -294,8 +293,7 @@ def first_letter_to_box(box, skip_stack, first_letter_style):
|
||||
letter_box = boxes.InlineBox(
|
||||
f'{box.element_tag}::first-letter', letter_style,
|
||||
box.element, [child])
|
||||
box.children = (
|
||||
(letter_box,) + tuple(box.children[1:]))
|
||||
box.children = ((letter_box, *box.children[1:]))
|
||||
elif child.text:
|
||||
character_found = False
|
||||
if skip_stack:
|
||||
@ -325,7 +323,7 @@ def first_letter_to_box(box, skip_stack, first_letter_style):
|
||||
f'{box.element_tag}::first-letter', letter_style,
|
||||
box.element, first_letter)
|
||||
letter_box.children = (text_box,)
|
||||
box.children = (letter_box,) + tuple(box.children)
|
||||
box.children = (letter_box, *box.children)
|
||||
else:
|
||||
letter_box = boxes.BlockBox(
|
||||
f'{box.element_tag}::first-letter',
|
||||
@ -339,7 +337,7 @@ def first_letter_to_box(box, skip_stack, first_letter_style):
|
||||
f'{box.element_tag}::first-letter', letter_style,
|
||||
box.element, first_letter)
|
||||
line_box.children = (text_box,)
|
||||
box.children = (letter_box,) + tuple(box.children)
|
||||
box.children = (letter_box, *box.children)
|
||||
build.process_text_transform(text_box)
|
||||
if skip_stack and child_skip_stack:
|
||||
index, = skip_stack
|
||||
@ -847,7 +845,7 @@ def split_inline_box(context, box, position_x, max_x, bottom_space, skip_stack,
|
||||
skip_stack=None)
|
||||
|
||||
if resume_at is not None:
|
||||
index = tuple(resume_at)[0]
|
||||
index = next(iter(resume_at))
|
||||
if index < float_resume_index:
|
||||
resume_at = {float_resume_index: None}
|
||||
|
||||
|
@ -431,8 +431,8 @@ def table_and_columns_preferred_widths(context, box, outer=True):
|
||||
box, outer=True, width=block_max_content_width(context, table))
|
||||
result = ([], [], [], [], total_horizontal_border_spacing, [])
|
||||
context.tables[table] = result = {
|
||||
False: (min_width, max_width) + result,
|
||||
True: (outer_min_width, outer_max_width) + result,
|
||||
False: (min_width, max_width, *result),
|
||||
True: (outer_min_width, outer_max_width, *result),
|
||||
}
|
||||
return result[outer]
|
||||
|
||||
@ -616,9 +616,9 @@ def table_and_columns_preferred_widths(context, box, outer=True):
|
||||
table_min_content_width = (
|
||||
total_horizontal_border_spacing + sum(min_content_widths))
|
||||
table_max_content_width = (
|
||||
total_horizontal_border_spacing + max(
|
||||
[sum(max_content_widths), large_percentage_contribution] +
|
||||
small_percentage_contributions))
|
||||
total_horizontal_border_spacing + max([
|
||||
sum(max_content_widths), large_percentage_contribution,
|
||||
*small_percentage_contributions]))
|
||||
|
||||
if table.style['width'] != 'auto' and table.style['width'].unit == 'px':
|
||||
# "percentages on the following properties are treated instead as
|
||||
@ -644,10 +644,8 @@ def table_and_columns_preferred_widths(context, box, outer=True):
|
||||
min_content_widths, max_content_widths, intrinsic_percentages,
|
||||
constrainedness, total_horizontal_border_spacing, zipped_grid)
|
||||
context.tables[table] = result = {
|
||||
False: (table_min_content_width, table_max_content_width) + result,
|
||||
True: (
|
||||
(table_outer_min_content_width, table_outer_max_content_width) +
|
||||
result),
|
||||
False: (table_min_content_width, table_max_content_width, *result),
|
||||
True: (table_outer_min_content_width, table_outer_max_content_width, *result),
|
||||
}
|
||||
return result[outer]
|
||||
|
||||
|
@ -13,7 +13,7 @@ from .preferred import max_content_width, table_and_columns_preferred_widths
|
||||
def table_layout(context, table, bottom_space, skip_stack, containing_block,
|
||||
page_is_empty, absolute_boxes, fixed_boxes):
|
||||
"""Layout for a table box."""
|
||||
from .block import (
|
||||
from .block import ( # isort:skip
|
||||
avoid_page_break, block_container_layout, block_level_page_break,
|
||||
find_earlier_page_break, force_page_break)
|
||||
|
||||
|
@ -7,12 +7,13 @@ from ..html import W3C_DATE_RE
|
||||
from ..logger import LOGGER, PROGRESS_LOGGER
|
||||
from ..matrix import Matrix
|
||||
from . import pdfa, pdfua
|
||||
from .anchors import (
|
||||
add_annotations, add_inputs, add_links, add_outlines, resolve_links,
|
||||
write_pdf_attachment)
|
||||
from .fonts import build_fonts_dictionary
|
||||
from .stream import Stream
|
||||
|
||||
from .anchors import ( # isort:skip
|
||||
add_annotations, add_inputs, add_links, add_outlines, resolve_links,
|
||||
write_pdf_attachment)
|
||||
|
||||
VARIANTS = {
|
||||
name: data for variants in (pdfa.VARIANTS, pdfua.VARIANTS)
|
||||
for (name, data) in variants.items()}
|
||||
|
@ -1,7 +1,6 @@
|
||||
"""PDF metadata stream generation."""
|
||||
|
||||
from xml.etree.ElementTree import (
|
||||
Element, SubElement, register_namespace, tostring)
|
||||
from xml.etree.ElementTree import Element, SubElement, register_namespace, tostring
|
||||
|
||||
import pydyf
|
||||
|
||||
|
@ -8,17 +8,16 @@ from xml.etree import ElementTree
|
||||
from cssselect2 import ElementWrapper
|
||||
|
||||
from ..urls import get_url_attribute
|
||||
from .bounding_box import (
|
||||
EMPTY_BOUNDING_BOX, bounding_box, extend_bounding_box,
|
||||
is_valid_bounding_box)
|
||||
from .css import parse_declarations, parse_stylesheets
|
||||
from .defs import (
|
||||
apply_filters, clip_path, draw_gradient_or_pattern, paint_mask, use)
|
||||
from .defs import apply_filters, clip_path, draw_gradient_or_pattern, paint_mask, use
|
||||
from .images import image, svg
|
||||
from .path import path
|
||||
from .shapes import circle, ellipse, line, polygon, polyline, rect
|
||||
from .text import text
|
||||
from .utils import (
|
||||
|
||||
from .bounding_box import ( # isort:skip
|
||||
EMPTY_BOUNDING_BOX, bounding_box, extend_bounding_box, is_valid_bounding_box)
|
||||
from .utils import ( # isort:skip
|
||||
PointError, alpha_value, color, normalize, parse_url, preserve_ratio, size,
|
||||
transform)
|
||||
|
||||
|
@ -249,17 +249,15 @@ def spread_linear_gradient(spread, positions, colors, x1, y1, x2, y2,
|
||||
|
||||
# Create cycles used to add colors
|
||||
if spread == 'repeat':
|
||||
next_steps = cycle([0] + position_steps)
|
||||
next_steps = cycle((0, *position_steps))
|
||||
next_colors = cycle(colors)
|
||||
previous_steps = cycle([0] + position_steps[::-1])
|
||||
previous_steps = cycle((0, *position_steps[::-1]))
|
||||
previous_colors = cycle(colors[::-1])
|
||||
else:
|
||||
assert spread == 'reflect'
|
||||
next_steps = cycle(
|
||||
[0] + position_steps[::-1] + [0] + position_steps)
|
||||
next_steps = cycle((0, *position_steps[::-1], 0, *position_steps))
|
||||
next_colors = cycle(colors[::-1] + colors)
|
||||
previous_steps = cycle(
|
||||
[0] + position_steps + [0] + position_steps[::-1])
|
||||
previous_steps = cycle((0, *position_steps, 0, *position_steps[::-1]))
|
||||
previous_colors = cycle(colors + colors[::-1])
|
||||
|
||||
# Normalize bounding box
|
||||
@ -402,8 +400,7 @@ def spread_radial_gradient(spread, positions, colors, fx, fy, fr, cx, cy, r,
|
||||
new_positions = [
|
||||
position - 1 - full_repeat for position
|
||||
in original_positions[-(i - 1):]]
|
||||
positions = (
|
||||
[ratio - 1 - full_repeat] + new_positions + positions)
|
||||
positions = [ratio - 1 - full_repeat, *new_positions, *positions]
|
||||
break
|
||||
|
||||
coords = (fx, fy, fr, cx, cy, r)
|
||||
|
@ -69,8 +69,8 @@ def rect(svg, node, font_size):
|
||||
|
||||
# Inspired by Cairo Cookbook
|
||||
# https://cairographics.org/cookbook/roundedrectangles/
|
||||
ARC_TO_BEZIER = 4 * (2 ** .5 - 1) / 3
|
||||
c1, c2 = ARC_TO_BEZIER * rx, ARC_TO_BEZIER * ry
|
||||
arc_to_bezier = 4 * (2 ** .5 - 1) / 3
|
||||
c1, c2 = arc_to_bezier * rx, arc_to_bezier * ry
|
||||
|
||||
svg.stream.move_to(x + rx, y)
|
||||
svg.stream.line_to(x + width - rx, y)
|
||||
|
@ -11,10 +11,11 @@ from fontTools.ttLib import TTFont, woff2
|
||||
|
||||
from ..logger import LOGGER
|
||||
from ..urls import FILESYSTEM_ENCODING, fetch
|
||||
from .constants import (
|
||||
|
||||
from .constants import ( # isort:skip
|
||||
CAPS_KEYS, EAST_ASIAN_KEYS, FONTCONFIG_STRETCH, FONTCONFIG_STYLE,
|
||||
FONTCONFIG_WEIGHT, LIGATURE_KEYS, NUMERIC_KEYS, PANGO_STRETCH, PANGO_STYLE)
|
||||
from .ffi import (
|
||||
from .ffi import ( # isort:skip
|
||||
ffi, fontconfig, gobject, pango, pangoft2, unicode_to_char_p,
|
||||
units_from_double)
|
||||
|
||||
|
@ -6,10 +6,11 @@ from math import inf
|
||||
import pyphen
|
||||
|
||||
from .constants import LST_TO_ISO, PANGO_WRAP_MODE
|
||||
from .ffi import (
|
||||
from .fonts import font_features, get_font_description
|
||||
|
||||
from .ffi import ( # isort:skip
|
||||
ffi, gobject, pango, pangoft2, unicode_to_char_p, units_from_double,
|
||||
units_to_double)
|
||||
from .fonts import font_features, get_font_description
|
||||
|
||||
|
||||
def line_size(line, style):
|
||||
|
Loading…
Reference in New Issue
Block a user