OpenType - Fractions

Before asking a question on FontCreator look here for possible solutions and tutorials. Please do not post support requests here.
Post Reply
Erwin Denissen
Moderator
Moderator
Posts: 7000
Joined: Fri Oct 04, 2002 12:41 am
Location: De Bilt, The Netherlands
Contact:

OpenType - Fractions

Post by Erwin Denissen » Thu Sep 14, 2017 1:50 pm

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 342 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!
Erwin Denissen
High-Logic
Proven Font Technology

MikeW
Posts: 498
Joined: Mon May 20, 2013 2:51 pm

Re: OpenType - Fractions

Post by MikeW » Thu Sep 14, 2017 2:01 pm

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

Erwin Denissen
Moderator
Moderator
Posts: 7000
Joined: Fri Oct 04, 2002 12:41 am
Location: De Bilt, The Netherlands
Contact:

Re: OpenType - Fractions

Post by Erwin Denissen » Thu Sep 14, 2017 2:49 pm

See this screenshot:
FractionsMore.png
FractionsMore.png (7.96 KiB) Viewed 334 times
Erwin Denissen
High-Logic
Proven Font Technology

PJMiller
Top Typographer
Top Typographer
Posts: 568
Joined: Tue Jun 16, 2015 8:12 pm
Location: Sheffield, South Yorkshire
Contact:

Re: OpenType - Fractions

Post by PJMiller » Thu Sep 14, 2017 5:50 pm

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.

PJMiller
Top Typographer
Top Typographer
Posts: 568
Joined: Tue Jun 16, 2015 8:12 pm
Location: Sheffield, South Yorkshire
Contact:

Re: OpenType - Fractions

Post by PJMiller » Sat Sep 16, 2017 11:56 am

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 316 times

Post Reply

Who is online

Users browsing this forum: No registered users and 1 guest