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
57 changes: 57 additions & 0 deletions .maestro/enrichedInput/flows/font_scaling.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
appId: swmansion.enriched.example
tags:
- accessibility
---
- launchApp

- tapOn:
id: "toggle-screen-button"

- runFlow:
file: "../subflows/capture_or_assert_screenshot.yaml"
env:
SCREENSHOT_NAME: "font_scaling__placeholder"

- tapOn:
id: "editor-input"

- inputText: "Plain "

- tapOn:
id: "toolbar-bold"
- inputText: "bold"
- tapOn:
id: "toolbar-bold"
- inputText: " "

- tapOn:
id: "toolbar-italic"
- inputText: "italic"
- tapOn:
id: "toolbar-italic"
- inputText: " "

- tapOn:
id: "toolbar-underline"
- inputText: "underline"
- tapOn:
id: "toolbar-underline"
- inputText: " "

- tapOn:
id: "toolbar-strikethrough"
- inputText: "strike-through"
- tapOn:
id: "toolbar-strikethrough"
- inputText: " "

- tapOn:
id: "toolbar-inline-code"
- inputText: "code"
- tapOn:
id: "toolbar-inline-code"

- runFlow:
file: "../subflows/capture_or_assert_screenshot.yaml"
env:
SCREENSHOT_NAME: "font_scaling"
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.
34 changes: 34 additions & 0 deletions .maestro/enrichedText/flows/font_scaling.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
appId: swmansion.enriched.example
tags:
- accessibility
---
# Validates that text alignment is displayed correctly
- launchApp

- tapOn:
id: 'toggle-screen-button'

- tapOn:
id: 'toggle-enriched-text-screen-button'

- runFlow:
file: '../subflows/set_enriched_text_value.yaml'
env:
VALUE: >
<html>
<p>Left aligned</p>
<p style="text-align: center">Centre aligned</p>
<h6 style="text-align: center">Heading 6</h6>
<p style="text-align: right">Right aligned</p>
<ol style="text-align: right">
<li>Element 1</li>
<li>Element 2</li>
</ol>
</html>

- scroll

- runFlow:
file: '../subflows/capture_or_assert_screenshot.yaml'
env:
SCREENSHOT_NAME: 'font_scaling'
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.
33 changes: 32 additions & 1 deletion .maestro/scripts/run-tests.sh
Original file line number Diff line number Diff line change
Expand Up @@ -71,6 +71,25 @@ app_installed() {
fi
}

# Adjusts the system's text-size setting.
set_font_scale() {
case "$1" in
default) ios_size="large"; android_scale="1.0" ;;
large) ios_size="accessibility-large"; android_scale="1.5" ;;
*) echo "set_font_scale: unknown size '$1'" >&2; return 1 ;;
esac
if [ "$PLATFORM" = ios ]; then
xcrun simctl ui "$DEVICE_ID" content_size "$ios_size"
else
adb -s "$DEVICE_ID" shell settings put system font_scale "$android_scale"
fi
}

# Guarantees the font scale is restored on any exit.
# Without this, the accessibility tests below would leave the device
# in a scaled-up state.
trap 'set_font_scale default' EXIT

if [ -n "$REBUILD" ] || ! app_installed; then
[ -n "$REBUILD" ] && echo "=== rebuild requested, building and installing ==="
[ -z "$REBUILD" ] && echo "=== App ($BUNDLE_ID) not found, building and installing ==="
Expand All @@ -97,6 +116,18 @@ esac
ASSETS_DIR="$MAESTRO_ROOT/assets"
[ -d "$ASSETS_DIR" ] && FLOWS="$ASSETS_DIR $FLOWS"

# A previous run could have died before its EXIT trap fired (e.g. SIGKILL),
# leaving the device scaled. Force a known state before the normal tests.
set_font_scale default

echo "=== Running maestro tests ==="
# shellcheck disable=SC2086
maestro test --device "$DEVICE_ID" $EXTRA $FLOWS
# maestro test --device "$DEVICE_ID" --exclude-tags accessibility $EXTRA $FLOWS

# These are the tests that require changing the system's settings
# - something maestro cannot run internally
echo "=== Running maestro accessibility tests ==="
set_font_scale large

# shellcheck disable=SC2086
maestro test --device "$DEVICE_ID" --include-tags accessibility $EXTRA $FLOWS
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
package com.swmansion.enriched.common

import com.facebook.react.bridge.ReadableMap
import com.facebook.react.uimanager.PixelUtil

internal const val ALLOW_FONT_SCALING_PROP = "allowFontScaling"

// Converts a logical font-unit value to pixels.
// Respects the system font scaling, depending on the `allowFontScaling` value
internal fun pixelFromSpOrDp(
value: Float,
allowFontScaling: Boolean,
): Float =
if (allowFontScaling) {
PixelUtil.toPixelFromSP(value)
} else {
PixelUtil.toPixelFromDIP(value)
}

internal fun pixelFromSpOrDp(
value: Double,
allowFontScaling: Boolean,
): Float =
if (allowFontScaling) {
PixelUtil.toPixelFromSP(value)
} else {
PixelUtil.toPixelFromDIP(value)
}

// Reads allowFontScaling from a serialized prop map (used in MeasurementStore
// where no view instance is available yet).
internal fun allowFontScalingFromProps(props: ReadableMap?): Boolean {
if (props == null) return EnrichedConstants.ALLOW_FONT_SCALING_DEFAULT
if (!props.hasKey(ALLOW_FONT_SCALING_PROP) || props.isNull(ALLOW_FONT_SCALING_PROP)) return EnrichedConstants.ALLOW_FONT_SCALING_DEFAULT
return props.getBoolean(ALLOW_FONT_SCALING_PROP)
}
Original file line number Diff line number Diff line change
Expand Up @@ -11,4 +11,6 @@ object EnrichedConstants {

const val TEXT_DEFAULT_FONT_SIZE = 16f
const val CLIPBOARD_TAG = "react-native-enriched-clipboard"

const val ALLOW_FONT_SCALING_DEFAULT = true
}
Original file line number Diff line number Diff line change
Expand Up @@ -4,10 +4,10 @@ import android.graphics.Color
import com.facebook.react.bridge.ColorPropConverter
import com.facebook.react.bridge.ReactContext
import com.facebook.react.bridge.ReadableMap
import com.facebook.react.uimanager.PixelUtil
import com.facebook.react.views.text.ReactTypefaceUtils.parseFontWeight
import com.swmansion.enriched.common.EnrichedStyle
import com.swmansion.enriched.common.MentionStyle
import com.swmansion.enriched.common.pixelFromSpOrDp
import kotlin.math.ceil

