Documentation page: CSS Font Features: Ligatures, Small Caps, Figures and OpenType Typography. This page is also available as Markdown at css-font-features-and-opentype-typography.md.

CSS Font Features: Ligatures, Small Caps, Figures and OpenType Typography

OpenType fonts hold more than one glyph per character: ligatures, real small capitals, old-style and tabular figures, fractions, ordinals, swashes, stylistic sets, the letter forms of a language. CSS chooses among them with font-variant-*, font-feature-settings and the lang attribute. PD4ML applies the font's substitutions itself, as Chrome does, so the PDF shows the typography the stylesheet asks for.

h1     { font-variant-caps: small-caps; }
.price { font-variant-numeric: oldstyle-nums tabular-nums; }
.intro { font-variant-ligatures: discretionary-ligatures; }

Available from PD4ML v4.2.0, with the v42 layout engine: call useHtmlRenderer(Constants.LayoutEngine.v42) before readHTML(). The v40 engine applies only a font's default ligatures (with enableLigatures(true)).

PD4ML pd4ml = new PD4ML();
pd4ml.useHtmlRenderer(Constants.LayoutEngine.v42);
pd4ml.readHTML(new URL("file:report.html"));
pd4ml.writePDF(new FileOutputStream("report.pdf"));

The features come from the font: they work with fonts that have them (an OpenType GSUB table), loaded with @font-face or from the configured font set. A font without small capitals or fractions draws the text as it is, apart from the fallbacks below. The screenshots on this page are PD4ML output of the sample document.

What is supported

Property Values OpenType features
font-variant-ligatures normal, none, [no-]common-ligatures, [no-]discretionary-ligatures, [no-]historical-ligatures, [no-]contextual liga clig, dlig, hlig, calt
font-variant-caps small-caps, all-small-caps, petite-caps, all-petite-caps, unicase, titling-caps smcp, c2sc, pcap, c2pc, unic, titl
font-variant-numeric lining-nums, oldstyle-nums, proportional-nums, tabular-nums, diagonal-fractions, stacked-fractions, ordinal, slashed-zero lnum, onum, pnum, tnum, frac, afrc, ordn, zero
font-variant-position super, sub sups, subs
font-variant-east-asian jis78 ... jis04, simplified, traditional, full-width, proportional-width, ruby jp78 ... jp04, smpl, trad, fwid, pwid, ruby
font-variant-alternates historical-forms, stylistic(), styleset(), character-variant(), swash(), ornaments(), annotation() hist, salt, ss01-ss20, cv01-cv99, swsh, ornm, nalt
@font-feature-values names for the font-variant-alternates functions, per font family
font-variant shorthand of the above (and the font shorthand resets them)
font-feature-settings any feature tags, "smcp", "ss03" 1, "liga" off any
font-kerning auto, normal, none kern (pair kerning)
lang, font-language-override a language (lang="sr"), an OpenType language tag ("SRB") locl
font-synthesis weight, style, small-caps, none (and the font-synthesis-* longhands)
text-transform full-width, full-size-kana (with uppercase, lowercase, capitalize)

Every text also gets the font's default features: ccmp, locl, rlig, rclt, liga, clig and calt - as in browsers, common ligatures are on unless font-variant-ligatures turns them off.

Ligatures and capitals: the same text without and with font-variant-ligatures: none, discretionary ligatures (ct, st), historical forms (the long s), small caps, all-small-caps, petite caps, unicase and synthesized small caps

Figures and alternates: old-style and tabular figures, diagonal fractions, ordinals, a slashed zero, superscript and subscript glyphs, a swash, stylistic sets, font-feature-settings and the font-variant shorthand

The PDF keeps the text: a ligature, a small capital or a fraction glyph copies as the characters it stands for, and searching the PDF finds them. (Capitals and lowercase letters that share small-capital glyphs copy as one of the two.)

Fallbacks and synthesis

