mikeo_410


SilverlightとWPFでのXPSファイルの表示

  Silverlight で作成したページに長い文章を入れる方法を検討しているのですが、その方法の1つとして XPSファイルの表示をして見ようと思いました。
  XPSファイルなら、ワードで作成できることが最大の利点です。 

1.WPFでの表示

 

  まず、WPF では、どうやって表示するのか試してみました。
  WPF では、XpsDocument と DocumentView が備わっていて、何の苦もなく表示します。
  2行です。1行にも書けます。

  どちらも Silverlight にはなさそうなので参考にもならないと言うことでした。

  一つ参考になったのは、実行して見ると検索ができたり、文字列がコピーできることです。
  これを見るまでは、XPSを表示するのは、イメージを表示するのに近いと思っていましたが、Silverlight でもテキストとして扱う方法があるなら利用価値が広がると思います。
  (ただし、テキストのコピーなどは、ビューワーの機能で、作るのは困難だとは思いますが。マウスポインタの位置から、文字位置を知る方法さえ想像できません。)

  左図を表示したプログラムを以下に示します。

  1. <Window x:Class="wpf_xps_viewer.Window1"
  2.     xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
  3.     xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
  4.     Title="Window1" Height="560" Width="680">
  5.     <Canvas x:Name="canvas1" Background="#FFEEEEEE" Loaded="canvas1_Loaded">
  6.         <DocumentViewer Name="documentViewer1" Height="560" Width="680"/>
  7.         
  8.     </Canvas>
  9. </Window>

  1. using System.Windows;
  2. using System.IO;
  3. using System.IO.Packaging;
  4. using System.Windows.Xps.Packaging;
  5. namespace wpf_xps_viewer
  6. {
  7.     public partial class Window1 : Window
  8.     {
  9.         public Window1()
  10.         {
  11.             InitializeComponent();
  12.         }
  13.         private void canvas1_Loaded(object sender, RoutedEventArgs e)
  14.         {
  15.             XpsDocument xps = new XpsDocument(©"\tmp\XPSファイルの表示.xps", 
  16.                                     FileAccess.Read, CompressionOption.Normal);
  17.             documentViewer1.Document = xps.GetFixedDocumentSequence();
  18.         }
  19.     }
  20. }

2.Silverlightでの表示


  左図をクリックすると SketchFlow プロジェクトで作ったサンプルプログラムが起動します。
  ナビゲーション窓で「XPSファイルの表示」をクリックしてください。

  やはり、Silverlight のプロジェクトでは、System.IO.Packaging、System.Windows.Xps と言った名前空間が無いようです。
 しかし、XamlReader があって、XAMLテキストからコントロールを生成できます。
  XPSのページの記述は、XAMLのようなのでなんとかなりそうです。

2.1.XPSファイルの在処

  まず、Silverlight アプリケーションと共にパッケージされることを前提にします。
  具体的には、プロジェクトに表示するXPSファイルを追加して、ビルドアクションを「コンテンツ」にします。また、「出力ディレクトリに常にコピー」にして置きます。
  これで、アプリケーションと共に、XAPファイルに格納され、ブラウザに送られます。

2.2.XAPからXPSを読み出す

  プログラムからXAP中のXPSファイルを読み出すには、以下のようにします。

  1. XmlXapResolver xxr = new XmlXapResolver();
  2. Stream xps = (Stream)xxr.GetEntity(new Uri("sample.xps", 
  3.                            UriKind.Relative), null, typeof(Stream));
  4. StreamResourceInfo sri = new StreamResourceInfo(xps, null);

  sri.Stream が、XPSファイルのストリームを指します。ただし、XPSファイルは、フォルダを圧縮したものなので単独のファイルではありません。
  (一度、展開してフォルダの構成やファイルの内容を確認すると理解し易いと思います。)
  sri を使って、以下のように個別のファイルを読み出します。

  1. StreamResourceInfo fdoc = Application.GetResourceStream(
  2.           sri, new Uri("Documents/1/FixedDoc.fdoc", UriKind.Relative));

  Zipを開くプログラムを使えば閲覧できるかもしれませんが、普通には、ファイル名を知っている前提で使うようです。
  pngなどのイメージファイル以外はXMLなので、XmlReader で、必要な情報が取り出せます。

  1. XmlReader xr = XmlReader.Create(fdoc.Stream);

