FOP's property subsystem is a component that is easily misunderstood. org.apache.fop.fo.properties.Property and its derived classes do not exactly correspond to FO properties, but rather, different types of FO properties. The mapping of a FO property name to a Property subclass is defined in org.apache.fop.fo.FOPropertyMapping, a class that seems a bit intimidating at first glance...
While the source file seems immense, all the code in there is only executed once for multiple FOP runs within the same virtual machine. What it gets to contain after this code has been executed, is a mapping of FO property names to one of the org.apache.fop.fo.properties.PropertyMaker subclasses. Those PropertyMakers have been customized to fit the behavior as mandated by or prescribed in the XSL-FO Recommendation for a given property, such as:
which enums and/or value keywords are allowed as values: addEnum() and addKeyword()
which shorthands can set the property: addShorthand()
for shorthands, which custom parser to use: setDatatypeParser()
which corresponding properties can set the property: setCorresponding()
whether a property is inherited or not: setInherited()
the default value: setDefault()
The customized PropertyMakers in FOPropertyMapping are used when the tree of FOs is built from the XML events coming in from the SAX Parser. In org.apache.fop.fo.FOTreeBuilder.MainFOHandler.startElement() the current node's list of attributes is given to an instance of org.apache.fop.fo.PropertyList, which uses the attribute names and FOPropertyMapping to get to the right PropertyMakers to convert each of the attribute values to the appropriate FOP-internal Property type.
There are two distinct points in the overall process where PropertyMakers are used:
when an explicitly specified attribute value is converted to a Property
when binding a PropertyList to a FO
Conversion of explicitly specified FO properties
This is initiated in PropertyList.convertAttributeToProperty(), and in the most basic case comes down to a call to PropertyMaker.make(PropertyList,String,FObj). The default implementation of this method provides for the following:
resolving explicit inheritance (specified value of "inherit")
substitution of value keywords (checkValueKeywords())
checking valid enum values (checkEnumValues())
parsing the expression (PropertyParser.parse()), if the above did not yet yield a Property
converting the resulting Property, if any, to the right type (convertProperty())
This method can be overridden by subclasses, for example to cater for custom property parsing if the generic org.apache.fop.fo.expr.PropertyParser does not suffice (see org.apache.fop.fo.properties.FontFamilyProperty.Maker, which bypasses the generic space-based expression parsing).
Binding a FOP PropertyList to an FObj
This is done in org.apache.fop.fo.FObj.bind(), and results in an FObj being tied to the set of applicable properties, so that the PropertyList (which reserves space for all possible properties) is no longer needed. In case of the explicitly specified properties, the PropertyMaker's role here is limited to finding the Property on the PropertyList and simply returning it unaltered. What the PropertyMakers are mostly used for in this part of the process is:
to get to a native XSL-FO property that was explicitly set by a CSS shorthand (e.g. border-before-width and border); ideally, only the native XSL-FO properties are bound to the FOs
to get to the relative property from an absolute one (e.g. space-before and margin-top)
to supply initial values for a property that is applicable but was not specified: PropertyMaker.findProperty(PropertyList, boolean) will return null, and PropertyMaker.make(PropertyList) will be called, returning the initial value.
What about those PropertyLists?
PropertyLists are large, but relatively short-lived in most cases. The PropertyList for the first fo:block in an fo:flow is released before the one for its sibling fo:block is created; only a reference to the parent is maintained (see FOTreeBuilder.MainFOHandler.endElement(): the currentPropertyList is set back to that of the parent FO, so the one for the processed node goes out of scope). The only notable exception is the PropertyList-ancestry of fo:retrieve-markers: that is preserved until layout, to be able to correctly deal with markers and property inheritance. The MarkerPropertyList subclass used for the attributes of descendants of an fo:marker themselves is actually a hybrid of PropertyList and SAX Attributes. It only stores simple name-value mappings and implements the Attributes interface so it can itself be used to create a full-fledged StaticPropertyList later on, when the marker is retrieved. (Note that a simple reference to the original Attributes does not suffice here, since the SAX parser is under no obligation to keep it available after the parent element's endElement() has passed, which is long before the point where they would be needed.)
PropertyLists are primarily important for:
- resolving inheritance
- triggering shorthand expansion
- computing properties from corresponding properties
They are designed to be a bridge between the FObjs and the PropertyMakers. The PropertyList is constructed separately from the FO, and first filled with the explicitly specified properties. In the bind() method, each org.apache.fop.fo.FObj subclass issues get(PR_XX)-requests for all applicable (and implemented) properties to the PropertyList, which in turn can trigger additional PropertyMakers' get()-calls.
Inheritance is handled almost entirely by the PropertyList, and comes down to having the PropertyMaker check whether the property is inherited, and if so, have it forward the get(PR_XX)-call to the parentPropertyList.
If there is no explicitly specified value available and the PropertyMaker was customized to allow the property to be set by a shorthand, the request is forwarded via the shorthand's PropertyMaker to the defined ShorthandParser implementation. As long as the FO has not requested the base properties, the shorthand is stored as a single Property instance whose associated PropertyMaker instance contains a ShorthandParser that is designed to serve get(PR_XX)-requests for the base properties. Once the bind() method has been executed, most of the shorthands themselves will only be referenced by (and thus, later on disappear together with) the PropertyList.
Example: resolution of a specified shorthand property
Conversion of the explicitly specified shorthand:
in PropertyList.convertAttributeToProperty(): ... propertyMaker = findMaker(PR_WHITE_SPACE); --> propertyMaker is an EnumProperty.Maker, equipped with a custom WhiteSpaceShorthandParser (see FOPropertyMapping.createShorthandProperties()) ... property = propertyMaker.make(this, "pre", parentFO); --> property is the EnumProperty(EN_PRE, "PRE")
Expansion of the explicitly specified shorthand:
in Block.bind(): ... linefeedTreatment = pList.get(PR_LINEFEED_TREATMENT).getEnum(); in PropertyList.get(): propertyMaker = findMaker(PR_LINEFEED_TREATMENT); --> propertyMaker is an EnumProperty.Maker, that was customized to be possibly set by the PR_WHITE_SPACE shorthand (see FOPropertyMapping.createBlockAndLineProperties()) ... property = propertyMaker.get(0, propertyList, true, true); in PropertyMaker.get(): property = findProperty(propertyList, true); in PropertyMaker.findProperty(): ... property = getShorthand(propertyList); in PropertyMaker.getShorthand(): ... prop = propertyList.getExplicit(PR_WHITE_SPACE); ... parser = shorthand.datatypeParser --> parser is a WhiteSpaceShorthandParser ... property = parser.getValueForProperty(PR_LINEFEED_TREATMENT, prop, this, propertyList); in WhiteSpaceShorthandParser.getValueForProperty(): ... return EnumProperty.getInstance(EN_PRESERVE, "PRESERVE");
Corresponding Property Computation
This is performed analogously, by involving another PropertyMaker that was associated to the base property's Maker in FOPropertyMapping.
Implementing an additional FO property in FOP without implementing a Property?
It's possible, if the property's datatype is already supported, which will be the case for most types of property, like NumberProperty, StringProperty or EnumProperty.
Add a symbolic constant (PR_XX) to org.apache.fop.fo.Constants: this will be used in the code that retrieves the right PropertyMaker for the new property. For some unimplemented properties this will already be defined. For an EnumProperty, you may need additional constants (EN_XX) for the enum values as well.
Register a customized PropertyMaker for the property in org.apache.fop.fo.FOPropertyMapping. Implement a new one if necessary. Check FOPropertyMapping if there is a ToBeImplementedProperty.Maker already in place.
Add an instance member to the applicable FOs, and bind it to the PropertyList's Property which becomes available through the above two steps. Accessors on the FOs for the new property will be needed too.
Add the necessary code to the corresponding LayoutManagers, Areas and/or Renderers to do something with the newly available property