Born2Root / Fast-Font

This font provides faster reading through facilitating the reading process by guiding the eyes through text with artificial fixation points.
https://born2root.github.io/Fast-Font/
MIT License
131 stars 9 forks source link

Adding fast-font type features to other fonts family #3

Open Rahul-fix opened 4 months ago

Rahul-fix commented 4 months ago

Hi, I want to modify the kindle font "Bookerly" to have same features as Fast-Fonts. How I can achieve it? Is it even possible? I am using the custom Bookerly fonts from here: drive from reddit I tried the following and got the error:

$ cat test.fea
feature calt {
    @az = [a-z A-Z];
    @AZ = [a.bold-z.bold A.bold-Z.bold];

    @all = [@az @AZ];

    # 4, 5, 6
    ignore sub @all @all @az' @all @all;
    sub @all @az' @all @all by @AZ;

    # 1, 2, 3
    ignore sub @all @az';
    sub @az' by @AZ;
} calt;

$ python addfeatures.py -o Fast-Bookerly.otf fonts/Bookerly\ Modified/Bookerly-Regular-Mod.otf  test.fea         
Adding features
Traceback (most recent call last):
  File "/Users/rk/Sync/utils/FastFonts/addfeatures.py", line 22, in <module>
    addOpenTypeFeatures(font, args.feature)
  File "/opt/homebrew/Caskroom/miniforge/base/envs/font/lib/python3.11/site-packages/fontTools/feaLib/builder.py", line 16, in addOpenTypeFeatures
    builder.build()
  File "/opt/homebrew/Caskroom/miniforge/base/envs/font/lib/python3.11/site-packages/fontTools/feaLib/builder.py", line 78, in build
    self.parseTree = Parser(self.file, self.glyphMap).parse()
                     ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/opt/homebrew/Caskroom/miniforge/base/envs/font/lib/python3.11/site-packages/fontTools/feaLib/parser.py", line 55, in parse
    statements.append(self.parse_feature_block_())
                      ^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/opt/homebrew/Caskroom/miniforge/base/envs/font/lib/python3.11/site-packages/fontTools/feaLib/parser.py", line 1143, in parse_feature_block_
    self.parse_block_(block, vertical, stylisticset, size_feature)
  File "/opt/homebrew/Caskroom/miniforge/base/envs/font/lib/python3.11/site-packages/fontTools/feaLib/parser.py", line 1201, in parse_block_
    statements.append(self.parse_glyphclass_definition_())
                      ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/opt/homebrew/Caskroom/miniforge/base/envs/font/lib/python3.11/site-packages/fontTools/feaLib/parser.py", line 194, in parse_glyphclass_definition_
    glyphs = self.parse_glyphclass_(accept_glyphname=False)
             ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/opt/homebrew/Caskroom/miniforge/base/envs/font/lib/python3.11/site-packages/fontTools/feaLib/parser.py", line 264, in parse_glyphclass_
    start, limit = self.split_glyph_range_(glyph, location)
                   ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/opt/homebrew/Caskroom/miniforge/base/envs/font/lib/python3.11/site-packages/fontTools/feaLib/parser.py", line 229, in split_glyph_range_
    raise FeatureLibError(
fontTools.feaLib.error.FeatureLibError: test.fea:3:12: "a.bold-z.bold" is not a glyph in the font, and it can not be split into a range of known glyphs
ramarivera commented 4 months ago

I am having the same issue. Did you ever figure out a solution?

Born2Root commented 4 months ago

Sorry for the late reply, I was on holiday...

I see no reason why it should not work with your font. I also applied the rules to different fonts to find the best for fast-reading. The only important thing is, the font should have the different style variants combined in one font-file. So having on font-file incorporating the regular and the bold version of each letter.

There are some fonts out there, that have a separate font-file for regular, bold, itallic and all other versions... At least on the google drive it looks, like Bookerfly is unfortunately exactly that type of font... They have an extra font-file for the regular, and bold variant.

2024-05-16 22_38_30-Bookerly Modified – Google Drive - Iron

That would explain, why your script is complaining that it could not find the .bold variant "a.bold-z.bold" is not a glyph in the font

To solve that issue, you have to combine the regular and the bold font variant into one file. That is an easy copy paste process, where you copy all the bold-characters from the bold-font and paste them into the regular font-file, with the naming convention .bold behind every pasted character. It should result in a font file containing the regular characters with standard naming and the bold character with a closing .bold, for instance a -> a.bold

Afterwards your feature python scripts should also work ;-)

