123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458 |
- using System;
- using System.Collections;
- using System.Drawing;
- using System.Drawing.Drawing2D;
- using System.ComponentModel;
- using System.Collections.Generic;
- using System.Drawing.Text;
- using System.Globalization;
- namespace FastReport.Utils
- {
- /// <summary>
- /// Used to draw a text with non-standard angle or justification.
- /// </summary>
- internal class DrawText
- {
- private List<Paragraph> paragraphs;
- #region Properties
- public List<Paragraph> Paragraphs
- {
- get { return paragraphs; }
- }
- #endregion
- #region Private Methods
- private void SplitToParagraphs(string text)
- {
- string[] lines = text.Split('\n');
- int originalTextIndex = 0;
- foreach (string line in lines)
- {
- string s = line;
- if (s.Length > 0 && s[s.Length - 1] == '\r')
- s = s.Remove(s.Length - 1);
- paragraphs.Add(new Paragraph(s, originalTextIndex));
- originalTextIndex += line.Length + 1;
- }
- }
- private void DrawBlockAlign(string text, Graphics g, Font font, Brush textBrush,
- RectangleF textRect, StringFormat format, HorzAlign horzAlign, float lineHeight, bool forceJustify)
- {
- bool wordWrap = (format.FormatFlags & StringFormatFlags.NoWrap) == 0;
- StringFormatFlags saveFlags = format.FormatFlags;
- format.FormatFlags |= StringFormatFlags.NoWrap;
- if (lineHeight == 0)
- lineHeight = font.GetHeight(g);
- // wrap words
- int charactersFitted;
- float height = CalcHeight(text, g, font, textRect.Width, textRect.Height, horzAlign,
- lineHeight, forceJustify, (format.FormatFlags & StringFormatFlags.DirectionRightToLeft) > 0,
- wordWrap, format.Trimming, out charactersFitted);
- // calculate offset if line alignment is not Near
- float offsetY = textRect.Top;
- if (format.LineAlignment == StringAlignment.Center)
- offsetY = textRect.Top + (textRect.Height - height) / 2;
- else if (format.LineAlignment == StringAlignment.Far)
- offsetY = textRect.Top + (textRect.Height - height) - 1;
- // set clip. needed if amount of text greather than textRect.Height
- Region saveClip = g.Clip;
- g.SetClip(textRect, CombineMode.Intersect);
- // draw each paragraph
- foreach (Paragraph paragraph in paragraphs)
- {
- paragraph.Draw(g, font, textBrush, textRect.Left, offsetY, textRect.Width, textRect.Height, format);
- }
- g.SetClip(saveClip, CombineMode.Replace);
- format.FormatFlags = saveFlags;
- }
- private void DrawNormalText(string text, Graphics g, Font font, Brush brush, RectangleF rect,
- StringFormat format, HorzAlign horzAlign, float fontWidthRatio, float lineHeight,
- bool wysiwyg, bool forceJustify)
- {
- GraphicsState state = g.Save();
- g.ScaleTransform(fontWidthRatio, 1);
- rect.X /= fontWidthRatio;
- rect.Width /= fontWidthRatio;
- if (horzAlign == HorzAlign.Justify || wysiwyg || lineHeight != 0)
- DrawBlockAlign(text, g, font, brush, rect, format, horzAlign, lineHeight, forceJustify);
- else
- g.DrawString(text, font, brush, rect, format);
- g.Restore(state);
- }
- private void DrawRotatedText(string text, Graphics g, Font font, Brush brush, RectangleF rect,
- StringFormat format, HorzAlign horzAlign, float fontWidthRatio, float lineHeight, int angle,
- bool wysiwyg, bool forceJustify)
- {
- GraphicsState state = g.Save();
- Region saveClip = g.Clip;
- g.SetClip(rect, CombineMode.Intersect);
- g.TranslateTransform(rect.Left + rect.Width / 2, rect.Top + rect.Height / 2);
- g.RotateTransform(angle);
- rect.X = -rect.Width / 2;
- rect.Y = -rect.Height / 2;
- if ((angle >= 90 && angle < 180) || (angle >= 270 && angle < 360))
- rect = new RectangleF(rect.Y, rect.X, rect.Height, rect.Width);
- g.ScaleTransform(fontWidthRatio, 1);
- rect.X /= fontWidthRatio;
- rect.Width /= fontWidthRatio;
- if ((angle == 0 || angle == 90 || angle == 180 || angle == 270) &&
- (horzAlign == HorzAlign.Justify || wysiwyg || lineHeight != 0))
- DrawBlockAlign(text, g, font, brush, rect, format, horzAlign, lineHeight, forceJustify);
- else
- g.DrawString(text, font, brush, rect, format);
- g.Restore(state);
- g.SetClip(saveClip, CombineMode.Replace);
- }
- #endregion
- #region Public Methods
- /// <summary>
- /// Draws a string.
- /// </summary>
- /// <param name="text">String to draw.</param>
- /// <param name="g"><b>Graphics</b> object to draw on.</param>
- /// <param name="font">Font that used to draw text.</param>
- /// <param name="brush">Brush that determines the color and texture of the drawn text. </param>
- /// <param name="rect"><b>RectangleF</b> structure that specifies the location of the drawn text.</param>
- /// <param name="format">StringFormat that specifies formatting attributes, such as line spacing and alignment, that are applied to the drawn text.</param>
- /// <param name="horzAlign">Horizontal alignment of the text.</param>
- /// <param name="fontWidthRatio">Width ratio of the font used to draw a string.</param>
- /// <param name="lineHeight">Line height, in pixels.</param>
- /// <param name="angle">Angle of the text, in degrees.</param>
- /// <param name="wysiwyg">Indicates whther to draw string close to the printout.</param>
- /// <param name="forceJustify">Force justify for the last line.</param>
- public void Draw(string text, Graphics g, Font font, Brush brush, RectangleF rect, StringFormat format,
- HorzAlign horzAlign, float fontWidthRatio, float lineHeight, int angle, bool wysiwyg, bool forceJustify)
- {
- angle %= 360;
- if (angle == 0)
- DrawNormalText(text, g, font, brush, rect, format, horzAlign, fontWidthRatio, lineHeight, wysiwyg, forceJustify);
- else
- DrawRotatedText(text, g, font, brush, rect, format, horzAlign, fontWidthRatio, lineHeight, angle, wysiwyg, forceJustify);
- }
- // Wraps the text and calculates its height, in pixels.
- public float CalcHeight(string text, Graphics g, Font font, float width, float lineHeight, bool wordWrap)
- {
- int charactersFit;
- return CalcHeight(text, g, font, width, 1000000, HorzAlign.Left, lineHeight, false, false,
- wordWrap, StringTrimming.None, out charactersFit);
- }
- // Wraps the text and calculates its height, in pixels.
- public float CalcHeight(string text, Graphics g, Font font, float width, float height, HorzAlign horzAlign,
- float lineHeight, bool forceJustify, bool rightToLeft, bool wordWrap, StringTrimming trimming)
- {
- int charactersFit;
- return CalcHeight(text, g, font, width, height, horzAlign, lineHeight, forceJustify, rightToLeft,
- wordWrap, trimming, out charactersFit);
- }
- // Wraps the text and calculates its height, in pixels.
- public float CalcHeight(string text, Graphics g, Font font, float width, float height, HorzAlign horzAlign,
- float lineHeight, bool forceJustify, bool rightToLeft, bool wordWrap, StringTrimming trimming,
- out int charactersFit)
- {
- if (lineHeight == 0)
- lineHeight = font.GetHeight(g);
- // make paragraphs
- SplitToParagraphs(text);
- charactersFit = -1;
- // wrap words in each paragraph
- float top = 0;
- foreach (Paragraph paragraph in paragraphs)
- {
- int charsFit = -1;
- top = paragraph.Wrap(g, font, top, (int)Math.Round(width), height, horzAlign, lineHeight, forceJustify,
- wordWrap, trimming, out charsFit);
- if (rightToLeft)
- paragraph.ApplyRTL((int)width);
- if (charsFit != -1 && charactersFit == -1)
- charactersFit = charsFit;
- }
- // height of the text
- if (charactersFit == -1)
- charactersFit = text.Length;
- return top;
- }
- #endregion
- /// <summary>
- /// Initializes a new instance of the <b>DrawText</b> class with default settings.
- /// </summary>
- public DrawText()
- {
- paragraphs = new List<Paragraph>();
- }
- }
- internal class Paragraph
- {
- private List<Word> words;
- public List<Word> Words
- {
- get { return words; }
- }
- private void AlignLine(int startWord, int count, float width, HorzAlign horzAlign)
- {
- if (count == 0)
- return;
- int lastWordIndex = startWord + count - 1;
- float lineWidth = words[lastWordIndex].Right;
- if (horzAlign == HorzAlign.Justify)
- {
- float delta = (width - lineWidth) / (count - 1);
- float curDelta = delta;
- for (int i = 1; i < count; i++)
- {
- words[i + startWord].left += curDelta;
- curDelta += delta;
- }
- }
- else
- {
- float delta = 0;
- if (horzAlign == HorzAlign.Center)
- delta = (width - lineWidth) / 2;
- else if (horzAlign == HorzAlign.Right)
- delta = width - lineWidth;
- for (int i = 0; i < count; i++)
- {
- words[i + startWord].left += delta;
- }
- }
- }
- private void WrapWord(Graphics g, Font font, float width, int index)
- {
- Word word = words[index];
- TextElementEnumerator charEnum = StringInfo.GetTextElementEnumerator(word.text);
- string text = "";
- while (charEnum.MoveNext())
- {
- string textElement = charEnum.GetTextElement();
- float textWidth = g.MeasureString(text + textElement, font).Width;
- if (textWidth > width)
- {
- if (text == "")
- text = textElement;
- Word newWord = new Word(word.text.Substring(text.Length), word.originalTextIndex + text.Length);
- word.text = text;
- words.Insert(index + 1, newWord);
- return;
- }
- else
- text += textElement;
- }
- }
- public void ApplyRTL(float width)
- {
- foreach (Word word in words)
- {
- word.left = width - word.left;
- }
- }
- public float Wrap(Graphics g, Font font, float top, float width, float height, HorzAlign horzAlign,
- float lineHeight, bool forceJustify, bool wordWrap, StringTrimming trimming, out int charactersFit)
- {
- float left = 0;
- int startWord = 0;
- float actualLineHeight = font.GetHeight(g);
- string text = "";
- charactersFit = -1;
- int i = 0;
- while (i < words.Count)
- {
- Word word = words[i];
- text += word.text;
- float textWidth = g.MeasureString(text, font).Width;
- word.left = left;
- word.top = top;
- word.width = textWidth - left;
- // check line fit
- if (top + actualLineHeight > height + 0.1f)
- charactersFit = word.originalTextIndex;
- // word does not fit
- if (word.Right > width)
- {
- int wordsFit = i - startWord;
- if (wordWrap)
- {
- text = "";
- left = 0;
- if (wordsFit == 0)
- {
- if (word.text.Length == 1)
- {
- // it's one-char word, we can't do anything more
- startWord = i + 1;
- top += lineHeight;
- }
- else
- {
- // there is only one word in the line, we need to break it
- WrapWord(g, font, width, i);
- // continue with the first word
- i--;
- }
- }
- else
- {
- // align the line
- AlignLine(startWord, wordsFit, width, horzAlign);
- // make it first word in the line and continue with it
- startWord = i;
- top += lineHeight;
- i--;
- }
- }
- else
- {
- switch (trimming)
- {
- case StringTrimming.None:
- case StringTrimming.Character:
- WrapWord(g, font, width - word.left, i);
- if (i < words.Count)
- words[i + 1].visible = false;
- break;
- case StringTrimming.Word:
- word.visible = false;
- break;
- case StringTrimming.EllipsisWord:
- if (wordsFit > 0)
- words[i - 1].text += "...";
- word.visible = false;
- break;
- case StringTrimming.EllipsisCharacter:
- WrapWord(g, font, width - word.left - g.MeasureString("..", font).Width, i);
- words[i].text += "...";
- if (i < words.Count)
- words[i + 1].visible = false;
- break;
- }
- break;
- }
- }
- else
- {
- text += " ";
- left = textWidth;
- }
- i++;
- }
- // align the rest
- AlignLine(startWord, i - startWord, width,
- horzAlign == HorzAlign.Justify && !forceJustify ? HorzAlign.Left : horzAlign);
- // adjust starting position for the next paragraph
- top += lineHeight;
- return top;
- }
- public void Draw(Graphics g, Font font, Brush brush, float offsetX, float offsetY,
- float width, float height, StringFormat format)
- {
- float actualLineHeight = font.GetHeight(g);
- StringAlignment saveAlign = format.Alignment;
- StringAlignment saveLineAlign = format.LineAlignment;
- format.Alignment = StringAlignment.Near;
- format.LineAlignment = StringAlignment.Near;
- try
- {
- foreach (Word word in words)
- {
- if (word.visible)
- g.DrawString(word.text, font, brush, word.left + offsetX, word.top + offsetY, format);
- }
- }
- finally
- {
- format.Alignment = saveAlign;
- format.LineAlignment = saveLineAlign;
- }
- }
- public Paragraph(string text, int originalTextIndex)
- {
- this.words = new List<Word>();
- string[] words = text.Split(' ');
- string textWithSpaces = "";
- foreach (string word in words)
- {
- if (word == "")
- textWithSpaces += " ";
- else
- {
- textWithSpaces += word;
- this.words.Add(new Word(textWithSpaces, originalTextIndex));
- originalTextIndex += textWithSpaces.Length + 1;
- textWithSpaces = "";
- }
- }
- }
- }
- internal class Word
- {
- public string text;
- public float left;
- public float top;
- public float width;
- public int originalTextIndex;
- public bool visible;
- public float Right
- {
- get { return left + width; }
- }
- public Word(string s, int originalTextIndex)
- {
- text = s;
- this.originalTextIndex = originalTextIndex;
- visible = true;
- }
- }
- }
|