約束どおり、.NETから
ベクトル形式で管理されたPDF印刷のテーマ(
1、2 )を続けます。
以前の記事でPCLを使用する理論的な側面について
説明しましたが、今度はPDFファイルをベクターでプリンターに印刷するためのプログラムを分解します。 このアプリケーションは、たとえば、さまざまな色や密度の紙に複数ページのフォームやアンケートのパックを印刷する必要がある場合に役立ちます。
プリンタートレイの管理方法を学ぶと、ページを手動で読み込む必要がなくなります;)テンプレートは、プリンターが現在のページの用紙を取り出すトレイの番号を示します。 さらに、テンプレートはドキュメントに周期的に適用されます。ドキュメントに32ページがあり、テンプレート4の場合、テンプレートはシンプレックスモードで8回、デュプレックスで4回繰り返されます。
手順を思い出させてください:
- PDFをPCLに変換します。
- テンプレートによってPCLを変更します。
- PCLバイトのストリームをプリンターに送信します。
依存関係のうち、アプリケーションには
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;
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 {
動作するには、テンプレートファイルが必要です。
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には実装されていません。
提案や不正確な点がある場合は、コメントに書いてください。
一連の記事:
ラスターアプローチベクトルアプローチ理論ベクトル実践アプローチ