DITA OT 3.3 <entry rotate="1"> cell boundary overrun with Apache FO

160 views
Skip to first unread message

Stephen Fischer

unread,
Mar 16, 2019, 2:20:57 PM3/16/19
to DITA-OT Users
It doesn't appear that the table header height is calculated properly, at least not when I have some header cells with rotate and others without rotate.  The rotated text just overruns the cell boundary if the text string is too long.

Is this a known limitation of Apache FO and/or DOT 3.3 with table header text rotation enabled?

Thanks,
Stephen

Toshihiko Makita

unread,
Mar 18, 2019, 4:44:11 AM3/18/19
to DITA-OT Users
Hi Stephen,

Have you seen the following discussion about cell rotation in FOP?

Support rotate attribute on table entries #2717

Or have you already customized org.dita.pdf2/xsl/fo/tables_fop.xsl line 48 or later?

  <!-- By default in FOP, rotated text in a table entry does not change the cell size, so rotated text will overwrite other cells.
       To enable rotation, explicitly set the height and width as follows:
       1) Uncomment the fo:block-container
       2) Adjust the height and width values to either
       2a) An appropriate default that is acceptable for all of your rotated cells, or
       2b) A specific or calculated value based on the cell content --> 
  <xsl:template match="*[contains(@class, ' topic/thead ')]/*[contains(@class, ' topic/row ')]/*[contains(@class, ' topic/entry ')]" mode="rotateTableEntryContent">
    <!--<fo:block-container reference-orientation="90" width="150px" height="80px">-->
      <fo:block xsl:use-attribute-sets="thead.row.entry__content">
        <xsl:call-template name="processEntryContent"/>
      </fo:block>
    <!--</fo:block-container>-->
  </xsl:template>

Regards,

-- 
/*--------------------------------------------------
 Toshihiko Makita
 Development Group. Antenna House, Inc. Ina Branch
 Web site:
 http://www.antenna.co.jp/
 http://www.antennahouse.com/
 --------------------------------------------------*/ 

2019年3月17日日曜日 3時20分57秒 UTC+9 Stephen Fischer:

Stephen Fischer

unread,
Mar 18, 2019, 3:41:25 PM3/18/19
to DITA-OT Users
Hi, Toshihiko, no I hadn't seen that discussion.  Helpful background.

In answer to your other question, I have done some table customization, but I don't see tables-fop.xsl in org.dita.pdf2/xsl/fo in my DOT 3.3.  My customization so far has been based on tables.xsl.  

That said, based on the github discussion, I think I understand what needs to be done.

Thanks also to Radu, who emailed me offline about this.

Thanks!
Stephen

Stephen Fischer

unread,
Mar 18, 2019, 3:44:37 PM3/18/19
to DITA-OT Users
OK, I found it, I had been customizing based on the org.dita.pdf2 plug-in and hadn't noticed the org.dita.pdf2.fop plugin.  Thanks.


On Saturday, March 16, 2019 at 2:20:57 PM UTC-4, Stephen Fischer wrote:

Stephen Fischer

unread,
Mar 20, 2019, 9:32:06 AM3/20/19
to DITA-OT Users
Here is what I came up with for computing the needed cell widths (rotated height), assuming no text wrapping, for the particular Sans Helvetica bold font we use for the headers.  XSL 1.0 solution and very brute force.  I bin the proportional width ascii characters into width buckets.  Sharing in case the approach is useful to anyone else and also curious what a more elegant approach might look like.  On the plus side, this works for our needs!  Thanks for the guidance.

