View Issue Details

IDProjectCategoryView StatusLast Update
0002380NoesisGUIC++ SDKpublic2024-02-26 11:12
Reportersteveh Assigned Tojsantos  
PrioritynormalSeverityfeatureReproducibilityalways
Status feedbackResolutionopen 
Product Version3.2.0 
Target Version3.2 
Summary0002380: Feature Request: Composite Fonts
DescriptionHi guys, I'm wondering if it's possible to get composite font support (https://docs.microsoft.com/en-us/dotnet/api/system.windows.media.fontfamily?view=windowsdesktop-6.0#composite-fonts)

The issue I'm having at the moment is I have a tonne of individual license fonts for a variety of languages. If I include all fonts in the font fallback array I have no way of only selecting a range of unicode characters to use a given font. So it creates an issue as the following:

- The Chinese font has glyphs for Traditional Chinese, Simplified Chinese and Korean.
- The Korean font has glyphs for Korean

If I make my font fallback map:

{KoreanFont, ChineseFont}

Then it works. If I reverse the order the Chinese font takes precedence. This is easy enough to solve in this case, but in my case I have 6 different fonts for different languages, and they share a huge overlap of supported uniocde ranges. I am unable to get into a position where the fallback map uses the correct font fallback for every language. I have managed to get around this issue by only loading the fonts for the current selected language, but we have some screens (e.g. language select and some editor sandboxes) where we show multiple languages on screen at the same time.

I could change our fonts so they remove the glyphs, but that would require that I change some licensed font.

Ideally we would be able to specify the unicode range that a font supports in the font fallback map, either via a bespoke solution or via the implementation of the composite fonts.
TagsNo tags attached.
PlatformAny

Activities

jsantos

jsantos

2022-07-14 16:30

manager   ~0008018

Hello, let me think more about this and I will come back again (by the way I am taking the next week off)

At first, I would say that implementing support for *.CompositeFont files seems to be the best solution. But I am not sure how complex this can be
jsantos

jsantos

2022-09-21 14:01

manager   ~0008077

Hi Steven, I am not sure if you found a solution for this or how critical this request is. I was investigating the implementation of CompositeFonts in Noesis and it is harder than expected and we want to do it after official 3.2 is released (we have plans to release the beta this month).

Please, let me know your thoughts about this and if we can help you with a workaround.
steveh

steveh

2022-09-21 14:51

reporter   ~0008078

Hey Jesús, we have not come to a solution yet. We have 2 critical parts that we're dealing with.

1. Gamertags - Microsoft dictate that with modern gamertags we must support 14 different character sets. We support these across a variety of different separate fonts.
2. Language Select - We need to show multiple different fonts on this page.

2 is fairly trivial to solve as we could just have a bunch of fixed pre-rendered bitmap images instead of fonts as there is a finite set of languages we support so we can bake the text into an image. However, we don't have any solution for gamertags.
jsantos

jsantos

2022-09-23 10:13

manager   ~0008079

As a temporary workaround, could you strip glyph ranges from needed fonts? Just to save time before we can properly implement this.

Are you 100% sure this would break the copyright of the font?
steveh

steveh

2022-09-23 10:57

reporter   ~0008080

Hey Jesús, I thought from the initial look we were not allowed to make any modifications to the fonts at all. I'll ask our producers to run it through our lawyers though to double check.
jsantos

jsantos

2022-09-23 11:18

manager   ~0008081

Thank you. Meanwhile let me see if I find an easier way to quickly implement this. I am thinking about adding a range to each font (manually).
jsantos

jsantos

2023-01-19 14:22

manager   ~0008227

Hi Steve, I hope everything is great over there.

Did you get an answer from the producers?

We have been really busy closing 3.2 BETA 1 (https://www.noesisengine.com/docs/3.2/Gui.Core.Changelog.html) and we have imminent plans to release BETA 2. After that, I think I can find time to offer you solutions for this.

Should we have a call?
steveh

steveh

2023-01-19 15:22

reporter   ~0008229

Hey Jesús, everything's going great thank you, hope the 3.2 release is going smoothly.

I got the initial okay from our production staff but they were planning on checking with legal before we commit to that, I've not heard back. I've not solved this yet, so it's still going to be an issue. I'll give our production a prod now to chase up with legal again.

If we need to specify a range for each font then that sounds fine to me and would be a find solution. I'm happy to join a call if you think that's easier. Just drop me an email with a desired time and I'm happy to jump on a call.

Cheers,

-Steven
jsantos

jsantos

2023-06-28 14:32

manager   ~0008570

Last edited: 2023-06-28 14:33

I am attaching the "hacky" solution for now. There are two important changes in the patch:

1) HarfBuzz.h and HarfBuzz.cpp are exposing now a new HB::Shape function with ranges. These changes will be part of Noesis

2) The rest of changes are experimental, we have added a vector of ranges to VGLFontFace and the layout is using that vector when calling HB::Shape. You need to manually detect the fonts by name and adjust the needed ranges

