diff --git a/.git-blame-ignore-revs b/.git-blame-ignore-revs
new file mode 100644
index 000000000..a2be59c52
--- /dev/null
+++ b/.git-blame-ignore-revs
@@ -0,0 +1,6 @@
+# Flake8
+8de95676e0fd89f2326b3953488ab66ff29cd2d0
+# Format with Black
+53a7e3500437a9fd5826bc04758f7116bd7e52dc
+# Format the C code with ClangFormat
+46b7e86bab79450ec0a2866c6c0c679afb659d17
diff --git a/.github/workflows/wheels.yml b/.github/workflows/wheels.yml
index 76d42b470..060fc497e 100644
--- a/.github/workflows/wheels.yml
+++ b/.github/workflows/wheels.yml
@@ -75,9 +75,9 @@ jobs:
           CIBW_TEST_SKIP: "*-macosx_arm64"
           MACOSX_DEPLOYMENT_TARGET: ${{ matrix.macosx_deployment_target }}
 
-      - uses: actions/upload-artifact@v3
+      - uses: actions/upload-artifact@v4
         with:
-          name: dist
+          name: dist-${{ matrix.os }}-${{ matrix.archs }}${{ matrix.manylinux && format('-{0}', matrix.manylinux) }}
           path: ./wheelhouse/*.whl
 
   windows:
@@ -116,10 +116,7 @@ jobs:
 
           & python.exe -m pip install -r .ci/requirements-cibw.txt
 
-          # Cannot cross-compile FriBiDi (only used for tests)
-          $FLAGS = ("--no-imagequant", "--architecture=${{ matrix.arch }}")
-          if ('${{ matrix.arch }}' -eq 'ARM64') { $FLAGS += "--no-fribidi" }
-          & python.exe winbuild\build_prepare.py -v @FLAGS
+          & python.exe winbuild\build_prepare.py -v --no-imagequant --architecture=${{ matrix.arch }}
         shell: pwsh
 
       - name: Build wheels
@@ -157,24 +154,16 @@ jobs:
         shell: cmd
 
       - name: Upload wheels
-        uses: actions/upload-artifact@v3
+        uses: actions/upload-artifact@v4
         with:
-          name: dist
+          name: dist-windows-${{ matrix.arch }}
           path: ./wheelhouse/*.whl
 
-      - name: Prepare to upload FriBiDi
-        if: "matrix.arch != 'ARM64'"
-        run: |
-          mkdir fribidi\${{ matrix.arch }}
-          copy winbuild\build\bin\fribidi* fribidi\${{ matrix.arch }}
-        shell: cmd
-
       - name: Upload fribidi.dll
-        if: "matrix.arch != 'ARM64'"
-        uses: actions/upload-artifact@v3
+        uses: actions/upload-artifact@v4
         with:
-          name: fribidi
-          path: fribidi\*
+          name: fribidi-windows-${{ matrix.arch }}
+          path: winbuild\build\bin\fribidi*
 
   sdist:
     runs-on: ubuntu-latest
@@ -190,17 +179,26 @@ jobs:
 
     - run: make sdist
 
-    - uses: actions/upload-artifact@v3
+    - uses: actions/upload-artifact@v4
       with:
-        name: dist
+        name: dist-sdist
         path: dist/*.tar.gz
 
-  success:
-    permissions:
-      contents: none
+  pypi-publish:
+    if: github.event_name == 'push' && startsWith(github.ref, 'refs/tags')
     needs: [build, windows, sdist]
     runs-on: ubuntu-latest
-    name: Wheels Successful
+    name: Upload release to PyPI
+    environment:
+      name: release-pypi
+      url: https://pypi.org/p/Pillow
+    permissions:
+      id-token: write
     steps:
-      - name: Success
-        run: echo Wheels Successful
+      - uses: actions/download-artifact@v4
+        with:
+          pattern: dist-*
+          path: dist
+          merge-multiple: true
+      - name: Publish to PyPI
+        uses: pypa/gh-action-pypi-publish@release/v1
diff --git a/CHANGES.rst b/CHANGES.rst
index f69c3ffa7..df4e11e0e 100644
--- a/CHANGES.rst
+++ b/CHANGES.rst
@@ -5,6 +5,9 @@ Changelog (Pillow)
 10.2.0 (unreleased)
 -------------------
 
+- Fix incorrect color blending for overlapping glyphs #7497
+  [ZachNagengast, nulano, radarhere]
+
 - Attempt memory mapping when tile args is a string #7565
   [radarhere]
 
diff --git a/Tests/fonts/CBDTTestFont.ttf b/Tests/fonts/CBDTTestFont.ttf
new file mode 100644
index 000000000..73444e8dc
Binary files /dev/null and b/Tests/fonts/CBDTTestFont.ttf differ
diff --git a/Tests/fonts/EBDTTestFont.ttf b/Tests/fonts/EBDTTestFont.ttf
new file mode 100644
index 000000000..046e9e45c
Binary files /dev/null and b/Tests/fonts/EBDTTestFont.ttf differ
diff --git a/Tests/fonts/LICENSE.txt b/Tests/fonts/LICENSE.txt
index da559b3d3..3c8a23197 100644
--- a/Tests/fonts/LICENSE.txt
+++ b/Tests/fonts/LICENSE.txt
@@ -2,7 +2,6 @@
 NotoNastaliqUrdu-Regular.ttf and NotoSansSymbols-Regular.ttf, from https://github.com/googlei18n/noto-fonts
 NotoSans-Regular.ttf, from https://www.google.com/get/noto/
 NotoSansJP-Thin.otf, from https://www.google.com/get/noto/help/cjk/
-NotoColorEmoji.ttf, from https://github.com/googlefonts/noto-emoji
 AdobeVFPrototype.ttf, from https://github.com/adobe-fonts/adobe-variable-font-prototype
 TINY5x3GX.ttf, from http://velvetyne.fr/fonts/tiny
 ArefRuqaa-Regular.ttf, from https://github.com/google/fonts/tree/master/ofl/arefruqaa
@@ -25,3 +24,5 @@ FreeMono.ttf is licensed under GPLv3.
 10x20-ISO8859-1.pcf, from https://packages.ubuntu.com/xenial/xfonts-base
 
 "Public domain font.  Share and enjoy."
+
+CBDTTestFont.ttf and EBDTTestFont.ttf from https://github.com/nulano/font-tests are public domain.
diff --git a/Tests/fonts/NotoColorEmoji.ttf b/Tests/fonts/NotoColorEmoji.ttf
deleted file mode 100644
index ef7b72575..000000000
Binary files a/Tests/fonts/NotoColorEmoji.ttf and /dev/null differ
diff --git a/Tests/images/bitmap_font_blend.png b/Tests/images/bitmap_font_blend.png
new file mode 100644
index 000000000..a5acf3667
Binary files /dev/null and b/Tests/images/bitmap_font_blend.png differ
diff --git a/Tests/images/bitmap_font_stroke_basic.png b/Tests/images/bitmap_font_stroke_basic.png
index 86b2d09f6..26aa3ab8e 100644
Binary files a/Tests/images/bitmap_font_stroke_basic.png and b/Tests/images/bitmap_font_stroke_basic.png differ
diff --git a/Tests/images/bitmap_font_stroke_raqm.png b/Tests/images/bitmap_font_stroke_raqm.png
index 08029ce34..be273d7cb 100644
Binary files a/Tests/images/bitmap_font_stroke_raqm.png and b/Tests/images/bitmap_font_stroke_raqm.png differ
diff --git a/Tests/images/cbdt.png b/Tests/images/cbdt.png
new file mode 100644
index 000000000..542bb812e
Binary files /dev/null and b/Tests/images/cbdt.png differ
diff --git a/Tests/images/cbdt_mask.png b/Tests/images/cbdt_mask.png
new file mode 100644
index 000000000..b0854a605
Binary files /dev/null and b/Tests/images/cbdt_mask.png differ
diff --git a/Tests/images/cbdt_notocoloremoji.png b/Tests/images/cbdt_notocoloremoji.png
deleted file mode 100644
index 1da12fba1..000000000
Binary files a/Tests/images/cbdt_notocoloremoji.png and /dev/null differ
diff --git a/Tests/images/cbdt_notocoloremoji_mask.png b/Tests/images/cbdt_notocoloremoji_mask.png
deleted file mode 100644
index 6d036a0b6..000000000
Binary files a/Tests/images/cbdt_notocoloremoji_mask.png and /dev/null differ
diff --git a/Tests/images/default_font_freetype.png b/Tests/images/default_font_freetype.png
index e00bb5d85..bc1654a25 100644
Binary files a/Tests/images/default_font_freetype.png and b/Tests/images/default_font_freetype.png differ
diff --git a/Tests/images/test_combine_caron_below_ttb.png b/Tests/images/test_combine_caron_below_ttb.png
index 5c7576de0..2b7cc89ea 100644
Binary files a/Tests/images/test_combine_caron_below_ttb.png and b/Tests/images/test_combine_caron_below_ttb.png differ
diff --git a/Tests/images/test_combine_caron_below_ttb_lb.png b/Tests/images/test_combine_caron_below_ttb_lb.png
index bacd6a141..3ced2dbfc 100644
Binary files a/Tests/images/test_combine_caron_below_ttb_lb.png and b/Tests/images/test_combine_caron_below_ttb_lb.png differ
diff --git a/Tests/images/test_combine_caron_ttb.png b/Tests/images/test_combine_caron_ttb.png
index a94be2f0a..569cc1ec3 100644
Binary files a/Tests/images/test_combine_caron_ttb.png and b/Tests/images/test_combine_caron_ttb.png differ
diff --git a/Tests/images/test_combine_caron_ttb_lt.png b/Tests/images/test_combine_caron_ttb_lt.png
index a94be2f0a..569cc1ec3 100644
Binary files a/Tests/images/test_combine_caron_ttb_lt.png and b/Tests/images/test_combine_caron_ttb_lt.png differ
diff --git a/Tests/oss-fuzz/fuzz_font.py b/Tests/oss-fuzz/fuzz_font.py
index 4e7c7deec..024117c56 100755
--- a/Tests/oss-fuzz/fuzz_font.py
+++ b/Tests/oss-fuzz/fuzz_font.py
@@ -1,5 +1,7 @@
 #!/usr/bin/python3
 
+from __future__ import annotations
+
 # Copyright 2020 Google LLC
 #
 # Licensed under the Apache License, Version 2.0 (the "License");
@@ -13,7 +15,6 @@
 # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 # See the License for the specific language governing permissions and
 # limitations under the License.
-from __future__ import annotations
 
 
 import atheris
diff --git a/Tests/oss-fuzz/fuzz_pillow.py b/Tests/oss-fuzz/fuzz_pillow.py
index e7cd0474a..c1ab42e56 100644
--- a/Tests/oss-fuzz/fuzz_pillow.py
+++ b/Tests/oss-fuzz/fuzz_pillow.py
@@ -1,5 +1,7 @@
 #!/usr/bin/python3
 
+from __future__ import annotations
+
 # Copyright 2020 Google LLC
 #
 # Licensed under the Apache License, Version 2.0 (the "License");
@@ -13,7 +15,6 @@
 # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 # See the License for the specific language governing permissions and
 # limitations under the License.
-from __future__ import annotations
 
 
 import atheris
diff --git a/Tests/test_imagefont.py b/Tests/test_imagefont.py
index 6ad56e2b1..6e04cddc7 100644
--- a/Tests/test_imagefont.py
+++ b/Tests/test_imagefont.py
@@ -859,6 +859,19 @@ def test_bitmap_font_stroke(layout_engine):
     assert_image_similar_tofile(im, target, 0.03)
 
 
+@pytest.mark.parametrize("embedded_color", (False, True))
+def test_bitmap_blend(layout_engine, embedded_color):
+    font = ImageFont.truetype(
+        "Tests/fonts/EBDTTestFont.ttf", size=64, layout_engine=layout_engine
+    )
+
+    im = Image.new("RGBA", (128, 96), "white")
+    d = ImageDraw.Draw(im)
+    d.text((16, 16), "AA", font=font, fill="#8E2F52", embedded_color=embedded_color)
+
+    assert_image_equal_tofile(im, "Tests/images/bitmap_font_blend.png")
+
+
 def test_standard_embedded_color(layout_engine):
     txt = "Hello World!"
     ttf = ImageFont.truetype(FONT_PATH, 40, layout_engine=layout_engine)
@@ -897,15 +910,15 @@ def test_float_coord(layout_engine, fontmode):
 def test_cbdt(layout_engine):
     try:
         font = ImageFont.truetype(
-            "Tests/fonts/NotoColorEmoji.ttf", size=109, layout_engine=layout_engine
+            "Tests/fonts/CBDTTestFont.ttf", size=64, layout_engine=layout_engine
         )
 
-        im = Image.new("RGB", (150, 150), "white")
+        im = Image.new("RGB", (128, 96), "white")
         d = ImageDraw.Draw(im)
 
-        d.text((10, 10), "\U0001f469", font=font, embedded_color=True)
+        d.text((16, 16), "AB", font=font, embedded_color=True)
 
-        assert_image_similar_tofile(im, "Tests/images/cbdt_notocoloremoji.png", 6.2)
+        assert_image_equal_tofile(im, "Tests/images/cbdt.png")
     except OSError as e:  # pragma: no cover
         assert str(e) in ("unimplemented feature", "unknown file format")
         pytest.skip("freetype compiled without libpng or CBDT support")
@@ -914,17 +927,15 @@ def test_cbdt(layout_engine):
 def test_cbdt_mask(layout_engine):
     try:
         font = ImageFont.truetype(
-            "Tests/fonts/NotoColorEmoji.ttf", size=109, layout_engine=layout_engine
+            "Tests/fonts/CBDTTestFont.ttf", size=64, layout_engine=layout_engine
         )
 
-        im = Image.new("RGB", (150, 150), "white")
+        im = Image.new("RGB", (128, 96), "white")
         d = ImageDraw.Draw(im)
 
-        d.text((10, 10), "\U0001f469", "black", font=font)
+        d.text((16, 16), "AB", "green", font=font)
 
-        assert_image_similar_tofile(
-            im, "Tests/images/cbdt_notocoloremoji_mask.png", 6.2
-        )
+        assert_image_equal_tofile(im, "Tests/images/cbdt_mask.png")
     except OSError as e:  # pragma: no cover
         assert str(e) in ("unimplemented feature", "unknown file format")
         pytest.skip("freetype compiled without libpng or CBDT support")
diff --git a/docs/conf.py b/docs/conf.py
index 9974b0f2a..a70dece74 100644
--- a/docs/conf.py
+++ b/docs/conf.py
@@ -233,7 +233,7 @@ htmlhelp_basename = "PillowPILForkdoc"
 
 # -- Options for LaTeX output ---------------------------------------------
 
-latex_elements = {
+latex_elements: dict[str, str] = {
     # The paper size ('letterpaper' or 'a4paper').
     # 'papersize': 'letterpaper',
     # The font size ('10pt', '11pt' or '12pt').
diff --git a/docs/example/anchors.py b/docs/example/anchors.py
index 3a0e40b84..b5d76b4fe 100644
--- a/docs/example/anchors.py
+++ b/docs/example/anchors.py
@@ -5,7 +5,7 @@ from PIL import Image, ImageDraw, ImageFont
 font = ImageFont.truetype("Tests/fonts/NotoSans-Regular.ttf", 16)
 
 
-def test(anchor):
+def test(anchor: str) -> Image.Image:
     im = Image.new("RGBA", (200, 100), "white")
     d = ImageDraw.Draw(im)
     d.line(((100, 0), (100, 100)), "gray")
diff --git a/selftest.py b/selftest.py
index 600fd6496..ed5252c44 100755
--- a/selftest.py
+++ b/selftest.py
@@ -15,7 +15,7 @@ except AttributeError:
     pass
 
 
-def testimage():
+def testimage() -> None:
     """
     PIL lets you create in-memory images with various pixel types:
 
diff --git a/src/PIL/ContainerIO.py b/src/PIL/ContainerIO.py
index 387a4c182..64d042426 100644
--- a/src/PIL/ContainerIO.py
+++ b/src/PIL/ContainerIO.py
@@ -24,7 +24,7 @@ class ContainerIO:
     file (for example a TAR file).
     """
 
-    def __init__(self, file, offset, length):
+    def __init__(self, file, offset, length) -> None:
         """
         Create file object.
 
diff --git a/src/PIL/Image.py b/src/PIL/Image.py
index ab8f1d4a0..49e3cfe9b 100644
--- a/src/PIL/Image.py
+++ b/src/PIL/Image.py
@@ -1181,7 +1181,7 @@ class Image:
 
         return im
 
-    def copy(self):
+    def copy(self) -> Image:
         """
         Copies this image. Use this method if you wish to paste things
         into an image, but still retain the original.
@@ -2467,7 +2467,7 @@ class Image:
             }
         )
 
-    def seek(self, frame):
+    def seek(self, frame) -> Image:
         """
         Seeks to the given frame in this sequence file. If you seek
         beyond the end of the sequence, the method raises an
@@ -2554,7 +2554,7 @@ class Image:
 
         return self._new(self.im.getband(channel))
 
-    def tell(self):
+    def tell(self) -> int:
         """
         Returns the current frame number. See :py:meth:`~PIL.Image.Image.seek`.
 
diff --git a/src/PIL/ImageDraw.py b/src/PIL/ImageDraw.py
index 8611dcc36..84665f54f 100644
--- a/src/PIL/ImageDraw.py
+++ b/src/PIL/ImageDraw.py
@@ -33,6 +33,7 @@ from __future__ import annotations
 
 import math
 import numbers
+import struct
 
 from . import Image, ImageColor
 
@@ -543,7 +544,8 @@ class ImageDraw:
                 # font.getmask2(mode="RGBA") returns color in RGB bands and mask in A
                 # extract mask and set text alpha
                 color, mask = mask, mask.getband(3)
-                color.fillband(3, (ink >> 24) & 0xFF)
+                ink_alpha = struct.pack("i", ink)[3]
+                color.fillband(3, ink_alpha)
                 x, y = coord
                 self.im.paste(color, (x, y, x + mask.size[0], y + mask.size[1]), mask)
             else:
diff --git a/src/PIL/ImageMode.py b/src/PIL/ImageMode.py
index 54c3d01c4..0b31f6081 100644
--- a/src/PIL/ImageMode.py
+++ b/src/PIL/ImageMode.py
@@ -15,77 +15,82 @@
 from __future__ import annotations
 
 import sys
-
-# mode descriptor cache
-_modes = None
+from functools import lru_cache
 
 
 class ModeDescriptor:
     """Wrapper for mode strings."""
 
-    def __init__(self, mode, bands, basemode, basetype, typestr):
+    def __init__(
+        self,
+        mode: str,
+        bands: tuple[str, ...],
+        basemode: str,
+        basetype: str,
+        typestr: str,
+    ) -> None:
         self.mode = mode
         self.bands = bands
         self.basemode = basemode
         self.basetype = basetype
         self.typestr = typestr
 
-    def __str__(self):
+    def __str__(self) -> str:
         return self.mode
 
 
-def getmode(mode):
+@lru_cache
+def getmode(mode: str) -> ModeDescriptor:
     """Gets a mode descriptor for the given mode."""
-    global _modes
-    if not _modes:
-        # initialize mode cache
-        modes = {}
-        endian = "<" if sys.byteorder == "little" else ">"
-        for m, (basemode, basetype, bands, typestr) in {
-            # core modes
-            # Bits need to be extended to bytes
-            "1": ("L", "L", ("1",), "|b1"),
-            "L": ("L", "L", ("L",), "|u1"),
-            "I": ("L", "I", ("I",), endian + "i4"),
-            "F": ("L", "F", ("F",), endian + "f4"),
-            "P": ("P", "L", ("P",), "|u1"),
-            "RGB": ("RGB", "L", ("R", "G", "B"), "|u1"),
-            "RGBX": ("RGB", "L", ("R", "G", "B", "X"), "|u1"),
-            "RGBA": ("RGB", "L", ("R", "G", "B", "A"), "|u1"),
-            "CMYK": ("RGB", "L", ("C", "M", "Y", "K"), "|u1"),
-            "YCbCr": ("RGB", "L", ("Y", "Cb", "Cr"), "|u1"),
-            # UNDONE - unsigned |u1i1i1
-            "LAB": ("RGB", "L", ("L", "A", "B"), "|u1"),
-            "HSV": ("RGB", "L", ("H", "S", "V"), "|u1"),
-            # extra experimental modes
-            "RGBa": ("RGB", "L", ("R", "G", "B", "a"), "|u1"),
-            "BGR;15": ("RGB", "L", ("B", "G", "R"), "|u1"),
-            "BGR;16": ("RGB", "L", ("B", "G", "R"), "|u1"),
-            "BGR;24": ("RGB", "L", ("B", "G", "R"), "|u1"),
-            "LA": ("L", "L", ("L", "A"), "|u1"),
-            "La": ("L", "L", ("L", "a"), "|u1"),
-            "PA": ("RGB", "L", ("P", "A"), "|u1"),
-        }.items():
-            modes[m] = ModeDescriptor(m, bands, basemode, basetype, typestr)
-        # mapping modes
-        for i16mode, typestr in {
-            # I;16 == I;16L, and I;32 == I;32L
-            "I;16": "<u2",
-            "I;16S": "<i2",
-            "I;16L": "<u2",
-            "I;16LS": "<i2",
-            "I;16B": ">u2",
-            "I;16BS": ">i2",
-            "I;16N": endian + "u2",
-            "I;16NS": endian + "i2",
-            "I;32": "<u4",
-            "I;32B": ">u4",
-            "I;32L": "<u4",
-            "I;32S": "<i4",
-            "I;32BS": ">i4",
-            "I;32LS": "<i4",
-        }.items():
-            modes[i16mode] = ModeDescriptor(i16mode, ("I",), "L", "L", typestr)
-        # set global mode cache atomically
-        _modes = modes
-    return _modes[mode]
+    # initialize mode cache
+    endian = "<" if sys.byteorder == "little" else ">"
+
+    modes = {
+        # core modes
+        # Bits need to be extended to bytes
+        "1": ("L", "L", ("1",), "|b1"),
+        "L": ("L", "L", ("L",), "|u1"),
+        "I": ("L", "I", ("I",), endian + "i4"),
+        "F": ("L", "F", ("F",), endian + "f4"),
+        "P": ("P", "L", ("P",), "|u1"),
+        "RGB": ("RGB", "L", ("R", "G", "B"), "|u1"),
+        "RGBX": ("RGB", "L", ("R", "G", "B", "X"), "|u1"),
+        "RGBA": ("RGB", "L", ("R", "G", "B", "A"), "|u1"),
+        "CMYK": ("RGB", "L", ("C", "M", "Y", "K"), "|u1"),
+        "YCbCr": ("RGB", "L", ("Y", "Cb", "Cr"), "|u1"),
+        # UNDONE - unsigned |u1i1i1
+        "LAB": ("RGB", "L", ("L", "A", "B"), "|u1"),
+        "HSV": ("RGB", "L", ("H", "S", "V"), "|u1"),
+        # extra experimental modes
+        "RGBa": ("RGB", "L", ("R", "G", "B", "a"), "|u1"),
+        "BGR;15": ("RGB", "L", ("B", "G", "R"), "|u1"),
+        "BGR;16": ("RGB", "L", ("B", "G", "R"), "|u1"),
+        "BGR;24": ("RGB", "L", ("B", "G", "R"), "|u1"),
+        "LA": ("L", "L", ("L", "A"), "|u1"),
+        "La": ("L", "L", ("L", "a"), "|u1"),
+        "PA": ("RGB", "L", ("P", "A"), "|u1"),
+    }
+    if mode in modes:
+        base_mode, base_type, bands, type_str = modes[mode]
+        return ModeDescriptor(mode, bands, base_mode, base_type, type_str)
+
+    mapping_modes = {
+        # I;16 == I;16L, and I;32 == I;32L
+        "I;16": "<u2",
+        "I;16S": "<i2",
+        "I;16L": "<u2",
+        "I;16LS": "<i2",
+        "I;16B": ">u2",
+        "I;16BS": ">i2",
+        "I;16N": endian + "u2",
+        "I;16NS": endian + "i2",
+        "I;32": "<u4",
+        "I;32B": ">u4",
+        "I;32L": "<u4",
+        "I;32S": "<i4",
+        "I;32BS": ">i4",
+        "I;32LS": "<i4",
+    }
+
+    type_str = mapping_modes[mode]
+    return ModeDescriptor(mode, ("I",), "L", "L", type_str)
diff --git a/src/PIL/ImageSequence.py b/src/PIL/ImageSequence.py
index e09b001e8..2c1850276 100644
--- a/src/PIL/ImageSequence.py
+++ b/src/PIL/ImageSequence.py
@@ -16,6 +16,10 @@
 ##
 from __future__ import annotations
 
+from typing import Callable
+
+from . import Image
+
 
 class Iterator:
     """
@@ -29,14 +33,14 @@ class Iterator:
     :param im: An image object.
     """
 
-    def __init__(self, im):
+    def __init__(self, im: Image.Image):
         if not hasattr(im, "seek"):
             msg = "im must have seek method"
             raise AttributeError(msg)
         self.im = im
         self.position = getattr(self.im, "_min_frame", 0)
 
-    def __getitem__(self, ix):
+    def __getitem__(self, ix: int) -> Image.Image:
         try:
             self.im.seek(ix)
             return self.im
@@ -44,10 +48,10 @@ class Iterator:
             msg = "end of sequence"
             raise IndexError(msg) from e
 
-    def __iter__(self):
+    def __iter__(self) -> Iterator:
         return self
 
-    def __next__(self):
+    def __next__(self) -> Image.Image:
         try:
             self.im.seek(self.position)
             self.position += 1
@@ -57,7 +61,10 @@ class Iterator:
             raise StopIteration(msg) from e
 
 
-def all_frames(im, func=None):
+def all_frames(
+    im: Image.Image | list[Image.Image],
+    func: Callable[[Image.Image], Image.Image] | None = None,
+) -> list[Image.Image]:
     """
     Applies a given function to all frames in an image or a list of images.
     The frames are returned as a list of separate images.
diff --git a/src/PIL/TarIO.py b/src/PIL/TarIO.py
index 26522d93f..c9923487d 100644
--- a/src/PIL/TarIO.py
+++ b/src/PIL/TarIO.py
@@ -16,6 +16,7 @@
 from __future__ import annotations
 
 import io
+from types import TracebackType
 
 from . import ContainerIO
 
@@ -23,7 +24,7 @@ from . import ContainerIO
 class TarIO(ContainerIO.ContainerIO):
     """A file object that provides read access to a given member of a TAR file."""
 
-    def __init__(self, tarfile, file):
+    def __init__(self, tarfile: str, file: str) -> None:
         """
         Create file object.
 
@@ -57,11 +58,16 @@ class TarIO(ContainerIO.ContainerIO):
         super().__init__(self.fh, self.fh.tell(), size)
 
     # Context manager support
-    def __enter__(self):
+    def __enter__(self) -> TarIO:
         return self
 
-    def __exit__(self, *args):
+    def __exit__(
+        self,
+        exc_type: type[BaseException] | None,
+        exc_val: BaseException | None,
+        exc_tb: TracebackType | None,
+    ) -> None:
         self.close()
 
-    def close(self):
+    def close(self) -> None:
         self.fh.close()
diff --git a/src/_imagingft.c b/src/_imagingft.c
index 4925dc233..68c66ac2c 100644
--- a/src/_imagingft.c
+++ b/src/_imagingft.c
@@ -1049,8 +1049,8 @@ font_render(FontObject *self, PyObject *args) {
             if (yy >= 0 && yy < im->ysize) {
                 /* blend this glyph into the buffer */
                 int k;
