Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
@@ -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)
Expand Down
3 changes: 2 additions & 1 deletion SpacedText/Constants.cs
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,8 @@ public enum TextAlignmentOptions
{
Left,
Center,
Right
Right,
Justify
}

public const string Space = " ";
Expand Down
86 changes: 57 additions & 29 deletions SpacedText/SpacedText.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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; }
Expand All @@ -34,7 +34,7 @@ internal class SpacedText : IDisposable

public SpacedText()
{
AntiAliasLevel = Constants.DefaultAntiAliasingLevel;
AntiAliasLevel = C.DefaultAntiAliasingLevel;

ColorMap[] colorMap = {
new ColorMap
Expand Down Expand Up @@ -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();
Expand All @@ -108,47 +103,80 @@ public void RenderText(Rectangle bounds)
private void DrawLines(List<string> 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();
Expand Down
64 changes: 33 additions & 31 deletions SpacedText/SpacedTextEffectsPlugin.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand All @@ -19,15 +19,12 @@
public class SpacedTextEffectsPlugin : PropertyBasedEffect
{
private readonly SpacedText helper;
private readonly List<string> fontFamilies;
private readonly List<FontFamily> 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();
}

Expand All @@ -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<object>(), fontFamilies.FirstIndexWhere(f => f == "Arial" || f == "Helvetica")),
new StaticListChoiceProperty(C.Properties.FontFamily.ToString(), fontFamilies.ToArray<object>(), fontFamilies.FirstIndexWhere(f => f.Name == "Arial" || f.Name == "Helvetica")),
new StaticListChoiceProperty(C.Properties.TextAlignment, Enum.GetNames(typeof(C.TextAlignmentOptions)).ToArray<object>(), 0),
new BooleanProperty(C.Properties.Bold.ToString(), false),
new BooleanProperty(C.Properties.Italic.ToString(), false),
Expand All @@ -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<StringProperty>(C.Properties.Text.ToString()).Value;
helper.FontFamily = newToken.GetProperty<StaticListChoiceProperty>(C.Properties.FontFamily.ToString()).Value.ToString();
helper.FontFamily = (FontFamily)newToken.GetProperty<StaticListChoiceProperty>(C.Properties.FontFamily.ToString()).Value;
helper.FontSize = newToken.GetProperty<Int32Property>(C.Properties.FontSize.ToString()).Value;
helper.LetterSpacing = newToken.GetProperty<DoubleProperty>(C.Properties.LetterSpacing.ToString()).Value;
helper.LineSpacing = newToken.GetProperty<DoubleProperty>(C.Properties.LineSpacing.ToString()).Value;
helper.AntiAliasLevel = newToken.GetProperty<Int32Property>(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<StaticListChoiceProperty>(C.Properties.TextAlignment.ToString())
.Value.ToString());
if (newToken.GetProperty<BooleanProperty>(C.Properties.Bold.ToString()).Value && fontFamily.IsStyleAvailable(FontStyle.Bold))
if (newToken.GetProperty<BooleanProperty>(C.Properties.Bold.ToString()).Value && helper.FontFamily.IsStyleAvailable(FontStyle.Bold))
{
helper.FontStyle |= FontStyle.Bold;
}
if (newToken.GetProperty<BooleanProperty>(C.Properties.Italic.ToString()).Value && fontFamily.IsStyleAvailable(FontStyle.Italic))
if (newToken.GetProperty<BooleanProperty>(C.Properties.Italic.ToString()).Value && helper.FontFamily.IsStyleAvailable(FontStyle.Italic))
{
helper.FontStyle |= FontStyle.Italic;
}
if (newToken.GetProperty<BooleanProperty>(C.Properties.Underline.ToString()).Value && fontFamily.IsStyleAvailable(FontStyle.Underline))
if (newToken.GetProperty<BooleanProperty>(C.Properties.Underline.ToString()).Value && helper.FontFamily.IsStyleAvailable(FontStyle.Underline))
{
helper.FontStyle |= FontStyle.Underline;
}
if (newToken.GetProperty<BooleanProperty>(C.Properties.Strikeout.ToString()).Value && fontFamily.IsStyleAvailable(FontStyle.Strikeout))
if (newToken.GetProperty<BooleanProperty>(C.Properties.Strikeout.ToString()).Value && helper.FontFamily.IsStyleAvailable(FontStyle.Strikeout))
{
helper.FontStyle |= FontStyle.Strikeout;
}
Expand All @@ -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(
Expand All @@ -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;
Expand Down