The complete TextFormatter listing.
using
System;
using
System.IO;
using
System.ComponentModel;
using
System.Text;
using
System.Collections;
using
System.Drawing;
using
System.Drawing.Drawing2D;
using
System.Windows.Forms;
using
System.Windows.Forms.Design;
namespace
WellFormed
{
///
<summary>
/// The range of
styles that can be used to justify text
///
</summary>
public enum
JustificationStyles
{
///
<summary>
///
Text is left justified
///
</summary>
Left,
///
<summary>
///
Text is right justified
///
</summary>
Right,
///
<summary>
///
Test is centered between the margins
///
</summary>
Centered,
///
<summary>
///
Text is fully justified between the margins
///
</summary>
Justified
}
///
<summary>
/// The types of
whitespace that the text justifier recognizes
///
</summary>
public enum
WhiteSpace
{
///
<summary>
///
Not a whitespace type
///
</summary>
None,
///
<summary>
///
a space
///
</summary>
Space,
///
<summary>
///
a tab character
///
</summary>
Tab,
///
<summary>
///
a new line cr/lf combination
///
</summary>
NewLine,
///
<summary>
///
special type used to indent the first line of a paragraph
///
</summary>
Indent
}
///
<summary>
/// A specialized
collection class that manages WordPos objects
///
</summary>
public class
WordPosCollection : CollectionBase
{
bool
_containsNewline=false;
bool
_newParagraph=false;
///
<summary>
///
///
</summary>
public
bool NewParagraph
{
get{return
_newParagraph;}
set{_newParagraph=value;}
}
///
<summary>
///
Readonly property. when true, the formatted text contains a newline whitespace
character
///
</summary>
public
bool ContainsNewline
{
get{return
_containsNewline;}
}
///
<summary>
///
Strongly typed method to add WordPos objects to the cllection.
///
</summary>
///
<param name="wp">The
WordPos object to add</param>
public
void Add(WordPos wp)
{
if(wp.WhiteSpace==WhiteSpace.NewLine)
_containsNewline=true;
List.Add(wp);
}
///
<summary>
///
Indexer that gets and sets WordPos objecs at a specified index within the
collection
///
</summary>
public
WordPos this[int
index]
{
get{return
(WordPos)List[index];}
set
{
if(value.WhiteSpace==WhiteSpace.NewLine)
this._containsNewline=true;
List[index]=value;
}
}
///
<summary>
///
Removes the last WordPos object from the collection.
///
</summary>
public
void RemoveLast()
{
List.RemoveAt(List.Count-1);
}
///
<summary>
///
Returns the last WordPos object in the collection.
///
</summary>
///
<returns>The
last WordPos object in the collection</returns>
public
WordPos Last()
{
if(List.Count==0)
return
null;
return
(WordPos)List[List.Count-1];
}
}
///
<summary>
/// WordPos is a
description for a word and poition.
///
</summary>
///
<remarks>
/// The WordPos object
maintains data about the linear position within the margins of a page, the
physical width of a word, the font and its whitespace characteristics.
///
</remarks>
public class
WordPos : ICloneable
{
public
float PagePos;
public
string Word;
public
float WordWidth;
public
WhiteSpace WhiteSpace;
public
Font Font;
///
<summary>
///
Constructor
///
</summary>
public
WordPos()
{
}
///
<summary>
///
Constructor for a whitespace word position
///
</summary>
///
<param name="ws">The
WhiteSpace type to use</param>
public
WordPos(WhiteSpace ws)
{
WhiteSpace = ws;
PagePos=0;
Word =
"";
WordWidth = 0;
}
///
<summary>
///
Constructor
///
</summary>
///
<param name="ws">The
WhiteSpace type</param>
///
<param name="pp">Page
position</param>
///
<param name="word">The
string definition of the word</param>
///
<param name="ww">Word
Width</param>
///
<param name="font">the
Font used to draw the word</param>
protected
WordPos(WhiteSpace ws, float pp,
string word, float
ww, Font font)
{
WhiteSpace = ws;
PagePos=pp;
Word =
word;
WordWidth = ww;
Font=font;
}
///
<summary>
///
Creates a string definition of this object for diagnostic purposes
///
</summary>
///
<returns>a
string describing the contents of the WordPos object</returns>
public
override string
ToString()
{
return
string.Format("WordPos:\r\n"+
"\tPagePos = {0}\r\n"+
"\tWord = {1}\r\n"+
"\tWordWidth = {2}\r\n"+
"\tWhiteSpace = {3}\r\n",
PagePos,
Word,
WordWidth,
WhiteSpace);
}
///
<summary>
///
Creates a copy of the object
///
</summary>
///
<returns>a copy
of the current object</returns>
object
ICloneable.Clone()
{
return
this.Clone();
}
///
<summary>
///
returns a copy of the object
///
</summary>
///
<returns>An
exact copy of the object</returns>
public
WordPos Clone()
{
return
new WordPos(WhiteSpace,
PagePos,
Word,
WordWidth,
Font);
}
}
///
<summary>
/// The Formatter
takes a string of text and arranges the words so that they are justified within
a specified set of boundaries
///
</summary>
public class
Formatter
{
float
_leftmargin;
float
_columnwidth;
float
_Pos;
float
_indent;
float[]
_tabs;
float
_tabstops;
JustificationStyles _justify;
string
_text;
WordPosCollection _words=new
WordPosCollection();
protected
int WordIndex;
///
<summary>
///
The value by which to indent the first word in the paragraph
///
</summary>
public
float Indent
{
get{return
_indent;}
set{_indent=value;}
}
///
<summary>
///
The text to be formatted
///
</summary>
public
string Text
{
get{return
_text;}
set{_text=value;}
}
///
<summary>
///
The collection of word position objects that define the current text
///
</summary>
[Browsable(false)]
public
WordPosCollection Words
{
get{return
_words;}
set{_words=value;}
}
///
<summary>
///
The style of justification used
///
</summary>
public
JustificationStyles Justify
{
get{return
_justify;}
set{_justify=value;}
}
///
<summary>
///
the value of the left margin. Normally 0 but may be used to add margins within
a text area
///
</summary>
public
float LeftMargin
{
get{return
_leftmargin;}
set{_leftmargin=value;}
}
///
<summary>
///
The width of the column used to format text into
///
</summary>
public
float ColumnWidth
{
get{return
_columnwidth;}
set{_columnwidth=value;}
}
///
<summary>
///
///
</summary>
[Browsable(false)]
public
float Pos
{
get{return
_Pos;}
set{_Pos=value;}
}
///
<summary>
///
An array of tab stop values
///
</summary>
public
float[] Tabs
{
get{return
_tabs;}
set{_tabs=value;}
}
///
<summary>
///
gets or sets the width of a tab-stop for this formatter.
///
</summary>
///
<remarks>
///
Setting the TabStops will reset the tab array
///
</remarks>
public
float TabStops
{
get{return
_tabstops;}
set
{
_tabstops=value;
ArrayList temptabs=new ArrayList();
for(float
f = LeftMargin;f<LeftMargin+ColumnWidth;f+=_tabstops)
temptabs.Add(f);
float[]
tabs=new float[temptabs.Count];
temptabs.CopyTo(tabs);
Tabs=tabs;
}
}
///
<summary>
///
Returns a word-width for a specific word, font and graphics object
///
</summary>
///
<param name="s">The
word to measure</param>
///
<param name="font">The
font used to display the word</param>
///
<param name="g">The
graphics device upon which formatted text will be displayed</param>
///
<returns></returns>
public
float GetWordWidth(string
s, Font font, Graphics g)
{
StringFormat sf=StringFormat.GenericTypographic;
sf.FormatFlags|=StringFormatFlags.MeasureTrailingSpaces;
SizeF
sz = g.MeasureString(s,font,4096,sf);
return
sz.Width;
}
///
<summary>
///
Removes all words from the word list
///
</summary>
void
ClearWords()
{
this._words.Clear();
}
///
<summary>
///
Adds a string of text to the wordlist
///
</summary>
///
<remarks>
///
The text is analysed to extract a full word position description for the
supplied paragraph
///
</remarks>
///
<param name="text">The
text to add</param>
///
<param name="font">The
font used to display the text</param>
///
<param name="g">The
graphics device upon which the text is to be displayed</param>
public
void AddWords(string
text, Font font, Graphics g)
{
WordPosCollection wpl=GetWords(text,font,g);
foreach(WordPos
wp in wpl)
_words.Add(wp);
}
///
<summary>
///
returns the word list
///
</summary>
///
<returns>The
collection of words contained in the formatter</returns>
public
WordPosCollection GetWords()
{
return
_words;
}
///
<summary>
///
Returns a word collection for a specified paragraph.
///
</summary>
///
<param name="text">The
text to format</param>
///
<param name="font">The
font used to display the word</param>
///
<param name="g">The
graphics device upon which formatted text will be displayed</param>
///
<returns>the
collection of words after analysis</returns>
public
WordPosCollection GetWords(string text, Font
font, Graphics g)
{
if(text==null
|| font==null)
return
new WordPosCollection();
WordPosCollection PossArray = new
WordPosCollection();
WordPos
wp;
//remove
carriage returns..
string[]
subsnocr = text.Split(new Char[]{'\r'});
StringBuilder
sb = new StringBuilder();
foreach(string
ss in subsnocr)
sb.Append(ss);
string
s=sb.ToString();
//we
test for this whitespace...
char[]
testarray = new char[]{'
','\t','\n'};
do
{
bool
done=false;
do
{
if(s.IndexOfAny(testarray)==0)
{
switch(s[0])
{
case ' ':
wp=new WordPos(WhiteSpace.Space);
wp.Word=" ";
break;
case '\t':
wp=new WordPos(WhiteSpace.Tab);
wp.Word="\t";
break;
case '\n':
wp=new
WordPos(WhiteSpace.NewLine);
break;
default:
wp=new WordPos();
break;
}
wp.Font=font;
wp.WordWidth=this.GetWordWidth(wp.Word,font,g);
PossArray.Add(wp);
s=s.Substring(1,s.Length-1);
}
else
done=true;
}
while(!done);
if(s.Length==0)
continue;
int
wsindex = s.IndexOfAny(testarray);
wp =
new WordPos();
if(wsindex>-1)
{
wp.Word=s.Substring(0,wsindex);
wp.WordWidth=this.GetWordWidth(wp.Word,font,g);
s=s.Substring(wsindex,s.Length-wsindex);
}
else
{
wp.Word=s;
wp.WordWidth=GetWordWidth(s,font,g);
s="";
}
wp.Font=font;
PossArray.Add(wp);
}
while(s.Length>0);
return
PossArray;
}
///
<summary>
///
Removes the whitespace from the front of a word list
///
</summary>
///
<remarks>Tab
characters are not removed because they perform sensible formatting at the
beginning of a line.</remarks>
///
<param name="words">A
reference to the word list that should be trimmed</param>
public
void TrimStart(ref
WordPosCollection words)
{
int
firstindex=0;
if(words.Count==0)
return;
if(words[0].WhiteSpace==WhiteSpace.Indent)
firstindex=1;
float
pos=words[0].PagePos;
bool
needsRePos = false;
while(words[firstindex].WhiteSpace==WhiteSpace.Space
|| words[firstindex].WhiteSpace==WhiteSpace.NewLine)
{
words.RemoveAt(firstindex);
needsRePos=true;
if(words.Count>0)
words[0].PagePos=pos;
}
if(needsRePos)
RePos(ref words);
}
///
<summary>
///
Trims the whitespace from the end of a word list
///
</summary>
///
<param name="words">A
reference to the word list to be trimmed</param>
public
void TrimEnd(ref
WordPosCollection words)
{
if(words.Count==0)
return;
while(words.Count!=0
&& (words.Last().WhiteSpace!=WhiteSpace.None))
{
words.RemoveLast();
}
}
///
<summary>
///
returns then position of the next tabstop after the current position
///
</summary>
///
<param name="currPos">The
current position</param>
///
<returns>The
linear value of the calculated tab-stop</returns>
public
float GetNextTab(float
currPos)
{
if(Tabs==null)
return
currPos;
foreach(float
f in Tabs)
{
if(f>currPos)
return
f;
}
return
currPos;
}
///
<summary>
///
Re positions words according to their calculated word widths
///
</summary>
///
<param name="words">A
reference to the line being repaginated</param>
public
void RePos(ref
WordPosCollection words)
{
if(words.Count==0)
return;
float
currPos = words[0].PagePos;
for(int
i=0;i<words.Count;i++)
{
words[i].PagePos=currPos;
currPos+=words[i].WordWidth;
}
}
///
<summary>
///
Calculates word positions for a centred line
///
</summary>
///
<param name="words">A
reference to the line being repaginated</param>
public
void CenterLine(ref
WordPosCollection words)
{
if(words.Count==0)
return;
TrimEnd(ref words);
if(words.Count==0)
return;
TrimStart(ref words);
if(words.Count==0)
return;
words[0].PagePos=(ColumnWidth/2) - (GetLineLength(ref
words)/2) + LeftMargin;
RePos(ref
words);
}
///
<summary>
///
Fully Justifies a line
///
</summary>
///
<param name="words">A
reference to the collection of word positions that constitute the current line</param>
public
void JustifyLine(ref
WordPosCollection words)
{
if(words.Count==0)
return;
if(words.ContainsNewline)
{
LeftJustifyLine(ref words);
return;
}
TrimEnd(ref words);
if(words.Count==0)
return;
TrimStart(ref words);
if(words.Count==0)
return;
int
lastTab = 0;
int
n=0;
int
spacecount=0;
foreach(WordPos
wp in words)
{
if(wp.WhiteSpace==WhiteSpace.Tab)
{
lastTab=n;
spacecount=0;
}
if(wp.WhiteSpace==WhiteSpace.Space)
spacecount++;
n++;
}
WordPos
lastWord = words.Last();
float
difference = ColumnWidth - GetLineLength(ref
words);
if(spacecount>0)
{
for(int
i=lastTab;i<words.Count;i++)
{
if(words[i].WhiteSpace==WhiteSpace.Space)
words[i].WordWidth+=difference/spacecount;
}
}
RePos(ref
words);
}
///
<summary>
///
Calculates word positions for a right justified line
///
</summary>
///
<param name="words">A
reference to the line being repaginated</param>
public
virtual void
RightJustifyLine(ref WordPosCollection words)
{
if(words.Count==0)
return;
TrimEnd(ref words);
if(words.Count==0)
return;
TrimStart(ref words);
if(words.Count==0)
return;
words[0].PagePos=LeftMargin;
RePos(ref
words);
words[0].PagePos=ColumnWidth-GetLineLength(ref
words)+LeftMargin;
RePos(ref
words);
}
///
<summary>
///
Calculates word positions for a left justified line
///
</summary>
///
<param name="words">A
reference to the line being repaginated</param>
public
void LeftJustifyLine(ref
WordPosCollection words)
{
if(words.Count==0)
return;
TrimEnd(ref words);
if(words.Count==0)
return;
TrimStart(ref words);
if(words.Count==0)
return;
RePos(ref
words);
}
///
<summary>
///
measures a total length of a line
///
</summary>
///
<param name="words">A
reference to the line being measured</param>
///
<returns>The
total length of the line</returns>
public
float GetLineLength(ref
WordPosCollection words)
{
float
f=0;
foreach(WordPos
wp in words)
f+=wp.WordWidth;
return
f;
}
///
<summary>
///
Justifies the specified line
///
</summary>
///
<param name="line">A
reference to the line being justified</param>
public
void DoJustify(ref
WordPosCollection line)
{
DoJustify(ref
line,this.Justify);
}
///
<summary>
///
Justifies the specified line
///
</summary>
///
<param name="line">A
reference to the line being justified</param>
///
<param name="justify">The
style of justifiation to perform</param>
public
void DoJustify(ref
WordPosCollection line, JustificationStyles justify)
{
switch(justify)
{
case
JustificationStyles.Centered:
CenterLine(ref
line);
break;
case
JustificationStyles.Justified:
JustifyLine(ref
line);
break;
case
JustificationStyles.Right:
RightJustifyLine(ref line);
break;
case
JustificationStyles.Left:
LeftJustifyLine(ref line);
break;
}
}
///
<summary>
///
Calculates an array of lines that are correctly justified for a paragraph of a
certain column width.
///
</summary>
///
<param name="words">The
raw list of words in the paragraph</param>
///
<returns>An
array of lines</returns>
public
WordPosCollection[] GetLines(WordPosCollection words)
{
ArrayList lines=new ArrayList();
bool
firstLine=true;
bool
paragraph=true;
int
WordIndex=0;
WordPosCollection line;
do
{
line
= new WordPosCollection();
line.NewParagraph=true;
float
lineTotal=LeftMargin;
//Does
this line need to be indented?
if(firstLine
&& Indent!=0)
{
WordPos iwp=new WordPos();
iwp.PagePos=LeftMargin;
iwp.WhiteSpace=WhiteSpace.Space;
iwp.WordWidth=Indent;
iwp.Font=words[0].Font;
line.Add(iwp);
}
firstLine=false;
for(;WordIndex<words.Count;WordIndex++)
{
//calculate
the word Pos
WordPos wp=words[WordIndex].Clone();
wp.PagePos=lineTotal;
//holds
the additional size to be added to lineTotal
//This
takes care of odd values added due to tab-stops etc.
float
extra=0;
bool
newline = false;
//calculate
the lineTotal shift
switch(wp.WhiteSpace)
{
case
WhiteSpace.Space:
case
WhiteSpace.None:
extra+=wp.WordWidth;
break;
case
WhiteSpace.Tab:
extra+=this.GetNextTab(lineTotal)-lineTotal;
wp.WordWidth=extra;
break;
case
WhiteSpace.NewLine:
extra=0;
line.Add(wp);
newline=true;
paragraph=true;
break;
}
if(lineTotal+extra
> LeftMargin+ColumnWidth)
newline=true;
//if
the line is long enough, justify whats in this line and move on
if(newline)
{
DoJustify(ref line);
lines.Add(line);
line=new WordPosCollection();
line.NewParagraph=paragraph;
paragraph=false;
lineTotal=LeftMargin;
wp.PagePos=lineTotal;
if(extra!=0)
{
line.Add(wp);
lineTotal+=extra;
}
}
else
//just add the current entity to the line
{
line.Add(wp);
lineTotal+=extra;
}
}
}
while(WordIndex<words.Count-1);
//until there are no more entities left in the word
list
if(line.Count!=0)
{
//Takes
care of the last line in a justified paragraph.
//which
must always be left justified.
if(this.Justify==JustificationStyles.Justified)
DoJustify(ref
line,JustificationStyles.Left);
else
DoJustify(ref line,this.Justify);
lines.Add(line);
}
WordPosCollection[] linearray=new
WordPosCollection[lines.Count];
lines.CopyTo(linearray);
return
linearray;
}
///
<summary>
///
Constructor
///
</summary>
public
Formatter()
{
}
}
[ToolboxBitmap(typeof(TextPanel),"TextFormatter.TextPanel.bmp")]
public class
TextPanel : Panel
{
Formatter
fmtr=new Formatter();
bool
_showWhiteSpace;
public
bool ShowWhiteSpace
{
get{return
_showWhiteSpace;}
set{_showWhiteSpace=value;}
}
[Browsable(true)]
public
override string
Text
{
get
{
return
base.Text;
}
set
{
base.Text
= value;
}
}
public
float[] Tabs
{
get{return
fmtr.Tabs;}
set{fmtr.Tabs=value;}
}
public
float StandardTabs
{
get{return
fmtr.TabStops;}
set{fmtr.TabStops=value;}
}
public
JustificationStyles JustificationStyle
{
get{return
fmtr.Justify;}
set{
fmtr.Justify=value;
Invalidate();
}
}
public
TextPanel() : base()
{
Graphics g=this.CreateGraphics();
fmtr.TabStops=g.DpiX/2; //standard tab-stops every
1/2 inch
g.Dispose();
}
protected
override void
OnPaint(PaintEventArgs e)
{
if(fmtr.Words.Count==0)
return;
float
yStep=Font.GetHeight();
float
ty=0;
fmtr.LeftMargin=0;
fmtr.ColumnWidth=this.ClientSize.Width;
SolidBrush sb=new SolidBrush(this.ForeColor);
foreach(WordPosCollection
wpc in fmtr.GetLines(fmtr.Words))
{
foreach(WordPos
wp in wpc)
{
if(wp.WhiteSpace==WhiteSpace.None)
{
e.Graphics.DrawString(wp.Word,Font,sb,wp.PagePos,ty,StringFormat.GenericTypographic);
}
else
{
if(_showWhiteSpace)
{
e.Graphics.DrawRectangle(Pens.Red,wp.PagePos,ty,wp.WordWidth,yStep);
}
}
}
ty+=yStep;
}
sb.Dispose();
}
protected
virtual void
RefreshText()
{
fmtr.Words.Clear();
Graphics g=this.CreateGraphics();
fmtr.AddWords(this.Text,Font,g);
g.Dispose();
Invalidate();
}
protected
override void
OnTextChanged(EventArgs e)
{
RefreshText();
base.OnTextChanged
(e);
}
protected
override void
OnFontChanged(EventArgs e)
{
RefreshText();
base.OnFontChanged
(e);
}
protected
override void
OnSizeChanged(EventArgs e)
{
fmtr.LeftMargin=0;
fmtr.ColumnWidth=this.ClientSize.Width;
Graphics g=CreateGraphics();
fmtr.TabStops=g.DpiX/2;
g.Dispose();
Invalidate();
base.OnSizeChanged
(e);
}
}
}
Imports
System
Imports
System.IO
Imports
System.Security.Cryptography
Imports
System.ComponentModel
Imports
System.Text
Imports
System.Collections
Imports
System.Drawing
Imports
System.Drawing.Drawing2D
Imports
System.Windows.Forms
Imports
System.Windows.Forms.Design
Namespace
WellFormed
_
'/ <summary>
'/ The range of styles that can be used to justify
text
'/ </summary>
Public Enum
JustificationStyles
'/
<summary>
'/
Text is left justified
'/
</summary>
Left
'/
<summary>
'/
Text is right justified
'/
</summary>
Right
'/
<summary>
'/
Test is centered between the margins
'/
</summary>
Centered
'/
<summary>
'/
Text is fully justified between the margins
'/
</summary>
Justified
End Enum
'JustificationStyles
_
'/ <summary>
'/ The types of whitespace that the text justifier
recognizes
'/ </summary>
Public Enum
WhiteSpace
'/
<summary>
'/
Not a whitespace type
'/
</summary>
None
'/
<summary>
'/
a space
'/
</summary>
Space
'/
<summary>
'/
a tab character
'/
</summary>
Tab
'/
<summary>
'/
a new line cr/lf combination
'/
</summary>
NewLine
'/
<summary>
'/
special type used to indent the first line of a paragraph
'/
</summary>
Indent
End Enum
'WhiteSpace
_
'/ <summary>
'/ A specialized collection class that manages WordPos
objects
'/ </summary>
Public Class
WordPosCollection
Inherits
CollectionBase
Private
_containsNewline As
Boolean = False
Private
_newParagraph As
Boolean = False
'/
<summary>
'/
'/
</summary>
Public
Property NewParagraph()
As Boolean
Get
Return
_newParagraph
End
Get
Set(ByVal
Value As Boolean)
_newParagraph = value
End
Set
End
Property
'/
<summary>
'/
Readonly property. when true, the formatted text contains a newline whitespace
character
'/
</summary>
Public
ReadOnly Property
ContainsNewline() As
Boolean
Get
Return
_containsNewline
End
Get
End
Property
'/
<summary>
'/
Strongly typed method to add WordPos objects to the cllection.
'/
</summary>
'/
<param name="wp">The WordPos object to add</param>
Public
Sub Add(ByVal wp
As WordPos)
If
wp.WhiteSpace = WhiteSpace.NewLine Then
_containsNewline = True
End
If
List.Add(wp)
End
Sub 'Add
'/
<summary>
'/
Indexer that gets and sets WordPos objecs at a specified index within the
collection
'/
</summary>
Default
Public Property
Item(ByVal index As
Integer) As
WordPos
Get
Return
CType(List(index), WordPos)
End
Get
Set(ByVal
Value As WordPos)
If
value.WhiteSpace = WhiteSpace.NewLine Then
Me._containsNewline
= True
End
If
List(index) = value
End
Set
End
Property
'/
<summary>
'/
Removes the last WordPos object from the collection.
'/
</summary>
Public
Sub RemoveLast()
List.RemoveAt((List.Count - 1))
End
Sub 'RemoveLast
'/
<summary>
'/
Returns the last WordPos object in the collection.
'/
</summary>
'/
<returns>The last WordPos object in the collection</returns>
Public
Function Last() As
WordPos
If
List.Count = 0 Then
Return
Nothing
End
If
Return
CType(List((List.Count - 1)), WordPos)
End
Function 'Last
End Class
'WordPosCollection
_
'/ <summary>
'/ WordPos is a description for a word and poition.
'/ </summary>
'/ <remarks>
'/ The WordPos object maintains data about the linear
position within the margins of a page, the physical width of a word, the font
and its whitespace characteristics.
'/ </remarks>
Public Class
WordPos
Implements
ICloneable 'ToDo: Add Implements Clauses for
implementation methods of these interface(s)
Public
PagePos As Single
Public
Word As String
Public
WordWidth As Single
Public
WhiteSpace As WhiteSpace
Public
Font As Font
'/
<summary>
'/
Constructor
'/
</summary>
Public
Sub New()
End
Sub 'New
'/
<summary>
'/
Constructor for a whitespace word position
'/
</summary>
'/
<param name="ws">The WhiteSpace type to use</param>
Public
Sub New(ByVal
ws As WhiteSpace)
WhiteSpace = ws
PagePos
= 0
Word =
""
WordWidth = 0
End
Sub 'New
'/
<summary>
'/
Constructor
'/
</summary>
'/
<param name="ws">The WhiteSpace type</param>
'/
<param name="pp">Page position</param>
'/
<param name="word">The string definition of the word</param>
'/
<param name="ww">Word Width</param>
'/
<param name="font">the Font used to draw the word</param>
Protected
Sub New(ByVal
ws As WhiteSpace, ByVal
pp As Single,
ByVal wrd As
String, ByVal ww
As Single,
ByVal fnt As
Font)
Me.WhiteSpace
= ws
Me.PagePos
= pp
Me.Word
= wrd
Me.WordWidth
= ww
Me.Font
= fnt
End
Sub 'New
'/
<summary>
'/
Creates a string definition of this object for diagnostic purposes
'/
</summary>
'/
<returns>a string describing the contents of the WordPos object</returns>
Public
Overrides Function
ToString() As String
Return
String.Format("WordPos:" + ControlChars.Cr +
ControlChars.Lf + ControlChars.Tab + "PagePos = {0}" + ControlChars.Cr +
ControlChars.Lf + ControlChars.Tab + "Word = {1}" + ControlChars.Cr +
ControlChars.Lf + ControlChars.Tab + "WordWidth = {2}" + ControlChars.Cr +
ControlChars.Lf + ControlChars.Tab + "WhiteSpace = {3}" + ControlChars.Cr +
ControlChars.Lf, PagePos, Word, WordWidth, WhiteSpace)
End
Function 'ToString
'/
<summary>
'/
Creates a copy of the object
'/
</summary>
'/
<returns>a copy of the current object</returns>
Function
Clone() As Object
Implements ICloneable.Clone
Return
New WordPos(WhiteSpace, PagePos, Word,
WordWidth, Font)
End
Function
'ICloneable.Clone
End Class
'WordPos
_
'/ <summary>
'/ The Formatter takes a string of text and arranges
the words so that they are justified within a specified set of boundaries
'/ </summary>
Public Class
Formatter
Private
_leftmargin As Single
Private
_columnwidth As Single
Private
_Pos As Single
Private
_indent As Single
Private
_tabs() As Single
Private
_tabstops As Single
Private
_justify As JustificationStyles
Private
_text As String
Private
_words As New
WordPosCollection
Protected
WordIndex As Integer
'/
<summary>
'/
The value by which to indent the first word in the paragraph
'/
</summary>
Public
Property Indent() As
Single
Get
Return
_indent
End
Get
Set(ByVal
Value As Single)
_indent = Value
End
Set
End
Property
'/
<summary>
'/
The text to be formatted
'/
</summary>
Public
Property [Text]() As
String
Get
Return
_text
End
Get
Set(ByVal
Value As String)
_text
= Value
End
Set
End
Property
'/
<summary>
'/
The collection of word position objects that define the current text
'/
</summary>
<Browsable(False)> _
Public
Property Words() As
WordPosCollection
Get
Return
_words
End
Get
Set(ByVal
Value As WordPosCollection)
_words = Value
End
Set
End
Property
'/
<summary>
'/
The style of justification used
'/
</summary>
Public
Property Justify() As
JustificationStyles
Get
Return
_justify
End
Get
Set(ByVal
Value As JustificationStyles)
_justify = Value
End
Set
End
Property
'/
<summary>
'/
the value of the left margin. Normally 0 but may be used to add margins within
a text area
'/
</summary>
Public
Property LeftMargin()
As Single
Get
Return
_leftmargin
End
Get
Set(ByVal
Value As Single)
_leftmargin = Value
End
Set
End
Property
'/
<summary>
'/
The width of the column used to format text into
'/
</summary>
Public
Property ColumnWidth()
As Single
Get
Return
_columnwidth
End
Get
Set(ByVal
Value As Single)
_columnwidth = Value
End
Set
End
Property
'/
<summary>
'/
'/
</summary>
<Browsable(False)>
_
Public
Property Pos() As
Single
Get
Return
_Pos
End
Get
Set(ByVal
Value As Single)
_Pos
= Value
End
Set
End
Property
'/
<summary>
'/
An array of tab stop values
'/
</summary>
Public
Property Tabs() As
Single()
Get
Return
_tabs
End
Get
Set(ByVal
Value As Single())
_tabs
= Value
End
Set
End
Property
'/
<summary>
'/
gets or sets the width of a tab-stop for this formatter.
'/
</summary>
'/
<remarks>
'/
Setting the TabStops will reset the tab array
'/
</remarks>
Public
Property TabStops() As
Single
Get
Return
_tabstops
End
Get
Set(ByVal
Value As Single)
_tabstops = Value
Dim
temptabs As New
ArrayList
Dim
f As Single
For
f = LeftMargin To (LeftMargin + ColumnWidth) -
_tabstops Step _tabstops
temptabs.Add(f)
Next
f
Dim
tabs(temptabs.Count) As
Single
temptabs.CopyTo(tabs)
tabs
= tabs
End
Set
End
Property
'/
<summary>
'/
Returns a word-width for a specific word, font and graphics object
'/
</summary>
'/
<param name="s">The word to measure</param>
'/
<param name="font">The font used to display the word</param>
'/
<param name="g">The graphics device upon which formatted text will be
displayed</param>
'/
<returns></returns>
Public
Function GetWordWidth(ByVal
s As String,
ByVal font As
Font, ByVal g As
Graphics) As Single
Dim
sf As StringFormat =
StringFormat.GenericTypographic
sf.FormatFlags = sf.FormatFlags Or
StringFormatFlags.MeasureTrailingSpaces
Dim
sz As SizeF = g.MeasureString(s, font, 4096, sf)
Return
sz.Width
End
Function 'GetWordWidth
'/
<summary>
'/
Removes all words from the word list
'/
</summary>
Sub
ClearWords()
Me._words.Clear()
End
Sub 'ClearWords
'/
<summary>
'/
Adds a string of text to the wordlist
'/
</summary>
'/
<remarks>
'/
The text is analysed to extract a full word position description for the
supplied paragraph
'/
</remarks>
'/
<param name="text">The text to add</param>
'/
<param name="font">The font used to display the text</param>
'/
<param name="g">The graphics device upon which the text is to be
displayed</param>
Public
Sub AddWords(ByVal
[text] As String,
ByVal font As
Font, ByVal g As
Graphics)
Dim
wpl As WordPosCollection = GetWords([text],
font, g)
Dim
wp As WordPos
For
Each wp In wpl
_words.Add(wp)
Next
wp
End
Sub 'AddWords
'/
<summary>
'/
returns the word list
'/
</summary>
'/
<returns>The collection of words contained in the formatter</returns>
Public
Overloads Function
GetWords() As WordPosCollection
Return
_words
End
Function 'GetWords
'/
<summary>
'/
Returns a word collection for a specified paragraph.
'/
</summary>
'/
<param name="text">The text to format</param>
'/
<param name="font">The font used to display the word</param>
'/
<param name="g">The graphics device upon which formatted text will be
displayed</param>
'/
<returns>the collection of words after analysis</returns>
Public
Overloads Function
GetWords(ByVal [text]
As String, ByVal
font As Font, ByVal
g As Graphics) As
WordPosCollection
If
[text] Is Nothing
Or font Is
Nothing Then
Return
New WordPosCollection
End
If
Dim
PossArray As New
WordPosCollection
Dim
wp As WordPos
'remove
carriage returns..
Dim
subsnocr As String()
= [text].Split(New [Char]() {ControlChars.Cr})
Dim
sb As New
StringBuilder
Dim
ss As String
For
Each ss In
subsnocr
sb.Append(ss)
Next
ss
Dim
s As String =
sb.ToString()
'we
test for this whitespace...
Dim
testarray() As Char
= {" "c, ControlChars.Tab, ControlChars.Lf}
Do
Dim
done As Boolean
= False
Do
If
s.IndexOfAny(testarray) = 0 Then
Select
Case s.Chars(0)
Case " "c
wp = New WordPos(WhiteSpace.Space)
wp.Word = " "
Case ControlChars.Tab
wp = New WordPos(WhiteSpace.Tab)
wp.Word = ControlChars.Tab
Case ControlChars.Lf
wp = New
WordPos(WhiteSpace.NewLine)
Case Else
wp = New WordPos
End
Select
wp.Font = font
wp.WordWidth = Me.GetWordWidth(wp.Word,
font, g)
PossArray.Add(wp)
s
= s.Substring(1, s.Length - 1)
Else
done = True
End
If
Loop
While Not done
If
s.Length = 0 Then
GoTo
ContinueDo1
End
If
Dim
wsindex As Integer
= s.IndexOfAny(testarray)
wp =
New WordPos
If
wsindex > -1 Then
wp.Word = s.Substring(0, wsindex)
wp.WordWidth = Me.GetWordWidth(wp.Word,
font, g)
s =
s.Substring(wsindex, s.Length - wsindex)
Else
wp.Word = s
wp.WordWidth = GetWordWidth(s, font, g)
s =
""
End
If
wp.Font = font
PossArray.Add(wp)
ContinueDo1:
Loop
While s.Length > 0
Return
PossArray
End
Function 'GetWords
'/
<summary>
'/
Removes the whitespace from the front of a word list
'/
</summary>
'/
<remarks>Tab characters are not removed because they perform sensible formatting
at the beginning of a line.</remarks>
'/
<param name="words">A reference to the word list that should be trimmed</param>
Public
Sub TrimStart(ByRef
words As WordPosCollection)
Dim
firstindex As Integer
= 0
If
words.Count = 0 Then
Return
End
If
If
words(0).WhiteSpace = WhiteSpace.Indent Then
firstindex = 1
End
If
Dim
pos As Single =
words(0).PagePos
Dim
needsRePos As Boolean
= False
While
words(firstindex).WhiteSpace = WhiteSpace.Space Or
words(firstindex).WhiteSpace = WhiteSpace.NewLine
words.RemoveAt(firstindex)
needsRePos = True
If
words.Count > 0 Then
words(0).PagePos = pos
End
If
End
While
If
needsRePos Then
RePos(words)
End
If
End
Sub 'TrimStart
'/
<summary>
'/
Trims the whitespace from the end of a word list
'/
</summary>
'/
<param name="words">A reference to the word list to be trimmed</param>
Public
Sub TrimEnd(ByRef
words As WordPosCollection)
If
words.Count = 0 Then
Return
End
If
Dim
done As Boolean
= False
Do
If
(words.Count > 0) Then
If
(words.Last().WhiteSpace <> WhiteSpace.None) Then
words.RemoveLast()
Else
done = True
End
If
End
If
Loop
While Not done
End
Sub 'TrimEnd
'/
<summary>
'/
returns then position of the next tabstop after the current position
'/
</summary>
'/
<param name="currPos">The current position</param>
'/
<returns>The linear value of the calculated tab-stop</returns>
Public
Function GetNextTab(ByVal
currPos As Single)
As Single
If
Tabs Is Nothing
Then
Return
currPos
End
If
Dim
f As Single
For
Each f In Tabs
If
f > currPos Then
Return
f
End
If
Next
f
Return
currPos
End
Function 'GetNextTab
'/
<summary>
'/
Re positions words according to their calculated word widths
'/
</summary>
'/
<param name="words">A reference to the line being repaginated</param>
Public
Sub RePos(ByRef
words As WordPosCollection)
If
words.Count = 0 Then
Return
End
If
Dim
currPos As Single
= words(0).PagePos
Dim
i As Integer
For
i = 0 To words.Count - 1
words(i).PagePos
= currPos
currPos
+= words(i).WordWidth
Next
i
End
Sub 'RePos
'/
<summary>
'/
Calculates word positions for a centred line
'/
</summary>
'/
<param name="words">A reference to the line being repaginated</param>
Public
Sub CenterLine(ByRef
words As WordPosCollection)
If
words.Count = 0 Then
Return
RePos(words)
End
Sub 'CenterLine
'/
<summary>
'/
Fully Justifies a line
'/
</summary>
'/
<param name="words">A reference to the collection of word positions that
constitute the current line</param>
Public
Sub JustifyLine(ByRef
words As WordPosCollection)
If
words.Count = 0 Then
Return
RePos(words)
End
Sub 'JustifyLine
'/
<summary>
'/
Calculates word positions for a right justified line
'/
</summary>
'/
<param name="words">A reference to the line being repaginated</param>
Public
Overridable Sub
RightJustifyLine(ByRef words
As WordPosCollection)
If
words.Count = 0 Then
Return
RePos(words)
End
Sub 'RightJustifyLine
'/
<summary>
'/
Calculates word positions for a left justified line
'/
</summary>
'/
<param name="words">A reference to the line being repaginated</param>
Public
Sub LeftJustifyLine(ByRef
words As WordPosCollection)
If
words.Count = 0 Then
Return
End
If
TrimEnd(words)
If
words.Count = 0 Then
Return
End
If
TrimStart(words)
If
words.Count = 0 Then
Return
End
If
RePos(words)
End
Sub 'LeftJustifyLine
'/
<summary>
'/
measures a total length of a line
'/
</summary>
'/
<param name="words">A reference to the line being measured</param>
'/
<returns>The total length of the line</returns>
Public
Function GetLineLength(ByRef
words As WordPosCollection)
As Single
Dim
f As Single = 0
Dim
wp As WordPos
For
Each wp In words
f +=
wp.WordWidth
Next
wp
Return
f
End
Function
'GetLineLength
'/
<summary>
'/
Justifies the specified line
'/
</summary>
'/
<param name="line">A reference to the line being justified</param>
Public
Overloads Sub
DoJustify(ByRef line As
WordPosCollection)
DoJustify(line, Me.Justify)
End
Sub 'DoJustify
'/
<summary>
'/
Justifies the specified line
'/
</summary>
'/
<param name="line">A reference to the line being justified</param>
'/
<param name="justify">The style of justifiation to perform</param>
Public
Overloads Sub
DoJustify(ByRef line As
WordPosCollection, ByVal justify
As JustificationStyles)
Select
Case justify
Case
JustificationStyles.Centered
CenterLine(line)
Case
JustificationStyles.Justified
JustifyLine(line)
Case
JustificationStyles.Right
RightJustifyLine(line)
Case
JustificationStyles.Left
LeftJustifyLine(line)
End
Select
End
Sub 'DoJustify
'/
<summary>
'/
Calculates an array of lines that are correctly justified for a paragraph of a
certain column width.
'/
</summary>
'/
<param name="words">The raw list of words in the paragraph</param>
'/
<returns>An array of lines</returns>
Public
Function GetLines(ByVal
words As WordPosCollection)
As WordPosCollection()
Dim
lines As New
ArrayList
Dim
firstLine As Boolean
= True
Dim
paragraph As Boolean
= True
Dim
WordIndex As Integer
= 0
Dim
line As WordPosCollection
Do
line
= New WordPosCollection
line.NewParagraph = True
Dim
lineTotal As Single
= LeftMargin
'Does
this line need to be indented?
If
firstLine And Indent <> 0
Then
Dim
iwp As New
WordPos
iwp.PagePos = LeftMargin
iwp.WhiteSpace = WhiteSpace.Space
iwp.WordWidth = Indent
iwp.Font = words(0).Font
line.Add(iwp)
End
If
firstLine = False
While
WordIndex < words.Count
'calculate
the word Pos
Dim
wp As WordPos = words(WordIndex).Clone()
wp.PagePos = lineTotal
'holds
the additional size to be added to lineTotal
'This
takes care of odd values added due to tab-stops etc.
Dim
extra As Single
= 0
Dim
newline As Boolean
= False
'calculate
the lineTotal shift
Select
Case wp.WhiteSpace
Case
WhiteSpace.Space, WhiteSpace.None
extra += wp.WordWidth
Case
WhiteSpace.Tab
extra += Me.GetNextTab(lineTotal) -
lineTotal
wp.WordWidth = extra
Case
WhiteSpace.NewLine
extra = 0
line.Add(wp)
newline = True
paragraph = True
End
Select
If
lineTotal + extra > LeftMargin + ColumnWidth Then
newline = True
End
If
'if
the line is long enough, justify whats in this line and move on
If
newline Then
DoJustify(line)
lines.Add(line)
line = New WordPosCollection
line.NewParagraph = paragraph
paragraph = False
lineTotal = LeftMargin
wp.PagePos = lineTotal
If
extra <> 0 Then
line.Add(wp)
lineTotal += extra
End
If
'just
add the current entity to the line
Else
line.Add(wp)
lineTotal += extra
End
If
WordIndex += 1
End
While
Loop
While WordIndex < words.Count - 1
'until
there are no more entities left in the word list
If
line.Count <> 0 Then
'Takes
care of the last line in a justified paragraph.
'which
must always be left justified.
If
Me.Justify = JustificationStyles.Justified
Then
DoJustify(line, JustificationStyles.Left)
Else
DoJustify(line, Me.Justify)
End
If
lines.Add(line)
End
If
Dim
linearray(lines.Count - 1) As WordPosCollection
lines.CopyTo(linearray)
Return
linearray
End
Function 'GetLines
'/
<summary>
'/
Constructor
'/
</summary>
Public
Sub New()
End
Sub 'New
End Class
'Formatter
<ToolboxBitmap(GetType(TextPanel),
"TextFormatter.TextPanel.bmp")> _
Public
Class TextPanel
Inherits
Panel
Private
fmtr As New
Formatter
Private
_showWhiteSpace As
Boolean
Public
Property ShowWhiteSpace()
As Boolean
Get
Return
_showWhiteSpace
End
Get
Set(ByVal
Value As Boolean)
_showWhiteSpace
= Value
End
Set
End
Property
<Browsable(True)>
_
Public
Overrides Property
[Text]() As String
Get
Return
MyBase.Text
End
Get
Set(ByVal
Value As String)
MyBase.Text
= Value
End
Set
End
Property
Public
Property Tabs() As
Single()
Get
Return
fmtr.Tabs
End
Get
Set(ByVal
Value As Single())
fmtr.Tabs = Value
End
Set
End
Property
Public
Property StandardTabs()
As Single
Get
Return
fmtr.TabStops
End
Get
Set(ByVal
Value As Single)
fmtr.TabStops = Value
End
Set
End
Property
Public
Property JustificationStyle()
As JustificationStyles
Get
Return
fmtr.Justify
End
Get
Set(ByVal
Value As JustificationStyles)
fmtr.Justify = Value
Invalidate()
End
Set
End
Property
Public
Sub New()
Dim
g As Graphics = Me.CreateGraphics()
fmtr.TabStops = g.DpiX / 2 'standard tab-stops
every 1/2 inch
g.Dispose()
End
Sub 'New
Protected
Overrides Sub
OnPaint(ByVal e As
PaintEventArgs)
If
fmtr.Words.Count = 0 Then
Return
End
If
Dim
yStep As Single
= Font.GetHeight()
Dim
ty As Single = 0
fmtr.LeftMargin = 0
fmtr.ColumnWidth = Me.ClientSize.Width
Dim
sb As New
SolidBrush(Me.ForeColor)
Dim
wpc As WordPosCollection
For
Each wpc In
fmtr.GetLines(fmtr.Words)
Dim
wp As WordPos
For
Each wp In wpc
If
wp.WhiteSpace = WhiteSpace.None Then
e.Graphics.DrawString(wp.Word, Font, sb, wp.PagePos, ty,
StringFormat.GenericTypographic)
Else
If
_showWhiteSpace Then
e.Graphics.DrawRectangle(Pens.Red, wp.PagePos, ty, wp.WordWidth,
yStep)
End
If
End
If
Next
wp
ty +=
yStep
Next
wpc
sb.Dispose()
End
Sub 'OnPaint
Protected
Overridable Sub
RefreshText()
fmtr.Words.Clear()
Dim
g As Graphics = Me.CreateGraphics()
fmtr.AddWords(Me.Text, Font, g)
g.Dispose()
Invalidate()
End
Sub 'RefreshText
Protected
Overrides Sub
OnTextChanged(ByVal e
As EventArgs)
RefreshText()
MyBase.OnTextChanged(e)
End
Sub 'OnTextChanged
Protected
Overrides Sub
OnFontChanged(ByVal e
As EventArgs)
RefreshText()
MyBase.OnFontChanged(e)
End
Sub 'OnFontChanged
Protected
Overrides Sub
OnSizeChanged(ByVal e
As EventArgs)
fmtr.LeftMargin
= 0
fmtr.ColumnWidth
= Me.ClientSize.Width
Dim
g As Graphics = CreateGraphics()
fmtr.TabStops
= g.DpiX / 2
g.Dispose()
Invalidate()
MyBase.OnSizeChanged(e)
End
Sub 'OnSizeChanged
End Class
'TextPanel
End
Namespace 'WellFormed