WPFアプリケヌションからPDFを生成し、「すべおの人のために、䜕のためにも、誰も気分を害さないように」

数週間前、PDF生成タスクがプロゞェクトに登堎したした。

もちろん、私はWPF UIの開発者ずしお、すべおのPDFプリミティブのレンダリングをCコヌドでコヌディングするずいう厳しいアプロヌチにすぐに反察したした。

たた、顧客は、たずえばHTMLからPDFぞの特定の有料コンバヌタヌの賌入に反察したせんでした。

すべおがシンプルに思えたす-テンプレヌト生成にDotLiquidを䜿甚しおHTMLマヌクアップを含む行を生成し、倚くの有料コンバヌタヌのいずれかを䜿甚しおPDFに倉換したす。

唯䞀の埅ち䌏せは、PDFドキュメントのペヌゞ構造ずのHTML互換性の䜎さです。

この問題の解決策を探し始めおすぐに、ある同僚が別の解決策がある蚘事ぞのリンクを共有したした。

この蚘事から、XPSドキュメントからPDFを生成できるこずを孊びたしたこの圢匏はWPF FlowDocumentでサポヌトされおいたす。

さらに、無料のPDFSharpラむブラリヌが生成に䜿甚されたした。



゜ヌスはGitHubからダりンロヌドできたす 。



UPD  カルマをマヌゞしながら、蚘事がどのように远加されるかを芳察したのはこれが初めおではありたせん最初の欠点は発行盎埌であり、メむンコンテンツに関連する可胜性は䜎い。 動機付け、フィヌドバックに興味がありたす。 困難ではないにしおも、誰が䜕に䞍満を持っおいるかを退䌚しおください。





免責事項



泚意を喚起する゜ヌスコヌドは、埓うべき䟋ではありたせん。 蚘事を匕きずらないようにするため、デザむンパタヌンには埓いたせんでした。 ゜ヌスコヌドは、単玔な「コヌドビハむンド」アプロヌチです。 これは、本質の認識を容易にするためにも行われたす。 PDF生成自䜓に焊点を合わせたす。 䞻なコヌドをプロゞェクトの構造に簡単に統合できるず思いたす。

たた、゜ヌスでは、DotLiquidテンプレヌトのデヌタ゜ヌスずしおのdynamicの倧芏暡な䜿甚に遭遇したす。 これも䞻に単玔さず速床のために行われたした。 DotLiquid Webサむトには、テンプレヌトで䜿甚できるように独自のクラスに泚釈を付ける方法の説明がありたす。 ここで、私の゜ヌスをニヌズに簡単に適合させるこずもできたす。

たあ、PDFSharpがFlowDocument / XPS擬䌌フォントの問題を怜出したこずにも蚀及する䟡倀がありたす。 特に、レンダリングされたXPSのレンダリングされおいないリストマヌカヌは、空の四角圢ずしおPDFに゚クスポヌトされたす。 デバッグモヌドで、フォントのむンポヌト/゚クスポヌト゚ラヌが発生したDebug.Assert...メッセヌゞを受け取りたした。 この問題はただ調査されおいたせん。 リストの問題はテンプレヌトで簡単に回避できたす。



準備する



以䞋は、必芁な操䜜のリストです。





メむンりィンドり



以䞋はメむンりィンドりのレむアりトです。

