New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Custom font support #1564

Merged
merged 44 commits into from May 24, 2018

Conversation

Projects
None yet
4 participants
@Gillibald
Contributor

Gillibald commented May 7, 2018

  • What does the pull request do?
    It introduces support for loading fonts from embedded resources.
  • What is the current behavior?
    Only system installed fonts are supported.
  • What is the updated/expected behavior with this PR?
    You can now define a FontFamily with resource syntax
  • How was the solution implemented (if it's not obvious)?
    To speed up loading times and save memory resources are cached. Items of that cache are idetified by a FontFamilyKey that holds a location and optional filename. If you define several FontFamilies with same location and filename it only loads resources once.

To define a FontFamily you can use the commonly used resm scheme. If you point to a resource folder all containing fonts are automaticly discovered. It also introduces filename globbing or so called wildcard support of filenames. That way you can define a FontFamily and automaticly lookup all typefaces in one line.

You can also define fallbacks for your FontFamily. If a font isnt installed on the system, the logic tries to use the fallback values you defined for your FontFamily to render the text.

Some examples of syntax:

Installed font
FontFamily="Arial"
Installed font with fallbacks
FontFamily="Arial, Times New Roman, Comic Sans MS"
All font files in a folder
FontFamily="resm:DefaultNamespace.Fonts#MyFont"
Single font file
FontFamily="resm:DefaultNamespace.Fonts.MyFont.ttf#MyFont"
All font files in a folder that match expression
FontFamily="resm:DefaultNamespace.Fonts.MyFont*.ttf#MyFont"

If your font resources are not in your main assembly you have to define the assembly attribute in your uri strings.

To use specific font weights or styles make sure your font is supporting them. If you want to have support for multiple typefaces you have to add them by folder. Your resources would look like this:

DefaultNamespace.Fonts.MyFont.MyFont_Regular.ttf
DefaultNamespace.Fonts.MyFont.MyFont_Bold.ttf
DefaultNamespace.Fonts.MyFont.MyFont_Italic.ttf
DefaultNamespace.Fonts.MyFont.MyFont_Italic_Bold.ttf
etc.

Then you can use them like so:

FontFamily="resm:DefaultNamespace.Fonts.MyFont#MyFont"
FontFamily="resm:DefaultNamespace.Fonts.MyFont#MyFont" FontWeight="Bold"
FontFamily="resm:DefaultNamespace.Fonts.MyFont#MyFont" FontStyle="Italic"
FontFamily="resm:DefaultNamespace.Fonts.MyFont#MyFont" FontWeight="Bold" FontStyle="Italic"

I noticed some ui freaze running this under DotNetCore that is gone after everything is loaded. Have to figure out what is causing this.

With this approach we could also support loading fonts from filesystem. Let me know if thats a wanted feature or if we should introduce that later.

Checklist:

  • Added unit tests (if possible)?
  • Added XML documentation to any related classes?

See #1313
See #1564
See #810

Gillibald added some commits Apr 30, 2018

@danwalmsley

This comment has been minimized.

Member

danwalmsley commented May 8, 2018

@Gillibald great work, i'll try and test this shortly.

Any ideas on how we can support font family lists:

(from msft docs)
fontFamilyNamesList
A string specifying multiple font family names, each separated by a comma (any whitespace following a comma is ignored). The first font family specified serves as the primary font family; subsequent font families serve as fallback families to be used in cases where the primary font family is unavailable or not applicable. For example, "Arial, Century Gothic" specifies Arial as the primary font family, with Century Gothic as the fallback font family.

i.e.

FontFamily="Arial, Verdana, Comic Sans Ms"
@Gillibald

This comment has been minimized.

Contributor

Gillibald commented May 8, 2018

To support a fallback for FontFamily we have to change FontFamily's Name to represent a FontFamilyNamesList. Then we just have to search for installed fonts and if a font is not present we try the next. If no fontname is present we sill use the default font. Skia and DirectWrite already expose a list of installed fonts so we could use that and probably make that accessible threw a SystemFontsCollection. Custom fonts don't need fallbacks so we just use a single name there.

@danwalmsley

This comment has been minimized.

Member

danwalmsley commented May 8, 2018

I think your PR should change the api of: TextFormatterFactory.CreateFormattedText to accept FontFamily instead of string for name of fontfamily?

After testing your PR I'm having to change that api in lots of places.

@Gillibald

This comment has been minimized.

Contributor

Gillibald commented May 8, 2018

In addition to use a fallback for non present fonts we could use a fallback in case a specific typeface isn't present. For example if we set FontWeight to Bold and the main FontFamily has no face to represent that setting we could check if the fallback has one and use that. Not sure how to cache that properly but should be doable.

@danwalmsley

This comment has been minimized.

Member

danwalmsley commented May 8, 2018

I'm getting System.MissingFieldException: 'Field not found: 'Avalonia.Controls.Primitives.TemplatedControl.FontFamilyProperty'.' maybe I didn't build something right!

@Gillibald

This comment has been minimized.

Contributor

Gillibald commented May 8, 2018

Will have a look at TextFormatterFactory.CreateFormattedText
This PR is tested with DirectD21 and Skia backend so it should just work. Even styles with FontFamily definitions should just work.

@danwalmsley

This comment has been minimized.

Member

danwalmsley commented May 8, 2018

Probably just add an extra overload that takes in fontfamily so we don't break existing api
TextFormatterFactory.CreateFormattedText

@Gillibald

This comment has been minimized.

Contributor

Gillibald commented May 8, 2018

Are you sure that TextFormatterFactory.CreateFormattedText is part of this project?

@danwalmsley

This comment has been minimized.

Member

danwalmsley commented May 8, 2018

oops sorry! I was looking at avalonia edit code.

@danwalmsley

This comment has been minimized.

Member

danwalmsley commented May 8, 2018

Note: This PR will not be binary compatible due to change of FontFamily property type.

@Gillibald

This comment has been minimized.

Contributor

Gillibald commented May 8, 2018

Not to confuse people i will remove the res scheme support. This can be added in the future if referencing of resources changes.

Gillibald added some commits May 20, 2018

IAssetLoader changes
Rename resource to asset
@Gillibald

This comment has been minimized.

Contributor

Gillibald commented May 20, 2018

I have added a GetAssets method to IAssetLoader and renamed resource to asset to keep the naming consistent. The changes to IAssetLoader should probably have its own PR. Will do that shortly and will revert the changes I made until that got merged.

@danwalmsley

This comment has been minimized.

Member

danwalmsley commented May 20, 2018

I will have a look soon. 😀

@danwalmsley

Some minor nits

@@ -83,7 +83,7 @@ public bool Exists(Uri uri, Uri baseUri = null)
/// <exception cref="FileNotFoundException">
/// The resource was not found.
/// </exception>
public Tuple<Stream, Assembly> OpenAndGetAssembly(Uri uri, Uri baseUri = null)
public (Stream Stream, Assembly Assembly) OpenAndGetAssembly(Uri uri, Uri baseUri = null)

This comment has been minimized.

@danwalmsley

danwalmsley May 21, 2018

Member

Nit:
Could we use camelCase for tuple values. It would look odd when a tuple is deconstructed and you end up with locals that start with capitals.

This comment has been minimized.

@Gillibald

Gillibald May 21, 2018

Contributor

I will change that.

}

