Click here to Skip to main content
15,868,016 members
Please Sign up or sign in to vote.
4.00/5 (2 votes)
Hi
I have to produce several bilingual epub books with both ltr and rtl text direction but some ebook readers, such as nook, can not handle rtl text, also apple based ebook readers overwrites embeeded fonts. My solution to this problem is to convert rtl text to images. Since there are many rtl sentences I am using John Dyer's dynamic image replacement method.(http://johndyer.name/post/2008/04/dynamic-text-replacement-with-javascript-csharp-asp-net.aspx[^]) In Dyer's code, you can convert html elements into images based on their class.

My problem is John Dyer's code produces one line image for every text no matter how long it is. Which means If I have a very long sentence, all the text makes up one very long single line image. If I force width to some value such as 600px, images with long lines of text gets shrunk to 600px but still single line and too small to read.

Simply I need achieve text wrap. How can I wrap long line of text over multiple lines based on specified width such as 600px then convert to image?

Or is there way to produce images with multiple lines when I set display: block with width:600px.

Also is there a way to change dpi to 96 or 150 because edge of the actual images looks little bit crispy and faded on ebook readers?

I got to finish this project soon, unfortunately I know C# very little (it turns out) I don't know c# at all.
using System;
using System.Web;
using System.Web.Caching;
using System.Drawing;
using System.IO;
using System.Drawing.Imaging;
using System.Drawing.Drawing2D;
using System.Drawing.Text;

public class FontWriter : IHttpHandler
{
    // set to false for testing
    private bool enableCache = false;

    private TextRenderingHint textHint = TextRenderingHint.AntiAlias;
    
    // in some cases, fonts might need to be offset a little bit if they draw strangely
    private int topOffset = 0;
    private int bottomOffset = 0;
    private int leftOffset = 0;
    private int rightOffset = 0;
    
    // for locally loaded fonts
    private string fontsPath = ""; // leave blank for current directory
   
    private string defaultColor = "#00FF00";
    private string defaultFontName = "Arial";
    private string defaultBackgroundColor = "#ffffff"; // use "transparent" for transparency
    private int defaultHeight = 20;
    private int defaultWidth = 200;
    private Double defaultFontSize = 18.0F;
    
