Chapter 9. Properties

Table of Contents

1. The basic setup of the property subsystem
1.1. Property values
1.2. The property list of an FO node
1.3. Property makers
1.4. Shorthand properties
1.5. Corresponding properties
1.6. Mapping between property names, IDs and makers
1.7. Storing the property values based on their PropID
2. Creating a property value
2.1. General
2.2. Example of a compound property
2.3. Enumerated property values
2.4. Example of a property with keywords
2.5. Parsing a property with an absolute value
3. Retrieving a property value
3.1. Overview
3.2. Detailed overview
3.3. Examples: Retrieving border and padding values
4. Percent-based and mixed property values
4.1. Overview
4.2. Parsing a mixed property value
4.3. Resolving a mixed property value

1. The basic setup of the property subsystem

1.1. Property values

The FO nodes in the FO tree contain property values as specified by the user. Each property value is represented by a object of type org.apache.fop.fo.Property, or of a subtype thereof.

There are various types of property values: CharacterProperty, ColorTypeProperty, CondLengthProperty, EnumProperty, KeepProperty, LengthPairProperty, LengthProperty, LengthRangeProperty, ListProperty, NCnameProperty, NumberProperty, NumericProperty, RelativeNumericProperty, StringProperty. The type ToBeImplementedProperty is used for properties that are not yet implemented. Some of these types have subtypes: AutoLength, FixedLength, PercentLength, TableColLength are subclasses of LengthProperty; SpaceProperty is a subclass of LengthRangeProperty. Each of these types is a subtype of org.apache.fop.fo.Property.

Property values may implement one or more of the interfaces defined in the package org.apache.fop.datatypes: Numeric, Length, ColorType

Some properties actually represent a set of properties, such as a minimum, an optimum and a maximum. These are represented by a property value which implements the CompoundDatatype interface. They contain a property value for each member property.

1.2. The property list of an FO node

Property values are held by the FO node corresponding to the fo element on which they are specified by the user. FO nodes contain a property list of type PropertyList, which extends HashMap.

The property types are known by the property name as used on the FO element, e.g. font-size. For efficiency of implementation, each type of property type is also known by an integer, the propID. The propIDs are defined in the interface org.apache.fop.fo.Constants, which gives them a symbolic name of the form PR_ + property name in capitals and spaces replaced by underscores, e.g. PR_FONT_SIZE. Wherever possible, the code uses the propIDs instead of the property names.

When an FO requests a property, it does so by propId. The request is eventually answered by PropertyList.getExplicitBaseProp, but before it can do so, it has to retrieve the property name from FOPropertyMapping.getPropertyName. A particular inefficiency as a consequence is found in FopPropValFunction.java and some other classes in the fo.expr package:

return pInfo.getPropertyList().get(FOPropertyMapping.getPropertyId(propName))

Here propName -> propId mapping is done, which later is reverted again.

  [1] org.apache.fop.fo.FOPropertyMapping.getPropertyName (FOPropertyMapping.java:486)
  [2] org.apache.fop.fo.PropertyList.getExplicitBaseProp (PropertyList.java:230)
  [3] org.apache.fop.fo.Property$Maker.findProperty (Property.java:281)
  [4] org.apache.fop.fo.Property$Maker.get (Property.java:314)
  [5] org.apache.fop.fo.PropertyList.get (PropertyList.java:281)
  [6] org.apache.fop.fo.PropertyList.get (PropertyList.java:267)
  [7] org.apache.fop.fo.FObj.getProperty (FObj.java:261)

1.3. Property makers

Property value objects are created by a property maker of type org.apache.fop.fo.PropertyMaker, or of a subtype thereof. For each property type there is a property maker object, which knows the property type, its default value, and some other characteristics.

The types of property makers are: CharacterProperty.Maker, ColorTypeProperty.Maker, CompoundPropertyMaker, EnumProperty.Maker, LengthProperty.Maker, ListProperty.Maker, NumberProperty.Maker, StringProperty.Maker, and ToBeImplementedProperty.Maker.

The property makers are lazily constructed when the FObj constructor wants to create its static member propertyListTable. The constructor calls FOPropertyMapping.getGenericMappings(), which constructs and returns Property.Maker[Constants.PROPERTY_COUNT+1] s_generics. The FObj constructor then copies this array of PropertyMakers into propertyListTable.

public static PropertyMaker[] getGenericMappings() first creates the shorthand property makers, so that they can be used in the creation of the makers of the real properties, and a set of generic property makers, which act as templates for the real property makers. Next it creates the makers for all property types. Related property types are grouped, e.g. createFontProperties().

An example is the creation of the maker for the font-size property type:

        m  = new LengthProperty.Maker(PR_FONT_SIZE);
        m.setInherited(true);
        m.setDefault("12pt");
        m.setPercentBase(LengthBase.INH_FONTSIZE);
        addPropertyMaker("font-size", m);

