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.
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
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
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.