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 + #endif ++#include "src/ports/SkFontHost_FreeType_common.h" + #include + #include + #include +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 + #endif ++#include "src/ports/SkFontHost_FreeType_common.h" + #include + #include + #include +@@ -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 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 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` as `VectorRuntimeEffectChildPtr`, + `SkSpan` 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 @@ -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", }; }), 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", R"docstring( A row-major 3x3 matrix (ie vals[row][col]) @@ -52,6 +62,15 @@ py::class_(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_>(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_(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 -#include -#include +#include +#include +#include #include -#include -#include +#include +#include #include #include -#include +#include #include #include #include @@ -898,7 +898,7 @@ py::class_, GrRecordingContext>(m, "GrDi )docstring", py::arg("info")) .def("flush", py::overload_cast<>(&GrDirectContext::flush)) - .def("submit", &GrDirectContext::submit, + .def("submit", py::overload_cast(&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 +#include +#include 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 +#include +#include #include 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 +#include 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 -#include +#include #include #include #include @@ -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( + &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( + &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( + &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( + &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( + &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( + &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( + &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_, SkFlattenable> ~skia.TrimPathEffect )docstring"); +/* SkPathEffect::DashInfo withdrawn from public API in m132 */ +/* py::class_(patheffect, "DashInfo") .def(py::init<>()) .def_property_readonly("fIntervals", @@ -160,6 +162,7 @@ py::class_(patheffect, "DashInfo") Offset into the dashed interval pattern. )docstring") ; +*/ /* py::class_ pointdata(patheffect, "PointData", @@ -195,6 +198,8 @@ pointdata ; */ +/* SkPathEffect::DashType withdrawn from public API in m132 */ +/* py::enum_(patheffect, "DashType", R"docstring( If the :py:class:`PathEffect` can be represented as a dash pattern, asADash @@ -215,6 +220,7 @@ py::enum_(patheffect, "DashType", .value("kDash_DashType", SkPathEffect::DashType::kDash_DashType, "fills in all of the info parameter") .export_values(); +*/ patheffect .def("filterPath", py::overload_cast(&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 +#include "common.h" +#include +//#include // defines SkV3, SkV4 ; M44 used in Matrix/Canvas ; Revisit. +#include + +PYBIND11_MAKE_OPAQUE(std::vector) + +void initRuntimeEffect(py::module &m) { +py::class_, SkRefCnt> runtime_effect(m, "RuntimeEffect"); + +py::class_ runtime_effect_childptr(m, "RuntimeEffectChildPtr"); + +py::bind_vector>(m, "VectorRuntimeEffectChildPtr"); +py::class_> span_runtime_effect_childptr(m, "SpanRuntimeEffectChildPtr"); + +py::class_ runtime_effect_builder(m, "RuntimeEffectBuilder"); + +py::class_(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(), v3[1].cast(), v3[2].cast()}; + }), + py::arg("v3")) + ; + +py::implicitly_convertible(); + +py::class_(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(), v4[1].cast(), v4[2].cast(), v4[3].cast()}; + }), + py::arg("v4")) + ; + +py::implicitly_convertible(); + +py::class_(m, "RuntimeEffectResult") + .def_readwrite("effect", &SkRuntimeEffect::Result::effect) + .def_readwrite("errorText", &SkRuntimeEffect::Result::errorText) + ; + +runtime_effect_childptr + .def(py::init<>()) + .def(py::init>()) + .def(py::init>()) + .def(py::init>()) + ; + +span_runtime_effect_childptr + .def(py::init<>()) + .def(py::init()) + .def(py::init&>()) + .def(py::init( + [] (std::vector& v) { + return SkSpan(&v[0], v.size()); + })) + .def(py::init&>()) + ; + +py::implicitly_convertible, SkRuntimeEffect::ChildPtr>(); +py::implicitly_convertible, SkRuntimeEffect::ChildPtr>(); +py::implicitly_convertible, SkRuntimeEffect::ChildPtr>(); +py::implicitly_convertible, SkSpan>(); + +/* + 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 uniforms) { + return runtime_effect.makeShader(uniforms, {}); + }, + py::arg("uniforms")) + .def("makeShader", + py::overload_cast, sk_sp[], 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, SkSpan, const SkMatrix*>(&SkRuntimeEffect::makeShader, py::const_), + py::arg("uniforms"), py::arg("children"), + py::arg("localMatrix") = nullptr) + .def("makeColorFilter", + py::overload_cast>(&SkRuntimeEffect::makeColorFilter, py::const_), + py::arg("uniforms")) + .def("makeColorFilter", + py::overload_cast, sk_sp[], size_t>(&SkRuntimeEffect::makeColorFilter, py::const_), + py::arg("uniforms"), py::arg("children"), + py::arg("childCount")) + .def("makeColorFilter", + py::overload_cast, SkSpan>(&SkRuntimeEffect::makeColorFilter, py::const_), + py::arg("uniforms"), py::arg("children")) + .def("makeBlender", + [] (SkRuntimeEffect& runtime_effect, sk_sp uniforms) { + return runtime_effect.makeBlender(uniforms, {}); + }, + py::arg("uniforms")) + .def("makeBlender", + py::overload_cast, SkSpan>(&SkRuntimeEffect::makeBlender, py::const_), + py::arg("uniforms"), py::arg("children") = SkSpan{}) + ; + +py::class_(m, "RuntimeEffectBuilderUniform") + .def(py::init<>()) + ; + +py::class_(m, "RuntimeEffectBuilderChild") + .def(py::init<>()) + ; + +runtime_effect_builder + .def(py::init>()) + .def(py::init, sk_sp>()) + .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(), vN[1].cast(), vN[2].cast()}; + if (vN.size() == 4) + v = SkV4{vN[0].cast(), vN[1].cast(), vN[2].cast(), vN[3].cast()}; + }, + py::arg("name"), py::arg("uniform")) + .def("setChild", + [] (SkRuntimeEffectBuilder& builder, std::string_view name, sk_sp child) { + auto v = builder.child(name); + v = child; + }, + py::arg("name"), py::arg("child")) + .def("setChild", + [] (SkRuntimeEffectBuilder& builder, std::string_view name, sk_sp child) { + auto v = builder.child(name); + v = child; + }, + py::arg("name"), py::arg("child")) + .def("setChild", + [] (SkRuntimeEffectBuilder& builder, std::string_view name, sk_sp 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 #include #include 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 #include -#include +#include #include #include #include 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 #include -#include +#include #include #include #include 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); 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)