Browse Source

Merge branch 'master' into feature/add-quality-value-to-IBitmapImpl-Save

pull/9106/head
Max Katz 4 years ago
committed by GitHub
parent
commit
9688823de0
No known key found for this signature in database GPG Key ID: 4AEE18F83AFDEB23
  1. 5
      Directory.Build.targets
  2. 4
      native/Avalonia.Native/src/OSX/WindowImpl.mm
  3. 11
      native/Avalonia.Native/src/OSX/app.mm
  4. 2
      native/Avalonia.Native/src/OSX/common.h
  5. 41
      native/Avalonia.Native/src/OSX/main.mm
  6. 2
      nukebuild/_build.csproj
  7. 2
      readme.md
  8. 6
      src/Avalonia.Base/Collections/AvaloniaDictionary.cs
  9. 3
      src/Avalonia.Base/Media/FormattedText.cs
  10. 77
      src/Avalonia.Base/Media/GlyphRun.cs
  11. 12
      src/Avalonia.Base/Media/PathMarkupParser.cs
  12. 19
      src/Avalonia.Base/Media/TextFormatting/GenericTextParagraphProperties.cs
  13. 3
      src/Avalonia.Base/Media/TextFormatting/TextFormatterImpl.cs
  14. 21
      src/Avalonia.Base/Media/TextFormatting/TextLayout.cs
  15. 9
      src/Avalonia.Base/Media/TextFormatting/TextParagraphProperties.cs
  16. 9
      src/Avalonia.Base/Media/TextFormatting/TextShaperOptions.cs
  17. 37
      src/Avalonia.Base/Platform/IPlatformRenderInterface.cs
  18. 6
      src/Avalonia.Base/Utilities/IdentifierParser.cs
  19. 20
      src/Avalonia.Controls/Presenters/TextPresenter.cs
  20. 56
      src/Avalonia.Controls/TextBlock.cs
  21. 12
      src/Avalonia.Controls/TextBox.cs
  22. 42
      src/Avalonia.Headless/HeadlessPlatformRenderInterface.cs
  23. 2
      src/Avalonia.MicroCom/Avalonia.MicroCom.csproj
  24. 2
      src/Avalonia.Native/Avalonia.Native.csproj
  25. 2
      src/Avalonia.Native/AvaloniaNativePlatform.cs
  26. 8
      src/Avalonia.Native/AvaloniaNativePlatformExtensions.cs
  27. 2
      src/Avalonia.Native/avn.idl
  28. 1
      src/Avalonia.Themes.Fluent/Controls/TextBox.xaml
  29. 4
      src/Avalonia.Themes.Simple/Controls/TextBox.xaml
  30. 12
      src/Markup/Avalonia.Markup/Markup/Parsers/BindingExpressionGrammar.cs
  31. 4
      src/Skia/Avalonia.Skia/GlyphTypefaceImpl.cs
  32. 126
      src/Skia/Avalonia.Skia/PlatformRenderInterface.cs
  33. 2
      src/Skia/Avalonia.Skia/TextShaperImpl.cs
  34. 131
      src/Windows/Avalonia.Direct2D1/Direct2D1Platform.cs
  35. 2
      src/Windows/Avalonia.Win32/Avalonia.Win32.csproj
  36. 17
      tests/Avalonia.Base.UnitTests/VisualTree/MockRenderInterface.cs
  37. 15
      tests/Avalonia.Benchmarks/NullRenderingPlatform.cs
  38. 2
      tests/Avalonia.Skia.UnitTests/Media/TextFormatting/TextFormatterTests.cs
  39. 6
      tests/Avalonia.Skia.UnitTests/Media/TextFormatting/TextLineTests.cs
  40. 12
      tests/Avalonia.UnitTests/MockGlyphRun.cs
  41. 2
      tests/Avalonia.UnitTests/MockPlatformRenderInterface.cs

5
Directory.Build.targets

@ -0,0 +1,5 @@
<Project>
<PropertyGroup Condition="$(NETCoreSdkVersion.StartsWith('7.0'))">
<DefineConstants>$(DefineConstants);NET7SDK</DefineConstants>
</PropertyGroup>
</Project>

4
native/Avalonia.Native/src/OSX/WindowImpl.mm

@ -63,7 +63,7 @@ HRESULT WindowImpl::Show(bool activate, bool isDialog) {
START_COM_CALL; START_COM_CALL;
@autoreleasepool { @autoreleasepool {
_isDialog = isDialog; _isDialog = isDialog || _parent != nullptr;
WindowBaseImpl::Show(activate, isDialog); WindowBaseImpl::Show(activate, isDialog);
@ -96,6 +96,8 @@ HRESULT WindowImpl::SetParent(IAvnWindow *parent) {
auto cparent = dynamic_cast<WindowImpl *>(parent); auto cparent = dynamic_cast<WindowImpl *>(parent);
_parent = cparent; _parent = cparent;
_isDialog = _parent != nullptr;
if(_parent != nullptr && Window != nullptr){ if(_parent != nullptr && Window != nullptr){
// If one tries to show a child window with a minimized parent window, then the parent window will be // If one tries to show a child window with a minimized parent window, then the parent window will be

11
native/Avalonia.Native/src/OSX/app.mm

@ -95,11 +95,14 @@ ComPtr<IAvnApplicationEvents> _events;
} }
@end @end
extern void InitializeAvnApp(IAvnApplicationEvents* events) extern void InitializeAvnApp(IAvnApplicationEvents* events, bool disableAppDelegate)
{ {
NSApplication* app = [AvnApplication sharedApplication]; if(!disableAppDelegate)
id delegate = [[AvnAppDelegate alloc] initWithEvents:events]; {
[app setDelegate:delegate]; NSApplication* app = [AvnApplication sharedApplication];
id delegate = [[AvnAppDelegate alloc] initWithEvents:events];
[app setDelegate:delegate];
}
} }
HRESULT AvnApplicationCommands::HideApp() HRESULT AvnApplicationCommands::HideApp()

2
native/Avalonia.Native/src/OSX/common.h

@ -32,7 +32,7 @@ extern void SetServicesMenu (IAvnMenu* menu);
extern IAvnMenu* GetAppMenu (); extern IAvnMenu* GetAppMenu ();
extern NSMenuItem* GetAppMenuItem (); extern NSMenuItem* GetAppMenuItem ();
extern void InitializeAvnApp(IAvnApplicationEvents* events); extern void InitializeAvnApp(IAvnApplicationEvents* events, bool disableAppDelegate);
extern NSApplicationActivationPolicy AvnDesiredActivationPolicy; extern NSApplicationActivationPolicy AvnDesiredActivationPolicy;
extern NSPoint ToNSPoint (AvnPoint p); extern NSPoint ToNSPoint (AvnPoint p);
extern NSRect ToNSRect (AvnRect r); extern NSRect ToNSRect (AvnRect r);

41
native/Avalonia.Native/src/OSX/main.mm

@ -3,6 +3,8 @@
#include "common.h" #include "common.h"
static NSString* s_appTitle = @"Avalonia"; static NSString* s_appTitle = @"Avalonia";
static int disableSetProcessName = 0;
static bool disableAppDelegate = false;
// Copyright (c) 2011 The Chromium Authors. All rights reserved. // Copyright (c) 2011 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be // Use of this source code is governed by a BSD-style license that can be
@ -111,11 +113,17 @@ public:
@autoreleasepool @autoreleasepool
{ {
auto appTitle = [NSString stringWithUTF8String: utf8String]; auto appTitle = [NSString stringWithUTF8String: utf8String];
if (disableSetProcessName == 0)
[[NSProcessInfo processInfo] setProcessName:appTitle]; {
[[NSProcessInfo processInfo] setProcessName:appTitle];
SetProcessName(appTitle); SetProcessName(appTitle);
}
if (disableSetProcessName == 1)
{
auto rootMenu = [NSApp mainMenu];
[rootMenu setTitle:appTitle];
}
return S_OK; return S_OK;
} }
@ -133,6 +141,27 @@ public:
} }
} }
virtual HRESULT SetDisableSetProcessName(int disable) override
{
START_COM_CALL;
@autoreleasepool
{
disableSetProcessName = disable;
return S_OK;
}
}
virtual HRESULT SetDisableAppDelegate(int disable) override
{
START_COM_CALL;
@autoreleasepool {
disableAppDelegate = disable;
return S_OK;
}
}
}; };
/// See "Using POSIX Threads in a Cocoa Application" section here: /// See "Using POSIX Threads in a Cocoa Application" section here:
@ -175,7 +204,7 @@ public:
@autoreleasepool{ @autoreleasepool{
[[ThreadingInitializer new] do]; [[ThreadingInitializer new] do];
} }
InitializeAvnApp(events); InitializeAvnApp(events, disableAppDelegate);
return S_OK; return S_OK;
}; };

2
nukebuild/_build.csproj

