Skip to content

Commit

Permalink
Add font support to Style
Browse files Browse the repository at this point in the history
  • Loading branch information
wingio committed Jul 20, 2024
1 parent fa69350 commit d2efac8
Show file tree
Hide file tree
Showing 20 changed files with 379 additions and 88 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -3,17 +3,24 @@ package xyz.wingio.syntakts.demo
import android.os.Bundle
import android.widget.TextView
import androidx.activity.ComponentActivity
import androidx.core.content.res.ResourcesCompat
import xyz.wingio.syntakts.android.render
import xyz.wingio.syntakts.android.style.DefaultFontResolver

class AndroidTestActivity : ComponentActivity() {

override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_android_test)

DefaultFontResolver.register(
"jetbrains mono" to ResourcesCompat.getFont(this, R.font.jetbrains_mono)!!
)

val testString = """
# Header
**Bold** *Italic* __Underline__ ~~Strikethrough~~ <@1234> :heart:
`println("hi")`
""".trimIndent()

findViewById<TextView>(R.id.test_text).render(
Expand Down
30 changes: 8 additions & 22 deletions demo/src/main/java/xyz/wingio/syntakts/demo/MainActivity.kt
Original file line number Diff line number Diff line change
@@ -1,9 +1,7 @@
package xyz.wingio.syntakts.demo

import android.content.Intent
import android.graphics.Typeface
import android.os.Bundle
import android.widget.Toast
import androidx.activity.ComponentActivity
import androidx.activity.compose.setContent
import androidx.compose.foundation.layout.Arrangement
Expand All @@ -21,51 +19,39 @@ import androidx.compose.material3.Icon
import androidx.compose.material3.LocalTextStyle
import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.OutlinedTextField
import androidx.compose.material3.ProvideTextStyle
import androidx.compose.material3.Surface
import androidx.compose.material3.Text
import androidx.compose.runtime.LaunchedEffect
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
import androidx.compose.runtime.setValue
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.text.Placeholder
import androidx.compose.ui.text.PlaceholderVerticalAlign
import androidx.compose.ui.text.font.Font
import androidx.compose.ui.text.font.FontFamily
import androidx.compose.ui.text.style.TextOverflow
import androidx.compose.ui.unit.dp
import xyz.wingio.syntakts.demo.ui.theme.SyntaktsTheme

import xyz.wingio.syntakts.compose.material3.clickable.ClickableText
import xyz.wingio.syntakts.compose.rememberRendered
import xyz.wingio.syntakts.compose.rememberSyntakts
import xyz.wingio.syntakts.compose.style.toSyntaktsColor
import xyz.wingio.syntakts.markdown.MarkdownSyntakts
import xyz.wingio.syntakts.markdown.addBasicMarkdownRules
import xyz.wingio.syntakts.markdown.addBoldRule
import xyz.wingio.syntakts.markdown.addHeaderRule
import xyz.wingio.syntakts.markdown.addItalicsRule
import xyz.wingio.syntakts.markdown.addMarkdownRules
import xyz.wingio.syntakts.markdown.addStrikethroughRule
import xyz.wingio.syntakts.markdown.addUnderlineRule
import xyz.wingio.syntakts.node.clickableNode
import xyz.wingio.syntakts.node.styleNode
import xyz.wingio.syntakts.style.Style
import java.io.File
import xyz.wingio.syntakts.style.Color as SyntaktsColor
import xyz.wingio.syntakts.compose.style.DefaultFontResolver

class MainActivity : ComponentActivity() {

override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
val atIntent = Intent(this, AndroidTestActivity::class.java)

DefaultFontResolver.register(
"jetbrains mono" to FontFamily(Font(R.font.jetbrains_mono))
)

setContent {
SyntaktsTheme {
var text by remember { mutableStateOf("**bold** *italic* __underline__ ~~strikethrough~~") }
var text by remember { mutableStateOf("**bold** *italic* __underline__ ~~strikethrough~~ `code`") }

// A surface container using the 'background' color from the theme
Surface(
Expand Down
7 changes: 7 additions & 0 deletions demo/src/main/java/xyz/wingio/syntakts/demo/TestSyntakts.kt
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ import xyz.wingio.syntakts.style.Style
import xyz.wingio.syntakts.syntakts

val TestSyntakts = syntakts<Context> {

rule("<@([0-9]+)>") { result, ctx ->
val username = ctx.userMap[result.groupValues[1]] ?: "Unknown"
appendClickable(
Expand All @@ -33,6 +34,12 @@ val TestSyntakts = syntakts<Context> {
)
}

rule("(`+)([\\s\\S]*?[^`])\\1(?!`)") { result, _ ->
append(result.groupValues[2]) {
font = "jetbrains mono"
}
}

addMarkdownRules()
}

Expand Down
Binary file added demo/src/main/res/font/jetbrains_mono.ttf
Binary file not shown.
32 changes: 23 additions & 9 deletions syntakts-android/api/syntakts-android.api
Original file line number Diff line number Diff line change
Expand Up @@ -9,12 +9,14 @@ public final class xyz/wingio/syntakts/android/ClickableMovementMethod$Companion
}

public final class xyz/wingio/syntakts/android/SyntaktsKt {
public static final fun render (Landroid/widget/TextView;Ljava/lang/CharSequence;Lxyz/wingio/syntakts/Syntakts;Ljava/lang/Object;Z)V
public static final fun render (Landroid/widget/TextView;Ljava/lang/CharSequence;Lxyz/wingio/syntakts/Syntakts;Z)V
public static final fun render (Lxyz/wingio/syntakts/Syntakts;Ljava/lang/CharSequence;Landroid/content/Context;)Ljava/lang/CharSequence;
public static final fun render (Lxyz/wingio/syntakts/Syntakts;Ljava/lang/CharSequence;Ljava/lang/Object;Landroid/content/Context;)Ljava/lang/CharSequence;
public static synthetic fun render$default (Landroid/widget/TextView;Ljava/lang/CharSequence;Lxyz/wingio/syntakts/Syntakts;Ljava/lang/Object;ZILjava/lang/Object;)V
public static synthetic fun render$default (Landroid/widget/TextView;Ljava/lang/CharSequence;Lxyz/wingio/syntakts/Syntakts;ZILjava/lang/Object;)V
public static final fun render (Landroid/widget/TextView;Ljava/lang/CharSequence;Lxyz/wingio/syntakts/Syntakts;Ljava/lang/Object;ZLxyz/wingio/syntakts/android/style/AndroidFontResolver;)V
public static final fun render (Landroid/widget/TextView;Ljava/lang/CharSequence;Lxyz/wingio/syntakts/Syntakts;ZLxyz/wingio/syntakts/android/style/AndroidFontResolver;)V
public static final fun render (Lxyz/wingio/syntakts/Syntakts;Ljava/lang/CharSequence;Landroid/content/Context;Lxyz/wingio/syntakts/android/style/AndroidFontResolver;)Ljava/lang/CharSequence;
public static final fun render (Lxyz/wingio/syntakts/Syntakts;Ljava/lang/CharSequence;Ljava/lang/Object;Landroid/content/Context;Lxyz/wingio/syntakts/android/style/AndroidFontResolver;)Ljava/lang/CharSequence;
public static synthetic fun render$default (Landroid/widget/TextView;Ljava/lang/CharSequence;Lxyz/wingio/syntakts/Syntakts;Ljava/lang/Object;ZLxyz/wingio/syntakts/android/style/AndroidFontResolver;ILjava/lang/Object;)V
public static synthetic fun render$default (Landroid/widget/TextView;Ljava/lang/CharSequence;Lxyz/wingio/syntakts/Syntakts;ZLxyz/wingio/syntakts/android/style/AndroidFontResolver;ILjava/lang/Object;)V
public static synthetic fun render$default (Lxyz/wingio/syntakts/Syntakts;Ljava/lang/CharSequence;Landroid/content/Context;Lxyz/wingio/syntakts/android/style/AndroidFontResolver;ILjava/lang/Object;)Ljava/lang/CharSequence;
public static synthetic fun render$default (Lxyz/wingio/syntakts/Syntakts;Ljava/lang/CharSequence;Ljava/lang/Object;Landroid/content/Context;Lxyz/wingio/syntakts/android/style/AndroidFontResolver;ILjava/lang/Object;)Ljava/lang/CharSequence;
}

public final class xyz/wingio/syntakts/android/markdown/MarkdownKt {
Expand All @@ -37,15 +39,27 @@ public class xyz/wingio/syntakts/android/spans/ClickableSpan : android/text/styl

public class xyz/wingio/syntakts/android/spans/SyntaktsStyleSpan : android/text/style/MetricAffectingSpan {
public static final field Companion Lxyz/wingio/syntakts/android/spans/SyntaktsStyleSpan$Companion;
public fun <init> (Lxyz/wingio/syntakts/style/Style;Landroid/content/Context;)V
public fun <init> (Lxyz/wingio/syntakts/style/Style;Landroid/content/Context;Lxyz/wingio/syntakts/android/style/AndroidFontResolver;)V
public final fun getContext ()Landroid/content/Context;
public final fun getStyle ()Lxyz/wingio/syntakts/style/Style;
public fun updateDrawState (Landroid/text/TextPaint;)V
public fun updateMeasureState (Landroid/text/TextPaint;)V
}

public final class xyz/wingio/syntakts/android/spans/SyntaktsStyleSpan$Companion {
public final fun apply (Landroid/text/TextPaint;Lxyz/wingio/syntakts/style/Style;Landroid/content/Context;)V
public final fun apply (Landroid/text/TextPaint;Lxyz/wingio/syntakts/style/Style;Landroid/content/Context;Lxyz/wingio/syntakts/android/style/AndroidFontResolver;)V
}

public final class xyz/wingio/syntakts/android/style/AndroidFontResolver : xyz/wingio/syntakts/style/FontResolver {
public fun <init> ()V
public fun registerFont (Ljava/lang/String;Landroid/graphics/Typeface;)V
public synthetic fun registerFont (Ljava/lang/String;Ljava/lang/Object;)V
public fun resolveFont (Ljava/lang/String;)Landroid/graphics/Typeface;
public synthetic fun resolveFont (Ljava/lang/String;)Ljava/lang/Object;
}

public final class xyz/wingio/syntakts/android/style/AndroidFontResolverKt {
public static final fun getDefaultFontResolver ()Lxyz/wingio/syntakts/android/style/AndroidFontResolver;
}

public final class xyz/wingio/syntakts/android/style/ColorKt {
Expand All @@ -55,7 +69,7 @@ public final class xyz/wingio/syntakts/android/style/ColorKt {
}

public final class xyz/wingio/syntakts/android/style/SpannableStyledTextBuilder : xyz/wingio/syntakts/style/StyledTextBuilder {
public fun <init> (Landroid/content/Context;)V
public fun <init> (Landroid/content/Context;Lxyz/wingio/syntakts/android/style/AndroidFontResolver;)V
public fun addAnnotation (Ljava/lang/String;Ljava/lang/String;II)Lxyz/wingio/syntakts/android/style/SpannableStyledTextBuilder;
public synthetic fun addAnnotation (Ljava/lang/String;Ljava/lang/String;II)Lxyz/wingio/syntakts/style/StyledTextBuilder;
public fun addAnnotation (Ljava/lang/String;Ljava/lang/String;Lkotlin/ranges/IntRange;)Lxyz/wingio/syntakts/style/StyledTextBuilder;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,8 @@ package xyz.wingio.syntakts.android
import android.content.Context
import android.widget.TextView
import xyz.wingio.syntakts.Syntakts
import xyz.wingio.syntakts.android.style.AndroidFontResolver
import xyz.wingio.syntakts.android.style.DefaultFontResolver
import xyz.wingio.syntakts.android.style.SpannableStyledTextBuilder

/**
Expand All @@ -11,10 +13,16 @@ import xyz.wingio.syntakts.android.style.SpannableStyledTextBuilder
* @param text What to parse and render
* @param context Additional information that nodes may need to render
* @param androidContext Necessary for certain text measurements
* @param fontResolver (optional) The [AndroidFontResolver] used to override fonts in this specific [TextView]
* @return SpannableString as a [CharSequence]
*/
public fun <C> Syntakts<C>.render(text: CharSequence, context: C, androidContext: Context): CharSequence {
val builder = SpannableStyledTextBuilder(androidContext)
public fun <C> Syntakts<C>.render(
text: CharSequence,
context: C,
androidContext: Context,
fontResolver: AndroidFontResolver = DefaultFontResolver
): CharSequence {
val builder = SpannableStyledTextBuilder(androidContext, fontResolver)
val nodes = parse(text)
for (node in nodes) {
node.render(builder, context)
Expand All @@ -27,10 +35,14 @@ public fun <C> Syntakts<C>.render(text: CharSequence, context: C, androidContext
*
* @param text What to parse and render
* @param androidContext Necessary for certain text measurements
* @param fontResolver (optional) The [AndroidFontResolver] used to override fonts in this specific [TextView]
* @return SpannableString as a [CharSequence]
*/
public fun Syntakts<Unit>.render(text: CharSequence, androidContext: Context): CharSequence =
render(text, Unit, androidContext)
public fun Syntakts<Unit>.render(
text: CharSequence,
androidContext: Context,
fontResolver: AndroidFontResolver = DefaultFontResolver
): CharSequence = render(text, Unit, androidContext, fontResolver)

/**
* Parse and render the given [text] using the [syntakts] onto this [TextView]
Expand All @@ -39,14 +51,16 @@ public fun Syntakts<Unit>.render(text: CharSequence, androidContext: Context): C
* @param syntakts An instance of [Syntakts] with the desired rules
* @param context Additional information that nodes may need to render
* @param enableClickable (optional) Whether or not to process click and long click events
* @param fontResolver (optional) The [AndroidFontResolver] used to override fonts in this specific [TextView]
*/
public fun <C> TextView.render(
text: CharSequence,
syntakts: Syntakts<C>,
context: C,
enableClickable: Boolean = false
enableClickable: Boolean = false,
fontResolver: AndroidFontResolver = DefaultFontResolver
) {
setText(syntakts.render(text, context, getContext()))
setText(syntakts.render(text, context, getContext(), fontResolver))
if (enableClickable) {
movementMethod = ClickableMovementMethod()
}
Expand All @@ -58,13 +72,15 @@ public fun <C> TextView.render(
* @param text What to parse and render
* @param syntakts An instance of [Syntakts] with the desired rules
* @param enableClickable (optional) Whether or not to process click and long click events
* @param fontResolver (optional) The [AndroidFontResolver] used to override fonts in this specific [TextView]
*/
public fun TextView.render(
text: CharSequence,
syntakts: Syntakts<Unit>,
enableClickable: Boolean = false
enableClickable: Boolean = false,
fontResolver: AndroidFontResolver = DefaultFontResolver
) {
setText(syntakts.render(text, context))
setText(syntakts.render(text, context, fontResolver))
if (enableClickable) {
movementMethod = ClickableMovementMethod()
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import android.graphics.Typeface
import android.text.TextPaint
import android.text.style.MetricAffectingSpan
import androidx.core.graphics.TypefaceCompat
import xyz.wingio.syntakts.android.style.AndroidFontResolver
import xyz.wingio.syntakts.android.style.toAndroidColorInt
import xyz.wingio.syntakts.android.util.emToPx
import xyz.wingio.syntakts.android.util.spToEm
Expand All @@ -20,18 +21,20 @@ import kotlin.math.roundToInt
*
* @param style The [Style] to apply
* @param context Necessary for certain measurements
* @param fontResolver Needed to resolve a [Typeface] from a font name
*/
public open class SyntaktsStyleSpan(
public val style: Style,
public val context: Context
public val context: Context,
private val fontResolver: AndroidFontResolver
) : MetricAffectingSpan() {

override fun updateDrawState(tp: TextPaint?) {
apply(tp, style, context)
apply(tp, style, context, fontResolver)
}

override fun updateMeasureState(textPaint: TextPaint) {
apply(textPaint, style, context)
apply(textPaint, style, context, fontResolver)
}

public companion object {
Expand All @@ -42,8 +45,9 @@ public open class SyntaktsStyleSpan(
* @param paint Information for how text can be displayed
* @param style The [Style] to apply
* @param context Necessary for certain measurements
* @param fontResolver Needed to resolve a [Typeface] from a font name
*/
public fun apply(paint: TextPaint?, style: Style, context: Context) {
public fun apply(paint: TextPaint?, style: Style, context: Context, fontResolver: AndroidFontResolver) {
if (paint == null) return

with(style) {
Expand All @@ -55,6 +59,10 @@ public open class SyntaktsStyleSpan(
paint.bgColor = background.toAndroidColorInt()
}

font?.let {
fontResolver.resolveFont(it)?.let { typeface -> paint.setTypeface(typeface) }
}

if (fontSize !is TextUnit.Unspecified) {
paint.textSize = when (fontSize.unit) {
"sp" -> context.spToPx(fontSize.value)
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
package xyz.wingio.syntakts.android.style

import android.graphics.Typeface
import xyz.wingio.syntakts.style.FontResolver
import xyz.wingio.syntakts.style.Fonts

/**
* Keeps a record of [Typefaces][Typeface] with a plain font name
*
* For most cases it is reccommended to use [DefaultFontResolver] to register your fonts
*/
public class AndroidFontResolver: FontResolver<Typeface>() {

override val fontMap: MutableMap<String, Typeface> = mutableMapOf(
Fonts.DEFAULT to Typeface.DEFAULT,
Fonts.MONOSPACE to Typeface.MONOSPACE,
"monospaced" to Typeface.MONOSPACE,
Fonts.SERIF to Typeface.SERIF,
Fonts.SANS_SERIF to Typeface.SANS_SERIF
)

/**
* Gets a font in the [Typeface] format from a given name
*
* @param fontName Case-insensitive font name to look up
*/
public override fun resolveFont(fontName: String): Typeface? {
return fontMap[fontName.lowercase()]
}

/**
* Registers a [Typeface] with the specified [name][fontName]
*
* @param fontName Case-insensitive name to use for the [font][platformFont]
* @param platformFont The font to register with the [fontName]
*/
override fun registerFont(fontName: String, platformFont: Typeface) {
fontMap[fontName.lowercase()] = platformFont
}


}

/**
* Default [FontResolver] used when rendering
*/
public val DefaultFontResolver: AndroidFontResolver = AndroidFontResolver()
Loading

0 comments on commit d2efac8

Please sign in to comment.