Система.Рисование.Шрифт — изменить интервал между буквами?

#.net #fonts #drawing #system.drawing

#.net #шрифты #рисование #system.drawing

Вопрос:

Используя System.Drawing.Font , есть ли способ изменить интервал между буквами, точно так же, как вы можете изменить размер шрифта?

Я хочу увеличить интервал между буквами, чтобы соответствовать определенной ширине.

Если это невозможно, есть ли альтернативы для получения желаемого эффекта? Например, есть ли простой способ иметь несколько графиков, которые будут соответствовать моей конкретной ширине?

Комментарии:

1. Где вы используете этот шрифт? Что использует это?

2. Он используется в изображении GDI с Graphics.DrawString()

3. Нет встроенного механизма System.Drawing для рисования текста с отрегулированным интервалом между буквами. Для этого вам придется создать свой собственный код. Начните с просмотра MeasureCharacterRanges .

Ответ №1:

Я не думаю, что DrawString позволит вам указывать что-либо столь же подробное, как интервал между символами, но я бы создал вспомогательную функцию, которая вычисляет интервал на основе желаемой ширины, а затем рисует каждый символ по размеру.

Попробуйте это и посмотрите, как у вас получится…

     public void DrawSpacedText(Graphics g, Font font, Brush brush, PointF point, string text, int desiredWidth)
    {
        //Calculate spacing
        float widthNeeded = 0;
        foreach (char c in text)
        {
            widthNeeded  = g.MeasureString(c.ToString(), font).Width;
        }
        float spacing = (desiredWidth - widthNeeded) / (text.Length - 1);

        //draw text
        float indent = 0;
        foreach (char c in text)
        {
            g.DrawString(c.ToString(), font, brush, new PointF(point.X   indent, point.Y));
            indent  = g.MeasureString(c.ToString(), font).Width   spacing;
        }
    }
  

Вероятно, вы могли бы оптимизировать это, чтобы сделать только один вызов MeasureString для каждого символа. Или даже использовать MeasureCharacterRanges для получения массива (который, я считаю, в любом случае более точен)

Редактировать: вот пример использования MeasureCharacterRanges вместо этого…

     public void DrawSpacedText(Graphics g, Font font, Brush brush, PointF point, string text, int desiredWidth)
    {
        //Calculate spacing
        float widthNeeded = 0;
        Region[] regions = g.MeasureCharacterRanges(text, font, new RectangleF(point, new SizeF(desiredWidth, font.Height   10)), StringFormat.GenericDefault);
        float[] widths = new float[regions.Length];
        for(int i = 0; i < widths.Length; i  )
        {
            widths[i] = regions[i].GetBounds(g).Width;
            widthNeeded  = widths[i];
        }
        float spacing = (desiredWidth - widthNeeded) / (text.Length - 1);

        //draw text
        float indent = 0;
        int index = 0;
        foreach (char c in text)
        {
            g.DrawString(c.ToString(), font, brush, new PointF(point.X   indent, point.Y));
            indent  = widths[index]   spacing;
            index  ;
        }
    }
  

Комментарии:

1. 1 Это хороший способ сделать это. Однако наличие постоянного интервала приводит к кернингу текста , чего трудно добиться должным образом. Другими словами, результаты не будут такими визуально приятными , как при правильном кернинге, цитируя Википедию.

2. Это несколько наивный подход, предполагающий, что один символ === глиф. Нет биди, нет лигатур и так далее. Просто нижняя половина таблицы ASCII.

3. @olegz Если единственная цель — измерить строку, а не рисовать ее, будет ли этого достаточно?

4. @pelican_george, ответ «да», если строка не содержит символов CJK, диакритических знаков или вы не хотите, чтобы код был дружественным к i18n

5. Как бы вы подошли к этому, чтобы он был дружественным к i18n?

Ответ №2:

Этот код поддерживает новую строку в тексте и возвращает объект изображения.

 public static Image ConvertTextToImage(String text, Font font, Color textColor, int spacing = 0)
{
    var textParts = SplitOnBreakLines(text);
    // dummy image, just create drawing
    var img = new Bitmap(1, 1);
    var drawing = Graphics.FromImage(img);
    // calculate width, height
    var width = 0.0F;
    foreach (char c in text)
    {
        width  = drawing.MeasureString(c.ToString(), font).Width;
    }           
    width = (width   spacing * (ClearBreakLines(text).Length - 1))  / textParts.Length;
    if (width <= 0)
        width = 1;
    var height = font.Height * textParts.Length;
    // clear and create new objects
    img.Dispose();
    drawing.Dispose();

    img = new Bitmap((int)width, (int)height);
    drawing = Graphics.FromImage(img);

    //Adjust for high quality
    drawing.CompositingQuality = CompositingQuality.HighQuality;
    drawing.InterpolationMode = InterpolationMode.HighQualityBilinear;
    drawing.PixelOffsetMode = PixelOffsetMode.HighQuality;
    drawing.SmoothingMode = SmoothingMode.HighQuality;
    drawing.TextRenderingHint = TextRenderingHint.AntiAliasGridFit;

    //paint the background
    drawing.Clear(Color.Transparent);

    //create a brush for the text
    var textBrush = new SolidBrush(textColor);
    //write text
    var indent = 0.0F;
    var point = new Point();            
    foreach (var textPart in textParts)
    {
        foreach (char c in textPart)
        {
            drawing.DrawString(c.ToString(), font, textBrush, new PointF(point.X   indent, point.Y));
            indent  = drawing.MeasureString(c.ToString(), font).Width   spacing;
        }
        indent = 0.0F;
        point.Y  = font.Height;
    }
    drawing.Save();
    textBrush.Dispose();
    drawing.Dispose();
    return img;           
}

public static string ClearBreakLines(string s)
{
    return Regex.Replace(s, @"rn?|n", string.Empty);
}

public static string[] SplitOnBreakLines(string s)
{
    return Regex.Split(s, @"rn?|n");           
}