Skip to content

Commit 45663b8

Browse files
committed
fix(android): full rewrite to improve perfs
1 parent 94c67a7 commit 45663b8

File tree

3 files changed

+781
-91
lines changed

3 files changed

+781
-91
lines changed

plugin/platforms/android/java/com/nativescript/label/Font.java

+165-70
Original file line numberDiff line numberDiff line change
@@ -4,21 +4,25 @@
44
import android.content.res.AssetManager;
55
import android.graphics.Typeface;
66
import android.os.Build;
7-
import android.text.Spannable;
87
import android.text.SpannableStringBuilder;
98
import android.text.Spanned;
9+
import android.text.style.AbsoluteSizeSpan;
10+
import android.text.style.BackgroundColorSpan;
11+
import android.text.style.ForegroundColorSpan;
1012
import android.text.style.TypefaceSpan;
1113
import android.util.Log;
1214

1315
import java.io.File;
1416
import java.util.ArrayList;
1517
import java.util.HashMap;
18+
import java.util.StringTokenizer;
1619

1720
import androidx.core.text.HtmlCompat;
1821

1922
public class Font {
2023
static AssetManager appAssets;
2124
static HashMap<String, Typeface> typefaceCache = new HashMap();
25+
static HashMap<String, Typeface> typefaceCreatedCache = new HashMap();
2226

2327
static final String TAG = "Font";
2428

@@ -93,25 +97,25 @@ public static String getFontWeightSuffix(String fontWeight) {
9397
return "";
9498
}
9599
switch (fontWeight) {
96-
case FontWeight.THIN:
97-
return Build.VERSION.SDK_INT >= 16 ? "-thin" : "";
98-
case FontWeight.EXTRA_LIGHT:
99-
case FontWeight.LIGHT:
100-
return Build.VERSION.SDK_INT >= 16 ? "-light" : "";
101-
case FontWeight.NORMAL:
102-
case "400":
103-
return "";
104-
case FontWeight.MEDIUM:
105-
case FontWeight.SEMI_BOLD:
106-
return Build.VERSION.SDK_INT >= 21 ? "-medium" : "";
107-
case FontWeight.BOLD:
108-
case "700":
109-
case FontWeight.EXTRA_BOLD:
110-
return "";
111-
case FontWeight.BLACK:
112-
return Build.VERSION.SDK_INT >= 21 ? "-black" : "";
113-
default:
114-
throw new Error("Invalid font weight:" + fontWeight);
100+
case FontWeight.THIN:
101+
return Build.VERSION.SDK_INT >= 16 ? "-thin" : "";
102+
case FontWeight.EXTRA_LIGHT:
103+
case FontWeight.LIGHT:
104+
return Build.VERSION.SDK_INT >= 16 ? "-light" : "";
105+
case FontWeight.NORMAL:
106+
case "400":
107+
return "";
108+
case FontWeight.MEDIUM:
109+
case FontWeight.SEMI_BOLD:
110+
return Build.VERSION.SDK_INT >= 21 ? "-medium" : "";
111+
case FontWeight.BOLD:
112+
case "700":
113+
case FontWeight.EXTRA_BOLD:
114+
return "";
115+
case FontWeight.BLACK:
116+
return Build.VERSION.SDK_INT >= 21 ? "-black" : "";
117+
default:
118+
throw new Error("Invalid font weight:" + fontWeight);
115119
}
116120
}
117121

