[PF] .NETでのPDF印刷、ベクターアプローチ、実践







約束どおり、.NETからベクトル形式で管理されたPDF印刷のテーマ( 1、2 )を続けます。 以前の記事でPCLを使用する理論的な側面について説明しましたが、今度はPDFファイルをベクターでプリンターに印刷するためのプログラムを分解します。 このアプリケーションは、たとえば、さまざまな色や密度の紙に複数ページのフォームやアンケートのパックを印刷する必要がある場合に役立ちます。 プリンタートレイの管理方法を学ぶと、ページを手動で読み込む必要がなくなります;)テンプレートは、プリンターが現在のページの用紙を取り出すトレイの番号を示します。 さらに、テンプレートはドキュメントに周期的に適用されます。ドキュメントに32ページがあり、テンプレート4の場合、テンプレートはシンプレックスモードで8回、デュプレックスで4回繰り返されます。



手順を思い出させてください:



依存関係のうち、アプリケーションにはGhostscript.NETのみがあり、これを使用してPDFをPCLに変換します。 Ghostscript.NETとその使用方法は、サイクルの最初の記事に記載されています。









Ghostscriptを使用するには、Ghostscript.NETと呼ばれる.NETのラッパーを使用します。 .NETラッパーは、Ghostscriptを任意の設定で使用できるユニバーサルクラスGhostscriptProcessorを実装します。 ConvertPcl2Pdfメソッドのみを使用してPdf2Pclクラスを作成しましょう。これは、入力としてPDFファイルへのパスを取り、PCLストリームをバイト配列として返します。



public class Pdf2Pcl { public static byte[] ConvertPcl2Pdf(string pdfFileName) { byte[] rawDocumentData = null; var gsPipedOutput = new GhostscriptPipedOutput(); var outputPipeHandle = "%handle%" + int.Parse(gsPipedOutput.ClientHandle).ToString("X2"); using (var processor = new GhostscriptProcessor()) { var switches = new List<string>(); switches.Add("-dQUIET"); switches.Add("-dSAFER"); switches.Add("-dBATCH"); switches.Add("-dNOPAUSE"); switches.Add("-dNOPROMPT"); switches.Add("-sDEVICE=pxlmono"); switches.Add("-dNumRenderingThreads=20"); switches.Add("-o" + outputPipeHandle); switches.Add("-f"); switches.Add(pdfFileName); try { processor.StartProcessing(switches.ToArray(), new GsIoHandler()); rawDocumentData = gsPipedOutput.Data; } catch (Exception ex) { Console.WriteLine(ex.Message); } finally { gsPipedOutput.Dispose(); gsPipedOutput = null; } } return rawDocumentData; } public class GsIoHandler : GhostscriptStdIO { public GsIoHandler() : base(true, true, true) { } public override void StdIn(out string input, int count) { input = string.Empty; } public override void StdOut(string output) { if (string.IsNullOrWhiteSpace(output)) return; output = output.Trim(); Console.WriteLine("GS: {0}",output); } public override void StdError(string error) { if (string.IsNullOrWhiteSpace(error)) return; error = error.Trim(); Console.WriteLine("GS: {0}", error); } } }
      
      







GsIoHandlerクラスは、GhostScriptからコンソールにメッセージを出力するためだけに必要であり、オプションです。 GsIoHandlerオブジェクトの代わりに、StartProcessingの2番目の引数がnullになる場合があります。









コンバーターから返されたPClストリームで、ドキュメントのページが宣言されている場所を見つける必要があります。 GhostScriptはこのような広告を同じように生成するため、1つのテンプレートで適切な場所を見つけることができます。



テンプレートを説明するには、定数を定義します。

 private const byte SkipTheByte = 0xff; private const byte UByte = 0xc0; private const byte AttrUByte = 0xf8; private const byte Orientation = 0x28; private const byte MediaSize = 0x25; private const byte MediaSource = 0x26; private const byte SimplexPageMode = 0x34; private const byte DuplexPageMode = 0x35; private const byte DuplexHorizontalBinding = 0x00; private const byte SimplexFrontSide = 0x00; private const byte DuplexVerticalBinding = 0x01; private const byte BeginPage = 0x43;
      
      







