mirror of
https://github.com/signalapp/Signal-Android.git
synced 2026-04-21 09:20:19 +01:00
Add defaults for script/text font pairings and guessing of script based on body contents.
Co-authored-by: Alex Hart <alex@signal.org>
This commit is contained in:
@@ -28,25 +28,25 @@ data class FontManifest(
|
||||
* @param chineseSimplified Hans Script Fonts
|
||||
*/
|
||||
data class FontScripts(
|
||||
@JsonProperty("latin-extended") val latinExtended: FontScript,
|
||||
@JsonProperty("cyrillic-extended") val cyrillicExtended: FontScript,
|
||||
val devanagari: FontScript,
|
||||
@JsonProperty("chinese-traditional-hk") val chineseTraditionalHk: FontScript,
|
||||
@JsonProperty("chinese-traditional") val chineseTraditional: FontScript,
|
||||
@JsonProperty("chinese-simplified") val chineseSimplified: FontScript,
|
||||
val arabic: FontScript,
|
||||
val japanese: FontScript,
|
||||
@JsonProperty("latin-extended") val latinExtended: FontScript?,
|
||||
@JsonProperty("cyrillic-extended") val cyrillicExtended: FontScript?,
|
||||
val devanagari: FontScript?,
|
||||
@JsonProperty("chinese-traditional-hk") val chineseTraditionalHk: FontScript?,
|
||||
@JsonProperty("chinese-traditional") val chineseTraditional: FontScript?,
|
||||
@JsonProperty("chinese-simplified") val chineseSimplified: FontScript?,
|
||||
val arabic: FontScript?,
|
||||
val japanese: FontScript?,
|
||||
)
|
||||
|
||||
/**
|
||||
* A collection of fonts for a specific script
|
||||
*/
|
||||
data class FontScript(
|
||||
val regular: String,
|
||||
val bold: String,
|
||||
val serif: String,
|
||||
val script: String,
|
||||
val condensed: String
|
||||
val regular: String?,
|
||||
val bold: String?,
|
||||
val serif: String?,
|
||||
val script: String?,
|
||||
val condensed: String?
|
||||
)
|
||||
|
||||
companion object {
|
||||
@@ -76,7 +76,7 @@ data class FontManifest(
|
||||
objectMapper.readValue(it, FontManifest::class.java)
|
||||
}
|
||||
} catch (e: Exception) {
|
||||
Log.w(TAG, "Failed to load manifest from disk")
|
||||
Log.w(TAG, "Failed to load manifest from disk", e)
|
||||
return null
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2,41 +2,44 @@ package org.thoughtcrime.securesms.fonts
|
||||
|
||||
import android.content.Context
|
||||
import android.graphics.Typeface
|
||||
import android.os.Build
|
||||
import org.signal.imageeditor.core.Renderer
|
||||
import org.signal.imageeditor.core.RendererContext
|
||||
import org.thoughtcrime.securesms.util.FutureTaskListener
|
||||
import org.thoughtcrime.securesms.util.LocaleUtil
|
||||
import java.util.Locale
|
||||
import java.util.concurrent.ExecutionException
|
||||
|
||||
/**
|
||||
* RenderContext TypefaceProvider that provides typefaces using TextFont.
|
||||
*/
|
||||
object FontTypefaceProvider : RendererContext.TypefaceProvider {
|
||||
override fun getSelectedTypeface(context: Context, renderer: Renderer, invalidate: RendererContext.Invalidate): Typeface {
|
||||
return getTypeface()
|
||||
// TODO [cody] Need to rework Fonts.kt to not hit network on main, reverting to old typeface for now
|
||||
// return when (val fontResult = Fonts.resolveFont(context, Locale.getDefault(), TextFont.BOLD)) {
|
||||
// is Fonts.FontResult.Immediate -> fontResult.typeface
|
||||
// is Fonts.FontResult.Async -> {
|
||||
// fontResult.future.addListener(object : FutureTaskListener<Typeface> {
|
||||
// override fun onSuccess(result: Typeface?) {
|
||||
// invalidate.onInvalidate(renderer)
|
||||
// }
|
||||
//
|
||||
// override fun onFailure(exception: ExecutionException?) = Unit
|
||||
// })
|
||||
//
|
||||
// fontResult.placeholder
|
||||
// }
|
||||
// }
|
||||
}
|
||||
class FontTypefaceProvider : RendererContext.TypefaceProvider {
|
||||
|
||||
private fun getTypeface(): Typeface {
|
||||
return if (Build.VERSION.SDK_INT < 26) {
|
||||
Typeface.create(Typeface.DEFAULT, Typeface.BOLD)
|
||||
} else {
|
||||
Typeface.Builder("")
|
||||
.setFallback("sans-serif")
|
||||
.setWeight(900)
|
||||
.build()
|
||||
private var cachedTypeface: Typeface? = null
|
||||
private var cachedLocale: Locale? = null
|
||||
|
||||
override fun getSelectedTypeface(context: Context, renderer: Renderer, invalidate: RendererContext.Invalidate): Typeface {
|
||||
val typeface = cachedTypeface
|
||||
if (typeface != null && cachedLocale == LocaleUtil.getFirstLocale()) {
|
||||
return typeface
|
||||
}
|
||||
|
||||
return when (val fontResult = Fonts.resolveFont(context, TextFont.BOLD)) {
|
||||
is Fonts.FontResult.Immediate -> {
|
||||
cachedTypeface = fontResult.typeface
|
||||
cachedLocale = LocaleUtil.getFirstLocale()
|
||||
fontResult.typeface
|
||||
}
|
||||
is Fonts.FontResult.Async -> {
|
||||
fontResult.future.addListener(object : FutureTaskListener<Typeface> {
|
||||
override fun onSuccess(result: Typeface?) {
|
||||
invalidate.onInvalidate(renderer)
|
||||
}
|
||||
|
||||
override fun onFailure(exception: ExecutionException?) = Unit
|
||||
})
|
||||
|
||||
fontResult.placeholder
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -7,6 +7,7 @@ import org.signal.core.util.concurrent.SignalExecutors
|
||||
import org.signal.core.util.logging.Log
|
||||
import org.thoughtcrime.securesms.s3.S3
|
||||
import org.thoughtcrime.securesms.util.ListenableFutureTask
|
||||
import org.thoughtcrime.securesms.util.LocaleUtil
|
||||
import java.io.File
|
||||
import java.util.Collections
|
||||
import java.util.Locale
|
||||
@@ -46,15 +47,16 @@ object Fonts {
|
||||
}
|
||||
|
||||
/**
|
||||
* Attempts to retrieve a Typeface for the given font / locale combination
|
||||
* Attempts to retrieve a Typeface for the given font / guessed script and default locales combination
|
||||
*
|
||||
* @param context An application context
|
||||
* @param locale The locale the content will be displayed in
|
||||
* @param font The desired font
|
||||
* @param guessedScript The script likely being used based on text content
|
||||
*
|
||||
* @return a FontResult that represents either a Typeface or a task retrieving a Typeface.
|
||||
*/
|
||||
@WorkerThread
|
||||
fun resolveFont(context: Context, locale: Locale, font: TextFont): FontResult {
|
||||
fun resolveFont(context: Context, font: TextFont, guessedScript: SupportedScript = SupportedScript.UNKNOWN): FontResult {
|
||||
synchronized(this) {
|
||||
val errorFallback = FontResult.Immediate(Typeface.create(font.fallbackFamily, font.fallbackStyle))
|
||||
val version = FontVersion.get(context)
|
||||
@@ -66,11 +68,21 @@ object Fonts {
|
||||
|
||||
Log.d(TAG, "Loaded manifest.")
|
||||
|
||||
val fontScript = resolveScriptNameFromLocale(locale, manifest) ?: return errorFallback
|
||||
val localeDefaults: List<Locale> = LocaleUtil.getLocaleDefaults()
|
||||
val supportedScript: SupportedScript = getSupportedScript(localeDefaults, guessedScript)
|
||||
val fontScript = resolveFontScriptFromScriptName(supportedScript, manifest)
|
||||
if (fontScript == null) {
|
||||
Log.d(TAG, "Manifest does not have an entry for $supportedScript. Using default.")
|
||||
return FontResult.Immediate(getDefaultFontForScriptAndStyle(supportedScript, font))
|
||||
}
|
||||
|
||||
Log.d(TAG, "Loaded script for locale.")
|
||||
|
||||
val fontNetworkPath = getScriptPath(font, fontScript)
|
||||
if (fontNetworkPath == null) {
|
||||
Log.d(TAG, "Manifest does not contain a network path for $supportedScript. Using default.")
|
||||
return FontResult.Immediate(getDefaultFontForScriptAndStyle(supportedScript, font))
|
||||
}
|
||||
|
||||
val fontLocalPath = FontFileMap.getNameOnDisk(context, version, fontNetworkPath)
|
||||
|
||||
@@ -80,7 +92,7 @@ object Fonts {
|
||||
}
|
||||
|
||||
val fontDownloadKey = FontDownloadKey(
|
||||
version, locale, font
|
||||
version, supportedScript, font
|
||||
)
|
||||
|
||||
val taskInProgress = taskCache[fontDownloadKey]
|
||||
@@ -93,7 +105,7 @@ object Fonts {
|
||||
} else {
|
||||
Log.d(TAG, "Could not find a task in progress. Returning new async.")
|
||||
val newTask = ListenableFutureTask {
|
||||
val newLocalPath = downloadFont(context, locale, font, version, manifest)
|
||||
val newLocalPath = downloadFont(context, supportedScript, font, version, manifest)
|
||||
Log.d(TAG, "Finished download, $newLocalPath")
|
||||
|
||||
val typeface = newLocalPath?.let { loadFontIntoTypeface(context, version, it) } ?: errorFallback.typeface
|
||||
@@ -112,6 +124,69 @@ object Fonts {
|
||||
}
|
||||
}
|
||||
|
||||
private fun getDefaultFontForScriptAndStyle(supportedScript: SupportedScript, font: TextFont): Typeface {
|
||||
return when (supportedScript) {
|
||||
SupportedScript.CYRILLIC -> {
|
||||
when (font) {
|
||||
TextFont.REGULAR -> Typeface.SANS_SERIF
|
||||
TextFont.BOLD -> Typeface.create(Typeface.SANS_SERIF, Typeface.BOLD)
|
||||
TextFont.SERIF -> Typeface.SERIF
|
||||
TextFont.SCRIPT -> TypefaceHelper.typefaceFor(TypefaceHelper.Family.SERIF, "semibold", TypefaceHelper.Weight.SEMI_BOLD)
|
||||
TextFont.CONDENSED -> TypefaceHelper.typefaceFor(TypefaceHelper.Family.SANS_SERIF, "light", TypefaceHelper.Weight.LIGHT)
|
||||
}
|
||||
}
|
||||
SupportedScript.DEVANAGARI -> {
|
||||
when (font) {
|
||||
TextFont.REGULAR -> Typeface.SANS_SERIF
|
||||
TextFont.BOLD -> Typeface.create(Typeface.SANS_SERIF, Typeface.BOLD)
|
||||
TextFont.SERIF -> Typeface.SANS_SERIF
|
||||
TextFont.SCRIPT -> Typeface.create(Typeface.SANS_SERIF, Typeface.BOLD)
|
||||
TextFont.CONDENSED -> TypefaceHelper.typefaceFor(TypefaceHelper.Family.SANS_SERIF, "light", TypefaceHelper.Weight.LIGHT)
|
||||
}
|
||||
}
|
||||
SupportedScript.CHINESE_TRADITIONAL_HK,
|
||||
SupportedScript.CHINESE_TRADITIONAL,
|
||||
SupportedScript.CHINESE_SIMPLIFIED,
|
||||
SupportedScript.UNKNOWN_CJK -> {
|
||||
when (font) {
|
||||
TextFont.REGULAR -> Typeface.SANS_SERIF
|
||||
TextFont.BOLD -> TypefaceHelper.typefaceFor(TypefaceHelper.Family.SANS_SERIF, "semibold", TypefaceHelper.Weight.SEMI_BOLD)
|
||||
TextFont.SERIF -> TypefaceHelper.typefaceFor(TypefaceHelper.Family.SANS_SERIF, "thin", TypefaceHelper.Weight.THIN)
|
||||
TextFont.SCRIPT -> TypefaceHelper.typefaceFor(TypefaceHelper.Family.SANS_SERIF, "light", TypefaceHelper.Weight.LIGHT)
|
||||
TextFont.CONDENSED -> TypefaceHelper.typefaceFor(TypefaceHelper.Family.SANS_SERIF, "demilight", TypefaceHelper.Weight.DEMI_LIGHT)
|
||||
}
|
||||
}
|
||||
SupportedScript.ARABIC -> {
|
||||
when (font) {
|
||||
TextFont.REGULAR -> Typeface.SANS_SERIF
|
||||
TextFont.BOLD -> Typeface.create(Typeface.SANS_SERIF, Typeface.BOLD)
|
||||
TextFont.SERIF -> Typeface.SERIF
|
||||
TextFont.SCRIPT -> Typeface.create(Typeface.SERIF, Typeface.BOLD)
|
||||
TextFont.CONDENSED -> TypefaceHelper.typefaceFor(TypefaceHelper.Family.SANS_SERIF, "black", TypefaceHelper.Weight.BLACK)
|
||||
}
|
||||
}
|
||||
SupportedScript.JAPANESE -> {
|
||||
when (font) {
|
||||
TextFont.REGULAR -> Typeface.SANS_SERIF
|
||||
TextFont.BOLD -> Typeface.create(Typeface.SANS_SERIF, Typeface.BOLD)
|
||||
TextFont.SERIF -> Typeface.SERIF
|
||||
TextFont.SCRIPT -> Typeface.create(Typeface.SERIF, Typeface.BOLD)
|
||||
TextFont.CONDENSED -> TypefaceHelper.typefaceFor(TypefaceHelper.Family.SANS_SERIF, "medium", TypefaceHelper.Weight.MEDIUM)
|
||||
}
|
||||
}
|
||||
SupportedScript.LATIN,
|
||||
SupportedScript.UNKNOWN -> {
|
||||
when (font) {
|
||||
TextFont.REGULAR -> Typeface.SANS_SERIF
|
||||
TextFont.BOLD -> Typeface.create(Typeface.SANS_SERIF, Typeface.BOLD)
|
||||
TextFont.SERIF -> Typeface.SERIF
|
||||
TextFont.SCRIPT -> Typeface.create(Typeface.SERIF, Typeface.BOLD)
|
||||
TextFont.CONDENSED -> TypefaceHelper.typefaceFor(TypefaceHelper.Family.SANS_SERIF, "black", TypefaceHelper.Weight.BLACK)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@WorkerThread
|
||||
private fun loadFontIntoTypeface(context: Context, fontVersion: FontVersion, fontLocalPath: String): Typeface? {
|
||||
return try {
|
||||
@@ -146,9 +221,13 @@ object Fonts {
|
||||
* Downloads the given font file from S3
|
||||
*/
|
||||
@WorkerThread
|
||||
private fun downloadFont(context: Context, locale: Locale, font: TextFont, fontVersion: FontVersion, fontManifest: FontManifest): String? {
|
||||
val script: FontManifest.FontScript = resolveScriptNameFromLocale(locale, fontManifest) ?: return null
|
||||
val path = getScriptPath(font, script)
|
||||
private fun downloadFont(context: Context, supportedScript: SupportedScript?, font: TextFont, fontVersion: FontVersion, fontManifest: FontManifest): String? {
|
||||
if (supportedScript == null) {
|
||||
return null
|
||||
}
|
||||
|
||||
val script: FontManifest.FontScript = resolveFontScriptFromScriptName(supportedScript, fontManifest) ?: return null
|
||||
val path = getScriptPath(font, script) ?: return null
|
||||
val networkPath = "$BASE_STATIC_BUCKET_URL/${fontVersion.id}/$path"
|
||||
val localUUID = UUID.randomUUID().toString()
|
||||
val localPath = "${fontVersion.path}/" + localUUID
|
||||
@@ -162,7 +241,7 @@ object Fonts {
|
||||
}
|
||||
}
|
||||
|
||||
private fun getScriptPath(font: TextFont, script: FontManifest.FontScript): String {
|
||||
private fun getScriptPath(font: TextFont, script: FontManifest.FontScript): String? {
|
||||
return when (font) {
|
||||
TextFont.REGULAR -> script.regular
|
||||
TextFont.BOLD -> script.bold
|
||||
@@ -172,22 +251,62 @@ object Fonts {
|
||||
}
|
||||
}
|
||||
|
||||
private fun resolveScriptNameFromLocale(locale: Locale, fontManifest: FontManifest): FontManifest.FontScript? {
|
||||
val fontScript: FontManifest.FontScript = when (ScriptUtil.getScript(locale).apply { Log.d(TAG, "Getting Script for $this") }) {
|
||||
ScriptUtil.LATIN -> fontManifest.scripts.latinExtended
|
||||
ScriptUtil.ARABIC -> fontManifest.scripts.arabic
|
||||
ScriptUtil.CHINESE_SIMPLIFIED -> fontManifest.scripts.chineseSimplified
|
||||
ScriptUtil.CHINESE_TRADITIONAL -> fontManifest.scripts.chineseTraditional
|
||||
ScriptUtil.CYRILLIC -> fontManifest.scripts.cyrillicExtended
|
||||
ScriptUtil.DEVANAGARI -> fontManifest.scripts.devanagari
|
||||
ScriptUtil.JAPANESE -> fontManifest.scripts.japanese
|
||||
else -> return null
|
||||
private fun getSupportedScript(locales: List<Locale>, guessedScript: SupportedScript): SupportedScript {
|
||||
if (guessedScript != SupportedScript.UNKNOWN && guessedScript != SupportedScript.UNKNOWN_CJK) {
|
||||
return guessedScript
|
||||
} else if (guessedScript == SupportedScript.UNKNOWN_CJK) {
|
||||
val likelyScript: SupportedScript? = locales.mapNotNull {
|
||||
try {
|
||||
when (it.isO3Country) {
|
||||
"HKG" -> SupportedScript.CHINESE_TRADITIONAL_HK
|
||||
"CHN" -> SupportedScript.CHINESE_SIMPLIFIED
|
||||
"TWN" -> SupportedScript.CHINESE_TRADITIONAL
|
||||
"JPN" -> SupportedScript.JAPANESE
|
||||
else -> null
|
||||
}
|
||||
} catch (e: java.util.MissingResourceException) {
|
||||
Log.w(TAG, "Unable to get ISO-3 country code for: $it")
|
||||
null
|
||||
}
|
||||
}.firstOrNull()
|
||||
|
||||
if (likelyScript != null) {
|
||||
return likelyScript
|
||||
}
|
||||
}
|
||||
|
||||
return if (fontScript == fontManifest.scripts.chineseSimplified && locale.isO3Country == "HKG") {
|
||||
fontManifest.scripts.chineseTraditionalHk
|
||||
val locale = locales.first()
|
||||
val supportedScript: SupportedScript = when (ScriptUtil.getScript(locale).also { Log.d(TAG, "Getting Script for $it") }) {
|
||||
ScriptUtil.LATIN -> SupportedScript.LATIN
|
||||
ScriptUtil.ARABIC -> SupportedScript.ARABIC
|
||||
ScriptUtil.CHINESE_SIMPLIFIED -> SupportedScript.CHINESE_SIMPLIFIED
|
||||
ScriptUtil.CHINESE_TRADITIONAL -> SupportedScript.CHINESE_TRADITIONAL
|
||||
ScriptUtil.CYRILLIC -> SupportedScript.CYRILLIC
|
||||
ScriptUtil.DEVANAGARI -> SupportedScript.DEVANAGARI
|
||||
ScriptUtil.JAPANESE -> SupportedScript.JAPANESE
|
||||
else -> SupportedScript.UNKNOWN
|
||||
}
|
||||
|
||||
return if (supportedScript == SupportedScript.CHINESE_SIMPLIFIED && locale.isO3Country == "HKG") {
|
||||
SupportedScript.CHINESE_TRADITIONAL_HK
|
||||
} else {
|
||||
fontScript
|
||||
supportedScript
|
||||
}
|
||||
}
|
||||
|
||||
private fun resolveFontScriptFromScriptName(supportedScript: SupportedScript?, fontManifest: FontManifest): FontManifest.FontScript? {
|
||||
return when (supportedScript.also { Log.d(TAG, "Getting Script for $it") }) {
|
||||
SupportedScript.LATIN -> fontManifest.scripts.latinExtended
|
||||
SupportedScript.ARABIC -> fontManifest.scripts.arabic
|
||||
SupportedScript.CHINESE_SIMPLIFIED -> fontManifest.scripts.chineseSimplified
|
||||
SupportedScript.CHINESE_TRADITIONAL -> fontManifest.scripts.chineseTraditional
|
||||
SupportedScript.CYRILLIC -> fontManifest.scripts.cyrillicExtended
|
||||
SupportedScript.DEVANAGARI -> fontManifest.scripts.devanagari
|
||||
SupportedScript.JAPANESE -> fontManifest.scripts.japanese
|
||||
SupportedScript.CHINESE_TRADITIONAL_HK -> fontManifest.scripts.chineseTraditionalHk
|
||||
SupportedScript.UNKNOWN_CJK -> null
|
||||
SupportedScript.UNKNOWN -> null
|
||||
null -> null
|
||||
}
|
||||
}
|
||||
|
||||
@@ -201,7 +320,7 @@ object Fonts {
|
||||
|
||||
private data class FontDownloadKey(
|
||||
val version: FontVersion,
|
||||
val locale: Locale,
|
||||
val script: SupportedScript,
|
||||
val font: TextFont
|
||||
)
|
||||
}
|
||||
|
||||
@@ -0,0 +1,17 @@
|
||||
package org.thoughtcrime.securesms.fonts
|
||||
|
||||
/**
|
||||
* Scripts with font support for Stories
|
||||
*/
|
||||
enum class SupportedScript {
|
||||
LATIN,
|
||||
CYRILLIC,
|
||||
DEVANAGARI,
|
||||
CHINESE_TRADITIONAL_HK,
|
||||
CHINESE_TRADITIONAL,
|
||||
CHINESE_SIMPLIFIED,
|
||||
UNKNOWN_CJK,
|
||||
ARABIC,
|
||||
JAPANESE,
|
||||
UNKNOWN
|
||||
}
|
||||
@@ -0,0 +1,50 @@
|
||||
package org.thoughtcrime.securesms.fonts
|
||||
|
||||
/**
|
||||
* Attempt to guess the script based on the unicode characters used. The script
|
||||
* with the most use in a string will be picked. Tie goes to [SupportedScript.LATIN].
|
||||
* Unicode does not cleanly separate Hong Kong and Chinese character ranges so some
|
||||
* other means must be used to distinguish it (e.g., Locale).
|
||||
*/
|
||||
object TextToScript {
|
||||
|
||||
private val LATIN_RANGES: List<IntRange> = listOf(0x0000..0x024F, 0x1E00..0x1EFF, 0x2C60..0x2C7F, 0xA720..0xA7FF, 0xAB30..0xAB6F)
|
||||
private val CYRILLIC_RANGES: List<IntRange> = listOf(0x0400..0x04FF, 0x0500..0x052F, 0x1C80..0x1C8F, 0x2DE0..0x2DFF, 0xA640..0xA69F)
|
||||
private val DEVANAGARI_RANGES: List<IntRange> = listOf(0x0900..0x097F, 0xA8E0..0xA8FF, 0x30A0..0x30FF)
|
||||
private val CJK_RANGES: List<IntRange> = listOf(0x31C0..0x31EF, 0x3300..0x33FF, 0x4E00..0x9FFF, 0xF900..0xFAFF, 0xFE30..0xFE4F, 0x20000..0x2EBEF, 0x2F800..0x2FA1F)
|
||||
private val CJK_JAPANESE_RANGES: List<IntRange> = listOf(0x3040..0x309F, 0x30A0..0x30FF, 0x3190..0x319F)
|
||||
private val ARABIC_RANGES: List<IntRange> = listOf(0x0600..0x06FF, 0x0750..0x077F, 0x0870..0x089F, 0x08A0..0x08FF)
|
||||
|
||||
private val allRanges = mapOf(
|
||||
SupportedScript.LATIN to LATIN_RANGES,
|
||||
SupportedScript.CYRILLIC to CYRILLIC_RANGES,
|
||||
SupportedScript.DEVANAGARI to DEVANAGARI_RANGES,
|
||||
SupportedScript.UNKNOWN_CJK to CJK_RANGES,
|
||||
SupportedScript.JAPANESE to CJK_JAPANESE_RANGES,
|
||||
SupportedScript.ARABIC to ARABIC_RANGES
|
||||
)
|
||||
|
||||
fun guessScript(text: CharSequence): SupportedScript {
|
||||
val scriptCounts: MutableMap<SupportedScript, Int> = SupportedScript.values().associate { it to 0 }.toMutableMap()
|
||||
val input = text.toString()
|
||||
|
||||
for (i in 0 until input.codePointCount(0, input.length)) {
|
||||
val codePoint = input.codePointAt(i)
|
||||
for ((script, ranges) in allRanges) {
|
||||
if (ranges.contains(codePoint)) {
|
||||
scriptCounts[script] = scriptCounts[script]!! + 1
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
val most: SupportedScript = scriptCounts.maxByOrNull { it.value }?.key ?: SupportedScript.UNKNOWN
|
||||
|
||||
return if (most == SupportedScript.UNKNOWN_CJK && scriptCounts[SupportedScript.JAPANESE]!! > 0) {
|
||||
SupportedScript.JAPANESE
|
||||
} else {
|
||||
most
|
||||
}
|
||||
}
|
||||
|
||||
private fun List<IntRange>.contains(x: Int): Boolean = any { x in it }
|
||||
}
|
||||
@@ -0,0 +1,39 @@
|
||||
package org.thoughtcrime.securesms.fonts
|
||||
|
||||
import android.graphics.Typeface
|
||||
import android.os.Build
|
||||
|
||||
/**
|
||||
* API independent, best-effort way of creating [Typeface]s of various family/weight.
|
||||
*/
|
||||
object TypefaceHelper {
|
||||
fun typefaceFor(family: Family, weightName: String, weight: Weight): Typeface {
|
||||
return when {
|
||||
Build.VERSION.SDK_INT >= 28 -> Typeface.create(
|
||||
Typeface.create(family.familyName, Typeface.NORMAL),
|
||||
weight.value,
|
||||
false
|
||||
)
|
||||
Build.VERSION.SDK_INT >= 21 -> Typeface.create("${family.familyName}-$weightName", Typeface.NORMAL)
|
||||
else -> Typeface.create(family.familyName, if (weight.value > Weight.MEDIUM.value) Typeface.BOLD else Typeface.NORMAL)
|
||||
}
|
||||
}
|
||||
|
||||
enum class Family(val familyName: String) {
|
||||
SANS_SERIF("sans-serif"),
|
||||
SERIF("serif")
|
||||
}
|
||||
|
||||
enum class Weight(val value: Int) {
|
||||
THIN(100),
|
||||
EXTRA_LIGHT(200),
|
||||
DEMI_LIGHT(200),
|
||||
LIGHT(300),
|
||||
NORMAL(400),
|
||||
MEDIUM(500),
|
||||
SEMI_BOLD(600),
|
||||
BOLD(700),
|
||||
EXTRA_BOLD(800),
|
||||
BLACK(900),
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user