-                unsigned char v;
                 unsigned char *target;
+                unsigned int tmp;
                 if (color) {
                     /* target[RGB] returns the color, target[A] returns the mask */
                     /* target bands get split again in ImageDraw.text */
@@ -1061,34 +1061,55 @@ font_render(FontObject *self, PyObject *args) {
                 if (color && bitmap.pixel_mode == FT_PIXEL_MODE_BGRA) {
                     /* paste color glyph */
                     for (k = x0; k < x1; k++) {
-                        if (target[k * 4 + 3] < source[k * 4 + 3]) {
-                            /* unpremultiply BGRa to RGBA */
-                            target[k * 4 + 0] = CLIP8(
-                                (255 * (int)source[k * 4 + 2]) / source[k * 4 + 3]);
-                            target[k * 4 + 1] = CLIP8(
-                                (255 * (int)source[k * 4 + 1]) / source[k * 4 + 3]);
-                            target[k * 4 + 2] = CLIP8(
-                                (255 * (int)source[k * 4 + 0]) / source[k * 4 + 3]);
-                            target[k * 4 + 3] = source[k * 4 + 3];
+                        unsigned int src_alpha = source[k * 4 + 3];
+
+                        /* paste only if source has data */
+                        if (src_alpha > 0) {
+                            /* unpremultiply BGRa */
+                            int src_red = CLIP8((255 * (int)source[k * 4 + 2]) / src_alpha);
+                            int src_green = CLIP8((255 * (int)source[k * 4 + 1]) / src_alpha);
+                            int src_blue = CLIP8((255 * (int)source[k * 4 + 0]) / src_alpha);
+
+                            /* blend required if target has data */
+                            if (target[k * 4 + 3] > 0) {
+                                /* blend RGBA colors */
+                                target[k * 4 + 0] = BLEND(src_alpha, target[k * 4 + 0], src_red, tmp);
+                                target[k * 4 + 1] = BLEND(src_alpha, target[k * 4 + 1], src_green, tmp);
+                                target[k * 4 + 2] = BLEND(src_alpha, target[k * 4 + 2], src_blue, tmp);
+                                target[k * 4 + 3] = CLIP8(src_alpha + MULDIV255(target[k * 4 + 3], (255 - src_alpha), tmp));
+                            } else {
+                                /* paste unpremultiplied RGBA values */
+                                target[k * 4 + 0] = src_red;
+                                target[k * 4 + 1] = src_green;
+                                target[k * 4 + 2] = src_blue;
+                                target[k * 4 + 3] = src_alpha;
+                            }
                         }
                     }
                 } else if (bitmap.pixel_mode == FT_PIXEL_MODE_GRAY) {
                     if (color) {
                         unsigned char *ink = (unsigned char *)&foreground_ink;
                         for (k = x0; k < x1; k++) {
-                            v = source[k] * convert_scale;
-                            if (target[k * 4 + 3] < v) {
-                                target[k * 4 + 0] = ink[0];
-                                target[k * 4 + 1] = ink[1];
-                                target[k * 4 + 2] = ink[2];
-                                target[k * 4 + 3] = v;
+                            unsigned int src_alpha = source[k] * convert_scale;
+                            if (src_alpha > 0) {
+                                if (target[k * 4 + 3] > 0) {
+                                    target[k * 4 + 0] = BLEND(src_alpha, target[k * 4 + 0], ink[0], tmp);
+                                    target[k * 4 + 1] = BLEND(src_alpha, target[k * 4 + 1], ink[1], tmp);
+                                    target[k * 4 + 2] = BLEND(src_alpha, target[k * 4 + 2], ink[2], tmp);
+                                    target[k * 4 + 3] = CLIP8(src_alpha + MULDIV255(target[k * 4 + 3], (255 - src_alpha), tmp));
+                                } else {
+                                    target[k * 4 + 0] = ink[0];
+                                    target[k * 4 + 1] = ink[1];
+                                    target[k * 4 + 2] = ink[2];
+                                    target[k * 4 + 3] = src_alpha;
+                                }
                             }
                         }
                     } else {
                         for (k = x0; k < x1; k++) {
-                            v = source[k] * convert_scale;
-                            if (target[k] < v) {
-                                target[k] = v;
+                            unsigned int src_alpha = source[k] * convert_scale;
+                            if (src_alpha > 0) {
+                                target[k] = target[k] > 0 ? CLIP8(src_alpha + MULDIV255(target[k], (255 - src_alpha), tmp)) : src_alpha;
                             }
                         }
                     }
diff --git a/winbuild/build_prepare.py b/winbuild/build_prepare.py
index f7e145fb9..8e3757ca8 100644
--- a/winbuild/build_prepare.py
+++ b/winbuild/build_prepare.py
@@ -56,7 +56,9 @@ def cmd_nmake(
     )
 
 
-def cmds_cmake(target: str | tuple[str, ...] | list[str], *params) -> list[str]:
+def cmds_cmake(
+    target: str | tuple[str, ...] | list[str], *params, build_dir: str = "."
+) -> list[str]:
     if not isinstance(target, str):
         target = " ".join(target)
 
@@ -73,10 +75,11 @@ def cmds_cmake(target: str | tuple[str, ...] | list[str], *params) -> list[str]:
                 "-DCMAKE_CXX_FLAGS=-nologo",
                 *params,
                 '-G "{cmake_generator}"',
-                ".",
+                f'-B "{build_dir}"',
+                "-S .",
             ]
         ),
-        f"{{cmake}} --build . --clean-first --parallel --target {target}",
+        f'{{cmake}} --build "{build_dir}" --clean-first --parallel --target {target}',
     ]
 
 
@@ -367,7 +370,14 @@ DEPS = {
         "build": [
             cmd_copy(r"COPYING", r"{bin_dir}\fribidi-1.0.13-COPYING"),
             cmd_copy(r"{winbuild_dir}\fribidi.cmake", r"CMakeLists.txt"),
-            *cmds_cmake("fribidi"),
+            # generated tab.i files cannot be cross-compiled
+            " ^&^& ".join(
+                [
+                    "if {architecture}==ARM64 cmd /c call {vcvarsall} x86",
+                    *cmds_cmake("fribidi-gen", "-DARCH=x86", build_dir="build_x86"),
+                ]
+            ),
+            *cmds_cmake("fribidi", "-DARCH={architecture}"),
         ],
         "bins": [r"*.dll"],
     },
@@ -381,10 +391,9 @@ def find_msvs(architecture: str) -> dict[str, str] | None:
         print("Program Files not found")
         return None
 
+    requires = ["-requires", "Microsoft.VisualStudio.Component.VC.Tools.x86.x64"]
     if architecture == "ARM64":
-        tools = "Microsoft.VisualStudio.Component.VC.Tools.ARM64"
-    else:
-        tools = "Microsoft.VisualStudio.Component.VC.Tools.x86.x64"
+        requires += ["-requires", "Microsoft.VisualStudio.Component.VC.Tools.ARM64"]
 
     try:
         vspath = (
@@ -395,8 +404,7 @@ def find_msvs(architecture: str) -> dict[str, str] | None:
                     ),
                     "-latest",
                     "-prerelease",
-                    "-requires",
-                    tools,
+                    *requires,
                     "-property",
                     "installationPath",
                     "-products",
@@ -707,11 +715,6 @@ if __name__ == "__main__":
         disabled += ["libimagequant"]
     if args.no_fribidi:
         disabled += ["fribidi"]
-    elif args.architecture == "ARM64" and platform.machine() != "ARM64":
-        import warnings
-
-        warnings.warn("Cross-compiling FriBiDi is currently not supported, disabling")
-        disabled += ["fribidi"]
 
     prefs = {
         "architecture": args.architecture,
diff --git a/winbuild/fribidi.cmake b/winbuild/fribidi.cmake
index 27b8d17a8..b16e0784c 100644
--- a/winbuild/fribidi.cmake
+++ b/winbuild/fribidi.cmake
@@ -2,9 +2,9 @@ cmake_minimum_required(VERSION 3.12)
 
 project(fribidi)
 
+
 add_definitions(-D_CRT_SECURE_NO_WARNINGS)
 
-include_directories(${CMAKE_CURRENT_BINARY_DIR})
 include_directories(lib)
 
 function(extract_regex_1 var text regex)
@@ -27,12 +27,20 @@ function(fribidi_conf)
 	set(PACKAGE_BUGREPORT "https://github.com/fribidi/fribidi/issues/new")
 	set(SIZEOF_INT 4)
 	set(FRIBIDI_MSVC_BUILD_PLACEHOLDER "#define FRIBIDI_BUILT_WITH_MSVC")
-	message("detected ${PACKAGE_NAME} version ${FRIBIDI_VERSION}")
-	configure_file(lib/fribidi-config.h.in lib/fribidi-config.h @ONLY)
+	message("Detected ${PACKAGE_NAME} version ${FRIBIDI_VERSION}")
+	configure_file(lib/fribidi-config.h.in ${CMAKE_CURRENT_SOURCE_DIR}/lib/fribidi-config.h @ONLY)
 endfunction()
 fribidi_conf()
 
 
+option(ARCH "Target architecture")
+if(${ARCH} STREQUAL ARM64)
+    set(GEN FALSE)
+else()
+    set(GEN TRUE)
+endif()
+message("Generate tab.i files: " ${GEN})
+
 function(prepend var prefix)
 	set(out "")
 	foreach(f ${ARGN})
@@ -56,18 +64,20 @@ macro(fribidi_definitions _TGT)
 endmacro()
 
 function(fribidi_gen _NAME _OUTNAME _PARAM)
-	set(_OUT lib/${_OUTNAME})
-	prepend(_DEP "${CMAKE_CURRENT_SOURCE_DIR}/gen.tab/" ${ARGN})
-	add_executable(gen-${_NAME}
-		gen.tab/gen-${_NAME}.c
-		gen.tab/packtab.c)
-	fribidi_definitions(gen-${_NAME})
-	target_compile_definitions(gen-${_NAME}
-		PUBLIC DONT_HAVE_FRIBIDI_CONFIG_H)
-	add_custom_command(
-		COMMAND gen-${_NAME} ${_PARAM} ${_DEP} > ${_OUT}
-		DEPENDS ${_DEP}
-		OUTPUT ${_OUT})
+	set(_OUT ${CMAKE_CURRENT_SOURCE_DIR}/lib/${_OUTNAME})
+	if(GEN)
+        prepend(_DEP "${CMAKE_CURRENT_SOURCE_DIR}/gen.tab/" ${ARGN})
+        add_executable(gen-${_NAME}
+            gen.tab/gen-${_NAME}.c
+            gen.tab/packtab.c)
+        fribidi_definitions(gen-${_NAME})
+        target_compile_definitions(gen-${_NAME}
+            PUBLIC DONT_HAVE_FRIBIDI_CONFIG_H)
+        add_custom_command(
+            COMMAND gen-${_NAME} ${_PARAM} ${_DEP} > ${_OUT}
+            DEPENDS ${_DEP}
+            OUTPUT ${_OUT})
+    endif(GEN)
 	list(APPEND FRIBIDI_SOURCES_GENERATED "${_OUT}")
 	set(FRIBIDI_SOURCES_GENERATED ${FRIBIDI_SOURCES_GENERATED} PARENT_SCOPE)
 endfunction()
@@ -78,8 +88,10 @@ fribidi_gen(unicode-version fribidi-unicode-version.h ""
 
 macro(fribidi_tab _NAME)
 	fribidi_gen(${_NAME}-tab ${_NAME}.tab.i 2 ${ARGN})
-	target_sources(gen-${_NAME}-tab
-		PRIVATE lib/fribidi-unicode-version.h)
+	if(GEN)
+        target_sources(gen-${_NAME}-tab
+            PRIVATE lib/fribidi-unicode-version.h)
+	endif(GEN)
 endmacro()
 
 fribidi_tab(bidi-type unidata/UnicodeData.txt)
@@ -89,14 +101,16 @@ fribidi_tab(mirroring unidata/BidiMirroring.txt)
 fribidi_tab(brackets unidata/BidiBrackets.txt unidata/UnicodeData.txt)
 fribidi_tab(brackets-type unidata/BidiBrackets.txt)
 
+add_custom_target(fribidi-gen DEPENDS ${FRIBIDI_SOURCES_GENERATED})
+
 
 file(GLOB FRIBIDI_SOURCES lib/*.c)
 file(GLOB FRIBIDI_HEADERS lib/*.h)
 
 add_library(fribidi SHARED
-	${FRIBIDI_SOURCES}
-	${FRIBIDI_HEADERS}
-	${FRIBIDI_SOURCES_GENERATED})
+    ${FRIBIDI_SOURCES}
+    ${FRIBIDI_HEADERS}
+    ${FRIBIDI_SOURCES_GENERATED})
 fribidi_definitions(fribidi)
 target_compile_definitions(fribidi
-	PUBLIC "-DFRIBIDI_BUILD")
+    PUBLIC "-DFRIBIDI_BUILD")