パターンのいくつかのバイトは異なる値を取ることができます;それらを除去するために、SkipTheByte定数が使用されます。 ページテンプレートは次のようになります。

 var pagePattern = new byte[] { UByte, SkipTheByte, AttrUByte, Orientation, UByte, SkipTheByte, AttrUByte, MediaSize, UByte, SkipTheByte, AttrUByte, MediaSource, UByte, SkipTheByte, AttrUByte, SimplexPageMode, BeginPage };
      
      







バイトストリームでは、これは次のフラグメントに対応します。





























最も効果的ではありませんが、かなり視覚的な検索アルゴリズムPatternMatchingは次のようになります。

 static int[] PatternMatching(byte[] data, byte[] pattern) { var pageShiftLst = new List<int>(); for (var i = 0; i < data.Count(); i++) { if (IsOnPattern(data, i, pattern)) { pageShiftLst.Add(i); Console.Write("{0:X8} | ", i); for (var j = 0; j < pattern.Count(); j++) { Console.Write("{0:X} ", data[i + j]); } Console.WriteLine(""); i += pattern.Count() - 1; } } return pageShiftLst.ToArray(); } static bool IsOnPattern(byte[] data, int shift, byte[] pattern) { for (var i = 0; i < pattern.Count() ; i++) { if (!((shift + i) < data.Count())) return false; if (pattern[i] != SkipTheByte) { if (pattern[i] != data[shift + i]) { return false; } } } return true; }
      
      







PatternMatching関数は、ページのオフセットの配列を返します。 これで、テンプレートに従ってページを変更し、印刷モードの両面/片面、および現在のページのトレイを指定できます。 これらの変更は、ファイルサイズを変更しません。 バイトの値は変更しますが、番号は変更しません。したがって、後続のオフセットが無関係になることを恐れることはできません。



ページの引数を変更するには、ページの説明の先頭のオフセットに対する可変バイトのオフセットが必要です。 これを行うには、対応する定数を作成します。



 private const int MediaSourceValueShift = 9; private const int DuplexBindingShift = 13; private const int PageModeShift = 15;
      
      







バイトストリームにページオフセットがあり、ページオフセットに対してオフセットバイトオフセットがあるため、たとえば次の関数を使用してPCLデータストリームを変更できます。



 static byte[] ApplyPattern(byte[] data, int[] pageIndexes, byte[] extraPattern, bool isDuplex) { for (int i = 0; i < pageIndexes.Length; i++) { var pageIndex = pageIndexes[i]; data[pageIndex + PageModeShift] = isDuplex ? DuplexPageMode : SimplexPageMode; data[pageIndex + DuplexBindingShift] = isDuplex ? DuplexVerticalBinding : SimplexFrontSide; data[pageIndex + MediaSourceValueShift] = extraPattern[i]; } return data; }
      
      













.Netには、プリンターにバイト配列を送信するための組み込み機能はありません。 しかし、 support.microsoft.comにはこれを行う方法の例があります 。 より具体的には、文字列またはファイルをRawDataとして送信する方法について説明します。 PostScriptを使用して作業している場合、PCLデータストリームの送信にはほとんど役に立たない例があります。 この例を修正して、バイト配列をプリンターに送信できるようにします。 そのためには、RawPrinterHelperクラスにメソッドを追加する必要があります。



 public static bool SendRawDataToPrinter(string szPrinterName, byte[] data, string docName) { bool bSuccess = false; IntPtr pUnmanagedBytes = new IntPtr(0); pUnmanagedBytes = Marshal.AllocCoTaskMem(data.Length); Marshal.Copy(data, 0, pUnmanagedBytes, data.Length); bSuccess = SendBytesToPrinter(szPrinterName, pUnmanagedBytes, data.Length, docName); Marshal.FreeCoTaskMem(pUnmanagedBytes); return bSuccess; }
      
      







