Needs Ideas how to implement Text

kungfoomasta

26-04-2008 00:19:38

So first of all, I want to allow the following kind of functionality with Text:

- mix colors on a character by character basis
- mix fonts on a character by character basis
- organize text and properties in a format that is accessible for rendering

Part of the problem here is that I don't know where to draw the line between responsibilities of my "Text" class, and responsibilities of say a "TextBox" class.

A note on how rendering works: I have followed the RBGUI example by having a "Brush" class. This Brush class is used to draw to the screen. The common use of the class is to draw textured rectangles on the screen. This could be used for each character within a body of Text, however it would be more convenient to batch these rectangles together. Also note that we can only batch characters of the same font and color together.

Another important point: This is the first draft, it doesn't have to be the most optimized process in the world. We should focus on having a concrete implementation, and after its all working, we can analyze weaknesses and strengths, and upgrade to more efficient methods.

So here is my current thoughts...

The Text class will manage text (UTFString support via Ogre's classes), including fonts and colors. Rendering of Cursors and text highlighting will be done by Widgets and not the Text. The Text will make this process easier by providing functions to determine cursor positions based on a given position.

Here are some structs or classes I've been thinking about.. although its definately not set in stone.


struct Character
{
codepoint,
Rect bounds
}

// Each Segment will be one rendering batch.
// This means all characters will have the same font/color
struct TextSegment
{
vector<Character>,
fontName,
colour
}

// I wrote this down originally, but I don't know how it fits with TextSegments..
struct TextLine
{
vector<Character>,
startIndex,
endIndex,
Rect bounds
}


I'm also not sure about how to organize the data. I wrote down that the Text class should build a list of TextLines, via the function:

Text::buildLineData(float maxWidth)

Part of my problem is that I'm thinking of text box functionality, (or Text functionality) that is removing characters, adding in characters, etc.

I guess I'm mainly posting to see if anybody has any good ideas, or to show me my approach won't be a good one. Please post any ideas you have. :)

chunky

26-04-2008 01:42:37

Things that immediately jump out at me:
struct Character
{
codepoint,
Rect bounds
}

// Each Segment will be one rendering batch.
// This means all characters will have the same font/color
struct TextSegment
{
vector<Character>,
fontName,
colour
}


That Rect bounds sounds like a recipe for pain. By having each character in a group of characters take absolute control over its own position, and the segment *not* having any kind of position information, it sounds to me like begging for issues with positioning.

My immediate thought is to move the bounds rect into the segment. I presume you've thought out the whole right-to-left thing and have an obvious answer [I know almost nothing about unicode]. One of the other benefits of doing it this way is that it gets around your concern with "removing characters, adding in characters, etc" because for each block of characters with a specific font & color, you only have to worry about a single positioning.


For the next part:
struct TextLine
{
vector<Character>,
startIndex,
endIndex,
Rect bounds
}


An immediate guess would be that instead of doing stuff on a per-line basis, you should do it on a per-block basis. Otherwise you run into dangers of issues with line wrapping. Use something like this
struct TextBlock {
vector<TextSegment>,
Rect bounds
}


Something else that is missing [I think?] is issues of line wrapping in the middle of a segment. While the bounds in characters thing fixes it above, it seems like you could have a vector<Rect> bounds in text segment instead, which also neatly solves the next problem of having multiple lines. Let the segments do the line breaks themselves, and completely skip out on having to implement any kind of "line".


Uh. That's the immediate and mostly incoherent stream of my conciousness. Feel free to point out all the places I've made stupid assumptions based on the fact I only know left-to-right languages made up of latin-1 characters :-)

