mdbFLT(5)

πŸ“¦
version 1.8.4 Β· 2 versions
, in debian trixie, ubuntu noble, ubuntu questing, ubuntu resolute, ubuntu stonking

✨ mdbFLT(5) β€” Font Layout Table ✨

okay bestie, welcome to The m17n Library's manual page for mdbFLT, aka the Font Layout Table. this one is lowkey a whole system for making complicated scripts look correct, and we're gonna read it together πŸ’–


πŸŽ€ NAME πŸŽ€

mdbFLT β€” Font Layout Table πŸ¦‹


πŸ’… DESCRIPTION πŸ’…

okay so for simple scripts, the rendering engine converts character codes into glyph codes one by one by consulting the encoding of each selected font. cute, love that for them. but to render text that requires complicated layout (e.g. Thai and Indic scripts), one to one conversion is not sufficient. a sequence of characters may have to be drawn as a single ligature. some glyphs may have to be drawn at 2-dimensionally shifted positions. the way that is so real 😭

to handle those complicated scripts, the m17n library uses Font Layout Tables (FLTs for short). the FLT driver interprets an FLT and converts a character sequence into a glyph sequence that is ready to be passed to the rendering engine. slay.

an FLT can contain information to extract a grapheme cluster from a character sequence and to reorder the characters in the cluster, in addition to information found in OpenType Layout Tables (CMAP, GSUB, and GPOS). multitasking queen behavior πŸ’…

an FLT is a cascade of one or more conversion stages. in each stage, a sequence is converted into another sequence to be read in the next stage. the length of sequences may differ from stage to stage. each element in a sequence has the following integer attributes.

  • code β€” in the first conversion stage, this is the character code in the original character sequence. in the last stage, it is the glyph code passed to the rendering engine. in other cases, it is an intermediate glyph code.
  • category β€” the category code defined in the CATEGORY-TABLE of the current stage, or defined in the one of the former stages and not overwritten by later stages.
  • combining-spec β€” if nonzero, it specifies how to combine this (intermediate) glyph with the previous one.
  • left-padding-flag β€” if nonzero, it instructs the rendering function to insert a padding space before this (intermediate) glyph so that the glyph does not overlap with the previous one.
  • right-padding-flag β€” if nonzero, it instructs the rendering function to insert a padding space after this (intermediate) glyph so that the glyph does not overlap with the next one.

when the layout engine draws text, it at first determines a font and an FLT for each character in the text. for each subsequence of characters that use the same font and FLT, the layout engine generates a corresponding intermediate glyph sequence. the code attribute of each element in the intermediate glyph sequence is its character code, and all other attributes are zeros. this sequence is processed in the first stage of FLT as the current run (substring). periodt.

each stage works as follows.

At first, if the stage has a CATEGORY-TABLE, the category of each glyph in the current run is updated. if there is a glyph that has no category, the current run ends before that glyph.

Then, the default values of code-offset, combining-spec, and left-padding-flag of this stage are initialized to zero.

Next, the initial conversion rule of the stage is applied to the current run.

Lastly, the current run is replaced with the newly produced (intermediate) glyph sequence.

πŸ¦‹ SYNTAX and SEMANTICS πŸ¦‹

ok so here's the tea, bestie β˜• β€” the m17n library loads an FLT out of the m17n database using the tag <font, layouter, FLT-NAME>. and the format of an FLT is, like, this:

FONT-LAYOUT-TABLE ::= FLT-DECLARATION ? STAGE0 STAGE *

FLT-DECLARATION ::= '(' 'font' 'layouter' FLT-NAME nil PROP * ')'
FLT-NAME ::= SYMBOL
PROP :: = VERSION | FONT
VERSION ::= '(' 'version' MTEXT ')'
FONT ::= '(' 'font' FONT-SPEC ')'
FONT-SPEC ::=
     '(' [[ FOUNDRY FAMILY
           [ WEIGHT [ STYLE [ STRETCH [ ADSTYLE ]]]]]
         REGISTRY ]
     [ OTF-SPEC ] [ LANG-SPEC ] ')'