If everything is working fine with these change we will keep working on a better solution by implementing the property FontFamily.FontFamilyMap. This will support scenarios like this:

<Grid
  xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
  xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
  xmlns:System="clr-namespace:System;assembly=mscorlib">
 
  <Grid.Resources>
    <FontFamily x:Key="TheFont">
   
        <FontFamily.FamilyMaps>  
            <FontFamilyMap
                Unicode="0048"
                Target="Times New Roman"
                Scale="1.0" />

            <FontFamilyMap
                Unicode="0000-052F"
                Target="Comic Sans MS, Verdana"
                Scale="4.0" />    
        </FontFamily.FamilyMaps>    
    </FontFamily>
  </Grid.Resources>
 
  <TextBlock Text="Hello World!" FontFamily="{StaticResource TheFont}" HorizontalAlignment="Center" VerticalAlignment="Center"/>
 
</Grid>

UnicodeRange.patch (6,408 bytes)   
Index: Drawing/VGL/Inc/NsDrawing/VGLFontFace.h
===================================================================
--- Drawing/VGL/Inc/NsDrawing/VGLFontFace.h	(revision 12570)
+++ Drawing/VGL/Inc/NsDrawing/VGLFontFace.h	(working copy)
@@ -58,6 +58,7 @@
     uint32_t SpaceIndex() const { NS_ASSERT(mHandle); return mSpaceIndex; }
 
     ArrayRef<uint32_t> Palette() const { NS_ASSERT(mHandle); return mPalette; }
+    ArrayRef<UnicodeRange> Ranges() const { NS_ASSERT(mHandle); return mRanges; }
 
 private:
     void LoadInternal();
@@ -86,6 +87,7 @@
     float mEllipsisWidth;
 
     Vector<uint32_t> mPalette;
+    Vector<UnicodeRange> mRanges;
 };
 
 NS_WARNING_POP
Index: Drawing/VGL/Src/HarfBuzz.cpp
===================================================================
--- Drawing/VGL/Src/HarfBuzz.cpp	(revision 12570)
+++ Drawing/VGL/Src/HarfBuzz.cpp	(working copy)
@@ -224,12 +224,54 @@
 }
 
 ////////////////////////////////////////////////////////////////////////////////////////////////////