    public void ProcessRequest (HttpContext context) {
       
        string text = context.Request["text"] + "";
        //text = context.Request.PhysicalPath.Substring(0, context.Request.PhysicalPath.LastIndexOf(@"\"));


        Double fontSize = -1.0F;
        if (!Double.TryParse((context.Request["size"] + ""+"").Replace("px",""), out fontSize))
        {
            fontSize = defaultFontSize;
        }
        
        int width = -1;
        if (!Int32.TryParse( (context.Request["width"]+"").Replace("px",""), out width)) {
            width = -1;
        }

        int height = -1;
        if (!Int32.TryParse((context.Request["height"]+"").Replace("px",""), out height)) {
            height = -1;
        }
         
        string fontName = context.Request["font"] + "";
        if (fontName == "" || fontName == "[font]")
            fontName = defaultFontName;
        
        string foreColor = context.Request["color"] + ""; ;
        if (foreColor == "" || foreColor == "[color]")
            foreColor = defaultColor;

        if (!foreColor.StartsWith("#"))
            foreColor = "#" + foreColor;

        string backColor = context.Request["bcolor"] + ""; ;
        if (backColor == "" || backColor == "[bcolor]")
            backColor = defaultBackgroundColor;

        
        context.Trace.Write(   String.Format("text: {0}; font:{1}; color:{2}; bgcolor:{3}; width:{4}; height:{5}; size:{6}", text, fontName, foreColor, backColor, width, height, fontSize)           );
        
        // main bitmap for drawing text
        Bitmap bitmap = null;
        
        // FIRST, attempt to retreive from cache        
        string key = String.Format("FontWriter-{0}-{1}-{2}-{3}-{4}-{5}-{6}", text, fontName, foreColor, backColor, width, height, fontSize);
        bitmap = context.Cache.Get(key) as Bitmap;

        
        if (bitmap == null || !enableCache)
        {            
            // CHECK FOR FONT
            Font font = null;

            FontFamily[] ff = FontFamily.Families;
            bool hasFont = false;
            foreach (FontFamily f in ff)
            {
                if (f.Name == fontName)
                {
                    hasFont = true;
                    break;
                }
            }

            context.Trace.Write("found font :" + hasFont.ToString());

            if (hasFont)
            {
                font = new Font(fontName, (float)fontSize);
            }
            else
            {
                // try to find the font locally if it's not available
                
                PrivateFontCollection pfc = new PrivateFontCollection();
                if (fontsPath == "")
                    fontsPath = context.Request.PhysicalPath.Substring(0, context.Request.PhysicalPath.LastIndexOf(Path.DirectorySeparatorChar));

                string fontPath = Path.Combine(fontsPath, fontName);

                context.Trace.Write("looking for: " + fontPath);

                if (File.Exists(fontPath))
                {
                    pfc.AddFontFile(fontPath);
                    context.Trace.Write("added: " + fontPath);
                } 
            

                // try to add the font the local font
                if (pfc.Families.Length > 0)
                {
                    context.Trace.Write("using loaded font");
                    font = new Font(pfc.Families[0], (float)fontSize);
                }
                else
                {
                    font = new Font(defaultFontName, (float)fontSize);
                }

            }


            // DRAWING CODE
            // dummy bitmap
            bitmap = new Bitmap(10, 10);

            SolidBrush brush = new SolidBrush(ColorTranslator.FromHtml(foreColor));
            Graphics g = Graphics.FromImage(bitmap);
            g.TextRenderingHint = textHint;

            // check if the size has been specified by the client
            if (width == -1 && height == -1)
            {
                // measure the string based on the font size and build the image to that size
                SizeF imgSize = g.MeasureString(text, font);
                bitmap = new Bitmap(Convert.ToInt32(imgSize.Width), Convert.ToInt32(imgSize.Height));
                g = Graphics.FromImage(bitmap);
                
            } else {

                bool foundCorrectSize = false;

                SizeF imgSize = new SizeF(0, 0);
                do
                {

                    imgSize = g.MeasureString(text, font);

                    // check for fit
                    if (imgSize.Width - leftOffset - rightOffset > width && width > 0 || imgSize.Height - topOffset - bottomOffset > height && height > 0)
                    {
                        // try a smaller font
                        fontSize = fontSize -= 0.5F;
                        font = new Font(font.FontFamily, (float)fontSize);
                    }
                    else
                    {
                        // yeah! found it
                        foundCorrectSize = true;
                    }

                } while (!foundCorrectSize);               

                bitmap = new Bitmap(Convert.ToInt32(imgSize.Width) , Convert.ToInt32(imgSize.Height) );
                g = Graphics.FromImage(bitmap);
            }


            if (backColor == "")
            {
                g.Clear(Color.Transparent);
            }
            else
            {
                try
                {
                    g.Clear(ColorTranslator.FromHtml(backColor));
                }
                catch (Exception ex)
                {
                    g.Clear(ColorTranslator.FromHtml(defaultBackgroundColor));
                }
            }
            g.TextRenderingHint = textHint;        
            g.DrawString(text, font, brush, -topOffset, -bottomOffset);

            context.Cache.Add(key, bitmap, null, Cache.NoAbsoluteExpiration, new TimeSpan(0, 10, 0), CacheItemPriority.Normal, null);
        }
        
        // PUSH OUT to client
        context.Response.Clear();
        
        if (backColor == "transparent")
        {
            MemoryStream io = new MemoryStream();
            bitmap.Save(io, ImageFormat.Png);

            context.Response.ContentType = "image/png";
            context.Response.BinaryWrite(io.GetBuffer());
        }
        else
        {
            context.Response.ContentType = "image/jpg";
            SaveAsJpeg(bitmap, context.Response.OutputStream, (long)100);
        }
    }
 
    public bool IsReusable {
        get {
            return false;
        }
    }

    void SaveAsJpeg(Image inputImage, Stream stream, long quality)
    {

        // generate JPEG stuff
        ImageCodecInfo codecEncoder = GetEncoder("image/jpeg");
        EncoderParameters encoderParams = new EncoderParameters(1);
        EncoderParameter qualityParam = new EncoderParameter(System.Drawing.Imaging.Encoder.Quality, quality);
        encoderParams.Param[0] = qualityParam;

        inputImage.Save(stream, codecEncoder, encoderParams);
    }

    ImageCodecInfo GetEncoder(string mimeType)
    {
        ImageCodecInfo[] codecs = ImageCodecInfo.GetImageEncoders();
        foreach (ImageCodecInfo codec in codecs)
        {
            if (codec.MimeType == mimeType)
            {
                return codec;
            }
        }
        return null;
    }
}
Posted
Updated 6-May-11 9:04am
v7

