Skip to main content

Modified version of CustomFont.java for Arabic support

Please note these java.net forums are being decommissioned and use the new and improved forums at https://community.oracle.com/community/java.
No replies
mikedawson
Offline
Joined: 2013-03-17

Hi All,

I had a problem with Arabic bitmap support in LWUIT. I'm working with Pashto - which is based on Arabic but has extra letters that no phone (I know of) supports. So I needed a bitmap font that would support connected letters. This would mean you could use Arabic, Perisan, Pashto etc.

This version of CustomFont.java hardcodes the different glyphs that are used for substitution to connect letters. I haven't checked Arabic characters that are not used in Persian/Pashto. In the LWUIT Resource Editor you need to include this as the characters to include in the font:

<br />
ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789.,;:!@/\*()[]{}|#$%^&<>?'"+- ?????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????<br />

Note: Just to make life more interesting - if using English and Arabic letters - you'll have to use an ant task to make the font - the LWUIT resources editor will cut off after 287 or so characters.

Some of those glyphs are in standard unicode places - others have been appropriated as per the Pashto Kror Asia Type font downloadable via BBC News.

I'll see if this can be contributed formally - but in the meantime for anyone struggling with supporting Arabic, Persian, Pashto and the like this is the source:

-Mike

CustomFont.java

<br />
/*<br />
 * Copyright (c) 2008, 2010, Oracle and/or its affiliates. All rights reserved.<br />
 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.<br />
 * This code is free software; you can redistribute it and/or modify it<br />
 * under the terms of the GNU General Public License version 2 only, as<br />
 * published by the Free Software Foundation.  Oracle designates this<br />
 * particular file as subject to the "Classpath" exception as provided<br />
 * by Oracle in the LICENSE file that accompanied this code.<br />
 *<br />
 * This code is distributed in the hope that it will be useful, but WITHOUT<br />
 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or<br />
 * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License<br />
 * version 2 for more details (a copy is included in the LICENSE file that<br />
 * accompanied this code).<br />
 *<br />
 * You should have received a copy of the GNU General Public License version<br />
 * 2 along with this work; if not, write to the Free Software Foundation,<br />
 * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.<br />
 *<br />
 * Please contact Oracle, 500 Oracle Parkway, Redwood Shores<br />
 * CA 94065 USA or visit <a href="http://www.oracle.com" title="www.oracle.com">www.oracle.com</a> if you need additional information or<br />
 * have any questions.<br />
 */<br />
package com.sun.lwuit;</p>
<p>import java.util.Hashtable;</p>
<p>/**<br />
 * Implements a bitmap font that uses an image and sets of offsets to draw a font<br />
 * with a given character set.<br />
 *<br />
 * @author Shai Almog<br />
 */<br />