<xsl:template match="*[contains(@class, ' topic/thead ')]/*[contains(@class, ' topic/row ')]/*[contains(@class, ' topic/entry ')]" mode="rotateTableEntryContent">
<!-- 72 dpi is 72 px/inch /25.4 is 2.8346 px/mm.  w is for 100 chars, so conversion factor is .028346 px/w -->
<xsl:variable name="cf"><xsl:value-of select="number(72 div 2540)"/></xsl:variable>
<!-- varname integer part is measured width in mm for 100 bold characters in each group -->
<xsl:variable name="c74"><xsl:value-of select="string-length(translate(text(),translate(text(),'''',''),''))"/></xsl:variable>
<xsl:variable name="c86"><xsl:value-of select="string-length(translate(text(),translate(text(),',./I\ijl|',''),''))"/></xsl:variable>
<xsl:variable name="c103"><xsl:value-of select="string-length(translate(text(),translate(text(),' !()-:;[]`ft',''),''))"/></xsl:variable>
<xsl:variable name="c120"><xsl:value-of select="string-length(translate(text(),translate(text(),'*r{}',''),''))"/></xsl:variable>
<xsl:variable name="c145"><xsl:value-of select="string-length(translate(text(),translate(text(),'&quot;',''),''))"/></xsl:variable>
<xsl:variable name="c154"><xsl:value-of select="string-length(translate(text(),translate(text(),'z',''),''))"/></xsl:variable>
<xsl:variable name="c170"><xsl:value-of select="string-length(translate(text(),translate(text(),'#$0123456789J_@aceksvxy',''),''))"/></xsl:variable>
<xsl:variable name="c179"><xsl:value-of select="string-length(translate(text(),translate(text(),'+&lt;=&gt;^~',''),''))"/></xsl:variable>
<xsl:variable name="c187"><xsl:value-of select="string-length(translate(text(),translate(text(),'?FLTZbdghnopqu',''),''))"/></xsl:variable>
<xsl:variable name="c204"><xsl:value-of select="string-length(translate(text(),translate(text(),'EPSVXY',''),''))"/></xsl:variable>
<xsl:variable name="c221"><xsl:value-of select="string-length(translate(text(),translate(text(),'&amp;ABCDHKNRU',''),''))"/></xsl:variable>
<xsl:variable name="c238"><xsl:value-of select="string-length(translate(text(),translate(text(),'GOQw',''),''))"/></xsl:variable>
<xsl:variable name="c255"><xsl:value-of select="string-length(translate(text(),translate(text(),'M',''),''))"/></xsl:variable>
<xsl:variable name="c272"><xsl:value-of select="string-length(translate(text(),translate(text(),'%m',''),''))"/></xsl:variable>
<xsl:variable name="c289"><xsl:value-of select="string-length(translate(text(),translate(text(),'W',''),''))"/></xsl:variable>
<xsl:variable name="c298"><xsl:value-of select="string-length(translate(text(),translate(text(),'@',''),''))"/></xsl:variable>
<xsl:variable name="counted"><xsl:value-of select="$c74+$c86+$c103+$c120+$c145+$c154+$c170+$c179+$c187+$c204+$c221+$c238+$c255+$c272+$c289+$c298"/></xsl:variable>
<xsl:variable name="uncounted"><xsl:value-of select="string-length(text())-$counted"/></xsl:variable>
<xsl:variable name="sum">
<xsl:value-of select="74*$c74+86*$c86+103*$c103+120*$c120+145*$c145+154*$c154+170*$c170+179*$c179+187*$c187+204*$c204+221*$c221+238*$c238+255*$c255+272*$c272+289*$c289+298*$c298"/>
</xsl:variable>
<!-- Use generous average width of 204 for all other characters for now -->
<xsl:variable name="cellHeight"><xsl:value-of select="ceiling($cf * ($sum + 204*$uncounted))"/></xsl:variable>
<!-- Simple sanity-check height just assumes 7 pixels per character. -->
<xsl:variable name="cellHeight0"><xsl:value-of select="number(7*string-length(text()))"/></xsl:variable>
<xsl:variable name="debug">
<xsl:value-of select="concat('h0=', $cellHeight0, 'h=', $cellHeight, 'u=', $uncounted, 'c=', $counted, 'cn= ', $c74, ',', $c86, ',', $c103, ',', $c120, ',', $c145, ',', $c154, ',', $c170, ',', $c179, ',', $c187, ',', $c204, ',', $c221, ',', $c238, ',', $c255, ',', $c272, ',', $c289, ',', $c298)"/>
</xsl:variable>
<xsl:variable name="nada"/>
<xsl:element name="fo:block-container">
<xsl:attribute name="reference-orientation">90</xsl:attribute>
<xsl:attribute name="width"><xsl:value-of select="concat($cellHeight, 'px')"/></xsl:attribute>
<fo:block xsl:use-attribute-sets="thead.row.entry__content">
<xsl:call-template name="processEntryContent"/><xsl:value-of select="$nada"/>
</fo:block>
</xsl:element>
</xsl:template>



On Saturday, March 16, 2019 at 2:20:57 PM UTC-4, Stephen Fischer wrote:

Toshihiko Makita

unread,
Mar 20, 2019, 11:39:26 AM3/20/19
to DITA-OT Users
Hi Stephen,

Looks great! Calculating row height (== width of rotated fo:block-container) is not so easy. Here is my *more rough* codes:


Regards,


2019年3月20日水曜日 22時32分06秒 UTC+9 Stephen Fischer:

Eliot Kimber

unread,
Mar 20, 2019, 12:24:56 PM3/20/19
to DITA-OT Users
The DITA Community i18n plugin provides code for estimating the rendered size of text given the font details, and it could be used in this case.