@ -13,7 +13,7 @@
<ItemGroup> <ItemGroup>
<PackageReference Include="Nuke.Common" Version="6.2.1" /> <PackageReference Include="Nuke.Common" Version="6.2.1" />
<PackageReference Include="vswhere" Version="2.6.7" Condition=" '$(OS)' == 'Windows_NT' " /> <PackageReference Include="vswhere" Version="2.6.7" Condition=" '$(OS)' == 'Windows_NT' " />
<PackageReference Include="MicroCom.CodeGenerator" Version="0.10.4" /> <PackageReference Include="MicroCom.CodeGenerator" Version="0.11.0" />
<!-- Keep in sync with Avalonia.Build.Tasks --> <!-- Keep in sync with Avalonia.Build.Tasks -->
<PackageReference Include="Mono.Cecil" Version="0.11.4" /> <PackageReference Include="Mono.Cecil" Version="0.11.4" />
<PackageReference Include="SourceLink" Version="1.1.0" GeneratePathProperty="true" /> <PackageReference Include="SourceLink" Version="1.1.0" GeneratePathProperty="true" />

2
readme.md

@ -104,7 +104,7 @@ Support this project by becoming a sponsor. Your logo will show up here with a l
## Commercial Support ## Commercial Support
We have a range of [support plans available](https://avaloniaui.net/support.html) for those looking to partner with the creators of Avalonia, enabling access to the best support at every step of the development process. We have a range of [support plans available](https://avaloniaui.net/support) for those looking to partner with the creators of Avalonia, enabling access to the best support at every step of the development process.
*Please note that donations are not considered payment for commercial support agreements. Please contact us to discuss your needs first. [team@avaloniaui.net](mailto://team@avaloniaui.net)* *Please note that donations are not considered payment for commercial support agreements. Please contact us to discuss your needs first. [team@avaloniaui.net](mailto://team@avaloniaui.net)*
## .NET Foundation ## .NET Foundation

6
src/Avalonia.Base/Collections/AvaloniaDictionary.cs

@ -81,7 +81,7 @@ namespace Avalonia.Collections
if (replace) if (replace)
{ {
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs($"Item[{key}]")); PropertyChanged?.Invoke(this, new PropertyChangedEventArgs($"{CommonPropertyNames.IndexerName}[{key}]"));
if (CollectionChanged != null) if (CollectionChanged != null)
{ {
@ -148,7 +148,7 @@ namespace Avalonia.Collections
{ {
_inner.Remove(key); _inner.Remove(key);
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(nameof(Count))); PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(nameof(Count)));
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs($"Item[{key}]")); PropertyChanged?.Invoke(this, new PropertyChangedEventArgs($"{CommonPropertyNames.IndexerName}[{key}]"));
if (CollectionChanged != null) if (CollectionChanged != null)
{ {
@ -208,7 +208,7 @@ namespace Avalonia.Collections
private void NotifyAdd(TKey key, TValue value) private void NotifyAdd(TKey key, TValue value)
{ {
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(nameof(Count))); PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(nameof(Count)));
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs($"Item[{key}]")); PropertyChanged?.Invoke(this, new PropertyChangedEventArgs($"{CommonPropertyNames.IndexerName}[{key}]"));
if (CollectionChanged != null) if (CollectionChanged != null)

3
src/Avalonia.Base/Media/FormattedText.cs

@ -93,7 +93,8 @@ namespace Avalonia.Media
runProps, runProps,
TextWrapping.WrapWithOverflow, TextWrapping.WrapWithOverflow,
0, // line height not specified 0, // line height not specified
0 // indentation not specified 0, // indentation not specified
0
); );
InvalidateMetrics(); InvalidateMetrics();

77
src/Avalonia.Base/Media/GlyphRun.cs

@ -170,7 +170,7 @@ namespace Avalonia.Media
} }
/// <summary> /// <summary>
/// Gets the scale of the current <see cref="Media.GlyphTypeface"/> /// Gets the scale of the current <see cref="IGlyphTypeface"/>
/// </summary> /// </summary>
internal double Scale => FontRenderingEmSize / GlyphTypeface.Metrics.DesignEmHeight; internal double Scale => FontRenderingEmSize / GlyphTypeface.Metrics.DesignEmHeight;
@ -860,82 +860,9 @@ namespace Avalonia.Media
private IGlyphRunImpl CreateGlyphRunImpl() private IGlyphRunImpl CreateGlyphRunImpl()
{ {
IGlyphRunImpl glyphRunImpl;
var platformRenderInterface = AvaloniaLocator.Current.GetRequiredService<IPlatformRenderInterface>(); var platformRenderInterface = AvaloniaLocator.Current.GetRequiredService<IPlatformRenderInterface>();
var count = GlyphIndices.Count;
var scale = (float)(FontRenderingEmSize / GlyphTypeface.Metrics.DesignEmHeight);
if (GlyphOffsets == null)
{
if (GlyphTypeface.Metrics.IsFixedPitch)
{
var buffer = platformRenderInterface.AllocateGlyphRun(GlyphTypeface, (float)FontRenderingEmSize, count);
var glyphs = buffer.GlyphIndices;
for (int i = 0; i < glyphs.Length; i++)
{
glyphs[i] = GlyphIndices[i];
}
glyphRunImpl = buffer.Build();
}
else
{
var buffer = platformRenderInterface.AllocateHorizontalGlyphRun(GlyphTypeface, (float)FontRenderingEmSize, count);
var glyphs = buffer.GlyphIndices;
var positions = buffer.GlyphPositions;
var width = 0d;
for (var i = 0; i < count; i++)
{
positions[i] = (float)width;
if (GlyphAdvances == null)
{
width += GlyphTypeface.GetGlyphAdvance(GlyphIndices[i]) * scale;
}
else
{
width += GlyphAdvances[i];
}
glyphs[i] = GlyphIndices[i];
}
glyphRunImpl = buffer.Build();
}
}
else
{
var buffer = platformRenderInterface.AllocatePositionedGlyphRun(GlyphTypeface, (float)FontRenderingEmSize, count);
var glyphs = buffer.GlyphIndices;
var glyphPositions = buffer.GlyphPositions;
var currentX = 0.0;
for (var i = 0; i < count; i++)
{
var glyphOffset = GlyphOffsets[i];
glyphPositions[i] = new PointF((float)(currentX + glyphOffset.X), (float)glyphOffset.Y);
if (GlyphAdvances == null)
{
currentX += GlyphTypeface.GetGlyphAdvance(GlyphIndices[i]) * scale;
}
else
{
currentX += GlyphAdvances[i];
}
glyphs[i] = GlyphIndices[i];
}
glyphRunImpl = buffer.Build();
}
return glyphRunImpl; return platformRenderInterface.CreateGlyphRun(GlyphTypeface, FontRenderingEmSize, GlyphIndices, GlyphAdvances, GlyphOffsets);
} }
void IDisposable.Dispose() void IDisposable.Dispose()

12
src/Avalonia.Base/Media/PathMarkupParser.cs

@ -188,7 +188,11 @@ namespace Avalonia.Media
_isOpen = true; _isOpen = true;
} }
private void SetFillRule(scoped ref ReadOnlySpan<char> span) private void SetFillRule(
#if NET7SDK
scoped
#endif
ref ReadOnlySpan<char> span)
{ {
ThrowIfDisposed(); ThrowIfDisposed();
@ -452,7 +456,11 @@ namespace Avalonia.Media
return !span.IsEmpty && (span[0] == ',' || span[0] == '-' || span[0] == '.' || char.IsDigit(span[0])); return !span.IsEmpty && (span[0] == ',' || span[0] == '-' || span[0] == '.' || char.IsDigit(span[0]));
} }
private static bool ReadArgument(scoped ref ReadOnlySpan<char> remaining, out ReadOnlySpan<char> argument) private static bool ReadArgument(
#if NET7SDK
scoped
#endif
ref ReadOnlySpan<char> remaining, out ReadOnlySpan<char> argument)
{ {
remaining = SkipWhitespace(remaining); remaining = SkipWhitespace(remaining);
if (remaining.IsEmpty) if (remaining.IsEmpty)

19
src/Avalonia.Base/Media/TextFormatting/GenericTextParagraphProperties.cs

@ -17,15 +17,18 @@
/// <param name="textAlignment">logical horizontal alignment</param> /// <param name="textAlignment">logical horizontal alignment</param>
/// <param name="textWrap">text wrap option</param> /// <param name="textWrap">text wrap option</param>
/// <param name="lineHeight">Paragraph line height</param> /// <param name="lineHeight">Paragraph line height</param>
/// <param name="letterSpacing">letter spacing</param>
public GenericTextParagraphProperties(TextRunProperties defaultTextRunProperties, public GenericTextParagraphProperties(TextRunProperties defaultTextRunProperties,
TextAlignment textAlignment = TextAlignment.Left, TextAlignment textAlignment = TextAlignment.Left,
TextWrapping textWrap = TextWrapping.NoWrap, TextWrapping textWrap = TextWrapping.NoWrap,
double lineHeight = 0) double lineHeight = 0,
double letterSpacing = 0)
{ {
DefaultTextRunProperties = defaultTextRunProperties; DefaultTextRunProperties = defaultTextRunProperties;
_textAlignment = textAlignment; _textAlignment = textAlignment;
_textWrap = textWrap; _textWrap = textWrap;
_lineHeight = lineHeight; _lineHeight = lineHeight;
LetterSpacing = letterSpacing;
} }
/// <summary> /// <summary>
@ -39,6 +42,7 @@
/// <param name="textWrap">text wrap option</param> /// <param name="textWrap">text wrap option</param>
/// <param name="lineHeight">Paragraph line height</param> /// <param name="lineHeight">Paragraph line height</param>
/// <param name="indent">line indentation</param> /// <param name="indent">line indentation</param>
/// <param name="letterSpacing">letter spacing</param>
public GenericTextParagraphProperties( public GenericTextParagraphProperties(
FlowDirection flowDirection, FlowDirection flowDirection,
TextAlignment textAlignment, TextAlignment textAlignment,
@ -47,8 +51,8 @@
TextRunProperties defaultTextRunProperties, TextRunProperties defaultTextRunProperties,
TextWrapping textWrap, TextWrapping textWrap,
double lineHeight, double lineHeight,
double indent double indent,
) double letterSpacing)
{ {
_flowDirection = flowDirection; _flowDirection = flowDirection;
_textAlignment = textAlignment; _textAlignment = textAlignment;
@ -57,6 +61,7 @@
DefaultTextRunProperties = defaultTextRunProperties; DefaultTextRunProperties = defaultTextRunProperties;
_textWrap = textWrap; _textWrap = textWrap;
_lineHeight = lineHeight; _lineHeight = lineHeight;
LetterSpacing = letterSpacing;
Indent = indent; Indent = indent;
} }
@ -72,7 +77,8 @@
textParagraphProperties.DefaultTextRunProperties, textParagraphProperties.DefaultTextRunProperties,
textParagraphProperties.TextWrapping, textParagraphProperties.TextWrapping,
textParagraphProperties.LineHeight, textParagraphProperties.LineHeight,
textParagraphProperties.Indent) textParagraphProperties.Indent,
textParagraphProperties.LetterSpacing)
{ {
} }
@ -131,6 +137,11 @@
/// </summary> /// </summary>
public override double Indent { get; } public override double Indent { get; }
/// <summary>
/// The letter spacing
/// </summary>
public override double LetterSpacing { get; }
/// <summary> /// <summary>
/// Set text flow direction /// Set text flow direction
/// </summary> /// </summary>

