Unverified Commit 37699f21 authored by Oleksandr Karpovich's avatar Oleksandr Karpovich Committed by GitHub

Shaping options and Shaper interop (#343)

* update Shaper jni bindings to use common ShapingOptions interop

* add Shaper shapeLine native binding impl

* add ShaperTest.canShapeLine for all targets

* add ShaperTest.canShapeTextBlob for all targets

* add PR review improvements

* remove unnecessary std::unique_ptr in Shaper.cc
Co-authored-by: 's avatarOleksandr Karpovich <oleksandr.karpovich@jetbrains.com>
parent c24886be
......@@ -98,14 +98,20 @@ class FontFeature(val _tag: Int, val value: Int, val start: UInt, val end: UInt)
return FontFeature(m.group(tagIx)!!, value, start, end)
}
fun parse(str: String): Array<FontFeature?> {
fun parse(str: String): Array<FontFeature> {
return _splitPattern.split(str).map { s -> parseOne(s) }.toTypedArray()
}
internal fun InteropScope.toInterop(fontFeatures: Array<FontFeature>?): InteropPointer {
val ints = fontFeatures?.flatMap {
it.toInteropIntArray().toList()
}?.toIntArray()
internal fun InteropScope.arrayOfFontFeaturesToInterop(fontFeatures: Array<FontFeature>?): InteropPointer {
val ints = IntArray(4 * (fontFeatures?.size ?: 0))
fontFeatures?.forEachIndexed { ix, fontFeature ->
val j = ix * 4
ints[j] = fontFeature._tag
ints[j + 1] = fontFeature.value
ints[j + 2] = fontFeature.start.toInt()
ints[j + 3] = fontFeature.end.toInt()
}
return toInterop(ints)
}
......
......@@ -191,11 +191,20 @@ class TextBlob internal constructor(ptr: NativePointer) : Managed(ptr, _Finalize
val glyphs: ShortArray
get() = try {
Stats.onNativeCall()
_nGetGlyphs(_ptr)
withResult(ShortArray(glyphsLength)) {
_nGetGlyphs(_ptr, it)
}
} finally {
reachabilityBarrier(this)
}
internal val glyphsLength: Int
get() = try {
Stats.onNativeCall()
_nGetGlyphsLength(_ptr)
} finally {
reachabilityBarrier(this)
}
/**
*
* Return result depends on how blob was constructed.
......@@ -325,8 +334,11 @@ private external fun _nMakeFromPos(glyphs: InteropPointer, pos: InteropPointer,
@ExternalSymbolName("org_jetbrains_skia_TextBlob__1nMakeFromRSXform")
private external fun _nMakeFromRSXform(glyphs: InteropPointer, xform: InteropPointer, fontPtr: NativePointer): NativePointer
@ExternalSymbolName("org_jetbrains_skia_TextBlob__1nGetGlyphsLength")
private external fun _nGetGlyphsLength(ptr: NativePointer): Int
@ExternalSymbolName("org_jetbrains_skia_TextBlob__1nGetGlyphs")
private external fun _nGetGlyphs(ptr: NativePointer): ShortArray
private external fun _nGetGlyphs(ptr: NativePointer, result: InteropPointer)
@ExternalSymbolName("org_jetbrains_skia_TextBlob__1nGetPositions")
private external fun _nGetPositions(ptr: NativePointer): FloatArray
......
......@@ -31,6 +31,7 @@ class TextLine internal constructor(ptr: NativePointer) : Managed(ptr, _Finalize
reachabilityBarrier(this)
}
}
val capHeight: Float
get() {
Stats.onNativeCall()
......@@ -40,6 +41,7 @@ class TextLine internal constructor(ptr: NativePointer) : Managed(ptr, _Finalize
reachabilityBarrier(this)
}
}
val xHeight: Float
get() {
Stats.onNativeCall()
......@@ -49,6 +51,7 @@ class TextLine internal constructor(ptr: NativePointer) : Managed(ptr, _Finalize
reachabilityBarrier(this)
}
}
val descent: Float
get() {
Stats.onNativeCall()
......@@ -58,6 +61,7 @@ class TextLine internal constructor(ptr: NativePointer) : Managed(ptr, _Finalize
reachabilityBarrier(this)
}
}
val leading: Float
get() {
Stats.onNativeCall()
......@@ -67,6 +71,7 @@ class TextLine internal constructor(ptr: NativePointer) : Managed(ptr, _Finalize
reachabilityBarrier(this)
}
}
val width: Float
get() {
Stats.onNativeCall()
......@@ -76,6 +81,7 @@ class TextLine internal constructor(ptr: NativePointer) : Managed(ptr, _Finalize
reachabilityBarrier(this)
}
}
val height: Float
get() {
Stats.onNativeCall()
......@@ -85,6 +91,7 @@ class TextLine internal constructor(ptr: NativePointer) : Managed(ptr, _Finalize
reachabilityBarrier(this)
}
}
val textBlob: TextBlob?
get() {
Stats.onNativeCall()
......@@ -95,11 +102,25 @@ class TextLine internal constructor(ptr: NativePointer) : Managed(ptr, _Finalize
reachabilityBarrier(this)
}
}
val glyphs: ShortArray
get() {
Stats.onNativeCall()
return try {
TextLine_nGetGlyphs(_ptr)
val length = glyphsLength
withResult(ShortArray(length)) {
TextLine_nGetGlyphs(_ptr, it, length)
}
} finally {
reachabilityBarrier(this)
}
}
internal val glyphsLength: Int
get() {
Stats.onNativeCall()
return try {
TextLine_nGetGlyphsLength(_ptr)
} finally {
reachabilityBarrier(this)
}
......@@ -204,8 +225,11 @@ private external fun TextLine_nGetWidth(ptr: NativePointer): Float
@ExternalSymbolName("org_jetbrains_skia_TextLine__1nGetHeight")
private external fun TextLine_nGetHeight(ptr: NativePointer): Float
@ExternalSymbolName("org_jetbrains_skia_TextLine__1nGetGlyphsLength")
private external fun TextLine_nGetGlyphsLength(ptr: NativePointer): Int
@ExternalSymbolName("org_jetbrains_skia_TextLine__1nGetGlyphs")
private external fun TextLine_nGetGlyphs(ptr: NativePointer): ShortArray
private external fun TextLine_nGetGlyphs(ptr: NativePointer, resultGlyphs: InteropPointer, resultLength: Int)
@ExternalSymbolName("org_jetbrains_skia_TextLine__1nGetPositions")
private external fun TextLine_nGetPositions(ptr: NativePointer): FloatArray
......
......@@ -3,13 +3,9 @@ package org.jetbrains.skia.shaper
import org.jetbrains.skia.impl.Library.Companion.staticLoad
import org.jetbrains.skia.*
import org.jetbrains.skia.impl.Managed
import org.jetbrains.skia.impl.Stats
import org.jetbrains.skia.impl.reachabilityBarrier
import org.jetbrains.skia.impl.use
import org.jetbrains.skia.ExternalSymbolName
import org.jetbrains.skia.impl.NativePointer
import org.jetbrains.skia.impl.getPtr
import org.jetbrains.skia.FontFeature.Companion.arrayOfFontFeaturesToInterop
import org.jetbrains.skia.impl.*
/**
* Shapes text using HarfBuzz and places the shaped text into a
......@@ -110,15 +106,19 @@ class Shaper internal constructor(ptr: NativePointer) : Managed(ptr, _FinalizerH
fun shape(text: String?, font: Font?, opts: ShapingOptions, width: Float, offset: Point): TextBlob? {
return try {
Stats.onNativeCall()
val ptr = _nShapeBlob(
val ptr = interopScope {
_nShapeBlob(
_ptr,
text,
ManagedString(text)._ptr,
getPtr(font),
opts,
width,
offset.x,
offset.y
optsFeaturesLen = opts.features?.size ?: 0,
optsFeaturesIntArray = arrayOfFontFeaturesToInterop(opts.features),
optsBooleanProps = opts._booleanPropsToInt(),
width = width,
offsetX = offset.x,
offsetY = offset.y
)
}
if (NullPointer == ptr) null else TextBlob(ptr)
} finally {
reachabilityBarrier(this)
......@@ -185,6 +185,7 @@ class Shaper internal constructor(ptr: NativePointer) : Managed(ptr, _FinalizerH
runHandler: RunHandler
): Shaper {
Stats.onNativeCall()
interopScope {
_nShape(
_ptr,
getPtr(textUtf8),
......@@ -192,24 +193,31 @@ class Shaper internal constructor(ptr: NativePointer) : Managed(ptr, _FinalizerH
bidiIter,
scriptIter,
langIter,
opts,
width,
runHandler
optsFeaturesLen = opts.features?.size ?: 0,
optsFeaturesIntArray = arrayOfFontFeaturesToInterop(opts.features),
optsBooleanProps = opts._booleanPropsToInt(),
width = width,
runHandler = runHandler
)
}
return this
}
fun shapeLine(text: String?, font: Font?, opts: ShapingOptions): TextLine {
return try {
Stats.onNativeCall()
interopScope {
TextLine(
_nShapeLine(
_ptr,
text,
ManagedString(text)._ptr,
getPtr(font),
opts
optsFeaturesLen = opts.features?.size ?: 0,
optsFeatures = arrayOfFontFeaturesToInterop(opts.features),
optsBooleanProps = opts._booleanPropsToInt()
)
)
}
} finally {
reachabilityBarrier(this)
reachabilityBarrier(font)
......@@ -250,9 +258,11 @@ private external fun _nMakeCoreText(): NativePointer
@ExternalSymbolName("org_jetbrains_skia_shaper_Shaper__1nShapeBlob")
private external fun _nShapeBlob(
ptr: NativePointer,
text: String?,
text: NativePointer,
fontPtr: NativePointer,
opts: ShapingOptions?,
optsFeaturesLen: Int,
optsFeaturesIntArray: InteropPointer,
optsBooleanProps: Int,
width: Float,
offsetX: Float,
offsetY: Float
......@@ -260,7 +270,14 @@ private external fun _nShapeBlob(
@ExternalSymbolName("org_jetbrains_skia_shaper_Shaper__1nShapeLine")
private external fun _nShapeLine(ptr: NativePointer, text: String?, fontPtr: NativePointer, opts: ShapingOptions?): NativePointer
private external fun _nShapeLine(
ptr: NativePointer,
text: NativePointer,
fontPtr: NativePointer,
optsFeaturesLen: Int,
optsFeatures: InteropPointer,
optsBooleanProps: Int
): NativePointer
@ExternalSymbolName("org_jetbrains_skia_shaper_Shaper__1nShape")
private external fun _nShape(
......@@ -270,7 +287,9 @@ private external fun _nShape(
bidiIter: Iterator<BidiRun?>?,
scriptIter: Iterator<ScriptRun?>?,
langIter: Iterator<LanguageRun?>?,
opts: ShapingOptions?,
optsFeaturesLen: Int,
optsFeaturesIntArray: InteropPointer,
optsBooleanProps: Int,
width: Float,
runHandler: RunHandler?
)
......@@ -5,7 +5,7 @@ import org.jetbrains.skia.FontMgr
class ShapingOptions(
internal val fontMgr: FontMgr?,
internal val features: Array<FontFeature?>?,
internal val features: Array<FontFeature>?,
internal val isLeftToRight: Boolean,
/**
* If enabled, fallback font runs will not be broken by whitespace from original font
......@@ -17,18 +17,26 @@ class ShapingOptions(
internal val isApproximatePunctuation: Boolean
) {
internal fun _booleanPropsToInt(): Int {
var i = 0
if (isLeftToRight) i = i or 0x04
if (isApproximateSpaces) i = i or 0x02
if (isApproximatePunctuation) i = i or 0x01
return i
}
/**
* If enabled, fallback font runs will not be broken by whitespace from original font
*/
/**
* If enabled, fallback font runs will not be broken by punctuation from original font
*/
fun withFeatures(features: Array<FontFeature?>?): ShapingOptions {
fun withFeatures(features: Array<FontFeature>?): ShapingOptions {
return ShapingOptions(fontMgr, features, isLeftToRight, isApproximateSpaces, isApproximatePunctuation)
}
fun withFeatures(featuresString: String?): ShapingOptions {
return if (featuresString == null) withFeatures(null as Array<FontFeature?>?) else withFeatures(
return if (featuresString == null) withFeatures(null as Array<FontFeature>?) else withFeatures(
FontFeature.Companion.parse(
featuresString
)
......
package org.jetbrains.skia
import org.jetbrains.skia.shaper.Shaper
import org.jetbrains.skia.tests.makeFromResource
import org.jetbrains.skiko.tests.SkipJsTarget
import org.jetbrains.skiko.tests.SkipNativeTarget
import org.jetbrains.skiko.tests.runTest
import kotlin.test.Test
import kotlin.test.assertContentEquals
import kotlin.test.assertEquals
import kotlin.test.assertNotEquals
class ShaperTest {
private suspend fun fontInter36() =
Font(Typeface.makeFromResource("./fonts/Inter-Hinted-Regular.ttf"), 36f)
@Test
fun canShapeLine() = runTest {
val textLine = Shaper.make().shapeLine(
text = "Abc123", font = fontInter36()
)
assertEquals(6, textLine.glyphsLength)
assertContentEquals(
expected = shortArrayOf(2, 574, 581, 1292, 1293, 1295),
actual = textLine.glyphs
)
}
@Test
fun canShapeTextBlob() = runTest {
val textBlob = Shaper.make().shape(
text = "text",
font = fontInter36(),
width = 100f
)
assertNotEquals(null, textBlob)
assertEquals(4, textBlob!!.glyphsLength)
assertContentEquals(
expected = shortArrayOf(882, 611, 943, 882),
actual = textBlob.glyphs
)
}
}
......@@ -162,19 +162,32 @@ public:
}
};
extern "C" JNIEXPORT jshortArray JNICALL Java_org_jetbrains_skia_TextBlobKt__1nGetGlyphs
extern "C" JNIEXPORT jint JNICALL Java_org_jetbrains_skia_TextBlobKt__1nGetGlyphsLength
(JNIEnv* env, jclass jclass, jlong ptr) {
SkTextBlob* instance = reinterpret_cast<SkTextBlob*>(static_cast<uintptr_t>(ptr));
SkTextBlob::Iter iter(*instance);
SkTextBlob::Iter::Run run;
jint stored = 0;
while (iter.next(&run)) {
stored += run.fGlyphCount;
}
return stored;
}
extern "C" JNIEXPORT void JNICALL Java_org_jetbrains_skia_TextBlobKt__1nGetGlyphs
(JNIEnv* env, jclass jclass, jlong ptr, jshortArray resultArray) {
SkTextBlob* instance = reinterpret_cast<SkTextBlob*>(static_cast<uintptr_t>(ptr));
SkTextBlob::Iter iter(*instance);
SkTextBlob::Iter::Run run;
std::vector<jshort> glyphs;
size_t stored = 0;
jshort * shorts = env->GetShortArrayElements(resultArray, nullptr);
while (iter.next(&run)) {
glyphs.resize(stored + run.fGlyphCount);
memcpy(&glyphs[stored], run.fGlyphIndices, run.fGlyphCount * sizeof(uint16_t));
memcpy(shorts + stored, run.fGlyphIndices, run.fGlyphCount * sizeof(uint16_t));
stored += run.fGlyphCount;
}
return javaShortArray(env, glyphs);
env->ReleaseShortArrayElements(resultArray, shorts, 0);
}
extern "C" JNIEXPORT jfloatArray JNICALL Java_org_jetbrains_skia_TextBlobKt__1nGetPositions
......
......@@ -65,17 +65,32 @@ extern "C" JNIEXPORT jlong JNICALL Java_org_jetbrains_skia_TextLineKt__1nGetText
return reinterpret_cast<jlong>(instance->fBlob.get());
}
extern "C" JNIEXPORT jshortArray JNICALL Java_org_jetbrains_skia_TextLineKt_TextLine_1nGetGlyphs
extern "C" JNIEXPORT jint Java_org_jetbrains_skia_TextLineKt_TextLine_1nGetGlyphsLength
(JNIEnv* env, jclass jclass, jlong ptr) {
TextLine* instance = reinterpret_cast<TextLine*>(static_cast<uintptr_t>(ptr));
std::vector<jshort> glyphs(instance->fGlyphCount);
return instance->fGlyphCount;
}
extern "C" JNIEXPORT void JNICALL Java_org_jetbrains_skia_TextLineKt_TextLine_1nGetGlyphs
(JNIEnv* env, jclass jclass, jlong ptr, jshortArray resultGlyphs, jint resultLength) {
TextLine* instance = reinterpret_cast<TextLine*>(static_cast<uintptr_t>(ptr));
jshort* glyphs = env->GetShortArrayElements(resultGlyphs, nullptr);
size_t idx = 0;
size_t freeBytesN = resultLength * sizeof(uint16_t);
for (auto& run: instance->fRuns) {
memcpy(glyphs.data() + idx, run.fGlyphs, run.fGlyphCount * sizeof(uint16_t));
size_t addBytesLen = run.fGlyphCount * sizeof(uint16_t);
if (freeBytesN - addBytesLen >= 0) {
memcpy(&glyphs[idx], run.fGlyphs, addBytesLen);
idx += run.fGlyphCount;
freeBytesN -= addBytesLen;
} else {
SkDEBUGFAIL("Incorrect resultGlyphs size");
}
}
env->ReleaseShortArrayElements(resultGlyphs, glyphs, 0);
SkASSERTF(idx == instance->fGlyphCount, "TextLine.cc: idx = %d != instance->fGlyphCount = %d", idx, instance->fGlyphCount);
return javaShortArray(env, glyphs);
}
extern "C" JNIEXPORT jfloatArray JNICALL Java_org_jetbrains_skia_TextLineKt_TextLine_1nGetPositions
......
......@@ -56,26 +56,20 @@ extern "C" JNIEXPORT jlong JNICALL Java_org_jetbrains_skia_shaper_ShaperKt_Shape
}
extern "C" JNIEXPORT jlong JNICALL Java_org_jetbrains_skia_shaper_ShaperKt__1nShapeBlob
(JNIEnv* env, jclass jclass, jlong ptr, jstring textObj, jlong fontPtr, jobject opts, jfloat width, jfloat offsetX, jfloat offsetY) {
(JNIEnv* env, jclass jclass, jlong ptr, jlong textPtr, jlong fontPtr, jint optsFeaturesLen, jintArray optsFeatures, jint optsBooleanProps, jfloat width, jfloat offsetX, jfloat offsetY) {
SkShaper* instance = reinterpret_cast<SkShaper*>(static_cast<uintptr_t>(ptr));
SkString text = skString(env, textObj);
SkString& text = *(reinterpret_cast<SkString*>(static_cast<uintptr_t>(textPtr)));
std::shared_ptr<UBreakIterator> graphemeIter = skija::shaper::graphemeBreakIterator(text);
if (!graphemeIter) return 0;
SkFont* font = reinterpret_cast<SkFont*>(static_cast<uintptr_t>(fontPtr));
std::vector<SkShaper::Feature> features = skija::shaper::ShapingOptions::getFeatures(env, opts);
std::unique_ptr<SkShaper::FontRunIterator> fontRunIter(new FontRunIterator(
text.c_str(),
text.size(),
*font,
SkFontMgr::RefDefault(),
graphemeIter,
env->GetBooleanField(opts, skija::shaper::ShapingOptions::_approximateSpaces),
env->GetBooleanField(opts, skija::shaper::ShapingOptions::_approximatePunctuation)
));
if (!fontRunIter) return 0;
std::vector<SkShaper::Feature> features = skija::shaper::ShapingOptions::getFeaturesFromIntsArray(env, optsFeatures, optsFeaturesLen);
bool aproximatePunctuation = (optsBooleanProps & 0x01) != 0;
bool aproximateSpaces = (optsBooleanProps & 0x02) != 0;
bool isLeftToRight = (optsBooleanProps & 0x04) != 0;
uint8_t defaultBiDiLevel = env->GetBooleanField(opts, skija::shaper::ShapingOptions::_leftToRight) ? UBIDI_DEFAULT_LTR : UBIDI_DEFAULT_RTL;
uint8_t defaultBiDiLevel = isLeftToRight ? UBIDI_DEFAULT_LTR : UBIDI_DEFAULT_RTL;
std::unique_ptr<SkShaper::BiDiRunIterator> bidiRunIter(SkShaper::MakeBiDiRunIterator(text.c_str(), text.size(), defaultBiDiLevel));
if (!bidiRunIter) return 0;
......@@ -85,35 +79,44 @@ extern "C" JNIEXPORT jlong JNICALL Java_org_jetbrains_skia_shaper_ShaperKt__1nSh
std::unique_ptr<SkShaper::LanguageRunIterator> languageRunIter(SkShaper::MakeStdLanguageRunIterator(text.c_str(), text.size()));
if (!languageRunIter) return 0;
FontRunIterator fontRunIter(
text.c_str(),
text.size(),
*font,
SkFontMgr::RefDefault(),
graphemeIter,
aproximateSpaces,
aproximatePunctuation
);
SkTextBlobBuilderRunHandler rh(text.c_str(), {offsetX, offsetY});
instance->shape(text.c_str(), text.size(), *fontRunIter, *bidiRunIter, *scriptRunIter, *languageRunIter, features.data(), features.size(), width, &rh);
instance->shape(text.c_str(), text.size(), fontRunIter, *bidiRunIter, *scriptRunIter, *languageRunIter, features.data(), features.size(), width, &rh);
SkTextBlob* blob = rh.makeBlob().release();
return reinterpret_cast<jlong>(blob);
}
extern "C" JNIEXPORT jlong JNICALL Java_org_jetbrains_skia_shaper_ShaperKt__1nShapeLine
(JNIEnv* env, jclass jclass, jlong ptr, jstring textObj, jlong fontPtr, jobject opts) {
(JNIEnv* env, jclass jclass, jlong ptr, jlong textPtr, jlong fontPtr, jint optsFeaturesLen, jintArray optsFeatures, jint optsBooleanProps) {
SkShaper* instance = reinterpret_cast<SkShaper*>(static_cast<uintptr_t>(ptr));
SkString text = skString(env, textObj);
SkString& text = *(reinterpret_cast<SkString*>(static_cast<uintptr_t>(textPtr)));
SkFont* font = reinterpret_cast<SkFont*>(static_cast<uintptr_t>(fontPtr));
if (text.size() == 0) {
return reinterpret_cast<jlong>(new TextLine(*font));
}
std::shared_ptr<UBreakIterator> graphemeIter = skija::shaper::graphemeBreakIterator(text);
if (!graphemeIter) return 0;
SkFont* font = reinterpret_cast<SkFont*>(static_cast<uintptr_t>(fontPtr));
std::vector<SkShaper::Feature> features = skija::shaper::ShapingOptions::getFeatures(env, opts);
std::vector<SkShaper::Feature> features = skija::shaper::ShapingOptions::getFeaturesFromIntsArray(env, optsFeatures, optsFeaturesLen);
std::unique_ptr<SkShaper::FontRunIterator> fontRunIter(new FontRunIterator(
text.c_str(),
text.size(),
*font,
SkFontMgr::RefDefault(),
graphemeIter,
env->GetBooleanField(opts, skija::shaper::ShapingOptions::_approximateSpaces),
env->GetBooleanField(opts, skija::shaper::ShapingOptions::_approximatePunctuation)));
if (!fontRunIter) return 0;
bool aproximatePunctuation = (optsBooleanProps & 0x01) != 0;
bool aproximateSpaces = (optsBooleanProps & 0x02) != 0;
bool isLeftToRight = (optsBooleanProps & 0x04) != 0;
uint8_t defaultBiDiLevel = env->GetBooleanField(opts, skija::shaper::ShapingOptions::_leftToRight) ? UBIDI_DEFAULT_LTR : UBIDI_DEFAULT_RTL;
uint8_t defaultBiDiLevel = isLeftToRight ? UBIDI_DEFAULT_LTR : UBIDI_DEFAULT_RTL;
std::unique_ptr<SkShaper::BiDiRunIterator> bidiRunIter(SkShaper::MakeBiDiRunIterator(text.c_str(), text.size(), defaultBiDiLevel));
if (!bidiRunIter) return 0;
......@@ -123,15 +126,19 @@ extern "C" JNIEXPORT jlong JNICALL Java_org_jetbrains_skia_shaper_ShaperKt__1nSh
std::unique_ptr<SkShaper::LanguageRunIterator> languageRunIter(SkShaper::MakeStdLanguageRunIterator(text.c_str(), text.size()));
if (!languageRunIter) return 0;
TextLine* line;
if (text.size() == 0)
line = new TextLine(*font);
else {
FontRunIterator fontRunIter(
text.c_str(),
text.size(),
*font,
SkFontMgr::RefDefault(),
graphemeIter,
aproximateSpaces,
aproximatePunctuation);
TextLineRunHandler rh(text, graphemeIter);
instance->shape(text.c_str(), text.size(), *fontRunIter, *bidiRunIter, *scriptRunIter, *languageRunIter, features.data(), features.size(), std::numeric_limits<float>::infinity(), &rh);
line = rh.makeLine().release();
}
return reinterpret_cast<jlong>(line);
instance->shape(text.c_str(), text.size(), fontRunIter, *bidiRunIter, *scriptRunIter, *languageRunIter, features.data(), features.size(), std::numeric_limits<float>::infinity(), &rh);
return reinterpret_cast<jlong>(rh.makeLine().release());
}
template <typename RunIteratorSubclass>
......@@ -329,7 +336,7 @@ private:
};
extern "C" JNIEXPORT void JNICALL Java_org_jetbrains_skia_shaper_ShaperKt__1nShape
(JNIEnv* env, jclass jclass, jlong ptr, jlong textPtr, jobject fontRunIterObj, jobject bidiRunIterObj, jobject scriptRunIterObj, jobject languageRunIterObj, jobject opts, jfloat width, jobject runHandlerObj)
(JNIEnv* env, jclass jclass, jlong ptr, jlong textPtr, jobject fontRunIterObj, jobject bidiRunIterObj, jobject scriptRunIterObj, jobject languageRunIterObj, jint optsFeaturesLen, jintArray optsFeatures, jint optsBooleanProps, jfloat width, jobject runHandlerObj)
{
SkShaper* instance = reinterpret_cast<SkShaper*>(static_cast<uintptr_t>(ptr));
SkString* text = reinterpret_cast<SkString*>(static_cast<uintptr_t>(textPtr));
......@@ -351,7 +358,7 @@ extern "C" JNIEXPORT void JNICALL Java_org_jetbrains_skia_shaper_ShaperKt__1nSha
auto languageRunIter = SkijaLanguageRunIterator(env, languageRunIterObj, *text);
std::vector<SkShaper::Feature> features = skija::shaper::ShapingOptions::getFeatures(env, opts);
std::vector<SkShaper::Feature> features = skija::shaper::ShapingOptions::getFeaturesFromIntsArray(env, optsFeatures, optsFeaturesLen);
auto nativeRunHandler = (SkShaper::RunHandler*) skija::impl::Native::fromJava(env, runHandlerObj, skija::shaper::TextBlobBuilderRunHandler::cls);
std::unique_ptr<SkijaRunHandler> localRunHandler;
......
......@@ -172,6 +172,10 @@ namespace skija {
std::vector<SkShaper::Feature> getFeatures(JNIEnv* env, jobject opts) {
return skija::FontFeature::fromJavaArray(env, (jobjectArray) env->GetObjectField(opts, _features));
}
std::vector<SkShaper::Feature> getFeaturesFromIntsArray(JNIEnv* env, jintArray featuresArray, jint featuresLen) {
return skija::FontFeature::fromIntArray(env, featuresArray, featuresLen);
}
}
namespace TextBlobBuilderRunHandler {
......
......@@ -89,6 +89,7 @@ namespace skija {
void onLoad(JNIEnv* env);
std::vector<SkShaper::Feature> getFeatures(JNIEnv* env, jobject opts);
std::vector<SkShaper::Feature> getFeaturesFromIntsArray(JNIEnv* env, jintArray featuresArray, jint featuresLen);
}
namespace TextBlobBuilderRunHandler {
......
......@@ -63,10 +63,29 @@ SKIKO_EXPORT KNativePointer org_jetbrains_skia_TextBlob__1nMakeFromData
return reinterpret_cast<KNativePointer>(instance);
}
SKIKO_EXPORT KShort* org_jetbrains_skia_TextBlob__1nGetGlyphs
SKIKO_EXPORT KInt org_jetbrains_skia_TextBlob__1nGetGlyphsLength
(KNativePointer ptr) {
TODO("implement org_jetbrains_skia_TextBlob__1nGetGlyphs");
SkTextBlob* instance = reinterpret_cast<SkTextBlob*>(ptr);
SkTextBlob::Iter iter(*instance);
SkTextBlob::Iter::Run run;
int stored = 0;
while (iter.next(&run)) {
stored += run.fGlyphCount;
}
return stored;
}
SKIKO_EXPORT void org_jetbrains_skia_TextBlob__1nGetGlyphs
(KNativePointer ptr, KShort* resultArray) {
SkTextBlob* instance = reinterpret_cast<SkTextBlob*>(ptr);
SkTextBlob::Iter iter(*instance);
SkTextBlob::Iter::Run run;
size_t stored = 0;
while (iter.next(&run)) {
memcpy(resultArray + stored, run.fGlyphIndices, run.fGlyphCount * sizeof(uint16_t));
stored += run.fGlyphCount;
}
}
SKIKO_EXPORT KFloat* org_jetbrains_skia_TextBlob__1nGetPositions
......
......@@ -5,20 +5,16 @@
#include <iostream>
#include "SkShaper.h"
#include "common.h"
#include "TextLine.hh"
SKIKO_EXPORT KNativePointer org_jetbrains_skia_TextLine__1nGetFinalizer
() {
TODO("implement org_jetbrains_skia_TextLine__1nGetFinalizer");
static void unrefTextLine(TextLine* ptr) {
ptr->unref();
}
#if 0
SKIKO_EXPORT KNativePointer org_jetbrains_skia_TextLine__1nGetFinalizer
() {
return reinterpret_cast<KNativePointer>((&unrefTextLine));
return reinterpret_cast<KNativePointer>(&unrefTextLine);
}
#endif
SKIKO_EXPORT KFloat org_jetbrains_skia_TextLine__1nGetAscent
......@@ -143,27 +139,32 @@ SKIKO_EXPORT KNativePointer org_jetbrains_skia_TextLine__1nGetTextBlob
#endif
SKIKO_EXPORT KShort* org_jetbrains_skia_TextLine__1nGetGlyphs
SKIKO_EXPORT KInt org_jetbrains_skia_TextLine__1nGetGlyphsLength
(KNativePointer ptr) {
TODO("implement org_jetbrains_skia_TextLine__1nGetGlyphs");
TextLine* instance = reinterpret_cast<TextLine*>((ptr));
return instance->fGlyphCount;
}
#if 0
SKIKO_EXPORT KShort* org_jetbrains_skia_TextLine__1nGetGlyphs
(KNativePointer ptr) {
SKIKO_EXPORT void org_jetbrains_skia_TextLine__1nGetGlyphs
(KNativePointer ptr, KInteropPointer glyphs, KInt resultLength) {
TextLine* instance = reinterpret_cast<TextLine*>((ptr));
std::vector<KShort> glyphs(instance->fGlyphCount);
size_t idx = 0;
size_t freeBytesN = resultLength * sizeof(uint16_t);
KShort* glyphsPtr = reinterpret_cast<KShort*>(glyphs);
for (auto& run: instance->fRuns) {
memcpy(glyphs.data() + idx, run.fGlyphs, run.fGlyphCount * sizeof(uint16_t));
size_t addBytesLen = run.fGlyphCount * sizeof(uint16_t);
if (freeBytesN - addBytesLen >= 0) {
memcpy(&glyphsPtr[idx], run.fGlyphs, addBytesLen);
idx += run.fGlyphCount;
freeBytesN -= addBytesLen;
} else {
SkDEBUGFAIL("Incorrect resultGlyphs size");
}
}
SkASSERTF(idx == instance->fGlyphCount, "TextLine.cc: idx = %d != instance->fGlyphCount = %d", idx, instance->fGlyphCount);
return javaShortArray(env, glyphs);
}
#endif
SKIKO_EXPORT KFloat* org_jetbrains_skia_TextLine__1nGetPositions
......
......@@ -21,6 +21,10 @@
#include "SkSurfaceProps.h"
#include "SkSVGTypes.h"
#include "TextStyle.h"
#include "SkFont.h"
#include "SkShaper.h"
#include "SkLoadICU.h"
#include "unicode/ubrk.h"
#include "types.h"
......@@ -95,6 +99,15 @@ namespace skija {
}
}
namespace shaper {
namespace ShapingOptions {
std::vector<SkShaper::Feature> getFeaturesFromIntsArray(KInt* featuresArray, KInt featuresLen);
}
using ICUUText = std::unique_ptr<UText, SkFunctionWrapper<decltype(utext_close), utext_close>>;
std::shared_ptr<UBreakIterator> graphemeBreakIterator(SkString& text);
}
namespace AnimationFrameInfo {
void copyToInterop(const SkCodec::FrameInfo& info, KInteropPointer dst);
void copyToInterop(const std::vector<SkCodec::FrameInfo>& infos, KInteropPointer dst);
......
......@@ -80,6 +80,40 @@ namespace skija {
}
}
}
namespace shaper {
namespace ShapingOptions {
std::vector<SkShaper::Feature> getFeaturesFromIntsArray(KInt* featuresArray, KInt featuresLen) {
return skija::FontFeature::fromIntArray(featuresArray, featuresLen);
}
}
std::shared_ptr<UBreakIterator> graphemeBreakIterator(SkString& text) {
UErrorCode status = U_ZERO_ERROR;
ICUUText utext(utext_openUTF8(nullptr, text.c_str(), text.size(), &status));
if (U_FAILURE(status)) {
SkDEBUGF("utext_openUTF8 error: %s", u_errorName(status));
return nullptr;
}
std::shared_ptr<UBreakIterator> graphemeIter(
ubrk_open(static_cast<UBreakIteratorType>(UBreakIteratorType::UBRK_CHARACTER), uloc_getDefault(), nullptr, 0, &status),
[](UBreakIterator* p) { ubrk_close(p); }
);
if (U_FAILURE(status)) {
SkDEBUGF("ubrk_open error: %s", u_errorName(status));
return nullptr;
}
ubrk_setUText(graphemeIter.get(), utext.get(), &status);
if (U_FAILURE(status)) {
SkDEBUGF("ubrk_setUText error: %s", u_errorName(status));
return nullptr;
}
return graphemeIter;
}
}
}
skija::UtfIndicesConverter::UtfIndicesConverter(const char* chars8, size_t len8):
......
......@@ -6,6 +6,9 @@
#include "src/utils/SkUTF.h"
#include "unicode/ubidi.h"
#include "common.h"
#include "FontRunIterator.hh"
#include "src/utils/SkUTF.h"
#include "TextLineRunHandler.hh"
static void deleteShaper(SkShaper* instance) {
// std::cout << "Deleting [SkShaper " << instance << "]" << std::endl;
......@@ -55,13 +58,91 @@ SKIKO_EXPORT KNativePointer org_jetbrains_skia_shaper_Shaper__1nMake
SKIKO_EXPORT KNativePointer org_jetbrains_skia_shaper_Shaper__1nShapeBlob
(KNativePointer ptr, KInteropPointer textObj, KNativePointer fontPtr, KInteropPointer opts, KFloat width, KFloat offsetX, KFloat offsetY) {
TODO("implement org_jetbrains_skia_shaper_Shaper__1nShapeBlob");
(KNativePointer ptr, KNativePointer textPtr, KNativePointer fontPtr, KInt optsFeaturesLen, KInt* optsFeatures, KInt optsBooleanProps, KFloat width, KFloat offsetX, KFloat offsetY) {
SkShaper* instance = reinterpret_cast<SkShaper*>(ptr);
SkString& text = *(reinterpret_cast<SkString*>(textPtr));
std::shared_ptr<UBreakIterator> graphemeIter = skija::shaper::graphemeBreakIterator(text);
if (!graphemeIter) return 0;
SkFont* font = reinterpret_cast<SkFont*>(fontPtr);
std::vector<SkShaper::Feature> features = skija::shaper::ShapingOptions::getFeaturesFromIntsArray(optsFeatures, optsFeaturesLen);
bool aproximatePunctuation = (optsBooleanProps & 0x01) != 0;
bool aproximateSpaces = (optsBooleanProps & 0x02) != 0;
bool isLeftToRight = (optsBooleanProps & 0x04) != 0;
uint8_t defaultBiDiLevel = isLeftToRight ? UBIDI_DEFAULT_LTR : UBIDI_DEFAULT_RTL;
std::unique_ptr<SkShaper::BiDiRunIterator> bidiRunIter(SkShaper::MakeBiDiRunIterator(text.c_str(), text.size(), defaultBiDiLevel));
if (!bidiRunIter) return 0;
std::unique_ptr<SkShaper::ScriptRunIterator> scriptRunIter(SkShaper::MakeHbIcuScriptRunIterator(text.c_str(), text.size()));
if (!scriptRunIter) return 0;
std::unique_ptr<SkShaper::LanguageRunIterator> languageRunIter(SkShaper::MakeStdLanguageRunIterator(text.c_str(), text.size()));
if (!languageRunIter) return 0;
FontRunIterator fontRunIter(
text.c_str(),
text.size(),
*font,
SkFontMgr::RefDefault(),
graphemeIter,
aproximateSpaces,
aproximatePunctuation
);
SkTextBlobBuilderRunHandler rh(text.c_str(), {offsetX, offsetY});
instance->shape(text.c_str(), text.size(), fontRunIter, *bidiRunIter, *scriptRunIter, *languageRunIter, features.data(), features.size(), width, &rh);
SkTextBlob* blob = rh.makeBlob().release();
return reinterpret_cast<KNativePointer>(blob);
}
SKIKO_EXPORT KNativePointer org_jetbrains_skia_shaper_Shaper__1nShapeLine
(KNativePointer ptr, KInteropPointer textObj, KNativePointer fontPtr, KInteropPointer opts) {
TODO("implement org_jetbrains_skia_shaper_Shaper__1nShapeLine");
(KNativePointer ptr, KNativePointer textManagedStringPtr, KNativePointer fontPtr, KInt optsFeaturesLen, KInt* optsFeatures, KInt optsBooleanProps) {
SkShaper* instance = reinterpret_cast<SkShaper*>(ptr);
SkString& text = *(reinterpret_cast<SkString*>(textManagedStringPtr));
SkFont* font = reinterpret_cast<SkFont*>(fontPtr);
if (text.size() == 0) {
return reinterpret_cast<KNativePointer>(new TextLine(*font));
}
std::shared_ptr<UBreakIterator> graphemeIter = skija::shaper::graphemeBreakIterator(text);
if (!graphemeIter) return 0;
std::vector<SkShaper::Feature> features = skija::shaper::ShapingOptions::getFeaturesFromIntsArray(optsFeatures, optsFeaturesLen);
bool aproximatePunctuation = (optsBooleanProps & 0x01) != 0;
bool aproximateSpaces = (optsBooleanProps & 0x02) != 0;
bool isLeftToRight = (optsBooleanProps & 0x04) != 0;
uint8_t defaultBiDiLevel = isLeftToRight ? UBIDI_DEFAULT_LTR : UBIDI_DEFAULT_RTL;
std::unique_ptr<SkShaper::BiDiRunIterator> bidiRunIter(SkShaper::MakeBiDiRunIterator(text.c_str(), text.size(), defaultBiDiLevel));
if (!bidiRunIter) return 0;
std::unique_ptr<SkShaper::ScriptRunIterator> scriptRunIter(SkShaper::MakeHbIcuScriptRunIterator(text.c_str(), text.size()));
if (!scriptRunIter) return 0;
std::unique_ptr<SkShaper::LanguageRunIterator> languageRunIter(SkShaper::MakeStdLanguageRunIterator(text.c_str(), text.size()));
if (!languageRunIter) return 0;
FontRunIterator fontRunIter(
text.c_str(),
text.size(),
*font,
SkFontMgr::RefDefault(),
graphemeIter,
aproximateSpaces,
aproximatePunctuation);
TextLineRunHandler rh(text, graphemeIter);
instance->shape(text.c_str(), text.size(), fontRunIter, *bidiRunIter, *scriptRunIter, *languageRunIter, features.data(), features.size(), std::numeric_limits<float>::infinity(), &rh);
return reinterpret_cast<KNativePointer>(rh.makeLine().release());
}
SKIKO_EXPORT void org_jetbrains_skia_shaper_Shaper__1nShape
(KNativePointer ptr, KNativePointer textPtr, KInteropPointer fontRunIterObj, KInteropPointer bidiRunIterObj, KInteropPointer scriptRunIterObj, KInteropPointer languageRunIterObj, KInteropPointer opts, KFloat width, KInteropPointer runHandlerObj)
{
......
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