| Story Library |
For its native file format, the text engine mostly uses MFC's serialization mechanism with all the benefits and drawbacks that entails.
You should call Story::CRoot::Serialize() directly on an instance of the root you have created yourself. This will store all the stories and their content, as well as the style sheet and other settings. It is best not to let the serialization code create the root by using operator>>() on a pointer, because that makes it harder to control the concrete classes used.
Serializing a story does not serialize its layouts. This makes it easier to use layouts for temporary views. Nor will it serialize the style sheet. The story content uses Story::CImportFormatNative and Story::CExportFormatNative, which are the classes used for import and export. Serializing a layout will serialize its story.
Generally the text engine will look after changes to its file format. However, because MFC stores the names of classes, if you subclass engine data structures you may create a file incompatibility with versions that don't subclass. You can deal with this, but it will take care. The details are beyond the scope of this document.
The MFC file format tracks references to shared objects, which mostly makes life simpler. The engine adds run-length encoding for glyph attributes and special-cases ASCII glyphs so they only take 1 byte each. It's reasonably compact.
There are two problems with MFC's serialization that you should be aware of. Firstly, it only stores one schema number per object, which is the schema of the most-derived class. This makes it hard for base classes to manage their versioning independantly, because they don't have their own schema numbers. The engine mostly ignores MFC's schemas and each implements its own schema instead, using Story::SerializeSchema().
Secondly, the CArchive keeps a reference to shared objects, but this reference is not accessible to the engine's reference counting code. This means Story::CPtr<> tends to delete objects while MFC is still using them. To avoid this serialization must be wrapped with an instance of Story::CArchiveOwnerSession. This effectively increments the reference counts temporarily. The engine will look after this as far as it can.
MFC doesn't support namespaces, bool or std::auto_ptr. It's inconsistent about whether to use operator>>() or serialize(). The engine keeps some archiving utilities in ArExt.h to help with these problems.
The engine root holds an object of type Story::CImporter which does most of the general work for importing logical story content. The data to be imported can come from the files, or from any COleDataObject including the Windows clipboard and OLE drag and drop. CImporter has a collection of import format objects which handle specific formats. It uses these to support much of general import API. For example, the result of CImporter:: GetFileFilters() is string containing all the known file extensions, which you can pass directly to CFileDialog.
The importer uses the Prototype pattern. That is, each separate import job eventually gets its own import format object, which the importer creates by cloning from its collection. Most formats get their own subclasses, but you can implement minor variations by using parameters in instance variables. For example, Story::CImportFormatAscii uses this to decide whether line ends should be mapped to paragraph ends.
Microsoft's Rich Text Format gets special attention. The Story::CImportFormatRtf class only contains a small portion of the code. The rest is placed in various classes in the Story::RtfImport namespace. Each keyword and destination is represented by a separate object with many needing their own subclasses. You can implement new keywords by writing your own subclasses and adding them to the format.
Export is similar to import. The engine does not use delayed rendering.
To add a new import format, you should write a subclass of Story::CImportFormat and add it to the importer. The key functions to override are SetFile(), which contains the input data for both file and Ole sources, and ImportAt(), which writes the new content to a CGlyphSink.
To add a new export format, you should write a subclass of Story::CExportFormat and add it to the exporter. The key functions are SetFile() as before, and ExportRange(). ExportRange() comes in two versions: the first describes the range to be exported in logical terms (ie Story::CLogPos) and the second in layout (ie Story::CLayPos). You can override either, but the logical one is preferred because the layout version requires the layout to be composed.
See also Import/Export Classes.