Since font-size is a length, its maker is a LengthProperty.Maker. It is inherited, and its default value is 12 pt. If the user specifies the font-size value as a percentage, then the actual value is calculated from the font-size value inherited from the parent FO node.

1.4. Shorthand properties

1.4.1. Overview

Shorthand properties are properties which are shorthand for a number of properties. In other words, they specify the value of a number of properties in a single attribute. All shorthand properties can take a list of values, which are space separated in the FO file. The FO spec specifies how this list of values determines the values of the properties for which this is the shorthand (the target properties.). The length of the list of values for a single shorthand property may vary. For each length the attribution of these values to the target properties is different.

When the FO tree is constructed, shorthand property values are parsed and stored like any other property value. Because the value can be a list, it is always of type ListProperty.

The meaning of shorthand properties is only dealt with when the value of one of the target properties is retrieved. For that purpose each target property maker knows the shorthand properties that may set its value, and when the target property value is retrieved, its maker checks with each of its shorthand property makers if it has a value. Note that the value of a shorthand property is never retrieved directly, because shorthand properties have no direct meaning for the layout.

When the shorthand property value has been retrieved, the value for the target property must be extracted from the list. That is done by a shorthand parser, which implements ShorthandParser. There are two implementing types: GenericShorthandParser and BoxPropShorthandParser. Their method convertValueForProperty knows how each specified value determines the value of the possible target properties. A shorthand parser object is added to the shorthand property maker when the maker is created.

Note that CompoundPropertyMaker also has a member shorthandMaker. I am not sure if this has anything to do with shorthand properties. It seems more related to CompoundPropertyMaker delegating certain tasks to a subproperty maker, viz. the one which is the shorthandMaker.

1.4.2. Example of a shorthand property

The property margin is shorthand for the four properties margin-top, margin-right, margin-bottom, margin-left. Its value can consist of 1 to 4 width values.

When the property maker for margin is created, it gets a BoxPropShorthandParser as shorthand parser:

m  = new ListProperty.Maker(PR_MARGIN);
m.setInherited(false);
m.setDefault("");
m.setDatatypeParser(new BoxPropShorthandParser());
m.setPercentBase(LengthBase.BLOCK_WIDTH);
addPropertyMaker("margin", m);

When the property maker for margin-top is created, the margin maker is registered with it as a shorthand maker:

m  = new LengthProperty.Maker(PR_MARGIN_TOP);
m.setInherited(false);
m.setDefault("0pt");
m.addShorthand(s_generics[PR_MARGIN]);
m.setPercentBase(LengthBase.BLOCK_WIDTH);
addPropertyMaker("margin-top", m);

The maker for border-top-width has three shorthands: border-top, border-width, and border:

 this.shorthands = instance of org.apache.fop.fo.properties.PropertyMaker[3] (id=772)
 this.shorthands[0] = "org.apache.fop.fo.properties.ListProperty$Maker@1e1dadb"
 this.shorthands[0].propId = 52
 this.shorthands[1] = "org.apache.fop.fo.properties.ListProperty$Maker@bac9b9"
 this.shorthands[1].propId = 56
 this.shorthands[2] = "org.apache.fop.fo.properties.ListProperty$Maker@8ceeea"
 this.shorthands[2].propId = 18

1.4.3. Parsing a shorthand property

The value of a shorthand property is parsed and the value of a target property is extracted in this call stack:

  [1] org.apache.fop.fo.BoxPropShorthandParser.convertValueForProperty (BoxPropShorthandParser.java:80)
  [2] org.apache.fop.fo.GenericShorthandParser.getValueForProperty (GenericShorthandParser.java:93)
  [3] org.apache.fop.fo.properties.PropertyMaker.getShorthand (PropertyMaker.java:617)
  [4] org.apache.fop.fo.properties.PropertyMaker.findProperty (PropertyMaker.java:277)
  [5] org.apache.fop.fo.properties.PropertyMaker.get (PropertyMaker.java:305)
  [6] org.apache.fop.fo.PropertyList.get (PropertyList.java:282)
  [7] org.apache.fop.fo.PropertyList.get (PropertyList.java:268)
  [8] org.apache.fop.fo.PropertyManager.getMarginProps (PropertyManager.java:301)

The extraction proceeds as follows:

  • PropertyMaker.getShorthand

    • parser.getValueForProperty(propId, listprop, propertyMaker, propertyList); propId is the ID of the target property, listprop is the shorthand property value, of type ListProperty, which was retrieved

      • if the shorthand value is inherit, get the value for the target property from the parent.

      • else convertValueForProperty(propId, listProperty, maker, propertyList)

        • get from the shorthand list of values the value that corresponds to the target property

        • if the retrieved value is not null, convert the property, maker.convertShorthandProperty(propertyList, p, null)

          • first try to convert it in the normal way: maker.convertProperty(prop, propertyList, fo)

          • if this gives a null value, test if the value is an enumerated value or a keyword; if so, process it.

