| Story Library |
Glyphs are the atoms of the engine; they represent individual characters, powerfields and the like. Most glyphs are ultimately created by Story::CGlyphFactory via a Story::CGlyphSink, but glyphs for special purposes (like powerfields) may be created explicitly by the application. You add Glyphs to a story by implementing a command that subclasses Story::CCmdReplaceText and overrides ImportAt(). ImportAt() takes a Story::CGlyphSink argument.
Glyph formatting information is factored out into a separate class, Story::CGlyphAtts. This makes the glyphs themselves very simple objects. Many glyphs are immutable, have no important identity of their own and so can be shared. For example, every letter 'A' in a document can be (and is) represented by a single glyph object, an instance of Story::CGlyphChar which holds information specific to that letter. This is sometimes called the "Flyweight Pattern". The glyph factory manages the sharing. A Story::CLogGlyphState is some glyph together with its logical formatting information. Story::CLayGlyphState adds physical layout information (for example, the glyphs final width). From either glyph state you can find out most information about the glyph. Paragraphs are collections of CLogGlyphStates and lines are collections of CLayGlyphStates.
Tabs get their own subclass Story::CGlyphTab because they need special rendering code to handle leader dots and such. Story::CGlyphEop marks the ends of paragraphs, and also stores the paragraph formatting (so it must be mutable). Story::CGlyphEos marks the end of the story.
Story::CGlyphField is a base class for powerfield glyphs. Powerfields are single glyphs which expand into longer strings of glyphs as part of the composition process. Subclasses generate their text by overriding the Story::CGlyphField::Expand() method and writing to the Story::CGlyphSink provided. The application can add its own kinds of glyph. For example, OLE or metafile objects could be implemented as new glyph classes.
Sometimes you need to know which subclass of CGlyph a given glyph is. For example, rendering is done by a process which iterates through a layout and decides how to draw each glyph based on its class. (Glyphs don't know how to render themselves; it would need too much knowledge of the application's output.) To make this kind of type discovery easier, glyphs support the Visitor Pattern of double-dispatch through Story::CVisitorGlyph. To use this you create a subclass of Story::CVisitorGlyph and override the routines which match the types of glyph you are interested in. You then pass this object to a Story::CLogGlyphState's Accept() method and it will call back to the routine with a properly typed glyph argument (and also a pointer back to the Story::CLogGlyphState).
For example, to count the letter 'e's in a paragraph you might write a class like:
class CCounter : private CVisitorGlyph
{
int m_count;
public:
int CountForParagraph( CParagraph *pPara )
{
m_count = 0;
typedef CParagraph::iterator iterator;
for (iterator i = pPara->begin(); i != pPara->end(); ++i)
i->Accept( this );
return m_count;
}
virtual void VisitChar( CGlyphChar *pGlyph, const CLogGlyphState *i )
{
if (pGlyph->GetChar() == 'e')
++m_count;
}
};
Here we use GetChar(), which only makes sense for glyphs which can represent a single character so it is not provided by the CGlyph base class. Inside VisitChar the glyph has the exact subclass type so we can use the method without a cast. Other types of glyph will be effectively ignored.
Another way is to use MFC's DYNAMIC_DOWNCAST; this can be more convenient if you are interested in only one subclass. (The speed should then be about the same.) However, you need either less often than you might think. Much can be done by using the base interface declared in CGlyph, or by thinking about the problem differently. For example, the above could also be written:
int CountParagraph( CParagraph *pPara )
{
<ul>
int count = 0;
const CGlyph *pE = CGlyphFactory::GetInstance()->GetChar( 'e' );
typedef CParagraph::iterator iterator;
for (iterator i = pPara->begin(); i != pPara->end(); ++i)
if (i->GetGlyph() == pE)
++count;
return count;
}
This avoids virtual functions in the inner loop. It relies on the fact that all occurances of the letter 'e' are represented by a single, shared glyph object. If you want to search for immutable objects as well, you should use "if (*i->GetGlyph() == *pE)" for the comparison.
Story::CGlyphString is a string of glyphs. A variation on the above could use it to search for arbitrary strings.
When you add a new glyph class, there are several areas you need to review and/or update: