mirror of
https://github.com/ZuneDev/MicrosoftIris.git
synced 2026-07-27 13:13:29 -07:00
Start text abstraction
This commit is contained in:
@@ -0,0 +1,16 @@
|
||||
using System;
|
||||
using Microsoft.Iris.Render.Internal;
|
||||
|
||||
namespace Microsoft.Iris.Render.Text;
|
||||
|
||||
// Based on Microsoft.Iris.Render.Bitmaps.BitmapInformation
|
||||
public abstract class FontResource : IDisposable
|
||||
{
|
||||
public abstract HRESULT LoadFile(string filename);
|
||||
|
||||
public abstract HRESULT LoadResource(string moduleName, string resourceId);
|
||||
|
||||
public abstract HRESULT LoadBuffer(IntPtr pvSrc, int cbSize);
|
||||
|
||||
public abstract void Dispose();
|
||||
}
|
||||
@@ -0,0 +1,41 @@
|
||||
using System;
|
||||
|
||||
namespace Microsoft.Iris.Render.Text;
|
||||
|
||||
// Based on Microsoft.Iris.Render.Extensions.ImageLoader
|
||||
//
|
||||
// Unlike ImageLoader's SpBitmapInformation/ImageSharpBitmapInformation split
|
||||
// (both of which live in this assembly), the Windows font/text backend needs
|
||||
// the marshalling machinery already implemented in Microsoft.Iris.OS.NativeApi,
|
||||
// which lives in the downstream UIX project (UIX -> UIX.RenderApi, not the
|
||||
// other way around). So instead of a #if WINDOWS factory switch, UIX registers
|
||||
// its backend here via RegisterWindowsBackend from a module initializer gated
|
||||
// on the WINDOWS compile constant - see Microsoft.Iris.OS.TextBackendRegistration.
|
||||
// On any platform where nothing registers a backend, this falls back to the
|
||||
// SixLabors.Fonts-based implementation.
|
||||
public static class FontResourceLoader
|
||||
{
|
||||
private static Func<FontResource> s_windowsBackend;
|
||||
|
||||
public static void RegisterWindowsBackend(Func<FontResource> factory) => s_windowsBackend = factory;
|
||||
|
||||
private static FontResource Create() => s_windowsBackend?.Invoke() ?? new SixLaborsFontResource();
|
||||
|
||||
public static bool LoadFromFile(string filename)
|
||||
{
|
||||
using var fontResource = Create();
|
||||
return fontResource.LoadFile(filename).IsSuccess();
|
||||
}
|
||||
|
||||
public static bool LoadFromModuleResource(string moduleName, string resourceId)
|
||||
{
|
||||
using var fontResource = Create();
|
||||
return fontResource.LoadResource(moduleName, resourceId).IsSuccess();
|
||||
}
|
||||
|
||||
public static bool LoadFromBuffer(IntPtr pvSrc, int cbSize)
|
||||
{
|
||||
using var fontResource = Create();
|
||||
return fontResource.LoadBuffer(pvSrc, cbSize).IsSuccess();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,47 @@
|
||||
using System;
|
||||
|
||||
namespace Microsoft.Iris.Render.Text;
|
||||
|
||||
// Based on the fields Microsoft.Iris.Drawing.TextRun copies out of the native
|
||||
// NativeApi.RasterizeRunPacket on construction.
|
||||
public sealed class GlyphRunInfo : IDisposable
|
||||
{
|
||||
public string Content { get; init; }
|
||||
public Rectangle LayoutBounds { get; init; }
|
||||
public float RenderBoundsX { get; init; }
|
||||
public float RenderBoundsY { get; init; }
|
||||
public float RenderBoundsWidth { get; init; }
|
||||
public float RenderBoundsHeight { get; init; }
|
||||
public Size NaturalExtent { get; init; }
|
||||
public int NaturalX { get; init; }
|
||||
public int NaturalY { get; init; }
|
||||
public int RasterizeX { get; init; }
|
||||
public int RasterizeY { get; init; }
|
||||
public byte RasterizerConfig { get; init; }
|
||||
public ColorF RunColor { get; init; }
|
||||
public ColorF HighlightColor { get; init; }
|
||||
public int FontFaceUniqueId { get; init; }
|
||||
public int FontHeight { get; init; }
|
||||
public int FontWeight { get; init; }
|
||||
public bool Italic { get; init; }
|
||||
public bool Underline { get; init; }
|
||||
public bool Link { get; init; }
|
||||
public UnderlineStyle UnderlineStyle { get; init; }
|
||||
public Rectangle UnderlineBounds { get; init; }
|
||||
public int Line { get; init; }
|
||||
public int AscenderInset { get; init; }
|
||||
public int BaselineInset { get; init; }
|
||||
|
||||
// Opaque backend-owned state (e.g. the native hGlyphRunInfo handle) needed
|
||||
// by TextDocument.Rasterize and released by Dispose. Never touched outside
|
||||
// the TextDocument implementation that produced this instance.
|
||||
public object BackendHandle { get; init; }
|
||||
private readonly Action<object> _disposeBackendHandle;
|
||||
|
||||
public GlyphRunInfo(Action<object> disposeBackendHandle = null)
|
||||
{
|
||||
_disposeBackendHandle = disposeBackendHandle;
|
||||
}
|
||||
|
||||
public void Dispose() => _disposeBackendHandle?.Invoke(BackendHandle);
|
||||
}
|
||||
@@ -0,0 +1,24 @@
|
||||
using System;
|
||||
|
||||
namespace Microsoft.Iris.Render.Text;
|
||||
|
||||
// Replaces the phTextBitmap/ppvBits/psizeBitmap triple returned by the native
|
||||
// SpRichTextRasterize. Bits is always unmanaged memory (owned by whichever
|
||||
// TextDocument implementation produced it) laid out as top-down A8R8G8B8.
|
||||
public sealed class RasterizedGlyphBitmap : IDisposable
|
||||
{
|
||||
public Size Size { get; init; }
|
||||
public IntPtr Bits { get; init; }
|
||||
|
||||
// Backend-native handle (e.g. the Windows HBITMAP/DIB handle), if any -
|
||||
// only meaningful to the backend that produced this instance.
|
||||
public IntPtr NativeHandle { get; init; }
|
||||
private readonly Action _disposeAction;
|
||||
|
||||
public RasterizedGlyphBitmap(Action disposeAction)
|
||||
{
|
||||
_disposeAction = disposeAction;
|
||||
}
|
||||
|
||||
public void Dispose() => _disposeAction?.Invoke();
|
||||
}
|
||||
@@ -0,0 +1,19 @@
|
||||
using SixLabors.Fonts;
|
||||
|
||||
namespace Microsoft.Iris.Render.Text;
|
||||
|
||||
// Shared font collection fonts get registered into (by SixLaborsFontResource)
|
||||
// and resolved from (by SixLaborsTextDocument), analogous to how the Windows
|
||||
// backend registers fonts process-wide via AddFontMemResourceEx/SpLoadFontResource.
|
||||
internal static class SixLaborsFontRegistry
|
||||
{
|
||||
public static FontCollection Collection { get; } = new();
|
||||
|
||||
public static bool TryGetFamily(string name, out FontFamily family)
|
||||
{
|
||||
if (!string.IsNullOrEmpty(name) && Collection.TryGet(name, out family))
|
||||
return true;
|
||||
|
||||
return SystemFonts.TryGet(name, out family);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,57 @@
|
||||
using System;
|
||||
using System.IO;
|
||||
using Microsoft.Iris.Render.Internal;
|
||||
|
||||
namespace Microsoft.Iris.Render.Text;
|
||||
|
||||
public sealed class SixLaborsFontResource : FontResource
|
||||
{
|
||||
public override HRESULT LoadFile(string filename) => Try(() =>
|
||||
{
|
||||
using var stream = File.OpenRead(filename);
|
||||
SixLaborsFontRegistry.Collection.Add(stream);
|
||||
});
|
||||
|
||||
public override HRESULT LoadResource(string moduleName, string resourceId) =>
|
||||
// No cross-platform equivalent of a Win32 module/resource handle exists.
|
||||
// Mirrors ImageSharpBitmapInformation._LoadResource's same limitation.
|
||||
Try(() => throw new NotImplementedException());
|
||||
|
||||
public override unsafe HRESULT LoadBuffer(IntPtr pvSrc, int cbSize) => Try(() =>
|
||||
{
|
||||
ReadOnlySpan<byte> buffer = new(pvSrc.ToPointer(), cbSize);
|
||||
using var stream = new MemoryStream(buffer.ToArray());
|
||||
|
||||
var isCollection = buffer[..4].SequenceEqual("ttcf"u8);
|
||||
if (isCollection)
|
||||
SixLaborsFontRegistry.Collection.AddCollection(stream);
|
||||
else
|
||||
SixLaborsFontRegistry.Collection.Add(stream);
|
||||
});
|
||||
|
||||
public override void Dispose()
|
||||
{
|
||||
}
|
||||
|
||||
private static HRESULT Try(Action action)
|
||||
{
|
||||
try
|
||||
{
|
||||
action();
|
||||
}
|
||||
catch (FileNotFoundException)
|
||||
{
|
||||
return 0x80070002;
|
||||
}
|
||||
catch (NotImplementedException)
|
||||
{
|
||||
return 0x80004001;
|
||||
}
|
||||
catch
|
||||
{
|
||||
return HRESULT.E_FAIL;
|
||||
}
|
||||
|
||||
return HRESULT.S_OK;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,154 @@
|
||||
using System;
|
||||
using System.Runtime.InteropServices;
|
||||
using Microsoft.Iris.Render.Internal;
|
||||
using SixLabors.Fonts;
|
||||
using SixLabors.ImageSharp;
|
||||
using SixLabors.ImageSharp.Drawing.Processing;
|
||||
using SixLabors.ImageSharp.PixelFormats;
|
||||
using SixLabors.ImageSharp.Processing;
|
||||
using Color = SixLabors.ImageSharp.Color;
|
||||
|
||||
namespace Microsoft.Iris.Render.Text;
|
||||
|
||||
public sealed class SixLaborsTextDocument : TextDocument
|
||||
{
|
||||
private string _content = string.Empty;
|
||||
private TextStyleInfo _lastStyle;
|
||||
|
||||
public override HRESULT SetContent(string content)
|
||||
{
|
||||
_content = content ?? string.Empty;
|
||||
return HRESULT.S_OK;
|
||||
}
|
||||
|
||||
public override HRESULT GetSimpleContent(out string content)
|
||||
{
|
||||
content = _content;
|
||||
return HRESULT.S_OK;
|
||||
}
|
||||
|
||||
public override HRESULT GetNaturalBounds(out Size bounds)
|
||||
{
|
||||
var hresult = MeasureCore(_content, _lastStyle ?? DefaultStyle, Size.Zero, out var fontRect, out _);
|
||||
bounds = hresult.IsSuccess() ? new Size((int)MathF.Ceiling(fontRect.Width), (int)MathF.Ceiling(fontRect.Height)) : Size.Zero;
|
||||
return hresult;
|
||||
}
|
||||
|
||||
public override HRESULT MeasurePossible(string content, TextStyleInfo style, out bool possible)
|
||||
{
|
||||
possible = TryResolveFont(style, out _);
|
||||
return HRESULT.S_OK;
|
||||
}
|
||||
|
||||
public override HRESULT Measure(string content, TextAlignment alignment, TextStyleInfo style, Size constraint, out GlyphRunInfo glyphRun)
|
||||
{
|
||||
_lastStyle = style;
|
||||
var hresult = MeasureCore(content, style, constraint, out var fontRect, out var font);
|
||||
if (!hresult.IsSuccess())
|
||||
{
|
||||
glyphRun = null;
|
||||
return hresult;
|
||||
}
|
||||
|
||||
var layoutBounds = new Rectangle((int)fontRect.X, (int)fontRect.Y, (int)MathF.Ceiling(fontRect.Width), (int)MathF.Ceiling(fontRect.Height));
|
||||
glyphRun = new GlyphRunInfo()
|
||||
{
|
||||
Content = content,
|
||||
LayoutBounds = layoutBounds,
|
||||
RenderBoundsX = fontRect.X,
|
||||
RenderBoundsY = fontRect.Y,
|
||||
RenderBoundsWidth = fontRect.Width,
|
||||
RenderBoundsHeight = fontRect.Height,
|
||||
NaturalExtent = new Size((int)MathF.Ceiling(fontRect.Width), (int)MathF.Ceiling(fontRect.Height)),
|
||||
RunColor = style.Color,
|
||||
HighlightColor = default,
|
||||
FontFaceUniqueId = style.FontFace?.GetHashCode() ?? 0,
|
||||
FontHeight = (int)style.FontSize,
|
||||
FontWeight = style.Bold ? 700 : 400,
|
||||
Italic = style.Italic,
|
||||
Underline = style.Underline,
|
||||
Link = false,
|
||||
UnderlineStyle = style.Underline ? UnderlineStyle.Solid : UnderlineStyle.None,
|
||||
UnderlineBounds = Rectangle.Zero,
|
||||
Line = 1,
|
||||
AscenderInset = 0,
|
||||
BaselineInset = 0,
|
||||
BackendHandle = font,
|
||||
};
|
||||
return HRESULT.S_OK;
|
||||
}
|
||||
|
||||
public override unsafe HRESULT Rasterize(GlyphRunInfo glyphRun, ColorF textColor, bool outline, bool shadow, out RasterizedGlyphBitmap bitmap)
|
||||
{
|
||||
if (glyphRun.BackendHandle is not Font font)
|
||||
{
|
||||
bitmap = null;
|
||||
return HRESULT.E_FAIL;
|
||||
}
|
||||
|
||||
var width = Math.Max(1, (int)MathF.Ceiling(glyphRun.RenderBoundsWidth));
|
||||
var height = Math.Max(1, (int)MathF.Ceiling(glyphRun.RenderBoundsHeight));
|
||||
var color = Color.FromPixel(new Rgba32(
|
||||
(byte)Math.Clamp(textColor.R * 255f, 0, 255),
|
||||
(byte)Math.Clamp(textColor.G * 255f, 0, 255),
|
||||
(byte)Math.Clamp(textColor.B * 255f, 0, 255),
|
||||
(byte)Math.Clamp(textColor.A * 255f, 0, 255)));
|
||||
var brush = new SolidBrush(color);
|
||||
var richTextOptions = new RichTextOptions(font) { Origin = PointF.Empty };
|
||||
|
||||
using var image = new Image<Rgba32>(width, height);
|
||||
image.Mutate(ctx => ctx.Paint(canvas => canvas.DrawText(richTextOptions, glyphRun.Content ?? string.Empty, brush, null)));
|
||||
|
||||
var byteCount = width * height * 4;
|
||||
var pBits = Marshal.AllocHGlobal(byteCount);
|
||||
image.CopyPixelDataTo(new Span<byte>(pBits.ToPointer(), byteCount));
|
||||
|
||||
var pBitsCaptured = pBits;
|
||||
bitmap = new RasterizedGlyphBitmap(() => Marshal.FreeHGlobal(pBitsCaptured))
|
||||
{
|
||||
Size = new Size(width, height),
|
||||
Bits = pBits,
|
||||
NativeHandle = IntPtr.Zero,
|
||||
};
|
||||
return HRESULT.S_OK;
|
||||
}
|
||||
|
||||
public override void Dispose()
|
||||
{
|
||||
}
|
||||
|
||||
private static TextStyleInfo DefaultStyle => new() { FontFace = "Arial", FontSize = 12f };
|
||||
|
||||
private static bool TryResolveFont(TextStyleInfo style, out Font font)
|
||||
{
|
||||
font = null;
|
||||
if (!SixLaborsFontRegistry.TryGetFamily(style?.FontFace, out var family))
|
||||
return false;
|
||||
|
||||
var fontStyle = (style is { Bold: true, Italic: true }) ? FontStyle.BoldItalic
|
||||
: style?.Bold == true ? FontStyle.Bold
|
||||
: style?.Italic == true ? FontStyle.Italic
|
||||
: FontStyle.Regular;
|
||||
|
||||
var size = style?.FontSize > 0 ? style.FontSize : 12f;
|
||||
font = family.CreateFont(size, fontStyle);
|
||||
return true;
|
||||
}
|
||||
|
||||
private static HRESULT MeasureCore(string content, TextStyleInfo style, Size constraint, out FontRectangle fontRect, out Font font)
|
||||
{
|
||||
content ??= string.Empty;
|
||||
if (!TryResolveFont(style, out font))
|
||||
{
|
||||
fontRect = default;
|
||||
return 0x80070490; // ERROR_NOT_FOUND
|
||||
}
|
||||
|
||||
var options = new TextOptions(font);
|
||||
if (constraint.Width > 0)
|
||||
options.WrappingLength = constraint.Width;
|
||||
|
||||
fontRect = TextMeasurer.MeasureBounds(content, options);
|
||||
return HRESULT.S_OK;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,8 @@
|
||||
namespace Microsoft.Iris.Render.Text;
|
||||
|
||||
public enum TextAlignment
|
||||
{
|
||||
Near,
|
||||
Center,
|
||||
Far,
|
||||
}
|
||||
@@ -0,0 +1,30 @@
|
||||
using System;
|
||||
using Microsoft.Iris.Render.Internal;
|
||||
|
||||
namespace Microsoft.Iris.Render.Text;
|
||||
|
||||
// Based on Microsoft.Iris.Render.Bitmaps.BitmapInformation
|
||||
//
|
||||
// Covers the "given text (+ a style), produce metrics/pixels" surface that is
|
||||
// safe to swap per-platform: content storage/query, measurement, and glyph
|
||||
// rasterization. Deliberately does NOT cover interactive text editing (IME,
|
||||
// keyboard/mouse forwarding, undo, clipboard, scrollbars, timers) or
|
||||
// multi-range rich-text formatting (TextMeasureParams' formatted ranges) -
|
||||
// those remain native/Windows-only in Microsoft.Iris.Drawing.RichText. See
|
||||
// the "Cross-platform text/font abstraction" plan and logs/ for the rationale.
|
||||
public abstract class TextDocument : IDisposable
|
||||
{
|
||||
public abstract HRESULT SetContent(string content);
|
||||
|
||||
public abstract HRESULT GetSimpleContent(out string content);
|
||||
|
||||
public abstract HRESULT GetNaturalBounds(out Size bounds);
|
||||
|
||||
public abstract HRESULT MeasurePossible(string content, TextStyleInfo style, out bool possible);
|
||||
|
||||
public abstract HRESULT Measure(string content, TextAlignment alignment, TextStyleInfo style, Size constraint, out GlyphRunInfo glyphRun);
|
||||
|
||||
public abstract HRESULT Rasterize(GlyphRunInfo glyphRun, ColorF textColor, bool outline, bool shadow, out RasterizedGlyphBitmap bitmap);
|
||||
|
||||
public abstract void Dispose();
|
||||
}
|
||||
@@ -0,0 +1,17 @@
|
||||
using System;
|
||||
|
||||
namespace Microsoft.Iris.Render.Text;
|
||||
|
||||
// See FontResourceLoader for why this is a registration seam rather than a
|
||||
// #if WINDOWS switch: the Windows backend needs Microsoft.Iris.OS.NativeApi's
|
||||
// marshalling, which lives in the downstream UIX project.
|
||||
public static class TextDocumentFactory
|
||||
{
|
||||
private static Func<TextDocument> s_windowsStandaloneBackend;
|
||||
|
||||
public static void RegisterWindowsBackend(Func<TextDocument> factory) => s_windowsStandaloneBackend = factory;
|
||||
|
||||
// A standalone TextDocument owns its own backend state independently of
|
||||
// any other object - this is what Microsoft.Iris.Drawing.SimpleText uses.
|
||||
public static TextDocument CreateStandalone() => s_windowsStandaloneBackend?.Invoke() ?? new SixLaborsTextDocument();
|
||||
}
|
||||
@@ -0,0 +1,16 @@
|
||||
namespace Microsoft.Iris.Render.Text;
|
||||
|
||||
public sealed class TextStyleInfo
|
||||
{
|
||||
public string FontFace { get; set; }
|
||||
public float FontSize { get; set; }
|
||||
public float AltFontSize { get; set; }
|
||||
public bool Bold { get; set; }
|
||||
public bool Italic { get; set; }
|
||||
public bool Underline { get; set; }
|
||||
public ColorF Color { get; set; }
|
||||
public bool HasColor { get; set; }
|
||||
public float LineSpacing { get; set; }
|
||||
public float CharacterSpacing { get; set; }
|
||||
public bool EnableKerning { get; set; }
|
||||
}
|
||||
@@ -0,0 +1,13 @@
|
||||
namespace Microsoft.Iris.Render.Text;
|
||||
|
||||
// Based on Microsoft.Iris.OS.NativeApi.UnderlineStyle
|
||||
public enum UnderlineStyle
|
||||
{
|
||||
None,
|
||||
Solid,
|
||||
Thick,
|
||||
Dotted,
|
||||
Dash,
|
||||
DashDot,
|
||||
DashDotDot,
|
||||
}
|
||||
@@ -15,5 +15,9 @@
|
||||
<!-- TODO: Only import on non-Windows targets -->
|
||||
<!-- ImageSharp provides support for decoding images -->
|
||||
<PackageReference Include="SixLabors.ImageSharp" Version="4.0.0" />
|
||||
<!-- ImageSharp.Drawing provides glyph rasterization on top of ImageSharp -->
|
||||
<PackageReference Include="SixLabors.ImageSharp.Drawing" Version="3.0.0" />
|
||||
<!-- Fonts provides font loading/measurement independent of ImageSharp -->
|
||||
<PackageReference Include="SixLabors.Fonts" Version="3.0.0" />
|
||||
</ItemGroup>
|
||||
</Project>
|
||||
@@ -6,6 +6,7 @@
|
||||
|
||||
using Microsoft.Iris.OS;
|
||||
using Microsoft.Iris.Render;
|
||||
using Microsoft.Iris.Render.Text;
|
||||
using Microsoft.Iris.RenderAPI;
|
||||
using Microsoft.Iris.RenderAPI.Drawing;
|
||||
using Microsoft.Iris.Session;
|
||||
@@ -22,6 +23,15 @@ namespace Microsoft.Iris.Drawing
|
||||
public const float MaxWidthConstraint = 4095f;
|
||||
public const float MaxHeightConstraint = 8191f;
|
||||
private Win32Api.HANDLE _rtoHandle;
|
||||
// Bound to _rtoHandle: routes SetContent/GetSimpleContent/GetNaturalBounds
|
||||
// through the cross-platform Microsoft.Iris.Render.Text.TextDocument
|
||||
// abstraction. Measure/Rasterize stay on NativeApi.SpRichText* directly
|
||||
// below - TextMeasureParams' multi-range formatting doesn't fit this
|
||||
// simplified single-style abstraction without losing fidelity. See
|
||||
// logs/ for the rationale. Only meaningful on Windows: RichText's own
|
||||
// constructor is native-only already (SpRichTextBuildObject), so this
|
||||
// instance is always the Sp-backed implementation in practice.
|
||||
private readonly TextDocument _textDocument;
|
||||
private NativeApi.ReportRunCallback _rrcb;
|
||||
private string _currentlyMeasuringText;
|
||||
private bool _oversampled;
|
||||
@@ -48,6 +58,7 @@ namespace Microsoft.Iris.Drawing
|
||||
_timerTickHandler = new EventHandler(OnTimerTick);
|
||||
}
|
||||
RendererApi.IFC(NativeApi.SpRichTextBuildObject(richTextMode, sizeMaximumSurface, callbacks, out _rtoHandle));
|
||||
_textDocument = new SpTextDocument(_rtoHandle);
|
||||
_oversampled = false;
|
||||
_lock = new object();
|
||||
_rrcb = new NativeApi.ReportRunCallback(OnReportRun);
|
||||
@@ -62,6 +73,7 @@ namespace Microsoft.Iris.Drawing
|
||||
GC.SuppressFinalize(this);
|
||||
lock (_lock)
|
||||
{
|
||||
_textDocument.Dispose();
|
||||
NativeApi.SpRichTextDestroyObject(_rtoHandle);
|
||||
_rtoHandle.h = IntPtr.Zero;
|
||||
}
|
||||
@@ -79,7 +91,7 @@ namespace Microsoft.Iris.Drawing
|
||||
set
|
||||
{
|
||||
lock (_lock)
|
||||
RendererApi.IFC(NativeApi.SpRichTextSetContent(_rtoHandle, value));
|
||||
RendererApi.IFC(new HRESULT(_textDocument.SetContent(value).Int));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -87,19 +99,11 @@ namespace Microsoft.Iris.Drawing
|
||||
{
|
||||
get
|
||||
{
|
||||
string str = null;
|
||||
int textLength = 0;
|
||||
lock (_lock)
|
||||
{
|
||||
RendererApi.IFC(NativeApi.SpRichTextGetSimpleContentLength(_rtoHandle, out textLength));
|
||||
if (textLength != 0)
|
||||
{
|
||||
StringBuilder textBuffer = new StringBuilder(textLength);
|
||||
RendererApi.IFC(NativeApi.SpRichTextGetSimpleContent(_rtoHandle, textBuffer, textBuffer.Capacity));
|
||||
str = textBuffer.ToString();
|
||||
}
|
||||
RendererApi.IFC(new HRESULT(_textDocument.GetSimpleContent(out var content).Int));
|
||||
return content;
|
||||
}
|
||||
return str;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -153,11 +157,11 @@ namespace Microsoft.Iris.Drawing
|
||||
|
||||
public Size GetNaturalBounds()
|
||||
{
|
||||
int cWidth;
|
||||
int cHeight;
|
||||
lock (_lock)
|
||||
RendererApi.IFC(NativeApi.SpRichTextGetNaturalBounds(_rtoHandle, out cWidth, out cHeight));
|
||||
return new Size(cWidth, cHeight);
|
||||
{
|
||||
RendererApi.IFC(new HRESULT(_textDocument.GetNaturalBounds(out var bounds).Int));
|
||||
return bounds;
|
||||
}
|
||||
}
|
||||
|
||||
public void SetSelectionRange(int selectionStart, int selectionEnd)
|
||||
|
||||
@@ -1,53 +1,43 @@
|
||||
// Decompiled with JetBrains decompiler
|
||||
// Based on decompilation with JetBrains decompiler
|
||||
// Type: Microsoft.Iris.Drawing.SimpleText
|
||||
// Assembly: UIX, Version=4.8.0.0, Culture=neutral, PublicKeyToken=ddd0da4d3e678217
|
||||
// MVID: A56C6C9D-B7F6-46A9-8BDE-B3D9B8D60B11
|
||||
// Assembly location: C:\Program Files\Zune\UIX.dll
|
||||
|
||||
using Microsoft.Iris.OS;
|
||||
using Microsoft.Iris.Render;
|
||||
using Microsoft.Iris.RenderAPI;
|
||||
using Microsoft.Iris.Session;
|
||||
using Microsoft.Iris.Render.Text;
|
||||
using Microsoft.Iris.ViewItems;
|
||||
using System;
|
||||
|
||||
namespace Microsoft.Iris.Drawing
|
||||
{
|
||||
// Uses the cross-platform TextDocument abstraction (Microsoft.Iris.Render.Text)
|
||||
// instead of calling NativeApi.SpSimpleText* directly, so this class works on
|
||||
// any platform: TextDocumentFactory.CreateStandalone() resolves to the native
|
||||
// SpTextDocument on Windows and to a SixLabors.Fonts-backed TextDocument
|
||||
// elsewhere.
|
||||
internal class SimpleText : IDisposable
|
||||
{
|
||||
private Win32Api.HANDLE _stoHandle;
|
||||
private readonly TextDocument _document;
|
||||
|
||||
public SimpleText()
|
||||
{
|
||||
Size sizeMaximumSurface = Size.Zero;
|
||||
if (UISession.Default != null)
|
||||
sizeMaximumSurface = UIImage.MaximumSurfaceSize(UISession.Default);
|
||||
RendererApi.IFC(NativeApi.SpSimpleTextBuildObject(sizeMaximumSurface, out _stoHandle));
|
||||
_document = TextDocumentFactory.CreateStandalone();
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
GC.SuppressFinalize(this);
|
||||
NativeApi.SpSimpleTextDestroyObject(_stoHandle);
|
||||
_stoHandle = Win32Api.HANDLE.NULL;
|
||||
_document.Dispose();
|
||||
}
|
||||
|
||||
public unsafe bool CanMeasure(string content, TextStyle textStyle)
|
||||
public bool CanMeasure(string content, TextStyle textStyle)
|
||||
{
|
||||
bool fPossible;
|
||||
|
||||
fixed (char* chPtr = textStyle.TruncatedFontFace)
|
||||
{
|
||||
var style = new TextStyle.MarshalledData(textStyle)
|
||||
{
|
||||
_fontFace = chPtr
|
||||
};
|
||||
RendererApi.IFC(NativeApi.SpSimpleTextMeasurePossible(_stoHandle, content, &style, out fPossible));
|
||||
}
|
||||
return fPossible;
|
||||
_document.MeasurePossible(content, ToStyleInfo(textStyle), out var possible);
|
||||
return possible;
|
||||
}
|
||||
|
||||
public unsafe TextFlow Measure(
|
||||
public TextFlow Measure(
|
||||
string content,
|
||||
LineAlignment alignment,
|
||||
TextStyle textStyle,
|
||||
@@ -56,33 +46,37 @@ namespace Microsoft.Iris.Drawing
|
||||
TextFlow textFlow = new TextFlow();
|
||||
if (content == null)
|
||||
content = string.Empty;
|
||||
short wAlignment = 0;
|
||||
switch (alignment)
|
||||
|
||||
var textAlignment = alignment switch
|
||||
{
|
||||
case LineAlignment.Near:
|
||||
wAlignment = 1;
|
||||
break;
|
||||
case LineAlignment.Center:
|
||||
wAlignment = 3;
|
||||
break;
|
||||
case LineAlignment.Far:
|
||||
wAlignment = 2;
|
||||
break;
|
||||
}
|
||||
IntPtr hGlyphRunInfo;
|
||||
NativeApi.RasterizeRunPacket rasterizeRunPacket;
|
||||
fixed (char* chPtr = textStyle.TruncatedFontFace)
|
||||
LineAlignment.Near => TextAlignment.Near,
|
||||
LineAlignment.Center => TextAlignment.Center,
|
||||
LineAlignment.Far => TextAlignment.Far,
|
||||
_ => TextAlignment.Near,
|
||||
};
|
||||
|
||||
var hresult = _document.Measure(content, textAlignment, ToStyleInfo(textStyle), constraint, out var glyphRunInfo);
|
||||
if (hresult.IsSuccess() && glyphRunInfo != null)
|
||||
{
|
||||
var style = new TextStyle.MarshalledData(textStyle)
|
||||
{
|
||||
_fontFace = chPtr
|
||||
};
|
||||
RendererApi.IFC(NativeApi.SpSimpleTextMeasure(_stoHandle, content, wAlignment,
|
||||
&style, constraint, out hGlyphRunInfo, &rasterizeRunPacket));
|
||||
var run = TextRun.FromGlyphRunInfo(glyphRunInfo, _document);
|
||||
textFlow.Add(run);
|
||||
}
|
||||
TextRun run = TextRun.FromRunPacket(hGlyphRunInfo, &rasterizeRunPacket, content);
|
||||
textFlow.Add(run);
|
||||
return textFlow;
|
||||
}
|
||||
|
||||
private static TextStyleInfo ToStyleInfo(TextStyle textStyle) => new()
|
||||
{
|
||||
FontFace = textStyle.FontFace,
|
||||
FontSize = textStyle.FontSize,
|
||||
AltFontSize = textStyle.AltFontSize,
|
||||
Bold = textStyle.Bold,
|
||||
Italic = textStyle.Italic,
|
||||
Underline = textStyle.Underline,
|
||||
Color = textStyle.Color.RenderConvert(),
|
||||
HasColor = textStyle.HasColor,
|
||||
LineSpacing = textStyle.LineSpacing,
|
||||
CharacterSpacing = textStyle.CharacterSpacing,
|
||||
EnableKerning = textStyle.EnableKerning,
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,40 @@
|
||||
using System;
|
||||
using System.Runtime.InteropServices;
|
||||
using Microsoft.Iris.OS;
|
||||
using Microsoft.Iris.Render.Internal;
|
||||
using Microsoft.Iris.Render.Text;
|
||||
|
||||
namespace Microsoft.Iris.Drawing;
|
||||
|
||||
// Windows backend for Microsoft.Iris.Render.Text.FontResource, living here
|
||||
// (rather than alongside the abstract class in UIX.RenderApi) so it can reuse
|
||||
// Microsoft.Iris.OS.NativeApi's existing P/Invoke declarations instead of
|
||||
// duplicating them in an assembly NativeApi isn't visible from. Registered
|
||||
// with FontResourceLoader by TextBackendRegistration.
|
||||
internal sealed class SpFontResource : FontResource
|
||||
{
|
||||
[DllImport("gdi32.dll", CharSet = CharSet.Unicode, SetLastError = true)]
|
||||
private static extern int AddFontResourceExW(string lpszFilename, uint fl, IntPtr pdv);
|
||||
|
||||
private const uint FR_PRIVATE = 0x10;
|
||||
|
||||
public override HRESULT LoadFile(string filename)
|
||||
{
|
||||
var addedCount = AddFontResourceExW(filename, FR_PRIVATE, IntPtr.Zero);
|
||||
return addedCount > 0 ? HRESULT.S_OK : HRESULT.E_FAIL;
|
||||
}
|
||||
|
||||
public override HRESULT LoadResource(string moduleName, string resourceId) =>
|
||||
NativeApi.SpLoadFontResource(moduleName, resourceId) ? HRESULT.S_OK : HRESULT.E_FAIL;
|
||||
|
||||
public override HRESULT LoadBuffer(IntPtr pvSrc, int cbSize)
|
||||
{
|
||||
uint cFonts = 0;
|
||||
var hFont = Win32Api.AddFontMemResourceEx(pvSrc, (uint)cbSize, IntPtr.Zero, ref cFonts);
|
||||
return hFont != IntPtr.Zero ? HRESULT.S_OK : HRESULT.E_FAIL;
|
||||
}
|
||||
|
||||
public override void Dispose()
|
||||
{
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,247 @@
|
||||
using System;
|
||||
using System.Text;
|
||||
using Microsoft.Iris.OS;
|
||||
using Microsoft.Iris.Render;
|
||||
using Microsoft.Iris.RenderAPI;
|
||||
using Microsoft.Iris.Render.Text;
|
||||
using Microsoft.Iris.Session;
|
||||
using Microsoft.Iris.ViewItems;
|
||||
using Size = Microsoft.Iris.Render.Size;
|
||||
using Color = Microsoft.Iris.Drawing.Color;
|
||||
using HRESULT = Microsoft.Iris.Render.Internal.HRESULT;
|
||||
|
||||
namespace Microsoft.Iris.Drawing;
|
||||
|
||||
// Windows backend for Microsoft.Iris.Render.Text.TextDocument - see the
|
||||
// remark on SpFontResource for why this lives in UIX rather than
|
||||
// UIX.RenderApi. Registered with TextDocumentFactory by TextBackendRegistration.
|
||||
//
|
||||
// Wraps one of the two native text object families NativeApi exposes:
|
||||
// - "standalone" wraps its own SpSimpleTextBuildObject handle (STO), exactly
|
||||
// what Microsoft.Iris.Drawing.SimpleText used before this refactor. Only
|
||||
// Measure/MeasurePossible/Rasterize are meaningful for an STO.
|
||||
// - "bound" is constructed with an existing RichText RTO handle so
|
||||
// SetContent/GetSimpleContent/GetNaturalBounds run against RichText's own
|
||||
// session. Measure/MeasurePossible are NOT implemented in bound mode:
|
||||
// RichText.Measure needs TextMeasureParams' multi-range formatting, which
|
||||
// this simplified single-style abstraction can't safely replicate without
|
||||
// regressing existing Windows behavior - RichText keeps calling
|
||||
// NativeApi.SpRichTextMeasure directly for that. See logs/ for the
|
||||
// rationale.
|
||||
internal sealed class SpTextDocument : TextDocument
|
||||
{
|
||||
private Win32Api.HANDLE _handle;
|
||||
private readonly bool _bound;
|
||||
|
||||
public SpTextDocument()
|
||||
{
|
||||
var sizeMaximumSurface = Size.Zero;
|
||||
if (UISession.Default != null)
|
||||
sizeMaximumSurface = UIImage.MaximumSurfaceSize(UISession.Default);
|
||||
RendererApi.IFC(NativeApi.SpSimpleTextBuildObject(sizeMaximumSurface, out _handle));
|
||||
_bound = false;
|
||||
}
|
||||
|
||||
public SpTextDocument(Win32Api.HANDLE boundRtoHandle)
|
||||
{
|
||||
_handle = boundRtoHandle;
|
||||
_bound = true;
|
||||
}
|
||||
|
||||
public override HRESULT SetContent(string content)
|
||||
{
|
||||
RequireBound();
|
||||
return NativeApi.SpRichTextSetContent(_handle, content).Int;
|
||||
}
|
||||
|
||||
public override HRESULT GetSimpleContent(out string content)
|
||||
{
|
||||
RequireBound();
|
||||
|
||||
var hresult = NativeApi.SpRichTextGetSimpleContentLength(_handle, out var textLength);
|
||||
if (!hresult.IsSuccess() || textLength == 0)
|
||||
{
|
||||
content = null;
|
||||
return hresult.Int;
|
||||
}
|
||||
|
||||
var textBuffer = new StringBuilder(textLength);
|
||||
hresult = NativeApi.SpRichTextGetSimpleContent(_handle, textBuffer, textBuffer.Capacity);
|
||||
content = hresult.IsSuccess() ? textBuffer.ToString() : null;
|
||||
return hresult.Int;
|
||||
}
|
||||
|
||||
public override HRESULT GetNaturalBounds(out Size bounds)
|
||||
{
|
||||
RequireBound();
|
||||
|
||||
var hresult = NativeApi.SpRichTextGetNaturalBounds(_handle, out var cWidth, out var cHeight);
|
||||
bounds = hresult.IsSuccess() ? new Size(cWidth, cHeight) : Size.Zero;
|
||||
return hresult.Int;
|
||||
}
|
||||
|
||||
public override unsafe HRESULT MeasurePossible(string content, TextStyleInfo style, out bool possible)
|
||||
{
|
||||
RequireStandalone();
|
||||
|
||||
fixed (char* facePtr = TruncateFontFace(style?.FontFace))
|
||||
{
|
||||
var marshalled = ToMarshalledData(style, facePtr);
|
||||
return NativeApi.SpSimpleTextMeasurePossible(_handle, content, &marshalled, out possible).Int;
|
||||
}
|
||||
}
|
||||
|
||||
public override unsafe HRESULT Measure(string content, TextAlignment alignment, TextStyleInfo style, Size constraint, out GlyphRunInfo glyphRun)
|
||||
{
|
||||
RequireStandalone();
|
||||
|
||||
content ??= string.Empty;
|
||||
short wAlignment = alignment switch
|
||||
{
|
||||
TextAlignment.Near => 1,
|
||||
TextAlignment.Center => 3,
|
||||
TextAlignment.Far => 2,
|
||||
_ => 0,
|
||||
};
|
||||
|
||||
IntPtr hGlyphRunInfo;
|
||||
NativeApi.RasterizeRunPacket rasterizeRunPacket;
|
||||
Microsoft.Iris.RenderAPI.HRESULT hresult;
|
||||
fixed (char* facePtr = TruncateFontFace(style?.FontFace))
|
||||
{
|
||||
var marshalled = ToMarshalledData(style, facePtr);
|
||||
hresult = NativeApi.SpSimpleTextMeasure(_handle, content, wAlignment, &marshalled, constraint, out hGlyphRunInfo, &rasterizeRunPacket);
|
||||
}
|
||||
|
||||
if (!hresult.IsSuccess())
|
||||
{
|
||||
glyphRun = null;
|
||||
return hresult.Int;
|
||||
}
|
||||
|
||||
glyphRun = FromRasterizeRunPacket(hGlyphRunInfo, ref rasterizeRunPacket, content);
|
||||
return HRESULT.S_OK;
|
||||
}
|
||||
|
||||
public override unsafe HRESULT Rasterize(GlyphRunInfo glyphRun, ColorF textColor, bool outline, bool shadow, out RasterizedGlyphBitmap bitmap)
|
||||
{
|
||||
if (glyphRun?.BackendHandle is not IntPtr hGlyphRunInfo)
|
||||
{
|
||||
bitmap = null;
|
||||
return HRESULT.E_FAIL;
|
||||
}
|
||||
|
||||
var textColorArgb = new Color(textColor.A, textColor.R, textColor.G, textColor.B);
|
||||
var hresult = NativeApi.SpRichTextRasterize(hGlyphRunInfo, outline ? 1 : 0, textColorArgb, shadow ? 1 : 0,
|
||||
out var phTextBitmap, out var ppvBits, out var psizeBitmap);
|
||||
|
||||
if (!hresult.IsSuccess())
|
||||
{
|
||||
bitmap = null;
|
||||
return hresult.Int;
|
||||
}
|
||||
|
||||
bitmap = new RasterizedGlyphBitmap(() => NativeApi.SpFreeDib(phTextBitmap))
|
||||
{
|
||||
Size = psizeBitmap,
|
||||
Bits = ppvBits,
|
||||
NativeHandle = phTextBitmap,
|
||||
};
|
||||
return HRESULT.S_OK;
|
||||
}
|
||||
|
||||
public override void Dispose()
|
||||
{
|
||||
if (_bound)
|
||||
return;
|
||||
|
||||
if (_handle == Win32Api.HANDLE.NULL)
|
||||
return;
|
||||
|
||||
NativeApi.SpSimpleTextDestroyObject(_handle);
|
||||
_handle = Win32Api.HANDLE.NULL;
|
||||
}
|
||||
|
||||
private void RequireBound()
|
||||
{
|
||||
if (!_bound)
|
||||
throw new NotSupportedException("Standalone (SimpleText) text documents have no persistent content buffer.");
|
||||
}
|
||||
|
||||
private void RequireStandalone()
|
||||
{
|
||||
if (_bound)
|
||||
throw new NotSupportedException("Bound (RichText) text documents don't support single-style Measure - RichText.Measure uses NativeApi.SpRichTextMeasure directly to keep multi-range formatting.");
|
||||
}
|
||||
|
||||
private static GlyphRunInfo FromRasterizeRunPacket(IntPtr hGlyphRunInfo, ref NativeApi.RasterizeRunPacket run, string content)
|
||||
{
|
||||
return new GlyphRunInfo(handle => NativeApi.SpRichTextDestroyGlyphRunInfo((IntPtr)handle))
|
||||
{
|
||||
Content = content,
|
||||
LayoutBounds = run.rcLayoutBounds,
|
||||
RenderBoundsX = run.rcfRenderBounds.X,
|
||||
RenderBoundsY = run.rcfRenderBounds.Y,
|
||||
RenderBoundsWidth = run.rcfRenderBounds.Width,
|
||||
RenderBoundsHeight = run.rcfRenderBounds.Height,
|
||||
NaturalExtent = run.sizeNatural,
|
||||
NaturalX = run.naturalX,
|
||||
NaturalY = run.naturalY,
|
||||
RasterizeX = run.rasterizeX,
|
||||
RasterizeY = run.rasterizeY,
|
||||
RasterizerConfig = run.AAConfig,
|
||||
RunColor = run.clrText.RenderConvert(),
|
||||
HighlightColor = run.clrBackground.RenderConvert(),
|
||||
FontFaceUniqueId = run.fontFaceUniqueId,
|
||||
FontHeight = run.lf.lfHeight,
|
||||
FontWeight = run.lf.lfWeight,
|
||||
Italic = run.lf.lfItalic != 0,
|
||||
Underline = run.lf.lfUnderline != 0,
|
||||
Link = (run.dwEffects & 32) != 0,
|
||||
UnderlineStyle = (Microsoft.Iris.Render.Text.UnderlineStyle)run.usUnderlineStyle,
|
||||
UnderlineBounds = run.rcUnderlineBounds,
|
||||
Line = run.nLineNumber,
|
||||
AscenderInset = run.ascenderInset,
|
||||
BaselineInset = run.baselineInset,
|
||||
BackendHandle = hGlyphRunInfo,
|
||||
};
|
||||
}
|
||||
|
||||
private static string TruncateFontFace(string fontFace)
|
||||
{
|
||||
fontFace ??= string.Empty;
|
||||
return fontFace.Length < 32 ? fontFace : fontFace.Substring(0, 31);
|
||||
}
|
||||
|
||||
private static unsafe TextStyle.MarshalledData ToMarshalledData(TextStyleInfo style, char* fontFacePtr)
|
||||
{
|
||||
style ??= new TextStyleInfo();
|
||||
|
||||
var flags = TextStyle.SetFlags.FontFace | TextStyle.SetFlags.FontHeight | TextStyle.SetFlags.Bold |
|
||||
TextStyle.SetFlags.Italic | TextStyle.SetFlags.Underline | TextStyle.SetFlags.LineSpacing |
|
||||
TextStyle.SetFlags.EnableKerning | TextStyle.SetFlags.CharacterSpacing;
|
||||
if (style.HasColor)
|
||||
flags |= TextStyle.SetFlags.TextColor;
|
||||
if (style.AltFontSize != 0)
|
||||
flags |= TextStyle.SetFlags.AltFontHeight;
|
||||
if (style.Bold)
|
||||
flags |= TextStyle.SetFlags.BoldValue;
|
||||
if (style.Italic)
|
||||
flags |= TextStyle.SetFlags.ItalicValue;
|
||||
if (style.Underline)
|
||||
flags |= TextStyle.SetFlags.UnderlineValue;
|
||||
if (style.EnableKerning)
|
||||
flags |= TextStyle.SetFlags.EnableKerningValue;
|
||||
|
||||
return new TextStyle.MarshalledData
|
||||
{
|
||||
_flags = (int)flags,
|
||||
_fontFace = fontFacePtr,
|
||||
_fontHeightPts = style.FontSize,
|
||||
_altFontHeightPts = style.AltFontSize,
|
||||
_lineSpacing = style.LineSpacing,
|
||||
_characterSpacing = style.CharacterSpacing,
|
||||
_textColor = new Color(style.Color.A, style.Color.R, style.Color.G, style.Color.B),
|
||||
};
|
||||
}
|
||||
}
|
||||
@@ -7,6 +7,7 @@
|
||||
using Microsoft.Iris.Library;
|
||||
using Microsoft.Iris.OS;
|
||||
using Microsoft.Iris.Render;
|
||||
using Microsoft.Iris.Render.Text;
|
||||
using Microsoft.Iris.RenderAPI.Drawing;
|
||||
using System;
|
||||
|
||||
@@ -17,6 +18,9 @@ namespace Microsoft.Iris.Drawing
|
||||
private const int CFE_LINK = 32;
|
||||
private IntPtr _hGlyphRunInfo;
|
||||
private IntPtr _hRasterizeRunPacket;
|
||||
private readonly GlyphRunInfo _glyphRunInfo;
|
||||
private readonly TextDocument _owner;
|
||||
private NativeApi.UnderlineStyle _underlineStyleManaged;
|
||||
private Rectangle _layoutBounds;
|
||||
private RectangleF _renderBounds;
|
||||
private Point _offsetPoint;
|
||||
@@ -63,7 +67,7 @@ namespace Microsoft.Iris.Drawing
|
||||
_lfWeight = runPacketPtr->lf.lfWeight;
|
||||
SetBit(Bits.Italic, runPacketPtr->lf.lfItalic != 0);
|
||||
SetBit(Bits.Underline, runPacketPtr->lf.lfUnderline != 0);
|
||||
SetBit(Bits.Link, (runPacketPtr->dwEffects & 32) != 0);
|
||||
SetBit(Bits.Link, (runPacketPtr->dwEffects & CFE_LINK) != 0);
|
||||
_underlineBounds = runPacketPtr->rcUnderlineBounds;
|
||||
_lineNumber = runPacketPtr->nLineNumber;
|
||||
_naturalRunExtent = runPacketPtr->sizeNatural;
|
||||
@@ -72,9 +76,46 @@ namespace Microsoft.Iris.Drawing
|
||||
_content = content;
|
||||
}
|
||||
|
||||
// Used for runs measured through the cross-platform TextDocument
|
||||
// abstraction (currently only Microsoft.Iris.Drawing.SimpleText) rather
|
||||
// than a raw native RasterizeRunPacket.
|
||||
private TextRun(GlyphRunInfo info, TextDocument owner)
|
||||
{
|
||||
_glyphRunInfo = info;
|
||||
_owner = owner;
|
||||
_layoutBounds = info.LayoutBounds;
|
||||
_renderBounds = new RectangleF(info.RenderBoundsX, info.RenderBoundsY, info.RenderBoundsWidth, info.RenderBoundsHeight);
|
||||
_naturalX = info.NaturalX;
|
||||
_naturalY = info.NaturalY;
|
||||
_rasterizeX = info.RasterizeX;
|
||||
_rasterizeY = info.RasterizeY;
|
||||
_rasterizerConfig = info.RasterizerConfig;
|
||||
_runColor = FromColorF(info.RunColor);
|
||||
_overrideColor = Color.Transparent;
|
||||
_highlightColor = FromColorF(info.HighlightColor);
|
||||
_fontFaceUniqueId = info.FontFaceUniqueId;
|
||||
_lfHeight = info.FontHeight;
|
||||
_lfWeight = info.FontWeight;
|
||||
SetBit(Bits.Italic, info.Italic);
|
||||
SetBit(Bits.Underline, info.Underline);
|
||||
SetBit(Bits.Link, info.Link);
|
||||
_underlineBounds = info.UnderlineBounds;
|
||||
_underlineStyleManaged = (NativeApi.UnderlineStyle)info.UnderlineStyle;
|
||||
_lineNumber = info.Line;
|
||||
_naturalRunExtent = info.NaturalExtent;
|
||||
_ascenderInset = info.AscenderInset;
|
||||
_baselineInset = info.BaselineInset;
|
||||
_content = info.Content;
|
||||
}
|
||||
|
||||
private static Color FromColorF(ColorF color) => new(color.A, color.R, color.G, color.B);
|
||||
|
||||
protected override void OnDispose()
|
||||
{
|
||||
NativeApi.SpRichTextDestroyGlyphRunInfo(_hGlyphRunInfo);
|
||||
if (_glyphRunInfo != null)
|
||||
_glyphRunInfo.Dispose();
|
||||
else
|
||||
NativeApi.SpRichTextDestroyGlyphRunInfo(_hGlyphRunInfo);
|
||||
base.OnDispose();
|
||||
}
|
||||
|
||||
@@ -140,9 +181,19 @@ namespace Microsoft.Iris.Drawing
|
||||
|
||||
public unsafe NativeApi.UnderlineStyle UnderlineStyle
|
||||
{
|
||||
get => _hRasterizeRunPacket == IntPtr.Zero ? NativeApi.UnderlineStyle.None : ((NativeApi.RasterizeRunPacket*)(void*)_hRasterizeRunPacket)->usUnderlineStyle;
|
||||
get
|
||||
{
|
||||
if (_glyphRunInfo != null)
|
||||
return _underlineStyleManaged;
|
||||
return _hRasterizeRunPacket == IntPtr.Zero ? NativeApi.UnderlineStyle.None : ((NativeApi.RasterizeRunPacket*)(void*)_hRasterizeRunPacket)->usUnderlineStyle;
|
||||
}
|
||||
set
|
||||
{
|
||||
if (_glyphRunInfo != null)
|
||||
{
|
||||
_underlineStyleManaged = value;
|
||||
return;
|
||||
}
|
||||
if (!(_hRasterizeRunPacket != IntPtr.Zero))
|
||||
return;
|
||||
((NativeApi.RasterizeRunPacket*)(void*)_hRasterizeRunPacket)->usUnderlineStyle = value;
|
||||
@@ -171,6 +222,15 @@ namespace Microsoft.Iris.Drawing
|
||||
bool shadowMode = false;
|
||||
if (samplingMode == "sdw")
|
||||
shadowMode = true;
|
||||
|
||||
if (_glyphRunInfo != null)
|
||||
{
|
||||
var hresult = _owner.Rasterize(_glyphRunInfo, textColor.RenderConvert(), outlineFlag, shadowMode, out var bitmap);
|
||||
return hresult.IsSuccess() && bitmap != null
|
||||
? new Dib(bitmap.NativeHandle, bitmap.Bits, bitmap.Size, bitmap.Dispose)
|
||||
: null;
|
||||
}
|
||||
|
||||
return RichText.Rasterize(_hGlyphRunInfo, outlineFlag, textColor, shadowMode);
|
||||
}
|
||||
|
||||
@@ -182,6 +242,8 @@ namespace Microsoft.Iris.Drawing
|
||||
return new TextRun(hGlyphRunInfo, runPacketPtr, content);
|
||||
}
|
||||
|
||||
internal static TextRun FromGlyphRunInfo(GlyphRunInfo info, TextDocument owner) => new(info, owner);
|
||||
|
||||
public void RemoveSprites(IVisualContainer container)
|
||||
{
|
||||
if (TextSprite != null)
|
||||
|
||||
@@ -8,6 +8,7 @@ using Microsoft.Iris.Data;
|
||||
using Microsoft.Iris.Drawing;
|
||||
using Microsoft.Iris.Library;
|
||||
using Microsoft.Iris.OS;
|
||||
using Microsoft.Iris.Render.Text;
|
||||
using Microsoft.Iris.Session;
|
||||
|
||||
namespace Microsoft.Iris.Markup.UIX
|
||||
@@ -217,27 +218,23 @@ namespace Microsoft.Iris.Markup.UIX
|
||||
if (string.IsNullOrEmpty(resourceName))
|
||||
ErrorManager.ReportError("Script runtime failure: Invalid 'null' value for '{0}'", "resourceName");
|
||||
|
||||
bool error;
|
||||
bool loaded;
|
||||
|
||||
// UIXRender doesn't know how to load resources from .NET assemblies, so we'll
|
||||
// call the relevant GDI API manually for CLR DLLs.
|
||||
// read the bytes ourselves for CLR DLLs and hand them to the platform's font loader.
|
||||
var assemblyName = System.IO.Path.GetFileNameWithoutExtension(moduleName);
|
||||
|
||||
if (ClrDllResources.Instance.TryGetResource($"{assemblyName}!{resourceName}", $"clr-res://{assemblyName}", true, out var resource))
|
||||
{
|
||||
resource.Acquire();
|
||||
|
||||
uint cFonts = 0;
|
||||
var hFont = Win32Api.AddFontMemResourceEx(resource.Buffer, resource.Length, System.IntPtr.Zero, ref cFonts);
|
||||
|
||||
error = hFont == System.IntPtr.Zero;
|
||||
loaded = FontResourceLoader.LoadFromBuffer(resource.Buffer, (int)resource.Length);
|
||||
}
|
||||
else
|
||||
{
|
||||
error = !NativeApi.SpLoadFontResource(moduleName, resourceName);
|
||||
loaded = FontResourceLoader.LoadFromModuleResource(moduleName, resourceName);
|
||||
}
|
||||
|
||||
if (error)
|
||||
if (!loaded)
|
||||
ErrorManager.ReportError("Font Resource {1} not found in module {0}", moduleName, resourceName);
|
||||
|
||||
return null;
|
||||
|
||||
@@ -0,0 +1,22 @@
|
||||
using System.Runtime.CompilerServices;
|
||||
using Microsoft.Iris.Drawing;
|
||||
using Microsoft.Iris.Render.Text;
|
||||
|
||||
namespace Microsoft.Iris.OS;
|
||||
|
||||
// Registers the native Sp*/NativeApi-backed text/font implementations with
|
||||
// UIX.RenderApi's factories. Gated on WINDOWS so non-Windows builds of this
|
||||
// assembly (UIX also targets plain net8.0, see Directory.Build.props) leave
|
||||
// nothing registered and FontResourceLoader/TextDocumentFactory fall back to
|
||||
// their SixLabors.Fonts-based defaults.
|
||||
internal static class TextBackendRegistration
|
||||
{
|
||||
[ModuleInitializer]
|
||||
internal static void Register()
|
||||
{
|
||||
#if WINDOWS
|
||||
FontResourceLoader.RegisterWindowsBackend(() => new SpFontResource());
|
||||
TextDocumentFactory.RegisterWindowsBackend(() => new SpTextDocument());
|
||||
#endif
|
||||
}
|
||||
}
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user