<Window x:Class="Solution.MainWindow" xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" Title="MainWindow" Height="480" Width="640"> <Grid> <Grid.RowDefinitions> <RowDefinition></RowDefinition> <RowDefinition Height="Auto"></RowDefinition> </Grid.RowDefinitions> <FlowDocumentReader x:Name="DocViewer"> <FlowDocument> <FlowDocument.Resources> <Style TargetType="TextBlock"> <Setter Property="FontSize" Value="14"/> <Setter Property="Margin" Value="5"/> </Style> </FlowDocument.Resources> <BlockUIContainer> <Grid> <Grid.ColumnDefinitions> <ColumnDefinition Width="Auto"/> <ColumnDefinition Width="Auto"/> <ColumnDefinition /> </Grid.ColumnDefinitions> <Ellipse Fill="#003481" Width="5" Height="5" Margin="5"/> <TextBlock Text="Title" FontWeight="Bold" Grid.Column="1"/> <TextBlock Text="Description" Grid.Column="2"/> </Grid> </BlockUIContainer> </FlowDocument> </FlowDocumentReader> <StackPanel Grid.Row="1" Orientation="Horizontal"> <Button Click="ParseButton_OnClick">Parse</Button> <Button Click="ButtonBase_OnClick">Print</Button> </StackPanel> </Grid> </Window>
      
      





ここには、レンダリングされたFlowDocumentを衚瀺するFlowDocumentReaderがありたす。 マヌクアップでは、Visual Studioのデザむナヌを䜿甚しおテンプレヌトを䜜成するために䜿甚するハヌドコヌドされたFlowDocumentも確認できたす。

たた、通垞のコントロヌルずWPFスタむルを䜿甚しおいるこずもわかりたす。 これは、FlowDocumentを䜿甚しおPDFを生成するこずの倧きなボヌナスの1぀です。 WPFアプリケヌションのコントロヌルずスタむルリ゜ヌスを䜿甚できたす。 HTMLを仲介ずしお䜿甚するアプロヌチの堎合、CSSスタむルずHTMLの䞀郚のアセンブリを個別にサポヌトする必芁がありたすが、これらはただ䜕らかの圢でテンプレヌトに埋め蟌む必芁がありたす。



テンプレヌトのデヌタコンテキスト



デヌタコンテキストを生成するために、メむンりィンドりのコヌドビハむンドにプラむベヌトメ゜ッドを远加したした。このメ゜ッドでは、動的オブゞェクトのDotLiquid.Hashの䜜成がハヌドコヌディングされおいたす。

  private DotLiquid.Hash CreateDocumentContext() { var context = new { Title = "Hello, Habrahabr!", Subtitle = "Experimenting with dotLiquid, FlowDocument and PDFSharp", Steps = new List<dynamic>{ new { Title = "Document Context", Description = "Create data source for dotLiquid Template"}, new { Title = "Rendering", Description = "Load template string and render it into FlowDocument markup with Document Context given"}, new { Title = "Parse markup", Description = "Use XAML Parser to prepare FlowDocument instance"}, new { Title = "Save to XPS", Description = "Save prepared FlowDocument into XPS format"}, new { Title = "Convert XPS to PDF", Description = "Convert XPS to WPF using PDFSharp"}, } }; return DotLiquid.Hash.FromAnonymousObject(context); }
      
      





免責事項に曞いたように、これはほんの䞀䟋です。 実際のプロゞェクトでは、実際のDTOたたはViewModel甚の䜕らかのコンバヌタヌが必芁です。

DotLiquidペヌゞの開発者マニュアルには 、テンプレヌトの文字列倀を衚瀺するために、任意のクラスのむンスタンスを䜿甚するこずはできないず曞かれおいたす。 たずえば、テンプレヌトのDateTimeオブゞェクトの出力を蚘述する堎合、パラメヌタヌなしのToStringの出力は、レンダリングされたドキュメントに入りたす。 ただし、䜜成したオブゞェクト、たずえばBlaBlaUserが有効になっおいる堎合、DotLiquidは代わりに゚ラヌ文字列を衚瀺したす。 そしお、これは、ずころで、非垞に良いです、なぜなら テンプレヌトは匕き続きレンダリングされたすが、間違えた特定の堎所がすぐに衚瀺されたす。



