Unverified Commit bc86bdc2 authored by Oleksandr Karpovich's avatar Oleksandr Karpovich Committed by GitHub

TextBlob common (#358)

* add TextBlobTest.kt: canMakeFromPos

* add TextBlobTest.kt: getIntercepts

* add TextBlobTest.kt: tightBounds

* add TextBlobTest.kt: blockBounds

* TextBlob: make _nGetBlockBounds code reusable by moving it to commonMain/cpp

* TextBlob: make _nGetTightBounds code reusable by moving it to commonMain/cpp

* TextBlob: make _nGetPositions code reusable by moving it to commonMain/cpp

* TextBlob: make _nGetGlyphs code reusable by moving it to commonMain/cpp

* TextBlob: firstBaseline and lastBaseline

* TextBlob: _nMakeFromPosH

* TextBlob: _nMakeFromRSXform

* TextBlob: _nGetClusters

* fix jni compilation for windows: add reinterpret_cast

* update github action to upload test reports for failing tests too

* update TextBlobTest.kt

* add comment

* PR review improvements

* update epsilon

* update epsilon

* update epsilon
Co-authored-by: 's avatarOleksandr Karpovich <oleksandr.karpovich@jetbrains.com>
parent 67667ba7
// Must match SkTextBlobPriv.h
//
// Extended Textblob runs have more data after the Pos[] array:
//
// -------------------------------------------------------------------------
// ... | RunRecord | Glyphs[] | Pos[] | TextSize | Clusters[] | Text[] | ...
// -------------------------------------------------------------------------
class RunRecordClone {
public:
SkFont fFont;
uint32_t fCount;
SkPoint fOffset;
uint32_t fFlags;
uint16_t* glyphBuffer() const {
// Glyphs are stored immediately following the record.
return reinterpret_cast<uint16_t*>(const_cast<RunRecordClone*>(this) + 1);
}
SkScalar* posBuffer() const {
// Position scalars follow the (aligned) glyph buffer.
return reinterpret_cast<SkScalar*>(reinterpret_cast<uint8_t*>(this->glyphBuffer()) +
SkAlign4(fCount * sizeof(uint16_t)));
}
uint32_t* textSizePtr() const {
// textSize follows the position buffer.
return (uint32_t*)(&this->posBuffer()[fCount * ScalarsPerGlyph(positioning())]);
}
uint32_t textSize() const {
return isExtended() ? *this->textSizePtr() : 0;
}
uint32_t* clusterBuffer() const {
// clusters follow the textSize.
return isExtended() ? 1 + this->textSizePtr() : nullptr;
}
char* textBuffer() const {
return isExtended()
? reinterpret_cast<char*>(this->clusterBuffer() + fCount)
: nullptr;
}
uint8_t positioning() const {
return fFlags & 0x3; // kPositioning_Mask
}
bool isExtended() const {
return fFlags & 0x8; // kExtended_Flag
}
static unsigned ScalarsPerGlyph(uint8_t pos) {
uint8_t res = 0;
switch(pos) {
case 0: // kDefault_Positioning
case 1: // kHorizontal_Positioning
case 2: // kFull_Positioning
res = pos;
break;
case 3: // kRSXform_Positioning
res = 4;
break;
}
return res;
}
};
#pragma once
#include "SkRect.h"
#include "SkTextBlob.h"
#include "SkFont.h"
#include "SkFontMetrics.h"
namespace skikoMpp {
namespace skrect {
void serializeAs4Floats(const SkRect& rect, float* result);
}
namespace textblob {
std::unique_ptr<SkRect> getBlockBounds(SkTextBlob* instance);
std::unique_ptr<SkRect> getTightBounds(SkTextBlob* instance);
int getPositionsLength(SkTextBlob* instance);
void getPositions(SkTextBlob* instance, float* resultArray);
void getGlyphs(SkTextBlob* instance, short* resultArray);
int getGlyphsLength(SkTextBlob* instance);
bool getFirstBaseline(SkTextBlob* instance, float* resultArray);
bool getLastBaseline(SkTextBlob* instance, float* resultArray);
int getClustersLength(SkTextBlob* instance);
bool getClusters(SkTextBlob* instance, int* clusters);
}
}
namespace skija {
class UtfIndicesConverter {
public:
UtfIndicesConverter(const char* chars8, size_t len8);
UtfIndicesConverter(const SkString& s);
const char* fStart8;
const char* fPtr8;
const char* fEnd8;
uint32_t fPos16;
size_t from16To8(uint32_t i16);
uint32_t from8To16(size_t i8);
};
}
#include "mppinterop.h"
#include "RunRecordClone.hh"
#include "src/utils/SkUTF.h"
namespace skikoMpp {
namespace skrect {
void serializeAs4Floats(const SkRect& rect, float* result) {
result[0] = rect.fLeft;
result[1] = rect.fTop;
result[2] = rect.fRight;
result[3] = rect.fBottom;
}
}
namespace textblob {
std::unique_ptr<SkRect> getBlockBounds(SkTextBlob* instance) {
SkTextBlob::Iter iter(*instance);
SkTextBlob::Iter::Run run;
auto bounds = new SkRect();
SkFontMetrics metrics;
while (iter.next(&run)) {
// run.fGlyphIndices points directly to runRecord.glyphBuffer(), which comes directly after RunRecord itself
auto runRecord = reinterpret_cast<const RunRecordClone*>(run.fGlyphIndices) - 1;
if (runRecord->positioning() != 2) // kFull_Positioning
return std::unique_ptr<SkRect>(nullptr);
SkScalar* posBuffer = runRecord->posBuffer();
const SkFont& font = runRecord->fFont;
font.getMetrics(&metrics);
SkScalar lastLeft = posBuffer[(run.fGlyphCount - 1) * 2];
SkScalar lastWidth;
if (run.fGlyphCount > 1 && SkScalarNearlyEqual(posBuffer[(run.fGlyphCount - 2) * 2], lastLeft))
lastWidth = 0;
else
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);
}
return std::unique_ptr<SkRect>(bounds);
}
std::unique_ptr<SkRect> getTightBounds(SkTextBlob* instance) {
SkTextBlob::Iter iter(*instance);
SkTextBlob::Iter::Run run;
auto bounds = new SkRect();
SkRect tmpBounds;
while (iter.next(&run)) {
// run.fGlyphIndices points directly to runRecord.glyphBuffer(), which comes directly after RunRecord itself
auto runRecord = reinterpret_cast<const RunRecordClone*>(run.fGlyphIndices) - 1;
if (runRecord->positioning() != 2) // kFull_Positioning
return std::unique_ptr<SkRect>(nullptr);
runRecord->fFont.measureText(runRecord->glyphBuffer(), run.fGlyphCount * sizeof(uint16_t), SkTextEncoding::kGlyphID, &tmpBounds, nullptr);
SkScalar* posBuffer = runRecord->posBuffer();
tmpBounds.offset(posBuffer[0], posBuffer[1]);
bounds->join(tmpBounds);
}
return std::unique_ptr<SkRect>(bounds);
}
int getPositionsLength(SkTextBlob* instance) {
SkTextBlob::Iter iter(*instance);
SkTextBlob::Iter::Run run;
int count = 0;
while (iter.next(&run)) {
// run.fGlyphIndices points directly to runRecord.glyphBuffer(), which comes directly after RunRecord itself
auto runRecord = reinterpret_cast<const RunRecordClone*>(run.fGlyphIndices) - 1;
unsigned scalarsPerGlyph = RunRecordClone::ScalarsPerGlyph(runRecord->positioning());
count += run.fGlyphCount * scalarsPerGlyph;
}
return count;
}
void getPositions(SkTextBlob* instance, float* resultArray) {
SkTextBlob::Iter iter(*instance);
SkTextBlob::Iter::Run run;
size_t stored = 0;
while (iter.next(&run)) {
// run.fGlyphIndices points directly to runRecord.glyphBuffer(), which comes directly after RunRecord itself
auto runRecord = reinterpret_cast<const RunRecordClone*>(run.fGlyphIndices) - 1;
unsigned scalarsPerGlyph = RunRecordClone::ScalarsPerGlyph(runRecord->positioning());
memcpy(&resultArray[stored], runRecord->posBuffer(), run.fGlyphCount * scalarsPerGlyph * sizeof(SkScalar));
stored += run.fGlyphCount * scalarsPerGlyph;
}
}
void getGlyphs(SkTextBlob* instance, short* resultArray) {
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;
}
}
int getGlyphsLength(SkTextBlob* instance) {
SkTextBlob::Iter iter(*instance);
SkTextBlob::Iter::Run run;
int stored = 0;
while (iter.next(&run)) {
stored += run.fGlyphCount;
}
return stored;
}
bool getFirstBaseline(SkTextBlob* instance, float* resultArray) {
SkTextBlob::Iter iter(*instance);
SkTextBlob::Iter::Run run;
if (iter.next(&run)) {
auto runRecord = reinterpret_cast<const RunRecordClone*>(run.fGlyphIndices) - 1;
if (runRecord->positioning() != 2) // kFull_Positioning
return false;
resultArray[0] = runRecord->posBuffer()[1];
return true;
}
return false;
}
bool getLastBaseline(SkTextBlob* instance, float* resultArray) {
SkTextBlob::Iter iter(*instance);
SkTextBlob::Iter::Run run;
SkScalar baseline = 0;
while (iter.next(&run)) {
// run.fGlyphIndices points directly to runRecord.glyphBuffer(), which comes directly after RunRecord itself
auto runRecord = reinterpret_cast<const RunRecordClone*>(run.fGlyphIndices) - 1;
if (runRecord->positioning() != 2) // kFull_Positioning
return false;
baseline = std::max(baseline, runRecord->posBuffer()[1]);
}
resultArray[0] = baseline;
return true;
}
int getClustersLength(SkTextBlob* instance) {
SkTextBlob::Iter iter(*instance);
SkTextBlob::Iter::Run run;
size_t count = 0;
while (iter.next(&run)) {
auto runRecord = reinterpret_cast<const RunRecordClone*>(run.fGlyphIndices) - 1;
if (!runRecord->isExtended())
return 0;
count += run.fGlyphCount;
}
return count;
}
bool getClusters(SkTextBlob* instance, int* clusters) { // implementation was taken from skija
SkTextBlob::Iter iter(*instance);
SkTextBlob::Iter::Run run;
size_t stored = 0;
// uint32_t cluster8 = 0;
uint32_t runStart16 = 0;
while (iter.next(&run)) {
// run.fGlyphIndices points directly to runRecord.glyphBuffer(), which comes directly after RunRecord itself
auto runRecord = reinterpret_cast<const RunRecordClone*>(run.fGlyphIndices) - 1;
if (!runRecord->isExtended()) {
return false;
}
skija::UtfIndicesConverter conv(runRecord->textBuffer(), runRecord->textSize());
uint32_t* clusterBuffer = runRecord->clusterBuffer();
for (int i = 0; i < run.fGlyphCount; ++i)
clusters[stored + i] = runStart16 + conv.from8To16(clusterBuffer[i]);
runStart16 += conv.from8To16(runRecord->textSize());
// memcpy(&clusters[stored], runRecord->clusterBuffer(), run.fGlyphCount * sizeof(uint32_t));
stored += run.fGlyphCount;
}
return true;
}
}
}
namespace skija {
UtfIndicesConverter::UtfIndicesConverter(const char* chars8, size_t len8):
fStart8(chars8),
fPtr8(chars8),
fEnd8(chars8 + len8),
fPos16(0)
{}
UtfIndicesConverter::UtfIndicesConverter(const SkString& str):
skija::UtfIndicesConverter::UtfIndicesConverter(str.c_str(), str.size())
{}
size_t UtfIndicesConverter::from16To8(uint32_t i16) {
if (i16 >= fPos16) {
// if new i16 >= last fPos16, continue from where we started
} else {
fPtr8 = fStart8;
fPos16 = 0;
}
while (fPtr8 < fEnd8 && fPos16 < i16) {
SkUnichar u = SkUTF::NextUTF8(&fPtr8, fEnd8);
fPos16 += (uint32_t) SkUTF::ToUTF16(u);
}
return fPtr8 - fStart8;
}
uint32_t UtfIndicesConverter::from8To16(size_t i8) {
if (i8 >= (size_t) (fPtr8 - fStart8)) {
// if new i8 >= last fPtr8, continue from where we started
} else {
fPtr8 = fStart8;
fPos16 = 0;
}
while (fPtr8 < fEnd8 && (size_t) (fPtr8 - fStart8) < i8) {
SkUnichar u = SkUTF::NextUTF8(&fPtr8, fEnd8);
fPos16 += (uint32_t) SkUTF::ToUTF16(u);
}
return fPos16;
}
}
package org.jetbrains.skia
import org.jetbrains.skia.impl.InteropPointer
import org.jetbrains.skia.impl.*
import org.jetbrains.skia.impl.Library.Companion.staticLoad
import org.jetbrains.skia.impl.Managed
import org.jetbrains.skia.impl.NativePointer
import org.jetbrains.skia.impl.Stats
import org.jetbrains.skia.impl.getPtr
import org.jetbrains.skia.impl.interopScope
import org.jetbrains.skia.impl.reachabilityBarrier
class PathMeasure internal constructor(ptr: NativePointer) : Managed(ptr, _FinalizerHolder.PTR) {
companion object {
......@@ -210,17 +204,6 @@ class PathMeasure internal constructor(ptr: NativePointer) : Managed(ptr, _Final
}
}
private inline fun withNullableResult(result: FloatArray, block: (InteropPointer) -> Boolean): FloatArray? = interopScope {
val handle = toInterop(result)
val blockResult = block(handle)
if (blockResult) {
handle.fromInterop(result)
result
} else {
null
}
}
@ExternalSymbolName("org_jetbrains_skia_PathMeasure__1nGetFinalizer")
private external fun PathMeasure_nGetFinalizer(): NativePointer
......
......@@ -22,6 +22,7 @@ class TextBlob internal constructor(ptr: NativePointer) : Managed(ptr, _Finalize
val ptr = interopScope {
_nMakeFromPosH(
toInterop(glyphs),
glyphs.size,
toInterop(xpos),
ypos,
getPtr(font)
......@@ -53,7 +54,7 @@ class TextBlob internal constructor(ptr: NativePointer) : Managed(ptr, _Finalize
Stats.onNativeCall()
val ptr =
interopScope {
_nMakeFromPos(toInterop(glyphs), toInterop(floatPos), getPtr(font))
_nMakeFromPos(toInterop(glyphs), glyphs.size, toInterop(floatPos), getPtr(font))
}
if (ptr == NullPointer) null else TextBlob(ptr)
} finally {
......@@ -75,6 +76,7 @@ class TextBlob internal constructor(ptr: NativePointer) : Managed(ptr, _Finalize
val ptr = interopScope {
_nMakeFromRSXform(
toInterop(glyphs),
glyphs.size,
toInterop(floatXform),
getPtr(font)
)
......@@ -110,7 +112,9 @@ class TextBlob internal constructor(ptr: NativePointer) : Managed(ptr, _Finalize
val bounds: Rect
get() = try {
Stats.onNativeCall()
_nBounds(_ptr)
Rect.fromInteropPointer {
_nBounds(_ptr, it)
}
} finally {
reachabilityBarrier(this)
}
......@@ -143,7 +147,7 @@ class TextBlob internal constructor(ptr: NativePointer) : Managed(ptr, _Finalize
* @return intersections; may be null
*/
fun getIntercepts(lowerBound: Float, upperBound: Float): FloatArray? {
return getIntercepts(lowerBound, upperBound)
return getIntercepts(lowerBound, upperBound, null)
}
/**
......@@ -164,12 +168,15 @@ class TextBlob internal constructor(ptr: NativePointer) : Managed(ptr, _Finalize
fun getIntercepts(lowerBound: Float, upperBound: Float, paint: Paint?): FloatArray? {
return try {
Stats.onNativeCall()
_nGetIntercepts(
_ptr,
lowerBound,
upperBound,
getPtr(paint)
)
withResult(FloatArray(_nGetInterceptsLength(_ptr, lowerBound, upperBound, getPtr(paint)))) {
_nGetIntercepts(
ptr = _ptr,
lower = lowerBound,
upper = upperBound,
paintPtr = getPtr(paint),
resultArray = it
)
}
} finally {
reachabilityBarrier(this)
reachabilityBarrier(paint)
......@@ -222,7 +229,9 @@ class TextBlob internal constructor(ptr: NativePointer) : Managed(ptr, _Finalize
val positions: FloatArray
get() = try {
Stats.onNativeCall()
_nGetPositions(_ptr)
withResult(FloatArray(_nGetPositionsLength(_ptr))) {
_nGetPositions(_ptr, it)
}
} finally {
reachabilityBarrier(this)
}
......@@ -236,8 +245,9 @@ class TextBlob internal constructor(ptr: NativePointer) : Managed(ptr, _Finalize
val clusters: IntArray
get() = try {
Stats.onNativeCall()
val res = _nGetClusters(_ptr) ?: throw IllegalArgumentException()
res
withNullableResult(IntArray(_nGetClustersLength(_ptr))) {
_nGetClusters(_ptr, it)
} ?: throw IllegalArgumentException()
} finally {
reachabilityBarrier(this)
}
......@@ -251,8 +261,9 @@ class TextBlob internal constructor(ptr: NativePointer) : Managed(ptr, _Finalize
val tightBounds: Rect
get() = try {
Stats.onNativeCall()
val res = _nGetTightBounds(_ptr) ?: throw IllegalArgumentException()
res
Rect.fromInteropPointerNullable {
_nGetTightBounds(_ptr, it)
} ?: throw IllegalArgumentException()
} finally {
reachabilityBarrier(this)
}
......@@ -266,8 +277,9 @@ class TextBlob internal constructor(ptr: NativePointer) : Managed(ptr, _Finalize
val blockBounds: Rect
get() = try {
Stats.onNativeCall()
val res = _nGetBlockBounds(_ptr) ?: throw IllegalArgumentException()
res
Rect.fromInteropPointerNullable {
_nGetBlockBounds(_ptr, it)
} ?: throw IllegalArgumentException()
} finally {
reachabilityBarrier(this)
}
......@@ -281,8 +293,9 @@ class TextBlob internal constructor(ptr: NativePointer) : Managed(ptr, _Finalize
val firstBaseline: Float
get() = try {
Stats.onNativeCall()
val res = _nGetFirstBaseline(_ptr) ?: throw IllegalArgumentException()
res
withNullableResult(FloatArray(1)) {
_nGetFirstBaseline(_ptr, it)
}?.firstOrNull() ?: throw IllegalArgumentException()
} finally {
reachabilityBarrier(this)
}
......@@ -296,8 +309,9 @@ class TextBlob internal constructor(ptr: NativePointer) : Managed(ptr, _Finalize
val lastBaseline: Float
get() = try {
Stats.onNativeCall()
val res = _nGetLastBaseline(_ptr) ?: throw IllegalArgumentException()
res
withNullableResult(FloatArray(1)) {
_nGetLastBaseline(_ptr, it)
}?.firstOrNull() ?: throw IllegalArgumentException()
} finally {
reachabilityBarrier(this)
}
......@@ -320,19 +334,22 @@ private external fun TextBlob_nSerializeToData(ptr: NativePointer /*, SkSerialPr
private external fun TextBlob_nMakeFromData(dataPtr: NativePointer /*, SkDeserialProcs */): NativePointer
@ExternalSymbolName("org_jetbrains_skia_TextBlob__1nBounds")
private external fun _nBounds(ptr: NativePointer): Rect
private external fun _nBounds(ptr: NativePointer, resultRect: InteropPointer)
@ExternalSymbolName("org_jetbrains_skia_TextBlob__1nGetInterceptsLength")
private external fun _nGetInterceptsLength(ptr: NativePointer, lower: Float, upper: Float, paintPtr: NativePointer): Int
@ExternalSymbolName("org_jetbrains_skia_TextBlob__1nGetIntercepts")
private external fun _nGetIntercepts(ptr: NativePointer, lower: Float, upper: Float, paintPtr: NativePointer): FloatArray?
private external fun _nGetIntercepts(ptr: NativePointer, lower: Float, upper: Float, paintPtr: NativePointer, resultArray: InteropPointer)
@ExternalSymbolName("org_jetbrains_skia_TextBlob__1nMakeFromPosH")
private external fun _nMakeFromPosH(glyphs: InteropPointer, xpos: InteropPointer, ypos: Float, fontPtr: NativePointer): NativePointer
private external fun _nMakeFromPosH(glyphs: InteropPointer, glyphsLen: Int, xpos: InteropPointer, ypos: Float, fontPtr: NativePointer): NativePointer
@ExternalSymbolName("org_jetbrains_skia_TextBlob__1nMakeFromPos")
private external fun _nMakeFromPos(glyphs: InteropPointer, pos: InteropPointer, fontPtr: NativePointer): NativePointer
private external fun _nMakeFromPos(glyphs: InteropPointer, glyphsLen: Int, pos: InteropPointer, fontPtr: NativePointer): NativePointer
@ExternalSymbolName("org_jetbrains_skia_TextBlob__1nMakeFromRSXform")
private external fun _nMakeFromRSXform(glyphs: InteropPointer, xform: InteropPointer, fontPtr: NativePointer): NativePointer
private external fun _nMakeFromRSXform(glyphs: InteropPointer, glyphsLen: Int, xform: InteropPointer, fontPtr: NativePointer): NativePointer
@ExternalSymbolName("org_jetbrains_skia_TextBlob__1nGetGlyphsLength")
private external fun _nGetGlyphsLength(ptr: NativePointer): Int
......@@ -340,20 +357,26 @@ private external fun _nGetGlyphsLength(ptr: NativePointer): Int
@ExternalSymbolName("org_jetbrains_skia_TextBlob__1nGetGlyphs")
private external fun _nGetGlyphs(ptr: NativePointer, result: InteropPointer)
@ExternalSymbolName("org_jetbrains_skia_TextBlob__1nGetPositionsLength")
private external fun _nGetPositionsLength(ptr: NativePointer): Int
@ExternalSymbolName("org_jetbrains_skia_TextBlob__1nGetPositions")
private external fun _nGetPositions(ptr: NativePointer): FloatArray
private external fun _nGetPositions(ptr: NativePointer, resultArray: InteropPointer)
@ExternalSymbolName("org_jetbrains_skia_TextBlob__1nGetClustersLength")
private external fun _nGetClustersLength(ptr: NativePointer): Int
@ExternalSymbolName("org_jetbrains_skia_TextBlob__1nGetClusters")
private external fun _nGetClusters(ptr: NativePointer): IntArray?
private external fun _nGetClusters(ptr: NativePointer, resultArray: InteropPointer): Boolean
@ExternalSymbolName("org_jetbrains_skia_TextBlob__1nGetTightBounds")
private external fun _nGetTightBounds(ptr: NativePointer): Rect?
private external fun _nGetTightBounds(ptr: NativePointer, resultArray: InteropPointer): Boolean
@ExternalSymbolName("org_jetbrains_skia_TextBlob__1nGetBlockBounds")
private external fun _nGetBlockBounds(ptr: NativePointer): Rect?
private external fun _nGetBlockBounds(ptr: NativePointer, resultArray: InteropPointer): Boolean
@ExternalSymbolName("org_jetbrains_skia_TextBlob__1nGetFirstBaseline")
private external fun _nGetFirstBaseline(ptr: NativePointer): Float?
private external fun _nGetFirstBaseline(ptr: NativePointer, resultArray: InteropPointer): Boolean
@ExternalSymbolName("org_jetbrains_skia_TextBlob__1nGetLastBaseline")
private external fun _nGetLastBaseline(ptr: NativePointer): Float?
private external fun _nGetLastBaseline(ptr: NativePointer, resultArray: InteropPointer): Boolean
......@@ -71,6 +71,17 @@ inline fun withResult(result: FloatArray, block: InteropScope.(InteropPointer) -
result
}
inline fun withNullableResult(result: FloatArray, block: InteropScope.(InteropPointer) -> Boolean): FloatArray? = interopScope {
val handle = toInterop(result)
val blockResult = block(handle)
if (blockResult) {
handle.fromInterop(result)
result
} else {
null
}
}
inline fun withResult(result: IntArray, block: InteropScope.(InteropPointer) -> Unit): IntArray = interopScope {
val handle = toInterop(result)
block(handle)
......
package org.jetbrains.skia
import org.jetbrains.skia.tests.assertCloseEnough
import org.jetbrains.skia.tests.assertContentCloseEnough
import org.jetbrains.skia.tests.makeFromResource
import org.jetbrains.skiko.KotlinBackend
import org.jetbrains.skiko.kotlinBackend
import org.jetbrains.skiko.tests.runTest
import kotlin.math.cos
import kotlin.math.sin
import kotlin.test.*
class TextBlobTest {
private val inter36: suspend () -> Font = suspend {
Font(Typeface.makeFromResource("./fonts/Inter-Hinted-Regular.ttf"), 36f)
}
// smaller eps values make tests fail
private val eps = when (kotlinBackend) {
KotlinBackend.JS -> 0.02f
KotlinBackend.Native -> 0.02f
else -> 0.02f
}
@Test
fun canMakeFromPos() = runTest {
val glyphs = shortArrayOf(1983, 830, 1213, 1205, 638, 1231, 1326, 161, 611, 721, 721, 773, 1326)
val positions = listOf(
0.0f, 0.0f, 26.0f, 0.0f, 48.0f, 0.0f, 69.0f, 0.0f, 89.0f, 0.0f, 109.28409f, 0.0f, 128.28409f, 0.0f,
138.28409f, 0.0f, 165.28409f, 0.0f, 186.28409f, 0.0f, 195.28409f, 0.0f, 204.28409f, 0.0f, 225.28409f, 0.0f
).chunked(2).map { Point(it[0], it[1]) }.toTypedArray()
val textBlob = TextBlob.makeFromPos(
glyphs = glyphs,
pos = positions,
font = inter36()
)!!
assertNotEquals(
illegal = 0,
actual = textBlob.uniqueId,
message = "uniqueId should return a non-zero value unique among all text blobs."
)
assertContentEquals(
expected = glyphs,
actual = textBlob.glyphs
)
assertContentEquals(
expected = Point.flattenArray(positions),
actual = textBlob.positions
)
assertCloseEnough(
expected = Rect(-26.59091f, -39.272827f, 318.27557f, 11.505432f),
actual = textBlob.bounds,
epsilon = eps
)
assertContentCloseEnough(
expected = floatArrayOf(
3.2215908f, 19.585226f, 28.761364f, 40.38493f, 50.761364f, 63.698864f, 71.76136f, 81.017044f, 97.01448f,
102.99094f, 132.33693f, 134.25397f, 141.45454f, 158.58522f, 173.29857f, 179.27502f, 212.21196f,
217.8335f, 229.33693f, 231.25397f
),
actual = textBlob.getIntercepts(lowerBound = 0f, upperBound = 1f),
epsilon = eps
)
// Ensure can get tightBounds. we don't make assertEquals because platforms' results vary
val tightBoundsRect = textBlob.tightBounds
assertCloseEnough(
expected = Rect(0f, -34.875f, 235.30681f, 8.692932f),
actual = textBlob.blockBounds.also { println(it) },
epsilon = 0.2f // smaller values make tests fail on some platforms
)
assertEquals(0f, textBlob.firstBaseline)
assertEquals(0f, textBlob.lastBaseline)
assertFailsWith<IllegalArgumentException> {
// this TextBlob doesn't have such information
textBlob.clusters
}
val data = textBlob.serializeToData()
val blobFromData = TextBlob.makeFromData(data)!!
assertContentEquals(expected = glyphs, actual = blobFromData.glyphs)
assertContentEquals(expected = Point.flattenArray(positions), actual = blobFromData.positions)
}
@Test
fun canMakeFromPosH() = runTest {
val glyphs = shortArrayOf(1983, 830, 1213, 1205, 638, 1231, 1326, 161, 611, 721, 721, 773, 1326)
val positions = listOf(
0.0f, 0.0f, 26.0f, 0.0f, 48.0f, 0.0f, 69.0f, 0.0f, 89.0f, 0.0f, 109.28409f, 0.0f, 128.28409f, 0.0f,
138.28409f, 0.0f, 165.28409f, 0.0f, 186.28409f, 0.0f, 195.28409f, 0.0f, 204.28409f, 0.0f, 225.28409f, 0.0f
).filterIndexed {
// remove y, leave only x (horizontal positions)
ix, _ ->
ix % 2 == 0
}.toTypedArray().toFloatArray()
val textBlob = TextBlob.makeFromPosH(
glyphs = glyphs,
xpos = positions,
ypos = 1f,
font = inter36()
)!!
assertNotEquals(
illegal = 0,
actual = textBlob.uniqueId,
message = "uniqueId should return a non-zero value unique among all text blobs."
)
assertNotEquals(
illegal = 0,
actual = textBlob.uniqueId,
message = "uniqueId should return a non-zero value unique among all text blobs."
)
assertContentEquals(
expected = glyphs,
actual = textBlob.glyphs
)
assertContentEquals(
expected = positions,
actual = textBlob.positions
)
assertCloseEnough(
expected = Rect(-26.59091f, -38.272827f, 318.27557f, 12.505432f),
actual = textBlob.bounds,
epsilon = eps
)
assertContentCloseEnough(
expected = floatArrayOf(
3.2215908f, 22.75568f, 28.761364f, 42.330772f, 50.761364f, 66.71591f, 71.76136f, 85.03906f, 94.86898f,
105.11858f, 117.41477f, 120.431816f, 131.26941f, 135.32149f, 141.45454f, 161.75568f, 171.15308f,
181.40268f, 189.04546f, 192.0625f, 198.04546f, 201.0625f, 210.14375f, 219.9017f, 228.26941f, 232.32149f
),
actual = textBlob.getIntercepts(lowerBound = 0f, upperBound = 1f)!!,
epsilon = eps
)
assertFailsWith<IllegalArgumentException> { textBlob.tightBounds }
assertFailsWith<IllegalArgumentException> { textBlob.blockBounds }
assertFailsWith<IllegalArgumentException> { textBlob.firstBaseline }
assertFailsWith<IllegalArgumentException> { textBlob.lastBaseline }
val data = textBlob.serializeToData()
val blobFromData = TextBlob.makeFromData(data)!!
assertContentEquals(expected = glyphs, actual = blobFromData.glyphs)
assertContentEquals(expected = positions, actual = blobFromData.positions)
}
@Test // https://fiddle.skia.org/c/@Canvas_drawTextRSXform
fun canMakeFromRSXform() = runTest {
val glyphs = shortArrayOf(1983, 830, 1213, 1205, 638, 1231, 1326, 161, 611, 721, 721, 773, 1326)
var angle = 0f
var scale = 1f
val rsxForms = glyphs.map {
val s = sin(angle.toDouble()).toFloat() * scale
val c = cos(angle.toDouble()).toFloat() * scale
angle += .45f
scale += .2f
RSXform(scos = c, ssin = s, tx = -s * 16, ty = c * 16)
}.toTypedArray()
val textBlob = TextBlob.makeFromRSXform(
glyphs = glyphs,
xform = rsxForms,
font = inter36()
)!!
assertNotEquals(
illegal = 0,
actual = textBlob.uniqueId,
message = "uniqueId should return a non-zero value unique among all text blobs."
)
assertNotEquals(
illegal = 0,
actual = textBlob.uniqueId,
message = "uniqueId should return a non-zero value unique among all text blobs."
)
assertContentEquals(
expected = glyphs,
actual = textBlob.glyphs
)
assertContentCloseEnough(
expected = floatArrayOf(
1.0f, 0.0f, -0.0f, 16.0f, 1.0805366f, 0.52195865f, -8.351338f, 17.288586f, 0.87025404f, 1.0966578f,
-17.546524f, 13.924065f, 0.35041088f, 1.5611575f, -24.97852f, 5.606574f, -0.4089637f, 1.7529259f,
-28.046814f, -6.5434194f, -1.2563474f, 1.5561466f, -24.898346f, -20.101559f, -1.9889591f, 0.9402358f,
-15.043773f, -31.823345f, -2.3999155f, -0.020177627f, 0.32284203f, -38.398647f, -2.331572f, -1.1505536f,
18.408857f, -37.305153f, -1.7220064f, -2.2078712f, 35.32594f, -27.552103f, -0.6323875f, -2.9325907f,
46.92145f, -10.1182f, 0.75322014f, -3.1100905f, 49.761448f, 12.051522f, 2.1579552f, -2.6274006f,
42.03841f, 34.527283f
),
actual = textBlob.positions.also { println(it.joinToString()) },
epsilon = eps
)
assertCloseEnough(
expected = Rect(-243.59294f, -306.74146f, 272.9392f, 173.94711f),
actual = textBlob.bounds,
epsilon = eps
)
// These values are not available for this textBlob
assertFailsWith<IllegalArgumentException> { textBlob.tightBounds }
assertFailsWith<IllegalArgumentException> { textBlob.blockBounds }
assertFailsWith<IllegalArgumentException> { textBlob.firstBaseline }
assertFailsWith<IllegalArgumentException> { textBlob.lastBaseline }
val data = textBlob.serializeToData()
val blobFromData = TextBlob.makeFromData(data)!!
assertContentEquals(expected = glyphs, actual = blobFromData.glyphs)
}
}
......@@ -5,6 +5,8 @@
#include "SkSerialProcs.h"
#include "SkTextBlob.h"
#include "interop.hh"
#include "mppinterop.h"
#include "RunRecordClone.hh"
static void unrefTextBlob(SkTextBlob* ptr) {
ptr->unref();
......@@ -15,11 +17,14 @@ extern "C" JNIEXPORT jlong JNICALL Java_org_jetbrains_skia_TextBlobKt_TextBlob_1
return static_cast<jlong>(reinterpret_cast<uintptr_t>(&unrefTextBlob));
}
extern "C" JNIEXPORT jobject JNICALL Java_org_jetbrains_skia_TextBlobKt__1nBounds
(JNIEnv* env, jclass jclass, jlong ptr) {
extern "C" JNIEXPORT void JNICALL Java_org_jetbrains_skia_TextBlobKt__1nBounds
(JNIEnv* env, jclass jclass, jlong ptr, jfloatArray resultRect) {
SkTextBlob* instance = reinterpret_cast<SkTextBlob*>(static_cast<uintptr_t>(ptr));
SkRect bounds = instance->bounds();
return skija::Rect::fromSkRect(env, instance->bounds());
jfloat* floats = env->GetFloatArrayElements(resultRect, 0);
skikoMpp::skrect::serializeAs4Floats(bounds, floats);
env->ReleaseFloatArrayElements(resultRect, floats, 0);
}
extern "C" JNIEXPORT jint JNICALL Java_org_jetbrains_skia_TextBlobKt_TextBlob_1nGetUniqueId
......@@ -28,25 +33,31 @@ extern "C" JNIEXPORT jint JNICALL Java_org_jetbrains_skia_TextBlobKt_TextBlob_1n
return instance->uniqueID();
}
extern "C" JNIEXPORT jfloatArray JNICALL Java_org_jetbrains_skia_TextBlobKt__1nGetIntercepts
extern "C" JNIEXPORT jint JNICALL Java_org_jetbrains_skia_TextBlobKt__1nGetInterceptsLength
(JNIEnv* env, jclass jclass, jlong ptr, jfloat lower, jfloat upper, jlong paintPtr) {
SkTextBlob* instance = reinterpret_cast<SkTextBlob*>(static_cast<uintptr_t>(ptr));
std::vector<float> bounds {lower, upper};
SkPaint* paint = reinterpret_cast<SkPaint*>(static_cast<uintptr_t>(paintPtr));
int len = instance->getIntercepts(bounds.data(), nullptr, paint);
return len;
}
extern "C" JNIEXPORT void JNICALL Java_org_jetbrains_skia_TextBlobKt__1nGetIntercepts
(JNIEnv* env, jclass jclass, jlong ptr, jfloat lower, jfloat upper, jlong paintPtr, jfloatArray resultArray) {
SkTextBlob* instance = reinterpret_cast<SkTextBlob*>(static_cast<uintptr_t>(ptr));
std::vector<float> bounds {lower, upper};
SkPaint* paint = reinterpret_cast<SkPaint*>(static_cast<uintptr_t>(paintPtr));
int len = instance->getIntercepts(bounds.data(), nullptr, paint);
std::vector<float> intervals(len);
instance->getIntercepts(bounds.data(), intervals.data(), paint);
return javaFloatArray(env, intervals);
jfloat* floats = env->GetFloatArrayElements(resultArray, 0);
std::vector<float> bounds {lower, upper};
instance->getIntercepts(bounds.data(), floats, paint);
env->ReleaseFloatArrayElements(resultArray, floats, 0);
}
extern "C" JNIEXPORT jlong JNICALL Java_org_jetbrains_skia_TextBlobKt__1nMakeFromPosH
(JNIEnv* env, jclass jclass, jshortArray glyphsArr, jfloatArray xposArr, jfloat ypos, jlong fontPtr) {
jsize len = env->GetArrayLength(glyphsArr);
(JNIEnv* env, jclass jclass, jshortArray glyphsArr, jint glyphsLen, jfloatArray xposArr, jfloat ypos, jlong fontPtr) {
jshort* glyphs = env->GetShortArrayElements(glyphsArr, nullptr);
jfloat* xpos = env->GetFloatArrayElements(xposArr, nullptr);
SkFont* font = reinterpret_cast<SkFont*>(static_cast<uintptr_t>(fontPtr));
SkTextBlob* instance = SkTextBlob::MakeFromPosTextH(glyphs, len * sizeof(jshort), xpos, ypos, *font, SkTextEncoding::kGlyphID).release();
SkTextBlob* instance = SkTextBlob::MakeFromPosTextH(glyphs, glyphsLen * sizeof(jshort), xpos, ypos, *font, SkTextEncoding::kGlyphID).release();
env->ReleaseShortArrayElements(glyphsArr, glyphs, 0);
env->ReleaseFloatArrayElements(xposArr, xpos, 0);
......@@ -55,13 +66,18 @@ extern "C" JNIEXPORT jlong JNICALL Java_org_jetbrains_skia_TextBlobKt__1nMakeFro
}
extern "C" JNIEXPORT jlong JNICALL Java_org_jetbrains_skia_TextBlobKt__1nMakeFromPos
(JNIEnv* env, jclass jclass, jshortArray glyphsArr, jfloatArray posArr, jlong fontPtr ) {
jsize len = env->GetArrayLength(glyphsArr);
(JNIEnv* env, jclass jclass, jshortArray glyphsArr, jint glyphsLen, jfloatArray posArr, jlong fontPtr ) {
jshort* glyphs = env->GetShortArrayElements(glyphsArr, nullptr);
jfloat* pos = env->GetFloatArrayElements(posArr, nullptr);
SkFont* font = reinterpret_cast<SkFont*>(static_cast<uintptr_t>(fontPtr));
SkTextBlob* instance = SkTextBlob::MakeFromPosText(glyphs, len * sizeof(jshort), reinterpret_cast<SkPoint*>(pos), *font, SkTextEncoding::kGlyphID).release();
SkTextBlob* instance = SkTextBlob::MakeFromPosText(
glyphs,
glyphsLen * sizeof(jshort),
reinterpret_cast<SkPoint*>(pos),
*font,
SkTextEncoding::kGlyphID
).release();
env->ReleaseShortArrayElements(glyphsArr, glyphs, 0);
env->ReleaseFloatArrayElements(posArr, pos, 0);
......@@ -70,13 +86,12 @@ extern "C" JNIEXPORT jlong JNICALL Java_org_jetbrains_skia_TextBlobKt__1nMakeFro
}
extern "C" JNIEXPORT jlong JNICALL Java_org_jetbrains_skia_TextBlobKt__1nMakeFromRSXform
(JNIEnv* env, jclass jclass, jshortArray glyphsArr, jfloatArray xformArr, jlong fontPtr ) {
jsize len = env->GetArrayLength(glyphsArr);
(JNIEnv* env, jclass jclass, jshortArray glyphsArr, jint glyphsLen, jfloatArray xformArr, jlong fontPtr ) {
jshort* glyphs = env->GetShortArrayElements(glyphsArr, nullptr);
jfloat* xform = env->GetFloatArrayElements(xformArr, nullptr);
SkFont* font = reinterpret_cast<SkFont*>(static_cast<uintptr_t>(fontPtr));
SkTextBlob* instance = SkTextBlob::MakeFromRSXform(glyphs, len * sizeof(jshort), reinterpret_cast<SkRSXform*>(xform), *font, SkTextEncoding::kGlyphID).release();
SkTextBlob* instance = SkTextBlob::MakeFromRSXform(glyphs, glyphsLen * sizeof(jshort), reinterpret_cast<SkRSXform*>(xform), *font, SkTextEncoding::kGlyphID).release();
env->ReleaseShortArrayElements(glyphsArr, glyphs, 0);
env->ReleaseFloatArrayElements(xformArr, xform, 0);
......@@ -98,225 +113,93 @@ extern "C" JNIEXPORT jlong JNICALL Java_org_jetbrains_skia_TextBlobKt_TextBlob_1
return reinterpret_cast<jlong>(instance);
}
// Must match SkTextBlobPriv.h
//
// Extended Textblob runs have more data after the Pos[] array:
//
// -------------------------------------------------------------------------
// ... | RunRecord | Glyphs[] | Pos[] | TextSize | Clusters[] | Text[] | ...
// -------------------------------------------------------------------------
class RunRecordClone {
public:
SkFont fFont;
uint32_t fCount;
SkPoint fOffset;
uint32_t fFlags;
uint16_t* glyphBuffer() const {
// Glyphs are stored immediately following the record.
return reinterpret_cast<uint16_t*>(const_cast<RunRecordClone*>(this) + 1);
}
SkScalar* posBuffer() const {
// Position scalars follow the (aligned) glyph buffer.
return reinterpret_cast<SkScalar*>(reinterpret_cast<uint8_t*>(this->glyphBuffer()) +
SkAlign4(fCount * sizeof(uint16_t)));
}
uint32_t* textSizePtr() const {
// textSize follows the position buffer.
return (uint32_t*)(&this->posBuffer()[fCount * ScalarsPerGlyph(positioning())]);
}
uint32_t textSize() const {
return isExtended() ? *this->textSizePtr() : 0;
}
uint32_t* clusterBuffer() const {
// clusters follow the textSize.
return isExtended() ? 1 + this->textSizePtr() : nullptr;
}
char* textBuffer() const {
return isExtended()
? reinterpret_cast<char*>(this->clusterBuffer() + fCount)
: nullptr;
}
uint8_t positioning() const {
return fFlags & 0x3; // kPositioning_Mask
}
bool isExtended() const {
return fFlags & 0x8; // kExtended_Flag
}
static unsigned ScalarsPerGlyph(uint8_t pos) {
const uint8_t gScalarsPerPositioning[] = {
0, // kDefault_Positioning
1, // kHorizontal_Positioning
2, // kFull_Positioning
4, // kRSXform_Positioning
};
return gScalarsPerPositioning[pos];
}
};
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;
return skikoMpp::textblob::getGlyphsLength(instance);
}
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;
size_t stored = 0;
jshort * shorts = env->GetShortArrayElements(resultArray, nullptr);
while (iter.next(&run)) {
memcpy(shorts + stored, run.fGlyphIndices, run.fGlyphCount * sizeof(uint16_t));
stored += run.fGlyphCount;
}
skikoMpp::textblob::getGlyphs(instance, reinterpret_cast<short*>(shorts));
env->ReleaseShortArrayElements(resultArray, shorts, 0);
}
extern "C" JNIEXPORT jfloatArray JNICALL Java_org_jetbrains_skia_TextBlobKt__1nGetPositions
extern "C" JNIEXPORT jint JNICALL Java_org_jetbrains_skia_TextBlobKt__1nGetPositionsLength
(JNIEnv* env, jclass jclass, jlong ptr) {
SkTextBlob* instance = reinterpret_cast<SkTextBlob*>(static_cast<uintptr_t>(ptr));
SkTextBlob::Iter iter(*instance);
SkTextBlob::Iter::Run run;
std::vector<jfloat> positions;
size_t stored = 0;
while (iter.next(&run)) {
// run.fGlyphIndices points directly to runRecord.glyphBuffer(), which comes directly after RunRecord itself
auto runRecord = reinterpret_cast<const RunRecordClone*>(run.fGlyphIndices) - 1;
unsigned scalarsPerGlyph = RunRecordClone::ScalarsPerGlyph(runRecord->positioning());
positions.resize(stored + run.fGlyphCount * scalarsPerGlyph);
memcpy(&positions[stored], runRecord->posBuffer(), run.fGlyphCount * scalarsPerGlyph * sizeof(SkScalar));
stored += run.fGlyphCount * scalarsPerGlyph;
}
return javaFloatArray(env, positions);
return skikoMpp::textblob::getPositionsLength(instance);
}
extern "C" JNIEXPORT jintArray JNICALL Java_org_jetbrains_skia_TextBlobKt__1nGetClusters
(JNIEnv* env, jclass jclass, jlong ptr) {
extern "C" JNIEXPORT void JNICALL Java_org_jetbrains_skia_TextBlobKt__1nGetPositions
(JNIEnv* env, jclass jclass, jlong ptr, jfloatArray resultArray) {
SkTextBlob* instance = reinterpret_cast<SkTextBlob*>(static_cast<uintptr_t>(ptr));
SkTextBlob::Iter iter(*instance);
SkTextBlob::Iter::Run run;
std::vector<jint> clusters;
size_t stored = 0;
// uint32_t cluster8 = 0;
uint32_t runStart16 = 0;
while (iter.next(&run)) {
// run.fGlyphIndices points directly to runRecord.glyphBuffer(), which comes directly after RunRecord itself
auto runRecord = reinterpret_cast<const RunRecordClone*>(run.fGlyphIndices) - 1;
if (!runRecord->isExtended())
return nullptr;
skija::UtfIndicesConverter conv(runRecord->textBuffer(), runRecord->textSize());
clusters.resize(stored + run.fGlyphCount);
uint32_t* clusterBuffer = runRecord->clusterBuffer();
for (int i = 0; i < run.fGlyphCount; ++i)
clusters[stored + i] = runStart16 + conv.from8To16(clusterBuffer[i]);
runStart16 += conv.from8To16(runRecord->textSize());
// memcpy(&clusters[stored], runRecord->clusterBuffer(), run.fGlyphCount * sizeof(uint32_t));
stored += run.fGlyphCount;
}
return javaIntArray(env, clusters);
jfloat* positions = env->GetFloatArrayElements(resultArray, 0);
skikoMpp::textblob::getPositions(instance, reinterpret_cast<float*>(positions));
env->ReleaseFloatArrayElements(resultArray, positions, 0);
}
extern "C" JNIEXPORT jobject JNICALL Java_org_jetbrains_skia_TextBlobKt__1nGetTightBounds
extern "C" JNIEXPORT jint JNICALL Java_org_jetbrains_skia_TextBlobKt__1nGetClustersLength
(JNIEnv* env, jclass jclass, jlong ptr) {
SkTextBlob* instance = reinterpret_cast<SkTextBlob*>(static_cast<uintptr_t>(ptr));
SkTextBlob::Iter iter(*instance);
SkTextBlob::Iter::Run run;
auto bounds = SkRect::MakeEmpty();
SkRect tmpBounds;
while (iter.next(&run)) {
// run.fGlyphIndices points directly to runRecord.glyphBuffer(), which comes directly after RunRecord itself
auto runRecord = reinterpret_cast<const RunRecordClone*>(run.fGlyphIndices) - 1;
if (runRecord->positioning() != 2) // kFull_Positioning
return nullptr;
runRecord->fFont.measureText(runRecord->glyphBuffer(), run.fGlyphCount * sizeof(uint16_t), SkTextEncoding::kGlyphID, &tmpBounds, nullptr);
SkScalar* posBuffer = runRecord->posBuffer();
tmpBounds.offset(posBuffer[0], posBuffer[1]);
bounds.join(tmpBounds);
}
return skija::Rect::fromSkRect(env, bounds);
return skikoMpp::textblob::getClustersLength(instance);
}
extern "C" JNIEXPORT jobject JNICALL Java_org_jetbrains_skia_TextBlobKt__1nGetBlockBounds
(JNIEnv* env, jclass jclass, jlong ptr) {
extern "C" JNIEXPORT jboolean JNICALL Java_org_jetbrains_skia_TextBlobKt__1nGetClusters
(JNIEnv* env, jclass jclass, jlong ptr, jintArray resultArray) {
SkTextBlob* instance = reinterpret_cast<SkTextBlob*>(static_cast<uintptr_t>(ptr));
SkTextBlob::Iter iter(*instance);
SkTextBlob::Iter::Run run;
auto bounds = SkRect::MakeEmpty();
SkFontMetrics metrics;
while (iter.next(&run)) {
// run.fGlyphIndices points directly to runRecord.glyphBuffer(), which comes directly after RunRecord itself
auto runRecord = reinterpret_cast<const RunRecordClone*>(run.fGlyphIndices) - 1;
if (runRecord->positioning() != 2) // kFull_Positioning
return nullptr;
SkScalar* posBuffer = runRecord->posBuffer();
const SkFont& font = runRecord->fFont;
font.getMetrics(&metrics);
SkScalar lastLeft = posBuffer[(run.fGlyphCount - 1) * 2];
SkScalar lastWidth;
if (run.fGlyphCount > 1 && SkScalarNearlyEqual(posBuffer[(run.fGlyphCount - 2) * 2], lastLeft))
lastWidth = 0;
else
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);
}
return skija::Rect::fromSkRect(env, bounds);
jint* clusters = env->GetIntArrayElements(resultArray, 0);
auto hasValue = skikoMpp::textblob::getClusters(instance, reinterpret_cast<int*>(clusters));
env->ReleaseIntArrayElements(resultArray, clusters, 0);
return hasValue;
}
extern "C" JNIEXPORT jobject JNICALL Java_org_jetbrains_skia_TextBlobKt__1nGetFirstBaseline
(JNIEnv* env, jclass jclass, jlong ptr) {
extern "C" JNIEXPORT jboolean JNICALL Java_org_jetbrains_skia_TextBlobKt__1nGetTightBounds
(JNIEnv* env, jclass jclass, jlong ptr, jfloatArray resultArray) {
SkTextBlob* instance = reinterpret_cast<SkTextBlob*>(static_cast<uintptr_t>(ptr));
SkTextBlob::Iter iter(*instance);
SkTextBlob::Iter::Run run;
while (iter.next(&run)) {
// run.fGlyphIndices points directly to runRecord.glyphBuffer(), which comes directly after RunRecord itself
auto runRecord = reinterpret_cast<const RunRecordClone*>(run.fGlyphIndices) - 1;
if (runRecord->positioning() != 2) // kFull_Positioning
return nullptr;
return javaFloat(env, runRecord->posBuffer()[1]);
}
return nullptr;
auto bounds = skikoMpp::textblob::getTightBounds(instance);
if (!bounds) return false;
jfloat* floats = env->GetFloatArrayElements(resultArray, 0);
skikoMpp::skrect::serializeAs4Floats(*bounds, floats);
env->ReleaseFloatArrayElements(resultArray, floats, 0);
return true;
}
extern "C" JNIEXPORT jobject JNICALL Java_org_jetbrains_skia_TextBlobKt__1nGetLastBaseline
(JNIEnv* env, jclass jclass, jlong ptr) {
extern "C" JNIEXPORT jboolean JNICALL Java_org_jetbrains_skia_TextBlobKt__1nGetBlockBounds
(JNIEnv* env, jclass jclass, jlong ptr, jfloatArray resultArray) {
SkTextBlob* instance = reinterpret_cast<SkTextBlob*>(static_cast<uintptr_t>(ptr));
std::unique_ptr<SkRect> bounds = skikoMpp::textblob::getBlockBounds(instance);
if (!bounds) return false;
jfloat* floats = env->GetFloatArrayElements(resultArray, 0);
skikoMpp::skrect::serializeAs4Floats(*bounds, floats);
env->ReleaseFloatArrayElements(resultArray, floats, 0);
return true;
}
extern "C" JNIEXPORT jboolean JNICALL Java_org_jetbrains_skia_TextBlobKt__1nGetFirstBaseline
(JNIEnv* env, jclass jclass, jlong ptr, jfloatArray resultArray) {
SkTextBlob* instance = reinterpret_cast<SkTextBlob*>(static_cast<uintptr_t>(ptr));
jfloat* floats = env->GetFloatArrayElements(resultArray, 0);
auto hasValue = skikoMpp::textblob::getFirstBaseline(instance, reinterpret_cast<float*>(floats));
env->ReleaseFloatArrayElements(resultArray, floats, 0);
return hasValue;
}
extern "C" JNIEXPORT jboolean JNICALL Java_org_jetbrains_skia_TextBlobKt__1nGetLastBaseline
(JNIEnv* env, jclass jclass, jlong ptr, jfloatArray resultArray) {
SkTextBlob* instance = reinterpret_cast<SkTextBlob*>(static_cast<uintptr_t>(ptr));
SkTextBlob::Iter iter(*instance);
SkTextBlob::Iter::Run run;
SkScalar baseline = 0;
while (iter.next(&run)) {
// run.fGlyphIndices points directly to runRecord.glyphBuffer(), which comes directly after RunRecord itself
auto runRecord = reinterpret_cast<const RunRecordClone*>(run.fGlyphIndices) - 1;
if (runRecord->positioning() != 2) // kFull_Positioning
return nullptr;
baseline = std::max(baseline, runRecord->posBuffer()[1]);
}
return javaFloat(env, baseline);
jfloat* floats = env->GetFloatArrayElements(resultArray, 0);
auto hasValue = skikoMpp::textblob::getLastBaseline(instance, reinterpret_cast<float*>(floats));
env->ReleaseFloatArrayElements(resultArray, floats, 0);
return hasValue;
}
......@@ -1202,46 +1202,3 @@ namespace skija {
}
}
}
skija::UtfIndicesConverter::UtfIndicesConverter(const char* chars8, size_t len8):
fStart8(chars8),
fPtr8(chars8),
fEnd8(chars8 + len8),
fPos16(0)
{}
skija::UtfIndicesConverter::UtfIndicesConverter(const SkString& str):
skija::UtfIndicesConverter::UtfIndicesConverter(str.c_str(), str.size())
{}
size_t skija::UtfIndicesConverter::from16To8(uint32_t i16) {
if (i16 >= fPos16) {
// if new i16 >= last fPos16, continue from where we started
} else {
fPtr8 = fStart8;
fPos16 = 0;
}
while (fPtr8 < fEnd8 && fPos16 < i16) {
SkUnichar u = SkUTF::NextUTF8(&fPtr8, fEnd8);
fPos16 += (uint32_t) SkUTF::ToUTF16(u);
}
return fPtr8 - fStart8;
}
uint32_t skija::UtfIndicesConverter::from8To16(size_t i8) {
if (i8 >= (size_t) (fPtr8 - fStart8)) {
// if new i8 >= last fPtr8, continue from where we started
} else {
fPtr8 = fStart8;
fPos16 = 0;
}
while (fPtr8 < fEnd8 && (size_t) (fPtr8 - fStart8) < i8) {
SkUnichar u = SkUTF::NextUTF8(&fPtr8, fEnd8);
fPos16 += (uint32_t) SkUTF::ToUTF16(u);
}
return fPos16;
}
......@@ -18,6 +18,7 @@
#include "SkString.h"
#include "SkSurfaceProps.h"
#include "TextStyle.h"
#include "mppinterop.h"
namespace java {
namespace io {
......@@ -321,20 +322,6 @@ namespace skija {
SkSamplingOptions unpackFrom2Ints(JNIEnv* env, jint val1, jint val2);
}
class UtfIndicesConverter {
public:
UtfIndicesConverter(const char* chars8, size_t len8);
UtfIndicesConverter(const SkString& s);
const char* fStart8;
const char* fPtr8;
const char* fEnd8;
uint32_t fPos16;
size_t from16To8(uint32_t i16);
uint32_t from8To16(size_t i8);
};
namespace impl {
namespace Native {
extern jfieldID _ptr;
......
......@@ -7,6 +7,8 @@
#include "SkSerialProcs.h"
#include "SkTextBlob.h"
#include "common.h"
#include "RunRecordClone.hh"
#include "mppinterop.h"
static void unrefTextBlob(SkTextBlob* ptr) {
ptr->unref();
......@@ -18,9 +20,11 @@ SKIKO_EXPORT KNativePointer org_jetbrains_skia_TextBlob__1nGetFinalizer
}
SKIKO_EXPORT KInteropPointer org_jetbrains_skia_TextBlob__1nBounds
(KNativePointer ptr) {
TODO("implement org_jetbrains_skia_TextBlob__1nBounds");
SKIKO_EXPORT void org_jetbrains_skia_TextBlob__1nBounds
(KNativePointer ptr, KInteropPointer resultFloats) {
SkTextBlob* instance = reinterpret_cast<SkTextBlob*>(ptr);
SkRect bounds = instance->bounds();
skikoMpp::skrect::serializeAs4Floats(bounds, reinterpret_cast<float*>(resultFloats));
}
SKIKO_EXPORT KInt org_jetbrains_skia_TextBlob__1nGetUniqueId
......@@ -29,24 +33,65 @@ SKIKO_EXPORT KInt org_jetbrains_skia_TextBlob__1nGetUniqueId
return instance->uniqueID();
}
SKIKO_EXPORT KFloat* org_jetbrains_skia_TextBlob__1nGetIntercepts
(KNativePointer ptr, KFloat lower, KFloat upper, KNativePointer paintPtr) {
TODO("implement org_jetbrains_skia_TextBlob__1nGetIntercepts");
SKIKO_EXPORT KInt org_jetbrains_skia_TextBlob__1nGetInterceptsLength
(KNativePointer ptr, KFloat lower, KFloat upper, KNativePointer paintPtr) {
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;
}
SKIKO_EXPORT void org_jetbrains_skia_TextBlob__1nGetIntercepts
(KNativePointer ptr, KFloat lower, KFloat upper, KNativePointer paintPtr, KFloat* resultArray) {
SkTextBlob* instance = reinterpret_cast<SkTextBlob*>(ptr);
SkPaint* paint = reinterpret_cast<SkPaint*>(paintPtr);
std::vector<float> bounds {lower, upper};
instance->getIntercepts(bounds.data(), resultArray, paint);
}
SKIKO_EXPORT KNativePointer org_jetbrains_skia_TextBlob__1nMakeFromPosH
(KShort* glyphsArr, KFloat* xposArr, KFloat ypos, KNativePointer fontPtr) {
TODO("implement org_jetbrains_skia_TextBlob__1nMakeFromPosH");
(KShort* glyphsArr, KInt glyphsLen, KFloat* xposArr, KFloat ypos, KNativePointer fontPtr) {
SkFont* font = reinterpret_cast<SkFont*>(fontPtr);
SkTextBlob* instance = SkTextBlob::MakeFromPosTextH(
glyphsArr,
glyphsLen * sizeof(short),
xposArr, ypos,
*font,
SkTextEncoding::kGlyphID
).release();
return reinterpret_cast<KNativePointer>(instance);
}
SKIKO_EXPORT KNativePointer org_jetbrains_skia_TextBlob__1nMakeFromPos
(KShort* glyphsArr, KFloat* posArr, KNativePointer fontPtr ) {
TODO("implement org_jetbrains_skia_TextBlob__1nMakeFromPos");
(KShort* glyphsArr, KInt glyphsLen, KFloat* posArr, KNativePointer fontPtr ) {
SkFont* font = reinterpret_cast<SkFont*>(fontPtr);
SkTextBlob* instance = SkTextBlob::MakeFromPosText(
glyphsArr,
glyphsLen * sizeof(KShort),
reinterpret_cast<SkPoint*>(posArr),
*font,
SkTextEncoding::kGlyphID
).release();
return reinterpret_cast<KNativePointer>(instance);
}
SKIKO_EXPORT KNativePointer org_jetbrains_skia_TextBlob__1nMakeFromRSXform
(KShort* glyphsArr, KFloat* xformArr, KNativePointer fontPtr ) {
TODO("implement org_jetbrains_skia_TextBlob__1nMakeFromRSXform");
(KShort* glyphsArr, KInt glyphsLen, KFloat* xformArr, KNativePointer fontPtr ) {
SkFont* font = reinterpret_cast<SkFont*>(fontPtr);
SkTextBlob* instance = SkTextBlob::MakeFromRSXform(
glyphsArr, glyphsLen * sizeof(short),
reinterpret_cast<SkRSXform*>(xformArr),
*font, SkTextEncoding::kGlyphID
).release();
return reinterpret_cast<KNativePointer>(instance);
}
SKIKO_EXPORT KNativePointer org_jetbrains_skia_TextBlob__1nSerializeToData
......@@ -66,54 +111,66 @@ SKIKO_EXPORT KNativePointer org_jetbrains_skia_TextBlob__1nMakeFromData
SKIKO_EXPORT KInt org_jetbrains_skia_TextBlob__1nGetGlyphsLength
(KNativePointer ptr) {
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;
return skikoMpp::textblob::getGlyphsLength(instance);
}
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;
}
skikoMpp::textblob::getGlyphs(instance, resultArray);
}
SKIKO_EXPORT KFloat* org_jetbrains_skia_TextBlob__1nGetPositions
SKIKO_EXPORT KInt org_jetbrains_skia_TextBlob__1nGetPositionsLength
(KNativePointer ptr) {
TODO("implement org_jetbrains_skia_TextBlob__1nGetPositions");
SkTextBlob* instance = reinterpret_cast<SkTextBlob*>(ptr);
return skikoMpp::textblob::getPositionsLength(instance);
}
SKIKO_EXPORT KInt* org_jetbrains_skia_TextBlob__1nGetClusters
(KNativePointer ptr) {
TODO("implement org_jetbrains_skia_TextBlob__1nGetClusters");
SKIKO_EXPORT void org_jetbrains_skia_TextBlob__1nGetPositions
(KNativePointer ptr, KFloat* resultArray) {
SkTextBlob* instance = reinterpret_cast<SkTextBlob*>(ptr);
skikoMpp::textblob::getPositions(instance, resultArray);
}
SKIKO_EXPORT KInteropPointer org_jetbrains_skia_TextBlob__1nGetTightBounds
SKIKO_EXPORT KInt org_jetbrains_skia_TextBlob__1nGetClustersLength
(KNativePointer ptr) {
TODO("implement org_jetbrains_skia_TextBlob__1nGetTightBounds");
SkTextBlob* instance = reinterpret_cast<SkTextBlob*>(ptr);
return skikoMpp::textblob::getClustersLength(instance);
}
SKIKO_EXPORT KInteropPointer org_jetbrains_skia_TextBlob__1nGetBlockBounds
(KNativePointer ptr) {
TODO("implement org_jetbrains_skia_TextBlob__1nGetBlockBounds");
SKIKO_EXPORT KBoolean org_jetbrains_skia_TextBlob__1nGetClusters
(KNativePointer ptr, KInt* clusters) {
SkTextBlob* instance = reinterpret_cast<SkTextBlob*>(ptr);
return skikoMpp::textblob::getClusters(instance, clusters);
}
SKIKO_EXPORT KInteropPointer org_jetbrains_skia_TextBlob__1nGetFirstBaseline
(KNativePointer ptr) {
TODO("implement org_jetbrains_skia_TextBlob__1nGetFirstBaseline");
SKIKO_EXPORT KBoolean org_jetbrains_skia_TextBlob__1nGetTightBounds
(KNativePointer ptr, KFloat* resultArray) {
SkTextBlob* instance = reinterpret_cast<SkTextBlob*>(ptr);
auto bounds = skikoMpp::textblob::getTightBounds(instance);
if (!bounds) return false;
skikoMpp::skrect::serializeAs4Floats(*bounds, resultArray);
return true;
}
SKIKO_EXPORT KInteropPointer org_jetbrains_skia_TextBlob__1nGetLastBaseline
(KNativePointer ptr) {
TODO("implement org_jetbrains_skia_TextBlob__1nGetLastBaseline");
SKIKO_EXPORT KBoolean org_jetbrains_skia_TextBlob__1nGetBlockBounds
(KNativePointer ptr, KFloat* resultArray) {
SkTextBlob* instance = reinterpret_cast<SkTextBlob*>(ptr);
std::unique_ptr<SkRect> bounds = skikoMpp::textblob::getBlockBounds(instance);
if (!bounds) return false;
skikoMpp::skrect::serializeAs4Floats(*bounds, resultArray);
return true;
}
SKIKO_EXPORT KBoolean org_jetbrains_skia_TextBlob__1nGetFirstBaseline
(KNativePointer ptr, KFloat* resultArray) {
return skikoMpp::textblob::getFirstBaseline(reinterpret_cast<SkTextBlob*>(ptr), resultArray);
}
SKIKO_EXPORT KBoolean org_jetbrains_skia_TextBlob__1nGetLastBaseline
(KNativePointer ptr, KFloat* resultArray) {
return skikoMpp::textblob::getLastBaseline(reinterpret_cast<SkTextBlob*>(ptr), resultArray);
}
......@@ -25,6 +25,7 @@
#include "SkShaper.h"
#include "SkLoadICU.h"
#include "unicode/ubrk.h"
#include "mppinterop.h"
#include "types.h"
......@@ -40,20 +41,6 @@ namespace skija {
SkSamplingOptions unpackFrom2Ints(KInt val1, KInt val2);
}
class UtfIndicesConverter {
public:
UtfIndicesConverter(const char* chars8, size_t len8);
UtfIndicesConverter(const SkString& s);
const char* fStart8;
const char* fPtr8;
const char* fEnd8;
uint32_t fPos16;
size_t from16To8(uint32_t i16);
uint32_t from8To16(size_t i8);
};
namespace Rect {
void copyToInterop(const SkRect& rect, KInteropPointer pointer);
}
......
......@@ -116,49 +116,6 @@ namespace skija {
}
}
skija::UtfIndicesConverter::UtfIndicesConverter(const char* chars8, size_t len8):
fStart8(chars8),
fPtr8(chars8),
fEnd8(chars8 + len8),
fPos16(0)
{}
skija::UtfIndicesConverter::UtfIndicesConverter(const SkString& str):
skija::UtfIndicesConverter::UtfIndicesConverter(str.c_str(), str.size())
{}
size_t skija::UtfIndicesConverter::from16To8(uint32_t i16) {
if (i16 >= fPos16) {
// if new i16 >= last fPos16, continue from where we started
} else {
fPtr8 = fStart8;
fPos16 = 0;
}
while (fPtr8 < fEnd8 && fPos16 < i16) {
SkUnichar u = SkUTF::NextUTF8(&fPtr8, fEnd8);
fPos16 += (uint32_t) SkUTF::ToUTF16(u);
}
return fPtr8 - fStart8;
}
uint32_t skija::UtfIndicesConverter::from8To16(size_t i8) {
if (i8 >= (size_t) (fPtr8 - fStart8)) {
// if new i8 >= last fPtr8, continue from where we started
} else {
fPtr8 = fStart8;
fPos16 = 0;
}
while (fPtr8 < fEnd8 && (size_t) (fPtr8 - fStart8) < i8) {
SkUnichar u = SkUTF::NextUTF8(&fPtr8, fEnd8);
fPos16 += (uint32_t) SkUTF::ToUTF16(u);
}
return fPos16;
}
void TODO(const char* message) {
printf("TODO: %s\n", message);
fflush(stdout);
......
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