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 5
  • Upvote 1
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

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...