FormSubmitXml.cs
// 
// このコードは、DioDocs for PDF のサンプルの一部として提供されています。
// © MESCIUS inc. All rights reserved.
// 
using System.Drawing;
using System.IO;
using System.Linq;
using System.Xml;
using System.Text;
using System.Collections.Generic;
using GrapeCity.Documents.Pdf;
using GrapeCity.Documents.Pdf.AcroForms;
using GrapeCity.Documents.Pdf.Actions;
using GrapeCity.Documents.Pdf.Annotations;
using GrapeCity.Documents.Text;

namespace DsPdfWeb.Demos
{
    // 新しい FormDataSubmit サンプルでは、より簡単にフォームデータを送信する方法を紹介しています。
    // 
    // このサンプルは、サーバーに送信できる AcroForm PDF を作成します。
    // これはサーバーに依存しており、送信されたデータを XML に入れ、その XML を
    // 同様のフォームを含む PDF にインポートし、ロードされたデータを含むフォームを
    // クライアントに送り返します。
    // フォームフィールドが満たされた生成後の PDF は、クライアントブラウザのデフォルト
    // の PDF ビューワに表示されることに注意してください。
    // このコードは FormSubmit に類似しています。
    public class FormSubmitXml
    {
        public int CreatePDF(Stream stream)
        {
            var doc = new GcPdfDocument();
            var page = doc.NewPage();

            var rc = Common.Util.AddNote("フォームのフィールドに入力し、「送信」をクリックしてサーバーに" +
                "送り返します。サンプルサーバーは、送信されたデータを XML に入れ、" +
                "その XML を別の互換性のあるフォームで PDF にフィードし、送信された" +
                "データで満たされた結果のフォームをブラウザに送り返します。" +
                "送信されたデータを含むフォームは、ブラウザのデフォルトの PDF ビューワで" +
                "開かれ、「送信」および「リセット」ボタンはありません。", page);

            var g = page.Graphics;
            var tf = new TextFormat() { Font = StandardFonts.Times, FontSize = 14 };
            var ip = new PointF(72, rc.Bottom + 36);
            float fldOffset = 72 * 2 + 46;
            float fldHeight = tf.FontSize * 1.2f;
            float dY = 32;

            // テキストフィールド。
            g.DrawString("First name:", tf, ip);
            var fldFirstName = new TextField() { Name = "FirstName", Value = "John" };
            fldFirstName.Widget.Page = page;
            fldFirstName.Widget.Rect = new RectangleF(ip.X + fldOffset, ip.Y, 72 * 3, fldHeight);
            fldFirstName.Widget.DefaultAppearance.Font = tf.Font;
            fldFirstName.Widget.DefaultAppearance.FontSize = tf.FontSize;
            doc.AcroForm.Fields.Add(fldFirstName);
            ip.Y += dY;

            // テキストフィールド。
            g.DrawString("Last name:", tf, ip);
            var fldLastName = new TextField() { Name = "LastName", Value = "Smith" };
            fldLastName.Widget.Page = page;
            fldLastName.Widget.Rect = new RectangleF(ip.X + fldOffset, ip.Y, 72 * 3, fldHeight);
            fldLastName.Widget.DefaultAppearance.Font = tf.Font;
            fldLastName.Widget.DefaultAppearance.FontSize = tf.FontSize;
            doc.AcroForm.Fields.Add(fldLastName);
            ip.Y += dY;

            // チェックボックス。
            g.DrawString("Subscribe to Mailing List:", tf, ip);
            var fldCheckbox = new CheckBoxField() { Name = "Subscribe", Checked = true };
            fldCheckbox.Widget.Page = page;
            fldCheckbox.Widget.Rect = new RectangleF(ip.X + fldOffset, ip.Y, fldHeight, fldHeight);
            doc.AcroForm.Fields.Add(fldCheckbox);
            ip.Y += dY;

            // 複数行テキストボックス。
            g.DrawString("Additional information:", tf, ip);
            var fldAdditionalInfo = new TextField() { Name = "AdditionalInfo", Multiline = true };
            fldAdditionalInfo.Widget.Page = page;
            fldAdditionalInfo.Widget.Rect = new RectangleF(ip.X + fldOffset, ip.Y, 72 * 3, fldHeight * 2);
            fldAdditionalInfo.Widget.DefaultAppearance.Font = tf.Font;
            fldAdditionalInfo.Widget.DefaultAppearance.FontSize = tf.FontSize;
            doc.AcroForm.Fields.Add(fldAdditionalInfo);
            ip.Y += dY * 2;

            // フォーム送信ボタン。
            var btnSubmit = new PushButtonField();
            btnSubmit.Widget.Rect = new RectangleF(ip.X + fldOffset, ip.Y, 72, fldHeight);
            btnSubmit.Widget.ButtonAppearance.Caption = "Submit";
            btnSubmit.Widget.Highlighting = HighlightingMode.Invert;
            btnSubmit.Widget.Page = page;

            // 送信用の URL。
            btnSubmit.Widget.Activate = new ActionSubmitForm("/Samples/HandleFormSubmitXml");
            doc.AcroForm.Fields.Add(btnSubmit);

            // フォームリセットボタン。
            var btnReset = new PushButtonField();
            btnReset.Widget.Rect = new RectangleF(ip.X + fldOffset + 72 * 1.5f, ip.Y, 72, fldHeight);
            btnReset.Widget.ButtonAppearance.Caption = "Reset";
            btnReset.Widget.Highlighting = HighlightingMode.Invert;
            btnReset.Widget.Page = page;
            btnReset.Widget.Activate = new ActionResetForm();
            doc.AcroForm.Fields.Add(btnReset);
            ip.Y += dY;

            // PDF ドキュメントを保存します。
            doc.Save(stream);
            return doc.Pages.Count;
        }

