react-native-advanced-text 0.2.8 → 0.2.10

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.Canvas
5
5
  import android.graphics.Color
6
6
  import android.graphics.Point
7
7
  import android.graphics.RectF
8
+ import android.os.Build
8
9
  import android.text.SpannableString
9
10
  import android.text.Spannable
10
11
  import android.text.Spanned
@@ -25,7 +26,6 @@ import com.facebook.react.uimanager.events.RCTEventEmitter
25
26
  import android.text.Selection
26
27
  import android.graphics.Typeface
27
28
  import androidx.core.text.getSpans
28
- import kotlin.math.ceil
29
29
 
30
30
  class AdvancedTextView : TextView {
31
31
 
@@ -64,6 +64,15 @@ class AdvancedTextView : TextView {
64
64
  // React Native `style` instead, exactly like <Text>.
65
65
  setPadding(0, 0, 0, 0)
66
66
  setTextIsSelectable(true)
67
+ // Fabric measures this component's text via RN's own TextLayoutManager,
68
+ // which explicitly builds its StaticLayout with
69
+ // `setUseLineSpacingFromFallbacks(true)`. TextView's own equivalent
70
+ // (`setFallbackLineSpacing`) defaults differently depending on
71
+ // targetSdkVersion, so without this the two engines can compute
72
+ // slightly different natural line heights for the same font/text.
73
+ if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.P) {
74
+ setFallbackLineSpacing(true)
75
+ }
67
76
 
68
77
  movementMethod = SmartMovementMethod
69
78
 
@@ -278,7 +287,7 @@ class AdvancedTextView : TextView {
278
287
  }
279
288
 
280
289
 
281
- applyLineHeight(spannableString)
290
+ setLineSpacing(0f, lineHeightMultiplier)
282
291
 
283
292
  // Android's letterSpacing is in em; the prop is in dp/points. The ratio
284
293
  // dp / fontSize is scale-invariant, so no density conversion is needed.
@@ -289,45 +298,16 @@ class AdvancedTextView : TextView {
289
298
  }
290
299
 
291
300
 
292
- post {
293
- setText(spannableString, BufferType.SPANNABLE)
294
- Log.d(TAG, "Text updated with ${wordPositions.size} spans")
295
- }
296
- }
297
-
298
- /**
299
- * Applies `lineHeightMultiplier` as a CSS-style, exact-per-line-height span
300
- * instead of `setLineSpacing(0, multiplier)`.
301
- *
302
- * The Fabric ShadowNode measures `lineHeight` by asking React Native's own
303
- * TextLayoutManager to lay out a fragment with that lineHeight -- which RN
304
- * implements with its own CSS-style line-height span (every line's box is
305
- * forced to exactly `lineHeight`, ascent/descent redistributed evenly).
306
- * That is a *different* algorithm from `setLineSpacing`, which scales the
307
- * font's natural per-line advance instead; the two do not produce the same
308
- * total height, and the gap between them is added on every wrapped line,
309
- * so it grows with text length. Using the same CSS-style algorithm here
310
- * (see CssLineHeightSpan) keeps this view's rendered height identical to
311
- * what was measured, regardless of how many lines the text wraps onto.
312
- */
313
- private fun applyLineHeight(spannableString: SpannableString) {
314
- if (lineHeightMultiplier == 1.0f || spannableString.isEmpty()) {
315
- return
316
- }
317
-
318
- val metrics = paint.fontMetricsInt
319
- val naturalLineHeight = -metrics.ascent + metrics.descent
320
- if (naturalLineHeight <= 0) {
321
- return
322
- }
323
-
324
- val targetLineHeight = ceil(lineHeightMultiplier * naturalLineHeight).toInt()
325
- spannableString.setSpan(
326
- CssLineHeightSpan(targetLineHeight),
327
- 0,
328
- spannableString.length,
329
- Spanned.SPAN_EXCLUSIVE_EXCLUSIVE
330
- )
301
+ // Set synchronously, not via `post {}`: in a virtualized list (e.g.
302
+ // FlashList) this view gets recycled for a new row on every scroll
303
+ // tick. A posted `setText` races the *next* recycle -- if this view
304
+ // is reused again before the queued runnable fires, whichever row's
305
+ // runnable happens to run last wins, and a row can end up rendering
306
+ // blank (with its correctly-measured height still reserved by
307
+ // Fabric) while its own `setText` was dropped. `setText` doesn't
308
+ // need to wait for anything here, so there's nothing to defer for.
309
+ setText(spannableString, BufferType.SPANNABLE)
310
+ Log.d(TAG, "Text updated with ${wordPositions.size} spans")
331
311
  }
