From 91c6c275519a1e49a018c542d726030008a30918 Mon Sep 17 00:00:00 2001 From: simmetric Date: Tue, 22 Aug 2017 15:15:56 +0200 Subject: [PATCH 1/7] Tryout for working with non-rectangular selections --- SpacedText/EffectConfigDialog.cs | 44 ++++++++ SpacedText/Extent.cs | 42 +++++++ SpacedText/LineInfo.cs | 11 ++ SpacedText/SpacedText.cs | 143 +++++++++++++++++------- SpacedText/SpacedText.csproj | 6 + SpacedText/SpacedTextEffectsPlugin.cs | 4 +- SpacedText/SpacedTextEffectsPluginV2.cs | 30 +++++ 7 files changed, 238 insertions(+), 42 deletions(-) create mode 100644 SpacedText/EffectConfigDialog.cs create mode 100644 SpacedText/Extent.cs create mode 100644 SpacedText/LineInfo.cs create mode 100644 SpacedText/SpacedTextEffectsPluginV2.cs diff --git a/SpacedText/EffectConfigDialog.cs b/SpacedText/EffectConfigDialog.cs new file mode 100644 index 0000000..eed8844 --- /dev/null +++ b/SpacedText/EffectConfigDialog.cs @@ -0,0 +1,44 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace SpacedTextPlugin +{ + using System.Drawing; + using System.Windows.Forms; + using PaintDotNet.Effects; + + public class SpacedTextEffectConfigDialog : EffectConfigDialog + { + private ComboBox fontName; + + public SpacedTextEffectConfigDialog() + { + this.Size = new Size(800, 800); + + fontName = new ComboBox(); + fontName.Items.Add("Comic Sans"); + fontName.Items.Add("Courier New"); + fontName.Items.Add("Impact"); + fontName.Items.Add("Tahoma"); + fontName.Items.Add("Times New Roman"); + fontName.DrawMode = DrawMode.OwnerDrawFixed; + fontName.DrawItem += FontName_DrawItem; + fontName.Width = 300; + this.Controls.Add(fontName); + } + + private void FontName_DrawItem(object sender, DrawItemEventArgs e) + { + e.DrawBackground(); + + string itemText = ((ComboBox) sender).Items[e.Index].ToString(); + + Font font = new Font(itemText, fontName.ItemHeight - 6); + + e.Graphics.DrawString(itemText, font, Brushes.Black, e.Bounds.X, e.Bounds.Y); + } + } +} diff --git a/SpacedText/Extent.cs b/SpacedText/Extent.cs new file mode 100644 index 0000000..cc7e6c5 --- /dev/null +++ b/SpacedText/Extent.cs @@ -0,0 +1,42 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace SpacedTextPlugin +{ + using System.Drawing; + + public struct Extent + { + public int VerticalPosition { get; private set; } + public int Left { get; private set; } + public int Right { get; private set; } + public int Width { get; private set; } + + public Point Start + { + get + { + return new Point(Left, VerticalPosition); + } + } + + public Point End + { + get + { + return new Point(Right, VerticalPosition); + } + } + + public Extent(int left, int right, int y) + { + Left = left; + Right = right; + Width = right - left; + VerticalPosition = y; + } + } +} diff --git a/SpacedText/LineInfo.cs b/SpacedText/LineInfo.cs new file mode 100644 index 0000000..7fa4db2 --- /dev/null +++ b/SpacedText/LineInfo.cs @@ -0,0 +1,11 @@ +namespace SpacedTextPlugin +{ + using System.Drawing; + + public class LineInfo + { + public string Text { get; set; } + public Rectangle LineBounds { get; set; } + public Size TextSize { get; set; } + } +} diff --git a/SpacedText/SpacedText.cs b/SpacedText/SpacedText.cs index 1ade63f..8323657 100644 --- a/SpacedText/SpacedText.cs +++ b/SpacedText/SpacedText.cs @@ -26,6 +26,7 @@ internal class SpacedText : IDisposable public bool IsCancelRequested { get; set; } //public result + public PdnRegion SelectionRegion { get; private set; } public Rectangle Bounds { get; private set; } public Surface BufferSurface { get; private set; } @@ -47,11 +48,12 @@ public SpacedText() imgAttr.SetRemapTable(colorMap); } - public void RenderText(Rectangle bounds) + public void RenderText(PdnRegion selection) { try { - Bounds = bounds; + SelectionRegion = selection; + Bounds = selection.GetBoundsInt(); Font font = new Font(FontFamily, FontSize*AntiAliasLevel, FontStyle, GraphicsUnit.Pixel); //render text on larger bitmap so it can be anti-aliased while scaling down @@ -62,18 +64,15 @@ public void RenderText(Rectangle bounds) gr.CompositingMode = CompositingMode.SourceOver; gr.Clear(Color.Black); - //letterspacing may be changed during execution - double letterSpacing = LetterSpacing; - if (!IsCancelRequested) { //split in lines - List lines = LineWrap(gr, font, letterSpacing, bm); + List lines = LineWrap(gr, font, bm); if (!IsCancelRequested) { //draw lines - DrawLines(lines, gr, font, letterSpacing, bm); + DrawLines(lines, gr, font, bm); } } @@ -100,46 +99,45 @@ public void RenderText(Rectangle bounds) } } - private void DrawLines(List lines, Graphics gr, Font font, double letterSpacing, Bitmap bm) + private void DrawLines(List lines, Graphics gr, Font font, Bitmap bm) { - int lineStart = 0; - foreach (string line in lines) + foreach (LineInfo line in lines) { - if (IsCancelRequested || lineStart > Bounds.Bottom * AntiAliasLevel) + if (IsCancelRequested || line.LineBounds.Top > Bounds.Bottom * AntiAliasLevel) { break; } - if (!string.IsNullOrWhiteSpace(line)) + if (!string.IsNullOrWhiteSpace(line.Text)) { - int left = FontSize / 2; + int left = (line.LineBounds.Left * AntiAliasLevel) + (FontSize / 2); if (TextAlign != C.TextAlignmentOptions.Justify) { - //measure text - Size textBounds = PInvoked.MeasureString(gr, line, font, letterSpacing); + //apply alignment: determine horizontal start position if (TextAlign == C.TextAlignmentOptions.Center) { - left = bm.Width / 2 - textBounds.Width / 2; + left = line.LineBounds.Left + (bm.Width / 2 - line.TextSize.Width / 2); } else if (TextAlign == C.TextAlignmentOptions.Right) { - left = bm.Width - (textBounds.Width + FontSize); + left = line.LineBounds.Left + (bm.Width - (line.TextSize.Width + FontSize)); } - if (textBounds.Width > 0 && textBounds.Height > 0 && - textBounds.Width * AntiAliasLevel < C.MaxBitmapSize && - textBounds.Height * AntiAliasLevel < C.MaxBitmapSize) + if (line.TextSize.Width > 0 && line.TextSize.Height > 0 && + line.TextSize.Width * AntiAliasLevel < C.MaxBitmapSize && + line.TextSize.Height * AntiAliasLevel < C.MaxBitmapSize) { //create new bitmap for line - Bitmap lineBm = new Bitmap(textBounds.Width * AntiAliasLevel, - textBounds.Height * AntiAliasLevel); + Bitmap lineBm = new Bitmap(line.TextSize.Width * AntiAliasLevel, + line.TextSize.Height * AntiAliasLevel); Graphics lineGr = Graphics.FromImage(lineBm); //draw text - PInvoked.TextOut(lineGr, line, 0, 0, font, letterSpacing); + PInvoked.TextOut(lineGr, line.Text, 0, 0, font, LetterSpacing); //draw lineBm to bm leaving out black - gr.DrawImage(lineBm, new Rectangle(new Point(left, lineStart), lineBm.Size), 0, 0, + gr.DrawRectangle(Pens.Gray, line.LineBounds); + gr.DrawImage(lineBm, new Rectangle(new Point(left * AntiAliasLevel, line.LineBounds.Top * AntiAliasLevel), lineBm.Size), 0, 0, lineBm.Width, lineBm.Height, GraphicsUnit.Pixel, imgAttr); lineGr.Dispose(); @@ -149,69 +147,85 @@ private void DrawLines(List lines, Graphics gr, Font font, double letter else { //measure text without spaces - string lineWithoutSpaces = line.Replace(" ", string.Empty); - Size textBounds = PInvoked.MeasureString(gr, lineWithoutSpaces, font, letterSpacing); + string lineWithoutSpaces = line.Text.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); + Math.Max(line.Text.Length - lineWithoutSpaces.Length, 1); } //create new bitmap for line Bitmap lineBm = new Bitmap(bm.Width, bm.Height); Graphics lineGr = Graphics.FromImage(lineBm); - //draw word by word with correct space in between.7 - foreach (string word in line.Split(' ')) + //draw word by word with correct space in between + foreach (string word in line.Text.Split(' ')) { //draw text - PInvoked.TextOut(lineGr, word, left, 0, font, letterSpacing); + PInvoked.TextOut(lineGr, word, left, 0, font, LetterSpacing); - Size wordBounds = PInvoked.MeasureString(lineGr, word, 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(0, lineStart), lineBm.Size), 0, 0, + gr.DrawImage(lineBm, new Rectangle(new Point(0, line.LineBounds.Top), lineBm.Size), 0, 0, lineBm.Width, lineBm.Height, GraphicsUnit.Pixel, imgAttr); lineGr.Dispose(); lineBm.Dispose(); } } - - lineStart += font.Height + (int)Math.Round(font.Height * LineSpacing); } } - private List LineWrap(Graphics gr, Font font, double letterSpacing, Bitmap bm) + private List LineWrap(Graphics gr, Font font, Bitmap bm) { string[] words = Text.Replace(Environment.NewLine, " " + Environment.NewLine + C.Space) .Split(new[] {C.SpaceChar}, StringSplitOptions.RemoveEmptyEntries); - List lines = new List(); + List lines = new List(); + + int y = 0; string currentLine = words.Any() ? words.First() + C.Space : string.Empty; + Rectangle currentLineBounds = DetermineLineBounds(TraceLineExtent(y), TraceLineExtent(y + font.Height)); + foreach (string word in words.Skip(1)) { //if manual line break: end current line and start new line if (word.Equals(Environment.NewLine)) { - lines.Add(currentLine.Trim()); + lines.Add(new LineInfo + { + Text = currentLine.Trim(), + LineBounds = currentLineBounds, + TextSize = new Size(1, font.Height) + }); + y += font.Height + (int)Math.Round(font.Height * LineSpacing); currentLine = string.Empty; continue; } //measure currentline + word //else add word to currentline - if (PInvoked.MeasureString(gr, currentLine + word, font, letterSpacing).Width > bm.Width - FontSize) + Size textBounds = PInvoked.MeasureString(gr, currentLine + word, font, LetterSpacing); + if (textBounds.Width > (currentLineBounds.Width * AntiAliasLevel) - FontSize) { //if outside bounds, then add line - lines.Add(currentLine.Trim()); + lines.Add(new LineInfo + { + Text = currentLine.Trim(), + LineBounds = currentLineBounds, + TextSize = textBounds + }); + y += font.Height + (int)Math.Round(font.Height * LineSpacing); currentLine = word + C.Space; + currentLineBounds = DetermineLineBounds(TraceLineExtent(y), TraceLineExtent(y + font.Height)); } else { @@ -221,11 +235,60 @@ private List LineWrap(Graphics gr, Font font, double letterSpacing, Bitm //add currentline if (!string.IsNullOrEmpty(currentLine)) { - lines.Add(currentLine.Trim()); + Size textBounds = PInvoked.MeasureString(gr, currentLine, font, LetterSpacing); + lines.Add(new LineInfo + { + Text = currentLine.Trim(), + LineBounds = currentLineBounds, + TextSize = textBounds + }); } return lines; } + private Rectangle DetermineLineBounds(Extent topLine, Extent bottomLine) + { + int maxLeftX = Math.Max(topLine.Left, bottomLine.Left); + int minRightX = Math.Min(topLine.Right, bottomLine.Right); + return new Rectangle(maxLeftX, topLine.VerticalPosition, minRightX-maxLeftX, bottomLine.VerticalPosition); + } + + /// + /// Returns the left and right visible extents of the current line in relation to the selection bounds + /// + /// + /// + private Extent TraceLineExtent(int lineY) + { + int leftX = Bounds.Left; + int rightX = Bounds.Right; + + lineY += Bounds.Top; + + //trace line inward from left to right + while (leftX < rightX) + { + if (SelectionRegion.IsVisible(leftX, lineY)) + { + break; + } + + leftX++; + } + + while (rightX > 0) + { + if (SelectionRegion.IsVisible(rightX, lineY)) + { + break; + } + + rightX--; + } + + return new Extent(leftX - Bounds.Left, rightX - Bounds.Left, lineY - Bounds.Top); + } + public void Dispose() { BufferSurface.Dispose(); diff --git a/SpacedText/SpacedText.csproj b/SpacedText/SpacedText.csproj index 4b11761..d4955d9 100644 --- a/SpacedText/SpacedText.csproj +++ b/SpacedText/SpacedText.csproj @@ -66,11 +66,17 @@ + + Form + + + +