You can create an image, and then simply write text onto it. I wrote an article that demonstrates how to write text onto a bitmap. You may be able to adjust the code for your purposes.

Render Text On A Bitmap (and other stuff)[^]
 
Share this answer
 
v2
Comments
Sergey Alexandrovich Kryukov 1-May-11 18:35pm    
Simple enough. My 5.
--SA
sinan38tr 5-May-11 8:50am    
Please, can you show me how to do it in this code?
Sergey Alexandrovich Kryukov 5-May-11 11:59am    
Already shown. Are you asking the question before looking at the article referenced. The code you're looking for is written in the first code sample in this article.
--SA
sinan38tr 6-May-11 15:02pm    
I'm sorry I have been trying to adapt the code past two days,but no luck, I just couldn't do it. I guess I don't know c# at all or I am just a plain idiot.
Sergey Alexandrovich Kryukov 6-May-11 15:20pm    
No reasonable person would question your abilities based just on the fact that you did not master some language or technique just yet. Perhaps you should communicate your concrete difficulties to get some help. I thought the code sample John provided is good enough to start with.

Also, maybe you should not try to solve your problem at once, but try to go in small iterations starting with simplest possible prototype. In this way, you can understand how it works and make yourself comfortable enough to make a next step. I think you can count on some help if you face a problem. (Right, John?)

--SA
Here is my God damn solution which took me ages to figure out but works perfectly though you have to hardcode defaultwidth of the images and also direction of the text.

If text length is larger than defaultwidth, text will continue on the next line, so you will have multiline image of the text.
You can change the text direction. Currently it is rtl but ltr is commented out. Just switch the comment lines you will have left aligned text.

<%@ WebHandler Language="C#" Class="FontWriter" %>

using System;
using System.Web;
using System.Web.Caching;
using System.Drawing;
using System.IO;
using System.Drawing.Imaging;
using System.Drawing.Drawing2D;
using System.Drawing.Text;
using System.Collections.Generic;
using System.Linq;
using System.Web.UI;

public class FontWriter : IHttpHandler
{
    // set to false for testing
    private bool enableCache = false;

    private TextRenderingHint textHint = TextRenderingHint.AntiAlias;
    
    // in some cases, fonts might need to be offset a little bit if they draw strangely
    private int topOffset = 0;
    private int bottomOffset = 0;
    private int leftOffset = 0;
    private int rightOffset = 0;
    
    // for locally loaded fonts
    private string fontsPath = ""; // leave blank for current directory
   
    private string defaultColor = "#00FF00";
    private string defaultFontName = "Arial";
    private string defaultBackgroundColor = "#ffffff"; // use "transparent" for transparency
    private int defaultHeight = 20;
    private Double defaultFontSize = 18.0F;
    Color FontColor = Color.Blue;
    Color BackColor = Color.White;
       
    //You have to hardcode default width
    private int defaultWidth = 1000;
    

    
   
