@@ -373,7 +373,7 @@ private static Spannable createSpannableFromAttributedString(
373373
374374 private static Layout createLayout (
375375 Spannable text ,
376- BoringLayout .Metrics boring ,
376+ @ Nullable BoringLayout .Metrics boring ,
377377 float width ,
378378 YogaMeasureMode widthYogaMeasureMode ,
379379 boolean includeFontPadding ,
@@ -417,7 +417,7 @@ private static Layout createLayout(
417417
418418 private static Layout createLayoutWithCorrectRounding (
419419 Spannable text ,
420- BoringLayout .Metrics boring ,
420+ @ Nullable BoringLayout .Metrics boring ,
421421 float width ,
422422 YogaMeasureMode widthYogaMeasureMode ,
423423 boolean includeFontPadding ,
@@ -475,7 +475,7 @@ private static Layout createLayoutWithCorrectRounding(
475475
476476 private static Layout createLayoutWithBuggedRounding (
477477 Spannable text ,
478- BoringLayout .Metrics boring ,
478+ @ Nullable BoringLayout .Metrics boring ,
479479 float width ,
480480 YogaMeasureMode widthYogaMeasureMode ,
481481 boolean includeFontPadding ,
@@ -571,14 +571,12 @@ private static Layout createLayoutWithBuggedRounding(
571571 return layout ;
572572 }
573573
574+ /**
575+ * Sets attributes on the TextPaint, used for content outside the Spannable text, like for empty
576+ * strings, or newlines after the last trailing character
577+ */
574578 private static void updateTextPaint (
575579 TextPaint paint , TextAttributeProps baseTextAttributes , Context context ) {
576- // TextPaint attributes will be used for content outside the Spannable, like for the
577- // hypothetical height of a new line after a trailing newline character (considered part of the
578- // previous line).
579- paint .reset ();
580- paint .setAntiAlias (true );
581-
582580 if (baseTextAttributes .getEffectiveFontSize () != ReactConstants .UNSET ) {
583581 paint .setTextSize (baseTextAttributes .getEffectiveFontSize ());
584582 }
@@ -602,13 +600,33 @@ private static void updateTextPaint(
602600 paint .setFakeBoldText ((missingStyle & Typeface .BOLD ) != 0 );
603601 paint .setTextSkewX ((missingStyle & Typeface .ITALIC ) != 0 ? -0.25f : 0 );
604602 }
605- } else {
606- paint .setTypeface (null );
607603 }
608604 }
609605
610- private static Layout createLayout (
611- @ NonNull Context context ,
606+ /**
607+ * WARNING: This paint should not be used for any layouts which may escape TextLayoutManager, as
608+ * they may need to be drawn later, and may not safely be reused
609+ */
610+ private static TextPaint scratchPaintWithAttributes (
611+ TextAttributeProps baseTextAttributes , Context context ) {
612+ TextPaint paint = Preconditions .checkNotNull (sTextPaintInstance .get ());
613+ paint .setTypeface (null );
614+ paint .setTextSize (12 );
615+ paint .setFakeBoldText (false );
616+ paint .setTextSkewX (0 );
617+ updateTextPaint (paint , baseTextAttributes , context );
618+ return paint ;
619+ }
620+
621+ private static TextPaint newPaintWithAttributes (
622+ TextAttributeProps baseTextAttributes , Context context ) {
623+ TextPaint paint = new TextPaint ();
624+ updateTextPaint (paint , baseTextAttributes , context );
625+ return paint ;
626+ }
627+
628+ private static Layout createLayoutForMeasurement (
629+ Context context ,
612630 MapBuffer attributedString ,
613631 MapBuffer paragraphAttributes ,
614632 float width ,
@@ -625,10 +643,29 @@ private static Layout createLayout(
625643 } else {
626644 TextAttributeProps baseTextAttributes =
627645 TextAttributeProps .fromMapBuffer (attributedString .getMapBuffer (AS_KEY_BASE_ATTRIBUTES ));
628- paint = Preconditions .checkNotNull (sTextPaintInstance .get ());
629- updateTextPaint (paint , baseTextAttributes , context );
646+ paint = scratchPaintWithAttributes (baseTextAttributes , context );
630647 }
631648
649+ return createLayout (
650+ text ,
651+ paint ,
652+ attributedString ,
653+ paragraphAttributes ,
654+ width ,
655+ widthYogaMeasureMode ,
656+ height ,
657+ heightYogaMeasureMode );
658+ }
659+
660+ private static Layout createLayout (
661+ Spannable text ,
662+ TextPaint paint ,
663+ MapBuffer attributedString ,
664+ MapBuffer paragraphAttributes ,
665+ float width ,
666+ YogaMeasureMode widthYogaMeasureMode ,
667+ float height ,
668+ YogaMeasureMode heightYogaMeasureMode ) {
632669 BoringLayout .Metrics boring = isBoring (text , paint );
633670
634671 int textBreakStrategy =
@@ -656,6 +693,7 @@ private static Layout createLayout(
656693 paragraphAttributes .getString (PA_KEY_ELLIPSIZE_MODE ))
657694 : null ;
658695
696+ // T226571629: textAlign should be moved to ParagraphAttributes
659697 @ Nullable String alignmentAttr = getTextAlignmentAttr (attributedString );
660698 Layout .Alignment alignment = getTextAlignment (attributedString , text , alignmentAttr );
661699 int justificationMode = getTextJustificationMode (alignmentAttr );
@@ -699,24 +737,28 @@ private static Layout createLayout(
699737
700738 @ UnstableReactNativeAPI
701739 public static PreparedLayout createPreparedLayout (
702- @ NonNull Context context ,
740+ Context context ,
703741 ReadableMapBuffer attributedString ,
704742 ReadableMapBuffer paragraphAttributes ,
705743 float width ,
706744 YogaMeasureMode widthYogaMeasureMode ,
707745 float height ,
708746 YogaMeasureMode heightYogaMeasureMode ,
709747 @ Nullable ReactTextViewManagerCallback reactTextViewManagerCallback ) {
748+ Spannable text =
749+ getOrCreateSpannableForText (context , attributedString , reactTextViewManagerCallback );
750+ TextAttributeProps baseTextAttributes =
751+ TextAttributeProps .fromMapBuffer (attributedString .getMapBuffer (AS_KEY_BASE_ATTRIBUTES ));
710752 Layout layout =
711753 TextLayoutManager .createLayout (
712- Preconditions .checkNotNull (context ),
754+ text ,
755+ newPaintWithAttributes (baseTextAttributes , context ),
713756 attributedString ,
714757 paragraphAttributes ,
715758 width ,
716759 widthYogaMeasureMode ,
717760 height ,
718- heightYogaMeasureMode ,
719- reactTextViewManagerCallback );
761+ heightYogaMeasureMode );
720762
721763 int maximumNumberOfLines =
722764 paragraphAttributes .contains (TextLayoutManager .PA_KEY_MAX_NUMBER_OF_LINES )
@@ -828,7 +870,7 @@ public static long measureText(
828870 @ Nullable float [] attachmentsPositions ) {
829871 // TODO(5578671): Handle text direction (see View#getTextDirectionHeuristic)
830872 Layout layout =
831- createLayout (
873+ createLayoutForMeasurement (
832874 context ,
833875 attributedString ,
834876 paragraphAttributes ,
@@ -1151,23 +1193,23 @@ private static int nextAttachmentMetrics(
11511193 }
11521194
11531195 public static WritableArray measureLines (
1154- @ NonNull Context context ,
1196+ Context context ,
11551197 MapBuffer attributedString ,
11561198 MapBuffer paragraphAttributes ,
11571199 float width ,
11581200 float height ) {
11591201 Layout layout =
1160- createLayout (
1202+ createLayoutForMeasurement (
11611203 context ,
11621204 attributedString ,
11631205 paragraphAttributes ,
11641206 width ,
11651207 YogaMeasureMode .EXACTLY ,
11661208 height ,
11671209 YogaMeasureMode .EXACTLY ,
1210+ // TODO T226571550: Fix measureLines with ReactTextViewManagerCallback
11681211 null );
1169- return FontMetricsUtil .getFontMetrics (
1170- layout .getText (), layout , Preconditions .checkNotNull (sTextPaintInstance .get ()), context );
1212+ return FontMetricsUtil .getFontMetrics (layout .getText (), layout , context );
11711213 }
11721214
11731215 private static @ Nullable BoringLayout .Metrics isBoring (Spannable text , TextPaint paint ) {
0 commit comments