Merge branch 'main' into test_lists_and_tuples

This commit is contained in:
Andrew Murray 2023-05-06 11:43:00 +10:00
commit fa97a1af10
47 changed files with 403 additions and 199 deletions

View File

@ -26,9 +26,9 @@ install:
- 7z x pillow-test-images.zip -oc:\ - 7z x pillow-test-images.zip -oc:\
- mv c:\pillow-depends-main c:\pillow-depends - mv c:\pillow-depends-main c:\pillow-depends
- xcopy /S /Y c:\test-images-main\* c:\pillow\tests\images - xcopy /S /Y c:\test-images-main\* c:\pillow\tests\images
- 7z x ..\pillow-depends\nasm-2.15.05-win64.zip -oc:\ - 7z x ..\pillow-depends\nasm-2.16.01-win64.zip -oc:\
- choco install ghostscript --version=10.0.0.20230317 - choco install ghostscript --version=10.0.0.20230317
- path c:\nasm-2.15.05;C:\Program Files\gs\gs10.00.0\bin;%PATH% - path c:\nasm-2.16.01;C:\Program Files\gs\gs10.00.0\bin;%PATH%
- cd c:\pillow\winbuild\ - cd c:\pillow\winbuild\
- ps: | - ps: |
c:\python38\python.exe c:\pillow\winbuild\build_prepare.py -v --depends=C:\pillow-depends\ c:\python38\python.exe c:\pillow\winbuild\build_prepare.py -v --depends=C:\pillow-depends\

View File

@ -39,7 +39,7 @@ jobs:
uses: actions/checkout@v3 uses: actions/checkout@v3
- name: Install Cygwin - name: Install Cygwin
uses: cygwin/cygwin-install-action@v3 uses: cygwin/cygwin-install-action@v4
with: with:
platform: x86_64 platform: x86_64
packages: > packages: >
@ -84,6 +84,10 @@ jobs:
restore-keys: | restore-keys: |
${{ runner.os }}-cygwin-pip3.${{ matrix.python-minor-version }}- ${{ runner.os }}-cygwin-pip3.${{ matrix.python-minor-version }}-
- name: Select Python version
run: |
ln -sf c:/cygwin/bin/python3.${{ matrix.python-minor-version }} c:/cygwin/bin/python3
- name: Build system information - name: Build system information
run: | run: |
dash.exe -c "python3 .github/workflows/system-info.py" dash.exe -c "python3 .github/workflows/system-info.py"
@ -95,7 +99,7 @@ jobs:
- name: Install a different NumPy - name: Install a different NumPy
shell: dash.exe -l "{0}" shell: dash.exe -l "{0}"
run: | run: |
python3 -m pip install -U 'numpy!=1.21.*' python3 -m pip install -U numpy
- name: Build - name: Build
shell: bash.exe -eo pipefail -o igncr "{0}" shell: bash.exe -eo pipefail -o igncr "{0}"

View File

@ -39,10 +39,9 @@ jobs:
centos-stream-8-amd64, centos-stream-8-amd64,
centos-stream-9-amd64, centos-stream-9-amd64,
debian-11-bullseye-x86, debian-11-bullseye-x86,
fedora-36-amd64,
fedora-37-amd64, fedora-37-amd64,
fedora-38-amd64,
gentoo, gentoo,
ubuntu-18.04-bionic-amd64,
ubuntu-20.04-focal-amd64, ubuntu-20.04-focal-amd64,
ubuntu-22.04-jammy-amd64, ubuntu-22.04-jammy-amd64,
] ]

View File

@ -80,7 +80,7 @@ jobs:
pushd depends && ./install_extra_test_images.sh && popd pushd depends && ./install_extra_test_images.sh && popd
- name: Build Pillow - name: Build Pillow
run: CFLAGS="-coverage" python3 -m pip install --global-option="build_ext" . run: SETUPTOOLS_USE_DISTUTILS="stdlib" CFLAGS="-coverage" python3 -m pip install --global-option="build_ext" .
- name: Test Pillow - name: Test Pillow
run: | run: |

View File

@ -71,8 +71,8 @@ jobs:
- name: Install dependencies - name: Install dependencies
id: install id: install
run: | run: |
7z x winbuild\depends\nasm-2.15.05-win64.zip "-o$env:RUNNER_WORKSPACE\" 7z x winbuild\depends\nasm-2.16.01-win64.zip "-o$env:RUNNER_WORKSPACE\"
echo "$env:RUNNER_WORKSPACE\nasm-2.15.05" >> $env:GITHUB_PATH echo "$env:RUNNER_WORKSPACE\nasm-2.16.01" >> $env:GITHUB_PATH
choco install ghostscript --version=10.0.0.20230317 choco install ghostscript --version=10.0.0.20230317
echo "C:\Program Files\gs\gs10.00.0\bin" >> $env:GITHUB_PATH echo "C:\Program Files\gs\gs10.00.0\bin" >> $env:GITHUB_PATH

View File

@ -57,7 +57,7 @@ repos:
- id: sphinx-lint - id: sphinx-lint
- repo: https://github.com/tox-dev/tox-ini-fmt - repo: https://github.com/tox-dev/tox-ini-fmt
rev: 1.0.0 rev: 1.3.0
hooks: hooks:
- id: tox-ini-fmt - id: tox-ini-fmt

View File

@ -1,5 +1,7 @@
version: 2 version: 2
formats: all
build: build:
os: ubuntu-22.04 os: ubuntu-22.04
tools: tools:

View File

@ -5,6 +5,30 @@ Changelog (Pillow)
10.0.0 (unreleased) 10.0.0 (unreleased)
------------------- -------------------
- Support reading signed 8-bit TIFF images #7111
[radarhere]
- Added width argument to ImageDraw regular_polygon #7132
[radarhere]
- Support I mode for ImageFilter.BuiltinFilter #7108
[radarhere]
- Raise error from stderr of Linux ImageGrab.grabclipboard() command #7112
[radarhere]
- Added unpacker from I;16B to I;16 #7125
[radarhere]
- Support float font sizes #7107
[radarhere]
- Use later value for duplicate xref entries in PdfParser #7102
[radarhere]
- Load before getting size in __getstate__ #7105
[bigcat88, radarhere]
- Fixed type handling for include and lib directories #7069 - Fixed type handling for include and lib directories #7069
[adisbladis, radarhere] [adisbladis, radarhere]

View File

