|
|||||||||||||||||||||||
|
|||||||||||||||||||||||
|
Announcements
Chapters
Services
Feature Zones
|
IntroductionA 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. PrerequisitesThe only choice I had was to - somehow - build small bitmaps and use them in 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 So, the task splits in two smaller tasks:
VerticalText pageLet'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 ( Response.ContentType = "image/png";
The above code generates PNG (Portable Network Graphic) content. We target that, when typing the following address
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
Let's throw some parametersOf 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. 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 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 Do the same for 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 angleThere are two ways to measure the size of the bitmap: measure the string rendered horizontally and reverse the 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:
Tip: to make the vertical text even more readable, make it 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 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 useTo 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 <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:
Now, pack it up!The second task on our list was to pack all that in an easy to use WebControl - the So, inherit the Next, we just have to generate the HTML code in the 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 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 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 s.Append(" Alt=\""); s.Append(Text); s.Append("\""); // !
Close the tag and write all that to the s.Append(">");
output.Write(s.ToString());
}
Wish ListI wish I was able to include the VerticalText.aspx page in the assembly of the 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 I wish also being able to include characters like 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...
|
||||||||||||||||||||||