mirror of
https://github.com/aphrody-labs/PowerShell.git
synced 2026-10-17 20:56:02 +00:00
345 lines
12 KiB
C#
345 lines
12 KiB
C#
// Copyright (c) Microsoft Corporation.
|
|
// Licensed under the MIT License.
|
|
|
|
using System;
|
|
using System.Collections.Generic;
|
|
using System.Drawing;
|
|
using System.Drawing.Printing;
|
|
using System.Management.Automation;
|
|
using System.Management.Automation.Internal;
|
|
|
|
namespace Microsoft.PowerShell.Commands.Internal.Format
|
|
{
|
|
/// <summary>
|
|
/// Implementation of the LineOutput interface for printer.
|
|
/// </summary>
|
|
internal sealed class PrinterLineOutput : LineOutput
|
|
{
|
|
#region LineOutput implementation
|
|
|
|
/// <summary>
|
|
/// Full buffering for printer.
|
|
/// </summary>
|
|
internal override bool RequiresBuffering { get { return true; } }
|
|
|
|
/// <summary>
|
|
/// Do the printing on playback.
|
|
/// </summary>
|
|
internal override void ExecuteBufferPlayBack(DoPlayBackCall playback)
|
|
{
|
|
_playbackCall = playback;
|
|
DoPrint();
|
|
}
|
|
|
|
/// <summary>
|
|
/// The # of columns for the printer.
|
|
/// </summary>
|
|
/// <value></value>
|
|
internal override int ColumnNumber
|
|
{
|
|
get
|
|
{
|
|
CheckStopProcessing();
|
|
return _deviceColumns;
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// The # of rows for the printer.
|
|
/// </summary>
|
|
/// <value></value>
|
|
internal override int RowNumber
|
|
{
|
|
get
|
|
{
|
|
CheckStopProcessing();
|
|
return _deviceRows;
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Write a line to the output device.
|
|
/// </summary>
|
|
/// <param name="s">Line to write.</param>
|
|
internal override void WriteLine(string s)
|
|
{
|
|
CheckStopProcessing();
|
|
|
|
// Remove all ANSI escape sequences before sending out to the printer.
|
|
s = new ValueStringDecorated(s).ToString(OutputRendering.PlainText);
|
|
WriteRawText(s);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Write a raw text by delegating to the writer underneath, with no change to the text.
|
|
/// For example, keeping VT escape sequences intact in it.
|
|
/// </summary>
|
|
/// <param name="s">The raw text to be written to the device.</param>
|
|
internal override void WriteRawText(string s)
|
|
{
|
|
CheckStopProcessing();
|
|
|
|
// Delegate the action to the helper, that will properly break the string into screen lines.
|
|
_writeLineHelper.WriteLine(s, ColumnNumber);
|
|
}
|
|
|
|
#endregion
|
|
|
|
/// <summary>
|
|
/// Initializes static members of the <see cref="PrinterLineOutput"/> class.
|
|
/// Used for static initializations like DefaultPrintFontName.
|
|
/// </summary>
|
|
static PrinterLineOutput()
|
|
{
|
|
// This default must be loaded from a resource file as different
|
|
// cultures will have different defaults and the localizer would
|
|
// know the default for different cultures.
|
|
s_defaultPrintFontName = OutPrinterDisplayStrings.DefaultPrintFontName;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Initializes a new instance of the <see cref="PrinterLineOutput"/> class.
|
|
/// </summary>
|
|
/// <param name="printerName">Name of printer, if null use default printer.</param>
|
|
internal PrinterLineOutput(string printerName)
|
|
{
|
|
_printerName = printerName;
|
|
|
|
// instantiate the helper to do the line processing when LineOutput.WriteXXX() is called
|
|
WriteLineHelper.WriteCallback wl = new(this.OnWriteLine);
|
|
WriteLineHelper.WriteCallback w = new(this.OnWrite);
|
|
|
|
_writeLineHelper = new WriteLineHelper(true, wl, w, this.DisplayCells);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Callback to be called when IConsole.WriteLine() is called by WriteLineHelper.
|
|
/// </summary>
|
|
/// <param name="s">String to write.</param>
|
|
private void OnWriteLine(string s)
|
|
{
|
|
_lines.Enqueue(s);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Callback to be called when Console.Write() is called by WriteLineHelper.
|
|
/// This is called when the WriteLineHelper needs to write a line whose length
|
|
/// is the same as the width of the screen buffer.
|
|
/// </summary>
|
|
/// <param name="s">String to write.</param>
|
|
private void OnWrite(string s)
|
|
{
|
|
_lines.Enqueue(s);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Do the printing.
|
|
/// </summary>
|
|
private void DoPrint()
|
|
{
|
|
try
|
|
{
|
|
// create a new print document object and set the printer name, if available
|
|
PrintDocument pd = new();
|
|
|
|
if (!string.IsNullOrEmpty(_printerName))
|
|
{
|
|
pd.PrinterSettings.PrinterName = _printerName;
|
|
}
|
|
|
|
// set up the callback mechanism
|
|
pd.PrintPage += this.pd_PrintPage;
|
|
|
|
// start printing
|
|
pd.Print();
|
|
}
|
|
finally
|
|
{
|
|
// make sure we do not leak the font
|
|
if (_printFont != null)
|
|
{
|
|
_printFont.Dispose();
|
|
_printFont = null;
|
|
}
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Helper to create a font.
|
|
/// If the font object exists, it does nothing.
|
|
/// Else, the a new object is created and verified.
|
|
/// </summary>
|
|
/// <param name="g">GDI+ graphics object needed for verification.</param>
|
|
private void CreateFont(Graphics g)
|
|
{
|
|
if (_printFont != null)
|
|
return;
|
|
|
|
// create the font
|
|
|
|
// do we have a specified font?
|
|
if (string.IsNullOrEmpty(_printFontName))
|
|
{
|
|
_printFontName = s_defaultPrintFontName;
|
|
}
|
|
|
|
if (_printFontSize <= 0)
|
|
{
|
|
_printFontSize = DefaultPrintFontSize;
|
|
}
|
|
|
|
_printFont = new Font(_printFontName, _printFontSize);
|
|
VerifyFont(g);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Internal helper to verify that the font is fixed pitch. If the test fails,
|
|
/// it reverts to the default font.
|
|
/// </summary>
|
|
/// <param name="g">GDI+ graphics object needed for verification.</param>
|
|
private void VerifyFont(Graphics g)
|
|
{
|
|
// check if the font is fixed pitch
|
|
// HEURISTICS:
|
|
// we compute the length of two strings, one made of "large" characters
|
|
// one made of "narrow" ones. If they are the same length, we assume that
|
|
// the font is fixed pitch.
|
|
const string large = "ABCDEF";
|
|
float wLarge = g.MeasureString(large, _printFont).Width / large.Length;
|
|
const string narrow = ".;'}l|";
|
|
float wNarrow = g.MeasureString(narrow, _printFont).Width / narrow.Length;
|
|
|
|
if (Math.Abs((float)(wLarge - wNarrow)) < 0.001F)
|
|
{
|
|
// we passed the test
|
|
return;
|
|
}
|
|
|
|
// just get back to the default, since it's not fixed pitch
|
|
_printFont.Dispose();
|
|
_printFont = new Font(s_defaultPrintFontName, DefaultPrintFontSize);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Event fired for each page to print.
|
|
/// </summary>
|
|
/// <param name="sender">Sender, not used.</param>
|
|
/// <param name="ev">Print page event.</param>
|
|
private void pd_PrintPage(object sender, PrintPageEventArgs ev)
|
|
{
|
|
float yPos = 0; // GDI+ coordinate down the page
|
|
int linesPrinted = 0; // linesPrinted
|
|
float leftMargin = ev.MarginBounds.Left;
|
|
float topMargin = ev.MarginBounds.Top;
|
|
|
|
CreateFont(ev.Graphics);
|
|
|
|
// compute the height of a line of text
|
|
float lineHeight = _printFont.GetHeight(ev.Graphics);
|
|
|
|
// Work out the number of lines per page
|
|
// Use the MarginBounds on the event to do this
|
|
float linesPerPage = ev.MarginBounds.Height / _printFont.GetHeight(ev.Graphics);
|
|
|
|
if (!_printingInitialized)
|
|
{
|
|
// on the first page we have to initialize the metrics for LineOutput
|
|
|
|
// work out the number of columns per page assuming fixed pitch font
|
|
const string s = "ABCDEF";
|
|
float w = ev.Graphics.MeasureString(s, _printFont).Width / s.Length;
|
|
float columnsPerPage = ev.MarginBounds.Width / w;
|
|
|
|
_printingInitialized = true;
|
|
_deviceRows = (int)linesPerPage;
|
|
_deviceColumns = (int)columnsPerPage;
|
|
|
|
// now that we initialized the column and row count for the LineOutput
|
|
// interface we can tell the outputter to playback from cache to do the
|
|
// proper computations of line widths
|
|
// returning from this call, the string queue on this object is full of
|
|
// lines of text to print
|
|
_playbackCall();
|
|
}
|
|
|
|
// now iterate over the file printing out each line
|
|
while ((linesPrinted < linesPerPage) && (_lines.Count > 0))
|
|
{
|
|
// get the string to be printed
|
|
string line = _lines.Dequeue();
|
|
|
|
// compute the Y position where to draw
|
|
yPos = topMargin + (linesPrinted * lineHeight);
|
|
|
|
// do the actual drawing
|
|
ev.Graphics.DrawString(line, _printFont, Brushes.Black, leftMargin, yPos, new StringFormat());
|
|
linesPrinted++;
|
|
}
|
|
|
|
// If we have more lines then print another page
|
|
ev.HasMorePages = _lines.Count > 0;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Flag for one-time initialization of the interface (columns, etc.).
|
|
/// </summary>
|
|
private bool _printingInitialized = false;
|
|
|
|
/// <summary>
|
|
/// Callback to ask the outputter to playback its cache.
|
|
/// </summary>
|
|
private DoPlayBackCall _playbackCall;
|
|
|
|
/// <summary>
|
|
/// Name of the printer to print to. Null means default printer.
|
|
/// </summary>
|
|
private readonly string _printerName = null;
|
|
|
|
/// <summary>
|
|
/// Name of the font to use, if null the default is used.
|
|
/// </summary>
|
|
private string _printFontName = null;
|
|
|
|
/// <summary>
|
|
/// Font size.
|
|
/// </summary>
|
|
private int _printFontSize = 0;
|
|
|
|
/// <summary>
|
|
/// Default font, used if the printFont is not specified or if the
|
|
/// printFont is not fixed pitch.
|
|
/// </summary>
|
|
/// <remarks>
|
|
/// This default must be loaded from a resource file as different
|
|
/// cultures will have different defaults and the localizer would
|
|
/// know the default for different cultures.
|
|
/// </remarks>
|
|
private static readonly string s_defaultPrintFontName;
|
|
|
|
/// <summary>
|
|
/// Default size for the default font.
|
|
/// </summary>
|
|
private const int DefaultPrintFontSize = 8;
|
|
|
|
/// <summary>
|
|
/// Number of columns on the sheet.
|
|
/// </summary>
|
|
private int _deviceColumns = 80;
|
|
|
|
// number of rows per sheet
|
|
private int _deviceRows = 40;
|
|
|
|
/// <summary>
|
|
/// Text lines ready to print (after output cache playback).
|
|
/// </summary>
|
|
private readonly Queue<string> _lines = new();
|
|
|
|
/// <summary>
|
|
/// Cached font object.
|
|
/// </summary>
|
|
private Font _printFont = null;
|
|
|
|
private readonly WriteLineHelper _writeLineHelper;
|
|
}
|
|
}
|