PD4ML follows Chrome where a font lacks the glyphs a feature asks for:

  • Small caps: without smcp, small capitals are synthesized from the capitals at 70% of the size; font-synthesis: none (or font-synthesis-small-caps: none) turns that off.
  • Petite caps fall back to the font's small caps (pcap to smcp, c2pc to c2sc), then to synthesized small caps; unicase without unic uses the font's small capitals of the capitals (c2sc).
  • Titling caps and superscripts/subscripts (font-variant-position) are not synthesized: without the glyphs the text stays as it is. For raised and lowered text use vertical-align: super / sub or <sup> / <sub>.
  • Bold and italic: a font family without a bold or an italic face gets one synthesized; font-synthesis: none (or leaving weight / style out of it) draws the regular face instead.

Languages

The lang attribute (or xml:lang) chooses the font's letter forms for a language - its locl substitutions: Serbian and Macedonian Cyrillic, Romanian and Moldavian comma-below letters, Japanese, Chinese and Korean forms of the same ideographs. font-language-override names the OpenType language system directly, for languages the attribute does not map.

<p lang="sr">бгдпт</p>
<p style="font-language-override: 'SRB'">бгдпт</p>

Languages and East Asian text: Serbian letter forms from lang="sr" and font-language-override, full-width forms, text-transform full-width and full-size-kana; font synthesis of bold and italic

text-transform: full-width replaces characters by their full-width forms (ABC becomes ABC) and full-size-kana small kana by full-size ones (ァィゥ becomes アイウ), as the CSS Text specification defines them; Chrome does not implement either.

Kerning

font-kerning: normal kerns the text with the font's pair kerning (OpenType GPOS and kern), none turns it off, and auto - the default - follows applyKerning().

RTF and DOCX output

Word draws the text of a Word document itself, so the features have to reach it as Word's own settings. In DOCX output (Word 2010 and later) PD4ML writes:

CSS In Word
ligatures (standard, contextual, historical, discretionary) Ligatures
oldstyle-nums, lining-nums Number forms
tabular-nums, proportional-nums Number spacing
stylistic sets Stylistic set (the first one: Word applies one set)
contextual alternates Contextual alternates
font-kerning Kerning for fonts
small-caps, petite-caps Small caps (Word's own); all-small-caps: small caps of the text in lowercase
font-variant-position with the font's glyphs Superscript / subscript (also in RTF)

Word has no equivalent for fractions, ordinals, the slashed zero, swashes, historical forms, East Asian widths, language forms or unicase: in Word output that text is plain. text-transform: full-width and full-size-kana change the text itself, so they carry over.

The features need the fonts: on a computer without them Word substitutes its own. With setParam("pd4ml.docx.embed.fonts", "true") the DOCX carries the fonts of the document - not the built-in fonts PD4ML maps to Arial, Times New Roman and Courier New, and not a font whose license (its embedding permissions) forbids it - so Word draws the text as the PDF has it. "true" embeds fonts of up to 10 MB each, "all" any size; embedding is off by default. RTF has no font embedding that Word reads.

pd4ml.setParam("pd4ml.docx.embed.fonts", "true");
pd4ml.writeDOCX(new FileOutputStream("report.docx"));

The sample document's DOCX open in Microsoft Word, with its fonts embedded: discretionary ligatures, small caps, all-small-caps and old-style figures as Word draws them; fractions and ordinals stay plain

See RTF and DOCX Output.

Limitations

  • font-variation-settings and font-optical-sizing are not supported: a variable font is used at its default instance.
  • Text that needs complex shaping - Indic scripts, Arabic, Hebrew, Thai, Khmer and others - is shaped by Java's own text layout, with the font's default features; the CSS features above are not applied to it.
  • Glyph positioning features beyond pair kerning (GPOS mark positioning of Latin text, palt, halt) are not applied.

Sample documents

The screenshots on this page come from one sample document, converted by PD4ML v4.2 with the v42 engine:

See also