toptensoftware / RichTextKit

Rich text rendering for SkiaSharp
Other
366 stars 73 forks source link

Google font synthesis? #17

Open Jonesie opened 4 years ago

Jonesie commented 4 years ago

Hi. We are using google fonts. I cant figure out how to get bold or italics to work. In the browser, these are synthesised from regular. Ive tried loading a specific bold font into the style, but it still comes out at 400. I would rather the font was faked to match the browser (not all google fonts come with italic and bold).

Is this possible? I read that Chrome uses Skia for this so maybe this is not part of SkiaSharp?

Same issue on windows and linux.

toptensoftware commented 4 years ago

Thanks for reporting this. I've always just used fonts with the various weights/italics available and served them up to RichTextKit with a custom font mapper.

I'm not sure how to simulate bold/italic when not available in the font directly - perhaps this is built into Skia but I don't know and would have to research it. (If you figure it out, please let me know)

For reference here's the important parts of my font mapper from my UI toolkit:

    /// <summary>
    /// Handles mapping of font family names to SKTypefaces
    /// </summary>
    public class FontMapper : Topten.RichTextKit.FontMapper
    {
        /// <summary>
        /// Loads a private font from a stream
        /// </summary>
        /// <param name="stream">The stream to load from</param>
        /// <param name="familyName">An optional family name to override the font's built in name</param>
        /// <returns>True if the font was successully loaded</returns>
        public static bool LoadPrivateFont(System.IO.Stream stream, string familyName=null)
        {
            var tf = SKTypeface.FromStream(stream);
            if (tf == null)
                return false;

            var qualifiedName = familyName ?? tf.FamilyName;
            if (tf.FontSlant != SKFontStyleSlant.Upright)
            {
                qualifiedName += "-Italic";
            }

            // Get a list of typefaces with this family
            if (!_customFonts.TryGetValue(qualifiedName, out var listFonts))
            {
                listFonts = new List<SKTypeface>();
                _customFonts[qualifiedName] = listFonts;
            }

            // Add to the list
            listFonts.Add(tf);

            return true;
        }

        /// <summary>
        /// Map a RichTextKit style to an SKTypeface
        /// </summary>
        /// <param name="style">The style</param>
        /// <param name="ignoreFontVariants">True to ignore variants (super/subscript)</param>
        /// <returns>The mapped typeface</returns>
        public override SKTypeface TypefaceFromStyle(IStyle style, bool ignoreFontVariants)
        {
            // Work out the qualified name
            var qualifiedName = style.FontFamily;
            if (style.FontItalic)
                qualifiedName += "-Italic";

            // Look up custom fonts
            List<SKTypeface> listFonts;
            if (_customFonts.TryGetValue(qualifiedName, out listFonts))
            {
                // Find closest weight
                return listFonts.MinBy(x => Math.Abs(x.FontWeight - style.FontWeight));
            }

            // Do default mapping
            return base.TypefaceFromStyle(style, ignoreFontVariants);
        }

        static FontMapper()
        {
            // Install self as the default RichTextKit font mapper
            Topten.RichTextKit.FontMapper.Default = new FontMapper();
        }

        // Constructor
        private FontMapper()
        {
        }

        static Dictionary<string, List<SKTypeface>> _customFonts = new Dictionary<string, List<SKTypeface>>();
}
Jonesie commented 4 years ago

I had a stupid error in my font loading so now at least it selects the correct font variants and I get bold and/or italic - BUT, only if the google font supports it. To simulate/synthesize these for the other fonts, it needs to look at the typeface that is created from the stream and change the paint stroke weight and skew - I think. I'll fork and see if I can mangle that :)

Jonesie commented 4 years ago

This is well above my pay grade, but I think FontRun.cs is the place to start:

image

After creating the font, it needs to check that the typeface supports the requested styles and if not, create a SKPaint with stroke weight and skew to simulate it.

I cant see where SKTextBlob.CreatePositioned() comes from. It's not in the SkiaSharp docs.

toptensoftware commented 4 years ago

Hi @Jonesie,

I haven't had a chance to dive into this myself, but I noticed when updating to the latest skia sharp that SKFont has a method "Embolden" which maps to this:

Increases stroke width when creating glyph bitmaps to approximate a bold typeface.

That sounds like it would handle the visual side of it however since RichTextKit uses HarfBuzz to actually place the glyphs presumably that would also need to be updated to be place each glyph slightly further apart.

Also, I noticed setSkewX which could probably be used to simulate italic (and shouldn't require updating the glyph placement)

Brad

Gillibald commented 4 years ago

Setting isEmbolden and setSkewX should work. But keep in mind that these values don't effect harfbuzz during the shaping process. You need to register font function callbacks to make this work.

toptensoftware commented 4 years ago

Hi @Gillibald ,

Thanks. I wasn't aware of the font function callbacks... that's interesting. I wonder if emboldening a SKFont causes it's returned font metrics to be updated. I guess that's my main question - after emboldening a font how to get it's new metrics.

Am I correct in assuming the simulating italics shouldn't require any change in the shaping? The characters are just drawn sloped but at their same shaped positions.

Brad

Gillibald commented 4 years ago

That assumption is right. Advances stay the same just the combined bounds of your shaped text change.