The easiest way to combine the fonts is to use a WYSIWYG-Font Editor. I would recommend FontLab, as I also used it for my experiments. An alternatives that should also work is FontCreator and you could also try the opensource alternative FontForge. But I have not worked with FontForge for a long time, so I am not sure if they improved in the meantime.

Rahul-fix commented 4 months ago

Thank you for the reply and suggestions. @Born2Root

  1. Tried the suggestion but getting follwoing error:
    1. Using FontLab8: copy pasting the bold into the regular. Then exporting it.
    2. Using FontForge: Combining the all *.ttf files to .ttc format

Tried the suggestion but getting follwoing error:

Using FontLab8: copy pasting the bold into the regular. Then exporting it.

❯  python3 addfeatures.py -o Fast-Bookerly.ttc testing/Bookerly/OpenType-TT/Bookerly.ttf  test.fea
Adding features
Traceback (most recent call last):
  File "/opt/homebrew/Caskroom/miniforge/base/envs/font/lib/python3.11/site-packages/fontTools/ttLib/__init__.py", line 409, in __getitem__
    return self.tables[tag]
           ~~~~~~~~~~~^^^^^
KeyError: 'post'

During handling of the above exception, another exception occurred:

Traceback (most recent call last):
  File "/Users/rk/Sync/utils/FastFonts/addfeatures.py", line 22, in <module>
    addOpenTypeFeatures(font, args.feature)
  File "/opt/homebrew/Caskroom/miniforge/base/envs/font/lib/python3.11/site-packages/fontTools/feaLib/builder.py", line 15, in addOpenTypeFeatures
    builder = Builder(font, featurefile)
              ^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/opt/homebrew/Caskroom/miniforge/base/envs/font/lib/python3.11/site-packages/fontTools/feaLib/builder.py", line 32, in __init__
    self.glyphMap = font.getReverseGlyphMap()
                    ^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/opt/homebrew/Caskroom/miniforge/base/envs/font/lib/python3.11/site-packages/fontTools/ttLib/__init__.py", line 636, in getReverseGlyphMap
    self._buildReverseGlyphOrderDict()
  File "/opt/homebrew/Caskroom/miniforge/base/envs/font/lib/python3.11/site-packages/fontTools/ttLib/__init__.py", line 641, in _buildReverseGlyphOrderDict
    glyphOrder = self.getGlyphOrder()
                 ^^^^^^^^^^^^^^^^^^^^
  File "/opt/homebrew/Caskroom/miniforge/base/envs/font/lib/python3.11/site-packages/fontTools/ttLib/__init__.py", line 472, in getGlyphOrder
    glyphOrder = self['post'].getGlyphOrder()
                 ~~~~^^^^^^^^
  File "/opt/homebrew/Caskroom/miniforge/base/envs/font/lib/python3.11/site-packages/fontTools/ttLib/__init__.py", line 424, in __getitem__
    table.decompile(data, self)
  File "/opt/homebrew/Caskroom/miniforge/base/envs/font/lib/python3.11/site-packages/fontTools/ttLib/tables/_p_o_s_t.py", line 37, in decompile
    self.decode_format_2_0(data, ttFont)
  File "/opt/homebrew/Caskroom/miniforge/base/envs/font/lib/python3.11/site-packages/fontTools/ttLib/tables/_p_o_s_t.py", line 86, in decode_format_2_0
    indices.fromstring(data[:2*numGlyphs])
    ^^^^^^^^^^^^^^^^^^
AttributeError: 'array.array' object has no attribute 'fromstring'

Using FontForge: Combining the all *.ttf files to .ttc format

❯  python3 addfeatures.py -o Fast-Bookerly.ttf testing/Bookerly/Bookerly-R_B_I_BI.ttc  test.fea
Traceback (most recent call last):
  File "/Users/rk/Sync/utils/FastFonts/addfeatures.py", line 20, in <module>
    font = TTFont(args.input)
           ^^^^^^^^^^^^^^^^^^
  File "/opt/homebrew/Caskroom/miniforge/base/envs/font/lib/python3.11/site-packages/fontTools/ttLib/__init__.py", line 180, in __init__
    self.reader = sfnt.SFNTReader(file, checkChecksums, fontNumber=fontNumber)
                  ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/opt/homebrew/Caskroom/miniforge/base/envs/font/lib/python3.11/site-packages/fontTools/ttLib/sfnt.py", line 62, in __init__
    raise ttLib.TTLibError("specify a font number between 0 and %d (inclusive)" % (self.numFonts - 1))
fontTools.ttLib.TTLibError: specify a font number between 0 and 3 (inclusive)
Born2Root commented 4 months ago

Hi @Rahul-fix, that seems like a problem with the python script.

If you are already using FontLab, then you don't really need Python any more. Simply add the whole font-features directly in FontLab, as it is a full-blown font editor solution. With FontLab you can also directly test the font in the integrated test-bench and are able to directly see the effect of the font-features. Futhermore you can compile the whole font including it's features with FontLab and are able to export the final Font-Files. So thats the premium solution without the need for any external hacky scripts and extra stuff ;-)

https://help.fontlab.com/fontlab/7/manual/OpenType-Features/

Born2Root commented 1 month ago

I was recently active with FontLab again, so I made some screenshots to make it easier for other users to get active.

First of all, open a font-file with FonLab.

Afterwards open the "Feature" Panel if it is not already visible. Open Feature Panel

Now you can start adding OpenType features. The one that is use for the bionic speed reading functionality is "calt" contextual alternatives. If it is not already included in the font you can add it via the + symbol.

Afterwards just add the whole program code that you can find in the repo. opentype_feature.fea

Feature Description

Use the code as it is or alternate it as you wish. Afterwards test the font and export it.

maiytf commented 1 week ago

Hi thank you this is really helpful! I did what you outlined above for the Bookerly font (Bookerly.ttf) and ran into this error after pressing the play button. I am a lay person and don't know anything about coding so wanted to ask for some guidance as the preview does not seem to be reflecting the feature.

Screenshot 2024-09-03 at 17 30 27
Born2Root commented 1 week ago

The problem are the lines claiming "Missing glyphs" That means, that there are more letters/ glyphs covered in the pgoram code and the "Fast-Font", than that there are existent in the Bookerly.ttf.

As an explanation, the first line with "Missing glyph: uni0334 -> uni1D6C" tells you that the Unicode-Character uni1D6C is missing in the Bookerly.ttf. Looking the character up on the web tells you that it is a character from the greek alphabet, that is not covered by the Bookerly.ttf.

There are two ways to fix this:

  1. Design the characters that are missing yourself. You can do that with FontLab, but you need at least some designer abilities.
  2. Much easier, and what I would recommend: Just remove all the code-lines that are related to the characters that the error is mentioning.

So, just delete the code lines at the beginning.

image

And delete the two lines with the russian, cyrillic characters, if you don't need them.

image

Afterwards try to compile again. If he is still complaining about missing glyps search the code for this glyphs and also remove them 😉

maiytf commented 6 days ago

