Word Wrap while Printing

J

Jeff B.

Has anyone come across a decent algorithm for implementing word wrap
features in .net printing? I have a small component that uses basic
printing techniques (i.e. e.Graphics.DrawString in a PrintPage event of a
PrintDocument object) to send some formatted text to the printer. However,
if the lines are too long they run off the page rather than wrapping around.
I'm sure I can spend the time and come up with a word wrapping algorithm but
figured why go through the trouble if someone already knows of one :)

--- Thanks, Jeff

--

Jeff Bramwell
Digerati Technologies, LLC
www.digeratitech.com

Manage Multiple Network Configurations with Select-a-Net
www.select-a-net.com
 
K

Kevin Yu [MSFT]

Hi Jeff,

First of all, I would like to confirm my understanding of your issue. From
your description, I understand that you need to print text with word wrap.
If there is any misunderstanding, please feel free to let me know.

As far as I know, if you're trying to print RTF text, you can either use
the old VB6 control and wrap it for .net, or you can write your own code.
You can also use MS Word Automation and copy the RichText to a Word object
and call its print method if you have Word. The latter is the lamest
approach, but it works.

Also, there are some 3rd party .NET components. Please try to check the
following link:

http://www.codeguru.com/Csharp/Csharp/cs_controls/richtext/article.php/c4781

Kevin Yu
=======
"This posting is provided "AS IS" with no warranties, and confers no
rights."
 
K

Kevin Yu [MSFT]

Hi Jeff,

If you use a drawstring that takes a rectanglef it will word wrap for you.

g.DrawString(textbox1.text, textbox1.font, Brushes.Black,
RectangleF.op_Implicit(e.PageBounds))

Kevin Yu
=======
"This posting is provided "AS IS" with no warranties, and confers no
rights."
 
P

Picho

Jeff,

When working with the Graphics.DrawString method, one of the parameters is a
StringFormat object. this object defines wrapping behaviour among other
things.
pass the StringFormat object definitions and a Rectangle object to draw on
(rather than a Point) as parameters to the Graphics.DrawString method and
it should do the trick.

I do that all the time - unless I misunderstood you.

Picho
 
J

Jeff B.

Thanks Kevin and Picho for the info. You both understood exactly what I was
after and the "rectangle" method you mention almost does exactly what I
need. The only part I'm missing is being able to increment the line count
by how many lines the text was wrapped so the subsequent lines display
correctly and the pages break correctly.

I think I'm going to take a slightly different approach and break the lines
out programmatically as I print them. I'll post back any successes - or
failures :)

--- Jeff

--

Jeff Bramwell
Digerati Technologies, LLC
www.digeratitech.com

Manage Multiple Network Configurations with Select-a-Net
www.select-a-net.com
 
K

Kevin Yu [MSFT]

Hi Jeff,

Thanks for sharing your experience with all the people here. If you have
any questions, please feel free to post them in the community.

Kevin Yu
=======
"This posting is provided "AS IS" with no warranties, and confers no
rights."
 
J

Jeff B.

I think I'm going to take a slightly different approach and break the
lines out programmatically as I print them. I'll post back any
successes - or failures :)

Just a follow up...

I ended up creating a method that takes a line of text and returns an
ArrayList of lines after performing a "word wrap" on them. This method lets
me easily, and programatically, print lines while keeping track of how many
lines have been printed - even if they've been "wrapped". Below is the code
to the method I've created. I'm sure it can still be optimized somewhat and
some things can probably be done more efficiently, so if anyone has any
suggestions, please let me know. Please don't pay too much attention to the
formatting, I've altered it somewhat so it takes up less space.

