Click here to Skip to main content
65,938 articles
CodeProject is changing. Read more.
Articles
(untagged)

Vertical Labels in Web Pages

0.00/5 (No votes)
20 Sep 2004 1  
A technique and a control for displaying vertical labels in web pages.

Sample Image - Hello colorful world!

Introduction

A recent project had to display long labels in the header of a table with a lot of columns. While the columns' content kept small, the header was growing huge. The best way to accomplish the task would have been to use 900 rotated texts. However, I did not know any techniques for rotating texts in HTML. So - here is the idea of another WebControl for creating a bitmap on the fly and rendering it in the page to simulate a vertical label.

During developing this project, I've encountered many articles dealing with creating images on the fly. However, lots of small details came in the way and I feel that summarizing them in an article would be beneficial.

Prerequisites

The only choice I had was to - somehow - build small bitmaps and use them in <IMG SRC=...> tags. Then, create a WebControl for simplifying the task of building pages. A WebControl would have an additional benefit: it could bind its Text property so the page would dynamically create labels or localize them.

However, I do not want to store all generated images in files on the hard drive: each file would have a unique name and, beside the problem with generating (long) names, files will soon accumulate in large quantities on the server and somehow somebody should manage or delete them. Fortunately, the IMG SRC tag attribute accepts a URL as the file name, and this URL can be as well the URL of a program/script/page which returns a stream in the same way the bitmap of GIF/JPG file would.

So, the task splits in two smaller tasks:

  1. Create a generator page for the content needed in the SRC attribute,
  2. Wrap all that in a WebControl.

VerticalText page

Let's start coding the VerticalText.aspx page:

namespace WebImageTest {
  /// <summary>

  /// VerticalText is a peculiar page which returns

  /// an image instead of an HTML code.

  /// </summary>

  public class VerticalText : System.Web.UI.Page {

  private void Page_Load(object sender, System.EventArgs e) {
      
//...

      Response.ContentType = "image/png";
      Response.BinaryWrite( ms.ToArray() );
//...

  }
//...

  }
}

This ASPX is peculiar in the sense of its content: instead of returning HTML content (<HEADER>, <BODY> etc.), it returns a picture. This is achieved in the Page_Load method, with the line:

Response.ContentType = "image/png";

ContentType, with the default "text/HTML" can change the response in various ways and produce interesting results. For example, you may start Excel on the client machine (assuming that the client machine has Excel installed.) In the same way, with a content of "image/bmp", you may start directly Paintbrush or with "image/gif" - PhotoPaint (assuming the corresponding file-extension associations).

The above code generates PNG (Portable Network Graphic) content.

We target that, when typing the following address http://localhost/WebImageTest/VerticalText.aspx?Text=Hello%20World&Font=Arial|24|B, the browser would render the following image:

Direct call to VerticalText.aspx page

It is worth mentioning here why I had to choose the PNG format over all other formats available. First of all, with GIF, I would have been able to use the following (shorter/fewer) lines:

      Response.ContentType = "image/gif";
      bm.Save(Response.OutputStream, ImageFormat.Gif);

However, the output generated this way is raster-ed by the usage of a palette of 256 colors. Saving in the OutputStream works as well for the JPEG format, but the JPEG image is a bit dirty due to an uncontrollable compression rate. On the other hand, the PNG format looks well on the screen, but cannot be saved in the OutputStream (albeit there is no hint why the trick with creating a MemoryStream works!)

PNG image magnified 3 times Gif image magnified 3 times Jpg image magnified 3 times

Let's throw some parameters

Of course, we need a parameter for the text of the label. Then it is as well important to be able to specify the font, unless we want to stick with the defaults. Then, we may extend a bit the idea by being able to change the background color and the ink of the text. A lithe padding would be beneficial in certain situations.

Defaults are good: when users forget or don't need to give values, the object should be able to render something instead of giving the x box. Page_Load starts by getting these values from the request URL. By design, the URL should look like this:

VerticalText.aspx?Text=Hello World&Font=
   Arial|24|B&BgColor=DarkGoldenrod&FrColor=Orange&Padding=3

As everybody knows, after the name of the page name, parameters are introduced with ? and are separated with &. To simplify and reduce the length of the line, I have compacted the font parameters using the | separator and ruling an order as: <Font name>|<Font Size>|<Font attributes>. In fact, the code:

    string fontName = "Arial";
    int fontSize = 12;
    FontStyle fontStyle = new FontStyle();

    try {
      string [] fontStr = 
        Request["Font"].Split(new char[] {'|',',',' ','/',':',';'});
      try { if (fontStr[0]!=string.Empty) fontName = fontStr[0]; } catch { }
      try { fontSize = int.Parse(fontStr[1]); } catch { }
      try { 
        if (fontStr[2].IndexOf('B')>=0) fontStyle |= FontStyle.Bold; 
        if (fontStr[2].IndexOf('I')>=0) fontStyle |= FontStyle.Italic; 
        if (fontStr[2].IndexOf('U')>=0) fontStyle |= FontStyle.Underline; 
      } catch {}
    } catch {}
      
    Font font = new Font(fontName, fontSize, fontStyle);