+static bool InRange(uint32_t ch, const UnicodeRange* ranges, uint32_t numRanges)
+{
+    for (uint32_t i = 0; i < numRanges; i++)
+    {
+        const UnicodeRange& r = ranges[i];
+
+        if (ch - r.fist <= r.size)
+        {
+            return true;
+        }
+    }
+
+    return false;
+}
+
+static thread_local const UnicodeRange* gUnicodeRanges;
+static thread_local uint32_t gUnicodeRangesSize;
+
+////////////////////////////////////////////////////////////////////////////////////////////////////
+static hb_bool_t GetGlyphInRange(hb_font_t* font, void* data, hb_codepoint_t unicode,
+    hb_codepoint_t* glyph, void *user)
+{
+    uint32_t numRanges = gUnicodeRangesSize;
+
+    if (numRanges == 0 || InRange(unicode, gUnicodeRanges, numRanges))
+    {
+        return hb_ot_get_nominal_glyph(font, data, unicode, glyph, user);
+    }
+
+    return false;
+}
+
+////////////////////////////////////////////////////////////////////////////////////////////////////
 void* HB::FontCreate(void* blob, uint32_t index)
 {
     NS_ASSERT(blob != nullptr);
     hb_face_t* face = hb_face_create((hb_blob_t*)blob, index);
-    hb_font_t* font = hb_font_create(face);
+    hb_font_t* parent = hb_font_create(face);
     hb_face_destroy(face);
+
+    hb_font_t* font = hb_font_create_sub_font(parent);
+    hb_font_destroy(parent);
+
+    hb_font_funcs_t* funcs = hb_font_funcs_create();
+    hb_font_funcs_set_nominal_glyph_func(funcs, GetGlyphInRange, nullptr, nullptr);
+    hb_font_set_funcs(font, funcs, &face->table, nullptr);
+    hb_font_funcs_destroy(funcs);
+
     return font;
 }
 
@@ -965,12 +1007,18 @@
 }
 
 ////////////////////////////////////////////////////////////////////////////////////////////////////
-void HB::Shape(void* font, void* buffer, const FontFeatures& features)
+void HB::Shape(void* font, void* buffer, const FontFeatures& features, const UnicodeRange* ranges,
+    uint32_t numRanges)
 {
     Vector<hb_feature_t, 16> v;
     FillFeatures(features, v);
 
+    gUnicodeRanges = ranges;
+    gUnicodeRangesSize = numRanges;
+
     hb_shape((hb_font_t*)font, (hb_buffer_t*)buffer, v.Data(), v.Size());
+
+    gUnicodeRangesSize = 0;
 }
 
 ////////////////////////////////////////////////////////////////////////////////////////////////////
Index: Drawing/VGL/Src/HarfBuzz.h
===================================================================
--- Drawing/VGL/Src/HarfBuzz.h	(revision 12570)
+++ Drawing/VGL/Src/HarfBuzz.h	(working copy)
@@ -18,6 +18,7 @@
 
 class Stream;
 struct FontFeatures;
+struct UnicodeRange;
 enum FontWeight : int32_t;
 enum FontStyle : int32_t;
 enum FontStretch : int32_t;
@@ -76,7 +77,8 @@
         uint32_t offset, uint32_t length, uint32_t script, bool rtl, void* language);
     static void BufferDestroy(void* buffer);
 
