Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Original file line number Diff line number Diff line change
Expand Up @@ -3,4 +3,12 @@ package com.swmansion.enriched.common
data class CustomStyle(
val foregroundColor: Int? = null,
val backgroundColor: Int? = null,
)
val fontSize: Float? = null,
val fontFamily: String? = null,
) {
fun isEmpty(): Boolean =
foregroundColor == null &&
backgroundColor == null &&
(fontSize == null || fontSize <= 0f) &&
fontFamily.isNullOrBlank()
}
Original file line number Diff line number Diff line change
Expand Up @@ -349,7 +349,12 @@ private static void withinParagraph(StringBuilder out, Spanned text, int start,
EnrichedCustomStyleSpan cs = (EnrichedCustomStyleSpan) style[j];
Integer fgColor = cs.getForegroundColor();
Integer bgColor = cs.getBackgroundColor();
if (fgColor != null || bgColor != null) {
Float fontSize = cs.getFontSize();
String fontFamily = cs.getFontFamily();
if (fgColor != null
|| bgColor != null
|| fontSize != null
|| (fontFamily != null && !fontFamily.isEmpty())) {
StringBuilder cssProps = new StringBuilder();
if (fgColor != null) {
cssProps
Expand All @@ -364,6 +369,18 @@ private static void withinParagraph(StringBuilder out, Spanned text, int start,
.append(EnrichedColorParser.colorToHex(bgColor))
.append(";");
}
if (fontSize != null) {
if (cssProps.length() > 0) cssProps.append(" ");
cssProps.append("font-size: ").append(formatCssFontSizeValue(fontSize)).append("px;");
}
if (fontFamily != null && !fontFamily.isEmpty()) {
if (cssProps.length() > 0) cssProps.append(" ");
if (fontFamily.indexOf(' ') >= 0) {
cssProps.append("font-family: '").append(fontFamily).append("';");
} else {
cssProps.append("font-family: ").append(fontFamily).append(";");
}
}
Comment thread
kacperzolkiewski marked this conversation as resolved.
out.append("<span style=\"").append(cssProps).append("\">");
} else {
out.append("<span>");
Expand Down Expand Up @@ -434,6 +451,13 @@ private static void withinStyle(StringBuilder out, CharSequence text, int start,
}
}
}

private static String formatCssFontSizeValue(float fontSize) {
if (fontSize == Math.rint(fontSize) && !Float.isInfinite(fontSize)) {
return String.valueOf((int) fontSize);
}
return String.valueOf(fontSize);
}
}

class HtmlToSpannedConverter<T> implements ContentHandler {
Expand All @@ -458,6 +482,13 @@ class HtmlToSpannedConverter<T> implements ContentHandler {
private static final Pattern CSS_BG_PATTERN =
Pattern.compile("background-color\\s*:\\s*([^;]+)", Pattern.CASE_INSENSITIVE);

private static final Pattern CSS_FONT_SIZE_PATTERN =
Pattern.compile(
"font-size\\s*:\\s*([0-9.]+)(?:\\s*px)?(?=\\s*;|\\s*$)", Pattern.CASE_INSENSITIVE);

private static final Pattern CSS_FONT_FAMILY_PATTERN =
Pattern.compile("font-family\\s*:\\s*([^;]+)", Pattern.CASE_INSENSITIVE);

private static String parseCssAlignmentValue(Attributes attributes) {
String style = attributes.getValue("", "style");
if (style == null) return null;
Expand Down Expand Up @@ -963,6 +994,8 @@ private static void startSpan(Editable text, Attributes attributes) {
String styleAttr = attributes.getValue("", "style");
Integer fg = null;
Integer bg = null;
Float fontSize = null;
String fontFamily = null;

if (styleAttr != null) {
Matcher fgMatcher = CSS_FG_PATTERN.matcher(styleAttr);
Expand All @@ -973,17 +1006,44 @@ private static void startSpan(Editable text, Attributes attributes) {
if (bgMatcher.find()) {
bg = EnrichedColorParser.parseCssColor(bgMatcher.group(1));
}
Matcher fontSizeMatcher = CSS_FONT_SIZE_PATTERN.matcher(styleAttr);
if (fontSizeMatcher.find()) {
String fontSizeString = fontSizeMatcher.group(1);
if (fontSizeString != null) {
fontSize = Float.parseFloat(fontSizeString);
}
}
Matcher fontFamilyMatcher = CSS_FONT_FAMILY_PATTERN.matcher(styleAttr);
if (fontFamilyMatcher.find()) {
fontFamily = parseCssFontFamily(fontFamilyMatcher.group(1));
}
}

if (fg != null || bg != null) {
start(text, new CustomStyleMark(fg, bg));
if (fg != null
|| bg != null
|| fontSize != null
|| (fontFamily != null && !fontFamily.isEmpty())) {
start(text, new CustomStyleMark(fg, bg, fontSize, fontFamily));
}
}

private static String parseCssFontFamily(String raw) {
if (raw == null) return null;
String value = raw.trim();
if ((value.startsWith("'") && value.endsWith("'"))
|| (value.startsWith("\"") && value.endsWith("\""))) {
value = value.substring(1, value.length() - 1);
}
return value.isEmpty() ? null : value;
}

private static <T> void endSpan(Editable text, T style, EnrichedSpanFactory<T> spanFactory) {
CustomStyleMark mark = getLast(text, CustomStyleMark.class);
if (mark == null) return;
setSpanFromMark(text, mark, spanFactory.createCustomStyleSpan(mark.mFg, mark.mBg));
setSpanFromMark(
text,
mark,
spanFactory.createCustomStyleSpan(mark.mFg, mark.mBg, mark.mFontSize, mark.mFontFamily));
}

public void setDocumentLocator(Locator locator) {}
Expand Down Expand Up @@ -1116,10 +1176,14 @@ public Newline(int numNewlines) {
private static class CustomStyleMark {
public final Integer mFg;
public final Integer mBg;
public final Float mFontSize;
public final String mFontFamily;

public CustomStyleMark(Integer fg, Integer bg) {
public CustomStyleMark(Integer fg, Integer bg, Float fontSize, String fontFamily) {
mFg = fg;
mBg = bg;
mFontSize = fontSize;
mFontFamily = fontFamily;
}
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -85,5 +85,7 @@ interface EnrichedSpanFactory<T> {
fun createCustomStyleSpan(
foregroundColor: Int?,
backgroundColor: Int?,
fontSize: Float?,
fontFamily: String?,
): EnrichedCustomStyleSpan
}
Original file line number Diff line number Diff line change
@@ -1,22 +1,68 @@
package com.swmansion.enriched.common.spans

import android.content.res.AssetManager
import android.graphics.Color
import android.text.TextPaint
import android.text.style.CharacterStyle
import android.text.style.MetricAffectingSpan
import com.facebook.react.common.ReactConstants
import com.facebook.react.views.text.ReactTypefaceUtils.applyStyles
import com.swmansion.enriched.common.CustomStyle
import com.swmansion.enriched.common.pixelFromSpOrDp
import com.swmansion.enriched.common.spans.interfaces.EnrichedInlineSpan

open class EnrichedCustomStyleSpan(
private val foregroundColor: Int?,
private val backgroundColor: Int?,
) : CharacterStyle(),
private val fontSizeSp: Float?,
private val fontFamily: String?,
private val assets: AssetManager,
private val allowFontScaling: Boolean,
) : MetricAffectingSpan(),
EnrichedInlineSpan {
fun getForegroundColor(): Int? = foregroundColor

fun getBackgroundColor(): Int? = backgroundColor

fun getFontSize(): Float? = fontSizeSp

fun getFontFamily(): String? = fontFamily

fun toCustomStyle(): CustomStyle =
CustomStyle(
foregroundColor = foregroundColor,
backgroundColor = backgroundColor,
fontSize = fontSizeSp,
fontFamily = fontFamily,
)

protected fun getAssets(): AssetManager = assets

protected fun getAllowFontScaling(): Boolean = allowFontScaling

override fun updateMeasureState(textPaint: TextPaint) {
applyFontState(textPaint)
}

override fun updateDrawState(textPaint: TextPaint) {
foregroundColor?.let { textPaint.color = it }
backgroundColor?.let { textPaint.bgColor = withOpacity(it, 80) }
applyFontState(textPaint)
}

private fun applyFontState(textPaint: TextPaint) {
fontFamily?.trim()?.takeIf { it.isNotEmpty() }?.let { family ->
textPaint.typeface =
applyStyles(
textPaint.typeface,
ReactConstants.UNSET,
ReactConstants.UNSET,
family,
assets,
)
}
fontSizeSp?.takeIf { it > 0f }?.let { size ->
textPaint.textSize = pixelFromSpOrDp(size, allowFontScaling)
}
}
Comment thread
Copilot marked this conversation as resolved.

private fun withOpacity(
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
package com.swmansion.enriched.text

import android.content.res.AssetManager
import com.swmansion.enriched.common.parser.EnrichedSpanFactory
import com.swmansion.enriched.common.spans.EnrichedCustomStyleSpan
import com.swmansion.enriched.text.spans.EnrichedTextAlignmentSpan
Expand All @@ -24,7 +25,10 @@ import com.swmansion.enriched.text.spans.EnrichedTextStrikeThroughSpan
import com.swmansion.enriched.text.spans.EnrichedTextUnderlineSpan
import com.swmansion.enriched.text.spans.EnrichedTextUnorderedListSpan

class EnrichedTextSpanFactory : EnrichedSpanFactory<EnrichedTextStyle> {
class EnrichedTextSpanFactory(
private val assets: AssetManager,
private val allowFontScaling: Boolean,
) : EnrichedSpanFactory<EnrichedTextStyle> {
override fun createAlignmentSpan(cssValue: String) = EnrichedTextAlignmentSpan(cssValue)

override fun createBoldSpan(style: EnrichedTextStyle) = EnrichedTextBoldSpan(style)
Expand Down Expand Up @@ -87,5 +91,15 @@ class EnrichedTextSpanFactory : EnrichedSpanFactory<EnrichedTextStyle> {
override fun createCustomStyleSpan(
foregroundColor: Int?,
backgroundColor: Int?,
): EnrichedCustomStyleSpan = EnrichedTextCustomStyleSpan(foregroundColor, backgroundColor)
fontSize: Float?,
fontFamily: String?,
): EnrichedCustomStyleSpan =
EnrichedTextCustomStyleSpan(
foregroundColor,
backgroundColor,
fontSize,
fontFamily,
assets,
allowFontScaling,
)
}
Original file line number Diff line number Diff line change
Expand Up @@ -46,12 +46,17 @@ class EnrichedTextView : AppCompatTextView {
set(value) {
if (field == value) return
field = value
// Invalidate the spannable factory so that it is recreated with the new allowFontScaling value
cachedSpannableFactory = null
fontSizeRaw?.let { setFontSize(it) }
htmlStyleMap?.let { setHtmlStyle(it) }
}

private var enrichedStyle: EnrichedTextStyle? = null
private val spannableFactory = EnrichedTextSpanFactory()

private var cachedSpannableFactory: EnrichedTextSpanFactory? = null
private val spannableFactory: EnrichedTextSpanFactory
get() = cachedSpannableFactory ?: EnrichedTextSpanFactory(context.assets, allowFontScaling).also { cachedSpannableFactory = it }

// We keep the parsedText around so that when an async image finishes loading we can re-call
// setText with the same instance and force the TextView to rebuild its layout.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -118,7 +118,7 @@ object MeasurementStore {
val allowFontScaling = allowFontScalingFromProps(props)
val enrichedStyle = EnrichedTextStyle.fromReadableMap(context as ReactContext, fontSize, style, allowFontScaling)

val factory = EnrichedTextSpanFactory()
val factory = EnrichedTextSpanFactory(context.assets, allowFontScaling)
val parsed = EnrichedParser.fromHtml(textToParse, enrichedStyle, factory)
return parsed.trimEnd('\n')
} catch (e: Exception) {
Expand Down
Original file line number Diff line number Diff line change
@@ -1,16 +1,35 @@
package com.swmansion.enriched.text.spans

import android.content.res.AssetManager
import com.swmansion.enriched.common.spans.EnrichedCustomStyleSpan
import com.swmansion.enriched.text.EnrichedTextStyle
import com.swmansion.enriched.text.spans.interfaces.EnrichedTextSpan

class EnrichedTextCustomStyleSpan(
foregroundColor: Int?,
backgroundColor: Int?,
) : EnrichedCustomStyleSpan(foregroundColor, backgroundColor),
fontSize: Float?,
fontFamily: String?,
assets: AssetManager,
allowFontScaling: Boolean,
) : EnrichedCustomStyleSpan(
foregroundColor,
backgroundColor,
fontSize,
fontFamily,
assets,
allowFontScaling,
),
EnrichedTextSpan {
override val dependsOnHtmlStyle: Boolean = false

override fun rebuildWithStyle(style: EnrichedTextStyle): EnrichedTextCustomStyleSpan =
EnrichedTextCustomStyleSpan(getForegroundColor(), getBackgroundColor())
EnrichedTextCustomStyleSpan(
getForegroundColor(),
getBackgroundColor(),
getFontSize(),
getFontFamily(),
getAssets(),
getAllowFontScaling(),
)
}
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
package com.swmansion.enriched.textinput

import android.content.res.AssetManager
import com.swmansion.enriched.common.parser.EnrichedSpanFactory
import com.swmansion.enriched.common.spans.EnrichedCustomStyleSpan
import com.swmansion.enriched.common.spans.EnrichedImageSpan
import com.swmansion.enriched.textinput.spans.EnrichedInputAlignmentSpan
import com.swmansion.enriched.textinput.spans.EnrichedInputBlockQuoteSpan
Expand All @@ -25,7 +27,10 @@ import com.swmansion.enriched.textinput.spans.EnrichedInputUnderlineSpan
import com.swmansion.enriched.textinput.spans.EnrichedInputUnorderedListSpan
import com.swmansion.enriched.textinput.styles.HtmlStyle

class EnrichedTextInputSpannableFactory : EnrichedSpanFactory<HtmlStyle> {
class EnrichedTextInputSpannableFactory(
private val assets: AssetManager,
private val allowFontScaling: Boolean,
) : EnrichedSpanFactory<HtmlStyle> {
override fun createAlignmentSpan(cssValue: String) = EnrichedInputAlignmentSpan(cssValue)

override fun createBoldSpan(style: HtmlStyle) = EnrichedInputBoldSpan(style)
Expand Down Expand Up @@ -88,5 +93,15 @@ class EnrichedTextInputSpannableFactory : EnrichedSpanFactory<HtmlStyle> {
override fun createCustomStyleSpan(
foregroundColor: Int?,
backgroundColor: Int?,
) = EnrichedInputCustomStyleSpan(foregroundColor, backgroundColor)
fontSize: Float?,
fontFamily: String?,
): EnrichedCustomStyleSpan =
EnrichedInputCustomStyleSpan(
foregroundColor,
backgroundColor,
fontSize,
fontFamily,
assets,
allowFontScaling,
)
}
Original file line number Diff line number Diff line change
Expand Up @@ -100,6 +100,8 @@ class EnrichedTextInputView :
set(value) {
if (field != value) {
field = value
// Invalidate the spannable factory so that it is recreated with the new allowFontScaling value
cachedSpannableFactory = null
val raw = fontSizeRaw
if (raw != null) {
setFontSize(raw) // re-invokes invalidateStyles internally
Expand Down Expand Up @@ -148,7 +150,13 @@ class EnrichedTextInputView :
private var defaultValueDirty: Boolean = false

private var inputMethodManager: InputMethodManager? = null
private val spannableFactory = EnrichedTextInputSpannableFactory()

private var cachedSpannableFactory: EnrichedTextInputSpannableFactory? = null
private val spannableFactory: EnrichedTextInputSpannableFactory
get() =
cachedSpannableFactory
?: EnrichedTextInputSpannableFactory(context.assets, allowFontScaling).also { cachedSpannableFactory = it }

private var contextMenuItems: List<Pair<Int, String>> = emptyList()

constructor(context: Context) : super(context) {
Expand Down
Loading