332
312
 
333
313
  override fun onDraw(canvas: Canvas) {
@@ -0,0 +1,69 @@
1
+ package com.advancedtext
2
+
3
+ import android.content.res.Resources
4
+ import android.graphics.Typeface
5
+ import android.text.StaticLayout
6
+ import android.text.TextPaint
7
+ import android.util.TypedValue
8
+
9
+ /**
10
+ * JNI bridge so the Fabric ShadowNode (C++ `measureContent()`) can ask
11
+ * Android's own `StaticLayout` -- the exact class `AdvancedTextView`'s real
12
+ * rendering is built on -- for this text's real height, instead of going
13
+ * through React Native's own text-measurement engine (`TextLayoutManager`).
14
+ *
15
+ * RN's own engine and Android's native `TextView` rendering are two
16
+ * independently-implemented paths that can drift apart by a few px per line
17
+ * (verified: RN's `TextLayoutManager` takes a different code path --
18
+ * `BoringLayout` vs `StaticLayout` -- for short/simple text than for real,
19
+ * multi-line paragraphs, and even StaticLayout-to-StaticLayout the two
20
+ * engines' resolved `Paint`/`Typeface` don't perfectly agree). Since this
21
+ * component doesn't reuse RN's own prepared text layout for drawing anyway
22
+ * (it re-renders through its own `TextView`), measuring with the same
23
+ * `StaticLayout` call the view itself is built on removes the drift
24
+ * entirely, rather than approximating it from the C++ side.
25
+ *
26
+ * Every parameter below is chosen to mirror exactly what `AdvancedTextView`
27
+ * itself configures on its own `TextView`/`Paint` in `updateTextWithHighlights()`
28
+ * -- font resolution, text size (SP, not raw px), `setLineSpacing`, and
29
+ * letter spacing. Spans (highlight/click color) are omitted: they don't
30
+ * affect font metrics or line breaking, only drawing.
31
+ */
32
+ object NativeTextMeasurement {
33
+
34
+ @JvmStatic
35
+ fun measureHeight(
36
+ text: String,
37
+ fontFamily: String,
38
+ typefaceStyle: Int,
39
+ fontSizeSp: Float,
40
+ lineHeightMultiplier: Float,
41
+ letterSpacingDp: Float,
42
+ widthPx: Float
43
+ ): Float {
44
+ if (text.isEmpty() || widthPx <= 0f) {
45
+ return 0f
46
+ }
47
+
48
+ val displayMetrics = Resources.getSystem().displayMetrics
49
+ val fontSizePx =
50
+ TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_SP, fontSizeSp, displayMetrics)
51
+
52
+ val paint = TextPaint(TextPaint.ANTI_ALIAS_FLAG)
53
+ paint.typeface = Typeface.create(fontFamily, typefaceStyle)
54
+ paint.textSize = fontSizePx
55
+ // Mirrors AdvancedTextView's own letterSpacing conversion: dp/points
56
+ // prop -> em, scale-invariant so no density conversion is needed.
57
+ if (letterSpacingDp != 0f && fontSizeSp > 0f) {
58
+ paint.letterSpacing = letterSpacingDp / fontSizeSp
59
+ }
60
+
61
+ val width = widthPx.toInt().coerceAtLeast(1)
62
+ val builder = StaticLayout.Builder.obtain(text, 0, text.length, paint, width)
63
+ // `setLineSpacing(0, multiplier)` is exactly what AdvancedTextView
64
+ // itself applies for rendering -- see updateTextWithHighlights().
65
+ builder.setLineSpacing(0f, if (lineHeightMultiplier > 0f) lineHeightMultiplier else 1f)
66
+ val layout = builder.build()
67
+ return layout.height.toFloat()
68
+ }
69
+ }
@@ -11,6 +11,7 @@
11
11
 
12
12
  #include <algorithm>
13
13
  #include <cctype>
14
+ #include <cmath>
14
15
  #include <string>
15
16
 
16
17
  #include <react/renderer/attributedstring/AttributedStringBox.h>
@@ -21,6 +22,10 @@
21
22
  #include <react/renderer/textlayoutmanager/TextLayoutContext.h>