allows a lot more delimiters (including blank) and prepares the defaults. The try...catch blocks are there just to make sure that defaults will stay when the request does not have requested elements.

Do the same for bgColor, frColor and pad. Problems with colors: first of all, I want my users to be able to use named colors as Red, or DarkGoldenrod or ControlDark. In the same time, I want to be able to choose any unnamed color like #804040. Well, there is a glitch that I could not fix: I can not use the # char in the URL. So, I decided to change it in $. The private method StrToColor will do the rest, and I believe is clear enough:

  private Color StrToColor(string c) {
    if (c.Trim().IndexOf("$")==0) 
        return ColorTranslator.FromHtml(c.Replace("$","#"));
    else
        return Color.FromName(c);
  }

Then set some defaults and decode the colors:

    int pad = 0;
    Color bgColor = Color.White;
    Brush br = Brushes.Black;

    try { pad = int.Parse(Request["Padding"]); } catch { }
    try { bgColor = StrToColor(Request["BgColor"]); } catch { }
    try { br = new SolidBrush( StrToColor(Request["FrColor"]) ); } catch { }

From a different angle

There are two ways to measure the size of the bitmap: measure the string rendered horizontally and reverse the width with the height, or use StringFormatFlags.DirectionVertical. (Thanks to Chris Garrett for this trick!):

StringFormat format = new StringFormat(StringFormat.GenericDefault);
format.FormatFlags = StringFormatFlags.MeasureTrailingSpaces | 
                     StringFormatFlags.DirectionVertical;

SizeF sz = (Graphics.FromImage(new Bitmap(1,1))).MeasureString(Text, 
                                          font, Point.Empty, format);

Bitmap bm = new Bitmap((int)sz.Width+2*pad,(int)sz.Height+2*pad);

Drawing the string this way, however, brings a perceptive problem: the string starts with the first character on the top while the bottom of the text is oriented towards the left. As I have the European school, I know that to be easy to read a vertical text, the string should be oriented the other way. There are pros and cons for that, and I'm going in to details. Firstly, at technical drawing lessons, I've learned that one should take a blueprint with the right hand from the upper-right corner and with the left hand from the lower-right corner. Then drag the paper from vertical to horizontal and the vertical text should become horizontal in the natural way. Americans however, do the other way (like all the other ways to figure how to do a projection in drawing!) and same does the Microsoft flag for rendering vertical texts. Personally, I feel that, in front of a fixed screen, it's easier to turn the head to the left instead of the right to read a vertical text (it's so natural that you do not have even to turn the head). Observe that Excel also adheres to this rule when it rotates texts in cells.

We can also argue about how multiple lines vertical text should be rendered, but for a text box, it makes no difference - can go both ways. However, for my project, I have to put vertical labels in the header of a table and we read table columns (as normal text) from left to right:

Table stuffed with Vertical Labels header

Tip: to make the vertical text even more readable, make it Italic.

So, the bottom line is that we have to rotate the text again:

    System.Drawing.Graphics g = System.Drawing.Graphics.FromImage(bm);
    try {
      g.Clear(bgColor);
      g.TextRenderingHint = TextRenderingHint.ClearTypeGridFit;
      //AntiAliasGridFit; //AntiAlias;


      g.TranslateTransform ((int)sz.Width,(int)sz.Height);
      g.RotateTransform(180.0F);
      g.DrawString(Text, font, br, -1-pad, 1-pad, format);

Now, push the bitmap content in the Response and clean up the resources:

      MemoryStream ms = new MemoryStream();
      bm.Save(ms,ImageFormat.Png);
      Response.ContentType = "image/png";
      Response.BinaryWrite( ms.ToArray() );
      ms.Close();
    } finally {
      g.Dispose();
      bm.Dispose();
      font.Dispose();
    }

Peculiar use

To refer the VerticalText.aspx page directly from the address bar of the browser makes not much sense - it will render just a label per screen.

We target, however, to use this reference in the SRC= attribute of images (<Imgtag) or image buttons. For example, using in a form the following code:

<asp:ImageButton id="ImageButton1" runat="server" 
  ImageUrl="VerticalText.aspx?Text= Login &Font=|16|B&BgColor=Silver&FrColor=Window"
  BorderStyle="Outset" BorderWidth="1px" AlternateText="Login">
</asp:ImageButton>

we may get ourselves a beautiful vertical button:

Beautiful vertical button

Now, pack it up!

The second task on our list was to pack all that in an easy to use WebControl - the VerticalLabel. The control will have to manage with its properties the text of the label, the font, and the colors. It will be also responsible for rendering the HTML code that uses the above VerticalText.aspx page. It would be nice to have the control on the ToolBox with a nice glyph to represent it there. (I have already discussed what the challenges for creating this small bitmap are and how to put it on the ToolBox, in my previous article Numeric Text Box.)

So, inherit the VerticalLabel control directly from the WebControl and add a reference to the INamingContainer interface, so the control will get a unique ID. Accept as it is the auto generated Text property with its Bindable(true) attribute. Observe also that the control has by default a Font and a BackColor. What we miss is the Padding property that we add ourselves.

Next, we just have to generate the HTML code in the Render method:

    protected override void Render(HtmlTextWriter output) {
      StringBuilder s = new StringBuilder("<img ID="); s.Append(this.ID);
      s.Append(" src=\"VerticalText.aspx?Text="); s.Append(Text);
      s.Append("&Font="); s.Append(Font.Name); s.Append("|");

Using a StringBuilder seems to be a good idea. (Although we could write directly in the output. However, this way may help when debugging.) Keep adding items separately instead of concatenating them with the + string operator. Let's have the Font parameter added anyway, whether its name is empty or not - the VerticalText.aspx page will have a default for that. In the same way, add the size and the font attributes by the rules VerticalText.aspx page accepts:

      if (Font.Size.Unit.Value>0) 
        s.Append(Font.Size.Unit.Value.ToString());
      if (Font.Bold || Font.Italic || Font.Underline) {
        s.Append("|");
        if (Font.Bold     ) s.Append("B");
        if (Font.Italic   ) s.Append("I");
        if (Font.Underline) s.Append("U");
      }

Don't add the BackColor and ForeColor when they had no value, but use a WebColorConverter if it was a known color, or the ColorTranslator to convert ToHtml otherwise. Remember, we were not able to use the # char in the URL, so replace it with $.

      if (!BackColor.IsEmpty) {
        s.Append("&BgColor=");
        s.Append(((BackColor.IsKnownColor)
          ?(new WebColorConverter()).ConvertToString(BackColor)
          :ColorTranslator.ToHtml(BackColor).Replace("#","$")));
      }
      if (!ForeColor.IsEmpty) {
        s.Append("&FrColor=");
        s.Append(((ForeColor.IsKnownColor)
          ?(new WebColorConverter()).ConvertToString(ForeColor)
          :ColorTranslator.ToHtml(ForeColor).Replace("#","$")));
      }
      if (Padding>0) {
        s.Append("&Padding=");
        s.Append(Padding.ToString());
      }
      s.Append("\"");

Just to have a horizontal text if something goes wrong in the VerticalText.aspx page, add the ALT= attribute to the HTML component. This might be a little odd when everything goes well, since the Alt text becomes for the label a hint with exactly the same text; so, you may decide to keep or discard the next line:

      s.Append(" Alt=\""); s.Append(Text); s.Append("\""); // !

Close the tag and write all that to the output. When debugging, you may place a breakpoint on the output statement or anywhere above to see how the string accumulates.

      s.Append(">");
      output.Write(s.ToString());
    }

Wish List

I wish I was able to include the VerticalText.aspx page in the assembly of the VerticalLabel control. It seems that this is not possible - the page should be part of the final assembly or be a separate one. (Then the above code should include the full path to that assembly, the control should include a property to point to that assembly URL, and the user would have to type that URL seed for each vertical label in the project; considering all that, it seems that the price to copy the VerticalText.aspx page in the final assembly is not too high.)

When I've finished writing this project, an article from asp.netPRO magazine (September 2004) - "Custom HTTP Handlers" by Dino Exposito - was suggesting that it would have been possible to use an IHttpHandler interfaced class to achieve the same effect as with the VerticalText.aspx page. I might go in that soon, but again - the price to pay for reconfiguring the IIS and having the handler active on any other project from the server seems to be as well too high at this point. Maybe with some added functionality as variable angles, graphic effects, and retrieving background/watermark images from databases or other sources...

I wish also being able to include characters like & and # in the caption of the label or in the specification of unknown colors.

The technique described here does not support transparent bitmaps. There is an excellent article - "Transparent GIFs with GDI+ / System.Drawing" about re-coloring GIFs and making them transparent, but the code is a bit too stuffy, and I'm not going yet to include it in here; in fact, my project works perfectly without...

Maybe, I'll get some good suggestions from you...

License

This article has no explicit license attached to it but may contain usage terms in the article text or the download files themselves. If in doubt please contact the author via the discussion board below.

A list of licenses authors might use can be found here