カテゴリー別アーカイブ: XSL-FO・CSS

XSL-FO試行錯誤 リストアイテムのインデントが効いていない?

改まった内容の記事は結構時間がかかりますが、このブログ連載のタイトルが「試行錯誤」なので、凡ミスを載せたりしても良いかな、ということで。
実際に起こった失敗より、一部改変しています。

XSLTで箇条書きにする変換を書いていたときのこと。

FOに変換後、PDF出力すると一部のリストブロックだけ箇条書きのラベルの上に箇条書きの内容が。
これ自体はインデントの指定を間違ったときにすぐ起こるので、あまり悩むことはありません。どこかでアイテムラベルかアイテムボディのインデント指定を上書きしてしまったのでしょう。

しかし、アイテムラベルやアイテムボディの周辺を見回しても特に不味いところは見当りません。仕様を把握している方は「ああ、それは多分……」とこの時点で見当がついたかもしれません。

それでは次のようなコードで、どんなことが起こり得るでしょうか?

<xsl:attribute-set name="atsList">
  <xsl:attribute name="provisional-distance-between-starts">ホニャララ</xsl:attribute>
  <xsl:attribute name="provisional-distance-label-separation">フガフガ</xsl:attribute>
</xsl:attribute-set>
<xsl:attribute-set name="atsListLabel">
  <xsl:attribute name="start-indent">いいかんじ</xsl:attribute>
  <xsl:attribute name="end-indent">label-end()</xsl:attribute>
</xsl:attribute-set>

<xsl:attribute-set name="atsListBody">
  <xsl:attribute name="start-indent">body-start()</xsl:attribute>
  <xsl:attribute name="end-indent">ほどほど</xsl:attribute>
</xsl:attribute-set>


<xsl:template ...>
  <fo:list-block xsl:use-attribute-sets="atsList">
    <fo:list-item>
      <fo:list-item-label
       xsl:use-attribute-sets="atsListLabel">
        <fo:block xsl:use-attribute-sets="atsListLabelBlock">
          LABEL
        </fo:block>
      </fo:list-item-label>
      <fo:list-item-body xsl:use-attribute-sets="atsListBody">
        <xsl:apply-templates select="ナンチャラ"/>
      </fo:list-item-body>
    </fo:list-item>
  </fo:list-block>
</xsl:template>

答え合わせにいきましょう。

<!-- 変換後のFO -->
      <fo:list-item-body start-indent="body-start()" ... > 
        <fo:block start-indent="0pt" >
        ...
        </fo:block>
      </fo:list-item-body> 

実際に問題の指定があったのはこの箇所ではなく<xsl:apply-templates>の先、アイテムボディの内容を記述するブロックでした。start-indent="0pt"の指定があるブロックへ変換していた箇所があったのです。XSLTでattribute-setを分けていたため、気づくのが遅れました。それでも変換結果のFOを確認しながら作業していればもっと早くに気づけたのではないかと思います。

設計するときは、小さなテストケースや検証環境をあらかじめ用意しましょう。ホントに。


XSL-FO試行錯誤 XSL-FOにおけるテーブルレイアウトの利点

かつて多くのWebサイトでは、table要素を表組ではなくレイアウトのために用いる「テーブルレイアウト」が多く見られました。
これはテーブルをグリッドのように使うもので、rowspanやcolspanを組み合わせ、比較的簡単に(そして恐ろしく複雑な)Webページを作成できました。
衰退していった理由は非推奨であること*1やCSSで求めるレイアウトが可能になったこと、アクセシビリティへの関心の高まりなどさまざまにありそうですが、その中には「テーブル構造では異なる画面サイズに最適化した表示調整が困難」という理由もあるのではなかろうかと個人的には考えています。

つまり、出力時一時的に変換される、固定レイアウトを前提としたXSL-FOではいまだそれなりに有用ということです。
とはいえ、それこそHTMLにおけるテーブルレイアウトのようにグリッドの代用として使うのは最終手段にしたいところで、XSL-FOにおけるテーブルレイアウトの利点は他にあります。

static-contentとretrieve

さて、<fo:retrieve-marker>という名前のFOがあります。これを活用しstatic-contentでflow中に記述されたマーカから要素を引っぱることが可能です。static-contentはヘッダやフッタに用いられます。

任意のマーカからflowの任意の場所で要素を引っぱって来れれば可能な表現も増えるのですが、それはできません*2

flow中でヘッダ・フッタを持てるFO

ところで、flow中でヘッダ・フッタを持てるFOがあります。はい、<fo:table>です。しかもページ分割のときヘッダフッタの表示是非も指定可能なのです*3。ちなみにAH XSL Formatterでは段分割でomitするかどうかを制御可能です*4
そしてこの<fo:table-header><fo:table-footer>中では<fo:retrieve-table-marker>によってマーカから要素を引っ張れます。

そして、<fo:table-cell>にはブロックレベルのFOが格納可能です。この意味するところがおわかりになりますでしょうか。

あるセルの中のブロックがページ分割されるとき、当然(例外もあるかもしれませんが)セルもページ分割されます。ヘッダやフッタにretrieveのFOを置くことにより、マーカで中身を更新できますから、次のような表現だって可能になります。

分割後に「……continued」をヘッダ部に表示

テーブル唯一のセル中にブロックコンテナを置き、ブロックコンテナが分割されるときに「……continued」と表示されるようになりました。

<fo:table>以外のブロックでも分割前後に表示を変更可能なFOが仕様としてあることが個人的には望ましいですが、(XSL-FOにおける)テーブルレイアウトは、簡易的なflow内static-contentとして有用なのです。

ちなみに、「ヘッダやフッタではbeforeやafterにしか表示できずブロックのstartやend側の端に何かを表示できないのでは」という懸念は、限定的に解消可能です。ブロックコンテナには絶対配置というものがありましたね。(ただし、絶対配置のブロックコンテナは、他のオブジェクトと重なり得る点に注意が必要です。)

  1. *1

    Tables must not be used as layout aids. Historically, some web authors have misused tables in HTML as a way to control their page layout. This usage is non-conforming, because tools attempting to extract tabular data from such documents would obtain very confusing results. In particular, users of accessibility tools like screen readers are likely to find it very difficult to navigate pages with tables used for layout.

    https://html.spec.whatwg.org/multipage/tables.html#the-table-element
  2. *2 基本的にflow内でページ分割が影響しない事柄はXSLTが担う役目であるのでFO文書の不要な複雑化を避けられる制約でもあるのですが。
  3. *3 17–4 表のヘッダーとフッター 『XSL-FOの基礎 第2版』(アンテナハウス)
  4. *4 https://www.antenna.co.jp/AHF/help/ja/ahf-ext.html#axf.table-omit-header-at-break, https://www.antenna.co.jp/AHF/help/ja/ahf-ext.html#axf.table-omit-footer-at-break



XSL-FO試行錯誤 fo:flow-mapの注意事項

前回、flow-mapの概要と、簡単な例、若干極端な利用方法と失敗例を紹介しました。

今回も注意深く使わなければ失敗してしまうという例を紹介します。

2つのregionであるregionA、regionBへ1つのflowを流し込むようなケースです。(というより、ほかの2つはこのパターンよりも複雑なので、利用しようとしたとき「うっかり失敗する」というケースは少ないのではないでしょうか。)

脚注はそれぞれのregionごとに配置される

一見1つのregion-bodyに見えますが、実は上下2つのregion-bodyで構成され、flow-mapでxsl-region-bodyを流し込んでいます。前回見たようにregionのサイズを超えるオブジェクトを挿入すると表示が壊れるかもしれませんし、あまりメリットがないように思えます。このflow-mapがどんなときに有用かというのは今後紹介するかもしれません。

とりあえず上手くいったように見えるFOですが、大きな欠点があります。

脚注をページ下部に配置するとき、正確にはregion-bodyのbottomから脚注の分の領域が確保されます。flow-mapはあくまでマッピングを行うだけで、region-bodyが1つになったわけではありません。つまり次のようなことが起こり得ます。

脚注をflow-mapで1つに見せかけたregionに流し込む
 <fo:flow flow-name="xsl-region-body">
   <fo:block >Lorem ipsum dolor sit amet<fo:footnote>
       <fo:inline ...>*<axf:footnote-number id="a"/></fo:inline>
         <fo:footnote-body>
           <fo:block ...>Lorem ipsum ...</fo:block>
         </fo:footnote-body>
     </fo:footnote>, consectetur ...
  </fo:block>
</fo:flow>

上部のregionAの下部に脚注は配置されました。たとえば左右に分けられているregionではそれぞれのregionの下部に表示してほしいでしょうし、当然の挙動ではあります。

複雑な回避方法を考えるよりも、このページシーケンスとflow-mapを使うときに「脚注を使用しない」「図表を使用しない」と制限した方が事故を防げるでしょう。
AH XSL Formatter拡張仕様ではregion-startやregion-endを注の配置に利用可能なので、「脚注ではなく傍注を利用する」といった方法も考えられます。

https://www.antenna.co.jp/AHF/help/ja/ahf-ext.html#axf.footnote-position

flow-mapで幅の違う疑似段組も実現可能だが、段抜きはできない

AH XSL Formatterではregion-bodyのほか、ブロックコンテナに段組を指定可能ですが、段組は通常段の行進行方向の長さが均等になります。
https://www.antenna.co.jp/AHF/help/ja/ahf-fo11.html#fo.block-container
flow-mapを活用することで、段の幅が異なる段組を擬似的に実現可能なのですが、span="all"を指定しても段抜きができないなど、これもまた使い所を考える必要があります。

幅の異なる(疑似)段組

これらのFOでは、表組などでもうっかり破綻する可能性があるので、くれぐれも注意深く使用しましょう。

参考資料


