I ve been looking for same damn thing over a year ago. And ended up implementing it by myself.
The width of the line is the sum of intersymbol space(kerning), widths of whitespaces between symbols, widths of bounds for your view.
intersymbol space(kerning) is different for each pair of symbols you want to use. WA and DA have different space if your font is not MONOSPACE.
same for whitespaces , it is different for each pair of symbols.
same for bounds on the left and right, it is different for each symbol.
so what i did - prepare an array of each possible symbol for your app (not all, but those that possibly would occur in the text you feeding to the view) then calculate width of each symbol.
HashMap<String, Integer[]> charMap = new HashMap<>();
String defaultSet = "ҢңӨөөҮүёЁQWERTYUIOPASDFGHJKLZXCVBNMqwertyuiopasdfghjklzxcvbnm\"'[]{}\\./`~!@#$%^&**()_+`12\uFEFF3456«;789»0—-=:|<>?йцуке…нгшщзхъфывапролджэячсмитьбюЙЦУКЕНГШЩЗХЪФЫВАПРОЛДЖЭЯЧСМИТЬБЮ,,–";
String[] charArray = defaultSet.split("");
Rect bounds = new Rect();
for (String mChar : charArray) {
if (mChar.length() == 0) {
continue;
}
//textView.setText(mChar);
textPaint.getTextBounds(String.valueOf(mChar), 0, 1, bounds);
int height = bounds.height();
int width = bounds.width();
Integer[] size = {width, height};
charMap.put(mChar, size);
}
The next thing is to get kerning, whitespace and bounds values for each pair of symbols. The last parameter in each method is the charMap you calculated in the first codebox.
/**
* Returns Bounds map for each symbol
* @param charArray
* @param textPaint
* @param mCharMap
* @return
*/
private HashMap<String, Integer> generateBoundsLeftMap(String[] charArray, TextPaint textPaint, HashMap<String, Integer[]> mCharMap){
//individual letterspacing, and whitespace width.
HashMap<String, Integer> boundsMap= new HashMap<>();
Rect bounds = new Rect();
for(int i=1;i<charArray.length;i++){
String mKeyChar=charArray[i];
String testLine="skajd fnфыв";
textPaint.getTextBounds(testLine,0,11,bounds);
int testwidth=bounds.width();
String checkLine= mKeyChar.concat(testLine).concat(mKeyChar);
textPaint.getTextBounds(checkLine, 0, 13, bounds);
boundsMap.put(charArray[i], bounds.left);
MLog.sout("boundsleft", "" + bounds.left, true);
MLog.sout("measuretext",""+textPaint.measureText(checkLine),true);
MLog.sout("boundsright",""+(textPaint.measureText(checkLine)-testwidth-(mCharMap.get(mKeyChar)[0]*2)-bounds.left),true);
}
return boundsMap;
}
/**
* Returns Bounds map for each symbol
* @param charArray
* @param textPaint
* @param mCharMap
* @return
*/
private HashMap<String, Integer> generateBoundsRightMap(String[] charArray, TextPaint textPaint,HashMap<String, Integer[]> mCharMap){
//individual letterspacing, and whitespace width.
HashMap<String, Integer> boundsMap= new HashMap<>();
Rect bounds=new Rect();
for(int i=1;i<charArray.length;i++){
String mKeyChar=charArray[i];
String testLine="aba";
String checkLine= mKeyChar.concat(testLine).concat(mKeyChar);
textPaint.getTextBounds(checkLine, 0, 5, bounds);
int testwidth=bounds.width();
int boundsright =(int) textPaint.measureText(checkLine)-testwidth-bounds.left;
boundsMap.put(charArray[i], boundsright);
}
return boundsMap;
}
/**
* returns Pairs map. HashMap<String, HashMap<String, Integer > >
* char1 char2 letterspacing
* @param charArray
* @param textPaint
* @return
*/
private HashMap<String, HashMap<String, Integer>> generateKerningPairs(String[] charArray, TextPaint textPaint, HashMap<String,Integer[]> mCharMap){
//individual letterspacing, and whitespace width.
HashMap<String, HashMap<String, Integer> > pairMap= new HashMap<>();
Rect bounds=new Rect();
int lineWidth;
int interspaceWidth;
for(int i=1;i<charArray.length;i++){
String mKeyChar=charArray[i];
HashMap<String , Integer> spaceMap=new HashMap<>();
for(int z=1; z<charArray.length;z++){
//getletterspacing
String checkLine=mKeyChar.concat(charArray[z]);
textPaint.getTextBounds(checkLine,0,2,bounds);
lineWidth = bounds.width();
interspaceWidth=lineWidth-(mCharMap.get(mKeyChar))[0] - (mCharMap.get(charArray[z]))[0];
spaceMap.put(charArray[z], interspaceWidth);
}
pairMap.put(mKeyChar,spaceMap);
}
return pairMap;
}
/**
* returns Pairs map. HashMap<String, HashMap<String, Integer > >
* char1 char2 letterspacing
* @param charArray
* @param textPaint
* @return
*/
private HashMap<String, HashMap<String, Integer>> generateSpacePairs(String[] charArray, TextPaint textPaint, HashMap<String,Integer[]> mCharMap){
//individual letterspacing, and whitespace width.
HashMap<String, HashMap<String, Integer> > pairMap= new HashMap<>();
Rect bounds=new Rect();
int lineWidth;
int spaceWidth;
for(int i=1;i<charArray.length;i++){
String mKeyChar=charArray[i];
HashMap<String , Integer> spaceMap=new HashMap<>();
for(int z=1; z<charArray.length;z++){
//getletterspacing
String checkLine=mKeyChar.concat(" ").concat(charArray[z]);
textPaint.getTextBounds(checkLine,0,3,bounds);
lineWidth = bounds.width();
spaceWidth=lineWidth-(mCharMap.get(mKeyChar))[0] - (mCharMap.get(charArray[z]))[0];
spaceMap.put(charArray[z], spaceWidth);
}
pairMap.put(mKeyChar,spaceMap);
}
return pairMap;
}
Calculating these values take a LOOOONG time so I did it once for each DPI (using genymotion and changing screen density) and saved into a JSON in txt files.
kernmap.txt
spacemap.txt
...
and so on.
You put these files into your assets folder , and the structure looks like this
assets/dpi/160/kernmap.txt
...
assets/dpi/240/kernmap.txt
...
and so on.
From this point - you cannot use symbols that you didnt put in default set string in the beginning
String defaultSet = "ҢңӨөөҮүёЁQWERTYUIOPASDFGHJKLZXCVBNMqwertyuiopasdfghjklzxcvbnm\"'[]{}\\./`~!@#$%^&**()_+`12\uFEFF3456«;789»0—-=:|<>?йцуке…нгшщзхъфывапролджэячсмитьбюЙЦУКЕНГШЩЗХЪФЫВАПРОЛДЖЭЯЧСМИТЬБЮ,,–";
even having all the maps prepared you will have problems with performance. I used jni to calculate the page length.
What next?
you get your string, lets say its
"This is a test line provided by blabla"
take "T" and "h" (the first 2 characters)
linewidth = boundsLeftmap.get("T") (you have it in a map) + widthofchars.get("T") + kerningbetween.get("T").get("h") + and so on.
Using JNI it takes 3-4 seconds to process and break a string of 80000 characters into pages that perfectly fit your view.