Page 1 of 1

OpenType - Fractions

Posted: Thu Sep 14, 2017 1:50 pm
by Erwin Denissen
The beauty of writing OpenType layout feature code is the fact you can come up with your own implementation of a specific problem.

More and more fonts come with feature code for fractions, some only contain ligatures to standard fractions like 1/2. But there are also several complex solutions which contain a generic solution so they cover a wider range of fractions.

This implementation shows how to add support for fractions supporting just a little more than the basics.

From a mathematical point of view, there is a specific order of precedence in which operations must be done.
  • Anything in parentheses must be done first
  • Next are divisions and multiplications
  • And finally additions and subtractions
So 1+2/3+4 ≠ (1+2)/(3+4) & 1x2/3x4 = (1x2)/(3x4) should display as:
OpenType Fractions.png
OpenType Fractions.png (55.14 KiB) Viewed 2646 times
The feature code that makes this happen is available here:

Code: Select all

script latn {
  feature Fractions;
}

class @hyphen [hyphen uni2010 uni2011];
class @Denominators [onequarter onehalf threequarters fraction onethird twothirds onefifth twofifths-fourfifths onesixth fivesixths oneeighth threeeighths fiveeighths seveneighths zero.dnom-nine.dnom plus.subs minus.subs equal.subs parenleft.subs parenright.subs a.subs-z.subs];
class @Numerators [zero.sups-nine.sups plus.sups minus.sups equal.sups parenleft.sups parenright.sups a.sups-z.sups];
class @basiclatinbasic [asterisk period zero-nine A-Z a-z];
class @basiclatin [asterisk plus hyphen period zero-nine A-Z a-z];
class @SingleClass1 [a-z];
class @SingleClass2 [a.subs-z.subs];
class @FIGS1 [zero-nine];
class @SingleClass3 [zero.dnom-nine.dnom];
class @SingleClass4 [a.sups-z.sups];
class @SingleClass5 [zero.sups-nine.sups];

feature Fractions frac {
  lookup FractionContextNumeratorsParentheses;
  lookup FractionContextBasic;
  lookup FractionContextDenominatorsParentheses;
}

lookup FractionContextNumeratorsParentheses {
  context parenleft (@basiclatin parenright slash);
  sub 0 NumbersToNumerators;
  context parenleft (@basiclatin @basiclatin parenright slash);
  sub 0 NumbersToNumerators;
  context parenleft (@basiclatin @basiclatin @basiclatin parenright slash);
  sub 0 NumbersToNumerators;
  context parenleft (@basiclatin @basiclatin @basiclatin @basiclatin parenright slash);
  sub 0 NumbersToNumerators;
  context parenleft (@basiclatin @basiclatin @basiclatin @basiclatin @basiclatin parenright slash);
  sub 0 NumbersToNumerators;
  context parenleft (@basiclatin @basiclatin @basiclatin @basiclatin @basiclatin @basiclatin parenright slash);
  sub 0 NumbersToNumerators;
  context parenleft (@basiclatin @basiclatin @basiclatin @basiclatin @basiclatin @basiclatin @basiclatin parenright slash);
  sub 0 NumbersToNumerators;
  context parenleft (@basiclatin @basiclatin @basiclatin @basiclatin @basiclatin @basiclatin @basiclatin @basiclatin parenright slash);
  sub 0 NumbersToNumerators;
  context parenleft (@basiclatin @basiclatin @basiclatin @basiclatin @basiclatin @basiclatin @basiclatin @basiclatin @basiclatin parenright slash);
  sub 0 NumbersToNumerators;
  context parenleft (@basiclatin @basiclatin @basiclatin @basiclatin @basiclatin @basiclatin @basiclatin @basiclatin @basiclatin @basiclatin parenright slash);
  sub 0 NumbersToNumerators;
  context parenleft (@basiclatin @basiclatin @basiclatin @basiclatin @basiclatin @basiclatin @basiclatin @basiclatin @basiclatin @basiclatin @basiclatin parenright slash);
  sub 0 NumbersToNumerators;
  context (@Numerators) @basiclatin;
  sub 0 NumbersToNumerators;
  context (@Numerators) parenright;
  sub 0 NumbersToNumerators;
  ignore context (fraction @Denominators) parenright;
  context (@Numerators) slash;
  sub 0 SlashToFraction;
}

