SVGToOTFFontConversion.cpp [plain text]
#include "config.h"
#include "SVGToOTFFontConversion.h"
#if ENABLE(SVG_FONTS)
#include "CSSStyleDeclaration.h"
#include "ElementChildIterator.h"
#include "Glyph.h"
#include "SVGFontElement.h"
#include "SVGFontFaceElement.h"
#include "SVGGlyphElement.h"
#include "SVGHKernElement.h"
#include "SVGMissingGlyphElement.h"
#include "SVGPathParser.h"
#include "SVGPathStringSource.h"
#include "SVGVKernElement.h"
#include <wtf/Optional.h>
#include <wtf/Vector.h>
#include <wtf/text/StringView.h>
namespace WebCore {
template <typename V>
static inline void append32(V& result, uint32_t value)
{
result.append(value >> 24);
result.append(value >> 16);
result.append(value >> 8);
result.append(value);
}
class SVGToOTFFontConverter {
public:
SVGToOTFFontConverter(const SVGFontElement&);
bool convertSVGToOTFFont();
Vector<char> releaseResult()
{
return WTFMove(m_result);
}
bool error() const
{
return m_error;
}
private:
struct GlyphData {
GlyphData(Vector<char>&& charString, const SVGGlyphElement* glyphElement, float horizontalAdvance, float verticalAdvance, FloatRect boundingBox, const String& codepoints)
: boundingBox(boundingBox)
, charString(charString)
, codepoints(codepoints)
, glyphElement(glyphElement)
, horizontalAdvance(horizontalAdvance)
, verticalAdvance(verticalAdvance)
{
}
FloatRect boundingBox;
Vector<char> charString;
String codepoints;
const SVGGlyphElement* glyphElement;
float horizontalAdvance;
float verticalAdvance;
};
class Placeholder {
public:
Placeholder(SVGToOTFFontConverter& converter, size_t baseOfOffset)
: m_converter(converter)
, m_baseOfOffset(baseOfOffset)
, m_location(m_converter.m_result.size())
{
m_converter.append16(0);
}
Placeholder(Placeholder&& other)
: m_converter(other.m_converter)
, m_baseOfOffset(other.m_baseOfOffset)
, m_location(other.m_location)
#if !ASSERT_DISABLED
, m_active(other.m_active)
#endif
{
#if !ASSERT_DISABLED
other.m_active = false;
#endif
}
void populate()
{
ASSERT(m_active);
size_t delta = m_converter.m_result.size() - m_baseOfOffset;
ASSERT(delta < std::numeric_limits<uint16_t>::max());
m_converter.overwrite16(m_location, delta);
#if !ASSERT_DISABLED
m_active = false;
#endif
}
~Placeholder()
{
ASSERT(!m_active);
}
private:
SVGToOTFFontConverter& m_converter;
const size_t m_baseOfOffset;
const size_t m_location;
#if !ASSERT_DISABLED
bool m_active = { true };
#endif
};
struct KerningData {
KerningData(uint16_t glyph1, uint16_t glyph2, int16_t adjustment)
: glyph1(glyph1)
, glyph2(glyph2)
, adjustment(adjustment)
{
}
uint16_t glyph1;
uint16_t glyph2;
int16_t adjustment;
};
Placeholder placeholder(size_t baseOfOffset)
{
return Placeholder(*this, baseOfOffset);
}
void append32(uint32_t value)
{
WebCore::append32(m_result, value);
}
void append32BitCode(const char code[4])
{
m_result.append(code[0]);
m_result.append(code[1]);
m_result.append(code[2]);
m_result.append(code[3]);
}
void append16(uint16_t value)
{
m_result.append(value >> 8);
m_result.append(value);
}
void grow(size_t delta)
{
m_result.grow(m_result.size() + delta);
}
void overwrite32(unsigned location, uint32_t value)
{
ASSERT(m_result.size() >= location + 4);
m_result[location] = value >> 24;
m_result[location + 1] = value >> 16;
m_result[location + 2] = value >> 8;
m_result[location + 3] = value;
}
void overwrite16(unsigned location, uint16_t value)
{
ASSERT(m_result.size() >= location + 2);
m_result[location] = value >> 8;
m_result[location + 1] = value;
}
static const size_t headerSize = 12;
static const size_t directoryEntrySize = 16;
uint32_t calculateChecksum(size_t startingOffset, size_t endingOffset) const;
void processGlyphElement(const SVGElement& glyphOrMissingGlyphElement, const SVGGlyphElement*, float defaultHorizontalAdvance, float defaultVerticalAdvance, const String& codepoints, Optional<FloatRect>& boundingBox);
typedef void (SVGToOTFFontConverter::*FontAppendingFunction)();
void appendTable(const char identifier[4], FontAppendingFunction);
void appendFormat12CMAPTable(const Vector<std::pair<UChar32, Glyph>>& codepointToGlyphMappings);
void appendFormat4CMAPTable(const Vector<std::pair<UChar32, Glyph>>& codepointToGlyphMappings);
void appendCMAPTable();
void appendGSUBTable();
void appendHEADTable();
void appendHHEATable();
void appendHMTXTable();
void appendVHEATable();
void appendVMTXTable();
void appendKERNTable();
void appendMAXPTable();
void appendNAMETable();
void appendOS2Table();
void appendPOSTTable();
void appendCFFTable();
void appendVORGTable();
void appendLigatureGlyphs();
static bool compareCodepointsLexicographically(const GlyphData&, const GlyphData&);
void appendValidCFFString(const String&);
Vector<char> transcodeGlyphPaths(float width, const SVGElement& glyphOrMissingGlyphElement, Optional<FloatRect>& boundingBox) const;
void addCodepointRanges(const UnicodeRanges&, HashSet<Glyph>& glyphSet) const;
void addCodepoints(const HashSet<String>& codepoints, HashSet<Glyph>& glyphSet) const;
void addGlyphNames(const HashSet<String>& glyphNames, HashSet<Glyph>& glyphSet) const;
void addKerningPair(Vector<KerningData>&, const SVGKerningPair&) const;
template<typename T> size_t appendKERNSubtable(bool (T::*buildKerningPair)(SVGKerningPair&) const, uint16_t coverage);
size_t finishAppendingKERNSubtable(Vector<KerningData>, uint16_t coverage);
void appendLigatureSubtable(size_t subtableRecordLocation);
void appendArabicReplacementSubtable(size_t subtableRecordLocation, const char arabicForm[]);
void appendScriptSubtable(unsigned featureCount);
Vector<Glyph, 1> glyphsForCodepoint(UChar32) const;
Glyph firstGlyph(const Vector<Glyph, 1>&, UChar32) const;
template<typename T> T scaleUnitsPerEm(T value) const
{
return value * s_outputUnitsPerEm / m_inputUnitsPerEm;
}
Vector<GlyphData> m_glyphs;
HashMap<String, Glyph> m_glyphNameToIndexMap; HashMap<String, Vector<Glyph, 1>> m_codepointsToIndicesMap;
Vector<char> m_result;
Vector<char, 17> m_emptyGlyphCharString;
FloatRect m_boundingBox;
const SVGFontElement& m_fontElement;
const SVGFontFaceElement* m_fontFaceElement;
const SVGMissingGlyphElement* m_missingGlyphElement;
String m_fontFamily;
float m_advanceWidthMax;
float m_advanceHeightMax;
float m_minRightSideBearing;
static const unsigned s_outputUnitsPerEm = 1000;
unsigned m_inputUnitsPerEm;
int m_lineGap;
int m_xHeight;
int m_capHeight;
int m_ascent;
int m_descent;
unsigned m_featureCountGSUB;
unsigned m_tablesAppendedCount;
uint8_t m_weight;
bool m_italic;
bool m_error { false };
};
static uint16_t roundDownToPowerOfTwo(uint16_t x)
{
x |= x >> 1;
x |= x >> 2;
x |= x >> 4;
x |= x >> 8;
return (x >> 1) + 1;
}
static uint16_t integralLog2(uint16_t x)
{
uint16_t result = 0;
while (x >>= 1)
++result;
return result;
}
void SVGToOTFFontConverter::appendFormat12CMAPTable(const Vector<std::pair<UChar32, Glyph>>& mappings)
{
ASSERT(m_glyphs.size() < 0xFFFF);
auto subtableLocation = m_result.size();
append32(12 << 16); append32(0); append32(0); append32(0); for (auto& mapping : mappings) {
append32(mapping.first); append32(mapping.first); append32(mapping.second); }
overwrite32(subtableLocation + 4, m_result.size() - subtableLocation);
overwrite32(subtableLocation + 12, mappings.size());
}
void SVGToOTFFontConverter::appendFormat4CMAPTable(const Vector<std::pair<UChar32, Glyph>>& bmpMappings)
{
auto subtableLocation = m_result.size();
append16(4); append16(0); append16(0); uint16_t segCount = bmpMappings.size() + 1;
append16(clampTo<uint16_t>(2 * segCount)); uint16_t originalSearchRange = roundDownToPowerOfTwo(segCount);
uint16_t searchRange = clampTo<uint16_t>(2 * originalSearchRange); append16(searchRange);
append16(integralLog2(originalSearchRange)); append16(clampTo<uint16_t>((2 * segCount) - searchRange));
for (auto& mapping : bmpMappings)
append16(mapping.first); append16(0xFFFF);
append16(0);
for (auto& mapping : bmpMappings)
append16(mapping.first); append16(0xFFFF);
for (auto& mapping : bmpMappings)
append16(static_cast<uint16_t>(mapping.second) - static_cast<uint16_t>(mapping.first)); append16(0x0001);
for (size_t i = 0; i < bmpMappings.size(); ++i)
append16(0); append16(0);
overwrite16(subtableLocation + 2, clampTo<uint16_t>(m_result.size() - subtableLocation));
}
void SVGToOTFFontConverter::appendCMAPTable()
{
auto startingOffset = m_result.size();
append16(0);
append16(3);
append16(0); append16(3); append32(28);
append16(3); append16(1); auto format4OffsetLocation = m_result.size();
append32(0);
append16(3); append16(10); append32(28);
Vector<std::pair<UChar32, Glyph>> mappings;
UChar32 previousCodepoint = std::numeric_limits<UChar32>::max();
for (size_t i = 0; i < m_glyphs.size(); ++i) {
auto& glyph = m_glyphs[i];
UChar32 codepoint;
auto codePoints = StringView(glyph.codepoints).codePoints();
auto iterator = codePoints.begin();
if (iterator == codePoints.end())
codepoint = 0;
else {
codepoint = *iterator;
++iterator;
if (iterator != codePoints.end() || codepoint == previousCodepoint)
continue;
}
mappings.append(std::make_pair(codepoint, Glyph(i)));
previousCodepoint = codepoint;
}
appendFormat12CMAPTable(mappings);
Vector<std::pair<UChar32, Glyph>> bmpMappings;
for (auto& mapping : mappings) {
if (mapping.first < 0x10000)
bmpMappings.append(mapping);
}
overwrite32(format4OffsetLocation, m_result.size() - startingOffset);
appendFormat4CMAPTable(bmpMappings);
}
void SVGToOTFFontConverter::appendHEADTable()
{
append32(0x00010000); append32(0x00010000); append32(0); append32(0x5F0F3CF5); append16((1 << 9) | 1);
append16(s_outputUnitsPerEm);
append32(0); append32(0); append32(0); append32(0); append16(clampTo<int16_t>(m_boundingBox.x()));
append16(clampTo<int16_t>(m_boundingBox.y()));
append16(clampTo<int16_t>(m_boundingBox.maxX()));
append16(clampTo<int16_t>(m_boundingBox.maxY()));
append16((m_italic ? 1 << 1 : 0) | (m_weight >= 7 ? 1 : 0));
append16(3); append16(0); append16(0); append16(0); }
void SVGToOTFFontConverter::appendHHEATable()
{
append32(0x00010000); append16(clampTo<int16_t>(m_ascent));
append16(clampTo<int16_t>(-m_descent));
append16(clampTo<int16_t>(m_lineGap));
append16(clampTo<uint16_t>(m_advanceWidthMax));
append16(clampTo<int16_t>(m_boundingBox.x())); append16(clampTo<int16_t>(m_minRightSideBearing)); append16(clampTo<int16_t>(m_boundingBox.maxX())); append16(1); append16(0); append16(0); append32(0); append32(0); append16(0); append16(m_glyphs.size()); }
void SVGToOTFFontConverter::appendHMTXTable()
{
for (auto& glyph : m_glyphs) {
append16(clampTo<uint16_t>(glyph.horizontalAdvance));
append16(clampTo<int16_t>(glyph.boundingBox.x()));
}
}
void SVGToOTFFontConverter::appendMAXPTable()
{
append32(0x00010000); append16(m_glyphs.size());
append16(0xFFFF); append16(0xFFFF); append16(0xFFFF); append16(0xFFFF); append16(2); append16(0); append16(0); append16(0); append16(0); append16(0); append16(0); append16(m_glyphs.size()); append16(0); }
void SVGToOTFFontConverter::appendNAMETable()
{
append16(0); append16(1); append16(18);
append16(0); append16(3); append16(0); append16(1); append16(m_fontFamily.length() * 2);
append16(0);
for (auto codeUnit : StringView(m_fontFamily).codeUnits())
append16(codeUnit);
}
void SVGToOTFFontConverter::appendOS2Table()
{
int16_t averageAdvance = s_outputUnitsPerEm;
bool ok;
int value = m_fontElement.attributeWithoutSynchronization(SVGNames::horiz_adv_xAttr).toInt(&ok);
if (!ok && m_missingGlyphElement)
value = m_missingGlyphElement->attributeWithoutSynchronization(SVGNames::horiz_adv_xAttr).toInt(&ok);
value = scaleUnitsPerEm(value);
if (ok)
averageAdvance = clampTo<int16_t>(value);
append16(2); append16(clampTo<int16_t>(averageAdvance));
append16(m_weight); append16(5); append16(0); append16(0); append16(0); append16(0); append16(0); append16(0); append16(0); append16(0); append16(0); append16(0); append16(0); append16(0);
unsigned numPanoseBytes = 0;
const unsigned panoseSize = 10;
char panoseBytes[panoseSize];
if (m_fontFaceElement) {
Vector<String> segments = m_fontFaceElement->attributeWithoutSynchronization(SVGNames::panose_1Attr).string().split(' ');
if (segments.size() == panoseSize) {
for (auto& segment : segments) {
bool ok;
int value = segment.toInt(&ok);
if (ok && value >= std::numeric_limits<uint8_t>::min() && value <= std::numeric_limits<uint8_t>::max())
panoseBytes[numPanoseBytes++] = value;
}
}
}
if (numPanoseBytes != panoseSize)
memset(panoseBytes, 0, panoseSize);
m_result.append(panoseBytes, panoseSize);
for (int i = 0; i < 4; ++i)
append32(0); append32(0x544B4257); append16((m_weight >= 7 ? 1 << 5 : 0) | (m_italic ? 1 : 0)); append16(0); append16(0xFFFF); append16(clampTo<int16_t>(m_ascent)); append16(clampTo<int16_t>(-m_descent)); append16(clampTo<int16_t>(m_lineGap)); append16(clampTo<uint16_t>(m_ascent)); append16(clampTo<uint16_t>(m_descent)); append32(0xFF10FC07); append32(0x0000FFFF); append16(clampTo<int16_t>(m_xHeight)); append16(clampTo<int16_t>(m_capHeight)); append16(0); append16(' '); append16(3); append16(3); append16(0xFFFF); }
void SVGToOTFFontConverter::appendPOSTTable()
{
append32(0x00030000); append32(0); append16(0); append16(0); append32(0); append32(0); append32(0); append32(0); append32(0); }
static bool isValidStringForCFF(const String& string)
{
for (auto c : StringView(string).codeUnits()) {
if (c < 33 || c > 126)
return false;
}
return true;
}
void SVGToOTFFontConverter::appendValidCFFString(const String& string)
{
ASSERT(isValidStringForCFF(string));
for (auto c : StringView(string).codeUnits())
m_result.append(c);
}
void SVGToOTFFontConverter::appendCFFTable()
{
auto startingOffset = m_result.size();
m_result.append(1); m_result.append(0); m_result.append(4); m_result.append(4);
String fontName;
if (m_fontFaceElement) {
String potentialFontName = m_fontFamily;
if (isValidStringForCFF(potentialFontName))
fontName = potentialFontName;
}
append16(1); m_result.append(4); append32(1); append32(fontName.length() + 1); appendValidCFFString(fontName);
String weight;
if (m_fontFaceElement) {
auto& potentialWeight = m_fontFaceElement->attributeWithoutSynchronization(SVGNames::font_weightAttr);
if (isValidStringForCFF(potentialWeight))
weight = potentialWeight;
}
bool hasWeight = !weight.isNull();
const char operand32Bit = 29;
const char fullNameKey = 2;
const char familyNameKey = 3;
const char weightKey = 4;
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 + (hasWeight ? 6 : 0);
append16(1); m_result.append(4); append32(1); append32(1 + sizeOfTopIndex);
#if !ASSERT_DISABLED
unsigned topDictStart = m_result.size();
#endif
m_result.append(operand32Bit);
append32(userDefinedStringStartIndex);
m_result.append(fullNameKey);
m_result.append(operand32Bit);
append32(userDefinedStringStartIndex);
m_result.append(familyNameKey);
if (hasWeight) {
m_result.append(operand32Bit);
append32(userDefinedStringStartIndex + 2);
m_result.append(weightKey);
}
m_result.append(operand32Bit);
append32(clampTo<int32_t>(m_boundingBox.x()));
m_result.append(operand32Bit);
append32(clampTo<int32_t>(m_boundingBox.y()));
m_result.append(operand32Bit);
append32(clampTo<int32_t>(m_boundingBox.width()));
m_result.append(operand32Bit);
append32(clampTo<int32_t>(m_boundingBox.height()));
m_result.append(fontBBoxKey);
m_result.append(operand32Bit);
unsigned charsetOffsetLocation = m_result.size();
append32(0); m_result.append(charsetIndexKey);
m_result.append(operand32Bit);
unsigned charstringsOffsetLocation = m_result.size();
append32(0); m_result.append(charstringsIndexKey);
m_result.append(operand32Bit);
append32(0); m_result.append(operand32Bit);
append32(0); m_result.append(privateDictIndexKey); ASSERT(m_result.size() == topDictStart + sizeOfTopIndex);
String unknownCharacter = "UnknownChar"_s;
append16(2 + (hasWeight ? 1 : 0)); m_result.append(4); uint32_t offset = 1;
append32(offset);
offset += fontName.length();
append32(offset);
offset += unknownCharacter.length();
append32(offset);
if (hasWeight) {
offset += weight.length();
append32(offset);
}
appendValidCFFString(fontName);
appendValidCFFString(unknownCharacter);
appendValidCFFString(weight);
append16(0);
overwrite32(charsetOffsetLocation, m_result.size() - startingOffset);
m_result.append(0);
for (Glyph i = 1; i < m_glyphs.size(); ++i)
append16(userDefinedStringStartIndex + 1);
overwrite32(charstringsOffsetLocation, m_result.size() - startingOffset);
append16(m_glyphs.size());
m_result.append(4); offset = 1;
append32(offset);
for (auto& glyph : m_glyphs) {
offset += glyph.charString.size();
append32(offset);
}
for (auto& glyph : m_glyphs)
m_result.appendVector(glyph.charString);
}
Glyph SVGToOTFFontConverter::firstGlyph(const Vector<Glyph, 1>& v, UChar32 codepoint) const
{
#if ASSERT_DISABLED
UNUSED_PARAM(codepoint);
#endif
ASSERT(!v.isEmpty());
if (v.isEmpty())
return 0;
#if !ASSERT_DISABLED
auto codePoints = StringView(m_glyphs[v[0]].codepoints).codePoints();
auto codePointsIterator = codePoints.begin();
ASSERT(codePointsIterator != codePoints.end());
ASSERT(codepoint == *codePointsIterator);
#endif
return v[0];
}
void SVGToOTFFontConverter::appendLigatureSubtable(size_t subtableRecordLocation)
{
typedef std::pair<Vector<Glyph, 3>, Glyph> LigaturePair;
Vector<LigaturePair> ligaturePairs;
for (Glyph glyphIndex = 0; glyphIndex < m_glyphs.size(); ++glyphIndex) {
ligaturePairs.append(LigaturePair(Vector<Glyph, 3>(), glyphIndex));
Vector<Glyph, 3>& ligatureGlyphs = ligaturePairs.last().first;
auto codePoints = StringView(m_glyphs[glyphIndex].codepoints).codePoints();
for (auto codePoint : codePoints)
ligatureGlyphs.append(firstGlyph(glyphsForCodepoint(codePoint), codePoint));
if (ligatureGlyphs.size() < 2)
ligaturePairs.removeLast();
}
if (ligaturePairs.size() > std::numeric_limits<uint16_t>::max())
ligaturePairs.clear();
std::sort(ligaturePairs.begin(), ligaturePairs.end(), [](auto& lhs, auto& rhs) {
return lhs.first[0] < rhs.first[0];
});
Vector<size_t> overlappingFirstGlyphSegmentLengths;
if (!ligaturePairs.isEmpty()) {
Glyph previousFirstGlyph = ligaturePairs[0].first[0];
size_t segmentStart = 0;
for (size_t i = 0; i < ligaturePairs.size(); ++i) {
auto& ligaturePair = ligaturePairs[i];
if (ligaturePair.first[0] != previousFirstGlyph) {
overlappingFirstGlyphSegmentLengths.append(i - segmentStart);
segmentStart = i;
previousFirstGlyph = ligaturePairs[0].first[0];
}
}
overlappingFirstGlyphSegmentLengths.append(ligaturePairs.size() - segmentStart);
}
overwrite16(subtableRecordLocation + 6, m_result.size() - subtableRecordLocation);
auto subtableLocation = m_result.size();
append16(1); append16(0); append16(ligaturePairs.size()); grow(overlappingFirstGlyphSegmentLengths.size() * 2);
Vector<size_t> ligatureSetTableLocations;
for (size_t i = 0; i < overlappingFirstGlyphSegmentLengths.size(); ++i) {
overwrite16(subtableLocation + 6 + 2 * i, m_result.size() - subtableLocation);
ligatureSetTableLocations.append(m_result.size());
append16(overlappingFirstGlyphSegmentLengths[i]); grow(overlappingFirstGlyphSegmentLengths[i] * 2); }
ASSERT(ligatureSetTableLocations.size() == overlappingFirstGlyphSegmentLengths.size());
size_t ligaturePairIndex = 0;
for (size_t i = 0; i < overlappingFirstGlyphSegmentLengths.size(); ++i) {
for (size_t j = 0; j < overlappingFirstGlyphSegmentLengths[i]; ++j) {
overwrite16(ligatureSetTableLocations[i] + 2 + 2 * j, m_result.size() - ligatureSetTableLocations[i]);
auto& ligaturePair = ligaturePairs[ligaturePairIndex];
append16(ligaturePair.second);
append16(ligaturePair.first.size());
for (size_t k = 1; k < ligaturePair.first.size(); ++k)
append16(ligaturePair.first[k]);
++ligaturePairIndex;
}
}
ASSERT(ligaturePairIndex == ligaturePairs.size());
overwrite16(subtableLocation + 2, m_result.size() - subtableLocation);
append16(1); append16(ligatureSetTableLocations.size()); ligaturePairIndex = 0;
for (auto segmentLength : overlappingFirstGlyphSegmentLengths) {
auto& ligaturePair = ligaturePairs[ligaturePairIndex];
ASSERT(ligaturePair.first.size() > 1);
append16(ligaturePair.first[0]);
ligaturePairIndex += segmentLength;
}
}
void SVGToOTFFontConverter::appendArabicReplacementSubtable(size_t subtableRecordLocation, const char arabicForm[])
{
Vector<std::pair<Glyph, Glyph>> arabicFinalReplacements;
for (auto& pair : m_codepointsToIndicesMap) {
for (auto glyphIndex : pair.value) {
auto& glyph = m_glyphs[glyphIndex];
if (glyph.glyphElement && equalIgnoringASCIICase(glyph.glyphElement->attributeWithoutSynchronization(SVGNames::arabic_formAttr), arabicForm))
arabicFinalReplacements.append(std::make_pair(pair.value[0], glyphIndex));
}
}
if (arabicFinalReplacements.size() > std::numeric_limits<uint16_t>::max())
arabicFinalReplacements.clear();
overwrite16(subtableRecordLocation + 6, m_result.size() - subtableRecordLocation);
auto subtableLocation = m_result.size();
append16(2); Placeholder toCoverageTable = placeholder(subtableLocation);
append16(arabicFinalReplacements.size()); for (auto& pair : arabicFinalReplacements)
append16(pair.second);
toCoverageTable.populate();
append16(1); append16(arabicFinalReplacements.size()); for (auto& pair : arabicFinalReplacements)
append16(pair.first);
}
void SVGToOTFFontConverter::appendScriptSubtable(unsigned featureCount)
{
auto dfltScriptTableLocation = m_result.size();
append16(0); append16(0);
overwrite16(dfltScriptTableLocation, m_result.size() - dfltScriptTableLocation);
append16(0); append16(0xFFFF); append16(featureCount); for (uint16_t i = 0; i < featureCount; ++i)
append16(m_featureCountGSUB++); }
void SVGToOTFFontConverter::appendGSUBTable()
{
auto tableLocation = m_result.size();
auto headerSize = 10;
append32(0x00010000); append16(headerSize); Placeholder toFeatureList = placeholder(tableLocation);
Placeholder toLookupList = placeholder(tableLocation);
ASSERT(tableLocation + headerSize == m_result.size());
auto scriptListLocation = m_result.size();
append16(2); append32BitCode("DFLT");
append16(0); append32BitCode("arab");
append16(0);
overwrite16(scriptListLocation + 6, m_result.size() - scriptListLocation);
appendScriptSubtable(1);
overwrite16(scriptListLocation + 12, m_result.size() - scriptListLocation);
appendScriptSubtable(4);
const unsigned featureCount = 5;
toFeatureList.populate();
auto featureListLocation = m_result.size();
size_t featureListSize = 2 + 6 * featureCount;
size_t featureTableSize = 6;
append16(featureCount); append32BitCode("liga");
append16(featureListSize + featureTableSize * 0); append32BitCode("fina");
append16(featureListSize + featureTableSize * 1); append32BitCode("medi");
append16(featureListSize + featureTableSize * 2); append32BitCode("init");
append16(featureListSize + featureTableSize * 3); append32BitCode("rlig");
append16(featureListSize + featureTableSize * 4); ASSERT_UNUSED(featureListLocation, featureListLocation + featureListSize == m_result.size());
for (unsigned i = 0; i < featureCount; ++i) {
auto featureTableStart = m_result.size();
append16(0); append16(1); append16(i); ASSERT_UNUSED(featureTableStart, featureTableStart + featureTableSize == m_result.size());
}
toLookupList.populate();
auto lookupListLocation = m_result.size();
append16(featureCount); for (unsigned i = 0; i < featureCount; ++i)
append16(0); size_t subtableRecordLocations[featureCount];
for (unsigned i = 0; i < featureCount; ++i) {
subtableRecordLocations[i] = m_result.size();
overwrite16(lookupListLocation + 2 + 2 * i, m_result.size() - lookupListLocation);
switch (i) {
case 4:
append16(3); break;
case 0:
append16(4); break;
default:
append16(1); break;
}
append16(0); append16(1); append16(0); }
appendLigatureSubtable(subtableRecordLocations[0]);
appendArabicReplacementSubtable(subtableRecordLocations[1], "terminal");
appendArabicReplacementSubtable(subtableRecordLocations[2], "medial");
appendArabicReplacementSubtable(subtableRecordLocations[3], "initial");
overwrite16(subtableRecordLocations[4] + 6, m_result.size() - subtableRecordLocations[4]);
append16(1); append16(6); append16(0); append16(1); append16(0); }
void SVGToOTFFontConverter::appendVORGTable()
{
append16(1); append16(0);
bool ok;
int defaultVerticalOriginY = m_fontElement.attributeWithoutSynchronization(SVGNames::vert_origin_yAttr).toInt(&ok);
if (!ok && m_missingGlyphElement)
defaultVerticalOriginY = m_missingGlyphElement->attributeWithoutSynchronization(SVGNames::vert_origin_yAttr).toInt();
defaultVerticalOriginY = scaleUnitsPerEm(defaultVerticalOriginY);
append16(clampTo<int16_t>(defaultVerticalOriginY));
auto tableSizeOffset = m_result.size();
append16(0); for (Glyph i = 0; i < m_glyphs.size(); ++i) {
if (auto* glyph = m_glyphs[i].glyphElement) {
if (int verticalOriginY = glyph->attributeWithoutSynchronization(SVGNames::vert_origin_yAttr).toInt()) {
append16(i);
append16(clampTo<int16_t>(scaleUnitsPerEm(verticalOriginY)));
}
}
}
ASSERT(!((m_result.size() - tableSizeOffset - 2) % 4));
overwrite16(tableSizeOffset, (m_result.size() - tableSizeOffset - 2) / 4);
}
void SVGToOTFFontConverter::appendVHEATable()
{
float height = m_ascent + m_descent;
append32(0x00011000); append16(clampTo<int16_t>(height / 2)); append16(clampTo<int16_t>(-static_cast<int>(height / 2))); append16(clampTo<int16_t>(s_outputUnitsPerEm / 10)); append16(clampTo<int16_t>(m_advanceHeightMax));
append16(clampTo<int16_t>(s_outputUnitsPerEm - m_boundingBox.maxY())); append16(clampTo<int16_t>(m_boundingBox.y())); append16(clampTo<int16_t>(s_outputUnitsPerEm - m_boundingBox.y())); append16(1); append16(0); append16(0); append32(0); append32(0); append16(0); append16(m_glyphs.size()); }
void SVGToOTFFontConverter::appendVMTXTable()
{
for (auto& glyph : m_glyphs) {
append16(clampTo<uint16_t>(glyph.verticalAdvance));
append16(clampTo<int16_t>(s_outputUnitsPerEm - glyph.boundingBox.maxY())); }
}
static String codepointToString(UChar32 codepoint)
{
UChar buffer[2];
uint8_t length = 0;
UBool error = false;
U16_APPEND(buffer, length, 2, codepoint, error);
return error ? String() : String(buffer, length);
}
Vector<Glyph, 1> SVGToOTFFontConverter::glyphsForCodepoint(UChar32 codepoint) const
{
return m_codepointsToIndicesMap.get(codepointToString(codepoint));
}
void SVGToOTFFontConverter::addCodepointRanges(const UnicodeRanges& unicodeRanges, HashSet<Glyph>& glyphSet) const
{
for (auto& unicodeRange : unicodeRanges) {
for (auto codepoint = unicodeRange.first; codepoint <= unicodeRange.second; ++codepoint) {
for (auto index : glyphsForCodepoint(codepoint))
glyphSet.add(index);
}
}
}
void SVGToOTFFontConverter::addCodepoints(const HashSet<String>& codepoints, HashSet<Glyph>& glyphSet) const
{
for (auto& codepointString : codepoints) {
for (auto index : m_codepointsToIndicesMap.get(codepointString))
glyphSet.add(index);
}
}
void SVGToOTFFontConverter::addGlyphNames(const HashSet<String>& glyphNames, HashSet<Glyph>& glyphSet) const
{
for (auto& glyphName : glyphNames) {
if (Glyph glyph = m_glyphNameToIndexMap.get(glyphName))
glyphSet.add(glyph);
}
}
void SVGToOTFFontConverter::addKerningPair(Vector<KerningData>& data, const SVGKerningPair& kerningPair) const
{
HashSet<Glyph> glyphSet1;
HashSet<Glyph> glyphSet2;
addCodepointRanges(kerningPair.unicodeRange1, glyphSet1);
addCodepointRanges(kerningPair.unicodeRange2, glyphSet2);
addGlyphNames(kerningPair.glyphName1, glyphSet1);
addGlyphNames(kerningPair.glyphName2, glyphSet2);
addCodepoints(kerningPair.unicodeName1, glyphSet1);
addCodepoints(kerningPair.unicodeName2, glyphSet2);
for (auto& glyph1 : glyphSet1) {
for (auto& glyph2 : glyphSet2)
data.append(KerningData(glyph1, glyph2, clampTo<int16_t>(-scaleUnitsPerEm(kerningPair.kerning))));
}
}
template<typename T> inline size_t SVGToOTFFontConverter::appendKERNSubtable(bool (T::*buildKerningPair)(SVGKerningPair&) const, uint16_t coverage)
{
Vector<KerningData> kerningData;
for (auto& element : childrenOfType<T>(m_fontElement)) {
SVGKerningPair kerningPair;
if ((element.*buildKerningPair)(kerningPair))
addKerningPair(kerningData, kerningPair);
}
return finishAppendingKERNSubtable(WTFMove(kerningData), coverage);
}
size_t SVGToOTFFontConverter::finishAppendingKERNSubtable(Vector<KerningData> kerningData, uint16_t coverage)
{
std::sort(kerningData.begin(), kerningData.end(), [](auto& a, auto& b) {
return a.glyph1 < b.glyph1 || (a.glyph1 == b.glyph1 && a.glyph2 < b.glyph2);
});
size_t sizeOfKerningDataTable = 14 + 6 * kerningData.size();
if (sizeOfKerningDataTable > std::numeric_limits<uint16_t>::max()) {
kerningData.clear();
sizeOfKerningDataTable = 14;
}
append16(0); append16(sizeOfKerningDataTable); append16(coverage);
uint16_t roundedNumKerningPairs = roundDownToPowerOfTwo(kerningData.size());
append16(kerningData.size());
append16(roundedNumKerningPairs * 6); append16(integralLog2(roundedNumKerningPairs)); append16((kerningData.size() - roundedNumKerningPairs) * 6);
for (auto& kerningDataElement : kerningData) {
append16(kerningDataElement.glyph1);
append16(kerningDataElement.glyph2);
append16(kerningDataElement.adjustment);
}
return sizeOfKerningDataTable;
}
void SVGToOTFFontConverter::appendKERNTable()
{
append16(0); append16(2);
#if !ASSERT_DISABLED
auto subtablesOffset = m_result.size();
#endif
size_t sizeOfHorizontalSubtable = appendKERNSubtable<SVGHKernElement>(&SVGHKernElement::buildHorizontalKerningPair, 1);
ASSERT_UNUSED(sizeOfHorizontalSubtable, subtablesOffset + sizeOfHorizontalSubtable == m_result.size());
size_t sizeOfVerticalSubtable = appendKERNSubtable<SVGVKernElement>(&SVGVKernElement::buildVerticalKerningPair, 0);
ASSERT_UNUSED(sizeOfVerticalSubtable, subtablesOffset + sizeOfHorizontalSubtable + sizeOfVerticalSubtable == m_result.size());
}
template <typename V>
static void writeCFFEncodedNumber(V& vector, float number)
{
vector.append(0xFF);
append32(vector, clampTo<int32_t>(number * 0x10000));
}
static const char rLineTo = 0x05;
static const char rrCurveTo = 0x08;
static const char endChar = 0x0e;
static const char rMoveTo = 0x15;
class CFFBuilder final : public SVGPathConsumer {
public:
CFFBuilder(Vector<char>& cffData, float width, FloatPoint origin, float unitsPerEmScalar)
: m_cffData(cffData)
, m_unitsPerEmScalar(unitsPerEmScalar)
{
writeCFFEncodedNumber(m_cffData, std::floor(width)); writeCFFEncodedNumber(m_cffData, origin.x());
writeCFFEncodedNumber(m_cffData, origin.y());
m_cffData.append(rMoveTo);
}
Optional<FloatRect> boundingBox() const
{
return m_boundingBox;
}
private:
void updateBoundingBox(FloatPoint point)
{
if (!m_boundingBox) {
m_boundingBox = FloatRect(point, FloatSize());
return;
}
m_boundingBox.value().extend(point);
}
void writePoint(FloatPoint destination)
{
updateBoundingBox(destination);
FloatSize delta = destination - m_current;
writeCFFEncodedNumber(m_cffData, delta.width());
writeCFFEncodedNumber(m_cffData, delta.height());
m_current = destination;
}
void moveTo(const FloatPoint& targetPoint, bool closed, PathCoordinateMode mode) final
{
if (closed && !m_cffData.isEmpty())
closePath();
FloatPoint scaledTargetPoint = FloatPoint(targetPoint.x() * m_unitsPerEmScalar, targetPoint.y() * m_unitsPerEmScalar);
FloatPoint destination = mode == AbsoluteCoordinates ? scaledTargetPoint : m_current + scaledTargetPoint;
writePoint(destination);
m_cffData.append(rMoveTo);
m_startingPoint = m_current;
}
void unscaledLineTo(const FloatPoint& targetPoint)
{
writePoint(targetPoint);
m_cffData.append(rLineTo);
}
void lineTo(const FloatPoint& targetPoint, PathCoordinateMode mode) final
{
FloatPoint scaledTargetPoint = FloatPoint(targetPoint.x() * m_unitsPerEmScalar, targetPoint.y() * m_unitsPerEmScalar);
FloatPoint destination = mode == AbsoluteCoordinates ? scaledTargetPoint : m_current + scaledTargetPoint;
unscaledLineTo(destination);
}
void curveToCubic(const FloatPoint& point1, const FloatPoint& point2, const FloatPoint& point3, PathCoordinateMode mode) final
{
FloatPoint scaledPoint1 = FloatPoint(point1.x() * m_unitsPerEmScalar, point1.y() * m_unitsPerEmScalar);
FloatPoint scaledPoint2 = FloatPoint(point2.x() * m_unitsPerEmScalar, point2.y() * m_unitsPerEmScalar);
FloatPoint scaledPoint3 = FloatPoint(point3.x() * m_unitsPerEmScalar, point3.y() * m_unitsPerEmScalar);
if (mode == RelativeCoordinates) {
scaledPoint1 += m_current;
scaledPoint2 += m_current;
scaledPoint3 += m_current;
}
writePoint(scaledPoint1);
writePoint(scaledPoint2);
writePoint(scaledPoint3);
m_cffData.append(rrCurveTo);
}
void closePath() final
{
if (m_current != m_startingPoint)
unscaledLineTo(m_startingPoint);
}
void incrementPathSegmentCount() final { }
bool continueConsuming() final { return true; }
void lineToHorizontal(float, PathCoordinateMode) final { ASSERT_NOT_REACHED(); }
void lineToVertical(float, PathCoordinateMode) final { ASSERT_NOT_REACHED(); }
void curveToCubicSmooth(const FloatPoint&, const FloatPoint&, PathCoordinateMode) final { ASSERT_NOT_REACHED(); }
void curveToQuadratic(const FloatPoint&, const FloatPoint&, PathCoordinateMode) final { ASSERT_NOT_REACHED(); }
void curveToQuadraticSmooth(const FloatPoint&, PathCoordinateMode) final { ASSERT_NOT_REACHED(); }
void arcTo(float, float, float, bool, bool, const FloatPoint&, PathCoordinateMode) final { ASSERT_NOT_REACHED(); }
Vector<char>& m_cffData;
FloatPoint m_startingPoint;
FloatPoint m_current;
Optional<FloatRect> m_boundingBox;
float m_unitsPerEmScalar;
};
Vector<char> SVGToOTFFontConverter::transcodeGlyphPaths(float width, const SVGElement& glyphOrMissingGlyphElement, Optional<FloatRect>& boundingBox) const
{
Vector<char> result;
auto& dAttribute = glyphOrMissingGlyphElement.attributeWithoutSynchronization(SVGNames::dAttr);
if (dAttribute.isEmpty()) {
writeCFFEncodedNumber(result, width);
writeCFFEncodedNumber(result, 0);
writeCFFEncodedNumber(result, 0);
result.append(rMoveTo);
result.append(endChar);
return result;
}
bool ok;
float horizontalOriginX = scaleUnitsPerEm(glyphOrMissingGlyphElement.attributeWithoutSynchronization(SVGNames::horiz_origin_xAttr).toFloat(&ok));
if (!ok && m_fontFaceElement)
horizontalOriginX = scaleUnitsPerEm(m_fontFaceElement->horizontalOriginX());
float horizontalOriginY = scaleUnitsPerEm(glyphOrMissingGlyphElement.attributeWithoutSynchronization(SVGNames::horiz_origin_yAttr).toFloat(&ok));
if (!ok && m_fontFaceElement)
horizontalOriginY = scaleUnitsPerEm(m_fontFaceElement->horizontalOriginY());
CFFBuilder builder(result, width, FloatPoint(horizontalOriginX, horizontalOriginY), static_cast<float>(s_outputUnitsPerEm) / m_inputUnitsPerEm);
SVGPathStringSource source(dAttribute);
ok = SVGPathParser::parse(source, builder);
if (!ok)
return { };
boundingBox = builder.boundingBox();
result.append(endChar);
return result;
}
void SVGToOTFFontConverter::processGlyphElement(const SVGElement& glyphOrMissingGlyphElement, const SVGGlyphElement* glyphElement, float defaultHorizontalAdvance, float defaultVerticalAdvance, const String& codepoints, Optional<FloatRect>& boundingBox)
{
bool ok;
float horizontalAdvance = scaleUnitsPerEm(glyphOrMissingGlyphElement.attributeWithoutSynchronization(SVGNames::horiz_adv_xAttr).toFloat(&ok));
if (!ok)
horizontalAdvance = defaultHorizontalAdvance;
m_advanceWidthMax = std::max(m_advanceWidthMax, horizontalAdvance);
float verticalAdvance = scaleUnitsPerEm(glyphOrMissingGlyphElement.attributeWithoutSynchronization(SVGNames::vert_adv_yAttr).toFloat(&ok));
if (!ok)
verticalAdvance = defaultVerticalAdvance;
m_advanceHeightMax = std::max(m_advanceHeightMax, verticalAdvance);
Optional<FloatRect> glyphBoundingBox;
auto path = transcodeGlyphPaths(horizontalAdvance, glyphOrMissingGlyphElement, glyphBoundingBox);
if (!path.size()) {
m_error = true;
}
if (!boundingBox)
boundingBox = glyphBoundingBox;
else if (glyphBoundingBox)
boundingBox.value().unite(glyphBoundingBox.value());
if (glyphBoundingBox)
m_minRightSideBearing = std::min(m_minRightSideBearing, horizontalAdvance - glyphBoundingBox.value().maxX());
m_glyphs.append(GlyphData(WTFMove(path), glyphElement, horizontalAdvance, verticalAdvance, glyphBoundingBox.valueOr(FloatRect()), codepoints));
}
void SVGToOTFFontConverter::appendLigatureGlyphs()
{
HashSet<UChar32> ligatureCodepoints;
HashSet<UChar32> nonLigatureCodepoints;
for (auto& glyph : m_glyphs) {
auto codePoints = StringView(glyph.codepoints).codePoints();
auto codePointsIterator = codePoints.begin();
if (codePointsIterator == codePoints.end())
continue;
UChar32 codepoint = *codePointsIterator;
++codePointsIterator;
if (codePointsIterator == codePoints.end())
nonLigatureCodepoints.add(codepoint);
else {
ligatureCodepoints.add(codepoint);
for (; codePointsIterator != codePoints.end(); ++codePointsIterator)
ligatureCodepoints.add(*codePointsIterator);
}
}
for (auto codepoint : nonLigatureCodepoints)
ligatureCodepoints.remove(codepoint);
for (auto codepoint : ligatureCodepoints) {
auto codepoints = codepointToString(codepoint);
if (!codepoints.isNull())
m_glyphs.append(GlyphData(Vector<char>(m_emptyGlyphCharString), nullptr, s_outputUnitsPerEm, s_outputUnitsPerEm, FloatRect(), codepoints));
}
}
bool SVGToOTFFontConverter::compareCodepointsLexicographically(const GlyphData& data1, const GlyphData& data2)
{
auto codePoints1 = StringView(data1.codepoints).codePoints();
auto codePoints2 = StringView(data2.codepoints).codePoints();
auto iterator1 = codePoints1.begin();
auto iterator2 = codePoints2.begin();
while (iterator1 != codePoints1.end() && iterator2 != codePoints2.end()) {
UChar32 codepoint1, codepoint2;
codepoint1 = *iterator1;
codepoint2 = *iterator2;
if (codepoint1 < codepoint2)
return true;
if (codepoint1 > codepoint2)
return false;
++iterator1;
++iterator2;
}
if (iterator1 == codePoints1.end() && iterator2 == codePoints2.end()) {
bool firstIsIsolated = data1.glyphElement && equalLettersIgnoringASCIICase(data1.glyphElement->attributeWithoutSynchronization(SVGNames::arabic_formAttr), "isolated");
bool secondIsIsolated = data2.glyphElement && equalLettersIgnoringASCIICase(data2.glyphElement->attributeWithoutSynchronization(SVGNames::arabic_formAttr), "isolated");
return firstIsIsolated && !secondIsIsolated;
}
return iterator1 == codePoints1.end();
}
static void populateEmptyGlyphCharString(Vector<char, 17>& o, unsigned unitsPerEm)
{
writeCFFEncodedNumber(o, unitsPerEm);
writeCFFEncodedNumber(o, 0);
writeCFFEncodedNumber(o, 0);
o.append(rMoveTo);
o.append(endChar);
}
SVGToOTFFontConverter::SVGToOTFFontConverter(const SVGFontElement& fontElement)
: m_fontElement(fontElement)
, m_fontFaceElement(childrenOfType<SVGFontFaceElement>(m_fontElement).first())
, m_missingGlyphElement(childrenOfType<SVGMissingGlyphElement>(m_fontElement).first())
, m_advanceWidthMax(0)
, m_advanceHeightMax(0)
, m_minRightSideBearing(std::numeric_limits<float>::max())
, m_featureCountGSUB(0)
, m_tablesAppendedCount(0)
, m_weight(5)
, m_italic(false)
{
if (!m_fontFaceElement) {
m_inputUnitsPerEm = 1;
m_ascent = s_outputUnitsPerEm;
m_descent = 1;
m_xHeight = s_outputUnitsPerEm;
m_capHeight = m_ascent;
} else {
m_inputUnitsPerEm = m_fontFaceElement->unitsPerEm();
m_ascent = scaleUnitsPerEm(m_fontFaceElement->ascent());
m_descent = scaleUnitsPerEm(m_fontFaceElement->descent());
m_xHeight = scaleUnitsPerEm(m_fontFaceElement->xHeight());
m_capHeight = scaleUnitsPerEm(m_fontFaceElement->capHeight());
if (!m_ascent)
m_ascent = 1;
if (!m_descent)
m_descent = 1;
}
float defaultHorizontalAdvance = m_fontFaceElement ? scaleUnitsPerEm(m_fontFaceElement->horizontalAdvanceX()) : 0;
float defaultVerticalAdvance = m_fontFaceElement ? scaleUnitsPerEm(m_fontFaceElement->verticalAdvanceY()) : 0;
m_lineGap = s_outputUnitsPerEm / 10;
populateEmptyGlyphCharString(m_emptyGlyphCharString, s_outputUnitsPerEm);
Optional<FloatRect> boundingBox;
if (m_missingGlyphElement)
processGlyphElement(*m_missingGlyphElement, nullptr, defaultHorizontalAdvance, defaultVerticalAdvance, String(), boundingBox);
else {
m_glyphs.append(GlyphData(Vector<char>(m_emptyGlyphCharString), nullptr, s_outputUnitsPerEm, s_outputUnitsPerEm, FloatRect(), String()));
boundingBox = FloatRect(0, 0, s_outputUnitsPerEm, s_outputUnitsPerEm);
}
for (auto& glyphElement : childrenOfType<SVGGlyphElement>(m_fontElement)) {
auto& unicodeAttribute = glyphElement.attributeWithoutSynchronization(SVGNames::unicodeAttr);
if (!unicodeAttribute.isEmpty()) processGlyphElement(glyphElement, &glyphElement, defaultHorizontalAdvance, defaultVerticalAdvance, unicodeAttribute, boundingBox);
}
m_boundingBox = boundingBox.valueOr(FloatRect());
appendLigatureGlyphs();
if (m_glyphs.size() > std::numeric_limits<Glyph>::max()) {
m_glyphs.clear();
return;
}
std::sort(m_glyphs.begin(), m_glyphs.end(), &compareCodepointsLexicographically);
for (Glyph i = 0; i < m_glyphs.size(); ++i) {
GlyphData& glyph = m_glyphs[i];
if (glyph.glyphElement) {
auto& glyphName = glyph.glyphElement->attributeWithoutSynchronization(SVGNames::glyph_nameAttr);
if (!glyphName.isNull())
m_glyphNameToIndexMap.add(glyphName, i);
}
if (m_codepointsToIndicesMap.isValidKey(glyph.codepoints)) {
auto& glyphVector = m_codepointsToIndicesMap.add(glyph.codepoints, Vector<Glyph>()).iterator->value;
if (glyph.glyphElement && equalLettersIgnoringASCIICase(glyph.glyphElement->attributeWithoutSynchronization(SVGNames::arabic_formAttr), "isolated"))
glyphVector.insert(0, i);
else
glyphVector.append(i);
}
}
if (m_fontFaceElement) {
auto& fontWeightAttribute = m_fontFaceElement->attributeWithoutSynchronization(SVGNames::font_weightAttr);
for (auto segment : StringView(fontWeightAttribute).split(' ')) {
if (equalLettersIgnoringASCIICase(segment, "bold")) {
m_weight = 7;
break;
}
bool ok;
int value = segment.toInt(ok);
if (ok && value >= 0 && value < 1000) {
m_weight = std::max(std::min((value + 50) / 100, static_cast<int>(std::numeric_limits<uint8_t>::max())), static_cast<int>(std::numeric_limits<uint8_t>::min()));
break;
}
}
auto& fontStyleAttribute = m_fontFaceElement->attributeWithoutSynchronization(SVGNames::font_styleAttr);
for (auto segment : StringView(fontStyleAttribute).split(' ')) {
if (equalLettersIgnoringASCIICase(segment, "italic") || equalLettersIgnoringASCIICase(segment, "oblique")) {
m_italic = true;
break;
}
}
}
if (m_fontFaceElement)
m_fontFamily = m_fontFaceElement->fontFamily();
}
static inline bool isFourByteAligned(size_t x)
{
return !(x & 3);
}
uint32_t SVGToOTFFontConverter::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>(m_result[offset + 3])
| (static_cast<unsigned char>(m_result[offset + 2]) << 8)
| (static_cast<unsigned char>(m_result[offset + 1]) << 16)
| (static_cast<unsigned char>(m_result[offset]) << 24);
}
return sum;
}
void SVGToOTFFontConverter::appendTable(const char identifier[4], FontAppendingFunction appendingFunction)
{
size_t offset = m_result.size();
ASSERT(isFourByteAligned(offset));
(this->*appendingFunction)();
size_t unpaddedSize = m_result.size() - offset;
while (!isFourByteAligned(m_result.size()))
m_result.append(0);
ASSERT(isFourByteAligned(m_result.size()));
size_t directoryEntryOffset = headerSize + m_tablesAppendedCount * directoryEntrySize;
m_result[directoryEntryOffset] = identifier[0];
m_result[directoryEntryOffset + 1] = identifier[1];
m_result[directoryEntryOffset + 2] = identifier[2];
m_result[directoryEntryOffset + 3] = identifier[3];
overwrite32(directoryEntryOffset + 4, calculateChecksum(offset, m_result.size()));
overwrite32(directoryEntryOffset + 8, offset);
overwrite32(directoryEntryOffset + 12, unpaddedSize);
++m_tablesAppendedCount;
}
bool SVGToOTFFontConverter::convertSVGToOTFFont()
{
if (m_glyphs.isEmpty())
return false;
uint16_t numTables = 14;
uint16_t roundedNumTables = roundDownToPowerOfTwo(numTables);
uint16_t searchRange = roundedNumTables * 16;
m_result.append('O');
m_result.append('T');
m_result.append('T');
m_result.append('O');
append16(numTables);
append16(searchRange);
append16(integralLog2(roundedNumTables)); append16(numTables * 16 - searchRange);
ASSERT(m_result.size() == headerSize);
for (size_t i = 0; i < directoryEntrySize * numTables; ++i)
m_result.append(0);
appendTable("CFF ", &SVGToOTFFontConverter::appendCFFTable);
appendTable("GSUB", &SVGToOTFFontConverter::appendGSUBTable);
appendTable("OS/2", &SVGToOTFFontConverter::appendOS2Table);
appendTable("VORG", &SVGToOTFFontConverter::appendVORGTable);
appendTable("cmap", &SVGToOTFFontConverter::appendCMAPTable);
auto headTableOffset = m_result.size();
appendTable("head", &SVGToOTFFontConverter::appendHEADTable);
appendTable("hhea", &SVGToOTFFontConverter::appendHHEATable);
appendTable("hmtx", &SVGToOTFFontConverter::appendHMTXTable);
appendTable("kern", &SVGToOTFFontConverter::appendKERNTable);
appendTable("maxp", &SVGToOTFFontConverter::appendMAXPTable);
appendTable("name", &SVGToOTFFontConverter::appendNAMETable);
appendTable("post", &SVGToOTFFontConverter::appendPOSTTable);
appendTable("vhea", &SVGToOTFFontConverter::appendVHEATable);
appendTable("vmtx", &SVGToOTFFontConverter::appendVMTXTable);
ASSERT(numTables == m_tablesAppendedCount);
overwrite32(headTableOffset + 8, 0xB1B0AFBAU - calculateChecksum(0, m_result.size()));
return true;
}
Optional<Vector<char>> convertSVGToOTFFont(const SVGFontElement& element)
{
SVGToOTFFontConverter converter(element);
if (converter.error())
return WTF::nullopt;
if (!converter.convertSVGToOTFFont())
return WTF::nullopt;
return converter.releaseResult();
}
}
#endif // ENABLE(SVG_FONTS)