Test font-variant-* and font-feature-settings with TrueType fonts
authormmaxfield@apple.com <mmaxfield@apple.com@268f45cc-cd09-0410-ab3c-d52691b4dbfc>
Thu, 8 Oct 2015 00:33:55 +0000 (00:33 +0000)
committermmaxfield@apple.com <mmaxfield@apple.com@268f45cc-cd09-0410-ab3c-d52691b4dbfc>
Thu, 8 Oct 2015 00:33:55 +0000 (00:33 +0000)
https://bugs.webkit.org/show_bug.cgi?id=149776

Reviewed by Simon Fraser.

Tools:

This test extends our existing FontWithFeatures project to be able to generate a
TrueType font. This font is conceptually similar as the existing OpenType font,
except the feature -> character mapping is different.

The font itself only supports the following characters:
ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz
However, the shape of these letters are either an X or a check mark.
The letter "A" always is a check mark.
The letter "B" always is an X.
Each font feature has an letter associated with it. When the font feature is enabled,
that letter is shown as a check mark. For example, when
"kLowerCaseType / kLowerCaseSmallCapsSelector" is enabled, "S" is shown as a check
mark.

Here are the mappings of font features to letters:
kLigaturesType / kCommonLigaturesOnSelector: C
kLigaturesType / kContextualLigaturesOnSelector: D
kLigaturesType / kRareLigaturesOnSelector: G
kLigaturesType / kHistoricalLigaturesOnSelector: I
kContextualAlternatesType / kContextualAlternatesOnSelector: L
kVerticalPositionType / kInferiorsSelector: O
kVerticalPositionType / kSuperiorsSelector: P
kLowerCaseType / kLowerCaseSmallCapsSelector: S
kUpperCaseType / kUpperCaseSmallCapsSelector: V
kLowerCaseType / kLowerCasePetiteCapsSelector: T
kUpperCaseType / kUpperCasePetiteCapsSelector: W
kLetterCaseType / 14: Y
kStyleOptionsType / kTitlingCapsSelector: a
kNumberCaseType / kUpperCaseNumbersSelector: c
kNumberCaseType / kLowerCaseNumbersSelector: d
kNumberSpacingType / kProportionalNumbersSelector: f
kNumberSpacingType / kMonospacedNumbersSelector: g
kFractionsType / kDiagonalFractionsSelector: i
kFractionsType / kVerticalFractionsSelector: j
kVerticalPositionType / kOrdinalsSelector: Q
kTypographicExtrasType / kSlashedZeroOnSelector: k
kLigaturesType / kHistoricalLigaturesOnSelector: K
kCharacterShapeType / kJIS1978CharactersSelector: m
kCharacterShapeType / kJIS1983CharactersSelector: n
kCharacterShapeType / kJIS1990CharactersSelector: o
kCharacterShapeType / kJIS2004CharactersSelector: p
kCharacterShapeType / kSimplifiedCharactersSelector: q
kCharacterShapeType / kTraditionalCharactersSelector: r
kTextSpacingType / kMonospacedTextSelector: t
kTextSpacingType / kProportionalTextSelector: u
kRubyKanaType / kRubyKanaOnSelector: v

* FontWithFeatures/FontWithFeatures.xcodeproj/project.pbxproj:
* FontWithFeatures/FontWithFeatures/FontCreator.cpp:
(CFFBuilder::moveTo):
(CFFBuilder::lineTo):
(GLYFBuilder::GLYFBuilder):
(GLYFBuilder::takeResult):
(GLYFBuilder::moveTo):
(GLYFBuilder::lineTo):
(GLYFBuilder::closePath):
(GLYFBuilder::writePoint):
(GLYFBuilder::append16):
(generateBoxCharString):
(generateCheckCharString):
(generateXCharString):
(itemForGlyph):
(Generator::generate):
(Generator::insertSelector):
(Generator::insertFeature):
(Generator::generateFeatureDescription):
(Generator::appendCFFTable):
(Generator::appendGLYFTable):
(Generator::appendLOCATable):
(Generator::appendFEATTable):
(Generator::appendMetamorphosisChain):
(Generator::appendMORXTable):
(Generator::appendHEADTable):
(Generator::appendHMTXTable):
(Generator::appendNameSubtable):
(Generator::append2ByteASCIIString):
(Generator::appendNAMETable):
(generateFont):
(CFFBuilder::curveToCubic): Deleted.
(charStringForGlyph): Deleted.
* FontWithFeatures/FontWithFeatures/FontCreator.h:
* FontWithFeatures/FontWithFeatures/main.cpp:
(constructFontWithTrueTypeFeature):
(constructFontWithOpenTypeFeature):
(drawText):
(main):
(drawTextWithFeature): Deleted.

LayoutTests:

* css3/font-feature-settings-rendering-2-expected.html:
* css3/font-feature-settings-rendering-2.html:
* css3/resources/FontWithFeatures.ttf: Added.

git-svn-id: https://svn.webkit.org/repository/webkit/trunk@190697 268f45cc-cd09-0410-ab3c-d52691b4dbfc

LayoutTests/ChangeLog
LayoutTests/css3/font-feature-settings-rendering-2-expected.html
LayoutTests/css3/font-feature-settings-rendering-2.html
LayoutTests/css3/resources/FontWithFeatures.ttf [new file with mode: 0644]
Tools/ChangeLog
Tools/FontWithFeatures/FontWithFeatures.xcodeproj/project.pbxproj
Tools/FontWithFeatures/FontWithFeatures/FontCreator.cpp
Tools/FontWithFeatures/FontWithFeatures/FontCreator.h
Tools/FontWithFeatures/FontWithFeatures/main.cpp

index 5766507..757035e 100644 (file)
@@ -1,3 +1,14 @@
+2015-10-07  Myles C. Maxfield  <mmaxfield@apple.com>
+
+        Test font-variant-* and font-feature-settings with TrueType fonts
+        https://bugs.webkit.org/show_bug.cgi?id=149776
+
+        Reviewed by Simon Fraser.
+
+        * css3/font-feature-settings-rendering-2-expected.html:
+        * css3/font-feature-settings-rendering-2.html:
+        * css3/resources/FontWithFeatures.ttf: Added.
+
 2015-10-07  Mark Lam  <mark.lam@apple.com>
 
         Disable tail calls because it is breaking some sites.
index a002260..a8515a2 100644 (file)
@@ -6,6 +6,10 @@
     font-family: "FontFeaturesTest";
     src: url("resources/FontWithFeatures.otf") format("opentype");
 }
+@font-face {
+    font-family: "FontFeaturesTestTTF";
+    src: url("resources/FontWithFeatures.ttf") format("truetype");
+}
 </style>
 </head>
 <body>
@@ -43,5 +47,39 @@ marks and X below.
 <span style="font-family: FontFeaturesTest;">BA</span>
 <span style="font-family: FontFeaturesTest;">BA</span>
 </div>
+<div><span style="font-family: FontFeaturesTestTTF;">BA</span>
+<span style="font-family: FontFeaturesTestTTF;">BA</span>
+<span style="font-family: FontFeaturesTestTTF;">BA</span>
+<span style="font-family: FontFeaturesTestTTF;">BA</span>
+<span style="font-family: FontFeaturesTestTTF;">BA</span>
+<span style="font-family: FontFeaturesTestTTF;">BA</span>
+<span style="font-family: FontFeaturesTestTTF;">BA</span>
+<span style="font-family: FontFeaturesTestTTF;">BA</span>
+<span style="font-family: FontFeaturesTestTTF;">BA</span>
+<span style="font-family: FontFeaturesTestTTF;">BA</span>
+<span style="font-family: FontFeaturesTestTTF;">BA</span>
+<span style="font-family: FontFeaturesTestTTF;">BA</span>
+<span style="font-family: FontFeaturesTestTTF;">BA</span>
+<span style="font-family: FontFeaturesTestTTF;">BA</span>
+<span style="font-family: FontFeaturesTestTTF;">BA</span>
+<span style="font-family: FontFeaturesTestTTF;">BA</span>
+<span style="font-family: FontFeaturesTestTTF;">BA</span>
+<span style="font-family: FontFeaturesTestTTF;">BA</span>
+<span style="font-family: FontFeaturesTestTTF;">BA</span>
+<span style="font-family: FontFeaturesTestTTF;">BA</span>
+<span style="font-family: FontFeaturesTestTTF;">BA</span>
+<span style="font-family: FontFeaturesTestTTF;">BA</span>
+<span style="font-family: FontFeaturesTestTTF;">BA</span>
+<span style="font-family: FontFeaturesTestTTF;">BA</span>
+<span style="font-family: FontFeaturesTestTTF;">BA</span>
+<span style="font-family: FontFeaturesTestTTF;">BA</span>
+<span style="font-family: FontFeaturesTestTTF;">BA</span>
+<span style="font-family: FontFeaturesTestTTF;">BA</span>
+<span style="font-family: FontFeaturesTestTTF;">BA</span>
+<span style="font-family: FontFeaturesTestTTF;">BA</span>
+<span style="font-family: FontFeaturesTestTTF;">BA</span>
+<span style="font-family: FontFeaturesTestTTF;">BA</span>
+<span style="font-family: FontFeaturesTestTTF;">BA</span>
+</div>
 </body>
 </html>