3
src/Avalonia.Base/Media/TextFormatting/TextFormatterImpl.cs

@ -249,7 +249,8 @@ namespace Avalonia.Media.TextFormatting
var shaperOptions = new TextShaperOptions(currentRun.Properties!.Typeface.GlyphTypeface, var shaperOptions = new TextShaperOptions(currentRun.Properties!.Typeface.GlyphTypeface,
currentRun.Properties.FontRenderingEmSize, currentRun.Properties.FontRenderingEmSize,
shapeableRun.BidiLevel, currentRun.Properties.CultureInfo, paragraphProperties.DefaultIncrementalTab); shapeableRun.BidiLevel, currentRun.Properties.CultureInfo,
paragraphProperties.DefaultIncrementalTab, paragraphProperties.LetterSpacing);
drawableTextRuns.AddRange(ShapeTogether(groupedRuns, text, shaperOptions)); drawableTextRuns.AddRange(ShapeTogether(groupedRuns, text, shaperOptions));

21
src/Avalonia.Base/Media/TextFormatting/TextLayout.cs

@ -31,6 +31,7 @@ namespace Avalonia.Media.TextFormatting
/// <param name="maxWidth">The maximum width.</param> /// <param name="maxWidth">The maximum width.</param>
/// <param name="maxHeight">The maximum height.</param> /// <param name="maxHeight">The maximum height.</param>
/// <param name="lineHeight">The height of each line of text.</param> /// <param name="lineHeight">The height of each line of text.</param>
/// <param name="letterSpacing">The letter spacing that is applied to rendered glyphs.</param>
/// <param name="maxLines">The maximum number of text lines.</param> /// <param name="maxLines">The maximum number of text lines.</param>
/// <param name="textStyleOverrides">The text style overrides.</param> /// <param name="textStyleOverrides">The text style overrides.</param>
public TextLayout( public TextLayout(
@ -46,12 +47,13 @@ namespace Avalonia.Media.TextFormatting
double maxWidth = double.PositiveInfinity, double maxWidth = double.PositiveInfinity,
double maxHeight = double.PositiveInfinity, double maxHeight = double.PositiveInfinity,
double lineHeight = double.NaN, double lineHeight = double.NaN,
double letterSpacing = 0,
int maxLines = 0, int maxLines = 0,
IReadOnlyList<ValueSpan<TextRunProperties>>? textStyleOverrides = null) IReadOnlyList<ValueSpan<TextRunProperties>>? textStyleOverrides = null)
{ {
_paragraphProperties = _paragraphProperties =
CreateTextParagraphProperties(typeface, fontSize, foreground, textAlignment, textWrapping, CreateTextParagraphProperties(typeface, fontSize, foreground, textAlignment, textWrapping,
textDecorations, flowDirection, lineHeight); textDecorations, flowDirection, lineHeight, letterSpacing);
_textSource = new FormattedTextSource(text.AsMemory(), _paragraphProperties.DefaultTextRunProperties, textStyleOverrides); _textSource = new FormattedTextSource(text.AsMemory(), _paragraphProperties.DefaultTextRunProperties, textStyleOverrides);
@ -63,6 +65,8 @@ namespace Avalonia.Media.TextFormatting
MaxHeight = maxHeight; MaxHeight = maxHeight;
LetterSpacing = letterSpacing;
MaxLines = maxLines; MaxLines = maxLines;
TextLines = CreateTextLines(); TextLines = CreateTextLines();
@ -77,6 +81,7 @@ namespace Avalonia.Media.TextFormatting
/// <param name="maxWidth">The maximum width.</param> /// <param name="maxWidth">The maximum width.</param>
/// <param name="maxHeight">The maximum height.</param> /// <param name="maxHeight">The maximum height.</param>
/// <param name="lineHeight">The height of each line of text.</param> /// <param name="lineHeight">The height of each line of text.</param>
/// <param name="letterSpacing">The letter spacing that is applied to rendered glyphs.</param>
/// <param name="maxLines">The maximum number of text lines.</param> /// <param name="maxLines">The maximum number of text lines.</param>
public TextLayout( public TextLayout(
ITextSource textSource, ITextSource textSource,
@ -85,6 +90,7 @@ namespace Avalonia.Media.TextFormatting
double maxWidth = double.PositiveInfinity, double maxWidth = double.PositiveInfinity,
double maxHeight = double.PositiveInfinity, double maxHeight = double.PositiveInfinity,
double lineHeight = double.NaN, double lineHeight = double.NaN,
double letterSpacing = 0,
int maxLines = 0) int maxLines = 0)
{ {
_textSource = textSource; _textSource = textSource;
@ -99,6 +105,8 @@ namespace Avalonia.Media.TextFormatting
MaxHeight = maxHeight; MaxHeight = maxHeight;
LetterSpacing = letterSpacing;
MaxLines = maxLines; MaxLines = maxLines;
TextLines = CreateTextLines(); TextLines = CreateTextLines();
@ -128,6 +136,11 @@ namespace Avalonia.Media.TextFormatting
/// </summary> /// </summary>
public int MaxLines { get; } public int MaxLines { get; }
/// <summary>
/// Gets the text spacing.
/// </summary>
public double LetterSpacing { get; }
/// <summary> /// <summary>
/// Gets the text lines. /// Gets the text lines.
/// </summary> /// </summary>
@ -374,15 +387,17 @@ namespace Avalonia.Media.TextFormatting
/// <param name="textDecorations">The text decorations.</param> /// <param name="textDecorations">The text decorations.</param>
/// <param name="flowDirection">The text flow direction.</param> /// <param name="flowDirection">The text flow direction.</param>
/// <param name="lineHeight">The height of each line of text.</param> /// <param name="lineHeight">The height of each line of text.</param>
/// <param name="letterSpacing">The letter spacing that is applied to rendered glyphs.</param>
/// <returns></returns> /// <returns></returns>
private static TextParagraphProperties CreateTextParagraphProperties(Typeface typeface, double fontSize, private static TextParagraphProperties CreateTextParagraphProperties(Typeface typeface, double fontSize,
IBrush? foreground, TextAlignment textAlignment, TextWrapping textWrapping, IBrush? foreground, TextAlignment textAlignment, TextWrapping textWrapping,
TextDecorationCollection? textDecorations, FlowDirection flowDirection, double lineHeight) TextDecorationCollection? textDecorations, FlowDirection flowDirection, double lineHeight,
double letterSpacing)
{ {
var textRunStyle = new GenericTextRunProperties(typeface, fontSize, textDecorations, foreground); var textRunStyle = new GenericTextRunProperties(typeface, fontSize, textDecorations, foreground);
return new GenericTextParagraphProperties(flowDirection, textAlignment, true, false, return new GenericTextParagraphProperties(flowDirection, textAlignment, true, false,
textRunStyle, textWrapping, lineHeight, 0); textRunStyle, textWrapping, lineHeight, 0, letterSpacing);
} }
/// <summary> /// <summary>

9
src/Avalonia.Base/Media/TextFormatting/TextParagraphProperties.cs

@ -57,7 +57,7 @@
public abstract double Indent { get; } public abstract double Indent { get; }
/// <summary> /// <summary>
/// Paragraph indentation /// Get the paragraph indentation.
/// </summary> /// </summary>
public virtual double ParagraphIndent public virtual double ParagraphIndent
{ {
@ -65,11 +65,16 @@
} }
/// <summary> /// <summary>
/// Default Incremental Tab /// Gets the default incremental tab width.
/// </summary> /// </summary>
public virtual double DefaultIncrementalTab public virtual double DefaultIncrementalTab
{ {
get { return 4 * DefaultTextRunProperties.FontRenderingEmSize; } get { return 4 * DefaultTextRunProperties.FontRenderingEmSize; }
} }
/// <summary>
/// Gets the letter spacing.
/// </summary>
public virtual double LetterSpacing { get; }
} }
} }

