Skip to content

Commit

Permalink
add abbreviations for invisible characters
Browse files Browse the repository at this point in the history
  • Loading branch information
vadiole committed Dec 23, 2024
1 parent 9ba3f6c commit 4f54d89
Show file tree
Hide file tree
Showing 10 changed files with 134 additions and 24 deletions.
22 changes: 21 additions & 1 deletion app/src/main/kotlin/vadiole/unicode/data/CodePoint.kt
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,26 @@ package vadiole.unicode.data
value class CodePoint(val value: Int) {
val char: String
get() = String(Character.toChars(value))
val abbreviation: String?
get() {
if (isPrintable()) return null
return runCatching {
Character
.getName(value)
?.split(" ")
?.joinToString("") { word ->
word.first().toString()
}
}.getOrNull()
}

private fun isPrintable(): Boolean {
if (Character.isISOControl(value)) {
return false
}
val block: Character.UnicodeBlock? = Character.UnicodeBlock.of(value)
return block != null && block !== Character.UnicodeBlock.SPECIALS
}
}

@JvmInline
Expand Down Expand Up @@ -42,4 +62,4 @@ inline fun CodePointArray.filterMaybe(predicate: (CodePoint) -> Boolean): CodePo

fun CodePointArray.binarySearch(element: CodePoint, fromIndex: Int = 0, toIndex: Int = size): Int {
return java.util.Arrays.binarySearch(storage, fromIndex, toIndex, element.value)
}
}
24 changes: 24 additions & 0 deletions app/src/main/kotlin/vadiole/unicode/data/UnicodeStorage.kt
Original file line number Diff line number Diff line change
Expand Up @@ -69,6 +69,27 @@ class UnicodeStorage(private val context: Context) {
return@withContext result
}

suspend fun getAbbreviations(): Map<CodePoint, String> = withContext(dispatcher) {
val result: Map<CodePoint, String>
openDatabase().rawQuery(queryGetAbbreviations, null).use { cursor ->
val codePointIndex = cursor.getColumnIndex("code_point")
val nameIndex = cursor.getColumnIndex("char_name")
result = HashMap<CodePoint, String>(cursor.count)
while (cursor.moveToNext()) {
val codePoint = CodePoint(cursor.getInt(codePointIndex))
val name = cursor.getString(nameIndex)
val abbreviation = name
.substringBefore(" (")
.split(" ")
.joinToString("") { word ->
word.first().toString()
}
result[codePoint] = abbreviation
}
}
return@withContext result
}

