Skip to content

Commit 4e33349

Browse files
committed
more progress on pygments
1 parent cb698cd commit 4e33349

File tree

25 files changed

+739
-1091
lines changed

25 files changed

+739
-1091
lines changed

src/WinPrint.Console/OutWinPrint.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -795,7 +795,7 @@ public object GetDynamicParameters() {
795795
HelpMessage = "Optional name of the WinPrint Content Type Engine (or Language) to use (e.g. \"text/plain\" or \"csharp\". Specifying a langauge will choose the \"text/code\" CTE.",
796796
ParameterSetName = "Print"
797797
},
798-
new ValidateSetAttribute(ContentTypeEngineBase.GetDerivedClassesCollection().Select(cte => cte.GetContentTypeName()).ToArray())
798+
new ValidateSetAttribute(ContentTypeEngineBase.GetDerivedClassesCollection().Select(cte => cte.ContentTypeEngineName).ToArray())
799799
}));
800800

801801
//// -Language

src/WinPrint.Console/WinPrint.Console.csproj

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@
99
<AssemblyName>winprint</AssemblyName>
1010
<StartupObject></StartupObject>
1111

12-
<Version>2.0.3.3</Version>
12+
<Version>2.0.3.26</Version>
1313
<Company>Kindel Systems</Company>
1414
<Product>winprint</Product>
1515
<Authors>Charlie Kindel</Authors>
Lines changed: 306 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,306 @@
1+
// Copyright Kindel Systems, LLC - http://www.kindel.com
2+
// Published under the MIT License at https://github.com/tig/winprint
3+
4+
using System;
5+
using System.Drawing;
6+
using System.IO;
7+
using System.Runtime.InteropServices;
8+
using System.Text;
9+
using System.Threading.Tasks;
10+
using libvt100;
11+
using Serilog;
12+
using WinPrint.Core.Models;
13+
using WinPrint.Core.Services;
14+
using static libvt100.Screen;
15+
16+
namespace WinPrint.Core.ContentTypeEngines {
17+
18+
/// <summary>
19+
/// Implements text/plain file type support.
20+
/// </summary>
21+
public class AnsiCte : ContentTypeEngineBase, IDisposable {
22+
private static readonly string _contentType = "text/ansi";
23+
/// <summary>
24+
/// ContentType identifier (shorthand for class name).
25+
/// </summary>
26+
public override string ContentTypeEngineName => _contentType;
27+
28+
public static AnsiCte Create() {
29+
var engine = new AnsiCte();
30+
// Populate it with the common settings
31+
engine.CopyPropertiesFrom(ModelLocator.Current.Settings.TextContentTypeEngineSettings);
32+
return engine;
33+
}
34+
35+
// All of the lines of the text file, after reflow/line-wrap
36+
private DynamicScreen _screen;
37+
38+
public IAnsiDecoderClient DecoderClient { get => (IAnsiDecoderClient)_screen; }
39+
40+
private float _lineHeight;
41+
private int _linesPerPage;
42+
43+
private float lineNumberWidth;
44+
private int _minLineLen;
45+
private System.Drawing.Font _cachedFont;
46+
47+
public void Dispose() {
48+
Dispose(true);
49+
GC.SuppressFinalize(this);
50+
}
51+
52+
// Protected implementation of Dispose pattern.
53+
// Flag: Has Dispose already been called?
54+
private bool _disposed = false;
55+
56+
private void Dispose(bool disposing) {
57+
LogService.TraceMessage($"disposing: {disposing}");
58+
59+
if (_disposed) {
60+
return;
61+
}
62+
63+
if (disposing) {
64+
if (_cachedFont != null) {
65+
_cachedFont.Dispose();
66+
}
67+
68+
_screen = null;
69+
}
70+
_disposed = true;
71+
}
72+
73+
// TODO: Pass doc around by ref to save copies
74+
public override async Task<bool> SetDocumentAsync(string doc) {
75+
Document = doc;
76+
return await Task.FromResult(true);
77+
}
78+
79+
/// <summary>
80+
/// Get total count of pages. Set any local page-size related values (e.g. linesPerPage).
81+
/// </summary>
82+
/// <param name="e"></param>
83+
/// <returns></returns>
84+
public override async Task<int> RenderAsync(System.Drawing.Printing.PrinterResolution printerResolution, EventHandler<string> reflowProgress) {
85+
LogService.TraceMessage();
86+
87+
if (document == null) {
88+
throw new ArgumentNullException("document can't be null for Render");
89+
}
90+
91+
var dpiX = printerResolution.X;
92+
var dpiY = printerResolution.Y;
93+
94+
// BUGBUG: On Windows we can use the printer's resolution to be more accurate. But on Linux we
95+
// have to use 96dpi. See https://github.com/mono/libgdiplus/issues/623, etc...
96+
if (!RuntimeInformation.IsOSPlatform(OSPlatform.Windows) || dpiX < 0 || dpiY < 0) {
97+
dpiX = dpiY = 96;
98+
}
99+
100+
// Create a representative Graphcis used for determining glyph metrics.
101+
using var bitmap = new Bitmap(1, 1);
102+
bitmap.SetResolution(dpiX, dpiY);
103+
var g = Graphics.FromImage(bitmap);
104+
g.PageUnit = GraphicsUnit.Display; // Display is 1/100th"
105+
106+
// Calculate the number of lines per page; first we need our font. Keep it around.
107+
_cachedFont = new System.Drawing.Font(ContentSettings.Font.Family, ContentSettings.Font.Size / 72F * 96, ContentSettings.Font.Style, GraphicsUnit.Pixel); // World?
108+
Log.Debug("Font: {f}, {s} ({p}), {st}", _cachedFont.Name, _cachedFont.Size, _cachedFont.SizeInPoints, _cachedFont.Style);
109+
110+
if (RuntimeInformation.IsOSPlatform(OSPlatform.Linux)) {
111+
_cachedFont.Dispose();
112+
_cachedFont = new System.Drawing.Font(ContentSettings.Font.Family, ContentSettings.Font.Size, ContentSettings.Font.Style, GraphicsUnit.Point);
113+
Log.Debug("Font: {f}, {s} ({p}), {st}", _cachedFont.Name, _cachedFont.Size, _cachedFont.SizeInPoints, _cachedFont.Style);
114+
g.PageUnit = GraphicsUnit.Display; // Display is 1/100th"
115+
}
116+
117+
_lineHeight = _cachedFont.GetHeight(dpiY);
118+
119+
if (PageSize.Height < _lineHeight) {
120+
throw new InvalidOperationException("The line height is greater than page height.");
121+
}
122+
123+
// Round down # of lines per page to ensure lines don't clip on bottom
124+
_linesPerPage = (int)Math.Floor(PageSize.Height / _lineHeight);
125+
126+
// 3 digits + 1 wide - Will support 999 lines before line numbers start to not fit
127+
// TODO: Make line number width dynamic
128+
// Note, MeasureString is actually dependent on lineNumberWidth!
129+
lineNumberWidth = ContentSettings.LineNumbers ? MeasureString(g, _cachedFont, new string('0', 4)).Width : 0;
130+
131+
// This is the shortest line length (in chars) that we think we'll see.
132+
// This is used as a performance optimization (probably premature) and
133+
// could be 0 with no functional change.
134+
_minLineLen = (int)((PageSize.Width - lineNumberWidth) / MeasureString(g, _cachedFont, "W").Width);
135+
136+
// Note, MeasureLines may increment numPages due to form feeds and line wrapping
137+
IAnsiDecoder _vt100 = new AnsiDecoder();
138+
Encoding.RegisterProvider(CodePagesEncodingProvider.Instance);
139+
_screen = new DynamicScreen(_minLineLen);
140+
_vt100.Encoding = CodePagesEncodingProvider.Instance.GetEncoding("ibm437");
141+
_vt100.Subscribe(_screen);
142+
143+
var bytes = _vt100.Encoding.GetBytes(document);
144+
if (bytes != null && bytes.Length > 0) {
145+
_vt100.Input(bytes);
146+
}
147+
148+
#if TESTVT100
149+
_screen[_screen.Lines.Count][0] = new Character('0') { Attributes = new GraphicAttributes() { ForegroundColor = Color.Red } };
150+
151+
for (var x = 0; x < _screen.Width; x++) {
152+
var c = _screen[x, x];
153+
if (c == null) c = new Character('*');
154+
_screen[x,x] = new Character(c.Char) { Attributes = new GraphicAttributes() { ForegroundColor = Color.Red } };
155+
}
156+
157+
for (var x = 0; x < 20; x++) {
158+
_screen[11][x] = new Character((char)((int)'0' + x)) { Attributes = new GraphicAttributes() {
159+
Bold = true,
160+
ForegroundColor = Color.Red } };
161+
}
162+
163+
for (var x = 0; x < _screen.Width; x++) {
164+
var c = _screen[x,20];
165+
if (c == null) c = new Character(' ');
166+
_screen[20][x] = new Character(c.Char) {
167+
Attributes = new GraphicAttributes() {
168+
Bold = true,
169+
ForegroundColor = Color.Green
170+
}
171+
};
172+
}
173+
174+
_screen[8][0] = new Character('_') { Attributes = new GraphicAttributes() { ForegroundColor = Color.Red } };
175+
_screen[23][31] = new Character('>') { Attributes = new GraphicAttributes() { ForegroundColor = Color.Red } };
176+
_screen[57][0] = new Character('{') { Attributes = new GraphicAttributes() { ForegroundColor = Color.Red } };
177+
178+
_screen.CursorPosition = new Point(0, 0);
179+
180+
var w = new StreamWriter("PygmentsCte.txt");
181+
w.Write(_screen);
182+
w.Close();
183+
#endif
184+
185+
186+
var n = (int)Math.Ceiling(_screen.Lines.Count / (double)_linesPerPage);
187+
188+
Log.Debug("Rendered {pages} pages of {linesperpage} lines per page, for a total of {lines} lines.", n, _linesPerPage, _screen.Lines.Count);
189+
return await Task.FromResult(n);
190+
}
191+
192+
private SizeF MeasureString(Graphics g, System.Drawing.Font font, string text) {
193+
return MeasureString(g, text, font, out var charsFitted, out var linesFilled);
194+
}
195+
196+
/// <summary>
197+
/// Measures how much width a string will take, given current page settings
198+
/// </summary>
199+
/// <param name="g"></param>
200+
/// <param name="text"></param>
201+
/// <param name="charsFitted"></param>
202+
/// <param name="linesFilled"></param>
203+
/// <returns></returns>
204+
private SizeF MeasureString(Graphics g, string text, System.Drawing.Font font, out int charsFitted, out int linesFilled) {
205+
if (g is null) {
206+
// define context used for determining glyph metrics.
207+
using var bitmap = new Bitmap(1, 1);
208+
g = Graphics.FromImage(bitmap);
209+
//g = Graphics.FromHwnd(PrintPreview.Instance.Handle);
210+
g.PageUnit = GraphicsUnit.Display;
211+
}
212+
213+
g.TextRenderingHint = ContentTypeEngineBase.TextRenderingHint;
214+
215+
// determine width
216+
var fontHeight = _lineHeight;
217+
// Use page settings including lineNumberWidth
218+
var proposedSize = new SizeF(PageSize.Width, _lineHeight + (_lineHeight / 2));
219+
var size = g.MeasureString(text, font, proposedSize, ContentTypeEngineBase.StringFormat, out charsFitted, out linesFilled);
220+
221+
// TODO: HACK to work around MeasureString not working right on Linux
222+
//if (RuntimeInformation.IsOSPlatform(OSPlatform.Linux))
223+
// linesFilled = 1;
224+
return size;
225+
}
226+
227+
/// <summary>
228+
/// Paints a single page.
229+
/// </summary>
230+
/// <param name="g">Graphics with 0,0 being the origin of the Page</param>
231+
/// <param name="pageNum">Page number to print</param>
232+
public override void PaintPage(Graphics g, int pageNum) {
233+
LogService.TraceMessage($"{pageNum}");
234+
if (_screen == null) {
235+
Log.Debug("_ansiDocument must not be null");
236+
return;
237+
}
238+
239+
g.TextRenderingHint = ContentTypeEngineBase.TextRenderingHint;
240+
241+
// Paint each line of the file
242+
var firstLineOnPage = _linesPerPage * (pageNum - 1);
243+
int i;
244+
for (i = firstLineOnPage; i < firstLineOnPage + _linesPerPage && i < _screen.Lines.Count; i++) {
245+
var yPos = (i - (_linesPerPage * (pageNum - 1))) * _lineHeight;
246+
var x = ContentSettings.LineNumberSeparator ? (int)(lineNumberWidth - 6 - MeasureString(g, _cachedFont, $"{_screen.Lines[i].LineNumber}").Width) : 0;
247+
// Line #s
248+
if (_screen.Lines[i].LineNumber > 0) {
249+
if (ContentSettings.LineNumbers && lineNumberWidth != 0) {
250+
// TOOD: Figure out how to make the spacig around separator more dynamic
251+
// TODO: Allow a different (non-monospace) font for line numbers
252+
g.DrawString($"{_screen.Lines[i].LineNumber}", _cachedFont, Brushes.Gray, x, yPos, ContentTypeEngineBase.StringFormat);
253+
}
254+
}
255+
256+
// Line # separator (draw even if there's no line number, but stop at end of doc)
257+
// TODO: Support setting color of line #s and separator
258+
if (ContentSettings.LineNumbers && ContentSettings.LineNumberSeparator && lineNumberWidth != 0) {
259+
g.DrawLine(Pens.Gray, lineNumberWidth - 2, yPos, lineNumberWidth - 2, yPos + _lineHeight);
260+
}
261+
262+
// Text
263+
float xPos = lineNumberWidth;
264+
foreach (var run in _screen.Lines[i].Runs) {
265+
System.Drawing.Font font = _cachedFont;
266+
if (run.Attributes.Bold) {
267+
if (run.Attributes.Italic) {
268+
font = new System.Drawing.Font(_cachedFont.FontFamily, _cachedFont.SizeInPoints, FontStyle.Bold | FontStyle.Italic, GraphicsUnit.Point);
269+
}
270+
else {
271+
font = new System.Drawing.Font(_cachedFont.FontFamily, _cachedFont.SizeInPoints, FontStyle.Bold, GraphicsUnit.Point);
272+
}
273+
}
274+
else if (run.Attributes.Italic) {
275+
font = new System.Drawing.Font(_cachedFont.FontFamily, _cachedFont.SizeInPoints, FontStyle.Italic, GraphicsUnit.Point);
276+
}
277+
var fg = Color.Black;
278+
if (run.Attributes.ForegroundColor != Color.White)
279+
fg = run.Attributes.ForegroundColor;
280+
281+
var text = _screen.Lines[i].Text[run.Start..(run.Start + run.Length)];
282+
var width = MeasureString(g, font, text).Width;
283+
RectangleF rect = new RectangleF(xPos, yPos, width, _lineHeight);
284+
g.DrawString(text, font, new SolidBrush(fg), rect, StringFormat);
285+
286+
xPos += width;
287+
}
288+
if (ContentSettings.Diagnostics) {
289+
g.DrawRectangle(Pens.Red, lineNumberWidth, yPos, PageSize.Width - lineNumberWidth, _lineHeight);
290+
}
291+
}
292+
#if CURSOR
293+
if (_screen.CursorPosition.Y >= firstLineOnPage && _screen.CursorPosition.Y < firstLineOnPage + _linesPerPage) {
294+
var text = $"{(char)219}";
295+
var x = ContentSettings.LineNumberSeparator ? (int)(lineNumberWidth) : 0;
296+
297+
var width = MeasureString(g, _cachedFont, text).Width;
298+
RectangleF rect = new RectangleF(x + _screen.CursorPosition.X * width, _screen.CursorPosition.Y * _lineHeight, width, _lineHeight);
299+
//g.DrawString(text, _cachedFont, new SolidBrush(Color.Blue), rect, StringFormat);
300+
g.DrawRectangle(Pens.Black, x + _screen.CursorPosition.X * width, _screen.CursorPosition.Y * _lineHeight, width, _lineHeight);
301+
}
302+
#endif
303+
Log.Debug("Painted {lineOnPage} lines.", i - 1);
304+
}
305+
}
306+
}

0 commit comments

Comments
 (0)