mirror of
https://github.com/Facepunch/sbox-public.git
synced 2026-01-24 14:20:39 -05:00
This commit imports the C# engine code and game files, excluding C++ source code. [Source-Commit: ceb3d758046e50faa6258bc3b658a30c97743268]
191 lines
6.1 KiB
C#
191 lines
6.1 KiB
C#
// RichTextKit
|
|
// Copyright © 2019-2020 Topten Software. All Rights Reserved.
|
|
//
|
|
// Licensed under the Apache License, Version 2.0 (the "License"); you may
|
|
// not use this product except in compliance with the License. You may obtain
|
|
// a copy of the License at
|
|
//
|
|
// http://www.apache.org/licenses/LICENSE-2.0
|
|
//
|
|
// Unless required by applicable law or agreed to in writing, software
|
|
// distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
|
// WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
|
// License for the specific language governing permissions and limitations
|
|
// under the License.
|
|
|
|
using SkiaSharp;
|
|
using System;
|
|
using System.Collections.Generic;
|
|
using Topten.RichTextKit.Utils;
|
|
|
|
namespace Topten.RichTextKit
|
|
{
|
|
/// <summary>
|
|
/// Helper to split a run of code points based on a particular typeface
|
|
/// into a series of runs where unsupported code points are mapped to a
|
|
/// fallback font.
|
|
/// </summary>
|
|
public class FontFallback
|
|
{
|
|
/// <summary>
|
|
/// Specified details about a font fallback run
|
|
/// </summary>
|
|
public struct Run
|
|
{
|
|
/// <summary>
|
|
/// The starting code point index of this run
|
|
/// </summary>
|
|
public int Start;
|
|
|
|
/// <summary>
|
|
/// The length of this run in code points
|
|
/// </summary>
|
|
public int Length;
|
|
|
|
/// <summary>
|
|
/// The typeface to be used for this run
|
|
/// </summary>
|
|
public SKTypeface Typeface;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Specifies the instance of the character matcher to be used for font fallback
|
|
/// </summary>
|
|
/// <remarks>
|
|
/// This instance is shared by all TextBlock instances and should be thread safe
|
|
/// if used in a multi-threaded environment.
|
|
/// </remarks>
|
|
public static ICharacterMatcher CharacterMatcher = new DefaultCharacterMatcher();
|
|
|
|
/// <summary>
|
|
/// Splits a sequence of code points into a series of runs with font fallback applied
|
|
/// </summary>
|
|
/// <param name="codePoints">The code points</param>
|
|
/// <param name="typeface">The preferred typeface</param>
|
|
/// <param name="replacementCharacter">The replacement character to be used for the run</param>
|
|
/// <returns>A sequence of runs with unsupported code points replaced by a selected font fallback</returns>
|
|
public static IEnumerable<Run> GetFontRuns( Slice<int> codePoints, SKTypeface typeface, char replacementCharacter = '\0' )
|
|
{
|
|
var font = new SKFont( typeface );
|
|
|
|
if ( replacementCharacter != '\0' )
|
|
{
|
|
var glyph = font.GetGlyph( replacementCharacter );
|
|
if ( glyph == 0 )
|
|
{
|
|
var fallbackTypeface = CharacterMatcher.MatchCharacter( typeface.FamilyName, typeface.FontWeight, typeface.FontWidth, typeface.FontSlant, null, replacementCharacter );
|
|
if ( fallbackTypeface != null )
|
|
typeface = fallbackTypeface;
|
|
}
|
|
|
|
yield return new Run()
|
|
{
|
|
Start = 0,
|
|
Length = codePoints.Length,
|
|
Typeface = typeface,
|
|
};
|
|
yield break;
|
|
}
|
|
|
|
// Get glyphs using the top-level typeface
|
|
var glyphs = new ushort[codePoints.Length];
|
|
font.GetGlyphs( codePoints.AsSpan(), glyphs );
|
|
|
|
// Look for subspans that need font fallback (where glyphs are zero)
|
|
int runStart = 0;
|
|
for ( int i = 0; i < codePoints.Length; i++ )
|
|
{
|
|
// Do we need fallback for this character?
|
|
if ( glyphs[i] == 0 )
|
|
{
|
|
// Check if there's a fallback available, if not, might as well continue with the current top-level typeface
|
|
var subSpanTypeface = CharacterMatcher.MatchCharacter( typeface.FamilyName, typeface.FontWeight, typeface.FontWidth, typeface.FontSlant, null, codePoints[i] );
|
|
if ( subSpanTypeface == null )
|
|
continue;
|
|
|
|
// Don't fallback for whitespace characters
|
|
if ( UnicodeClasses.BoundaryGroup( codePoints[i] ) == WordBoundaryClass.Space )
|
|
continue;
|
|
|
|
// Must be a cluster boundary
|
|
if ( !GraphemeClusterAlgorithm.IsBoundary( codePoints, i ) )
|
|
continue;
|
|
|
|
// We can do font fallback...
|
|
|
|
// Flush the current top-level run
|
|
if ( i > runStart )
|
|
{
|
|
yield return new Run()
|
|
{
|
|
Start = runStart,
|
|
Length = i - runStart,
|
|
Typeface = typeface,
|
|
};
|
|
}
|
|
|
|
// Count how many unmatched characters
|
|
var unmatchedStart = i;
|
|
var unmatchedEnd = i + 1;
|
|
while ( unmatchedEnd < codePoints.Length &&
|
|
(glyphs[unmatchedEnd] == 0 || !GraphemeClusterAlgorithm.IsBoundary( codePoints, unmatchedEnd )) )
|
|
{
|
|
unmatchedEnd++;
|
|
}
|
|
var unmatchedLength = unmatchedEnd - unmatchedStart;
|
|
|
|
// Match the missing characters
|
|
while ( unmatchedLength > 0 )
|
|
{
|
|
// Find the font fallback using the first character
|
|
subSpanTypeface = CharacterMatcher.MatchCharacter( typeface.FamilyName, typeface.FontWeight, typeface.FontWidth, typeface.FontSlant, null, codePoints[unmatchedStart] );
|
|
if ( subSpanTypeface == null )
|
|
{
|
|
unmatchedEnd = unmatchedStart;
|
|
break;
|
|
}
|
|
var subSpanFont = new SKFont( subSpanTypeface );
|
|
|
|
// Get the glyphs over the current unmatched range
|
|
subSpanFont.GetGlyphs( codePoints.SubSlice( unmatchedStart, unmatchedLength ).AsSpan(), new Span<ushort>( glyphs, unmatchedStart, unmatchedLength ) );
|
|
|
|
// Count how many characters were matched
|
|
var fallbackStart = unmatchedStart;
|
|
var fallbackEnd = unmatchedStart + 1;
|
|
while ( fallbackEnd < unmatchedEnd && glyphs[fallbackEnd] != 0 )
|
|
fallbackEnd++;
|
|
var fallbackLength = fallbackEnd - fallbackStart;
|
|
|
|
// Yield this font fallback run
|
|
yield return new Run()
|
|
{
|
|
Start = fallbackStart,
|
|
Length = fallbackLength,
|
|
Typeface = subSpanTypeface,
|
|
};
|
|
|
|
// Continue selecting font fallbacks until the entire unmatched ranges has been matched
|
|
unmatchedStart += fallbackLength;
|
|
unmatchedLength -= fallbackLength;
|
|
}
|
|
|
|
// Move onto the next top level span
|
|
i = unmatchedEnd - 1; // account for i++ on for loop
|
|
runStart = unmatchedEnd;
|
|
}
|
|
}
|
|
|
|
// Flush find run
|
|
if ( codePoints.Length > runStart )
|
|
{
|
|
yield return new Run()
|
|
{
|
|
Start = runStart,
|
|
Length = codePoints.Length - runStart,
|
|
Typeface = typeface,
|
|
};
|
|
}
|
|
}
|
|
}
|
|
}
|