2.3.ページデータの補正

  ワードで XPS を出力したら Documents/1/FixedDoc.fdoc に、ページのパスが書かれていました。
  これを元に、同じ方法でページを読み出します。
  このファイルは、XAMLのようにCanvas、Path、ImageBrush、Glyphs などのエレメントでできています。
  しかし、そのままでは、コントロールを生成して、表示することができませんでした。
  修正は、属性の削除で、エラーになった属性を数種類削除しました。
  削除の影響は分かりませんが。

2.4.コントロールの生成

  補正したXAML記述からCanvasコントロールを生成し、表示のルートのChildrenに加えると表示されます。

  1. Canvas canvas = (Canvas)XamlReader.Load(xaml);
  2. canvas1.Children.Add(canvas);

  Visual Studio が生成するページのXAML中に canvas1 と名前を付けた Canvas があり、このChildrenとして表示しています。

  大筋は説明できましたが大きな問題を残しています。フォントとイメージです。
  ここまでの話では、実際には表示できません。

2.5.フォントの扱い

  文字列は<Glyphs>を使って表現されています。これには必ず、FontUriと言う属性が付いていて、XPSファイル中にあるフォントファイルを指しています。
  このファイルを使うのは、アプリケーションではないので、フレームワークが検索可能な位置に置く以外にはありません。
  「Silverlightのフォントとイメージの検索」に書いたように、マニフェストリソースに入れる以外には方法がありませんでした。
  具体的には、XPSを解凍して、フォントファイルをプロジェクトに追加します。このファイルのビルドアクションを「Resource」にします。
  わたしは、プロジェクトに、Resources/Fonts と言うフォルダを作ってここに置きました。
  前述の「2.3.ページデータの補正」の際に、パスを書き換えました。

  1. string value = reader.Value;
  2. if ((elementName == "Glyphs") && (attrName == "FontUri"))
  3. {
  4.     value = "Resources/Fonts/"+Path.GetFileName(value);
  5. }

  XPSを解凍してフォントを移すのは少し残念ですが、他の方法に比べればまだ優れている点もあります。大抵の場合、WEBに設置するには図などの関連ファイルをいくつも移動しますが、この方法では、使うのはVisual Studio だけですし、出力も1つのファイルになります。

2.6.イメージ・ファイルの扱い

  ワードで文書中に張り付けたイメージは、png形式でXPSファイルに格納されていました。
  ページ記述中では、ImageBrush の ImageSource に記述されています。
  このファイルも、XSPファイルの中にあって直接参照するようなXAML記述ができません。
  イメージの場合は、ResourceDirectory に置くことができます。(「Silverlightのリソースの分類」を参照
  しかし、XamlReader.Load() が "{StaticResource bitmap1}" のような記述を通しません。
  これは、XamlReader.Load() はスタティックなメソッドで、引数にXAML文字列だけを渡しているので、this.Resources が参照できないのは当然です。
  WPFなら、Context 引数を渡せるようなので、Silverlight用の仕様のようです。 

  対策としては、XamlReader.Load()を通して、出来上がったCanvasオブジェクトの子にあるImageBrush を設定し直すことです。
  出来上がった Canvas オブジェクトでは、子にPathオブジェクトがあり、この Fill プロパティが ImageBrush になっています。
  この ImageBrush の ImageSource を見ると BitmapImage オブジェクトです。表示はされませんが、BitmapImage オブジェクトは作られています。BitmapImageの SourceUri には、ページの記述にあったイメージファイルのパスが設定されています。  XamlReader.Load()は、ImageSource に書かれたファイルが無いことはエラーにしないようです。
  SourceUri の名前で、前述の方法で XAPファイル中の XSPファイルから、ストリームを取得します。
  BitmapImage を作り、SetSource()でストリームを指定します。ImageBrush を作り、ImageSource に BitmapImage を割り当てます。
  この、ImageBrush を PathのFill にセットすれば表示できました。

   もともと、Path の Fill の ImageBrush の ImageSource は、既にBitmapImage です。これの、SetSource()では表示されませんでした。


mikeo_410@hotmail.com