-    static void Shape(void* font, void* buffer, const FontFeatures& features);
+    static void Shape(void* font, void* buffer, const FontFeatures& features,
+        const UnicodeRange* ranges, uint32_t numRanges);
 
     static uint32_t BufferGetGlyphCount(void* buffer);
     static void BufferGetGlyph(void* buffer, uint32_t index, uint32_t& glyph, uint32_t& cluster,
Index: Drawing/VGL/Src/ShapingFull.h
===================================================================
--- Drawing/VGL/Src/ShapingFull.h	(revision 12570)
+++ Drawing/VGL/Src/ShapingFull.h	(working copy)
@@ -541,10 +541,12 @@
 
     HB::BufferSetContent(buffer, chars, numChars, start, len, script, rtl, lang);
 
-    properties->faces[faceIndex]->Load();
-    void* font = properties->faces[faceIndex]->Handle();
-    HB::Shape(font, buffer, properties->features);
+    VGLFontFace* face = properties->faces[faceIndex];
+    face->Load();
 
+    ArrayRef<UnicodeRange> ranges = face->Ranges();
+    HB::Shape(face->Handle(), buffer, properties->features, ranges.Data(), ranges.Size());
+
     uint32_t numGlyphs = HB::BufferGetGlyphCount(buffer);
 
     // New glyphs replace the [replacePos, replacePos + replaceSize] range
Index: Drawing/VGL/Src/VGLFontFace.cpp
===================================================================
--- Drawing/VGL/Src/VGLFontFace.cpp	(revision 12570)
+++ Drawing/VGL/Src/VGLFontFace.cpp	(working copy)
@@ -59,6 +59,17 @@
 VGLFontFace::VGLFontFace(const char* uri, Stream* stream, uint32_t index):
     mUriHash(StrCaseHash(uri)), mStream(stream), mIndex(index), mHandle(0)
 {
+    //if (StrEquals(uri, "#Aero Matics"))
+    //{
+    //    {
+    //        UnicodeRange range{ 'H', 1 };
+    //        mRanges.PushBack(range);
+    //    }
+    //    {
+    //        UnicodeRange range{ 'e', 1 };
+    //        mRanges.PushBack(range);
+    //    }
+    //}
 }
 
 ////////////////////////////////////////////////////////////////////////////////////////////////////
Index: Gui/Providers/Include/NsGui/FontProperties.h
===================================================================
--- Gui/Providers/Include/NsGui/FontProperties.h	(revision 12570)
+++ Gui/Providers/Include/NsGui/FontProperties.h	(working copy)
@@ -61,6 +61,12 @@
     FontStretch_UltraExpanded = 9
 };
 
+struct UnicodeRange
+{
+    uint32_t fist;
+    uint32_t size;
+};
+
 }
 
 NS_DECLARE_REFLECTION_ENUM_EXPORT(NS_GUI_PROVIDERS_API, Noesis::FontWeight)
UnicodeRange.patch (6,408 bytes)   
Demond

Demond

2024-02-25 06:52

reporter   ~0009237

Good afternoon It’s good that I came across this ticket before creating a post requesting this function)) I want to make a composite font in which “extra” glyphs will be removed and number glyphs from another font will be added. It would also be a good idea to experiment with glyphs for different languages. How soon will FontFamilyMap support be added?
jsantos

jsantos

2024-02-26 11:12

manager   ~0009238

After the first releases of the Studio we will find time to work on this feature.

Issue History

Date Modified Username Field Change
2022-07-14 11:45 steveh New Issue
2022-07-14 11:52 steveh Description Updated
2022-07-14 16:27 jsantos Assigned To => jsantos
2022-07-14 16:27 jsantos Status new => assigned
2022-07-14 16:27 jsantos Target Version => 3.2.0
2022-07-14 16:30 jsantos Note Added: 0008018
2022-09-21 14:01 jsantos Note Added: 0008077
2022-09-21 14:01 jsantos Status assigned => feedback
2022-09-21 14:51 steveh Note Added: 0008078
2022-09-21 14:51 steveh Status feedback => assigned
2022-09-23 10:13 jsantos Note Added: 0008079
2022-09-23 10:14 jsantos Status assigned => feedback
2022-09-23 10:57 steveh Note Added: 0008080
2022-09-23 10:57 steveh Status feedback => assigned
2022-09-23 11:18 jsantos Note Added: 0008081
2023-01-19 14:22 jsantos Note Added: 0008227
2023-01-19 14:22 jsantos Status assigned => feedback
2023-01-19 15:22 steveh Note Added: 0008229
2023-01-19 15:22 steveh Status feedback => assigned
2023-03-27 12:14 jsantos Target Version 3.2.0 => 3.2
2023-06-28 14:32 jsantos Note Added: 0008570
2023-06-28 14:32 jsantos File Added: UnicodeRange.patch
2023-06-28 14:32 jsantos Status assigned => feedback
2023-06-28 14:33 jsantos Note Edited: 0008570
2023-06-28 14:33 jsantos Note Edited: 0008570
2024-02-25 06:52 Demond Note Added: 0009237
2024-02-26 11:12 jsantos Note Added: 0009238