Click here to Skip to main content
15,671,882 members
Articles / Desktop Programming / WPF
Article
Posted 21 Oct 2008

Stats

32.6K views
269 downloads
10 bookmarked

Persian (Farsi) language support for Silverlight 2.0

Rate me:
Please Sign up or sign in to vote.
4.62/5 (8 votes)
21 Oct 2008Ms-PL2 min read
Persian (Farsi) language support for Silverlight 2.0

Download DLL - 4.59 KB

Introduction

Due to the lack of Right to Left (RTL) language support like Persian/Farsi in Silverlight 2.0, RTL texts are not rendered correctly in Silverlight. This project is a helper library to allow using Persian texts in Silverlight 2.0.

The source code is hosted on the CodePlex as an open source project available here.

Code Description

PersianCharacter.cs class is used to define a Persian Character Map.

C#
internal class PersianCharacter
{
    public PersianCharacter(string characterName, bool noLastAppend, int
             startChar, int middleRightSideBoundedChar, int 
         middleLeftSideBoundedChar, int middleTwoSideBoundedChar, int 
         endingBoundedChar, int endingChar)
    {
        CharacterName = characterName;
        NoLastAppend = noLastAppend;
        StartChar = startChar;
        MiddleRightSideBoundedChar = middleRightSideBoundedChar;
        MiddleLeftSideBoundedChar = middleLeftSideBoundedChar;
        MiddleTwoSideBoundedChar = middleTwoSideBoundedChar;
        EndingBoundedChar = endingBoundedChar;
        EndingChar = endingChar;
    }
    private string _CharacterName = "";
    public string CharacterName
    {
        get
        {
            return _CharacterName;
        }
        set
        {
            _CharacterName = value;
        }
    }
    private bool _NoLastAppend = false;
    public bool NoLastAppend
    {
        get
        {
            return _NoLastAppend;
        }
        set
        {
            _NoLastAppend = value;
        }
    }

    private int _StartChar = 0;
    public int StartChar
    {
        get
        {
            return _StartChar;
        }
        set
        {
            _StartChar = value;
        }
    }

    private int _MiddleRightSideBoundedChar = 0;
    public int MiddleRightSideBoundedChar
    {
        get
        {
            return _MiddleRightSideBoundedChar;
        }
        set
        {
            _MiddleRightSideBoundedChar = value;
        }
    }

    private int _MiddleLeftSideBoundedChar = 0;
    public int MiddleLeftSideBoundedChar
    {
        get
        {
            return _MiddleLeftSideBoundedChar;
        }
        set
        {
            _MiddleLeftSideBoundedChar = value;
        }
    }

    private int _MiddleTwoSideBoundedChar = 0;
    public int MiddleTwoSideBoundedChar
    {
        get
        {
            return _MiddleTwoSideBoundedChar;
        }
        set
        {
            _MiddleTwoSideBoundedChar = value;
        }
    }

    private int _EndingBoundedChar = 0;
    public int EndingBoundedChar
    {
        get
        {
            return _EndingBoundedChar;
        }
        set
        {
            _EndingBoundedChar = value;
        }
    }

    private int _EndingChar = 0;
    public int EndingChar
    {
        get
        {
            return _EndingChar;
        }
        set
        {
            _EndingChar = value;
        }
    }
}

PersianMaker.cs class does the process of formatting a Persian string. It has five methods which I will discuss briefly here.

First, we define all the Persian characters and add them to the CharacterMap list. There are about 40 characters, even though the actual Persian characters count is less than 40. There are some Arabic characters which are used nowadays in Persian writing. After populating the Character Map with the objects of PersianCharacter class, it will be ready to be used. Thanks to LINQ for supporting LINQ to Objects because it reduces the number of loops.

IsPersianCharacter method is for checking whether a specified character belongs to the Persian Character Map or not. This will be done by querying the CharacterMap list.

IsNoLastAppendCharacter which checks if the specified character is a Persian character to which no character can be appended to the end, such as Je(ژ) or Re (ر).

ReverseString will accept a string, and then return it reversed. We need this method because we should reverse the string to be presented correctly in Silverlight because of the lack of right to left support.

ToPersianString is the heart of this class because it accepts a raw (unformatted) text and produces a string that can be presented correctly in Silverlight content. This method iterates the text, character by character, to concatenate Persian characters correctly.

ToPersian first separates words by space, and then passed them to ToPersianString. Then it concatenates the words and returns the final string that can be presented in Silverlight 2.0 content correctly.

C#
public class PersianMaker
{
    // Persian character map
    private List<PersianCharacter> CharacterMap;

    public PersianMaker()
    {
        CharacterMap = new List<PersianCharacter>();

#region ABaKolah آ
        CharacterMap.Add(new PersianCharacter("ABaKolah", true, 
                         0x622, 0x622, 0xfe82, 0xfe81, 0xfe82, 0xfe81));
# endregion

#region Alef ا
        CharacterMap.Add(new PersianCharacter("Alef", true, 0x627, 
                         0x627, 0xfe8e, 0xfe8d, 0xfe8e, 0xfe8d));
# endregion

#region AlefWithHamzeAbove أ
        CharacterMap.Add(new PersianCharacter("AlefWithHamzeAbove", 
                         true, 0x623, 0x623, 0xfe84, 0xfe83, 0xfe84, 0xfe83));
# endregion

#region AlefWithHamzeBelow إ
        CharacterMap.Add(new PersianCharacter("AlefWithHamzeBelow", 
                         true, 0x625, 0x625, 0xfe88, 0xfe87, 0xfe88, 0xfe87));
# endregion

#region Hamze ء
        CharacterMap.Add(new PersianCharacter("Hamze", true, 
                         0x621, 0x621, 0xfe80, 0xfe80, 0xfe80, 0xfe7f));
# endregion

#region Be ب
        CharacterMap.Add(new PersianCharacter("Be", false, 
                         0x628, 0x628, 0xfe90, 0xfe91, 0xfe92, 0xfe8f));
# endregion

#region Pe پ
        CharacterMap.Add(new PersianCharacter("Pe", false, 
                         0x67e, 0x67e, 0xfb57, 0xfb58, 0xfb59, 0xfb56));
# endregion

#region Te ت
        CharacterMap.Add(new PersianCharacter("Te", false, 
                         0x62a, 0x62a, 0xfe96, 0xfe97, 0xfe98, 0xfe95));
# endregion

#region Theh ث
        CharacterMap.Add(new PersianCharacter("Theh", false, 
                         0x62b, 0x62b, 0xfe9a, 0xfe9b, 0xfe9c, 0xfe99));
# endregion

#region Jeem ج
        CharacterMap.Add(new PersianCharacter("Jeem", false, 
                         0x62c, 0x62c, 0xfe9e, 0xfe9f, 0xfea0, 0xfe9d));
# endregion

#region Che چ
        CharacterMap.Add(new PersianCharacter("Che", false, 
                         0x686, 0x686, 0xfb7b, 0xfb7c, 0xfb7d, 0xfb7a));
# endregion

#region Hah ح
        CharacterMap.Add(new PersianCharacter("Hah", false, 
                         0x62d, 0x62d, 0xfea2, 0xfea3, 0xfea4, 0xfea1));
# endregion

#region Kheh خ
        CharacterMap.Add(new PersianCharacter("Kheh", false, 
                         0x62e, 0x62e, 0xfea6, 0xfea7, 0xfea8, 0xfea5));
# endregion

#region Dal د
        CharacterMap.Add(new PersianCharacter("Dal", true, 
                         0x62f, 0x62f, 0xfeaa, 0xfea9, 0xfeaa, 0xfea9));
# endregion

#region Zal ذ
        CharacterMap.Add(new PersianCharacter("Zal", true, 
                         0x630, 0x630, 0xfeac, 0xfeab, 0xfeac, 0xfeab));
# endregion

#region Re ر
        CharacterMap.Add(new PersianCharacter("Re", true, 0x631, 
                         0x631, 0xfeae, 0xfead, 0xfeae, 0xfead));
# endregion

#region Ze ز
        CharacterMap.Add(new PersianCharacter("Ze", true, 
                         0x632, 0x632, 0xfeb0, 0xfeaf, 0xfeb0, 0xfeaf));
# endregion

#region Je ژ
        CharacterMap.Add(new PersianCharacter("Je", true, 
                         0x698, 0x698, 0xfb8b, 0xfb8a, 0xfb8b, 0xfb8a));
# endregion

#region Seen س
        CharacterMap.Add(new PersianCharacter("Seen", false, 
                         0x633, 0x633, 0xfeb2, 0xfeb3, 0xfeb4, 0xfeb1));
# endregion

#region Sheen ش
        CharacterMap.Add(new PersianCharacter("Sheen", false, 
                         0x634, 0x634, 0xfeb6, 0xfeb7, 0xfeb8, 0xfeb5));
# endregion

#region Sad ص
        CharacterMap.Add(new PersianCharacter("Sad", false, 
                         0x635, 0x635, 0xfeba, 0xfebb, 0xfebc, 0xfeb9));
# endregion

#region Zad ض
        CharacterMap.Add(new PersianCharacter("Zad", false, 
                         0x636, 0x636, 0xfebe, 0xfebf, 0xfec0, 0xfebd));
# endregion

#region Ta ط
        CharacterMap.Add(new PersianCharacter("Ta", false, 
                         0x637, 0x637, 0xfec2, 0xfec3, 0xfec4, 0xfec1));
# endregion

#region Za ظ
        CharacterMap.Add(new PersianCharacter("Za", false, 
                         0x638, 0x638, 0xfec6, 0xfec7, 0xfec8, 0xfec5));
# endregion

#region Ein ع
        CharacterMap.Add(new PersianCharacter("Ein", false, 
                         0x639, 0xfeca, 0xfeca, 0xfecb, 0xfecc, 0xfec9));
# endregion

#region Ghein غ
        CharacterMap.Add(new PersianCharacter("Ghein", false, 
                         0x63a, 0x63a, 0xfece, 0xfecf, 0xfed0, 0xfecd));
# endregion

#region Fe ف
        CharacterMap.Add(new PersianCharacter("Fe", false, 
                         0x641, 0x641, 0xfed2, 0xfed3, 0xfed4, 0xfed1));
# endregion

#region Ghaf ق
        CharacterMap.Add(new PersianCharacter("Ghaf", false, 
                         0x642, 0x642, 0xfed6, 0xfed7, 0xfed8, 0xfed5));
# endregion

#region kaf ک
        CharacterMap.Add(new PersianCharacter("kaf", false, 
                         0x6a9, 0x6a9, 0xfeda, 0xfedb, 0xfedc, 0xfed9));
# endregion

#region Gaf گ
        CharacterMap.Add(new PersianCharacter("Gaf", false, 
                         0x6af, 0x6af, 0xfb93, 0xfb94, 0xfb95, 0xfb92));
# endregion

#region Lam ل
        CharacterMap.Add(new PersianCharacter("Lam", false, 
                         0x644, 0x644, 0xfede, 0xfedf, 0xfee0, 0xfedd));
# endregion

#region Meem م
        CharacterMap.Add(new PersianCharacter("Meem", false, 
                         0x645, 0x645, 0xfee2, 0xfee3, 0xfee4, 0xfee1));
# endregion

#region Noon ن
        CharacterMap.Add(new PersianCharacter("Noon", false, 
                         0x646, 0x646, 0xfee6, 0xfee7, 0xfee8, 0xfee5));
# endregion

#region Vav و
        CharacterMap.Add(new PersianCharacter("Vav", true, 0x648, 
                         0x648, 0xfeee, 0xfeed, 0xfeee, 0xfeed));
# endregion

#region VavBaHamze ؤ
        CharacterMap.Add(new PersianCharacter("VavBaHamze", true, 
                         0x624, 0x624, 0xfe86, 0xfe85, 0xfe86, 0xfe85));
# endregion

#region Heh ه
        CharacterMap.Add(new PersianCharacter("Heh", false, 
                         0x647, 0x647, 0xfeea, 0xfeeb, 0xfeec, 0xfee9));
# endregion

#region TehMarbuta ة
        CharacterMap.Add(new PersianCharacter("TehMarbuta", 
                         true, 0x629, 0x629, 0xfe94, 0xfe93, 0xfe93, 0xfe93));
# endregion

#region Ye ی
        CharacterMap.Add(new PersianCharacter("Ye", false, 
                         0x6cc, 0x6cc, 0xfef0, 0xfef3, 0xfef4, 0xfeef));
# endregion

#region ArabicYe ي
        CharacterMap.Add(new PersianCharacter("ArabicYe", false, 
                         0x64a, 0x64a, 0xfef2, 0xfef3, 0xfef4, 0xfef1));
# endregion

#region YeBaHamze ئ
        CharacterMap.Add(new PersianCharacter("YeBaHamze", false, 
                         0x626, 0x626, 0xfe8a, 0xfe8b, 0xfe8c, 0xfe89));
# endregion 

    }

