using System; using System.IO; using System.Collections.Generic; using System.Linq; //using System.Text; //using System.Threading; //using System.Threading.Tasks; //using System.Windows; //using System.Windows.Media.Imaging; using System.Reflection; // Assembly. using System.Text.RegularExpressions; using System.Threading; using System.Threading.Tasks; using System.Runtime.InteropServices; // UWP-APIの使用 // using Windows.Data.Pdf; // 作成クラス using CSRender; using CSRender.UtHash; // *重要* デスクトップアプリで UWP Api を呼び出す プロジェクトの設定方法が記載されている // https://docs.microsoft.com/ja-jp/windows/apps/desktop/modernize/desktop-to-uwp-enhance // 参照はwindows. winmd :C:\Program Files (x86) \Windows Kits\10\UnionMetadata\/ファサード namespace CSRenderMain { class Program { static void DispHelp() { var pgName = Path.GetFileName(Path.GetFileName(Assembly.GetExecutingAssembly().Location));// プログラム名 string msg = $"{pgName} [/] \n" + $"* Render of PDF file. available 3 command mode:[Basic Rendering] [Make Hash command] [Compare command], and [Render Options]\n" + $"\tPDFの画像化は[Basic Rendering]。\n" + $"\t比較は/MkHash([Make Hash command])後に、/FC([Compare command])で高速実行できます。\n" + $"\n" + $"[Basic Rendering] 基本的なレンダリング\n" + $"\t{pgName} [/] \n" + $"\t/F : pdfPath(pdfファイル名|ディレクトリ) /Fは省略可能\n" + $"\t/O : 出力ディレクトリ。省略時は\"IMG\"フォルダ\n" + $"\n" + $"[Render Options] レンダリングオプション\n" + $"\t/D <解像度> : 解像度指定 9 - 300dpi(default=72dpi)\n" + $"\t/BM,/BT,/BA,/BA,/BC: Select one box.(default=/BC:CrobBox): Boxies:MediaBox/BleedBox/TrimBox/ArtBox/CropBox\n" + $"\t/JPG,/JPEG,/PNG,/TIF,/TIFF,/GIF,/BMP: Select one output format.(default=/JPG)\n" + $"\t/JPEGQ : Jpegの品質指定1-100(default=91)\n" + $"\n" + $"\t/P : ページの範囲を指定する(省略時は全ページ)\n" + $"\t\t連続した範囲を指定する場合は、ハイフン('-')を用いる。終了側を省略すると最終pageまで。\n" + $"\t\t複数のページを指定する場合は、カンマ(',')を用いる\n" + $"\t\tEx. /P \"1,2,30-100\" //1,2pages and 30-100pages.\n" + $"\t[unsupport] 未対応\n" + $"\t/L : 入力PDFファイルリスト(*unsupport)\n" + $"\t/T : テンポラリフォルダを指定(省略時は出力先フォルダと同じ(*unsupport no need)\n" + $"\t/OP <0|1> : オーバープリントのOn/Off (省略時は1)(*unsupport allways on[1])\n" + $"\t/U <0|1> : 同名上書き設定 0:上書きしない 1:上書き(*unsupport allways overwrite[1])\n" + $"\t/OFFSET : ミリ単位でオフセットを指定する(省略時は共に0mm)(*unsupport)\n" + $"\n" + $"[Make Hash command] 比較用ハッシュ値作成コマンド\n" + $"\t{pgName} /MkHash ...... \n" + $"\t/MKHash : ハッシュ値を出力する。前記の[Render Options]を指定すること\n" + $"\t[HASHファイル作成]\n" + $"\n" + $"[Compare command] 比較コマンド\n" + $"\t{pgName} /FC ...... \n" + $"\t/FC : 2つのPDFを比較する。前記の[Render Options]を指定すること。無名引数が2つ必要です\n" + $"\t 事前に/MkHashを実行しておくことで高速に処理できる\n" + $"\t/RESULT : 比較結果を格納するファイルパス\n" + $"\t\t/FCコマンドを指定すると一致したら0,不一致なら1を返却するようになる\n" + $"\t\tは、,の行で構成される\n" + $"\n" + $"/H or /? : This help\n" /* PDFと同じフォルダに data.<出力条件>.jsonファイルを作成する 出力条件:+++ data.72_BT_JPG_Q34.json 既に存在したら、それを読んでから、追記(もしくは書き換える>。 */ // Remove TEST //+$"\t[for TEST]\n" //+$"\t/M Sync(default) or ASync\n" //+$"\t/M : pageBitMap(default), pageBitMapImage, page(same as pageBitMap), org(orginal source)\n" + "\n"; Console.Write(msg); } #if COMENT // 設計の見直し * ハッシュ作成のみのコマンド → /MkHashをつけると ハッシュファイルのみ作成。ただし、ディレクトリ指定に限る → CSRender /MkHash <各種Renderパラメータ> * 単純なRenderを維持。それに同時にハッシュファイルのOn/Off → /MkHashをつけなければよい。 → CSRender <各種Renderパラメータ> * 比較モードは、Autoハッシュで、存在しなければ最初に作成。Target/Refともに。比較しながら作成してよし Manualでハッシュなしで、遅いけどできるようにする。ハッシュ動作の検証用に → /FCをつけると、デフォルトでハッシュ優先で検査(Auto相当)、ハッシュ検査を無効にしたければ → /NoHash追加でハッシュを無視してTarget/Refとも再計算。ハッシュを使いたければ、事前に/MkHashで 作成しておけばよい。 * ヘルプは <単純なRender> <ハッシュ作成> <比較>にわける * <単純なレンダラ>: 1ファイルおよびディレクトリの比較。 Renderingパラメータの一覧(項目分ける)まで. * <ハッシュ作成>: /MkHashで高速比較のための前準備。ディレクトリモードでハッシュファイルのみを作成する。Renderingパラメータは合わせること * <比較>:/FCコマンドで比較の説明 * ここにAsiccDocを埋められないの? → 情報なし* // 遅くなるので * 比較モード時の差異があったとき、Diffを出す上限値をLimitDiffNumがほしい(デフォルトは各PDFで1pageとしたい) * 比較NGになって、フルでDIFF画像を出したいときはLimitDiffを解除すべき。単独ファイル指定のとき。 // 設計の見直し(END) #endif /// /// PDFレンダラーメイン /// /// /// static int Main(string[] args) { string outuptImageDir = ""; // /O string dpi = "72.0"; // /D string boxSelect = "Crop"; //B string pageRange = "1-*"; //P string mode = "page"; //MODE string imageType = "JPG"; //JPG or /PNG string jpegQ = "91"; //JPEGQ string pdfPath =""; bool bHash = false; //ハッシュ値を生成する bool bMkHash = false; //ハッシュファイルを作成する bool bFC = false; bool bDirMode = false; // ディレクト指定の場合 string pdfPath2 = ""; //比較ファイル string resultPath = ""; //var pdfPathLst = new List(); string[] pdfPathLst = null; string[] pdfPathLst2 = null; var pdfPathLstBoth = new List(); var pdfPathLstNoBoth = new List(); //ハッシュ関係 string hashBaseName = "RenderHash"; // CSRender.Check.GetDPI(); {// ベースハッシュ値の計算テスト var streamPDF = ResData.GetBaseHashPDF(); RenderPDF.RenderPdfInitCheck(streamPDF); //return 0; } var qu = new Queue(args); // 引数をQueに登録 (qu.Enqueue(a)でも可能) while (qu.Count > 0) { // 引数のパース var wd = qu.Dequeue(); // 取り出しqu.Dequeue()で次の要素を取得する if (wd.First() == '-') { // 先頭-(ハイフンもオプション扱いにする)→"/"に置換 wd = Regex.Replace(wd,@"^\-","/"); } //大文字小文字無視でオプションチェック var eIgnoreCase = StringComparer.OrdinalIgnoreCase; // オプションチェックローカル関数 bool isOpt(params string[] opts) => opts.Contains(wd, eIgnoreCase); // ボックスオプション辞書 var BoxSelOptDic = new Dictionary(eIgnoreCase) { ["/BM"] = "Media", ["/BT"] = "Trim", ["/BB"] = "Bleed", ["/BC"] = "Crop", ["/BA"] = "Art" }; if (isOpt("/?","/H")) { DispHelp(); return -1; } else if (isOpt("/F")) { pdfPath = (qu.Count > 0) ? qu.Dequeue() :"";// next word. } else if (isOpt("/O")) { outuptImageDir = (qu.Count > 0) ? qu.Dequeue() :"";// next word. } else if (isOpt("/D")) { dpi = (qu.Count > 0) ? qu.Dequeue():"";// next word. if (!double.TryParse(dpi, out double dmy)) { Console.WriteLine($"解像度が不正です:/D {dpi}"); DispHelp(); return -1; } } else if (isOpt("/P")) { pageRange = (qu.Count > 0) ? qu.Dequeue() : "";// next word. } else if (isOpt("/JPG","/JPEG")) { imageType = "JPG"; } else if (isOpt("/PNG")) { imageType = "PNG"; } else if (isOpt("/TIF","/TIFF")) { imageType = "TIFF"; } else if (isOpt("/GIF", "/GIFF")) { imageType = "GIF"; } else if (isOpt("/BMP")) { imageType = "BMP"; } else if (isOpt("/JPEGQ")) { jpegQ = (qu.Count > 0) ? qu.Dequeue() : "";// next word. if (!int.TryParse(jpegQ, out int dmy)) { Console.WriteLine($"JPEG Qualityが不正です:/JPEGQ {jpegQ}"); DispHelp(); return -1; } if ( !(0 < dmy && dmy <= 100) ) { Console.WriteLine($"JPEG Qualityが不正です(not 1-100):/JPEGQ {dmy}"); return -1; } } else if (isOpt("/M") ) { mode = (qu.Count > 0) ? qu.Dequeue() : "";// next word. } else if ( BoxSelOptDic.ContainsKey(wd) ) { boxSelect = BoxSelOptDic[wd];// "/BT" -> "Trim",... } else if (isOpt("/HASH")) { bHash = true; } else if (isOpt("/MKHASH")) { bMkHash = true; bHash = true; } else if (isOpt("/FC")) { bFC = true; } else if (isOpt("/RESULT")) { resultPath = (qu.Count > 0) ? qu.Dequeue() : "";// next word. } else if (wd.First()=='/') { // 処理の無いオプションを明示的に無視する Console.WriteLine($"Warning::Ignore opt:{wd}"); } else { // 引数をファイル名として取得する if (pdfPath == "") { pdfPath = wd; continue; } if (bFC && (pdfPath2 == "") ) { pdfPath2 = wd;// 比較時のみ取得 continue; } } } if (pdfPath=="") { Console.WriteLine("pdfファイルが指定されてません"); DispHelp(); return -1; } if ( !(File.Exists(pdfPath)|| Directory.Exists(pdfPath)) ) { // ファイルもしくはディレクトも見つからない場合 Console.WriteLine($"ファイルが存在しません:{pdfPath}"); return -1; } var rdCond = new RenderPDF.RenderConditionParams { Dpi = double.Parse(dpi), ImageType = imageType, BoxType = boxSelect, JpegQ= int.Parse(jpegQ) }; bool bDir = File.GetAttributes(pdfPath).HasFlag(FileAttributes.Directory); if ( bDir ) { bDirMode = true; // Directoryが指定されたのでファイルリストアップ pdfPathLst = System.IO.Directory.GetFiles(pdfPath,"*.pdf"/*, System.IO.SearchOption.AllDirectories*/); pdfPathLst = Array.ConvertAll(pdfPathLst, f => Path.GetFileName(f)); // 配列書き換え(ファイル名のみにする // var enumLst = pdfPathLst.Select(f => Path.GetFileName(f)); LINQ式に置き換えることも可能(返り値は配列ではない) foreach ( var f in pdfPathLst) { Console.WriteLine($@"path1={f}"); } } if ((!bFC) && bDir ) { // 単純レンダリング時およびディレクト時に対象リストに追加 foreach (var f in pdfPathLst) { pdfPathLstBoth.Add(f); } } if (bFC) {// 比較モード時 if (pdfPath2 == "") { Console.WriteLine("比較モードで2つ目のpdfファイルが指定されてません"); return -1; } if (!(File.Exists(pdfPath2) || Directory.Exists(pdfPath2))) { Console.WriteLine($"比較ファイルが存在しません:{pdfPath2}"); return -1; } bool bDir2 = File.GetAttributes(pdfPath2).HasFlag(FileAttributes.Directory); if (bDir2) { if (!bDir) { Console.WriteLine($"比較対象はファイルパスでないといけません:{pdfPath2}"); return -1; } // 2つ目のファイルリストアップ pdfPathLst2 = System.IO.Directory.GetFiles(pdfPath2, "*.pdf"/*, System.IO.SearchOption.AllDirectories*/); pdfPathLst2 = Array.ConvertAll( pdfPathLst2, f => Path.GetFileName(f) );// 配列の書き換え foreach( var f in pdfPathLst2) { Console.WriteLine($@"path2={f}"); } // 共通のファイルを見つける。Lstの要素がLst2に含まれているかどうか foreach (var f in pdfPathLst) { if (pdfPathLst2.Contains(f)) { pdfPathLstBoth.Add(f); } else { pdfPathLstNoBoth.Add(f);// LstがLst2に含まれていない } } // Lst2のみファイルをNoBothに登録 foreach (var f in pdfPathLst2) { if (pdfPathLstBoth.Contains(f)) { pdfPathLstNoBoth.Add(f); } } // pdfPathBoth,pdfPathNoBothが作成済み } } var watch = System.Diagnostics.Stopwatch.StartNew(); // 時間の生成と計測開始を同時に行う var otDir = outuptImageDir; if (otDir == "" ) { // 出力ディレクトリの作成 // 元PDFの同一フォルダにIMGフォルダを作成する otDir = Path.Combine(Directory.GetParent(pdfPath).FullName, "IMG"); } if (!Directory.Exists(otDir) /* && (!bMkHash)*/ ) { // MkHashのとき不要→必要 Directory.CreateDirectory(otDir); } var otHashPath = ""; var otHashPath2 = ""; int ret = -1; if (!bDirMode) { if (bFC) { //比較モード // ハッシュデータの読み込み Console.WriteLine($@"pre pdfPath={pdfPath}"); Console.WriteLine($@"pre pdfPath2={pdfPath2}"); var hashDataTgt = HashData.load(pdfPath, hashBaseName, dpi, boxSelect, imageType, jpegQ, ref otHashPath); var hashDataRef = HashData.load(pdfPath2, hashBaseName, dpi, boxSelect, imageType, jpegQ, ref otHashPath2); Console.WriteLine(""); ret = RenderPDF.RenderPdfDocCompare( pdfPath: Path.GetFullPath(pdfPath), refPdfPath: Path.GetFullPath(pdfPath2), inDir: otDir, pm:rdCond, inPageRange: pageRange, inHashDataTgt: hashDataTgt, inHashDataRef: hashDataRef ); //} else if (mode.ToLower() == "async") { // // これはもはや不要 // ret = RenderPDF.RenderPdfDocAsync( // ... // ).Result;// スレッドの終了まで「待つ」 } else { // Sync version. // Console.WriteLine(""); // シングル指定では既存のハッシュファイルを読みださない → 単独名のハッシュファイル");[ var count= pdfPathLstBoth.Count; //ParallelOptions options = new ParallelOptions { MaxDegreeOfParallelism = 6 }; //Parallel.For(0,pdfPathLstBoth.Count, options, index => { //逆に遅くなる UWPコール(render)は対応していない? for (var index=0; index < pdfPathLstBoth.Count; index++) { GC.Collect();// これでメモリリークが解決した var tgt = Path.Combine(pdfPath, pdfPathLstBoth[index]); Console.WriteLine($@"tgt={tgt}:{index}/{pdfPathLstBoth.Count}"); if (bFC) { //比較モード var reff = Path.Combine(pdfPath2, pdfPathLstBoth[index]); Console.WriteLine(""); ret = RenderPDF.RenderPdfDocCompare( pdfPath: Path.GetFullPath(tgt), refPdfPath: Path.GetFullPath(reff), inDir: otDir, pm:rdCond, inPageRange: pageRange, inHashDataTgt: hashDataTgt, inHashDataRef: hashDataRef ); } else { // Sync version(Paralles). ret = RenderPDF.RenderPdfDoc( pdfPath: Path.GetFullPath(tgt), inDir: otDir, pm: rdCond, inPageRange: pageRange, bSaveImage: bMkHash ? false:true,// ハッシュ値生成ではイメージ保存しない。 bHash: bHash,// bHash,dumy inHashData : bMkHash ? hashDataTgt:null //ハッシュコマンドモードのみDataを渡す ); } count--; }; if (bMkHash) { hashDataTgt.save(otHashPath);//ハッシュモードで保存する(Dutyチェックもいるでしょう } } watch.Stop(); Console.WriteLine( $"result={ret},time={ watch.ElapsedMilliseconds/1000.0 }[sec]"); #if COMMNET Assembly assm = Assembly.GetExecutingAssembly(); Console.WriteLine(assm.FullName); // 参照しているすべてのアセンブリを取得し、表示する foreach (AssemblyName refassm in assm.GetReferencedAssemblies()) { Console.WriteLine($@" {refassm.FullName},Ver({refassm.Version}),VerComp({refassm.VersionCompatibility})" ); } string clrVersion = System.Environment.Version.ToString(); Console.WriteLine($@"clrVer={clrVersion}"); #endif return ret;//success. } // リソースからの取得 public static class ResData { public static Stream GetBaseHashPDF(string resName = "CSRender.RES.BaseHash.pdf") { System.Reflection.Assembly asm = System.Reflection.Assembly.GetExecutingAssembly(); var sel = from x in asm.GetManifestResourceNames() select resName; if ( sel.Count() == 1) { return asm.GetManifestResourceStream(sel.First()); } return null; } } } }