Layout Code
Here is the general description of how AbiWord supports layoutobjects like tables, footnotes, text frames, positioned images and to allow text to flow around images and other embedded objects. Note that this text originates from around 2001 and almost certainly contains inaccuracies. These will be corrected as they are found and as time allows.
Basic layout on pages
To start with I'll recap how text is layed out on fp_Page.
Fixed size header | |
-------Line 1------ | ----------Line 1---------- |
-------Line 2------ | ----------Line 2---------- |
-------Line 3------ | ----------Line 3---------- |
-------Line 4------ | ----------Line 4---------- |
-------Column 1------ | ----------Column2---------- |
-------Line 5------ | ----------Line 5---------- |
-------Line 6------ | ----------Line 6---------- |
-------Line 7------ | ----------Line 7---------- |
-------Line 8------ | ----------Line 8---------- |
-------Line 1------ | ----------Line 1---------- |
-------Line 2------ | ----------Line 2---------- |
-------Line 3------ | ----------Line 3---------- |
-------Line 4------ | ----------Line 4---------- |
-------Column 3------ | ----------Column4---------- |
-------Line 5------ | ----------Line 5---------- |
-------Line 6------ | ----------Line 6---------- |
-------Line 7------ | ----------Line 7---------- |
-------Line 8------ | ----------Line 8---------- |
Fixed size footer |
Images are embedded in lines. If an image is large it expands the height of a line.
The lines are contained as a vector in the class fp_Column which derives from the base class fp_Container.
Here is what we like to layout on a page.
----------Line 5---------- -------Line 6------ ----------Line 6---------- -------Line 7------ ----------Line 7---------- -------Line 8------ ----------Line 8---------- Table -------Line 1------ ----------Line 1---------- -------Line 2------ ----------Line 2---------- -------Line 3------ ----------Line 3---------- -------Line 4------ ----------Line 4---------- -------Column 3------ ----------Column4---------- -------Line 5------ ----------Line 5---------- -------Line 6------ ----------Line 6---------- ----------Line 7---------- Footnote ----------Line 8---------- Fixed size footer
Fixed size header | |
-------Line 1------ ----------Line 1---------- | |
-------Line 2------ ----------Line 2---------- | |
-------Line 3------ ----------Line 3---------- | |
----------Line 4---------- Embeded Container |
OK so under the new scheme we continue to have a fp_Page class whose job it is to layout out fp_Columns on a page and place headers/footers in the margins.
Now however fp_Columns can contain containers other than fp_Lines. fp_Columns can contain lines, other containers or footnote containers.
In order to be able to continue to use a vector of void * pointers as the content of the column, this means that fp_line should become a subclass of a general container class. If we have getType() method in this class we can dynamically determine what sort of container is returned from a
fp_ContainerObject * pCO = (fp_ContainerObject *) m_pLines.getNthItem(i);
assignment.
At present we have the following fp_* class heiracy.
fp_Run --> lots of run subclasses. fp_Line --> no Line subclasses fp_Container ---> -----> fp_Column
-----> fp_HdrFtrContainer -----> fp_ShadowContainer -----> fp_EndNoteContainer
For the next generation I propose this: fp_ContainerObject---- --> fp_Run ---> Lots of subclasses
--> fp_Container -->
--> fp_TableContainer --> fp_CellContainer
--> fp_VerticalContainer
--> fp_Column- ->fp_ShadowColumn ->fp_PositionedColumn -->fp_HdrFtrContainer -->fp_EndNoteContainer -->fp_FootnoteContainer --> fp_RowContainer--> -----> fp_Line
I've thought through a number of suggestions to make fp_Line derive from fp_RowContainer. They make sense if fp_Run also derives from fp_ContainerObject. Since fp_Run has all the required methods defined this is no problem at all.
The big win from this is to be able to "pack" arbitary collections of containers, left,right, center and full justfied by using a generalized fb_LineBreaker class.
Given this I thought we should have a fp_verticalContainer that enables the "breakSection" method in fl_DocSectionLayout to layout vertical containers. fp_Column now dervices this too since columns are vertical collections of containers.
In answer to Tomas I think we want fp_Column to derive from fp_Container so we can cast a getContainer() to fp_Column. The getContainer method of fp_Column should always return NULL though.
Further thought revealed that restricting the tableContainer to a vertical collection of rows is much too restrictive.
So tables need to have their own layout algorithmn to size and position their containers. Such algorithms already exist. We could easily just grab the methods in GtkTable for a simple 4-pass algorithm.
Then in addition to these methods available to all fp_ContainerObjects.
getContainerType(void) draw( drawArgs) clearScreen() getX(void) getY(void) getWidth(void) getHeight(void) getXLayoutUnits(void) getYLayoutUnits(void) getWidthLayoutUnits(void) getHeightUnits(void) getNext(void) getPrev(void) getSectionLayout(void) setX(UT_sint32 ) setY(UT_sint32 ) setWidth(UT_uint32) setHeight(UT_uint32 ) setXLayoutUnits(UT_uint32 ) setYLayoutUnits(UT_uint32 ) setWidthLayoutUnits(UT_uint32) setHeightUnits(UT_uint32) setNext(fp_ContainerObject *) setPrev(fp_ContainerObject *) isVBreakable(void) isHBreakable(void) wantVBreakAt(UT_sint32 ) wantHBreakAt(UT_sint32 ) fp_ContainerObject * VBreakAt(UT_sint32) fp_ContainerObject * HBreakAt(UT_sint32)
The fp_Container class have:
getColumn(void); getContainer(void); UT_Vector * (fp_ContainerObject *) getContainerChildren();
The getColumn would work by just recursively calling getColumn until a column was obtained. The getContainer() would just return the holding container.
The getContainerChilder(0 method returns a pointer to vector of pointers to the children of the container. This is a useful generic way of obtaining all the children of a given container.
fp_Column classes have:
getPage() getColumnLeader() getFollower()
The advantage of this aproach is that it makes it possible to nest containers inside each other while preserving the distinction of fp_Columns which get layed out right on a page.
Ome further thought on this. Once we get allow positioned objects we have to start worrying about itterative layout on pages in conjunction with the new section breaker class. When we start to layout columns on the page we don't know whether there will be a positioned object on the page or container. If a positioned object is found, it should be placed on the current page and the layed out around it. This means breaking the containers up into rectangular objects that get positioned
Logical Layout Fl_* classes
To make the Tables/footnotes/positioned objects work we need additional fl_Layout classes as well as the fp_Container classes. The idea of course is to rewrite as little as possible of our current code.
The fl_Layout classes contain the logical assembly of text and images in the document.
Currently our fl_Layout classes consist of:
fl_Layout - generic Base class. fl_DocLayout - overall class for the entire document - contains all the sectionlayouts and pages. fl_SectionLayout - generic SectionLayout for collecting groups of
fl_BlockLayouts.
fl_DocSectionLayout - Collection of fl_BlockLayout's within a given
section of the document. It positions lines in containers.
fl_HdrFtrSectionLayout - Collection of fl_BlockLayout's that make up an
invisible HdrFtr for a given DocSectionLayout
fl_ShadowLayout Collection of fl_BlockLayout's that are copies of
the master HdrFtr that are actually visible and drawn to the screen.
fl_BlockLayout Container of text and image runs that make up the
document.
fl_DocListener Interface between the pieceTable and the Layout
classes
The primary job of the layout classes is to assemble smaller pieces into larger collections. So fl_BlockLayout assembles runs into lines, fl_DocSectionLayout assembles lines into columns, fl_HdrFtrSectionLayout assembles lines into a header/footer etc.
The current class heiracy is:
fl_Layout |
|--> fl_DocLayout |--> fl_BlockLayout |--> fl_SectionLayout |--> fl_DocSectionLayout |--> fl_HdrFtrSectionLayout |--> fl_EndNoteSectionLayout
Within fl_DocSectionLayout is a method called breakSection() and some associated methods. I think these should be liberated and placed into seperate classes called fb_BreakSection in analagy with fb_LineBreaker.
The linked list of classes was:
m_pFirstSection->DocSec<=>DocSec<=>DocSec<=>EndNoteSec<=>HdrFtrSec=>NULL
Within each section is a linked list of fl_BlockLayouts.
m_pFirstBlockLayout<=>Block<=>Block<=>Block<=>NULL.
Each block has is own linked list runs and lines.
So for the next generation code we add cells, tables, footnotes and endnotes. These must be contained by fl_DocSectionLayout and they also contain fl_BlockLayouts.
So we need new classes derived from fl_SectionLayout to contain the fl_BlockLayouts for these new containers.
These are fl_TableSectionLayout, fl_CellSectionLayout, fl_FootnoteSectionLayout. The class heiracy I propose is:
fl_Layout |
|--> fl_DocLayout |--> fl_ContainerLayout |--> fl_BlockLayout |--> fl_SectionLayout |--> fl_HdrFtrSectionLayout |-->fl_BreakSectionLayout |--> fl_DocSectionLayout |--> fl_EndNoteSectionLayout |--> fl_TableSectionLayout |--> fl_PositionedSectionLayout |--> fl_CellSectionLayout |--> fl_FootnoteSectionLayout
The idea of putting fl_BlockLayout and fl_SectionLayout under a new base class is that methods like the following can be applied to any fl_ContainerLayout
purgeLayout(void); collapse(void); isCollapsed(void); setNeedsReformat(void); needsReformat(void); setNeedsRedraw(void); markAllRunsDirty(void); format(void); getLayoutType(void); clearScreen(void); getNext(void); setNext(fl_ContainerLayout *); updateBackgroundColor(void);
Maybe others too.
This allows us to easily generalize the simple linked list of fl_BlockLayout classes which used to be all a DocSection could hold to something like:
m_pFirstBlockLayout<=>fl_Block<=>fl_Table<=>fl_Block<=>fl_Position<=> fl_Table<=>fl_Footnote<fl_Block<=>fl_EndNote=>NULL
Then each Table holds the following linked list m_pFirstCell<=>fl_Cell<=>fl_Cell<=>fl_Cell=>NULL
Each Cell holds:
m_pFirstBlock=>fl_Block<=>fl_Table<=>fl_Block=>NULL
each.
By placing all these classes under generic heiracy we can treat each layout as a type of container and apply the same methods to each.
So a collapse method on a DocSectionLayout is translated down through all the layout classes under it's linked list.
Now the other thing we do is to generalize fb_BreakSection to assemble not just lines into columns but any container found in the DocSection into a column. This code can also be made generic so that for example a cell can assemble text into itself and we allow containers to be broken.
fl_TableSectionLayout of course needs a method to assemble cells into a table.
What of fl_DocListener? Well we'll need new piecetable struxes to hold the properties of the new containers Table,cell,footnote, positionedObject. If a property of any of these containers change, the contents of the container are cleared and then recalculate, the same way we do things for DocSectionLayouts now.
OK folks, opinions on this? Once again I think it will not be too hard to refactor the code to allow these generalizations.
PieceTable Changes
To build tables/footnotes/positioned objects etc we also need to make some changes to the piecetable. My proposal is to add new frag_stux's
In particular:
PTX_SectionTable, PTX_SectionTableEnd,PTX_SectionCell, PTX_SectionFootnote,PTX_SectionFootnoteEnd,PTX_SectionPositioned, PTX_SectionPositionedEnd
These derive directly from the pf_Frag_Section class. The only difference is the type.
We need to the PTX_SectionTableEnd, PTX_SectionFootnoteEnd and PTX_SectionPositionedEnd
struxes to close off the table, footnote and positioned object definitions.
The properties associated with the table,cell,footnote and positionedobject struxes define the type of structure.
Editting operations. All normal editting operations will be passed throough to the fl_BlockLayouts as before. I see no need to change this.
Operations on cell/table/footnote/positionedobject struxes will be transmitted via fl_DocListener to the SectionLayout classes associated with these piecetable items. The associated layouts will be collapsed and redrawn with the new properties, the same way we do things for fl_DocSectionLayout now.
The fb_SectionBreaker classes will take care of breaking these objects so they fit into columns on a page.
Migration to the new Layout Engine
Ok the final email on these new classes is a strategy for migrating our code base to use the new layout engine. Here is my proposal.
When we're happy that we've reached consesus the way we want to go,
Hub creates new CVS modules which are initially just a copy of the current modules.
We implement the fp_Container class heiracy and make the current code work for the new heiracy with fp_Lines as fp_ContainerObjects etc. We generalize fb_LineBreaker to break any fp_ContainerObjects into horizontally laid out lines.
Once the new class heiracy works with existing documents we go to stage 3.
We implement the new Layout class heiracy with fl_BLockLayout and fl_SectionLayout as subclasses of fl_ContainerLayout. The fl_ContainerLayout abstract base class is fully defined. We create the new fb_SectionBreaker class to layout any collection of objects vertically.
Once we've made this new heiracy work with our existing code we go to stage 4.
Put the new struxes into the piecetable and investigate the properties we need them to define. I suggest we use RTF as a model here. RTF version 1.6 is basically a blueprint on how MS Word 2000 works.
Now the fun really starts. We implement the new fp_Container classes, then the fl_Section classes and connect them to the piecetable via fl_Doclistener. Once this is done we define the new tags needed for our AbiWord_2 importer/exporter. This is actually very easy. We just invent tag names for our new struxes and include them in the "case" statements.
Once we can import/export tables/footnotes/endnotes to *.abw we begin work on the Table/footnote/endnote/positioned object UI.
IMHO MS Word provides a base from which to work here. In my opinion this is still rather cumbersome so I'd love to get some help for how to do a better Table UI.
We will definitely need to rework how to do selections and how to keep the cursor inside a container. The latter can be done with a generalization of getEdittableBounds().
In the case of the former may not want to draw a selection over footnotes/endnotes and positioned object's.
For deletions that cross cell boundaries we will want to pop up a little window to ask above deleting cells/rows/columns etc the way Gnumeric/excell/MS Word does now. It should not be hard to trigger this. Just put a hook into in to detect attempts to delete Table or cell struxes.
This strategy allows us several checkpoints to make sure we're on the right track to a much more sophisticated layout engine.