Commit 572f0e08 authored by liuqiang's avatar liuqiang

feat(ohos): Adapt 0.150.1 and skia m150

Signed-off-by: liuqiang's avatarqiangliu <liuqiang@eazytec.com>
parent 3956e988
...@@ -13,3 +13,5 @@ skiko/src/jvmMain/java ...@@ -13,3 +13,5 @@ skiko/src/jvmMain/java
.cache .cache
.clangd .clangd
**/kotlin-js-store/ **/kotlin-js-store/
*.log
/dependencies/
[versions] [versions]
kotlin = "2.3.20" kotlin = "2.4.0-ST.0.4.0-0812"
kotlinxBrowser = "0.5.0" kotlinxBrowser = "0.5.0"
coroutines = "1.8.0" coroutines = "1.10.2-0.4.0"
jetbrainsRuntime-api = "1.5.0" jetbrainsRuntime-api = "1.5.0"
androidGradlePlugin = "9.0.0" androidGradlePlugin = "9.0.0"
...@@ -15,6 +15,9 @@ cryptoChecksumPlugin = "1.4.0" ...@@ -15,6 +15,9 @@ cryptoChecksumPlugin = "1.4.0"
kotlinxBenchmark = "0.4.14" kotlinxBenchmark = "0.4.14"
[plugins]
kotlin-jvm = { id = "org.jetbrains.kotlin.jvm", version.ref = "kotlin" }
[libraries] [libraries]
coroutines-core = { module = "org.jetbrains.kotlinx:kotlinx-coroutines-core", version.ref = "coroutines" } coroutines-core = { module = "org.jetbrains.kotlinx:kotlinx-coroutines-core", version.ref = "coroutines" }
coroutines-core-jvm = { module = "org.jetbrains.kotlinx:kotlinx-coroutines-core-jvm", version.ref = "coroutines" } coroutines-core-jvm = { module = "org.jetbrains.kotlinx:kotlinx-coroutines-core-jvm", version.ref = "coroutines" }
...@@ -33,4 +36,4 @@ githubApi = { module = "org.kohsuke:github-api", version.ref = "githubApi" } ...@@ -33,4 +36,4 @@ githubApi = { module = "org.kohsuke:github-api", version.ref = "githubApi" }
buildHelpers-publishing-gradlePlugin = { module = "org.jetbrains.compose.internal.build-helpers:publishing", version.ref = "buildHelpers-publishing" } buildHelpers-publishing-gradlePlugin = { module = "org.jetbrains.compose.internal.build-helpers:publishing", version.ref = "buildHelpers-publishing" }
crypto-checksum-gradlePlugin = { module = "gradle.plugin.org.gradle.crypto:checksum", version.ref = "cryptoChecksumPlugin" } crypto-checksum-gradlePlugin = { module = "gradle.plugin.org.gradle.crypto:checksum", version.ref = "cryptoChecksumPlugin" }
kotlinx-benchmark-gradlePlugin = { module = "org.jetbrains.kotlinx:kotlinx-benchmark-plugin", version.ref = "kotlinxBenchmark" } kotlinx-benchmark-gradlePlugin = { module = "org.jetbrains.kotlinx:kotlinx-benchmark-plugin", version.ref = "kotlinxBenchmark" }
kotlinx-benchmark-runtime = { module = "org.jetbrains.kotlinx:kotlinx-benchmark-runtime", version.ref = "kotlinxBenchmark"} kotlinx-benchmark-runtime = { module = "org.jetbrains.kotlinx:kotlinx-benchmark-runtime", version.ref = "kotlinxBenchmark"}
\ No newline at end of file
#!/usr/bin/env bash
# Copyright (c) 2026 Huawei Device Co., Ltd.
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
set -euo pipefail
RETRY_MAX_ATTEMPTS="${RETRY_MAX_ATTEMPTS:-3}"
RETRY_DELAY_SECONDS="${RETRY_DELAY_SECONDS:-10}"
DEPLOY_VERSION="${DEPLOY_VERSION:-0.150.1-0.1.0-01}"
DEPLOY_RELEASE="${DEPLOY_RELEASE:-true}"
PUBLISH_REPOSITORY="${PUBLISH_REPOSITORY:-MavenLocal}"
PUBLISH_MODE="default"
# region Ohos
SKIA_TAG="m150-1f14f1166a"
# endregion
case "$PUBLISH_REPOSITORY" in
MavenLocal)
PUBLISH_TASK_SUFFIX="ToMavenLocal"
;;
ComposeRepo)
PUBLISH_TASK_SUFFIX="ToComposeRepoRepository"
;;
*)
echo "[build-ohos] ERROR: unsupported PUBLISH_REPOSITORY: $PUBLISH_REPOSITORY"
echo "[build-ohos] Supported repositories: MavenLocal, ComposeRepo"
exit 1
;;
esac
PUBLISH_OHOS_ARM64_TASK=":skiko:publishOhosArm64Publication${PUBLISH_TASK_SUFFIX}"
PUBLISH_OHOS_X64_TASK=":skiko:publishOhosX64Publication${PUBLISH_TASK_SUFFIX}"
PUBLISH_KMP_TASK=":skiko:publishKotlinMultiplatformPublication${PUBLISH_TASK_SUFFIX}"
if [ $# -gt 1 ]; then
echo "[build-ohos] ERROR: too many arguments."
echo "[build-ohos] Usage: $0 [--skia|--fusionRenderer]"
exit 1
fi
if [ $# -eq 1 ]; then
case "$1" in
--skia)
PUBLISH_MODE="skia"
;;
--fusionRenderer)
PUBLISH_MODE="fusionRenderer"
;;
*)
echo "[build-ohos] ERROR: unsupported argument: $1"
echo "[build-ohos] Usage: $0 [--skia|--fusionRenderer]"
exit 1
;;
esac
fi
if [ -z "${JAVA_HOME:-}" ] || [ ! -x "$JAVA_HOME/bin/java" ]; then
echo "[build-ohos] ERROR: JAVA_HOME is not set to a valid JDK."
echo "[build-ohos] Configure JAVA_HOME in the shell or pass it when invoking this script."
exit 1
fi
export JAVA_HOME
export PATH="$JAVA_HOME/bin:$PATH"
echo "[build-ohos] Using JAVA_HOME: $JAVA_HOME"
java -version 2>&1 | sed -n '1p'
# 切换到项目根目录,支持从任意路径调用(如 CI 流水线)
SCRIPT_PATH="${BASH_SOURCE[0]:-$0}"
SCRIPT_DIR="$(cd "$(dirname "$SCRIPT_PATH")" && pwd)"
PROJECT_ROOT="$(cd "$SCRIPT_DIR/.." && pwd)"
cd "$PROJECT_ROOT"
OHOS_NATIVE_DIR="${OHOS_NATIVE_DIR:-/Applications/DevEco-Studio.app/Contents/sdk/default/openharmony/native}"
export OHOS_LLVM="${OHOS_LLVM:-$OHOS_NATIVE_DIR/llvm}"
export OHOS_SYSROOT="${OHOS_SYSROOT:-$OHOS_NATIVE_DIR/sysroot}"
if [ ! -d "$OHOS_LLVM" ] || [ ! -d "$OHOS_SYSROOT" ]; then
echo "[build-ohos] ERROR: OHOS native toolchain not found: $OHOS_NATIVE_DIR"
exit 1
fi
echo "[build-ohos] OHOS native SDK: $OHOS_NATIVE_DIR"
# region Ohos
# Prepares the patched m150 checkout when no external checkout is supplied.
# Building is delegated to the upstream Gradle task.
prepare_ohos_skia() {
local managed_skia_dir arm64_archive x64_archive
if [ -n "${SKIA_DIR:-}" ]; then
if [ ! -d "$SKIA_DIR/.git" ]; then
echo "[build-ohos] ERROR: SKIA_DIR is not a Git checkout: $SKIA_DIR"
return 1
fi
managed_skia_dir="$SKIA_DIR"
else
managed_skia_dir="$PROJECT_ROOT/dependencies/skia/$SKIA_TAG"
if [ ! -d "$managed_skia_dir/.git" ]; then
if [ -e "$managed_skia_dir" ]; then
echo "[build-ohos] ERROR: managed Skia path exists but is not a Git checkout: $managed_skia_dir"
return 1
fi
mkdir -p "$(dirname "$managed_skia_dir")"
echo "[build-ohos] Cloning official Skia $SKIA_TAG"
git clone --depth 1 --branch "$SKIA_TAG" https://github.com/JetBrains/skia.git "$managed_skia_dir"
fi
fi
"$PROJECT_ROOT/skia/apply_patch.sh" "$managed_skia_dir"
export OHOS_SDK_HOME="${OHOS_SDK_HOME:-$(dirname "$OHOS_NATIVE_DIR")}"
arm64_archive="$managed_skia_dir/Skia-$SKIA_TAG-ohos-Release-arm64.zip"
x64_archive="$managed_skia_dir/Skia-$SKIA_TAG-ohos-Release-x64.zip"
if [ ! -f "$arm64_archive" ] || [ ! -f "$x64_archive" ]; then
echo "[build-ohos] Building Skia $SKIA_TAG through prepareLocalSkiaBuild"
SKIA_TARGET=ohos SKIA_VERSION="$SKIA_TAG" \
./gradlew :skiko:prepareLocalSkiaBuild \
-Pskia.repo.dir="$managed_skia_dir" \
-Pskiko.awt.enabled=false \
-Pskiko.native.ohos.enabled=true
fi
SKIA_DIR="$managed_skia_dir"
export SKIA_DIR
echo "[build-ohos] Skia source: $SKIA_DIR"
}
# endregion
# fusionRenderer requires the fusion-render repository root (the directory containing OHRender/).
# Keep the legacy lookup order: OHRENDER_DIR, then the sibling fusion-render checkout.
if [ -z "${OHRENDER_DIR:-}" ]; then
OHRENDER_DIR="$(cd "$(dirname "$PROJECT_ROOT")" && pwd)/fusion-render"
fi
export OHRENDER_DIR
echo "[build-ohos] OHRENDER_DIR: $OHRENDER_DIR"
echo "[build-ohos] Publish repository: $PUBLISH_REPOSITORY"
# 避免 CI 未注入 BUILD_LOG 时导致 tee 空路径报错
BUILD_LOG="${BUILD_LOG:-$PROJECT_ROOT/build-ohos.log}"
mkdir -p "$(dirname "$BUILD_LOG")"
resolve_android_sdk() {
local default_sdk
if [ -n "${ANDROID_HOME:-}" ] && [ -d "${ANDROID_HOME}" ]; then
echo "${ANDROID_HOME}"
return 0
fi
if [ -n "${ANDROID_SDK_ROOT:-}" ] && [ -d "${ANDROID_SDK_ROOT}" ]; then
echo "${ANDROID_SDK_ROOT}"
return 0
fi
default_sdk="${HOME:-}/Library/Android/sdk"
if [ -n "${HOME:-}" ] && [ -d "$default_sdk" ]; then
echo "$default_sdk"
return 0
fi
return 1
}
ensure_android_local_properties() {
local sdk_path local_props tmp_file sdk_escaped input_file
if ! sdk_path="$(resolve_android_sdk)"; then
echo "[build-ohos] ERROR: Android SDK not found."
echo "[build-ohos] Please install Android SDK to: ${HOME:-<your_home>}/Library/Android/sdk"
echo "[build-ohos] Or set ANDROID_HOME / ANDROID_SDK_ROOT manually."
return 1
fi
export ANDROID_HOME="$sdk_path"
export ANDROID_SDK_ROOT="$sdk_path"
local_props="$PROJECT_ROOT/skiko/local.properties"
mkdir -p "$(dirname "$local_props")"
tmp_file="$(mktemp)"
sdk_escaped="$(printf '%s' "$sdk_path" | sed 's/:/\\:/g')"
input_file="$local_props"
[ -f "$local_props" ] || input_file="/dev/null"
awk -v sdk="$sdk_escaped" '
BEGIN { updated = 0 }
/^sdk\.dir=/ {
print "sdk.dir=" sdk
updated = 1
next
}
{ print }
END {
if (!updated) {
print "sdk.dir=" sdk
}
}
' "$input_file" > "$tmp_file"
mv "$tmp_file" "$local_props"
echo "[build-ohos] Android SDK: $sdk_path"
echo "[build-ohos] Ensured local.properties: $local_props"
}
# 默认沿用用户本机 Gradle 缓存(通常是 ~/.gradle),避免每次冷启动拉依赖。
# 仅在外部显式传入 GRADLE_USER_HOME 时使用自定义目录。
if [ -n "${GRADLE_USER_HOME:-}" ]; then
mkdir -p "$GRADLE_USER_HOME"
fi
# skiko/settings.gradle.kts 的 buildscript 默认会访问 JetBrains Space;内网或 Space 503 时改为跳过,
# 依赖需来自 Nexus、~/.m2 或 skiko/gradle/local-buildscript-m2(见 skiko/gradle.properties 注释)。
export SKIKO_BUILDSCRIPT_SKIP_JETBRAINS_SPACE="${SKIKO_BUILDSCRIPT_SKIP_JETBRAINS_SPACE:-true}"
# 增大 Gradle 访问仓库超时,降低网络抖动导致的误失败
GRADLE_NETWORK_OPTS="-Dorg.gradle.internal.http.connectionTimeout=${GRADLE_CONNECTION_TIMEOUT_MS:-60000} -Dorg.gradle.internal.http.socketTimeout=${GRADLE_SOCKET_TIMEOUT_MS:-120000}"
export GRADLE_OPTS="${GRADLE_OPTS:-} $GRADLE_NETWORK_OPTS"
is_dependency_download_error() {
local log_file="$1"
grep -Eiq \
'Could not resolve|Could not GET|Could not HEAD|Read timed out|Connection reset|Connection timed out|Temporary failure|503 Service Unavailable|502 Bad Gateway|Failed to fetch|ArtifactResolveException|Could not download' \
"$log_file"
}
run_publish_scheme() {
echo "[build-ohos] Pass 1/2: OHOS Skia"
prepare_ohos_skia || return 1
ensure_android_local_properties || return 1
if ! ./gradlew \
"$PUBLISH_OHOS_ARM64_TASK" \
"$PUBLISH_OHOS_X64_TASK" \
"$PUBLISH_KMP_TASK" \
-x :skiko:compileCommonMainKotlinMetadata \
-x :skiko:compileNativeJsMainKotlinMetadata \
-x :skiko:compileNativeMainKotlinMetadata \
-Pskiko.awt.enabled=false \
-Pskiko.android.enabled=true \
-Pskiko.js.enabled=false \
-Pskiko.wasm.enabled=false \
-Pskiko.native.enabled=false \
-Pskiko.native.mac.enabled=false \
-Pskiko.native.ios.enabled=false \
-Pskiko.native.ios.arm64.enabled=true \
-Pskiko.native.ios.simulatorArm64.enabled=true \
-Pskiko.native.ios.x64.enabled=false \
-Pskiko.native.ohos.enabled=true \
-Pskiko.native.ohos.arm64.enabled=true \
-Pskiko.native.ohos.x64.enabled=true \
-Pskiko.upstream.redirect.enabled=true \
-Pkotlin.stdlib.default.dependency=false \
-Pskia.dir="$SKIA_DIR" \
-Pdeploy.version="$DEPLOY_VERSION" \
-Pdeploy.release="$DEPLOY_RELEASE" \
--no-build-cache --rerun-tasks; then
return 1
fi
echo "[build-ohos] Pass 2/2: OHOS fusionRenderer"
if ! ./gradlew \
"$PUBLISH_OHOS_ARM64_TASK" \
"$PUBLISH_OHOS_X64_TASK" \
-Pskiko.awt.enabled=false \
-Pskiko.android.enabled=false \
-Pskiko.js.enabled=false \
-Pskiko.wasm.enabled=false \
-Pskiko.native.enabled=false \
-Pskiko.native.mac.enabled=false \
-Pskiko.native.ios.enabled=false \
-Pskiko.native.ohos.enabled=true \
-Pskiko.native.ohos.arm64.enabled=true \
-Pskiko.native.ohos.x64.enabled=true \
-Pskiko.native.ohos.fusionRenderer.enabled=true \
-Pohrender.dir="$OHRENDER_DIR" \
-Pdeploy.version="$DEPLOY_VERSION" \
-Pdeploy.release="$DEPLOY_RELEASE" \
--no-build-cache --rerun-tasks; then
return 1
fi
}
run_publish_skia_only() {
echo "[build-ohos] Mode: --skia (OHOS Skia)"
prepare_ohos_skia || return 1
ensure_android_local_properties || return 1
./gradlew \
"$PUBLISH_OHOS_ARM64_TASK" \
"$PUBLISH_OHOS_X64_TASK" \
"$PUBLISH_KMP_TASK" \
-x :skiko:compileCommonMainKotlinMetadata \
-x :skiko:compileNativeJsMainKotlinMetadata \
-x :skiko:compileNativeMainKotlinMetadata \
-Pskiko.awt.enabled=false \
-Pskiko.android.enabled=true \
-Pskiko.js.enabled=false \
-Pskiko.wasm.enabled=false \
-Pskiko.native.enabled=false \
-Pskiko.native.mac.enabled=false \
-Pskiko.native.ios.enabled=false \
-Pskiko.native.ios.arm64.enabled=true \
-Pskiko.native.ios.simulatorArm64.enabled=true \
-Pskiko.native.ios.x64.enabled=false \
-Pskiko.native.ohos.enabled=true \
-Pskiko.native.ohos.arm64.enabled=true \
-Pskiko.native.ohos.x64.enabled=true \
-Pskiko.upstream.redirect.enabled=true \
-Pkotlin.stdlib.default.dependency=false \
-Pskia.dir="$SKIA_DIR" \
-Pdeploy.version="$DEPLOY_VERSION" \
-Pdeploy.release="$DEPLOY_RELEASE" \
--no-build-cache --rerun-tasks
}
run_publish_fusion_renderer_only() {
echo "[build-ohos] Mode: --fusionRenderer (OHOS fusionRenderer)"
./gradlew \
"$PUBLISH_OHOS_ARM64_TASK" \
"$PUBLISH_OHOS_X64_TASK" \
-Pskiko.awt.enabled=false \
-Pskiko.android.enabled=false \
-Pskiko.js.enabled=false \
-Pskiko.wasm.enabled=false \
-Pskiko.native.enabled=false \
-Pskiko.native.mac.enabled=false \
-Pskiko.native.ios.enabled=false \
-Pskiko.native.ohos.enabled=true \
-Pskiko.native.ohos.arm64.enabled=true \
-Pskiko.native.ohos.x64.enabled=true \
-Pskiko.native.ohos.fusionRenderer.enabled=true \
-Pohrender.dir="$OHRENDER_DIR" \
-Pdeploy.version="$DEPLOY_VERSION" \
-Pdeploy.release="$DEPLOY_RELEASE" \
--no-build-cache --rerun-tasks
}
run_with_dependency_retry() {
local attempt=1
local max_attempts delay attempt_log
max_attempts="$RETRY_MAX_ATTEMPTS"
delay="$RETRY_DELAY_SECONDS"
while [ "$attempt" -le "$max_attempts" ]; do
attempt_log="$(mktemp)"
echo "[build-ohos] publish attempt ${attempt}/${max_attempts} (mode=${PUBLISH_MODE})"
if (
case "$PUBLISH_MODE" in
skia)
run_publish_skia_only
;;
fusionRenderer)
run_publish_fusion_renderer_only
;;
*)
run_publish_scheme
;;
esac
) 2>&1 | tee "$BUILD_LOG" "$attempt_log"; then
rm -f "$attempt_log"
return 0
fi
if [ "$attempt" -lt "$max_attempts" ] && is_dependency_download_error "$attempt_log"; then
echo "[build-ohos] 依赖下载疑似失败,${delay}s 后重试..."
rm -f "$attempt_log"
sleep "$delay"
attempt=$((attempt + 1))
continue
fi
rm -f "$attempt_log"
return 1
done
}
if run_with_dependency_retry; then
exit 0
fi
echo "[build-ohos] 构建失败。"
exit 1
# OHOS Skia m150
The OHOS Skia backend is based on JetBrains Skia tag
`m150-1f14f1166a` (`1f14f1166a847cb0e148f981600bf1e3e6ca6e46`).
The Skia checkout and build outputs are not part of this repository. The
complete source delta is stored in `patches/skia-m150-ohos.patch`.
## Build from a clean Skiko checkout
Run the OHOS build normally:
```bash
DEPLOY_VERSION=0.150.1-0.1.0-01 \
DEPLOY_RELEASE=true \
./scripts/build-ohos.sh
```
When `SKIA_DIR` is not set, the script automatically:
1. clones the official `m150-1f14f1166a` tag into the ignored
`dependencies/skia/m150-1f14f1166a` directory;
2. applies `patches/skia-m150-ohos.patch`;
3. delegates the OHOS arm64/x64 build to the upstream
`prepareLocalSkiaBuild` task; and
4. publishes Skiko using that checkout.
Subsequent builds reuse the checkout and compiled libraries.
## Optional: use an external checkout
Clone Skia in any external working directory, check out the exact m150 tag,
and apply the patch:
```bash
git clone https://github.com/JetBrains/skia.git /path/to/skia-m150
git -C /path/to/skia-m150 checkout m150-1f14f1166a
./skia/apply_patch.sh /path/to/skia-m150
```
Then point the normal build at it:
```bash
SKIA_DIR=/path/to/skia-m150 \
DEPLOY_VERSION=0.150.1-0.1.0-01 \
DEPLOY_RELEASE=true \
./scripts/build-ohos.sh
```
#!/usr/bin/env bash
set -euo pipefail
SCRIPT_PATH="${BASH_SOURCE[0]:-$0}"
SCRIPT_DIR="$(cd "$(dirname "$SCRIPT_PATH")" && pwd)"
REPOSITORY_ROOT="$(cd "$SCRIPT_DIR/.." && pwd)"
SKIA_DIR="${1:-$REPOSITORY_ROOT/dependencies/skia/m150-1f14f1166a}"
PATCH_FILE="$SCRIPT_DIR/patches/skia-m150-ohos.patch"
EXPECTED_COMMIT="1f14f1166a847cb0e148f981600bf1e3e6ca6e46"
if [ ! -d "$SKIA_DIR/.git" ]; then
echo "[skia] ERROR: Skia checkout not found: $SKIA_DIR"
exit 1
fi
ACTUAL_COMMIT="$(git -C "$SKIA_DIR" rev-parse HEAD)"
if [ "$ACTUAL_COMMIT" != "$EXPECTED_COMMIT" ]; then
echo "[skia] ERROR: expected m150 commit $EXPECTED_COMMIT, found $ACTUAL_COMMIT"
exit 1
fi
if git -C "$SKIA_DIR" apply --reverse --check "$PATCH_FILE" >/dev/null 2>&1; then
echo "[skia] OHOS m150 patch is already applied."
exit 0
fi
git -C "$SKIA_DIR" apply --check "$PATCH_FILE"
git -C "$SKIA_DIR" apply "$PATCH_FILE"
echo "[skia] Applied OHOS m150 patch to $SKIA_DIR"
This source diff could not be displayed because it is too large. You can view the blob instead.
@file:OptIn(ExperimentalKotlinGradlePluginApi::class, ExperimentalWasmDsl::class) @file:OptIn(ExperimentalKotlinGradlePluginApi::class, ExperimentalWasmDsl::class)
import com.android.build.api.dsl.KotlinMultiplatformAndroidLibraryTarget import com.android.build.api.dsl.KotlinMultiplatformAndroidLibraryTarget
import org.gradle.api.attributes.Category
import org.gradle.api.attributes.Usage
import org.gradle.api.tasks.compile.JavaCompile import org.gradle.api.tasks.compile.JavaCompile
import org.gradle.api.tasks.testing.logging.TestExceptionFormat import org.gradle.api.tasks.testing.logging.TestExceptionFormat
import org.gradle.crypto.checksum.Checksum import org.gradle.crypto.checksum.Checksum
...@@ -8,13 +10,16 @@ import org.jetbrains.compose.internal.publishing.MavenCentralProperties ...@@ -8,13 +10,16 @@ import org.jetbrains.compose.internal.publishing.MavenCentralProperties
import org.jetbrains.kotlin.gradle.ExperimentalKotlinGradlePluginApi import org.jetbrains.kotlin.gradle.ExperimentalKotlinGradlePluginApi
import org.jetbrains.kotlin.gradle.dsl.JvmTarget import org.jetbrains.kotlin.gradle.dsl.JvmTarget
import org.jetbrains.kotlin.gradle.dsl.KotlinJsCompile import org.jetbrains.kotlin.gradle.dsl.KotlinJsCompile
import org.jetbrains.kotlin.gradle.plugin.KotlinPlatformType
import org.jetbrains.kotlin.gradle.tasks.KotlinCompilationTask import org.jetbrains.kotlin.gradle.tasks.KotlinCompilationTask
import org.jetbrains.kotlin.gradle.tasks.KotlinNativeCompile import org.jetbrains.kotlin.gradle.tasks.KotlinNativeCompile
import org.jetbrains.kotlin.gradle.ExperimentalWasmDsl import org.jetbrains.kotlin.gradle.ExperimentalWasmDsl
import org.gradle.kotlin.dsl.withType import org.gradle.kotlin.dsl.withType
import tasks.configuration.* import tasks.configuration.*
import org.jetbrains.kotlin.gradle.dsl.KotlinVersion import org.jetbrains.kotlin.gradle.dsl.KotlinVersion
import org.jetbrains.kotlin.gradle.dsl.KotlinNativeCompilerOptions
import dsl.SkikoDependencyScope import dsl.SkikoDependencyScope
import java.util.Properties
plugins { plugins {
kotlin("multiplatform") kotlin("multiplatform")
...@@ -38,6 +43,9 @@ val buildType = skiko.buildType ...@@ -38,6 +43,9 @@ val buildType = skiko.buildType
val targetOs = hostOs val targetOs = hostOs
val targetArch = skiko.targetArch val targetArch = skiko.targetArch
val skikoArtifacts = SkikoArtifacts() val skikoArtifacts = SkikoArtifacts()
val upstreamRedirectEnabled = findProperty("skiko.upstream.redirect.enabled") == "true"
val upstreamKotlinVersion = providers.gradleProperty("skiko.upstream.kotlin.version").orElse("2.3.20")
val upstreamCoroutinesVersion = providers.gradleProperty("skiko.upstream.coroutines.version").orElse("1.8.0")
val coreDependencies: SkikoDependencyScope.() -> Unit = { val coreDependencies: SkikoDependencyScope.() -> Unit = {
targets { targets {
...@@ -201,6 +209,22 @@ allprojects { ...@@ -201,6 +209,22 @@ allprojects {
} }
repositories { repositories {
val composeRepoProperties = Properties().apply {
val propertiesFile = rootDir.parentFile.resolve("local.properties")
if (propertiesFile.isFile) propertiesFile.inputStream().use { load(it) }
}
composeRepoProperties.getProperty("COMPOSE_REPO_URL")
?.takeIf(String::isNotBlank)
?.let { repoUrl ->
maven(repoUrl) {
credentials {
username = composeRepoProperties.getProperty("COMPOSE_REPO_USERNAME")
password = composeRepoProperties.getProperty("COMPOSE_REPO_KEY")
}
}
}
maven("https://maven.eazytec-cloud.com/nexus/repository/maven-inner/")
maven("https://maven.eazytec-cloud.com/nexus/repository/maven-public/")
mavenCentral { mavenCentral {
url = uri("https://cache-redirector.jetbrains.com/maven-central") url = uri("https://cache-redirector.jetbrains.com/maven-central")
} }
...@@ -302,6 +326,50 @@ kotlin { ...@@ -302,6 +326,50 @@ kotlin {
skikoProjectContext.configureNativeTarget(OS.Linux, Arch.X64, linuxX64()) skikoProjectContext.configureNativeTarget(OS.Linux, Arch.X64, linuxX64())
skikoProjectContext.configureNativeTarget(OS.Linux, Arch.Arm64, linuxArm64()) skikoProjectContext.configureNativeTarget(OS.Linux, Arch.Arm64, linuxArm64())
} }
if (supportNativeOhosArm64) {
skikoProjectContext.configureNativeTarget(OS.OHOS, Arch.Arm64, ohosArm64())
}
if (supportNativeOhosX64) {
skikoProjectContext.configureNativeTarget(OS.OHOS, Arch.X64, ohosX64())
}
if (findProperty("skiko.native.ohos.fusionRenderer.enabled") == "true") {
val fusionModuleName = "org.jetbrains.skiko.fusion:skiko"
listOf("ohosArm64", "ohosX64").forEach { targetName ->
targets.findByName(targetName)?.compilations?.getByName("main")?.compileTaskProvider?.configure {
(compilerOptions as KotlinNativeCompilerOptions).moduleName.set(fusionModuleName)
}
}
}
val ohosSourceVariant = if (
findProperty("skiko.native.ohos.fusionRenderer.enabled") == "true"
) "fusionRenderer" else "skia"
if (supportNativeOhosArm64) {
sourceSets.named("ohosArm64Main") {
kotlin.srcDir("src/ohosMain/kotlin")
kotlin.srcDir("src/ohosMain/$ohosSourceVariant/kotlin")
if (upstreamRedirectEnabled) {
dependencies {
implementation("org.jetbrains.kotlin:kotlin-stdlib:${libs.versions.kotlin.get()}")
implementation(libs.coroutines.core)
}
}
}
}
if (supportNativeOhosX64) {
sourceSets.named("ohosX64Main") {
kotlin.srcDir("src/ohosMain/kotlin")
kotlin.srcDir("src/ohosMain/$ohosSourceVariant/kotlin")
if (upstreamRedirectEnabled) {
dependencies {
implementation("org.jetbrains.kotlin:kotlin-stdlib:${libs.versions.kotlin.get()}")
implementation(libs.coroutines.core)
}
}
}
}
if (supportNativeIosArm64) { if (supportNativeIosArm64) {
skikoProjectContext.configureNativeTarget(OS.IOS, Arch.Arm64, iosArm64()) skikoProjectContext.configureNativeTarget(OS.IOS, Arch.Arm64, iosArm64())
} }
...@@ -322,8 +390,13 @@ kotlin { ...@@ -322,8 +390,13 @@ kotlin {
} }
sourceSets.commonMain.dependencies { sourceSets.commonMain.dependencies {
implementation(kotlin("stdlib")) if (upstreamRedirectEnabled) {
implementation(libs.coroutines.core) implementation("org.jetbrains.kotlin:kotlin-stdlib:${upstreamKotlinVersion.get()}")
implementation("org.jetbrains.kotlinx:kotlinx-coroutines-core:${upstreamCoroutinesVersion.get()}")
} else {
implementation(kotlin("stdlib"))
implementation(libs.coroutines.core)
}
} }
sourceSets.commonTest.dependencies { sourceSets.commonTest.dependencies {
...@@ -359,6 +432,17 @@ kotlin { ...@@ -359,6 +432,17 @@ kotlin {
} }
} }
//region Ohos
// AGP 9 creates Android as an external KMP target, which is not selected by
// this project's `withAndroidTarget()` hierarchy group. Connect the proxy
// target explicitly so commonMain has a JVM consumer during redirect builds.
if (supportAndroid && upstreamRedirectEnabled) {
sourceSets.named("androidMain") {
dependsOn(sourceSets.getByName("commonMain"))
}
}
//endregion
skikoProjectContext.jvmTestSourceSet?.dependencies { skikoProjectContext.jvmTestSourceSet?.dependencies {
implementation(libs.coroutines.test) implementation(libs.coroutines.test)
implementation(kotlin("test-junit")) implementation(kotlin("test-junit"))
...@@ -499,6 +583,60 @@ if (supportAndroid) { ...@@ -499,6 +583,60 @@ if (supportAndroid) {
skikoProjectContext.declarePublications() skikoProjectContext.declarePublications()
//region Ohos
// Android/iOS variants in the fork root metadata redirect to official m150
// target artifacts. Keep their shared metadata contract official as well; the
// locally compiled OHOS leaf klibs remain free to expose OHOS-only additions.
if (upstreamRedirectEnabled) {
val upstreamSkikoMetadataVersion = providers
.gradleProperty("skiko.upstream.redirect.version")
.orElse("0.150.1")
val upstreamSkikoMetadata by configurations.creating {
isCanBeConsumed = false
isCanBeResolved = true
isTransitive = false
resolutionStrategy.useGlobalDependencySubstitutionRules = false
attributes {
attribute(Category.CATEGORY_ATTRIBUTE, objects.named(Category.LIBRARY))
attribute(Usage.USAGE_ATTRIBUTE, objects.named("kotlin-metadata"))
attribute(KotlinPlatformType.attribute, KotlinPlatformType.common)
}
}
val upstreamSkikoSources by configurations.creating {
isCanBeConsumed = false
isCanBeResolved = true
isTransitive = false
resolutionStrategy.useGlobalDependencySubstitutionRules = false
}
dependencies {
upstreamSkikoMetadata(
"org.jetbrains.skiko:skiko:${upstreamSkikoMetadataVersion.get()}"
)
upstreamSkikoSources(
"org.jetbrains.skiko:skiko:${upstreamSkikoMetadataVersion.get()}:sources@jar"
)
}
tasks.named<Jar>("allMetadataJar") {
duplicatesStrategy = DuplicatesStrategy.EXCLUDE
from({ upstreamSkikoMetadata.files.map(::zipTree) }) {
include("commonMain/**")
include("nativeJsMain/**")
include("nativeMain/**")
}
}
val upstreamSkikoSourcesFile = upstreamSkikoSources.elements.map { it.single().asFile }
tasks.named<org.gradle.jvm.tasks.Jar>("sourcesJar") {
inputs.file(upstreamSkikoSourcesFile).withPropertyName("upstreamSkikoSources")
// The root publication redirects Android/iOS to official artifacts, so its
// source contract must match the official root as well. Keep this task and
// output path intact because KGP wires metadataSourcesElements to them.
doLast {
upstreamSkikoSourcesFile.get().copyTo(archiveFile.get().asFile, overwrite = true)
}
}
}
//endregion
val mavenCentral = MavenCentralProperties(project) val mavenCentral = MavenCentralProperties(project)
if (skiko.isTeamcityCIBuild || mavenCentral.signArtifacts) { if (skiko.isTeamcityCIBuild || mavenCentral.signArtifacts) {
signing { signing {
......
plugins { plugins {
alias(libs.plugins.kotlin.jvm)
`kotlin-dsl` `kotlin-dsl`
} }
...@@ -15,3 +16,10 @@ dependencies { ...@@ -15,3 +16,10 @@ dependencies {
implementation(libs.crypto.checksum.gradlePlugin) implementation(libs.crypto.checksum.gradlePlugin)
implementation(libs.kotlinx.benchmark.gradlePlugin) implementation(libs.kotlinx.benchmark.gradlePlugin)
} }
tasks.withType<org.jetbrains.kotlin.gradle.tasks.KotlinCompile>().configureEach {
compilerOptions {
languageVersion.set(org.jetbrains.kotlin.gradle.dsl.KotlinVersion.KOTLIN_2_2)
apiVersion.set(org.jetbrains.kotlin.gradle.dsl.KotlinVersion.KOTLIN_2_2)
}
}
kotlin.version=2.3.20 kotlin.version=2.4.0-ST.0.4.0-0812
pluginManagement { pluginManagement {
repositories { repositories {
val composeRepoProperties = java.util.Properties().apply {
val propertiesFile = rootDir.parentFile.parentFile.resolve("local.properties")
if (propertiesFile.isFile) propertiesFile.inputStream().use(::load)
}
composeRepoProperties.getProperty("COMPOSE_REPO_URL")
?.takeIf(String::isNotBlank)
?.let { repoUrl ->
maven(repoUrl) {
credentials {
username = composeRepoProperties.getProperty("COMPOSE_REPO_USERNAME")
password = composeRepoProperties.getProperty("COMPOSE_REPO_KEY")
}
}
}
maven("https://maven.eazytec-cloud.com/nexus/repository/maven-inner/")
maven("https://maven.eazytec-cloud.com/nexus/repository/maven-public/")
mavenCentral { mavenCentral {
url = uri("https://cache-redirector.jetbrains.com/maven-central") url = uri("https://cache-redirector.jetbrains.com/maven-central")
} }
...@@ -9,6 +25,22 @@ pluginManagement { ...@@ -9,6 +25,22 @@ pluginManagement {
dependencyResolutionManagement { dependencyResolutionManagement {
repositories { repositories {
val composeRepoProperties = java.util.Properties().apply {
val propertiesFile = rootDir.parentFile.parentFile.resolve("local.properties")
if (propertiesFile.isFile) propertiesFile.inputStream().use(::load)
}
composeRepoProperties.getProperty("COMPOSE_REPO_URL")
?.takeIf(String::isNotBlank)
?.let { repoUrl ->
maven(repoUrl) {
credentials {
username = composeRepoProperties.getProperty("COMPOSE_REPO_USERNAME")
password = composeRepoProperties.getProperty("COMPOSE_REPO_KEY")
}
}
}
maven("https://maven.eazytec-cloud.com/nexus/repository/maven-inner/")
maven("https://maven.eazytec-cloud.com/nexus/repository/maven-public/")
maven("https://packages.jetbrains.team/maven/p/cmp/dev") { maven("https://packages.jetbrains.team/maven/p/cmp/dev") {
content { content {
includeGroupByRegex("org.jetbrains.compose.internal.*") includeGroupByRegex("org.jetbrains.compose.internal.*")
......
...@@ -30,6 +30,9 @@ abstract class CompileSkikoCppTask() : AbstractSkikoNativeToolTask() { ...@@ -30,6 +30,9 @@ abstract class CompileSkikoCppTask() : AbstractSkikoNativeToolTask() {
@get:Internal @get:Internal
abstract val sourceRoots: ListProperty<Directory> abstract val sourceRoots: ListProperty<Directory>
@get:Input
abstract val excludePatterns: ListProperty<String>
@get:Input @get:Input
abstract val compiler: Property<String> abstract val compiler: Property<String>
...@@ -43,7 +46,10 @@ abstract class CompileSkikoCppTask() : AbstractSkikoNativeToolTask() { ...@@ -43,7 +46,10 @@ abstract class CompileSkikoCppTask() : AbstractSkikoNativeToolTask() {
val sources = project.objects.fileCollection() val sources = project.objects.fileCollection()
for (sourceRoot in sourceRoots.get()) { for (sourceRoot in sourceRoots.get()) {
for (srcExtension in srcExtensions) { for (srcExtension in srcExtensions) {
sources.from(project.fileTree(sourceRoot) { include("**/*.$srcExtension") }) sources.from(project.fileTree(sourceRoot) {
include("**/*.$srcExtension")
exclude(excludePatterns.getOrElse(emptyList()))
})
} }
} }
sources sources
......
...@@ -7,6 +7,9 @@ enum class SkiaTarget( ...@@ -7,6 +7,9 @@ enum class SkiaTarget(
MACOS("macos", listOf("-P${SkikoGradleProperties.AWT_ENABLED}=true")), MACOS("macos", listOf("-P${SkikoGradleProperties.AWT_ENABLED}=true")),
WINDOWS("windows", listOf("-P${SkikoGradleProperties.AWT_ENABLED}=true")), WINDOWS("windows", listOf("-P${SkikoGradleProperties.AWT_ENABLED}=true")),
LINUX("linux", listOf("-P${SkikoGradleProperties.AWT_ENABLED}=true")), LINUX("linux", listOf("-P${SkikoGradleProperties.AWT_ENABLED}=true")),
// region Ohos
OHOS("ohos", listOf("-P${SkikoGradleProperties.AWT_ENABLED}=false")),
// endregion
WASM("wasm", listOf("-P${SkikoGradleProperties.WASM_ENABLED}=true", "-P${SkikoGradleProperties.AWT_ENABLED}=false")); WASM("wasm", listOf("-P${SkikoGradleProperties.WASM_ENABLED}=true", "-P${SkikoGradleProperties.AWT_ENABLED}=false"));
private val Arch.gradleProperty: String private val Arch.gradleProperty: String
...@@ -29,6 +32,9 @@ enum class SkiaTarget( ...@@ -29,6 +32,9 @@ enum class SkiaTarget(
MACOS -> if (hostArch == Arch.Arm64) listOf(Arch.Arm64, Arch.X64) else listOf(Arch.X64) MACOS -> if (hostArch == Arch.Arm64) listOf(Arch.Arm64, Arch.X64) else listOf(Arch.X64)
WINDOWS -> listOf(hostArch) WINDOWS -> listOf(hostArch)
LINUX -> listOf(hostArch) LINUX -> listOf(hostArch)
// region Ohos
OHOS -> listOf(Arch.Arm64, Arch.X64)
// endregion
WASM -> listOf(hostArch) WASM -> listOf(hostArch)
} }
...@@ -48,9 +54,12 @@ enum class SkiaTarget( ...@@ -48,9 +54,12 @@ enum class SkiaTarget(
"macos" -> MACOS "macos" -> MACOS
"windows" -> WINDOWS "windows" -> WINDOWS
"linux" -> LINUX "linux" -> LINUX
// region Ohos
"ohos" -> OHOS
// endregion
"wasm" -> WASM "wasm" -> WASM
else -> throw IllegalArgumentException( else -> throw IllegalArgumentException(
"Unknown SKIA_TARGET: $target. Valid targets: ios, iosSim, macos, windows, linux, wasm" "Unknown SKIA_TARGET: $target. Valid targets: ios, iosSim, macos, windows, linux, ohos, wasm"
) )
} }
} }
......
...@@ -95,6 +95,7 @@ fun SkikoProjectContext.declareSkiaTasks() { ...@@ -95,6 +95,7 @@ fun SkikoProjectContext.declareSkiaTasks() {
"tvosSim" to listOf("arm64", "x64"), "tvosSim" to listOf("arm64", "x64"),
"wasm" to listOf("wasm"), "wasm" to listOf("wasm"),
"windows" to listOf("arm64", "x64"), "windows" to listOf("arm64", "x64"),
"ohos" to listOf("arm64", "x64"),
).forEach { (config, architectures) -> ).forEach { (config, architectures) ->
architectures.forEach { arch -> architectures.forEach { arch ->
val taskNameSuffix = joinToTitleCamelCase(config, arch) val taskNameSuffix = joinToTitleCamelCase(config, arch)
...@@ -221,8 +222,17 @@ val Project.supportNativeMac: Boolean ...@@ -221,8 +222,17 @@ val Project.supportNativeMac: Boolean
val Project.supportNativeLinux: Boolean val Project.supportNativeLinux: Boolean
get() = supportAllNative || findProperty(SkikoGradleProperties.NATIVE_LINUX) == "true" || isInIdea get() = supportAllNative || findProperty(SkikoGradleProperties.NATIVE_LINUX) == "true" || isInIdea
val Project.supportNativeOhos: Boolean
get() = findProperty(SkikoGradleProperties.NATIVE_OHOS) == "true"
val Project.supportNativeOhosArm64: Boolean
get() = supportNativeOhos || findProperty(SkikoGradleProperties.NATIVE_OHOS_ARM64) == "true"
val Project.supportNativeOhosX64: Boolean
get() = supportNativeOhos || findProperty(SkikoGradleProperties.NATIVE_OHOS_X64) == "true"
val Project.supportAnyNative: Boolean val Project.supportAnyNative: Boolean
get() = supportAllNative || supportAnyNativeIos || supportNativeMac || supportNativeLinux get() = supportAllNative || supportAnyNativeIos || supportNativeMac || supportNativeLinux || supportNativeOhosArm64 || supportNativeOhosX64
val Project.supportWeb: Boolean val Project.supportWeb: Boolean
get() = findProperty(SkikoGradleProperties.WASM_ENABLED) == "true" || isInIdea get() = findProperty(SkikoGradleProperties.WASM_ENABLED) == "true" || isInIdea
......
...@@ -25,6 +25,8 @@ val OS.validEnvs: List<TargetEnv> ...@@ -25,6 +25,8 @@ val OS.validEnvs: List<TargetEnv>
OS.Windows, OS.Android -> listOf(TargetEnv.JVM) OS.Windows, OS.Android -> listOf(TargetEnv.JVM)
OS.IOS, OS.TVOS -> listOf(TargetEnv.NATIVE) OS.IOS, OS.TVOS -> listOf(TargetEnv.NATIVE)
OS.Wasm -> listOf(TargetEnv.WASM) OS.Wasm -> listOf(TargetEnv.WASM)
// OHOS uses its locally-built Skia archive list instead of the upstream binary DSL.
OS.OHOS -> emptyList()
} }
class BinaryRegistry { class BinaryRegistry {
......
...@@ -37,7 +37,12 @@ internal abstract class AbstractArgBuilder : ArgBuilder { ...@@ -37,7 +37,12 @@ internal abstract class AbstractArgBuilder : ArgBuilder {
} }
override fun createArgFile(file: File): String { override fun createArgFile(file: File): String {
file.writeLines(args) // Clang response files split unquoted whitespace even when the response
// file itself was passed as one Gradle process argument. Quote paths
// here so paths such as "Android Studio.app" stay a single argument.
file.writeLines(args.map { arg ->
if (" " in arg && !arg.startsWith('"')) "\"$arg\"" else arg
})
return "@${escapePathIfNeeded(file)}" return "@${escapePathIfNeeded(file)}"
} }
...@@ -143,4 +148,4 @@ internal class VisualCppLinkerArgBuilder : BaseVisualStudioBuildToolsArgBuilder( ...@@ -143,4 +148,4 @@ internal class VisualCppLinkerArgBuilder : BaseVisualStudioBuildToolsArgBuilder(
super.addTransformedArgs(argName, value) super.addTransformedArgs(argName, value)
} }
} }
} }
\ No newline at end of file
...@@ -13,7 +13,8 @@ enum class OS( ...@@ -13,7 +13,8 @@ enum class OS(
MacOS("macos", arrayOf("-mmacosx-version-min=11.0")), MacOS("macos", arrayOf("-mmacosx-version-min=11.0")),
Wasm("wasm", arrayOf()), Wasm("wasm", arrayOf()),
IOS("ios", arrayOf()), IOS("ios", arrayOf()),
TVOS("tvos", arrayOf()) TVOS("tvos", arrayOf()),
OHOS("ohos", arrayOf())
; ;
val isWindows val isWindows
...@@ -36,7 +37,7 @@ val OS.isCompatibleWithHost: Boolean ...@@ -36,7 +37,7 @@ val OS.isCompatibleWithHost: Boolean
OS.Windows -> hostOs == OS.Windows OS.Windows -> hostOs == OS.Windows
OS.MacOS, OS.IOS, OS.TVOS -> hostOs == OS.MacOS OS.MacOS, OS.IOS, OS.TVOS -> hostOs == OS.MacOS
OS.Wasm -> true OS.Wasm -> true
OS.Android -> true OS.Android, OS.OHOS -> true
} }
fun compilerForTarget(os: OS, arch: Arch): String = fun compilerForTarget(os: OS, arch: Arch): String =
...@@ -49,7 +50,7 @@ fun compilerForTarget(os: OS, arch: Arch): String = ...@@ -49,7 +50,7 @@ fun compilerForTarget(os: OS, arch: Arch): String =
} }
OS.Android -> "clang++" OS.Android -> "clang++"
OS.Windows -> "clang-cl.exe" OS.Windows -> "clang-cl.exe"
OS.MacOS, OS.IOS, OS.TVOS -> "clang++" OS.MacOS, OS.IOS, OS.TVOS, OS.OHOS -> "clang++"
OS.Wasm -> if (Os.isFamily(Os.FAMILY_WINDOWS)) "emcc.bat" else "emcc" OS.Wasm -> if (Os.isFamily(Os.FAMILY_WINDOWS)) "emcc.bat" else "emcc"
} }
...@@ -58,7 +59,7 @@ fun linkerForTarget(os: OS, arch: Arch): String = ...@@ -58,7 +59,7 @@ fun linkerForTarget(os: OS, arch: Arch): String =
val OS.dynamicLibExt: String val OS.dynamicLibExt: String
get() = when (this) { get() = when (this) {
OS.Linux, OS.Android -> ".so" OS.Linux, OS.Android, OS.OHOS -> ".so"
OS.Windows -> ".dll" OS.Windows -> ".dll"
OS.MacOS, OS.IOS, OS.TVOS -> ".dylib" OS.MacOS, OS.IOS, OS.TVOS -> ".dylib"
OS.Wasm -> ".wasm" OS.Wasm -> ".wasm"
...@@ -271,6 +272,9 @@ object SkikoGradleProperties { ...@@ -271,6 +272,9 @@ object SkikoGradleProperties {
const val NATIVE_TVOS_X64 = "skiko.native.tvos.x64.enabled" const val NATIVE_TVOS_X64 = "skiko.native.tvos.x64.enabled"
const val NATIVE_MAC = "skiko.native.mac.enabled" const val NATIVE_MAC = "skiko.native.mac.enabled"
const val NATIVE_LINUX = "skiko.native.linux.enabled" const val NATIVE_LINUX = "skiko.native.linux.enabled"
const val NATIVE_OHOS = "skiko.native.ohos.enabled"
const val NATIVE_OHOS_ARM64 = "skiko.native.ohos.arm64.enabled"
const val NATIVE_OHOS_X64 = "skiko.native.ohos.x64.enabled"
} }
class SkikoArtifacts( class SkikoArtifacts(
......
...@@ -56,7 +56,65 @@ fun SkikoProjectContext.declarePublications() { ...@@ -56,7 +56,65 @@ fun SkikoProjectContext.declarePublications() {
ctx.configureAndroidPublication() ctx.configureAndroidPublication()
ctx.configurePomNames() ctx.configurePomNames()
//region Ohos
// Keep Android and the supported iOS targets in the root Gradle module metadata,
// but route those variants to the official m150 artifacts. Only the OHOS target
// artifacts are built and published by this fork.
ctx.configureUpstreamTargetRedirects()
//endregion
if (project.findProperty("skiko.native.ohos.fusionRenderer.enabled") == "true") {
val ohosArtifacts = artifacts
project.afterEvaluate {
publishing.publications.withType(MavenPublication::class.java).configureEach {
artifactId = when (name) {
"ohosArm64" -> ohosArtifacts.nativeArtifactIdFor(OS.OHOS, Arch.Arm64) + "-fusionrenderer"
"ohosX64" -> ohosArtifacts.nativeArtifactIdFor(OS.OHOS, Arch.X64) + "-fusionrenderer"
else -> artifactId
}
}
}
}
}
//region Ohos
private fun SkikoPublishingContext.configureUpstreamTargetRedirects() {
if (project.findProperty("skiko.upstream.redirect.enabled") != "true") return
val upstreamVersion = project.providers
.gradleProperty("skiko.upstream.redirect.version")
.orElse("0.150.1")
.get()
val redirectedPublicationNames = setOf(
"android",
"iosArm64",
"iosSimulatorArm64",
)
val redirectedMavenLocalTaskNames = setOf(
"publishAndroidPublicationToMavenLocal",
"publishIosArm64PublicationToMavenLocal",
"publishIosSimulatorArm64PublicationToMavenLocal",
)
publications {
withType(MavenPublication::class.java).configureEach {
if (name in redirectedPublicationNames) {
version = upstreamVersion
}
}
}
// These publications only provide coordinates for root-module `available-at`
// entries. Never publish local leaf artifacts under the official version.
project.tasks.configureEach {
if (name in redirectedMavenLocalTaskNames) {
enabled = false
}
}
} }
//endregion
private val SkikoPublishingContext.emptySourcesJar private val SkikoPublishingContext.emptySourcesJar
get() = project.tasks.registerOrGetTask<Jar>("emptySourcesJar") { get() = project.tasks.registerOrGetTask<Jar>("emptySourcesJar") {
......
...@@ -32,6 +32,10 @@ val skikoSourceSetHierarchyTemplate = KotlinHierarchyTemplate { ...@@ -32,6 +32,10 @@ val skikoSourceSetHierarchyTemplate = KotlinHierarchyTemplate {
} }
group("native") { group("native") {
//region Ohos
withOhos()
//endregion
group("linux") { group("linux") {
withLinux() withLinux()
} }
......
...@@ -17,7 +17,7 @@ internal class SymbolExtractor( ...@@ -17,7 +17,7 @@ internal class SymbolExtractor(
"Symbol extraction does not support ${type.name} symbols" "Symbol extraction does not support ${type.name} symbols"
} }
return when (os) { return when (os) {
OS.Linux, OS.Android, OS.MacOS, OS.IOS, OS.TVOS -> { OS.Linux, OS.Android, OS.OHOS, OS.MacOS, OS.IOS, OS.TVOS -> {
val nmCommand = nmCommand(type) val nmCommand = nmCommand(type)
val output = run(nmCommand.command, nmCommand.args + actualFiles.map { it.absolutePath }) val output = run(nmCommand.command, nmCommand.args + actualFiles.map { it.absolutePath })
parseNmPosix(output) parseNmPosix(output)
...@@ -44,7 +44,7 @@ internal class SymbolExtractor( ...@@ -44,7 +44,7 @@ internal class SymbolExtractor(
SymbolType.Undefined -> listOf("-P", "-u") SymbolType.Undefined -> listOf("-P", "-u")
SymbolType.DefinedGlobal -> when (os) { SymbolType.DefinedGlobal -> when (os) {
OS.MacOS, OS.IOS, OS.TVOS -> listOf("-P", "-g", "-U") OS.MacOS, OS.IOS, OS.TVOS -> listOf("-P", "-g", "-U")
OS.Linux, OS.Android -> listOf("-P", "-g", "--defined-only") OS.Linux, OS.Android, OS.OHOS -> listOf("-P", "-g", "--defined-only")
else -> error("nm symbol extraction does not support ${os.name} target") else -> error("nm symbol extraction does not support ${os.name} target")
} }
} }
......
...@@ -127,6 +127,9 @@ fun skiaPreprocessorFlags(os: OS, buildType: SkiaBuildType): Array<String> { ...@@ -127,6 +127,9 @@ fun skiaPreprocessorFlags(os: OS, buildType: SkiaBuildType): Array<String> {
OS.Android -> listOf( OS.Android -> listOf(
"-DSK_BUILD_FOR_ANDROID" "-DSK_BUILD_FOR_ANDROID"
) )
OS.OHOS -> listOf(
"-DSK_BUILD_FOR_OHOS"
)
} }
return (base + perOs).toTypedArray() return (base + perOs).toTypedArray()
......
...@@ -133,7 +133,7 @@ fun SkikoProjectContext.createCompileJvmBindingsTask( ...@@ -133,7 +133,7 @@ fun SkikoProjectContext.createCompileJvmBindingsTask(
"-fPIC" "-fPIC"
) )
} }
OS.Wasm, OS.IOS, OS.TVOS -> error("Should not reach here") OS.Wasm, OS.IOS, OS.TVOS, OS.OHOS -> error("Should not reach here")
} }
flags.set( flags.set(
...@@ -392,7 +392,7 @@ fun SkikoProjectContext.createLinkJvmBindings( ...@@ -392,7 +392,7 @@ fun SkikoProjectContext.createLinkJvmBindings(
osFlags = androidFlags.toTypedArray() osFlags = androidFlags.toTypedArray()
linker.set(project.androidClangFor(targetArch)) linker.set(project.androidClangFor(targetArch))
} }
OS.Wasm, OS.IOS, OS.TVOS -> { OS.Wasm, OS.IOS, OS.TVOS, OS.OHOS -> {
throw GradleException("This task shalln't be used with $targetOs") throw GradleException("This task shalln't be used with $targetOs")
} }
} }
...@@ -570,7 +570,7 @@ fun SkikoProjectContext.skikoRuntimeDirForTestsTask( ...@@ -570,7 +570,7 @@ fun SkikoProjectContext.skikoRuntimeDirForTestsTask(
additionalRuntimeLibraries.forEach { lib -> additionalRuntimeLibraries.forEach { lib ->
from(project.zipTree(lib.jarTask.flatMap { it.archiveFile })) from(project.zipTree(lib.jarTask.flatMap { it.archiveFile }))
} }
duplicatesStrategy = DuplicatesStrategy.WARN duplicatesStrategy = DuplicatesStrategy.WARN
destinationDir = project.layout.buildDirectory.dir("skiko-runtime-for-tests").get().asFile destinationDir = project.layout.buildDirectory.dir("skiko-runtime-for-tests").get().asFile
} }
......
...@@ -9,6 +9,7 @@ import SkikoProjectContext ...@@ -9,6 +9,7 @@ import SkikoProjectContext
import WriteCInteropDefFile import WriteCInteropDefFile
import compilerForTarget import compilerForTarget
import dsl.TargetEnv import dsl.TargetEnv
import dsl.ResolvedBinaryConfiguration
import hostArch import hostArch
import isCompatibleWithHost import isCompatibleWithHost
import joinToTitleCamelCase import joinToTitleCamelCase
...@@ -16,8 +17,10 @@ import mutableListOfLinkerOptions ...@@ -16,8 +17,10 @@ import mutableListOfLinkerOptions
import org.gradle.api.GradleException import org.gradle.api.GradleException
import org.gradle.api.Project import org.gradle.api.Project
import org.gradle.api.tasks.Exec import org.gradle.api.tasks.Exec
import org.gradle.api.tasks.Sync
import org.gradle.api.tasks.TaskProvider import org.gradle.api.tasks.TaskProvider
import org.gradle.kotlin.dsl.getByName import org.gradle.kotlin.dsl.getByName
import org.gradle.kotlin.dsl.named
import org.jetbrains.kotlin.gradle.dsl.KotlinMultiplatformExtension import org.jetbrains.kotlin.gradle.dsl.KotlinMultiplatformExtension
import org.jetbrains.kotlin.gradle.plugin.KotlinTarget import org.jetbrains.kotlin.gradle.plugin.KotlinTarget
import org.jetbrains.kotlin.gradle.plugin.mpp.KotlinNativeTarget import org.jetbrains.kotlin.gradle.plugin.mpp.KotlinNativeTarget
...@@ -29,6 +32,177 @@ import symbols.HideSkiaSymbolsTask ...@@ -29,6 +32,177 @@ import symbols.HideSkiaSymbolsTask
import java.io.File import java.io.File
import kotlin.collections.plus import kotlin.collections.plus
private fun ohosClangTarget(arch: Arch): String = when (arch) {
Arch.Arm64 -> "aarch64-linux-ohos"
Arch.X64 -> "x86_64-linux-ohos"
Arch.Wasm -> error("OHOS does not support Wasm")
}
private fun Project.ohosToolchainRoot(): File? =
(findProperty("ohos.llvm.dir")?.toString() ?: System.getenv("OHOS_LLVM"))
?.let(::File)
?.takeIf(File::isDirectory)
private fun Project.ohosSysroot(): File? =
(findProperty("ohos.sysroot")?.toString() ?: System.getenv("OHOS_SYSROOT"))
?.let(::File)
?.takeIf(File::isDirectory)
private fun Project.ohosCompiler(): String =
ohosToolchainRoot()?.resolve("bin/clang++")?.takeIf(File::canExecute)?.absolutePath ?: "clang++"
private fun Project.ohosToolchainFlags(): List<String> {
val toolchain = ohosToolchainRoot() ?: return emptyList()
val sysroot = ohosSysroot() ?: return emptyList()
val resourceDir = toolchain.resolve("lib/clang").listFiles()
?.filter(File::isDirectory)
?.maxByOrNull(File::getName)
?: error("Clang resource directory not found under ${toolchain.resolve("lib/clang")}")
return listOf(
"--sysroot=${sysroot.absolutePath}",
"-resource-dir=${resourceDir.absolutePath}",
"-isystem", toolchain.resolve("include/libcxx-ohos/include/c++/v1").absolutePath,
"-isystem", resourceDir.resolve("include").absolutePath,
"-DOHOS",
"-D__MUSL__",
"-D_LIBCPP_HAS_MUSL_LIBC",
"-D__BUILD_LINUX_WITH_CLANG",
"-D__GNU_SOURCE",
"-D_GNU_SOURCE",
)
}
private fun Project.isOhosFusionRendererEnabled(): Boolean =
findProperty("skiko.native.ohos.fusionRenderer.enabled") == "true"
private fun Project.ohRenderRoot(): File {
val configured = findProperty("ohrender.dir")?.toString()?.takeIf(String::isNotBlank)
?: System.getenv("OHRENDER_DIR")?.takeIf(String::isNotBlank)
?: projectDir.resolve("../../fusion-render").absolutePath
val configuredDir = File(configured)
val root = if (configuredDir.name == "OHRender") configuredDir else configuredDir.resolve("OHRender")
require(root.isDirectory) {
"OHRender directory not found: $root. Set -Pohrender.dir or OHRENDER_DIR to the OHRender repository."
}
return root
}
private fun ohRenderHeadersDirs(root: File): List<File> = listOf(
root,
root.resolve("include"),
root.resolve("include/codec"),
root.resolve("include/core"),
root.resolve("include/effects"),
root.resolve("include/pathops"),
root.resolve("include/utils"),
root.resolve("modules/skcms"),
root.resolve("modules/skparagraph/include"),
root.resolve("modules/skresources/include"),
root.resolve("modules/skshaper/include"),
root.resolve("modules/skunicode/include"),
root.resolve("modules/sksg/include"),
root.resolve("modules/svg/include"),
root.resolve("src"),
root.resolve("third_party/icu"),
root.resolve("third_party/expat/expat/lib"),
root.resolve("third_party/include"),
)
private fun SkikoProjectContext.prepareFusionBridgeSourcesTask(): TaskProvider<Exec> = with(project) {
val output = layout.buildDirectory.dir("fusionBridgeCompat").get().asFile
val syncTask = if ("syncFusionBridgeSources" in tasks.names) {
tasks.named<Sync>("syncFusionBridgeSources")
} else {
tasks.register("syncFusionBridgeSources", Sync::class.java) {
from(projectDir) {
include("src/commonMain/cpp/common/**")
include("src/nativeJsMain/cpp/**")
}
into(output)
}
}
return if ("prepareFusionBridgeSources" in tasks.names) {
tasks.named<Exec>("prepareFusionBridgeSources")
} else {
tasks.register("prepareFusionBridgeSources", Exec::class.java) {
dependsOn(syncTask)
//region ohos
// The bridge sources and exported ABI come from Skiko m150. This patch only
// translates m150 implementation details to the older OHRender C++ API.
val patch = projectDir.resolve("src/ohosMain/fusionRenderer/patches/m150-to-ohrender.patch")
//endregion
inputs.file(patch)
outputs.dir(output)
workingDir(output)
commandLine("/usr/bin/patch", "-p1", "--forward", "--input=${patch.absolutePath}")
}
}
}
fun SkikoProjectContext.compileOhRenderTask(os: OS, arch: Arch): TaskProvider<CompileSkikoCppTask> =
with(project) {
require(os == OS.OHOS)
val root = ohRenderRoot()
registerSkikoTask<CompileSkikoCppTask>("compileOhRender", os, arch) {
compiler.set(ohosCompiler())
buildTargetOS.set(os)
buildTargetArch.set(arch)
buildVariant.set(buildType)
buildSuffix.set("fusion-ohrender")
flags.set(listOf(
"-target", ohosClangTarget(arch),
*ohosToolchainFlags().toTypedArray(),
*buildType.clangFlags,
//region ohos
// Keep SkRefCnt's inline ABI consistent with the Fusion bridge. In particular,
// Release must define NDEBUG; otherwise OHRender emits debug destructors while
// the bridge emits release-mode internal_dispose(), causing a ref-count abort.
*buildType.flags,
//endregion
"-fPIC", "-frtti", "-fno-exceptions",
"-DSK_BUILD_FOR_OHOS", "-DSK_TRACE",
))
sourceRoots.set(listOf(
root.resolve("src"),
root.resolve("modules/skcms"),
root.resolve("modules/skparagraph"),
root.resolve("modules/skresources"),
root.resolve("modules/svg/src"),
root.resolve("modules/skshaper/src"),
).map { layout.dir(provider { it }).get() })
excludePatterns.set(listOf(
"**/SkOHAvifCodec.cpp",
"**/SkShaper_harfbuzz.cpp",
))
includeHeadersNonRecursive(ohRenderHeadersDirs(root))
}
}
fun SkikoProjectContext.archiveOhRenderTask(
os: OS,
arch: Arch,
compileTask: TaskProvider<CompileSkikoCppTask>,
): TaskProvider<Exec> = with(project) {
require(os == OS.OHOS)
val targetString = "${os.id}-${arch.id}"
registerSkikoTask<Exec>("archiveOhRender", os, arch) {
dependsOn(compileTask)
val objectFiles = fileTree(compileTask.flatMap { it.outDir }) { include("**/*.o") }
inputs.files(objectFiles)
val outDir = layout.buildDirectory.dir("nativeFusion/static/$targetString")
val output = outDir.map { it.file("ohrender-$targetString.a") }
outputs.file(output)
doFirst { outDir.get().asFile.mkdirs() }
workingDir = outDir.get().asFile
executable = System.getenv("OHOS_LLVM_AR")
?: ohosToolchainRoot()?.resolve("bin/llvm-ar")?.absolutePath
?: "llvm-ar"
argumentProviders.add {
listOf("-crs", output.get().asFile.absolutePath) + objectFiles.files.map(File::getAbsolutePath)
}
}
}
fun String.withSuffix(isUikitSim: Boolean = false) = fun String.withSuffix(isUikitSim: Boolean = false) =
this + if (isUikitSim) "Sim" else "" this + if (isUikitSim) "Sim" else ""
...@@ -53,19 +227,26 @@ fun Project.findXcodeSdkRoot(): String { ...@@ -53,19 +227,26 @@ fun Project.findXcodeSdkRoot(): String {
fun SkikoProjectContext.compileNativeBridgesTask( fun SkikoProjectContext.compileNativeBridgesTask(
os: OS, arch: Arch, isUikitSim: Boolean os: OS, arch: Arch, isUikitSim: Boolean
): TaskProvider<CompileSkikoCppTask> = with (this.project) { ): TaskProvider<CompileSkikoCppTask> = with (this.project) {
val skiaNativeDir = registerOrGetSkiaDirProvider(os, arch, isUikitSim = isUikitSim) val isFusion = os == OS.OHOS && isOhosFusionRendererEnabled()
val skiaNativeDir = if (isFusion) null else registerOrGetSkiaDirProvider(os, arch, isUikitSim = isUikitSim)
val actionName = "compileNativeBridges".withSuffix(isUikitSim = isUikitSim) val actionName = (if (isFusion) "compileFusionNativeBridges" else "compileNativeBridges")
.withSuffix(isUikitSim = isUikitSim)
val fusionBridgeSources = if (isFusion) prepareFusionBridgeSourcesTask() else null
return project.registerSkikoTask<CompileSkikoCppTask>(actionName, os, arch) { return project.registerSkikoTask<CompileSkikoCppTask>(actionName, os, arch) {
dependsOn(skiaNativeDir) skiaNativeDir?.let { dependsOn(it) }
val unpackedSkia = skiaNativeDir.get() fusionBridgeSources?.let { dependsOn(it) }
val nativeHeadersRoot = if (isFusion) ohRenderRoot() else skiaNativeDir!!.get()
compiler.set(compilerForTarget(os, arch)) compiler.set(if (os == OS.OHOS) ohosCompiler() else compilerForTarget(os, arch))
buildTargetOS.set(os) buildTargetOS.set(os)
if (isUikitSim) { if (isUikitSim) {
buildSuffix.set("sim") buildSuffix.set("sim")
} }
if (isFusion) {
buildSuffix.set("fusion")
}
buildTargetArch.set(arch) buildTargetArch.set(arch)
buildVariant.set(buildType) buildVariant.set(buildType)
...@@ -120,15 +301,16 @@ fun SkikoProjectContext.compileNativeBridgesTask( ...@@ -120,15 +301,16 @@ fun SkikoProjectContext.compileNativeBridgesTask(
} }
OS.MacOS -> { OS.MacOS -> {
compiler.set(project.appleToolchainExecutableOrDefault("clang++", compiler.get())) compiler.set(project.appleToolchainExecutableOrDefault("clang++", compiler.get()))
val archFlags = when (arch) {
Arch.Arm64 -> arrayOf("-arch", "arm64")
Arch.X64 -> arrayOf("-arch", "x86_64")
else -> error("Unexpected arch: $arch for $os")
}
flags.set(listOf( flags.set(listOf(
*project.appleMacOsSdkFlags().toTypedArray(), *project.appleMacOsSdkFlags().toTypedArray(),
*buildType.clangFlags, *buildType.clangFlags,
*skiaPreprocessorFlags(OS.MacOS, buildType), *skiaPreprocessorFlags(OS.MacOS, buildType),
when(arch) { *archFlags
Arch.Arm64 -> "-arch arm64"
Arch.X64 -> "-arch x86_64"
else -> error("Unexpected arch: $arch for $os")
}
)) ))
} }
OS.Linux -> { OS.Linux -> {
...@@ -152,16 +334,72 @@ fun SkikoProjectContext.compileNativeBridgesTask( ...@@ -152,16 +334,72 @@ fun SkikoProjectContext.compileNativeBridgesTask(
} }
flags.set(linuxFlags) flags.set(linuxFlags)
} }
OS.OHOS -> flags.set(listOf(
"-target", ohosClangTarget(arch),
*ohosToolchainFlags().toTypedArray(),
*buildType.clangFlags,
"-fPIC", if (isFusion) "-frtti" else "-fno-rtti", "-fno-exceptions",
"-DSK_BUILD_FOR_OHOS",
*skiaPreprocessorFlags(OS.OHOS, buildType),
*(if (isFusion) arrayOf(
"-DUSE_OHRENDER", "-USK_GANESH", "-USK_GL", "-USK_UNICODE_AVAILABLE"
) else emptyArray())
))
else -> throw GradleException("$os not yet supported") else -> throw GradleException("$os not yet supported")
} }
val srcDirs = projectDirs("src/commonMain/cpp/common", "src/nativeNativeJs/cpp", "src/nativeJsMain/cpp") + val sharedSrcDirs = if (isFusion) {
if (skiko.includeTestHelpers) projectDirs("src/nativeJsTest/cpp") else emptyList() val compatRoot = layout.buildDirectory.dir("fusionBridgeCompat").get().asFile
listOf(
compatRoot.resolve("src/commonMain/cpp/common"),
compatRoot.resolve("src/nativeJsMain/cpp"),
).map { layout.dir(provider { it }).get() }
} else {
projectDirs("src/commonMain/cpp/common", "src/nativeNativeJs/cpp", "src/nativeJsMain/cpp")
}
val srcDirs = sharedSrcDirs +
(if (os == OS.OHOS) projectDirs("src/ohosMain/cpp") else emptyList()) +
(if (os == OS.OHOS) projectDirs(
"src/ohosMain/" +
(if (project.findProperty("skiko.native.ohos.fusionRenderer.enabled") == "true")
"fusionRenderer" else "skia") + "/cpp"
) else emptyList()) +
(if (skiko.includeTestHelpers) projectDirs("src/nativeJsTest/cpp") else emptyList())
sourceRoots.set(srcDirs) sourceRoots.set(srcDirs)
includeHeadersNonRecursive(projectDir.resolve("src/nativeJsMain/cpp")) if (isFusion) {
includeHeadersNonRecursive(projectDir.resolve("src/commonMain/cpp/common/include")) excludePatterns.set(listOf(
includeHeadersNonRecursive(skiaHeadersDirs(unpackedSkia)) "**/DirectContext.cc",
"**/BackendRenderTarget.cc",
"**/BackendTexture.cc",
"**/RuntimeEffect.cc",
"**/RuntimeShaderBuilder.cc",
"**/render.cc",
"**/skottie/**",
"**/sksg/**",
"**/SVGCanvas.cc",
"**/GrGLMakeNativeInterface_ohos.cpp",
))
}
if (isFusion) {
val compatRoot = layout.buildDirectory.dir("fusionBridgeCompat").get().asFile
includeHeadersNonRecursive(compatRoot.resolve("src/nativeJsMain/cpp"))
includeHeadersNonRecursive(compatRoot.resolve("src/commonMain/cpp/common/include"))
} else {
includeHeadersNonRecursive(projectDir.resolve("src/nativeJsMain/cpp"))
includeHeadersNonRecursive(projectDir.resolve("src/commonMain/cpp/common/include"))
}
if (os == OS.OHOS) includeHeadersNonRecursive(
projectDir.resolve(
"src/ohosMain/" +
(if (project.findProperty("skiko.native.ohos.fusionRenderer.enabled") == "true")
"fusionRenderer" else "skia") + "/cpp"
)
)
includeHeadersNonRecursive(
if (isFusion) ohRenderHeadersDirs(nativeHeadersRoot) else skiaHeadersDirs(nativeHeadersRoot)
)
} }
} }
...@@ -192,19 +430,67 @@ fun configureCinterop( ...@@ -192,19 +430,67 @@ fun configureCinterop(
} }
} }
private fun ohosSkiaStaticLibraries(
skiaDir: String,
targetString: String,
buildType: SkiaBuildType,
): List<String> {
val skiaBinSubdir = "$skiaDir/out/${buildType.id}-$targetString"
val libraries = if (targetString.startsWith("ohos-")) listOf(
"libskresources.a",
"libskparagraph.a",
"libskia.a",
"libjsonreader.a",
"libskottie.a",
"libsksg.a",
"libskunicode_core.a",
"libskunicode_icu.a",
"libskshaper.a",
"libskcms.a",
"libwuffs.a",
) else listOf(
"libskresources.a",
"libskparagraph.a",
"libskia.a",
"libskia_ganesh_ext.a",
"libicu.a",
"libjsonreader.a",
"libskottie.a",
"libsvg.a",
"libpng.a",
"libwebp_sse41.a",
"libsksg.a",
"libskunicode_core.a",
"libskunicode_icu.a",
"libwebp.a",
"libdng_sdk.a",
"libpiex.a",
"libharfbuzz.a",
"libexpat.a",
"libzlib.a",
"libjpeg.a",
"libskshaper.a"
)
return libraries.map {
"$skiaBinSubdir/$it"
}
}
fun SkikoProjectContext.configureNativeTarget(os: OS, arch: Arch, target: KotlinNativeTarget) = with(this.project) { fun SkikoProjectContext.configureNativeTarget(os: OS, arch: Arch, target: KotlinNativeTarget) = with(this.project) {
if (!os.isCompatibleWithHost) return if (!os.isCompatibleWithHost) return
target.generateVersion(os, arch, skiko) target.generateVersion(os, arch, skiko)
val isUikitSim = target.isUikitSimulator() val isUikitSim = target.isUikitSimulator()
val isFusion = os == OS.OHOS && isOhosFusionRendererEnabled()
val targetString = "${os.idWithSuffix(isUikitSim = isUikitSim)}-${arch.id}" val targetString = "${os.idWithSuffix(isUikitSim = isUikitSim)}-${arch.id}"
val bridgeArtifactKey = if (isFusion) "fusion-$targetString" else targetString
val unzipper = if (isFusion) null else registerOrGetSkiaDirProvider(os, arch, isUikitSim)
val skiaDir = unzipper?.get()?.absolutePath
val unzipper = registerOrGetSkiaDirProvider(os, arch, isUikitSim) val bridgesLibrary = layout.buildDirectory.file(
val unpackedSkia = unzipper.get() "nativeBridges/static/$bridgeArtifactKey/$nativeBridgesLibPrefix-$bridgeArtifactKey.a"
val skiaDir = unpackedSkia.absolutePath )
val bridgesLibrary = layout.buildDirectory.file("nativeBridges/static/$targetString/$nativeBridgesLibPrefix-$targetString.a")
val bridgesLibraryPath = bridgesLibrary.get().asFile.absolutePath val bridgesLibraryPath = bridgesLibrary.get().asFile.absolutePath
// For iOS/tvOS we patch every library so that public Skia symbols are // For iOS/tvOS we patch every library so that public Skia symbols are
...@@ -215,10 +501,29 @@ fun SkikoProjectContext.configureNativeTarget(os: OS, arch: Arch, target: Kotlin ...@@ -215,10 +501,29 @@ fun SkikoProjectContext.configureNativeTarget(os: OS, arch: Arch, target: Kotlin
val requiresSymbolPatching = os == OS.IOS || os == OS.TVOS val requiresSymbolPatching = os == OS.IOS || os == OS.TVOS
val patchedLibsDir = layout.buildDirectory.dir("nativeBridges/patched/$targetString").get().asFile val patchedLibsDir = layout.buildDirectory.dir("nativeBridges/patched/$targetString").get().asFile
val skiaBinDir = "$skiaDir/out/${buildType.id}-$targetString" val ohRenderCompile = if (isFusion) compileOhRenderTask(os, arch) else null
val resolvedBinaryInputs = resolveBinaryInputs(os, arch, TargetEnv.NATIVE, skiaBinDir) val ohRenderArchive = ohRenderCompile?.let { archiveOhRenderTask(os, arch, it) }
val nativeArchives = resolvedBinaryInputs.staticArchivePaths.distinct() val skiaBinDir = skiaDir?.let { "$it/out/${buildType.id}-$targetString" }
val allLibraries = if (requiresSymbolPatching) { val resolvedBinaryInputs = if (isFusion || os == OS.OHOS) {
ResolvedBinaryConfiguration()
} else {
resolveBinaryInputs(os, arch, TargetEnv.NATIVE, skiaBinDir!!)
}
val nativeArchives = if (os == OS.OHOS && !isFusion) {
ohosSkiaStaticLibraries(skiaDir!!, targetString, buildType)
} else {
resolvedBinaryInputs.staticArchivePaths.distinct()
}
val allLibraries = if (isFusion) {
val ohRenderRoot = ohRenderRoot()
val archPrefix = if (arch == Arch.Arm64) "aarch64" else "x86_64"
listOf(
bridgesLibraryPath,
layout.buildDirectory.file("nativeFusion/static/$targetString/ohrender-$targetString.a")
.get().asFile.absolutePath,
ohRenderRoot.resolve("third_party/libs/${archPrefix}_libexpat.a").absolutePath,
)
} else if (requiresSymbolPatching) {
nativeArchives.map { lib -> nativeArchives.map { lib ->
"${patchedLibsDir.absolutePath}/${File(lib).name}" "${patchedLibsDir.absolutePath}/${File(lib).name}"
} + "${patchedLibsDir.absolutePath}/$nativeBridgesLibPrefix-$targetString.a" } + "${patchedLibsDir.absolutePath}/$nativeBridgesLibPrefix-$targetString.a"
...@@ -290,6 +595,19 @@ fun SkikoProjectContext.configureNativeTarget(os: OS, arch: Arch, target: Kotlin ...@@ -290,6 +595,19 @@ fun SkikoProjectContext.configureNativeTarget(os: OS, arch: Arch, target: Kotlin
options.add("--version-script=${hiddenSymbolsFile.get().asFile.absolutePath}") options.add("--version-script=${hiddenSymbolsFile.get().asFile.absolutePath}")
mutableListOfLinkerOptions(options) mutableListOfLinkerOptions(options)
} }
OS.OHOS -> mutableListOfLinkerOptions(if (isFusion) listOf(
"-target", ohosClangTarget(arch),
"-lace_napi.z", "-lhilog_ndk.z", "-lnative_drawing",
"-lpixelmap", "-lpixelmap_ndk.z", "-limage_source",
"-lhitrace_ndk.z", "-luv", "-limage_packer",
"-ldeviceinfo_ndk.z", "-lace_ndk.z", "-larkui_ndk", "-lz",
"-lrawfile.z", "-lohresmgr",
) else listOf(
"-target", ohosClangTarget(arch),
"-L$skiaBinDir", "-lGLESv3", "-lEGL", "-ldl", "-lhilog",
"-lrawfile.z", "-lohresmgr",
"$skiaBinDir/libskia.a", "$skiaBinDir/libskparagraph.a", "$skiaBinDir/libskshaper.a"
))
else -> mutableListOf() else -> mutableListOf()
} }
if (skiko.includeTestHelpers) { if (skiko.includeTestHelpers) {
...@@ -319,7 +637,8 @@ fun SkikoProjectContext.configureNativeTarget(os: OS, arch: Arch, target: Kotlin ...@@ -319,7 +637,8 @@ fun SkikoProjectContext.configureNativeTarget(os: OS, arch: Arch, target: Kotlin
val crossCompileTask = compileNativeBridgesTask(os, arch, isUikitSim = isUikitSim) val crossCompileTask = compileNativeBridgesTask(os, arch, isUikitSim = isUikitSim)
// TODO: move to LinkSkikoTask. // TODO: move to LinkSkikoTask.
val actionName = "linkNativeBridges".withSuffix(isUikitSim = isUikitSim) val actionName = (if (isFusion) "linkFusionNativeBridges" else "linkNativeBridges")
.withSuffix(isUikitSim = isUikitSim)
val linkTask = project.registerSkikoTask<Exec>(actionName, os, arch) { val linkTask = project.registerSkikoTask<Exec>(actionName, os, arch) {
dependsOn(crossCompileTask) dependsOn(crossCompileTask)
val objectFilesDir = crossCompileTask.map { it.outDir.get() } val objectFilesDir = crossCompileTask.map { it.outDir.get() }
...@@ -327,12 +646,15 @@ fun SkikoProjectContext.configureNativeTarget(os: OS, arch: Arch, target: Kotlin ...@@ -327,12 +646,15 @@ fun SkikoProjectContext.configureNativeTarget(os: OS, arch: Arch, target: Kotlin
include("**/*.o") include("**/*.o")
} }
inputs.files(objectFiles) inputs.files(objectFiles)
val outDir = layout.buildDirectory.dir("nativeBridges/static/$targetString").get().asFile val outDir = layout.buildDirectory.dir("nativeBridges/static/$bridgeArtifactKey").get().asFile
val staticLib = "$nativeBridgesLibPrefix-$targetString.a" val staticLib = "$nativeBridgesLibPrefix-$bridgeArtifactKey.a"
workingDir = outDir workingDir = outDir
when (os) { when (os) {
OS.Linux -> { OS.Linux, OS.OHOS -> {
executable = if (arch == Arch.Arm64 && hostArch != Arch.Arm64) "aarch64-linux-gnu-ar" else "ar" executable = if (arch == Arch.Arm64 && hostArch != Arch.Arm64) "aarch64-linux-gnu-ar" else "ar"
if (os == OS.OHOS) executable = System.getenv("OHOS_LLVM_AR")
?: ohosToolchainRoot()?.resolve("bin/llvm-ar")?.absolutePath
?: "llvm-ar"
argumentProviders.add { listOf("-crs", staticLib) } argumentProviders.add { listOf("-crs", staticLib) }
} }
OS.MacOS, OS.IOS, OS.TVOS -> { OS.MacOS, OS.IOS, OS.TVOS -> {
...@@ -350,7 +672,7 @@ fun SkikoProjectContext.configureNativeTarget(os: OS, arch: Arch, target: Kotlin ...@@ -350,7 +672,7 @@ fun SkikoProjectContext.configureNativeTarget(os: OS, arch: Arch, target: Kotlin
val compilationDependency = if (requiresSymbolPatching) { val compilationDependency = if (requiresSymbolPatching) {
val patchActionName = "patchSkikoSymbols".withSuffix(isUikitSim = isUikitSim) val patchActionName = "patchSkikoSymbols".withSuffix(isUikitSim = isUikitSim)
project.registerSkikoTask<PatchSkiaSymbolsTask>(patchActionName, os, arch) { project.registerSkikoTask<PatchSkiaSymbolsTask>(patchActionName, os, arch) {
dependsOn(unzipper) dependsOn(unzipper!!)
dependsOn(linkTask) dependsOn(linkTask)
skiaLibs.set(nativeArchives.map { File(it) }) skiaLibs.set(nativeArchives.map { File(it) })
symbolSourceLibs.set(emptyList()) symbolSourceLibs.set(emptyList())
...@@ -362,13 +684,14 @@ fun SkikoProjectContext.configureNativeTarget(os: OS, arch: Arch, target: Kotlin ...@@ -362,13 +684,14 @@ fun SkikoProjectContext.configureNativeTarget(os: OS, arch: Arch, target: Kotlin
} }
hideSkiaSymbols.configure { hideSkiaSymbols.configure {
dependsOn(unzipper) unzipper?.let { dependsOn(it) }
dependsOn(compilationDependency) dependsOn(compilationDependency)
} }
target.compilations.all { target.compilations.all {
compileTaskProvider.configure { compileTaskProvider.configure {
dependsOn(hideSkiaSymbols) dependsOn(hideSkiaSymbols)
ohRenderArchive?.let { dependsOn(it) }
} }
} }
} }
......
...@@ -5,11 +5,24 @@ kotlin.mpp.applyDefaultHierarchyTemplate=false ...@@ -5,11 +5,24 @@ kotlin.mpp.applyDefaultHierarchyTemplate=false
kotlin.mpp.enableCInteropCommonization=true kotlin.mpp.enableCInteropCommonization=true
kotlin.js.yarn.lock.file=false kotlin.js.yarn.lock.file=false
deploy.version=0.0.0 deploy.version=0.150.1-0.1.0-01
deploy.release=true
# a tag from https://github.com/JetBrains/skia/releases # a tag from https://github.com/JetBrains/skia/releases
dependencies.skia=m150-1f14f1166a dependencies.skia=m150-1f14f1166a
# OHOS is enabled explicitly by the command line so the default build remains portable.
skiko.native.ohos.enabled=false
skiko.native.ohos.arm64.enabled=false
skiko.native.ohos.x64.enabled=false
skiko.native.ohos.fusionRenderer.enabled=false
# Official m150 version used by Android/iOS `available-at` redirects in the
# fork root module. The redirect is enabled explicitly by build-ohos.sh.
skiko.upstream.redirect.version=0.150.1
skiko.upstream.kotlin.version=2.3.20
skiko.upstream.coroutines.version=1.8.0
# a tag from https://github.com/JetBrains/angle-pack # a tag from https://github.com/JetBrains/angle-pack
dependencies.angle=ec4d8f8e4d dependencies.angle=ec4d8f8e4d
......
dependencyResolutionManagement { dependencyResolutionManagement {
repositories {
val composeRepoProperties = java.util.Properties().apply {
val propertiesFile = rootDir.parentFile.resolve("local.properties")
if (propertiesFile.isFile) propertiesFile.inputStream().use(::load)
}
composeRepoProperties.getProperty("COMPOSE_REPO_URL")
?.takeIf(String::isNotBlank)
?.let { repoUrl ->
maven(repoUrl) {
credentials {
username = composeRepoProperties.getProperty("COMPOSE_REPO_USERNAME")
password = composeRepoProperties.getProperty("COMPOSE_REPO_KEY")
}
}
}
maven("https://maven.eazytec-cloud.com/nexus/repository/maven-inner/")
maven("https://maven.eazytec-cloud.com/nexus/repository/maven-public/")
mavenCentral {
url = uri("https://cache-redirector.jetbrains.com/maven-central")
}
google()
}
versionCatalogs { versionCatalogs {
register("libs") { register("libs") {
from(files("../dependencies.toml")) from(files("../dependencies.toml"))
...@@ -8,6 +30,22 @@ dependencyResolutionManagement { ...@@ -8,6 +30,22 @@ dependencyResolutionManagement {
pluginManagement { pluginManagement {
repositories { repositories {
val composeRepoProperties = java.util.Properties().apply {
val propertiesFile = rootDir.parentFile.resolve("local.properties")
if (propertiesFile.isFile) propertiesFile.inputStream().use(::load)
}
composeRepoProperties.getProperty("COMPOSE_REPO_URL")
?.takeIf(String::isNotBlank)
?.let { repoUrl ->
maven(repoUrl) {
credentials {
username = composeRepoProperties.getProperty("COMPOSE_REPO_USERNAME")
password = composeRepoProperties.getProperty("COMPOSE_REPO_KEY")
}
}
}
maven("https://maven.eazytec-cloud.com/nexus/repository/maven-inner/")
maven("https://maven.eazytec-cloud.com/nexus/repository/maven-public/")
mavenCentral { mavenCentral {
url = uri("https://cache-redirector.jetbrains.com/maven-central") url = uri("https://cache-redirector.jetbrains.com/maven-central")
} }
......
...@@ -9,11 +9,15 @@ ...@@ -9,11 +9,15 @@
#include "ports/SkTypeface_win.h" #include "ports/SkTypeface_win.h"
#endif #endif
#if (defined(SK_BUILD_FOR_UNIX) || defined(SK_BUILD_FOR_LINUX)) && !defined(SKIKO_WASM) #if (defined(SK_BUILD_FOR_UNIX) || defined(SK_BUILD_FOR_LINUX)) && !defined(SKIKO_WASM) && !defined(SK_BUILD_FOR_OHOS)
#include "ports/SkFontMgr_fontconfig.h" #include "ports/SkFontMgr_fontconfig.h"
#include "ports/SkFontScanner_FreeType.h" #include "ports/SkFontScanner_FreeType.h"
#endif #endif
#ifdef SK_BUILD_FOR_OHOS
SK_API sk_sp<SkFontMgr> SkFontMgr_New_OHOS(const char* path = nullptr);
#endif
#ifdef SK_BUILD_FOR_ANDROID #ifdef SK_BUILD_FOR_ANDROID
#include "ports/SkFontMgr_android.h" #include "ports/SkFontMgr_android.h"
#include "ports/SkFontScanner_FreeType.h" #include "ports/SkFontScanner_FreeType.h"
...@@ -37,7 +41,7 @@ sk_sp<SkFontMgr> SkFontMgrSkikoDefault() { ...@@ -37,7 +41,7 @@ sk_sp<SkFontMgr> SkFontMgrSkikoDefault() {
return SkFontMgr_New_DirectWrite(); return SkFontMgr_New_DirectWrite();
#endif #endif
#if (defined(SK_BUILD_FOR_UNIX) || defined(SK_BUILD_FOR_LINUX)) && !defined(SKIKO_WASM) #if (defined(SK_BUILD_FOR_UNIX) || defined(SK_BUILD_FOR_LINUX)) && !defined(SKIKO_WASM) && !defined(SK_BUILD_FOR_OHOS)
return SkFontMgr_New_FontConfig(nullptr, SkFontScanner_Make_FreeType()); return SkFontMgr_New_FontConfig(nullptr, SkFontScanner_Make_FreeType());
#endif #endif
...@@ -45,6 +49,10 @@ sk_sp<SkFontMgr> SkFontMgrSkikoDefault() { ...@@ -45,6 +49,10 @@ sk_sp<SkFontMgr> SkFontMgrSkikoDefault() {
return SkFontMgr_New_Android(nullptr, SkFontScanner_Make_FreeType()); return SkFontMgr_New_Android(nullptr, SkFontScanner_Make_FreeType());
#endif #endif
#ifdef SK_BUILD_FOR_OHOS
return SkFontMgr_New_OHOS();
#endif
#ifdef SKIKO_WASM #ifdef SKIKO_WASM
sk_sp<SkData> embeddedFontData = sk_sp<SkData> embeddedFontData =
SkData::MakeWithoutCopy(SK_EMBEDDED_FONTS.entries[0].data, SK_EMBEDDED_FONTS.entries[0].size); SkData::MakeWithoutCopy(SK_EMBEDDED_FONTS.entries[0].data, SK_EMBEDDED_FONTS.entries[0].size);
......
...@@ -10,6 +10,10 @@ open class Canvas internal constructor(ptr: NativePointer, managed: Boolean, int ...@@ -10,6 +10,10 @@ open class Canvas internal constructor(ptr: NativePointer, managed: Boolean, int
init { init {
staticLoad() staticLoad()
} }
//region Ohos
fun makeFromNative(ptr: NativePointer): Canvas = Canvas(ptr, false, Unit)
//endregion
} }
/** /**
* *
...@@ -1857,4 +1861,4 @@ internal inline fun Canvas.runRestoringState(block: Canvas.() -> Unit) { ...@@ -1857,4 +1861,4 @@ internal inline fun Canvas.runRestoringState(block: Canvas.() -> Unit) {
} finally { } finally {
restoreToCount(restoreCount) restoreToCount(restoreCount)
} }
} }
\ No newline at end of file
...@@ -7,6 +7,9 @@ enum class OS(val id: String) { ...@@ -7,6 +7,9 @@ enum class OS(val id: String) {
MacOS("macos"), MacOS("macos"),
Ios("ios"), Ios("ios"),
Tvos("tvos"), Tvos("tvos"),
//region ohos
Ohos("ohos"),
//endregion
Unknown("unknown") Unknown("unknown")
; ;
......
/*
* Copyright 2025-2025 Huawei Technologies Co., Ltd. and Kotlin Programming Language contributors.
* Use of this source code is governed by the Apache 2.0 license that can be found in the license/LICENSE.txt file.
*/
#ifdef SK_BUILD_FOR_OHOS
#include "include/gpu/ganesh/gl/GrGLAssembleInterface.h"
#include "include/gpu/ganesh/gl/GrGLInterface.h"
#include "include/gpu/ganesh/gl/GrGLExtensions.h"
#include "src/gpu/ganesh/gl/GrGLCoreFunctions.h"
#include "src/gpu/ganesh/gl/GrGLUtil.h"
#include <cstring>
#include <dlfcn.h>
#include <EGL/egl.h>
#include <GLES3/gl3.h>
static GrGLFuncPtr ohos_get_gl_proc(void* ctx, const char name[])
{
(void)ctx;
if (!name) {
return nullptr;
}
// Prefer EGL for extensions, but fall back to dlsym for core GLES symbols.
// This keeps core entry points like glGetString available even when
// eglGetProcAddress only resolves extensions.
GrGLFuncPtr proc = eglGetProcAddress(name);
if (proc) {
return proc;
}
return reinterpret_cast<GrGLFuncPtr>(dlsym(RTLD_DEFAULT, name));
}
sk_sp<const GrGLInterface> GrGLMakeNativeInterface_OHOS()
{
sk_sp<const GrGLInterface> interface = GrGLMakeAssembledInterface(nullptr, ohos_get_gl_proc);
if (!interface) {
return nullptr;
}
// Initialize GL extensions - this is CRITICAL for validation
// We need to cast away const to initialize extensions
GrGLInterface* mutableInterface = const_cast<GrGLInterface*>(interface.get());
if (!mutableInterface->fExtensions.isInitialized()) {
if (!mutableInterface->fExtensions.init(
mutableInterface->fStandard,
mutableInterface->fFunctions.fGetString,
mutableInterface->fFunctions.fGetStringi,
mutableInterface->fFunctions.fGetIntegerv)) {
return nullptr;
}
}
return interface;
}
#endif // SK_BUILD_FOR_OHOS
/*
* Copyright 2025-2025 Huawei Technologies Co., Ltd. and Kotlin Programming Language contributors.
* Use of this source code is governed by the Apache 2.0 license that can be found in the license/LICENSE.txt file.
*/
#pragma once
#ifdef SK_BUILD_FOR_OHOS
#include "ganesh/gl/GrGLInterface.h"
// Declared in GrGLMakeNativeInterface_ohos.cpp
sk_sp<const GrGLInterface> GrGLMakeNativeInterface_OHOS();
#endif // SK_BUILD_FOR_OHOS
...@@ -81,7 +81,9 @@ SKIKO_EXPORT KBoolean org_jetbrains_skia_Region__1nSetRects(KNativePointer ptr, ...@@ -81,7 +81,9 @@ SKIKO_EXPORT KBoolean org_jetbrains_skia_Region__1nSetRects(KNativePointer ptr,
std::vector<SkIRect> rects(count); std::vector<SkIRect> rects(count);
for (int i = 0, off = 0; i < count; i++, off += 4) for (int i = 0, off = 0; i < count; i++, off += 4)
rects[i] = {coords[off], coords[off+1], coords[off+2], coords[off+3]}; rects[i] = {coords[off], coords[off+1], coords[off+2], coords[off+3]};
return instance->setRects({rects.data(), count}); // Keep the pointer/count overload: OHRender still exposes this signature, while m150 Skia
// retains it as a compatibility wrapper around the SkSpan overload.
return instance->setRects(rects.data(), count);
} }
SKIKO_EXPORT KBoolean org_jetbrains_skia_Region__1nSetRegion(KNativePointer ptr, KNativePointer regionPtr) { SKIKO_EXPORT KBoolean org_jetbrains_skia_Region__1nSetRegion(KNativePointer ptr, KNativePointer regionPtr) {
......
...@@ -237,4 +237,3 @@ private class RunHandlerImpl(val runHandler: RunHandler) : Managed(RunHandler_nC ...@@ -237,4 +237,3 @@ private class RunHandlerImpl(val runHandler: RunHandler) : Managed(RunHandler_nC
val PTR = RunHandler_nGetFinalizer() val PTR = RunHandler_nGetFinalizer()
} }
} }
...@@ -10,6 +10,9 @@ actual val hostOs: OS by lazy { ...@@ -10,6 +10,9 @@ actual val hostOs: OS by lazy {
OsFamily.WINDOWS -> OS.Windows OsFamily.WINDOWS -> OS.Windows
OsFamily.IOS -> OS.Ios OsFamily.IOS -> OS.Ios
OsFamily.TVOS -> OS.Tvos OsFamily.TVOS -> OS.Tvos
//region ohos
OsFamily.OHOS -> OS.Ohos
//endregion
else -> throw Error("Unsupported OS ${Platform.osFamily}") else -> throw Error("Unsupported OS ${Platform.osFamily}")
} }
} }
...@@ -28,4 +31,4 @@ actual val hostId by lazy { ...@@ -28,4 +31,4 @@ actual val hostId by lazy {
} }
actual val kotlinBackend: KotlinBackend actual val kotlinBackend: KotlinBackend
get() = KotlinBackend.Native get() = KotlinBackend.Native
\ No newline at end of file
/*
* Copyright (c) 2026 Huawei Device Co., Ltd.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#include <algorithm>
#include <cmath>
#include <cstdint>
#include <cstdlib>
#include <limits>
#include <memory>
#include <tuple>
#include <utility>
#include "SkCodec.h"
#include "SkData.h"
#include "SkImage.h"
#include "SkRect.h"
#include "common.h"
#include "rawfile/raw_file_manager.h"
#include "resourcemanager/ohresmgr.h"
namespace {
constexpr KInt kDefaultScale = 0;
constexpr KInt kCropScale = 1;
constexpr KInt kFitScale = 2;
constexpr KInt kInsideScale = 3;
constexpr KInt kFillBoundsScale = 4;
constexpr KInt kFillWidthScale = 5;
constexpr KInt kFillHeightScale = 6;
constexpr KInt kNoneScale = 7;
struct TargetDecodePlan {
int width;
int height;
bool crop;
SkIRect subset;
};
void ReleaseMallocData(const void* data, void*) {
std::free(const_cast<void*>(data));
}
int CeilToBucket(int value, int step) {
if (value <= 0) return 0;
const int safeStep = step > 0 ? step : 16;
const int64_t bucket =
((static_cast<int64_t>(value) + safeStep - 1) / safeStep) * safeStep;
return static_cast<int>(std::min<int64_t>(bucket, std::numeric_limits<int>::max()));
}
int CeilScaledDimension(int source, double scale) {
if (!std::isfinite(scale) || scale <= 0.0) return 1;
return std::max(1, static_cast<int>(std::ceil(std::min<double>(source * scale, source))));
}
bool BuildTargetDecodePlan(const SkImageInfo& sourceInfo,
KInt targetWidth,
KInt targetHeight,
KInt contentScaleMode,
KInt decodeSizeBucket,
TargetDecodePlan* result) {
const int sourceWidth = sourceInfo.width();
const int sourceHeight = sourceInfo.height();
if (result == nullptr || sourceWidth <= 0 || sourceHeight <= 0 ||
targetWidth <= 0 || targetHeight <= 0) {
return false;
}
const double widthScale = static_cast<double>(targetWidth) / sourceWidth;
const double heightScale = static_cast<double>(targetHeight) / sourceHeight;
double scaleX = widthScale;
double scaleY = heightScale;
switch (contentScaleMode) {
case kCropScale: {
const double scale = std::max(widthScale, heightScale);
scaleX = scale;
scaleY = scale;
break;
}
case kFitScale: {
const double scale = std::min(widthScale, heightScale);
scaleX = scale;
scaleY = scale;
break;
}
case kInsideScale: {
const double scale = std::min(1.0, std::min(widthScale, heightScale));
scaleX = scale;
scaleY = scale;
break;
}
case kFillWidthScale:
scaleX = widthScale;
scaleY = widthScale;
break;
case kFillHeightScale:
scaleX = heightScale;
scaleY = heightScale;
break;
case kNoneScale:
scaleX = 1.0;
scaleY = 1.0;
break;
case kDefaultScale:
case kFillBoundsScale:
break;
default:
return false;
}
const int requestedWidth = CeilScaledDimension(sourceWidth, scaleX);
const int requestedHeight = CeilScaledDimension(sourceHeight, scaleY);
int sampleSize = 1;
while (sampleSize <= std::numeric_limits<int>::max() / 2 &&
sourceWidth / (sampleSize * 2) >= requestedWidth &&
sourceHeight / (sampleSize * 2) >= requestedHeight) {
sampleSize *= 2;
}
const int sampledWidth = std::max(1, sourceWidth / sampleSize);
const int sampledHeight = std::max(1, sourceHeight / sampleSize);
const bool forceExactOutput =
contentScaleMode == kCropScale || contentScaleMode == kFillBoundsScale;
int outputWidth = forceExactOutput
? targetWidth
: std::min(sampledWidth, CeilToBucket(targetWidth, decodeSizeBucket));
int outputHeight = forceExactOutput
? targetHeight
: std::min(sampledHeight, CeilToBucket(targetHeight, decodeSizeBucket));
outputWidth = std::max(1, std::min(outputWidth, std::numeric_limits<int>::max()));
outputHeight = std::max(1, std::min(outputHeight, std::numeric_limits<int>::max()));
if (outputWidth >= sourceWidth && outputHeight >= sourceHeight) {
outputWidth = sourceWidth;
outputHeight = sourceHeight;
}
result->width = outputWidth;
result->height = outputHeight;
result->crop = contentScaleMode == kCropScale;
result->subset = SkIRect::MakeWH(sourceWidth, sourceHeight);
if (result->crop) {
const double sourceRatio = static_cast<double>(sourceWidth) / sourceHeight;
const double targetRatio = static_cast<double>(outputWidth) / outputHeight;
if (sourceRatio > targetRatio) {
const double cropWidth = sourceHeight * targetRatio;
const int left = std::max(0, static_cast<int>((sourceWidth - cropWidth) / 2.0));
const int right = std::min(sourceWidth, static_cast<int>(std::ceil(left + cropWidth)));
result->subset = SkIRect::MakeLTRB(left, 0, std::max(left + 1, right), sourceHeight);
} else {
const double cropHeight = sourceWidth / targetRatio;
const int top = std::max(0, static_cast<int>((sourceHeight - cropHeight) / 2.0));
const int bottom = std::min(sourceHeight, static_cast<int>(std::ceil(top + cropHeight)));
result->subset = SkIRect::MakeLTRB(0, top, sourceWidth, std::max(top + 1, bottom));
}
}
return true;
}
sk_sp<SkData> MakeRawFileData(void* nativeResourceManager, const char* rawFilePath) {
if (nativeResourceManager == nullptr || rawFilePath == nullptr || rawFilePath[0] == '\0') {
return nullptr;
}
auto* manager = static_cast<NativeResourceManager*>(nativeResourceManager);
RawFile* rawFile = OH_ResourceManager_OpenRawFile(manager, rawFilePath);
if (rawFile == nullptr) return nullptr;
RawFileDescriptor descriptor = {};
if (OH_ResourceManager_GetRawFileDescriptorData(rawFile, &descriptor) &&
descriptor.fd >= 0 && descriptor.start >= 0 && descriptor.length > 0) {
sk_sp<SkData> hapData = SkData::MakeFromFD(descriptor.fd);
const size_t start = static_cast<size_t>(descriptor.start);
const size_t length = static_cast<size_t>(descriptor.length);
const bool validRange = hapData != nullptr && start <= hapData->size() &&
length <= hapData->size() - start;
OH_ResourceManager_ReleaseRawFileDescriptorData(&descriptor);
OH_ResourceManager_CloseRawFile(rawFile);
if (validRange) {
return SkData::MakeSubset(hapData.get(), start, length);
}
return nullptr;
}
const long rawFileSize = OH_ResourceManager_GetRawFileSize(rawFile);
if (rawFileSize <= 0) {
OH_ResourceManager_CloseRawFile(rawFile);
return nullptr;
}
const size_t dataLength = static_cast<size_t>(rawFileSize);
void* data = std::malloc(dataLength);
if (data == nullptr) {
OH_ResourceManager_CloseRawFile(rawFile);
return nullptr;
}
const int bytesRead = OH_ResourceManager_ReadRawFile(rawFile, data, dataLength);
OH_ResourceManager_CloseRawFile(rawFile);
if (bytesRead <= 0 || static_cast<size_t>(bytesRead) != dataLength) {
std::free(data);
return nullptr;
}
return SkData::MakeWithProc(data, dataLength, ReleaseMallocData, nullptr);
}
sk_sp<SkData> MakeMediaData(void* nativeResourceManager, KInt mediaResId) {
if (nativeResourceManager == nullptr || mediaResId <= 0) return nullptr;
uint8_t* data = nullptr;
uint64_t dataLength = 0;
const auto error = OH_ResourceManager_GetMediaData(
static_cast<NativeResourceManager*>(nativeResourceManager),
static_cast<uint32_t>(mediaResId),
&data,
&dataLength,
0);
if (error != SUCCESS || data == nullptr || dataLength == 0 ||
dataLength > std::numeric_limits<size_t>::max()) {
std::free(data);
return nullptr;
}
return SkData::MakeWithProc(
data,
static_cast<size_t>(dataLength),
ReleaseMallocData,
nullptr);
}
KNativePointer DecodeResourceImage(sk_sp<SkData> encodedData,
KInt targetWidth,
KInt targetHeight,
KInt contentScaleMode,
KInt decodeSizeBucket) {
if (encodedData == nullptr) return 0;
if (targetWidth == 0 && targetHeight == 0) {
sk_sp<SkImage> image = SkImages::DeferredFromEncodedData(encodedData);
return reinterpret_cast<KNativePointer>(image.release());
}
if (targetWidth <= 0 || targetHeight <= 0) return 0;
std::unique_ptr<SkCodec> codec = SkCodec::MakeFromData(std::move(encodedData));
if (codec == nullptr) return 0;
const SkImageInfo sourceInfo = codec->getInfo();
TargetDecodePlan plan = {};
if (!BuildTargetDecodePlan(
sourceInfo,
targetWidth,
targetHeight,
contentScaleMode,
decodeSizeBucket,
&plan)) {
return 0;
}
SkCodec::Options options;
if (plan.crop) options.fSubset = &plan.subset;
const SkImageInfo targetInfo = sourceInfo.makeWH(plan.width, plan.height);
auto decoded = codec->getImage(targetInfo, plan.crop ? &options : nullptr);
sk_sp<SkImage> image = std::move(std::get<0>(decoded));
if (std::get<1>(decoded) != SkCodec::kSuccess || image == nullptr ||
image->width() != plan.width || image->height() != plan.height) {
return 0;
}
return reinterpret_cast<KNativePointer>(image.release());
}
} // namespace
SKIKO_EXPORT KNativePointer org_jetbrains_skia_Image__1nMakeFromEncodedOhosTargetSize(
KByte* encodedArray,
KInt encodedLength,
KInt desiredWidth,
KInt desiredHeight,
KInt cropX,
KInt cropY,
KInt cropWidth,
KInt cropHeight) {
if (encodedArray == nullptr || encodedLength <= 0 || desiredWidth <= 0 || desiredHeight <= 0) {
return 0;
}
sk_sp<SkData> encodedData = SkData::MakeWithCopy(encodedArray, encodedLength);
if (encodedData == nullptr) {
return 0;
}
std::unique_ptr<SkCodec> codec = SkCodec::MakeFromData(std::move(encodedData));
if (codec == nullptr) {
return 0;
}
const SkImageInfo sourceInfo = codec->getInfo();
const bool hasCrop = cropWidth > 0 && cropHeight > 0;
const bool hasPartialCrop = cropX != 0 || cropY != 0 || cropWidth != 0 || cropHeight != 0;
if (hasPartialCrop && !hasCrop) {
return 0;
}
SkIRect subset;
SkCodec::Options options;
if (hasCrop) {
if (cropX < 0 || cropY < 0 || cropX >= sourceInfo.width() || cropY >= sourceInfo.height() ||
cropWidth > sourceInfo.width() - cropX || cropHeight > sourceInfo.height() - cropY) {
return 0;
}
subset = SkIRect::MakeXYWH(cropX, cropY, cropWidth, cropHeight);
options.fSubset = &subset;
}
const SkImageInfo targetInfo = sourceInfo.makeWH(desiredWidth, desiredHeight);
auto decoded = codec->getImage(targetInfo, hasCrop ? &options : nullptr);
sk_sp<SkImage> image = std::move(std::get<0>(decoded));
if (std::get<1>(decoded) != SkCodec::kSuccess || image == nullptr ||
image->width() != desiredWidth || image->height() != desiredHeight) {
return 0;
}
return reinterpret_cast<KNativePointer>(image.release());
}
SKIKO_EXPORT KNativePointer org_jetbrains_skia_Image__1nMakeFromRawFileOhos(
void* nativeResourceManager,
const char* rawFilePath,
KInt targetWidth,
KInt targetHeight,
KInt contentScaleMode,
KInt decodeSizeBucket) {
return DecodeResourceImage(
MakeRawFileData(nativeResourceManager, rawFilePath),
targetWidth,
targetHeight,
contentScaleMode,
decodeSizeBucket);
}
SKIKO_EXPORT KNativePointer org_jetbrains_skia_Image__1nMakeFromMediaOhos(
void* nativeResourceManager,
KInt mediaResId,
KInt targetWidth,
KInt targetHeight,
KInt contentScaleMode,
KInt decodeSizeBucket) {
return DecodeResourceImage(
MakeMediaData(nativeResourceManager, mediaResId),
targetWidth,
targetHeight,
contentScaleMode,
decodeSizeBucket);
}
/*
* Copyright (c) 2026 Huawei Device Co., Ltd.
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#include <atomic>
#include <arkui/native_node_napi.h>
#include <arkui/native_interface.h>
#include "common.h"
#include "Utils.h"
#include "include/oh/OHRenderNode.h"
#include "SkCanvas.h"
static ArkUI_NativeNodeAPI_1* GetNativeNodeAPI() {
static ArkUI_NativeNodeAPI_1* s_api = nullptr;
if (s_api == nullptr) {
OH_ArkUI_GetModuleInterface(ARKUI_NATIVE_NODE, ArkUI_NativeNodeAPI_1, s_api);
}
return s_api;
}
// Mounts an ArkUI_NodeHandle into a NodeContent container
static void mountHandleToNodeContent(napi_env env, napi_value nodeContent, ArkUI_NodeHandle handle) {
ArkUI_NativeNodeAPI_1 *arkUINativeNodeApi = GetNativeNodeAPI();
if (arkUINativeNodeApi == nullptr) {
Error("mountHandleToNodeContent: Failed to get ArkUI_NativeNodeAPI_1");
return;
}
ArkUI_NumberValue value[] = {{.f32 = 1.0f}};
ArkUI_AttributeItem item = {value, 1};
arkUINativeNodeApi->setAttribute(handle, NODE_WIDTH_PERCENT, &item);
arkUINativeNodeApi->setAttribute(handle, NODE_HEIGHT_PERCENT, &item);
ArkUI_NodeContentHandle contentHandle;
OH_ArkUI_GetNodeContentFromNapiValue(env, nodeContent, &contentHandle);
OH_ArkUI_NodeContent_AddNode(contentHandle, handle);
}
class ExternalRenderNode {
public:
std::shared_ptr<OHRenderNode> ptr;
ArkUI_NodeHandle nodeHandle = nullptr;
// Type 1: ArkTS path — wrap ArkTS FrameNode directly via CreateExternalNode.
explicit ExternalRenderNode(napi_value node) {
ptr = OHRenderNode::CreateExternalNode(node);
}
// Type 2: C-API path — wrap outerFrameNode in a CUSTOM parent node via CreateExternalNodeC.
ExternalRenderNode(napi_env env, int32_t rootNodeId, int32_t extNodeId, napi_value node)
: ExternalRenderNode(rootNodeId, extNodeId, [&] {
ArkUI_NodeHandle handle = nullptr;
OH_ArkUI_GetNodeHandleFromNapiValue(env, node, &handle);
return handle;
}()) {}
ExternalRenderNode(int32_t rootNodeId, int32_t extNodeId, ArkUI_NodeHandle handle) {
this->nodeHandle = handle;
ArkUI_NativeNodeAPI_1 *arkUINativeNodeApi = GetNativeNodeAPI();
if (arkUINativeNodeApi == nullptr) {
Error("ExternalRenderNode: Failed to get ArkUI_NativeNodeAPI_1");
return;
}
ArkUI_NodeHandle handleExtra = arkUINativeNodeApi->createNode(ARKUI_NODE_CUSTOM);
arkUINativeNodeApi->addChild(handleExtra, handle);
RootRenderNodeManager::Instance().AdoptNode(rootNodeId, handleExtra);
ptr = OHRenderNode::CreateExternalNodeC(handleExtra);
std::weak_ptr<OHRenderNode> weakNode = ptr;
RootRenderNodeManager::Instance().MapAdoptNode(rootNodeId, extNodeId, weakNode);
}
~ExternalRenderNode() {
// C-API path only (Type 2/3): nodeHandle is the user's outer FrameNode (JS-owned),
// which we wrapped under a C-side handleExtra in the constructor:
//
// handleExtra (C-API CUSTOM, owned by RootRenderNodeManager.adoptedChildren_)
// └── nodeHandle == outerFrameNode (JS-owned)
// └── ArkUIView (JS-owned, has its own .dispose())
//
// Detach ArkUIView from outerFrameNode here, before either side runs its own
// teardown. If we leave the C-side parent/child link in place, the GC ordering
// between (a) ArkUIView.dispose() in ets, (b) JS GC of outerFrameNode, and
// (c) RootRenderNodeManager.UnregisterNode -> disposeNode(handleExtra) is
// unspecified, and any disposeNode walking through stale child pointers
// crashes. Breaking the link on the Main thread (cleaner schedules here) lets
// each owner clean up its own subgraph in isolation.
//
// NOTE: handleExtra itself is not freed here — it is reaped only when its
// root node is unregistered via RootRenderNodeManager.UnregisterNode (see
// OHRender's RenderNodeImplC.cpp UnregisterNodeInternal). There is no
// per-extNode unadopt API today, so handleExtra entries accumulate inside
// adoptedChildren_/adoptedNodeMap_ for the lifetime of the rootNodeId.
//
// Type 1 (ArkTS) keeps nodeHandle == nullptr and skips this branch entirely.
if (this->nodeHandle != nullptr) {
ArkUI_NativeNodeAPI_1 *arkUINativeNodeApi = GetNativeNodeAPI();
if (arkUINativeNodeApi != nullptr) {
arkUINativeNodeApi->removeAllChildren(this->nodeHandle);
}
this->nodeHandle = nullptr;
}
ptr = nullptr;
}
};
static void freeExternalRenderNode(ExternalRenderNode* node) {
delete node;
}
SKIKO_EXPORT KNativePointer org_jetbrains_skia_ExternalRenderNode__1nMakeForArkTs
(napi_value node) {
ExternalRenderNode* renderNode = new ExternalRenderNode(node);
if (renderNode->ptr == nullptr) {
delete renderNode;
return nullptr;
}
return reinterpret_cast<KNativePointer>(renderNode);
}
SKIKO_EXPORT KNativePointer org_jetbrains_skia_ExternalRenderNode__1nMakeForCApi
(napi_env env, int32_t rootNodeId, int32_t extNodeId, napi_value node) {
ExternalRenderNode* renderNode = new ExternalRenderNode(env, rootNodeId, extNodeId, node);
if (renderNode->ptr == nullptr) {
delete renderNode;
return nullptr;
}
return reinterpret_cast<KNativePointer>(renderNode);
}
SKIKO_EXPORT KNativePointer org_jetbrains_skia_ExternalRenderNode__1nMakeFromHandle
(int32_t rootNodeId, int32_t extNodeId, ArkUI_NodeHandle handle)
{
ExternalRenderNode* renderNode = new ExternalRenderNode(rootNodeId, extNodeId, handle);
if (renderNode->ptr == nullptr) {
delete renderNode;
return nullptr;
}
return reinterpret_cast<KNativePointer>(renderNode);
}
SKIKO_EXPORT void org_jetbrains_skia_ExternalRenderNode__1nMountHandleToNodeContent
(napi_env env, napi_value nodeContent, ArkUI_NodeHandle handle) {
mountHandleToNodeContent(env, nodeContent, handle);
}
SKIKO_EXPORT KNativePointer org_jetbrains_skia_ExternalRenderNode__1nGetExternalRenderNodeFinalizer() {
return reinterpret_cast<KNativePointer>(freeExternalRenderNode);
}
SKIKO_EXPORT void org_jetbrains_skia_Canvas__1nDrawRenderNode
(SkCanvas* canvas, ExternalRenderNode* node, KFloat left, KFloat top, KFloat right, KFloat bottom) {
if (canvas == nullptr || node == nullptr) {
return;
}
SkRect rect{left, top, right, bottom};
canvas->drawRenderNode(node->ptr, rect);
}
SKIKO_EXPORT void org_jetbrains_skia_ExternalRenderNode__1nMapFrameNodePos
(int32_t rootNodeId, int32_t extNodeId, double touchX, double touchY,
float* outX, float* outY) {
SkPoint gPoint = SkPoint::Make(touchX, touchY);
SkPoint lPoint = SkPoint::Make(0, 0);
if (auto node = RootRenderNodeManager::Instance().GetAdoptOHRenderNode(rootNodeId, extNodeId)) {
node->mapPointFromGlobal(gPoint, &lPoint);
}
*outX = lPoint.x();
*outY = lPoint.y();
}
SKIKO_EXPORT void org_jetbrains_skia_ExternalRenderNode__1nMapPointFromGlobal
(ExternalRenderNode* renderNode, double globalX, double globalY,
float* outX, float* outY) {
SkPoint gPoint = SkPoint::Make((float)globalX, (float)globalY);
SkPoint lPoint = SkPoint::Make(0, 0);
if (renderNode != nullptr && renderNode->ptr != nullptr) {
renderNode->ptr->mapPointFromGlobal(gPoint, &lPoint);
}
*outX = lPoint.x();
*outY = lPoint.y();
}
#include <chrono>
#include <vector>
#include "SkString.h"
#include "common.h"
//region ohos
// The Fusion renderer uses OHRender instead of Ganesh and OHRender is built
// without SkSL, Skottie and several advanced typeface APIs. Keep the complete
// m150 native ABI linkable and return deterministic unsupported values here.
// This turns missing-symbol/IrLinkageError crashes into ordinary null results
// (or a descriptive RuntimeEffect compilation error).
static void deleteUnsupportedBackendTexture(KNativePointer) {
}
SKIKO_EXPORT KNativePointer org_jetbrains_skia_BackendTexture__1nGetFinalizer() {
return reinterpret_cast<KNativePointer>(&deleteUnsupportedBackendTexture);
}
SKIKO_EXPORT KNativePointer org_jetbrains_skia_BackendTexture__1nMakeGL
(KInt, KInt, KBoolean, KInt, KInt, KInt) {
return nullptr;
}
SKIKO_EXPORT void org_jetbrains_skia_BackendTexture__1nGLTextureParametersModified
(KNativePointer) {
}
SKIKO_EXPORT KNativePointer BackendRenderTarget_nMakeMetal
(KInt, KInt, KNativePointer) {
return nullptr;
}
SKIKO_EXPORT KNativePointer BackendRenderTarget_MakeDirect3D
(KInt, KInt, KNativePointer, KInt, KInt, KInt) {
return nullptr;
}
SKIKO_EXPORT KNativePointer org_jetbrains_skia_DirectContext__1nMakeMetal
(KNativePointer, KNativePointer) {
return nullptr;
}
SKIKO_EXPORT KNativePointer org_jetbrains_skia_DirectContext__1nMakeDirect3D
(KNativePointer, KNativePointer, KNativePointer) {
return nullptr;
}
SKIKO_EXPORT void org_jetbrains_skia_DirectContext__1nFlush
(KNativePointer, KNativePointer) {
}
SKIKO_EXPORT KLong org_jetbrains_skia_DirectContext__1nGetResourceCacheLimit
(KNativePointer) {
return 0;
}
SKIKO_EXPORT void org_jetbrains_skia_DirectContext__1nSetResourceCacheLimit
(KNativePointer, KLong) {
}
SKIKO_EXPORT void org_jetbrains_skia_DirectContext__1nFlushAndSubmit
(KNativePointer, KNativePointer, KBoolean) {
}
SKIKO_EXPORT void org_jetbrains_skia_DirectContext__1nReset
(KNativePointer, KInt) {
}
SKIKO_EXPORT KNativePointer org_jetbrains_skia_Surface__1nMakeRenderTarget
(KNativePointer, KBoolean, KInt, KInt, KInt, KInt, KNativePointer,
KInt, KInt, KInteropPointer, KBoolean) {
return nullptr;
}
struct UnsupportedRuntimeEffectResult {
SkString error = SkString("RuntimeEffect is not supported by OHRender Fusion Renderer");
};
static KNativePointer makeUnsupportedRuntimeEffectResult() {
return reinterpret_cast<KNativePointer>(new UnsupportedRuntimeEffectResult());
}
SKIKO_EXPORT KNativePointer org_jetbrains_skia_RuntimeEffect__1nMakeShader
(KNativePointer, KNativePointer, KNativePointerArray, KInt, KFloat*) {
return nullptr;
}
SKIKO_EXPORT KNativePointer org_jetbrains_skia_RuntimeEffect__1nMakeBlender
(KNativePointer, KNativePointer) {
return nullptr;
}
SKIKO_EXPORT KNativePointer org_jetbrains_skia_RuntimeEffect__1nMakeForShader
(KInteropPointer) {
return makeUnsupportedRuntimeEffectResult();
}
SKIKO_EXPORT KNativePointer org_jetbrains_skia_RuntimeEffect__1nMakeForColorFilter
(KInteropPointer) {
return makeUnsupportedRuntimeEffectResult();
}
SKIKO_EXPORT KNativePointer org_jetbrains_skia_RuntimeEffect__1nMakeForBlender
(KInteropPointer) {
return makeUnsupportedRuntimeEffectResult();
}
SKIKO_EXPORT KNativePointer org_jetbrains_skia_RuntimeEffect__1Result_nGetPtr
(KNativePointer) {
return nullptr;
}
SKIKO_EXPORT KNativePointer org_jetbrains_skia_RuntimeEffect__1Result_nGetError
(KNativePointer ptr) {
auto* result = reinterpret_cast<UnsupportedRuntimeEffectResult*>(ptr);
return result == nullptr ? nullptr : reinterpret_cast<KNativePointer>(&result->error);
}
SKIKO_EXPORT void org_jetbrains_skia_RuntimeEffect__1Result_nDestroy
(KNativePointer ptr) {
delete reinterpret_cast<UnsupportedRuntimeEffectResult*>(ptr);
}
static void deleteUnsupportedRuntimeShaderBuilder(KNativePointer) {
}
SKIKO_EXPORT KNativePointer org_jetbrains_skia_RuntimeShaderBuilder__1nGetFinalizer() {
return reinterpret_cast<KNativePointer>(&deleteUnsupportedRuntimeShaderBuilder);
}
SKIKO_EXPORT KNativePointer org_jetbrains_skia_RuntimeShaderBuilder__1nMakeFromRuntimeEffect
(KNativePointer) {
return nullptr;
}
SKIKO_EXPORT void org_jetbrains_skia_RuntimeShaderBuilder__1nUniformInt
(KNativePointer, KInteropPointer, KInt) {
}
SKIKO_EXPORT void org_jetbrains_skia_RuntimeShaderBuilder__1nUniformInt2
(KNativePointer, KInteropPointer, KInt, KInt) {
}
SKIKO_EXPORT void org_jetbrains_skia_RuntimeShaderBuilder__1nUniformInt3
(KNativePointer, KInteropPointer, KInt, KInt, KInt) {
}
SKIKO_EXPORT void org_jetbrains_skia_RuntimeShaderBuilder__1nUniformInt4
(KNativePointer, KInteropPointer, KInt, KInt, KInt, KInt) {
}
SKIKO_EXPORT void org_jetbrains_skia_RuntimeShaderBuilder__1nUniformFloat
(KNativePointer, KInteropPointer, KFloat) {
}
SKIKO_EXPORT void org_jetbrains_skia_RuntimeShaderBuilder__1nUniformFloat2
(KNativePointer, KInteropPointer, KFloat, KFloat) {
}
SKIKO_EXPORT void org_jetbrains_skia_RuntimeShaderBuilder__1nUniformFloat3
(KNativePointer, KInteropPointer, KFloat, KFloat, KFloat) {
}
SKIKO_EXPORT void org_jetbrains_skia_RuntimeShaderBuilder__1nUniformFloat4
(KNativePointer, KInteropPointer, KFloat, KFloat, KFloat, KFloat) {
}
SKIKO_EXPORT void org_jetbrains_skia_RuntimeShaderBuilder__1nUniformFloatArray
(KNativePointer, KInteropPointer, KFloat*, int32_t) {
}
SKIKO_EXPORT void org_jetbrains_skia_RuntimeShaderBuilder__1nUniformFloatMatrix22
(KNativePointer, KInteropPointer, KFloat*) {
}
SKIKO_EXPORT void org_jetbrains_skia_RuntimeShaderBuilder__1nUniformFloatMatrix33
(KNativePointer, KInteropPointer, KFloat*) {
}
SKIKO_EXPORT void org_jetbrains_skia_RuntimeShaderBuilder__1nUniformFloatMatrix44
(KNativePointer, KInteropPointer, KFloat*) {
}
SKIKO_EXPORT void org_jetbrains_skia_RuntimeShaderBuilder__1nChildShader
(KNativePointer, KInteropPointer, KNativePointer) {
}
SKIKO_EXPORT void org_jetbrains_skia_RuntimeShaderBuilder__1nChildColorFilter
(KNativePointer, KInteropPointer, KNativePointer) {
}
SKIKO_EXPORT KNativePointer org_jetbrains_skia_RuntimeShaderBuilder__1nMakeShader
(KNativePointer, KFloat*) {
return nullptr;
}
SKIKO_EXPORT void org_jetbrains_skia_Typeface__1nGetUTF32Glyphs
(KNativePointer, KInt*, KInt count, KShort* result) {
for (KInt index = 0; index < count; ++index) result[index] = 0;
}
SKIKO_EXPORT KShort org_jetbrains_skia_Typeface__1nGetUTF32Glyph
(KNativePointer, KInt) {
return 0;
}
SKIKO_EXPORT KInt org_jetbrains_skia_Typeface__1nGetGlyphsCount
(KNativePointer) {
return 0;
}
SKIKO_EXPORT KInt org_jetbrains_skia_Typeface__1nGetTablesCount
(KNativePointer) {
return 0;
}
SKIKO_EXPORT KInt org_jetbrains_skia_Typeface__1nGetTableTagsCount
(KNativePointer) {
return 0;
}
SKIKO_EXPORT void org_jetbrains_skia_Typeface__1nGetTableTags
(KNativePointer, KInt*, KInt) {
}
SKIKO_EXPORT KInt org_jetbrains_skia_Typeface__1nGetTableSize
(KNativePointer, KInt) {
return 0;
}
SKIKO_EXPORT KNativePointer org_jetbrains_skia_Typeface__1nGetTableData
(KNativePointer, KInt) {
return nullptr;
}
SKIKO_EXPORT KInt org_jetbrains_skia_Typeface__1nGetUnitsPerEm
(KNativePointer) {
return 0;
}
SKIKO_EXPORT bool org_jetbrains_skia_Typeface__1nGetKerningPairAdjustments
(KNativePointer, KInteropPointer, KInt, KInt*) {
return false;
}
SKIKO_EXPORT KInteropPointer org_jetbrains_skia_Typeface__1nGetFamilyNames
(KNativePointer) {
return reinterpret_cast<KInteropPointer>(new std::vector<KInteropPointer>());
}
SKIKO_EXPORT void org_jetbrains_skia_Typeface__1nGetBounds
(KNativePointer, KFloat* bounds) {
bounds[0] = 0;
bounds[1] = 0;
bounds[2] = 0;
bounds[3] = 0;
}
SKIKO_EXPORT KLong org_jetbrains_skia_OHRenderNode__1nGetCurrentTimeMicros() {
return static_cast<KLong>(std::chrono::duration_cast<std::chrono::microseconds>(
std::chrono::steady_clock::now().time_since_epoch()
).count());
}
SKIKO_EXPORT KInt skikobridge_napi_get_system_theme() {
return 0;
}
//endregion
#include "SkPath.h"
#include "SkRRect.h"
#include "common.h"
//region ohos
// m150 exposes immutable Path factories backed by newer SkSpan-based Skia APIs.
// OHRender uses the older pointer/count overloads, so keep the Kotlin ABI and
// translate only the native construction calls here.
SKIKO_EXPORT KNativePointer org_jetbrains_skia_Path__1nMakeFromRaw
(KFloat* ptsArray, KInt ptsCount,
KByte* verbsArray, KInt verbsCount,
KFloat* conicWeightsArray, KInt conicWeightsCount,
KInt fillType, KBoolean isVolatile) {
SkPath path = SkPath::Make(
reinterpret_cast<const SkPoint*>(ptsArray), ptsCount,
reinterpret_cast<const uint8_t*>(verbsArray), verbsCount,
conicWeightsArray, conicWeightsCount,
static_cast<SkPathFillType>(fillType),
isVolatile
);
return reinterpret_cast<KNativePointer>(new SkPath(path));
}
SKIKO_EXPORT KNativePointer org_jetbrains_skia_Path__1nMakeFromRect
(KFloat left, KFloat top, KFloat right, KFloat bottom,
KInt fillType, KInt direction, KInt startIndex) {
SkPath path = SkPath::Rect(
SkRect::MakeLTRB(left, top, right, bottom),
static_cast<SkPathDirection>(direction),
startIndex
);
path.setFillType(static_cast<SkPathFillType>(fillType));
return reinterpret_cast<KNativePointer>(new SkPath(path));
}
SKIKO_EXPORT KNativePointer org_jetbrains_skia_Path__1nMakeFromOval
(KFloat left, KFloat top, KFloat right, KFloat bottom,
KInt direction, KInt startIndex) {
SkPath path = SkPath::Oval(
SkRect::MakeLTRB(left, top, right, bottom),
static_cast<SkPathDirection>(direction),
startIndex
);
return reinterpret_cast<KNativePointer>(new SkPath(path));
}
SKIKO_EXPORT KNativePointer org_jetbrains_skia_Path__1nMakeFromCircle
(KFloat centerX, KFloat centerY, KFloat radius, KInt direction) {
SkPath path = SkPath::Circle(
centerX,
centerY,
radius,
static_cast<SkPathDirection>(direction)
);
return reinterpret_cast<KNativePointer>(new SkPath(path));
}
SKIKO_EXPORT KNativePointer org_jetbrains_skia_Path__1nMakeFromRRect
(KFloat* radiiArray,
KFloat left, KFloat top, KFloat right, KFloat bottom,
KInt direction, KInt startIndex) {
SkRRect rrect;
rrect.setRectRadii(
SkRect::MakeLTRB(left, top, right, bottom),
reinterpret_cast<const SkVector*>(radiiArray)
);
SkPath path = SkPath::RRect(
rrect,
static_cast<SkPathDirection>(direction),
startIndex
);
return reinterpret_cast<KNativePointer>(new SkPath(path));
}
SKIKO_EXPORT KNativePointer org_jetbrains_skia_Path__1nMakeFromRRectXY
(KFloat left, KFloat top, KFloat right, KFloat bottom,
KFloat rx, KFloat ry, KInt direction) {
SkPath path = SkPath::RRect(
SkRect::MakeLTRB(left, top, right, bottom),
rx,
ry,
static_cast<SkPathDirection>(direction)
);
return reinterpret_cast<KNativePointer>(new SkPath(path));
}
SKIKO_EXPORT KNativePointer org_jetbrains_skia_Path__1nMakeFromPolygon
(KFloat* ptsArray, KInt ptsCount,
KBoolean isClosed, KInt fillType, KBoolean isVolatile) {
SkPath path = SkPath::Polygon(
reinterpret_cast<const SkPoint*>(ptsArray), ptsCount,
isClosed,
static_cast<SkPathFillType>(fillType),
isVolatile
);
return reinterpret_cast<KNativePointer>(new SkPath(path));
}
//endregion
#include "common.h"
//region ohos
// Fusion Renderer owns its rendering context and surface through OHRender. Keep
// the public Skiko Ganesh entry points link-safe, but report them as unavailable
// instead of leaving lazy native symbols that crash when called.
static void deleteUnsupportedBackendRenderTarget(KNativePointer) {
}
SKIKO_EXPORT KNativePointer org_jetbrains_skia_BackendRenderTarget__1nGetFinalizer() {
return reinterpret_cast<KNativePointer>(&deleteUnsupportedBackendRenderTarget);
}
SKIKO_EXPORT KNativePointer org_jetbrains_skia_BackendRenderTarget__1nMakeGL
(KInt width, KInt height, KInt sampleCnt, KInt stencilBits, KInt fbId, KInt fbFormat) {
return nullptr;
}
SKIKO_EXPORT KNativePointer org_jetbrains_skia_DirectContext__1nMakeGL() {
return nullptr;
}
SKIKO_EXPORT void org_jetbrains_skia_DirectContext__1nFlushDefault(KNativePointer ptr) {
}
SKIKO_EXPORT void org_jetbrains_skia_DirectContext__1nSubmit
(KNativePointer ptr, KBoolean syncCpu) {
}
SKIKO_EXPORT void org_jetbrains_skia_DirectContext__1nAbandon
(KNativePointer ptr, KInt flags) {
}
SKIKO_EXPORT KNativePointer org_jetbrains_skia_Surface__1nMakeFromBackendRenderTarget
(KNativePointer pContext, KNativePointer pBackendRenderTarget, KInt surfaceOrigin,
KInt colorType, KNativePointer colorSpacePtr, KInteropPointer surfacePropsInts) {
return nullptr;
}
//endregion
#include <iostream>
#include "SkCanvas.h"
#include "SkRRect.h"
#include "common.h"
#include "include/oh/OHRenderNode.h"
SKIKO_EXPORT KNativePointer org_jetbrains_skia_OHRenderNode__1nCreateRootRenderNode
(KNativePointer context, void* callback) {
auto cb = reinterpret_cast<NodeDrawCallback>(callback);
auto node = reinterpret_cast<KNativePointer>(OHRenderNode::CreateRootRenderNode(context, cb));
return node;
}
SKIKO_EXPORT void org_jetbrains_skia_OHRenderNode__1nSetNodeConstructor
(napi_env env, napi_value fun) {
OHRenderNode::SetNodeConstructor(env, fun);
return;
}
SKIKO_EXPORT void org_jetbrains_skia_OHRenderNode__1nRenderNodeDraw
(napi_env env, napi_value jsCanvas, napi_value jsNode) {
OHRenderNode::RenderNodeDraw(env, jsCanvas, jsNode);
return;
}
SKIKO_EXPORT void org_jetbrains_skia_OHRenderNode__1nRenderNodeNotifyRedraw
(napi_env env, napi_value jsNode) {
OHRenderNode::RenderNodeNotifyRedraw(env, jsNode);
return;
}
SKIKO_EXPORT void org_jetbrains_skia_OHRenderNode__1nSetPixelRatio
(double ratio) {
OHRenderNode::SetPixelRatio(ratio);
return;
}
SKIKO_EXPORT void org_jetbrains_skia_OHRenderNode__1nBuildInstance
(napi_env env, napi_value fun) {
NodeStatusModify::BuildInstance(env, fun);
return;
}
#include <atomic>
#include <arkui/native_node_napi.h>
#include "common.h"
#include "Utils.h"
#include "include/oh/OHRenderNode.h"
typedef void (*RenderNodeDrawCallback)(KInt, KNativePointer);
static RenderNodeDrawCallback drawCallback = nullptr;
static std::atomic<int32_t> renderNodeID(0);
class IRenderNode {
public:
virtual void notifyRedraw() = 0;
virtual int32_t getID() = 0;
};
class JsRenderNode : IRenderNode {
private:
const napi_env env;
const int32_t id;
napi_ref nodeRef = nullptr;
public:
JsRenderNode(napi_env env, int32_t id) : env(env), id(id) {};
~JsRenderNode() {
if (nodeRef != nullptr) {
napi_delete_reference(env, nodeRef);
nodeRef = nullptr;
}
}
static void freeJsRenderNode(JsRenderNode* node) {
delete node;
}
static void jsNodeDrawCallback(void *context, void *canvas) {
if (drawCallback == nullptr) {
Error("jsNodeDrawCallback: empty drawCallback");
return;
}
JsRenderNode* renderNode = reinterpret_cast<JsRenderNode*>(context);
drawCallback(renderNode->getID(), canvas);
}
void createJsNode() {
if (nodeRef == nullptr) {
napi_value jsNode = OHRenderNode::CreateRootRenderNode(this, &jsNodeDrawCallback);
napi_status status = napi_create_reference(env, jsNode, 1, &nodeRef);
if (status != napi_ok) {
Error("JsRenderNode createJsNode failed: createReference failed");
nodeRef = nullptr;
}
}
}
napi_value getJsNode() {
if (nodeRef == nullptr) {
Error("JsRenderNode getJsNode failed: empty nodeRef");
return nullptr;
}
napi_value jsNode;
napi_status status = napi_get_reference_value(env, nodeRef, &jsNode);
if (status != napi_ok) {
Error("JsRenderNode getJsNode failed: getReference failed");
return nullptr;
}
return jsNode;
}
void notifyRedraw() override {
napi_handle_scope scope = nullptr;
napi_open_handle_scope(env, &scope);
const napi_value jsNode = getJsNode();
if (jsNode == nullptr) {
Error("JsRenderNode notifyRedraw failed: empty jsNode");
return;
}
OHRenderNode::RenderNodeNotifyRedraw(env, jsNode);
napi_close_handle_scope(env, scope);
}
int32_t getID() override {
return id;
}
void draw(napi_value canvas) {
napi_handle_scope scope = nullptr;
napi_open_handle_scope(env, &scope);
const napi_value jsNode = getJsNode();
if (jsNode == nullptr) {
Error("JsRenderNode draw failed: empty jsNode");
return;
}
OHRenderNode::RenderNodeDraw(env, canvas, jsNode);
napi_close_handle_scope(env, scope);
}
};
class CRenderNode : IRenderNode {
private:
const int32_t id;
const ArkUI_NodeHandle node;
public:
static void cRenderNodeDraw(int32_t id, void* canvas) {
if (drawCallback == nullptr) {
Error("cNodeDrawCallback: empty drawCallback");
return;
}
drawCallback(id, canvas);
}
CRenderNode(int32_t id): id(id), node(OHRenderNode::CreateRootRenderNodeWithoutEvent(id, &cRenderNodeDraw)) {};
~CRenderNode() {
RootRenderNodeManager::Instance().UnregisterNode(id);
}
void notifyRedraw() override {
RootRenderNodeManager::Instance().NotifyRedraw(id);
}
int32_t getID() override {
return id;
}
int32_t attach(ArkUI_NodeContentHandle parent) {
return OH_ArkUI_NodeContent_AddNode(parent, node);
}
ArkUI_NodeHandle getNodeHandle() {
return node;
}
void reSize(int32_t width, int32_t height) {
RootRenderNodeManager::Instance().Resize(id, width, height);
}
static void freeCRenderNode(CRenderNode* node) {
delete node;
}
};
SKIKO_EXPORT void org_jetbrains_skia_RenderNode__1nSetDrawCallback
(void* callback) {
drawCallback = reinterpret_cast<RenderNodeDrawCallback>(callback);
}
SKIKO_EXPORT void org_jetbrains_skia_RenderNode__1nSetNodeConstructor
(napi_env env, napi_value function) {
OHRenderNode::SetNodeConstructor(env, function);
}
SKIKO_EXPORT void org_jetbrains_skia_RenderNode__1nSetPixelRatio
(double ratio) {
OHRenderNode::SetPixelRatio(ratio);
}
SKIKO_EXPORT void org_jetbrains_skia_RenderNode__1nNodeStatusModifyBuildInstance
(napi_env env, napi_value function) {
NodeStatusModify::BuildInstance(env, function);
}
SKIKO_EXPORT KNativePointer org_jetbrains_skia_RenderNode__1nCreateJsRenderNode
(napi_env env) {
JsRenderNode* node = new JsRenderNode(env, ++renderNodeID);
node->createJsNode();
return reinterpret_cast<KNativePointer>(node);
}
SKIKO_EXPORT KNativePointer org_jetbrains_skia_RenderNode__1nGetJsNodeFinalizer() {
return reinterpret_cast<KNativePointer>((&JsRenderNode::freeJsRenderNode));
}
SKIKO_EXPORT void org_jetbrains_skia_RenderNode__1nRenderNodeNotifyRedraw
(void* ptr) {
IRenderNode* node = reinterpret_cast<IRenderNode*>(ptr);
node->notifyRedraw();
}
SKIKO_EXPORT KInt org_jetbrains_skia_RenderNode__1nRenderNodeGetNodeId
(void* ptr) {
IRenderNode* node = reinterpret_cast<IRenderNode*>(ptr);
return node->getID();
}
SKIKO_EXPORT void org_jetbrains_skia_RenderNode__1nRenderNodeDraw
(void* ptr, napi_value canvas) {
JsRenderNode* node = reinterpret_cast<JsRenderNode*>(ptr);
node->draw(canvas);
}
SKIKO_EXPORT napi_value org_jetbrains_skia_RenderNode__1nRenderNodeGetJsNode
(void* ptr) {
JsRenderNode* node = reinterpret_cast<JsRenderNode*>(ptr);
return node->getJsNode();
}
SKIKO_EXPORT void org_jetbrains_skia_RenderNode__1nRenderNodeJsNodeDraw
(napi_env env, napi_value node, napi_value canvas) {
OHRenderNode::RenderNodeDraw(env, canvas, node);
}
SKIKO_EXPORT KNativePointer org_jetbrains_skia_RenderNode__1nCreateCRenderNode
(napi_env env, napi_value content) {
ArkUI_NodeContentHandle handle{};
auto ret = OH_ArkUI_GetNodeContentFromNapiValue(env, content, &handle);
if (ret != ARKUI_ERROR_CODE_NO_ERROR) {
Error("CRenderNode create GetNodeContentFromNapiValue failed: %{public}d", ret);
return nullptr;
}
CRenderNode* node = new CRenderNode(++renderNodeID);
ret = node->attach(handle);
if (ret != ARKUI_ERROR_CODE_NO_ERROR) {
Error("CRenderNode create attach failed: %{public}d", ret);
delete node;
return nullptr;
}
return reinterpret_cast<KNativePointer>(node);
}
SKIKO_EXPORT void org_jetbrains_skia_RenderNode__1nRenderNodeReSize
(void* ptr, int width, int height) {
CRenderNode* node = reinterpret_cast<CRenderNode*>(ptr);
node->reSize(width, height);
}
SKIKO_EXPORT ArkUI_NodeHandle org_jetbrains_skia_RenderNode__1nRenderNodeGetNodeHandle
(void* ptr) {
CRenderNode* node = reinterpret_cast<CRenderNode*>(ptr);
return node->getNodeHandle();
}
SKIKO_EXPORT KNativePointer org_jetbrains_skia_RenderNode__1nGetCNodeFinalizer() {
return reinterpret_cast<KNativePointer>((&CRenderNode::freeCRenderNode));
}
SKIKO_EXPORT void org_jetbrains_skia_RenderNode__1nSetCAPIFixed
(bool fixed) {
OHRenderNode::SetCAPIFixed(fixed);
}
#include <hilog/log.h>
#define OHOS_LOG_TAG "SkikoOHOS"
#ifdef SKIKO_DEBUG
#define Debug(...) ((void)OH_LOG_Print(LOG_APP, LOG_DEBUG, LOG_DOMAIN, OHOS_LOG_TAG, __VA_ARGS__))
#else
#define Debug(...)
#endif
#define Info(...) ((void)OH_LOG_Print(LOG_APP, LOG_INFO, LOG_DOMAIN, OHOS_LOG_TAG, __VA_ARGS__))
#define Warn(...) ((void)OH_LOG_Print(LOG_APP, LOG_WARN, LOG_DOMAIN, OHOS_LOG_TAG, __VA_ARGS__))
#define Error(...) ((void)OH_LOG_Print(LOG_APP, LOG_ERROR, LOG_DOMAIN, OHOS_LOG_TAG, __VA_ARGS__))
\ No newline at end of file
/*
* Copyright (c) 2026 Huawei Device Co., Ltd.
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.jetbrains.skia
import org.jetbrains.skia.impl.NativePointer
import org.jetbrains.skia.impl.Stats
import org.jetbrains.skia.impl.getPtr
import org.jetbrains.skia.impl.reachabilityBarrier
import org.jetbrains.skiko.ExternalRenderNode
fun Canvas.drawRenderNode(node: ExternalRenderNode, rect: Rect): Canvas {
Stats.onNativeCall()
try {
_nDrawRenderNode(getPtr(this), node.nativePtr, rect.left, rect.top, rect.right, rect.bottom)
} finally {
reachabilityBarrier(this)
}
return this
}
@ExternalSymbolName("org_jetbrains_skia_Canvas__1nDrawRenderNode")
private external fun _nDrawRenderNode(ptr: NativePointer, nodePtr: NativePointer, left: Float, top: Float, right: Float, bottom: Float)
/*
* Copyright (c) 2026 Huawei Device Co., Ltd.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
@file:OptIn(kotlinx.cinterop.ExperimentalForeignApi::class)
package org.jetbrains.skia
import org.jetbrains.skia.impl.NativePointer
@Deprecated(
message = "Use a renderer-scoped OhosImageInteropSession instead",
)
@Suppress("UNUSED_PARAMETER")
fun Image.Companion.makeFromOhosSurfaceBuffer(
context: DirectContext,
surfaceBufferPtr: NativePointer,
colorType: ColorType = ColorType.RGBA_8888,
alphaType: ColorAlphaType = ColorAlphaType.PREMUL,
colorSpace: ColorSpace = ColorSpace.sRGB,
): Image {
throw UnsupportedOperationException(
"Fusion Renderer does not support the legacy DirectContext SurfaceBuffer import API. " +
"Use OhosImageInteropSession and handle OhosImageImportResult.Unsupported.",
)
}
/**
* Fusion Renderer has no queued EGL SurfaceBuffer releases.
*/
fun drainOhosSurfaceBufferReleases() = Unit
package org.jetbrains.skia
import org.jetbrains.skia.impl.NativePointer
import kotlinx.cinterop.COpaquePointer
import platform.ArkTS.ArkTS_Napi_NativeModule.napi_env
import platform.ArkTS.ArkTS_Napi_NativeModule.napi_value
import kotlin.native.SymbolName
public object OHRenderNode {
fun createRootRenderNode(context: NativePointer?, callback: COpaquePointer?): napi_value{
val node = OHRenderNode_nCreateRootRenderNode(context, callback)
return node
}
fun setNodeConstructor(env: napi_env, func: napi_value) {
OHRenderNode_nSetNodeConstructor(env, func)
return
}
fun renderNodeDraw(env: napi_env, jsCanvas: napi_value, jsNode: napi_value) {
OHRenderNode_nRenderNodeDraw(env, jsCanvas, jsNode)
return
}
fun renderNodeNotifyRedraw(env: napi_env, jsNode: napi_value) {
OHRenderNode_nRenderNodeNotifyRedraw(env, jsNode)
return
}
fun setPixelRatio(ratio: Double) {
OHRenderNode_nSetPixelRatio(ratio)
return
}
fun buildInstance(env: napi_env, func: napi_value) {
OHRenderNode_nBuildInstance(env, func)
return
}
}
@ExternalSymbolName("org_jetbrains_skia_OHRenderNode__1nCreateRootRenderNode")
private external fun OHRenderNode_nCreateRootRenderNode(context: NativePointer?, callback: COpaquePointer?): napi_value
@ExternalSymbolName("org_jetbrains_skia_OHRenderNode__1nSetNodeConstructor")
private external fun OHRenderNode_nSetNodeConstructor(env: napi_env, func: napi_value)
@ExternalSymbolName("org_jetbrains_skia_OHRenderNode__1nRenderNodeDraw")
private external fun OHRenderNode_nRenderNodeDraw(env: napi_env, jsCanvas: napi_value, jsNode: napi_value)
@ExternalSymbolName("org_jetbrains_skia_OHRenderNode__1nRenderNodeNotifyRedraw")
private external fun OHRenderNode_nRenderNodeNotifyRedraw(env: napi_env, jsNode: napi_value)
@ExternalSymbolName("org_jetbrains_skia_OHRenderNode__1nSetPixelRatio")
private external fun OHRenderNode_nSetPixelRatio(ratio: Double)
@ExternalSymbolName("org_jetbrains_skia_OHRenderNode__1nBuildInstance")
private external fun OHRenderNode_nBuildInstance(env: napi_env, func: napi_value)
@ExternalSymbolName("org_jetbrains_skia_OHRenderNode__1nGetCurrentTimeMicros")
private external fun OHRenderNode_nGetCurrentTimeMicros(): Long
package org.jetbrains.skia
import org.jetbrains.skia.impl.*
/**
* Fusion-renderer (OHRender) only: create a [Typeface] directly from font [data].
*
* Backed by the OHRender fork's `SkTypeface::MakeFromData` (a data-only typeface;
* its glyphs are resolved later when `TypefaceFontProvider::registerTypeface`
* registers the buffer into the OHOS FontCollection by family name).
*
* This restores the `Typeface.makeFromData` binding that skiko removed in
* d04654c6 ("update to skia m126 #969") — upstream skia m126 dropped the public
* `SkTypeface::MakeFromData`, which is why it was removed for all targets. It is
* re-added here in the fusion ohos source set ONLY (paired with the
* `org_jetbrains_skia_Typeface__1nMakeFromData` C++ binding guarded by
* `#ifdef USE_OHRENDER`), so iOS / native / js / ohos-skia are unaffected.
*/
fun Typeface.Companion.makeFromData(data: Data, index: Int = 0): Typeface {
return try {
Stats.onNativeCall()
val ptr = _nMakeFromData(getPtr(data), index)
require(ptr != Native.NullPointer) { "Failed to create Typeface from data $data" }
Typeface(ptr)
} finally {
reachabilityBarrier(data)
}
}
@ExternalSymbolName("org_jetbrains_skia_Typeface__1nMakeFromData")
private external fun _nMakeFromData(dataPtr: NativePointer, index: Int): NativePointer
package org.jetbrains.skiko
import org.jetbrains.skia.ColorType
import platform.ArkTS.ArkTS_Napi_NativeModule.napi_env
import kotlin.concurrent.Volatile
object ArkTsContext {
@Volatile
private var napiEnv: napi_env? = null
val NapiEnv: napi_env get() = napiEnv ?: error("napiEnv is null")
fun initEnv(env: napi_env) {
napiEnv = env;
// Fusion-renderer only: align the platform-native 32-bit color order with OHRender's C++ side.
// OHRender is built with SK_R32_SHIFT defaulting to 0, so its native kN32 is RGBA_8888 (its
// image decoders and MakeRasterN32Premul all produce RGBA). skiko's commonMain default is
// ColorType.N32 = BGRA_8888, leaving the Kotlin side disagreeing with native: a BGRA dst (e.g.
// AnimatedImagePainter.readFrameToImage's makeN32Premul) reaches an RGBA codec and used to hit
// kInvalidConversion. Setting N32 = RGBA here makes the Kotlin side match native everywhere.
// The self-render (skia) source set has no ArkTsContext, so it keeps the BGRA default untouched.
ColorType.N32 = ColorType.RGBA_8888
}
private var ratio: Float = 1.0F
val Ratio: Float get() = ratio
fun initRatio(rat: Float) {
ratio = rat
}
}
/*
* Eazytec is pleased to support the open source community by making Skiko available.
* Copyright (C) 2026 Eazytec. All rights reserved.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.jetbrains.skiko
interface IClipboardProxy {
fun setText(text: String)
fun getText(): String?
fun hasText(): Boolean
}
object PlatformProxy {
var clipboardProxy: IClipboardProxy? = null
}
package org.jetbrains.skiko
import kotlinx.cinterop.COpaquePointer
import kotlinx.cinterop.CPointer
import kotlinx.cinterop.FloatVar
import kotlinx.cinterop.alloc
import kotlinx.cinterop.memScoped
import kotlinx.cinterop.nativeNullPtr
import kotlinx.cinterop.ptr
import kotlinx.cinterop.value
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.launch
import org.jetbrains.skia.ExternalSymbolName
import org.jetbrains.skia.impl.Managed
import org.jetbrains.skia.impl._nInvokeFinalizer
import platform.ArkUI.ArkUI_NativeModule.ArkUI_NodeHandle
import platform.ArkTS.ArkTS_Napi_NativeModule.napi_env
import platform.ArkTS.ArkTS_Napi_NativeModule.napi_value
import kotlin.concurrent.AtomicNativePtr
import kotlin.native.internal.NativePtr
import kotlin.native.ref.createCleaner
abstract class RenderNode protected constructor(val ptr: NativePtr, private val finalizer: NativePtr) :
Managed(ptr, finalizer, managed = false) {
private val _ptrAtomic = AtomicNativePtr(ptr)
@OptIn(kotlin.experimental.ExperimentalNativeApi::class)
private val mainCleaner = createCleaner(_ptrAtomic to finalizer) { (atomicPtr, fin) ->
val p = atomicPtr.value
if (p != nativeNullPtr && atomicPtr.compareAndSet(p, nativeNullPtr)) {
CoroutineScope(Dispatchers.Main).launch {
_nInvokeFinalizer(fin, p)
}
}
}
override fun close() {
val p = _ptrAtomic.value
if (p == nativeNullPtr) return
if (_ptrAtomic.compareAndSet(p, nativeNullPtr)) {
_ptr = NativePtr.NULL
CoroutineScope(Dispatchers.Main).launch {
_nInvokeFinalizer(finalizer, p)
}
}
}
override val isClosed: Boolean
get() = _ptrAtomic.value == nativeNullPtr
val id: Int by lazy { _nRenderNodeGetId(ptr) }
fun notifyRedraw() {
_nRenderNodeNotifyRedraw(ptr)
}
}
class JsRenderNode internal constructor(ptr: NativePtr) : RenderNode(ptr, FINALIZER) {
companion object {
val FINALIZER =_nRenderNodeGetJsNodeFinalizer()
var isContextReady: Boolean = false
private set
fun initJsRenderNodeContext(nodeConstructor: napi_value, statusModifyConstructor: napi_value,
drawCallback: COpaquePointer) {
_nRenderNodeSetNodeConstructor(ArkTsContext.NapiEnv, nodeConstructor)
_nRenderNodeNodeStatusModifyBuildInstance(ArkTsContext.NapiEnv, statusModifyConstructor)
_nRenderNodeNodeSetDrawCallback(drawCallback)
isContextReady = true
}
fun setPixelRatio(ratio: Double) {
_nRenderNodeNodeSetPixelRatio(ratio)
}
fun jsNodeDraw(canvas: napi_value, jsNode: napi_value) {
_nRenderNodeNodeJsNodeDraw(ArkTsContext.NapiEnv, jsNode, canvas)
}
}
constructor() : this(_nRenderNodeNodeCreateJsRenderNode(ArkTsContext.NapiEnv))
fun getJsNode(): napi_value {
return _nRenderNodeNodeGetJsNode(ptr)
}
fun draw(canvas: napi_value) {
_nRenderNodeNodeDraw(ptr, canvas)
}
}
class CRenderNode internal constructor(ptr: NativePtr) : RenderNode(ptr, FINALIZER) {
companion object {
val FINALIZER = _nRenderNodeGetCNodeFinalizer()
fun setCAPIFixed(fixed: Boolean) {
_nRenderNodeNodeSetCAPIFixed(fixed)
}
}
private var width: Int = 0
private var height: Int = 0
constructor(content: napi_value) : this(_nRenderNodeCreateCRenderNode(ArkTsContext.NapiEnv, content)) {}
val nodeHandle: ArkUI_NodeHandle
get() = _nRenderNodeGetNodeHandle(ptr)
fun reSize(width: Int, height: Int) {
if (this.width != width || this.height != height) {
this.width = width
this.height = height
_nRenderNodeReSize(ptr, width, height)
}
}
}
class ExternalRenderNode internal constructor(val nativePtr: NativePtr) : Managed(nativePtr, FIN, managed = false) {
private val _ptrAtomic = AtomicNativePtr(nativePtr)
@OptIn(kotlin.experimental.ExperimentalNativeApi::class)
private val mainCleaner = createCleaner(_ptrAtomic to FIN) { (atomicPtr, fin) ->
val p = atomicPtr.value
if (p != nativeNullPtr && atomicPtr.compareAndSet(p, nativeNullPtr)) {
CoroutineScope(Dispatchers.Main).launch {
_nInvokeFinalizer(fin, p)
}
}
}
override fun close() {
val p = _ptrAtomic.value
if (p == nativeNullPtr) return
if (_ptrAtomic.compareAndSet(p, nativeNullPtr)) {
_ptr = NativePtr.NULL
CoroutineScope(Dispatchers.Main).launch {
_nInvokeFinalizer(FIN, p)
}
}
}
override val isClosed: Boolean
get() = _ptrAtomic.value == nativeNullPtr
companion object {
private val FIN = _nExternalRenderNodeGetFinalizer()
// ArkTS path: wraps an ArkTS RenderNode (napi_value) directly as an ExternalRenderNode.
fun makeForArkTs(node: napi_value): ExternalRenderNode? {
val ptr = _nExternalRenderNodeMakeForArkTs(node)
if (ptr == NativePtr.NULL) return null
return ExternalRenderNode(ptr)
}
// CAPI path: converts an ArkTS FrameNode (napi_value) to ArkUI_NodeHandle inside C++, then wraps as an ExternalRenderNode.
fun makeForCApi(rootNodeId: Int, extNodeId: Int, node: napi_value): ExternalRenderNode? {
val ptr = _nExternalRenderNodeMakeForCApi(ArkTsContext.NapiEnv, rootNodeId, extNodeId, node)
if (ptr == NativePtr.NULL) return null
return ExternalRenderNode(ptr)
}
fun makeFromHandle(rootNodeId: Int, extNodeId: Int, handle: ArkUI_NodeHandle): ExternalRenderNode? {
val ptr = _nExternalRenderNodeMakeFromHandle(rootNodeId, extNodeId, handle)
if (ptr == NativePtr.NULL) return null
return ExternalRenderNode(ptr)
}
// Helper: mount a CAPI ArkUI_NodeHandle into a NodeContent container (100% width/height).
// Must be called before makeForCApi when the interop view is a CAPI node.
fun mountHandleToNodeContent(nodeContent: napi_value, handle: ArkUI_NodeHandle) {
_nExternalRenderNodeMountHandleToNodeContent(ArkTsContext.NapiEnv, nodeContent, handle)
}
fun mapFrameNodePos(rootNodeId: Int, extNodeId: Int, touchX: Double, touchY: Double): Pair<Float, Float> {
return memScoped {
val outX = alloc<FloatVar>()
val outY = alloc<FloatVar>()
_nExternalRenderNodeMapFrameNodePos(rootNodeId, extNodeId, touchX, touchY, outX.ptr, outY.ptr)
Pair(outX.value, outY.value)
}
}
}
fun mapPointFromGlobal(x: Double, y: Double): Pair<Float, Float> {
return memScoped {
val outX = alloc<FloatVar>()
val outY = alloc<FloatVar>()
_nExternalRenderNodeMapPointFromGlobal(nativePtr, x, y, outX.ptr, outY.ptr)
Pair(outX.value, outY.value)
}
}
}
@ExternalSymbolName("org_jetbrains_skia_RenderNode__1nRenderNodeGetNodeId")
private external fun _nRenderNodeGetId(ptr: NativePtr): Int
@ExternalSymbolName("org_jetbrains_skia_RenderNode__1nRenderNodeNotifyRedraw")
private external fun _nRenderNodeNotifyRedraw(ptr: NativePtr)
@ExternalSymbolName("org_jetbrains_skia_RenderNode__1nGetJsNodeFinalizer")
private external fun _nRenderNodeGetJsNodeFinalizer(): NativePtr
@ExternalSymbolName("org_jetbrains_skia_RenderNode__1nSetNodeConstructor")
private external fun _nRenderNodeSetNodeConstructor(env: napi_env, function: napi_value)
@ExternalSymbolName("org_jetbrains_skia_RenderNode__1nNodeStatusModifyBuildInstance")
private external fun _nRenderNodeNodeStatusModifyBuildInstance(env: napi_env, function: napi_value)
@ExternalSymbolName("org_jetbrains_skia_RenderNode__1nSetDrawCallback")
private external fun _nRenderNodeNodeSetDrawCallback(callback: COpaquePointer)
@ExternalSymbolName("org_jetbrains_skia_RenderNode__1nCreateJsRenderNode")
private external fun _nRenderNodeNodeCreateJsRenderNode(env: napi_env): NativePtr
@ExternalSymbolName("org_jetbrains_skia_RenderNode__1nSetPixelRatio")
private external fun _nRenderNodeNodeSetPixelRatio(ratio: Double)
@ExternalSymbolName("org_jetbrains_skia_RenderNode__1nRenderNodeGetJsNode")
private external fun _nRenderNodeNodeGetJsNode(ptr: NativePtr): napi_value
@ExternalSymbolName("org_jetbrains_skia_RenderNode__1nRenderNodeDraw")
private external fun _nRenderNodeNodeDraw(ptr: NativePtr, canvas: napi_value)
@ExternalSymbolName("org_jetbrains_skia_RenderNode__1nRenderNodeJsNodeDraw")
private external fun _nRenderNodeNodeJsNodeDraw(env: napi_env, node: napi_value, canvas: napi_value)
@ExternalSymbolName("org_jetbrains_skia_RenderNode__1nCreateCRenderNode")
private external fun _nRenderNodeCreateCRenderNode(napiEnv: napi_env, content: napi_value): NativePtr
@ExternalSymbolName("org_jetbrains_skia_RenderNode__1nRenderNodeReSize")
private external fun _nRenderNodeReSize(ptr: NativePtr, width: Int, height: Int)
@ExternalSymbolName("org_jetbrains_skia_RenderNode__1nRenderNodeGetNodeHandle")
private external fun _nRenderNodeGetNodeHandle(ptr: NativePtr): ArkUI_NodeHandle
@ExternalSymbolName("org_jetbrains_skia_RenderNode__1nGetCNodeFinalizer")
private external fun _nRenderNodeGetCNodeFinalizer(): NativePtr
@ExternalSymbolName("org_jetbrains_skia_RenderNode__1nSetCAPIFixed")
private external fun _nRenderNodeNodeSetCAPIFixed(fixed: Boolean)
@ExternalSymbolName("org_jetbrains_skia_ExternalRenderNode__1nGetExternalRenderNodeFinalizer")
private external fun _nExternalRenderNodeGetFinalizer(): NativePtr
@ExternalSymbolName("org_jetbrains_skia_ExternalRenderNode__1nMakeForArkTs")
private external fun _nExternalRenderNodeMakeForArkTs(ptr: napi_value): NativePtr
@ExternalSymbolName("org_jetbrains_skia_ExternalRenderNode__1nMakeForCApi")
private external fun _nExternalRenderNodeMakeForCApi(env: napi_env, rootNodeId: Int, extNodeId: Int, ptr: napi_value): NativePtr
@ExternalSymbolName("org_jetbrains_skia_ExternalRenderNode__1nMakeFromHandle")
private external fun _nExternalRenderNodeMakeFromHandle(rootNodeId: Int, extNodeId: Int, handle: ArkUI_NodeHandle): NativePtr
@ExternalSymbolName("org_jetbrains_skia_ExternalRenderNode__1nMountHandleToNodeContent")
private external fun _nExternalRenderNodeMountHandleToNodeContent(env: napi_env, nodeContent: napi_value, handle: ArkUI_NodeHandle)
@ExternalSymbolName("org_jetbrains_skia_ExternalRenderNode__1nMapFrameNodePos")
private external fun _nExternalRenderNodeMapFrameNodePos(
rootNodeId: Int, extNodeId: Int, touchX: Double, touchY: Double,
outX: CPointer<FloatVar>, outY: CPointer<FloatVar>
)
@ExternalSymbolName("org_jetbrains_skia_ExternalRenderNode__1nMapPointFromGlobal")
private external fun _nExternalRenderNodeMapPointFromGlobal(
ptr: NativePtr, x: Double, y: Double,
outX: CPointer<FloatVar>, outY: CPointer<FloatVar>
)
/*
* Eazytec is pleased to support the open source community by making Skiko available.
* Copyright (C) 2026 Eazytec. All rights reserved.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.jetbrains.skiko
import org.jetbrains.skia.Canvas
import org.jetbrains.skia.PixelGeometry
/**
* Generic layer for Skiko rendering.
*/
actual open class SkiaLayer {
/**
* Current graphics API used for rendering.
*/
actual var renderApi: GraphicsApi
get() = GraphicsApi.OPENGL
set(_) { throw UnsupportedOperationException() }
/**
* Current content scale.
*/
actual val contentScale: Float
get() = TODO("Not yet implemented")
/**
* Pixel geometry corresponding to graphics device which renders this layer
*/
actual val pixelGeometry: PixelGeometry
get() = TODO("Not yet implemented")
/**
* If rendering is full screen.
*/
actual var fullscreen: Boolean
get() = TODO("Not yet implemented")
set(value) {}
/**
* Underlying platform component.
*/
actual val component: Any?
get() = TODO("Not yet implemented")
/**
* Current render delegate used for rendering.
*/
actual var renderDelegate: SkikoRenderDelegate?
get() = TODO("Not yet implemented")
set(value) {}
/**
* Attach this SkikoView to platform container.
* Actual type of attach container is platform-specific.
*/
actual fun attachTo(container: Any) {
}
/**
* Detach this SkikoView from platform container.
*/
actual fun detach() {
}
/**
* Force redraw.
*/
actual fun needRender(throttledToVsync: Boolean) {
}
@Deprecated("Use needRender() instead", ReplaceWith("needRender()"))
actual fun needRedraw() = needRender()
/**
* Drawing function.
*/
internal actual fun draw(canvas: Canvas) {
}
}
/*
* Eazytec is pleased to support the open source community by making Skiko available.
* Copyright (C) 2026 Eazytec. All rights reserved.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.jetbrains.skiko
import org.jetbrains.skia.ExternalSymbolName
@ExternalSymbolName("skikobridge_napi_get_system_theme")
private external fun skikobridge_napi_get_system_theme(): Int
actual val currentSystemTheme: SystemTheme
get() = try {
val themeValue = skikobridge_napi_get_system_theme()
when (themeValue) {
1 -> SystemTheme.LIGHT
2 -> SystemTheme.DARK
else -> SystemTheme.UNKNOWN
}
} catch (_: Exception) {
SystemTheme.UNKNOWN
}
diff --git b/src/commonMain/cpp/common/FontMgrDefaultFactory.cc a/src/commonMain/cpp/common/FontMgrDefaultFactory.cc
index cd15290b..f36e1ff3 100644
--- b/src/commonMain/cpp/common/FontMgrDefaultFactory.cc
+++ a/src/commonMain/cpp/common/FontMgrDefaultFactory.cc
@@ -9,18 +9,23 @@
#include "ports/SkTypeface_win.h"
#endif
-#if (defined(SK_BUILD_FOR_UNIX) || defined(SK_BUILD_FOR_LINUX)) && !defined(SKIKO_WASM) && !defined(SK_BUILD_FOR_OHOS)
+#if (defined(SK_BUILD_FOR_UNIX) || defined(SK_BUILD_FOR_LINUX)) && !defined(SKIKO_WASM) && !defined(SK_BUILD_FOR_OHOS) && !defined(USE_OHRENDER)
#include "ports/SkFontMgr_fontconfig.h"
-#include "ports/SkFontScanner_FreeType.h"
#endif
-#ifdef SK_BUILD_FOR_OHOS
+#ifdef SK_BUILD_FOR_ANDROID
+#include "ports/SkFontMgr_android.h"
+#endif
+
+#if defined(SK_BUILD_FOR_OHOS) && !defined(USE_OHRENDER)
+// OHOS uses custom font manager implementation
+// 移除对 SkFontMgr_ohos.h 的包含,改为仅声明函数签名(实现已在 libskia.a 中)。
+// #include "src/ports/skia_ohos/SkFontMgr_ohos.h"
SK_API sk_sp<SkFontMgr> SkFontMgr_New_OHOS(const char* path = nullptr);
#endif
-#ifdef SK_BUILD_FOR_ANDROID
-#include "ports/SkFontMgr_android.h"
-#include "ports/SkFontScanner_FreeType.h"
+#ifdef USE_OHRENDER
+#include "core/SkFontMgr.h"
#endif
#ifdef SKIKO_WASM
@@ -41,15 +46,20 @@ sk_sp<SkFontMgr> SkFontMgrSkikoDefault() {
return SkFontMgr_New_DirectWrite();
#endif
-#if (defined(SK_BUILD_FOR_UNIX) || defined(SK_BUILD_FOR_LINUX)) && !defined(SKIKO_WASM) && !defined(SK_BUILD_FOR_OHOS)
- return SkFontMgr_New_FontConfig(nullptr, SkFontScanner_Make_FreeType());
+#ifdef USE_OHRENDER
+ return SkFontMgr::RefDefault();
+#endif
+
+#if (defined(SK_BUILD_FOR_UNIX) || defined(SK_BUILD_FOR_LINUX)) && !defined(SKIKO_WASM) && !defined(SK_BUILD_FOR_OHOS) && !defined(USE_OHRENDER)
+ return SkFontMgr_New_FontConfig(nullptr);
#endif
#ifdef SK_BUILD_FOR_ANDROID
- return SkFontMgr_New_Android(nullptr, SkFontScanner_Make_FreeType());
+ return SkFontMgr_New_Android(nullptr);
#endif
-#ifdef SK_BUILD_FOR_OHOS
+#if defined(SK_BUILD_FOR_OHOS) && !defined(USE_OHRENDER)
+ // Use OHOS font manager with system font config
return SkFontMgr_New_OHOS();
#endif
diff --git b/src/commonMain/cpp/common/FontMgrWithFallbackWrapper.cc a/src/commonMain/cpp/common/FontMgrWithFallbackWrapper.cc
index e2ba237d..7ee2c7ec 100644
--- b/src/commonMain/cpp/common/FontMgrWithFallbackWrapper.cc
+++ a/src/commonMain/cpp/common/FontMgrWithFallbackWrapper.cc
@@ -77,7 +77,11 @@ size_t TypefaceFontProviderWithFallback::registerTypeface(sk_sp<SkTypeface> type
sk_sp<SkTypeface> TypefaceFontProviderWithFallback::fallbackForChar(SkUnichar character) const {
for (const auto& typeface : registeredTypefaces) {
if (!typeface) continue;
+#if !defined(USE_OHRENDER) || defined(OHOS_CHECK_NOIMPLEMENTED)
auto glyph = typeface->unicharToGlyph(character);
+#else
+ int glyph = 0;
+#endif
if (glyph != 0) {
return typeface;
}
diff --git b/src/commonMain/cpp/common/include/mppinterop.h a/src/commonMain/cpp/common/include/mppinterop.h
index 2e38cbae..8f12d827 100644
--- b/src/commonMain/cpp/common/include/mppinterop.h
+++ a/src/commonMain/cpp/common/include/mppinterop.h
@@ -1,4 +1,6 @@
#pragma once
+#ifndef SKIKO_MPPINTEROP_H
+#define SKIKO_MPPINTEROP_H
#include "SkRect.h"
#include "SkTextBlob.h"
#include "SkFont.h"
@@ -45,3 +47,4 @@ namespace skija {
uint32_t from8To16(size_t i8);
};
}
+#endif // SKIKO_MPPINTEROP_H
diff --git b/src/commonMain/cpp/common/mppinterop.cc a/src/commonMain/cpp/common/mppinterop.cc
index b210b45c..57272b08 100644
--- b/src/commonMain/cpp/common/mppinterop.cc
+++ a/src/commonMain/cpp/common/mppinterop.cc
@@ -49,7 +49,7 @@ namespace skikoMpp {
if (run.fGlyphCount > 1 && SkScalarNearlyEqual(posBuffer[(run.fGlyphCount - 2) * 2], lastLeft))
lastWidth = 0;
else
- font.getWidths({&run.fGlyphIndices[run.fGlyphCount - 1], 1}, {&lastWidth, 1});
+ font.getWidths(&run.fGlyphIndices[run.fGlyphCount - 1], 1, &lastWidth);
auto runBounds = SkRect::MakeLTRB(posBuffer[0], posBuffer[1] + metrics.fAscent, lastLeft + lastWidth, posBuffer[1] + metrics.fDescent);
bounds->join(runBounds);
diff --git b/src/commonMain/cpp/common/node/RenderNode.cpp a/src/commonMain/cpp/common/node/RenderNode.cpp
index 391322d9..351f9833 100644
--- b/src/commonMain/cpp/common/node/RenderNode.cpp
+++ a/src/commonMain/cpp/common/node/RenderNode.cpp
@@ -1,5 +1,4 @@
#include <SkNWayCanvas.h>
-#include <SkPathBuilder.h>
#include <SkPicture.h>
#include <SkShadowUtils.h>
#include "node/RenderNode.h"
@@ -42,7 +41,7 @@ static SkColor multiplyAlpha(SkColor color, float alpha) {
return SkColorSetA(color, alpha * SkColorGetA(color));
}
-
+#ifndef USE_OHRENDER
class UnrollDrawableCanvas : public SkNWayCanvas {
public:
UnrollDrawableCanvas(SkCanvas* canvas)
@@ -55,6 +54,7 @@ protected:
drawable->draw(this, matrix);
}
};
+#endif
RenderNode::RenderNode(const sk_sp<RenderNodeContext>& context)
: context(context),
@@ -86,6 +86,9 @@ RenderNode::RenderNode(const sk_sp<RenderNodeContext>& context)
matrixIdentity(true),
matrixDirty(false) {
this->setCameraLocation(0.0f, 0.0f, DEFAULT_CAMERA_DISTANCE);
+#ifdef USE_OHRENDER
+ this->markUINodeUsage();
+#endif
}
RenderNode::~RenderNode() {
@@ -97,67 +100,122 @@ RenderNode::~RenderNode() {
void RenderNode::setLayerPaint(const std::optional<SkPaint>& layerPaint) {
this->layerPaint = layerPaint;
+#ifdef USE_OHRENDER
+ this->notifyDrawingChanged();
+#endif
}
void RenderNode::setBounds(const SkRect& bounds) {
this->bounds = bounds;
this->matrixDirty = true;
+#ifdef USE_OHRENDER
+ if (this->contentCache) {
+ bool needCalcMatrix = !this->pivot.isFinite() && !(isZero(this->rotationX) && isZero(this->rotationY) && isZero(this->rotationZ) && isZero(this->scaleX - 1) && isZero(this->scaleY - 1));
+ if (needCalcMatrix) {
+ this->updateMatrix();
+ }
+ SkMatrix m;
+ m.preTranslate(this->bounds.left(), this->bounds.top());
+ if (!this->matrixIdentity) {
+ m.preConcat(this->transformMatrix);
+ }
+ this->contentCache->applyMatrix(m);
+ } else {
+ this->notifyDrawingChanged();
+ }
+#endif
}
void RenderNode::setPivot(const SkPoint& pivot) {
this->pivot = pivot;
this->matrixDirty = true;
+#ifdef USE_OHRENDER
+ this->notifyDrawingChanged();
+#endif
}
void RenderNode::setAlpha(float alpha) {
this->alpha = alpha;
+#ifdef USE_OHRENDER
+ this->notifyDrawingChanged();
+#endif
}
void RenderNode::setScaleX(float scaleX) {
this->scaleX = scaleX;
this->matrixDirty = true;
+#ifdef USE_OHRENDER
+ this->notifyDrawingChanged();
+#endif
}
void RenderNode::setScaleY(float scaleY) {
this->scaleY = scaleY;
this->matrixDirty = true;
+#ifdef USE_OHRENDER
+ this->notifyDrawingChanged();
+#endif
}
void RenderNode::setTranslationX(float translationX) {
this->translationX = translationX;
this->matrixDirty = true;
+#ifdef USE_OHRENDER
+ this->notifyDrawingChanged();
+#endif
}
void RenderNode::setTranslationY(float translationY) {
this->translationY = translationY;
this->matrixDirty = true;
+#ifdef USE_OHRENDER
+ this->notifyDrawingChanged();
+#endif
}
void RenderNode::setShadowElevation(float shadowElevation) {
this->shadowElevation = shadowElevation;
+#ifdef USE_OHRENDER
+ this->notifyDrawingChanged();
+#endif
}
void RenderNode::setAmbientShadowColor(SkColor ambientShadowColor) {
this->ambientShadowColor = ambientShadowColor;
+#ifdef USE_OHRENDER
+ this->notifyDrawingChanged();
+#endif
}
void RenderNode::setSpotShadowColor(SkColor spotShadowColor) {
this->spotShadowColor = spotShadowColor;
+#ifdef USE_OHRENDER
+ this->notifyDrawingChanged();
+#endif
}
void RenderNode::setRotationX(float rotationX) {
this->rotationX = rotationX;
this->matrixDirty = true;
+#ifdef USE_OHRENDER
+ this->notifyDrawingChanged();
+#endif
}
void RenderNode::setRotationY(float rotationY) {
this->rotationY = rotationY;
this->matrixDirty = true;
+#ifdef USE_OHRENDER
+ this->notifyDrawingChanged();
+#endif
}
void RenderNode::setRotationZ(float rotationZ) {
this->rotationZ = rotationZ;
this->matrixDirty = true;
+#ifdef USE_OHRENDER
+ this->notifyDrawingChanged();
+#endif
}
float RenderNode::getCameraDistance() const {
@@ -167,6 +225,9 @@ float RenderNode::getCameraDistance() const {
void RenderNode::setCameraDistance(float cameraDistance) {
this->setCameraLocation(0.0f, 0.0f, cameraDistance);
this->matrixDirty = true;
+#ifdef USE_OHRENDER
+ this->notifyDrawingChanged();
+#endif
}
void RenderNode::setClipRect(const std::optional<SkRect>& clipRect, SkClipOp op, bool doAntiAlias) {
@@ -175,6 +236,9 @@ void RenderNode::setClipRect(const std::optional<SkRect>& clipRect, SkClipOp op,
this->clipPath.reset();
this->clipOp = op;
this->clipAntiAlias = doAntiAlias;
+#ifdef USE_OHRENDER
+ this->notifyDrawingChanged();
+#endif
}
void RenderNode::setClipRRect(const std::optional<SkRRect>& clipRRect, SkClipOp op, bool doAntiAlias) {
@@ -183,6 +247,9 @@ void RenderNode::setClipRRect(const std::optional<SkRRect>& clipRRect, SkClipOp
this->clipPath.reset();
this->clipOp = op;
this->clipAntiAlias = doAntiAlias;
+#ifdef USE_OHRENDER
+ this->notifyDrawingChanged();
+#endif
}
void RenderNode::setClipPath(const std::optional<SkPath>& clipPath, SkClipOp op, bool doAntiAlias) {
@@ -191,10 +258,16 @@ void RenderNode::setClipPath(const std::optional<SkPath>& clipPath, SkClipOp op,
this->clipPath = clipPath;
this->clipOp = op;
this->clipAntiAlias = doAntiAlias;
+#ifdef USE_OHRENDER
+ this->notifyDrawingChanged();
+#endif
}
void RenderNode::setClip(bool clip) {
this->clip = clip;
+#ifdef USE_OHRENDER
+ this->notifyDrawingChanged();
+#endif
}
const SkMatrix& RenderNode::getMatrix() {
@@ -206,10 +279,27 @@ SkCanvas *RenderNode::beginRecording() {
bool measureDrawBounds = !clip || shadowElevation > 0.0f;
const SkRect& bounds = measureDrawBounds ? PICTURE_BOUNDS : SkRect::MakeWH(this->bounds.width(), this->bounds.height());
SkBBHFactory* bbhFactory = measureDrawBounds ? this->bbhFactory : nullptr;
+#ifdef USE_OHRENDER
+ if (this->contentCache) {
+ SkCanvas *canvas = nullptr;
+ // beginRecording is ok but cannot reuse the node cache
+ if ((canvas = this->contentCache->beginUpdate())) {
+ return canvas;
+ }
+ this->contentCache = nullptr;
+ }
+#endif
return this->recorder.beginRecording(bounds, bbhFactory);
}
void RenderNode::endRecording() {
+#ifdef USE_OHRENDER
+ if (this->contentCache && this->contentCache->endUpdate()) {
+ return;
+ } else {
+ this->notifyDrawingChanged();
+ }
+#endif
this->contentCache = this->recorder.finishRecordingAsDrawable();
}
@@ -260,10 +350,15 @@ SkRect RenderNode::onGetBounds() {
}
sk_sp<SkPicture> RenderNode::onMakePictureSnapshot() {
- SkCanvas* canvas = this->beginRecording();
+#ifndef USE_OHRENDER
+ const SkRect bounds = this->getBounds();
+ SkCanvas* canvas = this->recorder.beginRecording(bounds);
UnrollDrawableCanvas unrollCanvas(canvas);
this->draw(&unrollCanvas);
return this->recorder.finishRecordingAsPicture();
+#else
+ return nullptr;
+#endif
}
// Adoption from frameworks/base/libs/hwui/RenderProperties.cpp
@@ -307,9 +402,9 @@ void RenderNode::updateMatrix() {
void RenderNode::drawShadow(SkCanvas *canvas, const LightGeometry& lightGeometry, const LightInfo& lightInfo) {
SkPath tmpPath, *path = &tmpPath;
if (this->clipRect) {
- tmpPath = SkPathBuilder().addRect(*this->clipRect).detach();
+ tmpPath.addRect(*this->clipRect);
} else if (this->clipRRect) {
- tmpPath = SkPathBuilder().addRRect(*this->clipRRect).detach();
+ tmpPath.addRRect(*this->clipRRect);
} else if (this->clipPath) {
path = &*this->clipPath;
} else {
diff --git b/src/commonMain/cpp/common/shaper/FontRunIterator.cc a/src/commonMain/cpp/common/shaper/FontRunIterator.cc
index 9fd101bc..a5006b7c 100644
--- b/src/commonMain/cpp/common/shaper/FontRunIterator.cc
+++ a/src/commonMain/cpp/common/shaper/FontRunIterator.cc
@@ -16,8 +16,12 @@ bool can_handle_cluster(SkTypeface* typeface, const char* clusterStart, const ch
const char *ptr = clusterStart;
while (ptr < clusterEnd) {
SkUnichar u = utf8_next(&ptr, clusterEnd);
+#if !defined(USE_OHRENDER) || defined(OHOS_CHECK_NOIMPLEMENTED)
if (0 == typeface->unicharToGlyph(u))
return false;
+#else
+ return false;
+#endif
}
return true;
}
@@ -63,7 +67,9 @@ void FontRunIterator::consume() {
// Do not switch font on control, whitespace or punct
if (ptr == clusterEnd
+#if !defined(USE_OHRENDER) || defined(OHOS_CHECK_NOIMPLEMENTED)
&& fCurrentFont->getTypeface()->unicharToGlyph(u) != 0
+#endif
&& (u_iscntrl(u)
|| (fApproximateSpaces && u_isWhitespace(u))
|| (fApproximatePunctuation && u_ispunct(u))))
diff --git b/src/nativeJsMain/cpp/BackendRenderTarget.cc a/src/nativeJsMain/cpp/BackendRenderTarget.cc
index 84ddad87..0c463234 100644
--- b/src/nativeJsMain/cpp/BackendRenderTarget.cc
+++ a/src/nativeJsMain/cpp/BackendRenderTarget.cc
@@ -40,7 +40,6 @@ SKIKO_EXPORT KNativePointer BackendRenderTarget_nMakeMetal
#ifdef SK_DIRECT3D
#include "ganesh/d3d/GrD3DTypes.h"
-#include "ganesh/d3d/GrD3DBackendSurface.h"
#endif
SKIKO_EXPORT KNativePointer BackendRenderTarget_MakeDirect3D
@@ -53,9 +52,7 @@ SKIKO_EXPORT KNativePointer BackendRenderTarget_MakeDirect3D
texResInfo.fFormat = static_cast<DXGI_FORMAT>(format);
texResInfo.fSampleCount = static_cast<uint32_t>(sampleCnt);
texResInfo.fLevelCount = static_cast<uint32_t>(levelCnt);
- GrBackendRenderTarget* instance = new GrBackendRenderTarget(
- GrBackendRenderTargets::MakeD3D(width, height, texResInfo)
- );
+ GrBackendRenderTarget* instance = new GrBackendRenderTarget(width, height, texResInfo);
return instance;
#else
return 0;
diff --git b/src/nativeJsMain/cpp/Canvas.cc a/src/nativeJsMain/cpp/Canvas.cc
index fb987095..eb5dd808 100644
--- b/src/nativeJsMain/cpp/Canvas.cc
+++ a/src/nativeJsMain/cpp/Canvas.cc
@@ -3,7 +3,9 @@
#include "SkRRect.h"
#include "SkTextBlob.h"
#include "SkVertices.h"
+#ifndef USE_OHRENDER
#include "hb.h"
+#endif
#include "common.h"
static void deleteCanvas(SkCanvas* canvas) {
@@ -35,7 +37,7 @@ SKIKO_EXPORT void org_jetbrains_skia_Canvas__1nDrawPoints
SkCanvas* canvas = reinterpret_cast<SkCanvas*>((canvasPtr));
SkPaint* paint = reinterpret_cast<SkPaint*>((paintPtr));
SkCanvas::PointMode skMode = static_cast<SkCanvas::PointMode>(mode);
- canvas->drawPoints(skMode, {reinterpret_cast<SkPoint*>(coords), coordsCount / 2}, *paint);
+ canvas->drawPoints(skMode, coordsCount / 2, reinterpret_cast<SkPoint*>(coords), *paint);
}
SKIKO_EXPORT void org_jetbrains_skia_Canvas__1nDrawLine
diff --git b/src/nativeJsMain/cpp/DirectContext.cc a/src/nativeJsMain/cpp/DirectContext.cc
index b2098969..78a21bff 100644
--- b/src/nativeJsMain/cpp/DirectContext.cc
+++ a/src/nativeJsMain/cpp/DirectContext.cc
@@ -3,6 +3,10 @@
#include "common.h"
#include "ganesh/gl/GrGLDirectContext.h" // TODO: skia update: check if it's correct
+#ifdef SK_BUILD_FOR_OHOS
+#include "GrGLMakeNativeInterface_ohos.h"
+#endif
+
#ifdef SK_METAL
#include "ganesh/mtl/GrMtlBackendContext.h"
#include "ganesh/mtl/GrMtlDirectContext.h"
@@ -10,12 +14,34 @@
#ifdef SK_DIRECT3D
#include "ganesh/d3d/GrD3DBackendContext.h"
-#include "ganesh/d3d/GrD3DDirectContext.h"
#endif
SKIKO_EXPORT KNativePointer org_jetbrains_skia_DirectContext__1nMakeGL
() {
- return static_cast<KNativePointer>(GrDirectContexts::MakeGL().release());
+#ifdef SK_BUILD_FOR_OHOS
+ sk_sp<const GrGLInterface> interface = GrGLMakeNativeInterface_OHOS();
+#else
+ sk_sp<const GrGLInterface> interface = GrGLMakeNativeInterface();
+#endif
+ if (!interface) {
+ sk_sp<GrDirectContext> context = GrDirectContexts::MakeGL();
+ if (!context) {
+ return nullptr;
+ }
+ return static_cast<KNativePointer>(context.release());
+ }
+
+ if (!interface->validate()) {
+ return nullptr;
+ }
+
+ sk_sp<GrDirectContext> context = GrDirectContexts::MakeGL(interface);
+
+ if (!context) {
+ return nullptr;
+ }
+
+ return static_cast<KNativePointer>(context.release());
}
SKIKO_EXPORT KNativePointer org_jetbrains_skia_DirectContext__1nMakeGLWithInterface
@@ -49,7 +75,7 @@ SKIKO_EXPORT KNativePointer org_jetbrains_skia_DirectContext__1nMakeDirect3D
backendContext.fAdapter.retain(adapter);
backendContext.fDevice.retain(device);
backendContext.fQueue.retain(queue);
- sk_sp<GrDirectContext> instance = GrDirectContexts::MakeD3D(backendContext);
+ sk_sp<GrDirectContext> instance = GrDirectContext::MakeDirect3D(backendContext);
return static_cast<KNativePointer>(instance.release());
#else // SK_DIRECT3D
return nullptr;
@@ -69,18 +95,6 @@ SKIKO_EXPORT void org_jetbrains_skia_DirectContext__1nFlush
context->flush(skSurface);
}
-SKIKO_EXPORT KLong org_jetbrains_skia_DirectContext__1nGetResourceCacheLimit
- (KNativePointer ptr) {
- GrDirectContext* context = reinterpret_cast<GrDirectContext*>(ptr);
- return (KLong) context->getResourceCacheLimit();
-}
-
-SKIKO_EXPORT void org_jetbrains_skia_DirectContext__1nSetResourceCacheLimit
- (KNativePointer ptr, KLong maxResourceBytes) {
- GrDirectContext* context = reinterpret_cast<GrDirectContext*>(ptr);
- context->setResourceCacheLimit((size_t) maxResourceBytes);
-}
-
GrSyncCpu grSyncCpuFromBool(bool syncCpu) {
if (syncCpu) return GrSyncCpu::kYes;
return GrSyncCpu::kNo;
diff --git b/src/nativeJsMain/cpp/Font.cc a/src/nativeJsMain/cpp/Font.cc
index 4dfe9217..2502f9dc 100644
--- b/src/nativeJsMain/cpp/Font.cc
+++ a/src/nativeJsMain/cpp/Font.cc
@@ -204,15 +204,19 @@ SKIKO_EXPORT void org_jetbrains_skia_Font__1nGetUTF32Glyphs
(KNativePointer ptr, KInt* uniArr, KInt uniArrLen, KShort* resultGlyphs) {
SkFont* instance = reinterpret_cast<SkFont*>(ptr);
instance->unicharsToGlyphs(
- {reinterpret_cast<SkUnichar*>(uniArr), uniArrLen},
- {reinterpret_cast<SkGlyphID*>(resultGlyphs), uniArrLen}
+ reinterpret_cast<SkUnichar*>(uniArr), uniArrLen,
+ reinterpret_cast<SkGlyphID*>(resultGlyphs)
);
}
SKIKO_EXPORT KShort org_jetbrains_skia_Font__1nGetUTF32Glyph
(KNativePointer ptr, KInt uni) {
SkFont* instance = reinterpret_cast<SkFont*>(ptr);
+#if !defined(USE_OHRENDER) || defined(OHOS_CHECK_NOIMPLEMENTED)
return instance->unicharToGlyph(uni);
+#else
+ return 0;
+#endif
}
SKIKO_EXPORT KInt org_jetbrains_skia_Font__1nGetStringGlyphsCount
@@ -246,7 +250,7 @@ SKIKO_EXPORT KFloat org_jetbrains_skia_Font__1nMeasureTextWidth
SKIKO_EXPORT void org_jetbrains_skia_Font__1nGetWidths
(KNativePointer ptr, KShort* glyphs, KInt count, KFloat* widths) {
SkFont* instance = reinterpret_cast<SkFont*>(ptr);
- instance->getWidths({reinterpret_cast<SkGlyphID*>(glyphs), count}, {widths, count});
+ instance->getWidths(reinterpret_cast<SkGlyphID*>(glyphs), count, widths);
}
SKIKO_EXPORT void org_jetbrains_skia_Font__1nGetBounds
@@ -254,13 +258,13 @@ SKIKO_EXPORT void org_jetbrains_skia_Font__1nGetBounds
SkFont* instance = reinterpret_cast<SkFont*>(ptr);
SkPaint* paint = reinterpret_cast<SkPaint*>(paintPtr);
std::vector<SkRect> bounds(count);
- instance->getBounds({reinterpret_cast<SkGlyphID*>(glyphs), count}, {bounds.data(), count}, paint);
+ instance->getBounds(reinterpret_cast<SkGlyphID*>(glyphs), count, bounds.data(), paint);
for (int i = 0; i < count; ++i) {
SkRect b = bounds[i];
res[4*i] = b.left();
- res[4*i + 1] = b.top();
- res[4*i + 2] = b.right();
+ res[4*i + 1] = b.right();
+ res[4*i + 2] = b.top();
res[4*i + 3] = b.bottom();
}
}
@@ -270,7 +274,7 @@ SKIKO_EXPORT void org_jetbrains_skia_Font__1nGetPositions
SkFont* instance = reinterpret_cast<SkFont*>(ptr);
std::vector<SkPoint> positions(count);
- instance->getPos({reinterpret_cast<SkGlyphID*>(glyphs), count}, {positions.data(), count}, {dx, dy});
+ instance->getPos(reinterpret_cast<SkGlyphID*>(glyphs), count, positions.data(), {dx, dy});
for (int i = 0; i < count; i++) {
res[2*i] = positions[i].fX;
@@ -281,18 +285,15 @@ SKIKO_EXPORT void org_jetbrains_skia_Font__1nGetPositions
SKIKO_EXPORT void org_jetbrains_skia_Font__1nGetXPositions
(KNativePointer ptr, KShort* glyphs, KFloat dx, KInt count, KFloat* positions) {
SkFont* instance = reinterpret_cast<SkFont*>(ptr);
- instance->getXPos({reinterpret_cast<SkGlyphID*>(glyphs), count}, {positions, count}, dx);
+ instance->getXPos(reinterpret_cast<SkGlyphID*>(glyphs), count, positions, dx);
}
SKIKO_EXPORT KNativePointer org_jetbrains_skia_Font__1nGetPath
(KNativePointer ptr, KShort glyph) {
SkFont* instance = reinterpret_cast<SkFont*>(ptr);
- std::optional<SkPath> pathOpt = instance->getPath(glyph);
- if (pathOpt.has_value()) {
- SkPath* path = new SkPath(pathOpt.value());
- return reinterpret_cast<KNativePointer>(path);
- }
- return 0;
+ SkPath* path = new SkPath();
+ instance->getPath(glyph, path);
+ return reinterpret_cast<KNativePointer>(path);
}
@@ -304,10 +305,11 @@ SKIKO_EXPORT KNativePointer org_jetbrains_skia_Font__1nGetPaths
std::vector<KNativePointer>* paths;
} ctx = { new std::vector<KNativePointer>() };
- instance->getPaths({reinterpret_cast<SkGlyphID*>(glyphs), count}, [](const SkPath* orig, const SkMatrix& mx, void* voidCtx) {
+ instance->getPaths(reinterpret_cast<SkGlyphID*>(glyphs), count, [](const SkPath* orig, const SkMatrix& mx, void* voidCtx) {
Ctx* ctx = static_cast<Ctx*>(voidCtx);
if (orig) {
- SkPath* path = new SkPath(orig->makeTransform(mx));
+ SkPath* path = new SkPath();
+ orig->transform(mx, path);
ctx->paths->push_back(reinterpret_cast<KNativePointer>(path));
}
}, &ctx);
diff --git b/src/nativeJsMain/cpp/Image.cc a/src/nativeJsMain/cpp/Image.cc
index f58cbb1e..d23ace8b 100644
--- b/src/nativeJsMain/cpp/Image.cc
+++ a/src/nativeJsMain/cpp/Image.cc
@@ -2,15 +2,16 @@
#include "SkData.h"
#include "SkImage.h"
#include "SkBitmap.h"
-#include "ganesh/GrDirectContext.h"
#include "SkShader.h"
#include "SkEncodedImageFormat.h"
+#ifndef USE_OHRENDER
+#include "ganesh/GrDirectContext.h"
#include "encode/SkPngEncoder.h"
#include "encode/SkJpegEncoder.h"
#include "encode/SkWebpEncoder.h"
-#include "common.h"
#include "include/gpu/ganesh/SkImageGanesh.h"
-
+#endif
+#include "common.h"
SKIKO_EXPORT KNativePointer org_jetbrains_skia_Image__1nMakeRaster
(KInt width, KInt height, KInt colorType, KInt alphaType, KNativePointer colorSpacePtr, KByte* bytesArr, KInt rowBytes) {
@@ -73,6 +74,7 @@ SKIKO_EXPORT KNativePointer org_jetbrains_skia_Image__1nEncodeToData
SkImage* instance = reinterpret_cast<SkImage*>((ptr));
SkEncodedImageFormat skFormat = static_cast<SkEncodedImageFormat>(format);
if (!instance->isTextureBacked()) {
+#ifndef USE_OHRENDER
switch (skFormat) {
case SkEncodedImageFormat::kPNG: {
SkPngEncoder::Options options = SkPngEncoder::Options();
@@ -95,6 +97,10 @@ SKIKO_EXPORT KNativePointer org_jetbrains_skia_Image__1nEncodeToData
default:
break;
}
+#else
+ SkData* data = instance->encodeToData(skFormat, quality).release();
+ return reinterpret_cast<KNativePointer>(data);
+#endif
}
return reinterpret_cast<KNativePointer>(0);
}
@@ -159,6 +165,7 @@ SKIKO_EXPORT KBoolean org_jetbrains_skia_Image__1nScalePixels
SKIKO_EXPORT KNativePointer org_jetbrains_skia_Image__1nAdoptTextureFrom
(KNativePointer contextPtr, KNativePointer backendTexturePtr, KInt surfaceOrigin, KInt colorType) {
+#ifndef USE_OHRENDER
GrDirectContext* context = reinterpret_cast<GrDirectContext*>(contextPtr);
GrBackendTexture* backendTexture = reinterpret_cast<GrBackendTexture*>(backendTexturePtr);
@@ -170,4 +177,7 @@ SKIKO_EXPORT KNativePointer org_jetbrains_skia_Image__1nAdoptTextureFrom
);
return reinterpret_cast<KNativePointer>(image.release());
+#else
+ return nullptr;
+#endif
}
\ No newline at end of file
diff --git b/src/nativeJsMain/cpp/ImageFilter.cc a/src/nativeJsMain/cpp/ImageFilter.cc
index c27c1152..6d2d7f34 100644
--- b/src/nativeJsMain/cpp/ImageFilter.cc
+++ a/src/nativeJsMain/cpp/ImageFilter.cc
@@ -45,10 +45,14 @@ SKIKO_EXPORT KNativePointer org_jetbrains_skia_ImageFilter__1nMakeColorFilter
SKIKO_EXPORT KNativePointer org_jetbrains_skia_ImageFilter__1nMakeCompose
(KNativePointer outerPtr, KNativePointer innerPtr) {
+#if !defined(USE_OHRENDER) || defined(OHOS_CHECK_NOIMPLEMENTED)
SkImageFilter* outer = reinterpret_cast<SkImageFilter*>((outerPtr));
SkImageFilter* inner = reinterpret_cast<SkImageFilter*>((innerPtr));
SkImageFilter* ptr = SkImageFilters::Compose(sk_ref_sp(outer), sk_ref_sp(inner)).release();
return reinterpret_cast<KNativePointer>(ptr);
+#else
+ return nullptr;
+#endif
}
SKIKO_EXPORT KNativePointer org_jetbrains_skia_ImageFilter__1nMakeDisplacementMap
@@ -64,18 +68,26 @@ SKIKO_EXPORT KNativePointer org_jetbrains_skia_ImageFilter__1nMakeDisplacementMa
SKIKO_EXPORT KNativePointer org_jetbrains_skia_ImageFilter__1nMakeDropShadow
(KFloat dx, KFloat dy, KFloat sigmaX, KFloat sigmaY, KInt color, KNativePointer inputPtr, KInt* cropRectInts) {
+#if !defined(USE_OHRENDER) || defined(OHOS_CHECK_NOIMPLEMENTED)
SkImageFilter* input = reinterpret_cast<SkImageFilter*>((inputPtr));
std::unique_ptr<SkIRect> crop = skija::IRect::toSkIRect(cropRectInts);
SkImageFilter* ptr = SkImageFilters::DropShadow(dx, dy, sigmaX, sigmaY, color, sk_ref_sp(input), crop.get()).release();
return reinterpret_cast<KNativePointer>(ptr);
+#else
+ return nullptr;
+#endif
}
SKIKO_EXPORT KNativePointer org_jetbrains_skia_ImageFilter__1nMakeDropShadowOnly
(KFloat dx, KFloat dy, KFloat sigmaX, KFloat sigmaY, KInt color, KNativePointer inputPtr, KInt* cropRectInts) {
+#if !defined(USE_OHRENDER) || defined(OHOS_CHECK_NOIMPLEMENTED)
SkImageFilter* input = reinterpret_cast<SkImageFilter*>((inputPtr));
std::unique_ptr<SkIRect> crop = skija::IRect::toSkIRect(cropRectInts);
SkImageFilter* ptr = SkImageFilters::DropShadowOnly(dx, dy, sigmaX, sigmaY, color, sk_ref_sp(input), crop.get()).release();
return reinterpret_cast<KNativePointer>(ptr);
+#else
+ return nullptr;
+#endif
}
@@ -150,15 +162,20 @@ SKIKO_EXPORT KNativePointer org_jetbrains_skia_ImageFilter__1nMakePicture
SKIKO_EXPORT KNativePointer org_jetbrains_skia_ImageFilter__1nMakeRuntimeShader
(KNativePointer runtimeShaderBuilderPtr, KInteropPointer childShaderName, KNativePointer inputPtr) {
+#ifndef USE_OHRENDER
SkRuntimeEffectBuilder* runtimeShaderBuilder = reinterpret_cast<SkRuntimeEffectBuilder*>(runtimeShaderBuilderPtr);
sk_sp<SkImageFilter> input = sk_ref_sp<SkImageFilter>(reinterpret_cast<SkImageFilter*>(inputPtr));
SkImageFilter* ptr = SkImageFilters::RuntimeShader(*runtimeShaderBuilder, reinterpret_cast<char *>(childShaderName), input).release();
return reinterpret_cast<KNativePointer>(ptr);
+#else
+ return nullptr;
+#endif
}
SKIKO_EXPORT KNativePointer org_jetbrains_skia_ImageFilter__1nMakeRuntimeShaderFromArray
(KNativePointer runtimeShaderBuilderPtr, KInteropPointerArray childShaderNamesArr, KNativePointerArray inputPtrsArray, KInt inputCount) {
+#ifndef USE_OHRENDER
SkRuntimeEffectBuilder* runtimeShaderBuilder = reinterpret_cast<SkRuntimeEffectBuilder*>(runtimeShaderBuilderPtr);
KNativePointer* inputPtrs = reinterpret_cast<KNativePointer*>(inputPtrsArray);
@@ -175,6 +192,9 @@ SKIKO_EXPORT KNativePointer org_jetbrains_skia_ImageFilter__1nMakeRuntimeShaderF
SkImageFilter* ptr = SkImageFilters::RuntimeShader(*runtimeShaderBuilder, childShaderNames.data(), inputChildren.data(), inputCount).release();
return reinterpret_cast<KNativePointer>(ptr);
+#else
+ return nullptr;
+#endif
}
SKIKO_EXPORT KNativePointer org_jetbrains_skia_ImageFilter__1nMakeTile
diff --git b/src/nativeJsMain/cpp/PaintFilterCanvas.cc a/src/nativeJsMain/cpp/PaintFilterCanvas.cc
index 93b10172..bced756e 100644
--- b/src/nativeJsMain/cpp/PaintFilterCanvas.cc
+++ a/src/nativeJsMain/cpp/PaintFilterCanvas.cc
@@ -1,8 +1,11 @@
#include "SkCanvas.h"
#include "SkDrawable.h"
-#include "SkPaintFilterCanvas.h"
#include "common.h"
+#ifndef USE_OHRENDER
+
+#include "SkPaintFilterCanvas.h"
+
class SkikoPaintFilterCanvas : public SkPaintFilterCanvas {
public:
SkikoPaintFilterCanvas(
@@ -62,3 +65,27 @@ SKIKO_EXPORT KNativePointer org_jetbrains_skia_PaintFilterCanvas__1nGetOnFilterP
return reinterpret_cast<KNativePointer>(canvas->onFilterPaint());
}
+#else // USE_OHRENDER
+
+// The OHOS fusion renderer does not route drawing through SkCanvas/SkDevice, so a
+// skia-side SkPaintFilterCanvas wrapper cannot intercept it. OHRender is also a
+// trimmed skia that does not provide the SkPaintFilterCanvas/SkNWayCanvas/
+// SkNoDrawCanvas implementations. Build PaintFilterCanvas out of the fusion target
+// (same as UnrollDrawableCanvas in node/RenderNode.cpp), but keep stub entry points
+// so the Kotlin bindings still resolve and libkn.so loads without undefined symbols.
+// PaintFilterCanvas is unsupported under fusion rendering.
+
+SKIKO_EXPORT void org_jetbrains_skia_PaintFilterCanvas__1nInit
+ (KNativePointer /*canvasPtr*/, KInteropPointer /*onFilter*/) {}
+
+SKIKO_EXPORT KNativePointer org_jetbrains_skia_PaintFilterCanvas__1nMake
+ (KNativePointer /*canvasPtr*/, KBoolean /*unrollDrawable*/) {
+ return nullptr;
+}
+
+SKIKO_EXPORT KNativePointer org_jetbrains_skia_PaintFilterCanvas__1nGetOnFilterPaint
+ (KNativePointer /*canvasPtr*/) {
+ return nullptr;
+}
+
+#endif // USE_OHRENDER
diff --git b/src/nativeJsMain/cpp/Path.cc a/src/nativeJsMain/cpp/Path.cc
index 8185dcf3..cd575fd0 100644
--- b/src/nativeJsMain/cpp/Path.cc
+++ a/src/nativeJsMain/cpp/Path.cc
@@ -73,6 +73,7 @@ SKIKO_EXPORT KBoolean org_jetbrains_skia_Path__1nIsConvex(KNativePointer ptr) {
return instance->isConvex();
}
+
SKIKO_EXPORT KBoolean org_jetbrains_skia_Path__1nIsOval(KNativePointer ptr, KInteropPointer resultArray) {
SkPath* instance = reinterpret_cast<SkPath*>((ptr));
SkRect bounds;
@@ -95,6 +96,17 @@ SKIKO_EXPORT KBoolean org_jetbrains_skia_Path__1nIsRRect(KNativePointer ptr, KIn
}
}
+
+SKIKO_EXPORT void org_jetbrains_skia_Path__1nReset(KNativePointer ptr) {
+ SkPath* instance = reinterpret_cast<SkPath*>((ptr));
+ instance->reset();
+}
+
+SKIKO_EXPORT void org_jetbrains_skia_Path__1nRewind(KNativePointer ptr) {
+ SkPath* instance = reinterpret_cast<SkPath*>((ptr));
+ instance->rewind();
+}
+
SKIKO_EXPORT KBoolean org_jetbrains_skia_Path__1nIsEmpty(KNativePointer ptr) {
SkPath* instance = reinterpret_cast<SkPath*>((ptr));
return instance->isEmpty();
@@ -120,12 +132,6 @@ SKIKO_EXPORT void org_jetbrains_skia_Path__1nSetVolatile(KNativePointer ptr, KBo
instance->setIsVolatile(isVolatile);
}
-SKIKO_EXPORT void org_jetbrains_skia_Path__1nSwap(KNativePointer ptr, KNativePointer otherPtr) {
- SkPath* instance = reinterpret_cast<SkPath*>((ptr));
- SkPath* other = reinterpret_cast<SkPath*>((otherPtr));
- instance->swap(*other);
-}
-
SKIKO_EXPORT KBoolean org_jetbrains_skia_Path__1nIsLineDegenerate(KFloat x0, KFloat y0, KFloat x1, KFloat y1, KBoolean exact) {
return SkPath::IsLineDegenerate({x0, y0}, {x1, y1}, exact);
}
@@ -138,6 +144,7 @@ SKIKO_EXPORT KBoolean org_jetbrains_skia_Path__1nIsCubicDegenerate(KFloat x0, KF
return SkPath::IsCubicDegenerate({x0, y0}, {x1, y1}, {x2, y2}, {x3, y3}, exact);
}
+
SKIKO_EXPORT KBoolean org_jetbrains_skia_Path__1nMaybeGetAsLine(KNativePointer ptr, KInteropPointer resultArray) {
SkPath* instance = reinterpret_cast<SkPath*>((ptr));
SkPoint line[2];
@@ -150,6 +157,7 @@ SKIKO_EXPORT KBoolean org_jetbrains_skia_Path__1nMaybeGetAsLine(KNativePointer p
}
}
+
SKIKO_EXPORT KInt org_jetbrains_skia_Path__1nGetPointsCount(KNativePointer ptr) {
SkPath* instance = reinterpret_cast<SkPath*>((ptr));
return instance->countPoints();
@@ -163,7 +171,7 @@ SKIKO_EXPORT void org_jetbrains_skia_Path__1nGetPoint(KNativePointer ptr, KInt i
SKIKO_EXPORT KInt org_jetbrains_skia_Path__1nGetPoints(KNativePointer ptr, KInteropPointer pointsArray, KInt max) {
SkPath* instance = reinterpret_cast<SkPath*>((ptr));
SkPoint* points = reinterpret_cast<SkPoint*>(pointsArray);
- return instance->getPoints({points, max});
+ return instance->getPoints(points, max);
}
SKIKO_EXPORT KInt org_jetbrains_skia_Path__1nCountVerbs(KNativePointer ptr) {
@@ -173,12 +181,19 @@ SKIKO_EXPORT KInt org_jetbrains_skia_Path__1nCountVerbs(KNativePointer ptr) {
SKIKO_EXPORT KInt org_jetbrains_skia_Path__1nGetVerbs(KNativePointer ptr, KByte* verbsArray, KInt max) {
SkPath* instance = reinterpret_cast<SkPath*>((ptr));
- return instance->getVerbs({reinterpret_cast<uint8_t *>(verbsArray), max});
+ return instance->getVerbs(reinterpret_cast<uint8_t *>(verbsArray), max);
}
+
SKIKO_EXPORT KInt org_jetbrains_skia_Path__1nApproximateBytesUsed(KNativePointer ptr) {
SkPath* instance = reinterpret_cast<SkPath*>((ptr));
- return static_cast<KInt>(instance->approximateBytesUsed());
+ return (KInt) instance->approximateBytesUsed();
+}
+
+SKIKO_EXPORT void org_jetbrains_skia_Path__1nSwap(KNativePointer ptr, KNativePointer otherPtr) {
+ SkPath* instance = reinterpret_cast<SkPath*>((ptr));
+ SkPath* other = reinterpret_cast<SkPath*>((otherPtr));
+ instance->swap(*other);
}
SKIKO_EXPORT void org_jetbrains_skia_Path__1nGetBounds(KNativePointer ptr, KInteropPointer resultArray) {
@@ -204,12 +219,94 @@ SKIKO_EXPORT KBoolean org_jetbrains_skia_Path__1nConservativelyContainsRect(KNat
return instance->conservativelyContainsRect(rect);
}
+SKIKO_EXPORT void org_jetbrains_skia_Path__1nIncReserve(KNativePointer ptr, int extraPtCount) {
+ SkPath* instance = reinterpret_cast<SkPath*>((ptr));
+ instance->incReserve(extraPtCount);
+}
+
+SKIKO_EXPORT void org_jetbrains_skia_Path__1nMoveTo(KNativePointer ptr, KFloat x, KFloat y) {
+ SkPath* instance = reinterpret_cast<SkPath*>((ptr));
+ instance->moveTo(x, y);
+}
+
+SKIKO_EXPORT void org_jetbrains_skia_Path__1nRMoveTo(KNativePointer ptr, KFloat dx, KFloat dy) {
+ SkPath* instance = reinterpret_cast<SkPath*>((ptr));
+ instance->rMoveTo(dx, dy);
+}
+
+SKIKO_EXPORT void org_jetbrains_skia_Path__1nLineTo(KNativePointer ptr, KFloat x, KFloat y) {
+ SkPath* instance = reinterpret_cast<SkPath*>((ptr));
+ instance->lineTo(x, y);
+}
+
+SKIKO_EXPORT void org_jetbrains_skia_Path__1nRLineTo(KNativePointer ptr, KFloat dx, KFloat dy) {
+ SkPath* instance = reinterpret_cast<SkPath*>((ptr));
+ instance->rLineTo(dx, dy);
+}
+
+SKIKO_EXPORT void org_jetbrains_skia_Path__1nQuadTo(KNativePointer ptr, KFloat x1, KFloat y1, KFloat x2, KFloat y2) {
+ SkPath* instance = reinterpret_cast<SkPath*>((ptr));
+ instance->quadTo(x1, y1, x2, y2);
+}
+
+SKIKO_EXPORT void org_jetbrains_skia_Path__1nRQuadTo(KNativePointer ptr, KFloat dx1, KFloat dy1, KFloat dx2, KFloat dy2) {
+ SkPath* instance = reinterpret_cast<SkPath*>((ptr));
+ instance->rQuadTo(dx1, dy1, dx2, dy2);
+}
+
+SKIKO_EXPORT void org_jetbrains_skia_Path__1nConicTo(KNativePointer ptr, KFloat x1, KFloat y1, KFloat x2, KFloat y2, KFloat w) {
+ SkPath* instance = reinterpret_cast<SkPath*>((ptr));
+ instance->conicTo(x1, y1, x2, y2, w);
+}
+
+SKIKO_EXPORT void org_jetbrains_skia_Path__1nRConicTo(KNativePointer ptr, KFloat dx1, KFloat dy1, KFloat dx2, KFloat dy2, KFloat w) {
+ SkPath* instance = reinterpret_cast<SkPath*>((ptr));
+ instance->rConicTo(dx1, dy1, dx2, dy2, w);
+}
+
+SKIKO_EXPORT void org_jetbrains_skia_Path__1nCubicTo(KNativePointer ptr, KFloat x1, KFloat y1, KFloat x2, KFloat y2, KFloat x3, KFloat y3) {
+ SkPath* instance = reinterpret_cast<SkPath*>((ptr));
+ instance->cubicTo(x1, y1, x2, y2, x3, y3);
+}
+
+SKIKO_EXPORT void org_jetbrains_skia_Path__1nRCubicTo(KNativePointer ptr, KFloat dx1, KFloat dy1, KFloat dx2, KFloat dy2, KFloat dx3, KFloat dy3) {
+ SkPath* instance = reinterpret_cast<SkPath*>((ptr));
+ instance->rCubicTo(dx1, dy1, dx2, dy2, dx3, dy3);
+}
+
+SKIKO_EXPORT void org_jetbrains_skia_Path__1nArcTo(KNativePointer ptr, KFloat left, KFloat top, KFloat right, KFloat bottom, KFloat startAngle, KFloat sweepAngle, KBoolean forceMoveTo) {
+ SkPath* instance = reinterpret_cast<SkPath*>((ptr));
+ instance->arcTo({left, top, right, bottom}, startAngle, sweepAngle, forceMoveTo);
+}
+
+SKIKO_EXPORT void org_jetbrains_skia_Path__1nTangentArcTo(KNativePointer ptr, KFloat x1, KFloat y1, KFloat x2, KFloat y2, KFloat radius) {
+ SkPath* instance = reinterpret_cast<SkPath*>((ptr));
+ instance->arcTo(x1, y1, x2, y2, radius);
+}
+
+SKIKO_EXPORT void org_jetbrains_skia_Path__1nEllipticalArcTo(KNativePointer ptr, KFloat rx, KFloat ry, KFloat xAxisRotate, KInt size, KInt direction, KFloat x, float y) {
+ SkPath* instance = reinterpret_cast<SkPath*>((ptr));
+ instance->arcTo(rx, ry, xAxisRotate, static_cast<SkPath::ArcSize>(size), static_cast<SkPathDirection>(direction), x, y);
+}
+
+SKIKO_EXPORT void org_jetbrains_skia_Path__1nREllipticalArcTo(KNativePointer ptr, KFloat rx, KFloat ry, KFloat xAxisRotate, KInt size, KInt direction, KFloat dx, float dy) {
+ SkPath* instance = reinterpret_cast<SkPath*>((ptr));
+ instance->rArcTo(rx, ry, xAxisRotate, static_cast<SkPath::ArcSize>(size), static_cast<SkPathDirection>(direction), dx, dy);
+}
+
+SKIKO_EXPORT void org_jetbrains_skia_Path__1nClosePath(KNativePointer ptr) {
+ SkPath* instance = reinterpret_cast<SkPath*>((ptr));
+ instance->close();
+}
+
+
SKIKO_EXPORT KInt org_jetbrains_skia_Path__1nConvertConicToQuads
(KFloat x0, KFloat y0, KFloat x1, KFloat y1, KFloat x2, KFloat y2, KFloat w, KInt pow2, KInteropPointer resultArray) {
SkPoint* result = reinterpret_cast<SkPoint*>(resultArray);
return SkPath::ConvertConicToQuads({x0, y0}, {x1, y1}, {x2, y2}, w, result, pow2);
}
+
SKIKO_EXPORT KBoolean org_jetbrains_skia_Path__1nIsRect
(KNativePointer ptr, KInteropPointer resultArray) {
SkPath* instance = reinterpret_cast<SkPath*>((ptr));
@@ -222,6 +319,101 @@ SKIKO_EXPORT KBoolean org_jetbrains_skia_Path__1nIsRect
}
}
+
+SKIKO_EXPORT void org_jetbrains_skia_Path__1nAddRect
+ (KNativePointer ptr, KFloat l, KFloat t, KFloat r, KFloat b, KInt dirInt, KInt start) {
+ SkPath* instance = reinterpret_cast<SkPath*>((ptr));
+ SkPathDirection dir = static_cast<SkPathDirection>(dirInt);
+ instance->addRect({l, t, r, b}, dir, start);
+}
+
+SKIKO_EXPORT void org_jetbrains_skia_Path__1nAddOval
+ (KNativePointer ptr, KFloat l, KFloat t, KFloat r, KFloat b, KInt dirInt, KInt start) {
+ SkPath* instance = reinterpret_cast<SkPath*>((ptr));
+ SkPathDirection dir = static_cast<SkPathDirection>(dirInt);
+ instance->addOval({l, t, r, b}, dir, start);
+}
+
+SKIKO_EXPORT void org_jetbrains_skia_Path__1nAddCircle
+ (KNativePointer ptr, KFloat x, KFloat y, KFloat r, KInt dirInt) {
+ SkPath* instance = reinterpret_cast<SkPath*>((ptr));
+ SkPathDirection dir = static_cast<SkPathDirection>(dirInt);
+ instance->addCircle(x, y, r, dir);
+}
+
+SKIKO_EXPORT void org_jetbrains_skia_Path__1nAddArc
+ (KNativePointer ptr, KFloat l, KFloat t, KFloat r, KFloat b, KFloat startAngle, KFloat sweepAngle) {
+ SkPath* instance = reinterpret_cast<SkPath*>((ptr));
+ instance->addArc({l, t, r, b}, startAngle, sweepAngle);
+}
+
+SKIKO_EXPORT void org_jetbrains_skia_Path__1nAddRRect
+ (KNativePointer ptr, KFloat l, KFloat t, KFloat r, KFloat b, KFloat* radii, KInt radiiSize, KInt dirInt, KInt start) {
+ SkPath* instance = reinterpret_cast<SkPath*>((ptr));
+ SkRRect rrect = skija::RRect::toSkRRect(l, t, r, b, radii, radiiSize);
+ SkPathDirection dir = static_cast<SkPathDirection>(dirInt);
+ instance->addRRect(rrect, dir, start);
+}
+
+SKIKO_EXPORT void org_jetbrains_skia_Path__1nAddPoly
+ (KNativePointer ptr, KFloat* coords, KInt count, KBoolean close) {
+ SkPath* instance = reinterpret_cast<SkPath*>((ptr));
+ instance->addPoly(reinterpret_cast<SkPoint*>(coords), count, close);
+}
+
+
+SKIKO_EXPORT void org_jetbrains_skia_Path__1nAddPath
+ (KNativePointer ptr, KNativePointer srcPtr, KBoolean extend) {
+ SkPath* instance = reinterpret_cast<SkPath*>((ptr));
+ SkPath* src = reinterpret_cast<SkPath*>((srcPtr));
+ SkPath::AddPathMode mode = extend ? SkPath::AddPathMode::kExtend_AddPathMode : SkPath::AddPathMode::kAppend_AddPathMode;
+ instance->addPath(*src, mode);
+}
+
+SKIKO_EXPORT void org_jetbrains_skia_Path__1nAddPathOffset
+ (KNativePointer ptr, KNativePointer srcPtr, KFloat dx, KFloat dy, KBoolean extend) {
+ SkPath* instance = reinterpret_cast<SkPath*>((ptr));
+ SkPath* src = reinterpret_cast<SkPath*>((srcPtr));
+ SkPath::AddPathMode mode = extend ? SkPath::AddPathMode::kExtend_AddPathMode : SkPath::AddPathMode::kAppend_AddPathMode;
+ instance->addPath(*src, dx, dy, mode);
+}
+
+
+SKIKO_EXPORT void org_jetbrains_skia_Path__1nAddPathTransform
+ (KNativePointer ptr, KNativePointer srcPtr, KFloat* matrixArr, KBoolean extend) {
+ SkPath* instance = reinterpret_cast<SkPath*>((ptr));
+ SkPath* src = reinterpret_cast<SkPath*>((srcPtr));
+ std::unique_ptr<SkMatrix> matrix = skMatrix(matrixArr);
+ SkPath::AddPathMode mode = extend ? SkPath::AddPathMode::kExtend_AddPathMode : SkPath::AddPathMode::kAppend_AddPathMode;
+ instance->addPath(*src, *matrix, mode);
+}
+
+
+SKIKO_EXPORT void org_jetbrains_skia_Path__1nReverseAddPath
+ (KNativePointer ptr, KNativePointer srcPtr) {
+ SkPath* instance = reinterpret_cast<SkPath*>((ptr));
+ SkPath* src = reinterpret_cast<SkPath*>((srcPtr));
+ instance->reverseAddPath(*src);
+}
+
+SKIKO_EXPORT void org_jetbrains_skia_Path__1nOffset
+ (KNativePointer ptr, KFloat dx, KFloat dy, KNativePointer dstPtr) {
+ SkPath* instance = reinterpret_cast<SkPath*>((ptr));
+ SkPath* dst = reinterpret_cast<SkPath*>((dstPtr));
+ instance->offset(dx, dy, dst);
+}
+
+
+SKIKO_EXPORT void org_jetbrains_skia_Path__1nTransform
+ (KNativePointer ptr, KFloat* matrixArr, KNativePointer dstPtr, KBoolean pcBool) {
+ SkPath* instance = reinterpret_cast<SkPath*>((ptr));
+ SkPath* dst = reinterpret_cast<SkPath*>((dstPtr));
+ std::unique_ptr<SkMatrix> matrix = skMatrix(matrixArr);
+ SkApplyPerspectiveClip pc = pcBool ? SkApplyPerspectiveClip::kYes : SkApplyPerspectiveClip::kNo;
+ instance->transform(*matrix, dst, pc);
+}
+
+
SKIKO_EXPORT KBoolean org_jetbrains_skia_Path__1nGetLastPt
(KNativePointer ptr, KInteropPointer resultArray) {
SkPath* instance = reinterpret_cast<SkPath*>((ptr));
@@ -234,6 +426,13 @@ SKIKO_EXPORT KBoolean org_jetbrains_skia_Path__1nGetLastPt
}
}
+
+SKIKO_EXPORT void org_jetbrains_skia_Path__1nSetLastPt
+ (KNativePointer ptr, KFloat x, KFloat y) {
+ SkPath* instance = reinterpret_cast<SkPath*>((ptr));
+ instance->setLastPt(x, y);
+}
+
SKIKO_EXPORT KInt org_jetbrains_skia_Path__1nGetSegmentMasks
(KNativePointer ptr) {
SkPath* instance = reinterpret_cast<SkPath*>((ptr));
@@ -258,10 +457,15 @@ SKIKO_EXPORT void org_jetbrains_skia_Path__1nDumpHex
instance->dumpHex();
}
+
SKIKO_EXPORT KInt org_jetbrains_skia_Path__1nSerializeToBytes
(KNativePointer ptr, KInteropPointer dst) {
SkPath* instance = reinterpret_cast<SkPath*>((ptr));
+#if !defined(USE_OHRENDER) || defined(OHOS_CHECK_NOIMPLEMENTED)
size_t count = instance->writeToMemory(reinterpret_cast<void*>(dst));
+#else
+ size_t count = 0;
+#endif
if (count > std::numeric_limits<KInt>::max()) {
return -1;
} else {
@@ -269,6 +473,7 @@ SKIKO_EXPORT KInt org_jetbrains_skia_Path__1nSerializeToBytes
}
}
+
SKIKO_EXPORT KNativePointer org_jetbrains_skia_Path__1nMakeCombining
(KNativePointer aPtr, KNativePointer bPtr, KInt jop) {
SkPath* a = reinterpret_cast<SkPath*>((aPtr));
@@ -281,18 +486,24 @@ SKIKO_EXPORT KNativePointer org_jetbrains_skia_Path__1nMakeCombining
return 0;
}
+
SKIKO_EXPORT KNativePointer org_jetbrains_skia_Path__1nMakeFromBytes
(KByte* bytesArray, KInt size) {
+#if !defined(USE_OHRENDER) || defined(OHOS_CHECK_NOIMPLEMENTED)
+ SkPath* instance = new SkPath();
void* bytes = reinterpret_cast<void*>(bytesArray);
- std::optional<SkPath> pathOpt = SkPath::ReadFromMemory(bytes, size);
- if (pathOpt.has_value()) {
- SkPath* instance = new SkPath(pathOpt.value());
+ if (instance->readFromMemory(bytes, size)) {
return reinterpret_cast<KNativePointer>(instance);
} else {
+ delete instance;
return 0;
}
+#else
+ return 0;
+#endif
}
+
SKIKO_EXPORT KInt org_jetbrains_skia_Path__1nGetGenerationId
(KNativePointer ptr) {
SkPath* instance = reinterpret_cast<SkPath*>((ptr));
@@ -304,106 +515,3 @@ SKIKO_EXPORT KBoolean org_jetbrains_skia_Path__1nIsValid
SkPath* instance = reinterpret_cast<SkPath*>((ptr));
return instance->isValid();
}
-
-SKIKO_EXPORT KNativePointer org_jetbrains_skia_Path__1nMakeFromRaw
- (KFloat* ptsArray, KInt ptsCount,
- KByte* verbsArray, KInt verbsCount,
- KFloat* conicWeightsArray, KInt conicWeightsCount,
- KInt fillType, KBoolean isVolatile) {
- SkPath path = SkPath::Raw(
- SkSpan<const SkPoint>(reinterpret_cast<const SkPoint*>(ptsArray), ptsCount),
- SkSpan<const SkPathVerb>(reinterpret_cast<const SkPathVerb*>(verbsArray), verbsCount),
- SkSpan<const SkScalar>(conicWeightsArray, conicWeightsCount),
- static_cast<SkPathFillType>(fillType),
- isVolatile
- );
-
- SkPath* instance = new SkPath(path);
- return reinterpret_cast<KNativePointer>(instance);
-}
-
-SKIKO_EXPORT KNativePointer org_jetbrains_skia_Path__1nMakeFromRect
- (KFloat left, KFloat top, KFloat right, KFloat bottom,
- KInt fillType, KInt direction, KInt startIndex) {
- SkRect rect = SkRect::MakeLTRB(left, top, right, bottom);
- SkPath path = SkPath::Rect(
- rect,
- static_cast<SkPathFillType>(fillType),
- static_cast<SkPathDirection>(direction),
- startIndex
- );
- SkPath* instance = new SkPath(path);
- return reinterpret_cast<KNativePointer>(instance);
-}
-
-SKIKO_EXPORT KNativePointer org_jetbrains_skia_Path__1nMakeFromOval
- (KFloat left, KFloat top, KFloat right, KFloat bottom,
- KInt direction, KInt startIndex) {
- SkRect rect = SkRect::MakeLTRB(left, top, right, bottom);
- SkPath path = SkPath::Oval(
- rect,
- static_cast<SkPathDirection>(direction),
- startIndex
- );
- SkPath* instance = new SkPath(path);
- return reinterpret_cast<KNativePointer>(instance);
-}
-
-SKIKO_EXPORT KNativePointer org_jetbrains_skia_Path__1nMakeFromCircle
- (KFloat centerX, KFloat centerY, KFloat radius, KInt direction) {
- SkPath path = SkPath::Circle(
- centerX,
- centerY,
- radius,
- static_cast<SkPathDirection>(direction)
- );
- SkPath* instance = new SkPath(path);
- return reinterpret_cast<KNativePointer>(instance);
-}
-
-SKIKO_EXPORT KNativePointer org_jetbrains_skia_Path__1nMakeFromRRect
- (KFloat* radiiArray,
- KFloat left, KFloat top, KFloat right, KFloat bottom,
- KInt direction, KInt startIndex) {
- SkRect rect = SkRect::MakeLTRB(left, top, right, bottom);
-
- SkRRect rrect;
- rrect.setRectRadii(rect, reinterpret_cast<const SkVector*>(radiiArray));
-
- SkPath path = SkPath::RRect(
- rrect,
- static_cast<SkPathDirection>(direction),
- startIndex
- );
-
- SkPath* instance = new SkPath(path);
- return reinterpret_cast<KNativePointer>(instance);
-}
-
-SKIKO_EXPORT KNativePointer org_jetbrains_skia_Path__1nMakeFromRRectXY
- (KFloat left, KFloat top, KFloat right, KFloat bottom,
- KFloat rx, KFloat ry, KInt direction) {
- SkRect rect = SkRect::MakeLTRB(left, top, right, bottom);
- SkPath path = SkPath::RRect(
- rect,
- rx,
- ry,
- static_cast<SkPathDirection>(direction)
- );
- SkPath* instance = new SkPath(path);
- return reinterpret_cast<KNativePointer>(instance);
-}
-
-SKIKO_EXPORT KNativePointer org_jetbrains_skia_Path__1nMakeFromPolygon
- (KFloat* ptsArray, KInt ptsCount,
- KBoolean isClosed, KInt fillType, KBoolean isVolatile) {
- SkPath path = SkPath::Polygon(
- SkSpan<const SkPoint>(reinterpret_cast<const SkPoint*>(ptsArray), ptsCount),
- isClosed,
- static_cast<SkPathFillType>(fillType),
- isVolatile
- );
-
- SkPath* instance = new SkPath(path);
- return reinterpret_cast<KNativePointer>(instance);
-}
diff --git b/src/nativeJsMain/cpp/PathBuilder.cc a/src/nativeJsMain/cpp/PathBuilder.cc
index 6b0ae76c..2fb4d57a 100644
--- b/src/nativeJsMain/cpp/PathBuilder.cc
+++ a/src/nativeJsMain/cpp/PathBuilder.cc
@@ -50,7 +50,15 @@ SKIKO_EXPORT void org_jetbrains_skia_PathBuilder__1nMoveTo(KNativePointer ptr, K
SKIKO_EXPORT void org_jetbrains_skia_PathBuilder__1nRMoveTo(KNativePointer ptr, KFloat dx, KFloat dy) {
SkPathBuilder* builder = reinterpret_cast<SkPathBuilder*>((ptr));
+#ifdef USE_OHRENDER
+ //region ohos
+ SkPoint lastPoint = {0, 0};
+ builder->snapshot().getLastPt(&lastPoint);
+ builder->moveTo(lastPoint.x() + dx, lastPoint.y() + dy);
+ //endregion
+#else
builder->rMoveTo(dx, dy);
+#endif
}
SKIKO_EXPORT void org_jetbrains_skia_PathBuilder__1nLineTo(KNativePointer ptr, KFloat x, KFloat y) {
@@ -110,7 +118,15 @@ SKIKO_EXPORT void org_jetbrains_skia_PathBuilder__1nEllipticalArcTo(KNativePoint
SKIKO_EXPORT void org_jetbrains_skia_PathBuilder__1nREllipticalArcTo(KNativePointer ptr, KFloat rx, KFloat ry, KFloat xAxisRotate, KInt size, KInt direction, KFloat dx, KFloat dy) {
SkPathBuilder* builder = reinterpret_cast<SkPathBuilder*>((ptr));
+#ifdef USE_OHRENDER
+ //region ohos
+ SkPoint lastPoint = {0, 0};
+ builder->snapshot().getLastPt(&lastPoint);
+ builder->arcTo({rx, ry}, xAxisRotate, static_cast<SkPathBuilder::ArcSize>(size), static_cast<SkPathDirection>(direction), {lastPoint.x() + dx, lastPoint.y() + dy});
+ //endregion
+#else
builder->rArcTo({rx, ry}, xAxisRotate, static_cast<SkPathBuilder::ArcSize>(size), static_cast<SkPathDirection>(direction), {dx, dy});
+#endif
}
SKIKO_EXPORT void org_jetbrains_skia_PathBuilder__1nClosePath(KNativePointer ptr) {
@@ -151,31 +167,69 @@ SKIKO_EXPORT void org_jetbrains_skia_PathBuilder__1nAddRRect(KNativePointer ptr,
SKIKO_EXPORT void org_jetbrains_skia_PathBuilder__1nAddPoly(KNativePointer ptr, KInteropPointer coords, KInt _count, KBoolean close) {
SkPathBuilder* builder = reinterpret_cast<SkPathBuilder*>((ptr));
float* arr = reinterpret_cast<float*>(coords);
+#ifdef USE_OHRENDER
+ //region ohos
+ builder->addPolygon(reinterpret_cast<SkPoint*>(arr), _count, close);
+ //endregion
+#else
builder->addPolygon({reinterpret_cast<SkPoint*>(arr), _count}, close);
+#endif
}
SKIKO_EXPORT void org_jetbrains_skia_PathBuilder__1nAddPath(KNativePointer ptr, KNativePointer srcPtr, KInt mode) {
SkPathBuilder* builder = reinterpret_cast<SkPathBuilder*>((ptr));
SkPath* src = reinterpret_cast<SkPath*>((srcPtr));
+#ifdef USE_OHRENDER
+ //region ohos
+ SkPath path = builder->detach();
+ path.addPath(*src, static_cast<SkPath::AddPathMode>(mode));
+ *builder = path;
+ //endregion
+#else
builder->addPath(*src, static_cast<SkPath::AddPathMode>(mode));
+#endif
}
SKIKO_EXPORT void org_jetbrains_skia_PathBuilder__1nAddPathOffset(KNativePointer ptr, KNativePointer srcPtr, KFloat dx, KFloat dy, KInt mode) {
SkPathBuilder* builder = reinterpret_cast<SkPathBuilder*>((ptr));
SkPath* src = reinterpret_cast<SkPath*>((srcPtr));
+#ifdef USE_OHRENDER
+ //region ohos
+ SkPath path = builder->detach();
+ path.addPath(*src, dx, dy, static_cast<SkPath::AddPathMode>(mode));
+ *builder = path;
+ //endregion
+#else
builder->addPath(*src, dx, dy, static_cast<SkPath::AddPathMode>(mode));
+#endif
}
SKIKO_EXPORT void org_jetbrains_skia_PathBuilder__1nAddPathTransform(KNativePointer ptr, KNativePointer srcPtr, KInteropPointer matrixArr, KInt mode) {
SkPathBuilder* builder = reinterpret_cast<SkPathBuilder*>((ptr));
SkPath* src = reinterpret_cast<SkPath*>((srcPtr));
std::unique_ptr<SkMatrix> matrix = skMatrix(reinterpret_cast<KFloat*>(matrixArr));
+#ifdef USE_OHRENDER
+ //region ohos
+ SkPath path = builder->detach();
+ path.addPath(*src, *matrix, static_cast<SkPath::AddPathMode>(mode));
+ *builder = path;
+ //endregion
+#else
builder->addPath(*src, *matrix, static_cast<SkPath::AddPathMode>(mode));
+#endif
}
SKIKO_EXPORT void org_jetbrains_skia_PathBuilder__1nSetLastPt(KNativePointer ptr, KFloat x, KFloat y) {
SkPathBuilder* builder = reinterpret_cast<SkPathBuilder*>((ptr));
+#ifdef USE_OHRENDER
+ //region ohos
+ SkPath path = builder->detach();
+ path.setLastPt(x, y);
+ *builder = path;
+ //endregion
+#else
builder->setLastPt(x, y);
+#endif
}
SKIKO_EXPORT KNativePointer org_jetbrains_skia_PathBuilder__1nDetach(KNativePointer ptr) {
@@ -198,5 +252,13 @@ SKIKO_EXPORT void org_jetbrains_skia_PathBuilder__1nOffset(KNativePointer ptr, K
SKIKO_EXPORT void org_jetbrains_skia_PathBuilder__1nTransform(KNativePointer ptr, KInteropPointer matrixArr) {
SkPathBuilder* builder = reinterpret_cast<SkPathBuilder*>((ptr));
std::unique_ptr<SkMatrix> matrix = skMatrix(reinterpret_cast<KFloat*>(matrixArr));
+#ifdef USE_OHRENDER
+ //region ohos
+ SkPath path = builder->detach();
+ path.transform(*matrix);
+ *builder = path;
+ //endregion
+#else
builder->transform(*matrix);
+#endif
}
diff --git b/src/nativeJsMain/cpp/PathEffect.cc a/src/nativeJsMain/cpp/PathEffect.cc
index 191b1866..54c907f4 100644
--- b/src/nativeJsMain/cpp/PathEffect.cc
+++ a/src/nativeJsMain/cpp/PathEffect.cc
@@ -54,7 +54,7 @@ SKIKO_EXPORT KNativePointer org_jetbrains_skia_PathEffect__1nMakeCorner
SKIKO_EXPORT KNativePointer org_jetbrains_skia_PathEffect__1nMakeDash
(KFloat* intervalsArray, KInt count, KFloat phase) {
- SkPathEffect* ptr = SkDashPathEffect::Make({reinterpret_cast<float*>(intervalsArray), count}, phase).release();
+ SkPathEffect* ptr = SkDashPathEffect::Make(reinterpret_cast<float*>(intervalsArray), count, phase).release();
return reinterpret_cast<KNativePointer>(ptr);
}
diff --git b/src/nativeJsMain/cpp/PathMeasure.cc a/src/nativeJsMain/cpp/PathMeasure.cc
index 8f72624b..7f180ea5 100644
--- b/src/nativeJsMain/cpp/PathMeasure.cc
+++ a/src/nativeJsMain/cpp/PathMeasure.cc
@@ -100,7 +100,12 @@ SKIKO_EXPORT KBoolean org_jetbrains_skia_PathMeasure__1nGetMatrix
SKIKO_EXPORT KBoolean org_jetbrains_skia_PathMeasure__1nGetSegment
(KNativePointer ptr, KFloat startD, KFloat endD, KNativePointer dstPtr, KBoolean startWithMoveTo) {
SkPathMeasure* instance = reinterpret_cast<SkPathMeasure*>((ptr));
SkPathBuilder* dst = reinterpret_cast<SkPathBuilder*>((dstPtr));
- return instance->getSegment(startD, endD, dst, startWithMoveTo);
+ //region ohos
+ SkPath path = dst->detach();
+ KBoolean result = instance->getSegment(startD, endD, &path, startWithMoveTo);
+ *dst = path;
+ return result;
+ //endregion
}
diff --git b/src/nativeJsMain/cpp/PathUtils.cc a/src/nativeJsMain/cpp/PathUtils.cc
index a19212eb..4a85b8be 100644
--- b/src/nativeJsMain/cpp/PathUtils.cc
+++ a/src/nativeJsMain/cpp/PathUtils.cc
@@ -8,7 +8,13 @@ SKIKO_EXPORT KNativePointer org_jetbrains_skia_PathUtils__1nFillPathWithPaint
(KNativePointer srcPtr, KNativePointer paintPtr) {
SkPath* src = reinterpret_cast<SkPath*>((srcPtr));
SkPaint* paint = reinterpret_cast<SkPaint*>((paintPtr));
- return reinterpret_cast<KNativePointer>(new SkPath(skpathutils::FillPathWithPaint(*src, *paint)));
+ //region ohos
+ SkPath* dst = new SkPath();
+ if (!skpathutils::FillPathWithPaint(*src, *paint, dst)) {
+ *dst = *src;
+ }
+ return reinterpret_cast<KNativePointer>(dst);
+ //endregion
}
SKIKO_EXPORT KBoolean org_jetbrains_skia_PathUtils__1nFillPathWithPaintBuilder
@@ -16,7 +22,15 @@ SKIKO_EXPORT KBoolean org_jetbrains_skia_PathUtils__1nFillPathWithPaintBuilder
SkPath* src = reinterpret_cast<SkPath*>(srcPtr);
SkPaint* paint = reinterpret_cast<SkPaint*>(paintPtr);
SkPathBuilder* dst = reinterpret_cast<SkPathBuilder*>(dstPtr);
- return skpathutils::FillPathWithPaint(*src, *paint, dst);
+ //region ohos
+ SkPath path = dst->detach();
+ KBoolean result = skpathutils::FillPathWithPaint(*src, *paint, &path);
+ if (!result) {
+ path = *src;
+ }
+ *dst = path;
+ return result;
+ //endregion
}
SKIKO_EXPORT KBoolean org_jetbrains_skia_PathUtils__1nFillPathWithPaintMatrix
@@ -25,7 +39,15 @@ SKIKO_EXPORT KBoolean org_jetbrains_skia_PathUtils__1nFillPathWithPaintMatrix
SkPath* src = reinterpret_cast<SkPath*>(srcPtr);
SkPaint* paint = reinterpret_cast<SkPaint*>(paintPtr);
SkPathBuilder* dst = reinterpret_cast<SkPathBuilder*>(dstPtr);
- return skpathutils::FillPathWithPaint(*src, *paint, dst, nullptr, *matrix);
+ //region ohos
+ SkPath path = dst->detach();
+ KBoolean result = skpathutils::FillPathWithPaint(*src, *paint, &path, nullptr, *matrix);
+ if (!result) {
+ path = *src;
+ }
+ *dst = path;
+ return result;
+ //endregion
}
SKIKO_EXPORT KBoolean org_jetbrains_skia_PathUtils__1nFillPathWithPaintCull
@@ -35,5 +57,13 @@ SKIKO_EXPORT KBoolean org_jetbrains_skia_PathUtils__1nFillPathWithPaintCull
SkPaint* paint = reinterpret_cast<SkPaint*>((paintPtr));
SkPathBuilder* dst = reinterpret_cast<SkPathBuilder*>(dstPtr);
SkRect cull {left, top, right, bottom};
- return skpathutils::FillPathWithPaint(*src, *paint, dst, &cull, *matrix);
+ //region ohos
+ SkPath path = dst->detach();
+ KBoolean result = skpathutils::FillPathWithPaint(*src, *paint, &path, &cull, *matrix);
+ if (!result) {
+ path = *src;
+ }
+ *dst = path;
+ return result;
+ //endregion
}
diff --git b/src/nativeJsMain/cpp/Picture.cc a/src/nativeJsMain/cpp/Picture.cc
index 69e760de..faad321c 100644
--- b/src/nativeJsMain/cpp/Picture.cc
+++ a/src/nativeJsMain/cpp/Picture.cc
@@ -52,9 +52,13 @@ SKIKO_EXPORT KInt org_jetbrains_skia_Picture__1nGetUniqueId
SKIKO_EXPORT KNativePointer org_jetbrains_skia_Picture__1nSerializeToData
(KNativePointer ptr) {
+#ifndef USE_OHRENDER
SkPicture* instance = reinterpret_cast<SkPicture*>((ptr));
SkData* data = instance->serialize().release();
return reinterpret_cast<KNativePointer>(data);
+#else
+ return nullptr;
+#endif
}
SKIKO_EXPORT KNativePointer org_jetbrains_skia_Picture__1nMakePlaceholder
@@ -70,10 +74,12 @@ SKIKO_EXPORT KInt org_jetbrains_skia_Picture__1nGetApproximateOpCount
return instance->approximateOpCount();
}
-SKIKO_EXPORT KInt org_jetbrains_skia_Picture__1nGetApproximateBytesUsed
+SKIKO_EXPORT KInt org_jetbrains_skia_Picture__1nGetApproximateBytesUsed
(KNativePointer ptr) {
SkPicture* instance = reinterpret_cast<SkPicture*>((ptr));
- return static_cast<KInt>(instance->approximateBytesUsed());
+ //region ohos
+ return static_cast<KInt>(instance->approximateBytesUsed());
+ //endregion
}
@@ -84,13 +88,15 @@ SKIKO_EXPORT KNativePointer org_jetbrains_skia_Picture__1nMakeShader
SkTileMode tmy = static_cast<SkTileMode>(tmyValue);
SkFilterMode filterMode = static_cast<SkFilterMode>(filterModeValue);
std::unique_ptr<SkMatrix> localMatrix = skMatrix(localMatrixArr);
- SkShader* shader;
+ SkShader* shader = nullptr;
+#if !defined(USE_OHRENDER) || defined(OHOS_CHECK_NOIMPLEMENTED)
if (hasTile) {
SkRect tileRect = SkRect::MakeLTRB(tileLeft, tileRight, tileBottom, tileTop);
shader = instance->makeShader(tmx, tmy, filterMode, localMatrix.get(), &tileRect).release();
} else {
shader = instance->makeShader(tmx, tmy, filterMode, localMatrix.get(), nullptr).release();
}
+ #endif
return reinterpret_cast<KNativePointer>(shader);
}
diff --git b/src/nativeJsMain/cpp/Region.cc a/src/nativeJsMain/cpp/Region.cc
index a8942768..365cafd7 100644
--- b/src/nativeJsMain/cpp/Region.cc
+++ a/src/nativeJsMain/cpp/Region.cc
@@ -58,12 +58,21 @@ SKIKO_EXPORT KInt org_jetbrains_skia_Region__1nComputeRegionComplexity(KNativePo
SKIKO_EXPORT KBoolean org_jetbrains_skia_Region__1nAddBoundaryPath(KNativePointer ptr, KNativePointer pathBuilderPtr) {
SkRegion* instance = reinterpret_cast<SkRegion*>((ptr));
SkPathBuilder* pathBuilder = reinterpret_cast<SkPathBuilder*>((pathBuilderPtr));
- return instance->addBoundaryPath(pathBuilder);
+ //region ohos
+ SkPath path = pathBuilder->detach();
+ KBoolean result = instance->getBoundaryPath(&path);
+ *pathBuilder = path;
+ return result;
+ //endregion
}
SKIKO_EXPORT KNativePointer org_jetbrains_skia_Region__1nGetBoundaryPath(KNativePointer ptr) {
SkRegion* instance = reinterpret_cast<SkRegion*>((ptr));
- return reinterpret_cast<KNativePointer>(new SkPath(instance->getBoundaryPath()));
+ //region ohos
+ SkPath* path = new SkPath();
+ instance->getBoundaryPath(path);
+ return reinterpret_cast<KNativePointer>(path);
+ //endregion
}
SKIKO_EXPORT KBoolean org_jetbrains_skia_Region__1nSetEmpty(KNativePointer ptr) {
diff --git b/src/nativeJsMain/cpp/Shader.cc a/src/nativeJsMain/cpp/Shader.cc
index bc63d566..7e996fa5 100644
--- b/src/nativeJsMain/cpp/Shader.cc
+++ a/src/nativeJsMain/cpp/Shader.cc
@@ -1,33 +1,25 @@
#include <iostream>
#include "SkColorFilter.h"
-#include "SkGradient.h"
+#include "SkGradientShader.h"
#include "SkPerlinNoiseShader.h"
#include "SkShader.h"
#include "SkSize.h"
#include "common.h"
-static SkGradient makeGradient(const SkColor4f* colors,
- sk_sp<SkColorSpace> colorSpace,
- const float* positions,
- int count,
- SkTileMode tileMode,
- KInt inPremul,
- KInt interpolationColorSpace,
- KInt hueMethod) {
- SkGradient::Interpolation interpolation{
- inPremul == 0 ? SkGradient::Interpolation::InPremul::kNo
- : SkGradient::Interpolation::InPremul::kYes,
- static_cast<SkGradient::Interpolation::ColorSpace>(interpolationColorSpace),
- static_cast<SkGradient::Interpolation::HueMethod>(hueMethod)
- };
- return SkGradient(
- SkGradient::Colors(
- SkSpan<const SkColor4f>(colors, count),
- positions == nullptr ? SkSpan<const float>() : SkSpan<const float>(positions, count),
- tileMode,
- std::move(colorSpace)),
- interpolation);
-}
+//region ohos
+static SkGradientShader::Interpolation makeGradientInterpolation(
+ KInt inPremul,
+ KInt interpolationColorSpace,
+ KInt hueMethod
+) {
+ return SkGradientShader::Interpolation{
+ inPremul == 0 ? SkGradientShader::Interpolation::InPremul::kNo
+ : SkGradientShader::Interpolation::InPremul::kYes,
+ static_cast<SkGradientShader::Interpolation::ColorSpace>(interpolationColorSpace),
+ static_cast<SkGradientShader::Interpolation::HueMethod>(hueMethod)
+ };
+}
+//endregion
SKIKO_EXPORT KNativePointer org_jetbrains_skia_Shader__1nMakeWithLocalMatrix
(KNativePointer ptr, KFloat* localMatrixArr) {
@@ -53,8 +45,12 @@
float* positions = reinterpret_cast<float*>(positionsArray);
SkTileMode tileMode = static_cast<SkTileMode>(tileModeInt);
std::unique_ptr<SkMatrix> localMatrix = skMatrix(matrixArray);
- SkGradient gradient = makeGradient(colors, colorSpace, positions, count, tileMode, inPremul, interpolationColorSpace, hueMethod);
- SkShader* ptr = SkShaders::LinearGradient(pts, gradient, localMatrix.get()).release();
+ //region ohos
+ SkGradientShader::Interpolation interpolation = makeGradientInterpolation(
+ inPremul, interpolationColorSpace, hueMethod);
+ SkShader* ptr = SkGradientShader::MakeLinear(
+ pts, colors, colorSpace, positions, count, tileMode, interpolation, localMatrix.get()).release();
+ //endregion
return reinterpret_cast<KNativePointer>(ptr);
}
@@ -65,8 +61,13 @@
float* positions = reinterpret_cast<float*>(positionsArray);
SkTileMode tileMode = static_cast<SkTileMode>(tileModeInt);
std::unique_ptr<SkMatrix> localMatrix = skMatrix(matrixArray);
- SkGradient gradient = makeGradient(colors, colorSpace, positions, count, tileMode, inPremul, interpolationColorSpace, hueMethod);
- SkShader* ptr = SkShaders::RadialGradient(SkPoint::Make(x, y), radius, gradient, localMatrix.get()).release();
+ //region ohos
+ SkGradientShader::Interpolation interpolation = makeGradientInterpolation(
+ inPremul, interpolationColorSpace, hueMethod);
+ SkShader* ptr = SkGradientShader::MakeRadial(
+ SkPoint::Make(x, y), radius, colors, colorSpace, positions, count,
+ tileMode, interpolation, localMatrix.get()).release();
+ //endregion
return reinterpret_cast<KNativePointer>(ptr);
}
@@ -77,8 +78,13 @@
float* positions = reinterpret_cast<float*>(positionsArray);
SkTileMode tileMode = static_cast<SkTileMode>(tileModeInt);
std::unique_ptr<SkMatrix> localMatrix = skMatrix(matrixArray);
- SkGradient gradient = makeGradient(colors, colorSpace, positions, count, tileMode, inPremul, interpolationColorSpace, hueMethod);
- SkShader* ptr = SkShaders::TwoPointConicalGradient(SkPoint::Make(x0, y0), startRadius, SkPoint::Make(x1, y1), endRadius, gradient, localMatrix.get()).release();
+ //region ohos
+ SkGradientShader::Interpolation interpolation = makeGradientInterpolation(
+ inPremul, interpolationColorSpace, hueMethod);
+ SkShader* ptr = SkGradientShader::MakeTwoPointConical(
+ SkPoint::Make(x0, y0), startRadius, SkPoint::Make(x1, y1), endRadius,
+ colors, colorSpace, positions, count, tileMode, interpolation, localMatrix.get()).release();
+ //endregion
return reinterpret_cast<KNativePointer>(ptr);
}
@@ -89,8 +95,13 @@
float* positions = reinterpret_cast<float*>(positionsArray);
SkTileMode tileMode = static_cast<SkTileMode>(tileModeInt);
std::unique_ptr<SkMatrix> localMatrix = skMatrix(matrixArray);
- SkGradient gradient = makeGradient(colors, colorSpace, positions, count, tileMode, inPremul, interpolationColorSpace, hueMethod);
- SkShader* ptr = SkShaders::SweepGradient(SkPoint::Make(x, y), startAngle, endAngle, gradient, localMatrix.get()).release();
+ //region ohos
+ SkGradientShader::Interpolation interpolation = makeGradientInterpolation(
+ inPremul, interpolationColorSpace, hueMethod);
+ SkShader* ptr = SkGradientShader::MakeSweep(
+ x, y, colors, colorSpace, positions, count, tileMode, startAngle, endAngle,
+ interpolation, localMatrix.get()).release();
+ //endregion
return reinterpret_cast<KNativePointer>(ptr);
}
@@ -120,14 +131,22 @@ SKIKO_EXPORT KNativePointer org_jetbrains_skia_Shader__1nMakeBlend(KInt blendMod
SKIKO_EXPORT KNativePointer org_jetbrains_skia_Shader__1nMakeFractalNoise
(KFloat baseFrequencyX, KFloat baseFrequencyY, KInt numOctaves, KFloat seed, KInt tileW, KInt tileH) {
+#if !defined(USE_OHRENDER) || defined(OHOS_CHECK_NOIMPLEMENTED)
const SkISize tileSize = SkISize::Make(tileW, tileH);
SkShader* ptr = SkShaders::MakeFractalNoise(baseFrequencyX, baseFrequencyY, numOctaves, seed, &tileSize).release();
return reinterpret_cast<KNativePointer>(ptr);
+#else
+ return nullptr;
+#endif
}
SKIKO_EXPORT KNativePointer org_jetbrains_skia_Shader__1nMakeTurbulence
(KFloat baseFrequencyX, KFloat baseFrequencyY, KInt numOctaves, KFloat seed, KInt tileW, KInt tileH) {
+#if !defined(USE_OHRENDER) || defined(OHOS_CHECK_NOIMPLEMENTED)
const SkISize tileSize = SkISize::Make(tileW, tileH);
SkShader* ptr = SkShaders::MakeTurbulence(baseFrequencyX, baseFrequencyY, numOctaves, seed, &tileSize).release();
return reinterpret_cast<KNativePointer>(ptr);
+#else
+ return nullptr;
+#endif
}
diff --git b/src/nativeJsMain/cpp/Surface.cc a/src/nativeJsMain/cpp/Surface.cc
index 255f7067..3c12a051 100644
--- b/src/nativeJsMain/cpp/Surface.cc
+++ a/src/nativeJsMain/cpp/Surface.cc
@@ -1,7 +1,11 @@
#include <iostream>
+#ifndef USE_OHRENDER
#include "ganesh/GrDirectContext.h"
+#endif
#include "SkSurface.h"
+#ifndef USE_OHRENDER
#include "include/gpu/ganesh/SkSurfaceGanesh.h"
+#endif
#include "common.h"
#ifdef SK_METAL
@@ -74,6 +78,7 @@ SKIKO_EXPORT KNativePointer org_jetbrains_skia_Surface__1nMakeRasterN32Premul
}
+#ifndef USE_OHRENDER
SKIKO_EXPORT KNativePointer org_jetbrains_skia_Surface__1nMakeFromBackendRenderTarget
(KNativePointer pContext, KNativePointer pBackendRenderTarget, KInt surfaceOrigin, KInt colorType, KNativePointer colorSpacePtr, KInteropPointer surfacePropsInts) {
GrDirectContext* context = reinterpret_cast<GrDirectContext*>((pContext));
@@ -96,6 +101,7 @@ SKIKO_EXPORT KNativePointer org_jetbrains_skia_Surface__1nMakeFromBackendRenderT
);
return reinterpret_cast<KNativePointer>(surface.release());
}
+#endif
SKIKO_EXPORT KNativePointer org_jetbrains_skia_Surface__1nMakeFromMTKView
(KNativePointer contextPtr, KNativePointer mtkViewPtr, KInt surfaceOrigin, KInt sampleCount, KInt colorType, KNativePointer colorSpacePtr, KInteropPointer surfacePropsInts) {
@@ -121,6 +127,7 @@ SKIKO_EXPORT KNativePointer org_jetbrains_skia_Surface__1nMakeFromMTKView
#endif // SK_METAL
}
+#ifndef USE_OHRENDER
SKIKO_EXPORT KNativePointer org_jetbrains_skia_Surface__1nMakeRenderTarget
(KNativePointer contextPtr, KBoolean budgeted,
KInt width, KInt height, KInt colorType, KInt alphaType, KNativePointer colorSpacePtr,
@@ -145,6 +152,7 @@ SKIKO_EXPORT KNativePointer org_jetbrains_skia_Surface__1nMakeRenderTarget
shouldCreateWithMips);
return reinterpret_cast<KNativePointer>(instance.release());
}
+#endif
SKIKO_EXPORT KNativePointer org_jetbrains_skia_Surface__1nMakeNull
(KInt width, KInt height) {
diff --git b/src/nativeJsMain/cpp/TextBlob.cc a/src/nativeJsMain/cpp/TextBlob.cc
index cdef0f56..65afb880 100644
--- b/src/nativeJsMain/cpp/TextBlob.cc
+++ a/src/nativeJsMain/cpp/TextBlob.cc
@@ -34,19 +34,27 @@ SKIKO_EXPORT KInt org_jetbrains_skia_TextBlob__1nGetUniqueId
SKIKO_EXPORT KInt org_jetbrains_skia_TextBlob__1nGetInterceptsLength
(KNativePointer ptr, KFloat lower, KFloat upper, KNativePointer paintPtr) {
+#if !defined(USE_OHRENDER) || defined(OHOS_CHECK_NOIMPLEMENTED)
SkTextBlob* instance = reinterpret_cast<SkTextBlob*>(ptr);
SkPaint* paint = reinterpret_cast<SkPaint*>(paintPtr);
std::vector<float> bounds {lower, upper};
int len = instance->getIntercepts(bounds.data(), nullptr, paint);
return len;
+#else
+ return 0;
+#endif
}
SKIKO_EXPORT void org_jetbrains_skia_TextBlob__1nGetIntercepts
(KNativePointer ptr, KFloat lower, KFloat upper, KNativePointer paintPtr, KFloat* resultArray) {
+#if !defined(USE_OHRENDER) || defined(OHOS_CHECK_NOIMPLEMENTED)
SkTextBlob* instance = reinterpret_cast<SkTextBlob*>(ptr);
SkPaint* paint = reinterpret_cast<SkPaint*>(paintPtr);
std::vector<float> bounds {lower, upper};
instance->getIntercepts(bounds.data(), resultArray, paint);
+#else
+ return;
+#endif
}
SKIKO_EXPORT KNativePointer org_jetbrains_skia_TextBlob__1nMakeFromPosH
@@ -56,7 +64,7 @@ SKIKO_EXPORT KNativePointer org_jetbrains_skia_TextBlob__1nMakeFromPosH
SkTextBlob* instance = SkTextBlob::MakeFromPosTextH(
glyphsArr,
glyphsLen * sizeof(short),
- {xposArr, glyphsLen}, ypos,
+ xposArr, ypos,
*font,
SkTextEncoding::kGlyphID
).release();
@@ -71,7 +79,7 @@ SKIKO_EXPORT KNativePointer org_jetbrains_skia_TextBlob__1nMakeFromPos
SkTextBlob* instance = SkTextBlob::MakeFromPosText(
glyphsArr,
glyphsLen * sizeof(KShort),
- {reinterpret_cast<SkPoint*>(posArr), glyphsLen},
+ reinterpret_cast<SkPoint*>(posArr),
*font,
SkTextEncoding::kGlyphID
).release();
@@ -85,7 +93,7 @@ SKIKO_EXPORT KNativePointer org_jetbrains_skia_TextBlob__1nMakeFromRSXform
SkTextBlob* instance = SkTextBlob::MakeFromRSXform(
glyphsArr, glyphsLen * sizeof(short),
- {reinterpret_cast<SkRSXform*>(xformArr), glyphsLen},
+ reinterpret_cast<SkRSXform*>(xformArr),
*font, SkTextEncoding::kGlyphID
).release();
@@ -94,16 +102,24 @@ SKIKO_EXPORT KNativePointer org_jetbrains_skia_TextBlob__1nMakeFromRSXform
SKIKO_EXPORT KNativePointer org_jetbrains_skia_TextBlob__1nSerializeToData
(KNativePointer ptr) {
+#if !defined(USE_OHRENDER) || defined(OHOS_CHECK_NOIMPLEMENTED)
SkTextBlob* instance = reinterpret_cast<SkTextBlob*>((ptr));
SkData* data = instance->serialize({}).release();
return reinterpret_cast<KNativePointer>(data);
+#else
+ return nullptr;
+#endif
}
SKIKO_EXPORT KNativePointer org_jetbrains_skia_TextBlob__1nMakeFromData
(KNativePointer dataPtr) {
+#if !defined(USE_OHRENDER) || defined(OHOS_CHECK_NOIMPLEMENTED)
SkData* data = reinterpret_cast<SkData*>((dataPtr));
SkTextBlob* instance = SkTextBlob::Deserialize(data->data(), data->size(), {}).release();
return reinterpret_cast<KNativePointer>(instance);
+#else
+ return nullptr;
+#endif
}
SKIKO_EXPORT KInt org_jetbrains_skia_TextBlob__1nGetGlyphsLength
diff --git b/src/nativeJsMain/cpp/Typeface.cc a/src/nativeJsMain/cpp/Typeface.cc
index f3e06855..43356eee 100644
--- b/src/nativeJsMain/cpp/Typeface.cc
+++ a/src/nativeJsMain/cpp/Typeface.cc
@@ -11,6 +11,7 @@ SKIKO_EXPORT KInt org_jetbrains_skia_Typeface__1nGetFontStyle
}
+#if !defined(USE_OHRENDER) || defined(OHOS_CHECK_NOIMPLEMENTED)
SKIKO_EXPORT KBoolean org_jetbrains_skia_Typeface__1nIsFixedPitch
(KNativePointer ptr) {
SkTypeface* instance = reinterpret_cast<SkTypeface*>((ptr));
@@ -20,7 +21,7 @@ SKIKO_EXPORT KBoolean org_jetbrains_skia_Typeface__1nIsFixedPitch
SKIKO_EXPORT KInt org_jetbrains_skia_Typeface__1nGetVariationsCount
(KNativePointer ptr) {
SkTypeface* instance = reinterpret_cast<SkTypeface*>(ptr);
- return instance->getVariationDesignPosition({});
+ return instance->getVariationDesignPosition(nullptr, 0);
}
SKIKO_EXPORT void org_jetbrains_skia_Typeface__1nGetVariations
@@ -28,7 +29,7 @@ SKIKO_EXPORT void org_jetbrains_skia_Typeface__1nGetVariations
SkTypeface* instance = reinterpret_cast<SkTypeface*>(ptr);
if (count > 0) {
std::vector<SkFontArguments::VariationPosition::Coordinate> coords(count);
- instance->getVariationDesignPosition({coords.data(), count});
+ instance->getVariationDesignPosition(coords.data(), count);
for (int i=0; i < count; ++i) {
res[2*i] = static_cast<KInt>(coords[i].axis);
res[2*i + 1] = rawBits(coords[i].value);
@@ -39,7 +40,7 @@ SKIKO_EXPORT void org_jetbrains_skia_Typeface__1nGetVariations
SKIKO_EXPORT KInt org_jetbrains_skia_Typeface__1nGetVariationAxesCount
(KNativePointer ptr) {
SkTypeface* instance = reinterpret_cast<SkTypeface*>(ptr);
- return instance->getVariationDesignParameters({});
+ return instance->getVariationDesignParameters(nullptr, 0);
}
@@ -48,7 +49,7 @@ SKIKO_EXPORT void org_jetbrains_skia_Typeface__1nGetVariationAxes
SkTypeface* instance = reinterpret_cast<SkTypeface*>(ptr);
if (count > 0) {
std::vector<SkFontParameters::Variation::Axis> params(count);
- instance->getVariationDesignParameters({params.data(), count});
+ instance->getVariationDesignParameters(params.data(), count);
for (int i = 0, j = 0; i < count; ++i) {
int p[5] = { static_cast<int>(params[i].tag), rawBits(params[i].min), rawBits(params[i].def), rawBits(params[i].max), params[i].isHidden()};
memcpy(axis + 5 * i, p, sizeof p);
@@ -69,10 +70,54 @@ SKIKO_EXPORT KInt org_jetbrains_skia_Typeface__1nEquals
SkTypeface* other = reinterpret_cast<SkTypeface*>((otherPtr));
return SkTypeface::Equal(instance, other);
}
+#else
+//region ohos
+SKIKO_EXPORT KBoolean org_jetbrains_skia_Typeface__1nIsFixedPitch
+ (KNativePointer ptr) {
+ return false;
+}
+
+SKIKO_EXPORT KInt org_jetbrains_skia_Typeface__1nGetVariationsCount
+ (KNativePointer ptr) {
+ return 0;
+}
+
+SKIKO_EXPORT void org_jetbrains_skia_Typeface__1nGetVariations
+ (KNativePointer ptr, KInt* res, KInt count) {
+}
+
+SKIKO_EXPORT KInt org_jetbrains_skia_Typeface__1nGetVariationAxesCount
+ (KNativePointer ptr) {
+ return 0;
+}
+
+SKIKO_EXPORT void org_jetbrains_skia_Typeface__1nGetVariationAxes
+ (KNativePointer ptr, int* axis, int count) {
+}
+
+SKIKO_EXPORT KInt org_jetbrains_skia_Typeface__1nGetUniqueId
+ (KNativePointer ptr) {
+ size_t address = reinterpret_cast<size_t>(ptr);
+ return static_cast<KInt>(address ^ (address >> 32));
+}
+
+SKIKO_EXPORT KInt org_jetbrains_skia_Typeface__1nEquals
+ (KNativePointer ptr, KNativePointer otherPtr) {
+ return ptr == otherPtr;
+}
+//endregion
+#endif // !USE_OHRENDER || OHOS_CHECK_NOIMPLEMENTED (closes block started at isFixedPitch; reopened below)
SKIKO_EXPORT KNativePointer org_jetbrains_skia_Typeface__1nMakeClone
(KNativePointer typefacePtr, KInt* variations, KInt variationsCount, KInt collectionIndex) {
SkTypeface* typeface = reinterpret_cast<SkTypeface*>(typefacePtr);
+#ifdef USE_OHRENDER
+ // OHRender's SkTypeface doesn't implement makeClone, and font weight is
+ // already applied via SkFontStyle in legacyMakeTypeface/matchFamilyStyle.
+ // Return the original typeface with a ref—variation axes are redundant here.
+ typeface->ref();
+ return reinterpret_cast<KNativePointer>(typeface);
+#else
std::vector<SkFontArguments::VariationPosition::Coordinate> coordinates(variationsCount);
for (int i=0; i < variationsCount; i+=2) {
coordinates[i] = {
@@ -85,12 +94,15 @@ SKIKO_EXPORT KNativePointer org_jetbrains_skia_Typeface__1nMakeClone
.setVariationDesignPosition({coordinates.data(), variationsCount});
SkTypeface* clone = typeface->makeClone(arg).release();
return reinterpret_cast<KNativePointer>(clone);
+#endif
}
+#if !defined(USE_OHRENDER) || defined(OHOS_CHECK_NOIMPLEMENTED)
+
SKIKO_EXPORT void org_jetbrains_skia_Typeface__1nGetUTF32Glyphs
(KNativePointer ptr, KInt* uni, KInt count, KShort* res) {
SkTypeface* instance = reinterpret_cast<SkTypeface*>(ptr);
- instance->unicharsToGlyphs({reinterpret_cast<SkUnichar*>(uni), count}, {reinterpret_cast<SkGlyphID*>(res), count});
+ instance->unicharsToGlyphs(reinterpret_cast<SkUnichar*>(uni), count, reinterpret_cast<SkGlyphID*>(res));
}
SKIKO_EXPORT KShort org_jetbrains_skia_Typeface__1nGetUTF32Glyph
@@ -120,9 +132,9 @@ SKIKO_EXPORT KInt org_jetbrains_skia_Typeface__1nGetTableTagsCount
SKIKO_EXPORT void org_jetbrains_skia_Typeface__1nGetTableTags
(KNativePointer ptr, KInt* res, KInt count) {
SkTypeface* instance = reinterpret_cast<SkTypeface*>(ptr);
- std::vector<SkFontTableTag> tags(count);
- int actualCount = instance->readTableTags({tags.data(), count});
- memcpy(res, tags.data(), actualCount * sizeof(KInt));
+ std::vector<int> tags(count);
+ instance->getTableTags(reinterpret_cast<SkFontTableTag*>(tags.data()));
+ memcpy(res, tags.data(), tags.size() * sizeof(KInt));
}
SKIKO_EXPORT KInt org_jetbrains_skia_Typeface__1nGetTableSize
@@ -149,9 +161,10 @@ SKIKO_EXPORT bool org_jetbrains_skia_Typeface__1nGetKerningPairAdjustments
(KNativePointer ptr, KInteropPointer glyphs, KInt count, KInt* res) {
SkTypeface* instance = reinterpret_cast<SkTypeface*>(ptr);
if (count > 0) {
+ std::vector<int> adjustments(count);
return instance->getKerningPairAdjustments(
- {reinterpret_cast<SkGlyphID*>(glyphs), count},
- {reinterpret_cast<int32_t*>(res), count}
+ reinterpret_cast<SkGlyphID*>(glyphs), count,
+ reinterpret_cast<int32_t*>(res)
);
}
@@ -173,6 +186,7 @@ SKIKO_EXPORT KInteropPointer org_jetbrains_skia_Typeface__1nGetFamilyNames
return reinterpret_cast<KInteropPointer>(res);
}
+#endif
SKIKO_EXPORT KInteropPointer org_jetbrains_skia_Typeface__1nGetFamilyName
(KNativePointer ptr) {
@@ -182,6 +196,7 @@ SKIKO_EXPORT KInteropPointer org_jetbrains_skia_Typeface__1nGetFamilyName
return new SkString(name);
}
+#if !defined(USE_OHRENDER) || defined(OHOS_CHECK_NOIMPLEMENTED)
SKIKO_EXPORT void org_jetbrains_skia_Typeface__1nGetBounds
(KNativePointer ptr, KFloat* bounds) {
SkTypeface* instance = reinterpret_cast<SkTypeface*>(ptr);
@@ -191,10 +206,27 @@ SKIKO_EXPORT void org_jetbrains_skia_Typeface__1nGetBounds
bounds[2] = b.right();
bounds[3] = b.bottom();
}
+#endif
SKIKO_EXPORT KInteropPointer org_jetbrains_skia_Typeface__1nMakeEmptyTypeface
() {
-
SkTypeface* res = SkTypeface::MakeEmpty().release();
return reinterpret_cast<KInteropPointer>(res);
-}
\ No newline at end of file
+}
+
+#ifdef USE_OHRENDER
+// Fusion-renderer (OHRender) only. Restores the Typeface.makeFromData binding
+// removed in skiko d04654c6 ("update to skia m126 #969"): upstream skia m126
+// dropped SkTypeface::MakeFromData, but the OHRender fork keeps a data-only
+// implementation (registered into the OHOS FontCollection later by
+// TypefaceFontProvider). Guarded by USE_OHRENDER so non-OHRender targets (iOS,
+// native, js, ohos-skia) never compile/link this and stay unaffected.
+SKIKO_EXPORT KNativePointer org_jetbrains_skia_Typeface__1nMakeFromData
+ (KNativePointer dataPtr, KInt index)
+{
+ SkData* data = reinterpret_cast<SkData*>((dataPtr));
+ sk_sp<SkTypeface> instance = SkTypeface::MakeFromData(sk_ref_sp(data), index);
+ SkTypeface* ptr = instance.release();
+ return reinterpret_cast<KNativePointer>(ptr);
+}
+#endif
diff --git b/src/nativeJsMain/cpp/common.h a/src/nativeJsMain/cpp/common.h
index 4208fa36..cf5a191b 100644
--- b/src/nativeJsMain/cpp/common.h
+++ a/src/nativeJsMain/cpp/common.h
@@ -26,6 +26,9 @@
#include "SkLoadICU.h"
#include "unicode/ubrk.h"
#include "mppinterop.h"
+#ifdef USE_OHRENDER
+#include <unicode/utext.h>
+#endif
#include "types.h"
diff --git b/src/nativeJsMain/cpp/common_interop.cc a/src/nativeJsMain/cpp/common_interop.cc
index 5fb653d8..ffe53e49 100644
--- b/src/nativeJsMain/cpp/common_interop.cc
+++ a/src/nativeJsMain/cpp/common_interop.cc
@@ -87,7 +87,7 @@ namespace skija {
return skija::FontFeature::fromIntArray(featuresArray, featuresLen);
}
}
-
+#if !defined(USE_OHRENDER) || defined(OHOS_CHECK_NOIMPLEMENTED)
std::shared_ptr<UBreakIterator> graphemeBreakIterator(SkString& text) {
UErrorCode status = U_ZERO_ERROR;
ICUUText utext(utext_openUTF8(nullptr, text.c_str(), text.size(), &status));
@@ -113,6 +113,7 @@ namespace skija {
return graphemeIter;
}
+#endif
}
}
diff --git b/src/nativeJsMain/cpp/icu/Unicode.cc a/src/nativeJsMain/cpp/icu/Unicode.cc
index 34712586..d6b214d5 100644
--- b/src/nativeJsMain/cpp/icu/Unicode.cc
+++ a/src/nativeJsMain/cpp/icu/Unicode.cc
@@ -1,5 +1,9 @@
#include "common.h"
+#ifndef USE_OHRENDER
#include "third_party/externals/icu/source/common/unicode/uchar.h"
+#else
+#include <unicode/uchar.h>
+#endif
SKIKO_EXPORT KInt org_jetbrains_skia_icu_Unicode__1nCharDirection(KInt codePoint) {
return u_charDirection(codePoint);
diff --git b/src/nativeJsMain/cpp/paragraph/FontCollection.cc a/src/nativeJsMain/cpp/paragraph/FontCollection.cc
index e65ff384..30b5b745 100644
--- b/src/nativeJsMain/cpp/paragraph/FontCollection.cc
+++ a/src/nativeJsMain/cpp/paragraph/FontCollection.cc
@@ -1,8 +1,6 @@
#include <iostream>
-#include <vector>
-#include "FontCollection.h"
#include "SkRefCnt.h"
-
+#include "FontCollection.h"
using namespace std;
using namespace skia::textlayout;
#include "common.h"
@@ -77,21 +75,29 @@ SKIKO_EXPORT KNativePointer org_jetbrains_skia_paragraph_FontCollection__1nFindT
}
SKIKO_EXPORT KNativePointer org_jetbrains_skia_paragraph_FontCollection__1nDefaultFallbackChar
(KNativePointer ptr, KInt unicode, KInteropPointerArray familyNamesArray, KInt len, KInt fontStyle, KInteropPointer locale) {
+#if !defined(USE_OHRENDER) || defined(OHOS_CHECK_NOIMPLEMENTED)
FontCollection* instance = reinterpret_cast<FontCollection*>(ptr);
return reinterpret_cast<KNativePointer>(instance->defaultFallback(
unicode,
skStringVector(familyNamesArray, len),
skija::FontStyle::fromKotlin(fontStyle),
skString(locale),
std::nullopt
).release());
+#else
+ return nullptr;
+#endif
}
SKIKO_EXPORT KNativePointer org_jetbrains_skia_paragraph_FontCollection__1nDefaultFallback
(KNativePointer ptr) {
+#if !defined(USE_OHRENDER) || defined(OHOS_CHECK_NOIMPLEMENTED)
FontCollection* instance = reinterpret_cast<FontCollection*>((ptr));
return reinterpret_cast<KNativePointer>(instance->defaultFallback().release());
+#else
+ return nullptr;
+#endif
}
SKIKO_EXPORT void org_jetbrains_skia_paragraph_FontCollection__1nSetEnableFallback
diff --git b/src/nativeJsMain/cpp/paragraph/ParagraphBuilder.cc a/src/nativeJsMain/cpp/paragraph/ParagraphBuilder.cc
index be3b9faa..9f8fcc67 100644
--- b/src/nativeJsMain/cpp/paragraph/ParagraphBuilder.cc
+++ a/src/nativeJsMain/cpp/paragraph/ParagraphBuilder.cc
@@ -1,11 +1,12 @@
#include <iostream>
#include <string>
-#include "modules/skunicode/include/SkUnicode_icu.h"
#include "ParagraphBuilder.h"
-
using namespace std;
using namespace skia::textlayout;
#include "common.h"
+#if !defined(USE_OHRENDER)
+#include "SkUnicode_icu.h" // For SkUnicodes::ICU::Make()
+#endif
static void deleteParagraphBuilder(ParagraphBuilder* instance) {
delete instance;
@@ -15,11 +16,15 @@ SKIKO_EXPORT KNativePointer org_jetbrains_skia_paragraph_ParagraphBuilder__1nMak
(KNativePointer paragraphStylePtr, KNativePointer fontCollectionPtr) {
ParagraphStyle* paragraphStyle = reinterpret_cast<ParagraphStyle*>((paragraphStylePtr));
FontCollection* fontCollection = reinterpret_cast<FontCollection*>((fontCollectionPtr));
- ParagraphBuilder* instance = ParagraphBuilder::make(
- *paragraphStyle,
- sk_ref_sp(fontCollection),
- SkUnicodes::ICU::Make()
- ).release();
+#if !defined(USE_OHRENDER)
+ // Skia m150: ParagraphBuilder::make() requires a SkUnicode parameter.
+ // Use ICU-based Unicode implementation (same as Shaper.cc:47)
+ sk_sp<SkUnicode> unicode = SkUnicodes::ICU::Make();
+ ParagraphBuilder* instance = ParagraphBuilder::make(*paragraphStyle, sk_ref_sp(fontCollection), unicode).release();
+#else
+ // OHRender: ParagraphBuilder::make() does not require SkUnicode
+ ParagraphBuilder* instance = ParagraphBuilder::make(*paragraphStyle, sk_ref_sp(fontCollection)).release();
+#endif
return reinterpret_cast<KNativePointer>(instance);
}
diff --git b/src/nativeJsMain/cpp/paragraph/ParagraphCache.cc a/src/nativeJsMain/cpp/paragraph/ParagraphCache.cc
index ac4744e2..f5c16160 100644
--- b/src/nativeJsMain/cpp/paragraph/ParagraphCache.cc
+++ a/src/nativeJsMain/cpp/paragraph/ParagraphCache.cc
@@ -1,5 +1,4 @@
#include <iostream>
-#include <memory>
#include "ParagraphCache.h"
#include "ParagraphStyle.h"
using namespace skia::textlayout;
diff --git b/src/nativeJsMain/cpp/paragraph/ParagraphStyle.cc a/src/nativeJsMain/cpp/paragraph/ParagraphStyle.cc
index 2793cbd6..3afd9adc 100644
--- b/src/nativeJsMain/cpp/paragraph/ParagraphStyle.cc
+++ a/src/nativeJsMain/cpp/paragraph/ParagraphStyle.cc
@@ -160,6 +160,37 @@ SKIKO_EXPORT void org_jetbrains_skia_paragraph_ParagraphStyle__1nDisableHinting
instance->turnHintingOff();
}
+SKIKO_EXPORT void org_jetbrains_skia_paragraph_ParagraphStyle__1nSetFontRastrSettings
+ (KNativePointer ptr, KInt edging, KInt hinting, KBoolean subpixel) {
+ ParagraphStyle* instance = reinterpret_cast<ParagraphStyle*>(ptr);
+ FontRastrSettings fontRastrSettings;
+ fontRastrSettings.fEdging = static_cast<SkFont::Edging>(edging);
+ fontRastrSettings.fHinting = static_cast<SkFontHinting>(hinting);
+ fontRastrSettings.fSubpixel = subpixel;
+ instance->setFontRastrSettings(fontRastrSettings);
+}
+
+SKIKO_EXPORT KInt org_jetbrains_skia_paragraph_ParagraphStyle__1nGetEdging
+ (KNativePointer ptr) {
+ ParagraphStyle* instance = reinterpret_cast<ParagraphStyle*>(ptr);
+ FontRastrSettings fontRastrSettings = instance->getFontRastrSettings();
+ return static_cast<KInt>(fontRastrSettings.fEdging);
+}
+
+SKIKO_EXPORT KInt org_jetbrains_skia_paragraph_ParagraphStyle__1nGetHinting
+ (KNativePointer ptr) {
+ ParagraphStyle* instance = reinterpret_cast<ParagraphStyle*>(ptr);
+ FontRastrSettings fontRastrSettings = instance->getFontRastrSettings();
+ return static_cast<KInt>(fontRastrSettings.fHinting);
+}
+
+SKIKO_EXPORT KBoolean org_jetbrains_skia_paragraph_ParagraphStyle__1nGetSubpixel
+ (KNativePointer ptr) {
+ ParagraphStyle* instance = reinterpret_cast<ParagraphStyle*>(ptr);
+ FontRastrSettings fontRastrSettings = instance->getFontRastrSettings();
+ return fontRastrSettings.fSubpixel;
+}
+
SKIKO_EXPORT KBoolean org_jetbrains_skia_paragraph_ParagraphStyle__1nGetApplyRoundingHack
(KNativePointer ptr) {
ParagraphStyle* instance = reinterpret_cast<ParagraphStyle*>(ptr);
diff --git b/src/nativeJsMain/cpp/paragraph/TextStyle.cc a/src/nativeJsMain/cpp/paragraph/TextStyle.cc
index 960d5934..d11ce3ae 100644
--- b/src/nativeJsMain/cpp/paragraph/TextStyle.cc
+++ a/src/nativeJsMain/cpp/paragraph/TextStyle.cc
@@ -390,39 +390,50 @@ SKIKO_EXPORT void org_jetbrains_skia_paragraph_TextStyle__1nSetPlaceholder
TextStyle* instance = reinterpret_cast<TextStyle*>(ptr);
instance->setPlaceholder();
}
+//region ohos
SKIKO_EXPORT KInt org_jetbrains_skia_paragraph_TextStyle__1nGetFontEdging
(KNativePointer ptr) {
TextStyle* instance = reinterpret_cast<TextStyle*>(ptr);
- return static_cast<KInt>(instance->getFontEdging());
+ FontRastrSettings settings = instance->getFontRastrSettings();
+ return static_cast<KInt>(settings.fEdging);
}
SKIKO_EXPORT void org_jetbrains_skia_paragraph_TextStyle__1nSetFontEdging
(KNativePointer ptr, KInt fontEdging) {
TextStyle* instance = reinterpret_cast<TextStyle*>(ptr);
- instance->setFontEdging(static_cast<SkFont::Edging>(fontEdging));
+ FontRastrSettings settings = instance->getFontRastrSettings();
+ settings.fEdging = static_cast<SkFont::Edging>(fontEdging);
+ instance->setFontRastrSettings(settings);
}
SKIKO_EXPORT KBoolean org_jetbrains_skia_paragraph_TextStyle__1nGetSubpixel
(KNativePointer ptr) {
TextStyle* instance = reinterpret_cast<TextStyle*>(ptr);
- return instance->getSubpixel();
+ FontRastrSettings settings = instance->getFontRastrSettings();
+ return settings.fSubpixel;
}
SKIKO_EXPORT void org_jetbrains_skia_paragraph_TextStyle__1nSetSubpixel
(KNativePointer ptr, KBoolean subpixel) {
TextStyle* instance = reinterpret_cast<TextStyle*>(ptr);
- instance->setSubpixel(subpixel);
+ FontRastrSettings settings = instance->getFontRastrSettings();
+ settings.fSubpixel = subpixel;
+ instance->setFontRastrSettings(settings);
}
SKIKO_EXPORT KInt org_jetbrains_skia_paragraph_TextStyle__1nGetFontHinting
(KNativePointer ptr) {
TextStyle* instance = reinterpret_cast<TextStyle*>(ptr);
- return static_cast<KInt>(instance->getFontHinting());
+ FontRastrSettings settings = instance->getFontRastrSettings();
+ return static_cast<KInt>(settings.fHinting);
}
SKIKO_EXPORT void org_jetbrains_skia_paragraph_TextStyle__1nSetFontHinting
(KNativePointer ptr, KInt fontHinting) {
TextStyle* instance = reinterpret_cast<TextStyle*>(ptr);
- instance->setFontHinting(static_cast<SkFontHinting>(fontHinting));
+ FontRastrSettings settings = instance->getFontRastrSettings();
+ settings.fHinting = static_cast<SkFontHinting>(fontHinting);
+ instance->setFontRastrSettings(settings);
}
+//endregion
diff --git b/src/nativeJsMain/cpp/svg/SVGSVG.cc a/src/nativeJsMain/cpp/svg/SVGSVG.cc
index db6e6d3c..e8fb9673 100644
--- b/src/nativeJsMain/cpp/svg/SVGSVG.cc
+++ a/src/nativeJsMain/cpp/svg/SVGSVG.cc
@@ -37,9 +37,9 @@ SKIKO_EXPORT void org_jetbrains_skia_svg_SVGSVG__1nGetPreserveAspectRatio
SKIKO_EXPORT KBoolean org_jetbrains_skia_svg_SVGSVG__1nGetViewBox
(KNativePointer ptr, KInteropPointer result) {
SkSVGSVG* instance = reinterpret_cast<SkSVGSVG*>((ptr));
- std::optional<SkSVGViewBoxType> viewBox = instance->getViewBox();
- if (viewBox.has_value()) {
- skija::Rect::copyToInterop(viewBox.value(), result);
+ SkTLazy<SkSVGViewBoxType> viewBox = instance->getViewBox();
+ if (viewBox.isValid()) {
+ skija::Rect::copyToInterop(*viewBox.get(), result);
return true;
} else {
return false;
diff --git b/src/nativeJsMain/cpp/shaper/IcuBidiRunIterator.cc a/src/nativeJsMain/cpp/shaper/IcuBidiRunIterator.cc
index 79d3852c..b180bf35 100644
--- b/src/nativeJsMain/cpp/shaper/IcuBidiRunIterator.cc
+++ a/src/nativeJsMain/cpp/shaper/IcuBidiRunIterator.cc
@@ -4,7 +4,10 @@
SKIKO_EXPORT KNativePointer org_jetbrains_skia_shaper_IcuBidiRunIterator__1nMake
(KNativePointer textPtr, KInt bidiLevel) {
SkString* text = reinterpret_cast<SkString*>((textPtr));
- std::unique_ptr<SkShaper::BiDiRunIterator> instance(SkShaper::MakeIcuBiDiRunIterator(text->c_str(), text->size(), bidiLevel & 0xFF));
+ //region ohos
+ std::unique_ptr<SkShaper::BiDiRunIterator> instance(
+ SkShaper::MakeBiDiRunIterator(text->c_str(), text->size(), bidiLevel & 0xFF));
+ //endregion
return reinterpret_cast<KNativePointer>(instance.release());
}
diff --git b/src/nativeJsMain/cpp/shaper/HbIcuScriptRunIterator.cc a/src/nativeJsMain/cpp/shaper/HbIcuScriptRunIterator.cc
index 321af1ea..1d43b9c6 100644
--- b/src/nativeJsMain/cpp/shaper/HbIcuScriptRunIterator.cc
+++ a/src/nativeJsMain/cpp/shaper/HbIcuScriptRunIterator.cc
@@ -4,7 +4,11 @@
SKIKO_EXPORT KNativePointer org_jetbrains_skia_shaper_HbIcuScriptRunIterator__1nMake
(KNativePointer textPtr) {
SkString* text = reinterpret_cast<SkString*>((textPtr));
- std::unique_ptr<SkShaper::ScriptRunIterator> instance(SkShaper::MakeHbIcuScriptRunIterator(text->c_str(), text->size()));
+ //region ohos
+ constexpr SkFourByteTag commonScript = 0x5A797979; // ISO 15924 Zyyy
+ std::unique_ptr<SkShaper::ScriptRunIterator> instance(
+ SkShaper::MakeScriptRunIterator(text->c_str(), text->size(), commonScript));
+ //endregion
return reinterpret_cast<KNativePointer>(instance.release());
}
diff --git b/src/nativeJsMain/cpp/shaper/Shaper.cc a/src/nativeJsMain/cpp/shaper/Shaper.cc
index f008251c..a03f6c46 100644
--- b/src/nativeJsMain/cpp/shaper/Shaper.cc
+++ a/src/nativeJsMain/cpp/shaper/Shaper.cc
@@ -1,14 +1,11 @@
#include <iostream>
#include "SkShaper.h"
-#include "SkShaper_harfbuzz.h"
-#include "SkUnicode.h"
#include "src/base/SkUTF.h"
#include "unicode/ubidi.h"
#include "common.h"
#include "FontRunIterator.hh"
#include "TextLineRunHandler.hh"
#include "FontMgrDefaultFactory.hh"
-#include "SkUnicode_icu.h"
#ifdef SK_SHAPER_CORETEXT_AVAILABLE
#include "SkShaper_coretext.h"
@@ -27,5 +24,7 @@ SKIKO_EXPORT KNativePointer org_jetbrains_skia_shaper_Shaper__1nMakePrimitive
() {
- return reinterpret_cast<KNativePointer>(SkShapers::Primitive::PrimitiveText().release());
+ //region ohos
+ return reinterpret_cast<KNativePointer>(SkShaper::MakePrimitive().release());
+ //endregion
}
SKIKO_EXPORT KNativePointer org_jetbrains_skia_shaper_Shaper__1nMakeShaperDrivenWrapper
@@ -32,18 +31,19 @@ SKIKO_EXPORT KNativePointer org_jetbrains_skia_shaper_Shaper__1nMakeShaperDrivenWrapper
(KNativePointer fontMgrPtr) {
SkFontMgr* fontMgr = reinterpret_cast<SkFontMgr*>((fontMgrPtr));
- return reinterpret_cast<KNativePointer>(SkShaper::MakeShaperDrivenWrapper(sk_ref_sp(fontMgr)).release());
+ //region ohos
+ return reinterpret_cast<KNativePointer>(SkShaper::Make(sk_ref_sp(fontMgr)).release());
}
SKIKO_EXPORT KNativePointer org_jetbrains_skia_shaper_Shaper__1nMakeShapeThenWrap
(KNativePointer fontMgrPtr) {
SkFontMgr* fontMgr = reinterpret_cast<SkFontMgr*>((fontMgrPtr));
- return reinterpret_cast<KNativePointer>(SkShaper::MakeShapeThenWrap(sk_ref_sp(fontMgr)).release());
+ return reinterpret_cast<KNativePointer>(SkShaper::Make(sk_ref_sp(fontMgr)).release());
}
SKIKO_EXPORT KNativePointer org_jetbrains_skia_shaper_Shaper__1nMakeShapeDontWrapOrReorder
(KNativePointer fontMgrPtr) {
SkFontMgr* fontMgr = reinterpret_cast<SkFontMgr*>((fontMgrPtr));
- // TODO: consider if we need/want to use ICU4X or Libgrapheme (skuincode/include has those implementations too)
- sk_sp<SkUnicode> unicode = SkUnicodes::ICU::Make();
- return reinterpret_cast<KNativePointer>(SkShapers::HB::ShapeDontWrapOrReorder(unicode, sk_ref_sp(fontMgr)).release());
+ KNativePointer shaper = reinterpret_cast<KNativePointer>(SkShaper::Make(sk_ref_sp(fontMgr)).release());
+ //endregion
+ return shaper;
}
@@ -81,7 +81,11 @@ SKIKO_EXPORT KNativePointer org_jetbrains_skia_shaper_Shaper__1nShapeBlob
std::unique_ptr<SkShaper::BiDiRunIterator> bidiRunIter(SkShaper::MakeBiDiRunIterator(text.c_str(), text.size(), defaultBiDiLevel));
if (!bidiRunIter) return nullptr;
- std::unique_ptr<SkShaper::ScriptRunIterator> scriptRunIter(SkShaper::MakeHbIcuScriptRunIterator(text.c_str(), text.size()));
+ //region ohos
+ constexpr SkFourByteTag commonScript = 0x5A797979; // ISO 15924 Zyyy
+ std::unique_ptr<SkShaper::ScriptRunIterator> scriptRunIter(
+ SkShaper::MakeScriptRunIterator(text.c_str(), text.size(), commonScript));
+ //endregion
if (!scriptRunIter) return nullptr;
std::unique_ptr<SkShaper::LanguageRunIterator> languageRunIter(SkShaper::MakeStdLanguageRunIterator(text.c_str(), text.size()));
@@ -128,7 +132,11 @@ SKIKO_EXPORT KNativePointer org_jetbrains_skia_shaper_Shaper__1nShapeLine
std::unique_ptr<SkShaper::BiDiRunIterator> bidiRunIter(SkShaper::MakeBiDiRunIterator(text.c_str(), text.size(), defaultBiDiLevel));
if (!bidiRunIter) return nullptr;
- std::unique_ptr<SkShaper::ScriptRunIterator> scriptRunIter(SkShaper::MakeHbIcuScriptRunIterator(text.c_str(), text.size()));
+ //region ohos
+ constexpr SkFourByteTag commonScript = 0x5A797979; // ISO 15924 Zyyy
+ std::unique_ptr<SkShaper::ScriptRunIterator> scriptRunIter(
+ SkShaper::MakeScriptRunIterator(text.c_str(), text.size(), commonScript));
+ //endregion
if (!scriptRunIter) return nullptr;
std::unique_ptr<SkShaper::LanguageRunIterator> languageRunIter(SkShaper::MakeStdLanguageRunIterator(text.c_str(), text.size()));
/*
* Copyright (c) 2026 Huawei Device Co., Ltd.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
@file:OptIn(
kotlinx.cinterop.ExperimentalForeignApi::class,
org.jetbrains.skiko.ExperimentalSkikoApi::class,
)
package org.jetbrains.skia
import kotlinx.cinterop.ByteVar
import kotlinx.cinterop.COpaquePointer
import kotlinx.cinterop.CPointer
import kotlinx.cinterop.cstr
import kotlinx.cinterop.memScoped
import org.jetbrains.skia.impl.InteropPointer
import org.jetbrains.skia.impl.Native
import org.jetbrains.skia.impl.NativePointer
import org.jetbrains.skia.impl.Stats
import org.jetbrains.skia.impl.interopScope
import org.jetbrains.skiko.ExperimentalSkikoApi
/**
* Renderer-independent OHOS image decode request.
*
* The same request and result contract is published by the Skia and Fusion Renderer artifacts.
*/
@ExperimentalSkikoApi
sealed class OhosImageDecodeRequest {
class EncodedTargetSize(
val bytes: ByteArray,
val desiredWidthPx: Int,
val desiredHeightPx: Int,
val cropX: Int = 0,
val cropY: Int = 0,
val cropWidth: Int = 0,
val cropHeight: Int = 0,
) : OhosImageDecodeRequest()
class RawFile(
// Nullable so request validation can classify a null manager as INVALID_INPUT. Callers
// that obtain the manager from OhosAssetReader pass a non-null value unchanged.
val nativeResourceManager: COpaquePointer?,
val path: String,
val targetWidthPx: Int? = null,
val targetHeightPx: Int? = null,
val contentScaleMode: Int,
val decodeSizeBucketPx: Int,
) : OhosImageDecodeRequest()
class MediaResource(
val nativeResourceManager: COpaquePointer?,
val resourceId: Int,
val targetWidthPx: Int? = null,
val targetHeightPx: Int? = null,
val contentScaleMode: Int,
val decodeSizeBucketPx: Int,
) : OhosImageDecodeRequest()
}
@ExperimentalSkikoApi
enum class OhosImageDecodeFailureReason {
INVALID_INPUT,
DECODE_FAILED,
}
/**
* Result of an OHOS image decode.
*
* A successful result owns [image]; the caller must close it.
*/
@ExperimentalSkikoApi
sealed class OhosImageDecodeResult {
class Success(val image: Image) : OhosImageDecodeResult()
class Failure(
val reason: OhosImageDecodeFailureReason,
val message: String,
val cause: Throwable? = null,
) : OhosImageDecodeResult()
}
/**
* Stable OHOS decode facade shared by Skia and Fusion Renderer artifacts.
*
* This is a **synchronous, blocking** call. Long-running decodes should be dispatched
* to a background thread by the caller (e.g. `withContext(Dispatchers.Default)`).
*
* This operation does not require a renderer or [DirectContext]. The returned [Image] is
* renderer-independent and owned by the caller — the caller must [Image.close] it when done.
*
* For [OhosImageDecodeRequest.RawFile] and [OhosImageDecodeRequest.MediaResource], the
* `nativeResourceManager` must remain valid for the entire duration of this call.
*/
@ExperimentalSkikoApi
object OhosImageDecoder {
/**
* Decodes an OHOS image synchronously.
*
* @param request the decode request; must pass [OhosImageDecodeRequest.isValid].
* @return [OhosImageDecodeResult.Success] with an [Image] the caller owns and must close,
* or [OhosImageDecodeResult.Failure] with a reason and message.
*/
fun decode(request: OhosImageDecodeRequest): OhosImageDecodeResult {
if (!request.isValid()) {
return OhosImageDecodeResult.Failure(
OhosImageDecodeFailureReason.INVALID_INPUT,
"Invalid OHOS image decode request",
)
}
val image = try {
when (request) {
is OhosImageDecodeRequest.EncodedTargetSize ->
makeFromEncodedOhosTargetSizeShared(
bytes = request.bytes,
desiredWidthPx = request.desiredWidthPx,
desiredHeightPx = request.desiredHeightPx,
cropX = request.cropX,
cropY = request.cropY,
cropWidth = request.cropWidth,
cropHeight = request.cropHeight,
)
is OhosImageDecodeRequest.RawFile ->
makeFromRawFileOhosShared(
// isValid() already guaranteed a non-null manager; !! is safe on this path.
nativeResourceManager = request.nativeResourceManager!!,
rawFilePath = request.path,
targetWidthPx = request.targetWidthPx,
targetHeightPx = request.targetHeightPx,
contentScaleMode = request.contentScaleMode,
decodeSizeBucketPx = request.decodeSizeBucketPx,
)
is OhosImageDecodeRequest.MediaResource ->
makeFromMediaOhosShared(
// isValid() already guaranteed a non-null manager; !! is safe on this path.
nativeResourceManager = request.nativeResourceManager!!,
mediaResId = request.resourceId,
targetWidthPx = request.targetWidthPx,
targetHeightPx = request.targetHeightPx,
contentScaleMode = request.contentScaleMode,
decodeSizeBucketPx = request.decodeSizeBucketPx,
)
}
} catch (error: Exception) {
// Catch only Exception, not Error/OOM/LinkageError — let fatal errors propagate.
return OhosImageDecodeResult.Failure(
OhosImageDecodeFailureReason.DECODE_FAILED,
"OHOS image decode failed",
error,
)
}
return if (image != null) {
OhosImageDecodeResult.Success(image)
} else {
OhosImageDecodeResult.Failure(
OhosImageDecodeFailureReason.DECODE_FAILED,
"OHOS image decode failed",
)
}
}
}
private fun OhosImageDecodeRequest.isValid(): Boolean = when (this) {
is OhosImageDecodeRequest.EncodedTargetSize -> {
val noCrop = cropX == 0 && cropY == 0 && cropWidth == 0 && cropHeight == 0
val validCrop = cropX >= 0 && cropY >= 0 && cropWidth > 0 && cropHeight > 0
bytes.isNotEmpty() && desiredWidthPx > 0 && desiredHeightPx > 0 && (noCrop || validCrop)
}
is OhosImageDecodeRequest.RawFile ->
// COpaquePointer is nullable; a null resource-manager pointer must be classified as
// INVALID_INPUT rather than reaching native code, which rejects it inconsistently.
nativeResourceManager != null &&
path.isNotEmpty() &&
hasValidOptionalTargetSize(targetWidthPx, targetHeightPx)
is OhosImageDecodeRequest.MediaResource ->
nativeResourceManager != null &&
resourceId > 0 &&
hasValidOptionalTargetSize(targetWidthPx, targetHeightPx)
}
internal fun makeFromEncodedOhosTargetSizeShared(
bytes: ByteArray,
desiredWidthPx: Int,
desiredHeightPx: Int,
cropX: Int = 0,
cropY: Int = 0,
cropWidth: Int = 0,
cropHeight: Int = 0,
): Image? {
if (bytes.isEmpty() || desiredWidthPx <= 0 || desiredHeightPx <= 0) return null
Stats.onNativeCall()
val ptr = interopScope {
_nMakeFromEncodedOhosTargetSize(
bytes = toInterop(bytes),
encodedLength = bytes.size,
desiredWidthPx = desiredWidthPx,
desiredHeightPx = desiredHeightPx,
cropX = cropX,
cropY = cropY,
cropWidth = cropWidth,
cropHeight = cropHeight,
)
}
return if (ptr == Native.NullPointer) null else Image(ptr)
}
internal fun makeFromRawFileOhosShared(
nativeResourceManager: COpaquePointer,
rawFilePath: String,
targetWidthPx: Int? = null,
targetHeightPx: Int? = null,
contentScaleMode: Int,
decodeSizeBucketPx: Int,
): Image? {
if (rawFilePath.isEmpty() || !hasValidOptionalTargetSize(targetWidthPx, targetHeightPx)) return null
Stats.onNativeCall()
val ptr = memScoped {
_nMakeFromRawFileOhos(
nativeResourceManager = nativeResourceManager,
rawFilePath = rawFilePath.cstr.ptr,
targetWidthPx = targetWidthPx ?: 0,
targetHeightPx = targetHeightPx ?: 0,
contentScaleMode = contentScaleMode,
decodeSizeBucketPx = decodeSizeBucketPx,
)
}
return if (ptr == Native.NullPointer) null else Image(ptr)
}
internal fun makeFromMediaOhosShared(
nativeResourceManager: COpaquePointer,
mediaResId: Int,
targetWidthPx: Int? = null,
targetHeightPx: Int? = null,
contentScaleMode: Int,
decodeSizeBucketPx: Int,
): Image? {
if (mediaResId <= 0 || !hasValidOptionalTargetSize(targetWidthPx, targetHeightPx)) return null
Stats.onNativeCall()
val ptr = _nMakeFromMediaOhos(
nativeResourceManager = nativeResourceManager,
mediaResId = mediaResId,
targetWidthPx = targetWidthPx ?: 0,
targetHeightPx = targetHeightPx ?: 0,
contentScaleMode = contentScaleMode,
decodeSizeBucketPx = decodeSizeBucketPx,
)
return if (ptr == Native.NullPointer) null else Image(ptr)
}
private fun hasValidOptionalTargetSize(targetWidthPx: Int?, targetHeightPx: Int?): Boolean =
(targetWidthPx == null && targetHeightPx == null) ||
(targetWidthPx != null && targetWidthPx > 0 && targetHeightPx != null && targetHeightPx > 0)
@ExternalSymbolName("org_jetbrains_skia_Image__1nMakeFromEncodedOhosTargetSize")
private external fun _nMakeFromEncodedOhosTargetSize(
bytes: InteropPointer,
encodedLength: Int,
desiredWidthPx: Int,
desiredHeightPx: Int,
cropX: Int,
cropY: Int,
cropWidth: Int,
cropHeight: Int,
): NativePointer
@ExternalSymbolName("org_jetbrains_skia_Image__1nMakeFromRawFileOhos")
private external fun _nMakeFromRawFileOhos(
nativeResourceManager: COpaquePointer,
rawFilePath: CPointer<ByteVar>,
targetWidthPx: Int,
targetHeightPx: Int,
contentScaleMode: Int,
decodeSizeBucketPx: Int,
): NativePointer
@ExternalSymbolName("org_jetbrains_skia_Image__1nMakeFromMediaOhos")
private external fun _nMakeFromMediaOhos(
nativeResourceManager: COpaquePointer,
mediaResId: Int,
targetWidthPx: Int,
targetHeightPx: Int,
contentScaleMode: Int,
decodeSizeBucketPx: Int,
): NativePointer
@ExperimentalSkikoApi
enum class OhosImageInteropCapability {
SURFACE_BUFFER_ZERO_COPY,
}
@ExperimentalSkikoApi
enum class OhosImageTransferMode {
ZERO_COPY,
}
/**
* Result of importing an OHOS native image into the active renderer.
*/
@ExperimentalSkikoApi
sealed class OhosImageImportResult {
class Success(
val image: Image,
val transferMode: OhosImageTransferMode,
) : OhosImageImportResult()
class Unsupported(
val capability: OhosImageInteropCapability,
val message: String,
) : OhosImageImportResult()
class Failure(
val message: String,
val cause: Throwable? = null,
) : OhosImageImportResult()
}
/**
* Renderer-scoped image interop contract.
*
* Implementations hide backend-only state such as a Skia [DirectContext]. Callers can use this
* interface without branching on the active renderer.
*
* **Ownership contract for [importSurfaceBuffer]:**
* - The caller retains ownership of the passed `SurfaceBuffer` reference. On Skia success,
* the implementation internally calls `OH_NativeBuffer_Reference` so the produced [Image]
* survives independently; the caller may unreference its own ref after the call returns.
* - On [OhosImageImportResult.Success], the returned [Image] is owned by the caller —
* it **must** be [Image.close]d when no longer needed.
* - The Fusion Renderer returns [OhosImageImportResult.Unsupported] and does **not** create
* or require a [DirectContext].
*/
@ExperimentalSkikoApi
interface OhosImageInteropSession {
fun supports(capability: OhosImageInteropCapability): Boolean
/**
* Imports an OHOS SurfaceBuffer into the active renderer without copying its pixels.
*
* Call this only on the active renderer thread. A session is scoped to its renderer lifecycle;
* callers must handle [OhosImageImportResult.Failure] when a previously captured session has
* expired. A successful result owns its image, which must be closed by the caller.
*/
fun importSurfaceBuffer(
surfaceBufferPtr: NativePointer,
colorType: ColorType = ColorType.RGBA_8888,
alphaType: ColorAlphaType = ColorAlphaType.PREMUL,
colorSpace: ColorSpace = ColorSpace.sRGB,
): OhosImageImportResult
companion object {
val Unsupported: OhosImageInteropSession = object : OhosImageInteropSession {
override fun supports(capability: OhosImageInteropCapability): Boolean = false
override fun importSurfaceBuffer(
surfaceBufferPtr: NativePointer,
colorType: ColorType,
alphaType: ColorAlphaType,
colorSpace: ColorSpace,
): OhosImageImportResult = OhosImageImportResult.Unsupported(
OhosImageInteropCapability.SURFACE_BUFFER_ZERO_COPY,
"The active renderer does not support OHOS SurfaceBuffer import",
)
}
}
}
/**
* Copyright (c) 2026 Eazytec Co., Ltd.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#include "SkData.h"
#include "SkImage.h"
#include "SkBitmap.h"
#include "SkShader.h"
#include "SkEncodedImageFormat.h"
#include "include/gpu/ganesh/SkImageGanesh.h"
#include "gpu/ganesh/GrBackendSurface.h"
#include "gpu/ganesh/GrDirectContext.h"
#include "gpu/ganesh/GrTypes.h"
#include "gpu/ganesh/gl/GrGLTypes.h"
#include "gpu/ganesh/gl/GrGLBackendSurface.h"
#include "gpu/GpuTypes.h"
#include "SkAlphaType.h"
#include "SkColorSpace.h"
#include "SkColorType.h"
#include "common.h"
#include "Utils.h"
#define EGL_EGLEXT_PROTOTYPES
#include <EGL/egl.h>
#include <EGL/eglext.h>
#include <GLES2/gl2.h>
#define GL_GLEXT_PROTOTYPES
#include <GLES2/gl2ext.h>
#include "native_window/external_window.h"
#include <native_buffer/native_buffer.h>
#include <hilog/log.h>
#include <mutex>
#include <vector>
namespace {
const unsigned int LOG_PRINT_DOMAIN = 0xFF00;
// A/B: YUV NativeBuffer texture target.
// ExternalOes is the Khronos-correct target for YUV EGLImage siblings; Texture2D matches
// current OHOS sample snippets. Flip if device/Skia rejects one path.
enum class YuvImportTargetMode {
Texture2D = 0,
ExternalOes = 1,
};
static constexpr YuvImportTargetMode kYuvImportTargetMode = YuvImportTargetMode::ExternalOes;
struct OhosEglImageReleaseContext {
EGLDisplay display = EGL_NO_DISPLAY;
EGLImageKHR image = EGL_NO_IMAGE_KHR;
GLuint textureId = 0;
GLenum textureTarget = GL_TEXTURE_2D;
OH_NativeBuffer* nativeBuffer = nullptr;
OHNativeWindowBuffer* nativeWindowBuffer = nullptr;
};
static std::mutex gReleaseQueueMutex;
static std::vector<OhosEglImageReleaseContext *> gReleaseQueue;
static bool IsYuv420SpNativeFormat(int32_t format) {
return format == NATIVEBUFFER_PIXEL_FMT_YCRCB_420_SP ||
format == NATIVEBUFFER_PIXEL_FMT_YCBCR_420_SP;
}
static void OhosTextureReleaseProc(SkImages::ReleaseContext ctx) {
auto releaseCtx = static_cast<OhosEglImageReleaseContext *>(ctx);
if (!releaseCtx) {
return;
}
std::lock_guard<std::mutex> lock(gReleaseQueueMutex);
gReleaseQueue.push_back(releaseCtx);
}
static void DrainOhosReleaseQueueOnCurrentGLThread() {
EGLContext curCtx = eglGetCurrentContext();
if (curCtx == EGL_NO_CONTEXT) {
return;
}
std::vector<OhosEglImageReleaseContext *> pending;
{
std::lock_guard<std::mutex> lock(gReleaseQueueMutex);
pending.swap(gReleaseQueue);
}
if (pending.empty()) {
return;
}
PFNEGLDESTROYIMAGEKHRPROC eglDestroyImageKHR =
reinterpret_cast<PFNEGLDESTROYIMAGEKHRPROC>(eglGetProcAddress("eglDestroyImageKHR"));
for (auto* releaseCtx : pending) {
if (!releaseCtx) continue;
if (releaseCtx->textureId != 0) {
glDeleteTextures(1, &releaseCtx->textureId);
}
if (eglDestroyImageKHR && releaseCtx->image != EGL_NO_IMAGE_KHR &&
releaseCtx->display != EGL_NO_DISPLAY) {
eglDestroyImageKHR(releaseCtx->display, releaseCtx->image);
}
if (releaseCtx->nativeWindowBuffer) {
OH_NativeWindow_DestroyNativeWindowBuffer(releaseCtx->nativeWindowBuffer);
}
if (releaseCtx->nativeBuffer) {
OH_NativeBuffer_Unreference(releaseCtx->nativeBuffer);
}
delete releaseCtx;
}
}
} // namespace
SKIKO_EXPORT KNativePointer
org_jetbrains_skia_Image__1nMakeFromOhosSurfaceBuffer(KNativePointer contextPtr,
KNativePointer surfaceBufferPtr,
KInt colorType,
KInt alphaType,
KNativePointer colorSpacePtr) {
if (!contextPtr || !surfaceBufferPtr) return nullptr;
auto* context = reinterpret_cast<GrDirectContext*>(contextPtr);
auto* surfaceBuffer = reinterpret_cast<OH_NativeBuffer*>(surfaceBufferPtr);
if (!context || !surfaceBuffer) return nullptr;
EGLContext curCtx = eglGetCurrentContext();
if (curCtx == EGL_NO_CONTEXT) {
OH_LOG_Print(LOG_APP, LOG_ERROR, LOG_PRINT_DOMAIN, "EGLCore",
"MakeFromOhosSurfaceBuffer failed: EGL_NO_CONTEXT");
return nullptr;
}
DrainOhosReleaseQueueOnCurrentGLThread();
EGLDisplay display = eglGetCurrentDisplay();
if (display == EGL_NO_DISPLAY) display = eglGetDisplay(EGL_DEFAULT_DISPLAY);
if (display == EGL_NO_DISPLAY) return nullptr;
PFNEGLCREATEIMAGEKHRPROC eglCreateImageKHR =
reinterpret_cast<PFNEGLCREATEIMAGEKHRPROC>(eglGetProcAddress("eglCreateImageKHR"));
PFNEGLDESTROYIMAGEKHRPROC eglDestroyImageKHR =
reinterpret_cast<PFNEGLDESTROYIMAGEKHRPROC>(eglGetProcAddress("eglDestroyImageKHR"));
if (!eglCreateImageKHR) return nullptr;
OH_NativeBuffer_Config config = {};
OH_NativeBuffer_GetConfig(surfaceBuffer, &config);
const bool isYuvBuffer = IsYuv420SpNativeFormat(config.format);
const GLenum textureTarget =
(isYuvBuffer && kYuvImportTargetMode == YuvImportTargetMode::ExternalOes)
? GL_TEXTURE_EXTERNAL_OES
: GL_TEXTURE_2D;
GLint prevActiveTex = GL_TEXTURE0;
GLint prevTex2D = 0;
GLint prevTexExternal = 0;
glGetIntegerv(GL_ACTIVE_TEXTURE, &prevActiveTex);
glGetIntegerv(GL_TEXTURE_BINDING_2D, &prevTex2D);
if (textureTarget == GL_TEXTURE_EXTERNAL_OES) {
glGetIntegerv(GL_TEXTURE_BINDING_EXTERNAL_OES, &prevTexExternal);
}
OHNativeWindowBuffer* nativeWindowBuffer = nullptr;
EGLImageKHR eglImage = EGL_NO_IMAGE_KHR;
GLuint texId = 0;
bool bufferRefHeld = false;
KNativePointer outPtr = nullptr;
auto cleanupFail = [&]() {
if (texId != 0) glDeleteTextures(1, &texId);
if (eglDestroyImageKHR && eglImage != EGL_NO_IMAGE_KHR) {
eglDestroyImageKHR(display, eglImage);
}
if (nativeWindowBuffer) {
OH_NativeWindow_DestroyNativeWindowBuffer(nativeWindowBuffer);
}
if (bufferRefHeld) {
OH_NativeBuffer_Unreference(surfaceBuffer);
}
};
nativeWindowBuffer = OH_NativeWindow_CreateNativeWindowBufferFromNativeBuffer(surfaceBuffer);
if (!nativeWindowBuffer) {
cleanupFail();
return nullptr;
}
if (OH_NativeBuffer_Reference(surfaceBuffer) != 0) {
cleanupFail();
return nullptr;
}
bufferRefHeld = true;
EGLint attrs[] = { EGL_IMAGE_PRESERVED, EGL_TRUE, EGL_NONE };
eglImage = eglCreateImageKHR(display, EGL_NO_CONTEXT, EGL_NATIVE_BUFFER_OHOS, nativeWindowBuffer, attrs);
if (eglImage == EGL_NO_IMAGE_KHR) {
OH_LOG_Print(LOG_APP, LOG_ERROR, LOG_PRINT_DOMAIN, "EGLCore",
"MakeFromOhosSurfaceBuffer: eglCreateImageKHR failed format=%{public}d yuv=%{public}d",
config.format, isYuvBuffer ? 1 : 0);
cleanupFail();
return nullptr;
}
glGenTextures(1, &texId);
if (texId == 0) {
cleanupFail();
return nullptr;
}
glBindTexture(textureTarget, texId);
glTexParameteri(textureTarget, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
glTexParameteri(textureTarget, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
glTexParameteri(textureTarget, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
glTexParameteri(textureTarget, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
glEGLImageTargetTexture2DOES(textureTarget, static_cast<GLeglImageOES>(eglImage));
const GLenum bindErr = glGetError();
if (bindErr != GL_NO_ERROR) {
OH_LOG_Print(LOG_APP, LOG_ERROR, LOG_PRINT_DOMAIN, "EGLCore",
"MakeFromOhosSurfaceBuffer: glEGLImageTargetTexture2DOES err=0x%{public}x "
"target=0x%{public}x format=%{public}d",
bindErr, textureTarget, config.format);
cleanupFail();
return nullptr;
}
if (textureTarget == GL_TEXTURE_EXTERNAL_OES) {
glBindTexture(GL_TEXTURE_EXTERNAL_OES, static_cast<GLuint>(prevTexExternal));
} else {
glBindTexture(GL_TEXTURE_2D, static_cast<GLuint>(prevTex2D));
}
glActiveTexture(prevActiveTex);
// GPU samples YUV EGLImage as RGB; Skia color type describes the sampled result.
#ifdef GL_RGBA8_OES
const GrGLenum glSizedFormat = GL_RGBA8_OES;
#else
const GrGLenum glSizedFormat = GL_RGBA8;
#endif
// OH_NativeBuffer_GetConfig returns void, so validate the populated dimensions before
// handing them to Skia. A zero or negative width/height would build a degenerate backend
// texture (and likely a GL error downstream); reject early and run the same cleanup as
// every other failure path so the EGL image, texture and NativeBuffer reference are freed.
if (config.width <= 0 || config.height <= 0) {
OH_LOG_Print(LOG_APP, LOG_ERROR, LOG_PRINT_DOMAIN, "EGLCore",
"MakeFromOhosSurfaceBuffer failed: invalid NativeBuffer config w=%d h=%d",
config.width, config.height);
cleanupFail();
return nullptr;
}
GrGLTextureInfo textureInfo = { textureTarget, texId, glSizedFormat };
GrBackendTexture backendTexture = GrBackendTextures::MakeGL(config.width,
config.height,
skgpu::Mipmapped::kNo,
textureInfo);
auto cs = reinterpret_cast<SkColorSpace *>(colorSpacePtr);
sk_sp<SkColorSpace> colorSpace = cs ? sk_ref_sp(cs) : nullptr;
// For YUV buffers ignore caller color/alpha: sampled texels are opaque RGB.
const SkColorType effectiveColorType =
isYuvBuffer ? kRGBA_8888_SkColorType : static_cast<SkColorType>(colorType);
const SkAlphaType effectiveAlphaType =
isYuvBuffer ? kOpaque_SkAlphaType : static_cast<SkAlphaType>(alphaType);
OH_LOG_Print(LOG_APP, LOG_DEBUG, LOG_PRINT_DOMAIN, "EGLCore",
"ImportContract: w=%{public}d h=%{public}d stride=%{public}d "
"nbFormat=%{public}d actualYuv=%{public}d glTarget=0x%{public}x "
"skColorType=%{public}d skAlphaType=%{public}d",
config.width,
config.height,
config.stride,
config.format,
isYuvBuffer ? 1 : 0,
textureTarget,
static_cast<int>(effectiveColorType),
static_cast<int>(effectiveAlphaType));
context->resetContext(kAll_GrBackendState);
auto releaseCtx = new OhosEglImageReleaseContext{
display, eglImage, texId, textureTarget, surfaceBuffer, nativeWindowBuffer
};
sk_sp<SkImage> image = SkImages::BorrowTextureFrom(
context,
backendTexture,
kTopLeft_GrSurfaceOrigin,
effectiveColorType,
effectiveAlphaType,
colorSpace,
OhosTextureReleaseProc,
releaseCtx);
if (!image) {
OH_LOG_Print(LOG_APP, LOG_ERROR, LOG_PRINT_DOMAIN, "EGLCore",
"MakeFromOhosSurfaceBuffer: BorrowTextureFrom failed yuv=%{public}d target=0x%{public}x",
isYuvBuffer ? 1 : 0, textureTarget);
OhosTextureReleaseProc(releaseCtx);
DrainOhosReleaseQueueOnCurrentGLThread();
return nullptr;
}
outPtr = reinterpret_cast<KNativePointer>(image.release());
return outPtr;
}
SKIKO_EXPORT void org_jetbrains_skia_Image__1nDrainOhosSurfaceBufferReleases() {
DrainOhosReleaseQueueOnCurrentGLThread();
}
/**
* Copyright (c) 2026 Eazytec Co., Ltd.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#ifndef SKIA_CPP_UTILS_H
#define SKIA_CPP_UTILS_H
#include <hilog/log.h>
#ifndef SKIKO_OHOS_LOG_TAG
#define SKIKO_OHOS_LOG_TAG "SkikoOHOS"
#endif
#ifdef SKIKO_DEBUG
#define SKIKO_LOG_DEBUG(...) ((void)OH_LOG_Print(LOG_APP, LOG_DEBUG, LOG_DOMAIN, SKIKO_OHOS_LOG_TAG, __VA_ARGS__))
#else
#define SKIKO_LOG_DEBUG(...)
#endif
#define SKIKO_LOG_INFO(...) ((void)OH_LOG_Print(LOG_APP, LOG_INFO, LOG_DOMAIN, SKIKO_OHOS_LOG_TAG, __VA_ARGS__))
#define SKIKO_LOG_WARN(...) ((void)OH_LOG_Print(LOG_APP, LOG_WARN, LOG_DOMAIN, SKIKO_OHOS_LOG_TAG, __VA_ARGS__))
#define SKIKO_LOG_ERROR(...) ((void)OH_LOG_Print(LOG_APP, LOG_ERROR, LOG_DOMAIN, SKIKO_OHOS_LOG_TAG, __VA_ARGS__))
#endif
/*
* Eazytec is pleased to support the open source community by making Skiko available.
* Copyright (C) 2026 Eazytec. All rights reserved.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
@file:OptIn(kotlinx.cinterop.ExperimentalForeignApi::class)
package org.jetbrains.skia
import org.jetbrains.skia.ExternalSymbolName
import org.jetbrains.skia.impl.NativePointer
import org.jetbrains.skia.impl.Native
import org.jetbrains.skia.impl.Stats
import org.jetbrains.skia.impl.getPtr
import org.jetbrains.skia.impl.reachabilityBarrier
/**
* Import an OH_NativeBuffer-backed texture into Skia.
*
* For YUV420 SP buffers (`YCRCB_420_SP` / `YCBCR_420_SP`), native code detects format and
* forces opaque RGBA8 semantics on the **sampled** result (GPU converts YUV→RGB). Caller
* [colorType]/[alphaType] are ignored in that case.
*/
@Deprecated(
message = "Use a renderer-scoped OhosImageInteropSession instead",
)
fun Image.Companion.makeFromOhosSurfaceBuffer(
context: DirectContext,
surfaceBufferPtr: NativePointer,
colorType: ColorType = ColorType.RGBA_8888,
alphaType: ColorAlphaType = ColorAlphaType.PREMUL,
colorSpace: ColorSpace = ColorSpace.sRGB
): Image {
return try {
Stats.onNativeCall()
val ptr = _nMakeFromOhosSurfaceBuffer(
getPtr(context),
surfaceBufferPtr,
colorType.ordinal,
alphaType.ordinal,
getPtr(colorSpace)
)
if (ptr == Native.NullPointer) throw RuntimeException("Failed to makeFromOhosSurfaceBuffer")
Image(ptr)
} finally {
reachabilityBarrier(context)
reachabilityBarrier(colorSpace)
}
}
fun drainOhosSurfaceBufferReleases() {
Stats.onNativeCall()
_nDrainOhosSurfaceBufferReleases()
}
@ExternalSymbolName("org_jetbrains_skia_Image__1nMakeFromOhosSurfaceBuffer")
private external fun _nMakeFromOhosSurfaceBuffer(
contextPtr: NativePointer,
surfaceBufferPtr: NativePointer,
colorType: Int,
alphaType: Int,
colorSpacePtr: NativePointer
): NativePointer
@ExternalSymbolName("org_jetbrains_skia_Image__1nDrainOhosSurfaceBufferReleases")
private external fun _nDrainOhosSurfaceBufferReleases()
/*
* Eazytec is pleased to support the open source community by making Skiko available.
* Copyright (C) 2026 Eazytec. All rights reserved.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.jetbrains.skiko
interface IClipboardProxy {
fun setText(text: String)
fun getText(): String?
fun hasText(): Boolean
}
object PlatformProxy {
var clipboardProxy: IClipboardProxy? = null
}
/*
* Eazytec is pleased to support the open source community by making Skiko available.
* Copyright (C) 2026 Eazytec. All rights reserved.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.jetbrains.skiko
import org.jetbrains.skia.Canvas
import org.jetbrains.skia.PixelGeometry
/**
* Generic layer for Skiko rendering.
*/
actual open class SkiaLayer {
/**
* Current graphics API used for rendering.
*/
actual var renderApi: GraphicsApi
get() = GraphicsApi.OPENGL
set(_) { throw UnsupportedOperationException() }
/**
* Current content scale.
*/
actual val contentScale: Float
get() = TODO("Not yet implemented")
/**
* Pixel geometry corresponding to graphics device which renders this layer
*/
actual val pixelGeometry: PixelGeometry
get() = TODO("Not yet implemented")
/**
* If rendering is full screen.
*/
actual var fullscreen: Boolean
get() = TODO("Not yet implemented")
set(value) {}
/**
* Underlying platform component.
*/
actual val component: Any?
get() = TODO("Not yet implemented")
/**
* Current render delegate used for rendering.
*/
actual var renderDelegate: SkikoRenderDelegate?
get() = TODO("Not yet implemented")
set(value) {}
/**
* Attach this SkikoView to platform container.
* Actual type of attach container is platform-specific.
*/
actual fun attachTo(container: Any) {
}
/**
* Detach this SkikoView from platform container.
*/
actual fun detach() {
}
/**
* Force redraw.
*/
actual fun needRender(throttledToVsync: Boolean) {
}
@Deprecated("Use needRender() instead", ReplaceWith("needRender()"))
actual fun needRedraw() {
needRender()
}
/**
* Drawing function.
*/
internal actual fun draw(canvas: Canvas) {
}
}
/*
* Eazytec is pleased to support the open source community by making Skiko available.
* Copyright (C) 2026 Eazytec. All rights reserved.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.jetbrains.skiko
import org.jetbrains.skia.ExternalSymbolName
@ExternalSymbolName("skikobridge_napi_get_system_theme")
private external fun skikobridge_napi_get_system_theme(): Int
actual val currentSystemTheme: SystemTheme
get() = try {
val themeValue = skikobridge_napi_get_system_theme()
when (themeValue) {
1 -> SystemTheme.LIGHT
2 -> SystemTheme.DARK
else -> SystemTheme.UNKNOWN
}
} catch (_: Exception) {
SystemTheme.UNKNOWN
}
...@@ -69,6 +69,15 @@ kotlin { ...@@ -69,6 +69,15 @@ kotlin {
linuxX64() linuxX64()
linuxArm64() linuxArm64()
} }
// region Ohos
// Keep this subproject valid when an OHOS-only build disables every other target.
if (supportNativeOhosArm64) {
ohosArm64()
}
if (supportNativeOhosX64) {
ohosX64()
}
// endregion
if (supportNativeIosArm64) { if (supportNativeIosArm64) {
iosArm64() iosArm64()
} }
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment