Click here to Skip to main content
15,867,704 members
Articles / Web Development / ASP.NET

Truncating a text string in ASP.NET to fit within a given pixel width

Rate me:
Please Sign up or sign in to vote.
4.75/5 (8 votes)
8 Sep 2010CPOL4 min read 42.6K   314   31   6
How to truncate a text string in ASP.NET to make it fit within a given width specified in pixels.

Introduction

Sometimes when developing a web application, you have strings that must fit within a certain pixel width (usually the width of a container, like a div, p, td, etc.). This is especially more common with links, as you sometimes need every link to fit within a single line and doesn't wrap to the next line (which will happen if the text of the link is too wide to fit within the width of the container). Or to express it visually, you might have a link that looks like this:

wrapping.gif

And want to make it look like this:

singleline.gif

Client Side vs. Server Side

This can be done either on the client side (using CSS or JavaScript), or on the server side. For a client side solution, the CSS3 text-overflow style (text-overflow: ellipsis OR text-overflow: ellipsis-word) is the simplest and also the standard way, but unfortunately, it's still not fully supported by all browsers. There are some JavaScript solutions that can be used for now until text-overflow is fully supported; still, I prefer a server-side solution as it:

  1. Saves bandwidth (which can also be good for the user experience if the text is too long and takes more time to download, especially if the user has a slow connection).
  2. Is guaranteed to work in all browsers (including the older ones) regardless of whether they have JavaScript enabled, or even support it.

The Code

I wrapped the code required for the truncation into a class, TextTruncator, which you can see here (also included in the download file along with a demo project):

C#
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Windows.Forms;
using System.Drawing;

namespace aspnet_TextTruncator
{
    public static class TextTruncator
    {
        // Private Properties
        //

        private static Dictionary<string, int[]> _fontWidthDic;
        private static Dictionary<string, int[]> FontWidthDic
        {
            get
            {
                if (_fontWidthDic == null)
                {
                    _fontWidthDic = new Dictionary<string, int[]>();
                }
                return _fontWidthDic;
            }
        }

        //
        // Public Methods
        //

        public static string TruncateText(string text, int textMaxWidth, 
               string fontName, int fontSizeInPixels)
        {
            return TruncateText(text, textMaxWidth, fontName, 
                                fontSizeInPixels, false);
        }

        public static string TruncateText(string text, int textMaxWidth, 
               string fontName, int fontSizeInPixels, bool isFontBold)
        {
            if (string.IsNullOrEmpty(text))
                return text;

            // Check
            //
            if (textMaxWidth < 1 ||
                string.IsNullOrEmpty(fontName) ||
                fontSizeInPixels < 1)
            {
                throw new ArgumentException();
            }

            int[] fontWidthArray = GetFontWidthArray(fontName, 
                                          fontSizeInPixels, isFontBold);
            int ellipsisWidth = fontWidthArray['.'] * 3;
            int totalCharCount = text.Length;
            int textWidth = 0;
            int charIndex = 0;
            for (int i = 0; i < totalCharCount; i++)
            {
                textWidth += fontWidthArray[text[i]];
                if (textWidth > textMaxWidth)
                {
                    return text.Substring(0, charIndex) + "...";
                }
                else if (textWidth + ellipsisWidth <= textMaxWidth)
                {
                    charIndex = i;
                }
            }
            return text;
        }

        //
        // Private Methods
        //

        private static int[] GetFontWidthArray(string fontName, 
                             int fontSizeInPixels, bool isFontBold)
        {
            string fontEntryName = fontName.ToLower() + "_" + 
                   fontSizeInPixels.ToString() + "px" + 
                   (isFontBold ? "_bold" : "");
            int[] fontWidthArray;
            if (!FontWidthDic.TryGetValue(fontEntryName, out fontWidthArray))
            {
                fontWidthArray = CreateFontWidthArray(new Font(fontName, 
                    fontSizeInPixels, isFontBold ? FontStyle.Bold : 
                    FontStyle.Regular, GraphicsUnit.Pixel));
                FontWidthDic[fontEntryName] = fontWidthArray;
            }

            return fontWidthArray;
        }

        private static int[] CreateFontWidthArray(Font font)
        {
            int[] fontWidthArray = new int[256];
            for (int i = 32; i < 256; i++)
            {
                char c = (char)i;
                fontWidthArray[i] = 
                  IsIllegalCharacter(c, false) ? 0 : GetCharWidth(c, font);
            }
            return fontWidthArray;
        }

        private static int GetCharWidth(char c, Font font)
        {
            // Note1: For typography related reasons,
            // TextRenderer.MeasureText() doesn't return the correct
            // width of the character in pixels, hence the need
            // to use this hack (with the '<' & '>'
            // characters and the subtraction). Note that <'
            // and '>' were chosen randomly, other characters 
            // can be used.
            //

            // Note2: As the TextRenderer class is intended
            // to be used with Windows Forms Applications, it has a 
            // special use for the ampersand character (used for Mnemonics).
            // Therefore, we need to check for the 
            // ampersand character and replace it with '&&'
            // to escape it (TextRenderer.MeasureText() will treat 
            // it as one ampersand character)
            //

            return
                TextRenderer.MeasureText("<" + (c == '&' ? "&&" : 
                                         c.ToString()) + ">", font).Width -
                TextRenderer.MeasureText("<>", font).Width;
        }

        private static bool ContainsIllegalCharacters(string text, 
                            bool excludeLineBreaks)
        {
            if (!string.IsNullOrEmpty(text))
            {
                foreach (char c in text)
                {
                    if (IsIllegalCharacter(c, excludeLineBreaks))
                        return true;
                }
            }

            return false;
        }

        private static bool IsIllegalCharacter(char c, bool excludeLineBreaks)
        {
            // See the Windows-1252 encoding
            // (we use ISO-8859-1, but all browsers, or at least
            // IE, FF, Opera, Chrome and Safari,
            // interpret ISO-8859-1 as Windows-1252).
            // For more information,
            // see http://en.wikipedia.org/wiki/ISO/
            //        IEC_8859-1#ISO-8859-1_and_Windows-1252_confusion
            //

            return
                (c < 32 && (!excludeLineBreaks || c != '\n')) ||
                c > 255 ||
                c == 127 ||
                c == 129 ||
                c == 141 ||
                c == 143 ||
                c == 144 ||
                c == 157;
        }
    }
}

Using the Code

The class has only one overloaded public method, TruncateText(), which you should call to truncate the text.

Usage Example

In your .aspx page:

ASP.NET
<%= TextTruncator.TruncateText("Some text that will " + 
      "likely come from your database, an XML file, or another source", 
      300, "Verdana", 12) %>

Notes About the Code

  • To measure the text width, I use TextRenderer.MeasureText(), which is actually intended for Windows Forms applications, but still can be used with ASP.NET. For performance reasons, I cache the width of the letters so that I don't have to call TextRenderer.MeasureText() again. This was done for good reasons as TextRenderer.MeasureText() can be really slow (in my testing, this change cut the truncation time for a million strings from 8 minutes to 2 seconds!).
  • Because I chose to cache the width of the letters, I had to limit the character set to ISO-8859-1 (for Latin based languages), which works well for my own purposes. If you need this code to work with Unicode, you'll need to remove the letter width caching mechanism and call TextRenderer.MeasureText() every time you want to measure the text width (which will slow things down a bit, but shouldn't be noticeable unless you plan to use this on a very high traffic website and on not-fast-enough machines; you'll have to do your own testing to be sure). You could also change the letter width caching mechanism to use a dictionary instead of an array, and only add the letters to the dictionary when they are actually used.
  • You may notice that in TruncateText(), instead of passing a font object, I pass the font name, size in pixels, and whether it's bold. I do this for convenience reasons, but this should be fairly easy to change if you want to pass a Font object (probably, to also use other font styles like italic, but you'll also need to change the caching mechanism of the letter width).
  • You'll need to add references to the System.Windows.Forms and System.Drawing assemblies in your project in order for this code to work.

A Note About Web Browsers

In many discussions on the web, you'll read that you shouldn't depend on the text width in pixels as there's no guarantee that different browsers will display the text in the same way (i.e., the width of the text may differ from one browser to another). While it's true that there's no guarantee, in my real life testing in all popular browsers (and less popular ones), I found that in all browsers, the width of the text as displayed in the browser never exceeded the maximum width I specified for the text in TruncateText(); sometimes it was slightly smaller (by only a few pixels - that was in the older versions of Safari on Windows), but again, it was never larger. If you are paranoid, like me, always use a 'safety margin'. For example, if the width of the container of the text (div, p, td, or whatever element) is 500px, make the maximum width (that's passed to TruncateText()) less by 10px or 20px, that's 490 or 480.

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)


Written By
Software Developer (Senior) Contractor
Egypt Egypt
My name is Waleed Eissa and I'm a software developer from Cairo, Egypt. I spent 7 years developing software for the banking industry, but has changed focus in recent years to Web development. I specialize in Microsoft technologies, esp. ASP.NET and C#, and am passionate about everything web. My main interests are user experience design (UX), performance tuning and scalability.

Website: http://www.waleedeissa.com
Blog: http://waldev.blogspot.com

Comments and Discussions

 
GeneralMy vote of 5 Pin
Zaibot14-Sep-10 2:28
Zaibot14-Sep-10 2:28 
GeneralRe: My vote of 5 Pin
Waleed Eissa15-Sep-10 20:00
Waleed Eissa15-Sep-10 20:00 
GeneralEncoding the & character Pin
Richard James Moss9-Sep-10 4:49
professionalRichard James Moss9-Sep-10 4:49 
GeneralRe: Encoding the & character Pin
Waleed Eissa10-Sep-10 0:06
Waleed Eissa10-Sep-10 0:06 
GeneralMy vote of 5 Pin
Eric Xue (brokensnow)8-Sep-10 23:33
Eric Xue (brokensnow)8-Sep-10 23:33 
GeneralRe: My vote of 5 Pin
Waleed Eissa9-Sep-10 23:58
Waleed Eissa9-Sep-10 23:58 

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.