Repository: kyamagu/skia-python (language: python)

Issue:
Skparagraph does not properly handle newlines on windows
**Describe the bug**

When given text that has newlines in it, Skparagraph fails to render the newlines and instead renders them as unknown characters.
![test](https://github.com/user-attachments/assets/3612d143-278c-438d-adde-c9e286515831)

But it works on linux:
![test](https://github.com/user-attachments/assets/0f69f683-9978-4493-aa68-c61ce687c08d)

I‘m not sure if it's an upstream problem, but when I tried it with rust-skia, it worked on both windows and linux.


**To Reproduce**

Code:
```python
import math

import skia
from skia import textlayout

paint = skia.Paint()
paint.setColor(skia.ColorBLACK)
paint.setAntiAlias(True)

style = textlayout.TextStyle()
style.setFontSize(50)
style.setForegroundPaint(paint)

font_collection = textlayout.FontCollection()
font_collection.setDefaultFontManager(skia.FontMgr())

para_style = textlayout.ParagraphStyle()

builder = textlayout.ParagraphBuilder.make(
    para_style, font_collection, skia.Unicodes.ICU.Make()
)
builder.pushStyle(style)

builder.addText("test\ntest")
paragraph = builder.Build()
paragraph.layout(300)

image_width = math.ceil(paragraph.LongestLine)
image_height = math.ceil(paragraph.Height)
surface = skia.Surfaces.MakeRasterN32Premul(image_width, image_height)
canvas = surface.getCanvas()
canvas.clear(skia.ColorWHITE)
paragraph.paint(canvas, 0, 0)
surface.flushAndSubmit()
image = surface.makeImageSnapshot()
image.save("test.png", skia.kPNG)
```

**Expected behavior**

The text with newlines is rendered properly on windows.

**Desktop (please complete the following information):**
 - OS: windows 11, ubuntu 22.04
 - Python: 3.10
 - skia-python version: v130.0b10



Discussion / hints:

Fix patch:
diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml
index f104578c..434cc580 100644
--- a/.github/workflows/ci.yml
+++ b/.github/workflows/ci.yml
@@ -25,7 +25,7 @@ jobs:
           key: linux-aarch64-skia-${{ github.sha }}-3rd-party
       - name: Pre-fetch skia deps
         if: ${{ steps.cache-skia.outputs.cache-hit != 'true' }}
-        run: git config --global core.compression 0 && cd skia && patch -p1 -i ../patch/skia-m130-minimize-download.patch && python tools/git-sync-deps && patch -p1 -R -i ../patch/skia-m130-minimize-download.patch
+        run: git config --global core.compression 0 && cd skia && patch -p1 -i ../patch/skia-m132-minimize-download.patch && python tools/git-sync-deps && patch -p1 -R -i ../patch/skia-m132-minimize-download.patch
       - name: Set up QEMU
         uses: docker/setup-qemu-action@v3
       - name: Build skia 3rd-Party
@@ -73,29 +73,89 @@ jobs:
     strategy:
       fail-fast: false
       matrix:
-        os: [ubuntu-22.04, windows-2022, macos-12]
+        os: [ubuntu-22.04, windows-2022, macos-13]
         arch: [auto64]
-        cp: ["cp3{8,9,10,11,12}"]
+        cp: ["cp3{10,11,12,13}"]
         include:
-          - os: macos-12
+          - os: macos-13
             arch: arm64
-            cp: "cp3{8,9,10,11,12}"
+            cp: "cp3{10,11,12,13}"
           # aarch64 is emulated and takes longer, build one wheel per job
           - os: ubuntu-22.04
             arch: aarch64
-            cp: cp38
+            cp: cp310
           - os: ubuntu-22.04
             arch: aarch64
-            cp: cp39
+            cp: cp311
           - os: ubuntu-22.04
             arch: aarch64
-            cp: cp310
+            cp: cp312
           - os: ubuntu-22.04
             arch: aarch64
-            cp: cp311
+            cp: cp313
+
+    steps:
+      - uses: actions/checkout@v4
+        with:
+          submodules: true
+
+      - uses: actions/cache/restore@v4
+        if: runner.os == 'Linux' && matrix.arch == 'aarch64'
+        with:
+          path: |
+            gn
+            skia
+          key: linux-aarch64-skia-${{ github.sha }}
+
+      - name: Set up QEMU
+        if: runner.os == 'Linux' && matrix.arch == 'aarch64'
+        uses: docker/setup-qemu-action@v3
+        with:
+          platforms: ${{ matrix.arch }}
+
+      - name: Build wheels
+        uses: pypa/cibuildwheel@v2.21.3
+        env:
+          CIBW_BUILD: "${{ matrix.cp }}-*"
+          CIBW_SKIP: "*musllinux*"
+          CIBW_ARCHS: ${{ matrix.arch }}
+          CIBW_ENVIRONMENT_MACOS: TARGET_ARCH=${{ matrix.arch }} MACOSX_DEPLOYMENT_TARGET=11.0
+          CIBW_BEFORE_ALL: bash scripts/build_${{ runner.os }}.sh
+          CIBW_BEFORE_BUILD: pip install pybind11 numpy
+          CIBW_TEST_REQUIRES: pytest pillow glfw
+          CIBW_TEST_REQUIRES_MACOS: pytest pillow pyopengl
+          CIBW_TEST_COMMAND: python -m pytest {project}/tests
+          CIBW_TEST_COMMAND_LINUX: >
+            xvfb-run -s "-screen 0 640x480x24" python -m pytest {project}/tests
+          CIBW_TEST_SKIP: "*-macosx_arm64"
+
+      - uses: actions/upload-artifact@v4
+        with:
+          name: wheel-${{ matrix.os }}-${{ matrix.arch }}-${{ matrix.cp }}
+          path: ./wheelhouse/*.whl
+
+  # identical to "build_wheels", except with the older pypa/cibuildwheel@v2.19.2
+  build_wheels_old:
+    name: Build wheels on ${{ matrix.os }} (${{ matrix.arch }}) for ${{ matrix.cp }}
+    needs: prebuild_linux_aarch64
+    runs-on: ${{ matrix.os }}
+    strategy:
+      fail-fast: false
+      matrix:
+        os: [ubuntu-22.04, windows-2022, macos-13]
+        arch: [auto64]
+        cp: ["cp3{8,9}"]
+        include:
+          - os: macos-13
+            arch: arm64
+            cp: "cp3{8,9}"
+          # aarch64 is emulated and takes longer, build one wheel per job
           - os: ubuntu-22.04
             arch: aarch64
-            cp: cp312
+            cp: cp38
+          - os: ubuntu-22.04
+            arch: aarch64
+            cp: cp39
 
     steps:
       - uses: actions/checkout@v4
@@ -139,7 +199,7 @@ jobs:
 
   build_docs:
     name: Build docs
-    needs: [build_wheels]
+    needs: [build_wheels, build_wheels_old]
     runs-on: ubuntu-latest
     steps:
       - uses: actions/checkout@v4
@@ -169,7 +229,7 @@ jobs:
 
   publish:
     name: Upload packages to PyPI
-    needs: [build_wheels]
+    needs: [build_wheels, build_wheels_old]
     runs-on: ubuntu-latest
     if: github.event_name == 'release' && github.event.action == 'published'
     permissions:
diff --git a/README.md b/README.md
index 37b2bfa8..5b84501a 100644
--- a/README.md
+++ b/README.md
@@ -15,7 +15,7 @@ Binary package is available on PyPI:
 pip install skia-python
 ```
 
-Supported platforms: Python 3.8-3.12 (CPython) on
+Supported platforms: Python 3.8-3.13 (CPython) on
 
 - Linux x86_64, aarch64
 - macOS x86_64, arm64
@@ -57,7 +57,8 @@ https://kyamagu.github.io/skia-python
   [README.m120](relnotes/README.m120.md), [README.m121](relnotes/README.m121.md), [README.m122](relnotes/README.m122.md),
   [README.m123](relnotes/README.m123.md), [README.m124](relnotes/README.m124.md), [README.m125](relnotes/README.m125.md),
   [README.m126](relnotes/README.m126.md), [README.m127](relnotes/README.m127.md), [README.m128](relnotes/README.m128.md),
-  [README.m129](relnotes/README.m129.md), [README.m130](relnotes/README.m130.md).
+  [README.m129](relnotes/README.m129.md), [README.m130](relnotes/README.m130.md), [README.m131](relnotes/README.m131.md),
+  [README.m132](relnotes/README.m132.md).
 
 ## Contributing
 
diff --git a/docs/tutorial/canvas.rst b/docs/tutorial/canvas.rst
index 16eb1791..ff658761 100644
--- a/docs/tutorial/canvas.rst
+++ b/docs/tutorial/canvas.rst
@@ -91,10 +91,11 @@ The following example uses glfw package to create an OpenGL context. Install
         glfw.window_hint(glfw.VISIBLE, glfw.FALSE)
         glfw.window_hint(glfw.STENCIL_BITS, 8)
         # see https://www.glfw.org/faq#macos
-        glfw.window_hint(glfw.CONTEXT_VERSION_MAJOR, 3)
-        glfw.window_hint(glfw.CONTEXT_VERSION_MINOR, 2)
-        glfw.window_hint(glfw.OPENGL_FORWARD_COMPAT, True)
-        glfw.window_hint(glfw.OPENGL_PROFILE, glfw.OPENGL_CORE_PROFILE)
+        if sys.platform.startswith("darwin"):
+            glfw.window_hint(glfw.CONTEXT_VERSION_MAJOR, 3)
+            glfw.window_hint(glfw.CONTEXT_VERSION_MINOR, 2)
+            glfw.window_hint(glfw.OPENGL_FORWARD_COMPAT, True)
+            glfw.window_hint(glfw.OPENGL_PROFILE, glfw.OPENGL_CORE_PROFILE)
         window = glfw.create_window(640, 480, '', None, None)
         glfw.make_context_current(window)
         yield window
@@ -144,10 +145,11 @@ Here's a complete example:
             raise RuntimeError('glfw.init() failed')
         glfw.window_hint(glfw.STENCIL_BITS, 8)
         # see https://www.glfw.org/faq#macos
-        glfw.window_hint(glfw.CONTEXT_VERSION_MAJOR, 3)
-        glfw.window_hint(glfw.CONTEXT_VERSION_MINOR, 2)
-        glfw.window_hint(glfw.OPENGL_FORWARD_COMPAT, True)
-        glfw.window_hint(glfw.OPENGL_PROFILE, glfw.OPENGL_CORE_PROFILE)
+        if sys.platform.startswith("darwin"):
+            glfw.window_hint(glfw.CONTEXT_VERSION_MAJOR, 3)
+            glfw.window_hint(glfw.CONTEXT_VERSION_MINOR, 2)
+            glfw.window_hint(glfw.OPENGL_FORWARD_COMPAT, True)
+            glfw.window_hint(glfw.OPENGL_PROFILE, glfw.OPENGL_CORE_PROFILE)
         window = glfw.create_window(WIDTH, HEIGHT, '', None, None)
         glfw.make_context_current(window)
         yield window
diff --git a/patch/skia-m131-minimize-download.patch b/patch/skia-m131-minimize-download.patch
new file mode 100644
index 00000000..2d3642cc
--- /dev/null
+++ b/patch/skia-m131-minimize-download.patch
@@ -0,0 +1,70 @@
+diff --git a/DEPS b/DEPS
+index a30e242..3d8dd72 100644
+--- a/DEPS
++++ b/DEPS
+@@ -31,53 +31,18 @@ vars = {
+ #     ./tools/git-sync-deps
+ deps = {
+   "buildtools"                                   : "https://chromium.googlesource.com/chromium/src/buildtools.git@b138e6ce86ae843c42a1a08f37903207bebcca75",
+-  "third_party/externals/angle2"                 : "https://chromium.googlesource.com/angle/angle.git@78a694a1b82a01623226a418cf2f765c75e45c70",
+-  "third_party/externals/brotli"                 : "https://skia.googlesource.com/external/github.com/google/brotli.git@6d03dfbedda1615c4cba1211f8d81735575209c8",
+-  "third_party/externals/d3d12allocator"         : "https://skia.googlesource.com/external/github.com/GPUOpen-LibrariesAndSDKs/D3D12MemoryAllocator.git@169895d529dfce00390a20e69c2f516066fe7a3b",
+-  # Dawn requires jinja2 and markupsafe for the code generator, tint for SPIRV compilation, and abseil for string formatting.
+-  # When the Dawn revision is updated these should be updated from the Dawn DEPS as well.
+-  "third_party/externals/dawn"                   : "https://dawn.googlesource.com/dawn.git@f3c7cc5c580eb743829c78bb77df0c1e8f6a6ce3",
+-  "third_party/externals/jinja2"                 : "https://chromium.googlesource.com/chromium/src/third_party/jinja2@e2d024354e11cc6b041b0cff032d73f0c7e43a07",
+-  "third_party/externals/markupsafe"             : "https://chromium.googlesource.com/chromium/src/third_party/markupsafe@0bad08bb207bbfc1d6f3bbc82b9242b0c50e5794",
+-  "third_party/externals/abseil-cpp"             : "https://skia.googlesource.com/external/github.com/abseil/abseil-cpp.git@65a55c2ba891f6d2492477707f4a2e327a0b40dc",
+   "third_party/externals/dng_sdk"                : "https://android.googlesource.com/platform/external/dng_sdk.git@c8d0c9b1d16bfda56f15165d39e0ffa360a11123",
+-  "third_party/externals/egl-registry"           : "https://skia.googlesource.com/external/github.com/KhronosGroup/EGL-Registry@b055c9b483e70ecd57b3cf7204db21f5a06f9ffe",
+-  "third_party/externals/emsdk"                  : "https://skia.googlesource.com/external/github.com/emscripten-core/emsdk.git@a896e3d066448b3530dbcaa48869fafefd738f57",
+   "third_party/externals/expat"                  : "https://chromium.googlesource.com/external/github.com/libexpat/libexpat.git@624da0f593bb8d7e146b9f42b06d8e6c80d032a3",
+   "third_party/externals/freetype"               : "https://chromium.googlesource.com/chromium/src/third_party/freetype2.git@83af801b552111e37d9466a887e1783a0fb5f196",
+   "third_party/externals/harfbuzz"               : "https://chromium.googlesource.com/external/github.com/harfbuzz/harfbuzz.git@a070f9ebbe88dc71b248af9731dd49ec93f4e6e6",
+-  "third_party/externals/highway"                : "https://chromium.googlesource.com/external/github.com/google/highway.git@424360251cdcfc314cfc528f53c872ecd63af0f0",
+   "third_party/externals/icu"                    : "https://chromium.googlesource.com/chromium/deps/icu.git@364118a1d9da24bb5b770ac3d762ac144d6da5a4",
+-  "third_party/externals/icu4x"                  : "https://chromium.googlesource.com/external/github.com/unicode-org/icu4x.git@bcf4f7198d4dc5f3127e84a6ca657c88e7d07a13",
+-  "third_party/externals/imgui"                  : "https://skia.googlesource.com/external/github.com/ocornut/imgui.git@55d35d8387c15bf0cfd71861df67af8cfbda7456",
+-  "third_party/externals/libavif"                : "https://skia.googlesource.com/external/github.com/AOMediaCodec/libavif.git@55aab4ac0607ab651055d354d64c4615cf3d8000",
+-  "third_party/externals/libgav1"                : "https://chromium.googlesource.com/codecs/libgav1.git@5cf722e659014ebaf2f573a6dd935116d36eadf1",
+-  "third_party/externals/libgrapheme"            : "https://skia.googlesource.com/external/github.com/FRIGN/libgrapheme/@c0cab63c5300fa12284194fbef57aa2ed62a94c0",
+   "third_party/externals/libjpeg-turbo"          : "https://chromium.googlesource.com/chromium/deps/libjpeg_turbo.git@ccfbe1c82a3b6dbe8647ceb36a3f9ee711fba3cf",
+-  "third_party/externals/libjxl"                 : "https://chromium.googlesource.com/external/gitlab.com/wg1/jpeg-xl.git@a205468bc5d3a353fb15dae2398a101dff52f2d3",
+   "third_party/externals/libpng"                 : "https://skia.googlesource.com/third_party/libpng.git@ed217e3e601d8e462f7fd1e04bed43ac42212429",
+   "third_party/externals/libwebp"                : "https://chromium.googlesource.com/webm/libwebp.git@845d5476a866141ba35ac133f856fa62f0b7445f",
+-  "third_party/externals/libyuv"                 : "https://chromium.googlesource.com/libyuv/libyuv.git@d248929c059ff7629a85333699717d7a677d8d96",
+-  "third_party/externals/microhttpd"             : "https://android.googlesource.com/platform/external/libmicrohttpd@748945ec6f1c67b7efc934ab0808e1d32f2fb98d",
+-  "third_party/externals/oboe"                   : "https://chromium.googlesource.com/external/github.com/google/oboe.git@b02a12d1dd821118763debec6b83d00a8a0ee419",
+-  "third_party/externals/opengl-registry"        : "https://skia.googlesource.com/external/github.com/KhronosGroup/OpenGL-Registry@14b80ebeab022b2c78f84a573f01028c96075553",
+-  "third_party/externals/partition_alloc"        : "https://chromium.googlesource.com/chromium/src/base/allocator/partition_allocator.git@ca4487e127c2e071da5d4a36a9f71fd7b65b1434",
+-  "third_party/externals/perfetto"               : "https://android.googlesource.com/platform/external/perfetto@93885509be1c9240bc55fa515ceb34811e54a394",
+   "third_party/externals/piex"                   : "https://android.googlesource.com/platform/external/piex.git@bb217acdca1cc0c16b704669dd6f91a1b509c406",
+-  "third_party/externals/swiftshader"            : "https://swiftshader.googlesource.com/SwiftShader@7a9a492a38b7c701f7c96a15a76046aed8f8c0c3",
+   "third_party/externals/vulkanmemoryallocator"  : "https://chromium.googlesource.com/external/github.com/GPUOpen-LibrariesAndSDKs/VulkanMemoryAllocator@a6bfc237255a6bac1513f7c1ebde6d8aed6b5191",
+-  # vulkan-deps is a meta-repo containing several interdependent Khronos Vulkan repositories.
+-  # When the vulkan-deps revision is updated, those repos (spirv-*, vulkan-*) should be updated as well.
+   "third_party/externals/vulkan-deps"            : "https://chromium.googlesource.com/vulkan-deps@8f346c5caf5a624f42324ffb88167fc90992cab5",
+-  "third_party/externals/spirv-cross"            : "https://chromium.googlesource.com/external/github.com/KhronosGroup/SPIRV-Cross@b8fcf307f1f347089e3c46eb4451d27f32ebc8d3",
+   "third_party/externals/spirv-headers"          : "https://skia.googlesource.com/external/github.com/KhronosGroup/SPIRV-Headers.git@50bc4debdc3eec5045edbeb8ce164090e29b91f3",
+-  "third_party/externals/spirv-tools"            : "https://skia.googlesource.com/external/github.com/KhronosGroup/SPIRV-Tools.git@42b315c15b1ff941b46bb3949c105e5386be8717",
+-  "third_party/externals/vello"                  : "https://skia.googlesource.com/external/github.com/linebender/vello.git@3ee3bea02164c5a816fe6c16ef4e3a810edb7620",
+-  "third_party/externals/vulkan-headers"         : "https://chromium.googlesource.com/external/github.com/KhronosGroup/Vulkan-Headers@d91597a82f881d473887b560a03a7edf2720b72c",
+-  "third_party/externals/vulkan-tools"           : "https://chromium.googlesource.com/external/github.com/KhronosGroup/Vulkan-Tools@eb9b6043be165f06c7ec78fadbb1ff773c5fc19c",
+-  "third_party/externals/vulkan-utility-libraries": "https://chromium.googlesource.com/external/github.com/KhronosGroup/Vulkan-Utility-Libraries@bfd85956e1b4c1c79842ce857fc7fb15adb8a573",
+-  "third_party/externals/unicodetools"           : "https://chromium.googlesource.com/external/github.com/unicode-org/unicodetools@66a3fa9dbdca3b67053a483d130564eabc5fe095",
+-  #"third_party/externals/v8"                     : "https://chromium.googlesource.com/v8/v8.git@5f1ae66d5634e43563b2d25ea652dfb94c31a3b4",
+   "third_party/externals/wuffs"                  : "https://skia.googlesource.com/external/github.com/google/wuffs-mirror-release-c.git@e3f919ccfe3ef542cfc983a82146070258fb57f8",
+   "third_party/externals/zlib"                   : "https://chromium.googlesource.com/chromium/src/third_party/zlib@646b7f569718921d7d4b5b8e22572ff6c76f2596",
+ 
+diff --git a/bin/activate-emsdk b/bin/activate-emsdk
+index 687ca9f..7167d8d 100755
+--- a/bin/activate-emsdk
++++ b/bin/activate-emsdk
+@@ -17,6 +17,7 @@ EMSDK_PATH = os.path.join(EMSDK_ROOT, 'emsdk.py')
+ EMSDK_VERSION = '3.1.44'
+ 
+ def main():
++    return
+     if sysconfig.get_platform() in ['linux-aarch64', 'linux-arm64']:
+         # This platform cannot install emsdk at the provided version. See
+         # https://github.com/emscripten-core/emsdk/blob/main/emscripten-releases-tags.json#L5
diff --git a/patch/skia-m132-colrv1-freetype.diff b/patch/skia-m132-colrv1-freetype.diff
new file mode 100644
index 00000000..7d818119
--- /dev/null
+++ b/patch/skia-m132-colrv1-freetype.diff
@@ -0,0 +1,122 @@
+diff --git a/src/core/SkFontDescriptor.h b/src/core/SkFontDescriptor.h
+index a3018ee..5eadbed 100644
+--- a/src/core/SkFontDescriptor.h
++++ b/src/core/SkFontDescriptor.h
+@@ -82,7 +82,7 @@ private:
+     skia_private::AutoSTMalloc<4, SkFontArguments::Palette::Override> fPaletteOverrides;
+ };
+ 
+-class SkFontDescriptor : SkNoncopyable {
++class SK_SPI SkFontDescriptor : SkNoncopyable {
+ public:
+     SkFontDescriptor();
+     // Does not affect ownership of SkStream.
+diff --git a/src/ports/SkFontHost_FreeType.cpp b/src/ports/SkFontHost_FreeType.cpp
+index 7798b91..7637709 100644
+--- a/src/ports/SkFontHost_FreeType.cpp
++++ b/src/ports/SkFontHost_FreeType.cpp
+@@ -32,7 +32,6 @@
+ #include "src/core/SkMask.h"
+ #include "src/core/SkMaskGamma.h"
+ #include "src/core/SkScalerContext.h"
+-#include "src/ports/SkFontHost_FreeType_common.h"
+ #include "src/ports/SkFontScanner_FreeType_priv.h"
+ #include "src/ports/SkTypeface_FreeType.h"
+ #include "src/sfnt/SkOTUtils.h"
+@@ -52,6 +51,7 @@
+ #ifdef FT_COLOR_H  // 2.10.0
+ #   include <freetype/ftcolor.h>
+ #endif
++#include "src/ports/SkFontHost_FreeType_common.h"
+ #include <freetype/freetype.h>
+ #include <freetype/ftlcdfil.h>
+ #include <freetype/ftmodapi.h>
+diff --git a/src/ports/SkFontHost_FreeType_common.cpp b/src/ports/SkFontHost_FreeType_common.cpp
+index be5bc52..12bd7fe 100644
+--- a/src/ports/SkFontHost_FreeType_common.cpp
++++ b/src/ports/SkFontHost_FreeType_common.cpp
+@@ -6,7 +6,6 @@
+  * found in the LICENSE file.
+  */
+ 
+-#include "src/ports/SkFontHost_FreeType_common.h"
+ 
+ #include "include/core/SkBitmap.h"
+ #include "include/core/SkCanvas.h"
+@@ -34,6 +33,7 @@
+ #ifdef FT_COLOR_H
+ #   include <freetype/ftcolor.h>
+ #endif
++#include "src/ports/SkFontHost_FreeType_common.h"
+ #include <freetype/ftimage.h>
+ #include <freetype/ftoutln.h>
+ #include <freetype/ftsizes.h>
+@@ -1575,6 +1575,41 @@ bool SkScalerContextFTUtils::drawCOLRv1Glyph(FT_Face face, const SkGlyph& glyph,
+                               face, glyph.getGlyphID(),
+                               FT_COLOR_INCLUDE_ROOT_TRANSFORM, &activePaints);
+ }
++/*
++ * This content is mostly just
++ *       SkTypeface_FreeType::FaceRec::setupPalette()
++ +     + SkScalerContext_FreeType_Base::drawCOLRv1Glyph()
++ +*/
++bool SkScalerContextFTUtils::skia_colrv1_start_glyph(SkCanvas* canvas,
++                                    FT_Face face,
++                                    uint16_t glyphId,
++                                    FT_UShort palette_index,
++                                    FT_Color_Root_Transform rootTransform
++                                    ) {
++    uint32_t fForegroundColor{SK_ColorBLACK};
++    FT_Palette_Data paletteData;
++    FT_Palette_Data_Get(face, &paletteData);
++
++    FT_Color* ftPalette = nullptr;
++    FT_Palette_Select(face, palette_index, &ftPalette);
++    std::unique_ptr<SkColor[]> ptr_palette(new SkColor[paletteData.num_palette_entries]);
++    for (int i = 0; i < paletteData.num_palette_entries; ++i) {
++      ptr_palette[i] = SkColorSetARGB(ftPalette[i].alpha,
++                                  ftPalette[i].red,
++                                  ftPalette[i].green,
++                                  ftPalette[i].blue);
++    }
++    SkSpan<SkColor> palette(ptr_palette.get(), paletteData.num_palette_entries);
++
++    VisitedSet activePaints;
++    bool haveLayers =  colrv1_start_glyph(canvas, palette,
++                                          fForegroundColor, // FT_Palette_Get_Foreground_Color?
++                                          face, glyphId,
++                                          FT_COLOR_INCLUDE_ROOT_TRANSFORM,
++                                          &activePaints);
++    SkASSERTF(haveLayers, "Could not get COLRv1 layers from '%s'.", face->family_name);
++    return haveLayers;
++}
+ #endif  // TT_SUPPORT_COLRV1
+ 
+ #ifdef FT_COLOR_H
+diff --git a/src/ports/SkFontHost_FreeType_common.h b/src/ports/SkFontHost_FreeType_common.h
+index cd19ec7..40842c5 100644
+--- a/src/ports/SkFontHost_FreeType_common.h
++++ b/src/ports/SkFontHost_FreeType_common.h
+@@ -20,6 +20,7 @@ class SkCanvas;
+ // These are forward declared to avoid pimpl but also hide the FreeType implementation.
+ typedef struct FT_FaceRec_* FT_Face;
+ typedef signed long FT_Pos;
++typedef unsigned short  FT_UShort; /* freetype/fttypes.h */
+ 
+ 
+ #ifdef SK_DEBUG
+@@ -31,7 +32,13 @@ const char* SkTraceFtrGetError(int);
+ #define SK_TRACEFTR(ERR, ...) do { sk_ignore_unused_variable(ERR); } while (false)
+ #endif
+ 
+-struct SkScalerContextFTUtils {
++struct SK_SPI SkScalerContextFTUtils {
++    static bool skia_colrv1_start_glyph(SkCanvas* canvas,
++                                        FT_Face face,
++                                        uint16_t glyphId,
++                                        FT_UShort palette_index,
++                                        FT_Color_Root_Transform rootTransform
++                                        );
+     SkColor                 fForegroundColor;
+     SkScalerContext::Flags  fFlags;
+ 
diff --git a/patch/skia-m132-minimize-download.patch b/patch/skia-m132-minimize-download.patch
new file mode 100644
index 00000000..ffb54097
--- /dev/null
+++ b/patch/skia-m132-minimize-download.patch
@@ -0,0 +1,70 @@
+diff --git a/DEPS b/DEPS
+index 16d496c..eb6a7cb 100644
+--- a/DEPS
++++ b/DEPS
+@@ -31,53 +31,18 @@ vars = {
+ #     ./tools/git-sync-deps
+ deps = {
+   "buildtools"                                   : "https://chromium.googlesource.com/chromium/src/buildtools.git@b138e6ce86ae843c42a1a08f37903207bebcca75",
+-  "third_party/externals/angle2"                 : "https://chromium.googlesource.com/angle/angle.git@7fea539cc99bed8fd315cfbc5026952a133ac3ae",
+-  "third_party/externals/brotli"                 : "https://skia.googlesource.com/external/github.com/google/brotli.git@6d03dfbedda1615c4cba1211f8d81735575209c8",
+-  "third_party/externals/d3d12allocator"         : "https://skia.googlesource.com/external/github.com/GPUOpen-LibrariesAndSDKs/D3D12MemoryAllocator.git@169895d529dfce00390a20e69c2f516066fe7a3b",
+-  # Dawn requires jinja2 and markupsafe for the code generator, tint for SPIRV compilation, and abseil for string formatting.
+-  # When the Dawn revision is updated these should be updated from the Dawn DEPS as well.
+-  "third_party/externals/dawn"                   : "https://dawn.googlesource.com/dawn.git@2a86250e561c56e9b1b9af5774f1253d9d66be97",
+-  "third_party/externals/jinja2"                 : "https://chromium.googlesource.com/chromium/src/third_party/jinja2@e2d024354e11cc6b041b0cff032d73f0c7e43a07",
+-  "third_party/externals/markupsafe"             : "https://chromium.googlesource.com/chromium/src/third_party/markupsafe@0bad08bb207bbfc1d6f3bbc82b9242b0c50e5794",
+-  "third_party/externals/abseil-cpp"             : "https://skia.googlesource.com/external/github.com/abseil/abseil-cpp.git@65a55c2ba891f6d2492477707f4a2e327a0b40dc",
+   "third_party/externals/dng_sdk"                : "https://android.googlesource.com/platform/external/dng_sdk.git@c8d0c9b1d16bfda56f15165d39e0ffa360a11123",
+-  "third_party/externals/egl-registry"           : "https://skia.googlesource.com/external/github.com/KhronosGroup/EGL-Registry@b055c9b483e70ecd57b3cf7204db21f5a06f9ffe",
+-  "third_party/externals/emsdk"                  : "https://skia.googlesource.com/external/github.com/emscripten-core/emsdk.git@a896e3d066448b3530dbcaa48869fafefd738f57",
+   "third_party/externals/expat"                  : "https://chromium.googlesource.com/external/github.com/libexpat/libexpat.git@624da0f593bb8d7e146b9f42b06d8e6c80d032a3",
+   "third_party/externals/freetype"               : "https://chromium.googlesource.com/chromium/src/third_party/freetype2.git@83af801b552111e37d9466a887e1783a0fb5f196",
+   "third_party/externals/harfbuzz"               : "https://chromium.googlesource.com/external/github.com/harfbuzz/harfbuzz.git@a070f9ebbe88dc71b248af9731dd49ec93f4e6e6",
+-  "third_party/externals/highway"                : "https://chromium.googlesource.com/external/github.com/google/highway.git@424360251cdcfc314cfc528f53c872ecd63af0f0",
+   "third_party/externals/icu"                    : "https://chromium.googlesource.com/chromium/deps/icu.git@364118a1d9da24bb5b770ac3d762ac144d6da5a4",
+-  "third_party/externals/icu4x"                  : "https://chromium.googlesource.com/external/github.com/unicode-org/icu4x.git@bcf4f7198d4dc5f3127e84a6ca657c88e7d07a13",
+-  "third_party/externals/imgui"                  : "https://skia.googlesource.com/external/github.com/ocornut/imgui.git@55d35d8387c15bf0cfd71861df67af8cfbda7456",
+-  "third_party/externals/libavif"                : "https://skia.googlesource.com/external/github.com/AOMediaCodec/libavif.git@55aab4ac0607ab651055d354d64c4615cf3d8000",
+-  "third_party/externals/libgav1"                : "https://chromium.googlesource.com/codecs/libgav1.git@5cf722e659014ebaf2f573a6dd935116d36eadf1",
+-  "third_party/externals/libgrapheme"            : "https://skia.googlesource.com/external/github.com/FRIGN/libgrapheme/@c0cab63c5300fa12284194fbef57aa2ed62a94c0",
+   "third_party/externals/libjpeg-turbo"          : "https://chromium.googlesource.com/chromium/deps/libjpeg_turbo.git@ccfbe1c82a3b6dbe8647ceb36a3f9ee711fba3cf",
+-  "third_party/externals/libjxl"                 : "https://chromium.googlesource.com/external/gitlab.com/wg1/jpeg-xl.git@a205468bc5d3a353fb15dae2398a101dff52f2d3",
+   "third_party/externals/libpng"                 : "https://skia.googlesource.com/third_party/libpng.git@ed217e3e601d8e462f7fd1e04bed43ac42212429",
+   "third_party/externals/libwebp"                : "https://chromium.googlesource.com/webm/libwebp.git@845d5476a866141ba35ac133f856fa62f0b7445f",
+-  "third_party/externals/libyuv"                 : "https://chromium.googlesource.com/libyuv/libyuv.git@d248929c059ff7629a85333699717d7a677d8d96",
+-  "third_party/externals/microhttpd"             : "https://android.googlesource.com/platform/external/libmicrohttpd@748945ec6f1c67b7efc934ab0808e1d32f2fb98d",
+-  "third_party/externals/oboe"                   : "https://chromium.googlesource.com/external/github.com/google/oboe.git@b02a12d1dd821118763debec6b83d00a8a0ee419",
+-  "third_party/externals/opengl-registry"        : "https://skia.googlesource.com/external/github.com/KhronosGroup/OpenGL-Registry@14b80ebeab022b2c78f84a573f01028c96075553",
+-  "third_party/externals/partition_alloc"        : "https://chromium.googlesource.com/chromium/src/base/allocator/partition_allocator.git@ce13777cb731e0a60c606d1741091fd11a0574d7",
+-  "third_party/externals/perfetto"               : "https://android.googlesource.com/platform/external/perfetto@93885509be1c9240bc55fa515ceb34811e54a394",
+   "third_party/externals/piex"                   : "https://android.googlesource.com/platform/external/piex.git@bb217acdca1cc0c16b704669dd6f91a1b509c406",
+-  "third_party/externals/swiftshader"            : "https://swiftshader.googlesource.com/SwiftShader@76855a9baecc97fa144ce70d7ae43a9f878e14c8",
+   "third_party/externals/vulkanmemoryallocator"  : "https://chromium.googlesource.com/external/github.com/GPUOpen-LibrariesAndSDKs/VulkanMemoryAllocator@a6bfc237255a6bac1513f7c1ebde6d8aed6b5191",
+-  # vulkan-deps is a meta-repo containing several interdependent Khronos Vulkan repositories.
+-  # When the vulkan-deps revision is updated, those repos (spirv-*, vulkan-*) should be updated as well.
+   "third_party/externals/vulkan-deps"            : "https://chromium.googlesource.com/vulkan-deps@a2dfb2276ea5f9467eb84c9a19ecf917d92e4135",
+-  "third_party/externals/spirv-cross"            : "https://chromium.googlesource.com/external/github.com/KhronosGroup/SPIRV-Cross@b8fcf307f1f347089e3c46eb4451d27f32ebc8d3",
+   "third_party/externals/spirv-headers"          : "https://skia.googlesource.com/external/github.com/KhronosGroup/SPIRV-Headers.git@996c728cf7dcfb29845cfa15222822318f047810",
+-  "third_party/externals/spirv-tools"            : "https://skia.googlesource.com/external/github.com/KhronosGroup/SPIRV-Tools.git@9117e042b93d4ff08d2406542708170f77aaa2a3",
+-  "third_party/externals/vello"                  : "https://skia.googlesource.com/external/github.com/linebender/vello.git@3ee3bea02164c5a816fe6c16ef4e3a810edb7620",
+-  "third_party/externals/vulkan-headers"         : "https://chromium.googlesource.com/external/github.com/KhronosGroup/Vulkan-Headers@cbcad3c0587dddc768d76641ea00f5c45ab5a278",
+-  "third_party/externals/vulkan-tools"           : "https://chromium.googlesource.com/external/github.com/KhronosGroup/Vulkan-Tools@15f2de809304aba619ee327f3273425418ca83de",
+-  "third_party/externals/vulkan-utility-libraries": "https://chromium.googlesource.com/external/github.com/KhronosGroup/Vulkan-Utility-Libraries@87ab6b39a97d084a2ef27db85e3cbaf5d2622a09",
+-  "third_party/externals/unicodetools"           : "https://chromium.googlesource.com/external/github.com/unicode-org/unicodetools@66a3fa9dbdca3b67053a483d130564eabc5fe095",
+-  #"third_party/externals/v8"                     : "https://chromium.googlesource.com/v8/v8.git@5f1ae66d5634e43563b2d25ea652dfb94c31a3b4",
+   "third_party/externals/wuffs"                  : "https://skia.googlesource.com/external/github.com/google/wuffs-mirror-release-c.git@e3f919ccfe3ef542cfc983a82146070258fb57f8",
+   "third_party/externals/zlib"                   : "https://chromium.googlesource.com/chromium/src/third_party/zlib@646b7f569718921d7d4b5b8e22572ff6c76f2596",
+ 
+diff --git a/bin/activate-emsdk b/bin/activate-emsdk
+index 687ca9f..7167d8d 100755
+--- a/bin/activate-emsdk
++++ b/bin/activate-emsdk
+@@ -17,6 +17,7 @@ EMSDK_PATH = os.path.join(EMSDK_ROOT, 'emsdk.py')
+ EMSDK_VERSION = '3.1.44'
+ 
+ def main():
++    return
+     if sysconfig.get_platform() in ['linux-aarch64', 'linux-arm64']:
+         # This platform cannot install emsdk at the provided version. See
+         # https://github.com/emscripten-core/emsdk/blob/main/emscripten-releases-tags.json#L5
diff --git a/relnotes/README.m130.md b/relnotes/README.m130.md
index fa8bc7ba..4b61d791 100644
--- a/relnotes/README.m130.md
+++ b/relnotes/README.m130.md
@@ -16,10 +16,10 @@ Since m128 (last beta release):
     overloaded as an alias to this, too.
 
   * The `SkUnicode` class is now available under python as `skia.Unicode`.
-    The constructor is known to fail on windows - It is likely that downloading
-    a `icudtl.dat` file, renaming from the versioned data-bin-{l,b}.zip in
-    https://github.com/unicode-org/icu/releases, is needed. Windows users please report
-    success/failure on this.
+    The constructor is known to fail on windows on m130, without a bundled
+    `icudtl.dat` file.
+
+    EDIT: Therefore, we bundle a `icudtl.dat` file for windows from m131 onward.
 
   * There are two examples `shape_text.py` (a python port of upstream's example), and
     `skparagraph-example.py` hosted [elsewhere](https://github.com/HinTak/skia-python-examples/).
diff --git a/relnotes/README.m131.md b/relnotes/README.m131.md
new file mode 100644
index 00000000..3c2602b8
--- /dev/null
+++ b/relnotes/README.m131.md
@@ -0,0 +1,10 @@
+Since m130:
+
+- We now build for Mac OS 13 (12 deprecated at github), and support python 3.13
+
+- GL examples and tests updated, to restrict "Mac OS friendly" GL settings to Mac only.
+  Some Linux installations - specifically, inside github CI - are sensitive to those;
+  typical desktop linux boxes with real graphic cards seem not to be affected.
+
+- `PDF.StructureElementNode.fAdditionalNodeIds` withdrawn.
+  `SkPDF::StructureElementNode::fAdditionalNodeIds` removed upstream.
diff --git a/relnotes/README.m132.md b/relnotes/README.m132.md
new file mode 100644
index 00000000..3e6f769f
--- /dev/null
+++ b/relnotes/README.m132.md
@@ -0,0 +1,26 @@
+Since m131:
+- `PathEffect.{DashType, DashInfo, asADash}` withdrawn.
+   Upstream removed them from public API in m132.
+
+- We now bundle a `icudtl.dat` for windows. This fixes windows-specific problems
+  with SkUnicode, libSkShaper, and SkParagraph. Thanks @meetwq for the change.
+  Relevant CI tests re-enabled and added.
+
+- Upstream's `SkNamedTransferFn::*` now available as `cms.NamedTransferFn.*`,
+  and `SkNamedGamut::*` as `cms.NamedGamut.*`. These are used in RuntimeEffect-related
+  code.
+
+- More overloads of `SkImage::makeShader` added, and `SkImage::makeRawShader` added
+  as `Image.makeRawShader`.
+
+- We binds `SkRuntimeEffect` as `skia.RuntimeEffect`. Added some helper classes:
+  `SkV3` as `skia.V3`, `SkV4` as `skia.V4`,
+  `SkRuntimeEffect::Result` as `RuntimeEffectResult`,
+  `SkRuntimeEffect::ChildPtr` as `RuntimeEffectChildPtr`,
+  `SkRuntimeEffectBuilder` as `RuntimeEffectBuilder`,
+  `std::vector<SkRuntimeEffect::ChildPtr>` as `VectorRuntimeEffectChildPtr`,
+  `SkSpan<const SkRuntimeEffect::ChildPtr>` as `SpanRuntimeEffectChildPtr`,
+  `SkRuntimeEffectBuilder::BuilderUniform` as `RuntimeEffectBuilderUniform`,
+  `SkRuntimeEffectBuilder::BuilderChild` as `RuntimeEffectBuilderChild`.
+  Details are subjected to change. We ported all 9 of current upstream SkSL c++ examples,
+  hosted [elsewhere](https://github.com/HinTak/skia-python-examples/).
diff --git a/scripts/build_Linux.sh b/scripts/build_Linux.sh
index 8fc6efca..4fda21eb 100644
--- a/scripts/build_Linux.sh
+++ b/scripts/build_Linux.sh
@@ -60,8 +60,8 @@ git clone https://gn.googlesource.com/gn && \
 
 # Build skia
 cd skia && \
-    patch -p1 < ../patch/skia-m130-minimize-download.patch && \
-    patch -p1 < ../patch/skia-m123-colrv1-freetype.diff && \
+    patch -p1 < ../patch/skia-m132-minimize-download.patch && \
+    patch -p1 < ../patch/skia-m132-colrv1-freetype.diff && \
     python3 tools/git-sync-deps && \
     cp -f ../gn/out/gn bin/gn && \
     bin/gn gen out/Release --args="
diff --git a/scripts/build_Windows.sh b/scripts/build_Windows.sh
index ce694e46..e56cc6de 100644
--- a/scripts/build_Windows.sh
+++ b/scripts/build_Windows.sh
@@ -4,8 +4,8 @@ export PATH="${PWD}/depot_tools:$PATH"
 
 # Build skia
 cd skia && \
-    patch -p1 < ../patch/skia-m130-minimize-download.patch && \
-    patch -p1 < ../patch/skia-m123-colrv1-freetype.diff && \
+    patch -p1 < ../patch/skia-m132-minimize-download.patch && \
+    patch -p1 < ../patch/skia-m132-colrv1-freetype.diff && \
     python tools/git-sync-deps && \
     bin/gn gen out/Release --args='
 is_official_build=true
diff --git a/scripts/build_macOS.sh b/scripts/build_macOS.sh
index 666d1fdb..2286603f 100644
--- a/scripts/build_macOS.sh
+++ b/scripts/build_macOS.sh
@@ -22,8 +22,8 @@ function apply_patch {
 }
 
 cd skia && \
-    patch -p1 < ../patch/skia-m130-minimize-download.patch && \
-    patch -p1 < ../patch/skia-m123-colrv1-freetype.diff && \
+    patch -p1 < ../patch/skia-m132-minimize-download.patch && \
+    patch -p1 < ../patch/skia-m132-colrv1-freetype.diff && \
     python3 tools/git-sync-deps && \
     bin/gn gen out/Release --args="
 is_official_build=true
diff --git a/setup.py b/setup.py
index e4494234..62277de9 100644
--- a/setup.py
+++ b/setup.py
@@ -12,13 +12,14 @@
     pass
 
 NAME = 'skia-python'
-__version__ = '130.0b10'
+__version__ = '132.0b11'
 
 SKIA_PATH = os.getenv('SKIA_PATH', 'skia')
 SKIA_OUT_PATH = os.getenv(
     'SKIA_OUT_PATH', os.path.join(SKIA_PATH, 'out', 'Release')
 )
 
+data_files = []
 if sys.platform == 'win32':
     DEFINE_MACROS = []  # doesn't work for cl.exe
     LIBRARIES = [
@@ -54,6 +55,7 @@
         '/OPT:ICF',
         '/OPT:REF',
     ]
+    data_files = [('Lib/site-packages', [os.path.join(SKIA_OUT_PATH, 'icudtl.dat')])]
 elif sys.platform == 'darwin':
     DEFINE_MACROS = [
         ('VERSION_INFO', __version__),
@@ -173,6 +175,7 @@ def build_extensions(self):
     long_description=open('README.md', 'r').read(),
     long_description_content_type='text/markdown',
     ext_modules=[extension],
+    data_files=data_files,
     install_requires=[
         'numpy',
         'pybind11>=2.6'
diff --git a/skia b/skia
index 0f81dc8b..b1fb88ac 160000
--- a/skia
+++ b/skia
@@ -1,1 +1,1 @@
-Subproject commit 0f81dc8b2e43c060972a8d8d229b346694a674f8
+Subproject commit b1fb88ac03b77cf36a1aa9802a5bdb673e3ff983
diff --git a/src/skia/ColorSpace.cpp b/src/skia/ColorSpace.cpp
index 25a5549f..c93bcf79 100644
--- a/src/skia/ColorSpace.cpp
+++ b/src/skia/ColorSpace.cpp
@@ -37,6 +37,16 @@ py::class_<skcms_TransferFunction>(skcms, "TransferFunction",
             };
         }), py::arg("v"));
 
+/* Upstream static constexpr's, which function like enum's,
+   but cannot be bound that way. */
+m.attr("cms").attr("NamedTransferFn") = m.attr("cms").attr("TransferFunction");
+m.attr("cms").attr("TransferFunction").attr("kSRGB")    = SkNamedTransferFn::kSRGB;
+m.attr("cms").attr("TransferFunction").attr("k2Dot2")   = SkNamedTransferFn::k2Dot2;
+m.attr("cms").attr("TransferFunction").attr("kLinear")  = SkNamedTransferFn::kLinear;
+m.attr("cms").attr("TransferFunction").attr("kRec2020") = SkNamedTransferFn::kRec2020;
+m.attr("cms").attr("TransferFunction").attr("kPQ")      = SkNamedTransferFn::kPQ;
+m.attr("cms").attr("TransferFunction").attr("kHLG")     = SkNamedTransferFn::kHLG;
+
 py::class_<skcms_Matrix3x3>(skcms, "Matrix3x3",
     R"docstring(
     A row-major 3x3 matrix (ie vals[row][col])
@@ -52,6 +62,15 @@ py::class_<skcms_Matrix3x3>(skcms, "Matrix3x3",
             }};
         }), py::arg("v"));
 
+/* Upstream static constexpr's, which function like enum's,
+   but cannot be bound that way. */
+m.attr("cms").attr("NamedGamut") = m.attr("cms").attr("Matrix3x3");
+m.attr("cms").attr("Matrix3x3").attr("kSRGB")      = SkNamedGamut::kSRGB;
+m.attr("cms").attr("Matrix3x3").attr("kAdobeRGB")  = SkNamedGamut::kAdobeRGB;
+m.attr("cms").attr("Matrix3x3").attr("kDisplayP3") = SkNamedGamut::kDisplayP3;
+m.attr("cms").attr("Matrix3x3").attr("kRec2020")   = SkNamedGamut::kRec2020;
+m.attr("cms").attr("Matrix3x3").attr("kXYZ")       = SkNamedGamut::kXYZ;
+
 py::class_<SkColorSpace, sk_sp<SkColorSpace>>(m, "ColorSpace")
     .def("toProfile",
         [] (const SkColorSpace& colorspace) {
diff --git a/src/skia/Document.cpp b/src/skia/Document.cpp
index 102cc10e..cdab79f7 100644
--- a/src/skia/Document.cpp
+++ b/src/skia/Document.cpp
@@ -216,8 +216,11 @@ py::class_<SkPDF::StructureElementNode>(pdf, "StructureElementNode",
     //     &SkPDF::StructureElementNode::fChildVector)
     .def_readwrite("fNodeId",
         &SkPDF::StructureElementNode::fNodeId)
+/* Removed in m131 */
+/*
     .def_readonly("fAdditionalNodeIds",
         &SkPDF::StructureElementNode::fAdditionalNodeIds)
+*/
     .def_readonly("fAttributes",
         &SkPDF::StructureElementNode::fAttributes)
     .def_readwrite("fAlt",
diff --git a/src/skia/GrContext.cpp b/src/skia/GrContext.cpp
index caaf7dd4..2c5e88fe 100644
--- a/src/skia/GrContext.cpp
+++ b/src/skia/GrContext.cpp
@@ -1,14 +1,14 @@
 #include "common.h"
 #include <include/core/SkTextureCompressionType.h>
-#include <include/gpu/GrBackendSemaphore.h>
-#include <include/gpu/GrBackendSurface.h>
-#include <include/gpu/GrContextThreadSafeProxy.h>
+#include <include/gpu/ganesh/GrBackendSemaphore.h>
+#include <include/gpu/ganesh/GrBackendSurface.h>
+#include <include/gpu/ganesh/GrContextThreadSafeProxy.h>
 #include <include/gpu/GpuTypes.h>
-#include <include/gpu/mock/GrMockTypes.h>
-#include <include/gpu/gl/GrGLInterface.h>
+#include <include/gpu/ganesh/mock/GrMockTypes.h>
+#include <include/gpu/ganesh/gl/GrGLInterface.h>
 #include <include/gpu/ganesh/gl/GrGLBackendSurface.h>
 #include <include/gpu/ganesh/gl/GrGLDirectContext.h>
-#include <include/gpu/vk/GrVkTypes.h>
+#include <include/gpu/ganesh/vk/GrVkTypes.h>
 #include <include/gpu/ganesh/vk/GrVkBackendSemaphore.h>
 #include <include/gpu/ganesh/vk/GrVkBackendSurface.h>
 #include <include/gpu/ganesh/vk/GrVkDirectContext.h>
@@ -898,7 +898,7 @@ py::class_<GrDirectContext, sk_sp<GrDirectContext>, GrRecordingContext>(m, "GrDi
         )docstring",
         py::arg("info"))
     .def("flush", py::overload_cast<>(&GrDirectContext::flush))
-    .def("submit", &GrDirectContext::submit,
+    .def("submit", py::overload_cast<GrSyncCpu>(&GrDirectContext::submit),
         R"docstring(
         Submit outstanding work to the gpu from all previously un-submitted
         flushes. The return value of the submit will indicate whether or not the
diff --git a/src/skia/GrContext_gl.cpp b/src/skia/GrContext_gl.cpp
index 6024abea..b0413997 100644
--- a/src/skia/GrContext_gl.cpp
+++ b/src/skia/GrContext_gl.cpp
@@ -1,6 +1,6 @@
 #include "common.h"
-#include <include/gpu/gl/GrGLTypes.h>
-#include <include/gpu/gl/GrGLInterface.h>
+#include <include/gpu/ganesh/gl/GrGLTypes.h>
+#include <include/gpu/ganesh/gl/GrGLInterface.h>
 
 void initGrContext_gl(py::module &m) {
 
diff --git a/src/skia/GrContext_mock.cpp b/src/skia/GrContext_mock.cpp
index 24d94a10..3b9f7307 100644
--- a/src/skia/GrContext_mock.cpp
+++ b/src/skia/GrContext_mock.cpp
@@ -1,6 +1,6 @@
 #include "common.h"
-#include <include/gpu/mock/GrMockTypes.h>
-#include <include/gpu/GrBackendSurface.h>
+#include <include/gpu/ganesh/mock/GrMockTypes.h>
+#include <include/gpu/ganesh/GrBackendSurface.h>
 #include <include/core/SkTextureCompressionType.h>
 
 void initGrContext_mock(py::module &m) {
diff --git a/src/skia/GrContext_vk.cpp b/src/skia/GrContext_vk.cpp
index 7a0705bd..75bdaa21 100644
--- a/src/skia/GrContext_vk.cpp
+++ b/src/skia/GrContext_vk.cpp
@@ -1,6 +1,6 @@
 #include "common.h"
 #include <include/gpu/vk/VulkanBackendContext.h>
-#include <include/gpu/vk/GrVkTypes.h>
+#include <include/gpu/ganesh/vk/GrVkTypes.h>
 
 void initGrContext_vk(py::module &m) {
 
diff --git a/src/skia/Image.cpp b/src/skia/Image.cpp
index 13bb1c97..1d987ee4 100644
--- a/src/skia/Image.cpp
+++ b/src/skia/Image.cpp
@@ -1,7 +1,7 @@
 #include "common.h"
 #include <include/codec/SkEncodedImageFormat.h>
 #include <include/core/SkSamplingOptions.h>
-#include <include/gpu/GrBackendSurface.h>
+#include <include/gpu/ganesh/GrBackendSurface.h>
 #include <include/gpu/GpuTypes.h>
 #include <include/gpu/ganesh/SkImageGanesh.h>
 #include <include/encode/SkJpegEncoder.h>
@@ -1230,7 +1230,153 @@ image
         py::arg_v("tmy", SkTileMode::kClamp, "skia.TileMode.kClamp"),
         py::arg_v("samplingOptions", SkSamplingOptions(), "skia.SamplingOptions()"),
         py::arg("localMatrix") = nullptr)
-    // TODO: Other makeShader overloads.
+    .def("makeShader",
+        py::overload_cast<SkTileMode, SkTileMode, const SkSamplingOptions&, const SkMatrix&>(
+            &SkImage::makeShader, py::const_),
+        R"docstring(
+        Creates :py:class:`Shader` from :py:class:`Image`.
+
+        :py:class:`Shader` dimensions are taken from :py:class:`Image`.
+        :py:class:`Shader` uses :py:class:`TileMode` rules to fill drawn area
+        outside :py:class:`Image`. localMatrix permits transforming
+        :py:class:`Image` before :py:class:`Canvas` matrix is applied.
+
+        :param skia.TileMode tmx: tiling in the x direction
+        :param skia.TileMode tmy: tiling in the y direction
+        :param skia.Matrix localMatrix: :py:class:`Image` transformation, or
+            nullptr
+        :return: :py:class:`Shader` containing :py:class:`Image`
+        )docstring",
+        py::arg_v("tmx", SkTileMode::kClamp, "skia.TileMode.kClamp"),
+        py::arg_v("tmy", SkTileMode::kClamp, "skia.TileMode.kClamp"),
+        py::arg_v("samplingOptions", SkSamplingOptions(), "skia.SamplingOptions()"),
+        py::arg("localMatrix"))
+    .def("makeShader",
+        py::overload_cast<const SkSamplingOptions&, const SkMatrix&>(
+            &SkImage::makeShader, py::const_),
+        R"docstring(
+        Creates :py:class:`Shader` from :py:class:`Image`.
+
+        :py:class:`Shader` dimensions are taken from :py:class:`Image`.
+        localMatrix permits transforming
+        :py:class:`Image` before :py:class:`Canvas` matrix is applied.
+
+        :param skia.Matrix localMatrix: :py:class:`Image` transformation, or
+            nullptr
+        :return: :py:class:`Shader` containing :py:class:`Image`
+        )docstring",
+        py::arg_v("samplingOptions", SkSamplingOptions(), "skia.SamplingOptions()"),
+        py::arg("localMatrix"))
+    .def("makeShader",
+        py::overload_cast<const SkSamplingOptions&, const SkMatrix*>(
+            &SkImage::makeShader, py::const_),
+        R"docstring(
+        Creates :py:class:`Shader` from :py:class:`Image`.
+
+        :py:class:`Shader` dimensions are taken from :py:class:`Image`.
+        localMatrix permits transforming
+        :py:class:`Image` before :py:class:`Canvas` matrix is applied.
+
+        :param skia.Matrix localMatrix: :py:class:`Image` transformation, or
+            nullptr
+        :return: :py:class:`Shader` containing :py:class:`Image`
+        )docstring",
+        py::arg_v("samplingOptions", SkSamplingOptions(), "skia.SamplingOptions()"),
+        py::arg("localMatrix") = nullptr)
+    .def("makeRawShader",
+        py::overload_cast<SkTileMode, SkTileMode, const SkSamplingOptions&, const SkMatrix*>(
+            &SkImage::makeRawShader, py::const_),
+        R"docstring(
+        Creates :py:class:`Shader` from :py:class:`Image`.
+
+        makeRawShader functions like makeShader, but for images that contain non-color data.
+        This includes images encoding things like normals, material properties (eg, roughness),
+        heightmaps, or any other purely mathematical data that happens to be stored in an image.
+        These types of images are useful with some programmable shaders (see: SkRuntimeEffect).
+
+        :py:class:`Shader` dimensions are taken from :py:class:`Image`.
+        :py:class:`Shader` uses :py:class:`TileMode` rules to fill drawn area
+        outside :py:class:`Image`. localMatrix permits transforming
+        :py:class:`Image` before :py:class:`Canvas` matrix is applied.
+
+        :param skia.TileMode tmx: tiling in the x direction
+        :param skia.TileMode tmy: tiling in the y direction
+        :param skia.Matrix localMatrix: :py:class:`Image` transformation, or
+            nullptr
+        :return: :py:class:`Shader` containing :py:class:`Image`
+        )docstring",
+        py::arg_v("tmx", SkTileMode::kClamp, "skia.TileMode.kClamp"),
+        py::arg_v("tmy", SkTileMode::kClamp, "skia.TileMode.kClamp"),
+        py::arg_v("samplingOptions", SkSamplingOptions(), "skia.SamplingOptions()"),
+        py::arg("localMatrix") = nullptr)
+    .def("makeRawShader",
+        py::overload_cast<SkTileMode, SkTileMode, const SkSamplingOptions&, const SkMatrix&>(
+            &SkImage::makeRawShader, py::const_),
+        R"docstring(
+        Creates :py:class:`Shader` from :py:class:`Image`.
+
+        makeRawShader functions like makeShader, but for images that contain non-color data.
+        This includes images encoding things like normals, material properties (eg, roughness),
+        heightmaps, or any other purely mathematical data that happens to be stored in an image.
+        These types of images are useful with some programmable shaders (see: SkRuntimeEffect).
+
+        :py:class:`Shader` dimensions are taken from :py:class:`Image`.
+        :py:class:`Shader` uses :py:class:`TileMode` rules to fill drawn area
+        outside :py:class:`Image`. localMatrix permits transforming
+        :py:class:`Image` before :py:class:`Canvas` matrix is applied.
+
+        :param skia.TileMode tmx: tiling in the x direction
+        :param skia.TileMode tmy: tiling in the y direction
+        :param skia.Matrix localMatrix: :py:class:`Image` transformation, or
+            nullptr
+        :return: :py:class:`Shader` containing :py:class:`Image`
+        )docstring",
+        py::arg_v("tmx", SkTileMode::kClamp, "skia.TileMode.kClamp"),
+        py::arg_v("tmy", SkTileMode::kClamp, "skia.TileMode.kClamp"),
+        py::arg_v("samplingOptions", SkSamplingOptions(), "skia.SamplingOptions()"),
+        py::arg("localMatrix"))
+    .def("makeRawShader",
+        py::overload_cast<const SkSamplingOptions&, const SkMatrix&>(
+            &SkImage::makeRawShader, py::const_),
+        R"docstring(
+        Creates :py:class:`Shader` from :py:class:`Image`.
+
+        makeRawShader functions like makeShader, but for images that contain non-color data.
+        This includes images encoding things like normals, material properties (eg, roughness),
+        heightmaps, or any other purely mathematical data that happens to be stored in an image.
+        These types of images are useful with some programmable shaders (see: SkRuntimeEffect).
+
+        :py:class:`Shader` dimensions are taken from :py:class:`Image`.
+        localMatrix permits transforming
+        :py:class:`Image` before :py:class:`Canvas` matrix is applied.
+
+        :param skia.Matrix localMatrix: :py:class:`Image` transformation, or
+            nullptr
+        :return: :py:class:`Shader` containing :py:class:`Image`
+        )docstring",
+        py::arg_v("samplingOptions", SkSamplingOptions(), "skia.SamplingOptions()"),
+        py::arg("localMatrix"))
+    .def("makeRawShader",
+        py::overload_cast<const SkSamplingOptions&, const SkMatrix*>(
+            &SkImage::makeRawShader, py::const_),
+        R"docstring(
+        Creates :py:class:`Shader` from :py:class:`Image`.
+
+        makeRawShader functions like makeShader, but for images that contain non-color data.
+        This includes images encoding things like normals, material properties (eg, roughness),
+        heightmaps, or any other purely mathematical data that happens to be stored in an image.
+        These types of images are useful with some programmable shaders (see: SkRuntimeEffect).
+
+        :py:class:`Shader` dimensions are taken from :py:class:`Image`.
+        localMatrix permits transforming
+        :py:class:`Image` before :py:class:`Canvas` matrix is applied.
+
+        :param skia.Matrix localMatrix: :py:class:`Image` transformation, or
+            nullptr
+        :return: :py:class:`Shader` containing :py:class:`Image`
+        )docstring",
+        py::arg_v("samplingOptions", SkSamplingOptions(), "skia.SamplingOptions()"),
+        py::arg("localMatrix") = nullptr)
     .def("peekPixels", &SkImage::peekPixels,
         R"docstring(
         Copies :py:class:`Image` pixel address, row bytes, and
diff --git a/src/skia/PathEffect.cpp b/src/skia/PathEffect.cpp
index 3e6a10ba..a01f2e7d 100644
--- a/src/skia/PathEffect.cpp
+++ b/src/skia/PathEffect.cpp
@@ -138,6 +138,8 @@ py::class_<SkPathEffect, sk_sp<SkPathEffect>, SkFlattenable>
         ~skia.TrimPathEffect
     )docstring");
 
+/* SkPathEffect::DashInfo withdrawn from public API in m132 */
+/*
 py::class_<SkPathEffect::DashInfo>(patheffect, "DashInfo")
     .def(py::init<>())
     .def_property_readonly("fIntervals",
@@ -160,6 +162,7 @@ py::class_<SkPathEffect::DashInfo>(patheffect, "DashInfo")
         Offset into the dashed interval pattern.
         )docstring")
     ;
+*/
 
 /*
 py::class_<SkPathEffect::PointData> pointdata(patheffect, "PointData",
@@ -195,6 +198,8 @@ pointdata
     ;
 */
 
+/* SkPathEffect::DashType withdrawn from public API in m132 */
+/*
 py::enum_<SkPathEffect::DashType>(patheffect, "DashType",
     R"docstring(
     If the :py:class:`PathEffect` can be represented as a dash pattern, asADash
@@ -215,6 +220,7 @@ py::enum_<SkPathEffect::DashType>(patheffect, "DashType",
     .value("kDash_DashType", SkPathEffect::DashType::kDash_DashType,
         "fills in all of the info parameter")
     .export_values();
+*/
 
 patheffect
     .def("filterPath", py::overload_cast<SkPath*, const SkPath&, SkStrokeRec*, const SkRect*>(&SkPathEffect::filterPath, py::const_),
@@ -251,7 +257,10 @@ patheffect
         py::arg("results"), py::arg("src"), py::arg("stroke_rec"),
         py::arg("matrix"), py::arg("cullR"))
 */
+/* SkPathEffect::asADash withdrawn from public API in m132 */
+/*
     .def("asADash", &SkPathEffect::asADash, py::arg("info"))
+*/
     .def_static("MakeSum",
         [] (const SkPathEffect& first, const SkPathEffect& second) {
             auto first_ = first.serialize();
diff --git a/src/skia/RuntimeEffect.cpp b/src/skia/RuntimeEffect.cpp
new file mode 100644
index 00000000..ee40d27a
--- /dev/null
+++ b/src/skia/RuntimeEffect.cpp
@@ -0,0 +1,238 @@
+#include <stdexcept>
+#include "common.h"
+#include <include/effects/SkRuntimeEffect.h>
+//#include <include/core/SkM44.h> // defines SkV3, SkV4 ; M44 used in Matrix/Canvas ; Revisit.
+#include <pybind11/stl_bind.h>
+
+PYBIND11_MAKE_OPAQUE(std::vector<SkRuntimeEffect::ChildPtr>)
+
+void initRuntimeEffect(py::module &m) {
+py::class_<SkRuntimeEffect, sk_sp<SkRuntimeEffect>, SkRefCnt> runtime_effect(m, "RuntimeEffect");
+
+py::class_<SkRuntimeEffect::ChildPtr> runtime_effect_childptr(m, "RuntimeEffectChildPtr");
+
+py::bind_vector<std::vector<SkRuntimeEffect::ChildPtr>>(m, "VectorRuntimeEffectChildPtr");
+py::class_<SkSpan<const SkRuntimeEffect::ChildPtr>> span_runtime_effect_childptr(m, "SpanRuntimeEffectChildPtr");
+
+py::class_<SkRuntimeEffectBuilder> runtime_effect_builder(m, "RuntimeEffectBuilder");
+
+py::class_<SkV3>(m, "V3")
+    .def(py::init(
+        [] (float x, float y, float z) {
+            return SkV3{x, y, z};
+        }))
+    .def(py::init(
+        [] (py::tuple v3) {
+            if (v3.size() != 3)
+                throw py::value_error("V3 must have exactly three elements.");
+            return SkV3{v3[0].cast<float>(), v3[1].cast<float>(), v3[2].cast<float>()};
+        }),
+        py::arg("v3"))
+    ;
+
+py::implicitly_convertible<py::tuple, SkV3>();
+
+py::class_<SkV4>(m, "V4")
+    .def(py::init(
+        [] (float x, float y, float z, float w) {
+            return SkV4{x, y, z, w};
+        }))
+    .def(py::init(
+        [] (py::tuple v4) {
+            if (v4.size() != 4)
+                throw py::value_error("V4 must have exactly four elements.");
+            return SkV4{v4[0].cast<float>(), v4[1].cast<float>(), v4[2].cast<float>(), v4[3].cast<float>()};
+        }),
+        py::arg("v4"))
+    ;
+
+py::implicitly_convertible<py::tuple, SkV4>();
+
+py::class_<SkRuntimeEffect::Result>(m, "RuntimeEffectResult")
+    .def_readwrite("effect", &SkRuntimeEffect::Result::effect)
+    .def_readwrite("errorText", &SkRuntimeEffect::Result::errorText)
+    ;
+
+runtime_effect_childptr
+    .def(py::init<>())
+    .def(py::init<sk_sp<SkShader>>())
+    .def(py::init<sk_sp<SkColorFilter>>())
+    .def(py::init<sk_sp<SkBlender>>())
+    ;
+
+span_runtime_effect_childptr
+    .def(py::init<>())
+    .def(py::init<const SkRuntimeEffect::ChildPtr*, size_t>())
+    .def(py::init<const SkSpan<const SkRuntimeEffect::ChildPtr>&>())
+    .def(py::init(
+        [] (std::vector<SkRuntimeEffect::ChildPtr>& v) {
+            return SkSpan<SkRuntimeEffect::ChildPtr>(&v[0], v.size());
+        }))
+    .def(py::init<const SkSpan<SkRuntimeEffect::ChildPtr>&>())
+    ;
+
+py::implicitly_convertible<sk_sp<SkShader>, SkRuntimeEffect::ChildPtr>();
+py::implicitly_convertible<sk_sp<SkColorFilter>, SkRuntimeEffect::ChildPtr>();
+py::implicitly_convertible<sk_sp<SkBlender>, SkRuntimeEffect::ChildPtr>();
+py::implicitly_convertible<std::vector<SkRuntimeEffect::ChildPtr>, SkSpan<const SkRuntimeEffect::ChildPtr>>();
+
+/*
+  All of these static methods check Result.effect being non-null, throw with errorText if null;
+  they differ from upsteam c++ APIs, which asks clients to check.
+*/
+runtime_effect
+    .def_static("MakeForColorFilter",
+        [] (SkString sksl, const SkRuntimeEffect::Options& options) {
+            auto [effect, err] = SkRuntimeEffect::MakeForColorFilter(sksl, options);
+            if (!effect)
+                throw std::runtime_error(err.data());
+            return effect;
+        },
+        py::arg("sksl"), py::arg("options"))
+    .def_static("MakeForColorFilter",
+        [] (SkString sksl) {
+            auto [effect, err] = SkRuntimeEffect::MakeForColorFilter(sksl);
+            if (!effect)
+                throw std::runtime_error(err.data());
+            return effect;
+        },
+        py::arg("sksl"))
+    .def_static("MakeForShader",
+        [] (SkString sksl, const SkRuntimeEffect::Options& options) {
+            auto [effect, err] = SkRuntimeEffect::MakeForShader(sksl, options);
+            if (!effect)
+                throw std::runtime_error(err.data());
+            return effect;
+        },
+        py::arg("sksl"), py::arg("options"))
+    .def_static("MakeForShader",
+        [] (SkString sksl) {
+            auto [effect, err] = SkRuntimeEffect::MakeForShader(sksl);
+            if (!effect)
+                throw std::runtime_error(err.data());
+            return effect;
+        },
+        py::arg("sksl"))
+    .def_static("MakeForBlender",
+        [] (SkString sksl, const SkRuntimeEffect::Options& options) {
+            auto [effect, err] = SkRuntimeEffect::MakeForBlender(sksl, options);
+            if (!effect)
+                throw std::runtime_error(err.data());
+            return effect;
+        },
+        py::arg("sksl"), py::arg("options"))
+    .def_static("MakeForBlender",
+        [] (SkString sksl) {
+            auto [effect, err] = SkRuntimeEffect::MakeForBlender(sksl);
+            if (!effect)
+                throw std::runtime_error(err.data());
+            return effect;
+        },
+        py::arg("sksl"))
+    .def("makeShader",
+        [] (SkRuntimeEffect& runtime_effect, sk_sp<const SkData> uniforms) {
+            return runtime_effect.makeShader(uniforms, {});
+        },
+         py::arg("uniforms"))
+    .def("makeShader",
+        py::overload_cast<sk_sp<const SkData>, sk_sp<SkShader>[], size_t, const SkMatrix*>(&SkRuntimeEffect::makeShader, py::const_),
+        py::arg("uniforms"), py::arg("children"),
+        py::arg("childCount"), py::arg("localMatrix") = nullptr)
+    .def("makeShader",
+        py::overload_cast<sk_sp<const SkData>, SkSpan<const SkRuntimeEffect::ChildPtr>, const SkMatrix*>(&SkRuntimeEffect::makeShader, py::const_),
+        py::arg("uniforms"), py::arg("children"),
+        py::arg("localMatrix") = nullptr)
+    .def("makeColorFilter",
+        py::overload_cast<sk_sp<const SkData>>(&SkRuntimeEffect::makeColorFilter, py::const_),
+        py::arg("uniforms"))
+    .def("makeColorFilter",
+        py::overload_cast<sk_sp<const SkData>, sk_sp<SkColorFilter>[], size_t>(&SkRuntimeEffect::makeColorFilter, py::const_),
+        py::arg("uniforms"), py::arg("children"),
+        py::arg("childCount"))
+    .def("makeColorFilter",
+        py::overload_cast<sk_sp<const SkData>, SkSpan<const SkRuntimeEffect::ChildPtr>>(&SkRuntimeEffect::makeColorFilter, py::const_),
+        py::arg("uniforms"), py::arg("children"))
+    .def("makeBlender",
+        [] (SkRuntimeEffect& runtime_effect, sk_sp<const SkData> uniforms) {
+            return runtime_effect.makeBlender(uniforms, {});
+        },
+        py::arg("uniforms"))
+    .def("makeBlender",
+        py::overload_cast<sk_sp<const SkData>, SkSpan<const SkRuntimeEffect::ChildPtr>>(&SkRuntimeEffect::makeBlender, py::const_),
+        py::arg("uniforms"), py::arg("children") = SkSpan<const SkRuntimeEffect::ChildPtr>{})
+    ;
+
+py::class_<SkRuntimeEffectBuilder::BuilderUniform>(m, "RuntimeEffectBuilderUniform")
+    .def(py::init<>())
+    ;
+
+py::class_<SkRuntimeEffectBuilder::BuilderChild>(m, "RuntimeEffectBuilderChild")
+    .def(py::init<>())
+    ;
+
+runtime_effect_builder
+    .def(py::init<sk_sp<SkRuntimeEffect>>())
+    .def(py::init<sk_sp<SkRuntimeEffect>, sk_sp<SkData>>())
+    .def("uniform", &SkRuntimeEffectBuilder::uniform,
+        py::arg("name"))
+    .def("child", &SkRuntimeEffectBuilder::child,
+        py::arg("name"))
+    .def("setUniform",
+        [] (SkRuntimeEffectBuilder& builder, std::string_view name, int uniform) {
+            auto v = builder.uniform(name);
+            v = uniform;
+        },
+        py::arg("name"), py::arg("uniform"))
+    .def("setUniform",
+        [] (SkRuntimeEffectBuilder& builder, std::string_view name, const SkV3& uniform) {
+            auto v = builder.uniform(name);
+            v = uniform;
+        },
+        py::arg("name"), py::arg("uniform"))
+    .def("setUniform",
+        [] (SkRuntimeEffectBuilder& builder, std::string_view name, const SkV4& uniform) {
+            auto v = builder.uniform(name);
+            v = uniform;
+        },
+        py::arg("name"), py::arg("uniform"))
+    .def("setUniform",
+        [] (SkRuntimeEffectBuilder& builder, std::string_view name, py::list vN) {
+          if (vN.size() != 3 && vN.size() != 4)
+                throw py::value_error("Input must have exactly three or four elements.");
+            auto v = builder.uniform(name);
+            if (vN.size() == 3)
+                v = SkV3{vN[0].cast<float>(), vN[1].cast<float>(), vN[2].cast<float>()};
+            if (vN.size() == 4)
+                v = SkV4{vN[0].cast<float>(), vN[1].cast<float>(), vN[2].cast<float>(), vN[3].cast<float>()};
+        },
+        py::arg("name"), py::arg("uniform"))
+    .def("setChild",
+        [] (SkRuntimeEffectBuilder& builder, std::string_view name, sk_sp<SkShader> child) {
+            auto v = builder.child(name);
+            v = child;
+        },
+        py::arg("name"), py::arg("child"))
+    .def("setChild",
+        [] (SkRuntimeEffectBuilder& builder, std::string_view name, sk_sp<SkColorFilter> child) {
+            auto v = builder.child(name);
+            v = child;
+        },
+        py::arg("name"), py::arg("child"))
+    .def("setChild",
+        [] (SkRuntimeEffectBuilder& builder, std::string_view name, sk_sp<SkBlender> child) {
+            auto v = builder.child(name);
+            v = child;
+        },
+        py::arg("name"), py::arg("child"))
+    .def("uniforms", &SkRuntimeEffectBuilder::uniforms)
+    .def("children", &SkRuntimeEffectBuilder::children)
+    .def("makeShader", &SkRuntimeEffectBuilder::makeShader,
+        py::arg("localMatrix") = nullptr)
+    .def("makeColorFilter", &SkRuntimeEffectBuilder::makeColorFilter)
+    .def("makeBlender", &SkRuntimeEffectBuilder::makeBlender)
+    ;
+
+m.attr("RuntimeShaderBuilder") = m.attr("RuntimeEffectBuilder");
+m.attr("RuntimeColorFilterBuilder") = m.attr("RuntimeEffectBuilder");
+m.attr("RuntimeBlendBuilder") = m.attr("RuntimeEffectBuilder");
+}
diff --git a/src/skia/Shader.cpp b/src/skia/Shader.cpp
index e89f95b5..d1b37690 100644
--- a/src/skia/Shader.cpp
+++ b/src/skia/Shader.cpp
@@ -1,5 +1,4 @@
 #include "common.h"
-#include <include/effects/SkRuntimeEffect.h>
 #include <include/effects/SkGradientShader.h>
 #include <include/effects/SkPerlinNoiseShader.h>
 #include <include/effects/SkBlenders.h>
diff --git a/src/skia/Surface.cpp b/src/skia/Surface.cpp
index 7ecaf7e7..d4bbc026 100644
--- a/src/skia/Surface.cpp
+++ b/src/skia/Surface.cpp
@@ -3,7 +3,7 @@
 #include <include/gpu/ganesh/gl/GrGLBackendSurface.h>
 #include <include/private/chromium/GrSurfaceCharacterization.h>
 #include <include/gpu/GpuTypes.h>
-#include <include/gpu/GrTypes.h>
+#include <include/gpu/ganesh/GrTypes.h>
 #include <include/gpu/ganesh/SkSurfaceGanesh.h>
 #include <include/gpu/MutableTextureState.h>
 #include <pybind11/operators.h>
diff --git a/src/skia/common.h b/src/skia/common.h
index d6f4d9d8..0cd21ab9 100644
--- a/src/skia/common.h
+++ b/src/skia/common.h
@@ -12,7 +12,7 @@
 #include <include/core/SkData.h>
 #include <include/core/SkDocument.h>
 #include <include/core/SkFont.h>
-#include <include/gpu/GrDirectContext.h>
+#include <include/gpu/ganesh/GrDirectContext.h>
 #include <include/core/SkImage.h>
 #include <include/core/SkImageFilter.h>
 #include <include/core/SkImageInfo.h>
diff --git a/src/skia/main.cpp b/src/skia/main.cpp
index 8d7caca5..450e7b1e 100644
--- a/src/skia/main.cpp
+++ b/src/skia/main.cpp
@@ -29,6 +29,7 @@ void initPoint(py::module &);
 void initRect(py::module &);
 void initRefCnt(py::module &);
 void initRegion(py::module &);
+void initRuntimeEffect(py::module &);
 void initSamplingOptions(py::module &);
 void initScalar(py::module &);
 void initSize(py::module &);
@@ -75,6 +76,7 @@ PYBIND11_MODULE(skia, m) {
     initPathMeasure(m);
     initPicture(m);
     initPixmap(m);
+    initRuntimeEffect(m);
     initScalar(m);
     initTextBlob(m);
     initVertices(m);


Test patch:
diff --git a/tests/conftest.py b/tests/conftest.py
index a8b40518..0658d2ae 100644
--- a/tests/conftest.py
+++ b/tests/conftest.py
@@ -16,10 +16,11 @@ def glfw_context():
     glfw.window_hint(glfw.VISIBLE, glfw.FALSE)
     glfw.window_hint(glfw.STENCIL_BITS, 8)
     # see https://www.glfw.org/faq#macos
-    glfw.window_hint(glfw.CONTEXT_VERSION_MAJOR, 3)
-    glfw.window_hint(glfw.CONTEXT_VERSION_MINOR, 2)
-    glfw.window_hint(glfw.OPENGL_FORWARD_COMPAT, True)
-    glfw.window_hint(glfw.OPENGL_PROFILE, glfw.OPENGL_CORE_PROFILE)
+    if sys.platform.startswith("darwin"):
+        glfw.window_hint(glfw.CONTEXT_VERSION_MAJOR, 3)
+        glfw.window_hint(glfw.CONTEXT_VERSION_MINOR, 2)
+        glfw.window_hint(glfw.OPENGL_FORWARD_COMPAT, True)
+        glfw.window_hint(glfw.OPENGL_PROFILE, glfw.OPENGL_CORE_PROFILE)
     context = glfw.create_window(640, 480, '', None, None)
     glfw.make_context_current(context)
     logger.debug('glfw context created')
diff --git a/tests/test_font.py b/tests/test_font.py
index 2a220f98..8e1d78e5 100644
--- a/tests/test_font.py
+++ b/tests/test_font.py
@@ -1,3 +1,5 @@
+import os
+import sys
 import skia
 import pytest
 
@@ -240,6 +242,17 @@ def svg_blob(svgface):
     blob = skia.TextBlob.MakeFromShapedText(text, font)
     return blob
 
+def test_svg_blob_1(svgface):
+    text = "abcdefgh"
+    font = skia.Font(svgface,109)
+    blob = skia.TextBlob.MakeFromText(text, font)
+    assert blob is not None
+
+def test_svg_blob_2(svgface):
+    text = "abcdefgh"
+    font = skia.Font(svgface,109)
+    blob = skia.TextBlob.MakeFromShapedText(text, font)
+    assert blob is not None
 
 # This test doesn't really test that the SVG table loads correctly -
 # Rather, it depends on the fact that, for this particular font,
@@ -418,6 +431,8 @@ def test_FontMgr_ref_unref(fontmgr):
 def font():
     return skia.Font(skia.Typeface(""))
 
+def test_font():
+    assert skia.Font(skia.Typeface("")) is not None
 
 @pytest.mark.parametrize('args', [
     tuple(),
@@ -643,3 +658,50 @@ def test_FontMetrics_hasStrikeoutThickness(fontmetrics):
 def test_FontMetrics_hasStrikeoutPosition(fontmetrics):
     position = 0.
     assert isinstance(fontmetrics.hasStrikeoutPosition(position), bool)
+
+
+@pytest.fixture
+def color_emoji_run():
+    if sys.platform.startswith("linux"):
+        if os.path.exists("/usr/share/fonts/truetype/noto/NotoColorEmoji.ttf"): # Ubuntu CI
+            # Ubuntu is weird - the font is on disk but not accessible to fontconfig
+            # - Possibly https://bugs.launchpad.net/bugs/2054924
+            typeface = skia.Typeface.MakeFromFile("/usr/share/fonts/truetype/noto/NotoColorEmoji.ttf")
+        elif os.path.exists("/usr/share/fonts/google-noto-color-emoji-fonts/NotoColorEmoji.ttf"): # Fedora
+            typeface = skia.Typeface.MakeFromFile("/usr/share/fonts/google-noto-color-emoji-fonts/NotoColorEmoji.ttf")
+        else:
+            pytest.skip("Not in Ubuntu CI")
+    if sys.platform.startswith("darwin"):
+        typeface = skia.Typeface("Apple Color Emoji")
+    if sys.platform.startswith("win"):
+        typeface = skia.Typeface("Segoe UI Emoji") # COLRv0
+    text = "✌✌🏻"
+    font = skia.Font(typeface,109)
+    blob = skia.TextBlob.MakeFromShapedText(text, font)
+    run = [x for x in blob]
+    return run[0]
+
+# We want this exactly two (and not three) on all platforms, under all circumstances; no conditionals.
+def test_emoji_count(color_emoji_run):
+    assert (color_emoji_run.fGlyphCount == 2)
+
+def test_emoji_typeface(color_emoji_run):
+    assert ((color_emoji_run.fTypeface.getFamilyName() == "Noto Color Emoji")
+            or (color_emoji_run.fTypeface.getFamilyName() == "Apple Color Emoji")
+            or (color_emoji_run.fTypeface.getFamilyName() == "Segoe UI Emoji"))
+
+# The numbers are hardcoded for three specific version of fonts,
+# and will change if the hosts are upgraded.
+def test_emoji_glyph1(color_emoji_run):
+    if (os.getenv("GITHUB_ACTION") == True):
+        assert ((color_emoji_run.fGlyphIndices[0] == 148)
+                or (color_emoji_run.fGlyphIndices[0] == 247)
+                or (color_emoji_run.fGlyphIndices[0] == 1567))
+
+# The numbers are hardcoded for three specific version of fonts,
+# and will change if the hosts are upgraded.
+def test_emoji_glyph2(color_emoji_run):
+    if (os.getenv("GITHUB_ACTION") == True):
+        assert ((color_emoji_run.fGlyphIndices[1] == 1512)
+                or (color_emoji_run.fGlyphIndices[1] == 248)
+                or (color_emoji_run.fGlyphIndices[1] == 1571))
diff --git a/tests/test_image.py b/tests/test_image.py
index 57ee5f47..91a8d7cc 100644
--- a/tests/test_image.py
+++ b/tests/test_image.py
@@ -161,11 +161,27 @@ def test_Image_isOpaque(image):
     (skia.TileMode.kRepeat, skia.TileMode.kRepeat),
     (skia.TileMode.kRepeat, skia.TileMode.kRepeat, skia.SamplingOptions()),
     (skia.TileMode.kRepeat, skia.TileMode.kRepeat, skia.SamplingOptions(), None),
+    (skia.TileMode.kRepeat, skia.TileMode.kRepeat, skia.SamplingOptions(), skia.Matrix()),
+    (skia.SamplingOptions(), None),
+    (skia.SamplingOptions(), skia.Matrix()),
 ])
 def test_Image_makeShader(image, args):
     assert isinstance(image.makeShader(*args), skia.Shader)
 
 
+@pytest.mark.parametrize('args', [
+    tuple(),
+    (skia.TileMode.kRepeat, skia.TileMode.kRepeat),
+    (skia.TileMode.kRepeat, skia.TileMode.kRepeat, skia.SamplingOptions()),
+    (skia.TileMode.kRepeat, skia.TileMode.kRepeat, skia.SamplingOptions(), None),
+    (skia.TileMode.kRepeat, skia.TileMode.kRepeat, skia.SamplingOptions(), skia.Matrix()),
+    (skia.SamplingOptions(), None),
+    (skia.SamplingOptions(), skia.Matrix()),
+])
+def test_Image_makeRawShader(image, args):
+    assert isinstance(image.makeRawShader(*args), skia.Shader)
+
+
 def test_Image_peekPixels(image):
     assert isinstance(image.peekPixels(skia.Pixmap()), bool)
 
diff --git a/tests/test_paragraph.py b/tests/test_paragraph.py
index fb759af9..5f463358 100644
--- a/tests/test_paragraph.py
+++ b/tests/test_paragraph.py
@@ -1,15 +1,29 @@
 import skia
 import pytest
-import sys
 
-def test_FontCollection_init0():
-    assert isinstance(skia.textlayout.FontCollection(), skia.textlayout_FontCollection)
+@pytest.fixture(scope='session')
+def textlayout_font_collection():
+    return skia.textlayout.FontCollection()
+
+def test_FontCollection_init0(textlayout_font_collection):
+    assert isinstance(textlayout_font_collection, skia.textlayout_FontCollection)
+
+
+@pytest.fixture(scope='session')
+def paragraph_style():
+    return skia.textlayout.ParagraphStyle()
 
-def test_ParagraphStyle_init0():
-    assert isinstance(skia.textlayout.ParagraphStyle(),skia.textlayout_ParagraphStyle)
+def test_ParagraphStyle_init0(paragraph_style):
+    assert isinstance(paragraph_style, skia.textlayout_ParagraphStyle)
+
+
+@pytest.fixture(scope='session')
+def textlayout_text_style():
+    return skia.textlayout.TextStyle()
+
+def test_TextStyle_init0(textlayout_text_style):
+    assert isinstance(textlayout_text_style, skia.textlayout_TextStyle)
 
-def test_TextStyle_init0():
-    assert isinstance(skia.textlayout.TextStyle(), skia.textlayout_TextStyle)
 
 @pytest.fixture(scope='session')
 def paragraph_builder():
@@ -21,8 +35,29 @@ def test_ParagraphBuilder_init0(paragraph_builder):
     assert isinstance(paragraph_builder, skia.textlayout_ParagraphBuilder)
 
 def test_Paragraph_init0(paragraph_builder):
-    if sys.platform.startswith("win"):
-        pytest.skip("Known not to work; To be investigated.")
     paragraph_builder.addText("")
     paragraph = paragraph_builder.Build()
     assert isinstance(paragraph, skia.textlayout_Paragraph)
+
+
+# Adapted from #278, to make sure that "\n" results in a line break  (rather that .notdef).
+# Height is larger than twice width, if a break happens.
+def test_Paragraph_linebreak(paragraph_builder, textlayout_text_style, textlayout_font_collection, paragraph_style):
+    paint = skia.Paint()
+    paint.setColor(skia.ColorBLACK)
+    paint.setAntiAlias(True)
+
+    textlayout_text_style.setFontSize(50)
+    textlayout_text_style.setForegroundPaint(paint)
+
+    textlayout_font_collection.setDefaultFontManager(skia.FontMgr())
+
+    builder = skia.textlayout.ParagraphBuilder.make(
+        paragraph_style, textlayout_font_collection, skia.Unicodes.ICU.Make()
+    )
+    builder.pushStyle(textlayout_text_style)
+
+    builder.addText("o\no")
+    paragraph = builder.Build()
+    paragraph.layout(300)
+    assert (paragraph.Height > 0) and (paragraph.Height > paragraph.LongestLine * 2)
diff --git a/tests/test_patheffect.py b/tests/test_patheffect.py
index 69330489..b98da4ed 100644
--- a/tests/test_patheffect.py
+++ b/tests/test_patheffect.py
@@ -104,14 +104,17 @@ def patheffect_dashinfo():
     return skia.PathEffect.DashInfo()
 
 
+@pytest.mark.xfail(reason='Withdrawn from public API in m132')
 def test_PathEffect_DashInfo_fIntervals(patheffect_dashinfo):
     assert isinstance(patheffect_dashinfo.fIntervals, list)
 
 
+@pytest.mark.xfail(reason='Withdrawn from public API in m132')
 def test_PathEffect_DashInfo_fCount(patheffect_dashinfo):
     assert isinstance(patheffect_dashinfo.fCount, int)
 
 
+@pytest.mark.xfail(reason='Withdrawn from public API in m132')
 def test_PathEffect_DashInfo_fPhase(patheffect_dashinfo):
     assert isinstance(patheffect_dashinfo.fPhase, float)
 
@@ -191,6 +194,7 @@ def test_PathEffect_asPoints(patheffect):
         patheffect.asPoints(results, path, rec, matrix, None), bool)
 
 
+@pytest.mark.xfail(reason='Withdrawn from public API in m132')
 def test_PathEffect_asADash(patheffect):
     dashinfo = skia.PathEffect.DashInfo()
     assert isinstance(patheffect.asADash(dashinfo), skia.PathEffect.DashType)
diff --git a/tests/test_runtime_effect.py b/tests/test_runtime_effect.py
new file mode 100644
index 00000000..8f62ca2f
--- /dev/null
+++ b/tests/test_runtime_effect.py
@@ -0,0 +1,94 @@
+import skia
+import pytest
+
+@pytest.fixture(scope='session')
+def runtime_effect():
+    return skia.RuntimeEffect.MakeForShader("vec4 main(vec2 inCoords){return vec4(0,0,0,0);}")
+
+@pytest.fixture(scope='session')
+def runtime_effect_color():
+    return skia.RuntimeEffect.MakeForColorFilter("vec4 main(vec4 inColor){return vec4(0,0,0,0);}")
+
+@pytest.fixture(scope='session')
+def runtime_effect_blender():
+    return skia.RuntimeEffect.MakeForBlender("vec4 main(vec4 srcColor, vec4 dstColor){return vec4(0,0,0,0);}")
+
+def test_RuntimeEffect_init0(runtime_effect):
+    assert isinstance(runtime_effect, skia.RuntimeEffect)
+
+def test_RuntimeEffect_init1(runtime_effect_color):
+    assert isinstance(runtime_effect_color, skia.RuntimeEffect)
+
+def test_RuntimeEffect_init2(runtime_effect_blender):
+    assert isinstance(runtime_effect_blender, skia.RuntimeEffect)
+
+def test_V3_init0():
+    assert isinstance(skia.V3(0,0,0), skia.V3)
+
+def test_V4_init0():
+    assert isinstance(skia.V4(0,0,0,0), skia.V4)
+
+
+@pytest.fixture(scope='session')
+def runtime_effect_builder(runtime_effect):
+    return skia.RuntimeEffectBuilder(runtime_effect)
+
+def test_RuntimeEffectBuilder_init0(runtime_effect_builder):
+    assert isinstance(runtime_effect_builder, skia.RuntimeEffectBuilder)
+
+# setUniform is polymorphic, so we are just testing that the
+# accepted object types do not throw errors.
+@pytest.mark.parametrize('arg', [
+    (0),
+    (skia.V3(0,0,0)),
+    (skia.V4(0,0,0,0)),
+    ([0,0,0]),
+    ([0,0,0,0]),
+    ((0,0,0)),
+    ((0,0,0,0)),
+])
+def test_RuntimeEffectBuilder_setUniform(runtime_effect_builder, arg):
+    runtime_effect_builder.setUniform("bogus", arg)
+
+
+def test_RuntimeEffect_make_type1(runtime_effect):
+    assert isinstance(runtime_effect.makeShader(None), skia.Shader)
+
+def test_RuntimeEffect_make_type2(runtime_effect_color):
+    assert isinstance(runtime_effect_color.makeColorFilter(None), skia.ColorFilter)
+
+@pytest.mark.xfail(reason="We don't bind SkBlender yet. TODO")
+def test_RuntimeEffect_make_type3(runtime_effect_blender):
+    assert isinstance(runtime_effect_blender.makeBlender(None), skia.Blender)
+
+
+# These test implicit conversion to RuntimeEffectChildPtr
+def test_RuntimeEffectBuilder_setChild1(runtime_effect_builder, runtime_effect):
+    runtime_effect_builder.setChild("bogus", runtime_effect.makeShader(None))
+
+def test_RuntimeEffectBuilder_setChild2(runtime_effect_builder, runtime_effect_color):
+    runtime_effect_builder.setChild("bogus", runtime_effect_color.makeColorFilter(None))
+
+@pytest.mark.xfail(reason="We don't bind SkBlender yet. TODO")
+def test_RuntimeEffectBuilder_setChild3(runtime_effect_builder, runtime_effect_blender):
+    runtime_effect_builder.setChild("bogus", runtime_effect_blender.makeBlender(None))
+
+
+@pytest.mark.xfail(reason="We throw on invalid struct and don't use the whole struct anymore / at the moment.")
+def test_RuntimeEffectResult_init0():
+    assert isinstance(skia.RuntimeEffectResult(), skia.RuntimeEffectResult)
+
+def test_RuntimeEffectChildPtr_init0():
+    assert isinstance(skia.RuntimeEffectChildPtr(), skia.RuntimeEffectChildPtr)
+
+def test_VectorRuntimeEffectChildPtr_init0():
+    assert isinstance(skia.VectorRuntimeEffectChildPtr(), skia.VectorRuntimeEffectChildPtr)
+
+def test_SpanRuntimeEffectChildPtr_init0():
+    assert isinstance(skia.SpanRuntimeEffectChildPtr(), skia.SpanRuntimeEffectChildPtr)
+
+def test_RuntimeEffectBuilderUniform_init0():
+    assert isinstance(skia.RuntimeEffectBuilderUniform(), skia.RuntimeEffectBuilderUniform)
+
+def test_RuntimeEffectBuilderChild_init0():
+    assert isinstance(skia.RuntimeEffectBuilderChild(), skia.RuntimeEffectBuilderChild)
diff --git a/tests/test_unicode.py b/tests/test_unicode.py
index 9a1684e4..c7b8a364 100644
--- a/tests/test_unicode.py
+++ b/tests/test_unicode.py
@@ -1,20 +1,13 @@
 import skia
 import pytest
-import sys
 
 def test_Unicodes_init0():
-    if sys.platform.startswith("win"):
-        pytest.skip("Known not to work; To be investigated.")
     assert isinstance(skia.Unicode(), skia.Unicode)
 
 # recommended alias (upstream):
 def test_Unicodes_init1():
-    if sys.platform.startswith("win"):
-        pytest.skip("Known not to work; To be investigated.")
     assert isinstance(skia.Unicodes.ICU.Make(), skia.Unicode)
 
 # Canonical
 def test_Unicodes_init2():
-    if sys.platform.startswith("win"):
-        pytest.skip("Known not to work; To be investigated.")
     assert isinstance(skia.Unicode.ICU_Make(), skia.Unicode)