blob: b6b660b43f1a1c61975a3ed5fec7fcd451601218 [file] [log] [blame]
/*
* Copyright (C) 2015 Apple Inc. All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions
* are met:
* 1. Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
* 2. Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
*
* THIS SOFTWARE IS PROVIDED BY APPLE INC. AND ITS CONTRIBUTORS ``AS IS''
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
* THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
* PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL APPLE INC. OR ITS CONTRIBUTORS
* BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
* CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
* SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
* INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
* CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
* ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF
* THE POSSIBILITY OF SUCH DAMAGE.
*/
#include "FontCreator.h"
#include <array>
#include <cassert>
#include <string>
static const size_t headerSize = 12;
static const size_t directoryEntrySize = 16;
static const int16_t unitsPerEm = 1024;
static const uint16_t numGlyphs = 26 * 2 + 1;
static inline uint16_t integralLog2(uint16_t x)
{
uint16_t result = 0;
while (x >>= 1)
++result;
return result;
}
static inline uint16_t roundDownToPowerOfTwo(uint16_t x)
{
x |= x >> 1;
x |= x >> 2;
x |= x >> 4;
x |= x >> 8;
return (x >> 1) + 1;
}
static inline bool isFourByteAligned(size_t x)
{
return !(x & 3);
}
// Assumption: T2 can hold every value that a T1 can hold.
template<typename T1, typename T2> static inline T1 clampTo(T2 x)
{
x = std::min(x, static_cast<T2>(std::numeric_limits<T1>::max()));
x = std::max(x, static_cast<T2>(std::numeric_limits<T1>::min()));
return static_cast<T1>(x);
}
template <typename V>
static inline void append32(V& result, uint32_t value)
{
result.push_back(value >> 24);
result.push_back(value >> 16);
result.push_back(value >> 8);
result.push_back(value);
}
template <typename V>
static void writeCFFEncodedNumber(V& vector, float number)
{
vector.push_back(0xFF);
// Convert to 16.16 fixed-point
append32(vector, clampTo<int32_t>(number * 0x10000));
}
static const char rLineTo = 0x05;
static const char endChar = 0x0e;
static const char rMoveTo = 0x15;
class CFFBuilder {
public:
CFFBuilder(float width, std::pair<float, float> origin)
{
writeCFFEncodedNumber(result, width);
writeCFFEncodedNumber(result, origin.first);
writeCFFEncodedNumber(result, origin.second);
result.push_back(rMoveTo);
}
std::vector<uint8_t> takeResult()
{
result.push_back(endChar);
return std::move(result);
}
void moveTo(std::pair<float, float> targetPoint)
{
writePoint(targetPoint);
result.push_back(rMoveTo);
startingPoint = current;
}
void lineTo(std::pair<float, float> targetPoint)
{
writePoint(targetPoint);
result.push_back(rLineTo);
}
void closePath()
{
if (current != startingPoint)
lineTo(startingPoint);
}
private:
void writePoint(std::pair<float, float> destination)
{
std::pair<float, float> delta = std::make_pair(destination.first - current.first, destination.second - current.second);
writeCFFEncodedNumber(result, delta.first);
writeCFFEncodedNumber(result, delta.second);
current = destination;
}
std::vector<uint8_t> result;
std::pair<float, float> startingPoint;
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()
{
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));
builder.closePath();
return builder.takeResult();
}
template <typename T>
std::vector<uint8_t> generateCheckCharString()
{
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));
builder.lineTo(std::make_pair(950.f, 650.f));
builder.lineTo(std::make_pair(500.f, 200.f));
builder.closePath();
return builder.takeResult();
}
template <typename T>
std::vector<uint8_t> generateXCharString()
{
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));
builder.lineTo(std::make_pair(950.f, 100.f));
builder.lineTo(std::make_pair(900.f, 50.f));
builder.lineTo(std::make_pair(500.f, 450.f));
builder.lineTo(std::make_pair(100.f, 50.f));
builder.lineTo(std::make_pair(50.f , 100.f));
builder.lineTo(std::make_pair(450.f, 500.f));
builder.lineTo(std::make_pair(50.f , 900.f));
builder.lineTo(std::make_pair(100.f, 950.f));
builder.closePath();
return builder.takeResult();
}
template<typename T>
const T& itemForGlyph(uint16_t glyph, const T& boxCharString, const T& checkCharString, const T& xCharString)
{
if (!glyph)
return boxCharString;
if (glyph == 1)
return checkCharString;
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(Type type)
{
if (type == Type::OpenType)
name = "FontWithFeaturesOTF";
else
name = "FontWithFeaturesTTF";
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."
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)."
append16(numTables * 16 - searchRange); // rangeShift: "NumTables x 16-searchRange."
assert(result.size() == headerSize);
// Leave space for the directory entries.
for (size_t i = 0; i < directoryEntrySize * numTables; ++i)
result.push_back(0);
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);
assert(numTables == m_tablesAppendedCount);
// 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);
}
private:
class Placeholder {
public:
Placeholder(Generator& generator, size_t baseOfOffset)
: generator(generator)
, baseOfOffset(baseOfOffset)
, location(generator.result.size())
{
generator.append16(0);
}
Placeholder(Placeholder&& other)
: generator(other.generator)
, baseOfOffset(other.baseOfOffset)
, location(other.location)
, active(other.active)
{
other.active = false;
}
void populate()
{
assert(active);
size_t delta = generator.result.size() - baseOfOffset;
assert(delta < std::numeric_limits<uint16_t>::max());
generator.overwrite16(location, delta);
active = false;
}
~Placeholder()
{
assert(!active);
}
private:
Generator& generator;
const size_t baseOfOffset;
const size_t location;
bool active { true };
};
Placeholder placeholder(size_t baseOfOffset)
{
return Placeholder(*this, baseOfOffset);
}
void append16(uint16_t value)
{
result.push_back(value >> 8);
result.push_back(value);
}
void append32(uint32_t value)
{
::append32(result, value);
}
void append32BitCode(const char code[4])
{
result.push_back(code[0]);
result.push_back(code[1]);
result.push_back(code[2]);
result.push_back(code[3]);
}
void overwrite16(size_t location, uint16_t value)
{
assert(result.size() >= location + 2);
result[location] = value >> 8;
result[location + 1] = value;
}
void overwrite32(size_t location, uint32_t value)
{
assert(result.size() >= location + 4);
result[location] = value >> 24;
result[location + 1] = value >> 16;
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()
{
auto startingOffset = result.size();
// Header
result.push_back(1); // Major version
result.push_back(0); // Minor version
result.push_back(4); // Header size
result.push_back(4); // Offsets within CFF table are 4 bytes long
// Name INDEX
std::string fontName = name;
append16(1); // INDEX contains 1 element
result.push_back(4); // Offsets in this INDEX are 4 bytes long
append32(1); // 1-index offset of name data
append32(static_cast<uint32_t>(fontName.length() + 1)); // 1-index offset just past end of name data
for (char c : fontName)
result.push_back(c);
const char operand32Bit = 29;
const char fullNameKey = 2;
const char familyNameKey = 3;
const char fontBBoxKey = 5;
const char charsetIndexKey = 15;
const char charstringsIndexKey = 17;
const char privateDictIndexKey = 18;
const uint32_t userDefinedStringStartIndex = 391;
const unsigned sizeOfTopIndex = 56;
// Top DICT INDEX.
append16(1); // INDEX contains 1 element
result.push_back(4); // Offsets in this INDEX are 4 bytes long
append32(1); // 1-index offset of DICT data
append32(1 + sizeOfTopIndex); // 1-index offset just past end of DICT data
// DICT information
size_t topDictStart = result.size();
result.push_back(operand32Bit);
append32(userDefinedStringStartIndex);
result.push_back(fullNameKey);
result.push_back(operand32Bit);
append32(userDefinedStringStartIndex);
result.push_back(familyNameKey);
result.push_back(operand32Bit);
append32(clampTo<int32_t>(0)); // Bounding box x
result.push_back(operand32Bit);
append32(clampTo<int32_t>(-1)); // Bounding box y
result.push_back(operand32Bit);
append32(clampTo<int32_t>(unitsPerEm)); // Bounding box max x
result.push_back(operand32Bit);
append32(clampTo<int32_t>(unitsPerEm)); // Bounding box max y
result.push_back(fontBBoxKey);
result.push_back(operand32Bit);
size_t charsetOffsetLocation = result.size();
append32(0); // Offset of Charset info. Will be overwritten later.
result.push_back(charsetIndexKey);
result.push_back(operand32Bit);
size_t charstringsOffsetLocation = result.size();
append32(0); // Offset of CharStrings INDEX. Will be overwritten later.
result.push_back(charstringsIndexKey);
result.push_back(operand32Bit);
append32(0); // 0-sized private dict
result.push_back(operand32Bit);
append32(0); // no location for private dict
result.push_back(privateDictIndexKey); // Private dict size and offset
assert(result.size() == topDictStart + sizeOfTopIndex);
// String INDEX
append16(1); // Number of elements in INDEX
result.push_back(4); // Offsets in this INDEX are 4 bytes long
uint32_t offset = 1;
append32(offset);
offset += fontName.length();
append32(offset);
for (char c : fontName)
result.push_back(c);
append16(0); // Empty subroutine INDEX
// Charset info
overwrite32(charsetOffsetLocation, static_cast<uint32_t>(result.size() - startingOffset));
result.push_back(0);
for (int i = 1; i < numGlyphs; ++i)
append16(i);
// CharStrings INDEX
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);
result.push_back(4); // Offsets in this INDEX are 4 bytes long
offset = 1;
append32(offset);
for (uint16_t glyph = 0; glyph < numGlyphs; ++glyph) {
offset += itemForGlyph(glyph, boxCharString, checkCharString, xCharString).size();
append32(offset);
}
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 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);
auto subtableLocation = result.size();
append16(2); // Format 2
append16(0); // Placeholder for offset to coverage table, relative to beginning of substitution table
append16(1); // GlyphCount
append16(replacedWithMe); // Substitute with this glyph.
// Coverage table
overwrite16(subtableLocation + 2, result.size() - subtableLocation);
append16(1); // CoverageFormat
append16(1); // GlyphCount
append16(iGetReplaced); // This glyph is covered in the coverage.
}
void appendScriptSubtable(uint16_t featureCount)
{
auto dfltScriptTableLocation = result.size();
append16(0); // Placeholder for offset of default language system table, relative to beginning of Script table
append16(0); // Number of following language system tables
// LangSys table
overwrite16(dfltScriptTableLocation, result.size() - dfltScriptTableLocation);
append16(0); // LookupOrder "= NULL ... reserved"
append16(0xFFFF); // No features are required
append16(featureCount); // Number of FeatureIndex values
for (uint16_t i = 0; i < featureCount; ++i)
append16(i); // Features indices
}
struct Feature {
bool operator<(const Feature& o) const
{
return tag < o.tag;
}
std::array<char, 5> tag;
uint16_t glyphToReplace;
};
void appendGSUBTable()
{
std::vector<Feature> features {{{"liga"}, 3}, {{"clig"}, 4}, {{"dlig"}, 5}, {{"hlig"}, 6}, {{"calt"}, 7}, {{"subs"}, 8}, {{"sups"}, 9}, {{"smcp"}, 10}, {{"c2sc"}, 11}, {{"pcap"}, 12}, {{"c2pc"}, 13}, {{"unic"}, 14}, {{"titl"}, 15}, {{"lnum"}, 16}, {{"onum"}, 17}, {{"pnum"}, 18}, {{"tnum"}, 19}, {{"frac"}, 20}, {{"afrc"}, 21}, {{"ordn"}, 22}, {{"zero"}, 23}, {{"hist"}, 24}, {{"jp78"}, 25}, {{"jp83"}, 26}, {{"jp90"}, 27}, {{"jp04"}, 28}, {{"smpl"}, 29}, {{"trad"}, 30}, {{"fwid"}, 31}, {{"pwid"}, 32}, {{"ruby"}, 33}};
std::sort(features.begin(), features.end());
auto tableLocation = result.size();
auto headerSize = 10;
append32(0x00010000); // Version
append16(headerSize); // Offset to ScriptList
Placeholder toFeatureList = placeholder(tableLocation);
Placeholder toLookupList = placeholder(tableLocation);
assert(tableLocation + headerSize == result.size());
// ScriptList
auto scriptListLocation = result.size();
append16(1); // Number of ScriptRecords
append32BitCode("DFLT");
append16(0); // Placeholder for offset of Script table, relative to beginning of ScriptList
overwrite16(scriptListLocation + 6, result.size() - scriptListLocation);
appendScriptSubtable(static_cast<uint16_t>(features.size()));
// FeatureList
toFeatureList.populate();
auto featureListLocation = result.size();
size_t featureListSize = 2 + 6 * features.size();
size_t featureTableSize = 6;
append16(features.size()); // FeatureCount
for (unsigned i = 0; i < features.size(); ++i) {
auto& code = features[i];
append32BitCode(code.tag.data()); // Feature name
append16(featureListSize + featureTableSize * i); // Offset of feature table, relative to beginning of FeatureList table
}
assert(featureListLocation + featureListSize == result.size());
for (unsigned i = 0; i < features.size(); ++i) {
auto featureTableStart = result.size();
append16(0); // FeatureParams "= NULL ... reserved"
append16(1); // LookupCount
append16(i); // LookupListIndex
assert(featureTableStart + featureTableSize == result.size());
}
// LookupList
toLookupList.populate();
auto lookupListLocation = result.size();
append16(features.size()); // LookupCount
for (unsigned i = 0; i < features.size(); ++i)
append16(0); // Placeholder for offset to lookup table, relative to beginning of LookupList
size_t subtableRecordLocations[features.size()];
for (unsigned i = 0; i < features.size(); ++i) {
subtableRecordLocations[i] = result.size();
overwrite16(lookupListLocation + 2 + 2 * i, result.size() - lookupListLocation);
append16(1); // Type 1: "Replace one glyph with one glyph"
append16(0); // LookupFlag
append16(1); // SubTableCount
append16(0); // Placeholder for offset to subtable, relative to beginning of Lookup table
}
for (unsigned i = 0; i < features.size(); ++i)
appendSubstitutionSubtable(subtableRecordLocations[i], features[i].glyphToReplace, 1);
}
void appendOS2Table()
{
append16(2); // Version
append16(clampTo<int16_t>(unitsPerEm)); // Average advance
append16(clampTo<uint16_t>(500)); // Weight class
append16(5); // Width class
append16(0); // Protected font
// WebKit handles these superscripts and subscripts
append16(0); // Subscript X Size
append16(0); // Subscript Y Size
append16(0); // Subscript X Offset
append16(0); // Subscript Y Offset
append16(0); // Superscript X Size
append16(0); // Superscript Y Size
append16(0); // Superscript X Offset
append16(0); // Superscript Y Offset
append16(0); // Strikeout width
append16(0); // Strikeout Position
append16(0); // No classification
for (int i = 0; i < 10; ++i)
result.push_back(0);
for (int i = 0; i < 4; ++i)
append32(0); // "Bit assignments are pending. Set to 0"
append32(0x544B4257); // Font Vendor. "WBKT"
append16(0); // Font Patterns.
append16(0); // First unicode index
append16(0xFFFF); // Last unicode index
append16(clampTo<int16_t>(unitsPerEm)); // Typographical ascender
append16(clampTo<int16_t>(-1)); // Typographical descender
append16(clampTo<int16_t>(unitsPerEm / 10)); // Typographical line gap
append16(clampTo<uint16_t>(unitsPerEm)); // Windows-specific ascent
append16(clampTo<uint16_t>(0)); // Windows-specific descent
append32(0xFF10FC07); // Bitmask for supported codepages (Part 1). Report all pages as supported.
append32(0x0000FFFF); // Bitmask for supported codepages (Part 2). Report all pages as supported.
append16(clampTo<int16_t>(unitsPerEm / 2)); // x-height
append16(clampTo<int16_t>(unitsPerEm)); // Cap-height
append16(0); // Default char
append16(' '); // Break character
append16(3); // Maximum context needed to perform font features
append16(3); // Smallest optical point size
append16(0xFFFF); // Largest optical point size
}
void appendFormat12CMAPTable()
{
// Braindead scheme: One segment for each character
auto subtableLocation = result.size();
append32(12 << 16); // Format 12
append32(0); // Placeholder for byte length
append32(0); // Language independent
append32(2); // nGroups
append32('A'); // startCharCode
append32('Z'); // endCharCode
append32(1); // startGlyphCode
append32('a'); // startCharCode
append32('z'); // endCharCode
append32(27); // startGlyphCode
overwrite32(subtableLocation + 4, static_cast<uint32_t>(result.size() - subtableLocation));
}
void appendFormat4CMAPTable()
{
auto subtableLocation = result.size();
append16(4); // Format 4
append16(0); // Placeholder for length in bytes
append16(0); // Language independent
uint16_t segCount = 3;
append16(clampTo<uint16_t>(2 * segCount)); // segCountX2: "2 x segCount"
uint16_t originalSearchRange = roundDownToPowerOfTwo(segCount);
uint16_t searchRange = clampTo<uint16_t>(2 * originalSearchRange); // searchRange: "2 x (2**floor(log2(segCount)))"
append16(searchRange);
append16(integralLog2(originalSearchRange)); // entrySelector: "log2(searchRange/2)"
append16(clampTo<uint16_t>((2 * segCount) - searchRange)); // rangeShift: "2 x segCount - searchRange"
// Ending character codes
append16('Z');
append16('z');
append16(0xFFFF);
append16(0); // reserved
// Starting character codes
append16('A');
append16('a');
append16(0xFFFF);
// idDelta
append16(static_cast<uint16_t>(27) - static_cast<uint16_t>('A'));
append16(static_cast<uint16_t>(1) - static_cast<uint16_t>('a'));
append16(0x0001);
// idRangeOffset
append16(0); // idRangeOffset
append16(0);
append16(0);
// Fonts strive to hold 2^16 glyphs, but with the current encoding scheme, we write 8 bytes per codepoint into this subtable.
// Because the size of this subtable must be represented as a 16-bit number, we are limiting the number of glyphs we support to 2^13.
// FIXME: If we hit this limit in the wild, use a more compact encoding scheme for this subtable.
overwrite16(subtableLocation + 2, clampTo<uint16_t>(result.size() - subtableLocation));
}
void appendCMAPTable()
{
auto startingOffset = result.size();
append16(0);
append16(3); // Number of subtables
append16(0); // Unicode
append16(3); // Unicode version 2.2+
append32(28); // Byte offset of subtable
append16(3); // Microsoft
append16(1); // Unicode BMP
auto format4OffsetLocation = result.size();
append32(0); // Byte offset of subtable
append16(3); // Microsoft
append16(10); // Unicode
append32(28); // Byte offset of subtable
appendFormat12CMAPTable();
overwrite32(format4OffsetLocation, static_cast<uint32_t>(result.size() - startingOffset));
appendFormat4CMAPTable();
}
void appendHEADTable()
{
append32(0x00010000); // Version
append32(0x00010000); // Revision
append32(0); // Checksum placeholder; to be overwritten by the caller.
append32(0x5F0F3CF5); // Magic number.
append16((1 << 9) | 1);
append16(unitsPerEm);
append32(0); // First half of creation date
append32(0); // Last half of creation date
append32(0); // First half of modification date
append32(0); // Last half of modification date
append16(clampTo<int16_t>(0)); // bounding box x
append16(clampTo<int16_t>(-1)); // bounding box y
append16(clampTo<int16_t>(unitsPerEm)); // bounding box max x
append16(clampTo<int16_t>(unitsPerEm)); // bounding box max y
append16(0); // Traits
append16(3); // Smallest readable size in pixels
append16(0); // Might contain LTR or RTL glyphs
append16(1); // Long offsets in the 'loca' table.
append16(0); // Glyph data format
}
void appendHHEATable()
{
append32(0x00010000); // Version
append16(clampTo<int16_t>(unitsPerEm)); // ascent
append16(clampTo<int16_t>(-1)); // descent
// WebKit SVG font rendering has hard coded the line gap to be 1/10th of the font size since 2008 (see r29719).
append16(clampTo<int16_t>(unitsPerEm / 10)); // line gap
append16(clampTo<uint16_t>(unitsPerEm)); // advance width max
append16(clampTo<int16_t>(0)); // Minimum left side bearing
append16(clampTo<int16_t>(0)); // Minimum right side bearing
append16(clampTo<int16_t>(unitsPerEm)); // X maximum extent
// Since WebKit draws the caret and ignores the following values, it doesn't matter what we set them to.
append16(1); // Vertical caret
append16(0); // Vertical caret
append16(0); // "Set value to 0 for non-slanted fonts"
append32(0); // Reserved
append32(0); // Reserved
append16(0); // Current format
append16(numGlyphs); // Number of advance widths in HMTX table
}
void appendHMTXTable()
{
for (unsigned i = 0; i < numGlyphs; ++i) {
append16(clampTo<uint16_t>(static_cast<int32_t>(unitsPerEm))); // horizontal advance
append16(itemForGlyph(i, 200, 200, 50)); // left side bearing
}
}
void appendMAXPTable()
{
append32(0x00010000); // Version
append16(numGlyphs); // Number of glyphs
append16(0xFFFF); // Maximum number of points in non-compound glyph
append16(0xFFFF); // Maximum number of contours in non-compound glyph
append16(0xFFFF); // Maximum number of points in compound glyph
append16(0xFFFF); // Maximum number of contours in compound glyph
append16(2); // Maximum number of zones
append16(0); // Maximum number of points used in zone 0
append16(0); // Maximum number of storage area locations
append16(0); // Maximum number of function definitions
append16(0); // Maximum number of instruction definitions
append16(0); // Maximum stack depth
append16(0); // Maximum size of instructions
append16(numGlyphs); // Maximum number of glyphs referenced at top level
append16(0); // No compound glyphs
}
struct NameRecord {
bool operator<(const NameRecord& o) const
{
return nameIdentifier < o.nameIdentifier;
}
uint16_t nameIdentifier;
std::string value;
};
void appendNameSubtable(const NameRecord& nameRecord)
{
append16(0); // Unicode
append16(3); // Unicode version 2.0 or later
append16(0); // Language
append16(nameRecord.nameIdentifier); // Name identifier
append16(nameRecord.value.length() * 2); // Code units get 2 bytes each
append16(m_nameOffset); // Offset into name data
m_nameOffset += nameRecord.value.size() * 2; // Code units get 2 bytes each
}
void append2ByteASCIIString(std::string& s)
{
for (auto codeUnit : s)
append16(codeUnit);
}
void appendNAMETable()
{
std::string familyName = name; // 1: Font Family
std::string version = "1.0";
uint16_t numberOfRecords = m_stringIndex + 6;
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
std::vector<NameRecord> nameRecords = { { 1, familyName }, { 2, familyName }, { 3, familyName }, { 4, familyName }, { 5, version }, { 6, familyName } };
for (FeatureType& type : featureDescription) {
nameRecords.push_back({ static_cast<uint16_t>(m_baseStringIndex + type.stringIndex), type.name });
for (FeatureSelector& selector : type.selectors)
nameRecords.push_back({ static_cast<uint16_t>(m_baseStringIndex + selector.stringIndex), selector.name });
}
std::sort(nameRecords.begin(), nameRecords.end());
for (auto& nameRecord : nameRecords)
appendNameSubtable(nameRecord);
for (auto& nameRecord : nameRecords)
append2ByteASCIIString(nameRecord.value);
}
void appendPOSTTable()
{
append32(0x00030000); // Format. Printing is undefined
append32(0); // Italic angle in degrees
append16(0); // Underline position
append16(0); // Underline thickness
append32(0); // Monospaced
append32(0); // "Minimum memory usage when a TrueType font is downloaded as a Type 42 font"
append32(0); // "Maximum memory usage when a TrueType font is downloaded as a Type 42 font"
append32(0); // "Minimum memory usage when a TrueType font is downloaded as a Type 1 font"
append32(0); // "Maximum memory usage when a TrueType font is downloaded as a Type 1 font"
}
uint32_t calculateChecksum(size_t startingOffset, size_t endingOffset) const
{
assert(isFourByteAligned(endingOffset - startingOffset));
uint32_t sum = 0;
for (size_t offset = startingOffset; offset < endingOffset; offset += 4) {
sum += static_cast<unsigned char>(result[offset + 3])
| (static_cast<unsigned char>(result[offset + 2]) << 8)
| (static_cast<unsigned char>(result[offset + 1]) << 16)
| (static_cast<unsigned char>(result[offset]) << 24);
}
return sum;
}
typedef void (Generator::*FontAppendingFunction)();
void appendTable(const char identifier[4], FontAppendingFunction appendingFunction)
{
size_t offset = result.size();
assert(isFourByteAligned(offset));
(this->*appendingFunction)();
size_t unpaddedSize = result.size() - offset;
while (!isFourByteAligned(result.size()))
result.push_back(0);
assert(isFourByteAligned(result.size()));
size_t directoryEntryOffset = headerSize + m_tablesAppendedCount * directoryEntrySize;
result[directoryEntryOffset] = identifier[0];
result[directoryEntryOffset + 1] = identifier[1];
result[directoryEntryOffset + 2] = identifier[2];
result[directoryEntryOffset + 3] = identifier[3];
overwrite32(directoryEntryOffset + 4, calculateChecksum(offset, result.size()));
overwrite32(directoryEntryOffset + 8, static_cast<uint32_t>(offset));
overwrite32(directoryEntryOffset + 12, static_cast<uint32_t>(unpaddedSize));
++m_tablesAppendedCount;
}
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::string name;
};
std::vector<uint8_t> generateFont(Type type)
{
return Generator().generate(type);
}