     private Rectangle GetConstrainedTextHeight(string textToParse, ref string resultText)
    {
        // to ease typing, we set a local variable to the value specified in the 
        // settings object
        int quoteAreaWidth = defaultWidth;

        // create a new bitmap - I don't knowe if the size matters, but just to 
        // be safe, I set it to be larger than the expected height, and the max 
        // area width
        Bitmap bitmap = new Bitmap(100, quoteAreaWidth);

        // create a graphics object from the image. This gives us access to the 
        // GDI drawing functions needed to do what we're here to do.
        Graphics g = Graphics.FromImage(bitmap);
          
        // Get the size of the area needed to display the original text as a 
        // single line.

        string fontName = HttpContext.Current.Request["font"] + "";
        if (fontName == "" || fontName == "[font]")
            fontName = defaultFontName;

        Double fontSize = -1.0F;
        if (!Double.TryParse((HttpContext.Current.Request["size"] + "" + "").Replace("px", ""), out fontSize))
        {
            fontSize = defaultFontSize;
        }

        Font font = new Font(fontName, (float)fontSize);
        
        SizeF sz = g.MeasureString(textToParse, font);

        // Make sure we actually have work to do. If the quote width is smaller 
        // than the desired max width, we can exit right now. This should almost 
        // always happen for author text.
        if (sz.Width <= quoteAreaWidth)
        {
            resultText = textToParse;
            // don't forget to clean up our resources
            g.Dispose();
            bitmap.Dispose();
            return new Rectangle(new Point(0, 0), new Size((int)sz.Width, (int)sz.Height));
        }

        // make sure our resultText is empty
        resultText = "";

        // split the orginal text into separate words
        string[] words = textToParse.Trim().Split(' ');
        string nextLine = "";
        string word = "";

        for (int i = 0; i < words.Length; i++)
        {
            word = words[i];

            // get the size of the current line
            SizeF lineSize = g.MeasureString(nextLine + 1, font);

            // get the size ofthe new word
            SizeF wordSize = g.MeasureString(" " + word, font);

            // if the line including the new word is smaller than our constrained size
            if (lineSize.Width + wordSize.Width < quoteAreaWidth)
            {
                // add the word to the line
                nextLine = string.Format("{0} {1}", nextLine, word);

                // If it's the last word in the original text, add the line 
                // to the resultText
                if (i == words.Length - 1)
                {
                    resultText += nextLine;
                }
            }
            else
            {
                // Add the current line to the resultText *without* the new word, 
                // but with a linefeed
                resultText += (nextLine + "\n");

                // Start a new current line
                nextLine = word;

                // If it's the last word in the original text, add the line 
                // to the resultText
                if (i == words.Length - 1)
                {
                    resultText += nextLine;
                }
            }
        }

        // It's time to get a new measurement for the string. The Graphics.MeasureString 
        // method takes into account the linefeed characters we inserted.
        sz = g.MeasureString(resultText, font);

        // Cleanup our resources
        g.Dispose();
        bitmap.Dispose();

        // Return the rectangle to the calling method - Alt satir burada kesiliyor. Dikkat et.
        //test  new Rectangle(new Point(0, 0), new Size((int)sz.Width, (int)sz.Height + 2*FontSize + FontSize/3));
        return new Rectangle(new Point(0, 0), new Size((int)sz.Width, (int)sz.Height ));
    }

    
    
