VeLC
-
Posts
17 -
Joined
-
Last visited
-
Days Won
6
Posts posted by VeLC
-
-
Feel free to suggest a better name for the plugin. There seems to be no technical term for this effect / format as per this old StackExchange.
Current "Slab" name is a reference to Erik Loyer's more advanced Slabtype Algorithm that achieves something similar.
-
Resizes and draws line/s of text to a specified width
Found in Effects > Text Formations
Versions:
1.0(20220513) - Initial Release
1.1(20220519) - Add 'Convert to all caps' and 'Set spacing from baseline' options. Prevent crash when selected font does not contain glyph/s of typed letters.
C# Code Snippet (Modified from CodeLab generated code)
License: LPGL-3.0
Spoilerprotected override void OnRender(Rectangle[] rois, int startIndex, int length) { if (length == 0 || IsCancelRequested) return; Render(DstArgs.Surface, SrcArgs.Surface, EnvironmentParameters.SelectionBounds); } #region UICode private MultiLineTextboxControl text = ""; // [1000] private CheckboxControl allCaps = false; // Convert to all caps private FontFamily fontFamily = new("Arial"); // private CheckboxControl bold = false; // Bold private CheckboxControl italic = false; // Italic private IntSliderControl targetWidth = 150; // [1,1000] Width private IntSliderControl spacing = 15; // [0,100] Spacing private CheckboxControl baseline = false; // Set spacing from baseline private PanSliderControl center = Pair.Create(0.000, 0.000); // Center #endregion private void Render(Surface dst, Surface src, Rectangle rect) { dst.CopySurface(src, rect.Location, rect); if (text == string.Empty || EnvironmentParameters.PrimaryColor.A == 0) return; using Graphics g = new RenderArgs(dst).Graphics; using Region gClipRegion = new(rect); g.Clip = gClipRegion; g.SmoothingMode = SmoothingMode.AntiAlias; g.TextRenderingHint = TextRenderingHint.AntiAlias; float centerX = (float)(rect.Left + (center.First + 1) / 2 * rect.Width); float centerY = (float)(rect.Top + (center.Second + 1) / 2 * rect.Height); g.TranslateTransform(centerX, centerY); StringSplitOptions options = StringSplitOptions.RemoveEmptyEntries | StringSplitOptions.TrimEntries; string[] lines = text.Split(new string[] { "\r\n", "\r", "\n" }, options); Pair<Bitmap, Rectangle>[] pairs = new Pair<Bitmap, Rectangle>[lines.Length]; int totalHeight = 0; try { for (int i = 0; i < lines.Length; ++i) { string line = allCaps ? lines[i].ToUpperInvariant() : lines[i]; pairs[i] = MakeBitmap(g, line); if (IsCancelRequested) return; totalHeight += pairs[i].Second.Height; } totalHeight += (lines.Length - 1) * spacing; g.TranslateTransform(targetWidth / -2f, totalHeight / -2f); foreach (Pair<Bitmap, Rectangle> pair in pairs) { if (IsCancelRequested) return; g.DrawImageUnscaled(pair.First, -pair.Second.Left, -pair.Second.Top); g.TranslateTransform(0, pair.Second.Height + spacing); } } finally { foreach (Pair<Bitmap, Rectangle> pair in pairs) { pair.First?.Dispose(); } } } private Pair<Bitmap, Rectangle> MakeBitmap(Graphics g, string line) { FontStyle fontStyle = FontStyle.Regular; if (bold) fontStyle |= FontStyle.Bold; if (italic) fontStyle |= FontStyle.Italic; using Brush brush = new SolidBrush(EnvironmentParameters.PrimaryColor); StringFormat format = new(StringFormat.GenericDefault); format.FormatFlags |= StringFormatFlags.NoFontFallback; Font refFont = new(fontFamily, 10, fontStyle, GraphicsUnit.Pixel); float refWidth = g.MeasureString(line, refFont, short.MaxValue, format).Width; while (!IsCancelRequested) { Font newFont = new(fontFamily, refFont.Size * targetWidth / refWidth, fontStyle, GraphicsUnit.Pixel); SizeF newSize = g.MeasureString(line, newFont, short.MaxValue, format); int width = (int)Math.Ceiling(newSize.Width); int height = (int)Math.Ceiling(newSize.Height); Bitmap bmp = new(width, height, PixelFormat.Format32bppArgb); using Graphics bmpG = Graphics.FromImage(bmp); bmpG.SmoothingMode = SmoothingMode.AntiAlias; bmpG.TextRenderingHint = TextRenderingHint.AntiAlias; bmpG.Clear(Color.Transparent); bmpG.DrawString(line, newFont, brush, 0, 0, format); Rectangle boundingBox = GetBoundingBox(bmp); if (boundingBox.Width == targetWidth) { if (baseline) { int bottom = GetBaseline(g, newFont, brush, format); boundingBox = Rectangle.FromLTRB(boundingBox.Left, boundingBox.Top, boundingBox.Right, bottom); } newFont.Dispose(); refFont.Dispose(); return new(bmp, boundingBox); } else { bmp.Dispose(); refFont.Dispose(); refFont = newFont; refWidth = boundingBox.Width; } } refFont.Dispose(); return default; } private static int GetBaseline(Graphics g, Font font, Brush brush, StringFormat format) { SizeF size = g.MeasureString("Z", font, short.MaxValue, format); int width = (int)Math.Ceiling(size.Width); int height = (int)Math.Ceiling(size.Height); using Bitmap bmp = new(width, height, PixelFormat.Format32bppArgb); using Graphics bmpG = Graphics.FromImage(bmp); bmpG.SmoothingMode = SmoothingMode.AntiAlias; bmpG.TextRenderingHint = TextRenderingHint.AntiAlias; bmpG.Clear(Color.Transparent); bmpG.DrawString("Z", font, brush, 0, 0, format); return GetBoundingBox(bmp).Bottom; } private static Rectangle GetBoundingBox(Bitmap bmp) { Rectangle rect = new(0, 0, bmp.Width, bmp.Height); BitmapData bmpData = bmp.LockBits(rect, ImageLockMode.ReadOnly, PixelFormat.Format32bppArgb); byte[] bytes = new byte[bmpData.Stride * bmpData.Height]; Marshal.Copy(bmpData.Scan0, bytes, 0, bytes.Length); int bottom = GetBottom(bytes, bmpData); int left = GetLeft(bytes, bmpData); int right = GetRight(bytes, bmpData); int top = GetTop(bytes, bmpData); bmp.UnlockBits(bmpData); return Rectangle.FromLTRB(left, top, right, bottom); } private static int GetBottom(byte[] bytes, BitmapData data) { for (int row = data.Height - 1; row >= 0; --row) { for (int col = 0; col < data.Width; ++col) { int alpha = bytes[row * data.Stride + col * 4 + 3]; if (alpha > 0) return row; } } return data.Height - 1; } private static int GetLeft(byte[] bytes, BitmapData data) { for (int col = 0; col < data.Width; ++col) { for (int row = 0; row < data.Height; ++row) { int alpha = bytes[row * data.Stride + col * 4 + 3]; if (alpha > 0) return col; } } return 0; } private static int GetRight(byte[] bytes, BitmapData data) { for (int col = data.Width - 1; col >= 0; --col) { for (int row = 0; row < data.Height; ++row) { int alpha = bytes[row * data.Stride + col * 4 + 3]; if (alpha > 0) return col; } } return data.Width - 1; } private static int GetTop(byte[] bytes, BitmapData data) { for (int row = 0; row < data.Height; ++row) { for (int col = 0; col < data.Width; ++col) { int alpha = bytes[row * data.Stride + col * 4 + 3]; if (alpha > 0) return row; } } return 0; }
- 6
- 2
-
Version 1.2 - Code Update and slightly shorter UI
- 1
- 1
-
Version 1.5 - Code Update and slightly shorter UI
- 1
- 1
- 1
-
Version 1.1 - Add Loop Spacing and Initial Point Offset options
- 4
-
Draws text along an Archimedean spiral path
Found in Effects > Text Formations. The menu name is "Spir@l Text" to differentiate with dpy's SpiralText
Versions:
1.0(20220506) - Initial Release
1.1(20220507) - Add Loop Spacing and Initial Point Offset options
1.2(20220507) - Code Update and slightly shorter UI
CodeLab Code
License: LPGL-3.0
Spoiler// Name: Spir@l Text // Submenu: Text Formations // Author: Louie Velarde // Title: Spiral Text // Version: 1.2 // Desc: Draws text along an Archimedean spiral path // Keywords: spiral text // URL: https://forums.getpaint.net/topic/119979-spiral-text/ // Help: https://forums.getpaint.net/topic/119979-spiral-text/ // Force Single Render Call #region UICode TextboxControl text = ""; // [18000] IntSliderControl repeat = 1; // [1,1800] Repeat FontFamily fontFamily = new FontFamily("Arial"); // IntSliderControl fontSize = 12; // [8,288] CheckboxControl bold = false; // Bold PanSliderControl center = Pair.Create(0.000, 0.000); // Center ListBoxControl spiralType = 0; // |Clockwise Inwards|Clockwise Outwards|Counterclockwise Inwards|Counterclockwise Outwards AngleControl rotation = 0; // [0,360] CheckboxControl useFontSize = true; // Calculate spacing based on font size IntSliderControl charSpacing = 0; // [0, 100] Character Spacing DoubleSliderControl lineSpacing = 1; // [0.001, 10] {useFontSize} Line Spacing IntSliderControl lineHeight = 25; // [1, 100] {!useFontSize} Loop Spacing ListBoxControl offsetType = 0; // |Initial Loop Number | Initial Point Offset IntSliderControl offset = 2; // [0,100] #endregion bool rendered; void PreRender(Surface dst, Surface src) { rendered = false; } void Render(Surface dst, Surface src, Rectangle rect) { if (rendered) return; rendered = true; rect = EnvironmentParameters.SelectionBounds; dst.CopySurface(src, rect.Location, rect); if (text == string.Empty) return; StringBuilder sb = new StringBuilder(text); for (int i = 2; i <= repeat; ++i) { if (IsCancelRequested) return; sb.Append(text); } using (Graphics g = new RenderArgs(dst).Graphics) using (Region gClipRegion = new Region(rect)) using (Font font = new Font(fontFamily, fontSize, bold ? FontStyle.Bold : FontStyle.Regular)) using (Brush brush = new SolidBrush(EnvironmentParameters.PrimaryColor)) { g.Clip = gClipRegion; g.SmoothingMode = SmoothingMode.AntiAlias; g.TextRenderingHint = TextRenderingHint.AntiAlias; float centerX = (float) (rect.Left + (center.First + 1) / 2 * rect.Width); float centerY = (float) (rect.Top + (center.Second + 1) / 2 * rect.Height); g.TranslateTransform(centerX, centerY); StringFormat format = new StringFormat(StringFormat.GenericTypographic); format.FormatFlags |= StringFormatFlags.MeasureTrailingSpaces; format.FormatFlags |= StringFormatFlags.NoFontFallback; double totalHeights = 0; SizeF[] sizes = new SizeF[sb.Length]; for (int i = 0; i < sb.Length; ++i) { if (IsCancelRequested) return; sizes[i] = g.MeasureString(sb[i].ToString(), font, short.MaxValue, format); totalHeights += sizes[i].Height; } double avgHeight = totalHeights / sb.Length; double b = (useFontSize ? avgHeight * lineSpacing : lineHeight) / Math.PI / 2; double len = offsetType == 0 ? b * Math.Pow(Math.PI * 2 * offset, 2) / 2 : offset * offset / b / 2; double radian = Math.Sqrt(2 * len / b); if (spiralType < 2) { g.RotateTransform((float) (90 - rotation)); } else { g.RotateTransform((float) (-90 - rotation)); } if ((spiralType & 1) == 0) { int s = spiralType == 0 ? -1 : 1; double radius = b * radian * s; double toDeg = 180 / Math.PI * s; int startIndex = sb.Length - 1; g.DrawString(sb[startIndex].ToString(), font, brush, sizes[startIndex].Width / -2, (float) (radius - avgHeight / 2), format); for (int i = startIndex - 1; i >=0; --i) { if (IsCancelRequested) return; len += sizes[i + 1].Width / 2 + charSpacing + sizes[i].Width / 2; double newRadian = Math.Sqrt(2 * len / b); g.RotateTransform((float) ((newRadian - radian) * toDeg)); radius = b * newRadian * s; g.DrawString(sb[i].ToString(), font, brush, sizes[i].Width / -2, (float) (radius - avgHeight / 2), format); radian = newRadian; } } else { int s = spiralType == 1 ? -1 : 1; double radius = b * radian * s; double toDeg = 180 / Math.PI * s * -1; g.DrawString(sb[0].ToString(), font, brush, sizes[0].Width / -2, (float) (radius - avgHeight / 2), format); for (int i = 1; i < sb.Length; ++i) { if (IsCancelRequested) return; len += sizes[i - 1].Width / 2 + charSpacing + sizes[i].Width / 2; double newRadian = Math.Sqrt(2 * len / b); g.RotateTransform((float) ((newRadian - radian) * toDeg)); radius = b * newRadian * s; g.DrawString(sb[i].ToString(), font, brush, sizes[i].Width / -2, (float) (radius - avgHeight / 2), format); radian = newRadian; } } } }
- 3
- 3
- 1
-
Version 1.4 - Fix space character not rendering when Character Spacing is zero and slightly shorter UI
- 3
-
Version 1.1 - Add Initial Loop Number / Initial Point Offset option. Add Center on Top option to change the order on which the dots are drawn and/or colored.
- 1
-
Version 1.1 - Add option for Initial Loop Number / larger Initial Point Offset increments
- 1
-
Draws dots along the path of an Archimedean spiral
Found in Effects > Render
Versions:
1.0(20220502) - Initial Release
1.1(20220504) - Add Initial Loop Number / Initial Point Offset option. Add Center on Top option to change the order on which the dots are drawn and/or colored.
C# Code Snippet (Modified from CodeLab generated code)
License: LPGL-3.0
Spoilerprotected override void OnRender(Rectangle[] rois, int startIndex, int length) { if (length == 0 || IsCancelRequested) return; Render(DstArgs.Surface, SrcArgs.Surface, EnvironmentParameters.SelectionBounds); } #region UICode private DoubleSliderControl loops = 5; // [2,100] Loops private IntSliderControl spacing = 25; // [1,100] Loop Spacing private ListBoxControl stepType = 1; // |Point Alignment|Point Spacing private DoubleSliderControl step = 10; // [1,360] private ListBoxControl offsetType = 1; // |Initial Loop Number|Initial Point Offset private IntSliderControl offset = 0; // [0,100] private IntSliderControl size = 5; // [1,100] Dot Size && Color private ListBoxControl color = 0; // |Primary Color|Secondary Color|Raindow 1|Rainbow 2 private IntSliderControl startHue = 0; // [0,360,1] private IntSliderControl transparency = 255; // [0,255,4] private CheckboxControl centerOnTop = true; // Center on Top private CheckboxControl clockwise = true; // Clockwise private PanSliderControl center = Pair.Create(0.000, 0.000); // Center private AngleControl rotation = 0; // [0,360] Rotation #endregion private List<Vector3> cachedPoints; private State cachedState; private void Render(Surface dst, Surface src, Rectangle rect) { dst.CopySurface(src, rect.Location, rect); State state = new(this); List<Vector3> points = state.Equals(cachedState) ? cachedPoints : stepType == 0 ? GenerateAlignedPoints() : GenerateEquidistantPoints(); if (IsCancelRequested) return; List<Vector3> ordered; if (centerOnTop || color < 2) { ordered = points; } else { ordered = new(points); ordered.Reverse(); } using Graphics g = new RenderArgs(dst).Graphics; using Region gClipRegion = new(rect); g.Clip = gClipRegion; g.SmoothingMode = SmoothingMode.AntiAlias; g.TextRenderingHint = TextRenderingHint.AntiAlias; float centerX = (float)(rect.Left + (center.First + 1) / 2 * rect.Width); float centerY = (float)(rect.Top + (center.Second + 1) / 2 * rect.Height); g.TranslateTransform(centerX, centerY); g.RotateTransform((float)-rotation); using SolidBrush brush = new(Color.Transparent); if (color == 0) { brush.Color = EnvironmentParameters.PrimaryColor; } else if (color == 1) { brush.Color = EnvironmentParameters.SecondaryColor; } HsvColor hsv = new(startHue == 0 ? 360 : startHue - 1, 100, 100); float halfSize = size / 2f; foreach (Vector3 v in ordered) { if (IsCancelRequested) return; if (color == 2) { hsv.Hue = (hsv.Hue + 1) % 361; ColorBgra bgra = hsv.ToColor(); bgra.A = (byte)transparency; brush.Color = bgra; } else if (color == 3) { hsv.Hue = (startHue + (int)Math.Round(v.Z, MidpointRounding.ToEven)) % 361; ColorBgra bgra = hsv.ToColor(); bgra.A = (byte)transparency; brush.Color = bgra; } g.FillEllipse(brush, v.X - halfSize, v.Y - halfSize, size, size); } cachedPoints = points; cachedState = state; } private List<Vector3> GenerateAlignedPoints() { double a = offsetType == 1 ? offset : offset * spacing; double b = spacing / 360.0; double toRad = Math.PI / (clockwise ? 180 : -180); List<Vector3> points = new(); for (double degree = loops * 360; degree >= 0; degree -= step) { if (IsCancelRequested) return null; double radius = a + b * degree; (double sin, double cos) = Math.SinCos(degree * toRad); float x = (float)(radius * cos); float y = (float)(radius * sin); points.Add(new Vector3(x, y, (float)degree)); } return points; } private List<Vector3> GenerateEquidistantPoints() { double a = offsetType == 1 ? offset : offset * spacing; double b = spacing / Math.PI / 2; List<Vector3> points = new(); for (double radian = loops * Math.PI * 2; radian >= 0;) { if (IsCancelRequested) return null; double radius = a + b * radian; (double sin, double cos) = Math.SinCos(clockwise ? radian : -radian); float x = (float)(radius * cos); float y = (float)(radius * sin); float z = (float)(radian * 180 / Math.PI); points.Add(new Vector3(x, y, z)); radian -= step / radius; } return points; } internal class State { private readonly double loops; private readonly int spacing; private readonly byte stepType; private readonly double step; private readonly byte offsetType; private readonly int offset; private readonly bool clockwise; public State(ArchimedeanDotsEffectPlugin plugin) { loops = plugin.loops; spacing = plugin.spacing; stepType = plugin.stepType; step = plugin.step; offsetType = plugin.offsetType; offset = plugin.offset; clockwise = plugin.clockwise; } public override bool Equals(object obj) { return obj is State state && loops == state.loops && spacing == state.spacing && stepType == state.stepType && step == state.step && offsetType == state.offsetType && offset == state.offset && clockwise == state.clockwise; } public override int GetHashCode() { return HashCode.Combine(loops, spacing, stepType, step, offsetType, offset, clockwise); } } }
- 4
- 1
-
Draws an Archimedean spiral
Found in Effects > Render
Versions:
1.0(20220502) - Initial Release
1.1(20220503) - Add option for Initial Loop Number / larger Initial Point Offset increments
1.2(20220506) - Fix Initial Point Offset "typo"
C# Code Snippet (Modified from CodeLab generated code)
License: LPGL-3.0
Spoilerprotected override void OnRender(Rectangle[] rois, int startIndex, int length) { if (length == 0 || IsCancelRequested) { return; } using CancellationTokenSource cts = new(); using System.Timers.Timer timer = new(20); timer.AutoReset = true; timer.Elapsed += (s, e) => { if (IsCancelRequested) { cts.Cancel(); } }; timer.Start(); try { Render(DstArgs.Surface, SrcArgs.Surface, EnvironmentParameters.SelectionBounds, cts.Token); } catch (OperationCanceledException) { } timer.Stop(); } #region UICode private DoubleSliderControl loops = 5; // [2,1000] Loops private IntSliderControl spacing = 25; // [1,100] Loop Spacing private DoubleSliderControl step = 10; // [1,360] Point Alignment private ListBoxControl offsetType = 1; // |Initial Loop Number|Initial Point Offset private IntSliderControl offset = 0; // [0,100] private DoubleSliderControl tension = 0.5; // [0,1] Line Curvature private IntSliderControl width = 1; // [1,100] Line Width private CheckboxControl clockwise = true; // Clockwise private PanSliderControl center = Pair.Create(0.000, 0.000); // Center private AngleControl rotation = 0; // [0,360] Rotation #endregion private PointF[] cachedPoints; private State cachedState; private void Render(Surface dst, Surface src, Rectangle rect, CancellationToken token) { dst.CopySurface(src, rect.Location, rect); using Graphics g = new RenderArgs(dst).Graphics; using Region gClipRegion = new(rect); g.Clip = gClipRegion; g.SmoothingMode = SmoothingMode.AntiAlias; g.TextRenderingHint = TextRenderingHint.AntiAlias; float centerX = (float)(rect.Left + (center.First + 1) / 2 * rect.Width); float centerY = (float)(rect.Top + (center.Second + 1) / 2 * rect.Height); g.TranslateTransform(centerX, centerY); g.RotateTransform((float)-rotation); if (IsCancelRequested) return; PointF[] points; State state = new(this); if (state.Equals(cachedState)) { points = cachedPoints; } else { double a = offsetType == 1 ? offset : offset * spacing; double b = spacing / 360.0; double toRad = Math.PI / (clockwise ? 180 : -180); points = Enumerable .Range(0, (int)(loops * 360 / step + 1)) .AsParallel() .AsOrdered() .WithCancellation(token) .Select(i => { double theta = i * step; double r = a + b * theta; double rad = theta * toRad; (double sin, double cos) = Math.SinCos(rad); float x = (float)(r * cos); float y = (float)(r * sin); return new PointF(x, y); }) .ToArray(); } using Pen pen = new(EnvironmentParameters.PrimaryColor, width); pen.StartCap = LineCap.Round; pen.EndCap = LineCap.Round; g.DrawCurve(pen, points, (float)tension); cachedPoints = points; cachedState = state; } internal class State { private readonly double loops; private readonly int spacing; private readonly double step; private readonly byte offsetType; private readonly int offset; private readonly bool clockwise; public State(ArchimedeanSpiralEffectPlugin plugin) { loops = plugin.loops; spacing = plugin.spacing; step = plugin.step; offsetType = plugin.offsetType; offset = plugin.offset; clockwise = plugin.clockwise; } public override bool Equals(object obj) { return obj is State state && loops == state.loops && spacing == state.spacing && step == state.step && offsetType == state.offsetType && offset == state.offset && clockwise == state.clockwise; } public override int GetHashCode() { return HashCode.Combine(loops, spacing, step, offsetType, offset, clockwise); } } }
- 5
- 1
-
Request: Make PaintDotNet.Effects.Effect.SignalCancelRequest virtual or add a method that returns cancellationTokenSource.Token for code like
Enumerable .Range(0, 100,000) .AsParallel() .AsOrdered() .WithCancellation(token) ...
-
Version 1.3 - Now works faster if there are multiple selections
- 3
- 1
-
Version 1.2 - Update code to make it compatible with CodeLab v6.1 (and below?). Also, detects if user canceled.
- 1
- 1
- 1
-
Version 1.1 - Now works with selection and slightly shorter UI
- 2
- 1
-
- Popular Post
- Popular Post
Draws text along an arc
This is basically dpy's Circle Text plugin with a different UI and better math for proportional fonts
Also in Effects > Text Formations
Versions:
1.0(20220418) - Initial Release
1.1(20220420) - Now works with selection and slightly shorter UI
1.2(20220421) - Update code to make it compatible with CodeLab v6.1 (and below?). Also, detects if user canceled.
1.3(20220428) - Now works faster if there are multiple selections
1.4(20220506) - Fix space character not rendering when Character Spacing is zero and slightly shorter UI
1.5(20220513) - Code Update and slightly shorter UI
CodeLab Code
License: LPGL-3.0
Spoiler// Name: Arc Text // Submenu: Text Formations // Author: Louie Velarde // Title: Arc Text // Version: 1.5 // Desc: Draws text along an arc // Keywords: arc text|circle text|circular text // URL: https://forums.getpaint.net/topic/119904-arc-text/ // Help: https://forums.getpaint.net/topic/119904-arc-text/ // Force Single Render Call #region UICode TextboxControl text = ""; // [3600] IntSliderControl repeat = 1; // [1,360] Repeat FontFamily fontFamily = new FontFamily("Arial"); // IntSliderControl fontSize = 12; // [8,288] CheckboxControl bold = false; // Bold PanSliderControl center = Pair.Create(0.000, 0.000); // Center ListBoxControl alignment = 0; // |Start Position|Center Position|End Position AngleControl position = 135; // [0,360] CheckboxControl clockwise = true; // Clockwise CheckboxControl useAngle = true; // Calculate character spacing based on arc angle CheckboxControl endSpace = false; // Append space after last character IntSliderControl charSpacing = 0; // [0,100] {!useAngle} Character Spacing IntSliderControl angle = 90; // [0,360] {useAngle} Arc Angle IntSliderControl radius = 150; // [1,1000] Arc Radius #endregion bool rendered; void PreRender(Surface dst, Surface src) { rendered = false; } void Render(Surface dst, Surface src, Rectangle rect) { if (rendered) return; rendered = true; rect = EnvironmentParameters.SelectionBounds; dst.CopySurface(src, rect.Location, rect); if (text == string.Empty) return; StringBuilder line = new StringBuilder(text); for (int i = 2; i <= repeat; ++i) { if (IsCancelRequested) return; line.Append(text); } using (Graphics g = new RenderArgs(dst).Graphics) using (Region gClipRegion = new Region(rect)) using (Font font = new Font(fontFamily, fontSize, bold ? FontStyle.Bold : FontStyle.Regular)) using (Brush brush = new SolidBrush(EnvironmentParameters.PrimaryColor)) { g.Clip = gClipRegion; g.SmoothingMode = SmoothingMode.AntiAlias; g.TextRenderingHint = TextRenderingHint.AntiAlias; double translateX = rect.Left + (center.First + 1) / 2 * rect.Width; double translateY = rect.Top + (center.Second + 1) / 2 * rect.Height; g.TranslateTransform((float) translateX, (float) translateY); StringFormat format = new StringFormat(StringFormat.GenericTypographic); format.FormatFlags |= StringFormatFlags.MeasureTrailingSpaces; format.FormatFlags |= StringFormatFlags.NoFontFallback; SizeF[] sizes = new SizeF[line.Length]; double totalWidths = 0; double totalHeights = 0; for (int i = 0; i < line.Length; ++i) { if (IsCancelRequested) return; sizes[i] = g.MeasureString(line[i].ToString(), font, short.MaxValue, format); totalWidths += sizes[i].Width; totalHeights += sizes[i].Height; } double height = totalHeights / line.Length; double arcLength; if (useAngle) { arcLength = Math.PI * radius * angle / 180; } else { arcLength = totalWidths + charSpacing * (line.Length - 1); if (endSpace) arcLength += charSpacing; } double arcAngle = arcLength * 180 / Math.PI / radius; double spaceLength = (arcLength - totalWidths) / (line.Length - (endSpace ? 0 : 1)); double spaceAngle = spaceLength * 180 / Math.PI / radius; int s = clockwise ? 1 : -1; switch (alignment) { case 0: g.RotateTransform((float) (s * 90 - position)); break; case 1: g.RotateTransform((float) (s * 90 - position - s * arcAngle / 2)); break; case 2: g.RotateTransform((float) (s * 90 - position - s * arcAngle)); break; } double halfCharAngle = sizes[0].Width * s * 90 / Math.PI / radius; g.RotateTransform((float) (halfCharAngle)); float y = (float) (clockwise ? -radius : radius - height); g.DrawString(line[0].ToString(), font, brush, sizes[0].Width / -2, y, format); for (int i = 1; i < line.Length; ++i) { if (IsCancelRequested) return; g.RotateTransform((float) (halfCharAngle + s * spaceAngle)); halfCharAngle = sizes[i].Width * s * 90 / Math.PI / radius; g.RotateTransform((float) (halfCharAngle)); g.DrawString(line[i].ToString(), font, brush, sizes[i].Width / -2, y, format); } } }
- 6
- 1
- 3
Slab Text (May 19, 2022)
in Plugins - Publishing ONLY!
Posted · Edited by VeLC
typo
Version 1.1 - Add 'Convert to all caps' and 'Set spacing from baseline' options. Prevent crash when selected font does not contain glyph/s of typed letter/s.