4
4
import android .content .res .AssetManager ;
5
5
import android .graphics .Typeface ;
6
6
import android .os .Build ;
7
- import android .text .Spannable ;
8
7
import android .text .SpannableStringBuilder ;
9
8
import android .text .Spanned ;
9
+ import android .text .style .AbsoluteSizeSpan ;
10
+ import android .text .style .BackgroundColorSpan ;
11
+ import android .text .style .ForegroundColorSpan ;
10
12
import android .text .style .TypefaceSpan ;
11
13
import android .util .Log ;
12
14
13
15
import java .io .File ;
14
16
import java .util .ArrayList ;
15
17
import java .util .HashMap ;
18
+ import java .util .StringTokenizer ;
16
19
17
20
import androidx .core .text .HtmlCompat ;
18
21
19
22
public class Font {
20
23
static AssetManager appAssets ;
21
24
static HashMap <String , Typeface > typefaceCache = new HashMap ();
25
+ static HashMap <String , Typeface > typefaceCreatedCache = new HashMap ();
22
26
23
27
static final String TAG = "Font" ;
24
28
@@ -93,25 +97,25 @@ public static String getFontWeightSuffix(String fontWeight) {
93
97
return "" ;
94
98
}
95
99
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 );
115
119
}
116
120
}
117
121
@@ -120,20 +124,26 @@ public static ArrayList<String> parseFontFamily(String value) {
120
124
if (value == null ) {
121
125
return result ;
122
126
}
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 ;
130
130
}
131
131
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
+ }
132
138
return result ;
133
139
}
134
140
135
141
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
+ }
137
147
int fontStyle = 0 ;
138
148
if (isBold ) {
139
149
fontStyle |= Typeface .BOLD ;
@@ -147,25 +157,26 @@ public static Typeface createTypeface(Context context, String fontFolder, String
147
157
Typeface result = null ;
148
158
for (int i = 0 ; i < fonts .size (); i ++) {
149
159
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 ;
169
180
}
170
181
171
182
if (result != null ) {
@@ -177,12 +188,12 @@ public static Typeface createTypeface(Context context, String fontFolder, String
177
188
if (result == null ) {
178
189
result = Typeface .create ("sans-serif" + getFontWeightSuffix (fontWeight ), fontStyle );
179
190
}
180
-
191
+ typefaceCreatedCache . put ( cacheKey , result );
181
192
return result ;
182
193
}
183
194
184
195
public static SpannableStringBuilder stringBuilderFromHtmlString (Context context , String fontFolder ,
185
- String htmlString ) {
196
+ String htmlString ) {
186
197
if (htmlString == null ) {
187
198
return null ;
188
199
}
@@ -200,10 +211,6 @@ public static SpannableStringBuilder stringBuilderFromHtmlString(Context context
200
211
if (split .length > 1 ) {
201
212
style = split [1 ];
202
213
}
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');
207
214
Typeface typeface = createTypeface (context , fontFolder , fontFamily , style == "bold" ? "bold" : "normal" ,
208
215
style == "bold" , style == "italic" );
209
216
@@ -216,25 +223,113 @@ public static SpannableStringBuilder stringBuilderFromHtmlString(Context context
216
223
}
217
224
}
218
225
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 );
229
252
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);
236
310
// }
311
+ }
237
312
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 ;
239
334
}
240
335
}
0 commit comments