RazrFalcon / resvg

An SVG rendering library.
Mozilla Public License 2.0
2.73k stars 220 forks source link

Support font synthesis? #297

Open ferdnyc opened 4 years ago

ferdnyc commented 4 years ago

As you know (OpenShot/openshot-qt#3462) we're using resvg in OpenShot to render our SVG title templates. One of the things that's been causing issues for some of our users is font styling, specifically the inability to apply certain styles to the text and have it render as expected.

A major barrier to meeting their needs on that front seems to be the lack of support for synthesized font styles. Here's a simple reproducer. Take this complete SVG code:

<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<svg
xmlns:dc="http://purl.org/dc/elements/1.1/"
xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
xmlns:svg="http://www.w3.org/2000/svg"
xmlns="http://www.w3.org/2000/svg"
width="105mm" height="18mm" viewBox="0 0 105 18" version="1.1" id="svg8">
<g style="image-rendering:auto" id="layer1">
<text id="text12" y="15" x="53" xml:space="preserve"
 style="font-style:normal;font-variant:normal;font-weight:normal;
font-stretch:normal;font-size:12pt;line-height:125%;
font-family:Impact;text-align:center;text-anchor:middle;fill:#000000;
fill-opacity:1;stroke:none;stroke-opacity:1;font-synthesis:style weight;">
<tspan id="tspan147"
 style="font-style:italic">A bold design.</tspan></text>
</g></svg>

When viewed in Chrome or Firefox (at least), this will render as the text "A bold design." in 12-point italic Impact font. When run through ImageMagick's convert -background none drawing.svg drawing.png (using Inkscape as the rendering delegate), it will produce the following image:

drawing

But, when converted by rendersvg or viewed in viewsvg, you get this:

drawing_rendersvg

The rub here is that the Impact typeface does not have an italic font. There is no way to download Impact Italic as a TTF or OTF font file (at least, not an official one), and if you open the "Text and Font" options pane in Inkscape, the only Style that will normally come up is "Condensed Regular".

However, if you enter a line of Impact text in Inkscape, then hit Ctrl+i, the text will be italicized nonetheless. And if you were to save that to SVG and later re-load it into Inkscape, you'd see that "Italic" has been added as a Style option for Impact.

The key to this magic is a feature supported by some (most?) browsers, and which is configurable (in some cases) via the CSS property font-synthesis (Mozilla documentation). The documented CSS Fonts Module Level 3 defaults for the property are style weight, meaning both Bold and Italic variations can be created for any typeface that lacks them — which is why the synthetic variations are also accessible on-demand from Inkscape. However, this does not appear to be supported by resvg, which seems to render fonts effectively as though style="font-synthesis:none;" has been specified in all cases.

This is especially a problem for us, because we allow users to select their title font using Qt's font dialog — either the native platform one, if available, or the fallback built-in one. In either case that dialog (unlike Inkscape's Text and Font panel), knowing that synthesis is normally supported by the font renderer, will present the user with Bold and/or Italic style selections for any font, even the ones which don't support it natively. This then understandably confuses users when their text isn't rendered in the style they choose... but then when they switch typefaces, suddenly it is. I have not found any way to configure the font dialog(s) to display only intrinsic, and not synthesized, font options.

Is there any possibility of supporting synthesized type styles in resvg?

RazrFalcon commented 4 years ago

Yes, looks like resvg is the only one. Mainly because we are using our own text engine.

In theory, in should be fairly trivial to implement.

Interestingly, different application use a slightly different methods (diff is relative to chrome):

pseudo-italic

ferdnyc commented 4 years ago

As a tangentially-related point of interest here, it seems Blender will be gaining font synthesis support in the future 2.90 release — a welcome enhancement since Blender's font support has long been charitably described as "abysmal". (Even if those limitations are more than understandable, given that rendering text in Blender requires it to perform a depth extrusion and generate a solid 3D model mesh for each glyph in the text.)

One outcome of this change is that it makes their implementation available for perusal and blatant stealing emulation.

It seems that Blender's italics synthesis will involve a 6° top-right shear transform:

@blender/blenfont/intern/blf_glyph.c:295

  /* Do not oblique a font that is designed to be italic! */
  if (((font->flags & BLF_ITALIC) != 0) && !(font->face->style_flags & FT_STYLE_FLAG_ITALIC) &&
      (font->face->glyph->format == FT_GLYPH_FORMAT_OUTLINE)) {
    /* For (fake) italic: a shear transform with a 6 degree angle. */
    FT_Matrix transform;
    transform.xx = 0x10000L;
    transform.yx = 0x00000L;
    transform.xy = 0x03000L;
    transform.yy = 0x10000L;
    FT_Outline_Transform(&font->face->glyph->outline, &transform);
  }

Whereas bold synthesis involves expanding the glyph with 1/28 extra height and 1/14 extra width. Then either the advance is increased by 5% (proportional fonts), or the glyph is shifted leftward by 50% of the width increase, to recenter it (fixed-width):

@blender/blenfont/intern/blf_glyph.c:307

  /* Do not embolden an already bold font! */
  if (((font->flags & BLF_BOLD) != 0) &&
      !(font->face->style_flags & FT_STYLE_FLAG_BOLD) &
          (font->face->glyph->format == FT_GLYPH_FORMAT_OUTLINE)) {
    /* Strengthen the width more than the height. */
    const FT_Pos extra_x = FT_MulFix(font->face->units_per_EM, font->face->size->metrics.x_scale) /
                           14;
    const FT_Pos extra_y = FT_MulFix(font->face->units_per_EM, font->face->size->metrics.y_scale) /
                           28;
    FT_Outline_EmboldenXY(&font->face->glyph->outline, extra_x, extra_y);
    if ((font->face->face_flags & FT_FACE_FLAG_FIXED_WIDTH) == 0) {
      /* Need to increase advance, but not for fixed-width fonts. */
      font->face->glyph->advance.x += (FT_Pos)(((float)extra_x) * 1.05f);
      font->face->glyph->advance.y += extra_y;
    }
    else {
      /* Widened fixed-pitch font gets a nudge left. */
      FT_Outline_Translate(&font->face->glyph->outline, (extra_x / -2), 0);
    }
  }

(Wait, no, for proportional fonts make that, "the advance is increased by 5% of the added width." That seems... insufficient? Maybe I'm reading that wrong.) Aha: The advance is increased by 105% of the added width. (Duh!)

...Admittedly, handwaving away the implementation of freetype's FT_MulFix() and FT_Outline_EmboldenXY() sort of downplays the actual amount of code involved, too.

RazrFalcon commented 4 years ago

Thanks. I hope I will find time to work on this feature in the near future.