-
Notifications
You must be signed in to change notification settings - Fork 123
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Add API to configure the fallback fonts provider when all default met…
…hods return null (#935) New API: ```kotlin fun TypefaceFontProvider.createAsFallbackProvider(): TypefaceFontProvider fun FontMgr.defaultWithFallbackFontProvider(fallback: TypefaceFontProvider): FontMgr ``` Usage: ```kotlin val fontCollection = FontCollection() val notoEmojiTypeface = Typeface.makeFromData(Data.makeFromBytes(notoEmojisBytes)) val fallbackProvider = TypefaceFontProvider.createAsFallbackProvider().apply { registerTypeface(notoEmojiTypeface) } fontCollection.setDefaultFontManager(FontMgr.defaultWithFallbackFontProvider(fallbackProvider)) ``` <img width="400" alt="Screenshot 2024-06-07 at 14 48 28" src="https://github.com/JetBrains/skiko/assets/7372778/76dc1330-d8e2-4be7-ad92-68214b058b4f">
- Loading branch information
Showing
21 changed files
with
495 additions
and
19 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,4 +1,4 @@ | ||
kotlin.code.style=official | ||
org.gradle.jvmargs=-Xmx3G -XX:MaxMetaspaceSize=512m | ||
kotlin.version=1.9.21 | ||
#skiko.composite.build=1 | ||
skiko.composite.build=1 |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
47 changes: 47 additions & 0 deletions
47
...es/SkiaMultiplatformSample/src/commonMain/kotlin/org/jetbrains/skiko/sample/EmojiStory.kt
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,47 @@ | ||
package org.jetbrains.skiko.sample | ||
|
||
import org.jetbrains.skia.Canvas | ||
import org.jetbrains.skia.FontMgr | ||
import org.jetbrains.skia.paragraph.FontCollection | ||
import org.jetbrains.skia.paragraph.ParagraphBuilder | ||
import org.jetbrains.skia.paragraph.ParagraphStyle | ||
import org.jetbrains.skia.paragraph.TextStyle | ||
import org.jetbrains.skiko.OS | ||
import org.jetbrains.skiko.SkikoRenderDelegate | ||
import org.jetbrains.skiko.hostOs | ||
|
||
class EmojiStory : SkikoRenderDelegate { | ||
|
||
private val platformYOffset = if (hostOs == OS.Ios) 50f else 5f | ||
|
||
private val style = ParagraphStyle().apply { | ||
textStyle = TextStyle().apply { | ||
this.fontSize = 16.0f | ||
}.setColor(0xFF000000.toInt()) | ||
} | ||
|
||
private val paragraph = ParagraphBuilder(style, fontCollection) | ||
.addText("\uD83D\uDCE1 Antenna - 天线\n") | ||
.addText("⁉\uFE0F 〰\uFE0F ⁉\uFE0F\n") | ||
.addText("\uD83D\uDE32 \uD83E\uDD14 \uD83D\uDCA1\n") | ||
.addText("\uD83C\uDF0D Earth - 地球\n") | ||
.addText("\uD83D\uDE80 Space rocket - 太空火箭\n") | ||
.addText("\uD83C\uDF14 Moon - 月亮\n") | ||
.addText("\uD83D\uDCAB Stars - 星星\n") | ||
.addText("\uD83C\uDF0C Galaxy - 星系\n") | ||
.addText("\uD83D\uDEF8 UFO - 飞碟\n") | ||
.addText("\uD83D\uDC7D Alien - 外星人\n") | ||
.addText("\uD83D\uDC4B \uD83E\uDD1D \uD83C\uDF7B \n") | ||
.build() | ||
|
||
override fun onRender(canvas: Canvas, width: Int, height: Int, nanoTime: Long) { | ||
paragraph.layout(Float.POSITIVE_INFINITY) | ||
paragraph.paint(canvas, 5f, platformYOffset) | ||
canvas.resetMatrix() | ||
} | ||
|
||
companion object { | ||
val fontCollection = FontCollection() | ||
.setDefaultFontManager(FontMgr.default) | ||
} | ||
} |
39 changes: 37 additions & 2 deletions
39
samples/SkiaMultiplatformSample/src/wasmJsMain/kotlin/org/jetbrains/skiko/sample/App.wasm.kt
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,19 +1,54 @@ | ||
package org.jetbrains.skiko.sample | ||
|
||
import kotlinx.browser.document | ||
import kotlinx.coroutines.MainScope | ||
import kotlinx.coroutines.launch | ||
import org.jetbrains.skia.Data | ||
import org.jetbrains.skia.FontMgrWithFallback | ||
import org.jetbrains.skia.Typeface | ||
import org.jetbrains.skia.paragraph.TypefaceFontProviderWithFallback | ||
import org.jetbrains.skiko.SkiaLayer | ||
import org.jetbrains.skiko.SkiaLayerRenderDelegate | ||
import org.w3c.dom.HTMLCanvasElement | ||
|
||
|
||
fun main() { | ||
runApp() | ||
//runClocksApp() | ||
runEmojiStoryApp() | ||
} | ||
|
||
internal fun runApp() { | ||
internal fun runClocksApp() { | ||
val canvas = document.getElementById("SkikoTarget") as HTMLCanvasElement | ||
canvas.setAttribute("tabindex", "0") | ||
val skiaLayer = SkiaLayer() | ||
val clocks = WebClocks(skiaLayer, canvas) | ||
skiaLayer.renderDelegate = SkiaLayerRenderDelegate(skiaLayer, clocks) | ||
skiaLayer.needRedraw() | ||
} | ||
|
||
private val notoColorEmoji = "https://storage.googleapis.com/skia-cdn/misc/NotoColorEmoji.ttf" | ||
private val notoSancSC = "./NotoSansSC-Regular.ttf" | ||
|
||
internal fun runEmojiStoryApp() { | ||
val canvas = document.getElementById("SkikoTarget") as HTMLCanvasElement | ||
canvas.setAttribute("tabindex", "0") | ||
val skiaLayer = SkiaLayer() | ||
val app = EmojiStory() | ||
skiaLayer.renderDelegate = SkiaLayerRenderDelegate(skiaLayer, app) | ||
skiaLayer.attachTo(canvas) | ||
|
||
MainScope().launch { | ||
val notoEmojisBytes = loadRes(notoColorEmoji).toByteArray() | ||
val notoSansSCBytes = loadRes(notoSancSC).toByteArray() | ||
val notoEmojiTypeface = Typeface.makeFromData(Data.makeFromBytes(notoEmojisBytes)) | ||
val notoSansSCTypeface = Typeface.makeFromData(Data.makeFromBytes(notoSansSCBytes)) | ||
|
||
val tfp = TypefaceFontProviderWithFallback().apply { | ||
registerTypeface(notoEmojiTypeface) | ||
registerTypeface(notoSansSCTypeface) | ||
} | ||
EmojiStory.fontCollection.setDefaultFontManager(FontMgrWithFallback(tfp)) | ||
|
||
skiaLayer.needRedraw() | ||
} | ||
} |
45 changes: 45 additions & 0 deletions
45
samples/SkiaMultiplatformSample/src/wasmJsMain/kotlin/org/jetbrains/skiko/sample/Utils.kt
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,45 @@ | ||
package org.jetbrains.skiko.sample | ||
|
||
import kotlinx.browser.window | ||
import kotlinx.coroutines.await | ||
import org.khronos.webgl.ArrayBuffer | ||
import org.khronos.webgl.Int8Array | ||
import org.w3c.fetch.Response | ||
import org.w3c.xhr.XMLHttpRequest | ||
import kotlin.coroutines.resume | ||
import kotlin.coroutines.resumeWithException | ||
import kotlin.coroutines.suspendCoroutine | ||
import kotlin.wasm.unsafe.UnsafeWasmMemoryApi | ||
import kotlin.wasm.unsafe.withScopedMemoryAllocator | ||
|
||
suspend fun loadRes(url: String): ArrayBuffer { | ||
return window.fetch(url).await<Response>().arrayBuffer().await() | ||
} | ||
|
||
private class MissingResourceException(url: String): Exception("GET $url failed") | ||
|
||
fun ArrayBuffer.toByteArray(): ByteArray { | ||
val source = Int8Array(this, 0, byteLength) | ||
return jsInt8ArrayToKotlinByteArray(source) | ||
} | ||
|
||
@JsFun( | ||
""" (src, size, dstAddr) => { | ||
const mem8 = new Int8Array(wasmExports.memory.buffer, dstAddr, size); | ||
mem8.set(src); | ||
} | ||
""" | ||
) | ||
internal external fun jsExportInt8ArrayToWasm(src: Int8Array, size: Int, dstAddr: Int) | ||
|
||
internal fun jsInt8ArrayToKotlinByteArray(x: Int8Array): ByteArray { | ||
val size = x.length | ||
|
||
@OptIn(UnsafeWasmMemoryApi::class) | ||
return withScopedMemoryAllocator { allocator -> | ||
val memBuffer = allocator.allocate(size) | ||
val dstAddress = memBuffer.address.toInt() | ||
jsExportInt8ArrayToWasm(x, size, dstAddress) | ||
ByteArray(size) { i -> (memBuffer + i).loadByte() } | ||
} | ||
} |
Binary file added
BIN
+10.1 MB
samples/SkiaMultiplatformSample/src/wasmJsMain/resources/NotoSansSC-Regular.ttf
Binary file not shown.
85 changes: 85 additions & 0 deletions
85
skiko/src/commonMain/cpp/common/FontMgrWithFallbackWrapper.cc
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,85 @@ | ||
#include "FontMgrWithFallbackWrapper.hh" | ||
|
||
FontMgrWithFallbackWrapper::FontMgrWithFallbackWrapper(sk_sp<TypefaceFontProviderWithFallback> fallbackTypefaceFontProvider) | ||
: fallbackFontProvider(std::move(fallbackTypefaceFontProvider)) { | ||
wrappedFntMgr = SkFontMgr::RefDefault(); | ||
} | ||
|
||
int FontMgrWithFallbackWrapper::onCountFamilies() const { | ||
return wrappedFntMgr->countFamilies(); | ||
} | ||
|
||
void FontMgrWithFallbackWrapper::onGetFamilyName(int index, SkString* familyName) const { | ||
wrappedFntMgr->getFamilyName(index, familyName); | ||
} | ||
|
||
sk_sp<SkFontStyleSet> FontMgrWithFallbackWrapper::onMatchFamily(const char familyName[]) const { | ||
return wrappedFntMgr->matchFamily(familyName); | ||
} | ||
|
||
sk_sp<SkFontStyleSet> FontMgrWithFallbackWrapper::onCreateStyleSet(int ix) const { | ||
return wrappedFntMgr->createStyleSet(ix); | ||
} | ||
|
||
sk_sp<SkTypeface> FontMgrWithFallbackWrapper::onMatchFamilyStyle(const char familyName[], const SkFontStyle& style) const { | ||
return wrappedFntMgr->matchFamilyStyle(familyName, style); | ||
} | ||
|
||
sk_sp<SkTypeface> FontMgrWithFallbackWrapper::onMatchFamilyStyleCharacter( | ||
const char fontFamily[], const SkFontStyle& style, | ||
const char* bcp47[], int bcp47Count, | ||
SkUnichar character | ||
) const { | ||
auto typeface = wrappedFntMgr->matchFamilyStyleCharacter(fontFamily, style, bcp47, bcp47Count, character); | ||
if (!typeface) { | ||
typeface = fallbackFontProvider->fallbackForChar(character); | ||
} | ||
return typeface; | ||
} | ||
|
||
sk_sp<SkTypeface> FontMgrWithFallbackWrapper::onMakeFromData(sk_sp<SkData> data, int ix) const { | ||
return wrappedFntMgr->makeFromData(std::move(data), ix); | ||
} | ||
|
||
sk_sp<SkTypeface> FontMgrWithFallbackWrapper::onMakeFromStreamIndex(std::unique_ptr<SkStreamAsset> stream, int ix) const { | ||
return wrappedFntMgr->makeFromStream(std::move(stream), ix); | ||
} | ||
|
||
sk_sp<SkTypeface> FontMgrWithFallbackWrapper::onMakeFromStreamArgs(std::unique_ptr<SkStreamAsset> stream, const SkFontArguments& args) const { | ||
return wrappedFntMgr->makeFromStream(std::move(stream), args); | ||
} | ||
|
||
sk_sp<SkTypeface> FontMgrWithFallbackWrapper::onMakeFromFile(const char path[], int ix) const { | ||
return wrappedFntMgr->makeFromFile(path, ix); | ||
} | ||
|
||
sk_sp<SkTypeface> FontMgrWithFallbackWrapper::onLegacyMakeTypeface(const char name[], SkFontStyle style) const { | ||
return wrappedFntMgr->legacyMakeTypeface(name, style); | ||
} | ||
|
||
void FontMgrWithFallbackWrapper::setFallbackFontProvider(sk_sp<TypefaceFontProviderWithFallback> fontProvider) { | ||
fallbackFontProvider = std::move(fontProvider); | ||
} | ||
|
||
/* TypefaceFontProviderWithFallback section */ | ||
|
||
size_t TypefaceFontProviderWithFallback::registerTypeface(sk_sp<SkTypeface> typeface) { | ||
registeredTypefaces.push_back(typeface); | ||
return TypefaceFontProvider::registerTypeface(std::move(typeface)); | ||
} | ||
|
||
size_t TypefaceFontProviderWithFallback::registerTypeface(sk_sp<SkTypeface> typeface, const SkString& alias) { | ||
registeredTypefaces.push_back(typeface); | ||
return TypefaceFontProvider::registerTypeface(std::move(typeface), alias); | ||
} | ||
|
||
sk_sp<SkTypeface> TypefaceFontProviderWithFallback::fallbackForChar(SkUnichar character) const { | ||
for (const auto& typeface : registeredTypefaces) { | ||
if (!typeface) continue; | ||
auto glyph = typeface->unicharToGlyph(character); | ||
if (glyph != 0) { | ||
return typeface; | ||
} | ||
} | ||
return nullptr; | ||
} |
57 changes: 57 additions & 0 deletions
57
skiko/src/commonMain/cpp/common/include/FontMgrWithFallbackWrapper.hh
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,57 @@ | ||
#ifndef FONT_MGR_WRAPPER_H | ||
#define FONT_MGR_WRAPPER_H | ||
|
||
#include "SkData.h" | ||
#include "SkStream.h" | ||
#include "SkTypeface.h" | ||
#include "SkFontMgr.h" | ||
#include "TypefaceFontProvider.h" | ||
|
||
using namespace skia::textlayout; | ||
|
||
// extends the default TypefaceFontProvider: | ||
// - all registered Typefaces are collected in registeredTypefaces | ||
// - then they are used to find a fallback Typeface in `fallbackForChar` | ||
class TypefaceFontProviderWithFallback : public TypefaceFontProvider { | ||
public: | ||
size_t registerTypeface(sk_sp<SkTypeface> typeface); | ||
size_t registerTypeface(sk_sp<SkTypeface> typeface, const SkString& alias); | ||
|
||
sk_sp<SkTypeface> fallbackForChar(SkUnichar character) const; | ||
|
||
private: | ||
std::vector<sk_sp<SkTypeface>> registeredTypefaces; | ||
}; | ||
|
||
// FontMgrWithFallbackWrapper implementation in most methods simply delegates to wrappedFntMgr. | ||
// The only modified implementation is onMatchFamilyStyleCharacter, which attempts to find | ||
// any Typeface (fallback) for a given character, if all other default fallbacks didn't find anything. | ||
// To achieve this it relies on fallbackFontProvider set via `setFallbackFontProvider`. | ||
class FontMgrWithFallbackWrapper : public SkFontMgr { | ||
public: | ||
explicit FontMgrWithFallbackWrapper(sk_sp<TypefaceFontProviderWithFallback>); | ||
int onCountFamilies() const override; | ||
void onGetFamilyName(int index, SkString* familyName) const override; | ||
sk_sp<SkFontStyleSet> onMatchFamily(const char familyName[]) const override; | ||
sk_sp<SkFontStyleSet> onCreateStyleSet(int) const override; | ||
sk_sp<SkTypeface> onMatchFamilyStyle(const char[], const SkFontStyle&) const override; | ||
sk_sp<SkTypeface> onMatchFamilyStyleCharacter(const char[], const SkFontStyle&, | ||
const char*[], int, | ||
SkUnichar) const override; | ||
sk_sp<SkTypeface> onMakeFromData(sk_sp<SkData>, int) const override; | ||
sk_sp<SkTypeface> onMakeFromStreamIndex(std::unique_ptr<SkStreamAsset>, int) const override; | ||
sk_sp<SkTypeface> onMakeFromStreamArgs(std::unique_ptr<SkStreamAsset>, | ||
const SkFontArguments&) const override; | ||
sk_sp<SkTypeface> onMakeFromFile(const char[], int) const override; | ||
|
||
sk_sp<SkTypeface> onLegacyMakeTypeface(const char[], SkFontStyle) const override; | ||
|
||
void setFallbackFontProvider(sk_sp<TypefaceFontProviderWithFallback>); | ||
|
||
private: | ||
sk_sp<SkFontMgr> wrappedFntMgr; | ||
sk_sp<TypefaceFontProviderWithFallback> fallbackFontProvider; | ||
}; | ||
|
||
|
||
#endif |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
19 changes: 19 additions & 0 deletions
19
skiko/src/commonMain/kotlin/org/jetbrains/skia/FontMgrWithFallback.kt
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,19 @@ | ||
package org.jetbrains.skia | ||
|
||
import org.jetbrains.skia.impl.NativePointer | ||
import org.jetbrains.skia.paragraph.TypefaceFontProviderWithFallback | ||
|
||
/** | ||
* Consider registering this FontMgr as a default one | ||
* to let all other possibly registered Font managers to look for their fallbacks first: | ||
* `FontCollection.setDefaultFontManager(...)` | ||
* | ||
* The fallbacks provided by this class will be used a last resort. | ||
*/ | ||
class FontMgrWithFallback( | ||
fallbackProvider: TypefaceFontProviderWithFallback | ||
) : FontMgr(_nDefaultWithFallbackFontProvider(fallbackProvider._ptr)) | ||
|
||
@ExternalSymbolName("org_jetbrains_skia_FontMgrWithFallback__1nDefaultWithFallbackFontProvider") | ||
@ModuleImport("./skiko.mjs", "org_jetbrains_skia_FontMgrWithFallback__1nDefaultWithFallbackFontProvider") | ||
private external fun _nDefaultWithFallbackFontProvider(fallbackPtr: NativePointer): NativePointer |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.