Is there anything in the allegro libraries the allows text to be drawn within a specified box? I just spent three weeks developing my own class that uses strings to draw, but I would like to know if there was an easier way.:'(
Is there anything in the allegro libraries the allows text to be drawn within a specified box?
Yes. Take a look at these:
text_length() text_height() textout() textprintf()
There are more functions like that, for working with strings, copying, splitting, finding delimiters, etc. All in the manual...
But is there something like
textout_rect(BITMAP *, FONT *, const char *, int x, int y, int width, int height, color)?
That is what I am looking for.
Not until your write it. You might find it if you search the forums though. This has probably been asked a few times before.
You can achieve the same effect by adjusting the clipping rectangle of the bitmap or by using a subbitmap.
Depending on how you want to use it, one or the other might be more appropriate. If you're writing text to the same area of the bitmap regularly, a subbitmap will be better (create it once and reuse it). If it's just once, then adjusting the clipping rectangle is a little cleaner.
Adjusting the clipping rectangle turns word wrapping on in Allegro? Wow, I didn't know that!
No, but it keeps the text from flowing out of the specified region.
Reading the original post (`allows text to be drawn within a specified box'), I assumed that was what he meant, but taking another look at the topic, I was probably wrong.
One way to do it, using only plain Allegro functions, is by using the d_text_proc function. This can be a bit messy and hackish, but it can be done.
This might work...
void draw_wrapping_text(BITMAP *bmp, FONT *font, const char *str, int x, int y, int w, int h, int color) { DIALOG d = { d_text_proc, x, y, w, h, color, 0/*unused?*/, 0, 0, 0, 0, (void*)str, (void*)font, NULL }; BITMAP *old_bmp = gui_get_screen(); gui_set_screen(bmp); d.proc(MSG_START, &d, 0); d.proc(MSG_DRAW, &d, 0); d.proc(MSG_END, &d, 0); gui_set_screen(old_bmp); }
In addition to the functions that Miran pointed out, you would need a function for finding the length of each character. Unfortunately, the only way you can do this in the Allegro public API is by converting each character into a string (copying it to a buffer that is followed by a terminating \0) and using text_length() on the string. However, the internal API has a char_length() method for the font, which will come in useful as we need to measure each character. To make use if it, add the line #include <allegro/internal/aintern.h> to the file where the multiline text-out code is stored.
It just so happens that I've written some code to do just that, so I thought I'd share it (see attatchment). The code can handle unicode characters (although this is currently untested).
[EDIT]The code can also handle newline characters, and prints the wrapped lines justified.[/EDIT]
Use the function aeGetNumTextLinesWithLengthLimit() to get the number of lines that the rendered text will take up, and aeTextoutMultiline() to actually render the text. The code is well commented (it even uses Doxygen-style comments so it can automatically generate docs if passed through Doxygen).
There are still a few things I could do to improve the code. Currently, the code uses a static buffer for the output of each line. This is determined by AEMULTILINEBUFSIZE (currently 512). Also, the code does not do tabs or hyphens, and the helper-function aeAdvanceToStartOfCurrentLineandGetNextLine() could be split into two separate functions.
Anyway, let me know if this is what you're after.
AE.
Easiest way: Download OpenLayer and turn on word wrapping. It even allows for several text alignments, like centered or justified text. Also line breaks inside text are supported.
Also line breaks inside text are supported.
See edit to my previous post.
AE.
int d_textbox_proc(int msg, DIALOG *d, int c);
A text box object. The dp field points to the text which is to be displayed in the box. If the text is long, there will be a vertical scrollbar on the right hand side of the object which can be used to scroll through the text. The default is to print the text with word wrapping, but if the D_SELECTED flag is set, the text will be printed with character wrapping. The d1 field is used internally to store the number of lines of text, and d2 is used to store how far it has scrolled through the text.
I would use this one. I would create my text box function, which would create a simple Allegro dialog with only one item, d_my_textbox_proc(), which would call d_textbox_proc() with the proper parameters and then return D_CLOSE.
aeAdvanceToStartOfCurrentLineandGetNextLine
I guess your IDE has auto-complete...
I tried the auto-complete thing on all my projects, but it didn't work.
One way to do it, using only plain Allegro functions, is by using the d_text_proc function. This can be a bit messy and hackish, but it can be done.
Yeah I've done this before. You have to use an internal function that d_text_proc uses and hack it a bit and then you can kind of use it to do it. Its all very messy and hacky though, probably only a little bit harder and way cleaner to just write your own word wrapping method. I have one laying around somewhere, maybe I'll come back and paste it sometime if I can find the thing.
[edit]
Ok, heres some code i just ripped from a really really old project. You know what the variables are as well as I do...
1 | int y_i = (state & inputing) ? 2 : 1; |
2 | // Start above line of input |
3 | |
4 | { |
5 | std::list<string>::const_reverse_iterator itr = text.rbegin(); |
6 | |
7 | for(int i = 0; i < scroll && itr != text.rend(); i++) |
8 | itr++; |
9 | |
10 | for(; itr != text.rend(); ++itr, ++y_i) { |
11 | if((y + h) - (text_height(font) + vert_spacing) * y_i < y + horz_spacing) |
12 | break; |
13 | |
14 | text_mode(-1); |
15 | textout(dest, font, |
16 | itr->c_str(), |
17 | x + horz_spacing, |
18 | (y + h) - (text_height(font) + vert_spacing) * y_i, |
19 | makecol(0xff, 0xff, 0xff)); |
20 | } |
21 | } |
My idea wouldn't be very ugly hacking. Inside this function:
...I'd create a DIALOG,
DIALOG *dlg[] = { { d_mytextbox_proc, x, y, width, height, color, bgcolor, -1, D_EXIT, 0, 0, str, NULL, NULL }, { NULL, 0, // etc };
...then I'd run it. d_mytextbox_proc would do the following:
int d_mytextbox_proc(int msg, DIALOG *d, int c) { int hlp; hlp = d_textbox_proc(msg, d, c); if (msg == MSG_DRAW && hlp == D_O_K) return D_CLOSE; return hlp; }
That is, it should in all aspects behave like d_textbox_proc, but as soon as it has been drawn, it closes the dialog, leaving the drawn textbox on the screen. I haven't tested that, but the idea should work. You get your text box sith word wrapped text. You need the background colour parameter, too. If your text doesn't fit in the given box size, you get automatic scrollbars, but you can't use them. If you want usable scrollbars, you simply have to do the whole thing as a dialog.
What I don't have there is a way to draw to bmp instead of screen, which is the Allegro dialog system's standard output. I haven't tested it but I guess one could start with:
What I don't have there is a way to draw to bmp instead of screen, which is the Allegro dialog system's standard output. I haven't tested it but I guess one could start with:
Yeach.
That's what gui_set_screen() and gui_get_screen() were created for.
My idea wouldn't be very ugly hacking. Inside this function:
...I'd create a DIALOG,
...then I'd run it.
Which is basically what my function does, just a lot cleaner and more straight-forward. Create a single dialog object (no need for a two-object array), send it MSG_START to init itself, then MSG_DRAW to draw itself using its given parameters, then MSG_END to deinit itself. Then the function returns.
You'll proably want to be able to scroll the box up and down.
If you're not using Allegro's GUI for anything else than drawing wrapped text, then doing so will bloat your program. Use my solution instead, and keep your EXEs small.
AE.
What I did to accomplish this was to create a class that understood simple markups.
The way it worked was you passed the class your modifiers:
Suggested max width, Suggested max height. Base Font. Color.
Then, you could pass it your display string, with markups, and it would return you a transparent BITMAP with your text.
I think that is your best solution. I coded for center, left, right, bold, underline, and italic markups.
For the bold/underline/italic markups, you will need to create a "Smart" Font library that can correctly upgrade your base font to the appropriate style based on the markups.
Good luck!
Mae
All of your ideas really helped. My Text Wrapping functions weren't as good as I thought they were. I think that Andrei's idea was best and will use it. But I have one question, how does nMaxLength (width of each line?) and npMaxLineLength(number of lines to draw?) work?;D
nMaxLength is the maximum length you are permitting your line to be (although this will be exceded if one line consists of only one character who'se width is greater than nMaxLength).
npMaxLineLength is a running count of the longest length we have so far. This is useful so we can find out the actual length of the longest line that will be printed. Often, this is shorter than nMaxLength, so we may want to shrink the horizontal size of the text-area to take this into account. aeGetNumTextLinesWithLengthLimit() and aeTextoutMultiline() both update the value pointed to by the npMaxLineLength paramater (although in practice, you will usually end up passing in NULL to aeTextoutMultiline()). Note that npMaxLineLength is optional, and you can pass in NULL if you don't want to print your text as compactly as possible.
Below is an example of the usage of the code.
1 | |
2 | char *szText1 = "A short line". |
3 | char *szText2 = "Testing...\nThis is an example of some text that we want to be able to print accross multiple lines. It is a long line of text so it is almost certainly going to be broken up." |
4 | |
5 | unsigned int nNumLines1, nNumLines2; /* Number of lines each string will take up when it will be printed */ |
6 | |
7 | unsigned int nMaxLength = SCREEN_W-150; /* The maximum length (in pixels) we are letting our lines of text be */ |
8 | unsigned int nMaxLineLength = 0; /* A running count of the longest line we have so far. Start off at 0. We could start off at some higher value if we want a minimum text-area width */ |
9 | |
10 | |
11 | nNumLines1 = aeGetNumTextLinesWithLengthLimit(szText1, font, nMaxLength, &nMaxLineLength); |
12 | /* nMaxLineLength is now the length of the longest line in szText1 */ |
13 | |
14 | nNumLines2 aeGetNumTextLinesWithLengthLimit(szText2, font, nMaxLength, &nMaxLineLength); |
15 | /* nMaxLineLength is now the length of the longest line in both szText1 and szText2 */ |
16 | |
17 | /* We have now calculated the longest line length, so in order to print our text as compactly as possible, we are using nMaxLineLength as the right-most boundry instead of nMaxLength. We could of course pass in nMaxLength instead if we want the text to fit nicely in the original text-area */ |
18 | /* We are no longer interested in finding the maximum line-length (we already have it), so pass in NULL to npMaxLineLength */ |
19 | aeTextoutMultiline(screen, szText1, font, 20, 10, 1, -1, nMaxLineLength, NULL); |
20 | aeTextoutMultiline(screen, szText2, font, 20, 10+nNumLines1*text_height(font), 2, -1, nMaxLineLength, NULL); |
21 | |
22 | /* And finally, draw a rectangle around our text */ |
23 | rect(screen, 10-2, 10-2, 10+nMaxLineLength+2, 10+(nNumLines1+nNumLines2)*text_height(font)+2, 3); |
note: this code is untested
The two strings will be printed. Both strings will have the same right-most border when they are justified. If the longest line-length is shorter than the maximum line-length, then the right-most border will be moved to the left to make the string more compact.
AE.
Below is an example of the usage of the code.
What about just implementing a print( font, text, width, alignment ) -function?
What about just implementing a print( font, text, width, alignment ) -function?
You can do that with aeTextoutMultiline(bitmapdest, text, font, x, y, fgcol, bgcol, width, NULL); The only reason why you need all the rest of the code in my example is if you want to find out how long the longest line is going to be and adjust the right-boundry accordingly. My code lacs the ability to set the alignment (the text always justified, except for the last line before each newline or \0 which are left-aligned).
AE.