Property Handling
General Info
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...
FOPropertyMapping
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()
andaddKeyword()
- 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.
The PropertyMakers
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 aProperty
- 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 {{PropertyMaker}}s 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
andborder
); 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
andmargin-top
) - to supply initial values for a property that is applicable but was not specified:
PropertyMaker.findProperty(PropertyList, boolean)
will returnnull
, andPropertyMaker.make(PropertyList)
will be called, returning the initial value.
What about those PropertyLists?
PropertyList}}s 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.)
{{PropertyList}}s are primarily important for:
- resolving inheritance
- triggering shorthand expansion
- computing properties from corresponding properties
They are designed to be a bridge between the FObj}}s and the {{PropertyMaker}}s. 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 PropertyMaker}}s' {{get()
-calls.
Property Inheritance
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
.
Shorthand Expansion
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
FO Source:
<fo:block white-space="pre">...
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
) toorg.apache.fop.fo.Constants
: this will be used in the code that retrieves the rightPropertyMaker
for the new property. For some unimplemented properties this will already be defined. For anEnumProperty
, you may need additional constants (EN_XX
) for the enum values as well. - Register a customized
PropertyMaker
for the property inorg.apache.fop.fo.FOPropertyMapping
. Implement a new one if necessary. CheckFOPropertyMapping
if there is aToBeImplementedProperty.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 {{LayoutManager}}s, {{Area}}s and/or {{Renderer}}s to do something with the newly available property