data class EnrichedTextStyle(
Expand Down Expand Up @@ -63,6 +63,7 @@ data class EnrichedTextStyle(
context: ReactContext,
fontSize: Int,
map: ReadableMap,
allowFontScaling: Boolean,
): EnrichedTextStyle {
val h1 = map.getMap("h1")
val h2 = map.getMap("h2")
Expand All @@ -80,40 +81,40 @@ data class EnrichedTextStyle(
val mentions = map.getMap("mention")

return EnrichedTextStyle(
h1FontSize = parseFloat(h1, "fontSize").toInt(),
h1FontSize = parseFloat(h1, "fontSize", allowFontScaling).toInt(),
h1Bold = h1?.getBoolean("bold") ?: false,
h2FontSize = parseFloat(h2, "fontSize").toInt(),
h2FontSize = parseFloat(h2, "fontSize", allowFontScaling).toInt(),
h2Bold = h2?.getBoolean("bold") ?: false,
h3FontSize = parseFloat(h3, "fontSize").toInt(),
h3FontSize = parseFloat(h3, "fontSize", allowFontScaling).toInt(),
h3Bold = h3?.getBoolean("bold") ?: false,
h4FontSize = parseFloat(h4, "fontSize").toInt(),
h4FontSize = parseFloat(h4, "fontSize", allowFontScaling).toInt(),
h4Bold = h4?.getBoolean("bold") ?: false,
h5FontSize = parseFloat(h5, "fontSize").toInt(),
h5FontSize = parseFloat(h5, "fontSize", allowFontScaling).toInt(),
h5Bold = h5?.getBoolean("bold") ?: false,
h6FontSize = parseFloat(h6, "fontSize").toInt(),
h6FontSize = parseFloat(h6, "fontSize", allowFontScaling).toInt(),
h6Bold = h6?.getBoolean("bold") ?: false,
blockquoteColor = parseOptionalColor(context, blockquote, "color"),
blockquoteBorderColor = parseColor(context, blockquote, "borderColor"),
blockquoteStripeWidth = parseFloat(blockquote, "borderWidth").toInt(),
blockquoteGapWidth = parseFloat(blockquote, "gapWidth").toInt(),
olGapWidth = parseFloat(orderedList, "gapWidth").toInt(),
olMarginLeft = calculateOlMarginLeft(fontSize, parseFloat(orderedList, "marginLeft").toInt()),
blockquoteStripeWidth = parseFloat(blockquote, "borderWidth", allowFontScaling).toInt(),
blockquoteGapWidth = parseFloat(blockquote, "gapWidth", allowFontScaling).toInt(),
olGapWidth = parseFloat(orderedList, "gapWidth", allowFontScaling).toInt(),
olMarginLeft = calculateOlMarginLeft(fontSize, parseFloat(orderedList, "marginLeft", allowFontScaling).toInt()),
olMarkerFontWeight = parseOptionalFontWeight(orderedList, "markerFontWeight"),
olMarkerColor = parseOptionalColor(context, orderedList, "markerColor"),
ulGapWidth = parseFloat(unorderedList, "gapWidth").toInt(),
ulMarginLeft = parseFloat(unorderedList, "marginLeft").toInt(),
ulBulletSize = parseFloat(unorderedList, "bulletSize").toInt(),
ulGapWidth = parseFloat(unorderedList, "gapWidth", allowFontScaling).toInt(),
ulMarginLeft = parseFloat(unorderedList, "marginLeft", allowFontScaling).toInt(),
ulBulletSize = parseFloat(unorderedList, "bulletSize", allowFontScaling).toInt(),
ulBulletColor = parseColor(context, unorderedList, "bulletColor"),
ulCheckboxBoxColor = parseColor(context, checkboxList, "boxColor"),
ulCheckboxBoxSize = parseFloat(checkboxList, "boxSize").toInt(),
ulCheckboxGapWidth = parseFloat(checkboxList, "gapWidth").toInt(),
ulCheckboxMarginLeft = parseFloat(checkboxList, "marginLeft").toInt(),
ulCheckboxBoxSize = parseFloat(checkboxList, "boxSize", allowFontScaling).toInt(),
ulCheckboxGapWidth = parseFloat(checkboxList, "gapWidth", allowFontScaling).toInt(),
ulCheckboxMarginLeft = parseFloat(checkboxList, "marginLeft", allowFontScaling).toInt(),
aColor = parseColor(context, link, "color"),
aUnderline = parseIsUnderline(link),
aPressColor = parseColor(context, link, "pressColor"),
codeBlockColor = parseColor(context, codeblock, "color"),
codeBlockBackgroundColor = parseColorWithOpacity(context, codeblock, "backgroundColor", 80),
codeBlockRadius = parseFloat(codeblock, "borderRadius"),
codeBlockRadius = parseFloat(codeblock, "borderRadius", allowFontScaling),
inlineCodeColor = parseColor(context, inlineCode, "color"),
inlineCodeBackgroundColor = parseColorWithOpacity(context, inlineCode, "backgroundColor", 80),
mentionsStyle = parseMentionsStyle(context, mentions),
Expand All @@ -123,9 +124,10 @@ data class EnrichedTextStyle(
private fun parseFloat(
map: ReadableMap?,
key: String,
allowFontScaling: Boolean,
): Float {
if (map == null || !map.hasKey(key) || map.isNull(key)) return 0f
return ceil(PixelUtil.toPixelFromSP(map.getDouble(key)))
return ceil(pixelFromSpOrDp(map.getDouble(key), allowFontScaling))
}

private fun parseColor(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,14 +18,14 @@ import androidx.appcompat.widget.AppCompatTextView
import com.facebook.react.bridge.ReactContext
import com.facebook.react.bridge.ReadableMap
import com.facebook.react.common.ReactConstants
import com.facebook.react.uimanager.PixelUtil
import com.facebook.react.uimanager.ViewDefaults
import com.facebook.react.views.text.ReactTypefaceUtils.applyStyles
import com.facebook.react.views.text.ReactTypefaceUtils.parseFontStyle
import com.facebook.react.views.text.ReactTypefaceUtils.parseFontWeight
import com.swmansion.enriched.common.EnrichedConstants
import com.swmansion.enriched.common.GumboNormalizer
import com.swmansion.enriched.common.parser.EnrichedParser
import com.swmansion.enriched.common.pixelFromSpOrDp
import com.swmansion.enriched.text.spans.EnrichedTextImageSpan
import com.swmansion.enriched.text.spans.interfaces.EnrichedTextClickableSpan
import com.swmansion.enriched.text.spans.interfaces.EnrichedTextSpan
Expand All @@ -39,6 +39,15 @@ class EnrichedTextView : AppCompatTextView {
private var fontStyle: Int = ReactConstants.UNSET
private var fontWeight: Int = ReactConstants.UNSET
private var fontSize: Float = EnrichedConstants.TEXT_DEFAULT_FONT_SIZE
private var fontSizeRaw: Float? = null
private var htmlStyleMap: ReadableMap? = null
var allowFontScaling: Boolean = EnrichedConstants.ALLOW_FONT_SCALING_DEFAULT
set(value) {
if (field == value) return
field = value
fontSizeRaw?.let { setFontSize(it) }
htmlStyleMap?.let { setHtmlStyle(it) }
}

private var enrichedStyle: EnrichedTextStyle? = null
private val spannableFactory = EnrichedTextSpanFactory()
Expand Down Expand Up @@ -247,7 +256,9 @@ class EnrichedTextView : AppCompatTextView {
fun setHtmlStyle(style: ReadableMap?) {
if (style == null) return

val enrichedStyle = EnrichedTextStyle.fromReadableMap(context as ReactContext, fontSize.toInt(), style)
htmlStyleMap = style
val enrichedStyle =
EnrichedTextStyle.fromReadableMap(context as ReactContext, fontSize.toInt(), style, allowFontScaling)
this.enrichedStyle = enrichedStyle

val currentText = text ?: return
Expand Down Expand Up @@ -287,7 +298,8 @@ class EnrichedTextView : AppCompatTextView {
fun setFontSize(size: Float) {
if (size == 0f) return

val sizeInt = ceil(PixelUtil.toPixelFromSP(size))
fontSizeRaw = size
val sizeInt = ceil(pixelFromSpOrDp(size, allowFontScaling))
fontSize = sizeInt
setTextSize(TypedValue.COMPLEX_UNIT_PX, sizeInt)
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -121,6 +121,13 @@ class EnrichedTextViewManager :
view?.setHtmlStyle(value)
}

override fun setAllowFontScaling(
view: EnrichedTextView?,
value: Boolean,
) {
view?.allowFontScaling = value
}

override fun setUseHtmlNormalizer(
view: EnrichedTextView?,
value: Boolean,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,9 @@ import com.facebook.react.views.text.ReactTypefaceUtils.parseFontWeight
import com.facebook.yoga.YogaMeasureMode
import com.facebook.yoga.YogaMeasureOutput
import com.swmansion.enriched.common.EnrichedConstants
import com.swmansion.enriched.common.allowFontScalingFromProps
import com.swmansion.enriched.common.parser.EnrichedParser
import com.swmansion.enriched.common.pixelFromSpOrDp
import kotlin.math.ceil

object MeasurementStore {
Expand Down Expand Up @@ -108,7 +110,9 @@ object MeasurementStore {

try {
val style = props?.getMap("htmlStyle") ?: return text
val enrichedStyle = EnrichedTextStyle.fromReadableMap(context as ReactContext, fontSize, style)
val allowFontScaling = allowFontScalingFromProps(props)
val enrichedStyle =
EnrichedTextStyle.fromReadableMap(context as ReactContext, fontSize, style, allowFontScaling)
val factory = EnrichedTextSpanFactory()
val parsed = EnrichedParser.fromHtml(text, enrichedStyle, factory)
return parsed.trimEnd('\n')
Expand All @@ -126,7 +130,7 @@ object MeasurementStore {
else -> EnrichedConstants.TEXT_DEFAULT_FONT_SIZE
}

return ceil(PixelUtil.toPixelFromSP(fontSize))
return ceil(pixelFromSpOrDp(fontSize, allowFontScalingFromProps(props)))
}

private fun getMeasureById(
Expand Down
Loading
Loading