and then the stages:

STAGE0 ::= CATEGORY-TABLE GENERATOR

STAGE ::= CATEGORY-TABLE ? GENERATOR

CATEGORY-TABLE ::= '(' 'category' CATEGORY-SPEC + ')'

CATEGORY-SPEC ::= '(' CODE CATEGORY ')'
                  | '(' CODE CODE CATEGORY ')'

CODE ::= INTEGER

CATEGORY ::= INTEGER

so inside a CATEGORY-SPEC, CODE is a glyph code, and CATEGORY is the ASCII code of an upper or lower letter β€” like, one of 'A', ... 'Z', 'a', .. 'z' πŸ’…

the first form of CATEGORY-SPEC hands CATEGORY to a glyph whose code is CODE. the second form hands CATEGORY to all the glyphs whose code falls between the two CODEs. slay πŸŽ€

GENERATOR ::= '(' 'generator' RULE MACRO-DEF * ')'

RULE ::= REGEXP-BLOCK | MATCH-BLOCK | SUBST-BLOCK | COND-BLOCK
         FONT-FACILITY-BLOCK | DIRECT-CODE | COMBINING-SPEC | OTF-SPEC
         | PREDEFINED-RULE | MACRO-NAME

MACOR-DEF ::= '(' MACRO-NAME RULE + ')'

every single RULE says which glyphs get consumed and which glyphs get produced 🫢. when some glyphs are consumed, they're literally taken away from the current run. a rule can fail under some condition β€” but if nothing says it fails, you gotta assume it succeeds, periodt.

DIRECT-CODE ::= INTEGER

this rule consumes no glyph and produces a glyph with these attributes, iykyk:

  • code : INTEGER plus the default code-offset
  • combining-spec : default value
  • left-padding-flag : default value
  • right-padding-flag : zero

and once the glyph is out there, the default code-offset, combining-spec, and left-padding-flag all get reset back to zero πŸ”

PREDEFINED-RULE ::= '=' | '*' | '<' | '>' | '|' | '[' | ']'