<<< BEGIN CODE SNIPPET >>>
private System.Collections.ArrayList wordWrap(string text,
System.Drawing.Font printFont, System.Drawing.Graphics graphics,
System.Drawing.Rectangle marginBounds)
{
System.Collections.ArrayList lines;
string buffer = text;
System.Drawing.SizeF size;
int index1;
int index2;
char[] whitespace = new char[] {' ', '\t', '\r', '\n'};

try {
lines = new ArrayList();

while (buffer.Length > 0) {
size = graphics.MeasureString(buffer, printFont);

if (size.Width > marginBounds.Width) {
// Find the wrapping point of the line based on the width
for (index1 = buffer.Length - 1; index1 >= 0; index1--) {
size = graphics.MeasureString(buffer.Substring(0, index1),
printFont);

if (size.Width <= marginBounds.Width) {
// We found the wrapping point now let's look for the first
// whitespace character - if there is one
index2 = buffer.LastIndexOfAny(whitespace, index1);

if (index2 >= 0) {
// Whitespace character found
lines.Add(buffer.Substring(0, index2));
buffer = buffer.Substring(index2);
break;
}
else {
// Whitespace was not found
lines.Add(buffer.Substring(0, index1));
buffer = buffer.Substring(index1);
}
break;
}
}
}
else {
// This line completely fits so add it to the buffer unaltered
lines.Add(buffer);
buffer = "";
}
}
return lines;
}
catch {
return null;
}
finally {
lines = null;
}
}
<<< END CODE SNIPPIET >>>

--

Jeff Bramwell
Digerati Technologies, LLC
www.digeratitech.com

Manage Multiple Network Configurations with Select-a-Net
www.select-a-net.com
 
R

Ron Allen

Jeff,
To get the # of lines used for the wrapped printing you can use the
MeasureString method that takes a SizeF argument (#7 of 7 in my Intellisense
list). The SizeF should be the width and height of the rectangle you are
printing in and, of course, use the same StringFormat as the DrawString
call. This overload has two out parameters nline, and nchars that return
the # of lines and characters that fit. If nchars is less than the length
of the string the string would be truncated on output. You can use this to
see if remaining text fits on the rest of the page and then advance the
baseline by the # of lines returned after you draw the string.

Ron Allen
 
J

Jeff Louie

Jeff B... Here is an attempt to break up text into an array of lines
that fit on a page.

===== // UNTESTED // ======

public void ResetArray(IPrintEngine pe,
Graphics g,
RectangleF clipRect)
{
arrayLines.Clear();
float pageWidth= clipRect.Width- (this.horizontalMargin*2);
char newLineChar= '\r';
char space= ' ';
// use tabAsString later to convert tabs to spaces
StringBuilder tabBuilder= new StringBuilder();
for(int i=0; i<this.tabSize; i++)
{
tabBuilder.Append(space);
}
string tabAsString= tabBuilder.ToString();

// get properties and parse text
Brush brush;
Font font;
StringFormat stringFormat;
if (this.isUseDocumentProperties)
{
brush= pe.Brush;
font= pe.Font;
stringFormat= pe.StringFormat;
}
else
{
brush= new SolidBrush(Color.Black);
font= this.Font;
stringFormat= this.stringFormat;
}
string text= this.Text;
if (this.isReplaceTokens)
{
text= pe.ReplaceTokens(text);
}

// break out lines with embedded line break
string temp= this.Text.Replace(System.Environment.NewLine,"\r");
string[] arrayUnfittedLines= temp.Split(newLineChar);

// break out lines that overflow
foreach (string line in arrayUnfittedLines)
{
// replace tabs with spaces
string parsed;
parsed= line.Replace("\t",tabAsString);
// add to array if line of word tokens fits in page margin
if (g.MeasureString(parsed,font,
Int32.MaxValue,
stringFormat).Width <= pageWidth)
{
arrayLines.Add(parsed);
}
else
// line does not fit on page
// need to break up line into word tokens
// and then place tokens into lines that fit in page margin
{
string[] arrayTokens= parsed.Split(space); // tokenize line
StringBuilder buffer;
int index=0;
while (index < arrayTokens.Length)
{
buffer= new StringBuilder();
// at least one token on line, ? may be clipped
buffer.Append(arrayTokens[index]+space);
index++;
while(index< arrayTokens.Length &&
g.MeasureString(buffer.ToString()+arrayTokens[index],
font,
Int32.MaxValue,
stringFormat).Width <= pageWidth)
{
buffer.Append(arrayTokens[index]+space);
index++;
}
// end of tokens or end of line
arrayLines.Add(buffer.ToString());
}
}
}
this.currentIndex= 0;
}
Regards,
Jeff
I think I'm going to take a slightly different approach and break the
lines out programmatically as I print them. I'll post back any successes
- or failures :)<



