|
| 1 | +namespace SpacedTextPlugin |
| 2 | +{ |
| 3 | + using System; |
| 4 | + using System.Collections.Generic; |
| 5 | + using System.Drawing; |
| 6 | + using System.Drawing.Drawing2D; |
| 7 | + using System.Drawing.Imaging; |
| 8 | + using System.Drawing.Text; |
| 9 | + using System.Linq; |
| 10 | + using PaintDotNet; |
| 11 | + using C = Constants; |
| 12 | + |
| 13 | + internal class SpacedText : IDisposable |
| 14 | + { |
| 15 | + //configuration options |
| 16 | + public string Text { get; set; } |
| 17 | + public string FontFamily { get; set; } |
| 18 | + public int FontSize { get; set; } |
| 19 | + public double LetterSpacing { get; set; } |
| 20 | + public double LineSpacing { get; set; } |
| 21 | + public int AntiAliasLevel { get; set; } |
| 22 | + public FontStyle FontStyle { get; set; } |
| 23 | + public Constants.TextAlignmentOptions TextAlign { get; set; } |
| 24 | + |
| 25 | + //flow control |
| 26 | + public bool IsCancelRequested { get; set; } |
| 27 | + |
| 28 | + //public result |
| 29 | + public Rectangle Bounds { get; private set; } |
| 30 | + public Surface BufferSurface { get; private set; } |
| 31 | + |
| 32 | + //private |
| 33 | + private readonly ImageAttributes imgAttr; |
| 34 | + |
| 35 | + public SpacedText() |
| 36 | + { |
| 37 | + AntiAliasLevel = Constants.DefaultAntiAliasingLevel; |
| 38 | + |
| 39 | + ColorMap[] colorMap = { |
| 40 | + new ColorMap |
| 41 | + { |
| 42 | + OldColor = Color.Black, |
| 43 | + NewColor = Color.Transparent |
| 44 | + } |
| 45 | + }; |
| 46 | + imgAttr = new ImageAttributes(); |
| 47 | + imgAttr.SetRemapTable(colorMap); |
| 48 | + } |
| 49 | + |
| 50 | + public void RenderText(Rectangle bounds) |
| 51 | + { |
| 52 | + Bounds = bounds; |
| 53 | + Font font = new Font(FontFamily, FontSize * AntiAliasLevel, FontStyle, GraphicsUnit.Pixel); |
| 54 | + |
| 55 | + //render text on larger bitmap so it can be anti-aliased while scaling down |
| 56 | + Bitmap bm = new Bitmap(Bounds.Size.Width * AntiAliasLevel, Bounds.Size.Height * AntiAliasLevel); |
| 57 | + Graphics gr = Graphics.FromImage(bm); |
| 58 | + gr.TextRenderingHint = TextRenderingHint.AntiAliasGridFit; |
| 59 | + gr.SmoothingMode = SmoothingMode.HighQuality; |
| 60 | + gr.CompositingMode = CompositingMode.SourceOver; |
| 61 | + gr.Clear(Color.Black); |
| 62 | + |
| 63 | + //letterspacing may be changed during execution |
| 64 | + double letterSpacing = LetterSpacing; |
| 65 | + |
| 66 | + if (!IsCancelRequested) |
| 67 | + { |
| 68 | + //split in lines |
| 69 | + List<string> lines = LineWrap(gr, font, letterSpacing, bm); |
| 70 | + |
| 71 | + if (!IsCancelRequested) |
| 72 | + { |
| 73 | + //draw lines |
| 74 | + DrawLines(lines, gr, font, letterSpacing, bm); |
| 75 | + } |
| 76 | + } |
| 77 | + |
| 78 | + //scale bitmap down onto result-size bitmap and apply anti-aliasing |
| 79 | + Bitmap resultBm = new Bitmap(Bounds.Width, Bounds.Height); |
| 80 | + Graphics resultGr = Graphics.FromImage(resultBm); |
| 81 | + resultGr.InterpolationMode = InterpolationMode.HighQualityBicubic; |
| 82 | + resultGr.DrawImage(bm, 0f, 0f, Bounds.Width, Bounds.Height); |
| 83 | + BufferSurface = Surface.CopyFromBitmap(resultBm); |
| 84 | + |
| 85 | +#if DEBUG |
| 86 | + bm.Save("C:\\dev\\buffer.png", ImageFormat.Png); |
| 87 | + resultBm.Save("C:\\dev\\result.png", ImageFormat.Png); |
| 88 | +#endif |
| 89 | + |
| 90 | + //cleanup |
| 91 | + gr.Dispose(); |
| 92 | + bm.Dispose(); |
| 93 | + resultGr.Dispose(); |
| 94 | + resultBm.Dispose(); |
| 95 | + } |
| 96 | + |
| 97 | + private void DrawLines(List<string> lines, Graphics gr, Font font, double letterSpacing, Bitmap bm) |
| 98 | + { |
| 99 | + int lineStart = 0; |
| 100 | + int lineNum = 0; |
| 101 | + foreach (string line in lines) |
| 102 | + { |
| 103 | + if (IsCancelRequested || lineStart > Bounds.Bottom * AntiAliasLevel) |
| 104 | + { |
| 105 | + break; |
| 106 | + } |
| 107 | + |
| 108 | + lineNum++; |
| 109 | + |
| 110 | + if (!line.Equals(string.Empty)) |
| 111 | + { |
| 112 | + int left = FontSize / 2; |
| 113 | + |
| 114 | + //measure text |
| 115 | + Size textBounds = PInvoked.MeasureString(gr, line, font, letterSpacing); |
| 116 | + if (TextAlign != Constants.TextAlignmentOptions.Left) |
| 117 | + { |
| 118 | + if (TextAlign == Constants.TextAlignmentOptions.Center) |
| 119 | + { |
| 120 | + left = bm.Width / 2 - textBounds.Width / 2; |
| 121 | + } |
| 122 | + else if (TextAlign == Constants.TextAlignmentOptions.Right) |
| 123 | + { |
| 124 | + left = bm.Width - (textBounds.Width + FontSize); |
| 125 | + } |
| 126 | + } |
| 127 | + |
| 128 | + if (textBounds.Width > 0 && textBounds.Height > 0 && |
| 129 | + textBounds.Width * AntiAliasLevel < Constants.MaxBitmapSize && textBounds.Height * AntiAliasLevel < Constants.MaxBitmapSize) |
| 130 | + { |
| 131 | + //create new bitmap for line |
| 132 | + Bitmap lineBm = new Bitmap(textBounds.Width * AntiAliasLevel, textBounds.Height * AntiAliasLevel); |
| 133 | + Graphics lineGr = Graphics.FromImage(lineBm); |
| 134 | + //draw text |
| 135 | + PInvoked.TextOut(lineGr, line, 0, 0, font, letterSpacing); |
| 136 | +#if DEBUG |
| 137 | + lineBm.Save($"C:\\dev\\line{lineNum}.png", ImageFormat.Png); |
| 138 | +#endif |
| 139 | + //draw lineBm to bm leaving out black |
| 140 | + gr.DrawImage(lineBm, new Rectangle(new Point(left, lineStart), lineBm.Size), 0, 0, lineBm.Width, |
| 141 | + lineBm.Height, GraphicsUnit.Pixel, imgAttr); |
| 142 | + lineGr.Dispose(); |
| 143 | + lineBm.Dispose(); |
| 144 | + } |
| 145 | + } |
| 146 | + |
| 147 | + lineStart += font.Height + (int)Math.Round(font.Height * LineSpacing); |
| 148 | + } |
| 149 | + } |
| 150 | + |
| 151 | + private List<string> LineWrap(Graphics gr, Font font, double letterSpacing, Bitmap bm) |
| 152 | + { |
| 153 | + string[] words = Text.Replace(Environment.NewLine, " " + Environment.NewLine + C.Space) |
| 154 | + .Split(new[] {C.SpaceChar}, StringSplitOptions.RemoveEmptyEntries); |
| 155 | + List<string> lines = new List<string>(); |
| 156 | + |
| 157 | + string currentLine = words.Any() ? words.First() + C.Space : string.Empty; |
| 158 | + |
| 159 | + foreach (string word in words.Skip(1)) |
| 160 | + { |
| 161 | + //if manual line break: end current line and start new line |
| 162 | + if (word.Equals(Environment.NewLine)) |
| 163 | + { |
| 164 | + lines.Add(currentLine.Trim()); |
| 165 | + currentLine = string.Empty; |
| 166 | + continue; |
| 167 | + } |
| 168 | + |
| 169 | + //measure currentline + word |
| 170 | + //else add word to currentline |
| 171 | + if (PInvoked.MeasureString(gr, currentLine + word, font, letterSpacing).Width > bm.Width - FontSize) |
| 172 | + { |
| 173 | + //if outside bounds, then add line |
| 174 | + lines.Add(currentLine.Trim()); |
| 175 | + currentLine = word + C.Space; |
| 176 | + } |
| 177 | + else |
| 178 | + { |
| 179 | + currentLine += word + C.Space; |
| 180 | + } |
| 181 | + } |
| 182 | + //add currentline |
| 183 | + if (!string.IsNullOrEmpty(currentLine)) |
| 184 | + { |
| 185 | + lines.Add(currentLine.Trim()); |
| 186 | + } |
| 187 | + return lines; |
| 188 | + } |
| 189 | + |
| 190 | + public void Dispose() |
| 191 | + { |
| 192 | + BufferSurface.Dispose(); |
| 193 | + } |
| 194 | + } |
| 195 | +} |
0 commit comments