Thank you very much! Any idea what I should do about the bottom two red errors about calt and "fatal error"? They still show up even after I remove those codes for the missing glyphs. The preview also does not seem to be working yet either.

Born2Root commented 2 days ago

I really like that you keep on trying. Don't give up, you are getting better and learn with each step! To help you and other users with the whole process, I wrote a Tutorial explaining the whole process with Screenshots. You can find it here: Tutorial

Regarding your error messages... ... working with Font-Features is like programming. You get errors and with the errors try to fix the mentioned lines. The bottom red errors are also referring to missing glyps. To get the base font working just remove the code that is related to the marked problems.

I recently also added the Fast-Reading features to another font, that was missing many glyphs, so like you I had the need to reduce the code. For me this reduced version worked.

feature calt {

        @de = [adieresis odieresis udieresis Adieresis Odieresis Udieresis germandbls];
        @DE = [adieresis.bold odieresis.bold udieresis.bold Adieresis.bold Odieresis.bold Udieresis.bold germandbls.bold];

        #EU languages: Albanian, Bosnian, Croatian, Czech, Danish, Dutch, Estonian, Finnish, French, Hungarian, Italian, Lithuanian, Montenegrin, Norwegian, Polish, Portugese, Romanian, Serbian, Slovene, Spanish, Swedish, Turkish
        @eu = [aring Scaron OE Zcaron Aring scaron oe zcaron Ydieresis Agrave Aacute Acircumflex Atilde AE Ccedilla Egrave Eacute Ecircumflex Edieresis Igrave Iacute Icircumflex Idieresis Ntilde Ograve Oacute Ocircumflex Otilde Oslash Ugrave Uacute Ucircumflex Yacute agrave aacute acircumflex atilde ae ccedilla egrave eacute ecircumflex edieresis igrave iacute icircumflex idieresis ntilde ograve oacute ocircumflex otilde oslash ugrave uacute ucircumflex yacute ydieresis Abreve abreve Aogonek aogonek Cacute cacute Ccaron ccaron Dcaron dcaron Dcroat dcroat Edotaccent edotaccent Eogonek eogonek Ecaron ecaron Gbreve gbreve Iogonek iogonek Idotaccent dotlessi IJ ij Lslash lslash Nacute nacute Ncaron ncaron Ohungarumlaut ohungarumlaut Rcaron rcaron Sacute sacute Scedilla scedilla Tcaron tcaron Umacron umacron Uring uring Uhungarumlaut uhungarumlaut Uogonek uogonek Zacute zacute Zdotaccent zdotaccent];
        @EU = [aring.bold Scaron.bold OE.bold Zcaron.bold Aring.bold scaron.bold oe.bold zcaron.bold Ydieresis.bold Agrave.bold Aacute.bold Acircumflex.bold Atilde.bold AE.bold Ccedilla.bold Egrave.bold Eacute.bold Ecircumflex.bold Edieresis.bold Igrave.bold Iacute.bold Icircumflex.bold Idieresis.bold Ntilde.bold Ograve.bold Oacute.bold Ocircumflex.bold Otilde.bold Oslash.bold Ugrave.bold Uacute.bold Ucircumflex.bold Yacute.bold agrave.bold aacute.bold acircumflex.bold atilde.bold ae.bold ccedilla.bold egrave.bold eacute.bold ecircumflex.bold edieresis.bold igrave.bold iacute.bold icircumflex.bold idieresis.bold ntilde.bold ograve.bold oacute.bold ocircumflex.bold otilde.bold oslash.bold ugrave.bold uacute.bold ucircumflex.bold yacute.bold ydieresis.bold Abreve.bold abreve.bold Aogonek.bold aogonek.bold Cacute.bold cacute.bold Ccaron.bold ccaron.bold Dcaron.bold dcaron.bold Dcroat.bold dcroat.bold Edotaccent.bold edotaccent.bold Eogonek.bold eogonek.bold Ecaron.bold ecaron.bold Gbreve.bold gbreve.bold Iogonek.bold iogonek.bold Idotaccent.bold dotlessi.bold IJ.bold ij.bold Lslash.bold lslash.bold Nacute.bold nacute.bold Ncaron.bold ncaron.bold Ohungarumlaut.bold ohungarumlaut.bold Rcaron.bold rcaron.bold Sacute.bold sacute.bold Scedilla.bold scedilla.bold Tcaron.bold tcaron.bold Umacron.bold umacron.bold Uring.bold uring.bold Uhungarumlaut.bold uhungarumlaut.bold Uogonek.bold uogonek.bold Zacute.bold zacute.bold Zdotaccent.bold zdotaccent.bold];

        @sp = [@de @eu];
        @SP = [@DE @EU];

        @az = [a-z A-Z @sp];
        @AZ = [a.bold-z.bold A.bold-Z.bold @SP];

    @all = [@az @AZ @sp @SP];

#17
        ignore sub @all @all @all @all @all @all @all @az' @all @all @all @all @all @all @all @all @all @all;
        sub @all @all @all @all @all @all @az' @all @all @all @all @all @all @all @all @all @all by @AZ;

#14,15,16
        ignore sub @all @all @all @all @all @all @az' @all @all @all @all @all @all @all @all; 
        sub @all @all @all @all @all @az' @all @all @all @all @all @all @all @all by @AZ;

#12,13
        ignore sub @all @all @all @all @all @az' @all @all @all @all @all @all; 
        sub @all @all @all @all @az' @all @all @all @all @all @all @all by @AZ;

#9,10,11
        #ignore sub @all @all @all @all @az' @all @all @all @all @all @all; 
        ignore sub @all @all @all @all @az' @all @all @all @all @all; 
        sub @all @all @all @az' @all @all @all @all @all by @AZ;

#7,8
        ignore sub @all @all @all @az' @all @all @all @all; 
        sub @all @all @az' @all @all @all @all by @AZ;

#4,5,6
        #ignore sub @all @all @az' @all @all @all; 
        ignore sub @all @all @az' @all @all;
        sub @all @az' @all @all by @AZ;

#1,2,3 
        ignore sub @all @az';
        sub @az' by @AZ;

} calt;

If you don't need any special characters, and just want the basic reading functionality, I guess the basic latin-alphabet would also be enough. The complete reduced code would look like this. This code version should work with every font out there, because it's just using the basic latin alphabet and no special characters at all.

feature calt {

        @az = [a-z A-Z];
        @AZ = [a.bold-z.bold A.bold-Z.bold];

    @all = [@az @AZ];

#17
        ignore sub @all @all @all @all @all @all @all @az' @all @all @all @all @all @all @all @all @all @all;
        sub @all @all @all @all @all @all @az' @all @all @all @all @all @all @all @all @all @all by @AZ;

#14,15,16
        ignore sub @all @all @all @all @all @all @az' @all @all @all @all @all @all @all @all; 
        sub @all @all @all @all @all @az' @all @all @all @all @all @all @all @all by @AZ;

#12,13
        ignore sub @all @all @all @all @all @az' @all @all @all @all @all @all; 
        sub @all @all @all @all @az' @all @all @all @all @all @all @all by @AZ;

#9,10,11
        #ignore sub @all @all @all @all @az' @all @all @all @all @all @all; 
        ignore sub @all @all @all @all @az' @all @all @all @all @all; 
        sub @all @all @all @az' @all @all @all @all @all by @AZ;

#7,8
        ignore sub @all @all @all @az' @all @all @all @all; 
        sub @all @all @az' @all @all @all @all by @AZ;

#4,5,6
        #ignore sub @all @all @az' @all @all @all; 
        ignore sub @all @all @az' @all @all;
        sub @all @az' @all @all by @AZ;

#1,2,3 
        ignore sub @all @az';
        sub @az' by @AZ;

} calt;