FOP Implementation Notes
This page is intended for assorted notes documenting the current implementation of FOP (HEAD). The notes are light weight. That is, they just contain what the author has available. No fancy layout, maybe not complete, may be outdated.
This page is becoming too long. I will make subpages.
Property lists
The property lists are held in a tree. FOTreeBuilder
holds a reference to the current property list. Each property list holds a reference to its parent. This keeps the current property list and its ancestors accessible and alive. When the FOTreeBuilder
changes the current property list to the parent property list, reference to the current property list is lost, and it is GC'ed. This happens in
FOTreeBuilder.endElement
.
RetrieveMarker
keeps a reference to its property list. In this way it keeps its property list and those of its ancestors alive, so that they can be searched when a marker's subtree is bound to it.
The property values in a property list are created and they are bound to the FO node at the start of the node, in FObj.processNode
in
FOTreeBuilder.startElement
.
FOTreeBuilder.startElement
:
foNode = fobjMaker.make(currentFObj); propertyList = foNode.createPropertyList(currentPropertyList, foEventHandler); foNode.processNode(localName, locator, attlist, propertyList); foNode.startOfNode(); if (propertyList != null) { currentPropertyList = propertyList; }
FObj.processNode
:
setLocator(locator); pList.addAttributesToList(attlist); pList.setWritingMode(); bind(pList);
FOTreeBuilder.endElement
:
currentFObj.endOfNode(); if (currentPropertyList.getFObj() == currentFObj) { currentPropertyList = currentPropertyList.getParentPropertyList(); }
StaticPropertyList
: explicit property values (explicit) and cached property values (values); both are arrays indexed by the propId.
propertyList = foNode.createPropertyList(currentPropertyList, foEventHandler);
delegates to foEventHandler.getPropertyListMaker().make(this, parent)
. This returns a StaticPropertyList
with fobj
and parentPropertyList
set.
Markers have a special type of property list, MarkerPropertyList
:
explicit property values (explicit
): hashmap.
marker.createPropertyList
returns a MarkerPropertyList
.
A marker keeps a reference to the property lists of its descendants, in member descPLists
, which is a hashmap keyed on the fobj.
Marker.startOfNode
changes the property maker of foEventHandler
to return a MarkerPropertyList
. In Marker.endOfNode
the original property maker is restored again. Thus all FO nodes in the subtree of a marker have a MarkerPropertyList
.
8 January 2005, SimonPepping
XMLObj
A subtree of XMLObj
is a tree of FONodes
, where each node has a reference to its parent, a reference to a corresponding DOM element, and a reference to the DOM document node. Only the top FONode is referred to from its parent as a child. As a consequence, all FONodes except the top one are GC'ed as soon as the reference to the deepest child is released.
8 January 2005, SimonPepping
Top-level structure
The interface of FOP to other XML applications is a SAX DefaultHandler
interface. It is implemented by its class fo.FOTreeBuilder
. Therefore FOP can conveniently be invoked using SAX or TrAX:
Transformer transformer = factory.newTransformer();
or
Transformer transformer = factory.newTransformer(stylesheet); Source src = new StreamSource(sourcefile); // FO or XML file Result res = new SAXResult(foTreeBuilder); transformer.transform(src, res);
An FOTreeBuilder
object can be created by a call to
fop.getDefaultHandler()
, where fop is an object of type apps.Fop
.
In its constructor the FOTreeBuilder
creates an FOEventHandler
object. Then it creates a PropertyListMaker
object and registers it with the FOEventHandler
. Then it sets the default element mappings and additional element mappings of the user. These element mappings map each type of XML element to a node maker, and thus determines the character of the FO node tree and DOM trees for foreign elements that are built.
For each SAX startElement event the FOTreeBuilder
instantiates the appropriate maker for the element, using the registered element mappings. The maker creates a node. Then the attributes of the element are converted to property values of the FO node or attributes of the DOM node.
The FO nodes send start and end events to the FOEventHandler
. At a suitable point the FOEventHandler
starts the layout or structuring process. FOP has three {{FOEventHandler}}types:
AreaTreeHandler
: At the end of thePageSequence
event it starts the
layout process. It sets up a tree of LayoutManagers
. These determine the possible break points of the lines and pages, and create the area tree. When a page is completed, it is handed over to the renderer for rendering.
RTFHandler
andMIFHandler
. These two structural handlers set up the
structure required for an rtf or mif output file.
FOP's process model is that of a SAX parser. The SAX parser or TrAX transformer drives the process. When the SAX events are received, FOP builds its FO tree. When the FO events are received, the
FOEventHandler
manages the layout process. When one or more pages are received, the renderer renders the pages. These actions are all done synchronously and in a single thread: The parser process waits until the tree building, layout and rendering processes return.
Plug-ins
After the invocation of the parse or transform methods the FOP process unfolds itself.
There are a few points in the process where the user can plug in an alternative implementation of a part of FOP: FOEventHandler
, Renderer
,
LayoutManagerMaker
, additional ElementMappings
.
Alternative objects are registered with the user agent of type
FOUserAgent
, using suitable set methods. A renderer object can be obtained from the render.RendererFactory.newInstance(int renderType)
.
A LayoutManagerMaker
and a Renderer
only make sense if one uses
AreaTreeHandler
as the FOEventHandler
.
In principle it is possible to use one's own default ElementMappings
and PropertyListMaker
, but there are no methods to override FOP's defaults.
A LayoutManagerMaker
should satisfy an additional constraint not shown in the LayoutManagerMaker
interface. The LayoutManager
for
PageSequence
must be a subclass of PageSequenceLayoutManager
, so that it implements the methods setAreaTreeHandler
and activateLayout
, cf. AreaTreeHandler.endPageSequence
.
Markers
LM.addAreas()
calls LM.addMarkers(starting, isfirst, islast)
.
PageViewport
holds the marker maps markersFirstStart
,
markersFirstAny
, markersLastStart
, markersLastEnd
, corresponding to the retrieve-positions
first-starting-within-page
,
first-including-carryover
, last-starting-within-page
,
last-ending-within-page
. In addition PageViewport
holds the marker map
LastAny
, from which a marker is retrieved when there is no marker with the requested position last-*-within-page
. Here first and last refer to the position on the page, starting and ending refer to the traits
is-first
and is-last
of the area to which the marker is associated.
The LM obtains the values of isfirst
and islast
from the BP. addMarkers
is called twice per addAreas
call, once at the start with the value starting = true
, before any child areas are returned, and once at the end with the value starting = false
, after all child areas have been returned. When starting == true
,
PageViewport.addMarkers
adds the markers to the maps markers*Start
and markers*Any
; when it is false, the markers are added to the maps
markers*End
and markers*Any
.
Retrieval of markers according to the spec:
first-starting-within-page
: first qualifying area in the containing page which is-first
(FirstStarting
); if null
, then the first qualifying area in the containing page (FirstAny
).
first-including-carryover
: first qualifying area in the containing page (FirstAny
).
last-starting-within-page
: last qualifying area in the containing page which is-first
(LastStarting
); if null
, then the last qualifying area in the containing page (LastAny
).
last-ending-within-page
: last qualifying area in the containing page which is-last
(LastEnding
); if null
, then the last qualifying area in the containing page (LastAny
).
If the containing page does not have a qualifying area, then the last qualifying area of the preceding page is used (LastAny
), etc. until a qualifying area is found within the specified retrieve-boundary
. (My own interpretation; the spec is not clear about preceding pages.)
Scheme of the values of the parameters of an LM.addMarkers
call, and of the addition of markers to the marker maps by
PageViewport.addMarkers
, for an area and a child area. Note that the start and end of an area always fall on a single page:
+----------------------------------------+ First?Starting? | starting is-first is-last | First?Any | | LastStarting? | +-------------------------------+ | // LastAny | | | | | | starting is-first is-last | | First?Starting? | | | | First?Any | | | | LastStarting? | | | | // LastAny | | | | | | | | | | !starting is-first is-last | | LastEnding? | | | | LastAny | +-------------------------------+ | | | | !starting is-first is-last | LastEnding? +----------------------------------------+ LastAny
First?
means: Only add if this is the first marker of its class.
Starting?
and Ending?
means: Only add if the area has the trait
is-first
or is-last
.
20 February 2005, SimonPepping