This comment has been minimized.

@danwalmsley

danwalmsley May 21, 2018

Member

unnecessary linespace.

_stream.Position = fileOffset;
fragmentStart = _stream.PositionPointer;

This comment has been minimized.

@danwalmsley

danwalmsley May 21, 2018

Member

Nit: unnecessary line space

/// Implicit conversion of FontFamily to string
/// </summary>
/// <param name="fontFamily"></param>
public static implicit operator string(FontFamily fontFamily)

This comment has been minimized.

@danwalmsley

danwalmsley May 21, 2018

Member

I think this should have been the other way around, i.e. string to FontFamily.

@grokys

Looking really good! Just one question and one change I think we need.

@@ -10,5 +10,7 @@
[assembly: XmlnsDefinition("https://github.com/avaloniaui", "Avalonia.Media")]
[assembly: XmlnsDefinition("https://github.com/avaloniaui", "Avalonia")]
[assembly: InternalsVisibleTo("Avalonia.Direct2D1")]

This comment has been minimized.

@grokys

grokys May 21, 2018

Member

We shouldn't really be doing this - it should be possible for people to write their own rendering backends without us adding an InternalsVisibleTo attribute.

/// Initializes a new instance of the <see cref="FontAsset"/> class.
/// </summary>
/// <param name="source">The source.</param>
public FontAsset(Uri source)

This comment has been minimized.

@grokys

grokys May 21, 2018

Member

This class just contains a single Uri - why is this abstraction needed? Why not just pass a Uri?

Gillibald added some commits May 21, 2018

@Gillibald

This comment has been minimized.

Contributor

Gillibald commented May 21, 2018

Dont know if I am happy with IFontFamily. Pls let me know your thoughts.

@Gillibald Gillibald referenced this pull request May 23, 2018

Merged

AssetLoader GetAssets #1608

1 of 2 tasks complete

Gillibald added some commits May 23, 2018

New asset loader
Added unit test for wildcard
@danwalmsley

LGTM 👍

@grokys

Hey, I don't understand the IFontFamily changes - I know we talked about this in gitter and I thought that it was supposed to hide some implementation details, but I can't see how it's doing that here.

@@ -11,6 +11,18 @@
</EmbeddedResource>
<EmbeddedResource Include="Assets\*" />
</ItemGroup>
<ItemGroup>
<None Remove="Assets\Fonts\SourceSansPro-Bold.ttf" />

This comment has been minimized.

@grokys

grokys May 23, 2018

Member

Why are these <None Remove> items needed?

This comment has been minimized.

@Gillibald

Gillibald May 23, 2018

Contributor

I have no idea why visual studio added them. They are not needed.

public string Name => FamilyNames.PrimaryFamilyName;
FamilyNameCollection IFontFamily.FamilyNames => FamilyNames;

This comment has been minimized.

@grokys

grokys May 23, 2018

Member

I don't understand this: IFontFamily exposes Name, FamilyNames and Key which are implemented here as explicit interface methods and forwarded to internal properties? Why not just make these properties public? That would have the same effect, no?

And why is IFontFamily needed in that case? It doesn't look like there can be any other implementation of it, as the TextBlock.FontFamily property is of type FontFamily. What use-case would having another implementation of FontFamily solve?

This comment has been minimized.

@Gillibald

Gillibald May 23, 2018

Contributor

Have removed the interface. It was ment to hide FamilyNames and Key for 3rd Party. The internal properties just made it easier to access them without casting.

Gillibald and others added some commits May 23, 2018

Removed IFontFamily
Some code style fixes
@grokys

grokys approved these changes May 24, 2018

@danwalmsley danwalmsley merged commit 06f4840 into AvaloniaUI:master May 24, 2018

2 checks passed

continuous-integration/appveyor/pr AppVeyor build succeeded
Details
continuous-integration/travis-ci/pr The Travis CI build passed
Details

@johannesegger johannesegger referenced this pull request Jun 5, 2018

Closed

Unicode support #1649

@Gillibald Gillibald deleted the Gillibald:feature/customFonts branch Jun 5, 2018

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment