Skip to content

Commit

Permalink
fix: remeasure elements with custom font after font is loaded
Browse files Browse the repository at this point in the history
  • Loading branch information
barjonp committed Sep 26, 2023
1 parent aec6d8b commit 1f4b798
Show file tree
Hide file tree
Showing 13 changed files with 395 additions and 161 deletions.
113 changes: 0 additions & 113 deletions src/Runtime/Runtime/Core/Other/INTERNAL_FontsHelper.cs

This file was deleted.

199 changes: 199 additions & 0 deletions src/Runtime/Runtime/OpenSilver/Internal/Media/FontFace.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,199 @@

/*===================================================================================
*
* Copyright (c) Userware/OpenSilver.net
*
* This file is part of the OpenSilver Runtime (https://opensilver.net), which is
* licensed under the MIT license: https://opensource.org/licenses/MIT
*
* As stated in the MIT license, "the above copyright notice and this permission
* notice shall be included in all copies or substantial portions of the Software."
*
\*====================================================================================*/

using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Threading.Tasks;
using CSHTML5.Internal;

#if MIGRATION
using System.Windows;
using System.Windows.Controls;
using System.Windows.Media;
#else
using Windows.UI.Xaml;
using Windows.UI.Xaml.Controls;
using Windows.UI.Xaml.Media;
#endif

namespace OpenSilver.Internal.Media;

internal sealed class FontFace
{
private static readonly Dictionary<string, FontFace> _fontFacesCache = new();
private static readonly Dictionary<string, string> _fontSourceToFamilyCache = new();
private static readonly ReferenceIDGenerator _idGenerator = new();
private static string _defaultCssFontFamily;

private TaskCompletionSource<bool> _loadOperation;
private List<WeakReference<UIElement>> _measureList;

internal static string DefaultCssFontFamily
{
get
{
if (_defaultCssFontFamily is null)
{
var rootDiv = Application.Current?.GetRootDiv();
if (rootDiv is not null)
{
string sDiv = CSHTML5.INTERNAL_InteropImplementation.GetVariableStringForJS(rootDiv);
_defaultCssFontFamily = Interop.ExecuteJavaScriptString(
$"window.getComputedStyle({sDiv}).getPropertyValue('font-family');");
}
}

return _defaultCssFontFamily ?? string.Empty;
}
}

internal static FontFace GetFontFace(string source, UIElement relativeTo)
{
(string fontName, string fontUrl) = ParseFontSource(source, relativeTo);

if (!_fontFacesCache.TryGetValue(fontName, out FontFace face))
{
face = new FontFace(fontName, fontUrl);
_fontFacesCache.Add(fontName, face);
}

return face;
}

private FontFace(string fontName, string fontUrl)
{
CssFontName = fontName;
CssFontUrl = fontUrl;
IsLoaded = string.IsNullOrEmpty(fontUrl);
}

public string CssFontName { get; }

public string CssFontUrl { get; }

public bool IsLoaded { get; private set; }

internal void RegisterForMeasure(UIElement uie)
{
if (IsLoaded) return;

_measureList ??= new List<WeakReference<UIElement>>();
_measureList.Add(new WeakReference<UIElement>(uie));
}

internal async Task<bool> LoadAsync()
{
if (IsLoaded) return true;

if (_loadOperation is null)
{
_loadOperation = new TaskCompletionSource<bool>();

var loadCallback = JavaScriptCallbackHelper.CreateSelfDisposedJavaScriptCallback<bool>(OnFontLoaded);

string uriEscaped = CSHTML5.INTERNAL_InteropImplementation.GetVariableStringForJS(CssFontUrl);
string sCallback = CSHTML5.INTERNAL_InteropImplementation.GetVariableStringForJS(loadCallback);
Interop.ExecuteJavaScriptVoid(
$"document.loadFont('{CssFontName}', 'url({uriEscaped})', {sCallback});");
}

return await _loadOperation.Task;
}

private void OnFontLoaded(bool success)
{
IsLoaded = true;
_loadOperation?.TrySetResult(success);
_loadOperation = null;

if (success)
{
if (_measureList is null)
{
return;
}

foreach (var weakRef in _measureList)
{
if (!weakRef.TryGetTarget(out UIElement uie))
{
continue;
}

switch (uie)
{
case TextBlock tb:
tb.InvalidateCacheAndMeasure();
break;
default:
uie.InvalidateMeasure();
break;
}
}
}

_measureList = null;
}

private static (string CssFontName, string CssFontUrl) ParseFontSource(string fontSource, UIElement relativeTo)
{
string fontPath = fontSource.Trim().ToLower();

// Note: if the path does not contain the character '.', then it means that there is no
// specified file. It is therefore a default font or thet path to a folder containing
// fonts, which we cannot handle so we simply return the font as is.
if (fontPath.IndexOf('.') == -1)
{
if (fontPath == "portable user interface")
{
fontPath = DefaultCssFontFamily;
}

return (fontPath, null);
}

int index = fontPath.IndexOf('#');
string fontUrl = index != -1 ? fontPath.Substring(0, index) : fontPath;

// we try to make the path fit by considering that it is the startup assembly if not
// specifically defined otherwise: (basically add a prefix ms-appx if there is none)
// Note: we should not enter the "if" below if the path was defined in xaml. This cas
// is already handled during compilation.
if (!fontUrl.StartsWith(@"ms-appx:/") &&
!fontUrl.StartsWith(@"http://") &&
!fontUrl.StartsWith(@"https://") &&
!fontUrl.Contains(@";component/"))
{
fontUrl = $"ms-appx:/{fontUrl}";
}

// Get a path that will lead to the position of the file
string relativeFontPath = INTERNAL_UriHelper.ConvertToHtml5Path(fontUrl, relativeTo);
relativeFontPath = relativeFontPath.Replace('\\', '/');
if (relativeFontPath.StartsWith("/"))
{
relativeFontPath = relativeFontPath.Substring(1);
}

if (!_fontSourceToFamilyCache.TryGetValue(relativeFontPath, out string fontName))
{
fontName = GenerateFontName();
_fontSourceToFamilyCache.Add(relativeFontPath, fontName);
}

return (fontName, relativeFontPath);
}

private static string GenerateFontName() => $"fontName{_idGenerator.NewId()}";
}
2 changes: 1 addition & 1 deletion src/Runtime/Runtime/Runtime.OpenSilver.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -288,6 +288,7 @@
<Compile Include="System.Windows.Media.Imaging\WriteableBitmap.Wasm.cs" />
<Compile Include="System.Windows.Media.Imaging\WriteableBitmap.Simulator.cs" />
<Compile Include="System.Windows.Media\ColorInterpolationMode.cs" />
<Compile Include="OpenSilver\Internal\Media\FontFace.cs" />
<Compile Include="System.Windows.Media\MediaElementState.cs" />
<Compile Include="System.Windows.Controls\WORKINPROGRESS\NotifyEventArgs.cs" />
<Compile Include="System.Windows.Controls\WORKINPROGRESS\Rating.cs" />
Expand Down Expand Up @@ -647,7 +648,6 @@
<Compile Include="Core\Main\TypeFromStringConverters.cs" />
<Compile Include="Core\Other\HashSet2_1.cs" />
<Compile Include="Core\Other\INTERNAL_DispatcherHelpers.cs" />
<Compile Include="Core\Other\INTERNAL_FontsHelper.cs" />
<Compile Include="Core\Other\INTERNAL_WorkaroundIE11IssuesWithScrollViewerInsideGrid.cs" />
<Compile Include="Core\Other\INTERNAL_XamlResourcesHandler.cs" />
<Compile Include="Core\Other\ISet2_1.cs" />
Expand Down
16 changes: 1 addition & 15 deletions src/Runtime/Runtime/System.Windows.Controls/Control.cs
Original file line number Diff line number Diff line change
Expand Up @@ -296,21 +296,7 @@ public FontFamily FontFamily
/// Identifies the <see cref="FontFamily"/> dependency property.
/// </summary>
public static readonly DependencyProperty FontFamilyProperty =
DependencyProperty.Register(
nameof(FontFamily),
typeof(FontFamily),
typeof(Control),
new FrameworkPropertyMetadata(null, FrameworkPropertyMetadataOptions.AffectsMeasure)
{
MethodToUpdateDom2 = static (d, oldValue, newValue) =>
{
var c = (Control)d;
var style = INTERNAL_HtmlDomManager.GetDomElementStyleForModification(c.INTERNAL_OuterDomElement);
style.fontFamily = newValue is FontFamily ff ?
INTERNAL_FontsHelper.LoadFont(ff.Source, c) :
string.Empty;
},
});
TextElementProperties.FontFamilyProperty.AddOwner(typeof(Control));

