Interpreting Indent Inheritance in XSL-FO

Overview

There seems to be confusion about the interpretation of start-indent and related properties in XSL-FO. I can create example FOs which render differently on each FO implementation I tried. This document tries to determine and describe the right interpretation in order to improve interoperability between FO implementations and to inform users about the formatting results that are to be expected.

For simplicity, we are only looking at the most often used case which excludes BIDI, reference orientation and things like that. For this document, imagine we are talking about a simple invoice document in English with blocks, block-containers and tables.

The properties in question

Here's a list of properties which are involved in this discussion:

Sources of information inside the spec and elsewhere

Main difference between start-indent and margin-left

start-indent is defined to be inherited (7.10.7), while margin-left is not inherited (7.10.3).

Examples

The examples below are all collected in these files:

Example 1, nested block with start-indent

<fo:flow>
  <fo:block start-indent="10pt">indented text
    <fo:block>nested block</fo:block>
  </fo:block>
</fo:flow>

Outer block:

The start-indent indents the text by 10pt from the left. Through the rules in 5.3.2 we can calculate the corresponding margin-left property can be calculated using the third rule set (because no corresponding margin property is explicitely specified):

margin-corresponding = start-indent - inherited_value_of(start-indent) - padding-corresponding - border-corresponding-width

margin-corresponding = 10pt - 0pt - 0pt - 0pt = 10pt

Nested block:

No properties are specified on the nested block. No margin property is specified so no special rules from 5.3.2 are triggered. The value of start-indent is simply inherited.

start-indent = inherited_value_of(start-indent) = 10pt

Example 2, nested block-container with start-indent

<fo:flow>
  <fo:block start-indent="10pt">indented text
    <fo:block-container>
      <fo:block>nested block</fo:block>
    </fo:block-container>
  </fo:block>
</fo:flow>

Outer block:

Still the same as in Example 1.

Nested block-container:

No properties are specified on the block-container. No margin property is specified so no special rules from 5.3.2 are triggered. The value of start-indent is simply inherited.

start-indent = inherited_value_of(start-indent) = 10pt

The block-container creates a new viewport/reference pair (See 6.5.3 fo:block-container). The references area is indented by 10pt due to the start-indent which makes the viewport and the reference area both narrower by 10pt than the parent reference area (the one established by the region-body in this case).

The reason for that: 4.4 Block-areas says: "A block-area which is not a line-area typically has its size in the inline-progression-direction determined by its start-indent and end-indent and by the size of its nearest ancestor reference-area." However, 6.5.3 doesn't explicitely say that the viewport/reference pair is a block-area, but 4.2.1 says at the beginning: "There are two types of areas: block-areas and inline-areas." So we assume that the reference area is a block-area and therefore has to be treated as indicated in 4.4.

Nested block inside the block-container:

No properties are specified on the block. No margin property is specified so no special rules from 5.3.2 are triggered. The value of start-indent is simply inherited.

start-indent = inherited_value_of(start-indent) = 10pt

Due to the fact that we're now inside a different reference area which is located 10pt to the left and we're still having a start-indent of 10pt, this adds up to 20pt effective indent relative to the left edge of the region-body's reference area.

Example 3, nested block with margin-left

<fo:flow>
  <fo:block margin-left="10pt">indented text
    <fo:block>nested block</fo:block>
  </fo:block>
</fo:flow>

Outer block:

Since margin-left is a corresponding property, we have to calculate start-indent first, which will then be used to define the content rectangle which will receive the text.

Since the corresponding margin-left property is specified and the formatting object (here an fo:block) does not generate a reference area, we use the rule set 2 in 5.3.2:

start-indent = inherited_value_of(start-indent) + margin-corresponding + padding-corresponding + border-corresponding-width

start-indent = 0pt + 10pt + 0pt + 0pt = 10pt

From here on we have the same process as in Example 1, since we now have a start-indent value which can be inherited to child nodes like the nested fo:block in this example.

The start-indent indents the text by 10pt from the left.

Nested block:

No properties are specified on the nested block. No margin property is specified so no special rules from 5.3.2 are triggered. The value of start-indent is simply inherited.

start-indent = inherited_value_of(start-indent) = 10pt

Example 4, nested block with start-indent (start-indent reset to 0pt on nested block)

<fo:flow>
  <fo:block start-indent="10pt">indented text
    <fo:block start-indent="0pt">nested block</fo:block>
  </fo:block>
</fo:flow>

Outer block:

The start-indent indents the text by 10pt from the left.

Nested block:

start-indent is explicitely specified. No rules from 5.3.2 are triggered. An inherited value is overwritten by the explicit value.

start-indent = 0pt

The text of the nested block starts exactly on the left edge of the nearest ancestor reference area (the region-body), i.e. the text is not indented.

Example 5, nested block-container with start-indent (start-indent reset to 0pt on the block-container)

<fo:flow>
  <fo:block start-indent="10pt">indented text
    <fo:block-container start-indent="0pt">
      <fo:block>nested block</fo:block>
    </fo:block-container>
  </fo:block>
</fo:flow>

Outer block:

The text is indented by 10pt due to the start-indent.

Nested block-container:

start-indent is explicitely set and overwrites any inherited value.

start-indent = 0pt

The block-container's reference area starts right at the left edge of the nearest ancestor reference area (region-body) and has the same with as the region-body's content rectangle.

Nested block inside the block-container:

No properties are specified on the block. No margin property is specified so no special rules from 5.3.2 are triggered. The value of start-indent is simply inherited.

start-indent = inherited_value_of(start-indent) = 0pt

The text is not indented relative to the left edge of the region-body reference area (=content rectangle).

Example 6, nested block with margin-left (margin-left again set to 0pt on nested block)

<fo:flow>
  <fo:block margin-left="10pt">indented text
    <fo:block margin-left="0pt">nested block</fo:block>
  </fo:block>
</fo:flow>

Outer block:

margin-left is explicitely specified.

Second rule set in 5.3.2 applies:

start-indent = inherited_value_of(start-indent) + margin-corresponding + padding-corresponding + border-corresponding-width

start-indent = 0pt + 10pt + 0pt + 0pt = 10pt

Nested block:

margin-left is explicitely specified.

Second rule set in 5.3.2 applies (nested block does not generate area):

start-indent = inherited_value_of(start-indent) + margin-corresponding + padding-corresponding + border-corresponding-width

start-indent = 10pt + 0pt + 0pt + 0pt = 10pt

Even though we set margin-left explicitely, the inherited start-indent value is dominant and results in an effective indent of 10pt.

Example 7, nested block-container with margin-left (margin-left again set to 0pt on the block-container)

<fo:flow>
  <fo:block margin-left="10pt">indented text
    <fo:block-container margin-left="0pt">
      <fo:block>nested block</fo:block>
    </fo:block-container>
  </fo:block>
</fo:flow>

Outer block:

margin-left is explicitely specified.

Second rule set in 5.3.2 applies:

start-indent = inherited_value_of(start-indent) + margin-corresponding + padding-corresponding + border-corresponding-width

start-indent = 0pt + 10pt + 0pt + 0pt = 10pt

Nested block-container:

margin-left is explicitely set.

First rule set in 5.3.2 applies (block-container generates a reference area):

start-indent = margin-corresponding + padding-corresponding + border-corresponding-width

start-indent = 0pt + 0pt + 0pt = 0pt

The block-container's reference area starts right at the left edge of the nearest ancestor reference area (region-body) and has the same with as the region-body's content rectangle.

Nested block inside the block-container:

No properties are specified on the block. No margin property is specified so no special rules from 5.3.2 are triggered. The value of start-indent is simply inherited.

start-indent = inherited_value_of(start-indent) = 0pt

The text is not indented relative to the left edge of the region-body reference area (=content rectangle).

Example 8, nested block-container with start-indent (start-indent reset to 0pt on the nested block in the block-container)

