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

Add a getter and setter for ParagraphStyle.replaceTabCharacters (#1036)

This change will be used in Compose Multiplatform to fix
https://youtrack.jetbrains.com/issue/CMP-6589/wasm-can-not-render-tab-symbol

The difference can be seen here:
**Good**: https://fiddle.skia.org/c/24fb7ebc20eec08077d84b9fe37ceb4f -
`paraStyle.setReplaceTabCharacters(true);`
**Bad**: https://fiddle.skia.org/c/fbcb4a52786d63ed42a85d05d5472164 -
`paraStyle.setReplaceTabCharacters(false);`
parent 4d4dc810
......@@ -15,13 +15,14 @@ class EmojiStory : SkikoRenderDelegate {
private val platformYOffset = if (hostOs == OS.Ios) 50f else 5f
private val style = ParagraphStyle().apply {
replaceTabCharacters = true
textStyle = TextStyle().apply {
this.fontSize = 16.0f
}.setColor(0xFF000000.toInt())
}
private val paragraph = ParagraphBuilder(style, fontCollection)
.addText("\uD83D\uDCE1 Antenna - 天线\n")
.addText("\uD83D\uDCE1\tAntenna\t-\t天线\t\n")
.addText("⁉\uFE0F 〰\uFE0F ⁉\uFE0F\n")
.addText("\uD83D\uDE32 \uD83E\uDD14 \uD83D\uDCA1\n")
.addText("\uD83C\uDF0D Earth - 地球\n")
......
......@@ -29,6 +29,27 @@ class ParagraphStyle : Managed(ParagraphStyle_nMake(), _FinalizerHolder.PTR) {
}
}
/**
* Controls whether tab characters are replaced with spaces in text layout.
*
* Note:
* All the platforms except web do not render tab characters even when this is 'false', but put a space instead.
* With this property we can make web behave similarly, by setting true.
*/
var replaceTabCharacters: Boolean
get() = try {
Stats.onNativeCall()
_nGetReplaceTabCharacters(_ptr)
} finally {
reachabilityBarrier(this)
}
set(value) = try {
Stats.onNativeCall()
_nSetReplaceTabCharacters(_ptr, value)
} finally {
reachabilityBarrier(this)
}
var strutStyle: StrutStyle
get() = try {
Stats.onNativeCall()
......@@ -350,3 +371,11 @@ private external fun _nSetTextIndent(ptr: NativePointer, firstLine: Float, restL
@ExternalSymbolName("org_jetbrains_skia_paragraph_ParagraphStyle__1nGetTextIndent")
@ModuleImport("./skiko.mjs", "org_jetbrains_skia_paragraph_ParagraphStyle__1nGetTextIndent")
private external fun _nGetTextIndent(ptr: NativePointer, result: InteropPointer)
@ExternalSymbolName("org_jetbrains_skia_paragraph_ParagraphStyle__1nGetReplaceTabCharacters")
@ModuleImport("./skiko.mjs", "org_jetbrains_skia_paragraph_ParagraphStyle__1nGetReplaceTabCharacters")
private external fun _nGetReplaceTabCharacters(ptr: NativePointer): Boolean
@ExternalSymbolName("org_jetbrains_skia_paragraph_ParagraphStyle__1nSetReplaceTabCharacters")
@ModuleImport("./skiko.mjs", "org_jetbrains_skia_paragraph_ParagraphStyle__1nSetReplaceTabCharacters")
private external fun _nSetReplaceTabCharacters(ptr: NativePointer, value: Boolean)
\ No newline at end of file
......@@ -5,14 +5,11 @@ 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.*
import kotlin.math.ceil
import kotlin.math.floor
import kotlin.math.truncate
import kotlin.test.Test
import kotlin.test.assertContentEquals
import kotlin.test.assertEquals
import kotlin.test.assertTrue
import org.jetbrains.skiko.tests.SkipJsTarget
import org.jetbrains.skiko.tests.SkipNativeTarget
import org.jetbrains.skiko.tests.SkipWasmTarget
import org.jetbrains.skiko.tests.runTest
import kotlin.test.*
class ParagraphTest {
private val fontCollection = suspend {
......@@ -210,4 +207,13 @@ class ParagraphTest {
testWraps(isApplyRoundingHackEnabled = false, unexpectedWrapsPresent = false)
testWraps(isApplyRoundingHackEnabled = true, unexpectedWrapsPresent = true)
}
@Test
fun paragraphStyleCanChangeReplaceTab() {
ParagraphStyle().use { it ->
assertFalse(it.replaceTabCharacters)
it.replaceTabCharacters = true
assertTrue(it.replaceTabCharacters)
}
}
}
package org.jetbrains.skia
import org.jetbrains.skia.impl.use
import org.jetbrains.skia.paragraph.*
import kotlin.math.roundToInt
import kotlin.test.Test
import kotlin.test.assertEquals
import kotlin.test.assertTrue
/**
* The tests here draw a Paragraph on canvas and check the pixels.
* Ideally it could be a screenshot test, but we don't have them on web yet.
*/
class ParagraphWebTest {
private val fontCollection = FontCollection().setDefaultFontManager(FontMgr.default)
@Test
fun paragraphWithTabulationReplacingTabs1() {
val paragraphStyle = ParagraphStyle().apply {
replaceTabCharacters = true
textStyle = TextStyle().apply {
this.fontSize = 32.0f
}.setColor(Color.BLACK)
}
val paragraph = ParagraphBuilder(paragraphStyle, fontCollection).use {
it.addText("\t\t\t .")
it.build().layout(100.0f)
}
val surface = Surface.makeRasterN32Premul(100, 50)
surface.canvas.clear(Color.WHITE)
paragraph.paint(surface.canvas, 0.0f, 0.0f)
surface.makeImageSnapshot().use { image ->
val bitmap = Bitmap.makeFromImage(image)
assertTrue(bitmap.height == 50)
assertTrue(bitmap.width == 100)
val right = paragraph.lineMetrics[0].right.roundToInt()
val baselineY = paragraph.lineMetrics[0].baseline.roundToInt()
var notAllWhite = false
for (x in (right - 5)until (right + 5)) {
for (y in (baselineY - 5)until (baselineY + 5)) {
if (bitmap.getColor(x, y) != Color.WHITE) {
notAllWhite = true
break
}
}
}
assertTrue(notAllWhite, "Expected some non-white pixels for a dot.")
val rects = paragraph.getRectsForRange(0, 4, RectHeightMode.TIGHT, RectWidthMode.TIGHT)
val tabsRectRight = rects[0].rect.right.roundToInt()
assertTrue(tabsRectRight > paragraph.lineMetrics[0].left &&
tabsRectRight < paragraph.lineMetrics[0].right
)
var countNotWhite = 0
for (x in 0 until tabsRectRight) {
for (y in 0 until bitmap.height) {
if (bitmap.getColor(x, y) != Color.WHITE) {
countNotWhite++
}
}
}
assertEquals(0, countNotWhite, "Expected all pixels for tabs to be white.")
}
}
@Test
fun paragraphWithTabulationNoReplacingTabs() {
val paragraphStyle = ParagraphStyle().apply {
replaceTabCharacters = false
textStyle = TextStyle().apply {
this.fontSize = 32.0f
}.setColor(Color.BLACK)
}
val paragraph = ParagraphBuilder(paragraphStyle, fontCollection).use {
it.addText("\t\t\t .")
it.build().layout(100.0f)
}
val surface = Surface.makeRasterN32Premul(100, 50)
surface.canvas.clear(Color.WHITE)
paragraph.paint(surface.canvas, 0.0f, 0.0f)
surface.makeImageSnapshot().use { image ->
val bitmap = Bitmap.makeFromImage(image)
assertTrue(bitmap.height == 50)
assertTrue(bitmap.width == 100)
val rects = paragraph.getRectsForRange(0, 4, RectHeightMode.TIGHT, RectWidthMode.TIGHT)
val tabsRect = rects[0].rect
val tabsRectRight = tabsRect.right.roundToInt()
assertTrue(tabsRectRight > paragraph.lineMetrics[0].left &&
tabsRectRight < paragraph.lineMetrics[0].right
)
var countNotWhite = 0
for (x in 0 until tabsRectRight) {
for (y in 0 until bitmap.height) {
if (bitmap.getColor(x, y) != Color.WHITE) {
countNotWhite++
}
}
}
assertTrue(countNotWhite < (tabsRect.width * tabsRect.height) / 2, "Too many non-white pixels for tabs.")
assertTrue(countNotWhite > 300, "Expected at least 300 non-white pixels for tabs (tofu glyphs), got $countNotWhite")
}
}
}
\ No newline at end of file
......@@ -23,6 +23,19 @@ extern "C" JNIEXPORT jlong JNICALL Java_org_jetbrains_skia_paragraph_ParagraphSt
return reinterpret_cast<jlong>(instance);
}
extern "C" JNIEXPORT jboolean JNICALL Java_org_jetbrains_skia_paragraph_ParagraphStyleKt__1nGetReplaceTabCharacters
(JNIEnv* env, jclass jclass, jlong ptr) {
ParagraphStyle* instance = reinterpret_cast<ParagraphStyle*>(static_cast<uintptr_t>(ptr));
return instance->getReplaceTabCharacters();
}
extern "C" JNIEXPORT void JNICALL Java_org_jetbrains_skia_paragraph_ParagraphStyleKt__1nSetReplaceTabCharacters
(JNIEnv* env, jclass jclass, jlong ptr, jboolean value) {
ParagraphStyle* instance = reinterpret_cast<ParagraphStyle*>(static_cast<uintptr_t>(ptr));
instance->setReplaceTabCharacters(value);
}
extern "C" JNIEXPORT jboolean JNICALL Java_org_jetbrains_skia_paragraph_ParagraphStyleKt__1nEquals
(JNIEnv* env, jclass jclass, jlong ptr, jlong otherPtr) {
ParagraphStyle* instance = reinterpret_cast<ParagraphStyle*>(static_cast<uintptr_t>(ptr));
......
......@@ -20,6 +20,19 @@ SKIKO_EXPORT KNativePointer org_jetbrains_skia_paragraph_ParagraphStyle__1nMake
return reinterpret_cast<KNativePointer>(instance);
}
SKIKO_EXPORT KBoolean org_jetbrains_skia_paragraph_ParagraphStyle__1nGetReplaceTabCharacters
(KNativePointer ptr) {
ParagraphStyle* instance = reinterpret_cast<ParagraphStyle*>((ptr));
return instance->getReplaceTabCharacters();
}
SKIKO_EXPORT void org_jetbrains_skia_paragraph_ParagraphStyle__1nSetReplaceTabCharacters
(KNativePointer ptr, KBoolean value) {
ParagraphStyle* instance = reinterpret_cast<ParagraphStyle*>((ptr));
instance->setReplaceTabCharacters(value);
}
SKIKO_EXPORT KBoolean org_jetbrains_skia_paragraph_ParagraphStyle__1nEquals
(KNativePointer ptr, KNativePointer otherPtr) {
ParagraphStyle* instance = reinterpret_cast<ParagraphStyle*>((ptr));
......
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