diff --git a/.github/workflows/nuget-static-graphics.yml b/.github/workflows/nuget-static-graphics.yml
new file mode 100644
index 0000000..70c5074
--- /dev/null
+++ b/.github/workflows/nuget-static-graphics.yml
@@ -0,0 +1,197 @@
+name: nuget-static-graphics
+
+on:
+ workflow_dispatch:
+ inputs:
+ publish_to_nuget:
+ description: Publish package to nuget.org
+ required: true
+ default: false
+ type: boolean
+ push:
+ tags:
+ - "static-graphics-v*"
+
+env:
+ SKIASHARP_VERSION: "3.119.2"
+ ANGLE_BRANCH: "7151"
+
+jobs:
+ build-linux:
+ strategy:
+ fail-fast: false
+ matrix:
+ include:
+ - rid: linux-x64
+ target_cpu: x64
+ runner: ubuntu-latest
+ - rid: linux-arm64
+ target_cpu: arm64
+ runner: ubuntu-24.04-arm
+ runs-on: ${{ matrix.runner }}
+ steps:
+ - uses: actions/checkout@v4
+
+ - name: Install native build dependencies
+ run: |
+ sudo apt-get update
+ sudo apt-get install -y clang lld llvm ninja-build pkg-config libfontconfig1-dev
+
+ - name: Build Linux static graphics libraries
+ env:
+ RID: ${{ matrix.rid }}
+ TARGET_CPU: ${{ matrix.target_cpu }}
+ LLVM_AR: llvm-ar
+ run: scripts/build-linux-static-graphics.sh all
+
+ - name: Upload Linux static archives
+ uses: actions/upload-artifact@v4
+ with:
+ name: native-static-${{ matrix.rid }}
+ path: External/NativeStatic/${{ matrix.rid }}/*
+ if-no-files-found: error
+
+ build-windows:
+ strategy:
+ fail-fast: false
+ matrix:
+ include:
+ - rid: win-x64
+ target_cpu: x64
+ msvc_arch: x64
+ - rid: win-arm64
+ target_cpu: arm64
+ msvc_arch: amd64_arm64
+ runs-on: windows-latest
+ steps:
+ - uses: actions/checkout@v4
+
+ - name: Setup MSVC environment
+ uses: ilammy/msvc-dev-cmd@v1
+ with:
+ arch: ${{ matrix.msvc_arch }}
+
+ - name: Install native build dependencies
+ shell: pwsh
+ run: choco install ninja llvm -y
+
+ - name: Build Windows static graphics libraries
+ shell: pwsh
+ env:
+ RID: ${{ matrix.rid }}
+ TARGET_CPU: ${{ matrix.target_cpu }}
+ run: .\scripts\build-windows-static-graphics.ps1 all
+
+ - name: Upload Windows static archives
+ uses: actions/upload-artifact@v4
+ with:
+ name: native-static-${{ matrix.rid }}
+ path: External/NativeStatic/${{ matrix.rid }}/*
+ if-no-files-found: error
+
+ build-macos:
+ strategy:
+ fail-fast: false
+ matrix:
+ include:
+ - rid: osx-x64
+ target_cpu: x64
+ - rid: osx-arm64
+ target_cpu: arm64
+ runs-on: macos-latest
+ steps:
+ - uses: actions/checkout@v4
+
+ - name: Install native build dependencies
+ run: brew install ninja
+
+ - name: Build macOS static graphics libraries
+ env:
+ RID: ${{ matrix.rid }}
+ TARGET_CPU: ${{ matrix.target_cpu }}
+ run: scripts/build-macos-static-graphics.sh all
+
+ - name: Upload macOS static archives
+ uses: actions/upload-artifact@v4
+ with:
+ name: native-static-${{ matrix.rid }}
+ path: External/NativeStatic/${{ matrix.rid }}/*
+ if-no-files-found: error
+
+ pack:
+ runs-on: ubuntu-latest
+ needs:
+ - build-linux
+ - build-windows
+ - build-macos
+ permissions:
+ contents: read
+ steps:
+ - uses: actions/checkout@v4
+
+ - name: Setup .NET
+ uses: actions/setup-dotnet@v4
+ with:
+ dotnet-version: "10.0.x"
+
+ - name: Download static archives
+ uses: actions/download-artifact@v4
+ with:
+ pattern: native-static-*
+ path: External/NativeStatic
+ merge-multiple: false
+
+ - name: Move static archives into RID directories
+ run: |
+ for rid in win-x64 win-arm64 linux-x64 linux-arm64 osx-x64 osx-arm64; do
+ mkdir -p "External/NativeStatic/$rid"
+ mv "External/NativeStatic/native-static-$rid"/* "External/NativeStatic/$rid/"
+ rmdir "External/NativeStatic/native-static-$rid"
+ done
+
+ - name: Verify static archives
+ run: |
+ for rid in linux-x64 linux-arm64 osx-x64 osx-arm64; do
+ test -f External/NativeStatic/$rid/libskia.a
+ test -f External/NativeStatic/$rid/libSkiaSharp.a
+ test -f External/NativeStatic/$rid/libHarfBuzzSharp.a
+ test -f External/NativeStatic/$rid/libANGLE_static.a
+ test -f External/NativeStatic/$rid/libGLESv2_static.a
+ done
+
+ for rid in win-x64 win-arm64; do
+ test -f External/NativeStatic/$rid/skia.lib
+ test -f External/NativeStatic/$rid/SkiaSharp.lib
+ test -f External/NativeStatic/$rid/libHarfBuzzSharp.lib
+ test -f External/NativeStatic/$rid/libANGLE_static.lib
+ test -f External/NativeStatic/$rid/libGLESv2_static.lib
+ done
+
+ - name: Pack NuGet package
+ run: dotnet pack NuGet/LinuxStaticGraphics/Greepar.Avalonia.StaticGraphics.csproj -c Release -o artifacts/nuget
+
+ - name: Upload NuGet artifact
+ uses: actions/upload-artifact@v4
+ with:
+ name: nuget-static-graphics
+ path: |
+ artifacts/nuget/*.nupkg
+ artifacts/nuget/*.snupkg
+ if-no-files-found: error
+
+ - name: Publish to nuget.org
+ if: ${{ startsWith(github.ref, 'refs/tags/static-graphics-v') || (github.event_name == 'workflow_dispatch' && inputs.publish_to_nuget == true) }}
+ env:
+ NUGET_API_KEY: ${{ secrets.NUGET_API_KEY }}
+ run: |
+ if [ -z "$NUGET_API_KEY" ]; then
+ echo "NUGET_API_KEY secret is not configured. Skipping publish."
+ exit 0
+ fi
+
+ for package in artifacts/nuget/*.nupkg; do
+ dotnet nuget push "$package" \
+ --source "https://api.nuget.org/v3/index.json" \
+ --api-key "$NUGET_API_KEY" \
+ --skip-duplicate
+ done
diff --git a/.gitignore b/.gitignore
new file mode 100644
index 0000000..b6f0af6
--- /dev/null
+++ b/.gitignore
@@ -0,0 +1,6 @@
+bin/
+obj/
+artifacts/
+*.nupkg
+*.snupkg
+External/NativeStatic/.work/
diff --git a/External/NativeStatic/linux-x64/libANGLE_static.a b/External/NativeStatic/linux-x64/libANGLE_static.a
new file mode 100644
index 0000000..11ab2b5
Binary files /dev/null and b/External/NativeStatic/linux-x64/libANGLE_static.a differ
diff --git a/External/NativeStatic/linux-x64/libGLESv2_static.a b/External/NativeStatic/linux-x64/libGLESv2_static.a
new file mode 100644
index 0000000..5ac5e06
Binary files /dev/null and b/External/NativeStatic/linux-x64/libGLESv2_static.a differ
diff --git a/External/NativeStatic/linux-x64/libHarfBuzzSharp.a b/External/NativeStatic/linux-x64/libHarfBuzzSharp.a
new file mode 100644
index 0000000..6289599
Binary files /dev/null and b/External/NativeStatic/linux-x64/libHarfBuzzSharp.a differ
diff --git a/External/NativeStatic/linux-x64/libSkiaSharp.a b/External/NativeStatic/linux-x64/libSkiaSharp.a
new file mode 100644
index 0000000..e319def
Binary files /dev/null and b/External/NativeStatic/linux-x64/libSkiaSharp.a differ
diff --git a/External/NativeStatic/linux-x64/libskia.a b/External/NativeStatic/linux-x64/libskia.a
new file mode 100644
index 0000000..57d1cba
Binary files /dev/null and b/External/NativeStatic/linux-x64/libskia.a differ
diff --git a/External/NativeStatic/patches/angle-chromium-7151.patch b/External/NativeStatic/patches/angle-chromium-7151.patch
new file mode 100644
index 0000000..e7f8167
--- /dev/null
+++ b/External/NativeStatic/patches/angle-chromium-7151.patch
@@ -0,0 +1,87 @@
+diff --git a/BUILD.gn b/BUILD.gn
+index 07e1268a57..25f7cba544 100644
+--- a/BUILD.gn
++++ b/BUILD.gn
+@@ -1472,7 +1472,18 @@ if (angle_has_frame_capture) {
+ }
+ }
+
++angle_static_library("libANGLE_static") {
++ complete_static_lib = true
++ public_deps = [ ":libANGLE" ]
++}
++
++angle_static_library("libANGLE_with_capture_static") {
++ complete_static_lib = true
++ public_deps = [ ":libANGLE_with_capture" ]
++}
++
+ angle_static_library("libGLESv2_static") {
++ complete_static_lib = true
+ sources = libglesv2_sources
+ if (angle_enable_cl) {
+ sources += libglesv2_cl_sources
+diff --git a/DEPS b/DEPS
+index 9085ba513e..565840a832 100644
+--- a/DEPS
++++ b/DEPS
+@@ -696,11 +696,6 @@ deps = {
+ 'dep_type': 'cipd',
+ },
+
+- 'third_party/catapult': {
+- 'url': Var('chromium_git') + '/catapult.git' + '@' + Var('catapult_revision'),
+- 'condition': 'not build_with_chromium',
+- },
+-
+ # Cherry is a dEQP/VK-GL-CTS management GUI written in Go. We use it for viewing test results.
+ 'third_party/cherry': {
+ 'url': Var('android_git') + '/platform/external/cherry@4f8fb08d33ca5ff05a1c638f04c85bbb8d8b52cc',
+@@ -722,12 +717,6 @@ deps = {
+ 'condition': 'checkout_android and not build_with_chromium',
+ },
+
+-
+- 'third_party/dawn': {
+- 'url': Var('dawn_git') + '/dawn.git' + '@' + Var('dawn_revision'),
+- 'condition': 'not build_with_chromium'
+- },
+-
+ 'third_party/depot_tools': {
+ 'url': Var('chromium_git') + '/chromium/tools/depot_tools.git@1fcc527019d786502b02f71b8b764ee674a40953',
+ 'condition': 'not build_with_chromium',
+@@ -809,11 +798,6 @@ deps = {
+ 'condition': 'not build_with_chromium',
+ },
+
+- 'third_party/llvm/src': {
+- 'url': Var('chromium_git') + '/external/github.com/llvm/llvm-project@1fd8d3fea53e6e4573cdce55bd38ef0a7813a442',
+- 'condition': 'not build_with_chromium',
+- },
+-
+ 'third_party/jdk': {
+ 'packages': [
+ {
+@@ -1000,11 +984,6 @@ deps = {
+ 'condition': 'checkout_android and not build_with_chromium',
+ },
+
+- 'third_party/SwiftShader': {
+- 'url': Var('swiftshader_git') + '/SwiftShader@7905fa19e456df5aa8e2233a7ec5832c9c6c287b',
+- 'condition': 'not build_with_chromium',
+- },
+-
+ 'third_party/turbine/cipd': {
+ 'packages': [
+ {
+@@ -1016,10 +995,6 @@ deps = {
+ 'dep_type': 'cipd',
+ },
+
+- 'third_party/VK-GL-CTS/src': {
+- 'url': Var('chromium_git') + '/external/github.com/KhronosGroup/VK-GL-CTS' + '@' + Var('vk_gl_cts_revision'),
+- },
+-
+ 'third_party/vulkan-deps': {
+ 'url': Var('chromium_git') + '/vulkan-deps@b9aab6c35f37e6532011ddab976c3ca71819c074',
+ 'condition': 'not build_with_chromium',
diff --git a/NuGet/LinuxStaticGraphics/Greepar.Avalonia.StaticGraphics.csproj b/NuGet/LinuxStaticGraphics/Greepar.Avalonia.StaticGraphics.csproj
new file mode 100644
index 0000000..703aeec
--- /dev/null
+++ b/NuGet/LinuxStaticGraphics/Greepar.Avalonia.StaticGraphics.csproj
@@ -0,0 +1,32 @@
+
+
+ net10.0
+ true
+ true
+ false
+ true
+ Greepar.Avalonia.StaticGraphics
+ 3.119.2-7151.4
+ greepar
+ greepar
+ Static SkiaSharp, HarfBuzzSharp, and ANGLE libraries for Avalonia NativeAOT single-file publishing.
+ Avalonia;SkiaSharp;ANGLE;NativeAOT;Windows;Linux;macOS;Static
+ MIT
+ README.md
+ git
+ https://github.com/greepar/Avalonia.StaticGraphics
+ https://github.com/greepar/Avalonia.StaticGraphics
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/NuGet/LinuxStaticGraphics/README.md b/NuGet/LinuxStaticGraphics/README.md
new file mode 100644
index 0000000..43dc51e
--- /dev/null
+++ b/NuGet/LinuxStaticGraphics/README.md
@@ -0,0 +1,163 @@
+# Greepar.Avalonia.StaticGraphics
+
+Static graphics libraries for Avalonia NativeAOT single-file publishing.
+
+This package provides static archives for SkiaSharp, HarfBuzzSharp, and ANGLE, then wires them into MSBuild through `buildTransitive` targets. It is intended for Avalonia applications that want NativeAOT single-file publish outputs without shipping Avalonia graphics native libraries next to the executable.
+
+Repository:
+
+## Supported RIDs
+
+- `win-x64`
+- `win-arm64`
+- `linux-x64`
+- `linux-arm64`
+- `osx-x64`
+- `osx-arm64`
+
+## Included Libraries
+
+Windows:
+
+- `skia.lib`
+- `SkiaSharp.lib`
+- `libHarfBuzzSharp.lib`
+- `libANGLE_static.lib`
+- `libGLESv2_static.lib`
+
+Linux and macOS:
+
+- `libskia.a`
+- `libSkiaSharp.a`
+- `libHarfBuzzSharp.a`
+- `libANGLE_static.a`
+- `libGLESv2_static.a`
+
+## Install
+
+```bash
+dotnet add package Greepar.Avalonia.StaticGraphics
+```
+
+## Publish
+
+Windows x64 or arm64:
+
+```bash
+dotnet publish -c Release -r win-x64 \
+ -p:PublishAot=true \
+ -p:SelfContained=true \
+ -p:PublishSingleFile=true \
+ -p:StripSymbols=true
+```
+
+Linux x64 or arm64:
+
+```bash
+dotnet publish -c Release -r linux-x64 \
+ -p:PublishAot=true \
+ -p:SelfContained=true \
+ -p:PublishSingleFile=true \
+ -p:StripSymbols=true \
+ -p:LinkerFlavor=lld
+```
+
+macOS x64 or arm64:
+
+```bash
+dotnet publish -c Release -r osx-arm64 \
+ -p:PublishAot=true \
+ -p:SelfContained=true \
+ -p:PublishSingleFile=true \
+ -p:StripSymbols=true
+```
+
+The package automatically contributes the static archives as `NativeLibrary` items when `PublishAot=true` and the `RuntimeIdentifier` is supported.
+
+## Verify Output
+
+After publishing, the publish directory should not contain Avalonia graphics runtime files such as:
+
+- `libSkiaSharp.so`
+- `libHarfBuzzSharp.so`
+- `libSkiaSharp.dylib`
+- `libHarfBuzzSharp.dylib`
+- `libSkiaSharp.dll`
+- `libHarfBuzzSharp.dll`
+- `av_libglesv2.dll`
+
+The package stores static archives under `static//native/` inside the NuGet package, so `.a` and `.lib` files are used for linking and are not normal runtime assets.
+
+## Build From Source
+
+This repository does not depend on 2ndLAB static packages. The GitHub Actions workflow builds the native libraries from upstream source repositories:
+
+- SkiaSharp:
+- ANGLE:
+
+Build scripts:
+
+- `scripts/build-linux-static-graphics.sh`
+- `scripts/build-windows-static-graphics.ps1`
+- `scripts/build-macos-static-graphics.sh`
+
+Build locally after the static archives exist:
+
+```bash
+dotnet pack NuGet/LinuxStaticGraphics/Greepar.Avalonia.StaticGraphics.csproj -c Release -o artifacts/nuget
+```
+
+The package will be written to:
+
+```text
+artifacts/nuget/Greepar.Avalonia.StaticGraphics.*.nupkg
+```
+
+## GitHub Actions
+
+The workflow `.github/workflows/nuget-static-graphics.yml` builds all native archives and packs a single NuGet package for all supported RIDs.
+
+It will:
+
+- build Linux x64 static archives on `ubuntu-latest`
+- build Linux arm64 static archives on `ubuntu-24.04-arm`
+- build Windows x64 and arm64 static archives on `windows-latest`
+- build macOS x64 and arm64 static archives on `macos-latest`
+- download all native artifacts into `External/NativeStatic//`
+- run `dotnet pack`
+- upload the generated `.nupkg` as a workflow artifact
+
+To publish automatically to nuget.org, add a repository secret named `NUGET_API_KEY`, then either:
+
+- run the workflow manually with `publish_to_nuget=true`
+- push a tag matching `static-graphics-v*`
+
+Example tag:
+
+```bash
+git tag static-graphics-v3.119.2-7151.4
+git push origin static-graphics-v3.119.2-7151.4
+```
+
+## Known Runtime Dependencies
+
+Single-file does not always mean dependency-free. Depending on platform and publish settings, the final executable may still depend on operating system libraries and frameworks.
+
+Linux examples:
+
+- `libc.so.6`
+- `libm.so.6`
+- `libstdc++.so.6`
+- `libgcc_s.so.1`
+- `libfontconfig.so.1`
+- `libfreetype.so.6`
+
+Linux systems also need usable fonts for text rendering and font fallback.
+
+## Scope
+
+This package only covers Avalonia graphics-related native libraries. If your application uses other native dependencies, such as SQLite, audio backends, OpenSSL, or custom native libraries, those dependencies may require separate handling.
+
+## License
+
+The NuGet package metadata uses the MIT license. The bundled third-party native libraries are built from their upstream projects and remain subject to their respective upstream licenses.
diff --git a/NuGet/LinuxStaticGraphics/buildTransitive/Greepar.Avalonia.StaticGraphics.props b/NuGet/LinuxStaticGraphics/buildTransitive/Greepar.Avalonia.StaticGraphics.props
new file mode 100644
index 0000000..77443c0
--- /dev/null
+++ b/NuGet/LinuxStaticGraphics/buildTransitive/Greepar.Avalonia.StaticGraphics.props
@@ -0,0 +1,5 @@
+
+
+ $(MSBuildThisFileDirectory)../static/
+
+
diff --git a/NuGet/LinuxStaticGraphics/buildTransitive/Greepar.Avalonia.StaticGraphics.targets b/NuGet/LinuxStaticGraphics/buildTransitive/Greepar.Avalonia.StaticGraphics.targets
new file mode 100644
index 0000000..70d95b9
--- /dev/null
+++ b/NuGet/LinuxStaticGraphics/buildTransitive/Greepar.Avalonia.StaticGraphics.targets
@@ -0,0 +1,92 @@
+
+
+ <_GreeparAvaloniaStaticGraphicsRid Condition="'$(RuntimeIdentifier)' == 'win-x64'">win-x64
+ <_GreeparAvaloniaStaticGraphicsRid Condition="'$(RuntimeIdentifier)' == 'win-arm64'">win-arm64
+ <_GreeparAvaloniaStaticGraphicsRid Condition="'$(RuntimeIdentifier)' == 'linux-x64'">linux-x64
+ <_GreeparAvaloniaStaticGraphicsRid Condition="'$(RuntimeIdentifier)' == 'linux-arm64'">linux-arm64
+ <_GreeparAvaloniaStaticGraphicsRid Condition="'$(RuntimeIdentifier)' == 'osx-x64'">osx-x64
+ <_GreeparAvaloniaStaticGraphicsRid Condition="'$(RuntimeIdentifier)' == 'osx-arm64'">osx-arm64
+ <_GreeparAvaloniaStaticGraphicsNativeRoot Condition="'$(_GreeparAvaloniaStaticGraphicsRid)' != ''">$(GreeparAvaloniaStaticGraphicsRoot)$(_GreeparAvaloniaStaticGraphicsRid)/native
+ lld
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ false
+
+
+
+
+
+
+
+
+
+ <_GreeparAvaloniaStaticGraphicsFilesToDelete Include="$(PublishDir)av_libglesv2.dll" />
+ <_GreeparAvaloniaStaticGraphicsFilesToDelete Include="$(PublishDir)libHarfBuzzSharp.dll" />
+ <_GreeparAvaloniaStaticGraphicsFilesToDelete Include="$(PublishDir)libSkiaSharp.dll" />
+ <_GreeparAvaloniaStaticGraphicsFilesToDelete Include="$(PublishDir)libHarfBuzzSharp.so" />
+ <_GreeparAvaloniaStaticGraphicsFilesToDelete Include="$(PublishDir)libSkiaSharp.so" />
+ <_GreeparAvaloniaStaticGraphicsFilesToDelete Include="$(PublishDir)libHarfBuzzSharp.dylib" />
+ <_GreeparAvaloniaStaticGraphicsFilesToDelete Include="$(PublishDir)libSkiaSharp.dylib" />
+
+
+
+
diff --git a/README.md b/README.md
new file mode 100644
index 0000000..43dc51e
--- /dev/null
+++ b/README.md
@@ -0,0 +1,163 @@
+# Greepar.Avalonia.StaticGraphics
+
+Static graphics libraries for Avalonia NativeAOT single-file publishing.
+
+This package provides static archives for SkiaSharp, HarfBuzzSharp, and ANGLE, then wires them into MSBuild through `buildTransitive` targets. It is intended for Avalonia applications that want NativeAOT single-file publish outputs without shipping Avalonia graphics native libraries next to the executable.
+
+Repository:
+
+## Supported RIDs
+
+- `win-x64`
+- `win-arm64`
+- `linux-x64`
+- `linux-arm64`
+- `osx-x64`
+- `osx-arm64`
+
+## Included Libraries
+
+Windows:
+
+- `skia.lib`
+- `SkiaSharp.lib`
+- `libHarfBuzzSharp.lib`
+- `libANGLE_static.lib`
+- `libGLESv2_static.lib`
+
+Linux and macOS:
+
+- `libskia.a`
+- `libSkiaSharp.a`
+- `libHarfBuzzSharp.a`
+- `libANGLE_static.a`
+- `libGLESv2_static.a`
+
+## Install
+
+```bash
+dotnet add package Greepar.Avalonia.StaticGraphics
+```
+
+## Publish
+
+Windows x64 or arm64:
+
+```bash
+dotnet publish -c Release -r win-x64 \
+ -p:PublishAot=true \
+ -p:SelfContained=true \
+ -p:PublishSingleFile=true \
+ -p:StripSymbols=true
+```
+
+Linux x64 or arm64:
+
+```bash
+dotnet publish -c Release -r linux-x64 \
+ -p:PublishAot=true \
+ -p:SelfContained=true \
+ -p:PublishSingleFile=true \
+ -p:StripSymbols=true \
+ -p:LinkerFlavor=lld
+```
+
+macOS x64 or arm64:
+
+```bash
+dotnet publish -c Release -r osx-arm64 \
+ -p:PublishAot=true \
+ -p:SelfContained=true \
+ -p:PublishSingleFile=true \
+ -p:StripSymbols=true
+```
+
+The package automatically contributes the static archives as `NativeLibrary` items when `PublishAot=true` and the `RuntimeIdentifier` is supported.
+
+## Verify Output
+
+After publishing, the publish directory should not contain Avalonia graphics runtime files such as:
+
+- `libSkiaSharp.so`
+- `libHarfBuzzSharp.so`
+- `libSkiaSharp.dylib`
+- `libHarfBuzzSharp.dylib`
+- `libSkiaSharp.dll`
+- `libHarfBuzzSharp.dll`
+- `av_libglesv2.dll`
+
+The package stores static archives under `static//native/` inside the NuGet package, so `.a` and `.lib` files are used for linking and are not normal runtime assets.
+
+## Build From Source
+
+This repository does not depend on 2ndLAB static packages. The GitHub Actions workflow builds the native libraries from upstream source repositories:
+
+- SkiaSharp:
+- ANGLE:
+
+Build scripts:
+
+- `scripts/build-linux-static-graphics.sh`
+- `scripts/build-windows-static-graphics.ps1`
+- `scripts/build-macos-static-graphics.sh`
+
+Build locally after the static archives exist:
+
+```bash
+dotnet pack NuGet/LinuxStaticGraphics/Greepar.Avalonia.StaticGraphics.csproj -c Release -o artifacts/nuget
+```
+
+The package will be written to:
+
+```text
+artifacts/nuget/Greepar.Avalonia.StaticGraphics.*.nupkg
+```
+
+## GitHub Actions
+
+The workflow `.github/workflows/nuget-static-graphics.yml` builds all native archives and packs a single NuGet package for all supported RIDs.
+
+It will:
+
+- build Linux x64 static archives on `ubuntu-latest`
+- build Linux arm64 static archives on `ubuntu-24.04-arm`
+- build Windows x64 and arm64 static archives on `windows-latest`
+- build macOS x64 and arm64 static archives on `macos-latest`
+- download all native artifacts into `External/NativeStatic//`
+- run `dotnet pack`
+- upload the generated `.nupkg` as a workflow artifact
+
+To publish automatically to nuget.org, add a repository secret named `NUGET_API_KEY`, then either:
+
+- run the workflow manually with `publish_to_nuget=true`
+- push a tag matching `static-graphics-v*`
+
+Example tag:
+
+```bash
+git tag static-graphics-v3.119.2-7151.4
+git push origin static-graphics-v3.119.2-7151.4
+```
+
+## Known Runtime Dependencies
+
+Single-file does not always mean dependency-free. Depending on platform and publish settings, the final executable may still depend on operating system libraries and frameworks.
+
+Linux examples:
+
+- `libc.so.6`
+- `libm.so.6`
+- `libstdc++.so.6`
+- `libgcc_s.so.1`
+- `libfontconfig.so.1`
+- `libfreetype.so.6`
+
+Linux systems also need usable fonts for text rendering and font fallback.
+
+## Scope
+
+This package only covers Avalonia graphics-related native libraries. If your application uses other native dependencies, such as SQLite, audio backends, OpenSSL, or custom native libraries, those dependencies may require separate handling.
+
+## License
+
+The NuGet package metadata uses the MIT license. The bundled third-party native libraries are built from their upstream projects and remain subject to their respective upstream licenses.
diff --git a/scripts/build-linux-static-graphics.sh b/scripts/build-linux-static-graphics.sh
new file mode 100755
index 0000000..7ba1876
--- /dev/null
+++ b/scripts/build-linux-static-graphics.sh
@@ -0,0 +1,273 @@
+#!/usr/bin/env bash
+set -euo pipefail
+
+ROOT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)"
+WORK_DIR="${WORK_DIR:-$ROOT_DIR/External/NativeStatic/.work}"
+TARGET_CPU="${TARGET_CPU:-x64}"
+RID="${RID:-linux-$TARGET_CPU}"
+OUTPUT_DIR="${OUTPUT_DIR:-$ROOT_DIR/External/NativeStatic/$RID}"
+SKIASHARP_VERSION="${SKIASHARP_VERSION:-3.119.2}"
+ANGLE_BRANCH="${ANGLE_BRANCH:-7151}"
+BUILD_JOBS="${BUILD_JOBS:-$(nproc)}"
+ANGLE_PATCH_DIR="${ANGLE_PATCH_DIR:-$ROOT_DIR/External/NativeStatic/patches}"
+LLVM_AR="${LLVM_AR:-llvm-ar-19}"
+
+usage() {
+ cat <<'USAGE'
+Usage: scripts/build-linux-static-graphics.sh [skia|angle|all]
+
+Environment:
+ WORK_DIR Source/build cache directory. Default: External/NativeStatic/.work
+ OUTPUT_DIR Final static library directory. Default: External/NativeStatic/linux-$TARGET_CPU
+ SKIASHARP_VERSION SkiaSharp release branch version. Default: 3.119.2
+ ANGLE_BRANCH ANGLE chromium branch. Default: 7151
+ TARGET_CPU GN target_cpu. Default: x64. Supported: x64, arm64
+ RID Output RID. Default: linux-$TARGET_CPU
+ BUILD_JOBS Ninja parallelism. Default: nproc
+ ANGLE_PATCH_DIR ANGLE patch directory. Default: External/NativeStatic/patches
+ LLVM_AR llvm-ar command used to expand thin archives. Default: llvm-ar-19
+USAGE
+}
+
+require_cmd() {
+ if ! command -v "$1" >/dev/null 2>&1; then
+ echo "Missing required command: $1" >&2
+ exit 1
+ fi
+}
+
+ensure_tools() {
+ require_cmd git
+ require_cmd python3
+ require_cmd clang
+ require_cmd clang++
+ require_cmd ar
+ require_cmd "$LLVM_AR"
+ require_cmd ninja
+ require_cmd pkg-config
+}
+
+ensure_depot_tools() {
+ local depot_dir="$WORK_DIR/depot_tools"
+ if [[ ! -d "$depot_dir/.git" ]]; then
+ git clone --depth 1 https://chromium.googlesource.com/chromium/tools/depot_tools.git "$depot_dir"
+ else
+ git -C "$depot_dir" pull --ff-only
+ fi
+ export PATH="$depot_dir:$PATH"
+}
+
+sync_skiasharp() {
+ local src="$WORK_DIR/SkiaSharp-$SKIASHARP_VERSION"
+ if [[ ! -d "$src/.git" ]]; then
+ git clone --depth 1 --branch "release/$SKIASHARP_VERSION" https://github.com/mono/SkiaSharp.git "$src"
+ else
+ git -C "$src" fetch --depth 1 origin "release/$SKIASHARP_VERSION"
+ git -C "$src" checkout -q FETCH_HEAD
+ fi
+ git -C "$src" submodule update --init --depth 1 externals/skia >&2
+ echo "$src"
+}
+
+sync_skia_deps() {
+ local skia_dir="$1/externals/skia"
+ if [[ ! -x "$skia_dir/bin/gn" ]]; then
+ python3 "$skia_dir/tools/git-sync-deps"
+ fi
+}
+
+build_skia() {
+ ensure_tools
+ ensure_depot_tools
+ local src
+ src="$(sync_skiasharp)"
+ sync_skia_deps "$src"
+
+ local skia_dir="$src/externals/skia"
+ local out_dir="$skia_dir/out/linux-static-$TARGET_CPU"
+ mkdir -p "$out_dir" "$OUTPUT_DIR"
+
+ cat >"$out_dir/args.gn" <"$out_dir/args.gn" </dev/null 2>&1; then
+ :
+ elif [[ -f "$deps_patch" ]]; then
+ git -C "$src" apply "$deps_patch"
+ fi
+}
+
+copy_first_existing() {
+ local dest="$1"
+ shift
+ for src in "$@"; do
+ if [[ -f "$src" ]]; then
+ cp "$src" "$dest"
+ echo "Wrote $dest"
+ return 0
+ fi
+ done
+ echo "None of the expected files exist for $dest:" >&2
+ printf ' %s\n' "$@" >&2
+ return 1
+}
+
+copy_full_archive_from_thin() {
+ local base_dir="$1"
+ local dest="$2"
+ shift 2
+
+ local src
+ for src in "$@"; do
+ if [[ -f "$src" ]]; then
+ if [[ "$(head -c 7 "$src")" == "!" ]]; then
+ local tmp="$dest.tmp"
+ local rel_src="${src#$base_dir/}"
+ (
+ cd "$base_dir"
+ mapfile -t members < <("$LLVM_AR" t "$rel_src")
+ "$LLVM_AR" rcs "$tmp" "${members[@]}"
+ )
+ mv "$tmp" "$dest"
+ else
+ cp "$src" "$dest"
+ fi
+ echo "Wrote $dest"
+ return 0
+ fi
+ done
+
+ echo "None of the expected files exist for $dest:" >&2
+ printf ' %s\n' "$@" >&2
+ return 1
+}
+
+main() {
+ mkdir -p "$WORK_DIR" "$OUTPUT_DIR"
+ case "${1:-all}" in
+ skia) build_skia ;;
+ angle) build_angle ;;
+ all)
+ build_skia
+ build_angle
+ ;;
+ -h|--help|help)
+ usage
+ ;;
+ *)
+ usage >&2
+ exit 1
+ ;;
+ esac
+}
+
+main "$@"
diff --git a/scripts/build-macos-static-graphics.sh b/scripts/build-macos-static-graphics.sh
new file mode 100755
index 0000000..ea3b2ee
--- /dev/null
+++ b/scripts/build-macos-static-graphics.sh
@@ -0,0 +1,180 @@
+#!/usr/bin/env bash
+set -euo pipefail
+
+ROOT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)"
+WORK_DIR="${WORK_DIR:-$ROOT_DIR/External/NativeStatic/.work}"
+TARGET_CPU="${TARGET_CPU:-arm64}"
+RID="${RID:-osx-$TARGET_CPU}"
+OUTPUT_DIR="${OUTPUT_DIR:-$ROOT_DIR/External/NativeStatic/$RID}"
+SKIASHARP_VERSION="${SKIASHARP_VERSION:-3.119.2}"
+ANGLE_BRANCH="${ANGLE_BRANCH:-7151}"
+BUILD_JOBS="${BUILD_JOBS:-$(sysctl -n hw.ncpu)}"
+ANGLE_PATCH_DIR="${ANGLE_PATCH_DIR:-$ROOT_DIR/External/NativeStatic/patches}"
+
+require_cmd() {
+ if ! command -v "$1" >/dev/null 2>&1; then
+ echo "Missing required command: $1" >&2
+ exit 1
+ fi
+}
+
+ensure_tools() {
+ require_cmd git
+ require_cmd python3
+ require_cmd clang
+ require_cmd clang++
+ require_cmd ar
+ require_cmd ninja
+}
+
+ensure_depot_tools() {
+ local depot_dir="$WORK_DIR/depot_tools"
+ if [[ ! -d "$depot_dir/.git" ]]; then
+ git clone --depth 1 https://chromium.googlesource.com/chromium/tools/depot_tools.git "$depot_dir"
+ else
+ git -C "$depot_dir" pull --ff-only
+ fi
+ export PATH="$depot_dir:$PATH"
+}
+
+copy_first_existing() {
+ local dest="$1"
+ shift
+ for src in "$@"; do
+ if [[ -f "$src" ]]; then
+ cp "$src" "$dest"
+ echo "Wrote $dest"
+ return 0
+ fi
+ done
+ echo "None of the expected files exist for $dest:" >&2
+ printf ' %s\n' "$@" >&2
+ return 1
+}
+
+sync_skiasharp() {
+ local src="$WORK_DIR/SkiaSharp-$SKIASHARP_VERSION"
+ if [[ ! -d "$src/.git" ]]; then
+ git clone --depth 1 --branch "release/$SKIASHARP_VERSION" https://github.com/mono/SkiaSharp.git "$src"
+ else
+ git -C "$src" fetch --depth 1 origin "release/$SKIASHARP_VERSION"
+ git -C "$src" checkout -q FETCH_HEAD
+ fi
+ git -C "$src" submodule update --init --depth 1 externals/skia >&2
+ echo "$src"
+}
+
+build_skia() {
+ ensure_tools
+ ensure_depot_tools
+ local src
+ src="$(sync_skiasharp)"
+ local skia_dir="$src/externals/skia"
+ if [[ ! -x "$skia_dir/bin/gn" ]]; then
+ python3 "$skia_dir/tools/git-sync-deps"
+ fi
+
+ local out_dir="$skia_dir/out/mac-static-$TARGET_CPU"
+ mkdir -p "$out_dir" "$OUTPUT_DIR"
+ cat >"$out_dir/args.gn" <"$out_dir/args.gn" <&2; exit 1 ;;
+ esac
+}
+
+main "$@"
diff --git a/scripts/build-windows-static-graphics.ps1 b/scripts/build-windows-static-graphics.ps1
new file mode 100644
index 0000000..3c9c28a
--- /dev/null
+++ b/scripts/build-windows-static-graphics.ps1
@@ -0,0 +1,170 @@
+param(
+ [ValidateSet("skia", "angle", "all")]
+ [string]$Target = "all"
+)
+
+$ErrorActionPreference = "Stop"
+
+$RootDir = Split-Path -Parent $PSScriptRoot
+$WorkDir = if ($env:WORK_DIR) { $env:WORK_DIR } else { Join-Path $RootDir "External\NativeStatic\.work" }
+$SkiaSharpVersion = if ($env:SKIASHARP_VERSION) { $env:SKIASHARP_VERSION } else { "3.119.2" }
+$AngleBranch = if ($env:ANGLE_BRANCH) { $env:ANGLE_BRANCH } else { "7151" }
+$TargetCpu = if ($env:TARGET_CPU) { $env:TARGET_CPU } else { "x64" }
+$Rid = if ($env:RID) { $env:RID } else { "win-$TargetCpu" }
+$OutputDir = if ($env:OUTPUT_DIR) { $env:OUTPUT_DIR } else { Join-Path $RootDir "External\NativeStatic\$Rid" }
+$BuildJobs = if ($env:BUILD_JOBS) { $env:BUILD_JOBS } else { [Environment]::ProcessorCount }
+$AnglePatchDir = if ($env:ANGLE_PATCH_DIR) { $env:ANGLE_PATCH_DIR } else { Join-Path $RootDir "External\NativeStatic\patches" }
+
+function Require-Command($Name) {
+ if (-not (Get-Command $Name -ErrorAction SilentlyContinue)) {
+ throw "Missing required command: $Name"
+ }
+}
+
+function Ensure-Tools {
+ Require-Command git
+ Require-Command python
+ Require-Command ninja
+}
+
+function Ensure-DepotTools {
+ $depotDir = Join-Path $WorkDir "depot_tools"
+ if (-not (Test-Path (Join-Path $depotDir ".git"))) {
+ git clone --depth 1 https://chromium.googlesource.com/chromium/tools/depot_tools.git $depotDir
+ } else {
+ git -C $depotDir pull --ff-only
+ }
+ $env:PATH = "$depotDir;$env:PATH"
+}
+
+function Copy-FirstExisting($Destination, [string[]]$Candidates) {
+ foreach ($candidate in $Candidates) {
+ if (Test-Path $candidate) {
+ Copy-Item $candidate $Destination -Force
+ Write-Host "Wrote $Destination"
+ return
+ }
+ }
+ throw "None of the expected files exist for $Destination: $($Candidates -join ', ')"
+}
+
+function Sync-SkiaSharp {
+ $src = Join-Path $WorkDir "SkiaSharp-$SkiaSharpVersion"
+ if (-not (Test-Path (Join-Path $src ".git"))) {
+ git clone --depth 1 --branch "release/$SkiaSharpVersion" https://github.com/mono/SkiaSharp.git $src
+ } else {
+ git -C $src fetch --depth 1 origin "release/$SkiaSharpVersion"
+ git -C $src checkout -q FETCH_HEAD
+ }
+ git -C $src submodule update --init --depth 1 externals/skia
+ return $src
+}
+
+function Build-Skia {
+ Ensure-Tools
+ Ensure-DepotTools
+ $src = Sync-SkiaSharp
+ $skiaDir = Join-Path $src "externals\skia"
+ if (-not (Test-Path (Join-Path $skiaDir "bin\gn.exe"))) {
+ python (Join-Path $skiaDir "tools\git-sync-deps")
+ }
+
+ $outDir = Join-Path $skiaDir "out\win-static-$TargetCpu"
+ New-Item -ItemType Directory -Path $outDir, $OutputDir -Force | Out-Null
+ @"
+target_os = "win"
+target_cpu = "$TargetCpu"
+is_official_build = true
+is_static_skiasharp = true
+is_clang = true
+skia_enable_tools = false
+skia_enable_ganesh = true
+skia_enable_pdf = false
+skia_enable_skottie = false
+skia_use_dng_sdk = false
+skia_use_fontconfig = false
+skia_use_freetype = true
+skia_use_harfbuzz = false
+skia_use_icu = false
+skia_use_piex = false
+skia_use_sfntly = false
+skia_use_system_expat = false
+skia_use_system_freetype2 = false
+skia_use_system_libjpeg_turbo = false
+skia_use_system_libpng = false
+skia_use_system_libwebp = false
+skia_use_system_zlib = false
+skia_use_vulkan = false
+skia_use_xps = false
+extra_cflags = [ "-DSKIA_C_DLL" ]
+extra_cflags_cc = [ "/GR" ]
+"@ | Set-Content -Path (Join-Path $outDir "args.gn") -Encoding ASCII
+
+ & (Join-Path $skiaDir "bin\gn.exe") gen $outDir
+ ninja -C $outDir -j $BuildJobs skia SkiaSharp HarfBuzzSharp
+
+ Copy-FirstExisting (Join-Path $OutputDir "skia.lib") @((Join-Path $outDir "skia.lib"), (Join-Path $outDir "obj\skia.lib"))
+ Copy-FirstExisting (Join-Path $OutputDir "SkiaSharp.lib") @((Join-Path $outDir "SkiaSharp.lib"), (Join-Path $outDir "obj\SkiaSharp.lib"))
+ Copy-FirstExisting (Join-Path $OutputDir "libHarfBuzzSharp.lib") @((Join-Path $outDir "libHarfBuzzSharp.lib"), (Join-Path $outDir "obj\libHarfBuzzSharp.lib"))
+}
+
+function Sync-Angle {
+ $src = Join-Path $WorkDir "ANGLE-$AngleBranch"
+ if (-not (Test-Path (Join-Path $src ".git"))) {
+ git clone --depth 1 --branch "chromium/$AngleBranch" https://github.com/google/angle.git $src
+ } else {
+ git -C $src fetch --depth 1 origin "chromium/$AngleBranch"
+ git -C $src checkout -q FETCH_HEAD
+ }
+ return $src
+}
+
+function Apply-AnglePatches($Src) {
+ $patch = Join-Path $AnglePatchDir "angle-chromium-$AngleBranch.patch"
+ if ((Test-Path $patch) -and -not (Select-String -Path (Join-Path $Src "BUILD.gn") -Pattern 'angle_static_library\("libANGLE_static"\)' -Quiet)) {
+ git -C $Src apply $patch
+ }
+}
+
+function Build-Angle {
+ Ensure-Tools
+ Ensure-DepotTools
+ $src = Sync-Angle
+ New-Item -ItemType Directory -Path $OutputDir -Force | Out-Null
+ Apply-AnglePatches $src
+ Push-Location $src
+ try {
+ python scripts/bootstrap.py
+ gclient sync -f -D -R
+ $outDir = Join-Path $src "out\win-static-$TargetCpu"
+ New-Item -ItemType Directory -Path $outDir -Force | Out-Null
+ @"
+target_os = "win"
+target_cpu = "$TargetCpu"
+is_debug = false
+is_component_build = false
+is_clang = true
+use_lld = false
+use_thin_lto = false
+symbol_level = 0
+angle_build_tests = false
+build_angle_deqp_tests = false
+angle_enable_swiftshader = false
+angle_enable_vulkan = false
+angle_enable_wgpu = false
+"@ | Set-Content -Path (Join-Path $outDir "args.gn") -Encoding ASCII
+ gn gen $outDir
+ ninja -C $outDir -j $BuildJobs libANGLE_static libGLESv2_static
+ Copy-FirstExisting (Join-Path $OutputDir "libANGLE_static.lib") @((Join-Path $outDir "libANGLE_static.lib"), (Join-Path $outDir "obj\libANGLE_static.lib"), (Join-Path $outDir "obj\libANGLE_static\libANGLE_static.lib"))
+ Copy-FirstExisting (Join-Path $OutputDir "libGLESv2_static.lib") @((Join-Path $outDir "libGLESv2_static.lib"), (Join-Path $outDir "obj\libGLESv2_static.lib"), (Join-Path $outDir "obj\libGLESv2_static\libGLESv2_static.lib"))
+ } finally {
+ Pop-Location
+ }
+}
+
+New-Item -ItemType Directory -Path $WorkDir, $OutputDir -Force | Out-Null
+switch ($Target) {
+ "skia" { Build-Skia }
+ "angle" { Build-Angle }
+ "all" { Build-Skia; Build-Angle }
+}