suspend fun getBlocks(): Array<Block> = withContext(dispatcher) {
val result: Array<Block>
openDatabase().rawQuery(queryGetBlocks, null).use { cursor ->
Expand Down Expand Up @@ -162,5 +183,8 @@ class UnicodeStorage(private val context: Context) {
"WHEN name LIKE ? THEN 5 " +
"ELSE 6 END), " +
"id"
private const val queryGetAbbreviations = "SELECT code_point, c.name AS char_name " +
"FROM char c " +
"WHERE code_point <= 159 AND (code_point >= 127 OR (code_point >> 5) = 0)"
}
}
12 changes: 6 additions & 6 deletions app/src/main/kotlin/vadiole/unicode/ui/NavigationView.kt
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,10 @@ import androidx.core.view.doOnLayout
import androidx.dynamicanimation.animation.DynamicAnimation
import androidx.dynamicanimation.animation.SpringAnimation
import androidx.dynamicanimation.animation.SpringForce
import kotlin.math.PI
import kotlin.math.abs
import kotlin.math.atan
import kotlin.math.hypot
import vadiole.unicode.R
import vadiole.unicode.UnicodeApp.Companion.unicodeStorage
import vadiole.unicode.UnicodeApp.Companion.userConfig
Expand All @@ -24,10 +28,6 @@ import vadiole.unicode.utils.extension.frameParams
import vadiole.unicode.utils.extension.isVisible
import vadiole.unicode.utils.extension.matchParent
import vadiole.unicode.utils.extension.with
import kotlin.math.PI
import kotlin.math.abs
import kotlin.math.atan
import kotlin.math.hypot

class NavigationView(context: Context) : FrameLayout(context) {
private val scaledTouchSlop = ViewConfiguration.get(context).scaledTouchSlop
Expand Down Expand Up @@ -100,7 +100,7 @@ class NavigationView(context: Context) : FrameLayout(context) {
val detailsSheet = detailsSheet
if (detailsSheet != null) {
if (codePoint.value >= 0) {
detailsSheet.bind(codePoint = codePoint)
detailsSheet.bind(codePoint = codePoint, abbreviations = tableHelper.abbreviations)
}
visibility = VISIBLE
isDetailsOpenOrOpening = true
Expand Down Expand Up @@ -261,4 +261,4 @@ class NavigationView(context: Context) : FrameLayout(context) {
detailsSheet?.onApplyWindowInsets(insets)
return super.onApplyWindowInsets(insets)
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -6,24 +6,32 @@ import android.graphics.Paint
import android.graphics.Typeface
import android.view.Gravity
import android.view.View
import vadiole.unicode.utils.extension.dp
import androidx.core.graphics.ColorUtils
import kotlin.math.min
import vadiole.unicode.utils.extension.dp

open class SimpleTextView(context: Context) : View(context) {
open class CharTextView(context: Context) : View(context) {
var text: String = ""
set(value) {
field = value
postInvalidate()
}
var abbreviation: String? = null
set(value) {
field = value
postInvalidate()
}
var textSize: Float
get() = textPaint.textSize
set(value) {
abbreviationPaint.textSize = value * 0.5f
textPaint.textSize = value
requestLayout()
}
var textColor: Int
get() = textPaint.color
set(value) {
abbreviationPaint.color = ColorUtils.setAlphaComponent(value, 128)
textPaint.color = value
postInvalidate()
}
Expand All @@ -49,20 +57,30 @@ open class SimpleTextView(context: Context) : View(context) {
letterSpacing = -0.02f
isSubpixelText = true
}
private val abbreviationPaint = Paint(Paint.ANTI_ALIAS_FLAG).apply {
textAlign = Paint.Align.CENTER
textSize = 7f.dp(context)
letterSpacing = -0.02f
isSubpixelText = true
}
private var textPositionY: Float = 0f
private var textPositionX: Float = 0f
private var abbreviationY: Float = 0f

override fun onSizeChanged(w: Int, h: Int, oldw: Int, oldh: Int) {
textPositionY = 0.5f * (h - textPaint.descent() - textPaint.ascent())
abbreviationY = 0.5f * (h - abbreviationPaint.descent() - abbreviationPaint.ascent())
when (gravity and Gravity.HORIZONTAL_GRAVITY_MASK) {
Gravity.CENTER_HORIZONTAL -> {
textPaint.textAlign = Paint.Align.CENTER
textPositionX = (w - paddingLeft - paddingRight) / 2f + paddingLeft
}

Gravity.LEFT -> {
textPaint.textAlign = Paint.Align.LEFT
textPositionX = paddingLeft.toFloat()
}

Gravity.RIGHT -> {
textPaint.textAlign = Paint.Align.RIGHT
textPositionX = w - paddingRight.toFloat()
Expand All @@ -76,7 +94,7 @@ open class SimpleTextView(context: Context) : View(context) {

// wrap content
if (widthMode == MeasureSpec.AT_MOST) {
val textWidth = textPaint.measureText(text).toInt()
val textWidth = measureWidth()
val totalWidth = textWidth + paddingLeft + paddingRight
val finalWidth = min(availableWidth, totalWidth)
val newWidthMeasureSpec = MeasureSpec.makeMeasureSpec(finalWidth, MeasureSpec.EXACTLY)
Expand All @@ -87,8 +105,29 @@ open class SimpleTextView(context: Context) : View(context) {
}

override fun onDraw(canvas: Canvas) {
val abbreviation = abbreviation
if (abbreviation != null) {
drawAbbreviation(canvas, abbreviation)
} else {
drawText(canvas)
}
}

private fun drawText(canvas: Canvas) {
canvas.drawText(text, textPositionX, textPositionY, textPaint)
}

private fun drawAbbreviation(canvas: Canvas, abbreviation: String) {
canvas.drawText(abbreviation, textPositionX, abbreviationY, abbreviationPaint)
}

private fun measureWidth(): Int {
return if (text.isNotEmpty()) {
textPaint.measureText(text).toInt()
} else {
abbreviationPaint.measureText(abbreviation).toInt()
}
}

override fun hasOverlappingRendering() = false
}
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ import android.graphics.Color
import vadiole.unicode.utils.extension.dp
import vadiole.unicode.utils.extension.setPaddingHorizontal

open class TextButton(context: Context) : SimpleTextView(context) {
open class TextButton(context: Context) : CharTextView(context) {
var colors: ColorStateList = ColorStateList.valueOf(Color.DKGRAY)
set(value) {
field = value
Expand Down
11 changes: 5 additions & 6 deletions app/src/main/kotlin/vadiole/unicode/ui/details/DetailsSheet.kt
Original file line number Diff line number Diff line change
Expand Up @@ -15,17 +15,15 @@ import androidx.core.view.updateLayoutParams
import kotlinx.coroutines.delay
import kotlinx.coroutines.launch
import vadiole.unicode.R

import vadiole.unicode.UnicodeApp.Companion.unicodeStorage
import vadiole.unicode.data.CharObj
import vadiole.unicode.data.CodePoint
import vadiole.unicode.ui.common.ActionCell
import vadiole.unicode.ui.common.CharInfoView
import vadiole.unicode.ui.common.CharTextView
import vadiole.unicode.ui.common.Screen
import vadiole.unicode.ui.common.SimpleTextView
import vadiole.unicode.ui.common.SpacerDrawable
import vadiole.unicode.ui.common.SquircleDrawable

import vadiole.unicode.ui.common.roboto_regular
import vadiole.unicode.ui.common.roboto_semibold
import vadiole.unicode.utils.extension.dp
Expand Down Expand Up @@ -87,7 +85,7 @@ class DetailsSheet(
})
private var divider1PositionY = 2f * screenPadding + titleHeight + subtitleHeight
private val charViewHeight = 200.dp(context)
private val charView = SimpleTextView(context).apply {
private val charView = CharTextView(context).apply {
vertical += verticalPadding
layoutParams = frameParams(matchParent, charViewHeight, gravity = Gravity.TOP, marginTop = vertical)
vertical += charViewHeight
Expand Down Expand Up @@ -196,11 +194,12 @@ class DetailsSheet(

// TODO: add strings to localeManager
private val infoNames: Array<String> = arrayOf("Code", "HTML", "CSS", "Version")
fun bind(codePoint: CodePoint) = launch {
fun bind(codePoint: CodePoint, abbreviations: Map<CodePoint, String>) = launch {
val obj: CharObj = unicodeStorage.getCharObj(codePoint) ?: return@launch
title.text = obj.name
subtitle.text = obj.blockName
charView.text = obj.char
charView.abbreviation = abbreviations[codePoint]
infoViews.forEachIndexed { index, infoView ->
infoView.name = infoNames[index]
infoView.value = obj.infoValues[index]
Expand All @@ -223,4 +222,4 @@ class DetailsSheet(
dividerPaint,
)
}
}
}
29 changes: 25 additions & 4 deletions app/src/main/kotlin/vadiole/unicode/ui/table/CharRow.kt
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import android.view.HapticFeedbackConstants
import android.view.MotionEvent
import android.view.View
import android.view.ViewConfiguration
import androidx.core.graphics.ColorUtils
import kotlin.math.floor
import vadiole.unicode.R
import vadiole.unicode.data.CodePoint
Expand All @@ -17,13 +18,18 @@ import vadiole.unicode.utils.extension.dp
class CharRow(
context: Context,
private val count: Int,
private val delegate: Delegate
private val delegate: Delegate,
) : View(context) {
private var codePoints: CodePointArray = CodePointArray(0)
private var abbreviations: Map<CodePoint, String> = emptyMap()
private val charPaint = Paint(Paint.ANTI_ALIAS_FLAG).apply {
textAlign = Paint.Align.CENTER
isSubpixelText = true
}
private val abbreviationPaint = Paint(Paint.ANTI_ALIAS_FLAG).apply {
textAlign = Paint.Align.CENTER
isSubpixelText = true
}
private val longClickRunnable = object : Runnable {
override fun run() {
val index = actionDownIndex
Expand All @@ -41,24 +47,29 @@ class CharRow(
private val longClickDuration: Long = ViewConfiguration.getLongPressTimeout().toLong()
private val ripplePaint = Paint(Paint.ANTI_ALIAS_FLAG)
private val charSize = 144f.dp(context) / count
private val abbreviationSize = charSize * 0.5f
private val charRipples: BooleanArray = BooleanArray(count)
private val charCoordsX: FloatArray = FloatArray(count)
private var charCoordY: Float = 0f
private var abbreviationCoordY: Float = 0f
private var charPivotY: Float = 0f
private var rippleRadius: Float = 200f.dp(context) / count
private var actionDownIndex = -1
private var highlightIndex = -1

init {
charPaint.color = context.getColor(R.color.windowTextPrimary)
abbreviationPaint.color = ColorUtils.setAlphaComponent(context.getColor(R.color.windowTextPrimary), 128)
ripplePaint.color = context.getColor(R.color.windowSurfacePressed)
charPaint.textSize = charSize
abbreviationPaint.textSize = abbreviationSize
isClickable = true
isFocusable = true
}

fun bind(codePoints: CodePointArray) {
fun bind(codePoints: CodePointArray, abbreviations: Map<CodePoint, String>) {
this.codePoints = codePoints
this.abbreviations = abbreviations
charRipples.fill(false)
invalidate()
}
Expand Down Expand Up @@ -128,6 +139,7 @@ class CharRow(
charCoordsX[index] = singleCharWidth * index + centerOffsetX
}
charCoordY = (h - charPaint.descent() - charPaint.ascent()) / 2
abbreviationCoordY = (h - abbreviationPaint.descent() - abbreviationPaint.ascent()) / 2
charPivotY = h / 2f
}

Expand All @@ -140,13 +152,22 @@ class CharRow(
@SuppressLint("MissingSuperCall")
override fun draw(canvas: Canvas) {
codePoints.forEachIndexed { index, codePoint ->
val text = codePoint.char
val charCoordX = charCoordsX[index]
val charPivotX = charCoordsX[index]
if (charRipples[index]) {
canvas.drawCircle(charPivotX, charPivotY, rippleRadius, ripplePaint)
}
canvas.drawText(text, charCoordX, charCoordY, charPaint)
drawChar(canvas, codePoint, charCoordX, charCoordY)
}
}

private fun drawChar(canvas: Canvas, codePoint: CodePoint, charCoordX: Float, charCoordY: Float) {
val abbreviation = abbreviations[codePoint]
if (abbreviation != null) {
canvas.drawText(abbreviation, charCoordX, abbreviationCoordY, abbreviationPaint)
} else {
Character.UnicodeBlock.SPECIALS
canvas.drawText(codePoint.char, charCoordX, charCoordY, charPaint)
}
}

Expand Down
5 changes: 5 additions & 0 deletions app/src/main/kotlin/vadiole/unicode/ui/table/TableHelper.kt
Original file line number Diff line number Diff line change
Expand Up @@ -15,11 +15,16 @@ import vadiole.unicode.utils.extension.worker
class TableHelper(private val unicodeStorage: UnicodeStorage, private val userConfig: UserConfig) {
var totalChars = UnicodeStorage.totalCharacters
var tableChars: CodePointArray = CodePointArray(0)
var abbreviations: Map<CodePoint, String> = emptyMap()
var blocks: Array<Block> = emptyArray()
private val glyphPaint = Paint()
private var lastBlock: Block? = null
private var lastBlockIndex = -1

suspend fun loadAbbreviations() = worker {
abbreviations = unicodeStorage.getAbbreviations()
}

suspend fun loadChars(fast: Boolean) = worker {
val count = if (fast) 256 else -1
var codePoints = unicodeStorage.getCodePoints(count)
Expand Down
Loading

0 comments on commit 4f54d89

Please sign in to comment.