lookup FractionContextBasic {
  context @basiclatinbasic (slash);
  sub 0 NumbersToNumerators;
  context @basiclatinbasic (@basiclatinbasic slash);
  sub 0 NumbersToNumerators;
  context @basiclatinbasic (@basiclatinbasic @basiclatinbasic slash);
  sub 0 NumbersToNumerators;
  context @basiclatinbasic (@basiclatinbasic @basiclatinbasic @basiclatinbasic slash);
  sub 0 NumbersToNumerators;
  context @basiclatinbasic (@basiclatinbasic @basiclatinbasic @basiclatinbasic @basiclatinbasic slash);
  sub 0 NumbersToNumerators;
  context @basiclatinbasic (@basiclatinbasic @basiclatinbasic @basiclatinbasic @basiclatinbasic @basiclatinbasic slash);
  sub 0 NumbersToNumerators;
  context @basiclatinbasic (@basiclatinbasic @basiclatinbasic @basiclatinbasic @basiclatinbasic @basiclatinbasic @basiclatinbasic slash);
  sub 0 NumbersToNumerators;
  context @basiclatinbasic (@basiclatinbasic @basiclatinbasic @basiclatinbasic @basiclatinbasic @basiclatinbasic @basiclatinbasic @basiclatinbasic slash);
  sub 0 NumbersToNumerators;
  context @basiclatinbasic (@basiclatinbasic @basiclatinbasic @basiclatinbasic @basiclatinbasic @basiclatinbasic @basiclatinbasic @basiclatinbasic @basiclatinbasic slash);
  sub 0 NumbersToNumerators;
  context @basiclatinbasic (@basiclatinbasic @basiclatinbasic @basiclatinbasic @basiclatinbasic @basiclatinbasic @basiclatinbasic @basiclatinbasic @basiclatinbasic @basiclatinbasic slash);
  sub 0 NumbersToNumerators;
  context @basiclatinbasic (@basiclatinbasic @basiclatinbasic @basiclatinbasic @basiclatinbasic @basiclatinbasic @basiclatinbasic @basiclatinbasic @basiclatinbasic @basiclatinbasic @basiclatinbasic @basiclatinbasic slash);
  sub 0 NumbersToNumerators;
  context @basiclatinbasic (@basiclatinbasic @basiclatinbasic @basiclatinbasic @basiclatinbasic @basiclatinbasic @basiclatinbasic @basiclatinbasic @basiclatinbasic @basiclatinbasic @basiclatinbasic @basiclatinbasic @basiclatinbasic @basiclatinbasic slash);
  sub 0 NumbersToNumerators;
  context (@Numerators) slash;
  sub 0 SlashToFraction;
  context (fraction) @basiclatinbasic;
  sub 0 NumbersToDenominators;
  context (@Denominators) @basiclatinbasic;
  sub 0 NumbersToDenominators;
}

