Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions demo/Common/HtmlRenderer.Demo.Common.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@
</ItemGroup>
<ItemGroup>
<EmbeddedResource Include="PerfSamples\1.Big table.htm" />
<EmbeddedResource Include="TestSamples\36.Emoji.htm" />
</ItemGroup>
<ItemGroup>
<EmbeddedResource Update="Properties\Resources.resx">
Expand Down
14 changes: 14 additions & 0 deletions demo/Common/TestSamples/36.Emoji.htm
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
<html>

<head>
<meta charset="utf-8">
<title>Emoji Example</title>
</head>

<body>
1️⃣<p>&#x1F600 This is a smiling face emoji: &#x1F600 &#x1F600 displayed using unicode value. &#x1F600</p>
2️⃣<p>&#128512 This is a smiling face emoji: &#128512 &#128512 displayed using decimal value. &#128512</p>
3️⃣<p>HTML is <span> 💜 </span> </p>
</body>

</html>
97 changes: 91 additions & 6 deletions external/HtmlRenderer/Core/Dom/CssBox.cs
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@
using System;
using System.Collections.Generic;
using System.Globalization;
using System.Linq;
using Avalonia.Controls.Documents;
using TheArtOfDev.HtmlRenderer.Adapters;
using TheArtOfDev.HtmlRenderer.Adapters.Entities;
Expand Down Expand Up @@ -561,6 +562,41 @@ public void ParseToWords()
bool preserveSpaces = WhiteSpace == CssConstants.Pre || WhiteSpace == CssConstants.PreWrap;
bool respoctNewline = preserveSpaces || WhiteSpace == CssConstants.PreLine;
var text = _text.Span;

//Fix for emoji 😎
Dictionary<int, int> emojiRegion = new();
int[] textIndices = StringInfo.ParseCombiningCharacters(text.ToString());

if (textIndices.Length != text.Length)
{
if (textIndices.Length == 1)
{
emojiRegion.Add(0, text.Length - 1);
}
else
{
int skippedChars = 0;
var previousChar = textIndices[0];
foreach (var index in textIndices.Skip(1))
{
if (index > previousChar + 1)
{
emojiRegion.Add(previousChar, index - 1);
skippedChars += index - 1 - previousChar;
}

previousChar = index;
}

//Add emoji at the end of a string
if (skippedChars + textIndices.Length != text.Length)
{
emojiRegion.Add(textIndices.Last(),
textIndices.Last() + (text.Length - textIndices.Last() - 1));
}
}
}