index 9eff14c..ec1e679 100644 (file)
@@ -6,56 +6,97 @@
     font-family: "FontFeaturesTest";
     src: url("resources/FontWithFeatures.otf") format("opentype");
 }
+@font-face {
+    font-family: "FontFeaturesTestTTF";
+    src: url("resources/FontWithFeatures.ttf") format("truetype");
+}
 </style>
 </head>
 <body>
 This tests that font features are able to be turned on and off as desired. It uses a special font
 designed specifically for this purpose. The test passes if you see a sequence of alternating check
 marks and X below.
-<div id="insertionpoint"></div>
+<div id="insertionPoint"></div>
+<div id="insertionPoint2"></div>
 <script>
-var insertionpoint = document.getElementById("insertionpoint");
-function addElement(feature, c) {
+var insertionPoint = document.getElementById("insertionPoint");
+var insertionPoint2 = document.getElementById("insertionPoint2");
+function addElement(placeToInsert, familyName, feature, c) {
     ["0", "1"].map(function(state) {
         var element = document.createElement("span");
         element.textContent = c;
-        element.style.fontFamily = "FontFeaturesTest";
+        element.style.fontFamily = familyName;
         element.style.fontFeatureSettings = '"' + feature + '" ' + state;
-        insertionpoint.appendChild(element);
+        placeToInsert.appendChild(element);
     });
-    insertionpoint.appendChild(document.createTextNode(" "));
+    placeToInsert.appendChild(document.createTextNode(" "));
 }
-addElement("liga", "C");
-addElement("clig", "D");
-addElement("dlig", "E");
-addElement("hlig", "F");
-addElement("calt", "G");
-addElement("subs", "H");
-addElement("sups", "I");
-addElement("smcp", "J");
-addElement("c2sc", "K");
-addElement("pcap", "L");
-addElement("c2pc", "M");
-addElement("unic", "N");
-addElement("titl", "O");
-addElement("lnum", "P");
-addElement("onum", "Q");
-addElement("pnum", "R");
-addElement("tnum", "S");
-addElement("frac", "T");
-//addElement("afrc", "U");
-addElement("ordn", "V");
-addElement("zero", "W");
-addElement("hist", "X");
-addElement("jp78", "Y");
-addElement("jp83", "Z");
-addElement("jp90", "a");
-addElement("jp04", "b");
-addElement("smpl", "c");
-addElement("trad", "d");
-addElement("fwid", "e");
-addElement("pwid", "f");
-addElement("ruby", "g");
+
+addElement(insertionPoint, "FontFeaturesTest", "liga", "C");
+addElement(insertionPoint, "FontFeaturesTest", "clig", "D");
+addElement(insertionPoint, "FontFeaturesTest", "dlig", "E");
+addElement(insertionPoint, "FontFeaturesTest", "hlig", "F");
+addElement(insertionPoint, "FontFeaturesTest", "calt", "G");
+addElement(insertionPoint, "FontFeaturesTest", "subs", "H");
+addElement(insertionPoint, "FontFeaturesTest", "sups", "I");
+addElement(insertionPoint, "FontFeaturesTest", "smcp", "J");
+addElement(insertionPoint, "FontFeaturesTest", "c2sc", "K");
+addElement(insertionPoint, "FontFeaturesTest", "pcap", "L");
+addElement(insertionPoint, "FontFeaturesTest", "c2pc", "M");
+addElement(insertionPoint, "FontFeaturesTest", "unic", "N");
+addElement(insertionPoint, "FontFeaturesTest", "titl", "O");
+addElement(insertionPoint, "FontFeaturesTest", "lnum", "P");
+addElement(insertionPoint, "FontFeaturesTest", "onum", "Q");
+addElement(insertionPoint, "FontFeaturesTest", "pnum", "R");
+addElement(insertionPoint, "FontFeaturesTest", "tnum", "S");
+addElement(insertionPoint, "FontFeaturesTest", "frac", "T");
+//addElement(insertionPoint, "FontFeaturesTest", "afrc", "U");
+addElement(insertionPoint, "FontFeaturesTest", "ordn", "V");
+addElement(insertionPoint, "FontFeaturesTest", "zero", "W");
+addElement(insertionPoint, "FontFeaturesTest", "hist", "X");
+addElement(insertionPoint, "FontFeaturesTest", "jp78", "Y");
+addElement(insertionPoint, "FontFeaturesTest", "jp83", "Z");
+addElement(insertionPoint, "FontFeaturesTest", "jp90", "a");
+addElement(insertionPoint, "FontFeaturesTest", "jp04", "b");
+addElement(insertionPoint, "FontFeaturesTest", "smpl", "c");
+addElement(insertionPoint, "FontFeaturesTest", "trad", "d");
+addElement(insertionPoint, "FontFeaturesTest", "fwid", "e");
+addElement(insertionPoint, "FontFeaturesTest", "pwid", "f");
+addElement(insertionPoint, "FontFeaturesTest", "ruby", "g");
+
+addElement(insertionPoint2, "FontFeaturesTestTTF", "liga", "C");
+addElement(insertionPoint2, "FontFeaturesTestTTF", "liga", "D");
+addElement(insertionPoint2, "FontFeaturesTestTTF", "clig", "C");
+addElement(insertionPoint2, "FontFeaturesTestTTF", "clig", "D");
+addElement(insertionPoint2, "FontFeaturesTestTTF", "dlig", "G");
+addElement(insertionPoint2, "FontFeaturesTestTTF", "hlig", "I");
+addElement(insertionPoint2, "FontFeaturesTestTTF", "calt", "L");
+addElement(insertionPoint2, "FontFeaturesTestTTF", "subs", "O");
+addElement(insertionPoint2, "FontFeaturesTestTTF", "sups", "P");
+addElement(insertionPoint2, "FontFeaturesTestTTF", "smcp", "S");
+addElement(insertionPoint2, "FontFeaturesTestTTF", "c2sc", "V");
+addElement(insertionPoint2, "FontFeaturesTestTTF", "pcap", "T");
+addElement(insertionPoint2, "FontFeaturesTestTTF", "c2pc", "W");
+addElement(insertionPoint2, "FontFeaturesTestTTF", "unic", "Y");
+addElement(insertionPoint2, "FontFeaturesTestTTF", "titl", "a");
+addElement(insertionPoint2, "FontFeaturesTestTTF", "lnum", "c");
+addElement(insertionPoint2, "FontFeaturesTestTTF", "onum", "d");
+addElement(insertionPoint2, "FontFeaturesTestTTF", "pnum", "f");
+addElement(insertionPoint2, "FontFeaturesTestTTF", "tnum", "g");
+addElement(insertionPoint2, "FontFeaturesTestTTF", "frac", "i");
+addElement(insertionPoint2, "FontFeaturesTestTTF", "afrc", "j");
+addElement(insertionPoint2, "FontFeaturesTestTTF", "ordn", "Q");
+addElement(insertionPoint2, "FontFeaturesTestTTF", "zero", "k");
+addElement(insertionPoint2, "FontFeaturesTestTTF", "hist", "K");
+addElement(insertionPoint2, "FontFeaturesTestTTF", "jp78", "m");
+addElement(insertionPoint2, "FontFeaturesTestTTF", "jp83", "n");
+addElement(insertionPoint2, "FontFeaturesTestTTF", "jp90", "o");
+addElement(insertionPoint2, "FontFeaturesTestTTF", "jp04", "p");
+addElement(insertionPoint2, "FontFeaturesTestTTF", "smpl", "q");
+addElement(insertionPoint2, "FontFeaturesTestTTF", "trad", "r");
+addElement(insertionPoint2, "FontFeaturesTestTTF", "fwid", "t");
+addElement(insertionPoint2, "FontFeaturesTestTTF", "pwid", "u");
+addElement(insertionPoint2, "FontFeaturesTestTTF", "ruby", "v");
 </script>
 </body>
 </html>
diff --git a/LayoutTests/css3/resources/FontWithFeatures.ttf b/LayoutTests/css3/resources/FontWithFeatures.ttf
new file mode 100644 (file)
index 0000000..e64da1c
Binary files /dev/null and b/LayoutTests/css3/resources/FontWithFeatures.ttf differ
index ac3c442..f31b204 100644 (file)
@@ -1,3 +1,98 @@
+2015-10-07  Myles C. Maxfield  <mmaxfield@apple.com>
+
+        Test font-variant-* and font-feature-settings with TrueType fonts
+        https://bugs.webkit.org/show_bug.cgi?id=149776
+
+        Reviewed by Simon Fraser.
+
+        This test extends our existing FontWithFeatures project to be able to generate a
+        TrueType font. This font is conceptually similar as the existing OpenType font,
+        except the feature -> character mapping is different.
+
+        The font itself only supports the following characters:
+        ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz
+        However, the shape of these letters are either an X or a check mark.
+        The letter "A" always is a check mark.
+        The letter "B" always is an X.
+        Each font feature has an letter associated with it. When the font feature is enabled,
+        that letter is shown as a check mark. For example, when
+        "kLowerCaseType / kLowerCaseSmallCapsSelector" is enabled, "S" is shown as a check
+        mark.
+
+        Here are the mappings of font features to letters:
+        kLigaturesType / kCommonLigaturesOnSelector: C
+        kLigaturesType / kContextualLigaturesOnSelector: D
+        kLigaturesType / kRareLigaturesOnSelector: G
+        kLigaturesType / kHistoricalLigaturesOnSelector: I
+        kContextualAlternatesType / kContextualAlternatesOnSelector: L
+        kVerticalPositionType / kInferiorsSelector: O
+        kVerticalPositionType / kSuperiorsSelector: P
+        kLowerCaseType / kLowerCaseSmallCapsSelector: S
+        kUpperCaseType / kUpperCaseSmallCapsSelector: V
+        kLowerCaseType / kLowerCasePetiteCapsSelector: T
+        kUpperCaseType / kUpperCasePetiteCapsSelector: W
+        kLetterCaseType / 14: Y
+        kStyleOptionsType / kTitlingCapsSelector: a
+        kNumberCaseType / kUpperCaseNumbersSelector: c
+        kNumberCaseType / kLowerCaseNumbersSelector: d
+        kNumberSpacingType / kProportionalNumbersSelector: f
+        kNumberSpacingType / kMonospacedNumbersSelector: g
+        kFractionsType / kDiagonalFractionsSelector: i
+        kFractionsType / kVerticalFractionsSelector: j
+        kVerticalPositionType / kOrdinalsSelector: Q
+        kTypographicExtrasType / kSlashedZeroOnSelector: k
+        kLigaturesType / kHistoricalLigaturesOnSelector: K
+        kCharacterShapeType / kJIS1978CharactersSelector: m
+        kCharacterShapeType / kJIS1983CharactersSelector: n
+        kCharacterShapeType / kJIS1990CharactersSelector: o
+        kCharacterShapeType / kJIS2004CharactersSelector: p
+        kCharacterShapeType / kSimplifiedCharactersSelector: q
+        kCharacterShapeType / kTraditionalCharactersSelector: r
+        kTextSpacingType / kMonospacedTextSelector: t
+        kTextSpacingType / kProportionalTextSelector: u
+        kRubyKanaType / kRubyKanaOnSelector: v
+
+        * FontWithFeatures/FontWithFeatures.xcodeproj/project.pbxproj:
+        * FontWithFeatures/FontWithFeatures/FontCreator.cpp:
+        (CFFBuilder::moveTo):
+        (CFFBuilder::lineTo):
+        (GLYFBuilder::GLYFBuilder):
+        (GLYFBuilder::takeResult):
+        (GLYFBuilder::moveTo):
+        (GLYFBuilder::lineTo):
+        (GLYFBuilder::closePath):
+        (GLYFBuilder::writePoint):
+        (GLYFBuilder::append16):
+        (generateBoxCharString):
+        (generateCheckCharString):
+        (generateXCharString):
+        (itemForGlyph):
+        (Generator::generate):
+        (Generator::insertSelector):
+        (Generator::insertFeature):
+        (Generator::generateFeatureDescription):
+        (Generator::appendCFFTable):
+        (Generator::appendGLYFTable):
+        (Generator::appendLOCATable):
+        (Generator::appendFEATTable):
+        (Generator::appendMetamorphosisChain):
+        (Generator::appendMORXTable):
+        (Generator::appendHEADTable):
+        (Generator::appendHMTXTable):
+        (Generator::appendNameSubtable):
+        (Generator::append2ByteASCIIString):
+        (Generator::appendNAMETable):
+        (generateFont):
+        (CFFBuilder::curveToCubic): Deleted.
+        (charStringForGlyph): Deleted.
+        * FontWithFeatures/FontWithFeatures/FontCreator.h:
+        * FontWithFeatures/FontWithFeatures/main.cpp:
+        (constructFontWithTrueTypeFeature):
+        (constructFontWithOpenTypeFeature):
+        (drawText):
+        (main):
+        (drawTextWithFeature): Deleted.
+
 2015-10-07  Commit Queue  <commit-queue@webkit.org>
 
         Unreviewed, rolling out r190572, r190593, r190594, and
index c5ff570..41c74bc 100644 (file)
                                GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
                                GCC_WARN_UNUSED_FUNCTION = YES;
                                GCC_WARN_UNUSED_VARIABLE = YES;
-                               MACOSX_DEPLOYMENT_TARGET = 10.11;
+                               MACOSX_DEPLOYMENT_TARGET = 10.10;
                                MTL_ENABLE_DEBUG_INFO = YES;
                                ONLY_ACTIVE_ARCH = YES;
                                SDKROOT = macosx;
                                GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
                                GCC_WARN_UNUSED_FUNCTION = YES;
                                GCC_WARN_UNUSED_VARIABLE = YES;
-                               MACOSX_DEPLOYMENT_TARGET = 10.11;
+                               MACOSX_DEPLOYMENT_TARGET = 10.10;
                                MTL_ENABLE_DEBUG_INFO = NO;
                                SDKROOT = macosx;
                        };
                                C28626AB1BA902B9001961D6 /* Release */,
                        );
                        defaultConfigurationIsVisible = 0;
+                       defaultConfigurationName = Release;
                };
 /* End XCConfigurationList section */
        };
index 7a3e141..b85bd20 100644 (file)
@@ -82,7 +82,6 @@ static void writeCFFEncodedNumber(V& vector, float number)
 }
 
 static const char rLineTo = 0x05;
-static const char rrCurveTo = 0x08;
 static const char endChar = 0x0e;
 static const char rMoveTo = 0x15;
 
@@ -102,14 +101,9 @@ public:
         return std::move(result);
     }
 
-    void moveTo(std::pair<float, float> targetPoint, bool closed)
+    void moveTo(std::pair<float, float> targetPoint)
     {
-        if (closed && !result.empty())
-            closePath();
-
-        std::pair<float, float> destination = targetPoint;
-
-        writePoint(destination);
+        writePoint(targetPoint);
         result.push_back(rMoveTo);
 
         startingPoint = current;
@@ -117,24 +111,10 @@ public:
 
     void lineTo(std::pair<float, float> targetPoint)
     {
-        std::pair<float, float> destination = targetPoint;
-
-        writePoint(destination);
+        writePoint(targetPoint);
         result.push_back(rLineTo);
     }
 
-    void curveToCubic(std::pair<float, float> point1, std::pair<float, float> point2, std::pair<float, float> targetPoint)
-    {
-        std::pair<float, float> destination1 = point1;
-        std::pair<float, float> destination2 = point2;
-        std::pair<float, float> destination3 = targetPoint;
-
-        writePoint(destination1);
-        writePoint(destination2);
-        writePoint(destination3);
-        result.push_back(rrCurveTo);
-    }
-
     void closePath()
     {
         if (current != startingPoint)
@@ -156,10 +136,94 @@ private:
     std::pair<float, float> current;
 };
 
+class GLYFBuilder {
+public:
+    GLYFBuilder(float, std::pair<float, float>)
+    {
+    }
+
+    std::vector<uint8_t> takeResult()
+    {
+        std::vector<uint8_t> result;
+        append16(result, endPtsOfContours.size());
+        append16(result, clampTo<int16_t>(minX));
+        append16(result, clampTo<int16_t>(minY));
+        append16(result, clampTo<int16_t>(maxX));
+        append16(result, clampTo<int16_t>(maxY));
+
+        for (uint16_t p : endPtsOfContours)
+            append16(result, p);
+        append16(result, 0);
+        for (uint8_t f : flags)
+            result.push_back(f);
+        for (uint16_t c : xCoordinates)
+            append16(result, c);
+        for (uint16_t c : yCoordinates)
+            append16(result, c);
+
+        return result;
+    }
+
+    void moveTo(std::pair<float, float> targetPoint)
+    {
+        writePoint(targetPoint, true);
+
+        startingPoint = current;
+    }
+
+    void lineTo(std::pair<float, float> targetPoint)
+    {
+        writePoint(targetPoint, true);
+    }
+
+    void closePath()
+    {
+        if (current != startingPoint)
+            lineTo(startingPoint);
+        endPtsOfContours.push_back(pointCount - 1);
+    }
+
+private:
+    void writePoint(std::pair<float, float> destination, bool onCurve)
+    {
+        flags.push_back(onCurve ? 1 : 0); // Flags
+
+        std::pair<float, float> delta = std::make_pair(destination.first - current.first, destination.second - current.second);
+        xCoordinates.push_back(delta.first);
+        yCoordinates.push_back(delta.second);
+
+        current = destination;
+        minX = std::min(minX, destination.first);
+        maxX = std::max(maxX, destination.first);
+        minY = std::min(minY, destination.second);
+        maxY = std::max(maxY, destination.second);
+        ++pointCount;
+    }
+
+    static void append16(std::vector<uint8_t>& destination, uint16_t value)
+    {
+        destination.push_back(value >> 8);
+        destination.push_back(value);
+    }
+
+    std::vector<uint16_t> endPtsOfContours;
+    std::vector<uint8_t> flags;
+    std::vector<int16_t> xCoordinates;
+    std::vector<int16_t> yCoordinates;
+    std::pair<float, float> startingPoint;
+    std::pair<float, float> current;
+    float minX { std::numeric_limits<float>::max() };
+    float maxX { std::numeric_limits<float>::min() };
+    float minY { std::numeric_limits<float>::max() };
+    float maxY { std::numeric_limits<float>::min() };
+    unsigned pointCount { 0 };
+};
+
+template <typename T>
 std::vector<uint8_t> generateBoxCharString()
 {
-    CFFBuilder builder(unitsPerEm, std::make_pair(0.f, 0.f));
-    builder.moveTo(std::make_pair(200.f, 200.f), false);
+    T builder(unitsPerEm, std::make_pair(0.f, 0.f));
+    builder.moveTo(std::make_pair(200.f, 200.f));
     builder.lineTo(std::make_pair(200.f, 800.f));
     builder.lineTo(std::make_pair(800.f, 800.f));
     builder.lineTo(std::make_pair(800.f, 200.f));
@@ -167,10 +231,11 @@ std::vector<uint8_t> generateBoxCharString()
     return builder.takeResult();
 }
 
+template <typename T>
 std::vector<uint8_t> generateCheckCharString()
 {
-    CFFBuilder builder(unitsPerEm, std::make_pair(0.f, 0.f));
-    builder.moveTo(std::make_pair(200.f, 500.f), false);
+    T builder(unitsPerEm, std::make_pair(0.f, 0.f));
+    builder.moveTo(std::make_pair(200.f, 500.f));
     builder.lineTo(std::make_pair(250.f, 550.f));
     builder.lineTo(std::make_pair(500.f, 300.f));
     builder.lineTo(std::make_pair(900.f, 700.f));
@@ -180,10 +245,11 @@ std::vector<uint8_t> generateCheckCharString()
     return builder.takeResult();
 }
 
+template <typename T>
 std::vector<uint8_t> generateXCharString()
 {
-    CFFBuilder builder(unitsPerEm, std::make_pair(0.f, 0.f));
-    builder.moveTo(std::make_pair(500.0f, 550.0f), false);
+    T builder(unitsPerEm, std::make_pair(0.f, 0.f));
+    builder.moveTo(std::make_pair(500.0f, 550.0f));
     builder.lineTo(std::make_pair(900.f, 950.f));
     builder.lineTo(std::make_pair(950.f, 900.f));
     builder.lineTo(std::make_pair(550.f, 500.f));
@@ -199,7 +265,8 @@ std::vector<uint8_t> generateXCharString()
     return builder.takeResult();
 }
 
-std::vector<uint8_t>& charStringForGlyph(uint16_t glyph, std::vector<uint8_t>& boxCharString, std::vector<uint8_t>& checkCharString, std::vector<uint8_t>& xCharString)
+template<typename T>
+const T& itemForGlyph(uint16_t glyph, const T& boxCharString, const T& checkCharString, const T& xCharString)
 {
     if (!glyph)
         return boxCharString;
@@ -208,18 +275,43 @@ std::vector<uint8_t>& charStringForGlyph(uint16_t glyph, std::vector<uint8_t>& b
     return xCharString;
 }
 
+struct FeatureSelector {
+    uint16_t selector;
+    std::string name;
+    uint16_t stringIndex;
+    bool defaultSelector;
+};
+
+struct FeatureType {
+    uint16_t type;
+    std::string name;
+    uint16_t stringIndex;
+    size_t settingTableOffsetLocation;
+    std::vector<FeatureSelector> selectors;
+    bool exclusive;
+};
+
 class Generator {
 public:
-    std::vector<uint8_t> generate()
+    std::vector<uint8_t> generate(Type type)
     {
-        uint16_t numTables = 10;
+        featureDescription = generateFeatureDescription();
+
+        uint16_t numTables = type == Type::OpenType ? 10 : 12;
         uint16_t roundedNumTables = roundDownToPowerOfTwo(numTables);
         uint16_t searchRange = roundedNumTables * 16; // searchRange: "(Maximum power of 2 <= numTables) x 16."
 
-        result.push_back('O');
-        result.push_back('T');
-        result.push_back('T');
-        result.push_back('O');
+        if (type == Type::OpenType) {
+            result.push_back('O');
+            result.push_back('T');
+            result.push_back('T');
+            result.push_back('O');
+        } else {
+            result.push_back('t');
+            result.push_back('r');
+            result.push_back('u');
+            result.push_back('e');
+        }
         append16(numTables);
         append16(searchRange);
         append16(integralLog2(roundedNumTables)); // entrySelector: "Log2(maximum power of 2 <= numTables)."
@@ -231,15 +323,25 @@ public:
         for (size_t i = 0; i < directoryEntrySize * numTables; ++i)
             result.push_back(0);
 
-        appendTable("CFF ", &Generator::appendCFFTable);
-        appendTable("GSUB", &Generator::appendGSUBTable);
+        if (type == Type::OpenType) {
+            appendTable("CFF ", &Generator::appendCFFTable);
+            appendTable("GSUB", &Generator::appendGSUBTable);
+        }
         appendTable("OS/2", &Generator::appendOS2Table);
         appendTable("cmap", &Generator::appendCMAPTable);
+        if (type == Type::TrueType) {
+            appendTable("feat", &Generator::appendFEATTable);
+            appendTable("glyf", &Generator::appendGLYFTable);
+        }
         auto headTableOffset = result.size();
         appendTable("head", &Generator::appendHEADTable);
         appendTable("hhea", &Generator::appendHHEATable);
         appendTable("hmtx", &Generator::appendHMTXTable);
+        if (type == Type::TrueType)
+            appendTable("loca", &Generator::appendLOCATable);
         appendTable("maxp", &Generator::appendMAXPTable);
+        if (type == Type::TrueType)
+            appendTable("morx", &Generator::appendMORXTable);
         appendTable("name", &Generator::appendNAMETable);
         appendTable("post", &Generator::appendPOSTTable);
 
@@ -248,6 +350,7 @@ public:
         // checksumAdjustment: "To compute: set it to 0, calculate the checksum for the 'head' table and put it in the table directory,
         // sum the entire font as uint32, then store B1B0AFBA - sum. The checksum for the 'head' table will now be wrong. That is OK."
         overwrite32(headTableOffset + 8, 0xB1B0AFBAU - calculateChecksum(0, result.size()));
+
         return std::move(result);
     }
 
@@ -331,6 +434,132 @@ private:
         result[location + 2] = value >> 8;
         result[location + 3] = value;
     }
+
+    void insertSelector(std::vector<FeatureSelector>& selectors, uint16_t selector, std::string selectorString, bool defaultSelector)
+    {
+        selectors.push_back({selector, selectorString, m_stringIndex++, defaultSelector});
+    }
+
+    void insertFeature(std::vector<FeatureType>& result, uint16_t type, std::string typeString, uint16_t selector, std::string selectorString, bool exclusive)
+    {
+        // O(n) but performance is not an issue here
+        for (size_t i = 0; i < result.size(); ++i) {
+            if (result[i].type == type) {
+                insertSelector(result[i].selectors, selector, selectorString, false);
+                return;
+            }
+        }
+        result.push_back({type, typeString, m_stringIndex++, 0, std::vector<FeatureSelector>(), exclusive});
+        insertSelector(result[result.size() - 1].selectors, selector, selectorString, true);
+    }
+
+    static const uint16_t kCharacterShapeType = 20;
+    static const uint16_t kContextualAlternatesType = 36;
+    static const uint16_t kFractionsType = 11;
+    static const uint16_t kLetterCaseType = 3;
+    static const uint16_t kLigaturesType = 1;
+    static const uint16_t kLowerCaseType = 37;
+    static const uint16_t kNumberCaseType = 21;
+    static const uint16_t kNumberSpacingType = 6;
+    static const uint16_t kRubyKanaType = 28;
+    static const uint16_t kStyleOptionsType = 19;
+    static const uint16_t kTextSpacingType = 22;
+    static const uint16_t kTypographicExtrasType = 14;
+    static const uint16_t kUpperCaseType = 38;
+    static const uint16_t kVerticalPositionType = 10;
+
+    static const uint16_t kCommonLigaturesOffSelector = 3;
+    static const uint16_t kCommonLigaturesOnSelector = 2;
+    static const uint16_t kContextualAlternatesOffSelector = 1;
+    static const uint16_t kContextualAlternatesOnSelector = 0;
+    static const uint16_t kContextualLigaturesOffSelector = 19;
+    static const uint16_t kContextualLigaturesOnSelector = 18;
+    static const uint16_t kDiagonalFractionsSelector = 2;
+    static const uint16_t kHistoricalLigaturesOffSelector = 21;
+    static const uint16_t kHistoricalLigaturesOnSelector = 20;
+    static const uint16_t kInferiorsSelector = 2;
+    static const uint16_t kJIS1978CharactersSelector = 2;
+    static const uint16_t kJIS1983CharactersSelector = 3;
+    static const uint16_t kJIS1990CharactersSelector = 4;
+    static const uint16_t kJIS2004CharactersSelector = 11;
+    static const uint16_t kLowerCaseNumbersSelector = 0;
+    static const uint16_t kLowerCasePetiteCapsSelector = 2;
+    static const uint16_t kLowerCaseSmallCapsSelector = 1;
+    static const uint16_t kMonospacedNumbersSelector = 0;
+    static const uint16_t kMonospacedTextSelector = 1;
+    static const uint16_t kOrdinalsSelector = 3;
+    static const uint16_t kProportionalNumbersSelector = 1;
+    static const uint16_t kProportionalTextSelector = 0;
+    static const uint16_t kRareLigaturesOffSelector = 5;
+    static const uint16_t kRareLigaturesOnSelector = 4;
+    static const uint16_t kRubyKanaOnSelector = 2;
+    static const uint16_t kRubyKanaSelector = 1;
+    static const uint16_t kSimplifiedCharactersSelector = 1;
+    static const uint16_t kSlashedZeroOnSelector = 4;
+    static const uint16_t kSuperiorsSelector = 1;
+    static const uint16_t kTitlingCapsSelector = 4;
+    static const uint16_t kTraditionalCharactersSelector = 0;
+    static const uint16_t kUpperCaseNumbersSelector = 1;
+    static const uint16_t kUpperCasePetiteCapsSelector = 2;
+    static const uint16_t kUpperCaseSmallCapsSelector = 1;
+    static const uint16_t kVerticalFractionsSelector = 1;
+
+    static const uint16_t defaultUnusedSelector = 99;
+
+    std::vector<FeatureType> generateFeatureDescription()
+    {
+        std::vector<FeatureType> result;
+
+        // For any given feature type, the first selector inside it is the default selector for that type.
+        insertFeature(result, kLigaturesType, "kLigaturesType", kCommonLigaturesOnSelector, "kCommonLigaturesOnSelector", false);
+        insertFeature(result, kLigaturesType, "kLigaturesType", kContextualLigaturesOnSelector, "kContextualLigaturesOnSelector", false);
+        insertFeature(result, kLigaturesType, "kLigaturesType", kCommonLigaturesOffSelector, "kCommonLigaturesOffSelector", false);
+        insertFeature(result, kLigaturesType, "kLigaturesType", kContextualLigaturesOffSelector, "kContextualLigaturesOffSelector", false);
+        insertFeature(result, kLigaturesType, "kLigaturesType", kRareLigaturesOnSelector, "kRareLigaturesOnSelector", false);
+        insertFeature(result, kLigaturesType, "kLigaturesType", kRareLigaturesOffSelector, "kRareLigaturesOffSelector", false);
+        insertFeature(result, kLigaturesType, "kLigaturesType", kHistoricalLigaturesOnSelector, "kHistoricalLigaturesOnSelector", false);
+        insertFeature(result, kLigaturesType, "kLigaturesType", kHistoricalLigaturesOffSelector, "kHistoricalLigaturesOffSelector", false);
+        insertFeature(result, kContextualAlternatesType, "kContextualAlternatesType", kContextualAlternatesOnSelector, "kContextualAlternatesOnSelector", false);
+        insertFeature(result, kContextualAlternatesType, "kContextualAlternatesType", kContextualAlternatesOffSelector, "kContextualAlternatesOffSelector", false);
+        insertFeature(result, kVerticalPositionType, "kVerticalPositionType", defaultUnusedSelector, "defaultUnusedSelector", true);
+        insertFeature(result, kVerticalPositionType, "kVerticalPositionType", kInferiorsSelector, "kInferiorsSelector", true);
+        insertFeature(result, kVerticalPositionType, "kVerticalPositionType", kSuperiorsSelector, "kSuperiorsSelector", true);
+        insertFeature(result, kLowerCaseType, "kLowerCaseType", defaultUnusedSelector, "defaultUnusedSelector", true);
+        insertFeature(result, kUpperCaseType, "kUpperCaseType", defaultUnusedSelector, "defaultUnusedSelector", true);
+        insertFeature(result, kLowerCaseType, "kLowerCaseType", kLowerCaseSmallCapsSelector, "kLowerCaseSmallCapsSelector", true);
+        insertFeature(result, kUpperCaseType, "kUpperCaseType", kUpperCaseSmallCapsSelector, "kUpperCaseSmallCapsSelector", true);
+        insertFeature(result, kLowerCaseType, "kLowerCaseType", kLowerCasePetiteCapsSelector, "kLowerCasePetiteCapsSelector", true);
+        insertFeature(result, kUpperCaseType, "kUpperCaseType", kUpperCasePetiteCapsSelector, "kUpperCasePetiteCapsSelector", true);
+        insertFeature(result, kLetterCaseType, "kLetterCaseType", defaultUnusedSelector, "defaultUnusedSelector", true);
+        insertFeature(result, kLetterCaseType, "kLetterCaseType", 14, "14", true);
+        insertFeature(result, kStyleOptionsType, "kStyleOptionsType", defaultUnusedSelector, "defaultUnusedSelector", true);
+        insertFeature(result, kStyleOptionsType, "kStyleOptionsType", kTitlingCapsSelector, "kTitlingCapsSelector", true);
+        insertFeature(result, kNumberCaseType, "kNumberCaseType", defaultUnusedSelector, "defaultUnusedSelector", true);
+        insertFeature(result, kNumberCaseType, "kNumberCaseType", kUpperCaseNumbersSelector, "kUpperCaseNumbersSelector", true);
+        insertFeature(result, kNumberCaseType, "kNumberCaseType", kLowerCaseNumbersSelector, "kLowerCaseNumbersSelector", true);
+        insertFeature(result, kNumberSpacingType, "kNumberSpacingType", defaultUnusedSelector, "defaultUnusedSelector", true);
+        insertFeature(result, kNumberSpacingType, "kNumberSpacingType", kProportionalNumbersSelector, "kProportionalNumbersSelector", true);
+        insertFeature(result, kNumberSpacingType, "kNumberSpacingType", kMonospacedNumbersSelector, "kMonospacedNumbersSelector", true);
+        insertFeature(result, kFractionsType, "kFractionsType", defaultUnusedSelector, "defaultUnusedSelector", true);
+        insertFeature(result, kFractionsType, "kFractionsType", kDiagonalFractionsSelector, "kDiagonalFractionsSelector", true);
+        insertFeature(result, kFractionsType, "kFractionsType", kVerticalFractionsSelector, "kVerticalFractionsSelector", true);
+        insertFeature(result, kVerticalPositionType, "kVerticalPositionType", kOrdinalsSelector, "kOrdinalsSelector", true);
+        insertFeature(result, kTypographicExtrasType, "kTypographicExtrasType", kSlashedZeroOnSelector, "kSlashedZeroOnSelector", false);
+        insertFeature(result, kLigaturesType, "kLigaturesType", kHistoricalLigaturesOnSelector, "kHistoricalLigaturesOnSelector", false);
+        insertFeature(result, kCharacterShapeType, "kCharacterShapeType", defaultUnusedSelector, "defaultUnusedSelector", true);
+        insertFeature(result, kCharacterShapeType, "kCharacterShapeType", kJIS1978CharactersSelector, "kJIS1978CharactersSelector", true);
+        insertFeature(result, kCharacterShapeType, "kCharacterShapeType", kJIS1983CharactersSelector, "kJIS1983CharactersSelector", true);
+        insertFeature(result, kCharacterShapeType, "kCharacterShapeType", kJIS1990CharactersSelector, "kJIS1990CharactersSelector", true);
+        insertFeature(result, kCharacterShapeType, "kCharacterShapeType", kJIS2004CharactersSelector, "kJIS2004CharactersSelector", true);
+        insertFeature(result, kCharacterShapeType, "kCharacterShapeType", kSimplifiedCharactersSelector, "kSimplifiedCharactersSelector", true);
+        insertFeature(result, kCharacterShapeType, "kCharacterShapeType", kTraditionalCharactersSelector, "kTraditionalCharactersSelector", true);
+        insertFeature(result, kTextSpacingType, "kTextSpacingType", defaultUnusedSelector, "defaultUnusedSelector", true);
+        insertFeature(result, kTextSpacingType, "kTextSpacingType", kMonospacedTextSelector, "kMonospacedTextSelector", true);
+        insertFeature(result, kTextSpacingType, "kTextSpacingType", kProportionalTextSelector, "kProportionalTextSelector", true);
+        insertFeature(result, kRubyKanaType, "kRubyKanaType", kRubyKanaOnSelector, "kRubyKanaOnSelector", false);
+
+        return result;
+    }
     
     void appendCFFTable()
     {
@@ -418,9 +647,9 @@ private:
             append16(i);
 
         // CharStrings INDEX
-        std::vector<uint8_t> boxCharString = generateBoxCharString();
-        std::vector<uint8_t> checkCharString = generateCheckCharString();
-        std::vector<uint8_t> xCharString = generateXCharString();
+        std::vector<uint8_t> boxCharString = generateBoxCharString<CFFBuilder>();
+        std::vector<uint8_t> checkCharString = generateCheckCharString<CFFBuilder>();
+        std::vector<uint8_t> xCharString = generateXCharString<CFFBuilder>();
         assert(numGlyphs > 26);
         overwrite32(charstringsOffsetLocation, static_cast<uint32_t>(result.size() - startingOffset));
         append16(numGlyphs);
@@ -428,15 +657,134 @@ private:
         offset = 1;
         append32(offset);
         for (uint16_t glyph = 0; glyph < numGlyphs; ++glyph) {
-            offset += charStringForGlyph(glyph, boxCharString, checkCharString, xCharString).size();
+            offset += itemForGlyph(glyph, boxCharString, checkCharString, xCharString).size();
             append32(offset);
         }
         for (uint16_t glyph = 0; glyph < numGlyphs; ++glyph) {
-            std::vector<uint8_t>& charString = charStringForGlyph(glyph, boxCharString, checkCharString, xCharString);
+            const std::vector<uint8_t>& charString = itemForGlyph(glyph, boxCharString, checkCharString, xCharString);
+            result.insert(result.end(), charString.begin(), charString.end());
+        }
+    }
+
+    // Keep in sync with loca
+    void appendGLYFTable()
+    {
+        std::vector<uint8_t> boxCharString = generateBoxCharString<GLYFBuilder>();
+        std::vector<uint8_t> checkCharString = generateCheckCharString<GLYFBuilder>();
+        std::vector<uint8_t> xCharString = generateXCharString<GLYFBuilder>();
+        for (uint16_t glyph = 0; glyph < numGlyphs; ++glyph) {
+            const std::vector<uint8_t>& charString = itemForGlyph(glyph, boxCharString, checkCharString, xCharString);
             result.insert(result.end(), charString.begin(), charString.end());
         }
     }
 
+    // Keep in sync with glyf
+    void appendLOCATable()
+    {
+        std::vector<uint8_t> boxCharString = generateBoxCharString<GLYFBuilder>();
+        std::vector<uint8_t> checkCharString = generateCheckCharString<GLYFBuilder>();
+        std::vector<uint8_t> xCharString = generateXCharString<GLYFBuilder>();
+        uint32_t index = 0;
+        for (uint16_t glyph = 0; glyph < numGlyphs; ++glyph) {
+            append32(index);
+            index += itemForGlyph(glyph, boxCharString, checkCharString, xCharString).size();
+        }
+        append32(index);
+    }
+
+    void appendFEATTable()
+    {
+        size_t tableLocation = result.size();
+        append32(0x00010000); // Version
+        append16(featureDescription.size()); // Number of entries in the feature name array
+        append16(0); // reserved
+        append32(0); // reserved
+
+        // Feature name array
+        for (FeatureType& type : featureDescription) {
+            append16(type.type); // Feature type
+            append16(type.selectors.size()); // Number of settings
+            type.settingTableOffsetLocation = result.size();
+            append32(0); // Offset in bytes from beginning of this table to feature's setting name array
+            append16(type.exclusive ? 0x8000 : 0); // Flags. 0x8000 = Exclusive
+            append16(type.stringIndex + m_baseStringIndex); // Index in the name table for the name of this feature
+        }
+
+        // Setting name array
+        for (FeatureType& type : featureDescription) {
+            overwrite32(type.settingTableOffsetLocation, static_cast<uint32_t>(result.size() - tableLocation));
+            for (FeatureSelector& selector : type.selectors) {
+                append16(selector.selector); // Setting: kNormalPositionSelector (initial setting is default)
+                append16(selector.stringIndex + m_baseStringIndex); // Index in the name table for the name of this setting
+            }
+        }
+    }
+
+    void appendMetamorphosisChain(const FeatureType& type, const FeatureSelector& selector, uint16_t glyphToReplace, uint16_t withMe)
+    {
+        size_t chainLocation = result.size();
+        append32(type.exclusive && selector.defaultSelector ? 1 : 0); // Default flags
+        size_t chainSizeLocation = result.size();
+        append32(0); // Placeholder for chain length in bytes (padded to multiple of 4)
+        append32(2); // Number of feature subtable entries
+        append32(1); // Number of subtables in the chain
+
+        // Feature table
+        append16(type.type); // Feature type
+        append16(selector.selector); // Feature selector
+        append32(1); // Enable flags
+        append32(0xFFFFFFFF); // disable flags
+
+        // Feature table 2
+        append16(0); // Feature type: kAllTypographicFeaturesType
+        append16(1); // Feature selector: kAllTypeFeaturesOffSelector
+        append32(0); // Enable flags
+        append32(0); // disable flags
+
+        // Metamorphosis subtable
+        size_t metamorphosisSubtableSizeLocation = result.size();
+        append32(0); // Placeholder for chain length in bytes (padded to multiple of 4)
+        append32(4); // Coverage flags and subtable type. Subtable type 4: Noncontextual ("swash") subtable
+        append32(1); // subFeature flags
+
+        // Non-contextual glyph substitution subtable
+        append16(6); // Lookup format: sorted list of (glyph index, lookup value) pairs
+        
+        // BinSrchHeader
+        append16(4); // Size of a lookup unit for this search in bytes
+        append16(1); // Number of units to be searched
+        append16(4); // Search range: The value of unitSize times the largest power of 2 that is less than or equal to the value of nUnits.
+        append16(0); // Entry selector: The log base 2 of the largest power of 2 less than or equal to the value of nUnits.
+        append16(0); // Range shift: The value of unitSize times the difference of the value of nUnits minus the largest power of 2 less than or equal to the value of nUnits.
+        // Entries
+        append16(glyphToReplace);
+        append16(withMe);
+
+        overwrite32(metamorphosisSubtableSizeLocation, static_cast<uint32_t>(result.size() - metamorphosisSubtableSizeLocation));
+
+        while (result.size() % 4)
+            result.push_back(0);
+        overwrite32(chainSizeLocation, static_cast<uint32_t>(result.size() - chainLocation));
+    }
+
+    void appendMORXTable()
+    {
+        append16(2); // Version
+        append16(0); // Unused
+        size_t numberOfChainsLocation = result.size();
+        append32(0); // Number of metamorphosis chains placeholder
+
+        int count = 0;
+        for (FeatureType& type : featureDescription) {
+            for (FeatureSelector& selector : type.selectors) {
+                appendMetamorphosisChain(type, selector, count + 3, 1);
+                count++;
+            }
+        }
+    
+        overwrite32(numberOfChainsLocation, count);
+    }
+
     void appendSubstitutionSubtable(size_t subtableRecordLocation, uint16_t iGetReplaced, uint16_t replacedWithMe)
     {
         overwrite16(subtableRecordLocation + 6, result.size() - subtableRecordLocation);
@@ -677,7 +1025,7 @@ private:
         append16(0); // Traits
         append16(3); // Smallest readable size in pixels
         append16(0); // Might contain LTR or RTL glyphs
-        append16(0); // Short offsets in the 'loca' table. However, OTF fonts don't have a 'loca' table so this is irrelevant
+        append16(1); // Long offsets in the 'loca' table.
         append16(0); // Glyph data format
     }
     
@@ -705,8 +1053,8 @@ private:
     void appendHMTXTable()
     {
         for (unsigned i = 0; i < numGlyphs; ++i) {
-            append16(clampTo<uint16_t>(unitsPerEm)); // horizontal advance
-            append16(clampTo<int16_t>(0)); // left side bearing
+            append16(clampTo<uint16_t>(static_cast<int32_t>(unitsPerEm))); // horizontal advance
+            append16(itemForGlyph(i, 200, 200, 50)); // left side bearing
         }
     }
     
@@ -728,25 +1076,47 @@ private:
         append16(numGlyphs); // Maximum number of glyphs referenced at top level
         append16(0); // No compound glyphs
     }
-    
-    void appendNAMETable()
-    {
-        std::string fontName = "MylesFont";
-
-        append16(0); // Format selector
-        append16(1); // Number of name records in table
-        append16(18); // Offset in bytes to the beginning of name character strings
 
+    void appendNameSubtable(const std::string& s, uint16_t nameIdentifier)
+    {
         append16(0); // Unicode
         append16(3); // Unicode version 2.0 or later
         append16(0); // Language
-        append16(1); // Name identifier. 1 = Font family
-        append16(fontName.length());
-        append16(0); // Offset into name data
+        append16(m_baseStringIndex + nameIdentifier); // Name identifier
+        append16(s.length());
+        append16(m_nameOffset); // Offset into name data
+        m_nameOffset += s.size() * 2; // Code units get 2 bytes each
+    }
 
-        for (auto codeUnit : fontName)
+    void append2ByteASCIIString(std::string& s)
+    {
+        for (auto codeUnit : s)
             append16(codeUnit);
     }
+
+    void appendNAMETable()
+    {
+        std::string familyName = "MylesFont"; // 1: Font Family
+
+        uint16_t numberOfRecords = m_stringIndex + 1;
+        append16(0); // Format selector
+        append16(numberOfRecords); // Number of name records in table
+        append16(6 + 12 * numberOfRecords); // Offset in bytes to the beginning of name character strings
+
+        appendNameSubtable(familyName, 1); // 1: Font Family
+        for (FeatureType& type : featureDescription) {
+            appendNameSubtable(type.name, type.stringIndex);
+            for (FeatureSelector& selector : type.selectors)
+                appendNameSubtable(selector.name, selector.stringIndex);
+        }
+
+        append2ByteASCIIString(familyName);
+        for (FeatureType& type : featureDescription) {
+            append2ByteASCIIString(type.name);
+            for (FeatureSelector& selector : type.selectors)
+                append2ByteASCIIString(selector.name);
+        }
+    }
     
     void appendPOSTTable()
     {
@@ -796,10 +1166,14 @@ private:
     }
 
     unsigned m_tablesAppendedCount { 0 };
+    unsigned m_nameOffset { 0 };
+    static constexpr uint16_t m_baseStringIndex { 257 };
+    uint16_t m_stringIndex { 0 };
+    std::vector<FeatureType> featureDescription;
     std::vector<uint8_t> result;
 };
 
-std::vector<uint8_t> generateFont()
+std::vector<uint8_t> generateFont(Type type)
 {
-    return Generator().generate();
+    return Generator().generate(type);
 }
index b192086..c3f5638 100644 (file)
 
 #include <vector>
 
-std::vector<uint8_t> generateFont();
+enum class Type {
+    OpenType,
+    TrueType
+};
+
+std::vector<uint8_t> generateFont(Type);
 
 #endif /* FontCreator_h */
index 162edf7..b7ab50e 100644 (file)
 #include <ImageIO/ImageIO.h>
 #include <fstream>
 
-void drawTextWithFeature(CGContextRef context, CTFontDescriptorRef fontDescriptor, CFStringRef feature, int value, CGPoint location)
+static CTFontDescriptorRef constructFontWithTrueTypeFeature(CTFontDescriptorRef fontDescriptor, int type, int selector)
 {
-    CGFloat fontSize = 25;
-    CGContextSetTextMatrix(context, CGAffineTransformScale(CGAffineTransformIdentity, 1, 1));
-    CGContextSetTextPosition(context, location.x, location.y);
+    CFNumberRef typeValue = CFNumberCreate(kCFAllocatorDefault, kCFNumberIntType, &type);
+    CFNumberRef selectorValue = CFNumberCreate(kCFAllocatorDefault, kCFNumberIntType, &selector);
+    CFTypeRef featureDictionaryKeys[] = { kCTFontFeatureTypeIdentifierKey, kCTFontFeatureSelectorIdentifierKey };
+    CFTypeRef featureDictionaryValues[] = { typeValue, selectorValue };
+    CFDictionaryRef featureDictionary = CFDictionaryCreate(kCFAllocatorDefault, featureDictionaryKeys, featureDictionaryValues, 2, &kCFTypeDictionaryKeyCallBacks, &kCFTypeDictionaryValueCallBacks);
+    CFRelease(typeValue);
+    CFRelease(selectorValue);
+
+    CFTypeRef featureSettingsValues[] = { featureDictionary };
+    CFArrayRef fontFeatureSettings = CFArrayCreate(kCFAllocatorDefault, featureSettingsValues, 1, &kCFTypeArrayCallBacks);
+    CFRelease(featureDictionary);
 
+    CFTypeRef fontDescriptorKeys[] = { kCTFontFeatureSettingsAttribute };
+    CFTypeRef fontDescriptorValues[] = { fontFeatureSettings };
+    CFDictionaryRef fontDescriptorAttributes = CFDictionaryCreate(kCFAllocatorDefault, fontDescriptorKeys, fontDescriptorValues, 1, &kCFTypeDictionaryKeyCallBacks, &kCFTypeDictionaryValueCallBacks);
+    CFRelease(fontFeatureSettings);
+
+    CTFontDescriptorRef modifiedFontDescriptor = CTFontDescriptorCreateCopyWithAttributes(fontDescriptor, fontDescriptorAttributes);
+    CFRelease(fontDescriptorAttributes);
+    return modifiedFontDescriptor;
+}
+
+static CTFontDescriptorRef constructFontWithOpenTypeFeature(CTFontDescriptorRef fontDescriptor, CFStringRef feature, int value)
+{
     CFNumberRef featureValue = CFNumberCreate(kCFAllocatorDefault, kCFNumberIntType, &value);
     CFTypeRef featureDictionaryKeys[] = { kCTFontOpenTypeFeatureTag, kCTFontOpenTypeFeatureValue };
     CFTypeRef featureDictionaryValues[] = { feature, featureValue };
@@ -55,14 +75,20 @@ void drawTextWithFeature(CGContextRef context, CTFontDescriptorRef fontDescripto
 
     CTFontDescriptorRef modifiedFontDescriptor = CTFontDescriptorCreateCopyWithAttributes(fontDescriptor, fontDescriptorAttributes);
     CFRelease(fontDescriptorAttributes);
+    return modifiedFontDescriptor;
+}
 
-    CTFontRef font = CTFontCreateWithFontDescriptor(modifiedFontDescriptor, fontSize, nullptr);
-    CFRelease(modifiedFontDescriptor);
+static void drawText(CGContextRef context, CTFontDescriptorRef fontDescriptor, CFStringRef prefix, CGPoint location)
+{
+    CGContextSetTextMatrix(context, CGAffineTransformScale(CGAffineTransformIdentity, 1, 1));
+    CGContextSetTextPosition(context, location.x, location.y);
+
+    CGFloat fontSize = 25;
+    CTFontRef font = CTFontCreateWithFontDescriptor(fontDescriptor, fontSize, nullptr);
 
     CFMutableStringRef string = CFStringCreateMutable(kCFAllocatorDefault, 0);
-    CFStringAppend(string, feature);
-    CFStringAppend(string, value ? CFSTR("  (on)") : CFSTR(" (off)"));
-    CFStringAppend(string, CFSTR(": ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz"));
+    CFStringAppend(string, prefix);
+    CFStringAppend(string, CFSTR("ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz"));
 
     CGColorRef red = CGColorCreateGenericRGB(1, 0, 0, 1);
     CFTypeRef lineKeys[] = { kCTForegroundColorAttributeName };
@@ -72,16 +98,16 @@ void drawTextWithFeature(CGContextRef context, CTFontDescriptorRef fontDescripto
 
     CFAttributedStringRef attributedString = CFAttributedStringCreate(kCFAllocatorDefault, string, lineAttributes);
     CFRelease(lineAttributes);
-    CFRelease(string);
 
     CFMutableAttributedStringRef mutableAttributedString = CFAttributedStringCreateMutableCopy(kCFAllocatorDefault, 0, attributedString);
     CFRelease(attributedString);
 
     CTFontRef monospaceFont = CTFontCreateWithName(CFSTR("Courier"), fontSize, nullptr);
-    CFAttributedStringSetAttribute(mutableAttributedString, CFRangeMake(0, 12), kCTFontAttributeName, monospaceFont);
+    CFAttributedStringSetAttribute(mutableAttributedString, CFRangeMake(0, CFStringGetLength(prefix)), kCTFontAttributeName, monospaceFont);
     CFRelease(monospaceFont);
 
-    CFAttributedStringSetAttribute(mutableAttributedString, CFRangeMake(12, 52), kCTFontAttributeName, font);
+    CFAttributedStringSetAttribute(mutableAttributedString, CFRangeMake(CFStringGetLength(prefix), CFStringGetLength(string) - CFStringGetLength(prefix)), kCTFontAttributeName, font);
+    CFRelease(string);
     CFRelease(font);
 
     CTLineRef line = CTLineCreateWithAttributedString(mutableAttributedString);
@@ -93,13 +119,14 @@ void drawTextWithFeature(CGContextRef context, CTFontDescriptorRef fontDescripto
 
 int main(int argc, const char * argv[])
 {
-    size_t width = 2000;
+    size_t width = 2500;
     size_t height = 2000;
     CGColorSpaceRef colorSpace = CGColorSpaceCreateDeviceRGB();
     CGContextRef context = CGBitmapContextCreate(nullptr, width, height, 8, width * 4, colorSpace, kCGImageAlphaNoneSkipLast);
     CGColorSpaceRelease(colorSpace);
-    const std::vector<uint8_t> fontVector = generateFont();
-    std::ofstream outputFile("/Volumes/Data/home/mmaxfield/tmp/output.otf", std::ios::out | std::ios::binary);
+    Type type = Type::TrueType;
+    const std::vector<uint8_t> fontVector = generateFont(type);
+    std::ofstream outputFile("/Volumes/Data/home/mmaxfield/tmp/output.ttf", std::ios::out | std::ios::binary);
     for (uint8_t b : fontVector)
         outputFile << b;
     outputFile.close();
@@ -108,15 +135,85 @@ int main(int argc, const char * argv[])
     CTFontDescriptorRef fontDescriptor = CTFontManagerCreateFontDescriptorFromData(fontData);
     CFRelease(fontData);
 
-    CFTypeRef featureValues[] = { CFSTR("liga"), CFSTR("clig"), CFSTR("dlig"), CFSTR("hlig"), CFSTR("calt"), CFSTR("subs"), CFSTR("sups"), CFSTR("smcp"), CFSTR("c2sc"), CFSTR("pcap"), CFSTR("c2pc"), CFSTR("unic"), CFSTR("titl"), CFSTR("onum"), CFSTR("pnum"), CFSTR("tnum"), CFSTR("frac"), CFSTR("afrc"), CFSTR("ordn"), CFSTR("zero"), CFSTR("hist"), CFSTR("jp78"), CFSTR("jp83"), CFSTR("jp90"), CFSTR("jp04"), CFSTR("smpl"), CFSTR("trad"), CFSTR("fwid"), CFSTR("pwid"), CFSTR("ruby") };
-    CFArrayRef features = CFArrayCreate(kCFAllocatorDefault, featureValues, 30, &kCFTypeArrayCallBacks);
-
-    for (CFIndex i = 0; i < CFArrayGetCount(features); ++i) {
-        drawTextWithFeature(context, fontDescriptor, static_cast<CFStringRef>(CFArrayGetValueAtIndex(features, i)), 1, CGPointMake(25, 1950 - 50 * i));
-        drawTextWithFeature(context, fontDescriptor, static_cast<CFStringRef>(CFArrayGetValueAtIndex(features, i)), 0, CGPointMake(25, 1925 - 50 * i));
+    if (type == Type::OpenType) {
+        CFTypeRef featureValuesOpenType[] = { CFSTR("liga"), CFSTR("clig"), CFSTR("dlig"), CFSTR("hlig"), CFSTR("calt"), CFSTR("subs"), CFSTR("sups"), CFSTR("smcp"), CFSTR("c2sc"), CFSTR("pcap"), CFSTR("c2pc"), CFSTR("unic"), CFSTR("titl"), CFSTR("onum"), CFSTR("pnum"), CFSTR("tnum"), CFSTR("frac"), CFSTR("afrc"), CFSTR("ordn"), CFSTR("zero"), CFSTR("hist"), CFSTR("jp78"), CFSTR("jp83"), CFSTR("jp90"), CFSTR("jp04"), CFSTR("smpl"), CFSTR("trad"), CFSTR("fwid"), CFSTR("pwid"), CFSTR("ruby") };
+        CFArrayRef features = CFArrayCreate(kCFAllocatorDefault, featureValuesOpenType, 30, &kCFTypeArrayCallBacks);
+
+        for (CFIndex i = 0; i < CFArrayGetCount(features); ++i) {
+            CFStringRef feature = static_cast<CFStringRef>(CFArrayGetValueAtIndex(features, i));
+            CTFontDescriptorRef modifiedFontDescriptor = constructFontWithOpenTypeFeature(fontDescriptor, feature, 1);
+            CFMutableStringRef prefix = CFStringCreateMutable(kCFAllocatorDefault, 0);
+            CFStringAppend(prefix, feature);
+            CFStringAppend(prefix, CFSTR("  (on): "));
+            drawText(context, modifiedFontDescriptor, prefix, CGPointMake(25, 1950 - 50 * i));
+            CFRelease(prefix);
+            CFRelease(modifiedFontDescriptor);
+
+            modifiedFontDescriptor = constructFontWithOpenTypeFeature(fontDescriptor, feature, 0);
+            prefix = CFStringCreateMutable(kCFAllocatorDefault, 0);
+            CFStringAppend(prefix, feature);
+            CFStringAppend(prefix, CFSTR(" (off): "));
+            drawText(context, modifiedFontDescriptor, prefix, CGPointMake(25, 1925 - 50 * i));
+            CFRelease(prefix);
+            CFRelease(modifiedFontDescriptor);
+        }
+
+        CFRelease(features);
+    } else {
+        __block int i = 0;
+        void (^handler)(uint16_t type, CFStringRef typeString, uint16_t selector, CFStringRef selectorString) = ^(uint16_t type, CFStringRef typeString, uint16_t selector, CFStringRef selectorString)
+        {
+            CTFontDescriptorRef modifiedFontDescriptor = constructFontWithTrueTypeFeature(fontDescriptor, type, selector);
+            CFMutableStringRef prefix = CFStringCreateMutable(kCFAllocatorDefault, 0);
+            CFStringAppend(prefix, typeString);
+            CFStringAppend(prefix, CFSTR(": "));
+            CFStringAppend(prefix, selectorString);
+            CFStringAppend(prefix, CFSTR(": "));
+            while (CFStringGetLength(prefix) < 65)
+                CFStringAppend(prefix, CFSTR(" "));
+            drawText(context, modifiedFontDescriptor, prefix, CGPointMake(25, 1950 - 40 * i));
+            CFRelease(prefix);
+            CFRelease(modifiedFontDescriptor);
+            ++i;
+        };
+        handler(kLigaturesType, CFSTR("kLigaturesType"), kCommonLigaturesOnSelector, CFSTR("kCommonLigaturesOnSelector"));
+        handler(kLigaturesType, CFSTR("kLigaturesType"), kContextualLigaturesOnSelector, CFSTR("kContextualLigaturesOnSelector"));
+        handler(kLigaturesType, CFSTR("kLigaturesType"), kCommonLigaturesOffSelector, CFSTR("kCommonLigaturesOffSelector"));
+        handler(kLigaturesType, CFSTR("kLigaturesType"), kContextualLigaturesOffSelector, CFSTR("kContextualLigaturesOffSelector"));
+        handler(kLigaturesType, CFSTR("kLigaturesType"), kRareLigaturesOnSelector, CFSTR("kRareLigaturesOnSelector"));
+        handler(kLigaturesType, CFSTR("kLigaturesType"), kRareLigaturesOffSelector, CFSTR("kRareLigaturesOffSelector"));
+        handler(kLigaturesType, CFSTR("kLigaturesType"), kHistoricalLigaturesOnSelector, CFSTR("kHistoricalLigaturesOnSelector"));
+        handler(kLigaturesType, CFSTR("kLigaturesType"), kHistoricalLigaturesOffSelector, CFSTR("kHistoricalLigaturesOffSelector"));
+        handler(kContextualAlternatesType, CFSTR("kContextualAlternatesType"), kContextualAlternatesOnSelector, CFSTR("kContextualAlternatesOnSelector"));
+        handler(kContextualAlternatesType, CFSTR("kContextualAlternatesType"), kContextualAlternatesOffSelector, CFSTR("kContextualAlternatesOffSelector"));
+        handler(kVerticalPositionType, CFSTR("kVerticalPositionType"), kInferiorsSelector, CFSTR("kInferiorsSelector"));
+        handler(kVerticalPositionType, CFSTR("kVerticalPositionType"), kSuperiorsSelector, CFSTR("kSuperiorsSelector"));
+        handler(kLowerCaseType, CFSTR("kLowerCaseType"), kLowerCaseSmallCapsSelector, CFSTR("kLowerCaseSmallCapsSelector"));
+        handler(kUpperCaseType, CFSTR("kUpperCaseType"), kUpperCaseSmallCapsSelector, CFSTR("kUpperCaseSmallCapsSelector"));
+        handler(kLowerCaseType, CFSTR("kLowerCaseType"), kLowerCasePetiteCapsSelector, CFSTR("kLowerCasePetiteCapsSelector"));
+        handler(kUpperCaseType, CFSTR("kUpperCaseType"), kUpperCasePetiteCapsSelector, CFSTR("kUpperCasePetiteCapsSelector"));
+        handler(kLetterCaseType, CFSTR("kLetterCaseType"), 14, CFSTR("14"));
+        handler(kStyleOptionsType, CFSTR("kStyleOptionsType"), kTitlingCapsSelector, CFSTR("kTitlingCapsSelector"));
+        handler(kNumberCaseType, CFSTR("kNumberCaseType"), kUpperCaseNumbersSelector, CFSTR("kUpperCaseNumbersSelector"));
+        handler(kNumberCaseType, CFSTR("kNumberCaseType"), kLowerCaseNumbersSelector, CFSTR("kLowerCaseNumbersSelector"));
+        handler(kNumberSpacingType, CFSTR("kNumberSpacingType"), kProportionalNumbersSelector, CFSTR("kProportionalNumbersSelector"));
+        handler(kNumberSpacingType, CFSTR("kNumberSpacingType"), kMonospacedNumbersSelector, CFSTR("kMonospacedNumbersSelector"));
+        handler(kFractionsType, CFSTR("kFractionsType"), kDiagonalFractionsSelector, CFSTR("kDiagonalFractionsSelector"));
+        handler(kFractionsType, CFSTR("kFractionsType"), kVerticalFractionsSelector, CFSTR("kVerticalFractionsSelector"));
+        handler(kVerticalPositionType, CFSTR("kVerticalPositionType"), kOrdinalsSelector, CFSTR("kOrdinalsSelector"));
+        handler(kTypographicExtrasType, CFSTR("kTypographicExtrasType"), kSlashedZeroOnSelector, CFSTR("kSlashedZeroOnSelector"));
+        handler(kLigaturesType, CFSTR("kLigaturesType"), kHistoricalLigaturesOnSelector, CFSTR("kHistoricalLigaturesOnSelector"));
+        handler(kCharacterShapeType, CFSTR("kCharacterShapeType"), kJIS1978CharactersSelector, CFSTR("kJIS1978CharactersSelector"));
+        handler(kCharacterShapeType, CFSTR("kCharacterShapeType"), kJIS1983CharactersSelector, CFSTR("kJIS1983CharactersSelector"));
+        handler(kCharacterShapeType, CFSTR("kCharacterShapeType"), kJIS1990CharactersSelector, CFSTR("kJIS1990CharactersSelector"));
+        handler(kCharacterShapeType, CFSTR("kCharacterShapeType"), kJIS2004CharactersSelector, CFSTR("kJIS2004CharactersSelector"));
+        handler(kCharacterShapeType, CFSTR("kCharacterShapeType"), kSimplifiedCharactersSelector, CFSTR("kSimplifiedCharactersSelector"));
+        handler(kCharacterShapeType, CFSTR("kCharacterShapeType"), kTraditionalCharactersSelector, CFSTR("kTraditionalCharactersSelector"));
+        handler(kTextSpacingType, CFSTR("kTextSpacingType"), kMonospacedTextSelector, CFSTR("kMonospacedTextSelector"));
+        handler(kTextSpacingType, CFSTR("kTextSpacingType"), kProportionalTextSelector, CFSTR("kProportionalTextSelector"));
+        handler(kRubyKanaType, CFSTR("kRubyKanaType"), kRubyKanaOnSelector, CFSTR("kRubyKanaOnSelector"));
     }
 
-    CFRelease(features);
     CFRelease(fontDescriptor);
     CGImageRef image = CGBitmapContextCreateImage(context);
     CGContextRelease(context);