Gary (-;

PS Added together now:
struct Character {
codepoint
}

struct TextSegment {
vector<Character>,
vector<Rect> bounds,
font,
color
}

struct TextBlock {
vector<TextSegment>,
Rect bounds
}

Zini

26-04-2008 14:38:23

I think you will get into trouble, when mixing different fonts in the same line. To align them properly you would need information about the baseline/ascender/descender (the position of the text segments would need to be adjusted so that all baselines are at the same height). From what I read in the Ogre API, these informations are not available in the Ogre Font class (though Freetype does seem to provide them).

twilight17

26-04-2008 16:00:55

Maybe GPU based fonts? would be cool, although it would be challenging.

kungfoomasta

26-04-2008 19:45:42

AA

@Zini:

I won't have problems, only that the text line's height is determined by the tallest character. All glyphs of a font are the same height, only the width differs. So I can just place everything side by side, with the bottom at the same height. I don't think this aspect will be different.

@chunky:

I will need each character to store its position and size so I can tell the user what character they've picked based on a position. The position/size of each character is only used for reading, the bounds of each character will be determined procedurally.

@twilight17:

That would be cool, but I still have a lot of other milestones to tackle, and Text isn't a strength of mine. :wink:

I have to run, will continue to think about this.

Zini

26-04-2008 20:00:12

Ajj

I think the browser is aligning the font at the baseline, which is the correct way of handling it. But with Ogre fonts you can't do the same, because you have no information about the baseline.If you align it at the descender instead (which seems to be what you are planning to do), you get into the kind of trouble I mentioned. In the above example the bigger j would be moved up by a small amount. This kind of formatting looks really bad.

http://en.wikipedia.org/wiki/Baseline_%28typography%29

kungfoomasta

26-04-2008 22:21:57

This kind of formatting looks really bad.

If it looks bad, don't do it. :lol:

The main purpose for mixing fonts is to support having bold and italicized text in the same body of text.

So lets continue with the assertion that mixing of fonts is supported. If the baseline becomes a huge deal, which it didn't in previous versions of QuickGui that used glyphs with descenders and ascenders, then we can revise the code at that time. :)

twilight17

27-04-2008 04:32:42

Maybe implement a tiny HTML/Javascript engine (kind of like Navi) and let the engine format it (isn't this what Navi does?) This way, text can be written with an HTML editor and stylized there (even notepad would work) then load it into QuickGUI... but this would probably be hard to implement, and window titles would look weird. Idk just an idea :?

Edit:Webkit Engine
Or
Mozilla Layout Engine look good

Zini

27-04-2008 11:22:25

@twilight17: No way! QuickGUI is a pretty compact library and that is a good thing. html rendering is anything but compact and you shouldn't even think about Javascript here.

@kungfoomasta: Actually with bold and italic the problem shouldn't be too visible (as long as it is the same font and the same size). I only mentioned the point anyway, because I didn't want you to waste time on implementing a feature, that would look so ugly in most cases, that no reasonable person would want to use it (do I sound overly pedantic, when I expect a typographical correct layout in a user interface?). But I didn't think of the option of using bold/italic versions. So it's okay with me.

twilight17

27-04-2008 14:25:14

@twilight17: No way! QuickGUI is a pretty compact library and that is a good thing. html rendering is anything but compact and you shouldn't even think about Javascript here.

Ok :roll: it was just an idea :mrgreen:

*quietly runs away from thread*

kungfoomasta

28-04-2008 06:16:10

it was just an idea

:lol:

All ideas are welcome. Although I probably won't take the route of using an external library that handles text for me.

I was thinking about Text a little over the weekend. I started with the quesiton of "what information will I need to render characters?" For each character I will need to know the position, uv coords (which also give size), and color. The uv coords are dependent on the font used. I figured it would be really innefficient if each Character instance had a font name associated with it, so I will try to work around this by using pointers:


struct Character
{
Ogre::UTFString::codepoint cp;
Ogre::FontPtr fp;
Point position;
Ogre::ColourValue* cv;
}


While I could store the actual Ogre::ColourValue object per character, I think that might take up a lot of space in memory, since there could be hundreds of character on the screen, or in a particular layout. I haven't ironed out how I'm going to store the actual ColourValue objects.. probably a std::set of used colours.

Also, I'll probably make TextLine into a class:


class TextLine
{
TextLine(float allottedWidth);
bool addCharacter(Character* c);
void draw();
};


addCharacter will return false if the character could not be added, for example if the character could not fit within the allottedWidth.

jingjie

28-04-2008 06:43:54



I was thinking about Text a little over the weekend. I started with the quesiton of "what information will I need to render characters?" For each character I will need to know the position, uv coords (which also give size), and color. The uv coords are dependent on the font used. I figured it would be really innefficient if each Character instance had a font name associated with it, so I will try to work around this by using pointers:


struct Character
{
Ogre::UTFString::codepoint cp;
Ogre::FontPtr fp;
Point position;
Ogre::ColourValue* cv;
}


While I could store the actual Ogre::ColourValue object per character, I think that might take up a lot of space in memory, since there could be hundreds of character on the screen, or in a particular layout. I haven't ironed out how I'm going to store the actual ColourValue objects.. probably a std::set of used colours.

Also, I'll probably make TextLine into a class:


class TextLine
{
TextLine(float allottedWidth);
bool addCharacter(Character* c);
void draw();
};


addCharacter will return false if the character could not be added, for example if the character could not fit within the allottedWidth.

A Flyweight is an object that minimizes memory occupation by sharing as much data as possible with other similar objects like structure Character.

it is a way to use objects in large numbers when a simple representation would use an unacceptable amount of memory. Often some parts of the object state cannot be shared and it's common to put them in external data structures and pass them to the flyweight objects temporarily when they are used.

http://www.javaworld.com/javaworld/jw-07-2003/jw-0725-designpatterns.html?page=1

C#

using System.Collections;
using System.Collections.Generic;
using System;

class GraphicChar {
char c;
string fontFace;
public GraphicChar(char c, string fontFace) { this.c = c; this.fontFace = fontFace; }
public static void printAtPosition(GraphicChar c, int x, int y) {
Console.WriteLine("Printing '{0}' in '{1}' at position {2}:{3}.", c.c, c.fontFace, x, y);
}
}

class GraphicCharFactory {
Hashtable pool = new Hashtable(); // the Flyweights

public int getNum() { return pool.Count; }

public GraphicChar get(char c, string fontFace) {
GraphicChar gc;
string key = c.ToString() + fontFace;
gc = (GraphicChar)pool[key];
if (gc == null) {
gc = new GraphicChar(c, fontFace);
pool.Add(key, gc);
}
return gc;
}
}

class FlyWeightExample {
public static void Main(string[] args) {
GraphicCharFactory cf = new GraphicCharFactory();

// Compose the text by storing the characters as objects.
List<GraphicChar> text = new List<GraphicChar>();
text.Add(cf.get('H', "Arial")); // 'H' and "Arial" are called intrinsic information
text.Add(cf.get('e', "Arial")); // because it is stored in the object itself.
text.Add(cf.get('l', "Arial"));
text.Add(cf.get('l', "Arial"));
text.Add(cf.get('o', "Times"));

// See how the Flyweight approach is beginning to save space:
Console.WriteLine("CharFactory created only {0} objects for {1} characters.", cf.getNum(), text.Count);

int x=0, y=0;
foreach (GraphicChar c in text) { // Passing position as extrinsic information to the objects,
GraphicChar.printAtPosition(c, x++, y); // as a top-left 'A' is not different from a top-right one.
}
}
}

kungfoomasta

28-04-2008 18:23:54

Thanks for sharing this, very interesting! I think conceptually we share similar ideas. I only want to store multiple sets of data that need to be unique for each character. For duplicate data, ie font name and colour value, I will just use pointers, since they are only 8 bytes, if I remember correctly. (8 hex values for an address space)

Ideally when I go to render the text I will just iterate character by character, and from the character's data I can get the size/texture/uv coords/location/color and render a rectangle to screen. I can also queue these for rendering if the font/color remains the same. (batching the characters) So the TextSegment idea I proposed earlier will not be needed. Also the benefit of having characters store size/position is that I can easily figure out where a text cursor should be placed. (left or right of a character)

Tonight or tomorrow I'll start writing code. :D

kungfoomasta

28-04-2008 22:38:56

Ajj

I think the browser is aligning the font at the baseline, which is the correct way of handling it. But with Ogre fonts you can't do the same, because you have no information about the baseline.If you align it at the descender instead (which seems to be what you are planning to do), you get into the kind of trouble I mentioned. In the above example the bigger j would be moved up by a small amount. This kind of formatting looks really bad.

http://en.wikipedia.org/wiki/Baseline_%28typography%29


I've been thinking about this lately. I'm not sure how to align multi font text together based on the base line. Since the baseline is not at the top of glyph bitmaps, it goes against my normal thinking. For example, if I draw a rectangle at position 0,0 I expect to see the top left corner at 0,0. However with text 0,0 is the left side, baseline height. Rendering a line of text at 0,0 wouldn't yeild the top left corner of the text at 0,0. ... Anybody have experience with Free Type? :?

kungfoomasta

28-04-2008 23:46:21

Awesome I think I figured out how to sync baselines. The Ogre::Font class already calculates the max bearingY (the max would be the baseline) for each font it loads, but its stored in a temporary variable and doesn't give any access. I've requested that data to be stored and publicly available. If not, I can derive the value myself, but its duplicate work..

Basically I just have the TextLine class keep track of the largest font's max bearingY, and I can offset smaller font glyph's down by (largest font's maxY - glyph font's maxY) and everything should be aligned according to the largest font's baseline. :D

jingjie

29-04-2008 07:18:19


A note on how rendering works: I have followed the RBGUI example by having a "Brush" class. This Brush class is used to draw to the screen. The common use of the class is to draw textured rectangles on the screen.

That's good news to supports a "Brush" rendering. :D
about Brush rendering of RBGUI, it support to draw lines , rectangles , text etc if I don't miss it.

The Brush Interface Of RBGUI

virtual void setBlendMode (BrushBlendMode vMode)=0
Sets the blend mode to use. (See BrushBlendMode).
virtual void setFilterMode (BrushFilterMode vMode)=0
Sets the filtering mode to use (See BrushFilterMode).
virtual void drawTiledRectangle (const Mocha::Rectangle &vRectangle)=0
Draws a tiled image within the provided rectangle.
virtual void drawTiledRectangle (const Mocha::Rectangle &vRectangle, const Mocha::Rectangle &vSubRect)=0
Draws a tiled image within the provided rectangle.
virtual void drawColorRectangle (const Mocha::Rectangle &vRect, const Mocha::Color &vTopLeft, const Mocha::Color &vTopRight, const Mocha::Color &vLowerLeft, const Mocha::Color &vLowerRight)=0
Draws a rectangle with a unique color at each corner.
virtual void drawGradient (const Mocha::Rectangle &vRect, GradientType vType, const Mocha::Color &vColor1, const Mocha::Color &vColor2)=0
Draws a gradient rectangle.
virtual void drawRectangle (const Mocha::Rectangle &vRectangle, const Mocha::Rectangle &vUV=Mocha::Rectangle(0.0f, 0.0f, 1.0f, 1.0f), bool vUVRelative=true)=0
Draws a rectangle with the current texture, color settings.
virtual void drawText (Font *vFont, const Mocha::String &vText, const Mocha::Rectangle &vRect, bool vWrap=false, TextAlignmentMode vHorzAlign=TEXTALIGN_LEFT, TextAlignmentMode vVertAlign=TEXTALIGN_TOP)=0
Draws a string of text into a rectangle using a given font.
virtual void drawText (Font *vFont, const LineInfoList &vText, const Mocha::Rectangle &vRect, TextAlignmentMode vHorzAlign=TEXTALIGN_LEFT, TextAlignmentMode vVertAlign=TEXTALIGN_TOP, bool vSelection=false, int vSelectionStart=-1, int vSelectionEnd=-1)=0
Draws a set of pre-processed text lines using a given font.
virtual void drawLine (const Mocha::Vector2 &vPoint1, const Mocha::Vector2 &vPoint2)=0
Draws a line.
virtual void beginLines ()=0
Begin rendering a batch of lines.
virtual void addLine (const Mocha::Vector2 &vPoint1, const Mocha::Vector2 &vPoint2)=0
Adds a line to the batch. Do not call this outside of beginLines/endLines.
virtual void endLines ()=0
Ends a batch of lines and renders them.
virtual void drawLineRectangle (const Mocha::Rectangle &vRectangle)=0
Draws a rectangle made of lines.
virtual void beginTriangles ()=0
Begins rendering a batch of triangles.
virtual void addTriangleVertex (const Mocha::Vector2 &vPoint, const Mocha::Vector2 &vUV, bool vScreenSpace=false)=0
Adds a triangle vertex to the batch.
virtual void endTriangles ()=0
Ends a batch of triangles and renders them.
virtual void beginGlyphs ()=0
Begin rendering a rectangle glyph batch.
virtual void addGlyph (const Mocha::Rectangle &vRect, const Mocha::Rectangle &vUV, bool vUVRelative=true)=0
Adds a batched glyph rectangle. Do not call this outside of beginGlyphs/endGlyphs.
virtual void endGlyphs ()=0
Ends a batch of glyphs and renders them.

I hope the design of brush rendering will be followed the Ogre material framework not to directly use RenderSystem functions.
Would you design brush rendering more flexible functions than RBGUI's one?

kungfoomasta

29-04-2008 07:28:10

Would you design brush rendering more functions than RBGUI's one ?

Definately! I've opened things up so its easier to create custom widgets, so you can make spline widgets, etc. All you have to do is code in the "onDraw" function, and make use of the Brush to draw lines, rects, etc. RBGUI did a good job with this design, its very easy to enhance and use.

kungfoomasta

01-05-2008 17:52:50

I have text writing on the screen with the new render system! Of course its unoptimized and not tested for multiple fonts and colors which it should support, but a lot of the ground work has been laid out! :D

I think it will be finished in a few days. (albeit without Text highlighting and cursor support)

jingjie

02-05-2008 04:42:42

I have text writing on the screen with the new render system! Of course its unoptimized and not tested for multiple fonts and colors which it should support, but a lot of the ground work has been laid out! :D

I think it will be finished in a few days. (albeit without Text highlighting and cursor support)

Wow , You really perform a task with expedition that almost complete the new rendering system. :shock: :D
I believe QuickGUI will be popular UI system in future. :D

kungfoomasta

04-05-2008 07:10:54

Just about done with text now! (The following picture is of a Label with 1 string of Text, with color and font manipulation. )




:D


Thanks to everybody who added suggestions and comments, the Text implementation is looking really good now.

- Zini, check out the j's, I got it working! As long as my patch is applied for Shoggoth we'll be good.

Zini

04-05-2008 12:00:36

Looks great! Originally I wasn't planning to use mixed fonts/styles in my current project, but this certainly has opened up some interesting options.

Maybe you should nudge Sinbad again about the patch in a week or two.

kungfoomasta

06-05-2008 17:50:30

I forgot about serialization of Text. I should have this complete tonight. :)

Klaim

07-05-2008 01:00:49

Hi! Nice work!

Finally, wich technics did you use to display the text? And is it "fast", i mean, is it interesting for really quick changes of text?

kungfoomasta

07-05-2008 04:13:05

I haven't been able to integrate the Text with any widgets except the Label, so I haven't really been able to test it out.

Here is whats in place (I had fun with bold :) ):

I have a Character class, that represents a {code_point, color, font, dimensions }.

The Text class manages a list of Character(s). The Text class has an "allottedWidth" member that specifies the bounds of the text. The bounds are used to create TextLine objects. The TextLine object defines each characters position, and vertically aligns them to match the baseline of the largest character in the line of text.

The TextLines are destroyed and created a lot, so I tried to minimize the number of times they are updated. (use dirty flag, check for need to update before important operations, like drawing)

You reminded me that I don't have any functions for removing a section of Text; I will add to the Text class when I make it support TextBoxes.