9
src/Avalonia.Base/Media/TextFormatting/TextShaperOptions.cs

@ -12,13 +12,15 @@ namespace Avalonia.Media.TextFormatting
double fontRenderingEmSize = 12, double fontRenderingEmSize = 12,
sbyte bidiLevel = 0, sbyte bidiLevel = 0,
CultureInfo? culture = null, CultureInfo? culture = null,
double incrementalTabWidth = 0) double incrementalTabWidth = 0,
double letterSpacing = 0)
{ {
Typeface = typeface; Typeface = typeface;
FontRenderingEmSize = fontRenderingEmSize; FontRenderingEmSize = fontRenderingEmSize;
BidiLevel = bidiLevel; BidiLevel = bidiLevel;
Culture = culture; Culture = culture;
IncrementalTabWidth = incrementalTabWidth; IncrementalTabWidth = incrementalTabWidth;
LetterSpacing = letterSpacing;
} }
/// <summary> /// <summary>
@ -45,5 +47,10 @@ namespace Avalonia.Media.TextFormatting
/// </summary> /// </summary>
public double IncrementalTabWidth { get; } public double IncrementalTabWidth { get; }
/// <summary>
/// Get the letter spacing.
/// </summary>
public double LetterSpacing { get; }
} }
} }

37
src/Avalonia.Base/Platform/IPlatformRenderInterface.cs

@ -171,40 +171,15 @@ namespace Avalonia.Platform
IBitmapImpl LoadBitmap(PixelFormat format, AlphaFormat alphaFormat, IntPtr data, PixelSize size, Vector dpi, int stride); IBitmapImpl LoadBitmap(PixelFormat format, AlphaFormat alphaFormat, IntPtr data, PixelSize size, Vector dpi, int stride);
/// <summary> /// <summary>
/// Allocates a platform glyph run buffer. /// Creates a platform implementation of a glyph run.
/// </summary> /// </summary>
/// <param name="glyphTypeface">The glyph typeface.</param> /// <param name="glyphTypeface">The glyph typeface.</param>
/// <param name="fontRenderingEmSize">The font rendering em size.</param> /// <param name="fontRenderingEmSize">The font rendering em size.</param>
/// <param name="length">The length.</param> /// <param name="glyphIndices">The glyph indices.</param>
/// <returns>An <see cref="IGlyphRunBuffer"/>.</returns> /// <param name="glyphAdvances">The glyph advances.</param>
/// <remarks> /// <param name="glyphOffsets">The glyph offsets.</param>
/// This buffer only holds glyph indices. /// <returns></returns>
/// </remarks> IGlyphRunImpl CreateGlyphRun(IGlyphTypeface glyphTypeface, double fontRenderingEmSize, IReadOnlyList<ushort> glyphIndices, IReadOnlyList<double>? glyphAdvances, IReadOnlyList<Vector>? glyphOffsets);
IGlyphRunBuffer AllocateGlyphRun(IGlyphTypeface glyphTypeface, float fontRenderingEmSize, int length);
/// <summary>
/// Allocates a horizontal platform glyph run buffer.
/// </summary>
/// <param name="glyphTypeface">The glyph typeface.</param>
/// <param name="fontRenderingEmSize">The font rendering em size.</param>
/// <param name="length">The length.</param>
/// <returns>An <see cref="IGlyphRunBuffer"/>.</returns>
/// <remarks>
/// This buffer holds glyph indices and glyph advances.
/// </remarks>
IHorizontalGlyphRunBuffer AllocateHorizontalGlyphRun(IGlyphTypeface glyphTypeface, float fontRenderingEmSize, int length);
/// <summary>
/// Allocates a positioned platform glyph run buffer.
/// </summary>
/// <param name="glyphTypeface">The glyph typeface.</param>
/// <param name="fontRenderingEmSize">The font rendering em size.</param>
/// <param name="length">The length.</param>
/// <returns>An <see cref="IGlyphRunBuffer"/>.</returns>
/// <remarks>
/// This buffer holds glyph indices, glyph advances and glyph positions.
/// </remarks>
IPositionedGlyphRunBuffer AllocatePositionedGlyphRun(IGlyphTypeface glyphTypeface, float fontRenderingEmSize, int length);
/// <summary> /// <summary>
/// Gets a value indicating whether the platform directly supports rectangles with rounded corners. /// Gets a value indicating whether the platform directly supports rectangles with rounded corners.

6
src/Avalonia.Base/Utilities/IdentifierParser.cs

@ -8,7 +8,11 @@ namespace Avalonia.Utilities
#endif #endif
static class IdentifierParser static class IdentifierParser
{ {
public static ReadOnlySpan<char> ParseIdentifier(this scoped ref CharacterReader r) public static ReadOnlySpan<char> ParseIdentifier(this
#if NET7SDK
scoped
#endif
ref CharacterReader r)
{ {
if (IsValidIdentifierStart(r.Peek)) if (IsValidIdentifierStart(r.Peek))
{ {

20
src/Avalonia.Controls/Presenters/TextPresenter.cs

@ -80,6 +80,12 @@ namespace Avalonia.Controls.Presenters
public static readonly StyledProperty<double> LineHeightProperty = public static readonly StyledProperty<double> LineHeightProperty =
TextBlock.LineHeightProperty.AddOwner<TextPresenter>(); TextBlock.LineHeightProperty.AddOwner<TextPresenter>();
/// <summary>
/// Defines the <see cref="LetterSpacing"/> property.
/// </summary>
public static readonly StyledProperty<double> LetterSpacingProperty =
TextBlock.LetterSpacingProperty.AddOwner<TextPresenter>();
/// <summary> /// <summary>
/// Defines the <see cref="Background"/> property. /// Defines the <see cref="Background"/> property.
/// </summary> /// </summary>
@ -212,6 +218,15 @@ namespace Avalonia.Controls.Presenters
set => SetValue(LineHeightProperty, value); set => SetValue(LineHeightProperty, value);
} }
/// <summary>
/// Gets or sets the letter spacing.
/// </summary>
public double LetterSpacing
{
get => GetValue(LetterSpacingProperty);
set => SetValue(LetterSpacingProperty, value);
}
/// <summary> /// <summary>
/// Gets or sets the text alignment. /// Gets or sets the text alignment.
/// </summary> /// </summary>
@ -333,7 +348,7 @@ namespace Avalonia.Controls.Presenters
var textLayout = new TextLayout(text, typeface, FontSize, foreground, TextAlignment, var textLayout = new TextLayout(text, typeface, FontSize, foreground, TextAlignment,
TextWrapping, maxWidth: maxWidth, maxHeight: maxHeight, textStyleOverrides: textStyleOverrides, TextWrapping, maxWidth: maxWidth, maxHeight: maxHeight, textStyleOverrides: textStyleOverrides,
flowDirection: FlowDirection, lineHeight: LineHeight); flowDirection: FlowDirection, lineHeight: LineHeight, letterSpacing: LetterSpacing);
return textLayout; return textLayout;
} }
@ -916,6 +931,9 @@ namespace Avalonia.Controls.Presenters
case nameof(TextAlignment): case nameof(TextAlignment):
case nameof(TextWrapping): case nameof(TextWrapping):
case nameof(LineHeight):
case nameof(LetterSpacing):
case nameof(SelectionStart): case nameof(SelectionStart):
case nameof(SelectionEnd): case nameof(SelectionEnd):
case nameof(SelectionForegroundBrush): case nameof(SelectionForegroundBrush):

56
src/Avalonia.Controls/TextBlock.cs

@ -82,6 +82,15 @@ namespace Avalonia.Controls
validate: IsValidLineHeight, validate: IsValidLineHeight,
inherits: true); inherits: true);
/// <summary>
/// Defines the <see cref="LetterSpacing"/> property.
/// </summary>
public static readonly AttachedProperty<double> LetterSpacingProperty =
AvaloniaProperty.RegisterAttached<TextBlock, Control, double>(
nameof(LetterSpacing),
0,
inherits: true);
/// <summary> /// <summary>
/// Defines the <see cref="MaxLines"/> property. /// Defines the <see cref="MaxLines"/> property.
/// </summary> /// </summary>
@ -262,6 +271,15 @@ namespace Avalonia.Controls
set => SetValue(LineHeightProperty, value); set => SetValue(LineHeightProperty, value);
} }
/// <summary>
/// Gets or sets the letter spacing.
/// </summary>
public double LetterSpacing
{
get => GetValue(LetterSpacingProperty);
set => SetValue(LetterSpacingProperty, value);
}
/// <summary> /// <summary>
/// Gets or sets the maximum number of text lines. /// Gets or sets the maximum number of text lines.
/// </summary> /// </summary>
@ -475,6 +493,35 @@ namespace Avalonia.Controls
control.SetValue(LineHeightProperty, height); control.SetValue(LineHeightProperty, height);
} }
/// <summary>
/// Reads the attached property from the given element
/// </summary>
/// <param name="control">The element to which to read the attached property.</param>
public static double GetLetterSpacing(Control control)
{
if (control == null)
{
throw new ArgumentNullException(nameof(control));
}
return control.GetValue(LetterSpacingProperty);
}
/// <summary>
/// Writes the attached property LetterSpacing to the given element.
/// </summary>
/// <param name="control">The element to which to write the attached property.</param>
/// <param name="letterSpacing">The property value to set</param>
public static void SetLetterSpacing(Control control, double letterSpacing)
{
if (control == null)
{
throw new ArgumentNullException(nameof(control));
}
control.SetValue(LetterSpacingProperty, letterSpacing);
}
/// <summary> /// <summary>
/// Reads the attached property from the given element /// Reads the attached property from the given element
/// </summary> /// </summary>
@ -584,7 +631,7 @@ namespace Avalonia.Controls
Foreground); Foreground);
var paragraphProperties = new GenericTextParagraphProperties(FlowDirection, TextAlignment, true, false, var paragraphProperties = new GenericTextParagraphProperties(FlowDirection, TextAlignment, true, false,
defaultProperties, TextWrapping, LineHeight, 0); defaultProperties, TextWrapping, LineHeight, 0, LetterSpacing);
ITextSource textSource; ITextSource textSource;
@ -744,9 +791,10 @@ namespace Avalonia.Controls
case nameof(FlowDirection): case nameof(FlowDirection):
case nameof(Padding): case nameof (Padding):
case nameof(LineHeight): case nameof (LineHeight):
case nameof(MaxLines): case nameof (LetterSpacing):
case nameof (MaxLines):
case nameof(Text): case nameof(Text):
case nameof(TextDecorations): case nameof(TextDecorations):