1.5. Corresponding properties

A number of traits can be specified by two alternative properties, e.g. border-left-width and border-start-width. These are called corresponding properties. One of a pair of corresponding properties is an absolute property, the other is a relative property. The meaning of the relative property depends on the writing mode. When the value of a property is retrieved that has a corresponding property, the value of that corresponding property should also be taken into account.

Corresponding properties are registered with the property maker when it is created:

bwm  = new BorderWidthPropertyMaker(PR_BORDER_LEFT_WIDTH);
bwm.useGeneric(genericBorderWidth);
bwm.setBorderStyleId(PR_BORDER_LEFT_STYLE);
bwm.addShorthand(s_generics[PR_BORDER_LEFT]);
bwm.addShorthand(s_generics[PR_BORDER_WIDTH]);
bwm.addShorthand(s_generics[PR_BORDER]);
corr = new CorrespondingPropertyMaker(bwm);
corr.setCorresponding(PR_BORDER_START_WIDTH, PR_BORDER_END_WIDTH,
        PR_BORDER_AFTER_WIDTH);
addPropertyMaker("border-left-width", bwm);

There are always three corresponding properties, for the three writing modes lr_tb, rl_tb, tb_rl, in this order:

 corr = {
    baseMaker: instance of org.apache.fop.fo.properties.BorderWidthPropertyMaker(id=702)
    lr_tb: 50
    rl_tb: 36
    tb_rl: 22
    useParent: false
    relative: false
}

When a property value is retrieved, the value of the corresponding property may have priority. This is determined by the method corresponding.isCorrespondingForced(). This is true if

  • this is a relative property

  • and the corresponding property has been explicitly specified on this FO node

Relative properties are marked by the fact that their corresponding property maker has its member relative set to true; this is set when the property is created.

If the corresponding property has priority, its value is computed. Otherwise, if the value of the property itself has been explicitly specified on this FO node, it is used. Otherwise, the corresponding property is computed. Computation in this connection means that also the shorthand properties are checked.

Because shorthand properties only exist for absolute properties, the values are effectively checked in this order:

  • An absolute property

    • The explicit value of this property.

    • The explicit value of the corresponding property.

    • The value of this property from the shorthand properties.

  • A relative property

    • The explicit value of the corresponding property.

    • The explicit value of this property.

    • The value of the corresponding property from the shorthand properties.

1.6. Mapping between property names, IDs and makers

The property subsystem is set up in the class FOPropertyMapping. It creates a property maker object for each property type, and it creates mappings of the names, IDs and makers of the property types. It holds the following static maps:

property name <=> property ID     => property maker
               |			      |
    s_htSubPropNames (<-)     s_htGeneric
	s_htPropIds      (->)

Each type of FObj holds a copy of s_htGeneric as its static member FObj.propertyListTable. According to design documents an FObj type may have its own specific makers for certain property types. Probably this is the reason that FObj holds its own copy of the list of makers. This allows subclasses to hold their own modified copy. As far as I know, this is not currently the case.

The mappings are filled in the static method

    private static void addPropertyMaker(String name, Property.Maker maker) {
        s_generics[maker.getPropId()] = maker;
        s_htPropNames.put(name, new Integer(maker.getPropId()));
        s_htPropIds.put(new Integer(maker.getPropId()), name);        
    }

which is called for each property type.

The constants for property IDs are defined in the interface org.apache.fop.fo.Constants:

    int PR_ABSOLUTE_POSITION = 1;
    int PR_ACTIVE_STATE = 2;
	...
    int PR_FONT_SIZE = 94;
    ...
    int PROPERTY_COUNT = 247;

Composite properties are defined by a compound number:

    int COMPOUND_SHIFT = 9;
    int CP_MAXIMUM = 5 << COMPOUND_SHIFT;
    int CP_MINIMUM = 6 << COMPOUND_SHIFT;
    int CP_OPTIMUM = 7 << COMPOUND_SHIFT;
	...

Enumerated property values are also defined here:

    int ABSOLUTE = 1;
    int ABSOLUTE_COLORMETRIC = 2;
    ...
    int VISIBLE = 105;
    int WRAP = 106;

For fast access to important characteristic of property inheritance, PropertyList maintains a static array boolean[Constants.PROPERTY_COUNT + 1] inheritableProperty, which lists for each property type if it is inherited. It is constructed by asking the maker of each property type if it is inherited.