@ -75,45 +75,42 @@ post-patch:
""" """
def test_qtables_leak(): standard_l_qtable = (
im = hopper("RGB") # fmt: off
16, 11, 10, 16, 24, 40, 51, 61,
12, 12, 14, 19, 26, 58, 60, 55,
14, 13, 16, 24, 40, 57, 69, 56,
14, 17, 22, 29, 51, 87, 80, 62,
18, 22, 37, 56, 68, 109, 103, 77,
24, 35, 55, 64, 81, 104, 113, 92,
49, 64, 78, 87, 103, 121, 120, 101,
72, 92, 95, 98, 112, 100, 103, 99,
# fmt: on
)
standard_l_qtable = [ standard_chrominance_qtable = (
int(s) # fmt: off
for s in """ 17, 18, 24, 47, 99, 99, 99, 99,
16 11 10 16 24 40 51 61 18, 21, 26, 66, 99, 99, 99, 99,
12 12 14 19 26 58 60 55 24, 26, 56, 99, 99, 99, 99, 99,
14 13 16 24 40 57 69 56 47, 66, 99, 99, 99, 99, 99, 99,
14 17 22 29 51 87 80 62 99, 99, 99, 99, 99, 99, 99, 99,
18 22 37 56 68 109 103 77 99, 99, 99, 99, 99, 99, 99, 99,
24 35 55 64 81 104 113 92 99, 99, 99, 99, 99, 99, 99, 99,
49 64 78 87 103 121 120 101 99, 99, 99, 99, 99, 99, 99, 99,
72 92 95 98 112 100 103 99 # fmt: on
""".split( )
None
)
]
standard_chrominance_qtable = [
int(s)
for s in """
17 18 24 47 99 99 99 99
18 21 26 66 99 99 99 99
24 26 56 99 99 99 99 99
47 66 99 99 99 99 99 99
99 99 99 99 99 99 99 99
99 99 99 99 99 99 99 99
99 99 99 99 99 99 99 99
99 99 99 99 99 99 99 99
""".split(
None
)
]
for qtables in ( @pytest.mark.parametrize(
"qtables",
(
(standard_l_qtable, standard_chrominance_qtable), (standard_l_qtable, standard_chrominance_qtable),
[standard_l_qtable, standard_chrominance_qtable], [standard_l_qtable, standard_chrominance_qtable],
): ),
)
def test_qtables_leak(qtables):
im = hopper("RGB")
for _ in range(iterations): for _ in range(iterations):
test_output = BytesIO() test_output = BytesIO()
im.save(test_output, "JPEG", qtables=qtables) im.save(test_output, "JPEG", qtables=qtables)

BIN
Tests/images/8bit.s.tif Normal file

Binary file not shown.

Binary file not shown.

Binary file not shown.

After

Width:  |  Height:  |  Size: 13 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 14 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 499 B

View File

@ -198,6 +198,12 @@ class TestFileTiff:
with pytest.raises(OSError): with pytest.raises(OSError):
im.save(outfile) im.save(outfile)
def test_8bit_s(self):
with Image.open("Tests/images/8bit.s.tif") as im:
im.load()
assert im.mode == "L"
assert im.getpixel((50, 50)) == 184
def test_little_endian(self): def test_little_endian(self):
with Image.open("Tests/images/16bit.cropped.tif") as im: with Image.open("Tests/images/16bit.cropped.tif") as im:
assert im.getpixel((0, 0)) == 480 assert im.getpixel((0, 0)) == 480

View File

@ -4,7 +4,7 @@ import pytest
from PIL import Image from PIL import Image
from .helper import hopper from .helper import hopper, skip_unless_feature
@pytest.mark.parametrize("mode", ("1", "P", "L", "RGB", "I", "F")) @pytest.mark.parametrize("mode", ("1", "P", "L", "RGB", "I", "F"))
@ -42,3 +42,10 @@ def test_copy_zero():
out = im.copy() out = im.copy()
assert out.mode == im.mode assert out.mode == im.mode
assert out.size == im.size assert out.size == im.size
@skip_unless_feature("libtiff")
def test_deepcopy():
with Image.open("Tests/images/g4_orientation_5.tif") as im:
out = copy.deepcopy(im)
assert out.size == (590, 88)

View File

