react-native-advanced-text 0.2.3 → 0.2.4

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.
@@ -11,6 +11,7 @@
11
11
 
12
12
  #include <algorithm>
13
13
  #include <cctype>
14
+ #include <limits>
14
15
  #include <string>
15
16
 
16
17
  #include <react/renderer/attributedstring/AttributedStringBox.h>
@@ -32,6 +33,10 @@ std::string toLowerAscii(std::string value) {
32
33
  return value;
33
34
  }
34
35
 
36
+ // Fallback only: an approximate ratio of a single line's natural height to
37
+ // its font size, used if a real measurement (Android) isn't available/valid.
38
+ constexpr Float kLineHeightFudgeFactor = 1.2;
39
+
35
40
  } // namespace
36
41
 
37
42
  AdvancedTextViewShadowNode::AdvancedTextViewShadowNode(
@@ -42,7 +47,47 @@ AdvancedTextViewShadowNode::AdvancedTextViewShadowNode(
42
47
  static_cast<const AdvancedTextViewShadowNode&>(sourceShadowNode)
43
48
  .textLayoutManager_) {}
44
49
 
45
- AttributedString AdvancedTextViewShadowNode::getAttributedString() const {
50
+ #if defined(ANDROID)
51
+ Float AdvancedTextViewShadowNode::measureNaturalSingleLineHeight(
52
+ const LayoutContext& layoutContext,
53
+ const TextAttributes& baseTextAttributes) const {
54
+ // Same font/size/weight/style as the real fragment, but with `lineHeight`
55
+ // cleared so this reports the font's own, unscaled single-line height --
56
+ // exactly what Android's `setLineSpacing(0, multiplier)` multiplies.
57
+ auto probeAttributes = baseTextAttributes;
58
+ probeAttributes.lineHeight = std::numeric_limits<Float>::quiet_NaN();
59
+
60
+ auto probe = AttributedString{};
61
+ probe.appendFragment(AttributedString::Fragment{
62
+ .string = "M",
63
+ .textAttributes = probeAttributes,
64
+ .parentShadowView = ShadowView(*this),
65
+ });
66
+
67
+ auto paragraphAttributes = ParagraphAttributes{};
68
+
69
+ auto textLayoutContext = TextLayoutContext{};
70
+ textLayoutContext.pointScaleFactor = layoutContext.pointScaleFactor;
71
+ textLayoutContext.surfaceId = getSurfaceId();
72
+
73
+ // Unconstrained so a single glyph can never be forced to wrap.
74
+ auto unconstrainedLayoutConstraints = LayoutConstraints{
75
+ {0, 0},
76
+ {std::numeric_limits<Float>::infinity(),
77
+ std::numeric_limits<Float>::infinity()}};
78
+
79
+ auto measurement = textLayoutManager_->measure(
80
+ AttributedStringBox{probe},
81
+ paragraphAttributes,
82
+ textLayoutContext,
83
+ unconstrainedLayoutConstraints);
84
+
85
+ return measurement.size.height;
86
+ }
87
+ #endif
88
+
89
+ AttributedString AdvancedTextViewShadowNode::getAttributedString(
90
+ const LayoutContext& layoutContext) const {
46
91
  const auto& props = getConcreteProps();
47
92
 
48
93
  auto textAttributes = TextAttributes::defaultTextAttributes();
@@ -68,13 +113,32 @@ AttributedString AdvancedTextViewShadowNode::getAttributedString() const {
68
113
  textAttributes.letterSpacing = props.letterSpacing;
69
114
  }
70
115
 
71
- if (props.lineHeight > 0) {
72
- // The component treats `lineHeight` as a multiple of the font size
73
- // (see AdvancedTextView.mm / AdvancedTextView.kt). `TextAttributes`
74
- // expects an absolute value; approximate the platform default single
75
- // line height as 1.2 * fontSize.
116
+ // The component treats `lineHeight` as a multiple of the font's own
117
+ // natural line height (see setLineSpacing(0, multiplier) in
118
+ // AdvancedTextView.kt / the NSParagraphStyle-based spacing in
119
+ // AdvancedTextView.mm) -- NOT the absolute per-line value
120
+ // `TextAttributes.lineHeight` expects. A multiplier of 1 *is* the natural
121
+ // line height, so there is nothing to override in that case (and
122
+ // overriding it with an approximation, as this used to do unconditionally,
123
+ // made even the "no lineHeight override" case measure taller or shorter
124
+ // than the real render).
125
+ if (props.lineHeight > 0 && props.lineHeight != 1.0) {
126
+ #if defined(ANDROID)
127
+ // Android's natural single-line height depends on the resolved
128
+ // font/size/weight and isn't a fixed ratio of fontSize, so a constant
129
+ // approximation drifts from AdvancedTextView.kt's real
130
+ // setLineSpacing()-based layout by an amount that compounds with every
131
+ // wrapped line -- which is why the error scaled with paragraph length.
132
+ // Measure the real natural height for these exact attributes instead.
133
+ auto naturalLineHeight =
134
+ measureNaturalSingleLineHeight(layoutContext, textAttributes);
135
+ textAttributes.lineHeight = naturalLineHeight > 0
136
+ ? props.lineHeight * naturalLineHeight
137
+ : props.lineHeight * textAttributes.fontSize * kLineHeightFudgeFactor;
138
+ #else
76
139
  textAttributes.lineHeight =
77
- props.lineHeight * textAttributes.fontSize * 1.2;
140
+ props.lineHeight * textAttributes.fontSize * kLineHeightFudgeFactor;
141
+ #endif
78
142
  }
79
143
 
80
144
  auto attributedString = AttributedString{};
@@ -109,7 +173,7 @@ Size AdvancedTextViewShadowNode::measureContent(
109
173
  textLayoutContext.surfaceId = getSurfaceId();
110
174
 
111
175
  auto measurement = textLayoutManager_->measure(
112
- AttributedStringBox{getAttributedString()},
176
+ AttributedStringBox{getAttributedString(layoutContext)},
113
177
  paragraphAttributes,
114
178
  textLayoutContext,
115
179
  layoutConstraints);
@@ -86,7 +86,20 @@ class AdvancedTextViewShadowNode final : public ConcreteViewShadowNode<
86
86
  * Builds a single-fragment `AttributedString` from the component props
87
87
  * (text + font attributes), mirroring how the native views render it.
88
88
  */
89
- AttributedString getAttributedString() const;
89
+ AttributedString getAttributedString(const LayoutContext& layoutContext) const;
90
+
91
+ #if defined(ANDROID)
92
+ /*
93
+ * Measures the natural (multiplier == 1) height of a single line set in
94
+ * `baseTextAttributes`, by measuring a single glyph with `lineHeight`
95
+ * cleared. Used to turn the component's `lineHeight` multiplier prop into
96
+ * the absolute value `TextAttributes.lineHeight` expects, using this
97
+ * font's *real* metrics instead of an approximation.
98
+ */
99
+ Float measureNaturalSingleLineHeight(
100
+ const LayoutContext& layoutContext,
101
+ const TextAttributes& baseTextAttributes) const;
102
+ #endif
90
103
 
91
104
  // Lazily created on first measure, then reused / copied across clones.
92
105
  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.3",
3
+ "version": "0.2.4",
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",