Regards,
Jeff
Has anyone come across a decent algorithm for implementing word wrap
features in .net printing?
 
J

Jeff Louie

I have had a chance to work on a word wrap alogrithm and it can be
improved by extracting the lines directly from the textbox:

string[] arrayUnfittedLines= new string[this.Lines.Length];
this.Lines.CopyTo(arrayUnfittedLines,0);

Secondly, there is no need to replace tabs, simply set the tab stops as
in:

stringFormat.SetTabStops(0.0f,pe.TabStops);

Regards,
Jeff
 
G

Guest

I see you've already found a fix to your issue but I thought others might
like a an answer to your original question. Here's what I do for wordwrap
using the tools supplied with .NET.

// This routine will print a document with 2 columns,
// wordraped text in each column, the left and right
// columns match row sizes, similare to equal cell sizes
private void document_PrintPage(object sender,
System.Drawing.Printing.PrintPageEventArgs e)
{
float left = e.MarginBounds.Left;
float top = e.MarginBounds.Top;
float width = e.MarginBounds.Width / 2 - 5; // this spaces the columns
evenly
float height = e.MarginBounds.Height;
int fitted = 0; // use only to correctly format the MeasureString method
int qFilled = 0, aFilled;

// The variables are named to identify a question column and an answer
column
RectangleF qDrawRect = new RectangleF(left, top, width, height); // Left
column rectangle
RectangleF aDrawRect = new RectangleF(left + width + 10, top, width,
height); // right

// Setup to measure the line lines
SizeF qSizeF = new SizeF(width, height);
SizeF aSizeF = new SizeF(width, height);

// Although you can initialize string attributes here
// this line is only to fill in the parameter list
// We'll use whatever is given to us
StringFormat newStringFormat = new StringFormat();

// The following code sets the font to be
// the same as the displayed font (in this case, in
// a datagrid)
System.Drawing.Font printFont = new System.Drawing.Font
(myGrid.DefaultCellStyle.Font.Name,
myGrid.DefaultCellStyle.Font.Size,
myGrid.DefaultCellStyle.Font.Style);

// Insert code to render the page here.
// This code will be called when the control is drawn.
while (linesPrinted <= lines.GetUpperBound(0))
{
// Here's where we find out how many lines will be printed during a
word wrap
e.Graphics.MeasureString(lines[linesPrinted,0], printFont,
qSizeF, newStringFormat, out fitted, out qFilled);
e.Graphics.MeasureString(lines[linesPrinted,1], printFont,
aSizeF, newStringFormat, out fitted, out aFilled);

// This prints the two columns, properly formatted and,
// if necessary, wordwrapped
e.Graphics.DrawString(lines[linesPrinted, 0], printFont,
System.Drawing.Brushes.Black, qDrawRect);
e.Graphics.DrawString(lines[linesPrinted++, 1], printFont,
System.Drawing.Brushes.Black, aDrawRect);

// Now we use the info we gathered in the MeasureString statement
// If either column wrapped, qFilled and/or aFilled will be larger
than 1
// I'm using the ?: operator to determine which is larger and using
that
// one. I also add a half line to the equation to provide "paragraph"
// spacing, a nice touch to the printout
aDrawRect.Y += (int)myGrid.DefaultCellStyle.Font.Height *
(qFilled > aFilled ? qFilled : aFilled) +
(int)(myGrid.DefaultCellStyle.Font.Height * .5);
qDrawRect.Y = aDrawRect.Y;

if (aDrawRect.Y >= e.MarginBounds.Bottom)
{
e.HasMorePages = true;
return;
}
}
linesPrinted = 0;
e.HasMorePages = false;
}
 

Ask a Question

Want to reply to this thread or ask your own question?

You'll need to choose a username for the site, which only take a couple of moments. After that, you can post your question and our members will help you out.

Ask a Question

Top