-
|
|
Why doesn't XNA have word-wrapping?
|
I'm curious to why XNA doesn't seem to have word wrapping for the SpriteFont.DrawString() method. I have done some programming with Managed DirectX and they had word-wrapping support already built in the framework. I have noticed that DirectX (unmanaged) also supports this feature. So my first question is: Isn't XNA a "wrapper" around DirectX, and if so, why doesn't it also support word wrapping, and all the other formatted text flags for that matter? I guess if XNA doesn't have it, its not the end of the world, but it certainly is inconvenient, and should be a possible option in the next iteration to come.
|
|
-
-
- (14748)
-
premium membership
Team XNA
-
Posts
9,342
|
Re: Why doesn't XNA have word-wrapping?
|
How do you want word wrapping? Breaking up by syllables? Letter by letter? Just on space? Insert hyphen or not? Do you want to support right-to-left languages? (You can see there are lots of different ways to do it). My guess as to why this wasn't implemented was because it was too much work to make a very generic system. Whereas for a single game it's probably 10 minutes of work to implement. For instance I implemented it (along with some other things) in about that time for a video on my site: http://nick.gravelyn.com/2008/04/01/tile-map-engine-pt-12/. So while it would have been nice to have, I think the team allocated their time on the features people really want and couldn't do on their own (networking for instance). Word wrapping is something that's fairly easy to set up and, in my opinion, a nice excercise for programmers to do at least once.
|
|
-
-
- (581)
-
premium membership
-
Posts
439
|
Re: Why doesn't XNA have word-wrapping?
|
My guess is that it's not in XNA because the DirectX calls for text are Windows only, and won't work on Xbox. Of course if I'm wrong, and those calls are available on Xbox, then I agree, why can't we have better text support? Anyway that seems to be the logical reason.
|
|
-
-
- (20872)
-
premium membership
MVP
-
Posts
12,545
|
Re: Why doesn't XNA have word-wrapping?
|
Bill is pretty close.
Text/Sprite rendering in Managed DirectX was a wrapper over the DirectX extensions (D3DX). D3DX doesn't exist on the DirectX that ships on the xbox so the XNA GS team had to implement their own sprite and text handling that would work on both. They chose a simper implementation for both than you get with D3DX e.g. you can do 3d sprites with d3dx.
If its a feature you need then add a request http://forums.xna.com/thread/1864.aspx Though its a fairly simple thing to implement yourself using the measuring APIs in spritefont.
Play Kissy Poo - a game for 4 year olds on Xbox and windows The ZBuffer News and information for XNA Follow The Zman on twitter, Email me Please read the forum FAQs - Bug/Feature reporting Don't forget to mark good answers and good playtest feedback when you see it!!!
|
|
-
|
|
Re: Why doesn't XNA have word-wrapping?
|
I guess i never thought of it that way. I'm working on my own word-wrapping that could be, for the most part, generic, so that i can use it in my own graphical user interface framework that i am designing. It would be nice to have it for when i make textboxes, labels, etc... But I agree, its a nice excercise for programmers to do at least once.
|
|
-
-
- (0)
-
premium membership
-
Posts
193
|
Re: Why doesn't XNA have word-wrapping?
|
Yes, though you'd be staggered how complicated it gets in foreign language. English is easy.
Pandemonium, an occasionally updated blog about my game, XNA, games development, and the games industry; XapParse, a parser for XAP (XACT) files
|
|
-
|
|
Re: Why doesn't XNA have word-wrapping?
|
its funny because i just wrote a generic word GUI, here is the code maybe you can get an idea.
PasteBin Snippet
Just post back if you have any question
|
|
-
|
|
Re: Why doesn't XNA have word-wrapping?
|
XNA at least needs something like System.Drawing.Graphics.DrawString method
which accepts a formatting rectangle and a StringFormat object.
If XNA is left so generic that we have to implement our own text-wrapping, then it NEEDS a better font metrics API than a simple SpriteFont.MeasureString which doesn't tell me jack about the string/character metrics. The documentation is useless too.
What does MeasureString return anyway (line height?, does it consider line breaks?, base lines?, maximum glyph height?, kerning pairs?, etc.)? For now, I'm resorting to parsing plain-text Adobe font metrics files to get character information and kerning data, and doing the text-layout on my own.
|
|
-
-
- (14748)
-
premium membership
Team XNA
-
Posts
9,342
|
Re: Why doesn't XNA have word-wrapping?
|
From MSDN ( LINK): Return Value The height and width, in pixels, of text, when it is rendered. It basically just tells you how wide and tall it will be when rendered with any line breaks you put into there. I think the main reason it isn't in there is that most games don't really need that advanced text rendering. You can toss in simple word-wrapping in minutes and that's probably good for most games. Lots of games just render their text in Photoshop or similar and draw all the text as textures (rather than rendering text in real time). I can't imagine needing that much control or data from fonts in a game. For some games, sure, this might be useful but I would think that there is a much larger percentage of games that simply don't need that control over the text rendering in-game.
|
|
-
|
|
Re: Why doesn't XNA have word-wrapping?
|
Ok, first of all, most games DO require basic text wrapping. Any game that displays
instructions or any kind of text to the user will require some basic
kind of text wrapping. RPGs have scrolling text boxes for stories, and
virtually every commercial game I've ever seen appears to have some
wrapped text (even if just for debugging or network chat in a game). I hope developers don't waste space with pre-formatted, pre-rendered
textures for displaying pages of text, rather than just storing the
text itself and having some built-in line breaking algorithms for
text-wrapping. That would be annoying to have to re-create a texture to correct a spelling mistake or change the wording.
Now back to XNA's implementation.
MeasureString is not an efficient method of implementing text-wrapping.
1. You can't progressively build lines of text by measuring single characters (which would be the efficient way of doing it), because there's no kerning data made available! 2. So you're forced to add one word at a time, measure the string, see if it fits, add another, measure the whole string all over again (inefficient!!!), see if it fits, etc. 3. Does the pixel-height returned by MeasureString start from the base-line of the text, or does it include all rendered pixels? If it goes from the lowest-hanging glyph to the highest (lowest to highest rendered pixel), then it's useless, because now you have this kind of floating range, but no idea where it's situated relative to the baseline! And if it does start from the baseline and go up, then you have no idea whether pixels hang below the baseline, and hence the documentation would be misleading.
XNA needs a function with a simple line-breaking algorithm for words, based on breaking characters such as spaces, tabs, and hyphens (or a user-defined set of breaking/non-printing characters). Such a function should accept a string of text, a maximum line width, and should return an array with the character indexes that should start each new line. The array's count would give the user the number of lines, which would also be useful. (Either that or we NEED access to kerning pair data).
Great job so far anyway, I just think the importance of basic text formatting has been wildly underestimated. Games without text would be strange, and text is bound to pop up in serious/educational games.
People complained about this stuff two or three years ago when Macromedia/Adobe were forced to deprecate their font metric API in flash. Only recently in Flash 9 has this finally been corrected with a font metric API that actually works.
|
|
-
-
- (20872)
-
premium membership
MVP
-
Posts
12,545
|
Re: Why doesn't XNA have word-wrapping?
|
1. You can't progressively build lines of text by measuring single characters (which would be the efficient way of doing it), because there's no kerning data made available!
Don't you just need the width of words rather than individual characters? I implemented word wrapping by just walking through the spaces and measuring until i pass the width I need and then backing off one word.
3. Does the pixel-height returned by MeasureString start from the base-line of the text, or does it include all rendered pixels? If it goes from the lowest-hanging glyph to the highest (lowest to highest rendered pixel), then it's useless, because now you have this kind of floating range, but no idea where it's situated relative to the baseline! And if it does start from the baseline and go up, then you have no idea whether pixels hang below the baseline, and hence the documentation would be misleading.
It gives you the height of the imaginary block around all characters... i.e. the line height. Basically this is a constant for a particular font/size combination so its sufficient for aligning a single font. Yes this is an issue if you need to mix font sizes and align by baseline... i added a connect feature request for this but it didn't get implemented in 2.0
If those scenarios are important to you then connect is the place for feature requests. Make sure you document the scenarios as it helps them decide.
Play Kissy Poo - a game for 4 year olds on Xbox and windows The ZBuffer News and information for XNA Follow The Zman on twitter, Email me Please read the forum FAQs - Bug/Feature reporting Don't forget to mark good answers and good playtest feedback when you see it!!!
|
|
-
-
- (14748)
-
premium membership
Team XNA
-
Posts
9,342
|
Re: Why doesn't XNA have word-wrapping?
|
JSpiel:Ok, first of all, most games DO require basic text wrapping. Any game that displays
instructions or any kind of text to the user will require some basic
kind of text wrapping. RPGs have scrolling text boxes for stories, and
virtually every commercial game I've ever seen appears to have some
wrapped text. I hope developers don't waste space with pre-formatted, pre-rendered
textures for displaying pages of text, rather than just storing the
text itself and having some built-in line breaking algorithms for
text-wrapping.
There's a big difference between importing all sorts of font information and 'basic text wrapping'. Like I said it is 5 minutes of work to get basic text wrapping. You are asking for all sorts of extra information that really isn't needed. If you just want to wrap text in a rectangle, it's really easy. Use the MeasureString for each word in your string to break down the lines and toss in the '\n' character where you need a line break. 1. You can't progressively build lines of text by measuring single characters (which would be the efficient way of doing it), because there's no kerning data! 2. So your forced to add one character at a time, measure the string, see if it fits, add another, measure the whole string all over again (inefficient!!!), see if it fits, etc.
I'm not sure why you want to go character by character considering most word wrapping is implemented on the word level, not character level. Simply take your full string and use Split to split it based on spaces (and tabs and hyphens if you want to). Then you have a list of words to work with. Then you can measure each word and build a string based on that. 3. Does the pixel-height returned by MeasureString start from the base-line of the text, or does it include all rendered pixels? If it goes from the lowest-hanging glyph to the highest (lowest to highest rendered pixel), then it's useless, because now you have this kind of floating range, but no idea where it's situated relative to the baseline! And if it does start from the baseline and go up, then you have no idea whether pixels hang below the baseline, and hence the documentation woudl be misleading.
It is lowest pixel to highest. Just like it says it gives you the size of the string if you rendered it. XNA needs a function with a simple line-breaking algorithm for words, based on breaking characters such as spaces, tabs, and hyphens (or a user-defined set of breaking/non-printing characters). Such a function should accept a string of text, a maximum line width, and should return an array with the character indexes that should start each new line. The array's count would give the user the number of lines, which would also be useful.
While I do agree this would be a nice feature, it shouldn't take more than 15-30 minutes to write up such a method yourself. Heck, here's one for you: http://xna.multigan.com/pastebin/?page=view&id=1207623835. You don't really need any more information than is present in the framework to do this. Unless you are trying to build a word processor in XNA, nobody is going to care if your lines aren't perfectly spaced based on the font baseline or the highest/lowest pixel.
|
|
-
|
|
Re: Why doesn't XNA have word-wrapping?
|
In the most efficient approach, you'd have to build it character by character, because there's no magic font metric which returns the width of any word.
In the second approach (the one we're forced to use), you can use MeasureString to get the width of a word (with proper kerning since GS2.0, I think), and that will work (I corrected that in point #2 of my post), but you'd still have to re-measure the word to account for hyphenated words, to get the proper kerning on the hyphen).
With the above practical method, you'd need to account for the width of a space (which doesn't render pixels, so again the documentation is misleading for MeasureString). Spaces can have different widths for different fonts, and it's not the same as character spacing.
Again, the documentation is misleading for the height of the font if it says "rendered pixels" but actually means "imaginary box the height of the whole font, or a multiple of that height if there are line-breaks". And if "rendered pixels" is accurate, then like I said, it's a useless value, because a word like "gone" would have the same height as a word like "hum".
|
|
-
-
- (20872)
-
premium membership
MVP
-
Posts
12,545
|
Re: Why doesn't XNA have word-wrapping?
|
JSpiel: Again, the documentation is misleading for the height of the font if it says "rendered pixels" but actually means "imaginary box the height of the whole font, or a multiple of that height if there are line-breaks". And if "rendered pixels" is accurate, then like I said, it's a useless value, because a word like "gone" would have the same height as a word like "hum".
I notified the XNA doc team.
The height you will get back is a fixed height for the font/size. So AAAA is the same height as eeeeee is the same height as yyyyyy
Play Kissy Poo - a game for 4 year olds on Xbox and windows The ZBuffer News and information for XNA Follow The Zman on twitter, Email me Please read the forum FAQs - Bug/Feature reporting Don't forget to mark good answers and good playtest feedback when you see it!!!
|
|
-
|
|
Re: Why doesn't XNA have word-wrapping?
|
Good work. I'm just throwing out ideas here, trying to get down into the details of the problem without sounding like I'm complaining, which isn't easy, lol.
Using String.Split would eradicate hyphens, so hyphenated words are a bit special. I'd recommend splitting the string on tabs/spaces, then handle words with hyphens as a special case (for "anti-war", perhaps consider using MeasureString( "anti-") and MeasureString("war")).
String.Split will also keep multiple spaces intact. So if you split "hi. there.", and it has two spaces, the split will return an empty string in the array between the two spaces, which you can assume means to add an additional space onto the already-implied space between each word.
So, in summary, for XNA line breaking, split the string on spaces/tabs, use MeasureString on each word, figure out how big your spaces should be, then add a word, test the width, add one space (plus an additional space for each empty string until the next word), then add the next word and test the width again. When you go to the next line, forget any previous trailing spaces and just start the next word at the beginning of the line (i.e. current line width = width of first word).
Great discussion, and thanks for the quick replies! I'm out for now, but may check back to see if anything is new.
|
|
-
-
- (13069)
-
Team XNA
-
Posts
8,589
|
Re: Why doesn't XNA have word-wrapping?
|
That works for English, but not for many other languages.
This is one of the situations where it sucks to be Microsoft. It's pretty easy to implement a basic English-only split-on-spaces wordwrapper, and that may be enough for many games, but as Microsoft we get in trouble if we release something that doesn't properly support Japanese, Chinese, etc.
XNA Framework Developer -
blog - homepage
|
|
-
|
|
Re: Why doesn't XNA have word-wrapping?
|
Shawn Hargreaves:That works for English, but not for many other languages.
I would expand that to most languanges that are using a latin alphabet, e.g. Danish, English, French and German :)
We are boki. The rest is known. The not so known part of the rest: It is Björn or Bjoern, but never Bjorn. Twitter ~ Bnoerj ~ SharpSteer ~ SgtConker.com
|
|
-
-
- (13069)
-
Team XNA
-
Posts
8,589
|
Re: Why doesn't XNA have word-wrapping?
|
Not so well for German actually. They tend to have very long compound words, which don't format too nicely if you just split at the end of a word, so to get attractive results you actually have to understand enough about the language to find the gaps between syllables or between each sub-word, in order to know a good place to split. Easy for a human (assuming they speak German) to know, but not so easy to implement in code...
XNA Framework Developer -
blog - homepage
|
|
-
-
- (20872)
-
premium membership
MVP
-
Posts
12,545
|
Re: Why doesn't XNA have word-wrapping?
|
|
|
-
|
|
Re: Why doesn't XNA have word-wrapping?
|
These long compound words are not that common in casual use. Intermission: browsers do this simple break-on-whitespace only and, while some layouts look a bit messed up when long words are encountered, the majority of texts look fine. Advising your translators to add softhypens and zero-width spaces to long words could mitigate the issue even more without increasing the complexity of the breaker code too much :]
Edit: softhyphens and zero-width spaces add to the text rendering and I do not know how the XNA FX SpriteFont messes with them...
We are boki. The rest is known. The not so known part of the rest: It is Björn or Bjoern, but never Bjorn. Twitter ~ Bnoerj ~ SharpSteer ~ SgtConker.com
|
|
-
|
|
Re: Why doesn't XNA have word-wrapping?
|
For people to implement their own word wrapping algorithms, I'd say the biggest omissions of the moment are: - The ability to measure and render substrings without causing memory allocation. For text heavy applications, say trying to render portion of a potentially very long batch of text in a scrollable, resizable area on screen with word wrap, splitting strings all over the place really doesn't quite cut it. The niave implementation would do this every frame, with disasterous consequences; but even if it were only done when the resizable area changes size, that's still a potentially rather large amount of garbage created for no very good reason. - The ability for MeasureString() to return the last character index of a (sub)string which fitted into the given bounds, or say -1 if no characters fitted. Whilst as noted measuring whitespace-delimited substrings will get you a fair way in English, lines without breaks such as "========...." still need to be broken if they don't fit within the available bounds. And calling MeasureString() once per character is a little crazy, especially given the garbage this would generate thanks to the lack of substring measurement. Overloads such as MeasureString(string text, int startIndex, int length, ..., out int lastFitIndex) would solve the above. For applications which are frequently building blocks of text dynamically, even string proves an undesirable atomic type. StringBuilder doesn't quite cut it either: I was amazed at the amount of garbage that class can generate under the right conditions. One solution would be to have MeasureString() and DrawString() take a value-type (stack allocated) adapter instead of string, for example a TextBlock struct: // Generically represents an indexed character array with length, // and does so without causing later garbage collection struct TextBlock { // internally can be either a string, a StringBuilder, // a character array, or a reference to a custom buffer // implementation via a particular interface */ public TextBlock(string s); public TextBlock(string s, int startIndex, int length); public TextBlock(StringBuilder sb); public TextBlock(StringBuilder sb, int startIndex, int length); public TextBlock(...); // (other constructors here)
// get the length of the substring, or the whole string if // there's no substring public int Length { get; }
// get the character at a given index in the substring, // or in the whole string if there's no substring public char this[int index] { get; } } One could then avoid having a billion overloads of DrawString() and MeasureString() by having them take a TextBlock instead: string s = "Hello, world..."; DrawString(new TextBlock(s),...); // print "Hello, world..." DrawString(new TextBlock(s,0,5),...); // print "Hello" Of course one starts to get way beyond what's expected of a framework like XNA out of the box. My own approach has been to rewrite font import and rendering from scratch using approaches rather like the above. Wheel reinvention, moi? At least I have complete control over the way text appears onscreen.
--formerly Genstein
|
|
-
|
|
Re: Why doesn't XNA have word-wrapping?
|
Come to think of it, it's pretty pathetic that SpriteFont doesn't expose basic properies of the font that were included in the spritefont xml file such as the font Size and the CharacterRegions included in the font. Having a way of knowing at run time which characters are included in the font would be really nice, considering all the methods in SpriteFont throw exceptions if the string contains any characters which were not included in the font. I got around that by making a SpriteFontDescription class which stores the SpriteFont reference, its Size, an array of CharacterRegion structs (with start and end characters). I populate such an instance for each SpriteFont immediately after loading it, and work with the SpriteFontDescription rather than with just the SpriteFont. I suppose such construction could be automated with reflection. ------- For WRAPPING, I've created a "WrappedText" class, which takes a SpriteFontDescription, a string of text, and a desired wrapping width. It has properties: Text (string), Lines (string[]), and WrapWidth (float). Initially, and when the text changes, it validates all characters, then procedes to wrap into lines which are stored as a string array. The WrapWidth property can be changed at any time, causing the Lines property to be updated. It also has an ActualWrapWidth property that's stored during the wrapping, which represents the actual maximum width of any given wrapped line (maybe a single word was larger than your wrap width and was forced to overhang; might be useful to know how far it goes). The WrappedText class properly handles '\r', '\r\n', '\n' as line break signals, and it will wrap on spaces, tabs, and hyphens. Finally, it has it's own draw method that accepts the vector where you want it drawn and automatically iterates over and draws all the lines for you (you can set your own SpriteBatch options before the call to draw). The line wrapping algorithm: It goes through your string one character at a time (absolutely necessary to propertly handle a variety of actual and potential word-delimiting and line-breaking characters; string.split would be an incredibly naive way of starting off). As such special characters are encountered, the character buffer is flushed to a special private Word struct that stores the word as string, a WordType enum value (one of: Normal, WhiteSpace, LineBreak), and the floating point width of the word as returned by MeasureString. Processed words are added to a List<Word> until all characters have been processed. Phase two iterates over the processed words, and for each word, it switches on it's WordType for the most efficient action. For WordType.LineBreak, it need not perform any checks, simply finalize the line and move on. For WordType.Normal, it adds its floating point width to the current line, determines whether it fits (including stipulation that first word on a line always fits to prevent infinite wrapping!), it then either moves on to the next word (if it fits) or finalizes the line (if it doesn't fit) and considers itself the first word of the next line. For WordType.WhiteSpace, it adds its floating point width to the current line and performs no further checks, moving on to the next word... this allows trailing spaces to chill safely at the end of a line where they can be trimmed before taking a final actual measurement of the line for the (max) ActualWrapWidth property. And if you wonder why I have a separate WhiteSpace category. Well, we cannot simply assume a space comes after every word, because hyphenated words are broken into two WordType.Normal words so they can be wrapped like any other word, but will properly not have spaces between their first and second part. This structure keeps the algorithm simple, able to propertly and efficiently handle all kinds of words. Alse note that whitespace and tabs are measured only once, and a stored value is passed to the Word constructor. Here's a code snippet of the start of the class; if you want more, let me know: public class WrappedText { /// <summary> /// A WordType is assigned to each processed word for efficiently determining what action to take in the wrapping routine. /// </summary> private enum WordType { Normal = 0, //add word width to current line, test new line width; success, move to next word; failure, start new line WhiteSpace = 1, //add width of space/tab to current line, move to next word (no tests necessary, as white space characters are allowed to overhang lines) LineBreak = 2, //start new line immediately }
/// <summary> /// Stores information about processed words. /// For the purpose of line breaking, a 'Word' is clearly defined as any group of characters which must remain together on the same line. /// </summary> private struct Word { public WordType word_type; //See the WordType enum for more information. public float width; //width of the word, according to SpriteFont.MeasureString( text ).X public string text; //text of the word
public Word( WordType word_type, float width, string text ) { this.word_type = word_type; this.width = width; this.text = text; } }
private SpriteFontDescription font_description; private string original_text; private string[] wrapped_text; private float desired_wrap_width; private float max_actual_wrap_width; And finally, here's the WrapText function that's used by the WrappedText class, you can figure out the rest, I'm sure. /// <summary> /// Wraps text and returns an array of strings representing the lines of text. /// </summary> /// <param name="font">The SpriteFont used for string measurements. Cannot be null.</param> /// <param name="text">The text to wrap. Cannot be null.</param> /// <param name="wrap_width">The maximum allowed line width. If the width is less than the width of the first word of any line, that word will not be wrapped, and some overhang must occur.</param> /// <param name="min_required_display_width">This is the actual calculated width of the longest line. This value may exceed wrap_width if wrap_width is so small that the first word of a line overhangs wrap_width.</param> /// <returns>An array of strings, where each element is a line of text.</returns> public static string[] WrapText( SpriteFont font, string text, float wrap_width, out float max_actual_line_width ) { max_actual_line_width = 0;
//font is required for measuring strings; it is impossible to proceed if it is null if (font == null) throw new Exception( "The 'font' parameter must not be null." );
//if text is null, we can return a single blank line; max_actual_line_width will remain zero. if (text == null) return new string[] {String.Empty};
//Initialize local variables int text_length = text.Length; //number of characters in the text string float line_width = 0f; //display width of the current line (presumably in pixels; whatever units SpriteFont.MeasureString returns) List<Word> processed_words = new List<Word>(); //words are further processed, broken up, and classified to account for extra white space, line breaks, and hyphenations StringBuilder string_builder = new StringBuilder(); List<string> lines = new List<string>();
//Deduce the width of a space/tab character since we may not be able to directly measure it; I've tested that is possible to directly measure it, but who knows what changes wil be made in the future. This way has to work. float underscore_width = font.MeasureString( "_" ).X; float space_width = font.MeasureString( "_ _" ).X - (2 * underscore_width); float tab_width = font.MeasureString( "_\t_" ).X - (2 * underscore_width);
//Construct words one character at a time. //Turns out, you have to do it this way to properly handle new-lines, extra white space, and hyphenated words. //Simply splitting the string would eradicate certain characters, and if you chose to split on just white space, you'd still have to do a character-by-character search for new lines and hyphens in each word anyway! //It's more efficient to just construct and classify words as we go, one character at a time, than it would be to first create an array of might-be-words and then further analyze them for line breaks and hyphens. for (int i = 0; i < text_length; i++) //I understand that the compiler optimizes loops on fixed-length arrays, so no need for a temporary length variable here { char c = text[ i]; if (c == ' ') { FinalizeWord( string_builder, processed_words, font ); processed_words.Add( new Word( WordType.WhiteSpace, space_width, " " ) ); } else if (c == '\t') { FinalizeWord( string_builder, processed_words, font ); processed_words.Add( new Word( WordType.WhiteSpace, tab_width, "\t" ) ); } else if (c == '\r') { if (i < text_length - 1) //i+1 < text_length if (text[i+1] == '\n') //treat "\r\n" sequences as a single line break (MS Windows tradition) i++; //skip over the '\n'
FinalizeWord( string_builder, processed_words, font ); processed_words.Add( new Word( WordType.LineBreak, 0f, null ) ); continue; } else if (c == '\n') { FinalizeWord( string_builder, processed_words, font ); processed_words.Add( new Word( WordType.LineBreak, 0f, null ) ); } else if (c == '-') { string_builder.Append( c ); //add the hyphen before finalizing the word FinalizeWord( string_builder, processed_words, font ); } else { string_builder.Append( c ); } } FinalizeWord( string_builder, processed_words, font ); //end of string reached, dump any word remaining in the buffer //Iterative over all processed words and perform line-breaking int processed_word_count = processed_words.Count; for (int i = 0; i < processed_word_count; i++) { Word current_word = processed_words[ i]; line_width += current_word.width; bool fits = (line_width <= wrap_width) || (line_width == current_word.width && current_word.width > wrap_width); //if line exceeds wrap width, it doesn't fit, unless the first word of the line is greater than the allowed line width
switch (current_word.word_type) { case WordType.Normal: //add word width to current line, test new line width; success, add a space and move to next word; failure, start new line if (fits) string_builder.Append( current_word.text ); else { FinalizeLine( string_builder, lines, font, ref line_width, ref max_actual_line_width ); i--; //retest the current word } break; case WordType.WhiteSpace: //add width of space to current line, move to next word (no tests necessary, as spaces are allowed to overhang) string_builder.Append( current_word.text ); //append the space or tab character to the line (trailing white space will ultimately be trimmed from the line) break; case WordType.LineBreak: //start new line immediately FinalizeLine( string_builder, lines, font, ref line_width, ref max_actual_line_width ); break; } } FinalizeLine( string_builder, lines, font, ref line_width, ref max_actual_line_width );
return lines.ToArray(); }
|
|
|