A few members of the array of PropertyMakers s_generics. It is indexed by the propID. Member 0 is null, and serves for unknown property types. Member 1 is for absolute-position, member 94 for font-size.

main[1] print org.apache.fop.fo.FOPropertyMapping.s_generics
 org.apache.fop.fo.FOPropertyMapping.s_generics = instance of org.apache.fop.fo.Property$Maker[248] (id=651)
main[1] print org.apache.fop.fo.FOPropertyMapping.s_generics[0]
 org.apache.fop.fo.FOPropertyMapping.s_generics[0] = null
main[1] print org.apache.fop.fo.FOPropertyMapping.s_generics[1]
 org.apache.fop.fo.FOPropertyMapping.s_generics[1] = "org.apache.fop.fo.EnumProperty$Maker@12884e0"
main[1] print org.apache.fop.fo.FOPropertyMapping.s_generics[94]
 org.apache.fop.fo.FOPropertyMapping.s_generics[94] = "org.apache.fop.fo.LengthProperty$Maker@32efa7"

A few members of the mapping s_htPropIds from propID to property name. The s_htPropIds for compound properties are shifted:

main[1] print org.apache.fop.fo.FOPropertyMapping.s_htPropIds
 org.apache.fop.fo.FOPropertyMapping.s_htPropIds = "{
  1=absolute-position
  ...
  94=font-size
  ...
  247=z-index
  512=block-progression-direction
  1024=conditionality
  ...
  5632=within-page
 }"

A few members of the mappings s_htPropNames and s_htSubPropNames from property name to propID. The propIds for compound properties are shifted:

main[1] print org.apache.fop.fo.FOPropertyMapping.s_htPropNames
 org.apache.fop.fo.FOPropertyMapping.s_htPropNames = "{
  absolute-position=1
  ...
  font-size=94
  ...
  z-index=247
 }"

main[1] print org.apache.fop.fo.FOPropertyMapping.s_htSubPropNames
 org.apache.fop.fo.FOPropertyMapping.s_htSubPropNames = "{
  block-progression-direction=512
  conditionality=1024
  ...
  within-page=5632
 }"

1.7. Storing the property values based on their PropID

The class PropertySets contains a setup by which property values may be retrieved by their PropId instead of their name. In this setup PropertyList no longer extends HashMap but contains an array of property objects, called values. In order to prevent that each FObj should contain an array of size Constants.PROPERTY_COUNT, a mapping is setup from a static array for all FO types and all property types to an index for the possible properties of an FO type, PropertySets.mapping.

PropertySets.mapping is a short[Constants.ELEMENT_COUNT+1][] matrix, which for each FO type contains a mapping from PropID to a sparse array of indices, which enumerates the properties that are valid for this FO type.

For an element fo:bar which supports 2 properties, foo, whose PropID is 21, and baz, whose PropID is 137, the array of indices has the values

   indices[21] = 1
   indices[137] = 2

and all other values are 0. Here indices denotes the row in mapping which corresponds to FO type bar.

The values are indices into the array PropertyList.values, which then looks like this:

   values[0] = null // always null.
   values[1] = reference to a 'foo' Property instance
   values[2] = reference to a 'baz' Property instance

Example of PropertySets.mapping:

PropID ->        | 0 1 2 3 4 5 ... (Contants.PR_XXX)
Element |        |
        v        |
-----------------|--------------------------------------------------
FO_BASIC_LINK    | 2 0 1 0 2 0
FO_BIDI_OVERRIDE | 3 0 0 1 2 3
FO_BLOCK         | 2 1 0 0 2 0
...              | ....
                 |

A property value of an FONode can then be retrieved as PropertyList.values[indices[propId]], where indices = PropertySets.getPropertySet(elementId) = PropertySets.mapping[elementId].

The matrix PropertySets.mapping is constructed in the routine PropertySets.initialize().

First it constructs the elements array. For each FO type this array contains an Element object. This object contains a list of properties which are valid for this type of FO, and a list of child Element objects. Each child Element object corresponds to an FO type that may occur as a child of this FO type.

Then the for (boolean dirty = true; dirty; ) loop is executed. It effect is as follows (from an email by Finn Bock). For each FO type the BitSet of allowed properties is merged with the BitSet of allowed properties of its possible direct children. When for any FO type the merge subroutine modifies its BitSet, it sets the boolean variable dirty to true to signal that another iteration of the loop is required. By iterating over the loop until no further modifications are made, one makes sure that the merging process propagates from below to the top, that is, from any FO type to its farthest possible ancestor. This ensures that every FO type registers the allowed properties of itself and of all FO types that may ever appear in its subtree.

The matrix PropertySets.mapping is still not used in PropertyList, and the array values does not yet exist (19 May 2004). The properties are held by name in PropertyList, which extends HashMap.