lookup FractionContextDenominatorsParentheses {
  ignore context (parenright.subs) parenright;
  context (fraction) parenleft;
  sub 0 NumbersToDenominators;
  context (parenleft.subs) @basiclatin;
  sub 0 NumbersToDenominators;
  context (parenleft.subs @Denominators) @basiclatin;
  sub 0 NumbersToDenominators;
  context (parenleft.subs @Denominators @Denominators) @basiclatin;
  sub 0 NumbersToDenominators;
  context (parenleft.subs @Denominators @Denominators @Denominators) @basiclatin;
  sub 0 NumbersToDenominators;
  context (parenleft.subs @Denominators @Denominators @Denominators @Denominators) @basiclatin;
  sub 0 NumbersToDenominators;
  context (parenleft.subs @Denominators @Denominators @Denominators @Denominators @Denominators) @basiclatin;
  sub 0 NumbersToDenominators;
  context (parenleft.subs @Denominators @Denominators @Denominators @Denominators @Denominators @Denominators) @basiclatin;
  sub 0 NumbersToDenominators;
  context (parenleft.subs @Denominators @Denominators @Denominators @Denominators @Denominators @Denominators @Denominators) @basiclatin;
  sub 0 NumbersToDenominators;
  context (parenleft.subs @Denominators @Denominators @Denominators @Denominators @Denominators @Denominators @Denominators @Denominators) @basiclatin;
  sub 0 NumbersToDenominators;
  context (parenleft.subs @Denominators @Denominators @Denominators @Denominators @Denominators @Denominators @Denominators @Denominators @Denominators) @basiclatin;
  sub 0 NumbersToDenominators;
  context (parenleft.subs @Denominators @Denominators @Denominators @Denominators @Denominators @Denominators @Denominators @Denominators @Denominators @Denominators) @basiclatin;
  sub 0 NumbersToDenominators;
  context (parenleft.subs @Denominators @Denominators @Denominators @Denominators @Denominators @Denominators @Denominators @Denominators @Denominators @Denominators @Denominators) @basiclatin;
  sub 0 NumbersToDenominators;
  context (parenleft.subs @Denominators) parenright;
  sub 0 NumbersToDenominators;
  context (parenleft.subs @Denominators @Denominators) parenright;
  sub 0 NumbersToDenominators;
  context (parenleft.subs @Denominators @Denominators @Denominators) parenright;
  sub 0 NumbersToDenominators;
  context (parenleft.subs @Denominators @Denominators @Denominators @Denominators) parenright;
  sub 0 NumbersToDenominators;
  context (parenleft.subs @Denominators @Denominators @Denominators @Denominators @Denominators) parenright;
  sub 0 NumbersToDenominators;
  context (parenleft.subs @Denominators @Denominators @Denominators @Denominators @Denominators @Denominators) parenright;
  sub 0 NumbersToDenominators;
  context (parenleft.subs @Denominators @Denominators @Denominators @Denominators @Denominators @Denominators @Denominators) parenright;
  sub 0 NumbersToDenominators;
  context (parenleft.subs @Denominators @Denominators @Denominators @Denominators @Denominators @Denominators @Denominators @Denominators) parenright;
  sub 0 NumbersToDenominators;
  context (parenleft.subs @Denominators @Denominators @Denominators @Denominators @Denominators @Denominators @Denominators @Denominators @Denominators) parenright;
  sub 0 NumbersToDenominators;
  context (parenleft.subs @Denominators @Denominators @Denominators @Denominators @Denominators @Denominators @Denominators @Denominators @Denominators @Denominators) parenright;
  sub 0 NumbersToDenominators;
  context (parenleft.subs @Denominators @Denominators @Denominators @Denominators @Denominators @Denominators @Denominators @Denominators @Denominators @Denominators @Denominators) parenright;
  sub 0 NumbersToDenominators;
}

lookup SlashToFraction {
  sub slash -> fraction;
}

lookup NumbersToDenominators {
  sub parenleft -> parenleft.subs;
  sub parenright -> parenright.subs;
  sub plus -> plus.subs;
  sub @hyphen -> minus.subs;
  sub @FIGS1 -> @SingleClass3;
  sub equal -> equal.subs;
  sub @SingleClass1 -> @SingleClass2;
  sub minus -> minus.subs;
}

lookup NumbersToNumerators {
  sub parenleft -> parenleft.sups;
  sub parenright -> parenright.sups;
  sub plus -> plus.sups;
  sub @hyphen -> minus.sups;
  sub @FIGS1 -> @SingleClass5;
  sub equal -> equal.sups;
  sub @SingleClass1 -> @SingleClass4;
  sub minus -> minus.sups;
}
Feel free to use this in your own fonts, and if you see room for improvements, let us know!

Re: OpenType - Fractions

Posted: Thu Sep 14, 2017 2:01 pm
by MikeW
Cool. Thanks, Erwin.

How well does it work with something like...

123/456 or (1+2)/(abc-xyz) | 1st = 2x + 1/3y (2+3)/(5-1) Hooray

Re: OpenType - Fractions

Posted: Thu Sep 14, 2017 2:49 pm
by Erwin Denissen
See this screenshot:
FractionsMore.png
FractionsMore.png (7.96 KiB) Viewed 2638 times

Re: OpenType - Fractions

Posted: Thu Sep 14, 2017 5:50 pm
by PJMiller
This is a problem I have been trying to deal with recently.

This looks good, I will copy it into 'Tobias' and see if I have any problems.

I will let you know how I get on.

Re: OpenType - Fractions

Posted: Sat Sep 16, 2017 11:56 am
by PJMiller
I finally got it working. My font didn't contain any .dnom characters so I put some in, same size as the .sinf or .sups characters but resting on the baseline.

The gaps before and after the fraction slash were ugly so I included a composite clone of the fraction slash with large negative bearings. As it is unmapped and only used with the fraction feature this will not cause any problems as it only ever appears with .sups characters to it's left and .dnom to it's right, this doesn't need kerning to work.

This is a great implimentation of the fraction feature and I commend it to the house! :D :D
linedance.gif
linedance.gif (60.22 KiB) Viewed 2620 times