12
src/Avalonia.Controls/TextBox.cs

@ -114,6 +114,12 @@ namespace Avalonia.Controls
public static readonly StyledProperty<double> LineHeightProperty = public static readonly StyledProperty<double> LineHeightProperty =
TextBlock.LineHeightProperty.AddOwner<TextBox>(); TextBlock.LineHeightProperty.AddOwner<TextBox>();
/// <summary>
/// Defines see <see cref="TextBlock.LetterSpacing"/> property.
/// </summary>
public static readonly StyledProperty<double> LetterSpacingProperty =
TextBlock.LetterSpacingProperty.AddOwner<TextBox>();
public static readonly StyledProperty<string?> WatermarkProperty = public static readonly StyledProperty<string?> WatermarkProperty =
AvaloniaProperty.Register<TextBox, string?>(nameof(Watermark)); AvaloniaProperty.Register<TextBox, string?>(nameof(Watermark));
@ -378,6 +384,12 @@ namespace Avalonia.Controls
set => SetValue(MaxLinesProperty, value); set => SetValue(MaxLinesProperty, value);
} }
public double LetterSpacing
{
get => GetValue(LetterSpacingProperty);
set => SetValue(LetterSpacingProperty, value);
}
/// <summary> /// <summary>
/// Gets or sets the line height. /// Gets or sets the line height.
/// </summary> /// </summary>

42
src/Avalonia.Headless/HeadlessPlatformRenderInterface.cs

@ -115,19 +115,16 @@ namespace Avalonia.Headless
return new HeadlessGeometryStub(new Rect(glyphRun.Size)); return new HeadlessGeometryStub(new Rect(glyphRun.Size));
} }
public IGlyphRunBuffer AllocateGlyphRun(IGlyphTypeface glyphTypeface, float fontRenderingEmSize, int length) public IGlyphRunImpl CreateGlyphRun(IGlyphTypeface glyphTypeface, double fontRenderingEmSize, IReadOnlyList<ushort> glyphIndices, IReadOnlyList<double> glyphAdvances, IReadOnlyList<Vector> glyphOffsets)
{ {
return new HeadlessGlyphRunBufferStub(); return new HeadlessGlyphRunStub();
} }
public IHorizontalGlyphRunBuffer AllocateHorizontalGlyphRun(IGlyphTypeface glyphTypeface, float fontRenderingEmSize, int length) class HeadlessGlyphRunStub : IGlyphRunImpl
{
return new HeadlessHorizontalGlyphRunBufferStub();
}
public IPositionedGlyphRunBuffer AllocatePositionedGlyphRun(IGlyphTypeface glyphTypeface, float fontRenderingEmSize, int length)
{ {
return new HeadlessPositionedGlyphRunBufferStub(); public void Dispose()
{
}
} }
class HeadlessGeometryStub : IGeometryImpl class HeadlessGeometryStub : IGeometryImpl
@ -213,33 +210,6 @@ namespace Avalonia.Headless
public Matrix Transform { get; } public Matrix Transform { get; }
} }
class HeadlessGlyphRunBufferStub : IGlyphRunBuffer
{
public Span<ushort> GlyphIndices => Span<ushort>.Empty;
public IGlyphRunImpl Build()
{
return new HeadlessGlyphRunStub();
}
}
class HeadlessHorizontalGlyphRunBufferStub : HeadlessGlyphRunBufferStub, IHorizontalGlyphRunBuffer
{
public Span<float> GlyphPositions => Span<float>.Empty;
}
class HeadlessPositionedGlyphRunBufferStub : HeadlessGlyphRunBufferStub, IPositionedGlyphRunBuffer
{
public Span<System.Drawing.PointF> GlyphPositions => Span<System.Drawing.PointF>.Empty;
}
class HeadlessGlyphRunStub : IGlyphRunImpl
{
public void Dispose()
{
}
}
class HeadlessStreamingGeometryStub : HeadlessGeometryStub, IStreamGeometryImpl class HeadlessStreamingGeometryStub : HeadlessGeometryStub, IStreamGeometryImpl
{ {
public HeadlessStreamingGeometryStub() : base(Rect.Empty) public HeadlessStreamingGeometryStub() : base(Rect.Empty)

2
src/Avalonia.MicroCom/Avalonia.MicroCom.csproj

@ -4,7 +4,7 @@
<AllowUnsafeBlocks>true</AllowUnsafeBlocks> <AllowUnsafeBlocks>true</AllowUnsafeBlocks>
</PropertyGroup> </PropertyGroup>
<ItemGroup> <ItemGroup>
<PackageReference Include="MicroCom.Runtime" Version="0.10.5" /> <PackageReference Include="MicroCom.Runtime" Version="0.11.0" />
<ProjectReference Include="..\Avalonia.Build.Tasks\Avalonia.Build.Tasks.csproj"> <ProjectReference Include="..\Avalonia.Build.Tasks\Avalonia.Build.Tasks.csproj">
<ReferenceOutputAssembly>false</ReferenceOutputAssembly> <ReferenceOutputAssembly>false</ReferenceOutputAssembly>
<ExcludeAssets>all</ExcludeAssets> <ExcludeAssets>all</ExcludeAssets>

2
src/Avalonia.Native/Avalonia.Native.csproj

@ -24,7 +24,7 @@
<ItemGroup> <ItemGroup>
<ProjectReference Include="..\..\packages\Avalonia\Avalonia.csproj" /> <ProjectReference Include="..\..\packages\Avalonia\Avalonia.csproj" />
<ProjectReference Include="..\Avalonia.Dialogs\Avalonia.Dialogs.csproj" /> <ProjectReference Include="..\Avalonia.Dialogs\Avalonia.Dialogs.csproj" />
<PackageReference Include="MicroCom.CodeGenerator.MSBuild" Version="0.10.4" PrivateAssets="all" /> <PackageReference Include="MicroCom.CodeGenerator.MSBuild" Version="0.11.0" PrivateAssets="all" />
<MicroComIdl Include="avn.idl" CSharpInteropPath="Interop.Generated.cs" /> <MicroComIdl Include="avn.idl" CSharpInteropPath="Interop.Generated.cs" />
</ItemGroup> </ItemGroup>

2
src/Avalonia.Native/AvaloniaNativePlatform.cs

@ -103,6 +103,8 @@ namespace Avalonia.Native
var macOpts = AvaloniaLocator.Current.GetService<MacOSPlatformOptions>() ?? new MacOSPlatformOptions(); var macOpts = AvaloniaLocator.Current.GetService<MacOSPlatformOptions>() ?? new MacOSPlatformOptions();
_factory.MacOptions.SetShowInDock(macOpts.ShowInDock ? 1 : 0); _factory.MacOptions.SetShowInDock(macOpts.ShowInDock ? 1 : 0);
_factory.MacOptions.SetDisableSetProcessName(macOpts.DisableSetProcessName ? 1 : 0);
_factory.MacOptions.SetDisableAppDelegate(macOpts.DisableAvaloniaAppDelegate ? 1 : 0);
} }
AvaloniaLocator.CurrentMutable AvaloniaLocator.CurrentMutable

8
src/Avalonia.Native/AvaloniaNativePlatformExtensions.cs