    /// <summary>
    /// Check whether the character belongs to the Persian character list or not.
    /// </summary>
    /// <param name="ch"></param>
    /// <returns>true if the character belongs to the
    /// Persian character list; otherwise, false.</returns>
    private bool IsPersianCharacter(char ch)
    {
        bool result = false;
        var query = from cm in CharacterMap
        where cm.StartChar == ch ||
        cm.MiddleRightSideBoundedChar == ch ||
        cm.MiddleLeftSideBoundedChar == ch ||
        cm.MiddleTwoSideBoundedChar == ch ||
        cm.EndingBoundedChar == ch ||
        cm.EndingChar == ch
        select cm.CharacterName;

        if (query.Count() > 0)
        {
            result = true;
        }
        return result;
    }

    /// <summary>
    /// Check whether the character is from NoLastAppend group or not.
    /// </summary>
    /// <param name="ch"></param>
    /// <returns>true if the character is from NoLastAppend group;
    /// otherwise, false.</returns>
    private bool IsNoLastAppendCharacter(char ch)
    {
        bool result = false;
        var query = from cm in CharacterMap
        where (cm.StartChar == ch ||
        cm.MiddleRightSideBoundedChar == ch ||
        cm.MiddleLeftSideBoundedChar == ch ||
        cm.MiddleTwoSideBoundedChar == ch ||
        cm.EndingBoundedChar == ch ||
        cm.EndingChar == ch) & cm.NoLastAppend == true
        select cm.NoLastAppend;
        if (query.Count() > 0)
        {
            result = true;
        }
        return result;
    }

    /// <summary>
    /// Generates a correct Persian text to be presented
    /// in the silverlight context.
    /// </summary>
    /// <param name="text"></param>
    /// <returns>Persian correct format of the text</returns>
    private string ToPersianString(string text)
    {
        int i = 0;
        string str2 = "";
        while (i < text.Length)
        {
            ECharPossition possition= ECharPossition.StartingChar;
            // get current letter
            if (IsPersianCharacter(Convert.ToChar(text.Substring(i, 1))))
            {
                if (i == 0)// This is the first chraracter of the string
                {
                    possition = ECharPossition.StartingChar;
                }
                else if (i == (text.Length - 1))
                // This is the last character of the string
                {
                    // get letter before
                    if (IsPersianCharacter(Convert.ToChar(text.Substring(i - 1, 1))))
                    {
                        if (IsNoLastAppendCharacter(
                             Convert.ToChar(text.Substring(i - 1, 1))))
                        {
                            possition = ECharPossition.EndingChar;
                        }
                        else
                        {
                            possition = ECharPossition.EndingBoundedChar;
                        }
                    }
                    else
                    {
                        possition = ECharPossition.EndingBoundedChar;
                    }
                }
                else // the current character is neither first nor last
                {
                    // get letter before
                    if (IsPersianCharacter(
                           Convert.ToChar(text.Substring(i - 1, 1))))
                    {
                        if (IsNoLastAppendCharacter(
                              Convert.ToChar(text.Substring(i - 1, 1))))
                        {
                            // get current letter
                            if (IsPersianCharacter(
                                 Convert.ToChar(text.Substring(i, 1))))
                            {
                                if (!IsNoLastAppendCharacter(
                                      Convert.ToChar(text.Substring(i, 1))))
                                {
                                    possition = 
                                      ECharPossition.MiddleLeftSideBoundedChar;
                                }
                                else
                                {
                                    possition = ECharPossition.StartingChar;
                                }
                            }
                            else
                            {
                                possition = ECharPossition.StartingChar;
                            }
                        }
                        else
                        {
                            possition = ECharPossition.MiddleTwoSidesBoundedChar;
                        }
                    }
                    else
                    {
                        possition = ECharPossition.StartingChar;
                    }
                }

                // get current letter
                char currentChar = Convert.ToChar(text.Substring(i, 1));
                var query = from cm in CharacterMap
                where cm.StartChar == currentChar ||
                cm.MiddleRightSideBoundedChar == currentChar ||
                cm.MiddleLeftSideBoundedChar == currentChar ||
                cm.MiddleTwoSideBoundedChar == currentChar ||
                cm.EndingBoundedChar == currentChar ||
                cm.EndingChar == currentChar
                select cm;
    
                //Retreive the character set of the current letter
                PersianCharacter pch = query.First();
                if (pch != null)
                {
                    switch (possition)
                    {
                        case ECharPossition.StartingChar:
                            str2 = str2 + 
                              Convert.ToChar(pch.MiddleTwoSideBoundedChar).ToString();
                            break;
                        case ECharPossition.MiddleRightSideBoundedChar: ;
                            str2 = str2 + 
                              Convert.ToChar(pch.EndingBoundedChar).ToString();
                            break;
                        case ECharPossition.MiddleLeftSideBoundedChar:
                            str2 = str2 + 
                              Convert.ToChar(pch.MiddleTwoSideBoundedChar).ToString();
                            break;
                        case ECharPossition.MiddleTwoSidesBoundedChar:
                            str2 = str2 + 
                              Convert.ToChar(pch.EndingBoundedChar).ToString();
                            break;
                        case ECharPossition.EndingBoundedChar:
                            str2 = str2 + 
                              Convert.ToChar(pch.MiddleLeftSideBoundedChar).ToString();
                            break;
                        case ECharPossition.EndingChar:
                            str2 = str2 + Convert.ToChar(pch.EndingChar).ToString();
                            break;
                        default:
                            break;
                    }
                }
            }
            else
            {
                // The character does not belong to the CharMap
                str2 = str2 + text.Substring(i, 1);
            }
            i++;
        }
        return this.ReverseString(str2);
    }

    public string ToPersian(string text)
    {
        string str = "";
        string[] strArray0 = text.Split('\n');
        for (int j = strArray0.Length - 1; j >= 0; j--)
        {
            string[] strArray1 = strArray0[j].Split(new char[] { ' ' });
            int upperBound = strArray1.GetUpperBound(0);
            for (int i = 0; i <= upperBound; i++)
            {
                str = this.ToPersianString(strArray1[i]) + " " + str;
            }
            str = "\n" + str;
        }
        str.Trim();
        return str;
    }

    /// <summary>
    /// Reverse the specified string to become right to left.
    /// </summary>
    /// <param name="str"></param>
    /// <returns></returns>
    private string ReverseString(string str)
    {
        string str2 = "";
        for (int i = str.Length - 1; i >= 0; i--)
        {
            if (IsPersianCharacter(Convert.ToChar(str.Substring(i, 1))))
            {
                str2 = str2 + str.Substring(i, 1);
            }
            else
            {
                str2 = str.Substring(i, 1) + str2;
            }
        }
        return str2;
    }

    private enum ECharPossition
    {
        StartingChar,
        MiddleRightSideBoundedChar,
        MiddleLeftSideBoundedChar,
        MiddleTwoSidesBoundedChar,
        EndingBoundedChar,
        EndingChar
    }

}

Using the code

The source code of this project is available here.

License

This article, along with any associated source code and files, is licensed under The Microsoft Public License (Ms-PL)


Written By
Software Developer
Canada Canada
This member has not yet provided a Biography. Assume it's interesting and varied, and probably something to do with programming.

Comments and Discussions

 
QuestionRight To Left Text Editor with highlighting Pin
Dirk Bahle1-Apr-13 5:44
Dirk Bahle1-Apr-13 5:44 
Hi Ali,

I have seen your article on Silverlight here on Codeplex and was wondering if you could support me in writing an article on RTL for text editors in WPF. I have already created an AvalonEdit version here:

https://edi.codeplex.com/wikipage?title=AvalonEdit%20with%20support%20for%20Arabic%20%28PRE%20Alpha%20version%29&referringTitle=Pre-Alpha%20features

Would you be able to review and test it for me? My problem is that I do not have an Arabic/Persian background but I'd like to develop software that could be used by everyone on this planet Smile | :) So, please let me know what you think about it - maybe we can do an article on it together? There is not enough information on that subject - so much is for sure Smile | :)
GeneralSilverlight 4.0 beta has added BiDi support. Pin
Ali Daneshmandi3-Mar-10 0:51
Ali Daneshmandi3-Mar-10 0:51 
GeneralProblems Pin
Masoud Zehtabi Oskuie23-Apr-09 9:42
professionalMasoud Zehtabi Oskuie23-Apr-09 9:42 
GeneralRe: Problems Pin
Ali Daneshmandi24-Apr-09 0:48
Ali Daneshmandi24-Apr-09 0:48 
GeneralThank you Pin
hasanhd30-Dec-08 12:49
hasanhd30-Dec-08 12:49 

General General    News News    Suggestion Suggestion    Question Question    Bug Bug    Answer Answer    Joke Joke    Praise Praise    Rant Rant    Admin Admin   

Use Ctrl+Left/Right to switch messages, Ctrl+Up/Down to switch threads, Ctrl+Shift+Left/Right to switch pages.