前々回:XSL-FO試行錯誤 脚注のインデント
前回:XSL-FO試行錯誤 fo:flow-map 概要編


XSL-FO試行錯誤 fo:flow-map 概要編

今回はflow-mapについてです。今回の記事は「概要編」としてflow-mapとはどんなものなのか紹介します。

region-bodyは複数記述可能

<fo:region-body><fo:simple-page-master>に複数記述可能です。region-body同士は重なり得るので、適切に配置します。<fo:flow>@flow-nameで利用するregion-bodyを指定します。

region-bodyを複数使う例

<fo:layout-master-set>
  <fo:simple-page-master master-name="main"
     size="JIS-B5 portrait" margin="10mm">
     <fo:region-body region-name="en" margin-right="182mm div 2 - 5mm"/>
     <fo:region-body region-name="jp" margin-left="182mm div 2 - 5mm"/>
   </fo:simple-page-master>
 </fo:layout-master-set>
 <fo:page-sequence master-reference="main">
   <fo:flow xml:lang="ja" flow-name="jp" >
     <fo:block ...>日本語</fo:block>
     ...
   </fo:flow>
   <fo:flow xml:lang="en" flow-name="en">
     <fo:block ...>English</fo:block>
     ...
   </fo:flow>
</fo:page-sequence>

region-bodyを複数使う例

たとえば、翻訳文を併記したいけれどテキストの長さが元の文と異なってしまうときなどにも対応可能ですね。表組や段組で左右の文の位置が揃うよう並べるよりも構造がすっきりしているのではないでしょうか。表組の解除や段抜きなどはないため、図版だけページ中央に表示したいなどの要求があるとややこしくなりますが。

flow-mapについて

本記事の本題はflow-mapです。名前の通りflowのマッピングを行える機能です。簡易な理解ではページシーケンスマスタでのページシーケンスとページマスタの関係を、flowとregionに置き換えたものとなります。ページシーケンスマスタのように条件よる参照分岐はできません。

XSL-FOの基礎 | 第4章 ページレイアウトの切り替え

JIS X 4179『拡張可能なスタイルシート言語(XSL) 1.1』で紹介されているflow-mapの使い方は3パターンほどです。簡単な例としては次のような形があります。

  1. 別々のregionであるregionA、regionBに対し、同じflowを流し込む
  2. 1つのregionに対し、別々のflowであるflow1、flow2を流し込む
  3. regionA、regionBにflow1、flow2を流し込む

この中でもっとも使い方として分かりやすいのは「regionA、regionBに対し同じflowを流し込む」という形でしょう。

flow-mapで2つのregionを1つのflowに対応させる
<fo:layout-master-set>
  <fo:simple-page-master master-name="complex"
     size="JIS-B5 portrait" margin="10mm">
     <fo:region-body region-name="regionA"
       margin-top="1cm" margin-bottom="130mm"
       margin-right="182mm div 2 - 5mm"/>
     <fo:region-body region-name="regionB"
       margin-top="180mm" 
       margin-left="182mm div 2 - 5mm"/>
   </fo:simple-page-master>
  ...
  <fo:flow-map flow-map-name="flowmap">
   <fo:flow-assignment>
     <fo:flow-source-list>
       <fo:flow-name-specifier flow-name-reference="flow-merged"/>
     </fo:flow-source-list>
   <fo:flow-target-list>
     <fo:region-name-specifier
       region-name-reference="regionA" />
     <fo:region-name-specifier 
       region-name-reference="regionB"/>
   </fo:flow-target-list>
   </fo:flow-assignment>
 </fo:flow-map>
<fo:layout-master-set>
<fo:page-sequence master-reference="complex"
  flow-map-reference="flowmap">
  <fo:flow flow-name="flow-merged"/>
    <fo:block>...
    <fo:block>
  &t;/fo:flow>
</fo:page-sequence>

flow-mapで2つのregionを1つのflowに対応させる

<fo:flow-map>@flow-map-nameを指定し、<fo:page-sequence>でそのflow-mapを参照するようにします。
流し込まれるソースのflowの@flow-nameと同じ値を<fo:flow-source-list>の子、 <fo:flow-name-specifier>@flow-name-referenceに、流し込み先のregionを<fo:flow-target-list>の子、<fo:region-name-specifier@region-name-referenceに指定します。今回はregion2つに対してflow1つを割り当てました。

<fo:page-sequence>@flow-map-referenceで参照するflow-mapを指定する必要があります。
複数flow-mapを用意すれば、同じflow-nameのflowに対し、flow-mapの参照を変更することで別のregionに流し込むことも可能です。

さらに細かいregionとflow-mapを利用することで、次のようなレイアウトも可能です*

沢山のregionをflow-mapで割り当てる

試行錯誤であるところはここからで、このregionはマッピングされているとはいえそれぞれ独立です。たとえばフォントサイズを大きくするとテキストの位置がずれ、次の行のテキストと重なるかもしれません。

一部のフォントサイズを変更した結果テキストが重なった