<fo:flow>
  <fo:block start-indent="10pt">indented text
    <fo:block-container>
      <fo:block start-indent="0pt">nested block</fo:block>
    </fo:block-container>
  </fo:block>
</fo:flow>

Outer block:

The text is indented by 10pt due to the start-indent.

Nested block-container:

No properties are specified on the block-container. No margin property is specified so no special rules from 5.3.2 are triggered. The value of start-indent is simply inherited.

start-indent = inherited_value_of(start-indent) = 10pt

Nested block inside the block-container:

start-indent is explicitely set to 0pt.

start-indent = 0pt

The text is effectively indented by 10pt relative to the left edge of the region-body reference area (=content rectangle) due to the indentation of the block-container's reference area.

Example 9, nested block-container with margin-left (margin-left reset to 0pt on the nested block in the block-container)

<fo:flow>
  <fo:block margin-left="10pt">indented text
    <fo:block-container>
      <fo:block margin-left="0pt">nested block</fo:block>
    </fo:block-container>
  </fo:block>
</fo:flow>

Outer block:

margin-left is explicitely specified.

Second rule set in 5.3.2 applies:

start-indent = inherited_value_of(start-indent) + margin-corresponding + padding-corresponding + border-corresponding-width

start-indent = 0pt + 10pt + 0pt + 0pt = 10pt

Nested block-container:

No properties are specified on the block-container. No margin property is specified so no special rules from 5.3.2 are triggered. The value of start-indent is simply inherited.

start-indent = inherited_value_of(start-indent) = 10pt

Nested block inside the block-container:

margin-left is explicitely set to 0pt.

Second rule set in 5.3.2 applies:

start-indent = inherited_value_of(start-indent) + margin-corresponding + padding-corresponding + border-corresponding-width

start-indent = 10pt + 0pt + 0pt + 0pt = 10pt

The text is effectively indented by 20pt relative to the left edge of the region-body reference area (=content rectangle) due to the indentation of the block-container's reference area and the nested block's own start-indent.

Discussion of the results

Especially the examples 2 and 9 will raise some eyebrows. They may not be as expected but they are easily explained (provided all of the above reasoning is correct):

A block-container creates a new viewport/reference pair, where the reference area is actually the most interesting here. "4.2.2 Common Traits" explains the is-reference-area trait: "The Boolean trait is-reference-area determines whether or not an area establishes a coordinate system for specifying indents." Now, since the reference area establishes a new coordinate system, we also have a new nearest ancestor reference area which may be placed differently from the previously valid reference area due to start-indent handling for the block-container.

The unexpected result also comes from the fact that start-indent is simply inherited inside the FO tree. Property inheritance doesn't take new reference areas into account. That's why indentation can sum up when reference areas are encountered later in layout. But this is just an effect of the coordinate system, not of the mathematics from property inheritance and the complex formulae in 5.3.2.

Further examples with tables

If we create an example for tables (see http://people.apache.org/~jeremias/fop/indent-inheritance2.fo) we can see similar effects. fo:table-cell (6.7.10) creates a normal reference area for its content. So if a start-indent is specified on an ancestor of the table or on the table itself, this gets propagated to the table-cell and may cause unexpected results (from a normal user's point of view). Either I'm totally wrong here, or that's why some FO implementors chose to deliberately break property inheritance in these cases. Please see comment 20 in the W3C document mentioned at the beginning of the document.

To avoid the strange effects, you can reset start-indent and end-indent to "0pt" on fo:table-body, fo:table-header and fo:table-footer. These two properties don't apply to these FOs but can still be specified as they are inherited.

Results

Apache FOP should, by default, stick to the rules defined by the XSL-FO specification. The W3C SG has recently confirmed that our interpretation is correct. However, an option was recently added that allows to enable an alternative rule set that lets FOP mimic the behaviour chosen by many commercial XSL-FO implementations. Please note that enabling this feature lets FOP behave differently from what the spec dictates.

Discussions on the fop-dev mailing list

Margins on simple-page-master and region-body

  • No labels