@ -83,5 +83,13 @@ namespace Avalonia
/// Gets or sets a value indicating whether the native macOS menu bar will be enabled for the application. /// Gets or sets a value indicating whether the native macOS menu bar will be enabled for the application.
/// </summary> /// </summary>
public bool DisableNativeMenus { get; set; } public bool DisableNativeMenus { get; set; }
public bool DisableSetProcessName { get; set; }
/// <summary>
/// Gets or sets a value indicating whether Avalonia can install its own AppDelegate.
/// Disabling this can be useful in some scenarios like when running as a plugin inside an existing macOS application.
/// </summary>
public bool DisableAvaloniaAppDelegate { get; set; }
} }
} }

2
src/Avalonia.Native/avn.idl

@ -599,6 +599,8 @@ interface IAvnMacOptions : IUnknown
{ {
HRESULT SetShowInDock(int show); HRESULT SetShowInDock(int show);
HRESULT SetApplicationTitle(char* utf8string); HRESULT SetApplicationTitle(char* utf8string);
HRESULT SetDisableSetProcessName(int disable);
HRESULT SetDisableAppDelegate(int disable);
} }
[uuid(04c1b049-1f43-418a-9159-cae627ec1367)] [uuid(04c1b049-1f43-418a-9159-cae627ec1367)]

1
src/Avalonia.Themes.Fluent/Controls/TextBox.xaml

@ -161,6 +161,7 @@
TextAlignment="{TemplateBinding TextAlignment}" TextAlignment="{TemplateBinding TextAlignment}"
TextWrapping="{TemplateBinding TextWrapping}" TextWrapping="{TemplateBinding TextWrapping}"
LineHeight="{TemplateBinding LineHeight}" LineHeight="{TemplateBinding LineHeight}"
LetterSpacing="{TemplateBinding LetterSpacing}"
PasswordChar="{TemplateBinding PasswordChar}" PasswordChar="{TemplateBinding PasswordChar}"
RevealPassword="{TemplateBinding RevealPassword}" RevealPassword="{TemplateBinding RevealPassword}"
SelectionBrush="{TemplateBinding SelectionBrush}" SelectionBrush="{TemplateBinding SelectionBrush}"

4
src/Avalonia.Themes.Simple/Controls/TextBox.xaml

@ -149,14 +149,14 @@
CaretBrush="{TemplateBinding CaretBrush}" CaretBrush="{TemplateBinding CaretBrush}"
CaretIndex="{TemplateBinding CaretIndex}" CaretIndex="{TemplateBinding CaretIndex}"
LineHeight="{TemplateBinding LineHeight}" LineHeight="{TemplateBinding LineHeight}"
LetterSpacing="{TemplateBinding LetterSpacing}"
PasswordChar="{TemplateBinding PasswordChar}" PasswordChar="{TemplateBinding PasswordChar}"
RevealPassword="{TemplateBinding RevealPassword}" RevealPassword="{TemplateBinding RevealPassword}"
SelectionBrush="{TemplateBinding SelectionBrush}" SelectionBrush="{TemplateBinding SelectionBrush}"
SelectionEnd="{TemplateBinding SelectionEnd}" SelectionEnd="{TemplateBinding SelectionEnd}"
SelectionForegroundBrush="{TemplateBinding SelectionForegroundBrush}" SelectionForegroundBrush="{TemplateBinding SelectionForegroundBrush}"
SelectionStart="{TemplateBinding SelectionStart}" SelectionStart="{TemplateBinding SelectionStart}"
Text="{TemplateBinding Text, Text="{TemplateBinding Text,Mode=TwoWay}"
Mode=TwoWay}"
TextAlignment="{TemplateBinding TextAlignment}" TextAlignment="{TemplateBinding TextAlignment}"
TextWrapping="{TemplateBinding TextWrapping}" /> TextWrapping="{TemplateBinding TextWrapping}" />
</Panel> </Panel>

12
src/Markup/Avalonia.Markup/Markup/Parsers/BindingExpressionGrammar.cs

@ -168,7 +168,11 @@ namespace Avalonia.Markup.Parsers
} }
} }
private static State ParseAttachedProperty(scoped ref CharacterReader r, List<INode> nodes) private static State ParseAttachedProperty(
#if NET7SDK
scoped
#endif
ref CharacterReader r, List<INode> nodes)
{ {
var (ns, owner) = ParseTypeName(ref r); var (ns, owner) = ParseTypeName(ref r);
@ -318,7 +322,11 @@ namespace Avalonia.Markup.Parsers
return State.AfterMember; return State.AfterMember;
} }
private static TypeName ParseTypeName(scoped ref CharacterReader r) private static TypeName ParseTypeName(
#if NET7SDK
scoped
#endif
ref CharacterReader r)
{ {
ReadOnlySpan<char> ns, typeName; ReadOnlySpan<char> ns, typeName;
ns = ReadOnlySpan<char>.Empty; ns = ReadOnlySpan<char>.Empty;

4
src/Skia/Avalonia.Skia/GlyphTypefaceImpl.cs

@ -69,10 +69,6 @@ namespace Avalonia.Skia
public int GlyphCount { get; } public int GlyphCount { get; }
public bool IsFakeBold { get; }
public bool IsFakeItalic { get; }
public bool TryGetGlyphMetrics(ushort glyph, out GlyphMetrics metrics) public bool TryGetGlyphMetrics(ushort glyph, out GlyphMetrics metrics)
{ {
metrics = default; metrics = default;

126
src/Skia/Avalonia.Skia/PlatformRenderInterface.cs

@ -12,8 +12,6 @@ using Avalonia.OpenGL.Imaging;
using Avalonia.Platform; using Avalonia.Platform;
using Avalonia.Media.Imaging; using Avalonia.Media.Imaging;
using SkiaSharp; using SkiaSharp;
using System.Runtime.InteropServices;
using System.Drawing;
namespace Avalonia.Skia namespace Avalonia.Skia
{ {
@ -79,7 +77,7 @@ namespace Avalonia.Skia
var skFont = new SKFont(glyphTypeface.Typeface, fontRenderingEmSize) var skFont = new SKFont(glyphTypeface.Typeface, fontRenderingEmSize)
{ {
Size = fontRenderingEmSize, Size = fontRenderingEmSize,
Edging = SKFontEdging.Antialias, Edging = SKFontEdging.Alias,
Hinting = SKFontHinting.None, Hinting = SKFontHinting.None,
LinearMetrics = true LinearMetrics = true
}; };
@ -244,85 +242,91 @@ namespace Avalonia.Skia
"Current GPU acceleration backend does not support OpenGL integration"); "Current GPU acceleration backend does not support OpenGL integration");
} }
public IGlyphRunBuffer AllocateGlyphRun(IGlyphTypeface glyphTypeface, float fontRenderingEmSize, int length) public IGlyphRunImpl CreateGlyphRun(IGlyphTypeface glyphTypeface, double fontRenderingEmSize, IReadOnlyList<ushort> glyphIndices,
=> new SKGlyphRunBuffer(glyphTypeface, fontRenderingEmSize, length); IReadOnlyList<double> glyphAdvances, IReadOnlyList<Vector> glyphOffsets)
{
if (glyphTypeface == null)
{
throw new ArgumentNullException(nameof(glyphTypeface));
}
public IHorizontalGlyphRunBuffer AllocateHorizontalGlyphRun(IGlyphTypeface glyphTypeface, float fontRenderingEmSize, int length) if (glyphIndices == null)
=> new SKHorizontalGlyphRunBuffer(glyphTypeface, fontRenderingEmSize, length); {
throw new ArgumentNullException(nameof(glyphIndices));
}
public IPositionedGlyphRunBuffer AllocatePositionedGlyphRun(IGlyphTypeface glyphTypeface, float fontRenderingEmSize, int length) var glyphTypefaceImpl = glyphTypeface as GlyphTypefaceImpl;
=> new SKPositionedGlyphRunBuffer(glyphTypeface, fontRenderingEmSize, length);
private abstract class SKGlyphRunBufferBase : IGlyphRunBuffer var font = new SKFont
{ {
protected readonly SKTextBlobBuilder _builder; LinearMetrics = true,
protected readonly SKFont _font; Subpixel = true,
Edging = SKFontEdging.SubpixelAntialias,
Hinting = SKFontHinting.Full,
Size = (float)fontRenderingEmSize,
Typeface = glyphTypefaceImpl.Typeface,
Embolden = (glyphTypefaceImpl.FontSimulations & FontSimulations.Bold) != 0,
SkewX = (glyphTypefaceImpl.FontSimulations & FontSimulations.Oblique) != 0 ? -0.2f : 0
};
var builder = new SKTextBlobBuilder();
public SKGlyphRunBufferBase(IGlyphTypeface glyphTypeface, float fontRenderingEmSize, int length) var count = glyphIndices.Count;
if(glyphOffsets != null && glyphAdvances != null)
{ {
_builder = new SKTextBlobBuilder(); var runBuffer = builder.AllocatePositionedRun(font, count);
var glyphTypefaceImpl = (GlyphTypefaceImpl)glyphTypeface; var glyphSpan = runBuffer.GetGlyphSpan();
var positionSpan = runBuffer.GetPositionSpan();
_font = new SKFont var currentX = 0.0;
{
Subpixel = true,
Edging = SKFontEdging.SubpixelAntialias,
Hinting = SKFontHinting.Full,
LinearMetrics = true,
Size = fontRenderingEmSize,
Typeface = glyphTypefaceImpl.Typeface,
Embolden = glyphTypefaceImpl.IsFakeBold,
SkewX = glyphTypefaceImpl.IsFakeItalic ? -0.2f : 0
};
}
public abstract Span<ushort> GlyphIndices { get; } for (int i = 0; i < glyphOffsets.Count; i++)
{
var offset = glyphOffsets[i];
public IGlyphRunImpl Build() glyphSpan[i] = glyphIndices[i];
{
return new GlyphRunImpl(_builder.Build());
}
}
private sealed class SKGlyphRunBuffer : SKGlyphRunBufferBase positionSpan[i] = new SKPoint((float)(currentX + offset.X), (float)offset.Y);
{
private readonly SKRunBuffer _buffer;
public SKGlyphRunBuffer(IGlyphTypeface glyphTypeface, float fontRenderingEmSize, int length) : base(glyphTypeface, fontRenderingEmSize, length) currentX += glyphAdvances[i];
{ }
_buffer = _builder.AllocateRun(_font, length, 0, 0);
} }
else
{
if(glyphAdvances != null)
{
var runBuffer = builder.AllocateHorizontalRun(font, count, 0);
public override Span<ushort> GlyphIndices => _buffer.GetGlyphSpan(); var glyphSpan = runBuffer.GetGlyphSpan();
} var positionSpan = runBuffer.GetPositionSpan();
private sealed class SKHorizontalGlyphRunBuffer : SKGlyphRunBufferBase, IHorizontalGlyphRunBuffer var currentX = 0.0;
{
private readonly SKHorizontalRunBuffer _buffer;
public SKHorizontalGlyphRunBuffer(IGlyphTypeface glyphTypeface, float fontRenderingEmSize, int length) : base(glyphTypeface, fontRenderingEmSize, length) for (int i = 0; i < glyphOffsets.Count; i++)
{ {
_buffer = _builder.AllocateHorizontalRun(_font, length, 0); glyphSpan[i] = glyphIndices[i];
}
public override Span<ushort> GlyphIndices => _buffer.GetGlyphSpan(); positionSpan[i] = (float)currentX;
public Span<float> GlyphPositions => _buffer.GetPositionSpan(); currentX += glyphAdvances[i];
} }
}
else
{
var runBuffer = builder.AllocateRun(font, count, 0, 0);
private sealed class SKPositionedGlyphRunBuffer : SKGlyphRunBufferBase, IPositionedGlyphRunBuffer var glyphSpan = runBuffer.GetGlyphSpan();
{
private readonly SKPositionedRunBuffer _buffer;
public SKPositionedGlyphRunBuffer(IGlyphTypeface glyphTypeface, float fontRenderingEmSize, int length) : base(glyphTypeface, fontRenderingEmSize, length) for (int i = 0; i < glyphOffsets.Count; i++)
{ {
_buffer = _builder.AllocatePositionedRun(_font, length); glyphSpan[i] = glyphIndices[i];
}
}
} }
public override Span<ushort> GlyphIndices => _buffer.GetGlyphSpan(); return new GlyphRunImpl(builder.Build());
public Span<PointF> GlyphPositions => MemoryMarshal.Cast<SKPoint, PointF>(_buffer.GetPositionSpan());
} }
} }
} }