@@ -120,20 +124,26 @@ public static ArrayList<String> parseFontFamily(String value) {
120124
if (value == null) {
121125
return result;
122126
}
123-
124-
String[] split = value.split(",");
125-
for (int i = 0; i < split.length; i++) {
126-
String str = split[i].trim().replaceAll("['\"]+", "");
127-
if (str != null) {
128-
result.add(str);
129-
}
127+
if (!value.contains(",")) {
128+
result.add(value);
129+
return result;
130130
}
131131

132+
// not removing the "['\"]+" and not trimming make the parseFontFamily much faster!
133+
// should be done in span/text properties
134+
StringTokenizer st = new StringTokenizer(value, ",");
135+
while(st.hasMoreTokens()) {
136+
result.add(st.nextToken());
137+
}
132138
return result;
133139
}
134140

135141
public static Typeface createTypeface(Context context, String fontFolder, String fontFamily, String fontWeight,
136-
boolean isBold, boolean isItalic) {
142+
boolean isBold, boolean isItalic) {
143+
final String cacheKey = fontFamily + fontWeight + isBold + isItalic;
144+
if (typefaceCreatedCache.containsKey(cacheKey)) {
145+
return typefaceCreatedCache.get(cacheKey);
146+
}
137147
int fontStyle = 0;
138148
if (isBold) {
139149
fontStyle |= Typeface.BOLD;
@@ -147,25 +157,26 @@ public static Typeface createTypeface(Context context, String fontFolder, String
147157
Typeface result = null;
148158
for (int i = 0; i < fonts.size(); i++) {
149159
switch (fonts.get(i).toLowerCase()) {
150-
case genericFontFamilies.serif:
151-
result = Typeface.create("serif" + getFontWeightSuffix(fontWeight), fontStyle);
152-
break;
153-
154-
case genericFontFamilies.sansSerif:
155-
case genericFontFamilies.system:
156-
result = Typeface.create("sans-serif" + getFontWeightSuffix(fontWeight), fontStyle);
157-
break;
158-
159-
case genericFontFamilies.monospace:
160-
result = Typeface.create("monospace" + getFontWeightSuffix(fontWeight), fontStyle);
161-
break;
162-
163-
default:
164-
result = loadFontFromFile(context, fontFolder, fonts.get(i));
165-
if (result != null && fontStyle != 0) {
166-
result = Typeface.create(result, fontStyle);
167-
}
168-
break;
160+
case genericFontFamilies.serif:
161+
result = Typeface.create("serif" + getFontWeightSuffix(fontWeight), fontStyle);
162+
break;
163+
164+
case genericFontFamilies.sansSerif:
165+
case genericFontFamilies.system:
166+
result = Typeface.create("sans-serif" + getFontWeightSuffix(fontWeight), fontStyle);
167+
break;
168+
169+
case genericFontFamilies.monospace:
170+
result = Typeface.create("monospace" + getFontWeightSuffix(fontWeight), fontStyle);
171+
break;
172+
173+
default:
174+
result = loadFontFromFile(context, fontFolder, fonts.get(i));
175+
176+
if (result != null && fontStyle != 0) {
177+
result = Typeface.create(result, fontStyle);
178+
}
179+
break;
169180
}
170181

171182
if (result != null) {
@@ -177,12 +188,12 @@ public static Typeface createTypeface(Context context, String fontFolder, String
177188
if (result == null) {
178189
result = Typeface.create("sans-serif" + getFontWeightSuffix(fontWeight), fontStyle);
179190
}
180-
191+
typefaceCreatedCache.put(cacheKey, result);
181192
return result;
182193
}
183194

184195
public static SpannableStringBuilder stringBuilderFromHtmlString(Context context, String fontFolder,
185-
String htmlString) {
196+
String htmlString) {
186197
if (htmlString == null) {
187198
return null;
188199
}
@@ -200,10 +211,6 @@ public static SpannableStringBuilder stringBuilderFromHtmlString(Context context
200211
if (split.length > 1) {
201212
style = split[1];
202213
}
203-
// String style = fontFamily.split("-")[1] || builder.removeSpan(span);
204-
// const
205-
// font = new Font(fontFamily, 0, style == = 'italic' ? 'italic' : 'normal',
206-
// style == = 'bold' ? 'bold' : 'normal');
207214
Typeface typeface = createTypeface(context, fontFolder, fontFamily, style == "bold" ? "bold" : "normal",
208215
style == "bold", style == "italic");
209216

@@ -216,25 +223,113 @@ public static SpannableStringBuilder stringBuilderFromHtmlString(Context context
216223
}
217224
}
218225

219-
// const ssb = new android.text.SpannableStringBuilder();
220-
// for (let i = 0, spanStart = 0, spanLength = 0, length =
221-
// formattedString.spans.length; i < length; i++) {
222-
// const span = formattedString.spans.getItem(i);
223-
// const text = span.text;
224-
// const textTransform = (<TextBase>formattedString.parent).textTransform;
225-
// let spanText = (text === null || text === undefined) ? "" : text.toString();
226-
// if (textTransform && textTransform !== "none") {
227-
// spanText = getTransformedText(spanText, textTransform);
228-
// }
226+
return builder;
227+
}
228+
static char SpanSeparator = (char)0x1F;
229+
static char PropertySeparator = (char)0x1E;
230+
static ArrayList<ArrayList<String>> parseFormattedString(String formattedString) {
231+
ArrayList<ArrayList<String>> result = new ArrayList();
232+
233+
final int len = formattedString.length();
234+
String buffer = "";
235+
ArrayList<String> spanProps = new ArrayList();
236+
for (int i = 0; i < len; i++) {
237+
char c = formattedString.charAt(i);
238+
if (c == PropertySeparator) {
239+
spanProps.add(buffer);
240+
buffer = "";
241+
} else if (c == SpanSeparator) {
242+
spanProps.add(buffer);
243+
result.add(spanProps);
244+
buffer = "";
245+
spanProps = new ArrayList();
246+
} else {
247+
buffer += c;
248+
}
249+
}
250+
spanProps.add(buffer);
251+
result.add(spanProps);
229252

230-
// spanLength = spanText.length;
231-
// if (spanLength > 0) {
232-
// ssb.insert(spanStart, spanText);
233-
// setSpanModifiers(ssb, span, spanStart, spanStart + spanLength);
234-
// spanStart += spanLength;
235-
// }
253+
return result;
254+
}
255+
256+
257+
public static void setSpanModifiers(Context context, String fontFolder, SpannableStringBuilder ssb, ArrayList<String> span, int start, int end) {
258+
boolean bold = span.get(2) == "1";
259+
boolean italic = span.get(3) == "1";
260+
261+
if (bold && italic) {
262+
ssb.setSpan(new android.text.style.StyleSpan(android.graphics.Typeface.BOLD_ITALIC), start, end, android.text.Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
263+
} else if (bold) {
264+
ssb.setSpan(new android.text.style.StyleSpan(android.graphics.Typeface.BOLD), start, end, android.text.Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
265+
} else if (italic) {
266+
ssb.setSpan(new android.text.style.StyleSpan(android.graphics.Typeface.ITALIC), start, end, android.text.Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
267+
}
268+
269+
String fontFamily = span.get(0);
270+
if (!fontFamily.equals("undefined") ) {
271+
Typeface typeface = createTypeface(context, fontFolder, fontFamily, bold ? "bold" : "normal",
272+
bold, italic);
273+
// const font = new Font(fontFamily, 0, (italic) ? "italic" : "normal", (bold) ? "bold" : "normal");
274+
// const typeface = font.getAndroidTypeface() || android.graphics.Typeface.create(fontFamily, 0);
275+
TypefaceSpan typefaceSpan = new CustomTypefaceSpan(fontFamily, typeface);
276+
ssb.setSpan(typefaceSpan, start, end, android.text.Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
277+
}
278+
279+
String fontSize = span.get(1);
280+
if (!fontSize.equals("undefined") ) {
281+
ssb.setSpan(new AbsoluteSizeSpan(Math.round(Float.parseFloat(fontSize) * context.getResources().getDisplayMetrics().density)), start, end, android.text.Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
282+
}
283+
284+
String color = span.get(5);
285+
if (!color.equals("undefined") ) {
286+
ssb.setSpan(new ForegroundColorSpan(Integer.parseInt(color)), start, end, android.text.Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
287+
}
288+
289+
290+
String backgroundColor = span.get(6);
291+
292+
if (!backgroundColor.equals("undefined") ) {
293+
ssb.setSpan(new BackgroundColorSpan(Integer.parseInt(backgroundColor)), start, end, android.text.Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
294+
}
295+
296+
297+
String textDecoration = span.get(4);
298+
if (textDecoration.contains("underline")) {
299+
ssb.setSpan(new android.text.style.UnderlineSpan(), start, end, android.text.Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
300+
}
301+
302+
if (textDecoration.contains("line-through")) {
303+
ssb.setSpan(new android.text.style.StrikethroughSpan(), start, end, android.text.Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
304+
}
305+
long stopTime = System.nanoTime();
306+
// TODO: Implement letterSpacing for Span here.
307+
// const letterSpacing = formattedString.parent.style.letterSpacing;
308+
// if (letterSpacing > 0) {
309+
// ssb.setSpan(new android.text.style.ScaleXSpan((letterSpacing + 1) / 10), start, end, android.text.Spannable.SPAN_EXCLUSIVE_EXCLUSIVE);
236310
// }
311+
}
237312

238-
return builder;
313+
314+
315+
public static SpannableStringBuilder stringBuilderFromFormattedString(Context context, String fontFolder,
316+
String formattedString) {
317+
if (formattedString == null) {
318+
return null;
319+
}
320+
ArrayList<ArrayList<String>> parsedFormattedString = parseFormattedString(formattedString);
321+
SpannableStringBuilder ssb = new SpannableStringBuilder();
322+
for (int i = 0, spanStart = 0, spanLength = 0, length = parsedFormattedString.size(); i < length; i++) {
323+
ArrayList<String> span = parsedFormattedString.get(i);
324+
String text = span.get(7);
325+
spanLength = text.length();
326+
if (spanLength > 0) {
327+
ssb.insert(spanStart, text);
328+
setSpanModifiers(context, fontFolder, ssb, span, spanStart, spanStart + spanLength);
329+
spanStart += spanLength;
330+
}
331+
}
332+
333+
return ssb;
239334
}
240335
}

plugin/platforms/android/java/com/nativescript/label/Label.java

+20-1
Original file line numberDiff line numberDiff line change
@@ -16,15 +16,19 @@ public TransformationMethod getTransformationMethod() {
1616
public void setTransformationMethod(TransformationMethod value) {
1717

1818
}
19+
1920
public void setSingleLine(boolean value) {
2021

2122
}
23+
2224
public void setMinLines(int value) {
2325

2426
}
27+
2528
public void setMinHeight(int value) {
2629

2730
}
31+
2832
public void setMaxHeight(int value) {
2933

3034
}
@@ -33,31 +37,46 @@ public int getMinHeight() {
3337
return 0;
3438

3539
}
40+
3641
public int getMinLines() {
3742
return 0;
3843

3944
}
45+
4046
public int getMaxHeight() {
4147
return 0;
4248

4349
}
50+
4451
public int getLineSpacingExtra() {
4552
return 0;
4653

4754
}
48-
public void setLineSpacingExtra(int value) {
55+
56+
public void setLineSpacing(float add, float mult) {
4957

5058
}
5159

60+
public void setLetterSpacing(float value) {
61+
getTextPaint().setLetterSpacing(value);
62+
}
63+
64+
public float getLetterSpacing() {
65+
return getTextPaint().getLetterSpacing();
66+
}
67+
5268
public Typeface getTypeface() {
5369
return getTextPaint().getTypeface();
5470
}
71+
5572
public void setTypeface(Typeface typeface) {
5673
getTextPaint().setTypeface(typeface);
5774
}
75+
5876
public int getPaintFlags() {
5977
return getTextPaint().getFlags();
6078
}
79+
6180
public void setPaintFlags(int typeface) {
6281
getTextPaint().setFlags(typeface);
6382
}

0 commit comments

Comments
 (0)