    public void ProcessRequest (HttpContext context) {
       
        string text = context.Request["text"] + "";
        //text = context.Request.PhysicalPath.Substring(0, context.Request.PhysicalPath.LastIndexOf(@"\"));


        Double fontSize = -1.0F;
        if (!Double.TryParse((context.Request["size"] + ""+"").Replace("px",""), out fontSize))
        {
            fontSize = defaultFontSize;
        }
        
        int width = -1;
        if (!Int32.TryParse( (context.Request["width"]+"").Replace("px",""), out width)) {
            width = -1;
        }

        int height = -1;
        if (!Int32.TryParse((context.Request["height"]+"").Replace("px",""), out height)) {
            height = -1;
        }
         
        string fontName = context.Request["font"] + "";
        if (fontName == "" || fontName == "[font]")
            fontName = defaultFontName;
        
        string foreColor = context.Request["color"] + ""; ;
        if (foreColor == "" || foreColor == "[color]")
            foreColor = defaultColor;

        if (!foreColor.StartsWith("#"))
            foreColor = "#" + foreColor;

        string backColor = context.Request["bcolor"] + ""; ;
        if (backColor == "" || backColor == "[bcolor]")
            backColor = defaultBackgroundColor;

        
        context.Trace.Write(   String.Format("text: {0}; font:{1}; color:{2}; bgcolor:{3}; width:{4}; height:{5}; size:{6}", text, fontName, foreColor, backColor, width, height, fontSize)           );
        
        // main bitmap for drawing text
        Bitmap bitmap = null;
        
        // FIRST, attempt to retreive from cache        
        string key = String.Format("FontWriter-{0}-{1}-{2}-{3}-{4}-{5}-{6}", text, fontName, foreColor, backColor, width, height, fontSize);
        bitmap = context.Cache.Get(key) as Bitmap;

        
        if (bitmap == null || !enableCache)
        {            
            // CHECK FOR FONT
            Font font = null;

            FontFamily[] ff = FontFamily.Families;
            bool hasFont = false;
            foreach (FontFamily f in ff)
            {
                if (f.Name == fontName)
                {
                    hasFont = true;
                    break;
                }
            }

            context.Trace.Write("found font :" + hasFont.ToString());

            if (hasFont)
            {
                font = new Font(fontName, (float)fontSize);
            }
            else
            {
                // try to find the font locally if it's not available
                
                PrivateFontCollection pfc = new PrivateFontCollection();
                if (fontsPath == "")
                    fontsPath = context.Request.PhysicalPath.Substring(0, context.Request.PhysicalPath.LastIndexOf(Path.DirectorySeparatorChar));

                string fontPath = Path.Combine(fontsPath, fontName);

                context.Trace.Write("looking for: " + fontPath);

                if (File.Exists(fontPath))
                {
                    pfc.AddFontFile(fontPath);
                    context.Trace.Write("added: " + fontPath);
                } 
            

                // try to add the font the local font
                if (pfc.Families.Length > 0)
                {
                    context.Trace.Write("using loaded font");
                    font = new Font(pfc.Families[0], (float)fontSize);
                }
                else
                {
                    font = new Font(defaultFontName, (float)fontSize);
                }

            }
            
            // DRAWING CODE

            string newQuoteText = "";
            Rectangle quoteRect = GetConstrainedTextHeight(text, ref newQuoteText);

                        
            font = new Font(fontName, (float)fontSize);

            bitmap = new Bitmap(quoteRect.Width, quoteRect.Height);
            Graphics g = Graphics.FromImage(bitmap);

            PointF point = new PointF(5.0F, 5.0F);

            SolidBrush BrushForeColor = new SolidBrush(FontColor);
            SolidBrush BrushBackColor = new SolidBrush(BackColor);
            Pen BorderPen = new Pen(Color.Black);
            
            // Set the text rendering characteristics - we want it to be attractive
            g.SmoothingMode = System.Drawing.Drawing2D.SmoothingMode.AntiAlias;
            g.TextRenderingHint = System.Drawing.Text.TextRenderingHint.ClearTypeGridFit;
            g.Clear(Color.Transparent);


            //TextRenderer.DrawText(g, newQuoteText,font,new Point(quoteRect.X, quoteRect.Y), Color.Black);


            //Draw text string using the text format

            g.FillRectangle(BrushBackColor, quoteRect);
            //g.DrawString(newQuoteText, font, Brushes.Red, (RectangleF)quoteRect, format2);
            

            SolidBrush brush = new SolidBrush(ColorTranslator.FromHtml(foreColor));
       
            if (backColor == "")
            {
                g.Clear(Color.Transparent);
            }
            else
            {
                try
                {
                    g.Clear(ColorTranslator.FromHtml(backColor));
                }
                catch (Exception ex)
                {
                    g.Clear(ColorTranslator.FromHtml(defaultBackgroundColor));
                }
            }
            g.TextRenderingHint = textHint;
            //Define string format 

            StringFormat format1 = new StringFormat();
            format1.FormatFlags = StringFormatFlags.DirectionRightToLeft;


            //For right alignment (format1) use this
            g.DrawString(newQuoteText, font, brush, quoteRect.Width, -bottomOffset, format1);

            //For left alignment use this
            //g.DrawString(newQuoteText, font, brush, -topOffset, -bottomOffset);

            context.Cache.Add(key, bitmap, null, Cache.NoAbsoluteExpiration, new TimeSpan(0, 10, 0), CacheItemPriority.Normal, null);
        }
        
        // PUSH OUT to client
        context.Response.Clear();
        
        if (backColor == "transparent")
        {
            MemoryStream io = new MemoryStream();
            bitmap.Save(io, ImageFormat.Png);

            context.Response.ContentType = "image/png";
            context.Response.BinaryWrite(io.GetBuffer());
        }
        else
        {
            context.Response.ContentType = "image/jpg";
            SaveAsJpeg(bitmap, context.Response.OutputStream, (long)100);
        }
    }
 
    public bool IsReusable {
        get {
            return false;
        }
    }

    void SaveAsJpeg(Image inputImage, Stream stream, long quality)
    {

        // generate JPEG stuff
        ImageCodecInfo codecEncoder = GetEncoder("image/jpeg");
        EncoderParameters encoderParams = new EncoderParameters(1);
        EncoderParameter qualityParam = new EncoderParameter(System.Drawing.Imaging.Encoder.Quality, quality);
        encoderParams.Param[0] = qualityParam;

        inputImage.Save(stream, codecEncoder, encoderParams);
    }

    ImageCodecInfo GetEncoder(string mimeType)
    {
        ImageCodecInfo[] codecs = ImageCodecInfo.GetImageEncoders();
        foreach (ImageCodecInfo codec in codecs)
        {
            if (codec.MimeType == mimeType)
            {
                return codec;
            }
        }
        return null;
    }

}


Thanks to Render Text On A Bitmap (and other stuff)[^] and
 
Share this answer
 
v3

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



CodeProject, 20 Bay Street, 11th Floor Toronto, Ontario, Canada M5J 2N8 +1 (416) 849-8900