2
src/Skia/Avalonia.Skia/TextShaperImpl.cs

@ -60,7 +60,7 @@ namespace Avalonia.Skia
var glyphCluster = (int)(sourceInfo.Cluster); var glyphCluster = (int)(sourceInfo.Cluster);
var glyphAdvance = GetGlyphAdvance(glyphPositions, i, textScale); var glyphAdvance = GetGlyphAdvance(glyphPositions, i, textScale) + options.LetterSpacing;
var glyphOffset = GetGlyphOffset(glyphPositions, i, textScale); var glyphOffset = GetGlyphOffset(glyphPositions, i, textScale);

131
src/Windows/Avalonia.Direct2D1/Direct2D1Platform.cs

@ -10,10 +10,7 @@ using Avalonia.Media.Imaging;
using Avalonia.Platform; using Avalonia.Platform;
using SharpDX.DirectWrite; using SharpDX.DirectWrite;
using GlyphRun = Avalonia.Media.GlyphRun; using GlyphRun = Avalonia.Media.GlyphRun;
using TextAlignment = Avalonia.Media.TextAlignment;
using SharpDX.Mathematics.Interop; using SharpDX.Mathematics.Interop;
using System.Runtime.InteropServices;
using System.Drawing;
namespace Avalonia namespace Avalonia
{ {
@ -160,6 +157,72 @@ namespace Avalonia.Direct2D1
public IGeometryImpl CreateGeometryGroup(FillRule fillRule, IReadOnlyList<Geometry> children) => new GeometryGroupImpl(fillRule, children); public IGeometryImpl CreateGeometryGroup(FillRule fillRule, IReadOnlyList<Geometry> children) => new GeometryGroupImpl(fillRule, children);
public IGeometryImpl CreateCombinedGeometry(GeometryCombineMode combineMode, Geometry g1, Geometry g2) => new CombinedGeometryImpl(combineMode, g1, g2); public IGeometryImpl CreateCombinedGeometry(GeometryCombineMode combineMode, Geometry g1, Geometry g2) => new CombinedGeometryImpl(combineMode, g1, g2);
public IGlyphRunImpl CreateGlyphRun(IGlyphTypeface glyphTypeface, double fontRenderingEmSize, IReadOnlyList<ushort> glyphIndices,
IReadOnlyList<double> glyphAdvances, IReadOnlyList<Vector> glyphOffsets)
{
var glyphTypefaceImpl = (GlyphTypefaceImpl)glyphTypeface;
var glyphCount = glyphIndices.Count;
var run = new SharpDX.DirectWrite.GlyphRun
{
FontFace = glyphTypefaceImpl.FontFace,
FontSize = (float)fontRenderingEmSize
};
var indices = new short[glyphCount];
for (var i = 0; i < glyphCount; i++)
{
indices[i] = (short)glyphIndices[i];
}
run.Indices = indices;
run.Advances = new float[glyphCount];
var scale = (float)(fontRenderingEmSize / glyphTypeface.Metrics.DesignEmHeight);
if (glyphAdvances == null)
{
for (var i = 0; i < glyphCount; i++)
{
var advance = glyphTypeface.GetGlyphAdvance(glyphIndices[i]) * scale;
run.Advances[i] = advance;
}
}
else
{
for (var i = 0; i < glyphCount; i++)
{
var advance = (float)glyphAdvances[i];
run.Advances[i] = advance;
}
}
if (glyphOffsets == null)
{
return new GlyphRunImpl(run);
}
run.Offsets = new GlyphOffset[glyphCount];
for (var i = 0; i < glyphCount; i++)
{
var (x, y) = glyphOffsets[i];
run.Offsets[i] = new GlyphOffset
{
AdvanceOffset = (float)x,
AscenderOffset = (float)y
};
}
return new GlyphRunImpl(run);
}
public IGeometryImpl BuildGlyphRunGeometry(GlyphRun glyphRun) public IGeometryImpl BuildGlyphRunGeometry(GlyphRun glyphRun)
{ {
if (glyphRun.GlyphTypeface is not GlyphTypefaceImpl glyphTypeface) if (glyphRun.GlyphTypeface is not GlyphTypefaceImpl glyphTypeface)
@ -260,68 +323,6 @@ namespace Avalonia.Direct2D1
return new WicBitmapImpl(format, alphaFormat, data, size, dpi, stride); return new WicBitmapImpl(format, alphaFormat, data, size, dpi, stride);
} }
private class DWGlyphRunBuffer : IGlyphRunBuffer
{
protected readonly SharpDX.DirectWrite.GlyphRun _dwRun;
public DWGlyphRunBuffer(IGlyphTypeface glyphTypeface, float fontRenderingEmSize, int length)
{
var glyphTypefaceImpl = (GlyphTypefaceImpl)glyphTypeface;
_dwRun = new SharpDX.DirectWrite.GlyphRun
{
FontFace = glyphTypefaceImpl.FontFace,
FontSize = fontRenderingEmSize,
Indices = new short[length]
};
}
public Span<ushort> GlyphIndices => MemoryMarshal.Cast<short, ushort>(_dwRun.Indices.AsSpan());
public IGlyphRunImpl Build()
{
return new GlyphRunImpl(_dwRun);
}
}
private class DWHorizontalGlyphRunBuffer : DWGlyphRunBuffer, IHorizontalGlyphRunBuffer
{
public DWHorizontalGlyphRunBuffer(IGlyphTypeface glyphTypeface, float fontRenderingEmSize, int length)
: base(glyphTypeface, fontRenderingEmSize, length)
{
_dwRun.Advances = new float[length];
}
public Span<float> GlyphPositions => _dwRun.Advances.AsSpan();
}
private class DWPositionedGlyphRunBuffer : DWGlyphRunBuffer, IPositionedGlyphRunBuffer
{
public DWPositionedGlyphRunBuffer(IGlyphTypeface glyphTypeface, float fontRenderingEmSize, int length)
: base(glyphTypeface, fontRenderingEmSize, length)
{
_dwRun.Advances = new float[length];
_dwRun.Offsets = new GlyphOffset[length];
}
public Span<PointF> GlyphPositions => MemoryMarshal.Cast<GlyphOffset, PointF>(_dwRun.Offsets.AsSpan());
}
public IGlyphRunBuffer AllocateGlyphRun(IGlyphTypeface glyphTypeface, float fontRenderingEmSize, int length)
{
return new DWGlyphRunBuffer(glyphTypeface, fontRenderingEmSize, length);
}
public IHorizontalGlyphRunBuffer AllocateHorizontalGlyphRun(IGlyphTypeface glyphTypeface, float fontRenderingEmSize, int length)
{
return new DWHorizontalGlyphRunBuffer(glyphTypeface, fontRenderingEmSize, length);
}
public IPositionedGlyphRunBuffer AllocatePositionedGlyphRun(IGlyphTypeface glyphTypeface, float fontRenderingEmSize, int length)
{
return new DWPositionedGlyphRunBuffer(glyphTypeface, fontRenderingEmSize, length);
}
public bool SupportsIndividualRoundRects => false; public bool SupportsIndividualRoundRects => false;
public AlphaFormat DefaultAlphaFormat => AlphaFormat.Premul; public AlphaFormat DefaultAlphaFormat => AlphaFormat.Premul;

2
src/Windows/Avalonia.Win32/Avalonia.Win32.csproj

@ -11,7 +11,7 @@
<ItemGroup> <ItemGroup>
<ProjectReference Include="..\..\..\packages\Avalonia\Avalonia.csproj" /> <ProjectReference Include="..\..\..\packages\Avalonia\Avalonia.csproj" />
<PackageReference Include="Avalonia.Angle.Windows.Natives" Version="2.1.0.2020091801" /> <PackageReference Include="Avalonia.Angle.Windows.Natives" Version="2.1.0.2020091801" />
<PackageReference Include="MicroCom.CodeGenerator.MSBuild" Version="0.10.4" PrivateAssets="all" /> <PackageReference Include="MicroCom.CodeGenerator.MSBuild" Version="0.11.0" PrivateAssets="all" />
<MicroComIdl Include="WinRT\winrt.idl" CSharpInteropPath="WinRT\WinRT.Generated.cs" /> <MicroComIdl Include="WinRT\winrt.idl" CSharpInteropPath="WinRT\WinRT.Generated.cs" />
<MicroComIdl Include="Win32Com\win32.idl" CSharpInteropPath="Win32Com\Win32.Generated.cs" /> <MicroComIdl Include="Win32Com\win32.idl" CSharpInteropPath="Win32Com\Win32.Generated.cs" />
</ItemGroup> </ItemGroup>

17
tests/Avalonia.Base.UnitTests/VisualTree/MockRenderInterface.cs

@ -72,7 +72,7 @@ namespace Avalonia.Base.UnitTests.VisualTree
throw new NotImplementedException(); throw new NotImplementedException();
} }
public IGlyphRunImpl CreateGlyphRun(GlyphRun glyphRun) public IGlyphRunImpl CreateGlyphRun(IGlyphTypeface glyphTypeface, double fontRenderingEmSize, IReadOnlyList<ushort> glyphIndices, IReadOnlyList<double> glyphAdvances, IReadOnlyList<Vector> glyphOffsets)
{ {
throw new NotImplementedException(); throw new NotImplementedException();
} }
@ -126,21 +126,6 @@ namespace Avalonia.Base.UnitTests.VisualTree
throw new NotImplementedException(); throw new NotImplementedException();
} }
public IGlyphRunBuffer AllocateGlyphRun(IGlyphTypeface glyphTypeface, float fontRenderingEmSize, int length)
{
throw new NotImplementedException();
}
public IHorizontalGlyphRunBuffer AllocateHorizontalGlyphRun(IGlyphTypeface glyphTypeface, float fontRenderingEmSize, int length)
{
throw new NotImplementedException();
}
public IPositionedGlyphRunBuffer AllocatePositionedGlyphRun(IGlyphTypeface glyphTypeface, float fontRenderingEmSize, int length)
{
throw new NotImplementedException();
}
class MockStreamGeometry : IStreamGeometryImpl class MockStreamGeometry : IStreamGeometryImpl
{ {
private MockStreamGeometryContext _impl = new MockStreamGeometryContext(); private MockStreamGeometryContext _impl = new MockStreamGeometryContext();

15
tests/Avalonia.Benchmarks/NullRenderingPlatform.cs

@ -5,6 +5,7 @@ using Avalonia.Media;
using Avalonia.Platform; using Avalonia.Platform;
using Avalonia.UnitTests; using Avalonia.UnitTests;
using Avalonia.Media.Imaging; using Avalonia.Media.Imaging;
using Microsoft.Diagnostics.Runtime;
namespace Avalonia.Benchmarks namespace Avalonia.Benchmarks
{ {
@ -117,19 +118,9 @@ namespace Avalonia.Benchmarks
return new MockStreamGeometryImpl(); return new MockStreamGeometryImpl();
} }
public IGlyphRunBuffer AllocateGlyphRun(IGlyphTypeface glyphTypeface, float fontRenderingEmSize, int length) public IGlyphRunImpl CreateGlyphRun(IGlyphTypeface glyphTypeface, double fontRenderingEmSize, IReadOnlyList<ushort> glyphIndices, IReadOnlyList<double> glyphAdvances, IReadOnlyList<Vector> glyphOffsets)
{ {
throw new NotImplementedException(); return new MockGlyphRun();
}
public IHorizontalGlyphRunBuffer AllocateHorizontalGlyphRun(IGlyphTypeface glyphTypeface, float fontRenderingEmSize, int length)
{
throw new NotImplementedException();
}
public IPositionedGlyphRunBuffer AllocatePositionedGlyphRun(IGlyphTypeface glyphTypeface, float fontRenderingEmSize, int length)
{
throw new NotImplementedException();
} }
public bool SupportsIndividualRoundRects => true; public bool SupportsIndividualRoundRects => true;

2
tests/Avalonia.Skia.UnitTests/Media/TextFormatting/TextFormatterTests.cs

@ -425,7 +425,7 @@ namespace Avalonia.Skia.UnitTests.Media.TextFormatting
var defaultProperties = new GenericTextRunProperties(Typeface.Default); var defaultProperties = new GenericTextRunProperties(Typeface.Default);
var paragraphProperties = new GenericTextParagraphProperties(flowDirection, textAlignment, true, true, var paragraphProperties = new GenericTextParagraphProperties(flowDirection, textAlignment, true, true,
defaultProperties, TextWrapping.NoWrap, 0, 0); defaultProperties, TextWrapping.NoWrap, 0, 0, 0);
var textSource = new SingleBufferTextSource(text, defaultProperties); var textSource = new SingleBufferTextSource(text, defaultProperties);
var formatter = new TextFormatterImpl(); var formatter = new TextFormatterImpl();

6
tests/Avalonia.Skia.UnitTests/Media/TextFormatting/TextLineTests.cs

@ -878,7 +878,8 @@ namespace Avalonia.Skia.UnitTests.Media.TextFormatting
var textLine = var textLine =
formatter.FormatLine(textSource, 0, 200, formatter.FormatLine(textSource, 0, 200,
new GenericTextParagraphProperties(FlowDirection.LeftToRight, TextAlignment.Left, true, true, defaultProperties, TextWrapping.NoWrap, 0, 0)); new GenericTextParagraphProperties(FlowDirection.LeftToRight, TextAlignment.Left,
true, true, defaultProperties, TextWrapping.NoWrap, 0, 0, 0));
var textBounds = textLine.GetTextBounds(0, 3); var textBounds = textLine.GetTextBounds(0, 3);
@ -924,7 +925,8 @@ namespace Avalonia.Skia.UnitTests.Media.TextFormatting
var textLine = var textLine =
formatter.FormatLine(textSource, 0, 200, formatter.FormatLine(textSource, 0, 200,
new GenericTextParagraphProperties(FlowDirection.RightToLeft, TextAlignment.Left, true, true, defaultProperties, TextWrapping.NoWrap, 0, 0)); new GenericTextParagraphProperties(FlowDirection.RightToLeft, TextAlignment.Left,
true, true, defaultProperties, TextWrapping.NoWrap, 0, 0, 0));
var textBounds = textLine.GetTextBounds(0, 4); var textBounds = textLine.GetTextBounds(0, 4);

12
tests/Avalonia.UnitTests/MockGlyphRun.cs

@ -0,0 +1,12 @@
using Avalonia.Platform;
namespace Avalonia.UnitTests
{
public class MockGlyphRun : IGlyphRunImpl
{
public void Dispose()
{
}
}
}

2
tests/Avalonia.UnitTests/MockPlatformRenderInterface.cs

@ -142,7 +142,7 @@ namespace Avalonia.UnitTests
throw new NotImplementedException(); throw new NotImplementedException();
} }
public IGlyphRunImpl CreateGlyphRun(GlyphRun glyphRun) public IGlyphRunImpl CreateGlyphRun(IGlyphTypeface glyphTypeface, double fontRenderingEmSize, IReadOnlyList<ushort> glyphIndices, IReadOnlyList<double> glyphAdvances, IReadOnlyList<Vector> glyphOffsets)
{ {
return Mock.Of<IGlyphRunImpl>(); return Mock.Of<IGlyphRunImpl>();
} }

Loading…
Cancel
Save