How to draw rich text with Skia or SkiaSharp - c#

I want to draw rich text like iOS's Attributed Text or Xamarin.Forms's FormattedString with SkiaSharp, but I can't find how to.
I found the DrawText method, but it's for simple text rendering with one color and one font. No mixed colors and/or fonts and no styles like bold, italic, strike-through, or underline.
Do I have to do it with my own rich text rendering logic?

This is doable using the SKPaint type. Basically, as you draw, you set the properties on the paint:
var paint = new SKPaint();
paint.StrikeThruText = true;
paint.TextSize = 24;
paint.Color = SKColors.Yellow;
paint.UnderlineText = true;
paint.Typeface = SKTypeface.FromFamilyName(
"Arial",
SKFontStyleWeight.Bold,
SKFontStyleWidth.Normal,
SKFontStyleSlant.Italic);
and then you can draw:
canvas.DrawText("Fancy Text", 30, 30, paint);
I hope this helps!
For other effects, you can use the SKShader and SKMaskFilter types. This does a blur:
path.MaskFilter = SKMaskFilter.CreateBlur(SKBlurStyle.Normal, 5);
EDIT
After some time, it seems we actually have a much better way to draw text - and not just the basic underlines. I would whole-heartedly recommend this library by Brad Robinson: https://github.com/toptensoftware/RichTextKit
I mean, just look at that beautiful thing!

Related

In C#, in tooltip how to change different color for differnt part of text?

