diff --git a/packages/react-native/React/Fabric/Mounting/ComponentViews/Text/RCTParagraphComponentView.mm b/packages/react-native/React/Fabric/Mounting/ComponentViews/Text/RCTParagraphComponentView.mm index 07fb9c4e7b6..333d56da763 100644 --- a/packages/react-native/React/Fabric/Mounting/ComponentViews/Text/RCTParagraphComponentView.mm +++ b/packages/react-native/React/Fabric/Mounting/ComponentViews/Text/RCTParagraphComponentView.mm @@ -389,6 +389,35 @@ - (UIView *)hitTest:(CGPoint)point withEvent:(UIEvent *)event return nil; } +- (void)adjustFrameForLineHeightCentering:(NSAttributedString *)attributedText + frame:(CGRect *)frame { + if (!attributedText || attributedText.length == 0) { + return; + } + + UIFont *font = [attributedText attribute:NSFontAttributeName atIndex:0 effectiveRange:NULL]; + if (!font) { + font = [UIFontMetrics.defaultMetrics scaledFontForFont:[UIFont systemFontOfSize:14]]; + } + + NSParagraphStyle *paragraphStyle = [attributedText attribute:NSParagraphStyleAttributeName atIndex:0 effectiveRange:NULL]; + + if (!paragraphStyle || paragraphStyle.minimumLineHeight == 0) { + return; + } + + CGFloat lineHeight = paragraphStyle.minimumLineHeight; + CGFloat ascent = font.ascender; + CGFloat descent = fabs(font.descender); + CGFloat textHeight = ascent + descent; + + // Adjust vertical offset to ensure text is vertically centered relative to the line height. + CGFloat difference = MAX(0, textHeight - lineHeight); + CGFloat verticalOffset = difference / 2.0; + + frame->origin.y += verticalOffset; +} + - (void)drawRect:(CGRect)rect { if (!_state) { @@ -406,6 +435,9 @@ - (void)drawRect:(CGRect)rect CGRect frame = RCTCGRectFromRect(_layoutMetrics.getContentFrame()); + NSAttributedString *attributedText = RCTNSAttributedStringFromAttributedString(_state->getData().attributedString); + [self adjustFrameForLineHeightCentering:attributedText frame:&frame]; + [nativeTextLayoutManager drawAttributedString:stateData.attributedString paragraphAttributes:_paragraphAttributes frame:frame diff --git a/packages/react-native/ReactCommon/react/renderer/textlayoutmanager/platform/ios/react/renderer/textlayoutmanager/RCTTextLayoutManager.mm b/packages/react-native/ReactCommon/react/renderer/textlayoutmanager/platform/ios/react/renderer/textlayoutmanager/RCTTextLayoutManager.mm index ac553045a9c..613a41aa6dd 100644 --- a/packages/react-native/ReactCommon/react/renderer/textlayoutmanager/platform/ios/react/renderer/textlayoutmanager/RCTTextLayoutManager.mm +++ b/packages/react-native/ReactCommon/react/renderer/textlayoutmanager/platform/ios/react/renderer/textlayoutmanager/RCTTextLayoutManager.mm @@ -207,17 +207,22 @@ - (LinesMeasurements)getLinesForAttributedString:(facebook::react::AttributedStr .size = facebook::react::Size{ .width = usedRect.size.width, .height = usedRect.size.height}}; - CGFloat baseline = [layoutManager locationForGlyphAtIndex:range.location].y; + CGFloat ascender = font.ascender; + CGFloat descender = fabs(font.descender); + CGFloat textHeight = ascender + descender; + CGFloat leading = usedRect.size.height - textHeight; + CGFloat adjustedAscender = ascender + round(leading / 2.0); + CGFloat adjustedDescender = descender + (leading - round(leading / 2.0)); const char *renderedUTF8 = [renderedString UTF8String]; auto line = LineMeasurement{ std::string(renderedUTF8 != nullptr ? renderedUTF8 : ""), rect, - overallRect.size.height - baseline, + adjustedDescender, font.capHeight, - baseline, + adjustedAscender, font.xHeight}; blockParagraphLines->push_back(line); - }]; + }]; return paragraphLines; }