//-----------------------
// FONTSIZE
Expand Down
13 changes: 8 additions & 5 deletions src/Runtime/Runtime/System.Windows.Controls/RichTextBlock.cs
Original file line number Diff line number Diff line change
Expand Up @@ -189,17 +189,15 @@ public FontStyle FontStyle
/// Identifies the <see cref="FontFamily"/> dependency property.
/// </summary>
public static readonly DependencyProperty FontFamilyProperty =
DependencyProperty.Register(
nameof(FontFamily),
typeof(FontFamily),
TextElementProperties.FontFamilyProperty.AddOwner(
typeof(RichTextBlock),
new FrameworkPropertyMetadata(null, FrameworkPropertyMetadataOptions.AffectsMeasure)
new FrameworkPropertyMetadata(FontFamily.Default, FrameworkPropertyMetadataOptions.Inherits, OnFontFamilyChanged)
{
MethodToUpdateDom2 = static (d, oldValue, newValue) =>
{
var rtb = (RichTextBlock)d;
var style = INTERNAL_HtmlDomManager.GetDomElementStyleForModification(rtb.INTERNAL_OuterDomElement);
style.fontFamily = newValue is FontFamily ff ? INTERNAL_FontsHelper.LoadFont(ff.Source, rtb) : string.Empty;
style.fontFamily = ((FontFamily)newValue).GetFontFace(rtb).CssFontName;
},
});

Expand All @@ -213,6 +211,11 @@ public FontFamily FontFamily
set => SetValue(FontFamilyProperty, value);
}

private static void OnFontFamilyChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
TextElementProperties.InvalidateMeasureOnFontFamilyChanged((RichTextBlock)d, (FontFamily)e.NewValue);
}

/// <summary>
/// Identifies the <see cref="TextWrapping"/> dependency
/// </summary>
Expand Down
Loading

0 comments on commit 1f4b798

Please sign in to comment.