diff --git a/README.md b/README.md index aca6006..e4206cc 100644 --- a/README.md +++ b/README.md @@ -1,12 +1,12 @@ # Paint.NET-Plugins Plugins for Paint .NET -##Spaced text +## Spaced text Renders grid-fit anti-aliased text with variable character and line spacing. Works with or without a selection area. Features: - Configurable letter spacing and line spacing -- Left, center or right alignment +- Left, center, right or justified alignment - Text is automatically wrapped to fit the selection - Supports all TTF fonts - Up to 8x AA (configurable) diff --git a/SpacedText/Constants.cs b/SpacedText/Constants.cs index d5a4ef4..8917c85 100644 --- a/SpacedText/Constants.cs +++ b/SpacedText/Constants.cs @@ -29,7 +29,8 @@ public enum TextAlignmentOptions { Left, Center, - Right + Right, + Justify } public const string Space = " "; diff --git a/SpacedText/SpacedText.cs b/SpacedText/SpacedText.cs index 3e8ec89..1ade63f 100644 --- a/SpacedText/SpacedText.cs +++ b/SpacedText/SpacedText.cs @@ -14,13 +14,13 @@ internal class SpacedText : IDisposable { //configuration options public string Text { get; set; } - public string FontFamily { get; set; } + public FontFamily FontFamily { get; set; } public int FontSize { get; set; } public double LetterSpacing { get; set; } public double LineSpacing { get; set; } public int AntiAliasLevel { get; set; } public FontStyle FontStyle { get; set; } - public Constants.TextAlignmentOptions TextAlign { get; set; } + public C.TextAlignmentOptions TextAlign { get; set; } //flow control public bool IsCancelRequested { get; set; } @@ -34,7 +34,7 @@ internal class SpacedText : IDisposable public SpacedText() { - AntiAliasLevel = Constants.DefaultAntiAliasingLevel; + AntiAliasLevel = C.DefaultAntiAliasingLevel; ColorMap[] colorMap = { new ColorMap @@ -83,12 +83,7 @@ public void RenderText(Rectangle bounds) resultGr.InterpolationMode = InterpolationMode.HighQualityBicubic; resultGr.DrawImage(bm, 0f, 0f, Bounds.Width, Bounds.Height); BufferSurface = Surface.CopyFromBitmap(resultBm); - -#if DEBUG - bm.Save("C:\\dev\\buffer.png", ImageFormat.Png); - resultBm.Save("C:\\dev\\result.png", ImageFormat.Png); -#endif - + //cleanup gr.Dispose(); bm.Dispose(); @@ -108,47 +103,80 @@ public void RenderText(Rectangle bounds) private void DrawLines(List lines, Graphics gr, Font font, double letterSpacing, Bitmap bm) { int lineStart = 0; - int lineNum = 0; foreach (string line in lines) { if (IsCancelRequested || lineStart > Bounds.Bottom * AntiAliasLevel) { break; } - - lineNum++; - - if (!line.Equals(string.Empty)) + + if (!string.IsNullOrWhiteSpace(line)) { int left = FontSize / 2; - //measure text - Size textBounds = PInvoked.MeasureString(gr, line, font, letterSpacing); - if (TextAlign != Constants.TextAlignmentOptions.Left) + if (TextAlign != C.TextAlignmentOptions.Justify) { - if (TextAlign == Constants.TextAlignmentOptions.Center) + //measure text + Size textBounds = PInvoked.MeasureString(gr, line, font, letterSpacing); + if (TextAlign == C.TextAlignmentOptions.Center) { left = bm.Width / 2 - textBounds.Width / 2; } - else if (TextAlign == Constants.TextAlignmentOptions.Right) + else if (TextAlign == C.TextAlignmentOptions.Right) { left = bm.Width - (textBounds.Width + FontSize); } - } - if (textBounds.Width > 0 && textBounds.Height > 0 && - textBounds.Width * AntiAliasLevel < Constants.MaxBitmapSize && textBounds.Height * AntiAliasLevel < Constants.MaxBitmapSize) + if (textBounds.Width > 0 && textBounds.Height > 0 && + textBounds.Width * AntiAliasLevel < C.MaxBitmapSize && + textBounds.Height * AntiAliasLevel < C.MaxBitmapSize) + { + //create new bitmap for line + Bitmap lineBm = new Bitmap(textBounds.Width * AntiAliasLevel, + textBounds.Height * AntiAliasLevel); + Graphics lineGr = Graphics.FromImage(lineBm); + //draw text + PInvoked.TextOut(lineGr, line, 0, 0, font, letterSpacing); + + //draw lineBm to bm leaving out black + gr.DrawImage(lineBm, new Rectangle(new Point(left, lineStart), lineBm.Size), 0, 0, + lineBm.Width, + lineBm.Height, GraphicsUnit.Pixel, imgAttr); + lineGr.Dispose(); + lineBm.Dispose(); + } + } + else { + //measure text without spaces + string lineWithoutSpaces = line.Replace(" ", string.Empty); + Size textBounds = PInvoked.MeasureString(gr, lineWithoutSpaces, font, letterSpacing); + + //calculate width of spaces + int spaceWidth = FontSize; + if (textBounds.Width > bm.Width / 2) + { + spaceWidth = (bm.Width - textBounds.Width - FontSize) / + Math.Max(line.Length - lineWithoutSpaces.Length, 1); + } + //create new bitmap for line - Bitmap lineBm = new Bitmap(textBounds.Width * AntiAliasLevel, textBounds.Height * AntiAliasLevel); + Bitmap lineBm = new Bitmap(bm.Width, bm.Height); Graphics lineGr = Graphics.FromImage(lineBm); - //draw text - PInvoked.TextOut(lineGr, line, 0, 0, font, letterSpacing); -#if DEBUG - lineBm.Save($"C:\\dev\\line{lineNum}.png", ImageFormat.Png); -#endif + + //draw word by word with correct space in between.7 + foreach (string word in line.Split(' ')) + { + //draw text + PInvoked.TextOut(lineGr, word, left, 0, font, letterSpacing); + + Size wordBounds = PInvoked.MeasureString(lineGr, word, font, letterSpacing); + left += wordBounds.Width + spaceWidth; + } + //draw lineBm to bm leaving out black - gr.DrawImage(lineBm, new Rectangle(new Point(left, lineStart), lineBm.Size), 0, 0, lineBm.Width, + gr.DrawImage(lineBm, new Rectangle(new Point(0, lineStart), lineBm.Size), 0, 0, + lineBm.Width, lineBm.Height, GraphicsUnit.Pixel, imgAttr); lineGr.Dispose(); lineBm.Dispose(); diff --git a/SpacedText/SpacedTextEffectsPlugin.cs b/SpacedText/SpacedTextEffectsPlugin.cs index e004ee1..5a95a24 100644 --- a/SpacedText/SpacedTextEffectsPlugin.cs +++ b/SpacedText/SpacedTextEffectsPlugin.cs @@ -7,8 +7,8 @@ using PaintDotNet.Drawing; using PaintDotNet.Effects; using PaintDotNet.PropertySystem; - using PaintDotNet.SystemLayer; using System.Collections.Generic; + using System.Drawing.Text; using System.Linq; using PaintDotNet.IndirectUI; using PaintDotNet.Rendering; @@ -19,15 +19,12 @@ public class SpacedTextEffectsPlugin : PropertyBasedEffect { private readonly SpacedText helper; - private readonly List fontFamilies; + private readonly List fontFamilies; public SpacedTextEffectsPlugin() : base("Spaced text", null, "Text Formations", EffectFlags.Configurable) { fontFamilies = - UIUtil.GetGdiFontNames() - .Where(f => f.Item2 == UIUtil.GdiFontType.TrueType) - .Select(f => f.Item1).OrderBy(f => f) - .ToList(); + new InstalledFontCollection().Families.ToList(); helper = new SpacedText(); } @@ -40,7 +37,7 @@ protected override PropertyCollection OnCreatePropertyCollection() new DoubleProperty(C.Properties.LetterSpacing.ToString(), C.DefaultLetterSpacing, C.MinLetterSpacing, C.MaxLetterSpacing), new DoubleProperty(C.Properties.LineSpacing.ToString(), C.DefaultLineSpacing, C.MinLineSpacing, C.MaxLineSpacing), new Int32Property(C.Properties.AntiAliasLevel.ToString(), C.DefaultAntiAliasingLevel, C.MinAntiAliasingLevel, C.MaxAntiAliasingLevel), - new StaticListChoiceProperty(C.Properties.FontFamily.ToString(), fontFamilies.ToArray(), fontFamilies.FirstIndexWhere(f => f == "Arial" || f == "Helvetica")), + new StaticListChoiceProperty(C.Properties.FontFamily.ToString(), fontFamilies.ToArray(), fontFamilies.FirstIndexWhere(f => f.Name == "Arial" || f.Name == "Helvetica")), new StaticListChoiceProperty(C.Properties.TextAlignment, Enum.GetNames(typeof(C.TextAlignmentOptions)).ToArray(), 0), new BooleanProperty(C.Properties.Bold.ToString(), false), new BooleanProperty(C.Properties.Italic.ToString(), false), @@ -53,60 +50,65 @@ protected override ControlInfo OnCreateConfigUI(PropertyCollection props) { ControlInfo configUI = CreateDefaultConfigUI(props); - configUI.SetPropertyControlValue(Constants.Properties.Text.ToString(), ControlInfoPropertyNames.Multiline, true); - configUI.SetPropertyControlValue(Constants.Properties.Bold.ToString(), ControlInfoPropertyNames.DisplayName, "Formatting"); - configUI.SetPropertyControlValue(Constants.Properties.Bold.ToString(), ControlInfoPropertyNames.Description, Constants.Properties.Bold.ToString()); - configUI.SetPropertyControlValue(Constants.Properties.Italic.ToString(), ControlInfoPropertyNames.DisplayName, string.Empty); - configUI.SetPropertyControlValue(Constants.Properties.Italic.ToString(), ControlInfoPropertyNames.Description, Constants.Properties.Italic.ToString()); - configUI.SetPropertyControlValue(Constants.Properties.Underline.ToString(), ControlInfoPropertyNames.DisplayName, string.Empty); - configUI.SetPropertyControlValue(Constants.Properties.Underline.ToString(), ControlInfoPropertyNames.Description, Constants.Properties.Underline.ToString()); - configUI.SetPropertyControlValue(Constants.Properties.Strikeout.ToString(), ControlInfoPropertyNames.DisplayName, string.Empty); - configUI.SetPropertyControlValue(Constants.Properties.Strikeout.ToString(), ControlInfoPropertyNames.Description, Constants.Properties.Strikeout.ToString()); + configUI.SetPropertyControlValue(C.Properties.Text.ToString(), ControlInfoPropertyNames.Multiline, true); + configUI.SetPropertyControlValue(C.Properties.Bold.ToString(), ControlInfoPropertyNames.DisplayName, "Formatting"); + configUI.SetPropertyControlValue(C.Properties.Bold.ToString(), ControlInfoPropertyNames.Description, C.Properties.Bold.ToString()); + configUI.SetPropertyControlValue(C.Properties.Italic.ToString(), ControlInfoPropertyNames.DisplayName, string.Empty); + configUI.SetPropertyControlValue(C.Properties.Italic.ToString(), ControlInfoPropertyNames.Description, C.Properties.Italic.ToString()); + configUI.SetPropertyControlValue(C.Properties.Underline.ToString(), ControlInfoPropertyNames.DisplayName, string.Empty); + configUI.SetPropertyControlValue(C.Properties.Underline.ToString(), ControlInfoPropertyNames.Description, C.Properties.Underline.ToString()); + configUI.SetPropertyControlValue(C.Properties.Strikeout.ToString(), ControlInfoPropertyNames.DisplayName, string.Empty); + configUI.SetPropertyControlValue(C.Properties.Strikeout.ToString(), ControlInfoPropertyNames.Description, C.Properties.Strikeout.ToString()); - configUI.SetPropertyControlValue(Constants.Properties.LetterSpacing.ToString(), + configUI.SetPropertyControlValue(C.Properties.LetterSpacing.ToString(), ControlInfoPropertyNames.SliderLargeChange, 0.25); - configUI.SetPropertyControlValue(Constants.Properties.LetterSpacing.ToString(), + configUI.SetPropertyControlValue(C.Properties.LetterSpacing.ToString(), ControlInfoPropertyNames.SliderSmallChange, 0.01); - configUI.SetPropertyControlValue(Constants.Properties.LetterSpacing.ToString(), + configUI.SetPropertyControlValue(C.Properties.LetterSpacing.ToString(), ControlInfoPropertyNames.UpDownIncrement, 0.01); - configUI.SetPropertyControlValue(Constants.Properties.LineSpacing.ToString(), + configUI.SetPropertyControlValue(C.Properties.LineSpacing.ToString(), ControlInfoPropertyNames.SliderLargeChange, 0.25); - configUI.SetPropertyControlValue(Constants.Properties.LineSpacing.ToString(), + configUI.SetPropertyControlValue(C.Properties.LineSpacing.ToString(), ControlInfoPropertyNames.SliderSmallChange, 0.01); - configUI.SetPropertyControlValue(Constants.Properties.LineSpacing.ToString(), + configUI.SetPropertyControlValue(C.Properties.LineSpacing.ToString(), ControlInfoPropertyNames.UpDownIncrement, 0.01); + PropertyControlInfo fontControl = configUI.FindControlForPropertyName(C.Properties.FontFamily); + foreach (FontFamily fontFamily in fontFamilies) + { + fontControl.SetValueDisplayName(fontFamily, fontFamily.Name); + } + return configUI; } protected override void OnSetRenderInfo(PropertyBasedEffectConfigToken newToken, RenderArgs dstArgs, RenderArgs srcArgs) { helper.Text = newToken.GetProperty(C.Properties.Text.ToString()).Value; - helper.FontFamily = newToken.GetProperty(C.Properties.FontFamily.ToString()).Value.ToString(); + helper.FontFamily = (FontFamily)newToken.GetProperty(C.Properties.FontFamily.ToString()).Value; helper.FontSize = newToken.GetProperty(C.Properties.FontSize.ToString()).Value; helper.LetterSpacing = newToken.GetProperty(C.Properties.LetterSpacing.ToString()).Value; helper.LineSpacing = newToken.GetProperty(C.Properties.LineSpacing.ToString()).Value; helper.AntiAliasLevel = newToken.GetProperty(C.Properties.AntiAliasLevel.ToString()).Value; - var fontFamily = new FontFamily(helper.FontFamily); - helper.FontStyle = fontFamily.IsStyleAvailable(FontStyle.Regular) ? FontStyle.Regular : fontFamily.IsStyleAvailable(FontStyle.Bold) ? FontStyle.Bold : FontStyle.Italic; + helper.FontStyle = helper.FontFamily.IsStyleAvailable(FontStyle.Regular) ? FontStyle.Regular : helper.FontFamily.IsStyleAvailable(FontStyle.Bold) ? FontStyle.Bold : FontStyle.Italic; helper.TextAlign = (C.TextAlignmentOptions) Enum.Parse(typeof(C.TextAlignmentOptions), newToken .GetProperty(C.Properties.TextAlignment.ToString()) .Value.ToString()); - if (newToken.GetProperty(C.Properties.Bold.ToString()).Value && fontFamily.IsStyleAvailable(FontStyle.Bold)) + if (newToken.GetProperty(C.Properties.Bold.ToString()).Value && helper.FontFamily.IsStyleAvailable(FontStyle.Bold)) { helper.FontStyle |= FontStyle.Bold; } - if (newToken.GetProperty(C.Properties.Italic.ToString()).Value && fontFamily.IsStyleAvailable(FontStyle.Italic)) + if (newToken.GetProperty(C.Properties.Italic.ToString()).Value && helper.FontFamily.IsStyleAvailable(FontStyle.Italic)) { helper.FontStyle |= FontStyle.Italic; } - if (newToken.GetProperty(C.Properties.Underline.ToString()).Value && fontFamily.IsStyleAvailable(FontStyle.Underline)) + if (newToken.GetProperty(C.Properties.Underline.ToString()).Value && helper.FontFamily.IsStyleAvailable(FontStyle.Underline)) { helper.FontStyle |= FontStyle.Underline; } - if (newToken.GetProperty(C.Properties.Strikeout.ToString()).Value && fontFamily.IsStyleAvailable(FontStyle.Strikeout)) + if (newToken.GetProperty(C.Properties.Strikeout.ToString()).Value && helper.FontFamily.IsStyleAvailable(FontStyle.Strikeout)) { helper.FontStyle |= FontStyle.Strikeout; } @@ -132,7 +134,7 @@ protected override void OnRender(Rectangle[] renderRects, int startIndex, int le renderBounds.Intersect(renderRects[i]); //since TextOut does not support transparent text, we will use the resulting bitmap as a transparency map - CopyRectangle(renderBounds, helper.BufferSurface, base.DstArgs.Surface); + CopyRectangle(renderBounds, helper.BufferSurface, DstArgs.Surface); //clear the remainder DstArgs.Surface.Clear(new RectInt32( @@ -156,7 +158,7 @@ private void CopyRectangle(Rectangle area, Surface buffer, Surface dest) for (int x = area.Left; x < area.Right; x++) { //use the buffer as an alpha map - ColorBgra color = base.EnvironmentParameters.PrimaryColor; + ColorBgra color = EnvironmentParameters.PrimaryColor; ColorBgra opacitySource = buffer[x - helper.Bounds.Left, y - helper.Bounds.Top]; color.A = opacitySource.R; dest[x, y] = color;