Jump to content

Slab Text (May 19, 2022)


Recommended Posts

Posted (edited)

Resizes and draws line/s of text to a specified width

 

Screenshot-SlabText.png.3d237468834101222f5e723372ad275b.png

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

 

Spoiler
        protected 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;
        }

 

 

SlabText.zip

Edited by VeLC
Version 1.1
  • Like 6
  • Upvote 2
Link to comment
Share on other sites

Posted (edited)

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.

Edited by VeLC
typo
Link to comment
Share on other sites

  • VeLC changed the title to Slab Text (May 19, 2022)
Posted (edited)

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.

Edited by VeLC
typo
  • Like 2
  • Upvote 1
Link to comment
Share on other sites

  • 2 weeks later...

Join the conversation

You can post now and register later. If you have an account, sign in now to post with your account.

Guest
Reply to this topic...

×   Pasted as rich text.   Paste as plain text instead

  Only 75 emoji are allowed.

×   Your link has been automatically embedded.   Display as a link instead

×   Your previous content has been restored.   Clear editor

×   You cannot paste images directly. Upload or insert images from URL.

 Share

×
×
  • Create New...