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:\
- mv c:\pillow-depends-main c:\pillow-depends
- 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
- 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\
- ps: |
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
- name: Install Cygwin
uses: cygwin/cygwin-install-action@v3
uses: cygwin/cygwin-install-action@v4
with:
platform: x86_64
packages: >
@ -84,6 +84,10 @@ jobs:
restore-keys: |
${{ 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
run: |
dash.exe -c "python3 .github/workflows/system-info.py"
@ -95,7 +99,7 @@ jobs:
- name: Install a different NumPy
shell: dash.exe -l "{0}"
run: |
python3 -m pip install -U 'numpy!=1.21.*'
python3 -m pip install -U numpy
- name: Build
shell: bash.exe -eo pipefail -o igncr "{0}"

View File

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

View File

@ -80,7 +80,7 @@ jobs:
pushd depends && ./install_extra_test_images.sh && popd
- 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
run: |

View File

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

View File

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

View File

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

View File

@ -5,6 +5,30 @@ Changelog (Pillow)
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
[adisbladis, radarhere]

View File

@ -75,48 +75,45 @@ post-patch:
"""
def test_qtables_leak():
im = hopper("RGB")
standard_l_qtable = (
# 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 = [
int(s)
for s in """
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
""".split(
None
)
]
standard_chrominance_qtable = (
# fmt: off
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,
# fmt: on
)
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],
):
for _ in range(iterations):
test_output = BytesIO()
im.save(test_output, "JPEG", qtables=qtables)
),
)
def test_qtables_leak(qtables):
im = hopper("RGB")
for _ in range(iterations):
test_output = BytesIO()
im.save(test_output, "JPEG", qtables=qtables)
def test_exif_leak():

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):
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):
with Image.open("Tests/images/16bit.cropped.tif") as im:
assert im.getpixel((0, 0)) == 480

View File

@ -4,7 +4,7 @@ import pytest
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"))
@ -42,3 +42,10 @@ def test_copy_zero():
out = im.copy()
assert out.mode == im.mode
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),
),
)
@pytest.mark.parametrize("mode", ("L", "RGB", "CMYK"))
@pytest.mark.parametrize("mode", ("L", "I", "RGB", "CMYK"))
def test_sanity(filter_to_apply, mode):
im = hopper(mode)
out = im.filter(filter_to_apply)
assert out.mode == im.mode
assert out.size == im.size
if mode != "I" or isinstance(filter_to_apply, ImageFilter.BuiltinFilter):
out = im.filter(filter_to_apply)
assert out.mode == im.mode
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):
with pytest.raises(TypeError):
im = hopper(mode)
@ -130,10 +131,12 @@ def test_kernel_not_enough_coefficients():
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):
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(
(3, 3),
# fmt: off
@ -146,16 +149,20 @@ def test_consistency_3x3(mode):
source = source.split() * 2
reference = reference.split() * 2
assert_image_equal(
Image.merge(mode, source[: len(mode)]).filter(kernel),
Image.merge(mode, reference[: len(mode)]),
)
if mode == "I":
source = source[0].convert(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):
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(
(5, 5),
# fmt: off
@ -170,10 +177,12 @@ def test_consistency_5x5(mode):
source = source.split() * 2
reference = reference.split() * 2
assert_image_equal(
Image.merge(mode, source[: len(mode)]).filter(kernel),
Image.merge(mode, reference[: len(mode)]),
)
if mode == "I":
source = source[0].convert(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():

View File

@ -1380,20 +1380,20 @@ def test_same_color_outline(bbox):
@pytest.mark.parametrize(
"n_sides, rotation, polygon_name",
[(4, 0, "square"), (8, 0, "regular_octagon"), (4, 45, "square")],
"n_sides, polygon_name, args",
[
(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))
filename_base = f"Tests/images/imagedraw_{polygon_name}"
filename = (
f"{filename_base}.png"
if rotation == 0
else f"{filename_base}_rotate_{rotation}.png"
)
filename = f"Tests/images/imagedraw_{polygon_name}.png"
draw = ImageDraw.Draw(im)
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)

View File

@ -191,6 +191,16 @@ def test_getlength(
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):
im = Image.new(mode="RGB", size=(300, 100))
draw = ImageDraw.Draw(im)

View File

@ -28,7 +28,7 @@ def test_path():
(6.0, 7.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)
@ -38,48 +38,65 @@ def test_path():
p.transform((1, 0, 1, 0, 1, 1))
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])
p = ImagePath.Path(arr.tobytes())
@pytest.mark.parametrize(
"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)]
def test_invalid_coords():
# Arrange
coords = ["a", "b"]
# Act / Assert
@pytest.mark.parametrize(
"coords",
(
("a", "b"),
([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:
ImagePath.Path(coords)
# Assert
assert str(e.value) == "incorrect coordinate type"
def test_path_odd_number_of_coordinates():
# Arrange
coords = [0]
# Act / Assert
@pytest.mark.parametrize(
"coords",
(
(0,),
[0],
(0, 1, 2),
[0, 1, 2],
),
)
def test_path_odd_number_of_coordinates(coords):
# Act
with pytest.raises(ValueError) as e:
ImagePath.Path(coords)
# Assert
assert str(e.value) == "wrong number of coordinates"

View File

@ -757,6 +757,7 @@ class TestLibUnpack:
def test_I16(self):
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;16L", "I;16L", 2, 0x0201, 0x0403, 0x0605)
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([123, True, {"a": PdfName(b"b")}]) == b"[ 123 true <<\n/a /b\n>> ]"
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
# 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

View File

@ -2,7 +2,7 @@
# 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

View File

@ -181,7 +181,7 @@ Many of Pillow's features require external libraries:
* **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
the Pillow license, therefore we will not be distributing binaries
with libimagequant support enabled.
@ -448,17 +448,15 @@ These platforms are built and tested for every change.
+----------------------------------+----------------------------+---------------------+
| Debian 11 Bullseye | 3.9 | x86 |
+----------------------------------+----------------------------+---------------------+
| Fedora 36 | 3.10 | x86-64 |
+----------------------------------+----------------------------+---------------------+
| Fedora 37 | 3.11 | x86-64 |
+----------------------------------+----------------------------+---------------------+
| Fedora 38 | 3.11 | x86-64 |
+----------------------------------+----------------------------+---------------------+
| Gentoo | 3.9 | x86-64 |
+----------------------------------+----------------------------+---------------------+
| macOS 12 Monterey | 3.8, 3.9, 3.10, 3.11, | x86-64 |
| | 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 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 |
| | | 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 |
+----------------------------------+---------------------------+------------------+--------------+

View File

@ -439,7 +439,7 @@ Used to specify the dithering method to use for the
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
:members:

View File

@ -296,7 +296,7 @@ Methods
: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``,
with ``n_sides``, and rotation of ``rotation`` degrees.
@ -311,6 +311,7 @@ Methods
(e.g. ``rotation=90``, applies a 90 degree rotation).
:param fill: Color to use for the fill.
: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)

View File

@ -48,7 +48,7 @@ vector data. Path objects can be passed to the methods on the
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), …].

View File

@ -135,10 +135,11 @@ TODO
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
=============
@ -159,7 +160,8 @@ TODO
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
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):
Image.__init__(self)

View File

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

View File

@ -279,11 +279,11 @@ class ImageDraw:
self.im.paste(im.im, (0, 0) + im.size, mask.im)
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."""
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):
"""Draw a rectangle."""

View File

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

View File

@ -957,14 +957,11 @@ class PdfParser:
check_format_condition(m, "xref entry not found")
offset = m.end()
is_free = m.group(3) == b"f"
generation = int(m.group(2))
if not is_free:
generation = int(m.group(2))
new_entry = (int(m.group(1)), generation)
check_format_condition(
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
if i not in self.xref_table:
self.xref_table[i] = new_entry
return offset
def read_indirect(self, ref, max_nesting=-1):

View File

@ -170,6 +170,8 @@ OPEN_INFO = {
(MM, 0, (1,), 2, (8,), ()): ("L", "L;IR"),
(II, 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"),
(MM, 1, (1,), 2, (8,), ()): ("L", "L;R"),
(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;
char *filename = NULL;
Py_ssize_t size;
float size;
FT_Size_RequestRec req;
FT_Long width;
Py_ssize_t index = 0;
Py_ssize_t layout_engine = 0;
unsigned char *encoding;
@ -133,7 +135,7 @@ getfont(PyObject *self_, PyObject *args, PyObject *kw) {
if (!PyArg_ParseTupleAndKeywords(
args,
kw,
"etn|nsy#n",
"etf|nsy#n",
kwlist,
Py_FileSystemDefaultEncoding,
&filename,
@ -179,7 +181,13 @@ getfont(PyObject *self_, PyObject *args, PyObject *kw) {
}
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) {

View File

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

View File

@ -281,7 +281,6 @@ j2k_encode_entry(Imaging im, ImagingCodecState state) {
int ret = -1;
unsigned prec = 8;
unsigned bpp = 8;
unsigned _overflow_scale_factor;
stream = opj_stream_create(BUFFER_SIZE, OPJ_FALSE);
@ -313,7 +312,6 @@ j2k_encode_entry(Imaging im, ImagingCodecState state) {
color_space = OPJ_CLRSPC_GRAY;
pack = j2k_pack_i16;
prec = 16;
bpp = 12;
} else if (strcmp(im->mode, "LA") == 0) {
components = 2;
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].x0 = image_params[n].y0 = 0;
image_params[n].prec = prec;
image_params[n].bpp = bpp;
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
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) {
int i;
for (i = 0; i < pixels; i++) {
@ -1764,6 +1774,7 @@ static struct {
{"I;16L", "I;16L", 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;16L", "I;16N", 16, unpackI16N_I16}, // LibTiff native->image endian.
{"I;16B", "I;16N", 16, unpackI16N_I16B},

View File

@ -1,7 +1,7 @@
The MIT License (MIT)
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
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
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
[2]: https://github.com/Tehreer/SheenBidi
[3]: https://github.com/harfbuzz/harfbuzz
[4]: https://www.freetype.org
[4]: https://freetype.org/
[5]: https://www.gtk.org/gtk-doc

View File

@ -33,9 +33,9 @@
#define RAQM_VERSION_MAJOR 0
#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) \
((major)*10000+(minor)*100+(micro) <= \

View File

@ -1,6 +1,6 @@
/*
* 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
* of this software and associated documentation files (the "Software"), to
@ -1432,7 +1432,7 @@ raqm_get_glyphs (raqm_t *rq,
*
* Since: 0.8
*/
RAQM_API raqm_direction_t
raqm_direction_t
raqm_get_par_resolved_direction (raqm_t *rq)
{
if (!rq)
@ -1455,7 +1455,7 @@ raqm_get_par_resolved_direction (raqm_t *rq)
*
* Since: 0.8
*/
RAQM_API raqm_direction_t
raqm_direction_t
raqm_get_direction_at_index (raqm_t *rq,
size_t index)
{
@ -2021,6 +2021,22 @@ _get_pair_index (const uint32_t ch)
#define STACK_IS_EMPTY(script) ((script)->size <= 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
* 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
@ -2033,10 +2049,9 @@ _raqm_resolve_scripts (raqm_t *rq)
int last_set_index = -1;
hb_script_t last_script = HB_SCRIPT_INVALID;
_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)
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
RAQM_TEST ("Before script detection:\n");

View File

@ -1,6 +1,6 @@
/*
* 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
* of this software and associated documentation files (the "Software"), to

View File

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

View File

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