@ -30,15 +30,16 @@ from .helper import assert_image_equal, hopper
ImageFilter.UnsharpMask(10), ImageFilter.UnsharpMask(10),
), ),
) )
@pytest.mark.parametrize("mode", ("L", "RGB", "CMYK")) @pytest.mark.parametrize("mode", ("L", "I", "RGB", "CMYK"))
def test_sanity(filter_to_apply, mode): def test_sanity(filter_to_apply, mode):
im = hopper(mode) im = hopper(mode)
if mode != "I" or isinstance(filter_to_apply, ImageFilter.BuiltinFilter):
out = im.filter(filter_to_apply) out = im.filter(filter_to_apply)
assert out.mode == im.mode assert out.mode == im.mode
assert out.size == im.size assert out.size == im.size
@pytest.mark.parametrize("mode", ("L", "RGB", "CMYK")) @pytest.mark.parametrize("mode", ("L", "I", "RGB", "CMYK"))
def test_sanity_error(mode): def test_sanity_error(mode):
with pytest.raises(TypeError): with pytest.raises(TypeError):
im = hopper(mode) im = hopper(mode)
@ -130,10 +131,12 @@ def test_kernel_not_enough_coefficients():
ImageFilter.Kernel((3, 3), (0, 0)) ImageFilter.Kernel((3, 3), (0, 0))
@pytest.mark.parametrize("mode", ("L", "LA", "RGB", "CMYK")) @pytest.mark.parametrize("mode", ("L", "LA", "I", "RGB", "CMYK"))
def test_consistency_3x3(mode): def test_consistency_3x3(mode):
with Image.open("Tests/images/hopper.bmp") as source: with Image.open("Tests/images/hopper.bmp") as source:
with Image.open("Tests/images/hopper_emboss.bmp") as reference: reference_name = "hopper_emboss"
reference_name += "_I.png" if mode == "I" else ".bmp"
with Image.open("Tests/images/" + reference_name) as reference:
kernel = ImageFilter.Kernel( kernel = ImageFilter.Kernel(
(3, 3), (3, 3),
# fmt: off # fmt: off
@ -146,16 +149,20 @@ def test_consistency_3x3(mode):
source = source.split() * 2 source = source.split() * 2
reference = reference.split() * 2 reference = reference.split() * 2
assert_image_equal( if mode == "I":
Image.merge(mode, source[: len(mode)]).filter(kernel), source = source[0].convert(mode)
Image.merge(mode, reference[: len(mode)]), else:
) source = Image.merge(mode, source[: len(mode)])
reference = Image.merge(mode, reference[: len(mode)])
assert_image_equal(source.filter(kernel), reference)
@pytest.mark.parametrize("mode", ("L", "LA", "RGB", "CMYK")) @pytest.mark.parametrize("mode", ("L", "LA", "I", "RGB", "CMYK"))
def test_consistency_5x5(mode): def test_consistency_5x5(mode):
with Image.open("Tests/images/hopper.bmp") as source: with Image.open("Tests/images/hopper.bmp") as source:
with Image.open("Tests/images/hopper_emboss_more.bmp") as reference: reference_name = "hopper_emboss_more"
reference_name += "_I.png" if mode == "I" else ".bmp"
with Image.open("Tests/images/" + reference_name) as reference:
kernel = ImageFilter.Kernel( kernel = ImageFilter.Kernel(
(5, 5), (5, 5),
# fmt: off # fmt: off
@ -170,10 +177,12 @@ def test_consistency_5x5(mode):
source = source.split() * 2 source = source.split() * 2
reference = reference.split() * 2 reference = reference.split() * 2
assert_image_equal( if mode == "I":
Image.merge(mode, source[: len(mode)]).filter(kernel), source = source[0].convert(mode)
Image.merge(mode, reference[: len(mode)]), else:
) source = Image.merge(mode, source[: len(mode)])
reference = Image.merge(mode, reference[: len(mode)])
assert_image_equal(source.filter(kernel), reference)
def test_invalid_box_blur_filter(): def test_invalid_box_blur_filter():

View File

@ -1380,20 +1380,20 @@ def test_same_color_outline(bbox):
@pytest.mark.parametrize( @pytest.mark.parametrize(
"n_sides, rotation, polygon_name", "n_sides, polygon_name, args",
[(4, 0, "square"), (8, 0, "regular_octagon"), (4, 45, "square")], [
(4, "square", {}),
(8, "regular_octagon", {}),
(4, "square_rotate_45", {"rotation": 45}),
(3, "triangle_width", {"width": 5, "outline": "yellow"}),
],
) )
def test_draw_regular_polygon(n_sides, rotation, polygon_name): def test_draw_regular_polygon(n_sides, polygon_name, args):
im = Image.new("RGBA", size=(W, H), color=(255, 0, 0, 0)) im = Image.new("RGBA", size=(W, H), color=(255, 0, 0, 0))
filename_base = f"Tests/images/imagedraw_{polygon_name}" filename = f"Tests/images/imagedraw_{polygon_name}.png"
filename = (
f"{filename_base}.png"
if rotation == 0
else f"{filename_base}_rotate_{rotation}.png"
)
draw = ImageDraw.Draw(im) draw = ImageDraw.Draw(im)
bounding_circle = ((W // 2, H // 2), 25) bounding_circle = ((W // 2, H // 2), 25)
draw.regular_polygon(bounding_circle, n_sides, rotation=rotation, fill="red") draw.regular_polygon(bounding_circle, n_sides, fill="red", **args)
assert_image_equal_tofile(im, filename) assert_image_equal_tofile(im, filename)

View File

@ -191,6 +191,16 @@ def test_getlength(
assert length == length_raqm assert length == length_raqm
def test_float_size():
lengths = []
for size in (48, 48.5, 49):
f = ImageFont.truetype(
"Tests/fonts/NotoSans-Regular.ttf", size, layout_engine=layout_engine
)
lengths.append(f.getlength("text"))
assert lengths[0] != lengths[1] != lengths[2]
def test_render_multiline(font): def test_render_multiline(font):
im = Image.new(mode="RGB", size=(300, 100)) im = Image.new(mode="RGB", size=(300, 100))
draw = ImageDraw.Draw(im) draw = ImageDraw.Draw(im)

View File

@ -28,7 +28,7 @@ def test_path():
(6.0, 7.0), (6.0, 7.0),
(8.0, 9.0), (8.0, 9.0),
] ]
assert p.tolist(1) == [0.0, 1.0, 2.0, 3.0, 4.0, 5.0, 6.0, 7.0, 8.0, 9.0] assert p.tolist(True) == [0.0, 1.0, 2.0, 3.0, 4.0, 5.0, 6.0, 7.0, 8.0, 9.0]
assert p.getbbox() == (0.0, 1.0, 8.0, 9.0) assert p.getbbox() == (0.0, 1.0, 8.0, 9.0)
@ -38,48 +38,65 @@ def test_path():
p.transform((1, 0, 1, 0, 1, 1)) p.transform((1, 0, 1, 0, 1, 1))
assert list(p) == [(1.0, 2.0), (5.0, 6.0), (9.0, 10.0)] assert list(p) == [(1.0, 2.0), (5.0, 6.0), (9.0, 10.0)]
# alternative constructors
p = ImagePath.Path([0, 1])
assert list(p) == [(0.0, 1.0)]
p = ImagePath.Path([0.0, 1.0])
assert list(p) == [(0.0, 1.0)]
p = ImagePath.Path([0, 1])
assert list(p) == [(0.0, 1.0)]
p = ImagePath.Path([(0, 1)])
assert list(p) == [(0.0, 1.0)]
p = ImagePath.Path(p)
assert list(p) == [(0.0, 1.0)]
p = ImagePath.Path(p.tolist(0))
assert list(p) == [(0.0, 1.0)]
p = ImagePath.Path(p.tolist(1))
assert list(p) == [(0.0, 1.0)]
p = ImagePath.Path(array.array("f", [0, 1]))
assert list(p) == [(0.0, 1.0)]
arr = array.array("f", [0, 1]) @pytest.mark.parametrize(
p = ImagePath.Path(arr.tobytes()) "coords",
(
(0, 1),
[0, 1],
(0.0, 1.0),
[0.0, 1.0],
((0, 1),),
[(0, 1)],
((0.0, 1.0),),
[(0.0, 1.0)],
array.array("f", [0, 1]),
array.array("f", [0, 1]).tobytes(),
ImagePath.Path((0, 1)),
),
)
def test_path_constructors(coords):
# Arrange / Act
p = ImagePath.Path(coords)
# Assert
assert list(p) == [(0.0, 1.0)] assert list(p) == [(0.0, 1.0)]
def test_invalid_coords(): @pytest.mark.parametrize(
# Arrange "coords",
coords = ["a", "b"] (
("a", "b"),
# Act / Assert ([0, 1],),
[[0, 1]],
([0.0, 1.0],),
[[0.0, 1.0]],
),
)
def test_invalid_path_constructors(coords):
# Act
with pytest.raises(ValueError) as e: with pytest.raises(ValueError) as e:
ImagePath.Path(coords) ImagePath.Path(coords)
# Assert
assert str(e.value) == "incorrect coordinate type" assert str(e.value) == "incorrect coordinate type"
def test_path_odd_number_of_coordinates(): @pytest.mark.parametrize(
# Arrange "coords",
coords = [0] (
(0,),
# Act / Assert [0],
(0, 1, 2),
[0, 1, 2],
),
)
def test_path_odd_number_of_coordinates(coords):
# Act
with pytest.raises(ValueError) as e: with pytest.raises(ValueError) as e:
ImagePath.Path(coords) ImagePath.Path(coords)
# Assert
assert str(e.value) == "wrong number of coordinates" assert str(e.value) == "wrong number of coordinates"

View File

@ -757,6 +757,7 @@ class TestLibUnpack:
def test_I16(self): def test_I16(self):
self.assert_unpack("I;16", "I;16", 2, 0x0201, 0x0403, 0x0605) self.assert_unpack("I;16", "I;16", 2, 0x0201, 0x0403, 0x0605)
self.assert_unpack("I;16", "I;16B", 2, 0x0102, 0x0304, 0x0506)
self.assert_unpack("I;16B", "I;16B", 2, 0x0102, 0x0304, 0x0506) self.assert_unpack("I;16B", "I;16B", 2, 0x0102, 0x0304, 0x0506)
self.assert_unpack("I;16L", "I;16L", 2, 0x0201, 0x0403, 0x0605) self.assert_unpack("I;16L", "I;16L", 2, 0x0201, 0x0403, 0x0605)
self.assert_unpack("I;16", "I;12", 2, 0x0010, 0x0203, 0x0040) self.assert_unpack("I;16", "I;12", 2, 0x0010, 0x0203, 0x0040)

View File

@ -117,3 +117,9 @@ def test_pdf_repr():
assert pdf_repr(b"a)/b\\(c") == rb"(a\)/b\\\(c)" assert pdf_repr(b"a)/b\\(c") == rb"(a\)/b\\\(c)"
assert pdf_repr([123, True, {"a": PdfName(b"b")}]) == b"[ 123 true <<\n/a /b\n>> ]" assert pdf_repr([123, True, {"a": PdfName(b"b")}]) == b"[ 123 true <<\n/a /b\n>> ]"
assert pdf_repr(PdfBinary(b"\x90\x1F\xA0")) == b"<901FA0>" assert pdf_repr(PdfBinary(b"\x90\x1F\xA0")) == b"<901FA0>"
def test_duplicate_xref_entry():
pdf = PdfParser("Tests/images/duplicate_xref_entry.pdf")
assert pdf.xref_table.existing_entries[6][0] == 1197
pdf.close()

View File

@ -1,7 +1,7 @@
#!/bin/bash #!/bin/bash
# install libimagequant # install libimagequant
archive=libimagequant-4.1.1 archive=libimagequant-4.2.0
./download-and-extract.sh $archive https://raw.githubusercontent.com/python-pillow/pillow-depends/main/$archive.tar.gz ./download-and-extract.sh $archive https://raw.githubusercontent.com/python-pillow/pillow-depends/main/$archive.tar.gz

View File

@ -2,7 +2,7 @@
# install raqm # install raqm
archive=libraqm-0.10.0 archive=libraqm-0.10.1
./download-and-extract.sh $archive https://raw.githubusercontent.com/python-pillow/pillow-depends/main/$archive.tar.gz ./download-and-extract.sh $archive https://raw.githubusercontent.com/python-pillow/pillow-depends/main/$archive.tar.gz

View File

@ -181,7 +181,7 @@ Many of Pillow's features require external libraries:
* **libimagequant** provides improved color quantization * **libimagequant** provides improved color quantization
* Pillow has been tested with libimagequant **2.6-4.1.1** * Pillow has been tested with libimagequant **2.6-4.2**
* Libimagequant is licensed GPLv3, which is more restrictive than * Libimagequant is licensed GPLv3, which is more restrictive than
the Pillow license, therefore we will not be distributing binaries the Pillow license, therefore we will not be distributing binaries
with libimagequant support enabled. with libimagequant support enabled.
@ -448,17 +448,15 @@ These platforms are built and tested for every change.
+----------------------------------+----------------------------+---------------------+ +----------------------------------+----------------------------+---------------------+
| Debian 11 Bullseye | 3.9 | x86 | | Debian 11 Bullseye | 3.9 | x86 |
+----------------------------------+----------------------------+---------------------+ +----------------------------------+----------------------------+---------------------+
| Fedora 36 | 3.10 | x86-64 |
+----------------------------------+----------------------------+---------------------+
| Fedora 37 | 3.11 | x86-64 | | Fedora 37 | 3.11 | x86-64 |
+----------------------------------+----------------------------+---------------------+ +----------------------------------+----------------------------+---------------------+
| Fedora 38 | 3.11 | x86-64 |
+----------------------------------+----------------------------+---------------------+
| Gentoo | 3.9 | x86-64 | | Gentoo | 3.9 | x86-64 |
+----------------------------------+----------------------------+---------------------+ +----------------------------------+----------------------------+---------------------+
| macOS 12 Monterey | 3.8, 3.9, 3.10, 3.11, | x86-64 | | macOS 12 Monterey | 3.8, 3.9, 3.10, 3.11, | x86-64 |
| | 3.12, PyPy3 | | | | 3.12, PyPy3 | |
+----------------------------------+----------------------------+---------------------+ +----------------------------------+----------------------------+---------------------+
| Ubuntu Linux 18.04 LTS (Bionic) | 3.9 | x86-64 |
+----------------------------------+----------------------------+---------------------+
| Ubuntu Linux 20.04 LTS (Focal) | 3.8 | x86-64 | | Ubuntu Linux 20.04 LTS (Focal) | 3.8 | x86-64 |
+----------------------------------+----------------------------+---------------------+ +----------------------------------+----------------------------+---------------------+
| Ubuntu Linux 22.04 LTS (Jammy) | 3.8, 3.9, 3.10, 3.11, | x86-64 | | Ubuntu Linux 22.04 LTS (Jammy) | 3.8, 3.9, 3.10, 3.11, | x86-64 |
@ -492,7 +490,7 @@ These platforms have been reported to work at the versions mentioned.
| Operating system | | Tested Python | | Latest tested | | Tested | | Operating system | | Tested Python | | Latest tested | | Tested |
| | | versions | | Pillow version | | processors | | | | versions | | Pillow version | | processors |
+==================================+===========================+==================+==============+ +==================================+===========================+==================+==============+
| macOS 13 Ventura | 3.7, 3.8, 3.9, 3.10, 3.11 | 9.4.0 |arm | | macOS 13 Ventura | 3.7, 3.8, 3.9, 3.10, 3.11 | 9.5.0 |arm |
+----------------------------------+---------------------------+------------------+--------------+ +----------------------------------+---------------------------+------------------+--------------+
| macOS 12 Big Sur | 3.7, 3.8, 3.9, 3.10, 3.11 | 9.3.0 |arm | | macOS 12 Big Sur | 3.7, 3.8, 3.9, 3.10, 3.11 | 9.3.0 |arm |
+----------------------------------+---------------------------+------------------+--------------+ +----------------------------------+---------------------------+------------------+--------------+

View File

@ -439,7 +439,7 @@ Used to specify the dithering method to use for the
Palettes Palettes
^^^^^^^^ ^^^^^^^^
Used to specify the pallete to use for the :meth:`~Image.convert` method. Used to specify the palette to use for the :meth:`~Image.convert` method.
.. autoclass:: Palette .. autoclass:: Palette
:members: :members:

View File

@ -296,7 +296,7 @@ Methods
:param width: The line width, in pixels. :param width: The line width, in pixels.
.. py:method:: ImageDraw.regular_polygon(bounding_circle, n_sides, rotation=0, fill=None, outline=None) .. py:method:: ImageDraw.regular_polygon(bounding_circle, n_sides, rotation=0, fill=None, outline=None, width=1)
Draws a regular polygon inscribed in ``bounding_circle``, Draws a regular polygon inscribed in ``bounding_circle``,
with ``n_sides``, and rotation of ``rotation`` degrees. with ``n_sides``, and rotation of ``rotation`` degrees.
@ -311,6 +311,7 @@ Methods
(e.g. ``rotation=90``, applies a 90 degree rotation). (e.g. ``rotation=90``, applies a 90 degree rotation).
:param fill: Color to use for the fill. :param fill: Color to use for the fill.
:param outline: Color to use for the outline. :param outline: Color to use for the outline.
:param width: The line width, in pixels.
.. py:method:: ImageDraw.rectangle(xy, fill=None, outline=None, width=1) .. py:method:: ImageDraw.rectangle(xy, fill=None, outline=None, width=1)

View File

@ -48,7 +48,7 @@ vector data. Path objects can be passed to the methods on the
Maps the path through a function. Maps the path through a function.
.. py:method:: PIL.ImagePath.Path.tolist(flat=0) .. py:method:: PIL.ImagePath.Path.tolist(flat=False)
Converts the path to a Python list [(x, y), …]. Converts the path to a Python list [(x, y), …].

View File

@ -135,10 +135,11 @@ TODO
API Changes API Changes
=========== ===========
TODO Added line width parameter to ImageDraw regular_polygon
^^^^ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
TODO An optional line ``width`` parameter has been added to
``ImageDraw.Draw.regular_polygon``.
API Additions API Additions
============= =============
@ -159,7 +160,8 @@ TODO
Other Changes Other Changes
============= =============
TODO Support reading signed 8-bit TIFF images
^^^^ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
TODO TIFF images with signed integer data, 8 bits per sample and a photometric
interpretaton of BlackIsZero can now be read.

View File

@ -672,7 +672,8 @@ class Image:
return new return new
def __getstate__(self): def __getstate__(self):
return [self.info, self.mode, self.size, self.getpalette(), self.tobytes()] im_data = self.tobytes() # load image first
return [self.info, self.mode, self.size, self.getpalette(), im_data]
def __setstate__(self, state): def __setstate__(self, state):
Image.__init__(self) Image.__init__(self)

View File

@ -185,12 +185,8 @@ class ImageCmsProfile:
def _set(self, profile, filename=None): def _set(self, profile, filename=None):
self.profile = profile self.profile = profile
self.filename = filename self.filename = filename
if profile:
self.product_name = None # profile.product_name self.product_name = None # profile.product_name
self.product_info = None # profile.product_info self.product_info = None # profile.product_info
else:
self.product_name = None
self.product_info = None
def tobytes(self): def tobytes(self):
""" """

View File

@ -279,11 +279,11 @@ class ImageDraw:
self.im.paste(im.im, (0, 0) + im.size, mask.im) self.im.paste(im.im, (0, 0) + im.size, mask.im)
def regular_polygon( def regular_polygon(
self, bounding_circle, n_sides, rotation=0, fill=None, outline=None self, bounding_circle, n_sides, rotation=0, fill=None, outline=None, width=1
): ):
"""Draw a regular polygon.""" """Draw a regular polygon."""
xy = _compute_regular_polygon_vertices(bounding_circle, n_sides, rotation) xy = _compute_regular_polygon_vertices(bounding_circle, n_sides, rotation)
self.polygon(xy, fill, outline) self.polygon(xy, fill, outline, width)
def rectangle(self, xy, fill=None, outline=None, width=1): def rectangle(self, xy, fill=None, outline=None, width=1):
"""Draw a rectangle.""" """Draw a rectangle."""

View File

@ -141,8 +141,11 @@ def grabclipboard():
msg = "wl-paste or xclip is required for ImageGrab.grabclipboard() on Linux" msg = "wl-paste or xclip is required for ImageGrab.grabclipboard() on Linux"
raise NotImplementedError(msg) raise NotImplementedError(msg)
fh, filepath = tempfile.mkstemp() fh, filepath = tempfile.mkstemp()
subprocess.call(args, stdout=fh) err = subprocess.run(args, stdout=fh, stderr=subprocess.PIPE).stderr
os.close(fh) os.close(fh)
if err:
msg = f"{args[0]} error: {err.strip().decode()}"
raise ChildProcessError(msg)
im = Image.open(filepath) im = Image.open(filepath)
im.load() im.load()
os.unlink(filepath) os.unlink(filepath)

View File

@ -957,13 +957,10 @@ class PdfParser:
check_format_condition(m, "xref entry not found") check_format_condition(m, "xref entry not found")
offset = m.end() offset = m.end()
is_free = m.group(3) == b"f" is_free = m.group(3) == b"f"
generation = int(m.group(2))
if not is_free: if not is_free:
generation = int(m.group(2))
new_entry = (int(m.group(1)), generation) new_entry = (int(m.group(1)), generation)
check_format_condition( if i not in self.xref_table:
i not in self.xref_table or self.xref_table[i] == new_entry,
"xref entry duplicated (and not identical)",
)
self.xref_table[i] = new_entry self.xref_table[i] = new_entry
return offset return offset

View File

@ -170,6 +170,8 @@ OPEN_INFO = {
(MM, 0, (1,), 2, (8,), ()): ("L", "L;IR"), (MM, 0, (1,), 2, (8,), ()): ("L", "L;IR"),
(II, 1, (1,), 1, (8,), ()): ("L", "L"), (II, 1, (1,), 1, (8,), ()): ("L", "L"),
(MM, 1, (1,), 1, (8,), ()): ("L", "L"), (MM, 1, (1,), 1, (8,), ()): ("L", "L"),
(II, 1, (2,), 1, (8,), ()): ("L", "L"),
(MM, 1, (2,), 1, (8,), ()): ("L", "L"),
(II, 1, (1,), 2, (8,), ()): ("L", "L;R"), (II, 1, (1,), 2, (8,), ()): ("L", "L;R"),
(MM, 1, (1,), 2, (8,), ()): ("L", "L;R"), (MM, 1, (1,), 2, (8,), ()): ("L", "L;R"),
(II, 1, (1,), 1, (12,), ()): ("I;16", "I;12"), (II, 1, (1,), 1, (12,), ()): ("I;16", "I;12"),

View File

@ -116,7 +116,9 @@ getfont(PyObject *self_, PyObject *args, PyObject *kw) {
int error = 0; int error = 0;
char *filename = NULL; char *filename = NULL;
Py_ssize_t size; float size;
FT_Size_RequestRec req;
FT_Long width;
Py_ssize_t index = 0; Py_ssize_t index = 0;
Py_ssize_t layout_engine = 0; Py_ssize_t layout_engine = 0;
unsigned char *encoding; unsigned char *encoding;
@ -133,7 +135,7 @@ getfont(PyObject *self_, PyObject *args, PyObject *kw) {
if (!PyArg_ParseTupleAndKeywords( if (!PyArg_ParseTupleAndKeywords(
args, args,
kw, kw,
"etn|nsy#n", "etf|nsy#n",
kwlist, kwlist,
Py_FileSystemDefaultEncoding, Py_FileSystemDefaultEncoding,
&filename, &filename,
@ -179,7 +181,13 @@ getfont(PyObject *self_, PyObject *args, PyObject *kw) {
} }
if (!error) { if (!error) {
error = FT_Set_Pixel_Sizes(self->face, 0, size); width = size * 64;
req.type = FT_SIZE_REQUEST_TYPE_NOMINAL;
req.width = width;
req.height = width;
req.horiResolution = 0;
req.vertResolution = 0;
error = FT_Request_Size(self->face, &req);
} }
if (!error && encoding && strlen((char *)encoding) == 4) { if (!error && encoding && strlen((char *)encoding) == 4) {

View File

@ -37,6 +37,17 @@ clip8(float in) {
return (UINT8)in; return (UINT8)in;
} }
static inline INT32
clip32(float in) {
if (in <= 0.0) {
return 0;
}
if (in >= pow(2, 31) - 1) {
return pow(2, 31) - 1;
}
return (INT32)in;
}
Imaging Imaging
ImagingExpand(Imaging imIn, int xmargin, int ymargin, int mode) { ImagingExpand(Imaging imIn, int xmargin, int ymargin, int mode) {
Imaging imOut; Imaging imOut;
@ -96,8 +107,8 @@ ImagingExpand(Imaging imIn, int xmargin, int ymargin, int mode) {
void void
ImagingFilter3x3(Imaging imOut, Imaging im, const float *kernel, float offset) { ImagingFilter3x3(Imaging imOut, Imaging im, const float *kernel, float offset) {
#define KERNEL1x3(in0, x, kernel, d) \ #define KERNEL1x3(in0, x, kernel, d) \
(_i2f((UINT8)in0[x - d]) * (kernel)[0] + _i2f((UINT8)in0[x]) * (kernel)[1] + \ (_i2f(in0[x - d]) * (kernel)[0] + _i2f(in0[x]) * (kernel)[1] + \
_i2f((UINT8)in0[x + d]) * (kernel)[2]) _i2f(in0[x + d]) * (kernel)[2])
int x = 0, y = 0; int x = 0, y = 0;
@ -105,6 +116,24 @@ ImagingFilter3x3(Imaging imOut, Imaging im, const float *kernel, float offset) {
if (im->bands == 1) { if (im->bands == 1) {
// Add one time for rounding // Add one time for rounding
offset += 0.5; offset += 0.5;
if (im->type == IMAGING_TYPE_INT32) {
for (y = 1; y < im->ysize - 1; y++) {
INT32 *in_1 = (INT32 *)im->image[y - 1];
INT32 *in0 = (INT32 *)im->image[y];
INT32 *in1 = (INT32 *)im->image[y + 1];
INT32 *out = (INT32 *)imOut->image[y];
out[0] = in0[0];
for (x = 1; x < im->xsize - 1; x++) {
float ss = offset;
ss += KERNEL1x3(in1, x, &kernel[0], 1);
ss += KERNEL1x3(in0, x, &kernel[3], 1);
ss += KERNEL1x3(in_1, x, &kernel[6], 1);
out[x] = clip32(ss);
}
out[x] = in0[x];
}
} else {
for (y = 1; y < im->ysize - 1; y++) { for (y = 1; y < im->ysize - 1; y++) {
UINT8 *in_1 = (UINT8 *)im->image[y - 1]; UINT8 *in_1 = (UINT8 *)im->image[y - 1];
UINT8 *in0 = (UINT8 *)im->image[y]; UINT8 *in0 = (UINT8 *)im->image[y];
@ -121,6 +150,7 @@ ImagingFilter3x3(Imaging imOut, Imaging im, const float *kernel, float offset) {
} }
out[x] = in0[x]; out[x] = in0[x];
} }
}
} else { } else {
// Add one time for rounding // Add one time for rounding
offset += 0.5; offset += 0.5;
@ -195,10 +225,10 @@ ImagingFilter3x3(Imaging imOut, Imaging im, const float *kernel, float offset) {
void void
ImagingFilter5x5(Imaging imOut, Imaging im, const float *kernel, float offset) { ImagingFilter5x5(Imaging imOut, Imaging im, const float *kernel, float offset) {
#define KERNEL1x5(in0, x, kernel, d) \ #define KERNEL1x5(in0, x, kernel, d) \
(_i2f((UINT8)in0[x - d - d]) * (kernel)[0] + \ (_i2f(in0[x - d - d]) * (kernel)[0] + \
_i2f((UINT8)in0[x - d]) * (kernel)[1] + _i2f((UINT8)in0[x]) * (kernel)[2] + \ _i2f(in0[x - d]) * (kernel)[1] + _i2f(in0[x]) * (kernel)[2] + \
_i2f((UINT8)in0[x + d]) * (kernel)[3] + \ _i2f(in0[x + d]) * (kernel)[3] + \
_i2f((UINT8)in0[x + d + d]) * (kernel)[4]) _i2f(in0[x + d + d]) * (kernel)[4])
int x = 0, y = 0; int x = 0, y = 0;
@ -207,6 +237,30 @@ ImagingFilter5x5(Imaging imOut, Imaging im, const float *kernel, float offset) {
if (im->bands == 1) { if (im->bands == 1) {
// Add one time for rounding // Add one time for rounding
offset += 0.5; offset += 0.5;
if (im->type == IMAGING_TYPE_INT32) {
for (y = 2; y < im->ysize - 2; y++) {
INT32 *in_2 = (INT32 *)im->image[y - 2];
INT32 *in_1 = (INT32 *)im->image[y - 1];
INT32 *in0 = (INT32 *)im->image[y];
INT32 *in1 = (INT32 *)im->image[y + 1];
INT32 *in2 = (INT32 *)im->image[y + 2];
INT32 *out = (INT32 *)imOut->image[y];
out[0] = in0[0];
out[1] = in0[1];
for (x = 2; x < im->xsize - 2; x++) {
float ss = offset;
ss += KERNEL1x5(in2, x, &kernel[0], 1);
ss += KERNEL1x5(in1, x, &kernel[5], 1);
ss += KERNEL1x5(in0, x, &kernel[10], 1);
ss += KERNEL1x5(in_1, x, &kernel[15], 1);
ss += KERNEL1x5(in_2, x, &kernel[20], 1);
out[x] = clip32(ss);
}
out[x + 0] = in0[x + 0];
out[x + 1] = in0[x + 1];
}
} else {
for (y = 2; y < im->ysize - 2; y++) { for (y = 2; y < im->ysize - 2; y++) {
UINT8 *in_2 = (UINT8 *)im->image[y - 2]; UINT8 *in_2 = (UINT8 *)im->image[y - 2];
UINT8 *in_1 = (UINT8 *)im->image[y - 1]; UINT8 *in_1 = (UINT8 *)im->image[y - 1];
@ -229,6 +283,7 @@ ImagingFilter5x5(Imaging imOut, Imaging im, const float *kernel, float offset) {
out[x + 0] = in0[x + 0]; out[x + 0] = in0[x + 0];
out[x + 1] = in0[x + 1]; out[x + 1] = in0[x + 1];
} }
}
} else { } else {
// Add one time for rounding // Add one time for rounding
offset += 0.5; offset += 0.5;
@ -327,7 +382,7 @@ ImagingFilter(Imaging im, int xsize, int ysize, const FLOAT32 *kernel, FLOAT32 o
Imaging imOut; Imaging imOut;
ImagingSectionCookie cookie; ImagingSectionCookie cookie;
if (!im || im->type != IMAGING_TYPE_UINT8) { if (im->type != IMAGING_TYPE_UINT8 && im->type != IMAGING_TYPE_INT32) {
return (Imaging)ImagingError_ModeError(); return (Imaging)ImagingError_ModeError();
} }

View File

@ -281,7 +281,6 @@ j2k_encode_entry(Imaging im, ImagingCodecState state) {
int ret = -1; int ret = -1;
unsigned prec = 8; unsigned prec = 8;
unsigned bpp = 8;
unsigned _overflow_scale_factor; unsigned _overflow_scale_factor;
stream = opj_stream_create(BUFFER_SIZE, OPJ_FALSE); stream = opj_stream_create(BUFFER_SIZE, OPJ_FALSE);
@ -313,7 +312,6 @@ j2k_encode_entry(Imaging im, ImagingCodecState state) {
color_space = OPJ_CLRSPC_GRAY; color_space = OPJ_CLRSPC_GRAY;
pack = j2k_pack_i16; pack = j2k_pack_i16;
prec = 16; prec = 16;
bpp = 12;
} else if (strcmp(im->mode, "LA") == 0) { } else if (strcmp(im->mode, "LA") == 0) {
components = 2; components = 2;
color_space = OPJ_CLRSPC_GRAY; color_space = OPJ_CLRSPC_GRAY;
@ -342,7 +340,6 @@ j2k_encode_entry(Imaging im, ImagingCodecState state) {
image_params[n].h = im->ysize; image_params[n].h = im->ysize;
image_params[n].x0 = image_params[n].y0 = 0; image_params[n].x0 = image_params[n].y0 = 0;
image_params[n].prec = prec; image_params[n].prec = prec;
image_params[n].bpp = bpp;
image_params[n].sgnd = context->sgnd == 0 ? 0 : 1; image_params[n].sgnd = context->sgnd == 0 ? 0 : 1;
} }

View File

@ -1149,6 +1149,16 @@ unpackI16N_I16(UINT8 *out, const UINT8 *in, int pixels) {
} }
} }
static void static void
unpackI16B_I16(UINT8 *out, const UINT8 *in, int pixels) {
int i;
for (i = 0; i < pixels; i++) {
out[0] = in[1];
out[1] = in[0];
in += 2;
out += 2;
}
}
static void
unpackI16R_I16(UINT8 *out, const UINT8 *in, int pixels) { unpackI16R_I16(UINT8 *out, const UINT8 *in, int pixels) {
int i; int i;
for (i = 0; i < pixels; i++) { for (i = 0; i < pixels; i++) {
@ -1764,6 +1774,7 @@ static struct {
{"I;16L", "I;16L", 16, copy2}, {"I;16L", "I;16L", 16, copy2},
{"I;16N", "I;16N", 16, copy2}, {"I;16N", "I;16N", 16, copy2},
{"I;16", "I;16B", 16, unpackI16B_I16},
{"I;16", "I;16N", 16, unpackI16N_I16}, // LibTiff native->image endian. {"I;16", "I;16N", 16, unpackI16N_I16}, // LibTiff native->image endian.
{"I;16L", "I;16N", 16, unpackI16N_I16}, // LibTiff native->image endian. {"I;16L", "I;16N", 16, unpackI16N_I16}, // LibTiff native->image endian.
{"I;16B", "I;16N", 16, unpackI16N_I16B}, {"I;16B", "I;16N", 16, unpackI16N_I16B},

View File

@ -1,7 +1,7 @@
The MIT License (MIT) The MIT License (MIT)
Copyright © 2015 Information Technology Authority (ITA) <foss@ita.gov.om> Copyright © 2015 Information Technology Authority (ITA) <foss@ita.gov.om>
Copyright © 2016-2022 Khaled Hosny <khaled@aliftype.com> Copyright © 2016-2023 Khaled Hosny <khaled@aliftype.com>
Permission is hereby granted, free of charge, to any person obtaining a copy Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal of this software and associated documentation files (the "Software"), to deal

View File

@ -1,3 +1,38 @@
Overview of changes leading to 0.10.1
Wednesday, April 12, 2023
====================================
Make combining marks always inherit the script of their base.
Overview of changes leading to 0.10.0
Wednesday, January 11, 2023
====================================
Fix font feature ranges.
Fix resolved direction for all-neutral text.
Implement letter and word spacing support.
New API:
* raqm_set_text_utf16
Overview of changes leading to 0.9.0
Sunday, January 30, 2022
====================================
Raise the minimum versions of Raqm dependencies: no longer conditionally
enabling any features based on specific dependency version.
raqm_t objects can now be reused by calling raqm_clear_contents() before
re-use, to potentially reduce the number memory allocations.
Don't hardcode python3 in tests.
New API:
* raqm_set_freetype_load_flags_range
* raqm_clear_contents
Overview of changes leading to 0.8.0 Overview of changes leading to 0.8.0
Monday, December 13, 2021 Monday, December 13, 2021
==================================== ====================================

View File

@ -81,5 +81,5 @@ The following projects have patches to support complex text layout using Raqm:
[1]: https://github.com/fribidi/fribidi [1]: https://github.com/fribidi/fribidi
[2]: https://github.com/Tehreer/SheenBidi [2]: https://github.com/Tehreer/SheenBidi
[3]: https://github.com/harfbuzz/harfbuzz [3]: https://github.com/harfbuzz/harfbuzz
[4]: https://www.freetype.org [4]: https://freetype.org/
[5]: https://www.gtk.org/gtk-doc [5]: https://www.gtk.org/gtk-doc

View File

@ -33,9 +33,9 @@
#define RAQM_VERSION_MAJOR 0 #define RAQM_VERSION_MAJOR 0
#define RAQM_VERSION_MINOR 10 #define RAQM_VERSION_MINOR 10
#define RAQM_VERSION_MICRO 0 #define RAQM_VERSION_MICRO 1
#define RAQM_VERSION_STRING "0.10.0" #define RAQM_VERSION_STRING "0.10.1"
#define RAQM_VERSION_ATLEAST(major,minor,micro) \ #define RAQM_VERSION_ATLEAST(major,minor,micro) \
((major)*10000+(minor)*100+(micro) <= \ ((major)*10000+(minor)*100+(micro) <= \

View File

@ -1,6 +1,6 @@
/* /*
* Copyright © 2015 Information Technology Authority (ITA) <foss@ita.gov.om> * Copyright © 2015 Information Technology Authority (ITA) <foss@ita.gov.om>
* Copyright © 2016-2022 Khaled Hosny <khaled@aliftype.com> * Copyright © 2016-2023 Khaled Hosny <khaled@aliftype.com>
* *
* Permission is hereby granted, free of charge, to any person obtaining a copy * Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to * of this software and associated documentation files (the "Software"), to
@ -1432,7 +1432,7 @@ raqm_get_glyphs (raqm_t *rq,
* *
* Since: 0.8 * Since: 0.8
*/ */
RAQM_API raqm_direction_t raqm_direction_t
raqm_get_par_resolved_direction (raqm_t *rq) raqm_get_par_resolved_direction (raqm_t *rq)
{ {
if (!rq) if (!rq)
@ -1455,7 +1455,7 @@ raqm_get_par_resolved_direction (raqm_t *rq)
* *
* Since: 0.8 * Since: 0.8
*/ */
RAQM_API raqm_direction_t raqm_direction_t
raqm_get_direction_at_index (raqm_t *rq, raqm_get_direction_at_index (raqm_t *rq,
size_t index) size_t index)
{ {
@ -2021,6 +2021,22 @@ _get_pair_index (const uint32_t ch)
#define STACK_IS_EMPTY(script) ((script)->size <= 0) #define STACK_IS_EMPTY(script) ((script)->size <= 0)
#define IS_OPEN(pair_index) (((pair_index) & 1) == 0) #define IS_OPEN(pair_index) (((pair_index) & 1) == 0)
static hb_script_t
_raqm_unicode_script (hb_codepoint_t u)
{
static hb_unicode_funcs_t* unicode_funcs;
unicode_funcs = hb_unicode_funcs_get_default ();
/* Make combining marks inherit the script of their bases, regardless of
* their own script.
*/
if (hb_unicode_general_category (unicode_funcs, u) == HB_UNICODE_GENERAL_CATEGORY_NON_SPACING_MARK)
return HB_SCRIPT_INHERITED;
return hb_unicode_script (unicode_funcs, u);
}
/* Resolve the script for each character in the input string, if the character /* Resolve the script for each character in the input string, if the character
* script is common or inherited it takes the script of the character before it * script is common or inherited it takes the script of the character before it
* except paired characters which we try to make them use the same script. We * except paired characters which we try to make them use the same script. We
@ -2033,10 +2049,9 @@ _raqm_resolve_scripts (raqm_t *rq)
int last_set_index = -1; int last_set_index = -1;
hb_script_t last_script = HB_SCRIPT_INVALID; hb_script_t last_script = HB_SCRIPT_INVALID;
_raqm_stack_t *stack = NULL; _raqm_stack_t *stack = NULL;
hb_unicode_funcs_t* unicode_funcs = hb_unicode_funcs_get_default ();
for (size_t i = 0; i < rq->text_len; ++i) for (size_t i = 0; i < rq->text_len; ++i)
rq->text_info[i].script = hb_unicode_script (unicode_funcs, rq->text[i]); rq->text_info[i].script = _raqm_unicode_script (rq->text[i]);
#ifdef RAQM_TESTING #ifdef RAQM_TESTING
RAQM_TEST ("Before script detection:\n"); RAQM_TEST ("Before script detection:\n");

View File

@ -1,6 +1,6 @@
/* /*
* Copyright © 2015 Information Technology Authority (ITA) <foss@ita.gov.om> * Copyright © 2015 Information Technology Authority (ITA) <foss@ita.gov.om>
* Copyright © 2016-2022 Khaled Hosny <khaled@aliftype.com> * Copyright © 2016-2023 Khaled Hosny <khaled@aliftype.com>
* *
* Permission is hereby granted, free of charge, to any person obtaining a copy * Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to * of this software and associated documentation files (the "Software"), to

View File

@ -1,6 +1,7 @@
[tox] [tox]
minversion = 1.9 requires =
envlist = tox>=4.2
env_list =
lint lint
py{py3, 311, 310, 39, 38} py{py3, 311, 310, 39, 38}
@ -23,7 +24,7 @@ skip_install = true
deps = deps =
check-manifest check-manifest
pre-commit pre-commit
passenv = pass_env =
PRE_COMMIT_COLOR PRE_COMMIT_COLOR
commands = commands =
pre-commit run --all-files --show-diff-on-failure pre-commit run --all-files --show-diff-on-failure

View File

@ -337,9 +337,9 @@ deps = {
"libs": [r"imagequant.lib"], "libs": [r"imagequant.lib"],
}, },
"harfbuzz": { "harfbuzz": {
"url": "https://github.com/harfbuzz/harfbuzz/archive/7.1.0.zip", "url": "https://github.com/harfbuzz/harfbuzz/archive/7.2.0.zip",
"filename": "harfbuzz-7.1.0.zip", "filename": "harfbuzz-7.2.0.zip",
"dir": "harfbuzz-7.1.0", "dir": "harfbuzz-7.2.0",
"license": "COPYING", "license": "COPYING",
"build": [ "build": [
*cmds_cmake( *cmds_cmake(