Unverified Commit 25ff8bee authored by Aleksandr Veselov's avatar Aleksandr Veselov Committed by GitHub

Implement Paragraph::getRectsForRange for native (#378)

parent 21a47543
package org.jetbrains.skia
import org.jetbrains.skia.impl.InteropPointer
import org.jetbrains.skia.impl.InteropScope
import org.jetbrains.skia.impl.withResult
class IRange(val start: Int, val end: Int) {
override fun equals(other: Any?): Boolean {
if (other === this) return true
......@@ -19,4 +23,11 @@ class IRange(val start: Int, val end: Int) {
override fun toString(): String {
return "IRange(_start=$start, _end=$end)"
}
companion object {}
}
internal fun IRange.Companion.fromInteropPointer(block: InteropScope.(InteropPointer) -> Unit): IRange {
val result = withResult(IntArray(2), block)
return IRange(result[0], result[1])
}
\ No newline at end of file
package org.jetbrains.skia.paragraph
import org.jetbrains.skia.impl.Library.Companion.staticLoad
import org.jetbrains.skia.*
import org.jetbrains.skia.ExternalSymbolName
import org.jetbrains.skia.impl.*
import org.jetbrains.skia.impl.Library.Companion.staticLoad
class Paragraph internal constructor(ptr: NativePointer, text: ManagedString?) : Managed(ptr, _FinalizerHolder.PTR) {
companion object {
......@@ -108,13 +107,15 @@ class Paragraph internal constructor(ptr: NativePointer, text: ManagedString?) :
): Array<TextBox> {
return try {
Stats.onNativeCall()
_nGetRectsForRange(_ptr, start, end, rectHeightMode.ordinal, rectWidthMode.ordinal)
interopScope {
_nGetRectsForRange(_ptr, start, end, rectHeightMode.ordinal, rectWidthMode.ordinal).fromInterop(TextBox)
}
} finally {
reachabilityBarrier(this)
}
}
val rectsForPlaceholders: Array<org.jetbrains.skia.paragraph.TextBox>
val rectsForPlaceholders: Array<TextBox>
get() = try {
Stats.onNativeCall()
interopScope {
......@@ -142,7 +143,7 @@ class Paragraph internal constructor(ptr: NativePointer, text: ManagedString?) :
fun getWordBoundary(offset: Int): IRange {
return try {
Stats.onNativeCall()
toIRange(_nGetWordBoundary(_ptr, offset))
IRange.fromInteropPointer { _nGetWordBoundary(_ptr, offset, it) }
} finally {
reachabilityBarrier(this)
}
......@@ -305,7 +306,7 @@ private external fun _nGetRectsForRange(
end: Int,
rectHeightMode: Int,
rectWidthMode: Int
): Array<TextBox>
): InteropPointer
@ExternalSymbolName("org_jetbrains_skia_paragraph_Paragraph__1nGetRectsForPlaceholders")
......@@ -315,7 +316,7 @@ private external fun _nGetRectsForPlaceholders(ptr: NativePointer): InteropPoint
private external fun _nGetGlyphPositionAtCoordinate(ptr: NativePointer, dx: Float, dy: Float): Int
@ExternalSymbolName("org_jetbrains_skia_paragraph_Paragraph__1nGetWordBoundary")
private external fun _nGetWordBoundary(ptr: NativePointer, offset: Int): Long
private external fun _nGetWordBoundary(ptr: NativePointer, offset: Int, result: InteropPointer)
@ExternalSymbolName("org_jetbrains_skia_paragraph_Paragraph__1nGetLineMetrics")
private external fun _nGetLineMetrics(ptr: NativePointer, textPtr: NativePointer): InteropPointer
......
package org.jetbrains.skia.paragraph
import org.jetbrains.skia.*
import org.jetbrains.skia.impl.*
import org.jetbrains.skia.ExternalSymbolName
import org.jetbrains.skia.Rect
import org.jetbrains.skia.impl.ArrayInteropDecoder
import org.jetbrains.skia.impl.InteropPointer
import org.jetbrains.skia.impl.interopScope
class TextBox(val rect: Rect, direction: Direction) {
val _direction: Direction
......@@ -42,7 +45,11 @@ class TextBox(val rect: Rect, direction: Direction) {
val rect = FloatArray(4)
val direction = IntArray(1)
interopScope {
TextBox_nGetArrayElement(array, index, toInterop(rect), toInterop(direction))
val rectPtr = toInterop(rect)
val directionPtr = toInterop(direction)
TextBox_nGetArrayElement(array, index, rectPtr, directionPtr)
rectPtr.fromInterop(rect)
directionPtr.fromInterop(direction)
}
return TextBox(rect[0], rect[1], rect[2], rect[3], direction[0])
}
......
package org.jetbrains.skia
import org.jetbrains.skia.impl.use
import org.jetbrains.skia.paragraph.FontCollection
import org.jetbrains.skia.paragraph.LineMetrics
import org.jetbrains.skia.paragraph.ParagraphBuilder
import org.jetbrains.skia.paragraph.ParagraphStyle
import org.jetbrains.skia.paragraph.*
import org.jetbrains.skia.tests.assertCloseEnough
import org.jetbrains.skia.tests.assertContentCloseEnough
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.assertTrue
@SkipJsTarget
@SkipNativeTarget
class ParagraphTest {
private val fontCollection = FontCollection().setDefaultFontManager(FontMgr.default)
private val fontCollection = suspend {
FontCollection().setDefaultFontManager(TypefaceFontProvider().apply {
registerTypeface(Typeface.makeFromResource("./fonts/Inter-Hinted-Regular.ttf"), "Inter")
})
}
private val style = ParagraphStyle().apply {
height = 40.0f
maxLinesCount = 2
textStyle = TextStyle().apply {
fontFamilies = arrayOf("Inter")
fontSize = 14.0f
}
}
@Test
fun findTypefaces() {
fontCollection.findTypefaces(emptyArray(), FontStyle.NORMAL)
@SkipJsTarget
@SkipNativeTarget
fun findTypefaces() = runTest {
fontCollection().findTypefaces(emptyArray(), FontStyle.NORMAL)
}
private fun singleLineMetrics(text: String): LineMetrics {
val style = ParagraphStyle()
return ParagraphBuilder(style, fontCollection).use {
private suspend fun singleLineMetrics(text: String): LineMetrics {
return ParagraphBuilder(style, fontCollection()).use {
it.addText(text)
it.build()
}.layout(Float.POSITIVE_INFINITY).lineMetrics.first()
}
@Test
fun layoutParagraph() {
@SkipJsTarget
@SkipNativeTarget
fun layoutParagraph() = runTest {
singleLineMetrics("aa").let { lineMetrics -> // latin
assertEquals(0, lineMetrics.startIndex)
assertEquals(2, lineMetrics.endIndex)
......@@ -44,4 +59,50 @@ class ParagraphTest {
assertEquals(2, lineMetrics.endExcludingWhitespaces)
}
}
@Test
fun canCreate() = runTest {
val text = "Hello,\n Пользователь1!"
var paragraph = ParagraphBuilder(style, fontCollection()).use {
it.addText(text)
it.build()
}.layout(100.0f)
assertCloseEnough(100.0f, paragraph.maxWidth,0.01f)
assertCloseEnough(102.63f, paragraph.minIntrinsicWidth,0.01f)
assertCloseEnough(110.47f, paragraph.maxIntrinsicWidth,0.01f)
assertCloseEnough(13.5625f, paragraph.alphabeticBaseline, 0.01f)
assertCloseEnough(16.943085f, paragraph.ideographicBaseline, 0.01f)
assertCloseEnough(92.3125f, paragraph.longestLine,0.01f)
assertCloseEnough(34.0f, paragraph.height)
assertTrue(paragraph.didExceedMaxLines())
assertEquals(IRange(0, 5), paragraph.getWordBoundary(0))
assertEquals(IRange(8, 21), paragraph.getWordBoundary(10))
assertEquals(0, paragraph.unresolvedGlyphsCount)
assertEquals(2, paragraph.lineNumber)
assertContentEquals(arrayOf(), paragraph.rectsForPlaceholders)
assertContentCloseEnough(arrayOf(
TextBox(Rect(3.94f, 17.06f, 92.31f, 34.0f), Direction.LTR)
), paragraph.getRectsForRange(8, 21, RectHeightMode.TIGHT, RectWidthMode.TIGHT),0.01f)
paragraph = paragraph
.updateFontSize(8, 21, 48.0f)
.updateForegroundPaint(8, 21, Paint().apply { color = Color.RED })
.updateBackgroundPaint(8, 21, Paint().apply { color = Color.BLACK })
.updateAlignment(Alignment.RIGHT)
.markDirty()
assertContentCloseEnough(arrayOf(
TextBox(Rect(3.94f, 17.06f, 92.31f, 34.0f), Direction.LTR)
), paragraph.getRectsForRange(8, 21, RectHeightMode.TIGHT, RectWidthMode.TIGHT),0.01f)
assertEquals(
PositionWithAffinity(5, Affinity.UPSTREAM),
paragraph.getGlyphPositionAtCoordinate(30f, 10f)
)
}
}
\ No newline at end of file
......@@ -4,6 +4,7 @@ import org.jetbrains.skia.Color4f
import org.jetbrains.skia.Matrix33
import org.jetbrains.skia.Point
import org.jetbrains.skia.Rect
import org.jetbrains.skia.paragraph.TextBox
import org.jetbrains.skiko.KotlinBackend
import org.jetbrains.skiko.kotlinBackend
import kotlin.math.abs
......@@ -25,6 +26,10 @@ private inline fun Rect.isCloseEnoughTo(rect: Rect, epsilon: Float): Boolean =
left.isCloseEnoughTo(rect.left, epsilon) && right.isCloseEnoughTo(rect.right, epsilon)
&& top.isCloseEnoughTo(rect.top, epsilon) && bottom.isCloseEnoughTo(rect.bottom, epsilon)
private inline fun TextBox.isCloseEnoughTo(textBox: TextBox, epsilon: Float = EPSILON): Boolean {
return (direction == textBox.direction) && rect.isCloseEnoughTo(textBox.rect, epsilon)
}
internal fun assertCloseEnough(expected: Float, actual: Float, epsilon: Float = EPSILON) {
assertTrue(expected.isCloseEnoughTo(actual, epsilon), message = "expected=$expected, actual=$actual, eps=$epsilon")
}
......@@ -33,6 +38,10 @@ internal fun assertCloseEnough(expected: Point, actual: Point, epsilon: Float =
assertTrue(expected.isCloseEnoughTo(actual, epsilon), message = "expected=$expected, actual=$actual, eps=$epsilon")
}
internal fun assertCloseEnough(expected: TextBox, actual: TextBox, epsilon: Float = EPSILON) {
assertTrue(expected.isCloseEnoughTo(actual, epsilon), message = "expected=$expected, actual=$actual, eps=$epsilon")
}
internal fun assertCloseEnough(expected: Matrix33, actual: Matrix33, epsilon: Float = EPSILON) {
assertTrue(
expected.mat.zip(actual.mat).all { (a, b) -> a.isCloseEnoughTo(b, epsilon) },
......@@ -80,3 +89,7 @@ internal fun assertContentCloseEnough(expected: FloatArray, actual: FloatArray,
internal fun assertContentCloseEnough(expected: Array<Point>, actual: Array<Point>, epsilon: Float = EPSILON) {
assertContentEquivalent(expected.iterator(), actual.iterator()) { a, b -> a.isCloseEnoughTo(b, epsilon) }
}
internal fun assertContentCloseEnough(expected: Array<TextBox>, actual: Array<TextBox>, epsilon: Float = EPSILON) {
assertContentEquivalent(expected.iterator(), actual.iterator()) { a, b -> a.isCloseEnoughTo(b, epsilon) }
}
......@@ -117,11 +117,15 @@ extern "C" JNIEXPORT jint JNICALL Java_org_jetbrains_skia_paragraph_ParagraphKt_
return -p.position-1;
}
extern "C" JNIEXPORT jlong JNICALL Java_org_jetbrains_skia_paragraph_ParagraphKt__1nGetWordBoundary
(JNIEnv* env, jclass jclass, jlong ptr, jint offset) {
extern "C" JNIEXPORT void JNICALL Java_org_jetbrains_skia_paragraph_ParagraphKt__1nGetWordBoundary
(JNIEnv* env, jclass jclass, jlong ptr, jint offset, jintArray resultArray) {
Paragraph* instance = reinterpret_cast<Paragraph*>(static_cast<uintptr_t>(ptr));
SkRange<size_t> range = instance->getWordBoundary(offset);
return packTwoInts(range.start & 0xFFFFFFFF, range.end & 0xFFFFFFFF);
jint result[] {
static_cast<jint>(range.start & 0xFFFFFFFF),
static_cast<jint>(range.end & 0xFFFFFFFF)
};
env->SetIntArrayRegion(resultArray, 0, 2, result);
}
extern "C" JNIEXPORT jobjectArray JNICALL Java_org_jetbrains_skia_paragraph_ParagraphKt__1nGetLineMetrics
......
......@@ -78,27 +78,12 @@ SKIKO_EXPORT void org_jetbrains_skia_paragraph_Paragraph__1nPaint
instance->paint(canvas, x, y);
}
SKIKO_EXPORT KInteropPointerArray org_jetbrains_skia_paragraph_Paragraph__1nGetRectsForRange
(KNativePointer ptr, KInt start, KInt end, KInt rectHeightStyle, KInt rectWidthStyle) {
TODO("implement org_jetbrains_skia_paragraph_Paragraph__1nGetRectsForRange");
}
#if 0
SKIKO_EXPORT KInteropPointerArray org_jetbrains_skia_paragraph_Paragraph__1nGetRectsForRange
(KNativePointer ptr, KInt start, KInt end, KInt rectHeightStyle, KInt rectWidthStyle) {
Paragraph* instance = reinterpret_cast<Paragraph*>((ptr));
std::vector<TextBox> rects = instance->getRectsForRange(start, end, static_cast<RectHeightStyle>(rectHeightStyle), static_cast<RectWidthStyle>(rectWidthStyle));
KInteropPointerArray rectsArray = env->NewObjectArray((jsize) rects.size(), skija::paragraph::TextBox::cls, nullptr);
for (int i = 0; i < rects.size(); ++i) {
TextBox box = rects[i];
KInteropPointer boxObj = env->NewObject(skija::paragraph::TextBox::cls, skija::paragraph::TextBox::ctor, box.rect.fLeft, box.rect.fTop, box.rect.fRight, box.rect.fBottom, static_cast<KInt>(box.direction));
env->SetObjectArrayElement(rectsArray, i, boxObj);
env->DeleteLocalRef(boxObj);
}
return rectsArray;
std::vector<TextBox> *rects = new std::vector<TextBox>(instance->getRectsForRange(start, end, static_cast<RectHeightStyle>(rectHeightStyle), static_cast<RectWidthStyle>(rectWidthStyle)));
return rects;
}
#endif
SKIKO_EXPORT KInteropPointerArray org_jetbrains_skia_paragraph_Paragraph__1nGetRectsForPlaceholders
(KNativePointer ptr) {
......@@ -117,11 +102,13 @@ SKIKO_EXPORT KInt org_jetbrains_skia_paragraph_Paragraph__1nGetGlyphPositionAtCo
return -p.position-1;
}
SKIKO_EXPORT KLong org_jetbrains_skia_paragraph_Paragraph__1nGetWordBoundary
(KNativePointer ptr, KInt offset) {
SKIKO_EXPORT void org_jetbrains_skia_paragraph_Paragraph__1nGetWordBoundary
(KNativePointer ptr, KInt offset, KInteropPointer resultPtr) {
Paragraph* instance = reinterpret_cast<Paragraph*>((ptr));
SkRange<size_t> range = instance->getWordBoundary(offset);
return packTwoInts(range.start & 0xFFFFFFFF, range.end & 0xFFFFFFFF);
KInt* result = reinterpret_cast<KInt*>(resultPtr);
result[0] = range.start & 0xFFFFFFFF;
result[1] = range.end & 0xFFFFFFFF;
}
......
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