プリンター名szPrinterNameは、PrinterSettings.InstalledPrintersクラスを介して標準的な方法で取得できます。 docName引数には、印刷キューに表示される名前が含まれます。



最新のHPプリンターで100%実行可能なテンプレートに従って、PDF文書を印刷するためのプログラムの要点を検討しました。 他のメーカーのプリンターの場合は、PCLサポートのドキュメントを参照することをお勧めします。 しかし、多くのメーカーが現在PCLプロセッサを統合しているため、問題が発生することはほとんどありません。 突然PCLが機能しない場合、この場合サイクルの最初の記事で説明したアプローチがあります。



キーポイントを詳細に説明しましたが、トピックに直接関連していないため、いくつかの詳細を意図的に省略しました。 ただし、それらがなければ、アプリケーションは起動しません。そのため、コンソールプログラムの完全なソースコードを提供します。 アイデアをテストするためだけに開発されたため、完璧にはほど遠いです(ただし、そのタスクに対応しています)。



Program.cs
 using System; using System.Collections.Generic; using System.Drawing.Printing; using System.IO; using System.Linq; using System.Text; using System.Threading.Tasks; namespace PCL_processing { class Program { private const byte SkipTheByte = 0xff; // 0xfc - 0xff Reserved for future use. private const byte UByte = 0xc0; private const byte AttrUByte = 0xf8; private const byte Orientation = 0x28; private const byte MediaSize = 0x25; private const byte MediaSource = 0x26; private const byte SimplexPageMode = 0x34; private const byte DuplexPageMode = 0x35; private const byte DuplexHorizontalBinding = 0x00; private const byte SimplexFrontSide = 0x00; private const byte DuplexVerticalBinding = 0x01; private const byte BeginPage = 0x43; private const int MediaSourceValueShift = 9; private const int DuplexBindingShift = 13; private const int PageModeShift = 15; static void Main(string[] args) { var pagePattern = new byte[] { UByte, SkipTheByte, AttrUByte, Orientation, UByte, SkipTheByte, AttrUByte, MediaSize, UByte, SkipTheByte, AttrUByte, MediaSource, UByte, SkipTheByte, AttrUByte, SimplexPageMode, BeginPage }; var fileName = ""; if (!args.Any()) { while (true) { Console.WriteLine("Please write pdf file name:"); fileName = Console.ReadLine(); if (string.IsNullOrWhiteSpace(fileName)) { Console.WriteLine("You have wrote empty string"); continue; } break; } } else { fileName = args[0]; } if (!File.Exists(fileName)) { Console.WriteLine("File \"{0}\" not found", fileName); return; } var data = Pdf2Pcl.ConvertPcl2Pdf(fileName); var pageIndexes = PatternMatching(data, pagePattern); Console.WriteLine("Found {0} pages", pageIndexes.Length); var printPattern = GetSourecPattern(); var isDuplex = Menu(new[] {"Simplex", "Duplex"}, "Selec mode:") > 0; var extraPattern = ExtractPattern(printPattern, pageIndexes.Length, isDuplex); data = ApplyPattern(data, pageIndexes, extraPattern, isDuplex); for (int i = 0; i < pageIndexes.Length; i++) { Console.Write("{0:X8} | ", pageIndexes[i]); for (var j = 0; j < pagePattern.Count(); j++) { Console.Write("{0:X} ", data[pageIndexes[i] + j]); } Console.WriteLine(""); } var printer = GetPrinter(); RawPrinter.SendRawDataToPrinter(printer, data, fileName); Console.WriteLine("*** DONE ***"); Console.ReadLine(); } static byte[] ApplyPattern(byte[] data, int[] pageIndexes, byte[] extraPattern, bool isDuplex) { for (int i = 0; i < pageIndexes.Length; i++) { var pageIndex = pageIndexes[i]; data[pageIndex + PageModeShift] = isDuplex ? DuplexPageMode : SimplexPageMode; data[pageIndex + DuplexBindingShift] = isDuplex ? DuplexVerticalBinding : SimplexFrontSide; data[pageIndex + MediaSourceValueShift] = extraPattern[i]; } return data; } static int[] PatternMatching(byte[] data, byte[] pattern) { var pageShiftLst = new List<int>(); for (var i = 0; i < data.Count(); i++) { if (IsOnPattern(data, i, pattern)) { pageShiftLst.Add(i); Console.Write("{0:X8} | ", i); for (var j = 0; j < pattern.Count(); j++) { Console.Write("{0:X} ", data[i + j]); } Console.WriteLine(""); i += pattern.Count() - 1; } } return pageShiftLst.ToArray(); } static bool IsOnPattern(byte[] data, int shift, byte[] pattern) { for (var i = 0; i < pattern.Count() ; i++) { if (!((shift + i) < data.Count())) return false; if (pattern[i] != SkipTheByte) { if (pattern[i] != data[shift + i]) { return false; } } } return true; } private static byte[] ExtractPattern(int[] pattern, int pageCount, bool isDublex) { var srcPoint = 0; var expandedPattern = new List<byte>(); for (var pageNumber = 0; pageNumber < pageCount; pageNumber++) { // expand-pattern expandedPattern.Add((byte)pattern[srcPoint]); if (isDublex) { if (pageNumber % 2 != 0) { srcPoint++; } } else { srcPoint++; } srcPoint = srcPoint < pattern.Count() ? srcPoint : 0; } return expandedPattern.ToArray(); } private static int[] GetSourecPattern() { var bindingsFile = "source-bindings.kv"; var patternsFile = "patterns.csv"; var Bindings = GetBindings(bindingsFile); var Patterns = GetPatterns(patternsFile, Bindings); var patternindex = Menu(Patterns.Keys.ToArray(), "Please select pattern:"); var pattern = Patterns.ElementAt(patternindex).Value; var srcPattern = pattern.Select(i => Bindings[i]).ToList(); return srcPattern.ToArray(); } private static int Menu(string[] items, string message) { var selectedIndex = -1; while (true) { Console.WriteLine(message); for (int i = 0; i < items.Length; i++) { Console.WriteLine("[{0}] -- \"{1}\"", i, items[i]); } var str = Console.ReadLine(); if (Int32.TryParse(str, out selectedIndex)) { if (selectedIndex >= 0 && selectedIndex < items.Length) { break; } } } return selectedIndex; } private static Dictionary<int, int> GetBindings(string fileName) { if (!File.Exists(fileName)) { Console.WriteLine("    \"{0}\"", fileName); return new Dictionary<int, int>(); } var res = new Dictionary<int, int>(); var lines = File.ReadAllLines(fileName, Encoding.Default); foreach (var line in lines) { var kv = line.Split('='); if (kv.Count() != 2) { Console.WriteLine("   : \"{0}\"", line); return new Dictionary<int, int>(); } int k = 0; int v = 0; if (!int.TryParse(kv[0], out k)) { Console.WriteLine("     : \"{0}\"", line); return new Dictionary<int, int>(); } if (!int.TryParse(kv[1], out v)) { Console.WriteLine("     : \"{0}\"", line); return new Dictionary<int, int>(); } res[k] = v; } return res; } private static Dictionary<string, int[]> GetPatterns(string fileName, Dictionary<int, int> bindings) { if (!File.Exists(fileName)) { Console.WriteLine("    \"{0}\"", fileName); return new Dictionary<string, int[]>(); } var lines = File.ReadAllLines(fileName, Encoding.Default); var res = new Dictionary<string, int[]>(); foreach (var line in lines) { var splt = line.Split(';'); if (!splt.Any()) { Console.WriteLine("  \"{0}\"", line); return new Dictionary<string, int[]>(); } var patternName = splt[0]; var patternBody = new List<int>(); for (var i = 1; i < splt.Count(); i++) { int item = 0; if (!int.TryParse(splt[i], out item)) { Console.WriteLine("     \"{0}\"", line); break; } if (!bindings.ContainsKey(item)) { Console.WriteLine("      \"{0}\"", line); break; } patternBody.Add(item); } res[patternName] = patternBody.ToArray(); } return res; } static string GetPrinter() { var printers = new string[PrinterSettings.InstalledPrinters.Count]; PrinterSettings.InstalledPrinters.CopyTo(printers, 0); var printerIndex = Menu(printers, "Please select printer:"); return printers[printerIndex]; } } }
      
      







Pdf2pcl.cs
 using System; using System.Collections.Generic; using System.Linq; using System.Text; using System.Threading.Tasks; using Ghostscript.NET; using Ghostscript.NET.Processor; namespace PCL_processing { public class Pdf2Pcl { public static byte[] ConvertPcl2Pdf(string pdfFileName) { byte[] rawDocumentData = null; var gsPipedOutput = new GhostscriptPipedOutput(); var outputPipeHandle = "%handle%" + int.Parse(gsPipedOutput.ClientHandle).ToString("X2"); using (var processor = new GhostscriptProcessor()) { var switches = new List<string>(); switches.Add("-dQUIET"); switches.Add("-dSAFER"); switches.Add("-dBATCH"); switches.Add("-dNOPAUSE"); switches.Add("-dNOPROMPT"); switches.Add("-sDEVICE=pxlmono"); switches.Add("-o" + outputPipeHandle); switches.Add("-f"); switches.Add(pdfFileName); try { processor.StartProcessing(switches.ToArray(), new GsIoHandler()); rawDocumentData = gsPipedOutput.Data; } catch (Exception ex) { Console.WriteLine(ex.Message); } finally { gsPipedOutput.Dispose(); gsPipedOutput = null; } } return rawDocumentData; } public class GsIoHandler : GhostscriptStdIO { public GsIoHandler() : base(true, true, true) { } public override void StdIn(out string input, int count) { input = string.Empty; } public override void StdOut(string output) { if (string.IsNullOrWhiteSpace(output)) return; output = output.Trim(); Console.WriteLine("GS: {0}",output); } public override void StdError(string error) { if (string.IsNullOrWhiteSpace(error)) return; error = error.Trim(); Console.WriteLine("GS: {0}", error); } } } }
      
      







RawPrinter.cs
 using System; using System.Collections.Generic; using System.Linq; using System.Runtime.InteropServices; using System.Text; using System.Threading.Tasks; namespace PCL_processing { class RawPrinter { // Structure and API declarions: [StructLayout(LayoutKind.Sequential, CharSet = CharSet.Ansi)] private class DOCINFOA { [MarshalAs(UnmanagedType.LPStr)] public string pDocName; [MarshalAs(UnmanagedType.LPStr)] public string pOutputFile; [MarshalAs(UnmanagedType.LPStr)] public string pDataType; } [DllImport("winspool.Drv", EntryPoint = "OpenPrinterA", SetLastError = true, CharSet = CharSet.Ansi, ExactSpelling = true, CallingConvention = CallingConvention.StdCall)] private static extern bool OpenPrinter([MarshalAs(UnmanagedType.LPStr)] string szPrinter, out IntPtr hPrinter, IntPtr pd); [DllImport("winspool.Drv", EntryPoint = "ClosePrinter", SetLastError = true, ExactSpelling = true, CallingConvention = CallingConvention.StdCall)] private static extern bool ClosePrinter(IntPtr hPrinter); [DllImport("winspool.Drv", EntryPoint = "StartDocPrinterA", SetLastError = true, CharSet = CharSet.Ansi, ExactSpelling = true, CallingConvention = CallingConvention.StdCall)] private static extern bool StartDocPrinter(IntPtr hPrinter, Int32 level, [In, MarshalAs(UnmanagedType.LPStruct)] DOCINFOA di); [DllImport("winspool.Drv", EntryPoint = "EndDocPrinter", SetLastError = true, ExactSpelling = true, CallingConvention = CallingConvention.StdCall)] private static extern bool EndDocPrinter(IntPtr hPrinter); [DllImport("winspool.Drv", EntryPoint = "StartPagePrinter", SetLastError = true, ExactSpelling = true, CallingConvention = CallingConvention.StdCall)] private static extern bool StartPagePrinter(IntPtr hPrinter); [DllImport("winspool.Drv", EntryPoint = "EndPagePrinter", SetLastError = true, ExactSpelling = true, CallingConvention = CallingConvention.StdCall)] private static extern bool EndPagePrinter(IntPtr hPrinter); [DllImport("winspool.Drv", EntryPoint = "WritePrinter", SetLastError = true, ExactSpelling = true, CallingConvention = CallingConvention.StdCall)] private static extern bool WritePrinter(IntPtr hPrinter, IntPtr pBytes, Int32 dwCount, out Int32 dwWritten); private static bool SendBytesToPrinter(string szPrinterName, IntPtr pBytes, Int32 dwCount, string docName) { Int32 dwError = 0, dwWritten = 0; IntPtr hPrinter = new IntPtr(0); DOCINFOA di = new DOCINFOA(); bool bSuccess = false; // Assume failure unless you specifically succeed. di.pDocName = docName; di.pDataType = "RAW"; // Open the printer. if (OpenPrinter(szPrinterName.Normalize(), out hPrinter, IntPtr.Zero)) { // Start a document. if (StartDocPrinter(hPrinter, 1, di)) { // Start a page. if (StartPagePrinter(hPrinter)) { // Write your bytes. bSuccess = WritePrinter(hPrinter, pBytes, dwCount, out dwWritten); EndPagePrinter(hPrinter); } EndDocPrinter(hPrinter); } ClosePrinter(hPrinter); } // If you did not succeed, GetLastError may give more information // about why not. if (bSuccess == false) { dwError = Marshal.GetLastWin32Error(); } return bSuccess; } public static bool SendRawDataToPrinter(string szPrinterName, byte[] data, string docName) { bool bSuccess = false; // Your unmanaged pointer. IntPtr pUnmanagedBytes = new IntPtr(0); // Allocate some unmanaged memory for those bytes. pUnmanagedBytes = Marshal.AllocCoTaskMem(data.Length); // Copy the managed byte array into the unmanaged array. Marshal.Copy(data, 0, pUnmanagedBytes, data.Length); // Send the unmanaged bytes to the printer. bSuccess = SendBytesToPrinter(szPrinterName, pUnmanagedBytes, data.Length, docName); // Free the unmanaged memory that you allocated earlier. Marshal.FreeCoTaskMem(pUnmanagedBytes); return bSuccess; } } }
      
      









動作するには、テンプレートファイルが必要です。



patterns.csv

;2;3;3;4;4

;2;3;4

;2;3;3;4

;2;4

;2









最初の列はテンプレートの名前で、残りはトレイ番号です



テンプレートをコンパイルするには、マニュアルのソースのIDではなく、トレイ番号で操作するのが便利です。









トレイ番号とソースIDの対応を含むバインディングファイルを作成します。



source-bindings.kv

1=3

2=4

3=5

4=7









この記事で、管理された印刷と一般的な.Netからの印刷に関連する情報のギャップを埋めることができたことを願っています。



この記事の目標の1つは、ドキュメントをPCLに印刷するタスクに何らかの形で遭遇する開発者の注意を引くことです。 PCLはPostScriptほど読みやすく便利ではありませんが、プリンターを微調整できます。 また、これは一部のプロジェクトにとって不可欠であり、PostScriptには実装されていません。



提案や不正確な点がある場合は、コメントに書いてください。



一連の記事:

ラスターアプローチ

ベクトルアプローチ理論

ベクトル実践アプローチ



All Articles