class CustomFont extends Font {<br />
    /**<br />
     * Keep two colors in cache by default to allow faster selection colors<br />
     */<br />
    private static final int COLOR_CACHE_SIZE = 20;</p>
<p>    private Hashtable colorCache = new Hashtable();</p>
<p>    private String charsets;<br />
    private int color;</p>
<p>    //forms of arabic / connected characters<br />
    public static final int ISO = 1;</p>
<p>    public static final int BEG = 4;</p>
<p>    public static final int MID = 3;</p>
<p>    public static final int END = 2;</p>
<p>    //whitespace<br />
    public static final int WS = 4;</p>
<p>    //public boolean bidi = true;</p>
<p>    static char[][] arabicCharList = {<br />
        //START ARABIC<br />
            //NORMAL UNICODE, ISOLATED, END, MIDDLE, BEGINNING<br />
            {'\u0623', '\uFE83', '\uFE84'}, //ALEF with hamza above<br />
            {'\u0622', '\uFE81', '\uFE82'}, //ALEF with madda above<br />
            {'\u0627', '\uFE8D', '\uFE8E'}, //ALEF<br />
            {'\u0626', '\uFE87', '\uFE88'}, //ALEF with hamza below<br />
            {'\u0628', '\uFE8F', '\uFE90', '\uFE92','\uFE91'},//BA<br />
            {'\u062A', '\uFE95', '\uFE96', '\uFE98', '\uFE97'},//TA<br />
            {'\u062B', '\uFE99', '\uFE9A', '\uFE9C', '\uFE9B'},//TAY<br />
            {'\u062C', '\uFE9D', '\uFE9E', '\uFEA0', '\uFE9F'},//GIM<br />
            {'\u062D', '\uFEA1', '\uFEA2', '\uFEA4', '\uFEA3'},//HA<br />
            {'\u062E', '\uFEA5', '\uFEA6', '\uFEA8', '\uFEA7'},//HA<br />
            {'\u062F', '\uFEA9', '\uFEAA'},//DAL<br />
            {'\u0630', '\uFEAB', '\uFEAC'},//DAL<br />
            {'\u0631', '\uFEAD', '\uFEAE'},//RA<br />
            {'\u0632', '\uFEAF', '\uFEB0'},//ZAYN<br />
            {'\u0633', '\uFEB1', '\uFEB2', '\uFEB4', '\uFEB3'},//SIN<br />
            {'\u0634', '\uFEB5', '\uFEB6', '\uFEB8', '\uFEB7'},//SHEEN<br />
            {'\u0635', '\uFEB9', '\uFEBa', '\uFEBC', '\uFEBB'},//SAD<br />
            {'\u0636', '\uFEBD', '\uFEBE', '\uFEC0', '\uFEBF'},//DAD<br />
            {'\u0637', '\uFEC1', '\uFEC2', '\uFEC4', '\uFEB3'},//TA<br />
            {'\u0638', '\uFEC5', '\uFEC6', '\uFEC8', '\uFEB7'},//ZA<br />
            {'\u0639', '\uFEC9', '\uFECA', '\uFECC', '\uFEBB'},//AYN<br />
            {'\u063A', '\uFECD', '\uFECE', '\uFED0', '\uFECF'},//GAYN<br />
            {'\u0641', '\uFED1', '\uFED2', '\uFED4', '\uFED3'},//FA<br />
            {'\u0642', '\uFED5', '\uFED6', '\uFED8', '\uFED7'},//QAF<br />
            {'\u0643', '\uFED9', '\uFEDA', '\uFEDC', '\uFEDB'},//KAF<br />
            {'\u0644', '\uFEDD', '\uFEDE', '\uFEE0', '\uFEDF'},//LAM<br />
            {'\u0645', '\uFEE1', '\uFEE2', '\uFEE4', '\uFEE3'},//MIM<br />
            {'\u0646', '\uFEE5', '\uFEE6', '\uFEE8', '\uFEE7'},//NUN<br />
            {'\u0647', '\uFEE9', '\uFEEA', '\uFEEC', '\uFEEB'},//HA<br />
            {'\u0648', '\uFEED', '\uFEEE'},//WAW<br />
            {'\u064A', '\uFEF1', '\uFEF2', '\uFEF4', '\uFEF3'},//YA<br />
            {'\u0622', '\uFE81', '\uFE82'},//ALIF MADDAH<br />
            {'\u0629', '\uFE93', '\uFE94'},//TA MURBAUTAH<br />
            {'\u0649', '\uFEEF', '\uFEF0'},//ALIF MAQSURAH<br />
            {'\u06A9', '\uFB8E', '\uFB8F', '\uFB91', '\uFB90'}, //GOF? ARABIC KEHEH</p>
<p>            //END ARABIC</p>
<p>            //START PERSIAN ISOLATED, END, MIDDLE, Beginning<br />
            {'\u067E', '\uFB56', '\uFB57', '\uFB59', '\uFB58'},   //Peh<br />
            {'\u0686', '\uFB7A', '\uFB7B', '\uFB7D', '\uFB7C'}, //CHEH<br />
            {'\u0698', '\uFB8A', '\uFB8B'  }, //ZEH<br />
            {'\u06AF', '\uFB92', '\uFB93', '\uFB95', '\uFB94' }, //GOF<br />
            //END PERSIAN</p>
<p>            //START PASHTO - matches Pashto Kror Asisatype mappings<br />
            { '\u067c', '\u067c', '\uff00', '\uff02', '\uff01'},            //SPECIAL TAY with two dots below ??<br />
            { '\u0681', '\u0681', '\uff03', '\uff05', '\uff04'},   //special zim    ???<br />
            { '\u0689', '\u0689', '\uff16' }, //special doll with dot below ??<br />
            { '\u0693', '\u0693', '\uff17' }, // special ray with dot below ??<br />
            { '\u0696', '\u0696', '\uff18' }, //special gay ?<br />
            { '\u069A', '\u069A', '\uFF09', '\uFF0B', '\uFF0A'}, // seen with two dots ?<br />
            { '\u06AB', '\u06AB', '\uff0c', '\uff0f', '\uff0d' }, //special gof with dot below hook   ??<br />
            { '\u06BC', '\u06BC', '\uff10', '\uff12', '\uff11' }, //special noon with dot ??<br />
            { '\u06D0', '\u06D0', '\uff13', '\uff15', '\uff14' }, //pasta yey  ??<br />
            { '\u06CC', '\u06cc', '\ufbfd', '\ufbff', '\ufbfe' }, //narina yey ??<br />
            { '\u06CD', '\u06CD', '\uff19' }, //xezina yey ??<br />
            { '\u0626', '\u0626', '\ufe8a', '\ufe8c', '\ufe8b'}  //f?iliya ye  ?? </p>
<p>    };</p>
<p>    // package protected for the resource editor<br />
    Image cache;</p>
<p>    /**<br />
     * The offset in which to cut the character from the bitmap<br />
     */<br />
    int[] cutOffsets;</p>
<p>    /**<br />
     * The width of the character when drawing... this should not be confused with<br />
     * the number of cutOffset[o + 1] - cutOffset[o]. They are completely different<br />
     * since a character can be "wider" and "seep" into the next region. This is<br />
     * especially true with italic characters all of which "lean" outside of their<br />
     * bounds.<br />
     */<br />
    int[] charWidth;</p>
<p>    private int imageWidth;<br />
    private int imageHeight;<br />
    private Object imageArrayRef;</p>
<p>    private int[] getImageArray() {<br />
        if(imageArrayRef != null) {<br />
            int[] a = (int[])Display.getInstance().extractHardRef(imageArrayRef);<br />
            if(a != null) {<br />
                return a;<br />
            }<br />
        }<br />
        int[] a = cache.getRGBCached();</p>
<p>        imageArrayRef = Display.getInstance().createSoftWeakRef(a);<br />
        return a;<br />
    }</p>
<p>    /**<br />
     * Creates a bitmap font with the given arguments<br />
     *<br />
     * @param bitmap a transparency map in red and black that indicates the characters<br />
     * @param cutOffsets character offsets matching the bitmap pixels and characters in the font<br />
     * @param charWidth The width of the character when drawing... this should not be confused with<br />
     *      the number of cutOffset[o + 1] - cutOffset[o]. They are completely different<br />
     *      since a character can be "wider" and "seep" into the next region. This is<br />
     *      especially true with italic characters all of which "lean" outside of their<br />
     *      bounds.<br />
     * @param charsets the set of characters in the font<br />
     * @return a font object to draw bitmap fonts<br />
     */<br />
    public CustomFont(Image bitmap, int[] cutOffsets, int[] charWidth, String charsets) {<br />
        this.cutOffsets = cutOffsets;<br />
        this.charWidth = charWidth;<br />
        this.charsets = charsets;<br />
        imageWidth = bitmap.getWidth();<br />
        imageHeight = bitmap.getHeight();<br />
        int[] imageArray = new int[imageWidth * imageHeight];</p>
<p>        // default to black colored font<br />
        bitmap.getRGB(imageArray, 0, 0, 0, imageWidth, imageHeight);<br />
        for(int iter = 0 ; iter < imageArray.length ; iter++) {<br />
            // extract the red component from the font image<br />
            // shift the alpha 8 bits to the left<br />
            // apply the alpha to the image<br />
            imageArray[iter] = ((imageArray[iter] & 0xff0000) << 8);<br />
        }<br />
        cache = Image.createImage(imageArray, imageWidth, imageHeight);<br />
        imageArrayRef = Display.getInstance().createSoftWeakRef(imageArray);<br />
    }</p>
<p>    /**<br />
     * @inheritDoc<br />
     */<br />
    public int charWidth(char ch) {<br />
        int i = charsets.indexOf(ch);<br />
        if(i < 0) {<br />
            return 0;<br />
        }<br />
        return charWidth[i];<br />
    }</p>
<p>    /**<br />
     * @inheritDoc<br />
     */<br />
    public int getHeight() {<br />
        return imageHeight;<br />
    }</p>
<p>    private boolean checkCacheCurrentColor(int newColor) {<br />
        Integer currentColor = new Integer(color);<br />
        Integer newColorKey = new Integer(newColor);<br />
        if(colorCache.get(currentColor) == null){<br />
            colorCache.put(currentColor, Display.getInstance().createSoftWeakRef(cache));<br />
        }<br />
        color = newColor;<br />
        Object newCache = Display.getInstance().extractHardRef(colorCache.get(newColorKey));<br />
        if(newCache != null) {<br />
            Image i = (Image)newCache;<br />
            if(i != null) {<br />
                cache = i;<br />
                if(colorCache.size() > COLOR_CACHE_SIZE) {<br />
                    // remove a random cache element<br />
                    colorCache.remove(colorCache.keys().nextElement());<br />
                }<br />
                return true;<br />
            }else{<br />
                colorCache.remove(newColorKey);<br />
            }<br />
        }<br />
        if(colorCache.size() > COLOR_CACHE_SIZE) {<br />
            // remove a random cache element<br />
            colorCache.remove(colorCache.keys().nextElement());<br />
        }<br />
        return false;<br />
    }</p>
<p>    private void initColor(Graphics g) {<br />
        int newColor = g.getColor();</p>
<p>        if(newColor != color && !checkCacheCurrentColor(newColor)) {<br />
            color = newColor & 0xffffff;<br />
            int[] imageArray = getImageArray();<br />
            for(int iter = 0 ; iter < imageArray.length ; iter++) {<br />
                // extract the red component from the font image<br />
                // shift the alpha 8 bits to the left<br />
                // apply the alpha to the image<br />
                imageArray[iter] = color | (imageArray[iter] & 0xff000000);<br />
            }<br />
            cache = Image.createImage(imageArray, imageWidth, imageHeight);<br />
        }<br />
    }</p>
<p>    /**<br />
     * @inheritDoc<br />
     */<br />
    void drawChar(Graphics g, char character, int x, int y) {<br />
        int clipX = g.getClipX();<br />
        int clipY = g.getClipY();<br />
        int clipWidth = g.getClipWidth();<br />
        int clipHeight = g.getClipHeight();</p>
<p>        int i = charsets.indexOf(character);<br />
        if(i > -1) {<br />
            initColor(g);</p>
<p>            // draw region is flaky on some devices, use setClip instead<br />
            g.clipRect(x, y, charWidth[i], imageHeight);<br />
            g.drawImage(cache, x - cutOffsets[i], y);<br />
            //g.drawRegion(cache, cutOffsets[i], 0, charWidth[i], imageHeight, x, y);<br />
        }</p>
<p>        // restore the clip<br />
        g.setClip(clipX, clipY, clipWidth, clipHeight);<br />
    }</p>
<p>    /**<br />
     * @inheritDoc<br />
     */<br />
    public void addContrast(byte value) {<br />
        int[] imageArray = getImageArray();<br />
        for(int iter = 0 ; iter < imageArray.length ; iter++) {<br />
            int alpha = (imageArray[iter] >> 24) & 0xff;<br />
            if(alpha != 0) {<br />
                alpha = Math.min(alpha + value, 255);<br />
                imageArray[iter] = ((alpha << 24) & 0xff000000) | color;<br />
            }<br />
        }<br />
    }</p>
<p>    private static final char subChar(char c, int form) {<br />
        for(int i = 0; i < arabicCharList.length; i++) {<br />
           if(arabicCharList[i][0] == c) {<br />
               if(arabicCharList[i].length > form) {<br />
                   return arabicCharList[i][form];<br />
               }<br />
           }<br />
        }</p>
<p>        return c;<br />
    }</p>
<p>    private static final boolean charHasForm(char c, int form) {<br />
        for(int i = 0; i < arabicCharList.length; i++) {<br />
           if(arabicCharList[i][0] == c) {<br />
               if(arabicCharList[i].length > form) {<br />
                   return true;<br />
               }<br />
           }<br />
        }</p>
<p>        return false;<br />
    }</p>
<p>    public String arabize(String s) {<br />
        char[] ret = s.toCharArray();<br />
        char[] c = s.toCharArray();<br />
        for(int i = 0; i < c.length; i++) {<br />
            boolean prevCharConnects = false;<br />
            int form = ISO;</p>
<p>            if(i < c.length -1) {<br />
                char prevChar = c[i+1];<br />
                prevCharConnects = charHasForm(c[i+1], BEG) || charHasForm(c[i+1], MID);<br />
            }</p>
<p>            boolean nextCharConnects = false;</p>
<p>            if(i > 0){<br />
                char nextChar = c[i-1];<br />
                nextCharConnects = charHasForm(c[i-1], MID) || charHasForm(c[i-1], END);<br />
            }</p>
<p>            if(prevCharConnects) {<br />
                if(nextCharConnects) {<br />
                    form = MID;<br />
                }else {<br />
                    form = END;<br />
                }<br />
            }else {<br />
                if(nextCharConnects) {<br />
                    form = BEG;<br />
                }else {<br />
                    form = ISO;<br />
                }<br />
            }</p>
<p>            if(form == MID && charHasForm(c[i], MID) == false) {<br />
                //this is a character that has no middle form and goes only on the end<br />
                form = END;<br />
            }</p>
<p>            ret[i] = subChar(c[i], form);<br />
        }</p>
<p>        return new String(ret);<br />
    }</p>
<p>    /**<br />
     * Override this frequently used method for a slight performance boost...<br />
     *<br />
     * @param g the component graphics<br />
     * @param data the chars to draw<br />
     * @param offset the offset to draw the chars<br />
     * @param length the length of chars<br />
     * @param x the x coordinate to draw the chars<br />
     * @param y the y coordinate to draw the chars<br />
     */<br />
    void drawChars(Graphics g, char[] data, int offset, int length, int x, int y) {<br />
        if(Display.getInstance().isBidiAlgorithm()) {<br />
            for(int i = offset ; i < length ; i++) {<br />
                if(Display.getInstance().isRTL(data[i])) {<br />
                    String s = Display.getInstance().convertBidiLogicalToVisual(new String(data, offset, length));<br />
                    s = arabize(s);<br />
                    data = s.toCharArray();<br />
                    offset = 0;<br />
                    length = s.length();<br />
                    break;<br />
                }<br />
            }<br />
        }<br />
        initColor(g);<br />
        int clipX = g.getClipX();<br />
        int clipY = g.getClipY();<br />
        int clipWidth = g.getClipWidth();<br />
        int clipHeight = g.getClipHeight();</p>
<p>        if(clipY <= y + getHeight() && clipY + clipHeight >= y) {<br />
            char c;<br />
            for ( int i = 0; i < length; i++ ) {<br />
                c = data[offset+i];<br />
                int position = charsets.indexOf(c);<br />
                if(position < 0) {<br />
                    continue;<br />
                }<br />
                // draw region is flaky on some devices, use setClip instead<br />
                g.clipRect(x, y, charWidth[position], imageHeight);<br />
                if(g.getClipWidth() > 0 && g.getClipHeight() > 0) {<br />
                    g.drawImage(cache, x - cutOffsets[position], y);<br />
                }<br />
                x += charWidth[position];<br />
                g.setClip(clipX, clipY, clipWidth, clipHeight);<br />
            }<br />
        }<br />
    }</p>
<p>    /**<br />
     * @inheritDoc<br />
     */<br />
    public String getCharset() {<br />
        return charsets;<br />
    }</p>
<p>    /**<br />
     * @inheritDoc<br />
     */<br />
    public int charsWidth(char[] ch, int offset, int length){<br />
        int retVal = 0;<br />
        for(int i=0; i<length; i++){<br />
            retVal += charWidth(ch[i + offset]);<br />
        }<br />
        return retVal;<br />
    }</p>
<p>    /**<br />
     * @inheritDoc<br />
     */<br />
    public int substringWidth(String str, int offset, int len){<br />
        return charsWidth(str.toCharArray(), offset, len);<br />
    }</p>
<p>    /**<br />
     * @inheritDoc<br />
     */<br />
    public int stringWidth(String str){<br />
        if( str==null || str.length()==0)<br />
            return 0;<br />
        return substringWidth(str, 0, str.length());<br />
    }</p>
<p>    /**<br />
     * @inheritDoc<br />
     */<br />
    public int getFace(){<br />
        return 0;<br />
    }</p>
<p>    /**<br />
     * @inheritDoc<br />
     */<br />
    public int getSize(){<br />
        return 0;<br />
    }</p>
<p>    /**<br />
     * @inheritDoc<br />
     */<br />
    public int getStyle() {<br />
        return 0;<br />
    }</p>
<p>    /**<br />
    * @inheritDoc<br />
    */<br />
   public boolean equals(Object o) {<br />
       if(o == this) {<br />
           return true;<br />
       }<br />
       if(o != null && o.getClass() == getClass()) {<br />
           CustomFont f = (CustomFont)o;<br />
           if(charsets.equals(f.charsets)) {<br />
               for(int iter = 0 ; iter < cutOffsets.length ; iter++) {<br />
                   if(cutOffsets[iter] != f.cutOffsets[iter]) {<br />
                       return false;<br />
                   }<br />
               }<br />
               return true;<br />
           }<br />
       }<br />
       return false;<br />
   }<br />
}</p>
<p>