Skip to content

[android] improve FormattedString performance #21712

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 1 commit into from
Apr 9, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -64,8 +64,6 @@ internal static SpannableString ToSpannableStringNewWay(

var builder = new StringBuilder();

var fontMetrics = PlatformInterop.GetFontMetrics(context, defaultFontSize);

for (int i = 0; i < formattedString.Spans.Count; i++)
{
Span span = formattedString.Spans[i];
Expand Down Expand Up @@ -103,22 +101,22 @@ internal static SpannableString ToSpannableStringNewWay(
spannable.SetSpan(new BackgroundColorSpan(span.BackgroundColor.ToPlatform()), start, end, SpanTypes.InclusiveExclusive);

// LineHeight
if (span.LineHeight >= 0 && fontMetrics is not null)
spannable.SetSpan(new LineHeightSpan(span.LineHeight, fontMetrics.Top), start, end, SpanTypes.InclusiveExclusive);
if (span.LineHeight >= 0)
spannable.SetSpan(new PlatformLineHeightSpan(context, (float)span.LineHeight, (float)defaultFontSize), start, end, SpanTypes.InclusiveExclusive);

// CharacterSpacing
var characterSpacing = span.CharacterSpacing >= 0
? span.CharacterSpacing
: defaultCharacterSpacing;
if (characterSpacing >= 0)
spannable.SetSpan(new LetterSpacingSpan(characterSpacing.ToEm()), start, end, SpanTypes.InclusiveInclusive);
spannable.SetSpan(new PlatformFontSpan(characterSpacing.ToEm()), start, end, SpanTypes.InclusiveInclusive);

// Font
var font = span.ToFont(defaultFontSize);
if (font.IsDefault && defaultFont.HasValue)
font = defaultFont.Value;
if (!font.IsDefault)
spannable.SetSpan(new FontSpan(font, fontManager, context), start, end, SpanTypes.InclusiveInclusive);
spannable.SetSpan(new PlatformFontSpan(context ?? AAplication.Context, font.ToTypeface(fontManager), font.AutoScalingEnabled, (float)font.Size), start, end, SpanTypes.InclusiveInclusive);

// TextDecorations
var textDecorations = span.IsSet(Span.TextDecorationsProperty)
Expand Down Expand Up @@ -226,86 +224,5 @@ public static void RecalculateSpanPositions(this TextView textView, Label elemen
((ISpatialElement)span).Region = Region.FromRectangles(spanRectangles).Inflate(10);
}
}

class FontSpan : MetricAffectingSpan
{
readonly Font _font;
readonly IFontManager _fontManager;
readonly Context? _context;

public FontSpan(Font font, IFontManager fontManager, Context? context)
{
_font = font;
_fontManager = fontManager;
_context = context;
}

public override void UpdateDrawState(TextPaint? tp)
{
if (tp != null)
Apply(tp);
}

public override void UpdateMeasureState(TextPaint p)
{
Apply(p);
}

void Apply(TextPaint paint)
{
paint.SetTypeface(_font.ToTypeface(_fontManager));

paint.TextSize = TypedValue.ApplyDimension(
_font.AutoScalingEnabled ? ComplexUnitType.Sp : ComplexUnitType.Dip,
(float)_font.Size,
(_context ?? AAplication.Context)?.Resources?.DisplayMetrics);
}
}

class LetterSpacingSpan : MetricAffectingSpan
{
readonly float _letterSpacing;

public LetterSpacingSpan(double letterSpacing)
{
_letterSpacing = (float)letterSpacing;
}

public override void UpdateDrawState(TextPaint? tp)
{
if (tp != null)
Apply(tp);
}

public override void UpdateMeasureState(TextPaint p)
{
Apply(p);
}

void Apply(TextPaint paint)
{
paint.LetterSpacing = _letterSpacing;
}
}

class LineHeightSpan : Java.Lang.Object, ILineHeightSpan
{
readonly double _relativeLineHeight;
readonly double _originalTop;

public LineHeightSpan(double relativeLineHeight, double originalTop)
{
_relativeLineHeight = relativeLineHeight;
_originalTop = originalTop;
}

public void ChooseHeight(Java.Lang.ICharSequence? text, int start, int end, int spanstartv, int lineHeight, Paint.FontMetricsInt? fm)
{
if (fm is null)
return;

fm.Ascent = (int)(_originalTop * _relativeLineHeight);
}
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,68 @@
package com.microsoft.maui;

import android.content.Context;
import android.graphics.Typeface;
import android.text.TextPaint;
import android.text.style.MetricAffectingSpan;
import android.util.TypedValue;

import androidx.annotation.NonNull;

/**
* Class for setting letterSpacing, textSize, or typeface on a Span
*/
public class PlatformFontSpan extends MetricAffectingSpan {
// NOTE: java.lang.Float is a "nullable" float
private Float letterSpacing;
private Float textSize;
private Typeface typeface;

/**
* Constructor for setting letterSpacing-only
* @param letterSpacing
*/
public PlatformFontSpan(float letterSpacing) {
this.letterSpacing = letterSpacing;
}

/**
* Constructor for setting typeface and computing textSize
* @param context
* @param typeface
* @param autoScalingEnabled
* @param fontSize
*/
public PlatformFontSpan(@NonNull Context context, Typeface typeface, boolean autoScalingEnabled, float fontSize) {
this.typeface = typeface;
textSize = TypedValue.applyDimension(
autoScalingEnabled ? TypedValue.COMPLEX_UNIT_SP : TypedValue.COMPLEX_UNIT_DIP,
fontSize,
context.getResources().getDisplayMetrics()
);
}

@Override
public void updateDrawState(TextPaint textPaint) {
if (textPaint != null) {
apply(textPaint);
}
}

@Override
public void updateMeasureState(@NonNull TextPaint textPaint) {
apply(textPaint);
}

void apply(TextPaint textPaint)
{
if (typeface != null) {
textPaint.setTypeface(typeface);
}
if (textSize != null) {
textPaint.setTextSize(textSize);
}
if (letterSpacing != null) {
textPaint.setLetterSpacing(letterSpacing);
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -598,7 +598,7 @@ public static Rect getCurrentWindowMetrics(Activity activity) {
* @param defaultFontSize
* @return FontMetrics object or null if context or display metrics is null
*/
public static Paint.FontMetrics getFontMetrics(Context context, double defaultFontSize) {
public static Paint.FontMetrics getFontMetrics(Context context, float defaultFontSize) {
if (context == null)
return null;

Expand All @@ -608,7 +608,7 @@ public static Paint.FontMetrics getFontMetrics(Context context, double defaultFo
setTextSize(
TypedValue.applyDimension(
TypedValue.COMPLEX_UNIT_SP,
(float) defaultFontSize,
defaultFontSize,
metrics
));
}}.getFontMetrics();
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
package com.microsoft.maui;

import android.content.Context;
import android.graphics.Paint;
import android.text.style.LineHeightSpan;

/**
* Class for setting a relativeLineHeight on a Span
*/
public class PlatformLineHeightSpan implements LineHeightSpan {
private final float relativeLineHeight;
private final Float top; //NOTE: nullable float

public PlatformLineHeightSpan(Context context, float relativeLineHeight, float defaultFontSize) {
this.relativeLineHeight = relativeLineHeight;
Paint.FontMetrics fontMetrics = PlatformInterop.getFontMetrics(context, defaultFontSize);
this.top = fontMetrics != null ? fontMetrics.top : null;
}

@Override
public void chooseHeight(CharSequence charSequence, int i, int i1, int i2, int i3, Paint.FontMetricsInt fontMetricsInt) {
if (fontMetricsInt != null) {
float top = this.top != null ? this.top : fontMetricsInt.top;
fontMetricsInt.ascent = (int)(top * relativeLineHeight);
}
}
}
Binary file modified src/Core/src/maui.aar
Binary file not shown.