while (startIdx < text.Length)
{
while (startIdx < text.Length && text[startIdx] == '\r')
Expand All @@ -569,28 +605,65 @@ public void ParseToWords()
if (startIdx < text.Length)
{
var endIdx = startIdx;

if (emojiRegion.Count > 0 && emojiRegion.ContainsKey(startIdx))
{
endIdx = emojiRegion[startIdx] + 1;

var hasSpaceBefore = !preserveSpaces && (startIdx > 0 && _boxWords.Count == 0 &&
char.IsWhiteSpace(text[startIdx - 1]));
var hasSpaceAfter =
!preserveSpaces && (endIdx < text.Length && char.IsWhiteSpace(text[endIdx]));
_boxWords.Add(new CssRectWord(this,
HtmlUtils.DecodeHtml(text.Slice(startIdx, endIdx - startIdx).ToString()).AsMemory(),
hasSpaceBefore, hasSpaceAfter){ IsEmoji = true });
startIdx = endIdx;
continue;
}

while (endIdx < text.Length && char.IsWhiteSpace(text[endIdx]) && text[endIdx] != '\n')
endIdx++;

if (endIdx > startIdx)
{
if (preserveSpaces)
_boxWords.Add(new CssRectWord(this, HtmlUtils.DecodeHtml(text.Slice(startIdx, endIdx - startIdx).ToString()).AsMemory(), false, false));
_boxWords.Add(new CssRectWord(this,
HtmlUtils.DecodeHtml(text.Slice(startIdx, endIdx - startIdx).ToString()).AsMemory(),
false, false));
}
else
{
endIdx = startIdx;
while (endIdx < text.Length && !char.IsWhiteSpace(text[endIdx]) && text[endIdx] != '-' && WordBreak != CssConstants.BreakAll && !CommonUtils.IsAsianCharecter(text[endIdx]))
while (endIdx < text.Length && !char.IsWhiteSpace(text[endIdx]) && text[endIdx] != '-' &&
WordBreak != CssConstants.BreakAll && !CommonUtils.IsAsianCharecter(text[endIdx]))
endIdx++;

if (endIdx < text.Length && (text[endIdx] == '-' || WordBreak == CssConstants.BreakAll || CommonUtils.IsAsianCharecter(text[endIdx])))
if (endIdx < text.Length && (text[endIdx] == '-' || WordBreak == CssConstants.BreakAll ||
CommonUtils.IsAsianCharecter(text[endIdx])))
endIdx++;

if (endIdx > startIdx)
{
var hasSpaceBefore = !preserveSpaces && (startIdx > 0 && _boxWords.Count == 0 && char.IsWhiteSpace(text[startIdx - 1]));
var hasSpaceAfter = !preserveSpaces && (endIdx < text.Length && char.IsWhiteSpace(text[endIdx]));
_boxWords.Add(new CssRectWord(this, HtmlUtils.DecodeHtml(text.Slice(startIdx, endIdx - startIdx).ToString()).AsMemory(), hasSpaceBefore, hasSpaceAfter));
if (emojiRegion.Count > 0)
{
//If index between an emoji region, then shift index to the start
foreach (var region in emojiRegion)
{
if (region.Key >= startIdx && region.Key <= endIdx)
{
endIdx = region.Key;
break;
}
}
}

var hasSpaceBefore = !preserveSpaces && (startIdx > 0 && _boxWords.Count == 0 &&
char.IsWhiteSpace(text[startIdx - 1]));
var hasSpaceAfter = !preserveSpaces &&
(endIdx < text.Length && char.IsWhiteSpace(text[endIdx]));
_boxWords.Add(new CssRectWord(this,
HtmlUtils.DecodeHtml(text.Slice(startIdx, endIdx - startIdx).ToString()).AsMemory(),
hasSpaceBefore, hasSpaceAfter));
}
}

Expand Down Expand Up @@ -740,8 +813,20 @@ internal virtual void MeasureWordsSize(RGraphics g)

if (Words.Count > 0)
{
var defaultFont = FontFamily;
foreach (var boxWord in Words)
{

ActualFont = null;
if (boxWord is CssRectWord rectWord && rectWord.IsEmoji)
{
FontFamily = rectWord.FontFamily;
}
else
{
FontFamily = defaultFont;
}

boxWord.Line = g.FormatLine(boxWord.Text, ActualFont);
var size = boxWord.Line.Size;
boxWord.Width = size.Width;
Expand Down
6 changes: 5 additions & 1 deletion external/HtmlRenderer/Core/Dom/CssBoxProperties.cs
Original file line number Diff line number Diff line change
Expand Up @@ -1265,6 +1265,7 @@ public RFont ActualFont
{
FontFamily = CssConstants.DefaultFont;
}

if (string.IsNullOrEmpty(FontSize))
{
FontSize = CssConstants.FontSize.ToString(CultureInfo.InvariantCulture) + "pt";
Expand All @@ -1277,7 +1278,8 @@ public RFont ActualFont
st |= RFontStyle.Italic;
}

if (FontWeight != CssConstants.Normal && FontWeight != CssConstants.Lighter && !string.IsNullOrEmpty(FontWeight) && FontWeight != CssConstants.Inherit)
if (FontWeight != CssConstants.Normal && FontWeight != CssConstants.Lighter &&
!string.IsNullOrEmpty(FontWeight) && FontWeight != CssConstants.Inherit)
{
st |= RFontStyle.Bold;
}
Expand Down Expand Up @@ -1329,8 +1331,10 @@ public RFont ActualFont

_actualFont = GetCachedFont(FontFamily, fsize, st);
}

return _actualFont;
}
set { _actualFont = value; }
}

protected abstract RFont GetCachedFont(string fontFamily, double fsize, RFontStyle st);
Expand Down
10 changes: 10 additions & 0 deletions external/HtmlRenderer/Core/Dom/CssRectWord.cs
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@

using System;
using TheArtOfDev.HtmlRenderer.Adapters;
using TheArtOfDev.HtmlRenderer.Core.Utils;

namespace TheArtOfDev.HtmlRenderer.Core.Dom
{
Expand Down Expand Up @@ -104,6 +105,15 @@ public override ReadOnlyMemory<char> Text
get { return _text; }
}


public bool IsEmoji { get; set; }

public string FontFamily
{
get { return IsEmoji ? CssConstants.DefaultEmojiFont : this.OwnerBox.FontFamily; }
}


/// <summary>
/// Represents this word for debugging purposes
/// </summary>
Expand Down
5 changes: 5 additions & 0 deletions external/HtmlRenderer/Core/Utils/CssConstants.cs
Original file line number Diff line number Diff line change
Expand Up @@ -165,5 +165,10 @@ internal static class CssConstants
/// Default font used for the generic 'serif' family
/// </summary>
public const string DefaultFont = "Segoe UI";

/// <summary>
/// Default font used for the emoji family
/// </summary>
public const string DefaultEmojiFont = "Segoe UI Emoji";
}
}