diff --git a/cli/.gitignore b/cli/.gitignore
new file mode 100644
index 0000000000000000000000000000000000000000..5d82de24ce1d41365d1f9401aedb00c029c0d21b
--- /dev/null
+++ b/cli/.gitignore
@@ -0,0 +1 @@
+/.PDFData
diff --git a/cli/CSRender.sln b/cli/CSRender.sln
new file mode 100644
index 0000000000000000000000000000000000000000..5560ded78f3c642250a99157507360c54601120e
--- /dev/null
+++ b/cli/CSRender.sln
@@ -0,0 +1,25 @@
+
+Microsoft Visual Studio Solution File, Format Version 12.00
+# Visual Studio 15
+VisualStudioVersion = 15.0.28307.960
+MinimumVisualStudioVersion = 10.0.40219.1
+Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "CSRender", "CSRender\CSRender.csproj", "{FD99EFAA-2479-4E3B-BD1A-5B785288E3B3}"
+EndProject
+Global
+ GlobalSection(SolutionConfigurationPlatforms) = preSolution
+ Debug|Any CPU = Debug|Any CPU
+ Release|Any CPU = Release|Any CPU
+ EndGlobalSection
+ GlobalSection(ProjectConfigurationPlatforms) = postSolution
+ {FD99EFAA-2479-4E3B-BD1A-5B785288E3B3}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {FD99EFAA-2479-4E3B-BD1A-5B785288E3B3}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {FD99EFAA-2479-4E3B-BD1A-5B785288E3B3}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {FD99EFAA-2479-4E3B-BD1A-5B785288E3B3}.Release|Any CPU.Build.0 = Release|Any CPU
+ EndGlobalSection
+ GlobalSection(SolutionProperties) = preSolution
+ HideSolutionNode = FALSE
+ EndGlobalSection
+ GlobalSection(ExtensibilityGlobals) = postSolution
+ SolutionGuid = {8B1418EB-CA5D-4F34-9026-020B0B30AE79}
+ EndGlobalSection
+EndGlobal
diff --git a/cli/CSRender/App.config b/cli/CSRender/App.config
new file mode 100644
index 0000000000000000000000000000000000000000..45437954e08a2d113d4b04d9d9f51e30f4676ab9
--- /dev/null
+++ b/cli/CSRender/App.config
@@ -0,0 +1,6 @@
+
+
+
+
+
+
diff --git a/cli/CSRender/BasehashPDF/BaseHash.odg b/cli/CSRender/BasehashPDF/BaseHash.odg
new file mode 100644
index 0000000000000000000000000000000000000000..98a0d31d5032bd12a1774b069f0704aa1e0e92af
Binary files /dev/null and b/cli/CSRender/BasehashPDF/BaseHash.odg differ
diff --git a/cli/CSRender/BasehashPDF/BaseHash.pdf b/cli/CSRender/BasehashPDF/BaseHash.pdf
new file mode 100644
index 0000000000000000000000000000000000000000..924facf767f8a27f551fe3af56da3c5435e92a2f
Binary files /dev/null and b/cli/CSRender/BasehashPDF/BaseHash.pdf differ
diff --git a/cli/CSRender/BasehashPDF/RenderHash_72.0_Crop_JPG_91.json b/cli/CSRender/BasehashPDF/RenderHash_72.0_Crop_JPG_91.json
new file mode 100644
index 0000000000000000000000000000000000000000..cdb84ef7e259944050e93db858113d123d6a841c
--- /dev/null
+++ b/cli/CSRender/BasehashPDF/RenderHash_72.0_Crop_JPG_91.json
@@ -0,0 +1,22 @@
+{
+ "Head": {
+ "HashBaseName": "72.0_Crop_JPG_91",
+ "HashFilePath": "C:\\Users\\cozy\\Documents\\GitDoc\\Scripts\\WinRT\\CSRender\\CSRender\\BasehashPDF\\RenderHash_72.0_Crop_JPG_91.json",
+ "Dpi": "72.0",
+ "Box": "Crop",
+ "ImType": "JPG",
+ "JQ": "91"
+ },
+ "Files": [
+ {
+ "Key": "BaseHash.pdf",
+ "Value": {
+ "UpdateTime": "\/Date(1585355435085)\/",
+ "UpdateTimeStr": "2020\/03\/28 9:30:35",
+ "PageHashCode": [
+ "B7D69FA446C6C64F985AA3446C6D5C6FD0A70C4C2F1E9AB55AB10993A5A67313"
+ ]
+ }
+ }
+ ]
+}
\ No newline at end of file
diff --git a/cli/CSRender/CSRender.cs b/cli/CSRender/CSRender.cs
new file mode 100644
index 0000000000000000000000000000000000000000..f4e87326ea715d11f28e0fb84441fb0fa1a63657
--- /dev/null
+++ b/cli/CSRender/CSRender.cs
@@ -0,0 +1,926 @@
+using System;
+using System.IO;
+using System.Collections.Generic; // List<>
+using System.Linq; // for Enumration
+
+using System.Text;
+using System.Threading;
+using System.Threading.Tasks;
+
+using System.Text.RegularExpressions;
+using System.Security.Cryptography; // for Hash
+using System.Runtime.Serialization;
+using System.Runtime.Serialization.Json;
+using System.Collections.ObjectModel;
+using System.Runtime.InteropServices; // for DLL import
+
+
+// PDFiumの追加
+using PdfiumViewer;
+
+// no need
+//using System.Windows; // No need
+//using System.Windows.Media.Imaging; // no need
+// for Stream conv.
+//using System.Runtime.InteropServices.WindowsRuntime;
+//using Windows.Storage.Streams;
+//using System.Reflection; // for Assembly.
+
+using static CSRender.RenderPDF;
+
+namespace CSRender {
+#pragma warning disable IDE1006 // 小文字のメソッドを許可
+ static public class Check
+ {
+ //https://gist.github.com/retorillo/4e0c4a3cf4c7096e05ac
+
+ static public bool bDump = false;
+ static public bool bThDump = false;
+
+ [DllImport("user32.dll")]
+ extern static bool SetProcessDPIAware();
+ [DllImport("user32.dll")]
+ extern static IntPtr GetWindowDC(IntPtr hwnd);
+ [DllImport("gdi32.dll")]
+ extern static int GetDeviceCaps(IntPtr hdc, int index);
+ [DllImport("user32.dll")]
+ extern static int ReleaseDC(IntPtr hwnd, IntPtr hdc);
+
+ public static int GetDPI() {
+ // モニタの解像度の取得
+ //return 96;
+ SetProcessDPIAware();// もしくはdpiAwareをマニフェストで設定すればよい。
+ var dc = GetWindowDC(IntPtr.Zero);
+ var dpi = GetDeviceCaps(dc, 88/*LOGPIXELSX*/);
+ ReleaseDC(IntPtr.Zero, dc);
+ return dpi;// 96;// dpi;
+ }
+ }
+
+ public static class RenderPDF
+ {
+ ///
+ /// Rendering Condition
+ ///
+ [DataContract]
+ public class RenderConditionParams
+ {
+ [DataMember(Order = 0)]
+ public double Dpi = 72.0;
+ [DataMember(Order = 1)]
+ public string BoxType = "Crop";
+ [DataMember(Order = 2)]
+ public string ImageType = "JPEG";
+ [DataMember(Order = 3)]
+ public int JpegQ = 91;
+ // Method non
+ }
+
+ /*
+ * Console Echo関係
+ */
+ public static bool bEcho = false;
+ public static void setEcho(bool b) {bEcho = b;}
+ public static void echo(params object[] args) {
+ if (!bEcho) return;
+ var s = "";
+ foreach (object a in args) {
+ s += a.ToString();
+ }
+ Console.WriteLine(s);
+ }
+
+ ///
+ /// PDFのレンダリング
+ ///
+ ///
+ ///
+ /// レンダリングの条件
+ ///
+ ///
+ ///
+ /// 未使用,常にtrueで使用
+ ///
+ ///
+ public static int RenderPdfDoc(
+ string pdfPath,
+ string inDir = "",
+ RenderConditionParams pm = null,
+ string inPageRange = "1-*",
+ string inMode = "pageBitMap",
+ bool bSaveImage = true, //ハッシュ値のみ計算するときにfalseにする
+ bool bHash = false,
+ UtHash.HashData inHashData = null,
+ bool bPDFium = false,
+ int nPageThreadNum = 4
+ ) {
+ if (pm == null) {
+ pm = new RenderConditionParams();
+ }
+ var otDir = inDir;
+ var otBaseName = Path.GetFileName(pdfPath);//*.pdf
+ var otExtention = pm.ImageType.ToLower();
+ if (bSaveImage) {
+ if ( otDir == "" ) {
+ // 出力ディレクトリが空→元PDFの同一フォルダにIMGフォルダを作成する
+ otDir = Path.Combine(Directory.GetParent(pdfPath).FullName, "IMG");
+ }
+ if (!Directory.Exists(otDir))
+ Directory.CreateDirectory(otDir);
+ echo("ouptput dir=",otDir);
+ }
+ // Open pdf by PDFium.
+ int pageCount = 0;
+ using(var docG = PdfiumViewer.PdfDocument.Load(pdfPath)){
+ pageCount = docG.PageCount;
+ inMode = "pageBitMap";
+ var taskList = new List>();
+ uint[] rNo = makePageRange(inPageRange, (uint)pageCount);
+
+ if (inHashData != null) {
+ echo("Create Hash");
+ // ハッシュ対象の最大ページ数の設定必須。
+ inHashData.SetFile(pdfPath, (int)pageCount);
+ }
+
+ echo($"Page.ParallelOptions:Thread={nPageThreadNum},Pages={rNo.Length}");
+ /////並行処理するスレッド数を指定(2-4ぐらいが穏便な数値)
+ ParallelOptions options = new ParallelOptions { MaxDegreeOfParallelism = nPageThreadNum };
+ Parallel.ForEach(rNo, options, i => {
+ //Console.WriteLine($"*****{i}*****/{rNo.Length}");
+ var hashValue = "";
+ var ret = RenderPage(
+ docG: docG,
+ index: (int)i-1,
+ otPath: Path.Combine(otDir, $"{otBaseName}.{i}.{otExtention}"),
+ pm: pm,
+ bSaveImage: bSaveImage, // ファイル保存
+ bHash: bHash,
+ otHashCode: ref hashValue
+ );
+ if (inHashData != null) {
+ lock (inHashData) {
+ inHashData.AddHashCode(pdfPath, (int)i, hashValue);
+ }
+ }
+ });
+ echo("End Para");
+ docG.Dispose();
+ }
+ return pageCount;// ページ数を返却します
+ }
+ ///
+ /// imageをRGB3チェンネルbitmapにRenderingする
+ ///
+ ///
+ public static System.Drawing.Bitmap RenderRgbBitMap(
+ PdfiumViewer.PdfDocument docG,
+ int index,
+ double dpiX,
+ double dpiY,
+ PdfRenderFlags flg
+ ){
+ var bitmap = docG.Render(index, (float)dpiX, (float)dpiY, flg) as System.Drawing.Bitmap;
+ if (bitmap.PixelFormat == System.Drawing.Imaging.PixelFormat.Format24bppRgb)
+ return bitmap;
+ //
+ var cloneRect = new System.Drawing.RectangleF(0, 0, bitmap.Size.Width, bitmap.Size.Height);
+ var format = System.Drawing.Imaging.PixelFormat.Format24bppRgb;
+ var clonebitmap = bitmap.Clone(cloneRect, format);
+ //
+ //bitmap.Dispose();
+ return clonebitmap;
+ /*
+ 保存時にBitmapのクローンを作成し、その際のBitmapの形式に24bitRGB(PixelFormat.Format24bppRgb)を
+ 指定し3版RGBTiffを出力します。
+ System.Drawing.Imaging.PixelFormat format = PixelFormat.Format24bppRgb;
+ clonebitmap = bitmap1.Clone(cloneRect, format);
+ ref:
+ https://maywork.net/computer/csharp_convert_to_format32bppargb/
+ https://www.webdevqa.jp.net/ja/c%23/c%EF%BC%83%E3%81%A7%E3%81%AE%E3%83%93%E3%83%83%E3%83%88%E3%83%9E%E3%83%83%E3%83%97pixelformats%E3%81%AE%E5%A4%89%E6%8F%9B/968763414/
+ https://gist.github.com/nissuk/888601/ee0943dd0d35dc9c6b47358b9e1a89af8cc9898b
+ https://stackoverflow.com/questions/28448474/render-pdf-page-to-bitmap-using-pdfium
+ https://qiita.com/Nuits/items/4a2fbc0f4e8583bd5531
+ PixelFormat dump https://maywork.net/computer/csharp_convert_to_format32bppargb/
+ */
+ }
+ ///
+ /// pdfのページを画像に出力する
+ ///
+ /// PdfDocument
+ /// ページ番号
+ /// 出力ファイル名
+ /// 正常:0
+ public static int RenderPage(
+ PdfiumViewer.PdfDocument docG,
+ int index,
+ string otPath,
+ ref string otHashCode,
+ // Optional:
+ RenderConditionParams pm = null,
+ bool bSaveImage = true,// ファイル保存有無。ハッシュ値計算のみのときにfalseにする
+ bool bHash = false
+ ){
+ var bDump = Check.bDump;
+ var bThDump = Check.bThDump;
+ if ( pm == null )
+ pm = new RenderConditionParams();
+
+ using (var memStrm = new MemoryStream()) {
+ PdfRenderFlags flg = (PdfRenderFlags.ForPrinting | PdfRenderFlags.CorrectFromDpi);
+ using(var img = RenderRgbBitMap(docG, index, (float)pm.Dpi, (float)pm.Dpi, flg) ){
+ img.Save(memStrm, System.Drawing.Imaging.ImageFormat.Bmp);
+ /*
+ // ImageFormatJPEGの時
+ dpi = 350,TIFF
+ 2691.777 = 44分51.777秒
+ 10335ファイルで20G
+ ーー
+ ImageFormat.Bmpに変更
+ result=0,time=2850.955[sec]
+ 10335ファイルで12.2G
+ ーー
+ pagePara=2
+ para=4
+ ImageFormat.Bmp
+ result=0,time=2824.644[sec]
+
+ 10335ファイルで12.2G
+ ーー
+ pagePara=2
+ para=8
+ ImageFormat.Bmp
+ result=0,time=2828.631[sec] => 47分
+ 10335ファイルで12.2G
+
+ */
+ // [注意] PDFuim用Flush()必要しないとだめです
+ memStrm.Flush();
+ img.Dispose();
+ }
+ var bmp = new System.Drawing.Bitmap(memStrm);
+ if (bDump) {
+ echo($"bmp=w:{bmp.Size.Width},h:{bmp.Size.Height}");
+ }
+ //Console.WriteLine($"OrgReso({bmp.HorizontalResolution},{bmp.VerticalResolution})");
+ if (bHash) {
+ var h = GetHashValue(memStrm);
+ //Console.WriteLine($"HashString:{h}");
+ otHashCode = h;
+ }
+ var th = Thread.CurrentThread.ManagedThreadId;
+ if (!bSaveImage) { //ファイル保存しない.ハッシュ計算のみ
+ if (bThDump) {
+ echo($"ot[th{th},{(index + 1)}/{docG.PageCount}]=,{Path.GetFileName(otPath)},hash:{otHashCode}");
+ }
+ return 0;
+ }
+ // image encodeを作成
+ var imEnc = new UtImage.Enc(pm.ImageType) { JpegQuality = pm.JpegQ };// Init membrers.
+ bmp.SetResolution((float)pm.Dpi, (float)pm.Dpi); // imageクラスでもPropertyTagX(Y)Resolutionで設定できそう
+ if (bThDump) {
+ echo($"ot[th{th},{(index + 1)}/{docG.PageCount}]={Path.GetFileName(otPath)}({Directory.GetParent(otPath)})");
+ }
+ //
+ imEnc.SaveImage(bmp, otPath);
+ //
+ bmp.Dispose();
+ //memStrm.Seek(0);
+ memStrm.Dispose();
+ }
+ return 0;//success
+ }
+
+ // Sync version( 比較モード(/FC ) **************************************************************************************
+
+ ///
+ /// レンダリング(比較モード)
+ ///
+ ///
+ ///
+ ///
+ /// レンダリング条件
+ ///
+ ///
+ /// 未使用常にtrueで利用する
+ ///
+ ///
+ /// 一致した場合は0、不一致の場合は!0を返却する
+ public static int RenderPdfDocCompare(
+ string pdfPath,
+ string refPdfPath,
+ string resultDataPath,
+ string inDir = "",
+ RenderConditionParams pm = null,
+ string inPageRange = "1-*",
+ string inMode = "pageBitMap",
+ bool bHash = false,
+ UtHash.HashData inHashDataTgt = null,
+ UtHash.HashData inHashDataRef = null,
+ bool bPDFium = false,
+ int nPageThreadNum = 4,
+ bool bVerify = false
+ // 比較結果を返す ページ番号とメッセージ
+ ) {
+ var bDump = Check.bDump;
+ var bThDump = Check.bThDump;
+
+ if(bDump)
+ echo("RenderPdfDocCompare & diff Image! & non Para");
+ if (pm == null)
+ pm = new RenderConditionParams();
+ var otDir = inDir;
+ var otBaseName = Path.GetFileName(pdfPath);//xxx.pdf
+ var otExtention = pm.ImageType.ToLower();
+ if (otDir == "") {
+ Console.WriteLine("Error:No output dir");
+ return -1;// error
+ }
+ if (!Directory.Exists(otDir))
+ Directory.CreateDirectory(otDir);
+
+ // PDFファイルを読み込む by PDFium.
+ var docG = PdfiumViewer.PdfDocument.Load(pdfPath);
+ var docGRef = PdfiumViewer.PdfDocument.Load(refPdfPath);
+
+ var pageCount = docG.PageCount;
+
+ uint[] rNo = makePageRange(inPageRange, (uint)pageCount);
+
+ int nRetAll = 0;// トータルの不一致ページ数
+
+ // ハッシュ情報の確認
+ /*
+ - 双方のハッシュ値が存在するか確認
+ - tgtののみなら、refのrender
+ - refのみなら、tgtのみrender
+ - とう感じ
+ */
+ UtHash.HashFile tgtHf=null;
+ if (inHashDataTgt!=null) {
+ var f = Path.GetFileName(pdfPath);
+ if (inHashDataTgt.Files.ContainsKey(f)) {
+ tgtHf = inHashDataTgt.Files[f];
+ }
+ }
+ UtHash.HashFile refHf = null;
+ if (inHashDataRef != null) {
+ var f = Path.GetFileName(refPdfPath);
+ if ( inHashDataRef.Files.ContainsKey(f) ) {
+ refHf = inHashDataRef.Files[f];
+ }
+ }
+ Console.WriteLine($@"UsingHashFile:tgt({(tgtHf==null?0:1)}),ref({(refHf == null ? 0 : 1)})");
+ // tgtHf,refHfを構築完了
+
+ var fcResultMsg = new SortedDictionary();// 比較結果を返す ページ番号とメッセージ
+
+ /////並行処理するスレッド数を指定(2-4ぐらいが穏便な数値)
+ ParallelOptions options = new ParallelOptions { MaxDegreeOfParallelism = 4 };
+ Parallel.ForEach(rNo, options, i => {
+ MemoryStream tgtStrm = null, refStrm = null;
+ string tgtHash = "", refHash = "";
+ // ターゲット レンダラ定義
+ MemoryStream RendPageTGT() {
+ var strm = RenderPageStream(docG:docG,index:(int)i-1, pm:pm);
+ return strm;
+ }
+ // リファレンス レンダラ定義
+ MemoryStream RendPageREF() {
+ var strm = RenderPageStream(docG:docGRef, index:(int)i-1, pm:pm);
+ return strm;
+ }
+ // ターゲット処理
+ if (tgtHf == null) {
+ tgtStrm = RendPageTGT();// Render
+ tgtHash = GetHashValue(tgtStrm);
+ } else {
+ lock (inHashDataTgt) {
+ tgtHash = tgtHf.GetHashValue((int)(i - 1));
+ }
+ }
+ // リファレンス処理
+ if (refHf == null) {
+ refStrm = RendPageREF();// Render
+ refHash = GetHashValue(refStrm);
+ } else {
+ lock (inHashDataRef) {
+ refHash = refHf.GetHashValue((int)(i - 1));
+ }
+ }
+ // compare stream;
+ // int nRet = 1;//異なる(diff).
+ var dateStr = DateTime.Now.ToString();
+ //Console.WriteLine($@"tgt={i}:{tgtHash}");
+ //Console.WriteLine($@"ref={i}:{refHash}");
+ //bool bVerify=true;
+ if (bVerify){
+ String p=null;
+ p=Path.Combine( otDir,"TGT");
+ if (!Directory.Exists(p)) Directory.CreateDirectory(p);
+ p=Path.Combine( otDir,"REF");
+ if (!Directory.Exists(p)) Directory.CreateDirectory(p);
+ }
+ if (tgtHash != refHash) {
+ // no match
+ // nRet = 1;
+ nRetAll++;// = nRet;//全体の不一致設定
+ echo($"{i}:NotMatch:{i},tgt[{tgtHash}],ref[{refHash}]");
+ //2020年3月2日 15:23:55:[@Difference]:.diff.jpg
+ lock (fcResultMsg) {
+ fcResultMsg.Add((int)i, $@"{dateStr}:[@Difference]:{Path.GetFileName(pdfPath)}.{i}");
+ }
+ echo("***** save diff image");
+ // diffのjpegを書き出してみる(tgt)
+ if (tgtStrm == null)
+ tgtStrm = RendPageTGT();
+ var otPath = bVerify ?
+ Path.Combine(otDir, "TGT",$"{otBaseName}.{i}.{otExtention}")
+ :
+ Path.Combine(otDir, $"{otBaseName}.{i}.tgt.{otExtention}");
+ using(var bmp = new System.Drawing.Bitmap(tgtStrm)){
+ var imEnc = new UtImage.Enc(pm.ImageType) { JpegQuality = pm.JpegQ };// Init membrers.
+ bmp.SetResolution((float)pm.Dpi, (float)pm.Dpi);
+ imEnc.SaveImage(bmp, otPath);
+ bmp.Dispose();
+ }
+ // diffのjpegを書き出してみる(ref)
+ if (refStrm == null)
+ refStrm = RendPageREF();
+ otPath = bVerify ?
+ Path.Combine(otDir, "REF",$"{otBaseName}.{i}.{otExtention}")
+ :
+ Path.Combine(otDir, $"{otBaseName}.{i}.ref.{otExtention}");
+
+ using(var bmp = new System.Drawing.Bitmap(refStrm)){
+ var imEnc = new UtImage.Enc(pm.ImageType) { JpegQuality = pm.JpegQ };// Init membrers.
+ bmp.SetResolution((float)pm.Dpi, (float)pm.Dpi);
+ imEnc.SaveImage(bmp, otPath);
+ bmp.Dispose();
+ }
+ } else {
+ // match
+ //nRet = 0;
+ lock (fcResultMsg) {
+ fcResultMsg.Add((int)i, $@"{dateStr}:[OK]:{Path.GetFileName(pdfPath)}.{i}");
+ // Ex 2019年12月18日 19:31:38:[OK]:fname.pdf.1.png
+ }
+ }
+ // dispose:
+ if (refStrm != null) refStrm.Dispose();
+ if (tgtStrm != null) tgtStrm.Dispose();
+ });
+ if ( docG != null)
+ docG.Dispose();
+ if (docGRef != null)
+ docGRef.Dispose();
+
+ // まとめて表示
+ foreach ( var v in fcResultMsg){
+ Console.WriteLine("@CMP@"+v.Value);
+ }
+ if (resultDataPath != "") {
+ // まとめて書き出し
+ var resCont =($"{pdfPath}\n");
+ foreach (var v in fcResultMsg) {
+ resCont += ("@CMP@" + v.Value) + "\n";
+ }
+ File.AppendAllText(resultDataPath, resCont);
+ }
+ return nRetAll;// success. 不一致数を返す
+ }
+
+ ///
+ /// 比較モード
+ ///
+ /// ドキュメントハンドル
+ /// ページ番号(0-)
+ /// 正常:0
+ public static MemoryStream RenderPageStream(
+ PdfiumViewer.PdfDocument docG,
+ int index,
+ // Optional:
+ RenderConditionParams pm = null
+ ) {
+ var memStrm = new MemoryStream();
+
+ var bDump = Check.bDump;
+ var bThDump = Check.bThDump;
+ if (pm == null)
+ pm = new RenderConditionParams();
+ PdfRenderFlags flg = (PdfRenderFlags.ForPrinting | PdfRenderFlags.CorrectFromDpi);
+ System.Drawing.Image img =RenderRgbBitMap(docG, index, (float)pm.Dpi, (float)pm.Dpi, flg);
+ img.Save(memStrm, System.Drawing.Imaging.ImageFormat.Bmp);
+ //img.Save(memStrm, System.Drawing.Imaging.ImageFormat.Jpeg);
+ // [注意] PDFuim用にFlush()必要
+ memStrm.Flush();
+ img.Dispose();
+ return memStrm;
+ }
+
+ ///
+ /// ページ1から始まるページ範囲の配列を返す
+ ///
+ ///
+ ///
+ ///
+ private static uint[] makePageRange(string pageRange = "1", uint endPage = 100) {
+ var range = new SortedSet();
+ var vec = pageRange.Split(',');//カンマで分割
+ var reRange = new Regex($@"([\d]+)[\s]*\-[\s]*([\d]*|\*)");// n-m or n- or n-*
+ var reOne = new Regex($@"([\d]+)"); // n
+ bool isRange(uint x) => (0 < x && x <= endPage); // 範囲チェック
+ void swap(ref T a, ref T b) { T tmp = a; a = b; b = tmp; }
+ foreach (var s in vec) {
+ //範囲指定
+ var m = reRange.Match(s);
+ if (m.Success) {
+ uint st = uint.Parse(m.Groups[1].Value);
+ uint en = 0;
+ if (!isRange(st))
+ continue;//開始値が範囲外で無効
+ uint.TryParse(m.Groups[2].Value, out en);
+ if (!isRange(en))
+ en = endPage;// 最終値が無効なため最終ページ
+ if (st > en) swap(ref st, ref en);
+ // 範囲登録
+ foreach (var g in Enumerable.Range((int)st, (int)(en - st + 1)))
+ range.Add((uint)g);
+ continue;
+ }
+ //ページ指定
+ m = reOne.Match(s);
+ if (m.Success) {
+ var n = uint.Parse(m.Groups[1].Value);
+ if (isRange(n))
+ range.Add(n);
+ }
+ }
+ return range.ToArray();
+ }
+ public static string GetHashValue(MemoryStream strm) {
+ var alg = new SHA256CryptoServiceProvider();
+ strm.Seek(0, SeekOrigin.Begin);
+ var bin = alg.ComputeHash(strm);
+ alg.Clear();
+ // バイト配列をUTF8エンコードで文字列化
+ var hashedText = new StringBuilder();
+ foreach (var b in bin) {
+ hashedText.AppendFormat("{0:X2}", b);
+ }
+ return hashedText.ToString();
+ }
+ }
+
+ ///
+ /// ファイルのハッシュ値のIO
+ ///
+ namespace UtHash
+ // https://qiita.com/Akasaki/items/dee137b24aea4b7e2bcb
+ // http://mokake.hatenablog.com/entry/2017/09/28/234433
+ // https://lifetime-engineer.com/csharp-create-json-indent/
+ // DataMemberでOrderを指定することで順序を確定。
+ // JsonReaderWriterFactory.CreateJsonWriterでindent=trueにすることでjsonの可視性向上
+ {
+ [DataContract]
+ public class HashHead
+ {
+ public bool Dirty = false; // 書き換えられたらTrue 保存対象外
+ [DataMember(Order = 0)]
+ public string HashBaseName; // 保存時にベース名を設定する
+ [DataMember(Order=1)]
+ public string HashFilePath;
+ [DataMember(Order=2)]
+ public string Dpi;
+ [DataMember(Order=3)]
+ public string Box;
+ [DataMember(Order=4)]
+ public string ImType;
+ [DataMember(Order=5)]
+ public string JQ;
+ // メソッド
+ public HashHead() {
+ SetRenderInfo("0", "Bx", "IMx", "xx");
+ }
+ public HashHead SetRenderInfo(string dpi, string box, string imType, string jq) {
+ Dpi = dpi; Box = box; ImType = imType; JQ = jq;
+
+ return this;
+ }
+ public string GetRenderInfoStr() { return $@"{Dpi}_{Box}_{ImType}_{JQ}"; }
+
+ }
+ [DataContract]
+ public class HashFile {
+ [DataMember(Order=0)]
+ public DateTime UpdateTime; // ファイルの更新日
+ [DataMember(Order=1)]
+ public string UpdateTimeStr; // ファイルの更新日(Json目視用)
+ [DataMember(Order=2)]
+ public List PageHashCode = new List(); // ページ毎のハッシュ値の配列
+ // メソッド
+ public string GetHashValue(int i) {
+ // 範囲チェック
+ if ( !(0 <= i && i < PageHashCode.Count) ){
+ return "";
+ }
+ return PageHashCode[i];
+ }
+ }
+ [DataContract]
+ public class HashData {
+ [DataMember(Order=0)]
+ public HashHead Head = new HashHead();
+ [DataMember(Order=1)]
+ public SortedDictionary Files = new SortedDictionary();
+ // temp value
+ string loadedPath;
+ // メソッド
+ static public string GetHashFileName(string baseName, string dpi, string box, string imType, string jq) {
+ var h = new HashHead();
+ var f = baseName + "_" + h.SetRenderInfo(dpi, box, imType, jq).GetRenderInfoStr() + ".json";
+ return f;
+ }
+ static public HashData load(string pdfPath/*or Dir*/, string baseName, string dpi, string box, string imType, string jq, ref string otPath) {
+ var hashPath = "";
+ if (Path.GetExtension(pdfPath).ToLower() == ".pdf") {
+ var paPath = Directory.GetParent(Path.GetFullPath(pdfPath)).ToString();
+ var hashFName = GetHashFileName(baseName, dpi, box, imType, jq);
+ hashPath = Path.Combine(paPath, hashFName);
+ } else if (File.GetAttributes(pdfPath).HasFlag(FileAttributes.Directory)) {
+ var hashFName = GetHashFileName(baseName, dpi, box, imType, jq);
+ hashPath = Path.Combine(pdfPath, hashFName);
+ }
+ //Console.WriteLine($@"hashPath={hashPath}");
+ if (otPath != null) {
+ otPath = hashPath;
+ }
+ var hd = HashData.load(hashPath); //ファイルの存在はこっちに任す
+ return hd;
+ }
+ static public HashData load(string hashPath) {
+ if (!File.Exists(hashPath)) {
+ echo("not find load hash data file");
+ return null;
+ }
+ DataContractJsonSerializer serializer = new DataContractJsonSerializer(typeof(HashData));
+ using (var rd = new FileStream(hashPath, FileMode.Open,FileAccess.Read,FileShare.ReadWrite)) {
+ var a = (HashData)serializer.ReadObject(rd);
+ a.Head.Dirty = false;// clear
+ a.loadedPath=hashPath;
+ return a;
+ }
+ }
+ static public HashData loadFromString(string s) {
+ DataContractJsonSerializer serializer = new DataContractJsonSerializer(typeof(HashData));
+ using (var rd = new MemoryStream( Encoding.UTF8.GetBytes(s) )) {
+ var a = (HashData)serializer.ReadObject(rd);
+ a.Head.Dirty = false;// clear
+ return a;
+ }
+ }
+ public override string ToString() {
+ //文字列に書き出す
+ using (var m = new MemoryStream())
+ using (var writer = JsonReaderWriterFactory.CreateJsonWriter(m, Encoding.UTF8, true, true, " ")) {
+ var serializer = new DataContractJsonSerializer(typeof(HashData));
+ serializer.WriteObject(writer, this);
+ var x= Encoding.UTF8.GetString(m.ToArray());// 文字列に変換
+ return x;
+ }
+ }
+ public bool save(string path) {
+ // JSONに変換するデータを作る。
+ // 再保存の抑制
+ //this.Head.Dirtyがfalseなら更新されていないことになる。
+ //またかつ保存パスが同じ this.Head.HashFilePath = path;
+
+ echo($@"HashData.save:{path}");
+ this.Head.HashFilePath = Path.GetFullPath(path);
+ this.Head.HashBaseName = this.Head.GetRenderInfoStr();
+ try {
+ using (var fs = new FileStream(path, FileMode.Create))
+ using (var writer = JsonReaderWriterFactory.CreateJsonWriter(fs, Encoding.UTF8, true, true, " ")) {
+ var serializer = new DataContractJsonSerializer(typeof(HashData));
+ serializer.WriteObject(writer, this);
+ return true;
+ }
+ } catch (Exception e) {
+ Console.WriteLine($@"error Save json anything error:{e}");
+ return false;
+ }
+ }
+ public bool SetFile(string pathPDF, int pageNum) {
+ var fi = new FileInfo(pathPDF);
+ var fName = fi.Name;
+ HashFile hf;
+ if (!Files.ContainsKey(fName)) {
+ this.Head.Dirty = true;// 変更あり
+ hf = new HashFile() {
+ UpdateTime = fi.LastWriteTimeUtc,
+ UpdateTimeStr= fi.LastWriteTime.ToString(),
+ PageHashCode = new List(pageNum)
+ };
+ // Console.WriteLine($@"update={hf.UpdateTime}");
+ for (var i = 0; i < pageNum; i++) {
+ hf.PageHashCode.Add("");
+ }
+ Files.Add(fName,hf);
+ } else {
+ // 既に存在、pageNumがかわらないことだけチェック
+ var pageNumOrg = Files[fName].PageHashCode.Count;
+ if (Files[fName].UpdateTime.ToString() != fi.LastWriteTimeUtc.ToString()) {
+ // 日付が一致しなければ更新(ToStringで比較しないとだめです
+ this.Head.Dirty = true;// 変更あり
+ Files[fName].UpdateTime = fi.LastWriteTimeUtc;
+ Files[fName].UpdateTimeStr = fi.LastWriteTime.ToString();
+ Files[fName].PageHashCode = new List(pageNum);
+ for (var i = 0; i < pageNum; i++) {
+ Files[fName].PageHashCode.Add("");
+ }
+ }
+ // ありえないけど、同一日付でページ数が異なる
+ if (pageNumOrg != pageNum) {
+ Console.WriteLine($@"Mismatch pageNum:setP:{pageNum},DbP:{Files[fName].PageHashCode.Count}");
+ this.Head.Dirty = true;// 変更あり
+ return false; // Page数が一致しない
+ }
+ }
+ return true;
+ }
+ public bool AddHashCode(string pathPDF, int pageNo/*1開始*/, string hashCode) {
+ //Console.WriteLine("call addHashCode:" + pathPDF + ":" + pageNo);
+ var fi = new FileInfo(pathPDF);
+ var fName = fi.Name;
+ var n = pageNo - 1;//0始まり
+ if (!Files.ContainsKey(fName)) {
+ this.Head.Dirty = true;// 変更あり
+ return false;
+ }
+ if (Files[fName].PageHashCode[n] != hashCode) {
+ this.Head.Dirty = true;// 変更あり
+ Files[fName].PageHashCode[n] = hashCode;
+ }
+ return true;
+ }
+ public string GetHashCode(string pathPDF, int pageNo/*1開始*/) {
+ var fi = new FileInfo(pathPDF);
+ var fName = fi.Name;
+ var n = pageNo - 1;//0始まり
+ if (!Files.ContainsKey(fName)) {
+ return "";
+ }
+ var v = Files[fName].PageHashCode;
+ return (n < v.Count) ? v[n] : "";
+ }
+ public bool IsValidHashFile(string[] fLst){
+ // ハッシュファイルとPDFファイルの整合性確認。更新日付のみ確認。正常:true、異常:false
+ //
+ if (fLst.Count() != Files.Count )
+ return false; // pdfファイル数の不一致
+
+ foreach (var f in Files) {
+ var fPath=Path.Combine( Path.GetDirectoryName(loadedPath) ,f.Key);
+ var fi = new FileInfo(fPath);
+ // Console.WriteLine(fPath+": "+f.Value.UpdateTimeStr);
+ // Console.WriteLine(fPath+"->"+fi.LastWriteTime.ToString());
+ if ( f.Value.UpdateTimeStr != fi.LastWriteTime.ToString() )
+ return false; // 1つでも異なっていたらすべてのファイルを異常とする
+ }
+ return true;//正常
+ }
+ }
+
+ }
+
+ namespace UtImage // Image保存のユーティリティー
+ {
+ // ref:https://water2litter.net/rum/post/cs_pdf_wpf/
+ //using EncType = System.Drawing.Imaging.Encoder;// 別名
+ //using EncParamType = System.Drawing.Imaging.EncoderParameter;//別名
+
+ ///
+ /// BitMapのSaveに使うImage Encodeパラメータの設定値の生成と 画像保存
+ /// var enc = new UtImage.Enc("jpeg");
+ /// //enc.SetImageType("PNG"); // 後で変更できる
+ /// //enc.JJpegQuality = 100; JpegのQuarty変更
+ /// enc.SaveImage(BitMap,path);
+ /// // enc.SaveImage(BitMap,path,"PNG");
+ ///
+ ///
+ public class Enc
+ {
+ // SEE:https://dobon.net/vb/dotnet/graphics/encoderparameters.html
+ public Enc(string imageType = "jpeg") { SetImageType(imageType); }
+ public long JpegQuality { get; set; } = 91;// 指定しないときの値は91と一致する
+ private string ImageType = "jpeg";
+
+ public Enc SetImageType(string imageType) {
+ imageType = imageType.ToLower();
+ if (imageType == "jpeg" || imageType == "jpg") {
+ ImageType = "jpeg";
+ } else if (imageType == "png") {
+ ImageType = "png";
+ } else if (imageType == "tif" || imageType == "tiff") {
+ ImageType = "tiff";
+ } else {
+ ImageType = imageType;// 小文字を設定
+ }
+ return this;
+ }
+ public int SaveImage(System.Drawing.Bitmap bitmap, string path, string imageType = null) {
+ var p = GetParams();
+ string imType = imageType?.ToLower();
+ if (p != null) {
+ bitmap.Save(path, GetInfo(imType), GetParams());// imageTypeがnullならSetImageType()のものが使われる
+ } else {// パラメータが空の場合
+ var imageFormat = new System.Drawing.Imaging.ImageFormat(GetInfo(imType).FormatID);
+ bitmap.Save(path, imageFormat);
+ }
+ return 0;
+ }
+ // Image版
+ public int SaveImage(System.Drawing.Image image, string path, string imageType = null)
+ {
+ var p = GetParams();
+ string imType = imageType?.ToLower();
+ if (p != null) {
+ image.Save(path, GetInfo(imType), GetParams());// imageTypeがnullならSetImageType()のものが使われる
+ }
+ else
+ {// パラメータが空の場合
+ var imageFormat = new System.Drawing.Imaging.ImageFormat(GetInfo(imType).FormatID);
+ image.Save(path, imageFormat);
+ }
+ return 0;
+ }
+ ///
+ /// サポートするEncodeパラメータ一覧
+ /// 現状Errorが発生して取得できない
+ ///
+ /// 対象のBitMap
+ public static void GetSupportedParameters(System.Drawing.Bitmap bitmap1 = null) {
+ // https://docs.microsoft.com/ja-jp/dotnet/framework/winforms/advanced/how-to-determine-the-parameters-supported-by-an-encoder
+ try {
+ if (bitmap1 == null) {
+ bitmap1 = new System.Drawing.Bitmap(100, 100);
+ var destBitmapData = bitmap1.LockBits(
+ new System.Drawing.Rectangle(0, 0, 100, 100),
+ System.Drawing.Imaging.ImageLockMode.ReadOnly,
+ bitmap1.PixelFormat
+ );
+ bitmap1.UnlockBits(destBitmapData);
+ }
+ var jpgEncoder = GetEncoderInfo(System.Drawing.Imaging.ImageFormat.Tiff);
+ var paramList = bitmap1.GetEncoderParameterList(jpgEncoder.Clsid);
+ var encParams = paramList.Param;
+ for (int i = 0; i < encParams.Length; i++) {
+ Console.WriteLine("Param " + i + " holds " + encParams[i].NumberOfValues +
+ " items of type " +
+ encParams[i].ValueType + "\r\n" + "Guid category: " + encParams[i].Encoder.Guid + "\r\n");
+ }
+ } catch (Exception e) {
+ Console.WriteLine($"Ignore error ={e.ToString()}");
+ }
+ }
+ // inner method.
+ private System.Drawing.Imaging.EncoderParameters GetParams() {
+ var encList = new List();
+ //encList.Clear();
+ // 今はJPEGパラメータだけ
+ encList.Add(new System.Drawing.Imaging.EncoderParameter(System.Drawing.Imaging.Encoder.Quality, JpegQuality));
+ //...
+ // 結合
+ var eps = new System.Drawing.Imaging.EncoderParameters(encList.Count);
+ for (var i = 0; i < encList.Count; i++)
+ eps.Param[i] = encList[i];
+ return eps;
+ }
+ private System.Drawing.Imaging.ImageCodecInfo GetInfo(string it = null) {
+ if (it == null) {// SetImageType()で指定したものを使う
+ it = ImageType;
+ }
+ return GetEncoderInfo($"image/{it}");
+ }
+ //ImageFormatで指定されたImageCodecInfoを探して返す
+ private static System.Drawing.Imaging.ImageCodecInfo GetEncoderInfo(System.Drawing.Imaging.ImageFormat f) {
+ var encs = System.Drawing.Imaging.ImageCodecInfo.GetImageEncoders();
+ foreach (var enc in encs) {
+ if (enc.FormatID == f.Guid)
+ return enc;
+ }
+ return null;
+ }
+ private static System.Drawing.Imaging.ImageCodecInfo GetEncoderInfo(string mineType) {
+ var encs = System.Drawing.Imaging.ImageCodecInfo.GetImageEncoders();
+ //指定されたMimeTypeを探して見つかれば返す
+ foreach (System.Drawing.Imaging.ImageCodecInfo enc in encs) {
+ if (enc.MimeType == mineType)
+ return enc;
+ }
+ return null;
+ }
+ }
+
+ }
+}
diff --git a/cli/CSRender/CSRender.csproj b/cli/CSRender/CSRender.csproj
new file mode 100644
index 0000000000000000000000000000000000000000..0ae266d7bc8d1fc1f4700ce7c74f32ff1a0eb6ab
--- /dev/null
+++ b/cli/CSRender/CSRender.csproj
@@ -0,0 +1,128 @@
+
+
+
+
+
+ Debug
+ AnyCPU
+ {FD99EFAA-2479-4E3B-BD1A-5B785288E3B3}
+ Exe
+ CSRender
+ CSRender
+ v4.6.1
+ 512
+ true
+ true
+
+ false
+ publish\
+ true
+ Disk
+ false
+ Foreground
+ 7
+ Days
+ false
+ false
+ true
+ 0
+ 1.0.0.%2a
+ false
+ true
+
+
+
+
+ AnyCPU
+ true
+ full
+ false
+ ..\bin\Debug\
+ DEBUG;TRACE
+ prompt
+ 4
+ false
+
+
+ x64
+ pdbonly
+ true
+ ..\bin\Release\
+ TRACE
+ prompt
+ 4
+ false
+
+
+
+
+ app.manifest
+
+
+
+ ..\packages\PdfiumViewer.2.13.0.0\lib\net20\PdfiumViewer.dll
+
+
+
+
+
+
+
+
+ False
+ c:\Program Files (x86)\Reference Assemblies\Microsoft\Framework\.NETCore\v4.5\System.Runtime.WindowsRuntime.dll
+ False
+
+
+
+
+
+
+ False
+ C:\Program Files (x86)\Windows Kits\8.1\References\CommonConfiguration\Neutral\Annotated\Windows.winmd
+ False
+
+
+
+
+
+
+
+
+ True
+ True
+ Settings.settings
+
+
+
+
+
+ Designer
+
+
+ Designer
+
+
+ SettingsSingleFileGenerator
+ Settings.Designer.cs
+
+
+
+
+
+ False
+ .NET Framework 3.5 SP1
+ false
+
+
+
+
+
+
+
+
+ このプロジェクトは、このコンピューター上にない NuGet パッケージを参照しています。それらのパッケージをダウンロードするには、[NuGet パッケージの復元] を使用します。詳細については、http://go.microsoft.com/fwlink/?LinkID=322105 を参照してください。見つからないファイルは {0} です。
+
+
+
+
\ No newline at end of file
diff --git a/cli/CSRender/CSRender2019.csproj b/cli/CSRender/CSRender2019.csproj
new file mode 100644
index 0000000000000000000000000000000000000000..f35f288a5349c09e06aaba3010270d63c8305b16
--- /dev/null
+++ b/cli/CSRender/CSRender2019.csproj
@@ -0,0 +1,121 @@
+
+
+
+
+ Debug
+ AnyCPU
+ {FD99EFAA-2479-4E3B-BD1A-5B785288E3B3}
+ Exe
+ CSRender
+ CSRender
+ v4.6.1
+ 512
+ true
+ true
+
+ false
+ publish\
+ true
+ Disk
+ false
+ Foreground
+ 7
+ Days
+ false
+ false
+ true
+ 0
+ 1.0.0.%2a
+ false
+ true
+
+
+
+
+ AnyCPU
+ true
+ full
+ false
+ ..\bin\Debug\
+ DEBUG;TRACE
+ prompt
+ 4
+ false
+
+
+ AnyCPU
+ pdbonly
+ true
+ ..\bin\Release\
+ TRACE
+ prompt
+ 4
+ false
+
+
+
+
+ app.manifest
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ True
+ True
+ Settings.settings
+
+
+
+
+
+ Designer
+
+
+ SettingsSingleFileGenerator
+ Settings.Designer.cs
+
+
+
+
+
+ False
+ .NET Framework 3.5 SP1
+ false
+
+
+
+
+
+
+
+ 10.0.19041.1
+
+
+ 2.13.0
+
+
+ 2018.4.8.256
+
+
+ 5.0.0-preview.5.20278.1
+
+
+
+
\ No newline at end of file
diff --git a/cli/CSRender/DynamicJson.cs b/cli/CSRender/DynamicJson.cs
new file mode 100644
index 0000000000000000000000000000000000000000..89252c4dd830b76d94655bd6ca37e1cef21af8a1
--- /dev/null
+++ b/cli/CSRender/DynamicJson.cs
@@ -0,0 +1,431 @@
+/*--------------------------------------------------------------------------
+* DynamicJson
+* ver 1.2.0.0 (May. 21th, 2010)
+*
+* created and maintained by neuecc
+* licensed under Microsoft Public License(Ms-PL)
+* http://neue.cc/
+* http://dynamicjson.codeplex.com/
+*--------------------------------------------------------------------------*/
+using System;
+using System.Collections;
+using System.Collections.Generic;
+using System.Diagnostics;
+using System.Dynamic;
+using System.IO;
+using System.Linq;
+using System.Reflection;
+using System.Runtime.Serialization.Json;
+using System.Text;
+using System.Xml;
+using System.Xml.Linq;
+
+namespace Codeplex.Data
+{
+ public class DynamicJson : DynamicObject
+ {
+ private enum JsonType
+ {
+ @string, number, boolean, @object, array, @null
+ }
+
+ // public static methods
+
+ /// from JsonSring to DynamicJson
+ public static dynamic Parse(string json)
+ {
+ return Parse(json, Encoding.Unicode);
+ }
+
+ /// from JsonSring to DynamicJson
+ public static dynamic Parse(string json, Encoding encoding)
+ {
+ using (var reader = JsonReaderWriterFactory.CreateJsonReader(encoding.GetBytes(json), XmlDictionaryReaderQuotas.Max))
+ {
+ return ToValue(XElement.Load(reader));
+ }
+ }
+
+ /// from JsonSringStream to DynamicJson
+ public static dynamic Parse(Stream stream)
+ {
+ using (var reader = JsonReaderWriterFactory.CreateJsonReader(stream, XmlDictionaryReaderQuotas.Max))
+ {
+ return ToValue(XElement.Load(reader));
+ }
+ }
+
+ /// from JsonSringStream to DynamicJson
+ public static dynamic Parse(Stream stream, Encoding encoding)
+ {
+ using (var reader = JsonReaderWriterFactory.CreateJsonReader(stream, encoding, XmlDictionaryReaderQuotas.Max, _ => { }))
+ {
+ return ToValue(XElement.Load(reader));
+ }
+ }
+
+ /// create JsonSring from primitive or IEnumerable or Object({public property name:property value})
+ public static string Serialize(object obj)
+ {
+ return CreateJsonString(new XStreamingElement("root", CreateTypeAttr(GetJsonType(obj)), CreateJsonNode(obj)));
+ }
+
+ // private static methods
+
+ private static dynamic ToValue(XElement element)
+ {
+ var type = (JsonType)Enum.Parse(typeof(JsonType), element.Attribute("type").Value);
+ switch (type)
+ {
+ case JsonType.boolean:
+ return (bool)element;
+ case JsonType.number:
+ return (double)element;
+ case JsonType.@string:
+ return (string)element;
+ case JsonType.@object:
+ case JsonType.array:
+ return new DynamicJson(element, type);
+ case JsonType.@null:
+ default:
+ return null;
+ }
+ }
+
+ private static JsonType GetJsonType(object obj)
+ {
+ if (obj == null) return JsonType.@null;
+
+ switch (Type.GetTypeCode(obj.GetType()))
+ {
+ case TypeCode.Boolean:
+ return JsonType.boolean;
+ case TypeCode.String:
+ case TypeCode.Char:
+ case TypeCode.DateTime:
+ return JsonType.@string;
+ case TypeCode.Int16:
+ case TypeCode.Int32:
+ case TypeCode.Int64:
+ case TypeCode.UInt16:
+ case TypeCode.UInt32:
+ case TypeCode.UInt64:
+ case TypeCode.Single:
+ case TypeCode.Double:
+ case TypeCode.Decimal:
+ case TypeCode.SByte:
+ case TypeCode.Byte:
+ return JsonType.number;
+ case TypeCode.Object:
+ return (obj is IEnumerable) ? JsonType.array : JsonType.@object;
+ case TypeCode.DBNull:
+ case TypeCode.Empty:
+ default:
+ return JsonType.@null;
+ }
+ }
+
+ private static XAttribute CreateTypeAttr(JsonType type)
+ {
+ return new XAttribute("type", type.ToString());
+ }
+
+ private static object CreateJsonNode(object obj)
+ {
+ var type = GetJsonType(obj);
+ switch (type)
+ {
+ case JsonType.@string:
+ case JsonType.number:
+ return obj;
+ case JsonType.boolean:
+ return obj.ToString().ToLower();
+ case JsonType.@object:
+ return CreateXObject(obj);
+ case JsonType.array:
+ return CreateXArray(obj as IEnumerable);
+ case JsonType.@null:
+ default:
+ return null;
+ }
+ }
+
+ private static IEnumerable CreateXArray(T obj) where T : IEnumerable
+ {
+ return obj.Cast