@whetware/react-native-stroke-text 0.0.11 → 0.0.12

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -5,6 +5,7 @@ import android.graphics.Color
5
5
  import android.graphics.Paint
6
6
  import android.graphics.Paint.FontMetricsInt
7
7
  import android.graphics.Typeface
8
+ import android.content.res.ColorStateList
8
9
  import android.os.Build
9
10
  import android.text.Layout
10
11
  import android.text.TextDirectionHeuristics
@@ -71,9 +72,9 @@ internal class StrokeTextView(context: ThemedReactContext) : TextView(context) {
71
72
  }
72
73
 
73
74
  override fun onDraw(canvas: Canvas) {
74
- // Draw stroke behind fill, using TextView's layout so metrics match RN <Text/> as closely as
75
- // possible (especially for bold fonts).
76
- val layout = layout
75
+ // Draw stroke behind fill through TextView's normal drawing path. Calling super.onDraw()
76
+ // for both passes keeps Android's TextView layout, glyph shaping, and color state handling
77
+ // consistent with the fill render while still letting us switch the paint style for outline.
77
78
  if (layout != null && strokeWidthPx > 0f && strokeColor != Color.TRANSPARENT) {
78
79
  val textPaint = paint
79
80
  val prevStyle = textPaint.style
@@ -81,29 +82,23 @@ internal class StrokeTextView(context: ThemedReactContext) : TextView(context) {
81
82
  val prevStrokeJoin = textPaint.strokeJoin
82
83
  val prevStrokeCap = textPaint.strokeCap
83
84
  val prevColor = textPaint.color
85
+ val prevTextColors: ColorStateList = textColors
84
86
  val prevUnderline = textPaint.isUnderlineText
85
87
  val prevStrike = textPaint.isStrikeThruText
86
88
 
87
- val saveCount = canvas.save()
88
- val compoundPaddingLeft = compoundPaddingLeft
89
- val extendedPaddingTop = extendedPaddingTop
90
-
91
- canvas.translate(compoundPaddingLeft.toFloat(), extendedPaddingTop.toFloat())
92
- canvas.translate(-scrollX.toFloat(), -scrollY.toFloat())
93
-
94
89
  // Only stroke the glyph outlines; keep underline/strike in the fill pass.
95
90
  textPaint.isUnderlineText = false
96
91
  textPaint.isStrikeThruText = false
97
92
 
93
+ setTextColor(strokeColor)
98
94
  textPaint.style = Paint.Style.STROKE
99
95
  textPaint.strokeJoin = Paint.Join.ROUND
100
96
  textPaint.strokeCap = Paint.Cap.ROUND
101
97
  textPaint.strokeWidth = strokeWidthPx
102
98
  textPaint.color = strokeColor
103
- layout.draw(canvas)
104
-
105
- canvas.restoreToCount(saveCount)
99
+ super.onDraw(canvas)
106
100
 
101
+ setTextColor(prevTextColors)
107
102
  textPaint.style = prevStyle
108
103
  textPaint.strokeWidth = prevStrokeWidth
109
104
  textPaint.strokeJoin = prevStrokeJoin
@@ -196,10 +191,6 @@ internal class StrokeTextView(context: ThemedReactContext) : TextView(context) {
196
191
 
197
192
  // Line limits / ellipsizing
198
193
  val maxLines = if (numberOfLines > 0) numberOfLines else Int.MAX_VALUE
199
- // React Native's single-line Text does not wrap at spaces. Without horizontal scrolling,
200
- // Android TextView can create an internal second line inside a host view that was measured
201
- // as one line by the hidden RN Text used for layout.
202
- setHorizontallyScrolling(numberOfLines == 1)
203
194
  setMaxLines(maxLines)
204
195
  ellipsize =
205
196
  if (numberOfLines <= 0) {
package/lib/StrokeText.js CHANGED
@@ -114,6 +114,12 @@ export function StrokeText({ text, children, style, hybridRef, ...rest }) {
114
114
  const [measuredNativeText, setMeasuredNativeText] = React.useState(undefined);
115
115
  const nativeText = measuredNativeText ?? resolvedText;
116
116
  const nativeTextTransform = measuredNativeText == null ? effectiveTextTransform : 'none';
117
+ // RN's hidden Text owns measurement, but Android TextView gets its own layout pass.
118
+ // Compensate for the native stroke inset on every edge, then give that native
119
+ // pass extra right-side room so one-pixel/font-metric drift cannot turn an RN
120
+ // single line into a native soft wrap.
121
+ const nativeOverlayInset = Platform.OS === 'android' && strokeInset > 0 ? strokeInset : 0;
122
+ const nativeOverlayRightInset = nativeOverlayInset > 0 ? nativeOverlayInset * 2 : 0;
117
123
  const wrappedHybridRef = React.useMemo(() => (hybridRef ? callback(hybridRef) : undefined), [hybridRef]);
118
124
  React.useEffect(() => {
119
125
  setMeasuredNativeText(undefined);
@@ -141,6 +147,11 @@ export function StrokeText({ text, children, style, hybridRef, ...rest }) {
141
147
  const handleTextLayout = React.useCallback((event) => {
142
148
  if (!shouldSyncMeasuredLineBreaks)
143
149
  return;
150
+ const lineCount = event.nativeEvent.lines.length;
151
+ if (lineCount <= 1) {
152
+ setMeasuredNativeText((currentText) => currentText == null ? currentText : undefined);
153
+ return;
154
+ }
144
155
  const nextText = toNativeMeasuredText(event.nativeEvent.lines);
145
156
  if (nextText == null)
146
157
  return;
@@ -189,7 +200,17 @@ export function StrokeText({ text, children, style, hybridRef, ...rest }) {
189
200
  },
190
201
  styles.hiddenText,
191
202
  ] }, resolvedText),
192
- React.createElement(NativeStrokeTextView, { ...nativeProps, text: nativeText, color: effectiveColor, fontSize: effectiveFontSize, fontWeight: effectiveFontWeight, fontFamily: effectiveFontFamily, fontStyle: effectiveFontStyle, lineHeight: effectiveLineHeight, letterSpacing: effectiveLetterSpacing, textAlign: effectiveTextAlign, textAlignVertical: effectiveTextAlignVertical, textDecorationLine: effectiveTextDecorationLine, textTransform: nativeTextTransform, opacity: nativeProps.opacity ?? toNumber(styleOpacity), includeFontPadding: effectiveIncludeFontPadding, numberOfLines: nativeProps.numberOfLines, ellipsizeMode: effectiveEllipsizeMode, paddingTop: baseTop, paddingRight: baseRight, paddingBottom: baseBottom, paddingLeft: baseLeft, hybridRef: wrappedHybridRef, pointerEvents: "none", style: styles.overlay })));
203
+ React.createElement(NativeStrokeTextView, { ...nativeProps, text: nativeText, color: effectiveColor, fontSize: effectiveFontSize, fontWeight: effectiveFontWeight, fontFamily: effectiveFontFamily, fontStyle: effectiveFontStyle, lineHeight: effectiveLineHeight, letterSpacing: effectiveLetterSpacing, textAlign: effectiveTextAlign, textAlignVertical: effectiveTextAlignVertical, textDecorationLine: effectiveTextDecorationLine, textTransform: nativeTextTransform, opacity: nativeProps.opacity ?? toNumber(styleOpacity), includeFontPadding: effectiveIncludeFontPadding, numberOfLines: nativeProps.numberOfLines, ellipsizeMode: effectiveEllipsizeMode, paddingTop: baseTop, paddingRight: baseRight, paddingBottom: baseBottom, paddingLeft: baseLeft, hybridRef: wrappedHybridRef, pointerEvents: "none", style: [
204
+ styles.overlay,
205
+ nativeOverlayInset > 0
206
+ ? {
207
+ top: -nativeOverlayInset,
208
+ right: -nativeOverlayRightInset,
209
+ bottom: -nativeOverlayInset,
210
+ left: -nativeOverlayInset,
211
+ }
212
+ : null,
213
+ ] })));
193
214
  }
194
215
  const styles = StyleSheet.create({
195
216
  container: {},
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@whetware/react-native-stroke-text",
3
- "version": "0.0.11",
3
+ "version": "0.0.12",
4
4
  "description": "Stroke/outline text for React Native (New Architecture) on iOS, Android, and Web.",
5
5
  "type": "module",
6
6
  "main": "./lib/index.js",
@@ -121,4 +121,4 @@
121
121
  "useTabs": false,
122
122
  "semi": false
123
123
  }
124
- }
124
+ }