        // 
        // メモ:以下のコードは、このサンプルで準備されたフォームが送信されるときに Web サンプルブラウザ
        // コントローラーによって使用され、CreatePDF() メソッドによって直接呼び出されることはありません。
        //     

        // GcPdfDocument を作成し、AcroForm PDF を ロードして、
        // GcPdfDocument.ImportFormDataFromXML() メソッドを使用してデータを入力します。
        // 
        // このメソッドは、このサンプルで準備されたフォームがユーザーによって送信
        // されると、サンプルコントローラーによって呼び出されます。サンプルコント
        // ローラは、クライアントの応答を解析し、送信されたフィールド値で満たされた
        // 「値」コレクションを構築し、このメソッドを呼び出して XML を準備し、新しく
        // 作成した PDF にインポートして、結果の PDF をコントローラに返し、それを
        // クライアントに戻します。
        public static Stream ImportFormData(List<FieldExportEntry> values)
        {
            var pdf = new GcPdfDocument();
            using var fs = File.OpenRead(Path.Combine("Resources", "PDFs", "ImportFormXML.pdf"));
                pdf.Load(fs);
                using (var ms = new MemoryStream())
                {
                    SaveFieldsToXML(values, ms);
                    ms.Seek(0, SeekOrigin.Begin);
                    pdf.ImportFormDataFromXML(ms);
                }
                var outMs = new MemoryStream();
                pdf.Save(outMs);
                outMs.Seek(0, SeekOrigin.Begin);
                return outMs;
            }

        // XML にエクスポートするためのフォームフィールドとその値を表します。
        public class FieldExportEntry
        {
            public string Name { get; set; }
            public List<string> Values { get; set; }
            // 注意:このサンプルは子フィールドをサポートしていません。
            // public List<FieldTreeNode> Children { get; set; }
        }

        // フィールドとその値をストリームに保存します。
        // 
        // このメソッドは GcPdfDocument.ExportFormDataToXML()に似て
        // いますが、次の重要な制限があります。
        // - 子フィールド(field.Children コレクション)はサポートしていません。
        // - 有効な XML 名(xfdf:original)ではない名前のフィールドは処理しません。
        public static void SaveFieldsToXML(List<FieldExportEntry> values, Stream stream)
        {
            var xws = new XmlWriterSettings()
            {
                Indent = true,
                CloseOutput = false,
                Encoding = Encoding.UTF8,
            };
            using XmlWriter xw = XmlWriter.Create(stream, xws);
                xw.WriteStartElement("fields");
                xw.WriteAttributeString("xmlns", "xfdf", null, "http://ns.adobe.com/xfdf-transition/");
                foreach (var ftn in values)
                {
                    xw.WriteStartElement(ftn.Name);
                    foreach (var v in ftn.Values)
                    {
                        xw.WriteStartElement("value");
                        // メモ:配列の値はクライアント PDF ビューワによって形成され、 'on'
                        // チェックボックス値を 'true' として表しますが、ImportFormDataFromXML
                        // は 'on'値が "Yes"として表されることを期待します(これが、Acrobat の
                        // ように ExportFormDataToXML を動作させる仕組みです)。 これは、
                        // このサンプルのためだけに行った、迅速で汚いハックです。
                        xw.WriteString(v == "true" ? "Yes" : v);
                        xw.WriteEndElement();
                    }
                    xw.WriteEndElement();
                }
                xw.WriteEndElement();
        }
    }
}