In the MS chart, I am displaying tooltip using below code.
ToolTip ToolTip = new ToolTip();
ToolTip .Show(" X value:"+s+"\nLine 1 Y value: =" + ss + "\nLine 2 Y value:=" + ss1, chart, (int)e.Location.X, (int)e.Location.Y);
I am able to set only one foreground color using ToolTip .ForeColor = System.Drawing.Color.Red;.
I am new to C#.
How to assign a different colour and draw text in custom tooltip class or how to use HTML renderer to achieve my requirement?
I could not assign a different colour for a different part of the tooltip text.
How to achieve it?
You can owner-draw the tooltip
Example:
ToolTip ToolTip = new ToolTip();
ToolTip.OwnerDraw = true;
ToolTip.Popup += (ss, ee) => { ee.ToolTipSize = new Size(200, 50); };
ToolTip.Draw += (ss, ee) =>
{
ee.DrawBackground();
ee.DrawBorder();
ee.Graphics.DrawString("Warning", Font, Brushes.Red, 10, 1);
ee.Graphics.DrawString(ee.ToolTipText, Font, Brushes.Black, 1, 22);
};
ToolTip.Show("Demo only", somecontrol..);
This is just a simple example; there are many more parameters to style the tooltip, including drawing stuff, images, brushes of all types, etc..
It is also recommended to use TextRenderer instead of the classic GDI+ DrawString.
Note how I set the Size in the PopUp event!
All sorts of formatting is possible with the text; for multiline text it is recommended to use an overload with bounding rectangle instead of x/y coordinates and maybe also alignment with a StringFormat. Do note though, that is is always tricky to embed formatted parts inside of a text.
Possible, but tedious to get really right, as always with GDI drawing. -
The basic trick is to determine a bounding rectangle first; this can be done with MeasureString.
The short answer would be "natively you simply can't" (Except you count drawing the label yourself as natively). But as always in programming there are creative ways to get the result you want.
The answer in the Question Orel suggested basically makes use of a renderer for HTML Markup to render the styled text inside your application.
If you have a look at the newer version of this library here they actually provide a WinForms ToolTip Control which accepts HTML Markup to render inside it's content area.
To use this renderer they provide a nuget package which makes installation trivial. Simply manage your projects nuget packages and search for HtmlRenderer.WinForms and install the latest version. (Check if it also installs the latest Version of HtmlRenderer.Core because it didn't on my end and I had to update the Core package)
After this rebuild your project to get the new controls in your designer toolbox.
To test the package I dragged a textbox and a HtmlToolTip onto my Form. To set the new toolTip you use it just like a normal WinForms tooltip.
public partial class Form1 : Form
{
public Form1()
{
InitializeComponent();
this.htmlToolTip1.SetToolTip(this.textBox1, "<h1 style=\"color:red\">Hello</h1><i style=\"color:blue\">World</i>");
}
}
Now you can style your toolTip content with HTML markup and change the foreground colors as you like:

How do I use DrawString without trimming?

I find the way the DrawString function cuts off entire characters or words to be counterintuitive. Showing parts of the text clearly conveys that something is missing.
Here are some examples:
StringTrimming.None:
StringTrimming.Character:
What I want:
(GIMP mockup)
Is StringTrimming.None the same as StringTrimming.Character? They seem to behave exactly alike in my case. Is there something I could have overlooked or is this a known "feature"?
According to the docs StringTrimming.None "Specifies no trimming."
This site with examples created with Java even show "None" to trim more characters than "Character".
Are there other tricks to get this effect?
Note: I do not want to display "…" or similar. I find this to be a waste of space but that is probably a discussion for UX.
It's possible that the text appears to be trimmed because it's actually wrapping invisibly onto the next line. In the call to Graphics.DrawString, disable wrapping by passing a StringFormat object whose FormatFlags property is set to NoWrap:
StringFormat format =
new StringFormat
{
FormatFlags = StringFormatFlags.NoWrap,
Trimming = StringTrimming.None
};
g.DrawString(s, font, brush, rect, format);
As a workaround, you can give the DrawString method a bigger RectangleF than you really want, and set the clipping region of your Graphics object to the actual RectangleF you want to draw the string in.
RectangleF rect = new RectangleF(10, 10, 30, 15);
e.Graphics.DrawRectangle(Pens.Red, Rectangle.Truncate(rect));
RectangleF fakeRect = new RectangleF(rect.Location, new SizeF(short.MaxValue, short.MaxValue));
Region r = e.Graphics.Clip;
e.Graphics.SetClip(rect);
e.Graphics.DrawString("1 Default", this.Font, SystemBrushes.ControlText, fakeRect);
e.Graphics.Clip = r;
Note that this code assumes you wish to anchor your text to the top left corner of the rectangle. Otherwise you should generate the fake rectangle in a method that maintains the same anchor point as your intended layout rectangle.

Drawing string by GDI+ in exactly in specified point

I have problem with drawing string in c#
Here is my code:
Graphic.TextRenderingHint = System.Drawing.Text.TextRenderingHint.AntiAliasGridFit;
Pen pen = new Pen(brush, 2f);
Font font = new Font("Segoue UI", 15);
graphic.DrawString("2", font, brush, new PointF(0f, 0f));
Previously i created picturebox and i did graphic obiect from it.
The problem is that "2" is not drawing in (0;0) but in (4;5). Event if i turn off AntiAlias is still in (4;5).
I check it by drawing two lines from (0;0) to (200;0) and from (0;0) to (0;200) and according to them i calculated that error in drawing string.
Any ideas why? I need draw string in exactly point.
//Edit
i was also trying set font in this way:
Font font = new Font("Microsoft Sans Serif", 10);
and draw "2" in (0,0), the real position of "2" is (3,3). With drawing "1" error is the same (3;3).
Graphics.DrawString is automatically padding the box it uses to draw strings. The reason is that it's purpose is to draw label texts etc. for controls and therefor is "ready-made" so there is padding around the text.
Microsoft eventually realized that this approach wasn't the best for situations when one needed more accurate text drawing (ie. text editors etc.), and for that reason developed the TextRendered class which wraps GDI instead of GDI+ and give back the "old" more accurate string drawing.
Try:
TextRenderer.DrawText(e.Graphics, "2", font, new Point(0, 0), brush);
You might experience padding even with this, but here you can more reliable compensate for that. Check the link below to see what flags you can use with the method.
Note: if you need to measure text it's important to use the TextRenderer.MeasureText() for this.
For more details, go to:
http://msdn.microsoft.com/en-us/library/system.windows.forms.textrenderer.aspx
Flags:
http://msdn.microsoft.com/en-us/library/w3cdh3zw.aspx
what happens if your "string" has a descending letter, like a g or a j? each font allows for descending parts of letters, so you might need to account for that by looking at the font's metrics
http://msdn.microsoft.com/en-us/library/xwf9s90b.aspx

Using a FormattedText object, how do I find out how much of my text was actually displayed?

If I have code like this:
FormattedText text = new FormattedText(sTheBook,
System.Globalization.CultureInfo.CurrentUICulture,
System.Windows.FlowDirection.LeftToRight,
new Typeface("Times New Roman"),
13, Brushes.Black);
text.MaxTextWidth = 300;
text.MaxTextHeight = 600;
text.TextAlignment = TextAlignment.Justify;
dc.DrawText(text, new Point(10, 0));
...then, if it is long, only some of the text that I passed in (via sTheBook) will be displayed on the screen. I need to know how much was displayed so I can display the rest later! I can easily measure an amount of text, but it seems silly to do a search by rendering and re-rendering my text over and over until I find the piece that fits exactly.
I'd be open to using some other WPF text rendering technique if it has this functionality.
Thanks!
You could simply determine its size by looking at the Width and Height properties. That will give you the "desired size" after line breaking has been applied.

Multiple colors in a C# .NET label

I'm looking for a way to display multiple colors in a single C#/.NET label. E.g the label is displaying a series of csv separated values that each take on a color depending on a bucket they fall into. I would prefer not to use multiple labels, as the values are variable length and I don't want to play with dynamic layouts. Is there a native support for this?
There is no native control in .NET that does this. Your best bet is to write your own UserControl (call it RainbowLabel or something). Normally you would have a custom label control inherit directly from Label, but since you can't get multi-colored text in one label, you would just inherit from UserControl.
For rendering the text, your UserControl could split the text on commas and then dynamically load a differently-colored Label for each chunk. A better way, however, would be to render the text directly onto your UserControl using the DrawString and MeasureString methods in the Graphics namespace.
Writing UserControls in .NET is really not difficult, and this kind of unusual problem is exactly what custom UserControls are for.
Update: here's a simple method you can use for rendering the multi-colored text on a PictureBox:
public void RenderRainbowText(string Text, PictureBox pb)
{
// PictureBox needs an image to draw on
pb.Image = new Bitmap(pb.Width, pb.Height);
using (Graphics g = Graphics.FromImage(pb.Image))
{
// create all-white background for drawing
SolidBrush brush = new SolidBrush(Color.White);
g.FillRectangle(brush, 0, 0,
pb.Image.Width, pb.Image.Height);
// draw comma-delimited elements in multiple colors
string[] chunks = Text.Split(',');
brush = new SolidBrush(Color.Black);
SolidBrush[] brushes = new SolidBrush[] {
new SolidBrush(Color.Red),
new SolidBrush(Color.Green),
new SolidBrush(Color.Blue),
new SolidBrush(Color.Purple) };
float x = 0;
for (int i = 0; i < chunks.Length; i++)
{
// draw text in whatever color
g.DrawString(chunks[i], pb.Font, brushes[i], x, 0);
// measure text and advance x
x += (g.MeasureString(chunks[i], pb.Font)).Width;
// draw the comma back in, in black
if (i < (chunks.Length - 1))
{
g.DrawString(",", pb.Font, brush, x, 0);
x += (g.MeasureString(",", pb.Font)).Width;
}
}
}
}
Obviously this will break if you have more than 4 comma-delimited elements in your text, but you get the idea. Also, there appears to be a small glitch in MeasureString that makes it return a width that is a couple pixels wider than necessary, so the multi-colored string appears stretched out - you might want to tweak that part.
It should be straightforward to modify this code for a UserControl.
Note: TextRenderer is a better class to use for drawing and measuring strings, since it uses ints. Graphics.DrawString and .MeasureString use floats, so you'll get off-by-a-pixel errors here and there.
Update: Forget about using TextRenderer. It is dog slow.
You could try using a RichTextBox so that you can get multiple colors for the string and then make it read only and remove the border. Change the background color to the same as the Form it is on and you might get away with it.
As an alternative, you might do this as rtf or html in a suitable control (such as WebBrowser). It would probably take a bit more resources that you'd ideally like, but it'll work fairly quickly.
If you are building your Windows app for people with XP and up, you can use WPF. Even if it's a Windows Forms app, you can add a WPF UserControl.
I would then use a Label, and set the "Foreground" property to be a gradient of colors.
Or, in Windows Forms (no WPF), you could just use a "Flow Panel", and then in a for loop add multiple Labels as segments of your sentense... they will all "flow" together as if it was one label.
I'm using colored labels quite often to mark keywords in red color etc.
Like in Phil Wright's answer I use a RichTextBox control, remove the border and set the background color to SystemColors.Control.
To write colored text the control is first cleared and then I use this function to append colored text:
private void rtb_AppendText(Font selfont, Color color, Color bcolor,
string text, RichTextBox box)
{
// append the text to the RichTextBox control
int start = box.TextLength;
box.AppendText(text);
int end = box.TextLength;
// select the new text
box.Select(start, end - start);
// set the attributes of the new text
box.SelectionColor = color;
box.SelectionFont = selfont;
box.SelectionBackColor = bcolor;
// unselect
box.Select(end, 0);
// only required for multi line text to scroll to the end
box.ScrollToCaret();
}
If you want to run this function with "mono" then add a space before every new colored text, or mono will not set new the color correctly. This is not required with .NET
Usage:
myRtb.Text = "";
rtb_AppendText(new Font("Courier New", (float)10),
Color.Red, SystemColors.Control, " my red text", myRtb);
rtb_AppendText(new Font("Courier New", (float)10),
Color.Blue, SystemColors.Control, " followed by blue", myRtb);
Slightly off topic ... You could check also:
generate html color table
model colors in sql
the result
You can simply use multiple labels. Set the font properties you want and then use the left. top and width properties to display the words you want displayed differently. This is assuming you are using windows forms.
Try this,
labelId.Text = "Successfully sent to" + "<a style='color:Blue'> " + name + "</a>";
There is no native support for this; you will either have to use multiple labels or find a 3rd-party control that will provide this functionality.
I don't think so. You should create one yourself.
As per your Question your requirement is simple like
lable.Text = "This color is Red", So it have to display text like this
"The color is" will be in Blue and "Red" will be red color ..
This can be done like this
lable.Text = "<span style='Color:Blue'>" + " The color is " +"</span>" + "<span style='Color:Red'>"Red"</span>"

Categories

Resources