these ones do the following, bestie:

  • = consumes the first glyph in the current run and produces the same glyph. it fails if the current run is empty πŸ’€
  • * repeatedly executes the previous rule. if the previous rule fails, this rule does nothing and fails.
  • < this rule specifies the start of a grapheme cluster.
  • > this rule specifies the end of a grapheme cluster.
  • @[ this rule sets the default left-padding-flag to 1. no glyph is consumed. no glyph is produced.
  • @] this rule changes the right-padding-flag of the lastly generated glyph to 1. no glyph is consumed. no glyph is produced.
  • | this rule consumes no glyph and produces a special glyph whose category is ' ' and whose other attributes are zero. this is the only rule that produces that special glyph πŸ‘‘
  • [ / ] β€” (they show up in the PREDEFINED-RULE list up there, so don't come for me)
REGEXP-BLOCK ::= '(' REGEXP RULE * ')'

REGEXP ::= MTEXT

MTEXT is a regular expression that should match the sequence of categories of the current run. if a match is found, this rule executes the RULEs while temporarily limiting the current run to the matched part. the matched part gets consumed by this rule. any parenthesized subexpressions get recorded so a MATCH-BLOCK showing up in one of the RULEs can use them. if no match is found, this rule fails πŸ™ˆ

MATCH-BLOCK ::= '(' MATCH-INDEX RULE * ')'

MATCH-INDEX ::= INTEGER

MATCH-INDEX is an integer pointing at a parenthesized subexpression that the previous REGEXP-BLOCK recorded. if that subexpression was found by the previous regex matching, this rule executes the RULEs while temporarily limiting the current run to the matched part of the subexpression, and the matched part gets consumed by this rule.

if no match was found, this rule fails. and if this is the first rule of the stage, MATCH-INDEX must be 0, and it matches the whole current run. no cap ✨

SUBST-BLOCK ::= '(' SOURCE-PATTERN RULE * ')'

SOURCE-PATTERN ::= '(' CODE + ')'
                   | (' 'range' CODE CODE ')'

if the sequence of codes of the current run matches SOURCE-PATTERN, this rule executes the RULEs while temporarily limiting the current run to the matched part, and the matched part gets consumed.

  • the first form of SOURCE-PATTERN specifies a sequence of glyph codes to be matched. in this case, the rule resets the default code-offset to zero.
  • the second form specifies a range of codes that should match the first glyph code of the code sequence. in this case, the rule sets the default code-offset to the first glyph code minus the first CODE specifying the range.

if no match is found, this rule fails πŸ’”

FONT-FACILITY-BLOCK ::= '(' FONT-FACILITY RULE * ')'
FONT-FACILITY = '(' 'font-facility' CODE * ')'
            | '(' 'font-facility' FONT-SPEC ')'

if the current font has glyphs for the CODEs, or matches FONT-SPEC, this rule succeeds and the RULEs get executed. otherwise, this rule fails 🚫

COND-BLOCK ::= '(' 'cond' RULE + ')'

this rule executes RULEs one after another until one succeeds. if no rule succeeds, this rule fails. otherwise, it succeeds. it's giving "try until something works" energy, honestly πŸ’…

OTF-SPEC ::= SYMBOL

OTF-SPEC is a symbol whose name specifies an instruction to the OTF driver. the name has this syntax:

  OTF-SPEC-NAME ::= ':otf=' SCRIPT LANGSYS ? GSUB-FEATURES ? GPOS-FEATURES ?

  SCRIPT ::= SYMBOL

  LANGSYS ::= '/' SYMBOL

  GSUB-FEATURES ::= '=' FEATURE-LIST ?

  GPOS-FEATURES ::= '+' FEATURE-LIST ?

  FEATURE-LIST ::= ( SYMBOL ',' ) * [ SYMBOL | '*' ].fi

every SYMBOL specifies a tag name defined in the OpenType specification.

  • for SCRIPT, SYMBOL specifies a Script tag name β€” e.g. deva for Devanagari.
  • for LANGSYS, SYMBOL specifies a Language System tag name. if LANGSYS is omitted, the Default Language System table is used.
  • for GSUB-FEATURES, each SYMBOL in FEATURE-LIST specifies a GSUB Feature tag name to apply. '*' is allowed as the last item to specify all remaining features. if a SYMBOL is preceded by '~' and the last item is '*', that SYMBOL gets excluded from the features to apply. if no SYMBOL is specified, no GSUB feature is applied. if GSUB-FEATURES itself is omitted, all GSUB features are applied.
  • when OTF-SPEC appears in a FONT-SPEC, FEATURE-LIST specifies features that the font must have (or must not have if preceded by '~'), and the last '*', even if it exists, has no meaning.

the specification of GPOS-FEATURES is analogous to that of GSUB-FEATURES πŸͺž

please note that all the tags above must be 4 ASCII printable characters. that's not a suggestion bestie, that's a rule πŸ“

see the following page for the OpenType specification:

http://www.microsoft.com/typography/otspec/default.htm

COMBINING ::= SYMBOL

COMBINING is a symbol whose name specifies how to combine the next glyph with the previous one. this rule sets the default combining-spec to an integer code that is unique to the symbol name. the name has this syntax:

  COMBINING-NAME ::= VPOS HPOS OFFSET VPOS HPOS

  VPOS ::= 't' | 'c' | 'b' | 'B'

  HPOS ::= 'l' | 'c' | 'r'

  OFFSET :: = '.' | XOFF | YOFF XOFF ?

  XOFF ::= ('<' | '>') INTEGER ?

  YOFF ::= ('+' | '-') INTEGER ?

VPOS and HPOS specify the vertical and horizontal positions, like, exactly like this:

                                 POINT VPOS HPOS
                                 ----- ---- ----
     0----1----2 <---- top       0     t    l
     |         |                 1     t    c
     |         |                 2     t    r
     |         |                 3     B    l
     9   10   11 <---- center    4     B    c
     |         |                 5     B    r
   --3----4----5-- <-- baseline  6     b    l
     |         |                 7     b    c
     6----7----8 <---- bottom    8     b    r
                                 9     c    l
     |    |    |                10     c    c
   left center right            11     c    r

the left figure shows the 12 reference points of a glyph, numbered 0 to 11 🌸. the rectangle 0-6-8-2 is the bounding box of the glyph. the positions 3, 4, and 5 are on the baseline, 9-11 are on the vertical center of the box, and 0-2 and 6-8 are on the top and on the bottom respectively. 1, 10, 4, and 7 are on the horizontal center of the box.

the right table shows how those reference points are specified by a pair of VPOS and HPOS πŸ’–

so: the first VPOS and HPOS in the definition of COMBINING-NAME specify the reference point of the previous glyph, and the second VPOS and HPOS specify that of the next glyph. the next glyph is drawn so that those two reference points align 🧲

OFFSET specifies the way of alignment in detail:

  • if it's '.', the reference points are on the same position.
  • XOFF specifies how much the X position of the reference point of the next glyph should be shifted to the left ('<') or right ('>') from the previous reference point.
  • YOFF specifies how much the Y position of the reference point of the next glyph should be shifted upward ('+') or downward ('-') from the previous reference point.

in both cases, INTEGER is the amount of shift expressed as a percentage of the font size β€” so if INTEGER is 10, it means 10% (1/10) of the font size. if INTEGER is omitted, it's assumed that 5 is specified πŸ‘€

and once the next glyph is combined with the previous one, they get treated as a single combined glyph. they're a unit now, they're inseparable, main character energy πŸ«‚

MACRO-NAME ::= SYMBOL

MACRO-NAME is a symbol that appears in one of MACRO-DEF. it gets exapanded to the sequence of the corresponding RULEs ✨ (yes, exapanded. that's the word. we don't gatekeep spelling here πŸŽ€)


πŸ’… CONTEXT DEPENDENT BEHAVIOR πŸ’…

okay so bestie, so far it's been assumed that each sequence, which is drawn with a specific font, is context free, i.e. not affected by the glyphs preceding or following that sequence. ✨ This is true when sequence S1 is drawn with font F1 while the preceding sequence S0 unconditionally requires font F0. πŸ‘‘

sequence                              S0      S1
currently used font                   F0      F1
usable font(s)                        F0      F1

Sometimes, however, a clear separation of sequences is not possible. πŸ™ˆ Suppose that the preceding sequence S0 can be drawn not only with F0 but also with F1. Then:

sequence                              S0      S1
currently used font                   F0      F1
usable font(s)                        F0,F1   F1

In this case, glyphs used to draw the preceding S0 may affect glyph generation of S1. 😭 Therefore it is necessary to access information about S0, which has already been processed, when processing S1. Generation rules in the first stage (only in the first stage) accept a special regular expression to access already processed parts. πŸ’–

"RE0 RE1"

RE0 and RE1 are regular expressions that match the preceding sequence S0 and the following sequence S1, respectively. πŸŽ€

Pay attention to the space between the two regular expressions. It represents the special category ' ' (see above). πŸ‘€ Note that the regular expression above belongs to glyph generation rules using font F1, therefore not only RE1 but also RE0 must be expressed with the categories for F1. This means when the preceding sequence S0 cannot be expressed with the categories for F1 (as in the first example above) generation rules having these patterns never match. πŸ’€


🌸 SEE ALSO 🌸


Copyright (C) 2001 Information-technology Promotion Agency (IPA) Copyright (C) 2001-2011 National Institute of Advanced Industrial Science and Technology (AIST)

Permission is granted to copy, distribute and/or modify this document under the terms of the GNU Free Documentation License <http://www.gnu.org/licenses/fdl.html>.


catch u later, bestie β€” this was mdbFLT(5), version 1.8.4, dated Mon Sep 25 2023 πŸ’–βœ¨

πŸ“¦ where this page comes from