Custom fonts for TextView based on languages inside String - android

I have two font ttf files that must be applied on a TextView based on languages inside String. So e.g. consider this sample text:
hey what's up ضعيف
I can just apply a typeface span based on language but it requires custom markup in every string that is fetched from our server e.g.
<ttf1>hey what's up <ttf1><ttf2>ضعيف</ttf2>
And parsing every String at run time will give a performance hit. Is there any other approach to achieve this?
For start lets say I need to do this just for direction of text i.e. RTL and LTR so in above example English is LTR and Arabic is RTL. Will this be any different?
I have tried merging those two font files but there are line height issues and if I fix it for one font file it gets broken for other file.

I found a more elegant solution than manual markup with help of someone:
String paragraph = "hey what's up ضعيف";
int NO_FLAG = 0;
Bidi bidi = new Bidi(paragraph, NO_FLAG);
int runCount = bidi.getRunCount();
for (int i = 0; i < runCount; i++) {
String ltrtl = bidi.getRunLevel(i) % 2 == 0 ? "ltr" : "rtl";
String subString = paragraph.substring(bidi.getRunStart(i), bidi.getRunLimit(i));
Log.d(">>bidi:" + i, subString+" is "+ltrtl);
}
prints:
hey what's up is ltr
ضعيف is rtl
So now one can easily build TypefaceSpan or MetricAffectingSpan based on language direction like this:
SpannableString spanString = new SpannableString(paragraph);
for (int i = 0; i < runCount; i++) {
Object span = bidi.getRunLevel(i) % 2 == 0 ? ltrFontSpan : rtlFontSpan;
spanString.setSpan(span, bidi.getRunStart(i), bidi.getRunLimit(i), Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
}
textView.setText(spanString);

Related

Android HtmlCompat.toHtml(Spanned) returns improperly nested HTML tags

I have an Edittext (binding.text using view binding) that contains text styled with StyleSpans, i.e. bold and italics. To save the formatted text, I use HtmlCompat.toHtml(spannable) in Kotlin to convert it into HTML.
var htmlString = HtmlCompat.toHtml(SpannableString(binding.text.text), HtmlCompat.FROM_HTML_MODE_LEGACY)
However, the HTML returned is improperly nested if the text has bold and italics applied to it at the same time.
Hello World: outputs <p dir="ltr><b><i>Hello world</b></i></p>
As you can see, the tags applied are <b><i> </b></i> instead of <b><i> </i></b>.
When the text is not formatted, or either in bold or italics, then proper HTML is returned:
Hello World: outputs <p dir="ltr>Hello world</p>
Hello World: outputs <p dir="ltr><b>Hello world</b></p>
I suppose the function likes to put <b> and </b> first as opposed to <i> and </i>, but this causes weird results as shown.
So the question: How do I get the function to return correctly formatted HTML?
You are right about the ordering, although the HTML should still display correctly in a browser. The offending code is in Html.java. Here is where the translations occur for <b></b> and <i></i>:
...
for (int j = 0; j < style.length; j++) {
...
if (style[j] instanceof StyleSpan) {
int s = ((StyleSpan) style[j]).getStyle();
if ((s & Typeface.BOLD) != 0) {
out.append("<b>");
}
if ((s & Typeface.ITALIC) != 0) {
out.append("<i>");
}
}
...
for (int j = style.length - 1; j >= 0; j--) {
,,,
if (style[j] instanceof StyleSpan) {
int s = ((StyleSpan) style[j]).getStyle();
if ((s & Typeface.BOLD) != 0) {
out.append("</b>");
}
if ((s & Typeface.ITALIC) != 0) {
out.append("</i>");
}
}
style is an array of spans. This code scans forward through the spans to place the opening tags and backward to close the tags. So, it looks like if your bold span and italic spans are different spans spanning the same text, then this code will produce properly nested tags.
However, if one StyleSpan specifies both italic and bold, then the tag order will be as you report <b><i></b></i>. The code that outputs the closing tags should place <i> before b. This looks like a bug and should be reported.
I assume that you have one StyleSpan that is a bolded-italics span. The fix would be to split that span into two separate spans - a bold span and an italics span. The code should then work as expected.

Setting minimum length for auto link

I have a big paragraph which may have numbers, email addresses and links. So I have to set setAutoLinkMask(Linkify.PHONE_NUMBERS | Linkify.EMAIL_ADDRESSES | Linkify.WEB_URLS) for my textview.
The content may contain digits of varying numbers. I want to set numbers having atleast 8 digits as phone number links.(For Eg : 12345678)
Is it possible to set minimum length for Linkify.PHONE_NUMBERS ?
Is there anyway to achieve this?
In case you can use Linkify.MatchFilter to specify minimum length or your some other requirements. There is not any direct way provided by Android.
Also somewhere in this SO post found some good examples.
use below pattern :
SpannableString buffer = new SpannableString(text);
Pattern pattern = Pattern.compile("^[0-9]\d{7,9}$");
Linkify.addLinks(buffer , pattern,"");
Yes, its possible. I researched this phenomenon :-)
To set the minimum length for a phone number, use this code:
private final Linkify.MatchFilter matchFilterForPhone = new Linkify.MatchFilter() {
#Override
public boolean acceptMatch(CharSequence s, int start, int end) {
int digitCount = 0;
for (int i = start; i < end; i++) {
if (Character.isDigit(s.charAt(i))) {
digitCount++;
if (digitCount >= 6) { // HERE: number 6 is minimum
return true;
}
}
}
return false;
}
};
To properly format and link phone numbers, use:
final SpannableString s = new SpannableString(myTekst);
Linkify.addLinks(s, android.util.Patterns.PHONE, "tel:", matchFilterForPhone, Linkify.sPhoneNumberTransformFilter);
Now place the formatted s in your TextView, and call:
findViewById(R.id.message).setLinkTextColor(Color.BLUE);
findViewById(R.id.message).setMovementMethod(LinkMovementMethod.getInstance());
That's all. Thanks for vote.

TextView with mixed languages

I have a TextView with 2 lines. first line rtl language (let's say hebrew), second line is ltr language (let's say english)
The View result is something like:
אחת שתיים שלוש
one two three
what i want: align rtl in that case
אחת שתיים שלוש
one two three
I've tried using setTextDirection() with TEXT_DIRECTION_FIRST_STRONG
but alas the results were the same. Also tried TEXT_ANY_RTL without success
myTextView.setTextDirection(View.TEXT_DIRECTION_FIRST_STRONG);
if i'm using TEXT_DIRECTION_RTL it's working as expected but this is not really a solution because most of the time the TextView will contain only one language.
Is this solvable?
--- UPDATE ---
How i'm populating the TextView
SpannableStringBuilder ssb = new SpannableStringBuilder(titleText);
int end = titlText.length();
ssb.append("\n").append(otheText);
ssb.setSpan(new AbsoluteSizeSpan(size), end, ssb.length(), Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
textView.setText(ssb);
Why not just use two TextViews?
I've managed to solve this problem using Character.getDirectionality.
The first char that is a directional char will signify the TextView direction
#TargetApi(Build.VERSION_CODES.JELLY_BEAN_MR1)
public static int getTextDirection(String text) {
final int length = text.length();
for (int i = 0; i < length; i++) {
final char c = text.charAt(i);
final byte directionality = Character.getDirectionality(c);
if(directionality == Character.DIRECTIONALITY_LEFT_TO_RIGHT){
return View.TEXT_DIRECTION_LTR;
}
else if(directionality == Character.DIRECTIONALITY_RIGHT_TO_LEFT){
return View.TEXT_DIRECTION_RTL;
}
}
return View.TEXT_DIRECTION_ANY_RTL;
}
and then:
textView.setTextDirection(textDirection);
I Strongly believe that TEXT_DIRECTION_FIRST_STRONG is supposed to do the exact same thing according to the docs. sadly it's not the case.
I'm not accepting my answer in hope that someone will suggest better solution
What About TEXT_DIRECTION_ANY_RTL
This text direction is using "any-RTL" algorithm. The paragraph direction is RTL if it contains any strong RTL character, otherwise it is LTR if it contains any strong LTR characters. If there are neither, the paragraph direction is the view's resolved layout direction.

Small Caps on TextViews, EditTexts, and Buttons in Android

Is there something I can do to make the text look in small caps/capital? As described here: http://en.wikipedia.org/wiki/Small_caps. I used a converter but some characters are missing.
EDIT 2015-08-02: As of API 21 (Lollipop) you can simply add:
android:fontFeatureSettings="smcp"
to your TextView declaration in XML, or at runtime, invoke:
textView.setFontFeatureSettings("smcp");
Of course, this only works for API 21 and up, so you'd still have to handle the old solution manually until you are only supporting Lollipop and above.
Being a bit of a typography geek at heart, this seemed like a really good question. I got to learn some more about Unicode today, as well as an answer for your question. :)
First, you'll need to have a font that includes "actual" small-caps characters. I'm assuming you know that since you're asking, but typically most professional fonts include these. Unfortunately most professional fonts are not licensed for distribution, so you may not be able to use them in your application. Anyway, in the event that you do find one (I used Chaparral Pro as an example here), this is how you can get small caps.
From this answer I found that the small caps characters (for A-Z) are located starting at Unicode-UF761. So I built a mapping of these characters:
private static char[] smallCaps = new char[]
{
'\uf761', //A
'\uf762',
'\uf763',
'\uf764',
'\uf765',
'\uf766',
'\uf767',
'\uf768',
'\uf769',
'\uf76A',
'\uf76B',
'\uf76C',
'\uf76D',
'\uf76E',
'\uf76F',
'\uf770',
'\uf771',
'\uf772',
'\uf773',
'\uf774',
'\uf775',
'\uf776',
'\uf777',
'\uf778',
'\uf779',
'\uf77A' //Z
};
Then added a helper method to convert an input string to one whose lowercase letters have been replaced by their Small Caps equivalents:
private static String getSmallCapsString (String input) {
char[] chars = input.toCharArray();
for(int i = 0; i < chars.length; i++) {
if(chars[i] >= 'a' && chars[i] <= 'z') {
chars[i] = smallCaps[chars[i] - 'a'];
}
}
return String.valueOf(chars);
}
Then just use that anywhere:
String regularCase = "The quick brown fox jumps over the lazy dog.";
textView.setText(getSmallCapsString(regularCase));
For which I got the following result:
Apologies for dragging up a very old question.
I liked #kcoppock's approach to this, but unfortunately the font I'm using is missing the small-cap characters. I suspect many others will find themselves in this situation.
That inspired me to write a little util method that will take a mixed-case string (e.g. Small Caps) and create a formatted spannable string that looks like Sᴍᴀʟʟ Cᴀᴘs but only uses the standard A-Z characters.
It works with any font that has the A-Z characters - nothing special required
It is easily useable in a TextView (or any other text-based view, for that matter)
It doesn't require any HTML
It doesn't require any editing of your original strings
I've posed the code here: https://gist.github.com/markormesher/3e912622d339af01d24e
Found an alternative here Is it possible to have multiple styles inside a TextView?
Basically you can use html tags formatting the size of the characters and give a small caps effect....
Just call this getSmallCaps(text) function:
public SpannableStringBuilder getSmallCaps(String text) {
text = text.toUpperCase();
text = text.trim();
SpannableStringBuilder spannableStringBuilder = new SpannableStringBuilder();
if (text.contains(" ")) {
String[] arr = text.split(" ");
for (int i = 0; i < arr.length; i++) {
spannableStringBuilder.append(getSpannableStringSmallCaps(arr[i]));
spannableStringBuilder.append(" ");
}
} else {
spannableStringBuilder=getSpannableStringSmallCaps(text);
}
return spannableStringBuilder;
}
public SpannableStringBuilder getSpannableStringSmallCaps(String text) {
SpannableStringBuilder spannableStringBuilder = new SpannableStringBuilder(
text);
spannableStringBuilder.setSpan(new AbsoluteSizeSpan(36), 0, 1, 0);
spannableStringBuilder.setSpan(new StyleSpan(Typeface.BOLD), 0, 1, 0);
spannableStringBuilder.setSpan(new StyleSpan(Typeface.BOLD), 1,
text.length(), 0);
return spannableStringBuilder;
}
This is not my code but its works perfectly.
public SpannableString getSmallCapsString(String input) {
// values needed to record start/end points of blocks of lowercase letters
char[] chars = input.toCharArray();
int currentBlock = 0;
int[] blockStarts = new int[chars.length];
int[] blockEnds = new int[chars.length];
boolean blockOpen = false;
// record where blocks of lowercase letters start/end
for (int i = 0; i < chars.length; ++i) {
char c = chars[i];
if (c >= 'a' && c <= 'z') {
if (!blockOpen) {
blockOpen = true;
blockStarts[currentBlock] = i;
}
// replace with uppercase letters
chars[i] = (char) (c - 'a' + '\u0041');
} else {
if (blockOpen) {
blockOpen = false;
blockEnds[currentBlock] = i;
++currentBlock;
}
}
}
// add the string end, in case the last character is a lowercase letter
blockEnds[currentBlock] = chars.length;
// shrink the blocks found above
SpannableString output = new SpannableString(String.valueOf(chars));
for (int i = 0; i < Math.min(blockStarts.length, blockEnds.length); ++i) {
output.setSpan(new RelativeSizeSpan(0.8f), blockStarts[i], blockEnds[i], Spannable.SPAN_EXCLUSIVE_INCLUSIVE);
}
return output;
}
Example:
SpannableString setStringObj = getSmallCapsStringTwo("Object"); tvObj.setText(setStringObj);
in XML
edit text has property :android:capitalize=""

Taking a char symbol by code in Android

I want to view all codes and symbols in custom font which I have loaded in assets of Android JDK. How do I do this? Thank you for helping.
You should create a TextView, add the characters that you want, then set the font of the TextView.
See here and here for changing fonts in a TextView.
Something like this should work to print out the characters:
StringBuilder sb = new StringBuilder();
// Loop over readable ASCII characters 32 to 127 as per http://www.asciitable.com/
for (int i = 32; i < 127; i++) {
sb.append((char) i);
}
TextView tv = (TextView) findViewByID(yourID);
tv.setText(sb.toString());

Categories

Resources