@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
|
|
75
|
-
//
|
|
76
|
-
|
|
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
|
-
|
|
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:
|
|
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.
|
|
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
|
+
}
|