toptensoftware / richtextkit Goto Github PK
View Code? Open in Web Editor NEWRich text rendering for SkiaSharp
License: Other
Rich text rendering for SkiaSharp
License: Other
Hi.
I stumbled upon the RichTextKit and it seems pretty awesome.
I made a small program in net 6.0, which works perfectly on my Windows computer, but when I try to run the program on my Raspberry pi, it hangs for some reason..
I did some debugging and found that it hangs when it tries to calculate the size.
The code is pretty simple, and my guess is that it has something to do with the fallback fonts, but.. Help :)
Just to make sure SkiaSharp was working, I tried to use the DrawText first - And that works like a charm..
Any idea what I'm missing / doing wrong??
var rs = new RichString
{
MaxWidth = 800,
MaxHeight = 480,
}
.FontSize(20)
.BackgroundColor(SKColors.White)
.LineHeight(1.1f)
.Alignment(TextAlignment.Center)
.Add("Test");
using (SKCanvas bitmapCanvas = new SKCanvas(this.image))
{
Console.WriteLine("WriteRichStringCentered - 1");
using (var textPaint = new SKPaint { Color = SKColors.White, TextSize = 60, IsAntialias = true })
{
bitmapCanvas.DrawRect(0, 100, this.image.Width, 30, textPaint);
textPaint.Color = SKColors.Coral;
bitmapCanvas.DrawText("TestTestTestTestTestTestTestTest", new SKPoint(10, 100), textPaint);
var top = (this.image.Height - richString.MeasuredHeight) / 2; <---- Code hangs here
Console.WriteLine("WriteRichStringCentered - 2");
}
}
Regards,
Martin
The comment/intellisense on TextBlock.Layout() says it returns a paragraph when in fact, its void.
I can do a PR for this if you like. Are you taking contribs?
I get, while using RichTextKit, the following error when calling Layout with FontFamily=null (default for new Font() ) or FontFamily="".
Error Message:
"Attempting to JIT compile method 'bool Topten.RichTextKit.FontFallback/d__2:MoveNext ()' while running in aot-only mode. See https://docs.microsoft.com/xamarin/ios/internals/limitations for more information.\n"
StackTrace:
at Topten.RichTextKit.TextBlock.AddDirectionalRun (Topten.RichTextKit.StyleRun styleRun, System.Int32 start, System.Int32 length, Topten.RichTextKit.TextDirection direction, Topten.RichTextKit.IStyle style) [0x00057] in <19e5ac464bd84f9c875ac01889614bdc>:0
What did I wrong?
I have the bolded text aaaaaaaaaa
in a text block with MaxWidth = 36
and MaxHeight = 30
.
If I add the letters as two separate texts:
textBlock.AddText("aaaaa", styleBold);
textBlock.AddText("aaaaa", styleBold);
On the other hand, if I add the entire text in one AddText:
textBlock.AddText("aaaaaaaaaa", styleBold);
I expect that I see the second image in both scenarios.
Hi Brad!
Did you plan adding bullets and list support to TextDocument ?
Thanks
Hi:
My method of using TextBlock Paint does not support Hindi problems. Is there a good solution?
Now it is drawn by the box instead of the box, and the correct one is not drawn
environment : linux environment
version : .net 5
Test code:
RichString richString = new RichString
{
MaxWidth = ScreenClientBounds.Width,
MaxHeight = ScreenClientBounds.Height
};
richString.FontFamily(Font.FontFamily.Name);
richString.FontSize(Font.Size * 1.25f * ZoomFactor);
richString.FontItalic(Font.Italic);
richString.Bold(Font.Bold);
richString.Add(text);
richString.Paint(canvas, clientRectangle.Location, new TextPaintOptions { IsAntialias = true, LcdRenderText = true });
Hello,
I have been wondering if there is a way to add each sentece on new line with TextBlock, the text displayed in the picture should look as bellow.
DestinationAddress,
DestinationType,
OneTimeToken,
Time,
AuthenticationType
However the text displayed is broken, the text doesn't have to be the same every time. If I am not mistaken it all depends on the MaxWidth, but can I somehow involve this in any other way?
I get this usually
DestinationAddres
s
OneTimeToken,Tim
e...
<ContentPage xmlns="http://xamarin.com/schemas/2014/forms"
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
xmlns:local="clr-namespace:Text"
xmlns:forms="clr-namespace:SkiaSharp.Views.Forms;assembly=SkiaSharp.Views.Forms"
x:Class="Text.MainPage">
<StackLayout>
<forms:SKCanvasView HorizontalOptions="FillAndExpand" VerticalOptions="FillAndExpand"
PaintSurface="OnPaintSurface"/>
</StackLayout>
</ContentPage>
private void OnPaintSurface(object sender, SKPaintSurfaceEventArgs e)
{
var canvas = e.Surface.Canvas;
// Create the text block
var tb = new TextBlock {MaxWidth = 600, Alignment = TextAlignment.Auto, MaxLines = 5};
var list = new List<string> {"DestinationAddress", "DestinationType", "OneTimeToken", "Time", "AuthenticationType"};
var styleNormal = new Style()
{
FontFamily = "Arial",
FontSize = 60
};
foreach (var field in list)
{
tb.AddText(field, styleNormal);
}
tb.Paint(canvas);
}
}
When using the latest SkiaSharp 2.80.0
and HarfBuzzSharp 2.6.1.5
, the following code hangs:
var tb = new TextBlock ();
tb.AddText ("Hello", new Style ());
Console.WriteLine (tb.MeasuredHeight);
This is caused by getting stuck in an infinite loop in FontFallback.GetFontRuns ()
due to both of these lines returning 0:
count = typeface.GetGlyphs((IntPtr)(pch + pos), length - pos, SKEncoding.Utf32, out var glyphs);
count = RunFace.GetGlyphs((IntPtr)(pch + pos), length - pos, SKEncoding.Utf32, out var glyphs);
^^ This overload of GetGlyphs
is marked as [Obsolete]
.
It seems like something in SkiaSharp has changed and this code no longer works as expected. I do not know enough about what is going on to determine if the RichTextKit code needs to be updated or if this is a bug in SkiaSharp.
Windows 10 - VS 2019 - netcoreapp3.1
Thank you for making this great library.
I am making a server application and need to be able to render multi-language text. Use multiple font files in different languages.
Since font files are not installed on the server, all font files must be included in the application.
Current font fallback is based on system fonts. Is there a way to set fallback from multiple private font files?
Certain fonts, especially when font size is small, result in strikethrough and underline not being rendered. I'm guessing this is because the thickness is determined by the font metric's StrikeoutThickness and UnderlineThickness properties, which may be smaller than 1. For some fonts this is worse than others. Whether the line is rendered also depends on the subpixel position of the line (and I guess what anti aliasing is used).
Should be reproducible with Arial font with a pixel font size smaller than 10 for example.
I can submit a PR to fix this. My two suggestions for the fix would be:
Any other suggestion I could also take a look at.
Hi,
I have a situation where I need to render Hebrew and I see if I just use the example code it's all blocks, is there a special font or setting I need to set to render the text correctly?
Thanks.
is it normal for RTK to do partial drawing?
this is my shader
var sksl = SKRuntimeEffect.Create(
"uniform shader input;\n" +
"uniform shader inputAlpha;\n" +
"uniform shader inputGradient;\n" +
"\n" +
"half4 main() {\n" +
" half4 color = sample(input);\n" +
// return zero if alpha is zero
" if (color.a == 0) return vec4(0,0,0,0);\n" +
" int alpha = 255.0 * sample(inputAlpha).a;\n" +
// return color if input alpha is 0, this means we only drawn this pixel once
// Skia's overdraw canvas increases the alpha of a pixel each time it drawn touched
// R G B A
" if (alpha == 0) {\n" +
// apply greyscale to the overdraw canvas in order to isolate the overdraw colors
" return half4(vec3((color.r + color.g + color.b) / 3), 1);\n" +
" }\n" +
" return half4(1,0,0,1);\n" +
//// gradient heatmap
//" return sample(inputGradient, float2(0, alpha));\n" +
"}\n",
out string err
);
and if use this
void drawText(SKCanvas canvas, int n, int x, int y)
{
Topten.RichTextKit.TextBlock block = new();
Topten.RichTextKit.Style style = new();
style.TextColor = SKColors.Silver;
style.FontSize = 20;
var t = new Topten.RichTextKit.TextPaintOptions();
t.Edging = SKFontEdging.SubpixelAntialias;
for (int i = 0; i < n; i++)
{
string text = "drawn " + n + " time";
if (i != 0) text += "s";
block.Clear();
block.AddText(text, style);
block.Paint(canvas, new SKPoint(x, y));
canvas.Flush();
}
}
then i get this
and if i use this
void drawText(SKCanvas canvas, int n, int x, int y)
{
using var paint = new SKPaint();
paint.Color = SKColors.Silver;
for (int i = 0; i < n; i++)
{
string text = "drawn " + n + " time";
if (i != 0) text += "s";
canvas.DrawText(text, x, y, paint);
}
}
then i get this (expected result)
my full code is this
SKShader createAlphaGradientShader()
{
return SKShader.CreateLinearGradient(
// start
new SKPoint(0, 0),
// end
new SKPoint(0, 255),
// colors
new SKColor[]
{
// light blue
//new SKColorF(0, 0.5f, 0.75f).ToSKColor(),
// blueish-whitish
//new SKColorF(0.37f, 0.5f, 0.75f).ToSKColor(),
// lighter orange
new SKColor(249, 205, 172),
// light orange
//new SKColor(243, 158, 95),
// orange-redish
//new SKColorF(1f, 0.28f, 0).ToSKColor(),
// red
new SKColorF(1f, 0, 0).ToSKColor(),
},
// distribution (color pos from 0 to 1)
new float[] { 0, 0.05f },
SKShaderTileMode.Clamp
);
}
using var gradient = createAlphaGradientShader();
// to improve overdraw quality we only apply overdraw to non transparent final output pixels
// this means we need to draw twice, once in full color, another in alpha
// if the full color pixel has an alpha of zero we discard the result
var sksl = SKRuntimeEffect.Create(
"uniform shader input;\n" +
"uniform shader inputAlpha;\n" +
"uniform shader inputGradient;\n" +
"\n" +
"half4 main() {\n" +
" half4 color = sample(input);\n" +
// return zero if alpha is zero
" if (color.a == 0) return vec4(0,0,0,0);\n" +
" int alpha = 255.0 * sample(inputAlpha).a;\n" +
// return color if input alpha is 0, this means we only drawn this pixel once
// Skia's overdraw canvas increases the alpha of a pixel each time it drawn touched
// R G B A
" if (alpha == 0) {\n" +
// apply greyscale to the overdraw canvas in order to isolate the overdraw colors
" return half4(vec3((color.r + color.g + color.b) / 3), 1);\n" +
" }\n" +
" return half4(1,0,0,1);\n" +
//// gradient heatmap
//" return sample(inputGradient, float2(0, alpha));\n" +
"}\n",
out string err
);
if (err != null)
{
Log.d("SHADER", "runtime effect compiled with errors: " + err);
return;
}
int w = canvas.BaseLayerSize.Width;
int h = canvas.BaseLayerSize.Height;
SKImageInfo offscreenInfo = new(w, h);
SKImageInfo offscreenAlphaInfo = new(w, h, SKColorType.Alpha8);
using var offscreenSurface = SKSurface.Create(offscreenInfo);
using var offscreenAlphaSurface = SKSurface.Create(offscreenAlphaInfo);
using SKCanvas imageCanvas = offscreenSurface.Canvas;
using SKOverdrawCanvas overdrawCanvas = new(offscreenAlphaSurface.Canvas);
using SKNWayCanvas nWayCanvas = new(w, h);
nWayCanvas.AddCanvas(overdrawCanvas);
nWayCanvas.AddCanvas(imageCanvas);
using SKPaint colorPaint = new();
void drawText_(SKCanvas canvas, int n, int x, int y)
{
Topten.RichTextKit.TextBlock block = new();
Topten.RichTextKit.Style style = new();
style.TextColor = SKColors.Silver;
style.FontSize = 20;
var t = new Topten.RichTextKit.TextPaintOptions();
t.Edging = SKFontEdging.SubpixelAntialias;
for (int i = 0; i < n; i++)
{
string text = "drawn " + n + " time";
if (i != 0) text += "s";
block.Clear();
block.AddText(text, style);
block.Paint(canvas, new SKPoint(x, y));
canvas.Flush();
}
}
void drawText(SKCanvas canvas, int n, int x, int y)
{
using var paint = new SKPaint();
paint.Color = SKColors.Silver;
for (int i = 0; i < n; i++)
{
string text = "drawn " + n + " time";
if (i != 0) text += "s";
canvas.DrawText(text, x, y, paint);
}
}
void drawMatrix(SKCanvas canvas, int count, int max_lines, int spacing)
{
max_lines++;
int n = 0;
int column = 0;
int line = 1;
for (int i = 0; i < count; i++)
{
n = i + 1;
if (line == max_lines)
{
line = 1;
column += spacing;
}
//int s = canvas.Save();
drawText(canvas, n, column, 20 * line);
//canvas.RestoreToCount(s);
line++;
}
}
drawMatrix(nWayCanvas, 20, 20, 50);
nWayCanvas.Flush();
using var imageAlpha = offscreenAlphaSurface.Snapshot();
using var image = offscreenSurface.Snapshot();
var imageAlphaShader = imageAlpha.ToShader();
var imageShader = image.ToShader();
SKRuntimeEffectChildren children = new(sksl) {
{ "input", imageShader },
{ "inputAlpha", imageAlphaShader },
{ "inputGradient", gradient },
};
var ourShader = sksl.ToShader(false, new(sksl), children);
sksl.Dispose();
imageAlphaShader.Dispose();
imageShader.Dispose();
// we only want to write our paint shader's output pixel to the canvas
// this is the same as if the canvas was cleared before painting the shader
colorPaint.BlendMode = SKBlendMode.Src;
colorPaint.Shader = ourShader;
canvas.DrawPaint(colorPaint);
ourShader.Dispose();
EDIT: duplicate (mostly exact same error) found dotnet/maui#5142
[DOTNET] ViewRootImpl: Caught exception while drawing view: System.InvalidOperationException: Exception in BuildFontRuns() with original length of 13 now 13, style run count 1, font run count 0, direction overrides: False
[DOTNET] ---> System.TypeLoadException: Could not resolve type with token 01000034 from typeref (expected class 'System.ReadOnlySpan`1' in assembly 'mscorlib, Version=2.0.5.0, Culture=neutral, PublicKeyToken=7cec85d7bea7798e')
[DOTNET] at Topten.RichTextKit.TextBlock.AddDirectionalRun(StyleRun styleRun, Int32 start, Int32 length, TextDirection direction, IStyle style) in C:\Users\Brad\Projects\RichTextKit\Topten.RichTextKit\TextBlock.cs:line 1182
[DOTNET] at Topten.RichTextKit.TextBlock.BuildFontRuns() in C:\Users\Brad\Projects\RichTextKit\Topten.RichTextKit\TextBlock.cs:line 1115
[DOTNET] --- End of inner exception stack trace ---
[DOTNET] at Topten.RichTextKit.TextBlock.BuildFontRuns() in C:\Users\Brad\Projects\RichTextKit\Topten.RichTextKit\TextBlock.cs:line 1133
[DOTNET] at Topten.RichTextKit.TextBlock.Layout() in C:\Users\Brad\Projects\RichTextKit\Topten.RichTextKit\TextBlock.cs:line 313
[DOTNET] at Topten.RichTextKit.TextBlock.Paint(SKCanvas canvas, TextPaintOptions options) in C:\Users\Brad\Projects\RichTextKit\Topten.RichTextKit\TextBlock.cs:line 359
[DOTNET] at AndroidUI.Topten_RichTextKit_TextView.onDraw(SKCanvas canvas) in C:\Users\small\source\repos\WindowsProject1\AndroidUI\Topten_RichTextKit_TextView.cs:line 196
[DOTNET] at AndroidUI.View.draw(SKCanvas canvas) in C:\Users\small\source\repos\WindowsProject1\AndroidUI\View.cs:line 6284
[DOTNET] at AndroidUI.View.updateDisplayListIfDirty() in C:\Users\small\source\repos\WindowsProject1\AndroidUI\View.cs:line 6173
[DOTNET] at AndroidUI.View.draw(SKCanvas canvas, View parent, Int64 drawingTime) in C:\Users\small\source\repos\WindowsProject1\AndroidUI\View.cs:line 11159
[DOTNET] at AndroidUI.View.drawChild(SKCanvas canvas, View child, Int64 drawingTime) in C:\Users\small\source\repos\WindowsProject1\AndroidUI\View.cs:line 11433
[DOTNET] at AndroidUI.View.dispatchDraw(SKCanvas canvas) in C:\Users\small\source\repos\WindowsProject1\AndroidUI\View.cs:line 11525
[DOTNET] at AndroidUI.View.updateDisplayListIfDirty() in C:\Users\small\source\repos\WindowsProject1\AndroidUI\View.cs:line 6160
[DOTNET] at AndroidUI.View.draw(SKCanvas canvas, View parent, Int64 drawingTime) in C:\Users\small\source\repos\WindowsProject1\AndroidUI\View.cs:line 11159
[DOTNET] at AndroidUI.View.drawChild(SKCanvas canvas, View child, Int64 drawingTime) in C:\Users\small\source\repos\WindowsProject1\AndroidUI\View.cs:line 11433
[DOTNET] at AndroidUI.View.dispatchDraw(SKCanvas canvas) in C:\Users\small\source\repos\WindowsProject1\AndroidUI\View.cs:line 11525
[DOTNET] at AndroidUI.View.updateDisplayListIfDirty() in C:\Users\small\source\repos\WindowsProject1\AndroidUI\View.cs:line 6160
[DOTNET] at AndroidUI.View.draw(SKCanvas canvas, View parent, Int64 drawingTime) in C:\Users\small\source\repos\WindowsProject1\AndroidUI\View.cs:line 11159
[DOTNET] at AndroidUI.View.drawChild(SKCanvas canvas, View child, Int64 drawingTime) in C:\Users\small\source\repos\WindowsProject1\AndroidUI\View.cs:line 11433
[DOTNET] at AndroidUI.View.dispatchDraw(SKCanvas canvas) in C:\Users\small\source\repos\WindowsProject1\AndroidUI\View.cs:line 11525
[DOTNET] at AndroidUI.View.updateDisplayListIfDirty() in C:\Users\small\source\repos\WindowsProject1\AndroidUI\View.cs:line 6160
[DOTNET] at AndroidUI.ViewRootImpl.updateViewTreeDisplayList(SKCanvas drawingCanvas, View view) in C:\Users\small\source\repos\WindowsProject1\AndroidUI\ViewRootImpl.cs:line 2179
[DOTNET] at AndroidUI.ViewRootImpl.updateRootDisplayList(SKCanvas canvas, View view, Object callbacks) in C:\Users\small\source\repos\WindowsProject1\AndroidUI\ViewRootImpl.cs:line 2192
[DOTNET] at AndroidUI.ViewRootImpl.draw(SKCanvas canvas, View view, AttachInfo attachInfo, Object callbacks) in C:\Users\small\source\repos\WindowsProject1\AndroidUI\ViewRootImpl.cs:line 2138
[DOTNET] at AndroidUI.ViewRootImpl.draw(SKCanvas canvas, Boolean fullRedrawNeeded) in C:\Users\small\source\repos\WindowsProject1\AndroidUI\ViewRootImpl.cs:line 2076
[DOTNET] at AndroidUI.ViewRootImpl.performDraw(SKCanvas canvas) in C:\Users\small\source\repos\WindowsProject1\AndroidUI\ViewRootImpl.cs:line 1462
this works fine in Xamarin build, but gives above in MAUI build
I have a custom Typeface that I am using for text in my application. For painting text with SKPaint
I can set the Typeface from an custom font that I load into a SKTypeface
object. I can't seem to find a way to do that for a Style
in the RichTextKit since Style.FontFamily
only takes a string. I have tried using SKTypeface.FamilyName
but the style doesn't seem to recognize it since it is a custom font.
Is there a way to do this? I have looked around but I can't seem to find anywhere that this is referenced. If it isn't possible could someone point me to a list of acceptable values for Style.FontFamily
?
I don't have good repro on this because it was reported on AppCenter, but here's the callstack:
Xamarin Exception Stack:
System.BadImageFormatException: Method has zero rva
at Topten.RichTextKit.TextShaper.GetHarfBuzzBlob (SkiaSharp.SKStreamAsset asset) [0x00045] in <48c43bfd31714027914cbea8c298ec38>:0
at Topten.RichTextKit.TextShaper..ctor (SkiaSharp.SKTypeface typeface) [0x00015] in <48c43bfd31714027914cbea8c298ec38>:0
at Topten.RichTextKit.TextShaper.ForTypeface (SkiaSharp.SKTypeface typeface) [0x0001f] in <48c43bfd31714027914cbea8c298ec38>:0
at Topten.RichTextKit.TextBlock.CreateFontRun (Topten.RichTextKit.StyleRun styleRun, Topten.RichTextKit.Utils.Slice`1[T] codePoints, Topten.RichTextKit.TextDirection direction, Topten.RichTextKit.IStyle style, SkiaSharp.SKTypeface typeface, SkiaSharp.SKTypeface asFallbackFor) [0x00000] in <48c43bfd31714027914cbea8c298ec38>:0
at Topten.RichTextKit.TextBlock.AddFontRun (Topten.RichTextKit.StyleRun styleRun, System.Int32 start, System.Int32 length, Topten.RichTextKit.TextDirection direction, Topten.RichTextKit.IStyle style, SkiaSharp.SKTypeface typeface, SkiaSharp.SKTypeface asFallbackFor) [0x0000e] in <48c43bfd31714027914cbea8c298ec38>:0
at Topten.RichTextKit.TextBlock.AddDirectionalRun (Topten.RichTextKit.StyleRun styleRun, System.Int32 start, System.Int32 length, Topten.RichTextKit.TextDirection direction, Topten.RichTextKit.IStyle style) [0x00030] in <48c43bfd31714027914cbea8c298ec38>:0
at Topten.RichTextKit.TextBlock.BuildFontRuns () [0x002db] in <48c43bfd31714027914cbea8c298ec38>:0
at Topten.RichTextKit.TextBlock.Layout () [0x000d8] in <48c43bfd31714027914cbea8c298ec38>:0
at Topten.RichTextKit.TextBlock.get_MeasuredWidth () [0x00000] in <48c43bfd31714027914cbea8c298ec38>:0
This was on Android using Topten.RichTextKit v0.1.118
I do realize this is somewhat of an old version, and we may upgrade just to be safe, but I also wanted to bring this up in case it wasn't a known issue.
"Missing glyphs" are rendered when rendering a grapheme cluster where the base character is contained in the font but the combining characters are not. This can be reproduced easily by rendering a text with obscure diacritics, e.g. A, using a font like "Agency FB".
I believe this issue occurs since the font fallback mechanisms only applies fallbacks at cluster boundaries (in FontFallback.GetFontRuns
):
// Must be a cluster boundary
if (!GraphemeClusterAlgorithm.IsBoundary(codePoints, i))
continue;
When rendering text like this with Firefox, Chrome, and GDI+, you get slightly different results for each but they all at least fallback to some other font for the missing combining characters. The CSS specification has some guidance w.r.t. this problem [1], however that solution may be a bit overkill for this library. A simpler solution may be to just use an available font for the missing characters (remove the boundary limitation, this seems to be what Firefox does), or use the font which matches the most consecutive characters in the cluster (seems to be what Chrome does).
Is there a reason why fallbacks are skipped when not at a cluster boundary? Simply removing this limitation results in rendering fairly similar to Firefox.
The selected range is not painted when drawing text if the SelectionStart is set to the length of the string to be drawn, and the SelectionEnd is less than the Selection start. Dragging or selecting rendered text from the end of the text does not show the blue selection background due this if one implements selection in their text control
is C++ Skia support planned? or only SkiaSharp ?
Heyo 👋
This issue appears on iOS when using "San Francisco" as the font family.
In the picture below, the characters for the formatted date (10/17/2020 11:40 AM) are spread too far apart. Looks like this occurs because the date string is added to a paragraph that already has text of a bigger font size.
Compare that to this, where I added a new paragraph before the formatted date. The character spacing is correct.
I am using a netcore application NextPVR that uses RichTextKit and on an RPi I see this error on '\n' breaks in a TextBlock Is anyone aware of how to deal with this? Replacing '\n' with 0x20 and there is no issue.
2022-01-03 17:50:02.245 [ERROR][71] System.InvalidOperationException: Exception in BuildFontRuns() with original length of 77 now 77, style run count 1, font run count 0, direction overrides: False
---> System.ArgumentNullException: Value cannot be null. (Parameter 'asset')
at Topten.RichTextKit.TextShaper.GetHarfBuzzBlob(SKStreamAsset asset)
at Topten.RichTextKit.TextShaper..ctor(SKTypeface typeface)
at Topten.RichTextKit.TextShaper.ForTypeface(SKTypeface typeface)
at Topten.RichTextKit.TextShaper.Shape(ResultBufferSet bufferSet, Slice`1 codePoints, IStyle style, TextDirection direction, Int32 clusterAdjustment, SKTypeface asFallbackFor, TextAlignment textAlignment)
at Topten.RichTextKit.TextBlock.CreateFontRun(StyleRun styleRun, Slice`1 codePoints, TextDirection direction, IStyle style, SKTypeface typeface, SKTypeface asFallbackFor)
at Topten.RichTextKit.TextBlock.FlushUnshapedRuns()
at Topten.RichTextKit.TextBlock.AddFontRun(StyleRun styleRun, Int32 start, Int32 length, TextDirection direction, IStyle style, SKTypeface typeface, SKTypeface asFallbackFor)
at Topten.RichTextKit.TextBlock.AddDirectionalRun(StyleRun styleRun, Int32 start, Int32 length, TextDirection direction, IStyle style)
at Topten.RichTextKit.TextBlock.BuildFontRuns()
--- End of inner exception stack trace ---
at Topten.RichTextKit.TextBlock.BuildFontRuns()
As far as I understand, there is no way to disable the ellipsis in RichString, it is always added if the TextBlock is truncated:
RichTextKit/Topten.RichTextKit/RichString.cs
Lines 1042 to 1048 in 5f4d2c8
Sometimes this behavior is undesirable, and it would be nice to be able to not render the ellipsis at the end.
I suggest to add a bool property to control ellipsis, for example Ellipsis
, EllipsisEnabled
(true by default).
Is the idea reasonable? If so, I can prepare a patch.
My use case:
I render fairly simple pdf reports, and use one RichString per page. The Truncated property serves as an indicator to me that a new page needs to be created. Everything works fine, but the ellipsis at the end of each page looks terrible, and I want to turn it off.
I also had a thought that maybe I was using RichString wrongly, but after reading the source code I couldn't find a better way. Please correct me if I have missed something obvious.
p.s. Thanks for the great RichTextKit project :)
Hi. We are using google fonts. I cant figure out how to get bold or italics to work. In the browser, these are synthesised from regular. Ive tried loading a specific bold font into the style, but it still comes out at 400. I would rather the font was faked to match the browser (not all google fonts come with italic and bold).
Is this possible? I read that Chrome uses Skia for this so maybe this is not part of SkiaSharp?
Same issue on windows and linux.
Hi!
Do you have plans to add an ability to customise TextBlock background color ?
Thanks!
Hi, how can I working with SKTypeface and use RichString object? Or can i get IStyle from SKTypeface for using with TextBox?
Is there an easy way to create a halo for a given TextBlock
? Perhaps by getting the SKPath
for the drawn characters and then use SKPaint.GetFillPath()
? Or draw TextBlock
twice?
Hello,
we encounter a problem, when using RichTextKit 0.3.134 with the version 2.80.2 or 2.80.3 of SkiaSharp. We get the following error message
Method not found: void SkiaSharp.SKFont.GetGlyphs(System.ReadOnlySpan`1<int>,System.Span`1<uint16>)
with the following call stack:
at Topten.RichTextKit.TextBlock.AddDirectionalRun (Topten.RichTextKit.StyleRun styleRun, System.Int32 start, System.Int32 length, Topten.RichTextKit.TextDirection direction, Topten.RichTextKit.IStyle style) [0x00057] in <19e5ac464bd84f9c875ac01889614bdc>:0
at Topten.RichTextKit.TextBlock.BuildFontRuns () [0x002e6] in <19e5ac464bd84f9c875ac01889614bdc>:0
at Topten.RichTextKit.TextBlock.Layout () [0x000f5] in <19e5ac464bd84f9c875ac01889614bdc>:0
We get this only on iOS, not on Android or UWP.
After updating to RichTextKit 0.4.145 and SkiaSharp 2.80.3 the error message is slightly different:
Exception in BuildFontRuns() with original length of 13 now 13, style run count 1, font run count 0, direction overrides: False
but the inner exception seems to be the same
Method not found: void SkiaSharp.SKFont.GetGlyphs(System.ReadOnlySpan`1<int>,System.Span`1<uint16>)
and a stack trace like
at Topten.RichTextKit.TextBlock.BuildFontRuns () [0x00370] in C:\Users\Brad\Projects\RichTextKit\Topten.RichTextKit\TextBlock.cs:1133
at Topten.RichTextKit.TextBlock.Layout () [0x000f5] in C:\Users\Brad\Projects\RichTextKit\Topten.RichTextKit\TextBlock.cs:313
Did anyone see this problem before? Are we missing something? Worked before (was it SkiaSharp 1.63?), so there must be a change while this time.
considering the following code:
var rs = new RichString("Test Text")
.FontSize(14)
.TextColor(SkColors.Blue);
when I do it this way the color and size are not taking.
The property MeasuredWith returns the proper with of a RichString if left and right margin are 0.
If I set the left and right margin to 10, it fails (it returns the same width as when it was without margin).
Here I have attached two scenarios, where I render the second parragraph following the first one. The top example is whithout margin. The second one is with a margin of 10 in every direction. I drew the bounding box of the RichString in red. You can clearly see the problem.
The bitmap size was calculated adding the MeassuredWidth of both RichText.
I hope you fix it.
PS: congrats for the great library. I had to start using Skia and I couldn't be happier after I found RichTextKit
I guess it's because it's vectorizing all the text (or storing the font?); files get huge pretty huge.
Is there a setting or something that might help with this?
I can provide more details if you can let me know what information would be useful.
Thanks in advance.
Small feature request: would it be possible to publish the source code for TextEditorView.cs?
I know it will not compile because of the GUIKit dependency, but it would be great reference code for implementing your own text editor.
Also, since recent additions in RichTextKit, such as IME support, aren't handled in your editor videos (which are great BTW), it would be nice to have some reference code on how to use this.
Is it possible to read from an RTF file or string and display using this?
I'm looking to (smoothly) scroll some text from a RichTextBox. This seems to render very well and fast but I just need a way to read the RTF from the RichTextBox.
https://www.toptensoftware.com/richtextkit/basics claims that \r\n
is normalized to \n
, but doing something like
TextBlock tb = new TextBlock();
tb.AddText("X \n Y \r\n Z", style);
renders as
X
Y□
Z
Absolutely love this library and it's API.
The only big feature that is missing IMHO, is support for (inline) placeholders that can be custom rendered. This would allow for rendering images, controls or anything custom you may want to draw.
The placeholders just have a size and (baseline) alignment, and possibly an event that is fired to render it.
The Unicode Object Replacement Character (U+FFFD) can be used to represent the placeholder in the text.
See Flutter's ParagraphBuilder.addPlaceholder method for possible API.
My project has a strong name so .net framework does not let me use this library because it does not have a strong name. Please add a strong name to it (SNK file)
A trickier problem this time. We use FabricJS on the front end designer and SkiaSharp on the back end to print documents. Getting the two things to match is always a challenge. Generating a PDF using SkiaSharp and RichTextKit produces different results to the same thing created in MS Word also.
Eg. A sample front end design like this:
generates a PDF like this:
(the rectangles in these images actually are the same size and are correct when printed - it's just the scaling of these screen shots that dont match).
The font is Arial 10 pt. Im assuming that RichTextKit Font Sizes are pixels and there are 72 points per inch and a PDF is 72 DPI so 10 pt is 10 pixels. However, the character width seems narrower, especially compared to the Word version (sorry, dont have an image of that).
I guess my question is, how realistic is it to expect to get identical/consistent results? I think I'd be happy to get a close match between FabricJS and RichTextKit / SkiaSharp so maybe Ive just answered my own question. :)
Right now the current logic, is spaces uses the last Font found during the fallback. When you take into a string like Ride the Comet! ☄️
with even spaces on each side, the spacing is being used to help with padding. It should still properly center, however Emoji character spaces are a lot larger than say Helvetica.
I propose the line should be changed to RunFace = typeface;
Hello,
I have the problem, that I don't find the Roboto Condensed font on my machine when selecting font family for my text. If I look into the fonts in the system manager or use Word, both can see the font. If I look into SKFontManager.FontFamilies I see only the Roboto font.
So I have to provide SKFontStyleWidth with SKFontStyleWidth.Condensed instead of SKFontStyleWidth.Normal. But how to do this with RichTextKit? Is this missing? Could it be, that this line is the problem?
Regards,
Dirk
Hi,
when using RichTextKit within an ASP.NET application for PDF export, we noticed that in a multi-threaded context RichTextKit is not behaving properly. We noticed multiple exceptions as well as weird issues with TextBlocks being rendered in different styles than the ones that were defined for the specific text block.
First of all my question would be: Should this usecase (using RichTextKit from multiple threads) be supported? If so, what follows is some example code to reproduce issues and some exceptions that occurred for me.
using System;
using System.IO;
using System.Threading;
using SkiaSharp;
using Topten.RichTextKit;
namespace RichTextKitIssue
{
class Program
{
private const int MAX_THREADS = 10;
static void Main(string[] args)
{
for (var i = 0; i < MAX_THREADS; i++)
{
var i1 = i;
Thread thread = new Thread(() =>
{
var index = i1;
Console.WriteLine("Thread started");
var text = GetLayout();
var bitmap = Render(text);
var path = $"{index.ToString()}.png";
using (var file = File.OpenWrite(path))
{
bitmap.Encode(file, SKEncodedImageFormat.Png, 90);
}
});
thread.Start();
}
}
public static RichString GetLayout()
{
var r = new RichString();
r = r.FontSize(64).FontWeight((int) SKFontStyleWeight.Bold).Add("Some Text in 64 Bold");
r = r.Paragraph().FontSize(48).FontWeight((int) SKFontStyleWeight.Bold).Add("Some Text in 48 Bold");
r = r.Paragraph().FontSize(32).FontWeight((int) SKFontStyleWeight.Bold).Add("Some Text in 32 Bold");
r = r.Paragraph().FontSize(24).FontWeight((int) SKFontStyleWeight.Bold).Add("Some Text in 24 Bold");
r = r.Paragraph().FontSize(18).FontWeight((int) SKFontStyleWeight.Bold).Add("Some Text in 18 Bold");
r = r.Paragraph().FontSize(12).FontWeight((int) SKFontStyleWeight.Bold).Add("Some Text in 12 Bold");
r = r.Paragraph().FontSize(64).FontWeight((int) SKFontStyleWeight.Normal).Add("Some Text in 64 Normal");
r = r.Paragraph().FontSize(48).FontWeight((int) SKFontStyleWeight.Normal).Add("Some Text in 48 Normal");
r = r.Paragraph().FontSize(32).FontWeight((int) SKFontStyleWeight.Normal).Add("Some Text in 32 Normal");
r = r.Paragraph().FontSize(24).FontWeight((int) SKFontStyleWeight.Normal).Add("Some Text in 24 Normal");
r = r.Paragraph().FontSize(18).FontWeight((int) SKFontStyleWeight.Normal).Add("Some Text in 18 Normal");
r = r.Paragraph().FontSize(12).FontWeight((int) SKFontStyleWeight.Normal).Add("Some Text in 12 Normal");
r = r.Paragraph().FontSize(64).FontWeight((int) SKFontStyleWeight.Light).Add("Some Text in 64 Light");
r = r.Paragraph().FontSize(48).FontWeight((int) SKFontStyleWeight.Light).Add("Some Text in 48 Light");
r = r.Paragraph().FontSize(32).FontWeight((int) SKFontStyleWeight.Light).Add("Some Text in 32 Light");
r = r.Paragraph().FontSize(24).FontWeight((int) SKFontStyleWeight.Light).Add("Some Text in 24 Light");
r = r.Paragraph().FontSize(18).FontWeight((int) SKFontStyleWeight.Light).Add("Some Text in 18 Light");
r = r.Paragraph().FontSize(12).FontWeight((int) SKFontStyleWeight.Light).Add("Some Text in 12 Light");
return r;
}
public static SKBitmap Render(RichString r)
{
var bitmap = new SKBitmap(1080, 1920);
var canvas = new SKCanvas(bitmap);
canvas.Clear(SKColors.White);
r.Paint(canvas);
return bitmap;
}
}
}
Exceptions that were noticed by just running the example:
System.InvalidOperationException: Operations that change non-concurrent collections must have exclusive access. A concurrent update was performed on this collection and corrupted its state. The collection's state is no longer correct.
at System.Collections.Generic.Dictionary`2.FindEntry(TKey key)
at System.Collections.Generic.Dictionary`2.TryGetValue(TKey key, TValue& value)
at Topten.RichTextKit.StyleManager.Update(String fontFamily, Nullable`1 fontSize, Nullable`1 fontWeight, Nullable`1 fontItalic, Nullable`1 underline, Nullable`1 strikeThrough, Nullable`1 lineHeight, Nullable`1 textColor, Nullable`1 letterSpacing, Nullable`1 fontVariant, Nullable`1 textDirection)
Unhandled exception. System.ArgumentException: An item with the same key has already been added. Key: Arial.64.700.False.None.None.1.#ff000000.0.Normal.Auto
at System.Collections.Generic.Dictionary`2.TryInsert(TKey key, TValue value, InsertionBehavior behavior)
at System.Collections.Generic.Dictionary`2.Add(TKey key, TValue value)
at Topten.RichTextKit.StyleManager.Update(String fontFamily, Nullable`1 fontSize, Nullable`1 fontWeight, Nullable`1 fontItalic, Nullable`1 underline, Nullable`1 strikeThrough, Nullable`1 lineHeight, Nullable`1 textColor, Nullable`1 letterSpacing, Nullable`1 fontVariant, Nullable`1 textDirection)
Also there seem to be some rendering issues where text styles are mixed up. The following is an example for this. This is the output of the above sample code when it is rendered correctly:
Here is an example of the above code with rendering issues, where the "Text in 32 Normal" is unfortunately not rendered in 32 normal
hi all,
i'm currently playing a bit with this api - it's really nice so far!
one thing i want to explore is hyphenation. so far, the line break algorithm works nice and also breaks lines correctly at syllables, when the strings contain soft hyphens. This is already great.
so my idea is to prepare the strings and insert soft hyphens based on some dictionary.
a great addition to richtextkit would be to control the hyphen symbol somehow. like:
if a line breaks at a soft hyphen, add a given hyphen character(s) at the end of this line
and maybe hyphenation also came to your mind already and there's more opinions about this?
best,
sebl
I can see no way to do line break mode on the Textblock.
Emoji sequences that are made up of multiple characters combined with the Zero-Width joiners are getting rendered as individual emoji, not the resulting single emoji. A good example of this is the pirate flag (🏴☠️) is getting rendered as a black flag (🏴) and a skull and crossbones (☠).
On line 79 of FontFallback.cs, there is a comment:
// Consume as many characters as possible using the requested type face
However, on line 80, there is a 1 hard-coded and the call to consume additional characters is commented out:
count = 1;// RunFace.GetGlyphs((IntPtr)(pch + pos), length - pos, SKEncoding.Utf32, out var glyphs);
If I remove the 1 and restore the call to RunFace.GetGlyphs
, the emoji render correctly.
I am curious why the 1 was hard-coded and what possibly breaks by restoring the commented out code.
Hi. I've started using RichTextKit and noticed that strikeout line is not centered - it is very close to the underline line. See the:
example .
fix text rtl drwing.
mono/SkiaSharp#1461
Hello,
first: thank you for this great project.
I have only one style in a TextBlock and want to change the TextColor of this text later. Is this possible? Or must I recreate the whole TextBlock?
Thank you for your help.
Best regards,
Dirk
Is it possible to do vertical alignment within an SKRect?
Historically (before I transitioned to this library) I would draw text twice. Once with a dark coloured text and a stroke. Then again with another colour over the top to give a high contrast to the text.
It would be great to be able to reproduce this effect. At the moment I'm having to set a background colour instead which is not quite as nice to look at.
Would you consider adding this feature or advise me how to achieve it if I have missed it in the docs.
Thanks
When a string ending with \n
is added to a TextBlock
, measurements and hit testing do not take the newline into account unless there are other characters after it. For example,
tb = new TextBlock();
tb.AddText("Abc\n", new Style());
has tb.LineCount == 1
, and it has a MeasuredHeight
of a single line.
Where,
tb.AddText("Abc\nxyz", new Style());
or
tb.AddText("Abc\n\n", new Style());
have, as expected, tb.LineCount == 2
and a MeasuredHeight
of two lines.
This makes even simple text editing difficult, as it's reasonable for a user to append a single \n
at the end of the last line, and it's difficult for developers to special-case correctly to handle this to, for example, put the cursor on the new empty line or correctly size a container to the TextBlock
.
A declarative, efficient, and flexible JavaScript library for building user interfaces.
🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.
TypeScript is a superset of JavaScript that compiles to clean JavaScript output.
An Open Source Machine Learning Framework for Everyone
The Web framework for perfectionists with deadlines.
A PHP framework for web artisans
Bring data to life with SVG, Canvas and HTML. 📊📈🎉
JavaScript (JS) is a lightweight interpreted programming language with first-class functions.
Some thing interesting about web. New door for the world.
A server is a program made to process requests and deliver data to clients.
Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.
Some thing interesting about visualization, use data art
Some thing interesting about game, make everyone happy.
We are working to build community through open source technology. NB: members must have two-factor auth.
Open source projects and samples from Microsoft.
Google ❤️ Open Source for everyone.
Alibaba Open Source for everyone
Data-Driven Documents codes.
China tencent open source team.