更新
This commit is contained in:
778
WPFDark/Internals/TextRenderer.cs
Normal file
778
WPFDark/Internals/TextRenderer.cs
Normal file
@@ -0,0 +1,778 @@
|
||||
using System;
|
||||
using System.Buffers;
|
||||
using System.Collections.Generic;
|
||||
using System.Diagnostics;
|
||||
using System.IO;
|
||||
using System.Runtime.CompilerServices;
|
||||
using System.Runtime.InteropServices;
|
||||
using System.Text;
|
||||
using System.Threading;
|
||||
using System.Windows;
|
||||
using System.Windows.Documents;
|
||||
using System.Windows.Media;
|
||||
using Jewelry.Collections;
|
||||
using WPFDark.Internals;
|
||||
|
||||
namespace WPFDark.Internals
|
||||
{
|
||||
internal class TextRenderer : TextRendererImpl<IsNotDefaultTextureRenderer>
|
||||
{
|
||||
internal static TextRenderer Italic =>
|
||||
LazyInitializer.EnsureInitialized(ref _Italic, () =>
|
||||
{
|
||||
var fontFamily = (FontFamily) TextElement.FontFamilyProperty.DefaultMetadata.DefaultValue;
|
||||
var fontSize = (double) TextElement.FontSizeProperty.DefaultMetadata.DefaultValue;
|
||||
|
||||
return new TextRenderer(
|
||||
fontFamily, fontSize,
|
||||
FontStyles.Italic, FontWeights.Normal, FontStretches.Normal);
|
||||
});
|
||||
|
||||
private static TextRenderer? _Italic;
|
||||
|
||||
internal TextRenderer(
|
||||
FontFamily fontFamily,
|
||||
double fontSize,
|
||||
FontStyle style,
|
||||
FontWeight weight,
|
||||
FontStretch stretch)
|
||||
: base(fontFamily, fontSize, style, weight, stretch)
|
||||
{
|
||||
}
|
||||
}
|
||||
|
||||
internal class DefaultTextRenderer : TextRendererImpl<IsDefaultTextureRenderer>
|
||||
{
|
||||
internal static readonly DefaultTextRenderer Instance;
|
||||
|
||||
static DefaultTextRenderer()
|
||||
{
|
||||
var fontFamily = (FontFamily) ThemeManager.Current.TryFindResource("WPFDarkFontFamily");
|
||||
var fontSize = (double) TextElement.FontSizeProperty.DefaultMetadata.DefaultValue;
|
||||
|
||||
Instance = new DefaultTextRenderer(
|
||||
fontFamily, fontSize,
|
||||
FontStyles.Normal, FontWeights.Normal, FontStretches.Normal);
|
||||
}
|
||||
|
||||
internal DefaultTextRenderer(
|
||||
FontFamily fontFamily,
|
||||
double fontSize,
|
||||
FontStyle style,
|
||||
FontWeight weight,
|
||||
FontStretch stretch)
|
||||
: base(fontFamily, fontSize, style, weight, stretch)
|
||||
{
|
||||
}
|
||||
}
|
||||
|
||||
internal struct IsDefaultTextureRenderer
|
||||
{
|
||||
}
|
||||
|
||||
internal struct IsNotDefaultTextureRenderer
|
||||
{
|
||||
}
|
||||
|
||||
internal partial class TextRendererImpl<TIsDefault>
|
||||
{
|
||||
internal TextRendererImpl(
|
||||
FontFamily fontFamily,
|
||||
double fontSize,
|
||||
FontStyle style,
|
||||
FontWeight weight,
|
||||
FontStretch stretch)
|
||||
{
|
||||
var typeface = new Typeface(fontFamily, style, weight, stretch);
|
||||
|
||||
if (typeface.TryGetGlyphTypeface(out _glyphTypeface) == false)
|
||||
{
|
||||
// エラーの場合はデフォルトで作り直す
|
||||
typeface =
|
||||
new Typeface(
|
||||
(FontFamily) TextElement.FontFamilyProperty.DefaultMetadata.DefaultValue,
|
||||
(FontStyle) TextElement.FontStyleProperty.DefaultMetadata.DefaultValue,
|
||||
(FontWeight) TextElement.FontWeightProperty.DefaultMetadata.DefaultValue,
|
||||
(FontStretch) TextElement.FontStretchProperty.DefaultMetadata.DefaultValue);
|
||||
|
||||
// デフォルトでもだめなら以降処理しない
|
||||
if (typeface.TryGetGlyphTypeface(out _glyphTypeface) == false)
|
||||
return;
|
||||
}
|
||||
|
||||
_fontSize = fontSize;
|
||||
|
||||
if (typeof(TIsDefault) == typeof(IsDefaultTextureRenderer))
|
||||
{
|
||||
_fontLineSpacing = DefaultFontLineSpacing;
|
||||
_dotGlyphIndex = DefaultDotGlyphIndex;
|
||||
_dotAdvanceWidth = DefaultDotAdvanceWidth;
|
||||
}
|
||||
else
|
||||
{
|
||||
_glyphDataCache = new Dictionary<int, (ushort GlyphIndex, double AdvanceWidth)>();
|
||||
_toGlyphMap = _glyphTypeface.CharacterToGlyphMap;
|
||||
_advanceWidthsDict = _glyphTypeface.AdvanceWidths;
|
||||
|
||||
_fontLineSpacing = fontFamily.LineSpacing;
|
||||
(_dotGlyphIndex, _dotAdvanceWidth) = CalcGlyphIndexAndWidth('.');
|
||||
}
|
||||
|
||||
#if DEBUG
|
||||
// グリフデータテーブルを作る
|
||||
// 作ったものは、手動でソースコードに組み込む。
|
||||
//MakeGlyphDataTable(fontFamily, _glyphTypeface, _fontSize);
|
||||
#endif
|
||||
}
|
||||
|
||||
#if !NETCOREAPP3_1
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
internal double Draw(
|
||||
Visual visual,
|
||||
string text,
|
||||
double x,
|
||||
double y,
|
||||
Brush brush,
|
||||
DrawingContext dc,
|
||||
double maxWidth,
|
||||
TextAlignment align,
|
||||
BiaTextTrimmingMode trimming,
|
||||
bool isUseCache)
|
||||
{
|
||||
return Draw(
|
||||
visual,
|
||||
text.AsSpan(),
|
||||
x,
|
||||
y,
|
||||
brush,
|
||||
dc,
|
||||
maxWidth,
|
||||
align,
|
||||
trimming,
|
||||
isUseCache);
|
||||
}
|
||||
|
||||
internal double CalcWidth(string text)
|
||||
{
|
||||
return CalcWidth(text.AsSpan());
|
||||
}
|
||||
#endif
|
||||
|
||||
internal double Draw(
|
||||
Visual visual,
|
||||
ReadOnlySpan<char> text,
|
||||
double x,
|
||||
double y,
|
||||
Brush brush,
|
||||
DrawingContext dc,
|
||||
double maxWidth,
|
||||
TextAlignment align,
|
||||
BiaTextTrimmingMode trimming,
|
||||
bool isUseCache)
|
||||
{
|
||||
if (NumberHelper.AreCloseZero(_fontSize))
|
||||
return 0;
|
||||
|
||||
if (text.Length == 0)
|
||||
return 0;
|
||||
|
||||
maxWidth = Math.Ceiling(maxWidth);
|
||||
|
||||
if (maxWidth <= 0)
|
||||
return 0;
|
||||
|
||||
var gr = trimming switch
|
||||
{
|
||||
BiaTextTrimmingMode.None => MakeGlyphRunNone(visual, text, maxWidth, isUseCache),
|
||||
BiaTextTrimmingMode.Standard => MakeGlyphRunStandard(visual, text, maxWidth, isUseCache),
|
||||
BiaTextTrimmingMode.Filepath => MakeGlyphRunFilepath(visual, text, maxWidth, isUseCache),
|
||||
_ => throw new ArgumentOutOfRangeException(nameof(trimming), trimming, null)
|
||||
};
|
||||
|
||||
if (gr == default)
|
||||
return 0;
|
||||
|
||||
switch (align)
|
||||
{
|
||||
case TextAlignment.Left:
|
||||
break;
|
||||
|
||||
case TextAlignment.Right:
|
||||
x += maxWidth - gr.Width;
|
||||
break;
|
||||
|
||||
case TextAlignment.Center:
|
||||
x += (maxWidth - gr.Width) / 2;
|
||||
break;
|
||||
|
||||
default:
|
||||
throw new ArgumentOutOfRangeException(nameof(align), align, null);
|
||||
}
|
||||
|
||||
if (NumberHelper.AreCloseZero(x) && NumberHelper.AreCloseZero(y))
|
||||
{
|
||||
dc.DrawGlyphRun(brush, gr.GlyphRun);
|
||||
}
|
||||
else
|
||||
{
|
||||
var hash = HashCodeMaker.Make(x, y);
|
||||
|
||||
if (_translateCache.TryGetValue(hash, out var t) == false)
|
||||
{
|
||||
t = new TranslateTransform(x, y);
|
||||
_translateCache.Add(hash, t);
|
||||
}
|
||||
|
||||
dc.PushTransform(t);
|
||||
dc.DrawGlyphRun(brush, gr.GlyphRun);
|
||||
dc.Pop();
|
||||
}
|
||||
|
||||
return gr.Width;
|
||||
}
|
||||
|
||||
#if NETCOREAPP3_1
|
||||
[MethodImpl(MethodImplOptions.AggressiveOptimization)]
|
||||
#endif
|
||||
internal double CalcWidth(ReadOnlySpan<char> text)
|
||||
{
|
||||
if (NumberHelper.AreCloseZero(_fontSize))
|
||||
return 0;
|
||||
|
||||
if (text.Length == 0)
|
||||
return 0;
|
||||
|
||||
var textHashCode = HashCodeMaker.Make(text);
|
||||
|
||||
if (_textWidthCache.TryGetValue(textHashCode, out var textWidth))
|
||||
return textWidth;
|
||||
|
||||
foreach (var c in text)
|
||||
textWidth += CalcWidth(c);
|
||||
|
||||
_textWidthCache.Add(textHashCode, textWidth);
|
||||
|
||||
return textWidth;
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
internal double CalcWidth(char c)
|
||||
{
|
||||
if (typeof(TIsDefault) == typeof(IsDefaultTextureRenderer))
|
||||
{
|
||||
return Unsafe.Add(ref GetDefaultAdvanceWidthTable(), (IntPtr) c);
|
||||
}
|
||||
else
|
||||
{
|
||||
Debug.Assert(_glyphDataCache != null);
|
||||
Debug.Assert(_toGlyphMap != null);
|
||||
Debug.Assert(_advanceWidthsDict != null);
|
||||
|
||||
if (_glyphDataCache.TryGetValue(c, out var data) == false)
|
||||
{
|
||||
if (_toGlyphMap.TryGetValue(c, out data.GlyphIndex) == false)
|
||||
_toGlyphMap.TryGetValue(' ', out data.GlyphIndex);
|
||||
|
||||
data.AdvanceWidth = _advanceWidthsDict[data.GlyphIndex] * _fontSize;
|
||||
|
||||
_glyphDataCache.Add(c, data);
|
||||
}
|
||||
|
||||
return data.AdvanceWidth;
|
||||
}
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
internal (ushort GlyphIndex, double AdvanceWidth) CalcGlyphIndexAndWidth(char c)
|
||||
{
|
||||
if (typeof(TIsDefault) == typeof(IsDefaultTextureRenderer))
|
||||
{
|
||||
var glyphIndex = Unsafe.Add(ref GetDefaultGlyphIndexTable(), (IntPtr) c);
|
||||
var advanceWidth = Unsafe.Add(ref GetDefaultAdvanceWidthTable(), (IntPtr) c);
|
||||
|
||||
return (glyphIndex, advanceWidth);
|
||||
}
|
||||
else
|
||||
{
|
||||
Debug.Assert(_glyphDataCache != null);
|
||||
Debug.Assert(_toGlyphMap != null);
|
||||
Debug.Assert(_advanceWidthsDict != null);
|
||||
|
||||
if (_glyphDataCache.TryGetValue(c, out var data) == false)
|
||||
{
|
||||
if (_toGlyphMap.TryGetValue(c, out data.GlyphIndex) == false)
|
||||
_toGlyphMap.TryGetValue(' ', out data.GlyphIndex);
|
||||
|
||||
data.AdvanceWidth = _advanceWidthsDict[data.GlyphIndex] * _fontSize;
|
||||
|
||||
_glyphDataCache.Add(c, data);
|
||||
}
|
||||
|
||||
return (data.GlyphIndex, data.AdvanceWidth);
|
||||
}
|
||||
}
|
||||
|
||||
internal double FontHeight => _fontLineSpacing * _fontSize;
|
||||
|
||||
#if NETCOREAPP3_1
|
||||
[MethodImpl(MethodImplOptions.AggressiveOptimization)]
|
||||
#endif
|
||||
private (GlyphRun GlyphRun, double Width) MakeGlyphRunNone(Visual visual, ReadOnlySpan<char> text, double maxWidth, bool isUseCache)
|
||||
{
|
||||
var srcTextLength = text.Length;
|
||||
|
||||
// ReSharper disable MergeConditionalExpression
|
||||
var glyphIndexesArray =
|
||||
text.Length >= 128
|
||||
? ArrayPool<ushort>.Shared.Rent(srcTextLength)
|
||||
: null;
|
||||
|
||||
var advanceWidthsArray =
|
||||
text.Length >= 128
|
||||
? ArrayPool<double>.Shared.Rent(srcTextLength)
|
||||
: null;
|
||||
|
||||
var glyphIndexes =
|
||||
glyphIndexesArray != null
|
||||
? glyphIndexesArray.AsSpan(0, srcTextLength)
|
||||
: stackalloc ushort[srcTextLength];
|
||||
|
||||
var advanceWidths =
|
||||
advanceWidthsArray != null
|
||||
? advanceWidthsArray.AsSpan(0, srcTextLength)
|
||||
: stackalloc double[srcTextLength];
|
||||
// ReSharper restore MergeConditionalExpression
|
||||
|
||||
try
|
||||
{
|
||||
var textWidth = 0.0;
|
||||
var isTrimmed = false;
|
||||
var newCount = 0;
|
||||
{
|
||||
for (var i = 0; i != text.Length; ++i)
|
||||
{
|
||||
(glyphIndexes[i], advanceWidths[i]) = CalcGlyphIndexAndWidth(text[i]);
|
||||
|
||||
var oldTextWidth = textWidth;
|
||||
textWidth += advanceWidths[i];
|
||||
|
||||
if (textWidth > maxWidth)
|
||||
{
|
||||
textWidth = oldTextWidth;
|
||||
newCount = i - 1;
|
||||
isTrimmed = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (isTrimmed && newCount <= 0)
|
||||
return default;
|
||||
|
||||
if (NumberHelper.AreCloseZero(textWidth))
|
||||
return default;
|
||||
|
||||
var dpi = visual.PixelsPerDip();
|
||||
|
||||
long textKey = default;
|
||||
|
||||
if (isUseCache)
|
||||
{
|
||||
textKey = MakeHashCode(text, textWidth, dpi, BiaTextTrimmingMode.None);
|
||||
|
||||
if (_textCache.TryGetValue(textKey, out var cachedGr))
|
||||
return cachedGr;
|
||||
}
|
||||
|
||||
var textLength = isTrimmed
|
||||
? newCount
|
||||
: text.Length;
|
||||
|
||||
var newGlyphIndexes = new ushort[textLength];
|
||||
var newAdvanceWidths = new double[textLength];
|
||||
|
||||
glyphIndexes.Slice(0, textLength).CopyTo(newGlyphIndexes.AsSpan());
|
||||
advanceWidths.Slice(0, textLength).CopyTo(newAdvanceWidths.AsSpan());
|
||||
|
||||
var gr =
|
||||
(new GlyphRun(
|
||||
_glyphTypeface,
|
||||
0,
|
||||
false,
|
||||
_fontSize,
|
||||
(float) dpi,
|
||||
newGlyphIndexes,
|
||||
new Point(0, _glyphTypeface!.Baseline * _fontSize),
|
||||
newAdvanceWidths,
|
||||
null, null, null, null, null, null), textWidth);
|
||||
|
||||
if (isUseCache)
|
||||
_textCache.Add(textKey, gr);
|
||||
|
||||
return gr;
|
||||
}
|
||||
finally
|
||||
{
|
||||
if (glyphIndexesArray != null)
|
||||
ArrayPool<ushort>.Shared.Return(glyphIndexesArray);
|
||||
|
||||
if (advanceWidthsArray != null)
|
||||
ArrayPool<double>.Shared.Return(advanceWidthsArray);
|
||||
}
|
||||
}
|
||||
|
||||
#if NETCOREAPP3_1
|
||||
[MethodImpl(MethodImplOptions.AggressiveOptimization)]
|
||||
#endif
|
||||
private (GlyphRun GlyphRun, double Width) MakeGlyphRunStandard(Visual visual, ReadOnlySpan<char> text, double maxWidth, bool isUseCache)
|
||||
{
|
||||
// ※ +3 「...」 が増えることがあるためのバッファ
|
||||
var srcTextLength = text.Length + 3;
|
||||
|
||||
// ReSharper disable MergeConditionalExpression
|
||||
var glyphIndexesArray =
|
||||
text.Length >= 128
|
||||
? ArrayPool<ushort>.Shared.Rent(srcTextLength)
|
||||
: null;
|
||||
|
||||
var advanceWidthsArray =
|
||||
text.Length >= 128
|
||||
? ArrayPool<double>.Shared.Rent(srcTextLength)
|
||||
: null;
|
||||
|
||||
var glyphIndexes =
|
||||
glyphIndexesArray != null
|
||||
? glyphIndexesArray.AsSpan(0, srcTextLength)
|
||||
: stackalloc ushort[srcTextLength];
|
||||
|
||||
var advanceWidths =
|
||||
advanceWidthsArray != null
|
||||
? advanceWidthsArray.AsSpan(0, srcTextLength)
|
||||
: stackalloc double[srcTextLength];
|
||||
// ReSharper restore MergeConditionalExpression
|
||||
|
||||
try
|
||||
{
|
||||
var textWidth = 0.0;
|
||||
var isTrimmed = false;
|
||||
var newCount = 0;
|
||||
{
|
||||
for (var i = 0; i != text.Length; ++i)
|
||||
{
|
||||
(glyphIndexes[i], advanceWidths[i]) = CalcGlyphIndexAndWidth(text[i]);
|
||||
|
||||
textWidth += advanceWidths[i];
|
||||
|
||||
if (textWidth > maxWidth)
|
||||
{
|
||||
(textWidth, newCount) = TrimGlyphRunStandard(glyphIndexes, advanceWidths, textWidth, maxWidth, i + 1);
|
||||
|
||||
isTrimmed = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (NumberHelper.AreCloseZero(textWidth))
|
||||
return default;
|
||||
|
||||
var dpi = visual.PixelsPerDip();
|
||||
|
||||
long textKey = default;
|
||||
|
||||
if (isUseCache)
|
||||
{
|
||||
textKey = MakeHashCode(text, textWidth, dpi, BiaTextTrimmingMode.None);
|
||||
|
||||
if (_textCache.TryGetValue(textKey, out var cachedGr))
|
||||
return cachedGr;
|
||||
}
|
||||
|
||||
var textLength = isTrimmed
|
||||
? newCount
|
||||
: text.Length;
|
||||
|
||||
var newGlyphIndexes = new ushort[textLength];
|
||||
var newAdvanceWidths = new double[textLength];
|
||||
|
||||
glyphIndexes.Slice(0, textLength).CopyTo(newGlyphIndexes.AsSpan());
|
||||
advanceWidths.Slice(0, textLength).CopyTo(newAdvanceWidths.AsSpan());
|
||||
|
||||
if (_glyphTypeface is null)
|
||||
throw new InvalidDataException();
|
||||
|
||||
var gr =
|
||||
(new GlyphRun(
|
||||
_glyphTypeface,
|
||||
0,
|
||||
false,
|
||||
_fontSize,
|
||||
(float) dpi,
|
||||
newGlyphIndexes,
|
||||
new Point(0, _glyphTypeface.Baseline * _fontSize),
|
||||
newAdvanceWidths,
|
||||
null, null, null, null, null, null), textWidth);
|
||||
|
||||
if (isUseCache)
|
||||
_textCache.Add(textKey, gr);
|
||||
|
||||
return gr;
|
||||
}
|
||||
finally
|
||||
{
|
||||
if (glyphIndexesArray != null)
|
||||
ArrayPool<ushort>.Shared.Return(glyphIndexesArray);
|
||||
|
||||
if (advanceWidthsArray != null)
|
||||
ArrayPool<double>.Shared.Return(advanceWidthsArray);
|
||||
}
|
||||
}
|
||||
|
||||
private (double Width, int NewCount) TrimGlyphRunStandard(
|
||||
Span<ushort> glyphIndexes,
|
||||
Span<double> advanceWidths,
|
||||
double textWidth,
|
||||
double maxWidth,
|
||||
int bufferSize)
|
||||
{
|
||||
Debug.Assert(textWidth > maxWidth);
|
||||
|
||||
// 文字列に ... を加える分を考慮して削る文字数を求める
|
||||
var dot3Width = _dotAdvanceWidth * 3.0;
|
||||
|
||||
var removeCount = 1;
|
||||
var newTextWidth = textWidth;
|
||||
{
|
||||
for (var i = bufferSize - 1; i >= 0; --i)
|
||||
{
|
||||
newTextWidth -= advanceWidths[i];
|
||||
|
||||
if (maxWidth - newTextWidth >= dot3Width)
|
||||
break;
|
||||
|
||||
++removeCount;
|
||||
}
|
||||
}
|
||||
|
||||
var newCount = bufferSize - removeCount + 3;
|
||||
if (newCount < 3)
|
||||
return (0.0, 0);
|
||||
|
||||
// 文字列に ... を追加する
|
||||
glyphIndexes[newCount - 1 - 2] = _dotGlyphIndex;
|
||||
glyphIndexes[newCount - 1 - 1] = _dotGlyphIndex;
|
||||
glyphIndexes[newCount - 1 - 0] = _dotGlyphIndex;
|
||||
advanceWidths[newCount - 1 - 2] = _dotAdvanceWidth;
|
||||
advanceWidths[newCount - 1 - 1] = _dotAdvanceWidth;
|
||||
advanceWidths[newCount - 1 - 0] = _dotAdvanceWidth;
|
||||
|
||||
return (newTextWidth + dot3Width, newCount);
|
||||
}
|
||||
|
||||
#if NETCOREAPP3_1
|
||||
[MethodImpl(MethodImplOptions.AggressiveOptimization)]
|
||||
#endif
|
||||
private (GlyphRun GlyphRun, double Width) MakeGlyphRunFilepath(Visual visual, ReadOnlySpan<char> text, double maxWidth, bool isUseCache)
|
||||
{
|
||||
var buffer = ArrayPool<char>.Shared.Rent(text.Length);
|
||||
|
||||
try
|
||||
{
|
||||
if (CalcWidth(text) > maxWidth)
|
||||
text = TrimmingFilepathText(text, maxWidth, buffer);
|
||||
|
||||
return MakeGlyphRunStandard(visual, text, maxWidth, isUseCache);
|
||||
}
|
||||
finally
|
||||
{
|
||||
ArrayPool<char>.Shared.Return(buffer);
|
||||
}
|
||||
}
|
||||
|
||||
#if NETCOREAPP3_1
|
||||
[MethodImpl(MethodImplOptions.AggressiveOptimization)]
|
||||
#endif
|
||||
private ReadOnlySpan<char> TrimmingFilepathText(ReadOnlySpan<char> text, double maxWidth, char[] buffer)
|
||||
{
|
||||
// ref: https://www.codeproject.com/Tips/467054/WPF-PathTrimmingTextBlock
|
||||
|
||||
bool widthOk;
|
||||
|
||||
#if NETCOREAPP3_1
|
||||
var filename = Path.GetFileName(text);
|
||||
var directory = Path.GetDirectoryName(text);
|
||||
#else
|
||||
var textString = text.ToString();
|
||||
var filename = Path.GetFileName(textString).AsSpan();
|
||||
var directory = Path.GetDirectoryName(textString).AsSpan();
|
||||
#endif
|
||||
|
||||
var changedWidth = false;
|
||||
|
||||
var sepSpan = "...\\".AsSpan();
|
||||
|
||||
var sepWidth = CalcWidth(sepSpan);
|
||||
var filepathWidth = CalcWidth(filename);
|
||||
var directoryWidth = CalcWidth(directory);
|
||||
|
||||
do
|
||||
{
|
||||
var pathWidth = directoryWidth + sepWidth + filepathWidth;
|
||||
|
||||
widthOk = pathWidth < maxWidth;
|
||||
|
||||
if (widthOk == false)
|
||||
{
|
||||
changedWidth = true;
|
||||
|
||||
directoryWidth -= CalcWidth(directory[directory.Length - 1]);
|
||||
directory = directory.Slice(0, directory.Length - 1);
|
||||
|
||||
if (directory.Length == 0)
|
||||
{
|
||||
// "...\\" + new string(filename);
|
||||
sepSpan.CopyTo(buffer);
|
||||
filename.CopyTo(buffer.AsSpan(sepSpan.Length));
|
||||
|
||||
return buffer.AsSpan(0, sepSpan.Length + filename.Length);
|
||||
}
|
||||
}
|
||||
} while (widthOk == false);
|
||||
|
||||
if (changedWidth)
|
||||
{
|
||||
// new string(directory) + "...\\" + new string(filename);
|
||||
directory.CopyTo(buffer);
|
||||
sepSpan.CopyTo(buffer.AsSpan(directory.Length));
|
||||
filename.CopyTo(buffer.AsSpan(directory.Length + sepSpan.Length));
|
||||
|
||||
return buffer.AsSpan(0, directory.Length + sepSpan.Length + filename.Length);
|
||||
}
|
||||
else
|
||||
return text;
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
private static long MakeHashCode(
|
||||
ReadOnlySpan<char> text,
|
||||
double textWidth,
|
||||
double dpi,
|
||||
BiaTextTrimmingMode textTrimming)
|
||||
{
|
||||
unchecked
|
||||
{
|
||||
var hashCode = HashCodeMaker.Make(text);
|
||||
|
||||
hashCode = (hashCode * 397) ^ (long) textTrimming;
|
||||
|
||||
return (hashCode * 397) ^ HashCodeMaker.Make(textWidth, dpi);
|
||||
}
|
||||
}
|
||||
|
||||
private readonly LruCache<long, (GlyphRun, double)> _textCache = new LruCache<long, (GlyphRun, double)>(10_0000);
|
||||
private readonly LruCache<long, double> _textWidthCache = new LruCache<long, double>(10_0000);
|
||||
|
||||
private readonly LruCache<long, TranslateTransform> _translateCache = new LruCache<long, TranslateTransform>(1000);
|
||||
|
||||
private readonly IDictionary<int, ushort>? _toGlyphMap;
|
||||
private readonly IDictionary<ushort, double>? _advanceWidthsDict;
|
||||
|
||||
// 最大65536エントリ
|
||||
private readonly Dictionary<int, (ushort GlyphIndex, double AdvanceWidth)>? _glyphDataCache;
|
||||
|
||||
private readonly GlyphTypeface? _glyphTypeface;
|
||||
private readonly ushort _dotGlyphIndex;
|
||||
private readonly double _dotAdvanceWidth;
|
||||
private readonly double _fontSize;
|
||||
private readonly double _fontLineSpacing;
|
||||
|
||||
#if DEBUG
|
||||
// ReSharper disable once UnusedMember.Local
|
||||
private static void MakeGlyphDataTable(FontFamily fontFamily, GlyphTypeface glyphTypeface, double fontSize)
|
||||
{
|
||||
var toGlyphMap = glyphTypeface.CharacterToGlyphMap;
|
||||
var advanceWidthsDict = glyphTypeface.AdvanceWidths;
|
||||
|
||||
toGlyphMap.TryGetValue(' ', out var dummyIndex);
|
||||
|
||||
var glyphIndexArray = new ushort[char.MaxValue + 1];
|
||||
var advanceWidthArray = new double[char.MaxValue + 1];
|
||||
|
||||
for (var i = 0; i <= char.MaxValue; ++i)
|
||||
{
|
||||
if (toGlyphMap.TryGetValue(i, out var glyphIndex) == false)
|
||||
glyphIndex = dummyIndex;
|
||||
|
||||
var advanceWidth = advanceWidthsDict[glyphIndex] * fontSize;
|
||||
|
||||
glyphIndexArray[i] = glyphIndex;
|
||||
advanceWidthArray[i] = advanceWidth;
|
||||
}
|
||||
|
||||
var glyphIndexByteArray = MemoryMarshal.Cast<ushort, byte>(glyphIndexArray).ToArray();
|
||||
var advanceWidthByteArray = MemoryMarshal.Cast<double, byte>(advanceWidthArray).ToArray();
|
||||
|
||||
var dotGlyphIndex = Unsafe.Add(ref MemoryMarshal.GetReference(MemoryMarshal.Cast<byte, ushort>(glyphIndexByteArray)), (IntPtr) '.');
|
||||
var dotAdvanceWidth = Unsafe.Add(ref MemoryMarshal.GetReference(MemoryMarshal.Cast<byte, double>(advanceWidthByteArray)), (IntPtr) '.');
|
||||
|
||||
var sb = new StringBuilder();
|
||||
|
||||
sb.AppendLine("// ReSharper disable All");
|
||||
sb.AppendLine("using System;");
|
||||
sb.AppendLine("using System.Runtime.CompilerServices;");
|
||||
sb.AppendLine("using System.Runtime.InteropServices;");
|
||||
sb.AppendLine("namespace WPFDark.Internals");
|
||||
sb.AppendLine("{");
|
||||
sb.AppendLine("internal partial class TextRendererImpl<TIsDefault>");
|
||||
sb.AppendLine("{");
|
||||
|
||||
sb.AppendLine($"private const double DefaultFontLineSpacing = {fontFamily.LineSpacing};");
|
||||
sb.AppendLine($"private const ushort DefaultDotGlyphIndex = {dotGlyphIndex};");
|
||||
sb.AppendLine($"private const double DefaultDotAdvanceWidth = {dotAdvanceWidth};");
|
||||
|
||||
sb.AppendLine("[MethodImpl(MethodImplOptions.AggressiveInlining)]");
|
||||
sb.AppendLine("private static ref ushort GetDefaultGlyphIndexTable(){");
|
||||
sb.AppendLine("var byteTable = new ReadOnlySpan<byte>(new byte[] {");
|
||||
sb.AppendLine(JoinStrings(glyphIndexByteArray));
|
||||
sb.AppendLine("});");
|
||||
sb.AppendLine("return ref MemoryMarshal.GetReference(MemoryMarshal.Cast<byte, ushort>(byteTable));");
|
||||
sb.AppendLine("}");
|
||||
|
||||
sb.AppendLine("[MethodImpl(MethodImplOptions.AggressiveInlining)]");
|
||||
sb.AppendLine("private static ref double GetDefaultAdvanceWidthTable(){");
|
||||
sb.AppendLine("var byteTable = new ReadOnlySpan<byte>(new byte[] {");
|
||||
sb.AppendLine(JoinStrings(advanceWidthByteArray));
|
||||
sb.AppendLine("});");
|
||||
sb.AppendLine("return ref MemoryMarshal.GetReference(MemoryMarshal.Cast<byte, double>(byteTable));");
|
||||
sb.AppendLine("}");
|
||||
|
||||
sb.AppendLine("}");
|
||||
sb.AppendLine("}");
|
||||
|
||||
var outputDir = Path.Combine(System.Environment.GetFolderPath(System.Environment.SpecialFolder.UserProfile), "TextRenderer.table.cs");
|
||||
File.WriteAllText(outputDir, sb.ToString());
|
||||
}
|
||||
|
||||
private static string JoinStrings<T>(IEnumerable<T> source)
|
||||
{
|
||||
var sb = new StringBuilder();
|
||||
|
||||
var c = 0;
|
||||
|
||||
foreach (var v in source)
|
||||
{
|
||||
++c;
|
||||
|
||||
sb.Append($"0x{v:x2}");
|
||||
|
||||
if (c > 0 && (c % 32 == 0))
|
||||
sb.AppendLine(",");
|
||||
else
|
||||
sb.Append(',');
|
||||
}
|
||||
|
||||
return sb.ToString();
|
||||
}
|
||||
#endif
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user