22
23
  #include <react/renderer/textlayoutmanager/TextLayoutManager.h>
23
24
 
25
+ #if defined(ANDROID)
26
+ #include <fbjni/fbjni.h>
27
+ #endif
28
+
24
29
  namespace facebook::react {
25
30
 
26
31
  namespace {
@@ -42,7 +47,7 @@ AdvancedTextViewShadowNode::AdvancedTextViewShadowNode(
42
47
  static_cast<const AdvancedTextViewShadowNode&>(sourceShadowNode)
43
48
  .textLayoutManager_) {}
44
49
 
45
- TextAttributes AdvancedTextViewShadowNode::baseTextAttributes() const {
50
+ AttributedString AdvancedTextViewShadowNode::getAttributedString() const {
46
51
  const auto& props = getConcreteProps();
47
52
 
48
53
  auto textAttributes = TextAttributes::defaultTextAttributes();
@@ -68,87 +73,82 @@ TextAttributes AdvancedTextViewShadowNode::baseTextAttributes() const {
68
73
  textAttributes.letterSpacing = props.letterSpacing;
69
74
  }
70
75
 
71
- return textAttributes;
72
- }
76
+ // Deliberately never sets `textAttributes.lineHeight`: the component's
77
+ // `lineHeight` prop is a *multiple* of the font's natural line height (see
78
+ // `setLineSpacing(0, multiplier)` in AdvancedTextView.kt / the
79
+ // NSParagraphStyle-based spacing in AdvancedTextView.mm on iOS), not the
80
+ // absolute per-line value `TextAttributes::lineHeight` expects. Forcing an
81
+ // absolute value here would make Fabric apply RN's own CSS-style
82
+ // forced-line-height algorithm, which is a *different* algorithm from
83
+ // `setLineSpacing`'s multiplicative one and drifts from it by an amount
84
+ // that compounds with every wrapped line. Measuring naturally instead and
85
+ // scaling the *result* in `measureContent()` uses the same "multiply the
86
+ // natural total" math `setLineSpacing` itself uses, so they agree exactly
87
+ // regardless of paragraph length.
73
88
 
74
- #if defined(ANDROID)
75
- Float AdvancedTextViewShadowNode::measureNaturalLineHeight(
76
- const TextAttributes& baseTextAttributes,
77
- const TextLayoutContext& textLayoutContext) const {
78
- // `baseTextAttributes` deliberately has no `lineHeight` set, so this
79
- // resolves to the font's own natural single-line height -- exactly what
80
- // CssLineHeightSpan.kt multiplies on the view side.
81
- auto probeString = AttributedString{};
82
- probeString.appendFragment(AttributedString::Fragment{
83
- .string = "M",
84
- .textAttributes = baseTextAttributes,
89
+ auto attributedString = AttributedString{};
90
+ attributedString.appendFragment(AttributedString::Fragment{
91
+ .string = props.text,
92
+ .textAttributes = textAttributes,
85
93
  .parentShadowView = ShadowView(*this),
86
94
  });
87
-
88
- auto probeParagraphAttributes = ParagraphAttributes{};
89
- probeParagraphAttributes.maximumNumberOfLines = 1;
90
- // Measure the tight ascent+descent box (no extra top/bottom accent
91
- // padding). This is the same "natural line height" basis
92
- // CssLineHeightSpan.kt derives from `Paint.getFontMetricsInt()` on the
93
- // view side (which is unaffected by `includeFontPadding`, a Layout-level
94
- // setting) -- measuring with padding included here would double count it
95
- // once per rendered line once the resulting value is multiplied and
96
- // applied to the whole paragraph.
97
- probeParagraphAttributes.includeFontPadding = false;
98
-
99
- auto measurement = textLayoutManager_->measure(
100
- AttributedStringBox{probeString},
101
- probeParagraphAttributes,
102
- textLayoutContext,
103
- LayoutConstraints{});
104
-
105
- return measurement.size.height;
95
+ return attributedString;
106
96
  }
107
- #endif
108
97
 
109
- AttributedString AdvancedTextViewShadowNode::getAttributedString(
110
- const TextAttributes& baseTextAttributes,
111
- const TextLayoutContext& textLayoutContext) const {
98
+ #if defined(ANDROID)
99
+ Float AdvancedTextViewShadowNode::measureHeightViaAndroidNative(
100
+ Float widthDp,
101
+ Float pointScaleFactor) const {
102
+ if (!std::isfinite(widthDp) || widthDp <= 0 || pointScaleFactor <= 0) {
103
+ return -1;
104
+ }
105
+
112
106
  const auto& props = getConcreteProps();
113
107
 
114
- auto textAttributes = baseTextAttributes;
115
-
116
- // The component treats `lineHeight` as a multiple of the font's own
117
- // natural line height (see CssLineHeightSpan.kt on Android / the
118
- // NSParagraphStyle-based spacing in AdvancedTextView.mm on iOS) -- NOT the
119
- // absolute per-line value `TextAttributes::lineHeight` expects. A
120
- // multiplier of 1 *is* the natural line height, so there is nothing to
121
- // override in that case (overriding it with an approximation, as this
122
- // used to do unconditionally, made even the "no lineHeight override" case
123
- // measure taller or shorter than the real render).
124
- if (props.lineHeight > 0 && props.lineHeight != 1.0) {
125
- #if defined(ANDROID)
126
- // Android's natural single-line height depends on the resolved
127
- // font/size/weight and isn't a fixed ratio of fontSize, so a constant
128
- // approximation drifts from the real value CssLineHeightSpan.kt derives
129
- // from Paint.getFontMetricsInt() -- by an amount that compounds with
130
- // every wrapped line, which is why the error scaled with paragraph
131
- // length. Measure the real natural height for these exact attributes
132
- // instead, the same way the view derives it.
133
- auto naturalLineHeight =
134
- measureNaturalLineHeight(textAttributes, textLayoutContext);
135
- textAttributes.lineHeight = naturalLineHeight > 0
136
- ? props.lineHeight * naturalLineHeight
137
- : props.lineHeight * textAttributes.fontSize * 1.2;
138
- #else
139
- textAttributes.lineHeight =
140
- props.lineHeight * textAttributes.fontSize * 1.2;
141
- #endif
108
+ const auto weight = toLowerAscii(props.fontWeight);
109
+ // Matches Typeface.NORMAL/BOLD/ITALIC -- the same three-way mapping
110
+ // AdvancedTextView.kt uses for `typeface = when (fontWeight) { ... }`.
111
+ int typefaceStyle = 0; // Typeface.NORMAL
112
+ if (weight == "bold") {
113
+ typefaceStyle = 1; // Typeface.BOLD
114
+ } else if (weight == "italic") {
115
+ typefaceStyle = 2; // Typeface.ITALIC
142
116
  }
143
117
 
144
- auto attributedString = AttributedString{};
145
- attributedString.appendFragment(AttributedString::Fragment{
146
- .string = props.text,
147
- .textAttributes = textAttributes,
148
- .parentShadowView = ShadowView(*this),
149
- });
150
- return attributedString;
118
+ std::string fontFamily =
119
+ props.fontFamily.empty() ? "sans-serif" : props.fontFamily;
120
+ Float fontSize = props.fontSize > 0 ? props.fontSize : 16.0;
121
+ Float lineHeightMultiplier = props.lineHeight > 0 ? props.lineHeight : 1.0;
122
+ Float widthPx = widthDp * pointScaleFactor;
123
+
124
+ static const auto cls =
125
+ facebook::jni::findClassStatic("com/advancedtext/NativeTextMeasurement");
126
+ static const auto method = cls->getStaticMethod<jfloat(
127
+ std::string,
128
+ std::string,
129
+ jint,
130
+ jfloat,
131
+ jfloat,
132
+ jfloat,
133
+ jfloat)>("measureHeight");
134
+
135
+ Float heightPx = method(
136
+ cls,
137
+ props.text,
138
+ fontFamily,
139
+ typefaceStyle,
140
+ static_cast<jfloat>(fontSize),
141
+ static_cast<jfloat>(lineHeightMultiplier),
142
+ static_cast<jfloat>(props.letterSpacing),
143
+ static_cast<jfloat>(widthPx));
144
+
145
+ if (heightPx <= 0) {
146
+ return -1;
147
+ }
148
+
149
+ return heightPx / pointScaleFactor;
151
150
  }
151
+ #endif
152
152
 
153
153
  Size AdvancedTextViewShadowNode::measureContent(
154
154
  const LayoutContext& layoutContext,
@@ -173,13 +173,40 @@ Size AdvancedTextViewShadowNode::measureContent(
173
173
  paragraphAttributes.adjustsFontSizeToFit = false;
174
174
 
175
175
  auto measurement = textLayoutManager_->measure(
176
- AttributedStringBox{
177
- getAttributedString(baseTextAttributes(), textLayoutContext)},
176
+ AttributedStringBox{getAttributedString()},
178
177
  paragraphAttributes,
179
178
  textLayoutContext,
180
179
  layoutConstraints);
181
180
 
182
- return layoutConstraints.clamp(measurement.size);
181
+ auto size = measurement.size;
182
+
183
+ #if defined(ANDROID)
184
+ // Prefer asking Android's own `StaticLayout` for the height directly (see
185
+ // `measureHeightViaAndroidNative()`) -- it's the same class
186
+ // `AdvancedTextView`'s real rendering is built on, so there's no
187
+ // measurement-vs-render drift to compound over long paragraphs. `size`
188
+ // (from RN's own TextLayoutManager, above) still supplies the width --
189
+ // this only replaces the height. Falls back to the `lineHeight`-scaling
190
+ // math below when the width isn't a finite, positive number (e.g. an
191
+ // unconstrained/intrinsic-width measure pass).
192
+ auto nativeHeight =
193
+ measureHeightViaAndroidNative(size.width, textLayoutContext.pointScaleFactor);
194
+ if (nativeHeight > 0) {
195
+ size.height = nativeHeight;
196
+ return layoutConstraints.clamp(size);
197
+ }
198
+ #endif
199
+
200
+ // `lineHeight` scales the font's own natural line height (see
201
+ // `getAttributedString()`); since `size.height` above is the *natural*
202
+ // (multiplier == 1) total, `setLineSpacing(0, multiplier)` scaling the
203
+ // same natural total on the view side means multiplying it here
204
+ // reproduces exactly the same number, for any paragraph length.
205
+ if (props.lineHeight > 0) {
206
+ size.height *= props.lineHeight;
207
+ }
208
+
209
+ return layoutConstraints.clamp(size);
183
210
  }
184
211
 
185
212
  } // namespace facebook::react
@@ -34,7 +34,6 @@
34
34
 
35
35
  #include <jsi/jsi.h>
36
36
  #include <react/renderer/attributedstring/AttributedString.h>
37
- #include <react/renderer/attributedstring/TextAttributes.h>
38
37
  #include <react/renderer/components/AdvancedTextViewSpec/EventEmitters.h>
39
38
  #include <react/renderer/components/AdvancedTextViewSpec/Props.h>
40
39
  #include <react/renderer/components/AdvancedTextViewSpec/States.h>
@@ -43,7 +42,6 @@
43
42
  #include <react/renderer/core/LayoutContext.h>
44
43
  #include <react/renderer/core/ShadowNode.h>
45
44
  #include <react/renderer/core/ShadowNodeFragment.h>
46
- #include <react/renderer/textlayoutmanager/TextLayoutContext.h>
47
45
 
48
46
  namespace facebook::react {
49
47
 
@@ -85,41 +83,25 @@ class AdvancedTextViewShadowNode final : public ConcreteViewShadowNode<
85
83
 
86
84
  private:
87
85
  /*
88
- * Builds the font-related `TextAttributes` from the component props
89
- * (family/weight/style/size/letterSpacing), *excluding* `lineHeight`.
90
- * Shared between the natural-line-height probe and the real measurement so
91
- * both resolve to the exact same font.
86
+ * Builds a single-fragment `AttributedString` from the component props
87
+ * (text + font attributes), mirroring how the native views render it.
88
+ * Deliberately never sets `lineHeight` -- see the .cpp for why.
92
89
  */
93
- TextAttributes baseTextAttributes() const;
90
+ AttributedString getAttributedString() const;
94
91
 
95
- /*
96
- * `lineHeight` is exposed to JS as a multiple of the font's own natural
97
- * line height (see CssLineHeightSpan.kt on Android), NOT the absolute
98
- * per-line value `TextAttributes::lineHeight` expects. A hardcoded ratio
99
- * (e.g. `fontSize * 1.2`) is only an approximation of a font's real
100
- * natural single-line height, and native font tables (especially OEM
101
- * ones) can deviate from it enough to make Yoga's measured block height
102
- * visibly disagree with what the native view actually draws. To keep them
103
- * in sync, measure a single line of the same resolved font (no
104
- * `lineHeight` override) and use its height as the real multiplier base --
105
- * with `includeFontPadding = false`, matching the tight ascent+descent
106
- * basis `Paint.getFontMetricsInt()` gives `CssLineHeightSpan.kt` on the
107
- * view side (Android only; iOS's own NSParagraphStyle-based spacing in
108
- * AdvancedTextView.mm doesn't need this).
109
- */
110
92
  #if defined(ANDROID)
111
- Float measureNaturalLineHeight(
112
- const TextAttributes& baseTextAttributes,
113
- const TextLayoutContext& textLayoutContext) const;
114
- #endif
115
-
116
93
  /*
117
- * Builds a single-fragment `AttributedString` from the component props
118
- * (text + font attributes), mirroring how the native views render it.
94
+ * Asks Android's own `StaticLayout` (via a JNI call into
95
+ * `NativeTextMeasurement.measureHeight()`) for this text's real rendered
96
+ * height at the given width, instead of approximating it through RN's own
97
+ * `TextLayoutManager`. Returns height in dp, or a negative value if it
98
+ * couldn't be computed (e.g. unconstrained width), signaling the caller to
99
+ * fall back to the `TextLayoutManager`-based path.
119
100
  */
120
- AttributedString getAttributedString(
121
- const TextAttributes& baseTextAttributes,
122
- const TextLayoutContext& textLayoutContext) const;
101
+ Float measureHeightViaAndroidNative(
102
+ Float widthDp,
103
+ Float pointScaleFactor) const;
104
+ #endif
123
105
 
124
106
  // Lazily created on first measure, then reused / copied across clones.
125
107
  mutable std::shared_ptr<const TextLayoutManager> textLayoutManager_;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "react-native-advanced-text",
3
- "version": "0.2.8",
3
+ "version": "0.2.10",
4
4
  "description": " Advanced text component for React Native with custom select options.",
5
5
  "main": "./lib/module/index.js",
6
6
  "types": "./lib/typescript/src/index.d.ts",
@@ -1,54 +0,0 @@
1
- package com.advancedtext
2
-
3
- import android.graphics.Paint
4
- import android.text.style.LineHeightSpan
5
- import kotlin.math.ceil
6
- import kotlin.math.floor
7
-
8
- /**
9
- * A [LineHeightSpan] that forces every line's box to exactly [lineHeightPx],
10
- * following CSS `line-height` semantics: the difference between the target
11
- * height and the font's natural ascent+descent ("leading") is split evenly
12
- * above and below, and the very first line's top / very last line's bottom
13
- * are pinned back to the font's own ascent/descent so the half-leading at
14
- * the block's own edges doesn't inflate its total height.
15
- *
16
- * This exists because `AdvancedTextView`'s Fabric ShadowNode measures the
17
- * `lineHeight` prop through React Native's `TextLayoutManager`, which applies
18
- * `lineHeight` using this exact CSS-style algorithm internally -- a different
19
- * one from Android's own `TextView.setLineSpacing(0, multiplier)`. Rendering
20
- * with `setLineSpacing` instead of this span made the real height diverge
21
- * from the measured one by a per-line amount, which is why the discrepancy
22
- * grew with the number of wrapped lines.
23
- *
24
- * `start`/`end` here are the offsets of the *current line*, not the span's
25
- * own range, so the first/last checks below correctly fire only once each
26
- * per paragraph. Mirrors React Native's own
27
- * `com.facebook.react.views.text.internal.span.CustomLineHeightSpan`
28
- * (used internally for `<Text lineHeight={}>`) verbatim, so it inherits that
29
- * implementation's on-device track record instead of a new one.
30
- *
31
- * See: https://www.w3.org/TR/css-inline-3/#inline-height
32
- */
33
- internal class CssLineHeightSpan(private val lineHeightPx: Int) : LineHeightSpan {
34
-
35
- override fun chooseHeight(
36
- text: CharSequence,
37
- start: Int,
38
- end: Int,
39
- spanstartv: Int,
40
- v: Int,
41
- fm: Paint.FontMetricsInt
42
- ) {
43
- val leading = lineHeightPx - ((-fm.ascent) + fm.descent)
44
- fm.ascent -= ceil(leading / 2.0f).toInt()
45
- fm.descent += floor(leading / 2.0f).toInt()
46
-
47
- if (start == 0) {
48
- fm.top = fm.ascent
49
- }
50
- if (end == text.length) {
51
- fm.bottom = fm.descent
52
- }
53
- }
54
- }