The latest code requires OT 3.3 (because of new features for automatically registering Saxon extension functions).


Cheers,

E.

--
You received this message because you are subscribed to the Google Groups "DITA-OT Users" group.
To unsubscribe from this group and stop receiving emails from it, send an email to dita-ot-user...@googlegroups.com.
For more options, visit https://groups.google.com/d/optout.


--
Eliot Kimber

Stephen Fischer

unread,
Mar 21, 2019, 7:14:32 AM3/21/19
to DITA-OT Users
Thanks, Toshihiko ... there is a great deal I could learn from studying your code.  From a quick glance it looks like your row height calculation is approximate because it is based on average character widths.  Is that correct?

Eliot, very good to know about the i18n plugin ... I couldn't imagine anyone else going to the pains it took to manually sort proportional-width font characters into width buckets!  I'm at DOT 3.3 so that should be fine.  Any comments on the accuracy of the i18n plugin size estimates vs my rather obsessive approach?

One reason I'm concerned about high accuracy in this case is because many of the rotated text string characters are relatively wide in proportional fonts: M, W, G, Q, and other capital letters.  But we also have some text strings which are mostly lower case.

Thanks!

On Saturday, March 16, 2019 at 2:20:57 PM UTC-4, Stephen Fischer wrote:

Toshihiko Makita

unread,
Mar 21, 2019, 8:52:57 AM3/21/19
to DITA-OT Users
>  From a quick glance it looks like your row height calculation is approximate because it is based on average character widths.  Is that correct?

Yes, unfortunately it is correct. It may better to use https://github.com/dita-community/org.dita-community.i18n. However my plug-in must support DITA-OT 2.x to 3.x versions. As a result, it may be a time to change the support range from 2.x to 3.x (newest) versions.

Regards,

2019年3月21日木曜日 20時14分32秒 UTC+9 Stephen Fischer:

Eliot Kimber

unread,
Mar 21, 2019, 10:10:13 AM3/21/19
to DITA-OT Users
The I18N library uses Java's 2D library to format text using the specified font size and style, so it's accurate as far as glyph size goes. What it can't do is reflect the application of any kerning or inter-word space adjustments that a formatter might do, so it will tend to overestimate the size slightly (on the assumption that a more sophisticated renderer will tend to use kerning to tighten the text, not expand it).

Obviously, the longer the text string the lower the accuracy will be as small errors accumulate. For the purposes of layout of text in relatively small regions it should be good enough.

Note that the library also includes locale-aware word and line breaking, so you can use it to lay out multi-line text. 

The code was used originally to do size estimates for generating HTML where the HTML would be flowed but the HTML page size was fixed (this was for EPUB generation). (If I was to do that again I would use Antenna House to render the pages to an area tree and then generate HTML from that but that's not how the original code developed.)

For both Antenna House and FOP it's also possible to use their Java APIs to render content and get the rendered details back within an XSLT processing context, which gives you precise details about how the text would be rendered. Tony Graham from Antenna House wrote a Balisage paper about this technique a few years ago where he used it to do dynamic sizing and placement of figures and tables in academic papers. This gives you a sort of 1 1/2 pass process that can be more layout aware than a normal one-pass FO generation process.

With OT 3.3 and the support for automatic registration of extension functions it shouldn't be too hard to implement general support for that in the OT but it's not something I've tried or had impetus to do (I would need to have the work funded at this point if I was to pursue it).

Of course, it's also possible to implement a full two-pass process using either AHF or FOP to generate an area tree in pass one and then evaluate the area tree to make layout-driven decisions in passes 2+ (you might need more than two passes depending on the nature of the decisions you need to make). This isn't that hard to do programmatically but of course it will increase formatting time. You can also post-process the area tree if needed (I've used this technique with Antenna House to implement loose-leaf publishing with point pages).

Cheers,

Eliot

--
You received this message because you are subscribed to the Google Groups "DITA-OT Users" group.
To unsubscribe from this group and stop receiving emails from it, send an email to dita-ot-user...@googlegroups.com.
For more options, visit https://groups.google.com/d/optout.


--
Eliot Kimber

Stephen Fischer

unread,
Mar 21, 2019, 10:27:54 AM3/21/19
to DITA-OT Users
Toshihiko,
  OK, understood, thanks.

Eliot,
  OK, good to hear and makes sense.  Seems like the I18N plugin should be fine for me.  But also good to know I can dig deeper as needed.  Thanks!


On Saturday, March 16, 2019 at 2:20:57 PM UTC-4, Stephen Fischer wrote:
Reply all
Reply to author
Forward
0 new messages