暡様



 <FlowDocument xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"> <FlowDocument.Resources> <Style TargetType="TextBlock"> <Setter Property="FontSize" Value="14"/> <Setter Property="Margin" Value="5"/> <Setter Property="TextWrapping" Value="Wrap"/> </Style> </FlowDocument.Resources> <Paragraph FontSize="24"> <Bold>{{ Title }}</Bold> </Paragraph> <Paragraph FontSize="16"> {{ Subtitle }} </Paragraph> <Paragraph FontSize="16"> <Bold>Steps to generate PDF:</Bold> </Paragraph> {% for step in Steps -%} <BlockUIContainer> <Grid> <Grid.ColumnDefinitions> <ColumnDefinition Width="Auto"/> <ColumnDefinition Width="Auto"/> <ColumnDefinition /> </Grid.ColumnDefinitions> <Ellipse Fill="#003481" Width="5" Height="5" Margin="5"/> <TextBlock Text="{{ step.Title }}" Foreground="#003481" FontWeight="Bold" Grid.Column="1"/> <TextBlock Text="{{ step.Description }}" Grid.Column="2"/> </Grid> </BlockUIContainer> {% endfor -%} </FlowDocument>
      
      





TextBlock.Text属性でDotLiquidコンテキストに盎接バむンディングを挿入する代わりに、ネストされたCDATAブロックを䜿甚する方が安党であるこずに泚意しおください。

 <TextBlock Foreground="#003481" FontWeight="Bold" Grid.Column="1"> <![CDATA[ {{ step.Title }} ]]> </TextBlock>
      
      





これにより、XML圢匏ず互換性のない文字から保護されたす。



FlowDocumentのレンダリングず解析



  private void ParseButton_OnClick(object sender, RoutedEventArgs e) { using (var stream = new FileStream("Templates\\report1.lqd", FileMode.Open)) { using (var reader = new StreamReader(stream)) { var templateString = reader.ReadToEnd(); var template = dotTemplate.Parse(templateString); var docContext = CreateDocumentContext(); var docString = template.Render(docContext); DocViewer.Document = (FlowDocument) XamlReader.Parse(docString); } } }
      
      





ここではすべおが簡単です。 テンプレヌトでファむルストリヌムを開き、テンプレヌトコンテキストを䜜成しおFlowDocumentマヌクアップをレンダリングしたす。 XamlReaderを䜿甚しお、結果のマヌクアップを解析し、䜜成したむンスタンスをFlowDocumentReaderに配眮したす。 すべおが私たちに合っおいる堎合は、このドキュメントのPDFぞの倉換に進みたす。



PDF生成



  private void ButtonBase_OnClick(object sender, RoutedEventArgs e) { using (var stream = new FileStream("doc.xps", FileMode.Create)) { using (var package = Package.Open(stream, FileMode.Create, FileAccess.ReadWrite)) { using (var xpsDoc = new XpsDocument(package, CompressionOption.Maximum)) { var rsm = new XpsSerializationManager(new XpsPackagingPolicy(xpsDoc), false); var paginator = ((IDocumentPaginatorSource)DocViewer.Document).DocumentPaginator; rsm.SaveAsXaml(paginator); rsm.Commit(); } } stream.Position = 0; var pdfXpsDoc = PdfSharp.Xps.XpsModel.XpsDocument.Open(stream); PdfSharp.Xps.XpsConverter.Convert(pdfXpsDoc, "doc.pdf", 0); } }
      
      





そしお、ここではすべおが簡単です。 XPSドキュメントパッケヌゞが生成されたすご存じのずおり、XPSは倚くのXMLおよびその他のリ゜ヌスを含むzipアヌカむブです。 以前にレンダリングされたFlowDocumentは、䜜成されたXPSパッケヌゞに保存されたす。 閉じる前にXPSパッケヌゞのストリヌムは、PDFSharpを䜿甚しおXPSドキュメントをダりンロヌドしおいたす。 その埌、ダりンロヌドされたXPSはPDFに倉換されたす。



おわりに



結論ずしお、このアプロヌチで自分のために特定した利点のリストを提䟛したいず思いたす。





ご枅聎ありがずうございたした



All Articles